From eeed1ed55b1ce8ed4e5a28c181e75d715ae1d901 Mon Sep 17 00:00:00 2001 From: tjr Date: Thu, 26 Dec 2002 14:34:18 +0000 Subject: Add an implementation of the POSIX wordexp() and wordfree() functions, which perform shell-style word expansion on strings. This is still a little rough around the edges. PR: 13420 --- lib/libc/gen/Makefile.inc | 5 +- lib/libc/gen/wordexp.3 | 207 +++++++++++++++++++++++++++++++ lib/libc/gen/wordexp.c | 308 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 518 insertions(+), 2 deletions(-) create mode 100644 lib/libc/gen/wordexp.3 create mode 100644 lib/libc/gen/wordexp.c diff --git a/lib/libc/gen/Makefile.inc b/lib/libc/gen/Makefile.inc index ba35e04..39ae3a2 100644 --- a/lib/libc/gen/Makefile.inc +++ b/lib/libc/gen/Makefile.inc @@ -29,7 +29,7 @@ SRCS+= __xuname.c _pthread_stubs.c _rand48.c _spinlock_stub.c _thread_init.c \ sysconf.c sysctl.c sysctlbyname.c sysctlnametomib.c \ syslog.c telldir.c termios.c time.c times.c timezone.c ttyname.c \ ttyslot.c ualarm.c ulimit.c uname.c unvis.c usleep.c utime.c \ - valloc.c vis.c wait.c wait3.c waitpid.c + valloc.c vis.c wait.c wait3.c waitpid.c wordexp.c # machine-dependent gen sources .if exists(${.CURDIR}/${MACHINE_ARCH}/gen/Makefile.inc) @@ -56,7 +56,7 @@ MAN+= alarm.3 arc4random.3 \ strtofflags.3 sysconf.3 sysctl.3 syslog.3 tcgetpgrp.3 \ tcsendbreak.3 tcsetattr.3 tcsetpgrp.3 time.3 times.3 timezone.3 \ ttyname.3 tzset.3 ualarm.3 ucontext.3 ulimit.3 uname.3 \ - unvis.3 usleep.3 utime.3 valloc.3 vis.3 + unvis.3 usleep.3 utime.3 valloc.3 vis.3 wordexp.3 MLINKS+=arc4random.3 arc4random_addrandom.3 arc4random.3 arc4random_stir.3 MLINKS+=ctermid.3 ctermid_r.3 @@ -131,3 +131,4 @@ MLINKS+=ttyname.3 isatty.3 ttyname.3 ttyslot.3 MLINKS+=tzset.3 tzsetwall.3 MLINKS+=unvis.3 strunvis.3 unvis.3 strunvisx.3 MLINKS+=vis.3 strvis.3 vis.3 strvisx.3 +MLINKS+=wordexp.3 wordfree.3 diff --git a/lib/libc/gen/wordexp.3 b/lib/libc/gen/wordexp.3 new file mode 100644 index 0000000..895e90e --- /dev/null +++ b/lib/libc/gen/wordexp.3 @@ -0,0 +1,207 @@ +.\" +.\" Copyright (c) 2002 Tim J. Robbins +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd December 27, 2002 +.Dt WORDEXP 3 +.Os +.Sh NAME +.Nm wordexp +.Nd "perform shell-style word expansions" +.Sh LIBRARY +.Lb libc +.Sh SYNOPSIS +.In sys/types.h +.In wordexp.h +.Ft int +.Fo wordexp +.Fa "const char * restrict words" +.Fa "wordexp_t * restrict we" +.Fa "int flags" +.Fc +.Fo wordfree +.Fa "wordexp_t *we" +.Fc +.Sh DESCRIPTION +The +.Fn wordexp +function performs shell-style word expansion on +.Fa words +and places the list of words into the +.Va we_wordv +member of +.Va we , +and the number of words into +.Va we_wordc . +.Pp +The +.Va flags +argument is the bitwise inclusive OR of any of the following constants: +.Bl -tag -width ".Dv WRDE_SHOWERR" +.It Dv WRDE_APPEND +Append the words to those generate by a previous call to +.Fn wordexp . +.It Dv WRDE_DOOFS +As many +.Dv NULL +pointers as are specified by the +.Va we_offs +member of +.Va we +are added to the front of +.Va we_wordv . +.It Dv WRDE_NOCMD +Disallow command substitution in +.Fa words . +See the note in +.Sx BUGS +before using this. +.It Dv WRDE_REUSE +The +.Va we +argument was passed to a previous successful call to +.Fn wordexp +but has not been passed to +.Fn wordfree . +The implementation may reuse the space allocated to it. +.It Dv WRDE_SHOWERR +Do not redirect shell error messages to +.Pa /dev/null . +.It Dv WRDE_UNDEF +Report error on an attempt to expand an undefined shell variable. +.El +.Pp +The +.Vt wordexp_t +structure is defined in +.Pa wordexp.h +as: +.Bd -literal -offset indent +typedef struct { + size_t we_wordc; /* count of words matched */ + char **we_wordv; /* pointer to list of words */ + size_t we_offs; /* slots to reserve in we_wordv */ +} wordexp_t; +.Ed +.Pp +The +.Fn wordfree +function frees the memory allocated by +.Fn wordexp . +.Sh IMPLEMENTATION NOTES +The +.Fn wordexp +function is implemented as a wrapper around the undocumented +.Ic wordexp +shell built-in command. +.Sh RETURN VALUES +The +.Fn wordexp +function returns zero if successful, otherwise it returns one of the following +error codes: +.Bl -tag -width ".Dv WRDE_NOSPACE" +.It Dv WRDE_BADCHAR +The +.Fa words +argument contains one of the following unquoted characters: +, +.Ql | , +.Ql & , +.Ql \&; , +.Ql < , +.Ql > , +.Ql \&( , +.Ql \&) , +.Ql { , +.Ql } . +.It Dv WRDE_BADVAL +An attempt was made to expand an undefined shell variable and +.Dv WRDE_UNDEF +is set in +.Fa flags . +.It Dv WRDE_CMDSUB +An attempt was made to use command substitution and +.Dv WRDE_NOCMD +is set in +.Fa flags . +.It Dv WRDE_NOSPACE +Not enough memory to store the result. +.It Dv WRDE_SYNTAX +Shell syntax error in +.Fa words . +.El +.Pp +The +.Fn wordfree +function returns no value. +.Sh ENVIRONMENT +.Bl -tag -width ".Ev IFS" +.It Ev IFS +Field separator. +.El +.Sh EXAMPLES +Invoke the editor on all +.Dq Li \&.c +files in the current directory, +and +.Pa /etc/motd +(error checking omitted): +.Bd -literal -offset indent +wordexp_t we; + +wordexp("${EDITOR:-vi} *.c /etc/motd", &we, 0); +execvp(we->we_wordv[0], we->we_wordv); +.Ed +.Sh DIAGNOSTICS +Diagnostic messages from the shell are written to the standard error output +if +.Dv WRDE_SHOWERR +is set in +.Fa flags . +.Sh SEE ALSO +.Xr sh 1 , +.Xr fnmatch 3 , +.Xr glob 3 , +.Xr popen 3 , +.Xr system 3 +.Sh STANDARDS +The +.Fn wordexp +and +.Fn wordfree +functions conforms to +.St -p1003.1-2001 . +.Sh BUGS +Do not pass untrusted user data to +.Fn wordexp , +regardless of whether the +.Dv WRDE_NOCMD +flag is set. +The +.Fn wordexp +function attempts to detect input that would cause commands to be +executed before passing it to the shell +but it does not use the same parser so it may be fooled. diff --git a/lib/libc/gen/wordexp.c b/lib/libc/gen/wordexp.c new file mode 100644 index 0000000..dd16fcf --- /dev/null +++ b/lib/libc/gen/wordexp.c @@ -0,0 +1,308 @@ +/*- + * Copyright (c) 2002 Tim J. Robbins. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "namespace.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "un-namespace.h" + +__FBSDID("$FreeBSD$"); + +static int we_askshell(const char *, wordexp_t *, int); +static int we_check(const char *, int); + +/* + * wordexp -- + * Perform shell word expansion on `words' and place the resulting list + * of words in `we'. See wordexp(3). + * + * Specified by IEEE Std. 1003.1-2001. + */ +int +wordexp(const char * __restrict words, wordexp_t * __restrict we, int flags) +{ + int error; + + if (flags & WRDE_REUSE) + wordfree(we); + if ((flags & WRDE_APPEND) == 0) { + we->we_wordc = 0; + we->we_wordv = NULL; + we->we_strings = NULL; + we->we_nbytes = 0; + } + if ((error = we_check(words, flags)) != 0) { + wordfree(we); + return (error); + } + if ((error = we_askshell(words, we, flags)) != 0) { + wordfree(we); + return (error); + } + return (0); +} + +/* + * we_askshell -- + * Use the `wordexp' /bin/sh builtin function to do most of the work + * in expanding the word string. This function is complicated by + * memory management. + */ +static int +we_askshell(const char *words, wordexp_t *we, int flags) +{ + int pdes[2]; /* Pipe to child */ + char bbuf[9]; /* Buffer for byte count */ + char wbuf[9]; /* Buffer for word count */ + long nwords, nbytes; /* Number of words, bytes from child */ + long i; /* Handy integer */ + size_t sofs; /* Offset into we->we_strings */ + size_t vofs; /* Offset into we->we_wordv */ + pid_t pid; /* Process ID of child */ + int status; /* Child exit status */ + char *ifs; /* IFS env. var. */ + char *np, *p; /* Handy pointers */ + char *nstrings; /* Temporary for realloc() */ + char **nwv; /* Temporary for realloc() */ + + if ((ifs = getenv("IFS")) == NULL) + ifs = " \t\n"; + + if (pipe(pdes) < 0) + return (WRDE_NOSPACE); /* XXX */ + if ((pid = fork()) < 0) { + close(pdes[0]); + close(pdes[1]); + return (WRDE_NOSPACE); /* XXX */ + } + else if (pid == 0) { + /* + * We are the child; just get /bin/sh to run the wordexp + * builtin on `words'. + */ + int devnull; + char *cmd; + + close(pdes[0]); + if (dup2(pdes[1], STDOUT_FILENO) < 0) + _exit(1); + close(pdes[1]); + if (asprintf(&cmd, "wordexp%c%s\n", *ifs, words) < 0) + _exit(1); + if ((flags & WRDE_SHOWERR) == 0) { + if ((devnull = open(_PATH_DEVNULL, O_RDWR, 0666)) < 0) + _exit(1); + if (dup2(devnull, STDERR_FILENO) < 0) + _exit(1); + close(devnull); + } + execl(_PATH_BSHELL, "sh", flags & WRDE_UNDEF ? "-u" : "+u", + "-c", cmd, NULL); + _exit(1); + } + + /* + * We are the parent; read the output of the shell wordexp function, + * which is a 32-bit hexadecimal word count, a 32-bit hexadecimal + * byte count (not including terminating null bytes), followed by + * the expanded words separated by nulls. + */ + close(pdes[1]); + if (read(pdes[0], wbuf, 8) != 8 || read(pdes[0], bbuf, 8) != 8) { + close(pdes[0]); + return (WRDE_NOSPACE); /* XXX */ + } + wbuf[8] = bbuf[8] = '\0'; + nwords = strtol(wbuf, NULL, 16); + nbytes = strtol(bbuf, NULL, 16) + nwords; + + /* + * Allocate or reallocate (when flags & WRDE_APPEND) the word vector + * and string storage buffers for the expanded words we're about to + * read from the child. + */ + sofs = we->we_nbytes; + vofs = we->we_wordc; + we->we_wordc += nwords; + we->we_nbytes += nbytes; + if ((nwv = realloc(we->we_wordv, (we->we_wordc + 1 + + (flags & WRDE_DOOFS ? we->we_offs : 0)) * + sizeof(char *))) == NULL) { + close(pdes[0]); + return (WRDE_NOSPACE); + } + we->we_wordv = nwv; + if ((nstrings = realloc(we->we_strings, we->we_nbytes)) == NULL) { + close(pdes[0]); + return (WRDE_NOSPACE); + } + for (i = 0; i < vofs; i++) + if (we->we_wordv[i] != NULL) + we->we_wordv[i] += nstrings - we->we_strings; + we->we_strings = nstrings; + + if (read(pdes[0], we->we_strings + sofs, nbytes) != nbytes) { + close(pdes[0]); + return (WRDE_NOSPACE); /* XXX */ + } + + if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status) || + WEXITSTATUS(status) != 0) { + close(pdes[0]); + return (flags & WRDE_UNDEF ? WRDE_BADVAL : WRDE_SYNTAX); + } + close(pdes[0]); + + /* + * Break the null-terminated expanded word strings out into + * the vector. + */ + if (vofs == 0 && flags & WRDE_DOOFS) + while (vofs < we->we_offs) + we->we_wordv[vofs++] = NULL; + p = we->we_strings + sofs; + while (nwords-- != 0) { + we->we_wordv[vofs++] = p; + if ((np = memchr(p, '\0', nbytes)) == NULL) + return (WRDE_NOSPACE); /* XXX */ + nbytes -= np - p + 1; + p = np + 1; + } + we->we_wordv[vofs] = NULL; + + return (0); +} + +/* + * we_check -- + * Check that the string contains none of the following unquoted + * special characters: |&;<>(){} + * or command substitutions when WRDE_NOCMD is set in flags. + */ +int +we_check(const char *words, int flags) +{ + char c; + int dquote, level, quote, squote; + + quote = squote = dquote = 0; + while ((c = *words++) != '\0') { + switch (c) { + case '\\': + quote ^= 1; + break; + case '\'': + if (quote + dquote == 0) + squote ^= 1; + break; + case '"': + if (quote + squote == 0) + dquote ^= 1; + break; + case '`': + if (quote + squote == 0 && flags & WRDE_NOCMD) + return (WRDE_CMDSUB); + while ((c = *words++) != '\0' && c != '`') + if (c == '\\' && (c = *words++) == '\0') + break; + if (c == '\0') + return (WRDE_SYNTAX); + break; + case '|': case '&': case ';': case '<': case '>': + case '{': case '}': case '(': case ')': case '\n': + if (quote + squote + dquote == 0) + return (WRDE_BADCHAR); + break; + case '$': + if ((c = *words++) == '\0') + break; + else if (c == '(') { + if (flags & WRDE_NOCMD) + return (WRDE_CMDSUB); + level = 1; + while ((c = *words++) != '\0') { + if (c == '\\') { + if ((c = *words++) == '\0') + break; + } else if (c == '(') + level++; + else if (c == ')' && --level == 0) + break; + } + if (c == '\0' || level != 0) + return (WRDE_SYNTAX); + } else if (c == '{') { + level = 1; + while ((c = *words++) != '\0') { + if (c == '\\') { + if ((c = *words++) == '\0') + break; + } else if (c == '{') + level++; + else if (c == '}' && --level == 0) + break; + } + if (c == '\0' || level != 0) + return (WRDE_SYNTAX); + } + break; + default: + break; + } + } + if (quote + squote + dquote != 0) + return (WRDE_SYNTAX); + + return (0); +} + +/* + * wordfree -- + * Free the result of wordexp(). See wordexp(3). + * + * Specified by IEEE Std. 1003.1-2001. + */ +void +wordfree(wordexp_t *we) +{ + + if (we == NULL) + return; + free(we->we_wordv); + free(we->we_strings); + we->we_wordv = NULL; + we->we_strings = NULL; + we->we_nbytes = 0; + we->we_wordc = 0; +} -- cgit v1.1