root/usr/src/lib/libc/port/regex/wordexp.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * This code is MKS code ported to Solaris originally with minimum
 * modifications so that upgrades from MKS would readily integrate.
 * The MKS basis for this modification was:
 *
 *      $Id: wordexp.c 1.22 1994/11/21 18:24:50 miked
 *
 * Additional modifications have been made to this code to make it
 * 64-bit clean.
 */

/*
 * wordexp, wordfree -- POSIX.2 D11.2 word expansion routines.
 *
 * Copyright 1985, 1992 by Mortice Kern Systems Inc.  All rights reserved.
 * Modified by Roland Mainz <roland.mainz@nrubsig.org> to support ksh93.
 */

#pragma weak _wordexp = wordexp
#pragma weak _wordfree = wordfree

#include "lint.h"
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <alloca.h>
#include <string.h>
#include <sys/wait.h>
#include <pthread.h>
#include <unistd.h>
#include <wordexp.h>
#include <stdio.h>
#include <spawn.h>
#include <errno.h>

#define INITIAL 8               /* initial pathv allocation */
#define BUFSZ   256             /* allocation unit of the line buffer */

/*
 * Needs no locking if fetched only once.
 * See getenv()/putenv()/setenv().
 */
extern  const char **_environ;

/* Local prototypes */
static int append(wordexp_t *, char *);

/*
 * |mystpcpy| - like |strcpy()| but returns the end of the buffer
 * We'll add this later (and a matching multibyte/widechar version)
 * as normal libc function.
 *
 * Copy string s2 to s1.  s1 must be large enough.
 * return s1-1 (position of string terminator ('\0') in destination buffer).
 */
static char *
mystpcpy(char *s1, const char *s2)
{
        while ((*s1++ = *s2++) != '\0')
                ;
        return (s1-1);
}

/*
 * Do word expansion.
 * We build a mini-script in |buff| which takes care of all details,
 * including stdin/stdout/stderr redirection, WRDE_NOCMD mode and
 * the word expansion itself.
 */
int
wordexp(const char *word, wordexp_t *wp, int flags)
{
        const char *path = "/usr/bin/ksh93";
        wordexp_t wptmp;
        size_t si;
        pid_t pid;
        char *line, *eob, *cp;          /* word from shell */
        int rv = WRDE_ERRNO;
        int status;
        int pv[2];                      /* pipe from shell stdout */
        FILE *fp;                       /* pipe read stream */
        int tmpalloc;
        char *wd = NULL;
        const char **env = NULL;
        const char **envp;
        const char *ev;
        int n;
        posix_spawnattr_t attr;
        posix_spawn_file_actions_t fact;
        int error;
        int cancel_state;
        size_t bufflen; /* Length of |buff| */
        char *buff;
        char *currbuffp; /* Current position of '\0' in |buff| */
        char *args[10];
        int i;

        /*
         * Do absolute minimum necessary for the REUSE flag. Eventually
         * want to be able to actually avoid excessive malloc calls.
         */
        if (flags & WRDE_REUSE)
                wordfree(wp);

        /*
         * Initialize wordexp_t
         *
         * XPG requires that the struct pointed to by wp not be modified
         * unless wordexp() either succeeds, or fails on WRDE_NOSPACE.
         * So we work with wptmp, and only copy wptmp to wp if one of the
         * previously mentioned conditions is satisfied.
         */
        wptmp = *wp;

        /*
         * Man page says:
         * 2. All of the calls must set WRDE_DOOFFS, or all must not
         *    set it.
         * Therefore, if it's not set, we_offs will always be reset.
         */
        if ((flags & WRDE_DOOFFS) == 0)
                wptmp.we_offs = 0;

        /*
         * If we get APPEND|REUSE, how should we do?
         * allocating buffer anyway to avoid segfault.
         */
        tmpalloc = 0;
        if ((flags & WRDE_APPEND) == 0 || (flags & WRDE_REUSE)) {
                wptmp.we_wordc = 0;
                wptmp.we_wordn = wptmp.we_offs + INITIAL;
                wptmp.we_wordv = malloc(sizeof (char *) * wptmp.we_wordn);
                if (wptmp.we_wordv == NULL)
                        return (WRDE_NOSPACE);
                wptmp.we_wordp = wptmp.we_wordv + wptmp.we_offs;
                for (si = 0; si < wptmp.we_offs; si++)
                        wptmp.we_wordv[si] = NULL;
                tmpalloc = 1;
        }

        /*
         * The UNIX98 Posix conformance test suite requires
         * |wordexp()| to not be a cancellation point.
         */
        (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state);

        /*
         * Make sure PWD is in the environment.
         */
        if ((envp = _environ) == NULL) {
                /* can happen when processing a SunOS 4.x AOUT file */
                ev = NULL;
                n = 0;
        } else {
                for (n = 0; (ev = envp[n]) != NULL; n++) {
                        if (*ev == 'P' && strncmp(ev, "PWD=", 4) == 0)
                                break;
                }
        }
        if (ev == NULL) {       /* PWD missing from the environment */
                /* allocate a new environment */
                if ((env = malloc((n + 2) * sizeof (char *))) == NULL ||
                    (wd = malloc(PATH_MAX + 4)) == NULL)
                        goto cleanup;
                for (i = 0; i < n; i++)
                        env[i] = envp[i];
                (void) strcpy(wd, "PWD=");
                if (getcwd(&wd[4], PATH_MAX) == NULL)
                        (void) strcpy(&wd[4], "/");
                env[i] = wd;
                env[i + 1] = NULL;
                envp = env;
        }

        /*
         * Calculate size of required buffer (which is size of the
         * input string (|word|) plus all string literals below;
         * this value MUST be adjusted each time the literals are
         * changed!!).
         */
        bufflen = 165 + strlen(word);
        buff = alloca(bufflen);
        i = 0;

        /* Start filling the buffer */
        buff[0] = '\0';
        currbuffp = buff;

        if (flags & WRDE_UNDEF)
                currbuffp = mystpcpy(currbuffp, "set -o nounset\n");
        if ((flags & WRDE_SHOWERR) == 0) {
                /*
                 * The newline ('\n') is neccesary to make sure that
                 * the redirection to /dev/null is already active in
                 * the case the printf below contains a syntax
                 * error...
                 */
                currbuffp = mystpcpy(currbuffp, "exec 2>/dev/null\n");
        }
        /* Squish stdin */
        currbuffp = mystpcpy(currbuffp, "exec 0</dev/null\n");

        if (flags & WRDE_NOCMD) {
                /*
                 * Switch to restricted shell (rksh) mode here to
                 * put the word expansion into a "cage" which
                 * prevents users from executing external commands
                 * (outside those listed by ${PATH} (which we set
                 * explicitly to /usr/no/such/path/element/)).
                 */
                currbuffp = mystpcpy(currbuffp,
                    "export PATH=/usr/no/such/path/element/ ; "
                    "set -o restricted\n");
        }

        (void) snprintf(currbuffp, bufflen,
            "print -f '%%s\\000' -- %s", word);

        args[i++] = strrchr(path, '/') + 1;
        args[i++] = "-c";
        args[i++] = buff;
        args[i++] = NULL;

        if ((error = posix_spawnattr_init(&attr)) != 0) {
                errno = error;
                goto cleanup;
        }
        if ((error = posix_spawn_file_actions_init(&fact)) != 0) {
                (void) posix_spawnattr_destroy(&attr);
                errno = error;
                goto cleanup;
        }

        /*
         * Set up pipe from shell stdout to "fp" for us
         */
        if (pipe(pv) < 0) {
                error = errno;
                (void) posix_spawnattr_destroy(&attr);
                (void) posix_spawn_file_actions_destroy(&fact);
                errno = error;
                goto cleanup;
        }

        /*
         * Spawn shell
         */
        error = posix_spawnattr_setflags(&attr,
            POSIX_SPAWN_NOSIGCHLD_NP | POSIX_SPAWN_WAITPID_NP);
        if (error == 0)
                error = posix_spawn_file_actions_adddup2(&fact, pv[1], 1);
        if (error == 0 && pv[0] != 1)
                error = posix_spawn_file_actions_addclose(&fact, pv[0]);
        if (error == 0 && pv[1] != 1)
                error = posix_spawn_file_actions_addclose(&fact, pv[1]);
        if (error == 0 && !(flags & WRDE_SHOWERR))
                error = posix_spawn_file_actions_addopen(&fact, 2,
                    "/dev/null", O_WRONLY, 0);

        if (error == 0)
                error = posix_spawn(&pid, path, &fact, &attr,
                    (char *const *)args, (char *const *)envp);
        (void) posix_spawnattr_destroy(&attr);
        (void) posix_spawn_file_actions_destroy(&fact);
        (void) close(pv[1]);
        if (error) {
                (void) close(pv[0]);
                errno = error;
                goto cleanup;
        }

        if ((fp = fdopen(pv[0], "rF")) == NULL) {
                error = errno;
                (void) close(pv[0]);
                errno = error;
                goto wait_cleanup;
        }

        /*
         * Read words from shell, separated with '\0'.
         * Since there is no way to disable IFS splitting,
         * it would be possible to separate the output with '\n'.
         */
        cp = line = malloc(BUFSZ);
        if (line == NULL) {
                error = errno;
                (void) fclose(fp);
                errno = error;
                goto wait_cleanup;
        }
        eob = line + BUFSZ;

        rv = 0;
        flockfile(fp);
        while ((i = getc_unlocked(fp)) != EOF) {
                *cp++ = (char)i;
                if (i == '\0') {
                        cp = line;
                        if ((rv = append(&wptmp, cp)) != 0) {
                                break;
                        }
                }
                if (cp == eob) {
                        size_t bs = (eob - line);
                        char *nl;

                        if ((nl = realloc(line, bs + BUFSZ)) == NULL) {
                                rv = WRDE_NOSPACE;
                                break;
                        }
                        line = nl;
                        cp = line + bs;
                        eob = cp + BUFSZ;
                }
        }
        funlockfile(fp);

        wptmp.we_wordp[wptmp.we_wordc] = NULL;

        free(line);
        (void) fclose(fp);      /* kill shell if still writing */

wait_cleanup:
        while (waitpid(pid, &status, 0) == -1) {
                if (errno != EINTR) {
                        if (rv == 0)
                                rv = WRDE_ERRNO;
                        break;
                }
        }
        if (rv == 0)
                rv = WEXITSTATUS(status); /* shell WRDE_* status */

cleanup:
        if (rv == 0)
                *wp = wptmp;
        else if (tmpalloc)
                wordfree(&wptmp);

        if (env)
                free(env);
        if (wd)
                free(wd);
        /*
         * Map ksh93 errors to |wordexp()| errors
         */
        switch (rv) {
                case 1:
                        rv = WRDE_BADVAL;
                        break;
                case 127:
                        rv = WRDE_BADCHAR;
                        break;
        }

        (void) pthread_setcancelstate(cancel_state, NULL);
        return (rv);
}

/*
 * Append a word to the wordexp_t structure, growing it as necessary.
 */
static int
append(wordexp_t *wp, char *str)
{
        char *cp;
        char **nwp;

        /*
         * We will be adding one entry and later adding
         * one more NULL. So we need 2 more free slots.
         */
        if ((wp->we_wordp + wp->we_wordc) ==
            (wp->we_wordv + wp->we_wordn - 1)) {
                nwp = realloc(wp->we_wordv,
                    (wp->we_wordn + INITIAL) * sizeof (char *));
                if (nwp == NULL)
                        return (WRDE_NOSPACE);
                wp->we_wordn += INITIAL;
                wp->we_wordv = nwp;
                wp->we_wordp = wp->we_wordv + wp->we_offs;
        }
        if ((cp = strdup(str)) == NULL)
                return (WRDE_NOSPACE);
        wp->we_wordp[wp->we_wordc++] = cp;
        return (0);
}

/*
 * Free all space owned by wordexp_t.
 */
void
wordfree(wordexp_t *wp)
{
        size_t i;

        if (wp->we_wordv == NULL)
                return;
        for (i = wp->we_offs; i < wp->we_offs + wp->we_wordc; i++)
                free(wp->we_wordv[i]);
        free((void *)wp->we_wordv);
        wp->we_wordc = 0;
        wp->we_wordv = NULL;
}