/*
 * SPDX-FileCopyrightText: 2003-2026 Sébastien Helleu <flashcode@flashtux.org>
 * SPDX-FileCopyrightText: 2005-2006 Emmanuel Bouthenot <kolter@openics.org>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

/* Configuration files/sections/options management */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <limits.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>

#include "weechat.h"
#include "core-config-file.h"
#include "core-arraylist.h"
#include "core-config.h"
#include "core-dir.h"
#include "core-hashtable.h"
#include "core-hdata.h"
#include "core-hook.h"
#include "core-infolist.h"
#include "core-log.h"
#include "core-string.h"
#include "core-util.h"
#include "core-version.h"
#include "../gui/gui-color.h"
#include "../gui/gui-chat.h"
#include "../plugins/plugin.h"


struct t_config_file *config_files = NULL;
struct t_config_file *last_config_file = NULL;

char *config_option_type_string[CONFIG_NUM_OPTION_TYPES] =
{ N_("boolean"), N_("integer"), N_("string"), N_("color"), N_("enum") };
char *config_boolean_true[] = { "on", "yes", "y", "true", "t", "1", NULL };
char *config_boolean_false[] = { "off", "no", "n", "false", "f", "0", NULL };


void config_file_option_free_data (struct t_config_option *option);


/*
 * Check if a configuration file pointer is valid.
 *
 * Return:
 *   1: configuration file exists
 *   0: configuration file does not exist
 */

int
config_file_valid (struct t_config_file *config_file)
{
    struct t_config_file *ptr_config;

    if (!config_file)
        return 0;

    for (ptr_config = config_files; ptr_config;
         ptr_config = ptr_config->next_config)
    {
        if (ptr_config == config_file)
            return 1;
    }

    /* Configuration file not found */
    return 0;
}

/*
 * Search for a configuration file.
 */

struct t_config_file *
config_file_search (const char *name)
{
    struct t_config_file *ptr_config;
    int rc;

    if (!name)
        return NULL;

    for (ptr_config = last_config_file; ptr_config;
         ptr_config = ptr_config->prev_config)
    {
        rc = strcmp (ptr_config->name, name);
        if (rc == 0)
            return ptr_config;
        else if (rc < 0)
            break;
    }

    /* Configuration file not found */
    return NULL;
}

/*
 * Search for position of configuration file (to keep configuration files
 * sorted by name).
 */

struct t_config_file *
config_file_find_pos (const char *name)
{
    struct t_config_file *ptr_config;

    if (!name)
        return NULL;

    for (ptr_config = config_files; ptr_config;
         ptr_config = ptr_config->next_config)
    {
        if (string_strcmp (name, ptr_config->name) < 0)
            return ptr_config;
    }

    /* Position not found (we will add to the end of list) */
    return NULL;
}

/*
 * Insert a configuration file (keeping files sorted by name).
 */

void
config_file_config_insert (struct t_config_file *config_file)
{
    struct t_config_file *pos_config;

    if (!config_file)
        return;

    if (config_files)
    {
        pos_config = config_file_find_pos (config_file->name);
        if (pos_config)
        {
            /* Insert config into the list (before config found). */
            config_file->prev_config = pos_config->prev_config;
            config_file->next_config = pos_config;
            if (pos_config->prev_config)
                (pos_config->prev_config)->next_config = config_file;
            else
                config_files = config_file;
            pos_config->prev_config = config_file;
        }
        else
        {
            /* Add config to the end. */
            config_file->prev_config = last_config_file;
            config_file->next_config = NULL;
            last_config_file->next_config = config_file;
            last_config_file = config_file;
        }
    }
    else
    {
        /* First config */
        config_file->prev_config = NULL;
        config_file->next_config = NULL;
        config_files = config_file;
        last_config_file = config_file;
    }
}

/*
 * Create a new configuration file.
 *
 * Return pointer to new configuration file, NULL if error.
 */

struct t_config_file *
config_file_new (struct t_weechat_plugin *plugin, const char *name,
                 int (*callback_reload)(const void *pointer,
                                        void *data,
                                        struct t_config_file *config_file),
                 const void *callback_reload_pointer,
                 void *callback_reload_data)
{
    struct t_config_file *new_config_file;
    const char *ptr_name;
    int priority;

    string_get_priority_and_name (name, &priority, &ptr_name,
                                  CONFIG_PRIORITY_DEFAULT);

    if (!ptr_name || !ptr_name[0])
        return NULL;

    /* Two configuration files cannot have same name. */
    if (config_file_search (ptr_name))
        return NULL;

    new_config_file = malloc (sizeof (*new_config_file));
    if (new_config_file)
    {
        new_config_file->plugin = plugin;
        new_config_file->priority = priority;
        new_config_file->name = strdup (ptr_name);
        if (!new_config_file->name)
        {
            free (new_config_file);
            return NULL;
        }
        if (string_asprintf (&new_config_file->filename, "%s.conf", ptr_name) < 0)
        {
            free (new_config_file->name);
            free (new_config_file);
            return NULL;
        }
        new_config_file->file = NULL;
        new_config_file->version = 1;
        new_config_file->callback_update = NULL;
        new_config_file->callback_update_pointer = NULL;
        new_config_file->callback_update_data = NULL;
        new_config_file->callback_reload = callback_reload;
        new_config_file->callback_reload_pointer = callback_reload_pointer;
        new_config_file->callback_reload_data = callback_reload_data;
        new_config_file->sections = NULL;
        new_config_file->last_section = NULL;

        config_file_config_insert (new_config_file);
    }

    return new_config_file;
}

/*
 * Set configuration file version and a callback to update config
 * sections/options on-the-fly when the config is read.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
config_file_set_version (struct t_config_file *config_file,
                         int version,
                         struct t_hashtable *(*callback_update)(const void *pointer,
                                                                void *data,
                                                                struct t_config_file *config_file,
                                                                int version_read,
                                                                struct t_hashtable *data_read),
                         const void *callback_update_pointer,
                         void *callback_update_data)
{
    if (version < 1)
        return 0;

    config_file->version = version;

    config_file->callback_update = callback_update;
    config_file->callback_update_pointer = callback_update_pointer;
    config_file->callback_update_data = callback_update_data;

    return 1;
}

/*
 * Compare two configuration files to sort them by priority (highest priority
 * at beginning of list).
 *
 * Return:
 *   -1: config1 has higher priority than config2
 *    1: config1 has same or lower priority than config2
 */

int
config_file_arraylist_cmp_config_cb (void *data,
                                     struct t_arraylist *arraylist,
                                     void *pointer1, void *pointer2)
{
    struct t_config_file *ptr_config1, *ptr_config2;

    /* Make C compiler happy. */
    (void) data;
    (void) arraylist;

    ptr_config1 = (struct t_config_file *)pointer1;
    ptr_config2 = (struct t_config_file *)pointer2;

    return (ptr_config1->priority > ptr_config2->priority) ? -1 : 1;
}

/*
 * Return an arraylist with pointers to configuration files, sorted by
 * priority (from highest to lowest).
 */

struct t_arraylist *
config_file_get_configs_by_priority (void)
{
    struct t_arraylist *list;
    struct t_config_file *ptr_config;

    /*
     * Build a list of pointers to configs sorted by priority,
     * so that configs with high priority are reloaded first.
     */
    list = arraylist_new (
        32, 1, 1,
        &config_file_arraylist_cmp_config_cb, NULL,
        NULL, NULL);
    if (!list)
        return NULL;

    for (ptr_config = config_files; ptr_config;
         ptr_config = ptr_config->next_config)
    {
        arraylist_add (list, ptr_config);
    }

    return list;
}

/*
 * Search for position of section in configuration file (to keep sections
 * sorted by name).
 */

struct t_config_section *
config_file_section_find_pos (struct t_config_file *config_file,
                              const char *name)
{
    struct t_config_section *ptr_section;

    if (!config_file || !name)
        return NULL;

    for (ptr_section = config_file->sections; ptr_section;
         ptr_section = ptr_section->next_section)
    {
        if (string_strcmp (name, ptr_section->name) < 0)
            return ptr_section;
    }

    /* Position not found (we will add to the end of list) */
    return NULL;
}

/*
 * Create a new section in a configuration file.
 *
 * Return pointer to new section, NULL if error.
 */

struct t_config_section *
config_file_new_section (struct t_config_file *config_file, const char *name,
                         int user_can_add_options, int user_can_delete_options,
                         int (*callback_read)(const void *pointer,
                                              void *data,
                                              struct t_config_file *config_file,
                                              struct t_config_section *section,
                                              const char *option_name,
                                              const char *value),
                         const void *callback_read_pointer,
                         void *callback_read_data,
                         int (*callback_write)(const void *pointer,
                                               void *data,
                                               struct t_config_file *config_file,
                                               const char *section_name),
                         const void *callback_write_pointer,
                         void *callback_write_data,
                         int (*callback_write_default)(const void *pointer,
                                                       void *data,
                                                       struct t_config_file *config_file,
                                                       const char *section_name),
                         const void *callback_write_default_pointer,
                         void *callback_write_default_data,
                         int (*callback_create_option)(const void *pointer,
                                                       void *data,
                                                       struct t_config_file *config_file,
                                                       struct t_config_section *section,
                                                       const char *option_name,
                                                       const char *value),
                         const void *callback_create_option_pointer,
                         void *callback_create_option_data,
                         int (*callback_delete_option)(const void *pointer,
                                                       void *data,
                                                       struct t_config_file *config_file,
                                                       struct t_config_section *section,
                                                       struct t_config_option *option),
                         const void *callback_delete_option_pointer,
                         void *callback_delete_option_data)
{
    struct t_config_section *new_section;

    if (!config_file || !name)
        return NULL;

    if (config_file_search_section (config_file, name))
        return NULL;

    new_section = malloc (sizeof (*new_section));
    if (new_section)
    {
        new_section->config_file = config_file;
        new_section->name = strdup (name);
        if (!new_section->name)
        {
            free (new_section);
            return NULL;
        }
        new_section->user_can_add_options = user_can_add_options;
        new_section->user_can_delete_options = user_can_delete_options;
        new_section->callback_read = callback_read;
        new_section->callback_read_pointer = callback_read_pointer;
        new_section->callback_read_data = callback_read_data;
        new_section->callback_write = callback_write;
        new_section->callback_write_pointer = callback_write_pointer;
        new_section->callback_write_data = callback_write_data;
        new_section->callback_write_default = callback_write_default;
        new_section->callback_write_default_pointer = callback_write_default_pointer;
        new_section->callback_write_default_data = callback_write_default_data;
        new_section->callback_create_option = callback_create_option;
        new_section->callback_create_option_pointer = callback_create_option_pointer;
        new_section->callback_create_option_data = callback_create_option_data;
        new_section->callback_delete_option = callback_delete_option;
        new_section->callback_delete_option_pointer = callback_delete_option_pointer;
        new_section->callback_delete_option_data = callback_delete_option_data;
        new_section->options = NULL;
        new_section->last_option = NULL;

        new_section->prev_section = config_file->last_section;
        new_section->next_section = NULL;
        if (config_file->last_section)
            config_file->last_section->next_section = new_section;
        else
            config_file->sections = new_section;
        config_file->last_section = new_section;
    }

    return new_section;
}

/*
 * Search for a section in a configuration file.
 *
 * Return pointer to section found, NULL if not found.
 */

struct t_config_section *
config_file_search_section (struct t_config_file *config_file,
                            const char *name)
{
    struct t_config_section *ptr_section;

    if (!config_file || !name)
        return NULL;

    for (ptr_section = config_file->sections; ptr_section;
         ptr_section = ptr_section->next_section)
    {
        if (strcmp (ptr_section->name, name) == 0)
            return ptr_section;
    }

    /* Section not found */
    return NULL;
}

/*
 * Build full name for an option, using format: "file.section.option".
 *
 * Note: result must be freed after use.
 */

char *
config_file_option_full_name (struct t_config_option *option)
{
    char *option_full_name;

    if (!option)
        return NULL;

    string_asprintf (&option_full_name,
                     "%s.%s.%s",
                     option->config_file->name,
                     option->section->name,
                     option->name);

    return option_full_name;
}

/*
 * Execute hook_config for modified option.
 */

void
config_file_hook_config_exec (struct t_config_option *option)
{
    char *option_full_name, str_value[256];

    if (!option || !option->config_file || !option->section)
        return;

    option_full_name = config_file_option_full_name (option);
    if (!option_full_name)
        return;

    if (option->value)
    {
        switch (option->type)
        {
            case CONFIG_OPTION_TYPE_BOOLEAN:
                hook_config_exec (option_full_name,
                                  (CONFIG_BOOLEAN(option) == CONFIG_BOOLEAN_TRUE) ?
                                  "on" : "off");
                break;
            case CONFIG_OPTION_TYPE_INTEGER:
                snprintf (str_value, sizeof (str_value),
                          "%d", CONFIG_INTEGER(option));
                hook_config_exec (option_full_name, str_value);
                break;
            case CONFIG_OPTION_TYPE_STRING:
                hook_config_exec (option_full_name, (char *)option->value);
                break;
            case CONFIG_OPTION_TYPE_COLOR:
                hook_config_exec (option_full_name,
                                  gui_color_get_name (CONFIG_COLOR(option)));
                break;
            case CONFIG_OPTION_TYPE_ENUM:
                hook_config_exec (option_full_name,
                                  option->string_values[CONFIG_ENUM(option)]);
                break;
            case CONFIG_NUM_OPTION_TYPES:
                break;
        }
    }
    else
    {
        hook_config_exec (option_full_name, NULL);
    }

    free (option_full_name);
}

/*
 * Search for position of option in section (to keep options sorted by name).
 */

struct t_config_option *
config_file_option_find_pos (struct t_config_section *section, const char *name)
{
    struct t_config_option *ptr_option;

    if (!section || !name)
        return NULL;

    for (ptr_option = section->last_option; ptr_option;
         ptr_option = ptr_option->prev_option)
    {
        if (string_strcmp (name, ptr_option->name) >= 0)
            return ptr_option->next_option;
    }

    return section->options;
}

/*
 * Insert an option in section (keeping options sorted by name).
 */

void
config_file_option_insert_in_section (struct t_config_option *option)
{
    struct t_config_option *pos_option;

    if (!option || !option->section)
        return;

    if (option->section->options)
    {
        pos_option = config_file_option_find_pos (option->section,
                                                  option->name);
        if (pos_option)
        {
            /* Insert option into the list (before option found). */
            option->prev_option = pos_option->prev_option;
            option->next_option = pos_option;
            if (pos_option->prev_option)
                (pos_option->prev_option)->next_option = option;
            else
                (option->section)->options = option;
            pos_option->prev_option = option;
        }
        else
        {
            /* Add option to end of section. */
            option->prev_option = (option->section)->last_option;
            option->next_option = NULL;
            (option->section)->last_option->next_option = option;
            (option->section)->last_option = option;
        }
    }
    else
    {
        /* First option for section */
        option->prev_option = NULL;
        option->next_option = NULL;
        (option->section)->options = option;
        (option->section)->last_option = option;
    }
}

/*
 * Allocate memory for a new option and initializes it.
 *
 * Return pointer to new option, NULL if error.
 */

struct t_config_option *
config_file_option_malloc (void)
{
    struct t_config_option *new_option;

    new_option = malloc (sizeof (*new_option));
    if (new_option)
    {
        new_option->config_file = NULL;
        new_option->section = NULL;
        new_option->name = NULL;
        new_option->parent_name = NULL;
        new_option->type = 0;
        new_option->themable = 0;
        new_option->description = NULL;
        new_option->string_values = NULL;
        new_option->min = 0;
        new_option->max = 0;
        new_option->default_value = NULL;
        new_option->value = NULL;
        new_option->null_value_allowed = 0;
        new_option->callback_check_value = NULL;
        new_option->callback_check_value_pointer = NULL;
        new_option->callback_check_value_data = NULL;
        new_option->callback_change = NULL;
        new_option->callback_change_pointer = NULL;
        new_option->callback_change_data = NULL;
        new_option->callback_delete = NULL;
        new_option->callback_delete_pointer = NULL;
        new_option->callback_delete_data = NULL;
        new_option->loaded = 0;
        new_option->prev_option = NULL;
        new_option->next_option = NULL;
    }

    return new_option;
}

/*
 * Create a new option.
 *
 * Return pointer to new option, NULL if error.
 */

struct t_config_option *
config_file_new_option (struct t_config_file *config_file,
                        struct t_config_section *section, const char *name,
                        const char *type, const char *description,
                        const char *string_values, int min, int max,
                        const char *default_value,
                        const char *value,
                        int null_value_allowed,
                        int (*callback_check_value)(const void *pointer,
                                                    void *data,
                                                    struct t_config_option *option,
                                                    const char *value),
                        const void *callback_check_value_pointer,
                        void *callback_check_value_data,
                        void (*callback_change)(const void *pointer,
                                                void *data,
                                                struct t_config_option *option),
                        const void *callback_change_pointer,
                        void *callback_change_data,
                        void (*callback_delete)(const void *pointer,
                                                void *data,
                                                struct t_config_option *option),
                        const void *callback_delete_pointer,
                        void *callback_delete_data)
{
    struct t_config_option *new_option;
    int var_type, int_value, argc, i, index_value, number, themable;
    const char *pos, *ptr_type;
    char *option_name, *parent_name, *type_name;

    new_option = NULL;
    option_name = NULL;
    parent_name = NULL;
    type_name = NULL;
    themable = 0;

    if (!name || !type)
        goto error;

    /*
     * Extract optional "|themable" suffix from type
     * (e.g.: "string|themable" for a string option that contains color names).
     */
    pos = strchr (type, '|');
    if (pos && (strcmp (pos, "|themable") == 0))
    {
        type_name = string_strndup (type, pos - type);
        if (!type_name)
            goto error;
        ptr_type = type_name;
        themable = 1;
    }
    else
    {
        ptr_type = type;
    }

    pos = strstr (name, " << ");
    if (pos)
    {
        option_name = string_strndup (name, pos - name);
        parent_name = strdup (pos + 4);
    }
    else
    {
        option_name = strdup (name);
    }

    if (config_file && section
        && config_file_search_option (config_file, section, option_name))
    {
        goto error;
    }

    var_type = -1;
    for (i = 0; i < CONFIG_NUM_OPTION_TYPES; i++)
    {
        if (strcmp (ptr_type, config_option_type_string[i]) == 0)
        {
            var_type = i;
            break;
        }
    }
    if (var_type < 0)
    {
        gui_chat_printf (NULL, "%sUnknown option type \"%s\"",
                         gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                         ptr_type);
        goto error;
    }

    /* Color options are always themable. */
    if (var_type == CONFIG_OPTION_TYPE_COLOR)
        themable = 1;

    /*
     * Compatibility with versions < 4.1.0: force enum type for an integer
     * with string values.
     */
    if ((var_type == CONFIG_OPTION_TYPE_INTEGER)
        && string_values && string_values[0])
    {
        var_type = CONFIG_OPTION_TYPE_ENUM;
    }

    if ((var_type == CONFIG_OPTION_TYPE_ENUM)
        && (!string_values || !string_values[0]))
    {
        goto error;
    }

    if (!null_value_allowed)
    {
        if (default_value && !value)
            value = default_value;
        else if (!default_value && value)
            default_value = value;
        if (!default_value || !value)
            goto error;
    }

    new_option = config_file_option_malloc ();
    if (new_option)
    {
        new_option->config_file = config_file;
        new_option->section = section;
        new_option->name = strdup (option_name);
        if (!new_option->name)
            goto error;
        new_option->parent_name = (parent_name) ? strdup (parent_name) : NULL;
        new_option->type = var_type;
        new_option->themable = themable;
        if (description)
        {
            new_option->description = strdup (description);
            if (!new_option->description)
                goto error;
        }
        argc = 0;
        switch (var_type)
        {
            case CONFIG_OPTION_TYPE_BOOLEAN:
                new_option->min = CONFIG_BOOLEAN_FALSE;
                new_option->max = CONFIG_BOOLEAN_TRUE;
                if (default_value)
                {
                    int_value = config_file_string_to_boolean (default_value);
                    new_option->default_value = malloc (sizeof (int));
                    if (!new_option->default_value)
                        goto error;
                    CONFIG_BOOLEAN_DEFAULT(new_option) = int_value;
                }
                if (value)
                {
                    int_value = config_file_string_to_boolean (value);
                    new_option->value = malloc (sizeof (int));
                    if (!new_option->value)
                        goto error;
                    CONFIG_BOOLEAN(new_option) = int_value;
                }
                break;
            case CONFIG_OPTION_TYPE_INTEGER:
                new_option->min = min;
                new_option->max = max;
                if (default_value)
                {
                    if (!util_parse_int (default_value, 10, &number))
                        number = 0;
                    if (number < min)
                        number = min;
                    else if (number > max)
                        number = max;
                    new_option->default_value = malloc (sizeof (int));
                    if (!new_option->default_value)
                        goto error;
                    CONFIG_INTEGER_DEFAULT(new_option) = number;
                }
                if (value)
                {
                    if (!util_parse_int (value, 10, &number))
                        number = 0;
                    if (number < min)
                        number = min;
                    else if (number > max)
                        number = max;
                    new_option->value = malloc (sizeof (int));
                    if (!new_option->value)
                        goto error;
                    CONFIG_INTEGER(new_option) = number;
                }
                break;
            case CONFIG_OPTION_TYPE_STRING:
                new_option->min = min;
                new_option->max = max;
                if (default_value)
                {
                    new_option->default_value = strdup (default_value);
                    if (!new_option->default_value)
                        goto error;
                }
                if (value)
                {
                    new_option->value = strdup (value);
                    if (!new_option->value)
                        goto error;
                }
                break;
            case CONFIG_OPTION_TYPE_COLOR:
                new_option->min = min;
                new_option->max = gui_color_get_weechat_colors_number () - 1;
                if (default_value)
                {
                    new_option->default_value = malloc (sizeof (int));
                    if (!new_option->default_value)
                        goto error;
                    if (!gui_color_assign (new_option->default_value, default_value))
                        CONFIG_COLOR_DEFAULT(new_option) = 0;
                }
                if (value)
                {
                    new_option->value = malloc (sizeof (int));
                    if (!new_option->value)
                        goto error;
                    if (!gui_color_assign (new_option->value, value))
                        CONFIG_COLOR(new_option) = 0;
                }
                break;
            case CONFIG_OPTION_TYPE_ENUM:
                new_option->string_values = string_split (
                    string_values,
                    "|",
                    NULL,
                    WEECHAT_STRING_SPLIT_STRIP_LEFT
                    | WEECHAT_STRING_SPLIT_STRIP_RIGHT
                    | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS,
                    0,
                    &argc);
                if (!new_option->string_values)
                    goto error;
                new_option->min = 0;
                new_option->max = (argc == 0) ? 0 : argc - 1;
                if (default_value)
                {
                    index_value = 0;
                    for (i = 0; i < argc; i++)
                    {
                        if (strcmp (new_option->string_values[i],
                                    default_value) == 0)
                        {
                            index_value = i;
                            break;
                        }
                    }
                    new_option->default_value = malloc (sizeof (int));
                    if (!new_option->default_value)
                        goto error;
                    CONFIG_ENUM_DEFAULT(new_option) = index_value;
                }
                if (value)
                {
                    index_value = 0;
                    for (i = 0; i < argc; i++)
                    {
                        if (strcmp (new_option->string_values[i],
                                    value) == 0)
                        {
                            index_value = i;
                            break;
                        }
                    }
                    new_option->value = malloc (sizeof (int));
                    if (!new_option->value)
                        goto error;
                    CONFIG_ENUM(new_option) = index_value;
                }
                break;
            case CONFIG_NUM_OPTION_TYPES:
                break;
        }
        new_option->null_value_allowed = null_value_allowed;
        new_option->callback_check_value = callback_check_value;
        new_option->callback_check_value_pointer = callback_check_value_pointer;
        new_option->callback_check_value_data = callback_check_value_data;
        new_option->callback_change = callback_change;
        new_option->callback_change_pointer = callback_change_pointer;
        new_option->callback_change_data = callback_change_data;
        new_option->callback_delete = callback_delete;
        new_option->callback_delete_pointer = callback_delete_pointer;
        new_option->callback_delete_data = callback_delete_data;
        new_option->loaded = 1;

        if (section)
        {
            config_file_option_insert_in_section (new_option);
        }
        else
        {
            new_option->prev_option = NULL;
            new_option->next_option = NULL;
        }

        config_file_hook_config_exec (new_option);
    }

    goto end;

error:
    if (new_option)
    {
        config_file_option_free_data (new_option);
        free (new_option);
        new_option = NULL;
    }

end:
    free (option_name);
    free (parent_name);
    free (type_name);
    return new_option;
}

/*
 * Search for an option in a configuration file or section.
 *
 * Return pointer to option found, NULL if error.
 */

struct t_config_option *
config_file_search_option (struct t_config_file *config_file,
                           struct t_config_section *section,
                           const char *option_name)
{
    struct t_config_section *ptr_section;
    struct t_config_option *ptr_option;
    int rc;

    if (!option_name)
        return NULL;

    if (section)
    {
        for (ptr_option = section->last_option; ptr_option;
             ptr_option = ptr_option->prev_option)
        {
            rc = strcmp (ptr_option->name, option_name);
            if (rc == 0)
                return ptr_option;
            else if (rc < 0)
                break;
        }
    }
    else if (config_file)
    {
        for (ptr_section = config_file->sections; ptr_section;
             ptr_section = ptr_section->next_section)
        {
            for (ptr_option = ptr_section->last_option; ptr_option;
                 ptr_option = ptr_option->prev_option)
            {
                rc = strcmp (ptr_option->name, option_name);
                if (rc == 0)
                    return ptr_option;
                else if (rc < 0)
                    break;
            }
        }
    }

    /* Option not found */
    return NULL;
}

/*
 * Search for an option in a configuration file or section.
 *
 * Return section/option found (in section_found/option_found), NULL if not
 * found.
 */

void
config_file_search_section_option (struct t_config_file *config_file,
                                   struct t_config_section *section,
                                   const char *option_name,
                                   struct t_config_section **section_found,
                                   struct t_config_option **option_found)
{
    struct t_config_section *ptr_section;
    struct t_config_option *ptr_option;
    int rc;

    *section_found = NULL;
    *option_found = NULL;

    if (!option_name)
        return;

    if (section)
    {
        for (ptr_option = section->last_option; ptr_option;
             ptr_option = ptr_option->prev_option)
        {
            rc = strcmp (ptr_option->name, option_name);
            if (rc == 0)
            {
                *section_found = section;
                *option_found = ptr_option;
                return;
            }
            else if (rc < 0)
                break;
        }
    }
    else if (config_file)
    {
        for (ptr_section = config_file->sections; ptr_section;
             ptr_section = ptr_section->next_section)
        {
            for (ptr_option = ptr_section->last_option; ptr_option;
                 ptr_option = ptr_option->prev_option)
            {
                rc = strcmp (ptr_option->name, option_name);
                if (rc == 0)
                {
                    *section_found = ptr_section;
                    *option_found = ptr_option;
                    return;
                }
                else if (rc < 0)
                    break;
            }
        }
    }
}

/*
 * Search for a file/section/option using a full name of option (format:
 * "file.section.option").
 */

void
config_file_search_with_string (const char *option_name,
                                struct t_config_file **config_file,
                                struct t_config_section **section,
                                struct t_config_option **option,
                                const char **pos_option_name)
{
    struct t_config_file *ptr_config;
    struct t_config_section *ptr_section;
    struct t_config_option *ptr_option;
    const char *pos_section, *pos_option;
    char *file_name, *section_name;

    if (config_file)
        *config_file = NULL;
    if (section)
        *section = NULL;
    if (option)
        *option = NULL;
    if (pos_option_name)
        *pos_option_name = NULL;

    if (!option_name)
        return;

    ptr_config = NULL;
    ptr_section = NULL;
    ptr_option = NULL;

    file_name = NULL;
    section_name = NULL;
    pos_option = NULL;

    pos_section = strchr (option_name, '.');
    pos_option = (pos_section) ? strchr (pos_section + 1, '.') : NULL;
    if (pos_section && pos_option)
    {
        file_name = string_strndup (option_name, pos_section - option_name);
        section_name = string_strndup (pos_section + 1,
                                       pos_option - pos_section - 1);
        pos_option++;
    }
    if (file_name && section_name && pos_option)
    {
        if (pos_option_name)
            *pos_option_name = pos_option;
        ptr_config = config_file_search (file_name);
        if (ptr_config)
        {
            ptr_section = config_file_search_section (ptr_config,
                                                      section_name);
            if (ptr_section)
            {
                ptr_option = config_file_search_option (ptr_config,
                                                        ptr_section,
                                                        pos_option);
            }
        }
    }

    free (file_name);
    free (section_name);

    if (config_file)
        *config_file = ptr_config;
    if (section)
        *section = ptr_section;
    if (option)
        *option = ptr_option;
}

/*
 * Get pointer to parent option, NULL if the option has no parent.
 */

struct t_config_option *
config_file_get_parent_option (struct t_config_option *option)
{
    struct t_config_option *ptr_parent_option;

    if (!option || !option->parent_name)
        return NULL;

    config_file_search_with_string (
        option->parent_name,
        NULL,  /* config_file */
        NULL,  /* section */
        &ptr_parent_option,
        NULL);  /* pos_option_name */

    return ptr_parent_option;
}

/*
 * Check if a string with boolean value is valid.
 *
 * Return:
 *   1: boolean value is valid
 *   0: boolean value is NOT valid
 */

int
config_file_string_boolean_is_valid (const char *text)
{
    int i;

    if (!text)
        return 0;

    for (i = 0; config_boolean_true[i]; i++)
    {
        if (strcmp (text, config_boolean_true[i]) == 0)
            return 1;
    }

    for (i = 0; config_boolean_false[i]; i++)
    {
        if (strcmp (text, config_boolean_false[i]) == 0)
            return 1;
    }

    /* Text is not a boolean. */
    return 0;
}

/*
 * Convert string to boolean value.
 *
 * Return:
 *   1: boolean value is true
 *   0: boolean value is false
 */

int
config_file_string_to_boolean (const char *text)
{
    int i;

    if (!text)
        return CONFIG_BOOLEAN_FALSE;

    for (i = 0; config_boolean_true[i]; i++)
    {
        if (strcmp (text, config_boolean_true[i]) == 0)
            return CONFIG_BOOLEAN_TRUE;
    }

    return CONFIG_BOOLEAN_FALSE;
}

/*
 * Reset an option to its default value.
 *
 * Return:
 *   WEECHAT_CONFIG_OPTION_SET_OK_CHANGED: OK, value has been changed
 *   WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE: OK, value not changed
 *   WEECHAT_CONFIG_OPTION_SET_ERROR: error
 */

int
config_file_option_reset (struct t_config_option *option, int run_callback)
{
    int rc, old_value_was_null;

    if (!option)
        return WEECHAT_CONFIG_OPTION_SET_ERROR;

    rc = WEECHAT_CONFIG_OPTION_SET_ERROR;

    if (option->default_value)
    {
        old_value_was_null = (option->value == NULL);
        switch (option->type)
        {
            case CONFIG_OPTION_TYPE_BOOLEAN:
                if (!option->value)
                {
                    option->value = malloc (sizeof (int));
                    if (option->value)
                    {
                        CONFIG_BOOLEAN(option) = CONFIG_BOOLEAN_DEFAULT(option);
                        rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                    }
                }
                else
                {
                    if (CONFIG_BOOLEAN(option) == CONFIG_BOOLEAN_DEFAULT(option))
                        rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                    else
                    {
                        CONFIG_BOOLEAN(option) = CONFIG_BOOLEAN_DEFAULT(option);
                        rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                    }
                }
                break;
            case CONFIG_OPTION_TYPE_INTEGER:
                if (!option->value)
                {
                    option->value = malloc (sizeof (int));
                    if (option->value)
                        CONFIG_INTEGER(option) = 0;
                    else
                        break;
                }
                if (CONFIG_INTEGER(option) == CONFIG_INTEGER_DEFAULT(option))
                    rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                else
                {
                    CONFIG_INTEGER(option) = CONFIG_INTEGER_DEFAULT(option);
                    rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                }
                break;
            case CONFIG_OPTION_TYPE_STRING:
                rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                if (!option->value
                    || (strcmp ((char *)option->value,
                                (char *)option->default_value) != 0))
                    rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                if (option->value)
                {
                    free (option->value);
                    option->value = NULL;
                }
                option->value = strdup ((char *)option->default_value);
                if (!option->value)
                    rc = WEECHAT_CONFIG_OPTION_SET_ERROR;
                break;
            case CONFIG_OPTION_TYPE_COLOR:
                if (!option->value)
                {
                    option->value = malloc (sizeof (int));
                    if (option->value)
                        CONFIG_COLOR(option) = 0;
                    else
                        break;
                }
                if (CONFIG_COLOR(option) == CONFIG_COLOR_DEFAULT(option))
                    rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                else
                {
                    CONFIG_COLOR(option) = CONFIG_COLOR_DEFAULT(option);
                    rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                }
                break;
            case CONFIG_OPTION_TYPE_ENUM:
                if (!option->value)
                {
                    option->value = malloc (sizeof (int));
                    if (option->value)
                        CONFIG_ENUM(option) = 0;
                    else
                        break;
                }
                if (CONFIG_ENUM(option) == CONFIG_ENUM_DEFAULT(option))
                    rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                else
                {
                    CONFIG_ENUM(option) = CONFIG_ENUM_DEFAULT(option);
                    rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                }
                break;
            case CONFIG_NUM_OPTION_TYPES:
                break;
        }
        if (old_value_was_null && option->value)
            rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
    }
    else
    {
        if (option->null_value_allowed)
        {
            if (option->value)
            {
                free (option->value);
                option->value = NULL;
                rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
            }
            else
                rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
        }
    }

    /* Run callback and config hook(s) if value was changed. */
    if (rc == WEECHAT_CONFIG_OPTION_SET_OK_CHANGED)
    {
        if (run_callback && option->callback_change)
        {
            (void) (option->callback_change) (
                option->callback_change_pointer,
                option->callback_change_data,
                option);
        }
        config_file_hook_config_exec (option);
    }

    return rc;
}

/*
 * Set the value for an option.
 *
 * Return:
 *   WEECHAT_CONFIG_OPTION_SET_OK_CHANGED: OK, value has been changed
 *   WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE: OK, value not changed
 *   WEECHAT_CONFIG_OPTION_SET_ERROR: error
 */

int
config_file_option_set (struct t_config_option *option, const char *value,
                        int run_callback)
{
    int value_int, i, rc, new_value_ok, old_value_was_null, old_value, number;

    if (!option)
        return WEECHAT_CONFIG_OPTION_SET_ERROR;

    rc = WEECHAT_CONFIG_OPTION_SET_ERROR;

    if (option->callback_check_value)
    {
        if (!(int)(option->callback_check_value) (
                option->callback_check_value_pointer,
                option->callback_check_value_data,
                option,
                value))
        {
            return WEECHAT_CONFIG_OPTION_SET_ERROR;
        }
    }

    if (value)
    {
        old_value_was_null = (option->value == NULL);
        switch (option->type)
        {
            case CONFIG_OPTION_TYPE_BOOLEAN:
                if (!option->value)
                {
                    option->value = malloc (sizeof (int));
                    if (option->value)
                    {
                        if (strcmp (value, "toggle") == 0)
                        {
                            CONFIG_BOOLEAN(option) = CONFIG_BOOLEAN_TRUE;
                            rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                        }
                        else
                        {
                            if (config_file_string_boolean_is_valid (value))
                            {
                                value_int = config_file_string_to_boolean (value);
                                CONFIG_BOOLEAN(option) = value_int;
                                rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                            }
                            else
                            {
                                free (option->value);
                                option->value = NULL;
                            }
                        }
                    }
                }
                else
                {
                    if (strcmp (value, "toggle") == 0)
                    {
                        CONFIG_BOOLEAN(option) =
                            (CONFIG_BOOLEAN(option) == CONFIG_BOOLEAN_TRUE) ?
                            CONFIG_BOOLEAN_FALSE : CONFIG_BOOLEAN_TRUE;
                        rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                    }
                    else
                    {
                        if (config_file_string_boolean_is_valid (value))
                        {
                            value_int = config_file_string_to_boolean (value);
                            if (value_int == CONFIG_BOOLEAN(option))
                                rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                            else
                            {
                                CONFIG_BOOLEAN(option) = value_int;
                                rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                            }
                        }
                    }
                }
                break;
            case CONFIG_OPTION_TYPE_INTEGER:
                old_value = 0;
                if (!option->value)
                    option->value = malloc (sizeof (int));
                else
                    old_value = CONFIG_INTEGER(option);
                if (option->value)
                {
                    new_value_ok = 0;
                    if (strncmp (value, "++", 2) == 0)
                    {
                        if (util_parse_int (value + 2, 10, &number)
                            && ((long)old_value + (long)number <= (long)(option->max)))
                        {
                            value_int = old_value + number;
                            new_value_ok = 1;
                        }
                    }
                    else if (strncmp (value, "--", 2) == 0)
                    {
                        if (util_parse_int (value + 2, 10, &number)
                            && ((long)old_value - (long)number >= (long)(option->min)))
                        {
                            value_int = old_value - number;
                            new_value_ok = 1;
                        }
                    }
                    else
                    {
                        if (util_parse_int (value, 10, &number))
                        {
                            value_int = number;
                            if ((value_int >= option->min) && (value_int <= option->max))
                                new_value_ok = 1;
                        }
                    }
                    if (new_value_ok)
                    {
                        if (old_value_was_null || (value_int != old_value))
                        {
                            CONFIG_INTEGER(option) = value_int;
                            rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                        }
                        else
                            rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                    }
                    else
                    {
                        if (old_value_was_null)
                        {
                            free (option->value);
                            option->value = NULL;
                        }
                    }
                }
                break;
            case CONFIG_OPTION_TYPE_STRING:
                rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                if (!option->value
                    || (strcmp (CONFIG_STRING(option), value) != 0))
                    rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                if (option->value)
                {
                    free (option->value);
                    option->value = NULL;
                }
                option->value = strdup (value);
                if (!option->value)
                    rc = WEECHAT_CONFIG_OPTION_SET_ERROR;
                break;
            case CONFIG_OPTION_TYPE_COLOR:
                old_value = 0;
                if (!option->value)
                    option->value = malloc (sizeof (int));
                else
                    old_value = CONFIG_COLOR(option);
                if (option->value)
                {
                    value_int = -1;
                    new_value_ok = 0;
                    if (strncmp (value, "++", 2) == 0)
                    {
                        if (util_parse_int (value + 2, 10, &number))
                        {
                            if (gui_color_assign_by_diff (&value_int,
                                                          gui_color_get_name (old_value),
                                                          number))
                                new_value_ok = 1;
                        }
                    }
                    else if (strncmp (value, "--", 2) == 0)
                    {
                        if (util_parse_int (value + 2, 10, &number))
                        {
                            if (gui_color_assign_by_diff (&value_int,
                                                          gui_color_get_name (old_value),
                                                          -1 * number))
                                new_value_ok = 1;
                        }
                    }
                    else
                    {
                        if (gui_color_assign (&value_int, value))
                            new_value_ok = 1;
                    }
                    if (new_value_ok)
                    {
                        if (old_value_was_null || (value_int != old_value))
                        {
                            CONFIG_COLOR(option) = value_int;
                            rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                        }
                        else
                            rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                    }
                    else
                    {
                        if (old_value_was_null)
                        {
                            free (option->value);
                            option->value = NULL;
                        }
                    }
                }
                break;
            case CONFIG_OPTION_TYPE_ENUM:
                old_value = 0;
                if (!option->value)
                    option->value = malloc (sizeof (int));
                else
                    old_value = CONFIG_ENUM(option);
                if (option->value)
                {
                    value_int = -1;
                    if (strncmp (value, "++", 2) == 0)
                    {
                        if (util_parse_int (value + 2, 10, &number))
                        {
                            number = number % (option->max + 1);
                            value_int = (old_value + number) %
                                (option->max + 1);
                        }
                    }
                    else if (strncmp (value, "--", 2) == 0)
                    {
                        if (util_parse_int (value + 2, 10, &number))
                        {
                            number = number % (option->max + 1);
                            value_int = (old_value + (option->max + 1) - number) %
                                (option->max + 1);
                        }
                    }
                    else
                    {
                        for (i = 0; option->string_values[i]; i++)
                        {
                            if (strcmp (option->string_values[i], value) == 0)
                            {
                                value_int = i;
                                break;
                            }
                        }
                    }
                    if (value_int >= 0)
                    {
                        if (old_value_was_null || (value_int != old_value))
                        {
                            CONFIG_ENUM(option) = value_int;
                            rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                        }
                        else
                            rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                    }
                    else
                    {
                        if (old_value_was_null)
                        {
                            free (option->value);
                            option->value = NULL;
                        }
                    }
                }
                break;
            case CONFIG_NUM_OPTION_TYPES:
                break;
        }
        if (old_value_was_null && option->value)
            rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
    }
    else
    {
        if (option->null_value_allowed && option->value)
        {
            free (option->value);
            option->value = NULL;
            rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
        }
        else
            rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
    }

    /* Run callback and config hook(s) if value was changed. */
    if (rc == WEECHAT_CONFIG_OPTION_SET_OK_CHANGED)
    {
        if (run_callback && option->callback_change)
        {
            (void) (option->callback_change) (
                option->callback_change_pointer,
                option->callback_change_data,
                option);
        }
        config_file_hook_config_exec (option);
    }

    return rc;
}

/*
 * Toggle value of an option.
 *
 * Return:
 *   WEECHAT_CONFIG_OPTION_SET_OK_CHANGED: OK, value has been changed
 *   WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE: OK, value not changed
 *   WEECHAT_CONFIG_OPTION_SET_ERROR: error
 */

int
config_file_option_toggle (struct t_config_option *option,
                           const char **values, int num_values,
                           int run_callback)
{
    char *current_value;
    const char *ptr_new_value, *empty_string = "";
    int i, rc, index_found, value_is_null, reset_value;

    if (!option || (num_values < 0))
        return WEECHAT_CONFIG_OPTION_SET_ERROR;

    rc = WEECHAT_CONFIG_OPTION_SET_ERROR;
    ptr_new_value = NULL;
    reset_value = 0;

    value_is_null = (option->value == NULL);
    current_value = config_file_option_value_to_string (option, 0, 0, 0);

    switch (option->type)
    {
        case CONFIG_OPTION_TYPE_BOOLEAN:
            if (!values)
            {
                ptr_new_value = (option->value && CONFIG_BOOLEAN(option)) ?
                    config_boolean_false[0] : config_boolean_true[0];
            }
            break;
        case CONFIG_OPTION_TYPE_INTEGER:
            if (!values)
                goto end;
            break;
        case CONFIG_OPTION_TYPE_STRING:
            if (!values)
            {
                if (option->value && (strcmp (CONFIG_STRING(option), "") == 0))
                    ptr_new_value = CONFIG_STRING_DEFAULT(option);
                else
                    ptr_new_value = empty_string;
            }
            break;
        case CONFIG_OPTION_TYPE_COLOR:
            if (!values)
                goto end;
            break;
        case CONFIG_OPTION_TYPE_ENUM:
            if (!values)
                goto end;
            break;
        case CONFIG_NUM_OPTION_TYPES:
            /* Make C compiler happy. */
            break;
    }

    /* Search new value to use with the provided list of values. */
    if (!ptr_new_value && values)
    {
        index_found = -1;
        for (i = 0; i < num_values; i++)
        {
            if ((value_is_null && !values[i])
                || (!value_is_null && current_value && values[i]
                    && strcmp (current_value, values[i]) == 0))
            {
                index_found = i;
                break;
            }
        }
        if (index_found >= 0)
        {
            if (index_found + 1 < num_values)
            {
                ptr_new_value =  values[index_found + 1];
            }
            else
            {
                if (num_values < 2)
                    reset_value = 1;
                else
                    ptr_new_value = values[0];
            }
        }
        else
        {
            ptr_new_value = values[0];
        }
    }

    if (reset_value)
        rc = config_file_option_reset (option, run_callback);
    else
        rc = config_file_option_set (option, ptr_new_value, run_callback);

end:
    free (current_value);
    return rc;
}

/*
 * Set null (undefined) value for an option.
 *
 * Return:
 *   WEECHAT_CONFIG_OPTION_SET_OK_CHANGED: OK, value has been changed
 *   WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE: OK, value not changed
 *   WEECHAT_CONFIG_OPTION_SET_ERROR: error
 */

int
config_file_option_set_null (struct t_config_option *option, int run_callback)
{
    int rc;

    if (!option)
        return WEECHAT_CONFIG_OPTION_SET_ERROR;

    rc = WEECHAT_CONFIG_OPTION_SET_ERROR;

    /* Null value is authorized only if it's allowed in option. */
    if (option->null_value_allowed)
    {
        /* Option was already null: do nothing. */
        if (!option->value)
            rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
        else
        {
            /* Set option to null. */
            free (option->value);
            option->value = NULL;
            rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
        }
    }

    /* Run callback and config hook(s) if value was changed. */
    if (rc == WEECHAT_CONFIG_OPTION_SET_OK_CHANGED)
    {
        if (run_callback && option->callback_change)
        {
            (void) (option->callback_change) (
                option->callback_change_pointer,
                option->callback_change_data,
                option);
        }
        config_file_hook_config_exec (option);
    }

    return rc;
}

/*
 * Set the default value for an option.
 *
 * Return:
 *   WEECHAT_CONFIG_OPTION_SET_OK_CHANGED: OK, default value has been changed
 *   WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE: OK, default value not changed
 *   WEECHAT_CONFIG_OPTION_SET_ERROR: error
 */

int
config_file_option_set_default (struct t_config_option *option,
                                const char *value,
                                int run_callback)
{
    int value_int, i, rc, new_value_ok, old_value_was_null, old_value, number;

    if (!option)
        return WEECHAT_CONFIG_OPTION_SET_ERROR;

    rc = WEECHAT_CONFIG_OPTION_SET_ERROR;

    if (value)
    {
        old_value_was_null = (option->default_value == NULL);
        switch (option->type)
        {
            case CONFIG_OPTION_TYPE_BOOLEAN:
                if (!option->default_value)
                {
                    option->default_value = malloc (sizeof (int));
                    if (option->default_value)
                    {
                        if (strcmp (value, "toggle") == 0)
                        {
                            CONFIG_BOOLEAN_DEFAULT(option) = CONFIG_BOOLEAN_TRUE;
                            rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                        }
                        else
                        {
                            if (config_file_string_boolean_is_valid (value))
                            {
                                value_int = config_file_string_to_boolean (value);
                                CONFIG_BOOLEAN_DEFAULT(option) = value_int;
                                rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                            }
                            else
                            {
                                free (option->default_value);
                                option->default_value = NULL;
                            }
                        }
                    }
                }
                else
                {
                    if (strcmp (value, "toggle") == 0)
                    {
                        CONFIG_BOOLEAN_DEFAULT(option) =
                            (CONFIG_BOOLEAN_DEFAULT(option) == CONFIG_BOOLEAN_TRUE) ?
                            CONFIG_BOOLEAN_FALSE : CONFIG_BOOLEAN_TRUE;
                        rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                    }
                    else
                    {
                        if (config_file_string_boolean_is_valid (value))
                        {
                            value_int = config_file_string_to_boolean (value);
                            if (value_int == CONFIG_BOOLEAN_DEFAULT(option))
                                rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                            else
                            {
                                CONFIG_BOOLEAN_DEFAULT(option) = value_int;
                                rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                            }
                        }
                    }
                }
                break;
            case CONFIG_OPTION_TYPE_INTEGER:
                old_value = 0;
                if (!option->default_value)
                    option->default_value = malloc (sizeof (int));
                else
                    old_value = CONFIG_INTEGER_DEFAULT(option);
                if (option->default_value)
                {
                    new_value_ok = 0;
                    if (strncmp (value, "++", 2) == 0)
                    {
                        if (util_parse_int (value + 2, 10, &number)
                            && ((long)old_value + (long)number <= (long)(option->max)))
                        {
                            value_int = old_value + number;
                            new_value_ok = 1;
                        }
                    }
                    else if (strncmp (value, "--", 2) == 0)
                    {
                        if (util_parse_int (value + 2, 10, &number)
                            && ((long)old_value - (long)number >= (long)(option->min)))
                        {
                            value_int = old_value - number;
                            new_value_ok = 1;
                        }
                    }
                    else
                    {
                        if (util_parse_int (value, 10, &number))
                        {
                            value_int = number;
                            if ((value_int >= option->min) && (value_int <= option->max))
                                new_value_ok = 1;
                        }
                    }
                    if (new_value_ok)
                    {
                        if (old_value_was_null || (value_int != old_value))
                        {
                            CONFIG_INTEGER_DEFAULT(option) = value_int;
                            rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                        }
                        else
                            rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                    }
                    else
                    {
                        if (old_value_was_null)
                        {
                            free (option->default_value);
                            option->default_value = NULL;
                        }
                    }
                }
                break;
            case CONFIG_OPTION_TYPE_STRING:
                rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                if (!option->default_value
                    || (strcmp (CONFIG_STRING_DEFAULT(option), value) != 0))
                    rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                if (option->default_value)
                {
                    free (option->default_value);
                    option->default_value = NULL;
                }
                option->default_value = strdup (value);
                if (!option->default_value)
                    rc = WEECHAT_CONFIG_OPTION_SET_ERROR;
                break;
            case CONFIG_OPTION_TYPE_COLOR:
                old_value = 0;
                if (!option->default_value)
                    option->default_value = malloc (sizeof (int));
                else
                    old_value = CONFIG_COLOR_DEFAULT(option);
                if (option->default_value)
                {
                    value_int = -1;
                    new_value_ok = 0;
                    if (strncmp (value, "++", 2) == 0)
                    {
                        if (util_parse_int (value + 2, 10, &number))
                        {
                            if (gui_color_assign_by_diff (&value_int,
                                                          gui_color_get_name (old_value),
                                                          number))
                                new_value_ok = 1;
                        }
                    }
                    else if (strncmp (value, "--", 2) == 0)
                    {
                        if (util_parse_int (value + 2, 10, &number))
                        {
                            if (gui_color_assign_by_diff (&value_int,
                                                          gui_color_get_name (old_value),
                                                          -1 * number))
                                new_value_ok = 1;
                        }
                    }
                    else
                    {
                        if (gui_color_assign (&value_int, value))
                            new_value_ok = 1;
                    }
                    if (new_value_ok)
                    {
                        if (old_value_was_null || (value_int != old_value))
                        {
                            CONFIG_COLOR_DEFAULT(option) = value_int;
                            rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                        }
                        else
                            rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                    }
                    else
                    {
                        if (old_value_was_null)
                        {
                            free (option->default_value);
                            option->default_value = NULL;
                        }
                    }
                }
                break;
            case CONFIG_OPTION_TYPE_ENUM:
                old_value = 0;
                if (!option->default_value)
                    option->default_value = malloc (sizeof (int));
                else
                    old_value = CONFIG_ENUM_DEFAULT(option);
                if (option->default_value)
                {
                    value_int = -1;
                    if (strncmp (value, "++", 2) == 0)
                    {
                        if (util_parse_int (value + 2, 10, &number))
                        {
                            number = number % (option->max + 1);
                            value_int = (old_value + number) %
                                (option->max + 1);
                        }
                    }
                    else if (strncmp (value, "--", 2) == 0)
                    {
                        if (util_parse_int (value + 2, 10, &number))
                        {
                            number = number % (option->max + 1);
                            value_int = (old_value + (option->max + 1) - number) %
                                (option->max + 1);
                        }
                    }
                    else
                    {
                        for (i = 0; option->string_values[i]; i++)
                        {
                            if (strcmp (option->string_values[i], value) == 0)
                            {
                                value_int = i;
                                break;
                            }
                        }
                    }
                    if (value_int >= 0)
                    {
                        if (old_value_was_null || (value_int != old_value))
                        {
                            CONFIG_ENUM_DEFAULT(option) = value_int;
                            rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
                        }
                        else
                            rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
                    }
                    else
                    {
                        if (old_value_was_null)
                        {
                            free (option->default_value);
                            option->default_value = NULL;
                        }
                    }
                }
                break;
            case CONFIG_NUM_OPTION_TYPES:
                break;
        }
        if (old_value_was_null && option->default_value)
            rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
    }
    else
    {
        if (option->null_value_allowed && option->default_value)
        {
            free (option->default_value);
            option->default_value = NULL;
            rc = WEECHAT_CONFIG_OPTION_SET_OK_CHANGED;
        }
        else
            rc = WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
    }

    /* Run callback and config hook(s) if default value was changed. */
    if (rc == WEECHAT_CONFIG_OPTION_SET_OK_CHANGED)
    {
        if (run_callback && option->callback_change)
        {
            (void) (option->callback_change) (
                option->callback_change_pointer,
                option->callback_change_data,
                option);
        }
        config_file_hook_config_exec (option);
    }

    return rc;
}

/*
 * Unset/reset an option.
 *
 * Return:
 *   WEECHAT_CONFIG_OPTION_UNSET_OK_NO_RESET: OK, value has not been reset
 *   WEECHAT_CONFIG_OPTION_UNSET_OK_RESET: OK, value has been reset
 *   WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED: OK, value has been removed
 *   WEECHAT_CONFIG_OPTION_UNSET_ERROR: error
 */

int
config_file_option_unset (struct t_config_option *option)
{
    int rc;
    char *option_full_name;

    if (!option)
        return WEECHAT_CONFIG_OPTION_UNSET_ERROR;

    rc = WEECHAT_CONFIG_OPTION_UNSET_OK_NO_RESET;

    if (option->section && option->section->user_can_delete_options)
    {
        /* Delete option. */
        if (option->callback_delete)
        {
            (void) (option->callback_delete) (
                option->callback_delete_pointer,
                option->callback_delete_data,
                option);
        }

        option_full_name = config_file_option_full_name (option);

        if (option->section->callback_delete_option)
        {
            rc = (int) (option->section->callback_delete_option) (
                option->section->callback_delete_option_pointer,
                option->section->callback_delete_option_data,
                option->config_file,
                option->section,
                option);
        }
        else
        {
            config_file_option_free (option, 0);
            rc = WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED;
        }

        if (option_full_name)
        {
            hook_config_exec (option_full_name, NULL);
            free (option_full_name);
        }
    }
    else
    {
        /* Reset value. */
        switch (config_file_option_reset (option, 1))
        {
            case WEECHAT_CONFIG_OPTION_SET_ERROR:
                rc = WEECHAT_CONFIG_OPTION_UNSET_ERROR;
                break;
            case WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE:
                rc = WEECHAT_CONFIG_OPTION_UNSET_OK_NO_RESET;
                break;
            case WEECHAT_CONFIG_OPTION_SET_OK_CHANGED:
                rc = WEECHAT_CONFIG_OPTION_UNSET_OK_RESET;
                break;
        }
    }

    return rc;
}

/*
 * Rename an option.
 */

void
config_file_option_rename (struct t_config_option *option,
                           const char *new_name)
{
    char *str_new_name, *full_old_name, *full_new_name;
    struct t_config_file *ptr_config;
    struct t_config_section *ptr_section;
    struct t_config_option *ptr_option;

    if (!option || !new_name || !new_name[0]
        || config_file_search_option (option->config_file, option->section, new_name))
        return;

    full_old_name = config_file_option_full_name (option);

    str_new_name = strdup (new_name);
    if (str_new_name)
    {
        /* Remove option from list. */
        if (option->section)
        {
            if (option->prev_option)
                (option->prev_option)->next_option = option->next_option;
            if (option->next_option)
                (option->next_option)->prev_option = option->prev_option;
            if (option->section->options == option)
                (option->section)->options = option->next_option;
            if (option->section->last_option == option)
                (option->section)->last_option = option->prev_option;
        }

        /* Rename option. */
        free (option->name);
        option->name = str_new_name;

        /* Re-insert option in section. */
        if (option->section)
            config_file_option_insert_in_section (option);
    }

    full_new_name = config_file_option_full_name (option);

    /* Rename "parent_name" in any option using the old option name. */
    if (full_old_name && full_new_name)
    {
        for (ptr_config = config_files; ptr_config;
             ptr_config = ptr_config->next_config)
        {
            for (ptr_section = ptr_config->sections; ptr_section;
                 ptr_section = ptr_section->next_section)
            {
                for (ptr_option = ptr_section->options; ptr_option;
                     ptr_option = ptr_option->next_option)
                {
                    if (ptr_option->parent_name
                        && (strcmp (ptr_option->parent_name, full_old_name) == 0))
                    {
                        free (ptr_option->parent_name);
                        ptr_option->parent_name = strdup (full_new_name);
                    }
                }
            }
        }
    }

    free (full_old_name);
    free (full_new_name);

    config_file_hook_config_exec (option);
}

/*
 * Build a string with the value or default value of option,
 * depending on the type of option.
 *
 * According to default_value:
 *   0: value of option is returned
 *   1: default value of option is returned
 *
 * Note: result must be freed after use.
 */

char *
config_file_option_value_to_string (struct t_config_option *option,
                                    int default_value,
                                    int use_colors,
                                    int use_delimiters)
{
    char *value;
    const char *ptr_value;
    int enabled;

    if (!option)
        return NULL;

    if ((default_value && !option->default_value)
        || (!default_value && !option->value))
    {
        string_asprintf (
            &value,
            "%s%s",
            (use_colors) ? GUI_COLOR(GUI_COLOR_CHAT_VALUE_NULL) : "",
            "null");
        return value;
    }

    switch (option->type)
    {
        case CONFIG_OPTION_TYPE_BOOLEAN:
            enabled = (default_value) ?
                CONFIG_BOOLEAN_DEFAULT(option) : CONFIG_BOOLEAN(option);
            string_asprintf (
                &value,
                "%s%s",
                (use_colors) ? GUI_COLOR(GUI_COLOR_CHAT_VALUE) : "",
                (enabled) ? "on" : "off");
            return value;
        case CONFIG_OPTION_TYPE_INTEGER:
            string_asprintf (
                &value,
                "%s%d",
                (use_colors) ? GUI_COLOR(GUI_COLOR_CHAT_VALUE) : "",
                (default_value) ? CONFIG_INTEGER_DEFAULT(option) : CONFIG_INTEGER(option));
            return value;
        case CONFIG_OPTION_TYPE_STRING:
            ptr_value = (default_value) ? CONFIG_STRING_DEFAULT(option) : CONFIG_STRING(option);
            string_asprintf (
                &value,
                "%s%s%s%s%s%s",
                (use_colors && use_delimiters) ? GUI_COLOR(GUI_COLOR_CHAT_DELIMITERS) : "",
                (use_delimiters) ? "\"" : "",
                (use_colors) ? GUI_COLOR(GUI_COLOR_CHAT_VALUE) : "",
                ptr_value,
                (use_colors && use_delimiters) ? GUI_COLOR(GUI_COLOR_CHAT_DELIMITERS) : "",
                (use_delimiters) ? "\"" : "");
            return value;
        case CONFIG_OPTION_TYPE_COLOR:
            ptr_value = gui_color_get_name (
                (default_value) ? CONFIG_COLOR_DEFAULT(option) : CONFIG_COLOR(option));
            if (!ptr_value)
                return NULL;
            string_asprintf (
                &value,
                "%s%s",
                (use_colors) ? GUI_COLOR(GUI_COLOR_CHAT_VALUE) : "",
                ptr_value);
            return value;
        case CONFIG_OPTION_TYPE_ENUM:
            ptr_value = (default_value) ?
                option->string_values[CONFIG_ENUM_DEFAULT(option)] :
                option->string_values[CONFIG_ENUM(option)];
            string_asprintf (
                &value,
                "%s%s",
                (use_colors) ? GUI_COLOR(GUI_COLOR_CHAT_VALUE) : "",
                ptr_value);
            return value;
        case CONFIG_NUM_OPTION_TYPES:
            /* Make C compiler happy. */
            break;
    }

    /* Make C static analyzer happy (never executed). */
    return NULL;
}

/*
 * Get a string value of an option property.
 */

const char *
config_file_option_get_string (struct t_config_option *option,
                               const char *property)
{
    if (!option || !property)
        return NULL;

    if (strcmp (property, "config_name") == 0)
        return option->config_file->name;
    else if (strcmp (property, "section_name") == 0)
        return option->section->name;
    else if (strcmp (property, "name") == 0)
        return option->name;
    else if (strcmp (property, "parent_name") == 0)
        return option->parent_name;
    else if (strcmp (property, "type") == 0)
        return config_option_type_string[option->type];
    else if (strcmp (property, "description") == 0)
        return option->description;

    return NULL;
}

/*
 * Get a pointer on an option property.
 */

void *
config_file_option_get_pointer (struct t_config_option *option,
                                const char *property)
{
    if (!option || !property)
        return NULL;

    if (strcmp (property, "config_file") == 0)
        return option->config_file;
    else if (strcmp (property, "section") == 0)
        return option->section;
    else if (strcmp (property, "name") == 0)
        return option->name;
    else if (strcmp (property, "parent_name") == 0)
        return option->parent_name;
    else if (strcmp (property, "type") == 0)
        return &option->type;
    else if (strcmp (property, "themable") == 0)
        return &option->themable;
    else if (strcmp (property, "description") == 0)
        return option->description;
    else if (strcmp (property, "string_values") == 0)
        return option->string_values;
    else if (strcmp (property, "min") == 0)
        return &option->min;
    else if (strcmp (property, "max") == 0)
        return &option->max;
    else if (strcmp (property, "default_value") == 0)
        return option->default_value;
    else if (strcmp (property, "value") == 0)
        return option->value;
    else if (strcmp (property, "prev_option") == 0)
        return option->prev_option;
    else if (strcmp (property, "next_option") == 0)
        return option->next_option;

    return NULL;
}

/*
 * Check if an option has a null value.
 *
 * Return:
 *   1: value of option is null
 *   0: value of option is not null
 */

int
config_file_option_is_null (struct t_config_option *option)
{
    if (!option)
        return 1;

    return (option->value) ? 0 : 1;
}

/*
 * Check if an option has a null default value.
 *
 * Return:
 *   1: default value of option is null
 *   0: default value of option is not null
 */

int
config_file_option_default_is_null (struct t_config_option *option)
{
    if (!option)
        return 1;

    return (option->default_value) ? 0 : 1;
}

/*
 * Check if an option has changed (current value different from default value).
 *
 * Return:
 *   1: option has changed
 *   0: option has default value
 */

int config_file_option_has_changed (struct t_config_option *option)
{
    /* Both default and current value are null => not changed. */
    if (!option->default_value && !option->value)
        return 0;

    /* Default is null and current value is not null => changed!. */
    if (!option->default_value && option->value)
        return 1;

    /* Default is not null and current value is null => changed!. */
    if (option->default_value && !option->value)
        return 1;

    /* Both default and current value are not null, compare their values. */
    switch (option->type)
    {
        case CONFIG_OPTION_TYPE_BOOLEAN:
            return CONFIG_BOOLEAN(option) != CONFIG_BOOLEAN_DEFAULT(option);
        case CONFIG_OPTION_TYPE_INTEGER:
            return CONFIG_INTEGER(option) != CONFIG_INTEGER_DEFAULT(option);
        case CONFIG_OPTION_TYPE_STRING:
            return strcmp (CONFIG_STRING(option), CONFIG_STRING_DEFAULT(option)) != 0;
        case CONFIG_OPTION_TYPE_COLOR:
            return CONFIG_COLOR(option) != CONFIG_COLOR_DEFAULT(option);
        case CONFIG_OPTION_TYPE_ENUM:
            return CONFIG_ENUM(option) != CONFIG_ENUM_DEFAULT(option);
        case CONFIG_NUM_OPTION_TYPES:
            /* Make C compiler happy. */
            break;
    }

    return 0;
}

/*
 * Set the value for an option using a full name of option (format:
 * "file.section.option").
 *
 * Return:
 *   WEECHAT_CONFIG_OPTION_SET_OK_CHANGED: OK, value has been changed
 *   WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE: OK, value not changed
 *   WEECHAT_CONFIG_OPTION_SET_ERROR: error
 *   WEECHAT_CONFIG_OPTION_SET_OPTION_NOT_FOUND: option not found
 */

int
config_file_option_set_with_string (const char *option_name, const char *value)
{
    int rc;
    struct t_config_file *ptr_config;
    struct t_config_section *ptr_section;
    struct t_config_option *ptr_option;
    const char *pos_option;

    rc = WEECHAT_CONFIG_OPTION_SET_OPTION_NOT_FOUND;

    config_file_search_with_string (option_name, &ptr_config, &ptr_section,
                                    &ptr_option, &pos_option);

    if (ptr_config && ptr_section)
    {
        if (ptr_option)
        {
            rc = (value) ?
                config_file_option_set (ptr_option, value, 1) :
                config_file_option_set_null (ptr_option, 1);
        }
        else
        {
            if (ptr_section->user_can_add_options
                && ptr_section->callback_create_option)
            {
                rc = (int) (ptr_section->callback_create_option) (
                    ptr_section->callback_create_option_pointer,
                    ptr_section->callback_create_option_data,
                    ptr_config,
                    ptr_section,
                    pos_option,
                    value);
            }
        }
    }

    return rc;
}

/*
 * Return boolean value of an option.
 *
 * Return 1 if value is true, 0 if it is false.
 */

int
config_file_option_boolean (struct t_config_option *option)
{
    if (!option || !option->value || (option->type != CONFIG_OPTION_TYPE_BOOLEAN))
        return 0;

    return CONFIG_BOOLEAN(option);
}

/*
 * Return default boolean value of an option.
 *
 * Return 1 if default value is true, 0 if it is false.
 */

int
config_file_option_boolean_default (struct t_config_option *option)
{
    if (!option || !option->default_value || (option->type != CONFIG_OPTION_TYPE_BOOLEAN))
        return 0;

    return CONFIG_BOOLEAN_DEFAULT(option);
}

/*
 * Return inherited boolean value of an option: value of option if not NULL,
 * or value of the parent option (if option inherits from another option).
 *
 * If the parent option is not found, return the default value of the option.
 * If the parent value is NULL, return the default value of the parent option.
 */

int
config_file_option_boolean_inherited (struct t_config_option *option)
{
    struct t_config_option *ptr_parent_option;

    if (option && option->value)
    {
        return config_file_option_boolean (option);
    }
    else
    {
        ptr_parent_option = config_file_get_parent_option (option);
        if (!ptr_parent_option)
            return config_file_option_boolean_default (option);
        if (!ptr_parent_option->value)
            return config_file_option_boolean_default (ptr_parent_option);
        return config_file_option_boolean (ptr_parent_option);
    }
}

/*
 * Return integer value of an option.
 */

int
config_file_option_integer (struct t_config_option *option)
{
    if (!option || !option->value)
        return 0;

    switch (option->type)
    {
        case CONFIG_OPTION_TYPE_BOOLEAN:
            if (CONFIG_BOOLEAN(option) == CONFIG_BOOLEAN_TRUE)
                return 1;
            else
                return 0;
        case CONFIG_OPTION_TYPE_INTEGER:
            return CONFIG_INTEGER(option);
        case CONFIG_OPTION_TYPE_STRING:
            return 0;
        case CONFIG_OPTION_TYPE_COLOR:
            return CONFIG_COLOR(option);
        case CONFIG_OPTION_TYPE_ENUM:
            return CONFIG_ENUM(option);
        case CONFIG_NUM_OPTION_TYPES:
            break;
    }
    return 0;
}

/*
 * Return default integer value of an option.
 */

int
config_file_option_integer_default (struct t_config_option *option)
{
    if (!option || !option->default_value)
        return 0;

    switch (option->type)
    {
        case CONFIG_OPTION_TYPE_BOOLEAN:
            if (CONFIG_BOOLEAN_DEFAULT(option) == CONFIG_BOOLEAN_TRUE)
                return 1;
            else
                return 0;
        case CONFIG_OPTION_TYPE_INTEGER:
            return CONFIG_INTEGER_DEFAULT(option);
        case CONFIG_OPTION_TYPE_STRING:
            return 0;
        case CONFIG_OPTION_TYPE_COLOR:
            return CONFIG_COLOR_DEFAULT(option);
        case CONFIG_OPTION_TYPE_ENUM:
            return CONFIG_ENUM_DEFAULT(option);
        case CONFIG_NUM_OPTION_TYPES:
            break;
    }
    return 0;
}

/*
 * Return inherited integer value of an option: value of option if not NULL,
 * or value of the parent option (if option inherits from another option).
 *
 * If the parent option is not found, return the default value of the option.
 * If the parent value is NULL, return the default value of the parent option.
 */

int
config_file_option_integer_inherited (struct t_config_option *option)
{
    struct t_config_option *ptr_parent_option;

    if (option && option->value)
    {
        return config_file_option_integer (option);
    }
    else
    {
        ptr_parent_option = config_file_get_parent_option (option);
        if (!ptr_parent_option)
            return config_file_option_integer_default (option);
        if (!ptr_parent_option->value)
            return config_file_option_integer_default (ptr_parent_option);
        return config_file_option_integer (ptr_parent_option);
    }
}

/*
 * Return string value of an option.
 */

const char *
config_file_option_string (struct t_config_option *option)
{
    if (!option || !option->value)
        return NULL;

    switch (option->type)
    {
        case CONFIG_OPTION_TYPE_BOOLEAN:
            if (CONFIG_BOOLEAN(option))
                return config_boolean_true[0];
            else
                return config_boolean_false[0];
        case CONFIG_OPTION_TYPE_INTEGER:
            return NULL;
        case CONFIG_OPTION_TYPE_STRING:
            return CONFIG_STRING(option);
        case CONFIG_OPTION_TYPE_COLOR:
            return gui_color_get_name (CONFIG_COLOR(option));
        case CONFIG_OPTION_TYPE_ENUM:
            return option->string_values[CONFIG_ENUM(option)];
        case CONFIG_NUM_OPTION_TYPES:
            return NULL;
    }
    return NULL;
}

/*
 * Return default string value of an option.
 */

const char *
config_file_option_string_default (struct t_config_option *option)
{
    if (!option || !option->default_value)
        return NULL;

    switch (option->type)
    {
        case CONFIG_OPTION_TYPE_BOOLEAN:
            if (CONFIG_BOOLEAN_DEFAULT(option))
                return config_boolean_true[0];
            else
                return config_boolean_false[0];
        case CONFIG_OPTION_TYPE_INTEGER:
            return NULL;
        case CONFIG_OPTION_TYPE_STRING:
            return CONFIG_STRING_DEFAULT(option);
        case CONFIG_OPTION_TYPE_COLOR:
            return gui_color_get_name (CONFIG_COLOR_DEFAULT(option));
        case CONFIG_OPTION_TYPE_ENUM:
            return option->string_values[CONFIG_ENUM_DEFAULT(option)];
        case CONFIG_NUM_OPTION_TYPES:
            return NULL;
    }
    return NULL;
}

/*
 * Return inherited string value of an option: value of option if not NULL,
 * or value of the parent option (if option inherits from another option).
 *
 * If the parent option is not found, return the default value of the option.
 * If the parent value is NULL, return the default value of the parent option.
 */

const char *
config_file_option_string_inherited (struct t_config_option *option)
{
    struct t_config_option *ptr_parent_option;

    if (option && option->value)
    {
        return config_file_option_string (option);
    }
    else
    {
        ptr_parent_option = config_file_get_parent_option (option);
        if (!ptr_parent_option)
            return config_file_option_string_default (option);
        if (!ptr_parent_option->value)
            return config_file_option_string_default (ptr_parent_option);
        return config_file_option_string (ptr_parent_option);
    }
}

/*
 * Return color value of an option.
 */

const char *
config_file_option_color (struct t_config_option *option)
{
    if (!option || !option->value || (option->type != CONFIG_OPTION_TYPE_COLOR))
        return NULL;

    return gui_color_get_name (CONFIG_COLOR(option));
}

/*
 * Return default color value of an option.
 */

const char *
config_file_option_color_default (struct t_config_option *option)
{
    if (!option || !option->default_value || (option->type != CONFIG_OPTION_TYPE_COLOR))
        return NULL;

    return gui_color_get_name (CONFIG_COLOR_DEFAULT(option));
}

/*
 * Return inherited color value of an option: value of option if not NULL,
 * or value of the parent option (if option inherits from another option).
 *
 * If the parent option is not found, return the default value of the option.
 * If the parent value is NULL, return the default value of the parent option.
 */

const char *
config_file_option_color_inherited (struct t_config_option *option)
{
    struct t_config_option *ptr_parent_option;

    if (option && option->value)
        return config_file_option_color (option);
    else
    {
        ptr_parent_option = config_file_get_parent_option (option);
        if (!ptr_parent_option)
            return config_file_option_color_default (option);
        if (!ptr_parent_option->value)
            return config_file_option_color_default (ptr_parent_option);
        return config_file_option_color (ptr_parent_option);
    }
}

/*
 * Return enum value of an option.
 */

int
config_file_option_enum (struct t_config_option *option)
{
    if (!option || !option->value)
        return 0;

    switch (option->type)
    {
        case CONFIG_OPTION_TYPE_BOOLEAN:
            if (CONFIG_BOOLEAN(option) == CONFIG_BOOLEAN_TRUE)
                return 1;
            else
                return 0;
        case CONFIG_OPTION_TYPE_INTEGER:
            return CONFIG_INTEGER(option);
        case CONFIG_OPTION_TYPE_STRING:
            return 0;
        case CONFIG_OPTION_TYPE_COLOR:
            return CONFIG_COLOR(option);
        case CONFIG_OPTION_TYPE_ENUM:
            return CONFIG_ENUM(option);
        case CONFIG_NUM_OPTION_TYPES:
            break;
    }
    return 0;
}

/*
 * Return default enum value of an option.
 */

int
config_file_option_enum_default (struct t_config_option *option)
{
    if (!option || !option->default_value)
        return 0;

    switch (option->type)
    {
        case CONFIG_OPTION_TYPE_BOOLEAN:
            if (CONFIG_BOOLEAN_DEFAULT(option) == CONFIG_BOOLEAN_TRUE)
                return 1;
            else
                return 0;
        case CONFIG_OPTION_TYPE_INTEGER:
            return CONFIG_INTEGER_DEFAULT(option);
        case CONFIG_OPTION_TYPE_STRING:
            return 0;
        case CONFIG_OPTION_TYPE_COLOR:
            return CONFIG_COLOR_DEFAULT(option);
        case CONFIG_OPTION_TYPE_ENUM:
            return CONFIG_ENUM_DEFAULT(option);
        case CONFIG_NUM_OPTION_TYPES:
            break;
    }
    return 0;
}

/*
 * Return inherited enum value of an option: value of option if not NULL,
 * or value of the parent option (if option inherits from another option).
 *
 * If the parent option is not found, return the default value of the option.
 * If the parent value is NULL, return the default value of the parent option.
 */

int
config_file_option_enum_inherited (struct t_config_option *option)
{
    struct t_config_option *ptr_parent_option;

    if (option && option->value)
    {
        return config_file_option_enum (option);
    }
    else
    {
        ptr_parent_option = config_file_get_parent_option (option);
        if (!ptr_parent_option)
            return config_file_option_enum_default (option);
        if (!ptr_parent_option->value)
            return config_file_option_enum_default (ptr_parent_option);
        return config_file_option_enum (ptr_parent_option);
    }
}

/*
 * Return a char to add before the name of option to escape it.
 *
 * Return:
 *   "\": name must be escaped with "\" (if names begins with # [ \)
 *   "": name must not be escaped
 */

const char *
config_file_option_escape (const char *name)
{
    static char str_escaped[2] = "\\", str_not_escaped[1] = { '\0' };

    if (!name)
        return str_escaped;

    if ((name[0] == '#') || (name[0] == '[') || (name[0] == '\\'))
        return str_escaped;

    return str_not_escaped;
}

/*
 * Write an option in a configuration file.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
config_file_write_option (struct t_config_file *config_file,
                          struct t_config_option *option)
{
    int rc;

    if (!config_file || !config_file->file || !option)
        return 0;

    rc = 1;

    if (option->value)
    {
        switch (option->type)
        {
            case CONFIG_OPTION_TYPE_BOOLEAN:
                rc = string_fprintf (config_file->file, "%s%s = %s\n",
                                     config_file_option_escape (option->name),
                                     option->name,
                                     (CONFIG_BOOLEAN(option) == CONFIG_BOOLEAN_TRUE) ?
                                     "on" : "off");
                break;
            case CONFIG_OPTION_TYPE_INTEGER:
                rc = string_fprintf (config_file->file, "%s%s = %d\n",
                                     config_file_option_escape (option->name),
                                     option->name,
                                     CONFIG_INTEGER(option));
                break;
            case CONFIG_OPTION_TYPE_STRING:
                rc = string_fprintf (config_file->file, "%s%s = \"%s\"\n",
                                     config_file_option_escape (option->name),
                                     option->name,
                                     (char *)option->value);
                break;
            case CONFIG_OPTION_TYPE_COLOR:
                rc = string_fprintf (config_file->file, "%s%s = %s\n",
                                     config_file_option_escape (option->name),
                                     option->name,
                                     gui_color_get_name (CONFIG_COLOR(option)));
                break;
            case CONFIG_OPTION_TYPE_ENUM:
                rc = string_fprintf (config_file->file, "%s%s = %s\n",
                                     config_file_option_escape (option->name),
                                     option->name,
                                     option->string_values[CONFIG_ENUM(option)]);
                break;
            case CONFIG_NUM_OPTION_TYPES:
                break;
        }
    }
    else
    {
        rc = string_fprintf (config_file->file, "%s%s\n",
                             config_file_option_escape (option->name),
                             option->name);
    }

    return rc;
}

/*
 * Write a line in a configuration file.
 *
 * If value is NULL, write a section with `[` and `]` around.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
config_file_write_line (struct t_config_file *config_file,
                        const char *option_name, const char *value, ...)
{
    int rc;

    if (!config_file || !option_name)
        return 0;

    if (value && value[0])
    {
        weechat_va_format (value);
        if (vbuffer)
        {
            if (vbuffer[0])
            {
                rc = string_fprintf (config_file->file, "%s%s = %s\n",
                                     config_file_option_escape (option_name),
                                     option_name, vbuffer);
                free (vbuffer);
                return rc;
            }
            free (vbuffer);
        }
    }

    return (string_fprintf (config_file->file, "\n[%s]\n",
                            option_name));
}

/*
 * Write a configuration file (this function must not be called directly).
 *
 * Return:
 *   WEECHAT_CONFIG_WRITE_OK: OK
 *   WEECHAT_CONFIG_WRITE_ERROR: error
 *   WEECHAT_CONFIG_WRITE_MEMORY_ERROR: not enough memory
 */

int
config_file_write_internal (struct t_config_file *config_file,
                            int default_options)
{
    int rc;
    long file_perms;
    char *filename, *filename2, resolved_path[PATH_MAX];
    struct t_config_section *ptr_section;
    struct t_config_option *ptr_option;

    if (!config_file)
        return WEECHAT_CONFIG_WRITE_ERROR;

    /* Build filename. */
    if (string_asprintf (&filename,
                         "%s%s%s",
                         weechat_config_dir,
                         DIR_SEPARATOR,
                         config_file->filename) < 0)
    {
        return WEECHAT_CONFIG_WRITE_MEMORY_ERROR;
    }

    /*
     * Build temporary filename, this temp file will be renamed to filename
     * after write.
     */
    if (string_asprintf (&filename2, "%s.weechattmp", filename) < 0)
    {
        free (filename);
        return WEECHAT_CONFIG_WRITE_MEMORY_ERROR;
    }

    /* If filename is a symbolic link, use target as filename. */
    if (realpath (filename, resolved_path))
    {
        if (strcmp (filename, resolved_path) != 0)
        {
            free (filename);
            filename = strdup (resolved_path);
            if (!filename)
            {
                free (filename2);
                return WEECHAT_CONFIG_WRITE_MEMORY_ERROR;
            }
        }
    }

    log_printf (_("Writing configuration file %s%s%s"),
                config_file->filename,
                (default_options) ? " " : "",
                (default_options) ? _("(default options)") : "");

    /* Open temp file in write mode. */
    config_file->file = fopen (filename2, "wb");
    if (!config_file->file)
    {
        gui_chat_printf (NULL,
                         _("%sCannot create file \"%s\""),
                         gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                         filename2);
        goto error;
    }

    /* Write header with name of config file and WeeChat version. */
    if (!string_fprintf (
            config_file->file,
            "#\n"
            "# %s -- %s\n"
            "#\n"
            "# WARNING: It is NOT recommended to edit this file by hand,\n"
            "# especially if WeeChat is running.\n"
            "#\n"
            "# Use commands like /set or /fset to change settings in WeeChat.\n"
            "#\n"
            "# For more info, see: https://weechat.org/doc/weechat/quickstart/\n"
            "#\n",
            version_get_name (),
            config_file->filename))
    {
        goto error;
    }

    /* Write config version (if different from 1). */
    if (config_file->version > 1)
    {
        if (!string_fprintf (config_file->file,
                             "\nconfig_version = %d\n",
                             config_file->version))
        {
            goto error;
        }
    }

    /* Write all sections. */
    for (ptr_section = config_file->sections; ptr_section;
         ptr_section = ptr_section->next_section)
    {
        /* Call write callback if defined for section. */
        if (default_options && ptr_section->callback_write_default)
        {
            if ((ptr_section->callback_write_default) (
                    ptr_section->callback_write_default_pointer,
                    ptr_section->callback_write_default_data,
                    config_file,
                    ptr_section->name) != WEECHAT_CONFIG_WRITE_OK)
                goto error;
        }
        else if (!default_options && ptr_section->callback_write)
        {
            if ((ptr_section->callback_write) (
                    ptr_section->callback_write_pointer,
                    ptr_section->callback_write_data,
                    config_file,
                    ptr_section->name) != WEECHAT_CONFIG_WRITE_OK)
                goto error;
        }
        else
        {
            /* Write all options for section. */
            if (!string_fprintf (config_file->file,
                                 "\n[%s]\n", ptr_section->name))
                goto error;
            for (ptr_option = ptr_section->options; ptr_option;
                 ptr_option = ptr_option->next_option)
            {
                if (!config_file_write_option (config_file, ptr_option))
                    goto error;
            }
        }
    }

    if (fflush (config_file->file) != 0)
        goto error;

    if (dir_file_compare (filename, filename2) == 0)
    {
        /* No changes in the config, don't write it on disk. */
        goto no_changes;
    }

    /*
     * Ensure the file is really written on the storage device;
     * this is disabled by default because it is really slow
     * (about 20 to 200x slower).
     */
    if (CONFIG_BOOLEAN(config_look_save_config_with_fsync))
    {
        if (fsync (fileno (config_file->file)) != 0)
            goto error;
    }

    /* Close temp file. */
    fclose (config_file->file);
    config_file->file = NULL;

    /* Update file mode. */
    if (!util_parse_long (CONFIG_STRING(config_look_config_permissions), 8, &file_perms))
        file_perms = 0600;
    if (chmod (filename2, file_perms) < 0)
    {
        gui_chat_printf (
            NULL,
            _("%sWARNING: failed to set permissions on configuration file "
              "\"%s\" (%s)"),
            gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
            filename2,
            strerror (errno));
    }

    /* Rename temp file to target file. */
    rc = rename (filename2, filename);

    free (filename);
    free (filename2);

    return (rc == 0) ? WEECHAT_CONFIG_WRITE_OK : WEECHAT_CONFIG_WRITE_ERROR;

no_changes:
    rc = WEECHAT_CONFIG_WRITE_OK;
    goto end;

error:
    gui_chat_printf (NULL,
                     _("%sError writing configuration file \"%s\""),
                     gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                     filename);
    log_printf (_("%sError writing configuration file \"%s\""),
                "", config_file->filename);
    rc = WEECHAT_CONFIG_WRITE_ERROR;
    goto end;

end:
    if (config_file->file)
    {
        fclose (config_file->file);
        config_file->file = NULL;
    }
    unlink (filename2);
    free (filename);
    free (filename2);
    return rc;
}

/*
 * Write a configuration file.
 *
 * Return:
 *   WEECHAT_CONFIG_WRITE_OK: OK
 *   WEECHAT_CONFIG_WRITE_ERROR: error
 *   WEECHAT_CONFIG_WRITE_MEMORY_ERROR: not enough memory
 */

int
config_file_write (struct t_config_file *config_file)
{
    return config_file_write_internal (config_file, 0);
}

/*
 * Parse configuration version.
 *
 * Return:
 *   >= 1: configuration version
 *     -1: error
 */

int
config_file_parse_version (const char *version)
{
    int number;

    if (!version)
        return -1;

    if (!util_parse_int (version, 10, &number) || (number < 1))
        return -1;

    return number;
}

/*
 * Backup a configuration file if its version is unsupported and cannot be loaded.
 */

void
config_file_backup (const char *filename)
{
    char *filename_backup, str_time[32], str_index[32];
    int length, index;
    struct tm *local_time;
    time_t date;

    if (!filename)
        return;

    length = strlen (filename) + 128;

    filename_backup = malloc (length);
    if (!filename_backup)
        return;

    date = time (NULL);
    local_time = localtime (&date);
    if (strftime (str_time, sizeof (str_time), ".%Y%m%d.%H%M%S", local_time) == 0)
        str_time[0] = '\0';

    index = 1;
    while (1)
    {
        if (index == 1)
            str_index[0] = '\0';
        else
            snprintf (str_index, sizeof (str_index), ".%d", index);
        snprintf (filename_backup, length,
                  "%s.backup%s%s",
                  filename, str_time, str_index);
        if (access (filename_backup, F_OK) != 0)
            break;
        index++;
    }

    if (dir_file_copy (filename, filename_backup))
    {
        gui_chat_printf (NULL,
                         _("%sFile %s has been backed up as %s"),
                         gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                         filename, filename_backup);
    }
    else
    {
        gui_chat_printf (NULL,
                         _("%sError: unable to backup file %s"),
                         gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                         filename);
    }

    free (filename_backup);
}

/*
 * Update data read from config file: either section or option + value.
 * The update callback (if defined in config) is called if the config version
 * read in file is less than to the current config version.
 *
 * Parameters "section", "option" and "value" are updated in place: if the
 * callback gives a new value, they are first freed and allocated again with
 * the new value (or set to NULL for the value if the callback return
 * special key "value_null").
 *
 * Section can be updated only if option and value are NULL (ie if we are
 * reading a section line like "[section]").
 *
 * Integer warning_update_displayed is set to 1 if a warning is displayed,
 * when the file is updated to a newer version (then it's not compatible any
 * more with previous versions).
 */

void
config_file_update_data_read (struct t_config_file *config_file,
                              const char *filename,
                              const char *section,
                              const char *option,
                              const char *value,
                              char **ret_section,
                              char **ret_option,
                              char **ret_value,
                              int *warning_update_displayed)
{
    struct t_hashtable *data_read, *hashtable;
    const char *ptr_section, *ptr_option, *ptr_value;
    int value_null;

    /* Do nothing if config is already the latest version. */
    if (config_file->version_read >= config_file->version)
        return;

    if (!*warning_update_displayed
        && (config_file->version_read < config_file->version))
    {
        gui_chat_printf (
            NULL,
            _("%sImportant: file %s has been updated from version %d to %d, "
              "it is not compatible and cannot be loaded anymore with any "
              "older version"),
            gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
            filename,
            config_file->version_read,
            config_file->version);
        *warning_update_displayed = 1;
    }

    /* Do nothing if there's no update callback. */
    if (!config_file->callback_update)
        return;

    value_null = 0;

    data_read = hashtable_new (
        32,
        WEECHAT_HASHTABLE_STRING,
        WEECHAT_HASHTABLE_STRING,
        NULL, NULL);
    if (!data_read)
        return;

    hashtable_set (data_read, "config", config_file->name);
    if (section)
        hashtable_set (data_read, "section", section);
    if (option)
    {
        hashtable_set (data_read, "option", option);
        if (value)
        {
            hashtable_set (data_read, "value", value);
        }
        else
        {
            hashtable_set (data_read, "value_null", "1");
            value_null = 1;
        }
    }

    hashtable = (config_file->callback_update)
        (config_file->callback_update_pointer,
         config_file->callback_update_data,
         config_file,
         config_file->version_read,
         data_read);

    if (hashtable)
    {
        /* If reading a section line, we can update its name. */
        if (section && !option && ret_section)
        {
            ptr_section = hashtable_get (hashtable, "section");
            if (ptr_section && ptr_section[0])
            {
                free (*ret_section);
                *ret_section = strdup (ptr_section);
            }
        }

        /* If reading an option line, we can update its name and value. */
        if (section && option)
        {
            /* Option name */
            if (ret_option)
            {
                ptr_option = hashtable_get (hashtable, "option");
                if (ptr_option)
                {
                    free (*ret_option);
                    *ret_option = strdup (ptr_option);
                }
            }
            /* Value */
            if (ret_value)
            {
                ptr_value = hashtable_get (hashtable, "value");
                if (!value_null && hashtable_has_key (hashtable, "value_null"))
                    ptr_value = NULL;
                free (*ret_value);
                *ret_value = (ptr_value) ? strdup (ptr_value) : NULL;
            }
        }
    }

    if (hashtable && (hashtable != data_read))
        hashtable_free (hashtable);
    hashtable_free (data_read);
}

/*
 * Read a configuration file (this function must not be called directly).
 *
 * Return:
 *   WEECHAT_CONFIG_READ_OK: OK
 *   WEECHAT_CONFIG_READ_MEMORY_ERROR: not enough memory
 *   WEECHAT_CONFIG_READ_FILE_NOT_FOUND: file not found
 */

int
config_file_read_internal (struct t_config_file *config_file, int reload)
{
    int line_number, rc, length, version, warning_update_displayed;
    char *filename, *section, *option, *value;
    struct t_config_section *ptr_section;
    struct t_config_option *ptr_option;
    char line[16384], *ptr_line, *ptr_line2, *pos, *pos2;

    if (!config_file)
        return WEECHAT_CONFIG_READ_FILE_NOT_FOUND;

    config_file->version_read = 1;
    warning_update_displayed = 0;

    /* Build filename. */
    if (string_asprintf (&filename,
                         "%s%s%s",
                         weechat_config_dir,
                         DIR_SEPARATOR,
                         config_file->filename) < 0)
    {
        return WEECHAT_CONFIG_READ_MEMORY_ERROR;
    }

    /* Create file with default options if it does not exist. */
    if (access (filename, F_OK) != 0)
    {
        if (strcmp (config_file->name, WEECHAT_CONFIG_NAME) == 0)
            weechat_first_start = 1;
        config_file_write_internal (config_file, 1);
    }

    /* Read config file. */
    config_file->file = fopen (filename, "r");
    if (!config_file->file)
    {
        gui_chat_printf (NULL,
                         _("%sWARNING: failed to read configuration file "
                           "\"%s\" (%s)"),
                         gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                         filename,
                         strerror (errno));
        gui_chat_printf (NULL,
                         _("%sWARNING: file \"%s\" will be overwritten on exit "
                           "with default values (it is HIGHLY recommended to "
                           "backup this file now)"),
                         gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                         filename);
        free (filename);
        return WEECHAT_CONFIG_READ_FILE_NOT_FOUND;
    }

    if (!reload)
        log_printf (_("Reading configuration file %s"), config_file->filename);

    /* Read all lines. */
    ptr_section = NULL;
    line_number = 0;
    while (!feof (config_file->file))
    {
        line_number++;

        option = NULL;
        value = NULL;

        ptr_line = fgets (line, sizeof (line) - 1, config_file->file);
        if (!ptr_line)
            goto end_line;

        /* Encode line to internal charset. */
        ptr_line2 = string_iconv_to_internal (NULL, ptr_line);
        if (ptr_line2)
        {
            snprintf (line, sizeof (line), "%s", ptr_line2);
            free (ptr_line2);
        }

        /* Skip spaces. */
        while (ptr_line[0] == ' ')
        {
            ptr_line++;
        }

        /* Remove CR/LF. */
        pos = strchr (ptr_line, '\r');
        if (pos)
            pos[0] = '\0';
        pos = strchr (ptr_line, '\n');
        if (pos)
            pos[0] = '\0';

        /* Ignore empty line or comment. */
        if (!ptr_line[0] || (ptr_line[0] == '#'))
            goto end_line;

        /* Beginning of section */
        if ((ptr_line[0] == '[') && !strchr (ptr_line, '='))
        {
            pos = strchr (ptr_line, ']');
            if (!pos)
            {
                gui_chat_printf (NULL,
                                 _("%sWarning: %s, line %d: invalid "
                                   "syntax, missing \"]\""),
                                 gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                                 filename, line_number);
            }
            else
            {
                section = string_strndup (ptr_line + 1, pos - ptr_line - 1);
                if (section)
                {
                    config_file_update_data_read (config_file, filename,
                                                  section, NULL, NULL,
                                                  &section, NULL, NULL,
                                                  &warning_update_displayed);
                    ptr_section = config_file_search_section (config_file,
                                                              section);
                    if (!ptr_section)
                    {
                        gui_chat_printf (
                            NULL,
                            _("%sWarning: %s, line %d: ignoring unknown "
                              "section identifier (\"%s\")"),
                            gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                            filename, line_number, section);
                    }
                    free (section);
                }
            }
            goto end_line;
        }

        /* Skip escape char. */
        if (ptr_line[0] == '\\')
            ptr_line++;

        pos = strstr (ptr_line, " =");
        if (pos)
        {
            /* Skip spaces before '='. */
            pos2 = pos - 1;
            while ((pos2 > ptr_line) && (pos2[0] == ' '))
            {
                pos2--;
            }
            option = string_strndup (ptr_line, pos2 + 1 - ptr_line);
            /* Skip spaces after '='. */
            pos += 2;
            while (pos[0] == ' ')
            {
                pos++;
            }
            if (strcmp (pos, WEECHAT_CONFIG_OPTION_NULL) != 0)
            {
                length = strlen (pos);
                if (length > 1)
                {
                    /* Remove simple or double quotes and spaces at the end. */
                    pos2 = pos + length - 1;
                    while ((pos2 > pos) && (pos2[0] == ' '))
                    {
                        pos2--;
                    }
                    if (((pos[0] == '\'') && (pos2[0] == '\''))
                        || ((pos[0] == '"') && (pos2[0] == '"')))
                    {
                        value = string_strndup (pos + 1, pos2 - pos - 1);
                    }
                    else
                    {
                        value = string_strndup (pos, pos2 + 1 - pos);
                    }
                }
                else
                {
                    value = strdup (pos);
                }
            }
        }
        else
        {
            option = strdup (ptr_line);
        }

        if (!ptr_section && (strcmp (option, CONFIG_VERSION_OPTION) == 0))
        {
            version = config_file_parse_version (pos);
            if (version < 0)
            {
                gui_chat_printf (
                    NULL,
                    _("%sError: %s, line %d: invalid config "
                      "version: \"%s\" => "
                      "rest of file is IGNORED, default options are used"),
                    gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                    filename, line_number,
                    line);
                config_file_backup (filename);
                free (option);
                free (value);
                goto end_file;
            }
            else
            {
                config_file->version_read = version;
                if (config_file->version_read > config_file->version)
                {
                    gui_chat_printf (
                        NULL,
                        _("%sError: %s, version read (%d) is newer than "
                          "supported version (%d) => "
                          "rest of file is IGNORED, default options are used"),
                        gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                        filename,
                        config_file->version_read,
                        config_file->version);
                    config_file_backup (filename);
                    free (option);
                    free (value);
                    goto end_file;
                }
            }
            goto end_line;
        }

        if (!ptr_section)
        {
            gui_chat_printf (NULL,
                             _("%sWarning: %s, line %d: "
                               "ignoring option outside section: %s"),
                             gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                             filename, line_number,
                             line);
            goto end_line;
        }

        config_file_update_data_read (config_file, filename,
                                      ptr_section->name, option, value,
                                      NULL, &option, &value,
                                      &warning_update_displayed);

        /* Option has been ignored by the update callback? */
        if (!option || !option[0])
            goto end_line;

        if (ptr_section->callback_read)
        {
            ptr_option = NULL;
            rc = (ptr_section->callback_read)
                (ptr_section->callback_read_pointer,
                 ptr_section->callback_read_data,
                 config_file,
                 ptr_section,
                 option,
                 value);
        }
        else
        {
            rc = WEECHAT_CONFIG_OPTION_SET_OPTION_NOT_FOUND;
            ptr_option = config_file_search_option (config_file,
                                                    ptr_section,
                                                    option);
            if (ptr_option)
            {
                rc = config_file_option_set (ptr_option, value, 1);
                ptr_option->loaded = 1;
            }
            else
            {
                if (ptr_section->callback_create_option)
                {
                    rc = (int) (ptr_section->callback_create_option) (
                        ptr_section->callback_create_option_pointer,
                        ptr_section->callback_create_option_data,
                        config_file,
                        ptr_section,
                        option,
                        value);
                }
            }
        }

        switch (rc)
        {
            case WEECHAT_CONFIG_OPTION_SET_OPTION_NOT_FOUND:
                gui_chat_printf (
                    NULL,
                    _("%sWarning: %s, line %d: "
                      "ignoring unknown option for section \"%s\": %s"),
                    gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                    filename, line_number,
                    ptr_section->name,
                    line);
                break;
            case WEECHAT_CONFIG_OPTION_SET_ERROR:
                gui_chat_printf (
                    NULL,
                    _("%sWarning: %s, line %d: "
                      "ignoring invalid value for option in section \"%s\": %s"),
                    gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                    filename, line_number,
                    ptr_section->name,
                    line);
                break;
        }

    end_line:
        free (option);
        free (value);
    }

end_file:
    fclose (config_file->file);
    config_file->file = NULL;
    free (filename);

    return WEECHAT_CONFIG_READ_OK;
}

/*
 * Read a configuration file.
 *
 * Return:
 *   WEECHAT_CONFIG_READ_OK: OK
 *   WEECHAT_CONFIG_READ_MEMORY_ERROR: not enough memory
 *   WEECHAT_CONFIG_READ_FILE_NOT_FOUND: file not found
 */

int
config_file_read (struct t_config_file *config_file)
{
    return config_file_read_internal (config_file, 0);
}

/*
 * Reload a configuration file.
 *
 * Return:
 *   WEECHAT_CONFIG_READ_OK: OK
 *   WEECHAT_CONFIG_READ_MEMORY_ERROR: not enough memory
 *   WEECHAT_CONFIG_READ_FILE_NOT_FOUND: file not found
 */

int
config_file_reload (struct t_config_file *config_file)
{
    struct t_config_section *ptr_section;
    struct t_config_option *ptr_option;
    int rc;

    if (!config_file)
        return WEECHAT_CONFIG_READ_FILE_NOT_FOUND;

    log_printf (_("Reloading configuration file %s"), config_file->filename);

    /* Init "loaded" flag for all options. */
    for (ptr_section = config_file->sections; ptr_section;
         ptr_section = ptr_section->next_section)
    {
        if (!ptr_section->callback_read)
        {
            for (ptr_option = ptr_section->options; ptr_option;
                 ptr_option = ptr_option->next_option)
            {
                ptr_option->loaded = 0;
            }
        }
    }

    /* Read configuration file. */
    rc = config_file_read_internal (config_file, 1);

    /* Reset options not found in configuration file. */
    for (ptr_section = config_file->sections; ptr_section;
         ptr_section = ptr_section->next_section)
    {
        if (!ptr_section->callback_read)
        {
            for (ptr_option = ptr_section->options; ptr_option;
                 ptr_option = ptr_option->next_option)
            {
                if (!ptr_option->loaded)
                    config_file_option_reset (ptr_option, 1);
            }
        }
    }

    return rc;
}

/*
 * Free data in an option.
 */

void
config_file_option_free_data (struct t_config_option *option)
{
    free (option->name);
    free (option->parent_name);
    free (option->description);
    string_free_split (option->string_values);
    free (option->default_value);
    free (option->value);
    free (option->callback_check_value_data);
    free (option->callback_change_data);
    free (option->callback_delete_data);
}

/*
 * Free an option.
 */

void
config_file_option_free (struct t_config_option *option, int run_callback)
{
    struct t_config_section *ptr_section;
    struct t_config_option *new_options;
    char *option_full_name;

    if (!option)
        return;

    option_full_name = (run_callback) ?
        config_file_option_full_name (option) : NULL;

    ptr_section = option->section;

    /* Free data. */
    config_file_option_free_data (option);

    /* Remove option from section. */
    if (ptr_section)
    {
        if (ptr_section->last_option == option)
            ptr_section->last_option = option->prev_option;
        if (option->prev_option)
        {
            (option->prev_option)->next_option = option->next_option;
            new_options = ptr_section->options;
        }
        else
            new_options = option->next_option;
        if (option->next_option)
            (option->next_option)->prev_option = option->prev_option;
        ptr_section->options = new_options;
    }

    free (option);

    if (option_full_name)
    {
        hook_config_exec (option_full_name, NULL);
        free (option_full_name);
    }
}

/*
 * Free options in a section.
 */

void
config_file_section_free_options (struct t_config_section *section)
{
    if (!section)
        return;

    while (section->options)
    {
        config_file_option_free (section->options, 1);
    }
}

/*
 * Free a section.
 */

void
config_file_section_free (struct t_config_section *section)
{
    struct t_config_file *ptr_config;
    struct t_config_section *new_sections;

    if (!section)
        return;

    ptr_config = section->config_file;

    /* Free data. */
    config_file_section_free_options (section);
    free (section->name);
    free (section->callback_read_data);
    free (section->callback_write_data);
    free (section->callback_write_default_data);
    free (section->callback_create_option_data);
    free (section->callback_delete_option_data);

    /* Remove section from list. */
    if (ptr_config->last_section == section)
        ptr_config->last_section = section->prev_section;
    if (section->prev_section)
    {
        (section->prev_section)->next_section = section->next_section;
        new_sections = ptr_config->sections;
    }
    else
        new_sections = section->next_section;

    if (section->next_section)
        (section->next_section)->prev_section = section->prev_section;

    free (section);

    ptr_config->sections = new_sections;
}

/*
 * Free a configuration file.
 */

void
config_file_free (struct t_config_file *config_file)
{
    struct t_config_file *new_config_files;

    if (!config_file)
        return;

    /* Free data. */
    while (config_file->sections)
    {
        config_file_section_free (config_file->sections);
    }
    free (config_file->name);
    free (config_file->filename);

    /* Remove configuration file from list. */
    if (last_config_file == config_file)
        last_config_file = config_file->prev_config;
    if (config_file->prev_config)
    {
        (config_file->prev_config)->next_config = config_file->next_config;
        new_config_files = config_files;
    }
    else
        new_config_files = config_file->next_config;

    if (config_file->next_config)
        (config_file->next_config)->prev_config = config_file->prev_config;

    /* Free data. */
    free (config_file->callback_update_data);
    free (config_file->callback_reload_data);

    free (config_file);

    config_files = new_config_files;
}

/*
 * Free all configuration files.
 */

void
config_file_free_all (void)
{
    while (config_files)
    {
        config_file_free (config_files);
    }
}

/*
 * Free all configuration files for a plugin.
 */

void
config_file_free_all_plugin (struct t_weechat_plugin *plugin)
{
    struct t_config_file *ptr_config, *next_config;

    ptr_config = config_files;
    while (ptr_config)
    {
        next_config = ptr_config->next_config;

        if (ptr_config->plugin == plugin)
            config_file_free (ptr_config);

        ptr_config = next_config;
    }
}

/*
 * Return hdata for structure t_config_file.
 */

struct t_hdata *
config_file_hdata_config_file_cb (const void *pointer, void *data,
                                  const char *hdata_name)
{
    struct t_hdata *hdata;

    /* Make C compiler happy. */
    (void) pointer;
    (void) data;

    hdata = hdata_new (NULL, hdata_name, "prev_config", "next_config",
                       0, 0, NULL, NULL);
    if (hdata)
    {
        HDATA_VAR(struct t_config_file, plugin, POINTER, 0, NULL, "plugin");
        HDATA_VAR(struct t_config_file, priority, INTEGER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_file, name, STRING, 0, NULL, NULL);
        HDATA_VAR(struct t_config_file, filename, STRING, 0, NULL, NULL);
        HDATA_VAR(struct t_config_file, file, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_file, version, INTEGER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_file, callback_reload, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_file, callback_reload_pointer, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_file, callback_reload_data, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_file, sections, POINTER, 0, NULL, "config_section");
        HDATA_VAR(struct t_config_file, last_section, POINTER, 0, NULL, "config_section");
        HDATA_VAR(struct t_config_file, prev_config, POINTER, 0, NULL, hdata_name);
        HDATA_VAR(struct t_config_file, next_config, POINTER, 0, NULL, hdata_name);
        HDATA_LIST(config_files, WEECHAT_HDATA_LIST_CHECK_POINTERS);
        HDATA_LIST(last_config_file, 0);
    }
    return hdata;
}

/*
 * Return hdata for structure t_config_section.
 */

struct t_hdata *
config_file_hdata_config_section_cb (const void *pointer, void *data,
                                     const char *hdata_name)
{
    struct t_hdata *hdata;

    /* Make C compiler happy. */
    (void) pointer;
    (void) data;

    hdata = hdata_new (NULL, hdata_name, "prev_section", "next_section",
                       0, 0, NULL, NULL);
    if (hdata)
    {
        HDATA_VAR(struct t_config_section, config_file, POINTER, 0, NULL, "config_file");
        HDATA_VAR(struct t_config_section, name, STRING, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, user_can_add_options, INTEGER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, user_can_delete_options, INTEGER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_read, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_read_pointer, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_read_data, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_write, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_write_pointer, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_write_data, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_write_default, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_write_default_pointer, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_write_default_data, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_create_option, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_create_option_pointer, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_create_option_data, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_delete_option, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_delete_option_pointer, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, callback_delete_option_data, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_section, options, POINTER, 0, NULL, "config_option");
        HDATA_VAR(struct t_config_section, last_option, POINTER, 0, NULL, "config_option");
        HDATA_VAR(struct t_config_section, prev_section, POINTER, 0, NULL, hdata_name);
        HDATA_VAR(struct t_config_section, next_section, POINTER, 0, NULL, hdata_name);
    }
    return hdata;
}

/*
 * Return hdata for structure t_config_option.
 */

struct t_hdata *
config_file_hdata_config_option_cb (const void *pointer, void *data,
                                    const char *hdata_name)
{
    struct t_hdata *hdata;

    /* Make C compiler happy. */
    (void) pointer;
    (void) data;

    hdata = hdata_new (NULL, hdata_name, "prev_option", "next_option",
                       0, 0, NULL, NULL);
    if (hdata)
    {
        HDATA_VAR(struct t_config_option, config_file, POINTER, 0, NULL, "config_file");
        HDATA_VAR(struct t_config_option, section, POINTER, 0, NULL, "config_section");
        HDATA_VAR(struct t_config_option, name, STRING, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, parent_name, STRING, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, type, INTEGER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, themable, INTEGER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, description, STRING, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, string_values, STRING, 0, "*,*", NULL);
        HDATA_VAR(struct t_config_option, min, INTEGER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, max, INTEGER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, default_value, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, value, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, null_value_allowed, INTEGER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, callback_check_value, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, callback_check_value_pointer, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, callback_check_value_data, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, callback_change, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, callback_change_pointer, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, callback_change_data, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, callback_delete, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, callback_delete_pointer, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, callback_delete_data, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, loaded, INTEGER, 0, NULL, NULL);
        HDATA_VAR(struct t_config_option, prev_option, POINTER, 0, NULL, hdata_name);
        HDATA_VAR(struct t_config_option, next_option, POINTER, 0, NULL, hdata_name);
    }
    return hdata;
}

/*
 * Add a configuration option in an infolist.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
config_file_add_option_to_infolist (struct t_infolist *infolist,
                                    struct t_config_file *config_file,
                                    struct t_config_section *section,
                                    struct t_config_option *option,
                                    const char *option_name)
{
    char *option_full_name, *value, *string_values;
    struct t_config_option *ptr_parent_option;
    struct t_infolist_item *ptr_item;
    int rc;

    rc = 1;

    option_full_name = config_file_option_full_name (option);
    if (!option_full_name)
        goto error;

    if (option_name && option_name[0]
        && (!string_match (option_full_name, option_name, 1)))
    {
        goto end;
    }

    ptr_item = infolist_new_item (infolist);
    if (!ptr_item)
        goto error;

    if (!infolist_new_var_string (ptr_item, "full_name", option_full_name))
        goto error;
    if (!infolist_new_var_string (ptr_item, "config_name", config_file->name))
        goto error;
    if (!infolist_new_var_string (ptr_item, "section_name", section->name))
        goto error;
    if (!infolist_new_var_string (ptr_item, "option_name", option->name))
        goto error;
    if (!infolist_new_var_string (ptr_item, "parent_name", option->parent_name))
        goto error;
    if (!infolist_new_var_string (ptr_item, "description", option->description))
        goto error;
    if (!infolist_new_var_string (ptr_item, "description_nls",
                                  (option->description
                                   && option->description[0]) ?
                                  _(option->description) : ""))
    {
        goto error;
    }
    string_values = string_rebuild_split_string (
        (const char **)option->string_values, "|", 0, -1);
    if (!infolist_new_var_string (ptr_item, "string_values", string_values))
    {
        free (string_values);
        goto error;
    }
    free (string_values);
    if (!infolist_new_var_integer (ptr_item, "min", option->min))
        goto error;
    if (!infolist_new_var_integer (ptr_item, "max", option->max))
        goto error;
    if (!infolist_new_var_integer (ptr_item, "null_value_allowed",
                                   option->null_value_allowed))
    {
        goto error;
    }
    if (!infolist_new_var_integer (ptr_item, "value_is_null",
                                   (option->value) ? 0 : 1))
    {
        goto error;
    }
    if (!infolist_new_var_integer (ptr_item,
                                   "default_value_is_null",
                                   (option->default_value) ?
                                   0 : 1))
    {
        goto error;
    }
    if (!infolist_new_var_string (ptr_item, "type",
                                  config_option_type_string[option->type]))
    {
        goto error;
    }
    if (!infolist_new_var_integer (ptr_item, "themable", option->themable))
        goto error;
    if (option->value)
    {
        value = config_file_option_value_to_string (option, 0, 0, 0);
        if (!value)
            goto error;
        if (!infolist_new_var_string (ptr_item, "value", value))
        {
            free (value);
            goto error;
        }
        free (value);
    }
    if (option->default_value)
    {
        value = config_file_option_value_to_string (option, 1, 0, 0);
        if (!value)
            goto error;
        if (!infolist_new_var_string (ptr_item, "default_value", value))
        {
            free (value);
            goto error;
        }
        free (value);
    }
    if (option->parent_name)
    {
        config_file_search_with_string (option->parent_name,
                                        NULL, NULL, &ptr_parent_option, NULL);
        if (ptr_parent_option && ptr_parent_option->value)
        {
            value = config_file_option_value_to_string (ptr_parent_option,
                                                        0, 0, 0);
            if (!value)
                goto error;
            if (!infolist_new_var_string (ptr_item, "parent_value", value))
            {
                free (value);
                goto error;
            }
            free (value);
        }
    }

    goto end;

error:
    rc = 0;

end:
    free (option_full_name);
    return rc;
}

/*
 * Add configuration options in an infolist.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
config_file_add_to_infolist (struct t_infolist *infolist,
                             const char *option_name)
{
    struct t_config_file *ptr_config;
    struct t_config_section *ptr_section;
    struct t_config_option *ptr_option;

    if (!infolist)
        return 0;

    for (ptr_config = config_files; ptr_config;
         ptr_config = ptr_config->next_config)
    {
        for (ptr_section = ptr_config->sections; ptr_section;
             ptr_section = ptr_section->next_section)
        {
            for (ptr_option = ptr_section->options; ptr_option;
                 ptr_option = ptr_option->next_option)
            {
                if (!config_file_add_option_to_infolist (infolist,
                                                         ptr_config,
                                                         ptr_section,
                                                         ptr_option,
                                                         option_name))
                {
                    return 0;
                }
            }
        }
    }

    return 1;
}

/*
 * Print configuration file in WeeChat log file (usually for crash dump).
 */

void
config_file_print_log (void)
{
    struct t_config_file *ptr_config_file;
    struct t_config_section *ptr_section;
    struct t_config_option *ptr_option;

    for (ptr_config_file = config_files; ptr_config_file;
         ptr_config_file = ptr_config_file->next_config)
    {
        log_printf ("");
        log_printf ("[config (addr:%p)]", ptr_config_file);
        log_printf ("  plugin . . . . . . . . : %p ('%s')",
                    ptr_config_file->plugin,
                    plugin_get_name (ptr_config_file->plugin));
        log_printf ("  priority . . . . . . . : %d", ptr_config_file->priority);
        log_printf ("  name . . . . . . . . . : '%s'", ptr_config_file->name);
        log_printf ("  filename . . . . . . . : '%s'", ptr_config_file->filename);
        log_printf ("  file . . . . . . . . . : %p", ptr_config_file->file);
        log_printf ("  callback_reload. . . . : %p", ptr_config_file->callback_reload);
        log_printf ("  callback_reload_pointer: %p", ptr_config_file->callback_reload_pointer);
        log_printf ("  callback_reload_data . : %p", ptr_config_file->callback_reload_data);
        log_printf ("  sections . . . . . . . : %p", ptr_config_file->sections);
        log_printf ("  last_section . . . . . : %p", ptr_config_file->last_section);
        log_printf ("  prev_config. . . . . . : %p", ptr_config_file->prev_config);
        log_printf ("  next_config. . . . . . : %p", ptr_config_file->next_config);

        for (ptr_section = ptr_config_file->sections; ptr_section;
             ptr_section = ptr_section->next_section)
        {
            log_printf ("");
            log_printf ("    [section (addr:%p)]", ptr_section);
            log_printf ("      config_file . . . . . . . . . : %p", ptr_section->config_file);
            log_printf ("      name. . . . . . . . . . . . . : '%s'", ptr_section->name);
            log_printf ("      callback_read . . . . . . . . : %p", ptr_section->callback_read);
            log_printf ("      callback_read_pointer . . . . : %p", ptr_section->callback_read_pointer);
            log_printf ("      callback_read_data. . . . . . : %p", ptr_section->callback_read_data);
            log_printf ("      callback_write. . . . . . . . : %p", ptr_section->callback_write);
            log_printf ("      callback_write_pointer. . . . : %p", ptr_section->callback_write_pointer);
            log_printf ("      callback_write_data . . . . . : %p", ptr_section->callback_write_data);
            log_printf ("      callback_write_default. . . . : %p", ptr_section->callback_write_default);
            log_printf ("      callback_write_default_pointer: %p", ptr_section->callback_write_default_pointer);
            log_printf ("      callback_write_default_data . : %p", ptr_section->callback_write_default_data);
            log_printf ("      callback_create_option. . . . : %p", ptr_section->callback_create_option);
            log_printf ("      callback_create_option_pointer: %p", ptr_section->callback_create_option_pointer);
            log_printf ("      callback_create_option_data . : %p", ptr_section->callback_create_option_data);
            log_printf ("      callback_delete_option. . . . : %p", ptr_section->callback_delete_option);
            log_printf ("      callback_delete_option_pointer: %p", ptr_section->callback_delete_option_pointer);
            log_printf ("      callback_delete_option_data . : %p", ptr_section->callback_delete_option_data);
            log_printf ("      options . . . . . . . . . . . : %p", ptr_section->options);
            log_printf ("      last_option . . . . . . . . . : %p", ptr_section->last_option);
            log_printf ("      prev_section. . . . . . . . . : %p", ptr_section->prev_section);
            log_printf ("      next_section. . . . . . . . . : %p", ptr_section->next_section);

            for (ptr_option = ptr_section->options; ptr_option;
                 ptr_option = ptr_option->next_option)
            {
                log_printf ("");
                log_printf ("      [option (addr:%p)]", ptr_option);
                log_printf ("        config_file. . . . . . . . . : %p", ptr_option->config_file);
                log_printf ("        section. . . . . . . . . . . : %p", ptr_option->section);
                log_printf ("        name . . . . . . . . . . . . : '%s'", ptr_option->name);
                log_printf ("        parent_name. . . . . . . . . : '%s'", ptr_option->parent_name);
                log_printf ("        type . . . . . . . . . . . . : %d", ptr_option->type);
                log_printf ("        themable . . . . . . . . . . : %d", ptr_option->themable);
                log_printf ("        description. . . . . . . . . : '%s'", ptr_option->description);
                log_printf ("        string_values. . . . . . . . : %p", ptr_option->string_values);
                log_printf ("        min. . . . . . . . . . . . . : %d", ptr_option->min);
                log_printf ("        max. . . . . . . . . . . . . : %d", ptr_option->max);
                switch (ptr_option->type)
                {
                    case CONFIG_OPTION_TYPE_BOOLEAN:
                        log_printf ("        default value. . . . . . . . : %s",
                                    (ptr_option->default_value) ?
                                    ((CONFIG_BOOLEAN_DEFAULT(ptr_option) == CONFIG_BOOLEAN_TRUE) ?
                                     "on" : "off") : "null");
                        log_printf ("        value (boolean). . . . . . . : %s",
                                    (ptr_option->value) ?
                                    ((CONFIG_BOOLEAN(ptr_option) == CONFIG_BOOLEAN_TRUE) ?
                                     "on" : "off") : "null");
                        break;
                    case CONFIG_OPTION_TYPE_INTEGER:
                        if (ptr_option->default_value)
                            log_printf ("        default value. . . . . . . . : %d",
                                        CONFIG_INTEGER_DEFAULT(ptr_option));
                        else
                            log_printf ("        default value. . . . . . . . : null");
                        if (ptr_option->value)
                            log_printf ("        value (integer). . . . . . . : %d",
                                        CONFIG_INTEGER(ptr_option));
                        else
                            log_printf ("        value (integer). . . . . . . : null");
                        break;
                    case CONFIG_OPTION_TYPE_STRING:
                        if (ptr_option->default_value)
                            log_printf ("        default value. . . . . . . . : '%s'",
                                        CONFIG_STRING_DEFAULT(ptr_option));
                        else
                            log_printf ("        default value. . . . . . . . : null");
                        if (ptr_option->value)
                            log_printf ("        value (string) . . . . . . . : '%s'",
                                        CONFIG_STRING(ptr_option));
                        else
                            log_printf ("        value (string) . . . . . . . : null");
                        break;
                    case CONFIG_OPTION_TYPE_COLOR:
                        if (ptr_option->default_value)
                            log_printf ("        default value. . . . . . . . : %d ('%s')",
                                        CONFIG_COLOR_DEFAULT(ptr_option),
                                        gui_color_get_name (CONFIG_COLOR_DEFAULT(ptr_option)));
                        else
                            log_printf ("        default value. . . . . . . . : null");
                        if (ptr_option->value)
                            log_printf ("        value (color). . . . . . . . : %d ('%s')",
                                        CONFIG_COLOR(ptr_option),
                                        gui_color_get_name (CONFIG_COLOR(ptr_option)));
                        else
                            log_printf ("        value (color). . . . . . . . : null");
                        break;
                    case CONFIG_OPTION_TYPE_ENUM:
                        log_printf ("        default value. . . . . . . . : '%s'",
                                    (ptr_option->default_value) ?
                                    ptr_option->string_values[CONFIG_ENUM_DEFAULT(ptr_option)] : "null");
                        log_printf ("        value (integer/str). . . . . : '%s'",
                                    (ptr_option->value) ?
                                    ptr_option->string_values[CONFIG_ENUM(ptr_option)] : "null");
                        break;
                    case CONFIG_NUM_OPTION_TYPES:
                        break;
                }
                log_printf ("        null_value_allowed . . . . . : %d", ptr_option->null_value_allowed);
                log_printf ("        callback_check_value . . . . : %p", ptr_option->callback_check_value);
                log_printf ("        callback_check_value_pointer : %p", ptr_option->callback_check_value_pointer);
                log_printf ("        callback_check_value_data. . : %p", ptr_option->callback_check_value_data);
                log_printf ("        callback_change. . . . . . . : %p", ptr_option->callback_change);
                log_printf ("        callback_change_pointer. . . : %p", ptr_option->callback_change_pointer);
                log_printf ("        callback_change_data . . . . : %p", ptr_option->callback_change_data);
                log_printf ("        callback_delete. . . . . . . : %p", ptr_option->callback_delete);
                log_printf ("        callback_delete_pointer. . . : %p", ptr_option->callback_delete_pointer);
                log_printf ("        callback_delete_data . . . . : %p", ptr_option->callback_delete_data);
                log_printf ("        loaded . . . . . . . . . . . : %d", ptr_option->loaded);
                log_printf ("        prev_option. . . . . . . . . : %p", ptr_option->prev_option);
                log_printf ("        next_option. . . . . . . . . : %p", ptr_option->next_option);
            }
        }
    }
}
