501 lines
9.8 KiB
C
501 lines
9.8 KiB
C
/*************************************************************************
|
|
*
|
|
* Copyright (C) 2018-2023 Ruilin Peng (Nick) <pymumu@gmail.com>.
|
|
*
|
|
* smartdns 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.
|
|
*
|
|
* smartdns is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "conf.h"
|
|
#include <getopt.h>
|
|
#include <libgen.h>
|
|
#include <linux/limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
static const char *current_conf_file = NULL;
|
|
static int current_conf_lineno = 0;
|
|
|
|
const char *conf_get_conf_file(void)
|
|
{
|
|
return current_conf_file;
|
|
}
|
|
|
|
int conf_get_current_lineno(void)
|
|
{
|
|
return current_conf_lineno;
|
|
}
|
|
|
|
static char *get_dir_name(char *path)
|
|
{
|
|
if (strstr(path, "/") == NULL) {
|
|
strncpy(path, "./", PATH_MAX);
|
|
return path;
|
|
}
|
|
|
|
return dirname(path);
|
|
}
|
|
|
|
const char *conf_get_conf_fullpath(const char *path, char *fullpath, size_t path_len)
|
|
{
|
|
char file_path_dir[PATH_MAX];
|
|
|
|
if (path_len < 1) {
|
|
return NULL;
|
|
}
|
|
|
|
if (path[0] == '/') {
|
|
strncpy(fullpath, path, path_len);
|
|
return fullpath;
|
|
}
|
|
|
|
strncpy(file_path_dir, conf_get_conf_file(), PATH_MAX - 1);
|
|
file_path_dir[PATH_MAX - 1] = 0;
|
|
get_dir_name(file_path_dir);
|
|
if (file_path_dir[0] == '\0') {
|
|
strncpy(fullpath, path, path_len);
|
|
return fullpath;
|
|
}
|
|
|
|
if (snprintf(fullpath, PATH_MAX, "%s/%s", file_path_dir, path) < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
return fullpath;
|
|
}
|
|
|
|
int conf_custom(const char *item, void *data, int argc, char *argv[])
|
|
{
|
|
struct config_item_custom *item_custom = data;
|
|
return item_custom->custom_func(item_custom->custom_data, argc, argv);
|
|
}
|
|
|
|
int conf_int(const char *item, void *data, int argc, char *argv[])
|
|
{
|
|
struct config_item_int *item_int = data;
|
|
int value = 0;
|
|
if (argc < 2) {
|
|
return -1;
|
|
}
|
|
|
|
value = atoi(argv[1]);
|
|
|
|
if (value < item_int->min) {
|
|
value = item_int->min;
|
|
} else if (value > item_int->max) {
|
|
value = item_int->max;
|
|
}
|
|
|
|
*(item_int->data) = value;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int conf_int_base(const char *item, void *data, int argc, char *argv[])
|
|
{
|
|
struct config_item_int_base *item_int = data;
|
|
int value = 0;
|
|
if (argc < 2) {
|
|
return -1;
|
|
}
|
|
|
|
value = strtol(argv[1], NULL, item_int->base);
|
|
|
|
if (value < item_int->min) {
|
|
value = item_int->min;
|
|
} else if (value > item_int->max) {
|
|
value = item_int->max;
|
|
}
|
|
|
|
*(item_int->data) = value;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int conf_string(const char *item, void *data, int argc, char *argv[])
|
|
{
|
|
struct config_item_string *item_string = data;
|
|
|
|
if (argc < 2) {
|
|
return -1;
|
|
}
|
|
|
|
strncpy(item_string->data, argv[1], item_string->size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int conf_yesno(const char *item, void *data, int argc, char *argv[])
|
|
{
|
|
struct config_item_yesno *item_yesno = data;
|
|
int yes = 0;
|
|
|
|
if (argc < 2) {
|
|
return -1;
|
|
}
|
|
|
|
char *value = argv[1];
|
|
if (strncmp("auto", value, sizeof("auto")) == 0 || strncmp("AUTO", value, sizeof("AUTO")) == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (strncmp("yes", value, sizeof("yes")) == 0 || strncmp("YES", value, sizeof("YES")) == 0) {
|
|
yes = 1;
|
|
} else if (strncmp("no", value, sizeof("no")) == 0 || strncmp("NO", value, sizeof("NO")) == 0) {
|
|
yes = 0;
|
|
}
|
|
|
|
*(item_yesno->data) = yes;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int conf_size(const char *item, void *data, int argc, char *argv[])
|
|
{
|
|
int base = 1;
|
|
size_t size = 0;
|
|
int num = 0;
|
|
struct config_item_size *item_size = data;
|
|
char *value = argv[1];
|
|
|
|
if (strstr(value, "k") || strstr(value, "K")) {
|
|
base = 1024;
|
|
} else if (strstr(value, "m") || strstr(value, "M")) {
|
|
base = 1024 * 1024;
|
|
} else if (strstr(value, "g") || strstr(value, "G")) {
|
|
base = 1024 * 1024 * 1024;
|
|
}
|
|
|
|
num = atoi(value);
|
|
if (num < 0) {
|
|
return -1;
|
|
}
|
|
|
|
size = num * base;
|
|
if (size > item_size->max) {
|
|
size = item_size->max;
|
|
} else if (size < item_size->min) {
|
|
size = item_size->min;
|
|
}
|
|
|
|
*(item_size->data) = size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int conf_ssize(const char *item, void *data, int argc, char *argv[])
|
|
{
|
|
int base = 1;
|
|
ssize_t size = 0;
|
|
int num = 0;
|
|
struct config_item_ssize *item_size = data;
|
|
char *value = argv[1];
|
|
|
|
if (strstr(value, "k") || strstr(value, "K")) {
|
|
base = 1024;
|
|
} else if (strstr(value, "m") || strstr(value, "M")) {
|
|
base = 1024 * 1024;
|
|
} else if (strstr(value, "g") || strstr(value, "G")) {
|
|
base = 1024 * 1024 * 1024;
|
|
}
|
|
|
|
num = atoi(value);
|
|
if (num < 0) {
|
|
return -1;
|
|
}
|
|
|
|
size = num * base;
|
|
if (size > item_size->max) {
|
|
size = item_size->max;
|
|
} else if (size < item_size->min) {
|
|
size = item_size->min;
|
|
}
|
|
|
|
*(item_size->data) = size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int conf_enum(const char *item, void *data, int argc, char *argv[])
|
|
{
|
|
struct config_enum *item_enum = data;
|
|
char *enum_name = argv[1];
|
|
int i = 0;
|
|
|
|
if (argc <= 0) {
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; item_enum->list[i].name != NULL; i++) {
|
|
if (strcmp(enum_name, item_enum->list[i].name) == 0) {
|
|
*(item_enum->data) = item_enum->list[i].id;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
printf("Not found config value '%s', valid value is:\n", enum_name);
|
|
for (i = 0; item_enum->list[i].name != NULL; i++) {
|
|
printf(" %s\n", item_enum->list[i].name);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void conf_getopt_reset(void)
|
|
{
|
|
static struct option long_options[] = {{"-", 0, 0, 0}, {0, 0, 0, 0}};
|
|
int argc = 2;
|
|
char *argv[3] = {"reset", "", 0};
|
|
|
|
optind = 0;
|
|
opterr = 0;
|
|
optopt = 0;
|
|
getopt_long(argc, argv, "", long_options, NULL);
|
|
optind = 0;
|
|
opterr = 0;
|
|
optopt = 0;
|
|
}
|
|
|
|
static int conf_parse_args(char *key, char *value, int *argc, char **argv)
|
|
{
|
|
char *start = NULL;
|
|
char *ptr = value;
|
|
int count = 0;
|
|
int sep_flag = ' ';
|
|
|
|
argv[0] = key;
|
|
count++;
|
|
|
|
while (*ptr != '\0') {
|
|
if (*ptr == '\\') {
|
|
char *tmp = ptr + 1;
|
|
while (*tmp != '\0') {
|
|
*(tmp - 1) = *tmp;
|
|
tmp++;
|
|
}
|
|
ptr++;
|
|
continue;
|
|
}
|
|
|
|
if ((*ptr == '"' || *ptr == '\'') && start == NULL) {
|
|
sep_flag = *ptr;
|
|
start = NULL;
|
|
}
|
|
|
|
if (*ptr != sep_flag && start == NULL) {
|
|
start = ptr;
|
|
ptr++;
|
|
continue;
|
|
}
|
|
|
|
if (*ptr == sep_flag && start == NULL) {
|
|
ptr++;
|
|
continue;
|
|
}
|
|
|
|
if (*ptr == sep_flag && start) {
|
|
argv[count] = start;
|
|
*ptr = '\0';
|
|
ptr++;
|
|
count++;
|
|
sep_flag = ' ';
|
|
start = NULL;
|
|
continue;
|
|
}
|
|
|
|
ptr++;
|
|
}
|
|
|
|
if (start != ptr && start) {
|
|
argv[count] = start;
|
|
count++;
|
|
}
|
|
|
|
*argc = count;
|
|
argv[count] = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void load_exit(void) {}
|
|
|
|
static int load_conf_printf(const char *file, int lineno, int ret)
|
|
{
|
|
if (ret != CONF_RET_OK) {
|
|
printf("process config file '%s' failed at line %d.", file, lineno);
|
|
if (ret == CONF_RET_ERR || ret == CONF_RET_NOENT) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int load_conf_file(const char *file, struct config_item *items, conf_error_handler handler)
|
|
{
|
|
FILE *fp = NULL;
|
|
char line[MAX_LINE_LEN + MAX_KEY_LEN];
|
|
char key[MAX_KEY_LEN];
|
|
char value[MAX_LINE_LEN];
|
|
int filed_num = 0;
|
|
int i = 0;
|
|
int last_item_index = -1;
|
|
int argc = 0;
|
|
char *argv[1024];
|
|
int ret = 0;
|
|
int call_ret = 0;
|
|
int line_no = 0;
|
|
int line_len = 0;
|
|
int read_len = 0;
|
|
int is_last_line_wrap = 0;
|
|
int current_line_wrap = 0;
|
|
int is_func_found = 0;
|
|
const char *last_file = NULL;
|
|
|
|
if (handler == NULL) {
|
|
handler = load_conf_printf;
|
|
}
|
|
|
|
fp = fopen(file, "r");
|
|
if (fp == NULL) {
|
|
fprintf(stderr, "open config file '%s' failed, %s\n", file, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
line_no = 0;
|
|
while (fgets(line + line_len, MAX_LINE_LEN - line_len, fp)) {
|
|
current_line_wrap = 0;
|
|
line_no++;
|
|
read_len = strnlen(line + line_len, sizeof(line));
|
|
if (read_len >= 2 && *(line + line_len + read_len - 2) == '\\') {
|
|
read_len -= 1;
|
|
current_line_wrap = 1;
|
|
}
|
|
|
|
/* comment in wrap line, skip */
|
|
if (is_last_line_wrap && read_len > 0) {
|
|
if (*(line + line_len) == '#') {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* trim prefix spaces in wrap line */
|
|
if ((current_line_wrap == 1 || is_last_line_wrap == 1) && read_len > 0) {
|
|
is_last_line_wrap = current_line_wrap;
|
|
read_len -= 1;
|
|
for (i = 0; i < read_len; i++) {
|
|
char *ptr = line + line_len + i;
|
|
if (*ptr == ' ' || *ptr == '\t') {
|
|
continue;
|
|
}
|
|
|
|
memmove(line + line_len, ptr, read_len - i + 1);
|
|
line_len += read_len - i;
|
|
break;
|
|
}
|
|
|
|
line[line_len] = '\0';
|
|
if (current_line_wrap) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
line_len = 0;
|
|
is_last_line_wrap = 0;
|
|
|
|
filed_num = sscanf(line, "%63s %8191[^\r\n]s", key, value);
|
|
if (filed_num <= 0) {
|
|
continue;
|
|
}
|
|
|
|
/* comment, skip */
|
|
if (key[0] == '#') {
|
|
continue;
|
|
}
|
|
|
|
/* if field format is not key = value, error */
|
|
if (filed_num != 2) {
|
|
handler(file, line_no, CONF_RET_BADCONF);
|
|
goto errout;
|
|
}
|
|
|
|
is_func_found = 0;
|
|
|
|
for (i = last_item_index;; i++) {
|
|
if (i < 0) {
|
|
continue;
|
|
}
|
|
|
|
if (items[i].item == NULL) {
|
|
break;
|
|
}
|
|
|
|
if (strncmp(items[i].item, key, MAX_KEY_LEN) != 0) {
|
|
if (last_item_index >= 0) {
|
|
i = -1;
|
|
last_item_index = -1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (conf_parse_args(key, value, &argc, argv) != 0) {
|
|
continue;
|
|
}
|
|
|
|
conf_getopt_reset();
|
|
/* call item function */
|
|
last_file = current_conf_file;
|
|
current_conf_file = file;
|
|
current_conf_lineno = line_no;
|
|
call_ret = items[i].item_func(items[i].item, items[i].data, argc, argv);
|
|
ret = handler(file, line_no, call_ret);
|
|
if (ret != 0) {
|
|
conf_getopt_reset();
|
|
goto errout;
|
|
}
|
|
|
|
conf_getopt_reset();
|
|
if (last_file) {
|
|
current_conf_file = last_file;
|
|
}
|
|
|
|
last_item_index = i;
|
|
is_func_found = 1;
|
|
break;
|
|
}
|
|
|
|
if (is_func_found == 0) {
|
|
handler(file, line_no, CONF_RET_NOENT);
|
|
}
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
return 0;
|
|
errout:
|
|
if (fp) {
|
|
fclose(fp);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int load_conf(const char *file, struct config_item items[], conf_error_handler handler)
|
|
{
|
|
return load_conf_file(file, items, handler);
|
|
}
|