6 n__config__file_8c
gullradriel edited this page 2026-10-09 00:55:18 +02:00

src/n_config_file.c

Config function. More...

Functions

Name
void free_no_null(void * ptr)
local free for config entries
void destroy_config_file_section(void * ptr)
Destroy a config file section.

Detailed Description

Config function.

Author: Castagnier Mickael

Version: 1.0

Date: 24/03/05

Functions Documentation

function free_no_null

void free_no_null(
    void * ptr
)

local free for config entries

Parameters:

  • ptr the ptr to free

function destroy_config_file_section

void destroy_config_file_section(
    void * ptr
)

Destroy a config file section.

Parameters:

  • ptr A pointer to a config file section

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdio.h>
#include <errno.h>

#include "nilorea/n_common.h"
#include "nilorea/n_config_file.h"
#include "nilorea/n_pcre.h"

void free_no_null(void* ptr) {
    char* strptr = ptr;
    if (strptr) {
        free(strptr);
    }
    return;
}

void destroy_config_file_section(void* ptr) {
    CONFIG_FILE_SECTION* section = (CONFIG_FILE_SECTION*)ptr;
    if (!section)
        return;
    Free(section->section_name);
    destroy_ht(&section->entries);
    Free(section);
} /* destroy_config_file_section(...) */

CONFIG_FILE* load_config_file(char* filename, int* errors) {
    CONFIG_FILE* cfg_file = NULL;
    CONFIG_FILE_SECTION* section = NULL;
    char buffer[MAX_CONFIG_LINE_LEN] = "";
    char** split_result = NULL;
    N_PCRE* npcre = npcre_new("(.*?=)(.*)", 0);
    int error = 0;
    FILE* in = NULL;

    __n_assert(filename, return NULL);
    int fd = open(filename, O_RDONLY);
    error = errno;

    if (fd == -1) {
        n_log(LOG_ERR, "Unable to open %s: %s", _str(filename), strerror(error));
        (*errors)++;
        npcre_delete(&npcre);
        return NULL;
    }

    in = fdopen(fd, "r");
    error = errno;
    if (!in) {
        n_log(LOG_ERR, "fdopen failed for %s (fd=%d): %s", _str(filename), fd, strerror(error));
        close(fd);
        (*errors)++;
        npcre_delete(&npcre);
        return NULL;
    }

    Malloc(cfg_file, CONFIG_FILE, 1);
    if (!cfg_file) {
        fclose(in);
        npcre_delete(&npcre);
        return NULL;
    }
    cfg_file->sections = new_generic_list(MAX_LIST_ITEMS);
    if (!cfg_file->sections) {
        Free(cfg_file);
        fclose(in);
        npcre_delete(&npcre);
        return NULL;
    }
    cfg_file->filename = strdup(filename);
    if (!cfg_file->filename) {
        n_log(LOG_ERR, "couldn't strdup( %s )", _str(filename));
        (*errors)++;
        list_destroy(&cfg_file->sections);
        Free(cfg_file);
        fclose(in);
        npcre_delete(&npcre);
        return NULL;
    }
    /* adding default section */
    CONFIG_FILE_SECTION* default_section = NULL;
    Malloc(default_section, CONFIG_FILE_SECTION, 1);
    if (!default_section) {
        n_log(LOG_ERR, "couldn't allocate default_section");
        (*errors)++;
        list_destroy(&cfg_file->sections);
        Free(cfg_file->filename);
        Free(cfg_file);
        fclose(in);
        npcre_delete(&npcre);
        return NULL;
    }

    default_section->section_name = strdup("__DEFAULT__");
    if (!default_section->section_name) {
        n_log(LOG_ERR, "couldn't allocate default_section name");
        (*errors)++;
        list_destroy(&cfg_file->sections);
        Free(cfg_file->filename);
        Free(default_section);
        Free(cfg_file);
        fclose(in);
        npcre_delete(&npcre);
        return NULL;
    }

    default_section->entries = new_ht(CONFIG_SECTION_HASH_TABLE_LEN);
    if (!default_section->entries) {
        n_log(LOG_ERR, "error creating hash table of size %d for default_section", CONFIG_SECTION_HASH_TABLE_LEN);
        (*errors)++;
        list_destroy(&cfg_file->sections);
        Free(cfg_file->filename);
        Free(default_section->section_name);
        Free(default_section);
        Free(cfg_file);
        fclose(in);
        npcre_delete(&npcre);
        return NULL;
    }
    list_push(cfg_file->sections, default_section, &destroy_config_file_section);

    size_t line_number = 0;
    while (fgets(buffer, MAX_CONFIG_LINE_LEN, in)) {
        NSTRBYTE end = 0;

        line_number++;

        char* bufptr = trim_nocopy(buffer);
        if (strlen(bufptr) == 0) {
            continue;
        }

        switch (bufptr[0]) {
            /* comment */
            case '#':
            case ';':
                continue;
            /* new section */
            case '[':
                end = 0;
                /* go to begin of section name */
                if (skipu(bufptr, ']', &end, 1) != TRUE) {
                    n_log(LOG_ERR, "coulnd't find end of section at line %zu of %s (string:%s)", line_number, filename, buffer);
                    (*errors)++;
                    continue;
                }
                /* keep only the name */
                bufptr++;
                bufptr[end - 1] = '\0';
                bufptr = trim_nocopy(bufptr);
                if (strlen(bufptr) == 0) {
                    n_log(LOG_ERR, "section without name at line %zu", line_number);
                    (*errors)++;
                    continue;
                }

                Malloc(section, CONFIG_FILE_SECTION, 1);
                __n_assert(section, break);

                section->section_name = strdup(bufptr);
                if (!section->section_name) {
                    n_log(LOG_ERR, "couldn't duplicate %s", bufptr);
                    (*errors)++;
                    Free(section);
                    continue;
                }

                section->entries = new_ht(CONFIG_SECTION_HASH_TABLE_LEN);
                if (!section->entries) {
                    n_log(LOG_ERR, "error creating [%s] hash table of size %d", bufptr, CONFIG_SECTION_HASH_TABLE_LEN);
                    (*errors)++;
                    Free(section->section_name);
                    Free(section);
                    continue;
                }
                list_push(cfg_file->sections, section, &destroy_config_file_section);
                continue;

            /* if it's not a comment, not a section, not empty, then it's an entry ! */
            default:
                if (!section) {
                    section = default_section;
                    n_log(LOG_DEBUG, "No section, setting __DEFAULT__ starting line %zu of %s (string:%s)", line_number, filename, bufptr);
                }

                if (npcre_match_capture(bufptr, npcre) == TRUE) {
                    Malloc(split_result, char*, 3);
                    if (!split_result) {
                        n_log(LOG_ERR, "allocation failed at line %zu of %s", line_number, filename);
                        (*errors)++;
                        continue;
                    }
                    split_result[2] = NULL;
                    split_result[0] = str_replace((char*)npcre->match_list[1], "=", " ");
                    split_result[1] = strdup((char*)npcre->match_list[2]);
                } else {
                    split_result = NULL;
                }

                if (!split_result) {
                    n_log(LOG_ERR, "couldn't find entry separator '=' at line %zu of %s (string:%s)", line_number, filename, bufptr);
                    (*errors)++;
                    continue;
                }
                if (split_count(split_result) < 2) {
                    n_log(LOG_ERR, "Invalid split count at line %zu of %s (string:%s, count:%d)", line_number, filename, bufptr, split_count(split_result));
                    (*errors)++;
                    free_split_result(&split_result);
                    continue;
                }
                if (strlen(trim_nocopy(split_result[0])) == 0) {
                    free_split_result(&split_result);
                    n_log(LOG_ERR, "couldn't find entry name at line %zu of %s (string:%s)", line_number, filename, buffer);
                    (*errors)++;
                    continue;
                }

                /* value pointer */
                const char* valptr = NULL;
                /* flags */
                int closing_delimiter_found = 0;
                int opening_delimiter_found = 0;
                /* initialize with empty delimiter */
                char delimiter = '\0';

                /* If the trimmed value have a zero length it means we only had spaces or tabs
                   In that particular case we set the val to NULL */
                if (strlen(trim_nocopy(split_result[1])) == 0) {
                    Free(split_result[1]);
                    valptr = NULL;
                    delimiter = '\0';
                } else /* we have a value, or something between delimiters */
                {
                    valptr = trim_nocopy(split_result[1]);
                    size_t it = strlen(valptr);

                    /* get value right boundary */
                    while (1) {
                        /* if comments tags replace them with end of string */
                        if (split_result[1][it] == ';' || split_result[1][it] == '#') {
                            split_result[1][it] = '\0';
                        } else {
                            /* we found a delimiter on the right */
                            if (split_result[1][it] == '"' || split_result[1][it] == '\'') {
                                delimiter = split_result[1][it];
                                split_result[1][it] = '\0';
                                closing_delimiter_found = 1;
                                break;
                            }
                        }
                        if (it == 0)
                            break;
                        else
                            it--;
                    }

                    if (strlen(trim_nocopy(split_result[1])) == 0) {
                        Free(split_result[1]);
                        valptr = NULL;
                        delimiter = '\0';
                    }

                    if (split_result[1]) {
                        /* get value left boundary */
                        it = 0;
                        while (split_result[1][it] != '\0') {
                            if (split_result[1][it] == ';' || split_result[1][it] == '#') {
                                split_result[1][it] = '\0';
                            } else {
                                if (delimiter != '\0' && split_result[1][it] == delimiter) {
                                    opening_delimiter_found = 1;
                                    it++;
                                    valptr = split_result[1] + it;
                                    break;
                                }
                                it++;
                            }
                        }
                        if (strlen(trim_nocopy(split_result[1])) == 0) {
                            Free(split_result[1]);
                            valptr = NULL;
                            delimiter = '\0';
                        }
                    }
                }
                if (delimiter != '\0' && ((opening_delimiter_found && !closing_delimiter_found) || (!opening_delimiter_found && closing_delimiter_found))) {
                    free_split_result(&split_result);
                    n_log(LOG_ERR, "Only one delimiter found at line %zu of %s (string:%s)", line_number, filename, buffer);
                    (*errors)++;
                    continue;
                }

                char* result = NULL;
                N_STR* entry_key = NULL;
                size_t nb_entry_in_sections = 0;
                while (TRUE) {
                    nstrprintf(entry_key, "%s%zu", trim_nocopy(split_result[0]), nb_entry_in_sections);
                    if (ht_get_ptr(section->entries, _nstr(entry_key), (void**)&result) == FALSE) {
                        break;
                    }
                    nb_entry_in_sections++;
                    free_nstr(&entry_key);
                }
                char* strptr = NULL;
                if (valptr) {
                    strptr = strdup(valptr);
                }
                ht_put_ptr(section->entries, _nstr(entry_key), strptr, &free_no_null, NULL);
                free_nstr(&entry_key);
                free_split_result(&split_result);
                continue;
        }
    }
    npcre_delete(&npcre);
    if (!feof(in)) {
        n_log(LOG_ERR, "couldn't read EOF for %s", filename);
        (*errors)++;
    }
    fclose(in);

    return cfg_file;

} /*load_config_file */

int write_config_file(CONFIG_FILE* cfg_file, char* filename) {
    __n_assert(cfg_file, return FALSE);
    __n_assert(filename, return FALSE);

    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0600);  // Secure permissions
    if (fd == -1) {
        int error = errno;
        n_log(LOG_ERR, "Unable to open '%s' for writing (0600): %s", _str(filename), strerror(error));
        return FALSE;
    }

    FILE* out = NULL;
    out = fdopen(fd, "w");
    if (!out) {
        int error = errno;
        n_log(LOG_ERR, "fdopen failed for '%s' (fd=%d): %s", _str(filename), fd, strerror(error));
        close(fd);
        return FALSE;
    }

    char* last_section = NULL;
    // cppcheck-suppress variableScope
    const char *section = NULL, *key = NULL, *val = NULL;

    config_foreach(cfg_file, section, key, val) {
        /* write section name */
        if (!last_section || strcmp(last_section, section) != 0) {
            fprintf(out, "[%s]\n", section);
            FreeNoLog(last_section);
            last_section = strdup(section);
        }
        fprintf(out, "%s=%s\n", key, val);
    }
    config_endfor;
    fclose(out);
    FreeNoLog(last_section);

    return TRUE;
} /* write_config_file */

size_t get_nb_config_file_sections(CONFIG_FILE* cfg_file, const char* section_name) {
    __n_assert(cfg_file, return 0);
    __n_assert(cfg_file->sections, return 0);

    size_t nb_sections = 0;
    list_foreach(listnode, cfg_file->sections) {
        const CONFIG_FILE_SECTION* section = (const CONFIG_FILE_SECTION*)listnode->ptr;
        if (section_name) {
            if (!strcmp(section->section_name, section_name)) {
                nb_sections++;
            }
        } else {
            nb_sections++;
        }
    }
    return nb_sections;
} /* get_nb_config_file_sections(...) */

size_t get_nb_config_file_sections_entries(CONFIG_FILE* cfg_file, const char* section_name, size_t section_position, char* entry) {
    __n_assert(cfg_file, return 0);
    __n_assert(cfg_file->sections, return 0);
    __n_assert(section_name, return 0);
    __n_assert(entry, return 0);

    size_t nb_sections = 0;
    size_t nb_entry_in_sections = 0;
    list_foreach(listnode, cfg_file->sections) {
        CONFIG_FILE_SECTION* section = (CONFIG_FILE_SECTION*)listnode->ptr;
        if (!strcmp(section->section_name, section_name)) {
            if (nb_sections == section_position) {
                char* result = NULL;
                N_STR* entry_key = NULL;
                while (TRUE) {
                    nstrprintf(entry_key, "%s%zu", entry, nb_entry_in_sections);
                    if (ht_get_ptr(section->entries, _nstr(entry_key), (void**)&result) == FALSE) {
                        free_nstr(&entry_key);
                        break;
                    }
                    nb_entry_in_sections++;
                    free_nstr(&entry_key);
                }
                break;
            }
            nb_sections++;
        }
    }
    return nb_entry_in_sections;
} /* get_nb_config_file_sections_entries(...) */

char* get_config_section_value(CONFIG_FILE* cfg_file, char* section_name, size_t section_position, char* entry, size_t entry_position) {
    __n_assert(cfg_file, return NULL);
    __n_assert(cfg_file->sections, return NULL);
    __n_assert(section_name, return NULL);
    __n_assert(entry, return NULL);

    if (section_position >= cfg_file->sections->nb_items) {
        n_log(LOG_DEBUG, "section_position (%zu) is higher than the number of item in list (%zu)", section_position, cfg_file->sections->nb_items);
        return NULL;
    }

    size_t section_position_it = 0;
    HASH_TABLE* entries = NULL;
    list_foreach(listnode, cfg_file->sections) {
        CONFIG_FILE_SECTION* section = (CONFIG_FILE_SECTION*)listnode->ptr;
        if (!strcmp(section->section_name, section_name)) {
            if (section_position_it == section_position) {
                entries = section->entries;
                break;
            }
            section_position_it++;
        }
    }

    if (!entries) {
        n_log(LOG_ERR, "section %s not found", section_name);
        return NULL;
    }

    char* result = NULL;
    N_STR* entry_key = NULL;
    nstrprintf(entry_key, "%s%zu", entry, entry_position);
    if (ht_get_ptr(entries, _nstr(entry_key), (void**)&result) == FALSE) {
        /* n_log( LOG_DEBUG , "ht_get_ptr( %p , trim_nocopy( %s ) , (void **)&result ) returned FALSE !" , entries , _nstr( entry_key ) ); */
        free_nstr(&entry_key);
        return NULL;
    }
    free_nstr(&entry_key);

    return result;
} /* get_config_section_value */

int destroy_config_file(CONFIG_FILE** cfg_file) {
    __n_assert(cfg_file && (*cfg_file), return FALSE);

    Free((*cfg_file)->filename);

    if ((*cfg_file)->sections) {
        list_destroy(&(*cfg_file)->sections);
    }

    Free((*cfg_file));

    return TRUE;
} /* destroy_config_file */