root/usr/src/cmd/saf/sacadm.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 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <unistd.h>
#include <sac.h>
#include <spawn.h>
#include "misc.h"
#include "structs.h"
#include "adm.h"
#include "extern.h"


/*
 * functions
 */

char    *pflags();
char    *getfield();
void    add_pm();
void    cleandirs();
void    rem_pm();
void    start_pm();
void    kill_pm();
void    enable_pm();
void    disable_pm();
void    list_pms();
void    read_db();
void    sendcmd();
void    checkresp();
void    single_print();
void    catch();
void    usage();
static  int invoke_rm(char *);

# define START          0x1     /* -s seen */
# define KILL           0x2     /* -k seen */
# define ENABLE         0x4     /* -e seen */
# define DISABLE        0x8     /* -d seen */
# define PLIST          0x10    /* -l seen */
# define LIST           0x20    /* -L seen */
# define DBREAD         0x40    /* -x seen */
# define CONFIG         0x80    /* -G seen */
# define PCONFIG        0x100   /* -g seen */
# define ADD            0x200   /* -a or other required options seen */
# define REMOVE         0x400   /* -r seen */

/*
 * common error messages
 */

# define NOTPRIV        "User not privileged for operation"
# define SACERR         "Can not contact SAC"
# define BADINP         "Embedded newlines not allowed"


int     Saferrno;       /* internal `errno' for exit */


/*
 * main - scan args for sacadm and call appropriate handling code
 */

int
main(int argc, char *argv[])
{
        int c;                  /* option letter */
        uid_t uid;              /* invoker's real uid */
        int ret;                /* return code from check_version */
        int flag = 0;           /* flag to record requested operations */
        int errflg = 0;         /* error indicator */
        int version = -1;       /* argument to -v */
        int count = 0;          /* argument to -n */
        int badcnt = 0;         /* count of bad args to -f */
        int sawaflag = 0;       /* true if actually saw -a */
        int conflag = 0;        /* true if output should be in condensed form */
        long flags = 0;         /* arguments to -f */
        FILE *fp;               /* scratch file pointer */
        char *pmtag = NULL;     /* argument to -p */
        char *type = NULL;      /* argument to -t */
        char *script = NULL;    /* argument to -z */
        char *command = NULL;   /* argument to -c */
        char *comment = " ";    /* argument to -y */
        char badargs[BADFARGSIZE];      /* place to hold bad args to -f */
        char buf[SIZE];         /* scratch buffer */
        register char *p;       /* scratch pointer */

        if (argc == 1)
                usage(argv[0]);
        while ((c = getopt(argc, argv, "ac:def:GgkLln:p:rst:v:xy:z:")) != -1) {
                switch (c) {
                case 'a':
                        flag |= ADD;
                        sawaflag = 1;
                        break;
                case 'c':
                        flag |= ADD;
                        if (strchr(optarg, '\n')) {
                                Saferrno = E_BADARGS;
                                error(BADINP);
                        }
                        command = optarg;
                        if (*command != '/') {
                                Saferrno = E_BADARGS;
                                error("command must be a full pathname");
                        }
                        break;
                case 'd':
                        flag |= DISABLE;
                        break;
                case 'e':
                        flag |= ENABLE;
                        break;
                case 'f':
                        flag |= ADD;
                        while (*optarg) {
                                switch (*optarg++) {
                                case 'd':
                                        flags |= D_FLAG;
                                        break;
                                case 'x':
                                        flags |= X_FLAG;
                                        break;
                                default:
                                        if (badcnt < (BADFARGSIZE -1))
                                            badargs[badcnt++] = *(optarg - 1);
                                        break;
                                }
                        }
                        /* null terminate just in case anything is there */
                        badargs[badcnt] = '\0';
                        break;
                case 'G':
                        flag |= CONFIG;
                        break;
                case 'g':
                        flag |= PCONFIG;
                        break;
                case 'k':
                        flag |= KILL;
                        break;
                case 'L':
                        flag |= LIST;
                        break;
                case 'l':
                        flag |= PLIST;
                        break;
                case 'n':
                        flag |= ADD;
                        count = atoi(optarg);
                        if (count < 0) {
                                Saferrno = E_BADARGS;
                                error("restart count can not be negative");
                        }
                        break;
                case 'p':
                        pmtag = optarg;
                        if (strchr(optarg, '\n')) {
                                Saferrno = E_BADARGS;
                                error(BADINP);
                        }
                        if (strlen(pmtag) > PMTAGSIZE) {
                                pmtag[PMTAGSIZE] = '\0';
                                (void) fprintf(stderr, "tag too long, truncated to <%s>\n", pmtag);
                        }
                        for (p = pmtag; *p; p++) {
                                if (!isalnum(*p)) {
                                        Saferrno = E_BADARGS;
                                        error("port monitor tag must be alphanumeric");
                                }
                        }
                        break;
                case 'r':
                        flag |= REMOVE;
                        break;
                case 's':
                        flag |= START;
                        break;
                case 't':
                        type = optarg;
                        if (strchr(optarg, '\n')) {
                                Saferrno = E_BADARGS;
                                error(BADINP);
                        }
                        if (strlen(type) > PMTYPESIZE) {
                                type[PMTYPESIZE] = '\0';
                                (void) fprintf(stderr, "type too long, truncated to <%s>\n", type);
                        }
                        for (p = type; *p; p++) {
                                if (!isalnum(*p)) {
                                        Saferrno = E_BADARGS;
                                        error("port monitor type must be alphanumeric");
                                }
                        }
                        break;
                case 'v':
                        flag |= ADD;
                        version = atoi(optarg);
                        if (version < 0) {
                                Saferrno = E_BADARGS;
                                error("version number can not be negative");
                        }
                        break;
                case 'x':
                        flag |= DBREAD;
                        break;
                case 'y':
                        flag |= ADD;
                        if (strchr(optarg, '\n')) {
                                Saferrno = E_BADARGS;
                                error(BADINP);
                        }
                        comment = optarg;
                        break;
                case 'z':
                        if (strchr(optarg, '\n')) {
                                Saferrno = E_BADARGS;
                                error(BADINP);
                        }
                        script = optarg;
                        break;
                case '?':
                        errflg++;
                }
        }
        if (errflg || (optind < argc))
                usage(argv[0]);

        if (badcnt) {
                /* bad flags were given to -f */
                Saferrno = E_BADARGS;
                (void) sprintf(buf,
                    "Invalid request, %s are not valid arguments for \"-f\"",
                        badargs);
                error(buf);
        }

        if ((ret = check_version(VERSION, SACTAB)) == 1) {
                Saferrno = E_SAFERR;
                error("_sactab version number is incorrect");
        }
        else if (ret == 2) {
                (void) sprintf(buf, "could not open %s", SACTAB);
                Saferrno = E_SYSERR;
                error(buf);
        }
        else if (ret == 3) {
                (void) sprintf(buf, "%s file is corrupt", SACTAB);
                Saferrno = E_SAFERR;
                error(buf);
        }
        uid = getuid();
        switch (flag) {
        case ADD:
                if (uid) {
                        Saferrno = E_NOPRIV;
                        error(NOTPRIV);
                }
                if (!sawaflag || !pmtag || !type || !command || (version < 0))
                        usage(argv[0]);
                add_pm(pmtag, type, command, version, flags, count, script, comment);
                break;
        case REMOVE:
                if (uid) {
                        Saferrno = E_NOPRIV;
                        error(NOTPRIV);
                }
                if (!pmtag || type || script)
                        usage(argv[0]);
                rem_pm(pmtag);
                break;
        case START:
                if (uid) {
                        Saferrno = E_NOPRIV;
                        error(NOTPRIV);
                }
                if (!pmtag || type || script)
                        usage(argv[0]);
                start_pm(pmtag);
                break;
        case KILL:
                if (uid) {
                        Saferrno = E_NOPRIV;
                        error(NOTPRIV);
                }
                if (!pmtag || type || script)
                        usage(argv[0]);
                kill_pm(pmtag);
                break;
        case ENABLE:
                if (uid) {
                        Saferrno = E_NOPRIV;
                        error(NOTPRIV);
                }
                if (!pmtag || type || script)
                        usage(argv[0]);
                enable_pm(pmtag);
                break;
        case DISABLE:
                if (uid) {
                        Saferrno = E_NOPRIV;
                        error(NOTPRIV);
                }
                if (!pmtag || type || script)
                        usage(argv[0]);
                disable_pm(pmtag);
                break;
        case LIST:
                conflag = 1;
                /* fall through */
        case PLIST:
                if ((pmtag && type) || script)
                        usage(argv[0]);
                list_pms(pmtag, type, conflag);
                break;
        case DBREAD:
                if (uid) {
                        Saferrno = E_NOPRIV;
                        error(NOTPRIV);
                }
                if (type || script)
                        usage(argv[0]);
                read_db(pmtag);
                break;
        case CONFIG:
                if (script && uid) {
                        Saferrno = E_NOPRIV;
                        error(NOTPRIV);
                }
                if (type || pmtag)
                        usage(argv[0]);
                (void) do_config(script, "_sysconfig");
                break;
        case PCONFIG:
                if (script && uid) {
                        Saferrno = E_NOPRIV;
                        error(NOTPRIV);
                }
                if (!pmtag || type)
                        usage(argv[0]);
                fp = fopen(SACTAB, "r");
                if (fp == NULL) {
                        Saferrno = E_SYSERR;
                        error("Could not open _sactab");
                }
                if (!find_pm(fp, pmtag)) {
                        Saferrno = E_NOEXIST;
                        (void) sprintf(buf, "Invalid request, %s does not exist", pmtag);
                        error(buf);
                }
                (void) fclose(fp);
                (void) sprintf(buf, "%s/_config", pmtag);
                (void) do_config(script, buf);
                break;
        default:
                /* we only get here if more than one flag bit was set */
                usage(argv[0]);
                /* NOTREACHED */
        }
        quit();
        /* NOTREACHED */
        return (0);
}


/*
 * usage - print out a usage message
 *
 *      args:   cmdname - the name command was invoked with
 */

void
usage(cmdname)
char *cmdname;
{
        (void) fprintf(stderr, "Usage:\t%s -a -p pmtag -t type -c cmd -v ver [ -f dx ] [ -n count ]\n", cmdname);
        (void) fprintf(stderr, "\t\t[ -y comment ] [ -z script]\n");
        (void) fprintf(stderr, "\t%s -r -p pmtag\n", cmdname);
        (void) fprintf(stderr, "\t%s -s -p pmtag\n", cmdname);
        (void) fprintf(stderr, "\t%s -k -p pmtag\n", cmdname);
        (void) fprintf(stderr, "\t%s -e -p pmtag\n", cmdname);
        (void) fprintf(stderr, "\t%s -d -p pmtag\n", cmdname);
        (void) fprintf(stderr, "\t%s -l [ -p pmtag | -t type ]\n", cmdname);
        (void) fprintf(stderr, "\t%s -L [ -p pmtag | -t type ]\n", cmdname);
        (void) fprintf(stderr, "\t%s -g -p pmtag [ -z script ]\n", cmdname);
        (void) fprintf(stderr, "\t%s -G [ -z script ]\n", cmdname);
        (void) fprintf(stderr, "\t%s -x [ -p pmtag ]\n", cmdname);
        Saferrno = E_BADARGS;
        quit();
}


/*
 * add_pm - add a port monitor entry
 *
 *      args:   tag - port monitor's tag
 *              type - port monitor's type
 *              command - command string to invoke port monitor
 *              version - version number of port monitor's pmtab
 *              flags - port monitor flags
 *              count - restart count
 *              script - port monitor's configuration script
 *              comment - comment describing port monitor
 */

void
add_pm(tag, type, command, version, flags, count, script, comment)
char *tag;
char *type;
char *command;
int version;
long flags;
int count;
char *script;
char *comment;
{
        FILE *fp;               /* file pointer for _sactab */
        int fd;                 /* scratch file descriptor */
        struct stat statbuf;    /* file status info */
        char buf[SIZE];         /* scratch buffer */
        char fname[SIZE];       /* scratch buffer for building names */
        register int i;         /* scratch variable */
        int retval = 0;         /* return value from invoke_rm() function */

        fp = fopen(SACTAB, "r");
        if (fp == NULL) {
                Saferrno = E_SYSERR;
                error("Could not open _sactab");
        }
        if (find_pm(fp, tag)) {
                Saferrno = E_DUP;
                (void) sprintf(buf, "Invalid request, %s already exists", tag);
                error(buf);
        }
        (void) fclose(fp);

/*
 * create the directories for it if needed and put in initial files
 * (/etc/saf and /var/saf)
 */

        for (i = 0; i < 2; i++) {
                /* i == 0 do /etc/saf i == 1 do /var/saf */
                (void) sprintf(fname, "%s/%s", (i == 0 ) ? HOME : ALTHOME, tag);
                if (access(fname, 0) == 0) {
                        /* something is there, find out what it is */
                        if (stat(fname, &statbuf) < 0) {
                                Saferrno = E_SYSERR;
                                (void) sprintf(buf, "could not stat <%s>", fname);
                                error(buf);
                        }
                        if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
                                Saferrno = E_SYSERR;
                                (void) sprintf(buf, "<%s> exists and is not a directory", fname);
                                error(buf);
                        }
                        /* note: this removes the directory too */
                        if ((retval = invoke_rm(fname)) != 0) {
                                Saferrno = E_SYSERR;
                                if (snprintf(buf, sizeof (buf),
                                    "could not remove files under <%s>",
                                    fname) >= sizeof (buf)) {
                                        snprintf(buf, sizeof (buf),
                                            "tag too long");
                                }
                                error(buf);
                        }
                }

/*
 * create the directory
 */

                if (mkdir(fname, 0755) < 0) {
                        Saferrno = E_SYSERR;
                        (void) sprintf(buf, "could not create directory <%s>", fname);
                        cleandirs(tag);
                        error(buf);
                }
        }

/*
 * put in the config script, if specified
 */

        if (script) {
                (void) sprintf(fname, "%s/_config", tag);
                if (do_config(script, fname)) {
                        cleandirs(tag);
                        /* do_config put out any messages */
                        quit();
                }
        }

/*
 * create the communications pipe, but first make sure that the
 * permissions we specify are what we get
 */

        (void) umask(0);
        (void) sprintf(fname, "%s/%s/_pmpipe", HOME, tag);
        if (mknod(fname, S_IFIFO | 0600, 0) < 0) {
                Saferrno = E_SYSERR;
                cleandirs(tag);
                error("could not create communications pipe");
        }

/*
 * create the _pid file
 */

        (void) sprintf(fname, "%s/%s/_pid", HOME, tag);
        if ((fd = creat(fname, 0644)) < 0) {
                Saferrno = E_SYSERR;
                cleandirs(tag);
                error("could not create _pid file");
        }
        (void) close(fd);

/*
 * create the _pmtab file
 */

        (void) sprintf(fname, "%s/%s/_pmtab", HOME, tag);
        if ((fd = creat(fname, 0644)) < 0) {
                Saferrno = E_SYSERR;
                cleandirs(tag);
                error("could not create _pmtab file");
        }
        (void) sprintf(buf, "%s%d\n", VSTR, version);
        if (write(fd, buf, (unsigned) strlen(buf)) != strlen(buf)) {
                (void) close(fd);
                (void) unlink(fname);
                Saferrno = E_SYSERR;
                cleandirs(tag);
                error("error initializing _pmtab");
        }
        (void) close(fd);

/*
 * isolate the command name, but remember it since strtok() trashes it
 */

        if (strlcpy(buf, command, sizeof (buf)) >= sizeof (buf)) {
                Saferrno = E_SYSERR;
                cleandirs(tag);
                error("command string too long");
        }

        (void) strtok(command, " \t");

/*
 * check out the command - let addition succeed if it doesn't exist (assume
 * it will be added later); fail anything else
 */

        if (access(command, 0) == 0) {
                if (stat(command, &statbuf) < 0) {
                        Saferrno = E_SYSERR;
                        (void) fprintf(stderr, "Could not stat <%s>\n", command);
                        cleandirs(tag);
                        quit();
                }
                if (!(statbuf.st_mode & 0111)) {
                        Saferrno = E_BADARGS;
                        (void) fprintf(stderr, "%s not executable\n", command);
                        cleandirs(tag);
                        quit();
                }
                if ((statbuf.st_mode & S_IFMT) != S_IFREG) {
                        Saferrno = E_BADARGS;
                        (void) fprintf(stderr, "%s not a regular file\n", command);
                        cleandirs(tag);
                        quit();
                }
        }
        else {
                (void) fprintf(stderr, "warning - %s does not exist\n", command);
        }

/*
 * add the line
 */

        fp = fopen(SACTAB, "a");
        if (fp == NULL) {
                Saferrno = E_SYSERR;
                cleandirs(tag);
                error("Could not open _sactab");
        }
        (void) fprintf(fp, "%s:%s:%s:%d:%s\t#%s\n", tag, type,
                (flags ? pflags(flags, FALSE) : ""), count, buf,
                (comment ? comment : ""));
        (void) fclose(fp);


/*
 * tell the SAC to read _sactab if its there (i.e. single user)
 */

        if (sac_home())
                read_db(NULL);
        return;
}


/*
 * cleandirs - remove anything that might have been created (i.e. failed
 *      addition.  Saferrno is set elsewhere; this is strictly an attempt
 *      to clean up what mess we've left, so don't check to see if the
 *      cleanup worked.
 *
 *      args:   tag - tag of port monitor whose trees should be removed
 */

void
cleandirs(tag)
char *tag;
{
        char buf[SIZE];         /* scratch buffer */

        /* note: this removes the directory too, first zap /etc/saf/<tag> */
        if (snprintf(buf, sizeof (buf), "%s/%s", HOME, tag) >= sizeof (buf))
                (void) fprintf(stderr, "tag too long\n");
        else
                (void) invoke_rm(buf);

        /* now remove /var/saf/<tag> */
        if (snprintf(buf, sizeof (buf), "%s/%s", ALTHOME, tag) >= sizeof (buf))
                (void) fprintf(stderr, "tag too long\n");
        else
                (void) rmdir(buf);
}


/*
 * rem_pm - remove a port monitor
 *
 *      args:   tag - tag of port monitor to be removed
 */

void
rem_pm(tag)
char *tag;
{
        FILE *fp;               /* file pointer for _sactab */
        FILE *tfp;              /* file pointer for temp file */
        int line;               /* line number entry is on */
        char *tname;            /* temp file name */
        char buf[SIZE];         /* scratch buffer */

        fp = fopen(SACTAB, "r");
        if (fp == NULL) {
                Saferrno = E_SYSERR;
                error("Could not open _sactab");
        }
        if ((line = find_pm(fp, tag)) == 0) {
                Saferrno = E_NOEXIST;
                (void) sprintf(buf, "Invalid request, %s does not exist", tag);
                error(buf);
        }
        tname = make_tempname("_sactab");
        tfp = open_temp(tname);
        if (line != 1) {
                if (copy_file(fp, tfp, 1, line - 1)) {
                        (void) unlink(tname);
                        Saferrno = E_SYSERR;
                        error("error accessing temp file");
                }
        }
        if (copy_file(fp, tfp, line + 1, -1)) {
                (void) unlink(tname);
                Saferrno = E_SYSERR;
                error("error accessing temp file");
        }
        (void) fclose(fp);
        if (fclose(tfp) == EOF) {
                (void) unlink(tname);
                Saferrno = E_SYSERR;
                error("error closing tempfile");
        }
        /* note - replace only returns if successful */
        replace("_sactab", tname);

/*
 * tell the SAC to read _sactab if its there (i.e. single user)
 */

        if (sac_home())
                read_db(NULL);
        return;
}


/*
 * start_pm - start a particular port monitor
 *
 *      args:   tag - tag of port monitor to be started
 */

void
start_pm(tag)
char *tag;
{
        struct admcmd cmd;                      /* command structure */
        register struct admcmd *ap = &cmd;      /* and a pointer to it */

        ap->ac_mtype = AC_START;
        (void) strcpy(ap->ac_tag, tag);
        ap->ac_pid = getpid();
        sendcmd(ap, NULL, tag);
        return;
}


/*
 * kill_pm - stop a particular port monitor
 *
 *      args:   tag - tag of port monitor to be stopped
 */

void
kill_pm(tag)
char *tag;
{
        struct admcmd cmd;                      /* command structure */
        register struct admcmd *ap = &cmd;      /* and a pointer to it */

        ap->ac_mtype = AC_KILL;
        (void) strcpy(ap->ac_tag, tag);
        ap->ac_pid = getpid();
        sendcmd(ap, NULL, tag);
        return;
}


/*
 * enable_pm - enable a particular port monitor
 *
 *      args:   tag - tag of port monitor to be enabled
 */

void
enable_pm(tag)
char *tag;
{
        struct admcmd cmd;                      /* command structure */
        register struct admcmd *ap = &cmd;      /* and a pointer to it */

        ap->ac_mtype = AC_ENABLE;
        (void) strcpy(ap->ac_tag, tag);
        ap->ac_pid = getpid();
        sendcmd(ap, NULL, tag);
        return;
}


/*
 * disable_pm - disable a particular port monitor
 *
 *      args:   tag - tag of port monitor to be disabled
 */

void
disable_pm(tag)
char *tag;
{
        struct admcmd cmd;                      /* command structure */
        register struct admcmd *ap = &cmd;      /* and a pointer to it */

        ap->ac_mtype = AC_DISABLE;
        (void) strcpy(ap->ac_tag, tag);
        ap->ac_pid = getpid();
        sendcmd(ap, NULL, tag);
        return;
}


/*
 * read_db - tell SAC or a port monitor to read its administrative file.
 *
 *      args:   tag - tag of port monitor that should read its administrative
 *                    file.  If NULL, it means SAC should.
 */

void
read_db(tag)
char *tag;
{
        struct admcmd cmd;                      /* command structure */
        register struct admcmd *ap = &cmd;      /* and a pointer to it */

        ap->ac_mtype = (tag) ? AC_PMREAD : AC_SACREAD;
        if (tag)
                (void) strcpy(ap->ac_tag, tag);
        ap->ac_pid = getpid();
        sendcmd(ap, NULL, tag);
        return;
}


/*
 * list_pms - request information about port monitors from SAC and output
 *              requested info
 *
 *      args:   pmtag - tag of port monitor to be listed (may be null)
 *              pmtype - type of port monitors to be listed (may be null)
 *              oflag - true if output should be easily parseable
 */

void
list_pms(pmtag, pmtype, oflag)
char *pmtag;
char *pmtype;
int oflag;
{
        struct admcmd acmd;                     /* command structure */
        register struct admcmd *ap = &acmd;     /* and a pointer to it */
        int nprint = 0;                         /* count # of PMs printed */
        char *p;                                /* scratch pointer */
        char *tag;                              /* returned tag */
        char *type;                             /* returned type */
        char *flags;                            /* returned flags */
        char *rsmax;                            /* returned restart count */
        char *state;                            /* returned state */
        char *cmd;                              /* returned command string */
        char *comment;                          /* returned comment string */

/*
 * if sac isn't there (single user), provide info direct from _sactab
 * note: when this routine returns, the process exits, so there is no
 * need to free up any memory
 */

        p = NULL;
        if (sac_home()) {
                ap->ac_mtype = AC_STATUS;
                ap->ac_tag[0] = '\0';
                ap->ac_pid = getpid();
                sendcmd(ap, &p, NULL);
        }
        else {
                single_print(&p);
        }

/*
 * SAC sends back info in condensed form, we have to separate it out
 * fields come in ':' separated, records are separated by newlines
 */

        while (p && *p) {
                tag = getfield(&p, ':');        /* PM tag */
                type = getfield(&p, ':');       /* PM type */
                flags = getfield(&p, ':');      /* flags */
                rsmax = getfield(&p, ':');      /* restart count */
                state = pstate((unchar) atoi(getfield(&p, ':')));       /* state in nice output format */
                cmd = getfield(&p, ':');        /* command */
                comment = getfield(&p, '\n');   /* comment */


/*
 * print out if no selectors specified, else check to see if
 * a selector matched
 */

                if ((!pmtag && !pmtype) || (pmtag && !strcmp(pmtag, tag)) || (pmtype && !strcmp(pmtype, type))) {
                        if (oflag) {
                                (void) printf("%s:%s:%s:%s:%s:%s#%s\n", tag, type, pflags(atol(flags), FALSE),
                                                rsmax, state, cmd, comment);
                        }
                        else {
                                if (nprint == 0) {
                                        (void) printf("PMTAG          PMTYPE         FLGS RCNT STATUS     COMMAND\n");
                                }
                                (void) printf("%-14s %-14s %-4s %-4s %-10s %s #%s\n", tag, type, pflags(atol(flags), TRUE),
                                                rsmax, state, cmd, comment);
                        }
                        nprint++;
                }
        }
        /*
         * if we didn't find any valid ones, indicate an error (note: 1 and
         * only 1 of the if statements should be true)
         */
        if (nprint == 0) {
                if (pmtype)
                        (void) fprintf(stderr, "Invalid request, %s does not exist\n", pmtype);
                else if (pmtag)
                        (void) fprintf(stderr, "Invalid request, %s does not exist\n", pmtag);
                else if (!pmtag && !pmtype)
                        (void) fprintf(stderr, "No port monitors defined\n");
                Saferrno = E_NOEXIST;
        }
        return;
}


/*
 * getfield - retrieve and return a field from the sac "status" string (input
 *      argument is modified to point to next field as a side-effect)
 *
 *      args:   p - address of remaining portion of string
 *              sepchar - field terminator character
 */

char *
getfield(p, sepchar)
char **p;
char sepchar;
{
        char *savep;    /* for saving argument */

        savep = *p;
        *p = strchr(*p, sepchar);
        if (*p == NULL) {
                Saferrno = E_SAFERR;
                (void) fprintf(stderr, "Improper message from SAC\n");
                return(NULL);
        }
        **p = '\0';
        (*p)++;
        return(savep);
}


/*
 * single_print - print out _sactab if sac not at home (should only happen
 *      in single user mode
 *
 *      args:   p - address of pointer where formatted data should be
 *                  placed (space allocated here)
 */

void
single_print(p)
char **p;
{
        FILE *fp;                               /* file pointer for _sactab */
        struct stat statbuf;                    /* file status info */
        register char *tp1;                     /* scratch pointer */
        register char *tp2;                     /* scratch pointer */
        struct sactab stab;                     /* place to hold parsed info */
        register struct sactab *sp = &stab;     /* and a pointer to it */
        char buf[SIZE];                         /* scratch buffer */

        fp = fopen(SACTAB, "r");
        if (fp == NULL) {
                Saferrno = E_SYSERR;
                error("Could not open _sactab");
        }
        if (fstat(fileno(fp), &statbuf) < 0) {
                Saferrno = E_SYSERR;
                error("could not stat _sactab");
        }

/*
 * allocate space to build return string, twice file size should be more
 * than enough (and make sure it's zero'ed out)
 */

        tp1 = calloc(2 * statbuf.st_size, sizeof(char));
        if (tp1 == NULL) {
                Saferrno = E_SYSERR;
                error("could not allocate storage");
        }

/*
 * read the file and build the string
 */

        while (fgets(buf, SIZE, fp)) {
                tp2 = trim(buf);
                if (*tp2 == '\0')
                        continue;
                parse(tp2, &stab);
                (void) sprintf(buf, "%s:%s:%d:%d:%d:%s:%s\n", sp->sc_tag, sp->sc_type,
                        sp->sc_flags, sp->sc_rsmax, SSTATE, sp->sc_cmd, sp->sc_comment);
                (void) strcat(tp1, buf);
                free(sp->sc_cmd);
                free(sp->sc_comment);
        }
        if (!feof(fp)) {
                Saferrno = E_SYSERR;
                error("error reading _sactab");
        }
        (void) fclose(fp);

/*
 * point at the just-built string
 */

        *p = tp1;
        return;
}


/*
 * openpipe - open up command pipe to SAC
 */

int
openpipe()
{
        int fd;         /* file descriptor associated with command pipe */

        fd = open(CMDPIPE, O_RDWR);
        if (fd < 0) {
                Saferrno = E_SYSERR;
                error(SACERR);
        }

/*
 * lock pipe to insure serial access, lock will disappear if process dies
 */

        if (lockf(fd, F_LOCK, 0) < 0) {
                Saferrno = E_SYSERR;
                error("unable to lock command pipe");
        }
        return(fd);
}


/*
 * sendcmd - send a command to the SAC
 *
 *      args:   ap - pointer to command to send
 *              info - pointer to return information from the SAC
 *              tag - tag of port monitor to which the command applies (may
 *                    be NULL)
 */

void
sendcmd(ap, info, tag)
struct admcmd *ap;
char **info;
char *tag;
{
        int fd;         /* file descriptor of command pipe */

        fd = openpipe();
        if (write(fd, ap, sizeof(struct admcmd)) < 0) {
                Saferrno = E_SYSERR;
                error(SACERR);
        }
        checkresp(fd, info, tag);

/*
 * unlock the command pipe - not really necessary since we're about to close
 */

        (void) lockf(fd, F_ULOCK, 0);
        (void) close(fd);
        return;
}


/*
 * checkresp - check the SAC's response to our command
 *
 *      args:   fd - file descriptor of command pipe
 *              info - pointer to return and info send along by SAC
 *              tag - tag of port monitor that the command had been
 *                    for, only used for error reporting
 */

void
checkresp(fd, info, tag)
int fd;
char **info;
char *tag;
{
        struct admack ack;                      /* acknowledgment struct */
        register struct admack *ak = &ack;      /* and a pointer to it */
        pid_t pid;                              /* my pid */
        struct sigaction sigact;                /* signal handler setup */

/*
 * make sure this ack is meant for me, put an alarm around the read
 * so we don't hang out forever.
 */

        pid = getpid();
        sigact.sa_flags = 0;
        sigact.sa_handler = catch;
        (void) sigemptyset(&sigact.sa_mask);
        (void) sigaddset(&sigact.sa_mask, SIGALRM);
        (void) sigaction(SIGALRM, &sigact, NULL);
        (void) alarm(10);
        do {
                if (read(fd, ak, sizeof(ack)) != sizeof(ack)) {
                        Saferrno = E_SACNOTRUN;
                        error(SACERR);
                }
        } while (pid != ak->ak_pid);
        (void) alarm(0);

/*
 * check out what happened
 */

        switch (ak->ak_resp) {
        case AK_ACK:
                /* everything was A-OK */
                if (info && ak->ak_size) {
                        /* there is return info and a place to put it */
                        if ((*info = malloc((unsigned) (ak->ak_size + 1))) == NULL) {
                                Saferrno = E_SYSERR;
                                error("could not allocate storage");
                        }
                        if (read(fd, *info, (unsigned) ak->ak_size) != ak->ak_size) {
                                Saferrno = E_SYSERR;
                                error(SACERR);
                        }
                        /* make sure "string" is null-terminated */
                        (*info)[ak->ak_size] = '\0';
                }
                return;
        /* something went wrong - see what */
        case AK_PMRUN:
                Saferrno = E_PMRUN;
                (void) fprintf(stderr, "Port monitor, %s, is already running\n", tag);
                break;
        case AK_PMNOTRUN:
                Saferrno = E_PMNOTRUN;
                (void) fprintf(stderr, "Port monitor, %s, is not running\n", tag);
                break;
        case AK_NOPM:
                Saferrno = E_NOEXIST;
                (void) fprintf(stderr, "Invalid request, %s does not exist\n", tag);
                break;
        case AK_UNKNOWN:
                Saferrno = E_SAFERR;
                (void) fprintf(stderr, "Internal error - sent invalid command\n");
                break;
        case AK_NOCONTACT:
                Saferrno = E_SAFERR;
                (void) fprintf(stderr, "Could not contact %s\n", tag);
                break;
        case AK_PMLOCK:
                Saferrno = E_SAFERR;
                (void) fprintf(stderr, "Could not start %s - _pid file locked\n", tag);
                break;
        case AK_RECOVER:
                Saferrno = E_RECOVER;
                (void) fprintf(stderr, "Port monitor, %s, is in recovery\n", tag);
                break;
        case AK_REQFAIL:
                Saferrno = E_SAFERR;
                (void) fprintf(stderr, "This request could not be completed - see sac log file for details\n");
                break;
        default:
                Saferrno = E_SAFERR;
                (void) fprintf(stderr, "unknown response\n");
                break;
        }
}


/*
 * catch - catcher for SIGALRM, don't need to do anything
 */

void
catch()
{
}


/*
 * pflags - put port monitor flags into intelligible form for output
 *
 *      args:   flags - binary representation of flags
 *              dflag - true if a "-" should be returned if no flags
 */

char *
pflags(flags, dflag)
long flags;
int dflag;
{
        register int i;                 /* scratch counter */
        static char buf[SIZE];          /* formatted flags */

        if (flags == 0) {
                if (dflag)
                        return("-");
                else
                        return("");
        }
        i = 0;
        if (flags & D_FLAG) {
                buf[i++] = 'd';
                flags &= ~D_FLAG;
        }
        if (flags & X_FLAG) {
                buf[i++] = 'x';
                flags &= ~X_FLAG;
        }
        if (flags) {
                (void) fprintf(stderr, "Bad information from SAC\n");
                exit(1);
        }
        buf[i] = '\0';
        return(buf);
}


/*
 * sac_home - returns true is sac has a lock on its logfile, false
 *      otherwise (useful to avoid errors for administrative actions in
 *      single user mode)
 */

int
sac_home()
{
        int fd;         /* fd to sac logfile */

        fd = open(LOGFILE, O_RDONLY);
        if (fd < 0) {
                fprintf(stderr, "warning - could not ascertain sac status\n");
                return(FALSE);
        }
        if (lockf(fd, F_TEST, 0) < 0) {
                /* everything is ok */
                (void) close(fd);
                return(TRUE);
        }
        else {
                /* no one home */
                (void) close(fd);
                return(FALSE);
        }
}

/*
 * invoke_rm - deletes the argument directory and all its files/subdirectories
 */
static int
invoke_rm(char *fname)
{
        pid_t cpid;             /* process ID of the child process */
        int cstatus;            /* status of child process */
        char *argvec[4];

        argvec[0] = "rm";
        argvec[1] = "-rf";
        argvec[2] = fname;
        argvec[3] = NULL;

        if (posix_spawn(&cpid, "/usr/bin/rm", NULL, NULL,
            (char *const *)argvec, NULL))
                return (-1);
        if (waitpid(cpid, &cstatus, 0) == -1)
                return (-1);

        return ((WIFEXITED(cstatus) == 0) ? 99 : WEXITSTATUS(cstatus));
}