1998-03-12 13:11  Tim Waugh  <tim@cyberelk.demon.co.uk>

	* posix/wordexp-test.c: More tests.
	(main): Set up arena for pathname expansion tests (in a temporary
	directory).
	(testit): Don't check word count or word vector if return value is
	non-zero.

	* posix/wordexp.c (exec_comm): Always chop off terminating
	linefeed (just like bash does).
	(parse_param): Change lots of occurrences of "if (!*env)" to "if
	(!env || !*env)".
	(parse_param): For assignment inside parameter expansion, use all
	expanded words in assignment rather than just the first.
	(parse_param): Corrected return value for parameter expansion of
	an unset variable when WRDE_UNDEF is in effect.
	(parse_dollars): Don't field-split if quoted.
	(wordexp): Opening brace character isn't allowed unquoted.

1998-03-12  Ulrich Drepper  <drepper@cygnus.com>

	* elf/dlerror.c: Fix concurrency problems with error string and
	number.

	* elf/dl-deps.c (_dl_map_object_deps): Print debug info when
	filter/auxiliary are being loaded.

1998-03-12 14:24  Ulrich Drepper  <drepper@cygnus.com>

	* elf/dlerror.c (last_object_name): Removed.
	(dlerror): Don't use last_object_name.
	(_dl_error_run): Omit second argument to _dl_catch_error.
	* elf/dl-error.c (struct catch): Remove objname member.
	(_dl_signal_error): Construct string including objname when given.
	(_dl_catch_error): Remove objname handling.
	* elf/link.h (_dl_catch_error): Fix prototype and comment.
	* elf/rtld.c (dl_main): Call _dl_catch_error correctly.
	* elf/dl-deps.c (_dl_map_objet_deps): Likewise.
	* nss/nsswitch.c (nss_dlerror_run): Likewise.
	* iconv/gconv_dl.c (dlerror_run): Likewise.
	[Corrects PR libc/501].
This commit is contained in:
Ulrich Drepper 1998-03-12 17:26:19 +00:00
parent eb13b9a02d
commit 8d9618b7f6
11 changed files with 302 additions and 79 deletions

View File

@ -1,3 +1,45 @@
1998-03-12 13:11 Tim Waugh <tim@cyberelk.demon.co.uk>
* posix/wordexp-test.c: More tests.
(main): Set up arena for pathname expansion tests (in a temporary
directory).
(testit): Don't check word count or word vector if return value is
non-zero.
* posix/wordexp.c (exec_comm): Always chop off terminating
linefeed (just like bash does).
(parse_param): Change lots of occurrences of "if (!*env)" to "if
(!env || !*env)".
(parse_param): For assignment inside parameter expansion, use all
expanded words in assignment rather than just the first.
(parse_param): Corrected return value for parameter expansion of
an unset variable when WRDE_UNDEF is in effect.
(parse_dollars): Don't field-split if quoted.
(wordexp): Opening brace character isn't allowed unquoted.
1998-03-12 Ulrich Drepper <drepper@cygnus.com>
* elf/dlerror.c: Fix concurrency problems with error string and
number.
* elf/dl-deps.c (_dl_map_object_deps): Print debug info when
filter/auxiliary are being loaded.
1998-03-12 14:24 Ulrich Drepper <drepper@cygnus.com>
* elf/dlerror.c (last_object_name): Removed.
(dlerror): Don't use last_object_name.
(_dl_error_run): Omit second argument to _dl_catch_error.
* elf/dl-error.c (struct catch): Remove objname member.
(_dl_signal_error): Construct string including objname when given.
(_dl_catch_error): Remove objname handling.
* elf/link.h (_dl_catch_error): Fix prototype and comment.
* elf/rtld.c (dl_main): Call _dl_catch_error correctly.
* elf/dl-deps.c (_dl_map_objet_deps): Likewise.
* nss/nsswitch.c (nss_dlerror_run): Likewise.
* iconv/gconv_dl.c (dlerror_run): Likewise.
[Corrects PR libc/501].
1998-03-12 Matthias Urlichs <smurf@noris.de>
* nscd/nscd.c: Ignore SIGPIPE.

View File

@ -194,7 +194,6 @@ _dl_map_object_deps (struct link_map *map,
else if (d->d_tag == DT_AUXILIARY || d->d_tag == DT_FILTER)
{
char *errstring;
const char *objname;
struct list *newp;
if (d->d_tag == DT_AUXILIARY)
@ -202,9 +201,18 @@ _dl_map_object_deps (struct link_map *map,
/* Store the tag in the argument structure. */
args.d = d;
/* Say that we are about to load an auxiliary library. */
if (_dl_debug_libs)
_dl_debug_message ("load auxiliary object=",
strtab + d->d_un.d_val,
" requested by file=",
l->l_name[0]
? l->l_name : _dl_argv[0],
"\n", NULL);
/* We must be prepared that the addressed shared
object is not available. */
if (_dl_catch_error (&errstring, &objname, openaux, &args))
if (_dl_catch_error (&errstring, openaux, &args))
{
/* We are not interested in the error message. */
assert (errstring != NULL);
@ -215,11 +223,22 @@ _dl_map_object_deps (struct link_map *map,
}
}
else
/* For filter objects the dependency must be available. */
args.aux = _dl_map_object (l, strtab + d->d_un.d_val, 0,
(l->l_type == lt_executable
? lt_library : l->l_type),
trace_mode);
{
/* Say that we are about to load an auxiliary library. */
if (_dl_debug_libs)
_dl_debug_message ("load filtered object=",
strtab + d->d_un.d_val,
" requested by file=",
l->l_name[0]
? l->l_name : _dl_argv[0],
"\n", NULL);
/* For filter objects the dependency must be available. */
args.aux = _dl_map_object (l, strtab + d->d_un.d_val, 0,
(l->l_type == lt_executable
? lt_library : l->l_type),
trace_mode);
}
/* The auxiliary object is actually available.
Incorporate the map in all the lists. */

View File

@ -31,7 +31,6 @@ extern char *_strerror_internal __P ((int, char *, size_t));
struct catch
{
char *errstring; /* Error detail filled in here. */
const char *objname;
jmp_buf env; /* longjmp here on error. */
};
@ -59,11 +58,18 @@ _dl_signal_error (int errcode,
/* We are inside _dl_catch_error. Return to it. We have to
duplicate the error string since it might be allocated on the
stack. */
size_t len = strlen (errstring) + 1;
catch->errstring = malloc (len);
size_t objname_len = objname ? strlen (objname) + 2 : 0;
size_t errstring_len = strlen (errstring) + 1;
catch->errstring = malloc (objname_len + errstring_len);
if (catch->errstring != NULL)
memcpy (catch->errstring, errstring, len);
catch->objname = objname;
{
if (objname_len > 0)
{
memcpy (catch->errstring, objname, objname_len - 2);
memcpy (catch->errstring + objname_len - 2, ": ", 2);
}
memcpy (catch->errstring + objname_len, errstring, errstring_len);
}
longjmp (catch->env, errcode ?: -1);
}
else if (receiver)
@ -89,7 +95,6 @@ _dl_signal_error (int errcode,
int
_dl_catch_error (char **errstring,
const char **objname,
void (*operate) (void *),
void *args)
{
@ -101,7 +106,6 @@ _dl_catch_error (char **errstring,
/* Some systems (e.g., SPARC) handle constructors to local variables
inefficient. So we initialize `c' by hand. */
c.errstring = NULL;
c.objname = NULL;
old = catch;
errcode = setjmp (c.env);
@ -111,14 +115,12 @@ _dl_catch_error (char **errstring,
(*operate) (args);
catch = old;
*errstring = NULL;
*objname = NULL;
return 0;
}
/* We get here only if we longjmp'd out of OPERATE. */
catch = old;
*errstring = c.errstring;
*objname = c.objname;
return errcode == -1 ? 0 : errcode;
}

View File

@ -1,5 +1,5 @@
/* Return error detail for failing <dlfcn.h> functions.
Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
@ -22,16 +22,32 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <bits/libc-lock.h>
/* Type for storing results of dynamic loading actions. */
struct dl_action_result
{
int errcode;
char *errstring;
};
static struct dl_action_result last_result;
static struct dl_action_result *static_buf;
/* This is the key for the thread specific memory. */
static __libc_key_t key;
/* Destructor for the thread-specific data. */
static void init (void);
static void free_key_mem (void *mem);
static int last_errcode;
static char *last_errstring;
static const char *last_object_name;
char *
dlerror (void)
{
static char *buf;
char *ret;
struct dl_action_result *result;
if (buf)
{
@ -39,39 +55,96 @@ dlerror (void)
buf = NULL;
}
if (! last_errstring)
/* Get error string. */
if (__libc_internal_tsd_get != NULL)
{
result = (struct dl_action_result *) __libc_getspecific (key);
if (result == NULL)
result = &last_result;
}
else
result = &last_result;
if (! result->errstring)
return NULL;
if (last_errcode == 0 && ! last_object_name)
ret = (char *) last_errstring;
else if (last_errcode == 0)
ret = (asprintf (&buf, "%s: %s", last_object_name, last_errstring) == -1
? NULL : buf);
else if (! last_object_name)
ret = (asprintf (&buf, "%s: %s",
last_errstring, strerror (last_errcode)) == -1
? NULL : buf);
if (result->errcode == 0)
buf = result->errstring;
else
ret = (asprintf (&buf, "%s: %s: %s",
last_object_name, last_errstring,
strerror (last_errcode)) == -1
? NULL : buf);
{
if (asprintf (&buf, "%s: %s",
result->errstring, strerror (result->errcode)) == -1)
buf = NULL;
/* We don't need the error string anymore. */
free (result->errstring);
}
/* Reset the error indicator. */
free (last_errstring);
last_errstring = NULL;
return ret;
result->errstring = NULL;
return buf;
}
int
_dlerror_run (void (*operate) (void *), void *args)
{
if (last_errstring != NULL)
__libc_once_define (static, once);
struct dl_action_result *result;
/* If we have not yet initialized the buffer do it now. */
__libc_once (once, init);
/* Get error string and number. */
if (static_buf != NULL)
result = static_buf;
else
{
/* We don't use the static buffer and so we have a key. Use it
to get the thread-specific buffer. */
result = __libc_getspecific (key);
if (result == NULL)
{
result = (struct dl_action_result *) calloc (1, sizeof (*result));
if (result == NULL)
/* We are out of memory. Since this is no really critical
situation we carry on by using the global variable.
This might lead to conflicts between the threads but
they soon all will have memory problems. */
result = &last_result;
else
/* Set the tsd. */
__libc_setspecific (key, result);
}
}
if (result->errstring != NULL)
/* Free the error string from the last failed command. This can
happen if `dlerror' was not run after an error was found. */
free (last_errstring);
free (result->errstring);
last_errcode = _dl_catch_error (&last_errstring, &last_object_name,
operate, args);
return last_errstring != NULL;
result->errcode = _dl_catch_error (&result->errstring, operate, args);
return result->errstring != NULL;
}
/* Initialize buffers for results. */
static void
init (void)
{
if (__libc_key_create (&key, free_key_mem))
/* Creating the key failed. This means something really went
wrong. In any case use a static buffer which is better than
nothing. */
static_buf = &last_result;
}
/* Free the thread specific data, this is done if a thread terminates. */
static void
free_key_mem (void *mem)
{
free (mem);
__libc_setspecific (key, NULL);
}

View File

@ -313,13 +313,12 @@ extern void _dl_signal_error (int errcode,
const char *errstring);
/* Call OPERATE, catching errors from `dl_signal_error'. If there is no
error, *ERRSTRING is set to null. If there is an error, *ERRSTRING and
*OBJECT are set to the strings passed to _dl_signal_error, and the error
code passed is the return value. ERRSTRING if nonzero points to a
malloc'ed string which the caller has to free after use.
error, *ERRSTRING is set to null. If there is an error, *ERRSTRING is
set to a string constructed from the strings passed to _dl_signal_error,
and the error code passed is the return value. ERRSTRING if nonzero
points to a malloc'ed string which the caller has to free after use.
ARGS is passed as argument to OPERATE. */
extern int _dl_catch_error (char **errstring,
const char **object,
void (*operate) (void *),
void *args);

View File

@ -380,11 +380,10 @@ of this helper program; chances are you did not intend to run this program.\n\
if (mode == verify)
{
char *err_str = NULL;
const char *obj_name __attribute__ ((unused));
struct map_args args;
args.str = _dl_argv[0];
(void) _dl_catch_error (&err_str, &obj_name, map_doit, &args);
(void) _dl_catch_error (&err_str, map_doit, &args);
main_map = args.main_map;
if (err_str != NULL)
{

View File

@ -1,5 +1,5 @@
/* Handle loading/unloading of shared object for transformation.
Copyright (C) 1997 Free Software Foundation, Inc.
Copyright (C) 1997, 1998 Free Software Foundation, Inc.
This file is part of the GNU C Library.
Contributed by Ulrich Drepper <drepper@cygnus.com>, 1997.
@ -88,10 +88,9 @@ internal_function
dlerror_run (void (*operate) (void *), void *args)
{
char *last_errstring = NULL;
const char *last_object_name = NULL;
int result;
(void) _dl_catch_error (&last_errstring, &last_object_name, operate, args);
(void) _dl_catch_error (&last_errstring, operate, args);
result = last_errstring != NULL;
if (result)

View File

@ -1,5 +1,5 @@
/* Convert Inet number to ASCII representation.
Copyright (C) 1997 Free Software Foundation, Inc.
Copyright (C) 1997, 1998 Free Software Foundation, Inc.
This file is part of the GNU C Library.
Contributed by Ulrich Drepper <drepper@cygnus.com>, 1997.
@ -88,7 +88,7 @@ init (void)
}
/* free the thread specific data, this is done if a thread terminates. */
/* Free the thread specific data, this is done if a thread terminates. */
static void
free_key_mem (void *mem)
{

View File

@ -252,10 +252,9 @@ static int
nss_dlerror_run (void (*operate) (void *), void *args)
{
char *last_errstring = NULL;
const char *last_object_name = NULL;
int result;
(void) _dl_catch_error (&last_errstring, &last_object_name, operate, args);
(void) _dl_catch_error (&last_errstring, operate, args);
result = last_errstring != NULL;
if (result)

View File

@ -16,6 +16,10 @@
write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA. */
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
@ -33,10 +37,12 @@ struct test_case_struct
const char *wordv[10];
} test_case[] =
{
/* Simple word-splitting */
/* Simple field-splitting */
{ 0, NULL, "one", 0, 1, { "one", } },
{ 0, NULL, "one two", 0, 2, { "one", "two", } },
{ 0, NULL, "one two three", 0, 3, { "one", "two", "three", } },
{ 0, NULL, " \tfoo\t\tbar ", 0, 2, { "foo", "bar", } },
{ 0, NULL, " red , white blue", 0, 3, { "red", "white", "blue", } },
/* Simple parameter expansion */
{ 0, "foo", "${var}", 0, 1, { "foo", } },
@ -50,15 +56,14 @@ struct test_case_struct
/* Simple command substitution */
{ 0, NULL, "$(echo hello)", 0, 1, { "hello", } },
{ 0, NULL, "$( (echo hello) )", 0, 1, { "hello", } },
{ 0, NULL, "$((echo hello);(echo there))", 0, 2, { "hello", "there", } },
{ 0, NULL, "`echo one two`", 0, 2, { "one", "two", } },
/* Simple arithmetic expansion */
{ 0, NULL, "$((1 + 1))", 0, 1, { "2", } },
{ 0, NULL, "$((2-3))", 0, 1, { "-1", } },
{ 0, NULL, "$((-1))", 0, 1, { "-1", } },
/* Field splitting */
{ 0, NULL, " \tfoo\t\tbar ", 0, 2, { "foo", "bar", } },
{ 0, NULL, " red , white blue", 0, 3, { "red", "white", "blue", } },
{ 0, NULL, "$[50+20]", 0, 1, { "70", } },
/* Advanced parameter expansion */
{ 0, NULL, "${var:-bar}", 0, 1, { "bar", } },
@ -84,6 +89,50 @@ struct test_case_struct
{ 0, "borabora-island", "${var#*bora}", 0, 1, { "bora-island", } },
{ 0, "borabora-island", "${var##*bora}", 0, 1, {"-island", } },
/* Pathname expansion */
{ 0, NULL, "???", 0, 2, { "one", "two", } },
{ 0, NULL, "[ot]??", 0, 2, { "one", "two", } },
{ 0, NULL, "t*", 0, 2, { "three", "two", } },
{ 0, NULL, "\"t\"*", 0, 2, { "three", "two", } },
/* Nested constructs */
{ 0, "one two", "$var", 0, 2, { "one", "two", } },
{ 0, "one two three", "$var", 0, 3, { "one", "two", "three", } },
{ 0, " \tfoo\t\tbar ", "$var", 0, 2, { "foo", "bar", } },
{ 0, " red , white blue", "$var", 0, 3, { "red", "white", "blue", } },
{ 0, " red , white blue", "\"$var\"", 0, 1, { " red , white blue", } },
{ 0, NULL, "\"$(echo hello there)\"", 0, 1, { "hello there", } },
{ 0, NULL, "\"$(echo \"hello there\")\"", 0, 1, { "hello there", } },
{ 0, NULL, "${var=one two} \"$var\"", 0, 3, { "one", "two", "one two", } },
{ 0, "1", "$(( $(echo 3)+$var ))", 0, 1, { "4", } },
{ 0, NULL, "\"$(echo \"*\")\"", 0, 1, { "*", } },
/* Other things that should succeed */
{ 0, NULL, "\\*\"|&;<>\"\\(\\)\\{\\}", 0, 1, { "*|&;<>(){}", } },
{ 0, "???", "$var", 0, 1, { "???", } },
{ 0, NULL, "$var", 0, 0, { NULL, } },
{ 0, NULL, "\"\\n\"", 0, 1, { "\\n", } },
{ 0, NULL, "", 0, 0, { NULL, } },
/* Things that should fail */
{ WRDE_BADCHAR, NULL, "new\nline", 0, 0, { NULL, } },
{ WRDE_BADCHAR, NULL, "pipe|symbol", 0, 0, { NULL, } },
{ WRDE_BADCHAR, NULL, "&ampersand", 0, 0, { NULL, } },
{ WRDE_BADCHAR, NULL, "semi;colon", 0, 0, { NULL, } },
{ WRDE_BADCHAR, NULL, "<greater", 0, 0, { NULL, } },
{ WRDE_BADCHAR, NULL, "less>", 0, 0, { NULL, } },
{ WRDE_BADCHAR, NULL, "(open-paren", 0, 0, { NULL, } },
{ WRDE_BADCHAR, NULL, "close-paren)", 0, 0, { NULL, } },
{ WRDE_BADCHAR, NULL, "{open-brace", 0, 0, { NULL, } },
{ WRDE_BADCHAR, NULL, "close-brace}", 0, 0, { NULL, } },
{ WRDE_CMDSUB, NULL, "$(ls)", WRDE_NOCMD, 0, { NULL, } },
{ WRDE_BADVAL, NULL, "$var", WRDE_UNDEF, 0, { NULL, } },
{ WRDE_SYNTAX, NULL, "$[50+20))", 0, 0, { NULL, } },
{ WRDE_SYNTAX, NULL, "${%%noparam}", 0, 0, { NULL, } },
{ WRDE_SYNTAX, NULL, "${missing-brace", 0, 0, { NULL, } },
{ WRDE_SYNTAX, NULL, "$((2+))", 0, 0, { NULL, } },
{ WRDE_SYNTAX, NULL, "`", 0, 0, { NULL, } },
{ -1, NULL, NULL, 0, 0, { NULL, } },
};
@ -103,9 +152,11 @@ command_line_test (const char *words)
int
main (int argc, char *argv[])
{
char tmpdir[32];
struct passwd *pw;
int test;
int fail = 0;
int fd;
if (argc > 1)
{
@ -114,6 +165,16 @@ main (int argc, char *argv[])
}
setenv ("IFS", IFS, 1);
/* Set up arena for pathname expansion */
tmpnam (tmpdir);
if (mkdir (tmpdir, S_IRWXU) ||
chdir (tmpdir) ||
(fd = creat ("one", S_IRWXU)) == -1 || close (fd) ||
(fd = creat ("two", S_IRWXU)) == -1 || close (fd) ||
(fd = creat ("three", S_IRWXU)) == -1 || close (fd))
return -1;
for (test = 0; test_case[test].retval != -1; test++)
if (testit (&test_case[test]))
++fail;
@ -155,7 +216,7 @@ testit (struct test_case_struct *tc)
printf ("Test %d: ", ++test);
retval = wordexp (tc->words, &we, tc->flags);
if (retval != tc->retval || we.we_wordc != tc->wordc)
if (retval != tc->retval || (retval != 0 && we.we_wordc != tc->wordc))
bzzzt = 1;
else
for (i = 0; i < we.we_wordc; ++i)

View File

@ -831,6 +831,11 @@ exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length,
}
close (fildes[0]);
/* bash chops off a terminating linefeed, which seems sensible */
if ((*word)[*word_length - 1] == '\n')
(*word)[--*word_length] = '\0';
return 0;
}
else
@ -1100,7 +1105,7 @@ parse_param (char **word, size_t *word_length, size_t *max_length,
break;
case '%':
if (!*env)
if (!env || !*env)
goto syntax;
/* Separating variable name from suffix pattern? */
@ -1124,7 +1129,7 @@ parse_param (char **word, size_t *word_length, size_t *max_length,
break;
case ':':
if (!*env)
if (!env || !*env)
goto syntax;
if (action != '\0' || remove != RP_NONE)
@ -1150,7 +1155,7 @@ parse_param (char **word, size_t *word_length, size_t *max_length,
case '=':
case '?':
case '+':
if (!*env)
if (!env || !*env)
goto syntax;
if (substitute_length)
@ -1252,14 +1257,16 @@ envsubst:
if (!quoted || *env == '*')
{
/* Build up value parameter by parameter (copy them) */
for (p = 1; __libc_argv[p]; p++)
for (p = 1; __libc_argv[p]; ++p)
{
char * old_pointer = value;
char *old_pointer = value;
size_t argv_len = strlen (__libc_argv[p]);
size_t old_plist_len = plist_len;
if (value)
value[plist_len - 1] = 0;
plist_len += 1 + strlen (__libc_argv[p]);
plist_len += 1 + argv_len;
/* First realloc will act as malloc because value is
* initialised to NULL. */
@ -1270,7 +1277,7 @@ envsubst:
return WRDE_NOSPACE;
}
strcat (value, __libc_argv[p]);
memcpy (&value[old_plist_len - 1], __libc_argv[p], argv_len + 1);
if (__libc_argv[p + 1])
{
value[plist_len - 1] = '\0';
@ -1497,16 +1504,38 @@ envsubst:
== WRDE_NOSPACE)
break;
if (i < we.we_wordc)
{
/* Ran out of space */
wordfree (&we);
goto no_space;
}
if (action == '=')
/* Also assign */
setenv (env, we.we_wordv[0], 1); /* need to strdup? */
{
char *words;
char *cp;
size_t words_size = 0;
for (i = 0; i < we.we_wordc; i++)
words_size += strlen (we.we_wordv[i]) + 1; /* for <space> */
words_size++;
cp = words = __alloca (words_size);
*words = 0;
for (i = 0; i < we.we_wordc - 1; i++)
{
cp = __stpcpy (cp, we.we_wordv[i]);
*cp++ = ' ';
}
__stpcpy (cp, we.we_wordv[i]);
/* Also assign */
setenv (env, words, 1);
}
wordfree (&we);
if (i < we.we_wordc)
/* Ran out of space */
goto no_space;
return 0;
}
@ -1550,7 +1579,7 @@ envsubst:
{
/* Variable not defined */
if (flags & WRDE_UNDEF)
return WRDE_SYNTAX;
return WRDE_BADVAL;
return 0;
}
@ -1694,7 +1723,7 @@ parse_dollars (char **word, size_t *word_length, size_t *max_length,
(*offset) += 2;
return parse_comm (word, word_length, max_length, words, offset, flags,
pwordexp, ifs, ifs_white);
quoted? NULL : pwordexp, ifs, ifs_white);
case '[':
(*offset) += 2;
@ -1946,6 +1975,7 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags)
case '>':
case '(':
case ')':
case '{':
case '}':
/* Fail */
wordfree (pwordexp);