root/usr/src/cmd/dispadmin/dispadmin.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 (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */

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

#include        <stdio.h>
#include        <stdlib.h>
#include        <string.h>
#include        <unistd.h>
#include        <fcntl.h>
#include        <errno.h>
#include        <string.h>
#include        <limits.h>
#include        <wait.h>
#include        <zone.h>
#include        <sys/types.h>
#include        <sys/stat.h>
#include        <sys/priocntl.h>

#include        "dispadmin.h"

/*
 * This file contains the code implementing the class independent part
 * of the dispadmin command.  Most of the functionality of the dispadmin
 * command is provided by the class specific sub-commands, the code for
 * which is elsewhere.  The class independent part of the command is
 * responsible for switching out to the appropriate class specific
 * sub-command based on the user supplied class argument.
 * Code in this file should never assume any knowledge of any specific
 * scheduler class (other than the SYS class).
 */

#define BASENMSZ        16
#define BUFSZ           (PATH_MAX + 80)
#define CLASSPATH       "/usr/lib/class"
#define CONFIGPATH      "/etc/dispadmin.conf"
#define CONFIGOWNER     0       /* uid 0 (root) */
#define CONFIGGROUP     1       /* gid 1 (other) */
#define CONFIGPERM      (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) /* 0644 */
#define TOKENNAME       "DEFAULT_SCHEDULER"

extern char *basename();

static char usage[] =
"usage: dispadmin -l\n\
        dispadmin -c class [class-specific options]\n\
        dispadmin -d [class]\n";

static char basenm[BASENMSZ];
static char cmdpath[PATH_MAX];

static void print_classlist();
static void exec_cscmd(char *, char **);
static void set_scheduler(char *);
static void class_info(pcinfo_t *);
static void set_default_class();

int
main(int argc, char **argv)
{
        extern char     *optarg;
        extern int      optind, opterr;

        int             c;
        int             uflag, cflag, dflag, lflag, csoptsflag;
        char            *clname;

        (void) strncpy(cmdpath, argv[0], PATH_MAX);
        (void) strncpy(basenm, basename(argv[0]), BASENMSZ);
        cflag = dflag = lflag = uflag = csoptsflag = 0;
        opterr = 0;
        while ((c = getopt(argc, argv, "c:dlu")) != -1) {
                switch (c) {

                case 'c':
                        cflag++;
                        clname = optarg;
                        break;

                case 'd':
                        dflag++;
                        clname = argv[optind];
                        break;

                case 'l':
                        lflag++;
                        break;

                case 'u':
                        uflag++;
                        break;


                case '?':
                        /*
                         * We assume for now that any option that
                         * getopt() doesn't recognize is intended for a
                         * class specific subcommand.
                         */
                        csoptsflag++;
                        if (argv[optind] && argv[optind][0] != '-') {


                                /*
                                 * Class specific option takes an
                                 * argument which we skip over for now.
                                 */
                                optind++;
                        }
                        break;

                default:
                        break;
                }
        }

        if (lflag) {
                if (uflag || cflag || dflag || csoptsflag)
                        fatalerr(usage);

                print_classlist();
                exit(0);

        } else if (uflag) {
                if (lflag || dflag || csoptsflag)
                        fatalerr(usage);

                set_default_class();
        } else if (cflag) {
                if (lflag || dflag)
                        fatalerr(usage);

                exec_cscmd(clname, argv);

        } else if (dflag) {
                if (cflag || lflag || csoptsflag)
                        fatalerr(usage);
                set_scheduler(clname);
                exit(0);

        } else {
                fatalerr(usage);
        }
        return (1);
}


/*
 * Print the heading for the class list and execute the
 * class specific sub-command with the -l option for each
 * configured class.
 */
static void
print_classlist()
{
        id_t            cid;
        int             nclass;
        pcinfo_t        pcinfo;

        if ((nclass = priocntl(0, 0, PC_GETCLINFO, NULL)) == -1)
                fatalerr("%s: Can't get number of configured classes\n",
                    cmdpath);

        (void) printf("CONFIGURED CLASSES\n==================\n\n");
        (void) printf("SYS\t(System Class)\n");
        (void) fflush(stdout);
        for (cid = 1; cid < nclass; cid++) {
                pcinfo.pc_cid = cid;
                if (priocntl(0, 0, PC_GETCLINFO, (caddr_t)&pcinfo) == -1)
                        fatalerr("%s: Can't get class name (class ID = %d)\n",
                            cmdpath, cid);
                class_info(&pcinfo);
        }
}


/*
 * Execute the appropriate class specific sub-command for the class
 * specified by clname, passing it the arguments in subcmdargv.
 */
static void
exec_cscmd(char *clname, char **subcmdargv)
{
        pcinfo_t        pcinfo;
        char            subcmdpath[PATH_MAX];

        /*
         * Do a quick check to make sure clname is valid.
         * We could just wait and see if the exec below
         * succeeds but we wouldn't know much about the reason.
         * This way we can give the user a more meaningful error
         * message.
         */
        (void) strncpy(pcinfo.pc_clname, clname, PC_CLNMSZ);
        if (priocntl(0, 0, PC_GETCID, (caddr_t)&pcinfo) == -1)
                fatalerr("%s: Invalid or unconfigured class %s\n", cmdpath,
                    clname);

        (void) snprintf(subcmdpath, PATH_MAX, "%s/%s/%s%s", CLASSPATH,
            clname, clname, basenm);
        subcmdargv[0] = subcmdpath;

        (void) execv(subcmdpath, subcmdargv);
        fatalerr("%s: Can't execute %s sub-command\n", cmdpath, clname);
}

static void
class_info(pcinfo_t *pcinfo)
{
        int pid;
        char subcmdpath[PATH_MAX];

        (void) snprintf(subcmdpath, PATH_MAX, "%s/%s/%s%s", CLASSPATH,
            pcinfo->pc_clname, pcinfo->pc_clname, basenm);
        if ((pid = fork()) == 0) {
                (void) execl(subcmdpath, subcmdpath, "-l", (char *)0);
                fatalerr("%s\n\tCan't execute %s specific subcommand\n",
                    pcinfo->pc_clname, pcinfo->pc_clname);
        } else if (pid == (pid_t)-1) {
                (void) fprintf(stderr,
                    "%s\nCan't execute %s specific subcommand)\n",
                    pcinfo->pc_clname, pcinfo->pc_clname);
        } else {
                (void) wait(NULL);
        }
}

/*
 * Return the current default scheduling class as specified in
 * /etc/dispadmin.conf.
 */
static char *
read_default_file(FILE *fp)
{
        char buf[BUFSZ];
        int line;

        for (line = 1; fgets(buf, BUFSZ, fp) != NULL; line++) {
                char name[BUFSZ], value[BUFSZ];
                int len;

                if (buf[0] == '#' || buf[0] == '\n')
                        continue;
                /* LINTED - unbounded string specifier */
                if (sscanf(buf, " %[^=]=%s \n%n", name, value, &len) == 2 &&
                    name[0] != '\0' && value[0] != '\0' && len == strlen(buf)) {

                        if (strcmp(name, TOKENNAME) != 0)
                                fatalerr("\"%s\", line %d: invalid "
                                    "token: %s\n", CONFIGPATH, line, name);

                        (void) fclose(fp);
                        return (strdup(value));
                } else {
                        fatalerr("\"%s\", line %d: syntax error\n", CONFIGPATH,
                            line);
                        (void) fclose(fp);
                }
        }
        if (line == 1)
                fatalerr("%s: %s is empty\n", cmdpath, CONFIGPATH);
        return (NULL);
}

/*
 * Set the default scheduling class for the system.
 * Update /etc/dispadmin.conf if necessary.
 */
static void
set_scheduler(char *clname)
{
        pcinfo_t pcinfo;
        FILE *fp;
        int fd;

        if (getzoneid() != GLOBAL_ZONEID)
                fatalerr("%s: Operation not supported in non-global zones\n",
                    cmdpath);

        if (clname == NULL) {
                if ((fd = open(CONFIGPATH, O_RDONLY, CONFIGPERM)) == -1) {
                        if (errno == ENOENT)
                                fatalerr("%s: Default scheduling class "
                                    "is not set\n", cmdpath);
                        else
                                fatalerr("%s: Failed to open %s (%s)\n",
                                    cmdpath, CONFIGPATH, strerror(errno));
                }

                if ((fp = fdopen(fd, "r")) == NULL)
                        fatalerr("%s: Failed to open stream for %s (%s)\n",
                            cmdpath, CONFIGPATH, strerror(errno));
                clname = read_default_file(fp);
                (void) strncpy(pcinfo.pc_clname, clname, PC_CLNMSZ);

                if (priocntl(0, 0, PC_GETCID, (caddr_t)&pcinfo) == -1)
                        fatalerr("\"%s\", scheduling class %s is not "
                            "available\n", CONFIGPATH, clname);
                else
                        class_info(&pcinfo);
                return;
        }

        /*
         * Do a quick check to make sure clname is valid class name.
         */
        (void) strncpy(pcinfo.pc_clname, clname, PC_CLNMSZ);
        if (priocntl(0, 0, PC_GETCID, (caddr_t)&pcinfo) == -1)
                fatalerr("%s: Invalid or unconfigured class %s\n", cmdpath,
                    clname);
        if ((fd = open(CONFIGPATH, O_RDWR | O_CREAT, CONFIGPERM)) == -1)
                fatalerr("%s: Failed to open %s (%s)\n", cmdpath, CONFIGPATH,
                    strerror(errno));
        if ((fp = fdopen(fd, "w")) == NULL)
                fatalerr("%s: Failed to open stream for %s\n", CONFIGPATH);
        if (ftruncate(fd, (off_t)0) == -1)
                fatalerr("%s: Failed to truncate %s\n", cmdpath, CONFIGPATH);
        (void) fputs("#\n# /etc/dispadmin.conf\n#\n"
            "# Do NOT edit this file by hand -- use dispadmin(8) instead.\n"
            "#\n", fp);
        if ((fprintf(fp, "%s=%s\n", TOKENNAME, clname)) == -1)
                fatalerr("%s: Failed to write to %s\n", cmdpath, CONFIGPATH);
        if (fflush(fp) != 0)
                (void) fprintf(stderr,
                    "%s: warning: failed to flush config file\n",
                    cmdpath);
        if (fsync(fd) == -1)
                (void) fprintf(stderr,
                    "%s: warning: failed to sync config file to disk\n",
                    cmdpath);
        if (fchmod(fd, CONFIGPERM) == -1)
                (void) fprintf(stderr,
                    "%s: warning: failed to reset config file mode\n",
                    cmdpath);
        if (fchown(fd, CONFIGOWNER, CONFIGGROUP) == -1)
                (void) fprintf(stderr,
                    "%s: warning: failed to reset config file owner\n",
                    cmdpath);
        (void) fclose(fp);

        if (priocntl(0, 0, PC_SETDFLCL, clname) == -1)
                fatalerr("%s: failed to set default class %s in kernel: %s\n",
                    cmdpath, clname, strerror(errno));
}

static void
set_default_class()
{
        char *clname;
        FILE *fp;
        int fd;

        if ((fd = open(CONFIGPATH, O_RDONLY, CONFIGPERM)) == -1) {
                /* silently succeed, there is nothing to do */
                if (errno == ENOENT)
                        return;
                else
                        fatalerr("%s: Failed to open %s (%s)\n",
                            cmdpath, CONFIGPATH, strerror(errno));
        }

        if ((fp = fdopen(fd, "r")) == NULL)
                fatalerr("%s: Failed to open stream for %s (%s)\n",
                    cmdpath, CONFIGPATH, strerror(errno));

        if ((clname = read_default_file(fp)) != NULL) {
                if (priocntl(0, 0, PC_SETDFLCL, clname) == -1)
                        fatalerr("%s: failed to set default class %s in "
                            "kernel: %s\n", cmdpath, clname, strerror(errno));
        }
}