/* $Id$ */ /* Copyright (c) 2005-2021 Pierre Pronchery */ /* This file is part of DeforaOS System libSystem */ /* All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include "System/error.h" #include "System/mutator.h" #include "System/config.h" #include "../config.h" /* constants */ #ifndef PREFIX # define PREFIX "/usr/local" #endif #ifndef SYSCONFDIR # define SYSCONFDIR PREFIX "/etc" #endif #define CONFIG_COMMENT '#' /* Config */ /* private */ /* types */ typedef struct _ConfigForeachData { ConfigForeachCallback callback; void * priv; } ConfigForeachData; typedef struct _ConfigForeachSectionData { ConfigForeachSectionCallback callback; String const * section; void * priv; } ConfigForeachSectionData; typedef struct _ConfigSave { FILE * fp; String const * sep; } ConfigSave; /* public */ /* functions */ /* config_new */ Config * config_new(void) { return mutator_new(); } /* config_new_copy */ typedef struct _ConfigError { Config * config; ErrorCode code; } ConfigError; static void _new_copy_foreach(Config const * from, String const * section, void * priv); static void _new_copy_foreach_section(Config const * from, String const * section, String const * variable, String const * value, void * priv); Config * config_new_copy(Config const * from) { ConfigError ce; if((ce.config = mutator_new()) == NULL) return NULL; ce.code = 0; config_foreach(from, _new_copy_foreach, &ce); if(ce.code != 0) { config_delete(ce.config); return NULL; } return ce.config; } static void _new_copy_foreach(Config const * from, String const * section, void * priv) { ConfigError * ce = (ConfigError *)priv; if(ce->code == 0) config_foreach_section(from, section, _new_copy_foreach_section, ce); } static void _new_copy_foreach_section(Config const * from, String const * section, String const * variable, String const * value, void * priv) { ConfigError * ce = (ConfigError *)priv; (void) from; if(config_set(ce->config, section, variable, value) != 0) ce->code = error_get_code(); } /* config_new_load */ Config * config_new_load(String const * filename) { Config * config; if(filename == NULL) return config_new(); if((config = config_new()) == NULL) return NULL; if(config_load(config, filename) != 0) { config_delete(config); return NULL; } return config; } /* config_delete */ void config_delete(Config * config) { config_reset(config); mutator_delete(config); } /* accessors */ /* config_get */ String const * config_get(Config const * config, String const * section, String const * variable) { Mutator * mutator; String const * value; if(section == NULL) section = ""; if((mutator = (Mutator *)mutator_get(config, section)) == NULL) { /* the section does not exist */ if(section[0] == '\0') error_set_code(1, "%s", "No default section"); else error_set_code(1, "%s%s", section, ": No such section"); return NULL; } if((value = (String const *)mutator_get(mutator, variable)) == NULL) { /* the variable is not defined */ error_set_code(1, "%s%s%s%s%s", variable, ": Not defined in", (section[0] == '\0') ? " default" : "", " section ", (section[0] != '\0') ? section : ""); return NULL; } return value; } /* config_set */ int config_set(Config * config, String const * section, String const * variable, String const * value) { Mutator * mutator; String * p; String * newvalue = NULL; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\", \"%s\")\n", __func__, section, variable, value); #endif if(section == NULL) section = ""; if(variable == NULL || string_get_length(variable) == 0) return error_set_code(-EINVAL, "variable: %s", strerror(EINVAL)); if((mutator = (Mutator *)mutator_get(config, section)) == NULL) { /* create a new section */ if((mutator = mutator_new()) == NULL) return -1; if(mutator_set(config, section, mutator) != 0) { mutator_delete(mutator); return -1; } p = NULL; } else if((p = (String *)mutator_get(mutator, variable)) == NULL && value == NULL) /* there is nothing to do */ return 0; if(value != NULL && (newvalue = string_new(value)) == NULL) return -1; if(mutator_set(mutator, variable, newvalue) != 0) { string_delete(newvalue); return -1; } /* free the former value */ string_delete(p); return 0; } /* useful */ /* config_foreach */ static void _foreach_callback(Config const * config, String const * key, void * value, void * data); void config_foreach(Config const * config, ConfigForeachCallback callback, void * priv) { ConfigForeachData data; data.callback = callback; data.priv = priv; mutator_foreach(config, _foreach_callback, &data); } static void _foreach_callback(Config const * config, String const * key, void * value, void * data) { ConfigForeachData * priv = (ConfigForeachData *)data; (void) value; priv->callback(config, key, priv->priv); } /* config_foreach_section */ static void _foreach_section_callback(Mutator const * mutator, String const * key, void * value, void * data); void config_foreach_section(Config const * config, String const * section, ConfigForeachSectionCallback callback, void * priv) { Mutator * mutator; ConfigForeachSectionData data; if((mutator = (Mutator *)mutator_get(config, section)) == NULL) return; /* could not find section */ data.callback = callback; data.section = section; data.priv = priv; mutator_foreach(mutator, _foreach_section_callback, &data); } static void _foreach_section_callback(Mutator const * mutator, String const * key, void * value, void * data) { ConfigForeachSectionData * priv = (ConfigForeachSectionData *)data; priv->callback(mutator, priv->section, key, (String const *)value, priv->priv); } /* config_load */ static int _load_isprint(int c); static String * _load_section(FILE * fp); static String * _load_variable(FILE * fp, int c); static String * _load_value(FILE * fp); int config_load(Config * config, String const * filename) { int ret = 0; size_t line; FILE * fp; String * section = NULL; String * variable = NULL; String * value = NULL; int c; String * str; if((fp = fopen(filename, "r")) == NULL) return error_set_code(-errno, "%s: %s", filename, strerror(errno)); for(line = 0; (c = fgetc(fp)) != EOF; line++) if(c == CONFIG_COMMENT) /* skip the comment */ while((c = fgetc(fp)) != EOF && c != '\n'); else if(c == '[') { if((str = _load_section(fp)) == NULL) break; string_delete(section); section = str; } else if(_load_isprint(c)) { if((str = _load_variable(fp, c)) == NULL) break; string_delete(variable); variable = str; if((str = _load_value(fp)) == NULL) break; string_delete(value); value = str; if(config_set(config, section, variable, value) != 0) break; } else if(c != '\n') break; string_delete(section); string_delete(variable); string_delete(value); if(c != EOF) ret = error_set_code(1, "%s: %s%ld", filename, "Syntax error" " at line ", line); if(fclose(fp) != 0) ret = error_set_code(-errno, "%s: %s", filename, strerror(errno)); return ret; } static int _load_isprint(int c) { if(c == EOF || c == '\n' || c == '\0') return 0; return 1; } static String * _load_section(FILE * fp) { int c; String * str; String buf[2] = "\0"; if((str = string_new("")) == NULL) return NULL; while((c = fgetc(fp)) != EOF && c != ']' && _load_isprint(c)) { buf[0] = c; if(string_append(&str, buf) != 0) break; } if(c != ']') { string_delete(str); return NULL; } return str; } static String * _load_variable(FILE * fp, int c) { String * str; String buf[2] = "\0"; buf[0] = c; if((str = string_new(buf)) == NULL) return NULL; while((c = fgetc(fp)) != EOF && c != '=' && _load_isprint(c)) { buf[0] = c; if(string_append(&str, buf) != 0) break; } if(c != '=') { string_delete(str); return NULL; } return str; } static String * _load_value(FILE * fp) { int c; String * str = NULL; String buf[2] = "\0"; while((c = fgetc(fp)) != EOF && _load_isprint(c)) { buf[0] = c; if(string_append(&str, buf) != 0) break; } if(c != EOF && c != '\n') { string_delete(str); return NULL; } if(str == NULL) return string_new(""); return str; } /* config_load_preferences */ int config_load_preferences(Config * config, String const * vendor, String const * package, String const * filename) { int ret; if((ret = config_load_preferences_system(config, vendor, package, filename)) != 0 && ret != -ENOENT && ret != -EPERM) return ret; if((ret = config_load_preferences_user(config, vendor, package, filename)) != 0 && ret != -ENOENT) return ret; return 0; } /* config_load_preferences_system */ int config_load_preferences_system(Config * config, String const * vendor, String const * package, String const * filename) { int ret; String * f; if(filename == NULL) return error_set_code(-EINVAL, "%s", strerror(EINVAL)); if(vendor != NULL && (string_compare_length(vendor, "../", 3) == 0 || string_find(vendor, "/..") != NULL)) return error_set_code(-EPERM, "%s", strerror(EPERM)); if(package != NULL && (string_compare_length(package, "../", 3) == 0 || string_find(package, "/..") != NULL)) return error_set_code(-EPERM, "%s", strerror(EPERM)); if((f = string_new_append(SYSCONFDIR, "/", (vendor != NULL) ? vendor : "", "/", (package != NULL) ? package : "", "/", filename, NULL)) == NULL) return error_get_code(); ret = config_load(config, f); string_delete(f); return ret; } /* config_load_preferences_user */ int config_load_preferences_user(Config * config, String const * vendor, String const * package, String const * filename) { int ret; String const * homedir; String * f; if(filename == NULL) return error_set_code(-EINVAL, "%s", strerror(EINVAL)); if(vendor != NULL && (string_compare_length(vendor, "../", 3) == 0 || string_find(vendor, "/..") != NULL)) return error_set_code(-EPERM, "%s", strerror(EPERM)); if(package != NULL && (string_compare_length(package, "../", 3) == 0 || string_find(package, "/..") != NULL)) return error_set_code(-EPERM, "%s", strerror(EPERM)); if(filename != NULL && string_find(filename, "/") != NULL) return error_set_code(-EPERM, "%s", strerror(EPERM)); if((homedir = getenv("HOME")) == NULL) return error_set_code(-errno, "%s", strerror(errno)); if((f = string_new_append(homedir, "/.config/", (vendor != NULL) ? vendor : "", "/", (package != NULL) ? package : "", "/", filename, NULL)) == NULL) return error_get_code(); ret = config_load(config, f); string_delete(f); return ret; } /* config_reset */ static void _delete_foreach(Mutator const * mutator, String const * key, void * value, void * data); static void _delete_foreach_section(Mutator const * mutator, String const * key, void * value, void * data); int config_reset(Config * config) { mutator_foreach(config, _delete_foreach, NULL); return mutator_reset(config); } static void _delete_foreach(Mutator const * mutator, String const * key, void * value, void * data) { Mutator * m = (Mutator *)value; (void) mutator; (void) key; (void) data; /* free the values */ mutator_foreach(m, _delete_foreach_section, NULL); mutator_delete(m); } static void _delete_foreach_section(Mutator const * mutator, String const * key, void * value, void * data) { String * v = (String *)value; (void) mutator; (void) key; (void) data; string_delete(v); } /* config_save */ static void _save_foreach_default(Mutator const * mutator, String const * section, void * value, void * data); static void _save_foreach(Mutator const * mutator, String const * section, void * value, void * data); static void _save_foreach_section(Mutator const * mutator, String const * key, void * value, void * data); int config_save(Config const * config, String const * filename) { ConfigSave save; save.sep = ""; if((save.fp = fopen(filename, "w")) == NULL) return error_set_code(-errno, "%s: %s", filename, strerror(errno)); mutator_foreach(config, _save_foreach_default, &save); mutator_foreach(config, _save_foreach, &save); if(save.fp != NULL && save.sep[0] != '\0' && fputs(save.sep, save.fp) == EOF) { fclose(save.fp); save.fp = NULL; } if(save.fp == NULL || fclose(save.fp) != 0) return error_set_code(-errno, "%s: %s", filename, strerror(errno)); return 0; } static void _save_foreach_default(Mutator const * mutator, String const * section, void * value, void * data) { ConfigSave * save = (ConfigSave *)data; Mutator * m = (Mutator *)value; (void) mutator; if(save->fp == NULL) return; if(section[0] != '\0') return; mutator_foreach(m, _save_foreach_section, save); } static void _save_foreach(Mutator const * mutator, String const * section, void * value, void * data) { ConfigSave * save = (ConfigSave *)data; Mutator * m = (Mutator *)value; (void) mutator; if(save->fp == NULL) return; if(section[0] == '\0') return; if(fprintf(save->fp, "%s%s[%s]", save->sep, save->sep, section) < 0) { fclose(save->fp); save->fp = NULL; return; } save->sep = "\n"; mutator_foreach(m, _save_foreach_section, save); } static void _save_foreach_section(Mutator const * mutator, String const * key, void * value, void * data) { ConfigSave * save = (ConfigSave *)data; String const * val = (String const *)value; (void) mutator; if(save->fp == NULL) return; if(val == NULL) return; if(fprintf(save->fp, "%s%s=%s", save->sep, key, val) < 0) { fclose(save->fp); save->fp = NULL; return; } save->sep = "\n"; } /* config_save_preferences_user */ static int _save_preferences_user_mkdir(String * dir); int config_save_preferences_user(Config const * config, String const * vendor, String const * package, String const * filename) { int ret; String const * homedir; String * f; if(filename == NULL) return error_set_code(-EINVAL, "%s", strerror(EINVAL)); if((homedir = getenv("HOME")) == NULL) return error_set_code(-errno, "%s", strerror(errno)); if((f = string_new_append(homedir, "/.config/", (vendor != NULL) ? vendor : "", (vendor != NULL) ? "/" : "", (package != NULL) ? package : "", (package != NULL) ? "/" : "", filename, NULL)) == NULL) return error_get_code(); if((ret = _save_preferences_user_mkdir(f)) == 0) ret = config_save(config, f); string_delete(f); return ret; } static int _save_preferences_user_mkdir(String * dir) { int ret = 0; size_t i; size_t j; size_t len; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, dir); #endif len = string_get_length(dir); for(i = 0, j = 0; j < len; j++) { if(dir[j] != '/') continue; if(i == j) { i++; continue; } dir[j] = '\0'; #ifdef DEBUG fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, dir); #endif if(dir[i] == '.') ret = error_set_code(-EPERM, "%s: %s", dir, strerror(EPERM)); else if(mkdir(dir, 0777) != 0 && errno != EEXIST) ret = error_set_code(-errno, "%s: %s", dir, strerror(errno)); dir[j] = '/'; if(ret != 0) return ret; } return 0; }