/* * config.c - Config file parsing * Copyright (C) 2024 Alexander Rosenberg * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. See the LICENSE file for more information. */ #include "config.h" #include "util.h" #include #include #include #include #include #include static int unknown_key_callback(struct figpar_config *opt, uint32_t line, char *directive, char *value) { if (GLOBAL_OPTS.strict_config) { errx(1, "line %" PRIu32 ": unknown configuration option: \"%s\"", line, directive); } else { warnx("line %" PRIu32 ": unknown configuration option: \"%s\"", line, directive); } return 0; } static int parse_int_callback(struct figpar_config *opt, uint32_t line, char *directive, char *value) { char last_char; if (sscanf(value, "%" SCNi32 " %c", &opt->value.num, &last_char) < 1 || (last_char && !isdigit(last_char))) { warnx("line %" PRIu32 ": not a valid number \"%s\"", line, value); return 1; } LOG_VERBOSE("Loaded config int option: %s = %" PRIi32 "\n", directive, opt->value.num); opt->type = FIGPAR_TYPE_INT; return 0; } static int parse_uint_callback(struct figpar_config *opt, uint32_t line, char *directive, char *value) { char last_char; if (sscanf(value, "%" SCNu32 " %c", &opt->value.u_num, &last_char) < 1 || (last_char && !isdigit(last_char))) { warnx("line %" PRIu32 ": not a valid unsigned number \"%s\"", line, value); return 1; } LOG_VERBOSE("Loaded config uint option: %s = %" PRIu32 "\n", directive, opt->value.u_num); opt->type = FIGPAR_TYPE_UINT; return 0; } static int parse_temp_unit_callback(struct figpar_config *opt, uint32_t line, char *directive, char *value) { if (strcasecmp(value, "c") == 0) { opt->value.num = TEMP_UNIT_C; } else if (strcasecmp(value, "f") == 0) { opt->value.num = TEMP_UNIT_F; } else { warnx("not a valid temperature unit: \"%s\"", value); return 1; } LOG_VERBOSE("Loaded temperature unit config option: %s = %c\n", directive, toupper(value[0])); opt->type = FIGPAR_TYPE_UINT; return 0; } #define CONFIG_UINT_ARR_TYPE FIGPAR_TYPE_DATA1 struct UIntArr { uint32_t *arr; size_t size; }; static int parse_uint_arr_callback(struct figpar_config *opt, uint32_t line, char *directive, char *value) { struct UIntArr *arr = malloc_checked(sizeof(struct UIntArr)); arr->size = 0; arr->arr = NULL; uint32_t num; char last_char = 1; int jump_len; while (last_char && sscanf(value, "%" SCNu32 " %c%n", &num, &last_char, &jump_len) >= 1) { if (last_char && !isdigit(last_char)) { warnx("line %" PRIu32 ": not a valid number array \"%s\"", line, value); free(arr->arr); free(arr); return 1; } arr->arr = realloc_checked(arr->arr, sizeof(uint32_t) * ++arr->size); arr->arr[arr->size - 1] = num; value += jump_len - 1; // -1 to add back the first digit } opt->type = CONFIG_UINT_ARR_TYPE; opt->value.data = arr; if (GLOBAL_OPTS.verbose) { fprintf(stderr, "Loaded config uint array option: %s = ", directive); for (size_t i = 0; i < arr->size; ++i) { fprintf(stderr, "%" PRIu32, arr->arr[i]); if (i < arr->size - 1) { fputc(' ', stderr); } } fputc('\n', stderr); } return 0; } static int parse_str_callback(struct figpar_config *opt, uint32_t line, char *directive, char *value) { free(opt->value.str); if (!value[0]) { opt->type = FIGPAR_TYPE_STR; opt->value.str = NULL; } else { opt->type = FIGPAR_TYPE_STR; opt->value.str = strdup_checked(value); LOG_VERBOSE("Loaded config string option: %s = %s\n", directive, value); } return 0; } static char *steal_opt_if_set(char *str) { if (str && !*str) { free(str); return NULL; } return str; } #define REQUIRE_KEY(ind, name) if (entries[ind].type == FIGPAR_TYPE_NONE) {\ errx(1, "%s must be specified", # name);\ } static void set_options_from_entries(struct figpar_config *entries, size_t nentries) { entries[0].type = FIGPAR_TYPE_NONE; GLOBAL_OPTS.gpio_path = entries[0].value.str; LOG_VERBOSE("Using gpio_path: \"%s\"\n", GLOBAL_OPTS.gpio_path); REQUIRE_KEY(1, rs_pin); GLOBAL_OPTS.rs_pin = entries[1].value.u_num; REQUIRE_KEY(2, rw_pin); GLOBAL_OPTS.rw_pin = entries[2].value.u_num; REQUIRE_KEY(3, en_pin); GLOBAL_OPTS.en_pin = entries[3].value.u_num; REQUIRE_KEY(4, data_pins); struct UIntArr *arr = entries[4].value.data; if (arr->size != 8) { errx(1, "data_pins must be an array of 8 uints"); } memcpy(GLOBAL_OPTS.data_pins, arr->arr, sizeof(uint32_t) * 8); entries[5].type = FIGPAR_TYPE_NONE; GLOBAL_OPTS.temp_key = steal_opt_if_set(entries[5].value.str); LOG_VERBOSE("Using temp_key: \"%s\"\n", GLOBAL_OPTS.temp_key); entries[6].type = FIGPAR_TYPE_NONE; GLOBAL_OPTS.humid_key = steal_opt_if_set(entries[6].value.str); LOG_VERBOSE("Using humid_key: \"%s\"\n", GLOBAL_OPTS.humid_key); entries[7].type = FIGPAR_TYPE_NONE; GLOBAL_OPTS.fail_key = steal_opt_if_set(entries[7].value.str); LOG_VERBOSE("Using fail_key: \"%s\"\n", GLOBAL_OPTS.fail_key); GLOBAL_OPTS.fail_limit = entries[8].value.u_num; entries[9].type = FIGPAR_TYPE_NONE; GLOBAL_OPTS.database_location = entries[9].value.str; LOG_VERBOSE("Using database_location: \"%s\"\n", GLOBAL_OPTS.database_location); GLOBAL_OPTS.refresh_time = entries[10].value.u_num; REQUIRE_KEY(11, sel_pin); GLOBAL_OPTS.sel_pin = entries[11].value.u_num; REQUIRE_KEY(12, up_pin); GLOBAL_OPTS.up_pin = entries[12].value.u_num; REQUIRE_KEY(13, down_pin); GLOBAL_OPTS.down_pin = entries[13].value.u_num; REQUIRE_KEY(14, back_pin); GLOBAL_OPTS.back_pin = entries[14].value.u_num; GLOBAL_OPTS.temp_unit = entries[15].value.num; GLOBAL_OPTS.bl_pin = entries[16].value.num; entries[17].type = FIGPAR_TYPE_NONE; GLOBAL_OPTS.export_file_name = steal_opt_if_set(entries[17].value.str); LOG_VERBOSE("Using export_file_name: \"%s\"\n", GLOBAL_OPTS.export_file_name); entries[18].type = FIGPAR_TYPE_NONE; GLOBAL_OPTS.timezone = steal_opt_if_set(entries[18].value.str); LOG_VERBOSE("Using timezone: \"%s\"\n", GLOBAL_OPTS.timezone); } static char *strdup_default_opt(const char *def) { if (!*def) { return NULL; } return strdup_checked(def); } void parse_config_file(const char *path) { LOG_VERBOSE("Loading config file: \"%s\"\n", path); struct figpar_config entries[] = { { .directive = "gpio_file", .type = FIGPAR_TYPE_STR, .action = parse_str_callback, .value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_GPIO_DEVICE))}, }, { .directive = "rs_pin", .type = FIGPAR_TYPE_NONE, .action = parse_uint_callback, }, { .directive = "rw_pin", .type = FIGPAR_TYPE_NONE, .action = parse_uint_callback, }, { .directive = "en_pin", .type = FIGPAR_TYPE_NONE, .action = parse_uint_callback, }, { .directive = "data_pins", .type = FIGPAR_TYPE_NONE, .action = parse_uint_arr_callback, }, { .directive = "temp_key", .type = FIGPAR_TYPE_STR, .action = parse_str_callback, .value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_TEMP_KEY))}, }, { .directive = "humid_key", .type = FIGPAR_TYPE_STR, .action = parse_str_callback, .value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_HUMID_KEY))}, }, { .directive = "fail_key", .type = FIGPAR_TYPE_STR, .action = parse_str_callback, .value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_FAIL_KEY))}, }, { .directive = "fail_limit", .type = FIGPAR_TYPE_UINT, .action = parse_uint_callback, .value = {.u_num = DEFAULT_FAIL_LIMIT}, }, { .directive = "database_location", .type = FIGPAR_TYPE_STR, .action = parse_str_callback, .value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_DATABASE_LOCATION))}, }, { .directive = "refresh_time", .type = FIGPAR_TYPE_UINT, .action = parse_uint_callback, .value = {.u_num = DEFAULT_REFRESH_TIME}, }, { .directive = "sel_pin", .type = FIGPAR_TYPE_NONE, .action = parse_uint_callback, }, { .directive = "up_pin", .type = FIGPAR_TYPE_NONE, .action = parse_uint_callback, }, { .directive = "down_pin", .type = FIGPAR_TYPE_NONE, .action = parse_uint_callback, }, { .directive = "back_pin", .type = FIGPAR_TYPE_NONE, .action = parse_uint_callback, }, { .directive = "temp_unit", .type = FIGPAR_TYPE_INT, .action = parse_temp_unit_callback, .value = {.num = STRINGIFY(DEFAULT_TEMP_UNIT)[0]}, }, { .directive = "bl_pin", .type = FIGPAR_TYPE_INT, .action = parse_int_callback, .value = {.num = -1}, }, { .directive = "export_file_name", .type = FIGPAR_TYPE_STR, .action = parse_str_callback, .value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_EXPORT_FILE_NAME))}, }, { .directive = "timezone", .type = FIGPAR_TYPE_STR, .action = parse_str_callback, .value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_TIMEZONE))}, }, { .directive = NULL }, }; size_t entry_count = sizeof(entries) / sizeof(struct figpar_config); errno = 0; int status = parse_config(entries, path, unknown_key_callback, FIGPAR_BREAK_ON_EQUALS); if (status < 0) { err(1, "could not parse config file: \"%s\"", path); } else if (status > 1) { errx(1, "could not parse config file: \"%s\"", path); } set_options_from_entries(entries, entry_count); for (size_t i = 0; i < entry_count; ++i) { switch (entries[i].type) { case FIGPAR_TYPE_STR: free(entries[i].value.str); break; case CONFIG_UINT_ARR_TYPE: free(((struct UIntArr *) entries[i].value.data)->arr); free(entries[i].value.data); break; default: ; // ignore } } LOG_VERBOSE("Finished loading config file\n"); }