root/usr.sbin/nscd/parser.c
/*-
 * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <sys/cdefs.h>
#include <sys/time.h>

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "config.h"
#include "debug.h"
#include "log.h"
#include "parser.h"

static void enable_cache(struct configuration *,const char *, int);
static struct configuration_entry *find_create_entry(struct configuration *,
        const char *);
static int get_number(const char *, int, int);
static enum cache_policy_t get_policy(const char *);
static int get_yesno(const char *);
static int check_cachename(const char *);
static void check_files(struct configuration *, const char *, int);
static void set_keep_hot_count(struct configuration *, const char *, int);
static void set_negative_policy(struct configuration *, const char *,
        enum cache_policy_t);
static void set_negative_time_to_live(struct configuration *,
        const char *, int);
static void set_positive_policy(struct configuration *, const char *,
        enum cache_policy_t);
static void set_perform_actual_lookups(struct configuration *, const char *,
        int);
static void set_positive_time_to_live(struct configuration *,
        const char *, int);
static void set_suggested_size(struct configuration *, const char *,
        int size);
static void set_threads_num(struct configuration *, int);
static int strbreak(char *, char **, int);

static int
strbreak(char *str, char **fields, int fields_size)
{
        char    *c = str;
        int     i, num;

        TRACE_IN(strbreak);
        num = 0;
        for (i = 0;
             ((*fields =
                strsep(i < fields_size ? &c : NULL, "\n\t ")) != NULL);
             ++i)
                if ((*(*fields)) != '\0') {
                        ++fields;
                        ++num;
                }

        TRACE_OUT(strbreak);
        return (num);
}

/*
 * Tries to find the configuration entry with the specified name. If search
 * fails, the new entry with the default parameters will be created.
 */
static struct configuration_entry *
find_create_entry(struct configuration *config,
        const char *entry_name)
{
        struct configuration_entry *entry = NULL;
        int res;

        TRACE_IN(find_create_entry);
        entry = configuration_find_entry(config, entry_name);
        if (entry == NULL) {
                entry = create_def_configuration_entry(entry_name);
                assert( entry != NULL);
                res = add_configuration_entry(config, entry);
                assert(res == 0);
        }

        TRACE_OUT(find_create_entry);
        return (entry);
}

/*
 * The vast majority of the functions below corresponds to the particular
 * keywords in the configuration file.
 */
static void
enable_cache(struct configuration *config, const char *entry_name, int flag)
{
        struct configuration_entry      *entry;

        TRACE_IN(enable_cache);
        entry = find_create_entry(config, entry_name);
        entry->enabled = flag;
        TRACE_OUT(enable_cache);
}

static void
set_positive_time_to_live(struct configuration *config,
        const char *entry_name, int ttl)
{
        struct configuration_entry *entry;
        struct timeval lifetime;

        TRACE_IN(set_positive_time_to_live);
        assert(ttl >= 0);
        assert(entry_name != NULL);
        memset(&lifetime, 0, sizeof(struct timeval));
        lifetime.tv_sec = ttl;

        entry = find_create_entry(config, entry_name);
        memcpy(&entry->positive_cache_params.max_lifetime,
                &lifetime, sizeof(struct timeval));
        memcpy(&entry->mp_cache_params.max_lifetime,
                &lifetime, sizeof(struct timeval));

        TRACE_OUT(set_positive_time_to_live);
}

static void
set_negative_time_to_live(struct configuration *config,
        const char *entry_name, int nttl)
{
        struct configuration_entry *entry;
        struct timeval lifetime;

        TRACE_IN(set_negative_time_to_live);
        assert(nttl > 0);
        assert(entry_name != NULL);
        memset(&lifetime, 0, sizeof(struct timeval));
        lifetime.tv_sec = nttl;

        entry = find_create_entry(config, entry_name);
        assert(entry != NULL);
        memcpy(&entry->negative_cache_params.max_lifetime,
                &lifetime, sizeof(struct timeval));

        TRACE_OUT(set_negative_time_to_live);
}

static void
set_positive_confidence_threshold(struct configuration *config,
        const char *entry_name, int conf_thresh)
{
        struct configuration_entry *entry;

        TRACE_IN(set_positive_conf_thresh);
        assert(conf_thresh > 0);
        assert(entry_name != NULL);

        entry = find_create_entry(config, entry_name);
        assert(entry != NULL);
        entry->positive_cache_params.confidence_threshold = conf_thresh;

        TRACE_OUT(set_positive_conf_thresh);
}

static void
set_negative_confidence_threshold(struct configuration *config,
        const char *entry_name, int conf_thresh)
{
        struct configuration_entry *entry;

        TRACE_IN(set_negative_conf_thresh);
        assert(conf_thresh > 0);
        assert(entry_name != NULL);
        entry = find_create_entry(config, entry_name);
        assert(entry != NULL);
        entry->negative_cache_params.confidence_threshold = conf_thresh;
        TRACE_OUT(set_negative_conf_thresh);
}

/*
 * Hot count is actually the elements size limit.
 */
static void
set_keep_hot_count(struct configuration *config,
        const char *entry_name, int count)
{
        struct configuration_entry *entry;

        TRACE_IN(set_keep_hot_count);
        assert(count >= 0);
        assert(entry_name != NULL);

        entry = find_create_entry(config, entry_name);
        assert(entry != NULL);
        entry->positive_cache_params.max_elemsize = count;

        entry = find_create_entry(config, entry_name);
        assert(entry != NULL);
        entry->negative_cache_params.max_elemsize = count;

        TRACE_OUT(set_keep_hot_count);
}

static void
set_positive_policy(struct configuration *config,
        const char *entry_name, enum cache_policy_t policy)
{
        struct configuration_entry *entry;

        TRACE_IN(set_positive_policy);
        assert(entry_name != NULL);

        entry = find_create_entry(config, entry_name);
        assert(entry != NULL);
        entry->positive_cache_params.policy = policy;

        TRACE_OUT(set_positive_policy);
}

static void
set_negative_policy(struct configuration *config,
        const char *entry_name, enum cache_policy_t policy)
{
        struct configuration_entry *entry;

        TRACE_IN(set_negative_policy);
        assert(entry_name != NULL);

        entry = find_create_entry(config, entry_name);
        assert(entry != NULL);
        entry->negative_cache_params.policy = policy;

        TRACE_OUT(set_negative_policy);
}

static void
set_perform_actual_lookups(struct configuration *config,
        const char *entry_name, int flag)
{
        struct configuration_entry *entry;

        TRACE_IN(set_perform_actual_lookups);
        assert(entry_name != NULL);

        entry = find_create_entry(config, entry_name);
        assert(entry != NULL);
        entry->perform_actual_lookups = flag;

        TRACE_OUT(set_perform_actual_lookups);
}

static void
set_suggested_size(struct configuration *config,
        const char *entry_name, int size)
{
        struct configuration_entry      *entry;

        TRACE_IN(set_suggested_size);
        assert(config != NULL);
        assert(entry_name != NULL);
        assert(size > 0);

        entry = find_create_entry(config, entry_name);
        assert(entry != NULL);
        entry->positive_cache_params.cache_entries_size = size;
        entry->negative_cache_params.cache_entries_size = size;

        TRACE_OUT(set_suggested_size);
}

static void
check_files(struct configuration *config, const char *entry_name, int flag)
{

        TRACE_IN(check_files);
        assert(entry_name != NULL);
        TRACE_OUT(check_files);
}

static int
get_yesno(const char *str)
{

        if (strcmp(str, "yes") == 0)
                return (1);
        else if (strcmp(str, "no") == 0)
                return (0);
        else
                return (-1);
}

static int
get_number(const char *str, int low, int max)
{

        char *end = NULL;
        int res = 0;

        if (str[0] == '\0')
                return (-1);

        res = strtol(str, &end, 10);
        if (*end != '\0')
                return (-1);
        else
                if (((res >= low) || (low == -1)) &&
                        ((res <= max) || (max == -1)))
                        return (res);
                else
                        return (-2);
}

static enum cache_policy_t
get_policy(const char *str)
{

        if (strcmp(str, "fifo") == 0)
                return (CPT_FIFO);
        else if (strcmp(str, "lru") == 0)
                return (CPT_LRU);
        else if (strcmp(str, "lfu") == 0)
                return (CPT_LFU);

        return (-1);
}

static int
check_cachename(const char *str)
{

        assert(str != NULL);
        return ((strlen(str) > 0) ? 0 : -1);
}

static void
set_threads_num(struct configuration *config, int value)
{

        assert(config != NULL);
        config->threads_num = value;
}

/*
 * The main configuration routine. Its implementation is hugely inspired by the
 * same routine implementation in Solaris NSCD.
 */
int
parse_config_file(struct configuration *config,
        const char *fname, char const **error_str, int *error_line)
{
        FILE    *fin;
        char    buffer[255];
        char    *fields[128];
        int     field_count, line_num, value;
        int     res;
        int     invalid_value;

        TRACE_IN(parse_config_file);
        assert(config != NULL);
        assert(fname != NULL);

        fin = fopen(fname, "r");
        if (fin == NULL) {
                TRACE_OUT(parse_config_file);
                return (-1);
        }

        res = 0;
        line_num = 0;
        invalid_value = 0;
        memset(buffer, 0, sizeof(buffer));
        while ((res == 0) && (fgets(buffer, sizeof(buffer) - 1, fin) != NULL)) {
                field_count = strbreak(buffer, fields, sizeof(fields));
                ++line_num;

                if (field_count == 0)
                        continue;

                switch (fields[0][0]) {
                case '#':
                case '\0':
                        continue;
                case 'e':
                        if ((field_count == 3) &&
                        (strcmp(fields[0], "enable-cache") == 0) &&
                        (check_cachename(fields[1]) == 0) &&
                        ((value = get_yesno(fields[2])) != -1)) {
                                enable_cache(config, fields[1], value);
                                continue;
                        }
                        break;
                case 'd':
                        if ((field_count == 2) &&
                        (strcmp(fields[0], "debug-level") == 0) &&
                        ((value = get_number(fields[1], 0, 10)) != -1)) {
                                continue;
                        }
                        break;
                case 'p':
                        if ((field_count == 3) &&
                        (strcmp(fields[0], "positive-time-to-live") == 0) &&
                        (check_cachename(fields[1]) == 0) &&
                        ((value = get_number(fields[2], 0, -1)) != -1)) {
                                if (value <= 0) {
                                        invalid_value = 1;
                                        break;
                                }
                                set_positive_time_to_live(config,
                                        fields[1], value);
                                continue;
                        } else if ((field_count == 3) &&
                        (strcmp(fields[0], "positive-confidence-threshold") == 0) &&
                        ((value = get_number(fields[2], 1, -1)) != -1)) {
                                if (value <= 0) {
                                        invalid_value = 1;
                                        break;
                                }
                                set_positive_confidence_threshold(config,
                                        fields[1], value);
                                continue;
                        } else if ((field_count == 3) &&
                        (strcmp(fields[0], "positive-policy") == 0) &&
                        (check_cachename(fields[1]) == 0) &&
                        ((value = get_policy(fields[2])) != -1)) {
                                set_positive_policy(config, fields[1], value);
                                continue;
                        } else if ((field_count == 3) &&
                        (strcmp(fields[0], "perform-actual-lookups") == 0) &&
                        (check_cachename(fields[1]) == 0) &&
                        ((value = get_yesno(fields[2])) != -1)) {
                                set_perform_actual_lookups(config, fields[1],
                                        value);
                                continue;
                        }
                        break;
                case 'n':
                        if ((field_count == 3) &&
                        (strcmp(fields[0], "negative-time-to-live") == 0) &&
                        (check_cachename(fields[1]) == 0) &&
                        ((value = get_number(fields[2], 0, -1)) != -1)) {
                                if (value <= 0) {
                                        invalid_value = 1;
                                        break;
                                }
                                set_negative_time_to_live(config,
                                        fields[1], value);
                                continue;
                        } else if ((field_count == 3) &&
                        (strcmp(fields[0], "negative-confidence-threshold") == 0) &&
                        ((value = get_number(fields[2], 1, -1)) != -1)) {
                                if (value <= 0) {
                                        invalid_value = 1;
                                        break;
                                }
                                set_negative_confidence_threshold(config,
                                        fields[1], value);
                                continue;
                        } else if ((field_count == 3) &&
                        (strcmp(fields[0], "negative-policy") == 0) &&
                        (check_cachename(fields[1]) == 0) &&
                        ((value = get_policy(fields[2])) != -1)) {
                                set_negative_policy(config,
                                        fields[1], value);
                                continue;
                        }
                        break;
                case 's':
                        if ((field_count == 3) &&
                        (strcmp(fields[0], "suggested-size") == 0) &&
                        (check_cachename(fields[1]) == 0) &&
                        ((value = get_number(fields[2], 1, -1)) != -1)) {
                                if (value <= 0) {
                                        invalid_value = 1;
                                        break;
                                }
                                set_suggested_size(config, fields[1], value);
                                continue;
                        }
                        break;
                case 't':
                        if ((field_count == 2) &&
                        (strcmp(fields[0], "threads") == 0) &&
                        ((value = get_number(fields[1], 1, -1)) != -1)) {
                                set_threads_num(config, value);
                                continue;
                        }
                        break;
                case 'k':
                        if ((field_count == 3) &&
                        (strcmp(fields[0], "keep-hot-count") == 0) &&
                        (check_cachename(fields[1]) == 0) &&
                        ((value = get_number(fields[2], 0, -1)) != -1)) {
                                if (value < 0) {
                                        invalid_value = 1;
                                        break;
                                }
                                set_keep_hot_count(config,
                                        fields[1], value);
                                continue;
                        }
                        break;
                case 'c':
                        if ((field_count == 3) &&
                        (strcmp(fields[0], "check-files") == 0) &&
                        (check_cachename(fields[1]) == 0) &&
                        ((value = get_yesno(fields[2])) != -1)) {
                                check_files(config,
                                        fields[1], value);
                                continue;
                        }
                        break;
                default:
                        break;
                }

                if (invalid_value != 0) {
                        LOG_ERR_2("Invalid value for parameter",
                                "error in file %s on line %d",
                                fname, line_num);
                        *error_str = "invalid value";
                } else {
                        LOG_ERR_2("config file parser", "error in file "
                                "%s on line %d", fname, line_num);
                        *error_str = "syntax error";
                }
                *error_line = line_num;
                res = -1;
        }
        fclose(fin);

        TRACE_OUT(parse_config_file);
        return (res);
}