Sélectionner une révision Git
config.c 7,58 Kio
#define _POSIX_C_SOURCE 200809L
#include <common/common.h>
#include <lektor/config.h>
#include <lektor/database.h>
#include <lektor/net.h>
#include <lektor/reg.h>
#include <strings.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <dlfcn.h>
#include <stdio.h>
#include <pwd.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <ctype.h>
static inline char *
strip(char *s)
{
char *p = s + strlen(s);
while (p > s && isspace(*(--p)))
*p = 0;
return s;
}
static inline char *
skip(char *s)
{
while (*s && isspace(*s))
s++;
return s;
}
static int
handler(volatile sqlite3 *user, const char *section, const char *name,
const char *value)
{
RETURN_UNLESS(section && name && value,
"I can't complete the database with incomplete lines", 1);
RETURN_UNLESS(database_config_set(user, section, name, value),
"Failed to update the database", 1);
return 0;
}
static inline void
__set_log_level(const char *name, const char *level)
{
if (!STR_MATCH(name, "log")) {
LOG_WARN("CONFIG", "Invalid option '%s[:=]%s' with no section",
name, level);
return;
}
if (!level[0]) {
LOG_WARN("CONFIG", "%s", "Invalid empty 'log' option");
return;
}
if (STR_MATCH(level, "error"))
log_level = ERROR;
else if (STR_MATCH(level, "warn") || STR_MATCH(level, "warning"))
log_level = WARN;
else if (STR_MATCH(level, "info"))
log_level = INFO;
else if (STR_MATCH(level, "debug"))
log_level = DEBUG;
else
log_level = strtol(level, NULL, 0);
LOG_INFO("CONFIG", "Log level set to %d", log_level);
}
static inline int
ini_parse(const char *path, volatile sqlite3 *db)
{
char *start, *end, *name, *value;
char section[INI_MAX_SECTION_LEN], line[INI_MAX_LINE_LEN];
int error = 0, linenum = 0, len;
FILE *file = fopen(path, "r");
if (!file) {
LOG_ERROR("PARSER", "Failed to open config file '%s'", path);
return 1;
}
memset(section, 0, INI_MAX_SECTION_LEN);
memset(line, 0, INI_MAX_LINE_LEN);
/* Parse the file */
while (NULL != fgets(line, INI_MAX_LINE_LEN, file)) {
++linenum;
start = skip(strip(line));
/* Skip comments */
if (strspn(start, ";#"))
continue;
/* Handle sections */
else if (start[0] == '[') {
end = &start[1 + strcspn(start + 1, "]")];
if (end[0] == ']') {
end[0] = '\0';
len = strlen(&start[1]);
len = MIN(len + 1, INI_MAX_SECTION_LEN);
memcpy(section, &start[1], len);
section[INI_MAX_SECTION_LEN - 1] = '\0';
}
else {
error = 1;
LOG_ERROR("PARSER", "Invalid section name at line '%d'",
linenum);
}
}
/* Handle name[:=]name pair */
else if (start[0]) {
end = &start[1 + strcspn(start + 1, ":=")];
if (end[0] == '=' || end[0] == ':') {
end[0] = '\0';
name = strip(start);
value = &end[1];
/* Find a comment */
end = &value[strcspn(value, ";#")];
if (end[0])
end[0] = '\0';
/* Skip all spaces */
value = skip(value);
strip(value);
/* Handle the SECTION, NAME[:=]VALUE
The only option that has no SECTION is the log level:
log[:=]ERROR|WARN|INFO|DEBUG|\d+ */
if (section[0]) {
if (handler(db, section, name, value)) {
error = 1;
LOG_ERROR("PARSER", "Failed to '[handle] %s, "
"%s{:,=}%s' at line '%d'",
section, name, value, linenum);
}
} else
__set_log_level(name, value);
}
else {
error = 1;
LOG_ERROR("PARSER", "Invalid name[:=]value pair at "
"line '%d'", linenum);
}
}
}
/* End of the function */
fclose(file);
if (error)
LOG_ERROR("PARSER", "An error occured while parsing the "
"file '%s'", path);
return error;
}
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 */
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;
}
int
config_new(volatile sqlite3 *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;
}
return 0;
error:
LOG_ERROR("CONFIG", "%s", "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);
}
int
config_open(volatile sqlite3 *db)
{
char conf_file[PATH_MAX];
int ret = 1;
GOTO_IF(config_detect_file(conf_file, PATH_MAX),
"Error while searching for a config file", error);
GOTO_IF(config_new(db, conf_file),
"Failed to read configuration file", error);
ret = 0;
error:
return ret;
}