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

/*
 *      syseventadm - command to administer the sysevent.conf registry
 *                  - administers the general purpose event framework
 *
 *      The current implementation of the registry using files in
 *      /etc/sysevent/config, files are named as event specifications
 *      are added with the combination of the vendor, publisher, event
 *      class and subclass strings:
 *
 *      [<vendor>,][<publisher>,][<class>,]sysevent.conf
 *
 */
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <door.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <strings.h>
#include <unistd.h>
#include <synch.h>
#include <syslog.h>
#include <thread.h>
#include <limits.h>
#include <locale.h>
#include <assert.h>
#include <libsysevent.h>
#include <zone.h>
#include <sys/sysevent_impl.h>
#include <sys/modctl.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/systeminfo.h>
#include <sys/wait.h>

#include "syseventadm.h"
#include "syseventadm_msg.h"

#ifndef DEBUG
#undef  assert
#define assert(EX) ((void)0)
#endif

static char     *whoami         = NULL;
static char     *root_dir       = "";

static char     *arg_vendor     = NULL;
static char     *arg_publisher  = NULL;
static char     *arg_class      = NULL;
static char     *arg_subclass   = NULL;
static char     *arg_username   = NULL;
static char     *arg_path       = NULL;
static int      arg_nargs       = 0;
static char     **arg_args      = NULL;

static  int     lock_fd;
static  char    lock_file[PATH_MAX + 1];

extern char     *optarg;
extern int      optind;

static int
usage_gen()
{
        (void) fprintf(stderr, MSG_USAGE_INTRO);
        (void) fprintf(stderr, MSG_USAGE_OPTIONS);
        (void) fprintf(stderr, "\n"
            "\tsyseventadm add ...\n"
            "\tsyseventadm remove ...\n"
            "\tsyseventadm list ...\n"
            "\tsyseventadm restart\n"
            "\tsyseventadm help\n");

        return (EXIT_USAGE);
}

static int
serve_syseventdotconf(int argc, char **argv, char *cmd)
{
        int     c;
        int     rval;

        while ((c = getopt(argc, argv, "R:v:p:c:s:u:")) != EOF) {
                switch (c) {
                case 'R':
                        /*
                         * Alternate root path for install, etc.
                         */
                        set_root_dir(optarg);
                        break;
                case 'v':
                        arg_vendor = optarg;
                        break;
                case 'p':
                        arg_publisher = optarg;
                        break;
                case 'c':
                        arg_class = optarg;
                        break;
                case 's':
                        arg_subclass = optarg;
                        break;
                case 'u':
                        arg_username = optarg;
                        break;
                default:
                        return (usage());
                }
        }

        if (optind < argc) {
                arg_path = argv[optind++];
                if (optind < argc) {
                        arg_nargs = argc - optind;
                        arg_args = argv + optind;
                }
        }

        enter_lock(root_dir);

        if (strcmp(cmd, "add") == 0) {
                rval = add_cmd();
        } else if (strcmp(cmd, "list") == 0) {
                rval = list_remove_cmd(CMD_LIST);
        } else if (strcmp(cmd, "remove") == 0) {
                rval = list_remove_cmd(CMD_REMOVE);
        } else if (strcmp(cmd, "restart") == 0) {
                rval = restart_cmd();
        } else {
                rval = usage();
        }

        exit_lock();

        return (rval);
}


int
main(int argc, char **argv)
{
        char    *cmd;
        int     rval;


        (void) setlocale(LC_ALL, "");
        (void) textdomain(TEXT_DOMAIN);

        if ((whoami = strrchr(argv[0], '/')) == NULL) {
                whoami = argv[0];
        } else {
                whoami++;
        }

        if (argc == 1) {
                return (usage_gen());
        }

        cmd = argv[optind++];

        /* Allow non-privileged users to get the help messages */
        if (strcmp(cmd, "help") == 0) {
                rval = usage_gen();
                return (rval);
        }

        if (getuid() != 0) {
                (void) fprintf(stderr, MSG_NOT_ROOT, whoami);
                exit(EXIT_PERM);
        }

        if (strcmp(cmd, "evc") != 0 && getzoneid() != GLOBAL_ZONEID) {
                (void) fprintf(stderr, MSG_NOT_GLOBAL, whoami);
                exit(EXIT_PERM);
        }

        if (strcmp(cmd, "add") == 0 ||
            strcmp(cmd, "remove") == 0 || strcmp(cmd, "list") == 0 ||
            strcmp(cmd, "restart") == 0) {
                rval = serve_syseventdotconf(argc, argv, cmd);
        } else {
                rval = usage_gen();
        }
        return (rval);
}


static void
enter_lock(char *root_dir)
{
        struct flock    lock;

        if (snprintf(lock_file, sizeof (lock_file), "%s%s", root_dir,
            LOCK_FILENAME) >= sizeof (lock_file)) {
                (void) fprintf(stderr, MSG_LOCK_PATH_ERR, whoami, lock_file);
                exit(EXIT_CMD_FAILED);
        }
        lock_fd = open(lock_file, O_CREAT|O_RDWR, 0644);
        if (lock_fd < 0) {
                (void) fprintf(stderr, MSG_LOCK_CREATE_ERR,
                        whoami, lock_file, strerror(errno));
                exit(EXIT_CMD_FAILED);
        }

        lock.l_type = F_WRLCK;
        lock.l_whence = SEEK_SET;
        lock.l_start = 0;
        lock.l_len = 0;

retry:
        if (fcntl(lock_fd, F_SETLKW, &lock) == -1) {
                if (errno == EAGAIN || errno == EINTR)
                        goto retry;
                (void) close(lock_fd);
                (void) fprintf(stderr, MSG_LOCK_SET_ERR,
                        whoami, lock_file, strerror(errno));
                exit(EXIT_CMD_FAILED);
        }
}


static void
exit_lock()
{
        struct flock    lock;

        lock.l_type = F_UNLCK;
        lock.l_whence = SEEK_SET;
        lock.l_start = 0;
        lock.l_len = 0;

        if (fcntl(lock_fd, F_SETLK, &lock) == -1) {
                (void) fprintf(stderr, MSG_LOCK_CLR_ERR,
                        whoami, lock_file, strerror(errno));
        }

        if (close(lock_fd) == -1) {
                (void) fprintf(stderr, MSG_LOCK_CLOSE_ERR,
                        whoami, lock_file, strerror(errno));
        }
}


static void
set_root_dir(char *dir)
{
        root_dir = sc_strdup(dir);
}


static char *usage_msg[] = {
        "\n"
        "\tsyseventadm add [-R <rootdir>] [-v vendor] [-p publisher]\n"
        "\t[-c class] [-s subclass] [-u username] path [args]\n"
        "\n"
        "\tsyseventadm remove [-R <rootdir>] [-v vendor] [-p publisher]\n"
        "\t[-c class] [-s subclass] [-u username] [path [args]]\n"
        "\n"
        "\tsyseventadm list [-R <rootdir>] [-v vendor] [-p publisher]\n"
        "\t[-c class] [-s subclass] [-u username] [path [args]]\n"
};

static int
usage()
{
        char    **msgs;
        int     i;

        msgs = usage_msg;
        for (i = 0; i < sizeof (usage_msg)/sizeof (char *); i++) {
                (void) fputs(*msgs++, stderr);
        }

        return (EXIT_USAGE);
}


static int
add_cmd(void)
{
        char    fname[MAXPATHLEN+1];
        int     need_comma = 0;
        int     noptions = 0;
        struct stat st;
        FILE    *fp;
        str_t   *line;
        int     i;

        /*
         * At least one of vendor/publisher/class must be specified.
         * Subclass is only defined within the context of class.
         * For add, path must also be specified.
         */
        if (arg_vendor)
                noptions++;
        if (arg_publisher)
                noptions++;
        if (arg_class)
                noptions++;

        if (noptions == 0 || (arg_subclass && arg_class == NULL)) {
                return (usage());
        }

        if (arg_path == NULL)
                return (usage());

        /*
         * Generate the sysevent.conf file name
         */
        (void) strcpy(fname, root_dir);
        (void) strcat(fname, SYSEVENT_CONFIG_DIR);
        (void) strcat(fname, "/");

        if (arg_vendor) {
                (void) strcat(fname, arg_vendor);
                need_comma = 1;
        }
        if (arg_publisher) {
                if (need_comma)
                        (void) strcat(fname, ",");
                (void) strcat(fname, arg_publisher);
                need_comma = 1;
        }
        if (arg_class) {
                if (need_comma)
                        (void) strcat(fname, ",");
                (void) strcat(fname, arg_class);
        }
        (void) strcat(fname, SYSEVENT_CONF_SUFFIX);

        /*
         * Prepare the line to be written to the sysevent.conf file
         */
        line = initstr(128);

        strcats(line, arg_class == NULL ? "-" : arg_class);
        strcatc(line, ' ');

        strcats(line, arg_subclass == NULL ? "-" : arg_subclass);
        strcatc(line, ' ');

        strcats(line, arg_vendor == NULL ? "-" : arg_vendor);
        strcatc(line, ' ');

        strcats(line, arg_publisher == NULL ? "-" : arg_publisher);
        strcatc(line, ' ');

        strcats(line, arg_username == NULL ? "-" : arg_username);
        strcatc(line, ' ');

        strcats(line, "- - ");
        strcats(line, arg_path);

        if (arg_nargs) {
                for (i = 0; i < arg_nargs; i++) {
                        strcatc(line, ' ');
                        strcats(line, arg_args[i]);
                }
        }

        if (stat(fname, &st) == -1) {
                if (creat(fname, 0644) == -1) {
                        (void) fprintf(stderr, MSG_CANNOT_CREATE,
                                whoami, fname, strerror(errno));
                        freestr(line);
                        return (EXIT_CMD_FAILED);
                }
        }

        fp = fopen(fname, "a");
        if (fp == NULL) {
                (void) fprintf(stderr, MSG_CANNOT_OPEN,
                        whoami, fname, strerror(errno));
                freestr(line);
                return (EXIT_CMD_FAILED);
        }

        (void) fprintf(fp, "%s\n", line->s_str);
        freestr(line);

        if (fclose(fp) == -1) {
                (void) fprintf(stderr, MSG_CLOSE_ERROR,
                        whoami, fname, strerror(errno));
                return (EXIT_CMD_FAILED);
        }

        if (chmod(fname, 0444) == -1) {
                (void) fprintf(stderr, MSG_CHMOD_ERROR,
                        whoami, fname, strerror(errno));
                return (EXIT_CMD_FAILED);
        }
        return (EXIT_OK);
}


static int
list_remove_cmd(int cmd)
{
        struct dirent   *dp;
        DIR             *dir;
        char            path[MAXPATHLEN+1];
        char            fname[MAXPATHLEN+1];
        char            *suffix;
        char            **dirlist = NULL;
        int             list_size = 0;
        int             list_alloc = 0;
        char            **p;
        int             rval;
        int             result;

        /*
         * For the remove cmd, at least one of vendor/publisher/class/username
         * path must be specified.  Subclass is only defined within the
         * context of a class.
         */
        if (cmd == CMD_REMOVE) {
                int     noptions = 0;
                if (arg_vendor)
                        noptions++;
                if (arg_publisher)
                        noptions++;
                if (arg_class)
                        noptions++;
                if (arg_username)
                        noptions++;
                if (arg_path)
                        noptions++;
                if (noptions == 0 || (arg_subclass && arg_class == NULL)) {
                        return (usage());
                }
        }

        (void) strcpy(path, root_dir);
        (void) strcat(path, SYSEVENT_CONFIG_DIR);

        if ((dir = opendir(path)) == NULL) {
                (void) fprintf(stderr, MSG_CANNOT_OPEN_DIR,
                        whoami, path, strerror(errno));
                return (EXIT_CMD_FAILED);
        }

        while ((dp = readdir(dir)) != NULL) {
                if (dp->d_name[0] == '.')
                        continue;
                if ((strlen(dp->d_name) == 0) ||
                    (strcmp(dp->d_name, "lost+found") == 0))
                        continue;
                suffix = strrchr(dp->d_name, ',');
                if (suffix && strcmp(suffix, SYSEVENT_CONF_SUFFIX) == 0) {
                        (void) strcpy(fname, path);
                        (void) strcat(fname, "/");
                        (void) strcat(fname, dp->d_name);
                        dirlist = build_strlist(dirlist,
                                &list_size, &list_alloc, fname);
                }
        }

        if (closedir(dir) == -1) {
                (void) fprintf(stderr, MSG_CLOSE_DIR_ERROR,
                        whoami, path, strerror(errno));
                return (EXIT_CMD_FAILED);
        }

        rval = EXIT_NO_MATCH;
        if (dirlist) {
                for (p = dirlist; *p != NULL; p++) {
                        switch (cmd) {
                        case CMD_LIST:
                                result = list_file(*p);
                                break;
                        case CMD_REMOVE:
                                result = remove_file(*p);
                                break;
                        }
                        if (rval == EXIT_NO_MATCH &&
                            result != EXIT_NO_MATCH)
                                rval = result;
                }
        }
        return (rval);
}


static int
list_file(char *fname)
{
        FILE            *fp;
        str_t           *line;
        serecord_t      *sep;
        int             rval = EXIT_NO_MATCH;

        fp = fopen(fname, "r");
        if (fp == NULL) {
                (void) fprintf(stderr, MSG_CANNOT_OPEN,
                        whoami, fname, strerror(errno));
                return (EXIT_CMD_FAILED);
        }
        for (;;) {
                line = read_next_line(fp);
                if (line == NULL)
                        break;
                sep = parse_line(line);
                if (sep != NULL) {
                        if (matches_serecord(sep)) {
                                print_serecord(stdout, sep);
                                rval = EXIT_OK;
                        }
                        free_serecord(sep);
                }
                freestr(line);
        }
        (void) fclose(fp);

        return (rval);
}


static int
remove_file(char *fname)
{
        FILE            *fp;
        FILE            *tmp_fp;
        str_t           *line;
        char            *raw_line;
        serecord_t      *sep;
        char            tmp_name[MAXPATHLEN+1];
        int             is_empty = 1;

        fp = fopen(fname, "r");
        if (fp == NULL) {
                (void) fprintf(stderr, MSG_CANNOT_OPEN,
                        whoami, fname, strerror(errno));
                return (EXIT_CMD_FAILED);
        }

        if (check_for_removes(fp) == 0) {
                (void) fclose(fp);
                return (EXIT_NO_MATCH);
        }

        rewind(fp);

        (void) strcpy(tmp_name, root_dir);
        (void) strcat(tmp_name, SYSEVENT_CONFIG_DIR);
        (void) strcat(tmp_name, "/tmp.XXXXXX");
        if (mktemp(tmp_name) == NULL) {
                (void) fprintf(stderr, "unable to make tmp file name\n");
                return (EXIT_CMD_FAILED);
        }

        if (creat(tmp_name, 0644) == -1) {
                (void) fprintf(stderr, MSG_CANNOT_CREATE,
                        whoami, tmp_name, strerror(errno));
                return (EXIT_CMD_FAILED);
        }

        tmp_fp = fopen(tmp_name, "a");
        if (tmp_fp == NULL) {
                (void) fprintf(stderr, MSG_CANNOT_OPEN,
                        whoami, tmp_name, strerror(errno));
                (void) unlink(tmp_name);
                (void) fclose(fp);
                return (EXIT_CMD_FAILED);
        }

        for (;;) {
                line = read_next_line(fp);
                if (line == NULL)
                        break;
                raw_line = sc_strdup(line->s_str);
                sep = parse_line(line);
                if (sep == NULL) {
                        (void) fputs(line->s_str, tmp_fp);
                } else {
                        if (!matches_serecord(sep)) {
                                is_empty = 0;
                                (void) fprintf(tmp_fp, "%s\n", raw_line);
                        }
                        free_serecord(sep);
                }
                freestr(line);
                sc_strfree(raw_line);
        }
        (void) fclose(fp);
        if (fclose(tmp_fp) == -1) {
                (void) fprintf(stderr, MSG_CLOSE_ERROR,
                        whoami, tmp_name, strerror(errno));
        }

        if (is_empty) {
                if (unlink(tmp_name) == -1) {
                        (void) fprintf(stderr, MSG_CANNOT_UNLINK,
                                whoami, tmp_name, strerror(errno));
                        return (EXIT_CMD_FAILED);
                }
                if (unlink(fname) == -1) {
                        (void) fprintf(stderr, MSG_CANNOT_UNLINK,
                                whoami, fname, strerror(errno));
                        return (EXIT_CMD_FAILED);
                }
        } else {
                if (unlink(fname) == -1) {
                        (void) fprintf(stderr, MSG_CANNOT_UNLINK,
                                whoami, fname, strerror(errno));
                        return (EXIT_CMD_FAILED);
                }
                if (rename(tmp_name, fname) == -1) {
                        (void) fprintf(stderr, MSG_CANNOT_RENAME,
                                whoami, tmp_name, fname, strerror(errno));
                        return (EXIT_CMD_FAILED);
                }
                if (chmod(fname, 0444) == -1) {
                        (void) fprintf(stderr, MSG_CHMOD_ERROR,
                                whoami, fname, strerror(errno));
                        return (EXIT_CMD_FAILED);
                }
        }

        return (EXIT_OK);
}

static int
check_for_removes(FILE *fp)
{
        str_t           *line;
        serecord_t      *sep;

        for (;;) {
                line = read_next_line(fp);
                if (line == NULL)
                        break;
                sep = parse_line(line);
                if (sep != NULL) {
                        if (matches_serecord(sep)) {
                                free_serecord(sep);
                                freestr(line);
                                return (1);
                        }
                        free_serecord(sep);
                }
                freestr(line);
        }

        return (0);
}


static int
matches_serecord(serecord_t *sep)
{
        char    *line;
        char    *lp;
        char    *token;
        int     i;

        if (arg_vendor &&
            strcmp(arg_vendor, sep->se_vendor) != 0) {
                return (0);
        }

        if (arg_publisher &&
            strcmp(arg_publisher, sep->se_publisher) != 0) {
                return (0);
        }

        if (arg_class &&
            strcmp(arg_class, sep->se_class) != 0) {
                return (0);
        }

        if (arg_subclass &&
            strcmp(arg_subclass, sep->se_subclass) != 0) {
                return (0);
        }

        if (arg_username &&
            strcmp(arg_username, sep->se_user) != 0) {
                return (0);
        }

        if (arg_path &&
            strcmp(arg_path, sep->se_path) != 0) {
                return (0);
        }

        if (arg_nargs > 0) {
                line = sc_strdup(sep->se_args);
                lp = line;
                for (i = 0; i < arg_nargs; i++) {
                        token = next_field(&lp);
                        if (strcmp(arg_args[i], token) != 0) {
                                sc_strfree(line);
                                return (0);
                        }
                }
                sc_strfree(line);
        }

        return (1);
}

static void
print_serecord(FILE *fp, serecord_t *sep)
{
        str_t   *line;

        line = initstr(128);

        if (strcmp(sep->se_vendor, "-") != 0) {
                strcats(line, "vendor=");
                strcats(line, sep->se_vendor);
                strcats(line, " ");
        }
        if (strcmp(sep->se_publisher, "-") != 0) {
                strcats(line, "publisher=");
                strcats(line, sep->se_publisher);
                strcats(line, " ");
        }
        if (strcmp(sep->se_class, "-") != 0) {
                strcats(line, "class=");
                strcats(line, sep->se_class);
                strcats(line, " ");
                if (strcmp(sep->se_subclass, "-") != 0) {
                        strcats(line, "subclass=");
                        strcats(line, sep->se_subclass);
                        strcats(line, " ");
                }
        }
        if (strcmp(sep->se_user, "-") != 0) {
                strcats(line, "username=");
                strcats(line, sep->se_user);
                strcats(line, " ");
        }
        strcats(line, sep->se_path);
        if (sep->se_args) {
                strcats(line, " ");
                strcats(line, sep->se_args);
        }
        strcats(line, "\n");

        (void) fputs(line->s_str, fp);
        freestr(line);
}




static int
restart_cmd(void)
{
        if (system("pkill -HUP syseventd") == -1) {
                (void) fprintf(stderr, MSG_RESTART_FAILED,
                        whoami, strerror(errno));
                return (EXIT_CMD_FAILED);
        }
        return (EXIT_OK);
}


static str_t *
read_next_line(FILE *fp)
{
        char    *lp;
        str_t   *line;

        line = initstr(128);

        lp = fstrgets(line, fp);
        if (lp == NULL) {
                freestr(line);
                return (NULL);
        }

        *(lp + strlen(lp)-1) = 0;
        return (line);
}


static serecord_t *
parse_line(str_t *line)
{
        char    *lp;
        char    *vendor, *publisher;
        char    *class, *subclass;
        char    *user;
        char    *reserved1, *reserved2;
        char    *path, *args;
        serecord_t *sep;

        lp = line->s_str;
        if (*lp == 0 || *lp == '#') {
                return (NULL);
        }

        if ((class = next_field(&lp)) != NULL) {
                subclass = next_field(&lp);
                if (lp == NULL)
                        return (NULL);
                vendor = next_field(&lp);
                if (lp == NULL)
                        return (NULL);
                publisher = next_field(&lp);
                if (lp == NULL)
                        return (NULL);
                user = next_field(&lp);
                if (lp == NULL)
                        return (NULL);
                reserved1 = next_field(&lp);
                if (lp == NULL)
                        return (NULL);
                reserved2 = next_field(&lp);
                if (lp == NULL)
                        return (NULL);
                path = next_field(&lp);
                if (lp == NULL)
                        return (NULL);
                args = skip_spaces(&lp);
        }

        sep = sc_malloc(sizeof (serecord_t));

        sep->se_vendor = sc_strdup(vendor);
        sep->se_publisher = sc_strdup(publisher);
        sep->se_class = sc_strdup(class);
        sep->se_subclass = sc_strdup(subclass);
        sep->se_user = sc_strdup(user);
        sep->se_reserved1 = sc_strdup(reserved1);
        sep->se_reserved2 = sc_strdup(reserved2);
        sep->se_path = sc_strdup(path);
        sep->se_args = (args == NULL) ? NULL : sc_strdup(args);

        return (sep);
}


static void
free_serecord(serecord_t *sep)
{
        sc_strfree(sep->se_vendor);
        sc_strfree(sep->se_publisher);
        sc_strfree(sep->se_class);
        sc_strfree(sep->se_subclass);
        sc_strfree(sep->se_user);
        sc_strfree(sep->se_reserved1);
        sc_strfree(sep->se_reserved2);
        sc_strfree(sep->se_path);
        sc_strfree(sep->se_args);
        sc_free(sep, sizeof (serecord_t));
}


/*
 * skip_spaces() - skip to next non-space character
 */
static char *
skip_spaces(char **cpp)
{
        char *cp = *cpp;

        while (*cp == ' ' || *cp == '\t')
                cp++;
        if (*cp == 0) {
                *cpp = 0;
                return (NULL);
        }
        return (cp);
}


/*
 * Get next white-space separated field.
 * next_field() will not check any characters on next line.
 * Each entry is composed of a single line.
 */
static char *
next_field(char **cpp)
{
        char *cp = *cpp;
        char *start;

        while (*cp == ' ' || *cp == '\t')
                cp++;
        if (*cp == 0) {
                *cpp = 0;
                return (NULL);
        }
        start = cp;
        while (*cp && *cp != ' ' && *cp != '\t')
                cp++;
        if (*cp != 0)
                *cp++ = 0;
        *cpp = cp;
        return (start);
}



/*
 * The following functions are simple wrappers/equivalents
 * for malloc, realloc, free, strdup and a special free
 * for strdup.
 */

static void *
sc_malloc(size_t n)
{
        void *p;

        p = malloc(n);
        if (p == NULL) {
                no_mem_err();
        }
        return (p);
}

/*ARGSUSED*/
static void *
sc_realloc(void *p, size_t current, size_t n)
{
        p = realloc(p, n);
        if (p == NULL) {
                no_mem_err();
        }
        return (p);
}


/*ARGSUSED*/
static void
sc_free(void *p, size_t n)
{
        free(p);
}


static char *
sc_strdup(char *cp)
{
        char *new;

        new = malloc((unsigned)(strlen(cp) + 1));
        if (new == NULL) {
                no_mem_err();
        }
        (void) strcpy(new, cp);
        return (new);
}


static void
sc_strfree(char *s)
{
        if (s)
                free(s);
}


/*
 * The following functions provide some simple dynamic string
 * capability.  This module has no hard-coded maximum string
 * lengths and should be able to parse and generate arbitrarily
 * long strings, macro expansion and command lines.
 *
 * Each string must be explicitly allocated and freed.
 */

/*
 * Allocate a dynamic string, with a hint to indicate how
 * much memory to dynamically add to the string as it grows
 * beyond its existing bounds, so as to avoid excessive
 * reallocs as a string grows.
 */
static str_t *
initstr(int hint)
{
        str_t   *str;

        str = sc_malloc(sizeof (str_t));
        str->s_str = NULL;
        str->s_len = 0;
        str->s_alloc = 0;
        str->s_hint = hint;
        return (str);
}


/*
 * Free a dynamically-allocated string
 */
static void
freestr(str_t *str)
{
        if (str->s_str) {
                sc_free(str->s_str, str->s_alloc);
        }
        sc_free(str, sizeof (str_t));
}


/*
 * Reset a dynamically-allocated string, allows reuse
 * rather than freeing the old and allocating a new one.
 */
static void
resetstr(str_t *str)
{
        str->s_len = 0;
}


/*
 * Concatenate a (simple) string onto a dynamically-allocated string
 */
static void
strcats(str_t *str, char *s)
{
        char    *new_str;
        int     len = str->s_len + strlen(s) + 1;

        if (str->s_alloc < len) {
                new_str = (str->s_str == NULL) ? sc_malloc(len+str->s_hint) :
                        sc_realloc(str->s_str, str->s_alloc, len+str->s_hint);
                str->s_str = new_str;
                str->s_alloc = len + str->s_hint;
        }
        (void) strcpy(str->s_str + str->s_len, s);
        str->s_len = len - 1;
}


/*
 * Concatenate a character onto a dynamically-allocated string
 */
static void
strcatc(str_t *str, int c)
{
        char    *new_str;
        int     len = str->s_len + 2;

        if (str->s_alloc < len) {
                new_str = (str->s_str == NULL) ? sc_malloc(len+str->s_hint) :
                        sc_realloc(str->s_str, str->s_alloc, len+str->s_hint);
                str->s_str = new_str;
                str->s_alloc = len + str->s_hint;
        }
        *(str->s_str + str->s_len) = (char)c;
        *(str->s_str + str->s_len + 1) = 0;
        str->s_len++;
}

/*
 * fgets() equivalent using a dynamically-allocated string
 */
static char *
fstrgets(str_t *line, FILE *fp)
{
        int     c;

        resetstr(line);
        while ((c = fgetc(fp)) != EOF) {
                strcatc(line, c);
                if (c == '\n')
                        break;
        }
        if (line->s_len == 0)
                return (NULL);
        return (line->s_str);
}



#define INITIAL_LISTSIZE        4
#define INCR_LISTSIZE           4

static char **
build_strlist(
        char    **argvlist,
        int     *size,
        int     *alloc,
        char    *str)
{
        int     n;

        if (*size + 1 > *alloc) {
                if (*alloc == 0) {
                        *alloc = INITIAL_LISTSIZE;
                        n = sizeof (char *) * (*alloc + 1);
                        argvlist = (char **)malloc(n);
                        if (argvlist == NULL)
                                no_mem_err();
                } else {
                        *alloc += INCR_LISTSIZE;
                        n = sizeof (char *) * (*alloc + 1);
                        argvlist = (char **)realloc(argvlist, n);
                        if (argvlist == NULL)
                                no_mem_err();
                }
        }

        argvlist[*size] = strdup(str);
        *size += 1;
        argvlist[*size] = NULL;

        return (argvlist);
}

static void
no_mem_err()
{
        (void) fprintf(stderr, MSG_NO_MEM, whoami);
        exit_lock();
        exit(EXIT_NO_MEM);
        /*NOTREACHED*/
}