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

#include <stdio.h>
#include <locale.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include "addrem.h"
#include "errmsg.h"
#include "plcysubr.h"

/* function prototypes */
static void     usage();
static int      unload_drv(char *, int, int);


/*
 * try to modunload driver.
 * return -1 on failure and 0 on success
 */
static int
unload_drv(char *driver_name, int force_flag, int verbose_flag)
{
        int modid;

        get_modid(driver_name, &modid);
        if (modid != -1) {
                if (modctl(MODUNLOAD, modid) < 0) {
                        (void) fprintf(stderr, gettext(ERR_MODUN), driver_name);
                        if (force_flag == 0) { /* no force flag */
                                if (verbose_flag) {
                                        (void) fprintf(stderr,
                                            gettext(NOUPDATE), driver_name);
                                }
                                /* clean up and exit. remove lock file */
                                err_exit();
                        }
                        (void) fprintf(stderr, gettext(FORCE_UPDATE),
                            driver_name);

                        return (-1);
                }
        }

        return (0);
}


static void
usage()
{
        (void) fprintf(stderr, gettext(UPD_DRV_USAGE));
        exit(1);
}


int
main(int argc, char *argv[])
{
        int     error, opt, major;
        int     cleanup_flag = 0;
        int     update_conf = 1;        /* reload driver.conf by default */
        int     verbose_flag = 0;       /* -v option */
        int     force_flag = 0;         /* -f option */
        int     a_flag = 0;             /* -a option */
        int     d_flag = 0;             /* -d option */
        int     i_flag = 0;             /* -i option */
        int     l_flag = 0;             /* -l option */
        int     m_flag = 0;             /* -m option */
        int     n_flag = 0;             /* -n option */
        char    *perms = NULL;
        char    *aliases = NULL;
        char    *basedir = NULL;
        char    *policy = NULL;
        char    *aliases2 = NULL;
        char    *priv = NULL;
        char    *driver_name;
        int     found;
        major_t major_num;
        int     rval;
        int     config_flags;

        (void) setlocale(LC_ALL, "");
#if     !defined(TEXT_DOMAIN)   /* Should be defined by cc -D */
#define TEXT_DOMAIN "SYS_TEST"  /* Use this only if it weren't */
#endif
        (void) textdomain(TEXT_DOMAIN);

        /*  must be run by root */
        if (getuid() != 0) {
                (void) fprintf(stderr, gettext(ERR_NOT_ROOT));
                exit(1);
        }

        while ((opt = getopt(argc, argv, "m:ni:b:p:adlfuvP:")) != EOF) {
                switch (opt) {
                case 'a':
                        a_flag++;
                        break;
                case 'b':
                        update_conf = 0;        /* don't update .conf file */
                        basedir = optarg;
                        break;
                case 'd':
                        d_flag++;
                        break;
                case 'f':
                        force_flag++;
                        break;
                case 'i':
                        i_flag++;
                        aliases = optarg;
                        if (check_space_within_quote(aliases) == ERROR) {
                                (void) fprintf(stderr, gettext(ERR_NO_SPACE),
                                    aliases);
                                exit(1);
                        }
                        break;
                case 'l':       /* private option */
                        l_flag++;
                        break;
                case 'm':
                        m_flag++;
                        perms = optarg;
                        break;
                case 'n':
                        n_flag++;
                        update_conf = 0;
                        break;
                case 'p':
                        policy = optarg;
                        break;
                case 'v':
                        verbose_flag++;
                        break;
                case 'P':
                        priv = optarg;
                        break;
                case '?' :
                default:
                        usage();
                }
        }

        /*
         * check for flags and extra args
         */
        if ((argv[optind] == NULL) || (optind + 1 != argc)) {
                usage();
        }

        /*
         * - cannot be adding and removing at the same time
         * - if -a or -d is specified, it's an error if none of
         *   -i/-m/-p/-P is specified.
         */
        if ((a_flag && d_flag) ||
            ((a_flag || d_flag) &&
            !m_flag && !i_flag && priv == NULL && policy == NULL)) {
                usage();
        }

        /*
         * - with -d option or -a option either -i 'identify_name',
         *      -m 'permission',  -p 'policy' or -P 'priv' should be specified
         */
        if (m_flag || i_flag || policy != NULL || priv != NULL) {
                if (!(a_flag || d_flag))
                        usage();
        }

        driver_name = argv[optind];

        /* set up update_drv filenames */
        if ((build_filenames(basedir)) == ERROR) {
                exit(1);
        }

        /* no lock is needed for listing minor perm entry */
        if (l_flag) {
                list_entry(minor_perm, driver_name, ":");

                return (NOERR);
        }

        /* must be only running version of add_drv/update_drv/rem_drv */
        enter_lock();

        if ((check_perms_aliases(m_flag, i_flag)) == ERROR) {
                err_exit();
        }

        /* update_drv doesn't modify /etc/name_to_major file */
        if ((check_name_to_major(R_OK)) == ERROR)
                err_exit();

        if ((n_flag == 0) &&
            (basedir == NULL || (strcmp(basedir, "/") == 0)) &&
            (priv != NULL) && check_priv_entry(priv, a_flag) != 0)
                err_exit();

        if (policy != NULL && (policy = check_plcy_entry(policy, driver_name,
            d_flag ? B_TRUE : B_FALSE)) == NULL)
                err_exit();

        /*
         * ADD: -a option
         * i_flag: update /etc/driver_aliases
         * m_flag: update /etc/minor_perm
         * -p: update /etc/security/device_policy
         * -P: update /etc/security/extra_privs
         * if force_flag is specified continue w/ the next operation
         */
        if (a_flag) {
                if (m_flag) {
                        /* check if the permissions are valid */
                        if ((error = check_perm_opts(perms)) == ERROR) {
                                if (force_flag == 0) { /* no force flag */
                                        exit_unlock();
                                        return (error);
                                }
                        }

                        /*
                         * update the file, if and only if
                         * we didn't run into error earlier.
                         */
                        if ((error != ERROR) &&
                            (error = update_minor_entry(driver_name, perms))) {
                                if (force_flag == 0) { /* no force flag */
                                        exit_unlock();
                                        return (error);
                                }
                        }
                        cleanup_flag |= CLEAN_NAM_MAJ;

                        /*
                         * Notify running system of minor perm change
                         */
                        if ((n_flag == 0) &&
                            (basedir == NULL || (strcmp(basedir, "/") == 0))) {
                                rval = devfs_add_minor_perm(driver_name,
                                    log_minorperm_error);
                                if (rval) {
                                        (void) fprintf(stderr,
                                            gettext(ERR_UPDATE_PERM),
                                            driver_name);
                                }
                        }
                }

                if (priv != NULL) {
                        (void) append_to_file(driver_name, priv, extra_privs,
                            ',', ":", 0);
                        cleanup_flag |= CLEAN_DRV_PRIV;
                }

                if (policy != NULL) {
                        if ((error = update_device_policy(device_policy,
                            policy, B_TRUE)) != 0) {
                                exit_unlock();
                                return (error);
                        }
                        cleanup_flag |= CLEAN_DEV_POLICY;
                }

                if (i_flag) {
                        found = get_major_no(driver_name, name_to_major);
                        if (found == ERROR) {
                                (void) fprintf(stderr, gettext(ERR_MAX_MAJOR),
                                    name_to_major);
                                err_exit();
                        }

                        if (found == UNIQUE) {
                                (void) fprintf(stderr,
                                    gettext(ERR_NOT_INSTALLED), driver_name);
                                err_exit();
                        }

                        major_num = (major_t)found;

                        /*
                         * To ease the nuisance of using update_drv
                         * in packaging scripts, do not require that
                         * existing driver aliases be trimmed from
                         * the command line.  If an invocation asks
                         * to add an alias and it's already there,
                         * drive on.  We implement this by removing
                         * duplicates now and add the remainder.
                         */
                        error = trim_duplicate_aliases(driver_name,
                            aliases, &aliases2);
                        if (error == ERROR) {
                                exit_unlock();
                                return (error);
                        }

                        /*
                         * if the list of aliases to be added is
                         * now empty, we're done.
                         */
                        if (aliases2 == NULL)
                                goto done;

                        /*
                         * unless force_flag is specified check that
                         * path-oriented aliases we are adding exist
                         */
                        if ((force_flag == 0) && ((error =
                            aliases_paths_exist(aliases2)) == ERROR)) {
                                exit_unlock();
                                return (error);
                        }

                        /* update the file */
                        if ((error = update_driver_aliases(driver_name,
                            aliases2)) == ERROR) {
                                exit_unlock();
                                return (error);
                        }


                        /* optionally update the running system - not -b */
                        if (update_conf) {
                                /* paranoia - if we crash whilst configuring */
                                sync();
                                config_flags = (verbose_flag) ?
                                    CONFIG_DRV_VERBOSE : 0;
                                cleanup_flag |= CLEAN_DRV_ALIAS;
                                if (config_driver(driver_name, major_num,
                                    aliases2, NULL, cleanup_flag,
                                    config_flags) == ERROR) {
                                        err_exit();
                                }
                        }

                }

done:
                if (update_conf && (i_flag || policy != NULL)) {
                        /* load the driver */
                        load_driver(driver_name, verbose_flag);
                }

                exit_unlock();

                return (0);
        }


        /*
         * DELETE: -d option
         * i_flag: update /etc/driver_aliases
         * m_flag: update /etc/minor_perm
         * -p: update /etc/security/device_policy
         * -P: update /etc/security/extra_privs
         * if force_flag is specified continue w/ the next operation
         */
        if (d_flag) {
                int err = NOERR;

                if (m_flag) {
                        /*
                         * On a running system, we first need to
                         * remove devfs's idea of the minor perms.
                         * We don't have any ability to do this singly
                         * at this point.
                         */
                        if ((n_flag == 0) &&
                            (basedir == NULL || (strcmp(basedir, "/") == 0))) {
                                rval = devfs_rm_minor_perm(driver_name,
                                    log_minorperm_error);
                                if (rval) {
                                        (void) fprintf(stderr,
                                            gettext(ERR_UPDATE_PERM),
                                            driver_name);
                                }
                        }

                        if ((error = delete_entry(minor_perm,
                            driver_name, ":", perms)) != NOERR) {
                                (void) fprintf(stderr, gettext(ERR_NO_ENTRY),
                                    driver_name, minor_perm);
                                err = error;
                        }
                        /*
                         * Notify running system of new minor perm state
                         */
                        if ((n_flag == 0) &&
                            (basedir == NULL || (strcmp(basedir, "/") == 0))) {
                                rval = devfs_add_minor_perm(driver_name,
                                    log_minorperm_error);
                                if (rval) {
                                        (void) fprintf(stderr,
                                            gettext(ERR_UPDATE_PERM),
                                            driver_name);
                                }
                        }
                }

                if (i_flag) {
                        found = get_major_no(driver_name, name_to_major);
                        if (found == ERROR) {
                                (void) fprintf(stderr, gettext(ERR_MAX_MAJOR),
                                    name_to_major);
                                err_exit();
                        }

                        if (found == UNIQUE) {
                                (void) fprintf(stderr,
                                    gettext(ERR_NOT_INSTALLED), driver_name);
                                err_exit();
                        }

                        major_num = (major_t)found;

                        /*
                         * verify that the aliases to be deleted exist
                         * before removal.  With -f, failing to
                         * remove an alias is not an error so we
                         * can continue on to update the kernel.
                         */
                        error = NOERR;
                        rval = aliases_exist(aliases);
                        if (rval == ERROR && (force_flag == 0)) {
                                (void) fprintf(stderr,
                                    gettext(ERR_ALIAS_NOT_BOUND),
                                    driver_name);
                                if (err != NOERR)
                                        err = rval;
                        }
                        if (rval == NOERR)
                                error = delete_entry(driver_aliases,
                                    driver_name, ":", aliases);
                        if (error != NOERR && (force_flag == 0)) {
                                (void) fprintf(stderr, gettext(ERR_NO_ENTRY),
                                    driver_name, driver_aliases);
                                if (err != NOERR)
                                        err = error;
                        }

                        /*
                         * optionally update the running system - not -b.
                         * Unless -f is specified, error if one or more
                         * devices remain bound to the alias.
                         */
                        if (err == NOERR && update_conf) {
                                /* paranoia - if we crash whilst configuring */
                                sync();

                                config_flags = 0;
                                if (verbose_flag)
                                        config_flags |= CONFIG_DRV_VERBOSE;
                                if (force_flag)
                                        config_flags |= CONFIG_DRV_FORCE;
                                error = unconfig_driver(driver_name, major_num,
                                    aliases, config_flags);
                                if (error == ERROR && force_flag == 0) {
                                        (void) fprintf(stderr,
                                            gettext(ERR_DEV_IN_USE),
                                            driver_name);
                                        if (err != NOERR)
                                                err = error;
                                }
                        }
                }

                if (priv != NULL) {
                        if ((error = delete_entry(extra_privs, driver_name, ":",
                            priv)) != NOERR) {
                                (void) fprintf(stderr, gettext(ERR_NO_ENTRY),
                                    driver_name, extra_privs);
                                if (err != NOERR)
                                        err = error;
                        }
                }

                if (policy != NULL) {
                        if ((error = delete_plcy_entry(device_policy,
                            policy)) != NOERR) {
                                (void) fprintf(stderr, gettext(ERR_NO_ENTRY),
                                    driver_name, device_policy);
                                if (err != NOERR)
                                        err = error;
                        }
                }

                if (err == NOERR && update_conf) {
                        if (i_flag || m_flag) {
                                /* try to unload the driver */
                                (void) unload_drv(driver_name,
                                    force_flag, verbose_flag);
                        }
                        /* reload the policy */
                        if (policy != NULL)
                                load_driver(driver_name, verbose_flag);
                }
                exit_unlock();

                return (err);
        }

        /* driver name must exist (for update_conf stuff) */
        major = get_major_no(driver_name, name_to_major);
        if (major == ERROR) {
                err_exit();
        }

        /*
         * Update driver.conf file:
         *      First try to unload driver module. If it fails, there may
         *      be attached devices using the old driver.conf properties,
         *      so we cannot safely update driver.conf
         *
         *      The user may specify -f to force a driver.conf update.
         *      In this case, we will update driver.conf cache. All attached
         *      devices still reference old driver.conf properties, including
         *      driver global properties. Devices attached in the future will
         *      referent properties in the updated driver.conf file.
         */
        if (update_conf) {
                (void) unload_drv(driver_name, force_flag, verbose_flag);

                if ((modctl(MODUNLOADDRVCONF, major) != 0) ||
                    (modctl(MODLOADDRVCONF, major, 0) != 0)) {
                        (void) fprintf(stderr, gettext(ERR_DRVCONF),
                            driver_name);
                        err_exit();
                }

                if (verbose_flag) {
                        (void) fprintf(stderr, gettext(DRVCONF_UPDATED),
                            driver_name);
                }
                load_driver(driver_name, verbose_flag);
        }

        exit_unlock();

        return (NOERR);
}