From 68dfe178e929a589393f63a3778d4e5d337c1e32 Mon Sep 17 00:00:00 2001 From: Chet Ramey Date: Mon, 12 Dec 2011 21:53:04 -0500 Subject: [PATCH] commit bash-20100121 snapshot --- CWRU/CWRU.chlog | 85 + CWRU/CWRU.chlog~ | 111 + arrayfunc.c | 30 +- arrayfunc.c~ | 1026 +++++ arrayfunc.h | 4 +- arrayfunc.h~ | 64 + autom4te.cache/output.0 | 25 +- autom4te.cache/traces.0 | 4 +- bashline.c | 5 +- bashline.c~ | 3663 ++++++++++++++++ builtins/setattr.def | 4 +- builtins/setattr.def~ | 514 +++ builtins/ulimit.def | 4 + builtins/ulimit.def~ | 776 ++++ config.h.in | 3 + config.h.in~ | 1110 +++++ configure | 25 +- configure.in | 6 +- configure.in~ | 1156 +++++ doc/bash.1 | 3 +- doc/bashref.texi | 3 +- expr.c | 156 +- expr.c~ | 1446 +++++++ jobs.c | 4 + jobs.c~ | 4240 ++++++++++++++++++ lib/sh/oslib.c | 7 +- lib/sh/oslib.c~ | 300 ++ print_cmd.c | 5 + print_cmd.c~ | 1534 +++++++ sig.c | 2 + sig.c~ | 664 +++ subst.c | 14 +- subst.c~ | 9072 +++++++++++++++++++++++++++++++++++++++ tests/RUN-ONE-TEST | 2 +- tests/RUN-ONE-TEST~ | 9 + variables.c | 1 - variables.c~ | 4686 ++++++++++++++++++++ 37 files changed, 30685 insertions(+), 78 deletions(-) create mode 100644 arrayfunc.c~ create mode 100644 arrayfunc.h~ create mode 100644 bashline.c~ create mode 100644 builtins/setattr.def~ create mode 100644 builtins/ulimit.def~ create mode 100644 config.h.in~ create mode 100644 configure.in~ create mode 100644 expr.c~ create mode 100644 jobs.c~ create mode 100644 lib/sh/oslib.c~ create mode 100644 print_cmd.c~ create mode 100644 sig.c~ create mode 100644 subst.c~ create mode 100755 tests/RUN-ONE-TEST~ create mode 100644 variables.c~ diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index 581bab0f2..f015b4dd5 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -9372,3 +9372,88 @@ doc/{bash.1,{bashref,version}.texi},lib/readine/doc/rluser.texi - some typo fixes from Aharon Robbins - added descriptions of ENV, COPROC, and MAPFILE variables - added descriptions of READLINE_LINE and READLINE_POINT + + 1/21 + ---- +arrayfunc.c + - free `akey', the word-expanded index into the assoc array to avoid + mem leak in array_value_internal + - free index into assoc array in unbind_array_element + - change array_value_internal to take an additional argument: an + arrayind_t *. If not null, an index to an indexed array is + returned there. If not an indexed array or subscript is @ or + *, the value is unchanged + + 1/22 + ---- +builtins/ulimit.def + - include if we found it during configure and we don't + have resources. Fixes omission reported by Joachim Schmitz + + +{configure,config.h}.in + - check for , define HAVE_ULIMIT_H if found + +lib/sh/oslib.c + - include for extern declaration for kill(2) if + HAVE_KILLPG not defined + +jobs.c + - if HAVE_KILLPG is not defined, add an extern prototype decl for + killpg() + + 1/24 + ---- +print_cmd.c + - when printing here-string redirections, don't quote the string. The + original quotes, if any, are still in place and don't need to be + requoted. Fixes bug reported by Arfrever Frehtes Taifersar Arahesis + + +subst.c + - fix array_length_reference to return 0 for variables that have not + been assigned a value. Fixes bug reported by Mart Frauenlab + , but is not backwards compatible + +arrayfunc.[ch] + - change array_value to take a new arrayind_t *indp parameter like + get_array_value; changed extern prototype declaration + +subst.c + - changed callers of array_value to add extra parameter + +expr.c + - change expr_streval to set a new `lvalue' parameter with information + about the string being evaluated: string, value, array index (if + any), variable evaluated (if set). + - saving and restoring current context now saves and restores the + current `lvalue' + - new function expr_bind_array_element, binds an array element with an + already-computed index to a specified value + - anywhere we set the current token to a string (STR), save and set + the current lvalue + - change calls to expr_bind_variable to check whether or not the + current lvalue indicates an indexed array was evaluated, and, if so, + call expr_bind_array_element using the already-computed index + (curlval.ind). Fixes problems with dynamic variables (e.g., RANDOM) + in array indices with {pre,post}-{inc,dec}rement and op= + operators reported by + + 1/25 + ---- +expr.c + - fix subexpr() to initialize curlval and lastlval when resetting all + of the rest of the expression-parsing variables + + 1/26 + ---- +builtins/setattr.def + - in show_var_attributes, if the variable is not set (value == 0), + don't print `name=""', just print `name'. Pointed out by + Mart Frauenlab + +arrayfunc.c + - fix array_keys to return NULL if the variable is not set or + invisible. Pointed out by Mart Frauenlab + - change array_value_internal to return NULL for variable which has + not been set diff --git a/CWRU/CWRU.chlog~ b/CWRU/CWRU.chlog~ index bf53bb629..6230712d4 100644 --- a/CWRU/CWRU.chlog~ +++ b/CWRU/CWRU.chlog~ @@ -9325,6 +9325,10 @@ doc/bash.1 [bash-4.1 frozen] + 12/31 + ----- +[bash-4.1 released] + 1/5/2010 -------- doc/bashref.texi @@ -9344,3 +9348,110 @@ lib/readline/complete.c builtins/printf.def - fix prototype in extern declaration for vsnprintf. Fix for bug reported by Yann Rouillard + + 1/9 + --- +parse.y + - fix shell_getc to handle alias expansions containing quoted + newlines. Problems in bash-4.1 with aliases containing quoted + newlines in the middle of and at the end of their expansion. + Fix for bug reported by Jonathan Claggett + + - change mk_alexpansion to not append a space to an alias + expansion ending with a newline. Works with shell_getc + + 1/11 + ---- +lib/glob/Makefile.in + - add dependencies on shell.h and pathnames.h. From Mike Frysinger + + + 1/15 + ---- +doc/{bash.1,{bashref,version}.texi},lib/readine/doc/rluser.texi + - some typo fixes from Aharon Robbins + - added descriptions of ENV, COPROC, and MAPFILE variables + - added descriptions of READLINE_LINE and READLINE_POINT + + 1/21 + ---- +arrayfunc.c + - free `akey', the word-expanded index into the assoc array to avoid + mem leak in array_value_internal + - free index into assoc array in unbind_array_element + - change array_value_internal to take an additional argument: an + arrayind_t *. If not null, an index to an indexed array is + returned there. If not an indexed array or subscript is @ or + *, the value is unchanged + + 1/22 + ---- +builtins/ulimit.def + - include if we found it during configure and we don't + have resources. Fixes omission reported by Joachim Schmitz + + +{configure,config.h}.in + - check for , define HAVE_ULIMIT_H if found + +lib/sh/oslib.c + - include for extern declaration for kill(2) if + HAVE_KILLPG not defined + +jobs.c + - if HAVE_KILLPG is not defined, add an extern prototype decl for + killpg() + + 1/24 + ---- +print_cmd.c + - when printing here-string redirections, don't quote the string. The + original quotes, if any, are still in place and don't need to be + requoted. Fixes bug reported by Arfrever Frehtes Taifersar Arahesis + + +subst.c + - fix array_length_reference to return 0 for variables that have not + been assigned a value. Fixes bug reported by Mart Frauenlab + , but is not backwards compatible + +arrayfunc.[ch] + - change array_value to take a new arrayind_t *indp parameter like + get_array_value; changed extern prototype declaration + +subst.c + - changed callers of array_value to add extra parameter + +expr.c + - change expr_streval to set a new `lvalue' parameter with information + about the string being evaluated: string, value, array index (if + any), variable evaluated (if set). + - saving and restoring current context now saves and restores the + current `lvalue' + - new function expr_bind_array_element, binds an array element with an + already-computed index to a specified value + - anywhere we set the current token to a string (STR), save and set + the current lvalue + - change calls to expr_bind_variable to check whether or not the + current lvalue indicates an indexed array was evaluated, and, if so, + call expr_bind_array_element using the already-computed index + (curlval.ind). Fixes problems with dynamic variables (e.g., RANDOM) + in array indices with {pre,post}-{inc,dec}rement and op= + operators reported by + + 1/25 + ---- +expr.c + - fix subexpr() to initialize curlval and lastlval when resetting all + of the rest of the expression-parsing variables + + 1/26 + ---- +builtins/setattr.def + - in show_var_attributes, if the variable is not set (value == 0), + don't print `name=""', just print `name'. Pointed out by + Mart Frauenlab + +arrayfunc.c + - fix array_keys to return NULL if the variable is not set or + invisible. Pointed out by Mart Frauenlab diff --git a/arrayfunc.c b/arrayfunc.c index 218faf3dd..ad5b2bdd7 100644 --- a/arrayfunc.c +++ b/arrayfunc.c @@ -44,7 +44,7 @@ static SHELL_VAR *bind_array_var_internal __P((SHELL_VAR *, arrayind_t, char *, 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 *)); +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"); @@ -658,6 +658,7 @@ unbind_array_element (var, sub) return -1; } assoc_remove (assoc_cell (var), akey); + free (akey); } else { @@ -848,9 +849,10 @@ array_variable_part (s, subp, lenp) 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, allow_all, rtype) +array_value_internal (s, quoted, allow_all, rtype, indp) char *s; int quoted, allow_all, *rtype; + arrayind_t *indp; { int len; arrayind_t ind; @@ -881,7 +883,7 @@ array_value_internal (s, quoted, allow_all, rtype) err_badarraysub (s); return ((char *)NULL); } - else if (var == 0 || value_cell (var) == 0) + 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); @@ -929,6 +931,8 @@ index_error: } return ((char *)NULL); } + if (indp) + *indp = ind; } else if (assoc_p (var)) { @@ -939,12 +943,15 @@ index_error: goto index_error; } - if (var == 0) + if (var == 0 || value_cell (var) == 0) /* XXX - check invisible_p(var) ? */ 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); + { + retval = assoc_reference (assoc_cell (var), akey); + free (akey); + } else retval = array_reference (array_cell (var), ind); } @@ -955,11 +962,12 @@ index_error: /* 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, rtype) +array_value (s, quoted, rtype, indp) char *s; int quoted, *rtype; + arrayind_t *indp; { - return (array_value_internal (s, quoted, 1, rtype)); + return (array_value_internal (s, quoted, 1, rtype, indp)); } /* Return the value of the array indexing expression S as a single string. @@ -967,11 +975,12 @@ array_value (s, quoted, rtype) by other parts of the shell such as the arithmetic expression evaluator in expr.c. */ char * -get_array_value (s, allow_all, rtype) +get_array_value (s, allow_all, rtype, indp) char *s; int allow_all, *rtype; + arrayind_t *indp; { - return (array_value_internal (s, 0, allow_all, rtype)); + return (array_value_internal (s, 0, allow_all, rtype, indp)); } char * @@ -990,6 +999,9 @@ array_keys (s, quoted) 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)) diff --git a/arrayfunc.c~ b/arrayfunc.c~ new file mode 100644 index 000000000..c5c926e70 --- /dev/null +++ b/arrayfunc.c~ @@ -0,0 +1,1026 @@ +/* arrayfunc.c -- High-level array functions used by other parts of the shell. */ + +/* Copyright (C) 2001-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) +# 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 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, *akey; + arrayind_t ind; + int sublen; + SHELL_VAR *entry; + + vname = array_variable_name (name, &sub, &sublen); + + if (vname == 0) + return ((SHELL_VAR *)NULL); + + if ((ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']') || (sublen <= 1)) + { + free (vname); + err_badarraysub (name); + return ((SHELL_VAR *)NULL); + } + + entry = find_variable (vname); + + 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) + { + free (vname); + err_badarraysub (name); + return ((SHELL_VAR *)NULL); + } + entry = bind_assoc_variable (entry, vname, akey, value, flags); + } + else + { + ind = array_expand_index (sub, sublen); + if (ind < 0) + { + free (vname); + err_badarraysub (name); + return ((SHELL_VAR *)NULL); + } + entry = bind_array_variable (vname, ind, value, flags); + } + + free (vname); + return (entry); +} + +/* Find the array variable corresponding to NAME. If there is no variable, + create a new array variable. If the variable exists but is not an array, + convert it to an indexed array. If 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; + char *val; + int ni; + + /* I don't believe this condition is ever true any more. */ + 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 we're using [subscript]=value, we need to quote each [ and ] to + prevent unwanted filename expansion. */ + if (list) + quote_array_assignment_chars (list); + + /* Now that we've split it, perform the shell expansions on each + word in the list. */ + nlist = list ? expand_words_no_vars (list) : (WORD_LIST *)NULL; + + dispose_words (list); + + if (val != value) + free (val); + + 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; + 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] == '[') + { + len = skipsubscript (w, 0, (var && assoc_p (var) != 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 (w + 1, len); + if (ind < 0) + { + err_badarraysub (w); + continue; + } + + last_ind = ind; + } + else if (assoc_p (var)) + { + akey = substring (w, 1, len); + if (akey == 0 || *akey == 0) + { + err_badarraysub (w); + 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; + } + + 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++; + } +} + +/* 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)); + return -1; + } + assoc_remove (assoc_cell (var), akey); + free (akey); + } + else + { + ind = array_expand_index (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 (s, len) + char *s; + int len; +{ + char *exp, *t; + int expok; + arrayind_t val; + + exp = (char *)xmalloc (len); + strncpy (exp, s, len - 1); + exp[len - 1] = '\0'; + t = expand_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; +} + +/* 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, allow_all, rtype, indp) + char *s; + int quoted, allow_all, *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 */ + + /* [ */ + if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']') + { + if (rtype) + *rtype = (t[0] == '*') ? 1 : 2; + if (allow_all == 0) + { + err_badarraysub (s); + return ((char *)NULL); + } + else if (var == 0 || value_cell (var) == 0) + 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) + { + ind = array_expand_index (t, len); + if (ind < 0) + { +index_error: + if (var) + err_badarraysub (var->name); + else + { + t[-1] = '\0'; + err_badarraysub (s); + t[-1] = '['; /* ] */ + } + return ((char *)NULL); + } + if (indp) + *indp = ind; + } + 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) + goto index_error; + } + + if (var == 0) + 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, rtype, indp) + char *s; + int quoted, *rtype; + arrayind_t *indp; +{ + return (array_value_internal (s, quoted, 1, rtype, indp)); +} + +/* Return the value of the array indexing expression S as a single string. + If ALLOW_ALL is 0, do not allow `@' and `*' subscripts. This is used + by other parts of the shell such as the arithmetic expression evaluator + in expr.c. */ +char * +get_array_value (s, allow_all, rtype, indp) + char *s; + int allow_all, *rtype; + arrayind_t *indp; +{ + return (array_value_internal (s, 0, allow_all, 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/arrayfunc.h b/arrayfunc.h index c12fc9932..9add11897 100644 --- a/arrayfunc.h +++ b/arrayfunc.h @@ -51,8 +51,8 @@ extern void print_assoc_assignment __P((SHELL_VAR *, int)); extern arrayind_t array_expand_index __P((char *, int)); extern int valid_array_reference __P((char *)); -extern char *array_value __P((char *, int, int *)); -extern char *get_array_value __P((char *, int, int *)); +extern char *array_value __P((char *, int, int *, arrayind_t *)); +extern char *get_array_value __P((char *, int, int *, arrayind_t *)); extern char *array_keys __P((char *, int)); diff --git a/arrayfunc.h~ b/arrayfunc.h~ new file mode 100644 index 000000000..85ebb483e --- /dev/null +++ b/arrayfunc.h~ @@ -0,0 +1,64 @@ +/* arrayfunc.h -- declarations for miscellaneous array functions in arrayfunc.c */ + +/* Copyright (C) 2001-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 . +*/ + +#if !defined (_ARRAYFUNC_H_) +#define _ARRAYFUNC_H_ + +/* Must include variables.h before including this file. */ + +#if defined (ARRAY_VARS) + +extern SHELL_VAR *convert_var_to_array __P((SHELL_VAR *)); +extern SHELL_VAR *convert_var_to_assoc __P((SHELL_VAR *)); + +extern SHELL_VAR *bind_array_variable __P((char *, arrayind_t, char *, int)); +extern SHELL_VAR *bind_array_element __P((SHELL_VAR *, arrayind_t, char *, int)); +extern SHELL_VAR *assign_array_element __P((char *, char *, int)); + +extern SHELL_VAR *bind_assoc_variable __P((SHELL_VAR *, char *, char *, char *, int)); + +extern SHELL_VAR *find_or_make_array_variable __P((char *, int)); + +extern SHELL_VAR *assign_array_from_string __P((char *, char *, int)); +extern SHELL_VAR *assign_array_var_from_word_list __P((SHELL_VAR *, WORD_LIST *, int)); + +extern WORD_LIST *expand_compound_array_assignment __P((SHELL_VAR *, char *, int)); +extern void assign_compound_array_list __P((SHELL_VAR *, WORD_LIST *, int)); +extern SHELL_VAR *assign_array_var_from_string __P((SHELL_VAR *, char *, int)); + +extern int unbind_array_element __P((SHELL_VAR *, char *)); +extern int skipsubscript __P((const char *, int, int)); + +extern void print_array_assignment __P((SHELL_VAR *, int)); +extern void print_assoc_assignment __P((SHELL_VAR *, int)); + +extern arrayind_t array_expand_index __P((char *, int)); +extern int valid_array_reference __P((char *)); +extern char *array_value __P((char *, int, int *)); +extern char *get_array_value __P((char *, int, int *, arrayind_t *)); + +extern char *array_keys __P((char *, int)); + +extern char *array_variable_name __P((char *, char **, int *)); +extern SHELL_VAR *array_variable_part __P((char *, char **, int *)); + +#endif + +#endif /* !_ARRAYFUNC_H_ */ diff --git a/autom4te.cache/output.0 b/autom4te.cache/output.0 index a14841a6a..8038cfbef 100644 --- a/autom4te.cache/output.0 +++ b/autom4te.cache/output.0 @@ -1,7 +1,7 @@ @%:@! /bin/sh -@%:@ From configure.in for Bash 4.1, version 4.019. +@%:@ From configure.in for Bash 4.1, version 4.020. @%:@ Guess values for system-dependent variables and create Makefiles. -@%:@ Generated by GNU Autoconf 2.63 for bash 4.1-release. +@%:@ Generated by GNU Autoconf 2.63 for bash 4.1-maint. @%:@ @%:@ Report bugs to . @%:@ @@ -597,8 +597,8 @@ SHELL=${CONFIG_SHELL-/bin/sh} # Identity of this package. PACKAGE_NAME='bash' PACKAGE_TARNAME='bash' -PACKAGE_VERSION='4.1-release' -PACKAGE_STRING='bash 4.1-release' +PACKAGE_VERSION='4.1-maint' +PACKAGE_STRING='bash 4.1-maint' PACKAGE_BUGREPORT='bug-bash@gnu.org' ac_unique_file="shell.h" @@ -1411,7 +1411,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures bash 4.1-release to adapt to many kinds of systems. +\`configure' configures bash 4.1-maint to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1476,7 +1476,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of bash 4.1-release:";; + short | recursive ) echo "Configuration of bash 4.1-maint:";; esac cat <<\_ACEOF @@ -1652,7 +1652,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -bash configure 4.1-release +bash configure 4.1-maint generated by GNU Autoconf 2.63 Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, @@ -1666,7 +1666,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by bash $as_me 4.1-release, which was +It was created by bash $as_me 4.1-maint, which was generated by GNU Autoconf 2.63. Invocation command line was $ $0 $@ @@ -2080,7 +2080,7 @@ ac_config_headers="$ac_config_headers config.h" BASHVERS=4.1 -RELSTATUS=release +RELSTATUS=maint case "$RELSTATUS" in alp*|bet*|dev*|rc*|maint*) DEBUG='-DDEBUG' MALLOC_DEBUG='-DMALLOC_DEBUG' ;; @@ -11502,12 +11502,13 @@ done + for ac_header in unistd.h stdlib.h stdarg.h varargs.h limits.h string.h \ memory.h locale.h termcap.h termio.h termios.h dlfcn.h \ stddef.h stdint.h netdb.h pwd.h grp.h strings.h regex.h \ - syslog.h + syslog.h ulimit.h do as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then @@ -31170,7 +31171,7 @@ exec 6>&1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by bash $as_me 4.1-release, which was +This file was extended by bash $as_me 4.1-maint, which was generated by GNU Autoconf 2.63. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -31233,7 +31234,7 @@ Report bugs to ." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_version="\\ -bash config.status 4.1-release +bash config.status 4.1-maint configured by $0, generated by GNU Autoconf 2.63, with options \\"`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\" diff --git a/autom4te.cache/traces.0 b/autom4te.cache/traces.0 index 497b57e2a..4663f479e 100644 --- a/autom4te.cache/traces.0 +++ b/autom4te.cache/traces.0 @@ -1,4 +1,4 @@ -m4trace:configure.in:29: -1- AC_INIT([bash], [4.1-release], [bug-bash@gnu.org]) +m4trace:configure.in:29: -1- AC_INIT([bash], [4.1-maint], [bug-bash@gnu.org]) m4trace:configure.in:29: -1- m4_pattern_forbid([^_?A[CHUM]_]) m4trace:configure.in:29: -1- m4_pattern_forbid([_AC_]) m4trace:configure.in:29: -1- m4_pattern_forbid([^LIBOBJS$], [do not use LIBOBJS directly, use AC_LIBOBJ (see section `AC_LIBOBJ vs LIBOBJS']) @@ -1190,6 +1190,8 @@ m4trace:configure.in:659: -1- AH_OUTPUT([HAVE_REGEX_H], [/* Define to 1 if you h #undef HAVE_REGEX_H]) m4trace:configure.in:659: -1- AH_OUTPUT([HAVE_SYSLOG_H], [/* Define to 1 if you have the header file. */ #undef HAVE_SYSLOG_H]) +m4trace:configure.in:659: -1- AH_OUTPUT([HAVE_ULIMIT_H], [/* Define to 1 if you have the header file. */ +#undef HAVE_ULIMIT_H]) m4trace:configure.in:663: -1- AH_OUTPUT([HAVE_SYS_PTE_H], [/* Define to 1 if you have the header file. */ #undef HAVE_SYS_PTE_H]) m4trace:configure.in:663: -1- AH_OUTPUT([HAVE_SYS_STREAM_H], [/* Define to 1 if you have the header file. */ diff --git a/bashline.c b/bashline.c index 7658771eb..345c5755d 100644 --- a/bashline.c +++ b/bashline.c @@ -3421,7 +3421,10 @@ static int putx(c) int c; { - putc (c, rl_outstream); + int x; + + x = putc (c, rl_outstream); + return (x); } static int diff --git a/bashline.c~ b/bashline.c~ new file mode 100644 index 000000000..4d0a1cbf0 --- /dev/null +++ b/bashline.c~ @@ -0,0 +1,3663 @@ +/* bashline.c -- Bash's interface to the readline library. */ + +/* 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 (READLINE) + +#include "bashtypes.h" +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#if defined (HAVE_GRP_H) +# include +#endif + +#if defined (HAVE_NETDB_H) +# include +#endif + +#include +#include "chartypes.h" +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "input.h" +#include "builtins.h" +#include "bashhist.h" +#include "bashline.h" +#include "execute_cmd.h" +#include "findcmd.h" +#include "pathexp.h" +#include "shmbutil.h" + +#include "builtins/common.h" + +#include +#include +#include + +#include + +#if defined (ALIAS) +# include "alias.h" +#endif + +#if defined (PROGRAMMABLE_COMPLETION) +# include "pcomplete.h" +#endif + +/* These should agree with the defines for emacs_mode and vi_mode in + rldefs.h, even though that's not a public readline header file. */ +#ifndef EMACS_EDITING_MODE +# define NO_EDITING_MODE -1 +# define EMACS_EDITING_MODE 1 +# define VI_EDITING_MODE 0 +#endif + +#define RL_BOOLEAN_VARIABLE_VALUE(s) ((s)[0] == 'o' && (s)[1] == 'n' && (s)[2] == '\0') + +#if defined (BRACE_COMPLETION) +extern int bash_brace_completion __P((int, int)); +#endif /* BRACE_COMPLETION */ + +/* To avoid including curses.h/term.h/termcap.h and that whole mess. */ +extern int tputs __P((const char *string, int nlines, int (*outx)(int))); + +/* Forward declarations */ + +/* Functions bound to keys in Readline for Bash users. */ +static int shell_expand_line __P((int, int)); +static int display_shell_version __P((int, int)); +static int operate_and_get_next __P((int, int)); + +static int bash_ignore_filenames __P((char **)); +static int bash_ignore_everything __P((char **)); + +#if defined (BANG_HISTORY) +static char *history_expand_line_internal __P((char *)); +static int history_expand_line __P((int, int)); +static int tcsh_magic_space __P((int, int)); +#endif /* BANG_HISTORY */ +#ifdef ALIAS +static int alias_expand_line __P((int, int)); +#endif +#if defined (BANG_HISTORY) && defined (ALIAS) +static int history_and_alias_expand_line __P((int, int)); +#endif + +static int bash_forward_shellword __P((int, int)); +static int bash_backward_shellword __P((int, int)); +static int bash_kill_shellword __P((int, int)); +static int bash_backward_kill_shellword __P((int, int)); + +/* Helper functions for Readline. */ +static char *restore_tilde __P((char *, char *)); + +static char *bash_filename_rewrite_hook __P((char *, int)); +static void bash_directory_expansion __P((char **)); +static int bash_directory_completion_hook __P((char **)); +static int filename_completion_ignore __P((char **)); +static int bash_push_line __P((void)); + +static void cleanup_expansion_error __P((void)); +static void maybe_make_readline_line __P((char *)); +static void set_up_new_line __P((char *)); + +static int check_redir __P((int)); +static char **attempt_shell_completion __P((const char *, int, int)); +static char *variable_completion_function __P((const char *, int)); +static char *hostname_completion_function __P((const char *, int)); +static char *command_subst_completion_function __P((const char *, int)); + +static void build_history_completion_array __P((void)); +static char *history_completion_generator __P((const char *, int)); +static int dynamic_complete_history __P((int, int)); +static int bash_dabbrev_expand __P((int, int)); + +static void initialize_hostname_list __P((void)); +static void add_host_name __P((char *)); +static void snarf_hosts_from_file __P((char *)); +static char **hostnames_matching __P((char *)); + +static void _ignore_completion_names __P((char **, sh_ignore_func_t *)); +static int name_is_acceptable __P((const char *)); +static int test_for_directory __P((const char *)); +static int return_zero __P((const char *)); + +static char *bash_dequote_filename __P((char *, int)); +static char *quote_word_break_chars __P((char *)); +static char *bash_quote_filename __P((char *, int, char *)); + +static int putx __P((int)); +static int bash_execute_unix_command __P((int, int)); +static void init_unix_command_map __P((void)); +static int isolate_sequence __P((char *, int, int, int *)); + +static int set_saved_history __P((void)); + +#if defined (ALIAS) +static int posix_edit_macros __P((int, int)); +#endif + +#if defined (PROGRAMMABLE_COMPLETION) +static int find_cmd_start __P((int)); +static int find_cmd_end __P((int)); +static char *find_cmd_name __P((int)); +static char *prog_complete_return __P((const char *, int)); + +static char **prog_complete_matches; +#endif + +/* Variables used here but defined in other files. */ +#if defined (BANG_HISTORY) +extern int hist_verify; +#endif + +extern int current_command_line_count, last_command_exit_value; +extern int array_needs_making; +extern int posixly_correct, no_symbolic_links; +extern char *current_prompt_string, *ps1_prompt; +extern STRING_INT_ALIST word_token_alist[]; +extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin; + +/* SPECIFIC_COMPLETION_FUNCTIONS specifies that we have individual + completion functions which indicate what type of completion should be + done (at or before point) that can be bound to key sequences with + the readline library. */ +#define SPECIFIC_COMPLETION_FUNCTIONS + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) +static int bash_specific_completion __P((int, rl_compentry_func_t *)); + +static int bash_complete_filename_internal __P((int)); +static int bash_complete_username_internal __P((int)); +static int bash_complete_hostname_internal __P((int)); +static int bash_complete_variable_internal __P((int)); +static int bash_complete_command_internal __P((int)); + +static int bash_complete_filename __P((int, int)); +static int bash_possible_filename_completions __P((int, int)); +static int bash_complete_username __P((int, int)); +static int bash_possible_username_completions __P((int, int)); +static int bash_complete_hostname __P((int, int)); +static int bash_possible_hostname_completions __P((int, int)); +static int bash_complete_variable __P((int, int)); +static int bash_possible_variable_completions __P((int, int)); +static int bash_complete_command __P((int, int)); +static int bash_possible_command_completions __P((int, int)); + +static char *glob_complete_word __P((const char *, int)); +static int bash_glob_completion_internal __P((int)); +static int bash_glob_complete_word __P((int, int)); +static int bash_glob_expand_word __P((int, int)); +static int bash_glob_list_expansions __P((int, int)); + +#endif /* SPECIFIC_COMPLETION_FUNCTIONS */ + +static int edit_and_execute_command __P((int, int, int, char *)); +#if defined (VI_MODE) +static int vi_edit_and_execute_command __P((int, int)); +static int bash_vi_complete __P((int, int)); +#endif +static int emacs_edit_and_execute_command __P((int, int)); + +/* Non-zero once initalize_readline () has been called. */ +int bash_readline_initialized = 0; + +/* If non-zero, we do hostname completion, breaking words at `@' and + trying to complete the stuff after the `@' from our own internal + host list. */ +int perform_hostname_completion = 1; + +/* If non-zero, we don't do command completion on an empty line. */ +int no_empty_command_completion; + +/* Set FORCE_FIGNORE if you want to honor FIGNORE even if it ignores the + only possible matches. Set to 0 if you want to match filenames if they + are the only possible matches, even if FIGNORE says to. */ +int force_fignore = 1; + +/* Perform spelling correction on directory names during word completion */ +int dircomplete_spelling = 0; + +static char *bash_completer_word_break_characters = " \t\n\"'@><=;|&(:"; +static char *bash_nohostname_word_break_characters = " \t\n\"'><=;|&(:"; +/* )) */ + +static rl_hook_func_t *old_rl_startup_hook = (rl_hook_func_t *)NULL; + +static int dot_in_path = 0; + +/* Set to non-zero when dabbrev-expand is running */ +static int dabbrev_expand_active = 0; + +/* What kind of quoting is performed by bash_quote_filename: + COMPLETE_DQUOTE = double-quoting the filename + COMPLETE_SQUOTE = single_quoting the filename + COMPLETE_BSQUOTE = backslash-quoting special chars in the filename +*/ +#define COMPLETE_DQUOTE 1 +#define COMPLETE_SQUOTE 2 +#define COMPLETE_BSQUOTE 3 +static int completion_quoting_style = COMPLETE_BSQUOTE; + +/* Flag values for the final argument to bash_default_completion */ +#define DEFCOMP_CMDPOS 1 + +/* Change the readline VI-mode keymaps into or out of Posix.2 compliance. + Called when the shell is put into or out of `posix' mode. */ +void +posix_readline_initialize (on_or_off) + int on_or_off; +{ + if (on_or_off) + rl_variable_bind ("comment-begin", "#"); +#if defined (VI_MODE) + rl_bind_key_in_map (CTRL ('I'), on_or_off ? rl_insert : rl_complete, vi_insertion_keymap); +#endif +} + +void +reset_completer_word_break_chars () +{ + rl_completer_word_break_characters = perform_hostname_completion ? savestring (bash_completer_word_break_characters) : savestring (bash_nohostname_word_break_characters); +} + +/* When this function returns, rl_completer_word_break_characters points to + dynamically allocated memory. */ +int +enable_hostname_completion (on_or_off) + int on_or_off; +{ + int old_value; + char *at, *nv, *nval; + + old_value = perform_hostname_completion; + + if (on_or_off) + { + perform_hostname_completion = 1; + rl_special_prefixes = "$@"; + } + else + { + perform_hostname_completion = 0; + rl_special_prefixes = "$"; + } + + /* Now we need to figure out how to appropriately modify and assign + rl_completer_word_break_characters depending on whether we want + hostname completion on or off. */ + + /* If this is the first time this has been called + (bash_readline_initialized == 0), use the sames values as before, but + allocate new memory for rl_completer_word_break_characters. */ + + if (bash_readline_initialized == 0 && + (rl_completer_word_break_characters == 0 || + rl_completer_word_break_characters == rl_basic_word_break_characters)) + { + if (on_or_off) + rl_completer_word_break_characters = savestring (bash_completer_word_break_characters); + else + rl_completer_word_break_characters = savestring (bash_nohostname_word_break_characters); + } + else + { + /* See if we have anything to do. */ + at = strchr (rl_completer_word_break_characters, '@'); + if ((at == 0 && on_or_off == 0) || (at != 0 && on_or_off != 0)) + return old_value; + + /* We have something to do. Do it. */ + nval = (char *)xmalloc (strlen (rl_completer_word_break_characters) + 1 + on_or_off); + + if (on_or_off == 0) + { + /* Turn it off -- just remove `@' from word break chars. We want + to remove all occurrences of `@' from the char list, so we loop + rather than just copy the rest of the list over AT. */ + for (nv = nval, at = rl_completer_word_break_characters; *at; ) + if (*at != '@') + *nv++ = *at++; + else + at++; + *nv = '\0'; + } + else + { + nval[0] = '@'; + strcpy (nval + 1, rl_completer_word_break_characters); + } + + free (rl_completer_word_break_characters); + rl_completer_word_break_characters = nval; + } + + return (old_value); +} + +/* Called once from parse.y if we are going to use readline. */ +void +initialize_readline () +{ + rl_command_func_t *func; + char kseq[2]; + + if (bash_readline_initialized) + return; + + rl_terminal_name = get_string_value ("TERM"); + rl_instream = stdin; + rl_outstream = stderr; + + /* Allow conditional parsing of the ~/.inputrc file. */ + rl_readline_name = "Bash"; + + /* Add bindable names before calling rl_initialize so they may be + referenced in the various inputrc files. */ + rl_add_defun ("shell-expand-line", shell_expand_line, -1); +#ifdef BANG_HISTORY + rl_add_defun ("history-expand-line", history_expand_line, -1); + rl_add_defun ("magic-space", tcsh_magic_space, -1); +#endif + + rl_add_defun ("shell-forward-word", bash_forward_shellword, -1); + rl_add_defun ("shell-backward-word", bash_backward_shellword, -1); + rl_add_defun ("shell-kill-word", bash_kill_shellword, -1); + rl_add_defun ("shell-backward-kill-word", bash_backward_kill_shellword, -1); + +#ifdef ALIAS + rl_add_defun ("alias-expand-line", alias_expand_line, -1); +# ifdef BANG_HISTORY + rl_add_defun ("history-and-alias-expand-line", history_and_alias_expand_line, -1); +# endif +#endif + + /* Backwards compatibility. */ + rl_add_defun ("insert-last-argument", rl_yank_last_arg, -1); + + rl_add_defun ("operate-and-get-next", operate_and_get_next, -1); + rl_add_defun ("display-shell-version", display_shell_version, -1); + rl_add_defun ("edit-and-execute-command", emacs_edit_and_execute_command, -1); + +#if defined (BRACE_COMPLETION) + rl_add_defun ("complete-into-braces", bash_brace_completion, -1); +#endif + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) + rl_add_defun ("complete-filename", bash_complete_filename, -1); + rl_add_defun ("possible-filename-completions", bash_possible_filename_completions, -1); + rl_add_defun ("complete-username", bash_complete_username, -1); + rl_add_defun ("possible-username-completions", bash_possible_username_completions, -1); + rl_add_defun ("complete-hostname", bash_complete_hostname, -1); + rl_add_defun ("possible-hostname-completions", bash_possible_hostname_completions, -1); + rl_add_defun ("complete-variable", bash_complete_variable, -1); + rl_add_defun ("possible-variable-completions", bash_possible_variable_completions, -1); + rl_add_defun ("complete-command", bash_complete_command, -1); + rl_add_defun ("possible-command-completions", bash_possible_command_completions, -1); + rl_add_defun ("glob-complete-word", bash_glob_complete_word, -1); + rl_add_defun ("glob-expand-word", bash_glob_expand_word, -1); + rl_add_defun ("glob-list-expansions", bash_glob_list_expansions, -1); +#endif + + rl_add_defun ("dynamic-complete-history", dynamic_complete_history, -1); + rl_add_defun ("dabbrev-expand", bash_dabbrev_expand, -1); + + /* Bind defaults before binding our custom shell keybindings. */ + if (RL_ISSTATE(RL_STATE_INITIALIZED) == 0) + rl_initialize (); + + /* Bind up our special shell functions. */ + rl_bind_key_if_unbound_in_map (CTRL('E'), shell_expand_line, emacs_meta_keymap); + +#ifdef BANG_HISTORY + rl_bind_key_if_unbound_in_map ('^', history_expand_line, emacs_meta_keymap); +#endif + + rl_bind_key_if_unbound_in_map (CTRL ('O'), operate_and_get_next, emacs_standard_keymap); + rl_bind_key_if_unbound_in_map (CTRL ('V'), display_shell_version, emacs_ctlx_keymap); + + /* In Bash, the user can switch editing modes with "set -o [vi emacs]", + so it is not necessary to allow C-M-j for context switching. Turn + off this occasionally confusing behaviour. */ + kseq[0] = CTRL('J'); + kseq[1] = '\0'; + func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL); + if (func == rl_vi_editing_mode) + rl_unbind_key_in_map (CTRL('J'), emacs_meta_keymap); + kseq[0] = CTRL('M'); + func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL); + if (func == rl_vi_editing_mode) + rl_unbind_key_in_map (CTRL('M'), emacs_meta_keymap); +#if defined (VI_MODE) + rl_unbind_key_in_map (CTRL('E'), vi_movement_keymap); +#endif + +#if defined (BRACE_COMPLETION) + rl_bind_key_if_unbound_in_map ('{', bash_brace_completion, emacs_meta_keymap); /*}*/ +#endif /* BRACE_COMPLETION */ + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) + rl_bind_key_if_unbound_in_map ('/', bash_complete_filename, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('/', bash_possible_filename_completions, emacs_ctlx_keymap); + + /* Have to jump through hoops here because there is a default binding for + M-~ (rl_tilde_expand) */ + kseq[0] = '~'; + kseq[1] = '\0'; + func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL); + if (func == 0 || func == rl_tilde_expand) + rl_bind_keyseq_in_map (kseq, bash_complete_username, emacs_meta_keymap); + + rl_bind_key_if_unbound_in_map ('~', bash_possible_username_completions, emacs_ctlx_keymap); + + rl_bind_key_if_unbound_in_map ('@', bash_complete_hostname, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('@', bash_possible_hostname_completions, emacs_ctlx_keymap); + + rl_bind_key_if_unbound_in_map ('$', bash_complete_variable, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('$', bash_possible_variable_completions, emacs_ctlx_keymap); + + rl_bind_key_if_unbound_in_map ('!', bash_complete_command, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('!', bash_possible_command_completions, emacs_ctlx_keymap); + + rl_bind_key_if_unbound_in_map ('g', bash_glob_complete_word, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('*', bash_glob_expand_word, emacs_ctlx_keymap); + rl_bind_key_if_unbound_in_map ('g', bash_glob_list_expansions, emacs_ctlx_keymap); + +#endif /* SPECIFIC_COMPLETION_FUNCTIONS */ + + kseq[0] = TAB; + kseq[1] = '\0'; + func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL); + if (func == 0 || func == rl_tab_insert) + rl_bind_key_in_map (TAB, dynamic_complete_history, emacs_meta_keymap); + + /* Tell the completer that we want a crack first. */ + rl_attempted_completion_function = attempt_shell_completion; + + /* Tell the completer that we might want to follow symbolic links or + do other expansion on directory names. */ + rl_directory_completion_hook = bash_directory_completion_hook; + + rl_filename_rewrite_hook = bash_filename_rewrite_hook; + + /* Tell the filename completer we want a chance to ignore some names. */ + rl_ignore_some_completions_function = filename_completion_ignore; + + /* Bind C-xC-e to invoke emacs and run result as commands. */ + rl_bind_key_if_unbound_in_map (CTRL ('E'), emacs_edit_and_execute_command, emacs_ctlx_keymap); +#if defined (VI_MODE) + rl_bind_key_if_unbound_in_map ('v', vi_edit_and_execute_command, vi_movement_keymap); +# if defined (ALIAS) + rl_bind_key_if_unbound_in_map ('@', posix_edit_macros, vi_movement_keymap); +# endif + + rl_bind_key_in_map ('\\', bash_vi_complete, vi_movement_keymap); + rl_bind_key_in_map ('*', bash_vi_complete, vi_movement_keymap); + rl_bind_key_in_map ('=', bash_vi_complete, vi_movement_keymap); +#endif + + rl_completer_quote_characters = "'\""; + + /* This sets rl_completer_word_break_characters and rl_special_prefixes + to the appropriate values, depending on whether or not hostname + completion is enabled. */ + enable_hostname_completion (perform_hostname_completion); + + /* characters that need to be quoted when appearing in filenames. */ +#if 0 + rl_filename_quote_characters = " \t\n\\\"'@<>=;|&()#$`?*[!:{"; /*}*/ +#else + rl_filename_quote_characters = " \t\n\\\"'@<>=;|&()#$`?*[!:{~"; /*}*/ +#endif + rl_filename_quoting_function = bash_quote_filename; + rl_filename_dequoting_function = bash_dequote_filename; + rl_char_is_quoted_p = char_is_quoted; + +#if 0 + /* This is superfluous and makes it impossible to use tab completion in + vi mode even when explicitly binding it in ~/.inputrc. sv_strict_posix() + should already have called posix_readline_initialize() when + posixly_correct was set. */ + if (posixly_correct) + posix_readline_initialize (1); +#endif + + bash_readline_initialized = 1; +} + +void +bashline_reinitialize () +{ + bash_readline_initialized = 0; +} + +/* On Sun systems at least, rl_attempted_completion_function can end up + getting set to NULL, and rl_completion_entry_function set to do command + word completion if Bash is interrupted while trying to complete a command + word. This just resets all the completion functions to the right thing. + It's called from throw_to_top_level(). */ +void +bashline_reset () +{ + tilde_initialize (); + rl_attempted_completion_function = attempt_shell_completion; + rl_completion_entry_function = NULL; + rl_directory_completion_hook = bash_directory_completion_hook; + rl_ignore_some_completions_function = filename_completion_ignore; +} + +/* Contains the line to push into readline. */ +static char *push_to_readline = (char *)NULL; + +/* Push the contents of push_to_readline into the + readline buffer. */ +static int +bash_push_line () +{ + if (push_to_readline) + { + rl_insert_text (push_to_readline); + free (push_to_readline); + push_to_readline = (char *)NULL; + rl_startup_hook = old_rl_startup_hook; + } + return 0; +} + +/* Call this to set the initial text for the next line to read + from readline. */ +int +bash_re_edit (line) + char *line; +{ + FREE (push_to_readline); + + push_to_readline = savestring (line); + old_rl_startup_hook = rl_startup_hook; + rl_startup_hook = bash_push_line; + + return (0); +} + +static int +display_shell_version (count, c) + int count, c; +{ + rl_crlf (); + show_shell_version (0); + putc ('\r', rl_outstream); + fflush (rl_outstream); + rl_on_new_line (); + rl_redisplay (); + return 0; +} + +/* **************************************************************** */ +/* */ +/* Readline Stuff */ +/* */ +/* **************************************************************** */ + +/* If the user requests hostname completion, then simply build a list + of hosts, and complete from that forever more, or at least until + HOSTFILE is unset. */ + +/* THIS SHOULD BE A STRINGLIST. */ +/* The kept list of hostnames. */ +static char **hostname_list = (char **)NULL; + +/* The physical size of the above list. */ +static int hostname_list_size; + +/* The number of hostnames in the above list. */ +static int hostname_list_length; + +/* Whether or not HOSTNAME_LIST has been initialized. */ +int hostname_list_initialized = 0; + +/* Initialize the hostname completion table. */ +static void +initialize_hostname_list () +{ + char *temp; + + temp = get_string_value ("HOSTFILE"); + if (temp == 0) + temp = get_string_value ("hostname_completion_file"); + if (temp == 0) + temp = DEFAULT_HOSTS_FILE; + + snarf_hosts_from_file (temp); + + if (hostname_list) + hostname_list_initialized++; +} + +/* Add NAME to the list of hosts. */ +static void +add_host_name (name) + char *name; +{ + if (hostname_list_length + 2 > hostname_list_size) + { + hostname_list_size = (hostname_list_size + 32) - (hostname_list_size % 32); + hostname_list = strvec_resize (hostname_list, hostname_list_size); + } + + hostname_list[hostname_list_length++] = savestring (name); + hostname_list[hostname_list_length] = (char *)NULL; +} + +#define cr_whitespace(c) ((c) == '\r' || (c) == '\n' || whitespace(c)) + +static void +snarf_hosts_from_file (filename) + char *filename; +{ + FILE *file; + char *temp, buffer[256], name[256]; + register int i, start; + + file = fopen (filename, "r"); + if (file == 0) + return; + + while (temp = fgets (buffer, 255, file)) + { + /* Skip to first character. */ + for (i = 0; buffer[i] && cr_whitespace (buffer[i]); i++) + ; + + /* If comment or blank line, ignore. */ + if (buffer[i] == '\0' || buffer[i] == '#') + continue; + + /* If `preprocessor' directive, do the include. */ + if (strncmp (buffer + i, "$include ", 9) == 0) + { + char *incfile, *t; + + /* Find start of filename. */ + for (incfile = buffer + i + 9; *incfile && whitespace (*incfile); incfile++) + ; + + /* Find end of filename. */ + for (t = incfile; *t && cr_whitespace (*t) == 0; t++) + ; + + *t = '\0'; + + snarf_hosts_from_file (incfile); + continue; + } + + /* Skip internet address if present. */ + if (DIGIT (buffer[i])) + for (; buffer[i] && cr_whitespace (buffer[i]) == 0; i++); + + /* Gobble up names. Each name is separated with whitespace. */ + while (buffer[i]) + { + for (; cr_whitespace (buffer[i]); i++) + ; + if (buffer[i] == '\0' || buffer[i] == '#') + break; + + /* Isolate the current word. */ + for (start = i; buffer[i] && cr_whitespace (buffer[i]) == 0; i++) + ; + if (i == start) + continue; + strncpy (name, buffer + start, i - start); + name[i - start] = '\0'; + add_host_name (name); + } + } + fclose (file); +} + +/* Return the hostname list. */ +char ** +get_hostname_list () +{ + if (hostname_list_initialized == 0) + initialize_hostname_list (); + return (hostname_list); +} + +void +clear_hostname_list () +{ + register int i; + + if (hostname_list_initialized == 0) + return; + for (i = 0; i < hostname_list_length; i++) + free (hostname_list[i]); + hostname_list_length = hostname_list_initialized = 0; +} + +/* Return a NULL terminated list of hostnames which begin with TEXT. + Initialize the hostname list the first time if neccessary. + The array is malloc ()'ed, but not the individual strings. */ +static char ** +hostnames_matching (text) + char *text; +{ + register int i, len, nmatch, rsize; + char **result; + + if (hostname_list_initialized == 0) + initialize_hostname_list (); + + if (hostname_list_initialized == 0) + return ((char **)NULL); + + /* Special case. If TEXT consists of nothing, then the whole list is + what is desired. */ + if (*text == '\0') + { + result = strvec_create (1 + hostname_list_length); + for (i = 0; i < hostname_list_length; i++) + result[i] = hostname_list[i]; + result[i] = (char *)NULL; + return (result); + } + + /* Scan until found, or failure. */ + len = strlen (text); + result = (char **)NULL; + for (i = nmatch = rsize = 0; i < hostname_list_length; i++) + { + if (STREQN (text, hostname_list[i], len) == 0) + continue; + + /* OK, it matches. Add it to the list. */ + if (nmatch >= (rsize - 1)) + { + rsize = (rsize + 16) - (rsize % 16); + result = strvec_resize (result, rsize); + } + + result[nmatch++] = hostname_list[i]; + } + if (nmatch) + result[nmatch] = (char *)NULL; + return (result); +} + +/* The equivalent of the Korn shell C-o operate-and-get-next-history-line + editing command. */ +static int saved_history_line_to_use = -1; + +static int +set_saved_history () +{ + if (saved_history_line_to_use >= 0) + rl_get_previous_history (history_length - saved_history_line_to_use, 0); + saved_history_line_to_use = -1; + rl_startup_hook = old_rl_startup_hook; + return (0); +} + +static int +operate_and_get_next (count, c) + int count, c; +{ + int where; + + /* Accept the current line. */ + rl_newline (1, c); + + /* Find the current line, and find the next line to use. */ + where = where_history (); + + if ((history_is_stifled () && (history_length >= history_max_entries)) || + (where >= history_length - 1)) + saved_history_line_to_use = where; + else + saved_history_line_to_use = where + 1; + + old_rl_startup_hook = rl_startup_hook; + rl_startup_hook = set_saved_history; + + return 0; +} + +/* This vi mode command causes VI_EDIT_COMMAND to be run on the current + command being entered (if no explicit argument is given), otherwise on + a command from the history file. */ + +#define VI_EDIT_COMMAND "fc -e \"${VISUAL:-${EDITOR:-vi}}\"" +#define EMACS_EDIT_COMMAND "fc -e \"${VISUAL:-${EDITOR:-emacs}}\"" +#define POSIX_VI_EDIT_COMMAND "fc -e vi" + +static int +edit_and_execute_command (count, c, editing_mode, edit_command) + int count, c, editing_mode; + char *edit_command; +{ + char *command, *metaval; + int r, cclc, rrs, metaflag; + + rrs = rl_readline_state; + cclc = current_command_line_count; + + /* Accept the current line. */ + rl_newline (1, c); + + if (rl_explicit_arg) + { + command = (char *)xmalloc (strlen (edit_command) + 8); + sprintf (command, "%s %d", edit_command, count); + } + else + { + /* Take the command we were just editing, add it to the history file, + then call fc to operate on it. We have to add a dummy command to + the end of the history because fc ignores the last command (assumes + it's supposed to deal with the command before the `fc'). */ + using_history (); + bash_add_history (rl_line_buffer); + bash_add_history (""); + history_lines_this_session++; + using_history (); + command = savestring (edit_command); + } + + metaval = rl_variable_value ("input-meta"); + metaflag = RL_BOOLEAN_VARIABLE_VALUE (metaval); + + /* Now, POSIX.1-2001 and SUSv3 say that the commands executed from the + temporary file should be placed into the history. We don't do that + yet. */ + if (rl_deprep_term_function) + (*rl_deprep_term_function) (); + r = parse_and_execute (command, (editing_mode == VI_EDITING_MODE) ? "v" : "C-xC-e", SEVAL_NOHIST); + if (rl_prep_term_function) + (*rl_prep_term_function) (metaflag); + + current_command_line_count = cclc; + + /* Now erase the contents of the current line and undo the effects of the + rl_accept_line() above. We don't even want to make the text we just + executed available for undoing. */ + rl_line_buffer[0] = '\0'; /* XXX */ + rl_point = rl_end = 0; + rl_done = 0; + rl_readline_state = rrs; + + rl_forced_update_display (); + + return r; +} + +#if defined (VI_MODE) +static int +vi_edit_and_execute_command (count, c) + int count, c; +{ + if (posixly_correct) + return (edit_and_execute_command (count, c, VI_EDITING_MODE, POSIX_VI_EDIT_COMMAND)); + else + return (edit_and_execute_command (count, c, VI_EDITING_MODE, VI_EDIT_COMMAND)); +} +#endif /* VI_MODE */ + +static int +emacs_edit_and_execute_command (count, c) + int count, c; +{ + return (edit_and_execute_command (count, c, EMACS_EDITING_MODE, EMACS_EDIT_COMMAND)); +} + +#if defined (ALIAS) +static int +posix_edit_macros (count, key) + int count, key; +{ + int c; + char alias_name[3], *alias_value, *macro; + + c = rl_read_key (); + alias_name[0] = '_'; + alias_name[1] = c; + alias_name[2] = '\0'; + + alias_value = get_alias_value (alias_name); + if (alias_value && *alias_value) + { + macro = savestring (alias_value); + rl_push_macro_input (macro); + } + return 0; +} +#endif + +/* Bindable commands that move `shell-words': that is, sequences of + non-unquoted-metacharacters. */ + +#define WORDDELIM(c) (shellmeta(c) || shellblank(c)) + +static int +bash_forward_shellword (count, key) + int count, key; +{ + size_t slen; + int sindex, c, p; + DECLARE_MBSTATE; + + if (count < 0) + return (bash_backward_shellword (-count, key)); + + /* The tricky part of this is deciding whether or not the first character + we're on is an unquoted metacharacter. Not completely handled yet. */ + /* XXX - need to test this stuff with backslash-escaped shell + metacharacters and unclosed single- and double-quoted strings. */ + + p = rl_point; + slen = rl_end; + + while (count) + { + if (p == rl_end) + { + rl_point = rl_end; + return 0; + } + + /* Move forward until we hit a non-metacharacter. */ + while (p < rl_end && (c = rl_line_buffer[p]) && WORDDELIM (c)) + { + switch (c) + { + default: + ADVANCE_CHAR (rl_line_buffer, slen, p); + continue; /* straight back to loop, don't increment p */ + case '\\': + if (p < rl_end && rl_line_buffer[p]) + ADVANCE_CHAR (rl_line_buffer, slen, p); + break; + case '\'': + p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP); + break; + case '"': + p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP); + break; + } + + if (p < rl_end) + p++; + } + + if (rl_line_buffer[p] == 0 || p == rl_end) + { + rl_point = rl_end; + rl_ding (); + return 0; + } + + /* Now move forward until we hit a non-quoted metacharacter or EOL */ + while (p < rl_end && (c = rl_line_buffer[p]) && WORDDELIM (c) == 0) + { + switch (c) + { + default: + ADVANCE_CHAR (rl_line_buffer, slen, p); + continue; /* straight back to loop, don't increment p */ + case '\\': + if (p < rl_end && rl_line_buffer[p]) + ADVANCE_CHAR (rl_line_buffer, slen, p); + break; + case '\'': + p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP); + break; + case '"': + p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP); + break; + } + + if (p < rl_end) + p++; + } + + if (p == rl_end || rl_line_buffer[p] == 0) + { + rl_point = rl_end; + return (0); + } + + count--; + } + + rl_point = p; + return (0); +} + +static int +bash_backward_shellword (count, key) + int count, key; +{ + size_t slen; + int sindex, c, p; + DECLARE_MBSTATE; + + if (count < 0) + return (bash_forward_shellword (-count, key)); + + p = rl_point; + slen = rl_end; + + while (count) + { + if (p == 0) + { + rl_point = 0; + return 0; + } + + /* Move backward until we hit a non-metacharacter. */ + while (p > 0) + { + c = rl_line_buffer[p]; + if (WORDDELIM (c) && char_is_quoted (rl_line_buffer, p) == 0) + BACKUP_CHAR (rl_line_buffer, slen, p); + break; + } + + if (p == 0) + { + rl_point = 0; + return 0; + } + + /* Now move backward until we hit a metacharacter or BOL. */ + while (p > 0) + { + c = rl_line_buffer[p]; + if (WORDDELIM (c) && char_is_quoted (rl_line_buffer, p) == 0) + break; + BACKUP_CHAR (rl_line_buffer, slen, p); + } + + count--; + } + + rl_point = p; + return 0; +} + +static int +bash_kill_shellword (count, key) + int count, key; +{ + int p; + + if (count < 0) + return (bash_backward_kill_shellword (-count, key)); + + p = rl_point; + bash_forward_shellword (count, key); + + if (rl_point != p) + rl_kill_text (p, rl_point); + + rl_point = p; + if (rl_editing_mode == 1) /* 1 == emacs_mode */ + rl_mark = rl_point; + + return 0; +} + +static int +bash_backward_kill_shellword (count, key) + int count, key; +{ + int p; + + if (count < 0) + return (bash_kill_shellword (-count, key)); + + p = rl_point; + bash_backward_shellword (count, key); + + if (rl_point != p) + rl_kill_text (p, rl_point); + + if (rl_editing_mode == 1) /* 1 == emacs_mode */ + rl_mark = rl_point; + + return 0; +} + + +/* **************************************************************** */ +/* */ +/* How To Do Shell Completion */ +/* */ +/* **************************************************************** */ + +#define COMMAND_SEPARATORS ";|&{(`" +/* )} */ + +static int +check_redir (ti) + int ti; +{ + register int this_char, prev_char; + + /* Handle the two character tokens `>&', `<&', and `>|'. + We are not in a command position after one of these. */ + this_char = rl_line_buffer[ti]; + prev_char = rl_line_buffer[ti - 1]; + + if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) || + (this_char == '|' && prev_char == '>')) + return (1); + else if ((this_char == '{' && prev_char == '$') || /* } */ + (char_is_quoted (rl_line_buffer, ti))) + return (1); + return (0); +} + +#if defined (PROGRAMMABLE_COMPLETION) +/* + * XXX - because of the <= start test, and setting os = s+1, this can + * potentially return os > start. This is probably not what we want to + * happen, but fix later after 2.05a-release. + */ +static int +find_cmd_start (start) + int start; +{ + register int s, os; + + os = 0; + while (((s = skip_to_delim (rl_line_buffer, os, COMMAND_SEPARATORS, SD_NOJMP|SD_NOSKIPCMD)) <= start) && + rl_line_buffer[s]) + os = s+1; + return os; +} + +static int +find_cmd_end (end) + int end; +{ + register int e; + + e = skip_to_delim (rl_line_buffer, end, COMMAND_SEPARATORS, SD_NOJMP); + return e; +} + +static char * +find_cmd_name (start) + int start; +{ + char *name; + register int s, e; + + for (s = start; whitespace (rl_line_buffer[s]); s++) + ; + + /* skip until a shell break character */ + e = skip_to_delim (rl_line_buffer, s, "()<>;&| \t\n", SD_NOJMP); + + name = substring (rl_line_buffer, s, e); + + return (name); +} + +static char * +prog_complete_return (text, matchnum) + const char *text; + int matchnum; +{ + static int ind; + + if (matchnum == 0) + ind = 0; + + if (prog_complete_matches == 0 || prog_complete_matches[ind] == 0) + return (char *)NULL; + return (prog_complete_matches[ind++]); +} + +#endif /* PROGRAMMABLE_COMPLETION */ + +/* Do some completion on TEXT. The indices of TEXT in RL_LINE_BUFFER are + at START and END. Return an array of matches, or NULL if none. */ +static char ** +attempt_shell_completion (text, start, end) + const char *text; + int start, end; +{ + int in_command_position, ti, saveti, qc, dflags; + char **matches, *command_separator_chars; + + command_separator_chars = COMMAND_SEPARATORS; + matches = (char **)NULL; + rl_ignore_some_completions_function = filename_completion_ignore; + + /* Determine if this could be a command word. It is if it appears at + the start of the line (ignoring preceding whitespace), or if it + appears after a character that separates commands. It cannot be a + command word if we aren't at the top-level prompt. */ + ti = start - 1; + saveti = qc = -1; + + while ((ti > -1) && (whitespace (rl_line_buffer[ti]))) + ti--; + +#if 1 + /* If this is an open quote, maybe we're trying to complete a quoted + command name. */ + if (ti >= 0 && (rl_line_buffer[ti] == '"' || rl_line_buffer[ti] == '\'')) + { + qc = rl_line_buffer[ti]; + saveti = ti--; + while (ti > -1 && (whitespace (rl_line_buffer[ti]))) + ti--; + } +#endif + + in_command_position = 0; + if (ti < 0) + { + /* Only do command completion at the start of a line when we + are prompting at the top level. */ + if (current_prompt_string == ps1_prompt) + in_command_position++; + } + else if (member (rl_line_buffer[ti], command_separator_chars)) + { + in_command_position++; + + if (check_redir (ti) == 1) + in_command_position = 0; + } + else + { + /* This still could be in command position. It is possible + that all of the previous words on the line are variable + assignments. */ + } + + /* Check that we haven't incorrectly flagged a closed command substitution + as indicating we're in a command position. */ + if (in_command_position && ti >= 0 && rl_line_buffer[ti] == '`' && + *text != '`' && unclosed_pair (rl_line_buffer, end, "`") == 0) + in_command_position = 0; + + /* Special handling for command substitution. If *TEXT is a backquote, + it can be the start or end of an old-style command substitution, or + unmatched. If it's unmatched, both calls to unclosed_pair will + succeed. Don't bother if readline found a single quote and we are + completing on the substring. */ + if (*text == '`' && rl_completion_quote_character != '\'' && + (in_command_position || (unclosed_pair (rl_line_buffer, start, "`") && + unclosed_pair (rl_line_buffer, end, "`")))) + matches = rl_completion_matches (text, command_subst_completion_function); + +#if defined (PROGRAMMABLE_COMPLETION) + /* Attempt programmable completion. */ + if (matches == 0 && (in_command_position == 0 || text[0] == '\0') && + prog_completion_enabled && (progcomp_size () > 0) && + current_prompt_string == ps1_prompt) + { + int s, e, foundcs; + char *n; + + /* XXX - don't free the members */ + if (prog_complete_matches) + free (prog_complete_matches); + prog_complete_matches = (char **)NULL; + + s = find_cmd_start (start); + e = find_cmd_end (end); + n = find_cmd_name (s); + if (e == 0 && e == s && text[0] == '\0') + prog_complete_matches = programmable_completions ("_EmptycmD_", text, s, e, &foundcs); + else if (e > s && assignment (n, 0) == 0) + prog_complete_matches = programmable_completions (n, text, s, e, &foundcs); + else + foundcs = 0; + FREE (n); + /* XXX - if we found a COMPSPEC for the command, just return whatever + the programmable completion code returns, and disable the default + filename completion that readline will do unless the COPT_DEFAULT + option has been set with the `-o default' option to complete or + compopt. */ + if (foundcs) + { + pcomp_set_readline_variables (foundcs, 1); + /* Turn what the programmable completion code returns into what + readline wants. I should have made compute_lcd_of_matches + external... */ + matches = rl_completion_matches (text, prog_complete_return); + if ((foundcs & COPT_DEFAULT) == 0) + rl_attempted_completion_over = 1; /* no default */ + if (matches || ((foundcs & COPT_BASHDEFAULT) == 0)) + return (matches); + } + } +#endif + + if (matches == 0) + { + dflags = 0; + if (in_command_position) + dflags |= DEFCOMP_CMDPOS; + matches = bash_default_completion (text, start, end, qc, dflags); + } + + return matches; +} + +char ** +bash_default_completion (text, start, end, qc, compflags) + const char *text; + int start, end, qc, compflags; +{ + char **matches; + + matches = (char **)NULL; + + /* New posix-style command substitution or variable name? */ + if (!matches && *text == '$') + { + if (qc != '\'' && text[1] == '(') /* ) */ + matches = rl_completion_matches (text, command_subst_completion_function); + else + matches = rl_completion_matches (text, variable_completion_function); + } + + /* If the word starts in `~', and there is no slash in the word, then + try completing this word as a username. */ + if (matches ==0 && *text == '~' && mbschr (text, '/') == 0) + matches = rl_completion_matches (text, rl_username_completion_function); + + /* Another one. Why not? If the word starts in '@', then look through + the world of known hostnames for completion first. */ + if (!matches && perform_hostname_completion && *text == '@') + matches = rl_completion_matches (text, hostname_completion_function); + + /* And last, (but not least) if this word is in a command position, then + complete over possible command names, including aliases, functions, + and command names. */ + if (matches == 0 && (compflags & DEFCOMP_CMDPOS)) + { + /* If END == START and text[0] == 0, we are trying to complete an empty + command word. */ + if (no_empty_command_completion && end == start && text[0] == '\0') + { + matches = (char **)NULL; + rl_ignore_some_completions_function = bash_ignore_everything; + } + else + { +#define CMD_IS_DIR(x) (absolute_pathname(x) == 0 && absolute_program(x) == 0 && *(x) != '~' && test_for_directory (x)) + + dot_in_path = 0; + matches = rl_completion_matches (text, command_word_completion_function); + + /* If we are attempting command completion and nothing matches, we + do not want readline to perform filename completion for us. We + still want to be able to complete partial pathnames, so set the + completion ignore function to something which will remove + filenames and leave directories in the match list. */ + if (matches == (char **)NULL) + rl_ignore_some_completions_function = bash_ignore_filenames; + else if (matches[1] == 0 && CMD_IS_DIR(matches[0]) && dot_in_path == 0) + /* If we found a single match, without looking in the current + directory (because it's not in $PATH), but the found name is + also a command in the current directory, suppress appending any + terminating character, since it's ambiguous. */ + { + rl_completion_suppress_append = 1; + rl_filename_completion_desired = 0; + } + else if (matches[0] && matches[1] && STREQ (matches[0], matches[1]) && CMD_IS_DIR (matches[0])) + /* There are multiple instances of the same match (duplicate + completions haven't yet been removed). In this case, all of + the matches will be the same, and the duplicate removal code + will distill them all down to one. We turn on + rl_completion_suppress_append for the same reason as above. + Remember: we only care if there's eventually a single unique + completion. If there are multiple completions this won't + make a difference and the problem won't occur. */ + { + rl_completion_suppress_append = 1; + rl_filename_completion_desired = 0; + } + } + } + + /* This could be a globbing pattern, so try to expand it using pathname + expansion. */ + if (!matches && glob_pattern_p (text)) + { + matches = rl_completion_matches (text, glob_complete_word); + /* A glob expression that matches more than one filename is problematic. + If we match more than one filename, punt. */ + if (matches && matches[1] && rl_completion_type == TAB) + { + strvec_dispose (matches); + matches = (char **)0; + } + } + + return (matches); +} + +/* This is the function to call when the word to complete is in a position + where a command word can be found. It grovels $PATH, looking for commands + that match. It also scans aliases, function names, and the shell_builtin + table. */ +char * +command_word_completion_function (hint_text, state) + const char *hint_text; + int state; +{ + static char *hint = (char *)NULL; + static char *path = (char *)NULL; + static char *val = (char *)NULL; + static char *filename_hint = (char *)NULL; + static char *dequoted_hint = (char *)NULL; + static char *directory_part = (char *)NULL; + static char **glob_matches = (char **)NULL; + static int path_index, hint_len, dequoted_len, istate, igncase; + static int mapping_over, local_index, searching_path, hint_is_dir; + static int old_glob_ignore_case, globpat; + static SHELL_VAR **varlist = (SHELL_VAR **)NULL; +#if defined (ALIAS) + static alias_t **alias_list = (alias_t **)NULL; +#endif /* ALIAS */ + char *temp; + + /* We have to map over the possibilities for command words. If we have + no state, then make one just for that purpose. */ + if (state == 0) + { + if (dequoted_hint && dequoted_hint != hint) + free (dequoted_hint); + if (hint) + free (hint); + + mapping_over = searching_path = 0; + hint_is_dir = CMD_IS_DIR (hint_text); + val = (char *)NULL; + + temp = rl_variable_value ("completion-ignore-case"); + igncase = RL_BOOLEAN_VARIABLE_VALUE (temp); + + if (glob_matches) + { + free (glob_matches); + glob_matches = (char **)NULL; + } + + globpat = glob_pattern_p (hint_text); + + /* If this is an absolute program name, do not check it against + aliases, reserved words, functions or builtins. We must check + whether or not it is unique, and, if so, whether that filename + is executable. */ + if (globpat || absolute_program (hint_text)) + { + /* Perform tilde expansion on what's passed, so we don't end up + passing filenames with tildes directly to stat(). */ + if (*hint_text == '~') + { + hint = bash_tilde_expand (hint_text, 0); + directory_part = savestring (hint_text); + temp = strchr (directory_part, '/'); + if (temp) + *temp = 0; + else + { + free (directory_part); + directory_part = (char *)NULL; + } + } + else + hint = savestring (hint_text); + + dequoted_hint = hint; + /* If readline's completer found a quote character somewhere, but + didn't set the quote character, there must have been a quote + character embedded in the filename. It can't be at the start of + the filename, so we need to dequote the filename before we look + in the file system for it. */ + if (rl_completion_found_quote && rl_completion_quote_character == 0) + { + dequoted_hint = bash_dequote_filename (hint, 0); + free (hint); + hint = dequoted_hint; + } + dequoted_len = hint_len = strlen (hint); + + if (filename_hint) + free (filename_hint); + + filename_hint = savestring (hint); + + istate = 0; + + if (globpat) + { + mapping_over = 5; + goto globword; + } + else + { + mapping_over = 4; + goto inner; + } + } + + dequoted_hint = hint = savestring (hint_text); + dequoted_len = hint_len = strlen (hint); + + if (rl_completion_found_quote && rl_completion_quote_character == 0) + { + dequoted_hint = bash_dequote_filename (hint, 0); + dequoted_len = strlen (dequoted_hint); + } + + path = get_string_value ("PATH"); + path_index = dot_in_path = 0; + + /* Initialize the variables for each type of command word. */ + local_index = 0; + + if (varlist) + free (varlist); + + varlist = all_visible_functions (); + +#if defined (ALIAS) + if (alias_list) + free (alias_list); + + alias_list = all_aliases (); +#endif /* ALIAS */ + } + + /* mapping_over says what we are currently hacking. Note that every case + in this list must fall through when there are no more possibilities. */ + + switch (mapping_over) + { + case 0: /* Aliases come first. */ +#if defined (ALIAS) + while (alias_list && alias_list[local_index]) + { + register char *alias; + + alias = alias_list[local_index++]->name; + + if (STREQN (alias, hint, hint_len)) + return (savestring (alias)); + } +#endif /* ALIAS */ + local_index = 0; + mapping_over++; + + case 1: /* Then shell reserved words. */ + { + while (word_token_alist[local_index].word) + { + register char *reserved_word; + + reserved_word = word_token_alist[local_index++].word; + + if (STREQN (reserved_word, hint, hint_len)) + return (savestring (reserved_word)); + } + local_index = 0; + mapping_over++; + } + + case 2: /* Then function names. */ + while (varlist && varlist[local_index]) + { + register char *varname; + + varname = varlist[local_index++]->name; + + if (STREQN (varname, hint, hint_len)) + return (savestring (varname)); + } + local_index = 0; + mapping_over++; + + case 3: /* Then shell builtins. */ + for (; local_index < num_shell_builtins; local_index++) + { + /* Ignore it if it doesn't have a function pointer or if it + is not currently enabled. */ + if (!shell_builtins[local_index].function || + (shell_builtins[local_index].flags & BUILTIN_ENABLED) == 0) + continue; + + if (STREQN (shell_builtins[local_index].name, hint, hint_len)) + { + int i = local_index++; + + return (savestring (shell_builtins[i].name)); + } + } + local_index = 0; + mapping_over++; + } + +globword: + /* Limited support for completing command words with globbing chars. Only + a single match (multiple matches that end up reducing the number of + characters in the common prefix are bad) will ever be returned on + regular completion. */ + if (glob_pattern_p (hint)) + { + if (state == 0) + { + glob_ignore_case = igncase; + glob_matches = shell_glob_filename (hint); + glob_ignore_case = old_glob_ignore_case; + + if (GLOB_FAILED (glob_matches) || glob_matches == 0) + { + glob_matches = (char **)NULL; + return ((char *)NULL); + } + + local_index = 0; + + if (glob_matches[1] && rl_completion_type == TAB) /* multiple matches are bad */ + return ((char *)NULL); + } + + while (val = glob_matches[local_index++]) + { + if (executable_or_directory (val)) + { + if (*hint_text == '~') + { + temp = restore_tilde (val, directory_part); + free (val); + val = temp; + } + return (val); + } + free (val); + } + + glob_ignore_case = old_glob_ignore_case; + return ((char *)NULL); + } + + /* If the text passed is a directory in the current directory, return it + as a possible match. Executables in directories in the current + directory can be specified using relative pathnames and successfully + executed even when `.' is not in $PATH. */ + if (hint_is_dir) + { + hint_is_dir = 0; /* only return the hint text once */ + return (savestring (hint_text)); + } + + /* Repeatedly call filename_completion_function while we have + members of PATH left. Question: should we stat each file? + Answer: we call executable_file () on each file. */ + outer: + + istate = (val != (char *)NULL); + + if (istate == 0) + { + char *current_path; + + /* Get the next directory from the path. If there is none, then we + are all done. */ + if (path == 0 || path[path_index] == 0 || + (current_path = extract_colon_unit (path, &path_index)) == 0) + return ((char *)NULL); + + searching_path = 1; + if (*current_path == 0) + { + free (current_path); + current_path = savestring ("."); + } + + if (*current_path == '~') + { + char *t; + + t = bash_tilde_expand (current_path, 0); + free (current_path); + current_path = t; + } + + if (current_path[0] == '.' && current_path[1] == '\0') + dot_in_path = 1; + + if (filename_hint) + free (filename_hint); + + filename_hint = sh_makepath (current_path, hint, 0); + free (current_path); /* XXX */ + } + + inner: + val = rl_filename_completion_function (filename_hint, istate); + istate = 1; + + if (val == 0) + { + /* If the hint text is an absolute program, then don't bother + searching through PATH. */ + if (absolute_program (hint)) + return ((char *)NULL); + + goto outer; + } + else + { + int match, freetemp; + + if (absolute_program (hint)) + { + if (igncase == 0) + match = strncmp (val, hint, hint_len) == 0; + else + match = strncasecmp (val, hint, hint_len) == 0; + + /* If we performed tilde expansion, restore the original + filename. */ + if (*hint_text == '~') + temp = restore_tilde (val, directory_part); + else + temp = savestring (val); + freetemp = 1; + } + else + { + temp = strrchr (val, '/'); + + if (temp) + { + temp++; + if (igncase == 0) + freetemp = match = strncmp (temp, hint, hint_len) == 0; + else + freetemp = match = strncasecmp (temp, hint, hint_len) == 0; + if (match) + temp = savestring (temp); + } + else + freetemp = match = 0; + } + +#if 0 + /* If we have found a match, and it is an executable file or a + directory name, return it. */ + if (match && executable_or_directory (val)) +#else + /* If we have found a match, and it is an executable file, return it. + We don't return directory names when searching $PATH, since the + bash execution code won't find executables in directories which + appear in directories in $PATH when they're specified using + relative pathnames. */ + if (match && (searching_path ? executable_file (val) : executable_or_directory (val))) +#endif + { + free (val); + val = ""; /* So it won't be NULL. */ + return (temp); + } + else + { + if (freetemp) + free (temp); + free (val); + goto inner; + } + } +} + +/* Completion inside an unterminated command substitution. */ +static char * +command_subst_completion_function (text, state) + const char *text; + int state; +{ + static char **matches = (char **)NULL; + static const char *orig_start; + static char *filename_text = (char *)NULL; + static int cmd_index, start_len; + char *value; + + if (state == 0) + { + if (filename_text) + free (filename_text); + orig_start = text; + if (*text == '`') + text++; + else if (*text == '$' && text[1] == '(') /* ) */ + text += 2; + /* If the text was quoted, suppress any quote character that the + readline completion code would insert. */ + rl_completion_suppress_quote = 1; + start_len = text - orig_start; + filename_text = savestring (text); + if (matches) + free (matches); + + /* + * At this point we can entertain the idea of re-parsing + * `filename_text' into a (possibly incomplete) command name and + * arguments, and doing completion based on that. This is + * currently very rudimentary, but it is a small improvement. + */ + for (value = filename_text + strlen (filename_text) - 1; value > filename_text; value--) + if (whitespace (*value) || member (*value, COMMAND_SEPARATORS)) + break; + if (value <= filename_text) + matches = rl_completion_matches (filename_text, command_word_completion_function); + else + { + value++; + start_len += value - filename_text; + if (whitespace (value[-1])) + matches = rl_completion_matches (value, rl_filename_completion_function); + else + matches = rl_completion_matches (value, command_word_completion_function); + } + + /* If there is more than one match, rl_completion_matches has already + put the lcd in matches[0]. Skip over it. */ + cmd_index = matches && matches[0] && matches[1]; + + /* If there's a single match and it's a directory, set the append char + to the expected `/'. Otherwise, don't append anything. */ + if (matches && matches[0] && matches[1] == 0 && test_for_directory (matches[0])) + rl_completion_append_character = '/'; + else + rl_completion_suppress_append = 1; + } + + if (!matches || !matches[cmd_index]) + { + rl_filename_quoting_desired = 0; /* disable quoting */ + return ((char *)NULL); + } + else + { + value = (char *)xmalloc (1 + start_len + strlen (matches[cmd_index])); + + if (start_len == 1) + value[0] = *orig_start; + else + strncpy (value, orig_start, start_len); + + strcpy (value + start_len, matches[cmd_index]); + + cmd_index++; + return (value); + } +} + +/* Okay, now we write the entry_function for variable completion. */ +static char * +variable_completion_function (text, state) + const char *text; + int state; +{ + static char **varlist = (char **)NULL; + static int varlist_index; + static char *varname = (char *)NULL; + static int namelen; + static int first_char, first_char_loc; + + if (!state) + { + if (varname) + free (varname); + + first_char_loc = 0; + first_char = text[0]; + + if (first_char == '$') + first_char_loc++; + + if (text[first_char_loc] == '{') + first_char_loc++; + + varname = savestring (text + first_char_loc); + + namelen = strlen (varname); + if (varlist) + strvec_dispose (varlist); + + varlist = all_variables_matching_prefix (varname); + varlist_index = 0; + } + + if (!varlist || !varlist[varlist_index]) + { + return ((char *)NULL); + } + else + { + char *value; + + value = (char *)xmalloc (4 + strlen (varlist[varlist_index])); + + if (first_char_loc) + { + value[0] = first_char; + if (first_char_loc == 2) + value[1] = '{'; + } + + strcpy (value + first_char_loc, varlist[varlist_index]); + if (first_char_loc == 2) + strcat (value, "}"); + + varlist_index++; + return (value); + } +} + +/* How about a completion function for hostnames? */ +static char * +hostname_completion_function (text, state) + const char *text; + int state; +{ + static char **list = (char **)NULL; + static int list_index = 0; + static int first_char, first_char_loc; + + /* If we don't have any state, make some. */ + if (state == 0) + { + FREE (list); + + list = (char **)NULL; + + first_char_loc = 0; + first_char = *text; + + if (first_char == '@') + first_char_loc++; + + list = hostnames_matching ((char *)text+first_char_loc); + list_index = 0; + } + + if (list && list[list_index]) + { + char *t; + + t = (char *)xmalloc (2 + strlen (list[list_index])); + *t = first_char; + strcpy (t + first_char_loc, list[list_index]); + list_index++; + return (t); + } + + return ((char *)NULL); +} + +/* + * A completion function for service names from /etc/services (or wherever). + */ +char * +bash_servicename_completion_function (text, state) + const char *text; + int state; +{ +#if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GETSERVENT) + return ((char *)NULL); +#else + static char *sname = (char *)NULL; + static struct servent *srvent; + static int snamelen, firstc; + char *value; + char **alist, *aentry; + int afound; + + if (state == 0) + { + FREE (sname); + firstc = *text; + + sname = savestring (text); + snamelen = strlen (sname); + setservent (0); + } + + while (srvent = getservent ()) + { + afound = 0; + if (snamelen == 0 || (STREQN (sname, srvent->s_name, snamelen))) + break; + /* Not primary, check aliases */ + for (alist = srvent->s_aliases; *alist; alist++) + { + aentry = *alist; + if (STREQN (sname, aentry, snamelen)) + { + afound = 1; + break; + } + } + + if (afound) + break; + } + + if (srvent == 0) + { + endservent (); + return ((char *)NULL); + } + + value = afound ? savestring (aentry) : savestring (srvent->s_name); + return value; +#endif +} + +/* + * A completion function for group names from /etc/group (or wherever). + */ +char * +bash_groupname_completion_function (text, state) + const char *text; + int state; +{ +#if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GRP_H) + return ((char *)NULL); +#else + static char *gname = (char *)NULL; + static struct group *grent; + static int gnamelen; + char *value; + + if (state == 0) + { + FREE (gname); + gname = savestring (text); + gnamelen = strlen (gname); + + setgrent (); + } + + while (grent = getgrent ()) + { + if (gnamelen == 0 || (STREQN (gname, grent->gr_name, gnamelen))) + break; + } + + if (grent == 0) + { + endgrent (); + return ((char *)NULL); + } + + value = savestring (grent->gr_name); + return (value); +#endif +} + +/* Functions to perform history and alias expansions on the current line. */ + +#if defined (BANG_HISTORY) +/* Perform history expansion on the current line. If no history expansion + is done, pre_process_line() returns what it was passed, so we need to + allocate a new line here. */ +static char * +history_expand_line_internal (line) + char *line; +{ + char *new_line; + int old_verify; + + old_verify = hist_verify; + hist_verify = 0; + new_line = pre_process_line (line, 0, 0); + hist_verify = old_verify; + + return (new_line == line) ? savestring (line) : new_line; +} +#endif + +/* There was an error in expansion. Let the preprocessor print + the error here. */ +static void +cleanup_expansion_error () +{ + char *to_free; +#if defined (BANG_HISTORY) + int old_verify; + + old_verify = hist_verify; + hist_verify = 0; +#endif + + fprintf (rl_outstream, "\r\n"); + to_free = pre_process_line (rl_line_buffer, 1, 0); +#if defined (BANG_HISTORY) + hist_verify = old_verify; +#endif + if (to_free != rl_line_buffer) + FREE (to_free); + putc ('\r', rl_outstream); + rl_forced_update_display (); +} + +/* If NEW_LINE differs from what is in the readline line buffer, add an + undo record to get from the readline line buffer contents to the new + line and make NEW_LINE the current readline line. */ +static void +maybe_make_readline_line (new_line) + char *new_line; +{ + if (strcmp (new_line, rl_line_buffer) != 0) + { + rl_point = rl_end; + + rl_add_undo (UNDO_BEGIN, 0, 0, 0); + rl_delete_text (0, rl_point); + rl_point = rl_end = rl_mark = 0; + rl_insert_text (new_line); + rl_add_undo (UNDO_END, 0, 0, 0); + } +} + +/* Make NEW_LINE be the current readline line. This frees NEW_LINE. */ +static void +set_up_new_line (new_line) + char *new_line; +{ + int old_point, at_end; + + old_point = rl_point; + at_end = rl_point == rl_end; + + /* If the line was history and alias expanded, then make that + be one thing to undo. */ + maybe_make_readline_line (new_line); + free (new_line); + + /* Place rl_point where we think it should go. */ + if (at_end) + rl_point = rl_end; + else if (old_point < rl_end) + { + rl_point = old_point; + if (!whitespace (rl_line_buffer[rl_point])) + rl_forward_word (1, 0); + } +} + +#if defined (ALIAS) +/* Expand aliases in the current readline line. */ +static int +alias_expand_line (count, ignore) + int count, ignore; +{ + char *new_line; + + new_line = alias_expand (rl_line_buffer); + + if (new_line) + { + set_up_new_line (new_line); + return (0); + } + else + { + cleanup_expansion_error (); + return (1); + } +} +#endif + +#if defined (BANG_HISTORY) +/* History expand the line. */ +static int +history_expand_line (count, ignore) + int count, ignore; +{ + char *new_line; + + new_line = history_expand_line_internal (rl_line_buffer); + + if (new_line) + { + set_up_new_line (new_line); + return (0); + } + else + { + cleanup_expansion_error (); + return (1); + } +} + +/* Expand history substitutions in the current line and then insert a + space (hopefully close to where we were before). */ +static int +tcsh_magic_space (count, ignore) + int count, ignore; +{ + int dist_from_end, old_point; + + old_point = rl_point; + dist_from_end = rl_end - rl_point; + if (history_expand_line (count, ignore) == 0) + { + /* Try a simple heuristic from Stephen Gildea . + This works if all expansions were before rl_point or if no expansions + were performed. */ + rl_point = (old_point == 0) ? old_point : rl_end - dist_from_end; + rl_insert (1, ' '); + return (0); + } + else + return (1); +} +#endif /* BANG_HISTORY */ + +/* History and alias expand the line. */ +static int +history_and_alias_expand_line (count, ignore) + int count, ignore; +{ + char *new_line; + + new_line = 0; +#if defined (BANG_HISTORY) + new_line = history_expand_line_internal (rl_line_buffer); +#endif + +#if defined (ALIAS) + if (new_line) + { + char *alias_line; + + alias_line = alias_expand (new_line); + free (new_line); + new_line = alias_line; + } +#endif /* ALIAS */ + + if (new_line) + { + set_up_new_line (new_line); + return (0); + } + else + { + cleanup_expansion_error (); + return (1); + } +} + +/* History and alias expand the line, then perform the shell word + expansions by calling expand_string. This can't use set_up_new_line() + because we want the variable expansions as a separate undo'able + set of operations. */ +static int +shell_expand_line (count, ignore) + int count, ignore; +{ + char *new_line; + WORD_LIST *expanded_string; + + new_line = 0; +#if defined (BANG_HISTORY) + new_line = history_expand_line_internal (rl_line_buffer); +#endif + +#if defined (ALIAS) + if (new_line) + { + char *alias_line; + + alias_line = alias_expand (new_line); + free (new_line); + new_line = alias_line; + } +#endif /* ALIAS */ + + if (new_line) + { + int old_point = rl_point; + int at_end = rl_point == rl_end; + + /* If the line was history and alias expanded, then make that + be one thing to undo. */ + maybe_make_readline_line (new_line); + free (new_line); + + /* If there is variable expansion to perform, do that as a separate + operation to be undone. */ + new_line = savestring (rl_line_buffer); + expanded_string = expand_string (new_line, 0); + FREE (new_line); + if (expanded_string == 0) + { + new_line = (char *)xmalloc (1); + new_line[0] = '\0'; + } + else + { + new_line = string_list (expanded_string); + dispose_words (expanded_string); + } + + maybe_make_readline_line (new_line); + free (new_line); + + /* Place rl_point where we think it should go. */ + if (at_end) + rl_point = rl_end; + else if (old_point < rl_end) + { + rl_point = old_point; + if (!whitespace (rl_line_buffer[rl_point])) + rl_forward_word (1, 0); + } + return 0; + } + else + { + cleanup_expansion_error (); + return 1; + } +} + +/* If FIGNORE is set, then don't match files with the given suffixes when + completing filenames. If only one of the possibilities has an acceptable + suffix, delete the others, else just return and let the completer + signal an error. It is called by the completer when real + completions are done on filenames by the completer's internal + function, not for completion lists (M-?) and not on "other" + completion types, such as hostnames or commands. */ + +static struct ignorevar fignore = +{ + "FIGNORE", + (struct ign *)0, + 0, + (char *)0, + (sh_iv_item_func_t *) 0, +}; + +static void +_ignore_completion_names (names, name_func) + char **names; + sh_ignore_func_t *name_func; +{ + char **newnames; + int idx, nidx; + char **oldnames; + int oidx; + + /* If there is only one completion, see if it is acceptable. If it is + not, free it up. In any case, short-circuit and return. This is a + special case because names[0] is not the prefix of the list of names + if there is only one completion; it is the completion itself. */ + if (names[1] == (char *)0) + { + if (force_fignore) + if ((*name_func) (names[0]) == 0) + { + free (names[0]); + names[0] = (char *)NULL; + } + + return; + } + + /* Allocate space for array to hold list of pointers to matching + filenames. The pointers are copied back to NAMES when done. */ + for (nidx = 1; names[nidx]; nidx++) + ; + newnames = strvec_create (nidx + 1); + + if (force_fignore == 0) + { + oldnames = strvec_create (nidx - 1); + oidx = 0; + } + + newnames[0] = names[0]; + for (idx = nidx = 1; names[idx]; idx++) + { + if ((*name_func) (names[idx])) + newnames[nidx++] = names[idx]; + else if (force_fignore == 0) + oldnames[oidx++] = names[idx]; + else + free (names[idx]); + } + + newnames[nidx] = (char *)NULL; + + /* If none are acceptable then let the completer handle it. */ + if (nidx == 1) + { + if (force_fignore) + { + free (names[0]); + names[0] = (char *)NULL; + } + else + free (oldnames); + + free (newnames); + return; + } + + if (force_fignore == 0) + { + while (oidx) + free (oldnames[--oidx]); + free (oldnames); + } + + /* If only one is acceptable, copy it to names[0] and return. */ + if (nidx == 2) + { + free (names[0]); + names[0] = newnames[1]; + names[1] = (char *)NULL; + free (newnames); + return; + } + + /* Copy the acceptable names back to NAMES, set the new array end, + and return. */ + for (nidx = 1; newnames[nidx]; nidx++) + names[nidx] = newnames[nidx]; + names[nidx] = (char *)NULL; + free (newnames); +} + +static int +name_is_acceptable (name) + const char *name; +{ + struct ign *p; + int nlen; + + for (nlen = strlen (name), p = fignore.ignores; p->val; p++) + { + if (nlen > p->len && p->len > 0 && STREQ (p->val, &name[nlen - p->len])) + return (0); + } + + return (1); +} + +#if 0 +static int +ignore_dot_names (name) + char *name; +{ + return (name[0] != '.'); +} +#endif + +static int +filename_completion_ignore (names) + char **names; +{ +#if 0 + if (glob_dot_filenames == 0) + _ignore_completion_names (names, ignore_dot_names); +#endif + + setup_ignore_patterns (&fignore); + + if (fignore.num_ignores == 0) + return 0; + + _ignore_completion_names (names, name_is_acceptable); + + return 0; +} + +/* Return 1 if NAME is a directory. NAME undergoes tilde expansion. */ +static int +test_for_directory (name) + const char *name; +{ + char *fn; + int r; + + fn = bash_tilde_expand (name, 0); + r = file_isdir (fn); + free (fn); + + return (r); +} + +/* Remove files from NAMES, leaving directories. */ +static int +bash_ignore_filenames (names) + char **names; +{ + _ignore_completion_names (names, test_for_directory); + return 0; +} + +static int +return_zero (name) + const char *name; +{ + return 0; +} + +static int +bash_ignore_everything (names) + char **names; +{ + _ignore_completion_names (names, return_zero); + return 0; +} + +/* Replace a tilde-prefix in VAL with a `~', assuming the user typed it. VAL + is an expanded filename. DIRECTORY_PART is the tilde-prefix portion + of the un-tilde-expanded version of VAL (what the user typed). */ +static char * +restore_tilde (val, directory_part) + char *val, *directory_part; +{ + int l, vl, dl2, xl; + char *dh2, *expdir, *ret; + + vl = strlen (val); + + /* We need to duplicate the expansions readline performs on the directory + portion before passing it to our completion function. */ + dh2 = directory_part ? bash_dequote_filename (directory_part, 0) : 0; + bash_directory_expansion (&dh2); + dl2 = strlen (dh2); + + expdir = bash_tilde_expand (directory_part, 0); + xl = strlen (expdir); + free (expdir); + + /* + dh2 = unexpanded but dequoted tilde-prefix + dl2 = length of tilde-prefix + expdir = tilde-expanded tilde-prefix + xl = length of expanded tilde-prefix + l = length of remainder after tilde-prefix + */ + l = (vl - xl) + 1; + + ret = (char *)xmalloc (dl2 + 2 + l); + strcpy (ret, dh2); + strcpy (ret + dl2, val + xl); + + free (dh2); + return (ret); +} + +/* Simulate the expansions that will be performed by + rl_filename_completion_function. This must be called with the address of + a pointer to malloc'd memory. */ +static void +bash_directory_expansion (dirname) + char **dirname; +{ + char *d, *nd; + + d = savestring (*dirname); + + if (rl_directory_rewrite_hook) + (*rl_directory_rewrite_hook) (&d); + + if (rl_directory_completion_hook && (*rl_directory_completion_hook) (&d)) + { + free (*dirname); + *dirname = d; + } + else if (rl_completion_found_quote) + { + nd = bash_dequote_filename (d, rl_completion_quote_character); + free (*dirname); + free (d); + *dirname = nd; + } +} + +/* If necessary, rewrite directory entry */ +static char * +bash_filename_rewrite_hook (fname, fnlen) + char *fname; + int fnlen; +{ + char *conv; + + conv = fnx_fromfs (fname, fnlen); + if (conv != fname) + conv = savestring (conv); + return conv; +} + +/* Handle symbolic link references and other directory name + expansions while hacking completion. */ +static int +bash_directory_completion_hook (dirname) + char **dirname; +{ + char *local_dirname, *new_dirname, *t; + int return_value, should_expand_dirname; + WORD_LIST *wl; + struct stat sb; + + return_value = should_expand_dirname = 0; + local_dirname = *dirname; + + if (mbschr (local_dirname, '$')) + should_expand_dirname = 1; + else + { + t = mbschr (local_dirname, '`'); + if (t && unclosed_pair (local_dirname, strlen (local_dirname), "`") == 0) + should_expand_dirname = 1; + } + +#if defined (HAVE_LSTAT) + if (should_expand_dirname && lstat (local_dirname, &sb) == 0) +#else + if (should_expand_dirname && stat (local_dirname, &sb) == 0) +#endif + should_expand_dirname = 0; + + if (should_expand_dirname) + { + new_dirname = savestring (local_dirname); + wl = expand_prompt_string (new_dirname, 0, W_NOCOMSUB); /* does the right thing */ + if (wl) + { + *dirname = string_list (wl); + /* Tell the completer to replace the directory name only if we + actually expanded something. */ + return_value = STREQ (local_dirname, *dirname) == 0; + free (local_dirname); + free (new_dirname); + dispose_words (wl); + local_dirname = *dirname; + } + else + { + free (new_dirname); + free (local_dirname); + *dirname = (char *)xmalloc (1); + **dirname = '\0'; + return 1; + } + } + else + { + /* Dequote the filename even if we don't expand it. */ + new_dirname = bash_dequote_filename (local_dirname, rl_completion_quote_character); + free (local_dirname); + local_dirname = *dirname = new_dirname; + } + + if (no_symbolic_links == 0 && (local_dirname[0] != '.' || local_dirname[1])) + { + char *temp1, *temp2; + int len1, len2; + + t = get_working_directory ("symlink-hook"); + temp1 = make_absolute (local_dirname, t); + free (t); + temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + + /* Try spelling correction if initial canonicalization fails. */ + if (temp2 == 0 && dircomplete_spelling) + { + temp2 = dirspell (temp1); + if (temp2) + { + free (temp1); + temp1 = temp2; + temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + return_value = temp2 != 0; + } + } + /* If we can't canonicalize, bail. */ + if (temp2 == 0) + { + free (temp1); + return 1; + } + len1 = strlen (temp1); + if (temp1[len1 - 1] == '/') + { + len2 = strlen (temp2); + if (len2 > 2) /* don't append `/' to `/' or `//' */ + { + temp2 = (char *)xrealloc (temp2, len2 + 2); + temp2[len2] = '/'; + temp2[len2 + 1] = '\0'; + } + } + free (local_dirname); + *dirname = temp2; + free (temp1); + } + return (return_value); +} + +static char **history_completion_array = (char **)NULL; +static int harry_size; +static int harry_len; + +static void +build_history_completion_array () +{ + register int i, j; + HIST_ENTRY **hlist; + char **tokens; + + /* First, clear out the current dynamic history completion list. */ + if (harry_size) + { + strvec_dispose (history_completion_array); + history_completion_array = (char **)NULL; + harry_size = 0; + harry_len = 0; + } + + /* Next, grovel each line of history, making each shell-sized token + a separate entry in the history_completion_array. */ + hlist = history_list (); + + if (hlist) + { + for (i = 0; hlist[i]; i++) + ; + for ( --i; i >= 0; i--) + { + /* Separate each token, and place into an array. */ + tokens = history_tokenize (hlist[i]->line); + + for (j = 0; tokens && tokens[j]; j++) + { + if (harry_len + 2 > harry_size) + history_completion_array = strvec_resize (history_completion_array, harry_size += 10); + + history_completion_array[harry_len++] = tokens[j]; + history_completion_array[harry_len] = (char *)NULL; + } + free (tokens); + } + + /* Sort the complete list of tokens. */ + if (dabbrev_expand_active == 0) + qsort (history_completion_array, harry_len, sizeof (char *), (QSFUNC *)strvec_strcmp); + } +} + +static char * +history_completion_generator (hint_text, state) + const char *hint_text; + int state; +{ + static int local_index, len; + static const char *text; + + /* If this is the first call to the generator, then initialize the + list of strings to complete over. */ + if (state == 0) + { + if (dabbrev_expand_active) /* This is kind of messy */ + rl_completion_suppress_append = 1; + local_index = 0; + build_history_completion_array (); + text = hint_text; + len = strlen (text); + } + + while (history_completion_array && history_completion_array[local_index]) + { + if (strncmp (text, history_completion_array[local_index++], len) == 0) + return (savestring (history_completion_array[local_index - 1])); + } + return ((char *)NULL); +} + +static int +dynamic_complete_history (count, key) + int count, key; +{ + int r; + rl_compentry_func_t *orig_func; + rl_completion_func_t *orig_attempt_func; + + orig_func = rl_completion_entry_function; + orig_attempt_func = rl_attempted_completion_function; + + rl_completion_entry_function = history_completion_generator; + rl_attempted_completion_function = (rl_completion_func_t *)NULL; + + /* XXX - use rl_completion_mode here? */ + if (rl_last_func == dynamic_complete_history) + r = rl_complete_internal ('?'); + else + r = rl_complete_internal (TAB); + + rl_completion_entry_function = orig_func; + rl_attempted_completion_function = orig_attempt_func; + return r; +} + +static int +bash_dabbrev_expand (count, key) + int count, key; +{ + int r, orig_suppress, orig_sort; + rl_compentry_func_t *orig_func; + rl_completion_func_t *orig_attempt_func; + + orig_func = rl_menu_completion_entry_function; + orig_attempt_func = rl_attempted_completion_function; + orig_suppress = rl_completion_suppress_append; + orig_sort = rl_sort_completion_matches; + + rl_menu_completion_entry_function = history_completion_generator; + rl_attempted_completion_function = (rl_completion_func_t *)NULL; + rl_filename_completion_desired = 0; + rl_completion_suppress_append = 1; + rl_sort_completion_matches = 0; + + /* XXX - use rl_completion_mode here? */ + dabbrev_expand_active = 1; + if (rl_last_func == bash_dabbrev_expand) + rl_last_func = rl_menu_complete; + r = rl_menu_complete (count, key); + dabbrev_expand_active = 0; + + rl_last_func = bash_dabbrev_expand; + rl_menu_completion_entry_function = orig_func; + rl_attempted_completion_function = orig_attempt_func; + rl_completion_suppress_append = orig_suppress; + rl_sort_completion_matches = orig_sort; + + return r; +} + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) +static int +bash_complete_username (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_username_internal (rl_completion_mode (bash_complete_username)); +} + +static int +bash_possible_username_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_username_internal ('?'); +} + +static int +bash_complete_username_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, rl_username_completion_function); +} + +static int +bash_complete_filename (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_filename_internal (rl_completion_mode (bash_complete_filename)); +} + +static int +bash_possible_filename_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_filename_internal ('?'); +} + +static int +bash_complete_filename_internal (what_to_do) + int what_to_do; +{ + rl_compentry_func_t *orig_func; + rl_completion_func_t *orig_attempt_func; + rl_icppfunc_t *orig_dir_func; + /*const*/ char *orig_rl_completer_word_break_characters; + int r; + + orig_func = rl_completion_entry_function; + orig_attempt_func = rl_attempted_completion_function; + orig_dir_func = rl_directory_completion_hook; + orig_rl_completer_word_break_characters = rl_completer_word_break_characters; + rl_completion_entry_function = rl_filename_completion_function; + rl_attempted_completion_function = (rl_completion_func_t *)NULL; + rl_directory_completion_hook = (rl_icppfunc_t *)NULL; + rl_completer_word_break_characters = " \t\n\"\'"; + + r = rl_complete_internal (what_to_do); + + rl_completion_entry_function = orig_func; + rl_attempted_completion_function = orig_attempt_func; + rl_directory_completion_hook = orig_dir_func; + rl_completer_word_break_characters = orig_rl_completer_word_break_characters; + + return r; +} + +static int +bash_complete_hostname (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_hostname_internal (rl_completion_mode (bash_complete_hostname)); +} + +static int +bash_possible_hostname_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_hostname_internal ('?'); +} + +static int +bash_complete_variable (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_variable_internal (rl_completion_mode (bash_complete_variable)); +} + +static int +bash_possible_variable_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_variable_internal ('?'); +} + +static int +bash_complete_command (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_command_internal (rl_completion_mode (bash_complete_command)); +} + +static int +bash_possible_command_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_command_internal ('?'); +} + +static int +bash_complete_hostname_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, hostname_completion_function); +} + +static int +bash_complete_variable_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, variable_completion_function); +} + +static int +bash_complete_command_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, command_word_completion_function); +} + +static char *globtext; +static char *globorig; + +static char * +glob_complete_word (text, state) + const char *text; + int state; +{ + static char **matches = (char **)NULL; + static int ind; + int glen; + char *ret, *ttext; + + if (state == 0) + { + rl_filename_completion_desired = 1; + FREE (matches); + if (globorig != globtext) + FREE (globorig); + FREE (globtext); + + ttext = bash_tilde_expand (text, 0); + + if (rl_explicit_arg) + { + globorig = savestring (ttext); + glen = strlen (ttext); + globtext = (char *)xmalloc (glen + 2); + strcpy (globtext, ttext); + globtext[glen] = '*'; + globtext[glen+1] = '\0'; + } + else + globtext = globorig = savestring (ttext); + + if (ttext != text) + free (ttext); + + matches = shell_glob_filename (globtext); + if (GLOB_FAILED (matches)) + matches = (char **)NULL; + ind = 0; + } + + ret = matches ? matches[ind] : (char *)NULL; + ind++; + return ret; +} + +static int +bash_glob_completion_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, glob_complete_word); +} + +/* A special quoting function so we don't end up quoting globbing characters + in the word if there are no matches or multiple matches. */ +static char * +bash_glob_quote_filename (s, rtype, qcp) + char *s; + int rtype; + char *qcp; +{ + if (globorig && qcp && *qcp == '\0' && STREQ (s, globorig)) + return (savestring (s)); + else + return (bash_quote_filename (s, rtype, qcp)); +} + +static int +bash_glob_complete_word (count, key) + int count, key; +{ + int r; + rl_quote_func_t *orig_quoting_function; + + if (rl_editing_mode == EMACS_EDITING_MODE) + rl_explicit_arg = 1; /* force `*' append */ + orig_quoting_function = rl_filename_quoting_function; + rl_filename_quoting_function = bash_glob_quote_filename; + + r = bash_glob_completion_internal (rl_completion_mode (bash_glob_complete_word)); + + rl_filename_quoting_function = orig_quoting_function; + return r; +} + +static int +bash_glob_expand_word (count, key) + int count, key; +{ + return bash_glob_completion_internal ('*'); +} + +static int +bash_glob_list_expansions (count, key) + int count, key; +{ + return bash_glob_completion_internal ('?'); +} + +static int +bash_specific_completion (what_to_do, generator) + int what_to_do; + rl_compentry_func_t *generator; +{ + rl_compentry_func_t *orig_func; + rl_completion_func_t *orig_attempt_func; + int r; + + orig_func = rl_completion_entry_function; + orig_attempt_func = rl_attempted_completion_function; + rl_completion_entry_function = generator; + rl_attempted_completion_function = NULL; + + r = rl_complete_internal (what_to_do); + + rl_completion_entry_function = orig_func; + rl_attempted_completion_function = orig_attempt_func; + + return r; +} + +#endif /* SPECIFIC_COMPLETION_FUNCTIONS */ + +#if defined (VI_MODE) +/* Completion, from vi mode's point of view. This is a modified version of + rl_vi_complete which uses the bash globbing code to implement what POSIX + specifies, which is to append a `*' and attempt filename generation (which + has the side effect of expanding any globbing characters in the word). */ +static int +bash_vi_complete (count, key) + int count, key; +{ +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) + int p, r; + char *t; + + if ((rl_point < rl_end) && (!whitespace (rl_line_buffer[rl_point]))) + { + if (!whitespace (rl_line_buffer[rl_point + 1])) + rl_vi_end_word (1, 'E'); + rl_point++; + } + + /* Find boundaries of current word, according to vi definition of a + `bigword'. */ + t = 0; + if (rl_point > 0) + { + p = rl_point; + rl_vi_bWord (1, 'B'); + r = rl_point; + rl_point = p; + p = r; + + t = substring (rl_line_buffer, p, rl_point); + } + + if (t && glob_pattern_p (t) == 0) + rl_explicit_arg = 1; /* XXX - force glob_complete_word to append `*' */ + FREE (t); + + if (key == '*') /* Expansion and replacement. */ + r = bash_glob_expand_word (count, key); + else if (key == '=') /* List possible completions. */ + r = bash_glob_list_expansions (count, key); + else if (key == '\\') /* Standard completion */ + r = bash_glob_complete_word (count, key); + else + r = rl_complete (0, key); + + if (key == '*' || key == '\\') + rl_vi_start_inserting (key, 1, 1); + + return (r); +#else + return rl_vi_complete (count, key); +#endif /* !SPECIFIC_COMPLETION_FUNCTIONS */ +} +#endif /* VI_MODE */ + +/* Filename quoting for completion. */ +/* A function to strip unquoted quote characters (single quotes, double + quotes, and backslashes). It allows single quotes to appear + within double quotes, and vice versa. It should be smarter. */ +static char * +bash_dequote_filename (text, quote_char) + char *text; + int quote_char; +{ + char *ret, *p, *r; + int l, quoted; + + l = strlen (text); + ret = (char *)xmalloc (l + 1); + for (quoted = quote_char, p = text, r = ret; p && *p; p++) + { + /* Allow backslash-escaped characters to pass through unscathed. */ + if (*p == '\\') + { + /* Backslashes are preserved within single quotes. */ + if (quoted == '\'') + *r++ = *p; + /* Backslashes are preserved within double quotes unless the + character is one that is defined to be escaped */ + else if (quoted == '"' && ((sh_syntaxtab[p[1]] & CBSDQUOTE) == 0)) + *r++ = *p; + + *r++ = *++p; + if (*p == '\0') + return ret; /* XXX - was break; */ + continue; + } + /* Close quote. */ + if (quoted && *p == quoted) + { + quoted = 0; + continue; + } + /* Open quote. */ + if (quoted == 0 && (*p == '\'' || *p == '"')) + { + quoted = *p; + continue; + } + *r++ = *p; + } + *r = '\0'; + return ret; +} + +/* Quote characters that the readline completion code would treat as + word break characters with backslashes. Pass backslash-quoted + characters through without examination. */ +static char * +quote_word_break_chars (text) + char *text; +{ + char *ret, *r, *s; + int l; + + l = strlen (text); + ret = (char *)xmalloc ((2 * l) + 1); + for (s = text, r = ret; *s; s++) + { + /* Pass backslash-quoted characters through, including the backslash. */ + if (*s == '\\') + { + *r++ = '\\'; + *r++ = *++s; + if (*s == '\0') + break; + continue; + } + /* OK, we have an unquoted character. Check its presence in + rl_completer_word_break_characters. */ + if (mbschr (rl_completer_word_break_characters, *s)) + *r++ = '\\'; + /* XXX -- check for standalone tildes here and backslash-quote them */ + if (s == text && *s == '~' && file_exists (text)) + *r++ = '\\'; + *r++ = *s; + } + *r = '\0'; + return ret; +} + +/* Quote a filename using double quotes, single quotes, or backslashes + depending on the value of completion_quoting_style. If we're + completing using backslashes, we need to quote some additional + characters (those that readline treats as word breaks), so we call + quote_word_break_chars on the result. This returns newly-allocated + memory. */ +static char * +bash_quote_filename (s, rtype, qcp) + char *s; + int rtype; + char *qcp; +{ + char *rtext, *mtext, *ret; + int rlen, cs; + + rtext = (char *)NULL; + + /* If RTYPE == MULT_MATCH, it means that there is + more than one match. In this case, we do not add + the closing quote or attempt to perform tilde + expansion. If RTYPE == SINGLE_MATCH, we try + to perform tilde expansion, because single and double + quotes inhibit tilde expansion by the shell. */ + + cs = completion_quoting_style; + /* Might need to modify the default completion style based on *qcp, + since it's set to any user-provided opening quote. We also change + to single-quoting if there is no user-provided opening quote and + the word being completed contains newlines, since those are not + quoted correctly using backslashes (a backslash-newline pair is + special to the shell parser). */ + if (*qcp == '\0' && cs == COMPLETE_BSQUOTE && mbschr (s, '\n')) + cs = COMPLETE_SQUOTE; + else if (*qcp == '"') + cs = COMPLETE_DQUOTE; + else if (*qcp == '\'') + cs = COMPLETE_SQUOTE; +#if defined (BANG_HISTORY) + else if (*qcp == '\0' && history_expansion && cs == COMPLETE_DQUOTE && + history_expansion_inhibited == 0 && mbschr (s, '!')) + cs = COMPLETE_BSQUOTE; + + if (*qcp == '"' && history_expansion && cs == COMPLETE_DQUOTE && + history_expansion_inhibited == 0 && mbschr (s, '!')) + { + cs = COMPLETE_BSQUOTE; + *qcp = '\0'; + } +#endif + + /* Don't tilde-expand backslash-quoted filenames, since only single and + double quotes inhibit tilde expansion. */ + mtext = s; + if (mtext[0] == '~' && rtype == SINGLE_MATCH && cs != COMPLETE_BSQUOTE) + mtext = bash_tilde_expand (s, 0); + + switch (cs) + { + case COMPLETE_DQUOTE: + rtext = sh_double_quote (mtext); + break; + case COMPLETE_SQUOTE: + rtext = sh_single_quote (mtext); + break; + case COMPLETE_BSQUOTE: + rtext = sh_backslash_quote (mtext); + break; + } + + if (mtext != s) + free (mtext); + + /* We may need to quote additional characters: those that readline treats + as word breaks that are not quoted by backslash_quote. */ + if (rtext && cs == COMPLETE_BSQUOTE) + { + mtext = quote_word_break_chars (rtext); + free (rtext); + rtext = mtext; + } + + /* Leave the opening quote intact. The readline completion code takes + care of avoiding doubled opening quotes. */ + rlen = strlen (rtext); + ret = (char *)xmalloc (rlen + 1); + strcpy (ret, rtext); + + /* If there are multiple matches, cut off the closing quote. */ + if (rtype == MULT_MATCH && cs != COMPLETE_BSQUOTE) + ret[rlen - 1] = '\0'; + free (rtext); + return ret; +} + +/* Support for binding readline key sequences to Unix commands. */ +static Keymap cmd_xmap; + +static int +putx(c) + int c; +{ + return (putc (c, rl_outstream)); +} + +static int +bash_execute_unix_command (count, key) + int count; /* ignored */ + int key; +{ + Keymap ckmap; /* current keymap */ + Keymap xkmap; /* unix command executing keymap */ + register int i, r; + intmax_t mi; + sh_parser_state_t ps; + char *cmd, *value, *l, *l1, *ce; + SHELL_VAR *v; + char ibuf[INT_STRLEN_BOUND(int) + 1]; + + /* First, we need to find the right command to execute. This is tricky, + because we might have already indirected into another keymap. */ + ckmap = rl_get_keymap (); + if (ckmap != rl_executing_keymap) + { + /* bogus. we have to search. only handle one level of indirection. */ + for (i = 0; i < KEYMAP_SIZE; i++) + { + if (ckmap[i].type == ISKMAP && (Keymap)ckmap[i].function == rl_executing_keymap) + break; + } + if (i < KEYMAP_SIZE) + xkmap = (Keymap)cmd_xmap[i].function; + else + { + rl_crlf (); + internal_error (_("bash_execute_unix_command: cannot find keymap for command")); + rl_forced_update_display (); + return 1; + } + } + else + xkmap = cmd_xmap; + + cmd = (char *)xkmap[key].function; + + if (cmd == 0) + { + rl_ding (); + return 1; + } + + ce = rl_get_termcap ("ce"); + if (ce) /* clear current line */ + { + fprintf (rl_outstream, "\r"); + tputs (ce, 1, putx); + fflush (rl_outstream); + } + else + rl_crlf (); /* move to a new line */ + + v = bind_variable ("READLINE_LINE", rl_line_buffer, 0); + if (v) + VSETATTR (v, att_exported); + l = v ? value_cell (v) : 0; + value = inttostr (rl_point, ibuf, sizeof (ibuf)); + v = bind_int_variable ("READLINE_POINT", value); + if (v) + VSETATTR (v, att_exported); + array_needs_making = 1; + + save_parser_state (&ps); + r = parse_and_execute (cmd, "bash_execute_unix_command", SEVAL_NOHIST|SEVAL_NOFREE); + restore_parser_state (&ps); + + v = find_variable ("READLINE_LINE"); + l1 = v ? value_cell (v) : 0; + if (l1 != l) + maybe_make_readline_line (value_cell (v)); + v = find_variable ("READLINE_POINT"); + if (v && legal_number (value_cell (v), &mi)) + { + i = mi; + if (i != rl_point) + { + rl_point = i; + if (rl_point > rl_end) + rl_point = rl_end; + else if (rl_point < 0) + rl_point = 0; + } + } + + unbind_variable ("READLINE_LINE"); + unbind_variable ("READLINE_POINT"); + array_needs_making = 1; + + /* and restore the readline buffer and display after command execution. */ + rl_forced_update_display (); + return 0; +} + +static void +init_unix_command_map () +{ + cmd_xmap = rl_make_bare_keymap (); +} + +static int +isolate_sequence (string, ind, need_dquote, startp) + char *string; + int ind, need_dquote, *startp; +{ + register int i; + int c, passc, delim; + + for (i = ind; string[i] && whitespace (string[i]); i++) + ; + /* NEED_DQUOTE means that the first non-white character *must* be `"'. */ + if (need_dquote && string[i] != '"') + { + builtin_error (_("%s: first non-whitespace character is not `\"'"), string); + return -1; + } + + /* We can have delimited strings even if NEED_DQUOTE == 0, like the command + string to bind the key sequence to. */ + delim = (string[i] == '"' || string[i] == '\'') ? string[i] : 0; + + if (startp) + *startp = delim ? ++i : i; + + for (passc = 0; c = string[i]; i++) + { + if (passc) + { + passc = 0; + continue; + } + if (c == '\\') + { + passc++; + continue; + } + if (c == delim) + break; + } + + if (delim && string[i] != delim) + { + builtin_error (_("no closing `%c' in %s"), delim, string); + return -1; + } + + return i; +} + +int +bind_keyseq_to_unix_command (line) + char *line; +{ + Keymap kmap; + char *kseq, *value; + int i, kstart; + + if (cmd_xmap == 0) + init_unix_command_map (); + + kmap = rl_get_keymap (); + + /* We duplicate some of the work done by rl_parse_and_bind here, but + this code only has to handle `"keyseq": ["]command["]' and can + generate an error for anything else. */ + i = isolate_sequence (line, 0, 1, &kstart); + if (i < 0) + return -1; + + /* Create the key sequence string to pass to rl_generic_bind */ + kseq = substring (line, kstart, i); + + for ( ; line[i] && line[i] != ':'; i++) + ; + if (line[i] != ':') + { + builtin_error (_("%s: missing colon separator"), line); + return -1; + } + + i = isolate_sequence (line, i + 1, 0, &kstart); + if (i < 0) + return -1; + + /* Create the value string containing the command to execute. */ + value = substring (line, kstart, i); + + /* Save the command to execute and the key sequence in the CMD_XMAP */ + rl_generic_bind (ISMACR, kseq, value, cmd_xmap); + + /* and bind the key sequence in the current keymap to a function that + understands how to execute from CMD_XMAP */ + rl_bind_keyseq_in_map (kseq, bash_execute_unix_command, kmap); + + return 0; +} + +/* Used by the programmable completion code. Complete TEXT as a filename, + but return only directories as matches. Dequotes the filename before + attempting to find matches. */ +char ** +bash_directory_completion_matches (text) + const char *text; +{ + char **m1; + char *dfn; + int qc; + + qc = rl_dispatching ? rl_completion_quote_character : 0; + dfn = bash_dequote_filename ((char *)text, qc); + m1 = rl_completion_matches (dfn, rl_filename_completion_function); + free (dfn); + + if (m1 == 0 || m1[0] == 0) + return m1; + /* We don't bother recomputing the lcd of the matches, because it will just + get thrown away by the programmable completion code and recomputed + later. */ + (void)bash_ignore_filenames (m1); + return m1; +} + +char * +bash_dequote_text (text) + const char *text; +{ + char *dtxt; + int qc; + + qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0; + dtxt = bash_dequote_filename ((char *)text, qc); + return (dtxt); +} +#endif /* READLINE */ diff --git a/builtins/setattr.def b/builtins/setattr.def index 8b4cdf765..f549d05f9 100644 --- a/builtins/setattr.def +++ b/builtins/setattr.def @@ -433,11 +433,11 @@ show_var_attributes (var, pattr, nodefs) printf ("%s\n", var->name); else if (function_p (var)) printf ("%s\n", named_function_string (var->name, function_cell (var), FUNC_MULTILINE|FUNC_EXTERNAL)); - else if (invisible_p (var)) + else if (invisible_p (var) || var_isset (var) == 0) printf ("%s\n", var->name); else { - x = sh_double_quote (var_isset (var) ? value_cell (var) : ""); + x = sh_double_quote (value_cell (var)); printf ("%s=%s\n", var->name, x); free (x); } diff --git a/builtins/setattr.def~ b/builtins/setattr.def~ new file mode 100644 index 000000000..29a835e6e --- /dev/null +++ b/builtins/setattr.def~ @@ -0,0 +1,514 @@ +This file is setattr.def, from which is created setattr.c. +It implements the builtins "export" and "readonly", 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 setattr.c + +#include + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include "../bashansi.h" +#include "../bashintl.h" + +#include "../shell.h" +#include "common.h" +#include "bashgetopt.h" + +extern int posixly_correct; +extern int array_needs_making; +extern char *this_command_name; +extern sh_builtin_func_t *this_shell_builtin; + +#ifdef ARRAY_VARS +extern int declare_builtin __P((WORD_LIST *)); +#endif + +#define READONLY_OR_EXPORT \ + (this_shell_builtin == readonly_builtin || this_shell_builtin == export_builtin) + +$BUILTIN export +$FUNCTION export_builtin +$SHORT_DOC export [-fn] [name[=value] ...] or export -p +Set export attribute for shell variables. + +Marks each NAME for automatic export to the environment of subsequently +executed commands. If VALUE is supplied, assign VALUE before exporting. + +Options: + -f refer to shell functions + -n remove the export property from each NAME + -p display a list of all exported variables and functions + +An argument of `--' disables further option processing. + +Exit Status: +Returns success unless an invalid option is given or NAME is invalid. +$END + +/* For each variable name in LIST, make that variable appear in the + environment passed to simple commands. If there is no LIST, then + print all such variables. An argument of `-n' says to remove the + exported attribute from variables named in LIST. An argument of + -f indicates that the names present in LIST refer to functions. */ +int +export_builtin (list) + register WORD_LIST *list; +{ + return (set_or_show_attributes (list, att_exported, 0)); +} + +$BUILTIN readonly +$FUNCTION readonly_builtin +$SHORT_DOC readonly [-af] [name[=value] ...] or readonly -p +Mark shell variables as unchangeable. + +Mark each NAME as read-only; the values of these NAMEs may not be +changed by subsequent assignment. If VALUE is supplied, assign VALUE +before marking as read-only. + +Options: + -a refer to indexed array variables + -A refer to associative array variables + -f refer to shell functions + -p display a list of all readonly variables and functions + +An argument of `--' disables further option processing. + +Exit Status: +Returns success unless an invalid option is given or NAME is invalid. +$END + +/* For each variable name in LIST, make that variable readonly. Given an + empty LIST, print out all existing readonly variables. */ +int +readonly_builtin (list) + register WORD_LIST *list; +{ + return (set_or_show_attributes (list, att_readonly, 0)); +} + +#if defined (ARRAY_VARS) +# define ATTROPTS "aAfnp" +#else +# define ATTROPTS "fnp" +#endif + +/* For each variable name in LIST, make that variable have the specified + ATTRIBUTE. An arg of `-n' says to remove the attribute from the the + remaining names in LIST (doesn't work for readonly). */ +int +set_or_show_attributes (list, attribute, nodefs) + register WORD_LIST *list; + int attribute, nodefs; +{ + register SHELL_VAR *var; + int assign, undo, any_failed, assign_error, opt; + int functions_only, arrays_only, assoc_only; + int aflags; + char *name; +#if defined (ARRAY_VARS) + WORD_LIST *nlist, *tlist; + WORD_DESC *w; +#endif + + functions_only = arrays_only = assoc_only = 0; + undo = any_failed = assign_error = 0; + /* Read arguments from the front of the list. */ + reset_internal_getopt (); + while ((opt = internal_getopt (list, ATTROPTS)) != -1) + { + switch (opt) + { + case 'n': + undo = 1; + break; + case 'f': + functions_only = 1; + break; +#if defined (ARRAY_VARS) + case 'a': + arrays_only = 1; + break; + case 'A': + assoc_only = 1; + break; +#endif + case 'p': + break; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + if (list) + { + if (attribute & att_exported) + array_needs_making = 1; + + /* Cannot undo readonly status, silently disallowed. */ + if (undo && (attribute & att_readonly)) + attribute &= ~att_readonly; + + while (list) + { + name = list->word->word; + + if (functions_only) /* xxx -f name */ + { + var = find_function (name); + if (var == 0) + { + builtin_error (_("%s: not a function"), name); + any_failed++; + } + else + SETVARATTR (var, attribute, undo); + + list = list->next; + continue; + } + + /* xxx [-np] name[=value] */ + assign = assignment (name, 0); + + aflags = 0; + if (assign) + { + name[assign] = '\0'; + if (name[assign - 1] == '+') + { + aflags |= ASS_APPEND; + name[assign - 1] = '\0'; + } + } + + if (legal_identifier (name) == 0) + { + sh_invalidid (name); + if (assign) + assign_error++; + else + any_failed++; + list = list->next; + continue; + } + + if (assign) /* xxx [-np] name=value */ + { + name[assign] = '='; + if (aflags & ASS_APPEND) + name[assign - 1] = '+'; +#if defined (ARRAY_VARS) + /* Let's try something here. Turn readonly -a xxx=yyy into + declare -ra xxx=yyy and see what that gets us. */ + if (arrays_only || assoc_only) + { + tlist = list->next; + list->next = (WORD_LIST *)NULL; + w = arrays_only ? make_word ("-ra") : make_word ("-rA"); + nlist = make_word_list (w, list); + opt = declare_builtin (nlist); + if (opt != EXECUTION_SUCCESS) + assign_error++; + list->next = tlist; + dispose_word (w); + free (nlist); + } + else +#endif + /* This word has already been expanded once with command + and parameter expansion. Call do_assignment_no_expand (), + which does not do command or parameter substitution. If + the assignment is not performed correctly, flag an error. */ + if (do_assignment_no_expand (name) == 0) + assign_error++; + name[assign] = '\0'; + if (aflags & ASS_APPEND) + name[assign - 1] = '\0'; + } + + set_var_attribute (name, attribute, undo); + list = list->next; + } + } + else + { + SHELL_VAR **variable_list; + register int i; + + if ((attribute & att_function) || functions_only) + { + variable_list = all_shell_functions (); + if (attribute != att_function) + attribute &= ~att_function; /* so declare -xf works, for example */ + } + else + variable_list = all_shell_variables (); + +#if defined (ARRAY_VARS) + if (attribute & att_array) + { + arrays_only++; + if (attribute != att_array) + attribute &= ~att_array; + } + else if (attribute & att_assoc) + { + assoc_only++; + if (attribute != att_assoc) + attribute &= ~att_assoc; + } +#endif + + if (variable_list) + { + for (i = 0; var = variable_list[i]; i++) + { +#if defined (ARRAY_VARS) + if (arrays_only && array_p (var) == 0) + continue; + else if (assoc_only && assoc_p (var) == 0) + continue; +#endif + if ((var->attributes & attribute)) + { + show_var_attributes (var, READONLY_OR_EXPORT, nodefs); + if (any_failed = sh_chkwrite (any_failed)) + break; + } + } + free (variable_list); + } + } + + return (assign_error ? EX_BADASSIGN + : ((any_failed == 0) ? EXECUTION_SUCCESS + : EXECUTION_FAILURE)); +} + +/* Show all variable variables (v == 1) or functions (v == 0) with + attributes. */ +int +show_all_var_attributes (v, nodefs) + int v, nodefs; +{ + SHELL_VAR **variable_list, *var; + int any_failed; + register int i; + + variable_list = v ? all_shell_variables () : all_shell_functions (); + if (variable_list == 0) + return (EXECUTION_SUCCESS); + + for (i = any_failed = 0; var = variable_list[i]; i++) + { + show_var_attributes (var, READONLY_OR_EXPORT, nodefs); + if (any_failed = sh_chkwrite (any_failed)) + break; + } + free (variable_list); + return (any_failed == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE); +} + +/* Show the attributes for shell variable VAR. If NODEFS is non-zero, + don't show function definitions along with the name. If PATTR is + non-zero, it indicates we're being called from `export' or `readonly'. + In POSIX mode, this prints the name of the calling builtin (`export' + or `readonly') instead of `declare', and doesn't print function defs + when called by `export' or `readonly'. */ +int +show_var_attributes (var, pattr, nodefs) + SHELL_VAR *var; + int pattr, nodefs; +{ + char flags[16], *x; + int i; + + i = 0; + + /* pattr == 0 means we are called from `declare'. */ + if (pattr == 0 || posixly_correct == 0) + { +#if defined (ARRAY_VARS) + if (array_p (var)) + flags[i++] = 'a'; + + if (assoc_p (var)) + flags[i++] = 'A'; +#endif + + if (function_p (var)) + flags[i++] = 'f'; + + if (integer_p (var)) + flags[i++] = 'i'; + + if (readonly_p (var)) + flags[i++] = 'r'; + + if (trace_p (var)) + flags[i++] = 't'; + + if (exported_p (var)) + flags[i++] = 'x'; + + if (capcase_p (var)) + flags[i++] = 'c'; + + if (lowercase_p (var)) + flags[i++] = 'l'; + + if (uppercase_p (var)) + flags[i++] = 'u'; + } + else + { +#if defined (ARRAY_VARS) + if (array_p (var)) + flags[i++] = 'a'; + + if (assoc_p (var)) + flags[i++] = 'A'; +#endif + + if (function_p (var)) + flags[i++] = 'f'; + } + + flags[i] = '\0'; + + /* If we're printing functions with definitions, print the function def + first, then the attributes, instead of printing output that can't be + reused as input to recreate the current state. */ + if (function_p (var) && nodefs == 0 && (pattr == 0 || posixly_correct == 0)) + { + printf ("%s\n", named_function_string (var->name, function_cell (var), FUNC_MULTILINE|FUNC_EXTERNAL)); + nodefs++; + if (pattr == 0 && i == 1 && flags[0] == 'f') + return 0; /* don't print `declare -f name' */ + } + + if (pattr == 0 || posixly_correct == 0) + printf ("declare -%s ", i ? flags : "-"); + else if (i) + printf ("%s -%s ", this_command_name, flags); + else + printf ("%s ", this_command_name); + +#if defined (ARRAY_VARS) + if (array_p (var)) + print_array_assignment (var, 1); + else if (assoc_p (var)) + print_assoc_assignment (var, 1); + else +#endif + /* force `readonly' and `export' to not print out function definitions + when in POSIX mode. */ + if (nodefs || (function_p (var) && pattr != 0 && posixly_correct)) + printf ("%s\n", var->name); + else if (function_p (var)) + printf ("%s\n", named_function_string (var->name, function_cell (var), FUNC_MULTILINE|FUNC_EXTERNAL)); + else if (invisible_p (var) || var_isnull (var)) + printf ("%s\n", var->name); + else + { + x = sh_double_quote (value_cell (var)); + printf ("%s=%s\n", var->name, x); + free (x); + } + return (0); +} + +int +show_name_attributes (name, nodefs) + char *name; + int nodefs; +{ + SHELL_VAR *var; + + var = find_variable_internal (name, 1); + + if (var && invisible_p (var) == 0) + { + show_var_attributes (var, READONLY_OR_EXPORT, nodefs); + return (0); + } + else + return (1); +} + +void +set_var_attribute (name, attribute, undo) + char *name; + int attribute, undo; +{ + SHELL_VAR *var, *tv; + char *tvalue; + + if (undo) + var = find_variable (name); + else + { + tv = find_tempenv_variable (name); + /* XXX -- need to handle case where tv is a temp variable in a + function-scope context, since function_env has been merged into + the local variables table. */ + if (tv && tempvar_p (tv)) + { + tvalue = var_isset (tv) ? savestring (value_cell (tv)) : savestring (""); + + var = bind_variable (tv->name, tvalue, 0); + var->attributes |= tv->attributes & ~att_tempvar; + VSETATTR (tv, att_propagate); + if (var->context != 0) + VSETATTR (var, att_propagate); + SETVARATTR (tv, attribute, undo); /* XXX */ + + stupidly_hack_special_variables (tv->name); + + free (tvalue); + } + else + { + var = find_variable_internal (name, 0); + if (var == 0) + { + var = bind_variable (name, (char *)NULL, 0); + VSETATTR (var, att_invisible); + } + else if (var->context != 0) + VSETATTR (var, att_propagate); + } + } + + if (var) + SETVARATTR (var, attribute, undo); + + if (var && (exported_p (var) || (attribute & att_exported))) + array_needs_making++; /* XXX */ +} diff --git a/builtins/ulimit.def b/builtins/ulimit.def index 7c1e2568b..97fcb9ce0 100644 --- a/builtins/ulimit.def +++ b/builtins/ulimit.def @@ -118,6 +118,10 @@ extern int errno; # undef HAVE_RESOURCE #endif +#if !defined (HAVE_RESOURCE) && defined (HAVE_ULIMIT_H) +# include +#endif + #if !defined (RLIMTYPE) # define RLIMTYPE long # define string_to_rlimtype(s) strtol(s, (char **)NULL, 10) diff --git a/builtins/ulimit.def~ b/builtins/ulimit.def~ new file mode 100644 index 000000000..16673063d --- /dev/null +++ b/builtins/ulimit.def~ @@ -0,0 +1,776 @@ +This file is ulimit.def, from which is created ulimit.c. +It implements the builtin "ulimit" 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 ulimit.c + +$BUILTIN ulimit +$FUNCTION ulimit_builtin +$DEPENDS_ON !_MINIX +$SHORT_DOC ulimit [-SHacdefilmnpqrstuvx] [limit] +Modify shell resource limits. + +Provides control over the resources available to the shell and processes +it creates, on systems that allow such control. + +Options: + -S use the `soft' resource limit + -H use the `hard' resource limit + -a all current limits are reported + -b the socket buffer size + -c the maximum size of core files created + -d the maximum size of a process's data segment + -e the maximum scheduling priority (`nice') + -f the maximum size of files written by the shell and its children + -i the maximum number of pending signals + -l the maximum size a process may lock into memory + -m the maximum resident set size + -n the maximum number of open file descriptors + -p the pipe buffer size + -q the maximum number of bytes in POSIX message queues + -r the maximum real-time scheduling priority + -s the maximum stack size + -t the maximum amount of cpu time in seconds + -u the maximum number of user processes + -v the size of virtual memory + -x the maximum number of file locks + +If LIMIT is given, it is the new value of the specified resource; the +special LIMIT values `soft', `hard', and `unlimited' stand for the +current soft limit, the current hard limit, and no limit, respectively. +Otherwise, the current value of the specified resource is printed. If +no option is given, then -f is assumed. + +Values are in 1024-byte increments, except for -t, which is in seconds, +-p, which is in increments of 512 bytes, and -u, which is an unscaled +number of processes. + +Exit Status: +Returns success unless an invalid option is supplied or an error occurs. +$END + +#if !defined (_MINIX) + +#include + +#include "../bashtypes.h" +#ifndef _MINIX +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include + +#include "../bashintl.h" + +#include "../shell.h" +#include "common.h" +#include "bashgetopt.h" +#include "pipesize.h" + +#if !defined (errno) +extern int errno; +#endif + +/* For some reason, HPUX chose to make these definitions visible only if + _KERNEL is defined, so we define _KERNEL before including + and #undef it afterward. */ +#if defined (HAVE_RESOURCE) +# include +# if defined (HPUX) && defined (RLIMIT_NEEDS_KERNEL) +# define _KERNEL +# endif +# include +# if defined (HPUX) && defined (RLIMIT_NEEDS_KERNEL) +# undef _KERNEL +# endif +#elif defined (HAVE_SYS_TIMES_H) +# include +#endif + +#if defined (HAVE_LIMITS_H) +# include +#endif + +/* Check for the most basic symbols. If they aren't present, this + system's isn't very useful to us. */ +#if !defined (RLIMIT_FSIZE) || !defined (HAVE_GETRLIMIT) +# undef HAVE_RESOURCE +#endif + +#if !HAVE_RESOURCE && HAVE_ULIMIT_H +# include +#endif + +#if !defined (RLIMTYPE) +# define RLIMTYPE long +# define string_to_rlimtype(s) strtol(s, (char **)NULL, 10) +# define print_rlimtype(num, nl) printf ("%ld%s", num, nl ? "\n" : "") +#endif + +/* Some systems use RLIMIT_NOFILE, others use RLIMIT_OFILE */ +#if defined (HAVE_RESOURCE) && defined (RLIMIT_OFILE) && !defined (RLIMIT_NOFILE) +# define RLIMIT_NOFILE RLIMIT_OFILE +#endif /* HAVE_RESOURCE && RLIMIT_OFILE && !RLIMIT_NOFILE */ + +/* Some systems have these, some do not. */ +#ifdef RLIMIT_FSIZE +# define RLIMIT_FILESIZE RLIMIT_FSIZE +#else +# define RLIMIT_FILESIZE 256 +#endif + +#define RLIMIT_PIPESIZE 257 + +#ifdef RLIMIT_NOFILE +# define RLIMIT_OPENFILES RLIMIT_NOFILE +#else +# define RLIMIT_OPENFILES 258 +#endif + +#ifdef RLIMIT_VMEM +# define RLIMIT_VIRTMEM RLIMIT_VMEM +# define RLIMIT_VMBLKSZ 1024 +#else +# ifdef RLIMIT_AS +# define RLIMIT_VIRTMEM RLIMIT_AS +# define RLIMIT_VMBLKSZ 1024 +# else +# define RLIMIT_VIRTMEM 259 +# define RLIMIT_VMBLKSZ 1 +# endif +#endif + +#ifdef RLIMIT_NPROC +# define RLIMIT_MAXUPROC RLIMIT_NPROC +#else +# define RLIMIT_MAXUPROC 260 +#endif + +#if !defined (RLIM_INFINITY) +# define RLIM_INFINITY 0x7fffffff +#endif + +#if !defined (RLIM_SAVED_CUR) +# define RLIM_SAVED_CUR RLIM_INFINITY +#endif + +#if !defined (RLIM_SAVED_MAX) +# define RLIM_SAVED_MAX RLIM_INFINITY +#endif + +#define LIMIT_HARD 0x01 +#define LIMIT_SOFT 0x02 + +/* "Blocks" are defined as 512 bytes when in Posix mode and 1024 bytes + otherwise. */ +#define POSIXBLK -2 + +#define BLOCKSIZE(x) (((x) == POSIXBLK) ? (posixly_correct ? 512 : 1024) : (x)) + +extern int posixly_correct; + +static int _findlim __P((int)); + +static int ulimit_internal __P((int, char *, int, int)); + +static int get_limit __P((int, RLIMTYPE *, RLIMTYPE *)); +static int set_limit __P((int, RLIMTYPE, int)); + +static void printone __P((int, RLIMTYPE, int)); +static void print_all_limits __P((int)); + +static int set_all_limits __P((int, RLIMTYPE)); + +static int filesize __P((RLIMTYPE *)); +static int pipesize __P((RLIMTYPE *)); +static int getmaxuprc __P((RLIMTYPE *)); +static int getmaxvm __P((RLIMTYPE *, RLIMTYPE *)); + +typedef struct { + int option; /* The ulimit option for this limit. */ + int parameter; /* Parameter to pass to get_limit (). */ + int block_factor; /* Blocking factor for specific limit. */ + const char * const description; /* Descriptive string to output. */ + const char * const units; /* scale */ +} RESOURCE_LIMITS; + +static RESOURCE_LIMITS limits[] = { +#ifdef RLIMIT_PTHREAD + { 'T', RLIMIT_PTHREAD, 1, "number of threads", (char *)NULL }, +#endif +#ifdef RLIMIT_SBSIZE + { 'b', RLIMIT_SBSIZE, 1, "socket buffer size", "bytes" }, +#endif +#ifdef RLIMIT_CORE + { 'c', RLIMIT_CORE, POSIXBLK, "core file size", "blocks" }, +#endif +#ifdef RLIMIT_DATA + { 'd', RLIMIT_DATA, 1024, "data seg size", "kbytes" }, +#endif +#ifdef RLIMIT_NICE + { 'e', RLIMIT_NICE, 1, "scheduling priority", (char *)NULL }, +#endif + { 'f', RLIMIT_FILESIZE, POSIXBLK, "file size", "blocks" }, +#ifdef RLIMIT_SIGPENDING + { 'i', RLIMIT_SIGPENDING, 1, "pending signals", (char *)NULL }, +#endif +#ifdef RLIMIT_MEMLOCK + { 'l', RLIMIT_MEMLOCK, 1024, "max locked memory", "kbytes" }, +#endif +#ifdef RLIMIT_RSS + { 'm', RLIMIT_RSS, 1024, "max memory size", "kbytes" }, +#endif /* RLIMIT_RSS */ + { 'n', RLIMIT_OPENFILES, 1, "open files", (char *)NULL}, + { 'p', RLIMIT_PIPESIZE, 512, "pipe size", "512 bytes" }, +#ifdef RLIMIT_MSGQUEUE + { 'q', RLIMIT_MSGQUEUE, 1, "POSIX message queues", "bytes" }, +#endif +#ifdef RLIMIT_RTPRIO + { 'r', RLIMIT_RTPRIO, 1, "real-time priority", (char *)NULL }, +#endif +#ifdef RLIMIT_STACK + { 's', RLIMIT_STACK, 1024, "stack size", "kbytes" }, +#endif +#ifdef RLIMIT_CPU + { 't', RLIMIT_CPU, 1, "cpu time", "seconds" }, +#endif /* RLIMIT_CPU */ + { 'u', RLIMIT_MAXUPROC, 1, "max user processes", (char *)NULL }, +#if defined (HAVE_RESOURCE) + { 'v', RLIMIT_VIRTMEM, RLIMIT_VMBLKSZ, "virtual memory", "kbytes" }, +#endif +#ifdef RLIMIT_SWAP + { 'w', RLIMIT_SWAP, 1024, "swap size", "kbytes" }, +#endif +#ifdef RLIMIT_LOCKS + { 'x', RLIMIT_LOCKS, 1, "file locks", (char *)NULL }, +#endif + { -1, -1, -1, (char *)NULL, (char *)NULL } +}; +#define NCMDS (sizeof(limits) / sizeof(limits[0])) + +typedef struct _cmd { + int cmd; + char *arg; +} ULCMD; + +static ULCMD *cmdlist; +static int ncmd; +static int cmdlistsz; + +#if !defined (HAVE_RESOURCE) && !defined (HAVE_ULIMIT) +long +ulimit (cmd, newlim) + int cmd; + long newlim; +{ + errno = EINVAL; + return -1; +} +#endif /* !HAVE_RESOURCE && !HAVE_ULIMIT */ + +static int +_findlim (opt) + int opt; +{ + register int i; + + for (i = 0; limits[i].option > 0; i++) + if (limits[i].option == opt) + return i; + return -1; +} + +static char optstring[4 + 2 * NCMDS]; + +/* Report or set limits associated with certain per-process resources. + See the help documentation in builtins.c for a full description. */ +int +ulimit_builtin (list) + register WORD_LIST *list; +{ + register char *s; + int c, limind, mode, opt, all_limits; + + mode = 0; + + all_limits = 0; + + /* Idea stolen from pdksh -- build option string the first time called. */ + if (optstring[0] == 0) + { + s = optstring; + *s++ = 'a'; *s++ = 'S'; *s++ = 'H'; + for (c = 0; limits[c].option > 0; c++) + { + *s++ = limits[c].option; + *s++ = ';'; + } + *s = '\0'; + } + + /* Initialize the command list. */ + if (cmdlistsz == 0) + cmdlist = (ULCMD *)xmalloc ((cmdlistsz = 16) * sizeof (ULCMD)); + ncmd = 0; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, optstring)) != -1) + { + switch (opt) + { + case 'a': + all_limits++; + break; + + /* -S and -H are modifiers, not real options. */ + case 'S': + mode |= LIMIT_SOFT; + break; + + case 'H': + mode |= LIMIT_HARD; + break; + + case '?': + builtin_usage (); + return (EX_USAGE); + + default: + if (ncmd >= cmdlistsz) + cmdlist = (ULCMD *)xrealloc (cmdlist, (cmdlistsz *= 2) * sizeof (ULCMD)); + cmdlist[ncmd].cmd = opt; + cmdlist[ncmd++].arg = list_optarg; + break; + } + } + list = loptend; + + if (all_limits) + { +#ifdef NOTYET + if (list) /* setting */ + { + if (STREQ (list->word->word, "unlimited") == 0) + { + builtin_error (_("%s: invalid limit argument"), list->word->word); + return (EXECUTION_FAILURE); + } + return (set_all_limits (mode == 0 ? LIMIT_SOFT|LIMIT_HARD : mode, RLIM_INFINITY)); + } +#endif + print_all_limits (mode == 0 ? LIMIT_SOFT : mode); + return (sh_chkwrite (EXECUTION_SUCCESS)); + } + + /* default is `ulimit -f' */ + if (ncmd == 0) + { + cmdlist[ncmd].cmd = 'f'; + /* `ulimit something' is same as `ulimit -f something' */ + cmdlist[ncmd++].arg = list ? list->word->word : (char *)NULL; + if (list) + list = list->next; + } + + /* verify each command in the list. */ + for (c = 0; c < ncmd; c++) + { + limind = _findlim (cmdlist[c].cmd); + if (limind == -1) + { + builtin_error (_("`%c': bad command"), cmdlist[c].cmd); + return (EX_USAGE); + } + } + + for (c = 0; c < ncmd; c++) + if (ulimit_internal (cmdlist[c].cmd, cmdlist[c].arg, mode, ncmd > 1) == EXECUTION_FAILURE) + return (EXECUTION_FAILURE); + + return (EXECUTION_SUCCESS); +} + +static int +ulimit_internal (cmd, cmdarg, mode, multiple) + int cmd; + char *cmdarg; + int mode, multiple; +{ + int opt, limind, setting; + int block_factor; + RLIMTYPE soft_limit, hard_limit, real_limit, limit; + + setting = cmdarg != 0; + limind = _findlim (cmd); + if (mode == 0) + mode = setting ? (LIMIT_HARD|LIMIT_SOFT) : LIMIT_SOFT; + opt = get_limit (limind, &soft_limit, &hard_limit); + if (opt < 0) + { + builtin_error (_("%s: cannot get limit: %s"), limits[limind].description, + strerror (errno)); + return (EXECUTION_FAILURE); + } + + if (setting == 0) /* print the value of the specified limit */ + { + printone (limind, (mode & LIMIT_SOFT) ? soft_limit : hard_limit, multiple); + return (EXECUTION_SUCCESS); + } + + /* Setting the limit. */ + if (STREQ (cmdarg, "hard")) + real_limit = hard_limit; + else if (STREQ (cmdarg, "soft")) + real_limit = soft_limit; + else if (STREQ (cmdarg, "unlimited")) + real_limit = RLIM_INFINITY; + else if (all_digits (cmdarg)) + { + limit = string_to_rlimtype (cmdarg); + block_factor = BLOCKSIZE(limits[limind].block_factor); + real_limit = limit * block_factor; + + if ((real_limit / block_factor) != limit) + { + sh_erange (cmdarg, _("limit")); + return (EXECUTION_FAILURE); + } + } + else + { + sh_invalidnum (cmdarg); + return (EXECUTION_FAILURE); + } + + if (set_limit (limind, real_limit, mode) < 0) + { + builtin_error (_("%s: cannot modify limit: %s"), limits[limind].description, + strerror (errno)); + return (EXECUTION_FAILURE); + } + + return (EXECUTION_SUCCESS); +} + +static int +get_limit (ind, softlim, hardlim) + int ind; + RLIMTYPE *softlim, *hardlim; +{ + RLIMTYPE value; +#if defined (HAVE_RESOURCE) + struct rlimit limit; +#endif + + if (limits[ind].parameter >= 256) + { + switch (limits[ind].parameter) + { + case RLIMIT_FILESIZE: + if (filesize (&value) < 0) + return -1; + break; + case RLIMIT_PIPESIZE: + if (pipesize (&value) < 0) + return -1; + break; + case RLIMIT_OPENFILES: + value = (RLIMTYPE)getdtablesize (); + break; + case RLIMIT_VIRTMEM: + return (getmaxvm (softlim, hardlim)); + case RLIMIT_MAXUPROC: + if (getmaxuprc (&value) < 0) + return -1; + break; + default: + errno = EINVAL; + return -1; + } + *softlim = *hardlim = value; + return (0); + } + else + { +#if defined (HAVE_RESOURCE) + if (getrlimit (limits[ind].parameter, &limit) < 0) + return -1; + *softlim = limit.rlim_cur; + *hardlim = limit.rlim_max; +# if defined (HPUX9) + if (limits[ind].parameter == RLIMIT_FILESIZE) + { + *softlim *= 512; + *hardlim *= 512; /* Ugh. */ + } + else +# endif /* HPUX9 */ + return 0; +#else + errno = EINVAL; + return -1; +#endif + } +} + +static int +set_limit (ind, newlim, mode) + int ind; + RLIMTYPE newlim; + int mode; +{ +#if defined (HAVE_RESOURCE) + struct rlimit limit; + RLIMTYPE val; +#endif + + if (limits[ind].parameter >= 256) + switch (limits[ind].parameter) + { + case RLIMIT_FILESIZE: +#if !defined (HAVE_RESOURCE) + return (ulimit (2, newlim / 512L)); +#else + errno = EINVAL; + return -1; +#endif + + case RLIMIT_OPENFILES: +#if defined (HAVE_SETDTABLESIZE) +# if defined (__CYGWIN__) + /* Grrr... Cygwin declares setdtablesize as void. */ + setdtablesize (newlim); + return 0; +# else + return (setdtablesize (newlim)); +# endif +#endif + case RLIMIT_PIPESIZE: + case RLIMIT_VIRTMEM: + case RLIMIT_MAXUPROC: + default: + errno = EINVAL; + return -1; + } + else + { +#if defined (HAVE_RESOURCE) + if (getrlimit (limits[ind].parameter, &limit) < 0) + return -1; +# if defined (HPUX9) + if (limits[ind].parameter == RLIMIT_FILESIZE) + newlim /= 512; /* Ugh. */ +# endif /* HPUX9 */ + val = (current_user.euid != 0 && newlim == RLIM_INFINITY && + (mode & LIMIT_HARD) == 0 && /* XXX -- test */ + (limit.rlim_cur <= limit.rlim_max)) + ? limit.rlim_max : newlim; + if (mode & LIMIT_SOFT) + limit.rlim_cur = val; + if (mode & LIMIT_HARD) + limit.rlim_max = val; + + return (setrlimit (limits[ind].parameter, &limit)); +#else + errno = EINVAL; + return -1; +#endif + } +} + +static int +getmaxvm (softlim, hardlim) + RLIMTYPE *softlim, *hardlim; +{ +#if defined (HAVE_RESOURCE) + struct rlimit datalim, stacklim; + + if (getrlimit (RLIMIT_DATA, &datalim) < 0) + return -1; + + if (getrlimit (RLIMIT_STACK, &stacklim) < 0) + return -1; + + /* Protect against overflow. */ + *softlim = (datalim.rlim_cur / 1024L) + (stacklim.rlim_cur / 1024L); + *hardlim = (datalim.rlim_max / 1024L) + (stacklim.rlim_max / 1024L); + return 0; +#else + errno = EINVAL; + return -1; +#endif /* HAVE_RESOURCE */ +} + +static int +filesize(valuep) + RLIMTYPE *valuep; +{ +#if !defined (HAVE_RESOURCE) + long result; + if ((result = ulimit (1, 0L)) < 0) + return -1; + else + *valuep = (RLIMTYPE) result * 512; + return 0; +#else + errno = EINVAL; + return -1; +#endif +} + +static int +pipesize (valuep) + RLIMTYPE *valuep; +{ +#if defined (PIPE_BUF) + /* This is defined on Posix systems. */ + *valuep = (RLIMTYPE) PIPE_BUF; + return 0; +#else +# if defined (_POSIX_PIPE_BUF) + *valuep = (RLIMTYPE) _POSIX_PIPE_BUF; + return 0; +# else +# if defined (PIPESIZE) + /* This is defined by running a program from the Makefile. */ + *valuep = (RLIMTYPE) PIPESIZE; + return 0; +# else + errno = EINVAL; + return -1; +# endif /* PIPESIZE */ +# endif /* _POSIX_PIPE_BUF */ +#endif /* PIPE_BUF */ +} + +static int +getmaxuprc (valuep) + RLIMTYPE *valuep; +{ + long maxchild; + + maxchild = getmaxchild (); + if (maxchild < 0) + { + errno = EINVAL; + return -1; + } + else + { + *valuep = (RLIMTYPE) maxchild; + return 0; + } +} + +static void +print_all_limits (mode) + int mode; +{ + register int i; + RLIMTYPE softlim, hardlim; + + if (mode == 0) + mode |= LIMIT_SOFT; + + for (i = 0; limits[i].option > 0; i++) + { + if (get_limit (i, &softlim, &hardlim) == 0) + printone (i, (mode & LIMIT_SOFT) ? softlim : hardlim, 1); + else if (errno != EINVAL) + builtin_error ("%s: cannot get limit: %s", limits[i].description, + strerror (errno)); + } +} + +static void +printone (limind, curlim, pdesc) + int limind; + RLIMTYPE curlim; + int pdesc; +{ + char unitstr[64]; + int factor; + + factor = BLOCKSIZE(limits[limind].block_factor); + if (pdesc) + { + if (limits[limind].units) + sprintf (unitstr, "(%s, -%c) ", limits[limind].units, limits[limind].option); + else + sprintf (unitstr, "(-%c) ", limits[limind].option); + + printf ("%-20s %16s", limits[limind].description, unitstr); + } + if (curlim == RLIM_INFINITY) + puts ("unlimited"); + else if (curlim == RLIM_SAVED_MAX) + puts ("hard"); + else if (curlim == RLIM_SAVED_CUR) + puts ("soft"); + else + print_rlimtype ((curlim / factor), 1); +} + +/* Set all limits to NEWLIM. NEWLIM currently must be RLIM_INFINITY, which + causes all limits to be set as high as possible depending on mode (like + csh `unlimit'). Returns -1 if NEWLIM is invalid, 0 if all limits + were set successfully, and 1 if at least one limit could not be set. + + To raise all soft limits to their corresponding hard limits, use + ulimit -S -a unlimited + To attempt to raise all hard limits to infinity (superuser-only), use + ulimit -H -a unlimited + To attempt to raise all soft and hard limits to infinity, use + ulimit -a unlimited +*/ + +static int +set_all_limits (mode, newlim) + int mode; + RLIMTYPE newlim; +{ + register int i; + int retval = 0; + + if (newlim != RLIM_INFINITY) + { + errno = EINVAL; + return -1; + } + + if (mode == 0) + mode = LIMIT_SOFT|LIMIT_HARD; + + for (retval = i = 0; limits[i].option > 0; i++) + if (set_limit (i, newlim, mode) < 0) + { + builtin_error (_("%s: cannot modify limit: %s"), limits[i].description, + strerror (errno)); + retval = 1; + } + return retval; +} + +#endif /* !_MINIX */ diff --git a/config.h.in b/config.h.in index 07352dd48..12167c3ac 100644 --- a/config.h.in +++ b/config.h.in @@ -994,6 +994,9 @@ /* Define if you have the header file. */ #undef HAVE_TERMIOS_H +/* Define if you have the header file. */ +#undef HAVE_ULIMIT_H + /* Define if you have the header file. */ #undef HAVE_UNISTD_H diff --git a/config.h.in~ b/config.h.in~ new file mode 100644 index 000000000..07352dd48 --- /dev/null +++ b/config.h.in~ @@ -0,0 +1,1110 @@ +/* config.h -- Configuration file for 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 . +*/ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +/* Configuration feature settings controllable by autoconf. */ + +/* Define JOB_CONTROL if your operating system supports + BSD-like job control. */ +#undef JOB_CONTROL + +/* Define ALIAS if you want the alias features. */ +#undef ALIAS + +/* Define PUSHD_AND_POPD if you want those commands to be compiled in. + (Also the `dirs' commands.) */ +#undef PUSHD_AND_POPD + +/* Define BRACE_EXPANSION if you want curly brace expansion a la Csh: + foo{a,b} -> fooa foob. Even if this is compiled in (the default) you + can turn it off at shell startup with `-nobraceexpansion', or during + shell execution with `set +o braceexpand'. */ +#undef BRACE_EXPANSION + +/* Define READLINE to get the nifty/glitzy editing features. + This is on by default. You can turn it off interactively + with the -nolineediting flag. */ +#undef READLINE + +/* Define BANG_HISTORY if you want to have Csh style "!" history expansion. + This is unrelated to READLINE. */ +#undef BANG_HISTORY + +/* Define HISTORY if you want to have access to previously typed commands. + + If both HISTORY and READLINE are defined, you can get at the commands + with line editing commands, and you can directly manipulate the history + from the command line. + + If only HISTORY is defined, the `fc' and `history' builtins are + available. */ +#undef HISTORY + +/* Define this if you want completion that puts all alternatives into + a brace expansion shell expression. */ +#if defined (BRACE_EXPANSION) && defined (READLINE) +# define BRACE_COMPLETION +#endif /* BRACE_EXPANSION */ + +/* Define DEFAULT_ECHO_TO_XPG if you want the echo builtin to interpret + the backslash-escape characters by default, like the XPG Single Unix + Specification V2 for echo. + This requires that V9_ECHO be defined. */ +#undef DEFAULT_ECHO_TO_XPG + +/* Define HELP_BUILTIN if you want the `help' shell builtin and the long + documentation strings compiled into the shell. */ +#undef HELP_BUILTIN + +/* Define RESTRICTED_SHELL if you want the generated shell to have the + ability to be a restricted one. The shell thus generated can become + restricted by being run with the name "rbash", or by setting the -r + flag. */ +#undef RESTRICTED_SHELL + +/* Define DISABLED_BUILTINS if you want "builtin foo" to always run the + shell builtin "foo", even if it has been disabled with "enable -n foo". */ +#undef DISABLED_BUILTINS + +/* Define PROCESS_SUBSTITUTION if you want the K*rn shell-like process + substitution features "<(file)". */ +/* Right now, you cannot do this on machines without fully operational + FIFO support. This currently include NeXT and Alliant. */ +#undef PROCESS_SUBSTITUTION + +/* Define PROMPT_STRING_DECODE if you want the backslash-escaped special + characters in PS1 and PS2 expanded. Variable expansion will still be + performed. */ +#undef PROMPT_STRING_DECODE + +/* Define SELECT_COMMAND if you want the Korn-shell style `select' command: + select word in word_list; do command_list; done */ +#undef SELECT_COMMAND + +/* Define COMMAND_TIMING of you want the ksh-style `time' reserved word and + the ability to time pipelines, functions, and builtins. */ +#undef COMMAND_TIMING + +/* Define ARRAY_VARS if you want ksh-style one-dimensional array variables. */ +#undef ARRAY_VARS + +/* Define DPAREN_ARITHMETIC if you want the ksh-style ((...)) arithmetic + evaluation command. */ +#undef DPAREN_ARITHMETIC + +/* Define EXTENDED_GLOB if you want the ksh-style [*+@?!](patlist) extended + pattern matching. */ +#undef EXTENDED_GLOB + +/* Define EXTGLOB_DEFAULT to the value you'd like the extglob shell option + to have by default */ +#undef EXTGLOB_DEFAULT + +/* Define COND_COMMAND if you want the ksh-style [[...]] conditional + command. */ +#undef COND_COMMAND + +/* Define COND_REGEXP if you want extended regular expression matching and the + =~ binary operator in the [[...]] conditional command. */ +#define COND_REGEXP + +/* Define COPROCESS_SUPPORT if you want support for ksh-like coprocesses and + the `coproc' reserved word */ +#define COPROCESS_SUPPORT + +/* Define ARITH_FOR_COMMAND if you want the ksh93-style + for (( init; test; step )) do list; done + arithmetic for command. */ +#undef ARITH_FOR_COMMAND + +/* Define NETWORK_REDIRECTIONS if you want /dev/(tcp|udp)/host/port to open + socket connections when used in redirections */ +#undef NETWORK_REDIRECTIONS + +/* Define PROGRAMMABLE_COMPLETION for the programmable completion features + and the complete builtin. */ +#undef PROGRAMMABLE_COMPLETION + +/* Define NO_MULTIBYTE_SUPPORT to not compile in support for multibyte + characters, even if the OS supports them. */ +#undef NO_MULTIBYTE_SUPPORT + +/* Define DEBUGGER if you want to compile in some features used only by the + bash debugger. */ +#undef DEBUGGER + +/* Define STRICT_POSIX if you want bash to be strictly posix.2 conformant by + default (except for echo; that is controlled separately). */ +#undef STRICT_POSIX + +/* Define MEMSCRAMBLE if you want the bash malloc and free to scramble + memory contents on malloc() and free(). */ +#undef MEMSCRAMBLE + +/* Define AFS if you are using Transarc's AFS. */ +#undef AFS + +/* Define for case-modifying variable attributes; variables modified on + assignment */ +#undef CASEMOD_ATTRS + +/* Define for case-modifying word expansions */ +#undef CASEMOD_EXPANSIONS + +#undef ENABLE_NLS + +/* End of configuration settings controllable by autoconf. */ +/* Other settable options appear in config-top.h. */ + +#include "config-top.h" + +/* Beginning of autoconf additions. */ + +/* Characteristics of the C compiler */ +#undef const + +#undef inline + +#undef restrict + +#undef volatile + +/* Define if cpp supports the ANSI-C stringizing `#' operator */ +#undef HAVE_STRINGIZE + +/* Define if the compiler supports `long double' variables. */ +#undef HAVE_LONG_DOUBLE + +#undef PROTOTYPES + +#undef __CHAR_UNSIGNED__ + +/* Define if the compiler supports `long long' variables. */ +#undef HAVE_LONG_LONG + +#undef HAVE_UNSIGNED_LONG_LONG + +/* The number of bytes in a int. */ +#undef SIZEOF_INT + +/* The number of bytes in a long. */ +#undef SIZEOF_LONG + +/* The number of bytes in a pointer to char. */ +#undef SIZEOF_CHAR_P + +/* The number of bytes in a double (hopefully 8). */ +#undef SIZEOF_DOUBLE + +/* The number of bytes in a `long long', if we have one. */ +#undef SIZEOF_LONG_LONG + +/* System paths */ + +#define DEFAULT_MAIL_DIRECTORY "/usr/spool/mail" + +/* Characteristics of the system's header files and libraries that affect + the compilation environment. */ + +/* Define if the system does not provide POSIX.1 features except + with this defined. */ +#undef _POSIX_1_SOURCE + +/* Define if you need to in order for stat and other things to work. */ +#undef _POSIX_SOURCE + +/* Define to use GNU libc extensions */ +#undef _GNU_SOURCE + +/* Define if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Memory management functions. */ + +/* Define if using the bash version of malloc in lib/malloc/malloc.c */ +#undef USING_BASH_MALLOC + +#undef DISABLE_MALLOC_WRAPPERS + +/* Define if using alloca.c. */ +#undef C_ALLOCA + +/* Define to one of _getb67, GETB67, getb67 for Cray-2 and Cray-YMP systems. + This function is required for alloca.c support on those systems. */ +#undef CRAY_STACKSEG_END + +/* Define if you have alloca, as a function or macro. */ +#undef HAVE_ALLOCA + +/* Define if you have and it should be used (not on Ultrix). */ +#undef HAVE_ALLOCA_H + + +/* SYSTEM TYPES */ + +/* Define to `long' if doesn't define. */ +#undef off_t + +/* Define to `int' if doesn't define. */ +#undef mode_t + +/* Define to `int' if doesn't define. */ +#undef sigset_t + +/* Define to `int' if doesn't define. */ +#undef pid_t + +/* Define to `short' if doesn't define. */ +#undef bits16_t + +/* Define to `unsigned short' if doesn't define. */ +#undef u_bits16_t + +/* Define to `int' if doesn't define. */ +#undef bits32_t + +/* Define to `unsigned int' if doesn't define. */ +#undef u_bits32_t + +/* Define to `double' if doesn't define. */ +#undef bits64_t + +/* Define to `unsigned int' if doesn't define. */ +#undef u_int + +/* Define to `unsigned long' if doesn't define. */ +#undef u_long + +/* Define to `int' if doesn't define. */ +#undef ptrdiff_t + +/* Define to `unsigned' if doesn't define. */ +#undef size_t + +/* Define to `int' if doesn't define. */ +#undef ssize_t + +/* Define to `long' if doesn't define. */ +#undef intmax_t + +/* Define to `unsigned long' if doesn't define. */ +#undef uintmax_t + +/* Define to `int' if doesn't define. */ +#undef uid_t + +/* Define to `long' if doesn't define. */ +#undef clock_t + +/* Define to `long' if doesn't define. */ +#undef time_t + +/* Define to `int' if doesn't define. */ +#undef gid_t + +/* Define to `unsigned int' if doesn't define. */ +#undef socklen_t + +/* Define to `int' if doesn't define. */ +#undef sig_atomic_t + +#undef HAVE_MBSTATE_T + +/* Define if you have quad_t in . */ +#undef HAVE_QUAD_T + +/* Define if you have wchar_t in . */ +#undef HAVE_WCHAR_T + +/* Define if you have wctype_t in . */ +#undef HAVE_WCTYPE_T + +/* Define if you have wint_t in . */ +#undef HAVE_WINT_T + +#undef RLIMTYPE + +/* Define to the type of elements in the array set by `getgroups'. + Usually this is either `int' or `gid_t'. */ +#undef GETGROUPS_T + +/* Characteristics of the machine archictecture. */ + +/* If using the C implementation of alloca, define if you know the + direction of stack growth for your system; otherwise it will be + automatically deduced at run-time. + STACK_DIRECTION > 0 => grows toward higher addresses + STACK_DIRECTION < 0 => grows toward lower addresses + STACK_DIRECTION = 0 => direction of growth unknown + */ +#undef STACK_DIRECTION + +/* Define if the machine architecture is big-endian. */ +#undef WORDS_BIGENDIAN + +/* Check for the presence of certain non-function symbols in the system + libraries. */ + +/* Define if `sys_siglist' is declared by or . */ +#undef HAVE_DECL_SYS_SIGLIST +#undef SYS_SIGLIST_DECLARED + +/* Define if `_sys_siglist' is declared by or . */ +#undef UNDER_SYS_SIGLIST_DECLARED + +#undef HAVE_SYS_SIGLIST + +#undef HAVE_UNDER_SYS_SIGLIST + +#undef HAVE_SYS_ERRLIST + +#undef HAVE_TZNAME +#undef HAVE_DECL_TZNAME + +/* Characteristics of some of the system structures. */ + +#undef HAVE_STRUCT_DIRENT_D_INO + +#undef HAVE_STRUCT_DIRENT_D_FILENO + +#undef HAVE_STRUCT_DIRENT_D_NAMLEN + +#undef TIOCSTAT_IN_SYS_IOCTL + +#undef FIONREAD_IN_SYS_IOCTL + +#undef GWINSZ_IN_SYS_IOCTL + +#undef STRUCT_WINSIZE_IN_SYS_IOCTL + +#undef TM_IN_SYS_TIME + +#undef STRUCT_WINSIZE_IN_TERMIOS + +#undef SPEED_T_IN_SYS_TYPES + +#undef TERMIOS_LDISC + +#undef TERMIO_LDISC + +#undef HAVE_STRUCT_STAT_ST_BLOCKS + +#undef HAVE_STRUCT_TM_TM_ZONE +#undef HAVE_TM_ZONE + +#undef HAVE_TIMEVAL + +#undef HAVE_STRUCT_TIMEZONE + +/* Characteristics of definitions in the system header files. */ + +#undef HAVE_GETPW_DECLS + +#undef HAVE_RESOURCE + +#undef HAVE_LIBC_FNM_EXTMATCH + +#undef HAVE_DECL_CONFSTR + +#undef HAVE_DECL_PRINTF + +#undef HAVE_DECL_SBRK + +#undef HAVE_DECL_STRCPY + +#undef HAVE_DECL_STRSIGNAL + +#undef HAVE_DECL_STRTOLD + +#undef PRI_MACROS_BROKEN + +#undef STRTOLD_BROKEN + +/* Define if WCONTINUED is defined in system headers, but rejected by waitpid */ +#undef WCONTINUED_BROKEN + +/* These are checked with BASH_CHECK_DECL */ + +#undef HAVE_DECL_STRTOIMAX +#undef HAVE_DECL_STRTOL +#undef HAVE_DECL_STRTOLL +#undef HAVE_DECL_STRTOUL +#undef HAVE_DECL_STRTOULL +#undef HAVE_DECL_STRTOUMAX + +/* Characteristics of system calls and C library functions. */ + +/* Define if the `getpgrp' function takes no argument. */ +#undef GETPGRP_VOID + +#undef NAMED_PIPES_MISSING + +#undef OPENDIR_NOT_ROBUST + +#undef PGRP_PIPE + +/* Define if the setvbuf function takes the buffering type as its second + argument and the buffer pointer as the third, as on System V + before release 3. */ +#undef SETVBUF_REVERSED + +#undef STAT_MACROS_BROKEN + +#undef ULIMIT_MAXFDS + +#undef CAN_REDEFINE_GETENV + +#undef HAVE_STD_PUTENV + +#undef HAVE_STD_UNSETENV + +#undef HAVE_PRINTF_A_FORMAT + +#undef CTYPE_NON_ASCII + +/* Define if you have and nl_langinfo(CODESET). */ +#undef HAVE_LANGINFO_CODESET + +/* Characteristics of properties exported by the kernel. */ + +/* Define if the kernel can exec files beginning with #! */ +#undef HAVE_HASH_BANG_EXEC + +/* Define if you have the /dev/fd devices to map open files into the file system. */ +#undef HAVE_DEV_FD + +/* Defined to /dev/fd or /proc/self/fd (linux). */ +#undef DEV_FD_PREFIX + +/* Define if you have the /dev/stdin device. */ +#undef HAVE_DEV_STDIN + +/* The type of iconv's `inbuf' argument */ +#undef ICONV_CONST + +/* Type and behavior of signal handling functions. */ + +/* Define as the return type of signal handlers (int or void). */ +#undef RETSIGTYPE + +/* Define if return type of signal handlers is void */ +#undef VOID_SIGHANDLER + +#undef MUST_REINSTALL_SIGHANDLERS + +#undef HAVE_BSD_SIGNALS + +#undef HAVE_POSIX_SIGNALS + +#undef HAVE_USG_SIGHOLD + +#undef UNUSABLE_RT_SIGNALS + + +/* Presence of system and C library functions. */ + +/* Define if you have the asprintf function. */ +#undef HAVE_ASPRINTF + +/* Define if you have the bcopy function. */ +#undef HAVE_BCOPY + +/* Define if you have the bzero function. */ +#undef HAVE_BZERO + +/* Define if you have the confstr function. */ +#undef HAVE_CONFSTR + +/* Define if you have the dlclose function. */ +#undef HAVE_DLCLOSE + +/* Define if you have the dlopen function. */ +#undef HAVE_DLOPEN + +/* Define if you have the dlsym function. */ +#undef HAVE_DLSYM + +/* Define if you don't have vprintf but do have _doprnt. */ +#undef HAVE_DOPRNT + +/* Define if you have the dup2 function. */ +#undef HAVE_DUP2 + +/* Define if you have the eaccess function. */ +#undef HAVE_EACCESS + +/* Define if you have the fcntl function. */ +#undef HAVE_FCNTL + +/* Define if you have the fdprintf function. */ +#undef HAVE_FDPRINTF + +/* Define if you have the fpurge/__fpurge function. */ +#undef HAVE_FPURGE +#undef HAVE___FPURGE +#undef HAVE_DECL_FPURGE + +/* Define if you have the getaddrinfo function. */ +#undef HAVE_GETADDRINFO + +/* Define if you have the getcwd function. */ +#undef HAVE_GETCWD + +/* Define if you have the getdtablesize function. */ +#undef HAVE_GETDTABLESIZE + +/* Define if you have the getgroups function. */ +#undef HAVE_GETGROUPS + +/* Define if you have the gethostbyname function. */ +#undef HAVE_GETHOSTBYNAME + +/* Define if you have the gethostname function. */ +#undef HAVE_GETHOSTNAME + +/* Define if you have the getpagesize function. */ +#undef HAVE_GETPAGESIZE + +/* Define if you have the getpeername function. */ +#undef HAVE_GETPEERNAME + +/* Define if you have the getpwent function. */ +#undef HAVE_GETPWENT + +/* Define if you have the getpwnam function. */ +#undef HAVE_GETPWNAM + +/* Define if you have the getpwuid function. */ +#undef HAVE_GETPWUID + +/* Define if you have the getrlimit function. */ +#undef HAVE_GETRLIMIT + +/* Define if you have the getrusage function. */ +#undef HAVE_GETRUSAGE + +/* Define if you have the getservbyname function. */ +#undef HAVE_GETSERVBYNAME + +/* Define if you have the getservent function. */ +#undef HAVE_GETSERVENT + +/* Define if you have the gettimeofday function. */ +#undef HAVE_GETTIMEOFDAY + +/* Define if you have the getwd function. */ +#undef HAVE_GETWD + +/* Define if you have the iconv function. */ +#undef HAVE_ICONV + +/* Define if you have the inet_aton function. */ +#undef HAVE_INET_ATON + +/* Define if you have the isascii function. */ +#undef HAVE_ISASCII + +/* Define if you have the isblank function. */ +#undef HAVE_ISBLANK + +/* Define if you have the isgraph function. */ +#undef HAVE_ISGRAPH + +/* Define if you have the isinf function in libc */ +#undef HAVE_ISINF_IN_LIBC + +/* Define if you have the isnan function in libc */ +#undef HAVE_ISNAN_IN_LIBC + +/* Define if you have the isprint function. */ +#undef HAVE_ISPRINT + +/* Define if you have the isspace function. */ +#undef HAVE_ISSPACE + +/* Define if you have the iswctype function. */ +#undef HAVE_ISWCTYPE + +/* Define if you have the iswlower function. */ +#undef HAVE_ISWLOWER + +/* Define if you have the iswupper function. */ +#undef HAVE_ISWUPPER + +/* Define if you have the isxdigit function. */ +#undef HAVE_ISXDIGIT + +/* Define if you have the kill function. */ +#undef HAVE_KILL + +/* Define if you have the killpg function. */ +#undef HAVE_KILLPG + +/* Define if you have the lstat function. */ +#undef HAVE_LSTAT + +/* Define if you have the locale_charset function. */ +#undef HAVE_LOCALE_CHARSET + +/* Define if you have the mbrlen function. */ +#undef HAVE_MBRLEN + +/* Define if you have the mbrtowc function. */ +#undef HAVE_MBRTOWC + +/* Define if you have the mbscasecmp function. */ +#undef HAVE_MBSCASECMP + +/* Define if you have the mbschr function. */ +#undef HAVE_MBSCHR + +/* Define if you have the mbscmp function. */ +#undef HAVE_MBSCMP + +/* Define if you have the mbsrtowcs function. */ +#undef HAVE_MBSRTOWCS + +/* Define if you have the memmove function. */ +#undef HAVE_MEMMOVE + +/* Define if you have the memset function. */ +#undef HAVE_MEMSET + +/* Define if you have the mkfifo function. */ +#undef HAVE_MKFIFO + +/* Define if you have the pathconf function. */ +#undef HAVE_PATHCONF + +/* Define if you have the putenv function. */ +#undef HAVE_PUTENV + +/* Define if you have the raise function. */ +#undef HAVE_RAISE + +/* Define if you have the readlink function. */ +#undef HAVE_READLINK + +/* Define if you have the regcomp function. */ +#undef HAVE_REGCOMP + +/* Define if you have the regexec function. */ +#undef HAVE_REGEXEC + +/* Define if you have the rename function. */ +#undef HAVE_RENAME + +/* Define if you have the sbrk function. */ +#undef HAVE_SBRK + +/* Define if you have the select function. */ +#undef HAVE_SELECT + +/* Define if you have the setdtablesize function. */ +#undef HAVE_SETDTABLESIZE + +/* Define if you have the setenv function. */ +#undef HAVE_SETENV + +/* Define if you have the setitimer function. */ +#undef HAVE_SETITIMER + +/* Define if you have the setlinebuf function. */ +#undef HAVE_SETLINEBUF + +/* Define if you have the setlocale function. */ +#undef HAVE_SETLOCALE + +/* Define if you have the setostype function. */ +#undef HAVE_SETOSTYPE + +/* Define if you have the setregid function. */ +#undef HAVE_SETREGID +#undef HAVE_DECL_SETREGID + +/* Define if you have the setvbuf function. */ +#undef HAVE_SETVBUF + +/* Define if you have the siginterrupt function. */ +#undef HAVE_SIGINTERRUPT + +/* Define if you have the POSIX.1-style sigsetjmp function. */ +#undef HAVE_POSIX_SIGSETJMP + +/* Define if you have the snprintf function. */ +#undef HAVE_SNPRINTF + +/* Define if you have the strcasecmp function. */ +#undef HAVE_STRCASECMP + +/* Define if you have the strcasestr function. */ +#undef HAVE_STRCASESTR + +/* Define if you have the strchr function. */ +#undef HAVE_STRCHR + +/* Define if you have the strcoll function. */ +#undef HAVE_STRCOLL + +/* Define if you have the strerror function. */ +#undef HAVE_STRERROR + +/* Define if you have the strftime function. */ +#undef HAVE_STRFTIME + +/* Define if you have the strnlen function. */ +#undef HAVE_STRNLEN + +/* Define if you have the strpbrk function. */ +#undef HAVE_STRPBRK + +/* Define if you have the strstr function. */ +#undef HAVE_STRSTR + +/* Define if you have the strtod function. */ +#undef HAVE_STRTOD + +/* Define if you have the strtoimax function. */ +#undef HAVE_STRTOIMAX + +/* Define if you have the strtol function. */ +#undef HAVE_STRTOL + +/* Define if you have the strtoll function. */ +#undef HAVE_STRTOLL + +/* Define if you have the strtoul function. */ +#undef HAVE_STRTOUL + +/* Define if you have the strtoull function. */ +#undef HAVE_STRTOULL + +/* Define if you have the strtoumax function. */ +#undef HAVE_STRTOUMAX + +/* Define if you have the strsignal function or macro. */ +#undef HAVE_STRSIGNAL + +/* Define if you have the sysconf function. */ +#undef HAVE_SYSCONF + +/* Define if you have the syslog function. */ +#undef HAVE_SYSLOG + +/* Define if you have the tcgetattr function. */ +#undef HAVE_TCGETATTR + +/* Define if you have the tcgetpgrp function. */ +#undef HAVE_TCGETPGRP + +/* Define if you have the times function. */ +#undef HAVE_TIMES + +/* Define if you have the towlower function. */ +#undef HAVE_TOWLOWER + +/* Define if you have the towupper function. */ +#undef HAVE_TOWUPPER + +/* Define if you have the ttyname function. */ +#undef HAVE_TTYNAME + +/* Define if you have the tzset function. */ +#undef HAVE_TZSET + +/* Define if you have the ulimit function. */ +#undef HAVE_ULIMIT + +/* Define if you have the uname function. */ +#undef HAVE_UNAME + +/* Define if you have the unsetenv function. */ +#undef HAVE_UNSETENV + +/* Define if you have the vasprintf function. */ +#undef HAVE_VASPRINTF + +/* Define if you have the vprintf function. */ +#undef HAVE_VPRINTF + +/* Define if you have the vsnprintf function. */ +#undef HAVE_VSNPRINTF + +/* Define if you have the waitpid function. */ +#undef HAVE_WAITPID + +/* Define if you have the wait3 function. */ +#undef HAVE_WAIT3 + +/* Define if you have the wcrtomb function. */ +#undef HAVE_WCRTOMB + +/* Define if you have the wcscoll function. */ +#undef HAVE_WCSCOLL + +/* Define if you have the wcsdup function. */ +#undef HAVE_WCSDUP + +/* Define if you have the wctype function. */ +#undef HAVE_WCTYPE + +/* Define if you have the wcwidth function. */ +#undef HAVE_WCWIDTH + +/* Presence of certain system include files. */ + +/* Define if you have the header file. */ +#undef HAVE_ARPA_INET_H + +/* Define if you have the header file. */ +#undef HAVE_DIRENT_H + +/* Define if you have the header file. */ +#undef HAVE_DLFCN_H + +/* Define if you have the header file. */ +#undef HAVE_GRP_H + +/* Define if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define if you have the header file. */ +#undef HAVE_LANGINFO_H + +/* Define if you have the header file. */ +#undef HAVE_LIBINTL_H + +/* Define if you have the header file. */ +#undef HAVE_LIMITS_H + +/* Define if you have the header file. */ +#undef HAVE_LOCALE_H + +/* Define if you have the header file. */ +#undef HAVE_NDIR_H + +/* Define if you have the header file. */ +#undef HAVE_NETDB_H + +/* Define if you have the header file. */ +#undef HAVE_NETINET_IN_H + +/* Define if you have the header file. */ +#undef HAVE_PWD_H + +/* Define if you have the header file. */ +#undef HAVE_REGEX_H + +/* Define if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define if you have the header file. */ +#undef HAVE_STDARG_H + +/* Define if you have the header file. */ +#undef HAVE_STRING_H + +/* Define if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define if you have the header file. */ +#undef HAVE_STDDEF_H + +/* Define if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define if you have the header file. */ +#undef HAVE_SYSLOG_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_DIR_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_FILE_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_NDIR_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_PARAM_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_PTE_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_PTEM_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_RESOURCE_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_SELECT_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_SOCKET_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_STREAM_H + +/* Define if you have */ +#undef HAVE_SYS_TIME_H + +#undef TIME_WITH_SYS_TIME + +/* Define if you have */ +#undef HAVE_SYS_TIMES_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define if you have that is POSIX.1 compatible. */ +#undef HAVE_SYS_WAIT_H + +/* Define if you have the header file. */ +#undef HAVE_TERMCAP_H + +/* Define if you have the header file. */ +#undef HAVE_TERMIO_H + +/* Define if you have the header file. */ +#undef HAVE_TERMIOS_H + +/* Define if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define if you have the header file. */ +#undef HAVE_VARARGS_H + +/* Define if you have the header file. */ +#undef HAVE_WCHAR_H + +/* Define if you have the header file. */ +#undef HAVE_WCTYPE_H + +/* Presence of certain system libraries. */ + +#undef HAVE_LIBDL + +#undef HAVE_LIBSUN + +#undef HAVE_LIBSOCKET + + +/* Define if on MINIX. */ +#undef _MINIX + +/* Are we running SVR5 (UnixWare 7)? */ +#undef SVR5 + +/* Are we running SVR4.2? */ +#undef SVR4_2 + +/* Are we running some version of SVR4? */ +#undef SVR4 + +/* Define if job control is unusable or unsupported. */ +#undef JOB_CONTROL_MISSING + +/* Do we need to define _KERNEL to get the RLIMIT_* defines from + ? */ +#undef RLIMIT_NEEDS_KERNEL + +/* Number of bits in a file offset, on hosts where this is settable. */ +#undef _FILE_OFFSET_BITS + +/* Define for large files on AIX-style hosts. */ +#undef _LARGE_FILES + +/* Do strcoll(3) and strcmp(3) give different results in the default locale? */ +#undef STRCOLL_BROKEN + +#undef DUP2_BROKEN + +#undef GETCWD_BROKEN + +/* Additional defines for configuring lib/intl, maintained by autoscan/autoheader */ + +/* Define if you have the header file. */ +#undef HAVE_ARGZ_H + +/* Define if you have the header file. */ +#undef HAVE_ERRNO_H + +/* Define if you have the header file. */ +#undef HAVE_FCNTL_H + +/* Define if you have the header file. */ +#undef HAVE_MALLOC_H + +/* Define if you have the header file. */ +#undef HAVE_STDIO_EXT_H + +/* Define if you have the `dcgettext' function. */ +#undef HAVE_DCGETTEXT + +/* Define if you have the `localeconv' function. */ +#undef HAVE_LOCALECONV + +/* Define if your system has a working `malloc' function. */ +/* #undef HAVE_MALLOC */ + +/* Define if you have the `mempcpy' function. */ +#undef HAVE_MEMPCPY + +/* Define if you have a working `mmap' system call. */ +#undef HAVE_MMAP + +/* Define if you have the `munmap' function. */ +#undef HAVE_MUNMAP + +/* Define if you have the `nl_langinfo' function. */ +#undef HAVE_NL_LANGINFO + +/* Define if you have the `stpcpy' function. */ +#undef HAVE_STPCPY + +/* Define if you have the `strcspn' function. */ +#undef HAVE_STRCSPN + +/* Define if you have the `strdup' function. */ +#undef HAVE_STRDUP + +/* Define if you have the `__argz_count' function. */ +#undef HAVE___ARGZ_COUNT + +/* Define if you have the `__argz_next' function. */ +#undef HAVE___ARGZ_NEXT + +/* Define if you have the `__argz_stringify' function. */ +#undef HAVE___ARGZ_STRINGIFY + +/* End additions for lib/intl */ + +#include "config-bot.h" + +#endif /* _CONFIG_H_ */ diff --git a/configure b/configure index 875bbbf1a..aa4b96a3d 100755 --- a/configure +++ b/configure @@ -1,7 +1,7 @@ #! /bin/sh -# From configure.in for Bash 4.1, version 4.019. +# From configure.in for Bash 4.1, version 4.020. # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.63 for bash 4.1-release. +# Generated by GNU Autoconf 2.63 for bash 4.1-maint. # # Report bugs to . # @@ -597,8 +597,8 @@ SHELL=${CONFIG_SHELL-/bin/sh} # Identity of this package. PACKAGE_NAME='bash' PACKAGE_TARNAME='bash' -PACKAGE_VERSION='4.1-release' -PACKAGE_STRING='bash 4.1-release' +PACKAGE_VERSION='4.1-maint' +PACKAGE_STRING='bash 4.1-maint' PACKAGE_BUGREPORT='bug-bash@gnu.org' ac_unique_file="shell.h" @@ -1411,7 +1411,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures bash 4.1-release to adapt to many kinds of systems. +\`configure' configures bash 4.1-maint to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1476,7 +1476,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of bash 4.1-release:";; + short | recursive ) echo "Configuration of bash 4.1-maint:";; esac cat <<\_ACEOF @@ -1652,7 +1652,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -bash configure 4.1-release +bash configure 4.1-maint generated by GNU Autoconf 2.63 Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, @@ -1666,7 +1666,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by bash $as_me 4.1-release, which was +It was created by bash $as_me 4.1-maint, which was generated by GNU Autoconf 2.63. Invocation command line was $ $0 $@ @@ -2080,7 +2080,7 @@ ac_config_headers="$ac_config_headers config.h" BASHVERS=4.1 -RELSTATUS=release +RELSTATUS=maint case "$RELSTATUS" in alp*|bet*|dev*|rc*|maint*) DEBUG='-DDEBUG' MALLOC_DEBUG='-DMALLOC_DEBUG' ;; @@ -11502,12 +11502,13 @@ done + for ac_header in unistd.h stdlib.h stdarg.h varargs.h limits.h string.h \ memory.h locale.h termcap.h termio.h termios.h dlfcn.h \ stddef.h stdint.h netdb.h pwd.h grp.h strings.h regex.h \ - syslog.h + syslog.h ulimit.h do as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then @@ -31170,7 +31171,7 @@ exec 6>&1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by bash $as_me 4.1-release, which was +This file was extended by bash $as_me 4.1-maint, which was generated by GNU Autoconf 2.63. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -31233,7 +31234,7 @@ Report bugs to ." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_version="\\ -bash config.status 4.1-release +bash config.status 4.1-maint configured by $0, generated by GNU Autoconf 2.63, with options \\"`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\" diff --git a/configure.in b/configure.in index 843bd700c..21bf7bb19 100644 --- a/configure.in +++ b/configure.in @@ -21,10 +21,10 @@ dnl Process this file with autoconf to produce a configure script. # You should have received a copy of the GNU General Public License # along with this program. If not, see . -AC_REVISION([for Bash 4.1, version 4.019])dnl +AC_REVISION([for Bash 4.1, version 4.020])dnl define(bashvers, 4.1) -define(relstatus, release) +define(relstatus, maint) AC_INIT([bash], bashvers-relstatus, [bug-bash@gnu.org]) @@ -659,7 +659,7 @@ BASH_HEADER_INTTYPES AC_CHECK_HEADERS(unistd.h stdlib.h stdarg.h varargs.h limits.h string.h \ memory.h locale.h termcap.h termio.h termios.h dlfcn.h \ stddef.h stdint.h netdb.h pwd.h grp.h strings.h regex.h \ - syslog.h) + syslog.h ulimit.h) AC_CHECK_HEADERS(sys/pte.h sys/stream.h sys/select.h sys/file.h \ sys/resource.h sys/param.h sys/socket.h sys/stat.h \ sys/time.h sys/times.h sys/types.h sys/wait.h) diff --git a/configure.in~ b/configure.in~ new file mode 100644 index 000000000..76ef24482 --- /dev/null +++ b/configure.in~ @@ -0,0 +1,1156 @@ +dnl +dnl Configure script for bash-4.1 +dnl +dnl report bugs to chet@po.cwru.edu +dnl +dnl Process this file with autoconf to produce a configure script. + +# Copyright (C) 1987-2009 Free Software Foundation, Inc. + +# +# This program 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. +# +# This program 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 this program. If not, see . + +AC_REVISION([for Bash 4.1, version 4.019])dnl + +define(bashvers, 4.1) +define(relstatus, maint) + +AC_INIT([bash], bashvers-relstatus, [bug-bash@gnu.org]) + +dnl make sure we are using a recent autoconf version +AC_PREREQ(2.50) + +AC_CONFIG_SRCDIR(shell.h) +dnl where to find install.sh, config.sub, and config.guess +AC_CONFIG_AUX_DIR(./support) +AC_CONFIG_HEADERS(config.h) + +dnl checks for version info +BASHVERS=bashvers +RELSTATUS=relstatus + +dnl defaults for debug settings +case "$RELSTATUS" in +alp*|bet*|dev*|rc*|maint*) DEBUG='-DDEBUG' MALLOC_DEBUG='-DMALLOC_DEBUG' ;; +*) DEBUG= MALLOC_DEBUG= ;; +esac + +dnl canonicalize the host and os so we can do some tricky things before +dnl parsing options +AC_CANONICAL_HOST + +dnl configure defaults +opt_bash_malloc=yes +opt_purify=no +opt_purecov=no +opt_afs=no +opt_curses=no +opt_with_installed_readline=no + +#htmldir= + +dnl some systems should be configured without the bash malloc by default +dnl and some need a special compiler or loader +dnl look in the NOTES file for more +case "${host_cpu}-${host_os}" in +alpha*-*) opt_bash_malloc=no ;; # alpha running osf/1 or linux +*[[Cc]]ray*-*) opt_bash_malloc=no ;; # Crays +*-osf1*) opt_bash_malloc=no ;; # other osf/1 machines +sparc-svr4*) opt_bash_malloc=no ;; # sparc SVR4, SVR4.2 +sparc-netbsd*) opt_bash_malloc=no ;; # needs 8-byte alignment +mips-irix6*) opt_bash_malloc=no ;; # needs 8-byte alignment +m68k-sysv) opt_bash_malloc=no ;; # fixes file descriptor leak in closedir +sparc-linux*) opt_bash_malloc=no ;; # sparc running linux; requires ELF +#*-freebsd*-gnu) opt_bash_malloc=no ;; # there's some undetermined problem here +#*-freebsd*) opt_bash_malloc=no ;; # they claim it's better; I disagree +*-openbsd*) opt_bash_malloc=no ;; # they claim it needs eight-bit alignment +*-aix*) opt_bash_malloc=no ;; # AIX machines +*-nextstep*) opt_bash_malloc=no ;; # NeXT machines running NeXTstep +*-macos*) opt_bash_malloc=no ;; # Apple MacOS X +*-rhapsody*) opt_bash_malloc=no ;; # Apple Rhapsody (MacOS X) +*-darwin*) opt_bash_malloc=no ;; # Apple Darwin (MacOS X) +*-dgux*) opt_bash_malloc=no ;; # DG/UX machines +*-qnx*) opt_bash_malloc=no ;; # QNX 4.2, QNX 6.x +*-machten4) opt_bash_malloc=no ;; # MachTen 4.x +*-bsdi2.1|*-bsdi3.?) opt_bash_malloc=no ; : ${CC:=shlicc2} ;; # for loadable builtins +*-beos*) opt_bash_malloc=no ;; # they say it's suitable +*-cygwin*) opt_bash_malloc=no ;; # Cygnus's CYGWIN environment +*-opennt*|*-interix*) opt_bash_malloc=no ;; # Interix, now owned by Microsoft +esac + +# memory scrambling on free() +case "${host_os}" in +sco3.2v5*|sco3.2v4*) opt_memscramble=no ;; +*) opt_memscramble=yes ;; +esac + +dnl +dnl macros for the bash debugger +dnl +AM_PATH_LISPDIR +AC_ARG_VAR(DEBUGGER_START_FILE, [location of bash debugger initialization file]) + +dnl arguments to configure +dnl packages +AC_ARG_WITH(afs, AC_HELP_STRING([--with-afs], [if you are running AFS]), opt_afs=$withval) +AC_ARG_WITH(bash-malloc, AC_HELP_STRING([--with-bash-malloc], [use the Bash version of malloc]), opt_bash_malloc=$withval) +AC_ARG_WITH(curses, AC_HELP_STRING([--with-curses], [use the curses library instead of the termcap library]), opt_curses=$withval) +AC_ARG_WITH(gnu-malloc, AC_HELP_STRING([--with-gnu-malloc], [synonym for --with-bash-malloc]), opt_bash_malloc=$withval) +AC_ARG_WITH(installed-readline, AC_HELP_STRING([--with-installed-readline], [use a version of the readline library that is already installed]), opt_with_installed_readline=$withval) +AC_ARG_WITH(purecov, AC_HELP_STRING([--with-purecov], [configure to postprocess with pure coverage]), opt_purecov=$withval) +AC_ARG_WITH(purify, AC_HELP_STRING([--with-purify], [configure to postprocess with purify]), opt_purify=$withval) + +if test "$opt_bash_malloc" = yes; then + MALLOC_TARGET=malloc + MALLOC_SRC=malloc.c + + MALLOC_LIB='-lmalloc' + MALLOC_LIBRARY='$(ALLOC_LIBDIR)/libmalloc.a' + MALLOC_LDFLAGS='-L$(ALLOC_LIBDIR)' + MALLOC_DEP='$(MALLOC_LIBRARY)' + + AC_DEFINE(USING_BASH_MALLOC) +else + MALLOC_LIB= + MALLOC_LIBRARY= + MALLOC_LDFLAGS= + MALLOC_DEP= +fi + +if test "$opt_purify" = yes; then + PURIFY="purify " + AC_DEFINE(DISABLE_MALLOC_WRAPPERS) +else + PURIFY= +fi + +if test "$opt_purecov" = yes; then + PURIFY="${PURIFY}purecov" +fi + +if test "$opt_afs" = yes; then + AC_DEFINE(AFS) +fi + +if test "$opt_curses" = yes; then + prefer_curses=yes +fi + +if test -z "${DEBUGGER_START_FILE}"; then + DEBUGGER_START_FILE='${datadir}/bashdb/bashdb-main.inc' +fi + +dnl optional shell features in config.h.in +opt_minimal_config=no + +opt_job_control=yes +opt_alias=yes +opt_readline=yes +opt_history=yes +opt_bang_history=yes +opt_dirstack=yes +opt_restricted=yes +opt_process_subst=yes +opt_prompt_decoding=yes +opt_select=yes +opt_help=yes +opt_array_variables=yes +opt_dparen_arith=yes +opt_extended_glob=yes +opt_brace_expansion=yes +opt_disabled_builtins=no +opt_command_timing=yes +opt_xpg_echo=no +opt_strict_posix=no +opt_cond_command=yes +opt_cond_regexp=yes +opt_coproc=yes +opt_arith_for_command=yes +opt_net_redirs=yes +opt_progcomp=yes +opt_separate_help=no +opt_multibyte=yes +opt_debugger=yes +opt_single_longdoc_strings=yes +opt_casemod_attrs=yes +opt_casemod_expansions=yes +opt_extglob_default=no + +dnl options that affect how bash is compiled and linked +opt_static_link=no +opt_profiling=no + +dnl argument parsing for optional features +AC_ARG_ENABLE(minimal-config, AC_HELP_STRING([--enable-minimal-config], [a minimal sh-like configuration]), opt_minimal_config=$enableval) + +dnl a minimal configuration turns everything off, but features can be +dnl added individually +if test $opt_minimal_config = yes; then + opt_job_control=no opt_alias=no opt_readline=no + opt_history=no opt_bang_history=no opt_dirstack=no + opt_restricted=no opt_process_subst=no opt_prompt_decoding=no + opt_select=no opt_help=no opt_array_variables=no opt_dparen_arith=no + opt_brace_expansion=no opt_disabled_builtins=no opt_command_timing=no + opt_extended_glob=no opt_cond_command=no opt_arith_for_command=no + opt_net_redirs=no opt_progcomp=no opt_separate_help=no + opt_multibyte=yes opt_cond_regexp=no opt_coproc=no + opt_casemod_attrs=no opt_casemod_expansions=no opt_extglob_default=no +fi + +AC_ARG_ENABLE(alias, AC_HELP_STRING([--enable-alias], [enable shell aliases]), opt_alias=$enableval) +AC_ARG_ENABLE(arith-for-command, AC_HELP_STRING([--enable-arith-for-command], [enable arithmetic for command]), opt_arith_for_command=$enableval) +AC_ARG_ENABLE(array-variables, AC_HELP_STRING([--enable-array-variables], [include shell array variables]), opt_array_variables=$enableval) +AC_ARG_ENABLE(bang-history, AC_HELP_STRING([--enable-bang-history], [turn on csh-style history substitution]), opt_bang_history=$enableval) +AC_ARG_ENABLE(brace-expansion, AC_HELP_STRING([--enable-brace-expansion], [include brace expansion]), opt_brace_expansion=$enableval) +AC_ARG_ENABLE(casemod-attributes, AC_HELP_STRING([--enable-casemod-attributes], [include case-modifying variable attributes]), opt_casemod_attrs=$enableval) +AC_ARG_ENABLE(casemod-expansions, AC_HELP_STRING([--enable-casemod-expansions], [include case-modifying word expansions]), opt_casemod_expansions=$enableval) +AC_ARG_ENABLE(command-timing, AC_HELP_STRING([--enable-command-timing], [enable the time reserved word and command timing]), opt_command_timing=$enableval) +AC_ARG_ENABLE(cond-command, AC_HELP_STRING([--enable-cond-command], [enable the conditional command]), opt_cond_command=$enableval) +AC_ARG_ENABLE(cond-regexp, AC_HELP_STRING([--enable-cond-regexp], [enable extended regular expression matching in conditional commands]), opt_cond_regexp=$enableval) +AC_ARG_ENABLE(coprocesses, AC_HELP_STRING([--enable-coprocesses], [enable coprocess support and the coproc reserved word]), opt_coproc=$enableval) +AC_ARG_ENABLE(debugger, AC_HELP_STRING([--enable-debugger], [enable support for bash debugger]), opt_debugger=$enableval) +AC_ARG_ENABLE(directory-stack, AC_HELP_STRING([--enable-directory-stack], [enable builtins pushd/popd/dirs]), opt_dirstack=$enableval) +AC_ARG_ENABLE(disabled-builtins, AC_HELP_STRING([--enable-disabled-builtins], [allow disabled builtins to still be invoked]), opt_disabled_builtins=$enableval) +AC_ARG_ENABLE(dparen-arithmetic, AC_HELP_STRING([--enable-dparen-arithmetic], [include ((...)) command]), opt_dparen_arith=$enableval) +AC_ARG_ENABLE(extended-glob, AC_HELP_STRING([--enable-extended-glob], [include ksh-style extended pattern matching]), opt_extended_glob=$enableval) +AC_ARG_ENABLE(extended-glob-default, AC_HELP_STRING([--enable-extended-glob-default], [force extended pattern matching to be enabled by default]), opt_extglob_default=$enableval) +AC_ARG_ENABLE(help-builtin, AC_HELP_STRING([--enable-help-builtin], [include the help builtin]), opt_help=$enableval) +AC_ARG_ENABLE(history, AC_HELP_STRING([--enable-history], [turn on command history]), opt_history=$enableval) +AC_ARG_ENABLE(job-control, AC_HELP_STRING([--enable-job-control], [enable job control features]), opt_job_control=$enableval) +AC_ARG_ENABLE(multibyte, AC_HELP_STRING([--enable-multibyte], [enable multibyte characters if OS supports them]), opt_multibyte=$enableval) +AC_ARG_ENABLE(net-redirections, AC_HELP_STRING([--enable-net-redirections], [enable /dev/tcp/host/port redirection]), opt_net_redirs=$enableval) +AC_ARG_ENABLE(process-substitution, AC_HELP_STRING([--enable-process-substitution], [enable process substitution]), opt_process_subst=$enableval) +AC_ARG_ENABLE(progcomp, AC_HELP_STRING([--enable-progcomp], [enable programmable completion and the complete builtin]), opt_progcomp=$enableval) +AC_ARG_ENABLE(prompt-string-decoding, AC_HELP_STRING([--enable-prompt-string-decoding], [turn on escape character decoding in prompts]), opt_prompt_decoding=$enableval) +AC_ARG_ENABLE(readline, AC_HELP_STRING([--enable-readline], [turn on command line editing]), opt_readline=$enableval) +AC_ARG_ENABLE(restricted, AC_HELP_STRING([--enable-restricted], [enable a restricted shell]), opt_restricted=$enableval) +AC_ARG_ENABLE(select, AC_HELP_STRING([--enable-select], [include select command]), opt_select=$enableval) +AC_ARG_ENABLE(separate-helpfiles, AC_HELP_STRING([--enable-separate-helpfiles], [use external files for help builtin documentation]), opt_separate_help=$enableval) +AC_ARG_ENABLE(single-help-strings, AC_HELP_STRING([--enable-single-help-strings], [store help documentation as a single string to ease translation]), opt_single_longdoc_strings=$enableval) +AC_ARG_ENABLE(strict-posix-default, AC_HELP_STRING([--enable-strict-posix-default], [configure bash to be posix-conformant by default]), opt_strict_posix=$enableval) +AC_ARG_ENABLE(usg-echo-default, AC_HELP_STRING([--enable-usg-echo-default], [a synonym for --enable-xpg-echo-default]), opt_xpg_echo=$enableval) +AC_ARG_ENABLE(xpg-echo-default, AC_HELP_STRING([--enable-xpg-echo-default], [make the echo builtin expand escape sequences by default]), opt_xpg_echo=$enableval) + +dnl options that alter how bash is compiled and linked +AC_ARG_ENABLE(mem-scramble, AC_HELP_STRING([--enable-mem-scramble], [scramble memory on calls to malloc and free]), opt_memscramble=$enableval) +AC_ARG_ENABLE(profiling, AC_HELP_STRING([--enable-profiling], [allow profiling with gprof]), opt_profiling=$enableval) +AC_ARG_ENABLE(static-link, AC_HELP_STRING([--enable-static-link], [link bash statically, for use as a root shell]), opt_static_link=$enableval) + +dnl opt_job_control is handled later, after BASH_JOB_CONTROL_MISSING runs + +dnl opt_readline and opt_history are handled later, because AC_PROG_CC needs +dnl to be run before we can check the version of an already-installed readline +dnl library + +if test $opt_alias = yes; then +AC_DEFINE(ALIAS) +fi +if test $opt_dirstack = yes; then +AC_DEFINE(PUSHD_AND_POPD) +fi +if test $opt_restricted = yes; then +AC_DEFINE(RESTRICTED_SHELL) +fi +if test $opt_process_subst = yes; then +AC_DEFINE(PROCESS_SUBSTITUTION) +fi +if test $opt_prompt_decoding = yes; then +AC_DEFINE(PROMPT_STRING_DECODE) +fi +if test $opt_select = yes; then +AC_DEFINE(SELECT_COMMAND) +fi +if test $opt_help = yes; then +AC_DEFINE(HELP_BUILTIN) +fi +if test $opt_array_variables = yes; then +AC_DEFINE(ARRAY_VARS) +fi +if test $opt_dparen_arith = yes; then +AC_DEFINE(DPAREN_ARITHMETIC) +fi +if test $opt_brace_expansion = yes; then +AC_DEFINE(BRACE_EXPANSION) +fi +if test $opt_disabled_builtins = yes; then +AC_DEFINE(DISABLED_BUILTINS) +fi +if test $opt_command_timing = yes; then +AC_DEFINE(COMMAND_TIMING) +fi +if test $opt_xpg_echo = yes ; then +AC_DEFINE(DEFAULT_ECHO_TO_XPG) +fi +if test $opt_strict_posix = yes; then +AC_DEFINE(STRICT_POSIX) +fi +if test $opt_extended_glob = yes ; then +AC_DEFINE(EXTENDED_GLOB) +fi +if test $opt_extglob_default = yes; then +AC_DEFINE(EXTGLOB_DEFAULT, 1) +else +AC_DEFINE(EXTGLOB_DEFAULT, 0) +fi +if test $opt_cond_command = yes ; then +AC_DEFINE(COND_COMMAND) +fi +if test $opt_cond_regexp = yes ; then +AC_DEFINE(COND_REGEXP) +fi +if test $opt_coproc = yes; then +AC_DEFINE(COPROCESS_SUPPORT) +fi +if test $opt_arith_for_command = yes; then +AC_DEFINE(ARITH_FOR_COMMAND) +fi +if test $opt_net_redirs = yes; then +AC_DEFINE(NETWORK_REDIRECTIONS) +fi +if test $opt_progcomp = yes; then +AC_DEFINE(PROGRAMMABLE_COMPLETION) +fi +if test $opt_multibyte = no; then +AC_DEFINE(NO_MULTIBYTE_SUPPORT) +fi +if test $opt_debugger = yes; then +AC_DEFINE(DEBUGGER) +fi +if test $opt_casemod_attrs = yes; then +AC_DEFINE(CASEMOD_ATTRS) +fi +if test $opt_casemod_expansions = yes; then +AC_DEFINE(CASEMOD_EXPANSIONS) +fi + +if test $opt_memscramble = yes; then +AC_DEFINE(MEMSCRAMBLE) +fi + +if test "$opt_minimal_config" = yes; then + TESTSCRIPT=run-minimal +else + TESTSCRIPT=run-all +fi + +HELPDIR= HELPDIRDEFINE= HELPINSTALL= +if test "$opt_separate_help" != no; then + if test "$opt_separate_help" = "yes" ; then + HELPDIR='${datadir}/bash' + else + HELPDIR=$opt_separate_help + fi + HELPDIRDEFINE='-H ${HELPDIR}' + HELPINSTALL='install-help' +fi +HELPSTRINGS= +if test "$opt_single_longdoc_strings" != "yes"; then + HELPSTRINGS='-S' +fi + +dnl now substitute in the values generated by arguments +AC_SUBST(TESTSCRIPT) +AC_SUBST(PURIFY) +AC_SUBST(MALLOC_TARGET) +AC_SUBST(MALLOC_SRC) + +AC_SUBST(MALLOC_LIB) +AC_SUBST(MALLOC_LIBRARY) +AC_SUBST(MALLOC_LDFLAGS) +AC_SUBST(MALLOC_DEP) + +AC_SUBST(htmldir) + +AC_SUBST(HELPDIR) +AC_SUBST(HELPDIRDEFINE) +AC_SUBST(HELPINSTALL) +AC_SUBST(HELPSTRINGS) + +echo "" +echo "Beginning configuration for bash-$BASHVERS-$RELSTATUS for ${host_cpu}-${host_vendor}-${host_os}" +echo "" + +dnl compilation checks +dnl AC_PROG_CC sets $cross_compiling to `yes' if cross-compiling for a +dnl different environment +AC_PROG_CC + +dnl test for Unix variants +AC_ISC_POSIX +AC_MINIX + +AC_SYS_LARGEFILE + +dnl BEGIN changes for cross-building (currently cygwin, minGW, and +dnl (obsolete) BeOS) + +SIGNAMES_O= +SIGNAMES_H=lsignames.h + +dnl load up the cross-building cache file -- add more cases and cache +dnl files as necessary + +dnl Note that host and target machine are the same, and different than the +dnl build machine. +dnl Set SIGNAMES_H based on whether or not we're cross-compiling. + +CROSS_COMPILE= +if test "x$cross_compiling" = "xyes"; then + case "${host}" in + *-cygwin*) + cross_cache=${srcdir}/cross-build/cygwin32.cache + ;; + *-mingw*) + cross_cache=${srcdir}/cross-build/cygwin32.cache + ;; + i[[3456]]86-*-beos*) + cross_cache=${srcdir}/cross-build/x86-beos.cache + ;; + *) echo "configure: cross-compiling for $host is not supported" >&2 + ;; + esac + if test -n "${cross_cache}" && test -r "${cross_cache}"; then + echo "loading cross-build cache file ${cross_cache}" + . ${cross_cache} + fi + unset cross_cache + SIGNAMES_O='signames.o' + CROSS_COMPILE='-DCROSS_COMPILING' + AC_SUBST(CROSS_COMPILE) +fi +AC_SUBST(SIGNAMES_H) +AC_SUBST(SIGNAMES_O) + +if test -z "$CC_FOR_BUILD"; then + if test "x$cross_compiling" = "xno"; then + CC_FOR_BUILD='$(CC)' + else + CC_FOR_BUILD=gcc + fi +fi +AC_SUBST(CC_FOR_BUILD) + +dnl END changes for cross-building + +dnl We want these before the checks, so the checks can modify their values. +test -z "$CFLAGS" && CFLAGS=-g auto_cflags=1 + +dnl If we're using gcc and the user hasn't specified CFLAGS, add -O2 to CFLAGS. +test -n "$GCC" && test -n "$auto_cflags" && CFLAGS="$CFLAGS -O2" + +dnl handle options that alter how bash is compiled and linked +dnl these must come after the test for cc/gcc +if test "$opt_profiling" = "yes"; then + PROFILE_FLAGS=-pg + case "$host_os" in + solaris2*) ;; + *) opt_static_link=yes ;; + esac + DEBUG= MALLOC_DEBUG= +fi + +if test "$opt_static_link" = yes; then + # if we're using gcc, add `-static' to LDFLAGS, except on Solaris >= 2 + if test -n "$GCC" || test "$ac_cv_prog_gcc" = "yes"; then + STATIC_LD="-static" + case "$host_os" in + solaris2*) ;; + *) LDFLAGS="$LDFLAGS -static" ;; # XXX experimental + esac + fi +fi + +if test "X$cross_compiling" = "Xno"; then + CPPFLAGS_FOR_BUILD=${CPPFLAGS_FOR_BUILD-"$CPPFLAGS"} + LDFLAGS_FOR_BUILD=${LDFLAGS_FOR_BUILD-'$(LDFLAGS)'} +else + CPPFLAGS_FOR_BUILD=${CPPFLAGS_FOR_BUILD-""} + LDFLAGS_FOR_BUILD=${LDFLAGS_FOR_BUILD-""} +fi + +test -z "$CFLAGS_FOR_BUILD" && CFLAGS_FOR_BUILD="-g" + +AC_SUBST(CFLAGS) +AC_SUBST(CPPFLAGS) +AC_SUBST(LDFLAGS) +AC_SUBST(STATIC_LD) + +AC_SUBST(CFLAGS_FOR_BUILD) +AC_SUBST(CPPFLAGS_FOR_BUILD) +AC_SUBST(LDFLAGS_FOR_BUILD) + +AC_PROG_GCC_TRADITIONAL + +dnl BEGIN READLINE and HISTORY LIBRARY SECTION +dnl prepare to allow bash to be linked against an already-installed readline + +dnl first test that the readline version is new enough to link bash against +if test "$opt_readline" = yes && test "$opt_with_installed_readline" != "no" +then + # If the user specified --with-installed-readline=PREFIX and PREFIX + # is not `yes', set ac_cv_rl_prefix to PREFIX + test $opt_with_installed_readline != "yes" && ac_cv_rl_prefix=$opt_with_installed_readline + + RL_LIB_READLINE_VERSION + + case "$ac_cv_rl_version" in + 5*|6*|7*|8*|9*) ;; + *) opt_with_installed_readline=no + AC_MSG_WARN([installed readline library is too old to be linked with bash]) + AC_MSG_WARN([using private bash version]) + ;; + esac +fi + +TILDE_LIB=-ltilde +if test $opt_readline = yes; then + AC_DEFINE(READLINE) + if test "$opt_with_installed_readline" != "no" ; then + case "$opt_with_installed_readline" in + yes) RL_INCLUDE= ;; + *) case "$RL_INCLUDEDIR" in + /usr/include) ;; + *) RL_INCLUDE='-I${RL_INCLUDEDIR}' ;; + esac + ;; + esac + READLINE_DEP= + READLINE_LIB=-lreadline + # section for OS versions that don't allow unresolved symbols + # to be compiled into dynamic libraries. + case "$host_os" in + cygwin*) TILDE_LIB= ;; + esac + else + RL_LIBDIR='$(dot)/$(LIBSUBDIR)/readline' + READLINE_DEP='$(READLINE_LIBRARY)' + # section for OS versions that ship an older/broken version of + # readline as a standard dynamic library and don't allow a + # static version specified as -llibname to override the + # dynamic version + case "${host_os}" in + darwin[[89]]*|darwin10*) READLINE_LIB='${READLINE_LIBRARY}' ;; + *) READLINE_LIB=-lreadline ;; + esac + fi +else + RL_LIBDIR='$(dot)/$(LIBSUBDIR)/readline' + READLINE_LIB= READLINE_DEP= +fi +if test $opt_history = yes || test $opt_bang_history = yes; then + if test $opt_history = yes; then + AC_DEFINE(HISTORY) + fi + if test $opt_bang_history = yes; then + AC_DEFINE(BANG_HISTORY) + fi + if test "$opt_with_installed_readline" != "no"; then + HIST_LIBDIR=$RL_LIBDIR + HISTORY_DEP= + HISTORY_LIB=-lhistory + case "$opt_with_installed_readline" in + yes) RL_INCLUDE= ;; + *) case "$RL_INCLUDEDIR" in + /usr/include) ;; + *) RL_INCLUDE='-I${RL_INCLUDEDIR}' ;; + esac + ;; + esac + else + HIST_LIBDIR='$(dot)/$(LIBSUBDIR)/readline' + HISTORY_DEP='$(HISTORY_LIBRARY)' + # section for OS versions that ship an older version of + # readline as a standard dynamic library and don't allow a + # static version specified as -llibname to override the + # dynamic version + case "${host_os}" in + darwin[[89]]*|darwin10*) HISTORY_LIB='${HISTORY_LIBRARY}' ;; + *) HISTORY_LIB=-lhistory ;; + esac + fi +else + HIST_LIBDIR='$(dot)/$(LIBSUBDIR)/readline' + HISTORY_LIB= HISTORY_DEP= +fi +AC_SUBST(READLINE_LIB) +AC_SUBST(READLINE_DEP) +AC_SUBST(RL_LIBDIR) +AC_SUBST(RL_INCLUDEDIR) +AC_SUBST(RL_INCLUDE) +AC_SUBST(HISTORY_LIB) +AC_SUBST(HISTORY_DEP) +AC_SUBST(HIST_LIBDIR) +AC_SUBST(TILDE_LIB) + +dnl END READLINE and HISTORY LIBRARY SECTION + +dnl programs needed by the build and install process +AC_PROG_INSTALL +AC_CHECK_PROG(AR, ar, , ar) +dnl Set default for ARFLAGS, since autoconf does not have a macro for it. +dnl This allows people to set it when running configure or make +test -n "$ARFLAGS" || ARFLAGS="cr" +AC_PROG_RANLIB +AC_PROG_YACC +AC_PROG_MAKE_SET + +case "$host_os" in +opennt*|interix*) MAKE_SHELL="$INTERIX_ROOT/bin/sh" ;; +*) MAKE_SHELL=/bin/sh ;; +esac +AC_SUBST(MAKE_SHELL) + +dnl this is similar to the expanded AC_PROG_RANLIB +if test x$SIZE = x; then + if test x$ac_tool_prefix = x; then + SIZE=size + else + SIZE=${ac_tool_prefix}size + save_IFS=$IFS ; IFS=: + size_found=0 + for dir in $PATH; do + if test -x $dir/$SIZE ; then + size_found=1 + break + fi + done + if test $size_found -eq 0; then + SIZE=: + fi + IFS=$save_IFS + fi +fi +AC_SUBST(SIZE) + +dnl Turn on any extensions available in the GNU C library. +AC_DEFINE(_GNU_SOURCE, 1) + +dnl C compiler characteristics +AC_C_CONST +AC_C_INLINE +AC_C_BIGENDIAN +AC_C_STRINGIZE +AC_C_LONG_DOUBLE +AC_C_PROTOTYPES +AC_C_CHAR_UNSIGNED +AC_C_VOLATILE +AC_C_RESTRICT + +dnl initialize GNU gettext +AM_GNU_GETTEXT([no-libtool], [need-ngettext], [lib/intl]) + +dnl header files +AC_HEADER_DIRENT +AC_HEADER_TIME + +BASH_HEADER_INTTYPES + +AC_CHECK_HEADERS(unistd.h stdlib.h stdarg.h varargs.h limits.h string.h \ + memory.h locale.h termcap.h termio.h termios.h dlfcn.h \ + stddef.h stdint.h netdb.h pwd.h grp.h strings.h regex.h \ + syslog.h ulimit.h) +AC_CHECK_HEADERS(sys/pte.h sys/stream.h sys/select.h sys/file.h \ + sys/resource.h sys/param.h sys/socket.h sys/stat.h \ + sys/time.h sys/times.h sys/types.h sys/wait.h) +AC_CHECK_HEADERS(netinet/in.h arpa/inet.h) + +dnl sys/ptem.h requires definitions from sys/stream.h on systems where it +dnl exists +AC_CHECK_HEADER(sys/ptem.h, , ,[[ +#if HAVE_SYS_STREAM_H +# include +#endif +]]) + +dnl special checks for libc functions +AC_FUNC_ALLOCA +AC_FUNC_GETPGRP +AC_FUNC_SETVBUF_REVERSED +AC_FUNC_VPRINTF +AC_FUNC_STRCOLL + +dnl if we're not using the bash malloc but require the C alloca, set things +dnl up to build a libmalloc.a containing only alloca.o + +if test "$ac_cv_func_alloca_works" = "no" && test "$opt_bash_malloc" = "no"; then + MALLOC_TARGET=alloca + MALLOC_SRC=alloca.c + + MALLOC_LIB='-lmalloc' + MALLOC_LIBRARY='$(ALLOC_LIBDIR)/libmalloc.a' + MALLOC_LDFLAGS='-L$(ALLOC_LIBDIR)' + MALLOC_DEP='$(MALLOC_LIBRARY)' +fi + +dnl if vprintf is not in libc, see if it's defined in stdio.h +if test "$ac_cv_func_vprintf" = no; then + AC_MSG_CHECKING(for declaration of vprintf in stdio.h) + AC_EGREP_HEADER([[int[ ]*vprintf[^a-zA-Z0-9]]],stdio.h,ac_cv_func_vprintf=yes) + AC_MSG_RESULT($ac_cv_func_vprintf) + if test $ac_cv_func_vprintf = yes; then + AC_DEFINE(HAVE_VPRINTF) + fi +fi + +if test "$ac_cv_func_vprintf" = no && test "$ac_cv_func__doprnt" = "yes"; then + AC_LIBOBJ(vprint) +fi + +dnl signal stuff +AC_TYPE_SIGNAL + +dnl checks for certain version-specific system calls and libc functions +AC_CHECK_FUNC(__setostype, AC_DEFINE(HAVE_SETOSTYPE)) +AC_CHECK_FUNC(wait3, AC_DEFINE(HAVE_WAIT3)) +AC_CHECK_FUNC(isinf, AC_DEFINE(HAVE_ISINF_IN_LIBC)) +AC_CHECK_FUNC(isnan, AC_DEFINE(HAVE_ISNAN_IN_LIBC)) + +dnl checks for missing libc functions +AC_CHECK_FUNC(mkfifo,AC_DEFINE(HAVE_MKFIFO),AC_DEFINE(MKFIFO_MISSING)) + +dnl checks for system calls +AC_CHECK_FUNCS(dup2 eaccess fcntl getdtablesize getgroups gethostname \ + getpagesize getpeername getrlimit getrusage gettimeofday \ + kill killpg lstat readlink sbrk select setdtablesize \ + setitimer tcgetpgrp uname ulimit waitpid) +AC_REPLACE_FUNCS(rename) + +dnl checks for c library functions +AC_CHECK_FUNCS(bcopy bzero confstr fnmatch \ + getaddrinfo gethostbyname getservbyname getservent inet_aton \ + memmove pathconf putenv raise regcomp regexec \ + setenv setlinebuf setlocale setvbuf siginterrupt strchr \ + sysconf syslog tcgetattr times ttyname tzset unsetenv) + +AC_CHECK_FUNCS(vasprintf asprintf) +AC_CHECK_FUNCS(isascii isblank isgraph isprint isspace isxdigit) +AC_CHECK_FUNCS(getpwent getpwnam getpwuid) +AC_REPLACE_FUNCS(getcwd memset) +AC_REPLACE_FUNCS(strcasecmp strcasestr strerror strftime strnlen strpbrk strstr) +AC_REPLACE_FUNCS(strtod strtol strtoul strtoll strtoull strtoimax strtoumax) +AC_REPLACE_FUNCS(fdprintf) + +AC_CHECK_DECLS([confstr]) +AC_CHECK_DECLS([printf]) +AC_CHECK_DECLS([sbrk]) +AC_CHECK_DECLS([setregid]) +AC_CHECK_DECLS([strcpy]) +AC_CHECK_DECLS([strsignal]) + +dnl Extra test to detect the horribly broken HP/UX 11.00 strtold(3) +AC_CHECK_DECLS([strtold], [ + AC_MSG_CHECKING([for broken strtold]) + AC_CACHE_VAL(bash_cv_strtold_broken, + [AC_TRY_COMPILE( + [#include ], + [int main() { long double r; char *foo, bar; r = strtold(foo, &bar);}], + bash_cv_strtold_broken=no, bash_cv_strtold_broken=yes, + [AC_MSG_WARN(cannot check for broken strtold if cross-compiling, defaulting to no)]) + ] + ) + AC_MSG_RESULT($bash_cv_strtold_broken) + if test "$bash_cv_strtold_broken" = "yes" ; then + AC_DEFINE(STRTOLD_BROKEN) + fi +]) + + +BASH_CHECK_DECL(strtoimax) +BASH_CHECK_DECL(strtol) +BASH_CHECK_DECL(strtoll) +BASH_CHECK_DECL(strtoul) +BASH_CHECK_DECL(strtoull) +BASH_CHECK_DECL(strtoumax) + +AC_FUNC_MKTIME + +dnl +dnl Checks for lib/intl and related code (uses some of the output from +dnl AM_GNU_GETTEXT) +dnl + +AC_CHECK_HEADERS([argz.h errno.h fcntl.h malloc.h stdio_ext.h]) + +dnl AC_FUNC_MALLOC +AC_FUNC_MMAP +AC_CHECK_FUNCS([__argz_count __argz_next __argz_stringify dcgettext mempcpy \ + munmap stpcpy strcspn strdup]) + +INTL_DEP= INTL_INC= LIBINTL_H= +if test "x$USE_INCLUDED_LIBINTL" = "xyes"; then + INTL_DEP='${INTL_LIBDIR}/libintl.a' + INTL_INC='-I${INTL_LIBSRC} -I${INTL_BUILDDIR}' + LIBINTL_H='${INTL_BUILDDIR}/libintl.h' +fi +AC_SUBST(INTL_DEP) +AC_SUBST(INTL_INC) +AC_SUBST(LIBINTL_H) + +dnl +dnl End of checks needed by files in lib/intl +dnl + +BASH_CHECK_MULTIBYTE + +dnl checks for the dynamic loading library functions in libc and libdl +if test "$opt_static_link" != yes; then +AC_CHECK_LIB(dl, dlopen) +AC_CHECK_FUNCS(dlopen dlclose dlsym) +fi + +dnl this defines HAVE_DECL_SYS_SIGLIST +AC_DECL_SYS_SIGLIST + +dnl network functions -- check for inet_aton again +if test "$ac_cv_func_inet_aton" != 'yes'; then +BASH_FUNC_INET_ATON +fi + +dnl libraries +dnl this is reportedly no longer necessary for irix[56].? +case "$host_os" in +irix4*) AC_CHECK_LIB(sun, getpwent) ;; +esac + +dnl check for getpeername in the socket library only if it's not in libc +if test "$ac_cv_func_getpeername" = no; then + BASH_CHECK_LIB_SOCKET +fi +dnl check for gethostbyname in socket libraries if it's not in libc +if test "$ac_cv_func_gethostbyname" = no; then + BASH_FUNC_GETHOSTBYNAME +fi + +dnl system types +AC_TYPE_GETGROUPS +AC_TYPE_OFF_T +AC_TYPE_MODE_T +AC_TYPE_UID_T +AC_TYPE_PID_T +AC_TYPE_SIZE_T +AC_CHECK_TYPE(ssize_t, int) +AC_CHECK_TYPE(time_t, long) + +BASH_TYPE_LONG_LONG +BASH_TYPE_UNSIGNED_LONG_LONG + +AC_TYPE_SIGNAL +BASH_TYPE_SIG_ATOMIC_T + +AC_CHECK_SIZEOF(char, 1) +AC_CHECK_SIZEOF(short, 2) +AC_CHECK_SIZEOF(int, 4) +AC_CHECK_SIZEOF(long, 4) +AC_CHECK_SIZEOF(char *, 4) +AC_CHECK_SIZEOF(double, 8) +AC_CHECK_SIZEOF([long long], 8) + +AC_CHECK_TYPE(u_int, [unsigned int]) +AC_CHECK_TYPE(u_long, [unsigned long]) + +BASH_TYPE_BITS16_T +BASH_TYPE_U_BITS16_T +BASH_TYPE_BITS32_T +BASH_TYPE_U_BITS32_T +BASH_TYPE_BITS64_T + +BASH_TYPE_PTRDIFF_T + +dnl structures +AC_HEADER_STAT + +dnl system services +AC_SYS_INTERPRETER +if test $ac_cv_sys_interpreter = yes; then +AC_DEFINE(HAVE_HASH_BANG_EXEC) +fi + +dnl Miscellaneous Bash tests +if test "$ac_cv_func_lstat" = "no"; then +BASH_FUNC_LSTAT +fi + +dnl behavior of system calls and library functions +BASH_FUNC_CTYPE_NONASCII +BASH_FUNC_DUP2_CLOEXEC_CHECK +BASH_SYS_PGRP_SYNC +BASH_SYS_SIGNAL_VINTAGE + +dnl checking for the presence of certain library symbols +BASH_SYS_ERRLIST +BASH_SYS_SIGLIST +BASH_UNDER_SYS_SIGLIST + +dnl various system types +BASH_TYPE_SIGHANDLER +BASH_CHECK_TYPE(clock_t, [#include ], long) +BASH_CHECK_TYPE(sigset_t, [#include ], int) +BASH_CHECK_TYPE(quad_t, , long, HAVE_QUAD_T) +BASH_CHECK_TYPE(intmax_t, , $bash_cv_type_long_long) +BASH_CHECK_TYPE(uintmax_t, , $bash_cv_type_unsigned_long_long) +if test "$ac_cv_header_sys_socket_h" = "yes"; then +BASH_CHECK_TYPE(socklen_t, [#include ], int, HAVE_SOCKLEN_T) +fi +BASH_TYPE_RLIMIT + +dnl presence and contents of structures used by system calls +BASH_STRUCT_TERMIOS_LDISC +BASH_STRUCT_TERMIO_LDISC +BASH_STRUCT_DIRENT_D_INO +BASH_STRUCT_DIRENT_D_FILENO +BASH_STRUCT_DIRENT_D_NAMLEN +BASH_STRUCT_WINSIZE +BASH_STRUCT_TIMEVAL +AC_CHECK_MEMBERS([struct stat.st_blocks]) +AC_STRUCT_TM +AC_STRUCT_TIMEZONE +BASH_STRUCT_TIMEZONE + +dnl presence and behavior of C library functions +BASH_FUNC_STRSIGNAL +BASH_FUNC_OPENDIR_CHECK +BASH_FUNC_ULIMIT_MAXFDS +BASH_FUNC_FPURGE +BASH_FUNC_GETENV +if test "$ac_cv_func_getcwd" = "yes"; then +BASH_FUNC_GETCWD +fi +BASH_FUNC_POSIX_SETJMP +BASH_FUNC_STRCOLL +BASH_FUNC_SNPRINTF +BASH_FUNC_VSNPRINTF + +dnl If putenv or unsetenv is not present, set the right define so the +dnl prototype and declaration in lib/sh/getenv.c will be standard-conformant + +if test "$ac_cv_func_putenv" = "yes"; then +BASH_FUNC_STD_PUTENV +else +AC_DEFINE(HAVE_STD_PUTENV) +fi +if test "$ac_cv_func_unsetenv" = "yes"; then +BASH_FUNC_STD_UNSETENV +else +AC_DEFINE(HAVE_STD_UNSETENV) +fi + +BASH_FUNC_PRINTF_A_FORMAT + +dnl presence and behavior of OS functions +BASH_SYS_REINSTALL_SIGHANDLERS +BASH_SYS_JOB_CONTROL_MISSING +BASH_SYS_NAMED_PIPES + +dnl presence of certain CPP defines +AC_HEADER_TIOCGWINSZ +BASH_HAVE_TIOCSTAT +BASH_HAVE_FIONREAD + +BASH_CHECK_WCONTINUED + +dnl miscellaneous +BASH_CHECK_SPEED_T +BASH_CHECK_GETPW_FUNCS +BASH_CHECK_RTSIGS +BASH_CHECK_SYS_SIGLIST + +dnl special checks +case "$host_os" in +hpux*) BASH_CHECK_KERNEL_RLIMIT ;; +esac + +if test "$opt_readline" = yes; then +dnl yuck +case "$host_os" in +aix*) prefer_curses=yes ;; +esac +BASH_CHECK_LIB_TERMCAP +fi +AC_SUBST(TERMCAP_LIB) +AC_SUBST(TERMCAP_DEP) + +BASH_CHECK_DEV_FD +BASH_CHECK_DEV_STDIN +BASH_SYS_DEFAULT_MAIL_DIR + +if test "$bash_cv_job_control_missing" = missing; then + opt_job_control=no +fi + +if test "$opt_job_control" = yes; then +AC_DEFINE(JOB_CONTROL) +JOBS_O=jobs.o +else +JOBS_O=nojobs.o +fi + +AC_SUBST(JOBS_O) + +dnl Defines that we want to propagate to the Makefiles in subdirectories, +dnl like glob and readline + +LOCAL_DEFS=-DSHELL + +dnl use this section to possibly define more cpp variables, specify local +dnl libraries, and specify any additional local cc or ld flags +dnl +dnl this should really go away someday + +case "${host_os}" in +sysv4.2*) AC_DEFINE(SVR4_2) + AC_DEFINE(SVR4) ;; +sysv4*) AC_DEFINE(SVR4) ;; +sysv5*) AC_DEFINE(SVR5) ;; +hpux9*) LOCAL_CFLAGS="-DHPUX9 -DHPUX" ;; +hpux*) LOCAL_CFLAGS=-DHPUX ;; +dgux*) LOCAL_CFLAGS=-D_DGUX_SOURCE; LOCAL_LIBS=-ldgc ;; +isc*) LOCAL_CFLAGS=-Disc386 ;; +rhapsody*) LOCAL_CFLAGS=-DRHAPSODY ;; +darwin*) LOCAL_CFLAGS=-DMACOSX ;; +sco3.2v5*) LOCAL_CFLAGS="-b elf -DWAITPID_BROKEN -DPATH_MAX=1024" ;; +sco3.2v4*) LOCAL_CFLAGS="-DMUST_UNBLOCK_CHLD -DPATH_MAX=1024" ;; +sco3.2*) LOCAL_CFLAGS=-DMUST_UNBLOCK_CHLD ;; +sunos4*) LOCAL_CFLAGS=-DSunOS4 ;; +solaris2.5*) LOCAL_CFLAGS="-DSunOS5 -DSOLARIS" ;; +solaris2.8*) LOCAL_CFLAGS=-DSOLARIS ;; +solaris2.9*) LOCAL_CFLAGS=-DSOLARIS ;; +solaris2.10*) LOCAL_CFLAGS=-DSOLARIS ;; +solaris2*) LOCAL_CFLAGS=-DSOLARIS ;; +lynxos*) LOCAL_CFLAGS=-DRECYCLES_PIDS ;; +linux*) LOCAL_LDFLAGS=-rdynamic # allow dynamic loading + case "`uname -r`" in + 2.[[456789]]*|3*) AC_DEFINE(PGRP_PIPE) ;; + esac ;; +*qnx6*) LOCAL_CFLAGS="-Dqnx -Dqnx6" LOCAL_LIBS="-lncurses" ;; +*qnx*) LOCAL_CFLAGS="-Dqnx -F -3s" LOCAL_LDFLAGS="-3s" LOCAL_LIBS="-lunix -lncurses" ;; +powerux*) LOCAL_LIBS="-lgen" ;; +cygwin*) LOCAL_CFLAGS=-DRECYCLES_PIDS ;; +opennt*|interix*) LOCAL_CFLAGS="-DNO_MAIN_ENV_ARG -DBROKEN_DIRENT_D_INO -D_POSIX_SOURCE -D_ALL_SOURCE" ;; +esac + +dnl Stanza for OS/compiler pair-specific flags +case "${host_os}-${CC}" in +aix4.2*-*gcc*) LOCAL_LDFLAGS="-Xlinker -bexpall -Xlinker -brtl" ;; +aix4.2*) LOCAL_LDFLAGS="-bexpall -brtl" ;; +bsdi4*-*gcc*) LOCAL_LDFLAGS="-rdynamic" ;; # allow dynamic loading, like Linux +esac + +dnl FreeBSD-3.x can have either a.out or ELF +case "${host_os}" in +freebsd[[3-9]]*) + if test -x /usr/bin/objformat && test "`/usr/bin/objformat`" = "elf" ; then + LOCAL_LDFLAGS=-rdynamic # allow dynamic loading + fi ;; +freebsdelf*) LOCAL_LDFLAGS=-rdynamic ;; # allow dynamic loading +dragonfly*) LOCAL_LDFLAGS=-rdynamic ;; # allow dynamic loading +esac + +case "$host_cpu" in +*cray*) LOCAL_CFLAGS="-DCRAY" ;; # shell var so config.h can use it +esac + +case "$host_cpu-$host_os" in +ibmrt-*bsd4*) LOCAL_CFLAGS="-ma -U__STDC__" ;; +esac + +case "$host_cpu-$host_vendor-$host_os" in +m88k-motorola-sysv3) LOCAL_CFLAGS=-DWAITPID_BROKEN ;; +mips-pyramid-sysv4) LOCAL_CFLAGS=-Xa ;; +esac + +# +# Shared object configuration section. These values are generated by +# ${srcdir}/support/shobj-conf +# +if test "$ac_cv_func_dlopen" = "yes" && test -f ${srcdir}/support/shobj-conf +then + AC_MSG_CHECKING(shared object configuration for loadable builtins) + eval `${CONFIG_SHELL-/bin/sh} ${srcdir}/support/shobj-conf -C "${CC}" -c "${host_cpu}" -o "${host_os}" -v "${host_vendor}"` + AC_SUBST(SHOBJ_CC) + AC_SUBST(SHOBJ_CFLAGS) + AC_SUBST(SHOBJ_LD) + AC_SUBST(SHOBJ_LDFLAGS) + AC_SUBST(SHOBJ_XLDFLAGS) + AC_SUBST(SHOBJ_LIBS) + AC_SUBST(SHOBJ_STATUS) + AC_MSG_RESULT($SHOBJ_STATUS) +fi + +# try to create a directory tree if the source is elsewhere +# this should be packaged into a script accessible via ${srcdir}/support +case "$srcdir" in +.) ;; +*) for d in doc tests support lib examples; do # dirs + test -d $d || mkdir $d + done + for ld in readline glob tilde malloc sh termcap; do # libdirs + test -d lib/$ld || mkdir lib/$ld + done + test -d examples/loadables || mkdir examples/loadables # loadable builtins + test -d examples/loadables/perl || mkdir examples/loadables/perl + ;; +esac + +BUILD_DIR=`pwd` +case "$BUILD_DIR" in +*\ *) BUILD_DIR=`echo "$BUILD_DIR" | sed 's: :\\\\ :g'` ;; +*) ;; +esac + +if test -z "$localedir"; then + localedir='${datarootdir}/locale' +fi +if test -z "$datarootdir"; then + datarootdir='${prefix}/share' +fi + +AC_SUBST(PROFILE_FLAGS) + +AC_SUBST(incdir) +AC_SUBST(BUILD_DIR) + +# Some versions of autoconf don't substitute these automatically +AC_SUBST(datarootdir) +AC_SUBST(localedir) + +AC_SUBST(YACC) +AC_SUBST(AR) +AC_SUBST(ARFLAGS) + +AC_SUBST(BASHVERS) +AC_SUBST(RELSTATUS) +AC_SUBST(DEBUG) +AC_SUBST(MALLOC_DEBUG) + +AC_SUBST(host_cpu) +AC_SUBST(host_vendor) +AC_SUBST(host_os) + +AC_SUBST(LOCAL_LIBS) +AC_SUBST(LOCAL_CFLAGS) +AC_SUBST(LOCAL_LDFLAGS) +AC_SUBST(LOCAL_DEFS) + +#AC_SUBST(ALLOCA_SOURCE) +#AC_SUBST(ALLOCA_OBJECT) + +AC_OUTPUT([Makefile builtins/Makefile lib/readline/Makefile lib/glob/Makefile \ + lib/intl/Makefile \ + lib/malloc/Makefile lib/sh/Makefile lib/termcap/Makefile \ + lib/tilde/Makefile doc/Makefile support/Makefile po/Makefile.in \ + examples/loadables/Makefile examples/loadables/perl/Makefile], +[ +# Makefile uses this timestamp file to record whether config.h is up to date. +echo timestamp > stamp-h +]) diff --git a/doc/bash.1 b/doc/bash.1 index 70085bae6..bec857637 100644 --- a/doc/bash.1 +++ b/doc/bash.1 @@ -9429,7 +9429,8 @@ The maximum amount of cpu time in seconds The maximum number of processes available to a single user .TP .B \-v -The maximum amount of virtual memory available to the shell +The maximum amount of virtual memory available to the shell and, on +some systems, to its children .TP .B \-x The maximum number of file locks diff --git a/doc/bashref.texi b/doc/bashref.texi index da7378c68..b9e6f7394 100644 --- a/doc/bashref.texi +++ b/doc/bashref.texi @@ -3939,7 +3939,8 @@ The maximum amount of cpu time in seconds. The maximum number of processes available to a single user. @item -v -The maximum amount of virtual memory available to the process. +The maximum amount of virtual memory available to the shell, and, on +some systems, to its children. @item -x The maximum number of file locks. diff --git a/expr.c b/expr.c index 446e67842..ef6d04b73 100644 --- a/expr.c +++ b/expr.c @@ -137,6 +137,28 @@ highest precedence. */ #define EXP_HIGHEST expcomma +#ifndef MAX_INT_LEN +# define MAX_INT_LEN 32 +#endif + +struct lvalue +{ + char *tokstr; /* possibly-rewritten lvalue if not NULL */ + intmax_t tokval; /* expression evaluated value */ + SHELL_VAR *tokvar; /* variable described by array or var reference */ + intmax_t ind; /* array index if not -1 */ +}; + +/* A structure defining a single expression context. */ +typedef struct { + int curtok, lasttok; + char *expression, *tp, *lasttp; + intmax_t tokval; + char *tokstr; + int noeval; + struct lvalue lval; +} EXPR_CONTEXT; + static char *expression; /* The current expression */ static char *tp; /* token lexical position */ static char *lasttp; /* pointer to last token position */ @@ -148,10 +170,17 @@ static intmax_t tokval; /* current token value */ static int noeval; /* set to 1 if no assignment to be done */ static procenv_t evalbuf; +static struct lvalue curlval = {0, 0, 0, -1}; +static struct lvalue lastlval = {0, 0, 0, -1}; + static int _is_arithop __P((int)); static void readtok __P((void)); /* lexical analyzer */ -static intmax_t expr_streval __P((char *, int)); +static void init_lvalue __P((struct lvalue *)); +static struct lvalue *alloc_lvalue __P((void)); +static void free_lvalue __P((struct lvalue *)); + +static intmax_t expr_streval __P((char *, int, struct lvalue *)); static intmax_t strlong __P((char *)); static void evalerror __P((const char *)); @@ -159,6 +188,7 @@ static void pushexp __P((void)); static void popexp __P((void)); static void expr_unwind __P((void)); static void expr_bind_variable __P((char *, char *)); +static void expr_bind_array_element __P((char *, arrayind_t, char *)); static intmax_t subexpr __P((char *)); @@ -179,23 +209,6 @@ static intmax_t exppower __P((void)); static intmax_t exp1 __P((void)); static intmax_t exp0 __P((void)); -/* A structure defining a single expression context. */ -typedef struct { - int curtok, lasttok; - char *expression, *tp, *lasttp; - intmax_t tokval; - char *tokstr; - int noeval; -} EXPR_CONTEXT; - -#ifdef INCLUDE_UNUSED -/* Not used yet. */ -typedef struct { - char *tokstr; - intmax_t tokval; -} LVALUE; -#endif - /* Global var which contains the stack of expression contexts. */ static EXPR_CONTEXT **expr_stack; static int expr_depth; /* Location in the stack. */ @@ -217,6 +230,7 @@ extern const char * const bash_badsub_errmsg; (X)->tokval = tokval; \ (X)->tokstr = tokstr; \ (X)->noeval = noeval; \ + (X)->lval = curlval; \ } while (0) #define RESTORETOK(X) \ @@ -228,6 +242,7 @@ extern const char * const bash_badsub_errmsg; tokval = (X)->tokval; \ tokstr = (X)->tokstr; \ noeval = (X)->noeval; \ + curlval = (X)->lval; \ } while (0) /* Push and save away the contents of the globals describing the @@ -298,6 +313,32 @@ expr_bind_variable (lhs, rhs) stupidly_hack_special_variables (lhs); } +/* Rewrite tok, which is of the form vname[expression], to vname[ind], where + IND is the already-calculated value of expression. */ +static void +expr_bind_array_element (tok, ind, rhs) + char *tok; + arrayind_t ind; + char *rhs; +{ + char *lhs, *vname; + size_t llen; + char ibuf[INT_STRLEN_BOUND (arrayind_t) + 1], *istr; + + istr = fmtumax (ind, 10, ibuf, sizeof (ibuf), 0); + vname = array_variable_name (tok, (char **)NULL, (int *)NULL); + + llen = strlen (vname) + sizeof (ibuf) + 3; + lhs = xmalloc (llen); + + sprintf (lhs, "%s[%s]", vname, istr); /* XXX */ + + expr_bind_variable (lhs, rhs); +/*itrace("expr_bind_array_element: %s=%s", lhs, rhs);*/ + free (vname); + free (lhs); +} + /* Evaluate EXPR, and return the arithmetic result. If VALIDP is non-null, a zero is stored into the location to which it points if the expression is invalid, non-zero otherwise. If a non-zero @@ -364,12 +405,14 @@ subexpr (expr) return (0); pushexp (); - curtok = lasttok = 0; expression = savestring (expr); tp = expression; + curtok = lasttok = 0; tokstr = (char *)NULL; tokval = 0; + init_lvalue (&curlval); + lastlval = curlval; readtok (); @@ -406,6 +449,7 @@ expassign () { register intmax_t value; char *lhs, *rhs; + arrayind_t lind; value = expcond (); if (curtok == EQ || curtok == OP_ASSIGN) @@ -425,6 +469,8 @@ expassign () } lhs = savestring (tokstr); + /* save ind in case rhs is string var and evaluation overwrites it */ + lind = curlval.ind; readtok (); value = expassign (); @@ -476,7 +522,12 @@ expassign () rhs = itos (value); if (noeval == 0) - expr_bind_variable (lhs, rhs); + { + if (lind != -1) + expr_bind_array_element (lhs, lind, rhs); + else + expr_bind_variable (lhs, rhs); + } free (rhs); free (lhs); FREE (tokstr); @@ -828,7 +879,12 @@ exp0 () v2 = tokval + ((stok == PREINC) ? 1 : -1); vincdec = itos (v2); if (noeval == 0) - expr_bind_variable (tokstr, vincdec); + { + if (curlval.ind != -1) + expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec); + else + expr_bind_variable (tokstr, vincdec); + } free (vincdec); val = v2; @@ -873,12 +929,18 @@ exp0 () /* restore certain portions of EC */ tokstr = ec.tokstr; noeval = ec.noeval; + curlval = ec.lval; lasttok = STR; /* ec.curtok */ v2 = val + ((stok == POSTINC) ? 1 : -1); vincdec = itos (v2); if (noeval == 0) - expr_bind_variable (tokstr, vincdec); + { + if (curlval.ind != -1) + expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec); + else + expr_bind_variable (tokstr, vincdec); + } free (vincdec); curtok = NUM; /* make sure x++=7 is flagged as an error */ } @@ -899,14 +961,44 @@ exp0 () return (val); } +static void +init_lvalue (lv) + struct lvalue *lv; +{ + lv->tokstr = 0; + lv->tokvar = 0; + lv->tokval = lv->ind = -1; +} + +static struct lvalue * +alloc_lvalue () +{ + struct lvalue *lv; + + lv = xmalloc (sizeof (struct lvalue)); + init_lvalue (lv); + return (lv); +} + +static void +free_lvalue (lv) + struct lvalue *lv; +{ + free (lv); /* should be inlined */ +} + static intmax_t -expr_streval (tok, e) +expr_streval (tok, e, lvalue) char *tok; int e; + struct lvalue *lvalue; { SHELL_VAR *v; char *value; intmax_t tval; +#if defined (ARRAY_VARS) + arrayind_t ind; +#endif /* [[[[[ */ #if defined (ARRAY_VARS) @@ -941,18 +1033,27 @@ expr_streval (tok, e) jump_to_top_level (FORCE_EOF); } + ind = -1; #if defined (ARRAY_VARS) /* Second argument of 0 to get_array_value means that we don't allow references like array[@]. In this case, get_array_value is just like get_variable_value in that it does not return newly-allocated memory or quote the results. */ - value = (e == ']') ? get_array_value (tok, 0, (int *)NULL) : get_variable_value (v); + value = (e == ']') ? get_array_value (tok, 0, (int *)NULL, &ind) : get_variable_value (v); #else value = get_variable_value (v); #endif tval = (value && *value) ? subexpr (value) : 0; + if (lvalue) + { + lvalue->tokstr = tok; /* XXX */ + lvalue->tokval = tval; + lvalue->tokvar = v; /* XXX */ + lvalue->ind = ind; + } + return (tval); } @@ -1024,6 +1125,7 @@ readtok () register char *cp, *xp; register unsigned char c, c1; register int e; + struct lvalue lval; /* Skip leading whitespace. */ cp = tp; @@ -1075,6 +1177,7 @@ readtok () tokstr = savestring (tp); *cp = c; + /* XXX - make peektok part of saved token state? */ SAVETOK (&ec); tokstr = (char *)NULL; /* keep it from being freed */ tp = savecp = cp; @@ -1090,7 +1193,10 @@ readtok () /* The tests for PREINC and PREDEC aren't strictly correct, but they preserve old behavior if a construct like --x=9 is given. */ if (lasttok == PREINC || lasttok == PREDEC || peektok != EQ) - tokval = expr_streval (tokstr, e); + { + lastlval = curlval; + tokval = expr_streval (tokstr, e, &curlval); + } else tokval = 0; diff --git a/expr.c~ b/expr.c~ new file mode 100644 index 000000000..3ed680370 --- /dev/null +++ b/expr.c~ @@ -0,0 +1,1446 @@ +/* expr.c -- arithmetic expression evaluation. */ + +/* Copyright (C) 1990-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 . +*/ + +/* + All arithmetic is done as intmax_t integers with no checking for overflow + (though division by 0 is caught and flagged as an error). + + The following operators are handled, grouped into a set of levels in + order of decreasing precedence. + + "id++", "id--" [post-increment and post-decrement] + "++id", "--id" [pre-increment and pre-decrement] + "-", "+" [(unary operators)] + "!", "~" + "**" [(exponentiation)] + "*", "/", "%" + "+", "-" + "<<", ">>" + "<=", ">=", "<", ">" + "==", "!=" + "&" + "^" + "|" + "&&" + "||" + "expr ? expr : expr" + "=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|=" + , [comma] + + (Note that most of these operators have special meaning to bash, and an + entire expression should be quoted, e.g. "a=$a+1" or "a=a+1" to ensure + that it is passed intact to the evaluator when using `let'. When using + the $[] or $(( )) forms, the text between the `[' and `]' or `((' and `))' + is treated as if in double quotes.) + + Sub-expressions within parentheses have a precedence level greater than + all of the above levels and are evaluated first. Within a single prece- + dence group, evaluation is left-to-right, except for the arithmetic + assignment operator (`='), which is evaluated right-to-left (as in C). + + The expression evaluator returns the value of the expression (assignment + statements have as a value what is returned by the RHS). The `let' + builtin, on the other hand, returns 0 if the last expression evaluates to + a non-zero, and 1 otherwise. + + Implementation is a recursive-descent parser. + + Chet Ramey + chet@ins.CWRU.Edu +*/ + +#include "config.h" + +#include +#include "bashansi.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "chartypes.h" +#include "bashintl.h" + +#include "shell.h" + +/* Because of the $((...)) construct, expressions may include newlines. + Here is a macro which accepts newlines, tabs and spaces as whitespace. */ +#define cr_whitespace(c) (whitespace(c) || ((c) == '\n')) + +/* Size be which the expression stack grows when neccessary. */ +#define EXPR_STACK_GROW_SIZE 10 + +/* Maximum amount of recursion allowed. This prevents a non-integer + variable such as "num=num+2" from infinitely adding to itself when + "let num=num+2" is given. */ +#define MAX_EXPR_RECURSION_LEVEL 1024 + +/* The Tokens. Singing "The Lion Sleeps Tonight". */ + +#define EQEQ 1 /* "==" */ +#define NEQ 2 /* "!=" */ +#define LEQ 3 /* "<=" */ +#define GEQ 4 /* ">=" */ +#define STR 5 /* string */ +#define NUM 6 /* number */ +#define LAND 7 /* "&&" Logical AND */ +#define LOR 8 /* "||" Logical OR */ +#define LSH 9 /* "<<" Left SHift */ +#define RSH 10 /* ">>" Right SHift */ +#define OP_ASSIGN 11 /* op= expassign as in Posix.2 */ +#define COND 12 /* exp1 ? exp2 : exp3 */ +#define POWER 13 /* exp1**exp2 */ +#define PREINC 14 /* ++var */ +#define PREDEC 15 /* --var */ +#define POSTINC 16 /* var++ */ +#define POSTDEC 17 /* var-- */ +#define EQ '=' +#define GT '>' +#define LT '<' +#define PLUS '+' +#define MINUS '-' +#define MUL '*' +#define DIV '/' +#define MOD '%' +#define NOT '!' +#define LPAR '(' +#define RPAR ')' +#define BAND '&' /* Bitwise AND */ +#define BOR '|' /* Bitwise OR. */ +#define BXOR '^' /* Bitwise eXclusive OR. */ +#define BNOT '~' /* Bitwise NOT; Two's complement. */ +#define QUES '?' +#define COL ':' +#define COMMA ',' + +/* This should be the function corresponding to the operator with the + highest precedence. */ +#define EXP_HIGHEST expcomma + +#ifndef MAX_INT_LEN +# define MAX_INT_LEN 32 +#endif + +struct lvalue +{ + char *tokstr; /* possibly-rewritten lvalue if not NULL */ + intmax_t tokval; /* expression evaluated value */ + SHELL_VAR *tokvar; /* variable described by array or var reference */ + intmax_t ind; /* array index if not -1 */ +}; + +/* A structure defining a single expression context. */ +typedef struct { + int curtok, lasttok; + char *expression, *tp, *lasttp; + intmax_t tokval; + char *tokstr; + int noeval; + struct lvalue lval; +} EXPR_CONTEXT; + +static char *expression; /* The current expression */ +static char *tp; /* token lexical position */ +static char *lasttp; /* pointer to last token position */ +static int curtok; /* the current token */ +static int lasttok; /* the previous token */ +static int assigntok; /* the OP in OP= */ +static char *tokstr; /* current token string */ +static intmax_t tokval; /* current token value */ +static int noeval; /* set to 1 if no assignment to be done */ +static procenv_t evalbuf; + +static struct lvalue curlval = {0, 0, 0, -1}; +static struct lvalue lastlval = {0, 0, 0, -1}; + +static int _is_arithop __P((int)); +static void readtok __P((void)); /* lexical analyzer */ + +static intmax_t expr_streval __P((char *, int, struct lvalue *)); +static intmax_t strlong __P((char *)); +static void evalerror __P((const char *)); + +static void pushexp __P((void)); +static void popexp __P((void)); +static void expr_unwind __P((void)); +static void expr_bind_variable __P((char *, char *)); +static void expr_bind_array_element __P((char *, arrayind_t, char *)); + +static intmax_t subexpr __P((char *)); + +static intmax_t expcomma __P((void)); +static intmax_t expassign __P((void)); +static intmax_t expcond __P((void)); +static intmax_t explor __P((void)); +static intmax_t expland __P((void)); +static intmax_t expbor __P((void)); +static intmax_t expbxor __P((void)); +static intmax_t expband __P((void)); +static intmax_t exp5 __P((void)); +static intmax_t exp4 __P((void)); +static intmax_t expshift __P((void)); +static intmax_t exp3 __P((void)); +static intmax_t exp2 __P((void)); +static intmax_t exppower __P((void)); +static intmax_t exp1 __P((void)); +static intmax_t exp0 __P((void)); + +/* Global var which contains the stack of expression contexts. */ +static EXPR_CONTEXT **expr_stack; +static int expr_depth; /* Location in the stack. */ +static int expr_stack_size; /* Number of slots already allocated. */ + +extern char *this_command_name; +extern int unbound_vars_is_error, last_command_exit_value; + +#if defined (ARRAY_VARS) +extern const char * const bash_badsub_errmsg; +#endif + +#define SAVETOK(X) \ + do { \ + (X)->curtok = curtok; \ + (X)->lasttok = lasttok; \ + (X)->tp = tp; \ + (X)->lasttp = lasttp; \ + (X)->tokval = tokval; \ + (X)->tokstr = tokstr; \ + (X)->noeval = noeval; \ + (X)->lval = curlval; \ + } while (0) + +#define RESTORETOK(X) \ + do { \ + curtok = (X)->curtok; \ + lasttok = (X)->lasttok; \ + tp = (X)->tp; \ + lasttp = (X)->lasttp; \ + tokval = (X)->tokval; \ + tokstr = (X)->tokstr; \ + noeval = (X)->noeval; \ + curlval = (X)->lval; \ + } while (0) + +/* Push and save away the contents of the globals describing the + current expression context. */ +static void +pushexp () +{ + EXPR_CONTEXT *context; + + if (expr_depth >= MAX_EXPR_RECURSION_LEVEL) + evalerror (_("expression recursion level exceeded")); + + if (expr_depth >= expr_stack_size) + { + expr_stack_size += EXPR_STACK_GROW_SIZE; + expr_stack = (EXPR_CONTEXT **)xrealloc (expr_stack, expr_stack_size * sizeof (EXPR_CONTEXT *)); + } + + context = (EXPR_CONTEXT *)xmalloc (sizeof (EXPR_CONTEXT)); + + context->expression = expression; + SAVETOK(context); + + expr_stack[expr_depth++] = context; +} + +/* Pop the the contents of the expression context stack into the + globals describing the current expression context. */ +static void +popexp () +{ + EXPR_CONTEXT *context; + + if (expr_depth == 0) + evalerror (_("recursion stack underflow")); + + context = expr_stack[--expr_depth]; + + expression = context->expression; + RESTORETOK (context); + + free (context); +} + +static void +expr_unwind () +{ + while (--expr_depth > 0) + { + if (expr_stack[expr_depth]->tokstr) + free (expr_stack[expr_depth]->tokstr); + + if (expr_stack[expr_depth]->expression) + free (expr_stack[expr_depth]->expression); + + free (expr_stack[expr_depth]); + } + free (expr_stack[expr_depth]); /* free the allocated EXPR_CONTEXT */ + + noeval = 0; /* XXX */ +} + +static void +expr_bind_variable (lhs, rhs) + char *lhs, *rhs; +{ + (void)bind_int_variable (lhs, rhs); + stupidly_hack_special_variables (lhs); +} + +/* Rewrite tok, which is of the form vname[expression], to vname[ind], where + IND is the already-calculated value of expression. */ +static void +expr_bind_array_element (tok, ind, rhs) + char *tok; + arrayind_t ind; + char *rhs; +{ + char *lhs, *vname; + size_t llen; + char ibuf[INT_STRLEN_BOUND (arrayind_t) + 1], *istr; + + istr = fmtulong (ind, 10, ibuf, sizeof (ibuf), 0); + vname = array_variable_name (tok, (char **)NULL, (int *)NULL); + + llen = strlen (vname) + sizeof (ibuf) + 3; + lhs = xmalloc (llen); + + sprintf (lhs, "%s[%s]", vname, istr); /* XXX */ + + expr_bind_variable (lhs, rhs); +/*itrace("expr_bind_array_element: %s=%s", lhs, rhs);*/ + free (vname); + free (lhs); +} + +/* Evaluate EXPR, and return the arithmetic result. If VALIDP is + non-null, a zero is stored into the location to which it points + if the expression is invalid, non-zero otherwise. If a non-zero + value is returned in *VALIDP, the return value of evalexp() may + be used. + + The `while' loop after the longjmp is caught relies on the above + implementation of pushexp and popexp leaving in expr_stack[0] the + values that the variables had when the program started. That is, + the first things saved are the initial values of the variables that + were assigned at program startup or by the compiler. Therefore, it is + safe to let the loop terminate when expr_depth == 0, without freeing up + any of the expr_depth[0] stuff. */ +intmax_t +evalexp (expr, validp) + char *expr; + int *validp; +{ + intmax_t val; + int c; + procenv_t oevalbuf; + + val = 0; + noeval = 0; + + FASTCOPY (evalbuf, oevalbuf, sizeof (evalbuf)); + + c = setjmp (evalbuf); + + if (c) + { + FREE (tokstr); + FREE (expression); + tokstr = expression = (char *)NULL; + + expr_unwind (); + + if (validp) + *validp = 0; + return (0); + } + + val = subexpr (expr); + + if (validp) + *validp = 1; + + FASTCOPY (oevalbuf, evalbuf, sizeof (evalbuf)); + + return (val); +} + +static intmax_t +subexpr (expr) + char *expr; +{ + intmax_t val; + char *p; + + for (p = expr; p && *p && cr_whitespace (*p); p++) + ; + + if (p == NULL || *p == '\0') + return (0); + + pushexp (); + curtok = lasttok = 0; + expression = savestring (expr); + tp = expression; + + tokstr = (char *)NULL; + tokval = 0; + + readtok (); + + val = EXP_HIGHEST (); + + if (curtok != 0) + evalerror (_("syntax error in expression")); + + FREE (tokstr); + FREE (expression); + + popexp (); + + return val; +} + +static intmax_t +expcomma () +{ + register intmax_t value; + + value = expassign (); + while (curtok == COMMA) + { + readtok (); + value = expassign (); + } + + return value; +} + +static intmax_t +expassign () +{ + register intmax_t value; + char *lhs, *rhs; + arrayind_t lind; + + value = expcond (); + if (curtok == EQ || curtok == OP_ASSIGN) + { + int special, op; + intmax_t lvalue; + + special = curtok == OP_ASSIGN; + + if (lasttok != STR) + evalerror (_("attempted assignment to non-variable")); + + if (special) + { + op = assigntok; /* a OP= b */ + lvalue = value; + } + + lhs = savestring (tokstr); + /* save ind in case rhs is string var and evaluation overwrites it */ + lind = curlval.ind; + readtok (); + value = expassign (); + + if (special) + { + switch (op) + { + case MUL: + lvalue *= value; + break; + case DIV: + if (value == 0) + evalerror (_("division by 0")); + lvalue /= value; + break; + case MOD: + if (value == 0) + evalerror (_("division by 0")); + lvalue %= value; + break; + case PLUS: + lvalue += value; + break; + case MINUS: + lvalue -= value; + break; + case LSH: + lvalue <<= value; + break; + case RSH: + lvalue >>= value; + break; + case BAND: + lvalue &= value; + break; + case BOR: + lvalue |= value; + break; + case BXOR: + lvalue ^= value; + break; + default: + free (lhs); + evalerror (_("bug: bad expassign token")); + break; + } + value = lvalue; + } + + rhs = itos (value); + if (noeval == 0) + { + if (lind != -1) + expr_bind_array_element (lhs, lind, rhs); + else + expr_bind_variable (lhs, rhs); + } + free (rhs); + free (lhs); + FREE (tokstr); + tokstr = (char *)NULL; /* For freeing on errors. */ + } + return (value); +} + +/* Conditional expression (expr?expr:expr) */ +static intmax_t +expcond () +{ + intmax_t cval, val1, val2, rval; + int set_noeval; + + set_noeval = 0; + rval = cval = explor (); + if (curtok == QUES) /* found conditional expr */ + { + readtok (); + if (curtok == 0 || curtok == COL) + evalerror (_("expression expected")); + if (cval == 0) + { + set_noeval = 1; + noeval++; + } + + val1 = EXP_HIGHEST (); + + if (set_noeval) + noeval--; + if (curtok != COL) + evalerror (_("`:' expected for conditional expression")); + readtok (); + if (curtok == 0) + evalerror (_("expression expected")); + set_noeval = 0; + if (cval) + { + set_noeval = 1; + noeval++; + } + + val2 = expcond (); + if (set_noeval) + noeval--; + rval = cval ? val1 : val2; + lasttok = COND; + } + return rval; +} + +/* Logical OR. */ +static intmax_t +explor () +{ + register intmax_t val1, val2; + int set_noeval; + + val1 = expland (); + + while (curtok == LOR) + { + set_noeval = 0; + if (val1 != 0) + { + noeval++; + set_noeval = 1; + } + readtok (); + val2 = expland (); + if (set_noeval) + noeval--; + val1 = val1 || val2; + lasttok = LOR; + } + + return (val1); +} + +/* Logical AND. */ +static intmax_t +expland () +{ + register intmax_t val1, val2; + int set_noeval; + + val1 = expbor (); + + while (curtok == LAND) + { + set_noeval = 0; + if (val1 == 0) + { + set_noeval = 1; + noeval++; + } + readtok (); + val2 = expbor (); + if (set_noeval) + noeval--; + val1 = val1 && val2; + lasttok = LAND; + } + + return (val1); +} + +/* Bitwise OR. */ +static intmax_t +expbor () +{ + register intmax_t val1, val2; + + val1 = expbxor (); + + while (curtok == BOR) + { + readtok (); + val2 = expbxor (); + val1 = val1 | val2; + } + + return (val1); +} + +/* Bitwise XOR. */ +static intmax_t +expbxor () +{ + register intmax_t val1, val2; + + val1 = expband (); + + while (curtok == BXOR) + { + readtok (); + val2 = expband (); + val1 = val1 ^ val2; + } + + return (val1); +} + +/* Bitwise AND. */ +static intmax_t +expband () +{ + register intmax_t val1, val2; + + val1 = exp5 (); + + while (curtok == BAND) + { + readtok (); + val2 = exp5 (); + val1 = val1 & val2; + } + + return (val1); +} + +static intmax_t +exp5 () +{ + register intmax_t val1, val2; + + val1 = exp4 (); + + while ((curtok == EQEQ) || (curtok == NEQ)) + { + int op = curtok; + + readtok (); + val2 = exp4 (); + if (op == EQEQ) + val1 = (val1 == val2); + else if (op == NEQ) + val1 = (val1 != val2); + } + return (val1); +} + +static intmax_t +exp4 () +{ + register intmax_t val1, val2; + + val1 = expshift (); + while ((curtok == LEQ) || + (curtok == GEQ) || + (curtok == LT) || + (curtok == GT)) + { + int op = curtok; + + readtok (); + val2 = expshift (); + + if (op == LEQ) + val1 = val1 <= val2; + else if (op == GEQ) + val1 = val1 >= val2; + else if (op == LT) + val1 = val1 < val2; + else /* (op == GT) */ + val1 = val1 > val2; + } + return (val1); +} + +/* Left and right shifts. */ +static intmax_t +expshift () +{ + register intmax_t val1, val2; + + val1 = exp3 (); + + while ((curtok == LSH) || (curtok == RSH)) + { + int op = curtok; + + readtok (); + val2 = exp3 (); + + if (op == LSH) + val1 = val1 << val2; + else + val1 = val1 >> val2; + } + + return (val1); +} + +static intmax_t +exp3 () +{ + register intmax_t val1, val2; + + val1 = exp2 (); + + while ((curtok == PLUS) || (curtok == MINUS)) + { + int op = curtok; + + readtok (); + val2 = exp2 (); + + if (op == PLUS) + val1 += val2; + else if (op == MINUS) + val1 -= val2; + } + return (val1); +} + +static intmax_t +exp2 () +{ + register intmax_t val1, val2; + + val1 = exppower (); + + while ((curtok == MUL) || + (curtok == DIV) || + (curtok == MOD)) + { + int op = curtok; + + readtok (); + + val2 = exppower (); + + if (((op == DIV) || (op == MOD)) && (val2 == 0)) + evalerror (_("division by 0")); + + if (op == MUL) + val1 *= val2; + else if (op == DIV) + val1 /= val2; + else if (op == MOD) + val1 %= val2; + } + return (val1); +} + +static intmax_t +exppower () +{ + register intmax_t val1, val2, c; + + val1 = exp1 (); + while (curtok == POWER) + { + readtok (); + val2 = exppower (); /* exponentiation is right-associative */ + if (val2 == 0) + return (1); + if (val2 < 0) + evalerror (_("exponent less than 0")); + for (c = 1; val2--; c *= val1) + ; + val1 = c; + } + return (val1); +} + +static intmax_t +exp1 () +{ + register intmax_t val; + + if (curtok == NOT) + { + readtok (); + val = !exp1 (); + } + else if (curtok == BNOT) + { + readtok (); + val = ~exp1 (); + } + else + val = exp0 (); + + return (val); +} + +static intmax_t +exp0 () +{ + register intmax_t val = 0, v2; + char *vincdec; + int stok; + EXPR_CONTEXT ec; + + /* XXX - might need additional logic here to decide whether or not + pre-increment or pre-decrement is legal at this point. */ + if (curtok == PREINC || curtok == PREDEC) + { + stok = lasttok = curtok; + readtok (); + if (curtok != STR) + /* readtok() catches this */ + evalerror (_("identifier expected after pre-increment or pre-decrement")); + + v2 = tokval + ((stok == PREINC) ? 1 : -1); + vincdec = itos (v2); + if (noeval == 0) + { + if (curlval.ind != -1) + expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec); + else + expr_bind_variable (tokstr, vincdec); + } + free (vincdec); + val = v2; + + curtok = NUM; /* make sure --x=7 is flagged as an error */ + readtok (); + } + else if (curtok == MINUS) + { + readtok (); + val = - exp0 (); + } + else if (curtok == PLUS) + { + readtok (); + val = exp0 (); + } + else if (curtok == LPAR) + { + readtok (); + val = EXP_HIGHEST (); + + if (curtok != RPAR) /* ( */ + evalerror (_("missing `)'")); + + /* Skip over closing paren. */ + readtok (); + } + else if ((curtok == NUM) || (curtok == STR)) + { + val = tokval; + if (curtok == STR) + { + SAVETOK (&ec); + tokstr = (char *)NULL; /* keep it from being freed */ + noeval = 1; + readtok (); + stok = curtok; + + /* post-increment or post-decrement */ + if (stok == POSTINC || stok == POSTDEC) + { + /* restore certain portions of EC */ + tokstr = ec.tokstr; + noeval = ec.noeval; + curlval = ec.lval; + lasttok = STR; /* ec.curtok */ + + v2 = val + ((stok == POSTINC) ? 1 : -1); + vincdec = itos (v2); + if (noeval == 0) + { + if (curlval.ind != -1) + expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec); + else + expr_bind_variable (tokstr, vincdec); + } + free (vincdec); + curtok = NUM; /* make sure x++=7 is flagged as an error */ + } + else + { + if (stok == STR) /* free new tokstr before old one is restored */ + FREE (tokstr); + RESTORETOK (&ec); + } + + } + + readtok (); + } + else + evalerror (_("syntax error: operand expected")); + + return (val); +} + +static struct lvalue * +alloc_lvalue () +{ + struct lvalue *lv; + + lv = xmalloc (sizeof (struct lvalue)); + lv->tokstr = 0; + lv->tokvar = 0; + lv->tokval = lv->ind = -1; + return (lv); +} + +static void +free_lvalue (lv) + struct lvalue *lv; +{ + free (lv); /* should be inlined */ +} + +static intmax_t +expr_streval (tok, e, lvalue) + char *tok; + int e; + struct lvalue *lvalue; +{ + SHELL_VAR *v; + char *value; + intmax_t tval; +#if defined (ARRAY_VARS) + arrayind_t ind; +#endif + + /* [[[[[ */ +#if defined (ARRAY_VARS) + v = (e == ']') ? array_variable_part (tok, (char **)0, (int *)0) : find_variable (tok); +#else + v = find_variable (tok); +#endif + + if ((v == 0 || invisible_p (v)) && unbound_vars_is_error) + { +#if defined (ARRAY_VARS) + value = (e == ']') ? array_variable_name (tok, (char **)0, (int *)0) : tok; +#else + value = tok; +#endif + + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (value); + +#if defined (ARRAY_VARS) + if (e == ']') + FREE (value); /* array_variable_name returns new memory */ +#endif + + if (interactive_shell) + { + expr_unwind (); + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + else + jump_to_top_level (FORCE_EOF); + } + + ind = -1; +#if defined (ARRAY_VARS) + /* Second argument of 0 to get_array_value means that we don't allow + references like array[@]. In this case, get_array_value is just + like get_variable_value in that it does not return newly-allocated + memory or quote the results. */ + value = (e == ']') ? get_array_value (tok, 0, (int *)NULL, &ind) : get_variable_value (v); +#else + value = get_variable_value (v); +#endif + + tval = (value && *value) ? subexpr (value) : 0; + + if (lvalue) + { + lvalue->tokstr = tok; /* XXX */ + lvalue->tokval = tval; + lvalue->tokvar = v; /* XXX */ + lvalue->ind = ind; + } + + return (tval); +} + +static int +_is_multiop (c) + int c; +{ + switch (c) + { + case EQEQ: + case NEQ: + case LEQ: + case GEQ: + case LAND: + case LOR: + case LSH: + case RSH: + case OP_ASSIGN: + case COND: + case POWER: + case PREINC: + case PREDEC: + case POSTINC: + case POSTDEC: + return 1; + default: + return 0; + } +} + +static int +_is_arithop (c) + int c; +{ + switch (c) + { + case EQ: + case GT: + case LT: + case PLUS: + case MINUS: + case MUL: + case DIV: + case MOD: + case NOT: + case LPAR: + case RPAR: + case BAND: + case BOR: + case BXOR: + case BNOT: + return 1; /* operator tokens */ + case QUES: + case COL: + case COMMA: + return 1; /* questionable */ + default: + return 0; /* anything else is invalid */ + } +} + +/* Lexical analyzer/token reader for the expression evaluator. Reads the + next token and puts its value into curtok, while advancing past it. + Updates value of tp. May also set tokval (for number) or tokstr (for + string). */ +static void +readtok () +{ + register char *cp, *xp; + register unsigned char c, c1; + register int e; + struct lvalue lval; + + /* Skip leading whitespace. */ + cp = tp; + c = e = 0; + while (cp && (c = *cp) && (cr_whitespace (c))) + cp++; + + if (c) + cp++; + + if (c == '\0') + { + lasttok = curtok; + curtok = 0; + tp = cp; + return; + } + lasttp = tp = cp - 1; + + if (legal_variable_starter (c)) + { + /* variable names not preceded with a dollar sign are shell variables. */ + char *savecp; + EXPR_CONTEXT ec; + int peektok; + + while (legal_variable_char (c)) + c = *cp++; + + c = *--cp; + +#if defined (ARRAY_VARS) + if (c == '[') + { + e = skipsubscript (cp, 0, 0); + if (cp[e] == ']') + { + cp += e + 1; + c = *cp; + e = ']'; + } + else + evalerror (bash_badsub_errmsg); + } +#endif /* ARRAY_VARS */ + + *cp = '\0'; + FREE (tokstr); + tokstr = savestring (tp); + *cp = c; + + /* XXX - make peektok part of saved token state? */ + SAVETOK (&ec); + tokstr = (char *)NULL; /* keep it from being freed */ + tp = savecp = cp; + noeval = 1; + curtok = STR; + readtok (); + peektok = curtok; + if (peektok == STR) /* free new tokstr before old one is restored */ + FREE (tokstr); + RESTORETOK (&ec); + cp = savecp; + + /* The tests for PREINC and PREDEC aren't strictly correct, but they + preserve old behavior if a construct like --x=9 is given. */ + if (lasttok == PREINC || lasttok == PREDEC || peektok != EQ) + { + lastlval = curlval; + tokval = expr_streval (tokstr, e, &curlval); + } + else + tokval = 0; + + lasttok = curtok; + curtok = STR; + } + else if (DIGIT(c)) + { + while (ISALNUM (c) || c == '#' || c == '@' || c == '_') + c = *cp++; + + c = *--cp; + *cp = '\0'; + + tokval = strlong (tp); + *cp = c; + lasttok = curtok; + curtok = NUM; + } + else + { + c1 = *cp++; + if ((c == EQ) && (c1 == EQ)) + c = EQEQ; + else if ((c == NOT) && (c1 == EQ)) + c = NEQ; + else if ((c == GT) && (c1 == EQ)) + c = GEQ; + else if ((c == LT) && (c1 == EQ)) + c = LEQ; + else if ((c == LT) && (c1 == LT)) + { + if (*cp == '=') /* a <<= b */ + { + assigntok = LSH; + c = OP_ASSIGN; + cp++; + } + else + c = LSH; + } + else if ((c == GT) && (c1 == GT)) + { + if (*cp == '=') + { + assigntok = RSH; /* a >>= b */ + c = OP_ASSIGN; + cp++; + } + else + c = RSH; + } + else if ((c == BAND) && (c1 == BAND)) + c = LAND; + else if ((c == BOR) && (c1 == BOR)) + c = LOR; + else if ((c == '*') && (c1 == '*')) + c = POWER; + else if ((c == '-' || c == '+') && c1 == c && curtok == STR) + c = (c == '-') ? POSTDEC : POSTINC; + else if ((c == '-' || c == '+') && c1 == c) + { + /* Quickly scan forward to see if this is followed by optional + whitespace and an identifier. */ + xp = cp; + while (xp && *xp && cr_whitespace (*xp)) + xp++; + if (legal_variable_starter ((unsigned char)*xp)) + c = (c == '-') ? PREDEC : PREINC; + else + cp--; /* not preinc or predec, so unget the character */ + } + else if (c1 == EQ && member (c, "*/%+-&^|")) + { + assigntok = c; /* a OP= b */ + c = OP_ASSIGN; + } + else if (_is_arithop (c) == 0) + { + cp--; + /* use curtok, since it hasn't been copied to lasttok yet */ + if (curtok == 0 || _is_arithop (curtok) || _is_multiop (curtok)) + evalerror (_("syntax error: operand expected")); + else + evalerror (_("syntax error: invalid arithmetic operator")); + } + else + cp--; /* `unget' the character */ + + /* Should check here to make sure that the current character is one + of the recognized operators and flag an error if not. Could create + a character map the first time through and check it on subsequent + calls. */ + lasttok = curtok; + curtok = c; + } + tp = cp; +} + +static void +evalerror (msg) + const char *msg; +{ + char *name, *t; + + name = this_command_name; + for (t = expression; whitespace (*t); t++) + ; + internal_error (_("%s%s%s: %s (error token is \"%s\")"), + name ? name : "", name ? ": " : "", t, + msg, (lasttp && *lasttp) ? lasttp : ""); + longjmp (evalbuf, 1); +} + +/* Convert a string to an intmax_t integer, with an arbitrary base. + 0nnn -> base 8 + 0[Xx]nn -> base 16 + Anything else: [base#]number (this is implemented to match ksh93) + + Base may be >=2 and <=64. If base is <= 36, the numbers are drawn + from [0-9][a-zA-Z], and lowercase and uppercase letters may be used + interchangably. If base is > 36 and <= 64, the numbers are drawn + from [0-9][a-z][A-Z]_@ (a = 10, z = 35, A = 36, Z = 61, @ = 62, _ = 63 -- + you get the picture). */ + +static intmax_t +strlong (num) + char *num; +{ + register char *s; + register unsigned char c; + int base, foundbase; + intmax_t val; + + s = num; + + base = 10; + foundbase = 0; + if (*s == '0') + { + s++; + + if (*s == '\0') + return 0; + + /* Base 16? */ + if (*s == 'x' || *s == 'X') + { + base = 16; + s++; + } + else + base = 8; + foundbase++; + } + + val = 0; + for (c = *s++; c; c = *s++) + { + if (c == '#') + { + if (foundbase) + evalerror (_("invalid number")); + + /* Illegal base specifications raise an evaluation error. */ + if (val < 2 || val > 64) + evalerror (_("invalid arithmetic base")); + + base = val; + val = 0; + foundbase++; + } + else if (ISALNUM(c) || (c == '_') || (c == '@')) + { + if (DIGIT(c)) + c = TODIGIT(c); + else if (c >= 'a' && c <= 'z') + c -= 'a' - 10; + else if (c >= 'A' && c <= 'Z') + c -= 'A' - ((base <= 36) ? 10 : 36); + else if (c == '@') + c = 62; + else if (c == '_') + c = 63; + + if (c >= base) + evalerror (_("value too great for base")); + + val = (val * base) + c; + } + else + break; + } + + return (val); +} + +#if defined (EXPR_TEST) +void * +xmalloc (n) + int n; +{ + return (malloc (n)); +} + +void * +xrealloc (s, n) + char *s; + int n; +{ + return (realloc (s, n)); +} + +SHELL_VAR *find_variable () { return 0;} +SHELL_VAR *bind_variable () { return 0; } + +char *get_string_value () { return 0; } + +procenv_t top_level; + +main (argc, argv) + int argc; + char **argv; +{ + register int i; + intmax_t v; + int expok; + + if (setjmp (top_level)) + exit (0); + + for (i = 1; i < argc; i++) + { + v = evalexp (argv[i], &expok); + if (expok == 0) + fprintf (stderr, _("%s: expression error\n"), argv[i]); + else + printf ("'%s' -> %ld\n", argv[i], v); + } + exit (0); +} + +int +builtin_error (format, arg1, arg2, arg3, arg4, arg5) + char *format; +{ + fprintf (stderr, "expr: "); + fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); + fprintf (stderr, "\n"); + return 0; +} + +char * +itos (n) + intmax_t n; +{ + return ("42"); +} + +#endif /* EXPR_TEST */ diff --git a/jobs.c b/jobs.c index df13ad9e9..1cfbb14bc 100644 --- a/jobs.c +++ b/jobs.c @@ -77,6 +77,10 @@ extern int errno; #endif /* !errno */ +#if !defined (HAVE_KILLPG) +extern int killpg __P((pid_t, int)); +#endif + #define DEFAULT_CHILD_MAX 32 #if !defined (DEBUG) #define MAX_JOBS_IN_ARRAY 4096 /* production */ diff --git a/jobs.c~ b/jobs.c~ new file mode 100644 index 000000000..df13ad9e9 --- /dev/null +++ b/jobs.c~ @@ -0,0 +1,4240 @@ +/* jobs.c - functions that make children, remember them, and handle their termination. */ + +/* This file works with both POSIX and BSD systems. It implements job + control. */ + +/* Copyright (C) 1989-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" +#include "trap.h" +#include +#include +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "posixtime.h" + +#if defined (HAVE_SYS_RESOURCE_H) && defined (HAVE_WAIT3) && !defined (_POSIX_VERSION) && !defined (RLIMTYPE) +# include +#endif /* !_POSIX_VERSION && HAVE_SYS_RESOURCE_H && HAVE_WAIT3 && !RLIMTYPE */ + +#if defined (HAVE_SYS_FILE_H) +# include +#endif + +#include "filecntl.h" +#include +#include + +#if defined (BUFFERED_INPUT) +# include "input.h" +#endif + +/* Need to include this up here for *_TTY_DRIVER definitions. */ +#include "shtty.h" + +/* Define this if your output is getting swallowed. It's a no-op on + machines with the termio or termios tty drivers. */ +/* #define DRAIN_OUTPUT */ + +/* For the TIOCGPGRP and TIOCSPGRP ioctl parameters on HP-UX */ +#if defined (hpux) && !defined (TERMIOS_TTY_DRIVER) +# include +#endif /* hpux && !TERMIOS_TTY_DRIVER */ + +#include "bashansi.h" +#include "bashintl.h" +#include "shell.h" +#include "jobs.h" +#include "execute_cmd.h" +#include "flags.h" + +#include "builtins/builtext.h" +#include "builtins/common.h" + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#define DEFAULT_CHILD_MAX 32 +#if !defined (DEBUG) +#define MAX_JOBS_IN_ARRAY 4096 /* production */ +#else +#define MAX_JOBS_IN_ARRAY 128 /* testing */ +#endif + +/* Flag values for second argument to delete_job */ +#define DEL_WARNSTOPPED 1 /* warn about deleting stopped jobs */ +#define DEL_NOBGPID 2 /* don't add pgrp leader to bgpids */ + +/* Take care of system dependencies that must be handled when waiting for + children. The arguments to the WAITPID macro match those to the Posix.1 + waitpid() function. */ + +#if defined (ultrix) && defined (mips) && defined (_POSIX_VERSION) +# define WAITPID(pid, statusp, options) \ + wait3 ((union wait *)statusp, options, (struct rusage *)0) +#else +# if defined (_POSIX_VERSION) || defined (HAVE_WAITPID) +# define WAITPID(pid, statusp, options) \ + waitpid ((pid_t)pid, statusp, options) +# else +# if defined (HAVE_WAIT3) +# define WAITPID(pid, statusp, options) \ + wait3 (statusp, options, (struct rusage *)0) +# else +# define WAITPID(pid, statusp, options) \ + wait3 (statusp, options, (int *)0) +# endif /* HAVE_WAIT3 */ +# endif /* !_POSIX_VERSION && !HAVE_WAITPID*/ +#endif /* !(Ultrix && mips && _POSIX_VERSION) */ + +/* getpgrp () varies between systems. Even systems that claim to be + Posix.1 compatible lie sometimes (Ultrix, SunOS4, apollo). */ +#if defined (GETPGRP_VOID) +# define getpgid(p) getpgrp () +#else +# define getpgid(p) getpgrp (p) +#endif /* !GETPGRP_VOID */ + +/* If the system needs it, REINSTALL_SIGCHLD_HANDLER will reinstall the + handler for SIGCHLD. */ +#if defined (MUST_REINSTALL_SIGHANDLERS) +# define REINSTALL_SIGCHLD_HANDLER signal (SIGCHLD, sigchld_handler) +#else +# define REINSTALL_SIGCHLD_HANDLER +#endif /* !MUST_REINSTALL_SIGHANDLERS */ + +/* Some systems let waitpid(2) tell callers about stopped children. */ +#if !defined (WCONTINUED) || defined (WCONTINUED_BROKEN) +# undef WCONTINUED +# define WCONTINUED 0 +#endif +#if !defined (WIFCONTINUED) +# define WIFCONTINUED(s) (0) +#endif + +/* The number of additional slots to allocate when we run out. */ +#define JOB_SLOTS 8 + +typedef int sh_job_map_func_t __P((JOB *, int, int, int)); + +/* Variables used here but defined in other files. */ +extern int subshell_environment, line_number; +extern int posixly_correct, shell_level; +extern int last_command_exit_value, last_command_exit_signal; +extern int loop_level, breaking; +extern int executing_list; +extern int sourcelevel; +extern int running_trap; +extern sh_builtin_func_t *this_shell_builtin; +extern char *shell_name, *this_command_name; +extern sigset_t top_level_mask; +extern procenv_t wait_intr_buf; +extern int wait_signal_received; +extern WORD_LIST *subst_assign_varlist; + +static struct jobstats zerojs = { -1L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NO_JOB, NO_JOB, 0, 0 }; +struct jobstats js = { -1L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NO_JOB, NO_JOB, 0, 0 }; + +struct bgpids bgpids = { 0, 0, 0 }; + +/* The array of known jobs. */ +JOB **jobs = (JOB **)NULL; + +#if 0 +/* The number of slots currently allocated to JOBS. */ +int job_slots = 0; +#endif + +/* The controlling tty for this shell. */ +int shell_tty = -1; + +/* The shell's process group. */ +pid_t shell_pgrp = NO_PID; + +/* The terminal's process group. */ +pid_t terminal_pgrp = NO_PID; + +/* The process group of the shell's parent. */ +pid_t original_pgrp = NO_PID; + +/* The process group of the pipeline currently being made. */ +pid_t pipeline_pgrp = (pid_t)0; + +#if defined (PGRP_PIPE) +/* Pipes which each shell uses to communicate with the process group leader + until all of the processes in a pipeline have been started. Then the + process leader is allowed to continue. */ +int pgrp_pipe[2] = { -1, -1 }; +#endif + +#if 0 +/* The job which is current; i.e. the one that `%+' stands for. */ +int current_job = NO_JOB; + +/* The previous job; i.e. the one that `%-' stands for. */ +int previous_job = NO_JOB; +#endif + +/* Last child made by the shell. */ +pid_t last_made_pid = NO_PID; + +/* Pid of the last asynchronous child. */ +pid_t last_asynchronous_pid = NO_PID; + +/* The pipeline currently being built. */ +PROCESS *the_pipeline = (PROCESS *)NULL; + +/* If this is non-zero, do job control. */ +int job_control = 1; + +/* Call this when you start making children. */ +int already_making_children = 0; + +/* If this is non-zero, $LINES and $COLUMNS are reset after every process + exits from get_tty_state(). */ +int check_window_size; + +/* Functions local to this file. */ + +static sighandler wait_sigint_handler __P((int)); +static sighandler sigchld_handler __P((int)); +static sighandler sigcont_sighandler __P((int)); +static sighandler sigstop_sighandler __P((int)); + +static int waitchld __P((pid_t, int)); + +static PROCESS *find_pipeline __P((pid_t, int, int *)); +static PROCESS *find_process __P((pid_t, int, int *)); + +static char *current_working_directory __P((void)); +static char *job_working_directory __P((void)); +static char *j_strsignal __P((int)); +static char *printable_job_status __P((int, PROCESS *, int)); + +static PROCESS *find_last_proc __P((int, int)); +static pid_t find_last_pid __P((int, int)); + +static int set_new_line_discipline __P((int)); +static int map_over_jobs __P((sh_job_map_func_t *, int, int)); +static int job_last_stopped __P((int)); +static int job_last_running __P((int)); +static int most_recent_job_in_state __P((int, JOB_STATE)); +static int find_job __P((pid_t, int, PROCESS **)); +static int print_job __P((JOB *, int, int, int)); +static int process_exit_status __P((WAIT)); +static int process_exit_signal __P((WAIT)); +static int job_exit_status __P((int)); +static int job_exit_signal __P((int)); +static int set_job_status_and_cleanup __P((int)); + +static WAIT job_signal_status __P((int)); +static WAIT raw_job_exit_status __P((int)); + +static void notify_of_job_status __P((void)); +static void reset_job_indices __P((void)); +static void cleanup_dead_jobs __P((void)); +static int processes_in_job __P((int)); +static void realloc_jobs_list __P((void)); +static int compact_jobs_list __P((int)); +static int discard_pipeline __P((PROCESS *)); +static void add_process __P((char *, pid_t)); +static void print_pipeline __P((PROCESS *, int, int, FILE *)); +static void pretty_print_job __P((int, int, FILE *)); +static void set_current_job __P((int)); +static void reset_current __P((void)); +static void set_job_running __P((int)); +static void setjstatus __P((int)); +static int maybe_give_terminal_to __P((pid_t, pid_t, int)); +static void mark_all_jobs_as_dead __P((void)); +static void mark_dead_jobs_as_notified __P((int)); +static void restore_sigint_handler __P((void)); +#if defined (PGRP_PIPE) +static void pipe_read __P((int *)); +#endif + +static struct pidstat *bgp_alloc __P((pid_t, int)); +static struct pidstat *bgp_add __P((pid_t, int)); +static int bgp_delete __P((pid_t)); +static void bgp_clear __P((void)); +static int bgp_search __P((pid_t)); +static void bgp_prune __P((void)); + +#if defined (ARRAY_VARS) +static int *pstatuses; /* list of pipeline statuses */ +static int statsize; +#endif + +/* Used to synchronize between wait_for and other functions and the SIGCHLD + signal handler. */ +static int sigchld; +static int queue_sigchld; + +#define QUEUE_SIGCHLD(os) (os) = sigchld, queue_sigchld++ + +#define UNQUEUE_SIGCHLD(os) \ + do { \ + queue_sigchld--; \ + if (queue_sigchld == 0 && os != sigchld) \ + waitchld (-1, 0); \ + } while (0) + +static SigHandler *old_tstp, *old_ttou, *old_ttin; +static SigHandler *old_cont = (SigHandler *)SIG_DFL; + +/* A place to temporarily save the current pipeline. */ +static PROCESS *saved_pipeline; +static int saved_already_making_children; + +/* Set this to non-zero whenever you don't want the jobs list to change at + all: no jobs deleted and no status change notifications. This is used, + for example, when executing SIGCHLD traps, which may run arbitrary + commands. */ +static int jobs_list_frozen; + +static char retcode_name_buffer[64]; + +/* flags to detect pid wraparound */ +static pid_t first_pid = NO_PID; +static int pid_wrap = -1; + +#if !defined (_POSIX_VERSION) + +/* These are definitions to map POSIX 1003.1 functions onto existing BSD + library functions and system calls. */ +#define setpgid(pid, pgrp) setpgrp (pid, pgrp) +#define tcsetpgrp(fd, pgrp) ioctl ((fd), TIOCSPGRP, &(pgrp)) + +pid_t +tcgetpgrp (fd) + int fd; +{ + pid_t pgrp; + + /* ioctl will handle setting errno correctly. */ + if (ioctl (fd, TIOCGPGRP, &pgrp) < 0) + return (-1); + return (pgrp); +} + +#endif /* !_POSIX_VERSION */ + +/* Initialize the global job stats structure and other bookkeeping variables */ +void +init_job_stats () +{ + js = zerojs; + first_pid = NO_PID; + pid_wrap = -1; +} + +/* Return the working directory for the current process. Unlike + job_working_directory, this does not call malloc (), nor do any + of the functions it calls. This is so that it can safely be called + from a signal handler. */ +static char * +current_working_directory () +{ + char *dir; + static char d[PATH_MAX]; + + dir = get_string_value ("PWD"); + + if (dir == 0 && the_current_working_directory && no_symbolic_links) + dir = the_current_working_directory; + + if (dir == 0) + { + dir = getcwd (d, sizeof(d)); + if (dir) + dir = d; + } + + return (dir == 0) ? "" : dir; +} + +/* Return the working directory for the current process. */ +static char * +job_working_directory () +{ + char *dir; + + dir = get_string_value ("PWD"); + if (dir) + return (savestring (dir)); + + dir = get_working_directory ("job-working-directory"); + if (dir) + return (dir); + + return (savestring ("")); +} + +void +making_children () +{ + if (already_making_children) + return; + + already_making_children = 1; + start_pipeline (); +} + +void +stop_making_children () +{ + already_making_children = 0; +} + +void +cleanup_the_pipeline () +{ + PROCESS *disposer; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + disposer = the_pipeline; + the_pipeline = (PROCESS *)NULL; + UNBLOCK_CHILD (oset); + + if (disposer) + discard_pipeline (disposer); +} + +void +save_pipeline (clear) + int clear; +{ + saved_pipeline = the_pipeline; + if (clear) + the_pipeline = (PROCESS *)NULL; + saved_already_making_children = already_making_children; +} + +void +restore_pipeline (discard) + int discard; +{ + PROCESS *old_pipeline; + + old_pipeline = the_pipeline; + the_pipeline = saved_pipeline; + already_making_children = saved_already_making_children; + if (discard && old_pipeline) + discard_pipeline (old_pipeline); +} + +/* Start building a pipeline. */ +void +start_pipeline () +{ + if (the_pipeline) + { + cleanup_the_pipeline (); + pipeline_pgrp = 0; +#if defined (PGRP_PIPE) + sh_closepipe (pgrp_pipe); +#endif + } + +#if defined (PGRP_PIPE) + if (job_control) + { + if (pipe (pgrp_pipe) == -1) + sys_error (_("start_pipeline: pgrp pipe")); + } +#endif +} + +/* Stop building a pipeline. Install the process list in the job array. + This returns the index of the newly installed job. + DEFERRED is a command structure to be executed upon satisfactory + execution exit of this pipeline. */ +int +stop_pipeline (async, deferred) + int async; + COMMAND *deferred; +{ + register int i, j; + JOB *newjob; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + +#if defined (PGRP_PIPE) + /* The parent closes the process group synchronization pipe. */ + sh_closepipe (pgrp_pipe); +#endif + + cleanup_dead_jobs (); + + if (js.j_jobslots == 0) + { + js.j_jobslots = JOB_SLOTS; + jobs = (JOB **)xmalloc (js.j_jobslots * sizeof (JOB *)); + + /* Now blank out these new entries. */ + for (i = 0; i < js.j_jobslots; i++) + jobs[i] = (JOB *)NULL; + + js.j_firstj = js.j_lastj = js.j_njobs = 0; + } + + /* Scan from the last slot backward, looking for the next free one. */ + /* XXX - revisit this interactive assumption */ + /* XXX - this way for now */ + if (interactive) + { + for (i = js.j_jobslots; i; i--) + if (jobs[i - 1]) + break; + } + else + { +#if 0 + /* This wraps around, but makes it inconvenient to extend the array */ + for (i = js.j_lastj+1; i != js.j_lastj; i++) + { + if (i >= js.j_jobslots) + i = 0; + if (jobs[i] == 0) + break; + } + if (i == js.j_lastj) + i = js.j_jobslots; +#else + /* This doesn't wrap around yet. */ + for (i = js.j_lastj ? js.j_lastj + 1 : js.j_lastj; i < js.j_jobslots; i++) + if (jobs[i] == 0) + break; +#endif + } + + /* Do we need more room? */ + + /* First try compaction */ + if ((interactive_shell == 0 || subshell_environment) && i == js.j_jobslots && js.j_jobslots >= MAX_JOBS_IN_ARRAY) + i = compact_jobs_list (0); + + /* If we can't compact, reallocate */ + if (i == js.j_jobslots) + { + js.j_jobslots += JOB_SLOTS; + jobs = (JOB **)xrealloc (jobs, (js.j_jobslots * sizeof (JOB *))); + + for (j = i; j < js.j_jobslots; j++) + jobs[j] = (JOB *)NULL; + } + + /* Add the current pipeline to the job list. */ + if (the_pipeline) + { + register PROCESS *p; + int any_running, any_stopped, n; + + newjob = (JOB *)xmalloc (sizeof (JOB)); + + for (n = 1, p = the_pipeline; p->next != the_pipeline; n++, p = p->next) + ; + p->next = (PROCESS *)NULL; + newjob->pipe = REVERSE_LIST (the_pipeline, PROCESS *); + for (p = newjob->pipe; p->next; p = p->next) + ; + p->next = newjob->pipe; + + the_pipeline = (PROCESS *)NULL; + newjob->pgrp = pipeline_pgrp; + pipeline_pgrp = 0; + + newjob->flags = 0; + + /* Flag to see if in another pgrp. */ + if (job_control) + newjob->flags |= J_JOBCONTROL; + + /* Set the state of this pipeline. */ + p = newjob->pipe; + any_running = any_stopped = 0; + do + { + any_running |= PRUNNING (p); + any_stopped |= PSTOPPED (p); + p = p->next; + } + while (p != newjob->pipe); + + newjob->state = any_running ? JRUNNING : (any_stopped ? JSTOPPED : JDEAD); + newjob->wd = job_working_directory (); + newjob->deferred = deferred; + + newjob->j_cleanup = (sh_vptrfunc_t *)NULL; + newjob->cleanarg = (PTR_T) NULL; + + jobs[i] = newjob; + if (newjob->state == JDEAD && (newjob->flags & J_FOREGROUND)) + setjstatus (i); + if (newjob->state == JDEAD) + { + js.c_reaped += n; /* wouldn't have been done since this was not part of a job */ + js.j_ndead++; + } + js.c_injobs += n; + + js.j_lastj = i; + js.j_njobs++; + } + else + newjob = (JOB *)NULL; + + if (newjob) + js.j_lastmade = newjob; + + if (async) + { + if (newjob) + { + newjob->flags &= ~J_FOREGROUND; + newjob->flags |= J_ASYNC; + js.j_lastasync = newjob; + } + reset_current (); + } + else + { + if (newjob) + { + newjob->flags |= J_FOREGROUND; + /* + * !!!!! NOTE !!!!! (chet@ins.cwru.edu) + * + * The currently-accepted job control wisdom says to set the + * terminal's process group n+1 times in an n-step pipeline: + * once in the parent and once in each child. This is where + * the parent gives it away. + * + * Don't give the terminal away if this shell is an asynchronous + * subshell. + * + */ + if (job_control && newjob->pgrp && (subshell_environment&SUBSHELL_ASYNC) == 0) + maybe_give_terminal_to (shell_pgrp, newjob->pgrp, 0); + } + } + + stop_making_children (); + UNBLOCK_CHILD (oset); + return (js.j_current); +} + +/* Functions to manage the list of exited background pids whose status has + been saved. */ + +static struct pidstat * +bgp_alloc (pid, status) + pid_t pid; + int status; +{ + struct pidstat *ps; + + ps = (struct pidstat *)xmalloc (sizeof (struct pidstat)); + ps->pid = pid; + ps->status = status; + ps->next = (struct pidstat *)0; + return ps; +} + +static struct pidstat * +bgp_add (pid, status) + pid_t pid; + int status; +{ + struct pidstat *ps; + + ps = bgp_alloc (pid, status); + + if (bgpids.list == 0) + { + bgpids.list = bgpids.end = ps; + bgpids.npid = 0; /* just to make sure */ + } + else + { + bgpids.end->next = ps; + bgpids.end = ps; + } + bgpids.npid++; + + if (bgpids.npid > js.c_childmax) + bgp_prune (); + + return ps; +} + +static int +bgp_delete (pid) + pid_t pid; +{ + struct pidstat *prev, *p; + + for (prev = p = bgpids.list; p; prev = p, p = p->next) + if (p->pid == pid) + { + prev->next = p->next; /* remove from list */ + break; + } + + if (p == 0) + return 0; /* not found */ + +#if defined (DEBUG) + itrace("bgp_delete: deleting %d", pid); +#endif + + /* Housekeeping in the border cases. */ + if (p == bgpids.list) + bgpids.list = bgpids.list->next; + else if (p == bgpids.end) + bgpids.end = prev; + + bgpids.npid--; + if (bgpids.npid == 0) + bgpids.list = bgpids.end = 0; + else if (bgpids.npid == 1) + bgpids.end = bgpids.list; /* just to make sure */ + + free (p); + return 1; +} + +/* Clear out the list of saved statuses */ +static void +bgp_clear () +{ + struct pidstat *ps, *p; + + for (ps = bgpids.list; ps; ) + { + p = ps; + ps = ps->next; + free (p); + } + bgpids.list = bgpids.end = 0; + bgpids.npid = 0; +} + +/* Search for PID in the list of saved background pids; return its status if + found. If not found, return -1. */ +static int +bgp_search (pid) + pid_t pid; +{ + struct pidstat *ps; + + for (ps = bgpids.list ; ps; ps = ps->next) + if (ps->pid == pid) + return ps->status; + return -1; +} + +static void +bgp_prune () +{ + struct pidstat *ps; + + while (bgpids.npid > js.c_childmax) + { + ps = bgpids.list; + bgpids.list = bgpids.list->next; + free (ps); + bgpids.npid--; + } +} + +/* Reset the values of js.j_lastj and js.j_firstj after one or both have + been deleted. The caller should check whether js.j_njobs is 0 before + calling this. This wraps around, but the rest of the code does not. At + this point, it should not matter. */ +static void +reset_job_indices () +{ + int old; + + if (jobs[js.j_firstj] == 0) + { + old = js.j_firstj++; + if (old >= js.j_jobslots) + old = js.j_jobslots - 1; + while (js.j_firstj != old) + { + if (js.j_firstj >= js.j_jobslots) + js.j_firstj = 0; + if (jobs[js.j_firstj] || js.j_firstj == old) /* needed if old == 0 */ + break; + js.j_firstj++; + } + if (js.j_firstj == old) + js.j_firstj = js.j_lastj = js.j_njobs = 0; + } + if (jobs[js.j_lastj] == 0) + { + old = js.j_lastj--; + if (old < 0) + old = 0; + while (js.j_lastj != old) + { + if (js.j_lastj < 0) + js.j_lastj = js.j_jobslots - 1; + if (jobs[js.j_lastj] || js.j_lastj == old) /* needed if old == js.j_jobslots */ + break; + js.j_lastj--; + } + if (js.j_lastj == old) + js.j_firstj = js.j_lastj = js.j_njobs = 0; + } +} + +/* Delete all DEAD jobs that the user had received notification about. */ +static void +cleanup_dead_jobs () +{ + register int i; + int os; + + if (js.j_jobslots == 0 || jobs_list_frozen) + return; + + QUEUE_SIGCHLD(os); + + /* XXX could use js.j_firstj and js.j_lastj here */ + for (i = 0; i < js.j_jobslots; i++) + { +#if defined (DEBUG) + if (i < js.j_firstj && jobs[i]) + itrace("cleanup_dead_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("cleanup_dead_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); +#endif + + if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i)) + delete_job (i, 0); + } + +#if defined (COPROCESS_SUPPORT) + coproc_reap (); +#endif + + UNQUEUE_SIGCHLD(os); +} + +static int +processes_in_job (job) + int job; +{ + int nproc; + register PROCESS *p; + + nproc = 0; + p = jobs[job]->pipe; + do + { + p = p->next; + nproc++; + } + while (p != jobs[job]->pipe); + + return nproc; +} + +static void +delete_old_job (pid) + pid_t pid; +{ + PROCESS *p; + int job; + + job = find_job (pid, 0, &p); + if (job != NO_JOB) + { +#ifdef DEBUG + itrace ("delete_old_job: found pid %d in job %d with state %d", pid, job, jobs[job]->state); +#endif + if (JOBSTATE (job) == JDEAD) + delete_job (job, DEL_NOBGPID); + else + { + internal_warning (_("forked pid %d appears in running job %d"), pid, job); + if (p) + p->pid = 0; + } + } +} + +/* Reallocate and compress the jobs list. This returns with a jobs array + whose size is a multiple of JOB_SLOTS and can hold the current number of + jobs. Heuristics are used to minimize the number of new reallocs. */ +static void +realloc_jobs_list () +{ + sigset_t set, oset; + int nsize, i, j, ncur, nprev; + JOB **nlist; + + ncur = nprev = NO_JOB; + nsize = ((js.j_njobs + JOB_SLOTS - 1) / JOB_SLOTS); + nsize *= JOB_SLOTS; + i = js.j_njobs % JOB_SLOTS; + if (i == 0 || i > (JOB_SLOTS >> 1)) + nsize += JOB_SLOTS; + + BLOCK_CHILD (set, oset); + nlist = (js.j_jobslots == nsize) ? jobs : (JOB **) xmalloc (nsize * sizeof (JOB *)); + + js.c_reaped = js.j_ndead = 0; + for (i = j = 0; i < js.j_jobslots; i++) + if (jobs[i]) + { + if (i == js.j_current) + ncur = j; + if (i == js.j_previous) + nprev = j; + nlist[j++] = jobs[i]; + if (jobs[i]->state == JDEAD) + { + js.j_ndead++; + js.c_reaped += processes_in_job (i); + } + } + +#if defined (DEBUG) + itrace ("realloc_jobs_list: resize jobs list from %d to %d", js.j_jobslots, nsize); + itrace ("realloc_jobs_list: j_lastj changed from %d to %d", js.j_lastj, (j > 0) ? j - 1 : 0); + itrace ("realloc_jobs_list: j_njobs changed from %d to %d", js.j_njobs, j); + itrace ("realloc_jobs_list: js.j_ndead %d js.c_reaped %d", js.j_ndead, js.c_reaped); +#endif + + js.j_firstj = 0; + js.j_lastj = (j > 0) ? j - 1 : 0; + js.j_njobs = j; + js.j_jobslots = nsize; + + /* Zero out remaining slots in new jobs list */ + for ( ; j < nsize; j++) + nlist[j] = (JOB *)NULL; + + if (jobs != nlist) + { + free (jobs); + jobs = nlist; + } + + if (ncur != NO_JOB) + js.j_current = ncur; + if (nprev != NO_JOB) + js.j_previous = nprev; + + /* Need to reset these */ + if (js.j_current == NO_JOB || js.j_previous == NO_JOB || js.j_current > js.j_lastj || js.j_previous > js.j_lastj) + reset_current (); + +#ifdef DEBUG + itrace ("realloc_jobs_list: reset js.j_current (%d) and js.j_previous (%d)", js.j_current, js.j_previous); +#endif + + UNBLOCK_CHILD (oset); +} + +/* Compact the jobs list by removing dead jobs. Assumed that we have filled + the jobs array to some predefined maximum. Called when the shell is not + the foreground process (subshell_environment != 0). Returns the first + available slot in the compacted list. If that value is js.j_jobslots, then + the list needs to be reallocated. The jobs array may be in new memory if + this returns > 0 and < js.j_jobslots. FLAGS is reserved for future use. */ +static int +compact_jobs_list (flags) + int flags; +{ + if (js.j_jobslots == 0 || jobs_list_frozen) + return js.j_jobslots; + + reap_dead_jobs (); + realloc_jobs_list (); + +#ifdef DEBUG + itrace("compact_jobs_list: returning %d", (js.j_lastj || jobs[js.j_lastj]) ? js.j_lastj + 1 : 0); +#endif + + return ((js.j_lastj || jobs[js.j_lastj]) ? js.j_lastj + 1 : 0); +} + +/* Delete the job at INDEX from the job list. Must be called + with SIGCHLD blocked. */ +void +delete_job (job_index, dflags) + int job_index, dflags; +{ + register JOB *temp; + PROCESS *proc; + int ndel; + + if (js.j_jobslots == 0 || jobs_list_frozen) + return; + + if ((dflags & DEL_WARNSTOPPED) && subshell_environment == 0 && STOPPED (job_index)) + internal_warning (_("deleting stopped job %d with process group %ld"), job_index+1, (long)jobs[job_index]->pgrp); + temp = jobs[job_index]; + if (temp == 0) + return; + + if ((dflags & DEL_NOBGPID) == 0) + { + proc = find_last_proc (job_index, 0); + /* Could do this just for J_ASYNC jobs, but we save all. */ + if (proc) + bgp_add (proc->pid, process_exit_status (proc->status)); + } + + jobs[job_index] = (JOB *)NULL; + if (temp == js.j_lastmade) + js.j_lastmade = 0; + else if (temp == js.j_lastasync) + js.j_lastasync = 0; + + free (temp->wd); + ndel = discard_pipeline (temp->pipe); + + js.c_injobs -= ndel; + if (temp->state == JDEAD) + { + js.c_reaped -= ndel; + js.j_ndead--; + if (js.c_reaped < 0) + { +#ifdef DEBUG + itrace("delete_job (%d pgrp %d): js.c_reaped (%d) < 0 ndel = %d js.j_ndead = %d", job_index, temp->pgrp, js.c_reaped, ndel, js.j_ndead); +#endif + js.c_reaped = 0; + } + } + + if (temp->deferred) + dispose_command (temp->deferred); + + free (temp); + + js.j_njobs--; + if (js.j_njobs == 0) + js.j_firstj = js.j_lastj = 0; + else if (jobs[js.j_firstj] == 0 || jobs[js.j_lastj] == 0) + reset_job_indices (); + + if (job_index == js.j_current || job_index == js.j_previous) + reset_current (); +} + +/* Must be called with SIGCHLD blocked. */ +void +nohup_job (job_index) + int job_index; +{ + register JOB *temp; + + if (js.j_jobslots == 0) + return; + + if (temp = jobs[job_index]) + temp->flags |= J_NOHUP; +} + +/* Get rid of the data structure associated with a process chain. */ +static int +discard_pipeline (chain) + register PROCESS *chain; +{ + register PROCESS *this, *next; + int n; + + this = chain; + n = 0; + do + { + next = this->next; + FREE (this->command); + free (this); + n++; + this = next; + } + while (this != chain); + + return n; +} + +/* Add this process to the chain being built in the_pipeline. + NAME is the command string that will be exec'ed later. + PID is the process id of the child. */ +static void +add_process (name, pid) + char *name; + pid_t pid; +{ + PROCESS *t, *p; + +#if defined (RECYCLES_PIDS) + int j; + p = find_process (pid, 0, &j); + if (p) + { +# ifdef DEBUG + if (j == NO_JOB) + internal_warning (_("add_process: process %5ld (%s) in the_pipeline"), (long)p->pid, p->command); +# endif + if (PALIVE (p)) + internal_warning (_("add_process: pid %5ld (%s) marked as still alive"), (long)p->pid, p->command); + p->running = PS_RECYCLED; /* mark as recycled */ + } +#endif + + t = (PROCESS *)xmalloc (sizeof (PROCESS)); + t->next = the_pipeline; + t->pid = pid; + WSTATUS (t->status) = 0; + t->running = PS_RUNNING; + t->command = name; + the_pipeline = t; + + if (t->next == 0) + t->next = t; + else + { + p = t->next; + while (p->next != t->next) + p = p->next; + p->next = t; + } +} + +#if 0 +/* Take the last job and make it the first job. Must be called with + SIGCHLD blocked. */ +int +rotate_the_pipeline () +{ + PROCESS *p; + + if (the_pipeline->next == the_pipeline) + return; + for (p = the_pipeline; p->next != the_pipeline; p = p->next) + ; + the_pipeline = p; +} + +/* Reverse the order of the processes in the_pipeline. Must be called with + SIGCHLD blocked. */ +int +reverse_the_pipeline () +{ + PROCESS *p, *n; + + if (the_pipeline->next == the_pipeline) + return; + + for (p = the_pipeline; p->next != the_pipeline; p = p->next) + ; + p->next = (PROCESS *)NULL; + + n = REVERSE_LIST (the_pipeline, PROCESS *); + + the_pipeline = n; + for (p = the_pipeline; p->next; p = p->next) + ; + p->next = the_pipeline; +} +#endif + +/* Map FUNC over the list of jobs. If FUNC returns non-zero, + then it is time to stop mapping, and that is the return value + for map_over_jobs. FUNC is called with a JOB, arg1, arg2, + and INDEX. */ +static int +map_over_jobs (func, arg1, arg2) + sh_job_map_func_t *func; + int arg1, arg2; +{ + register int i; + int result; + sigset_t set, oset; + + if (js.j_jobslots == 0) + return 0; + + BLOCK_CHILD (set, oset); + + /* XXX could use js.j_firstj here */ + for (i = result = 0; i < js.j_jobslots; i++) + { +#if defined (DEBUG) + if (i < js.j_firstj && jobs[i]) + itrace("map_over_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("map_over_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); +#endif + if (jobs[i]) + { + result = (*func)(jobs[i], arg1, arg2, i); + if (result) + break; + } + } + + UNBLOCK_CHILD (oset); + + return (result); +} + +/* Cause all the jobs in the current pipeline to exit. */ +void +terminate_current_pipeline () +{ + if (pipeline_pgrp && pipeline_pgrp != shell_pgrp) + { + killpg (pipeline_pgrp, SIGTERM); + killpg (pipeline_pgrp, SIGCONT); + } +} + +/* Cause all stopped jobs to exit. */ +void +terminate_stopped_jobs () +{ + register int i; + + /* XXX could use js.j_firstj here */ + for (i = 0; i < js.j_jobslots; i++) + { + if (jobs[i] && STOPPED (i)) + { + killpg (jobs[i]->pgrp, SIGTERM); + killpg (jobs[i]->pgrp, SIGCONT); + } + } +} + +/* Cause all jobs, running or stopped, to receive a hangup signal. If + a job is marked J_NOHUP, don't send the SIGHUP. */ +void +hangup_all_jobs () +{ + register int i; + + /* XXX could use js.j_firstj here */ + for (i = 0; i < js.j_jobslots; i++) + { + if (jobs[i]) + { + if (jobs[i]->flags & J_NOHUP) + continue; + killpg (jobs[i]->pgrp, SIGHUP); + if (STOPPED (i)) + killpg (jobs[i]->pgrp, SIGCONT); + } + } +} + +void +kill_current_pipeline () +{ + stop_making_children (); + start_pipeline (); +} + +/* Return the pipeline that PID belongs to. Note that the pipeline + doesn't have to belong to a job. Must be called with SIGCHLD blocked. + If JOBP is non-null, return the index of the job containing PID. */ +static PROCESS * +find_pipeline (pid, alive_only, jobp) + pid_t pid; + int alive_only; + int *jobp; /* index into jobs list or NO_JOB */ +{ + int job; + PROCESS *p; + + /* See if this process is in the pipeline that we are building. */ + if (jobp) + *jobp = NO_JOB; + if (the_pipeline) + { + p = the_pipeline; + do + { + /* Return it if we found it. Don't ever return a recycled pid. */ + if (p->pid == pid && ((alive_only == 0 && PRECYCLED(p) == 0) || PALIVE(p))) + return (p); + + p = p->next; + } + while (p != the_pipeline); + } + + job = find_job (pid, alive_only, &p); + if (jobp) + *jobp = job; + return (job == NO_JOB) ? (PROCESS *)NULL : jobs[job]->pipe; +} + +/* Return the PROCESS * describing PID. If JOBP is non-null return the index + into the jobs array of the job containing PID. Must be called with + SIGCHLD blocked. */ +static PROCESS * +find_process (pid, alive_only, jobp) + pid_t pid; + int alive_only; + int *jobp; /* index into jobs list or NO_JOB */ +{ + PROCESS *p; + + p = find_pipeline (pid, alive_only, jobp); + while (p && p->pid != pid) + p = p->next; + return p; +} + +/* Return the job index that PID belongs to, or NO_JOB if it doesn't + belong to any job. Must be called with SIGCHLD blocked. */ +static int +find_job (pid, alive_only, procp) + pid_t pid; + int alive_only; + PROCESS **procp; +{ + register int i; + PROCESS *p; + + /* XXX could use js.j_firstj here, and should check js.j_lastj */ + for (i = 0; i < js.j_jobslots; i++) + { +#if defined (DEBUG) + if (i < js.j_firstj && jobs[i]) + itrace("find_job: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("find_job: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); +#endif + if (jobs[i]) + { + p = jobs[i]->pipe; + + do + { + if (p->pid == pid && ((alive_only == 0 && PRECYCLED(p) == 0) || PALIVE(p))) + { + if (procp) + *procp = p; + return (i); + } + + p = p->next; + } + while (p != jobs[i]->pipe); + } + } + + return (NO_JOB); +} + +/* Find a job given a PID. If BLOCK is non-zero, block SIGCHLD as + required by find_job. */ +int +get_job_by_pid (pid, block) + pid_t pid; + int block; +{ + int job; + sigset_t set, oset; + + if (block) + BLOCK_CHILD (set, oset); + + job = find_job (pid, 0, NULL); + + if (block) + UNBLOCK_CHILD (oset); + + return job; +} + +/* Print descriptive information about the job with leader pid PID. */ +void +describe_pid (pid) + pid_t pid; +{ + int job; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + job = find_job (pid, 0, NULL); + + if (job != NO_JOB) + fprintf (stderr, "[%d] %ld\n", job + 1, (long)pid); + else + programming_error (_("describe_pid: %ld: no such pid"), (long)pid); + + UNBLOCK_CHILD (oset); +} + +static char * +j_strsignal (s) + int s; +{ + char *x; + + x = strsignal (s); + if (x == 0) + { + x = retcode_name_buffer; + sprintf (x, _("Signal %d"), s); + } + return x; +} + +static char * +printable_job_status (j, p, format) + int j; + PROCESS *p; + int format; +{ + static char *temp; + int es; + + temp = _("Done"); + + if (STOPPED (j) && format == 0) + { + if (posixly_correct == 0 || p == 0 || (WIFSTOPPED (p->status) == 0)) + temp = _("Stopped"); + else + { + temp = retcode_name_buffer; + sprintf (temp, _("Stopped(%s)"), signal_name (WSTOPSIG (p->status))); + } + } + else if (RUNNING (j)) + temp = _("Running"); + else + { + if (WIFSTOPPED (p->status)) + temp = j_strsignal (WSTOPSIG (p->status)); + else if (WIFSIGNALED (p->status)) + temp = j_strsignal (WTERMSIG (p->status)); + else if (WIFEXITED (p->status)) + { + temp = retcode_name_buffer; + es = WEXITSTATUS (p->status); + if (es == 0) + strcpy (temp, _("Done")); + else if (posixly_correct) + sprintf (temp, _("Done(%d)"), es); + else + sprintf (temp, _("Exit %d"), es); + } + else + temp = _("Unknown status"); + } + + return temp; +} + +/* This is the way to print out information on a job if you + know the index. FORMAT is: + + JLIST_NORMAL) [1]+ Running emacs + JLIST_LONG ) [1]+ 2378 Running emacs + -1 ) [1]+ 2378 emacs + + JLIST_NORMAL) [1]+ Stopped ls | more + JLIST_LONG ) [1]+ 2369 Stopped ls + 2367 | more + JLIST_PID_ONLY) + Just list the pid of the process group leader (really + the process group). + JLIST_CHANGED_ONLY) + Use format JLIST_NORMAL, but list only jobs about which + the user has not been notified. */ + +/* Print status for pipeline P. If JOB_INDEX is >= 0, it is the index into + the JOBS array corresponding to this pipeline. FORMAT is as described + above. Must be called with SIGCHLD blocked. + + If you're printing a pipeline that's not in the jobs array, like the + current pipeline as it's being created, pass -1 for JOB_INDEX */ +static void +print_pipeline (p, job_index, format, stream) + PROCESS *p; + int job_index, format; + FILE *stream; +{ + PROCESS *first, *last, *show; + int es, name_padding; + char *temp; + + if (p == 0) + return; + + first = last = p; + while (last->next != first) + last = last->next; + + for (;;) + { + if (p != first) + fprintf (stream, format ? " " : " |"); + + if (format != JLIST_STANDARD) + fprintf (stream, "%5ld", (long)p->pid); + + fprintf (stream, " "); + + if (format > -1 && job_index >= 0) + { + show = format ? p : last; + temp = printable_job_status (job_index, show, format); + + if (p != first) + { + if (format) + { + if (show->running == first->running && + WSTATUS (show->status) == WSTATUS (first->status)) + temp = ""; + } + else + temp = (char *)NULL; + } + + if (temp) + { + fprintf (stream, "%s", temp); + + es = STRLEN (temp); + if (es == 0) + es = 2; /* strlen ("| ") */ + name_padding = LONGEST_SIGNAL_DESC - es; + + fprintf (stream, "%*s", name_padding, ""); + + if ((WIFSTOPPED (show->status) == 0) && + (WIFCONTINUED (show->status) == 0) && + WIFCORED (show->status)) + fprintf (stream, _("(core dumped) ")); + } + } + + if (p != first && format) + fprintf (stream, "| "); + + if (p->command) + fprintf (stream, "%s", p->command); + + if (p == last && job_index >= 0) + { + temp = current_working_directory (); + + if (RUNNING (job_index) && (IS_FOREGROUND (job_index) == 0)) + fprintf (stream, " &"); + + if (strcmp (temp, jobs[job_index]->wd) != 0) + fprintf (stream, + _(" (wd: %s)"), polite_directory_format (jobs[job_index]->wd)); + } + + if (format || (p == last)) + { + /* We need to add a CR only if this is an interactive shell, and + we're reporting the status of a completed job asynchronously. + We can't really check whether this particular job is being + reported asynchronously, so just add the CR if the shell is + currently interactive and asynchronous notification is enabled. */ + if (asynchronous_notification && interactive) + fprintf (stream, "\r\n"); + else + fprintf (stream, "\n"); + } + + if (p == last) + break; + p = p->next; + } + fflush (stream); +} + +/* Print information to STREAM about jobs[JOB_INDEX] according to FORMAT. + Must be called with SIGCHLD blocked or queued with queue_sigchld */ +static void +pretty_print_job (job_index, format, stream) + int job_index, format; + FILE *stream; +{ + register PROCESS *p; + + /* Format only pid information about the process group leader? */ + if (format == JLIST_PID_ONLY) + { + fprintf (stream, "%ld\n", (long)jobs[job_index]->pipe->pid); + return; + } + + if (format == JLIST_CHANGED_ONLY) + { + if (IS_NOTIFIED (job_index)) + return; + format = JLIST_STANDARD; + } + + if (format != JLIST_NONINTERACTIVE) + fprintf (stream, "[%d]%c ", job_index + 1, + (job_index == js.j_current) ? '+': + (job_index == js.j_previous) ? '-' : ' '); + + if (format == JLIST_NONINTERACTIVE) + format = JLIST_LONG; + + p = jobs[job_index]->pipe; + + print_pipeline (p, job_index, format, stream); + + /* We have printed information about this job. When the job's + status changes, waitchld () sets the notification flag to 0. */ + jobs[job_index]->flags |= J_NOTIFIED; +} + +static int +print_job (job, format, state, job_index) + JOB *job; + int format, state, job_index; +{ + if (state == -1 || (JOB_STATE)state == job->state) + pretty_print_job (job_index, format, stdout); + return (0); +} + +void +list_one_job (job, format, ignore, job_index) + JOB *job; + int format, ignore, job_index; +{ + pretty_print_job (job_index, format, stdout); +} + +void +list_stopped_jobs (format) + int format; +{ + cleanup_dead_jobs (); + map_over_jobs (print_job, format, (int)JSTOPPED); +} + +void +list_running_jobs (format) + int format; +{ + cleanup_dead_jobs (); + map_over_jobs (print_job, format, (int)JRUNNING); +} + +/* List jobs. If FORMAT is non-zero, then the long form of the information + is printed, else just a short version. */ +void +list_all_jobs (format) + int format; +{ + cleanup_dead_jobs (); + map_over_jobs (print_job, format, -1); +} + +/* Fork, handling errors. Returns the pid of the newly made child, or 0. + COMMAND is just for remembering the name of the command; we don't do + anything else with it. ASYNC_P says what to do with the tty. If + non-zero, then don't give it away. */ +pid_t +make_child (command, async_p) + char *command; + int async_p; +{ + int forksleep; + sigset_t set, oset; + pid_t pid; + + sigemptyset (&set); + sigaddset (&set, SIGCHLD); + sigaddset (&set, SIGINT); + sigemptyset (&oset); + sigprocmask (SIG_BLOCK, &set, &oset); + + making_children (); + + forksleep = 1; + +#if defined (BUFFERED_INPUT) + /* If default_buffered_input is active, we are reading a script. If + the command is asynchronous, we have already duplicated /dev/null + as fd 0, but have not changed the buffered stream corresponding to + the old fd 0. We don't want to sync the stream in this case. */ + if (default_buffered_input != -1 && + (!async_p || default_buffered_input > 0)) + sync_buffered_stream (default_buffered_input); +#endif /* BUFFERED_INPUT */ + + /* Create the child, handle severe errors. Retry on EAGAIN. */ + while ((pid = fork ()) < 0 && errno == EAGAIN && forksleep < FORKSLEEP_MAX) + { +#if 0 /* for bash-4.2 */ + /* If we can't create any children, try to reap some dead ones. */ + waitchld (-1, 0); +#endif + sys_error ("fork: retry"); + if (sleep (forksleep) != 0) + break; + forksleep <<= 1; + } + + if (pid < 0) + { + sys_error ("fork"); + + /* Kill all of the processes in the current pipeline. */ + terminate_current_pipeline (); + + /* Discard the current pipeline, if any. */ + if (the_pipeline) + kill_current_pipeline (); + + last_command_exit_value = EX_NOEXEC; + throw_to_top_level (); /* Reset signals, etc. */ + } + + if (pid == 0) + { + /* In the child. Give this child the right process group, set the + signals to the default state for a new process. */ + pid_t mypid; + + mypid = getpid (); +#if defined (BUFFERED_INPUT) + /* Close default_buffered_input if it's > 0. We don't close it if it's + 0 because that's the file descriptor used when redirecting input, + and it's wrong to close the file in that case. */ + unset_bash_input (0); +#endif /* BUFFERED_INPUT */ + + /* Restore top-level signal mask. */ + sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL); + + if (job_control) + { + /* All processes in this pipeline belong in the same + process group. */ + + if (pipeline_pgrp == 0) /* This is the first child. */ + pipeline_pgrp = mypid; + + /* Check for running command in backquotes. */ + if (pipeline_pgrp == shell_pgrp) + ignore_tty_job_signals (); + else + default_tty_job_signals (); + + /* Set the process group before trying to mess with the terminal's + process group. This is mandated by POSIX. */ + /* This is in accordance with the Posix 1003.1 standard, + section B.7.2.4, which says that trying to set the terminal + process group with tcsetpgrp() to an unused pgrp value (like + this would have for the first child) is an error. Section + B.4.3.3, p. 237 also covers this, in the context of job control + shells. */ + if (setpgid (mypid, pipeline_pgrp) < 0) + sys_error (_("child setpgid (%ld to %ld)"), (long)mypid, (long)pipeline_pgrp); + + /* By convention (and assumption above), if + pipeline_pgrp == shell_pgrp, we are making a child for + command substitution. + In this case, we don't want to give the terminal to the + shell's process group (we could be in the middle of a + pipeline, for example). */ + if (async_p == 0 && pipeline_pgrp != shell_pgrp && ((subshell_environment&SUBSHELL_ASYNC) == 0)) + give_terminal_to (pipeline_pgrp, 0); + +#if defined (PGRP_PIPE) + if (pipeline_pgrp == mypid) + pipe_read (pgrp_pipe); +#endif + } + else /* Without job control... */ + { + if (pipeline_pgrp == 0) + pipeline_pgrp = shell_pgrp; + + /* If these signals are set to SIG_DFL, we encounter the curious + situation of an interactive ^Z to a running process *working* + and stopping the process, but being unable to do anything with + that process to change its state. On the other hand, if they + are set to SIG_IGN, jobs started from scripts do not stop when + the shell running the script gets a SIGTSTP and stops. */ + + default_tty_job_signals (); + } + +#if defined (PGRP_PIPE) + /* Release the process group pipe, since our call to setpgid () + is done. The last call to sh_closepipe is done in stop_pipeline. */ + sh_closepipe (pgrp_pipe); +#endif /* PGRP_PIPE */ + +#if 0 + /* Don't set last_asynchronous_pid in the child */ + if (async_p) + last_asynchronous_pid = mypid; /* XXX */ + else +#endif +#if defined (RECYCLES_PIDS) + if (last_asynchronous_pid == mypid) + /* Avoid pid aliasing. 1 seems like a safe, unusual pid value. */ + last_asynchronous_pid = 1; +#endif + } + else + { + /* In the parent. Remember the pid of the child just created + as the proper pgrp if this is the first child. */ + + if (first_pid == NO_PID) + first_pid = pid; + else if (pid_wrap == -1 && pid < first_pid) + pid_wrap = 0; + else if (pid_wrap == 0 && pid >= first_pid) + pid_wrap = 1; + + if (job_control) + { + if (pipeline_pgrp == 0) + { + pipeline_pgrp = pid; + /* Don't twiddle terminal pgrps in the parent! This is the bug, + not the good thing of twiddling them in the child! */ + /* give_terminal_to (pipeline_pgrp, 0); */ + } + /* This is done on the recommendation of the Rationale section of + the POSIX 1003.1 standard, where it discusses job control and + shells. It is done to avoid possible race conditions. (Ref. + 1003.1 Rationale, section B.4.3.3, page 236). */ + setpgid (pid, pipeline_pgrp); + } + else + { + if (pipeline_pgrp == 0) + pipeline_pgrp = shell_pgrp; + } + + /* Place all processes into the jobs array regardless of the + state of job_control. */ + add_process (command, pid); + + if (async_p) + last_asynchronous_pid = pid; +#if defined (RECYCLES_PIDS) + else if (last_asynchronous_pid == pid) + /* Avoid pid aliasing. 1 seems like a safe, unusual pid value. */ + last_asynchronous_pid = 1; +#endif + + if (pid_wrap > 0) + delete_old_job (pid); + +#if !defined (RECYCLES_PIDS) + /* Only check for saved status if we've saved more than CHILD_MAX + statuses, unless the system recycles pids. */ + if ((js.c_reaped + bgpids.npid) >= js.c_childmax) +#endif + bgp_delete (pid); /* new process, discard any saved status */ + + last_made_pid = pid; + + /* keep stats */ + js.c_totforked++; + js.c_living++; + + /* Unblock SIGINT and SIGCHLD unless creating a pipeline, in which case + SIGCHLD remains blocked until all commands in the pipeline have been + created. */ + sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); + } + + return (pid); +} + +/* These two functions are called only in child processes. */ +void +ignore_tty_job_signals () +{ + set_signal_handler (SIGTSTP, SIG_IGN); + set_signal_handler (SIGTTIN, SIG_IGN); + set_signal_handler (SIGTTOU, SIG_IGN); +} + +void +default_tty_job_signals () +{ + set_signal_handler (SIGTSTP, SIG_DFL); + set_signal_handler (SIGTTIN, SIG_DFL); + set_signal_handler (SIGTTOU, SIG_DFL); +} + +/* When we end a job abnormally, or if we stop a job, we set the tty to the + state kept in here. When a job ends normally, we set the state in here + to the state of the tty. */ + +static TTYSTRUCT shell_tty_info; + +#if defined (NEW_TTY_DRIVER) +static struct tchars shell_tchars; +static struct ltchars shell_ltchars; +#endif /* NEW_TTY_DRIVER */ + +#if defined (NEW_TTY_DRIVER) && defined (DRAIN_OUTPUT) +/* Since the BSD tty driver does not allow us to change the tty modes + while simultaneously waiting for output to drain and preserving + typeahead, we have to drain the output ourselves before calling + ioctl. We cheat by finding the length of the output queue, and + using select to wait for an appropriate length of time. This is + a hack, and should be labeled as such (it's a hastily-adapted + mutation of a `usleep' implementation). It's only reason for + existing is the flaw in the BSD tty driver. */ + +static int ttspeeds[] = +{ + 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, + 1800, 2400, 4800, 9600, 19200, 38400 +}; + +static void +draino (fd, ospeed) + int fd, ospeed; +{ + register int delay = ttspeeds[ospeed]; + int n; + + if (!delay) + return; + + while ((ioctl (fd, TIOCOUTQ, &n) == 0) && n) + { + if (n > (delay / 100)) + { + struct timeval tv; + + n *= 10; /* 2 bits more for conservativeness. */ + tv.tv_sec = n / delay; + tv.tv_usec = ((n % delay) * 1000000) / delay; + select (fd, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tv); + } + else + break; + } +} +#endif /* NEW_TTY_DRIVER && DRAIN_OUTPUT */ + +/* Return the fd from which we are actually getting input. */ +#define input_tty() (shell_tty != -1) ? shell_tty : fileno (stderr) + +/* Fill the contents of shell_tty_info with the current tty info. */ +int +get_tty_state () +{ + int tty; + + tty = input_tty (); + if (tty != -1) + { +#if defined (NEW_TTY_DRIVER) + ioctl (tty, TIOCGETP, &shell_tty_info); + ioctl (tty, TIOCGETC, &shell_tchars); + ioctl (tty, TIOCGLTC, &shell_ltchars); +#endif /* NEW_TTY_DRIVER */ + +#if defined (TERMIO_TTY_DRIVER) + ioctl (tty, TCGETA, &shell_tty_info); +#endif /* TERMIO_TTY_DRIVER */ + +#if defined (TERMIOS_TTY_DRIVER) + if (tcgetattr (tty, &shell_tty_info) < 0) + { +#if 0 + /* Only print an error message if we're really interactive at + this time. */ + if (interactive) + sys_error ("[%ld: %d (%d)] tcgetattr", (long)getpid (), shell_level, tty); +#endif + return -1; + } +#endif /* TERMIOS_TTY_DRIVER */ + if (check_window_size) + get_new_window_size (0, (int *)0, (int *)0); + } + return 0; +} + +/* Make the current tty use the state in shell_tty_info. */ +int +set_tty_state () +{ + int tty; + + tty = input_tty (); + if (tty != -1) + { +#if defined (NEW_TTY_DRIVER) +# if defined (DRAIN_OUTPUT) + draino (tty, shell_tty_info.sg_ospeed); +# endif /* DRAIN_OUTPUT */ + ioctl (tty, TIOCSETN, &shell_tty_info); + ioctl (tty, TIOCSETC, &shell_tchars); + ioctl (tty, TIOCSLTC, &shell_ltchars); +#endif /* NEW_TTY_DRIVER */ + +#if defined (TERMIO_TTY_DRIVER) + ioctl (tty, TCSETAW, &shell_tty_info); +#endif /* TERMIO_TTY_DRIVER */ + +#if defined (TERMIOS_TTY_DRIVER) + if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0) + { + /* Only print an error message if we're really interactive at + this time. */ + if (interactive) + sys_error ("[%ld: %d (%d)] tcsetattr", (long)getpid (), shell_level, tty); + return -1; + } +#endif /* TERMIOS_TTY_DRIVER */ + } + return 0; +} + +/* Given an index into the jobs array JOB, return the PROCESS struct of the last + process in that job's pipeline. This is the one whose exit status + counts. Must be called with SIGCHLD blocked or queued. */ +static PROCESS * +find_last_proc (job, block) + int job; + int block; +{ + register PROCESS *p; + sigset_t set, oset; + + if (block) + BLOCK_CHILD (set, oset); + + p = jobs[job]->pipe; + while (p && p->next != jobs[job]->pipe) + p = p->next; + + if (block) + UNBLOCK_CHILD (oset); + + return (p); +} + +static pid_t +find_last_pid (job, block) + int job; + int block; +{ + PROCESS *p; + + p = find_last_proc (job, block); + /* Possible race condition here. */ + return p->pid; +} + +/* Wait for a particular child of the shell to finish executing. + This low-level function prints an error message if PID is not + a child of this shell. It returns -1 if it fails, or whatever + wait_for returns otherwise. If the child is not found in the + jobs table, it returns 127. */ +int +wait_for_single_pid (pid) + pid_t pid; +{ + register PROCESS *child; + sigset_t set, oset; + int r, job; + + BLOCK_CHILD (set, oset); + child = find_pipeline (pid, 0, (int *)NULL); + UNBLOCK_CHILD (oset); + + if (child == 0) + { + r = bgp_search (pid); + if (r >= 0) + return r; + } + + if (child == 0) + { + internal_error (_("wait: pid %ld is not a child of this shell"), (long)pid); + return (127); + } + + r = wait_for (pid); + + /* POSIX.2: if we just waited for a job, we can remove it from the jobs + table. */ + BLOCK_CHILD (set, oset); + job = find_job (pid, 0, NULL); + if (job != NO_JOB && jobs[job] && DEADJOB (job)) + jobs[job]->flags |= J_NOTIFIED; + UNBLOCK_CHILD (oset); + + /* If running in posix mode, remove the job from the jobs table immediately */ + if (posixly_correct) + { + cleanup_dead_jobs (); + bgp_delete (pid); + } + + return r; +} + +/* Wait for all of the backgrounds of this shell to finish. */ +void +wait_for_background_pids () +{ + register int i, r, waited_for; + sigset_t set, oset; + pid_t pid; + + for (waited_for = 0;;) + { + BLOCK_CHILD (set, oset); + + /* find first running job; if none running in foreground, break */ + /* XXX could use js.j_firstj and js.j_lastj here */ + for (i = 0; i < js.j_jobslots; i++) + { +#if defined (DEBUG) + if (i < js.j_firstj && jobs[i]) + itrace("wait_for_background_pids: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("wait_for_background_pids: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); +#endif + if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0) + break; + } + if (i == js.j_jobslots) + { + UNBLOCK_CHILD (oset); + break; + } + + /* now wait for the last pid in that job. */ + pid = find_last_pid (i, 0); + UNBLOCK_CHILD (oset); + QUIT; + errno = 0; /* XXX */ + r = wait_for_single_pid (pid); + if (r == -1) + { + /* If we're mistaken about job state, compensate. */ + if (errno == ECHILD) + mark_all_jobs_as_dead (); + } + else + waited_for++; + } + + /* POSIX.2 says the shell can discard the statuses of all completed jobs if + `wait' is called with no arguments. */ + mark_dead_jobs_as_notified (1); + cleanup_dead_jobs (); + bgp_clear (); +} + +/* Make OLD_SIGINT_HANDLER the SIGINT signal handler. */ +#define INVALID_SIGNAL_HANDLER (SigHandler *)wait_for_background_pids +static SigHandler *old_sigint_handler = INVALID_SIGNAL_HANDLER; + +static void +restore_sigint_handler () +{ + if (old_sigint_handler != INVALID_SIGNAL_HANDLER) + { + set_signal_handler (SIGINT, old_sigint_handler); + old_sigint_handler = INVALID_SIGNAL_HANDLER; + } +} + +static int wait_sigint_received; + +/* Handle SIGINT while we are waiting for children in a script to exit. + The `wait' builtin should be interruptible, but all others should be + effectively ignored (i.e. not cause the shell to exit). */ +static sighandler +wait_sigint_handler (sig) + int sig; +{ + SigHandler *sigint_handler; + + if (interrupt_immediately || + (this_shell_builtin && this_shell_builtin == wait_builtin)) + { + last_command_exit_value = EXECUTION_FAILURE; + restore_sigint_handler (); + /* If we got a SIGINT while in `wait', and SIGINT is trapped, do + what POSIX.2 says (see builtins/wait.def for more info). */ + if (this_shell_builtin && this_shell_builtin == wait_builtin && + signal_is_trapped (SIGINT) && + ((sigint_handler = trap_to_sighandler (SIGINT)) == trap_handler)) + { + interrupt_immediately = 0; + trap_handler (SIGINT); /* set pending_traps[SIGINT] */ + wait_signal_received = SIGINT; + longjmp (wait_intr_buf, 1); + } + + ADDINTERRUPT; + QUIT; + } + + /* XXX - should this be interrupt_state? If it is, the shell will act + as if it got the SIGINT interrupt. */ + wait_sigint_received = 1; + + /* Otherwise effectively ignore the SIGINT and allow the running job to + be killed. */ + SIGRETURN (0); +} + +static int +process_exit_signal (status) + WAIT status; +{ + return (WIFSIGNALED (status) ? WTERMSIG (status) : 0); +} + +static int +process_exit_status (status) + WAIT status; +{ + if (WIFSIGNALED (status)) + return (128 + WTERMSIG (status)); + else if (WIFSTOPPED (status) == 0) + return (WEXITSTATUS (status)); + else + return (EXECUTION_SUCCESS); +} + +static WAIT +job_signal_status (job) + int job; +{ + register PROCESS *p; + WAIT s; + + p = jobs[job]->pipe; + do + { + s = p->status; + if (WIFSIGNALED(s) || WIFSTOPPED(s)) + break; + p = p->next; + } + while (p != jobs[job]->pipe); + + return s; +} + +/* Return the exit status of the last process in the pipeline for job JOB. + This is the exit status of the entire job. */ +static WAIT +raw_job_exit_status (job) + int job; +{ + register PROCESS *p; + int fail; + WAIT ret; + + if (pipefail_opt) + { + fail = 0; + p = jobs[job]->pipe; + do + { + if (WSTATUS (p->status) != EXECUTION_SUCCESS) + fail = WSTATUS(p->status); + p = p->next; + } + while (p != jobs[job]->pipe); + WSTATUS (ret) = fail; + return ret; + } + + for (p = jobs[job]->pipe; p->next != jobs[job]->pipe; p = p->next) + ; + return (p->status); +} + +/* Return the exit status of job JOB. This is the exit status of the last + (rightmost) process in the job's pipeline, modified if the job was killed + by a signal or stopped. */ +static int +job_exit_status (job) + int job; +{ + return (process_exit_status (raw_job_exit_status (job))); +} + +static int +job_exit_signal (job) + int job; +{ + return (process_exit_signal (raw_job_exit_status (job))); +} + +#define FIND_CHILD(pid, child) \ + do \ + { \ + child = find_pipeline (pid, 0, (int *)NULL); \ + if (child == 0) \ + { \ + give_terminal_to (shell_pgrp, 0); \ + UNBLOCK_CHILD (oset); \ + internal_error (_("wait_for: No record of process %ld"), (long)pid); \ + restore_sigint_handler (); \ + return (termination_state = 127); \ + } \ + } \ + while (0) + +/* Wait for pid (one of our children) to terminate, then + return the termination state. Returns 127 if PID is not found in + the jobs table. Returns -1 if waitchld() returns -1, indicating + that there are no unwaited-for child processes. */ +int +wait_for (pid) + pid_t pid; +{ + int job, termination_state, r; + WAIT s; + register PROCESS *child; + sigset_t set, oset; + register PROCESS *p; + + /* In the case that this code is interrupted, and we longjmp () out of it, + we are relying on the code in throw_to_top_level () to restore the + top-level signal mask. */ + BLOCK_CHILD (set, oset); + + /* Ignore interrupts while waiting for a job run without job control + to finish. We don't want the shell to exit if an interrupt is + received, only if one of the jobs run is killed via SIGINT. If + job control is not set, the job will be run in the same pgrp as + the shell, and the shell will see any signals the job gets. In + fact, we want this set every time the waiting shell and the waited- + for process are in the same process group, including command + substitution. */ + + /* This is possibly a race condition -- should it go in stop_pipeline? */ + wait_sigint_received = 0; + if (job_control == 0 || (subshell_environment&SUBSHELL_COMSUB)) + { + old_sigint_handler = set_signal_handler (SIGINT, wait_sigint_handler); + if (old_sigint_handler == SIG_IGN) + set_signal_handler (SIGINT, old_sigint_handler); + } + + termination_state = last_command_exit_value; + + if (interactive && job_control == 0) + QUIT; + /* Check for terminating signals and exit the shell if we receive one */ + CHECK_TERMSIG; + + /* If we say wait_for (), then we have a record of this child somewhere. + If it and none of its peers are running, don't call waitchld(). */ + + job = NO_JOB; + do + { + FIND_CHILD (pid, child); + + /* If this child is part of a job, then we are really waiting for the + job to finish. Otherwise, we are waiting for the child to finish. + We check for JDEAD in case the job state has been set by waitchld + after receipt of a SIGCHLD. */ + if (job == NO_JOB) + job = find_job (pid, 0, NULL); + + /* waitchld() takes care of setting the state of the job. If the job + has already exited before this is called, sigchld_handler will have + called waitchld and the state will be set to JDEAD. */ + + if (PRUNNING(child) || (job != NO_JOB && RUNNING (job))) + { +#if defined (WAITPID_BROKEN) /* SCOv4 */ + sigset_t suspend_set; + sigemptyset (&suspend_set); + sigsuspend (&suspend_set); +#else /* !WAITPID_BROKEN */ +# if defined (MUST_UNBLOCK_CHLD) + struct sigaction act, oact; + sigset_t nullset, chldset; + + sigemptyset (&nullset); + sigemptyset (&chldset); + sigprocmask (SIG_SETMASK, &nullset, &chldset); + act.sa_handler = SIG_DFL; + sigemptyset (&act.sa_mask); + sigemptyset (&oact.sa_mask); + act.sa_flags = 0; + sigaction (SIGCHLD, &act, &oact); +# endif + queue_sigchld = 1; + r = waitchld (pid, 1); +# if defined (MUST_UNBLOCK_CHLD) + sigaction (SIGCHLD, &oact, (struct sigaction *)NULL); + sigprocmask (SIG_SETMASK, &chldset, (sigset_t *)NULL); +# endif + queue_sigchld = 0; + if (r == -1 && errno == ECHILD && this_shell_builtin == wait_builtin) + { + termination_state = -1; + goto wait_for_return; + } + + /* If child is marked as running, but waitpid() returns -1/ECHILD, + there is something wrong. Somewhere, wait should have returned + that child's pid. Mark the child as not running and the job, + if it exists, as JDEAD. */ + if (r == -1 && errno == ECHILD) + { + child->running = PS_DONE; + WSTATUS (child->status) = 0; /* XXX -- can't find true status */ + js.c_living = 0; /* no living child processes */ + if (job != NO_JOB) + { + jobs[job]->state = JDEAD; + js.c_reaped++; + js.j_ndead++; + } + } +#endif /* WAITPID_BROKEN */ + } + + /* If the shell is interactive, and job control is disabled, see + if the foreground process has died due to SIGINT and jump out + of the wait loop if it has. waitchld has already restored the + old SIGINT signal handler. */ + if (interactive && job_control == 0) + QUIT; + /* Check for terminating signals and exit the shell if we receive one */ + CHECK_TERMSIG; + } + while (PRUNNING (child) || (job != NO_JOB && RUNNING (job))); + + /* The exit state of the command is either the termination state of the + child, or the termination state of the job. If a job, the status + of the last child in the pipeline is the significant one. If the command + or job was terminated by a signal, note that value also. */ + termination_state = (job != NO_JOB) ? job_exit_status (job) + : process_exit_status (child->status); + last_command_exit_signal = (job != NO_JOB) ? job_exit_signal (job) + : process_exit_signal (child->status); + + /* XXX */ + if ((job != NO_JOB && JOBSTATE (job) == JSTOPPED) || WIFSTOPPED (child->status)) + termination_state = 128 + WSTOPSIG (child->status); + + if (job == NO_JOB || IS_JOBCONTROL (job)) + { + /* XXX - under what circumstances is a job not present in the jobs + table (job == NO_JOB)? + 1. command substitution + + In the case of command substitution, at least, it's probably not + the right thing to give the terminal to the shell's process group, + even though there is code in subst.c:command_substitute to work + around it. + + Things that don't: + $PROMPT_COMMAND execution + process substitution + */ +#if 0 +if (job == NO_JOB) + itrace("wait_for: job == NO_JOB, giving the terminal to shell_pgrp (%ld)", (long)shell_pgrp); +#endif + give_terminal_to (shell_pgrp, 0); + } + + /* If the command did not exit cleanly, or the job is just + being stopped, then reset the tty state back to what it + was before this command. Reset the tty state and notify + the user of the job termination only if the shell is + interactive. Clean up any dead jobs in either case. */ + if (job != NO_JOB) + { + if (interactive_shell && subshell_environment == 0) + { + /* This used to use `child->status'. That's wrong, however, for + pipelines. `child' is the first process in the pipeline. It's + likely that the process we want to check for abnormal termination + or stopping is the last process in the pipeline, especially if + it's long-lived and the first process is short-lived. Since we + know we have a job here, we can check all the processes in this + job's pipeline and see if one of them stopped or terminated due + to a signal. We might want to change this later to just check + the last process in the pipeline. If no process exits due to a + signal, S is left as the status of the last job in the pipeline. */ + s = job_signal_status (job); + + if (WIFSIGNALED (s) || WIFSTOPPED (s)) + { + set_tty_state (); + + /* If the current job was stopped or killed by a signal, and + the user has requested it, get a possibly new window size */ + if (check_window_size && (job == js.j_current || IS_FOREGROUND (job))) + get_new_window_size (0, (int *)0, (int *)0); + } + else + get_tty_state (); + + /* If job control is enabled, the job was started with job + control, the job was the foreground job, and it was killed + by SIGINT, then print a newline to compensate for the kernel + printing the ^C without a trailing newline. */ + if (job_control && IS_JOBCONTROL (job) && IS_FOREGROUND (job) && + WIFSIGNALED (s) && WTERMSIG (s) == SIGINT) + { + /* If SIGINT is not trapped and the shell is in a for, while, + or until loop, act as if the shell received SIGINT as + well, so the loop can be broken. This doesn't call the + SIGINT signal handler; maybe it should. */ + if (signal_is_trapped (SIGINT) == 0 && (loop_level || (shell_compatibility_level > 32 && executing_list))) + ADDINTERRUPT; + else + { + putchar ('\n'); + fflush (stdout); + } + } + } + else if ((subshell_environment & SUBSHELL_COMSUB) && wait_sigint_received) + { + /* If waiting for a job in a subshell started to do command + substitution, simulate getting and being killed by the SIGINT to + pass the status back to our parent. */ + s = job_signal_status (job); + + if (WIFSIGNALED (s) && WTERMSIG (s) == SIGINT && signal_is_trapped (SIGINT) == 0) + { + UNBLOCK_CHILD (oset); + restore_sigint_handler (); + old_sigint_handler = set_signal_handler (SIGINT, SIG_DFL); + if (old_sigint_handler == SIG_IGN) + restore_sigint_handler (); + else + kill (getpid (), SIGINT); + } + } + + /* Moved here from set_job_status_and_cleanup, which is in the SIGCHLD + signal handler path */ + if (DEADJOB (job) && IS_FOREGROUND (job) /*&& subshell_environment == 0*/) + setjstatus (job); + + /* If this job is dead, notify the user of the status. If the shell + is interactive, this will display a message on the terminal. If + the shell is not interactive, make sure we turn on the notify bit + so we don't get an unwanted message about the job's termination, + and so delete_job really clears the slot in the jobs table. */ + notify_and_cleanup (); + } + +wait_for_return: + + UNBLOCK_CHILD (oset); + + /* Restore the original SIGINT signal handler before we return. */ + restore_sigint_handler (); + + return (termination_state); +} + +/* Wait for the last process in the pipeline for JOB. Returns whatever + wait_for returns: the last process's termination state or -1 if there + are no unwaited-for child processes or an error occurs. */ +int +wait_for_job (job) + int job; +{ + pid_t pid; + int r; + sigset_t set, oset; + + BLOCK_CHILD(set, oset); + if (JOBSTATE (job) == JSTOPPED) + internal_warning (_("wait_for_job: job %d is stopped"), job+1); + + pid = find_last_pid (job, 0); + UNBLOCK_CHILD(oset); + r = wait_for (pid); + + /* POSIX.2: we can remove the job from the jobs table if we just waited + for it. */ + BLOCK_CHILD (set, oset); + if (job != NO_JOB && jobs[job] && DEADJOB (job)) + jobs[job]->flags |= J_NOTIFIED; + UNBLOCK_CHILD (oset); + + return r; +} + +/* Print info about dead jobs, and then delete them from the list + of known jobs. This does not actually delete jobs when the + shell is not interactive, because the dead jobs are not marked + as notified. */ +void +notify_and_cleanup () +{ + if (jobs_list_frozen) + return; + + if (interactive || interactive_shell == 0 || sourcelevel) + notify_of_job_status (); + + cleanup_dead_jobs (); +} + +/* Make dead jobs disappear from the jobs array without notification. + This is used when the shell is not interactive. */ +void +reap_dead_jobs () +{ + mark_dead_jobs_as_notified (0); + cleanup_dead_jobs (); +} + +/* Return the next closest (chronologically) job to JOB which is in + STATE. STATE can be JSTOPPED, JRUNNING. NO_JOB is returned if + there is no next recent job. */ +static int +most_recent_job_in_state (job, state) + int job; + JOB_STATE state; +{ + register int i, result; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + for (result = NO_JOB, i = job - 1; i >= 0; i--) + { + if (jobs[i] && (JOBSTATE (i) == state)) + { + result = i; + break; + } + } + + UNBLOCK_CHILD (oset); + + return (result); +} + +/* Return the newest *stopped* job older than JOB, or NO_JOB if not + found. */ +static int +job_last_stopped (job) + int job; +{ + return (most_recent_job_in_state (job, JSTOPPED)); +} + +/* Return the newest *running* job older than JOB, or NO_JOB if not + found. */ +static int +job_last_running (job) + int job; +{ + return (most_recent_job_in_state (job, JRUNNING)); +} + +/* Make JOB be the current job, and make previous be useful. Must be + called with SIGCHLD blocked. */ +static void +set_current_job (job) + int job; +{ + int candidate; + + if (js.j_current != job) + { + js.j_previous = js.j_current; + js.j_current = job; + } + + /* First choice for previous job is the old current job. */ + if (js.j_previous != js.j_current && + js.j_previous != NO_JOB && + jobs[js.j_previous] && + STOPPED (js.j_previous)) + return; + + /* Second choice: Newest stopped job that is older than + the current job. */ + candidate = NO_JOB; + if (STOPPED (js.j_current)) + { + candidate = job_last_stopped (js.j_current); + + if (candidate != NO_JOB) + { + js.j_previous = candidate; + return; + } + } + + /* If we get here, there is either only one stopped job, in which case it is + the current job and the previous job should be set to the newest running + job, or there are only running jobs and the previous job should be set to + the newest running job older than the current job. We decide on which + alternative to use based on whether or not JOBSTATE(js.j_current) is + JSTOPPED. */ + + candidate = RUNNING (js.j_current) ? job_last_running (js.j_current) + : job_last_running (js.j_jobslots); + + if (candidate != NO_JOB) + { + js.j_previous = candidate; + return; + } + + /* There is only a single job, and it is both `+' and `-'. */ + js.j_previous = js.j_current; +} + +/* Make current_job be something useful, if it isn't already. */ + +/* Here's the deal: The newest non-running job should be `+', and the + next-newest non-running job should be `-'. If there is only a single + stopped job, the js.j_previous is the newest non-running job. If there + are only running jobs, the newest running job is `+' and the + next-newest running job is `-'. Must be called with SIGCHLD blocked. */ + +static void +reset_current () +{ + int candidate; + + if (js.j_jobslots && js.j_current != NO_JOB && jobs[js.j_current] && STOPPED (js.j_current)) + candidate = js.j_current; + else + { + candidate = NO_JOB; + + /* First choice: the previous job. */ + if (js.j_previous != NO_JOB && jobs[js.j_previous] && STOPPED (js.j_previous)) + candidate = js.j_previous; + + /* Second choice: the most recently stopped job. */ + if (candidate == NO_JOB) + candidate = job_last_stopped (js.j_jobslots); + + /* Third choice: the newest running job. */ + if (candidate == NO_JOB) + candidate = job_last_running (js.j_jobslots); + } + + /* If we found a job to use, then use it. Otherwise, there + are no jobs period. */ + if (candidate != NO_JOB) + set_current_job (candidate); + else + js.j_current = js.j_previous = NO_JOB; +} + +/* Set up the job structures so we know the job and its processes are + all running. */ +static void +set_job_running (job) + int job; +{ + register PROCESS *p; + + /* Each member of the pipeline is now running. */ + p = jobs[job]->pipe; + + do + { + if (WIFSTOPPED (p->status)) + p->running = PS_RUNNING; /* XXX - could be PS_STOPPED */ + p = p->next; + } + while (p != jobs[job]->pipe); + + /* This means that the job is running. */ + JOBSTATE (job) = JRUNNING; +} + +/* Start a job. FOREGROUND if non-zero says to do that. Otherwise, + start the job in the background. JOB is a zero-based index into + JOBS. Returns -1 if it is unable to start a job, and the return + status of the job otherwise. */ +int +start_job (job, foreground) + int job, foreground; +{ + register PROCESS *p; + int already_running; + sigset_t set, oset; + char *wd, *s; + static TTYSTRUCT save_stty; + + BLOCK_CHILD (set, oset); + + if (DEADJOB (job)) + { + internal_error (_("%s: job has terminated"), this_command_name); + UNBLOCK_CHILD (oset); + return (-1); + } + + already_running = RUNNING (job); + + if (foreground == 0 && already_running) + { + internal_error (_("%s: job %d already in background"), this_command_name, job + 1); + UNBLOCK_CHILD (oset); + return (0); /* XPG6/SUSv3 says this is not an error */ + } + + wd = current_working_directory (); + + /* You don't know about the state of this job. Do you? */ + jobs[job]->flags &= ~J_NOTIFIED; + + if (foreground) + { + set_current_job (job); + jobs[job]->flags |= J_FOREGROUND; + } + + /* Tell the outside world what we're doing. */ + p = jobs[job]->pipe; + + if (foreground == 0) + { + /* POSIX.2 says `bg' doesn't give any indication about current or + previous job. */ + if (posixly_correct == 0) + s = (job == js.j_current) ? "+ ": ((job == js.j_previous) ? "- " : " "); + else + s = " "; + printf ("[%d]%s", job + 1, s); + } + + do + { + printf ("%s%s", + p->command ? p->command : "", + p->next != jobs[job]->pipe? " | " : ""); + p = p->next; + } + while (p != jobs[job]->pipe); + + if (foreground == 0) + printf (" &"); + + if (strcmp (wd, jobs[job]->wd) != 0) + printf (" (wd: %s)", polite_directory_format (jobs[job]->wd)); + + printf ("\n"); + + /* Run the job. */ + if (already_running == 0) + set_job_running (job); + + /* Save the tty settings before we start the job in the foreground. */ + if (foreground) + { + get_tty_state (); + save_stty = shell_tty_info; + /* Give the terminal to this job. */ + if (IS_JOBCONTROL (job)) + give_terminal_to (jobs[job]->pgrp, 0); + } + else + jobs[job]->flags &= ~J_FOREGROUND; + + /* If the job is already running, then don't bother jump-starting it. */ + if (already_running == 0) + { + jobs[job]->flags |= J_NOTIFIED; + killpg (jobs[job]->pgrp, SIGCONT); + } + + if (foreground) + { + pid_t pid; + int st; + + pid = find_last_pid (job, 0); + UNBLOCK_CHILD (oset); + st = wait_for (pid); + shell_tty_info = save_stty; + set_tty_state (); + return (st); + } + else + { + reset_current (); + UNBLOCK_CHILD (oset); + return (0); + } +} + +/* Give PID SIGNAL. This determines what job the pid belongs to (if any). + If PID does belong to a job, and the job is stopped, then CONTinue the + job after giving it SIGNAL. Returns -1 on failure. If GROUP is non-null, + then kill the process group associated with PID. */ +int +kill_pid (pid, sig, group) + pid_t pid; + int sig, group; +{ + register PROCESS *p; + int job, result, negative; + sigset_t set, oset; + + if (pid < -1) + { + pid = -pid; + group = negative = 1; + } + else + negative = 0; + + result = EXECUTION_SUCCESS; + if (group) + { + BLOCK_CHILD (set, oset); + p = find_pipeline (pid, 0, &job); + + if (job != NO_JOB) + { + jobs[job]->flags &= ~J_NOTIFIED; + + /* Kill process in backquotes or one started without job control? */ + + /* If we're passed a pid < -1, just call killpg and see what happens */ + if (negative && jobs[job]->pgrp == shell_pgrp) + result = killpg (pid, sig); + /* If we're killing using job control notification, for example, + without job control active, we have to do things ourselves. */ + else if (jobs[job]->pgrp == shell_pgrp) + { + p = jobs[job]->pipe; + do + { + if (PALIVE (p) == 0) + continue; /* avoid pid recycling problem */ + kill (p->pid, sig); + if (PEXITED (p) && (sig == SIGTERM || sig == SIGHUP)) + kill (p->pid, SIGCONT); + p = p->next; + } + while (p != jobs[job]->pipe); + } + else + { + result = killpg (jobs[job]->pgrp, sig); + if (p && STOPPED (job) && (sig == SIGTERM || sig == SIGHUP)) + killpg (jobs[job]->pgrp, SIGCONT); + /* If we're continuing a stopped job via kill rather than bg or + fg, emulate the `bg' behavior. */ + if (p && STOPPED (job) && (sig == SIGCONT)) + { + set_job_running (job); + jobs[job]->flags &= ~J_FOREGROUND; + jobs[job]->flags |= J_NOTIFIED; + } + } + } + else + result = killpg (pid, sig); + + UNBLOCK_CHILD (oset); + } + else + result = kill (pid, sig); + + return (result); +} + +/* sigchld_handler () flushes at least one of the children that we are + waiting for. It gets run when we have gotten a SIGCHLD signal. */ +static sighandler +sigchld_handler (sig) + int sig; +{ + int n, oerrno; + + oerrno = errno; + REINSTALL_SIGCHLD_HANDLER; + sigchld++; + n = 0; + if (queue_sigchld == 0) + n = waitchld (-1, 0); + errno = oerrno; + SIGRETURN (n); +} + +/* waitchld() reaps dead or stopped children. It's called by wait_for and + sigchld_handler, and runs until there aren't any children terminating any + more. + If BLOCK is 1, this is to be a blocking wait for a single child, although + an arriving SIGCHLD could cause the wait to be non-blocking. It returns + the number of children reaped, or -1 if there are no unwaited-for child + processes. */ +static int +waitchld (wpid, block) + pid_t wpid; + int block; +{ + WAIT status; + PROCESS *child; + pid_t pid; + int call_set_current, last_stopped_job, job, children_exited, waitpid_flags; + static int wcontinued = WCONTINUED; /* run-time fix for glibc problem */ + + call_set_current = children_exited = 0; + last_stopped_job = NO_JOB; + + do + { + /* We don't want to be notified about jobs stopping if job control + is not active. XXX - was interactive_shell instead of job_control */ + waitpid_flags = (job_control && subshell_environment == 0) + ? (WUNTRACED|wcontinued) + : 0; + if (sigchld || block == 0) + waitpid_flags |= WNOHANG; + /* Check for terminating signals and exit the shell if we receive one */ + CHECK_TERMSIG; + + if (block == 1 && queue_sigchld == 0 && (waitpid_flags & WNOHANG) == 0) + { + internal_warning (_("waitchld: turning on WNOHANG to avoid indefinite block")); + waitpid_flags |= WNOHANG; + } + + pid = WAITPID (-1, &status, waitpid_flags); + + /* WCONTINUED may be rejected by waitpid as invalid even when defined */ + if (wcontinued && pid < 0 && errno == EINVAL) + { + wcontinued = 0; + continue; /* jump back to the test and retry without WCONTINUED */ + } + + /* The check for WNOHANG is to make sure we decrement sigchld only + if it was non-zero before we called waitpid. */ + if (sigchld > 0 && (waitpid_flags & WNOHANG)) + sigchld--; + + /* If waitpid returns -1 with errno == ECHILD, there are no more + unwaited-for child processes of this shell. */ + if (pid < 0 && errno == ECHILD) + { + if (children_exited == 0) + return -1; + else + break; + } + + /* If waitpid returns 0, there are running children. If it returns -1, + the only other error POSIX says it can return is EINTR. */ + CHECK_TERMSIG; + if (pid <= 0) + continue; /* jumps right to the test */ + + /* children_exited is used to run traps on SIGCHLD. We don't want to + run the trap if a process is just being continued. */ + if (WIFCONTINUED(status) == 0) + { + children_exited++; + js.c_living--; + } + + /* Locate our PROCESS for this pid. */ + child = find_process (pid, 1, &job); /* want living procs only */ + +#if defined (COPROCESS_SUPPORT) + coproc_pidchk (pid, status); +#endif + + /* It is not an error to have a child terminate that we did + not have a record of. This child could have been part of + a pipeline in backquote substitution. Even so, I'm not + sure child is ever non-zero. */ + if (child == 0) + { + if (WIFEXITED (status) || WIFSIGNALED (status)) + js.c_reaped++; + continue; + } + + /* Remember status, and whether or not the process is running. */ + child->status = status; + child->running = WIFCONTINUED(status) ? PS_RUNNING : PS_DONE; + + if (PEXITED (child)) + { + js.c_totreaped++; + if (job != NO_JOB) + js.c_reaped++; + } + + if (job == NO_JOB) + continue; + + call_set_current += set_job_status_and_cleanup (job); + + if (STOPPED (job)) + last_stopped_job = job; + else if (DEADJOB (job) && last_stopped_job == job) + last_stopped_job = NO_JOB; + } + while ((sigchld || block == 0) && pid > (pid_t)0); + + /* If a job was running and became stopped, then set the current + job. Otherwise, don't change a thing. */ + if (call_set_current) + { + if (last_stopped_job != NO_JOB) + set_current_job (last_stopped_job); + else + reset_current (); + } + + /* Call a SIGCHLD trap handler for each child that exits, if one is set. */ + if (job_control && signal_is_trapped (SIGCHLD) && children_exited && + trap_list[SIGCHLD] != (char *)IGNORE_SIG) + { + if (posixly_correct && this_shell_builtin && this_shell_builtin == wait_builtin) + { + interrupt_immediately = 0; + trap_handler (SIGCHLD); /* set pending_traps[SIGCHLD] */ + wait_signal_received = SIGCHLD; + longjmp (wait_intr_buf, 1); + } + + run_sigchld_trap (children_exited); + } + + /* We have successfully recorded the useful information about this process + that has just changed state. If we notify asynchronously, and the job + that this process belongs to is no longer running, then notify the user + of that fact now. */ + if (asynchronous_notification && interactive) + notify_of_job_status (); + + return (children_exited); +} + +/* Set the status of JOB and perform any necessary cleanup if the job is + marked as JDEAD. + + Currently, the cleanup activity is restricted to handling any SIGINT + received while waiting for a foreground job to finish. */ +static int +set_job_status_and_cleanup (job) + int job; +{ + PROCESS *child; + int tstatus, job_state, any_stopped, any_tstped, call_set_current; + SigHandler *temp_handler; + + child = jobs[job]->pipe; + jobs[job]->flags &= ~J_NOTIFIED; + + call_set_current = 0; + + /* + * COMPUTE JOB STATUS + */ + + /* If all children are not running, but any of them is stopped, then + the job is stopped, not dead. */ + job_state = any_stopped = any_tstped = 0; + do + { + job_state |= PRUNNING (child); +#if 0 + if (PEXITED (child) && (WIFSTOPPED (child->status))) +#else + /* Only checking for WIFSTOPPED now, not for PS_DONE */ + if (PSTOPPED (child)) +#endif + { + any_stopped = 1; + any_tstped |= interactive && job_control && + (WSTOPSIG (child->status) == SIGTSTP); + } + child = child->next; + } + while (child != jobs[job]->pipe); + + /* If job_state != 0, the job is still running, so don't bother with + setting the process exit status and job state unless we're + transitioning from stopped to running. */ + if (job_state != 0 && JOBSTATE(job) != JSTOPPED) + return 0; + + /* + * SET JOB STATUS + */ + + /* The job is either stopped or dead. Set the state of the job accordingly. */ + if (any_stopped) + { + jobs[job]->state = JSTOPPED; + jobs[job]->flags &= ~J_FOREGROUND; + call_set_current++; + /* Suspending a job with SIGTSTP breaks all active loops. */ + if (any_tstped && loop_level) + breaking = loop_level; + } + else if (job_state != 0) /* was stopped, now running */ + { + jobs[job]->state = JRUNNING; + call_set_current++; + } + else + { + jobs[job]->state = JDEAD; + js.j_ndead++; + +#if 0 + if (IS_FOREGROUND (job)) + setjstatus (job); +#endif + + /* If this job has a cleanup function associated with it, call it + with `cleanarg' as the single argument, then set the function + pointer to NULL so it is not inadvertently called twice. The + cleanup function is responsible for deallocating cleanarg. */ + if (jobs[job]->j_cleanup) + { + (*jobs[job]->j_cleanup) (jobs[job]->cleanarg); + jobs[job]->j_cleanup = (sh_vptrfunc_t *)NULL; + } + } + + /* + * CLEANUP + * + * Currently, we just do special things if we got a SIGINT while waiting + * for a foreground job to complete + */ + + if (JOBSTATE (job) == JDEAD) + { + /* If we're running a shell script and we get a SIGINT with a + SIGINT trap handler, but the foreground job handles it and + does not exit due to SIGINT, run the trap handler but do not + otherwise act as if we got the interrupt. */ + if (wait_sigint_received && interactive_shell == 0 && + WIFSIGNALED (child->status) == 0 && IS_FOREGROUND (job) && + signal_is_trapped (SIGINT)) + { + int old_frozen; + wait_sigint_received = 0; + last_command_exit_value = process_exit_status (child->status); + + old_frozen = jobs_list_frozen; + jobs_list_frozen = 1; + tstatus = maybe_call_trap_handler (SIGINT); + jobs_list_frozen = old_frozen; + } + + /* If the foreground job is killed by SIGINT when job control is not + active, we need to perform some special handling. + + The check of wait_sigint_received is a way to determine if the + SIGINT came from the keyboard (in which case the shell has already + seen it, and wait_sigint_received is non-zero, because keyboard + signals are sent to process groups) or via kill(2) to the foreground + process by another process (or itself). If the shell did receive the + SIGINT, it needs to perform normal SIGINT processing. */ + else if (wait_sigint_received && (WTERMSIG (child->status) == SIGINT) && + IS_FOREGROUND (job) && IS_JOBCONTROL (job) == 0) + { + int old_frozen; + + wait_sigint_received = 0; + + /* If SIGINT is trapped, set the exit status so that the trap + handler can see it. */ + if (signal_is_trapped (SIGINT)) + last_command_exit_value = process_exit_status (child->status); + + /* If the signal is trapped, let the trap handler get it no matter + what and simply return if the trap handler returns. + maybe_call_trap_handler() may cause dead jobs to be removed from + the job table because of a call to execute_command. We work + around this by setting JOBS_LIST_FROZEN. */ + old_frozen = jobs_list_frozen; + jobs_list_frozen = 1; + tstatus = maybe_call_trap_handler (SIGINT); + jobs_list_frozen = old_frozen; + if (tstatus == 0 && old_sigint_handler != INVALID_SIGNAL_HANDLER) + { + /* wait_sigint_handler () has already seen SIGINT and + allowed the wait builtin to jump out. We need to + call the original SIGINT handler, if necessary. If + the original handler is SIG_DFL, we need to resend + the signal to ourselves. */ + + temp_handler = old_sigint_handler; + + /* Bogus. If we've reset the signal handler as the result + of a trap caught on SIGINT, then old_sigint_handler + will point to trap_handler, which now knows nothing about + SIGINT (if we reset the sighandler to the default). + In this case, we have to fix things up. What a crock. */ + if (temp_handler == trap_handler && signal_is_trapped (SIGINT) == 0) + temp_handler = trap_to_sighandler (SIGINT); + restore_sigint_handler (); + if (temp_handler == SIG_DFL) + termsig_handler (SIGINT); + else if (temp_handler != SIG_IGN) + (*temp_handler) (SIGINT); + } + } + } + + return call_set_current; +} + +/* Build the array of values for the $PIPESTATUS variable from the set of + exit statuses of all processes in the job J. */ +static void +setjstatus (j) + int j; +{ +#if defined (ARRAY_VARS) + register int i; + register PROCESS *p; + + for (i = 1, p = jobs[j]->pipe; p->next != jobs[j]->pipe; p = p->next, i++) + ; + i++; + if (statsize < i) + { + pstatuses = (int *)xrealloc (pstatuses, i * sizeof (int)); + statsize = i; + } + i = 0; + p = jobs[j]->pipe; + do + { + pstatuses[i++] = process_exit_status (p->status); + p = p->next; + } + while (p != jobs[j]->pipe); + + pstatuses[i] = -1; /* sentinel */ + set_pipestatus_array (pstatuses, i); +#endif +} + +void +run_sigchld_trap (nchild) + int nchild; +{ + char *trap_command; + int i; + + /* Turn off the trap list during the call to parse_and_execute () + to avoid potentially infinite recursive calls. Preserve the + values of last_command_exit_value, last_made_pid, and the_pipeline + around the execution of the trap commands. */ + trap_command = savestring (trap_list[SIGCHLD]); + + begin_unwind_frame ("SIGCHLD trap"); + unwind_protect_int (last_command_exit_value); + unwind_protect_int (last_command_exit_signal); + unwind_protect_var (last_made_pid); + unwind_protect_int (interrupt_immediately); + unwind_protect_int (jobs_list_frozen); + unwind_protect_pointer (the_pipeline); + unwind_protect_pointer (subst_assign_varlist); + + /* We have to add the commands this way because they will be run + in reverse order of adding. We don't want maybe_set_sigchld_trap () + to reference freed memory. */ + add_unwind_protect (xfree, trap_command); + add_unwind_protect (maybe_set_sigchld_trap, trap_command); + + subst_assign_varlist = (WORD_LIST *)NULL; + the_pipeline = (PROCESS *)NULL; + + set_impossible_sigchld_trap (); + jobs_list_frozen = 1; + for (i = 0; i < nchild; i++) + { + interrupt_immediately = 1; + parse_and_execute (savestring (trap_command), "trap", SEVAL_NOHIST|SEVAL_RESETLINE); + } + + run_unwind_frame ("SIGCHLD trap"); +} + +/* Function to call when you want to notify people of changes + in job status. This prints out all jobs which are pending + notification to stderr, and marks those printed as already + notified, thus making them candidates for cleanup. */ +static void +notify_of_job_status () +{ + register int job, termsig; + char *dir; + sigset_t set, oset; + WAIT s; + + if (jobs == 0 || js.j_jobslots == 0) + return; + + if (old_ttou != 0) + { + sigemptyset (&set); + sigaddset (&set, SIGCHLD); + sigaddset (&set, SIGTTOU); + sigemptyset (&oset); + sigprocmask (SIG_BLOCK, &set, &oset); + } + else + queue_sigchld++; + + /* XXX could use js.j_firstj here */ + for (job = 0, dir = (char *)NULL; job < js.j_jobslots; job++) + { + if (jobs[job] && IS_NOTIFIED (job) == 0) + { + s = raw_job_exit_status (job); + termsig = WTERMSIG (s); + + /* POSIX.2 says we have to hang onto the statuses of at most the + last CHILD_MAX background processes if the shell is running a + script. If the shell is running a script, either from a file + or standard input, don't print anything unless the job was + killed by a signal. */ + if (startup_state == 0 && WIFSIGNALED (s) == 0 && + ((DEADJOB (job) && IS_FOREGROUND (job) == 0) || STOPPED (job))) + continue; + +#if 0 + /* If job control is disabled, don't print the status messages. + Mark dead jobs as notified so that they get cleaned up. If + startup_state == 2, we were started to run `-c command', so + don't print anything. */ + if ((job_control == 0 && interactive_shell) || startup_state == 2) +#else + /* If job control is disabled, don't print the status messages. + Mark dead jobs as notified so that they get cleaned up. If + startup_state == 2 and subshell_environment has the + SUBSHELL_COMSUB bit turned on, we were started to run a command + substitution, so don't print anything. */ + if ((job_control == 0 && interactive_shell) || + (startup_state == 2 && (subshell_environment & SUBSHELL_COMSUB))) +#endif + { + /* POSIX.2 compatibility: if the shell is not interactive, + hang onto the job corresponding to the last asynchronous + pid until the user has been notified of its status or does + a `wait'. */ + if (DEADJOB (job) && (interactive_shell || (find_last_pid (job, 0) != last_asynchronous_pid))) + jobs[job]->flags |= J_NOTIFIED; + continue; + } + + /* Print info on jobs that are running in the background, + and on foreground jobs that were killed by anything + except SIGINT (and possibly SIGPIPE). */ + switch (JOBSTATE (job)) + { + case JDEAD: + if (interactive_shell == 0 && termsig && WIFSIGNALED (s) && + termsig != SIGINT && +#if defined (DONT_REPORT_SIGPIPE) + termsig != SIGPIPE && +#endif + signal_is_trapped (termsig) == 0) + { + /* Don't print `0' for a line number. */ + fprintf (stderr, _("%s: line %d: "), get_name_for_error (), (line_number == 0) ? 1 : line_number); + pretty_print_job (job, JLIST_NONINTERACTIVE, stderr); + } + else if (IS_FOREGROUND (job)) + { +#if !defined (DONT_REPORT_SIGPIPE) + if (termsig && WIFSIGNALED (s) && termsig != SIGINT) +#else + if (termsig && WIFSIGNALED (s) && termsig != SIGINT && termsig != SIGPIPE) +#endif + { + fprintf (stderr, "%s", j_strsignal (termsig)); + + if (WIFCORED (s)) + fprintf (stderr, _(" (core dumped)")); + + fprintf (stderr, "\n"); + } + } + else if (job_control) /* XXX job control test added */ + { + if (dir == 0) + dir = current_working_directory (); + pretty_print_job (job, JLIST_STANDARD, stderr); + if (dir && strcmp (dir, jobs[job]->wd) != 0) + fprintf (stderr, + _("(wd now: %s)\n"), polite_directory_format (dir)); + } + + jobs[job]->flags |= J_NOTIFIED; + break; + + case JSTOPPED: + fprintf (stderr, "\n"); + if (dir == 0) + dir = current_working_directory (); + pretty_print_job (job, JLIST_STANDARD, stderr); + if (dir && (strcmp (dir, jobs[job]->wd) != 0)) + fprintf (stderr, + _("(wd now: %s)\n"), polite_directory_format (dir)); + jobs[job]->flags |= J_NOTIFIED; + break; + + case JRUNNING: + case JMIXED: + break; + + default: + programming_error ("notify_of_job_status"); + } + } + } + if (old_ttou != 0) + sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); + else + queue_sigchld--; +} + +/* Initialize the job control mechanism, and set up the tty stuff. */ +int +initialize_job_control (force) + int force; +{ + pid_t t; + int t_errno; + + t_errno = -1; + shell_pgrp = getpgid (0); + + if (shell_pgrp == -1) + { + sys_error (_("initialize_job_control: getpgrp failed")); + exit (1); + } + + /* We can only have job control if we are interactive. */ + if (interactive == 0) + { + job_control = 0; + original_pgrp = NO_PID; + shell_tty = fileno (stderr); + } + else + { + shell_tty = -1; + + /* If forced_interactive is set, we skip the normal check that stderr + is attached to a tty, so we need to check here. If it's not, we + need to see whether we have a controlling tty by opening /dev/tty, + since trying to use job control tty pgrp manipulations on a non-tty + is going to fail. */ + if (forced_interactive && isatty (fileno (stderr)) == 0) + shell_tty = open ("/dev/tty", O_RDWR|O_NONBLOCK); + + /* Get our controlling terminal. If job_control is set, or + interactive is set, then this is an interactive shell no + matter where fd 2 is directed. */ + if (shell_tty == -1) + shell_tty = dup (fileno (stderr)); /* fd 2 */ + + 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. */ + if (shell_pgrp == 0) + { + shell_pgrp = getpid (); + setpgid (0, shell_pgrp); + tcsetpgrp (shell_tty, shell_pgrp); + } + + while ((terminal_pgrp = tcgetpgrp (shell_tty)) != -1) + { + if (shell_pgrp != terminal_pgrp) + { + SigHandler *ottin; + + ottin = set_signal_handler(SIGTTIN, SIG_DFL); + kill (0, SIGTTIN); + set_signal_handler (SIGTTIN, ottin); + continue; + } + break; + } + + if (terminal_pgrp == -1) + t_errno = errno; + + /* Make sure that we are using the new line discipline. */ + if (set_new_line_discipline (shell_tty) < 0) + { + sys_error (_("initialize_job_control: line discipline")); + job_control = 0; + } + else + { + original_pgrp = shell_pgrp; + shell_pgrp = getpid (); + + if ((original_pgrp != shell_pgrp) && (setpgid (0, shell_pgrp) < 0)) + { + sys_error (_("initialize_job_control: setpgid")); + shell_pgrp = original_pgrp; + } + + job_control = 1; + + /* If (and only if) we just set our process group to our pid, + thereby becoming a process group leader, and the terminal + is not in the same process group as our (new) process group, + then set the terminal's process group to our (new) process + group. If that fails, set our process group back to what it + was originally (so we can still read from the terminal) and + turn off job control. */ + if (shell_pgrp != original_pgrp && shell_pgrp != terminal_pgrp) + { + if (give_terminal_to (shell_pgrp, 0) < 0) + { + t_errno = errno; + setpgid (0, original_pgrp); + shell_pgrp = original_pgrp; + job_control = 0; + } + } + + if (job_control && ((t = tcgetpgrp (shell_tty)) == -1 || t != shell_pgrp)) + { + if (t_errno != -1) + errno = t_errno; + sys_error (_("cannot set terminal process group (%d)"), t); + job_control = 0; + } + } + if (job_control == 0) + internal_error (_("no job control in this shell")); + } + + if (shell_tty != fileno (stderr)) + SET_CLOSE_ON_EXEC (shell_tty); + + set_signal_handler (SIGCHLD, sigchld_handler); + + change_flag ('m', job_control ? '-' : '+'); + + if (interactive) + get_tty_state (); + + if (js.c_childmax < 0) + js.c_childmax = getmaxchild (); + if (js.c_childmax < 0) + js.c_childmax = DEFAULT_CHILD_MAX; + + return job_control; +} + +#ifdef DEBUG +void +debug_print_pgrps () +{ + itrace("original_pgrp = %ld shell_pgrp = %ld terminal_pgrp = %ld", + (long)original_pgrp, (long)shell_pgrp, (long)terminal_pgrp); + itrace("tcgetpgrp(%d) -> %ld, getpgid(0) -> %ld", + shell_tty, (long)tcgetpgrp (shell_tty), (long)getpgid(0)); +} +#endif + +/* Set the line discipline to the best this system has to offer. + Return -1 if this is not possible. */ +static int +set_new_line_discipline (tty) + int tty; +{ +#if defined (NEW_TTY_DRIVER) + int ldisc; + + if (ioctl (tty, TIOCGETD, &ldisc) < 0) + return (-1); + + if (ldisc != NTTYDISC) + { + ldisc = NTTYDISC; + + if (ioctl (tty, TIOCSETD, &ldisc) < 0) + return (-1); + } + return (0); +#endif /* NEW_TTY_DRIVER */ + +#if defined (TERMIO_TTY_DRIVER) +# if defined (TERMIO_LDISC) && (NTTYDISC) + if (ioctl (tty, TCGETA, &shell_tty_info) < 0) + return (-1); + + if (shell_tty_info.c_line != NTTYDISC) + { + shell_tty_info.c_line = NTTYDISC; + if (ioctl (tty, TCSETAW, &shell_tty_info) < 0) + return (-1); + } +# endif /* TERMIO_LDISC && NTTYDISC */ + return (0); +#endif /* TERMIO_TTY_DRIVER */ + +#if defined (TERMIOS_TTY_DRIVER) +# if defined (TERMIOS_LDISC) && defined (NTTYDISC) + if (tcgetattr (tty, &shell_tty_info) < 0) + return (-1); + + if (shell_tty_info.c_line != NTTYDISC) + { + shell_tty_info.c_line = NTTYDISC; + if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0) + return (-1); + } +# endif /* TERMIOS_LDISC && NTTYDISC */ + return (0); +#endif /* TERMIOS_TTY_DRIVER */ + +#if !defined (NEW_TTY_DRIVER) && !defined (TERMIO_TTY_DRIVER) && !defined (TERMIOS_TTY_DRIVER) + return (-1); +#endif +} + +/* Setup this shell to handle C-C, etc. */ +void +initialize_job_signals () +{ + if (interactive) + { + set_signal_handler (SIGINT, sigint_sighandler); + set_signal_handler (SIGTSTP, SIG_IGN); + set_signal_handler (SIGTTOU, SIG_IGN); + set_signal_handler (SIGTTIN, SIG_IGN); + } + else if (job_control) + { + old_tstp = set_signal_handler (SIGTSTP, sigstop_sighandler); + old_ttin = set_signal_handler (SIGTTIN, sigstop_sighandler); + old_ttou = set_signal_handler (SIGTTOU, sigstop_sighandler); + } + /* Leave these things alone for non-interactive shells without job + control. */ +} + +/* Here we handle CONT signals. */ +static sighandler +sigcont_sighandler (sig) + int sig; +{ + initialize_job_signals (); + set_signal_handler (SIGCONT, old_cont); + kill (getpid (), SIGCONT); + + SIGRETURN (0); +} + +/* Here we handle stop signals while we are running not as a login shell. */ +static sighandler +sigstop_sighandler (sig) + int sig; +{ + set_signal_handler (SIGTSTP, old_tstp); + set_signal_handler (SIGTTOU, old_ttou); + set_signal_handler (SIGTTIN, old_ttin); + + old_cont = set_signal_handler (SIGCONT, sigcont_sighandler); + + give_terminal_to (shell_pgrp, 0); + + kill (getpid (), sig); + + SIGRETURN (0); +} + +/* Give the terminal to PGRP. */ +int +give_terminal_to (pgrp, force) + pid_t pgrp; + int force; +{ + sigset_t set, oset; + int r, e; + + r = 0; + if (job_control || force) + { + sigemptyset (&set); + sigaddset (&set, SIGTTOU); + sigaddset (&set, SIGTTIN); + sigaddset (&set, SIGTSTP); + sigaddset (&set, SIGCHLD); + sigemptyset (&oset); + sigprocmask (SIG_BLOCK, &set, &oset); + + if (tcsetpgrp (shell_tty, pgrp) < 0) + { + /* Maybe we should print an error message? */ +#if 0 + sys_error ("tcsetpgrp(%d) failed: pid %ld to pgrp %ld", + shell_tty, (long)getpid(), (long)pgrp); +#endif + r = -1; + e = errno; + } + else + terminal_pgrp = pgrp; + sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); + } + + if (r == -1) + errno = e; + + return r; +} + +/* Give terminal to NPGRP iff it's currently owned by OPGRP. FLAGS are the + flags to pass to give_terminal_to(). */ +static int +maybe_give_terminal_to (opgrp, npgrp, flags) + pid_t opgrp, npgrp; + int flags; +{ + int tpgrp; + + tpgrp = tcgetpgrp (shell_tty); + if (tpgrp < 0 && errno == ENOTTY) + return -1; + if (tpgrp == npgrp) + { + terminal_pgrp = npgrp; + return 0; + } + else if (tpgrp != opgrp) + { +#if defined (DEBUG) + internal_warning ("maybe_give_terminal_to: terminal pgrp == %d shell pgrp = %d new pgrp = %d", tpgrp, opgrp, npgrp); +#endif + return -1; + } + else + return (give_terminal_to (npgrp, flags)); +} + +/* Clear out any jobs in the job array. This is intended to be used by + children of the shell, who should not have any job structures as baggage + when they start executing (forking subshells for parenthesized execution + and functions with pipes are the two that spring to mind). If RUNNING_ONLY + is nonzero, only running jobs are removed from the table. */ +void +delete_all_jobs (running_only) + int running_only; +{ + register int i; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + /* XXX - need to set j_lastj, j_firstj appropriately if running_only != 0. */ + if (js.j_jobslots) + { + js.j_current = js.j_previous = NO_JOB; + + /* XXX could use js.j_firstj here */ + for (i = 0; i < js.j_jobslots; i++) + { +#if defined (DEBUG) + if (i < js.j_firstj && jobs[i]) + itrace("delete_all_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("delete_all_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); +#endif + if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i)))) + delete_job (i, DEL_WARNSTOPPED); + } + if (running_only == 0) + { + free ((char *)jobs); + js.j_jobslots = 0; + js.j_firstj = js.j_lastj = js.j_njobs = 0; + } + } + + if (running_only == 0) + bgp_clear (); + + UNBLOCK_CHILD (oset); +} + +/* Mark all jobs in the job array so that they don't get a SIGHUP when the + shell gets one. If RUNNING_ONLY is nonzero, mark only running jobs. */ +void +nohup_all_jobs (running_only) + int running_only; +{ + register int i; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + if (js.j_jobslots) + { + /* XXX could use js.j_firstj here */ + for (i = 0; i < js.j_jobslots; i++) + if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i)))) + nohup_job (i); + } + + UNBLOCK_CHILD (oset); +} + +int +count_all_jobs () +{ + int i, n; + sigset_t set, oset; + + /* This really counts all non-dead jobs. */ + BLOCK_CHILD (set, oset); + /* XXX could use js.j_firstj here */ + for (i = n = 0; i < js.j_jobslots; i++) + { +#if defined (DEBUG) + if (i < js.j_firstj && jobs[i]) + itrace("count_all_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("count_all_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); +#endif + if (jobs[i] && DEADJOB(i) == 0) + n++; + } + UNBLOCK_CHILD (oset); + return n; +} + +static void +mark_all_jobs_as_dead () +{ + register int i; + sigset_t set, oset; + + if (js.j_jobslots == 0) + return; + + BLOCK_CHILD (set, oset); + + /* XXX could use js.j_firstj here */ + for (i = 0; i < js.j_jobslots; i++) + if (jobs[i]) + { + jobs[i]->state = JDEAD; + js.j_ndead++; + } + + UNBLOCK_CHILD (oset); +} + +/* Mark all dead jobs as notified, so delete_job () cleans them out + of the job table properly. POSIX.2 says we need to save the + status of the last CHILD_MAX jobs, so we count the number of dead + jobs and mark only enough as notified to save CHILD_MAX statuses. */ +static void +mark_dead_jobs_as_notified (force) + int force; +{ + register int i, ndead, ndeadproc; + sigset_t set, oset; + + if (js.j_jobslots == 0) + return; + + BLOCK_CHILD (set, oset); + + /* If FORCE is non-zero, we don't have to keep CHILD_MAX statuses + around; just run through the array. */ + if (force) + { + /* XXX could use js.j_firstj here */ + for (i = 0; i < js.j_jobslots; i++) + { + if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid))) + jobs[i]->flags |= J_NOTIFIED; + } + UNBLOCK_CHILD (oset); + return; + } + + /* Mark enough dead jobs as notified to keep CHILD_MAX processes left in the + array with the corresponding not marked as notified. This is a better + way to avoid pid aliasing and reuse problems than keeping the POSIX- + mandated CHILD_MAX jobs around. delete_job() takes care of keeping the + bgpids list regulated. */ + + /* Count the number of dead jobs */ + /* XXX could use js.j_firstj here */ + for (i = ndead = ndeadproc = 0; i < js.j_jobslots; i++) + { +#if defined (DEBUG) + if (i < js.j_firstj && jobs[i]) + itrace("mark_dead_jobs_as_notified: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("mark_dead_jobs_as_notified: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); +#endif + if (jobs[i] && DEADJOB (i)) + { + ndead++; + ndeadproc += processes_in_job (i); + } + } + +#ifdef DEBUG + if (ndeadproc != js.c_reaped) + itrace("mark_dead_jobs_as_notified: ndeadproc (%d) != js.c_reaped (%d)", ndeadproc, js.c_reaped); + if (ndead != js.j_ndead) + itrace("mark_dead_jobs_as_notified: ndead (%d) != js.j_ndead (%d)", ndead, js.j_ndead); +#endif + + if (js.c_childmax < 0) + js.c_childmax = getmaxchild (); + if (js.c_childmax < 0) + js.c_childmax = DEFAULT_CHILD_MAX; + + /* Don't do anything if the number of dead processes is less than CHILD_MAX + and we're not forcing a cleanup. */ + if (ndeadproc <= js.c_childmax) + { + UNBLOCK_CHILD (oset); + return; + } + +#if 0 +itrace("mark_dead_jobs_as_notified: child_max = %d ndead = %d ndeadproc = %d", js.c_childmax, ndead, ndeadproc); +#endif + + /* Mark enough dead jobs as notified that we keep CHILD_MAX jobs in + the list. This isn't exactly right yet; changes need to be made + to stop_pipeline so we don't mark the newer jobs after we've + created CHILD_MAX slots in the jobs array. This needs to be + integrated with a way to keep the jobs array from growing without + bound. Maybe we wrap back around to 0 after we reach some max + limit, and there are sufficient job slots free (keep track of total + size of jobs array (js.j_jobslots) and running count of number of jobs + in jobs array. Then keep a job index corresponding to the `oldest job' + and start this loop there, wrapping around as necessary. In effect, + we turn the list into a circular buffer. */ + /* XXX could use js.j_firstj here */ + for (i = 0; i < js.j_jobslots; i++) + { + if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid))) + { +#if defined (DEBUG) + if (i < js.j_firstj && jobs[i]) + itrace("mark_dead_jobs_as_notified: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("mark_dead_jobs_as_notified: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); +#endif + /* If marking this job as notified would drop us down below + child_max, don't mark it so we can keep at least child_max + statuses. XXX -- need to check what Posix actually says + about keeping statuses. */ + if ((ndeadproc -= processes_in_job (i)) <= js.c_childmax) + break; + jobs[i]->flags |= J_NOTIFIED; + } + } + + UNBLOCK_CHILD (oset); +} + +/* Here to allow other parts of the shell (like the trap stuff) to + unfreeze the jobs list. */ +void +unfreeze_jobs_list () +{ + jobs_list_frozen = 0; +} + +/* Allow or disallow job control to take place. Returns the old value + of job_control. */ +int +set_job_control (arg) + int arg; +{ + int old; + + old = job_control; + job_control = arg; + + /* If we're turning on job control, reset pipeline_pgrp so make_child will + put new child processes into the right pgrp */ + if (job_control != old && job_control) + pipeline_pgrp = 0; + + return (old); +} + +/* Turn off all traces of job control. This is run by children of the shell + which are going to do shellsy things, like wait (), etc. */ +void +without_job_control () +{ + stop_making_children (); + start_pipeline (); +#if defined (PGRP_PIPE) + sh_closepipe (pgrp_pipe); +#endif + delete_all_jobs (0); + set_job_control (0); +} + +/* If this shell is interactive, terminate all stopped jobs and + restore the original terminal process group. This is done + before the `exec' builtin calls shell_execve. */ +void +end_job_control () +{ + if (interactive_shell) /* XXX - should it be interactive? */ + { + terminate_stopped_jobs (); + + if (original_pgrp >= 0) + give_terminal_to (original_pgrp, 1); + } + + if (original_pgrp >= 0) + setpgid (0, original_pgrp); +} + +/* Restart job control by closing shell tty and reinitializing. This is + called after an exec fails in an interactive shell and we do not exit. */ +void +restart_job_control () +{ + if (shell_tty != -1) + close (shell_tty); + initialize_job_control (0); +} + +/* Set the handler to run when the shell receives a SIGCHLD signal. */ +void +set_sigchld_handler () +{ + set_signal_handler (SIGCHLD, sigchld_handler); +} + +#if defined (PGRP_PIPE) +/* Read from the read end of a pipe. This is how the process group leader + blocks until all of the processes in a pipeline have been made. */ +static void +pipe_read (pp) + int *pp; +{ + char ch; + + if (pp[1] >= 0) + { + close (pp[1]); + pp[1] = -1; + } + + if (pp[0] >= 0) + { + while (read (pp[0], &ch, 1) == -1 && errno == EINTR) + ; + } +} + +/* Functional interface closes our local-to-job-control pipes. */ +void +close_pgrp_pipe () +{ + sh_closepipe (pgrp_pipe); +} + +void +save_pgrp_pipe (p, clear) + int *p; + int clear; +{ + p[0] = pgrp_pipe[0]; + p[1] = pgrp_pipe[1]; + if (clear) + pgrp_pipe[0] = pgrp_pipe[1] = -1; +} + +void +restore_pgrp_pipe (p) + int *p; +{ + pgrp_pipe[0] = p[0]; + pgrp_pipe[1] = p[1]; +} + +#endif /* PGRP_PIPE */ diff --git a/lib/sh/oslib.c b/lib/sh/oslib.c index d47f9dc80..d8eba6e6f 100644 --- a/lib/sh/oslib.c +++ b/lib/sh/oslib.c @@ -37,6 +37,10 @@ #include #include +#if !defined (HAVE_KILLPG) +# include +#endif + #include #include #include @@ -209,7 +213,8 @@ gethostname (name, namelen) # else /* !HAVE_UNAME */ int gethostname (name, namelen) - int name, namelen; + char *name; + int namelen; { strncpy (name, "unknown", namelen); name[namelen] = '\0'; diff --git a/lib/sh/oslib.c~ b/lib/sh/oslib.c~ new file mode 100644 index 000000000..e554c5cb0 --- /dev/null +++ b/lib/sh/oslib.c~ @@ -0,0 +1,300 @@ +/* oslib.c - functions present only in some unix versions. */ + +/* Copyright (C) 1995 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 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 +#ifndef _MINIX +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#if defined (HAVE_LIMITS_H) +# include +#endif + +#include +#include +#include + +#if !defined (HAVE_KILLPG) +# include +#endif + +#include +#include +#include + +#include + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +/* Make the functions strchr and strrchr if they do not exist. */ +#if !defined (HAVE_STRCHR) +char * +strchr (string, c) + char *string; + int c; +{ + register char *s; + + for (s = string; s && *s; s++) + if (*s == c) + return (s); + + return ((char *) NULL); +} + +char * +strrchr (string, c) + char *string; + int c; +{ + register char *s, *t; + + for (s = string, t = (char *)NULL; s && *s; s++) + if (*s == c) + t = s; + return (t); +} +#endif /* !HAVE_STRCHR */ + +#if !defined (HAVE_DUP2) || defined (DUP2_BROKEN) +/* Replacement for dup2 (), for those systems which either don't have it, + or supply one with broken behaviour. */ +int +dup2 (fd1, fd2) + int fd1, fd2; +{ + int saved_errno, r; + + /* If FD1 is not a valid file descriptor, then return immediately with + an error. */ + if (fcntl (fd1, F_GETFL, 0) == -1) + return (-1); + + if (fd2 < 0 || fd2 >= getdtablesize ()) + { + errno = EBADF; + return (-1); + } + + if (fd1 == fd2) + return (0); + + saved_errno = errno; + + (void) close (fd2); + r = fcntl (fd1, F_DUPFD, fd2); + + if (r >= 0) + errno = saved_errno; + else + if (errno == EINVAL) + errno = EBADF; + + /* Force the new file descriptor to remain open across exec () calls. */ + SET_OPEN_ON_EXEC (fd2); + return (r); +} +#endif /* !HAVE_DUP2 */ + +/* + * Return the total number of available file descriptors. + * + * On some systems, like 4.2BSD and its descendents, there is a system call + * that returns the size of the descriptor table: getdtablesize(). There are + * lots of ways to emulate this on non-BSD systems. + * + * On System V.3, this can be obtained via a call to ulimit: + * return (ulimit(4, 0L)); + * + * On other System V systems, NOFILE is defined in /usr/include/sys/param.h + * (this is what we assume below), so we can simply use it: + * return (NOFILE); + * + * On POSIX systems, there are specific functions for retrieving various + * configuration parameters: + * return (sysconf(_SC_OPEN_MAX)); + * + */ + +#if !defined (HAVE_GETDTABLESIZE) +int +getdtablesize () +{ +# if defined (_POSIX_VERSION) && defined (HAVE_SYSCONF) && defined (_SC_OPEN_MAX) + return (sysconf(_SC_OPEN_MAX)); /* Posix systems use sysconf */ +# else /* ! (_POSIX_VERSION && HAVE_SYSCONF && _SC_OPEN_MAX) */ +# if defined (ULIMIT_MAXFDS) + return (ulimit (4, 0L)); /* System V.3 systems use ulimit(4, 0L) */ +# else /* !ULIMIT_MAXFDS */ +# if defined (NOFILE) /* Other systems use NOFILE */ + return (NOFILE); +# else /* !NOFILE */ + return (20); /* XXX - traditional value is 20 */ +# endif /* !NOFILE */ +# endif /* !ULIMIT_MAXFDS */ +# endif /* ! (_POSIX_VERSION && _SC_OPEN_MAX) */ +} +#endif /* !HAVE_GETDTABLESIZE */ + +#if !defined (HAVE_BCOPY) +# if defined (bcopy) +# undef bcopy +# endif +void +bcopy (s,d,n) + char *d, *s; + int n; +{ + FASTCOPY (s, d, n); +} +#endif /* !HAVE_BCOPY */ + +#if !defined (HAVE_BZERO) +# if defined (bzero) +# undef bzero +# endif +void +bzero (s, n) + char *s; + int n; +{ + register int i; + register char *r; + + for (i = 0, r = s; i < n; i++) + *r++ = '\0'; +} +#endif + +#if !defined (HAVE_GETHOSTNAME) +# if defined (HAVE_UNAME) +# include +int +gethostname (name, namelen) + char *name; + int namelen; +{ + int i; + struct utsname ut; + + --namelen; + + uname (&ut); + i = strlen (ut.nodename) + 1; + strncpy (name, ut.nodename, i < namelen ? i : namelen); + name[namelen] = '\0'; + return (0); +} +# else /* !HAVE_UNAME */ +int +gethostname (name, namelen) + int name, namelen; +{ + strncpy (name, "unknown", namelen); + name[namelen] = '\0'; + return 0; +} +# endif /* !HAVE_UNAME */ +#endif /* !HAVE_GETHOSTNAME */ + +#if !defined (HAVE_KILLPG) +int +killpg (pgrp, sig) + pid_t pgrp; + int sig; +{ + return (kill (-pgrp, sig)); +} +#endif /* !HAVE_KILLPG */ + +#if !defined (HAVE_MKFIFO) && defined (PROCESS_SUBSTITUTION) +int +mkfifo (path, mode) + char *path; + int mode; +{ +#if defined (S_IFIFO) + return (mknod (path, (mode | S_IFIFO), 0)); +#else /* !S_IFIFO */ + return (-1); +#endif /* !S_IFIFO */ +} +#endif /* !HAVE_MKFIFO && PROCESS_SUBSTITUTION */ + +#define DEFAULT_MAXGROUPS 64 + +int +getmaxgroups () +{ + static int maxgroups = -1; + + if (maxgroups > 0) + return maxgroups; + +#if defined (HAVE_SYSCONF) && defined (_SC_NGROUPS_MAX) + maxgroups = sysconf (_SC_NGROUPS_MAX); +#else +# if defined (NGROUPS_MAX) + maxgroups = NGROUPS_MAX; +# else /* !NGROUPS_MAX */ +# if defined (NGROUPS) + maxgroups = NGROUPS; +# else /* !NGROUPS */ + maxgroups = DEFAULT_MAXGROUPS; +# endif /* !NGROUPS */ +# endif /* !NGROUPS_MAX */ +#endif /* !HAVE_SYSCONF || !SC_NGROUPS_MAX */ + + if (maxgroups <= 0) + maxgroups = DEFAULT_MAXGROUPS; + + return maxgroups; +} + +long +getmaxchild () +{ + static long maxchild = -1L; + + if (maxchild > 0) + return maxchild; + +#if defined (HAVE_SYSCONF) && defined (_SC_CHILD_MAX) + maxchild = sysconf (_SC_CHILD_MAX); +#else +# if defined (CHILD_MAX) + maxchild = CHILD_MAX; +# else +# if defined (MAXUPRC) + maxchild = MAXUPRC; +# endif /* MAXUPRC */ +# endif /* CHILD_MAX */ +#endif /* !HAVE_SYSCONF || !_SC_CHILD_MAX */ + + return (maxchild); +} diff --git a/print_cmd.c b/print_cmd.c index 9a41dcbd4..bf405c68f 100644 --- a/print_cmd.c +++ b/print_cmd.c @@ -1120,6 +1120,10 @@ print_redirection (redirect) cprintf ("{%s}", redir_word->word); else if (redirector != 0) cprintf ("%d", redirector); +#if 0 + /* Don't need to check whether or not to requote, since original quotes + are still intact. The only thing that has happened is that $'...' + has been replaced with 'expanded ...'. */ if (ansic_shouldquote (redirect->redirectee.filename->word)) { char *x; @@ -1128,6 +1132,7 @@ print_redirection (redirect) free (x); } else +#endif cprintf ("<<< %s", redirect->redirectee.filename->word); break; diff --git a/print_cmd.c~ b/print_cmd.c~ new file mode 100644 index 000000000..841739b65 --- /dev/null +++ b/print_cmd.c~ @@ -0,0 +1,1534 @@ +/* print_command -- A way to make readable commands from a command tree. */ + +/* Copyright (C) 1989-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 + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#if defined (PREFER_STDARG) +# include +#else +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "flags.h" +#include /* use <...> so we pick it up from the build directory */ + +#include "shmbutil.h" + +#include "builtins/common.h" + +#if !HAVE_DECL_PRINTF +extern int printf __P((const char *, ...)); /* Yuck. Double yuck. */ +#endif + +extern int indirection_level; + +static int indentation; +static int indentation_amount = 4; + +#if defined (PREFER_STDARG) +typedef void PFUNC __P((const char *, ...)); + +static void cprintf __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); +static void xprintf __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); +#else +#define PFUNC VFunction +static void cprintf (); +static void xprintf (); +#endif + +static void reset_locals __P((void)); +static void newline __P((char *)); +static void indent __P((int)); +static void semicolon __P((void)); +static void the_printed_command_resize __P((int)); + +static void make_command_string_internal __P((COMMAND *)); +static void _print_word_list __P((WORD_LIST *, char *, PFUNC *)); +static void command_print_word_list __P((WORD_LIST *, char *)); +static void print_case_clauses __P((PATTERN_LIST *)); +static void print_redirection_list __P((REDIRECT *)); +static void print_redirection __P((REDIRECT *)); +static void print_heredoc_header __P((REDIRECT *)); +static void print_heredoc_body __P((REDIRECT *)); +static void print_heredocs __P((REDIRECT *)); +static void print_deferred_heredocs __P((const char *)); + +static void print_for_command __P((FOR_COM *)); +#if defined (ARITH_FOR_COMMAND) +static void print_arith_for_command __P((ARITH_FOR_COM *)); +#endif +#if defined (SELECT_COMMAND) +static void print_select_command __P((SELECT_COM *)); +#endif +static void print_group_command __P((GROUP_COM *)); +static void print_case_command __P((CASE_COM *)); +static void print_while_command __P((WHILE_COM *)); +static void print_until_command __P((WHILE_COM *)); +static void print_until_or_while __P((WHILE_COM *, char *)); +static void print_if_command __P((IF_COM *)); +#if defined (COND_COMMAND) +static void print_cond_node __P((COND_COM *)); +#endif +static void print_function_def __P((FUNCTION_DEF *)); + +#define PRINTED_COMMAND_INITIAL_SIZE 64 +#define PRINTED_COMMAND_GROW_SIZE 128 + +char *the_printed_command = (char *)NULL; +int the_printed_command_size = 0; +int command_string_index = 0; + +int xtrace_fd = -1; +FILE *xtrace_fp = 0; + +#define CHECK_XTRACE_FP xtrace_fp = (xtrace_fp ? xtrace_fp : stderr) + +/* Non-zero means the stuff being printed is inside of a function def. */ +static int inside_function_def; +static int skip_this_indent; +static int was_heredoc; +static int printing_connection; +static REDIRECT *deferred_heredocs; + +/* The depth of the group commands that we are currently printing. This + includes the group command that is a function body. */ +static int group_command_nesting; + +/* A buffer to indicate the indirection level (PS4) when set -x is enabled. */ +static char indirection_string[100]; + +/* Print COMMAND (a command tree) on standard output. */ +void +print_command (command) + COMMAND *command; +{ + command_string_index = 0; + printf ("%s", make_command_string (command)); +} + +/* Make a string which is the printed representation of the command + tree in COMMAND. We return this string. However, the string is + not consed, so you have to do that yourself if you want it to + remain around. */ +char * +make_command_string (command) + COMMAND *command; +{ + command_string_index = was_heredoc = 0; + deferred_heredocs = 0; + make_command_string_internal (command); + return (the_printed_command); +} + +/* The internal function. This is the real workhorse. */ +static void +make_command_string_internal (command) + COMMAND *command; +{ + char s[3], *op; + + if (command == 0) + cprintf (""); + else + { + if (skip_this_indent) + skip_this_indent--; + else + indent (indentation); + + if (command->flags & CMD_TIME_PIPELINE) + { + cprintf ("time "); + if (command->flags & CMD_TIME_POSIX) + cprintf ("-p "); + } + + if (command->flags & CMD_INVERT_RETURN) + cprintf ("! "); + + switch (command->type) + { + case cm_for: + print_for_command (command->value.For); + break; + +#if defined (ARITH_FOR_COMMAND) + case cm_arith_for: + print_arith_for_command (command->value.ArithFor); + break; +#endif + +#if defined (SELECT_COMMAND) + case cm_select: + print_select_command (command->value.Select); + break; +#endif + + case cm_case: + print_case_command (command->value.Case); + break; + + case cm_while: + print_while_command (command->value.While); + break; + + case cm_until: + print_until_command (command->value.While); + break; + + case cm_if: + print_if_command (command->value.If); + break; + +#if defined (DPAREN_ARITHMETIC) + case cm_arith: + print_arith_command (command->value.Arith->exp); + break; +#endif + +#if defined (COND_COMMAND) + case cm_cond: + print_cond_command (command->value.Cond); + break; +#endif + + case cm_simple: + print_simple_command (command->value.Simple); + break; + + case cm_connection: + + skip_this_indent++; + printing_connection++; + make_command_string_internal (command->value.Connection->first); + + switch (command->value.Connection->connector) + { + case '&': + case '|': + { + char c = command->value.Connection->connector; + + s[0] = ' '; + s[1] = c; + s[2] = '\0'; + + print_deferred_heredocs (s); + + if (c != '&' || command->value.Connection->second) + { + cprintf (" "); + skip_this_indent++; + } + } + break; + + case AND_AND: + print_deferred_heredocs (" && "); + if (command->value.Connection->second) + skip_this_indent++; + break; + + case OR_OR: + print_deferred_heredocs (" || "); + if (command->value.Connection->second) + skip_this_indent++; + break; + + case ';': + if (deferred_heredocs == 0) + { + if (was_heredoc == 0) + cprintf (";"); + else + was_heredoc = 0; + } + else + print_deferred_heredocs (inside_function_def ? "" : ";"); + + if (inside_function_def) + cprintf ("\n"); + else + { + cprintf (" "); + if (command->value.Connection->second) + skip_this_indent++; + } + break; + + default: + cprintf (_("print_command: bad connector `%d'"), + command->value.Connection->connector); + break; + } + + make_command_string_internal (command->value.Connection->second); + if (deferred_heredocs) + print_deferred_heredocs (""); + printing_connection--; + break; + + case cm_function_def: + print_function_def (command->value.Function_def); + break; + + case cm_group: + print_group_command (command->value.Group); + break; + + case cm_subshell: + cprintf ("( "); + skip_this_indent++; + make_command_string_internal (command->value.Subshell->command); + cprintf (" )"); + break; + + case cm_coproc: + cprintf ("coproc %s ", command->value.Coproc->name); + skip_this_indent++; + make_command_string_internal (command->value.Coproc->command); + break; + + default: + command_error ("print_command", CMDERR_BADTYPE, command->type, 0); + break; + } + + + if (command->redirects) + { + cprintf (" "); + print_redirection_list (command->redirects); + } + } +} + +static void +_print_word_list (list, separator, pfunc) + WORD_LIST *list; + char *separator; + PFUNC *pfunc; +{ + WORD_LIST *w; + + for (w = list; w; w = w->next) + (*pfunc) ("%s%s", w->word->word, w->next ? separator : ""); +} + +void +print_word_list (list, separator) + WORD_LIST *list; + char *separator; +{ + _print_word_list (list, separator, xprintf); +} + +void +xtrace_set (fd, fp) + int fd; + FILE *fp; +{ + if (fd >= 0 && sh_validfd (fd) == 0) + { + internal_error (_("xtrace_set: %d: invalid file descriptor"), fd); + return; + } + if (fp == 0) + { + internal_error (_("xtrace_set: NULL file pointer")); + return; + } + if (fd >= 0 && fileno (fp) != fd) + internal_warning (_("xtrace fd (%d) != fileno xtrace fp (%d)"), fd, fileno (fp)); + + xtrace_fd = fd; + xtrace_fp = fp; +} + +void +xtrace_init () +{ + xtrace_set (-1, stderr); +} + +void +xtrace_reset () +{ + if (xtrace_fd >= 0 && xtrace_fp) + { + fflush (xtrace_fp); + fclose (xtrace_fp); + } + else if (xtrace_fd >= 0) + close (xtrace_fd); + + xtrace_fd = -1; + xtrace_fp = stderr; +} + +void +xtrace_fdchk (fd) + int fd; +{ + if (fd == xtrace_fd) + xtrace_reset (); +} + +/* Return a string denoting what our indirection level is. */ + +char * +indirection_level_string () +{ + register int i, j; + char *ps4; + char ps4_firstc[MB_LEN_MAX+1]; + int ps4_firstc_len, ps4_len; + + indirection_string[0] = '\0'; + ps4 = get_string_value ("PS4"); + + if (ps4 == 0 || *ps4 == '\0') + return (indirection_string); + + change_flag ('x', FLAG_OFF); + ps4 = decode_prompt_string (ps4); + change_flag ('x', FLAG_ON); + + if (ps4 == 0 || *ps4 == '\0') + return (indirection_string); + +#if defined (HANDLE_MULTIBYTE) + ps4_len = strnlen (ps4, MB_CUR_MAX); + ps4_firstc_len = MBLEN (ps4, ps4_len); + if (ps4_firstc_len == 1 || ps4_firstc_len == 0 || MB_INVALIDCH (ps4_firstc_len)) + { + ps4_firstc[0] = ps4[0]; + ps4_firstc[ps4_firstc_len = 1] = '\0'; + } + else + memcpy (ps4_firstc, ps4, ps4_firstc_len); +#else + ps4_firstc[0] = ps4[0]; + ps4_firstc[ps4_firstc_len = 1] = '\0'; +#endif + + for (i = j = 0; ps4_firstc[0] && j < indirection_level && i < 99; i += ps4_firstc_len, j++) + { + if (ps4_firstc_len == 1) + indirection_string[i] = ps4_firstc[0]; + else + memcpy (indirection_string+i, ps4_firstc, ps4_firstc_len); + } + + for (j = ps4_firstc_len; *ps4 && ps4[j] && i < 99; i++, j++) + indirection_string[i] = ps4[j]; + + indirection_string[i] = '\0'; + free (ps4); + return (indirection_string); +} + +void +xtrace_print_assignment (name, value, assign_list, xflags) + char *name, *value; + int assign_list, xflags; +{ + char *nval; + + CHECK_XTRACE_FP; + + if (xflags) + fprintf (xtrace_fp, "%s", indirection_level_string ()); + + /* VALUE should not be NULL when this is called. */ + if (*value == '\0' || assign_list) + nval = value; + else if (sh_contains_shell_metas (value)) + nval = sh_single_quote (value); + else if (ansic_shouldquote (value)) + nval = ansic_quote (value, 0, (int *)0); + else + nval = value; + + if (assign_list) + fprintf (xtrace_fp, "%s=(%s)\n", name, nval); + else + fprintf (xtrace_fp, "%s=%s\n", name, nval); + + if (nval != value) + FREE (nval); + + fflush (xtrace_fp); +} + +/* A function to print the words of a simple command when set -x is on. */ +void +xtrace_print_word_list (list, xtflags) + WORD_LIST *list; + int xtflags; +{ + WORD_LIST *w; + char *t, *x; + + CHECK_XTRACE_FP; + + if (xtflags) + fprintf (xtrace_fp, "%s", indirection_level_string ()); + + for (w = list; w; w = w->next) + { + t = w->word->word; + if (t == 0 || *t == '\0') + fprintf (xtrace_fp, "''%s", w->next ? " " : ""); + else if (sh_contains_shell_metas (t)) + { + x = sh_single_quote (t); + fprintf (xtrace_fp, "%s%s", x, w->next ? " " : ""); + free (x); + } + else if (ansic_shouldquote (t)) + { + x = ansic_quote (t, 0, (int *)0); + fprintf (xtrace_fp, "%s%s", x, w->next ? " " : ""); + free (x); + } + else + fprintf (xtrace_fp, "%s%s", t, w->next ? " " : ""); + } + fprintf (xtrace_fp, "\n"); + fflush (xtrace_fp); +} + +static void +command_print_word_list (list, separator) + WORD_LIST *list; + char *separator; +{ + _print_word_list (list, separator, cprintf); +} + +void +print_for_command_head (for_command) + FOR_COM *for_command; +{ + cprintf ("for %s in ", for_command->name->word); + command_print_word_list (for_command->map_list, " "); +} + +void +xtrace_print_for_command_head (for_command) + FOR_COM *for_command; +{ + CHECK_XTRACE_FP; + fprintf (xtrace_fp, "%s", indirection_level_string ()); + fprintf (xtrace_fp, "for %s in ", for_command->name->word); + xtrace_print_word_list (for_command->map_list, 0); +} + +static void +print_for_command (for_command) + FOR_COM *for_command; +{ + print_for_command_head (for_command); + + cprintf (";"); + newline ("do\n"); + indentation += indentation_amount; + make_command_string_internal (for_command->action); + semicolon (); + indentation -= indentation_amount; + newline ("done"); +} + +#if defined (ARITH_FOR_COMMAND) +static void +print_arith_for_command (arith_for_command) + ARITH_FOR_COM *arith_for_command; +{ + cprintf ("for (("); + command_print_word_list (arith_for_command->init, " "); + cprintf ("; "); + command_print_word_list (arith_for_command->test, " "); + cprintf ("; "); + command_print_word_list (arith_for_command->step, " "); + cprintf ("))"); + newline ("do\n"); + indentation += indentation_amount; + make_command_string_internal (arith_for_command->action); + semicolon (); + indentation -= indentation_amount; + newline ("done"); +} +#endif /* ARITH_FOR_COMMAND */ + +#if defined (SELECT_COMMAND) +void +print_select_command_head (select_command) + SELECT_COM *select_command; +{ + cprintf ("select %s in ", select_command->name->word); + command_print_word_list (select_command->map_list, " "); +} + +void +xtrace_print_select_command_head (select_command) + SELECT_COM *select_command; +{ + CHECK_XTRACE_FP; + fprintf (xtrace_fp, "%s", indirection_level_string ()); + fprintf (xtrace_fp, "select %s in ", select_command->name->word); + xtrace_print_word_list (select_command->map_list, 0); +} + +static void +print_select_command (select_command) + SELECT_COM *select_command; +{ + print_select_command_head (select_command); + + cprintf (";"); + newline ("do\n"); + indentation += indentation_amount; + make_command_string_internal (select_command->action); + semicolon (); + indentation -= indentation_amount; + newline ("done"); +} +#endif /* SELECT_COMMAND */ + +static void +print_group_command (group_command) + GROUP_COM *group_command; +{ + group_command_nesting++; + cprintf ("{ "); + + if (inside_function_def == 0) + skip_this_indent++; + else + { + /* This is a group command { ... } inside of a function + definition, and should be printed as a multiline group + command, using the current indentation. */ + cprintf ("\n"); + indentation += indentation_amount; + } + + make_command_string_internal (group_command->command); + + if (inside_function_def) + { + cprintf ("\n"); + indentation -= indentation_amount; + indent (indentation); + } + else + { + semicolon (); + cprintf (" "); + } + + cprintf ("}"); + + group_command_nesting--; +} + +void +print_case_command_head (case_command) + CASE_COM *case_command; +{ + cprintf ("case %s in ", case_command->word->word); +} + +void +xtrace_print_case_command_head (case_command) + CASE_COM *case_command; +{ + CHECK_XTRACE_FP; + fprintf (xtrace_fp, "%s", indirection_level_string ()); + fprintf (xtrace_fp, "case %s in\n", case_command->word->word); +} + +static void +print_case_command (case_command) + CASE_COM *case_command; +{ + print_case_command_head (case_command); + + if (case_command->clauses) + print_case_clauses (case_command->clauses); + newline ("esac"); +} + +static void +print_case_clauses (clauses) + PATTERN_LIST *clauses; +{ + indentation += indentation_amount; + while (clauses) + { + newline (""); + command_print_word_list (clauses->patterns, " | "); + cprintf (")\n"); + indentation += indentation_amount; + make_command_string_internal (clauses->action); + indentation -= indentation_amount; + if (clauses->flags & CASEPAT_FALLTHROUGH) + newline (";&"); + else if (clauses->flags & CASEPAT_TESTNEXT) + newline (";;&"); + else + newline (";;"); + clauses = clauses->next; + } + indentation -= indentation_amount; +} + +static void +print_while_command (while_command) + WHILE_COM *while_command; +{ + print_until_or_while (while_command, "while"); +} + +static void +print_until_command (while_command) + WHILE_COM *while_command; +{ + print_until_or_while (while_command, "until"); +} + +static void +print_until_or_while (while_command, which) + WHILE_COM *while_command; + char *which; +{ + cprintf ("%s ", which); + skip_this_indent++; + make_command_string_internal (while_command->test); + semicolon (); + cprintf (" do\n"); /* was newline ("do\n"); */ + indentation += indentation_amount; + make_command_string_internal (while_command->action); + indentation -= indentation_amount; + semicolon (); + newline ("done"); +} + +static void +print_if_command (if_command) + IF_COM *if_command; +{ + cprintf ("if "); + skip_this_indent++; + make_command_string_internal (if_command->test); + semicolon (); + cprintf (" then\n"); + indentation += indentation_amount; + make_command_string_internal (if_command->true_case); + indentation -= indentation_amount; + + if (if_command->false_case) + { + semicolon (); + newline ("else\n"); + indentation += indentation_amount; + make_command_string_internal (if_command->false_case); + indentation -= indentation_amount; + } + semicolon (); + newline ("fi"); +} + +#if defined (DPAREN_ARITHMETIC) +void +print_arith_command (arith_cmd_list) + WORD_LIST *arith_cmd_list; +{ + cprintf ("(("); + command_print_word_list (arith_cmd_list, " "); + cprintf ("))"); +} +#endif + +#if defined (COND_COMMAND) +static void +print_cond_node (cond) + COND_COM *cond; +{ + if (cond->flags & CMD_INVERT_RETURN) + cprintf ("! "); + + if (cond->type == COND_EXPR) + { + cprintf ("( "); + print_cond_node (cond->left); + cprintf (" )"); + } + else if (cond->type == COND_AND) + { + print_cond_node (cond->left); + cprintf (" && "); + print_cond_node (cond->right); + } + else if (cond->type == COND_OR) + { + print_cond_node (cond->left); + cprintf (" || "); + print_cond_node (cond->right); + } + else if (cond->type == COND_UNARY) + { + cprintf ("%s", cond->op->word); + cprintf (" "); + print_cond_node (cond->left); + } + else if (cond->type == COND_BINARY) + { + print_cond_node (cond->left); + cprintf (" "); + cprintf ("%s", cond->op->word); + cprintf (" "); + print_cond_node (cond->right); + } + else if (cond->type == COND_TERM) + { + cprintf ("%s", cond->op->word); /* need to add quoting here */ + } +} + +void +print_cond_command (cond) + COND_COM *cond; +{ + cprintf ("[[ "); + print_cond_node (cond); + cprintf (" ]]"); +} + +#ifdef DEBUG +void +debug_print_cond_command (cond) + COND_COM *cond; +{ + fprintf (stderr, "DEBUG: "); + command_string_index = 0; + print_cond_command (cond); + fprintf (stderr, "%s\n", the_printed_command); +} +#endif + +void +xtrace_print_cond_term (type, invert, op, arg1, arg2) + int type, invert; + WORD_DESC *op; + char *arg1, *arg2; +{ + CHECK_XTRACE_FP; + command_string_index = 0; + fprintf (xtrace_fp, "%s", indirection_level_string ()); + fprintf (xtrace_fp, "[[ "); + if (invert) + fprintf (xtrace_fp, "! "); + + if (type == COND_UNARY) + { + fprintf (xtrace_fp, "%s ", op->word); + fprintf (xtrace_fp, "%s", (arg1 && *arg1) ? arg1 : "''"); + } + else if (type == COND_BINARY) + { + fprintf (xtrace_fp, "%s", (arg1 && *arg1) ? arg1 : "''"); + fprintf (xtrace_fp, " %s ", op->word); + fprintf (xtrace_fp, "%s", (arg2 && *arg2) ? arg2 : "''"); + } + + fprintf (xtrace_fp, " ]]\n"); + + fflush (xtrace_fp); +} +#endif /* COND_COMMAND */ + +#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND) +/* A function to print the words of an arithmetic command when set -x is on. */ +void +xtrace_print_arith_cmd (list) + WORD_LIST *list; +{ + WORD_LIST *w; + + CHECK_XTRACE_FP; + fprintf (xtrace_fp, "%s", indirection_level_string ()); + fprintf (xtrace_fp, "(( "); + for (w = list; w; w = w->next) + fprintf (xtrace_fp, "%s%s", w->word->word, w->next ? " " : ""); + fprintf (xtrace_fp, " ))\n"); + + fflush (xtrace_fp); +} +#endif + +void +print_simple_command (simple_command) + SIMPLE_COM *simple_command; +{ + command_print_word_list (simple_command->words, " "); + + if (simple_command->redirects) + { + cprintf (" "); + print_redirection_list (simple_command->redirects); + } +} + +static void +print_heredocs (heredocs) + REDIRECT *heredocs; +{ + REDIRECT *hdtail; + + cprintf (" "); + for (hdtail = heredocs; hdtail; hdtail = hdtail->next) + { + print_redirection (hdtail); + cprintf ("\n"); + } + was_heredoc = 1; +} + +/* Print heredocs that are attached to the command before the connector + represented by CSTRING. The parsing semantics require us to print the + here-doc delimiters, then the connector (CSTRING), then the here-doc + bodies. We don't print the connector if it's a `;', but we use it to + note not to print an extra space after the last heredoc body and + newline. */ +static void +print_deferred_heredocs (cstring) + const char *cstring; +{ + REDIRECT *hdtail; + + for (hdtail = deferred_heredocs; hdtail; hdtail = hdtail->next) + { + cprintf (" "); + print_heredoc_header (hdtail); + } + if (cstring[0] != ';' || cstring[1]) + cprintf ("%s", cstring); + if (deferred_heredocs) + cprintf ("\n"); + for (hdtail = deferred_heredocs; hdtail; hdtail = hdtail->next) + { + print_heredoc_body (hdtail); + cprintf ("\n"); + } + if (deferred_heredocs) + { + if (cstring && cstring[0] && (cstring[0] != ';' || cstring[1])) + cprintf (" "); /* make sure there's at least one space */ + dispose_redirects (deferred_heredocs); + was_heredoc = 1; + } + deferred_heredocs = (REDIRECT *)NULL; +} + +static void +print_redirection_list (redirects) + REDIRECT *redirects; +{ + REDIRECT *heredocs, *hdtail, *newredir; + + heredocs = (REDIRECT *)NULL; + hdtail = heredocs; + + was_heredoc = 0; + while (redirects) + { + /* Defer printing the here documents until we've printed the + rest of the redirections. */ + if (redirects->instruction == r_reading_until || redirects->instruction == r_deblank_reading_until) + { + newredir = copy_redirect (redirects); + newredir->next = (REDIRECT *)NULL; + if (heredocs) + { + hdtail->next = newredir; + hdtail = newredir; + } + else + hdtail = heredocs = newredir; + } + else if (redirects->instruction == r_duplicating_output_word && redirects->redirector.dest == 1) + { + /* Temporarily translate it as the execution code does. */ + redirects->instruction = r_err_and_out; + print_redirection (redirects); + redirects->instruction = r_duplicating_output_word; + } + else + print_redirection (redirects); + + redirects = redirects->next; + if (redirects) + cprintf (" "); + } + + /* Now that we've printed all the other redirections (on one line), + print the here documents. */ + if (heredocs && printing_connection) + deferred_heredocs = heredocs; + else if (heredocs) + { + print_heredocs (heredocs); + dispose_redirects (heredocs); + } +} + +static void +print_heredoc_header (redirect) + REDIRECT *redirect; +{ + int kill_leading; + char *x; + + kill_leading = redirect->instruction == r_deblank_reading_until; + + /* Here doc header */ + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}", redirect->redirector.filename->word); + else if (redirect->redirector.dest != 0) + cprintf ("%d", redirect->redirector.dest); + + /* If the here document delimiter is quoted, single-quote it. */ + if (redirect->redirectee.filename->flags & W_QUOTED) + { + x = sh_single_quote (redirect->here_doc_eof); + cprintf ("<<%s%s", kill_leading ? "-" : "", x); + free (x); + } + else + cprintf ("<<%s%s", kill_leading ? "-" : "", redirect->here_doc_eof); +} + +static void +print_heredoc_body (redirect) + REDIRECT *redirect; +{ + /* Here doc body */ + cprintf ("%s%s", redirect->redirectee.filename->word, redirect->here_doc_eof); +} + +static void +print_redirection (redirect) + REDIRECT *redirect; +{ + int kill_leading, redirector, redir_fd; + WORD_DESC *redirectee, *redir_word; + + kill_leading = 0; + redirectee = redirect->redirectee.filename; + redir_fd = redirect->redirectee.dest; + + redir_word = redirect->redirector.filename; + redirector = redirect->redirector.dest; + + switch (redirect->instruction) + { + case r_input_direction: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}", redir_word->word); + else if (redirector != 0) + cprintf ("%d", redirector); + cprintf ("< %s", redirectee->word); + break; + + case r_output_direction: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}", redir_word->word); + else if (redirector != 1) + cprintf ("%d", redirector); + cprintf ("> %s", redirectee->word); + break; + + case r_inputa_direction: /* Redirection created by the shell. */ + cprintf ("&"); + break; + + case r_output_force: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}", redir_word->word); + else if (redirector != 1) + cprintf ("%d", redirector); + cprintf (">|%s", redirectee->word); + break; + + case r_appending_to: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}", redir_word->word); + else if (redirector != 1) + cprintf ("%d", redirector); + cprintf (">> %s", redirectee->word); + break; + + case r_input_output: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}", redir_word->word); + else if (redirector != 1) + cprintf ("%d", redirector); + cprintf ("<> %s", redirectee->word); + break; + + case r_deblank_reading_until: + case r_reading_until: + print_heredoc_header (redirect); + cprintf ("\n"); + print_heredoc_body (redirect); + break; + + case r_reading_string: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}", redir_word->word); + else if (redirector != 0) + cprintf ("%d", redirector); +#if 0 + if (ansic_shouldquote (redirect->redirectee.filename->word)) + { + char *x; + x = ansic_quote (redirect->redirectee.filename->word, 0, (int *)0); + cprintf ("<<< %s", x); + free (x); + } + else +#endif + cprintf ("<<< %s", redirect->redirectee.filename->word); + break; + + case r_duplicating_input: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}<&%d", redir_word->word, redir_fd); + else + cprintf ("%d<&%d", redirector, redir_fd); + break; + + case r_duplicating_output: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}>&%d", redir_word->word, redir_fd); + else + cprintf ("%d>&%d", redirector, redir_fd); + break; + + case r_duplicating_input_word: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}<&%s", redir_word->word, redirectee->word); + else + cprintf ("%d<&%s", redirector, redirectee->word); + break; + + case r_duplicating_output_word: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}>&%s", redir_word->word, redirectee->word); + else + cprintf ("%d>&%s", redirector, redirectee->word); + break; + + case r_move_input: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}<&%d-", redir_word->word, redir_fd); + else + cprintf ("%d<&%d-", redirector, redir_fd); + break; + + case r_move_output: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}>&%d-", redir_word->word, redir_fd); + else + cprintf ("%d>&%d-", redirector, redir_fd); + break; + + case r_move_input_word: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}<&%s-", redir_word->word, redirectee->word); + else + cprintf ("%d<&%s-", redirector, redirectee->word); + break; + + case r_move_output_word: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}>&%s-", redir_word->word, redirectee->word); + else + cprintf ("%d>&%s-", redirector, redirectee->word); + break; + + case r_close_this: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}>&-", redir_word->word); + else + cprintf ("%d>&-", redirector); + break; + + case r_err_and_out: + cprintf ("&>%s", redirectee->word); + break; + + case r_append_err_and_out: + cprintf ("&>>%s", redirectee->word); + break; + } +} + +static void +reset_locals () +{ + inside_function_def = 0; + indentation = 0; + printing_connection = 0; + deferred_heredocs = 0; +} + +static void +print_function_def (func) + FUNCTION_DEF *func; +{ + COMMAND *cmdcopy; + REDIRECT *func_redirects; + + func_redirects = NULL; + cprintf ("function %s () \n", func->name->word); + add_unwind_protect (reset_locals, 0); + + indent (indentation); + cprintf ("{ \n"); + + inside_function_def++; + indentation += indentation_amount; + + cmdcopy = copy_command (func->command); + if (cmdcopy->type == cm_group) + { + func_redirects = cmdcopy->redirects; + cmdcopy->redirects = (REDIRECT *)NULL; + } + make_command_string_internal (cmdcopy->type == cm_group + ? cmdcopy->value.Group->command + : cmdcopy); + + remove_unwind_protect (); + indentation -= indentation_amount; + inside_function_def--; + + if (func_redirects) + { /* { */ + newline ("} "); + print_redirection_list (func_redirects); + cmdcopy->redirects = func_redirects; + } + else + newline ("}"); + + dispose_command (cmdcopy); +} + +/* Return the string representation of the named function. + NAME is the name of the function. + COMMAND is the function body. It should be a GROUP_COM. + flags&FUNC_MULTILINE is non-zero to pretty-print, or zero for all on one line. + flags&FUNC_EXTERNAL means convert from internal to external form + */ +char * +named_function_string (name, command, flags) + char *name; + COMMAND *command; + int flags; +{ + char *result; + int old_indent, old_amount; + COMMAND *cmdcopy; + REDIRECT *func_redirects; + + old_indent = indentation; + old_amount = indentation_amount; + command_string_index = was_heredoc = 0; + deferred_heredocs = 0; + + if (name && *name) + cprintf ("%s ", name); + + cprintf ("() "); + + if ((flags & FUNC_MULTILINE) == 0) + { + indentation = 1; + indentation_amount = 0; + } + else + { + cprintf ("\n"); + indentation += indentation_amount; + } + + inside_function_def++; + + cprintf ((flags & FUNC_MULTILINE) ? "{ \n" : "{ "); + + cmdcopy = copy_command (command); + /* Take any redirections specified in the function definition (which should + apply to the function as a whole) and save them for printing later. */ + func_redirects = (REDIRECT *)NULL; + if (cmdcopy->type == cm_group) + { + func_redirects = cmdcopy->redirects; + cmdcopy->redirects = (REDIRECT *)NULL; + } + make_command_string_internal (cmdcopy->type == cm_group + ? cmdcopy->value.Group->command + : cmdcopy); + + indentation = old_indent; + indentation_amount = old_amount; + inside_function_def--; + + if (func_redirects) + { /* { */ + newline ("} "); + print_redirection_list (func_redirects); + cmdcopy->redirects = func_redirects; + } + else + newline ("}"); + + result = the_printed_command; + + if ((flags & FUNC_MULTILINE) == 0) + { +#if 0 + register int i; + for (i = 0; result[i]; i++) + if (result[i] == '\n') + { + strcpy (result + i, result + i + 1); + --i; + } +#else + if (result[2] == '\n') /* XXX -- experimental */ + strcpy (result + 2, result + 3); +#endif + } + + dispose_command (cmdcopy); + + if (flags & FUNC_EXTERNAL) + result = remove_quoted_escapes (result); + + return (result); +} + +static void +newline (string) + char *string; +{ + cprintf ("\n"); + indent (indentation); + if (string && *string) + cprintf ("%s", string); +} + +static char *indentation_string; +static int indentation_size; + +static void +indent (amount) + int amount; +{ + register int i; + + RESIZE_MALLOCED_BUFFER (indentation_string, 0, amount, indentation_size, 16); + + for (i = 0; amount > 0; amount--) + indentation_string[i++] = ' '; + indentation_string[i] = '\0'; + cprintf (indentation_string); +} + +static void +semicolon () +{ + if (command_string_index > 0 && + (the_printed_command[command_string_index - 1] == '&' || + the_printed_command[command_string_index - 1] == '\n')) + return; + cprintf (";"); +} + +/* How to make the string. */ +static void +#if defined (PREFER_STDARG) +cprintf (const char *control, ...) +#else +cprintf (control, va_alist) + const char *control; + va_dcl +#endif +{ + register const char *s; + char char_arg[2], *argp, intbuf[INT_STRLEN_BOUND (int) + 1]; + int digit_arg, arg_len, c; + va_list args; + + SH_VA_START (args, control); + + arg_len = strlen (control); + the_printed_command_resize (arg_len + 1); + + char_arg[1] = '\0'; + s = control; + while (s && *s) + { + c = *s++; + argp = (char *)NULL; + if (c != '%' || !*s) + { + char_arg[0] = c; + argp = char_arg; + arg_len = 1; + } + else + { + c = *s++; + switch (c) + { + case '%': + char_arg[0] = c; + argp = char_arg; + arg_len = 1; + break; + + case 's': + argp = va_arg (args, char *); + arg_len = strlen (argp); + break; + + case 'd': + /* Represent an out-of-range file descriptor with an out-of-range + integer value. We can do this because the only use of `%d' in + the calls to cprintf is to output a file descriptor number for + a redirection. */ + digit_arg = va_arg (args, int); + if (digit_arg < 0) + { + sprintf (intbuf, "%u", (unsigned)-1); + argp = intbuf; + } + else + argp = inttostr (digit_arg, intbuf, sizeof (intbuf)); + arg_len = strlen (argp); + break; + + case 'c': + char_arg[0] = va_arg (args, int); + argp = char_arg; + arg_len = 1; + break; + + default: + programming_error (_("cprintf: `%c': invalid format character"), c); + /*NOTREACHED*/ + } + } + + if (argp && arg_len) + { + the_printed_command_resize (arg_len + 1); + FASTCOPY (argp, the_printed_command + command_string_index, arg_len); + command_string_index += arg_len; + } + } + + the_printed_command[command_string_index] = '\0'; +} + +/* Ensure that there is enough space to stuff LENGTH characters into + THE_PRINTED_COMMAND. */ +static void +the_printed_command_resize (length) + int length; +{ + if (the_printed_command == 0) + { + the_printed_command_size = (length + PRINTED_COMMAND_INITIAL_SIZE - 1) & ~(PRINTED_COMMAND_INITIAL_SIZE - 1); + the_printed_command = (char *)xmalloc (the_printed_command_size); + command_string_index = 0; + } + else if ((command_string_index + length) >= the_printed_command_size) + { + int new; + new = command_string_index + length + 1; + + /* Round up to the next multiple of PRINTED_COMMAND_GROW_SIZE. */ + new = (new + PRINTED_COMMAND_GROW_SIZE - 1) & ~(PRINTED_COMMAND_GROW_SIZE - 1); + the_printed_command_size = new; + + the_printed_command = (char *)xrealloc (the_printed_command, the_printed_command_size); + } +} + +#if defined (HAVE_VPRINTF) +/* ``If vprintf is available, you may assume that vfprintf and vsprintf are + also available.'' */ + +static void +#if defined (PREFER_STDARG) +xprintf (const char *format, ...) +#else +xprintf (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + + SH_VA_START (args, format); + + vfprintf (stdout, format, args); + va_end (args); +} + +#else + +static void +xprintf (format, arg1, arg2, arg3, arg4, arg5) + const char *format; +{ + printf (format, arg1, arg2, arg3, arg4, arg5); +} + +#endif /* !HAVE_VPRINTF */ diff --git a/sig.c b/sig.c index a217b8947..dd9bd3ddd 100644 --- a/sig.c +++ b/sig.c @@ -60,6 +60,8 @@ extern int executing_list; extern int comsub_ignore_return; extern int parse_and_execute_level, shell_initialized; +extern void intialize_siglist (); + /* Non-zero after SIGINT. */ volatile int interrupt_state = 0; diff --git a/sig.c~ b/sig.c~ new file mode 100644 index 000000000..a217b8947 --- /dev/null +++ b/sig.c~ @@ -0,0 +1,664 @@ +/* sig.c - interface for shell signal handlers and signal initialization. */ + +/* Copyright (C) 1994-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) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include + +#include "bashintl.h" + +#include "shell.h" +#if defined (JOB_CONTROL) +#include "jobs.h" +#endif /* JOB_CONTROL */ +#include "siglist.h" +#include "sig.h" +#include "trap.h" + +#include "builtins/common.h" + +#if defined (READLINE) +# include "bashline.h" +#endif + +#if defined (HISTORY) +# include "bashhist.h" +#endif + +extern int last_command_exit_value; +extern int last_command_exit_signal; +extern int return_catch_flag; +extern int loop_level, continuing, breaking; +extern int executing_list; +extern int comsub_ignore_return; +extern int parse_and_execute_level, shell_initialized; + +/* Non-zero after SIGINT. */ +volatile int interrupt_state = 0; + +/* Non-zero after SIGWINCH */ +volatile int sigwinch_received = 0; + +/* Set to the value of any terminating signal received. */ +volatile int terminating_signal = 0; + +/* The environment at the top-level R-E loop. We use this in + the case of error return. */ +procenv_t top_level; + +#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) +/* The signal masks that this shell runs with. */ +sigset_t top_level_mask; +#endif /* JOB_CONTROL */ + +/* When non-zero, we throw_to_top_level (). */ +int interrupt_immediately = 0; + +/* When non-zero, we call the terminating signal handler immediately. */ +int terminate_immediately = 0; + +#if defined (SIGWINCH) +static SigHandler *old_winch = (SigHandler *)SIG_DFL; +#endif + +static void initialize_shell_signals __P((void)); + +void +initialize_signals (reinit) + int reinit; +{ + initialize_shell_signals (); + initialize_job_signals (); +#if !defined (HAVE_SYS_SIGLIST) && !defined (HAVE_UNDER_SYS_SIGLIST) && !defined (HAVE_STRSIGNAL) + if (reinit == 0) + initialize_siglist (); +#endif /* !HAVE_SYS_SIGLIST && !HAVE_UNDER_SYS_SIGLIST && !HAVE_STRSIGNAL */ +} + +/* A structure describing a signal that terminates the shell if not + caught. The orig_handler member is present so children can reset + these signals back to their original handlers. */ +struct termsig { + int signum; + SigHandler *orig_handler; + int orig_flags; +}; + +#define NULL_HANDLER (SigHandler *)SIG_DFL + +/* The list of signals that would terminate the shell if not caught. + We catch them, but just so that we can write the history file, + and so forth. */ +static struct termsig terminating_signals[] = { +#ifdef SIGHUP +{ SIGHUP, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGINT +{ SIGINT, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGILL +{ SIGILL, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGTRAP +{ SIGTRAP, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGIOT +{ SIGIOT, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGDANGER +{ SIGDANGER, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGEMT +{ SIGEMT, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGFPE +{ SIGFPE, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGBUS +{ SIGBUS, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGSEGV +{ SIGSEGV, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGSYS +{ SIGSYS, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGPIPE +{ SIGPIPE, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGALRM +{ SIGALRM, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGTERM +{ SIGTERM, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGXCPU +{ SIGXCPU, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGXFSZ +{ SIGXFSZ, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGVTALRM +{ SIGVTALRM, NULL_HANDLER, 0 }, +#endif + +#if 0 +#ifdef SIGPROF +{ SIGPROF, NULL_HANDLER, 0 }, +#endif +#endif + +#ifdef SIGLOST +{ SIGLOST, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGUSR1 +{ SIGUSR1, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGUSR2 +{ SIGUSR2, NULL_HANDLER, 0 }, +#endif +}; + +#define TERMSIGS_LENGTH (sizeof (terminating_signals) / sizeof (struct termsig)) + +#define XSIG(x) (terminating_signals[x].signum) +#define XHANDLER(x) (terminating_signals[x].orig_handler) +#define XSAFLAGS(x) (terminating_signals[x].orig_flags) + +static int termsigs_initialized = 0; + +/* Initialize signals that will terminate the shell to do some + unwind protection. For non-interactive shells, we only call + this when a trap is defined for EXIT (0). */ +void +initialize_terminating_signals () +{ + register int i; +#if defined (HAVE_POSIX_SIGNALS) + struct sigaction act, oact; +#endif + + if (termsigs_initialized) + return; + + /* The following code is to avoid an expensive call to + set_signal_handler () for each terminating_signals. Fortunately, + this is possible in Posix. Unfortunately, we have to call signal () + on non-Posix systems for each signal in terminating_signals. */ +#if defined (HAVE_POSIX_SIGNALS) + act.sa_handler = termsig_sighandler; + act.sa_flags = 0; + sigemptyset (&act.sa_mask); + sigemptyset (&oact.sa_mask); + for (i = 0; i < TERMSIGS_LENGTH; i++) + sigaddset (&act.sa_mask, XSIG (i)); + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + /* If we've already trapped it, don't do anything. */ + if (signal_is_trapped (XSIG (i))) + continue; + + sigaction (XSIG (i), &act, &oact); + XHANDLER(i) = oact.sa_handler; + XSAFLAGS(i) = oact.sa_flags; + /* Don't do anything with signals that are ignored at shell entry + if the shell is not interactive. */ + if (!interactive_shell && XHANDLER (i) == SIG_IGN) + { + sigaction (XSIG (i), &oact, &act); + set_signal_ignored (XSIG (i)); + } +#if defined (SIGPROF) && !defined (_MINIX) + if (XSIG (i) == SIGPROF && XHANDLER (i) != SIG_DFL && XHANDLER (i) != SIG_IGN) + sigaction (XSIG (i), &oact, (struct sigaction *)NULL); +#endif /* SIGPROF && !_MINIX */ + } + +#else /* !HAVE_POSIX_SIGNALS */ + + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + /* If we've already trapped it, don't do anything. */ + if (signal_is_trapped (XSIG (i))) + continue; + + XHANDLER(i) = signal (XSIG (i), termsig_sighandler); + XSAFLAGS(i) = 0; + /* Don't do anything with signals that are ignored at shell entry + if the shell is not interactive. */ + if (!interactive_shell && XHANDLER (i) == SIG_IGN) + { + signal (XSIG (i), SIG_IGN); + set_signal_ignored (XSIG (i)); + } +#ifdef SIGPROF + if (XSIG (i) == SIGPROF && XHANDLER (i) != SIG_DFL && XHANDLER (i) != SIG_IGN) + signal (XSIG (i), XHANDLER (i)); +#endif + } + +#endif /* !HAVE_POSIX_SIGNALS */ + + termsigs_initialized = 1; +} + +static void +initialize_shell_signals () +{ + if (interactive) + initialize_terminating_signals (); + +#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) + /* All shells use the signal mask they inherit, and pass it along + to child processes. Children will never block SIGCHLD, though. */ + sigemptyset (&top_level_mask); + sigprocmask (SIG_BLOCK, (sigset_t *)NULL, &top_level_mask); +# if defined (SIGCHLD) + sigdelset (&top_level_mask, SIGCHLD); +# endif +#endif /* JOB_CONTROL || HAVE_POSIX_SIGNALS */ + + /* And, some signals that are specifically ignored by the shell. */ + set_signal_handler (SIGQUIT, SIG_IGN); + + if (interactive) + { + set_signal_handler (SIGINT, sigint_sighandler); + set_signal_handler (SIGTERM, SIG_IGN); + set_sigwinch_handler (); + } +} + +void +reset_terminating_signals () +{ + register int i; +#if defined (HAVE_POSIX_SIGNALS) + struct sigaction act; +#endif + + if (termsigs_initialized == 0) + return; + +#if defined (HAVE_POSIX_SIGNALS) + act.sa_flags = 0; + sigemptyset (&act.sa_mask); + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + /* Skip a signal if it's trapped or handled specially, because the + trap code will restore the correct value. */ + if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i))) + continue; + + act.sa_handler = XHANDLER (i); + act.sa_flags = XSAFLAGS (i); + sigaction (XSIG (i), &act, (struct sigaction *) NULL); + } +#else /* !HAVE_POSIX_SIGNALS */ + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i))) + continue; + + signal (XSIG (i), XHANDLER (i)); + } +#endif /* !HAVE_POSIX_SIGNALS */ +} +#undef XSIG +#undef XHANDLER + +/* Run some of the cleanups that should be performed when we run + jump_to_top_level from a builtin command context. XXX - might want to + also call reset_parser here. */ +void +top_level_cleanup () +{ + /* Clean up string parser environment. */ + while (parse_and_execute_level) + parse_and_execute_cleanup (); + +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + + run_unwind_protects (); + loop_level = continuing = breaking = 0; + executing_list = comsub_ignore_return = return_catch_flag = 0; +} + +/* What to do when we've been interrupted, and it is safe to handle it. */ +void +throw_to_top_level () +{ + int print_newline = 0; + + if (interrupt_state) + { + print_newline = 1; + DELINTERRUPT; + } + + if (interrupt_state) + return; + + last_command_exit_signal = (last_command_exit_value > 128) ? + (last_command_exit_value - 128) : 0; + last_command_exit_value |= 128; + + /* Run any traps set on SIGINT. */ + run_interrupt_trap (); + + /* Clean up string parser environment. */ + while (parse_and_execute_level) + parse_and_execute_cleanup (); + +#if defined (JOB_CONTROL) + give_terminal_to (shell_pgrp, 0); +#endif /* JOB_CONTROL */ + +#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) + /* This should not be necessary on systems using sigsetjmp/siglongjmp. */ + sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL); +#endif + + reset_parser (); + +#if defined (READLINE) + if (interactive) + bashline_reset (); +#endif /* READLINE */ + +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + + run_unwind_protects (); + loop_level = continuing = breaking = 0; + executing_list = comsub_ignore_return = return_catch_flag = 0; + + if (interactive && print_newline) + { + fflush (stdout); + fprintf (stderr, "\n"); + fflush (stderr); + } + + /* An interrupted `wait' command in a script does not exit the script. */ + if (interactive || (interactive_shell && !shell_initialized) || + (print_newline && signal_is_trapped (SIGINT))) + jump_to_top_level (DISCARD); + else + jump_to_top_level (EXITPROG); +} + +/* This is just here to isolate the longjmp calls. */ +void +jump_to_top_level (value) + int value; +{ + longjmp (top_level, value); +} + +sighandler +termsig_sighandler (sig) + int sig; +{ + /* If we get called twice with the same signal before handling it, + terminate right away. */ + if ( +#ifdef SIGHUP + sig != SIGHUP && +#endif +#ifdef SIGINT + sig != SIGINT && +#endif +#ifdef SIGDANGER + sig != SIGDANGER && +#endif +#ifdef SIGPIPE + sig != SIGPIPE && +#endif +#ifdef SIGALRM + sig != SIGALRM && +#endif +#ifdef SIGTERM + sig != SIGTERM && +#endif +#ifdef SIGXCPU + sig != SIGXCPU && +#endif +#ifdef SIGXFSZ + sig != SIGXFSZ && +#endif +#ifdef SIGVTALRM + sig != SIGVTALRM && +#endif +#ifdef SIGLOST + sig != SIGLOST && +#endif +#ifdef SIGUSR1 + sig != SIGUSR1 && +#endif +#ifdef SIGUSR2 + sig != SIGUSR2 && +#endif + sig == terminating_signal) + terminate_immediately = 1; + + terminating_signal = sig; + + /* XXX - should this also trigger when interrupt_immediately is set? */ + if (terminate_immediately) + { + terminate_immediately = 0; + termsig_handler (sig); + } + + SIGRETURN (0); +} + +void +termsig_handler (sig) + int sig; +{ + static int handling_termsig = 0; + + /* Simple semaphore to keep this function from being executed multiple + times. Since we no longer are running as a signal handler, we don't + block multiple occurrences of the terminating signals while running. */ + if (handling_termsig) + return; + handling_termsig = 1; + terminating_signal = 0; /* keep macro from re-testing true. */ + + /* I don't believe this condition ever tests true. */ + if (sig == SIGINT && signal_is_trapped (SIGINT)) + run_interrupt_trap (); + +#if defined (HISTORY) + if (interactive_shell && sig != SIGABRT) + maybe_save_shell_history (); +#endif /* HISTORY */ + +#if defined (JOB_CONTROL) + if (sig == SIGHUP && (interactive || (subshell_environment & (SUBSHELL_COMSUB|SUBSHELL_PROCSUB)))) + hangup_all_jobs (); + end_job_control (); +#endif /* JOB_CONTROL */ + +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + + /* Reset execution context */ + loop_level = continuing = breaking = 0; + executing_list = comsub_ignore_return = return_catch_flag = 0; + + run_exit_trap (); + set_signal_handler (sig, SIG_DFL); + kill (getpid (), sig); +} + +/* What we really do when SIGINT occurs. */ +sighandler +sigint_sighandler (sig) + int sig; +{ +#if defined (MUST_REINSTALL_SIGHANDLERS) + signal (sig, sigint_sighandler); +#endif + + /* interrupt_state needs to be set for the stack of interrupts to work + right. Should it be set unconditionally? */ + if (interrupt_state == 0) + ADDINTERRUPT; + + if (interrupt_immediately) + { + interrupt_immediately = 0; + last_command_exit_value = 128 + sig; + throw_to_top_level (); + } + + SIGRETURN (0); +} + +#if defined (SIGWINCH) +sighandler +sigwinch_sighandler (sig) + int sig; +{ +#if defined (MUST_REINSTALL_SIGHANDLERS) + set_signal_handler (SIGWINCH, sigwinch_sighandler); +#endif /* MUST_REINSTALL_SIGHANDLERS */ + sigwinch_received = 1; + SIGRETURN (0); +} +#endif /* SIGWINCH */ + +void +set_sigwinch_handler () +{ +#if defined (SIGWINCH) + old_winch = set_signal_handler (SIGWINCH, sigwinch_sighandler); +#endif +} + +void +unset_sigwinch_handler () +{ +#if defined (SIGWINCH) + set_signal_handler (SIGWINCH, old_winch); +#endif +} + +/* Signal functions used by the rest of the code. */ +#if !defined (HAVE_POSIX_SIGNALS) + +#if defined (JOB_CONTROL) +/* Perform OPERATION on NEWSET, perhaps leaving information in OLDSET. */ +sigprocmask (operation, newset, oldset) + int operation, *newset, *oldset; +{ + int old, new; + + if (newset) + new = *newset; + else + new = 0; + + switch (operation) + { + case SIG_BLOCK: + old = sigblock (new); + break; + + case SIG_SETMASK: + sigsetmask (new); + break; + + default: + internal_error (_("sigprocmask: %d: invalid operation"), operation); + } + + if (oldset) + *oldset = old; +} +#endif /* JOB_CONTROL */ + +#else + +#if !defined (SA_INTERRUPT) +# define SA_INTERRUPT 0 +#endif + +#if !defined (SA_RESTART) +# define SA_RESTART 0 +#endif + +SigHandler * +set_signal_handler (sig, handler) + int sig; + SigHandler *handler; +{ + struct sigaction act, oact; + + act.sa_handler = handler; + act.sa_flags = 0; +#if 0 + if (sig == SIGALRM) + act.sa_flags |= SA_INTERRUPT; /* XXX */ + else + act.sa_flags |= SA_RESTART; /* XXX */ +#endif + sigemptyset (&act.sa_mask); + sigemptyset (&oact.sa_mask); + sigaction (sig, &act, &oact); + return (oact.sa_handler); +} +#endif /* HAVE_POSIX_SIGNALS */ diff --git a/subst.c b/subst.c index 81a3256c2..679151612 100644 --- a/subst.c +++ b/subst.c @@ -5243,6 +5243,7 @@ array_length_reference (s) char *akey; char *t, c; ARRAY *array; + HASH_TABLE *h; SHELL_VAR *var; var = array_variable_part (s, &t, &len); @@ -5266,15 +5267,16 @@ array_length_reference (s) 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 (assoc_num_elements (assoc_cell (var))); + return (h ? assoc_num_elements (h) : 0); else if (array_p (var)) - return (array_num_elements (array)); + return (array ? array_num_elements (array) : 0); else - return 1; + return (var_isset (var) ? 1 : 0); } if (assoc_p (var)) @@ -5432,7 +5434,7 @@ parameter_brace_expand_word (name, var_is_special, quoted, pflags) #if defined (ARRAY_VARS) else if (valid_array_reference (name)) { - temp = array_value (name, quoted, &atype); + temp = array_value (name, quoted, &atype, (arrayind_t *)NULL); if (atype == 0 && temp) temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) ? quote_string (temp) @@ -5967,7 +5969,7 @@ get_var_and_type (varname, value, quoted, varp, valp) else { vtype = VT_ARRAYMEMBER; - *valp = array_value (varname, 1, (int *)NULL); + *valp = array_value (varname, 1, (int *)NULL, (arrayind_t *)NULL); } *varp = v; } @@ -5984,7 +5986,7 @@ get_var_and_type (varname, value, quoted, varp, valp) { vtype = VT_ARRAYMEMBER; *varp = v; - *valp = array_value (varname, 1, (int *)NULL); + *valp = array_value (varname, 1, (int *)NULL, (arrayind_t *)NULL); } } else if ((v = find_variable (varname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v))) diff --git a/subst.c~ b/subst.c~ new file mode 100644 index 000000000..58e61d963 --- /dev/null +++ b/subst.c~ @@ -0,0 +1,9072 @@ +/* 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-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" +#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 "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 "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 */ + +/* 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) \ + ((c) == '#' || (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_pattern_char __P((char *, char *)); +static int match_upattern __P((char *, char *, int, char **, char **)); +#if defined (HANDLE_MULTIBYTE) +static int match_pattern_wchar __P((wchar_t *, wchar_t *)); +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 *, char *, 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)); +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 *, int, SHELL_VAR **, char **)); +static char *mb_substring __P((char *, int, int)); +static char *parameter_brace_substring __P((char *, char *, char *, int)); + +static char *pos_params_pat_subst __P((char *, char *, char *, int)); + +static char *parameter_brace_patsub __P((char *, char *, char *, int)); + +static char *pos_params_casemod __P((char *, char *, int, int)); +static char *parameter_brace_casemod __P((char *, char *, int, char *, 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) + { + /* 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, 1, 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, 1, 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, "(", ")", 0)); +} +#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; + } + +#if 0 + /* Process a nested command substitution, but only if we're parsing a + command substitution. XXX - for bash-4.2 */ + if ((flags & SX_COMMAND) && string[i] == '$' && string[i+1] == LPAREN) + { + si = i + 2; + t = extract_command_subst (string, &si, flags); + i = si + 1; + continue; + } +#endif + + /* 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; + char *result, *t; + DECLARE_MBSTATE; + + pass_character = 0; + nesting_level = 1; + slen = strlen (string + *sindex) + *sindex; + + 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; + } + + /* 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; + } + + /* 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"), "}", 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; + 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); + i = si; + if (string[i] == '\0') + break; + i++; + continue; + } +#endif /* PROCESS_SUBSTITUTION */ + 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 */ +#if 0 + tlist = ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (ifs && *ifs == 0)) +#else + tlist = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE)) +#endif + ? 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, tlen, appendop, assign_list, aflags, retval; + char *name, *value; + 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] == '=') + { + char *temp; + + if (name[offset - 1] == '+') + { + appendop = 1; + name[offset - 1] = '\0'; + } + + name[offset] = 0; /* might need this set later */ + temp = name + offset + 1; + tlen = STRLEN (temp); + +#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) + WORD_DESC *word; +{ + 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. */ +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 = 0; + 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++; + + 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 + +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 (savestring (param)); /* no match, return original string */ +} + +#if defined (HANDLE_MULTIBYTE) +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 (wcsdup (wparam)); /* no match, return original string */ +} +#endif /* HANDLE_MULTIBYTE */ + +static char * +remove_pattern (param, pattern, op) + char *param, *pattern; + int op; +{ + 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; + char *xret; + + n = xdupmbstowcs (&wpattern, NULL, pattern); + if (n == (size_t)-1) + return (remove_upattern (param, pattern, op)); + n = xdupmbstowcs (&wparam, NULL, param); + if (n == (size_t)-1) + { + free (wpattern); + return (remove_upattern (param, pattern, op)); + } + oret = ret = remove_wpattern (wparam, n, wpattern, op); + + 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 + return (remove_upattern (param, pattern, op)); +} + +/* Return 1 of the first character of STRING could match the first + character of pattern PAT. Used to avoid n2 calls to strmatch(). */ +static int +match_pattern_char (pat, string) + char *pat, *string; +{ + char c; + + if (*string == 0) + return (0); + + switch (c = *pat++) + { + default: + return (*string == c); + case '\\': + return (*string == *pat); + case '?': + return (*pat == LPAREN ? 1 : (*string != '\0')); + case '*': + return (1); + case '+': + case '!': + case '@': + return (*pat == LPAREN ? 1 : (*string == c)); + case '[': + return (*string != '\0'); + } +} + +/* 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; + register char *p, *p1, *npat; + char *end; + + /* 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; + + switch (mtype) + { + case MATCH_ANY: + for (p = string; p <= end; p++) + { + if (match_pattern_char (pat, p)) + { + for (p1 = end; p1 >= p; p1--) + { + c = *p1; *p1 = '\0'; + if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0) + { + *p1 = c; + *sp = p; + *ep = p1; + return 1; + } + *p1 = c; + } + } + } + + return (0); + + case MATCH_BEG: + if (match_pattern_char (pat, string) == 0) + return (0); + + for (p = end; p >= string; p--) + { + c = *p; *p = '\0'; + if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0) + { + *p = c; + *sp = string; + *ep = p; + return 1; + } + *p = c; + } + + return (0); + + case MATCH_END: + for (p = string; p <= end; p++) + { + if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0) + { + *sp = p; + *ep = end; + return 1; + } + + } + + return (0); + } + + return (0); +} + +#if defined (HANDLE_MULTIBYTE) +/* Return 1 of the first character of WSTRING could match the first + character of pattern WPAT. Wide character version. */ +static int +match_pattern_wchar (wpat, wstring) + wchar_t *wpat, *wstring; +{ + wchar_t wc; + + if (*wstring == 0) + return (0); + + switch (wc = *wpat++) + { + default: + return (*wstring == wc); + case L'\\': + return (*wstring == *wpat); + case L'?': + return (*wpat == LPAREN ? 1 : (*wstring != L'\0')); + case L'*': + return (1); + case L'+': + case L'!': + case L'@': + return (*wpat == LPAREN ? 1 : (*wstring == wc)); + case L'[': + return (*wstring != L'\0'); + } +} + +/* 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; + int len; +#if 0 + size_t n, n1; /* Apple's gcc seems to miscompile this badly */ +#else + int n, n1; +#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. */ + /* XXX - check this later if I ever implement `**' with special meaning, + since this will potentially result in `**' at the beginning or end */ + 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); + + switch (mtype) + { + case MATCH_ANY: + for (n = 0; n <= wstrlen; n++) + { + if (match_pattern_wchar (wpat, wstring + n)) + { + for (n1 = wstrlen; n1 >= n; n1--) + { + 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; + } + } + } + + return (0); + + case MATCH_BEG: + if (match_pattern_wchar (wpat, wstring) == 0) + return (0); + + for (n = wstrlen; n >= 0; n--) + { + 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; + } + + return (0); + + case MATCH_END: + for (n = 0; n <= wstrlen; n++) + { + if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0) + { + *sp = indices[n]; + *ep = indices[wstrlen]; + return 1; + } + } + + 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; +#endif + + if (string == 0 || *string == 0 || pat == 0 || *pat == 0) + return (0); + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1) + { + 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, patstr, rtype, quoted) + char *varname, *value, *patstr; + int rtype, quoted; +{ + 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, quoted, &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; + 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; + +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_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; +} + +int +fifos_pending () +{ + 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. */ + +static void +add_fifo_list (fd) + int fd; +{ + if (!dev_fd_list || 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 */ +} + +void +unlink_fifo_list () +{ + register int i; + + if (nfds == 0) + return; + + for (i = 0; nfds && i < totfds; i++) + if (dev_fd_list[i]) + { + close (i); + dev_fd_list[i] = 0; + nfds--; + } + + nfds = 0; +} + +#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 (); + 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; + +#ifdef __CYGWIN__ + setmode (fd, O_TEXT); /* we don't want CR/LF, we want Unix-style */ +#endif + + /* 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. */ + reset_signal_handlers (); + +#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]); + + /* 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 0 + if (interactive && pipeline_pgrp != (pid_t)0 && pipeline_pgrp != last_asynchronous_pid) +#else + if (interactive && pipeline_pgrp != (pid_t)0 && (subshell_environment & SUBSHELL_ASYNC) == 0) +#endif + 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 (value_cell (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); + return (-1); + } + t = assoc_reference (assoc_cell (var), akey); + } + else + { + ind = array_expand_index (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) + char *name; + int var_is_special, quoted, pflags; +{ + WORD_DESC *ret; + char *temp, *tt; + intmax_t arg_index; + SHELL_VAR *var; + int atype, rflags; + + ret = 0; + temp = 0; + rflags = 0; + + /* 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)) + { + temp = array_value (name, quoted, &atype, (arrayind_t *)NULL); + if (atype == 0 && temp) + temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (temp) + : quote_escapes (temp); + 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); + 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); + 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); + free (t1); + + w->word = temp; + 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} */ +} + +#if defined (HANDLE_MULTIBYTE) +size_t +mbstrlen (s) + const char *s; +{ + size_t clen, nc; + mbstate_t mbs, mbsbak; + + nc = 0; + memset (&mbs, 0, sizeof (mbs)); + mbsbak = mbs; + while ((clen = mbrlen(s, MB_CUR_MAX, &mbs)) != 0) + { + if (MB_INVALIDCH(clen)) + { + clen = 1; /* assume single byte */ + mbs = mbsbak; + } + + s += clen; + nc++; + mbsbak = mbs; + } + return nc; +} +#endif + + +/* 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; + 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); + 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); + 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 = MB_STRLEN (t); + 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 (*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 + { + *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). + 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, quoted, varp, valp) + char *varname, *value; + int quoted; + SHELL_VAR **varp; + char **valp; +{ + int vtype; + char *temp; +#if defined (ARRAY_VARS) + SHELL_VAR *v; +#endif + + /* 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 (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, 1, (int *)NULL, (arrayind_t *)NULL); + } + *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, 1, (int *)NULL, (arrayind_t *)NULL); + } + } + 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, substr, quoted) + char *varname, *value, *substr; + int quoted; +{ + 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, quoted, &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) + 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 */ +/* */ +/****************************************************************/ + +char * +pat_subst (string, pat, rep, mflags) + char *string, *pat, *rep; + int mflags; +{ + char *ret, *s, *e, *str; + int rsize, rptr, l, replen, mtype; + + mtype = mflags & MATCH_TYPEMASK; + + /* 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. + */ + 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; + RESIZE_MALLOCED_BUFFER (ret, rptr, (l + replen), 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, rep, replen); + rptr += replen; + } + str = e; /* e == end of match */ + + 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) + { + 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, patsub, quoted) + char *varname, *value, *patsub; + int quoted; +{ + 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, quoted, &v, &val); + if (vtype == -1) + return ((char *)NULL); + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + mflags = 0; + if (patsub && *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. */ +#if 0 + if (rep = quoted_strchr ((*patsub == '/') ? lpatsub+1 : lpatsub, '/', ST_BACKSL)) + *rep++ = '\0'; + else + rep = (char *)NULL; +#else + delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0); + if (lpatsub[delim] == '/') + { + lpatsub[delim] = 0; + rep = lpatsub + delim + 1; + } + else + rep = (char *)NULL; +#endif + + 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, modspec, patspec, quoted) + char *varname, *value; + int modspec; + char *patspec; + int quoted; +{ + 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, quoted, &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; + + 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; + + /* 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 && + (string[t_index] == '-' || + string[t_index] == '?' || + string[t_index] == '#')) || + (sindex == t_index - 1 && string[sindex] == '!' && + (string[t_index] == '#' || + string[t_index] == '?' || + string[t_index] == '@' || + string[t_index] == '*'))) + { + t_index++; + free (name); + temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0); + name = (char *)xmalloc (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) + 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); + 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; + + 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)); + + 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, 0); + if (string[sindex] == RBRACE) + sindex++; + else + goto bad_substitution; + } + else + value = (char *)NULL; + + *indexp = sindex; + + /* If this is a substring spec, process it and add the result. */ + if (want_substring) + { + temp1 = parameter_brace_substring (name, temp, value, quoted); + 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, value, quoted); + 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; + 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, modspec, value, quoted); + 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: + 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); + } + 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, value, c, quoted); + free (temp); + free (value); + + 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) + { + 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; + + 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); + 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; + +#if 0 + if (pflags & PF_NOSPLIT2) + temp = string_list_internal (quoted ? quote_list (list) : list, " "); + else +#endif + /* 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. */ + temp = string_list_dollar_at (list, quoted); + + 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; + 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->word; + 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; + + 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. */ + if (temp == 0 && quoted_state == PARTIALLY_QUOTED) + 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 = make_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; + output_list = make_word_list (w, output_list); + free (expansions[eindex]); + } + 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); + 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); + /* 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; + + /* 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; + + 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); + 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) + 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/tests/RUN-ONE-TEST b/tests/RUN-ONE-TEST index 3efcf32d6..72ec06a2c 100755 --- a/tests/RUN-ONE-TEST +++ b/tests/RUN-ONE-TEST @@ -1,4 +1,4 @@ -BUILD_DIR=/usr/local/build/chet/bash/bash-current +BUILD_DIR=/usr/local/build/bash/bash-current THIS_SH=$BUILD_DIR/bash PATH=$PATH:$BUILD_DIR diff --git a/tests/RUN-ONE-TEST~ b/tests/RUN-ONE-TEST~ new file mode 100755 index 000000000..3efcf32d6 --- /dev/null +++ b/tests/RUN-ONE-TEST~ @@ -0,0 +1,9 @@ +BUILD_DIR=/usr/local/build/chet/bash/bash-current +THIS_SH=$BUILD_DIR/bash +PATH=$PATH:$BUILD_DIR + +export THIS_SH PATH + +rm -f /tmp/xx + +/bin/sh "$@" diff --git a/variables.c b/variables.c index bb399b102..d1da8f67b 100644 --- a/variables.c +++ b/variables.c @@ -1309,7 +1309,6 @@ get_random (var) rv = get_random_number (); last_random_value = rv; -fprintf(stderr, "get_random: rv = %d\n", rv); p = itos (rv); FREE (value_cell (var)); diff --git a/variables.c~ b/variables.c~ new file mode 100644 index 000000000..bb399b102 --- /dev/null +++ b/variables.c~ @@ -0,0 +1,4686 @@ +/* variables.c -- Functions for hacking shell variables. */ + +/* 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" + +#include "bashtypes.h" +#include "posixstat.h" +#include "posixtime.h" + +#if defined (__QNX__) +# if defined (__QNXNTO__) +# include +# else +# include +# endif /* !__QNXNTO__ */ +#endif /* __QNX__ */ + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include "chartypes.h" +#if defined (HAVE_PWD_H) +# include +#endif +#include "bashansi.h" +#include "bashintl.h" + +#define NEED_XTRACE_SET_DECL + +#include "shell.h" +#include "flags.h" +#include "execute_cmd.h" +#include "findcmd.h" +#include "mailcheck.h" +#include "input.h" +#include "hashcmd.h" +#include "pathexp.h" +#include "alias.h" + +#include "builtins/getopt.h" +#include "builtins/common.h" + +#if defined (READLINE) +# include "bashline.h" +# include +#else +# include +#endif + +#if defined (HISTORY) +# include "bashhist.h" +# include +#endif /* HISTORY */ + +#if defined (PROGRAMMABLE_COMPLETION) +# include "pcomplete.h" +#endif + +#define TEMPENV_HASH_BUCKETS 4 /* must be power of two */ + +#define ifsname(s) ((s)[0] == 'I' && (s)[1] == 'F' && (s)[2] == 'S' && (s)[3] == '\0') + +extern char **environ; + +/* Variables used here and defined in other files. */ +extern int posixly_correct; +extern int line_number; +extern int subshell_environment, indirection_level, subshell_level; +extern int build_version, patch_level; +extern int expanding_redir; +extern char *dist_version, *release_status; +extern char *shell_name; +extern char *primary_prompt, *secondary_prompt; +extern char *current_host_name; +extern sh_builtin_func_t *this_shell_builtin; +extern SHELL_VAR *this_shell_function; +extern char *the_printed_command_except_trap; +extern char *this_command_name; +extern char *command_execution_string; +extern time_t shell_start_time; +extern int assigning_in_environment; +extern int executing_builtin; + +#if defined (READLINE) +extern int no_line_editing; +extern int perform_hostname_completion; +#endif + +/* The list of shell variables that the user has created at the global + scope, or that came from the environment. */ +VAR_CONTEXT *global_variables = (VAR_CONTEXT *)NULL; + +/* The current list of shell variables, including function scopes */ +VAR_CONTEXT *shell_variables = (VAR_CONTEXT *)NULL; + +/* The list of shell functions that the user has created, or that came from + the environment. */ +HASH_TABLE *shell_functions = (HASH_TABLE *)NULL; + +#if defined (DEBUGGER) +/* The table of shell function definitions that the user defined or that + came from the environment. */ +HASH_TABLE *shell_function_defs = (HASH_TABLE *)NULL; +#endif + +/* The current variable context. This is really a count of how deep into + executing functions we are. */ +int variable_context = 0; + +/* The set of shell assignments which are made only in the environment + for a single command. */ +HASH_TABLE *temporary_env = (HASH_TABLE *)NULL; + +/* Set to non-zero if an assignment error occurs while putting variables + into the temporary environment. */ +int tempenv_assign_error; + +/* Some funky variables which are known about specially. Here is where + "$*", "$1", and all the cruft is kept. */ +char *dollar_vars[10]; +WORD_LIST *rest_of_args = (WORD_LIST *)NULL; + +/* The value of $$. */ +pid_t dollar_dollar_pid; + +/* Non-zero means that we have to remake EXPORT_ENV. */ +int array_needs_making = 1; + +/* The number of times BASH has been executed. This is set + by initialize_variables (). */ +int shell_level = 0; + +/* An array which is passed to commands as their environment. It is + manufactured from the union of the initial environment and the + shell variables that are marked for export. */ +char **export_env = (char **)NULL; +static int export_env_index; +static int export_env_size; + +#if defined (READLINE) +static int winsize_assignment; /* currently assigning to LINES or COLUMNS */ +static int winsize_assigned; /* assigned to LINES or COLUMNS */ +#endif + +/* Some forward declarations. */ +static void create_variable_tables __P((void)); + +static void set_machine_vars __P((void)); +static void set_home_var __P((void)); +static void set_shell_var __P((void)); +static char *get_bash_name __P((void)); +static void initialize_shell_level __P((void)); +static void uidset __P((void)); +#if defined (ARRAY_VARS) +static void make_vers_array __P((void)); +#endif + +static SHELL_VAR *null_assign __P((SHELL_VAR *, char *, arrayind_t, char *)); +#if defined (ARRAY_VARS) +static SHELL_VAR *null_array_assign __P((SHELL_VAR *, char *, arrayind_t, char *)); +#endif +static SHELL_VAR *get_self __P((SHELL_VAR *)); + +#if defined (ARRAY_VARS) +static SHELL_VAR *init_dynamic_array_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int)); +static SHELL_VAR *init_dynamic_assoc_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int)); +#endif + +static SHELL_VAR *assign_seconds __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_seconds __P((SHELL_VAR *)); +static SHELL_VAR *init_seconds_var __P((void)); + +static int brand __P((void)); +static void sbrand __P((unsigned long)); /* set bash random number generator. */ +static void seedrand __P((void)); /* seed generator randomly */ +static SHELL_VAR *assign_random __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_random __P((SHELL_VAR *)); + +static SHELL_VAR *assign_lineno __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_lineno __P((SHELL_VAR *)); + +static SHELL_VAR *assign_subshell __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_subshell __P((SHELL_VAR *)); + +static SHELL_VAR *get_bashpid __P((SHELL_VAR *)); + +#if defined (HISTORY) +static SHELL_VAR *get_histcmd __P((SHELL_VAR *)); +#endif + +#if defined (READLINE) +static SHELL_VAR *get_comp_wordbreaks __P((SHELL_VAR *)); +static SHELL_VAR *assign_comp_wordbreaks __P((SHELL_VAR *, char *, arrayind_t, char *)); +#endif + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) +static SHELL_VAR *assign_dirstack __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_dirstack __P((SHELL_VAR *)); +#endif + +#if defined (ARRAY_VARS) +static SHELL_VAR *get_groupset __P((SHELL_VAR *)); + +static SHELL_VAR *build_hashcmd __P((SHELL_VAR *)); +static SHELL_VAR *get_hashcmd __P((SHELL_VAR *)); +static SHELL_VAR *assign_hashcmd __P((SHELL_VAR *, char *, arrayind_t, char *)); +# if defined (ALIAS) +static SHELL_VAR *build_aliasvar __P((SHELL_VAR *)); +static SHELL_VAR *get_aliasvar __P((SHELL_VAR *)); +static SHELL_VAR *assign_aliasvar __P((SHELL_VAR *, char *, arrayind_t, char *)); +# endif +#endif + +static SHELL_VAR *get_funcname __P((SHELL_VAR *)); +static SHELL_VAR *init_funcname_var __P((void)); + +static void initialize_dynamic_variables __P((void)); + +static SHELL_VAR *hash_lookup __P((const char *, HASH_TABLE *)); +static SHELL_VAR *new_shell_variable __P((const char *)); +static SHELL_VAR *make_new_variable __P((const char *, HASH_TABLE *)); +static SHELL_VAR *bind_variable_internal __P((const char *, char *, HASH_TABLE *, int, int)); + +static void dispose_variable_value __P((SHELL_VAR *)); +static void free_variable_hash_data __P((PTR_T)); + +static VARLIST *vlist_alloc __P((int)); +static VARLIST *vlist_realloc __P((VARLIST *, int)); +static void vlist_add __P((VARLIST *, SHELL_VAR *, int)); + +static void flatten __P((HASH_TABLE *, sh_var_map_func_t *, VARLIST *, int)); + +static int qsort_var_comp __P((SHELL_VAR **, SHELL_VAR **)); + +static SHELL_VAR **vapply __P((sh_var_map_func_t *)); +static SHELL_VAR **fapply __P((sh_var_map_func_t *)); + +static int visible_var __P((SHELL_VAR *)); +static int visible_and_exported __P((SHELL_VAR *)); +static int export_environment_candidate __P((SHELL_VAR *)); +static int local_and_exported __P((SHELL_VAR *)); +static int variable_in_context __P((SHELL_VAR *)); +#if defined (ARRAY_VARS) +static int visible_array_vars __P((SHELL_VAR *)); +#endif + +static SHELL_VAR *bind_tempenv_variable __P((const char *, char *)); +static void push_temp_var __P((PTR_T)); +static void propagate_temp_var __P((PTR_T)); +static void dispose_temporary_env __P((sh_free_func_t *)); + +static inline char *mk_env_string __P((const char *, const char *)); +static char **make_env_array_from_var_list __P((SHELL_VAR **)); +static char **make_var_export_array __P((VAR_CONTEXT *)); +static char **make_func_export_array __P((void)); +static void add_temp_array_to_env __P((char **, int, int)); + +static int n_shell_variables __P((void)); +static int set_context __P((SHELL_VAR *)); + +static void push_func_var __P((PTR_T)); +static void push_exported_var __P((PTR_T)); + +static inline int find_special_var __P((const char *)); + +static void +create_variable_tables () +{ + if (shell_variables == 0) + { + shell_variables = global_variables = new_var_context ((char *)NULL, 0); + shell_variables->scope = 0; + shell_variables->table = hash_create (0); + } + + if (shell_functions == 0) + shell_functions = hash_create (0); + +#if defined (DEBUGGER) + if (shell_function_defs == 0) + shell_function_defs = hash_create (0); +#endif +} + +/* Initialize the shell variables from the current environment. + If PRIVMODE is nonzero, don't import functions from ENV or + parse $SHELLOPTS. */ +void +initialize_shell_variables (env, privmode) + char **env; + int privmode; +{ + char *name, *string, *temp_string; + int c, char_index, string_index, string_length; + SHELL_VAR *temp_var; + + create_variable_tables (); + + for (string_index = 0; string = env[string_index++]; ) + { + char_index = 0; + name = string; + while ((c = *string++) && c != '=') + ; + if (string[-1] == '=') + char_index = string - name - 1; + + /* If there are weird things in the environment, like `=xxx' or a + string without an `=', just skip them. */ + if (char_index == 0) + continue; + + /* ASSERT(name[char_index] == '=') */ + name[char_index] = '\0'; + /* Now, name = env variable name, string = env variable value, and + char_index == strlen (name) */ + + temp_var = (SHELL_VAR *)NULL; + + /* If exported function, define it now. Don't import functions from + the environment in privileged mode. */ + if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4)) + { + string_length = strlen (string); + temp_string = (char *)xmalloc (3 + string_length + char_index); + + strcpy (temp_string, name); + temp_string[char_index] = ' '; + strcpy (temp_string + char_index + 1, string); + + parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST); + + /* Ancient backwards compatibility. Old versions of bash exported + functions like name()=() {...} */ + if (name[char_index - 1] == ')' && name[char_index - 2] == '(') + name[char_index - 2] = '\0'; + + if (temp_var = find_function (name)) + { + VSETATTR (temp_var, (att_exported|att_imported)); + array_needs_making = 1; + } + else + report_error (_("error importing function definition for `%s'"), name); + + /* ( */ + if (name[char_index - 1] == ')' && name[char_index - 2] == '\0') + name[char_index - 2] = '('; /* ) */ + } +#if defined (ARRAY_VARS) +# if 0 + /* Array variables may not yet be exported. */ + else if (*string == '(' && string[1] == '[' && string[strlen (string) - 1] == ')') + { + string_length = 1; + temp_string = extract_array_assignment_list (string, &string_length); + temp_var = assign_array_from_string (name, temp_string); + FREE (temp_string); + VSETATTR (temp_var, (att_exported | att_imported)); + array_needs_making = 1; + } +# endif +#endif +#if 0 + else if (legal_identifier (name)) +#else + else +#endif + { + temp_var = bind_variable (name, string, 0); + if (legal_identifier (name)) + VSETATTR (temp_var, (att_exported | att_imported)); + else + VSETATTR (temp_var, (att_exported | att_imported | att_invisible)); + array_needs_making = 1; + } + + name[char_index] = '='; + /* temp_var can be NULL if it was an exported function with a syntax + error (a different bug, but it still shouldn't dump core). */ + if (temp_var && function_p (temp_var) == 0) /* XXX not yet */ + { + CACHE_IMPORTSTR (temp_var, name); + } + } + + set_pwd (); + + /* Set up initial value of $_ */ + temp_var = set_if_not ("_", dollar_vars[0]); + + /* Remember this pid. */ + dollar_dollar_pid = getpid (); + + /* Now make our own defaults in case the vars that we think are + important are missing. */ + temp_var = set_if_not ("PATH", DEFAULT_PATH_VALUE); +#if 0 + set_auto_export (temp_var); /* XXX */ +#endif + + temp_var = set_if_not ("TERM", "dumb"); +#if 0 + set_auto_export (temp_var); /* XXX */ +#endif + +#if defined (__QNX__) + /* set node id -- don't import it from the environment */ + { + char node_name[22]; +# if defined (__QNXNTO__) + netmgr_ndtostr(ND2S_LOCAL_STR, ND_LOCAL_NODE, node_name, sizeof(node_name)); +# else + qnx_nidtostr (getnid (), node_name, sizeof (node_name)); +# endif + temp_var = bind_variable ("NODE", node_name, 0); + set_auto_export (temp_var); + } +#endif + + /* set up the prompts. */ + if (interactive_shell) + { +#if defined (PROMPT_STRING_DECODE) + set_if_not ("PS1", primary_prompt); +#else + if (current_user.uid == -1) + get_current_user_info (); + set_if_not ("PS1", current_user.euid == 0 ? "# " : primary_prompt); +#endif + set_if_not ("PS2", secondary_prompt); + } + set_if_not ("PS4", "+ "); + + /* Don't allow IFS to be imported from the environment. */ + temp_var = bind_variable ("IFS", " \t\n", 0); + setifs (temp_var); + + /* Magic machine types. Pretty convenient. */ + set_machine_vars (); + + /* Default MAILCHECK for interactive shells. Defer the creation of a + default MAILPATH until the startup files are read, because MAIL + names a mail file if MAILPATH is not set, and we should provide a + default only if neither is set. */ + if (interactive_shell) + { + temp_var = set_if_not ("MAILCHECK", posixly_correct ? "600" : "60"); + VSETATTR (temp_var, att_integer); + } + + /* Do some things with shell level. */ + initialize_shell_level (); + + set_ppid (); + + /* Initialize the `getopts' stuff. */ + temp_var = bind_variable ("OPTIND", "1", 0); + VSETATTR (temp_var, att_integer); + getopts_reset (0); + bind_variable ("OPTERR", "1", 0); + sh_opterr = 1; + + if (login_shell == 1 && posixly_correct == 0) + set_home_var (); + + /* Get the full pathname to THIS shell, and set the BASH variable + to it. */ + name = get_bash_name (); + temp_var = bind_variable ("BASH", name, 0); + free (name); + + /* Make the exported environment variable SHELL be the user's login + shell. Note that the `tset' command looks at this variable + to determine what style of commands to output; if it ends in "csh", + then C-shell commands are output, else Bourne shell commands. */ + set_shell_var (); + + /* Make a variable called BASH_VERSION which contains the version info. */ + bind_variable ("BASH_VERSION", shell_version_string (), 0); +#if defined (ARRAY_VARS) + make_vers_array (); +#endif + + if (command_execution_string) + bind_variable ("BASH_EXECUTION_STRING", command_execution_string, 0); + + /* Find out if we're supposed to be in Posix.2 mode via an + environment variable. */ + temp_var = find_variable ("POSIXLY_CORRECT"); + if (!temp_var) + temp_var = find_variable ("POSIX_PEDANTIC"); + if (temp_var && imported_p (temp_var)) + sv_strict_posix (temp_var->name); + +#if defined (HISTORY) + /* Set history variables to defaults, and then do whatever we would + do if the variable had just been set. Do this only in the case + that we are remembering commands on the history list. */ + if (remember_on_history) + { + name = bash_tilde_expand (posixly_correct ? "~/.sh_history" : "~/.bash_history", 0); + + set_if_not ("HISTFILE", name); + free (name); + +#if 0 + set_if_not ("HISTSIZE", "500"); + sv_histsize ("HISTSIZE"); +#endif + } +#endif /* HISTORY */ + + /* Seed the random number generator. */ + seedrand (); + + /* Handle some "special" variables that we may have inherited from a + parent shell. */ + if (interactive_shell) + { + temp_var = find_variable ("IGNOREEOF"); + if (!temp_var) + temp_var = find_variable ("ignoreeof"); + if (temp_var && imported_p (temp_var)) + sv_ignoreeof (temp_var->name); + } + +#if defined (HISTORY) + if (interactive_shell && remember_on_history) + { + sv_history_control ("HISTCONTROL"); + sv_histignore ("HISTIGNORE"); + sv_histtimefmt ("HISTTIMEFORMAT"); + } +#endif /* HISTORY */ + +#if defined (READLINE) && defined (STRICT_POSIX) + /* POSIXLY_CORRECT will only be 1 here if the shell was compiled + -DSTRICT_POSIX */ + if (interactive_shell && posixly_correct && no_line_editing == 0) + rl_prefer_env_winsize = 1; +#endif /* READLINE && STRICT_POSIX */ + + /* + * 24 October 2001 + * + * I'm tired of the arguing and bug reports. Bash now leaves SSH_CLIENT + * and SSH2_CLIENT alone. I'm going to rely on the shell_level check in + * isnetconn() to avoid running the startup files more often than wanted. + * That will, of course, only work if the user's login shell is bash, so + * I've made that behavior conditional on SSH_SOURCE_BASHRC being defined + * in config-top.h. + */ +#if 0 + temp_var = find_variable ("SSH_CLIENT"); + if (temp_var && imported_p (temp_var)) + { + VUNSETATTR (temp_var, att_exported); + array_needs_making = 1; + } + temp_var = find_variable ("SSH2_CLIENT"); + if (temp_var && imported_p (temp_var)) + { + VUNSETATTR (temp_var, att_exported); + array_needs_making = 1; + } +#endif + + /* Get the user's real and effective user ids. */ + uidset (); + + /* Initialize the dynamic variables, and seed their values. */ + initialize_dynamic_variables (); +} + +/* **************************************************************** */ +/* */ +/* Setting values for special shell variables */ +/* */ +/* **************************************************************** */ + +static void +set_machine_vars () +{ + SHELL_VAR *temp_var; + + temp_var = set_if_not ("HOSTTYPE", HOSTTYPE); + temp_var = set_if_not ("OSTYPE", OSTYPE); + temp_var = set_if_not ("MACHTYPE", MACHTYPE); + + temp_var = set_if_not ("HOSTNAME", current_host_name); +} + +/* Set $HOME to the information in the password file if we didn't get + it from the environment. */ + +/* This function is not static so the tilde and readline libraries can + use it. */ +char * +sh_get_home_dir () +{ + if (current_user.home_dir == 0) + get_current_user_info (); + return current_user.home_dir; +} + +static void +set_home_var () +{ + SHELL_VAR *temp_var; + + temp_var = find_variable ("HOME"); + if (temp_var == 0) + temp_var = bind_variable ("HOME", sh_get_home_dir (), 0); +#if 0 + VSETATTR (temp_var, att_exported); +#endif +} + +/* Set $SHELL to the user's login shell if it is not already set. Call + get_current_user_info if we haven't already fetched the shell. */ +static void +set_shell_var () +{ + SHELL_VAR *temp_var; + + temp_var = find_variable ("SHELL"); + if (temp_var == 0) + { + if (current_user.shell == 0) + get_current_user_info (); + temp_var = bind_variable ("SHELL", current_user.shell, 0); + } +#if 0 + VSETATTR (temp_var, att_exported); +#endif +} + +static char * +get_bash_name () +{ + char *name; + + if ((login_shell == 1) && RELPATH(shell_name)) + { + if (current_user.shell == 0) + get_current_user_info (); + name = savestring (current_user.shell); + } + else if (ABSPATH(shell_name)) + name = savestring (shell_name); + else if (shell_name[0] == '.' && shell_name[1] == '/') + { + /* Fast path for common case. */ + char *cdir; + int len; + + cdir = get_string_value ("PWD"); + if (cdir) + { + len = strlen (cdir); + name = (char *)xmalloc (len + strlen (shell_name) + 1); + strcpy (name, cdir); + strcpy (name + len, shell_name + 1); + } + else + name = savestring (shell_name); + } + else + { + char *tname; + int s; + + tname = find_user_command (shell_name); + + if (tname == 0) + { + /* Try the current directory. If there is not an executable + there, just punt and use the login shell. */ + s = file_status (shell_name); + if (s & FS_EXECABLE) + { + tname = make_absolute (shell_name, get_string_value ("PWD")); + if (*shell_name == '.') + { + name = sh_canonpath (tname, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + if (name == 0) + name = tname; + else + free (tname); + } + else + name = tname; + } + else + { + if (current_user.shell == 0) + get_current_user_info (); + name = savestring (current_user.shell); + } + } + else + { + name = full_pathname (tname); + free (tname); + } + } + + return (name); +} + +void +adjust_shell_level (change) + int change; +{ + char new_level[5], *old_SHLVL; + intmax_t old_level; + SHELL_VAR *temp_var; + + old_SHLVL = get_string_value ("SHLVL"); + if (old_SHLVL == 0 || *old_SHLVL == '\0' || legal_number (old_SHLVL, &old_level) == 0) + old_level = 0; + + shell_level = old_level + change; + if (shell_level < 0) + shell_level = 0; + else if (shell_level > 1000) + { + internal_warning (_("shell level (%d) too high, resetting to 1"), shell_level); + shell_level = 1; + } + + /* We don't need the full generality of itos here. */ + if (shell_level < 10) + { + new_level[0] = shell_level + '0'; + new_level[1] = '\0'; + } + else if (shell_level < 100) + { + new_level[0] = (shell_level / 10) + '0'; + new_level[1] = (shell_level % 10) + '0'; + new_level[2] = '\0'; + } + else if (shell_level < 1000) + { + new_level[0] = (shell_level / 100) + '0'; + old_level = shell_level % 100; + new_level[1] = (old_level / 10) + '0'; + new_level[2] = (old_level % 10) + '0'; + new_level[3] = '\0'; + } + + temp_var = bind_variable ("SHLVL", new_level, 0); + set_auto_export (temp_var); +} + +static void +initialize_shell_level () +{ + adjust_shell_level (1); +} + +/* If we got PWD from the environment, update our idea of the current + working directory. In any case, make sure that PWD exists before + checking it. It is possible for getcwd () to fail on shell startup, + and in that case, PWD would be undefined. If this is an interactive + login shell, see if $HOME is the current working directory, and if + that's not the same string as $PWD, set PWD=$HOME. */ + +void +set_pwd () +{ + SHELL_VAR *temp_var, *home_var; + char *temp_string, *home_string; + + home_var = find_variable ("HOME"); + home_string = home_var ? value_cell (home_var) : (char *)NULL; + + temp_var = find_variable ("PWD"); + if (temp_var && imported_p (temp_var) && + (temp_string = value_cell (temp_var)) && + same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL)) + set_working_directory (temp_string); + else if (home_string && interactive_shell && login_shell && + same_file (home_string, ".", (struct stat *)NULL, (struct stat *)NULL)) + { + set_working_directory (home_string); + temp_var = bind_variable ("PWD", home_string, 0); + set_auto_export (temp_var); + } + else + { + temp_string = get_working_directory ("shell-init"); + if (temp_string) + { + temp_var = bind_variable ("PWD", temp_string, 0); + set_auto_export (temp_var); + free (temp_string); + } + } + + /* According to the Single Unix Specification, v2, $OLDPWD is an + `environment variable' and therefore should be auto-exported. + Make a dummy invisible variable for OLDPWD, and mark it as exported. */ + temp_var = bind_variable ("OLDPWD", (char *)NULL, 0); + VSETATTR (temp_var, (att_exported | att_invisible)); +} + +/* Make a variable $PPID, which holds the pid of the shell's parent. */ +void +set_ppid () +{ + char namebuf[INT_STRLEN_BOUND(pid_t) + 1], *name; + SHELL_VAR *temp_var; + + name = inttostr (getppid (), namebuf, sizeof(namebuf)); + temp_var = find_variable ("PPID"); + if (temp_var) + VUNSETATTR (temp_var, (att_readonly | att_exported)); + temp_var = bind_variable ("PPID", name, 0); + VSETATTR (temp_var, (att_readonly | att_integer)); +} + +static void +uidset () +{ + char buff[INT_STRLEN_BOUND(uid_t) + 1], *b; + register SHELL_VAR *v; + + b = inttostr (current_user.uid, buff, sizeof (buff)); + v = find_variable ("UID"); + if (v == 0) + { + v = bind_variable ("UID", b, 0); + VSETATTR (v, (att_readonly | att_integer)); + } + + if (current_user.euid != current_user.uid) + b = inttostr (current_user.euid, buff, sizeof (buff)); + + v = find_variable ("EUID"); + if (v == 0) + { + v = bind_variable ("EUID", b, 0); + VSETATTR (v, (att_readonly | att_integer)); + } +} + +#if defined (ARRAY_VARS) +static void +make_vers_array () +{ + SHELL_VAR *vv; + ARRAY *av; + char *s, d[32], b[INT_STRLEN_BOUND(int) + 1]; + + unbind_variable ("BASH_VERSINFO"); + + vv = make_new_array_variable ("BASH_VERSINFO"); + av = array_cell (vv); + strcpy (d, dist_version); + s = strchr (d, '.'); + if (s) + *s++ = '\0'; + array_insert (av, 0, d); + array_insert (av, 1, s); + s = inttostr (patch_level, b, sizeof (b)); + array_insert (av, 2, s); + s = inttostr (build_version, b, sizeof (b)); + array_insert (av, 3, s); + array_insert (av, 4, release_status); + array_insert (av, 5, MACHTYPE); + + VSETATTR (vv, att_readonly); +} +#endif /* ARRAY_VARS */ + +/* Set the environment variables $LINES and $COLUMNS in response to + a window size change. */ +void +sh_set_lines_and_columns (lines, cols) + int lines, cols; +{ + char val[INT_STRLEN_BOUND(int) + 1], *v; + +#if defined (READLINE) + /* If we are currently assigning to LINES or COLUMNS, don't do anything. */ + if (winsize_assignment) + return; +#endif + + v = inttostr (lines, val, sizeof (val)); + bind_variable ("LINES", v, 0); + + v = inttostr (cols, val, sizeof (val)); + bind_variable ("COLUMNS", v, 0); +} + +/* **************************************************************** */ +/* */ +/* Printing variables and values */ +/* */ +/* **************************************************************** */ + +/* Print LIST (a list of shell variables) to stdout in such a way that + they can be read back in. */ +void +print_var_list (list) + register SHELL_VAR **list; +{ + register int i; + register SHELL_VAR *var; + + for (i = 0; list && (var = list[i]); i++) + if (invisible_p (var) == 0) + print_assignment (var); +} + +/* Print LIST (a list of shell functions) to stdout in such a way that + they can be read back in. */ +void +print_func_list (list) + register SHELL_VAR **list; +{ + register int i; + register SHELL_VAR *var; + + for (i = 0; list && (var = list[i]); i++) + { + printf ("%s ", var->name); + print_var_function (var); + printf ("\n"); + } +} + +/* Print the value of a single SHELL_VAR. No newline is + output, but the variable is printed in such a way that + it can be read back in. */ +void +print_assignment (var) + SHELL_VAR *var; +{ + if (var_isset (var) == 0) + return; + + if (function_p (var)) + { + printf ("%s", var->name); + print_var_function (var); + printf ("\n"); + } +#if defined (ARRAY_VARS) + else if (array_p (var)) + print_array_assignment (var, 0); + else if (assoc_p (var)) + print_assoc_assignment (var, 0); +#endif /* ARRAY_VARS */ + else + { + printf ("%s=", var->name); + print_var_value (var, 1); + printf ("\n"); + } +} + +/* Print the value cell of VAR, a shell variable. Do not print + the name, nor leading/trailing newline. If QUOTE is non-zero, + and the value contains shell metacharacters, quote the value + in such a way that it can be read back in. */ +void +print_var_value (var, quote) + SHELL_VAR *var; + int quote; +{ + char *t; + + if (var_isset (var) == 0) + return; + + if (quote && posixly_correct == 0 && ansic_shouldquote (value_cell (var))) + { + t = ansic_quote (value_cell (var), 0, (int *)0); + printf ("%s", t); + free (t); + } + else if (quote && sh_contains_shell_metas (value_cell (var))) + { + t = sh_single_quote (value_cell (var)); + printf ("%s", t); + free (t); + } + else + printf ("%s", value_cell (var)); +} + +/* Print the function cell of VAR, a shell variable. Do not + print the name, nor leading/trailing newline. */ +void +print_var_function (var) + SHELL_VAR *var; +{ + char *x; + + if (function_p (var) && var_isset (var)) + { + x = named_function_string ((char *)NULL, function_cell(var), FUNC_MULTILINE|FUNC_EXTERNAL); + printf ("%s", x); + } +} + +/* **************************************************************** */ +/* */ +/* Dynamic Variables */ +/* */ +/* **************************************************************** */ + +/* DYNAMIC VARIABLES + + These are variables whose values are generated anew each time they are + referenced. These are implemented using a pair of function pointers + in the struct variable: assign_func, which is called from bind_variable + and, if arrays are compiled into the shell, some of the functions in + arrayfunc.c, and dynamic_value, which is called from find_variable. + + assign_func is called from bind_variable_internal, if + bind_variable_internal discovers that the variable being assigned to + has such a function. The function is called as + SHELL_VAR *temp = (*(entry->assign_func)) (entry, value, ind) + and the (SHELL_VAR *)temp is returned as the value of bind_variable. It + is usually ENTRY (self). IND is an index for an array variable, and + unused otherwise. + + dynamic_value is called from find_variable_internal to return a `new' + value for the specified dynamic varible. If this function is NULL, + the variable is treated as a `normal' shell variable. If it is not, + however, then this function is called like this: + tempvar = (*(var->dynamic_value)) (var); + + Sometimes `tempvar' will replace the value of `var'. Other times, the + shell will simply use the string value. Pretty object-oriented, huh? + + Be warned, though: if you `unset' a special variable, it loses its + special meaning, even if you subsequently set it. + + The special assignment code would probably have been better put in + subst.c: do_assignment_internal, in the same style as + stupidly_hack_special_variables, but I wanted the changes as + localized as possible. */ + +#define INIT_DYNAMIC_VAR(var, val, gfunc, afunc) \ + do \ + { \ + v = bind_variable (var, (val), 0); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +#define INIT_DYNAMIC_ARRAY_VAR(var, gfunc, afunc) \ + do \ + { \ + v = make_new_array_variable (var); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +#define INIT_DYNAMIC_ASSOC_VAR(var, gfunc, afunc) \ + do \ + { \ + v = make_new_assoc_variable (var); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +static SHELL_VAR * +null_assign (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + return (self); +} + +#if defined (ARRAY_VARS) +static SHELL_VAR * +null_array_assign (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ + return (self); +} +#endif + +/* Degenerate `dynamic_value' function; just returns what's passed without + manipulation. */ +static SHELL_VAR * +get_self (self) + SHELL_VAR *self; +{ + return (self); +} + +#if defined (ARRAY_VARS) +/* A generic dynamic array variable initializer. Intialize array variable + NAME with dynamic value function GETFUNC and assignment function SETFUNC. */ +static SHELL_VAR * +init_dynamic_array_var (name, getfunc, setfunc, attrs) + char *name; + sh_var_value_func_t *getfunc; + sh_var_assign_func_t *setfunc; + int attrs; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v) + return (v); + INIT_DYNAMIC_ARRAY_VAR (name, getfunc, setfunc); + if (attrs) + VSETATTR (v, attrs); + return v; +} + +static SHELL_VAR * +init_dynamic_assoc_var (name, getfunc, setfunc, attrs) + char *name; + sh_var_value_func_t *getfunc; + sh_var_assign_func_t *setfunc; + int attrs; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v) + return (v); + INIT_DYNAMIC_ASSOC_VAR (name, getfunc, setfunc); + if (attrs) + VSETATTR (v, attrs); + return v; +} +#endif + +/* The value of $SECONDS. This is the number of seconds since shell + invocation, or, the number of seconds since the last assignment + the + value of the last assignment. */ +static intmax_t seconds_value_assigned; + +static SHELL_VAR * +assign_seconds (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + if (legal_number (value, &seconds_value_assigned) == 0) + seconds_value_assigned = 0; + shell_start_time = NOW; + return (self); +} + +static SHELL_VAR * +get_seconds (var) + SHELL_VAR *var; +{ + time_t time_since_start; + char *p; + + time_since_start = NOW - shell_start_time; + p = itos(seconds_value_assigned + time_since_start); + + FREE (value_cell (var)); + + VSETATTR (var, att_integer); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +init_seconds_var () +{ + SHELL_VAR *v; + + v = find_variable ("SECONDS"); + if (v) + { + if (legal_number (value_cell(v), &seconds_value_assigned) == 0) + seconds_value_assigned = 0; + } + INIT_DYNAMIC_VAR ("SECONDS", (v ? value_cell (v) : (char *)NULL), get_seconds, assign_seconds); + return v; +} + +/* The random number seed. You can change this by setting RANDOM. */ +static unsigned long rseed = 1; +static int last_random_value; +static int seeded_subshell = 0; + +/* A linear congruential random number generator based on the example + one in the ANSI C standard. This one isn't very good, but a more + complicated one is overkill. */ + +/* Returns a pseudo-random number between 0 and 32767. */ +static int +brand () +{ +#if 0 + rseed = rseed * 1103515245 + 12345; + return ((unsigned int)((rseed >> 16) & 32767)); /* was % 32768 */ +#else + /* From "Random number generators: good ones are hard to find", + Park and Miller, Communications of the ACM, vol. 31, no. 10, + October 1988, p. 1195. filtered through FreeBSD */ + long h, l; + + if (rseed == 0) + seedrand (); + h = rseed / 127773; + l = rseed % 127773; + rseed = 16807 * l - 2836 * h; +#if 0 + if (rseed < 0) + rseed += 0x7fffffff; +#endif + return ((unsigned int)(rseed & 32767)); /* was % 32768 */ +#endif +} + +/* Set the random number generator seed to SEED. */ +static void +sbrand (seed) + unsigned long seed; +{ + rseed = seed; + last_random_value = 0; +} + +static void +seedrand () +{ + struct timeval tv; + + gettimeofday (&tv, NULL); + sbrand (tv.tv_sec ^ tv.tv_usec ^ getpid ()); +} + +static SHELL_VAR * +assign_random (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + sbrand (strtoul (value, (char **)NULL, 10)); + if (subshell_environment) + seeded_subshell = getpid (); + return (self); +} + +int +get_random_number () +{ + int rv, pid; + + /* Reset for command and process substitution. */ + pid = getpid (); + if (subshell_environment && seeded_subshell != pid) + { + seedrand (); + seeded_subshell = pid; + } + + do + rv = brand (); + while (rv == last_random_value); + return rv; +} + +static SHELL_VAR * +get_random (var) + SHELL_VAR *var; +{ + int rv; + char *p; + + rv = get_random_number (); + last_random_value = rv; +fprintf(stderr, "get_random: rv = %d\n", rv); + p = itos (rv); + + FREE (value_cell (var)); + + VSETATTR (var, att_integer); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +assign_lineno (var, value, unused, key) + SHELL_VAR *var; + char *value; + arrayind_t unused; + char *key; +{ + intmax_t new_value; + + if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0) + new_value = 0; + line_number = new_value; + return var; +} + +/* Function which returns the current line number. */ +static SHELL_VAR * +get_lineno (var) + SHELL_VAR *var; +{ + char *p; + int ln; + + ln = executing_line_number (); + p = itos (ln); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +assign_subshell (var, value, unused, key) + SHELL_VAR *var; + char *value; + arrayind_t unused; + char *key; +{ + intmax_t new_value; + + if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0) + new_value = 0; + subshell_level = new_value; + return var; +} + +static SHELL_VAR * +get_subshell (var) + SHELL_VAR *var; +{ + char *p; + + p = itos (subshell_level); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +get_bashpid (var) + SHELL_VAR *var; +{ + int pid; + char *p; + + pid = getpid (); + p = itos (pid); + + FREE (value_cell (var)); + VSETATTR (var, att_integer|att_readonly); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +get_bash_command (var) + SHELL_VAR *var; +{ + char *p; + + if (the_printed_command_except_trap) + p = savestring (the_printed_command_except_trap); + else + { + p = (char *)xmalloc (1); + p[0] = '\0'; + } + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +#if defined (HISTORY) +static SHELL_VAR * +get_histcmd (var) + SHELL_VAR *var; +{ + char *p; + + p = itos (history_number ()); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} +#endif + +#if defined (READLINE) +/* When this function returns, VAR->value points to malloced memory. */ +static SHELL_VAR * +get_comp_wordbreaks (var) + SHELL_VAR *var; +{ + /* If we don't have anything yet, assign a default value. */ + if (rl_completer_word_break_characters == 0 && bash_readline_initialized == 0) + enable_hostname_completion (perform_hostname_completion); + + FREE (value_cell (var)); + var_setvalue (var, savestring (rl_completer_word_break_characters)); + + return (var); +} + +/* When this function returns, rl_completer_word_break_characters points to + malloced memory. */ +static SHELL_VAR * +assign_comp_wordbreaks (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + if (rl_completer_word_break_characters && + rl_completer_word_break_characters != rl_basic_word_break_characters) + free (rl_completer_word_break_characters); + + rl_completer_word_break_characters = savestring (value); + return self; +} +#endif /* READLINE */ + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) +static SHELL_VAR * +assign_dirstack (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ + set_dirstack_element (ind, 1, value); + return self; +} + +static SHELL_VAR * +get_dirstack (self) + SHELL_VAR *self; +{ + ARRAY *a; + WORD_LIST *l; + + l = get_directory_stack (0); + a = array_from_word_list (l); + array_dispose (array_cell (self)); + dispose_words (l); + var_setarray (self, a); + return self; +} +#endif /* PUSHD AND POPD && ARRAY_VARS */ + +#if defined (ARRAY_VARS) +/* We don't want to initialize the group set with a call to getgroups() + unless we're asked to, but we only want to do it once. */ +static SHELL_VAR * +get_groupset (self) + SHELL_VAR *self; +{ + register int i; + int ng; + ARRAY *a; + static char **group_set = (char **)NULL; + + if (group_set == 0) + { + group_set = get_group_list (&ng); + a = array_cell (self); + for (i = 0; i < ng; i++) + array_insert (a, i, group_set[i]); + } + return (self); +} + +static SHELL_VAR * +build_hashcmd (self) + SHELL_VAR *self; +{ + HASH_TABLE *h; + int i; + char *k, *v; + BUCKET_CONTENTS *item; + + h = assoc_cell (self); + if (h) + assoc_dispose (h); + + if (hashed_filenames == 0 || HASH_ENTRIES (hashed_filenames) == 0) + { + var_setvalue (self, (char *)NULL); + return self; + } + + h = assoc_create (hashed_filenames->nbuckets); + for (i = 0; i < hashed_filenames->nbuckets; i++) + { + for (item = hash_items (i, hashed_filenames); item; item = item->next) + { + k = savestring (item->key); + v = pathdata(item)->path; + assoc_insert (h, k, v); + } + } + + var_setvalue (self, (char *)h); + return self; +} + +static SHELL_VAR * +get_hashcmd (self) + SHELL_VAR *self; +{ + build_hashcmd (self); + return (self); +} + +static SHELL_VAR * +assign_hashcmd (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ + phash_insert (key, value, 0, 0); + return (build_hashcmd (self)); +} + +#if defined (ALIAS) +static SHELL_VAR * +build_aliasvar (self) + SHELL_VAR *self; +{ + HASH_TABLE *h; + int i; + char *k, *v; + BUCKET_CONTENTS *item; + + h = assoc_cell (self); + if (h) + assoc_dispose (h); + + if (aliases == 0 || HASH_ENTRIES (aliases) == 0) + { + var_setvalue (self, (char *)NULL); + return self; + } + + h = assoc_create (aliases->nbuckets); + for (i = 0; i < aliases->nbuckets; i++) + { + for (item = hash_items (i, aliases); item; item = item->next) + { + k = savestring (item->key); + v = ((alias_t *)(item->data))->value; + assoc_insert (h, k, v); + } + } + + var_setvalue (self, (char *)h); + return self; +} + +static SHELL_VAR * +get_aliasvar (self) + SHELL_VAR *self; +{ + build_aliasvar (self); + return (self); +} + +static SHELL_VAR * +assign_aliasvar (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ + add_alias (key, value); + return (build_aliasvar (self)); +} +#endif /* ALIAS */ + +#endif /* ARRAY_VARS */ + +/* If ARRAY_VARS is not defined, this just returns the name of any + currently-executing function. If we have arrays, it's a call stack. */ +static SHELL_VAR * +get_funcname (self) + SHELL_VAR *self; +{ +#if ! defined (ARRAY_VARS) + char *t; + if (variable_context && this_shell_function) + { + FREE (value_cell (self)); + t = savestring (this_shell_function->name); + var_setvalue (self, t); + } +#endif + return (self); +} + +void +make_funcname_visible (on_or_off) + int on_or_off; +{ + SHELL_VAR *v; + + v = find_variable ("FUNCNAME"); + if (v == 0 || v->dynamic_value == 0) + return; + + if (on_or_off) + VUNSETATTR (v, att_invisible); + else + VSETATTR (v, att_invisible); +} + +static SHELL_VAR * +init_funcname_var () +{ + SHELL_VAR *v; + + v = find_variable ("FUNCNAME"); + if (v) + return v; +#if defined (ARRAY_VARS) + INIT_DYNAMIC_ARRAY_VAR ("FUNCNAME", get_funcname, null_array_assign); +#else + INIT_DYNAMIC_VAR ("FUNCNAME", (char *)NULL, get_funcname, null_assign); +#endif + VSETATTR (v, att_invisible|att_noassign); + return v; +} + +static void +initialize_dynamic_variables () +{ + SHELL_VAR *v; + + v = init_seconds_var (); + + INIT_DYNAMIC_VAR ("BASH_COMMAND", (char *)NULL, get_bash_command, (sh_var_assign_func_t *)NULL); + INIT_DYNAMIC_VAR ("BASH_SUBSHELL", (char *)NULL, get_subshell, assign_subshell); + + INIT_DYNAMIC_VAR ("RANDOM", (char *)NULL, get_random, assign_random); + VSETATTR (v, att_integer); + INIT_DYNAMIC_VAR ("LINENO", (char *)NULL, get_lineno, assign_lineno); + VSETATTR (v, att_integer); + + INIT_DYNAMIC_VAR ("BASHPID", (char *)NULL, get_bashpid, null_assign); + VSETATTR (v, att_integer|att_readonly); + +#if defined (HISTORY) + INIT_DYNAMIC_VAR ("HISTCMD", (char *)NULL, get_histcmd, (sh_var_assign_func_t *)NULL); + VSETATTR (v, att_integer); +#endif + +#if defined (READLINE) + INIT_DYNAMIC_VAR ("COMP_WORDBREAKS", (char *)NULL, get_comp_wordbreaks, assign_comp_wordbreaks); +#endif + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) + v = init_dynamic_array_var ("DIRSTACK", get_dirstack, assign_dirstack, 0); +#endif /* PUSHD_AND_POPD && ARRAY_VARS */ + +#if defined (ARRAY_VARS) + v = init_dynamic_array_var ("GROUPS", get_groupset, null_array_assign, att_noassign); + +# if defined (DEBUGGER) + v = init_dynamic_array_var ("BASH_ARGC", get_self, null_array_assign, att_noassign|att_nounset); + v = init_dynamic_array_var ("BASH_ARGV", get_self, null_array_assign, att_noassign|att_nounset); +# endif /* DEBUGGER */ + v = init_dynamic_array_var ("BASH_SOURCE", get_self, null_array_assign, att_noassign|att_nounset); + v = init_dynamic_array_var ("BASH_LINENO", get_self, null_array_assign, att_noassign|att_nounset); + + v = init_dynamic_assoc_var ("BASH_CMDS", get_hashcmd, assign_hashcmd, att_nofree); +# if defined (ALIAS) + v = init_dynamic_assoc_var ("BASH_ALIASES", get_aliasvar, assign_aliasvar, att_nofree); +# endif +#endif + + v = init_funcname_var (); +} + +/* **************************************************************** */ +/* */ +/* Retrieving variables and values */ +/* */ +/* **************************************************************** */ + +/* How to get a pointer to the shell variable or function named NAME. + HASHED_VARS is a pointer to the hash table containing the list + of interest (either variables or functions). */ + +static SHELL_VAR * +hash_lookup (name, hashed_vars) + const char *name; + HASH_TABLE *hashed_vars; +{ + BUCKET_CONTENTS *bucket; + + bucket = hash_search (name, hashed_vars, 0); + return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL); +} + +SHELL_VAR * +var_lookup (name, vcontext) + const char *name; + VAR_CONTEXT *vcontext; +{ + VAR_CONTEXT *vc; + SHELL_VAR *v; + + v = (SHELL_VAR *)NULL; + for (vc = vcontext; vc; vc = vc->down) + if (v = hash_lookup (name, vc->table)) + break; + + return v; +} + +/* Look up the variable entry named NAME. If SEARCH_TEMPENV is non-zero, + then also search the temporarily built list of exported variables. + The lookup order is: + temporary_env + shell_variables list +*/ + +SHELL_VAR * +find_variable_internal (name, force_tempenv) + const char *name; + int force_tempenv; +{ + SHELL_VAR *var; + int search_tempenv; + + var = (SHELL_VAR *)NULL; + + /* If explicitly requested, first look in the temporary environment for + the variable. This allows constructs such as "foo=x eval 'echo $foo'" + to get the `exported' value of $foo. This happens if we are executing + a function or builtin, or if we are looking up a variable in a + "subshell environment". */ + search_tempenv = force_tempenv || (expanding_redir == 0 && subshell_environment); + + if (search_tempenv && temporary_env) + var = hash_lookup (name, temporary_env); + + if (var == 0) + var = var_lookup (name, shell_variables); + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +/* Look up the variable entry named NAME. Returns the entry or NULL. */ +SHELL_VAR * +find_variable (name) + const char *name; +{ + return (find_variable_internal (name, (expanding_redir == 0 && (assigning_in_environment || executing_builtin)))); +} + +/* Look up the function entry whose name matches STRING. + Returns the entry or NULL. */ +SHELL_VAR * +find_function (name) + const char *name; +{ + return (hash_lookup (name, shell_functions)); +} + +/* Find the function definition for the shell function named NAME. Returns + the entry or NULL. */ +FUNCTION_DEF * +find_function_def (name) + const char *name; +{ +#if defined (DEBUGGER) + return ((FUNCTION_DEF *)hash_lookup (name, shell_function_defs)); +#else + return ((FUNCTION_DEF *)0); +#endif +} + +/* Return the value of VAR. VAR is assumed to have been the result of a + lookup without any subscript, if arrays are compiled into the shell. */ +char * +get_variable_value (var) + SHELL_VAR *var; +{ + if (var == 0) + return ((char *)NULL); +#if defined (ARRAY_VARS) + else if (array_p (var)) + return (array_reference (array_cell (var), 0)); + else if (assoc_p (var)) + return (assoc_reference (assoc_cell (var), "0")); +#endif + else + return (value_cell (var)); +} + +/* Return the string value of a variable. Return NULL if the variable + doesn't exist. Don't cons a new string. This is a potential memory + leak if the variable is found in the temporary environment. Since + functions and variables have separate name spaces, returns NULL if + var_name is a shell function only. */ +char * +get_string_value (var_name) + const char *var_name; +{ + SHELL_VAR *var; + + var = find_variable (var_name); + return ((var) ? get_variable_value (var) : (char *)NULL); +} + +/* This is present for use by the tilde and readline libraries. */ +char * +sh_get_env_value (v) + const char *v; +{ + return get_string_value (v); +} + +/* **************************************************************** */ +/* */ +/* Creating and setting variables */ +/* */ +/* **************************************************************** */ + +/* Set NAME to VALUE if NAME has no value. */ +SHELL_VAR * +set_if_not (name, value) + char *name, *value; +{ + SHELL_VAR *v; + + if (shell_variables == 0) + create_variable_tables (); + + v = find_variable (name); + if (v == 0) + v = bind_variable_internal (name, value, global_variables->table, HASH_NOSRCH, 0); + return (v); +} + +/* Create a local variable referenced by NAME. */ +SHELL_VAR * +make_local_variable (name) + const char *name; +{ + SHELL_VAR *new_var, *old_var; + VAR_CONTEXT *vc; + int was_tmpvar; + char *tmp_value; + + /* local foo; local foo; is a no-op. */ + old_var = find_variable (name); + if (old_var && local_p (old_var) && old_var->context == variable_context) + { + VUNSETATTR (old_var, att_invisible); + return (old_var); + } + + was_tmpvar = old_var && tempvar_p (old_var); + if (was_tmpvar) + tmp_value = value_cell (old_var); + + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + + if (vc == 0) + { + internal_error (_("make_local_variable: no function context at current scope")); + return ((SHELL_VAR *)NULL); + } + else if (vc->table == 0) + vc->table = hash_create (TEMPENV_HASH_BUCKETS); + + /* Since this is called only from the local/declare/typeset code, we can + call builtin_error here without worry (of course, it will also work + for anything that sets this_command_name). Variables with the `noassign' + attribute may not be made local. The test against old_var's context + level is to disallow local copies of readonly global variables (since I + believe that this could be a security hole). Readonly copies of calling + function local variables are OK. */ + if (old_var && (noassign_p (old_var) || + (readonly_p (old_var) && old_var->context == 0))) + { + if (readonly_p (old_var)) + sh_readonly (name); + return ((SHELL_VAR *)NULL); + } + + if (old_var == 0) + new_var = make_new_variable (name, vc->table); + else + { + new_var = make_new_variable (name, vc->table); + + /* If we found this variable in one of the temporary environments, + inherit its value. Watch to see if this causes problems with + things like `x=4 local x'. */ + if (was_tmpvar) + var_setvalue (new_var, savestring (tmp_value)); + + new_var->attributes = exported_p (old_var) ? att_exported : 0; + } + + vc->flags |= VC_HASLOCAL; + + new_var->context = variable_context; + VSETATTR (new_var, att_local); + + if (ifsname (name)) + setifs (new_var); + + return (new_var); +} + +/* Create a new shell variable with name NAME. */ +static SHELL_VAR * +new_shell_variable (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); + + entry->name = savestring (name); + var_setvalue (entry, (char *)NULL); + CLEAR_EXPORTSTR (entry); + + entry->dynamic_value = (sh_var_value_func_t *)NULL; + entry->assign_func = (sh_var_assign_func_t *)NULL; + + entry->attributes = 0; + + /* Always assume variables are to be made at toplevel! + make_local_variable has the responsibilty of changing the + variable context. */ + entry->context = 0; + + return (entry); +} + +/* Create a new shell variable with name NAME and add it to the hash table + TABLE. */ +static SHELL_VAR * +make_new_variable (name, table) + const char *name; + HASH_TABLE *table; +{ + SHELL_VAR *entry; + BUCKET_CONTENTS *elt; + + entry = new_shell_variable (name); + + /* Make sure we have a shell_variables hash table to add to. */ + if (shell_variables == 0) + create_variable_tables (); + + elt = hash_insert (savestring (name), table, HASH_NOSRCH); + elt->data = (PTR_T)entry; + + return entry; +} + +#if defined (ARRAY_VARS) +SHELL_VAR * +make_new_array_variable (name) + char *name; +{ + SHELL_VAR *entry; + ARRAY *array; + + entry = make_new_variable (name, global_variables->table); + array = array_create (); + + var_setarray (entry, array); + VSETATTR (entry, att_array); + return entry; +} + +SHELL_VAR * +make_local_array_variable (name) + char *name; +{ + SHELL_VAR *var; + ARRAY *array; + + var = make_local_variable (name); + if (var == 0 || array_p (var)) + return var; + + array = array_create (); + + dispose_variable_value (var); + var_setarray (var, array); + VSETATTR (var, att_array); + return var; +} + +SHELL_VAR * +make_new_assoc_variable (name) + char *name; +{ + SHELL_VAR *entry; + HASH_TABLE *hash; + + entry = make_new_variable (name, global_variables->table); + hash = assoc_create (0); + + var_setassoc (entry, hash); + VSETATTR (entry, att_assoc); + return entry; +} + +SHELL_VAR * +make_local_assoc_variable (name) + char *name; +{ + SHELL_VAR *var; + HASH_TABLE *hash; + + var = make_local_variable (name); + if (var == 0 || assoc_p (var)) + return var; + + dispose_variable_value (var); + hash = assoc_create (0); + + var_setassoc (var, hash); + VSETATTR (var, att_assoc); + return var; +} +#endif + +char * +make_variable_value (var, value, flags) + SHELL_VAR *var; + char *value; + int flags; +{ + char *retval, *oval; + intmax_t lval, rval; + int expok, olen, op; + + /* If this variable has had its type set to integer (via `declare -i'), + then do expression evaluation on it and store the result. The + functions in expr.c (evalexp()) and bind_int_variable() are responsible + for turning off the integer flag if they don't want further + evaluation done. */ + if (integer_p (var)) + { + if (flags & ASS_APPEND) + { + oval = value_cell (var); + lval = evalexp (oval, &expok); /* ksh93 seems to do this */ + if (expok == 0) + { + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + } + rval = evalexp (value, &expok); + if (expok == 0) + { + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + if (flags & ASS_APPEND) + rval += lval; + retval = itos (rval); + } +#if defined (CASEMOD_ATTRS) + else if (capcase_p (var) || uppercase_p (var) || lowercase_p (var)) + { + if (flags & ASS_APPEND) + { + oval = get_variable_value (var); + if (oval == 0) /* paranoia */ + oval = ""; + olen = STRLEN (oval); + retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1); + strcpy (retval, oval); + if (value) + strcpy (retval+olen, value); + } + else if (*value) + retval = savestring (value); + else + { + retval = (char *)xmalloc (1); + retval[0] = '\0'; + } + op = capcase_p (var) ? CASE_CAPITALIZE + : (uppercase_p (var) ? CASE_UPPER : CASE_LOWER); + oval = sh_modcase (retval, (char *)0, op); + free (retval); + retval = oval; + } +#endif /* CASEMOD_ATTRS */ + else if (value) + { + if (flags & ASS_APPEND) + { + oval = get_variable_value (var); + if (oval == 0) /* paranoia */ + oval = ""; + olen = STRLEN (oval); + retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1); + strcpy (retval, oval); + if (value) + strcpy (retval+olen, value); + } + else if (*value) + retval = savestring (value); + else + { + retval = (char *)xmalloc (1); + retval[0] = '\0'; + } + } + else + retval = (char *)NULL; + + return retval; +} + +/* Bind a variable NAME to VALUE in the HASH_TABLE TABLE, which may be the + temporary environment (but usually is not). */ +static SHELL_VAR * +bind_variable_internal (name, value, table, hflags, aflags) + const char *name; + char *value; + HASH_TABLE *table; + int hflags, aflags; +{ + char *newval; + SHELL_VAR *entry; + + entry = (hflags & HASH_NOSRCH) ? (SHELL_VAR *)NULL : hash_lookup (name, table); + + if (entry == 0) + { + entry = make_new_variable (name, table); + var_setvalue (entry, make_variable_value (entry, value, 0)); /* XXX */ + } + else if (entry->assign_func) /* array vars have assign functions now */ + { + INVALIDATE_EXPORTSTR (entry); + newval = (aflags & ASS_APPEND) ? make_variable_value (entry, value, aflags) : value; + if (assoc_p (entry)) + entry = (*(entry->assign_func)) (entry, newval, -1, savestring ("0")); + else if (array_p (entry)) + entry = (*(entry->assign_func)) (entry, newval, 0, 0); + else + entry = (*(entry->assign_func)) (entry, newval, -1, 0); + if (newval != value) + free (newval); + return (entry); + } + else + { + if (readonly_p (entry) || noassign_p (entry)) + { + if (readonly_p (entry)) + err_readonly (name); + return (entry); + } + + /* Variables which are bound are visible. */ + VUNSETATTR (entry, att_invisible); + + newval = make_variable_value (entry, value, aflags); /* XXX */ + + /* Invalidate any cached export string */ + INVALIDATE_EXPORTSTR (entry); + +#if defined (ARRAY_VARS) + /* XXX -- this bears looking at again -- XXX */ + /* If an existing array variable x is being assigned to with x=b or + `read x' or something of that nature, silently convert it to + x[0]=b or `read x[0]'. */ + if (array_p (entry)) + { + array_insert (array_cell (entry), 0, newval); + free (newval); + } + else if (assoc_p (entry)) + { + assoc_insert (assoc_cell (entry), savestring ("0"), newval); + free (newval); + } + else +#endif + { + FREE (value_cell (entry)); + var_setvalue (entry, newval); + } + } + + if (mark_modified_vars) + VSETATTR (entry, att_exported); + + if (exported_p (entry)) + array_needs_making = 1; + + return (entry); +} + +/* Bind a variable NAME to VALUE. This conses up the name + and value strings. If we have a temporary environment, we bind there + first, then we bind into shell_variables. */ + +SHELL_VAR * +bind_variable (name, value, flags) + const char *name; + char *value; + int flags; +{ + SHELL_VAR *v; + VAR_CONTEXT *vc; + + if (shell_variables == 0) + create_variable_tables (); + + /* If we have a temporary environment, look there first for the variable, + and, if found, modify the value there before modifying it in the + shell_variables table. This allows sourced scripts to modify values + given to them in a temporary environment while modifying the variable + value that the caller sees. */ + if (temporary_env) + bind_tempenv_variable (name, value); + + /* XXX -- handle local variables here. */ + for (vc = shell_variables; vc; vc = vc->down) + { + if (vc_isfuncenv (vc) || vc_isbltnenv (vc)) + { + v = hash_lookup (name, vc->table); + if (v) + return (bind_variable_internal (name, value, vc->table, 0, flags)); + } + } + return (bind_variable_internal (name, value, global_variables->table, 0, flags)); +} + +/* Make VAR, a simple shell variable, have value VALUE. Once assigned a + value, variables are no longer invisible. This is a duplicate of part + of the internals of bind_variable. If the variable is exported, or + all modified variables should be exported, mark the variable for export + and note that the export environment needs to be recreated. */ +SHELL_VAR * +bind_variable_value (var, value, aflags) + SHELL_VAR *var; + char *value; + int aflags; +{ + char *t; + + VUNSETATTR (var, att_invisible); + + if (var->assign_func) + { + /* If we're appending, we need the old value, so use + make_variable_value */ + t = (aflags & ASS_APPEND) ? make_variable_value (var, value, aflags) : value; + (*(var->assign_func)) (var, t, -1, 0); + if (t != value && t) + free (t); + } + else + { + t = make_variable_value (var, value, aflags); + FREE (value_cell (var)); + var_setvalue (var, t); + } + + INVALIDATE_EXPORTSTR (var); + + if (mark_modified_vars) + VSETATTR (var, att_exported); + + if (exported_p (var)) + array_needs_making = 1; + + return (var); +} + +/* Bind/create a shell variable with the name LHS to the RHS. + This creates or modifies a variable such that it is an integer. + + This used to be in expr.c, but it is here so that all of the + variable binding stuff is localized. Since we don't want any + recursive evaluation from bind_variable() (possible without this code, + since bind_variable() calls the evaluator for variables with the integer + attribute set), we temporarily turn off the integer attribute for each + variable we set here, then turn it back on after binding as necessary. */ + +SHELL_VAR * +bind_int_variable (lhs, rhs) + char *lhs, *rhs; +{ + register SHELL_VAR *v; + int isint, isarr; + + isint = isarr = 0; +#if defined (ARRAY_VARS) + if (valid_array_reference (lhs)) + { + isarr = 1; + v = array_variable_part (lhs, (char **)0, (int *)0); + } + else +#endif + v = find_variable (lhs); + + if (v) + { + isint = integer_p (v); + VUNSETATTR (v, att_integer); + } + +#if defined (ARRAY_VARS) + if (isarr) + v = assign_array_element (lhs, rhs, 0); + else +#endif + v = bind_variable (lhs, rhs, 0); + + if (isint) + VSETATTR (v, att_integer); + + return (v); +} + +SHELL_VAR * +bind_var_to_int (var, val) + char *var; + intmax_t val; +{ + char ibuf[INT_STRLEN_BOUND (intmax_t) + 1], *p; + + p = fmtulong (val, 10, ibuf, sizeof (ibuf), 0); + return (bind_int_variable (var, p)); +} + +/* Do a function binding to a variable. You pass the name and + the command to bind to. This conses the name and command. */ +SHELL_VAR * +bind_function (name, value) + const char *name; + COMMAND *value; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry == 0) + { + BUCKET_CONTENTS *elt; + + elt = hash_insert (savestring (name), shell_functions, HASH_NOSRCH); + entry = new_shell_variable (name); + elt->data = (PTR_T)entry; + } + else + INVALIDATE_EXPORTSTR (entry); + + if (var_isset (entry)) + dispose_command (function_cell (entry)); + + if (value) + var_setfunc (entry, copy_command (value)); + else + var_setfunc (entry, 0); + + VSETATTR (entry, att_function); + + if (mark_modified_vars) + VSETATTR (entry, att_exported); + + VUNSETATTR (entry, att_invisible); /* Just to be sure */ + + if (exported_p (entry)) + array_needs_making = 1; + +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_functions); +#endif + + return (entry); +} + +#if defined (DEBUGGER) +/* Bind a function definition, which includes source file and line number + information in addition to the command, into the FUNCTION_DEF hash table.*/ +void +bind_function_def (name, value) + const char *name; + FUNCTION_DEF *value; +{ + FUNCTION_DEF *entry; + BUCKET_CONTENTS *elt; + COMMAND *cmd; + + entry = find_function_def (name); + if (entry) + { + dispose_function_def_contents (entry); + entry = copy_function_def_contents (value, entry); + } + else + { + cmd = value->command; + value->command = 0; + entry = copy_function_def (value); + value->command = cmd; + + elt = hash_insert (savestring (name), shell_function_defs, HASH_NOSRCH); + elt->data = (PTR_T *)entry; + } +} +#endif /* DEBUGGER */ + +/* Add STRING, which is of the form foo=bar, to the temporary environment + HASH_TABLE (temporary_env). The functions in execute_cmd.c are + responsible for moving the main temporary env to one of the other + temporary environments. The expansion code in subst.c calls this. */ +int +assign_in_env (word) + WORD_DESC *word; +{ + int offset; + char *name, *temp, *value; + SHELL_VAR *var; + const char *string; + + string = word->word; + + offset = assignment (string, 0); + name = savestring (string); + value = (char *)NULL; + + if (name[offset] == '=') + { + name[offset] = 0; + + /* ignore the `+' when assigning temporary environment */ + if (name[offset - 1] == '+') + name[offset - 1] = '\0'; + + var = find_variable (name); + if (var && (readonly_p (var) || noassign_p (var))) + { + if (readonly_p (var)) + err_readonly (name); + free (name); + return (0); + } + + temp = name + offset + 1; + value = expand_assignment_string_to_string (temp, 0); + } + + if (temporary_env == 0) + temporary_env = hash_create (TEMPENV_HASH_BUCKETS); + + var = hash_lookup (name, temporary_env); + if (var == 0) + var = make_new_variable (name, temporary_env); + else + FREE (value_cell (var)); + + if (value == 0) + { + value = (char *)xmalloc (1); /* like do_assignment_internal */ + value[0] = '\0'; + } + + var_setvalue (var, value); + var->attributes |= (att_exported|att_tempvar); + var->context = variable_context; /* XXX */ + + INVALIDATE_EXPORTSTR (var); + var->exportstr = mk_env_string (name, value); + + array_needs_making = 1; + + if (ifsname (name)) + setifs (var); + + if (echo_command_at_execute) + /* The Korn shell prints the `+ ' in front of assignment statements, + so we do too. */ + xtrace_print_assignment (name, value, 0, 1); + + free (name); + return 1; +} + +/* **************************************************************** */ +/* */ +/* Copying variables */ +/* */ +/* **************************************************************** */ + +#ifdef INCLUDE_UNUSED +/* Copy VAR to a new data structure and return that structure. */ +SHELL_VAR * +copy_variable (var) + SHELL_VAR *var; +{ + SHELL_VAR *copy = (SHELL_VAR *)NULL; + + if (var) + { + copy = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); + + copy->attributes = var->attributes; + copy->name = savestring (var->name); + + if (function_p (var)) + var_setfunc (copy, copy_command (function_cell (var))); +#if defined (ARRAY_VARS) + else if (array_p (var)) + var_setarray (copy, array_copy (array_cell (var))); + else if (assoc_p (var)) + var_setassoc (copy, assoc_copy (assoc_cell (var))); +#endif + else if (value_cell (var)) + var_setvalue (copy, savestring (value_cell (var))); + else + var_setvalue (copy, (char *)NULL); + + copy->dynamic_value = var->dynamic_value; + copy->assign_func = var->assign_func; + + copy->exportstr = COPY_EXPORTSTR (var); + + copy->context = var->context; + } + return (copy); +} +#endif + +/* **************************************************************** */ +/* */ +/* Deleting and unsetting variables */ +/* */ +/* **************************************************************** */ + +/* Dispose of the information attached to VAR. */ +static void +dispose_variable_value (var) + SHELL_VAR *var; +{ + if (function_p (var)) + dispose_command (function_cell (var)); +#if defined (ARRAY_VARS) + else if (array_p (var)) + array_dispose (array_cell (var)); + else if (assoc_p (var)) + assoc_dispose (assoc_cell (var)); +#endif + else + FREE (value_cell (var)); +} + +void +dispose_variable (var) + SHELL_VAR *var; +{ + if (var == 0) + return; + + if (nofree_p (var) == 0) + dispose_variable_value (var); + + FREE_EXPORTSTR (var); + + free (var->name); + + if (exported_p (var)) + array_needs_making = 1; + + free (var); +} + +/* Unset the shell variable referenced by NAME. */ +int +unbind_variable (name) + const char *name; +{ + return makunbound (name, shell_variables); +} + +/* Unset the shell function named NAME. */ +int +unbind_func (name) + const char *name; +{ + BUCKET_CONTENTS *elt; + SHELL_VAR *func; + + elt = hash_remove (name, shell_functions, 0); + + if (elt == 0) + return -1; + +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_functions); +#endif + + func = (SHELL_VAR *)elt->data; + if (func) + { + if (exported_p (func)) + array_needs_making++; + dispose_variable (func); + } + + free (elt->key); + free (elt); + + return 0; +} + +#if defined (DEBUGGER) +int +unbind_function_def (name) + const char *name; +{ + BUCKET_CONTENTS *elt; + FUNCTION_DEF *funcdef; + + elt = hash_remove (name, shell_function_defs, 0); + + if (elt == 0) + return -1; + + funcdef = (FUNCTION_DEF *)elt->data; + if (funcdef) + dispose_function_def (funcdef); + + free (elt->key); + free (elt); + + return 0; +} +#endif /* DEBUGGER */ + +/* Make the variable associated with NAME go away. HASH_LIST is the + hash table from which this variable should be deleted (either + shell_variables or shell_functions). + Returns non-zero if the variable couldn't be found. */ +int +makunbound (name, vc) + const char *name; + VAR_CONTEXT *vc; +{ + BUCKET_CONTENTS *elt, *new_elt; + SHELL_VAR *old_var; + VAR_CONTEXT *v; + char *t; + + for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down) + if (elt = hash_remove (name, v->table, 0)) + break; + + if (elt == 0) + return (-1); + + old_var = (SHELL_VAR *)elt->data; + + if (old_var && exported_p (old_var)) + array_needs_making++; + + /* If we're unsetting a local variable and we're still executing inside + the function, just mark the variable as invisible. The function + eventually called by pop_var_context() will clean it up later. This + must be done so that if the variable is subsequently assigned a new + value inside the function, the `local' attribute is still present. + We also need to add it back into the correct hash table. */ + if (old_var && local_p (old_var) && variable_context == old_var->context) + { + if (nofree_p (old_var)) + var_setvalue (old_var, (char *)NULL); +#if defined (ARRAY_VARS) + else if (array_p (old_var)) + array_dispose (array_cell (old_var)); + else if (assoc_p (old_var)) + assoc_dispose (assoc_cell (old_var)); +#endif + else + FREE (value_cell (old_var)); + /* Reset the attributes. Preserve the export attribute if the variable + came from a temporary environment. Make sure it stays local, and + make it invisible. */ + old_var->attributes = (exported_p (old_var) && tempvar_p (old_var)) ? att_exported : 0; + VSETATTR (old_var, att_local); + VSETATTR (old_var, att_invisible); + var_setvalue (old_var, (char *)NULL); + INVALIDATE_EXPORTSTR (old_var); + + new_elt = hash_insert (savestring (old_var->name), v->table, 0); + new_elt->data = (PTR_T)old_var; + stupidly_hack_special_variables (old_var->name); + + free (elt->key); + free (elt); + return (0); + } + + /* Have to save a copy of name here, because it might refer to + old_var->name. If so, stupidly_hack_special_variables will + reference freed memory. */ + t = savestring (name); + + free (elt->key); + free (elt); + + dispose_variable (old_var); + stupidly_hack_special_variables (t); + free (t); + + return (0); +} + +/* Get rid of all of the variables in the current context. */ +void +kill_all_local_variables () +{ + VAR_CONTEXT *vc; + + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + if (vc == 0) + return; /* XXX */ + + if (vc->table && vc_haslocals (vc)) + { + delete_all_variables (vc->table); + hash_dispose (vc->table); + } + vc->table = (HASH_TABLE *)NULL; +} + +static void +free_variable_hash_data (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + dispose_variable (var); +} + +/* Delete the entire contents of the hash table. */ +void +delete_all_variables (hashed_vars) + HASH_TABLE *hashed_vars; +{ + hash_flush (hashed_vars, free_variable_hash_data); +} + +/* **************************************************************** */ +/* */ +/* Setting variable attributes */ +/* */ +/* **************************************************************** */ + +#define FIND_OR_MAKE_VARIABLE(name, entry) \ + do \ + { \ + entry = find_variable (name); \ + if (!entry) \ + { \ + entry = bind_variable (name, "", 0); \ + if (!no_invisible_vars) entry->attributes |= att_invisible; \ + } \ + } \ + while (0) + +/* Make the variable associated with NAME be readonly. + If NAME does not exist yet, create it. */ +void +set_var_read_only (name) + char *name; +{ + SHELL_VAR *entry; + + FIND_OR_MAKE_VARIABLE (name, entry); + VSETATTR (entry, att_readonly); +} + +#ifdef INCLUDE_UNUSED +/* Make the function associated with NAME be readonly. + If NAME does not exist, we just punt, like auto_export code below. */ +void +set_func_read_only (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry) + VSETATTR (entry, att_readonly); +} + +/* Make the variable associated with NAME be auto-exported. + If NAME does not exist yet, create it. */ +void +set_var_auto_export (name) + char *name; +{ + SHELL_VAR *entry; + + FIND_OR_MAKE_VARIABLE (name, entry); + set_auto_export (entry); +} + +/* Make the function associated with NAME be auto-exported. */ +void +set_func_auto_export (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry) + set_auto_export (entry); +} +#endif + +/* **************************************************************** */ +/* */ +/* Creating lists of variables */ +/* */ +/* **************************************************************** */ + +static VARLIST * +vlist_alloc (nentries) + int nentries; +{ + VARLIST *vlist; + + vlist = (VARLIST *)xmalloc (sizeof (VARLIST)); + vlist->list = (SHELL_VAR **)xmalloc ((nentries + 1) * sizeof (SHELL_VAR *)); + vlist->list_size = nentries; + vlist->list_len = 0; + vlist->list[0] = (SHELL_VAR *)NULL; + + return vlist; +} + +static VARLIST * +vlist_realloc (vlist, n) + VARLIST *vlist; + int n; +{ + if (vlist == 0) + return (vlist = vlist_alloc (n)); + if (n > vlist->list_size) + { + vlist->list_size = n; + vlist->list = (SHELL_VAR **)xrealloc (vlist->list, (vlist->list_size + 1) * sizeof (SHELL_VAR *)); + } + return vlist; +} + +static void +vlist_add (vlist, var, flags) + VARLIST *vlist; + SHELL_VAR *var; + int flags; +{ + register int i; + + for (i = 0; i < vlist->list_len; i++) + if (STREQ (var->name, vlist->list[i]->name)) + break; + if (i < vlist->list_len) + return; + + if (i >= vlist->list_size) + vlist = vlist_realloc (vlist, vlist->list_size + 16); + + vlist->list[vlist->list_len++] = var; + vlist->list[vlist->list_len] = (SHELL_VAR *)NULL; +} + +/* Map FUNCTION over the variables in VAR_HASH_TABLE. Return an array of the + variables for which FUNCTION returns a non-zero value. A NULL value + for FUNCTION means to use all variables. */ +SHELL_VAR ** +map_over (function, vc) + sh_var_map_func_t *function; + VAR_CONTEXT *vc; +{ + VAR_CONTEXT *v; + VARLIST *vlist; + SHELL_VAR **ret; + int nentries; + + for (nentries = 0, v = vc; v; v = v->down) + nentries += HASH_ENTRIES (v->table); + + if (nentries == 0) + return (SHELL_VAR **)NULL; + + vlist = vlist_alloc (nentries); + + for (v = vc; v; v = v->down) + flatten (v->table, function, vlist, 0); + + ret = vlist->list; + free (vlist); + return ret; +} + +SHELL_VAR ** +map_over_funcs (function) + sh_var_map_func_t *function; +{ + VARLIST *vlist; + SHELL_VAR **ret; + + if (shell_functions == 0 || HASH_ENTRIES (shell_functions) == 0) + return ((SHELL_VAR **)NULL); + + vlist = vlist_alloc (HASH_ENTRIES (shell_functions)); + + flatten (shell_functions, function, vlist, 0); + + ret = vlist->list; + free (vlist); + return ret; +} + +/* Flatten VAR_HASH_TABLE, applying FUNC to each member and adding those + elements for which FUNC succeeds to VLIST->list. FLAGS is reserved + for future use. Only unique names are added to VLIST. If FUNC is + NULL, each variable in VAR_HASH_TABLE is added to VLIST. If VLIST is + NULL, FUNC is applied to each SHELL_VAR in VAR_HASH_TABLE. If VLIST + and FUNC are both NULL, nothing happens. */ +static void +flatten (var_hash_table, func, vlist, flags) + HASH_TABLE *var_hash_table; + sh_var_map_func_t *func; + VARLIST *vlist; + int flags; +{ + register int i; + register BUCKET_CONTENTS *tlist; + int r; + SHELL_VAR *var; + + if (var_hash_table == 0 || (HASH_ENTRIES (var_hash_table) == 0) || (vlist == 0 && func == 0)) + return; + + for (i = 0; i < var_hash_table->nbuckets; i++) + { + for (tlist = hash_items (i, var_hash_table); tlist; tlist = tlist->next) + { + var = (SHELL_VAR *)tlist->data; + + r = func ? (*func) (var) : 1; + if (r && vlist) + vlist_add (vlist, var, flags); + } + } +} + +void +sort_variables (array) + SHELL_VAR **array; +{ + qsort (array, strvec_len ((char **)array), sizeof (SHELL_VAR *), (QSFUNC *)qsort_var_comp); +} + +static int +qsort_var_comp (var1, var2) + SHELL_VAR **var1, **var2; +{ + int result; + + if ((result = (*var1)->name[0] - (*var2)->name[0]) == 0) + result = strcmp ((*var1)->name, (*var2)->name); + + return (result); +} + +/* Apply FUNC to each variable in SHELL_VARIABLES, adding each one for + which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */ +static SHELL_VAR ** +vapply (func) + sh_var_map_func_t *func; +{ + SHELL_VAR **list; + + list = map_over (func, shell_variables); + if (list /* && posixly_correct */) + sort_variables (list); + return (list); +} + +/* Apply FUNC to each variable in SHELL_FUNCTIONS, adding each one for + which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */ +static SHELL_VAR ** +fapply (func) + sh_var_map_func_t *func; +{ + SHELL_VAR **list; + + list = map_over_funcs (func); + if (list /* && posixly_correct */) + sort_variables (list); + return (list); +} + +/* Create a NULL terminated array of all the shell variables. */ +SHELL_VAR ** +all_shell_variables () +{ + return (vapply ((sh_var_map_func_t *)NULL)); +} + +/* Create a NULL terminated array of all the shell functions. */ +SHELL_VAR ** +all_shell_functions () +{ + return (fapply ((sh_var_map_func_t *)NULL)); +} + +static int +visible_var (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0); +} + +SHELL_VAR ** +all_visible_functions () +{ + return (fapply (visible_var)); +} + +SHELL_VAR ** +all_visible_variables () +{ + return (vapply (visible_var)); +} + +/* Return non-zero if the variable VAR is visible and exported. Array + variables cannot be exported. */ +static int +visible_and_exported (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && exported_p (var)); +} + +/* Candidate variables for the export environment are either valid variables + with the export attribute or invalid variables inherited from the initial + environment and simply passed through. */ +static int +export_environment_candidate (var) + SHELL_VAR *var; +{ + return (exported_p (var) && (invisible_p (var) == 0 || imported_p (var))); +} + +/* Return non-zero if VAR is a local variable in the current context and + is exported. */ +static int +local_and_exported (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context && exported_p (var)); +} + +SHELL_VAR ** +all_exported_variables () +{ + return (vapply (visible_and_exported)); +} + +SHELL_VAR ** +local_exported_variables () +{ + return (vapply (local_and_exported)); +} + +static int +variable_in_context (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context); +} + +SHELL_VAR ** +all_local_variables () +{ + VARLIST *vlist; + SHELL_VAR **ret; + VAR_CONTEXT *vc; + + vc = shell_variables; + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + + if (vc == 0) + { + internal_error (_("all_local_variables: no function context at current scope")); + return (SHELL_VAR **)NULL; + } + if (vc->table == 0 || HASH_ENTRIES (vc->table) == 0 || vc_haslocals (vc) == 0) + return (SHELL_VAR **)NULL; + + vlist = vlist_alloc (HASH_ENTRIES (vc->table)); + + flatten (vc->table, variable_in_context, vlist, 0); + + ret = vlist->list; + free (vlist); + if (ret) + sort_variables (ret); + return ret; +} + +#if defined (ARRAY_VARS) +/* Return non-zero if the variable VAR is visible and an array. */ +static int +visible_array_vars (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && array_p (var)); +} + +SHELL_VAR ** +all_array_variables () +{ + return (vapply (visible_array_vars)); +} +#endif /* ARRAY_VARS */ + +char ** +all_variables_matching_prefix (prefix) + const char *prefix; +{ + SHELL_VAR **varlist; + char **rlist; + int vind, rind, plen; + + plen = STRLEN (prefix); + varlist = all_visible_variables (); + for (vind = 0; varlist && varlist[vind]; vind++) + ; + if (varlist == 0 || vind == 0) + return ((char **)NULL); + rlist = strvec_create (vind + 1); + for (vind = rind = 0; varlist[vind]; vind++) + { + if (plen == 0 || STREQN (prefix, varlist[vind]->name, plen)) + rlist[rind++] = savestring (varlist[vind]->name); + } + rlist[rind] = (char *)0; + free (varlist); + + return rlist; +} + +/* **************************************************************** */ +/* */ +/* Managing temporary variable scopes */ +/* */ +/* **************************************************************** */ + +/* Make variable NAME have VALUE in the temporary environment. */ +static SHELL_VAR * +bind_tempenv_variable (name, value) + const char *name; + char *value; +{ + SHELL_VAR *var; + + var = temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL; + + if (var) + { + FREE (value_cell (var)); + var_setvalue (var, savestring (value)); + INVALIDATE_EXPORTSTR (var); + } + + return (var); +} + +/* Find a variable in the temporary environment that is named NAME. + Return the SHELL_VAR *, or NULL if not found. */ +SHELL_VAR * +find_tempenv_variable (name) + const char *name; +{ + return (temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL); +} + +/* Push the variable described by (SHELL_VAR *)DATA down to the next + variable context from the temporary environment. */ +static void +push_temp_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + HASH_TABLE *binding_table; + + var = (SHELL_VAR *)data; + + binding_table = shell_variables->table; + if (binding_table == 0) + { + if (shell_variables == global_variables) + /* shouldn't happen */ + binding_table = shell_variables->table = global_variables->table = hash_create (0); + else + binding_table = shell_variables->table = hash_create (TEMPENV_HASH_BUCKETS); + } + + v = bind_variable_internal (var->name, value_cell (var), binding_table, 0, 0); + + /* XXX - should we set the context here? It shouldn't matter because of how + assign_in_env works, but might want to check. */ + if (binding_table == global_variables->table) /* XXX */ + var->attributes &= ~(att_tempvar|att_propagate); + else + { + var->attributes |= att_propagate; + if (binding_table == shell_variables->table) + shell_variables->flags |= VC_HASTMPVAR; + } + v->attributes |= var->attributes; + + dispose_variable (var); +} + +static void +propagate_temp_var (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + if (tempvar_p (var) && (var->attributes & att_propagate)) + push_temp_var (data); + else + dispose_variable (var); +} + +/* Free the storage used in the hash table for temporary + environment variables. PUSHF is a function to be called + to free each hash table entry. It takes care of pushing variables + to previous scopes if appropriate. */ +static void +dispose_temporary_env (pushf) + sh_free_func_t *pushf; +{ + hash_flush (temporary_env, pushf); + hash_dispose (temporary_env); + temporary_env = (HASH_TABLE *)NULL; + + array_needs_making = 1; + + sv_ifs ("IFS"); /* XXX here for now */ +} + +void +dispose_used_env_vars () +{ + if (temporary_env) + { + dispose_temporary_env (propagate_temp_var); + maybe_make_export_env (); + } +} + +/* Take all of the shell variables in the temporary environment HASH_TABLE + and make shell variables from them at the current variable context. */ +void +merge_temporary_env () +{ + if (temporary_env) + dispose_temporary_env (push_temp_var); +} + +/* **************************************************************** */ +/* */ +/* Creating and manipulating the environment */ +/* */ +/* **************************************************************** */ + +static inline char * +mk_env_string (name, value) + const char *name, *value; +{ + int name_len, value_len; + char *p; + + name_len = strlen (name); + value_len = STRLEN (value); + p = (char *)xmalloc (2 + name_len + value_len); + strcpy (p, name); + p[name_len] = '='; + if (value && *value) + strcpy (p + name_len + 1, value); + else + p[name_len + 1] = '\0'; + return (p); +} + +#ifdef DEBUG +/* Debugging */ +static int +valid_exportstr (v) + SHELL_VAR *v; +{ + char *s; + + s = v->exportstr; + if (s == 0) + { + internal_error (_("%s has null exportstr"), v->name); + return (0); + } + if (legal_variable_starter ((unsigned char)*s) == 0) + { + internal_error (_("invalid character %d in exportstr for %s"), *s, v->name); + return (0); + } + for (s = v->exportstr + 1; s && *s; s++) + { + if (*s == '=') + break; + if (legal_variable_char ((unsigned char)*s) == 0) + { + internal_error (_("invalid character %d in exportstr for %s"), *s, v->name); + return (0); + } + } + if (*s != '=') + { + internal_error (_("no `=' in exportstr for %s"), v->name); + return (0); + } + return (1); +} +#endif + +static char ** +make_env_array_from_var_list (vars) + SHELL_VAR **vars; +{ + register int i, list_index; + register SHELL_VAR *var; + char **list, *value; + + list = strvec_create ((1 + strvec_len ((char **)vars))); + +#define USE_EXPORTSTR (value == var->exportstr) + + for (i = 0, list_index = 0; var = vars[i]; i++) + { +#if defined (__CYGWIN__) + /* We don't use the exportstr stuff on Cygwin at all. */ + INVALIDATE_EXPORTSTR (var); +#endif + if (var->exportstr) + value = var->exportstr; + else if (function_p (var)) + value = named_function_string ((char *)NULL, function_cell (var), 0); +#if defined (ARRAY_VARS) + else if (array_p (var)) +# if 0 + value = array_to_assignment_string (array_cell (var)); +# else + continue; /* XXX array vars cannot yet be exported */ +# endif + else if (assoc_p (var)) +# if 0 + value = assoc_to_assignment_string (assoc_cell (var)); +# else + continue; /* XXX associative array vars cannot yet be exported */ +# endif +#endif + else + value = value_cell (var); + + if (value) + { + /* Gee, I'd like to get away with not using savestring() if we're + using the cached exportstr... */ + list[list_index] = USE_EXPORTSTR ? savestring (value) + : mk_env_string (var->name, value); + + if (USE_EXPORTSTR == 0) + SAVE_EXPORTSTR (var, list[list_index]); + + list_index++; +#undef USE_EXPORTSTR + +#if 0 /* not yet */ +#if defined (ARRAY_VARS) + if (array_p (var) || assoc_p (var)) + free (value); +#endif +#endif + } + } + + list[list_index] = (char *)NULL; + return (list); +} + +/* Make an array of assignment statements from the hash table + HASHED_VARS which contains SHELL_VARs. Only visible, exported + variables are eligible. */ +static char ** +make_var_export_array (vcxt) + VAR_CONTEXT *vcxt; +{ + char **list; + SHELL_VAR **vars; + +#if 0 + vars = map_over (visible_and_exported, vcxt); +#else + vars = map_over (export_environment_candidate, vcxt); +#endif + + if (vars == 0) + return (char **)NULL; + + list = make_env_array_from_var_list (vars); + + free (vars); + return (list); +} + +static char ** +make_func_export_array () +{ + char **list; + SHELL_VAR **vars; + + vars = map_over_funcs (visible_and_exported); + if (vars == 0) + return (char **)NULL; + + list = make_env_array_from_var_list (vars); + + free (vars); + return (list); +} + +/* Add ENVSTR to the end of the exported environment, EXPORT_ENV. */ +#define add_to_export_env(envstr,do_alloc) \ +do \ + { \ + if (export_env_index >= (export_env_size - 1)) \ + { \ + export_env_size += 16; \ + export_env = strvec_resize (export_env, export_env_size); \ + environ = export_env; \ + } \ + export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \ + export_env[export_env_index] = (char *)NULL; \ + } while (0) + +/* Add ASSIGN to EXPORT_ENV, or supercede a previous assignment in the + array with the same left-hand side. Return the new EXPORT_ENV. */ +char ** +add_or_supercede_exported_var (assign, do_alloc) + char *assign; + int do_alloc; +{ + register int i; + int equal_offset; + + equal_offset = assignment (assign, 0); + if (equal_offset == 0) + return (export_env); + + /* If this is a function, then only supersede the function definition. + We do this by including the `=() {' in the comparison, like + initialize_shell_variables does. */ + if (assign[equal_offset + 1] == '(' && + strncmp (assign + equal_offset + 2, ") {", 3) == 0) /* } */ + equal_offset += 4; + + for (i = 0; i < export_env_index; i++) + { + if (STREQN (assign, export_env[i], equal_offset + 1)) + { + free (export_env[i]); + export_env[i] = do_alloc ? savestring (assign) : assign; + return (export_env); + } + } + add_to_export_env (assign, do_alloc); + return (export_env); +} + +static void +add_temp_array_to_env (temp_array, do_alloc, do_supercede) + char **temp_array; + int do_alloc, do_supercede; +{ + register int i; + + if (temp_array == 0) + return; + + for (i = 0; temp_array[i]; i++) + { + if (do_supercede) + export_env = add_or_supercede_exported_var (temp_array[i], do_alloc); + else + add_to_export_env (temp_array[i], do_alloc); + } + + free (temp_array); +} + +/* Make the environment array for the command about to be executed, if the + array needs making. Otherwise, do nothing. If a shell action could + change the array that commands receive for their environment, then the + code should `array_needs_making++'. + + The order to add to the array is: + temporary_env + list of var contexts whose head is shell_variables + shell_functions + + This is the shell variable lookup order. We add only new variable + names at each step, which allows local variables and variables in + the temporary environments to shadow variables in the global (or + any previous) scope. +*/ + +static int +n_shell_variables () +{ + VAR_CONTEXT *vc; + int n; + + for (n = 0, vc = shell_variables; vc; vc = vc->down) + n += HASH_ENTRIES (vc->table); + return n; +} + +void +maybe_make_export_env () +{ + register char **temp_array; + int new_size; + VAR_CONTEXT *tcxt; + + if (array_needs_making) + { + if (export_env) + strvec_flush (export_env); + + /* Make a guess based on how many shell variables and functions we + have. Since there will always be array variables, and array + variables are not (yet) exported, this will always be big enough + for the exported variables and functions. */ + new_size = n_shell_variables () + HASH_ENTRIES (shell_functions) + 1 + + HASH_ENTRIES (temporary_env); + if (new_size > export_env_size) + { + export_env_size = new_size; + export_env = strvec_resize (export_env, export_env_size); + environ = export_env; + } + export_env[export_env_index = 0] = (char *)NULL; + + /* Make a dummy variable context from the temporary_env, stick it on + the front of shell_variables, call make_var_export_array on the + whole thing to flatten it, and convert the list of SHELL_VAR *s + to the form needed by the environment. */ + if (temporary_env) + { + tcxt = new_var_context ((char *)NULL, 0); + tcxt->table = temporary_env; + tcxt->down = shell_variables; + } + else + tcxt = shell_variables; + + temp_array = make_var_export_array (tcxt); + if (temp_array) + add_temp_array_to_env (temp_array, 0, 0); + + if (tcxt != shell_variables) + free (tcxt); + +#if defined (RESTRICTED_SHELL) + /* Restricted shells may not export shell functions. */ + temp_array = restricted ? (char **)0 : make_func_export_array (); +#else + temp_array = make_func_export_array (); +#endif + if (temp_array) + add_temp_array_to_env (temp_array, 0, 0); + + array_needs_making = 0; + } +} + +/* This is an efficiency hack. PWD and OLDPWD are auto-exported, so + we will need to remake the exported environment every time we + change directories. `_' is always put into the environment for + every external command, so without special treatment it will always + cause the environment to be remade. + + If there is no other reason to make the exported environment, we can + just update the variables in place and mark the exported environment + as no longer needing a remake. */ +void +update_export_env_inplace (env_prefix, preflen, value) + char *env_prefix; + int preflen; + char *value; +{ + char *evar; + + evar = (char *)xmalloc (STRLEN (value) + preflen + 1); + strcpy (evar, env_prefix); + if (value) + strcpy (evar + preflen, value); + export_env = add_or_supercede_exported_var (evar, 0); +} + +/* We always put _ in the environment as the name of this command. */ +void +put_command_name_into_env (command_name) + char *command_name; +{ + update_export_env_inplace ("_=", 2, command_name); +} + +#if 0 /* UNUSED -- it caused too many problems */ +void +put_gnu_argv_flags_into_env (pid, flags_string) + intmax_t pid; + char *flags_string; +{ + char *dummy, *pbuf; + int l, fl; + + pbuf = itos (pid); + l = strlen (pbuf); + + fl = strlen (flags_string); + + dummy = (char *)xmalloc (l + fl + 30); + dummy[0] = '_'; + strcpy (dummy + 1, pbuf); + strcpy (dummy + 1 + l, "_GNU_nonoption_argv_flags_"); + dummy[l + 27] = '='; + strcpy (dummy + l + 28, flags_string); + + free (pbuf); + + export_env = add_or_supercede_exported_var (dummy, 0); +} +#endif + +/* **************************************************************** */ +/* */ +/* Managing variable contexts */ +/* */ +/* **************************************************************** */ + +/* Allocate and return a new variable context with NAME and FLAGS. + NAME can be NULL. */ + +VAR_CONTEXT * +new_var_context (name, flags) + char *name; + int flags; +{ + VAR_CONTEXT *vc; + + vc = (VAR_CONTEXT *)xmalloc (sizeof (VAR_CONTEXT)); + vc->name = name ? savestring (name) : (char *)NULL; + vc->scope = variable_context; + vc->flags = flags; + + vc->up = vc->down = (VAR_CONTEXT *)NULL; + vc->table = (HASH_TABLE *)NULL; + + return vc; +} + +/* Free a variable context and its data, including the hash table. Dispose + all of the variables. */ +void +dispose_var_context (vc) + VAR_CONTEXT *vc; +{ + FREE (vc->name); + + if (vc->table) + { + delete_all_variables (vc->table); + hash_dispose (vc->table); + } + + free (vc); +} + +/* Set VAR's scope level to the current variable context. */ +static int +set_context (var) + SHELL_VAR *var; +{ + return (var->context = variable_context); +} + +/* Make a new variable context with NAME and FLAGS and a HASH_TABLE of + temporary variables, and push it onto shell_variables. This is + for shell functions. */ +VAR_CONTEXT * +push_var_context (name, flags, tempvars) + char *name; + int flags; + HASH_TABLE *tempvars; +{ + VAR_CONTEXT *vc; + + vc = new_var_context (name, flags); + vc->table = tempvars; + if (tempvars) + { + /* Have to do this because the temp environment was created before + variable_context was incremented. */ + flatten (tempvars, set_context, (VARLIST *)NULL, 0); + vc->flags |= VC_HASTMPVAR; + } + vc->down = shell_variables; + shell_variables->up = vc; + + return (shell_variables = vc); +} + +static void +push_func_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + + var = (SHELL_VAR *)data; + + if (tempvar_p (var) && (posixly_correct || (var->attributes & att_propagate))) + { + /* XXX - should we set v->context here? */ + v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0); + if (shell_variables == global_variables) + var->attributes &= ~(att_tempvar|att_propagate); + else + shell_variables->flags |= VC_HASTMPVAR; + v->attributes |= var->attributes; + } + else + stupidly_hack_special_variables (var->name); /* XXX */ + + dispose_variable (var); +} + +/* Pop the top context off of VCXT and dispose of it, returning the rest of + the stack. */ +void +pop_var_context () +{ + VAR_CONTEXT *ret, *vcxt; + + vcxt = shell_variables; + if (vc_isfuncenv (vcxt) == 0) + { + internal_error (_("pop_var_context: head of shell_variables not a function context")); + return; + } + + if (ret = vcxt->down) + { + ret->up = (VAR_CONTEXT *)NULL; + shell_variables = ret; + if (vcxt->table) + hash_flush (vcxt->table, push_func_var); + dispose_var_context (vcxt); + } + else + internal_error (_("pop_var_context: no global_variables context")); +} + +/* Delete the HASH_TABLEs for all variable contexts beginning at VCXT, and + all of the VAR_CONTEXTs except GLOBAL_VARIABLES. */ +void +delete_all_contexts (vcxt) + VAR_CONTEXT *vcxt; +{ + VAR_CONTEXT *v, *t; + + for (v = vcxt; v != global_variables; v = t) + { + t = v->down; + dispose_var_context (v); + } + + delete_all_variables (global_variables->table); + shell_variables = global_variables; +} + +/* **************************************************************** */ +/* */ +/* Pushing and Popping temporary variable scopes */ +/* */ +/* **************************************************************** */ + +VAR_CONTEXT * +push_scope (flags, tmpvars) + int flags; + HASH_TABLE *tmpvars; +{ + return (push_var_context ((char *)NULL, flags, tmpvars)); +} + +static void +push_exported_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + + var = (SHELL_VAR *)data; + + /* If a temp var had its export attribute set, or it's marked to be + propagated, bind it in the previous scope before disposing it. */ + /* XXX - This isn't exactly right, because all tempenv variables have the + export attribute set. */ +#if 0 + if (exported_p (var) || (var->attributes & att_propagate)) +#else + if (tempvar_p (var) && exported_p (var) && (var->attributes & att_propagate)) +#endif + { + var->attributes &= ~att_tempvar; /* XXX */ + v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0); + if (shell_variables == global_variables) + var->attributes &= ~att_propagate; + v->attributes |= var->attributes; + } + else + stupidly_hack_special_variables (var->name); /* XXX */ + + dispose_variable (var); +} + +void +pop_scope (is_special) + int is_special; +{ + VAR_CONTEXT *vcxt, *ret; + + vcxt = shell_variables; + if (vc_istempscope (vcxt) == 0) + { + internal_error (_("pop_scope: head of shell_variables not a temporary environment scope")); + return; + } + + ret = vcxt->down; + if (ret) + ret->up = (VAR_CONTEXT *)NULL; + + shell_variables = ret; + + /* Now we can take care of merging variables in VCXT into set of scopes + whose head is RET (shell_variables). */ + FREE (vcxt->name); + if (vcxt->table) + { + if (is_special) + hash_flush (vcxt->table, push_func_var); + else + hash_flush (vcxt->table, push_exported_var); + hash_dispose (vcxt->table); + } + free (vcxt); + + sv_ifs ("IFS"); /* XXX here for now */ +} + +/* **************************************************************** */ +/* */ +/* Pushing and Popping function contexts */ +/* */ +/* **************************************************************** */ + +static WORD_LIST **dollar_arg_stack = (WORD_LIST **)NULL; +static int dollar_arg_stack_slots; +static int dollar_arg_stack_index; + +/* XXX - we might want to consider pushing and popping the `getopts' state + when we modify the positional parameters. */ +void +push_context (name, is_subshell, tempvars) + char *name; /* function name */ + int is_subshell; + HASH_TABLE *tempvars; +{ + if (is_subshell == 0) + push_dollar_vars (); + variable_context++; + push_var_context (name, VC_FUNCENV, tempvars); +} + +/* Only called when subshell == 0, so we don't need to check, and can + unconditionally pop the dollar vars off the stack. */ +void +pop_context () +{ + pop_dollar_vars (); + variable_context--; + pop_var_context (); + + sv_ifs ("IFS"); /* XXX here for now */ +} + +/* Save the existing positional parameters on a stack. */ +void +push_dollar_vars () +{ + if (dollar_arg_stack_index + 2 > dollar_arg_stack_slots) + { + dollar_arg_stack = (WORD_LIST **) + xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10) + * sizeof (WORD_LIST **)); + } + dollar_arg_stack[dollar_arg_stack_index++] = list_rest_of_args (); + dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; +} + +/* Restore the positional parameters from our stack. */ +void +pop_dollar_vars () +{ + if (!dollar_arg_stack || dollar_arg_stack_index == 0) + return; + + remember_args (dollar_arg_stack[--dollar_arg_stack_index], 1); + dispose_words (dollar_arg_stack[dollar_arg_stack_index]); + dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; + set_dollar_vars_unchanged (); +} + +void +dispose_saved_dollar_vars () +{ + if (!dollar_arg_stack || dollar_arg_stack_index == 0) + return; + + dispose_words (dollar_arg_stack[dollar_arg_stack_index]); + dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; +} + +/* Manipulate the special BASH_ARGV and BASH_ARGC variables. */ + +void +push_args (list) + WORD_LIST *list; +{ +#if defined (ARRAY_VARS) && defined (DEBUGGER) + SHELL_VAR *bash_argv_v, *bash_argc_v; + ARRAY *bash_argv_a, *bash_argc_a; + WORD_LIST *l; + arrayind_t i; + char *t; + + GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a); + GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a); + + for (l = list, i = 0; l; l = l->next, i++) + array_push (bash_argv_a, l->word->word); + + t = itos (i); + array_push (bash_argc_a, t); + free (t); +#endif /* ARRAY_VARS && DEBUGGER */ +} + +/* Remove arguments from BASH_ARGV array. Pop top element off BASH_ARGC + array and use that value as the count of elements to remove from + BASH_ARGV. */ +void +pop_args () +{ +#if defined (ARRAY_VARS) && defined (DEBUGGER) + SHELL_VAR *bash_argv_v, *bash_argc_v; + ARRAY *bash_argv_a, *bash_argc_a; + ARRAY_ELEMENT *ce; + intmax_t i; + + GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a); + GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a); + + ce = array_shift (bash_argc_a, 1, 0); + if (ce == 0 || legal_number (element_value (ce), &i) == 0) + i = 0; + + for ( ; i > 0; i--) + array_pop (bash_argv_a); + array_dispose_element (ce); +#endif /* ARRAY_VARS && DEBUGGER */ +} + +/************************************************* + * * + * Functions to manage special variables * + * * + *************************************************/ + +/* Extern declarations for variables this code has to manage. */ +extern int eof_encountered, eof_encountered_limit, ignoreeof; + +#if defined (READLINE) +extern int hostname_list_initialized; +#endif + +/* An alist of name.function for each special variable. Most of the + functions don't do much, and in fact, this would be faster with a + switch statement, but by the end of this file, I am sick of switch + statements. */ + +#define SET_INT_VAR(name, intvar) intvar = find_variable (name) != 0 + +/* This table will be sorted with qsort() the first time it's accessed. */ +struct name_and_function { + char *name; + sh_sv_func_t *function; +}; + +static struct name_and_function special_vars[] = { + { "BASH_XTRACEFD", sv_xtracefd }, + +#if defined (READLINE) +# if defined (STRICT_POSIX) + { "COLUMNS", sv_winsize }, +# endif + { "COMP_WORDBREAKS", sv_comp_wordbreaks }, +#endif + + { "GLOBIGNORE", sv_globignore }, + +#if defined (HISTORY) + { "HISTCONTROL", sv_history_control }, + { "HISTFILESIZE", sv_histsize }, + { "HISTIGNORE", sv_histignore }, + { "HISTSIZE", sv_histsize }, + { "HISTTIMEFORMAT", sv_histtimefmt }, +#endif + +#if defined (__CYGWIN__) + { "HOME", sv_home }, +#endif + +#if defined (READLINE) + { "HOSTFILE", sv_hostfile }, +#endif + + { "IFS", sv_ifs }, + { "IGNOREEOF", sv_ignoreeof }, + + { "LANG", sv_locale }, + { "LC_ALL", sv_locale }, + { "LC_COLLATE", sv_locale }, + { "LC_CTYPE", sv_locale }, + { "LC_MESSAGES", sv_locale }, + { "LC_NUMERIC", sv_locale }, + { "LC_TIME", sv_locale }, + +#if defined (READLINE) && defined (STRICT_POSIX) + { "LINES", sv_winsize }, +#endif + + { "MAIL", sv_mail }, + { "MAILCHECK", sv_mail }, + { "MAILPATH", sv_mail }, + + { "OPTERR", sv_opterr }, + { "OPTIND", sv_optind }, + + { "PATH", sv_path }, + { "POSIXLY_CORRECT", sv_strict_posix }, + +#if defined (READLINE) + { "TERM", sv_terminal }, + { "TERMCAP", sv_terminal }, + { "TERMINFO", sv_terminal }, +#endif /* READLINE */ + + { "TEXTDOMAIN", sv_locale }, + { "TEXTDOMAINDIR", sv_locale }, + +#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE) + { "TZ", sv_tz }, +#endif + +#if defined (HISTORY) && defined (BANG_HISTORY) + { "histchars", sv_histchars }, +#endif /* HISTORY && BANG_HISTORY */ + + { "ignoreeof", sv_ignoreeof }, + + { (char *)0, (sh_sv_func_t *)0 } +}; + +#define N_SPECIAL_VARS (sizeof (special_vars) / sizeof (special_vars[0]) - 1) + +static int +sv_compare (sv1, sv2) + struct name_and_function *sv1, *sv2; +{ + int r; + + if ((r = sv1->name[0] - sv2->name[0]) == 0) + r = strcmp (sv1->name, sv2->name); + return r; +} + +static inline int +find_special_var (name) + const char *name; +{ + register int i, r; + + for (i = 0; special_vars[i].name; i++) + { + r = special_vars[i].name[0] - name[0]; + if (r == 0) + r = strcmp (special_vars[i].name, name); + if (r == 0) + return i; + else if (r > 0) + /* Can't match any of rest of elements in sorted list. Take this out + if it causes problems in certain environments. */ + break; + } + return -1; +} + +/* The variable in NAME has just had its state changed. Check to see if it + is one of the special ones where something special happens. */ +void +stupidly_hack_special_variables (name) + char *name; +{ + static int sv_sorted = 0; + int i; + + if (sv_sorted == 0) /* shouldn't need, but it's fairly cheap. */ + { + qsort (special_vars, N_SPECIAL_VARS, sizeof (special_vars[0]), + (QSFUNC *)sv_compare); + sv_sorted = 1; + } + + i = find_special_var (name); + if (i != -1) + (*(special_vars[i].function)) (name); +} + +/* Special variables that need hooks to be run when they are unset as part + of shell reinitialization should have their sv_ functions run here. */ +void +reinit_special_variables () +{ +#if defined (READLINE) + sv_comp_wordbreaks ("COMP_WORDBREAKS"); +#endif + sv_globignore ("GLOBIGNORE"); + sv_opterr ("OPTERR"); +} + +void +sv_ifs (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable ("IFS"); + setifs (v); +} + +/* What to do just after the PATH variable has changed. */ +void +sv_path (name) + char *name; +{ + /* hash -r */ + phash_flush (); +} + +/* What to do just after one of the MAILxxxx variables has changed. NAME + is the name of the variable. This is called with NAME set to one of + MAIL, MAILCHECK, or MAILPATH. */ +void +sv_mail (name) + char *name; +{ + /* If the time interval for checking the files has changed, then + reset the mail timer. Otherwise, one of the pathname vars + to the users mailbox has changed, so rebuild the array of + filenames. */ + if (name[4] == 'C') /* if (strcmp (name, "MAILCHECK") == 0) */ + reset_mail_timer (); + else + { + free_mail_files (); + remember_mail_dates (); + } +} + +/* What to do when GLOBIGNORE changes. */ +void +sv_globignore (name) + char *name; +{ + if (privileged_mode == 0) + setup_glob_ignore (name); +} + +#if defined (READLINE) +void +sv_comp_wordbreaks (name) + char *name; +{ + SHELL_VAR *sv; + + sv = find_variable (name); + if (sv == 0) + reset_completer_word_break_chars (); +} + +/* What to do just after one of the TERMxxx variables has changed. + If we are an interactive shell, then try to reset the terminal + information in readline. */ +void +sv_terminal (name) + char *name; +{ + if (interactive_shell && no_line_editing == 0) + rl_reset_terminal (get_string_value ("TERM")); +} + +void +sv_hostfile (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v == 0) + clear_hostname_list (); + else + hostname_list_initialized = 0; +} + +#if defined (STRICT_POSIX) +/* In strict posix mode, we allow assignments to LINES and COLUMNS (and values + found in the initial environment) to override the terminal size reported by + the kernel. */ +void +sv_winsize (name) + char *name; +{ + SHELL_VAR *v; + intmax_t xd; + int d; + + if (posixly_correct == 0 || interactive_shell == 0 || no_line_editing) + return; + + v = find_variable (name); + if (v == 0 || var_isnull (v)) + rl_reset_screen_size (); + else + { + if (legal_number (value_cell (v), &xd) == 0) + return; + winsize_assignment = winsize_assigned = 1; + d = xd; /* truncate */ + if (name[0] == 'L') /* LINES */ + rl_set_screen_size (d, -1); + else /* COLUMNS */ + rl_set_screen_size (-1, d); + winsize_assignment = 0; + } +} +#endif /* STRICT_POSIX */ +#endif /* READLINE */ + +/* Update the value of HOME in the export environment so tilde expansion will + work on cygwin. */ +#if defined (__CYGWIN__) +sv_home (name) + char *name; +{ + array_needs_making = 1; + maybe_make_export_env (); +} +#endif + +#if defined (HISTORY) +/* What to do after the HISTSIZE or HISTFILESIZE variables change. + If there is a value for this HISTSIZE (and it is numeric), then stifle + the history. Otherwise, if there is NO value for this variable, + unstifle the history. If name is HISTFILESIZE, and its value is + numeric, truncate the history file to hold no more than that many + lines. */ +void +sv_histsize (name) + char *name; +{ + char *temp; + intmax_t num; + int hmax; + + temp = get_string_value (name); + + if (temp && *temp) + { + if (legal_number (temp, &num)) + { + hmax = num; + if (name[4] == 'S') + { + stifle_history (hmax); + hmax = where_history (); + if (history_lines_this_session > hmax) + history_lines_this_session = hmax; + } + else + { + history_truncate_file (get_string_value ("HISTFILE"), hmax); + if (hmax <= history_lines_in_file) + history_lines_in_file = hmax; + } + } + } + else if (name[4] == 'S') + unstifle_history (); +} + +/* What to do after the HISTIGNORE variable changes. */ +void +sv_histignore (name) + char *name; +{ + setup_history_ignore (name); +} + +/* What to do after the HISTCONTROL variable changes. */ +void +sv_history_control (name) + char *name; +{ + char *temp; + char *val; + int tptr; + + history_control = 0; + temp = get_string_value (name); + + if (temp == 0 || *temp == 0) + return; + + tptr = 0; + while (val = extract_colon_unit (temp, &tptr)) + { + if (STREQ (val, "ignorespace")) + history_control |= HC_IGNSPACE; + else if (STREQ (val, "ignoredups")) + history_control |= HC_IGNDUPS; + else if (STREQ (val, "ignoreboth")) + history_control |= HC_IGNBOTH; + else if (STREQ (val, "erasedups")) + history_control |= HC_ERASEDUPS; + + free (val); + } +} + +#if defined (BANG_HISTORY) +/* Setting/unsetting of the history expansion character. */ +void +sv_histchars (name) + char *name; +{ + char *temp; + + temp = get_string_value (name); + if (temp) + { + history_expansion_char = *temp; + if (temp[0] && temp[1]) + { + history_subst_char = temp[1]; + if (temp[2]) + history_comment_char = temp[2]; + } + } + else + { + history_expansion_char = '!'; + history_subst_char = '^'; + history_comment_char = '#'; + } +} +#endif /* BANG_HISTORY */ + +void +sv_histtimefmt (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + history_write_timestamps = (v != 0); +} +#endif /* HISTORY */ + +#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE) +void +sv_tz (name) + char *name; +{ + tzset (); +} +#endif + +/* If the variable exists, then the value of it can be the number + of times we actually ignore the EOF. The default is small, + (smaller than csh, anyway). */ +void +sv_ignoreeof (name) + char *name; +{ + SHELL_VAR *tmp_var; + char *temp; + + eof_encountered = 0; + + tmp_var = find_variable (name); + ignoreeof = tmp_var != 0; + temp = tmp_var ? value_cell (tmp_var) : (char *)NULL; + if (temp) + eof_encountered_limit = (*temp && all_digits (temp)) ? atoi (temp) : 10; + set_shellopts (); /* make sure `ignoreeof' is/is not in $SHELLOPTS */ +} + +void +sv_optind (name) + char *name; +{ + char *tt; + int s; + + tt = get_string_value ("OPTIND"); + if (tt && *tt) + { + s = atoi (tt); + + /* According to POSIX, setting OPTIND=1 resets the internal state + of getopt (). */ + if (s < 0 || s == 1) + s = 0; + } + else + s = 0; + getopts_reset (s); +} + +void +sv_opterr (name) + char *name; +{ + char *tt; + + tt = get_string_value ("OPTERR"); + sh_opterr = (tt && *tt) ? atoi (tt) : 1; +} + +void +sv_strict_posix (name) + char *name; +{ + SET_INT_VAR (name, posixly_correct); + posix_initialize (posixly_correct); +#if defined (READLINE) + if (interactive_shell) + posix_readline_initialize (posixly_correct); +#endif /* READLINE */ + set_shellopts (); /* make sure `posix' is/is not in $SHELLOPTS */ +} + +void +sv_locale (name) + char *name; +{ + char *v; + + v = get_string_value (name); + if (name[0] == 'L' && name[1] == 'A') /* LANG */ + set_lang (name, v); + else + set_locale_var (name, v); /* LC_*, TEXTDOMAIN* */ +} + +#if defined (ARRAY_VARS) +void +set_pipestatus_array (ps, nproc) + int *ps; + int nproc; +{ + SHELL_VAR *v; + ARRAY *a; + ARRAY_ELEMENT *ae; + register int i; + char *t, tbuf[INT_STRLEN_BOUND(int) + 1]; + + v = find_variable ("PIPESTATUS"); + if (v == 0) + v = make_new_array_variable ("PIPESTATUS"); + if (array_p (v) == 0) + return; /* Do nothing if not an array variable. */ + a = array_cell (v); + + if (a == 0 || array_num_elements (a) == 0) + { + for (i = 0; i < nproc; i++) /* was ps[i] != -1, not i < nproc */ + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + return; + } + + /* Fast case */ + if (array_num_elements (a) == nproc && nproc == 1) + { + ae = element_forw (a->head); + free (element_value (ae)); + ae->value = itos (ps[0]); + } + else if (array_num_elements (a) <= nproc) + { + /* modify in array_num_elements members in place, then add */ + ae = a->head; + for (i = 0; i < array_num_elements (a); i++) + { + ae = element_forw (ae); + free (element_value (ae)); + ae->value = itos (ps[i]); + } + /* add any more */ + for ( ; i < nproc; i++) + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + } + else + { + /* deleting elements. it's faster to rebuild the array. */ + array_flush (a); + for (i = 0; ps[i] != -1; i++) + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + } +} +#endif + +void +set_pipestatus_from_exit (s) + int s; +{ +#if defined (ARRAY_VARS) + static int v[2] = { 0, -1 }; + + v[0] = s; + set_pipestatus_array (v, 1); +#endif +} + +void +sv_xtracefd (name) + char *name; +{ + SHELL_VAR *v; + char *t, *e; + int fd; + FILE *fp; + + v = find_variable (name); + if (v == 0) + { + xtrace_reset (); + return; + } + + t = value_cell (v); + if (t == 0 || *t == 0) + xtrace_reset (); + else + { + fd = (int)strtol (t, &e, 10); + if (e != t && *e == '\0' && sh_validfd (fd)) + { + fp = fdopen (fd, "w"); + if (fp == 0) + internal_error (_("%s: %s: cannot open as FILE"), name, value_cell (v)); + else + xtrace_set (fd, fp); + } + else + internal_error (_("%s: %s: invalid value for trace file descriptor"), name, value_cell (v)); + } +} -- 2.47.3