Sélectionner une révision Git
-
Loïc Wikle DUBARD a rédigéLoïc Wikle DUBARD a rédigé
config.c 13,35 Kio
#include <lektor/common.h>
#include <lektor/config.h>
#include <lektor/database.h>
#include <lektor/net.h>
#include <lektor/reg.h>
#include <lektor/lib/strv.h>
#include <dlfcn.h>
#include <pwd.h>
/* Get the path to the config file that may be red, taking into account the
priority between existing files. The returned path is a path to an existing
file. If no file is found, returns a non zero value. Returns 0 otherwise. */
PRIVATE_FUNCTION int config_detect_file(char *conf, size_t conf_len);
/* Create and read the configuration in the conf file and write it into
lkt_conf. The type is opaque because it's a C++ class.
Return 0 if the read operation was a success, a non zero value otherwise. */
PRIVATE_FUNCTION int config_new(lkt_db *db, const char *conf);
/* Get the default user config file, the ~/.config/lektor/lektor.ini file. */
PRIVATE_FUNCTION void config_default_file(char *dest, size_t len);
PRIVATE_FUNCTION int
handler(lkt_db *user, const char *section, const struct strv name, const struct strv value)
{
RETURN_UNLESS(section && strv_len(name) && strv_len(value), "Skip incomplete line", 1);
RETURN_UNLESS(database_config_set_strv_1_2(user, section, name, value),
"Failed to update config", 1);
return 0;
}
PRIVATE_FUNCTION void
___set_log_level_internal(const struct strv level)
{
static UNUSED struct strv error_strv = STRV_STATIC("error");
static UNUSED struct strv warning_strv = STRV_STATIC("warning");
static UNUSED struct strv warn_strv = STRV_STATIC("warn");
static UNUSED struct strv debug_strv = STRV_STATIC("debug");
static UNUSED struct strv info_strv = STRV_STATIC("info");
if (strv_len(level) == 0) {
LOG_WARN("CONFIG", "Invalid empty 'log' option");
return;
}
if (strv_equal_nocase(level, error_strv))
lkt_set_log_level(LOG_LEVEL_ERROR);
else if (strv_equal_nocase(level, warn_strv))
lkt_set_log_level(LOG_LEVEL_WARN);
else if (strv_equal_nocase(level, warning_strv))
lkt_set_log_level(LOG_LEVEL_WARN);
else if (strv_equal_nocase(level, info_strv))
lkt_set_log_level(LOG_LEVEL_INFO);
else if (strv_equal_nocase(level, debug_strv))
lkt_set_log_level(LOG_LEVEL_DEBUG);
else
lkt_set_log_level((LOG_LEVEL)(int)strv_to_int(level));
LOG_INFO("CONFIG", "Log level set to %d", lkt_get_log_level());
}
PRIVATE_FUNCTION void
___set_log_level(const struct strv name, const struct strv level)
{
static const struct strv log_strv = STRV_STATIC("log");
/* Just check the key name here */
if (strv_equal_nocase(name, log_strv)) {
___set_log_level_internal(level);
} else {
LOG_WARN("CONFIG", "Invalid option '" STRV_FMT "=" STRV_FMT "' with no section",
STRV_ARG(name), STRV_ARG(level));
}
}
PRIVATE_FUNCTION void
___apply_log_level(lkt_db *db)
{
char loglevel[LKT_LINE_MAX];
if (!database_config_get_text_nospace(db, "log", "level", loglevel, LKT_LINE_MAX)) {
/* 'log/level' is not present, use the '/log' */
LOG_DEBUG("CONFIG", "No entry in config for 'log/level', set it to the '/log' entry");
safe_snprintf(loglevel, LKT_LINE_MAX, "%d", lkt_get_log_level());
database_config_set(db, "log", "level", loglevel);
} else {
/* 'log/level' is present, use that one */
___set_log_level_internal(strv_from_str(loglevel));
}
}
PRIVATE_FUNCTION bool
___char_is_not_comment_delim(char x)
{
return !(x == ';' || x == '#');
}
PRIVATE_FUNCTION int
ini_parse(const char *path, lkt_db *db)
{
char section[LKT_LINE_MAX], file_line[LKT_LINE_MAX];
int error = 0, linenum = 0;
FILE *file = fopen(path, "r");
if (!file) {
LOG_ERROR("PARSER", "Failed to open config file '%s'", path);
return 1;
}
memset(section, 0, LKT_LINE_MAX);
memset(file_line, 0, LKT_LINE_MAX);
static struct strv comment_prefix_1 = STRV_STATIC(";");
static struct strv comment_prefix_2 = STRV_STATIC("#");
static struct strv bracket_left_strv = STRV_STATIC("[");
static struct strv bracket_right_strv = STRV_STATIC("]");
LOG_INFO("CONFIG", "Save config to database from file");
/* Parse the file */
while (NULL != fgets(file_line, LKT_LINE_MAX, file)) {
++linenum;
struct strv line = strv_trim(strv_from_str(file_line));
/* Skip comments */
if (strv_starts_with(line, comment_prefix_1) || strv_starts_with(line, comment_prefix_2))
continue;
/* Handle sections */
else if (strv_starts_with(line, bracket_left_strv)) {
if (strv_ends_with(line, bracket_right_strv)) {
strv_chop_left(&line, 1);
strv_chop_right(&line, 1);
if (!strv_as_str(line, section, LKT_LINE_MAX)) {
error = 1;
LOG_ERROR("PARSER", "Failed to copy the section name at line '%d'", linenum);
}
} else {
error = 1;
LOG_ERROR("PARSER", "Invalid section name at line '%d'", linenum);
}
}
/* Handle empty lines */
else if (strv_len(line) == 0) {
continue;
}
/* Handle name=name pair */
else {
int separator_index = -1;
if (!strv_index_of(line, '=', &separator_index)) {
LOG_ERROR("PARSER", "Invalid line '%d', no (key, value) pair found", linenum);
continue;
}
const struct strv key = strv_trim(strv_chop_left(&line, separator_index));
strv_chop_left(&line, 1); /* Skip the "=" character */
const struct strv value =
strv_trim(strv_take_left_while(line, ___char_is_not_comment_delim));
/* Handle the (key, value) pair */
if (strv_len(value) == 0 || strv_len(key) == 0) {
LOG_ERROR("PARSER", "Invalid key pair at line '%d'", linenum);
LOG_ERROR_IF(strv_len(key), "PARSER", "The key was: '" STRV_FMT "'", STRV_ARG(key));
LOG_ERROR_IF(strv_len(value), "PARSER", "The value was: '" STRV_FMT "'",
STRV_ARG(value));
}
else if (section[0]) {
LOG_DEBUG("PARSER", "Found line: [%s] '" STRV_FMT "' = '" STRV_FMT "'", section,
STRV_ARG(key), STRV_ARG(value));
if (handler(db, section, key, value)) {
error = 1;
LOG_ERROR("PARSER",
"Failed to '[handle] %s, " STRV_FMT "=" STRV_FMT "' at line '%d'",
section, STRV_ARG(key), STRV_ARG(value), linenum);
}
}
else {
/* Keep for legacy reasons */
___set_log_level(key, value);
}
}
}
___apply_log_level(db);
/* End of the function */
fclose(file);
if (error)
LOG_ERROR("PARSER", "An error occured while parsing the file '%s'", path);
return error;
}
PRIVATE_FUNCTION void
config_default_file(char *dest, size_t len)
{
/* First try the XDG_CONFIG_HOME variable, else the default location HOME/.config. */
memset(dest, 0, len * sizeof(char));
char *home = getenv("XDG_CONFIG_HOME");
if ((NULL == home) || (strlen(home) >= len)) {
LOG_DEBUG("CONFIG", "No env variable XDG_CONFIG_HOME, try to use HOME");
home = getenv("HOME");
if ((NULL == home) || (strlen(home) >= len)) {
LOG_FATAL("Failed to get home folder for user, will now exit");
}
LOG_DEBUG("CONFIG", "Using HOME: %s", home);
safe_strncpy(dest, home, len - 1);
strncat(dest, "/.config/lektor/lektor.ini", len - 1 - strlen(home));
} else {
LOG_DEBUG("CONFIG", "Using XDG_CONFIG_HOME: %s", home);
safe_strncpy(dest, home, len - 1);
strncat(dest, "/lektor/lektor.ini", len - 1 - strlen(home));
}
}
PRIVATE_FUNCTION int
config_detect_file(char *conf, size_t conf_len)
{
bool is_malloc = false;
struct passwd *pw = getpwuid(getuid());
char *home;
if (conf == NULL)
return 1;
memset(conf, 0, conf_len * sizeof(char));
/* Try the current working dir config file. */
if (getcwd(conf, conf_len - 1) != NULL) {
strncat(conf, "/lektor.ini", conf_len - 1);
LOG_INFO("CONFIG", "Trying %s", conf);
if (!access(conf, R_OK | F_OK))
goto found;
}
/* Try the config file from the config directory. */
home = getenv("XDG_CONFIG_HOME");
if (home && strlen(home) < conf_len) {
/* Skip the strncat to not append the '.config/' to
the XDG_CONFIG_HOME which must already have this directory in it */
memcpy(conf, home, (strlen(home) + 1) * sizeof(char));
strncat(conf, "/lektor/lektor.ini", conf_len - 1);
goto skip_this_strcat;
}
if (!home || (strlen(home) >= conf_len))
home = getenv("HOME");
if (!home || (strlen(home) >= conf_len))
home = pw->pw_dir;
if (!home || (strlen(home) >= conf_len))
goto no_config_directory;
memcpy(conf, home, (strlen(home) + 1) * sizeof(char));
strncat(conf, "/.config/lektor/lektor.ini", conf_len - 1);
skip_this_strcat:
LOG_INFO("CONFIG", "Trying %s", conf);
if (!access(conf, R_OK | F_OK))
goto found;
no_config_directory:
/* Try the '/opt/lektor' file. */
memcpy(conf, "/opt/lektor/lektor.ini", sizeof("/opt/lektor/lektor.ini"));
LOG_INFO("CONFIG", "Trying %s", conf);
if (!access(conf, R_OK | F_OK))
goto found;
/* Try the '/usr/local/etc/lektor.ini' file. */
memcpy(conf, "/usr/local/etc/lektor.ini", sizeof("/usr/local/etc/lektor.ini"));
LOG_INFO("CONFIG", "Trying %s", conf);
if (!access(conf, R_OK | F_OK))
goto found;
/* Try the '/etc/lektor.ini' file. */
memcpy(conf, "/etc/lektor.ini", sizeof("/etc/lektor.ini"));
LOG_INFO("CONFIG", "Trying %s", conf);
if (!access(conf, R_OK | F_OK))
goto found;
/* Error */
LOG_ERROR("CONFIG", "An error occured with file %s: %s", conf, strerror(errno));
if (is_malloc)
free(conf);
return 1;
found:
LOG_INFO("CONFIG", "Using file %s", conf);
return 0;
}
PRIVATE_FUNCTION int
config_new(lkt_db *db, const char *conf)
{
if (ini_parse(conf, db)) {
LOG_ERROR("CONFIG", "Failed to parse file %s", conf);
goto error;
}
if (!database_validate_conf(db)) {
LOG_ERROR("CONFIG", "Configuration file %s is incomplete", conf);
goto error;
}
LOG_INFO("CONFIG", "Ensure that optional fields are present: set then to default "
"value if not present");
database_config_set_optional_fields(db);
return 0;
error:
LOG_ERROR("CONFIG", "Errors detected, here is a default config");
config_default(stdout);
return 1;
}
void
config_default(FILE *output)
{
fwrite(lkt_default_config_file, sizeof(char), strlen(lkt_default_config_file), output);
}
/* Recursive mkdir, where the last word of the string is a file, not a folder. */
PRIVATE_FUNCTION void
___mkdir(const char *dir)
{
char tmp[PATH_MAX];
char *p = NULL;
safe_snprintf(tmp, sizeof(tmp), "%s", dir);
size_t len = strlen(tmp);
/* In our case, the final word is always a file, not a folder. */
if (tmp[len - 1] == '/')
tmp[len - 1] = 0;
for (p = tmp + 1; *p; p++) {
if (*p == '/') {
*p = 0;
mkdir(tmp, 00700);
*p = '/';
}
}
/* Don't do final mkdir here, because in our case the final word in the string
* is a file, not a folder.
* mkdir(tmp, S_IRWXU); */
}
int
config_open(lkt_db *db, char *conf_file, size_t conf_len)
{
bool retry_config_once = false;
retry_config:
if ((conf_file[0] == '\0') && config_detect_file(conf_file, conf_len)) {
RETURN_IF(retry_config_once, "Failed to find a config file", 1);
LOG_INFO("INIT", "Creating default config file");
config_default_file(conf_file, conf_len);
___mkdir(conf_file); /* Create the folder for the file. */
errno = 0;
FILE *file_desc = fopen(conf_file, "w+");
if (file_desc == NULL) {
LOG_ERROR("INIT", "Failed to open default config file and initialize it");
LOG_ERROR("INTI", "Conf file is %s, errno is %d: %s", conf_file, errno,
strerror(errno));
return 1;
}
config_default(file_desc);
fclose(file_desc);
LOG_INFO("INIT", "Default configuration file has been writen to %s", conf_file);
retry_config_once = true;
goto retry_config;
}
RETURN_IF(config_new(db, conf_file), "Failed to read the config", 1);
return 0;
}
void
config_handle_hook(lkt_db *db, const char *hook_name)
{
char hook_handlers[LKT_LINE_MAX];
if (!database_config_get_text_nospace(db, "hook", hook_name, hook_handlers, LKT_LINE_MAX)) {
LOG_ERROR("HOOK", "Failed to get the hook handlers list for hook '%s'", hook_name);
return;
}
size_t save = 0;
char name[LKT_LINE_MAX];
int code;
struct module_reg *server_reg;
reg_global(&server_reg);
while (0 == (code = iter_string(hook_handlers, ",", name, LKT_LINE_MAX, &save))) {
if (STR_MATCH("none", name))
continue;
reg_call(server_reg, name, 1, db);
}
if (code == -1)
LOG_ERROR("HOOK", "Failed to parse the handle list for hook '%s'", hook_name);
}