|  | /* POSIX.2 wordexp implementation. | 
|  | Copyright (C) 1997-2016 Free Software Foundation, Inc. | 
|  | This file is part of the GNU C Library. | 
|  | Contributed by Tim Waugh <tim@cyberelk.demon.co.uk>. | 
|  |  | 
|  | The GNU C Library is free software; you can redistribute it and/or | 
|  | modify it under the terms of the GNU Lesser General Public | 
|  | License as published by the Free Software Foundation; either | 
|  | version 2.1 of the License, or (at your option) any later version. | 
|  |  | 
|  | The GNU C Library 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 | 
|  | Lesser General Public License for more details. | 
|  |  | 
|  | You should have received a copy of the GNU Lesser General Public | 
|  | License along with the GNU C Library; if not, see | 
|  | <http://www.gnu.org/licenses/>.  */ | 
|  |  | 
|  | #include <alloca.h> | 
|  | #include <ctype.h> | 
|  | #include <errno.h> | 
|  | #include <fcntl.h> | 
|  | #include <fnmatch.h> | 
|  | #include <glob.h> | 
|  | #include <libintl.h> | 
|  | #include <paths.h> | 
|  | #include <pwd.h> | 
|  | #include <signal.h> | 
|  | #include <stdbool.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <sys/param.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/time.h> | 
|  | #include <sys/types.h> | 
|  | #include <sys/types.h> | 
|  | #include <sys/wait.h> | 
|  | #include <unistd.h> | 
|  | #include <wchar.h> | 
|  | #include <wordexp.h> | 
|  | #include <kernel-features.h> | 
|  |  | 
|  | #include <libc-lock.h> | 
|  | #include <_itoa.h> | 
|  |  | 
|  | /* Undefine the following line for the production version.  */ | 
|  | /* #define NDEBUG 1 */ | 
|  | #include <assert.h> | 
|  |  | 
|  | /* Get some device information.  */ | 
|  | #include <device-nrs.h> | 
|  |  | 
|  | /* | 
|  | * This is a recursive-descent-style word expansion routine. | 
|  | */ | 
|  |  | 
|  | /* These variables are defined and initialized in the startup code.  */ | 
|  | extern int __libc_argc attribute_hidden; | 
|  | extern char **__libc_argv attribute_hidden; | 
|  |  | 
|  | /* Some forward declarations */ | 
|  | static int parse_dollars (char **word, size_t *word_length, size_t *max_length, | 
|  | const char *words, size_t *offset, int flags, | 
|  | wordexp_t *pwordexp, const char *ifs, | 
|  | const char *ifs_white, int quoted) | 
|  | internal_function; | 
|  | static int parse_backtick (char **word, size_t *word_length, | 
|  | size_t *max_length, const char *words, | 
|  | size_t *offset, int flags, wordexp_t *pwordexp, | 
|  | const char *ifs, const char *ifs_white) | 
|  | internal_function; | 
|  | static int parse_dquote (char **word, size_t *word_length, size_t *max_length, | 
|  | const char *words, size_t *offset, int flags, | 
|  | wordexp_t *pwordexp, const char *ifs, | 
|  | const char *ifs_white) | 
|  | internal_function; | 
|  | static int eval_expr (char *expr, long int *result) internal_function; | 
|  |  | 
|  | /* The w_*() functions manipulate word lists. */ | 
|  |  | 
|  | #define W_CHUNK	(100) | 
|  |  | 
|  | /* Result of w_newword will be ignored if it's the last word. */ | 
|  | static inline char * | 
|  | w_newword (size_t *actlen, size_t *maxlen) | 
|  | { | 
|  | *actlen = *maxlen = 0; | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static char * | 
|  | w_addchar (char *buffer, size_t *actlen, size_t *maxlen, char ch) | 
|  | /* (lengths exclude trailing zero) */ | 
|  | { | 
|  | /* Add a character to the buffer, allocating room for it if needed.  */ | 
|  |  | 
|  | if (*actlen == *maxlen) | 
|  | { | 
|  | char *old_buffer = buffer; | 
|  | assert (buffer == NULL || *maxlen != 0); | 
|  | *maxlen += W_CHUNK; | 
|  | buffer = (char *) realloc (buffer, 1 + *maxlen); | 
|  |  | 
|  | if (buffer == NULL) | 
|  | free (old_buffer); | 
|  | } | 
|  |  | 
|  | if (buffer != NULL) | 
|  | { | 
|  | buffer[*actlen] = ch; | 
|  | buffer[++(*actlen)] = '\0'; | 
|  | } | 
|  |  | 
|  | return buffer; | 
|  | } | 
|  |  | 
|  | static char * | 
|  | internal_function | 
|  | w_addmem (char *buffer, size_t *actlen, size_t *maxlen, const char *str, | 
|  | size_t len) | 
|  | { | 
|  | /* Add a string to the buffer, allocating room for it if needed. | 
|  | */ | 
|  | if (*actlen + len > *maxlen) | 
|  | { | 
|  | char *old_buffer = buffer; | 
|  | assert (buffer == NULL || *maxlen != 0); | 
|  | *maxlen += MAX (2 * len, W_CHUNK); | 
|  | buffer = realloc (old_buffer, 1 + *maxlen); | 
|  |  | 
|  | if (buffer == NULL) | 
|  | free (old_buffer); | 
|  | } | 
|  |  | 
|  | if (buffer != NULL) | 
|  | { | 
|  | *((char *) __mempcpy (&buffer[*actlen], str, len)) = '\0'; | 
|  | *actlen += len; | 
|  | } | 
|  |  | 
|  | return buffer; | 
|  | } | 
|  |  | 
|  | static char * | 
|  | internal_function | 
|  | w_addstr (char *buffer, size_t *actlen, size_t *maxlen, const char *str) | 
|  | /* (lengths exclude trailing zero) */ | 
|  | { | 
|  | /* Add a string to the buffer, allocating room for it if needed. | 
|  | */ | 
|  | size_t len; | 
|  |  | 
|  | assert (str != NULL); /* w_addstr only called from this file */ | 
|  | len = strlen (str); | 
|  |  | 
|  | return w_addmem (buffer, actlen, maxlen, str, len); | 
|  | } | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | w_addword (wordexp_t *pwordexp, char *word) | 
|  | { | 
|  | /* Add a word to the wordlist */ | 
|  | size_t num_p; | 
|  | char **new_wordv; | 
|  | bool allocated = false; | 
|  |  | 
|  | /* Internally, NULL acts like "".  Convert NULLs to "" before | 
|  | * the caller sees them. | 
|  | */ | 
|  | if (word == NULL) | 
|  | { | 
|  | word = __strdup (""); | 
|  | if (word == NULL) | 
|  | goto no_space; | 
|  | allocated = true; | 
|  | } | 
|  |  | 
|  | num_p = 2 + pwordexp->we_wordc + pwordexp->we_offs; | 
|  | new_wordv = realloc (pwordexp->we_wordv, sizeof (char *) * num_p); | 
|  | if (new_wordv != NULL) | 
|  | { | 
|  | pwordexp->we_wordv = new_wordv; | 
|  | pwordexp->we_wordv[pwordexp->we_offs + pwordexp->we_wordc++] = word; | 
|  | pwordexp->we_wordv[pwordexp->we_offs + pwordexp->we_wordc] = NULL; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (allocated) | 
|  | free (word); | 
|  |  | 
|  | no_space: | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  |  | 
|  | /* The parse_*() functions should leave *offset being the offset in 'words' | 
|  | * to the last character processed. | 
|  | */ | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | parse_backslash (char **word, size_t *word_length, size_t *max_length, | 
|  | const char *words, size_t *offset) | 
|  | { | 
|  | /* We are poised _at_ a backslash, not in quotes */ | 
|  |  | 
|  | switch (words[1 + *offset]) | 
|  | { | 
|  | case 0: | 
|  | /* Backslash is last character of input words */ | 
|  | return WRDE_SYNTAX; | 
|  |  | 
|  | case '\n': | 
|  | ++(*offset); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | *word = w_addchar (*word, word_length, max_length, words[1 + *offset]); | 
|  | if (*word == NULL) | 
|  | return WRDE_NOSPACE; | 
|  |  | 
|  | ++(*offset); | 
|  | break; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | parse_qtd_backslash (char **word, size_t *word_length, size_t *max_length, | 
|  | const char *words, size_t *offset) | 
|  | { | 
|  | /* We are poised _at_ a backslash, inside quotes */ | 
|  |  | 
|  | switch (words[1 + *offset]) | 
|  | { | 
|  | case 0: | 
|  | /* Backslash is last character of input words */ | 
|  | return WRDE_SYNTAX; | 
|  |  | 
|  | case '\n': | 
|  | ++(*offset); | 
|  | break; | 
|  |  | 
|  | case '$': | 
|  | case '`': | 
|  | case '"': | 
|  | case '\\': | 
|  | *word = w_addchar (*word, word_length, max_length, words[1 + *offset]); | 
|  | if (*word == NULL) | 
|  | return WRDE_NOSPACE; | 
|  |  | 
|  | ++(*offset); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | *word = w_addchar (*word, word_length, max_length, words[*offset]); | 
|  | if (*word != NULL) | 
|  | *word = w_addchar (*word, word_length, max_length, words[1 + *offset]); | 
|  |  | 
|  | if (*word == NULL) | 
|  | return WRDE_NOSPACE; | 
|  |  | 
|  | ++(*offset); | 
|  | break; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | parse_tilde (char **word, size_t *word_length, size_t *max_length, | 
|  | const char *words, size_t *offset, size_t wordc) | 
|  | { | 
|  | /* We are poised _at_ a tilde */ | 
|  | size_t i; | 
|  |  | 
|  | if (*word_length != 0) | 
|  | { | 
|  | if (!((*word)[*word_length - 1] == '=' && wordc == 0)) | 
|  | { | 
|  | if (!((*word)[*word_length - 1] == ':' | 
|  | && strchr (*word, '=') && wordc == 0)) | 
|  | { | 
|  | *word = w_addchar (*word, word_length, max_length, '~'); | 
|  | return *word ? 0 : WRDE_NOSPACE; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | for (i = 1 + *offset; words[i]; i++) | 
|  | { | 
|  | if (words[i] == ':' || words[i] == '/' || words[i] == ' ' || | 
|  | words[i] == '\t' || words[i] == 0 ) | 
|  | break; | 
|  |  | 
|  | if (words[i] == '\\') | 
|  | { | 
|  | *word = w_addchar (*word, word_length, max_length, '~'); | 
|  | return *word ? 0 : WRDE_NOSPACE; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (i == 1 + *offset) | 
|  | { | 
|  | /* Tilde appears on its own */ | 
|  | uid_t uid; | 
|  | struct passwd pwd, *tpwd; | 
|  | int buflen = 1000; | 
|  | char* home; | 
|  | char* buffer; | 
|  | int result; | 
|  |  | 
|  | /* POSIX.2 says ~ expands to $HOME and if HOME is unset the | 
|  | results are unspecified.  We do a lookup on the uid if | 
|  | HOME is unset. */ | 
|  |  | 
|  | home = getenv ("HOME"); | 
|  | if (home != NULL) | 
|  | { | 
|  | *word = w_addstr (*word, word_length, max_length, home); | 
|  | if (*word == NULL) | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  | else | 
|  | { | 
|  | uid = __getuid (); | 
|  | buffer = __alloca (buflen); | 
|  |  | 
|  | while ((result = __getpwuid_r (uid, &pwd, buffer, buflen, &tpwd)) != 0 | 
|  | && errno == ERANGE) | 
|  | buffer = extend_alloca (buffer, buflen, buflen + 1000); | 
|  |  | 
|  | if (result == 0 && tpwd != NULL && pwd.pw_dir != NULL) | 
|  | { | 
|  | *word = w_addstr (*word, word_length, max_length, pwd.pw_dir); | 
|  | if (*word == NULL) | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  | else | 
|  | { | 
|  | *word = w_addchar (*word, word_length, max_length, '~'); | 
|  | if (*word == NULL) | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | /* Look up user name in database to get home directory */ | 
|  | char *user = strndupa (&words[1 + *offset], i - (1 + *offset)); | 
|  | struct passwd pwd, *tpwd; | 
|  | int buflen = 1000; | 
|  | char* buffer = __alloca (buflen); | 
|  | int result; | 
|  |  | 
|  | while ((result = __getpwnam_r (user, &pwd, buffer, buflen, &tpwd)) != 0 | 
|  | && errno == ERANGE) | 
|  | buffer = extend_alloca (buffer, buflen, buflen + 1000); | 
|  |  | 
|  | if (result == 0 && tpwd != NULL && pwd.pw_dir) | 
|  | *word = w_addstr (*word, word_length, max_length, pwd.pw_dir); | 
|  | else | 
|  | { | 
|  | /* (invalid login name) */ | 
|  | *word = w_addchar (*word, word_length, max_length, '~'); | 
|  | if (*word != NULL) | 
|  | *word = w_addstr (*word, word_length, max_length, user); | 
|  | } | 
|  |  | 
|  | *offset = i - 1; | 
|  | } | 
|  | return *word ? 0 : WRDE_NOSPACE; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | do_parse_glob (const char *glob_word, char **word, size_t *word_length, | 
|  | size_t *max_length, wordexp_t *pwordexp, const char *ifs, | 
|  | const char *ifs_white) | 
|  | { | 
|  | int error; | 
|  | unsigned int match; | 
|  | glob_t globbuf; | 
|  |  | 
|  | error = glob (glob_word, GLOB_NOCHECK, NULL, &globbuf); | 
|  |  | 
|  | if (error != 0) | 
|  | { | 
|  | /* We can only run into memory problems.  */ | 
|  | assert (error == GLOB_NOSPACE); | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  |  | 
|  | if (ifs && !*ifs) | 
|  | { | 
|  | /* No field splitting allowed. */ | 
|  | assert (globbuf.gl_pathv[0] != NULL); | 
|  | *word = w_addstr (*word, word_length, max_length, globbuf.gl_pathv[0]); | 
|  | for (match = 1; match < globbuf.gl_pathc && *word != NULL; ++match) | 
|  | { | 
|  | *word = w_addchar (*word, word_length, max_length, ' '); | 
|  | if (*word != NULL) | 
|  | *word = w_addstr (*word, word_length, max_length, | 
|  | globbuf.gl_pathv[match]); | 
|  | } | 
|  |  | 
|  | globfree (&globbuf); | 
|  | return *word ? 0 : WRDE_NOSPACE; | 
|  | } | 
|  |  | 
|  | assert (ifs == NULL || *ifs != '\0'); | 
|  | if (*word != NULL) | 
|  | { | 
|  | free (*word); | 
|  | *word = w_newword (word_length, max_length); | 
|  | } | 
|  |  | 
|  | for (match = 0; match < globbuf.gl_pathc; ++match) | 
|  | { | 
|  | char *matching_word = __strdup (globbuf.gl_pathv[match]); | 
|  | if (matching_word == NULL || w_addword (pwordexp, matching_word)) | 
|  | { | 
|  | globfree (&globbuf); | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  | } | 
|  |  | 
|  | globfree (&globbuf); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | parse_glob (char **word, size_t *word_length, size_t *max_length, | 
|  | const char *words, size_t *offset, int flags, | 
|  | wordexp_t *pwordexp, const char *ifs, const char *ifs_white) | 
|  | { | 
|  | /* We are poised just after a '*', a '[' or a '?'. */ | 
|  | int error = WRDE_NOSPACE; | 
|  | int quoted = 0; /* 1 if singly-quoted, 2 if doubly */ | 
|  | size_t i; | 
|  | wordexp_t glob_list; /* List of words to glob */ | 
|  |  | 
|  | glob_list.we_wordc = 0; | 
|  | glob_list.we_wordv = NULL; | 
|  | glob_list.we_offs = 0; | 
|  | for (; words[*offset] != '\0'; ++*offset) | 
|  | { | 
|  | if (strchr (ifs, words[*offset]) != NULL) | 
|  | /* Reached IFS */ | 
|  | break; | 
|  |  | 
|  | /* Sort out quoting */ | 
|  | if (words[*offset] == '\'') | 
|  | { | 
|  | if (quoted == 0) | 
|  | { | 
|  | quoted = 1; | 
|  | continue; | 
|  | } | 
|  | else if (quoted == 1) | 
|  | { | 
|  | quoted = 0; | 
|  | continue; | 
|  | } | 
|  | } | 
|  | else if (words[*offset] == '"') | 
|  | { | 
|  | if (quoted == 0) | 
|  | { | 
|  | quoted = 2; | 
|  | continue; | 
|  | } | 
|  | else if (quoted == 2) | 
|  | { | 
|  | quoted = 0; | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Sort out other special characters */ | 
|  | if (quoted != 1 && words[*offset] == '$') | 
|  | { | 
|  | error = parse_dollars (word, word_length, max_length, words, | 
|  | offset, flags, &glob_list, ifs, ifs_white, | 
|  | quoted == 2); | 
|  | if (error) | 
|  | goto tidy_up; | 
|  |  | 
|  | continue; | 
|  | } | 
|  | else if (words[*offset] == '\\') | 
|  | { | 
|  | if (quoted) | 
|  | error = parse_qtd_backslash (word, word_length, max_length, | 
|  | words, offset); | 
|  | else | 
|  | error = parse_backslash (word, word_length, max_length, | 
|  | words, offset); | 
|  |  | 
|  | if (error) | 
|  | goto tidy_up; | 
|  |  | 
|  | continue; | 
|  | } | 
|  |  | 
|  | *word = w_addchar (*word, word_length, max_length, words[*offset]); | 
|  | if (*word == NULL) | 
|  | goto tidy_up; | 
|  | } | 
|  |  | 
|  | /* Don't forget to re-parse the character we stopped at. */ | 
|  | --*offset; | 
|  |  | 
|  | /* Glob the words */ | 
|  | error = w_addword (&glob_list, *word); | 
|  | *word = w_newword (word_length, max_length); | 
|  | for (i = 0; error == 0 && i < glob_list.we_wordc; i++) | 
|  | error = do_parse_glob (glob_list.we_wordv[i], word, word_length, | 
|  | max_length, pwordexp, ifs, ifs_white); | 
|  |  | 
|  | /* Now tidy up */ | 
|  | tidy_up: | 
|  | wordfree (&glob_list); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | parse_squote (char **word, size_t *word_length, size_t *max_length, | 
|  | const char *words, size_t *offset) | 
|  | { | 
|  | /* We are poised just after a single quote */ | 
|  | for (; words[*offset]; ++(*offset)) | 
|  | { | 
|  | if (words[*offset] != '\'') | 
|  | { | 
|  | *word = w_addchar (*word, word_length, max_length, words[*offset]); | 
|  | if (*word == NULL) | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  | else return 0; | 
|  | } | 
|  |  | 
|  | /* Unterminated string */ | 
|  | return WRDE_SYNTAX; | 
|  | } | 
|  |  | 
|  | /* Functions to evaluate an arithmetic expression */ | 
|  | static int | 
|  | internal_function | 
|  | eval_expr_val (char **expr, long int *result) | 
|  | { | 
|  | char *digit; | 
|  |  | 
|  | /* Skip white space */ | 
|  | for (digit = *expr; digit && *digit && isspace (*digit); ++digit); | 
|  |  | 
|  | if (*digit == '(') | 
|  | { | 
|  | /* Scan for closing paren */ | 
|  | for (++digit; **expr && **expr != ')'; ++(*expr)); | 
|  |  | 
|  | /* Is there one? */ | 
|  | if (!**expr) | 
|  | return WRDE_SYNTAX; | 
|  |  | 
|  | *(*expr)++ = 0; | 
|  |  | 
|  | if (eval_expr (digit, result)) | 
|  | return WRDE_SYNTAX; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* POSIX requires that decimal, octal, and hexadecimal constants are | 
|  | recognized.  Therefore we pass 0 as the third parameter to strtol.  */ | 
|  | *result = strtol (digit, expr, 0); | 
|  | if (digit == *expr) | 
|  | return WRDE_SYNTAX; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | eval_expr_multdiv (char **expr, long int *result) | 
|  | { | 
|  | long int arg; | 
|  |  | 
|  | /* Read a Value */ | 
|  | if (eval_expr_val (expr, result) != 0) | 
|  | return WRDE_SYNTAX; | 
|  |  | 
|  | while (**expr) | 
|  | { | 
|  | /* Skip white space */ | 
|  | for (; *expr && **expr && isspace (**expr); ++(*expr)); | 
|  |  | 
|  | if (**expr == '*') | 
|  | { | 
|  | ++(*expr); | 
|  | if (eval_expr_val (expr, &arg) != 0) | 
|  | return WRDE_SYNTAX; | 
|  |  | 
|  | *result *= arg; | 
|  | } | 
|  | else if (**expr == '/') | 
|  | { | 
|  | ++(*expr); | 
|  | if (eval_expr_val (expr, &arg) != 0) | 
|  | return WRDE_SYNTAX; | 
|  |  | 
|  | /* Division by zero or integer overflow.  */ | 
|  | if (arg == 0 || (arg == -1 && *result == LONG_MIN)) | 
|  | return WRDE_SYNTAX; | 
|  |  | 
|  | *result /= arg; | 
|  | } | 
|  | else break; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | eval_expr (char *expr, long int *result) | 
|  | { | 
|  | long int arg; | 
|  |  | 
|  | /* Read a Multdiv */ | 
|  | if (eval_expr_multdiv (&expr, result) != 0) | 
|  | return WRDE_SYNTAX; | 
|  |  | 
|  | while (*expr) | 
|  | { | 
|  | /* Skip white space */ | 
|  | for (; expr && *expr && isspace (*expr); ++expr); | 
|  |  | 
|  | if (*expr == '+') | 
|  | { | 
|  | ++expr; | 
|  | if (eval_expr_multdiv (&expr, &arg) != 0) | 
|  | return WRDE_SYNTAX; | 
|  |  | 
|  | *result += arg; | 
|  | } | 
|  | else if (*expr == '-') | 
|  | { | 
|  | ++expr; | 
|  | if (eval_expr_multdiv (&expr, &arg) != 0) | 
|  | return WRDE_SYNTAX; | 
|  |  | 
|  | *result -= arg; | 
|  | } | 
|  | else break; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | parse_arith (char **word, size_t *word_length, size_t *max_length, | 
|  | const char *words, size_t *offset, int flags, int bracket) | 
|  | { | 
|  | /* We are poised just after "$((" or "$[" */ | 
|  | int error; | 
|  | int paren_depth = 1; | 
|  | size_t expr_length; | 
|  | size_t expr_maxlen; | 
|  | char *expr; | 
|  |  | 
|  | expr = w_newword (&expr_length, &expr_maxlen); | 
|  | for (; words[*offset]; ++(*offset)) | 
|  | { | 
|  | switch (words[*offset]) | 
|  | { | 
|  | case '$': | 
|  | error = parse_dollars (&expr, &expr_length, &expr_maxlen, | 
|  | words, offset, flags, NULL, NULL, NULL, 1); | 
|  | /* The ``1'' here is to tell parse_dollars not to | 
|  | * split the fields. | 
|  | */ | 
|  | if (error) | 
|  | { | 
|  | free (expr); | 
|  | return error; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case '`': | 
|  | (*offset)++; | 
|  | error = parse_backtick (&expr, &expr_length, &expr_maxlen, | 
|  | words, offset, flags, NULL, NULL, NULL); | 
|  | /* The first NULL here is to tell parse_backtick not to | 
|  | * split the fields. | 
|  | */ | 
|  | if (error) | 
|  | { | 
|  | free (expr); | 
|  | return error; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case '\\': | 
|  | error = parse_qtd_backslash (&expr, &expr_length, &expr_maxlen, | 
|  | words, offset); | 
|  | if (error) | 
|  | { | 
|  | free (expr); | 
|  | return error; | 
|  | } | 
|  | /* I think that a backslash within an | 
|  | * arithmetic expansion is bound to | 
|  | * cause an error sooner or later anyway though. | 
|  | */ | 
|  | break; | 
|  |  | 
|  | case ')': | 
|  | if (--paren_depth == 0) | 
|  | { | 
|  | char result[21];	/* 21 = ceil(log10(2^64)) + 1 */ | 
|  | long int numresult = 0; | 
|  | long long int convertme; | 
|  |  | 
|  | if (bracket || words[1 + *offset] != ')') | 
|  | { | 
|  | free (expr); | 
|  | return WRDE_SYNTAX; | 
|  | } | 
|  |  | 
|  | ++(*offset); | 
|  |  | 
|  | /* Go - evaluate. */ | 
|  | if (*expr && eval_expr (expr, &numresult) != 0) | 
|  | { | 
|  | free (expr); | 
|  | return WRDE_SYNTAX; | 
|  | } | 
|  |  | 
|  | if (numresult < 0) | 
|  | { | 
|  | convertme = -numresult; | 
|  | *word = w_addchar (*word, word_length, max_length, '-'); | 
|  | if (!*word) | 
|  | { | 
|  | free (expr); | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  | } | 
|  | else | 
|  | convertme = numresult; | 
|  |  | 
|  | result[20] = '\0'; | 
|  | *word = w_addstr (*word, word_length, max_length, | 
|  | _itoa (convertme, &result[20], 10, 0)); | 
|  | free (expr); | 
|  | return *word ? 0 : WRDE_NOSPACE; | 
|  | } | 
|  | expr = w_addchar (expr, &expr_length, &expr_maxlen, words[*offset]); | 
|  | if (expr == NULL) | 
|  | return WRDE_NOSPACE; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case ']': | 
|  | if (bracket && paren_depth == 1) | 
|  | { | 
|  | char result[21];	/* 21 = ceil(log10(2^64)) + 1 */ | 
|  | long int numresult = 0; | 
|  |  | 
|  | /* Go - evaluate. */ | 
|  | if (*expr && eval_expr (expr, &numresult) != 0) | 
|  | { | 
|  | free (expr); | 
|  | return WRDE_SYNTAX; | 
|  | } | 
|  |  | 
|  | result[20] = '\0'; | 
|  | *word = w_addstr (*word, word_length, max_length, | 
|  | _itoa_word (numresult, &result[20], 10, 0)); | 
|  | free (expr); | 
|  | return *word ? 0 : WRDE_NOSPACE; | 
|  | } | 
|  |  | 
|  | free (expr); | 
|  | return WRDE_SYNTAX; | 
|  |  | 
|  | case '\n': | 
|  | case ';': | 
|  | case '{': | 
|  | case '}': | 
|  | free (expr); | 
|  | return WRDE_BADCHAR; | 
|  |  | 
|  | case '(': | 
|  | ++paren_depth; | 
|  | default: | 
|  | expr = w_addchar (expr, &expr_length, &expr_maxlen, words[*offset]); | 
|  | if (expr == NULL) | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Premature end */ | 
|  | free (expr); | 
|  | return WRDE_SYNTAX; | 
|  | } | 
|  |  | 
|  | /* Function called by child process in exec_comm() */ | 
|  | static inline void | 
|  | internal_function __attribute__ ((always_inline)) | 
|  | exec_comm_child (char *comm, int *fildes, int showerr, int noexec) | 
|  | { | 
|  | const char *args[4] = { _PATH_BSHELL, "-c", comm, NULL }; | 
|  |  | 
|  | /* Execute the command, or just check syntax? */ | 
|  | if (noexec) | 
|  | args[1] = "-nc"; | 
|  |  | 
|  | /* Redirect output.  */ | 
|  | if (__glibc_likely (fildes[1] != STDOUT_FILENO)) | 
|  | { | 
|  | __dup2 (fildes[1], STDOUT_FILENO); | 
|  | __close (fildes[1]); | 
|  | } | 
|  | else | 
|  | { | 
|  | #ifdef O_CLOEXEC | 
|  | /* Reset the close-on-exec flag (if necessary).  */ | 
|  | # ifndef __ASSUME_PIPE2 | 
|  | if (__have_pipe2 > 0) | 
|  | # endif | 
|  | __fcntl (fildes[1], F_SETFD, 0); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | /* Redirect stderr to /dev/null if we have to.  */ | 
|  | if (showerr == 0) | 
|  | { | 
|  | struct stat64 st; | 
|  | int fd; | 
|  | __close (STDERR_FILENO); | 
|  | fd = __open (_PATH_DEVNULL, O_WRONLY); | 
|  | if (fd >= 0 && fd != STDERR_FILENO) | 
|  | { | 
|  | __dup2 (fd, STDERR_FILENO); | 
|  | __close (fd); | 
|  | } | 
|  | /* Be paranoid.  Check that we actually opened the /dev/null | 
|  | device.  */ | 
|  | if (__builtin_expect (__fxstat64 (_STAT_VER, STDERR_FILENO, &st), 0) != 0 | 
|  | || __builtin_expect (S_ISCHR (st.st_mode), 1) == 0 | 
|  | #if defined DEV_NULL_MAJOR && defined DEV_NULL_MINOR | 
|  | || st.st_rdev != makedev (DEV_NULL_MAJOR, DEV_NULL_MINOR) | 
|  | #endif | 
|  | ) | 
|  | /* It's not the /dev/null device.  Stop right here.  The | 
|  | problem is: how do we stop?  We use _exit() with an | 
|  | hopefully unusual exit code.  */ | 
|  | _exit (90); | 
|  | } | 
|  |  | 
|  | /* Make sure the subshell doesn't field-split on our behalf. */ | 
|  | __unsetenv ("IFS"); | 
|  |  | 
|  | __close (fildes[0]); | 
|  | __execve (_PATH_BSHELL, (char *const *) args, __environ); | 
|  |  | 
|  | /* Bad.  What now?  */ | 
|  | abort (); | 
|  | } | 
|  |  | 
|  | /* Function to execute a command and retrieve the results */ | 
|  | /* pwordexp contains NULL if field-splitting is forbidden */ | 
|  | static int | 
|  | internal_function | 
|  | exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length, | 
|  | int flags, wordexp_t *pwordexp, const char *ifs, | 
|  | const char *ifs_white) | 
|  | { | 
|  | int fildes[2]; | 
|  | #define bufsize 128 | 
|  | int buflen; | 
|  | int i; | 
|  | int status = 0; | 
|  | size_t maxnewlines = 0; | 
|  | char buffer[bufsize]; | 
|  | pid_t pid; | 
|  | int noexec = 0; | 
|  |  | 
|  | /* Do nothing if command substitution should not succeed.  */ | 
|  | if (flags & WRDE_NOCMD) | 
|  | return WRDE_CMDSUB; | 
|  |  | 
|  | /* Don't fork() unless necessary */ | 
|  | if (!comm || !*comm) | 
|  | return 0; | 
|  |  | 
|  | #ifdef O_CLOEXEC | 
|  | # ifndef __ASSUME_PIPE2 | 
|  | if (__have_pipe2 >= 0) | 
|  | # endif | 
|  | { | 
|  | int r = __pipe2 (fildes, O_CLOEXEC); | 
|  | # ifndef __ASSUME_PIPE2 | 
|  | if (__have_pipe2 == 0) | 
|  | __have_pipe2 = r != -1 || errno != ENOSYS ? 1 : -1; | 
|  |  | 
|  | if (__have_pipe2 > 0) | 
|  | # endif | 
|  | if (r < 0) | 
|  | /* Bad */ | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  | #endif | 
|  | #ifndef __ASSUME_PIPE2 | 
|  | # ifdef O_CLOEXEC | 
|  | if (__have_pipe2 < 0) | 
|  | # endif | 
|  | if (__pipe (fildes) < 0) | 
|  | /* Bad */ | 
|  | return WRDE_NOSPACE; | 
|  | #endif | 
|  |  | 
|  | again: | 
|  | if ((pid = __fork ()) < 0) | 
|  | { | 
|  | /* Bad */ | 
|  | __close (fildes[0]); | 
|  | __close (fildes[1]); | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  |  | 
|  | if (pid == 0) | 
|  | exec_comm_child (comm, fildes, noexec ? 0 : flags & WRDE_SHOWERR, noexec); | 
|  |  | 
|  | /* Parent */ | 
|  |  | 
|  | /* If we are just testing the syntax, only wait.  */ | 
|  | if (noexec) | 
|  | return (TEMP_FAILURE_RETRY (__waitpid (pid, &status, 0)) == pid | 
|  | && status != 0) ? WRDE_SYNTAX : 0; | 
|  |  | 
|  | __close (fildes[1]); | 
|  | fildes[1] = -1; | 
|  |  | 
|  | if (!pwordexp) | 
|  | /* Quoted - no field splitting */ | 
|  | { | 
|  | while (1) | 
|  | { | 
|  | if ((buflen = TEMP_FAILURE_RETRY (__read (fildes[0], buffer, | 
|  | bufsize))) < 1) | 
|  | { | 
|  | /* If read returned 0 then the process has closed its | 
|  | stdout.  Don't use WNOHANG in that case to avoid busy | 
|  | looping until the process eventually exits.  */ | 
|  | if (TEMP_FAILURE_RETRY (__waitpid (pid, &status, | 
|  | buflen == 0 ? 0 : WNOHANG)) | 
|  | == 0) | 
|  | continue; | 
|  | if ((buflen = TEMP_FAILURE_RETRY (__read (fildes[0], buffer, | 
|  | bufsize))) < 1) | 
|  | break; | 
|  | } | 
|  |  | 
|  | maxnewlines += buflen; | 
|  |  | 
|  | *word = w_addmem (*word, word_length, max_length, buffer, buflen); | 
|  | if (*word == NULL) | 
|  | goto no_space; | 
|  | } | 
|  | } | 
|  | else | 
|  | /* Not quoted - split fields */ | 
|  | { | 
|  | int copying = 0; | 
|  | /* 'copying' is: | 
|  | *  0 when searching for first character in a field not IFS white space | 
|  | *  1 when copying the text of a field | 
|  | *  2 when searching for possible non-whitespace IFS | 
|  | *  3 when searching for non-newline after copying field | 
|  | */ | 
|  |  | 
|  | while (1) | 
|  | { | 
|  | if ((buflen = TEMP_FAILURE_RETRY (__read (fildes[0], buffer, | 
|  | bufsize))) < 1) | 
|  | { | 
|  | /* If read returned 0 then the process has closed its | 
|  | stdout.  Don't use WNOHANG in that case to avoid busy | 
|  | looping until the process eventually exits.  */ | 
|  | if (TEMP_FAILURE_RETRY (__waitpid (pid, &status, | 
|  | buflen == 0 ? 0 : WNOHANG)) | 
|  | == 0) | 
|  | continue; | 
|  | if ((buflen = TEMP_FAILURE_RETRY (__read (fildes[0], buffer, | 
|  | bufsize))) < 1) | 
|  | break; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < buflen; ++i) | 
|  | { | 
|  | if (strchr (ifs, buffer[i]) != NULL) | 
|  | { | 
|  | /* Current character is IFS */ | 
|  | if (strchr (ifs_white, buffer[i]) == NULL) | 
|  | { | 
|  | /* Current character is IFS but not whitespace */ | 
|  | if (copying == 2) | 
|  | { | 
|  | /*            current character | 
|  | *                   | | 
|  | *                   V | 
|  | * eg: text<space><comma><space>moretext | 
|  | * | 
|  | * So, strip whitespace IFS (like at the start) | 
|  | */ | 
|  | copying = 0; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | copying = 0; | 
|  | /* fall through and delimit field.. */ | 
|  | } | 
|  | else | 
|  | { | 
|  | if (buffer[i] == '\n') | 
|  | { | 
|  | /* Current character is (IFS) newline */ | 
|  |  | 
|  | /* If copying a field, this is the end of it, | 
|  | but maybe all that's left is trailing newlines. | 
|  | So start searching for a non-newline. */ | 
|  | if (copying == 1) | 
|  | copying = 3; | 
|  |  | 
|  | continue; | 
|  | } | 
|  | else | 
|  | { | 
|  | /* Current character is IFS white space, but | 
|  | not a newline */ | 
|  |  | 
|  | /* If not either copying a field or searching | 
|  | for non-newline after a field, ignore it */ | 
|  | if (copying != 1 && copying != 3) | 
|  | continue; | 
|  |  | 
|  | /* End of field (search for non-ws IFS afterwards) */ | 
|  | copying = 2; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* First IFS white space (non-newline), or IFS non-whitespace. | 
|  | * Delimit the field.  Nulls are converted by w_addword. */ | 
|  | if (w_addword (pwordexp, *word) == WRDE_NOSPACE) | 
|  | goto no_space; | 
|  |  | 
|  | *word = w_newword (word_length, max_length); | 
|  |  | 
|  | maxnewlines = 0; | 
|  | /* fall back round the loop.. */ | 
|  | } | 
|  | else | 
|  | { | 
|  | /* Not IFS character */ | 
|  |  | 
|  | if (copying == 3) | 
|  | { | 
|  | /* Nothing but (IFS) newlines since the last field, | 
|  | so delimit it here before starting new word */ | 
|  | if (w_addword (pwordexp, *word) == WRDE_NOSPACE) | 
|  | goto no_space; | 
|  |  | 
|  | *word = w_newword (word_length, max_length); | 
|  | } | 
|  |  | 
|  | copying = 1; | 
|  |  | 
|  | if (buffer[i] == '\n') /* happens if newline not in IFS */ | 
|  | maxnewlines++; | 
|  | else | 
|  | maxnewlines = 0; | 
|  |  | 
|  | *word = w_addchar (*word, word_length, max_length, | 
|  | buffer[i]); | 
|  | if (*word == NULL) | 
|  | goto no_space; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Chop off trailing newlines (required by POSIX.2)  */ | 
|  | /* Ensure we don't go back further than the beginning of the | 
|  | substitution (i.e. remove maxnewlines bytes at most) */ | 
|  | while (maxnewlines-- != 0 && | 
|  | *word_length > 0 && (*word)[*word_length - 1] == '\n') | 
|  | { | 
|  | (*word)[--*word_length] = '\0'; | 
|  |  | 
|  | /* If the last word was entirely newlines, turn it into a new word | 
|  | * which can be ignored if there's nothing following it. */ | 
|  | if (*word_length == 0) | 
|  | { | 
|  | free (*word); | 
|  | *word = w_newword (word_length, max_length); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | __close (fildes[0]); | 
|  | fildes[0] = -1; | 
|  |  | 
|  | /* Check for syntax error (re-execute but with "-n" flag) */ | 
|  | if (buflen < 1 && status != 0) | 
|  | { | 
|  | noexec = 1; | 
|  | goto again; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | no_space: | 
|  | __kill (pid, SIGKILL); | 
|  | TEMP_FAILURE_RETRY (__waitpid (pid, NULL, 0)); | 
|  | __close (fildes[0]); | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | parse_comm (char **word, size_t *word_length, size_t *max_length, | 
|  | const char *words, size_t *offset, int flags, wordexp_t *pwordexp, | 
|  | const char *ifs, const char *ifs_white) | 
|  | { | 
|  | /* We are poised just after "$(" */ | 
|  | int paren_depth = 1; | 
|  | int error = 0; | 
|  | int quoted = 0; /* 1 for singly-quoted, 2 for doubly-quoted */ | 
|  | size_t comm_length; | 
|  | size_t comm_maxlen; | 
|  | char *comm = w_newword (&comm_length, &comm_maxlen); | 
|  |  | 
|  | for (; words[*offset]; ++(*offset)) | 
|  | { | 
|  | switch (words[*offset]) | 
|  | { | 
|  | case '\'': | 
|  | if (quoted == 0) | 
|  | quoted = 1; | 
|  | else if (quoted == 1) | 
|  | quoted = 0; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case '"': | 
|  | if (quoted == 0) | 
|  | quoted = 2; | 
|  | else if (quoted == 2) | 
|  | quoted = 0; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case ')': | 
|  | if (!quoted && --paren_depth == 0) | 
|  | { | 
|  | /* Go -- give script to the shell */ | 
|  | if (comm) | 
|  | { | 
|  | #ifdef __libc_ptf_call | 
|  | /* We do not want the exec_comm call to be cut short | 
|  | by a thread cancellation since cleanup is very | 
|  | ugly.  Therefore disable cancellation for | 
|  | now.  */ | 
|  | // XXX Ideally we do want the thread being cancelable. | 
|  | // XXX If demand is there we'll change it. | 
|  | int state = PTHREAD_CANCEL_ENABLE; | 
|  | __libc_ptf_call (__pthread_setcancelstate, | 
|  | (PTHREAD_CANCEL_DISABLE, &state), 0); | 
|  | #endif | 
|  |  | 
|  | error = exec_comm (comm, word, word_length, max_length, | 
|  | flags, pwordexp, ifs, ifs_white); | 
|  |  | 
|  | #ifdef __libc_ptf_call | 
|  | __libc_ptf_call (__pthread_setcancelstate, | 
|  | (state, NULL), 0); | 
|  | #endif | 
|  |  | 
|  | free (comm); | 
|  | } | 
|  |  | 
|  | return error; | 
|  | } | 
|  |  | 
|  | /* This is just part of the script */ | 
|  | break; | 
|  |  | 
|  | case '(': | 
|  | if (!quoted) | 
|  | ++paren_depth; | 
|  | } | 
|  |  | 
|  | comm = w_addchar (comm, &comm_length, &comm_maxlen, words[*offset]); | 
|  | if (comm == NULL) | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  |  | 
|  | /* Premature end.  */ | 
|  | free (comm); | 
|  |  | 
|  | return WRDE_SYNTAX; | 
|  | } | 
|  |  | 
|  | #define CHAR_IN_SET(ch, char_set) \ | 
|  | (memchr (char_set "", ch, sizeof (char_set) - 1) != NULL) | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | parse_param (char **word, size_t *word_length, size_t *max_length, | 
|  | const char *words, size_t *offset, int flags, wordexp_t *pwordexp, | 
|  | const char *ifs, const char *ifs_white, int quoted) | 
|  | { | 
|  | /* We are poised just after "$" */ | 
|  | enum action | 
|  | { | 
|  | ACT_NONE, | 
|  | ACT_RP_SHORT_LEFT = '#', | 
|  | ACT_RP_LONG_LEFT = 'L', | 
|  | ACT_RP_SHORT_RIGHT = '%', | 
|  | ACT_RP_LONG_RIGHT = 'R', | 
|  | ACT_NULL_ERROR = '?', | 
|  | ACT_NULL_SUBST = '-', | 
|  | ACT_NONNULL_SUBST = '+', | 
|  | ACT_NULL_ASSIGN = '=' | 
|  | }; | 
|  | size_t env_length; | 
|  | size_t env_maxlen; | 
|  | size_t pat_length; | 
|  | size_t pat_maxlen; | 
|  | size_t start = *offset; | 
|  | char *env; | 
|  | char *pattern; | 
|  | char *value = NULL; | 
|  | enum action action = ACT_NONE; | 
|  | int depth = 0; | 
|  | int colon_seen = 0; | 
|  | int seen_hash = 0; | 
|  | int free_value = 0; | 
|  | int pattern_is_quoted = 0; /* 1 for singly-quoted, 2 for doubly-quoted */ | 
|  | int error; | 
|  | int special = 0; | 
|  | char buffer[21]; | 
|  | int brace = words[*offset] == '{'; | 
|  |  | 
|  | env = w_newword (&env_length, &env_maxlen); | 
|  | pattern = w_newword (&pat_length, &pat_maxlen); | 
|  |  | 
|  | if (brace) | 
|  | ++*offset; | 
|  |  | 
|  | /* First collect the parameter name. */ | 
|  |  | 
|  | if (words[*offset] == '#') | 
|  | { | 
|  | seen_hash = 1; | 
|  | if (!brace) | 
|  | goto envsubst; | 
|  | ++*offset; | 
|  | } | 
|  |  | 
|  | if (isalpha (words[*offset]) || words[*offset] == '_') | 
|  | { | 
|  | /* Normal parameter name. */ | 
|  | do | 
|  | { | 
|  | env = w_addchar (env, &env_length, &env_maxlen, | 
|  | words[*offset]); | 
|  | if (env == NULL) | 
|  | goto no_space; | 
|  | } | 
|  | while (isalnum (words[++*offset]) || words[*offset] == '_'); | 
|  | } | 
|  | else if (isdigit (words[*offset])) | 
|  | { | 
|  | /* Numeric parameter name. */ | 
|  | special = 1; | 
|  | do | 
|  | { | 
|  | env = w_addchar (env, &env_length, &env_maxlen, | 
|  | words[*offset]); | 
|  | if (env == NULL) | 
|  | goto no_space; | 
|  | if (!brace) | 
|  | goto envsubst; | 
|  | } | 
|  | while (isdigit(words[++*offset])); | 
|  | } | 
|  | else if (CHAR_IN_SET (words[*offset], "*@$")) | 
|  | { | 
|  | /* Special parameter. */ | 
|  | special = 1; | 
|  | env = w_addchar (env, &env_length, &env_maxlen, | 
|  | words[*offset]); | 
|  | if (env == NULL) | 
|  | goto no_space; | 
|  | ++*offset; | 
|  | } | 
|  | else | 
|  | { | 
|  | if (brace) | 
|  | goto syntax; | 
|  | } | 
|  |  | 
|  | if (brace) | 
|  | { | 
|  | /* Check for special action to be applied to the value. */ | 
|  | switch (words[*offset]) | 
|  | { | 
|  | case '}': | 
|  | /* Evaluate. */ | 
|  | goto envsubst; | 
|  |  | 
|  | case '#': | 
|  | action = ACT_RP_SHORT_LEFT; | 
|  | if (words[1 + *offset] == '#') | 
|  | { | 
|  | ++*offset; | 
|  | action = ACT_RP_LONG_LEFT; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case '%': | 
|  | action = ACT_RP_SHORT_RIGHT; | 
|  | if (words[1 + *offset] == '%') | 
|  | { | 
|  | ++*offset; | 
|  | action = ACT_RP_LONG_RIGHT; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case ':': | 
|  | if (!CHAR_IN_SET (words[1 + *offset], "-=?+")) | 
|  | goto syntax; | 
|  |  | 
|  | colon_seen = 1; | 
|  | action = words[++*offset]; | 
|  | break; | 
|  |  | 
|  | case '-': | 
|  | case '=': | 
|  | case '?': | 
|  | case '+': | 
|  | action = words[*offset]; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | goto syntax; | 
|  | } | 
|  |  | 
|  | /* Now collect the pattern, but don't expand it yet. */ | 
|  | ++*offset; | 
|  | for (; words[*offset]; ++(*offset)) | 
|  | { | 
|  | switch (words[*offset]) | 
|  | { | 
|  | case '{': | 
|  | if (!pattern_is_quoted) | 
|  | ++depth; | 
|  | break; | 
|  |  | 
|  | case '}': | 
|  | if (!pattern_is_quoted) | 
|  | { | 
|  | if (depth == 0) | 
|  | goto envsubst; | 
|  | --depth; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case '\\': | 
|  | if (pattern_is_quoted) | 
|  | /* Quoted; treat as normal character. */ | 
|  | break; | 
|  |  | 
|  | /* Otherwise, it's an escape: next character is literal. */ | 
|  | if (words[++*offset] == '\0') | 
|  | goto syntax; | 
|  |  | 
|  | pattern = w_addchar (pattern, &pat_length, &pat_maxlen, '\\'); | 
|  | if (pattern == NULL) | 
|  | goto no_space; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case '\'': | 
|  | if (pattern_is_quoted == 0) | 
|  | pattern_is_quoted = 1; | 
|  | else if (pattern_is_quoted == 1) | 
|  | pattern_is_quoted = 0; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case '"': | 
|  | if (pattern_is_quoted == 0) | 
|  | pattern_is_quoted = 2; | 
|  | else if (pattern_is_quoted == 2) | 
|  | pattern_is_quoted = 0; | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | pattern = w_addchar (pattern, &pat_length, &pat_maxlen, | 
|  | words[*offset]); | 
|  | if (pattern == NULL) | 
|  | goto no_space; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* End of input string -- remember to reparse the character that we | 
|  | * stopped at.  */ | 
|  | --(*offset); | 
|  |  | 
|  | envsubst: | 
|  | if (words[start] == '{' && words[*offset] != '}') | 
|  | goto syntax; | 
|  |  | 
|  | if (env == NULL) | 
|  | { | 
|  | if (seen_hash) | 
|  | { | 
|  | /* $# expands to the number of positional parameters */ | 
|  | buffer[20] = '\0'; | 
|  | value = _itoa_word (__libc_argc - 1, &buffer[20], 10, 0); | 
|  | seen_hash = 0; | 
|  | } | 
|  | else | 
|  | { | 
|  | /* Just $ on its own */ | 
|  | *offset = start - 1; | 
|  | *word = w_addchar (*word, word_length, max_length, '$'); | 
|  | return *word ? 0 : WRDE_NOSPACE; | 
|  | } | 
|  | } | 
|  | /* Is it a numeric parameter? */ | 
|  | else if (isdigit (env[0])) | 
|  | { | 
|  | int n = atoi (env); | 
|  |  | 
|  | if (n >= __libc_argc) | 
|  | /* Substitute NULL. */ | 
|  | value = NULL; | 
|  | else | 
|  | /* Replace with appropriate positional parameter. */ | 
|  | value = __libc_argv[n]; | 
|  | } | 
|  | /* Is it a special parameter? */ | 
|  | else if (special) | 
|  | { | 
|  | /* Is it `$$'? */ | 
|  | if (*env == '$') | 
|  | { | 
|  | buffer[20] = '\0'; | 
|  | value = _itoa_word (__getpid (), &buffer[20], 10, 0); | 
|  | } | 
|  | /* Is it `${#*}' or `${#@}'? */ | 
|  | else if ((*env == '*' || *env == '@') && seen_hash) | 
|  | { | 
|  | buffer[20] = '\0'; | 
|  | value = _itoa_word (__libc_argc > 0 ? __libc_argc - 1 : 0, | 
|  | &buffer[20], 10, 0); | 
|  | *word = w_addstr (*word, word_length, max_length, value); | 
|  | free (env); | 
|  | free (pattern); | 
|  | return *word ? 0 : WRDE_NOSPACE; | 
|  | } | 
|  | /* Is it `$*' or `$@' (unquoted) ? */ | 
|  | else if (*env == '*' || (*env == '@' && !quoted)) | 
|  | { | 
|  | size_t plist_len = 0; | 
|  | int p; | 
|  | char *end; | 
|  |  | 
|  | /* Build up value parameter by parameter (copy them) */ | 
|  | for (p = 1; __libc_argv[p]; ++p) | 
|  | plist_len += strlen (__libc_argv[p]) + 1; /* for space */ | 
|  | value = malloc (plist_len); | 
|  | if (value == NULL) | 
|  | goto no_space; | 
|  | end = value; | 
|  | *end = 0; | 
|  | for (p = 1; __libc_argv[p]; ++p) | 
|  | { | 
|  | if (p > 1) | 
|  | *end++ = ' '; | 
|  | end = __stpcpy (end, __libc_argv[p]); | 
|  | } | 
|  |  | 
|  | free_value = 1; | 
|  | } | 
|  | else | 
|  | { | 
|  | /* Must be a quoted `$@' */ | 
|  | assert (*env == '@' && quoted); | 
|  |  | 
|  | /* Each parameter is a separate word ("$@") */ | 
|  | if (__libc_argc == 2) | 
|  | value = __libc_argv[1]; | 
|  | else if (__libc_argc > 2) | 
|  | { | 
|  | int p; | 
|  |  | 
|  | /* Append first parameter to current word. */ | 
|  | value = w_addstr (*word, word_length, max_length, | 
|  | __libc_argv[1]); | 
|  | if (value == NULL || w_addword (pwordexp, value)) | 
|  | goto no_space; | 
|  |  | 
|  | for (p = 2; __libc_argv[p + 1]; p++) | 
|  | { | 
|  | char *newword = __strdup (__libc_argv[p]); | 
|  | if (newword == NULL || w_addword (pwordexp, newword)) | 
|  | goto no_space; | 
|  | } | 
|  |  | 
|  | /* Start a new word with the last parameter. */ | 
|  | *word = w_newword (word_length, max_length); | 
|  | value = __libc_argv[p]; | 
|  | } | 
|  | else | 
|  | { | 
|  | free (env); | 
|  | free (pattern); | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | } | 
|  | else | 
|  | value = getenv (env); | 
|  |  | 
|  | if (value == NULL && (flags & WRDE_UNDEF)) | 
|  | { | 
|  | /* Variable not defined. */ | 
|  | error = WRDE_BADVAL; | 
|  | goto do_error; | 
|  | } | 
|  |  | 
|  | if (action != ACT_NONE) | 
|  | { | 
|  | int expand_pattern = 0; | 
|  |  | 
|  | /* First, find out if we need to expand pattern (i.e. if we will | 
|  | * use it). */ | 
|  | switch (action) | 
|  | { | 
|  | case ACT_RP_SHORT_LEFT: | 
|  | case ACT_RP_LONG_LEFT: | 
|  | case ACT_RP_SHORT_RIGHT: | 
|  | case ACT_RP_LONG_RIGHT: | 
|  | /* Always expand for these. */ | 
|  | expand_pattern = 1; | 
|  | break; | 
|  |  | 
|  | case ACT_NULL_ERROR: | 
|  | case ACT_NULL_SUBST: | 
|  | case ACT_NULL_ASSIGN: | 
|  | if (!value || (!*value && colon_seen)) | 
|  | /* If param is unset, or set but null and a colon has been seen, | 
|  | the expansion of the pattern will be needed. */ | 
|  | expand_pattern = 1; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case ACT_NONNULL_SUBST: | 
|  | /* Expansion of word will be needed if parameter is set and not null, | 
|  | or set null but no colon has been seen. */ | 
|  | if (value && (*value || !colon_seen)) | 
|  | expand_pattern = 1; | 
|  |  | 
|  | break; | 
|  |  | 
|  | default: | 
|  | assert (! "Unrecognised action!"); | 
|  | } | 
|  |  | 
|  | if (expand_pattern) | 
|  | { | 
|  | /* We need to perform tilde expansion, parameter expansion, | 
|  | command substitution, and arithmetic expansion.  We also | 
|  | have to be a bit careful with wildcard characters, as | 
|  | pattern might be given to fnmatch soon.  To do this, we | 
|  | convert quotes to escapes. */ | 
|  |  | 
|  | char *expanded; | 
|  | size_t exp_len; | 
|  | size_t exp_maxl; | 
|  | char *p; | 
|  | int quoted = 0; /* 1: single quotes; 2: double */ | 
|  |  | 
|  | expanded = w_newword (&exp_len, &exp_maxl); | 
|  | for (p = pattern; p && *p; p++) | 
|  | { | 
|  | size_t offset; | 
|  |  | 
|  | switch (*p) | 
|  | { | 
|  | case '"': | 
|  | if (quoted == 2) | 
|  | quoted = 0; | 
|  | else if (quoted == 0) | 
|  | quoted = 2; | 
|  | else break; | 
|  |  | 
|  | continue; | 
|  |  | 
|  | case '\'': | 
|  | if (quoted == 1) | 
|  | quoted = 0; | 
|  | else if (quoted == 0) | 
|  | quoted = 1; | 
|  | else break; | 
|  |  | 
|  | continue; | 
|  |  | 
|  | case '*': | 
|  | case '?': | 
|  | if (quoted) | 
|  | { | 
|  | /* Convert quoted wildchar to escaped wildchar. */ | 
|  | expanded = w_addchar (expanded, &exp_len, | 
|  | &exp_maxl, '\\'); | 
|  |  | 
|  | if (expanded == NULL) | 
|  | goto no_space; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case '$': | 
|  | offset = 0; | 
|  | error = parse_dollars (&expanded, &exp_len, &exp_maxl, p, | 
|  | &offset, flags, NULL, NULL, NULL, 1); | 
|  | if (error) | 
|  | { | 
|  | if (free_value) | 
|  | free (value); | 
|  |  | 
|  | free (expanded); | 
|  |  | 
|  | goto do_error; | 
|  | } | 
|  |  | 
|  | p += offset; | 
|  | continue; | 
|  |  | 
|  | case '~': | 
|  | if (quoted || exp_len) | 
|  | break; | 
|  |  | 
|  | offset = 0; | 
|  | error = parse_tilde (&expanded, &exp_len, &exp_maxl, p, | 
|  | &offset, 0); | 
|  | if (error) | 
|  | { | 
|  | if (free_value) | 
|  | free (value); | 
|  |  | 
|  | free (expanded); | 
|  |  | 
|  | goto do_error; | 
|  | } | 
|  |  | 
|  | p += offset; | 
|  | continue; | 
|  |  | 
|  | case '\\': | 
|  | expanded = w_addchar (expanded, &exp_len, &exp_maxl, '\\'); | 
|  | ++p; | 
|  | assert (*p); /* checked when extracted initially */ | 
|  | if (expanded == NULL) | 
|  | goto no_space; | 
|  | } | 
|  |  | 
|  | expanded = w_addchar (expanded, &exp_len, &exp_maxl, *p); | 
|  |  | 
|  | if (expanded == NULL) | 
|  | goto no_space; | 
|  | } | 
|  |  | 
|  | free (pattern); | 
|  |  | 
|  | pattern = expanded; | 
|  | } | 
|  |  | 
|  | switch (action) | 
|  | { | 
|  | case ACT_RP_SHORT_LEFT: | 
|  | case ACT_RP_LONG_LEFT: | 
|  | case ACT_RP_SHORT_RIGHT: | 
|  | case ACT_RP_LONG_RIGHT: | 
|  | { | 
|  | char *p; | 
|  | char c; | 
|  | char *end; | 
|  |  | 
|  | if (value == NULL || pattern == NULL || *pattern == '\0') | 
|  | break; | 
|  |  | 
|  | end = value + strlen (value); | 
|  |  | 
|  | switch (action) | 
|  | { | 
|  | case ACT_RP_SHORT_LEFT: | 
|  | for (p = value; p <= end; ++p) | 
|  | { | 
|  | c = *p; | 
|  | *p = '\0'; | 
|  | if (fnmatch (pattern, value, 0) != FNM_NOMATCH) | 
|  | { | 
|  | *p = c; | 
|  | if (free_value) | 
|  | { | 
|  | char *newval = __strdup (p); | 
|  | if (newval == NULL) | 
|  | { | 
|  | free (value); | 
|  | goto no_space; | 
|  | } | 
|  | free (value); | 
|  | value = newval; | 
|  | } | 
|  | else | 
|  | value = p; | 
|  | break; | 
|  | } | 
|  | *p = c; | 
|  | } | 
|  |  | 
|  | break; | 
|  |  | 
|  | case ACT_RP_LONG_LEFT: | 
|  | for (p = end; p >= value; --p) | 
|  | { | 
|  | c = *p; | 
|  | *p = '\0'; | 
|  | if (fnmatch (pattern, value, 0) != FNM_NOMATCH) | 
|  | { | 
|  | *p = c; | 
|  | if (free_value) | 
|  | { | 
|  | char *newval = __strdup (p); | 
|  | if (newval == NULL) | 
|  | { | 
|  | free (value); | 
|  | goto no_space; | 
|  | } | 
|  | free (value); | 
|  | value = newval; | 
|  | } | 
|  | else | 
|  | value = p; | 
|  | break; | 
|  | } | 
|  | *p = c; | 
|  | } | 
|  |  | 
|  | break; | 
|  |  | 
|  | case ACT_RP_SHORT_RIGHT: | 
|  | for (p = end; p >= value; --p) | 
|  | { | 
|  | if (fnmatch (pattern, p, 0) != FNM_NOMATCH) | 
|  | { | 
|  | char *newval; | 
|  | newval = malloc (p - value + 1); | 
|  |  | 
|  | if (newval == NULL) | 
|  | { | 
|  | if (free_value) | 
|  | free (value); | 
|  | goto no_space; | 
|  | } | 
|  |  | 
|  | *(char *) __mempcpy (newval, value, p - value) = '\0'; | 
|  | if (free_value) | 
|  | free (value); | 
|  | value = newval; | 
|  | free_value = 1; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | break; | 
|  |  | 
|  | case ACT_RP_LONG_RIGHT: | 
|  | for (p = value; p <= end; ++p) | 
|  | { | 
|  | if (fnmatch (pattern, p, 0) != FNM_NOMATCH) | 
|  | { | 
|  | char *newval; | 
|  | newval = malloc (p - value + 1); | 
|  |  | 
|  | if (newval == NULL) | 
|  | { | 
|  | if (free_value) | 
|  | free (value); | 
|  | goto no_space; | 
|  | } | 
|  |  | 
|  | *(char *) __mempcpy (newval, value, p - value) = '\0'; | 
|  | if (free_value) | 
|  | free (value); | 
|  | value = newval; | 
|  | free_value = 1; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | break; | 
|  |  | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | case ACT_NULL_ERROR: | 
|  | if (value && *value) | 
|  | /* Substitute parameter */ | 
|  | break; | 
|  |  | 
|  | error = 0; | 
|  | if (!colon_seen && value) | 
|  | /* Substitute NULL */ | 
|  | ; | 
|  | else | 
|  | { | 
|  | const char *str = pattern; | 
|  |  | 
|  | if (str[0] == '\0') | 
|  | str = _("parameter null or not set"); | 
|  |  | 
|  | __fxprintf (NULL, "%s: %s\n", env, str); | 
|  | } | 
|  |  | 
|  | if (free_value) | 
|  | free (value); | 
|  | goto do_error; | 
|  |  | 
|  | case ACT_NULL_SUBST: | 
|  | if (value && *value) | 
|  | /* Substitute parameter */ | 
|  | break; | 
|  |  | 
|  | if (free_value) | 
|  | free (value); | 
|  |  | 
|  | if (!colon_seen && value) | 
|  | /* Substitute NULL */ | 
|  | goto success; | 
|  |  | 
|  | value = pattern ? __strdup (pattern) : pattern; | 
|  | free_value = 1; | 
|  |  | 
|  | if (pattern && !value) | 
|  | goto no_space; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case ACT_NONNULL_SUBST: | 
|  | if (value && (*value || !colon_seen)) | 
|  | { | 
|  | if (free_value) | 
|  | free (value); | 
|  |  | 
|  | value = pattern ? __strdup (pattern) : pattern; | 
|  | free_value = 1; | 
|  |  | 
|  | if (pattern && !value) | 
|  | goto no_space; | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* Substitute NULL */ | 
|  | if (free_value) | 
|  | free (value); | 
|  | goto success; | 
|  |  | 
|  | case ACT_NULL_ASSIGN: | 
|  | if (value && *value) | 
|  | /* Substitute parameter */ | 
|  | break; | 
|  |  | 
|  | if (!colon_seen && value) | 
|  | { | 
|  | /* Substitute NULL */ | 
|  | if (free_value) | 
|  | free (value); | 
|  | goto success; | 
|  | } | 
|  |  | 
|  | if (free_value) | 
|  | free (value); | 
|  |  | 
|  | value = pattern ? __strdup (pattern) : pattern; | 
|  | free_value = 1; | 
|  |  | 
|  | if (pattern && !value) | 
|  | goto no_space; | 
|  |  | 
|  | __setenv (env, value ?: "", 1); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | assert (! "Unrecognised action!"); | 
|  | } | 
|  | } | 
|  |  | 
|  | free (env); | 
|  | env = NULL; | 
|  | free (pattern); | 
|  | pattern = NULL; | 
|  |  | 
|  | if (seen_hash) | 
|  | { | 
|  | char param_length[21]; | 
|  | param_length[20] = '\0'; | 
|  | *word = w_addstr (*word, word_length, max_length, | 
|  | _itoa_word (value ? strlen (value) : 0, | 
|  | ¶m_length[20], 10, 0)); | 
|  | if (free_value) | 
|  | { | 
|  | assert (value != NULL); | 
|  | free (value); | 
|  | } | 
|  |  | 
|  | return *word ? 0 : WRDE_NOSPACE; | 
|  | } | 
|  |  | 
|  | if (value == NULL) | 
|  | return 0; | 
|  |  | 
|  | if (quoted || !pwordexp) | 
|  | { | 
|  | /* Quoted - no field split */ | 
|  | *word = w_addstr (*word, word_length, max_length, value); | 
|  | if (free_value) | 
|  | free (value); | 
|  |  | 
|  | return *word ? 0 : WRDE_NOSPACE; | 
|  | } | 
|  | else | 
|  | { | 
|  | /* Need to field-split */ | 
|  | char *value_copy = __strdup (value); /* Don't modify value */ | 
|  | char *field_begin = value_copy; | 
|  | int seen_nonws_ifs = 0; | 
|  |  | 
|  | if (free_value) | 
|  | free (value); | 
|  |  | 
|  | if (value_copy == NULL) | 
|  | goto no_space; | 
|  |  | 
|  | do | 
|  | { | 
|  | char *field_end = field_begin; | 
|  | char *next_field; | 
|  |  | 
|  | /* If this isn't the first field, start a new word */ | 
|  | if (field_begin != value_copy) | 
|  | { | 
|  | if (w_addword (pwordexp, *word) == WRDE_NOSPACE) | 
|  | { | 
|  | free (value_copy); | 
|  | goto no_space; | 
|  | } | 
|  |  | 
|  | *word = w_newword (word_length, max_length); | 
|  | } | 
|  |  | 
|  | /* Skip IFS whitespace before the field */ | 
|  | field_begin += strspn (field_begin, ifs_white); | 
|  |  | 
|  | if (!seen_nonws_ifs && *field_begin == 0) | 
|  | /* Nothing but whitespace */ | 
|  | break; | 
|  |  | 
|  | /* Search for the end of the field */ | 
|  | field_end = field_begin + strcspn (field_begin, ifs); | 
|  |  | 
|  | /* Set up pointer to the character after end of field and | 
|  | skip whitespace IFS after it. */ | 
|  | next_field = field_end + strspn (field_end, ifs_white); | 
|  |  | 
|  | /* Skip at most one non-whitespace IFS character after the field */ | 
|  | seen_nonws_ifs = 0; | 
|  | if (*next_field && strchr (ifs, *next_field)) | 
|  | { | 
|  | seen_nonws_ifs = 1; | 
|  | next_field++; | 
|  | } | 
|  |  | 
|  | /* Null-terminate it */ | 
|  | *field_end = 0; | 
|  |  | 
|  | /* Tag a copy onto the current word */ | 
|  | *word = w_addstr (*word, word_length, max_length, field_begin); | 
|  |  | 
|  | if (*word == NULL && *field_begin != '\0') | 
|  | { | 
|  | free (value_copy); | 
|  | goto no_space; | 
|  | } | 
|  |  | 
|  | field_begin = next_field; | 
|  | } | 
|  | while (seen_nonws_ifs || *field_begin); | 
|  |  | 
|  | free (value_copy); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | success: | 
|  | error = 0; | 
|  | goto do_error; | 
|  |  | 
|  | no_space: | 
|  | error = WRDE_NOSPACE; | 
|  | goto do_error; | 
|  |  | 
|  | syntax: | 
|  | error = WRDE_SYNTAX; | 
|  |  | 
|  | do_error: | 
|  | free (env); | 
|  |  | 
|  | free (pattern); | 
|  |  | 
|  | return error; | 
|  | } | 
|  |  | 
|  | #undef CHAR_IN_SET | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | parse_dollars (char **word, size_t *word_length, size_t *max_length, | 
|  | const char *words, size_t *offset, int flags, | 
|  | wordexp_t *pwordexp, const char *ifs, const char *ifs_white, | 
|  | int quoted) | 
|  | { | 
|  | /* We are poised _at_ "$" */ | 
|  | switch (words[1 + *offset]) | 
|  | { | 
|  | case '"': | 
|  | case '\'': | 
|  | case 0: | 
|  | *word = w_addchar (*word, word_length, max_length, '$'); | 
|  | return *word ? 0 : WRDE_NOSPACE; | 
|  |  | 
|  | case '(': | 
|  | if (words[2 + *offset] == '(') | 
|  | { | 
|  | /* Differentiate between $((1+3)) and $((echo);(ls)) */ | 
|  | int i = 3 + *offset; | 
|  | int depth = 0; | 
|  | while (words[i] && !(depth == 0 && words[i] == ')')) | 
|  | { | 
|  | if (words[i] == '(') | 
|  | ++depth; | 
|  | else if (words[i] == ')') | 
|  | --depth; | 
|  |  | 
|  | ++i; | 
|  | } | 
|  |  | 
|  | if (words[i] == ')' && words[i + 1] == ')') | 
|  | { | 
|  | (*offset) += 3; | 
|  | /* Call parse_arith -- 0 is for "no brackets" */ | 
|  | return parse_arith (word, word_length, max_length, words, offset, | 
|  | flags, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | (*offset) += 2; | 
|  | return parse_comm (word, word_length, max_length, words, offset, flags, | 
|  | quoted? NULL : pwordexp, ifs, ifs_white); | 
|  |  | 
|  | case '[': | 
|  | (*offset) += 2; | 
|  | /* Call parse_arith -- 1 is for "brackets" */ | 
|  | return parse_arith (word, word_length, max_length, words, offset, flags, | 
|  | 1); | 
|  |  | 
|  | case '{': | 
|  | default: | 
|  | ++(*offset);	/* parse_param needs to know if "{" is there */ | 
|  | return parse_param (word, word_length, max_length, words, offset, flags, | 
|  | pwordexp, ifs, ifs_white, quoted); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | parse_backtick (char **word, size_t *word_length, size_t *max_length, | 
|  | const char *words, size_t *offset, int flags, | 
|  | wordexp_t *pwordexp, const char *ifs, const char *ifs_white) | 
|  | { | 
|  | /* We are poised just after "`" */ | 
|  | int error; | 
|  | int squoting = 0; | 
|  | size_t comm_length; | 
|  | size_t comm_maxlen; | 
|  | char *comm = w_newword (&comm_length, &comm_maxlen); | 
|  |  | 
|  | for (; words[*offset]; ++(*offset)) | 
|  | { | 
|  | switch (words[*offset]) | 
|  | { | 
|  | case '`': | 
|  | /* Go -- give the script to the shell */ | 
|  | error = exec_comm (comm, word, word_length, max_length, flags, | 
|  | pwordexp, ifs, ifs_white); | 
|  | free (comm); | 
|  | return error; | 
|  |  | 
|  | case '\\': | 
|  | if (squoting) | 
|  | { | 
|  | error = parse_qtd_backslash (&comm, &comm_length, &comm_maxlen, | 
|  | words, offset); | 
|  |  | 
|  | if (error) | 
|  | { | 
|  | free (comm); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | error = parse_backslash (&comm, &comm_length, &comm_maxlen, words, | 
|  | offset); | 
|  |  | 
|  | if (error) | 
|  | { | 
|  | free (comm); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | break; | 
|  |  | 
|  | case '\'': | 
|  | squoting = 1 - squoting; | 
|  | default: | 
|  | comm = w_addchar (comm, &comm_length, &comm_maxlen, words[*offset]); | 
|  | if (comm == NULL) | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Premature end */ | 
|  | free (comm); | 
|  | return WRDE_SYNTAX; | 
|  | } | 
|  |  | 
|  | static int | 
|  | internal_function | 
|  | parse_dquote (char **word, size_t *word_length, size_t *max_length, | 
|  | const char *words, size_t *offset, int flags, | 
|  | wordexp_t *pwordexp, const char * ifs, const char * ifs_white) | 
|  | { | 
|  | /* We are poised just after a double-quote */ | 
|  | int error; | 
|  |  | 
|  | for (; words[*offset]; ++(*offset)) | 
|  | { | 
|  | switch (words[*offset]) | 
|  | { | 
|  | case '"': | 
|  | return 0; | 
|  |  | 
|  | case '$': | 
|  | error = parse_dollars (word, word_length, max_length, words, offset, | 
|  | flags, pwordexp, ifs, ifs_white, 1); | 
|  | /* The ``1'' here is to tell parse_dollars not to | 
|  | * split the fields.  It may need to, however ("$@"). | 
|  | */ | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case '`': | 
|  | ++(*offset); | 
|  | error = parse_backtick (word, word_length, max_length, words, | 
|  | offset, flags, NULL, NULL, NULL); | 
|  | /* The first NULL here is to tell parse_backtick not to | 
|  | * split the fields. | 
|  | */ | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case '\\': | 
|  | error = parse_qtd_backslash (word, word_length, max_length, words, | 
|  | offset); | 
|  |  | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | break; | 
|  |  | 
|  | default: | 
|  | *word = w_addchar (*word, word_length, max_length, words[*offset]); | 
|  | if (*word == NULL) | 
|  | return WRDE_NOSPACE; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Unterminated string */ | 
|  | return WRDE_SYNTAX; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * wordfree() is to be called after pwordexp is finished with. | 
|  | */ | 
|  |  | 
|  | void | 
|  | wordfree (wordexp_t *pwordexp) | 
|  | { | 
|  |  | 
|  | /* wordexp can set pwordexp to NULL */ | 
|  | if (pwordexp && pwordexp->we_wordv) | 
|  | { | 
|  | char **wordv = pwordexp->we_wordv; | 
|  |  | 
|  | for (wordv += pwordexp->we_offs; *wordv; ++wordv) | 
|  | free (*wordv); | 
|  |  | 
|  | free (pwordexp->we_wordv); | 
|  | pwordexp->we_wordv = NULL; | 
|  | } | 
|  | } | 
|  | libc_hidden_def (wordfree) | 
|  |  | 
|  | /* | 
|  | * wordexp() | 
|  | */ | 
|  |  | 
|  | int | 
|  | wordexp (const char *words, wordexp_t *pwordexp, int flags) | 
|  | { | 
|  | size_t words_offset; | 
|  | size_t word_length; | 
|  | size_t max_length; | 
|  | char *word = w_newword (&word_length, &max_length); | 
|  | int error; | 
|  | char *ifs; | 
|  | char ifs_white[4]; | 
|  | wordexp_t old_word = *pwordexp; | 
|  |  | 
|  | if (flags & WRDE_REUSE) | 
|  | { | 
|  | /* Minimal implementation of WRDE_REUSE for now */ | 
|  | wordfree (pwordexp); | 
|  | old_word.we_wordv = NULL; | 
|  | } | 
|  |  | 
|  | if ((flags & WRDE_APPEND) == 0) | 
|  | { | 
|  | pwordexp->we_wordc = 0; | 
|  |  | 
|  | if (flags & WRDE_DOOFFS) | 
|  | { | 
|  | pwordexp->we_wordv = calloc (1 + pwordexp->we_offs, sizeof (char *)); | 
|  | if (pwordexp->we_wordv == NULL) | 
|  | { | 
|  | error = WRDE_NOSPACE; | 
|  | goto do_error; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | pwordexp->we_wordv = calloc (1, sizeof (char *)); | 
|  | if (pwordexp->we_wordv == NULL) | 
|  | { | 
|  | error = WRDE_NOSPACE; | 
|  | goto do_error; | 
|  | } | 
|  |  | 
|  | pwordexp->we_offs = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Find out what the field separators are. | 
|  | * There are two types: whitespace and non-whitespace. | 
|  | */ | 
|  | ifs = getenv ("IFS"); | 
|  |  | 
|  | if (ifs == NULL) | 
|  | /* IFS unset - use <space><tab><newline>. */ | 
|  | ifs = strcpy (ifs_white, " \t\n"); | 
|  | else | 
|  | { | 
|  | char *ifsch = ifs; | 
|  | char *whch = ifs_white; | 
|  |  | 
|  | while (*ifsch != '\0') | 
|  | { | 
|  | if (*ifsch == ' ' || *ifsch == '\t' || *ifsch == '\n') | 
|  | { | 
|  | /* Whitespace IFS.  See first whether it is already in our | 
|  | collection.  */ | 
|  | char *runp = ifs_white; | 
|  |  | 
|  | while (runp < whch && *runp != *ifsch) | 
|  | ++runp; | 
|  |  | 
|  | if (runp == whch) | 
|  | *whch++ = *ifsch; | 
|  | } | 
|  |  | 
|  | ++ifsch; | 
|  | } | 
|  | *whch = '\0'; | 
|  | } | 
|  |  | 
|  | for (words_offset = 0 ; words[words_offset] ; ++words_offset) | 
|  | switch (words[words_offset]) | 
|  | { | 
|  | case '\\': | 
|  | error = parse_backslash (&word, &word_length, &max_length, words, | 
|  | &words_offset); | 
|  |  | 
|  | if (error) | 
|  | goto do_error; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case '$': | 
|  | error = parse_dollars (&word, &word_length, &max_length, words, | 
|  | &words_offset, flags, pwordexp, ifs, ifs_white, | 
|  | 0); | 
|  |  | 
|  | if (error) | 
|  | goto do_error; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case '`': | 
|  | ++words_offset; | 
|  | error = parse_backtick (&word, &word_length, &max_length, words, | 
|  | &words_offset, flags, pwordexp, ifs, | 
|  | ifs_white); | 
|  |  | 
|  | if (error) | 
|  | goto do_error; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case '"': | 
|  | ++words_offset; | 
|  | error = parse_dquote (&word, &word_length, &max_length, words, | 
|  | &words_offset, flags, pwordexp, ifs, ifs_white); | 
|  |  | 
|  | if (error) | 
|  | goto do_error; | 
|  |  | 
|  | if (!word_length) | 
|  | { | 
|  | error = w_addword (pwordexp, NULL); | 
|  |  | 
|  | if (error) | 
|  | return error; | 
|  | } | 
|  |  | 
|  | break; | 
|  |  | 
|  | case '\'': | 
|  | ++words_offset; | 
|  | error = parse_squote (&word, &word_length, &max_length, words, | 
|  | &words_offset); | 
|  |  | 
|  | if (error) | 
|  | goto do_error; | 
|  |  | 
|  | if (!word_length) | 
|  | { | 
|  | error = w_addword (pwordexp, NULL); | 
|  |  | 
|  | if (error) | 
|  | return error; | 
|  | } | 
|  |  | 
|  | break; | 
|  |  | 
|  | case '~': | 
|  | error = parse_tilde (&word, &word_length, &max_length, words, | 
|  | &words_offset, pwordexp->we_wordc); | 
|  |  | 
|  | if (error) | 
|  | goto do_error; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case '*': | 
|  | case '[': | 
|  | case '?': | 
|  | error = parse_glob (&word, &word_length, &max_length, words, | 
|  | &words_offset, flags, pwordexp, ifs, ifs_white); | 
|  |  | 
|  | if (error) | 
|  | goto do_error; | 
|  |  | 
|  | break; | 
|  |  | 
|  | default: | 
|  | /* Is it a word separator? */ | 
|  | if (strchr (" \t", words[words_offset]) == NULL) | 
|  | { | 
|  | char ch = words[words_offset]; | 
|  |  | 
|  | /* Not a word separator -- but is it a valid word char? */ | 
|  | if (strchr ("\n|&;<>(){}", ch)) | 
|  | { | 
|  | /* Fail */ | 
|  | error = WRDE_BADCHAR; | 
|  | goto do_error; | 
|  | } | 
|  |  | 
|  | /* "Ordinary" character -- add it to word */ | 
|  | word = w_addchar (word, &word_length, &max_length, | 
|  | ch); | 
|  | if (word == NULL) | 
|  | { | 
|  | error = WRDE_NOSPACE; | 
|  | goto do_error; | 
|  | } | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* If a word has been delimited, add it to the list. */ | 
|  | if (word != NULL) | 
|  | { | 
|  | error = w_addword (pwordexp, word); | 
|  | if (error) | 
|  | goto do_error; | 
|  | } | 
|  |  | 
|  | word = w_newword (&word_length, &max_length); | 
|  | } | 
|  |  | 
|  | /* End of string */ | 
|  |  | 
|  | /* There was a word separator at the end */ | 
|  | if (word == NULL) /* i.e. w_newword */ | 
|  | return 0; | 
|  |  | 
|  | /* There was no field separator at the end */ | 
|  | return w_addword (pwordexp, word); | 
|  |  | 
|  | do_error: | 
|  | /* Error: | 
|  | *	free memory used (unless error is WRDE_NOSPACE), and | 
|  | *	set pwordexp members back to what they were. | 
|  | */ | 
|  |  | 
|  | free (word); | 
|  |  | 
|  | if (error == WRDE_NOSPACE) | 
|  | return WRDE_NOSPACE; | 
|  |  | 
|  | if ((flags & WRDE_APPEND) == 0) | 
|  | wordfree (pwordexp); | 
|  |  | 
|  | *pwordexp = old_word; | 
|  | return error; | 
|  | } |