root/usr/src/cmd/streams/strcmd/strchg.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */

/*
 * Streams Command strchg:      change the configuration of the
 *                              stream associated with stdin.
 *
 * USAGE:       strchg -h module1[,module2,module3 ...]
 *    or:       strchg -p
 *    or:       strchg -p -a
 *    or:       strchg -p -u module
 *    or:       strchg -f file
 *
 * -h           pusHes the named module(s) onto the stdin stream
 * -p           poPs the topmost module from the stdin stream
 * -p -a        poPs All modules
 * -p -u module poPs all modules Up to, but not including, the named module
 * -f file      reads a list of modules from the named File, pops all modules,
 *              then pushes the list of modules
 *
 * RETURNS:
 *      0       SUCCESS         it worked
 *      1       ERR_USAGE       bad invocation
 *      2       ERR_MODULE      bad module name(s)
 *      3       ERR_STDIN       an ioctl or stat on the stdin stream failed
 *      4       ERR_MEM         couldn't allocate memory
 *      5       ERR_OPEN        couldn't open file in -f opt
 *      6       ERR_PERM        not owner or superuser
 *
 */


#include <stdio.h>
#include <sys/stropts.h>
#include <sys/termio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define FALSE           0
#define TRUE            1

#define SUCCESS         0
#define FAILURE         1

#define NMODULES        16      /* "reasonable" # of modules to push      */
                                /*      (can push more if you like)       */
#define MAXMODULES      2048    /* max # of modules to push               */

#define OPTLIST         "af:h:pu:"
#define USAGE           "Usage:\t%s -h module1[,module2 ... ]\n\t%s -f file"\
                        "\n\t%s -p [-a | -u module ]\n"

#define ERR_USAGE       1       /* bad invocation                         */
#define ERR_MODULE      2       /* bad module name(s) or too many modules */
#define ERR_STDIN       3       /* an ioctl or stat on stdin failed       */
#define ERR_MEM         4       /* couldn't allocate memory               */
#define ERR_OPEN        5       /* couldn't open file in -f opt           */
#define ERR_PERM        6       /* not owner or superuser                 */

#define STDIN           0

static char             *Cmd_namep;             /* how was it invoked?  */
static struct str_mlist Oldmods[NMODULES];      /* modlist for Oldlist  */
static struct str_list  Oldlist;                /* original modules     */

static int      pop_modules(int);
static int      push_module(const char *);
static int      more_modules(struct str_list *, int);
static void     restore(int, int);

int
main(int argc, char **argv)
{
        char            buf[BUFSIZ];    /* input buffer                 */
        char            *file_namep;    /* file from -f opt             */
        char            *modnamep;      /* mods from -h or -u opt       */
        char            *modp;          /* for walking thru modnamep    */

        FILE            *fp;            /* file pointer for -f file     */

        int             i;              /* loop index and junk var      */
        int             j;              /* loop index and junk var      */
        int             euid;           /* effective uid                */

        short           error;          /* TRUE if usage error          */
        short           fromfile;       /* TRUE if -f file              */
        short           is_a_tty;       /* TRUE if TCGETA succeeds      */
        short           pop;            /* TRUE if -p                   */
        short           popall;         /* TRUE if -p -a                */
        short           popupto;        /* TRUE if -p -u module         */
        short           push;           /* TRUE if -h mod1[,mod2 ...]   */

        struct str_mlist newmods[NMODULES]; /* mod list for new list    */
        struct stat     stats;          /* stream stats                 */
        struct str_list newlist;        /* modules to be pushed         */
        struct termio   termio;         /* save state of tty            */

        /*
         *      init
         */

        Cmd_namep = argv[0];
        error = fromfile = is_a_tty = pop = popall = popupto = push = FALSE;
        Oldlist.sl_modlist = Oldmods;
        Oldlist.sl_nmods = NMODULES;
        newlist.sl_modlist = newmods;
        newlist.sl_nmods = NMODULES;

        /*
         *      only owner and root can change stream configuration
         */
        if ((euid = geteuid()) != 0) {
                if (fstat(0, &stats) < 0) {
                        perror("fstat");
                        (void) fprintf(stderr, "%s: fstat of stdin failed\n",
                                Cmd_namep);
                        return (ERR_STDIN);
                }
                if (euid != stats.st_uid) {
                        (void) fprintf(stderr,
                                "%s: not owner of stdin\n", Cmd_namep);
                        return (ERR_PERM);
                }
        }


        /*
         *      parse args
         */

        if (argc == 1) {
                (void) fprintf(stderr, USAGE, Cmd_namep, Cmd_namep, Cmd_namep);
                return (ERR_USAGE);
        }

        while (!error && (i = getopt(argc, argv, OPTLIST)) != -1) {

                switch (i) {

                case 'a':                               /* pop All      */
                        if (fromfile || popupto || push)
                                error = TRUE;
                        else
                                popall = TRUE;
                        break;

                case 'f':                               /* read from File */
                        if (pop || push)
                                error = TRUE;
                        else {
                                fromfile = TRUE;
                                file_namep = optarg;
                        }
                        break;

                case 'h':                               /* pusH         */
                        if (fromfile || pop)
                                error = TRUE;
                        else {
                                push = TRUE;
                                modnamep = optarg;
                        }
                        break;

                case 'p':                               /* poP          */
                        if (fromfile || push)
                                error = TRUE;
                        else
                                pop = TRUE;
                        break;

                case 'u':                               /* pop Upto     */
                        if (fromfile || popall || push)
                                error = TRUE;
                        else {
                                popupto = TRUE;
                                modnamep = optarg;
                        }
                        break;

                default:
                        (void) fprintf(stderr,
                                USAGE, Cmd_namep, Cmd_namep, Cmd_namep);
                        return (ERR_USAGE);
                        /*NOTREACHED*/
                }
        }

        if (error || optind < argc)  {
                (void) fprintf(stderr, USAGE, Cmd_namep, Cmd_namep, Cmd_namep);
                return (ERR_USAGE);
        }

        if (!pop && (popall || popupto)) {
                (void) fprintf(stderr,
                    "%s: -p option must be used with -a or -u to pop modules\n",
                    Cmd_namep);
                (void) fprintf(stderr, USAGE, Cmd_namep, Cmd_namep, Cmd_namep);
                return (ERR_USAGE);
        }


        /*
         * Save state so can restore if something goes wrong
         * (If are only going to push modules, don't need to
         * save original module list for restore.)
         */
        if (fromfile || pop) {

                /*
                 * get number of modules on stream
                 * allocate more room if needed
                 */
                if ((i =  ioctl(STDIN, I_LIST, NULL)) < 0) {
                        perror("I_LIST");
                        (void) fprintf(stderr,
                                "%s: I_LIST ioctl failed\n", Cmd_namep);
                        return (ERR_STDIN);
                }
                if (i > Oldlist.sl_nmods &&
                    more_modules(&Oldlist, i) != SUCCESS)
                                return (ERR_MEM);

                /*
                 * get list of modules on stream
                 */
                Oldlist.sl_nmods = i;
                if (ioctl(STDIN, I_LIST, &Oldlist) < 0) {
                        perror("I_LIST");
                        (void) fprintf(stderr,
                                "%s: I_LIST ioctl failed\n", Cmd_namep);
                        return (ERR_STDIN);
                }

                /*
                 * The following attempts to avoid leaving a
                 * terminal line that does not respond to anything
                 * if the strchg -h or -f options failed due to
                 * specifying invalid module names for pushing
                 */
                if (ioctl(STDIN, TCGETA, &termio) >= 0)
                        is_a_tty = TRUE;
        }


        /*
         *      push modules on stream
         */
        if (push) {
                /*
                 * pull mod names out of comma-separated list
                 */
                for (i = 0, modp = strtok(modnamep, ",");
                    modp != NULL; ++i, modp = strtok(NULL, ",")) {
                        if (push_module(modp) == FAILURE) {
                                /* pop the 'i' modules we just added */
                                restore(i, 0);
                                return (ERR_STDIN);
                        }
                }
                return (SUCCESS);
        }

        /*
         *      read configuration from a file
         */
        if (fromfile) {

                if ((fp = fopen(file_namep, "r")) == NULL) {
                        perror("fopen");
                        (void) fprintf(stderr,
                                "%s: could not open file '%s'\n",
                                Cmd_namep, file_namep);
                        return (ERR_OPEN);
                }

                /*
                 * read file and construct a new strlist
                 */
                i = 0;
                while (fgets(buf, BUFSIZ, fp) != NULL) {

                        if (buf[0] == '#')
                                continue;       /* skip comments */

                        /*
                         * skip trailing newline, trailing and leading
                         * whitespace
                         */
                        if ((modp = strtok(buf, " \t\n")) == NULL)
                                continue;       /* blank line */

                        (void) strncpy(newlist.sl_modlist[i].l_name,
                            modp, FMNAMESZ);
                        ++i;
                        if ((modp = strtok(NULL, " \t\n")) != NULL) {
                                /*
                                 * bad format
                                 * should only be one name per line
                                 */
                                (void) fprintf(stderr,
                                    "%s: error on line %d in file %s: "
                                    "multiple module names??\n",
                                    Cmd_namep, i, file_namep);
                                return (ERR_MODULE);
                        }
                        if (i > newlist.sl_nmods)
                                if (more_modules(&newlist, i) != SUCCESS)
                                        return (ERR_MEM);
                }
                newlist.sl_nmods = i;

                /*
                 * If an empty file, exit silently
                 */
                if (i == 0)
                        return (SUCCESS);

                /*
                 * Pop all modules currently on the stream.
                 */
                if ((i = pop_modules(Oldlist.sl_nmods - 1))
                    != (Oldlist.sl_nmods - 1)) {
                        /* put back whatever we've popped */
                        restore(0, i);
                        return (ERR_STDIN);
                }

                /*
                 * Push new modules
                 */
                for (i = newlist.sl_nmods - 1; i >= 0; --i) {
                        if (push_module(newlist.sl_modlist[i].l_name) ==
                            FAILURE) {

                                /*
                                 * pop whatever new modules we've pushed
                                 * then push old module list back on
                                 */
                                restore((newlist.sl_nmods - 1 - i),
                                    (Oldlist.sl_nmods - 1));

                                /*
                                 * If the stream is a tty line, at least try
                                 * to set the state to what it was before.
                                 */
                                if (is_a_tty &&
                                    ioctl(STDIN, TCSETA, &termio) < 0) {
                                        perror("TCSETA");
                                        (void) fprintf(stderr,
                                            "%s: WARNING: Could not restore "
                                            "the states of the terminal line "
                                            "discipline\n", Cmd_namep);
                                }
                                return (ERR_STDIN);
                        }
                }
                return (SUCCESS);
        }       /* end if-fromfile */


        /*
         *      pop all modules (except driver)
         */
        if (popall) {
                if (Oldlist.sl_nmods > 1) {
                        if ((i = pop_modules(Oldlist.sl_nmods - 1)) !=
                            (Oldlist.sl_nmods - 1)) {
                                restore(0, i);
                                return (ERR_STDIN);
                        }
                }
                return (SUCCESS);
        }

        /*
         *      pop up to (but not including) a module
         */
        if (popupto) {
                /*
                 * check that the module is in fact on the stream
                 */
                for (i = 0; i < Oldlist.sl_nmods; ++i)
                        if (strncmp(Oldlist.sl_modlist[i].l_name, modnamep,
                            FMNAMESZ) == 0)
                                break;
                if (i == Oldlist.sl_nmods) {
                        /* no match found */
                        (void) fprintf(stderr, "%s: %s not found on stream\n",
                                                        Cmd_namep, modnamep);
                        return (ERR_MODULE);
                }

                if ((j = pop_modules(i)) != i) {
                        /* put back whatever we've popped */
                        restore(0, j);
                        return (ERR_STDIN);
                }
                return (SUCCESS);
        }

        /*
         *      pop the topmost module
         */
        if (pop) {
                if (Oldlist.sl_nmods > 1)
                        if (pop_modules(1) != 1)
                                /* no need to restore */
                                return (ERR_STDIN);
                return (SUCCESS);
        }

        return (SUCCESS);
}

/*
 * pop_module(n)                pop 'n' modules from stream
 *
 * returns # of modules popped
 */
static int
pop_modules(int num_modules)
{
        int i;

        for (i = 0; i < num_modules; i++) {
                if (ioctl(STDIN, I_POP, 0) < 0) {
                        perror("I_POP");
                        (void) fprintf(stderr,
                            "%s: I_POP ioctl failed\n", Cmd_namep);
                        return (i);
                }
        }
        return (i);
}

/*
 * push_module(modnamep)        pushes 'modnamep' module on stream
 *
 * returns SUCCESS or FAILURE
 */
static int
push_module(const char *modnamep)
{
        if (ioctl(STDIN, I_PUSH, modnamep) < 0) {
                perror("I_PUSH");
                (void) fprintf(stderr,
                    "%s: I_PUSH ioctl of %s failed\n", Cmd_namep, modnamep);
                return (FAILURE);
        }
        return (SUCCESS);
}


/*
 * restore(npop, npush)         restore original state of stream
 *
 * pops 'npop' modules, then pushes the topmost 'npush' modules from
 * Oldlist
 *
 */
static void
restore(int npop, int npush)
{
        int     i;

        if ((i = pop_modules(npop)) != npop) {
                (void) fprintf(stderr,
                    "%s: WARNING: could not restore state of stream\n",
                    Cmd_namep);
                return;
        }

        if (npush >= Oldlist.sl_nmods) {        /* "cannot" happen */
                (void) fprintf(stderr,
                    "%s: internal logic error in restore\n", Cmd_namep);
                (void) fprintf(stderr,
                    "%s: WARNING: could not restore state of stream\n",
                    Cmd_namep);
                return;
        }

        for (i = npush - 1; i >= 0; --i) {
                if (push_module(Oldlist.sl_modlist[i].l_name) == FAILURE) {
                        (void) fprintf(stderr,
                            "%s: WARNING: could not restore state of stream\n",
                            Cmd_namep);
                        return;
                }
        }
}

/*
 * more_modules(listp, n)       allocate space for 'n' modules in 'listp'
 *
 * returns:     SUCCESS or FAILURE
 */

static int
more_modules(struct str_list *listp, int n)
{
        int                     i;
        struct str_mlist        *modp;

        if (n > MAXMODULES) {
                (void) fprintf(stderr,
                    "%s: too many modules (%d) -- max is %d\n",
                    Cmd_namep, n, MAXMODULES);
                return (FAILURE);
        }

        if ((modp = calloc(n, sizeof (struct str_mlist))) == NULL) {
                perror("calloc");
                (void) fprintf(stderr,
                    "%s: failed to allocate space for module list\n",
                    Cmd_namep);
                return (FAILURE);
        }

        for (i = 0; i < listp->sl_nmods; ++i)
                (void) strncpy(modp[i].l_name, listp->sl_modlist[i].l_name,
                    FMNAMESZ);
        listp->sl_nmods = n;
        listp->sl_modlist = modp;
        return (SUCCESS);
}