/* Copyright (c) 1990,1991,1992 Chris and John Downey */
#ifndef lint
static char *sccsid = "@(#)unix.c       2.1 (Chris & John Downey) 7/29/92";
#endif

/***

* program name:
    xvi
* function:
    PD version of UNIX "vi" editor, with extensions.
* module name:
    unix.c
* module function:
    System interface routines for all versions of UNIX.
* history:
    STEVIE - ST Editor for VI Enthusiasts, Version 3.10
    Originally by Tim Thompson (twitch!tjt)
    Extensive modifications by Tony Andrews (onecom!wldrdg!tony)
    Heavily modified by Chris & John Downey

    Bug fix by Laurent FAILLIE : Add a check if ioctl() fail ... because
    it CAN FAIL (e.g. when launched by a shell on a serial terminal on
    amiga system). NOTE: I actualy only add a check when getting the size
    of a window. This works but all ioctl() calls may be checked...
***/

#include "xvi.h"

#ifndef SIGINT
#   include <signal.h>          /* get signals for call_shell() */
#endif

#ifdef  BSD
#   include <sys/wait.h>        /* get wait stuff for call_shell() */
    typedef     union wait Wait_t;
#else
    typedef     int Wait_t;
#endif

/*
 * CTRL is defined by sgtty.h (or by a file it includes)
 * so we undefine it here to avoid conflicts with the
 * version defined in "xvi.h".
 */
#undef  CTRL

#ifdef  sun
#   ifndef TERMIOS
#       define  TERMIOS
#   endif
#endif

#ifdef TERMIOS
#   ifndef TERMIO
#       define  TERMIO
#   endif
#endif

#ifdef  TERMIO
#   ifdef       TERMIOS
#       include <termios.h>
   typedef struct termios       Termstate;
#       define getstate(p)      ((void) tcgetattr(0, (p)))
#       define setstate(p)      ((void) tcsetattr(0, TCSANOW, (p)))
#       define w_setstate(p)    ((void) tcsetattr(0, TCSADRAIN, (p)))
#   else        /* no TERMIOS */
#       include <termio.h>
   typedef struct termio        Termstate;
#       define getstate(p)      ((void) ioctl(0,TCGETA,(char *)(p)))
#       define setstate(p)      ((void) ioctl(0,TCSETA,(char *)(p)))
#       define w_setstate(p)    ((void) ioctl(0,TCSETAW,(char *)(p)))
#   endif       /* no TERMIOS */

    /*
     * Table of line speeds ... exactly 16 long, and the CBAUD mask
     * is 017 (i.e. 15) so we will never access outside the array.
     */
    short       speeds[] = {
        /* B0 */        0,
        /* B50 */       50,
        /* B75 */       75,
        /* B110 */      110,
        /* B134 */      134,
        /* B150 */      150,
        /* B200 */      200,
        /* B300 */      300,
        /* B600 */      600,
        /* B1200 */     1200,
        /* B1800 */     1800,
        /* B2400 */     2400,
        /* B4800 */     4800,
        /* B9600 */     9600,
        /* EXTA */      19200,          /* not defined at V.2 */
        /* EXTB */      38400,          /* not defined at V.2 */
    };

#else   /* not TERMIO */

#   include <sgtty.h>
    typedef struct sgttyb       Termstate;

    static      struct  tchars  ckd_tchars, raw_tchars;
    static      struct  ltchars ckd_ltchars, raw_ltchars;

#   ifdef FD_SET
#       define  fd_set_type     fd_set
#   else                /* FD_SET not defined */
        /*
         * BSD 4.2 doesn't have these macros.
         */
        typedef int fd_set_type;
#       define  FD_ZERO(p)      (*(p) = 0)
#       define  FD_SET(f,p)     (*(p) |= (1 << (f)))
#   endif               /* FD_SET not defined */
#endif  /* not TERMIO */

static  Termstate       cooked_state, raw_state;

#undef  CTRL

#ifdef  SETVBUF_AVAIL
    /*
     * Output buffer to save function calls.
     */
    static      char    outbuffer[128];
#endif

#ifdef MEMTEST
#   include <sys/resource.h>
#endif          /* MEMTEST */

/*
 * Expected for termcap's benefit.
 */
short           ospeed;                 /* tty's baud rate */

/*
 * We sometimes use a lot of system calls while trying to read from
 * the keyboard; these are needed to make our automatic buffer
 * preservation and input timeouts work properly. Nevertheless, it
 * is possible that, with this much overhead, a reasonably fast typist
 * could get ahead of us, so we do a small amount of input buffering
 * to reduce the number of system calls.
 *
 * This variable gives the number of characters in the buffer.
 */
static int      kb_nchars;

/*
 * Get a single byte from the keyboard.
 *
 * If the keyboard input buffer is empty, & read() fails or times out,
 * return EOF.
 */
static int
kbgetc()
{
    static unsigned char        kbuf[48];
    static unsigned char        *kbp;

    if (kb_nchars <= 0) {
        int nread;

        if ((nread = read(0, (char *) kbuf, sizeof kbuf)) <= 0) {
            return EOF;
        } else {
            kb_nchars = nread;
            kbp = kbuf;
        }
    }
    --kb_nchars;
    return(*kbp++);
}

#ifdef TERMIO

/*
 * Set a timeout on standard input. 0 means no timeout.
 *
 * This depends on raw_state having been properly initialized, which
 * should have been done by sys_startv().
 */
static void
input_timeout(msec)
long    msec;
{
    int         vtime;
    static int  lastvtime;

    /*
     * If the device state hasn't been changed since last time, we
     * don't need to do anything.
     */
    if ((vtime = (msec + 99) / 100) != lastvtime) {
        lastvtime = vtime;
        raw_state.c_cc[VMIN] = (vtime == 0 ? 1 : 0);
        raw_state.c_cc[VTIME] = vtime;
        setstate(&raw_state);
    }
}

#endif  /* TERMIO */

/*
 * Get a character from the keyboard.
 *
 * Make sure screen is updated first.
 */
int
inch(timeout)
long    timeout;
{
    int         c;

    /*
     * If we had characters left over from last time, return one.
     *
     * Note that if this happens, we don't call flush_output().
     */
    if (kb_nchars > 0) {
        return(kbgetc());
    }

    /*
     * Need to get a character. First, flush output to the screen.
     */
    flush_output();

#ifdef TERMIO
    if (timeout != 0) {
        input_timeout(timeout);
        c = kbgetc();
        input_timeout(0L);
        return(c);
    }
#else   /* no TERMIO */
    if (timeout != 0) {
        struct timeval  tv;
        fd_set_type     readfds;

        tv.tv_sec = (long) (timeout / 1000);
        tv.tv_usec = ((long) timeout * 1000) % (long) 1000000;

        FD_ZERO(&readfds);
        FD_SET(0, &readfds);

        /*
         * If select does not return 0, some input is available
         * (ignoring the possibility of errors). Otherwise, we
         * timed out, so return EOF.
         */
        if (select(1, &readfds, (fd_set_type *) NULL,
                    (fd_set_type *) NULL, &tv) == 0) {
            return(EOF);
        }
    }
#endif  /* no TERMIO */

    /*
     * Keep trying until we get at least one character.
     */
    while ((c = kbgetc()) == EOF)
        ;

    return(c);
}

#if !(defined(__STDC__) || (defined(ultrix) && defined(mips)))
/*
 * If we have ANSI C, we should have strerror() anyway. Also, Ultrix
 * on DECStations provides it.
 */
const char *
strerror(err)
int     err;
{
    extern char *sys_errlist[];
    extern int  sys_nerr;

    return(
        err == 0 ?
            "No error"
            :
            (err > 0 && err < sys_nerr) ?
                sys_errlist[err]
                :
                "Unknown error"
    );
}

#endif /* !(defined(__STDC__) || (defined(ultrix) && defined(mips))) */

static int
runvp(args)
char    **args;
{
    int         pid;
    Wait_t      status;

    pid = fork();
    switch (pid) {
    case -1:            /* fork() failed */
        return(-1);

    case 0:                     /* this is the child */
        (void) signal(SIGINT, SIG_DFL);
        (void) signal(SIGQUIT, SIG_DFL);
        (void) execvp(args[0], args);

        /*
         * Couldn't do it ... use standard output functions here
         * because none of xvi's higher-level functions are usable
         * from this module.
         */
        (void) fputs("\007Can't execute ", stdout);
        (void) fputs(args[0], stdout);
        (void) fputs("\n(", stdout);
        (void) fputs(strerror(errno), stdout);
        (void) fputs(")\nHit return to continue", stdout);
        (void) fflush(stdout);
        (void) getc(stdin);
        exit(1);

    default:            /* this is the parent */
        while (wait(&status) != pid)
            ;
        return(0);
    }
}

int
call_shell(sh)
char    *sh;
{
    static char *args[] = { NULL, NULL };

    args[0] = sh;
    return(runvp(args));
}

int
call_system(command)
char    *command;
{
    static char *args[] = { NULL, "-c", NULL, NULL };

    if (Ps(P_shell) == NULL) {
        (void) puts("\007Can't execute command without SHELL parameter");
        return(-1);
    }
    args[0] = Ps(P_shell);
    args[2] = command;
    return(runvp(args));
}

#ifdef ITIMER_REAL
    static int
    nothing()
    {
        return(0);
    }
#endif

/*
 * Delay for a short time - preferably less than 1 second.
 * This is for use by showmatch, which wants to hold the
 * cursor over the matching bracket for just long enough
 * that it will be seen.
 */
void
delay()
{
#ifdef ITIMER_REAL
    struct itimerval    timer;

    (void) signal(SIGALRM, nothing);

    /*
     * We want to pause for 200 msec (1/5th of a second) here,
     * as this seems like a reasonable figure. Note that we can
     * assume that the implementation will have defined tv_usec
     * of a type large enough to hold up to 999999, since that's
     * the largest number of microseconds we can possibly need.
     */
    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = 0;
    timer.it_value.tv_sec = 0;
    timer.it_value.tv_usec = 200000;
    if (setitimer(ITIMER_REAL, &timer, (struct itimerval *) NULL) == -1)
        return;
    (void) pause();

    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = 0;
    (void) setitimer(ITIMER_REAL, &timer, (struct itimerval *) NULL);
#else /* not ITIMER_REAL */
    sleep(1);
#endif /* not ITIMER_REAL */
}

/*
 * Initialise the terminal so that we can do single-character
 * input and output, with no strange mapping, and no echo of
 * input characters.
 *
 * This must be done before any screen-based i/o is performed.
 */
void
sys_init()
{
#ifdef  TIOCGWINSZ
    struct winsize      winsz;  /* for getting window wize */
#endif

    /*
     * What the device driver thinks the window's dimensions are.
     */
    unsigned int        rows = 0;
    unsigned int        columns = 0;

#ifdef MEMTEST
    {
        static struct rlimit dlimit = { 400 * 1024, 400 * 1024 };

        (void) setrlimit(RLIMIT_DATA, &dlimit);
    }
#endif          /* MEMTEST */

    /*
     * Set up tty flags in raw and cooked structures.
     * Do this before any termcap-initialisation stuff
     * so that start sequences can be sent.
     */
#ifdef  TERMIO

    getstate(&cooked_state);
    raw_state = cooked_state;
#   ifdef   POSIX
        raw_state.c_oflag &= ~(OPOST
#       ifdef   ONLCR
                                | ONLCR
#       endif
#       ifdef   OXTABS
                                | OXTABS
#       endif
                            );
        raw_state.c_iflag &= ~(ICRNL | IGNCR | INLCR);
        raw_state.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL
#       ifdef   ECHOCTL
                                | ECHOCTL
#       endif
#       ifdef   ECHOPRT
                                | ECHOPRT
#       endif
#       ifdef   ECHOKE
                                | ECHOKE
#       endif
                            );
#   else    /* not POSIX */
        /*
         * Assume this is a Sun, but it might not be ...
         */
        raw_state.c_oflag &= ~(OLCUC | ONLCR | OCRNL | ONLRET | XTABS);
        raw_state.c_iflag &= ~(ICRNL | IGNCR | INLCR);
        raw_state.c_lflag &= ~(ICANON | XCASE | ECHO | ECHOE | ECHOK |
                   ECHONL | ECHOCTL | ECHOPRT | ECHOKE);
#   endif
    raw_state.c_cc[VMIN] = 1;
    raw_state.c_cc[VTIME] = 0;
#   ifdef       TERMIOS
#       ifdef       VDISCARD
            raw_state.c_cc[VDISCARD] = 0;
#       endif
        raw_state.c_cc[VSUSP] = 0;
#   endif       /* TERMIOS */

#else   /* not TERMIO */

    /*
     * Probably a BSD system; we must
     *  turn off echo,
     *  set cbreak mode (not raw because we lose
     *          typeahead when switching modes),
     *  turn off tab expansion on output (in case termcap
     *          puts out any tabs in cursor motions etc),
     *  turn off CRMOD so we get \r and \n as they are typed,
     * and
     *  turn off nasty interrupt characters like ^Y so that
     *          we get the control characters we want.
     *
     * All this has to be put back as it was when we go into
     * system mode; a pain, but we have to get it right.
     */
    (void) ioctl(0, TIOCGETP, (char *) &cooked_state);
    raw_state = cooked_state;
    raw_state.sg_flags |= CBREAK;
    raw_state.sg_flags &= ~ (ECHO | XTABS | CRMOD);

    (void) ioctl(0, TIOCGETC, (char *) &ckd_tchars);
    raw_tchars = ckd_tchars;
    raw_tchars.t_quitc = -1;

    (void) ioctl(0, TIOCGLTC, (char *) &ckd_ltchars);
    raw_ltchars = ckd_ltchars;
    raw_ltchars.t_flushc = -1;
    raw_ltchars.t_lnextc = -1;
    raw_ltchars.t_suspc = -1;
    raw_ltchars.t_dsuspc = -1;

#endif  /* not TERMIO */

#ifdef  SETVBUF_AVAIL
    /*
     * Set output buffering to avoid calling _flsbuf()
     * for every character sent to the screen.
     */
    setvbuf(stdout, outbuffer, _IOFBF, sizeof(outbuffer));
#endif

    /*
     * This is for termcap's benefit.
     */
#ifdef  TERMIO
#   ifdef       POSIX
        ospeed = cooked_state.c_ospeed;
#   else        /* not POSIX */
        ospeed = speeds[cooked_state.c_cflag & CBAUD];
#   endif
#else
    ospeed = cooked_state.sg_ospeed;
#endif

#ifdef  TIOCGWINSZ
    /*
     * Find out how big the kernel thinks the window is.
     * These values (if they are non-zero) will override
     * any settings obtained by the terminal interface code.
     */
     {
/* LF : Bug fix. In the original code, ioctl() return a void (so no return code
 * is checked... In fact, this function may fail and winsz is filled with
 * random values (it's occure for example on the Amiga by launching xvi on
 * a serial teminal). In case of fail, rows and colums are set to 0 so
 * this value is read from your termcap database (file gnu:etc/termcap for
 * the amiga version). Don't forget to set your 'TERM' variable to the right
 * value.
 */
        int ret = ioctl(0, TIOCGWINSZ, (char *) &winsz);
        if(!ret){
            rows = winsz.ws_row;
            columns = winsz.ws_col;
        } else
            rows = columns = 0;
    }
#else
    rows = columns = 0;
#endif

    /*
     * Now set up the terminal interface.
     */
    tty_open(&rows, &columns);

    /*
     * Go into raw/cbreak mode, and do any initialisation stuff.
     */
    sys_startv();
}

static enum { m_SYS = 0, m_VI = 1 } curmode;

/*
 * Set terminal into the mode it was in when we started.
 *
 * sys_endv() can be called when we're already in system mode, so we
 * have to check.
 */
void
sys_endv()
{
    if (curmode == m_SYS)
        return;
    tty_goto((int) Rows - 1, 0);
    set_colour(Pn(P_systemcolour));
    erase_line();
    tty_endv();

    (void) fflush(stdout);

    /*
     * Restore terminal modes.
     */
#ifdef  TERMIO
    w_setstate(&cooked_state);
#else
    (void) ioctl(0, TIOCSETN, (char *) &cooked_state);
    (void) ioctl(0, TIOCSETC, (char *) &ckd_tchars);
    (void) ioctl(0, TIOCSLTC, (char *) &ckd_ltchars);
#endif
    curmode = m_SYS;
}

/*
 * Set terminal to raw/cbreak mode.
 */
void
sys_startv()
{
    if (curmode == m_VI)
        return;
#ifdef  TERMIO
    w_setstate(&raw_state);
#else
    (void) ioctl(0, TIOCSETN, (char *) &raw_state);
    (void) ioctl(0, TIOCSETC, (char *) &raw_tchars);
    (void) ioctl(0, TIOCSLTC, (char *) &raw_ltchars);
#endif

    tty_startv();

    set_colour(Pn(P_colour));

    curmode = m_VI;
}

/*
 * "Safe" form of exit - doesn't leave the tty in yillruction mode.
 */
void
sys_exit(code)
int     code;
{
    sys_endv();
    tty_close();
    (void) fclose(stdin);
    (void) fclose(stdout);
    (void) fclose(stderr);

    /*
     * This is desperation really.
     * On some BSD systems, calling exit() here produces a core dump.
     */
    _exit(code);
}

/*
 * This is a routine that can be passed to tputs() (in the termcap
 * library): it does the same thing as putchar().
 */
void
foutch(c)
int     c;
{
    putchar(c);
}

/*
 * Construct unique name for temporary file, to be used as a backup
 * file for the named file.
 */
char *
tempfname(srcname)
char    *srcname;
{
    char        tailname[MAXNAMLEN + 1];
    char        *srctail;
    char        *endp;
    char        *retp;
    unsigned    headlen;
    unsigned    rootlen;
    unsigned    indexnum = 0;

    if ((srctail = strrchr(srcname, '/')) == NULL) {
        srctail = srcname;
    } else {
        srctail++;
    }
    headlen = srctail - srcname;

    /*
     * Make copy of filename's tail & change it from "wombat" to
     * "#wombat.tmp".
     */
    tailname [0] = '#';
    (void) strncpy(& tailname [1], srctail, sizeof tailname - 1);
    tailname [sizeof tailname - 1] = '\0';
    endp = tailname + strlen(tailname);

    /*
     * Don't let name get too long.
     */
    if (endp > & tailname [sizeof tailname - 5]) {
        endp = & tailname [sizeof tailname - 5];
    }
    rootlen = endp - tailname;

    /*
     * Now allocate space for new pathname.
     */
    retp = alloc(headlen + rootlen + 5);
    if (retp == NULL) {
        return(NULL);
    }

    /*
     * Copy name to new storage, leaving space for ".tmp"
     * (or ".xxx") suffix ...
     */
    if (headlen > 0) {
        (void) memcpy(retp, srcname, (int) headlen);
    }
    (void) memcpy(&retp[headlen], tailname, (int) rootlen);

    /*
     * ... & make endp point to the space we're leaving for the suffix.
     */
    endp = &retp[headlen + rootlen];
    strcpy(endp++, ".tmp");
    while (access(retp, 0) == 0) {
        /*
         * Keep trying this until we get a unique file name.
         */
        Flexbuf suffix;

        flexnew(&suffix);
        (void) lformat(&suffix, "%03u", ++indexnum);
        (void) strncpy(endp, flexgetstr(&suffix), 3);
        flexdelete(&suffix);
    }
    return retp;
}

/*
 * Fork off children thus:
 *
 * [CHILD 1] --- pipe1 ---> [CHILD 2] --- pipe2 ---> [PARENT]
 *    |                        |                        |
 *    V                        V                        V
 * writefunc                exec(cmd)                readfunc
 *
 * connecting the pipes to stdin/stdout as appropriate.
 *
 * We assume that on entry to this function, file descriptors 0, 1 and 2
 * are already open, so we do not have to deal with the possibility of
 * them being allocated as pipe descriptors.
 */
bool_t
sys_pipe(cmd, writefunc, readfunc)
char    *cmd;
int     (*writefunc) P((FILE *));
long    (*readfunc) P((FILE *));
{
    int         pd1[2], pd2[2];         /* descriptors for pipes 1 and 2 */
    int         pid1, pid2;             /* process ids for children 1 and 2 */
    int         died;
    int         retval;
    FILE        *fp;
    Wait_t      status;
    static char *args[] = { NULL, "-c", NULL, NULL };

    if (Ps(P_shell) == NULL) {
        return(FALSE);
    }
    args[0] = Ps(P_shell);
    args[2] = cmd;

    /*
     * Initialise these values so we can work out what has happened
     * so far if we have to goto fail for any reason.
     */
    pd1[0] = pd1[1] = pd2[0] = pd2[1] = -1;
    pid1 = pid2 = -1;

    if (pipe(pd1) == -1) {
        goto fail;
    }

    switch (pid1 = fork()) {
    case -1:                                    /* error */
        goto fail;

    case 0:                                     /* child 1 */
        /*
         * Fdopen pipe1 to get a stream.
         */
        (void) close(pd1[0]);
        fp = fdopen(pd1[1], "w");
        if (fp == NULL) {
            exit(1);
        }

        /*
         * Call writefunc.
         */
        (void) (*writefunc)(fp);
        (void) fclose(fp);

        /*
         * Our work is done.
         */
        exit(0);

    default:                                    /* parent */
        (void) close(pd1[1]);
        break;
    }

    if (pipe(pd2) == -1) {
        goto fail;
    }
    switch (pid2 = fork()) {
    case -1:                                    /* error */
        goto fail;

    case 0:                                             /* child 2 */

        /*
         * Rearrange file descriptors onto stdin/stdout/stderr.
         */
        (void) close(pd1[1]);
        (void) close(pd2[0]);

        (void) close(0);
        (void) dup(pd1[0]);             /* dup2(pd1[0], 0) */
        (void) close(pd1[0]);

        (void) close(1);
        (void) dup(pd2[1]);             /* dup2(pd2[1], 1) */
        (void) close(pd2[1]);

        (void) close(2);
        (void) dup(1);                  /* dup2(1, 2) */

        /*
         * Exec the command.
         */
        (void) execvp(args[0], args);
        exit(1);

    default:                                    /* parent */
        (void) close(pd1[0]);
        (void) close(pd2[1]);
        pd1[0] = pd2[1] = -1;
        fp = fdopen(pd2[0], "r");
        if (fp == NULL) {
            goto fail;
        }
        (void) (*readfunc)(fp);
        (void) fclose(fp);
        break;
    }

    /*
     * Finally, clean up the children.
     */
    retval = TRUE;
    goto cleanup;

fail:
    /*
     * Come here if anything fails (if we are the original process).
     * Close any open pipes and goto cleanup to reap the child processes.
     */
    if (pd1[0] >= 0) {
        (void) close(pd1[0]);
        (void) close(pd1[1]);
    }
    if (pd2[0] >= 0) {
        (void) close(pd2[0]);
        (void) close(pd2[1]);
    }
    retval = FALSE;

cleanup:
    /*
     * Come here whether or not we failed, to clean up the children ...
     */
#ifdef  WIFEXITED
#   define      FAILED(s)       (!WIFEXITED(s) || (s).w_retcode != 0)
#else
#   define      FAILED(s)       ((s) != 0)
#endif
#ifndef WTERMSIG
#   ifdef   WIFSIGNALED
#       define  WTERMSIG(s)     (WIFSIGNALED(s) ? (s).w_termsig : 0)
#   else
#       define  WTERMSIG(s)     ((s) & 0177)
#   endif
#endif
    while ((died = wait(&status)) != -1) {
        if (died == pid1 || died == pid2) {
            /*
             * If child 1 was killed with SIGPIPE -
             * because child 2 exited before reading all
             * of its input - it isn't necessarily an
             * error.
             */
            if (
                FAILED(status)
                &&
                (
                    died == pid2
                    ||
                    WTERMSIG(status) != SIGPIPE
                )
            ) {
                retval = FALSE;
            }
        }
    }

    return(retval);
}

char *
fexpand(name)
char    *name;
{
    static char meta[] = "*?[]~${}`";
    char        *cp;
    int         pd[2];
    int         has_meta;

    has_meta = FALSE;
    for (cp = meta; *cp != '\0'; cp++) {
        if (strchr(name, *cp) != NULL) {
            has_meta = TRUE;
            break;
        }
    }
    if (!has_meta) {
        return(name);
    }

    if (Ps(P_shell) == NULL) {
        return(name);
    }
    if (pipe(pd) == -1) {
        return(name);
    }
    fflush(stdout);
    switch (fork()) {
    case -1:                            /* error */
        return(name);

    case 0:                             /* child */
    {
        static char     *args[] = {
            NULL,                       /* path of shell */
            "-c",
            NULL,                       /* echo %s */
            NULL,
        };
        Flexbuf         cmd;
        int             errout;

        args[0] = Ps(P_shell);
        flexnew(&cmd);
        (void) lformat(&cmd, "echo %s", name);
        args[2] = flexgetstr(&cmd);
        (void) fclose(stdout);
        (void) fclose(stderr);
        (void) close(pd[0]);
        while (dup(pd[1]) < 2)          /* redirect both stdout & stderr */
            ;
        (void) close(pd[1]);
        (void) close(0);
        (void) execvp(args[0], args);
        puts(name);
        exit(0);
    }
    default:                            /* parent */
    {
        Wait_t          status;
        FILE            *pfp;
        static Flexbuf  newname;
        register int    c;

        flexclear(&newname);
        (void) close(pd[1]);
        pfp = fdopen(pd[0], "r");
        if (pfp != NULL) {
            while ((c = getc(pfp)) != EOF && c != '\n') {
                if (!flexaddch(&newname, c)) {
                    break;
                }
            }
        }
        (void) fclose(pfp);
        (void) wait(&status);
        if (flexempty(&newname)) {
            return(name);
        }
        return(flexgetstr(&newname));
    }
    }
}
