root/usr/src/cmd/nscd/nscd_admin.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 2012 Milan Jurik. All rights reserved.
 */

#include <stdlib.h>
#include <locale.h>
#include <string.h>
#include "cache.h"
#include "nscd_door.h"
#include "nscd_log.h"
#include "nscd_admin.h"

extern nsc_ctx_t        *cache_ctx_p[];
extern char             *cache_name[];

static nscd_admin_t     admin_c = { 0 };
static nscd_admin_mod_t admin_mod = { 0 };
static mutex_t          mod_lock = DEFAULTMUTEX;

/*ARGSUSED*/
int
_nscd_door_getadmin(void *outbuf)
{
        int                     i;
        int                     data_size = NSCD_N2N_DOOR_BUF_SIZE(admin_c);
        nss_pheader_t           *phdr = (nss_pheader_t *)outbuf;
        nscd_cfg_cache_t        cfg_default = NSCD_CFG_CACHE_DEFAULTS;

        /*
         * if size of buffer is not big enough, tell the caller to
         * increase it to the size returned
         */
        if (phdr->pbufsiz < data_size)
                return (sizeof (admin_c));

        NSCD_SET_STATUS_SUCCESS(phdr);
        phdr->data_off = sizeof (nss_pheader_t);
        phdr->data_len = sizeof (admin_c);

        for (i = 0; i < CACHE_CTX_COUNT; i++) {
                if (cache_ctx_p[i] != NULL) {
                        (void) rw_rdlock(&cache_ctx_p[i]->cfg_rwlp);
                        admin_c.cache_cfg[i] = cache_ctx_p[i]->cfg;
                        (void) rw_unlock(&cache_ctx_p[i]->cfg_rwlp);

                        (void) mutex_lock(&cache_ctx_p[i]->stats_mutex);
                        admin_c.cache_stats[i] = cache_ctx_p[i]->stats;
                        (void) mutex_unlock(&cache_ctx_p[i]->stats_mutex);
                } else {
                        admin_c.cache_cfg[i] = cfg_default;
                        (void) memset(&admin_c.cache_stats[i], 0,
                            sizeof (admin_c.cache_stats[0]));
                }
        }
        (void) memcpy(((char *)outbuf) + phdr->data_off,
            &admin_c, sizeof (admin_c));

        return (0);
}

void
_nscd_client_showstats()
{
        (void) printf("nscd configuration:\n\n");
        (void) printf("%10d  server debug level\n", admin_c.debug_level);
        (void) printf("\"%s\"  is server log file\n", admin_c.logfile);

        (void) nsc_info(NULL, NULL, admin_c.cache_cfg, admin_c.cache_stats);
}

/*ARGSUSED*/
nscd_rc_t
_nscd_server_setadmin(nscd_admin_mod_t *set)
{
        nscd_rc_t               rc = NSCD_ADMIN_FAIL_TO_SET;
        nscd_cfg_handle_t       *h;
        int                     i, j;
        char                    *group = "param-group-cache";
        char                    *dbname;
        nscd_cfg_error_t        *err = NULL;
        char                    *me = "_nscd_server_setadmin";

        if (set == NULL)
                set = &admin_mod;

        /* one setadmin at a time */
        (void) mutex_lock(&mod_lock);

        _NSCD_LOG_IF(NSCD_LOG_ADMIN, NSCD_LOG_LEVEL_DEBUG) {

                _nscd_logit(me, "total_size = %d\n", set->total_size);

                _nscd_logit(me, "debug_level_set = %d, debug_level = %d\n",
                    set->debug_level_set, set->debug_level);

                _nscd_logit(me, "logfile_set = %d, logfile = %s\n",
                    set->logfile_set, *set->logfile == '\0' ?
                    "" : set->logfile);

                _nscd_logit(me, "cache_cfg_num = %d\n",
                    set->cache_cfg_num);
                _nscd_logit(me, "cache_flush_num = %d\n",
                    set->cache_flush_num);
        }

        /*
         *  global admin stuff
         */

        if (set->debug_level_set == nscd_true) {
                if (_nscd_set_debug_level(set->debug_level)
                    != NSCD_SUCCESS) {

                        _NSCD_LOG(NSCD_LOG_ADMIN, NSCD_LOG_LEVEL_ERROR)
                        (me, "unable to set debug level %d\n",
                            set->debug_level);

                        goto err_exit;
                }
                admin_c.debug_level = set->debug_level;
        }

        if (set->logfile_set == nscd_true) {
                if (_nscd_set_log_file(set->logfile) != NSCD_SUCCESS) {

                        _NSCD_LOG(NSCD_LOG_ADMIN, NSCD_LOG_LEVEL_ERROR)
                        (me, "unable to set log file %s\n", set->logfile);

                        goto err_exit;
                }
                (void) strlcpy(admin_c.logfile, set->logfile,
                    NSCD_LOGFILE_LEN);
        }

        /*
         *  For caches to be changed
         */
        if (set->cache_cfg_num > CACHE_CTX_COUNT) {

                _NSCD_LOG(NSCD_LOG_ADMIN, NSCD_LOG_LEVEL_ERROR)
                (me, "number of caches (%d) to change out of bound %s\n",
                    set->cache_cfg_num);

                goto err_exit;
        }

        for (i = 0; i < set->cache_cfg_num; i++) {

                nscd_cfg_cache_t *new_cfg;

                j = set->cache_cfg_set[i];
                new_cfg = &set->cache_cfg[i];
                dbname = cache_name[j];
                if (cache_ctx_p[j] == NULL) {
                        _NSCD_LOG(NSCD_LOG_ADMIN, NSCD_LOG_LEVEL_ERROR)
                        (me, "unable to find cache context for %s\n",
                            dbname);
                }

                rc = _nscd_cfg_get_handle(group, dbname, &h, NULL);
                if (rc != NSCD_SUCCESS) {
                        _NSCD_LOG(NSCD_LOG_ADMIN, NSCD_LOG_LEVEL_ERROR)
                        (me, "unable to get handle for < %s : %s >\n",
                            dbname, group);

                        goto err_exit;
                }

                rc = _nscd_cfg_set(h, new_cfg, &err);
                if (rc != NSCD_SUCCESS) {
                        _NSCD_LOG(NSCD_LOG_ADMIN, NSCD_LOG_LEVEL_ERROR)
                        (me, "unable to set admin data for < %s : %s >\n",
                            dbname, group);

                        _nscd_cfg_free_handle(h);

                        goto err_exit;
                }
                _nscd_cfg_free_handle(h);
        }

        /*
         *  For caches to be flushed
         */
        if (set->cache_flush_num > CACHE_CTX_COUNT) {

                _NSCD_LOG(NSCD_LOG_ADMIN, NSCD_LOG_LEVEL_ERROR)
                (me, "number of caches (%d) to flush out of bound %s\n",
                    set->cache_flush_num);

                goto err_exit;
        }

        for (i = 0; i < set->cache_flush_num; i++) {
                int j;

                j = set->cache_flush_set[i];

                if (cache_ctx_p[j] == NULL) {
                        _NSCD_LOG(NSCD_LOG_ADMIN, NSCD_LOG_LEVEL_ERROR)
                        (me, "unable to find cache context for %s\n",
                            dbname);
                }
                nsc_invalidate(cache_ctx_p[j], NULL, NULL);
        }

        rc = NSCD_SUCCESS;
        err_exit:

        (void) mutex_unlock(&mod_lock);
        return (rc);
}


/*ARGSUSED*/
void
_nscd_door_setadmin(void *buf)
{
        nscd_rc_t       rc;
        nss_pheader_t   *phdr = (nss_pheader_t *)buf;
        char            *me = "_nscd_door_setadmin";

        rc = _nscd_server_setadmin(NSCD_N2N_DOOR_DATA(nscd_admin_mod_t, buf));
        if (rc != NSCD_SUCCESS) {
                _NSCD_LOG(NSCD_LOG_ADMIN, NSCD_LOG_LEVEL_ERROR)
                (me, "SETADMIN call failed\n");

                NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0, rc);
        } else {
                NSCD_SET_STATUS_SUCCESS(phdr);
        }
}

/*
 * for a database 'dbname', add config value 'val' of option 'opt'
 * to the global admin_mod structure
 */
int
_nscd_add_admin_mod(char *dbname, char opt,
        char *val, char *msg, int msglen) {
        int                     i, j;
        nscd_cfg_cache_t        *cfg;
        nscd_cfg_group_info_t   gi = NSCD_CFG_GROUP_INFO_CACHE;
        char                    dbn[64], *cp;

        /* set initial admin_mod size; assume no cache config to set */
        if (admin_mod.total_size == 0)
                admin_mod.total_size = sizeof (admin_mod) -
                        sizeof (admin_mod.cache_cfg);

        /* global admin stuff */
        if (opt == 'l' || opt == 'd') {
                if (opt == 'l') {
                        (void) strlcpy(admin_mod.logfile,
                                val, NSCD_LOGFILE_LEN);
                        admin_mod.logfile_set = nscd_true;
                } else {
                        admin_mod.debug_level = atoi(val);
                        admin_mod.debug_level_set = nscd_true;
                }
                return (0);
        }

        /* options to be processed next requires cache name */
        (void) strlcpy(dbn, dbname, sizeof (dbn));
        if ((cp = strchr(dbn, ',')) != NULL)
                *cp = '\0';
        i = get_cache_idx(dbn);
        if (i == -1) {
                (void) snprintf(msg, msglen,
                        gettext("invalid cache name \"%s\""), dbn);
                return (-1);
        }

        /* flush cache ? */
        if (opt == 'i') {
                admin_mod.cache_flush_set[admin_mod.cache_flush_num++] = i;
                return (0);
        }

        /* options to be processed next requires a param value */
        if (val == NULL) {
                (void) snprintf(msg, msglen,
                        gettext("value missing after \"%s\""), dbn);
                return (-1);
        }

        /* try to use an existing cache_cfg in admin_mod */
        for (j = 0; j < admin_mod.cache_cfg_num; j++) {
                if (admin_mod.cache_cfg_set[j] == i)
                        break;
        }

        /* no existing one, set up another one */
        if (j == admin_mod.cache_cfg_num) {
                admin_mod.cache_cfg_set[j] = i;
                admin_mod.cache_cfg_num++;
                admin_mod.total_size += sizeof (admin_mod.cache_cfg[0]);
        }

        cfg = &admin_mod.cache_cfg[j];
        cfg->gi.num_param = gi.num_param;

        switch (opt) {

        case 'e':
                /* enable cache */

                _nscd_cfg_bitmap_set_nth(cfg->gi.bitmap, 0);
                if (strcmp(val, "yes") == 0)
                    cfg->enable = nscd_true;
                else if (strcmp(val, "no") == 0)
                    cfg->enable = nscd_false;
                else {
                        (void) snprintf(msg, msglen,
        gettext("\"yes\" or \"no\" not specified after \"%s\""), dbn);
                        return (-1);
                }
                break;

        case 'c':
                /* check files */

                _nscd_cfg_bitmap_set_nth(cfg->gi.bitmap, 3);
                if (strcmp(val, "yes") == 0)
                    cfg->check_files = nscd_true;
                else if (strcmp(val, "no") == 0)
                    cfg->check_files = nscd_false;
                else {
                        (void) snprintf(msg, msglen,
        gettext("\"yes\" or \"no\" not specified after \"%s\""), dbn);
                        return (-1);
                }
                break;

        case 'p':
                /* positive time to live */

                _nscd_cfg_bitmap_set_nth(cfg->gi.bitmap, 5);
                cfg->pos_ttl = atoi(val);
                break;

        case 'n':
                /* negative time to live */

                _nscd_cfg_bitmap_set_nth(cfg->gi.bitmap, 6);
                cfg->neg_ttl = atoi(val);
                break;

        case 'h':
                /* keep hot count */

                _nscd_cfg_bitmap_set_nth(cfg->gi.bitmap, 7);
                cfg->keephot = atoi(val);
                break;
        }

        return (0);
}

int
_nscd_client_getadmin(char opt)
{
        int             callnum;
        nss_pheader_t   phdr;

        if (opt == 'G')
                callnum = NSCD_GETPUADMIN;
        else
                callnum = NSCD_GETADMIN;

        (void) _nscd_doorcall_data(callnum, NULL, sizeof (admin_c),
            &admin_c, sizeof (admin_c), &phdr);

        if (NSCD_STATUS_IS_NOT_OK(&phdr)) {
                return (1);
        }

        return (0);
}

int
_nscd_client_setadmin()
{
        return (_nscd_doorcall_data(NSCD_SETADMIN, &admin_mod,
            sizeof (admin_mod), NULL, 0, NULL));
}