root/usr/src/lib/libshare/autofs/libshare_autofs.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) 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * AUTOMOUNT specific functions
 */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <zone.h>
#include <errno.h>
#include <locale.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
#include "libshare.h"
#include "libshare_impl.h"
#include <pwd.h>
#include <limits.h>
#include <libscf.h>
#include <strings.h>
#include <libdlpi.h>
#include "smfcfg.h"


static int autofs_init();
static void autofs_fini();
static int autofs_validate_property(sa_handle_t, sa_property_t, sa_optionset_t);
static int autofs_set_proto_prop(sa_property_t);
static sa_protocol_properties_t autofs_get_proto_set();
static char *autofs_get_status();
static uint64_t autofs_features();

static int initautofsprotofromsmf();
static int true_false_validator(int index, char *value);
static int strlen_validator(int index, char *value);
static int range_check_validator(int index, char *value);

/*
 * ops vector that provides the protocol specific info and operations
 * for share management.
 */
struct sa_plugin_ops sa_plugin_ops = {
        SA_PLUGIN_VERSION,
        "autofs",
        autofs_init,            /* Init autofs */
        autofs_fini,            /* Fini autofs */
        NULL,                   /* Start Sharing */
        NULL,                   /* stop sharing */
        autofs_validate_property,
        NULL,                   /* valid_space */
        NULL,                   /* security_prop */
        NULL,                   /* parse optstring */
        NULL,                   /* format optstring */
        autofs_set_proto_prop,  /* Set properties */
        autofs_get_proto_set,   /* get properties */
        autofs_get_status,      /* get status */
        NULL,                   /* space_alias */
        NULL,                   /* update_legacy */
        NULL,                   /* delete_legacy */
        NULL,                   /* change notify */
        NULL,                   /* enable resource */
        NULL,                   /* disable resource */
        autofs_features,        /* features */
        NULL,                   /* transient shares */
        NULL,                   /* notify resource */
        NULL,                   /* rename resource */
        NULL,                   /* run_command */
        NULL,                   /* command_help */
        NULL                    /* delete_proto_section */
};


static sa_protocol_properties_t protoset;

#define AUTOMOUNT_VERBOSE_DEFAULT       0
#define AUTOMOUNTD_VERBOSE_DEFAULT      0
#define AUTOMOUNT_NOBROWSE_DEFAULT      0
#define AUTOMOUNT_TIMEOUT_DEFAULT       600
#define AUTOMOUNT_TRACE_DEFAULT         0
/*
 * Protocol Management functions
 */
struct proto_option_defs {
        char *tag;
        char *name;     /* display name -- remove protocol identifier */
        int index;
        scf_type_t type;
        union {
            int intval;
            char *string;
        } defvalue;
        int32_t minval;
        int32_t maxval;
        int (*check)(int, char *);
} proto_options[] = {
#define PROTO_OPT_AUTOMOUNT_TIMEOUT     0
        { "timeout",
            "timeout",  PROTO_OPT_AUTOMOUNT_TIMEOUT,
            SCF_TYPE_INTEGER, AUTOMOUNT_TIMEOUT_DEFAULT,
            1, INT32_MAX, range_check_validator},
#define PROTO_OPT_AUTOMOUNT_VERBOSE     1
        { "automount_verbose",
            "automount_verbose", PROTO_OPT_AUTOMOUNT_VERBOSE,
            SCF_TYPE_BOOLEAN, AUTOMOUNT_VERBOSE_DEFAULT, 0, 1,
            true_false_validator},
#define PROTO_OPT_AUTOMOUNTD_VERBOSE    2
        { "automountd_verbose",
            "automountd_verbose", PROTO_OPT_AUTOMOUNTD_VERBOSE,
            SCF_TYPE_BOOLEAN, AUTOMOUNTD_VERBOSE_DEFAULT, 0, 1,
            true_false_validator},
#define PROTO_OPT_AUTOMOUNTD_NOBROWSE   3
        { "nobrowse",
            "nobrowse", PROTO_OPT_AUTOMOUNTD_NOBROWSE, SCF_TYPE_BOOLEAN,
            AUTOMOUNT_NOBROWSE_DEFAULT, 0, 1, true_false_validator},
#define PROTO_OPT_AUTOMOUNTD_TRACE      4
        { "trace",
            "trace", PROTO_OPT_AUTOMOUNTD_TRACE,
            SCF_TYPE_INTEGER, AUTOMOUNT_TRACE_DEFAULT,
            0, 20, range_check_validator},
#define PROTO_OPT_AUTOMOUNTD_ENV        5
        { "environment",
            "environment", PROTO_OPT_AUTOMOUNTD_ENV, SCF_TYPE_ASTRING,
            0, 0, 1024, strlen_validator},
        {NULL, NULL, 0, 0, 0, 0, 0, NULL}
};

#define AUTOFS_PROP_MAX (sizeof (proto_options) / sizeof (proto_options[0]))

static void
add_defaults()
{
        int i;
        char number[MAXDIGITS];

        for (i = 0; proto_options[i].tag != NULL; i++) {
                sa_property_t prop;
                prop = sa_get_protocol_property(protoset,
                    proto_options[i].name);
                if (prop == NULL) {
                        /* add the default value */
                        switch (proto_options[i].type) {
                        case SCF_TYPE_INTEGER:
                                (void) snprintf(number, sizeof (number), "%d",
                                    proto_options[i].defvalue.intval);
                                prop = sa_create_property(proto_options[i].name,
                                    number);
                                break;

                        case SCF_TYPE_BOOLEAN:
                                prop = sa_create_property(proto_options[i].name,
                                    proto_options[i].defvalue.intval ?
                                    "true" : "false");
                                break;

                        default:
                                /* treat as strings of zero length */
                                prop = sa_create_property(proto_options[i].name,
                                    "");
                                break;
                        }
                        if (prop != NULL)
                                (void) sa_add_protocol_property(protoset, prop);
                }
        }
}

static int
autofs_init()
{
        int ret = SA_OK;

        if (sa_plugin_ops.sa_init != autofs_init) {
                (void) printf(dgettext(TEXT_DOMAIN,
                    "AUTOFS plugin not installed properly\n"));
                return (SA_CONFIG_ERR);
        }

        ret = initautofsprotofromsmf();
        if (ret != SA_OK) {
                (void) printf(dgettext(TEXT_DOMAIN,
                    "AUTOFS plugin problem with SMF properties: %s\n"),
                    sa_errorstr(ret));
                ret = SA_OK;
        }
        add_defaults();
        return (ret);
}

static void
free_protoprops()
{
        if (protoset != NULL) {
                xmlFreeNode(protoset);
                protoset = NULL;
        }
}

static void
autofs_fini()
{
        free_protoprops();
}

static int
findprotoopt(char *propname)
{
        int i;

        for (i = 0; proto_options[i].tag != NULL; i++)
                if (strcmp(proto_options[i].name, propname) == 0)
                        return (i);
        return (-1);
}

static int
autofs_validate_property(sa_handle_t handle, sa_property_t property,
    sa_optionset_t parent)
{
        int ret = SA_OK;
        char *propname;
        int optionindex;
        char *value;

#ifdef lint
        handle = handle;
        parent = parent;
#endif
        propname = sa_get_property(property, "type");
        if (propname == NULL)
                return (SA_NO_SUCH_PROP);

        if ((optionindex = findprotoopt(propname)) < 0)
                ret = SA_NO_SUCH_PROP;

        if (ret != SA_OK) {
                if (propname != NULL)
                        sa_free_attr_string(propname);
                return (ret);
        }

        value = sa_get_property_attr(property, "value");
        if (value != NULL) {
                /*
                 * If any property is added to AUTOFS, which is a different
                 * type than the below list, a case needs to be added for that
                 * to check the values. For now AUTOFS type are just integers,
                 * string and boolean properties. Just taking care of them.
                 */
                switch (proto_options[optionindex].type) {
                case SCF_TYPE_INTEGER:
                case SCF_TYPE_BOOLEAN:
                case SCF_TYPE_ASTRING:
                        ret = proto_options[optionindex].check(optionindex,
                            value);
                        break;
                default:
                        break;
                }
        }

        /* Free the value */
        if (value != NULL)
                sa_free_attr_string(value);
        if (propname != NULL)
                sa_free_attr_string(propname);
        return (ret);
}

/*
 * service_in_state(service, chkstate)
 *
 * Want to know if the specified service is in the desired state
 * (chkstate) or not. Return true (1) if it is and false (0) if it
 * isn't.
 */
static int
service_in_state(char *service, const char *chkstate)
{
        char *state;
        int ret = B_FALSE;

        state = smf_get_state(service);
        if (state != NULL) {
                /* got the state so get the equality for the return value */
                ret = strcmp(state, chkstate) == 0 ? B_TRUE : B_FALSE;
                free(state);
        }
        return (ret);
}

static void
restart_service(char *service)
{
        int ret = -1;

        /*
         * Only attempt to restart the service if it is
         * currently running. In the future, it may be
         * desirable to use smf_refresh_instance if the AUTOFS
         * services ever implement the refresh method.
         */
        if (service_in_state(service, SCF_STATE_STRING_ONLINE)) {
                ret = smf_restart_instance(service);
                /*
                 * There are only a few SMF errors at this point, but
                 * it is also possible that a bad value may have put
                 * the service into maintenance if there wasn't an
                 * SMF level error.
                 */
                if (ret != 0) {
                        (void) fprintf(stderr,
                            dgettext(TEXT_DOMAIN,
                            "%s failed to restart: %s\n"),
                            scf_strerror(scf_error()));
                } else {
                        /*
                         * Check whether it has gone to "maintenance"
                         * mode or not. Maintenance implies something
                         * went wrong.
                         */
                        if (service_in_state(service,
                            SCF_STATE_STRING_MAINT)) {
                                (void) fprintf(stderr,
                                    dgettext(TEXT_DOMAIN,
                                    "%s failed to restart\n"),
                                    service);
                        }
                }
        }
}

static int
is_a_number(char *number)
{
        int ret = 1;
        int hex = 0;

        if (strncmp(number, "0x", 2) == 0) {
                number += 2;
                hex = 1;
        } else if (*number == '-') {
                number++; /* skip the minus */
        }
        while (ret == 1 && *number != '\0') {
                if (hex) {
                        ret = isxdigit(*number++);
                } else {
                        ret = isdigit(*number++);
                }
        }
        return (ret);
}

/*
 * fixcaselower(str)
 *
 * convert a string to lower case (inplace).
 */

static void
fixcaselower(char *str)
{
        while (*str) {
                *str = tolower(*str);
                str++;
        }
}

/*
 * skipwhitespace(str)
 *
 * Skip leading white space. It is assumed that it is called with a
 * valid pointer.
 */
static char *
skipwhitespace(char *str)
{
        while (*str && isspace(*str))
                str++;

        return (str);
}

/*
 * extractprop()
 *
 * Extract the property and value out of the line and create the
 * property in the optionset.
 */
static int
extractprop(char *name, char *value)
{
        sa_property_t prop;
        int index;
        int ret = SA_OK;
        /*
         * Remove any leading
         * white space.
         */
        name = skipwhitespace(name);

        index = findprotoopt(name);
        if (index >= 0) {
                fixcaselower(name);
                prop = sa_create_property(proto_options[index].name, value);
                if (prop != NULL)
                        ret = sa_add_protocol_property(protoset, prop);
                else
                        ret = SA_NO_MEMORY;
        }
        return (ret);
}

static int
initautofsprotofromsmf(void)
{
        char name[PATH_MAX];
        char value[PATH_MAX];
        int ret = SA_OK, bufsz = 0, i;
        char *instance = NULL;
        scf_type_t sctype;

        protoset = sa_create_protocol_properties("autofs");
        if (protoset != NULL) {
                for (i = 0; proto_options[i].tag != NULL; i++) {
                        bzero(value, PATH_MAX);
                        (void) strncpy(name, proto_options[i].name, PATH_MAX);
                        sctype = proto_options[i].type;
                        bufsz = PATH_MAX;
                        ret = autofs_smf_get_prop(name, value,
                            instance, sctype, AUTOFS_FMRI, &bufsz);
                        if (ret == SA_OK) {
                                ret = extractprop(name, value);
                        }
                }
        } else {
                ret = SA_NO_MEMORY;
        }
        return (ret);
}

static int
range_check_validator(int index, char *value)
{
        int ret = SA_OK;
        if (!is_a_number(value)) {
                ret = SA_BAD_VALUE;
        } else {
                int val;
                errno = 0;
                val = strtoul(value, NULL, 0);
                if (errno != 0)
                        return (SA_BAD_VALUE);

                if (val < proto_options[index].minval ||
                    val > proto_options[index].maxval)
                        ret = SA_BAD_VALUE;
        }
        return (ret);
}

static int
true_false_validator(int index, char *value)
{

#ifdef lint
        index = index;
#endif
        if ((strcasecmp(value, "true") == 0) ||
            (strcasecmp(value, "on") == 0) ||
            (strcasecmp(value, "yes") == 0) ||
            (strcmp(value, "1") == 0) ||
            (strcasecmp(value, "false") == 0) ||
            (strcasecmp(value, "off") == 0) ||
            (strcasecmp(value, "no") == 0) ||
            (strcmp(value, "0") == 0)) {
                return (SA_OK);
        }
        return (SA_BAD_VALUE);
}

static int
strlen_validator(int index, char *value)
{
        int ret = SA_OK;
        if (value == NULL) {
                if (proto_options[index].minval == 0) {
                        return (ret);
                } else {
                        return (SA_BAD_VALUE);
                }
        }
        if (strlen(value) > proto_options[index].maxval ||
            strlen(value) < proto_options[index].minval)
                ret = SA_BAD_VALUE;
        return (ret);
}

static int
autofs_validate_proto_prop(int index, char *name, char *value)
{
#ifdef lint
        name = name;
#endif
        return (proto_options[index].check(index, value));
}

static int
autofs_set_proto_prop(sa_property_t prop)
{
        int ret = SA_OK;
        char *name;
        char *value, *instance = NULL;
        scf_type_t sctype;

        name = sa_get_property_attr(prop, "type");
        value = sa_get_property_attr(prop, "value");
        if (name != NULL && value != NULL) {
                int index = findprotoopt(name);
                if (index >= 0) {
                        ret = autofs_validate_proto_prop(index, name, value);
                        if (ret == SA_OK) {
                                sctype = proto_options[index].type;
                                if (sctype == SCF_TYPE_BOOLEAN) {
                                        if (value != NULL)
                                                sa_free_attr_string(value);
                                        if (string_to_boolean(value) == 0)
                                                value = strdup("0");
                                        else
                                                value = strdup("1");
                                }
                                ret = autofs_smf_set_prop(name, value,
                                    instance, sctype, AUTOFS_FMRI);
                                /*
                                 * Make an instance based FMRI.
                                 * For now its DEFAULT_AUTOFS_FMRI.
                                 */
                                if (ret == SA_OK)
                                        restart_service(AUTOFS_DEFAULT_FMRI);
                        }
                } else {
                        ret = SA_NO_SUCH_PROP;
                }
        } else {
                ret = SA_CONFIG_ERR;
        }

        if (name != NULL)
                sa_free_attr_string(name);
        if (value != NULL)
                sa_free_attr_string(value);
        return (ret);
}


static sa_protocol_properties_t
autofs_get_proto_set(void)
{
        return (protoset);
}

static uint64_t
autofs_features(void)
{
        return (0);
}

static char *
autofs_get_status(void)
{
        return (smf_get_state(AUTOFS_DEFAULT_FMRI));
}