From: Chet Ramey Date: Mon, 9 Jan 2012 13:31:41 +0000 (-0500) Subject: commit bash-20111104 snapshot X-Git-Tag: bash-4.3-alpha~99 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=631b20c6099be340c921e94fb48f63df92f13be9;p=thirdparty%2Fbash.git commit bash-20111104 snapshot --- diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index 1ee709d6b..f5d6ceeda 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -12407,3 +12407,145 @@ make_cmd.c when breaking the string between the double parens into three separate components instead of a simple character loop. Fixes bug reported by Dan Douglas + + 11/2 + ---- +Makefile.in + - make libbuiltins.a depend on builtext.h to serialize its creation + and avoid conflict between multiple invocations of mkbuiltins. + Fix from Mike Frysinger + + 11/5 + ---- +findcmd.c + - user_command_matches: if stat(".", ...) returns -1, set st_dev + and st_ino fields in dotinfo to 0 to avoid same_file matches + - find_user_command_in_path: check stat(2) return the same way + +lib/glob/glob.c + - glob_vector: don't call strlen(pat) without checking pat == 0 + - glob_dir_to_array: make sure to free `result' and all allocated + members before returning error due to malloc failure + - glob_vector: make sure to free `nextname' and `npat' on errors + (mostly when setting lose = 1) + - glob_vector: if flags & GX_MATCHDIRS but not GX_ALLDIRS, make + sure we free `subdir' + - glob_filename: when expanding ** (GX_ALLDIRS), make sure we + free temp_results (return value from glob_vector) + +lib/glob/xmbsrtowcs.c + - xdupmbstowcs: fix call to realloc to use sizeof (char *) instead + of sizeof (char **) when assigning idxtmp + +execute_cmd.c + - print_index_and_element: return 0 right away if L == 0 + - is_dirname: fix memory leak by freeing `temp' + - time_command: don't try to deref NULL `command' when assigning + to `posix_time' + - shell_execve: null-terminate `sample' after READ_SAMPLE_BUF so it's + terminated for functions that expect that + +builtins/read.def + - read_builtin: don't call bind_read_variable with a potentially-null + string + +pcomplete.c + - gen_command_matches: don't call dispose_word_desc with a NULL arg + - gen_compspec_completions: fix memory leak by freeing `ret' before + calling gen_action_completions (tcs, ...). happens when + performing directory completion as default and no completions + have been generated + - gen_progcomp_completions: make sure to set foundp to 0 whenever + returning NULL + - it_init_aliases: fix memory leak by freeing alias_list before + returning + +bashline.c + - command_word_completion_function: don't call restore_tilde with a + NULL directory_part argument + - bash_directory_expansion: bugfix: don't throw away results of + rl_directory_rewrite_hook if it's set and returns non-zero + - bind_keyseq_to_unix_command: free `kseq' before returning error + +arrayfunc.c + - assign_array_element_internal: make sure `akey' is freed if non-null + before returning error + - assign_compound_array_list: free `akey' before returning error + - array_value_internal: free `akey' before returning error + - unbind_array_element: free `akey' before returning error + +subst.c + - array_length_reference: free `akey' before returning error in case + of expand_assignment_string_to_string error + - array_length_reference: free `akey' after call to assoc_reference + - skip_to_delim: if skipping process and command substitution, free + return value from extract_process_subst + - parameter_brace_substring: free `val' (vtype == VT_VARIABLE) before + returning if verify_substring_values fails + - parameter_brace_expand: remove two duplicate lines that allocate + ret in parameter_brace_substring case + - parameter_brace_expand: convert `free (name); name = xmalloc (...)' + to use `xrealloc (name, ...)' + - parameter_brace_expand: free `name' before returning when handling + ${!PREFIX*} expansion + - split_at_delims: fix memory leak by freeing `d2' before returning + +redir.c + - redirection_error: free `filename' if the redirection operator is + REDIR_VARASSIGN by assigning allocname + +eval.c + - send_pwd_to_eterm: fix memory leak by freeing value returned by + get_working_directory() + +builtins/cd.def + - change_to_directory: fix memory leak by freeing return value from + resetpwd() + - cd_builtin: fix memory leak by freeing value returned by dirspell() + - cd_builtin: fix memory leak by freeing `directory' if appropriate + before overwriting with return value from resetpwd() + +builtins/type.def + - describe_command: free `full_path' before overwriting it with return + value from sh_makepath + +builtins/complete.def + - compgen_builtin: fix memory leak by calling strlist_dispose (sl) + before overwriting sl with return value from completions_to_stringlist + +builtins/hash.def + - list_hashed_filename_targets: fix memory leak by freeing `target' + +make_cmd.c + - make_arith_for_command: free `init', `test', and `step' before + returning error on parse error + +jobs.c + - initialize_job_control: don't call move_to_high_fd if shell_tty == -1 + +general.c + - check_dev_tty: don't call close with an fd < 0 + - legal_number: deal with NULL `string' argument, return invalid + +lib/sh/fmtulong.c + - fmtulong: if the `base' argument is invalid, make sure we index + buf by `len-1' at maximum + +print_cmd.c + - print_deferred_heredocs: don't try to dereference a NULL `cstring' + - cprintf: make sure to call va_end (args) + +variables.c + - push_dollar_vars: fix call to xrealloc to use sizeof (WORD_LIST *) + instead of sizeof (WORD_LIST **) + +lib/sh/zmapfd.c + - zmapfd: if read returns error, free result and return -1 immediately + instead of trying to reallocate it + + 11/6 + ---- +execute_cmd.c + - cpl_reap: rewrote to avoid using pointer after freeing it; now builds + new coproc list on the fly while traversing the old one and sets the + right values for coproc_list when done diff --git a/CWRU/CWRU.chlog~ b/CWRU/CWRU.chlog~ index c284216af..6e2d1d05a 100644 --- a/CWRU/CWRU.chlog~ +++ b/CWRU/CWRU.chlog~ @@ -12399,3 +12399,146 @@ lib/readline/histexpand.c we assume that the double quote is followed by a character in history_no_expand_chars. tcsh and csh seem to do this. This answers a persistent complaint about history expansion + + 10/29 + ----- +make_cmd.c + - make_arith_for_command: use skip_to_delim to find the next `;' + when breaking the string between the double parens into three + separate components instead of a simple character loop. Fixes + bug reported by Dan Douglas + + 11/2 + ---- +Makefile.in + - make libbuiltins.a depend on builtext.h to serialize its creation + and avoid conflict between multiple invocations of mkbuiltins. + Fix from Mike Frysinger + + 11/5 + ---- +findcmd.c + - user_command_matches: if stat(".", ...) returns -1, set st_dev + and st_ino fields in dotinfo to 0 to avoid same_file matches + - find_user_command_in_path: check stat(2) return the same way + +lib/glob/glob.c + - glob_vector: don't call strlen(pat) without checking pat == 0 + - glob_dir_to_array: make sure to free `result' and all allocated + members before returning error due to malloc failure + - glob_vector: make sure to free `nextname' and `npat' on errors + (mostly when setting lose = 1) + - glob_vector: if flags & GX_MATCHDIRS but not GX_ALLDIRS, make + sure we free `subdir' + - glob_filename: when expanding ** (GX_ALLDIRS), make sure we + free temp_results (return value from glob_vector) + +lib/glob/xmbsrtowcs.c + - xdupmbstowcs: fix call to realloc to use sizeof (char *) instead + of sizeof (char **) when assigning idxtmp + +execute_cmd.c + - print_index_and_element: return 0 right away if L == 0 + - is_dirname: fix memory leak by freeing `temp' + - time_command: don't try to deref NULL `command' when assigning + to `posix_time' + - shell_execve: null-terminate `sample' after READ_SAMPLE_BUF so it's + terminated for functions that expect that + +builtins/read.def + - read_builtin: don't call bind_read_variable with a potentially-null + string + +pcomplete.c + - gen_command_matches: don't call dispose_word_desc with a NULL arg + - gen_compspec_completions: fix memory leak by freeing `ret' before + calling gen_action_completions (tcs, ...). happens when + performing directory completion as default and no completions + have been generated + - gen_progcomp_completions: make sure to set foundp to 0 whenever + returning NULL + - it_init_aliases: fix memory leak by freeing alias_list before + returning + +bashline.c + - command_word_completion_function: don't call restore_tilde with a + NULL directory_part argument + - bash_directory_expansion: bugfix: don't throw away results of + rl_directory_rewrite_hook if it's set and returns non-zero + - bind_keyseq_to_unix_command: free `kseq' before returning error + +arrayfunc.c + - assign_array_element_internal: make sure `akey' is freed if non-null + before returning error + - assign_compound_array_list: free `akey' before returning error + - array_value_internal: free `akey' before returning error + - unbind_array_element: free `akey' before returning error + +subst.c + - array_length_reference: free `akey' before returning error in case + of expand_assignment_string_to_string error + - array_length_reference: free `akey' after call to assoc_reference + - skip_to_delim: if skipping process and command substitution, free + return value from extract_process_subst + - parameter_brace_substring: free `val' (vtype == VT_VARIABLE) before + returning if verify_substring_values fails + - parameter_brace_expand: remove two duplicate lines that allocate + ret in parameter_brace_substring case + - parameter_brace_expand: convert `free (name); name = xmalloc (...)' + to use `xrealloc (name, ...)' + - parameter_brace_expand: free `name' before returning when handling + ${!PREFIX*} expansion + - split_at_delims: fix memory leak by freeing `d2' before returning + +redir.c + - redirection_error: free `filename' if the redirection operator is + REDIR_VARASSIGN by assigning allocname + +eval.c + - send_pwd_to_eterm: fix memory leak by freeing value returned by + get_working_directory() + +builtins/cd.def + - change_to_directory: fix memory leak by freeing return value from + resetpwd() + - cd_builtin: fix memory leak by freeing value returned by dirspell() + - cd_builtin: fix memory leak by freeing `directory' if appropriate + before overwriting with return value from resetpwd() + +builtins/type.def + - describe_command: free `full_path' before overwriting it with return + value from sh_makepath + +builtins/complete.def + - compgen_builtin: fix memory leak by calling strlist_dispose (sl) + before overwriting sl with return value from completions_to_stringlist + +builtins/hash.def + - list_hashed_filename_targets: fix memory leak by freeing `target' + +make_cmd.c + - make_arith_for_command: free `init', `test', and `step' before + returning error on parse error + +jobs.c + - initialize_job_control: don't call move_to_high_fd if shell_tty == -1 + +general.c + - check_dev_tty: don't call close with an fd < 0 + - legal_number: deal with NULL `string' argument, return invalid + +lib/sh/fmtulong.c + - fmtulong: if the `base' argument is invalid, make sure we index + buf by `len-1' at maximum + +print_cmd.c + - print_deferred_heredocs: don't try to dereference a NULL `cstring' + - cprintf: make sure to call va_end (args) + +variables.c + - push_dollar_vars: fix call to xrealloc to use sizeof (WORD_LIST *) + instead of sizeof (WORD_LIST **) + +lib/sh/zmapfd.c + - zmapfd: if read returns error, free result and return -1 immediately + instead of trying to reallocate it diff --git a/Makefile.in b/Makefile.in index b5a2193c2..2274fc054 100644 --- a/Makefile.in +++ b/Makefile.in @@ -672,7 +672,7 @@ syntax.c: mksyntax${EXEEXT} $(srcdir)/syntax.h $(RM) $@ ./mksyntax$(EXEEXT) -o $@ -$(BUILTINS_LIBRARY): $(BUILTIN_DEFS) $(BUILTIN_C_SRC) config.h ${BASHINCDIR}/memalloc.h version.h +$(BUILTINS_LIBRARY): $(BUILTIN_DEFS) $(BUILTIN_C_SRC) config.h ${BASHINCDIR}/memalloc.h $(DEFDIR)/builtext.h version.h @(cd $(DEFDIR) && $(MAKE) $(MFLAGS) DEBUG=${DEBUG} libbuiltins.a ) || exit 1 # these require special rules to circumvent make builtin rules diff --git a/alias.c b/alias.c index 6c953760f..783922965 100644 --- a/alias.c +++ b/alias.c @@ -110,7 +110,7 @@ add_alias (name, value) alias_t *temp; int n; - if (!aliases) + if (aliases == 0) { initialize_aliases (); temp = (alias_t *)NULL; diff --git a/alias.c~ b/alias.c~ new file mode 100644 index 000000000..6c953760f --- /dev/null +++ b/alias.c~ @@ -0,0 +1,580 @@ +/* alias.c -- Not a full alias, but just the kind that we use in the + shell. Csh style alias is somewhere else (`over there, in a box'). */ + +/* Copyright (C) 1987-2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (ALIAS) + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include "chartypes.h" +#include "bashansi.h" +#include "command.h" +#include "general.h" +#include "externs.h" +#include "alias.h" + +#if defined (PROGRAMMABLE_COMPLETION) +# include "pcomplete.h" +#endif + +#define ALIAS_HASH_BUCKETS 16 /* must be power of two */ + +typedef int sh_alias_map_func_t __P((alias_t *)); + +static void free_alias_data __P((PTR_T)); +static alias_t **map_over_aliases __P((sh_alias_map_func_t *)); +static void sort_aliases __P((alias_t **)); +static int qsort_alias_compare __P((alias_t **, alias_t **)); + +#if defined (READLINE) +static int skipquotes __P((char *, int)); +static int skipws __P((char *, int)); +static int rd_token __P((char *, int)); +#endif + +/* Non-zero means expand all words on the line. Otherwise, expand + after first expansion if the expansion ends in a space. */ +int alias_expand_all = 0; + +/* The list of aliases that we have. */ +HASH_TABLE *aliases = (HASH_TABLE *)NULL; + +void +initialize_aliases () +{ + if (aliases == 0) + aliases = hash_create (ALIAS_HASH_BUCKETS); +} + +/* Scan the list of aliases looking for one with NAME. Return NULL + if the alias doesn't exist, else a pointer to the alias_t. */ +alias_t * +find_alias (name) + char *name; +{ + BUCKET_CONTENTS *al; + + if (aliases == 0) + return ((alias_t *)NULL); + + al = hash_search (name, aliases, 0); + return (al ? (alias_t *)al->data : (alias_t *)NULL); +} + +/* Return the value of the alias for NAME, or NULL if there is none. */ +char * +get_alias_value (name) + char *name; +{ + alias_t *alias; + + if (aliases == 0) + return ((char *)NULL); + + alias = find_alias (name); + return (alias ? alias->value : (char *)NULL); +} + +/* Make a new alias from NAME and VALUE. If NAME can be found, + then replace its value. */ +void +add_alias (name, value) + char *name, *value; +{ + BUCKET_CONTENTS *elt; + alias_t *temp; + int n; + + if (!aliases) + { + initialize_aliases (); + temp = (alias_t *)NULL; + } + else + temp = find_alias (name); + + if (temp) + { + free (temp->value); + temp->value = savestring (value); + temp->flags &= ~AL_EXPANDNEXT; + n = value[strlen (value) - 1]; + if (n == ' ' || n == '\t') + temp->flags |= AL_EXPANDNEXT; + } + else + { + temp = (alias_t *)xmalloc (sizeof (alias_t)); + temp->name = savestring (name); + temp->value = savestring (value); + temp->flags = 0; + + n = value[strlen (value) - 1]; + if (n == ' ' || n == '\t') + temp->flags |= AL_EXPANDNEXT; + + elt = hash_insert (savestring (name), aliases, HASH_NOSRCH); + elt->data = temp; +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_aliases); +#endif + } +} + +/* Delete a single alias structure. */ +static void +free_alias_data (data) + PTR_T data; +{ + register alias_t *a; + + a = (alias_t *)data; + free (a->value); + free (a->name); + free (data); +} + +/* Remove the alias with name NAME from the alias table. Returns + the number of aliases left in the table, or -1 if the alias didn't + exist. */ +int +remove_alias (name) + char *name; +{ + BUCKET_CONTENTS *elt; + + if (aliases == 0) + return (-1); + + elt = hash_remove (name, aliases, 0); + if (elt) + { + free_alias_data (elt->data); + free (elt->key); /* alias name */ + free (elt); /* XXX */ +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_aliases); +#endif + return (aliases->nentries); + } + return (-1); +} + +/* Delete all aliases. */ +void +delete_all_aliases () +{ + if (aliases == 0) + return; + + hash_flush (aliases, free_alias_data); + hash_dispose (aliases); + aliases = (HASH_TABLE *)NULL; +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_aliases); +#endif +} + +/* Return an array of aliases that satisfy the conditions tested by FUNCTION. + If FUNCTION is NULL, return all aliases. */ +static alias_t ** +map_over_aliases (function) + sh_alias_map_func_t *function; +{ + register int i; + register BUCKET_CONTENTS *tlist; + alias_t *alias, **list; + int list_index; + + i = HASH_ENTRIES (aliases); + if (i == 0) + return ((alias_t **)NULL); + + list = (alias_t **)xmalloc ((i + 1) * sizeof (alias_t *)); + for (i = list_index = 0; i < aliases->nbuckets; i++) + { + for (tlist = hash_items (i, aliases); tlist; tlist = tlist->next) + { + alias = (alias_t *)tlist->data; + + if (!function || (*function) (alias)) + { + list[list_index++] = alias; + list[list_index] = (alias_t *)NULL; + } + } + } + return (list); +} + +static void +sort_aliases (array) + alias_t **array; +{ + qsort (array, strvec_len ((char **)array), sizeof (alias_t *), (QSFUNC *)qsort_alias_compare); +} + +static int +qsort_alias_compare (as1, as2) + alias_t **as1, **as2; +{ + int result; + + if ((result = (*as1)->name[0] - (*as2)->name[0]) == 0) + result = strcmp ((*as1)->name, (*as2)->name); + + return (result); +} + +/* Return a sorted list of all defined aliases */ +alias_t ** +all_aliases () +{ + alias_t **list; + + if (aliases == 0 || HASH_ENTRIES (aliases) == 0) + return ((alias_t **)NULL); + + list = map_over_aliases ((sh_alias_map_func_t *)NULL); + if (list) + sort_aliases (list); + return (list); +} + +char * +alias_expand_word (s) + char *s; +{ + alias_t *r; + + r = find_alias (s); + return (r ? savestring (r->value) : (char *)NULL); +} + +/* Readline support functions -- expand all aliases in a line. */ + +#if defined (READLINE) + +/* Return non-zero if CHARACTER is a member of the class of characters + that are self-delimiting in the shell (this really means that these + characters delimit tokens). */ +#define self_delimiting(character) (member ((character), " \t\n\r;|&()")) + +/* Return non-zero if CHARACTER is a member of the class of characters + that delimit commands in the shell. */ +#define command_separator(character) (member ((character), "\r\n;|&(")) + +/* If this is 1, we are checking the next token read for alias expansion + because it is the first word in a command. */ +static int command_word; + +/* This is for skipping quoted strings in alias expansions. */ +#define quote_char(c) (((c) == '\'') || ((c) == '"')) + +/* Consume a quoted string from STRING, starting at string[START] (so + string[START] is the opening quote character), and return the index + of the closing quote character matching the opening quote character. + This handles single matching pairs of unquoted quotes; it could afford + to be a little smarter... This skips words between balanced pairs of + quotes, words where the first character is quoted with a `\', and other + backslash-escaped characters. */ + +static int +skipquotes (string, start) + char *string; + int start; +{ + register int i; + int delimiter = string[start]; + + /* i starts at START + 1 because string[START] is the opening quote + character. */ + for (i = start + 1 ; string[i] ; i++) + { + if (string[i] == '\\') + { + i++; /* skip backslash-quoted quote characters, too */ + if (string[i] == 0) + break; + continue; + } + + if (string[i] == delimiter) + return i; + } + return (i); +} + +/* Skip the white space and any quoted characters in STRING, starting at + START. Return the new index into STRING, after zero or more characters + have been skipped. */ +static int +skipws (string, start) + char *string; + int start; +{ + register int i; + int pass_next, backslash_quoted_word; + unsigned char peekc; + + /* skip quoted strings, in ' or ", and words in which a character is quoted + with a `\'. */ + i = backslash_quoted_word = pass_next = 0; + + /* Skip leading whitespace (or separator characters), and quoted words. + But save it in the output. */ + + for (i = start; string[i]; i++) + { + if (pass_next) + { + pass_next = 0; + continue; + } + + if (whitespace (string[i])) + { + backslash_quoted_word = 0; /* we are no longer in a backslash-quoted word */ + continue; + } + + if (string[i] == '\\') + { + peekc = string[i+1]; + if (peekc == 0) + break; + if (ISLETTER (peekc)) + backslash_quoted_word++; /* this is a backslash-quoted word */ + else + pass_next++; + continue; + } + + /* This only handles single pairs of non-escaped quotes. This + overloads backslash_quoted_word to also mean that a word like + ""f is being scanned, so that the quotes will inhibit any expansion + of the word. */ + if (quote_char(string[i])) + { + i = skipquotes (string, i); + /* This could be a line that contains a single quote character, + in which case skipquotes () terminates with string[i] == '\0' + (the end of the string). Check for that here. */ + if (string[i] == '\0') + break; + + peekc = string[i + 1]; + if (ISLETTER (peekc)) + backslash_quoted_word++; + continue; + } + + /* If we're in the middle of some kind of quoted word, let it + pass through. */ + if (backslash_quoted_word) + continue; + + /* If this character is a shell command separator, then set a hint for + alias_expand that the next token is the first word in a command. */ + + if (command_separator (string[i])) + { + command_word++; + continue; + } + break; + } + return (i); +} + +/* Characters that may appear in a token. Basically, anything except white + space and a token separator. */ +#define token_char(c) (!((whitespace (string[i]) || self_delimiting (string[i])))) + +/* Read from START in STRING until the next separator character, and return + the index of that separator. Skip backslash-quoted characters. Call + skipquotes () for quoted strings in the middle or at the end of tokens, + so all characters show up (e.g. foo'' and foo""bar) */ +static int +rd_token (string, start) + char *string; + int start; +{ + register int i; + + /* From here to next separator character is a token. */ + for (i = start; string[i] && token_char (string[i]); i++) + { + if (string[i] == '\\') + { + i++; /* skip backslash-escaped character */ + if (string[i] == 0) + break; + continue; + } + + /* If this character is a quote character, we want to call skipquotes + to get the whole quoted portion as part of this word. That word + will not generally match an alias, even if te unquoted word would + have. The presence of the quotes in the token serves then to + inhibit expansion. */ + if (quote_char (string[i])) + { + i = skipquotes (string, i); + /* This could be a line that contains a single quote character, + in which case skipquotes () terminates with string[i] == '\0' + (the end of the string). Check for that here. */ + if (string[i] == '\0') + break; + + /* Now string[i] is the matching quote character, and the + quoted portion of the token has been scanned. */ + continue; + } + } + return (i); +} + +/* Return a new line, with any aliases substituted. */ +char * +alias_expand (string) + char *string; +{ + register int i, j, start; + char *line, *token; + int line_len, tl, real_start, expand_next, expand_this_token; + alias_t *alias; + + line_len = strlen (string) + 1; + line = (char *)xmalloc (line_len); + token = (char *)xmalloc (line_len); + + line[0] = i = 0; + expand_next = 0; + command_word = 1; /* initialized to expand the first word on the line */ + + /* Each time through the loop we find the next word in line. If it + has an alias, substitute the alias value. If the value ends in ` ', + then try again with the next word. Else, if there is no value, or if + the value does not end in space, we are done. */ + + for (;;) + { + + token[0] = 0; + start = i; + + /* Skip white space and quoted characters */ + i = skipws (string, start); + + if (start == i && string[i] == '\0') + { + free (token); + return (line); + } + + /* copy the just-skipped characters into the output string, + expanding it if there is not enough room. */ + j = strlen (line); + tl = i - start; /* number of characters just skipped */ + RESIZE_MALLOCED_BUFFER (line, j, (tl + 1), line_len, (tl + 50)); + strncpy (line + j, string + start, tl); + line[j + tl] = '\0'; + + real_start = i; + + command_word = command_word || (command_separator (string[i])); + expand_this_token = (command_word || expand_next); + expand_next = 0; + + /* Read the next token, and copy it into TOKEN. */ + start = i; + i = rd_token (string, start); + + tl = i - start; /* token length */ + + /* If tl == 0, but we're not at the end of the string, then we have a + single-character token, probably a delimiter */ + if (tl == 0 && string[i] != '\0') + { + tl = 1; + i++; /* move past it */ + } + + strncpy (token, string + start, tl); + token [tl] = '\0'; + + /* If there is a backslash-escaped character quoted in TOKEN, + then we don't do alias expansion. This should check for all + other quoting characters, too. */ + if (mbschr (token, '\\')) + expand_this_token = 0; + + /* If we should be expanding here, if we are expanding all words, or if + we are in a location in the string where an expansion is supposed to + take place, see if this word has a substitution. If it does, then do + the expansion. Note that we defer the alias value lookup until we + are sure we are expanding this token. */ + + if ((token[0]) && + (expand_this_token || alias_expand_all) && + (alias = find_alias (token))) + { + char *v; + int vlen, llen; + + v = alias->value; + vlen = strlen (v); + llen = strlen (line); + + /* +3 because we possibly add one more character below. */ + RESIZE_MALLOCED_BUFFER (line, llen, (vlen + 3), line_len, (vlen + 50)); + + strcpy (line + llen, v); + + if ((expand_this_token && vlen && whitespace (v[vlen - 1])) || + alias_expand_all) + expand_next = 1; + } + else + { + int llen, tlen; + + llen = strlen (line); + tlen = i - real_start; /* tlen == strlen(token) */ + + RESIZE_MALLOCED_BUFFER (line, llen, (tlen + 1), line_len, (llen + tlen + 50)); + + strncpy (line + llen, string + real_start, tlen); + line[llen + tlen] = '\0'; + } + command_word = 0; + } +} +#endif /* READLINE */ +#endif /* ALIAS */ diff --git a/array.c b/array.c index 27fe17083..a6cca015a 100644 --- a/array.c +++ b/array.c @@ -816,7 +816,7 @@ int quoted; rsize, rsize); strcpy(result + rlen, t); rlen += reg; - if (quoted && t) + if (quoted) free(t); /* * Add a separator only after non-null elements. diff --git a/array.c~ b/array.c~ new file mode 100644 index 000000000..27fe17083 --- /dev/null +++ b/array.c~ @@ -0,0 +1,1130 @@ +/* + * array.c - functions to create, destroy, access, and manipulate arrays + * of strings. + * + * Arrays are sparse doubly-linked lists. An element's index is stored + * with it. + * + * Chet Ramey + * chet@ins.cwru.edu + */ + +/* Copyright (C) 1997-2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (ARRAY_VARS) + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include "bashansi.h" + +#include "shell.h" +#include "array.h" +#include "builtins/common.h" + +#define ADD_BEFORE(ae, new) \ + do { \ + ae->prev->next = new; \ + new->prev = ae->prev; \ + ae->prev = new; \ + new->next = ae; \ + } while(0) + +static char *array_to_string_internal __P((ARRAY_ELEMENT *, ARRAY_ELEMENT *, char *, int)); + +static ARRAY *lastarray = 0; +static ARRAY_ELEMENT *lastref = 0; + +#define IS_LASTREF(a) ((a) == lastarray) + +#define INVALIDATE_LASTREF(a) \ +do { \ + if ((a) == lastarray) { \ + lastarray = 0; \ + lastref = 0; \ + } \ +} while (0) + +#define SET_LASTREF(a, e) \ +do { \ + lastarray = (a); \ + lastref = (e); \ +} while (0) + +#define UNSET_LASTREF() \ +do { \ + lastarray = 0; \ + lastref = 0; \ +} while (0) + +ARRAY * +array_create() +{ + ARRAY *r; + ARRAY_ELEMENT *head; + + r =(ARRAY *)xmalloc(sizeof(ARRAY)); + r->type = array_indexed; + r->max_index = -1; + r->num_elements = 0; + head = array_create_element(-1, (char *)NULL); /* dummy head */ + head->prev = head->next = head; + r->head = head; + return(r); +} + +void +array_flush (a) +ARRAY *a; +{ + register ARRAY_ELEMENT *r, *r1; + + if (a == 0) + return; + for (r = element_forw(a->head); r != a->head; ) { + r1 = element_forw(r); + array_dispose_element(r); + r = r1; + } + a->head->next = a->head->prev = a->head; + a->max_index = -1; + a->num_elements = 0; + INVALIDATE_LASTREF(a); +} + +void +array_dispose(a) +ARRAY *a; +{ + if (a == 0) + return; + array_flush (a); + array_dispose_element(a->head); + free(a); +} + +ARRAY * +array_copy(a) +ARRAY *a; +{ + ARRAY *a1; + ARRAY_ELEMENT *ae, *new; + + if (a == 0) + return((ARRAY *) NULL); + a1 = array_create(); + a1->type = a->type; + a1->max_index = a->max_index; + a1->num_elements = a->num_elements; + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { + new = array_create_element(element_index(ae), element_value(ae)); + ADD_BEFORE(a1->head, new); + } + return(a1); +} + +/* + * Make and return a new array composed of the elements in array A from + * S to E, inclusive. + */ +ARRAY * +array_slice(array, s, e) +ARRAY *array; +ARRAY_ELEMENT *s, *e; +{ + ARRAY *a; + ARRAY_ELEMENT *p, *n; + int i; + arrayind_t mi; + + a = array_create (); + a->type = array->type; + + for (mi = 0, p = s, i = 0; p != e; p = element_forw(p), i++) { + n = array_create_element (element_index(p), element_value(p)); + ADD_BEFORE(a->head, n); + mi = element_index(n); + } + a->num_elements = i; + a->max_index = mi; + return a; +} + +/* + * Walk the array, calling FUNC once for each element, with the array + * element as the argument. + */ +void +array_walk(a, func, udata) +ARRAY *a; +sh_ae_map_func_t *func; +void *udata; +{ + register ARRAY_ELEMENT *ae; + + if (a == 0 || array_empty(a)) + return; + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) + if ((*func)(ae, udata) < 0) + return; +} + +/* + * Shift the array A N elements to the left. Delete the first N elements + * and subtract N from the indices of the remaining elements. If FLAGS + * does not include AS_DISPOSE, this returns a singly-linked null-terminated + * list of elements so the caller can dispose of the chain. If FLAGS + * includes AS_DISPOSE, this function disposes of the shifted-out elements + * and returns NULL. + */ +ARRAY_ELEMENT * +array_shift(a, n, flags) +ARRAY *a; +int n, flags; +{ + register ARRAY_ELEMENT *ae, *ret; + register int i; + + if (a == 0 || array_empty(a) || n <= 0) + return ((ARRAY_ELEMENT *)NULL); + + INVALIDATE_LASTREF(a); + for (i = 0, ret = ae = element_forw(a->head); ae != a->head && i < n; ae = element_forw(ae), i++) + ; + if (ae == a->head) { + /* Easy case; shifting out all of the elements */ + if (flags & AS_DISPOSE) { + array_flush (a); + return ((ARRAY_ELEMENT *)NULL); + } + for (ae = ret; element_forw(ae) != a->head; ae = element_forw(ae)) + ; + element_forw(ae) = (ARRAY_ELEMENT *)NULL; + a->head->next = a->head->prev = a->head; + a->max_index = -1; + a->num_elements = 0; + return ret; + } + /* + * ae now points to the list of elements we want to retain. + * ret points to the list we want to either destroy or return. + */ + ae->prev->next = (ARRAY_ELEMENT *)NULL; /* null-terminate RET */ + + a->head->next = ae; /* slice RET out of the array */ + ae->prev = a->head; + + for ( ; ae != a->head; ae = element_forw(ae)) + element_index(ae) -= n; /* renumber retained indices */ + + a->num_elements -= n; /* modify bookkeeping information */ + a->max_index = element_index(a->head->prev); + + if (flags & AS_DISPOSE) { + for (ae = ret; ae; ) { + ret = element_forw(ae); + array_dispose_element(ae); + ae = ret; + } + return ((ARRAY_ELEMENT *)NULL); + } + + return ret; +} + +/* + * Shift array A right N indices. If S is non-null, it becomes the value of + * the new element 0. Returns the number of elements in the array after the + * shift. + */ +int +array_rshift (a, n, s) +ARRAY *a; +int n; +char *s; +{ + register ARRAY_ELEMENT *ae, *new; + + if (a == 0 || (array_empty(a) && s == 0)) + return 0; + else if (n <= 0) + return (a->num_elements); + + ae = element_forw(a->head); + if (s) { + new = array_create_element(0, s); + ADD_BEFORE(ae, new); + a->num_elements++; + if (array_num_elements(a) == 1) { /* array was empty */ + a->max_index = 0; + return 1; + } + } + + /* + * Renumber all elements in the array except the one we just added. + */ + for ( ; ae != a->head; ae = element_forw(ae)) + element_index(ae) += n; + + a->max_index = element_index(a->head->prev); + + INVALIDATE_LASTREF(a); + return (a->num_elements); +} + +ARRAY_ELEMENT * +array_unshift_element(a) +ARRAY *a; +{ + return (array_shift (a, 1, 0)); +} + +int +array_shift_element(a, v) +ARRAY *a; +char *v; +{ + return (array_rshift (a, 1, v)); +} + +ARRAY * +array_quote(array) +ARRAY *array; +{ + ARRAY_ELEMENT *a; + char *t; + + if (array == 0 || array_head(array) == 0 || array_empty(array)) + return (ARRAY *)NULL; + for (a = element_forw(array->head); a != array->head; a = element_forw(a)) { + t = quote_string (a->value); + FREE(a->value); + a->value = t; + } + return array; +} + +ARRAY * +array_quote_escapes(array) +ARRAY *array; +{ + ARRAY_ELEMENT *a; + char *t; + + if (array == 0 || array_head(array) == 0 || array_empty(array)) + return (ARRAY *)NULL; + for (a = element_forw(array->head); a != array->head; a = element_forw(a)) { + t = quote_escapes (a->value); + FREE(a->value); + a->value = t; + } + return array; +} + +ARRAY * +array_dequote(array) +ARRAY *array; +{ + ARRAY_ELEMENT *a; + char *t; + + if (array == 0 || array_head(array) == 0 || array_empty(array)) + return (ARRAY *)NULL; + for (a = element_forw(array->head); a != array->head; a = element_forw(a)) { + t = dequote_string (a->value); + FREE(a->value); + a->value = t; + } + return array; +} + +ARRAY * +array_dequote_escapes(array) +ARRAY *array; +{ + ARRAY_ELEMENT *a; + char *t; + + if (array == 0 || array_head(array) == 0 || array_empty(array)) + return (ARRAY *)NULL; + for (a = element_forw(array->head); a != array->head; a = element_forw(a)) { + t = dequote_escapes (a->value); + FREE(a->value); + a->value = t; + } + return array; +} + +ARRAY * +array_remove_quoted_nulls(array) +ARRAY *array; +{ + ARRAY_ELEMENT *a; + char *t; + + if (array == 0 || array_head(array) == 0 || array_empty(array)) + return (ARRAY *)NULL; + for (a = element_forw(array->head); a != array->head; a = element_forw(a)) + a->value = remove_quoted_nulls (a->value); + return array; +} + +/* + * Return a string whose elements are the members of array A beginning at + * index START and spanning NELEM members. Null elements are counted. + * Since arrays are sparse, unset array elements are not counted. + */ +char * +array_subrange (a, start, nelem, starsub, quoted) +ARRAY *a; +arrayind_t start, nelem; +int starsub, quoted; +{ + ARRAY *a2; + ARRAY_ELEMENT *h, *p; + arrayind_t i; + char *ifs, *sifs, *t; + int slen; + + p = a ? array_head (a) : 0; + if (p == 0 || array_empty (a) || start > array_max_index(a)) + return ((char *)NULL); + + /* + * Find element with index START. If START corresponds to an unset + * element (arrays can be sparse), use the first element whose index + * is >= START. If START is < 0, we count START indices back from + * the end of A (not elements, even with sparse arrays -- START is an + * index). + */ + for (p = element_forw(p); p != array_head(a) && start > element_index(p); p = element_forw(p)) + ; + + if (p == a->head) + return ((char *)NULL); + + /* Starting at P, take NELEM elements, inclusive. */ + for (i = 0, h = p; p != a->head && i < nelem; i++, p = element_forw(p)) + ; + + a2 = array_slice(a, h, p); + + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + array_quote(a2); + else + array_quote_escapes(a2); + + if (starsub && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) { + /* ${array[*]} */ + array_remove_quoted_nulls (a2); + sifs = ifs_firstchar ((int *)NULL); + t = array_to_string (a2, sifs, 0); + free (sifs); + } else if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) { + /* ${array[@]} */ + sifs = ifs_firstchar (&slen); + ifs = getifs (); + if (ifs == 0 || *ifs == 0) { + if (slen < 2) + sifs = xrealloc(sifs, 2); + sifs[0] = ' '; + sifs[1] = '\0'; + } + t = array_to_string (a2, sifs, 0); + free (sifs); + } else + t = array_to_string (a2, " ", 0); + array_dispose(a2); + + return t; +} + +char * +array_patsub (a, pat, rep, mflags) +ARRAY *a; +char *pat, *rep; +int mflags; +{ + ARRAY *a2; + ARRAY_ELEMENT *e; + char *t, *sifs, *ifs; + int slen; + + if (a == 0 || array_head(a) == 0 || array_empty(a)) + return ((char *)NULL); + + a2 = array_copy(a); + for (e = element_forw(a2->head); e != a2->head; e = element_forw(e)) { + t = pat_subst(element_value(e), pat, rep, mflags); + FREE(element_value(e)); + e->value = t; + } + + if (mflags & MATCH_QUOTED) + array_quote(a2); + else + array_quote_escapes(a2); + + if (mflags & MATCH_STARSUB) { + array_remove_quoted_nulls (a2); + sifs = ifs_firstchar((int *)NULL); + t = array_to_string (a2, sifs, 0); + free(sifs); + } else if (mflags & MATCH_QUOTED) { + /* ${array[@]} */ + sifs = ifs_firstchar (&slen); + ifs = getifs (); + if (ifs == 0 || *ifs == 0) { + if (slen < 2) + sifs = xrealloc (sifs, 2); + sifs[0] = ' '; + sifs[1] = '\0'; + } + t = array_to_string (a2, sifs, 0); + free(sifs); + } else + t = array_to_string (a2, " ", 0); + array_dispose (a2); + + return t; +} + +char * +array_modcase (a, pat, modop, mflags) +ARRAY *a; +char *pat; +int modop; +int mflags; +{ + ARRAY *a2; + ARRAY_ELEMENT *e; + char *t, *sifs, *ifs; + int slen; + + if (a == 0 || array_head(a) == 0 || array_empty(a)) + return ((char *)NULL); + + a2 = array_copy(a); + for (e = element_forw(a2->head); e != a2->head; e = element_forw(e)) { + t = sh_modcase(element_value(e), pat, modop); + FREE(element_value(e)); + e->value = t; + } + + if (mflags & MATCH_QUOTED) + array_quote(a2); + else + array_quote_escapes(a2); + + if (mflags & MATCH_STARSUB) { + array_remove_quoted_nulls (a2); + sifs = ifs_firstchar((int *)NULL); + t = array_to_string (a2, sifs, 0); + free(sifs); + } else if (mflags & MATCH_QUOTED) { + /* ${array[@]} */ + sifs = ifs_firstchar (&slen); + ifs = getifs (); + if (ifs == 0 || *ifs == 0) { + if (slen < 2) + sifs = xrealloc (sifs, 2); + sifs[0] = ' '; + sifs[1] = '\0'; + } + t = array_to_string (a2, sifs, 0); + free(sifs); + } else + t = array_to_string (a2, " ", 0); + array_dispose (a2); + + return t; +} +/* + * Allocate and return a new array element with index INDEX and value + * VALUE. + */ +ARRAY_ELEMENT * +array_create_element(indx, value) +arrayind_t indx; +char *value; +{ + ARRAY_ELEMENT *r; + + r = (ARRAY_ELEMENT *)xmalloc(sizeof(ARRAY_ELEMENT)); + r->ind = indx; + r->value = value ? savestring(value) : (char *)NULL; + r->next = r->prev = (ARRAY_ELEMENT *) NULL; + return(r); +} + +#ifdef INCLUDE_UNUSED +ARRAY_ELEMENT * +array_copy_element(ae) +ARRAY_ELEMENT *ae; +{ + return(ae ? array_create_element(element_index(ae), element_value(ae)) + : (ARRAY_ELEMENT *) NULL); +} +#endif + +void +array_dispose_element(ae) +ARRAY_ELEMENT *ae; +{ + if (ae) { + FREE(ae->value); + free(ae); + } +} + +/* + * Add a new element with index I and value V to array A (a[i] = v). + */ +int +array_insert(a, i, v) +ARRAY *a; +arrayind_t i; +char *v; +{ + register ARRAY_ELEMENT *new, *ae; + + if (a == 0) + return(-1); + new = array_create_element(i, v); + if (i > array_max_index(a)) { + /* + * Hook onto the end. This also works for an empty array. + * Fast path for the common case of allocating arrays + * sequentially. + */ + ADD_BEFORE(a->head, new); + a->max_index = i; + a->num_elements++; + SET_LASTREF(a, new); + return(0); + } + /* + * Otherwise we search for the spot to insert it. + */ + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { + if (element_index(ae) == i) { + /* + * Replacing an existing element. + */ + array_dispose_element(new); + free(element_value(ae)); + ae->value = v ? savestring(v) : (char *)NULL; + SET_LASTREF(a, ae); + return(0); + } else if (element_index(ae) > i) { + ADD_BEFORE(ae, new); + a->num_elements++; + SET_LASTREF(a, new); + return(0); + } + } + INVALIDATE_LASTREF(a); + return (-1); /* problem */ +} + +/* + * Delete the element with index I from array A and return it so the + * caller can dispose of it. + */ +ARRAY_ELEMENT * +array_remove(a, i) +ARRAY *a; +arrayind_t i; +{ + register ARRAY_ELEMENT *ae; + + if (a == 0 || array_empty(a)) + return((ARRAY_ELEMENT *) NULL); + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) + if (element_index(ae) == i) { + ae->next->prev = ae->prev; + ae->prev->next = ae->next; + a->num_elements--; + if (i == array_max_index(a)) + a->max_index = element_index(ae->prev); + INVALIDATE_LASTREF(a); + return(ae); + } + return((ARRAY_ELEMENT *) NULL); +} + +/* + * Return the value of a[i]. + */ +char * +array_reference(a, i) +ARRAY *a; +arrayind_t i; +{ + register ARRAY_ELEMENT *ae; + + if (a == 0 || array_empty(a)) + return((char *) NULL); + if (i > array_max_index(a)) + return((char *)NULL); + /* Keep roving pointer into array to optimize sequential access */ + if (lastref && IS_LASTREF(a)) + ae = (i >= element_index(lastref)) ? lastref : element_forw(a->head); + else + ae = element_forw(a->head); + for ( ; ae != a->head; ae = element_forw(ae)) + if (element_index(ae) == i) { + SET_LASTREF(a, ae); + return(element_value(ae)); + } + UNSET_LASTREF(); + return((char *) NULL); +} + +/* Convenience routines for the shell to translate to and from the form used + by the rest of the code. */ + +WORD_LIST * +array_to_word_list(a) +ARRAY *a; +{ + WORD_LIST *list; + ARRAY_ELEMENT *ae; + + if (a == 0 || array_empty(a)) + return((WORD_LIST *)NULL); + list = (WORD_LIST *)NULL; + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) + list = make_word_list (make_bare_word(element_value(ae)), list); + return (REVERSE_LIST(list, WORD_LIST *)); +} + +ARRAY * +array_from_word_list (list) +WORD_LIST *list; +{ + ARRAY *a; + + if (list == 0) + return((ARRAY *)NULL); + a = array_create(); + return (array_assign_list (a, list)); +} + +WORD_LIST * +array_keys_to_word_list(a) +ARRAY *a; +{ + WORD_LIST *list; + ARRAY_ELEMENT *ae; + char *t; + + if (a == 0 || array_empty(a)) + return((WORD_LIST *)NULL); + list = (WORD_LIST *)NULL; + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { + t = itos(element_index(ae)); + list = make_word_list (make_bare_word(t), list); + free(t); + } + return (REVERSE_LIST(list, WORD_LIST *)); +} + +ARRAY * +array_assign_list (array, list) +ARRAY *array; +WORD_LIST *list; +{ + register WORD_LIST *l; + register arrayind_t i; + + for (l = list, i = 0; l; l = l->next, i++) + array_insert(array, i, l->word->word); + return array; +} + +char ** +array_to_argv (a) +ARRAY *a; +{ + char **ret, *t; + int i; + ARRAY_ELEMENT *ae; + + if (a == 0 || array_empty(a)) + return ((char **)NULL); + ret = strvec_create (array_num_elements (a) + 1); + i = 0; + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { + t = element_value (ae); + ret[i++] = t ? savestring (t) : (char *)NULL; + } + ret[i] = (char *)NULL; + return (ret); +} + +/* + * Return a string that is the concatenation of the elements in A from START + * to END, separated by SEP. + */ +static char * +array_to_string_internal (start, end, sep, quoted) +ARRAY_ELEMENT *start, *end; +char *sep; +int quoted; +{ + char *result, *t; + ARRAY_ELEMENT *ae; + int slen, rsize, rlen, reg; + + if (start == end) /* XXX - should not happen */ + return ((char *)NULL); + + slen = strlen(sep); + result = NULL; + for (rsize = rlen = 0, ae = start; ae != end; ae = element_forw(ae)) { + if (rsize == 0) + result = (char *)xmalloc (rsize = 64); + if (element_value(ae)) { + t = quoted ? quote_string(element_value(ae)) : element_value(ae); + reg = strlen(t); + RESIZE_MALLOCED_BUFFER (result, rlen, (reg + slen + 2), + rsize, rsize); + strcpy(result + rlen, t); + rlen += reg; + if (quoted && t) + free(t); + /* + * Add a separator only after non-null elements. + */ + if (element_forw(ae) != end) { + strcpy(result + rlen, sep); + rlen += slen; + } + } + } + if (result) + result[rlen] = '\0'; /* XXX */ + return(result); +} + +char * +array_to_assign (a, quoted) +ARRAY *a; +int quoted; +{ + char *result, *valstr, *is; + char indstr[INT_STRLEN_BOUND(intmax_t) + 1]; + ARRAY_ELEMENT *ae; + int rsize, rlen, elen; + + if (a == 0 || array_empty (a)) + return((char *)NULL); + + result = (char *)xmalloc (rsize = 128); + result[0] = '('; + rlen = 1; + + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { + is = inttostr (element_index(ae), indstr, sizeof(indstr)); + valstr = element_value (ae) ? sh_double_quote (element_value(ae)) + : (char *)NULL; + elen = STRLEN (is) + 8 + STRLEN (valstr); + RESIZE_MALLOCED_BUFFER (result, rlen, (elen + 1), rsize, rsize); + + result[rlen++] = '['; + strcpy (result + rlen, is); + rlen += STRLEN (is); + result[rlen++] = ']'; + result[rlen++] = '='; + if (valstr) { + strcpy (result + rlen, valstr); + rlen += STRLEN (valstr); + } + + if (element_forw(ae) != a->head) + result[rlen++] = ' '; + + FREE (valstr); + } + RESIZE_MALLOCED_BUFFER (result, rlen, 1, rsize, 8); + result[rlen++] = ')'; + result[rlen] = '\0'; + if (quoted) { + /* This is not as efficient as it could be... */ + valstr = sh_single_quote (result); + free (result); + result = valstr; + } + return(result); +} + +char * +array_to_string (a, sep, quoted) +ARRAY *a; +char *sep; +int quoted; +{ + if (a == 0) + return((char *)NULL); + if (array_empty(a)) + return(savestring("")); + return (array_to_string_internal (element_forw(a->head), a->head, sep, quoted)); +} + +#if defined (INCLUDE_UNUSED) || defined (TEST_ARRAY) +/* + * Return an array consisting of elements in S, separated by SEP + */ +ARRAY * +array_from_string(s, sep) +char *s, *sep; +{ + ARRAY *a; + WORD_LIST *w; + + if (s == 0) + return((ARRAY *)NULL); + w = list_string (s, sep, 0); + if (w == 0) + return((ARRAY *)NULL); + a = array_from_word_list (w); + return (a); +} +#endif + +#if defined (TEST_ARRAY) +/* + * To make a running version, compile -DTEST_ARRAY and link with: + * xmalloc.o syntax.o lib/malloc/libmalloc.a lib/sh/libsh.a + */ +int interrupt_immediately = 0; + +int +signal_is_trapped(s) +int s; +{ + return 0; +} + +void +fatal_error(const char *s, ...) +{ + fprintf(stderr, "array_test: fatal memory error\n"); + abort(); +} + +void +programming_error(const char *s, ...) +{ + fprintf(stderr, "array_test: fatal programming error\n"); + abort(); +} + +WORD_DESC * +make_bare_word (s) +const char *s; +{ + WORD_DESC *w; + + w = (WORD_DESC *)xmalloc(sizeof(WORD_DESC)); + w->word = s ? savestring(s) : savestring (""); + w->flags = 0; + return w; +} + +WORD_LIST * +make_word_list(x, l) +WORD_DESC *x; +WORD_LIST *l; +{ + WORD_LIST *w; + + w = (WORD_LIST *)xmalloc(sizeof(WORD_LIST)); + w->word = x; + w->next = l; + return w; +} + +WORD_LIST * +list_string(s, t, i) +char *s, *t; +int i; +{ + char *r, *a; + WORD_LIST *wl; + + if (s == 0) + return (WORD_LIST *)NULL; + r = savestring(s); + wl = (WORD_LIST *)NULL; + a = strtok(r, t); + while (a) { + wl = make_word_list (make_bare_word(a), wl); + a = strtok((char *)NULL, t); + } + return (REVERSE_LIST (wl, WORD_LIST *)); +} + +GENERIC_LIST * +list_reverse (list) +GENERIC_LIST *list; +{ + register GENERIC_LIST *next, *prev; + + for (prev = 0; list; ) { + next = list->next; + list->next = prev; + prev = list; + list = next; + } + return prev; +} + +char * +pat_subst(s, t, u, i) +char *s, *t, *u; +int i; +{ + return ((char *)NULL); +} + +char * +quote_string(s) +char *s; +{ + return savestring(s); +} + +print_element(ae) +ARRAY_ELEMENT *ae; +{ + char lbuf[INT_STRLEN_BOUND (intmax_t) + 1]; + + printf("array[%s] = %s\n", + inttostr (element_index(ae), lbuf, sizeof (lbuf)), + element_value(ae)); +} + +print_array(a) +ARRAY *a; +{ + printf("\n"); + array_walk(a, print_element, (void *)NULL); +} + +main() +{ + ARRAY *a, *new_a, *copy_of_a; + ARRAY_ELEMENT *ae, *aew; + char *s; + + a = array_create(); + array_insert(a, 1, "one"); + array_insert(a, 7, "seven"); + array_insert(a, 4, "four"); + array_insert(a, 1029, "one thousand twenty-nine"); + array_insert(a, 12, "twelve"); + array_insert(a, 42, "forty-two"); + print_array(a); + s = array_to_string (a, " ", 0); + printf("s = %s\n", s); + copy_of_a = array_from_string(s, " "); + printf("copy_of_a:"); + print_array(copy_of_a); + array_dispose(copy_of_a); + printf("\n"); + free(s); + ae = array_remove(a, 4); + array_dispose_element(ae); + ae = array_remove(a, 1029); + array_dispose_element(ae); + array_insert(a, 16, "sixteen"); + print_array(a); + s = array_to_string (a, " ", 0); + printf("s = %s\n", s); + copy_of_a = array_from_string(s, " "); + printf("copy_of_a:"); + print_array(copy_of_a); + array_dispose(copy_of_a); + printf("\n"); + free(s); + array_insert(a, 2, "two"); + array_insert(a, 1029, "new one thousand twenty-nine"); + array_insert(a, 0, "zero"); + array_insert(a, 134, ""); + print_array(a); + s = array_to_string (a, ":", 0); + printf("s = %s\n", s); + copy_of_a = array_from_string(s, ":"); + printf("copy_of_a:"); + print_array(copy_of_a); + array_dispose(copy_of_a); + printf("\n"); + free(s); + new_a = array_copy(a); + print_array(new_a); + s = array_to_string (new_a, ":", 0); + printf("s = %s\n", s); + copy_of_a = array_from_string(s, ":"); + free(s); + printf("copy_of_a:"); + print_array(copy_of_a); + array_shift(copy_of_a, 2, AS_DISPOSE); + printf("copy_of_a shifted by two:"); + print_array(copy_of_a); + ae = array_shift(copy_of_a, 2, 0); + printf("copy_of_a shifted by two:"); + print_array(copy_of_a); + for ( ; ae; ) { + aew = element_forw(ae); + array_dispose_element(ae); + ae = aew; + } + array_rshift(copy_of_a, 1, (char *)0); + printf("copy_of_a rshift by 1:"); + print_array(copy_of_a); + array_rshift(copy_of_a, 2, "new element zero"); + printf("copy_of_a rshift again by 2 with new element zero:"); + print_array(copy_of_a); + s = array_to_assign(copy_of_a, 0); + printf("copy_of_a=%s\n", s); + free(s); + ae = array_shift(copy_of_a, array_num_elements(copy_of_a), 0); + for ( ; ae; ) { + aew = element_forw(ae); + array_dispose_element(ae); + ae = aew; + } + array_dispose(copy_of_a); + printf("\n"); + array_dispose(a); + array_dispose(new_a); +} + +#endif /* TEST_ARRAY */ +#endif /* ARRAY_VARS */ diff --git a/arrayfunc.c b/arrayfunc.c index 82058b748..d46e3b021 100644 --- a/arrayfunc.c +++ b/arrayfunc.c @@ -282,6 +282,7 @@ assign_array_element_internal (entry, name, vname, sub, sublen, value, flags) if (akey == 0 || *akey == 0) { err_badarraysub (name); + FREE (akey); return ((SHELL_VAR *)NULL); } entry = bind_assoc_variable (entry, vname, akey, value, flags); @@ -534,6 +535,7 @@ assign_compound_array_list (var, nlist, flags) if (akey == 0 || *akey == 0) { err_badarraysub (w); + FREE (akey); continue; } } @@ -700,6 +702,7 @@ unbind_array_element (var, sub) if (akey == 0 || *akey == 0) { builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg)); + FREE (akey); return -1; } assoc_remove (assoc_cell (var), akey); @@ -935,6 +938,7 @@ array_value_internal (s, quoted, flags, rtype, indp) return ((char *)NULL); /* error message already printed */ /* [ */ + akey = 0; if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']') { if (rtype) @@ -964,11 +968,11 @@ array_value_internal (s, quoted, flags, rtype, indp) if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) { temp = string_list_dollar_star (l); - retval = quote_string (temp); + retval = quote_string (temp); /* XXX - leak here */ free (temp); } else /* ${name[@]} or unquoted ${name[*]} */ - retval = string_list_dollar_at (l, quoted); + retval = string_list_dollar_at (l, quoted); /* XXX - leak here */ dispose_words (l); } @@ -1001,11 +1005,17 @@ array_value_internal (s, quoted, flags, rtype, indp) akey = expand_assignment_string_to_string (t, 0); /* [ */ t[len - 1] = ']'; if (akey == 0 || *akey == 0) - INDEX_ERROR(); + { + FREE (akey); + INDEX_ERROR(); + } } if (var == 0 || value_cell (var) == 0) /* XXX - check invisible_p(var) ? */ - return ((char *)NULL); + { + FREE (akey); + return ((char *)NULL); + } if (array_p (var) == 0 && assoc_p (var) == 0) return (ind == 0 ? value_cell (var) : (char *)NULL); else if (assoc_p (var)) diff --git a/arrayfunc.c~ b/arrayfunc.c~ new file mode 100644 index 000000000..0d64a2f52 --- /dev/null +++ b/arrayfunc.c~ @@ -0,0 +1,1097 @@ +/* arrayfunc.c -- High-level array functions used by other parts of the shell. */ + +/* Copyright (C) 2001-2011 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (ARRAY_VARS) + +#if defined (HAVE_UNISTD_H) +# include +#endif +#include + +#include "bashintl.h" + +#include "shell.h" +#include "pathexp.h" + +#include "shmbutil.h" + +#include "builtins/common.h" + +extern char *this_command_name; +extern int last_command_exit_value; +extern int array_needs_making; + +static SHELL_VAR *bind_array_var_internal __P((SHELL_VAR *, arrayind_t, char *, char *, int)); +static SHELL_VAR *assign_array_element_internal __P((SHELL_VAR *, char *, char *, char *, int, char *, int)); + +static char *quote_assign __P((const char *)); +static void quote_array_assignment_chars __P((WORD_LIST *)); +static char *array_value_internal __P((char *, int, int, int *, arrayind_t *)); + +/* Standard error message to use when encountering an invalid array subscript */ +const char * const bash_badsub_errmsg = N_("bad array subscript"); + +/* **************************************************************** */ +/* */ +/* Functions to manipulate array variables and perform assignments */ +/* */ +/* **************************************************************** */ + +/* Convert a shell variable to an array variable. The original value is + saved as array[0]. */ +SHELL_VAR * +convert_var_to_array (var) + SHELL_VAR *var; +{ + char *oldval; + ARRAY *array; + + oldval = value_cell (var); + array = array_create (); + if (oldval) + array_insert (array, 0, oldval); + + FREE (value_cell (var)); + var_setarray (var, array); + + /* these aren't valid anymore */ + var->dynamic_value = (sh_var_value_func_t *)NULL; + var->assign_func = (sh_var_assign_func_t *)NULL; + + INVALIDATE_EXPORTSTR (var); + if (exported_p (var)) + array_needs_making++; + + VSETATTR (var, att_array); + VUNSETATTR (var, att_invisible); + + return var; +} + +/* Convert a shell variable to an array variable. The original value is + saved as array[0]. */ +SHELL_VAR * +convert_var_to_assoc (var) + SHELL_VAR *var; +{ + char *oldval; + HASH_TABLE *hash; + + oldval = value_cell (var); + hash = assoc_create (0); + if (oldval) + assoc_insert (hash, savestring ("0"), oldval); + + FREE (value_cell (var)); + var_setassoc (var, hash); + + /* these aren't valid anymore */ + var->dynamic_value = (sh_var_value_func_t *)NULL; + var->assign_func = (sh_var_assign_func_t *)NULL; + + INVALIDATE_EXPORTSTR (var); + if (exported_p (var)) + array_needs_making++; + + VSETATTR (var, att_assoc); + VUNSETATTR (var, att_invisible); + + return var; +} + +static SHELL_VAR * +bind_array_var_internal (entry, ind, key, value, flags) + SHELL_VAR *entry; + arrayind_t ind; + char *key; + char *value; + int flags; +{ + SHELL_VAR *dentry; + char *newval; + + /* If we're appending, we need the old value of the array reference, so + fake out make_variable_value with a dummy SHELL_VAR */ + if (flags & ASS_APPEND) + { + dentry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); + dentry->name = savestring (entry->name); + if (assoc_p (entry)) + newval = assoc_reference (assoc_cell (entry), key); + else + newval = array_reference (array_cell (entry), ind); + if (newval) + dentry->value = savestring (newval); + else + { + dentry->value = (char *)xmalloc (1); + dentry->value[0] = '\0'; + } + dentry->exportstr = 0; + dentry->attributes = entry->attributes & ~(att_array|att_assoc|att_exported); + /* Leave the rest of the members uninitialized; the code doesn't look + at them. */ + newval = make_variable_value (dentry, value, flags); + dispose_variable (dentry); + } + else + newval = make_variable_value (entry, value, flags); + + if (entry->assign_func) + (*entry->assign_func) (entry, newval, ind, key); + else if (assoc_p (entry)) + assoc_insert (assoc_cell (entry), key, newval); + else + array_insert (array_cell (entry), ind, newval); + FREE (newval); + + return (entry); +} + +/* Perform an array assignment name[ind]=value. If NAME already exists and + is not an array, and IND is 0, perform name=value instead. If NAME exists + and is not an array, and IND is not 0, convert it into an array with the + existing value as name[0]. + + If NAME does not exist, just create an array variable, no matter what + IND's value may be. */ +SHELL_VAR * +bind_array_variable (name, ind, value, flags) + char *name; + arrayind_t ind; + char *value; + int flags; +{ + SHELL_VAR *entry; + + entry = var_lookup (name, shell_variables); + + if (entry == (SHELL_VAR *) 0) + entry = make_new_array_variable (name); + else if (readonly_p (entry) || noassign_p (entry)) + { + if (readonly_p (entry)) + err_readonly (name); + return (entry); + } + else if (array_p (entry) == 0) + entry = convert_var_to_array (entry); + + /* ENTRY is an array variable, and ARRAY points to the value. */ + return (bind_array_var_internal (entry, ind, 0, value, flags)); +} + +SHELL_VAR * +bind_array_element (entry, ind, value, flags) + SHELL_VAR *entry; + arrayind_t ind; + char *value; + int flags; +{ + return (bind_array_var_internal (entry, ind, 0, value, flags)); +} + +SHELL_VAR * +bind_assoc_variable (entry, name, key, value, flags) + SHELL_VAR *entry; + char *name; + char *key; + char *value; + int flags; +{ + SHELL_VAR *dentry; + char *newval; + + if (readonly_p (entry) || noassign_p (entry)) + { + if (readonly_p (entry)) + err_readonly (name); + return (entry); + } + + return (bind_array_var_internal (entry, 0, key, value, flags)); +} + +/* Parse NAME, a lhs of an assignment statement of the form v[s], and + assign VALUE to that array element by calling bind_array_variable(). */ +SHELL_VAR * +assign_array_element (name, value, flags) + char *name, *value; + int flags; +{ + char *sub, *vname; + int sublen; + SHELL_VAR *entry; + + vname = array_variable_name (name, &sub, &sublen); + + if (vname == 0) + return ((SHELL_VAR *)NULL); + + if ((ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']') || (sublen <= 1)) + { + free (vname); + err_badarraysub (name); + return ((SHELL_VAR *)NULL); + } + + entry = find_variable (vname); + entry = assign_array_element_internal (entry, name, vname, sub, sublen, value, flags); + + free (vname); + return entry; +} + +static SHELL_VAR * +assign_array_element_internal (entry, name, vname, sub, sublen, value, flags) + SHELL_VAR *entry; + char *name; /* only used for error messages */ + char *vname; + char *sub; + int sublen; + char *value; + int flags; +{ + char *akey; + arrayind_t ind; + + if (entry && assoc_p (entry)) + { + sub[sublen-1] = '\0'; + akey = expand_assignment_string_to_string (sub, 0); /* [ */ + sub[sublen-1] = ']'; + if (akey == 0 || *akey == 0) + { + err_badarraysub (name); + FREE (akey); + return ((SHELL_VAR *)NULL); + } + entry = bind_assoc_variable (entry, vname, akey, value, flags); + } + else + { + ind = array_expand_index (entry, sub, sublen); + if (ind < 0) + { + err_badarraysub (name); + return ((SHELL_VAR *)NULL); + } + entry = bind_array_variable (vname, ind, value, flags); + } + + return (entry); +} + +/* Find the array variable corresponding to NAME. If there is no variable, + create a new array variable. If the variable exists but is not an array, + convert it to an indexed array. If FLAGS&1 is non-zero, an existing + variable is checked for the readonly or noassign attribute in preparation + for assignment (e.g., by the `read' builtin). If FLAGS&2 is non-zero, we + create an associative array. */ +SHELL_VAR * +find_or_make_array_variable (name, flags) + char *name; + int flags; +{ + SHELL_VAR *var; + + var = find_variable (name); + + if (var == 0) + var = (flags & 2) ? make_new_assoc_variable (name) : make_new_array_variable (name); + else if ((flags & 1) && (readonly_p (var) || noassign_p (var))) + { + if (readonly_p (var)) + err_readonly (name); + return ((SHELL_VAR *)NULL); + } + else if ((flags & 2) && array_p (var)) + { + report_error (_("%s: cannot convert indexed to associative array"), name); + return ((SHELL_VAR *)NULL); + } + else if (array_p (var) == 0 && assoc_p (var) == 0) + var = convert_var_to_array (var); + + return (var); +} + +/* Perform a compound assignment statement for array NAME, where VALUE is + the text between the parens: NAME=( VALUE ) */ +SHELL_VAR * +assign_array_from_string (name, value, flags) + char *name, *value; + int flags; +{ + SHELL_VAR *var; + int vflags; + + vflags = 1; + if (flags & ASS_MKASSOC) + vflags |= 2; + + var = find_or_make_array_variable (name, vflags); + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (assign_array_var_from_string (var, value, flags)); +} + +/* Sequentially assign the indices of indexed array variable VAR from the + words in LIST. */ +SHELL_VAR * +assign_array_var_from_word_list (var, list, flags) + SHELL_VAR *var; + WORD_LIST *list; + int flags; +{ + register arrayind_t i; + register WORD_LIST *l; + ARRAY *a; + + a = array_cell (var); + i = (flags & ASS_APPEND) ? array_max_index (a) + 1 : 0; + + for (l = list; l; l = l->next, i++) + if (var->assign_func) + (*var->assign_func) (var, l->word->word, i, 0); + else + array_insert (a, i, l->word->word); + return var; +} + +WORD_LIST * +expand_compound_array_assignment (var, value, flags) + SHELL_VAR *var; + char *value; + int flags; +{ + WORD_LIST *list, *nlist; + WORD_LIST *hd, *tl, *t, *n; + char *val; + int ni; + + /* This condition is true when invoked from the declare builtin with a + command like + declare -a d='([1]="" [2]="bdef" [5]="hello world" "test")' */ + if (*value == '(') /*)*/ + { + ni = 1; + val = extract_array_assignment_list (value, &ni); + if (val == 0) + return (WORD_LIST *)NULL; + } + else + val = value; + + /* Expand the value string into a list of words, performing all the + shell expansions including pathname generation and word splitting. */ + /* First we split the string on whitespace, using the shell parser + (ksh93 seems to do this). */ + list = parse_string_to_word_list (val, 1, "array assign"); + + if (var && assoc_p (var)) + { + if (val != value) + free (val); + return list; + } + + /* If we're using [subscript]=value, we need to quote each [ and ] to + prevent unwanted filename expansion. This doesn't need to be done + for associative array expansion, since that uses a different expansion + function (see assign_compound_array_list below). */ + if (list) + quote_array_assignment_chars (list); + + /* Now that we've split it, perform the shell expansions on each + word in the list. */ + nlist = list ? expand_words_no_vars (list) : (WORD_LIST *)NULL; + + dispose_words (list); + + if (val != value) + free (val); + + return nlist; +} + +/* Callers ensure that VAR is not NULL */ +void +assign_compound_array_list (var, nlist, flags) + SHELL_VAR *var; + WORD_LIST *nlist; + int flags; +{ + ARRAY *a; + HASH_TABLE *h; + WORD_LIST *list; + char *w, *val, *nval; + int len, iflags, free_val; + arrayind_t ind, last_ind; + char *akey; + + a = (var && array_p (var)) ? array_cell (var) : (ARRAY *)0; + h = (var && assoc_p (var)) ? assoc_cell (var) : (HASH_TABLE *)0; + + akey = (char *)0; + ind = 0; + + /* Now that we are ready to assign values to the array, kill the existing + value. */ + if ((flags & ASS_APPEND) == 0) + { + if (a && array_p (var)) + array_flush (a); + else if (h && assoc_p (var)) + assoc_flush (h); + } + + last_ind = (a && (flags & ASS_APPEND)) ? array_max_index (a) + 1 : 0; + + for (list = nlist; list; list = list->next) + { + iflags = flags; + w = list->word->word; + + /* We have a word of the form [ind]=value */ + if ((list->word->flags & W_ASSIGNMENT) && w[0] == '[') + { + /* Don't have to handle embedded quotes specially any more, since + associative array subscripts have not been expanded yet (see + above). */ + len = skipsubscript (w, 0, 0); + + /* XXX - changes for `+=' */ + if (w[len] != ']' || (w[len+1] != '=' && (w[len+1] != '+' || w[len+2] != '='))) + { + if (assoc_p (var)) + { + err_badarraysub (w); + continue; + } + nval = make_variable_value (var, w, flags); + if (var->assign_func) + (*var->assign_func) (var, nval, last_ind, 0); + else + array_insert (a, last_ind, nval); + FREE (nval); + last_ind++; + continue; + } + + if (len == 1) + { + err_badarraysub (w); + continue; + } + + if (ALL_ELEMENT_SUB (w[1]) && len == 2) + { + if (assoc_p (var)) + report_error (_("%s: invalid associative array key"), w); + else + report_error (_("%s: cannot assign to non-numeric index"), w); + continue; + } + + if (array_p (var)) + { + ind = array_expand_index (var, w + 1, len); + if (ind < 0) + { + err_badarraysub (w); + continue; + } + + last_ind = ind; + } + else if (assoc_p (var)) + { + /* This is not performed above, see expand_compound_array_assignment */ + w[len] = '\0'; /*[*/ + akey = expand_assignment_string_to_string (w+1, 0); + w[len] = ']'; + /* And we need to expand the value also, see below */ + if (akey == 0 || *akey == 0) + { + err_badarraysub (w); + FREE (akey); + continue; + } + } + + /* XXX - changes for `+=' -- just accept the syntax. ksh93 doesn't do this */ + if (w[len + 1] == '+' && w[len + 2] == '=') + { + iflags |= ASS_APPEND; + val = w + len + 3; + } + else + val = w + len + 2; + } + else if (assoc_p (var)) + { + report_error (_("%s: %s: must use subscript when assigning associative array"), var->name, w); + continue; + } + else /* No [ind]=value, just a stray `=' */ + { + ind = last_ind; + val = w; + } + + free_val = 0; + /* See above; we need to expand the value here */ + if (assoc_p (var)) + { + val = expand_assignment_string_to_string (val, 0); + free_val = 1; + } + + if (integer_p (var)) + this_command_name = (char *)NULL; /* no command name for errors */ + bind_array_var_internal (var, ind, akey, val, iflags); + last_ind++; + + if (free_val) + free (val); + } +} + +/* Perform a compound array assignment: VAR->name=( VALUE ). The + VALUE has already had the parentheses stripped. */ +SHELL_VAR * +assign_array_var_from_string (var, value, flags) + SHELL_VAR *var; + char *value; + int flags; +{ + WORD_LIST *nlist; + + if (value == 0) + return var; + + nlist = expand_compound_array_assignment (var, value, flags); + assign_compound_array_list (var, nlist, flags); + + if (nlist) + dispose_words (nlist); + return (var); +} + +/* Quote globbing chars and characters in $IFS before the `=' in an assignment + statement (usually a compound array assignment) to protect them from + unwanted filename expansion or word splitting. */ +static char * +quote_assign (string) + const char *string; +{ + size_t slen; + int saw_eq; + char *temp, *t, *subs; + const char *s, *send; + int ss, se; + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + + t = temp = (char *)xmalloc (slen * 2 + 1); + saw_eq = 0; + for (s = string; *s; ) + { + if (*s == '=') + saw_eq = 1; + if (saw_eq == 0 && *s == '[') /* looks like a subscript */ + { + ss = s - string; + se = skipsubscript (string, ss, 0); + subs = substring (s, ss, se); + *t++ = '\\'; + strcpy (t, subs); + t += se - ss; + *t++ = '\\'; + *t++ = ']'; + s += se + 1; + free (subs); + continue; + } + if (saw_eq == 0 && (glob_char_p (s) || isifs (*s))) + *t++ = '\\'; + + COPY_CHAR_P (t, s, send); + } + *t = '\0'; + return temp; +} + +/* For each word in a compound array assignment, if the word looks like + [ind]=value, quote globbing chars and characters in $IFS before the `='. */ +static void +quote_array_assignment_chars (list) + WORD_LIST *list; +{ + char *nword; + WORD_LIST *l; + + for (l = list; l; l = l->next) + { + if (l->word == 0 || l->word->word == 0 || l->word->word[0] == '\0') + continue; /* should not happen, but just in case... */ + /* Don't bother if it doesn't look like [ind]=value */ + if (l->word->word[0] != '[' || mbschr (l->word->word, '=') == 0) /* ] */ + continue; + nword = quote_assign (l->word->word); + free (l->word->word); + l->word->word = nword; + } +} + +/* skipsubscript moved to subst.c to use private functions. 2009/02/24. */ + +/* This function is called with SUB pointing to just after the beginning + `[' of an array subscript and removes the array element to which SUB + expands from array VAR. A subscript of `*' or `@' unsets the array. */ +int +unbind_array_element (var, sub) + SHELL_VAR *var; + char *sub; +{ + int len; + arrayind_t ind; + char *akey; + ARRAY_ELEMENT *ae; + + len = skipsubscript (sub, 0, 0); + if (sub[len] != ']' || len == 0) + { + builtin_error ("%s[%s: %s", var->name, sub, _(bash_badsub_errmsg)); + return -1; + } + sub[len] = '\0'; + + if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0) + { + unbind_variable (var->name); + return (0); + } + + if (assoc_p (var)) + { + akey = expand_assignment_string_to_string (sub, 0); /* [ */ + if (akey == 0 || *akey == 0) + { + builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg)); + FREE (akey); + return -1; + } + assoc_remove (assoc_cell (var), akey); + free (akey); + } + else + { + ind = array_expand_index (var, sub, len+1); + if (ind < 0) + { + builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg)); + return -1; + } + ae = array_remove (array_cell (var), ind); + if (ae) + array_dispose_element (ae); + } + + return 0; +} + +/* Format and output an array assignment in compound form VAR=(VALUES), + suitable for re-use as input. */ +void +print_array_assignment (var, quoted) + SHELL_VAR *var; + int quoted; +{ + char *vstr; + + vstr = array_to_assign (array_cell (var), quoted); + + if (vstr == 0) + printf ("%s=%s\n", var->name, quoted ? "'()'" : "()"); + else + { + printf ("%s=%s\n", var->name, vstr); + free (vstr); + } +} + +/* Format and output an associative array assignment in compound form + VAR=(VALUES), suitable for re-use as input. */ +void +print_assoc_assignment (var, quoted) + SHELL_VAR *var; + int quoted; +{ + char *vstr; + + vstr = assoc_to_assign (assoc_cell (var), quoted); + + if (vstr == 0) + printf ("%s=%s\n", var->name, quoted ? "'()'" : "()"); + else + { + printf ("%s=%s\n", var->name, vstr); + free (vstr); + } +} + +/***********************************************************************/ +/* */ +/* Utility functions to manage arrays and their contents for expansion */ +/* */ +/***********************************************************************/ + +/* Return 1 if NAME is a properly-formed array reference v[sub]. */ +int +valid_array_reference (name) + char *name; +{ + char *t; + int r, len; + + t = mbschr (name, '['); /* ] */ + if (t) + { + *t = '\0'; + r = legal_identifier (name); + *t = '['; + if (r == 0) + return 0; + /* Check for a properly-terminated non-blank subscript. */ + len = skipsubscript (t, 0, 0); + if (t[len] != ']' || len == 1) + return 0; + for (r = 1; r < len; r++) + if (whitespace (t[r]) == 0) + return 1; + return 0; + } + return 0; +} + +/* Expand the array index beginning at S and extending LEN characters. */ +arrayind_t +array_expand_index (var, s, len) + SHELL_VAR *var; + char *s; + int len; +{ + char *exp, *t; + int expok; + arrayind_t val; + + exp = (char *)xmalloc (len); + strncpy (exp, s, len - 1); + exp[len - 1] = '\0'; + t = expand_arith_string (exp, 0); + this_command_name = (char *)NULL; + val = evalexp (t, &expok); + free (t); + free (exp); + if (expok == 0) + { + last_command_exit_value = EXECUTION_FAILURE; + + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + return val; +} + +/* Return the name of the variable specified by S without any subscript. + If SUBP is non-null, return a pointer to the start of the subscript + in *SUBP. If LENP is non-null, the length of the subscript is returned + in *LENP. This returns newly-allocated memory. */ +char * +array_variable_name (s, subp, lenp) + char *s, **subp; + int *lenp; +{ + char *t, *ret; + int ind, ni; + + t = mbschr (s, '['); + if (t == 0) + { + if (subp) + *subp = t; + if (lenp) + *lenp = 0; + return ((char *)NULL); + } + ind = t - s; + ni = skipsubscript (s, ind, 0); + if (ni <= ind + 1 || s[ni] != ']') + { + err_badarraysub (s); + if (subp) + *subp = t; + if (lenp) + *lenp = 0; + return ((char *)NULL); + } + + *t = '\0'; + ret = savestring (s); + *t++ = '['; /* ] */ + + if (subp) + *subp = t; + if (lenp) + *lenp = ni - ind; + + return ret; +} + +/* Return the variable specified by S without any subscript. If SUBP is + non-null, return a pointer to the start of the subscript in *SUBP. + If LENP is non-null, the length of the subscript is returned in *LENP. */ +SHELL_VAR * +array_variable_part (s, subp, lenp) + char *s, **subp; + int *lenp; +{ + char *t; + SHELL_VAR *var; + + t = array_variable_name (s, subp, lenp); + if (t == 0) + return ((SHELL_VAR *)NULL); + var = find_variable (t); + + free (t); + return (var == 0 || invisible_p (var)) ? (SHELL_VAR *)0 : var; +} + +#define INDEX_ERROR() \ + do \ + { \ + if (var) \ + err_badarraysub (var->name); \ + else \ + { \ + t[-1] = '\0'; \ + err_badarraysub (s); \ + t[-1] = '['; /* ] */\ + } \ + return ((char *)NULL); \ + } \ + while (0) + +/* Return a string containing the elements in the array and subscript + described by S. If the subscript is * or @, obeys quoting rules akin + to the expansion of $* and $@ including double quoting. If RTYPE + is non-null it gets 1 if the array reference is name[*], 2 if the + reference is name[@], and 0 otherwise. */ +static char * +array_value_internal (s, quoted, flags, rtype, indp) + char *s; + int quoted, flags, *rtype; + arrayind_t *indp; +{ + int len; + arrayind_t ind; + char *akey; + char *retval, *t, *temp; + WORD_LIST *l; + SHELL_VAR *var; + + var = array_variable_part (s, &t, &len); + + /* Expand the index, even if the variable doesn't exist, in case side + effects are needed, like ${w[i++]} where w is unset. */ +#if 0 + if (var == 0) + return (char *)NULL; +#endif + + if (len == 0) + return ((char *)NULL); /* error message already printed */ + + /* [ */ + akey = 0; + if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']') + { + if (rtype) + *rtype = (t[0] == '*') ? 1 : 2; + if ((flags & AV_ALLOWALL) == 0) + { + err_badarraysub (s); + return ((char *)NULL); + } + else if (var == 0 || value_cell (var) == 0) /* XXX - check for invisible_p(var) ? */ + return ((char *)NULL); + else if (array_p (var) == 0 && assoc_p (var) == 0) + l = add_string_to_list (value_cell (var), (WORD_LIST *)NULL); + else if (assoc_p (var)) + { + l = assoc_to_word_list (assoc_cell (var)); + if (l == (WORD_LIST *)NULL) + return ((char *)NULL); + } + else + { + l = array_to_word_list (array_cell (var)); + if (l == (WORD_LIST *)NULL) + return ((char *) NULL); + } + + if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + { + temp = string_list_dollar_star (l); + retval = quote_string (temp); + free (temp); + } + else /* ${name[@]} or unquoted ${name[*]} */ + retval = string_list_dollar_at (l, quoted); + + dispose_words (l); + } + else + { + if (rtype) + *rtype = 0; + if (var == 0 || array_p (var) || assoc_p (var) == 0) + { + if ((flags & AV_USEIND) == 0 || indp == 0) + { + ind = array_expand_index (var, t, len); + if (ind < 0) + { + /* negative subscripts to indexed arrays count back from end */ + if (var && array_p (var)) + ind = array_max_index (array_cell (var)) + 1 + ind; + if (ind < 0) + INDEX_ERROR(); + } + if (indp) + *indp = ind; + } + else if (indp) + ind = *indp; + } + else if (assoc_p (var)) + { + t[len - 1] = '\0'; + akey = expand_assignment_string_to_string (t, 0); /* [ */ + t[len - 1] = ']'; + if (akey == 0 || *akey == 0) + { + FREE (akey); + INDEX_ERROR(); + } + } + + if (var == 0 || value_cell (var) == 0) /* XXX - check invisible_p(var) ? */ + { + FREE (akey); + return ((char *)NULL); + } + if (array_p (var) == 0 && assoc_p (var) == 0) + return (ind == 0 ? value_cell (var) : (char *)NULL); + else if (assoc_p (var)) + { + retval = assoc_reference (assoc_cell (var), akey); + free (akey); + } + else + retval = array_reference (array_cell (var), ind); + } + + return retval; +} + +/* Return a string containing the elements described by the array and + subscript contained in S, obeying quoting for subscripts * and @. */ +char * +array_value (s, quoted, flags, rtype, indp) + char *s; + int quoted, flags, *rtype; + arrayind_t *indp; +{ + return (array_value_internal (s, quoted, flags|AV_ALLOWALL, rtype, indp)); +} + +/* Return the value of the array indexing expression S as a single string. + If (FLAGS & AV_ALLOWALL) is 0, do not allow `@' and `*' subscripts. This + is used by other parts of the shell such as the arithmetic expression + evaluator in expr.c. */ +char * +get_array_value (s, flags, rtype, indp) + char *s; + int flags, *rtype; + arrayind_t *indp; +{ + return (array_value_internal (s, 0, flags, rtype, indp)); +} + +char * +array_keys (s, quoted) + char *s; + int quoted; +{ + int len; + char *retval, *t, *temp; + WORD_LIST *l; + SHELL_VAR *var; + + var = array_variable_part (s, &t, &len); + + /* [ */ + if (var == 0 || ALL_ELEMENT_SUB (t[0]) == 0 || t[1] != ']') + return (char *)NULL; + + if (var_isset (var) == 0 || invisible_p (var)) + return (char *)NULL; + + if (array_p (var) == 0 && assoc_p (var) == 0) + l = add_string_to_list ("0", (WORD_LIST *)NULL); + else if (assoc_p (var)) + l = assoc_keys_to_word_list (assoc_cell (var)); + else + l = array_keys_to_word_list (array_cell (var)); + if (l == (WORD_LIST *)NULL) + return ((char *) NULL); + + if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + { + temp = string_list_dollar_star (l); + retval = quote_string (temp); + free (temp); + } + else /* ${!name[@]} or unquoted ${!name[*]} */ + retval = string_list_dollar_at (l, quoted); + + dispose_words (l); + return retval; +} +#endif /* ARRAY_VARS */ diff --git a/assoc.c b/assoc.c index 5d3cd11bc..f9ed8816e 100644 --- a/assoc.c +++ b/assoc.c @@ -533,7 +533,7 @@ assoc_to_string (h, sep, quoted) return (savestring ("")); result = NULL; - list = NULL; + l = list = NULL; /* This might be better implemented directly, but it's simple to implement by converting to a word list first, possibly quoting the data, then using list_string */ @@ -551,6 +551,8 @@ assoc_to_string (h, sep, quoted) l = REVERSE_LIST(list, WORD_LIST *); result = l ? string_list_internal (l, sep) : savestring (""); + dispose_words (l); + return result; } diff --git a/assoc.c~ b/assoc.c~ new file mode 100644 index 000000000..5d3cd11bc --- /dev/null +++ b/assoc.c~ @@ -0,0 +1,557 @@ +/* + * assoc.c - functions to manipulate associative arrays + * + * Associative arrays are standard shell hash tables. + * + * Chet Ramey + * chet@ins.cwru.edu + */ + +/* Copyright (C) 2008,2009,2011 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (ARRAY_VARS) + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include "bashansi.h" + +#include "shell.h" +#include "array.h" +#include "assoc.h" +#include "builtins/common.h" + +static WORD_LIST *assoc_to_word_list_internal __P((HASH_TABLE *, int)); + +/* assoc_create == hash_create */ + +void +assoc_dispose (hash) + HASH_TABLE *hash; +{ + if (hash) + { + hash_flush (hash, 0); + hash_dispose (hash); + } +} + +void +assoc_flush (hash) + HASH_TABLE *hash; +{ + hash_flush (hash, 0); +} + +int +assoc_insert (hash, key, value) + HASH_TABLE *hash; + char *key; + char *value; +{ + BUCKET_CONTENTS *b; + + b = hash_search (key, hash, HASH_CREATE); + if (b == 0) + return -1; + /* If we are overwriting an existing element's value, we're not going to + use the key. Nothing in the array assignment code path frees the key + string, so we can free it here to avoid a memory leak. */ + if (b->key != key) + free (key); + FREE (b->data); + b->data = value ? savestring (value) : (char *)0; + return (0); +} + +/* Like assoc_insert, but returns b->data instead of freeing it */ +PTR_T +assoc_replace (hash, key, value) + HASH_TABLE *hash; + char *key; + char *value; +{ + BUCKET_CONTENTS *b; + PTR_T t; + + b = hash_search (key, hash, HASH_CREATE); + if (b == 0) + return (PTR_T)0; + /* If we are overwriting an existing element's value, we're not going to + use the key. Nothing in the array assignment code path frees the key + string, so we can free it here to avoid a memory leak. */ + if (b->key != key) + free (key); + t = b->data; + b->data = value ? savestring (value) : (char *)0; + return t; +} + +void +assoc_remove (hash, string) + HASH_TABLE *hash; + char *string; +{ + BUCKET_CONTENTS *b; + + b = hash_remove (string, hash, 0); + if (b) + { + free ((char *)b->data); + free (b->key); + free (b); + } +} + +char * +assoc_reference (hash, string) + HASH_TABLE *hash; + char *string; +{ + BUCKET_CONTENTS *b; + + if (hash == 0) + return (char *)0; + + b = hash_search (string, hash, 0); + return (b ? (char *)b->data : 0); +} + +/* Quote the data associated with each element of the hash table ASSOC, + using quote_string */ +HASH_TABLE * +assoc_quote (h) + HASH_TABLE *h; +{ + int i; + BUCKET_CONTENTS *tlist; + char *t; + + if (h == 0 || assoc_empty (h)) + return ((HASH_TABLE *)NULL); + + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + t = quote_string ((char *)tlist->data); + FREE (tlist->data); + tlist->data = t; + } + + return h; +} + +/* Quote escape characters in the data associated with each element + of the hash table ASSOC, using quote_escapes */ +HASH_TABLE * +assoc_quote_escapes (h) + HASH_TABLE *h; +{ + int i; + BUCKET_CONTENTS *tlist; + char *t; + + if (h == 0 || assoc_empty (h)) + return ((HASH_TABLE *)NULL); + + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + t = quote_escapes ((char *)tlist->data); + FREE (tlist->data); + tlist->data = t; + } + + return h; +} + +HASH_TABLE * +assoc_dequote (h) + HASH_TABLE *h; +{ + int i; + BUCKET_CONTENTS *tlist; + char *t; + + if (h == 0 || assoc_empty (h)) + return ((HASH_TABLE *)NULL); + + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + t = dequote_string ((char *)tlist->data); + FREE (tlist->data); + tlist->data = t; + } + + return h; +} + +HASH_TABLE * +assoc_dequote_escapes (h) + HASH_TABLE *h; +{ + int i; + BUCKET_CONTENTS *tlist; + char *t; + + if (h == 0 || assoc_empty (h)) + return ((HASH_TABLE *)NULL); + + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + t = dequote_escapes ((char *)tlist->data); + FREE (tlist->data); + tlist->data = t; + } + + return h; +} + +HASH_TABLE * +assoc_remove_quoted_nulls (h) + HASH_TABLE *h; +{ + int i; + BUCKET_CONTENTS *tlist; + char *t; + + if (h == 0 || assoc_empty (h)) + return ((HASH_TABLE *)NULL); + + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + t = remove_quoted_nulls ((char *)tlist->data); + tlist->data = t; + } + + return h; +} + +/* + * Return a string whose elements are the members of array H beginning at + * the STARTth element and spanning NELEM members. Null elements are counted. + */ +char * +assoc_subrange (hash, start, nelem, starsub, quoted) +HASH_TABLE *hash; +arrayind_t start, nelem; +int starsub, quoted; +{ + WORD_LIST *l, *save, *h, *t; + int i, j; + char *ret; + + if (assoc_empty (hash)) + return ((char *)NULL); + + save = l = assoc_to_word_list (hash); + if (save == 0) + return ((char *)NULL); + + for (i = 1; l && i < start; i++) + l = l->next; + if (l == 0) + return ((char *)NULL); + for (j = 0,h = t = l; l && j < nelem; j++) + { + t = l; + l = l->next; + } + + t->next = (WORD_LIST *)NULL; + + ret = string_list_pos_params (starsub ? '*' : '@', h, quoted); + + if (t != l) + t->next = l; + + dispose_words (save); + return (ret); + +} + +char * +assoc_patsub (h, pat, rep, mflags) + HASH_TABLE *h; + char *pat, *rep; + int mflags; +{ + BUCKET_CONTENTS *tlist; + int i, slen; + HASH_TABLE *h2; + char *t, *sifs, *ifs; + + if (h == 0 || assoc_empty (h)) + return ((char *)NULL); + + h2 = assoc_copy (h); + for (i = 0; i < h2->nbuckets; i++) + for (tlist = hash_items (i, h2); tlist; tlist = tlist->next) + { + t = pat_subst ((char *)tlist->data, pat, rep, mflags); + FREE (tlist->data); + tlist->data = t; + } + + if (mflags & MATCH_QUOTED) + assoc_quote (h2); + else + assoc_quote_escapes (h2); + + if (mflags & MATCH_STARSUB) + { + assoc_remove_quoted_nulls (h2); + sifs = ifs_firstchar ((int *)NULL); + t = assoc_to_string (h2, sifs, 0); + free (sifs); + } + else if (mflags & MATCH_QUOTED) + { + /* ${array[@]} */ + sifs = ifs_firstchar (&slen); + ifs = getifs (); + if (ifs == 0 || *ifs == 0) + { + if (slen < 2) + sifs = xrealloc (sifs, 2); + sifs[0] = ' '; + sifs[1] = '\0'; + } + t = assoc_to_string (h2, sifs, 0); + free(sifs); + } + else + t = assoc_to_string (h2, " ", 0); + + assoc_dispose (h2); + + return t; +} + +char * +assoc_modcase (h, pat, modop, mflags) + HASH_TABLE *h; + char *pat; + int modop; + int mflags; +{ + BUCKET_CONTENTS *tlist; + int i, slen; + HASH_TABLE *h2; + char *t, *sifs, *ifs; + + if (h == 0 || assoc_empty (h)) + return ((char *)NULL); + + h2 = assoc_copy (h); + for (i = 0; i < h2->nbuckets; i++) + for (tlist = hash_items (i, h2); tlist; tlist = tlist->next) + { + t = sh_modcase ((char *)tlist->data, pat, modop); + FREE (tlist->data); + tlist->data = t; + } + + if (mflags & MATCH_QUOTED) + assoc_quote (h2); + else + assoc_quote_escapes (h2); + + if (mflags & MATCH_STARSUB) + { + assoc_remove_quoted_nulls (h2); + sifs = ifs_firstchar ((int *)NULL); + t = assoc_to_string (h2, sifs, 0); + free (sifs); + } + else if (mflags & MATCH_QUOTED) + { + /* ${array[@]} */ + sifs = ifs_firstchar (&slen); + ifs = getifs (); + if (ifs == 0 || *ifs == 0) + { + if (slen < 2) + sifs = xrealloc (sifs, 2); + sifs[0] = ' '; + sifs[1] = '\0'; + } + t = assoc_to_string (h2, sifs, 0); + free(sifs); + } + else + t = assoc_to_string (h2, " ", 0); + + assoc_dispose (h2); + + return t; +} + +char * +assoc_to_assign (hash, quoted) + HASH_TABLE *hash; + int quoted; +{ + char *ret; + char *istr, *vstr; + int i, rsize, rlen, elen; + BUCKET_CONTENTS *tlist; + + if (hash == 0 || assoc_empty (hash)) + return (char *)0; + + ret = xmalloc (rsize = 128); + ret[0] = '('; + rlen = 1; + + for (i = 0; i < hash->nbuckets; i++) + for (tlist = hash_items (i, hash); tlist; tlist = tlist->next) + { +#if 1 + if (sh_contains_shell_metas (tlist->key)) + istr = sh_double_quote (tlist->key); + else + istr = tlist->key; +#else + istr = tlist->key; +#endif + vstr = tlist->data ? sh_double_quote ((char *)tlist->data) : (char *)0; + + elen = STRLEN (istr) + 8 + STRLEN (vstr); + RESIZE_MALLOCED_BUFFER (ret, rlen, (elen+1), rsize, rsize); + + ret[rlen++] = '['; + strcpy (ret+rlen, istr); + rlen += STRLEN (istr); + ret[rlen++] = ']'; + ret[rlen++] = '='; + if (vstr) + { + strcpy (ret + rlen, vstr); + rlen += STRLEN (vstr); + } + ret[rlen++] = ' '; + + + if (istr != tlist->key) + FREE (istr); + + FREE (vstr); + } + + RESIZE_MALLOCED_BUFFER (ret, rlen, 1, rsize, 8); + ret[rlen++] = ')'; + ret[rlen] = '\0'; + + if (quoted) + { + vstr = sh_single_quote (ret); + free (ret); + ret = vstr; + } + + return ret; +} + +static WORD_LIST * +assoc_to_word_list_internal (h, t) + HASH_TABLE *h; + int t; +{ + WORD_LIST *list; + int i; + BUCKET_CONTENTS *tlist; + char *w; + + if (h == 0 || assoc_empty (h)) + return((WORD_LIST *)NULL); + list = (WORD_LIST *)NULL; + + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + w = (t == 0) ? (char *)tlist->data : (char *)tlist->key; + list = make_word_list (make_bare_word(w), list); + } + return (REVERSE_LIST(list, WORD_LIST *)); +} + +WORD_LIST * +assoc_to_word_list (h) + HASH_TABLE *h; +{ + return (assoc_to_word_list_internal (h, 0)); +} + +WORD_LIST * +assoc_keys_to_word_list (h) + HASH_TABLE *h; +{ + return (assoc_to_word_list_internal (h, 1)); +} + +char * +assoc_to_string (h, sep, quoted) + HASH_TABLE *h; + char *sep; + int quoted; +{ + BUCKET_CONTENTS *tlist; + int i; + char *result, *t, *w; + WORD_LIST *list, *l; + + if (h == 0) + return ((char *)NULL); + if (assoc_empty (h)) + return (savestring ("")); + + result = NULL; + list = NULL; + /* This might be better implemented directly, but it's simple to implement + by converting to a word list first, possibly quoting the data, then + using list_string */ + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + w = (char *)tlist->data; + if (w == 0) + continue; + t = quoted ? quote_string (w) : savestring (w); + list = make_word_list (make_bare_word(t), list); + FREE (t); + } + + l = REVERSE_LIST(list, WORD_LIST *); + + result = l ? string_list_internal (l, sep) : savestring (""); + return result; +} + +#endif /* ARRAY_VARS */ diff --git a/bashline.c b/bashline.c index 70dce90b1..982c82283 100644 --- a/bashline.c +++ b/bashline.c @@ -1807,7 +1807,7 @@ globword: { if (executable_or_directory (val)) { - if (*hint_text == '~') + if (*hint_text == '~' && directory_part) { temp = restore_tilde (val, directory_part); free (val); @@ -2773,8 +2773,11 @@ bash_directory_expansion (dirname) d = savestring (*dirname); - if (rl_directory_rewrite_hook) - (*rl_directory_rewrite_hook) (&d); + if ((rl_directory_rewrite_hook) && (*rl_directory_rewrite_hook) (&d)) + { + free (*dirname); + *dirname = d; + } else if (rl_directory_completion_hook && (*rl_directory_completion_hook) (&d)) { free (*dirname); @@ -3904,12 +3907,16 @@ bind_keyseq_to_unix_command (line) if (line[i] != ':') { builtin_error (_("%s: missing colon separator"), line); + FREE (kseq); return -1; } i = isolate_sequence (line, i + 1, 0, &kstart); if (i < 0) - return -1; + { + FREE (kseq); + return -1; + } /* Create the value string containing the command to execute. */ value = substring (line, kstart, i); @@ -3920,7 +3927,8 @@ bind_keyseq_to_unix_command (line) /* and bind the key sequence in the current keymap to a function that understands how to execute from CMD_XMAP */ rl_bind_keyseq_in_map (kseq, bash_execute_unix_command, kmap); - + + free (kseq); return 0; } diff --git a/bashline.c~ b/bashline.c~ index bec1b655d..58c5ba5a5 100644 --- a/bashline.c~ +++ b/bashline.c~ @@ -526,9 +526,8 @@ initialize_readline () set_directory_hook (); rl_filename_rewrite_hook = bash_filename_rewrite_hook; -#if 0 + rl_filename_stat_hook = bash_filename_stat_hook; -#endif /* Tell the filename completer we want a chance to ignore some names. */ rl_ignore_some_completions_function = filename_completion_ignore; @@ -1340,6 +1339,7 @@ attempt_shell_completion (text, start, end) rl_filename_quote_characters = default_filename_quote_characters; set_filename_bstab (rl_filename_quote_characters); + set_directory_hook (); /* Determine if this could be a command word. It is if it appears at the start of the line (ignoring preceding whitespace), or if it @@ -1671,6 +1671,12 @@ command_word_completion_function (hint_text, state) } else { + if (dircomplete_expand && path_dot_or_dotdot (filename_hint)) + { + dircomplete_expand = 0; + set_directory_hook (); + dircomplete_expand = 1; + } mapping_over = 4; goto inner; } @@ -1801,7 +1807,7 @@ globword: { if (executable_or_directory (val)) { - if (*hint_text == '~') + if (*hint_text == '~' && directory_part) { temp = restore_tilde (val, directory_part); free (val); @@ -1871,6 +1877,9 @@ globword: inner: val = rl_filename_completion_function (filename_hint, istate); + if (mapping_over == 4 && dircomplete_expand) + set_directory_hook (); + istate = 1; if (val == 0) @@ -2029,7 +2038,7 @@ command_subst_completion_function (text, state) rl_completion_suppress_append = 1; } - if (!matches || !matches[cmd_index]) + if (matches == 0 || matches[cmd_index] == 0) { rl_filename_quoting_desired = 0; /* disable quoting */ return ((char *)NULL); diff --git a/builtins/cd.def b/builtins/cd.def index 27b9eef23..fb0caaac2 100644 --- a/builtins/cd.def +++ b/builtins/cd.def @@ -1,7 +1,7 @@ This file is cd.def, from which is created cd.c. It implements the builtins "cd" and "pwd" in Bash. -Copyright (C) 1987-2010 Free Software Foundation, Inc. +Copyright (C) 1987-2011 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -338,6 +338,7 @@ cd_builtin (list) if (temp && change_to_directory (temp, no_symlinks)) { printf ("%s\n", temp); + free (temp); return (bindpwd (no_symlinks)); } else @@ -406,7 +407,11 @@ pwd_builtin (list) the file system has changed state underneath bash). */ if ((tcwd && directory == 0) || (posixly_correct && same_file (".", tcwd, (struct stat *)0, (struct stat *)0) == 0)) - directory = resetpwd ("pwd"); + { + if (directory && directory != tcwd) + free (directory); + directory = resetpwd ("pwd"); + } #undef tcwd @@ -495,6 +500,8 @@ change_to_directory (newdir, nolinks) t = resetpwd ("cd"); if (t == 0) set_working_directory (tdir); + else + free (t); } else set_working_directory (tdir); diff --git a/builtins/cd.def~ b/builtins/cd.def~ index b1aae26d0..7756931aa 100644 --- a/builtins/cd.def~ +++ b/builtins/cd.def~ @@ -1,7 +1,7 @@ This file is cd.def, from which is created cd.c. It implements the builtins "cd" and "pwd" in Bash. -Copyright (C) 1987-2010 Free Software Foundation, Inc. +Copyright (C) 1987-2011 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -290,7 +290,8 @@ cd_builtin (list) free (temp); } -#if 0 /* changed for bash-4.2 Posix cd description steps 5-6 */ +#if 0 + /* changed for bash-4.2 Posix cd description steps 5-6 */ /* POSIX.2 says that if `.' does not appear in $CDPATH, we don't try the current directory, so we just punt now with an error message if POSIXLY_CORRECT is non-zero. The check for cdpath[0] @@ -494,6 +495,8 @@ change_to_directory (newdir, nolinks) t = resetpwd ("cd"); if (t == 0) set_working_directory (tdir); + else + free (t); } else set_working_directory (tdir); diff --git a/builtins/complete.def b/builtins/complete.def index 8d4ec3cd5..f5b323fea 100644 --- a/builtins/complete.def +++ b/builtins/complete.def @@ -1,7 +1,7 @@ This file is complete.def, from which is created complete.c. It implements the builtins "complete", "compgen", and "compopt" in Bash. -Copyright (C) 1999-2010 Free Software Foundation, Inc. +Copyright (C) 1999-2011 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -728,6 +728,7 @@ compgen_builtin (list) if ((sl == 0 || sl->list_len == 0) && (copts & COPT_DEFAULT)) { matches = rl_completion_matches (word, rl_filename_completion_function); + strlist_dispose (sl); sl = completions_to_stringlist (matches); strvec_dispose (matches); } diff --git a/builtins/complete.def~ b/builtins/complete.def~ new file mode 100644 index 000000000..8d4ec3cd5 --- /dev/null +++ b/builtins/complete.def~ @@ -0,0 +1,869 @@ +This file is complete.def, from which is created complete.c. +It implements the builtins "complete", "compgen", and "compopt" in Bash. + +Copyright (C) 1999-2010 Free Software Foundation, Inc. + +This file is part of GNU Bash, the Bourne Again SHell. + +Bash is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Bash is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Bash. If not, see . + +$PRODUCES complete.c + +$BUILTIN complete +$DEPENDS_ON PROGRAMMABLE_COMPLETION +$FUNCTION complete_builtin +$SHORT_DOC complete [-abcdefgjksuv] [-pr] [-DE] [-o option] [-A action] [-G globpat] [-W wordlist] [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [name ...] +Specify how arguments are to be completed by Readline. + +For each NAME, specify how arguments are to be completed. If no options +are supplied, existing completion specifications are printed in a way that +allows them to be reused as input. + +Options: + -p print existing completion specifications in a reusable format + -r remove a completion specification for each NAME, or, if no + NAMEs are supplied, all completion specifications + -D apply the completions and actions as the default for commands + without any specific completion defined + -E apply the completions and actions to "empty" commands -- + completion attempted on a blank line + +When completion is attempted, the actions are applied in the order the +uppercase-letter options are listed above. The -D option takes +precedence over -E. + +Exit Status: +Returns success unless an invalid option is supplied or an error occurs. +$END + +#include + +#include + +#include "../bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "../bashansi.h" +#include "../bashintl.h" + +#include "../shell.h" +#include "../builtins.h" +#include "../pcomplete.h" +#include "../bashline.h" + +#include "common.h" +#include "bashgetopt.h" + +#include + +#define STRDUP(x) ((x) ? savestring (x) : (char *)NULL) + +/* Structure containing all the non-action (binary) options; filled in by + build_actions(). */ +struct _optflags { + int pflag; + int rflag; + int Dflag; + int Eflag; +}; + +static int find_compact __P((char *)); +static int find_compopt __P((char *)); + +static int build_actions __P((WORD_LIST *, struct _optflags *, unsigned long *, unsigned long *)); + +static int remove_cmd_completions __P((WORD_LIST *)); + +static int print_one_completion __P((char *, COMPSPEC *)); +static int print_compitem __P((BUCKET_CONTENTS *)); +static void print_compopts __P((const char *, COMPSPEC *, int)); +static void print_all_completions __P((void)); +static int print_cmd_completions __P((WORD_LIST *)); + +static char *Garg, *Warg, *Parg, *Sarg, *Xarg, *Farg, *Carg; + +static const struct _compacts { + const char * const actname; + int actflag; + int actopt; +} compacts[] = { + { "alias", CA_ALIAS, 'a' }, + { "arrayvar", CA_ARRAYVAR, 0 }, + { "binding", CA_BINDING, 0 }, + { "builtin", CA_BUILTIN, 'b' }, + { "command", CA_COMMAND, 'c' }, + { "directory", CA_DIRECTORY, 'd' }, + { "disabled", CA_DISABLED, 0 }, + { "enabled", CA_ENABLED, 0 }, + { "export", CA_EXPORT, 'e' }, + { "file", CA_FILE, 'f' }, + { "function", CA_FUNCTION, 0 }, + { "helptopic", CA_HELPTOPIC, 0 }, + { "hostname", CA_HOSTNAME, 0 }, + { "group", CA_GROUP, 'g' }, + { "job", CA_JOB, 'j' }, + { "keyword", CA_KEYWORD, 'k' }, + { "running", CA_RUNNING, 0 }, + { "service", CA_SERVICE, 's' }, + { "setopt", CA_SETOPT, 0 }, + { "shopt", CA_SHOPT, 0 }, + { "signal", CA_SIGNAL, 0 }, + { "stopped", CA_STOPPED, 0 }, + { "user", CA_USER, 'u' }, + { "variable", CA_VARIABLE, 'v' }, + { (char *)NULL, 0, 0 }, +}; + +/* This should be a STRING_INT_ALIST */ +static const struct _compopt { + const char * const optname; + int optflag; +} compopts[] = { + { "bashdefault", COPT_BASHDEFAULT }, + { "default", COPT_DEFAULT }, + { "dirnames", COPT_DIRNAMES }, + { "filenames",COPT_FILENAMES}, + { "nospace", COPT_NOSPACE }, + { "plusdirs", COPT_PLUSDIRS }, + { (char *)NULL, 0 }, +}; + +static int +find_compact (name) + char *name; +{ + register int i; + + for (i = 0; compacts[i].actname; i++) + if (STREQ (name, compacts[i].actname)) + return i; + return -1; +} + +static int +find_compopt (name) + char *name; +{ + register int i; + + for (i = 0; compopts[i].optname; i++) + if (STREQ (name, compopts[i].optname)) + return i; + return -1; +} + +/* Build the actions and compspec options from the options specified in LIST. + ACTP is a pointer to an unsigned long in which to place the bitmap of + actions. OPTP is a pointer to an unsigned long in which to place the + btmap of compspec options (arguments to `-o'). PP, if non-null, gets 1 + if -p is supplied; RP, if non-null, gets 1 if -r is supplied. + If either is null, the corresponding option generates an error. + This also sets variables corresponding to options that take arguments as + a side effect; the caller should ensure that those variables are set to + NULL before calling build_actions. Return value: + EX_USAGE = bad option + EXECUTION_SUCCESS = some options supplied + EXECUTION_FAILURE = no options supplied +*/ + +static int +build_actions (list, flagp, actp, optp) + WORD_LIST *list; + struct _optflags *flagp; + unsigned long *actp, *optp; +{ + int opt, ind, opt_given; + unsigned long acts, copts; + + acts = copts = (unsigned long)0L; + opt_given = 0; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "abcdefgjko:prsuvA:G:W:P:S:X:F:C:DE")) != -1) + { + opt_given = 1; + switch (opt) + { + case 'r': + if (flagp) + { + flagp->rflag = 1; + break; + } + else + { + sh_invalidopt ("-r"); + builtin_usage (); + return (EX_USAGE); + } + + case 'p': + if (flagp) + { + flagp->pflag = 1; + break; + } + else + { + sh_invalidopt ("-p"); + builtin_usage (); + return (EX_USAGE); + } + + case 'a': + acts |= CA_ALIAS; + break; + case 'b': + acts |= CA_BUILTIN; + break; + case 'c': + acts |= CA_COMMAND; + break; + case 'd': + acts |= CA_DIRECTORY; + break; + case 'e': + acts |= CA_EXPORT; + break; + case 'f': + acts |= CA_FILE; + break; + case 'g': + acts |= CA_GROUP; + break; + case 'j': + acts |= CA_JOB; + break; + case 'k': + acts |= CA_KEYWORD; + break; + case 's': + acts |= CA_SERVICE; + break; + case 'u': + acts |= CA_USER; + break; + case 'v': + acts |= CA_VARIABLE; + break; + case 'o': + ind = find_compopt (list_optarg); + if (ind < 0) + { + sh_invalidoptname (list_optarg); + return (EX_USAGE); + } + copts |= compopts[ind].optflag; + break; + case 'A': + ind = find_compact (list_optarg); + if (ind < 0) + { + builtin_error (_("%s: invalid action name"), list_optarg); + return (EX_USAGE); + } + acts |= compacts[ind].actflag; + break; + case 'C': + Carg = list_optarg; + break; + case 'D': + if (flagp) + { + flagp->Dflag = 1; + break; + } + else + { + sh_invalidopt ("-D"); + builtin_usage (); + return (EX_USAGE); + } + case 'E': + if (flagp) + { + flagp->Eflag = 1; + break; + } + else + { + sh_invalidopt ("-E"); + builtin_usage (); + return (EX_USAGE); + } + case 'F': + Farg = list_optarg; + break; + case 'G': + Garg = list_optarg; + break; + case 'P': + Parg = list_optarg; + break; + case 'S': + Sarg = list_optarg; + break; + case 'W': + Warg = list_optarg; + break; + case 'X': + Xarg = list_optarg; + break; + default: + builtin_usage (); + return (EX_USAGE); + } + } + + *actp = acts; + *optp = copts; + + return (opt_given ? EXECUTION_SUCCESS : EXECUTION_FAILURE); +} + +/* Add, remove, and display completion specifiers. */ +int +complete_builtin (list) + WORD_LIST *list; +{ + int opt_given, rval; + unsigned long acts, copts; + COMPSPEC *cs; + struct _optflags oflags; + WORD_LIST *l, *wl; + + if (list == 0) + { + print_all_completions (); + return (EXECUTION_SUCCESS); + } + + opt_given = oflags.pflag = oflags.rflag = oflags.Dflag = oflags.Eflag = 0; + + acts = copts = (unsigned long)0L; + Garg = Warg = Parg = Sarg = Xarg = Farg = Carg = (char *)NULL; + cs = (COMPSPEC *)NULL; + + /* Build the actions from the arguments. Also sets the [A-Z]arg variables + as a side effect if they are supplied as options. */ + rval = build_actions (list, &oflags, &acts, &copts); + if (rval == EX_USAGE) + return (rval); + opt_given = rval != EXECUTION_FAILURE; + + list = loptend; + + wl = oflags.Dflag ? make_word_list (make_bare_word (DEFAULTCMD), (WORD_LIST *)NULL) + : (oflags.Eflag ? make_word_list (make_bare_word (EMPTYCMD), (WORD_LIST *)NULL) : 0); + + /* -p overrides everything else */ + if (oflags.pflag || (list == 0 && opt_given == 0)) + { + if (wl) + { + rval = print_cmd_completions (wl); + dispose_words (wl); + return rval; + } + else if (list == 0) + { + print_all_completions (); + return (EXECUTION_SUCCESS); + } + return (print_cmd_completions (list)); + } + + /* next, -r overrides everything else. */ + if (oflags.rflag) + { + if (wl) + { + rval = remove_cmd_completions (wl); + dispose_words (wl); + return rval; + } + else if (list == 0) + { + progcomp_flush (); + return (EXECUTION_SUCCESS); + } + return (remove_cmd_completions (list)); + } + + if (wl == 0 && list == 0 && opt_given) + { + builtin_usage (); + return (EX_USAGE); + } + + /* If we get here, we need to build a compspec and add it for each + remaining argument. */ + cs = compspec_create (); + cs->actions = acts; + cs->options = copts; + + cs->globpat = STRDUP (Garg); + cs->words = STRDUP (Warg); + cs->prefix = STRDUP (Parg); + cs->suffix = STRDUP (Sarg); + cs->funcname = STRDUP (Farg); + cs->command = STRDUP (Carg); + cs->filterpat = STRDUP (Xarg); + + for (rval = EXECUTION_SUCCESS, l = wl ? wl : list ; l; l = l->next) + { + /* Add CS as the compspec for the specified commands. */ + if (progcomp_insert (l->word->word, cs) == 0) + rval = EXECUTION_FAILURE; + } + + dispose_words (wl); + return (rval); +} + +static int +remove_cmd_completions (list) + WORD_LIST *list; +{ + WORD_LIST *l; + int ret; + + for (ret = EXECUTION_SUCCESS, l = list; l; l = l->next) + { + if (progcomp_remove (l->word->word) == 0) + { + builtin_error (_("%s: no completion specification"), l->word->word); + ret = EXECUTION_FAILURE; + } + } + return ret; +} + +#define SQPRINTARG(a, f) \ + do { \ + if (a) \ + { \ + x = sh_single_quote (a); \ + printf ("%s %s ", f, x); \ + free (x); \ + } \ + } while (0) + +#define PRINTARG(a, f) \ + do { \ + if (a) \ + printf ("%s %s ", f, a); \ + } while (0) + +#define PRINTOPT(a, f) \ + do { \ + if (acts & a) \ + printf ("%s ", f); \ + } while (0) + +#define PRINTACT(a, f) \ + do { \ + if (acts & a) \ + printf ("-A %s ", f); \ + } while (0) + +#define PRINTCOMPOPT(a, f) \ + do { \ + if (copts & a) \ + printf ("-o %s ", f); \ + } while (0) + +#define XPRINTCOMPOPT(a, f) \ + do { \ + if (copts & a) \ + printf ("-o %s ", f); \ + else \ + printf ("+o %s ", f); \ + } while (0) + +static int +print_one_completion (cmd, cs) + char *cmd; + COMPSPEC *cs; +{ + unsigned long acts, copts; + char *x; + + printf ("complete "); + + copts = cs->options; + + /* First, print the -o options. */ + PRINTCOMPOPT (COPT_BASHDEFAULT, "bashdefault"); + PRINTCOMPOPT (COPT_DEFAULT, "default"); + PRINTCOMPOPT (COPT_DIRNAMES, "dirnames"); + PRINTCOMPOPT (COPT_FILENAMES, "filenames"); + PRINTCOMPOPT (COPT_NOSPACE, "nospace"); + PRINTCOMPOPT (COPT_PLUSDIRS, "plusdirs"); + + acts = cs->actions; + + /* simple flags next */ + PRINTOPT (CA_ALIAS, "-a"); + PRINTOPT (CA_BUILTIN, "-b"); + PRINTOPT (CA_COMMAND, "-c"); + PRINTOPT (CA_DIRECTORY, "-d"); + PRINTOPT (CA_EXPORT, "-e"); + PRINTOPT (CA_FILE, "-f"); + PRINTOPT (CA_GROUP, "-g"); + PRINTOPT (CA_JOB, "-j"); + PRINTOPT (CA_KEYWORD, "-k"); + PRINTOPT (CA_SERVICE, "-s"); + PRINTOPT (CA_USER, "-u"); + PRINTOPT (CA_VARIABLE, "-v"); + + /* now the rest of the actions */ + PRINTACT (CA_ARRAYVAR, "arrayvar"); + PRINTACT (CA_BINDING, "binding"); + PRINTACT (CA_DISABLED, "disabled"); + PRINTACT (CA_ENABLED, "enabled"); + PRINTACT (CA_FUNCTION, "function"); + PRINTACT (CA_HELPTOPIC, "helptopic"); + PRINTACT (CA_HOSTNAME, "hostname"); + PRINTACT (CA_RUNNING, "running"); + PRINTACT (CA_SETOPT, "setopt"); + PRINTACT (CA_SHOPT, "shopt"); + PRINTACT (CA_SIGNAL, "signal"); + PRINTACT (CA_STOPPED, "stopped"); + + /* now the rest of the arguments */ + + /* arguments that require quoting */ + SQPRINTARG (cs->globpat, "-G"); + SQPRINTARG (cs->words, "-W"); + SQPRINTARG (cs->prefix, "-P"); + SQPRINTARG (cs->suffix, "-S"); + SQPRINTARG (cs->filterpat, "-X"); + + SQPRINTARG (cs->command, "-C"); + + /* simple arguments that don't require quoting */ + PRINTARG (cs->funcname, "-F"); + + if (STREQ (cmd, EMPTYCMD)) + printf ("-E\n"); + else if (STREQ (cmd, DEFAULTCMD)) + printf ("-D\n"); + else + printf ("%s\n", cmd); + + return (0); +} + +static void +print_compopts (cmd, cs, full) + const char *cmd; + COMPSPEC *cs; + int full; +{ + int copts; + + printf ("compopt "); + copts = cs->options; + + if (full) + { + XPRINTCOMPOPT (COPT_BASHDEFAULT, "bashdefault"); + XPRINTCOMPOPT (COPT_DEFAULT, "default"); + XPRINTCOMPOPT (COPT_DIRNAMES, "dirnames"); + XPRINTCOMPOPT (COPT_FILENAMES, "filenames"); + XPRINTCOMPOPT (COPT_NOSPACE, "nospace"); + XPRINTCOMPOPT (COPT_PLUSDIRS, "plusdirs"); + } + else + { + PRINTCOMPOPT (COPT_BASHDEFAULT, "bashdefault"); + PRINTCOMPOPT (COPT_DEFAULT, "default"); + PRINTCOMPOPT (COPT_DIRNAMES, "dirnames"); + PRINTCOMPOPT (COPT_FILENAMES, "filenames"); + PRINTCOMPOPT (COPT_NOSPACE, "nospace"); + PRINTCOMPOPT (COPT_PLUSDIRS, "plusdirs"); + } + + if (STREQ (cmd, EMPTYCMD)) + printf ("-E\n"); + else if (STREQ (cmd, DEFAULTCMD)) + printf ("-D\n"); + else + printf ("%s\n", cmd); +} + +static int +print_compitem (item) + BUCKET_CONTENTS *item; +{ + COMPSPEC *cs; + char *cmd; + + cmd = item->key; + cs = (COMPSPEC *)item->data; + + return (print_one_completion (cmd, cs)); +} + +static void +print_all_completions () +{ + progcomp_walk (print_compitem); +} + +static int +print_cmd_completions (list) + WORD_LIST *list; +{ + WORD_LIST *l; + COMPSPEC *cs; + int ret; + + for (ret = EXECUTION_SUCCESS, l = list; l; l = l->next) + { + cs = progcomp_search (l->word->word); + if (cs) + print_one_completion (l->word->word, cs); + else + { + builtin_error (_("%s: no completion specification"), l->word->word); + ret = EXECUTION_FAILURE; + } + } + + return (sh_chkwrite (ret)); +} + +$BUILTIN compgen +$DEPENDS_ON PROGRAMMABLE_COMPLETION +$FUNCTION compgen_builtin +$SHORT_DOC compgen [-abcdefgjksuv] [-o option] [-A action] [-G globpat] [-W wordlist] [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [word] +Display possible completions depending on the options. + +Intended to be used from within a shell function generating possible +completions. If the optional WORD argument is supplied, matches against +WORD are generated. + +Exit Status: +Returns success unless an invalid option is supplied or an error occurs. +$END + +int +compgen_builtin (list) + WORD_LIST *list; +{ + int rval; + unsigned long acts, copts; + COMPSPEC *cs; + STRINGLIST *sl; + char *word, **matches; + + if (list == 0) + return (EXECUTION_SUCCESS); + + acts = copts = (unsigned long)0L; + Garg = Warg = Parg = Sarg = Xarg = Farg = Carg = (char *)NULL; + cs = (COMPSPEC *)NULL; + + /* Build the actions from the arguments. Also sets the [A-Z]arg variables + as a side effect if they are supplied as options. */ + rval = build_actions (list, (struct _optflags *)NULL, &acts, &copts); + if (rval == EX_USAGE) + return (rval); + if (rval == EXECUTION_FAILURE) + return (EXECUTION_SUCCESS); + + list = loptend; + + word = (list && list->word) ? list->word->word : ""; + + if (Farg) + builtin_error (_("warning: -F option may not work as you expect")); + if (Carg) + builtin_error (_("warning: -C option may not work as you expect")); + + /* If we get here, we need to build a compspec and evaluate it. */ + cs = compspec_create (); + cs->actions = acts; + cs->options = copts; + cs->refcount = 1; + + cs->globpat = STRDUP (Garg); + cs->words = STRDUP (Warg); + cs->prefix = STRDUP (Parg); + cs->suffix = STRDUP (Sarg); + cs->funcname = STRDUP (Farg); + cs->command = STRDUP (Carg); + cs->filterpat = STRDUP (Xarg); + + rval = EXECUTION_FAILURE; + sl = gen_compspec_completions (cs, "compgen", word, 0, 0, 0); + + /* If the compspec wants the bash default completions, temporarily + turn off programmable completion and call the bash completion code. */ + if ((sl == 0 || sl->list_len == 0) && (copts & COPT_BASHDEFAULT)) + { + matches = bash_default_completion (word, 0, 0, 0, 0); + sl = completions_to_stringlist (matches); + strvec_dispose (matches); + } + + /* This isn't perfect, but it's the best we can do, given what readline + exports from its set of completion utility functions. */ + if ((sl == 0 || sl->list_len == 0) && (copts & COPT_DEFAULT)) + { + matches = rl_completion_matches (word, rl_filename_completion_function); + sl = completions_to_stringlist (matches); + strvec_dispose (matches); + } + + if (sl) + { + if (sl->list && sl->list_len) + { + rval = EXECUTION_SUCCESS; + strlist_print (sl, (char *)NULL); + } + strlist_dispose (sl); + } + + compspec_dispose (cs); + return (rval); +} + +$BUILTIN compopt +$DEPENDS_ON PROGRAMMABLE_COMPLETION +$FUNCTION compopt_builtin +$SHORT_DOC compopt [-o|+o option] [-DE] [name ...] +Modify or display completion options. + +Modify the completion options for each NAME, or, if no NAMEs are supplied, +the completion currently being executed. If no OPTIONs are given, print +the completion options for each NAME or the current completion specification. + +Options: + -o option Set completion option OPTION for each NAME + -D Change options for the "default" command completion + -E Change options for the "empty" command completion + +Using `+o' instead of `-o' turns off the specified option. + +Arguments: + +Each NAME refers to a command for which a completion specification must +have previously been defined using the `complete' builtin. If no NAMEs +are supplied, compopt must be called by a function currently generating +completions, and the options for that currently-executing completion +generator are modified. + +Exit Status: +Returns success unless an invalid option is supplied or NAME does not +have a completion specification defined. +$END + +int +compopt_builtin (list) + WORD_LIST *list; +{ + int opts_on, opts_off, *opts, opt, oind, ret, Dflag, Eflag; + WORD_LIST *l, *wl; + COMPSPEC *cs; + + opts_on = opts_off = Eflag = Dflag = 0; + ret = EXECUTION_SUCCESS; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "+o:DE")) != EOF) + { + opts = (list_opttype == '-') ? &opts_on : &opts_off; + + switch (opt) + { + case 'o': + oind = find_compopt (list_optarg); + if (oind < 0) + { + sh_invalidoptname (list_optarg); + return (EX_USAGE); + } + *opts |= compopts[oind].optflag; + break; + case 'D': + Dflag = 1; + break; + case 'E': + Eflag = 1; + break; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + wl = Dflag ? make_word_list (make_bare_word (DEFAULTCMD), (WORD_LIST *)NULL) + : (Eflag ? make_word_list (make_bare_word (EMPTYCMD), (WORD_LIST *)NULL) : 0); + + if (list == 0 && wl == 0) + { + if (RL_ISSTATE (RL_STATE_COMPLETING) == 0 || pcomp_curcs == 0) + { + builtin_error (_("not currently executing completion function")); + return (EXECUTION_FAILURE); + } + cs = pcomp_curcs; + + if (opts_on == 0 && opts_off == 0) + { + print_compopts (pcomp_curcmd, cs, 1); + return (sh_chkwrite (ret)); + } + + /* Set the compspec options */ + pcomp_set_compspec_options (cs, opts_on, 1); + pcomp_set_compspec_options (cs, opts_off, 0); + + /* And change the readline variables the options control */ + pcomp_set_readline_variables (opts_on, 1); + pcomp_set_readline_variables (opts_off, 0); + + return (ret); + } + + for (l = wl ? wl : list; l; l = l->next) + { + cs = progcomp_search (l->word->word); + if (cs == 0) + { + builtin_error (_("%s: no completion specification"), l->word->word); + ret = EXECUTION_FAILURE; + continue; + } + if (opts_on == 0 && opts_off == 0) + { + print_compopts (l->word->word, cs, 1); + continue; /* XXX -- fill in later */ + } + + /* Set the compspec options */ + pcomp_set_compspec_options (cs, opts_on, 1); + pcomp_set_compspec_options (cs, opts_off, 0); + } + + return (ret); +} diff --git a/builtins/hash.def b/builtins/hash.def index 6724ad178..b75a42d43 100644 --- a/builtins/hash.def +++ b/builtins/hash.def @@ -276,6 +276,7 @@ list_hashed_filename_targets (list, fmt) printf ("%s\t", l->word->word); printf ("%s\n", target); } + free (target); } return (all_found ? EXECUTION_SUCCESS : EXECUTION_FAILURE); diff --git a/builtins/hash.def~ b/builtins/hash.def~ new file mode 100644 index 000000000..6724ad178 --- /dev/null +++ b/builtins/hash.def~ @@ -0,0 +1,282 @@ +This file is hash.def, from which is created hash.c. +It implements the builtin "hash" in Bash. + +Copyright (C) 1987-2010 Free Software Foundation, Inc. + +This file is part of GNU Bash, the Bourne Again SHell. + +Bash is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Bash is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Bash. If not, see . + +$PRODUCES hash.c + +$BUILTIN hash +$FUNCTION hash_builtin +$SHORT_DOC hash [-lr] [-p pathname] [-dt] [name ...] +Remember or display program locations. + +Determine and remember the full pathname of each command NAME. If +no arguments are given, information about remembered commands is displayed. + +Options: + -d forget the remembered location of each NAME + -l display in a format that may be reused as input + -p pathname use PATHNAME is the full pathname of NAME + -r forget all remembered locations + -t print the remembered location of each NAME, preceding + each location with the corresponding NAME if multiple + NAMEs are given +Arguments: + NAME Each NAME is searched for in $PATH and added to the list + of remembered commands. + +Exit Status: +Returns success unless NAME is not found or an invalid option is given. +$END + +#include + +#include + +#include "../bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include + +#include "../bashansi.h" +#include "../bashintl.h" + +#include "../shell.h" +#include "../builtins.h" +#include "../flags.h" +#include "../findcmd.h" +#include "../hashcmd.h" +#include "common.h" +#include "bashgetopt.h" + +extern int posixly_correct; +extern int dot_found_in_search; +extern char *this_command_name; + +static int add_hashed_command __P((char *, int)); +static int print_hash_info __P((BUCKET_CONTENTS *)); +static int print_portable_hash_info __P((BUCKET_CONTENTS *)); +static int print_hashed_commands __P((int)); +static int list_hashed_filename_targets __P((WORD_LIST *, int)); + +/* Print statistics on the current state of hashed commands. If LIST is + not empty, then rehash (or hash in the first place) the specified + commands. */ +int +hash_builtin (list) + WORD_LIST *list; +{ + int expunge_hash_table, list_targets, list_portably, delete, opt; + char *w, *pathname; + + if (hashing_enabled == 0) + { + builtin_error (_("hashing disabled")); + return (EXECUTION_FAILURE); + } + + expunge_hash_table = list_targets = list_portably = delete = 0; + pathname = (char *)NULL; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "dlp:rt")) != -1) + { + switch (opt) + { + case 'd': + delete = 1; + break; + case 'l': + list_portably = 1; + break; + case 'p': + pathname = list_optarg; + break; + case 'r': + expunge_hash_table = 1; + break; + case 't': + list_targets = 1; + break; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + /* hash -t requires at least one argument. */ + if (list == 0 && list_targets) + { + sh_needarg ("-t"); + return (EXECUTION_FAILURE); + } + + /* We want hash -r to be silent, but hash -- to print hashing info, so + we test expunge_hash_table. */ + if (list == 0 && expunge_hash_table == 0) + { + opt = print_hashed_commands (list_portably); + if (opt == 0 && posixly_correct == 0) + printf (_("%s: hash table empty\n"), this_command_name); + + return (EXECUTION_SUCCESS); + } + + if (expunge_hash_table) + phash_flush (); + + /* If someone runs `hash -r -t xyz' he will be disappointed. */ + if (list_targets) + return (list_hashed_filename_targets (list, list_portably)); + +#if defined (RESTRICTED_SHELL) + if (restricted && pathname && strchr (pathname, '/')) + { + sh_restricted (pathname); + return (EXECUTION_FAILURE); + } +#endif + + for (opt = EXECUTION_SUCCESS; list; list = list->next) + { + /* Add, remove or rehash the specified commands. */ + w = list->word->word; + if (absolute_program (w)) + continue; + else if (pathname) + { + if (is_directory (pathname)) + { +#ifdef EISDIR + builtin_error ("%s: %s", pathname, strerror (EISDIR)); +#else + builtin_error (_("%s: is a directory"), pathname); +#endif + opt = EXECUTION_FAILURE; + } + else + phash_insert (w, pathname, 0, 0); + } + else if (delete) + { + if (phash_remove (w)) + { + sh_notfound (w); + opt = EXECUTION_FAILURE; + } + } + else if (add_hashed_command (w, 0)) + opt = EXECUTION_FAILURE; + } + + fflush (stdout); + return (opt); +} + +static int +add_hashed_command (w, quiet) + char *w; + int quiet; +{ + int rv; + char *full_path; + + rv = 0; + if (find_function (w) == 0 && find_shell_builtin (w) == 0) + { + phash_remove (w); + full_path = find_user_command (w); + if (full_path && executable_file (full_path)) + phash_insert (w, full_path, dot_found_in_search, 0); + else + { + if (quiet == 0) + sh_notfound (w); + rv++; + } + FREE (full_path); + } + return (rv); +} + +/* Print information about current hashed info. */ +static int +print_hash_info (item) + BUCKET_CONTENTS *item; +{ + printf ("%4d\t%s\n", item->times_found, pathdata(item)->path); + return 0; +} + +static int +print_portable_hash_info (item) + BUCKET_CONTENTS *item; +{ + printf ("builtin hash -p %s %s\n", pathdata(item)->path, item->key); + return 0; +} + +static int +print_hashed_commands (fmt) + int fmt; +{ + if (hashed_filenames == 0 || HASH_ENTRIES (hashed_filenames) == 0) + return (0); + + if (fmt == 0) + printf (_("hits\tcommand\n")); + hash_walk (hashed_filenames, fmt ? print_portable_hash_info : print_hash_info); + return (1); +} + +static int +list_hashed_filename_targets (list, fmt) + WORD_LIST *list; + int fmt; +{ + int all_found, multiple; + char *target; + WORD_LIST *l; + + all_found = 1; + multiple = list->next != 0; + + for (l = list; l; l = l->next) + { + target = phash_search (l->word->word); + if (target == 0) + { + all_found = 0; + sh_notfound (l->word->word); + continue; + } + if (fmt) + printf ("builtin hash -p %s %s\n", target, l->word->word); + else + { + if (multiple) + printf ("%s\t", l->word->word); + printf ("%s\n", target); + } + } + + return (all_found ? EXECUTION_SUCCESS : EXECUTION_FAILURE); +} diff --git a/builtins/help.def b/builtins/help.def index 21cdf2755..91eb83989 100644 --- a/builtins/help.def +++ b/builtins/help.def @@ -1,7 +1,7 @@ This file is help.def, from which is created help.c. It implements the builtin "help" in Bash. -Copyright (C) 1987-2009 Free Software Foundation, Inc. +Copyright (C) 1987-2011 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -209,7 +209,7 @@ show_longdoc (i) zcatfd (fd, 1, doc[0]); close (fd); } - else + else if (doc) for (j = 0; doc[j]; j++) printf ("%*s%s\n", BASE_INDENT, " ", _(doc[j])); } diff --git a/builtins/help.def~ b/builtins/help.def~ new file mode 100644 index 000000000..21cdf2755 --- /dev/null +++ b/builtins/help.def~ @@ -0,0 +1,385 @@ +This file is help.def, from which is created help.c. +It implements the builtin "help" in Bash. + +Copyright (C) 1987-2009 Free Software Foundation, Inc. + +This file is part of GNU Bash, the Bourne Again SHell. + +Bash is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Bash is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Bash. If not, see . + +$PRODUCES help.c + +$BUILTIN help +$FUNCTION help_builtin +$DEPENDS_ON HELP_BUILTIN +$SHORT_DOC help [-dms] [pattern ...] +Display information about builtin commands. + +Displays brief summaries of builtin commands. If PATTERN is +specified, gives detailed help on all commands matching PATTERN, +otherwise the list of help topics is printed. + +Options: + -d output short description for each topic + -m display usage in pseudo-manpage format + -s output only a short usage synopsis for each topic matching + PATTERN + +Arguments: + PATTERN Pattern specifiying a help topic + +Exit Status: +Returns success unless PATTERN is not found or an invalid option is given. +$END + +#include + +#if defined (HELP_BUILTIN) +#include + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include + +#include + +#include "../bashintl.h" + +#include "../shell.h" +#include "../builtins.h" +#include "../pathexp.h" +#include "common.h" +#include "bashgetopt.h" + +#include +#include + +#ifndef errno +extern int errno; +#endif + +extern const char * const bash_copyright; +extern const char * const bash_license; + +static void show_builtin_command_help __P((void)); +static int open_helpfile __P((char *)); +static void show_desc __P((char *, int)); +static void show_manpage __P((char *, int)); +static void show_longdoc __P((int)); + +/* Print out a list of the known functions in the shell, and what they do. + If LIST is supplied, print out the list which matches for each pattern + specified. */ +int +help_builtin (list) + WORD_LIST *list; +{ + register int i; + char *pattern, *name; + int plen, match_found, sflag, dflag, mflag; + + dflag = sflag = mflag = 0; + reset_internal_getopt (); + while ((i = internal_getopt (list, "dms")) != -1) + { + switch (i) + { + case 'd': + dflag = 1; + break; + case 'm': + mflag = 1; + break; + case 's': + sflag = 1; + break; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + if (list == 0) + { + show_shell_version (0); + show_builtin_command_help (); + return (EXECUTION_SUCCESS); + } + + /* We should consider making `help bash' do something. */ + + if (glob_pattern_p (list->word->word)) + { + printf (ngettext ("Shell commands matching keyword `", "Shell commands matching keywords `", (list->next ? 2 : 1))); + print_word_list (list, ", "); + printf ("'\n\n"); + } + + for (match_found = 0, pattern = ""; list; list = list->next) + { + pattern = list->word->word; + plen = strlen (pattern); + + for (i = 0; name = shell_builtins[i].name; i++) + { + QUIT; + if ((strcmp (pattern, name) == 0) || + (strmatch (pattern, name, FNMATCH_EXTFLAG) != FNM_NOMATCH)) + { + match_found++; + if (dflag) + { + show_desc (name, i); + continue; + } + else if (mflag) + { + show_manpage (name, i); + continue; + } + + printf ("%s: %s\n", name, _(shell_builtins[i].short_doc)); + + if (sflag == 0) + show_longdoc (i); + } + } + } + + if (match_found == 0) + { + builtin_error (_("no help topics match `%s'. Try `help help' or `man -k %s' or `info %s'."), pattern, pattern, pattern); + return (EXECUTION_FAILURE); + } + + fflush (stdout); + return (EXECUTION_SUCCESS); +} + +static int +open_helpfile (name) + char *name; +{ + int fd; + + fd = open (name, O_RDONLY); + if (fd == -1) + { + builtin_error (_("%s: cannot open: %s"), name, strerror (errno)); + return -1; + } + return fd; +} + +/* By convention, enforced by mkbuiltins.c, if separate help files are being + used, the long_doc array contains one string -- the full pathname of the + help file for this builtin. */ +static void +show_longdoc (i) + int i; +{ + register int j; + char * const *doc; + int fd; + + doc = shell_builtins[i].long_doc; + + if (doc && doc[0] && *doc[0] == '/' && doc[1] == (char *)NULL) + { + fd = open_helpfile (doc[0]); + if (fd < 0) + return; + zcatfd (fd, 1, doc[0]); + close (fd); + } + else + for (j = 0; doc[j]; j++) + printf ("%*s%s\n", BASE_INDENT, " ", _(doc[j])); +} + +static void +show_desc (name, i) + char *name; + int i; +{ + register int j; + char **doc, *line; + int fd, usefile; + + doc = (char **)shell_builtins[i].long_doc; + + usefile = (doc && doc[0] && *doc[0] == '/' && doc[1] == (char *)NULL); + if (usefile) + { + fd = open_helpfile (doc[0]); + if (fd < 0) + return; + zmapfd (fd, &line, doc[0]); + close (fd); + } + else + line = doc ? doc[0] : (char *)NULL; + + printf ("%s - ", name); + for (j = 0; line && line[j]; j++) + { + putchar (line[j]); + if (line[j] == '\n') + break; + } + + fflush (stdout); + + if (usefile) + free (line); +} + +/* Print builtin help in pseudo-manpage format. */ +static void +show_manpage (name, i) + char *name; + int i; +{ + register int j; + char **doc, *line; + int fd, usefile; + + doc = (char **)shell_builtins[i].long_doc; + + usefile = (doc && doc[0] && *doc[0] == '/' && doc[1] == (char *)NULL); + if (usefile) + { + fd = open_helpfile (doc[0]); + if (fd < 0) + return; + zmapfd (fd, &line, doc[0]); + close (fd); + } + else + line = doc ? _(doc[0]) : (char *)NULL; + + /* NAME */ + printf ("NAME\n"); + printf ("%*s%s - ", BASE_INDENT, " ", name); + for (j = 0; line && line[j]; j++) + { + putchar (line[j]); + if (line[j] == '\n') + break; + } + printf ("\n"); + + /* SYNOPSIS */ + printf ("SYNOPSIS\n"); + printf ("%*s%s\n\n", BASE_INDENT, " ", _(shell_builtins[i].short_doc)); + + /* DESCRIPTION */ + printf ("DESCRIPTION\n"); + if (usefile == 0) + { + for (j = 0; doc[j]; j++) + printf ("%*s%s\n", BASE_INDENT, " ", _(doc[j])); + } + else + { + for (j = 0; line && line[j]; j++) + { + putchar (line[j]); + if (line[j] == '\n') + printf ("%*s", BASE_INDENT, " "); + } + } + putchar ('\n'); + + /* SEE ALSO */ + printf ("SEE ALSO\n"); + printf ("%*sbash(1)\n\n", BASE_INDENT, " "); + + /* IMPLEMENTATION */ + printf ("IMPLEMENTATION\n"); + printf ("%*s", BASE_INDENT, " "); + show_shell_version (0); + printf ("%*s", BASE_INDENT, " "); + printf ("%s\n", _(bash_copyright)); + printf ("%*s", BASE_INDENT, " "); + printf ("%s\n", _(bash_license)); + + fflush (stdout); + if (usefile) + free (line); +} + +static void +show_builtin_command_help () +{ + int i, j; + int height, width; + char *t, blurb[128]; + + printf ( +_("These shell commands are defined internally. Type `help' to see this list.\n\ +Type `help name' to find out more about the function `name'.\n\ +Use `info bash' to find out more about the shell in general.\n\ +Use `man -k' or `info' to find out more about commands not in this list.\n\ +\n\ +A star (*) next to a name means that the command is disabled.\n\ +\n")); + + t = get_string_value ("COLUMNS"); + width = (t && *t) ? atoi (t) : 80; + if (width <= 0) + width = 80; + + width /= 2; + if (width > sizeof (blurb)) + width = sizeof (blurb); + if (width <= 3) + width = 40; + height = (num_shell_builtins + 1) / 2; /* number of rows */ + + for (i = 0; i < height; i++) + { + QUIT; + + /* first column */ + blurb[0] = (shell_builtins[i].flags & BUILTIN_ENABLED) ? ' ' : '*'; + strncpy (blurb + 1, _(shell_builtins[i].short_doc), width - 2); + blurb[width - 2] = '>'; /* indicate truncation */ + blurb[width - 1] = '\0'; + printf ("%s", blurb); + if (((i << 1) >= num_shell_builtins) || (i+height >= num_shell_builtins)) + { + printf ("\n"); + break; + } + + /* two spaces */ + for (j = strlen (blurb); j < width; j++) + putc (' ', stdout); + + /* second column */ + blurb[0] = (shell_builtins[i+height].flags & BUILTIN_ENABLED) ? ' ' : '*'; + strncpy (blurb + 1, _(shell_builtins[i+height].short_doc), width - 3); + blurb[width - 3] = '>'; /* indicate truncation */ + blurb[width - 2] = '\0'; + printf ("%s\n", blurb); + } +} +#endif /* HELP_BUILTIN */ diff --git a/builtins/mkbuiltins.c b/builtins/mkbuiltins.c index c05cf30f5..95648ddf0 100644 --- a/builtins/mkbuiltins.c +++ b/builtins/mkbuiltins.c @@ -1,7 +1,7 @@ /* mkbuiltins.c - Create builtins.c, builtext.h, and builtdoc.c from a single source file called builtins.def. */ -/* Copyright (C) 1987-2010 Free Software Foundation, Inc. +/* Copyright (C) 1987-2011 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -519,6 +519,7 @@ extract_info (filename, structfile, externfile) if (nr == 0) { fprintf (stderr, "mkbuiltins: %s: skipping zero-length file\n", filename); + free (buffer); return; } @@ -1407,7 +1408,7 @@ write_documentation (stream, documentation, indentation, flags) base_indent = (string_array && single_longdoc_strings && filename_p == 0) ? BASE_INDENT : 0; - for (i = 0, texinfo = (flags & TEXINFO); line = documentation[i]; i++) + for (i = 0, texinfo = (flags & TEXINFO); documentation && (line = documentation[i]); i++) { /* Allow #ifdef's to be written out verbatim, but don't put them into separate help files. */ diff --git a/builtins/mkbuiltins.c~ b/builtins/mkbuiltins.c~ new file mode 100644 index 000000000..ad72cdcd8 --- /dev/null +++ b/builtins/mkbuiltins.c~ @@ -0,0 +1,1593 @@ +/* mkbuiltins.c - Create builtins.c, builtext.h, and builtdoc.c from + a single source file called builtins.def. */ + +/* Copyright (C) 1987-2011 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (CROSS_COMPILING) +# include +#else /* CROSS_COMPILING */ +/* A conservative set of defines based on POSIX/SUS3/XPG6 */ +# define HAVE_UNISTD_H +# define HAVE_STRING_H +# define HAVE_STDLIB_H + +# define HAVE_RENAME +#endif /* CROSS_COMPILING */ + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#ifndef _MINIX +# include "../bashtypes.h" +# if defined (HAVE_SYS_FILE_H) +# include +# endif +#endif + +#include "posixstat.h" +#include "filecntl.h" + +#include "../bashansi.h" +#include +#include + +#include "stdc.h" + +#define DOCFILE "builtins.texi" + +#ifndef errno +extern int errno; +#endif + +static char *xmalloc (), *xrealloc (); + +#if !defined (__STDC__) && !defined (strcpy) +extern char *strcpy (); +#endif /* !__STDC__ && !strcpy */ + +#define savestring(x) strcpy (xmalloc (1 + strlen (x)), (x)) +#define whitespace(c) (((c) == ' ') || ((c) == '\t')) + +/* Flag values that builtins can have. */ +#define BUILTIN_FLAG_SPECIAL 0x01 +#define BUILTIN_FLAG_ASSIGNMENT 0x02 +#define BUILTIN_FLAG_POSIX_BUILTIN 0x04 + +#define BASE_INDENT 4 + +/* If this stream descriptor is non-zero, then write + texinfo documentation to it. */ +FILE *documentation_file = (FILE *)NULL; + +/* Non-zero means to only produce documentation. */ +int only_documentation = 0; + +/* Non-zero means to not do any productions. */ +int inhibit_production = 0; + +/* Non-zero means to produce separate help files for each builtin, named by + the builtin name, in `./helpfiles'. */ +int separate_helpfiles = 0; + +/* Non-zero means to create single C strings for each `longdoc', with + embedded newlines, for ease of translation. */ +int single_longdoc_strings = 1; + +/* The name of a directory into which the separate external help files will + eventually be installed. */ +char *helpfile_directory; + +/* The name of a directory to precede the filename when reporting + errors. */ +char *error_directory = (char *)NULL; + +/* The name of the structure file. */ +char *struct_filename = (char *)NULL; + +/* The name of the external declaration file. */ +char *extern_filename = (char *)NULL; + +/* Here is a structure for manipulating arrays of data. */ +typedef struct { + int size; /* Number of slots allocated to array. */ + int sindex; /* Current location in array. */ + int width; /* Size of each element. */ + int growth_rate; /* How fast to grow. */ + char **array; /* The array itself. */ +} ARRAY; + +/* Here is a structure defining a single BUILTIN. */ +typedef struct { + char *name; /* The name of this builtin. */ + char *function; /* The name of the function to call. */ + char *shortdoc; /* The short documentation for this builtin. */ + char *docname; /* Possible name for documentation string. */ + ARRAY *longdoc; /* The long documentation for this builtin. */ + ARRAY *dependencies; /* Null terminated array of #define names. */ + int flags; /* Flags for this builtin. */ +} BUILTIN_DESC; + +/* Here is a structure which defines a DEF file. */ +typedef struct { + char *filename; /* The name of the input def file. */ + ARRAY *lines; /* The contents of the file. */ + int line_number; /* The current line number. */ + char *production; /* The name of the production file. */ + FILE *output; /* Open file stream for PRODUCTION. */ + ARRAY *builtins; /* Null terminated array of BUILTIN_DESC *. */ +} DEF_FILE; + +/* The array of all builtins encountered during execution of this code. */ +ARRAY *saved_builtins = (ARRAY *)NULL; + +/* The Posix.2 so-called `special' builtins. */ +char *special_builtins[] = +{ + ":", ".", "source", "break", "continue", "eval", "exec", "exit", + "export", "readonly", "return", "set", "shift", "times", "trap", "unset", + (char *)NULL +}; + +/* The builtin commands that take assignment statements as arguments. */ +char *assignment_builtins[] = +{ + "alias", "declare", "export", "local", "readonly", "typeset", + (char *)NULL +}; + +/* The builtin commands that are special to the POSIX search order. */ +char *posix_builtins[] = +{ + "alias", "bg", "cd", "command", "false", "fc", "fg", "getopts", "jobs", + "kill", "newgrp", "pwd", "read", "true", "umask", "unalias", "wait", + (char *)NULL +}; + +/* Forward declarations. */ +static int is_special_builtin (); +static int is_assignment_builtin (); +static int is_posix_builtin (); + +#if !defined (HAVE_RENAME) +static int rename (); +#endif + +void extract_info (); + +void file_error (); +void line_error (); + +void write_file_headers (); +void write_file_footers (); +void write_ifdefs (); +void write_endifs (); +void write_documentation (); +void write_longdocs (); +void write_builtins (); + +int write_helpfiles (); + +void free_defs (); +void add_documentation (); + +void must_be_building (); +void remove_trailing_whitespace (); + +#define document_name(b) ((b)->docname ? (b)->docname : (b)->name) + + +/* For each file mentioned on the command line, process it and + write the information to STRUCTFILE and EXTERNFILE, while + creating the production file if neccessary. */ +int +main (argc, argv) + int argc; + char **argv; +{ + int arg_index = 1; + FILE *structfile, *externfile; + char *documentation_filename, *temp_struct_filename; + + structfile = externfile = (FILE *)NULL; + documentation_filename = DOCFILE; + temp_struct_filename = (char *)NULL; + + while (arg_index < argc && argv[arg_index][0] == '-') + { + char *arg = argv[arg_index++]; + + if (strcmp (arg, "-externfile") == 0) + extern_filename = argv[arg_index++]; + else if (strcmp (arg, "-structfile") == 0) + struct_filename = argv[arg_index++]; + else if (strcmp (arg, "-noproduction") == 0) + inhibit_production = 1; + else if (strcmp (arg, "-document") == 0) + documentation_file = fopen (documentation_filename, "w"); + else if (strcmp (arg, "-D") == 0) + { + int len; + + if (error_directory) + free (error_directory); + + error_directory = xmalloc (2 + strlen (argv[arg_index])); + strcpy (error_directory, argv[arg_index]); + len = strlen (error_directory); + + if (len && error_directory[len - 1] != '/') + strcat (error_directory, "/"); + + arg_index++; + } + else if (strcmp (arg, "-documentonly") == 0) + { + only_documentation = 1; + documentation_file = fopen (documentation_filename, "w"); + } + else if (strcmp (arg, "-H") == 0) + { + separate_helpfiles = 1; + helpfile_directory = argv[arg_index++]; + } + else if (strcmp (arg, "-S") == 0) + single_longdoc_strings = 0; + else + { + fprintf (stderr, "%s: Unknown flag %s.\n", argv[0], arg); + exit (2); + } + } + + /* If there are no files to process, just quit now. */ + if (arg_index == argc) + exit (0); + + if (!only_documentation) + { + /* Open the files. */ + if (struct_filename) + { + temp_struct_filename = xmalloc (15); + sprintf (temp_struct_filename, "mk-%ld", (long) getpid ()); + structfile = fopen (temp_struct_filename, "w"); + + if (!structfile) + file_error (temp_struct_filename); + } + + if (extern_filename) + { + externfile = fopen (extern_filename, "w"); + + if (!externfile) + file_error (extern_filename); + } + + /* Write out the headers. */ + write_file_headers (structfile, externfile); + } + + if (documentation_file) + { + fprintf (documentation_file, "@c Table of builtins created with %s.\n", + argv[0]); + fprintf (documentation_file, "@ftable @asis\n"); + } + + /* Process the .def files. */ + while (arg_index < argc) + { + register char *arg; + + arg = argv[arg_index++]; + + extract_info (arg, structfile, externfile); + } + + /* Close the files. */ + if (!only_documentation) + { + /* Write the footers. */ + write_file_footers (structfile, externfile); + + if (structfile) + { + write_longdocs (structfile, saved_builtins); + fclose (structfile); + rename (temp_struct_filename, struct_filename); + } + + if (externfile) + fclose (externfile); + } + + if (separate_helpfiles) + { + write_helpfiles (saved_builtins); + } + + if (documentation_file) + { + fprintf (documentation_file, "@end ftable\n"); + fclose (documentation_file); + } + + exit (0); +} + +/* **************************************************************** */ +/* */ +/* Array Functions and Manipulators */ +/* */ +/* **************************************************************** */ + +/* Make a new array, and return a pointer to it. The array will + contain elements of size WIDTH, and is initialized to no elements. */ +ARRAY * +array_create (width) + int width; +{ + ARRAY *array; + + array = (ARRAY *)xmalloc (sizeof (ARRAY)); + array->size = 0; + array->sindex = 0; + array->width = width; + + /* Default to increasing size in units of 20. */ + array->growth_rate = 20; + + array->array = (char **)NULL; + + return (array); +} + +/* Copy the array of strings in ARRAY. */ +ARRAY * +copy_string_array (array) + ARRAY *array; +{ + register int i; + ARRAY *copy; + + if (!array) + return (ARRAY *)NULL; + + copy = array_create (sizeof (char *)); + + copy->size = array->size; + copy->sindex = array->sindex; + copy->width = array->width; + + copy->array = (char **)xmalloc ((1 + array->sindex) * sizeof (char *)); + + for (i = 0; i < array->sindex; i++) + copy->array[i] = savestring (array->array[i]); + + copy->array[i] = (char *)NULL; + + return (copy); +} + +/* Add ELEMENT to ARRAY, growing the array if neccessary. */ +void +array_add (element, array) + char *element; + ARRAY *array; +{ + if (array->sindex + 2 > array->size) + array->array = (char **)xrealloc + (array->array, (array->size += array->growth_rate) * array->width); + + array->array[array->sindex++] = element; + array->array[array->sindex] = (char *)NULL; +} + +/* Free an allocated array and data pointer. */ +void +array_free (array) + ARRAY *array; +{ + if (array->array) + free (array->array); + + free (array); +} + +/* **************************************************************** */ +/* */ +/* Processing a DEF File */ +/* */ +/* **************************************************************** */ + +/* The definition of a function. */ +typedef int Function (); +typedef int mk_handler_func_t __P((char *, DEF_FILE *, char *)); + +/* Structure handles processor directives. */ +typedef struct { + char *directive; + mk_handler_func_t *function; +} HANDLER_ENTRY; + +extern int builtin_handler __P((char *, DEF_FILE *, char *)); +extern int function_handler __P((char *, DEF_FILE *, char *)); +extern int short_doc_handler __P((char *, DEF_FILE *, char *)); +extern int comment_handler __P((char *, DEF_FILE *, char *)); +extern int depends_on_handler __P((char *, DEF_FILE *, char *)); +extern int produces_handler __P((char *, DEF_FILE *, char *)); +extern int end_handler __P((char *, DEF_FILE *, char *)); +extern int docname_handler __P((char *, DEF_FILE *, char *)); + +HANDLER_ENTRY handlers[] = { + { "BUILTIN", builtin_handler }, + { "DOCNAME", docname_handler }, + { "FUNCTION", function_handler }, + { "SHORT_DOC", short_doc_handler }, + { "$", comment_handler }, + { "COMMENT", comment_handler }, + { "DEPENDS_ON", depends_on_handler }, + { "PRODUCES", produces_handler }, + { "END", end_handler }, + { (char *)NULL, (mk_handler_func_t *)NULL } +}; + +/* Return the entry in the table of handlers for NAME. */ +HANDLER_ENTRY * +find_directive (directive) + char *directive; +{ + register int i; + + for (i = 0; handlers[i].directive; i++) + if (strcmp (handlers[i].directive, directive) == 0) + return (&handlers[i]); + + return ((HANDLER_ENTRY *)NULL); +} + +/* Non-zero indicates that a $BUILTIN has been seen, but not + the corresponding $END. */ +static int building_builtin = 0; + +/* Non-zero means to output cpp line and file information before + printing the current line to the production file. */ +int output_cpp_line_info = 0; + +/* The main function of this program. Read FILENAME and act on what is + found. Lines not starting with a dollar sign are copied to the + $PRODUCES target, if one is present. Lines starting with a dollar sign + are directives to this program, specifying the name of the builtin, the + function to call, the short documentation and the long documentation + strings. FILENAME can contain multiple $BUILTINs, but only one $PRODUCES + target. After the file has been processed, write out the names of + builtins found in each $BUILTIN. Plain text found before the $PRODUCES + is ignored, as is "$$ comment text". */ +void +extract_info (filename, structfile, externfile) + char *filename; + FILE *structfile, *externfile; +{ + register int i; + DEF_FILE *defs; + struct stat finfo; + size_t file_size; + char *buffer, *line; + int fd, nr; + + if (stat (filename, &finfo) == -1) + file_error (filename); + + fd = open (filename, O_RDONLY, 0666); + + if (fd == -1) + file_error (filename); + + file_size = (size_t)finfo.st_size; + buffer = xmalloc (1 + file_size); + + if ((nr = read (fd, buffer, file_size)) < 0) + file_error (filename); + + /* This is needed on WIN32, and does not hurt on Unix. */ + if (nr < file_size) + file_size = nr; + + close (fd); + + if (nr == 0) + { + fprintf (stderr, "mkbuiltins: %s: skipping zero-length file\n", filename); + free (buffer); + return; + } + + /* Create and fill in the initial structure describing this file. */ + defs = (DEF_FILE *)xmalloc (sizeof (DEF_FILE)); + defs->filename = filename; + defs->lines = array_create (sizeof (char *)); + defs->line_number = 0; + defs->production = (char *)NULL; + defs->output = (FILE *)NULL; + defs->builtins = (ARRAY *)NULL; + + /* Build the array of lines. */ + i = 0; + while (i < file_size) + { + array_add (&buffer[i], defs->lines); + + while (i < file_size && buffer[i] != '\n') + i++; + buffer[i++] = '\0'; + } + + /* Begin processing the input file. We don't write any output + until we have a file to write output to. */ + output_cpp_line_info = 1; + + /* Process each line in the array. */ + for (i = 0; line = defs->lines->array[i]; i++) + { + defs->line_number = i; + + if (*line == '$') + { + register int j; + char *directive; + HANDLER_ENTRY *handler; + + /* Isolate the directive. */ + for (j = 0; line[j] && !whitespace (line[j]); j++); + + directive = xmalloc (j); + strncpy (directive, line + 1, j - 1); + directive[j -1] = '\0'; + + /* Get the function handler and call it. */ + handler = find_directive (directive); + + if (!handler) + { + line_error (defs, "Unknown directive `%s'", directive); + free (directive); + continue; + } + else + { + /* Advance to the first non-whitespace character. */ + while (whitespace (line[j])) + j++; + + /* Call the directive handler with the FILE, and ARGS. */ + (*(handler->function)) (directive, defs, line + j); + } + free (directive); + } + else + { + if (building_builtin) + add_documentation (defs, line); + else if (defs->output) + { + if (output_cpp_line_info) + { + /* If we're handed an absolute pathname, don't prepend + the directory name. */ + if (defs->filename[0] == '/') + fprintf (defs->output, "#line %d \"%s\"\n", + defs->line_number + 1, defs->filename); + else + fprintf (defs->output, "#line %d \"%s%s\"\n", + defs->line_number + 1, + error_directory ? error_directory : "./", + defs->filename); + output_cpp_line_info = 0; + } + + fprintf (defs->output, "%s\n", line); + } + } + } + + /* Close the production file. */ + if (defs->output) + fclose (defs->output); + + /* The file has been processed. Write the accumulated builtins to + the builtins.c file, and write the extern definitions to the + builtext.h file. */ + write_builtins (defs, structfile, externfile); + + free (buffer); + free_defs (defs); +} + +#define free_safely(x) if (x) free (x) + +static void +free_builtin (builtin) + BUILTIN_DESC *builtin; +{ + register int i; + + free_safely (builtin->name); + free_safely (builtin->function); + free_safely (builtin->shortdoc); + free_safely (builtin->docname); + + if (builtin->longdoc) + array_free (builtin->longdoc); + + if (builtin->dependencies) + { + for (i = 0; builtin->dependencies->array[i]; i++) + free (builtin->dependencies->array[i]); + array_free (builtin->dependencies); + } +} + +/* Free all of the memory allocated to a DEF_FILE. */ +void +free_defs (defs) + DEF_FILE *defs; +{ + register int i; + register BUILTIN_DESC *builtin; + + if (defs->production) + free (defs->production); + + if (defs->lines) + array_free (defs->lines); + + if (defs->builtins) + { + for (i = 0; builtin = (BUILTIN_DESC *)defs->builtins->array[i]; i++) + { + free_builtin (builtin); + free (builtin); + } + array_free (defs->builtins); + } + free (defs); +} + +/* **************************************************************** */ +/* */ +/* The Handler Functions Themselves */ +/* */ +/* **************************************************************** */ + +/* Strip surrounding whitespace from STRING, and + return a pointer to the start of it. */ +char * +strip_whitespace (string) + char *string; +{ + while (whitespace (*string)) + string++; + + remove_trailing_whitespace (string); + return (string); +} + +/* Remove only the trailing whitespace from STRING. */ +void +remove_trailing_whitespace (string) + char *string; +{ + register int i; + + i = strlen (string) - 1; + + while (i > 0 && whitespace (string[i])) + i--; + + string[++i] = '\0'; +} + +/* Ensure that there is a argument in STRING and return it. + FOR_WHOM is the name of the directive which needs the argument. + DEFS is the DEF_FILE in which the directive is found. + If there is no argument, produce an error. */ +char * +get_arg (for_whom, defs, string) + char *for_whom, *string; + DEF_FILE *defs; +{ + char *new; + + new = strip_whitespace (string); + + if (!*new) + line_error (defs, "%s requires an argument", for_whom); + + return (savestring (new)); +} + +/* Error if not building a builtin. */ +void +must_be_building (directive, defs) + char *directive; + DEF_FILE *defs; +{ + if (!building_builtin) + line_error (defs, "%s must be inside of a $BUILTIN block", directive); +} + +/* Return the current builtin. */ +BUILTIN_DESC * +current_builtin (directive, defs) + char *directive; + DEF_FILE *defs; +{ + must_be_building (directive, defs); + if (defs->builtins) + return ((BUILTIN_DESC *)defs->builtins->array[defs->builtins->sindex - 1]); + else + return ((BUILTIN_DESC *)NULL); +} + +/* Add LINE to the long documentation for the current builtin. + Ignore blank lines until the first non-blank line has been seen. */ +void +add_documentation (defs, line) + DEF_FILE *defs; + char *line; +{ + register BUILTIN_DESC *builtin; + + builtin = current_builtin ("(implied LONGDOC)", defs); + + remove_trailing_whitespace (line); + + if (!*line && !builtin->longdoc) + return; + + if (!builtin->longdoc) + builtin->longdoc = array_create (sizeof (char *)); + + array_add (line, builtin->longdoc); +} + +/* How to handle the $BUILTIN directive. */ +int +builtin_handler (self, defs, arg) + char *self; + DEF_FILE *defs; + char *arg; +{ + BUILTIN_DESC *new; + char *name; + + /* If we are already building a builtin, we cannot start a new one. */ + if (building_builtin) + { + line_error (defs, "%s found before $END", self); + return (-1); + } + + output_cpp_line_info++; + + /* Get the name of this builtin, and stick it in the array. */ + name = get_arg (self, defs, arg); + + /* If this is the first builtin, create the array to hold them. */ + if (!defs->builtins) + defs->builtins = array_create (sizeof (BUILTIN_DESC *)); + + new = (BUILTIN_DESC *)xmalloc (sizeof (BUILTIN_DESC)); + new->name = name; + new->function = (char *)NULL; + new->shortdoc = (char *)NULL; + new->docname = (char *)NULL; + new->longdoc = (ARRAY *)NULL; + new->dependencies = (ARRAY *)NULL; + new->flags = 0; + + if (is_special_builtin (name)) + new->flags |= BUILTIN_FLAG_SPECIAL; + if (is_assignment_builtin (name)) + new->flags |= BUILTIN_FLAG_ASSIGNMENT; + if (is_posix_builtin (name)) + new->flags |= BUILTIN_FLAG_POSIX_BUILTIN; + + array_add ((char *)new, defs->builtins); + building_builtin = 1; + + return (0); +} + +/* How to handle the $FUNCTION directive. */ +int +function_handler (self, defs, arg) + char *self; + DEF_FILE *defs; + char *arg; +{ + register BUILTIN_DESC *builtin; + + builtin = current_builtin (self, defs); + + if (builtin == 0) + { + line_error (defs, "syntax error: no current builtin for $FUNCTION directive"); + exit (1); + } + if (builtin->function) + line_error (defs, "%s already has a function (%s)", + builtin->name, builtin->function); + else + builtin->function = get_arg (self, defs, arg); + + return (0); +} + +/* How to handle the $DOCNAME directive. */ +int +docname_handler (self, defs, arg) + char *self; + DEF_FILE *defs; + char *arg; +{ + register BUILTIN_DESC *builtin; + + builtin = current_builtin (self, defs); + + if (builtin->docname) + line_error (defs, "%s already had a docname (%s)", + builtin->name, builtin->docname); + else + builtin->docname = get_arg (self, defs, arg); + + return (0); +} + +/* How to handle the $SHORT_DOC directive. */ +int +short_doc_handler (self, defs, arg) + char *self; + DEF_FILE *defs; + char *arg; +{ + register BUILTIN_DESC *builtin; + + builtin = current_builtin (self, defs); + + if (builtin->shortdoc) + line_error (defs, "%s already has short documentation (%s)", + builtin->name, builtin->shortdoc); + else + builtin->shortdoc = get_arg (self, defs, arg); + + return (0); +} + +/* How to handle the $COMMENT directive. */ +int +comment_handler (self, defs, arg) + char *self; + DEF_FILE *defs; + char *arg; +{ + return (0); +} + +/* How to handle the $DEPENDS_ON directive. */ +int +depends_on_handler (self, defs, arg) + char *self; + DEF_FILE *defs; + char *arg; +{ + register BUILTIN_DESC *builtin; + char *dependent; + + builtin = current_builtin (self, defs); + dependent = get_arg (self, defs, arg); + + if (!builtin->dependencies) + builtin->dependencies = array_create (sizeof (char *)); + + array_add (dependent, builtin->dependencies); + + return (0); +} + +/* How to handle the $PRODUCES directive. */ +int +produces_handler (self, defs, arg) + char *self; + DEF_FILE *defs; + char *arg; +{ + /* If just hacking documentation, don't change any of the production + files. */ + if (only_documentation) + return (0); + + output_cpp_line_info++; + + if (defs->production) + line_error (defs, "%s already has a %s definition", defs->filename, self); + else + { + defs->production = get_arg (self, defs, arg); + + if (inhibit_production) + return (0); + + defs->output = fopen (defs->production, "w"); + + if (!defs->output) + file_error (defs->production); + + fprintf (defs->output, "/* %s, created from %s. */\n", + defs->production, defs->filename); + } + return (0); +} + +/* How to handle the $END directive. */ +int +end_handler (self, defs, arg) + char *self; + DEF_FILE *defs; + char *arg; +{ + must_be_building (self, defs); + building_builtin = 0; + return (0); +} + +/* **************************************************************** */ +/* */ +/* Error Handling Functions */ +/* */ +/* **************************************************************** */ + +/* Produce an error for DEFS with FORMAT and ARGS. */ +void +line_error (defs, format, arg1, arg2) + DEF_FILE *defs; + char *format, *arg1, *arg2; +{ + if (defs->filename[0] != '/') + fprintf (stderr, "%s", error_directory ? error_directory : "./"); + fprintf (stderr, "%s:%d:", defs->filename, defs->line_number + 1); + fprintf (stderr, format, arg1, arg2); + fprintf (stderr, "\n"); + fflush (stderr); +} + +/* Print error message for FILENAME. */ +void +file_error (filename) + char *filename; +{ + perror (filename); + exit (2); +} + +/* **************************************************************** */ +/* */ +/* xmalloc and xrealloc () */ +/* */ +/* **************************************************************** */ + +static void memory_error_and_abort (); + +static char * +xmalloc (bytes) + int bytes; +{ + char *temp = (char *)malloc (bytes); + + if (!temp) + memory_error_and_abort (); + return (temp); +} + +static char * +xrealloc (pointer, bytes) + char *pointer; + int bytes; +{ + char *temp; + + if (!pointer) + temp = (char *)malloc (bytes); + else + temp = (char *)realloc (pointer, bytes); + + if (!temp) + memory_error_and_abort (); + + return (temp); +} + +static void +memory_error_and_abort () +{ + fprintf (stderr, "mkbuiltins: out of virtual memory\n"); + abort (); +} + +/* **************************************************************** */ +/* */ +/* Creating the Struct and Extern Files */ +/* */ +/* **************************************************************** */ + +/* Return a pointer to a newly allocated builtin which is + an exact copy of BUILTIN. */ +BUILTIN_DESC * +copy_builtin (builtin) + BUILTIN_DESC *builtin; +{ + BUILTIN_DESC *new; + + new = (BUILTIN_DESC *)xmalloc (sizeof (BUILTIN_DESC)); + + new->name = savestring (builtin->name); + new->shortdoc = savestring (builtin->shortdoc); + new->longdoc = copy_string_array (builtin->longdoc); + new->dependencies = copy_string_array (builtin->dependencies); + + new->function = + builtin->function ? savestring (builtin->function) : (char *)NULL; + new->docname = + builtin->docname ? savestring (builtin->docname) : (char *)NULL; + + return (new); +} + +/* How to save away a builtin. */ +void +save_builtin (builtin) + BUILTIN_DESC *builtin; +{ + BUILTIN_DESC *newbuiltin; + + newbuiltin = copy_builtin (builtin); + + /* If this is the first builtin to be saved, create the array + to hold it. */ + if (!saved_builtins) + saved_builtins = array_create (sizeof (BUILTIN_DESC *)); + + array_add ((char *)newbuiltin, saved_builtins); +} + +/* Flags that mean something to write_documentation (). */ +#define STRING_ARRAY 0x01 +#define TEXINFO 0x02 +#define PLAINTEXT 0x04 +#define HELPFILE 0x08 + +char *structfile_header[] = { + "/* builtins.c -- the built in shell commands. */", + "", + "/* This file is manufactured by ./mkbuiltins, and should not be", + " edited by hand. See the source to mkbuiltins for details. */", + "", + "/* Copyright (C) 1987-2009 Free Software Foundation, Inc.", + "", + " This file is part of GNU Bash, the Bourne Again SHell.", + "", + " Bash is free software: you can redistribute it and/or modify", + " it under the terms of the GNU General Public License as published by", + " the Free Software Foundation, either version 3 of the License, or", + " (at your option) any later version.", + "", + " Bash is distributed in the hope that it will be useful,", + " but WITHOUT ANY WARRANTY; without even the implied warranty of", + " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the", + " GNU General Public License for more details.", + "", + " You should have received a copy of the GNU General Public License", + " along with Bash. If not, see .", + "*/", + "", + "/* The list of shell builtins. Each element is name, function, flags,", + " long-doc, short-doc. The long-doc field contains a pointer to an array", + " of help lines. The function takes a WORD_LIST *; the first word in the", + " list is the first arg to the command. The list has already had word", + " expansion performed.", + "", + " Functions which need to look at only the simple commands (e.g.", + " the enable_builtin ()), should ignore entries where", + " (array[i].function == (sh_builtin_func_t *)NULL). Such entries are for", + " the list of shell reserved control structures, like `if' and `while'.", + " The end of the list is denoted with a NULL name field. */", + "", + "#include \"../builtins.h\"", + (char *)NULL + }; + +char *structfile_footer[] = { + " { (char *)0x0, (sh_builtin_func_t *)0x0, 0, (char **)0x0, (char *)0x0, (char *)0x0 }", + "};", + "", + "struct builtin *shell_builtins = static_shell_builtins;", + "struct builtin *current_builtin;", + "", + "int num_shell_builtins =", + "\tsizeof (static_shell_builtins) / sizeof (struct builtin) - 1;", + (char *)NULL +}; + +/* Write out any neccessary opening information for + STRUCTFILE and EXTERNFILE. */ +void +write_file_headers (structfile, externfile) + FILE *structfile, *externfile; +{ + register int i; + + if (structfile) + { + for (i = 0; structfile_header[i]; i++) + fprintf (structfile, "%s\n", structfile_header[i]); + + fprintf (structfile, "#include \"%s\"\n", + extern_filename ? extern_filename : "builtext.h"); + + fprintf (structfile, "#include \"bashintl.h\"\n"); + + fprintf (structfile, "\nstruct builtin static_shell_builtins[] = {\n"); + } + + if (externfile) + fprintf (externfile, + "/* %s - The list of builtins found in libbuiltins.a. */\n", + extern_filename ? extern_filename : "builtext.h"); +} + +/* Write out any necessary closing information for + STRUCTFILE and EXTERNFILE. */ +void +write_file_footers (structfile, externfile) + FILE *structfile, *externfile; +{ + register int i; + + /* Write out the footers. */ + if (structfile) + { + for (i = 0; structfile_footer[i]; i++) + fprintf (structfile, "%s\n", structfile_footer[i]); + } +} + +/* Write out the information accumulated in DEFS to + STRUCTFILE and EXTERNFILE. */ +void +write_builtins (defs, structfile, externfile) + DEF_FILE *defs; + FILE *structfile, *externfile; +{ + register int i; + + /* Write out the information. */ + if (defs->builtins) + { + register BUILTIN_DESC *builtin; + + for (i = 0; i < defs->builtins->sindex; i++) + { + builtin = (BUILTIN_DESC *)defs->builtins->array[i]; + + /* Write out any #ifdefs that may be there. */ + if (!only_documentation) + { + if (builtin->dependencies) + { + write_ifdefs (externfile, builtin->dependencies->array); + write_ifdefs (structfile, builtin->dependencies->array); + } + + /* Write the extern definition. */ + if (externfile) + { + if (builtin->function) + fprintf (externfile, "extern int %s __P((WORD_LIST *));\n", + builtin->function); + + fprintf (externfile, "extern char * const %s_doc[];\n", + document_name (builtin)); + } + + /* Write the structure definition. */ + if (structfile) + { + fprintf (structfile, " { \"%s\", ", builtin->name); + + if (builtin->function) + fprintf (structfile, "%s, ", builtin->function); + else + fprintf (structfile, "(sh_builtin_func_t *)0x0, "); + + fprintf (structfile, "%s%s%s%s, %s_doc,\n", + "BUILTIN_ENABLED | STATIC_BUILTIN", + (builtin->flags & BUILTIN_FLAG_SPECIAL) ? " | SPECIAL_BUILTIN" : "", + (builtin->flags & BUILTIN_FLAG_ASSIGNMENT) ? " | ASSIGNMENT_BUILTIN" : "", + (builtin->flags & BUILTIN_FLAG_POSIX_BUILTIN) ? " | POSIX_BUILTIN" : "", + document_name (builtin)); + + fprintf + (structfile, " N_(\"%s\"), (char *)NULL },\n", + builtin->shortdoc ? builtin->shortdoc : builtin->name); + + } + + if (structfile || separate_helpfiles) + /* Save away this builtin for later writing of the + long documentation strings. */ + save_builtin (builtin); + + /* Write out the matching #endif, if neccessary. */ + if (builtin->dependencies) + { + if (externfile) + write_endifs (externfile, builtin->dependencies->array); + + if (structfile) + write_endifs (structfile, builtin->dependencies->array); + } + } + + if (documentation_file) + { + fprintf (documentation_file, "@item %s\n", builtin->name); + write_documentation + (documentation_file, builtin->longdoc->array, 0, TEXINFO); + } + } + } +} + +/* Write out the long documentation strings in BUILTINS to STREAM. */ +void +write_longdocs (stream, builtins) + FILE *stream; + ARRAY *builtins; +{ + register int i; + register BUILTIN_DESC *builtin; + char *dname; + char *sarray[2]; + + for (i = 0; i < builtins->sindex; i++) + { + builtin = (BUILTIN_DESC *)builtins->array[i]; + + if (builtin->dependencies) + write_ifdefs (stream, builtin->dependencies->array); + + /* Write the long documentation strings. */ + dname = document_name (builtin); + fprintf (stream, "char * const %s_doc[] =", dname); + + if (separate_helpfiles) + { + int l = strlen (helpfile_directory) + strlen (dname) + 1; + sarray[0] = (char *)xmalloc (l + 1); + sprintf (sarray[0], "%s/%s", helpfile_directory, dname); + sarray[1] = (char *)NULL; + write_documentation (stream, sarray, 0, STRING_ARRAY|HELPFILE); + free (sarray[0]); + } + else + write_documentation (stream, builtin->longdoc->array, 0, STRING_ARRAY); + + if (builtin->dependencies) + write_endifs (stream, builtin->dependencies->array); + + } +} + +/* Write an #ifdef string saying what needs to be defined (or not defined) + in order to allow compilation of the code that will follow. + STREAM is the stream to write the information to, + DEFINES is a null terminated array of define names. + If a define is preceded by an `!', then the sense of the test is + reversed. */ +void +write_ifdefs (stream, defines) + FILE *stream; + char **defines; +{ + register int i; + + if (!stream) + return; + + fprintf (stream, "#if "); + + for (i = 0; defines[i]; i++) + { + char *def = defines[i]; + + if (*def == '!') + fprintf (stream, "!defined (%s)", def + 1); + else + fprintf (stream, "defined (%s)", def); + + if (defines[i + 1]) + fprintf (stream, " && "); + } + fprintf (stream, "\n"); +} + +/* Write an #endif string saying what defines controlled the compilation + of the immediately preceding code. + STREAM is the stream to write the information to. + DEFINES is a null terminated array of define names. */ +void +write_endifs (stream, defines) + FILE *stream; + char **defines; +{ + register int i; + + if (!stream) + return; + + fprintf (stream, "#endif /* "); + + for (i = 0; defines[i]; i++) + { + fprintf (stream, "%s", defines[i]); + + if (defines[i + 1]) + fprintf (stream, " && "); + } + + fprintf (stream, " */\n"); +} + +/* Write DOCUMENTATION to STREAM, perhaps surrounding it with double-quotes + and quoting special characters in the string. Handle special things for + internationalization (gettext) and the single-string vs. multiple-strings + issues. */ +void +write_documentation (stream, documentation, indentation, flags) + FILE *stream; + char **documentation; + int indentation, flags; +{ + register int i, j; + register char *line; + int string_array, texinfo, base_indent, filename_p; + + if (stream == 0) + return; + + string_array = flags & STRING_ARRAY; + filename_p = flags & HELPFILE; + + if (string_array) + { + fprintf (stream, " {\n#if defined (HELP_BUILTIN)\n"); /* } */ + if (single_longdoc_strings) + { + if (filename_p == 0) + { + if (documentation && documentation[0] && documentation[0][0]) + fprintf (stream, "N_(\""); + else + fprintf (stream, "N_(\" "); /* the empty string translates specially. */ + } + else + fprintf (stream, "\""); + } + } + + base_indent = (string_array && single_longdoc_strings && filename_p == 0) ? BASE_INDENT : 0; + + for (i = 0, texinfo = (flags & TEXINFO); line = documentation[i]; i++) + { + /* Allow #ifdef's to be written out verbatim, but don't put them into + separate help files. */ + if (*line == '#') + { + if (string_array && filename_p == 0 && single_longdoc_strings == 0) + fprintf (stream, "%s\n", line); + continue; + } + + /* prefix with N_( for gettext */ + if (string_array && single_longdoc_strings == 0) + { + if (filename_p == 0) + { + if (line[0]) + fprintf (stream, " N_(\""); + else + fprintf (stream, " N_(\" "); /* the empty string translates specially. */ + } + else + fprintf (stream, " \""); + } + + if (indentation) + for (j = 0; j < indentation; j++) + fprintf (stream, " "); + + /* Don't indent the first line, because of how the help builtin works. */ + if (i == 0) + indentation += base_indent; + + if (string_array) + { + for (j = 0; line[j]; j++) + { + switch (line[j]) + { + case '\\': + case '"': + fprintf (stream, "\\%c", line[j]); + break; + + default: + fprintf (stream, "%c", line[j]); + } + } + + /* closing right paren for gettext */ + if (single_longdoc_strings == 0) + { + if (filename_p == 0) + fprintf (stream, "\"),\n"); + else + fprintf (stream, "\",\n"); + } + else if (documentation[i+1]) + /* don't add extra newline after last line */ + fprintf (stream, "\\n\\\n"); + } + else if (texinfo) + { + for (j = 0; line[j]; j++) + { + switch (line[j]) + { + case '@': + case '{': + case '}': + fprintf (stream, "@%c", line[j]); + break; + + default: + fprintf (stream, "%c", line[j]); + } + } + fprintf (stream, "\n"); + } + else + fprintf (stream, "%s\n", line); + } + + /* closing right paren for gettext */ + if (string_array && single_longdoc_strings) + { + if (filename_p == 0) + fprintf (stream, "\"),\n"); + else + fprintf (stream, "\",\n"); + } + + if (string_array) + fprintf (stream, "#endif /* HELP_BUILTIN */\n (char *)NULL\n};\n"); +} + +int +write_helpfiles (builtins) + ARRAY *builtins; +{ + char *helpfile, *bname; + FILE *helpfp; + int i, hdlen; + BUILTIN_DESC *builtin; + + i = mkdir ("helpfiles", 0777); + if (i < 0 && errno != EEXIST) + { + fprintf (stderr, "write_helpfiles: helpfiles: cannot create directory\n"); + return -1; + } + + hdlen = strlen ("helpfiles/"); + for (i = 0; i < builtins->sindex; i++) + { + builtin = (BUILTIN_DESC *)builtins->array[i]; + + bname = document_name (builtin); + helpfile = (char *)xmalloc (hdlen + strlen (bname) + 1); + sprintf (helpfile, "helpfiles/%s", bname); + + helpfp = fopen (helpfile, "w"); + if (helpfp == 0) + { + fprintf (stderr, "write_helpfiles: cannot open %s\n", helpfile); + free (helpfile); + continue; + } + + write_documentation (helpfp, builtin->longdoc->array, 4, PLAINTEXT); + + fflush (helpfp); + fclose (helpfp); + free (helpfile); + } + return 0; +} + +static int +_find_in_table (name, name_table) + char *name, *name_table[]; +{ + register int i; + + for (i = 0; name_table[i]; i++) + if (strcmp (name, name_table[i]) == 0) + return 1; + return 0; +} + +static int +is_special_builtin (name) + char *name; +{ + return (_find_in_table (name, special_builtins)); +} + +static int +is_assignment_builtin (name) + char *name; +{ + return (_find_in_table (name, assignment_builtins)); +} + +static int +is_posix_builtin (name) + char *name; +{ + return (_find_in_table (name, posix_builtins)); +} + +#if !defined (HAVE_RENAME) +static int +rename (from, to) + char *from, *to; +{ + unlink (to); + if (link (from, to) < 0) + return (-1); + unlink (from); + return (0); +} +#endif /* !HAVE_RENAME */ diff --git a/builtins/read.def b/builtins/read.def index 41757c0c8..ba75be2ae 100644 --- a/builtins/read.def +++ b/builtins/read.def @@ -737,7 +737,7 @@ assign_vars: xfree (t1); } else - var = bind_read_variable (varname, t); + var = bind_read_variable (varname, t ? t : ""); } else { diff --git a/builtins/read.def~ b/builtins/read.def~ new file mode 100644 index 000000000..41757c0c8 --- /dev/null +++ b/builtins/read.def~ @@ -0,0 +1,999 @@ +This file is read.def, from which is created read.c. +It implements the builtin "read" in Bash. + +Copyright (C) 1987-2010 Free Software Foundation, Inc. + +This file is part of GNU Bash, the Bourne Again SHell. + +Bash is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Bash is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Bash. If not, see . + +$PRODUCES read.c + +$BUILTIN read +$FUNCTION read_builtin +$SHORT_DOC read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...] +Read a line from the standard input and split it into fields. + +Reads a single line from the standard input, or from file descriptor FD +if the -u option is supplied. The line is split into fields as with word +splitting, and the first word is assigned to the first NAME, the second +word to the second NAME, and so on, with any leftover words assigned to +the last NAME. Only the characters found in $IFS are recognized as word +delimiters. + +If no NAMEs are supplied, the line read is stored in the REPLY variable. + +Options: + -a array assign the words read to sequential indices of the array + variable ARRAY, starting at zero + -d delim continue until the first character of DELIM is read, rather + than newline + -e use Readline to obtain the line in an interactive shell + -i text Use TEXT as the initial text for Readline + -n nchars return after reading NCHARS characters rather than waiting + for a newline, but honor a delimiter if fewer than NCHARS + characters are read before the delimiter + -N nchars return only after reading exactly NCHARS characters, unless + EOF is encountered or read times out, ignoring any delimiter + -p prompt output the string PROMPT without a trailing newline before + attempting to read + -r do not allow backslashes to escape any characters + -s do not echo input coming from a terminal + -t timeout time out and return failure if a complete line of input is + not read withint TIMEOUT seconds. The value of the TMOUT + variable is the default timeout. TIMEOUT may be a + fractional number. If TIMEOUT is 0, read returns immediately, + without trying to read any data, returning success only if + input is available on the specified file descriptor. The + exit status is greater than 128 if the timeout is exceeded + -u fd read from file descriptor FD instead of the standard input + +Exit Status: +The return code is zero, unless end-of-file is encountered, read times out, +or an invalid file descriptor is supplied as the argument to -u. +$END + +#include + +#include "bashtypes.h" +#include "posixstat.h" + +#include + +#include "bashansi.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include + +#ifdef __CYGWIN__ +# include +# include +#endif + +#include "../bashintl.h" + +#include "../shell.h" +#include "common.h" +#include "bashgetopt.h" + +#include + +#if defined (READLINE) +#include "../bashline.h" +#include +#endif + +#if defined (BUFFERED_INPUT) +# include "input.h" +#endif + +#if !defined(errno) +extern int errno; +#endif + +struct ttsave +{ + int fd; + TTYSTRUCT *attrs; +}; + +#if defined (READLINE) +static void reset_attempted_completion_function __P((char *)); +static int set_itext __P((void)); +static char *edit_line __P((char *, char *)); +static void set_eol_delim __P((int)); +static void reset_eol_delim __P((char *)); +#endif +static SHELL_VAR *bind_read_variable __P((char *, char *)); +#if defined (HANDLE_MULTIBYTE) +static int read_mbchar __P((int, char *, int, int, int)); +#endif +static void ttyrestore __P((struct ttsave *)); + +static sighandler sigalrm __P((int)); +static void reset_alarm __P((void)); + +static procenv_t alrmbuf; +static SigHandler *old_alrm; +static unsigned char delim; + +static sighandler +sigalrm (s) + int s; +{ + longjmp (alrmbuf, 1); +} + +static void +reset_alarm () +{ + set_signal_handler (SIGALRM, old_alrm); + falarm (0, 0); +} + +/* Read the value of the shell variables whose names follow. + The reading is done from the current input stream, whatever + that may be. Successive words of the input line are assigned + to the variables mentioned in LIST. The last variable in LIST + gets the remainder of the words on the line. If no variables + are mentioned in LIST, then the default variable is $REPLY. */ +int +read_builtin (list) + WORD_LIST *list; +{ + register char *varname; + int size, i, nr, pass_next, saw_escape, eof, opt, retval, code, print_ps2; + int input_is_tty, input_is_pipe, unbuffered_read, skip_ctlesc, skip_ctlnul; + int raw, edit, nchars, silent, have_timeout, ignore_delim, fd; + unsigned int tmsec, tmusec; + long ival, uval; + intmax_t intval; + char c; + char *input_string, *orig_input_string, *ifs_chars, *prompt, *arrayname; + char *e, *t, *t1, *ps2, *tofree; + struct stat tsb; + SHELL_VAR *var; + TTYSTRUCT ttattrs, ttset; + struct ttsave termsave; +#if defined (ARRAY_VARS) + WORD_LIST *alist; +#endif +#if defined (READLINE) + char *rlbuf, *itext; + int rlind; +#endif + + USE_VAR(size); + USE_VAR(i); + USE_VAR(pass_next); + USE_VAR(print_ps2); + USE_VAR(saw_escape); + USE_VAR(input_is_pipe); +/* USE_VAR(raw); */ + USE_VAR(edit); + USE_VAR(tmsec); + USE_VAR(tmusec); + USE_VAR(nchars); + USE_VAR(silent); + USE_VAR(ifs_chars); + USE_VAR(prompt); + USE_VAR(arrayname); +#if defined (READLINE) + USE_VAR(rlbuf); + USE_VAR(rlind); + USE_VAR(itext); +#endif + USE_VAR(list); + USE_VAR(ps2); + + i = 0; /* Index into the string that we are reading. */ + raw = edit = 0; /* Not reading raw input by default. */ + silent = 0; + arrayname = prompt = (char *)NULL; + fd = 0; /* file descriptor to read from */ + +#if defined (READLINE) + rlbuf = itext = (char *)0; + rlind = 0; +#endif + + tmsec = tmusec = 0; /* no timeout */ + nr = nchars = input_is_tty = input_is_pipe = unbuffered_read = have_timeout = 0; + delim = '\n'; /* read until newline */ + ignore_delim = 0; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "ersa:d:i:n:p:t:u:N:")) != -1) + { + switch (opt) + { + case 'r': + raw = 1; + break; + case 'p': + prompt = list_optarg; + break; + case 's': + silent = 1; + break; + case 'e': +#if defined (READLINE) + edit = 1; +#endif + break; + case 'i': +#if defined (READLINE) + itext = list_optarg; +#endif + break; +#if defined (ARRAY_VARS) + case 'a': + arrayname = list_optarg; + break; +#endif + case 't': + code = uconvert (list_optarg, &ival, &uval); + if (code == 0 || ival < 0 || uval < 0) + { + builtin_error (_("%s: invalid timeout specification"), list_optarg); + return (EXECUTION_FAILURE); + } + else + { + have_timeout = 1; + tmsec = ival; + tmusec = uval; + } + break; + case 'N': + ignore_delim = 1; + delim = -1; + case 'n': + code = legal_number (list_optarg, &intval); + if (code == 0 || intval < 0 || intval != (int)intval) + { + sh_invalidnum (list_optarg); + return (EXECUTION_FAILURE); + } + else + nchars = intval; + break; + case 'u': + code = legal_number (list_optarg, &intval); + if (code == 0 || intval < 0 || intval != (int)intval) + { + builtin_error (_("%s: invalid file descriptor specification"), list_optarg); + return (EXECUTION_FAILURE); + } + else + fd = intval; + if (sh_validfd (fd) == 0) + { + builtin_error (_("%d: invalid file descriptor: %s"), fd, strerror (errno)); + return (EXECUTION_FAILURE); + } + break; + case 'd': + delim = *list_optarg; + break; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + /* `read -t 0 var' tests whether input is available with select/FIONREAD, + and fails if those are unavailable */ + if (have_timeout && tmsec == 0 && tmusec == 0) +#if 0 + return (EXECUTION_FAILURE); +#else + return (input_avail (fd) ? EXECUTION_SUCCESS : EXECUTION_FAILURE); +#endif + + /* If we're asked to ignore the delimiter, make sure we do. */ + if (ignore_delim) + delim = -1; + + /* IF IFS is unset, we use the default of " \t\n". */ + ifs_chars = getifs (); + if (ifs_chars == 0) /* XXX - shouldn't happen */ + ifs_chars = ""; + /* If we want to read exactly NCHARS chars, don't split on IFS */ + if (ignore_delim) + ifs_chars = ""; + for (skip_ctlesc = skip_ctlnul = 0, e = ifs_chars; *e; e++) + skip_ctlesc |= *e == CTLESC, skip_ctlnul |= *e == CTLNUL; + + input_string = (char *)xmalloc (size = 112); /* XXX was 128 */ + input_string[0] = '\0'; + + /* $TMOUT, if set, is the default timeout for read. */ + if (have_timeout == 0 && (e = get_string_value ("TMOUT"))) + { + code = uconvert (e, &ival, &uval); + if (code == 0 || ival < 0 || uval < 0) + tmsec = tmusec = 0; + else + { + tmsec = ival; + tmusec = uval; + } + } + + begin_unwind_frame ("read_builtin"); + +#if defined (BUFFERED_INPUT) + if (interactive == 0 && default_buffered_input >= 0 && fd_is_bash_input (fd)) + sync_buffered_stream (default_buffered_input); +#endif + + input_is_tty = isatty (fd); + if (input_is_tty == 0) +#ifndef __CYGWIN__ + input_is_pipe = (lseek (fd, 0L, SEEK_CUR) < 0) && (errno == ESPIPE); +#else + input_is_pipe = 1; +#endif + + /* If the -p, -e or -s flags were given, but input is not coming from the + terminal, turn them off. */ + if ((prompt || edit || silent) && input_is_tty == 0) + { + prompt = (char *)NULL; +#if defined (READLINE) + itext = (char *)NULL; +#endif + edit = silent = 0; + } + +#if defined (READLINE) + if (edit) + add_unwind_protect (xfree, rlbuf); +#endif + + pass_next = 0; /* Non-zero signifies last char was backslash. */ + saw_escape = 0; /* Non-zero signifies that we saw an escape char */ + + if (tmsec > 0 || tmusec > 0) + { + /* Turn off the timeout if stdin is a regular file (e.g. from + input redirection). */ + if ((fstat (fd, &tsb) < 0) || S_ISREG (tsb.st_mode)) + tmsec = tmusec = 0; + } + + if (tmsec > 0 || tmusec > 0) + { + code = setjmp (alrmbuf); + if (code) + { + /* Tricky. The top of the unwind-protect stack is the free of + input_string. We want to run all the rest and use input_string, + so we have to remove it from the stack. */ + remove_unwind_protect (); + run_unwind_frame ("read_builtin"); + input_string[i] = '\0'; /* make sure it's terminated */ + retval = 128+SIGALRM; + goto assign_vars; + } + old_alrm = set_signal_handler (SIGALRM, sigalrm); + add_unwind_protect (reset_alarm, (char *)NULL); +#if defined (READLINE) + if (edit) + add_unwind_protect (reset_attempted_completion_function, (char *)NULL); +#endif + falarm (tmsec, tmusec); + } + + /* If we've been asked to read only NCHARS chars, or we're using some + character other than newline to terminate the line, do the right + thing to readline or the tty. */ + if (nchars > 0 || delim != '\n') + { +#if defined (READLINE) + if (edit) + { + if (nchars > 0) + { + unwind_protect_int (rl_num_chars_to_read); + rl_num_chars_to_read = nchars; + } + if (delim != '\n') + { + set_eol_delim (delim); + add_unwind_protect (reset_eol_delim, (char *)NULL); + } + } + else +#endif + if (input_is_tty) + { + /* ttsave() */ + termsave.fd = fd; + ttgetattr (fd, &ttattrs); + termsave.attrs = &ttattrs; + + ttset = ttattrs; + i = silent ? ttfd_cbreak (fd, &ttset) : ttfd_onechar (fd, &ttset); + if (i < 0) + sh_ttyerror (1); + add_unwind_protect ((Function *)ttyrestore, (char *)&termsave); + } + } + else if (silent) /* turn off echo but leave term in canonical mode */ + { + /* ttsave (); */ + termsave.fd = fd; + ttgetattr (fd, &ttattrs); + termsave.attrs = &ttattrs; + + ttset = ttattrs; + i = ttfd_noecho (fd, &ttset); /* ttnoecho (); */ + if (i < 0) + sh_ttyerror (1); + + add_unwind_protect ((Function *)ttyrestore, (char *)&termsave); + } + + /* This *must* be the top unwind-protect on the stack, so the manipulation + of the unwind-protect stack after the realloc() works right. */ + add_unwind_protect (xfree, input_string); + interrupt_immediately++; + + unbuffered_read = (nchars > 0) || (delim != '\n') || input_is_pipe; + + if (prompt && edit == 0) + { + fprintf (stderr, "%s", prompt); + fflush (stderr); + } + +#if defined (__CYGWIN__) && defined (O_TEXT) + setmode (0, O_TEXT); +#endif + + ps2 = 0; + for (print_ps2 = eof = retval = 0;;) + { +#if defined (READLINE) + if (edit) + { + if (rlbuf && rlbuf[rlind] == '\0') + { + xfree (rlbuf); + rlbuf = (char *)0; + } + if (rlbuf == 0) + { + rlbuf = edit_line (prompt ? prompt : "", itext); + rlind = 0; + } + if (rlbuf == 0) + { + eof = 1; + break; + } + c = rlbuf[rlind++]; + } + else + { +#endif + + if (print_ps2) + { + if (ps2 == 0) + ps2 = get_string_value ("PS2"); + fprintf (stderr, "%s", ps2 ? ps2 : ""); + fflush (stderr); + print_ps2 = 0; + } + + if (unbuffered_read) + retval = zread (fd, &c, 1); + else + retval = zreadc (fd, &c); + + if (retval <= 0) + { + CHECK_TERMSIG; + eof = 1; + break; + } + +#if defined (READLINE) + } +#endif + + if (i + 4 >= size) /* XXX was i + 2; use i + 4 for multibyte/read_mbchar */ + { + input_string = (char *)xrealloc (input_string, size += 128); + remove_unwind_protect (); + add_unwind_protect (xfree, input_string); + } + + /* If the next character is to be accepted verbatim, a backslash + newline pair still disappears from the input. */ + if (pass_next) + { + pass_next = 0; + if (c == '\n') + { + i--; /* back up over the CTLESC */ + if (interactive && input_is_tty && raw == 0) + print_ps2 = 1; + } + else + goto add_char; + continue; + } + + /* This may cause problems if IFS contains CTLESC */ + if (c == '\\' && raw == 0) + { + pass_next++; + if (skip_ctlesc == 0) + { + saw_escape++; + input_string[i++] = CTLESC; + } + continue; + } + + if ((unsigned char)c == delim) + break; + + if ((skip_ctlesc == 0 && c == CTLESC) || (skip_ctlnul == 0 && c == CTLNUL)) + { + saw_escape++; + input_string[i++] = CTLESC; + } + +add_char: + input_string[i++] = c; + +#if defined (HANDLE_MULTIBYTE) + if (nchars > 0 && MB_CUR_MAX > 1) + { + input_string[i] = '\0'; /* for simplicity and debugging */ + i += read_mbchar (fd, input_string, i, c, unbuffered_read); + } +#endif + + nr++; + + if (nchars > 0 && nr >= nchars) + break; + } + input_string[i] = '\0'; + +#if 1 + if (retval < 0) + { + builtin_error (_("read error: %d: %s"), fd, strerror (errno)); + run_unwind_frame ("read_builtin"); + return (EXECUTION_FAILURE); + } +#endif + + if (tmsec > 0 || tmusec > 0) + reset_alarm (); + + if (nchars > 0 || delim != '\n') + { +#if defined (READLINE) + if (edit) + { + if (nchars > 0) + rl_num_chars_to_read = 0; + if (delim != '\n') + reset_eol_delim ((char *)NULL); + } + else +#endif + if (input_is_tty) + ttyrestore (&termsave); + } + else if (silent) + ttyrestore (&termsave); + + if (unbuffered_read == 0) + zsyncfd (fd); + + discard_unwind_frame ("read_builtin"); + + retval = eof ? EXECUTION_FAILURE : EXECUTION_SUCCESS; + +assign_vars: + + interrupt_immediately--; + +#if defined (ARRAY_VARS) + /* If -a was given, take the string read, break it into a list of words, + an assign them to `arrayname' in turn. */ + if (arrayname) + { + if (legal_identifier (arrayname) == 0) + { + sh_invalidid (arrayname); + xfree (input_string); + return (EXECUTION_FAILURE); + } + + var = find_or_make_array_variable (arrayname, 1); + if (var == 0) + { + xfree (input_string); + return EXECUTION_FAILURE; /* readonly or noassign */ + } + if (assoc_p (var)) + { + builtin_error (_("%s: cannot convert associative to indexed array"), arrayname); + xfree (input_string); + return EXECUTION_FAILURE; /* existing associative array */ + } + array_flush (array_cell (var)); + + alist = list_string (input_string, ifs_chars, 0); + if (alist) + { + if (saw_escape) + dequote_list (alist); + else + word_list_remove_quoted_nulls (alist); + assign_array_var_from_word_list (var, alist, 0); + dispose_words (alist); + } + xfree (input_string); + return (retval); + } +#endif /* ARRAY_VARS */ + + /* If there are no variables, save the text of the line read to the + variable $REPLY. ksh93 strips leading and trailing IFS whitespace, + so that `read x ; echo "$x"' and `read ; echo "$REPLY"' behave the + same way, but I believe that the difference in behaviors is useful + enough to not do it. Without the bash behavior, there is no way + to read a line completely without interpretation or modification + unless you mess with $IFS (e.g., setting it to the empty string). + If you disagree, change the occurrences of `#if 0' to `#if 1' below. */ + if (list == 0) + { +#if 0 + orig_input_string = input_string; + for (t = input_string; ifs_chars && *ifs_chars && spctabnl(*t) && isifs(*t); t++) + ; + input_string = t; + input_string = strip_trailing_ifs_whitespace (input_string, ifs_chars, saw_escape); +#endif + + if (saw_escape) + { + t = dequote_string (input_string); + var = bind_variable ("REPLY", t, 0); + free (t); + } + else + var = bind_variable ("REPLY", input_string, 0); + VUNSETATTR (var, att_invisible); + + free (input_string); + return (retval); + } + + /* This code implements the Posix.2 spec for splitting the words + read and assigning them to variables. */ + orig_input_string = input_string; + + /* Remove IFS white space at the beginning of the input string. If + $IFS is null, no field splitting is performed. */ + for (t = input_string; ifs_chars && *ifs_chars && spctabnl(*t) && isifs(*t); t++) + ; + input_string = t; + for (; list->next; list = list->next) + { + varname = list->word->word; +#if defined (ARRAY_VARS) + if (legal_identifier (varname) == 0 && valid_array_reference (varname) == 0) +#else + if (legal_identifier (varname) == 0) +#endif + { + sh_invalidid (varname); + xfree (orig_input_string); + return (EXECUTION_FAILURE); + } + + /* If there are more variables than words read from the input, + the remaining variables are set to the empty string. */ + if (*input_string) + { + /* This call updates INPUT_STRING. */ + t = get_word_from_string (&input_string, ifs_chars, &e); + if (t) + *e = '\0'; + /* Don't bother to remove the CTLESC unless we added one + somewhere while reading the string. */ + if (t && saw_escape) + { + t1 = dequote_string (t); + var = bind_read_variable (varname, t1); + xfree (t1); + } + else + var = bind_read_variable (varname, t); + } + else + { + t = (char *)0; + var = bind_read_variable (varname, ""); + } + + FREE (t); + if (var == 0) + { + xfree (orig_input_string); + return (EXECUTION_FAILURE); + } + + stupidly_hack_special_variables (varname); + VUNSETATTR (var, att_invisible); + } + + /* Now assign the rest of the line to the last variable argument. */ +#if defined (ARRAY_VARS) + if (legal_identifier (list->word->word) == 0 && valid_array_reference (list->word->word) == 0) +#else + if (legal_identifier (list->word->word) == 0) +#endif + { + sh_invalidid (list->word->word); + xfree (orig_input_string); + return (EXECUTION_FAILURE); + } + +#if 0 + /* This has to be done this way rather than using string_list + and list_string because Posix.2 says that the last variable gets the + remaining words and their intervening separators. */ + input_string = strip_trailing_ifs_whitespace (input_string, ifs_chars, saw_escape); +#else + /* Check whether or not the number of fields is exactly the same as the + number of variables. */ + tofree = NULL; + if (*input_string) + { + t1 = input_string; + t = get_word_from_string (&input_string, ifs_chars, &e); + if (*input_string == 0) + tofree = input_string = t; + else + { + input_string = strip_trailing_ifs_whitespace (t1, ifs_chars, saw_escape); + tofree = t; + } + } +#endif + + if (saw_escape) + { + t = dequote_string (input_string); + var = bind_read_variable (list->word->word, t); + xfree (t); + } + else + var = bind_read_variable (list->word->word, input_string); + + if (var) + { + stupidly_hack_special_variables (list->word->word); + VUNSETATTR (var, att_invisible); + } + else + retval = EXECUTION_FAILURE; + + FREE (tofree); + xfree (orig_input_string); + + return (retval); +} + +static SHELL_VAR * +bind_read_variable (name, value) + char *name, *value; +{ + SHELL_VAR *v; +#if defined (ARRAY_VARS) + if (valid_array_reference (name) == 0) + v = bind_variable (name, value, 0); + else + v = assign_array_element (name, value, 0); +#else /* !ARRAY_VARS */ + v = bind_variable (name, value, 0); +#endif /* !ARRAY_VARS */ + return (v == 0 ? v + : ((readonly_p (v) || noassign_p (v)) ? (SHELL_VAR *)NULL : v)); +} + +#if defined (HANDLE_MULTIBYTE) +static int +read_mbchar (fd, string, ind, ch, unbuffered) + int fd; + char *string; + int ind, ch, unbuffered; +{ + char mbchar[MB_LEN_MAX + 1]; + int i, n, r; + char c; + size_t ret; + mbstate_t ps, ps_back; + wchar_t wc; + + memset (&ps, '\0', sizeof (mbstate_t)); + memset (&ps_back, '\0', sizeof (mbstate_t)); + + mbchar[0] = ch; + i = 1; + for (n = 0; n <= MB_LEN_MAX; n++) + { + ps_back = ps; + ret = mbrtowc (&wc, mbchar, i, &ps); + if (ret == (size_t)-2) + { + ps = ps_back; + if (unbuffered) + r = zread (fd, &c, 1); + else + r = zreadc (fd, &c); + if (r < 0) + goto mbchar_return; + mbchar[i++] = c; + continue; + } + else if (ret == (size_t)-1 || ret == (size_t)0 || ret > (size_t)0) + break; + } + +mbchar_return: + if (i > 1) /* read a multibyte char */ + /* mbchar[0] is already string[ind-1] */ + for (r = 1; r < i; r++) + string[ind+r-1] = mbchar[r]; + return i - 1; +} +#endif + + +static void +ttyrestore (ttp) + struct ttsave *ttp; +{ + ttsetattr (ttp->fd, ttp->attrs); +} + +#if defined (READLINE) +static rl_completion_func_t *old_attempted_completion_function = 0; +static rl_hook_func_t *old_startup_hook; +static char *deftext; + +static void +reset_attempted_completion_function (cp) + char *cp; +{ + if (rl_attempted_completion_function == 0 && old_attempted_completion_function) + rl_attempted_completion_function = old_attempted_completion_function; +} + +static int +set_itext () +{ + int r1, r2; + + r1 = r2 = 0; + if (old_startup_hook) + r1 = (*old_startup_hook) (); + if (deftext) + { + r2 = rl_insert_text (deftext); + deftext = (char *)NULL; + rl_startup_hook = old_startup_hook; + old_startup_hook = (rl_hook_func_t *)NULL; + } + return (r1 || r2); +} + +static char * +edit_line (p, itext) + char *p; + char *itext; +{ + char *ret; + int len; + + if (bash_readline_initialized == 0) + initialize_readline (); + + old_attempted_completion_function = rl_attempted_completion_function; + rl_attempted_completion_function = (rl_completion_func_t *)NULL; + if (itext) + { + old_startup_hook = rl_startup_hook; + rl_startup_hook = set_itext; + deftext = itext; + } + ret = readline (p); + rl_attempted_completion_function = old_attempted_completion_function; + old_attempted_completion_function = (rl_completion_func_t *)NULL; + + if (ret == 0) + return ret; + len = strlen (ret); + ret = (char *)xrealloc (ret, len + 2); + ret[len++] = delim; + ret[len] = '\0'; + return ret; +} + +static int old_delim_ctype; +static rl_command_func_t *old_delim_func; +static int old_newline_ctype; +static rl_command_func_t *old_newline_func; + +static unsigned char delim_char; + +static void +set_eol_delim (c) + int c; +{ + Keymap cmap; + + if (bash_readline_initialized == 0) + initialize_readline (); + cmap = rl_get_keymap (); + + /* Change newline to self-insert */ + old_newline_ctype = cmap[RETURN].type; + old_newline_func = cmap[RETURN].function; + cmap[RETURN].type = ISFUNC; + cmap[RETURN].function = rl_insert; + + /* Bind the delimiter character to accept-line. */ + old_delim_ctype = cmap[c].type; + old_delim_func = cmap[c].function; + cmap[c].type = ISFUNC; + cmap[c].function = rl_newline; + + delim_char = c; +} + +static void +reset_eol_delim (cp) + char *cp; +{ + Keymap cmap; + + cmap = rl_get_keymap (); + + cmap[RETURN].type = old_newline_ctype; + cmap[RETURN].function = old_newline_func; + + cmap[delim_char].type = old_delim_ctype; + cmap[delim_char].function = old_delim_func; +} +#endif diff --git a/builtins/type.def b/builtins/type.def index ee341bb8b..bd9ecfc1f 100644 --- a/builtins/type.def +++ b/builtins/type.def @@ -1,7 +1,7 @@ This file is type.def, from which is created type.c. It implements the builtin "type" in Bash. -Copyright (C) 1987-2009 Free Software Foundation, Inc. +Copyright (C) 1987-2011 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -353,7 +353,7 @@ describe_command (command, dflags) user_command_matches (command, FS_EXEC_ONLY, found_file); /* XXX - should that be FS_EXEC_PREFERRED? */ - if (!full_path) + if (full_path == 0) break; /* If we found the command as itself by looking through $PATH, it @@ -375,7 +375,9 @@ describe_command (command, dflags) else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY|CDESC_SHORTDESC)) { f = MP_DOCWD | ((dflags & CDESC_ABSPATH) ? MP_RMDOT : 0); - full_path = sh_makepath ((char *)NULL, full_path, f); + x = sh_makepath ((char *)NULL, full_path, f); + free (full_path); + full_path = x; } } /* If we require a full path and don't have one, make one */ diff --git a/builtins/type.def~ b/builtins/type.def~ new file mode 100644 index 000000000..ee341bb8b --- /dev/null +++ b/builtins/type.def~ @@ -0,0 +1,403 @@ +This file is type.def, from which is created type.c. +It implements the builtin "type" in Bash. + +Copyright (C) 1987-2009 Free Software Foundation, Inc. + +This file is part of GNU Bash, the Bourne Again SHell. + +Bash is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Bash is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Bash. If not, see . + +$PRODUCES type.c + +$BUILTIN type +$FUNCTION type_builtin +$SHORT_DOC type [-afptP] name [name ...] +Display information about command type. + +For each NAME, indicate how it would be interpreted if used as a +command name. + +Options: + -a display all locations containing an executable named NAME; + includes aliases, builtins, and functions, if and only if + the `-p' option is not also used + -f suppress shell function lookup + -P force a PATH search for each NAME, even if it is an alias, + builtin, or function, and returns the name of the disk file + that would be executed + -p returns either the name of the disk file that would be executed, + or nothing if `type -t NAME' would not return `file'. + -t output a single word which is one of `alias', `keyword', + `function', `builtin', `file' or `', if NAME is an alias, shell + reserved word, shell function, shell builtin, disk file, or not + found, respectively + +Arguments: + NAME Command name to be interpreted. + +Exit Status: +Returns success if all of the NAMEs are found; fails if any are not found. +$END + +#include + +#include "../bashtypes.h" +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include "../bashansi.h" +#include "../bashintl.h" + +#include "../shell.h" +#include "../findcmd.h" +#include "../hashcmd.h" + +#if defined (ALIAS) +#include "../alias.h" +#endif /* ALIAS */ + +#include "common.h" +#include "bashgetopt.h" + +extern int find_reserved_word __P((char *)); + +extern char *this_command_name; +extern int expand_aliases, posixly_correct; + +/* For each word in LIST, find out what the shell is going to do with + it as a simple command. i.e., which file would this shell use to + execve, or if it is a builtin command, or an alias. Possible flag + arguments: + -t Returns the "type" of the object, one of + `alias', `keyword', `function', `builtin', + or `file'. + + -p Returns the pathname of the file if -type is + a file. + + -a Returns all occurrences of words, whether they + be a filename in the path, alias, function, + or builtin. + + -f Suppress shell function lookup, like `command'. + + -P Force a path search even in the presence of other + definitions. + + Order of evaluation: + alias + keyword + function + builtin + file + */ + +int +type_builtin (list) + WORD_LIST *list; +{ + int dflags, any_failed, opt; + WORD_LIST *this; + + if (list == 0) + return (EXECUTION_SUCCESS); + + dflags = CDESC_SHORTDESC; /* default */ + any_failed = 0; + + /* Handle the obsolescent `-type', `-path', and `-all' by prescanning + the arguments and converting those options to the form that + internal_getopt recognizes. Converts `--type', `--path', and `--all' + also. THIS SHOULD REALLY GO AWAY. */ + for (this = list; this && this->word->word[0] == '-'; this = this->next) + { + char *flag = &(this->word->word[1]); + + if (STREQ (flag, "type") || STREQ (flag, "-type")) + { + this->word->word[1] = 't'; + this->word->word[2] = '\0'; + } + else if (STREQ (flag, "path") || STREQ (flag, "-path")) + { + this->word->word[1] = 'p'; + this->word->word[2] = '\0'; + } + else if (STREQ (flag, "all") || STREQ (flag, "-all")) + { + this->word->word[1] = 'a'; + this->word->word[2] = '\0'; + } + } + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "afptP")) != -1) + { + switch (opt) + { + case 'a': + dflags |= CDESC_ALL; + break; + case 'f': + dflags |= CDESC_NOFUNCS; + break; + case 'p': + dflags |= CDESC_PATH_ONLY; + dflags &= ~(CDESC_TYPE|CDESC_SHORTDESC); + break; + case 't': + dflags |= CDESC_TYPE; + dflags &= ~(CDESC_PATH_ONLY|CDESC_SHORTDESC); + break; + case 'P': /* shorthand for type -ap */ + dflags |= (CDESC_PATH_ONLY|CDESC_FORCE_PATH); + dflags &= ~(CDESC_TYPE|CDESC_SHORTDESC); + break; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + while (list) + { + int found; + + found = describe_command (list->word->word, dflags); + + if (!found && (dflags & (CDESC_PATH_ONLY|CDESC_TYPE)) == 0) + sh_notfound (list->word->word); + + any_failed += found == 0; + list = list->next; + } + + opt = (any_failed == 0) ? EXECUTION_SUCCESS : EXECUTION_FAILURE; + return (sh_chkwrite (opt)); +} + +/* + * Describe COMMAND as required by the type and command builtins. + * + * Behavior is controlled by DFLAGS. Flag values are + * CDESC_ALL print all descriptions of a command + * CDESC_SHORTDESC print the description for type and command -V + * CDESC_REUSABLE print in a format that may be reused as input + * CDESC_TYPE print the type for type -t + * CDESC_PATH_ONLY print the path for type -p + * CDESC_FORCE_PATH force a path search for type -P + * CDESC_NOFUNCS skip function lookup for type -f + * CDESC_ABSPATH convert to absolute path, no ./ prefix + * + * CDESC_ALL says whether or not to look for all occurrences of COMMAND, or + * return after finding it once. + */ +int +describe_command (command, dflags) + char *command; + int dflags; +{ + int found, i, found_file, f, all; + char *full_path, *x; + SHELL_VAR *func; +#if defined (ALIAS) + alias_t *alias; +#endif + + all = (dflags & CDESC_ALL) != 0; + found = found_file = 0; + full_path = (char *)NULL; + +#if defined (ALIAS) + /* Command is an alias? */ + if (((dflags & CDESC_FORCE_PATH) == 0) && expand_aliases && (alias = find_alias (command))) + { + if (dflags & CDESC_TYPE) + puts ("alias"); + else if (dflags & CDESC_SHORTDESC) + printf (_("%s is aliased to `%s'\n"), command, alias->value); + else if (dflags & CDESC_REUSABLE) + { + x = sh_single_quote (alias->value); + printf ("alias %s=%s\n", command, x); + free (x); + } + + found = 1; + + if (all == 0) + return (1); + } +#endif /* ALIAS */ + + /* Command is a shell reserved word? */ + if (((dflags & CDESC_FORCE_PATH) == 0) && (i = find_reserved_word (command)) >= 0) + { + if (dflags & CDESC_TYPE) + puts ("keyword"); + else if (dflags & CDESC_SHORTDESC) + printf (_("%s is a shell keyword\n"), command); + else if (dflags & CDESC_REUSABLE) + printf ("%s\n", command); + + found = 1; + + if (all == 0) + return (1); + } + + /* Command is a function? */ + if (((dflags & (CDESC_FORCE_PATH|CDESC_NOFUNCS)) == 0) && (func = find_function (command))) + { + if (dflags & CDESC_TYPE) + puts ("function"); + else if (dflags & CDESC_SHORTDESC) + { + char *result; + + printf (_("%s is a function\n"), command); + + /* We're blowing away THE_PRINTED_COMMAND here... */ + + result = named_function_string (command, function_cell (func), FUNC_MULTILINE|FUNC_EXTERNAL); + printf ("%s\n", result); + } + else if (dflags & CDESC_REUSABLE) + printf ("%s\n", command); + + found = 1; + + if (all == 0) + return (1); + } + + /* Command is a builtin? */ + if (((dflags & CDESC_FORCE_PATH) == 0) && find_shell_builtin (command)) + { + if (dflags & CDESC_TYPE) + puts ("builtin"); + else if (dflags & CDESC_SHORTDESC) + printf (_("%s is a shell builtin\n"), command); + else if (dflags & CDESC_REUSABLE) + printf ("%s\n", command); + + found = 1; + + if (all == 0) + return (1); + } + + /* Command is a disk file? */ + /* If the command name given is already an absolute command, just + check to see if it is executable. */ + if (absolute_program (command)) + { + f = file_status (command); + if (f & FS_EXECABLE) + { + if (dflags & CDESC_TYPE) + puts ("file"); + else if (dflags & CDESC_SHORTDESC) + printf (_("%s is %s\n"), command, command); + else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY)) + printf ("%s\n", command); + + /* There's no use looking in the hash table or in $PATH, + because they're not consulted when an absolute program + name is supplied. */ + return (1); + } + } + + /* If the user isn't doing "-a", then we might care about + whether the file is present in our hash table. */ + if (all == 0 || (dflags & CDESC_FORCE_PATH)) + { + if (full_path = phash_search (command)) + { + if (dflags & CDESC_TYPE) + puts ("file"); + else if (dflags & CDESC_SHORTDESC) + printf (_("%s is hashed (%s)\n"), command, full_path); + else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY)) + printf ("%s\n", full_path); + + free (full_path); + return (1); + } + } + + /* Now search through $PATH. */ + while (1) + { + if (all == 0) + full_path = find_user_command (command); + else + full_path = + user_command_matches (command, FS_EXEC_ONLY, found_file); + /* XXX - should that be FS_EXEC_PREFERRED? */ + + if (!full_path) + break; + + /* If we found the command as itself by looking through $PATH, it + probably doesn't exist. Check whether or not the command is an + executable file. If it's not, don't report a match. This is + the default posix mode behavior */ + if (STREQ (full_path, command) || posixly_correct) + { + f = file_status (full_path); + if ((f & FS_EXECABLE) == 0) + { + free (full_path); + full_path = (char *)NULL; + if (all == 0) + break; + } + else if (ABSPATH (full_path)) + ; /* placeholder; don't need to do anything yet */ + else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY|CDESC_SHORTDESC)) + { + f = MP_DOCWD | ((dflags & CDESC_ABSPATH) ? MP_RMDOT : 0); + full_path = sh_makepath ((char *)NULL, full_path, f); + } + } + /* If we require a full path and don't have one, make one */ + else if ((dflags & CDESC_ABSPATH) && ABSPATH (full_path) == 0) + full_path = sh_makepath ((char *)NULL, full_path, MP_DOCWD|MP_RMDOT); + + found_file++; + found = 1; + + if (dflags & CDESC_TYPE) + puts ("file"); + else if (dflags & CDESC_SHORTDESC) + printf (_("%s is %s\n"), command, full_path); + else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY)) + printf ("%s\n", full_path); + + free (full_path); + full_path = (char *)NULL; + + if (all == 0) + break; + } + + return (found); +} diff --git a/eval.c b/eval.c index 9011e0bf4..aaa61dd72 100644 --- a/eval.c +++ b/eval.c @@ -1,6 +1,6 @@ /* eval.c -- reading and evaluating commands. */ -/* Copyright (C) 1996-2009 Free Software Foundation, Inc. +/* Copyright (C) 1996-2011 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -190,12 +190,14 @@ alrm_catcher(i) static void send_pwd_to_eterm () { - char *pwd; + char *pwd, *f; + f = 0; pwd = get_string_value ("PWD"); if (pwd == 0) - pwd = get_working_directory ("eterm"); + f = pwd = get_working_directory ("eterm"); fprintf (stderr, "\032/%s\n", pwd); + free (f); } /* Call the YACC-generated parser and return the status of the parse. diff --git a/eval.c~ b/eval.c~ new file mode 100644 index 000000000..97423ba0b --- /dev/null +++ b/eval.c~ @@ -0,0 +1,283 @@ +/* eval.c -- reading and evaluating commands. */ + +/* Copyright (C) 1996-2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashansi.h" +#include + +#include "bashintl.h" + +#include "shell.h" +#include "flags.h" +#include "trap.h" + +#include "builtins/common.h" + +#include "input.h" +#include "execute_cmd.h" + +#if defined (HISTORY) +# include "bashhist.h" +#endif + +extern int EOF_reached; +extern int indirection_level; +extern int posixly_correct; +extern int subshell_environment, running_under_emacs; +extern int last_command_exit_value, stdin_redir; +extern int need_here_doc; +extern int current_command_number, current_command_line_count, line_number; +extern int expand_aliases; + +static void send_pwd_to_eterm __P((void)); +static sighandler alrm_catcher __P((int)); + +/* Read and execute commands until EOF is reached. This assumes that + the input source has already been initialized. */ +int +reader_loop () +{ + int our_indirection_level; + COMMAND * volatile current_command; + + USE_VAR(current_command); + + current_command = (COMMAND *)NULL; + + our_indirection_level = ++indirection_level; + + while (EOF_Reached == 0) + { + int code; + + code = setjmp (top_level); + +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + + if (interactive_shell && signal_is_ignored (SIGINT) == 0) + set_signal_handler (SIGINT, sigint_sighandler); + + if (code != NOT_JUMPED) + { + indirection_level = our_indirection_level; + + switch (code) + { + /* Some kind of throw to top_level has occured. */ + case FORCE_EOF: + case ERREXIT: + case EXITPROG: + current_command = (COMMAND *)NULL; + if (exit_immediately_on_error) + variable_context = 0; /* not in a function */ + EOF_Reached = EOF; + goto exec_done; + + case DISCARD: + /* Make sure the exit status is reset to a non-zero value, but + leave existing non-zero values (e.g., > 128 on signal) + alone. */ + if (last_command_exit_value == 0) + last_command_exit_value = EXECUTION_FAILURE; + if (subshell_environment) + { + current_command = (COMMAND *)NULL; + EOF_Reached = EOF; + goto exec_done; + } + /* Obstack free command elements, etc. */ + if (current_command) + { + dispose_command (current_command); + current_command = (COMMAND *)NULL; + } + break; + + default: + command_error ("reader_loop", CMDERR_BADJUMP, code, 0); + } + } + + executing = 0; + if (temporary_env) + dispose_used_env_vars (); + +#if (defined (ultrix) && defined (mips)) || defined (C_ALLOCA) + /* Attempt to reclaim memory allocated with alloca (). */ + (void) alloca (0); +#endif + + if (read_command () == 0) + { + if (interactive_shell == 0 && read_but_dont_execute) + { + last_command_exit_value = EXECUTION_SUCCESS; + dispose_command (global_command); + global_command = (COMMAND *)NULL; + } + else if (current_command = global_command) + { + global_command = (COMMAND *)NULL; + current_command_number++; + + executing = 1; + stdin_redir = 0; + execute_command (current_command); + + exec_done: + QUIT; + + if (current_command) + { + dispose_command (current_command); + current_command = (COMMAND *)NULL; + } + } + } + else + { + /* Parse error, maybe discard rest of stream if not interactive. */ + if (interactive == 0) + EOF_Reached = EOF; + } + if (just_one_command) + EOF_Reached = EOF; + } + indirection_level--; + return (last_command_exit_value); +} + +static sighandler +alrm_catcher(i) + int i; +{ + printf (_("\007timed out waiting for input: auto-logout\n")); + fflush (stdout); + bash_logout (); /* run ~/.bash_logout if this is a login shell */ + jump_to_top_level (EXITPROG); + SIGRETURN (0); +} + +/* Send an escape sequence to emacs term mode to tell it the + current working directory. */ +static void +send_pwd_to_eterm () +{ + char *pwd, *f; + + f = 0; + pwd = get_string_value ("PWD"); + if (pwd == 0) + f = pwd = get_working_directory ("eterm"); + fprintf (stderr, "\032/%s\n", pwd); + free (f); +} + +/* Call the YACC-generated parser and return the status of the parse. + Input is read from the current input stream (bash_input). yyparse + leaves the parsed command in the global variable GLOBAL_COMMAND. + This is where PROMPT_COMMAND is executed. */ +int +parse_command () +{ + int r; + char *command_to_execute; + + need_here_doc = 0; + run_pending_traps (); + + /* Allow the execution of a random command just before the printing + of each primary prompt. If the shell variable PROMPT_COMMAND + is set then the value of it is the command to execute. */ + if (interactive && bash_input.type != st_string) + { + command_to_execute = get_string_value ("PROMPT_COMMAND"); + if (command_to_execute) + execute_variable_command (command_to_execute, "PROMPT_COMMAND"); + + if (running_under_emacs == 2) + send_pwd_to_eterm (); /* Yuck */ + } + + current_command_line_count = 0; + r = yyparse (); + + if (need_here_doc) + gather_here_documents (); + + return (r); +} + +/* Read and parse a command, returning the status of the parse. The command + is left in the globval variable GLOBAL_COMMAND for use by reader_loop. + This is where the shell timeout code is executed. */ +int +read_command () +{ + SHELL_VAR *tmout_var; + int tmout_len, result; + SigHandler *old_alrm; + + set_current_prompt_level (1); + global_command = (COMMAND *)NULL; + + /* Only do timeouts if interactive. */ + tmout_var = (SHELL_VAR *)NULL; + tmout_len = 0; + old_alrm = (SigHandler *)NULL; + + if (interactive) + { + tmout_var = find_variable ("TMOUT"); + + if (tmout_var && var_isset (tmout_var)) + { + tmout_len = atoi (value_cell (tmout_var)); + if (tmout_len > 0) + { + old_alrm = set_signal_handler (SIGALRM, alrm_catcher); + alarm (tmout_len); + } + } + } + + QUIT; + + current_command_line_count = 0; + result = parse_command (); + + if (interactive && tmout_var && (tmout_len > 0)) + { + alarm(0); + set_signal_handler (SIGALRM, old_alrm); + } + + return (result); +} diff --git a/execute_cmd.c b/execute_cmd.c index ea521396a..a5bd4bed9 100644 --- a/execute_cmd.c +++ b/execute_cmd.c @@ -1252,7 +1252,7 @@ time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close) # endif #endif - posix_time = (command->flags & CMD_TIME_POSIX); + posix_time = command && (command->flags & CMD_TIME_POSIX); nullcmd = (command == 0) || (command->type == cm_simple && command->value.Simple->words == 0 && command->value.Simple->redirects == 0); if (posixly_correct && nullcmd) @@ -1672,32 +1672,45 @@ cpl_delete (pid) static void cpl_reap () { - struct cpelement *prev, *p; - - for (prev = p = coproc_list.head; p; prev = p, p = p->next) - if (p->coproc->c_flags & COPROC_DEAD) - { - prev->next = p->next; /* remove from list */ - - /* Housekeeping in the border cases. */ - if (p == coproc_list.head) - coproc_list.head = coproc_list.head->next; - else if (p == coproc_list.tail) - coproc_list.tail = prev; + struct cpelement *p, *next, *nh, *nt; - coproc_list.ncoproc--; - if (coproc_list.ncoproc == 0) - coproc_list.head = coproc_list.tail = 0; - else if (coproc_list.ncoproc == 1) - coproc_list.tail = coproc_list.head; /* just to make sure */ + /* Build a new list by removing dead coprocs and fix up the coproc_list + pointers when done. */ + nh = nt = next = (struct cpelement *)0; + for (p = coproc_list.head; p; p = next) + { + next = p->next; + if (p->coproc->c_flags & COPROC_DEAD) + { + coproc_list.ncoproc--; /* keep running count, fix up pointers later */ #if defined (DEBUG) - itrace("cpl_reap: deleting %d", p->coproc->c_pid); + itrace("cpl_reap: deleting %d", p->coproc->c_pid); #endif - coproc_dispose (p->coproc); - cpe_dispose (p); - } + coproc_dispose (p->coproc); + cpe_dispose (p); + } + else if (nh == 0) + nh = nt = p; + else + { + nt->next = p; + nt = nt->next; + } + } + + if (coproc_list.ncoproc == 0) + coproc_list.head = coproc_list.tail = 0; + else + { + if (nt) + nt->next = 0; + coproc_list.head = nh; + coproc_list.tail = nt; + if (coproc_list.ncoproc == 1) + coproc_list.tail = coproc_list.head; /* just to make sure */ + } } /* Clear out the list of saved statuses */ @@ -2321,7 +2334,8 @@ execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close) lastpipe_jid = stop_pipeline (0, (COMMAND *)NULL); /* XXX */ add_unwind_protect (lastpipe_cleanup, lastpipe_jid); } - cmd->flags |= CMD_LASTPIPE; + if (cmd) + cmd->flags |= CMD_LASTPIPE; } if (prev >= 0) add_unwind_protect (close, prev); @@ -2850,6 +2864,8 @@ print_index_and_element (len, ind, list) return (0); for (i = ind, l = list; l && --i; l = l->next) ; + if (l == 0) /* don't think this can happen */ + return (0); fprintf (stderr, "%*d%s%s", len, ind, RP_SPACE, l->word->word); return (displen (l->word->word)); } @@ -2990,7 +3006,7 @@ select_query (list, list_len, prompt, print_menu) for (l = list; l && --reply; l = l->next) ; - return (l->word->word); + return (l->word->word); /* XXX - can't be null? */ } } @@ -3431,6 +3447,7 @@ execute_arith_command (arith_command) static char * const nullstr = ""; +/* XXX - can COND ever be NULL when this is called? */ static int execute_cond_node (cond) COND_COM *cond; @@ -3745,8 +3762,12 @@ is_dirname (pathname) char *pathname; { char *temp; + int ret; + temp = search_for_command (pathname); - return (temp ? file_isdir (temp) : file_isdir (pathname)); + ret = (temp ? file_isdir (temp) : file_isdir (pathname)); + free (temp); + return ret; } /* The meaty part of all the executions. We have to start hacking the @@ -5092,6 +5113,7 @@ shell_execve (command, args, env) run it for some reason. See why. */ #if defined (HAVE_HASH_BANG_EXEC) READ_SAMPLE_BUF (command, sample, sample_len); + sample[sample_len - 1] = '\0'; if (sample_len > 2 && sample[0] == '#' && sample[1] == '!') { char *interp; diff --git a/execute_cmd.c~ b/execute_cmd.c~ index 253fcf796..7d3bc4236 100644 --- a/execute_cmd.c~ +++ b/execute_cmd.c~ @@ -530,6 +530,10 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out, REDIRECT *my_undo_list, *exec_undo_list; volatile int last_pid; volatile int save_line_number; +#if defined (PROCESS_SUBSTITUTION) + volatile int ofifo, nfifo, osize, saved_fifo; + volatile char *ofifo_list; +#endif #if 0 if (command == 0 || breaking || continuing || read_but_dont_execute) @@ -670,6 +674,17 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out, if (shell_control_structure (command->type) && command->redirects) stdin_redir = stdin_redirects (command->redirects); +#if defined (PROCESS_SUBSTITUTION) + if (variable_context != 0) + { + ofifo = num_fifos (); + ofifo_list = copy_fifo_list (&osize); + saved_fifo = 1; + } + else + saved_fifo = 0; +#endif + /* Handle WHILE FOR CASE etc. with redirections. (Also '&' input redirection.) */ if (do_redirections (command->redirects, RX_ACTIVE|RX_UNDOABLE) != 0) @@ -677,6 +692,9 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out, cleanup_redirects (redirection_undo_list); redirection_undo_list = (REDIRECT *)NULL; dispose_exec_redirects (); +#if defined (PROCESS_SUBSTITUTION) + free (ofifo_list); +#endif return (last_command_exit_value = EXECUTION_FAILURE); } @@ -971,6 +989,16 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out, if (my_undo_list || exec_undo_list) discard_unwind_frame ("loop_redirections"); +#if defined (PROCESS_SUBSTITUTION) + if (saved_fifo) + { + nfifo = num_fifos (); + if (nfifo > ofifo) + close_new_fifos (ofifo_list, osize); + free (ofifo_list); + } +#endif + /* Invert the return value if we have to */ if (invert) exec_result = (exec_result == EXECUTION_SUCCESS) @@ -1001,6 +1029,7 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out, if (running_trap == 0) #endif currently_executing_command = (COMMAND *)NULL; + return (last_command_exit_value); } @@ -1223,7 +1252,7 @@ time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close) # endif #endif - posix_time = (command->flags & CMD_TIME_POSIX); + posix_time = command && (command->flags & CMD_TIME_POSIX); nullcmd = (command == 0) || (command->type == cm_simple && command->value.Simple->words == 0 && command->value.Simple->redirects == 0); if (posixly_correct && nullcmd) @@ -1643,32 +1672,45 @@ cpl_delete (pid) static void cpl_reap () { - struct cpelement *prev, *p; + struct cpelement *p, *next, *nh, *nt; - for (prev = p = coproc_list.head; p; prev = p, p = p->next) - if (p->coproc->c_flags & COPROC_DEAD) - { - prev->next = p->next; /* remove from list */ - - /* Housekeeping in the border cases. */ - if (p == coproc_list.head) - coproc_list.head = coproc_list.head->next; - else if (p == coproc_list.tail) - coproc_list.tail = prev; - - coproc_list.ncoproc--; - if (coproc_list.ncoproc == 0) - coproc_list.head = coproc_list.tail = 0; - else if (coproc_list.ncoproc == 1) - coproc_list.tail = coproc_list.head; /* just to make sure */ + /* Build a new list by removing dead coprocs and fix up the coproc_list + pointers when done. */ + nh = nt = next = (struct cpelement *)0; + for (p = coproc_list.head; p; p = next) + { + next = p->next; + if (p->coproc->c_flags & COPROC_DEAD) + { + coproc_list.ncoproc--; /* keep running count, fix up pointers later */ #if defined (DEBUG) - itrace("cpl_reap: deleting %d", p->coproc->c_pid); + itrace("cpl_reap: deleting %d", p->coproc->c_pid); #endif - coproc_dispose (p->coproc); - cpe_dispose (p); - } + coproc_dispose (p->coproc); + cpe_dispose (p); + } + else if (nh == 0) + nh = nt = p; + else + { + nt->next = p; + nt = nt->next; + } + } + + nt->next = 0; + + if (coproc_list.ncoproc == 0) + coproc_list.head = coproc_list.tail = 0; + else + { + coproc_list.head = nh; + coproc_list.tail = nt; + if (coproc_list.ncoproc == 1) + coproc_list.tail = coproc_list.head; /* just to make sure */ + } } /* Clear out the list of saved statuses */ @@ -2292,7 +2334,8 @@ execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close) lastpipe_jid = stop_pipeline (0, (COMMAND *)NULL); /* XXX */ add_unwind_protect (lastpipe_cleanup, lastpipe_jid); } - cmd->flags |= CMD_LASTPIPE; + if (cmd) + cmd->flags |= CMD_LASTPIPE; } if (prev >= 0) add_unwind_protect (close, prev); @@ -2821,6 +2864,8 @@ print_index_and_element (len, ind, list) return (0); for (i = ind, l = list; l && --i; l = l->next) ; + if (l == 0) /* don't think this can happen */ + return (0); fprintf (stderr, "%*d%s%s", len, ind, RP_SPACE, l->word->word); return (displen (l->word->word)); } @@ -2961,7 +3006,7 @@ select_query (list, list_len, prompt, print_menu) for (l = list; l && --reply; l = l->next) ; - return (l->word->word); + return (l->word->word); /* XXX - can't be null? */ } } @@ -3402,6 +3447,7 @@ execute_arith_command (arith_command) static char * const nullstr = ""; +/* XXX - can COND ever be NULL when this is called? */ static int execute_cond_node (cond) COND_COM *cond; @@ -3716,8 +3762,12 @@ is_dirname (pathname) char *pathname; { char *temp; + int ret; + temp = search_for_command (pathname); - return (temp ? file_isdir (temp) : file_isdir (pathname)); + ret = (temp ? file_isdir (temp) : file_isdir (pathname)); + free (temp); + return ret; } /* The meaty part of all the executions. We have to start hacking the @@ -5063,6 +5113,7 @@ shell_execve (command, args, env) run it for some reason. See why. */ #if defined (HAVE_HASH_BANG_EXEC) READ_SAMPLE_BUF (command, sample, sample_len); + sample[sample_len - 1] = '\0'; if (sample_len > 2 && sample[0] == '#' && sample[1] == '!') { char *interp; diff --git a/expr.c b/expr.c index 0125df490..b6d3b727f 100644 --- a/expr.c +++ b/expr.c @@ -1,6 +1,6 @@ /* expr.c -- arithmetic expression evaluation. */ -/* Copyright (C) 1990-2010 Free Software Foundation, Inc. +/* Copyright (C) 1990-2011 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. diff --git a/expr.c~ b/expr.c~ index a42918f28..0125df490 100644 --- a/expr.c~ +++ b/expr.c~ @@ -494,10 +494,16 @@ expassign () lvalue *= value; break; case DIV: - lvalue /= value; + if (lvalue == INTMAX_MIN && value == -1) + value = 1; + else + lvalue /= value; break; case MOD: - lvalue %= value; + if (lvalue == INTMAX_MIN && value == -1) + lvalue = 0; + else + lvalue %= value; break; case PLUS: lvalue += value; @@ -550,7 +556,6 @@ expcond () { intmax_t cval, val1, val2, rval; int set_noeval; - EXPR_CONTEXT ec; set_noeval = 0; rval = cval = explor (); @@ -812,6 +817,7 @@ exp2 () val2 = exppower (); + /* Handle division by 0 and twos-complement arithmetic overflow */ if (((op == DIV) || (op == MOD)) && (val2 == 0)) { if (noeval == 0) @@ -819,6 +825,13 @@ exp2 () else val2 = 1; } + else if (op == MOD && val1 == INTMAX_MIN && val2 == -1) + { + val1 = 0; + continue; + } + else if (op == DIV && val1 == INTMAX_MIN && val2 == -1) + val2 = 1; if (op == MUL) val1 *= val2; @@ -830,6 +843,23 @@ exp2 () return (val1); } +static intmax_t +ipow (base, exp) + intmax_t base, exp; +{ + intmax_t result; + + result = 1; + while (exp) + { + if (exp & 1) + result *= base; + exp >>= 1; + base *= base; + } + return result; +} + static intmax_t exppower () { @@ -844,9 +874,7 @@ exppower () return (1); if (val2 < 0) evalerror (_("exponent less than 0")); - for (c = 1; val2--; c *= val1) - ; - val1 = c; + val1 = ipow (val1, val2); } return (val1); } diff --git a/findcmd.c b/findcmd.c index 330e39544..1ebc71ec1 100644 --- a/findcmd.c +++ b/findcmd.c @@ -398,7 +398,8 @@ user_command_matches (name, flags, state) name_len = strlen (name); file_to_lose_on = (char *)NULL; dot_found_in_search = 0; - stat (".", &dotinfo); + if (stat (".", &dotinfo) < 0) + dotinfo.st_dev = dotinfo.st_ino = 0; /* so same_file won't match */ path_list = get_string_value ("PATH"); path_index = 0; } @@ -569,7 +570,8 @@ find_user_command_in_path (name, path_list, flags) file_to_lose_on = (char *)NULL; name_len = strlen (name); - stat (".", &dotinfo); + if (stat (".", &dotinfo) < 0) + dotinfo.st_dev = dotinfo.st_ino = 0; path_index = 0; while (path_list[path_index]) diff --git a/findcmd.c~ b/findcmd.c~ new file mode 100644 index 000000000..65e036a7d --- /dev/null +++ b/findcmd.c~ @@ -0,0 +1,618 @@ +/* findcmd.c -- Functions to search for commands by name. */ + +/* Copyright (C) 1997-2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include +#include "chartypes.h" +#include "bashtypes.h" +#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif +#include "filecntl.h" +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif +#include + +#include "bashansi.h" + +#include "memalloc.h" +#include "shell.h" +#include "flags.h" +#include "hashlib.h" +#include "pathexp.h" +#include "hashcmd.h" +#include "findcmd.h" /* matching prototypes and declarations */ + +#if !defined (errno) +extern int errno; +#endif + +extern int posixly_correct; + +/* Static functions defined and used in this file. */ +static char *_find_user_command_internal __P((const char *, int)); +static char *find_user_command_internal __P((const char *, int)); +static char *find_user_command_in_path __P((const char *, char *, int)); +static char *find_in_path_element __P((const char *, char *, int, int, struct stat *)); +static char *find_absolute_program __P((const char *, int)); + +static char *get_next_path_element __P((char *, int *)); + +/* The file name which we would try to execute, except that it isn't + possible to execute it. This is the first file that matches the + name that we are looking for while we are searching $PATH for a + suitable one to execute. If we cannot find a suitable executable + file, then we use this one. */ +static char *file_to_lose_on; + +/* Non-zero if we should stat every command found in the hash table to + make sure it still exists. */ +int check_hashed_filenames; + +/* DOT_FOUND_IN_SEARCH becomes non-zero when find_user_command () + encounters a `.' as the directory pathname while scanning the + list of possible pathnames; i.e., if `.' comes before the directory + containing the file of interest. */ +int dot_found_in_search = 0; + +/* Return some flags based on information about this file. + The EXISTS bit is non-zero if the file is found. + The EXECABLE bit is non-zero the file is executble. + Zero is returned if the file is not found. */ +int +file_status (name) + const char *name; +{ + struct stat finfo; + int r; + + /* Determine whether this file exists or not. */ + if (stat (name, &finfo) < 0) + return (0); + + /* If the file is a directory, then it is not "executable" in the + sense of the shell. */ + if (S_ISDIR (finfo.st_mode)) + return (FS_EXISTS|FS_DIRECTORY); + + r = FS_EXISTS; + +#if defined (HAVE_EACCESS) + /* Use eaccess(2) if we have it to take things like ACLs and other + file access mechanisms into account. eaccess uses the effective + user and group IDs, not the real ones. We could use sh_eaccess, + but we don't want any special treatment for /dev/fd. */ + if (eaccess (name, X_OK) == 0) + r |= FS_EXECABLE; + if (eaccess (name, R_OK) == 0) + r |= FS_READABLE; + + return r; +#elif defined (AFS) + /* We have to use access(2) to determine access because AFS does not + support Unix file system semantics. This may produce wrong + answers for non-AFS files when ruid != euid. I hate AFS. */ + if (access (name, X_OK) == 0) + r |= FS_EXECABLE; + if (access (name, R_OK) == 0) + r |= FS_READABLE; + + return r; +#else /* !HAVE_EACCESS && !AFS */ + + /* Find out if the file is actually executable. By definition, the + only other criteria is that the file has an execute bit set that + we can use. The same with whether or not a file is readable. */ + + /* Root only requires execute permission for any of owner, group or + others to be able to exec a file, and can read any file. */ + if (current_user.euid == (uid_t)0) + { + r |= FS_READABLE; + if (finfo.st_mode & S_IXUGO) + r |= FS_EXECABLE; + return r; + } + + /* If we are the owner of the file, the owner bits apply. */ + if (current_user.euid == finfo.st_uid) + { + if (finfo.st_mode & S_IXUSR) + r |= FS_EXECABLE; + if (finfo.st_mode & S_IRUSR) + r |= FS_READABLE; + } + + /* If we are in the owning group, the group permissions apply. */ + else if (group_member (finfo.st_gid)) + { + if (finfo.st_mode & S_IXGRP) + r |= FS_EXECABLE; + if (finfo.st_mode & S_IRGRP) + r |= FS_READABLE; + } + + /* Else we check whether `others' have permission to execute the file */ + else + { + if (finfo.st_mode & S_IXOTH) + r |= FS_EXECABLE; + if (finfo.st_mode & S_IROTH) + r |= FS_READABLE; + } + + return r; +#endif /* !AFS */ +} + +/* Return non-zero if FILE exists and is executable. + Note that this function is the definition of what an + executable file is; do not change this unless YOU know + what an executable file is. */ +int +executable_file (file) + const char *file; +{ + int s; + + s = file_status (file); +#if defined EISDIR + if (s & FS_DIRECTORY) + errno = EISDIR; /* let's see if we can improve error messages */ +#endif + return ((s & FS_EXECABLE) && ((s & FS_DIRECTORY) == 0)); +} + +int +is_directory (file) + const char *file; +{ + return (file_status (file) & FS_DIRECTORY); +} + +int +executable_or_directory (file) + const char *file; +{ + int s; + + s = file_status (file); + return ((s & FS_EXECABLE) || (s & FS_DIRECTORY)); +} + +/* Locate the executable file referenced by NAME, searching along + the contents of the shell PATH variable. Return a new string + which is the full pathname to the file, or NULL if the file + couldn't be found. If a file is found that isn't executable, + and that is the only match, then return that. */ +char * +find_user_command (name) + const char *name; +{ + return (find_user_command_internal (name, FS_EXEC_PREFERRED|FS_NODIRS)); +} + +/* Locate the file referenced by NAME, searching along the contents + of the shell PATH variable. Return a new string which is the full + pathname to the file, or NULL if the file couldn't be found. This + returns the first readable file found; designed to be used to look + for shell scripts or files to source. */ +char * +find_path_file (name) + const char *name; +{ + return (find_user_command_internal (name, FS_READABLE)); +} + +static char * +_find_user_command_internal (name, flags) + const char *name; + int flags; +{ + char *path_list, *cmd; + SHELL_VAR *var; + + /* Search for the value of PATH in both the temporary environments and + in the regular list of variables. */ + if (var = find_variable_internal ("PATH", 1)) /* XXX could be array? */ + path_list = value_cell (var); + else + path_list = (char *)NULL; + + if (path_list == 0 || *path_list == '\0') + return (savestring (name)); + + cmd = find_user_command_in_path (name, path_list, flags); + + return (cmd); +} + +static char * +find_user_command_internal (name, flags) + const char *name; + int flags; +{ +#ifdef __WIN32__ + char *res, *dotexe; + + dotexe = (char *)xmalloc (strlen (name) + 5); + strcpy (dotexe, name); + strcat (dotexe, ".exe"); + res = _find_user_command_internal (dotexe, flags); + free (dotexe); + if (res == 0) + res = _find_user_command_internal (name, flags); + return res; +#else + return (_find_user_command_internal (name, flags)); +#endif +} + +/* Return the next element from PATH_LIST, a colon separated list of + paths. PATH_INDEX_POINTER is the address of an index into PATH_LIST; + the index is modified by this function. + Return the next element of PATH_LIST or NULL if there are no more. */ +static char * +get_next_path_element (path_list, path_index_pointer) + char *path_list; + int *path_index_pointer; +{ + char *path; + + path = extract_colon_unit (path_list, path_index_pointer); + + if (path == 0) + return (path); + + if (*path == '\0') + { + free (path); + path = savestring ("."); + } + + return (path); +} + +/* Look for PATHNAME in $PATH. Returns either the hashed command + corresponding to PATHNAME or the first instance of PATHNAME found + in $PATH. Returns a newly-allocated string. */ +char * +search_for_command (pathname) + const char *pathname; +{ + char *hashed_file, *command; + int temp_path, st; + SHELL_VAR *path; + + hashed_file = command = (char *)NULL; + + /* If PATH is in the temporary environment for this command, don't use the + hash table to search for the full pathname. */ + path = find_variable_internal ("PATH", 1); + temp_path = path && tempvar_p (path); + if (temp_path == 0 && path) + path = (SHELL_VAR *)NULL; + + /* 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 = phash_search (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)) != (FS_EXISTS|FS_EXECABLE)) + { + phash_remove (pathname); + free (hashed_file); + hashed_file = (char *)NULL; + } + } + + if (hashed_file) + command = hashed_file; + else if (absolute_program (pathname)) + /* A command containing a slash is not looked up in PATH or saved in + the hash table. */ + command = savestring (pathname); + else + { + /* If $PATH is in the temporary environment, we've already retrieved + it, so don't bother trying again. */ + if (temp_path) + { + command = find_user_command_in_path (pathname, value_cell (path), + FS_EXEC_PREFERRED|FS_NODIRS); + } + else + command = find_user_command (pathname); + if (command && hashing_enabled && temp_path == 0) + phash_insert ((char *)pathname, command, dot_found_in_search, 1); /* XXX fix const later */ + } + return (command); +} + +char * +user_command_matches (name, flags, state) + const char *name; + int flags, state; +{ + register int i; + int path_index, name_len; + char *path_list, *path_element, *match; + struct stat dotinfo; + static char **match_list = NULL; + static int match_list_size = 0; + static int match_index = 0; + + if (state == 0) + { + /* Create the list of matches. */ + if (match_list == 0) + { + match_list_size = 5; + match_list = strvec_create (match_list_size); + } + + /* Clear out the old match list. */ + for (i = 0; i < match_list_size; i++) + match_list[i] = 0; + + /* We haven't found any files yet. */ + match_index = 0; + + if (absolute_program (name)) + { + match_list[0] = find_absolute_program (name, flags); + match_list[1] = (char *)NULL; + path_list = (char *)NULL; + } + else + { + name_len = strlen (name); + file_to_lose_on = (char *)NULL; + dot_found_in_search = 0; + if (stat (".", &dotinfo) < 0) + dotinfo.st_dev = dotinfo.st_ino = 0; /* so same_file won't match */ + path_list = get_string_value ("PATH"); + path_index = 0; + } + + while (path_list && path_list[path_index]) + { + path_element = get_next_path_element (path_list, &path_index); + + if (path_element == 0) + break; + + match = find_in_path_element (name, path_element, flags, name_len, &dotinfo); + + free (path_element); + + if (match == 0) + continue; + + if (match_index + 1 == match_list_size) + { + match_list_size += 10; + match_list = strvec_resize (match_list, (match_list_size + 1)); + } + + match_list[match_index++] = match; + match_list[match_index] = (char *)NULL; + FREE (file_to_lose_on); + file_to_lose_on = (char *)NULL; + } + + /* We haven't returned any strings yet. */ + match_index = 0; + } + + match = match_list[match_index]; + + if (match) + match_index++; + + return (match); +} + +static char * +find_absolute_program (name, flags) + const char *name; + int flags; +{ + int st; + + st = file_status (name); + + /* If the file doesn't exist, quit now. */ + if ((st & FS_EXISTS) == 0) + return ((char *)NULL); + + /* If we only care about whether the file exists or not, return + this filename. Otherwise, maybe we care about whether this + file is executable. If it is, and that is what we want, return it. */ + if ((flags & FS_EXISTS) || ((flags & FS_EXEC_ONLY) && (st & FS_EXECABLE))) + return (savestring (name)); + + return (NULL); +} + +static char * +find_in_path_element (name, path, flags, name_len, dotinfop) + const char *name; + char *path; + int flags, name_len; + struct stat *dotinfop; +{ + int status; + char *full_path, *xpath; + + xpath = (*path == '~') ? bash_tilde_expand (path, 0) : path; + + /* Remember the location of "." in the path, in all its forms + (as long as they begin with a `.', e.g. `./.') */ + if (dot_found_in_search == 0 && *xpath == '.') + dot_found_in_search = same_file (".", xpath, dotinfop, (struct stat *)NULL); + + full_path = sh_makepath (xpath, name, 0); + + status = file_status (full_path); + + if (xpath != path) + free (xpath); + + if ((status & FS_EXISTS) == 0) + { + free (full_path); + return ((char *)NULL); + } + + /* The file exists. If the caller simply wants the first file, here it is. */ + if (flags & FS_EXISTS) + return (full_path); + + /* If we have a readable file, and the caller wants a readable file, this + is it. */ + if ((flags & FS_READABLE) && (status & FS_READABLE)) + 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_EXEC_ONLY|FS_EXEC_PREFERRED)) && + (((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, or we want a readable file and this file + isn't readable, fail. */ + if ((flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) || + ((flags & FS_NODIRS) && (status & FS_DIRECTORY)) || + ((flags & FS_READABLE) && (status & FS_READABLE) == 0)) + { + free (full_path); + return ((char *)NULL); + } + else + return (full_path); +} + +/* This does the dirty work for find_user_command_internal () and + user_command_matches (). + NAME is the name of the file to search for. + PATH_LIST is a colon separated list of directories to search. + FLAGS contains bit fields which control the files which are eligible. + Some values are: + FS_EXEC_ONLY: The file must be an executable to be found. + FS_EXEC_PREFERRED: If we can't find an executable, then the + the first file matching NAME will do. + FS_EXISTS: The first file found will do. + FS_NODIRS: Don't find any directories. +*/ +static char * +find_user_command_in_path (name, path_list, flags) + const char *name; + char *path_list; + int flags; +{ + char *full_path, *path; + int path_index, name_len; + struct stat dotinfo; + + /* We haven't started looking, so we certainly haven't seen + a `.' as the directory path yet. */ + dot_found_in_search = 0; + + if (absolute_program (name)) + { + full_path = find_absolute_program (name, flags); + return (full_path); + } + + if (path_list == 0 || *path_list == '\0') + return (savestring (name)); /* XXX */ + + file_to_lose_on = (char *)NULL; + name_len = strlen (name); + stat (".", &dotinfo); + path_index = 0; + + while (path_list[path_index]) + { + /* Allow the user to interrupt out of a lengthy path search. */ + QUIT; + + path = get_next_path_element (path_list, &path_index); + if (path == 0) + break; + + /* Side effects: sets dot_found_in_search, possibly sets + file_to_lose_on. */ + full_path = find_in_path_element (name, path, flags, name_len, &dotinfo); + free (path); + + /* This should really be in find_in_path_element, but there isn't the + right combination of flags. */ + if (full_path && is_directory (full_path)) + { + free (full_path); + continue; + } + + if (full_path) + { + FREE (file_to_lose_on); + return (full_path); + } + } + + /* We didn't find exactly what the user was looking for. Return + the contents of FILE_TO_LOSE_ON which is NULL when the search + required an executable, or non-NULL if a file was found and the + search would accept a non-executable as a last resort. If the + caller specified FS_NODIRS, and file_to_lose_on is a directory, + return NULL. */ + if (file_to_lose_on && (flags & FS_NODIRS) && is_directory (file_to_lose_on)) + { + free (file_to_lose_on); + file_to_lose_on = (char *)NULL; + } + + return (file_to_lose_on); +} diff --git a/general.c b/general.c index 86c8ea1e9..aa5ce7e25 100644 --- a/general.c +++ b/general.c @@ -1,6 +1,6 @@ /* general.c -- Stuff that is used by all files. */ -/* Copyright (C) 1987-2009 Free Software Foundation, Inc. +/* Copyright (C) 1987-2011 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -171,6 +171,9 @@ legal_number (string, result) if (result) *result = 0; + if (string == 0) + return 0; + errno = 0; value = strtoimax (string, &ep, 10); if (errno || ep == string) @@ -182,7 +185,7 @@ legal_number (string, result) /* If *string is not '\0' but *ep is '\0' on return, the entire string is valid. */ - if (string && *string && *ep == '\0') + if (*string && *ep == '\0') { if (result) *result = value; @@ -398,7 +401,8 @@ check_dev_tty () return; tty_fd = open (tty, O_RDWR|O_NONBLOCK); } - close (tty_fd); + if (tty_fd >= 0) + close (tty_fd); } /* Return 1 if PATH1 and PATH2 are the same file. This is kind of diff --git a/general.c~ b/general.c~ new file mode 100644 index 000000000..c71cf2f71 --- /dev/null +++ b/general.c~ @@ -0,0 +1,1163 @@ +/* general.c -- Stuff that is used by all files. */ + +/* Copyright (C) 1987-2011 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#ifndef _MINIX +# include +#endif +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "filecntl.h" +#include "bashansi.h" +#include +#include "chartypes.h" +#include + +#include "bashintl.h" + +#include "shell.h" +#include "test.h" + +#include + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +extern int expand_aliases; +extern int interactive_comments; +extern int check_hashed_filenames; +extern int source_uses_path; +extern int source_searches_cwd; + +static char *bash_special_tilde_expansions __P((char *)); +static int unquoted_tilde_word __P((const char *)); +static void initialize_group_array __P((void)); + +/* A standard error message to use when getcwd() returns NULL. */ +const char * const bash_getcwd_errstr = N_("getcwd: cannot access parent directories"); + +/* Do whatever is necessary to initialize `Posix mode'. */ +void +posix_initialize (on) + int on; +{ + /* Things that should be turned on when posix mode is enabled. */ + if (on != 0) + { + interactive_comments = source_uses_path = expand_aliases = 1; + source_searches_cwd = 0; + } + + /* Things that should be turned on when posix mode is disabled. */ + if (on == 0) + { + source_searches_cwd = 1; + expand_aliases = interactive_shell; + } +} + +/* **************************************************************** */ +/* */ +/* Functions to convert to and from and display non-standard types */ +/* */ +/* **************************************************************** */ + +#if defined (RLIMTYPE) +RLIMTYPE +string_to_rlimtype (s) + char *s; +{ + RLIMTYPE ret; + int neg; + + ret = 0; + neg = 0; + while (s && *s && whitespace (*s)) + s++; + if (s && (*s == '-' || *s == '+')) + { + neg = *s == '-'; + s++; + } + for ( ; s && *s && DIGIT (*s); s++) + ret = (ret * 10) + TODIGIT (*s); + return (neg ? -ret : ret); +} + +void +print_rlimtype (n, addnl) + RLIMTYPE n; + int addnl; +{ + char s[INT_STRLEN_BOUND (RLIMTYPE) + 1], *p; + + p = s + sizeof(s); + *--p = '\0'; + + if (n < 0) + { + do + *--p = '0' - n % 10; + while ((n /= 10) != 0); + + *--p = '-'; + } + else + { + do + *--p = '0' + n % 10; + while ((n /= 10) != 0); + } + + printf ("%s%s", p, addnl ? "\n" : ""); +} +#endif /* RLIMTYPE */ + +/* **************************************************************** */ +/* */ +/* Input Validation Functions */ +/* */ +/* **************************************************************** */ + +/* Return non-zero if all of the characters in STRING are digits. */ +int +all_digits (string) + char *string; +{ + register char *s; + + for (s = string; *s; s++) + if (DIGIT (*s) == 0) + return (0); + + return (1); +} + +/* Return non-zero if the characters pointed to by STRING constitute a + valid number. Stuff the converted number into RESULT if RESULT is + not null. */ +int +legal_number (string, result) + const char *string; + intmax_t *result; +{ + intmax_t value; + char *ep; + + if (result) + *result = 0; + + errno = 0; + value = strtoimax (string, &ep, 10); + if (errno || ep == string) + return 0; /* errno is set on overflow or underflow */ + + /* Skip any trailing whitespace, since strtoimax does not. */ + while (whitespace (*ep)) + ep++; + + /* If *string is not '\0' but *ep is '\0' on return, the entire string + is valid. */ + if (string && *string && *ep == '\0') + { + if (result) + *result = value; + /* The SunOS4 implementation of strtol() will happily ignore + overflow conditions, so this cannot do overflow correctly + on those systems. */ + return 1; + } + + return (0); +} + +/* Return 1 if this token is a legal shell `identifier'; that is, it consists + solely of letters, digits, and underscores, and does not begin with a + digit. */ +int +legal_identifier (name) + char *name; +{ + register char *s; + unsigned char c; + + if (!name || !(c = *name) || (legal_variable_starter (c) == 0)) + return (0); + + for (s = name + 1; (c = *s) != 0; s++) + { + if (legal_variable_char (c) == 0) + return (0); + } + return (1); +} + +/* Make sure that WORD is a valid shell identifier, i.e. + does not contain a dollar sign, nor is quoted in any way. Nor + does it consist of all digits. If CHECK_WORD is non-zero, + the word is checked to ensure that it consists of only letters, + digits, and underscores. */ +int +check_identifier (word, check_word) + WORD_DESC *word; + int check_word; +{ + if ((word->flags & (W_HASDOLLAR|W_QUOTED)) || all_digits (word->word)) + { + internal_error (_("`%s': not a valid identifier"), word->word); + return (0); + } + else if (check_word && legal_identifier (word->word) == 0) + { + internal_error (_("`%s': not a valid identifier"), word->word); + return (0); + } + else + return (1); +} + +/* Return 1 if STRING comprises a valid alias name. The shell accepts + essentially all characters except those which must be quoted to the + parser (which disqualifies them from alias expansion anyway) and `/'. */ +int +legal_alias_name (string, flags) + char *string; + int flags; +{ + register char *s; + + for (s = string; *s; s++) + if (shellbreak (*s) || shellxquote (*s) || shellexp (*s) || (*s == '/')) + return 0; + return 1; +} + +/* Returns non-zero if STRING is an assignment statement. The returned value + is the index of the `=' sign. */ +int +assignment (string, flags) + const char *string; + int flags; +{ + register unsigned char c; + register int newi, indx; + + c = string[indx = 0]; + +#if defined (ARRAY_VARS) + if ((legal_variable_starter (c) == 0) && (flags == 0 || c != '[')) /* ] */ +#else + if (legal_variable_starter (c) == 0) +#endif + return (0); + + while (c = string[indx]) + { + /* The following is safe. Note that '=' at the start of a word + is not an assignment statement. */ + if (c == '=') + return (indx); + +#if defined (ARRAY_VARS) + if (c == '[') + { + newi = skipsubscript (string, indx, 0); + if (string[newi++] != ']') + return (0); + if (string[newi] == '+' && string[newi+1] == '=') + return (newi + 1); + return ((string[newi] == '=') ? newi : 0); + } +#endif /* ARRAY_VARS */ + + /* Check for `+=' */ + if (c == '+' && string[indx+1] == '=') + return (indx + 1); + + /* Variable names in assignment statements may contain only letters, + digits, and `_'. */ + if (legal_variable_char (c) == 0) + return (0); + + indx++; + } + return (0); +} + +/* **************************************************************** */ +/* */ +/* Functions to manage files and file descriptors */ +/* */ +/* **************************************************************** */ + +/* A function to unset no-delay mode on a file descriptor. Used in shell.c + to unset it on the fd passed as stdin. Should be called on stdin if + readline gets an EAGAIN or EWOULDBLOCK when trying to read input. */ + +#if !defined (O_NDELAY) +# if defined (FNDELAY) +# define O_NDELAY FNDELAY +# endif +#endif /* O_NDELAY */ + +/* Make sure no-delay mode is not set on file descriptor FD. */ +int +sh_unset_nodelay_mode (fd) + int fd; +{ + int flags, bflags; + + if ((flags = fcntl (fd, F_GETFL, 0)) < 0) + return -1; + + bflags = 0; + + /* This is defined to O_NDELAY in filecntl.h if O_NONBLOCK is not present + and O_NDELAY is defined. */ +#ifdef O_NONBLOCK + bflags |= O_NONBLOCK; +#endif + +#ifdef O_NDELAY + bflags |= O_NDELAY; +#endif + + if (flags & bflags) + { + flags &= ~bflags; + return (fcntl (fd, F_SETFL, flags)); + } + + return 0; +} + +/* Return 1 if file descriptor FD is valid; 0 otherwise. */ +int +sh_validfd (fd) + int fd; +{ + return (fcntl (fd, F_GETFD, 0) >= 0); +} + +int +fd_ispipe (fd) + int fd; +{ + errno = 0; + if (lseek ((fd), 0L, SEEK_CUR) < 0) + return (errno == ESPIPE); + return 0; +} + +/* There is a bug in the NeXT 2.1 rlogind that causes opens + of /dev/tty to fail. */ + +#if defined (__BEOS__) +/* On BeOS, opening in non-blocking mode exposes a bug in BeOS, so turn it + into a no-op. This should probably go away in the future. */ +# undef O_NONBLOCK +# define O_NONBLOCK 0 +#endif /* __BEOS__ */ + +void +check_dev_tty () +{ + int tty_fd; + char *tty; + + tty_fd = open ("/dev/tty", O_RDWR|O_NONBLOCK); + + if (tty_fd < 0) + { + tty = (char *)ttyname (fileno (stdin)); + if (tty == 0) + return; + tty_fd = open (tty, O_RDWR|O_NONBLOCK); + } + if (tty_fd >= 0) + close (tty_fd); +} + +/* Return 1 if PATH1 and PATH2 are the same file. This is kind of + expensive. If non-NULL STP1 and STP2 point to stat structures + corresponding to PATH1 and PATH2, respectively. */ +int +same_file (path1, path2, stp1, stp2) + char *path1, *path2; + struct stat *stp1, *stp2; +{ + struct stat st1, st2; + + if (stp1 == NULL) + { + if (stat (path1, &st1) != 0) + return (0); + stp1 = &st1; + } + + if (stp2 == NULL) + { + if (stat (path2, &st2) != 0) + return (0); + stp2 = &st2; + } + + return ((stp1->st_dev == stp2->st_dev) && (stp1->st_ino == stp2->st_ino)); +} + +/* Move FD to a number close to the maximum number of file descriptors + allowed in the shell process, to avoid the user stepping on it with + redirection and causing us extra work. If CHECK_NEW is non-zero, + we check whether or not the file descriptors are in use before + duplicating FD onto them. MAXFD says where to start checking the + file descriptors. If it's less than 20, we get the maximum value + available from getdtablesize(2). */ +int +move_to_high_fd (fd, check_new, maxfd) + int fd, check_new, maxfd; +{ + int script_fd, nfds, ignore; + + if (maxfd < 20) + { + nfds = getdtablesize (); + if (nfds <= 0) + nfds = 20; + if (nfds > HIGH_FD_MAX) + nfds = HIGH_FD_MAX; /* reasonable maximum */ + } + else + nfds = maxfd; + + for (nfds--; check_new && nfds > 3; nfds--) + if (fcntl (nfds, F_GETFD, &ignore) == -1) + break; + + if (nfds > 3 && fd != nfds && (script_fd = dup2 (fd, nfds)) != -1) + { + if (check_new == 0 || fd != fileno (stderr)) /* don't close stderr */ + close (fd); + return (script_fd); + } + + /* OK, we didn't find one less than our artificial maximum; return the + original file descriptor. */ + return (fd); +} + +/* Return non-zero if the characters from SAMPLE are not all valid + characters to be found in the first line of a shell script. We + check up to the first newline, or SAMPLE_LEN, whichever comes first. + All of the characters must be printable or whitespace. */ + +int +check_binary_file (sample, sample_len) + char *sample; + int sample_len; +{ + register int i; + unsigned char c; + + for (i = 0; i < sample_len; i++) + { + c = sample[i]; + if (c == '\n') + return (0); + if (c == '\0') + return (1); + } + + return (0); +} + +/* **************************************************************** */ +/* */ +/* Functions to manipulate pipes */ +/* */ +/* **************************************************************** */ + +int +sh_openpipe (pv) + int *pv; +{ + int r; + + if ((r = pipe (pv)) < 0) + return r; + + pv[0] = move_to_high_fd (pv[0], 1, 64); + pv[1] = move_to_high_fd (pv[1], 1, 64); + + return 0; +} + +int +sh_closepipe (pv) + int *pv; +{ + if (pv[0] >= 0) + close (pv[0]); + + if (pv[1] >= 0) + close (pv[1]); + + pv[0] = pv[1] = -1; + return 0; +} + +/* **************************************************************** */ +/* */ +/* Functions to inspect pathnames */ +/* */ +/* **************************************************************** */ + +int +file_exists (fn) + char *fn; +{ + struct stat sb; + + return (stat (fn, &sb) == 0); +} + +int +file_isdir (fn) + char *fn; +{ + struct stat sb; + + return ((stat (fn, &sb) == 0) && S_ISDIR (sb.st_mode)); +} + +int +file_iswdir (fn) + char *fn; +{ + return (file_isdir (fn) && sh_eaccess (fn, W_OK) == 0); +} + +/* Return 1 if STRING is "." or "..", optionally followed by a directory + separator */ +int +path_dot_or_dotdot (string) + const char *string; +{ + if (string == 0 || *string == '\0' || *string != '.') + return (0); + + /* string[0] == '.' */ + if (PATHSEP(string[1]) || (string[1] == '.' && PATHSEP(string[2]))) + return (1); + + return (0); +} + +/* Return 1 if STRING contains an absolute pathname, else 0. Used by `cd' + to decide whether or not to look up a directory name in $CDPATH. */ +int +absolute_pathname (string) + const char *string; +{ + if (string == 0 || *string == '\0') + return (0); + + if (ABSPATH(string)) + return (1); + + if (string[0] == '.' && PATHSEP(string[1])) /* . and ./ */ + return (1); + + if (string[0] == '.' && string[1] == '.' && PATHSEP(string[2])) /* .. and ../ */ + return (1); + + return (0); +} + +/* Return 1 if STRING is an absolute program name; it is absolute if it + contains any slashes. This is used to decide whether or not to look + up through $PATH. */ +int +absolute_program (string) + const char *string; +{ + return ((char *)mbschr (string, '/') != (char *)NULL); +} + +/* **************************************************************** */ +/* */ +/* Functions to manipulate pathnames */ +/* */ +/* **************************************************************** */ + +/* Turn STRING (a pathname) into an absolute pathname, assuming that + DOT_PATH contains the symbolic location of `.'. This always + returns a new string, even if STRING was an absolute pathname to + begin with. */ +char * +make_absolute (string, dot_path) + char *string, *dot_path; +{ + char *result; + + if (dot_path == 0 || ABSPATH(string)) +#ifdef __CYGWIN__ + { + char pathbuf[PATH_MAX + 1]; + + cygwin_conv_to_full_posix_path (string, pathbuf); + result = savestring (pathbuf); + } +#else + result = savestring (string); +#endif + else + result = sh_makepath (dot_path, string, 0); + + return (result); +} + +/* Return the `basename' of the pathname in STRING (the stuff after the + last '/'). If STRING is `/', just return it. */ +char * +base_pathname (string) + char *string; +{ + char *p; + +#if 0 + if (absolute_pathname (string) == 0) + return (string); +#endif + + if (string[0] == '/' && string[1] == 0) + return (string); + + p = (char *)strrchr (string, '/'); + return (p ? ++p : string); +} + +/* Return the full pathname of FILE. Easy. Filenames that begin + with a '/' are returned as themselves. Other filenames have + the current working directory prepended. A new string is + returned in either case. */ +char * +full_pathname (file) + char *file; +{ + char *ret; + + file = (*file == '~') ? bash_tilde_expand (file, 0) : savestring (file); + + if (ABSPATH(file)) + return (file); + + ret = sh_makepath ((char *)NULL, file, (MP_DOCWD|MP_RMDOT)); + free (file); + + return (ret); +} + +/* A slightly related function. Get the prettiest name of this + directory possible. */ +static char tdir[PATH_MAX]; + +/* Return a pretty pathname. If the first part of the pathname is + the same as $HOME, then replace that with `~'. */ +char * +polite_directory_format (name) + char *name; +{ + char *home; + int l; + + home = get_string_value ("HOME"); + l = home ? strlen (home) : 0; + if (l > 1 && strncmp (home, name, l) == 0 && (!name[l] || name[l] == '/')) + { + strncpy (tdir + 1, name + l, sizeof(tdir) - 2); + tdir[0] = '~'; + tdir[sizeof(tdir) - 1] = '\0'; + return (tdir); + } + else + return (name); +} + +/* Trim NAME. If NAME begins with `~/', skip over tilde prefix. Trim to + keep any tilde prefix and PROMPT_DIRTRIM trailing directory components + and replace the intervening characters with `...' */ +char * +trim_pathname (name, maxlen) + char *name; + int maxlen; +{ + int nlen, ndirs; + intmax_t nskip; + char *nbeg, *nend, *ntail, *v; + + if (name == 0 || (nlen = strlen (name)) == 0) + return name; + nend = name + nlen; + + v = get_string_value ("PROMPT_DIRTRIM"); + if (v == 0 || *v == 0) + return name; + if (legal_number (v, &nskip) == 0 || nskip <= 0) + return name; + + /* Skip over tilde prefix */ + nbeg = name; + if (name[0] == '~') + for (nbeg = name; *nbeg; nbeg++) + if (*nbeg == '/') + { + nbeg++; + break; + } + if (*nbeg == 0) + return name; + + for (ndirs = 0, ntail = nbeg; *ntail; ntail++) + if (*ntail == '/') + ndirs++; + if (ndirs < nskip) + return name; + + for (ntail = (*nend == '/') ? nend : nend - 1; ntail > nbeg; ntail--) + { + if (*ntail == '/') + nskip--; + if (nskip == 0) + break; + } + if (ntail == nbeg) + return name; + + /* Now we want to return name[0..nbeg]+"..."+ntail, modifying name in place */ + nlen = ntail - nbeg; + if (nlen <= 3) + return name; + + *nbeg++ = '.'; + *nbeg++ = '.'; + *nbeg++ = '.'; + + nlen = nend - ntail; + memcpy (nbeg, ntail, nlen); + nbeg[nlen] = '\0'; + + return name; +} + +/* Given a string containing units of information separated by colons, + return the next one pointed to by (P_INDEX), or NULL if there are no more. + Advance (P_INDEX) to the character after the colon. */ +char * +extract_colon_unit (string, p_index) + char *string; + int *p_index; +{ + int i, start, len; + char *value; + + if (string == 0) + return (string); + + len = strlen (string); + if (*p_index >= len) + return ((char *)NULL); + + i = *p_index; + + /* Each call to this routine leaves the index pointing at a colon if + there is more to the path. If I is > 0, then increment past the + `:'. If I is 0, then the path has a leading colon. Trailing colons + are handled OK by the `else' part of the if statement; an empty + string is returned in that case. */ + if (i && string[i] == ':') + i++; + + for (start = i; string[i] && string[i] != ':'; i++) + ; + + *p_index = i; + + if (i == start) + { + if (string[i]) + (*p_index)++; + /* Return "" in the case of a trailing `:'. */ + value = (char *)xmalloc (1); + value[0] = '\0'; + } + else + value = substring (string, start, i); + + return (value); +} + +/* **************************************************************** */ +/* */ +/* Tilde Initialization and Expansion */ +/* */ +/* **************************************************************** */ + +#if defined (PUSHD_AND_POPD) +extern char *get_dirstack_from_string __P((char *)); +#endif + +static char **bash_tilde_prefixes; +static char **bash_tilde_prefixes2; +static char **bash_tilde_suffixes; +static char **bash_tilde_suffixes2; + +/* If tilde_expand hasn't been able to expand the text, perhaps it + is a special shell expansion. This function is installed as the + tilde_expansion_preexpansion_hook. It knows how to expand ~- and ~+. + If PUSHD_AND_POPD is defined, ~[+-]N expands to directories from the + directory stack. */ +static char * +bash_special_tilde_expansions (text) + char *text; +{ + char *result; + + result = (char *)NULL; + + if (text[0] == '+' && text[1] == '\0') + result = get_string_value ("PWD"); + else if (text[0] == '-' && text[1] == '\0') + result = get_string_value ("OLDPWD"); +#if defined (PUSHD_AND_POPD) + else if (DIGIT (*text) || ((*text == '+' || *text == '-') && DIGIT (text[1]))) + result = get_dirstack_from_string (text); +#endif + + return (result ? savestring (result) : (char *)NULL); +} + +/* Initialize the tilde expander. In Bash, we handle `~-' and `~+', as + well as handling special tilde prefixes; `:~" and `=~' are indications + that we should do tilde expansion. */ +void +tilde_initialize () +{ + static int times_called = 0; + + /* Tell the tilde expander that we want a crack first. */ + tilde_expansion_preexpansion_hook = bash_special_tilde_expansions; + + /* Tell the tilde expander about special strings which start a tilde + expansion, and the special strings that end one. Only do this once. + tilde_initialize () is called from within bashline_reinitialize (). */ + if (times_called++ == 0) + { + bash_tilde_prefixes = strvec_create (3); + bash_tilde_prefixes[0] = "=~"; + bash_tilde_prefixes[1] = ":~"; + bash_tilde_prefixes[2] = (char *)NULL; + + bash_tilde_prefixes2 = strvec_create (2); + bash_tilde_prefixes2[0] = ":~"; + bash_tilde_prefixes2[1] = (char *)NULL; + + tilde_additional_prefixes = bash_tilde_prefixes; + + bash_tilde_suffixes = strvec_create (3); + bash_tilde_suffixes[0] = ":"; + bash_tilde_suffixes[1] = "=~"; /* XXX - ?? */ + bash_tilde_suffixes[2] = (char *)NULL; + + tilde_additional_suffixes = bash_tilde_suffixes; + + bash_tilde_suffixes2 = strvec_create (2); + bash_tilde_suffixes2[0] = ":"; + bash_tilde_suffixes2[1] = (char *)NULL; + } +} + +/* POSIX.2, 3.6.1: A tilde-prefix consists of an unquoted tilde character + at the beginning of the word, followed by all of the characters preceding + the first unquoted slash in the word, or all the characters in the word + if there is no slash...If none of the characters in the tilde-prefix are + quoted, the characters in the tilde-prefix following the tilde shell be + treated as a possible login name. */ + +#define TILDE_END(c) ((c) == '\0' || (c) == '/' || (c) == ':') + +static int +unquoted_tilde_word (s) + const char *s; +{ + const char *r; + + for (r = s; TILDE_END(*r) == 0; r++) + { + switch (*r) + { + case '\\': + case '\'': + case '"': + return 0; + } + } + return 1; +} + +/* Find the end of the tilde-prefix starting at S, and return the tilde + prefix in newly-allocated memory. Return the length of the string in + *LENP. FLAGS tells whether or not we're in an assignment context -- + if so, `:' delimits the end of the tilde prefix as well. */ +char * +bash_tilde_find_word (s, flags, lenp) + const char *s; + int flags, *lenp; +{ + const char *r; + char *ret; + int l; + + for (r = s; *r && *r != '/'; r++) + { + /* Short-circuit immediately if we see a quote character. Even though + POSIX says that `the first unquoted slash' (or `:') terminates the + tilde-prefix, in practice, any quoted portion of the tilde prefix + will cause it to not be expanded. */ + if (*r == '\\' || *r == '\'' || *r == '"') + { + ret = savestring (s); + if (lenp) + *lenp = 0; + return ret; + } + else if (flags && *r == ':') + break; + } + l = r - s; + ret = xmalloc (l + 1); + strncpy (ret, s, l); + ret[l] = '\0'; + if (lenp) + *lenp = l; + return ret; +} + +/* Tilde-expand S by running it through the tilde expansion library. + ASSIGN_P is 1 if this is a variable assignment, so the alternate + tilde prefixes should be enabled (`=~' and `:~', see above). If + ASSIGN_P is 2, we are expanding the rhs of an assignment statement, + so `=~' is not valid. */ +char * +bash_tilde_expand (s, assign_p) + const char *s; + int assign_p; +{ + int old_immed, old_term, r; + char *ret; + + old_immed = interrupt_immediately; + old_term = terminate_immediately; + interrupt_immediately = terminate_immediately = 1; + + tilde_additional_prefixes = assign_p == 0 ? (char **)0 + : (assign_p == 2 ? bash_tilde_prefixes2 : bash_tilde_prefixes); + if (assign_p == 2) + tilde_additional_suffixes = bash_tilde_suffixes2; + + r = (*s == '~') ? unquoted_tilde_word (s) : 1; + ret = r ? tilde_expand (s) : savestring (s); + interrupt_immediately = old_immed; + terminate_immediately = old_term; + return (ret); +} + +/* **************************************************************** */ +/* */ +/* Functions to manipulate and search the group list */ +/* */ +/* **************************************************************** */ + +static int ngroups, maxgroups; + +/* The set of groups that this user is a member of. */ +static GETGROUPS_T *group_array = (GETGROUPS_T *)NULL; + +#if !defined (NOGROUP) +# define NOGROUP (gid_t) -1 +#endif + +static void +initialize_group_array () +{ + register int i; + + if (maxgroups == 0) + maxgroups = getmaxgroups (); + + ngroups = 0; + group_array = (GETGROUPS_T *)xrealloc (group_array, maxgroups * sizeof (GETGROUPS_T)); + +#if defined (HAVE_GETGROUPS) + ngroups = getgroups (maxgroups, group_array); +#endif + + /* If getgroups returns nothing, or the OS does not support getgroups(), + make sure the groups array includes at least the current gid. */ + if (ngroups == 0) + { + group_array[0] = current_user.gid; + ngroups = 1; + } + + /* If the primary group is not in the groups array, add it as group_array[0] + and shuffle everything else up 1, if there's room. */ + for (i = 0; i < ngroups; i++) + if (current_user.gid == (gid_t)group_array[i]) + break; + if (i == ngroups && ngroups < maxgroups) + { + for (i = ngroups; i > 0; i--) + group_array[i] = group_array[i - 1]; + group_array[0] = current_user.gid; + ngroups++; + } + + /* If the primary group is not group_array[0], swap group_array[0] and + whatever the current group is. The vast majority of systems should + not need this; a notable exception is Linux. */ + if (group_array[0] != current_user.gid) + { + for (i = 0; i < ngroups; i++) + if (group_array[i] == current_user.gid) + break; + if (i < ngroups) + { + group_array[i] = group_array[0]; + group_array[0] = current_user.gid; + } + } +} + +/* Return non-zero if GID is one that we have in our groups list. */ +int +#if defined (__STDC__) || defined ( _MINIX) +group_member (gid_t gid) +#else +group_member (gid) + gid_t gid; +#endif /* !__STDC__ && !_MINIX */ +{ +#if defined (HAVE_GETGROUPS) + register int i; +#endif + + /* Short-circuit if possible, maybe saving a call to getgroups(). */ + if (gid == current_user.gid || gid == current_user.egid) + return (1); + +#if defined (HAVE_GETGROUPS) + if (ngroups == 0) + initialize_group_array (); + + /* In case of error, the user loses. */ + if (ngroups <= 0) + return (0); + + /* Search through the list looking for GID. */ + for (i = 0; i < ngroups; i++) + if (gid == (gid_t)group_array[i]) + return (1); +#endif + + return (0); +} + +char ** +get_group_list (ngp) + int *ngp; +{ + static char **group_vector = (char **)NULL; + register int i; + + if (group_vector) + { + if (ngp) + *ngp = ngroups; + return group_vector; + } + + if (ngroups == 0) + initialize_group_array (); + + if (ngroups <= 0) + { + if (ngp) + *ngp = 0; + return (char **)NULL; + } + + group_vector = strvec_create (ngroups); + for (i = 0; i < ngroups; i++) + group_vector[i] = itos (group_array[i]); + + if (ngp) + *ngp = ngroups; + return group_vector; +} + +int * +get_group_array (ngp) + int *ngp; +{ + int i; + static int *group_iarray = (int *)NULL; + + if (group_iarray) + { + if (ngp) + *ngp = ngroups; + return (group_iarray); + } + + if (ngroups == 0) + initialize_group_array (); + + if (ngroups <= 0) + { + if (ngp) + *ngp = 0; + return (int *)NULL; + } + + group_iarray = (int *)xmalloc (ngroups * sizeof (int)); + for (i = 0; i < ngroups; i++) + group_iarray[i] = (int)group_array[i]; + + if (ngp) + *ngp = ngroups; + return group_iarray; +} diff --git a/jobs.c b/jobs.c index 2f21b1715..aef4164cf 100644 --- a/jobs.c +++ b/jobs.c @@ -3669,7 +3669,8 @@ initialize_job_control (force) if (shell_tty == -1) shell_tty = dup (fileno (stderr)); /* fd 2 */ - shell_tty = move_to_high_fd (shell_tty, 1, -1); + if (shell_tty != -1) + shell_tty = move_to_high_fd (shell_tty, 1, -1); /* Compensate for a bug in systems that compiled the BSD rlogind with DEBUG defined, like NeXT and Alliant. */ diff --git a/jobs.c~ b/jobs.c~ index 17615c712..2f21b1715 100644 --- a/jobs.c~ +++ b/jobs.c~ @@ -45,7 +45,9 @@ #include "filecntl.h" #include +#ifdef HAVE_SYS_PARAM_H #include +#endif #if defined (BUFFERED_INPUT) # include "input.h" diff --git a/lib/glob/glob.c b/lib/glob/glob.c index 2cea8a624..bfd46ce2c 100644 --- a/lib/glob/glob.c +++ b/lib/glob/glob.c @@ -404,7 +404,6 @@ finddirs (pat, sdir, flags, ep, np) return ret; } - /* Return a vector of names of files in directory DIR whose names match glob pattern PAT. @@ -475,7 +474,7 @@ glob_vector (pat, dir, flags) skip = 1; } - patlen = strlen (pat); + patlen = (pat && *pat) ? strlen (pat) : 0; /* If the filename pattern (PAT) does not contain any globbing characters, we can dispense with reading the directory, and just see if there is @@ -493,7 +492,11 @@ glob_vector (pat, dir, flags) nextname = (char *)malloc (dirlen + patlen + 2); npat = (char *)malloc (patlen + 1); if (nextname == 0 || npat == 0) - lose = 1; + { + FREE (nextname); + FREE (npat); + lose = 1; + } else { strcpy (npat, pat); @@ -515,7 +518,10 @@ glob_vector (pat, dir, flags) count = 1; } else - lose = 1; + { + free (npat); + lose = 1; + } } else { @@ -632,6 +638,8 @@ glob_vector (pat, dir, flags) nextname = (char *) malloc (sdlen + 1); if (nextlink == 0 || nextname == 0) { + FREE (nextlink); + FREE (nextname); free (subdir); lose = 1; break; @@ -644,6 +652,8 @@ glob_vector (pat, dir, flags) ++count; continue; } + else if (flags & GX_MATCHDIRS) + free (subdir); convfn = fnx_fromfs (dp->d_name, D_NAMLEN (dp)); if (strmatch (pat, convfn, mflags) != FNM_NOMATCH) @@ -663,6 +673,8 @@ glob_vector (pat, dir, flags) nextname = (char *) malloc (D_NAMLEN (dp) + 1); if (nextlink == 0 || nextname == 0) { + FREE (nextlink); + FREE (nextname); lose = 1; break; } @@ -686,7 +698,11 @@ glob_vector (pat, dir, flags) nextname = (char *)malloc (sdlen + 1); nextlink = (struct globval *) malloc (sizeof (struct globval)); if (nextlink == 0 || nextname == 0) - lose = 1; + { + FREE (nextlink); + FREE (nextname); + lose = 1; + } else { nextlink->name = nextname; @@ -812,7 +828,13 @@ glob_dir_to_array (dir, array, flags) result[i] = (char *) malloc (l + strlen (array[i]) + 3); if (result[i] == NULL) - return (NULL); + { + int ind; + for (ind = 0; ind < i; ind++) + free (result[ind]); + free (result); + return (NULL); + } strcpy (result[i], dir); if (add_slash) @@ -983,6 +1005,8 @@ glob_filename (pathname, flags) /* Note that the elements of ARRAY are not freed. */ if (array != temp_results) free ((char *) array); + else if ((dflags & GX_ALLDIRS) && filename[0] == '*' && filename[1] == '*' && filename[2] == '\0') + free (temp_results); /* expanding ** case above */ } } /* Free the directories. */ diff --git a/lib/glob/glob.c~ b/lib/glob/glob.c~ new file mode 100644 index 000000000..cb7d242e6 --- /dev/null +++ b/lib/glob/glob.c~ @@ -0,0 +1,1129 @@ +/* glob.c -- file-name wildcard pattern matching for Bash. + + Copyright (C) 1985-2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne-Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* To whomever it may concern: I have never seen the code which most + Unix programs use to perform this function. I wrote this from scratch + based on specifications for the pattern matching. --RMS. */ + +#include + +#if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX) + #pragma alloca +#endif /* _AIX && RISC6000 && !__GNUC__ */ + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "posixdir.h" +#include "posixstat.h" +#include "shmbutil.h" +#include "xmalloc.h" + +#include "filecntl.h" +#if !defined (F_OK) +# define F_OK 0 +#endif + +#include "stdc.h" +#include "memalloc.h" + +#include "shell.h" + +#include "glob.h" +#include "strmatch.h" + +#if !defined (HAVE_BCOPY) && !defined (bcopy) +# define bcopy(s, d, n) ((void) memcpy ((d), (s), (n))) +#endif /* !HAVE_BCOPY && !bcopy */ + +#if !defined (NULL) +# if defined (__STDC__) +# define NULL ((void *) 0) +# else +# define NULL 0x0 +# endif /* __STDC__ */ +#endif /* !NULL */ + +#if !defined (FREE) +# define FREE(x) if (x) free (x) +#endif + +/* Don't try to alloca() more than this much memory for `struct globval' + in glob_vector() */ +#ifndef ALLOCA_MAX +# define ALLOCA_MAX 100000 +#endif + +struct globval + { + struct globval *next; + char *name; + }; + +extern void throw_to_top_level __P((void)); +extern int sh_eaccess __P((char *, int)); +extern char *sh_makepath __P((const char *, const char *, int)); + +extern int extended_glob; + +/* Global variable which controls whether or not * matches .*. + Non-zero means don't match .*. */ +int noglob_dot_filenames = 1; + +/* Global variable which controls whether or not filename globbing + is done without regard to case. */ +int glob_ignore_case = 0; + +/* Global variable to return to signify an error in globbing. */ +char *glob_error_return; + +static struct globval finddirs_error_return; + +/* Some forward declarations. */ +static int skipname __P((char *, char *, int)); +#if HANDLE_MULTIBYTE +static int mbskipname __P((char *, char *, int)); +#endif +#if HANDLE_MULTIBYTE +static void udequote_pathname __P((char *)); +static void wdequote_pathname __P((char *)); +#else +# define dequote_pathname udequote_pathname +#endif +static void dequote_pathname __P((char *)); +static int glob_testdir __P((char *)); +static char **glob_dir_to_array __P((char *, char **, int)); + +/* Compile `glob_loop.c' for single-byte characters. */ +#define CHAR unsigned char +#define INT int +#define L(CS) CS +#define INTERNAL_GLOB_PATTERN_P internal_glob_pattern_p +#include "glob_loop.c" + +/* Compile `glob_loop.c' again for multibyte characters. */ +#if HANDLE_MULTIBYTE + +#define CHAR wchar_t +#define INT wint_t +#define L(CS) L##CS +#define INTERNAL_GLOB_PATTERN_P internal_glob_wpattern_p +#include "glob_loop.c" + +#endif /* HANDLE_MULTIBYTE */ + +/* And now a function that calls either the single-byte or multibyte version + of internal_glob_pattern_p. */ +int +glob_pattern_p (pattern) + const char *pattern; +{ +#if HANDLE_MULTIBYTE + size_t n; + wchar_t *wpattern; + int r; + + if (MB_CUR_MAX == 1) + return (internal_glob_pattern_p ((unsigned char *)pattern)); + + /* Convert strings to wide chars, and call the multibyte version. */ + n = xdupmbstowcs (&wpattern, NULL, pattern); + if (n == (size_t)-1) + /* Oops. Invalid multibyte sequence. Try it as single-byte sequence. */ + return (internal_glob_pattern_p ((unsigned char *)pattern)); + + r = internal_glob_wpattern_p (wpattern); + free (wpattern); + + return r; +#else + return (internal_glob_pattern_p (pattern)); +#endif +} + +/* Return 1 if DNAME should be skipped according to PAT. Mostly concerned + with matching leading `.'. */ + +static int +skipname (pat, dname, flags) + char *pat; + char *dname; + int flags; +{ + /* If a leading dot need not be explicitly matched, and the pattern + doesn't start with a `.', don't match `.' or `..' */ + if (noglob_dot_filenames == 0 && pat[0] != '.' && + (pat[0] != '\\' || pat[1] != '.') && + (dname[0] == '.' && + (dname[1] == '\0' || (dname[1] == '.' && dname[2] == '\0')))) + return 1; + + /* If a dot must be explicity matched, check to see if they do. */ + else if (noglob_dot_filenames && dname[0] == '.' && pat[0] != '.' && + (pat[0] != '\\' || pat[1] != '.')) + return 1; + + return 0; +} + +#if HANDLE_MULTIBYTE +/* Return 1 if DNAME should be skipped according to PAT. Handles multibyte + characters in PAT and DNAME. Mostly concerned with matching leading `.'. */ + +static int +mbskipname (pat, dname, flags) + char *pat, *dname; + int flags; +{ + int ret; + wchar_t *pat_wc, *dn_wc; + size_t pat_n, dn_n; + + pat_wc = dn_wc = (wchar_t *)NULL; + + pat_n = xdupmbstowcs (&pat_wc, NULL, pat); + if (pat_n != (size_t)-1) + dn_n = xdupmbstowcs (&dn_wc, NULL, dname); + + ret = 0; + if (pat_n != (size_t)-1 && dn_n !=(size_t)-1) + { + /* If a leading dot need not be explicitly matched, and the + pattern doesn't start with a `.', don't match `.' or `..' */ + if (noglob_dot_filenames == 0 && pat_wc[0] != L'.' && + (pat_wc[0] != L'\\' || pat_wc[1] != L'.') && + (dn_wc[0] == L'.' && + (dn_wc[1] == L'\0' || (dn_wc[1] == L'.' && dn_wc[2] == L'\0')))) + ret = 1; + + /* If a leading dot must be explicity matched, check to see if the + pattern and dirname both have one. */ + else if (noglob_dot_filenames && dn_wc[0] == L'.' && + pat_wc[0] != L'.' && + (pat_wc[0] != L'\\' || pat_wc[1] != L'.')) + ret = 1; + } + else + ret = skipname (pat, dname, flags); + + FREE (pat_wc); + FREE (dn_wc); + + return ret; +} +#endif /* HANDLE_MULTIBYTE */ + +/* Remove backslashes quoting characters in PATHNAME by modifying PATHNAME. */ +static void +udequote_pathname (pathname) + char *pathname; +{ + register int i, j; + + for (i = j = 0; pathname && pathname[i]; ) + { + if (pathname[i] == '\\') + i++; + + pathname[j++] = pathname[i++]; + + if (pathname[i - 1] == 0) + break; + } + if (pathname) + pathname[j] = '\0'; +} + +#if HANDLE_MULTIBYTE +/* Remove backslashes quoting characters in PATHNAME by modifying PATHNAME. */ +static void +wdequote_pathname (pathname) + char *pathname; +{ + mbstate_t ps; + size_t len, n; + wchar_t *wpathname; + int i, j; + wchar_t *orig_wpathname; + + len = strlen (pathname); + /* Convert the strings into wide characters. */ + n = xdupmbstowcs (&wpathname, NULL, pathname); + if (n == (size_t) -1) + /* Something wrong. Fall back to single-byte */ + return udequote_pathname (pathname); + orig_wpathname = wpathname; + + for (i = j = 0; wpathname && wpathname[i]; ) + { + if (wpathname[i] == L'\\') + i++; + + wpathname[j++] = wpathname[i++]; + + if (wpathname[i - 1] == L'\0') + break; + } + if (wpathname) + wpathname[j] = L'\0'; + + /* Convert the wide character string into unibyte character set. */ + memset (&ps, '\0', sizeof(mbstate_t)); + n = wcsrtombs(pathname, (const wchar_t **)&wpathname, len, &ps); + pathname[len] = '\0'; + + /* Can't just free wpathname here; wcsrtombs changes it in many cases. */ + free (orig_wpathname); +} + +static void +dequote_pathname (pathname) + char *pathname; +{ + if (MB_CUR_MAX > 1) + wdequote_pathname (pathname); + else + udequote_pathname (pathname); +} +#endif /* HANDLE_MULTIBYTE */ + +/* Test whether NAME exists. */ + +#if defined (HAVE_LSTAT) +# define GLOB_TESTNAME(name) (lstat (name, &finfo)) +#else /* !HAVE_LSTAT */ +# if !defined (AFS) +# define GLOB_TESTNAME(name) (sh_eaccess (name, F_OK)) +# else /* AFS */ +# define GLOB_TESTNAME(name) (access (name, F_OK)) +# endif /* AFS */ +#endif /* !HAVE_LSTAT */ + +/* Return 0 if DIR is a directory, -1 otherwise. */ +static int +glob_testdir (dir) + char *dir; +{ + struct stat finfo; + +/*itrace("glob_testdir: testing %s", dir);*/ + if (stat (dir, &finfo) < 0) + return (-1); + + if (S_ISDIR (finfo.st_mode) == 0) + return (-1); + + return (0); +} + +/* Recursively scan SDIR for directories matching PAT (PAT is always `**'). + FLAGS is simply passed down to the recursive call to glob_vector. Returns + a list of matching directory names. EP, if non-null, is set to the last + element of the returned list. NP, if non-null, is set to the number of + directories in the returned list. These two variables exist for the + convenience of the caller (always glob_vector). */ +static struct globval * +finddirs (pat, sdir, flags, ep, np) + char *pat; + char *sdir; + int flags; + struct globval **ep; + int *np; +{ + char **r, *n; + int ndirs; + struct globval *ret, *e, *g; + +/*itrace("finddirs: pat = `%s' sdir = `%s' flags = 0x%x", pat, sdir, flags);*/ + e = ret = 0; + r = glob_vector (pat, sdir, flags); + if (r == 0 || r[0] == 0) + { + if (np) + *np = 0; + if (ep) + *ep = 0; + if (r && r != &glob_error_return) + free (r); + return (struct globval *)0; + } + for (ndirs = 0; r[ndirs] != 0; ndirs++) + { + g = (struct globval *) malloc (sizeof (struct globval)); + if (g == 0) + { + while (ret) /* free list built so far */ + { + g = ret->next; + free (ret); + ret = g; + } + + free (r); + if (np) + *np = 0; + if (ep) + *ep = 0; + return (&finddirs_error_return); + } + if (e == 0) + e = g; + + g->next = ret; + ret = g; + + g->name = r[ndirs]; + } + + free (r); + if (ep) + *ep = e; + if (np) + *np = ndirs; + + return ret; +} + +/* Return a vector of names of files in directory DIR + whose names match glob pattern PAT. + The names are not in any particular order. + Wildcards at the beginning of PAT do not match an initial period. + + The vector is terminated by an element that is a null pointer. + + To free the space allocated, first free the vector's elements, + then free the vector. + + Return 0 if cannot get enough memory to hold the pointer + and the names. + + Return -1 if cannot access directory DIR. + Look in errno for more information. */ + +char ** +glob_vector (pat, dir, flags) + char *pat; + char *dir; + int flags; +{ + DIR *d; + register struct dirent *dp; + struct globval *lastlink, *e, *dirlist; + register struct globval *nextlink; + register char *nextname, *npat, *subdir; + unsigned int count; + int lose, skip, ndirs, isdir, sdlen, add_current, patlen; + register char **name_vector; + register unsigned int i; + int mflags; /* Flags passed to strmatch (). */ + int pflags; /* flags passed to sh_makepath () */ + int nalloca; + struct globval *firstmalloc, *tmplink; + char *convfn; + + lastlink = 0; + count = lose = skip = add_current = 0; + + firstmalloc = 0; + nalloca = 0; + +/*itrace("glob_vector: pat = `%s' dir = `%s' flags = 0x%x", pat, dir, flags);*/ + /* If PAT is empty, skip the loop, but return one (empty) filename. */ + if (pat == 0 || *pat == '\0') + { + if (glob_testdir (dir) < 0) + return ((char **) &glob_error_return); + + nextlink = (struct globval *)alloca (sizeof (struct globval)); + if (nextlink == NULL) + return ((char **) NULL); + + nextlink->next = (struct globval *)0; + nextname = (char *) malloc (1); + if (nextname == 0) + lose = 1; + else + { + lastlink = nextlink; + nextlink->name = nextname; + nextname[0] = '\0'; + count = 1; + } + + skip = 1; + } + + patlen = (pat && *pat) ? strlen (pat) : 0; + + /* If the filename pattern (PAT) does not contain any globbing characters, + we can dispense with reading the directory, and just see if there is + a filename `DIR/PAT'. If there is, and we can access it, just make the + vector to return and bail immediately. */ + if (skip == 0 && glob_pattern_p (pat) == 0) + { + int dirlen; + struct stat finfo; + + if (glob_testdir (dir) < 0) + return ((char **) &glob_error_return); + + dirlen = strlen (dir); + nextname = (char *)malloc (dirlen + patlen + 2); + npat = (char *)malloc (patlen + 1); + if (nextname == 0 || npat == 0) + { + FREE (nextname); + FREE (npat); + lose = 1; + } + else + { + strcpy (npat, pat); + dequote_pathname (npat); + + strcpy (nextname, dir); + nextname[dirlen++] = '/'; + strcpy (nextname + dirlen, npat); + + if (GLOB_TESTNAME (nextname) >= 0) + { + free (nextname); + nextlink = (struct globval *)alloca (sizeof (struct globval)); + if (nextlink) + { + nextlink->next = (struct globval *)0; + lastlink = nextlink; + nextlink->name = npat; + count = 1; + } + else + { + free (npat); + lose = 1; + } + } + else + { + free (nextname); + free (npat); + } + } + + skip = 1; + } + + if (skip == 0) + { + /* Open the directory, punting immediately if we cannot. If opendir + is not robust (i.e., it opens non-directories successfully), test + that DIR is a directory and punt if it's not. */ +#if defined (OPENDIR_NOT_ROBUST) + if (glob_testdir (dir) < 0) + return ((char **) &glob_error_return); +#endif + + d = opendir (dir); + if (d == NULL) + return ((char **) &glob_error_return); + + /* Compute the flags that will be passed to strmatch(). We don't + need to do this every time through the loop. */ + mflags = (noglob_dot_filenames ? FNM_PERIOD : 0) | FNM_PATHNAME; + +#ifdef FNM_CASEFOLD + if (glob_ignore_case) + mflags |= FNM_CASEFOLD; +#endif + + if (extended_glob) + mflags |= FNM_EXTMATCH; + + add_current = ((flags & (GX_ALLDIRS|GX_ADDCURDIR)) == (GX_ALLDIRS|GX_ADDCURDIR)); + + /* Scan the directory, finding all names that match. + For each name that matches, allocate a struct globval + on the stack and store the name in it. + Chain those structs together; lastlink is the front of the chain. */ + while (1) + { + /* Make globbing interruptible in the shell. */ + if (interrupt_state || terminating_signal) + { + lose = 1; + break; + } + + dp = readdir (d); + if (dp == NULL) + break; + + /* If this directory entry is not to be used, try again. */ + if (REAL_DIR_ENTRY (dp) == 0) + continue; + +#if 0 + if (dp->d_name == 0 || *dp->d_name == 0) + continue; +#endif + +#if HANDLE_MULTIBYTE + if (MB_CUR_MAX > 1 && mbskipname (pat, dp->d_name, flags)) + continue; + else +#endif + if (skipname (pat, dp->d_name, flags)) + continue; + + /* If we're only interested in directories, don't bother with files */ + if (flags & (GX_MATCHDIRS|GX_ALLDIRS)) + { + pflags = (flags & GX_ALLDIRS) ? MP_RMDOT : 0; + if (flags & GX_NULLDIR) + pflags |= MP_IGNDOT; + subdir = sh_makepath (dir, dp->d_name, pflags); + isdir = glob_testdir (subdir); + if (isdir < 0 && (flags & GX_MATCHDIRS)) + { + free (subdir); + continue; + } + } + + if (flags & GX_ALLDIRS) + { + if (isdir == 0) + { + dirlist = finddirs (pat, subdir, (flags & ~GX_ADDCURDIR), &e, &ndirs); + if (dirlist == &finddirs_error_return) + { + free (subdir); + lose = 1; + break; + } + if (ndirs) /* add recursive directories to list */ + { + if (firstmalloc == 0) + firstmalloc = e; + e->next = lastlink; + lastlink = dirlist; + count += ndirs; + } + } + + nextlink = (struct globval *) malloc (sizeof (struct globval)); + if (firstmalloc == 0) + firstmalloc = nextlink; + sdlen = strlen (subdir); + nextname = (char *) malloc (sdlen + 1); + if (nextlink == 0 || nextname == 0) + { + FREE (nextlink); + FREE (nextname); + free (subdir); + lose = 1; + break; + } + nextlink->next = lastlink; + lastlink = nextlink; + nextlink->name = nextname; + bcopy (subdir, nextname, sdlen + 1); + free (subdir); + ++count; + continue; + } + else if (flags & GX_MATCHDIRS) + free (subdir); + + convfn = fnx_fromfs (dp->d_name, D_NAMLEN (dp)); + if (strmatch (pat, convfn, mflags) != FNM_NOMATCH) + { + if (nalloca < ALLOCA_MAX) + { + nextlink = (struct globval *) alloca (sizeof (struct globval)); + nalloca += sizeof (struct globval); + } + else + { + nextlink = (struct globval *) malloc (sizeof (struct globval)); + if (firstmalloc == 0) + firstmalloc = nextlink; + } + + nextname = (char *) malloc (D_NAMLEN (dp) + 1); + if (nextlink == 0 || nextname == 0) + { + FREE (nextlink); + FREE (nextname); + lose = 1; + break; + } + nextlink->next = lastlink; + lastlink = nextlink; + nextlink->name = nextname; + bcopy (dp->d_name, nextname, D_NAMLEN (dp) + 1); + ++count; + } + } + + (void) closedir (d); + } + + /* compat: if GX_ADDCURDIR, add the passed directory also. Add an empty + directory name as a placeholder if GX_NULLDIR (in which case the passed + directory name is "."). */ + if (add_current) + { + sdlen = strlen (dir); + nextname = (char *)malloc (sdlen + 1); + nextlink = (struct globval *) malloc (sizeof (struct globval)); + if (nextlink == 0 || nextname == 0) + { + FREE (nextlink); + FREE (nextname); + lose = 1; + } + else + { + nextlink->name = nextname; + nextlink->next = lastlink; + lastlink = nextlink; + if (flags & GX_NULLDIR) + nextname[0] = '\0'; + else + bcopy (dir, nextname, sdlen + 1); + ++count; + } + } + + if (lose == 0) + { + name_vector = (char **) malloc ((count + 1) * sizeof (char *)); + lose |= name_vector == NULL; + } + + /* Have we run out of memory? */ + if (lose) + { + tmplink = 0; + + /* Here free the strings we have got. */ + while (lastlink) + { + /* Since we build the list in reverse order, the first N entries + will be allocated with malloc, if firstmalloc is set, from + lastlink to firstmalloc. */ + if (firstmalloc) + { + if (lastlink == firstmalloc) + firstmalloc = 0; + tmplink = lastlink; + } + else + tmplink = 0; + free (lastlink->name); + lastlink = lastlink->next; + FREE (tmplink); + } + + QUIT; + + return ((char **)NULL); + } + + /* Copy the name pointers from the linked list into the vector. */ + for (tmplink = lastlink, i = 0; i < count; ++i) + { + name_vector[i] = tmplink->name; + tmplink = tmplink->next; + } + + name_vector[count] = NULL; + + /* If we allocated some of the struct globvals, free them now. */ + if (firstmalloc) + { + tmplink = 0; + while (lastlink) + { + tmplink = lastlink; + if (lastlink == firstmalloc) + lastlink = firstmalloc = 0; + else + lastlink = lastlink->next; + free (tmplink); + } + } + + return (name_vector); +} + +/* Return a new array which is the concatenation of each string in ARRAY + to DIR. This function expects you to pass in an allocated ARRAY, and + it takes care of free()ing that array. Thus, you might think of this + function as side-effecting ARRAY. This should handle GX_MARKDIRS. */ +static char ** +glob_dir_to_array (dir, array, flags) + char *dir, **array; + int flags; +{ + register unsigned int i, l; + int add_slash; + char **result, *new; + struct stat sb; + + l = strlen (dir); + if (l == 0) + { + if (flags & GX_MARKDIRS) + for (i = 0; array[i]; i++) + { + if ((stat (array[i], &sb) == 0) && S_ISDIR (sb.st_mode)) + { + l = strlen (array[i]); + new = (char *)realloc (array[i], l + 2); + if (new == 0) + return NULL; + new[l] = '/'; + new[l+1] = '\0'; + array[i] = new; + } + } + return (array); + } + + add_slash = dir[l - 1] != '/'; + + i = 0; + while (array[i] != NULL) + ++i; + + result = (char **) malloc ((i + 1) * sizeof (char *)); + if (result == NULL) + return (NULL); + + for (i = 0; array[i] != NULL; i++) + { + /* 3 == 1 for NUL, 1 for slash at end of DIR, 1 for GX_MARKDIRS */ + result[i] = (char *) malloc (l + strlen (array[i]) + 3); + + if (result[i] == NULL) + { + int ind; + for (ind = 0; ind < i; ind++) + free (result[ind]); + free (result); + return (NULL); + } + + strcpy (result[i], dir); + if (add_slash) + result[i][l] = '/'; + strcpy (result[i] + l + add_slash, array[i]); + if (flags & GX_MARKDIRS) + { + if ((stat (result[i], &sb) == 0) && S_ISDIR (sb.st_mode)) + { + size_t rlen; + rlen = strlen (result[i]); + result[i][rlen] = '/'; + result[i][rlen+1] = '\0'; + } + } + } + result[i] = NULL; + + /* Free the input array. */ + for (i = 0; array[i] != NULL; i++) + free (array[i]); + free ((char *) array); + + return (result); +} + +/* Do globbing on PATHNAME. Return an array of pathnames that match, + marking the end of the array with a null-pointer as an element. + If no pathnames match, then the array is empty (first element is null). + If there isn't enough memory, then return NULL. + If a file system error occurs, return -1; `errno' has the error code. */ +char ** +glob_filename (pathname, flags) + char *pathname; + int flags; +{ + char **result; + unsigned int result_size; + char *directory_name, *filename, *dname; + unsigned int directory_len; + int free_dirname; /* flag */ + int dflags; + + result = (char **) malloc (sizeof (char *)); + result_size = 1; + if (result == NULL) + return (NULL); + + result[0] = NULL; + + directory_name = NULL; + + /* Find the filename. */ + filename = strrchr (pathname, '/'); + if (filename == NULL) + { + filename = pathname; + directory_name = ""; + directory_len = 0; + free_dirname = 0; + } + else + { + directory_len = (filename - pathname) + 1; + directory_name = (char *) malloc (directory_len + 1); + + if (directory_name == 0) /* allocation failed? */ + return (NULL); + + bcopy (pathname, directory_name, directory_len); + directory_name[directory_len] = '\0'; + ++filename; + free_dirname = 1; + } + + /* If directory_name contains globbing characters, then we + have to expand the previous levels. Just recurse. */ + if (glob_pattern_p (directory_name)) + { + char **directories; + register unsigned int i; + + dflags = flags & ~GX_MARKDIRS; + if ((flags & GX_GLOBSTAR) && directory_name[0] == '*' && directory_name[1] == '*' && (directory_name[2] == '/' || directory_name[2] == '\0')) + dflags |= GX_ALLDIRS|GX_ADDCURDIR; + + if (directory_name[directory_len - 1] == '/') + directory_name[directory_len - 1] = '\0'; + + directories = glob_filename (directory_name, dflags); + + if (free_dirname) + { + free (directory_name); + directory_name = NULL; + } + + if (directories == NULL) + goto memory_error; + else if (directories == (char **)&glob_error_return) + { + free ((char *) result); + return ((char **) &glob_error_return); + } + else if (*directories == NULL) + { + free ((char *) directories); + free ((char *) result); + return ((char **) &glob_error_return); + } + + /* We have successfully globbed the preceding directory name. + For each name in DIRECTORIES, call glob_vector on it and + FILENAME. Concatenate the results together. */ + for (i = 0; directories[i] != NULL; ++i) + { + char **temp_results; + + /* XXX -- we've recursively scanned any directories resulting from + a `**', so turn off the flag. We turn it on again below if + filename is `**' */ + /* Scan directory even on a NULL filename. That way, `*h/' + returns only directories ending in `h', instead of all + files ending in `h' with a `/' appended. */ + dname = directories[i]; + dflags = flags & ~(GX_MARKDIRS|GX_ALLDIRS|GX_ADDCURDIR); + if ((flags & GX_GLOBSTAR) && filename[0] == '*' && filename[1] == '*' && filename[2] == '\0') + dflags |= GX_ALLDIRS|GX_ADDCURDIR; + if (dname[0] == '\0' && filename[0]) + { + dflags |= GX_NULLDIR; + dname = "."; /* treat null directory name and non-null filename as current directory */ + } + temp_results = glob_vector (filename, dname, dflags); + + /* Handle error cases. */ + if (temp_results == NULL) + goto memory_error; + else if (temp_results == (char **)&glob_error_return) + /* This filename is probably not a directory. Ignore it. */ + ; + else + { + char **array; + register unsigned int l; + + /* If we're expanding **, we don't need to glue the directory + name to the results; we've already done it in glob_vector */ + if ((dflags & GX_ALLDIRS) && filename[0] == '*' && filename[1] == '*' && filename[2] == '\0') + array = temp_results; + else + array = glob_dir_to_array (directories[i], temp_results, flags); + l = 0; + while (array[l] != NULL) + ++l; + + result = + (char **)realloc (result, (result_size + l) * sizeof (char *)); + + if (result == NULL) + goto memory_error; + + for (l = 0; array[l] != NULL; ++l) + result[result_size++ - 1] = array[l]; + + result[result_size - 1] = NULL; + + /* Note that the elements of ARRAY are not freed. */ + if (array != temp_results) + free ((char *) array); + else + free (temp_results); /* expanding ** case above */ + } + } + /* Free the directories. */ + for (i = 0; directories[i]; i++) + free (directories[i]); + + free ((char *) directories); + + return (result); + } + + /* If there is only a directory name, return it. */ + if (*filename == '\0') + { + result = (char **) realloc ((char *) result, 2 * sizeof (char *)); + if (result == NULL) + return (NULL); + /* Handle GX_MARKDIRS here. */ + result[0] = (char *) malloc (directory_len + 1); + if (result[0] == NULL) + goto memory_error; + bcopy (directory_name, result[0], directory_len + 1); + if (free_dirname) + free (directory_name); + result[1] = NULL; + return (result); + } + else + { + char **temp_results; + + /* There are no unquoted globbing characters in DIRECTORY_NAME. + Dequote it before we try to open the directory since there may + be quoted globbing characters which should be treated verbatim. */ + if (directory_len > 0) + dequote_pathname (directory_name); + + /* We allocated a small array called RESULT, which we won't be using. + Free that memory now. */ + free (result); + + /* Just return what glob_vector () returns appended to the + directory name. */ + /* If flags & GX_ALLDIRS, we're called recursively */ + dflags = flags & ~GX_MARKDIRS; + if (directory_len == 0) + dflags |= GX_NULLDIR; + if ((flags & GX_GLOBSTAR) && filename[0] == '*' && filename[1] == '*' && filename[2] == '\0') + { + dflags |= GX_ALLDIRS|GX_ADDCURDIR; +#if 0 + /* If we want all directories (dflags & GX_ALLDIRS) and we're not + being called recursively as something like `echo [star][star]/[star].o' + ((flags & GX_ALLDIRS) == 0), we want to prevent glob_vector from + adding a null directory name to the front of the temp_results + array. We turn off ADDCURDIR if not called recursively and + dlen == 0 */ +#endif + if (directory_len == 0 && (flags & GX_ALLDIRS) == 0) + dflags &= ~GX_ADDCURDIR; + } + temp_results = glob_vector (filename, + (directory_len == 0 ? "." : directory_name), + dflags); + + if (temp_results == NULL || temp_results == (char **)&glob_error_return) + { + if (free_dirname) + free (directory_name); + return (temp_results); + } + + result = glob_dir_to_array ((dflags & GX_ALLDIRS) ? "" : directory_name, temp_results, flags); + if (free_dirname) + free (directory_name); + return (result); + } + + /* We get to memory_error if the program has run out of memory, or + if this is the shell, and we have been interrupted. */ + memory_error: + if (result != NULL) + { + register unsigned int i; + for (i = 0; result[i] != NULL; ++i) + free (result[i]); + free ((char *) result); + } + + if (free_dirname && directory_name) + free (directory_name); + + QUIT; + + return (NULL); +} + +#if defined (TEST) + +main (argc, argv) + int argc; + char **argv; +{ + unsigned int i; + + for (i = 1; i < argc; ++i) + { + char **value = glob_filename (argv[i], 0); + if (value == NULL) + puts ("Out of memory."); + else if (value == &glob_error_return) + perror (argv[i]); + else + for (i = 0; value[i] != NULL; i++) + puts (value[i]); + } + + exit (0); +} +#endif /* TEST. */ diff --git a/lib/glob/xmbsrtowcs.c b/lib/glob/xmbsrtowcs.c index 84b99416f..5af8983b7 100644 --- a/lib/glob/xmbsrtowcs.c +++ b/lib/glob/xmbsrtowcs.c @@ -271,6 +271,8 @@ xdupmbstowcs (destp, indicesp, src) { if (destp) *destp = NULL; + if (indicesp) + *indicesp = NULL; return (size_t)-1; } @@ -286,6 +288,8 @@ xdupmbstowcs (destp, indicesp, src) if (wsbuf == NULL) { *destp = NULL; + if (indicesp) + *indicesp = NULL; return (size_t)-1; } @@ -297,6 +301,7 @@ xdupmbstowcs (destp, indicesp, src) { free (wsbuf); *destp = NULL; + *indicesp = NULL; return (size_t)-1; } } @@ -331,6 +336,8 @@ xdupmbstowcs (destp, indicesp, src) free (wsbuf); FREE (indices); *destp = NULL; + if (indicesp) + *indicesp = NULL; return (size_t)-1; } @@ -350,18 +357,22 @@ xdupmbstowcs (destp, indicesp, src) free (wsbuf); FREE (indices); *destp = NULL; + if (indicesp) + *indicesp = NULL; return (size_t)-1; } wsbuf = wstmp; if (indicesp) { - idxtmp = (char **) realloc (indices, wsbuf_size * sizeof (char **)); + idxtmp = (char **) realloc (indices, wsbuf_size * sizeof (char *)); if (idxtmp == NULL) { free (wsbuf); free (indices); *destp = NULL; + if (indicesp) + *indicesp = NULL; return (size_t)-1; } indices = idxtmp; diff --git a/lib/glob/xmbsrtowcs.c~ b/lib/glob/xmbsrtowcs.c~ new file mode 100644 index 000000000..3baeee022 --- /dev/null +++ b/lib/glob/xmbsrtowcs.c~ @@ -0,0 +1,397 @@ +/* xmbsrtowcs.c -- replacement function for mbsrtowcs */ + +/* Copyright (C) 2002-2011 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* Ask for GNU extensions to get extern declaration for mbsnrtowcs if + available via glibc. */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +#endif + +#include + +#include + +/* , and are included in "shmbutil.h". + If , , mbsrtowcs(), exist, HANDLE_MULTIBYTE + is defined as 1. */ +#include + +#if HANDLE_MULTIBYTE + +#define WSBUF_INC 32 + +#ifndef FREE +# define FREE(x) do { if (x) free (x); } while (0) +#endif + +#if ! HAVE_STRCHRNUL +extern char *strchrnul __P((const char *, int)); +#endif + +/* On some locales (ex. ja_JP.sjis), mbsrtowc doesn't convert 0x5c to U<0x5c>. + So, this function is made for converting 0x5c to U<0x5c>. */ + +static mbstate_t local_state; +static int local_state_use = 0; + +size_t +xmbsrtowcs (dest, src, len, pstate) + wchar_t *dest; + const char **src; + size_t len; + mbstate_t *pstate; +{ + mbstate_t *ps; + size_t mblength, wclength, n; + + ps = pstate; + if (pstate == NULL) + { + if (!local_state_use) + { + memset (&local_state, '\0', sizeof(mbstate_t)); + local_state_use = 1; + } + ps = &local_state; + } + + n = strlen (*src); + + if (dest == NULL) + { + wchar_t *wsbuf; + const char *mbs; + mbstate_t psbuf; + + /* It doesn't matter if malloc fails here, since mbsrtowcs should do + the right thing with a NULL first argument. */ + wsbuf = (wchar_t *) malloc ((n + 1) * sizeof(wchar_t)); + mbs = *src; + psbuf = *ps; + + wclength = mbsrtowcs (wsbuf, &mbs, n, &psbuf); + + if (wsbuf) + free (wsbuf); + return wclength; + } + + for (wclength = 0; wclength < len; wclength++, dest++) + { + if (mbsinit(ps)) + { + if (**src == '\0') + { + *dest = L'\0'; + *src = NULL; + return (wclength); + } + else if (**src == '\\') + { + *dest = L'\\'; + mblength = 1; + } + else + mblength = mbrtowc(dest, *src, n, ps); + } + else + mblength = mbrtowc(dest, *src, n, ps); + + /* Cannot convert multibyte character to wide character. */ + if (mblength == (size_t)-1 || mblength == (size_t)-2) + return (size_t)-1; + + *src += mblength; + n -= mblength; + + /* The multibyte string has been completely converted, + including the terminating '\0'. */ + if (*dest == L'\0') + { + *src = NULL; + break; + } + } + + return (wclength); +} + +#if HAVE_MBSNRTOWCS +/* Convert a multibyte string to a wide character string. Memory for the + new wide character string is obtained with malloc. + + Fast multiple-character version of xdupmbstowcs used when the indices are + not required and mbsnrtowcs is available. */ + +static size_t +xdupmbstowcs2 (destp, src) + wchar_t **destp; /* Store the pointer to the wide character string */ + const char *src; /* Multibyte character string */ +{ + const char *p; /* Conversion start position of src */ + wchar_t *wsbuf; /* Buffer for wide characters. */ + size_t wsbuf_size; /* Size of WSBUF */ + size_t wcnum; /* Number of wide characters in WSBUF */ + mbstate_t state; /* Conversion State */ + size_t n, wcslength; /* Number of wide characters produced by the conversion. */ + const char *end_or_backslash; + size_t nms; /* Number of multibyte characters to convert at one time. */ + mbstate_t tmp_state; + const char *tmp_p; + + memset (&state, '\0', sizeof(mbstate_t)); + + wsbuf_size = 0; + wsbuf = NULL; + + p = src; + wcnum = 0; + do + { + end_or_backslash = strchrnul(p, '\\'); + nms = end_or_backslash - p; + if (*end_or_backslash == '\0') + nms++; + + /* Compute the number of produced wide-characters. */ + tmp_p = p; + tmp_state = state; + + if (nms == 0 && *p == '\\') /* special initial case */ + nms = wcslength = 1; + else + wcslength = mbsnrtowcs (NULL, &tmp_p, nms, 0, &tmp_state); + + if (wcslength == 0) + { + tmp_p = p; /* will need below */ + tmp_state = state; + wcslength = 1; /* take a single byte */ + } + + /* Conversion failed. */ + if (wcslength == (size_t)-1) + { + free (wsbuf); + *destp = NULL; + return (size_t)-1; + } + + /* Resize the buffer if it is not large enough. */ + if (wsbuf_size < wcnum+wcslength+1) /* 1 for the L'\0' or the potential L'\\' */ + { + wchar_t *wstmp; + + while (wsbuf_size < wcnum+wcslength+1) /* 1 for the L'\0' or the potential L'\\' */ + wsbuf_size += WSBUF_INC; + + wstmp = (wchar_t *) realloc (wsbuf, wsbuf_size * sizeof (wchar_t)); + if (wstmp == NULL) + { + free (wsbuf); + *destp = NULL; + return (size_t)-1; + } + wsbuf = wstmp; + } + + /* Perform the conversion. This is assumed to return 'wcslength'. + It may set 'p' to NULL. */ + n = mbsnrtowcs(wsbuf+wcnum, &p, nms, wsbuf_size-wcnum, &state); + + /* Compensate for taking single byte on wcs conversion failure above. */ + if (wcslength == 1 && (n == 0 || n == (size_t)-1)) + { + state = tmp_state; + p = tmp_p; + wsbuf[wcnum++] = *p++; + } + else + wcnum += wcslength; + + if (mbsinit (&state) && (p != NULL) && (*p == '\\')) + { + wsbuf[wcnum++] = L'\\'; + p++; + } + } + while (p != NULL); + + *destp = wsbuf; + + /* Return the length of the wide character string, not including `\0'. */ + return wcnum; +} +#endif /* HAVE_MBSNRTOWCS */ + +/* Convert a multibyte string to a wide character string. Memory for the + new wide character string is obtained with malloc. + + The return value is the length of the wide character string. Returns a + pointer to the wide character string in DESTP. If INDICESP is not NULL, + INDICESP stores the pointer to the pointer array. Each pointer is to + the first byte of each multibyte character. Memory for the pointer array + is obtained with malloc, too. + If conversion is failed, the return value is (size_t)-1 and the values + of DESTP and INDICESP are NULL. */ + +size_t +xdupmbstowcs (destp, indicesp, src) + wchar_t **destp; /* Store the pointer to the wide character string */ + char ***indicesp; /* Store the pointer to the pointer array. */ + const char *src; /* Multibyte character string */ +{ + const char *p; /* Conversion start position of src */ + wchar_t wc; /* Created wide character by conversion */ + wchar_t *wsbuf; /* Buffer for wide characters. */ + char **indices; /* Buffer for indices. */ + size_t wsbuf_size; /* Size of WSBUF */ + size_t wcnum; /* Number of wide characters in WSBUF */ + mbstate_t state; /* Conversion State */ + + /* In case SRC or DESP is NULL, conversion doesn't take place. */ + if (src == NULL || destp == NULL) + { + if (destp) + *destp = NULL; + if (indicesp) + *indicesp = NULL; + return (size_t)-1; + } + +#if HAVE_MBSNRTOWCS + if (indicesp == NULL) + return (xdupmbstowcs2 (destp, src)); +#endif + + memset (&state, '\0', sizeof(mbstate_t)); + wsbuf_size = WSBUF_INC; + + wsbuf = (wchar_t *) malloc (wsbuf_size * sizeof(wchar_t)); + if (wsbuf == NULL) + { + *destp = NULL; + if (indicesp) + *indicesp = NULL; + return (size_t)-1; + } + + indices = NULL; + if (indicesp) + { + indices = (char **) malloc (wsbuf_size * sizeof(char *)); + if (indices == NULL) + { + free (wsbuf); + *destp = NULL; + *indicesp = NULL; + return (size_t)-1; + } + } + + p = src; + wcnum = 0; + do + { + size_t mblength; /* Byte length of one multibyte character. */ + + if (mbsinit (&state)) + { + if (*p == '\0') + { + wc = L'\0'; + mblength = 1; + } + else if (*p == '\\') + { + wc = L'\\'; + mblength = 1; + } + else + mblength = mbrtowc(&wc, p, MB_LEN_MAX, &state); + } + else + mblength = mbrtowc(&wc, p, MB_LEN_MAX, &state); + + /* Conversion failed. */ + if (MB_INVALIDCH (mblength)) + { + free (wsbuf); + FREE (indices); + *destp = NULL; + if (indicesp) + *indicesp = NULL; + return (size_t)-1; + } + + ++wcnum; + + /* Resize buffers when they are not large enough. */ + if (wsbuf_size < wcnum) + { + wchar_t *wstmp; + char **idxtmp; + + wsbuf_size += WSBUF_INC; + + wstmp = (wchar_t *) realloc (wsbuf, wsbuf_size * sizeof (wchar_t)); + if (wstmp == NULL) + { + free (wsbuf); + FREE (indices); + *destp = NULL; + if (indicesp) + *indicesp = NULL; + return (size_t)-1; + } + wsbuf = wstmp; + + if (indicesp) + { + idxtmp = (char **) realloc (indices, wsbuf_size * sizeof (char **)); + if (idxtmp == NULL) + { + free (wsbuf); + free (indices); + *destp = NULL; + if (indicesp) + *indicesp = NULL; + return (size_t)-1; + } + indices = idxtmp; + } + } + + wsbuf[wcnum - 1] = wc; + if (indices) + indices[wcnum - 1] = (char *)p; + p += mblength; + } + while (MB_NULLWCH (wc) == 0); + + /* Return the length of the wide character string, not including `\0'. */ + *destp = wsbuf; + if (indicesp != NULL) + *indicesp = indices; + + return (wcnum - 1); +} + +#endif /* HANDLE_MULTIBYTE */ diff --git a/lib/sh/fmtulong.c b/lib/sh/fmtulong.c index 214b1466a..0ccc22b4c 100644 --- a/lib/sh/fmtulong.c +++ b/lib/sh/fmtulong.c @@ -1,6 +1,6 @@ /* fmtulong.c -- Convert unsigned long int to string. */ -/* Copyright (C) 1998-2002 Free Software Foundation, Inc. +/* Copyright (C) 1998-2011 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -98,8 +98,9 @@ fmtulong (ui, base, buf, len, flags) if (base < 2 || base > 64) { #if 1 + /* XXX - truncation possible with long translation */ strncpy (buf, _("invalid base"), len - 1); - buf[len] = '\0'; + buf[len-1] = '\0'; errno = EINVAL; return (p = buf); #else diff --git a/lib/sh/fmtulong.c~ b/lib/sh/fmtulong.c~ new file mode 100644 index 000000000..f55774b84 --- /dev/null +++ b/lib/sh/fmtulong.c~ @@ -0,0 +1,191 @@ +/* fmtulong.c -- Convert unsigned long int to string. */ + +/* Copyright (C) 1998-2002 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#if defined (HAVE_LIMITS_H) +# include +#endif + +#include +#ifdef HAVE_STDDEF_H +# include +#endif + +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#include +#include + +#include + +#include "stdc.h" + +#include + +#ifndef errno +extern int errno; +#endif + +#define x_digs "0123456789abcdef" +#define X_digs "0123456789ABCDEF" + +/* XXX -- assumes uppercase letters, lowercase letters, and digits are + contiguous */ +#define FMTCHAR(x) \ + ((x) < 10) ? (x) + '0' \ + : (((x) < 36) ? (x) - 10 + 'a' \ + : (((x) < 62) ? (x) - 36 + 'A' \ + : (((x) == 62) ? '@' : '_'))) + +#ifndef FL_PREFIX +# define FL_PREFIX 0x01 /* add 0x, 0X, or 0 prefix as appropriate */ +# define FL_ADDBASE 0x02 /* add base# prefix to converted value */ +# define FL_HEXUPPER 0x04 /* use uppercase when converting to hex */ +# define FL_UNSIGNED 0x08 /* don't add any sign */ +#endif + +#ifndef LONG +# define LONG long +# define UNSIGNED_LONG unsigned long +#endif + +/* `unsigned long' (or unsigned long long) to string conversion for a given + base. The caller passes the output buffer and the size. This should + check for buffer underflow, but currently does not. */ +char * +fmtulong (ui, base, buf, len, flags) + UNSIGNED_LONG ui; + int base; + char *buf; + size_t len; + int flags; +{ + char *p; + int sign; + LONG si; + + if (base == 0) + base = 10; + + if (base < 2 || base > 64) + { +#if 1 + /* XXX - truncation possible with long translation */ + strncpy (buf, _("invalid base"), len - 1); + buf[len-1] = '\0'; + errno = EINVAL; + return (p = buf); +#else + base = 10; +#endif + } + + sign = 0; + if ((flags & FL_UNSIGNED) == 0 && (LONG)ui < 0) + { + ui = -ui; + sign = '-'; + } + + p = buf + len - 2; + p[1] = '\0'; + + /* handle common cases explicitly */ + switch (base) + { + case 10: + if (ui < 10) + { + *p-- = TOCHAR (ui); + break; + } + /* Favor signed arithmetic over unsigned arithmetic; it is faster on + many machines. */ + if ((LONG)ui < 0) + { + *p-- = TOCHAR (ui % 10); + si = ui / 10; + } + else + si = ui; + do + *p-- = TOCHAR (si % 10); + while (si /= 10); + break; + + case 8: + do + *p-- = TOCHAR (ui & 7); + while (ui >>= 3); + break; + + case 16: + do + *p-- = (flags & FL_HEXUPPER) ? X_digs[ui & 15] : x_digs[ui & 15]; + while (ui >>= 4); + break; + + case 2: + do + *p-- = TOCHAR (ui & 1); + while (ui >>= 1); + break; + + default: + do + *p-- = FMTCHAR (ui % base); + while (ui /= base); + break; + } + + if ((flags & FL_PREFIX) && (base == 8 || base == 16)) + { + if (base == 16) + { + *p-- = (flags & FL_HEXUPPER) ? 'X' : 'x'; + *p-- = '0'; + } + else if (p[1] != '0') + *p-- = '0'; + } + else if ((flags & FL_ADDBASE) && base != 10) + { + *p-- = '#'; + *p-- = TOCHAR (base % 10); + if (base > 10) + *p-- = TOCHAR (base / 10); + } + + if (sign) + *p-- = '-'; + + return (p + 1); +} diff --git a/lib/sh/zmapfd.c b/lib/sh/zmapfd.c index 4000c4a5a..e72089211 100644 --- a/lib/sh/zmapfd.c +++ b/lib/sh/zmapfd.c @@ -66,11 +66,10 @@ zmapfd (fd, ostr, fn) } else if (nr < 0) { - rval = -1; free (result); if (ostr) *ostr = (char *)NULL; - break; + return -1; } RESIZE_MALLOCED_BUFFER (result, rind, nr, rsize, 128); diff --git a/lib/sh/zmapfd.c~ b/lib/sh/zmapfd.c~ new file mode 100644 index 000000000..4000c4a5a --- /dev/null +++ b/lib/sh/zmapfd.c~ @@ -0,0 +1,90 @@ +/* zmapfd - read contents of file descriptor into a newly-allocated buffer */ + +/* Copyright (C) 2006 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include + +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include + +#include "bashansi.h" +#include "command.h" +#include "general.h" + +#if !defined (errno) +extern int errno; +#endif + +extern ssize_t zread __P((int, char *, size_t)); + +/* Dump contents of file descriptor FD to *OSTR. FN is the filename for + error messages (not used right now). */ +int +zmapfd (fd, ostr, fn) + int fd; + char **ostr; + char *fn; +{ + ssize_t nr; + int rval; + char lbuf[128]; + char *result; + int rsize, rind; + + rval = 0; + result = (char *)xmalloc (rsize = 64); + rind = 0; + + while (1) + { + nr = zread (fd, lbuf, sizeof (lbuf)); + if (nr == 0) + { + rval = rind; + break; + } + else if (nr < 0) + { + rval = -1; + free (result); + if (ostr) + *ostr = (char *)NULL; + break; + } + + RESIZE_MALLOCED_BUFFER (result, rind, nr, rsize, 128); + memcpy (result+rind, lbuf, nr); + rind += nr; + } + + RESIZE_MALLOCED_BUFFER (result, rind, 1, rsize, 128); + result[rind] = '\0'; + + if (ostr) + *ostr = result; + else + free (result); + + return rval; +} diff --git a/make_cmd.c b/make_cmd.c index 7db5bc76e..c984c8aed 100644 --- a/make_cmd.c +++ b/make_cmd.c @@ -324,6 +324,9 @@ make_arith_for_command (exprs, action, lineno) else parser_error (lineno, _("syntax error: `;' unexpected")); parser_error (lineno, _("syntax error: `((%s))'"), exprs->word->word); + free (init); + free (test); + free (step); last_command_exit_value = 2; return ((COMMAND *)NULL); } diff --git a/make_cmd.c~ b/make_cmd.c~ index d42f32f6d..7db5bc76e 100644 --- a/make_cmd.c~ +++ b/make_cmd.c~ @@ -292,7 +292,7 @@ make_arith_for_command (exprs, action, lineno) s++; start = s; /* skip to the semicolon or EOS */ - i = skip_to_delim (s, 0, ";", SD_NOJMP); + i = skip_to_delim (start, 0, ";", SD_NOJMP); s = start + i; t = (i > 0) ? substring (start, 0, i) : (char *)NULL; diff --git a/pcomplete.c b/pcomplete.c index 1ed5ac934..baa86643f 100644 --- a/pcomplete.c +++ b/pcomplete.c @@ -1,6 +1,6 @@ /* pcomplete.c - functions to generate lists of matches for programmable completion. */ -/* Copyright (C) 1999-2009 Free Software Foundation, Inc. +/* Copyright (C) 1999-2011 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -372,6 +372,7 @@ it_init_aliases (itp) #else itp->slist = (STRINGLIST *)NULL; #endif + free (alias_list); return 1; } @@ -1191,7 +1192,8 @@ gen_command_matches (cs, text, line, ind, lwords, nw, cw) tw = command_substitute (cscmd, 0); csbuf = tw ? tw->word : (char *)NULL; - dispose_word_desc (tw); + if (tw) + dispose_word_desc (tw); /* Now clean up and destroy everything. */ dispose_words (cmdlist); @@ -1431,6 +1433,7 @@ gen_compspec_completions (cs, cmd, word, start, end, foundp) { tcs = compspec_create (); tcs->actions = CA_DIRECTORY; + FREE (ret); ret = gen_action_completions (tcs, word); compspec_dispose (tcs); } @@ -1491,7 +1494,13 @@ gen_progcomp_completions (ocmd, cmd, word, start, end, foundp, retryp, lastcs) cs = progcomp_search (ocmd); if (cs == 0 || cs == *lastcs) - return (NULL); + { +#if 0 + if (foundp) + *foundp = 0; +#endif + return (NULL); + } if (*lastcs) compspec_dispose (*lastcs); diff --git a/pcomplete.c~ b/pcomplete.c~ index 522f765b4..34c232d46 100644 --- a/pcomplete.c~ +++ b/pcomplete.c~ @@ -83,6 +83,7 @@ extern char *strpbrk __P((char *, char *)); extern int array_needs_making; extern STRING_INT_ALIST word_token_alist[]; extern char *signal_names[]; +extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin; #if defined (DEBUG) #if defined (PREFER_STDARG) @@ -1190,7 +1191,8 @@ gen_command_matches (cs, text, line, ind, lwords, nw, cw) tw = command_substitute (cscmd, 0); csbuf = tw ? tw->word : (char *)NULL; - dispose_word_desc (tw); + if (tw) + dispose_word_desc (tw); /* Now clean up and destroy everything. */ dispose_words (cmdlist); @@ -1430,6 +1432,7 @@ gen_compspec_completions (cs, cmd, word, start, end, foundp) { tcs = compspec_create (); tcs->actions = CA_DIRECTORY; + FREE (ret); ret = gen_action_completions (tcs, word); compspec_dispose (tcs); } @@ -1490,7 +1493,13 @@ gen_progcomp_completions (ocmd, cmd, word, start, end, foundp, retryp, lastcs) cs = progcomp_search (ocmd); if (cs == 0 || cs == *lastcs) - return (NULL); + { +#if 0 + if (foundp) + *foundp = 0; +#endif + return (NULL); + } if (*lastcs) compspec_dispose (*lastcs); diff --git a/print_cmd.c b/print_cmd.c index e4590ebc7..0ccc3191b 100644 --- a/print_cmd.c +++ b/print_cmd.c @@ -972,7 +972,7 @@ print_deferred_heredocs (cstring) cprintf (" "); print_heredoc_header (hdtail); } - if (cstring[0] && (cstring[0] != ';' || cstring[1])) + if (cstring && cstring[0] && (cstring[0] != ';' || cstring[1])) cprintf ("%s", cstring); if (deferred_heredocs) cprintf ("\n"); @@ -1507,6 +1507,8 @@ cprintf (control, va_alist) } } + va_end (args); + the_printed_command[command_string_index] = '\0'; } diff --git a/print_cmd.c~ b/print_cmd.c~ index 3ceccc664..b9045321b 100644 --- a/print_cmd.c~ +++ b/print_cmd.c~ @@ -431,7 +431,7 @@ indirection_level_string () old = change_flag ('x', FLAG_OFF); ps4 = decode_prompt_string (ps4); - if (old == FLAG_ON) + if (old) change_flag ('x', FLAG_ON); if (ps4 == 0 || *ps4 == '\0') @@ -972,7 +972,7 @@ print_deferred_heredocs (cstring) cprintf (" "); print_heredoc_header (hdtail); } - if (cstring[0] && (cstring[0] != ';' || cstring[1])) + if (cstring && cstring[0] && (cstring[0] != ';' || cstring[1])) cprintf ("%s", cstring); if (deferred_heredocs) cprintf ("\n"); diff --git a/redir.c b/redir.c index 921be8cad..022da6800 100644 --- a/redir.c +++ b/redir.c @@ -115,7 +115,7 @@ redirection_error (temp, error) allocname = 0; if (temp->rflags & REDIR_VARASSIGN) - filename = savestring (temp->redirector.filename->word); + filename = allocname = savestring (temp->redirector.filename->word); else if (temp->redirector.dest < 0) /* This can happen when read_token_word encounters overflow, like in exec 4294967297>x */ diff --git a/redir.c~ b/redir.c~ new file mode 100644 index 000000000..921be8cad --- /dev/null +++ b/redir.c~ @@ -0,0 +1,1314 @@ +/* redir.c -- Functions to perform input and output redirection. */ + +/* Copyright (C) 1997-2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX) + #pragma alloca +#endif /* _AIX && RISC6000 && !__GNUC__ */ + +#include +#include "bashtypes.h" +#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif +#include "filecntl.h" +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include + +#if !defined (errno) +extern int errno; +#endif + +#include "bashansi.h" +#include "bashintl.h" +#include "memalloc.h" + +#define NEED_FPURGE_DECL + +#include "shell.h" +#include "flags.h" +#include "execute_cmd.h" +#include "redir.h" + +#if defined (BUFFERED_INPUT) +# include "input.h" +#endif + +#define SHELL_FD_BASE 10 + +int expanding_redir; + +extern int posixly_correct; +extern int last_command_exit_value; +extern REDIRECT *redirection_undo_list; +extern REDIRECT *exec_redirection_undo_list; + +/* Static functions defined and used in this file. */ +static void add_exec_redirect __P((REDIRECT *)); +static int add_undo_redirect __P((int, enum r_instruction, int)); +static int add_undo_close_redirect __P((int)); +static int expandable_redirection_filename __P((REDIRECT *)); +static int stdin_redirection __P((enum r_instruction, int)); +static int undoablefd __P((int)); +static int do_redirection_internal __P((REDIRECT *, int)); + +static int write_here_document __P((int, WORD_DESC *)); +static int write_here_string __P((int, WORD_DESC *)); +static int here_document_to_fd __P((WORD_DESC *, enum r_instruction)); + +static int redir_special_open __P((int, char *, int, int, enum r_instruction)); +static int noclobber_open __P((char *, int, int, enum r_instruction)); +static int redir_open __P((char *, int, int, enum r_instruction)); + +static int redir_varassign __P((REDIRECT *, int)); +static int redir_varvalue __P((REDIRECT *)); + +/* Spare redirector used when translating [N]>&WORD[-] or [N]<&WORD[-] to + a new redirection and when creating the redirection undo list. */ +static REDIRECTEE rd; + +/* Set to errno when a here document cannot be created for some reason. + Used to print a reasonable error message. */ +static int heredoc_errno; + +#define REDIRECTION_ERROR(r, e, fd) \ +do { \ + if ((r) < 0) \ + { \ + if (fd >= 0) \ + close (fd); \ + last_command_exit_value = EXECUTION_FAILURE;\ + return ((e) == 0 ? EINVAL : (e));\ + } \ +} while (0) + +void +redirection_error (temp, error) + REDIRECT *temp; + int error; +{ + char *filename, *allocname; + int oflags; + + allocname = 0; + if (temp->rflags & REDIR_VARASSIGN) + filename = savestring (temp->redirector.filename->word); + else if (temp->redirector.dest < 0) + /* This can happen when read_token_word encounters overflow, like in + exec 4294967297>x */ + filename = _("file descriptor out of range"); +#ifdef EBADF + /* This error can never involve NOCLOBBER */ + else if (error != NOCLOBBER_REDIRECT && temp->redirector.dest >= 0 && error == EBADF) + { + /* If we're dealing with two file descriptors, we have to guess about + which one is invalid; in the cases of r_{duplicating,move}_input and + r_{duplicating,move}_output we're here because dup2() failed. */ + switch (temp->instruction) + { + case r_duplicating_input: + case r_duplicating_output: + case r_move_input: + case r_move_output: + filename = allocname = itos (temp->redirectee.dest); + break; + case r_duplicating_input_word: + if (temp->redirector.dest == 0) /* Guess */ + filename = temp->redirectee.filename->word; /* XXX */ + else + filename = allocname = itos (temp->redirector.dest); + break; + case r_duplicating_output_word: + if (temp->redirector.dest == 1) /* Guess */ + filename = temp->redirectee.filename->word; /* XXX */ + else + filename = allocname = itos (temp->redirector.dest); + break; + default: + filename = allocname = itos (temp->redirector.dest); + break; + } + } +#endif + else if (expandable_redirection_filename (temp)) + { +expandable_filename: + if (posixly_correct && interactive_shell == 0) + { + oflags = temp->redirectee.filename->flags; + temp->redirectee.filename->flags |= W_NOGLOB; + } + filename = allocname = redirection_expand (temp->redirectee.filename); + if (posixly_correct && interactive_shell == 0) + temp->redirectee.filename->flags = oflags; + if (filename == 0) + filename = temp->redirectee.filename->word; + } + else if (temp->redirectee.dest < 0) + filename = "file descriptor out of range"; + else + filename = allocname = itos (temp->redirectee.dest); + + switch (error) + { + case AMBIGUOUS_REDIRECT: + internal_error (_("%s: ambiguous redirect"), filename); + break; + + case NOCLOBBER_REDIRECT: + internal_error (_("%s: cannot overwrite existing file"), filename); + break; + +#if defined (RESTRICTED_SHELL) + case RESTRICTED_REDIRECT: + internal_error (_("%s: restricted: cannot redirect output"), filename); + break; +#endif /* RESTRICTED_SHELL */ + + case HEREDOC_REDIRECT: + internal_error (_("cannot create temp file for here-document: %s"), strerror (heredoc_errno)); + break; + + case BADVAR_REDIRECT: + internal_error (_("%s: cannot assign fd to variable"), filename); + break; + + default: + internal_error ("%s: %s", filename, strerror (error)); + break; + } + + FREE (allocname); +} + +/* Perform the redirections on LIST. If flags & RX_ACTIVE, then actually + make input and output file descriptors, otherwise just do whatever is + neccessary for side effecting. flags & RX_UNDOABLE says to remember + how to undo the redirections later, if non-zero. If flags & RX_CLEXEC + is non-zero, file descriptors opened in do_redirection () have their + close-on-exec flag set. */ +int +do_redirections (list, flags) + REDIRECT *list; + int flags; +{ + int error; + REDIRECT *temp; + + if (flags & RX_UNDOABLE) + { + if (redirection_undo_list) + { + dispose_redirects (redirection_undo_list); + redirection_undo_list = (REDIRECT *)NULL; + } + if (exec_redirection_undo_list) + dispose_exec_redirects (); + } + + for (temp = list; temp; temp = temp->next) + { + error = do_redirection_internal (temp, flags); + if (error) + { + redirection_error (temp, error); + return (error); + } + } + return (0); +} + +/* Return non-zero if the redirection pointed to by REDIRECT has a + redirectee.filename that can be expanded. */ +static int +expandable_redirection_filename (redirect) + REDIRECT *redirect; +{ + switch (redirect->instruction) + { + case r_output_direction: + case r_appending_to: + case r_input_direction: + case r_inputa_direction: + case r_err_and_out: + case r_append_err_and_out: + case r_input_output: + case r_output_force: + case r_duplicating_input_word: + case r_duplicating_output_word: + case r_move_input_word: + case r_move_output_word: + return 1; + + default: + return 0; + } +} + +/* Expand the word in WORD returning a string. If WORD expands to + multiple words (or no words), then return NULL. */ +char * +redirection_expand (word) + WORD_DESC *word; +{ + char *result; + WORD_LIST *tlist1, *tlist2; + WORD_DESC *w; + + w = copy_word (word); + if (posixly_correct) + w->flags |= W_NOSPLIT; + + tlist1 = make_word_list (w, (WORD_LIST *)NULL); + expanding_redir = 1; + tlist2 = expand_words_no_vars (tlist1); + expanding_redir = 0; + dispose_words (tlist1); + + if (!tlist2 || tlist2->next) + { + /* We expanded to no words, or to more than a single word. + Dispose of the word list and return NULL. */ + if (tlist2) + dispose_words (tlist2); + return ((char *)NULL); + } + result = string_list (tlist2); /* XXX savestring (tlist2->word->word)? */ + dispose_words (tlist2); + return (result); +} + +static int +write_here_string (fd, redirectee) + int fd; + WORD_DESC *redirectee; +{ + char *herestr; + int herelen, n, e; + + expanding_redir = 1; + herestr = expand_string_to_string (redirectee->word, 0); + expanding_redir = 0; + herelen = STRLEN (herestr); + + n = write (fd, herestr, herelen); + if (n == herelen) + { + n = write (fd, "\n", 1); + herelen = 1; + } + e = errno; + FREE (herestr); + if (n != herelen) + { + if (e == 0) + e = ENOSPC; + return e; + } + return 0; +} + +/* Write the text of the here document pointed to by REDIRECTEE to the file + descriptor FD, which is already open to a temp file. Return 0 if the + write is successful, otherwise return errno. */ +static int +write_here_document (fd, redirectee) + int fd; + WORD_DESC *redirectee; +{ + char *document; + int document_len, fd2; + FILE *fp; + register WORD_LIST *t, *tlist; + + /* Expand the text if the word that was specified had + no quoting. The text that we expand is treated + exactly as if it were surrounded by double quotes. */ + + if (redirectee->flags & W_QUOTED) + { + document = redirectee->word; + document_len = strlen (document); + /* Set errno to something reasonable if the write fails. */ + if (write (fd, document, document_len) < document_len) + { + if (errno == 0) + errno = ENOSPC; + return (errno); + } + else + return 0; + } + + expanding_redir = 1; + tlist = expand_string (redirectee->word, Q_HERE_DOCUMENT); + expanding_redir = 0; + + if (tlist) + { + /* Try using buffered I/O (stdio) and writing a word + at a time, letting stdio do the work of buffering + for us rather than managing our own strings. Most + stdios are not particularly fast, however -- this + may need to be reconsidered later. */ + if ((fd2 = dup (fd)) < 0 || (fp = fdopen (fd2, "w")) == NULL) + { + if (fd2 >= 0) + close (fd2); + return (errno); + } + errno = 0; + for (t = tlist; t; t = t->next) + { + /* This is essentially the body of + string_list_internal expanded inline. */ + document = t->word->word; + document_len = strlen (document); + if (t != tlist) + putc (' ', fp); /* separator */ + fwrite (document, document_len, 1, fp); + if (ferror (fp)) + { + if (errno == 0) + errno = ENOSPC; + fd2 = errno; + fclose(fp); + dispose_words (tlist); + return (fd2); + } + } + dispose_words (tlist); + if (fclose (fp) != 0) + { + if (errno == 0) + errno = ENOSPC; + return (errno); + } + } + return 0; +} + +/* Create a temporary file holding the text of the here document pointed to + by REDIRECTEE, and return a file descriptor open for reading to the temp + file. Return -1 on any error, and make sure errno is set appropriately. */ +static int +here_document_to_fd (redirectee, ri) + WORD_DESC *redirectee; + enum r_instruction ri; +{ + char *filename; + int r, fd, fd2; + + fd = sh_mktmpfd ("sh-thd", MT_USERANDOM|MT_USETMPDIR, &filename); + + /* If we failed for some reason other than the file existing, abort */ + if (fd < 0) + { + FREE (filename); + return (fd); + } + + errno = r = 0; /* XXX */ + /* write_here_document returns 0 on success, errno on failure. */ + if (redirectee->word) + r = (ri != r_reading_string) ? write_here_document (fd, redirectee) + : write_here_string (fd, redirectee); + + if (r) + { + close (fd); + unlink (filename); + free (filename); + errno = r; + return (-1); + } + + /* In an attempt to avoid races, we close the first fd only after opening + the second. */ + /* Make the document really temporary. Also make it the input. */ + fd2 = open (filename, O_RDONLY|O_BINARY, 0600); + + if (fd2 < 0) + { + r = errno; + unlink (filename); + free (filename); + close (fd); + errno = r; + return -1; + } + + close (fd); + if (unlink (filename) < 0) + { + r = errno; + close (fd2); + free (filename); + errno = r; + return (-1); + } + + free (filename); + return (fd2); +} + +#define RF_DEVFD 1 +#define RF_DEVSTDERR 2 +#define RF_DEVSTDIN 3 +#define RF_DEVSTDOUT 4 +#define RF_DEVTCP 5 +#define RF_DEVUDP 6 + +/* A list of pattern/value pairs for filenames that the redirection + code handles specially. */ +static STRING_INT_ALIST _redir_special_filenames[] = { +#if !defined (HAVE_DEV_FD) + { "/dev/fd/[0-9]*", RF_DEVFD }, +#endif +#if !defined (HAVE_DEV_STDIN) + { "/dev/stderr", RF_DEVSTDERR }, + { "/dev/stdin", RF_DEVSTDIN }, + { "/dev/stdout", RF_DEVSTDOUT }, +#endif +#if defined (NETWORK_REDIRECTIONS) + { "/dev/tcp/*/*", RF_DEVTCP }, + { "/dev/udp/*/*", RF_DEVUDP }, +#endif + { (char *)NULL, -1 } +}; + +static int +redir_special_open (spec, filename, flags, mode, ri) + int spec; + char *filename; + int flags, mode; + enum r_instruction ri; +{ + int fd; +#if !defined (HAVE_DEV_FD) + intmax_t lfd; +#endif + + fd = -1; + switch (spec) + { +#if !defined (HAVE_DEV_FD) + case RF_DEVFD: + if (all_digits (filename+8) && legal_number (filename+8, &lfd) && lfd == (int)lfd) + { + fd = lfd; + fd = fcntl (fd, F_DUPFD, SHELL_FD_BASE); + } + else + fd = AMBIGUOUS_REDIRECT; + break; +#endif + +#if !defined (HAVE_DEV_STDIN) + case RF_DEVSTDIN: + fd = fcntl (0, F_DUPFD, SHELL_FD_BASE); + break; + case RF_DEVSTDOUT: + fd = fcntl (1, F_DUPFD, SHELL_FD_BASE); + break; + case RF_DEVSTDERR: + fd = fcntl (2, F_DUPFD, SHELL_FD_BASE); + break; +#endif + +#if defined (NETWORK_REDIRECTIONS) + case RF_DEVTCP: + case RF_DEVUDP: +#if defined (HAVE_NETWORK) + fd = netopen (filename); +#else + internal_warning (_("/dev/(tcp|udp)/host/port not supported without networking")); + fd = open (filename, flags, mode); +#endif + break; +#endif /* NETWORK_REDIRECTIONS */ + } + + return fd; +} + +/* Open FILENAME with FLAGS in noclobber mode, hopefully avoiding most + race conditions and avoiding the problem where the file is replaced + between the stat(2) and open(2). */ +static int +noclobber_open (filename, flags, mode, ri) + char *filename; + int flags, mode; + enum r_instruction ri; +{ + int r, fd; + struct stat finfo, finfo2; + + /* If the file exists and is a regular file, return an error + immediately. */ + r = stat (filename, &finfo); + if (r == 0 && (S_ISREG (finfo.st_mode))) + return (NOCLOBBER_REDIRECT); + + /* If the file was not present (r != 0), make sure we open it + exclusively so that if it is created before we open it, our open + will fail. Make sure that we do not truncate an existing file. + Note that we don't turn on O_EXCL unless the stat failed -- if + the file was not a regular file, we leave O_EXCL off. */ + flags &= ~O_TRUNC; + if (r != 0) + { + fd = open (filename, flags|O_EXCL, mode); + return ((fd < 0 && errno == EEXIST) ? NOCLOBBER_REDIRECT : fd); + } + fd = open (filename, flags, mode); + + /* If the open failed, return the file descriptor right away. */ + if (fd < 0) + return (errno == EEXIST ? NOCLOBBER_REDIRECT : fd); + + /* OK, the open succeeded, but the file may have been changed from a + non-regular file to a regular file between the stat and the open. + We are assuming that the O_EXCL open handles the case where FILENAME + did not exist and is symlinked to an existing file between the stat + and open. */ + + /* If we can open it and fstat the file descriptor, and neither check + revealed that it was a regular file, and the file has not been replaced, + return the file descriptor. */ + if ((fstat (fd, &finfo2) == 0) && (S_ISREG (finfo2.st_mode) == 0) && + r == 0 && (S_ISREG (finfo.st_mode) == 0) && + same_file (filename, filename, &finfo, &finfo2)) + return fd; + + /* The file has been replaced. badness. */ + close (fd); + errno = EEXIST; + return (NOCLOBBER_REDIRECT); +} + +static int +redir_open (filename, flags, mode, ri) + char *filename; + int flags, mode; + enum r_instruction ri; +{ + int fd, r; + + r = find_string_in_alist (filename, _redir_special_filenames, 1); + if (r >= 0) + return (redir_special_open (r, filename, flags, mode, ri)); + + /* If we are in noclobber mode, you are not allowed to overwrite + existing files. Check before opening. */ + if (noclobber && CLOBBERING_REDIRECT (ri)) + { + fd = noclobber_open (filename, flags, mode, ri); + if (fd == NOCLOBBER_REDIRECT) + return (NOCLOBBER_REDIRECT); + } + else + { + fd = open (filename, flags, mode); +#if defined (AFS) + if ((fd < 0) && (errno == EACCES)) + { + fd = open (filename, flags & ~O_CREAT, mode); + errno = EACCES; /* restore errno */ + } +#endif /* AFS */ + } + + return fd; +} + +static int +undoablefd (fd) + int fd; +{ + int clexec; + + clexec = fcntl (fd, F_GETFD, 0); + if (clexec == -1 || (fd >= SHELL_FD_BASE && clexec == 1)) + return 0; + return 1; +} + +/* Do the specific redirection requested. Returns errno or one of the + special redirection errors (*_REDIRECT) in case of error, 0 on success. + If flags & RX_ACTIVE is zero, then just do whatever is neccessary to + produce the appropriate side effects. flags & RX_UNDOABLE, if non-zero, + says to remember how to undo each redirection. If flags & RX_CLEXEC is + non-zero, then we set all file descriptors > 2 that we open to be + close-on-exec. */ +static int +do_redirection_internal (redirect, flags) + REDIRECT *redirect; + int flags; +{ + WORD_DESC *redirectee; + int redir_fd, fd, redirector, r, oflags; + intmax_t lfd; + char *redirectee_word; + enum r_instruction ri; + REDIRECT *new_redirect; + REDIRECTEE sd; + + redirectee = redirect->redirectee.filename; + redir_fd = redirect->redirectee.dest; + redirector = redirect->redirector.dest; + ri = redirect->instruction; + + if (redirect->flags & RX_INTERNAL) + flags |= RX_INTERNAL; + + if (TRANSLATE_REDIRECT (ri)) + { + /* We have [N]>&WORD[-] or [N]<&WORD[-] (or {V}>&WORD[-] or {V}<&WORD-). + and WORD, then translate the redirection into a new one and + continue. */ + redirectee_word = redirection_expand (redirectee); + + /* XXX - what to do with [N]<&$w- where w is unset or null? ksh93 + closes N. */ + if (redirectee_word == 0) + return (AMBIGUOUS_REDIRECT); + else if (redirectee_word[0] == '-' && redirectee_word[1] == '\0') + { + sd = redirect->redirector; + rd.dest = 0; + new_redirect = make_redirection (sd, r_close_this, rd, 0); + } + else if (all_digits (redirectee_word)) + { + sd = redirect->redirector; + if (legal_number (redirectee_word, &lfd) && (int)lfd == lfd) + rd.dest = lfd; + else + rd.dest = -1; /* XXX */ + switch (ri) + { + case r_duplicating_input_word: + new_redirect = make_redirection (sd, r_duplicating_input, rd, 0); + break; + case r_duplicating_output_word: + new_redirect = make_redirection (sd, r_duplicating_output, rd, 0); + break; + case r_move_input_word: + new_redirect = make_redirection (sd, r_move_input, rd, 0); + break; + case r_move_output_word: + new_redirect = make_redirection (sd, r_move_output, rd, 0); + break; + } + } + else if (ri == r_duplicating_output_word && (redirect->rflags & REDIR_VARASSIGN) == 0 && redirector == 1) + { + sd = redirect->redirector; + rd.filename = make_bare_word (redirectee_word); + new_redirect = make_redirection (sd, r_err_and_out, rd, 0); + } + else + { + free (redirectee_word); + return (AMBIGUOUS_REDIRECT); + } + + free (redirectee_word); + + /* Set up the variables needed by the rest of the function from the + new redirection. */ + if (new_redirect->instruction == r_err_and_out) + { + char *alloca_hack; + + /* Copy the word without allocating any memory that must be + explicitly freed. */ + redirectee = (WORD_DESC *)alloca (sizeof (WORD_DESC)); + xbcopy ((char *)new_redirect->redirectee.filename, + (char *)redirectee, sizeof (WORD_DESC)); + + alloca_hack = (char *) + alloca (1 + strlen (new_redirect->redirectee.filename->word)); + redirectee->word = alloca_hack; + strcpy (redirectee->word, new_redirect->redirectee.filename->word); + } + else + /* It's guaranteed to be an integer, and shouldn't be freed. */ + redirectee = new_redirect->redirectee.filename; + + redir_fd = new_redirect->redirectee.dest; + redirector = new_redirect->redirector.dest; + ri = new_redirect->instruction; + + /* Overwrite the flags element of the old redirect with the new value. */ + redirect->flags = new_redirect->flags; + dispose_redirects (new_redirect); + } + + switch (ri) + { + case r_output_direction: + case r_appending_to: + case r_input_direction: + case r_inputa_direction: + case r_err_and_out: /* command &>filename */ + case r_append_err_and_out: /* command &>> filename */ + case r_input_output: + case r_output_force: + if (posixly_correct && interactive_shell == 0) + { + oflags = redirectee->flags; + redirectee->flags |= W_NOGLOB; + } + redirectee_word = redirection_expand (redirectee); + if (posixly_correct && interactive_shell == 0) + redirectee->flags = oflags; + + if (redirectee_word == 0) + return (AMBIGUOUS_REDIRECT); + +#if defined (RESTRICTED_SHELL) + if (restricted && (WRITE_REDIRECT (ri))) + { + free (redirectee_word); + return (RESTRICTED_REDIRECT); + } +#endif /* RESTRICTED_SHELL */ + + fd = redir_open (redirectee_word, redirect->flags, 0666, ri); + free (redirectee_word); + + if (fd == NOCLOBBER_REDIRECT) + return (fd); + + if (fd < 0) + return (errno); + + if (flags & RX_ACTIVE) + { + if (redirect->rflags & REDIR_VARASSIGN) + { + redirector = fcntl (fd, F_DUPFD, SHELL_FD_BASE); /* XXX try this for now */ + r = errno; + if (redirector < 0) + sys_error (_("redirection error: cannot duplicate fd")); + REDIRECTION_ERROR (redirector, r, fd); + } + + if (flags & RX_UNDOABLE) + { + /* Only setup to undo it if the thing to undo is active. */ + if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1)) + r = add_undo_redirect (redirector, ri, -1); + else + r = add_undo_close_redirect (redirector); + if (r < 0 && (redirect->rflags & REDIR_VARASSIGN)) + close (redirector); + REDIRECTION_ERROR (r, errno, fd); + } + +#if defined (BUFFERED_INPUT) + check_bash_input (redirector); +#endif + + /* Make sure there is no pending output before we change the state + of the underlying file descriptor, since the builtins use stdio + for output. */ + if (redirector == 1 && fileno (stdout) == redirector) + { + fflush (stdout); + fpurge (stdout); + } + else if (redirector == 2 && fileno (stderr) == redirector) + { + fflush (stderr); + fpurge (stderr); + } + + if (redirect->rflags & REDIR_VARASSIGN) + { + if ((r = redir_varassign (redirect, redirector)) < 0) + { + close (redirector); + close (fd); + return (r); /* XXX */ + } + } + else if ((fd != redirector) && (dup2 (fd, redirector) < 0)) + return (errno); + +#if defined (BUFFERED_INPUT) + /* Do not change the buffered stream for an implicit redirection + of /dev/null to fd 0 for asynchronous commands without job + control (r_inputa_direction). */ + if (ri == r_input_direction || ri == r_input_output) + duplicate_buffered_stream (fd, redirector); +#endif /* BUFFERED_INPUT */ + + /* + * If we're remembering, then this is the result of a while, for + * or until loop with a loop redirection, or a function/builtin + * executing in the parent shell with a redirection. In the + * function/builtin case, we want to set all file descriptors > 2 + * to be close-on-exec to duplicate the effect of the old + * for i = 3 to NOFILE close(i) loop. In the case of the loops, + * both sh and ksh leave the file descriptors open across execs. + * The Posix standard mentions only the exec builtin. + */ + if ((flags & RX_CLEXEC) && (redirector > 2)) + SET_CLOSE_ON_EXEC (redirector); + } + + if (fd != redirector) + { +#if defined (BUFFERED_INPUT) + if (INPUT_REDIRECT (ri)) + close_buffered_fd (fd); + else +#endif /* !BUFFERED_INPUT */ + close (fd); /* Don't close what we just opened! */ + } + + /* If we are hacking both stdout and stderr, do the stderr + redirection here. XXX - handle {var} here? */ + if (ri == r_err_and_out || ri == r_append_err_and_out) + { + if (flags & RX_ACTIVE) + { + if (flags & RX_UNDOABLE) + add_undo_redirect (2, ri, -1); + if (dup2 (1, 2) < 0) + return (errno); + } + } + break; + + case r_reading_until: + case r_deblank_reading_until: + case r_reading_string: + /* REDIRECTEE is a pointer to a WORD_DESC containing the text of + the new input. Place it in a temporary file. */ + if (redirectee) + { + fd = here_document_to_fd (redirectee, ri); + + if (fd < 0) + { + heredoc_errno = errno; + return (HEREDOC_REDIRECT); + } + + if (redirect->rflags & REDIR_VARASSIGN) + { + redirector = fcntl (fd, F_DUPFD, SHELL_FD_BASE); /* XXX try this for now */ + r = errno; + if (redirector < 0) + sys_error (_("redirection error: cannot duplicate fd")); + REDIRECTION_ERROR (redirector, r, fd); + } + + if (flags & RX_ACTIVE) + { + if (flags & RX_UNDOABLE) + { + /* Only setup to undo it if the thing to undo is active. */ + if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1)) + r = add_undo_redirect (redirector, ri, -1); + else + r = add_undo_close_redirect (redirector); + if (r < 0 && (redirect->rflags & REDIR_VARASSIGN)) + close (redirector); + REDIRECTION_ERROR (r, errno, fd); + } + +#if defined (BUFFERED_INPUT) + check_bash_input (redirector); +#endif + if (redirect->rflags & REDIR_VARASSIGN) + { + if ((r = redir_varassign (redirect, redirector)) < 0) + { + close (redirector); + close (fd); + return (r); /* XXX */ + } + } + else if (fd != redirector && dup2 (fd, redirector) < 0) + { + r = errno; + close (fd); + return (r); + } + +#if defined (BUFFERED_INPUT) + duplicate_buffered_stream (fd, redirector); +#endif + + if ((flags & RX_CLEXEC) && (redirector > 2)) + SET_CLOSE_ON_EXEC (redirector); + } + + if (fd != redirector) +#if defined (BUFFERED_INPUT) + close_buffered_fd (fd); +#else + close (fd); +#endif + } + break; + + case r_duplicating_input: + case r_duplicating_output: + case r_move_input: + case r_move_output: + if ((flags & RX_ACTIVE) && (redirect->rflags & REDIR_VARASSIGN)) + { + redirector = fcntl (redir_fd, F_DUPFD, SHELL_FD_BASE); /* XXX try this for now */ + r = errno; + if (redirector < 0) + sys_error (_("redirection error: cannot duplicate fd")); + REDIRECTION_ERROR (redirector, r, -1); + } + + if ((flags & RX_ACTIVE) && (redir_fd != redirector)) + { + if (flags & RX_UNDOABLE) + { + /* Only setup to undo it if the thing to undo is active. */ + if (fcntl (redirector, F_GETFD, 0) != -1) + r = add_undo_redirect (redirector, ri, redir_fd); + else + r = add_undo_close_redirect (redirector); + if (r < 0 && (redirect->rflags & REDIR_VARASSIGN)) + close (redirector); + REDIRECTION_ERROR (r, errno, -1); + } +#if defined (BUFFERED_INPUT) + check_bash_input (redirector); +#endif + if (redirect->rflags & REDIR_VARASSIGN) + { + if ((r = redir_varassign (redirect, redirector)) < 0) + { + close (redirector); + return (r); /* XXX */ + } + } + /* This is correct. 2>&1 means dup2 (1, 2); */ + else if (dup2 (redir_fd, redirector) < 0) + return (errno); + +#if defined (BUFFERED_INPUT) + if (ri == r_duplicating_input || ri == r_move_input) + duplicate_buffered_stream (redir_fd, redirector); +#endif /* BUFFERED_INPUT */ + + /* First duplicate the close-on-exec state of redirectee. dup2 + leaves the flag unset on the new descriptor, which means it + stays open. Only set the close-on-exec bit for file descriptors + greater than 2 in any case, since 0-2 should always be open + unless closed by something like `exec 2<&-'. It should always + be safe to set fds > 2 to close-on-exec if they're being used to + save file descriptors < 2, since we don't need to preserve the + state of the close-on-exec flag for those fds -- they should + always be open. */ + /* if ((already_set || set_unconditionally) && (ok_to_set)) + set_it () */ +#if 0 + if (((fcntl (redir_fd, F_GETFD, 0) == 1) || redir_fd < 2 || (flags & RX_CLEXEC)) && + (redirector > 2)) +#else + if (((fcntl (redir_fd, F_GETFD, 0) == 1) || (redir_fd < 2 && (flags & RX_INTERNAL)) || (flags & RX_CLEXEC)) && + (redirector > 2)) +#endif + SET_CLOSE_ON_EXEC (redirector); + + /* When undoing saving of non-standard file descriptors (>=3) using + file descriptors >= SHELL_FD_BASE, we set the saving fd to be + close-on-exec and use a flag to decide how to set close-on-exec + when the fd is restored. */ + if ((redirect->flags & RX_INTERNAL) && (redirect->flags & RX_SAVCLEXEC) && redirector >= 3 && redir_fd >= SHELL_FD_BASE) + SET_OPEN_ON_EXEC (redirector); + + /* dup-and-close redirection */ + if (ri == r_move_input || ri == r_move_output) + { + xtrace_fdchk (redir_fd); + + close (redir_fd); +#if defined (COPROCESS_SUPPORT) + coproc_fdchk (redir_fd); /* XXX - loses coproc fds */ +#endif + } + } + break; + + case r_close_this: + if (flags & RX_ACTIVE) + { + if (redirect->rflags & REDIR_VARASSIGN) + { + redirector = redir_varvalue (redirect); + if (redirector < 0) + return AMBIGUOUS_REDIRECT; + } + + r = 0; + if ((flags & RX_UNDOABLE) && (fcntl (redirector, F_GETFD, 0) != -1)) + { + r = add_undo_redirect (redirector, ri, -1); + REDIRECTION_ERROR (r, errno, redirector); + } + +#if defined (COPROCESS_SUPPORT) + coproc_fdchk (redirector); +#endif + xtrace_fdchk (redirector); + +#if defined (BUFFERED_INPUT) + check_bash_input (redirector); + close_buffered_fd (redirector); +#else /* !BUFFERED_INPUT */ + close (redirector); +#endif /* !BUFFERED_INPUT */ + } + break; + + case r_duplicating_input_word: + case r_duplicating_output_word: + break; + } + return (0); +} + +/* Remember the file descriptor associated with the slot FD, + on REDIRECTION_UNDO_LIST. Note that the list will be reversed + before it is executed. Any redirections that need to be undone + even if REDIRECTION_UNDO_LIST is discarded by the exec builtin + are also saved on EXEC_REDIRECTION_UNDO_LIST. FDBASE says where to + start the duplicating. If it's less than SHELL_FD_BASE, we're ok, + and can use SHELL_FD_BASE (-1 == don't care). If it's >= SHELL_FD_BASE, + we have to make sure we don't use fdbase to save a file descriptor, + since we're going to use it later (e.g., make sure we don't save fd 0 + to fd 10 if we have a redirection like 0<&10). If the value of fdbase + puts the process over its fd limit, causing fcntl to fail, we try + again with SHELL_FD_BASE. Return 0 on success, -1 on error. */ +static int +add_undo_redirect (fd, ri, fdbase) + int fd; + enum r_instruction ri; + int fdbase; +{ + int new_fd, clexec_flag; + REDIRECT *new_redirect, *closer, *dummy_redirect; + REDIRECTEE sd; + + new_fd = fcntl (fd, F_DUPFD, (fdbase < SHELL_FD_BASE) ? SHELL_FD_BASE : fdbase+1); + if (new_fd < 0) + new_fd = fcntl (fd, F_DUPFD, SHELL_FD_BASE); + + if (new_fd < 0) + { + sys_error (_("redirection error: cannot duplicate fd")); + return (-1); + } + + clexec_flag = fcntl (fd, F_GETFD, 0); + + sd.dest = new_fd; + rd.dest = 0; + closer = make_redirection (sd, r_close_this, rd, 0); + closer->flags |= RX_INTERNAL; + dummy_redirect = copy_redirects (closer); + + sd.dest = fd; + rd.dest = new_fd; + if (fd == 0) + new_redirect = make_redirection (sd, r_duplicating_input, rd, 0); + else + new_redirect = make_redirection (sd, r_duplicating_output, rd, 0); + new_redirect->flags |= RX_INTERNAL; + if (clexec_flag == 0 && fd >= 3 && new_fd >= SHELL_FD_BASE) + new_redirect->flags |= RX_SAVCLEXEC; + new_redirect->next = closer; + + closer->next = redirection_undo_list; + redirection_undo_list = new_redirect; + + /* Save redirections that need to be undone even if the undo list + is thrown away by the `exec' builtin. */ + add_exec_redirect (dummy_redirect); + + /* experimental: if we're saving a redirection to undo for a file descriptor + above SHELL_FD_BASE, add a redirection to be undone if the exec builtin + causes redirections to be discarded. There needs to be a difference + between fds that are used to save other fds and then are the target of + user redirections and fds that are just the target of user redirections. + We use the close-on-exec flag to tell the difference; fds > SHELL_FD_BASE + that have the close-on-exec flag set are assumed to be fds used internally + to save others. */ + if (fd >= SHELL_FD_BASE && ri != r_close_this && clexec_flag) + { + sd.dest = fd; + rd.dest = new_fd; + new_redirect = make_redirection (sd, r_duplicating_output, rd, 0); + new_redirect->flags |= RX_INTERNAL; + + add_exec_redirect (new_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); + else if (redirection_undo_list->flags & RX_SAVCLEXEC) + SET_CLOSE_ON_EXEC (new_fd); + + return (0); +} + +/* Set up to close FD when we are finished with the current command + and its redirections. Return 0 on success, -1 on error. */ +static int +add_undo_close_redirect (fd) + int fd; +{ + REDIRECT *closer; + REDIRECTEE sd; + + sd.dest = fd; + rd.dest = 0; + closer = make_redirection (sd, r_close_this, rd, 0); + closer->flags |= RX_INTERNAL; + closer->next = redirection_undo_list; + redirection_undo_list = closer; + + return 0; +} + +static void +add_exec_redirect (dummy_redirect) + REDIRECT *dummy_redirect; +{ + dummy_redirect->next = exec_redirection_undo_list; + exec_redirection_undo_list = dummy_redirect; +} + +/* Return 1 if the redirection specified by RI and REDIRECTOR alters the + standard input. */ +static int +stdin_redirection (ri, redirector) + enum r_instruction ri; + int redirector; +{ + switch (ri) + { + case r_input_direction: + case r_inputa_direction: + case r_input_output: + case r_reading_until: + case r_deblank_reading_until: + case r_reading_string: + return (1); + case r_duplicating_input: + case r_duplicating_input_word: + case r_close_this: + return (redirector == 0); + case r_output_direction: + case r_appending_to: + case r_duplicating_output: + case r_err_and_out: + case r_append_err_and_out: + case r_output_force: + case r_duplicating_output_word: + return (0); + } + return (0); +} + +/* Return non-zero if any of the redirections in REDIRS alter the standard + input. */ +int +stdin_redirects (redirs) + REDIRECT *redirs; +{ + REDIRECT *rp; + int n; + + for (n = 0, rp = redirs; rp; rp = rp->next) + if ((rp->rflags & REDIR_VARASSIGN) == 0) + n += stdin_redirection (rp->instruction, rp->redirector.dest); + return n; +} + +/* These don't yet handle array references */ +static int +redir_varassign (redir, fd) + REDIRECT *redir; + int fd; +{ + WORD_DESC *w; + SHELL_VAR *v; + + w = redir->redirector.filename; + v = bind_var_to_int (w->word, fd); + if (v == 0 || readonly_p (v) || noassign_p (v)) + return BADVAR_REDIRECT; + + return 0; +} + +static int +redir_varvalue (redir) + REDIRECT *redir; +{ + SHELL_VAR *v; + char *val; + intmax_t vmax; + int i; + + /* XXX - handle set -u here? */ + v = find_variable (redir->redirector.filename->word); + if (v == 0 || invisible_p (v)) + return -1; + + val = get_variable_value (v); + if (val == 0 || *val == 0) + return -1; + + if (legal_number (val, &vmax) < 0) + return -1; + + i = vmax; /* integer truncation */ + return i; +} diff --git a/stringlib.c b/stringlib.c index 0a612ca90..59670a920 100644 --- a/stringlib.c +++ b/stringlib.c @@ -158,7 +158,7 @@ strsub (string, pat, rep, global) if (replen) RESIZE_MALLOCED_BUFFER (temp, templen, replen, tempsize, (replen * 2)); - for (r = rep; *r; ) + for (r = rep; *r; ) /* can rep == "" */ temp[templen++] = *r++; i += patlen ? patlen : 1; /* avoid infinite recursion */ diff --git a/stringlib.c~ b/stringlib.c~ new file mode 100644 index 000000000..0a612ca90 --- /dev/null +++ b/stringlib.c~ @@ -0,0 +1,287 @@ +/* stringlib.c - Miscellaneous string functions. */ + +/* Copyright (C) 1996-2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include +#include "chartypes.h" + +#include "shell.h" +#include "pathexp.h" + +#include + +#if defined (EXTENDED_GLOB) +# include +#endif + +/* **************************************************************** */ +/* */ +/* Functions to manage arrays of strings */ +/* */ +/* **************************************************************** */ + +/* Find STRING in ALIST, a list of string key/int value pairs. If FLAGS + is 1, STRING is treated as a pattern and matched using strmatch. */ +int +find_string_in_alist (string, alist, flags) + char *string; + STRING_INT_ALIST *alist; + int flags; +{ + register int i; + int r; + + for (i = r = 0; alist[i].word; i++) + { +#if defined (EXTENDED_GLOB) + if (flags) + r = strmatch (alist[i].word, string, FNM_EXTMATCH) != FNM_NOMATCH; + else +#endif + r = STREQ (string, alist[i].word); + + if (r) + return (alist[i].token); + } + return -1; +} + +/* Find TOKEN in ALIST, a list of string/int value pairs. Return the + corresponding string. Allocates memory for the returned + string. FLAGS is currently ignored, but reserved. */ +char * +find_token_in_alist (token, alist, flags) + int token; + STRING_INT_ALIST *alist; + int flags; +{ + register int i; + + for (i = 0; alist[i].word; i++) + { + if (alist[i].token == token) + return (savestring (alist[i].word)); + } + return ((char *)NULL); +} + +int +find_index_in_alist (string, alist, flags) + char *string; + STRING_INT_ALIST *alist; + int flags; +{ + register int i; + int r; + + for (i = r = 0; alist[i].word; i++) + { +#if defined (EXTENDED_GLOB) + if (flags) + r = strmatch (alist[i].word, string, FNM_EXTMATCH) != FNM_NOMATCH; + else +#endif + r = STREQ (string, alist[i].word); + + if (r) + return (i); + } + + return -1; +} + +/* **************************************************************** */ +/* */ +/* String Management Functions */ +/* */ +/* **************************************************************** */ + +/* Cons a new string from STRING starting at START and ending at END, + not including END. */ +char * +substring (string, start, end) + const char *string; + int start, end; +{ + register int len; + register char *result; + + len = end - start; + result = (char *)xmalloc (len + 1); + strncpy (result, string + start, len); + result[len] = '\0'; + return (result); +} + +/* Replace occurrences of PAT with REP in STRING. If GLOBAL is non-zero, + replace all occurrences, otherwise replace only the first. + This returns a new string; the caller should free it. */ +char * +strsub (string, pat, rep, global) + char *string, *pat, *rep; + int global; +{ + int patlen, replen, templen, tempsize, repl, i; + char *temp, *r; + + patlen = strlen (pat); + replen = strlen (rep); + for (temp = (char *)NULL, i = templen = tempsize = 0, repl = 1; string[i]; ) + { + if (repl && STREQN (string + i, pat, patlen)) + { + if (replen) + RESIZE_MALLOCED_BUFFER (temp, templen, replen, tempsize, (replen * 2)); + + for (r = rep; *r; ) + temp[templen++] = *r++; + + i += patlen ? patlen : 1; /* avoid infinite recursion */ + repl = global != 0; + } + else + { + RESIZE_MALLOCED_BUFFER (temp, templen, 1, tempsize, 16); + temp[templen++] = string[i++]; + } + } + if (temp) + temp[templen] = 0; + else + temp = savestring (string); + return (temp); +} + +/* Replace all instances of C in STRING with TEXT. TEXT may be empty or + NULL. If DO_GLOB is non-zero, we quote the replacement text for + globbing. Backslash may be used to quote C. */ +char * +strcreplace (string, c, text, do_glob) + char *string; + int c; + char *text; + int do_glob; +{ + char *ret, *p, *r, *t; + int len, rlen, ind, tlen; + + len = STRLEN (text); + rlen = len + strlen (string) + 2; + ret = (char *)xmalloc (rlen); + + for (p = string, r = ret; p && *p; ) + { + if (*p == c) + { + if (len) + { + ind = r - ret; + if (do_glob && (glob_pattern_p (text) || strchr (text, '\\'))) + { + t = quote_globbing_chars (text); + tlen = strlen (t); + RESIZE_MALLOCED_BUFFER (ret, ind, tlen, rlen, rlen); + r = ret + ind; /* in case reallocated */ + strcpy (r, t); + r += tlen; + free (t); + } + else + { + RESIZE_MALLOCED_BUFFER (ret, ind, len, rlen, rlen); + r = ret + ind; /* in case reallocated */ + strcpy (r, text); + r += len; + } + } + p++; + continue; + } + + if (*p == '\\' && p[1] == c) + p++; + + ind = r - ret; + RESIZE_MALLOCED_BUFFER (ret, ind, 2, rlen, rlen); + r = ret + ind; /* in case reallocated */ + *r++ = *p++; + } + *r = '\0'; + + return ret; +} + +#ifdef INCLUDE_UNUSED +/* Remove all leading whitespace from STRING. This includes + newlines. STRING should be terminated with a zero. */ +void +strip_leading (string) + char *string; +{ + char *start = string; + + while (*string && (whitespace (*string) || *string == '\n')) + string++; + + if (string != start) + { + int len = strlen (string); + FASTCOPY (string, start, len); + start[len] = '\0'; + } +} +#endif + +/* Remove all trailing whitespace from STRING. This includes + newlines. If NEWLINES_ONLY is non-zero, only trailing newlines + are removed. STRING should be terminated with a zero. */ +void +strip_trailing (string, len, newlines_only) + char *string; + int len; + int newlines_only; +{ + while (len >= 0) + { + if ((newlines_only && string[len] == '\n') || + (!newlines_only && whitespace (string[len]))) + len--; + else + break; + } + string[len + 1] = '\0'; +} + +/* A wrapper for bcopy that can be prototyped in general.h */ +void +xbcopy (s, d, n) + char *s, *d; + int n; +{ + FASTCOPY (s, d, n); +} diff --git a/subst.c b/subst.c index 32e9cfc04..f53e8e512 100644 --- a/subst.c +++ b/subst.c @@ -1772,6 +1772,7 @@ skip_to_delim (string, start, delims, flags) if (string[si] == '\0') CQ_RETURN(si); temp = extract_process_subst (string, (c == '<') ? "<(" : ">(", &si); + free (temp); /* no SX_ALLOC here */ i = si; if (string[i] == '\0') break; @@ -2070,6 +2071,8 @@ split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp) if (cwp) *cwp = cw; + FREE (d2); + return (REVERSE_LIST (ret, WORD_LIST *)); } #endif /* READLINE */ @@ -5524,9 +5527,11 @@ array_length_reference (s) if (akey == 0 || *akey == 0) { err_badarraysub (t); + FREE (akey); return (-1); } t = assoc_reference (assoc_cell (var), akey); + free (akey); } else { @@ -5676,6 +5681,7 @@ parameter_brace_expand_word (name, var_is_special, quoted, pflags, indp) #if defined (ARRAY_VARS) else if (valid_array_reference (name)) { + /* XXX - does this leak if name[@] or name[*]? */ temp = array_value (name, quoted, 0, &atype, &ind); if (atype == 0 && temp) { @@ -6327,7 +6333,11 @@ parameter_brace_substring (varname, value, ind, substr, quoted, flags) r = verify_substring_values (v, val, substr, vtype, &e1, &e2); this_command_name = oname; if (r <= 0) - return ((r == 0) ? &expand_param_error : (char *)NULL); + { + if (vtype == VT_VARIABLE) + FREE (val); + return ((r == 0) ? &expand_param_error : (char *)NULL); + } switch (vtype) { @@ -6956,9 +6966,8 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta (sindex == t_index - 1 && string[sindex] == '!' && VALID_INDIR_PARAM (string[t_index]))) { t_index++; - free (name); temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0); - name = (char *)xmalloc (3 + (strlen (temp1))); + name = (char *)xrealloc (name, 3 + (strlen (temp1))); *name = string[sindex]; if (string[sindex] == '!') { @@ -7106,6 +7115,8 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta free (temp1); *indexp = sindex; + free (name); + ret = alloc_word_desc (); ret->word = temp; return ret; @@ -7236,8 +7247,6 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta else if (temp1 == &expand_param_fatal) return (&expand_wdesc_fatal); - ret = alloc_word_desc (); - ret->word = temp1; ret = alloc_word_desc (); ret->word = temp1; if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) diff --git a/subst.c~ b/subst.c~ new file mode 100644 index 000000000..c47975715 --- /dev/null +++ b/subst.c~ @@ -0,0 +1,9410 @@ +/* subst.c -- The part of the shell that does parameter, command, arithmetic, + and globbing substitutions. */ + +/* ``Have a little faith, there's magic in the night. You ain't a + beauty, but, hey, you're alright.'' */ + +/* Copyright (C) 1987-2010 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#include +#include "chartypes.h" +#if defined (HAVE_PWD_H) +# include +#endif +#include +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "posixstat.h" +#include "bashintl.h" + +#include "shell.h" +#include "parser.h" +#include "flags.h" +#include "jobs.h" +#include "execute_cmd.h" +#include "filecntl.h" +#include "trap.h" +#include "pathexp.h" +#include "mailcheck.h" + +#include "shmbutil.h" +#include "typemax.h" + +#include "builtins/getopt.h" +#include "builtins/common.h" + +#include "builtins/builtext.h" + +#include +#include + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +/* The size that strings change by. */ +#define DEFAULT_INITIAL_ARRAY_SIZE 112 +#define DEFAULT_ARRAY_SIZE 128 + +/* Variable types. */ +#define VT_VARIABLE 0 +#define VT_POSPARMS 1 +#define VT_ARRAYVAR 2 +#define VT_ARRAYMEMBER 3 +#define VT_ASSOCVAR 4 + +#define VT_STARSUB 128 /* $* or ${array[*]} -- used to split */ + +/* Flags for quoted_strchr */ +#define ST_BACKSL 0x01 +#define ST_CTLESC 0x02 +#define ST_SQUOTE 0x04 /* unused yet */ +#define ST_DQUOTE 0x08 /* unused yet */ + +/* Flags for the `pflags' argument to param_expand() */ +#define PF_NOCOMSUB 0x01 /* Do not perform command substitution */ +#define PF_IGNUNBOUND 0x02 /* ignore unbound vars even if -u set */ +#define PF_NOSPLIT2 0x04 /* same as W_NOSPLIT2 */ +#define PF_ASSIGNRHS 0x08 /* same as W_ASSIGNRHS */ + +/* These defs make it easier to use the editor. */ +#define LBRACE '{' +#define RBRACE '}' +#define LPAREN '(' +#define RPAREN ')' + +#if defined (HANDLE_MULTIBYTE) +#define WLPAREN L'(' +#define WRPAREN L')' +#endif + +/* Evaluates to 1 if C is one of the shell's special parameters whose length + can be taken, but is also one of the special expansion characters. */ +#define VALID_SPECIAL_LENGTH_PARAM(c) \ + ((c) == '-' || (c) == '?' || (c) == '#') + +/* Evaluates to 1 if C is one of the shell's special parameters for which an + indirect variable reference may be made. */ +#define VALID_INDIR_PARAM(c) \ + ((posixly_correct == 0 && (c) == '#') || (posixly_correct == 0 && (c) == '?') || (c) == '@' || (c) == '*') + +/* Evaluates to 1 if C is one of the OP characters that follows the parameter + in ${parameter[:]OPword}. */ +#define VALID_PARAM_EXPAND_CHAR(c) (sh_syntaxtab[(unsigned char)c] & CSUBSTOP) + +/* Evaluates to 1 if this is one of the shell's special variables. */ +#define SPECIAL_VAR(name, wi) \ + ((DIGIT (*name) && all_digits (name)) || \ + (name[1] == '\0' && (sh_syntaxtab[(unsigned char)*name] & CSPECVAR)) || \ + (wi && name[2] == '\0' && VALID_INDIR_PARAM (name[1]))) + +/* An expansion function that takes a string and a quoted flag and returns + a WORD_LIST *. Used as the type of the third argument to + expand_string_if_necessary(). */ +typedef WORD_LIST *EXPFUNC __P((char *, int)); + +/* Process ID of the last command executed within command substitution. */ +pid_t last_command_subst_pid = NO_PID; +pid_t current_command_subst_pid = NO_PID; + +/* Variables used to keep track of the characters in IFS. */ +SHELL_VAR *ifs_var; +char *ifs_value; +unsigned char ifs_cmap[UCHAR_MAX + 1]; + +#if defined (HANDLE_MULTIBYTE) +unsigned char ifs_firstc[MB_LEN_MAX]; +size_t ifs_firstc_len; +#else +unsigned char ifs_firstc; +#endif + +/* Sentinel to tell when we are performing variable assignments preceding a + command name and putting them into the environment. Used to make sure + we use the temporary environment when looking up variable values. */ +int assigning_in_environment; + +/* Used to hold a list of variable assignments preceding a command. Global + so the SIGCHLD handler in jobs.c can unwind-protect it when it runs a + SIGCHLD trap and so it can be saved and restored by the trap handlers. */ +WORD_LIST *subst_assign_varlist = (WORD_LIST *)NULL; + +/* Extern functions and variables from different files. */ +extern int last_command_exit_value, last_command_exit_signal; +extern int subshell_environment, line_number; +extern int subshell_level, parse_and_execute_level, sourcelevel; +extern int eof_encountered; +extern int return_catch_flag, return_catch_value; +extern pid_t dollar_dollar_pid; +extern int posixly_correct; +extern char *this_command_name; +extern struct fd_bitmap *current_fds_to_close; +extern int wordexp_only; +extern int expanding_redir; +extern int tempenv_assign_error; + +#if !defined (HAVE_WCSDUP) && defined (HANDLE_MULTIBYTE) +extern wchar_t *wcsdup __P((const wchar_t *)); +#endif + +/* Non-zero means to allow unmatched globbed filenames to expand to + a null file. */ +int allow_null_glob_expansion; + +/* Non-zero means to throw an error when globbing fails to match anything. */ +int fail_glob_expansion; + +#if 0 +/* Variables to keep track of which words in an expanded word list (the + output of expand_word_list_internal) are the result of globbing + expansions. GLOB_ARGV_FLAGS is used by execute_cmd.c. + (CURRENTLY UNUSED). */ +char *glob_argv_flags; +static int glob_argv_flags_size; +#endif + +static WORD_LIST expand_word_error, expand_word_fatal; +static WORD_DESC expand_wdesc_error, expand_wdesc_fatal; +static char expand_param_error, expand_param_fatal; +static char extract_string_error, extract_string_fatal; + +/* Tell the expansion functions to not longjmp back to top_level on fatal + errors. Enabled when doing completion and prompt string expansion. */ +static int no_longjmp_on_fatal_error = 0; + +/* Set by expand_word_unsplit; used to inhibit splitting and re-joining + $* on $IFS, primarily when doing assignment statements. */ +static int expand_no_split_dollar_star = 0; + +/* A WORD_LIST of words to be expanded by expand_word_list_internal, + without any leading variable assignments. */ +static WORD_LIST *garglist = (WORD_LIST *)NULL; + +static char *quoted_substring __P((char *, int, int)); +static int quoted_strlen __P((char *)); +static char *quoted_strchr __P((char *, int, int)); + +static char *expand_string_if_necessary __P((char *, int, EXPFUNC *)); +static inline char *expand_string_to_string_internal __P((char *, int, EXPFUNC *)); +static WORD_LIST *call_expand_word_internal __P((WORD_DESC *, int, int, int *, int *)); +static WORD_LIST *expand_string_internal __P((char *, int)); +static WORD_LIST *expand_string_leave_quoted __P((char *, int)); +static WORD_LIST *expand_string_for_rhs __P((char *, int, int *, int *)); + +static WORD_LIST *list_quote_escapes __P((WORD_LIST *)); +static char *make_quoted_char __P((int)); +static WORD_LIST *quote_list __P((WORD_LIST *)); + +static int unquoted_substring __P((char *, char *)); +static int unquoted_member __P((int, char *)); + +#if defined (ARRAY_VARS) +static SHELL_VAR *do_compound_assignment __P((char *, char *, int)); +#endif +static int do_assignment_internal __P((const WORD_DESC *, int)); + +static char *string_extract_verbatim __P((char *, size_t, int *, char *, int)); +static char *string_extract __P((char *, int *, char *, int)); +static char *string_extract_double_quoted __P((char *, int *, int)); +static inline char *string_extract_single_quoted __P((char *, int *)); +static inline int skip_single_quoted __P((const char *, size_t, int)); +static int skip_double_quoted __P((char *, size_t, int)); +static char *extract_delimited_string __P((char *, int *, char *, char *, char *, int)); +static char *extract_dollar_brace_string __P((char *, int *, int, int)); +static int skip_matched_pair __P((const char *, int, int, int, int)); + +static char *pos_params __P((char *, int, int, int)); + +static unsigned char *mb_getcharlens __P((char *, int)); + +static char *remove_upattern __P((char *, char *, int)); +#if defined (HANDLE_MULTIBYTE) +static wchar_t *remove_wpattern __P((wchar_t *, size_t, wchar_t *, int)); +#endif +static char *remove_pattern __P((char *, char *, int)); + +static int match_upattern __P((char *, char *, int, char **, char **)); +#if defined (HANDLE_MULTIBYTE) +static int match_wpattern __P((wchar_t *, char **, size_t, wchar_t *, int, char **, char **)); +#endif +static int match_pattern __P((char *, char *, int, char **, char **)); +static int getpatspec __P((int, char *)); +static char *getpattern __P((char *, int, int)); +static char *variable_remove_pattern __P((char *, char *, int, int)); +static char *list_remove_pattern __P((WORD_LIST *, char *, int, int, int)); +static char *parameter_list_remove_pattern __P((int, char *, int, int)); +#ifdef ARRAY_VARS +static char *array_remove_pattern __P((SHELL_VAR *, char *, int, char *, int)); +#endif +static char *parameter_brace_remove_pattern __P((char *, char *, int, char *, int, int, int)); + +static char *process_substitute __P((char *, int)); + +static char *read_comsub __P((int, int, int *)); + +#ifdef ARRAY_VARS +static arrayind_t array_length_reference __P((char *)); +#endif + +static int valid_brace_expansion_word __P((char *, int)); +static int chk_atstar __P((char *, int, int *, int *)); +static int chk_arithsub __P((const char *, int)); + +static WORD_DESC *parameter_brace_expand_word __P((char *, int, int, int, arrayind_t *)); +static WORD_DESC *parameter_brace_expand_indir __P((char *, int, int, int *, int *)); +static WORD_DESC *parameter_brace_expand_rhs __P((char *, char *, int, int, int *, int *)); +static void parameter_brace_expand_error __P((char *, char *)); + +static int valid_length_expression __P((char *)); +static intmax_t parameter_brace_expand_length __P((char *)); + +static char *skiparith __P((char *, int)); +static int verify_substring_values __P((SHELL_VAR *, char *, char *, int, intmax_t *, intmax_t *)); +static int get_var_and_type __P((char *, char *, arrayind_t, int, int, SHELL_VAR **, char **)); +static char *mb_substring __P((char *, int, int)); +static char *parameter_brace_substring __P((char *, char *, int, char *, int, int)); + +static int shouldexp_replacement __P((char *)); + +static char *pos_params_pat_subst __P((char *, char *, char *, int)); + +static char *parameter_brace_patsub __P((char *, char *, int, char *, int, int)); + +static char *pos_params_casemod __P((char *, char *, int, int)); +static char *parameter_brace_casemod __P((char *, char *, int, int, char *, int, int)); + +static WORD_DESC *parameter_brace_expand __P((char *, int *, int, int, int *, int *)); +static WORD_DESC *param_expand __P((char *, int *, int, int *, int *, int *, int *, int)); + +static WORD_LIST *expand_word_internal __P((WORD_DESC *, int, int, int *, int *)); + +static WORD_LIST *word_list_split __P((WORD_LIST *)); + +static void exp_jump_to_top_level __P((int)); + +static WORD_LIST *separate_out_assignments __P((WORD_LIST *)); +static WORD_LIST *glob_expand_word_list __P((WORD_LIST *, int)); +#ifdef BRACE_EXPANSION +static WORD_LIST *brace_expand_word_list __P((WORD_LIST *, int)); +#endif +#if defined (ARRAY_VARS) +static int make_internal_declare __P((char *, char *)); +#endif +static WORD_LIST *shell_expand_word_list __P((WORD_LIST *, int)); +static WORD_LIST *expand_word_list_internal __P((WORD_LIST *, int)); + +/* **************************************************************** */ +/* */ +/* Utility Functions */ +/* */ +/* **************************************************************** */ + +#if defined (DEBUG) +void +dump_word_flags (flags) + int flags; +{ + int f; + + f = flags; + fprintf (stderr, "%d -> ", f); + if (f & W_ASSIGNASSOC) + { + f &= ~W_ASSIGNASSOC; + fprintf (stderr, "W_ASSIGNASSOC%s", f ? "|" : ""); + } + if (f & W_HASCTLESC) + { + f &= ~W_HASCTLESC; + fprintf (stderr, "W_HASCTLESC%s", f ? "|" : ""); + } + if (f & W_NOPROCSUB) + { + f &= ~W_NOPROCSUB; + fprintf (stderr, "W_NOPROCSUB%s", f ? "|" : ""); + } + if (f & W_DQUOTE) + { + f &= ~W_DQUOTE; + fprintf (stderr, "W_DQUOTE%s", f ? "|" : ""); + } + if (f & W_HASQUOTEDNULL) + { + f &= ~W_HASQUOTEDNULL; + fprintf (stderr, "W_HASQUOTEDNULL%s", f ? "|" : ""); + } + if (f & W_ASSIGNARG) + { + f &= ~W_ASSIGNARG; + fprintf (stderr, "W_ASSIGNARG%s", f ? "|" : ""); + } + if (f & W_ASSNBLTIN) + { + f &= ~W_ASSNBLTIN; + fprintf (stderr, "W_ASSNBLTIN%s", f ? "|" : ""); + } + if (f & W_COMPASSIGN) + { + f &= ~W_COMPASSIGN; + fprintf (stderr, "W_COMPASSIGN%s", f ? "|" : ""); + } + if (f & W_NOEXPAND) + { + f &= ~W_NOEXPAND; + fprintf (stderr, "W_NOEXPAND%s", f ? "|" : ""); + } + if (f & W_ITILDE) + { + f &= ~W_ITILDE; + fprintf (stderr, "W_ITILDE%s", f ? "|" : ""); + } + if (f & W_NOTILDE) + { + f &= ~W_NOTILDE; + fprintf (stderr, "W_NOTILDE%s", f ? "|" : ""); + } + if (f & W_ASSIGNRHS) + { + f &= ~W_ASSIGNRHS; + fprintf (stderr, "W_ASSIGNRHS%s", f ? "|" : ""); + } + if (f & W_NOCOMSUB) + { + f &= ~W_NOCOMSUB; + fprintf (stderr, "W_NOCOMSUB%s", f ? "|" : ""); + } + if (f & W_DOLLARSTAR) + { + f &= ~W_DOLLARSTAR; + fprintf (stderr, "W_DOLLARSTAR%s", f ? "|" : ""); + } + if (f & W_DOLLARAT) + { + f &= ~W_DOLLARAT; + fprintf (stderr, "W_DOLLARAT%s", f ? "|" : ""); + } + if (f & W_TILDEEXP) + { + f &= ~W_TILDEEXP; + fprintf (stderr, "W_TILDEEXP%s", f ? "|" : ""); + } + if (f & W_NOSPLIT2) + { + f &= ~W_NOSPLIT2; + fprintf (stderr, "W_NOSPLIT2%s", f ? "|" : ""); + } + if (f & W_NOGLOB) + { + f &= ~W_NOGLOB; + fprintf (stderr, "W_NOGLOB%s", f ? "|" : ""); + } + if (f & W_NOSPLIT) + { + f &= ~W_NOSPLIT; + fprintf (stderr, "W_NOSPLIT%s", f ? "|" : ""); + } + if (f & W_GLOBEXP) + { + f &= ~W_GLOBEXP; + fprintf (stderr, "W_GLOBEXP%s", f ? "|" : ""); + } + if (f & W_ASSIGNMENT) + { + f &= ~W_ASSIGNMENT; + fprintf (stderr, "W_ASSIGNMENT%s", f ? "|" : ""); + } + if (f & W_QUOTED) + { + f &= ~W_QUOTED; + fprintf (stderr, "W_QUOTED%s", f ? "|" : ""); + } + if (f & W_HASDOLLAR) + { + f &= ~W_HASDOLLAR; + fprintf (stderr, "W_HASDOLLAR%s", f ? "|" : ""); + } + fprintf (stderr, "\n"); + fflush (stderr); +} +#endif + +#ifdef INCLUDE_UNUSED +static char * +quoted_substring (string, start, end) + char *string; + int start, end; +{ + register int len, l; + register char *result, *s, *r; + + len = end - start; + + /* Move to string[start], skipping quoted characters. */ + for (s = string, l = 0; *s && l < start; ) + { + if (*s == CTLESC) + { + s++; + continue; + } + l++; + if (*s == 0) + break; + } + + r = result = (char *)xmalloc (2*len + 1); /* save room for quotes */ + + /* Copy LEN characters, including quote characters. */ + s = string + l; + for (l = 0; l < len; s++) + { + if (*s == CTLESC) + *r++ = *s++; + *r++ = *s; + l++; + if (*s == 0) + break; + } + *r = '\0'; + return result; +} +#endif + +#ifdef INCLUDE_UNUSED +/* Return the length of S, skipping over quoted characters */ +static int +quoted_strlen (s) + char *s; +{ + register char *p; + int i; + + i = 0; + for (p = s; *p; p++) + { + if (*p == CTLESC) + { + p++; + if (*p == 0) + return (i + 1); + } + i++; + } + + return i; +} +#endif + +/* Find the first occurrence of character C in string S, obeying shell + quoting rules. If (FLAGS & ST_BACKSL) is non-zero, backslash-escaped + characters are skipped. If (FLAGS & ST_CTLESC) is non-zero, characters + escaped with CTLESC are skipped. */ +static char * +quoted_strchr (s, c, flags) + char *s; + int c, flags; +{ + register char *p; + + for (p = s; *p; p++) + { + if (((flags & ST_BACKSL) && *p == '\\') + || ((flags & ST_CTLESC) && *p == CTLESC)) + { + p++; + if (*p == '\0') + return ((char *)NULL); + continue; + } + else if (*p == c) + return p; + } + return ((char *)NULL); +} + +/* Return 1 if CHARACTER appears in an unquoted portion of + STRING. Return 0 otherwise. CHARACTER must be a single-byte character. */ +static int +unquoted_member (character, string) + int character; + char *string; +{ + size_t slen; + int sindex, c; + DECLARE_MBSTATE; + + slen = strlen (string); + sindex = 0; + while (c = string[sindex]) + { + if (c == character) + return (1); + + switch (c) + { + default: + ADVANCE_CHAR (string, slen, sindex); + break; + + case '\\': + sindex++; + if (string[sindex]) + ADVANCE_CHAR (string, slen, sindex); + break; + + case '\'': + sindex = skip_single_quoted (string, slen, ++sindex); + break; + + case '"': + sindex = skip_double_quoted (string, slen, ++sindex); + break; + } + } + return (0); +} + +/* Return 1 if SUBSTR appears in an unquoted portion of STRING. */ +static int +unquoted_substring (substr, string) + char *substr, *string; +{ + size_t slen; + int sindex, c, sublen; + DECLARE_MBSTATE; + + if (substr == 0 || *substr == '\0') + return (0); + + slen = strlen (string); + sublen = strlen (substr); + for (sindex = 0; c = string[sindex]; ) + { + if (STREQN (string + sindex, substr, sublen)) + return (1); + + switch (c) + { + case '\\': + sindex++; + if (string[sindex]) + ADVANCE_CHAR (string, slen, sindex); + break; + + case '\'': + sindex = skip_single_quoted (string, slen, ++sindex); + break; + + case '"': + sindex = skip_double_quoted (string, slen, ++sindex); + break; + + default: + ADVANCE_CHAR (string, slen, sindex); + break; + } + } + return (0); +} + +/* Most of the substitutions must be done in parallel. In order + to avoid using tons of unclear goto's, I have some functions + for manipulating malloc'ed strings. They all take INDX, a + pointer to an integer which is the offset into the string + where manipulation is taking place. They also take SIZE, a + pointer to an integer which is the current length of the + character array for this string. */ + +/* Append SOURCE to TARGET at INDEX. SIZE is the current amount + of space allocated to TARGET. SOURCE can be NULL, in which + case nothing happens. Gets rid of SOURCE by freeing it. + Returns TARGET in case the location has changed. */ +INLINE char * +sub_append_string (source, target, indx, size) + char *source, *target; + int *indx, *size; +{ + if (source) + { + int srclen, n; + + srclen = STRLEN (source); + if (srclen >= (int)(*size - *indx)) + { + n = srclen + *indx; + n = (n + DEFAULT_ARRAY_SIZE) - (n % DEFAULT_ARRAY_SIZE); + target = (char *)xrealloc (target, (*size = n)); + } + + FASTCOPY (source, target + *indx, srclen); + *indx += srclen; + target[*indx] = '\0'; + + free (source); + } + return (target); +} + +#if 0 +/* UNUSED */ +/* Append the textual representation of NUMBER to TARGET. + INDX and SIZE are as in SUB_APPEND_STRING. */ +char * +sub_append_number (number, target, indx, size) + intmax_t number; + int *indx, *size; + char *target; +{ + char *temp; + + temp = itos (number); + return (sub_append_string (temp, target, indx, size)); +} +#endif + +/* Extract a substring from STRING, starting at SINDEX and ending with + one of the characters in CHARLIST. Don't make the ending character + part of the string. Leave SINDEX pointing at the ending character. + Understand about backslashes in the string. If (flags & SX_VARNAME) + is non-zero, and array variables have been compiled into the shell, + everything between a `[' and a corresponding `]' is skipped over. + If (flags & SX_NOALLOC) is non-zero, don't return the substring, just + update SINDEX. If (flags & SX_REQMATCH) is non-zero, the string must + contain a closing character from CHARLIST. */ +static char * +string_extract (string, sindex, charlist, flags) + char *string; + int *sindex; + char *charlist; + int flags; +{ + register int c, i; + int found; + size_t slen; + char *temp; + DECLARE_MBSTATE; + + slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 0; + i = *sindex; + found = 0; + while (c = string[i]) + { + if (c == '\\') + { + if (string[i + 1]) + i++; + else + break; + } +#if defined (ARRAY_VARS) + else if ((flags & SX_VARNAME) && c == '[') + { + int ni; + /* If this is an array subscript, skip over it and continue. */ + ni = skipsubscript (string, i, 0); + if (string[ni] == ']') + i = ni; + } +#endif + else if (MEMBER (c, charlist)) + { + found = 1; + break; + } + + ADVANCE_CHAR (string, slen, i); + } + + /* If we had to have a matching delimiter and didn't find one, return an + error and let the caller deal with it. */ + if ((flags & SX_REQMATCH) && found == 0) + { + *sindex = i; + return (&extract_string_error); + } + + temp = (flags & SX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i); + *sindex = i; + + return (temp); +} + +/* Extract the contents of STRING as if it is enclosed in double quotes. + SINDEX, when passed in, is the offset of the character immediately + following the opening double quote; on exit, SINDEX is left pointing after + the closing double quote. If STRIPDQ is non-zero, unquoted double + quotes are stripped and the string is terminated by a null byte. + Backslashes between the embedded double quotes are processed. If STRIPDQ + is zero, an unquoted `"' terminates the string. */ +static char * +string_extract_double_quoted (string, sindex, stripdq) + char *string; + int *sindex, stripdq; +{ + size_t slen; + char *send; + int j, i, t; + unsigned char c; + char *temp, *ret; /* The new string we return. */ + int pass_next, backquote, si; /* State variables for the machine. */ + int dquote; + DECLARE_MBSTATE; + + slen = strlen (string + *sindex) + *sindex; + send = string + slen; + + pass_next = backquote = dquote = 0; + temp = (char *)xmalloc (1 + slen - *sindex); + + j = 0; + i = *sindex; + while (c = string[i]) + { + /* Process a character that was quoted by a backslash. */ + if (pass_next) + { + /* XXX - take another look at this in light of Interp 221 */ + /* Posix.2 sez: + + ``The backslash shall retain its special meaning as an escape + character only when followed by one of the characters: + $ ` " \ ''. + + If STRIPDQ is zero, we handle the double quotes here and let + expand_word_internal handle the rest. If STRIPDQ is non-zero, + we have already been through one round of backslash stripping, + and want to strip these backslashes only if DQUOTE is non-zero, + indicating that we are inside an embedded double-quoted string. */ + + /* If we are in an embedded quoted string, then don't strip + backslashes before characters for which the backslash + retains its special meaning, but remove backslashes in + front of other characters. If we are not in an + embedded quoted string, don't strip backslashes at all. + This mess is necessary because the string was already + surrounded by double quotes (and sh has some really weird + quoting rules). + The returned string will be run through expansion as if + it were double-quoted. */ + if ((stripdq == 0 && c != '"') || + (stripdq && ((dquote && (sh_syntaxtab[c] & CBSDQUOTE)) || dquote == 0))) + temp[j++] = '\\'; + pass_next = 0; + +add_one_character: + COPY_CHAR_I (temp, j, string, send, i); + continue; + } + + /* A backslash protects the next character. The code just above + handles preserving the backslash in front of any character but + a double quote. */ + if (c == '\\') + { + pass_next++; + i++; + continue; + } + + /* Inside backquotes, ``the portion of the quoted string from the + initial backquote and the characters up to the next backquote + that is not preceded by a backslash, having escape characters + removed, defines that command''. */ + if (backquote) + { + if (c == '`') + backquote = 0; + temp[j++] = c; + i++; + continue; + } + + if (c == '`') + { + temp[j++] = c; + backquote++; + i++; + continue; + } + + /* Pass everything between `$(' and the matching `)' or a quoted + ${ ... } pair through according to the Posix.2 specification. */ + if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE))) + { + int free_ret = 1; + + si = i + 2; + if (string[i + 1] == LPAREN) + ret = extract_command_subst (string, &si, 0); + else + ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, 0); + + temp[j++] = '$'; + temp[j++] = string[i + 1]; + + /* Just paranoia; ret will not be 0 unless no_longjmp_on_fatal_error + is set. */ + if (ret == 0 && no_longjmp_on_fatal_error) + { + free_ret = 0; + ret = string + i + 2; + } + + for (t = 0; ret[t]; t++, j++) + temp[j] = ret[t]; + temp[j] = string[si]; + + if (string[si]) + { + j++; + i = si + 1; + } + else + i = si; + + if (free_ret) + free (ret); + continue; + } + + /* Add any character but a double quote to the quoted string we're + accumulating. */ + if (c != '"') + goto add_one_character; + + /* c == '"' */ + if (stripdq) + { + dquote ^= 1; + i++; + continue; + } + + break; + } + temp[j] = '\0'; + + /* Point to after the closing quote. */ + if (c) + i++; + *sindex = i; + + return (temp); +} + +/* This should really be another option to string_extract_double_quoted. */ +static int +skip_double_quoted (string, slen, sind) + char *string; + size_t slen; + int sind; +{ + int c, i; + char *ret; + int pass_next, backquote, si; + DECLARE_MBSTATE; + + pass_next = backquote = 0; + i = sind; + while (c = string[i]) + { + if (pass_next) + { + pass_next = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next++; + i++; + continue; + } + else if (backquote) + { + if (c == '`') + backquote = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '`') + { + backquote++; + i++; + continue; + } + else if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE))) + { + si = i + 2; + if (string[i + 1] == LPAREN) + ret = extract_command_subst (string, &si, SX_NOALLOC); + else + ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, SX_NOALLOC); + + i = si + 1; + continue; + } + else if (c != '"') + { + ADVANCE_CHAR (string, slen, i); + continue; + } + else + break; + } + + if (c) + i++; + + return (i); +} + +/* Extract the contents of STRING as if it is enclosed in single quotes. + SINDEX, when passed in, is the offset of the character immediately + following the opening single quote; on exit, SINDEX is left pointing after + the closing single quote. */ +static inline char * +string_extract_single_quoted (string, sindex) + char *string; + int *sindex; +{ + register int i; + size_t slen; + char *t; + DECLARE_MBSTATE; + + /* Don't need slen for ADVANCE_CHAR unless multibyte chars possible. */ + slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 0; + i = *sindex; + while (string[i] && string[i] != '\'') + ADVANCE_CHAR (string, slen, i); + + t = substring (string, *sindex, i); + + if (string[i]) + i++; + *sindex = i; + + return (t); +} + +static inline int +skip_single_quoted (string, slen, sind) + const char *string; + size_t slen; + int sind; +{ + register int c; + DECLARE_MBSTATE; + + c = sind; + while (string[c] && string[c] != '\'') + ADVANCE_CHAR (string, slen, c); + + if (string[c]) + c++; + return c; +} + +/* Just like string_extract, but doesn't hack backslashes or any of + that other stuff. Obeys CTLESC quoting. Used to do splitting on $IFS. */ +static char * +string_extract_verbatim (string, slen, sindex, charlist, flags) + char *string; + size_t slen; + int *sindex; + char *charlist; + int flags; +{ + register int i; +#if defined (HANDLE_MULTIBYTE) + size_t clen; + wchar_t *wcharlist; +#endif + int c; + char *temp; + DECLARE_MBSTATE; + + if (charlist[0] == '\'' && charlist[1] == '\0') + { + temp = string_extract_single_quoted (string, sindex); + --*sindex; /* leave *sindex at separator character */ + return temp; + } + + i = *sindex; +#if 0 + /* See how the MBLEN and ADVANCE_CHAR macros work to understand why we need + this only if MB_CUR_MAX > 1. */ + slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 1; +#endif +#if defined (HANDLE_MULTIBYTE) + clen = strlen (charlist); + wcharlist = 0; +#endif + while (c = string[i]) + { +#if defined (HANDLE_MULTIBYTE) + size_t mblength; +#endif + if ((flags & SX_NOCTLESC) == 0 && c == CTLESC) + { + i += 2; + continue; + } + /* Even if flags contains SX_NOCTLESC, we let CTLESC quoting CTLNUL + through, to protect the CTLNULs from later calls to + remove_quoted_nulls. */ + else if ((flags & SX_NOESCCTLNUL) == 0 && c == CTLESC && string[i+1] == CTLNUL) + { + i += 2; + continue; + } + +#if defined (HANDLE_MULTIBYTE) + mblength = MBLEN (string + i, slen - i); + if (mblength > 1) + { + wchar_t wc; + mblength = mbtowc (&wc, string + i, slen - i); + if (MB_INVALIDCH (mblength)) + { + if (MEMBER (c, charlist)) + break; + } + else + { + if (wcharlist == 0) + { + size_t len; + len = mbstowcs (wcharlist, charlist, 0); + if (len == -1) + len = 0; + wcharlist = (wchar_t *)xmalloc (sizeof (wchar_t) * (len + 1)); + mbstowcs (wcharlist, charlist, len + 1); + } + + if (wcschr (wcharlist, wc)) + break; + } + } + else +#endif + if (MEMBER (c, charlist)) + break; + + ADVANCE_CHAR (string, slen, i); + } + +#if defined (HANDLE_MULTIBYTE) + FREE (wcharlist); +#endif + + temp = substring (string, *sindex, i); + *sindex = i; + + return (temp); +} + +/* Extract the $( construct in STRING, and return a new string. + Start extracting at (SINDEX) as if we had just seen "$(". + Make (SINDEX) get the position of the matching ")". ) + XFLAGS is additional flags to pass to other extraction functions. */ +char * +extract_command_subst (string, sindex, xflags) + char *string; + int *sindex; + int xflags; +{ + if (string[*sindex] == LPAREN) + return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/ + else + { + xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0); + return (xparse_dolparen (string, string+*sindex, sindex, xflags)); + } +} + +/* Extract the $[ construct in STRING, and return a new string. (]) + Start extracting at (SINDEX) as if we had just seen "$[". + Make (SINDEX) get the position of the matching "]". */ +char * +extract_arithmetic_subst (string, sindex) + char *string; + int *sindex; +{ + return (extract_delimited_string (string, sindex, "$[", "[", "]", 0)); /*]*/ +} + +#if defined (PROCESS_SUBSTITUTION) +/* Extract the <( or >( construct in STRING, and return a new string. + Start extracting at (SINDEX) as if we had just seen "<(". + Make (SINDEX) get the position of the matching ")". */ /*))*/ +char * +extract_process_subst (string, starter, sindex) + char *string; + char *starter; + int *sindex; +{ + return (extract_delimited_string (string, sindex, starter, "(", ")", SX_COMMAND)); +} +#endif /* PROCESS_SUBSTITUTION */ + +#if defined (ARRAY_VARS) +/* This can be fooled by unquoted right parens in the passed string. If + each caller verifies that the last character in STRING is a right paren, + we don't even need to call extract_delimited_string. */ +char * +extract_array_assignment_list (string, sindex) + char *string; + int *sindex; +{ + int slen; + char *ret; + + slen = strlen (string); /* ( */ + if (string[slen - 1] == ')') + { + ret = substring (string, *sindex, slen - 1); + *sindex = slen - 1; + return ret; + } + return 0; +} +#endif + +/* Extract and create a new string from the contents of STRING, a + character string delimited with OPENER and CLOSER. SINDEX is + the address of an int describing the current offset in STRING; + it should point to just after the first OPENER found. On exit, + SINDEX gets the position of the last character of the matching CLOSER. + If OPENER is more than a single character, ALT_OPENER, if non-null, + contains a character string that can also match CLOSER and thus + needs to be skipped. */ +static char * +extract_delimited_string (string, sindex, opener, alt_opener, closer, flags) + char *string; + int *sindex; + char *opener, *alt_opener, *closer; + int flags; +{ + int i, c, si; + size_t slen; + char *t, *result; + int pass_character, nesting_level, in_comment; + int len_closer, len_opener, len_alt_opener; + DECLARE_MBSTATE; + + slen = strlen (string + *sindex) + *sindex; + len_opener = STRLEN (opener); + len_alt_opener = STRLEN (alt_opener); + len_closer = STRLEN (closer); + + pass_character = in_comment = 0; + + nesting_level = 1; + i = *sindex; + + while (nesting_level) + { + c = string[i]; + + if (c == 0) + break; + + if (in_comment) + { + if (c == '\n') + in_comment = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + + if (pass_character) /* previous char was backslash */ + { + pass_character = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + + /* Not exactly right yet; should handle shell metacharacters and + multibyte characters, too. See COMMENT_BEGIN define in parse.y */ + if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1]))) + { + in_comment = 1; + ADVANCE_CHAR (string, slen, i); + continue; + } + + if (c == CTLESC || c == '\\') + { + pass_character++; + i++; + continue; + } + + /* Process a nested command substitution, but only if we're parsing an + arithmetic substitution. */ + if ((flags & SX_COMMAND) && string[i] == '$' && string[i+1] == LPAREN) + { + si = i + 2; + t = extract_command_subst (string, &si, flags|SX_NOALLOC); + i = si + 1; + continue; + } + + /* Process a nested OPENER. */ + if (STREQN (string + i, opener, len_opener)) + { + si = i + len_opener; + t = extract_delimited_string (string, &si, opener, alt_opener, closer, flags|SX_NOALLOC); + i = si + 1; + continue; + } + + /* Process a nested ALT_OPENER */ + if (len_alt_opener && STREQN (string + i, alt_opener, len_alt_opener)) + { + si = i + len_alt_opener; + t = extract_delimited_string (string, &si, alt_opener, alt_opener, closer, flags|SX_NOALLOC); + i = si + 1; + continue; + } + + /* If the current substring terminates the delimited string, decrement + the nesting level. */ + if (STREQN (string + i, closer, len_closer)) + { + i += len_closer - 1; /* move to last byte of the closer */ + nesting_level--; + if (nesting_level == 0) + break; + } + + /* Pass old-style command substitution through verbatim. */ + if (c == '`') + { + si = i + 1; + t = string_extract (string, &si, "`", flags|SX_NOALLOC); + i = si + 1; + continue; + } + + /* Pass single-quoted and double-quoted strings through verbatim. */ + if (c == '\'' || c == '"') + { + si = i + 1; + i = (c == '\'') ? skip_single_quoted (string, slen, si) + : skip_double_quoted (string, slen, si); + continue; + } + + /* move past this character, which was not special. */ + ADVANCE_CHAR (string, slen, i); + } + + if (c == 0 && nesting_level) + { + if (no_longjmp_on_fatal_error == 0) + { + report_error (_("bad substitution: no closing `%s' in %s"), closer, string); + last_command_exit_value = EXECUTION_FAILURE; + exp_jump_to_top_level (DISCARD); + } + else + { + *sindex = i; + return (char *)NULL; + } + } + + si = i - *sindex - len_closer + 1; + if (flags & SX_NOALLOC) + result = (char *)NULL; + else + { + result = (char *)xmalloc (1 + si); + strncpy (result, string + *sindex, si); + result[si] = '\0'; + } + *sindex = i; + + return (result); +} + +/* Extract a parameter expansion expression within ${ and } from STRING. + Obey the Posix.2 rules for finding the ending `}': count braces while + skipping over enclosed quoted strings and command substitutions. + SINDEX is the address of an int describing the current offset in STRING; + it should point to just after the first `{' found. On exit, SINDEX + gets the position of the matching `}'. QUOTED is non-zero if this + occurs inside double quotes. */ +/* XXX -- this is very similar to extract_delimited_string -- XXX */ +static char * +extract_dollar_brace_string (string, sindex, quoted, flags) + char *string; + int *sindex, quoted, flags; +{ + register int i, c; + size_t slen; + int pass_character, nesting_level, si, dolbrace_state; + char *result, *t; + DECLARE_MBSTATE; + + pass_character = 0; + nesting_level = 1; + slen = strlen (string + *sindex) + *sindex; + + /* The handling of dolbrace_state needs to agree with the code in parse.y: + parse_matched_pair(). The different initial value is to handle the + case where this function is called to parse the word in + ${param op word} (SX_WORD). */ + dolbrace_state = (flags & SX_WORD) ? DOLBRACE_WORD : DOLBRACE_PARAM; + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && (flags & SX_POSIXEXP)) + dolbrace_state = DOLBRACE_QUOTE; + + i = *sindex; + while (c = string[i]) + { + if (pass_character) + { + pass_character = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + + /* CTLESCs and backslashes quote the next character. */ + if (c == CTLESC || c == '\\') + { + pass_character++; + i++; + continue; + } + + if (string[i] == '$' && string[i+1] == LBRACE) + { + nesting_level++; + i += 2; + continue; + } + + if (c == RBRACE) + { + nesting_level--; + if (nesting_level == 0) + break; + i++; + continue; + } + + /* Pass the contents of old-style command substitutions through + verbatim. */ + if (c == '`') + { + si = i + 1; + t = string_extract (string, &si, "`", flags|SX_NOALLOC); + i = si + 1; + continue; + } + + /* Pass the contents of new-style command substitutions and + arithmetic substitutions through verbatim. */ + if (string[i] == '$' && string[i+1] == LPAREN) + { + si = i + 2; + t = extract_command_subst (string, &si, flags|SX_NOALLOC); + i = si + 1; + continue; + } + +#if 0 + /* Pass the contents of single-quoted and double-quoted strings + through verbatim. */ + if (c == '\'' || c == '"') + { + si = i + 1; + i = (c == '\'') ? skip_single_quoted (string, slen, si) + : skip_double_quoted (string, slen, si); + /* skip_XXX_quoted leaves index one past close quote */ + continue; + } +#else /* XXX - bash-4.2 */ + /* Pass the contents of double-quoted strings through verbatim. */ + if (c == '"') + { + si = i + 1; + i = skip_double_quoted (string, slen, si); + /* skip_XXX_quoted leaves index one past close quote */ + continue; + } + + if (c == '\'') + { +/*itrace("extract_dollar_brace_string: c == single quote flags = %d quoted = %d dolbrace_state = %d", flags, quoted, dolbrace_state);*/ + if (posixly_correct && shell_compatibility_level > 41 && dolbrace_state != DOLBRACE_QUOTE && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ADVANCE_CHAR (string, slen, i); + else + { + si = i + 1; + i = skip_single_quoted (string, slen, si); + } + + continue; + } +#endif + + /* move past this character, which was not special. */ + ADVANCE_CHAR (string, slen, i); + + /* This logic must agree with parse.y:parse_matched_pair, since they + share the same defines. */ + if (dolbrace_state == DOLBRACE_PARAM && c == '%' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && c == '#' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && c == '/' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && c == '^' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && c == ',' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", c) != 0) + dolbrace_state = DOLBRACE_OP; + else if (dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", c) == 0) + dolbrace_state = DOLBRACE_WORD; + } + + if (c == 0 && nesting_level) + { + if (no_longjmp_on_fatal_error == 0) + { /* { */ + report_error (_("bad substitution: no closing `%s' in %s"), "}", string); + last_command_exit_value = EXECUTION_FAILURE; + exp_jump_to_top_level (DISCARD); + } + else + { + *sindex = i; + return ((char *)NULL); + } + } + + result = (flags & SX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i); + *sindex = i; + + return (result); +} + +/* Remove backslashes which are quoting backquotes from STRING. Modifies + STRING, and returns a pointer to it. */ +char * +de_backslash (string) + char *string; +{ + register size_t slen; + register int i, j, prev_i; + DECLARE_MBSTATE; + + slen = strlen (string); + i = j = 0; + + /* Loop copying string[i] to string[j], i >= j. */ + while (i < slen) + { + if (string[i] == '\\' && (string[i + 1] == '`' || string[i + 1] == '\\' || + string[i + 1] == '$')) + i++; + prev_i = i; + ADVANCE_CHAR (string, slen, i); + if (j < prev_i) + do string[j++] = string[prev_i++]; while (prev_i < i); + else + j = i; + } + string[j] = '\0'; + + return (string); +} + +#if 0 +/*UNUSED*/ +/* Replace instances of \! in a string with !. */ +void +unquote_bang (string) + char *string; +{ + register int i, j; + register char *temp; + + temp = (char *)xmalloc (1 + strlen (string)); + + for (i = 0, j = 0; (temp[j] = string[i]); i++, j++) + { + if (string[i] == '\\' && string[i + 1] == '!') + { + temp[j] = '!'; + i++; + } + } + strcpy (string, temp); + free (temp); +} +#endif + +#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0) + +/* This function assumes s[i] == open; returns with s[ret] == close; used to + parse array subscripts. FLAGS & 1 means to not attempt to skip over + matched pairs of quotes or backquotes, or skip word expansions; it is + intended to be used after expansion has been performed and during final + assignment parsing (see arrayfunc.c:assign_compound_array_list()). */ +static int +skip_matched_pair (string, start, open, close, flags) + const char *string; + int start, open, close, flags; +{ + int i, pass_next, backq, si, c, count; + size_t slen; + char *temp, *ss; + DECLARE_MBSTATE; + + slen = strlen (string + start) + start; + no_longjmp_on_fatal_error = 1; + + i = start + 1; /* skip over leading bracket */ + count = 1; + pass_next = backq = 0; + ss = (char *)string; + while (c = string[i]) + { + if (pass_next) + { + pass_next = 0; + if (c == 0) + CQ_RETURN(i); + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (backq) + { + if (c == '`') + backq = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if ((flags & 1) == 0 && c == '`') + { + backq = 1; + i++; + continue; + } + else if ((flags & 1) == 0 && c == open) + { + count++; + i++; + continue; + } + else if (c == close) + { + count--; + if (count == 0) + break; + i++; + continue; + } + else if ((flags & 1) == 0 && (c == '\'' || c == '"')) + { + i = (c == '\'') ? skip_single_quoted (ss, slen, ++i) + : skip_double_quoted (ss, slen, ++i); + /* no increment, the skip functions increment past the closing quote. */ + } + else if ((flags&1) == 0 && c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE)) + { + si = i + 2; + if (string[si] == '\0') + CQ_RETURN(si); + + if (string[i+1] == LPAREN) + temp = extract_delimited_string (ss, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */ + else + temp = extract_dollar_brace_string (ss, &si, 0, SX_NOALLOC); + i = si; + if (string[i] == '\0') /* don't increment i past EOS in loop */ + break; + i++; + continue; + } + else + ADVANCE_CHAR (string, slen, i); + } + + CQ_RETURN(i); +} + +#if defined (ARRAY_VARS) +int +skipsubscript (string, start, flags) + const char *string; + int start, flags; +{ + return (skip_matched_pair (string, start, '[', ']', flags)); +} +#endif + +/* Skip characters in STRING until we find a character in DELIMS, and return + the index of that character. START is the index into string at which we + begin. This is similar in spirit to strpbrk, but it returns an index into + STRING and takes a starting index. This little piece of code knows quite + a lot of shell syntax. It's very similar to skip_double_quoted and other + functions of that ilk. */ +int +skip_to_delim (string, start, delims, flags) + char *string; + int start; + char *delims; + int flags; +{ + int i, pass_next, backq, si, c, invert, skipquote, skipcmd; + size_t slen; + char *temp, open[3]; + DECLARE_MBSTATE; + + slen = strlen (string + start) + start; + if (flags & SD_NOJMP) + no_longjmp_on_fatal_error = 1; + invert = (flags & SD_INVERT); + skipcmd = (flags & SD_NOSKIPCMD) == 0; + + i = start; + pass_next = backq = 0; + while (c = string[i]) + { + /* If this is non-zero, we should not let quote characters be delimiters + and the current character is a single or double quote. We should not + test whether or not it's a delimiter until after we skip single- or + double-quoted strings. */ + skipquote = ((flags & SD_NOQUOTEDELIM) && (c == '\'' || c =='"')); + if (pass_next) + { + pass_next = 0; + if (c == 0) + CQ_RETURN(i); + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (backq) + { + if (c == '`') + backq = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '`') + { + backq = 1; + i++; + continue; + } + else if (skipquote == 0 && invert == 0 && member (c, delims)) + break; + else if (c == '\'' || c == '"') + { + i = (c == '\'') ? skip_single_quoted (string, slen, ++i) + : skip_double_quoted (string, slen, ++i); + /* no increment, the skip functions increment past the closing quote. */ + } + else if (c == '$' && ((skipcmd && string[i+1] == LPAREN) || string[i+1] == LBRACE)) + { + si = i + 2; + if (string[si] == '\0') + CQ_RETURN(si); + + if (string[i+1] == LPAREN) + temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */ + else + temp = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC); + i = si; + if (string[i] == '\0') /* don't increment i past EOS in loop */ + break; + i++; + continue; + } +#if defined (PROCESS_SUBSTITUTION) + else if (skipcmd && (c == '<' || c == '>') && string[i+1] == LPAREN) + { + si = i + 2; + if (string[si] == '\0') + CQ_RETURN(si); + temp = extract_process_subst (string, (c == '<') ? "<(" : ">(", &si); + free (temp); /* no SX_ALLOC here */ + i = si; + if (string[i] == '\0') + break; + i++; + continue; + } +#endif /* PROCESS_SUBSTITUTION */ +#if defined (EXTENDED_GLOB) + else if ((flags & SD_EXTGLOB) && extended_glob && string[i+1] == LPAREN && member (c, "?*+!@")) + { + si = i + 2; + if (string[si] == '\0') + CQ_RETURN(si); + + open[0] = c; + open[1] = LPAREN; + open[2] = '\0'; + temp = extract_delimited_string (string, &si, open, "(", ")", SX_NOALLOC); /* ) */ + + i = si; + if (string[i] == '\0') /* don't increment i past EOS in loop */ + break; + i++; + continue; + } +#endif + else if ((skipquote || invert) && (member (c, delims) == 0)) + break; + else + ADVANCE_CHAR (string, slen, i); + } + + CQ_RETURN(i); +} + +#if defined (READLINE) +/* Return 1 if the portion of STRING ending at EINDEX is quoted (there is + an unclosed quoted string), or if the character at EINDEX is quoted + by a backslash. NO_LONGJMP_ON_FATAL_ERROR is used to flag that the various + single and double-quoted string parsing functions should not return an + error if there are unclosed quotes or braces. The characters that this + recognizes need to be the same as the contents of + rl_completer_quote_characters. */ + +int +char_is_quoted (string, eindex) + char *string; + int eindex; +{ + int i, pass_next, c; + size_t slen; + DECLARE_MBSTATE; + + slen = strlen (string); + no_longjmp_on_fatal_error = 1; + i = pass_next = 0; + while (i <= eindex) + { + c = string[i]; + + if (pass_next) + { + pass_next = 0; + if (i >= eindex) /* XXX was if (i >= eindex - 1) */ + CQ_RETURN(1); + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (c == '\'' || c == '"') + { + i = (c == '\'') ? skip_single_quoted (string, slen, ++i) + : skip_double_quoted (string, slen, ++i); + if (i > eindex) + CQ_RETURN(1); + /* no increment, the skip_xxx functions go one past end */ + } + else + ADVANCE_CHAR (string, slen, i); + } + + CQ_RETURN(0); +} + +int +unclosed_pair (string, eindex, openstr) + char *string; + int eindex; + char *openstr; +{ + int i, pass_next, openc, olen; + size_t slen; + DECLARE_MBSTATE; + + slen = strlen (string); + olen = strlen (openstr); + i = pass_next = openc = 0; + while (i <= eindex) + { + if (pass_next) + { + pass_next = 0; + if (i >= eindex) /* XXX was if (i >= eindex - 1) */ + return 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (string[i] == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (STREQN (string + i, openstr, olen)) + { + openc = 1 - openc; + i += olen; + } + else if (string[i] == '\'' || string[i] == '"') + { + i = (string[i] == '\'') ? skip_single_quoted (string, slen, i) + : skip_double_quoted (string, slen, i); + if (i > eindex) + return 0; + } + else + ADVANCE_CHAR (string, slen, i); + } + return (openc); +} + +/* Split STRING (length SLEN) at DELIMS, and return a WORD_LIST with the + individual words. If DELIMS is NULL, the current value of $IFS is used + to split the string, and the function follows the shell field splitting + rules. SENTINEL is an index to look for. NWP, if non-NULL, + gets the number of words in the returned list. CWP, if non-NULL, gets + the index of the word containing SENTINEL. Non-whitespace chars in + DELIMS delimit separate fields. */ +WORD_LIST * +split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp) + char *string; + int slen; + char *delims; + int sentinel, flags; + int *nwp, *cwp; +{ + int ts, te, i, nw, cw, ifs_split, dflags; + char *token, *d, *d2; + WORD_LIST *ret, *tl; + + if (string == 0 || *string == '\0') + { + if (nwp) + *nwp = 0; + if (cwp) + *cwp = 0; + return ((WORD_LIST *)NULL); + } + + d = (delims == 0) ? ifs_value : delims; + ifs_split = delims == 0; + + /* Make d2 the non-whitespace characters in delims */ + d2 = 0; + if (delims) + { + size_t slength; +#if defined (HANDLE_MULTIBYTE) + size_t mblength = 1; +#endif + DECLARE_MBSTATE; + + slength = strlen (delims); + d2 = (char *)xmalloc (slength + 1); + i = ts = 0; + while (delims[i]) + { +#if defined (HANDLE_MULTIBYTE) + mbstate_t state_bak; + state_bak = state; + mblength = MBRLEN (delims + i, slength, &state); + if (MB_INVALIDCH (mblength)) + state = state_bak; + else if (mblength > 1) + { + memcpy (d2 + ts, delims + i, mblength); + ts += mblength; + i += mblength; + slength -= mblength; + continue; + } +#endif + if (whitespace (delims[i]) == 0) + d2[ts++] = delims[i]; + + i++; + slength--; + } + d2[ts] = '\0'; + } + + ret = (WORD_LIST *)NULL; + + /* Remove sequences of whitespace characters at the start of the string, as + long as those characters are delimiters. */ + for (i = 0; member (string[i], d) && spctabnl (string[i]); i++) + ; + if (string[i] == '\0') + return (ret); + + ts = i; + nw = 0; + cw = -1; + dflags = flags|SD_NOJMP; + while (1) + { + te = skip_to_delim (string, ts, d, dflags); + + /* If we have a non-whitespace delimiter character, use it to make a + separate field. This is just about what $IFS splitting does and + is closer to the behavior of the shell parser. */ + if (ts == te && d2 && member (string[ts], d2)) + { + te = ts + 1; + /* If we're using IFS splitting, the non-whitespace delimiter char + and any additional IFS whitespace delimits a field. */ + if (ifs_split) + while (member (string[te], d) && spctabnl (string[te])) + te++; + else + while (member (string[te], d2)) + te++; + } + + token = substring (string, ts, te); + + ret = add_string_to_list (token, ret); + free (token); + nw++; + + if (sentinel >= ts && sentinel <= te) + cw = nw; + + /* If the cursor is at whitespace just before word start, set the + sentinel word to the current word. */ + if (cwp && cw == -1 && sentinel == ts-1) + cw = nw; + + /* If the cursor is at whitespace between two words, make a new, empty + word, add it before (well, after, since the list is in reverse order) + the word we just added, and set the current word to that one. */ + if (cwp && cw == -1 && sentinel < ts) + { + tl = make_word_list (make_word (""), ret->next); + ret->next = tl; + cw = nw; + nw++; + } + + if (string[te] == 0) + break; + + i = te; + while (member (string[i], d) && (ifs_split || spctabnl(string[i]))) + i++; + + if (string[i]) + ts = i; + else + break; + } + + /* Special case for SENTINEL at the end of STRING. If we haven't found + the word containing SENTINEL yet, and the index we're looking for is at + the end of STRING (or past the end of the previously-found token, + possible if the end of the line is composed solely of IFS whitespace) + add an additional null argument and set the current word pointer to that. */ + if (cwp && cw == -1 && (sentinel >= slen || sentinel >= te)) + { + if (whitespace (string[sentinel - 1])) + { + token = ""; + ret = add_string_to_list (token, ret); + nw++; + } + cw = nw; + } + + if (nwp) + *nwp = nw; + if (cwp) + *cwp = cw; + + return (REVERSE_LIST (ret, WORD_LIST *)); +} +#endif /* READLINE */ + +#if 0 +/* UNUSED */ +/* Extract the name of the variable to bind to from the assignment string. */ +char * +assignment_name (string) + char *string; +{ + int offset; + char *temp; + + offset = assignment (string, 0); + if (offset == 0) + return (char *)NULL; + temp = substring (string, 0, offset); + return (temp); +} +#endif + +/* **************************************************************** */ +/* */ +/* Functions to convert strings to WORD_LISTs and vice versa */ +/* */ +/* **************************************************************** */ + +/* Return a single string of all the words in LIST. SEP is the separator + to put between individual elements of LIST in the output string. */ +char * +string_list_internal (list, sep) + WORD_LIST *list; + char *sep; +{ + register WORD_LIST *t; + char *result, *r; + int word_len, sep_len, result_size; + + if (list == 0) + return ((char *)NULL); + + /* Short-circuit quickly if we don't need to separate anything. */ + if (list->next == 0) + return (savestring (list->word->word)); + + /* This is nearly always called with either sep[0] == 0 or sep[1] == 0. */ + sep_len = STRLEN (sep); + result_size = 0; + + for (t = list; t; t = t->next) + { + if (t != list) + result_size += sep_len; + result_size += strlen (t->word->word); + } + + r = result = (char *)xmalloc (result_size + 1); + + for (t = list; t; t = t->next) + { + if (t != list && sep_len) + { + if (sep_len > 1) + { + FASTCOPY (sep, r, sep_len); + r += sep_len; + } + else + *r++ = sep[0]; + } + + word_len = strlen (t->word->word); + FASTCOPY (t->word->word, r, word_len); + r += word_len; + } + + *r = '\0'; + return (result); +} + +/* Return a single string of all the words present in LIST, separating + each word with a space. */ +char * +string_list (list) + WORD_LIST *list; +{ + return (string_list_internal (list, " ")); +} + +/* An external interface that can be used by the rest of the shell to + obtain a string containing the first character in $IFS. Handles all + the multibyte complications. If LENP is non-null, it is set to the + length of the returned string. */ +char * +ifs_firstchar (lenp) + int *lenp; +{ + char *ret; + int len; + + ret = xmalloc (MB_LEN_MAX + 1); +#if defined (HANDLE_MULTIBYTE) + if (ifs_firstc_len == 1) + { + ret[0] = ifs_firstc[0]; + ret[1] = '\0'; + len = ret[0] ? 1 : 0; + } + else + { + memcpy (ret, ifs_firstc, ifs_firstc_len); + ret[len = ifs_firstc_len] = '\0'; + } +#else + ret[0] = ifs_firstc; + ret[1] = '\0'; + len = ret[0] ? 0 : 1; +#endif + + if (lenp) + *lenp = len; + + return ret; +} + +/* Return a single string of all the words present in LIST, obeying the + quoting rules for "$*", to wit: (P1003.2, draft 11, 3.5.2) "If the + expansion [of $*] appears within a double quoted string, it expands + to a single field with the value of each parameter separated by the + first character of the IFS variable, or by a if IFS is unset." */ +char * +string_list_dollar_star (list) + WORD_LIST *list; +{ + char *ret; +#if defined (HANDLE_MULTIBYTE) +# if defined (__GNUC__) + char sep[MB_CUR_MAX + 1]; +# else + char *sep = 0; +# endif +#else + char sep[2]; +#endif + +#if defined (HANDLE_MULTIBYTE) +# if !defined (__GNUC__) + sep = (char *)xmalloc (MB_CUR_MAX + 1); +# endif /* !__GNUC__ */ + if (ifs_firstc_len == 1) + { + sep[0] = ifs_firstc[0]; + sep[1] = '\0'; + } + else + { + memcpy (sep, ifs_firstc, ifs_firstc_len); + sep[ifs_firstc_len] = '\0'; + } +#else + sep[0] = ifs_firstc; + sep[1] = '\0'; +#endif + + ret = string_list_internal (list, sep); +#if defined (HANDLE_MULTIBYTE) && !defined (__GNUC__) + free (sep); +#endif + return ret; +} + +/* Turn $@ into a string. If (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + is non-zero, the $@ appears within double quotes, and we should quote + the list before converting it into a string. If IFS is unset, and the + word is not quoted, we just need to quote CTLESC and CTLNUL characters + in the words in the list, because the default value of $IFS is + , IFS characters in the words in the list should + also be split. If IFS is null, and the word is not quoted, we need + to quote the words in the list to preserve the positional parameters + exactly. */ +char * +string_list_dollar_at (list, quoted) + WORD_LIST *list; + int quoted; +{ + char *ifs, *ret; +#if defined (HANDLE_MULTIBYTE) +# if defined (__GNUC__) + char sep[MB_CUR_MAX + 1]; +# else + char *sep = 0; +# endif /* !__GNUC__ */ +#else + char sep[2]; +#endif + WORD_LIST *tlist; + + /* XXX this could just be ifs = ifs_value; */ + ifs = ifs_var ? value_cell (ifs_var) : (char *)0; + +#if defined (HANDLE_MULTIBYTE) +# if !defined (__GNUC__) + sep = (char *)xmalloc (MB_CUR_MAX + 1); +# endif /* !__GNUC__ */ + if (ifs && *ifs) + { + if (ifs_firstc_len == 1) + { + sep[0] = ifs_firstc[0]; + sep[1] = '\0'; + } + else + { + memcpy (sep, ifs_firstc, ifs_firstc_len); + sep[ifs_firstc_len] = '\0'; + } + } + else + { + sep[0] = ' '; + sep[1] = '\0'; + } +#else + sep[0] = (ifs == 0 || *ifs == 0) ? ' ' : *ifs; + sep[1] = '\0'; +#endif + + /* XXX -- why call quote_list if ifs == 0? we can get away without doing + it now that quote_escapes quotes spaces */ + tlist = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE)) + ? quote_list (list) + : list_quote_escapes (list); + + ret = string_list_internal (tlist, sep); +#if defined (HANDLE_MULTIBYTE) && !defined (__GNUC__) + free (sep); +#endif + return ret; +} + +/* Turn the positional paramters into a string, understanding quoting and + the various subtleties of using the first character of $IFS as the + separator. Calls string_list_dollar_at, string_list_dollar_star, and + string_list as appropriate. */ +char * +string_list_pos_params (pchar, list, quoted) + int pchar; + WORD_LIST *list; + int quoted; +{ + char *ret; + WORD_LIST *tlist; + + if (pchar == '*' && (quoted & Q_DOUBLE_QUOTES)) + { + tlist = quote_list (list); + word_list_remove_quoted_nulls (tlist); + ret = string_list_dollar_star (tlist); + } + else if (pchar == '*' && (quoted & Q_HERE_DOCUMENT)) + { + tlist = quote_list (list); + word_list_remove_quoted_nulls (tlist); + ret = string_list (tlist); + } + else if (pchar == '*') + { + /* Even when unquoted, string_list_dollar_star does the right thing + making sure that the first character of $IFS is used as the + separator. */ + ret = string_list_dollar_star (list); + } + else if (pchar == '@' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + /* We use string_list_dollar_at, but only if the string is quoted, since + that quotes the escapes if it's not, which we don't want. We could + use string_list (the old code did), but that doesn't do the right + thing if the first character of $IFS is not a space. We use + string_list_dollar_star if the string is unquoted so we make sure that + the elements of $@ are separated by the first character of $IFS for + later splitting. */ + ret = string_list_dollar_at (list, quoted); + else if (pchar == '@') + ret = string_list_dollar_star (list); + else + ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (list) : list); + + return ret; +} + +/* Return the list of words present in STRING. Separate the string into + words at any of the characters found in SEPARATORS. If QUOTED is + non-zero then word in the list will have its quoted flag set, otherwise + the quoted flag is left as make_word () deemed fit. + + This obeys the P1003.2 word splitting semantics. If `separators' is + exactly , then the splitting algorithm is that of + the Bourne shell, which treats any sequence of characters from `separators' + as a delimiter. If IFS is unset, which results in `separators' being set + to "", no splitting occurs. If separators has some other value, the + following rules are applied (`IFS white space' means zero or more + occurrences of , , or , as long as those characters + are in `separators'): + + 1) IFS white space is ignored at the start and the end of the + string. + 2) Each occurrence of a character in `separators' that is not + IFS white space, along with any adjacent occurrences of + IFS white space delimits a field. + 3) Any nonzero-length sequence of IFS white space delimits a field. + */ + +/* BEWARE! list_string strips null arguments. Don't call it twice and + expect to have "" preserved! */ + +/* This performs word splitting and quoted null character removal on + STRING. */ +#define issep(c) \ + (((separators)[0]) ? ((separators)[1] ? isifs(c) \ + : (c) == (separators)[0]) \ + : 0) + +WORD_LIST * +list_string (string, separators, quoted) + register char *string, *separators; + int quoted; +{ + WORD_LIST *result; + WORD_DESC *t; + char *current_word, *s; + int sindex, sh_style_split, whitesep, xflags; + size_t slen; + + if (!string || !*string) + return ((WORD_LIST *)NULL); + + sh_style_split = separators && separators[0] == ' ' && + separators[1] == '\t' && + separators[2] == '\n' && + separators[3] == '\0'; + for (xflags = 0, s = ifs_value; s && *s; s++) + { + if (*s == CTLESC) xflags |= SX_NOCTLESC; + else if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL; + } + + slen = 0; + /* Remove sequences of whitespace at the beginning of STRING, as + long as those characters appear in IFS. Do not do this if + STRING is quoted or if there are no separator characters. */ + if (!quoted || !separators || !*separators) + { + for (s = string; *s && spctabnl (*s) && issep (*s); s++); + + if (!*s) + return ((WORD_LIST *)NULL); + + string = s; + } + + /* OK, now STRING points to a word that does not begin with white space. + The splitting algorithm is: + extract a word, stopping at a separator + skip sequences of spc, tab, or nl as long as they are separators + This obeys the field splitting rules in Posix.2. */ + slen = (MB_CUR_MAX > 1) ? strlen (string) : 1; + for (result = (WORD_LIST *)NULL, sindex = 0; string[sindex]; ) + { + /* Don't need string length in ADVANCE_CHAR or string_extract_verbatim + unless multibyte chars are possible. */ + current_word = string_extract_verbatim (string, slen, &sindex, separators, xflags); + if (current_word == 0) + break; + + /* If we have a quoted empty string, add a quoted null argument. We + want to preserve the quoted null character iff this is a quoted + empty string; otherwise the quoted null characters are removed + below. */ + if (QUOTED_NULL (current_word)) + { + t = alloc_word_desc (); + t->word = make_quoted_char ('\0'); + t->flags |= W_QUOTED|W_HASQUOTEDNULL; + result = make_word_list (t, result); + } + else if (current_word[0] != '\0') + { + /* If we have something, then add it regardless. However, + perform quoted null character removal on the current word. */ + remove_quoted_nulls (current_word); + result = add_string_to_list (current_word, result); + result->word->flags &= ~W_HASQUOTEDNULL; /* just to be sure */ + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + result->word->flags |= W_QUOTED; + } + + /* If we're not doing sequences of separators in the traditional + Bourne shell style, then add a quoted null argument. */ + else if (!sh_style_split && !spctabnl (string[sindex])) + { + t = alloc_word_desc (); + t->word = make_quoted_char ('\0'); + t->flags |= W_QUOTED|W_HASQUOTEDNULL; + result = make_word_list (t, result); + } + + free (current_word); + + /* Note whether or not the separator is IFS whitespace, used later. */ + whitesep = string[sindex] && spctabnl (string[sindex]); + + /* Move past the current separator character. */ + if (string[sindex]) + { + DECLARE_MBSTATE; + ADVANCE_CHAR (string, slen, sindex); + } + + /* Now skip sequences of space, tab, or newline characters if they are + in the list of separators. */ + while (string[sindex] && spctabnl (string[sindex]) && issep (string[sindex])) + sindex++; + + /* If the first separator was IFS whitespace and the current character + is a non-whitespace IFS character, it should be part of the current + field delimiter, not a separate delimiter that would result in an + empty field. Look at POSIX.2, 3.6.5, (3)(b). */ + if (string[sindex] && whitesep && issep (string[sindex]) && !spctabnl (string[sindex])) + { + sindex++; + /* An IFS character that is not IFS white space, along with any + adjacent IFS white space, shall delimit a field. (SUSv3) */ + while (string[sindex] && spctabnl (string[sindex]) && isifs (string[sindex])) + sindex++; + } + } + return (REVERSE_LIST (result, WORD_LIST *)); +} + +/* Parse a single word from STRING, using SEPARATORS to separate fields. + ENDPTR is set to the first character after the word. This is used by + the `read' builtin. This is never called with SEPARATORS != $IFS; + it should be simplified. + + XXX - this function is very similar to list_string; they should be + combined - XXX */ +char * +get_word_from_string (stringp, separators, endptr) + char **stringp, *separators, **endptr; +{ + register char *s; + char *current_word; + int sindex, sh_style_split, whitesep, xflags; + size_t slen; + + if (!stringp || !*stringp || !**stringp) + return ((char *)NULL); + + sh_style_split = separators && separators[0] == ' ' && + separators[1] == '\t' && + separators[2] == '\n' && + separators[3] == '\0'; + for (xflags = 0, s = ifs_value; s && *s; s++) + { + if (*s == CTLESC) xflags |= SX_NOCTLESC; + if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL; + } + + s = *stringp; + slen = 0; + + /* Remove sequences of whitespace at the beginning of STRING, as + long as those characters appear in IFS. */ + if (sh_style_split || !separators || !*separators) + { + for (; *s && spctabnl (*s) && isifs (*s); s++); + + /* If the string is nothing but whitespace, update it and return. */ + if (!*s) + { + *stringp = s; + if (endptr) + *endptr = s; + return ((char *)NULL); + } + } + + /* OK, S points to a word that does not begin with white space. + Now extract a word, stopping at a separator, save a pointer to + the first character after the word, then skip sequences of spc, + tab, or nl as long as they are separators. + + This obeys the field splitting rules in Posix.2. */ + sindex = 0; + /* Don't need string length in ADVANCE_CHAR or string_extract_verbatim + unless multibyte chars are possible. */ + slen = (MB_CUR_MAX > 1) ? strlen (s) : 1; + current_word = string_extract_verbatim (s, slen, &sindex, separators, xflags); + + /* Set ENDPTR to the first character after the end of the word. */ + if (endptr) + *endptr = s + sindex; + + /* Note whether or not the separator is IFS whitespace, used later. */ + whitesep = s[sindex] && spctabnl (s[sindex]); + + /* Move past the current separator character. */ + if (s[sindex]) + { + DECLARE_MBSTATE; + ADVANCE_CHAR (s, slen, sindex); + } + + /* Now skip sequences of space, tab, or newline characters if they are + in the list of separators. */ + while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex])) + sindex++; + + /* If the first separator was IFS whitespace and the current character is + a non-whitespace IFS character, it should be part of the current field + delimiter, not a separate delimiter that would result in an empty field. + Look at POSIX.2, 3.6.5, (3)(b). */ + if (s[sindex] && whitesep && isifs (s[sindex]) && !spctabnl (s[sindex])) + { + sindex++; + /* An IFS character that is not IFS white space, along with any adjacent + IFS white space, shall delimit a field. */ + while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex])) + sindex++; + } + + /* Update STRING to point to the next field. */ + *stringp = s + sindex; + return (current_word); +} + +/* Remove IFS white space at the end of STRING. Start at the end + of the string and walk backwards until the beginning of the string + or we find a character that's not IFS white space and not CTLESC. + Only let CTLESC escape a white space character if SAW_ESCAPE is + non-zero. */ +char * +strip_trailing_ifs_whitespace (string, separators, saw_escape) + char *string, *separators; + int saw_escape; +{ + char *s; + + s = string + STRLEN (string) - 1; + while (s > string && ((spctabnl (*s) && isifs (*s)) || + (saw_escape && *s == CTLESC && spctabnl (s[1])))) + s--; + *++s = '\0'; + return string; +} + +#if 0 +/* UNUSED */ +/* Split STRING into words at whitespace. Obeys shell-style quoting with + backslashes, single and double quotes. */ +WORD_LIST * +list_string_with_quotes (string) + char *string; +{ + WORD_LIST *list; + char *token, *s; + size_t s_len; + int c, i, tokstart, len; + + for (s = string; s && *s && spctabnl (*s); s++) + ; + if (s == 0 || *s == 0) + return ((WORD_LIST *)NULL); + + s_len = strlen (s); + tokstart = i = 0; + list = (WORD_LIST *)NULL; + while (1) + { + c = s[i]; + if (c == '\\') + { + i++; + if (s[i]) + i++; + } + else if (c == '\'') + i = skip_single_quoted (s, s_len, ++i); + else if (c == '"') + i = skip_double_quoted (s, s_len, ++i); + else if (c == 0 || spctabnl (c)) + { + /* We have found the end of a token. Make a word out of it and + add it to the word list. */ + token = substring (s, tokstart, i); + list = add_string_to_list (token, list); + free (token); + while (spctabnl (s[i])) + i++; + if (s[i]) + tokstart = i; + else + break; + } + else + i++; /* normal character */ + } + return (REVERSE_LIST (list, WORD_LIST *)); +} +#endif + +/********************************************************/ +/* */ +/* Functions to perform assignment statements */ +/* */ +/********************************************************/ + +#if defined (ARRAY_VARS) +static SHELL_VAR * +do_compound_assignment (name, value, flags) + char *name, *value; + int flags; +{ + SHELL_VAR *v; + int mklocal, mkassoc; + WORD_LIST *list; + + mklocal = flags & ASS_MKLOCAL; + mkassoc = flags & ASS_MKASSOC; + + if (mklocal && variable_context) + { + v = find_variable (name); + list = expand_compound_array_assignment (v, value, flags); + if (mkassoc) + v = make_local_assoc_variable (name); + else if (v == 0 || (array_p (v) == 0 && assoc_p (v) == 0) || v->context != variable_context) + v = make_local_array_variable (name); + assign_compound_array_list (v, list, flags); + } + else + v = assign_array_from_string (name, value, flags); + + return (v); +} +#endif + +/* Given STRING, an assignment string, get the value of the right side + of the `=', and bind it to the left side. If EXPAND is true, then + perform parameter expansion, command substitution, and arithmetic + expansion on the right-hand side. Perform tilde expansion in any + case. Do not perform word splitting on the result of expansion. */ +static int +do_assignment_internal (word, expand) + const WORD_DESC *word; + int expand; +{ + int offset, appendop, assign_list, aflags, retval; + char *name, *value, *temp; + SHELL_VAR *entry; +#if defined (ARRAY_VARS) + char *t; + int ni; +#endif + const char *string; + + if (word == 0 || word->word == 0) + return 0; + + appendop = assign_list = aflags = 0; + string = word->word; + offset = assignment (string, 0); + name = savestring (string); + value = (char *)NULL; + + if (name[offset] == '=') + { + if (name[offset - 1] == '+') + { + appendop = 1; + name[offset - 1] = '\0'; + } + + name[offset] = 0; /* might need this set later */ + temp = name + offset + 1; + +#if defined (ARRAY_VARS) + if (expand && (word->flags & W_COMPASSIGN)) + { + assign_list = ni = 1; + value = extract_array_assignment_list (temp, &ni); + } + else +#endif + if (expand && temp[0]) + value = expand_string_if_necessary (temp, 0, expand_string_assignment); + else + value = savestring (temp); + } + + if (value == 0) + { + value = (char *)xmalloc (1); + value[0] = '\0'; + } + + if (echo_command_at_execute) + { + if (appendop) + name[offset - 1] = '+'; + xtrace_print_assignment (name, value, assign_list, 1); + if (appendop) + name[offset - 1] = '\0'; + } + +#define ASSIGN_RETURN(r) do { FREE (value); free (name); return (r); } while (0) + + if (appendop) + aflags |= ASS_APPEND; + +#if defined (ARRAY_VARS) + if (t = mbschr (name, '[')) /*]*/ + { + if (assign_list) + { + report_error (_("%s: cannot assign list to array member"), name); + ASSIGN_RETURN (0); + } + entry = assign_array_element (name, value, aflags); + if (entry == 0) + ASSIGN_RETURN (0); + } + else if (assign_list) + { + if (word->flags & W_ASSIGNARG) + aflags |= ASS_MKLOCAL; + if (word->flags & W_ASSIGNASSOC) + aflags |= ASS_MKASSOC; + entry = do_compound_assignment (name, value, aflags); + } + else +#endif /* ARRAY_VARS */ + entry = bind_variable (name, value, aflags); + + stupidly_hack_special_variables (name); + +#if 1 + /* Return 1 if the assignment seems to have been performed correctly. */ + if (entry == 0 || readonly_p (entry)) + retval = 0; /* assignment failure */ + else if (noassign_p (entry)) + { + last_command_exit_value = EXECUTION_FAILURE; + retval = 1; /* error status, but not assignment failure */ + } + else + retval = 1; + + if (entry && retval != 0 && noassign_p (entry) == 0) + VUNSETATTR (entry, att_invisible); + + ASSIGN_RETURN (retval); +#else + if (entry) + VUNSETATTR (entry, att_invisible); + + ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0); +#endif +} + +/* Perform the assignment statement in STRING, and expand the + right side by doing tilde, command and parameter expansion. */ +int +do_assignment (string) + char *string; +{ + WORD_DESC td; + + td.flags = W_ASSIGNMENT; + td.word = string; + + return do_assignment_internal (&td, 1); +} + +int +do_word_assignment (word, flags) + WORD_DESC *word; + int flags; +{ + return do_assignment_internal (word, 1); +} + +/* Given STRING, an assignment string, get the value of the right side + of the `=', and bind it to the left side. Do not perform any word + expansions on the right hand side. */ +int +do_assignment_no_expand (string) + char *string; +{ + WORD_DESC td; + + td.flags = W_ASSIGNMENT; + td.word = string; + + return (do_assignment_internal (&td, 0)); +} + +/*************************************************** + * * + * Functions to manage the positional parameters * + * * + ***************************************************/ + +/* Return the word list that corresponds to `$*'. */ +WORD_LIST * +list_rest_of_args () +{ + register WORD_LIST *list, *args; + int i; + + /* Break out of the loop as soon as one of the dollar variables is null. */ + for (i = 1, list = (WORD_LIST *)NULL; i < 10 && dollar_vars[i]; i++) + list = make_word_list (make_bare_word (dollar_vars[i]), list); + + for (args = rest_of_args; args; args = args->next) + list = make_word_list (make_bare_word (args->word->word), list); + + return (REVERSE_LIST (list, WORD_LIST *)); +} + +int +number_of_args () +{ + register WORD_LIST *list; + int n; + + for (n = 0; n < 9 && dollar_vars[n+1]; n++) + ; + for (list = rest_of_args; list; list = list->next) + n++; + return n; +} + +/* Return the value of a positional parameter. This handles values > 10. */ +char * +get_dollar_var_value (ind) + intmax_t ind; +{ + char *temp; + WORD_LIST *p; + + if (ind < 10) + temp = dollar_vars[ind] ? savestring (dollar_vars[ind]) : (char *)NULL; + else /* We want something like ${11} */ + { + ind -= 10; + for (p = rest_of_args; p && ind--; p = p->next) + ; + temp = p ? savestring (p->word->word) : (char *)NULL; + } + return (temp); +} + +/* Make a single large string out of the dollar digit variables, + and the rest_of_args. If DOLLAR_STAR is 1, then obey the special + case of "$*" with respect to IFS. */ +char * +string_rest_of_args (dollar_star) + int dollar_star; +{ + register WORD_LIST *list; + char *string; + + list = list_rest_of_args (); + string = dollar_star ? string_list_dollar_star (list) : string_list (list); + dispose_words (list); + return (string); +} + +/* Return a string containing the positional parameters from START to + END, inclusive. If STRING[0] == '*', we obey the rules for $*, + which only makes a difference if QUOTED is non-zero. If QUOTED includes + Q_HERE_DOCUMENT or Q_DOUBLE_QUOTES, this returns a quoted list, otherwise + no quoting chars are added. */ +static char * +pos_params (string, start, end, quoted) + char *string; + int start, end, quoted; +{ + WORD_LIST *save, *params, *h, *t; + char *ret; + int i; + + /* see if we can short-circuit. if start == end, we want 0 parameters. */ + if (start == end) + return ((char *)NULL); + + save = params = list_rest_of_args (); + if (save == 0) + return ((char *)NULL); + + if (start == 0) /* handle ${@:0[:x]} specially */ + { + t = make_word_list (make_word (dollar_vars[0]), params); + save = params = t; + } + + for (i = start ? 1 : 0; params && i < start; i++) + params = params->next; + if (params == 0) + return ((char *)NULL); + for (h = t = params; params && i < end; i++) + { + t = params; + params = params->next; + } + + t->next = (WORD_LIST *)NULL; + + ret = string_list_pos_params (string[0], h, quoted); + + if (t != params) + t->next = params; + + dispose_words (save); + return (ret); +} + +/******************************************************************/ +/* */ +/* Functions to expand strings to strings or WORD_LISTs */ +/* */ +/******************************************************************/ + +#if defined (PROCESS_SUBSTITUTION) +#define EXP_CHAR(s) (s == '$' || s == '`' || s == '<' || s == '>' || s == CTLESC || s == '~') +#else +#define EXP_CHAR(s) (s == '$' || s == '`' || s == CTLESC || s == '~') +#endif + +/* If there are any characters in STRING that require full expansion, + then call FUNC to expand STRING; otherwise just perform quote + removal if necessary. This returns a new string. */ +static char * +expand_string_if_necessary (string, quoted, func) + char *string; + int quoted; + EXPFUNC *func; +{ + WORD_LIST *list; + size_t slen; + int i, saw_quote; + char *ret; + DECLARE_MBSTATE; + + /* Don't need string length for ADVANCE_CHAR unless multibyte chars possible. */ + slen = (MB_CUR_MAX > 1) ? strlen (string) : 0; + i = saw_quote = 0; + while (string[i]) + { + if (EXP_CHAR (string[i])) + break; + else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"') + saw_quote = 1; + ADVANCE_CHAR (string, slen, i); + } + + if (string[i]) + { + list = (*func) (string, quoted); + if (list) + { + ret = string_list (list); + dispose_words (list); + } + else + ret = (char *)NULL; + } + else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + ret = string_quote_removal (string, quoted); + else + ret = savestring (string); + + return ret; +} + +static inline char * +expand_string_to_string_internal (string, quoted, func) + char *string; + int quoted; + EXPFUNC *func; +{ + WORD_LIST *list; + char *ret; + + if (string == 0 || *string == '\0') + return ((char *)NULL); + + list = (*func) (string, quoted); + if (list) + { + ret = string_list (list); + dispose_words (list); + } + else + ret = (char *)NULL; + + return (ret); +} + +char * +expand_string_to_string (string, quoted) + char *string; + int quoted; +{ + return (expand_string_to_string_internal (string, quoted, expand_string)); +} + +char * +expand_string_unsplit_to_string (string, quoted) + char *string; + int quoted; +{ + return (expand_string_to_string_internal (string, quoted, expand_string_unsplit)); +} + +char * +expand_assignment_string_to_string (string, quoted) + char *string; + int quoted; +{ + return (expand_string_to_string_internal (string, quoted, expand_string_assignment)); +} + +char * +expand_arith_string (string, quoted) + char *string; + int quoted; +{ + return (expand_string_if_necessary (string, quoted, expand_string)); +} + +#if defined (COND_COMMAND) +/* Just remove backslashes in STRING. Returns a new string. */ +char * +remove_backslashes (string) + char *string; +{ + char *r, *ret, *s; + + r = ret = (char *)xmalloc (strlen (string) + 1); + for (s = string; s && *s; ) + { + if (*s == '\\') + s++; + if (*s == 0) + break; + *r++ = *s++; + } + *r = '\0'; + return ret; +} + +/* This needs better error handling. */ +/* Expand W for use as an argument to a unary or binary operator in a + [[...]] expression. If SPECIAL is 1, this is the rhs argument + to the != or == operator, and should be treated as a pattern. In + this case, we quote the string specially for the globbing code. If + SPECIAL is 2, this is an rhs argument for the =~ operator, and should + be quoted appropriately for regcomp/regexec. The caller is responsible + for removing the backslashes if the unquoted word is needed later. */ +char * +cond_expand_word (w, special) + WORD_DESC *w; + int special; +{ + char *r, *p; + WORD_LIST *l; + int qflags; + + if (w->word == 0 || w->word[0] == '\0') + return ((char *)NULL); + + w->flags |= W_NOSPLIT2; + l = call_expand_word_internal (w, 0, 0, (int *)0, (int *)0); + if (l) + { + if (special == 0) + { + dequote_list (l); + r = string_list (l); + } + else + { + qflags = QGLOB_CVTNULL; + if (special == 2) + qflags |= QGLOB_REGEXP; + p = string_list (l); + r = quote_string_for_globbing (p, qflags); + free (p); + } + dispose_words (l); + } + else + r = (char *)NULL; + + return r; +} +#endif + +/* Call expand_word_internal to expand W and handle error returns. + A convenience function for functions that don't want to handle + any errors or free any memory before aborting. */ +static WORD_LIST * +call_expand_word_internal (w, q, i, c, e) + WORD_DESC *w; + int q, i, *c, *e; +{ + WORD_LIST *result; + + result = expand_word_internal (w, q, i, c, e); + if (result == &expand_word_error || result == &expand_word_fatal) + { + /* By convention, each time this error is returned, w->word has + already been freed (it sometimes may not be in the fatal case, + but that doesn't result in a memory leak because we're going + to exit in most cases). */ + w->word = (char *)NULL; + last_command_exit_value = EXECUTION_FAILURE; + exp_jump_to_top_level ((result == &expand_word_error) ? DISCARD : FORCE_EOF); + /* NOTREACHED */ + } + else + return (result); +} + +/* Perform parameter expansion, command substitution, and arithmetic + expansion on STRING, as if it were a word. Leave the result quoted. + Since this does not perform word splitting, it leaves quoted nulls + in the result. */ +static WORD_LIST * +expand_string_internal (string, quoted) + char *string; + int quoted; +{ + WORD_DESC td; + WORD_LIST *tresult; + + if (string == 0 || *string == 0) + return ((WORD_LIST *)NULL); + + td.flags = 0; + td.word = savestring (string); + + tresult = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); + + FREE (td.word); + return (tresult); +} + +/* Expand STRING by performing parameter expansion, command substitution, + and arithmetic expansion. Dequote the resulting WORD_LIST before + returning it, but do not perform word splitting. The call to + remove_quoted_nulls () is in here because word splitting normally + takes care of quote removal. */ +WORD_LIST * +expand_string_unsplit (string, quoted) + char *string; + int quoted; +{ + WORD_LIST *value; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + expand_no_split_dollar_star = 1; + value = expand_string_internal (string, quoted); + expand_no_split_dollar_star = 0; + + if (value) + { + if (value->word) + { + remove_quoted_nulls (value->word->word); + value->word->flags &= ~W_HASQUOTEDNULL; + } + dequote_list (value); + } + return (value); +} + +/* Expand the rhs of an assignment statement */ +WORD_LIST * +expand_string_assignment (string, quoted) + char *string; + int quoted; +{ + WORD_DESC td; + WORD_LIST *value; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + expand_no_split_dollar_star = 1; + + td.flags = W_ASSIGNRHS; + td.word = savestring (string); + value = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); + FREE (td.word); + + expand_no_split_dollar_star = 0; + + if (value) + { + if (value->word) + { + remove_quoted_nulls (value->word->word); + value->word->flags &= ~W_HASQUOTEDNULL; + } + dequote_list (value); + } + return (value); +} + + +/* Expand one of the PS? prompt strings. This is a sort of combination of + expand_string_unsplit and expand_string_internal, but returns the + passed string when an error occurs. Might want to trap other calls + to jump_to_top_level here so we don't endlessly loop. */ +WORD_LIST * +expand_prompt_string (string, quoted, wflags) + char *string; + int quoted; + int wflags; +{ + WORD_LIST *value; + WORD_DESC td; + + if (string == 0 || *string == 0) + return ((WORD_LIST *)NULL); + + td.flags = wflags; + td.word = savestring (string); + + no_longjmp_on_fatal_error = 1; + value = expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); + no_longjmp_on_fatal_error = 0; + + if (value == &expand_word_error || value == &expand_word_fatal) + { + value = make_word_list (make_bare_word (string), (WORD_LIST *)NULL); + return value; + } + FREE (td.word); + if (value) + { + if (value->word) + { + remove_quoted_nulls (value->word->word); + value->word->flags &= ~W_HASQUOTEDNULL; + } + dequote_list (value); + } + return (value); +} + +/* Expand STRING just as if you were expanding a word, but do not dequote + the resultant WORD_LIST. This is called only from within this file, + and is used to correctly preserve quoted characters when expanding + things like ${1+"$@"}. This does parameter expansion, command + substitution, arithmetic expansion, and word splitting. */ +static WORD_LIST * +expand_string_leave_quoted (string, quoted) + char *string; + int quoted; +{ + WORD_LIST *tlist; + WORD_LIST *tresult; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + tlist = expand_string_internal (string, quoted); + + if (tlist) + { + tresult = word_list_split (tlist); + dispose_words (tlist); + return (tresult); + } + return ((WORD_LIST *)NULL); +} + +/* This does not perform word splitting or dequote the WORD_LIST + it returns. */ +static WORD_LIST * +expand_string_for_rhs (string, quoted, dollar_at_p, has_dollar_at) + char *string; + int quoted, *dollar_at_p, *has_dollar_at; +{ + WORD_DESC td; + WORD_LIST *tresult; + + if (string == 0 || *string == '\0') + return (WORD_LIST *)NULL; + + td.flags = W_NOSPLIT2; /* no splitting, remove "" and '' */ + td.word = string; + tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, has_dollar_at); + return (tresult); +} + +/* Expand STRING just as if you were expanding a word. This also returns + a list of words. Note that filename globbing is *NOT* done for word + or string expansion, just when the shell is expanding a command. This + does parameter expansion, command substitution, arithmetic expansion, + and word splitting. Dequote the resultant WORD_LIST before returning. */ +WORD_LIST * +expand_string (string, quoted) + char *string; + int quoted; +{ + WORD_LIST *result; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + result = expand_string_leave_quoted (string, quoted); + return (result ? dequote_list (result) : result); +} + +/*************************************************** + * * + * Functions to handle quoting chars * + * * + ***************************************************/ + +/* Conventions: + + A string with s[0] == CTLNUL && s[1] == 0 is a quoted null string. + The parser passes CTLNUL as CTLESC CTLNUL. */ + +/* Quote escape characters in string s, but no other characters. This is + used to protect CTLESC and CTLNUL in variable values from the rest of + the word expansion process after the variable is expanded (word splitting + and filename generation). If IFS is null, we quote spaces as well, just + in case we split on spaces later (in the case of unquoted $@, we will + eventually attempt to split the entire word on spaces). Corresponding + code exists in dequote_escapes. Even if we don't end up splitting on + spaces, quoting spaces is not a problem. This should never be called on + a string that is quoted with single or double quotes or part of a here + document (effectively double-quoted). */ +char * +quote_escapes (string) + char *string; +{ + register char *s, *t; + size_t slen; + char *result, *send; + int quote_spaces, skip_ctlesc, skip_ctlnul; + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + + quote_spaces = (ifs_value && *ifs_value == 0); + + for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++) + skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL; + + t = result = (char *)xmalloc ((slen * 2) + 1); + s = string; + + while (*s) + { + if ((skip_ctlesc == 0 && *s == CTLESC) || (skip_ctlnul == 0 && *s == CTLNUL) || (quote_spaces && *s == ' ')) + *t++ = CTLESC; + COPY_CHAR_P (t, s, send); + } + *t = '\0'; + return (result); +} + +static WORD_LIST * +list_quote_escapes (list) + WORD_LIST *list; +{ + register WORD_LIST *w; + char *t; + + for (w = list; w; w = w->next) + { + t = w->word->word; + w->word->word = quote_escapes (t); + free (t); + } + return list; +} + +/* Inverse of quote_escapes; remove CTLESC protecting CTLESC or CTLNUL. + + The parser passes us CTLESC as CTLESC CTLESC and CTLNUL as CTLESC CTLNUL. + This is necessary to make unquoted CTLESC and CTLNUL characters in the + data stream pass through properly. + + We need to remove doubled CTLESC characters inside quoted strings before + quoting the entire string, so we do not double the number of CTLESC + characters. + + Also used by parts of the pattern substitution code. */ +char * +dequote_escapes (string) + char *string; +{ + register char *s, *t, *s1; + size_t slen; + char *result, *send; + int quote_spaces; + DECLARE_MBSTATE; + + if (string == 0) + return string; + + slen = strlen (string); + send = string + slen; + + t = result = (char *)xmalloc (slen + 1); + + if (strchr (string, CTLESC) == 0) + return (strcpy (result, string)); + + quote_spaces = (ifs_value && *ifs_value == 0); + + s = string; + while (*s) + { + if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL || (quote_spaces && s[1] == ' '))) + { + s++; + if (*s == '\0') + break; + } + COPY_CHAR_P (t, s, send); + } + *t = '\0'; + return result; +} + +/* Return a new string with the quoted representation of character C. + This turns "" into QUOTED_NULL, so the W_HASQUOTEDNULL flag needs to be + set in any resultant WORD_DESC where this value is the word. */ +static char * +make_quoted_char (c) + int c; +{ + char *temp; + + temp = (char *)xmalloc (3); + if (c == 0) + { + temp[0] = CTLNUL; + temp[1] = '\0'; + } + else + { + temp[0] = CTLESC; + temp[1] = c; + temp[2] = '\0'; + } + return (temp); +} + +/* Quote STRING, returning a new string. This turns "" into QUOTED_NULL, so + the W_HASQUOTEDNULL flag needs to be set in any resultant WORD_DESC where + this value is the word. */ +char * +quote_string (string) + char *string; +{ + register char *t; + size_t slen; + char *result, *send; + + if (*string == 0) + { + result = (char *)xmalloc (2); + result[0] = CTLNUL; + result[1] = '\0'; + } + else + { + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + + result = (char *)xmalloc ((slen * 2) + 1); + + for (t = result; string < send; ) + { + *t++ = CTLESC; + COPY_CHAR_P (t, string, send); + } + *t = '\0'; + } + return (result); +} + +/* De-quote quoted characters in STRING. */ +char * +dequote_string (string) + char *string; +{ + register char *s, *t; + size_t slen; + char *result, *send; + DECLARE_MBSTATE; + + slen = strlen (string); + + t = result = (char *)xmalloc (slen + 1); + + if (QUOTED_NULL (string)) + { + result[0] = '\0'; + return (result); + } + + /* If no character in the string can be quoted, don't bother examining + each character. Just return a copy of the string passed to us. */ + if (strchr (string, CTLESC) == NULL) + return (strcpy (result, string)); + + send = string + slen; + s = string; + while (*s) + { + if (*s == CTLESC) + { + s++; + if (*s == '\0') + break; + } + COPY_CHAR_P (t, s, send); + } + + *t = '\0'; + return (result); +} + +/* Quote the entire WORD_LIST list. */ +static WORD_LIST * +quote_list (list) + WORD_LIST *list; +{ + register WORD_LIST *w; + char *t; + + for (w = list; w; w = w->next) + { + t = w->word->word; + w->word->word = quote_string (t); + if (*t == 0) + w->word->flags |= W_HASQUOTEDNULL; /* XXX - turn on W_HASQUOTEDNULL here? */ + w->word->flags |= W_QUOTED; + free (t); + } + return list; +} + +/* De-quote quoted characters in each word in LIST. */ +WORD_LIST * +dequote_list (list) + WORD_LIST *list; +{ + register char *s; + register WORD_LIST *tlist; + + for (tlist = list; tlist; tlist = tlist->next) + { + s = dequote_string (tlist->word->word); + if (QUOTED_NULL (tlist->word->word)) + tlist->word->flags &= ~W_HASQUOTEDNULL; + free (tlist->word->word); + tlist->word->word = s; + } + return list; +} + +/* Remove CTLESC protecting a CTLESC or CTLNUL in place. Return the passed + string. */ +char * +remove_quoted_escapes (string) + char *string; +{ + char *t; + + if (string) + { + t = dequote_escapes (string); + strcpy (string, t); + free (t); + } + + return (string); +} + +/* Perform quoted null character removal on STRING. We don't allow any + quoted null characters in the middle or at the ends of strings because + of how expand_word_internal works. remove_quoted_nulls () turns + STRING into an empty string iff it only consists of a quoted null, + and removes all unquoted CTLNUL characters. */ +char * +remove_quoted_nulls (string) + char *string; +{ + register size_t slen; + register int i, j, prev_i; + DECLARE_MBSTATE; + + if (strchr (string, CTLNUL) == 0) /* XXX */ + return string; /* XXX */ + + slen = strlen (string); + i = j = 0; + + while (i < slen) + { + if (string[i] == CTLESC) + { + /* Old code had j++, but we cannot assume that i == j at this + point -- what if a CTLNUL has already been removed from the + string? We don't want to drop the CTLESC or recopy characters + that we've already copied down. */ + i++; string[j++] = CTLESC; + if (i == slen) + break; + } + else if (string[i] == CTLNUL) + { + i++; + continue; + } + + prev_i = i; + ADVANCE_CHAR (string, slen, i); + if (j < prev_i) + { + do string[j++] = string[prev_i++]; while (prev_i < i); + } + else + j = i; + } + string[j] = '\0'; + + return (string); +} + +/* Perform quoted null character removal on each element of LIST. + This modifies LIST. */ +void +word_list_remove_quoted_nulls (list) + WORD_LIST *list; +{ + register WORD_LIST *t; + + for (t = list; t; t = t->next) + { + remove_quoted_nulls (t->word->word); + t->word->flags &= ~W_HASQUOTEDNULL; + } +} + +/* **************************************************************** */ +/* */ +/* Functions for Matching and Removing Patterns */ +/* */ +/* **************************************************************** */ + +#if defined (HANDLE_MULTIBYTE) +#if 0 /* Currently unused */ +static unsigned char * +mb_getcharlens (string, len) + char *string; + int len; +{ + int i, offset, last; + unsigned char *ret; + char *p; + DECLARE_MBSTATE; + + i = offset = 0; + last = 0; + ret = (unsigned char *)xmalloc (len); + memset (ret, 0, len); + while (string[last]) + { + ADVANCE_CHAR (string, len, offset); + ret[last] = offset - last; + last = offset; + } + return ret; +} +#endif +#endif + +/* Remove the portion of PARAM matched by PATTERN according to OP, where OP + can have one of 4 values: + RP_LONG_LEFT remove longest matching portion at start of PARAM + RP_SHORT_LEFT remove shortest matching portion at start of PARAM + RP_LONG_RIGHT remove longest matching portion at end of PARAM + RP_SHORT_RIGHT remove shortest matching portion at end of PARAM +*/ + +#define RP_LONG_LEFT 1 +#define RP_SHORT_LEFT 2 +#define RP_LONG_RIGHT 3 +#define RP_SHORT_RIGHT 4 + +/* Returns its first argument if nothing matched; new memory otherwise */ +static char * +remove_upattern (param, pattern, op) + char *param, *pattern; + int op; +{ + register int len; + register char *end; + register char *p, *ret, c; + + len = STRLEN (param); + end = param + len; + + switch (op) + { + case RP_LONG_LEFT: /* remove longest match at start */ + for (p = end; p >= param; p--) + { + c = *p; *p = '\0'; + if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + *p = c; + return (savestring (p)); + } + *p = c; + + } + break; + + case RP_SHORT_LEFT: /* remove shortest match at start */ + for (p = param; p <= end; p++) + { + c = *p; *p = '\0'; + if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + *p = c; + return (savestring (p)); + } + *p = c; + } + break; + + case RP_LONG_RIGHT: /* remove longest match at end */ + for (p = param; p <= end; p++) + { + if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + c = *p; *p = '\0'; + ret = savestring (param); + *p = c; + return (ret); + } + } + break; + + case RP_SHORT_RIGHT: /* remove shortest match at end */ + for (p = end; p >= param; p--) + { + if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + c = *p; *p = '\0'; + ret = savestring (param); + *p = c; + return (ret); + } + } + break; + } + + return (param); /* no match, return original string */ +} + +#if defined (HANDLE_MULTIBYTE) +/* Returns its first argument if nothing matched; new memory otherwise */ +static wchar_t * +remove_wpattern (wparam, wstrlen, wpattern, op) + wchar_t *wparam; + size_t wstrlen; + wchar_t *wpattern; + int op; +{ + wchar_t wc, *ret; + int n; + + switch (op) + { + case RP_LONG_LEFT: /* remove longest match at start */ + for (n = wstrlen; n >= 0; n--) + { + wc = wparam[n]; wparam[n] = L'\0'; + if (wcsmatch (wpattern, wparam, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wparam[n] = wc; + return (wcsdup (wparam + n)); + } + wparam[n] = wc; + } + break; + + case RP_SHORT_LEFT: /* remove shortest match at start */ + for (n = 0; n <= wstrlen; n++) + { + wc = wparam[n]; wparam[n] = L'\0'; + if (wcsmatch (wpattern, wparam, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wparam[n] = wc; + return (wcsdup (wparam + n)); + } + wparam[n] = wc; + } + break; + + case RP_LONG_RIGHT: /* remove longest match at end */ + for (n = 0; n <= wstrlen; n++) + { + if (wcsmatch (wpattern, wparam + n, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wc = wparam[n]; wparam[n] = L'\0'; + ret = wcsdup (wparam); + wparam[n] = wc; + return (ret); + } + } + break; + + case RP_SHORT_RIGHT: /* remove shortest match at end */ + for (n = wstrlen; n >= 0; n--) + { + if (wcsmatch (wpattern, wparam + n, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wc = wparam[n]; wparam[n] = L'\0'; + ret = wcsdup (wparam); + wparam[n] = wc; + return (ret); + } + } + break; + } + + return (wparam); /* no match, return original string */ +} +#endif /* HANDLE_MULTIBYTE */ + +static char * +remove_pattern (param, pattern, op) + char *param, *pattern; + int op; +{ + char *xret; + + if (param == NULL) + return (param); + if (*param == '\0' || pattern == NULL || *pattern == '\0') /* minor optimization */ + return (savestring (param)); + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1) + { + wchar_t *ret, *oret; + size_t n; + wchar_t *wparam, *wpattern; + mbstate_t ps; + + n = xdupmbstowcs (&wpattern, NULL, pattern); + if (n == (size_t)-1) + { + xret = remove_upattern (param, pattern, op); + return ((xret == param) ? savestring (param) : xret); + } + n = xdupmbstowcs (&wparam, NULL, param); + if (n == (size_t)-1) + { + free (wpattern); + xret = remove_upattern (param, pattern, op); + return ((xret == param) ? savestring (param) : xret); + } + oret = ret = remove_wpattern (wparam, n, wpattern, op); + /* Don't bother to convert wparam back to multibyte string if nothing + matched; just return copy of original string */ + if (ret == wparam) + { + free (wparam); + free (wpattern); + return (savestring (param)); + } + + free (wparam); + free (wpattern); + + n = strlen (param); + xret = (char *)xmalloc (n + 1); + memset (&ps, '\0', sizeof (mbstate_t)); + n = wcsrtombs (xret, (const wchar_t **)&ret, n, &ps); + xret[n] = '\0'; /* just to make sure */ + free (oret); + return xret; + } + else +#endif + { + xret = remove_upattern (param, pattern, op); + return ((xret == param) ? savestring (param) : xret); + } +} + +/* Match PAT anywhere in STRING and return the match boundaries. + This returns 1 in case of a successful match, 0 otherwise. SP + and EP are pointers into the string where the match begins and + ends, respectively. MTYPE controls what kind of match is attempted. + MATCH_BEG and MATCH_END anchor the match at the beginning and end + of the string, respectively. The longest match is returned. */ +static int +match_upattern (string, pat, mtype, sp, ep) + char *string, *pat; + int mtype; + char **sp, **ep; +{ + int c, len, mlen; + register char *p, *p1, *npat; + char *end; + int n1; + + /* If the pattern doesn't match anywhere in the string, go ahead and + short-circuit right away. A minor optimization, saves a bunch of + unnecessary calls to strmatch (up to N calls for a string of N + characters) if the match is unsuccessful. To preserve the semantics + of the substring matches below, we make sure that the pattern has + `*' as first and last character, making a new pattern if necessary. */ + /* XXX - check this later if I ever implement `**' with special meaning, + since this will potentially result in `**' at the beginning or end */ + len = STRLEN (pat); + if (pat[0] != '*' || (pat[0] == '*' && pat[1] == LPAREN && extended_glob) || pat[len - 1] != '*') + { + p = npat = (char *)xmalloc (len + 3); + p1 = pat; + if (*p1 != '*' || (*p1 == '*' && p1[1] == LPAREN && extended_glob)) + *p++ = '*'; + while (*p1) + *p++ = *p1++; + if (p1[-1] != '*' || p[-2] == '\\') + *p++ = '*'; + *p = '\0'; + } + else + npat = pat; + c = strmatch (npat, string, FNMATCH_EXTFLAG); + if (npat != pat) + free (npat); + if (c == FNM_NOMATCH) + return (0); + + len = STRLEN (string); + end = string + len; + + mlen = umatchlen (pat, len); + + switch (mtype) + { + case MATCH_ANY: + for (p = string; p <= end; p++) + { + if (match_pattern_char (pat, p)) + { +#if 0 + for (p1 = end; p1 >= p; p1--) +#else + p1 = (mlen == -1) ? end : p + mlen; + /* p1 - p = length of portion of string to be considered + p = current position in string + mlen = number of characters consumed by match (-1 for entire string) + end = end of string + we want to break immediately if the potential match len + is greater than the number of characters remaining in the + string + */ + if (p1 > end) + break; + for ( ; p1 >= p; p1--) +#endif + { + c = *p1; *p1 = '\0'; + if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0) + { + *p1 = c; + *sp = p; + *ep = p1; + return 1; + } + *p1 = c; +#if 1 + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; +#endif + } + } + } + + return (0); + + case MATCH_BEG: + if (match_pattern_char (pat, string) == 0) + return (0); + +#if 0 + for (p = end; p >= string; p--) +#else + for (p = (mlen == -1) ? end : string + mlen; p >= string; p--) +#endif + { + c = *p; *p = '\0'; + if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0) + { + *p = c; + *sp = string; + *ep = p; + return 1; + } + *p = c; +#if 1 + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; +#endif + } + + return (0); + + case MATCH_END: +#if 0 + for (p = string; p <= end; p++) +#else + for (p = end - ((mlen == -1) ? len : mlen); p <= end; p++) +#endif + { + if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0) + { + *sp = p; + *ep = end; + return 1; + } +#if 1 + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; +#endif + } + + return (0); + } + + return (0); +} + +#if defined (HANDLE_MULTIBYTE) +/* Match WPAT anywhere in WSTRING and return the match boundaries. + This returns 1 in case of a successful match, 0 otherwise. Wide + character version. */ +static int +match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep) + wchar_t *wstring; + char **indices; + size_t wstrlen; + wchar_t *wpat; + int mtype; + char **sp, **ep; +{ + wchar_t wc, *wp, *nwpat, *wp1; + size_t len; + int mlen; + int n, n1, n2, simple; + + simple = (wpat[0] != L'\\' && wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'['); +#if defined (EXTENDED_GLOB) + if (extended_glob) + simple |= (wpat[1] != L'(' || (wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'+' && wpat[0] != L'!' && wpat[0] != L'@')); /*)*/ +#endif + + /* If the pattern doesn't match anywhere in the string, go ahead and + short-circuit right away. A minor optimization, saves a bunch of + unnecessary calls to strmatch (up to N calls for a string of N + characters) if the match is unsuccessful. To preserve the semantics + of the substring matches below, we make sure that the pattern has + `*' as first and last character, making a new pattern if necessary. */ + len = wcslen (wpat); + if (wpat[0] != L'*' || (wpat[0] == L'*' && wpat[1] == WLPAREN && extended_glob) || wpat[len - 1] != L'*') + { + wp = nwpat = (wchar_t *)xmalloc ((len + 3) * sizeof (wchar_t)); + wp1 = wpat; + if (*wp1 != L'*' || (*wp1 == '*' && wp1[1] == WLPAREN && extended_glob)) + *wp++ = L'*'; + while (*wp1 != L'\0') + *wp++ = *wp1++; + if (wp1[-1] != L'*' || wp1[-2] == L'\\') + *wp++ = L'*'; + *wp = '\0'; + } + else + nwpat = wpat; + len = wcsmatch (nwpat, wstring, FNMATCH_EXTFLAG); + if (nwpat != wpat) + free (nwpat); + if (len == FNM_NOMATCH) + return (0); + + mlen = wmatchlen (wpat, wstrlen); + +/* itrace("wmatchlen (%ls) -> %d", wpat, mlen); */ + switch (mtype) + { + case MATCH_ANY: + for (n = 0; n <= wstrlen; n++) + { +#if 1 + n2 = simple ? (*wpat == wstring[n]) : match_pattern_wchar (wpat, wstring + n); +#else + n2 = match_pattern_wchar (wpat, wstring + n); +#endif + if (n2) + { +#if 0 + for (n1 = wstrlen; n1 >= n; n1--) +#else + n1 = (mlen == -1) ? wstrlen : n + mlen; + if (n1 > wstrlen) + break; + + for ( ; n1 >= n; n1--) +#endif + { + wc = wstring[n1]; wstring[n1] = L'\0'; + if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0) + { + wstring[n1] = wc; + *sp = indices[n]; + *ep = indices[n1]; + return 1; + } + wstring[n1] = wc; +#if 1 + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; +#endif + } + } + } + + return (0); + + case MATCH_BEG: + if (match_pattern_wchar (wpat, wstring) == 0) + return (0); + +#if 0 + for (n = wstrlen; n >= 0; n--) +#else + for (n = (mlen == -1) ? wstrlen : mlen; n >= 0; n--) +#endif + { + wc = wstring[n]; wstring[n] = L'\0'; + if (wcsmatch (wpat, wstring, FNMATCH_EXTFLAG) == 0) + { + wstring[n] = wc; + *sp = indices[0]; + *ep = indices[n]; + return 1; + } + wstring[n] = wc; +#if 1 + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; +#endif + } + + return (0); + + case MATCH_END: +#if 0 + for (n = 0; n <= wstrlen; n++) +#else + for (n = wstrlen - ((mlen == -1) ? wstrlen : mlen); n <= wstrlen; n++) +#endif + { + if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0) + { + *sp = indices[n]; + *ep = indices[wstrlen]; + return 1; + } +#if 1 + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; +#endif + } + + return (0); + } + + return (0); +} +#endif /* HANDLE_MULTIBYTE */ + +static int +match_pattern (string, pat, mtype, sp, ep) + char *string, *pat; + int mtype; + char **sp, **ep; +{ +#if defined (HANDLE_MULTIBYTE) + int ret; + size_t n; + wchar_t *wstring, *wpat; + char **indices; + size_t slen, plen, mslen, mplen; +#endif + + if (string == 0 || *string == 0 || pat == 0 || *pat == 0) + return (0); + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1) + { +#if 0 + slen = STRLEN (string); + mslen = MBSLEN (string); + plen = STRLEN (pat); + mplen = MBSLEN (pat); + if (slen == mslen && plen == mplen) +#else + if (mbsmbchar (string) == 0 && mbsmbchar (pat) == 0) +#endif + return (match_upattern (string, pat, mtype, sp, ep)); + + n = xdupmbstowcs (&wpat, NULL, pat); + if (n == (size_t)-1) + return (match_upattern (string, pat, mtype, sp, ep)); + n = xdupmbstowcs (&wstring, &indices, string); + if (n == (size_t)-1) + { + free (wpat); + return (match_upattern (string, pat, mtype, sp, ep)); + } + ret = match_wpattern (wstring, indices, n, wpat, mtype, sp, ep); + + free (wpat); + free (wstring); + free (indices); + + return (ret); + } + else +#endif + return (match_upattern (string, pat, mtype, sp, ep)); +} + +static int +getpatspec (c, value) + int c; + char *value; +{ + if (c == '#') + return ((*value == '#') ? RP_LONG_LEFT : RP_SHORT_LEFT); + else /* c == '%' */ + return ((*value == '%') ? RP_LONG_RIGHT : RP_SHORT_RIGHT); +} + +/* Posix.2 says that the WORD should be run through tilde expansion, + parameter expansion, command substitution and arithmetic expansion. + This leaves the result quoted, so quote_string_for_globbing () has + to be called to fix it up for strmatch (). If QUOTED is non-zero, + it means that the entire expression was enclosed in double quotes. + This means that quoting characters in the pattern do not make any + special pattern characters quoted. For example, the `*' in the + following retains its special meaning: "${foo#'*'}". */ +static char * +getpattern (value, quoted, expandpat) + char *value; + int quoted, expandpat; +{ + char *pat, *tword; + WORD_LIST *l; +#if 0 + int i; +#endif + /* There is a problem here: how to handle single or double quotes in the + pattern string when the whole expression is between double quotes? + POSIX.2 says that enclosing double quotes do not cause the pattern to + be quoted, but does that leave us a problem with @ and array[@] and their + expansions inside a pattern? */ +#if 0 + if (expandpat && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *tword) + { + i = 0; + pat = string_extract_double_quoted (tword, &i, 1); + free (tword); + tword = pat; + } +#endif + + /* expand_string_for_rhs () leaves WORD quoted and does not perform + word splitting. */ + l = *value ? expand_string_for_rhs (value, + (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? Q_PATQUOTE : quoted, + (int *)NULL, (int *)NULL) + : (WORD_LIST *)0; + pat = string_list (l); + dispose_words (l); + if (pat) + { + tword = quote_string_for_globbing (pat, QGLOB_CVTNULL); + free (pat); + pat = tword; + } + return (pat); +} + +#if 0 +/* Handle removing a pattern from a string as a result of ${name%[%]value} + or ${name#[#]value}. */ +static char * +variable_remove_pattern (value, pattern, patspec, quoted) + char *value, *pattern; + int patspec, quoted; +{ + char *tword; + + tword = remove_pattern (value, pattern, patspec); + + return (tword); +} +#endif + +static char * +list_remove_pattern (list, pattern, patspec, itype, quoted) + WORD_LIST *list; + char *pattern; + int patspec, itype, quoted; +{ + WORD_LIST *new, *l; + WORD_DESC *w; + char *tword; + + for (new = (WORD_LIST *)NULL, l = list; l; l = l->next) + { + tword = remove_pattern (l->word->word, pattern, patspec); + w = alloc_word_desc (); + w->word = tword ? tword : savestring (""); + new = make_word_list (w, new); + } + + l = REVERSE_LIST (new, WORD_LIST *); + tword = string_list_pos_params (itype, l, quoted); + dispose_words (l); + + return (tword); +} + +static char * +parameter_list_remove_pattern (itype, pattern, patspec, quoted) + int itype; + char *pattern; + int patspec, quoted; +{ + char *ret; + WORD_LIST *list; + + list = list_rest_of_args (); + if (list == 0) + return ((char *)NULL); + ret = list_remove_pattern (list, pattern, patspec, itype, quoted); + dispose_words (list); + return (ret); +} + +#if defined (ARRAY_VARS) +static char * +array_remove_pattern (var, pattern, patspec, varname, quoted) + SHELL_VAR *var; + char *pattern; + int patspec; + char *varname; /* so we can figure out how it's indexed */ + int quoted; +{ + ARRAY *a; + HASH_TABLE *h; + int itype; + char *ret; + WORD_LIST *list; + SHELL_VAR *v; + + /* compute itype from varname here */ + v = array_variable_part (varname, &ret, 0); + itype = ret[0]; + + a = (v && array_p (v)) ? array_cell (v) : 0; + h = (v && assoc_p (v)) ? assoc_cell (v) : 0; + + list = a ? array_to_word_list (a) : (h ? assoc_to_word_list (h) : 0); + if (list == 0) + return ((char *)NULL); + ret = list_remove_pattern (list, pattern, patspec, itype, quoted); + dispose_words (list); + + return ret; +} +#endif /* ARRAY_VARS */ + +static char * +parameter_brace_remove_pattern (varname, value, ind, patstr, rtype, quoted, flags) + char *varname, *value; + int ind; + char *patstr; + int rtype, quoted, flags; +{ + int vtype, patspec, starsub; + char *temp1, *val, *pattern; + SHELL_VAR *v; + + if (value == 0) + return ((char *)NULL); + + this_command_name = varname; + + vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val); + if (vtype == -1) + return ((char *)NULL); + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + patspec = getpatspec (rtype, patstr); + if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT) + patstr++; + + /* Need to pass getpattern newly-allocated memory in case of expansion -- + the expansion code will free the passed string on an error. */ + temp1 = savestring (patstr); + pattern = getpattern (temp1, quoted, 1); + free (temp1); + + temp1 = (char *)NULL; /* shut up gcc */ + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + temp1 = remove_pattern (val, pattern, patspec); + if (vtype == VT_VARIABLE) + FREE (val); + if (temp1) + { + val = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + ? quote_string (temp1) + : quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + temp1 = array_remove_pattern (v, pattern, patspec, varname, quoted); + if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + { + val = quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; +#endif + case VT_POSPARMS: + temp1 = parameter_list_remove_pattern (varname[0], pattern, patspec, quoted); + if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + { + val = quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; + } + + FREE (pattern); + return temp1; +} + +/******************************************* + * * + * Functions to expand WORD_DESCs * + * * + *******************************************/ + +/* Expand WORD, performing word splitting on the result. This does + parameter expansion, command substitution, arithmetic expansion, + word splitting, and quote removal. */ + +WORD_LIST * +expand_word (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_LIST *result, *tresult; + + tresult = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL); + result = word_list_split (tresult); + dispose_words (tresult); + return (result ? dequote_list (result) : result); +} + +/* Expand WORD, but do not perform word splitting on the result. This + does parameter expansion, command substitution, arithmetic expansion, + and quote removal. */ +WORD_LIST * +expand_word_unsplit (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_LIST *result; + + expand_no_split_dollar_star = 1; +#if defined (HANDLE_MULTIBYTE) + if (ifs_firstc[0] == 0) +#else + if (ifs_firstc == 0) +#endif + word->flags |= W_NOSPLIT; + word->flags |= W_NOSPLIT2; + result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL); + expand_no_split_dollar_star = 0; + + return (result ? dequote_list (result) : result); +} + +/* Perform shell expansions on WORD, but do not perform word splitting or + quote removal on the result. Virtually identical to expand_word_unsplit; + could be combined if implementations don't diverge. */ +WORD_LIST * +expand_word_leave_quoted (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_LIST *result; + + expand_no_split_dollar_star = 1; +#if defined (HANDLE_MULTIBYTE) + if (ifs_firstc[0] == 0) +#else + if (ifs_firstc == 0) +#endif + word->flags |= W_NOSPLIT; + word->flags |= W_NOSPLIT2; + result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL); + expand_no_split_dollar_star = 0; + + return result; +} + +#if defined (PROCESS_SUBSTITUTION) + +/*****************************************************************/ +/* */ +/* Hacking Process Substitution */ +/* */ +/*****************************************************************/ + +#if !defined (HAVE_DEV_FD) +/* Named pipes must be removed explicitly with `unlink'. This keeps a list + of FIFOs the shell has open. unlink_fifo_list will walk the list and + unlink all of them. add_fifo_list adds the name of an open FIFO to the + list. NFIFO is a count of the number of FIFOs in the list. */ +#define FIFO_INCR 20 + +struct temp_fifo { + char *file; + pid_t proc; +}; + +static struct temp_fifo *fifo_list = (struct temp_fifo *)NULL; +static int nfifo; +static int fifo_list_size; + +char * +copy_fifo_list (sizep) + int *sizep; +{ + if (sizep) + *sizep = 0; + return (char *)NULL; +} + +static void +add_fifo_list (pathname) + char *pathname; +{ + if (nfifo >= fifo_list_size - 1) + { + fifo_list_size += FIFO_INCR; + fifo_list = (struct temp_fifo *)xrealloc (fifo_list, + fifo_list_size * sizeof (struct temp_fifo)); + } + + fifo_list[nfifo].file = savestring (pathname); + nfifo++; +} + +void +unlink_fifo (i) + int i; +{ + if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1)) + { + unlink (fifo_list[i].file); + free (fifo_list[i].file); + fifo_list[i].file = (char *)NULL; + fifo_list[i].proc = -1; + } +} + +void +unlink_fifo_list () +{ + int saved, i, j; + + if (nfifo == 0) + return; + + for (i = saved = 0; i < nfifo; i++) + { + if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1)) + { + unlink (fifo_list[i].file); + free (fifo_list[i].file); + fifo_list[i].file = (char *)NULL; + fifo_list[i].proc = -1; + } + else + saved++; + } + + /* If we didn't remove some of the FIFOs, compact the list. */ + if (saved) + { + for (i = j = 0; i < nfifo; i++) + if (fifo_list[i].file) + { + fifo_list[j].file = fifo_list[i].file; + fifo_list[j].proc = fifo_list[i].proc; + j++; + } + nfifo = j; + } + else + nfifo = 0; +} + +/* Take LIST, which is a bitmap denoting active FIFOs in fifo_list + from some point in the past, and close all open FIFOs in fifo_list + that are not marked as active in LIST. If LIST is NULL, close + everything in fifo_list. LSIZE is the number of elements in LIST, in + case it's larger than fifo_list_size (size of fifo_list). */ +void +close_new_fifos (list, lsize) + char *list; + int lsize; +{ + int i; + + if (list == 0) + { + unlink_fifo_list (); + return; + } + + for (i = 0; i < lsize; i++) + if (list[i] == 0 && i < fifo_list_size && fifo_list[i].proc != -1) + unlink_fifo (i); + + for (i = lsize; i < fifo_list_size; i++) + unlink_fifo (i); +} + +int +fifos_pending () +{ + return nfifo; +} + +int +num_fifos () +{ + return nfifo; +} + +static char * +make_named_pipe () +{ + char *tname; + + tname = sh_mktmpname ("sh-np", MT_USERANDOM|MT_USETMPDIR); + if (mkfifo (tname, 0600) < 0) + { + free (tname); + return ((char *)NULL); + } + + add_fifo_list (tname); + return (tname); +} + +#else /* HAVE_DEV_FD */ + +/* DEV_FD_LIST is a bitmap of file descriptors attached to pipes the shell + has open to children. NFDS is a count of the number of bits currently + set in DEV_FD_LIST. TOTFDS is a count of the highest possible number + of open files. */ +static char *dev_fd_list = (char *)NULL; +static int nfds; +static int totfds; /* The highest possible number of open files. */ + +char * +copy_fifo_list (sizep) + int *sizep; +{ + char *ret; + + if (nfds == 0 || totfds == 0) + { + if (sizep) + *sizep = 0; + return (char *)NULL; + } + + if (sizep) + *sizep = totfds; + ret = (char *)xmalloc (totfds); + return (memcpy (ret, dev_fd_list, totfds)); +} + +static void +add_fifo_list (fd) + int fd; +{ + if (dev_fd_list == 0 || fd >= totfds) + { + int ofds; + + ofds = totfds; + totfds = getdtablesize (); + if (totfds < 0 || totfds > 256) + totfds = 256; + if (fd >= totfds) + totfds = fd + 2; + + dev_fd_list = (char *)xrealloc (dev_fd_list, totfds); + memset (dev_fd_list + ofds, '\0', totfds - ofds); + } + + dev_fd_list[fd] = 1; + nfds++; +} + +int +fifos_pending () +{ + return 0; /* used for cleanup; not needed with /dev/fd */ +} + +int +num_fifos () +{ + return nfds; +} + +void +unlink_fifo (fd) + int fd; +{ + if (dev_fd_list[fd]) + { + close (fd); + dev_fd_list[fd] = 0; + nfds--; + } +} + +void +unlink_fifo_list () +{ + register int i; + + if (nfds == 0) + return; + + for (i = 0; nfds && i < totfds; i++) + unlink_fifo (i); + + nfds = 0; +} + +/* Take LIST, which is a snapshot copy of dev_fd_list from some point in + the past, and close all open fds in dev_fd_list that are not marked + as open in LIST. If LIST is NULL, close everything in dev_fd_list. + LSIZE is the number of elements in LIST, in case it's larger than + totfds (size of dev_fd_list). */ +void +close_new_fifos (list, lsize) + char *list; + int lsize; +{ + int i; + + if (list == 0) + { + unlink_fifo_list (); + return; + } + + for (i = 0; i < lsize; i++) + if (list[i] == 0 && i < totfds && dev_fd_list[i]) + unlink_fifo (i); + + for (i = lsize; i < totfds; i++) + unlink_fifo (i); +} + +#if defined (NOTDEF) +print_dev_fd_list () +{ + register int i; + + fprintf (stderr, "pid %ld: dev_fd_list:", (long)getpid ()); + fflush (stderr); + + for (i = 0; i < totfds; i++) + { + if (dev_fd_list[i]) + fprintf (stderr, " %d", i); + } + fprintf (stderr, "\n"); +} +#endif /* NOTDEF */ + +static char * +make_dev_fd_filename (fd) + int fd; +{ + char *ret, intbuf[INT_STRLEN_BOUND (int) + 1], *p; + + ret = (char *)xmalloc (sizeof (DEV_FD_PREFIX) + 8); + + strcpy (ret, DEV_FD_PREFIX); + p = inttostr (fd, intbuf, sizeof (intbuf)); + strcpy (ret + sizeof (DEV_FD_PREFIX) - 1, p); + + add_fifo_list (fd); + return (ret); +} + +#endif /* HAVE_DEV_FD */ + +/* Return a filename that will open a connection to the process defined by + executing STRING. HAVE_DEV_FD, if defined, means open a pipe and return + a filename in /dev/fd corresponding to a descriptor that is one of the + ends of the pipe. If not defined, we use named pipes on systems that have + them. Systems without /dev/fd and named pipes are out of luck. + + OPEN_FOR_READ_IN_CHILD, if 1, means open the named pipe for reading or + use the read end of the pipe and dup that file descriptor to fd 0 in + the child. If OPEN_FOR_READ_IN_CHILD is 0, we open the named pipe for + writing or use the write end of the pipe in the child, and dup that + file descriptor to fd 1 in the child. The parent does the opposite. */ + +static char * +process_substitute (string, open_for_read_in_child) + char *string; + int open_for_read_in_child; +{ + char *pathname; + int fd, result; + pid_t old_pid, pid; +#if defined (HAVE_DEV_FD) + int parent_pipe_fd, child_pipe_fd; + int fildes[2]; +#endif /* HAVE_DEV_FD */ +#if defined (JOB_CONTROL) + pid_t old_pipeline_pgrp; +#endif + + if (!string || !*string || wordexp_only) + return ((char *)NULL); + +#if !defined (HAVE_DEV_FD) + pathname = make_named_pipe (); +#else /* HAVE_DEV_FD */ + if (pipe (fildes) < 0) + { + sys_error (_("cannot make pipe for process substitution")); + return ((char *)NULL); + } + /* If OPEN_FOR_READ_IN_CHILD == 1, we want to use the write end of + the pipe in the parent, otherwise the read end. */ + parent_pipe_fd = fildes[open_for_read_in_child]; + child_pipe_fd = fildes[1 - open_for_read_in_child]; + /* Move the parent end of the pipe to some high file descriptor, to + avoid clashes with FDs used by the script. */ + parent_pipe_fd = move_to_high_fd (parent_pipe_fd, 1, 64); + + pathname = make_dev_fd_filename (parent_pipe_fd); +#endif /* HAVE_DEV_FD */ + + if (pathname == 0) + { + sys_error (_("cannot make pipe for process substitution")); + return ((char *)NULL); + } + + old_pid = last_made_pid; + +#if defined (JOB_CONTROL) + old_pipeline_pgrp = pipeline_pgrp; + pipeline_pgrp = shell_pgrp; + save_pipeline (1); +#endif /* JOB_CONTROL */ + + pid = make_child ((char *)NULL, 1); + if (pid == 0) + { + reset_terminating_signals (); /* XXX */ + free_pushed_string_input (); + /* Cancel traps, in trap.c. */ + restore_original_signals (); /* XXX - what about special builtins? bash-4.2 */ + setup_async_signals (); + subshell_environment |= SUBSHELL_COMSUB|SUBSHELL_PROCSUB; + } + +#if defined (JOB_CONTROL) + set_sigchld_handler (); + stop_making_children (); + /* XXX - should we only do this in the parent? (as in command subst) */ + pipeline_pgrp = old_pipeline_pgrp; +#endif /* JOB_CONTROL */ + + if (pid < 0) + { + sys_error (_("cannot make child for process substitution")); + free (pathname); +#if defined (HAVE_DEV_FD) + close (parent_pipe_fd); + close (child_pipe_fd); +#endif /* HAVE_DEV_FD */ + return ((char *)NULL); + } + + if (pid > 0) + { +#if defined (JOB_CONTROL) + restore_pipeline (1); +#endif + +#if !defined (HAVE_DEV_FD) + fifo_list[nfifo-1].proc = pid; +#endif + + last_made_pid = old_pid; + +#if defined (JOB_CONTROL) && defined (PGRP_PIPE) + close_pgrp_pipe (); +#endif /* JOB_CONTROL && PGRP_PIPE */ + +#if defined (HAVE_DEV_FD) + close (child_pipe_fd); +#endif /* HAVE_DEV_FD */ + + return (pathname); + } + + set_sigint_handler (); + +#if defined (JOB_CONTROL) + set_job_control (0); +#endif /* JOB_CONTROL */ + +#if !defined (HAVE_DEV_FD) + /* Open the named pipe in the child. */ + fd = open (pathname, open_for_read_in_child ? O_RDONLY|O_NONBLOCK : O_WRONLY); + if (fd < 0) + { + /* Two separate strings for ease of translation. */ + if (open_for_read_in_child) + sys_error (_("cannot open named pipe %s for reading"), pathname); + else + sys_error (_("cannot open named pipe %s for writing"), pathname); + + exit (127); + } + if (open_for_read_in_child) + { + if (sh_unset_nodelay_mode (fd) < 0) + { + sys_error (_("cannot reset nodelay mode for fd %d"), fd); + exit (127); + } + } +#else /* HAVE_DEV_FD */ + fd = child_pipe_fd; +#endif /* HAVE_DEV_FD */ + + if (dup2 (fd, open_for_read_in_child ? 0 : 1) < 0) + { + sys_error (_("cannot duplicate named pipe %s as fd %d"), pathname, + open_for_read_in_child ? 0 : 1); + exit (127); + } + + if (fd != (open_for_read_in_child ? 0 : 1)) + close (fd); + + /* Need to close any files that this process has open to pipes inherited + from its parent. */ + if (current_fds_to_close) + { + close_fd_bitmap (current_fds_to_close); + current_fds_to_close = (struct fd_bitmap *)NULL; + } + +#if defined (HAVE_DEV_FD) + /* Make sure we close the parent's end of the pipe and clear the slot + in the fd list so it is not closed later, if reallocated by, for + instance, pipe(2). */ + close (parent_pipe_fd); + dev_fd_list[parent_pipe_fd] = 0; +#endif /* HAVE_DEV_FD */ + + result = parse_and_execute (string, "process substitution", (SEVAL_NONINT|SEVAL_NOHIST)); + +#if !defined (HAVE_DEV_FD) + /* Make sure we close the named pipe in the child before we exit. */ + close (open_for_read_in_child ? 0 : 1); +#endif /* !HAVE_DEV_FD */ + + exit (result); + /*NOTREACHED*/ +} +#endif /* PROCESS_SUBSTITUTION */ + +/***********************************/ +/* */ +/* Command Substitution */ +/* */ +/***********************************/ + +static char * +read_comsub (fd, quoted, rflag) + int fd, quoted; + int *rflag; +{ + char *istring, buf[128], *bufp, *s; + int istring_index, istring_size, c, tflag, skip_ctlesc, skip_ctlnul; + ssize_t bufn; + + istring = (char *)NULL; + istring_index = istring_size = bufn = tflag = 0; + + for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++) + skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL; + + /* Read the output of the command through the pipe. This may need to be + changed to understand multibyte characters in the future. */ + while (1) + { + if (fd < 0) + break; + if (--bufn <= 0) + { + bufn = zread (fd, buf, sizeof (buf)); + if (bufn <= 0) + break; + bufp = buf; + } + c = *bufp++; + + if (c == 0) + { +#if 0 + internal_warning ("read_comsub: ignored null byte in input"); +#endif + continue; + } + + /* Add the character to ISTRING, possibly after resizing it. */ + RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, DEFAULT_ARRAY_SIZE); + + /* This is essentially quote_string inline */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) /* || c == CTLESC || c == CTLNUL */) + istring[istring_index++] = CTLESC; + /* Escape CTLESC and CTLNUL in the output to protect those characters + from the rest of the word expansions (word splitting and globbing.) + This is essentially quote_escapes inline. */ + else if (skip_ctlesc == 0 && c == CTLESC) + { + tflag |= W_HASCTLESC; + istring[istring_index++] = CTLESC; + } + else if ((skip_ctlnul == 0 && c == CTLNUL) || (c == ' ' && (ifs_value && *ifs_value == 0))) + istring[istring_index++] = CTLESC; + + istring[istring_index++] = c; + +#if 0 +#if defined (__CYGWIN__) + if (c == '\n' && istring_index > 1 && istring[istring_index - 2] == '\r') + { + istring_index--; + istring[istring_index - 1] = '\n'; + } +#endif +#endif + } + + if (istring) + istring[istring_index] = '\0'; + + /* If we read no output, just return now and save ourselves some + trouble. */ + if (istring_index == 0) + { + FREE (istring); + if (rflag) + *rflag = tflag; + return (char *)NULL; + } + + /* Strip trailing newlines from the output of the command. */ + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + { + while (istring_index > 0) + { + if (istring[istring_index - 1] == '\n') + { + --istring_index; + + /* If the newline was quoted, remove the quoting char. */ + if (istring[istring_index - 1] == CTLESC) + --istring_index; + } + else + break; + } + istring[istring_index] = '\0'; + } + else + strip_trailing (istring, istring_index - 1, 1); + + if (rflag) + *rflag = tflag; + return istring; +} + +/* Perform command substitution on STRING. This returns a WORD_DESC * with the + contained string possibly quoted. */ +WORD_DESC * +command_substitute (string, quoted) + char *string; + int quoted; +{ + pid_t pid, old_pid, old_pipeline_pgrp, old_async_pid; + char *istring; + int result, fildes[2], function_value, pflags, rc, tflag; + WORD_DESC *ret; + + istring = (char *)NULL; + + /* Don't fork () if there is no need to. In the case of no command to + run, just return NULL. */ + if (!string || !*string || (string[0] == '\n' && !string[1])) + return ((WORD_DESC *)NULL); + + if (wordexp_only && read_but_dont_execute) + { + last_command_exit_value = EX_WEXPCOMSUB; + jump_to_top_level (EXITPROG); + } + + /* We're making the assumption here that the command substitution will + eventually run a command from the file system. Since we'll run + maybe_make_export_env in this subshell before executing that command, + the parent shell and any other shells it starts will have to remake + the environment. If we make it before we fork, other shells won't + have to. Don't bother if we have any temporary variable assignments, + though, because the export environment will be remade after this + command completes anyway, but do it if all the words to be expanded + are variable assignments. */ + if (subst_assign_varlist == 0 || garglist == 0) + maybe_make_export_env (); /* XXX */ + + /* Flags to pass to parse_and_execute() */ + pflags = (interactive && sourcelevel == 0) ? SEVAL_RESETLINE : 0; + + /* Pipe the output of executing STRING into the current shell. */ + if (pipe (fildes) < 0) + { + sys_error (_("cannot make pipe for command substitution")); + goto error_exit; + } + + old_pid = last_made_pid; +#if defined (JOB_CONTROL) + old_pipeline_pgrp = pipeline_pgrp; + /* Don't reset the pipeline pgrp if we're already a subshell in a pipeline. */ + if ((subshell_environment & SUBSHELL_PIPE) == 0) + pipeline_pgrp = shell_pgrp; + cleanup_the_pipeline (); +#endif /* JOB_CONTROL */ + + old_async_pid = last_asynchronous_pid; + pid = make_child ((char *)NULL, subshell_environment&SUBSHELL_ASYNC); + last_asynchronous_pid = old_async_pid; + + if (pid == 0) + { + /* Reset the signal handlers in the child, but don't free the + trap strings. Set a flag noting that we have to free the + trap strings if we run trap to change a signal disposition. */ + reset_signal_handlers (); + subshell_environment |= SUBSHELL_RESETTRAP; + } + +#if defined (JOB_CONTROL) + /* XXX DO THIS ONLY IN PARENT ? XXX */ + set_sigchld_handler (); + stop_making_children (); + if (pid != 0) + pipeline_pgrp = old_pipeline_pgrp; +#else + stop_making_children (); +#endif /* JOB_CONTROL */ + + if (pid < 0) + { + sys_error (_("cannot make child for command substitution")); + error_exit: + + FREE (istring); + close (fildes[0]); + close (fildes[1]); + return ((WORD_DESC *)NULL); + } + + if (pid == 0) + { + set_sigint_handler (); /* XXX */ + + free_pushed_string_input (); + + if (dup2 (fildes[1], 1) < 0) + { + sys_error (_("command_substitute: cannot duplicate pipe as fd 1")); + exit (EXECUTION_FAILURE); + } + + /* If standard output is closed in the parent shell + (such as after `exec >&-'), file descriptor 1 will be + the lowest available file descriptor, and end up in + fildes[0]. This can happen for stdin and stderr as well, + but stdout is more important -- it will cause no output + to be generated from this command. */ + if ((fildes[1] != fileno (stdin)) && + (fildes[1] != fileno (stdout)) && + (fildes[1] != fileno (stderr))) + close (fildes[1]); + + if ((fildes[0] != fileno (stdin)) && + (fildes[0] != fileno (stdout)) && + (fildes[0] != fileno (stderr))) + close (fildes[0]); + +#ifdef __CYGWIN__ + /* Let stdio know the fd may have changed from text to binary mode, and + make sure to preserve stdout line buffering. */ + freopen (NULL, "w", stdout); + sh_setlinebuf (stdout); +#endif /* __CYGWIN__ */ + + /* The currently executing shell is not interactive. */ + interactive = 0; + + /* This is a subshell environment. */ + subshell_environment |= SUBSHELL_COMSUB; + + /* When not in POSIX mode, command substitution does not inherit + the -e flag. */ + if (posixly_correct == 0) + exit_immediately_on_error = 0; + + remove_quoted_escapes (string); + + startup_state = 2; /* see if we can avoid a fork */ + /* Give command substitution a place to jump back to on failure, + so we don't go back up to main (). */ + result = setjmp (top_level); + + /* If we're running a command substitution inside a shell function, + trap `return' so we don't return from the function in the subshell + and go off to never-never land. */ + if (result == 0 && return_catch_flag) + function_value = setjmp (return_catch); + else + function_value = 0; + + if (result == ERREXIT) + rc = last_command_exit_value; + else if (result == EXITPROG) + rc = last_command_exit_value; + else if (result) + rc = EXECUTION_FAILURE; + else if (function_value) + rc = return_catch_value; + else + { + subshell_level++; + rc = parse_and_execute (string, "command substitution", pflags|SEVAL_NOHIST); + subshell_level--; + } + + last_command_exit_value = rc; + rc = run_exit_trap (); +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif + exit (rc); + } + else + { +#if defined (JOB_CONTROL) && defined (PGRP_PIPE) + close_pgrp_pipe (); +#endif /* JOB_CONTROL && PGRP_PIPE */ + + close (fildes[1]); + + tflag = 0; + istring = read_comsub (fildes[0], quoted, &tflag); + + close (fildes[0]); + + current_command_subst_pid = pid; + last_command_exit_value = wait_for (pid); + last_command_subst_pid = pid; + last_made_pid = old_pid; + +#if defined (JOB_CONTROL) + /* If last_command_exit_value > 128, then the substituted command + was terminated by a signal. If that signal was SIGINT, then send + SIGINT to ourselves. This will break out of loops, for instance. */ + if (last_command_exit_value == (128 + SIGINT) && last_command_exit_signal == SIGINT) + kill (getpid (), SIGINT); + + /* wait_for gives the terminal back to shell_pgrp. If some other + process group should have it, give it away to that group here. + pipeline_pgrp is non-zero only while we are constructing a + pipline, so what we are concerned about is whether or not that + pipeline was started in the background. A pipeline started in + the background should never get the tty back here. */ + if (interactive && pipeline_pgrp != (pid_t)0 && (subshell_environment & SUBSHELL_ASYNC) == 0) + give_terminal_to (pipeline_pgrp, 0); +#endif /* JOB_CONTROL */ + + ret = alloc_word_desc (); + ret->word = istring; + ret->flags = tflag; + + return ret; + } +} + +/******************************************************** + * * + * Utility functions for parameter expansion * + * * + ********************************************************/ + +#if defined (ARRAY_VARS) + +static arrayind_t +array_length_reference (s) + char *s; +{ + int len; + arrayind_t ind; + char *akey; + char *t, c; + ARRAY *array; + HASH_TABLE *h; + SHELL_VAR *var; + + var = array_variable_part (s, &t, &len); + + /* If unbound variables should generate an error, report one and return + failure. */ + if ((var == 0 || (assoc_p (var) == 0 && array_p (var) == 0)) && unbound_vars_is_error) + { + c = *--t; + *t = '\0'; + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (s); + *t = c; + return (-1); + } + else if (var == 0) + return 0; + + /* We support a couple of expansions for variables that are not arrays. + We'll return the length of the value for v[0], and 1 for v[@] or + v[*]. Return 0 for everything else. */ + + array = array_p (var) ? array_cell (var) : (ARRAY *)NULL; + h = assoc_p (var) ? assoc_cell (var) : (HASH_TABLE *)NULL; + + if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']') + { + if (assoc_p (var)) + return (h ? assoc_num_elements (h) : 0); + else if (array_p (var)) + return (array ? array_num_elements (array) : 0); + else + return (var_isset (var) ? 1 : 0); + } + + if (assoc_p (var)) + { + t[len - 1] = '\0'; + akey = expand_assignment_string_to_string (t, 0); /* [ */ + t[len - 1] = ']'; + if (akey == 0 || *akey == 0) + { + err_badarraysub (t); + FREE (akey); + return (-1); + } + t = assoc_reference (assoc_cell (var), akey); + free (akey); + } + else + { + ind = array_expand_index (var, t, len); + if (ind < 0) + { + err_badarraysub (t); + return (-1); + } + if (array_p (var)) + t = array_reference (array, ind); + else + t = (ind == 0) ? value_cell (var) : (char *)NULL; + } + + len = MB_STRLEN (t); + return (len); +} +#endif /* ARRAY_VARS */ + +static int +valid_brace_expansion_word (name, var_is_special) + char *name; + int var_is_special; +{ + if (DIGIT (*name) && all_digits (name)) + return 1; + else if (var_is_special) + return 1; +#if defined (ARRAY_VARS) + else if (valid_array_reference (name)) + return 1; +#endif /* ARRAY_VARS */ + else if (legal_identifier (name)) + return 1; + else + return 0; +} + +static int +chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at) + char *name; + int quoted; + int *quoted_dollar_atp, *contains_dollar_at; +{ + char *temp1; + + if (name == 0) + { + if (quoted_dollar_atp) + *quoted_dollar_atp = 0; + if (contains_dollar_at) + *contains_dollar_at = 0; + return 0; + } + + /* check for $@ and $* */ + if (name[0] == '@' && name[1] == 0) + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } + else if (name[0] == '*' && name[1] == '\0' && quoted == 0) + { + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } + + /* Now check for ${array[@]} and ${array[*]} */ +#if defined (ARRAY_VARS) + else if (valid_array_reference (name)) + { + temp1 = mbschr (name, '['); + if (temp1 && temp1[1] == '@' && temp1[2] == ']') + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } /* [ */ + /* ${array[*]}, when unquoted, should be treated like ${array[@]}, + which should result in separate words even when IFS is unset. */ + if (temp1 && temp1[1] == '*' && temp1[2] == ']' && quoted == 0) + { + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } + } +#endif + return 0; +} + +/* Parameter expand NAME, and return a new string which is the expansion, + or NULL if there was no expansion. + VAR_IS_SPECIAL is non-zero if NAME is one of the special variables in + the shell, e.g., "@", "$", "*", etc. QUOTED, if non-zero, means that + NAME was found inside of a double-quoted expression. */ +static WORD_DESC * +parameter_brace_expand_word (name, var_is_special, quoted, pflags, indp) + char *name; + int var_is_special, quoted, pflags; + arrayind_t *indp; +{ + WORD_DESC *ret; + char *temp, *tt; + intmax_t arg_index; + SHELL_VAR *var; + int atype, rflags; + arrayind_t ind; + + ret = 0; + temp = 0; + rflags = 0; + + if (indp) + *indp = INTMAX_MIN; + + /* Handle multiple digit arguments, as in ${11}. */ + if (legal_number (name, &arg_index)) + { + tt = get_dollar_var_value (arg_index); + if (tt) + temp = (*tt && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (tt) + : quote_escapes (tt); + else + temp = (char *)NULL; + FREE (tt); + } + else if (var_is_special) /* ${@} */ + { + int sindex; + tt = (char *)xmalloc (2 + strlen (name)); + tt[sindex = 0] = '$'; + strcpy (tt + 1, name); + + ret = param_expand (tt, &sindex, quoted, (int *)NULL, (int *)NULL, + (int *)NULL, (int *)NULL, pflags); + free (tt); + } +#if defined (ARRAY_VARS) + else if (valid_array_reference (name)) + { + /* XXX - does this leak if name[@] or name[*]? */ + temp = array_value (name, quoted, 0, &atype, &ind); + if (atype == 0 && temp) + { + temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (temp) + : quote_escapes (temp); + rflags |= W_ARRAYIND; + if (indp) + *indp = ind; + } + else if (atype == 1 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + rflags |= W_HASQUOTEDNULL; + } +#endif + else if (var = find_variable (name)) + { + if (var_isset (var) && invisible_p (var) == 0) + { +#if defined (ARRAY_VARS) + if (assoc_p (var)) + temp = assoc_reference (assoc_cell (var), "0"); + else if (array_p (var)) + temp = array_reference (array_cell (var), 0); + else + temp = value_cell (var); +#else + temp = value_cell (var); +#endif + + if (temp) + temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (temp) + : quote_escapes (temp); + } + else + temp = (char *)NULL; + } + else + temp = (char *)NULL; + + if (ret == 0) + { + ret = alloc_word_desc (); + ret->word = temp; + ret->flags |= rflags; + } + return ret; +} + +/* Expand an indirect reference to a variable: ${!NAME} expands to the + value of the variable whose name is the value of NAME. */ +static WORD_DESC * +parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at) + char *name; + int var_is_special, quoted; + int *quoted_dollar_atp, *contains_dollar_at; +{ + char *temp, *t; + WORD_DESC *w; + + w = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND, 0); + t = w->word; + /* Have to dequote here if necessary */ + if (t) + { + temp = (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + ? dequote_string (t) + : dequote_escapes (t); + free (t); + t = temp; + } + dispose_word_desc (w); + + chk_atstar (t, quoted, quoted_dollar_atp, contains_dollar_at); + if (t == 0) + return (WORD_DESC *)NULL; + + w = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted, 0, 0); + free (t); + + return w; +} + +/* Expand the right side of a parameter expansion of the form ${NAMEcVALUE}, + depending on the value of C, the separating character. C can be one of + "-", "+", or "=". QUOTED is true if the entire brace expression occurs + between double quotes. */ +static WORD_DESC * +parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat) + char *name, *value; + int c, quoted, *qdollaratp, *hasdollarat; +{ + WORD_DESC *w; + WORD_LIST *l; + char *t, *t1, *temp; + int hasdol; + + /* If the entire expression is between double quotes, we want to treat + the value as a double-quoted string, with the exception that we strip + embedded unescaped double quotes (for sh backwards compatibility). */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *value) + { + hasdol = 0; + temp = string_extract_double_quoted (value, &hasdol, 1); + } + else + temp = value; + + w = alloc_word_desc (); + hasdol = 0; + /* XXX was 0 not quoted */ + l = *temp ? expand_string_for_rhs (temp, quoted, &hasdol, (int *)NULL) + : (WORD_LIST *)0; + if (hasdollarat) + *hasdollarat = hasdol || (l && l->next); + if (temp != value) + free (temp); + if (l) + { + /* The expansion of TEMP returned something. We need to treat things + slightly differently if HASDOL is non-zero. If we have "$@", the + individual words have already been quoted. We need to turn them + into a string with the words separated by the first character of + $IFS without any additional quoting, so string_list_dollar_at won't + do the right thing. We use string_list_dollar_star instead. */ + temp = (hasdol || l->next) ? string_list_dollar_star (l) : string_list (l); + + /* If l->next is not null, we know that TEMP contained "$@", since that + is the only expansion that creates more than one word. */ + if (qdollaratp && ((hasdol && quoted) || l->next)) + *qdollaratp = 1; + dispose_words (l); + } + else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && hasdol) + { + /* The brace expansion occurred between double quotes and there was + a $@ in TEMP. It does not matter if the $@ is quoted, as long as + it does not expand to anything. In this case, we want to return + a quoted empty string. */ + temp = make_quoted_char ('\0'); + w->flags |= W_HASQUOTEDNULL; + } + else + temp = (char *)NULL; + + if (c == '-' || c == '+') + { + w->word = temp; + return w; + } + + /* c == '=' */ + t = temp ? savestring (temp) : savestring (""); + t1 = dequote_string (t); + free (t); +#if defined (ARRAY_VARS) + if (valid_array_reference (name)) + assign_array_element (name, t1, 0); + else +#endif /* ARRAY_VARS */ + bind_variable (name, t1, 0); + + /* From Posix group discussion Feb-March 2010. Issue 7 0000221 */ + free (temp); + + w->word = t1; + return w; +} + +/* Deal with the right hand side of a ${name:?value} expansion in the case + that NAME is null or not set. If VALUE is non-null it is expanded and + used as the error message to print, otherwise a standard message is + printed. */ +static void +parameter_brace_expand_error (name, value) + char *name, *value; +{ + WORD_LIST *l; + char *temp; + + if (value && *value) + { + l = expand_string (value, 0); + temp = string_list (l); + report_error ("%s: %s", name, temp ? temp : ""); /* XXX was value not "" */ + FREE (temp); + dispose_words (l); + } + else + report_error (_("%s: parameter null or not set"), name); + + /* Free the data we have allocated during this expansion, since we + are about to longjmp out. */ + free (name); + FREE (value); +} + +/* Return 1 if NAME is something for which parameter_brace_expand_length is + OK to do. */ +static int +valid_length_expression (name) + char *name; +{ + return (name[1] == '\0' || /* ${#} */ + ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') || /* special param */ + (DIGIT (name[1]) && all_digits (name + 1)) || /* ${#11} */ +#if defined (ARRAY_VARS) + valid_array_reference (name + 1) || /* ${#a[7]} */ +#endif + legal_identifier (name + 1)); /* ${#PS1} */ +} + +/* Handle the parameter brace expansion that requires us to return the + length of a parameter. */ +static intmax_t +parameter_brace_expand_length (name) + char *name; +{ + char *t, *newname; + intmax_t number, arg_index; + WORD_LIST *list; +#if defined (ARRAY_VARS) + SHELL_VAR *var; +#endif + + if (name[1] == '\0') /* ${#} */ + number = number_of_args (); + else if ((name[1] == '@' || name[1] == '*') && name[2] == '\0') /* ${#@}, ${#*} */ + number = number_of_args (); + else if ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') + { + /* Take the lengths of some of the shell's special parameters. */ + switch (name[1]) + { + case '-': + t = which_set_flags (); + break; + case '?': + t = itos (last_command_exit_value); + break; + case '$': + t = itos (dollar_dollar_pid); + break; + case '!': + if (last_asynchronous_pid == NO_PID) + t = (char *)NULL; /* XXX - error if set -u set? */ + else + t = itos (last_asynchronous_pid); + break; + case '#': + t = itos (number_of_args ()); + break; + } + number = STRLEN (t); + FREE (t); + } +#if defined (ARRAY_VARS) + else if (valid_array_reference (name + 1)) + number = array_length_reference (name + 1); +#endif /* ARRAY_VARS */ + else + { + number = 0; + + if (legal_number (name + 1, &arg_index)) /* ${#1} */ + { + t = get_dollar_var_value (arg_index); + if (t == 0 && unbound_vars_is_error) + return INTMAX_MIN; + number = MB_STRLEN (t); + FREE (t); + } +#if defined (ARRAY_VARS) + else if ((var = find_variable (name + 1)) && (invisible_p (var) == 0) && (array_p (var) || assoc_p (var))) + { + if (assoc_p (var)) + t = assoc_reference (assoc_cell (var), "0"); + else + t = array_reference (array_cell (var), 0); + if (t == 0 && unbound_vars_is_error) + return INTMAX_MIN; + number = MB_STRLEN (t); + } +#endif + else /* ${#PS1} */ + { + newname = savestring (name); + newname[0] = '$'; + list = expand_string (newname, Q_DOUBLE_QUOTES); + t = list ? string_list (list) : (char *)NULL; + free (newname); + if (list) + dispose_words (list); + + number = t ? MB_STRLEN (t) : 0; + FREE (t); + } + } + + return (number); +} + +/* Skip characters in SUBSTR until DELIM. SUBSTR is an arithmetic expression, + so we do some ad-hoc parsing of an arithmetic expression to find + the first DELIM, instead of using strchr(3). Two rules: + 1. If the substring contains a `(', read until closing `)'. + 2. If the substring contains a `?', read past one `:' for each `?'. +*/ + +static char * +skiparith (substr, delim) + char *substr; + int delim; +{ + size_t sublen; + int skipcol, pcount, i; + DECLARE_MBSTATE; + + sublen = strlen (substr); + i = skipcol = pcount = 0; + while (substr[i]) + { + /* Balance parens */ + if (substr[i] == LPAREN) + { + pcount++; + i++; + continue; + } + if (substr[i] == RPAREN && pcount) + { + pcount--; + i++; + continue; + } + if (pcount) + { + ADVANCE_CHAR (substr, sublen, i); + continue; + } + + /* Skip one `:' for each `?' */ + if (substr[i] == ':' && skipcol) + { + skipcol--; + i++; + continue; + } + if (substr[i] == delim) + break; + if (substr[i] == '?') + { + skipcol++; + i++; + continue; + } + ADVANCE_CHAR (substr, sublen, i); + } + + return (substr + i); +} + +/* Verify and limit the start and end of the desired substring. If + VTYPE == 0, a regular shell variable is being used; if it is 1, + then the positional parameters are being used; if it is 2, then + VALUE is really a pointer to an array variable that should be used. + Return value is 1 if both values were OK, 0 if there was a problem + with an invalid expression, or -1 if the values were out of range. */ +static int +verify_substring_values (v, value, substr, vtype, e1p, e2p) + SHELL_VAR *v; + char *value, *substr; + int vtype; + intmax_t *e1p, *e2p; +{ + char *t, *temp1, *temp2; + arrayind_t len; + int expok; +#if defined (ARRAY_VARS) + ARRAY *a; + HASH_TABLE *h; +#endif + + /* duplicate behavior of strchr(3) */ + t = skiparith (substr, ':'); + if (*t && *t == ':') + *t = '\0'; + else + t = (char *)0; + + temp1 = expand_arith_string (substr, Q_DOUBLE_QUOTES); + *e1p = evalexp (temp1, &expok); + free (temp1); + if (expok == 0) + return (0); + + len = -1; /* paranoia */ + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + len = MB_STRLEN (value); + break; + case VT_POSPARMS: + len = number_of_args () + 1; + if (*e1p == 0) + len++; /* add one arg if counting from $0 */ + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + /* For arrays, the first value deals with array indices. Negative + offsets count from one past the array's maximum index. Associative + arrays treat the number of elements as the maximum index. */ + if (assoc_p (v)) + { + h = assoc_cell (v); + len = assoc_num_elements (h) + (*e1p < 0); + } + else + { + a = (ARRAY *)value; + len = array_max_index (a) + (*e1p < 0); /* arrays index from 0 to n - 1 */ + } + break; +#endif + } + + if (len == -1) /* paranoia */ + return -1; + + if (*e1p < 0) /* negative offsets count from end */ + *e1p += len; + + if (*e1p > len || *e1p < 0) + return (-1); + +#if defined (ARRAY_VARS) + /* For arrays, the second offset deals with the number of elements. */ + if (vtype == VT_ARRAYVAR) + len = assoc_p (v) ? assoc_num_elements (h) : array_num_elements (a); +#endif + + if (t) + { + t++; + temp2 = savestring (t); + temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES); + free (temp2); + t[-1] = ':'; + *e2p = evalexp (temp1, &expok); + free (temp1); + if (expok == 0) + return (0); + if ((vtype == VT_ARRAYVAR || vtype == VT_POSPARMS) && *e2p < 0) + { + internal_error (_("%s: substring expression < 0"), t); + return (0); + } +#if defined (ARRAY_VARS) + /* In order to deal with sparse arrays, push the intelligence about how + to deal with the number of elements desired down to the array- + specific functions. */ + if (vtype != VT_ARRAYVAR) +#endif + { + if (*e2p < 0) + { + *e2p += len; + if (*e2p < 0 || *e2p < *e1p) + { + internal_error (_("%s: substring expression < 0"), t); + return (0); + } + } + else + *e2p += *e1p; /* want E2 chars starting at E1 */ + if (*e2p > len) + *e2p = len; + } + } + else + *e2p = len; + + return (1); +} + +/* Return the type of variable specified by VARNAME (simple variable, + positional param, or array variable). Also return the value specified + by VARNAME (value of a variable or a reference to an array element). + QUOTED is the standard description of quoting state, using Q_* defines. + FLAGS is currently a set of flags to pass to array_value. If IND is + non-null and not INTMAX_MIN, and FLAGS includes AV_USEIND, IND is + passed to array_value so the array index is not computed again. + If this returns VT_VARIABLE, the caller assumes that CTLESC and CTLNUL + characters in the value are quoted with CTLESC and takes appropriate + steps. For convenience, *VALP is set to the dequoted VALUE. */ +static int +get_var_and_type (varname, value, ind, quoted, flags, varp, valp) + char *varname, *value; + arrayind_t ind; + int quoted, flags; + SHELL_VAR **varp; + char **valp; +{ + int vtype; + char *temp; +#if defined (ARRAY_VARS) + SHELL_VAR *v; +#endif + arrayind_t lind; + + /* This sets vtype to VT_VARIABLE or VT_POSPARMS */ + vtype = (varname[0] == '@' || varname[0] == '*') && varname[1] == '\0'; + if (vtype == VT_POSPARMS && varname[0] == '*') + vtype |= VT_STARSUB; + *varp = (SHELL_VAR *)NULL; + +#if defined (ARRAY_VARS) + if (valid_array_reference (varname)) + { + v = array_variable_part (varname, &temp, (int *)0); + /* If we want to signal array_value to use an already-computed index, + set LIND to that index */ + lind = (ind != INTMAX_MIN && (flags & AV_USEIND)) ? ind : 0; + if (v && (array_p (v) || assoc_p (v))) + { /* [ */ + if (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']') + { + /* Callers have to differentiate betwen indexed and associative */ + vtype = VT_ARRAYVAR; + if (temp[0] == '*') + vtype |= VT_STARSUB; + *valp = array_p (v) ? (char *)array_cell (v) : (char *)assoc_cell (v); + } + else + { + vtype = VT_ARRAYMEMBER; + *valp = array_value (varname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind); + } + *varp = v; + } + else if (v && (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']')) + { + vtype = VT_VARIABLE; + *varp = v; + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + *valp = dequote_string (value); + else + *valp = dequote_escapes (value); + } + else + { + vtype = VT_ARRAYMEMBER; + *varp = v; + *valp = array_value (varname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind); + } + } + else if ((v = find_variable (varname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v))) + { + vtype = VT_ARRAYMEMBER; + *varp = v; + *valp = assoc_p (v) ? assoc_reference (assoc_cell (v), "0") : array_reference (array_cell (v), 0); + } + else +#endif + { + if (value && vtype == VT_VARIABLE) + { + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + *valp = dequote_string (value); + else + *valp = dequote_escapes (value); + } + else + *valp = value; + } + + return vtype; +} + +/******************************************************/ +/* */ +/* Functions to extract substrings of variable values */ +/* */ +/******************************************************/ + +#if defined (HANDLE_MULTIBYTE) +/* Character-oriented rather than strictly byte-oriented substrings. S and + E, rather being strict indices into STRING, indicate character (possibly + multibyte character) positions that require calculation. + Used by the ${param:offset[:length]} expansion. */ +static char * +mb_substring (string, s, e) + char *string; + int s, e; +{ + char *tt; + int start, stop, i, slen; + DECLARE_MBSTATE; + + start = 0; + /* Don't need string length in ADVANCE_CHAR unless multibyte chars possible. */ + slen = (MB_CUR_MAX > 1) ? STRLEN (string) : 0; + + i = s; + while (string[start] && i--) + ADVANCE_CHAR (string, slen, start); + stop = start; + i = e - s; + while (string[stop] && i--) + ADVANCE_CHAR (string, slen, stop); + tt = substring (string, start, stop); + return tt; +} +#endif + +/* Process a variable substring expansion: ${name:e1[:e2]}. If VARNAME + is `@', use the positional parameters; otherwise, use the value of + VARNAME. If VARNAME is an array variable, use the array elements. */ + +static char * +parameter_brace_substring (varname, value, ind, substr, quoted, flags) + char *varname, *value; + int ind; + char *substr; + int quoted, flags; +{ + intmax_t e1, e2; + int vtype, r, starsub; + char *temp, *val, *tt, *oname; + SHELL_VAR *v; + + if (value == 0) + return ((char *)NULL); + + oname = this_command_name; + this_command_name = varname; + + vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val); + if (vtype == -1) + { + this_command_name = oname; + return ((char *)NULL); + } + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + r = verify_substring_values (v, val, substr, vtype, &e1, &e2); + this_command_name = oname; + if (r <= 0) + { + if (vtype == VT_VARIABLE) + FREE (val); + return ((r == 0) ? &expand_param_error : (char *)NULL); + } + + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1) + tt = mb_substring (val, e1, e2); + else +#endif + tt = substring (val, e1, e2); + + if (vtype == VT_VARIABLE) + FREE (val); + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + temp = quote_string (tt); + else + temp = tt ? quote_escapes (tt) : (char *)NULL; + FREE (tt); + break; + case VT_POSPARMS: + tt = pos_params (varname, e1, e2, quoted); + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0) + { + temp = tt ? quote_escapes (tt) : (char *)NULL; + FREE (tt); + } + else + temp = tt; + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + if (assoc_p (v)) + /* we convert to list and take first e2 elements starting at e1th + element -- officially undefined for now */ + temp = assoc_subrange (assoc_cell (v), e1, e2, starsub, quoted); + else + /* We want E2 to be the number of elements desired (arrays can be sparse, + so verify_substring_values just returns the numbers specified and we + rely on array_subrange to understand how to deal with them). */ + temp = array_subrange (array_cell (v), e1, e2, starsub, quoted); + /* array_subrange now calls array_quote_escapes as appropriate, so the + caller no longer needs to. */ + break; +#endif + default: + temp = (char *)NULL; + } + + return temp; +} + +/****************************************************************/ +/* */ +/* Functions to perform pattern substitution on variable values */ +/* */ +/****************************************************************/ + +static int +shouldexp_replacement (s) + char *s; +{ + register char *p; + + for (p = s; p && *p; p++) + { + if (*p == '\\') + p++; + else if (*p == '&') + return 1; + } + return 0; +} + +char * +pat_subst (string, pat, rep, mflags) + char *string, *pat, *rep; + int mflags; +{ + char *ret, *s, *e, *str, *rstr, *mstr; + int rsize, rptr, l, replen, mtype, rxpand, rslen, mlen; + + if (string == 0) + return (savestring ("")); + + mtype = mflags & MATCH_TYPEMASK; + +#if 0 /* bash-4.2 ? */ + rxpand = (rep && *rep) ? shouldexp_replacement (rep) : 0; +#else + rxpand = 0; +#endif + + /* Special cases: + * 1. A null pattern with mtype == MATCH_BEG means to prefix STRING + * with REP and return the result. + * 2. A null pattern with mtype == MATCH_END means to append REP to + * STRING and return the result. + * These don't understand or process `&' in the replacement string. + */ + if ((pat == 0 || *pat == 0) && (mtype == MATCH_BEG || mtype == MATCH_END)) + { + replen = STRLEN (rep); + l = STRLEN (string); + ret = (char *)xmalloc (replen + l + 2); + if (replen == 0) + strcpy (ret, string); + else if (mtype == MATCH_BEG) + { + strcpy (ret, rep); + strcpy (ret + replen, string); + } + else + { + strcpy (ret, string); + strcpy (ret + l, rep); + } + return (ret); + } + + ret = (char *)xmalloc (rsize = 64); + ret[0] = '\0'; + + for (replen = STRLEN (rep), rptr = 0, str = string;;) + { + if (match_pattern (str, pat, mtype, &s, &e) == 0) + break; + l = s - str; + + if (rxpand) + { + int x; + mlen = e - s; + mstr = xmalloc (mlen + 1); + for (x = 0; x < mlen; x++) + mstr[x] = s[x]; + mstr[mlen] = '\0'; + rstr = strcreplace (rep, '&', mstr, 0); + rslen = strlen (rstr); + } + else + { + rstr = rep; + rslen = replen; + } + + RESIZE_MALLOCED_BUFFER (ret, rptr, (l + rslen), rsize, 64); + + /* OK, now copy the leading unmatched portion of the string (from + str to s) to ret starting at rptr (the current offset). Then copy + the replacement string at ret + rptr + (s - str). Increment + rptr (if necessary) and str and go on. */ + if (l) + { + strncpy (ret + rptr, str, l); + rptr += l; + } + if (replen) + { + strncpy (ret + rptr, rstr, rslen); + rptr += rslen; + } + str = e; /* e == end of match */ + + if (rstr != rep) + free (rstr); + + if (((mflags & MATCH_GLOBREP) == 0) || mtype != MATCH_ANY) + break; + + if (s == e) + { + /* On a zero-length match, make sure we copy one character, since + we increment one character to avoid infinite recursion. */ + RESIZE_MALLOCED_BUFFER (ret, rptr, 1, rsize, 64); + ret[rptr++] = *str++; + e++; /* avoid infinite recursion on zero-length match */ + } + } + + /* Now copy the unmatched portion of the input string */ + if (str && *str) + { + RESIZE_MALLOCED_BUFFER (ret, rptr, STRLEN(str) + 1, rsize, 64); + strcpy (ret + rptr, str); + } + else + ret[rptr] = '\0'; + + return ret; +} + +/* Do pattern match and replacement on the positional parameters. */ +static char * +pos_params_pat_subst (string, pat, rep, mflags) + char *string, *pat, *rep; + int mflags; +{ + WORD_LIST *save, *params; + WORD_DESC *w; + char *ret; + int pchar, qflags; + + save = params = list_rest_of_args (); + if (save == 0) + return ((char *)NULL); + + for ( ; params; params = params->next) + { + ret = pat_subst (params->word->word, pat, rep, mflags); + w = alloc_word_desc (); + w->word = ret ? ret : savestring (""); + dispose_word (params->word); + params->word = w; + } + + pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@'; + qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0; + +#if 0 + if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB)) + ret = string_list_dollar_star (quote_list (save)); + else if ((mflags & MATCH_STARSUB) == MATCH_STARSUB) + ret = string_list_dollar_star (save); + else if ((mflags & MATCH_QUOTED) == MATCH_QUOTED) + ret = string_list_dollar_at (save, qflags); + else + ret = string_list_dollar_star (save); +#else + ret = string_list_pos_params (pchar, save, qflags); +#endif + + dispose_words (save); + + return (ret); +} + +/* Perform pattern substitution on VALUE, which is the expansion of + VARNAME. PATSUB is an expression supplying the pattern to match + and the string to substitute. QUOTED is a flags word containing + the type of quoting currently in effect. */ +static char * +parameter_brace_patsub (varname, value, ind, patsub, quoted, flags) + char *varname, *value; + int ind; + char *patsub; + int quoted, flags; +{ + int vtype, mflags, starsub, delim; + char *val, *temp, *pat, *rep, *p, *lpatsub, *tt; + SHELL_VAR *v; + + if (value == 0) + return ((char *)NULL); + + this_command_name = varname; + + vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val); + if (vtype == -1) + return ((char *)NULL); + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + mflags = 0; + /* PATSUB is never NULL when this is called. */ + if (*patsub == '/') + { + mflags |= MATCH_GLOBREP; + patsub++; + } + + /* Malloc this because expand_string_if_necessary or one of the expansion + functions in its call chain may free it on a substitution error. */ + lpatsub = savestring (patsub); + + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + mflags |= MATCH_QUOTED; + + if (starsub) + mflags |= MATCH_STARSUB; + + /* If the pattern starts with a `/', make sure we skip over it when looking + for the replacement delimiter. */ + delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0); + if (lpatsub[delim] == '/') + { + lpatsub[delim] = 0; + rep = lpatsub + delim + 1; + } + else + rep = (char *)NULL; + + if (rep && *rep == '\0') + rep = (char *)NULL; + + /* Perform the same expansions on the pattern as performed by the + pattern removal expansions. */ + pat = getpattern (lpatsub, quoted, 1); + + if (rep) + { + if ((mflags & MATCH_QUOTED) == 0) + rep = expand_string_if_necessary (rep, quoted, expand_string_unsplit); + else + rep = expand_string_to_string_internal (rep, quoted, expand_string_unsplit); + } + + /* ksh93 doesn't allow the match specifier to be a part of the expanded + pattern. This is an extension. Make sure we don't anchor the pattern + at the beginning or end of the string if we're doing global replacement, + though. */ + p = pat; + if (mflags & MATCH_GLOBREP) + mflags |= MATCH_ANY; + else if (pat && pat[0] == '#') + { + mflags |= MATCH_BEG; + p++; + } + else if (pat && pat[0] == '%') + { + mflags |= MATCH_END; + p++; + } + else + mflags |= MATCH_ANY; + + /* OK, we now want to substitute REP for PAT in VAL. If + flags & MATCH_GLOBREP is non-zero, the substitution is done + everywhere, otherwise only the first occurrence of PAT is + replaced. The pattern matching code doesn't understand + CTLESC quoting CTLESC and CTLNUL so we use the dequoted variable + values passed in (VT_VARIABLE) so the pattern substitution + code works right. We need to requote special chars after + we're done for VT_VARIABLE and VT_ARRAYMEMBER, and for the + other cases if QUOTED == 0, since the posparams and arrays + indexed by * or @ do special things when QUOTED != 0. */ + + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + temp = pat_subst (val, p, rep, mflags); + if (vtype == VT_VARIABLE) + FREE (val); + if (temp) + { + tt = (mflags & MATCH_QUOTED) ? quote_string (temp) : quote_escapes (temp); + free (temp); + temp = tt; + } + break; + case VT_POSPARMS: + temp = pos_params_pat_subst (val, p, rep, mflags); + if (temp && (mflags & MATCH_QUOTED) == 0) + { + tt = quote_escapes (temp); + free (temp); + temp = tt; + } + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + temp = assoc_p (v) ? assoc_patsub (assoc_cell (v), p, rep, mflags) + : array_patsub (array_cell (v), p, rep, mflags); + /* Don't call quote_escapes anymore; array_patsub calls + array_quote_escapes as appropriate before adding the + space separators; ditto for assoc_patsub. */ + break; +#endif + } + + FREE (pat); + FREE (rep); + free (lpatsub); + + return temp; +} + +/****************************************************************/ +/* */ +/* Functions to perform case modification on variable values */ +/* */ +/****************************************************************/ + +/* Do case modification on the positional parameters. */ + +static char * +pos_params_modcase (string, pat, modop, mflags) + char *string, *pat; + int modop; + int mflags; +{ + WORD_LIST *save, *params; + WORD_DESC *w; + char *ret; + int pchar, qflags; + + save = params = list_rest_of_args (); + if (save == 0) + return ((char *)NULL); + + for ( ; params; params = params->next) + { + ret = sh_modcase (params->word->word, pat, modop); + w = alloc_word_desc (); + w->word = ret ? ret : savestring (""); + dispose_word (params->word); + params->word = w; + } + + pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@'; + qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0; + + ret = string_list_pos_params (pchar, save, qflags); + dispose_words (save); + + return (ret); +} + +/* Perform case modification on VALUE, which is the expansion of + VARNAME. MODSPEC is an expression supplying the type of modification + to perform. QUOTED is a flags word containing the type of quoting + currently in effect. */ +static char * +parameter_brace_casemod (varname, value, ind, modspec, patspec, quoted, flags) + char *varname, *value; + int ind, modspec; + char *patspec; + int quoted, flags; +{ + int vtype, starsub, modop, mflags, x; + char *val, *temp, *pat, *p, *lpat, *tt; + SHELL_VAR *v; + + if (value == 0) + return ((char *)NULL); + + this_command_name = varname; + + vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val); + if (vtype == -1) + return ((char *)NULL); + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + modop = 0; + mflags = 0; + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + mflags |= MATCH_QUOTED; + if (starsub) + mflags |= MATCH_STARSUB; + + p = patspec; + if (modspec == '^') + { + x = p && p[0] == modspec; + modop = x ? CASE_UPPER : CASE_UPFIRST; + p += x; + } + else if (modspec == ',') + { + x = p && p[0] == modspec; + modop = x ? CASE_LOWER : CASE_LOWFIRST; + p += x; + } + else if (modspec == '~') + { + x = p && p[0] == modspec; + modop = x ? CASE_TOGGLEALL : CASE_TOGGLE; + p += x; + } + + lpat = p ? savestring (p) : 0; + /* Perform the same expansions on the pattern as performed by the + pattern removal expansions. FOR LATER */ + pat = lpat ? getpattern (lpat, quoted, 1) : 0; + + /* OK, now we do the case modification. */ + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + temp = sh_modcase (val, pat, modop); + if (vtype == VT_VARIABLE) + FREE (val); + if (temp) + { + tt = (mflags & MATCH_QUOTED) ? quote_string (temp) : quote_escapes (temp); + free (temp); + temp = tt; + } + break; + + case VT_POSPARMS: + temp = pos_params_modcase (val, pat, modop, mflags); + if (temp && (mflags & MATCH_QUOTED) == 0) + { + tt = quote_escapes (temp); + free (temp); + temp = tt; + } + break; + +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + temp = assoc_p (v) ? assoc_modcase (assoc_cell (v), pat, modop, mflags) + : array_modcase (array_cell (v), pat, modop, mflags); + /* Don't call quote_escapes; array_modcase calls array_quote_escapes + as appropriate before adding the space separators; ditto for + assoc_modcase. */ + break; +#endif + } + + FREE (pat); + free (lpat); + + return temp; +} + +/* Check for unbalanced parens in S, which is the contents of $(( ... )). If + any occur, this must be a nested command substitution, so return 0. + Otherwise, return 1. A valid arithmetic expression must always have a + ( before a matching ), so any cases where there are more right parens + means that this must not be an arithmetic expression, though the parser + will not accept it without a balanced total number of parens. */ +static int +chk_arithsub (s, len) + const char *s; + int len; +{ + int i, count; + DECLARE_MBSTATE; + + i = count = 0; + while (i < len) + { + if (s[i] == LPAREN) + count++; + else if (s[i] == RPAREN) + { + count--; + if (count < 0) + return 0; + } + + switch (s[i]) + { + default: + ADVANCE_CHAR (s, len, i); + break; + + case '\\': + i++; + if (s[i]) + ADVANCE_CHAR (s, len, i); + break; + + case '\'': + i = skip_single_quoted (s, len, ++i); + break; + + case '"': + i = skip_double_quoted ((char *)s, len, ++i); + break; + } + } + + return (count == 0); +} + +/****************************************************************/ +/* */ +/* Functions to perform parameter expansion on a string */ +/* */ +/****************************************************************/ + +/* ${[#][!]name[[:][^[^]][,[,]]#[#]%[%]-=?+[word][:e1[:e2]]]} */ +static WORD_DESC * +parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, contains_dollar_at) + char *string; + int *indexp, quoted, *quoted_dollar_atp, *contains_dollar_at, pflags; +{ + int check_nullness, var_is_set, var_is_null, var_is_special; + int want_substring, want_indir, want_patsub, want_casemod; + char *name, *value, *temp, *temp1; + WORD_DESC *tdesc, *ret; + int t_index, sindex, c, tflag, modspec; + intmax_t number; + arrayind_t ind; + + temp = temp1 = value = (char *)NULL; + var_is_set = var_is_null = var_is_special = check_nullness = 0; + want_substring = want_indir = want_patsub = want_casemod = 0; + + sindex = *indexp; + t_index = ++sindex; + /* ${#var} doesn't have any of the other parameter expansions on it. */ + if (string[t_index] == '#' && legal_variable_starter (string[t_index+1])) /* {{ */ + name = string_extract (string, &t_index, "}", SX_VARNAME); + else +#if defined (CASEMOD_EXPANSIONS) + /* To enable case-toggling expansions using the `~' operator character + change the 1 to 0. */ +# if defined (CASEMOD_CAPCASE) + name = string_extract (string, &t_index, "#%^,~:-=?+/}", SX_VARNAME); +# else + name = string_extract (string, &t_index, "#%^,:-=?+/}", SX_VARNAME); +# endif /* CASEMOD_CAPCASE */ +#else + name = string_extract (string, &t_index, "#%:-=?+/}", SX_VARNAME); +#endif /* CASEMOD_EXPANSIONS */ + + ret = 0; + tflag = 0; + + ind = INTMAX_MIN; + + /* If the name really consists of a special variable, then make sure + that we have the entire name. We don't allow indirect references + to special variables except `#', `?', `@' and `*'. */ + if ((sindex == t_index && VALID_SPECIAL_LENGTH_PARAM (string[t_index])) || + (sindex == t_index - 1 && string[sindex] == '!' && VALID_INDIR_PARAM (string[t_index]))) + { + t_index++; + temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0); + name = (char *)xrealloc (name, 3 + (strlen (temp1))); + *name = string[sindex]; + if (string[sindex] == '!') + { + /* indirect reference of $#, $?, $@, or $* */ + name[1] = string[sindex + 1]; + strcpy (name + 2, temp1); + } + else + strcpy (name + 1, temp1); + free (temp1); + } + sindex = t_index; + + /* Find out what character ended the variable name. Then + do the appropriate thing. */ + if (c = string[sindex]) + sindex++; + + /* If c is followed by one of the valid parameter expansion + characters, move past it as normal. If not, assume that + a substring specification is being given, and do not move + past it. */ + if (c == ':' && VALID_PARAM_EXPAND_CHAR (string[sindex])) + { + check_nullness++; + if (c = string[sindex]) + sindex++; + } + else if (c == ':' && string[sindex] != RBRACE) + want_substring = 1; + else if (c == '/' /* && string[sindex] != RBRACE */) /* XXX */ + want_patsub = 1; +#if defined (CASEMOD_EXPANSIONS) + else if (c == '^' || c == ',' || c == '~') + { + modspec = c; + want_casemod = 1; + } +#endif + + /* Catch the valid and invalid brace expressions that made it through the + tests above. */ + /* ${#-} is a valid expansion and means to take the length of $-. + Similarly for ${#?} and ${##}... */ + if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 && + VALID_SPECIAL_LENGTH_PARAM (c) && string[sindex] == RBRACE) + { + name = (char *)xrealloc (name, 3); + name[1] = c; + name[2] = '\0'; + c = string[sindex++]; + } + + /* ...but ${#%}, ${#:}, ${#=}, ${#+}, and ${#/} are errors. */ + if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 && + member (c, "%:=+/") && string[sindex] == RBRACE) + { + temp = (char *)NULL; + goto bad_substitution; + } + + /* Indirect expansion begins with a `!'. A valid indirect expansion is + either a variable name, one of the positional parameters or a special + variable that expands to one of the positional parameters. */ + want_indir = *name == '!' && + (legal_variable_starter ((unsigned char)name[1]) || DIGIT (name[1]) + || VALID_INDIR_PARAM (name[1])); + + /* Determine the value of this variable. */ + + /* Check for special variables, directly referenced. */ + if (SPECIAL_VAR (name, want_indir)) + var_is_special++; + + /* Check for special expansion things, like the length of a parameter */ + if (*name == '#' && name[1]) + { + /* If we are not pointing at the character just after the + closing brace, then we haven't gotten all of the name. + Since it begins with a special character, this is a bad + substitution. Also check NAME for validity before trying + to go on. */ + if (string[sindex - 1] != RBRACE || (valid_length_expression (name) == 0)) + { + temp = (char *)NULL; + goto bad_substitution; + } + + number = parameter_brace_expand_length (name); + if (number == INTMAX_MIN && unbound_vars_is_error) + { + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (name+1); + free (name); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + free (name); + + *indexp = sindex; + if (number < 0) + return (&expand_wdesc_error); + else + { + ret = alloc_word_desc (); + ret->word = itos (number); + return ret; + } + } + + /* ${@} is identical to $@. */ + if (name[0] == '@' && name[1] == '\0') + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + + if (contains_dollar_at) + *contains_dollar_at = 1; + } + + /* Process ${!PREFIX*} expansion. */ + if (want_indir && string[sindex - 1] == RBRACE && + (string[sindex - 2] == '*' || string[sindex - 2] == '@') && + legal_variable_starter ((unsigned char) name[1])) + { + char **x; + WORD_LIST *xlist; + + temp1 = savestring (name + 1); + number = strlen (temp1); + temp1[number - 1] = '\0'; + x = all_variables_matching_prefix (temp1); + xlist = strvec_to_word_list (x, 0, 0); + if (string[sindex - 2] == '*') + temp = string_list_dollar_star (xlist); + else + { + temp = string_list_dollar_at (xlist, quoted); + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + } + free (x); + dispose_words (xlist); + free (temp1); + *indexp = sindex; + + free (name); + + ret = alloc_word_desc (); + ret->word = temp; + return ret; + } + +#if defined (ARRAY_VARS) + /* Process ${!ARRAY[@]} and ${!ARRAY[*]} expansion. */ /* [ */ + if (want_indir && string[sindex - 1] == RBRACE && + string[sindex - 2] == ']' && valid_array_reference (name+1)) + { + char *x, *x1; + + temp1 = savestring (name + 1); + x = array_variable_name (temp1, &x1, (int *)0); /* [ */ + FREE (x); + if (ALL_ELEMENT_SUB (x1[0]) && x1[1] == ']') + { + temp = array_keys (temp1, quoted); /* handles assoc vars too */ + if (x1[0] == '@') + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + } + + free (temp1); + *indexp = sindex; + + ret = alloc_word_desc (); + ret->word = temp; + return ret; + } + + free (temp1); + } +#endif /* ARRAY_VARS */ + + /* Make sure that NAME is valid before trying to go on. */ + if (valid_brace_expansion_word (want_indir ? name + 1 : name, + var_is_special) == 0) + { + temp = (char *)NULL; + goto bad_substitution; + } + + if (want_indir) + tdesc = parameter_brace_expand_indir (name + 1, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at); + else + tdesc = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND|(pflags&PF_NOSPLIT2), &ind); + + if (tdesc) + { + temp = tdesc->word; + tflag = tdesc->flags; + dispose_word_desc (tdesc); + } + else + temp = (char *)0; + +#if defined (ARRAY_VARS) + if (valid_array_reference (name)) + chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at); +#endif + + var_is_set = temp != (char *)0; + var_is_null = check_nullness && (var_is_set == 0 || *temp == 0); + + /* Get the rest of the stuff inside the braces. */ + if (c && c != RBRACE) + { + /* Extract the contents of the ${ ... } expansion + according to the Posix.2 rules. */ + value = extract_dollar_brace_string (string, &sindex, quoted, (c == '%' || c == '#' || c =='/' || c == '^' || c == ',' || c ==':') ? SX_POSIXEXP|SX_WORD : SX_WORD); + if (string[sindex] == RBRACE) + sindex++; + else + goto bad_substitution; + } + else + value = (char *)NULL; + + *indexp = sindex; + + /* All the cases where an expansion can possibly generate an unbound + variable error. */ + if (want_substring || want_patsub || want_casemod || c == '#' || c == '%' || c == RBRACE) + { + if (var_is_set == 0 && unbound_vars_is_error && ((name[0] != '@' && name[0] != '*') || name[1])) + { + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (name); + FREE (value); + FREE (temp); + free (name); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + } + + /* If this is a substring spec, process it and add the result. */ + if (want_substring) + { + temp1 = parameter_brace_substring (name, temp, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0); + FREE (name); + FREE (value); + FREE (temp); + + if (temp1 == &expand_param_error) + return (&expand_wdesc_error); + else if (temp1 == &expand_param_fatal) + return (&expand_wdesc_fatal); + + ret = alloc_word_desc (); + ret->word = temp1; + if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ret->flags |= W_QUOTED|W_HASQUOTEDNULL; + return ret; + } + else if (want_patsub) + { + temp1 = parameter_brace_patsub (name, temp, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0); + FREE (name); + FREE (value); + FREE (temp); + + if (temp1 == &expand_param_error) + return (&expand_wdesc_error); + else if (temp1 == &expand_param_fatal) + return (&expand_wdesc_fatal); + + ret = alloc_word_desc (); + ret->word = temp1; + if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ret->flags |= W_QUOTED|W_HASQUOTEDNULL; + return ret; + } +#if defined (CASEMOD_EXPANSIONS) + else if (want_casemod) + { + temp1 = parameter_brace_casemod (name, temp, ind, modspec, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0); + FREE (name); + FREE (value); + FREE (temp); + + if (temp1 == &expand_param_error) + return (&expand_wdesc_error); + else if (temp1 == &expand_param_fatal) + return (&expand_wdesc_fatal); + + ret = alloc_word_desc (); + ret->word = temp1; + if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ret->flags |= W_QUOTED|W_HASQUOTEDNULL; + return ret; + } +#endif + + /* Do the right thing based on which character ended the variable name. */ + switch (c) + { + default: + case '\0': + bad_substitution: + report_error (_("%s: bad substitution"), string ? string : "??"); + FREE (value); + FREE (temp); + free (name); + return &expand_wdesc_error; + + case RBRACE: + break; + + case '#': /* ${param#[#]pattern} */ + case '%': /* ${param%[%]pattern} */ + if (value == 0 || *value == '\0' || temp == 0 || *temp == '\0') + { + FREE (value); + break; + } + temp1 = parameter_brace_remove_pattern (name, temp, ind, value, c, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0); + free (temp); + free (value); + free (name); + + ret = alloc_word_desc (); + ret->word = temp1; + if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ret->flags |= W_QUOTED|W_HASQUOTEDNULL; + return ret; + + case '-': + case '=': + case '?': + case '+': + if (var_is_set && var_is_null == 0) + { + /* If the operator is `+', we don't want the value of the named + variable for anything, just the value of the right hand side. */ + if (c == '+') + { + /* XXX -- if we're double-quoted and the named variable is "$@", + we want to turn off any special handling of "$@" -- + we're not using it, so whatever is on the rhs applies. */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 0; + if (contains_dollar_at) + *contains_dollar_at = 0; + + FREE (temp); + if (value) + { + /* From Posix discussion on austin-group list. Issue 221 + requires that backslashes escaping `}' inside + double-quoted ${...} be removed. */ + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + quoted |= Q_DOLBRACE; + ret = parameter_brace_expand_rhs (name, value, c, + quoted, + quoted_dollar_atp, + contains_dollar_at); + /* XXX - fix up later, esp. noting presence of + W_HASQUOTEDNULL in ret->flags */ + free (value); + } + else + temp = (char *)NULL; + } + else + { + FREE (value); + } + /* Otherwise do nothing; just use the value in TEMP. */ + } + else /* VAR not set or VAR is NULL. */ + { + FREE (temp); + temp = (char *)NULL; + if (c == '=' && var_is_special) + { + report_error (_("$%s: cannot assign in this way"), name); + free (name); + free (value); + return &expand_wdesc_error; + } + else if (c == '?') + { + parameter_brace_expand_error (name, value); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + else if (c != '+') + { + /* XXX -- if we're double-quoted and the named variable is "$@", + we want to turn off any special handling of "$@" -- + we're not using it, so whatever is on the rhs applies. */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 0; + if (contains_dollar_at) + *contains_dollar_at = 0; + + /* From Posix discussion on austin-group list. Issue 221 requires + that backslashes escaping `}' inside double-quoted ${...} be + removed. */ + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + quoted |= Q_DOLBRACE; + ret = parameter_brace_expand_rhs (name, value, c, quoted, + quoted_dollar_atp, + contains_dollar_at); + /* XXX - fix up later, esp. noting presence of + W_HASQUOTEDNULL in tdesc->flags */ + } + free (value); + } + + break; + } + free (name); + + if (ret == 0) + { + ret = alloc_word_desc (); + ret->flags = tflag; + ret->word = temp; + } + return (ret); +} + +/* Expand a single ${xxx} expansion. The braces are optional. When + the braces are used, parameter_brace_expand() does the work, + possibly calling param_expand recursively. */ +static WORD_DESC * +param_expand (string, sindex, quoted, expanded_something, + contains_dollar_at, quoted_dollar_at_p, had_quoted_null_p, + pflags) + char *string; + int *sindex, quoted, *expanded_something, *contains_dollar_at; + int *quoted_dollar_at_p, *had_quoted_null_p, pflags; +{ + char *temp, *temp1, uerror[3]; + int zindex, t_index, expok; + unsigned char c; + intmax_t number; + SHELL_VAR *var; + WORD_LIST *list; + WORD_DESC *tdesc, *ret; + int tflag; + + zindex = *sindex; + c = string[++zindex]; + + temp = (char *)NULL; + ret = tdesc = (WORD_DESC *)NULL; + tflag = 0; + + /* Do simple cases first. Switch on what follows '$'. */ + switch (c) + { + /* $0 .. $9? */ + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + temp1 = dollar_vars[TODIGIT (c)]; + if (unbound_vars_is_error && temp1 == (char *)NULL) + { + uerror[0] = '$'; + uerror[1] = c; + uerror[2] = '\0'; + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (uerror); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + if (temp1) + temp = (*temp1 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp1) + : quote_escapes (temp1); + else + temp = (char *)NULL; + + break; + + /* $$ -- pid of the invoking shell. */ + case '$': + temp = itos (dollar_dollar_pid); + break; + + /* $# -- number of positional parameters. */ + case '#': + temp = itos (number_of_args ()); + break; + + /* $? -- return value of the last synchronous command. */ + case '?': + temp = itos (last_command_exit_value); + break; + + /* $- -- flags supplied to the shell on invocation or by `set'. */ + case '-': + temp = which_set_flags (); + break; + + /* $! -- Pid of the last asynchronous command. */ + case '!': + /* If no asynchronous pids have been created, expand to nothing. + If `set -u' has been executed, and no async processes have + been created, this is an expansion error. */ + if (last_asynchronous_pid == NO_PID) + { + if (expanded_something) + *expanded_something = 0; + temp = (char *)NULL; + if (unbound_vars_is_error) + { + uerror[0] = '$'; + uerror[1] = c; + uerror[2] = '\0'; + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (uerror); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + } + else + temp = itos (last_asynchronous_pid); + break; + + /* The only difference between this and $@ is when the arg is quoted. */ + case '*': /* `$*' */ + list = list_rest_of_args (); + +#if 0 + /* According to austin-group posix proposal by Geoff Clare in + <20090505091501.GA10097@squonk.masqnet> of 5 May 2009: + + "The shell shall write a message to standard error and + immediately exit when it tries to expand an unset parameter + other than the '@' and '*' special parameters." + */ + + if (list == 0 && unbound_vars_is_error && (pflags & PF_IGNUNBOUND) == 0) + { + uerror[0] = '$'; + uerror[1] = '*'; + uerror[2] = '\0'; + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (uerror); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } +#endif + + /* If there are no command-line arguments, this should just + disappear if there are other characters in the expansion, + even if it's quoted. */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && list == 0) + temp = (char *)NULL; + else if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE)) + { + /* If we have "$*" we want to make a string of the positional + parameters, separated by the first character of $IFS, and + quote the whole string, including the separators. If IFS + is unset, the parameters are separated by ' '; if $IFS is + null, the parameters are concatenated. */ + temp = (quoted & (Q_DOUBLE_QUOTES|Q_PATQUOTE)) ? string_list_dollar_star (list) : string_list (list); + if (temp) + { + temp1 = quote_string (temp); + if (*temp == 0) + tflag |= W_HASQUOTEDNULL; + free (temp); + temp = temp1; + } + } + else + { + /* We check whether or not we're eventually going to split $* here, + for example when IFS is empty and we are processing the rhs of + an assignment statement. In that case, we don't separate the + arguments at all. Otherwise, if the $* is not quoted it is + identical to $@ */ +#if 1 +# if defined (HANDLE_MULTIBYTE) + if (expand_no_split_dollar_star && ifs_firstc[0] == 0) +# else + if (expand_no_split_dollar_star && ifs_firstc == 0) +# endif + temp = string_list_dollar_star (list); + else + temp = string_list_dollar_at (list, quoted); +#else + temp = string_list_dollar_at (list, quoted); +#endif + if (expand_no_split_dollar_star == 0 && contains_dollar_at) + *contains_dollar_at = 1; + } + + dispose_words (list); + break; + + /* When we have "$@" what we want is "$1" "$2" "$3" ... This + means that we have to turn quoting off after we split into + the individually quoted arguments so that the final split + on the first character of $IFS is still done. */ + case '@': /* `$@' */ + list = list_rest_of_args (); + +#if 0 + /* According to austin-group posix proposal by Geoff Clare in + <20090505091501.GA10097@squonk.masqnet> of 5 May 2009: + + "The shell shall write a message to standard error and + immediately exit when it tries to expand an unset parameter + other than the '@' and '*' special parameters." + */ + + if (list == 0 && unbound_vars_is_error && (pflags & PF_IGNUNBOUND) == 0) + { + uerror[0] = '$'; + uerror[1] = '@'; + uerror[2] = '\0'; + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (uerror); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } +#endif + + /* We want to flag the fact that we saw this. We can't turn + off quoting entirely, because other characters in the + string might need it (consider "\"$@\""), but we need some + way to signal that the final split on the first character + of $IFS should be done, even though QUOTED is 1. */ + /* XXX - should this test include Q_PATQUOTE? */ + if (quoted_dollar_at_p && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + *quoted_dollar_at_p = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + + /* We want to separate the positional parameters with the first + character of $IFS in case $IFS is something other than a space. + We also want to make sure that splitting is done no matter what -- + according to POSIX.2, this expands to a list of the positional + parameters no matter what IFS is set to. */ +#if 0 + temp = string_list_dollar_at (list, quoted); +#else + temp = string_list_dollar_at (list, (pflags & PF_ASSIGNRHS) ? (quoted|Q_DOUBLE_QUOTES) : quoted); +#endif + + dispose_words (list); + break; + + case LBRACE: + tdesc = parameter_brace_expand (string, &zindex, quoted, pflags, + quoted_dollar_at_p, + contains_dollar_at); + + if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal) + return (tdesc); + temp = tdesc ? tdesc->word : (char *)0; + + /* XXX */ + /* Quoted nulls should be removed if there is anything else + in the string. */ + /* Note that we saw the quoted null so we can add one back at + the end of this function if there are no other characters + in the string, discard TEMP, and go on. The exception to + this is when we have "${@}" and $1 is '', since $@ needs + special handling. */ + if (tdesc && tdesc->word && (tdesc->flags & W_HASQUOTEDNULL) && QUOTED_NULL (temp)) + { + if (had_quoted_null_p) + *had_quoted_null_p = 1; + if (*quoted_dollar_at_p == 0) + { + free (temp); + tdesc->word = temp = (char *)NULL; + } + + } + + ret = tdesc; + goto return0; + + /* Do command or arithmetic substitution. */ + case LPAREN: + /* We have to extract the contents of this paren substitution. */ + t_index = zindex + 1; + temp = extract_command_subst (string, &t_index, 0); + zindex = t_index; + + /* For Posix.2-style `$(( ))' arithmetic substitution, + extract the expression and pass it to the evaluator. */ + if (temp && *temp == LPAREN) + { + char *temp2; + temp1 = temp + 1; + temp2 = savestring (temp1); + t_index = strlen (temp2) - 1; + + if (temp2[t_index] != RPAREN) + { + free (temp2); + goto comsub; + } + + /* Cut off ending `)' */ + temp2[t_index] = '\0'; + + if (chk_arithsub (temp2, t_index) == 0) + { + free (temp2); +#if 0 + internal_warning (_("future versions of the shell will force evaluation as an arithmetic substitution")); +#endif + goto comsub; + } + + /* Expand variables found inside the expression. */ + temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES); + free (temp2); + +arithsub: + /* No error messages. */ + this_command_name = (char *)NULL; + number = evalexp (temp1, &expok); + free (temp); + free (temp1); + if (expok == 0) + { + if (interactive_shell == 0 && posixly_correct) + { + last_command_exit_value = EXECUTION_FAILURE; + return (&expand_wdesc_fatal); + } + else + return (&expand_wdesc_error); + } + temp = itos (number); + break; + } + +comsub: + if (pflags & PF_NOCOMSUB) + /* we need zindex+1 because string[zindex] == RPAREN */ + temp1 = substring (string, *sindex, zindex+1); + else + { + tdesc = command_substitute (temp, quoted); + temp1 = tdesc ? tdesc->word : (char *)NULL; + if (tdesc) + dispose_word_desc (tdesc); + } + FREE (temp); + temp = temp1; + break; + + /* Do POSIX.2d9-style arithmetic substitution. This will probably go + away in a future bash release. */ + case '[': + /* Extract the contents of this arithmetic substitution. */ + t_index = zindex + 1; + temp = extract_arithmetic_subst (string, &t_index); + zindex = t_index; + if (temp == 0) + { + temp = savestring (string); + if (expanded_something) + *expanded_something = 0; + goto return0; + } + + /* Do initial variable expansion. */ + temp1 = expand_arith_string (temp, Q_DOUBLE_QUOTES); + + goto arithsub; + + default: + /* Find the variable in VARIABLE_LIST. */ + temp = (char *)NULL; + + for (t_index = zindex; (c = string[zindex]) && legal_variable_char (c); zindex++) + ; + temp1 = (zindex > t_index) ? substring (string, t_index, zindex) : (char *)NULL; + + /* If this isn't a variable name, then just output the `$'. */ + if (temp1 == 0 || *temp1 == '\0') + { + FREE (temp1); + temp = (char *)xmalloc (2); + temp[0] = '$'; + temp[1] = '\0'; + if (expanded_something) + *expanded_something = 0; + goto return0; + } + + /* If the variable exists, return its value cell. */ + var = find_variable (temp1); + + if (var && invisible_p (var) == 0 && var_isset (var)) + { +#if defined (ARRAY_VARS) + if (assoc_p (var) || array_p (var)) + { + temp = array_p (var) ? array_reference (array_cell (var), 0) + : assoc_reference (assoc_cell (var), "0"); + if (temp) + temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp) + : quote_escapes (temp); + else if (unbound_vars_is_error) + goto unbound_variable; + } + else +#endif + { + temp = value_cell (var); + + temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp) + : quote_escapes (temp); + } + + free (temp1); + + goto return0; + } + + temp = (char *)NULL; + +unbound_variable: + if (unbound_vars_is_error) + { + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (temp1); + } + else + { + free (temp1); + goto return0; + } + + free (temp1); + last_command_exit_value = EXECUTION_FAILURE; + return ((unbound_vars_is_error && interactive_shell == 0) + ? &expand_wdesc_fatal + : &expand_wdesc_error); + } + + if (string[zindex]) + zindex++; + +return0: + *sindex = zindex; + + if (ret == 0) + { + ret = alloc_word_desc (); + ret->flags = tflag; /* XXX */ + ret->word = temp; + } + return ret; +} + +/* Make a word list which is the result of parameter and variable + expansion, command substitution, arithmetic substitution, and + quote removal of WORD. Return a pointer to a WORD_LIST which is + the result of the expansion. If WORD contains a null word, the + word list returned is also null. + + QUOTED contains flag values defined in shell.h. + + ISEXP is used to tell expand_word_internal that the word should be + treated as the result of an expansion. This has implications for + how IFS characters in the word are treated. + + CONTAINS_DOLLAR_AT and EXPANDED_SOMETHING are return values; when non-null + they point to an integer value which receives information about expansion. + CONTAINS_DOLLAR_AT gets non-zero if WORD contained "$@", else zero. + EXPANDED_SOMETHING get non-zero if WORD contained any parameter expansions, + else zero. + + This only does word splitting in the case of $@ expansion. In that + case, we split on ' '. */ + +/* Values for the local variable quoted_state. */ +#define UNQUOTED 0 +#define PARTIALLY_QUOTED 1 +#define WHOLLY_QUOTED 2 + +static WORD_LIST * +expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_something) + WORD_DESC *word; + int quoted, isexp; + int *contains_dollar_at; + int *expanded_something; +{ + WORD_LIST *list; + WORD_DESC *tword; + + /* The intermediate string that we build while expanding. */ + char *istring; + + /* The current size of the above object. */ + int istring_size; + + /* Index into ISTRING. */ + int istring_index; + + /* Temporary string storage. */ + char *temp, *temp1; + + /* The text of WORD. */ + register char *string; + + /* The size of STRING. */ + size_t string_size; + + /* The index into STRING. */ + int sindex; + + /* This gets 1 if we see a $@ while quoted. */ + int quoted_dollar_at; + + /* One of UNQUOTED, PARTIALLY_QUOTED, or WHOLLY_QUOTED, depending on + whether WORD contains no quoting characters, a partially quoted + string (e.g., "xx"ab), or is fully quoted (e.g., "xxab"). */ + int quoted_state; + + /* State flags */ + int had_quoted_null; + int has_dollar_at; + int tflag; + int pflags; /* flags passed to param_expand */ + + int assignoff; /* If assignment, offset of `=' */ + + register unsigned char c; /* Current character. */ + int t_index; /* For calls to string_extract_xxx. */ + + char twochars[2]; + + DECLARE_MBSTATE; + + istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE); + istring[istring_index = 0] = '\0'; + quoted_dollar_at = had_quoted_null = has_dollar_at = 0; + quoted_state = UNQUOTED; + + string = word->word; + if (string == 0) + goto finished_with_string; + /* Don't need the string length for the SADD... and COPY_ macros unless + multibyte characters are possible. */ + string_size = (MB_CUR_MAX > 1) ? strlen (string) : 1; + + if (contains_dollar_at) + *contains_dollar_at = 0; + + assignoff = -1; + + /* Begin the expansion. */ + + for (sindex = 0; ;) + { + c = string[sindex]; + + /* Case on toplevel character. */ + switch (c) + { + case '\0': + goto finished_with_string; + + case CTLESC: + sindex++; +#if HANDLE_MULTIBYTE + if (MB_CUR_MAX > 1 && string[sindex]) + { + SADD_MBQCHAR_BODY(temp, string, sindex, string_size); + } + else +#endif + { + temp = (char *)xmalloc (3); + temp[0] = CTLESC; + temp[1] = c = string[sindex]; + temp[2] = '\0'; + } + +dollar_add_string: + if (string[sindex]) + sindex++; + +add_string: + if (temp) + { + istring = sub_append_string (temp, istring, &istring_index, &istring_size); + temp = (char *)0; + } + + break; + +#if defined (PROCESS_SUBSTITUTION) + /* Process substitution. */ + case '<': + case '>': + { + if (string[++sindex] != LPAREN || (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (word->flags & (W_DQUOTE|W_NOPROCSUB)) || posixly_correct) + { + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + else + t_index = sindex + 1; /* skip past both '<' and LPAREN */ + + temp1 = extract_process_subst (string, (c == '<') ? "<(" : ">(", &t_index); /*))*/ + sindex = t_index; + + /* If the process substitution specification is `<()', we want to + open the pipe for writing in the child and produce output; if + it is `>()', we want to open the pipe for reading in the child + and consume input. */ + temp = temp1 ? process_substitute (temp1, (c == '>')) : (char *)0; + + FREE (temp1); + + goto dollar_add_string; + } +#endif /* PROCESS_SUBSTITUTION */ + + case '=': + /* Posix.2 section 3.6.1 says that tildes following `=' in words + which are not assignment statements are not expanded. If the + shell isn't in posix mode, though, we perform tilde expansion + on `likely candidate' unquoted assignment statements (flags + include W_ASSIGNMENT but not W_QUOTED). A likely candidate + contains an unquoted :~ or =~. Something to think about: we + now have a flag that says to perform tilde expansion on arguments + to `assignment builtins' like declare and export that look like + assignment statements. We now do tilde expansion on such words + even in POSIX mode. */ + if (word->flags & (W_ASSIGNRHS|W_NOTILDE)) + { + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c)) + goto add_ifs_character; + else + goto add_character; + } + /* If we're not in posix mode or forcing assignment-statement tilde + expansion, note where the `=' appears in the word and prepare to + do tilde expansion following the first `='. */ + if ((word->flags & W_ASSIGNMENT) && + (posixly_correct == 0 || (word->flags & W_TILDEEXP)) && + assignoff == -1 && sindex > 0) + assignoff = sindex; + if (sindex == assignoff && string[sindex+1] == '~') /* XXX */ + word->flags |= W_ITILDE; +#if 0 + else if ((word->flags & W_ASSIGNMENT) && + (posixly_correct == 0 || (word->flags & W_TILDEEXP)) && + string[sindex+1] == '~') + word->flags |= W_ITILDE; +#endif + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c)) + goto add_ifs_character; + else + goto add_character; + + case ':': + if (word->flags & W_NOTILDE) + { + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c)) + goto add_ifs_character; + else + goto add_character; + } + + if ((word->flags & (W_ASSIGNMENT|W_ASSIGNRHS|W_TILDEEXP)) && + string[sindex+1] == '~') + word->flags |= W_ITILDE; + + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c)) + goto add_ifs_character; + else + goto add_character; + + case '~': + /* If the word isn't supposed to be tilde expanded, or we're not + at the start of a word or after an unquoted : or = in an + assignment statement, we don't do tilde expansion. */ + if ((word->flags & (W_NOTILDE|W_DQUOTE)) || + (sindex > 0 && ((word->flags & W_ITILDE) == 0)) || + (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + { + word->flags &= ~W_ITILDE; + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0) + goto add_ifs_character; + else + goto add_character; + } + + if (word->flags & W_ASSIGNRHS) + tflag = 2; + else if (word->flags & (W_ASSIGNMENT|W_TILDEEXP)) + tflag = 1; + else + tflag = 0; + + temp = bash_tilde_find_word (string + sindex, tflag, &t_index); + + word->flags &= ~W_ITILDE; + + if (temp && *temp && t_index > 0) + { + temp1 = bash_tilde_expand (temp, tflag); + if (temp1 && *temp1 == '~' && STREQ (temp, temp1)) + { + FREE (temp); + FREE (temp1); + goto add_character; /* tilde expansion failed */ + } + free (temp); + temp = temp1; + sindex += t_index; + goto add_quoted_string; /* XXX was add_string */ + } + else + { + FREE (temp); + goto add_character; + } + + case '$': + if (expanded_something) + *expanded_something = 1; + + has_dollar_at = 0; + pflags = (word->flags & W_NOCOMSUB) ? PF_NOCOMSUB : 0; + if (word->flags & W_NOSPLIT2) + pflags |= PF_NOSPLIT2; + if (word->flags & W_ASSIGNRHS) + pflags |= PF_ASSIGNRHS; + tword = param_expand (string, &sindex, quoted, expanded_something, + &has_dollar_at, "ed_dollar_at, + &had_quoted_null, pflags); + + if (tword == &expand_wdesc_error || tword == &expand_wdesc_fatal) + { + free (string); + free (istring); + return ((tword == &expand_wdesc_error) ? &expand_word_error + : &expand_word_fatal); + } + if (contains_dollar_at && has_dollar_at) + *contains_dollar_at = 1; + + if (tword && (tword->flags & W_HASQUOTEDNULL)) + had_quoted_null = 1; + + temp = tword ? tword->word : (char *)NULL; + dispose_word_desc (tword); + + goto add_string; + break; + + case '`': /* Backquoted command substitution. */ + { + t_index = sindex++; + + temp = string_extract (string, &sindex, "`", SX_REQMATCH); + /* The test of sindex against t_index is to allow bare instances of + ` to pass through, for backwards compatibility. */ + if (temp == &extract_string_error || temp == &extract_string_fatal) + { + if (sindex - 1 == t_index) + { + sindex = t_index; + goto add_character; + } + report_error (_("bad substitution: no closing \"`\" in %s") , string+t_index); + free (string); + free (istring); + return ((temp == &extract_string_error) ? &expand_word_error + : &expand_word_fatal); + } + + if (expanded_something) + *expanded_something = 1; + + if (word->flags & W_NOCOMSUB) + /* sindex + 1 because string[sindex] == '`' */ + temp1 = substring (string, t_index, sindex + 1); + else + { + de_backslash (temp); + tword = command_substitute (temp, quoted); + temp1 = tword ? tword->word : (char *)NULL; + if (tword) + dispose_word_desc (tword); + } + FREE (temp); + temp = temp1; + goto dollar_add_string; + } + + case '\\': + if (string[sindex + 1] == '\n') + { + sindex += 2; + continue; + } + + c = string[++sindex]; + + if (quoted & Q_HERE_DOCUMENT) + tflag = CBSHDOC; + else if (quoted & Q_DOUBLE_QUOTES) + tflag = CBSDQUOTE; + else + tflag = 0; + + /* From Posix discussion on austin-group list: Backslash escaping + a } in ${...} is removed. Issue 0000221 */ + if ((quoted & Q_DOLBRACE) && c == RBRACE) + { + SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size); + } + else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0)) + { + SCOPY_CHAR_I (twochars, '\\', c, string, sindex, string_size); + } + else if (c == 0) + { + c = CTLNUL; + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + else + { + SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size); + } + + sindex++; +add_twochars: + /* BEFORE jumping here, we need to increment sindex if appropriate */ + RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, + DEFAULT_ARRAY_SIZE); + istring[istring_index++] = twochars[0]; + istring[istring_index++] = twochars[1]; + istring[istring_index] = '\0'; + + break; + + case '"': +#if 0 + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE)) +#else + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) +#endif + goto add_character; + + t_index = ++sindex; + temp = string_extract_double_quoted (string, &sindex, 0); + + /* If the quotes surrounded the entire string, then the + whole word was quoted. */ + quoted_state = (t_index == 1 && string[sindex] == '\0') + ? WHOLLY_QUOTED + : PARTIALLY_QUOTED; + + if (temp && *temp) + { + tword = alloc_word_desc (); + tword->word = temp; + + temp = (char *)NULL; + + has_dollar_at = 0; + /* Need to get W_HASQUOTEDNULL flag through this function. */ + list = expand_word_internal (tword, Q_DOUBLE_QUOTES, 0, &has_dollar_at, (int *)NULL); + + if (list == &expand_word_error || list == &expand_word_fatal) + { + free (istring); + free (string); + /* expand_word_internal has already freed temp_word->word + for us because of the way it prints error messages. */ + tword->word = (char *)NULL; + dispose_word (tword); + return list; + } + + dispose_word (tword); + + /* "$@" (a double-quoted dollar-at) expands into nothing, + not even a NULL word, when there are no positional + parameters. */ + if (list == 0 && has_dollar_at) + { + quoted_dollar_at++; + break; + } + + /* If we get "$@", we know we have expanded something, so we + need to remember it for the final split on $IFS. This is + a special case; it's the only case where a quoted string + can expand into more than one word. It's going to come back + from the above call to expand_word_internal as a list with + a single word, in which all characters are quoted and + separated by blanks. What we want to do is to turn it back + into a list for the next piece of code. */ + if (list) + dequote_list (list); + + if (list && list->word && (list->word->flags & W_HASQUOTEDNULL)) + had_quoted_null = 1; + + if (has_dollar_at) + { + quoted_dollar_at++; + if (contains_dollar_at) + *contains_dollar_at = 1; + if (expanded_something) + *expanded_something = 1; + } + } + else + { + /* What we have is "". This is a minor optimization. */ + FREE (temp); + list = (WORD_LIST *)NULL; + } + + /* The code above *might* return a list (consider the case of "$@", + where it returns "$1", "$2", etc.). We can't throw away the + rest of the list, and we have to make sure each word gets added + as quoted. We test on tresult->next: if it is non-NULL, we + quote the whole list, save it to a string with string_list, and + add that string. We don't need to quote the results of this + (and it would be wrong, since that would quote the separators + as well), so we go directly to add_string. */ + if (list) + { + if (list->next) + { +#if 0 + if (quoted_dollar_at && (word->flags & W_NOSPLIT2)) + temp = string_list_internal (quote_list (list), " "); + else +#endif + /* Testing quoted_dollar_at makes sure that "$@" is + split correctly when $IFS does not contain a space. */ + temp = quoted_dollar_at + ? string_list_dollar_at (list, Q_DOUBLE_QUOTES) + : string_list (quote_list (list)); + dispose_words (list); + goto add_string; + } + else + { + temp = savestring (list->word->word); + tflag = list->word->flags; + dispose_words (list); + + /* If the string is not a quoted null string, we want + to remove any embedded unquoted CTLNUL characters. + We do not want to turn quoted null strings back into + the empty string, though. We do this because we + want to remove any quoted nulls from expansions that + contain other characters. For example, if we have + x"$*"y or "x$*y" and there are no positional parameters, + the $* should expand into nothing. */ + /* We use the W_HASQUOTEDNULL flag to differentiate the + cases: a quoted null character as above and when + CTLNUL is contained in the (non-null) expansion + of some variable. We use the had_quoted_null flag to + pass the value through this function to its caller. */ + if ((tflag & W_HASQUOTEDNULL) && QUOTED_NULL (temp) == 0) + remove_quoted_nulls (temp); /* XXX */ + } + } + else + temp = (char *)NULL; + + /* We do not want to add quoted nulls to strings that are only + partially quoted; we can throw them away. The execption to + this is when we are going to be performing word splitting, + since we have to preserve a null argument if the next character + will cause word splitting. */ + if (temp == 0 && quoted_state == PARTIALLY_QUOTED && (word->flags & (W_NOSPLIT|W_NOSPLIT2))) + continue; + + add_quoted_string: + + if (temp) + { + temp1 = temp; + temp = quote_string (temp); + free (temp1); + goto add_string; + } + else + { + /* Add NULL arg. */ + c = CTLNUL; + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + + /* break; */ + + case '\'': +#if 0 + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE)) +#else + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) +#endif + goto add_character; + + t_index = ++sindex; + temp = string_extract_single_quoted (string, &sindex); + + /* If the entire STRING was surrounded by single quotes, + then the string is wholly quoted. */ + quoted_state = (t_index == 1 && string[sindex] == '\0') + ? WHOLLY_QUOTED + : PARTIALLY_QUOTED; + + /* If all we had was '', it is a null expansion. */ + if (*temp == '\0') + { + free (temp); + temp = (char *)NULL; + } + else + remove_quoted_escapes (temp); /* ??? */ + + /* We do not want to add quoted nulls to strings that are only + partially quoted; such nulls are discarded. */ + if (temp == 0 && (quoted_state == PARTIALLY_QUOTED)) + continue; + + /* If we have a quoted null expansion, add a quoted NULL to istring. */ + if (temp == 0) + { + c = CTLNUL; + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + else + goto add_quoted_string; + + /* break; */ + + default: + /* This is the fix for " $@ " */ + add_ifs_character: + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (isexp == 0 && isifs (c))) + { + if (string[sindex]) /* from old goto dollar_add_string */ + sindex++; + if (c == 0) + { + c = CTLNUL; + goto add_character; + } + else + { +#if HANDLE_MULTIBYTE + if (MB_CUR_MAX > 1) + sindex--; + + if (MB_CUR_MAX > 1) + { + SADD_MBQCHAR_BODY(temp, string, sindex, string_size); + } + else +#endif + { + twochars[0] = CTLESC; + twochars[1] = c; + goto add_twochars; + } + } + } + + SADD_MBCHAR (temp, string, sindex, string_size); + + add_character: + RESIZE_MALLOCED_BUFFER (istring, istring_index, 1, istring_size, + DEFAULT_ARRAY_SIZE); + istring[istring_index++] = c; + istring[istring_index] = '\0'; + + /* Next character. */ + sindex++; + } + } + +finished_with_string: + /* OK, we're ready to return. If we have a quoted string, and + quoted_dollar_at is not set, we do no splitting at all; otherwise + we split on ' '. The routines that call this will handle what to + do if nothing has been expanded. */ + + /* Partially and wholly quoted strings which expand to the empty + string are retained as an empty arguments. Unquoted strings + which expand to the empty string are discarded. The single + exception is the case of expanding "$@" when there are no + positional parameters. In that case, we discard the expansion. */ + + /* Because of how the code that handles "" and '' in partially + quoted strings works, we need to make ISTRING into a QUOTED_NULL + if we saw quoting characters, but the expansion was empty. + "" and '' are tossed away before we get to this point when + processing partially quoted strings. This makes "" and $xxx"" + equivalent when xxx is unset. We also look to see whether we + saw a quoted null from a ${} expansion and add one back if we + need to. */ + + /* If we expand to nothing and there were no single or double quotes + in the word, we throw it away. Otherwise, we return a NULL word. + The single exception is for $@ surrounded by double quotes when + there are no positional parameters. In that case, we also throw + the word away. */ + + if (*istring == '\0') + { + if (quoted_dollar_at == 0 && (had_quoted_null || quoted_state == PARTIALLY_QUOTED)) + { + istring[0] = CTLNUL; + istring[1] = '\0'; + tword = make_bare_word (istring); + tword->flags |= W_HASQUOTEDNULL; /* XXX */ + list = make_word_list (tword, (WORD_LIST *)NULL); + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + tword->flags |= W_QUOTED; + } + /* According to sh, ksh, and Posix.2, if a word expands into nothing + and a double-quoted "$@" appears anywhere in it, then the entire + word is removed. */ + else if (quoted_state == UNQUOTED || quoted_dollar_at) + list = (WORD_LIST *)NULL; +#if 0 + else + { + tword = make_bare_word (istring); + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + tword->flags |= W_QUOTED; + list = make_word_list (tword, (WORD_LIST *)NULL); + } +#else + else + list = (WORD_LIST *)NULL; +#endif + } + else if (word->flags & W_NOSPLIT) + { + tword = make_bare_word (istring); + if (word->flags & W_ASSIGNMENT) + tword->flags |= W_ASSIGNMENT; /* XXX */ + if (word->flags & W_COMPASSIGN) + tword->flags |= W_COMPASSIGN; /* XXX */ + if (word->flags & W_NOGLOB) + tword->flags |= W_NOGLOB; /* XXX */ + if (word->flags & W_NOEXPAND) + tword->flags |= W_NOEXPAND; /* XXX */ + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + tword->flags |= W_QUOTED; + if (had_quoted_null) + tword->flags |= W_HASQUOTEDNULL; + list = make_word_list (tword, (WORD_LIST *)NULL); + } + else + { + char *ifs_chars; + + ifs_chars = (quoted_dollar_at || has_dollar_at) ? ifs_value : (char *)NULL; + + /* If we have $@, we need to split the results no matter what. If + IFS is unset or NULL, string_list_dollar_at has separated the + positional parameters with a space, so we split on space (we have + set ifs_chars to " \t\n" above if ifs is unset). If IFS is set, + string_list_dollar_at has separated the positional parameters + with the first character of $IFS, so we split on $IFS. */ + if (has_dollar_at && ifs_chars) + list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1); + else + { + tword = make_bare_word (istring); + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (quoted_state == WHOLLY_QUOTED)) + tword->flags |= W_QUOTED; + if (word->flags & W_ASSIGNMENT) + tword->flags |= W_ASSIGNMENT; + if (word->flags & W_COMPASSIGN) + tword->flags |= W_COMPASSIGN; + if (word->flags & W_NOGLOB) + tword->flags |= W_NOGLOB; + if (word->flags & W_NOEXPAND) + tword->flags |= W_NOEXPAND; + if (had_quoted_null) + tword->flags |= W_HASQUOTEDNULL; /* XXX */ + list = make_word_list (tword, (WORD_LIST *)NULL); + } + } + + free (istring); + return (list); +} + +/* **************************************************************** */ +/* */ +/* Functions for Quote Removal */ +/* */ +/* **************************************************************** */ + +/* Perform quote removal on STRING. If QUOTED > 0, assume we are obeying the + backslash quoting rules for within double quotes or a here document. */ +char * +string_quote_removal (string, quoted) + char *string; + int quoted; +{ + size_t slen; + char *r, *result_string, *temp, *send; + int sindex, tindex, dquote; + unsigned char c; + DECLARE_MBSTATE; + + /* The result can be no longer than the original string. */ + slen = strlen (string); + send = string + slen; + + r = result_string = (char *)xmalloc (slen + 1); + + for (dquote = sindex = 0; c = string[sindex];) + { + switch (c) + { + case '\\': + c = string[++sindex]; + if (c == 0) + { + *r++ = '\\'; + break; + } + if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0) + *r++ = '\\'; + /* FALLTHROUGH */ + + default: + SCOPY_CHAR_M (r, string, send, sindex); + break; + + case '\'': + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) + { + *r++ = c; + sindex++; + break; + } + tindex = sindex + 1; + temp = string_extract_single_quoted (string, &tindex); + if (temp) + { + strcpy (r, temp); + r += strlen (r); + free (temp); + } + sindex = tindex; + break; + + case '"': + dquote = 1 - dquote; + sindex++; + break; + } + } + *r = '\0'; + return (result_string); +} + +#if 0 +/* UNUSED */ +/* Perform quote removal on word WORD. This allocates and returns a new + WORD_DESC *. */ +WORD_DESC * +word_quote_removal (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_DESC *w; + char *t; + + t = string_quote_removal (word->word, quoted); + w = alloc_word_desc (); + w->word = t ? t : savestring (""); + return (w); +} + +/* Perform quote removal on all words in LIST. If QUOTED is non-zero, + the members of the list are treated as if they are surrounded by + double quotes. Return a new list, or NULL if LIST is NULL. */ +WORD_LIST * +word_list_quote_removal (list, quoted) + WORD_LIST *list; + int quoted; +{ + WORD_LIST *result, *t, *tresult, *e; + + for (t = list, result = (WORD_LIST *)NULL; t; t = t->next) + { + tresult = make_word_list (word_quote_removal (t->word, quoted), (WORD_LIST *)NULL); +#if 0 + result = (WORD_LIST *) list_append (result, tresult); +#else + if (result == 0) + result = e = tresult; + else + { + e->next = tresult; + while (e->next) + e = e->next; + } +#endif + } + return (result); +} +#endif + +/******************************************* + * * + * Functions to perform word splitting * + * * + *******************************************/ + +void +setifs (v) + SHELL_VAR *v; +{ + char *t; + unsigned char uc; + + ifs_var = v; + ifs_value = (v && value_cell (v)) ? value_cell (v) : " \t\n"; + + /* Should really merge ifs_cmap with sh_syntaxtab. XXX - doesn't yet + handle multibyte chars in IFS */ + memset (ifs_cmap, '\0', sizeof (ifs_cmap)); + for (t = ifs_value ; t && *t; t++) + { + uc = *t; + ifs_cmap[uc] = 1; + } + +#if defined (HANDLE_MULTIBYTE) + if (ifs_value == 0) + { + ifs_firstc[0] = '\0'; + ifs_firstc_len = 1; + } + else + { + size_t ifs_len; + ifs_len = strnlen (ifs_value, MB_CUR_MAX); + ifs_firstc_len = MBLEN (ifs_value, ifs_len); + if (ifs_firstc_len == 1 || ifs_firstc_len == 0 || MB_INVALIDCH (ifs_firstc_len)) + { + ifs_firstc[0] = ifs_value[0]; + ifs_firstc[1] = '\0'; + ifs_firstc_len = 1; + } + else + memcpy (ifs_firstc, ifs_value, ifs_firstc_len); + } +#else + ifs_firstc = ifs_value ? *ifs_value : 0; +#endif +} + +char * +getifs () +{ + return ifs_value; +} + +/* This splits a single word into a WORD LIST on $IFS, but only if the word + is not quoted. list_string () performs quote removal for us, even if we + don't do any splitting. */ +WORD_LIST * +word_split (w, ifs_chars) + WORD_DESC *w; + char *ifs_chars; +{ + WORD_LIST *result; + + if (w) + { + char *xifs; + + xifs = ((w->flags & W_QUOTED) || ifs_chars == 0) ? "" : ifs_chars; + result = list_string (w->word, xifs, w->flags & W_QUOTED); + } + else + result = (WORD_LIST *)NULL; + + return (result); +} + +/* Perform word splitting on LIST and return the RESULT. It is possible + to return (WORD_LIST *)NULL. */ +static WORD_LIST * +word_list_split (list) + WORD_LIST *list; +{ + WORD_LIST *result, *t, *tresult, *e; + + for (t = list, result = (WORD_LIST *)NULL; t; t = t->next) + { + tresult = word_split (t->word, ifs_value); + if (result == 0) + result = e = tresult; + else + { + e->next = tresult; + while (e->next) + e = e->next; + } + } + return (result); +} + +/************************************************** + * * + * Functions to expand an entire WORD_LIST * + * * + **************************************************/ + +/* Do any word-expansion-specific cleanup and jump to top_level */ +static void +exp_jump_to_top_level (v) + int v; +{ + set_pipestatus_from_exit (last_command_exit_value); + + /* Cleanup code goes here. */ + expand_no_split_dollar_star = 0; /* XXX */ + expanding_redir = 0; + assigning_in_environment = 0; + + if (parse_and_execute_level == 0) + top_level_cleanup (); /* from sig.c */ + + jump_to_top_level (v); +} + +/* Put NLIST (which is a WORD_LIST * of only one element) at the front of + ELIST, and set ELIST to the new list. */ +#define PREPEND_LIST(nlist, elist) \ + do { nlist->next = elist; elist = nlist; } while (0) + +/* Separate out any initial variable assignments from TLIST. If set -k has + been executed, remove all assignment statements from TLIST. Initial + variable assignments and other environment assignments are placed + on SUBST_ASSIGN_VARLIST. */ +static WORD_LIST * +separate_out_assignments (tlist) + WORD_LIST *tlist; +{ + register WORD_LIST *vp, *lp; + + if (tlist == 0) + return ((WORD_LIST *)NULL); + + if (subst_assign_varlist) + dispose_words (subst_assign_varlist); /* Clean up after previous error */ + + subst_assign_varlist = (WORD_LIST *)NULL; + vp = lp = tlist; + + /* Separate out variable assignments at the start of the command. + Loop invariant: vp->next == lp + Loop postcondition: + lp = list of words left after assignment statements skipped + tlist = original list of words + */ + while (lp && (lp->word->flags & W_ASSIGNMENT)) + { + vp = lp; + lp = lp->next; + } + + /* If lp != tlist, we have some initial assignment statements. + We make SUBST_ASSIGN_VARLIST point to the list of assignment + words and TLIST point to the remaining words. */ + if (lp != tlist) + { + subst_assign_varlist = tlist; + /* ASSERT(vp->next == lp); */ + vp->next = (WORD_LIST *)NULL; /* terminate variable list */ + tlist = lp; /* remainder of word list */ + } + + /* vp == end of variable list */ + /* tlist == remainder of original word list without variable assignments */ + if (!tlist) + /* All the words in tlist were assignment statements */ + return ((WORD_LIST *)NULL); + + /* ASSERT(tlist != NULL); */ + /* ASSERT((tlist->word->flags & W_ASSIGNMENT) == 0); */ + + /* If the -k option is in effect, we need to go through the remaining + words, separate out the assignment words, and place them on + SUBST_ASSIGN_VARLIST. */ + if (place_keywords_in_env) + { + WORD_LIST *tp; /* tp == running pointer into tlist */ + + tp = tlist; + lp = tlist->next; + + /* Loop Invariant: tp->next == lp */ + /* Loop postcondition: tlist == word list without assignment statements */ + while (lp) + { + if (lp->word->flags & W_ASSIGNMENT) + { + /* Found an assignment statement, add this word to end of + subst_assign_varlist (vp). */ + if (!subst_assign_varlist) + subst_assign_varlist = vp = lp; + else + { + vp->next = lp; + vp = lp; + } + + /* Remove the word pointed to by LP from TLIST. */ + tp->next = lp->next; + /* ASSERT(vp == lp); */ + lp->next = (WORD_LIST *)NULL; + lp = tp->next; + } + else + { + tp = lp; + lp = lp->next; + } + } + } + return (tlist); +} + +#define WEXP_VARASSIGN 0x001 +#define WEXP_BRACEEXP 0x002 +#define WEXP_TILDEEXP 0x004 +#define WEXP_PARAMEXP 0x008 +#define WEXP_PATHEXP 0x010 + +/* All of the expansions, including variable assignments at the start of + the list. */ +#define WEXP_ALL (WEXP_VARASSIGN|WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP) + +/* All of the expansions except variable assignments at the start of + the list. */ +#define WEXP_NOVARS (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP) + +/* All of the `shell expansions': brace expansion, tilde expansion, parameter + expansion, command substitution, arithmetic expansion, word splitting, and + quote removal. */ +#define WEXP_SHELLEXP (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP) + +/* Take the list of words in LIST and do the various substitutions. Return + a new list of words which is the expanded list, and without things like + variable assignments. */ + +WORD_LIST * +expand_words (list) + WORD_LIST *list; +{ + return (expand_word_list_internal (list, WEXP_ALL)); +} + +/* Same as expand_words (), but doesn't hack variable or environment + variables. */ +WORD_LIST * +expand_words_no_vars (list) + WORD_LIST *list; +{ + return (expand_word_list_internal (list, WEXP_NOVARS)); +} + +WORD_LIST * +expand_words_shellexp (list) + WORD_LIST *list; +{ + return (expand_word_list_internal (list, WEXP_SHELLEXP)); +} + +static WORD_LIST * +glob_expand_word_list (tlist, eflags) + WORD_LIST *tlist; + int eflags; +{ + char **glob_array, *temp_string; + register int glob_index; + WORD_LIST *glob_list, *output_list, *disposables, *next; + WORD_DESC *tword; + + output_list = disposables = (WORD_LIST *)NULL; + glob_array = (char **)NULL; + while (tlist) + { + /* For each word, either globbing is attempted or the word is + added to orig_list. If globbing succeeds, the results are + added to orig_list and the word (tlist) is added to the list + of disposable words. If globbing fails and failed glob + expansions are left unchanged (the shell default), the + original word is added to orig_list. If globbing fails and + failed glob expansions are removed, the original word is + added to the list of disposable words. orig_list ends up + in reverse order and requires a call to REVERSE_LIST to + be set right. After all words are examined, the disposable + words are freed. */ + next = tlist->next; + + /* If the word isn't an assignment and contains an unquoted + pattern matching character, then glob it. */ + if ((tlist->word->flags & W_NOGLOB) == 0 && + unquoted_glob_pattern_p (tlist->word->word)) + { + glob_array = shell_glob_filename (tlist->word->word); + + /* Handle error cases. + I don't think we should report errors like "No such file + or directory". However, I would like to report errors + like "Read failed". */ + + if (glob_array == 0 || GLOB_FAILED (glob_array)) + { + glob_array = (char **)xmalloc (sizeof (char *)); + glob_array[0] = (char *)NULL; + } + + /* Dequote the current word in case we have to use it. */ + if (glob_array[0] == NULL) + { + temp_string = dequote_string (tlist->word->word); + free (tlist->word->word); + tlist->word->word = temp_string; + } + + /* Make the array into a word list. */ + glob_list = (WORD_LIST *)NULL; + for (glob_index = 0; glob_array[glob_index]; glob_index++) + { + tword = make_bare_word (glob_array[glob_index]); + tword->flags |= W_GLOBEXP; /* XXX */ + glob_list = make_word_list (tword, glob_list); + } + + if (glob_list) + { + output_list = (WORD_LIST *)list_append (glob_list, output_list); + PREPEND_LIST (tlist, disposables); + } + else if (fail_glob_expansion != 0) + { + report_error (_("no match: %s"), tlist->word->word); + exp_jump_to_top_level (DISCARD); + } + else if (allow_null_glob_expansion == 0) + { + /* Failed glob expressions are left unchanged. */ + PREPEND_LIST (tlist, output_list); + } + else + { + /* Failed glob expressions are removed. */ + PREPEND_LIST (tlist, disposables); + } + } + else + { + /* Dequote the string. */ + temp_string = dequote_string (tlist->word->word); + free (tlist->word->word); + tlist->word->word = temp_string; + PREPEND_LIST (tlist, output_list); + } + + strvec_dispose (glob_array); + glob_array = (char **)NULL; + + tlist = next; + } + + if (disposables) + dispose_words (disposables); + + if (output_list) + output_list = REVERSE_LIST (output_list, WORD_LIST *); + + return (output_list); +} + +#if defined (BRACE_EXPANSION) +static WORD_LIST * +brace_expand_word_list (tlist, eflags) + WORD_LIST *tlist; + int eflags; +{ + register char **expansions; + char *temp_string; + WORD_LIST *disposables, *output_list, *next; + WORD_DESC *w; + int eindex; + + for (disposables = output_list = (WORD_LIST *)NULL; tlist; tlist = next) + { + next = tlist->next; + + if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG)) + { +/*itrace("brace_expand_word_list: %s: W_COMPASSIGN|W_ASSIGNARG", tlist->word->word);*/ + PREPEND_LIST (tlist, output_list); + continue; + } + + /* Only do brace expansion if the word has a brace character. If + not, just add the word list element to BRACES and continue. In + the common case, at least when running shell scripts, this will + degenerate to a bunch of calls to `mbschr', and then what is + basically a reversal of TLIST into BRACES, which is corrected + by a call to REVERSE_LIST () on BRACES when the end of TLIST + is reached. */ + if (mbschr (tlist->word->word, LBRACE)) + { + expansions = brace_expand (tlist->word->word); + + for (eindex = 0; temp_string = expansions[eindex]; eindex++) + { + w = alloc_word_desc (); + w->word = temp_string; + + /* If brace expansion didn't change the word, preserve + the flags. We may want to preserve the flags + unconditionally someday -- XXX */ + if (STREQ (temp_string, tlist->word->word)) + w->flags = tlist->word->flags; + else + w = make_word_flags (w, temp_string); + + output_list = make_word_list (w, output_list); + } + free (expansions); + + /* Add TLIST to the list of words to be freed after brace + expansion has been performed. */ + PREPEND_LIST (tlist, disposables); + } + else + PREPEND_LIST (tlist, output_list); + } + + if (disposables) + dispose_words (disposables); + + if (output_list) + output_list = REVERSE_LIST (output_list, WORD_LIST *); + + return (output_list); +} +#endif + +#if defined (ARRAY_VARS) +/* Take WORD, a compound associative array assignment, and internally run + 'declare -A w', where W is the variable name portion of WORD. */ +static int +make_internal_declare (word, option) + char *word; + char *option; +{ + int t; + WORD_LIST *wl; + WORD_DESC *w; + + w = make_word (word); + + t = assignment (w->word, 0); + w->word[t] = '\0'; + + wl = make_word_list (w, (WORD_LIST *)NULL); + wl = make_word_list (make_word (option), wl); + + return (declare_builtin (wl)); +} +#endif + +static WORD_LIST * +shell_expand_word_list (tlist, eflags) + WORD_LIST *tlist; + int eflags; +{ + WORD_LIST *expanded, *orig_list, *new_list, *next, *temp_list; + int expanded_something, has_dollar_at; + char *temp_string; + + /* We do tilde expansion all the time. This is what 1003.2 says. */ + new_list = (WORD_LIST *)NULL; + for (orig_list = tlist; tlist; tlist = next) + { + temp_string = tlist->word->word; + + next = tlist->next; + +#if defined (ARRAY_VARS) + /* If this is a compound array assignment to a builtin that accepts + such assignments (e.g., `declare'), take the assignment and perform + it separately, handling the semantics of declarations inside shell + functions. This avoids the double-evaluation of such arguments, + because `declare' does some evaluation of compound assignments on + its own. */ + if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG)) + { + int t; + + if (tlist->word->flags & W_ASSIGNASSOC) + make_internal_declare (tlist->word->word, "-A"); + + t = do_word_assignment (tlist->word, 0); + if (t == 0) + { + last_command_exit_value = EXECUTION_FAILURE; + exp_jump_to_top_level (DISCARD); + } + + /* Now transform the word as ksh93 appears to do and go on */ + t = assignment (tlist->word->word, 0); + tlist->word->word[t] = '\0'; + tlist->word->flags &= ~(W_ASSIGNMENT|W_NOSPLIT|W_COMPASSIGN|W_ASSIGNARG|W_ASSIGNASSOC); + } +#endif + + expanded_something = 0; + expanded = expand_word_internal + (tlist->word, 0, 0, &has_dollar_at, &expanded_something); + + if (expanded == &expand_word_error || expanded == &expand_word_fatal) + { + /* By convention, each time this error is returned, + tlist->word->word has already been freed. */ + tlist->word->word = (char *)NULL; + + /* Dispose our copy of the original list. */ + dispose_words (orig_list); + /* Dispose the new list we're building. */ + dispose_words (new_list); + + last_command_exit_value = EXECUTION_FAILURE; + if (expanded == &expand_word_error) + exp_jump_to_top_level (DISCARD); + else + exp_jump_to_top_level (FORCE_EOF); + } + + /* Don't split words marked W_NOSPLIT. */ + if (expanded_something && (tlist->word->flags & W_NOSPLIT) == 0) + { + temp_list = word_list_split (expanded); + dispose_words (expanded); + } + else + { + /* If no parameter expansion, command substitution, process + substitution, or arithmetic substitution took place, then + do not do word splitting. We still have to remove quoted + null characters from the result. */ + word_list_remove_quoted_nulls (expanded); + temp_list = expanded; + } + + expanded = REVERSE_LIST (temp_list, WORD_LIST *); + new_list = (WORD_LIST *)list_append (expanded, new_list); + } + + if (orig_list) + dispose_words (orig_list); + + if (new_list) + new_list = REVERSE_LIST (new_list, WORD_LIST *); + + return (new_list); +} + +/* The workhorse for expand_words () and expand_words_no_vars (). + First arg is LIST, a WORD_LIST of words. + Second arg EFLAGS is a flags word controlling which expansions are + performed. + + This does all of the substitutions: brace expansion, tilde expansion, + parameter expansion, command substitution, arithmetic expansion, + process substitution, word splitting, and pathname expansion, according + to the bits set in EFLAGS. Words with the W_QUOTED or W_NOSPLIT bits + set, or for which no expansion is done, do not undergo word splitting. + Words with the W_NOGLOB bit set do not undergo pathname expansion. */ +static WORD_LIST * +expand_word_list_internal (list, eflags) + WORD_LIST *list; + int eflags; +{ + WORD_LIST *new_list, *temp_list; + int tint; + + if (list == 0) + return ((WORD_LIST *)NULL); + + garglist = new_list = copy_word_list (list); + if (eflags & WEXP_VARASSIGN) + { + garglist = new_list = separate_out_assignments (new_list); + if (new_list == 0) + { + if (subst_assign_varlist) + { + /* All the words were variable assignments, so they are placed + into the shell's environment. */ + for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next) + { + this_command_name = (char *)NULL; /* no arithmetic errors */ + tint = do_word_assignment (temp_list->word, 0); + /* Variable assignment errors in non-interactive shells + running in Posix.2 mode cause the shell to exit. */ + if (tint == 0) + { + last_command_exit_value = EXECUTION_FAILURE; + if (interactive_shell == 0 && posixly_correct) + exp_jump_to_top_level (FORCE_EOF); + else + exp_jump_to_top_level (DISCARD); + } + } + dispose_words (subst_assign_varlist); + subst_assign_varlist = (WORD_LIST *)NULL; + } + return ((WORD_LIST *)NULL); + } + } + + /* Begin expanding the words that remain. The expansions take place on + things that aren't really variable assignments. */ + +#if defined (BRACE_EXPANSION) + /* Do brace expansion on this word if there are any brace characters + in the string. */ + if ((eflags & WEXP_BRACEEXP) && brace_expansion && new_list) + new_list = brace_expand_word_list (new_list, eflags); +#endif /* BRACE_EXPANSION */ + + /* Perform the `normal' shell expansions: tilde expansion, parameter and + variable substitution, command substitution, arithmetic expansion, + and word splitting. */ + new_list = shell_expand_word_list (new_list, eflags); + + /* Okay, we're almost done. Now let's just do some filename + globbing. */ + if (new_list) + { + if ((eflags & WEXP_PATHEXP) && disallow_filename_globbing == 0) + /* Glob expand the word list unless globbing has been disabled. */ + new_list = glob_expand_word_list (new_list, eflags); + else + /* Dequote the words, because we're not performing globbing. */ + new_list = dequote_list (new_list); + } + + if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist) + { + sh_wassign_func_t *assign_func; + int is_special_builtin, is_builtin_or_func; + + /* If the remainder of the words expand to nothing, Posix.2 requires + that the variable and environment assignments affect the shell's + environment. */ + assign_func = new_list ? assign_in_env : do_word_assignment; + tempenv_assign_error = 0; + + is_builtin_or_func = (new_list && new_list->word && (find_shell_builtin (new_list->word->word) || find_function (new_list->word->word))); + /* Posix says that special builtins exit if a variable assignment error + occurs in an assignment preceding it. */ + is_special_builtin = (posixly_correct && new_list && new_list->word && find_special_builtin (new_list->word->word)); + + for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next) + { + this_command_name = (char *)NULL; + assigning_in_environment = (assign_func == assign_in_env); + tint = (*assign_func) (temp_list->word, is_builtin_or_func); + assigning_in_environment = 0; + /* Variable assignment errors in non-interactive shells running + in Posix.2 mode cause the shell to exit. */ + if (tint == 0) + { + if (assign_func == do_word_assignment) + { + last_command_exit_value = EXECUTION_FAILURE; + if (interactive_shell == 0 && posixly_correct && is_special_builtin) + exp_jump_to_top_level (FORCE_EOF); + else + exp_jump_to_top_level (DISCARD); + } + else + tempenv_assign_error++; + } + } + + dispose_words (subst_assign_varlist); + subst_assign_varlist = (WORD_LIST *)NULL; + } + +#if 0 + tint = list_length (new_list) + 1; + RESIZE_MALLOCED_BUFFER (glob_argv_flags, 0, tint, glob_argv_flags_size, 16); + for (tint = 0, temp_list = new_list; temp_list; temp_list = temp_list->next) + glob_argv_flags[tint++] = (temp_list->word->flags & W_GLOBEXP) ? '1' : '0'; + glob_argv_flags[tint] = '\0'; +#endif + + return (new_list); +} diff --git a/trap.c b/trap.c index 2b02e3e68..c85a56149 100644 --- a/trap.c +++ b/trap.c @@ -1058,7 +1058,7 @@ restore_original_signals () } /* If a trap handler exists for signal SIG, then call it; otherwise just - return failure. */ + return failure. Returns 1 if it called the trap handler. */ int maybe_call_trap_handler (sig) int sig; diff --git a/trap.c~ b/trap.c~ new file mode 100644 index 000000000..2b02e3e68 --- /dev/null +++ b/trap.c~ @@ -0,0 +1,1134 @@ +/* trap.c -- Not the trap command, but useful functions for manipulating + those objects. The trap command is in builtins/trap.def. */ + +/* Copyright (C) 1987-2011 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashtypes.h" +#include "bashansi.h" + +#include +#include + +#include "bashintl.h" + +#include "trap.h" + +#include "shell.h" +#include "flags.h" +#include "input.h" /* for save_token_state, restore_token_state */ +#include "jobs.h" +#include "signames.h" +#include "builtins.h" +#include "builtins/common.h" +#include "builtins/builtext.h" + +#ifndef errno +extern int errno; +#endif + +/* Flags which describe the current handling state of a signal. */ +#define SIG_INHERITED 0x0 /* Value inherited from parent. */ +#define SIG_TRAPPED 0x1 /* Currently trapped. */ +#define SIG_HARD_IGNORE 0x2 /* Signal was ignored on shell entry. */ +#define SIG_SPECIAL 0x4 /* Treat this signal specially. */ +#define SIG_NO_TRAP 0x8 /* Signal cannot be trapped. */ +#define SIG_INPROGRESS 0x10 /* Signal handler currently executing. */ +#define SIG_CHANGED 0x20 /* Trap value changed in trap handler. */ +#define SIG_IGNORED 0x40 /* The signal is currently being ignored. */ + +#define SPECIAL_TRAP(s) ((s) == EXIT_TRAP || (s) == DEBUG_TRAP || (s) == ERROR_TRAP || (s) == RETURN_TRAP) + +/* An array of such flags, one for each signal, describing what the + shell will do with a signal. DEBUG_TRAP == NSIG; some code below + assumes this. */ +static int sigmodes[BASH_NSIG]; + +static void free_trap_command __P((int)); +static void change_signal __P((int, char *)); + +static void get_original_signal __P((int)); + +static int _run_trap_internal __P((int, char *)); + +static void free_trap_string __P((int)); +static void reset_signal __P((int)); +static void restore_signal __P((int)); +static void reset_or_restore_signal_handlers __P((sh_resetsig_func_t *)); + +/* Variables used here but defined in other files. */ +extern int last_command_exit_value; +extern int line_number; + +extern char *this_command_name; +extern sh_builtin_func_t *this_shell_builtin; +extern procenv_t wait_intr_buf; +extern int return_catch_flag, return_catch_value; +extern int subshell_level; +extern WORD_LIST *subst_assign_varlist; + +/* The list of things to do originally, before we started trapping. */ +SigHandler *original_signals[NSIG]; + +/* For each signal, a slot for a string, which is a command to be + executed when that signal is recieved. The slot can also contain + DEFAULT_SIG, which means do whatever you were going to do before + you were so rudely interrupted, or IGNORE_SIG, which says ignore + this signal. */ +char *trap_list[BASH_NSIG]; + +/* A bitmap of signals received for which we have trap handlers. */ +int pending_traps[NSIG]; + +/* Set to the number of the signal we're running the trap for + 1. + Used in execute_cmd.c and builtins/common.c to clean up when + parse_and_execute does not return normally after executing the + trap command (e.g., when `return' is executed in the trap command). */ +int running_trap; + +/* Set to last_command_exit_value before running a trap. */ +int trap_saved_exit_value; + +/* The (trapped) signal received while executing in the `wait' builtin */ +int wait_signal_received; + +#define GETORIGSIG(sig) \ + do { \ + original_signals[sig] = (SigHandler *)set_signal_handler (sig, SIG_DFL); \ + set_signal_handler (sig, original_signals[sig]); \ + if (original_signals[sig] == SIG_IGN) \ + sigmodes[sig] |= SIG_HARD_IGNORE; \ + } while (0) + +#define SETORIGSIG(sig,handler) \ + do { \ + original_signals[sig] = handler; \ + if (original_signals[sig] == SIG_IGN) \ + sigmodes[sig] |= SIG_HARD_IGNORE; \ + } while (0) + +#define GET_ORIGINAL_SIGNAL(sig) \ + if (sig && sig < NSIG && original_signals[sig] == IMPOSSIBLE_TRAP_HANDLER) \ + GETORIGSIG(sig) + +void +initialize_traps () +{ + register int i; + + initialize_signames(); + + trap_list[EXIT_TRAP] = trap_list[DEBUG_TRAP] = trap_list[ERROR_TRAP] = trap_list[RETURN_TRAP] = (char *)NULL; + sigmodes[EXIT_TRAP] = sigmodes[DEBUG_TRAP] = sigmodes[ERROR_TRAP] = sigmodes[RETURN_TRAP] = SIG_INHERITED; + original_signals[EXIT_TRAP] = IMPOSSIBLE_TRAP_HANDLER; + + for (i = 1; i < NSIG; i++) + { + pending_traps[i] = 0; + trap_list[i] = (char *)DEFAULT_SIG; + sigmodes[i] = SIG_INHERITED; /* XXX - only set, not used */ + original_signals[i] = IMPOSSIBLE_TRAP_HANDLER; + } + + /* Show which signals are treated specially by the shell. */ +#if defined (SIGCHLD) + GETORIGSIG (SIGCHLD); + sigmodes[SIGCHLD] |= (SIG_SPECIAL | SIG_NO_TRAP); +#endif /* SIGCHLD */ + + GETORIGSIG (SIGINT); + sigmodes[SIGINT] |= SIG_SPECIAL; + +#if defined (__BEOS__) + /* BeOS sets SIGINT to SIG_IGN! */ + original_signals[SIGINT] = SIG_DFL; + sigmodes[SIGINT] &= ~SIG_HARD_IGNORE; +#endif + + GETORIGSIG (SIGQUIT); + sigmodes[SIGQUIT] |= SIG_SPECIAL; + + if (interactive) + { + GETORIGSIG (SIGTERM); + sigmodes[SIGTERM] |= SIG_SPECIAL; + } +} + +#ifdef DEBUG +/* Return a printable representation of the trap handler for SIG. */ +static char * +trap_handler_string (sig) + int sig; +{ + if (trap_list[sig] == (char *)DEFAULT_SIG) + return "DEFAULT_SIG"; + else if (trap_list[sig] == (char *)IGNORE_SIG) + return "IGNORE_SIG"; + else if (trap_list[sig] == (char *)IMPOSSIBLE_TRAP_HANDLER) + return "IMPOSSIBLE_TRAP_HANDLER"; + else if (trap_list[sig]) + return trap_list[sig]; + else + return "NULL"; +} +#endif + +/* Return the print name of this signal. */ +char * +signal_name (sig) + int sig; +{ + char *ret; + + /* on cygwin32, signal_names[sig] could be null */ + ret = (sig >= BASH_NSIG || sig < 0 || signal_names[sig] == NULL) + ? _("invalid signal number") + : signal_names[sig]; + + return ret; +} + +/* Turn a string into a signal number, or a number into + a signal number. If STRING is "2", "SIGINT", or "INT", + then (int)2 is returned. Return NO_SIG if STRING doesn't + contain a valid signal descriptor. */ +int +decode_signal (string, flags) + char *string; + int flags; +{ + intmax_t sig; + char *name; + + if (legal_number (string, &sig)) + return ((sig >= 0 && sig < NSIG) ? (int)sig : NO_SIG); + + /* A leading `SIG' may be omitted. */ + for (sig = 0; sig < BASH_NSIG; sig++) + { + name = signal_names[sig]; + if (name == 0 || name[0] == '\0') + continue; + + /* Check name without the SIG prefix first case sensitivly or + insensitively depending on whether flags includes DSIG_NOCASE */ + if (STREQN (name, "SIG", 3)) + { + name += 3; + + if ((flags & DSIG_NOCASE) && strcasecmp (string, name) == 0) + return ((int)sig); + else if ((flags & DSIG_NOCASE) == 0 && strcmp (string, name) == 0) + return ((int)sig); + /* If we can't use the `SIG' prefix to match, punt on this + name now. */ + else if ((flags & DSIG_SIGPREFIX) == 0) + continue; + } + + /* Check name with SIG prefix case sensitively or insensitively + depending on whether flags includes DSIG_NOCASE */ + name = signal_names[sig]; + if ((flags & DSIG_NOCASE) && strcasecmp (string, name) == 0) + return ((int)sig); + else if ((flags & DSIG_NOCASE) == 0 && strcmp (string, name) == 0) + return ((int)sig); + } + + return (NO_SIG); +} + +/* Non-zero when we catch a trapped signal. */ +static int catch_flag; + +void +run_pending_traps () +{ + register int sig; + int old_exit_value, *token_state; + WORD_LIST *save_subst_varlist; +#if defined (ARRAY_VARS) + ARRAY *ps; +#endif + + if (catch_flag == 0) /* simple optimization */ + return; + +itrace("run_pending_traps: "); + + catch_flag = 0; + + /* Preserve $? when running trap. */ + old_exit_value = last_command_exit_value; +#if defined (ARRAY_VARS) + ps = save_pipestatus_array (); +#endif + + for (sig = 1; sig < NSIG; sig++) + { + /* XXX this could be made into a counter by using + while (pending_traps[sig]--) instead of the if statement. */ + if (pending_traps[sig]) + { +#if defined (HAVE_POSIX_SIGNALS) + sigset_t set, oset; + + sigemptyset (&set); + sigemptyset (&oset); + + sigaddset (&set, sig); + sigprocmask (SIG_BLOCK, &set, &oset); +#else +# if defined (HAVE_BSD_SIGNALS) + int oldmask = sigblock (sigmask (sig)); +# endif +#endif /* HAVE_POSIX_SIGNALS */ + + if (sig == SIGINT) + { + run_interrupt_trap (); + CLRINTERRUPT; + } +#if defined (JOB_CONTROL) && defined (SIGCHLD) + else if (sig == SIGCHLD && + trap_list[SIGCHLD] != (char *)IMPOSSIBLE_TRAP_HANDLER && + (sigmodes[SIGCHLD] & SIG_INPROGRESS) == 0) + { + run_sigchld_trap (pending_traps[sig]); /* use as counter */ + } +#endif + else if (trap_list[sig] == (char *)DEFAULT_SIG || + trap_list[sig] == (char *)IGNORE_SIG || + trap_list[sig] == (char *)IMPOSSIBLE_TRAP_HANDLER) + { + /* This is possible due to a race condition. Say a bash + process has SIGTERM trapped. A subshell is spawned + using { list; } & and the parent does something and kills + the subshell with SIGTERM. It's possible for the subshell + to set pending_traps[SIGTERM] to 1 before the code in + execute_cmd.c eventually calls restore_original_signals + to reset the SIGTERM signal handler in the subshell. The + next time run_pending_traps is called, pending_traps[SIGTERM] + will be 1, but the trap handler in trap_list[SIGTERM] will + be invalid (probably DEFAULT_SIG, but it could be IGNORE_SIG). + Unless we catch this, the subshell will dump core when + trap_list[SIGTERM] == DEFAULT_SIG, because DEFAULT_SIG is + usually 0x0. */ + internal_warning (_("run_pending_traps: bad value in trap_list[%d]: %p"), + sig, trap_list[sig]); + if (trap_list[sig] == (char *)DEFAULT_SIG) + { + internal_warning (_("run_pending_traps: signal handler is SIG_DFL, resending %d (%s) to myself"), sig, signal_name (sig)); + kill (getpid (), sig); + } + } + else + { + token_state = save_token_state (); + save_subst_varlist = subst_assign_varlist; + subst_assign_varlist = 0; + + parse_and_execute (savestring (trap_list[sig]), "trap", SEVAL_NONINT|SEVAL_NOHIST|SEVAL_RESETLINE); + restore_token_state (token_state); + free (token_state); + + subst_assign_varlist = save_subst_varlist; + } + + pending_traps[sig] = 0; + +#if defined (HAVE_POSIX_SIGNALS) + sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); +#else +# if defined (HAVE_BSD_SIGNALS) + sigsetmask (oldmask); +# endif +#endif /* POSIX_VERSION */ + } + } + +#if defined (ARRAY_VARS) + restore_pipestatus_array (ps); +#endif + last_command_exit_value = old_exit_value; +} + +sighandler +trap_handler (sig) + int sig; +{ + int oerrno; + + if ((sigmodes[sig] & SIG_TRAPPED) == 0) + { +#if defined (DEBUG) + internal_warning ("trap_handler: signal %d: signal not trapped", sig); +#endif + SIGRETURN (0); + } + +itrace("trap_handler: sig = %d", sig); + + if ((sig >= NSIG) || + (trap_list[sig] == (char *)DEFAULT_SIG) || + (trap_list[sig] == (char *)IGNORE_SIG)) + programming_error (_("trap_handler: bad signal %d"), sig); + else + { + oerrno = errno; +#if defined (MUST_REINSTALL_SIGHANDLERS) +# if defined (JOB_CONTROL) && defined (SIGCHLD) + if (sig != SIGCHLD) +# endif /* JOB_CONTROL && SIGCHLD */ + set_signal_handler (sig, trap_handler); +#endif /* MUST_REINSTALL_SIGHANDLERS */ + + catch_flag = 1; + pending_traps[sig]++; + + if (interrupt_immediately && this_shell_builtin && (this_shell_builtin == wait_builtin)) + { + wait_signal_received = sig; + longjmp (wait_intr_buf, 1); + } + + if (interrupt_immediately) + run_pending_traps (); + + errno = oerrno; + } + + SIGRETURN (0); +} + +#if defined (JOB_CONTROL) && defined (SIGCHLD) + +#ifdef INCLUDE_UNUSED +/* Make COMMAND_STRING be executed when SIGCHLD is caught. */ +void +set_sigchld_trap (command_string) + char *command_string; +{ + set_signal (SIGCHLD, command_string); +} +#endif + +/* Make COMMAND_STRING be executed when SIGCHLD is caught iff SIGCHLD + is not already trapped. IMPOSSIBLE_TRAP_HANDLER is used as a sentinel + to make sure that a SIGCHLD trap handler run via run_sigchld_trap can + reset the disposition to the default and not have the original signal + accidentally restored, undoing the user's command. */ +void +maybe_set_sigchld_trap (command_string) + char *command_string; +{ + if ((sigmodes[SIGCHLD] & SIG_TRAPPED) == 0 && trap_list[SIGCHLD] == (char *)IMPOSSIBLE_TRAP_HANDLER) + set_signal (SIGCHLD, command_string); +} + +/* Temporarily set the SIGCHLD trap string to IMPOSSIBLE_TRAP_HANDLER. Used + as a sentinel in run_sigchld_trap and maybe_set_sigchld_trap to see whether + or not a SIGCHLD trap handler reset SIGCHLD disposition to the default. */ +void +set_impossible_sigchld_trap () +{ + restore_default_signal (SIGCHLD); + change_signal (SIGCHLD, (char *)IMPOSSIBLE_TRAP_HANDLER); + sigmodes[SIGCHLD] &= ~SIG_TRAPPED; /* maybe_set_sigchld_trap checks this */ +} +#endif /* JOB_CONTROL && SIGCHLD */ + +void +set_debug_trap (command) + char *command; +{ + set_signal (DEBUG_TRAP, command); +} + +void +set_error_trap (command) + char *command; +{ + set_signal (ERROR_TRAP, command); +} + +void +set_return_trap (command) + char *command; +{ + set_signal (RETURN_TRAP, command); +} + +#ifdef INCLUDE_UNUSED +void +set_sigint_trap (command) + char *command; +{ + set_signal (SIGINT, command); +} +#endif + +/* Reset the SIGINT handler so that subshells that are doing `shellsy' + things, like waiting for command substitution or executing commands + in explicit subshells ( ( cmd ) ), can catch interrupts properly. */ +SigHandler * +set_sigint_handler () +{ + if (sigmodes[SIGINT] & SIG_HARD_IGNORE) + return ((SigHandler *)SIG_IGN); + + else if (sigmodes[SIGINT] & SIG_IGNORED) + return ((SigHandler *)set_signal_handler (SIGINT, SIG_IGN)); /* XXX */ + + else if (sigmodes[SIGINT] & SIG_TRAPPED) + return ((SigHandler *)set_signal_handler (SIGINT, trap_handler)); + + /* The signal is not trapped, so set the handler to the shell's special + interrupt handler. */ + else if (interactive) /* XXX - was interactive_shell */ + return (set_signal_handler (SIGINT, sigint_sighandler)); + else + return (set_signal_handler (SIGINT, termsig_sighandler)); +} + +/* Return the correct handler for signal SIG according to the values in + sigmodes[SIG]. */ +SigHandler * +trap_to_sighandler (sig) + int sig; +{ + if (sigmodes[sig] & (SIG_IGNORED|SIG_HARD_IGNORE)) + return (SIG_IGN); + else if (sigmodes[sig] & SIG_TRAPPED) + return (trap_handler); + else + return (SIG_DFL); +} + +/* Set SIG to call STRING as a command. */ +void +set_signal (sig, string) + int sig; + char *string; +{ + sigset_t set, oset; + + if (SPECIAL_TRAP (sig)) + { + change_signal (sig, savestring (string)); + if (sig == EXIT_TRAP && interactive == 0) + initialize_terminating_signals (); + return; + } + + /* A signal ignored on entry to the shell cannot be trapped or reset, but + no error is reported when attempting to do so. -- Posix.2 */ + if (sigmodes[sig] & SIG_HARD_IGNORE) + return; + + /* Make sure we have original_signals[sig] if the signal has not yet + been trapped. */ + if ((sigmodes[sig] & SIG_TRAPPED) == 0) + { + /* If we aren't sure of the original value, check it. */ + if (original_signals[sig] == IMPOSSIBLE_TRAP_HANDLER) + GETORIGSIG (sig); + if (original_signals[sig] == SIG_IGN) + return; + } + + /* Only change the system signal handler if SIG_NO_TRAP is not set. + The trap command string is changed in either case. The shell signal + handlers for SIGINT and SIGCHLD run the user specified traps in an + environment in which it is safe to do so. */ + if ((sigmodes[sig] & SIG_NO_TRAP) == 0) + { + BLOCK_SIGNAL (sig, set, oset); + change_signal (sig, savestring (string)); + set_signal_handler (sig, trap_handler); + UNBLOCK_SIGNAL (oset); + } + else + change_signal (sig, savestring (string)); +} + +static void +free_trap_command (sig) + int sig; +{ + if ((sigmodes[sig] & SIG_TRAPPED) && trap_list[sig] && + (trap_list[sig] != (char *)IGNORE_SIG) && + (trap_list[sig] != (char *)DEFAULT_SIG) && + (trap_list[sig] != (char *)IMPOSSIBLE_TRAP_HANDLER)) + free (trap_list[sig]); +} + +/* If SIG has a string assigned to it, get rid of it. Then give it + VALUE. */ +static void +change_signal (sig, value) + int sig; + char *value; +{ + if ((sigmodes[sig] & SIG_INPROGRESS) == 0) + free_trap_command (sig); + trap_list[sig] = value; + + sigmodes[sig] |= SIG_TRAPPED; + if (value == (char *)IGNORE_SIG) + sigmodes[sig] |= SIG_IGNORED; + else + sigmodes[sig] &= ~SIG_IGNORED; + if (sigmodes[sig] & SIG_INPROGRESS) + sigmodes[sig] |= SIG_CHANGED; +} + +static void +get_original_signal (sig) + int sig; +{ + /* If we aren't sure the of the original value, then get it. */ + if (sig > 0 && sig < NSIG && original_signals[sig] == (SigHandler *)IMPOSSIBLE_TRAP_HANDLER) + GETORIGSIG (sig); +} + +void +get_all_original_signals () +{ + register int i; + + for (i = 1; i < NSIG; i++) + GET_ORIGINAL_SIGNAL (i); +} + +void +set_original_signal (sig, handler) + int sig; + SigHandler *handler; +{ + if (sig > 0 && sig < NSIG && original_signals[sig] == (SigHandler *)IMPOSSIBLE_TRAP_HANDLER) + SETORIGSIG (sig, handler); +} + +/* Restore the default action for SIG; i.e., the action the shell + would have taken before you used the trap command. This is called + from trap_builtin (), which takes care to restore the handlers for + the signals the shell treats specially. */ +void +restore_default_signal (sig) + int sig; +{ + if (SPECIAL_TRAP (sig)) + { + if ((sig != DEBUG_TRAP && sig != ERROR_TRAP && sig != RETURN_TRAP) || + (sigmodes[sig] & SIG_INPROGRESS) == 0) + free_trap_command (sig); + trap_list[sig] = (char *)NULL; + sigmodes[sig] &= ~SIG_TRAPPED; + if (sigmodes[sig] & SIG_INPROGRESS) + sigmodes[sig] |= SIG_CHANGED; + return; + } + + GET_ORIGINAL_SIGNAL (sig); + + /* A signal ignored on entry to the shell cannot be trapped or reset, but + no error is reported when attempting to do so. Thanks Posix.2. */ + if (sigmodes[sig] & SIG_HARD_IGNORE) + return; + + /* If we aren't trapping this signal, don't bother doing anything else. */ + if ((sigmodes[sig] & SIG_TRAPPED) == 0) + return; + + /* Only change the signal handler for SIG if it allows it. */ + if ((sigmodes[sig] & SIG_NO_TRAP) == 0) + set_signal_handler (sig, original_signals[sig]); + + /* Change the trap command in either case. */ + change_signal (sig, (char *)DEFAULT_SIG); + + /* Mark the signal as no longer trapped. */ + sigmodes[sig] &= ~SIG_TRAPPED; +} + +/* Make this signal be ignored. */ +void +ignore_signal (sig) + int sig; +{ + if (SPECIAL_TRAP (sig) && ((sigmodes[sig] & SIG_IGNORED) == 0)) + { + change_signal (sig, (char *)IGNORE_SIG); + return; + } + + GET_ORIGINAL_SIGNAL (sig); + + /* A signal ignored on entry to the shell cannot be trapped or reset. + No error is reported when the user attempts to do so. */ + if (sigmodes[sig] & SIG_HARD_IGNORE) + return; + + /* If already trapped and ignored, no change necessary. */ + if (sigmodes[sig] & SIG_IGNORED) + return; + + /* Only change the signal handler for SIG if it allows it. */ + if ((sigmodes[sig] & SIG_NO_TRAP) == 0) + set_signal_handler (sig, SIG_IGN); + + /* Change the trap command in either case. */ + change_signal (sig, (char *)IGNORE_SIG); +} + +/* Handle the calling of "trap 0". The only sticky situation is when + the command to be executed includes an "exit". This is why we have + to provide our own place for top_level to jump to. */ +int +run_exit_trap () +{ + char *trap_command; + int code, function_code, retval; +#if defined (ARRAY_VARS) + ARRAY *ps; +#endif + + trap_saved_exit_value = last_command_exit_value; +#if defined (ARRAY_VARS) + ps = save_pipestatus_array (); +#endif + function_code = 0; + + /* Run the trap only if signal 0 is trapped and not ignored, and we are not + currently running in the trap handler (call to exit in the list of + commands given to trap 0). */ + if ((sigmodes[EXIT_TRAP] & SIG_TRAPPED) && + (sigmodes[EXIT_TRAP] & (SIG_IGNORED|SIG_INPROGRESS)) == 0) + { + trap_command = savestring (trap_list[EXIT_TRAP]); + sigmodes[EXIT_TRAP] &= ~SIG_TRAPPED; + sigmodes[EXIT_TRAP] |= SIG_INPROGRESS; + + retval = trap_saved_exit_value; + running_trap = 1; + + code = setjmp (top_level); + + /* If we're in a function, make sure return longjmps come here, too. */ + if (return_catch_flag) + function_code = setjmp (return_catch); + + if (code == 0 && function_code == 0) + { + reset_parser (); + parse_and_execute (trap_command, "exit trap", SEVAL_NONINT|SEVAL_NOHIST|SEVAL_RESETLINE); + } + else if (code == ERREXIT) + retval = last_command_exit_value; + else if (code == EXITPROG) + retval = last_command_exit_value; + else if (function_code != 0) + retval = return_catch_value; + else + retval = trap_saved_exit_value; + + running_trap = 0; + return retval; + } + +#if defined (ARRAY_VARS) + restore_pipestatus_array (ps); +#endif + return (trap_saved_exit_value); +} + +void +run_trap_cleanup (sig) + int sig; +{ + sigmodes[sig] &= ~(SIG_INPROGRESS|SIG_CHANGED); +} + +/* Run a trap command for SIG. SIG is one of the signals the shell treats + specially. Returns the exit status of the executed trap command list. */ +static int +_run_trap_internal (sig, tag) + int sig; + char *tag; +{ + char *trap_command, *old_trap; + int trap_exit_value, *token_state; + int save_return_catch_flag, function_code, flags; + procenv_t save_return_catch; + WORD_LIST *save_subst_varlist; +#if defined (ARRAY_VARS) + ARRAY *ps; +#endif + + trap_exit_value = function_code = 0; + /* Run the trap only if SIG is trapped and not ignored, and we are not + currently executing in the trap handler. */ + if ((sigmodes[sig] & SIG_TRAPPED) && ((sigmodes[sig] & SIG_IGNORED) == 0) && + (trap_list[sig] != (char *)IMPOSSIBLE_TRAP_HANDLER) && + ((sigmodes[sig] & SIG_INPROGRESS) == 0)) + { + old_trap = trap_list[sig]; + sigmodes[sig] |= SIG_INPROGRESS; + sigmodes[sig] &= ~SIG_CHANGED; /* just to be sure */ + trap_command = savestring (old_trap); + + running_trap = sig + 1; + trap_saved_exit_value = last_command_exit_value; +#if defined (ARRAY_VARS) + ps = save_pipestatus_array (); +#endif + + token_state = save_token_state (); + save_subst_varlist = subst_assign_varlist; + subst_assign_varlist = 0; + + /* If we're in a function, make sure return longjmps come here, too. */ + save_return_catch_flag = return_catch_flag; + if (return_catch_flag) + { + COPY_PROCENV (return_catch, save_return_catch); + function_code = setjmp (return_catch); + } + + flags = SEVAL_NONINT|SEVAL_NOHIST; + if (sig != DEBUG_TRAP && sig != RETURN_TRAP && sig != ERROR_TRAP) + flags |= SEVAL_RESETLINE; + if (function_code == 0) + parse_and_execute (trap_command, tag, flags); + + restore_token_state (token_state); + free (token_state); + + subst_assign_varlist = save_subst_varlist; + + trap_exit_value = last_command_exit_value; + last_command_exit_value = trap_saved_exit_value; +#if defined (ARRAY_VARS) + restore_pipestatus_array (ps); +#endif + running_trap = 0; + + sigmodes[sig] &= ~SIG_INPROGRESS; + + if (sigmodes[sig] & SIG_CHANGED) + { +#if 0 + /* Special traps like EXIT, DEBUG, RETURN are handled explicitly in + the places where they can be changed using unwind-protects. For + example, look at execute_cmd.c:execute_function(). */ + if (SPECIAL_TRAP (sig) == 0) +#endif + free (old_trap); + sigmodes[sig] &= ~SIG_CHANGED; + } + + if (save_return_catch_flag) + { + return_catch_flag = save_return_catch_flag; + return_catch_value = trap_exit_value; + COPY_PROCENV (save_return_catch, return_catch); + if (function_code) + longjmp (return_catch, 1); + } + } + + return trap_exit_value; +} + +int +run_debug_trap () +{ + int trap_exit_value; + pid_t save_pgrp; + int save_pipe[2]; + + /* XXX - question: should the DEBUG trap inherit the RETURN trap? */ + trap_exit_value = 0; + if ((sigmodes[DEBUG_TRAP] & SIG_TRAPPED) && ((sigmodes[DEBUG_TRAP] & SIG_IGNORED) == 0) && ((sigmodes[DEBUG_TRAP] & SIG_INPROGRESS) == 0)) + { +#if defined (JOB_CONTROL) + save_pgrp = pipeline_pgrp; + pipeline_pgrp = 0; + save_pipeline (1); +# if defined (PGRP_PIPE) + save_pgrp_pipe (save_pipe, 1); +# endif + stop_making_children (); +#endif + + trap_exit_value = _run_trap_internal (DEBUG_TRAP, "debug trap"); + +#if defined (JOB_CONTROL) + pipeline_pgrp = save_pgrp; + restore_pipeline (1); +# if defined (PGRP_PIPE) + close_pgrp_pipe (); + restore_pgrp_pipe (save_pipe); +# endif + if (pipeline_pgrp > 0) + give_terminal_to (pipeline_pgrp, 1); + notify_and_cleanup (); +#endif + +#if defined (DEBUGGER) + /* If we're in the debugger and the DEBUG trap returns 2 while we're in + a function or sourced script, we force a `return'. */ + if (debugging_mode && trap_exit_value == 2 && return_catch_flag) + { + return_catch_value = trap_exit_value; + longjmp (return_catch, 1); + } +#endif + } + return trap_exit_value; +} + +void +run_error_trap () +{ + if ((sigmodes[ERROR_TRAP] & SIG_TRAPPED) && ((sigmodes[ERROR_TRAP] & SIG_IGNORED) == 0) && (sigmodes[ERROR_TRAP] & SIG_INPROGRESS) == 0) + _run_trap_internal (ERROR_TRAP, "error trap"); +} + +void +run_return_trap () +{ + int old_exit_value; + +#if 0 + if ((sigmodes[DEBUG_TRAP] & SIG_TRAPPED) && (sigmodes[DEBUG_TRAP] & SIG_INPROGRESS)) + return; +#endif + + if ((sigmodes[RETURN_TRAP] & SIG_TRAPPED) && ((sigmodes[RETURN_TRAP] & SIG_IGNORED) == 0) && (sigmodes[RETURN_TRAP] & SIG_INPROGRESS) == 0) + { + old_exit_value = last_command_exit_value; + _run_trap_internal (RETURN_TRAP, "return trap"); + last_command_exit_value = old_exit_value; + } +} + +/* Run a trap set on SIGINT. This is called from throw_to_top_level (), and + declared here to localize the trap functions. */ +void +run_interrupt_trap () +{ + _run_trap_internal (SIGINT, "interrupt trap"); +} + +/* Free all the allocated strings in the list of traps and reset the trap + values to the default. Intended to be called from subshells that want + to complete work done by reset_signal_handlers upon execution of a + subsequent `trap' command that changes a signal's disposition. We need + to make sure that we duplicate the behavior of + reset_or_restore_signal_handlers and not change the disposition of signals + that are set to be ignored. */ +void +free_trap_strings () +{ + register int i; + + for (i = 0; i < BASH_NSIG; i++) + { + if (trap_list[i] != (char *)IGNORE_SIG) + free_trap_string (i); + } + trap_list[DEBUG_TRAP] = trap_list[EXIT_TRAP] = trap_list[ERROR_TRAP] = trap_list[RETURN_TRAP] = (char *)NULL; +} + +/* Free a trap command string associated with SIG without changing signal + disposition. Intended to be called from free_trap_strings() */ +static void +free_trap_string (sig) + int sig; +{ + change_signal (sig, (char *)DEFAULT_SIG); + sigmodes[sig] &= ~SIG_TRAPPED; +} + +/* Reset the handler for SIG to the original value but leave the trap string + in place. */ +static void +reset_signal (sig) + int sig; +{ + set_signal_handler (sig, original_signals[sig]); + sigmodes[sig] &= ~SIG_TRAPPED; +} + +/* Set the handler signal SIG to the original and free any trap + command associated with it. */ +static void +restore_signal (sig) + int sig; +{ + set_signal_handler (sig, original_signals[sig]); + change_signal (sig, (char *)DEFAULT_SIG); + sigmodes[sig] &= ~SIG_TRAPPED; +} + +static void +reset_or_restore_signal_handlers (reset) + sh_resetsig_func_t *reset; +{ + register int i; + + /* Take care of the exit trap first */ + if (sigmodes[EXIT_TRAP] & SIG_TRAPPED) + { + sigmodes[EXIT_TRAP] &= ~SIG_TRAPPED; + if (reset != reset_signal) + { + free_trap_command (EXIT_TRAP); + trap_list[EXIT_TRAP] = (char *)NULL; + } + } + + for (i = 1; i < NSIG; i++) + { + if (sigmodes[i] & SIG_TRAPPED) + { + if (trap_list[i] == (char *)IGNORE_SIG) + set_signal_handler (i, SIG_IGN); + else + (*reset) (i); + } + else if (sigmodes[i] & SIG_SPECIAL) + (*reset) (i); + } + + /* Command substitution and other child processes don't inherit the + debug, error, or return traps. If we're in the debugger, and the + `functrace' or `errtrace' options have been set, then let command + substitutions inherit them. Let command substitution inherit the + RETURN trap if we're in the debugger and tracing functions. */ + if (function_trace_mode == 0) + { + sigmodes[DEBUG_TRAP] &= ~SIG_TRAPPED; + sigmodes[RETURN_TRAP] &= ~SIG_TRAPPED; + } + if (error_trace_mode == 0) + sigmodes[ERROR_TRAP] &= ~SIG_TRAPPED; +} + +/* Reset trapped signals to their original values, but don't free the + trap strings. Called by the command substitution code and other places + that create a "subshell environment". */ +void +reset_signal_handlers () +{ + reset_or_restore_signal_handlers (reset_signal); +} + +/* Reset all trapped signals to their original values. Signals set to be + ignored with trap '' SIGNAL should be ignored, so we make sure that they + are. Called by child processes after they are forked. */ +void +restore_original_signals () +{ + reset_or_restore_signal_handlers (restore_signal); +} + +/* If a trap handler exists for signal SIG, then call it; otherwise just + return failure. */ +int +maybe_call_trap_handler (sig) + int sig; +{ + /* Call the trap handler for SIG if the signal is trapped and not ignored. */ + if ((sigmodes[sig] & SIG_TRAPPED) && ((sigmodes[sig] & SIG_IGNORED) == 0)) + { + switch (sig) + { + case SIGINT: + run_interrupt_trap (); + break; + case EXIT_TRAP: + run_exit_trap (); + break; + case DEBUG_TRAP: + run_debug_trap (); + break; + case ERROR_TRAP: + run_error_trap (); + break; + default: + trap_handler (sig); + break; + } + return (1); + } + else + return (0); +} + +int +signal_is_trapped (sig) + int sig; +{ + return (sigmodes[sig] & SIG_TRAPPED); +} + +int +signal_is_special (sig) + int sig; +{ + return (sigmodes[sig] & SIG_SPECIAL); +} + +int +signal_is_ignored (sig) + int sig; +{ + return (sigmodes[sig] & SIG_IGNORED); +} + +int +signal_is_hard_ignored (sig) + int sig; +{ + return (sigmodes[sig] & SIG_HARD_IGNORE); +} + +void +set_signal_ignored (sig) + int sig; +{ + sigmodes[sig] |= SIG_HARD_IGNORE; + original_signals[sig] = SIG_IGN; +} + +int +signal_in_progress (sig) + int sig; +{ + return (sigmodes[sig] & SIG_INPROGRESS); +} diff --git a/variables.c b/variables.c index 85f7bb4f2..652d6ce47 100644 --- a/variables.c +++ b/variables.c @@ -4071,7 +4071,7 @@ push_dollar_vars () { dollar_arg_stack = (WORD_LIST **) xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10) - * sizeof (WORD_LIST **)); + * sizeof (WORD_LIST *)); } dollar_arg_stack[dollar_arg_stack_index++] = list_rest_of_args (); dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; diff --git a/variables.c~ b/variables.c~ index 98d751645..85f7bb4f2 100644 --- a/variables.c~ +++ b/variables.c~ @@ -4650,7 +4650,6 @@ void sv_strict_posix (name) char *name; { -itrace("sv_strict_posix: entry"); SET_INT_VAR (name, posixly_correct); posix_initialize (posixly_correct); #if defined (READLINE) @@ -4658,7 +4657,6 @@ itrace("sv_strict_posix: entry"); posix_readline_initialize (posixly_correct); #endif /* READLINE */ set_shellopts (); /* make sure `posix' is/is not in $SHELLOPTS */ -itrace("sv_strict_posix: return"); } void