424 lines
13 KiB
C
424 lines
13 KiB
C
#include "config.h"
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
#include <strings.h> // strcasecmp
|
||
#include <libxml/parser.h>
|
||
#include <libxml/tree.h>
|
||
#include <errno.h>
|
||
#include <sys/mman.h>
|
||
#include <sys/types.h>
|
||
#include <sys/stat.h>
|
||
#include <fcntl.h>
|
||
#include <unistd.h>
|
||
|
||
static xmlNodePtr find_child(xmlNodePtr parent, const char *name)
|
||
{
|
||
if (!parent || !name) return NULL;
|
||
|
||
for (xmlNodePtr n = parent->children; n; n = n->next)
|
||
{
|
||
if (n->type == XML_ELEMENT_NODE &&
|
||
xmlStrcmp(n->name, BAD_CAST name) == 0)
|
||
{
|
||
return n;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
static void set_default_config(AppConfig *cfg)
|
||
{
|
||
memset(cfg, 0, sizeof(*cfg));
|
||
strncpy(cfg->ip, "127.0.0.1", sizeof(cfg->ip) - 1);
|
||
cfg->ip[sizeof(cfg->ip) - 1] = '\0';
|
||
strncpy(cfg->master_ip, "127.0.0.1", sizeof(cfg->master_ip) - 1);
|
||
cfg->master_ip[sizeof(cfg->master_ip) - 1] = '\0';
|
||
|
||
strncpy(cfg->persist_dir, "data.default", sizeof(cfg->persist_dir) - 1);
|
||
cfg->persist_dir[sizeof(cfg->persist_dir) - 1] = '\0';
|
||
|
||
strncpy(cfg->oplog_file, "kvs_oplog.default.db", sizeof(cfg->oplog_file) - 1);
|
||
cfg->oplog_file[sizeof(cfg->oplog_file) - 1] = '\0';
|
||
|
||
strncpy(cfg->array_file, "kvs_array.default.db", sizeof(cfg->array_file) - 1);
|
||
cfg->array_file[sizeof(cfg->array_file) - 1] = '\0';
|
||
|
||
strncpy(cfg->rbtree_file, "kvs_rbtree.default.db", sizeof(cfg->rbtree_file) - 1);
|
||
cfg->rbtree_file[sizeof(cfg->rbtree_file) - 1] = '\0';
|
||
|
||
strncpy(cfg->hash_file, "kvs_hash.default.db", sizeof(cfg->hash_file) - 1);
|
||
cfg->hash_file[sizeof(cfg->hash_file) - 1] = '\0';
|
||
|
||
cfg->port = 8888;
|
||
cfg->log_level = LOG_LEVEL_INFO;
|
||
cfg->mode = MODE_MASTER;
|
||
cfg->master_port = 8888;
|
||
cfg->persistence = PERSIST_NONE;
|
||
cfg->allocator = ALLOC_JEMALLOC;
|
||
cfg->leak_mode = MEMLEAK_DETECT_OFF;
|
||
}
|
||
|
||
/* ---- 字符串转枚举 ---- */
|
||
|
||
static void parse_log_level(const char *s, LogLevel *out)
|
||
{
|
||
if (!s || !out) return;
|
||
|
||
if (!strcasecmp(s, "DEBUG")) *out = LOG_LEVEL_DEBUG;
|
||
else if (!strcasecmp(s, "INFO")) *out = LOG_LEVEL_INFO;
|
||
else if (!strcasecmp(s, "ERROR")) *out = LOG_LEVEL_ERROR;
|
||
/* 不认识就保持默认 */
|
||
}
|
||
|
||
static void parse_server_mode(const char *s, ServerMode *out)
|
||
{
|
||
if (!s || !out) return;
|
||
if (!strcasecmp(s, "master")) *out = MODE_MASTER;
|
||
else if (!strcasecmp(s, "slave")) *out = MODE_SLAVE;
|
||
}
|
||
|
||
static void parse_persistence(const char *s, PersistenceType *out)
|
||
{
|
||
if (!s || !out) return;
|
||
if (!strcasecmp(s, "incremental")) *out = PERSIST_INCREMENTAL;
|
||
else if (!strcasecmp(s, "none")) *out = PERSIST_NONE;
|
||
}
|
||
|
||
static void parse_allocator(const char *s, AllocatorType *out)
|
||
{
|
||
if (!s || !out) return;
|
||
if (!strcasecmp(s, "jemalloc")) *out = ALLOC_JEMALLOC;
|
||
else if (!strcasecmp(s, "malloc")) *out = ALLOC_MALLOC;
|
||
else if (!strcasecmp(s, "mypool")) *out = ALLOC_MYPOOL;
|
||
else *out = ALLOC_OTHER;
|
||
}
|
||
|
||
static void parse_leakage(const char *s, MemLeakDetectMode *out)
|
||
{
|
||
if (!s || !out) return;
|
||
if (!strcasecmp(s, "enable")) *out = MEMLEAK_DETECT_ON;
|
||
else if (!strcasecmp(s, "disable")) *out = MEMLEAK_DETECT_OFF;
|
||
else *out = MEMLEAK_DETECT_OFF;
|
||
}
|
||
|
||
static int read_file_mmap(const char *filename, void **out_addr, size_t *out_len, int *out_fd) {
|
||
if (!filename || !out_addr || !out_len || !out_fd) return -1;
|
||
|
||
*out_addr = NULL;
|
||
*out_len = 0;
|
||
*out_fd = -1;
|
||
|
||
int fd = open(filename, O_RDONLY);
|
||
if (fd < 0) {
|
||
fprintf(stderr, "config_load: open(%s) failed: %s\n", filename, strerror(errno));
|
||
return -1;
|
||
}
|
||
|
||
struct stat st;
|
||
if (fstat(fd, &st) != 0) {
|
||
fprintf(stderr, "config_load: fstat(%s) failed: %s\n", filename, strerror(errno));
|
||
close(fd);
|
||
return -1;
|
||
}
|
||
|
||
if (!S_ISREG(st.st_mode)) {
|
||
fprintf(stderr, "config_load: %s is not a regular file\n", filename);
|
||
close(fd);
|
||
return -1;
|
||
}
|
||
|
||
if (st.st_size <= 0) {
|
||
fprintf(stderr, "config_load: %s is empty\n", filename);
|
||
close(fd);
|
||
return -1;
|
||
}
|
||
|
||
size_t len = (size_t)st.st_size;
|
||
void *addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
|
||
if (addr == MAP_FAILED) {
|
||
fprintf(stderr, "config_load: mmap(%s) failed: %s\n", filename, strerror(errno));
|
||
close(fd);
|
||
return -1;
|
||
}
|
||
|
||
*out_addr = addr;
|
||
*out_len = len;
|
||
*out_fd = fd;
|
||
return 0;
|
||
}
|
||
|
||
/* ---- 对外的枚举转字符串工具 ---- */
|
||
|
||
const char *log_level_to_string(LogLevel lvl)
|
||
{
|
||
switch (lvl) {
|
||
case LOG_LEVEL_DEBUG: return "DEBUG";
|
||
case LOG_LEVEL_INFO: return "INFO";
|
||
case LOG_LEVEL_ERROR: return "ERROR";
|
||
default: return "UNKNOWN";
|
||
}
|
||
}
|
||
|
||
const char *server_mode_to_string(ServerMode mode)
|
||
{
|
||
switch (mode) {
|
||
case MODE_MASTER: return "master";
|
||
case MODE_SLAVE: return "slave";
|
||
default: return "unknown";
|
||
}
|
||
}
|
||
|
||
const char *persistence_to_string(PersistenceType p)
|
||
{
|
||
switch (p) {
|
||
case PERSIST_INCREMENTAL: return "incremental";
|
||
case PERSIST_NONE: return "none";
|
||
default: return "unknown";
|
||
}
|
||
}
|
||
|
||
const char *allocator_to_string(AllocatorType a)
|
||
{
|
||
switch (a) {
|
||
case ALLOC_JEMALLOC: return "jemalloc";
|
||
case ALLOC_MALLOC: return "malloc";
|
||
case ALLOC_MYPOOL: return "mypool";
|
||
case ALLOC_OTHER: return "other";
|
||
default: return "unknown";
|
||
}
|
||
}
|
||
|
||
const char *leakage_to_string(MemLeakDetectMode a)
|
||
{
|
||
switch (a) {
|
||
case MEMLEAK_DETECT_ON: return "enable";
|
||
case MEMLEAK_DETECT_OFF: return "disable";
|
||
default: return "unknown";
|
||
}
|
||
}
|
||
|
||
/* ---- 主函数:从 XML 加载配置 ---- */
|
||
|
||
/* server 部分 */
|
||
void server_load(xmlNodePtr *root, AppConfig *out_cfg){
|
||
xmlNodePtr server = find_child(*root, "server");
|
||
if (server) {
|
||
/* ip */
|
||
xmlNodePtr ip_node = find_child(server, "ip");
|
||
if (ip_node) {
|
||
xmlChar *txt = xmlNodeGetContent(ip_node);
|
||
if (txt) {
|
||
strncpy(out_cfg->ip, (char *)txt, sizeof(out_cfg->ip) - 1);
|
||
out_cfg->ip[sizeof(out_cfg->ip) - 1] = '\0';
|
||
xmlFree(txt);
|
||
}
|
||
}
|
||
|
||
/* port */
|
||
xmlNodePtr port_node = find_child(server, "port");
|
||
if (port_node) {
|
||
xmlChar *txt = xmlNodeGetContent(port_node);
|
||
if (txt) {
|
||
out_cfg->port = atoi((char *)txt);
|
||
xmlFree(txt);
|
||
}
|
||
}
|
||
|
||
/* mode: master / slave */
|
||
xmlNodePtr mode_node = find_child(server, "mode");
|
||
if (mode_node) {
|
||
xmlChar *txt = xmlNodeGetContent(mode_node);
|
||
if (txt) {
|
||
parse_server_mode((char *)txt, &out_cfg->mode);
|
||
xmlFree(txt);
|
||
}
|
||
}
|
||
|
||
/* master (always read if present) */
|
||
xmlNodePtr master = find_child(server, "master");
|
||
if (master) {
|
||
xmlNodePtr mip_node = find_child(master, "ip");
|
||
if (mip_node) {
|
||
xmlChar *txt = xmlNodeGetContent(mip_node);
|
||
if (txt) {
|
||
strncpy(out_cfg->master_ip, (char *)txt, sizeof(out_cfg->master_ip) - 1);
|
||
out_cfg->master_ip[sizeof(out_cfg->master_ip) - 1] = '\0';
|
||
xmlFree(txt);
|
||
}
|
||
}
|
||
|
||
xmlNodePtr mport_node = find_child(master, "port");
|
||
if (mport_node) {
|
||
xmlChar *txt = xmlNodeGetContent(mport_node);
|
||
if (txt) {
|
||
out_cfg->master_port = atoi((char *)txt);
|
||
xmlFree(txt);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* log 部分 */
|
||
void log_load(xmlNodePtr *root, AppConfig *out_cfg){
|
||
xmlNodePtr log = find_child(*root, "log");
|
||
if (log) {
|
||
xmlNodePtr lvl_node = find_child(log, "level");
|
||
if (lvl_node) {
|
||
xmlChar *txt = xmlNodeGetContent(lvl_node);
|
||
if (txt) {
|
||
parse_log_level((char *)txt, &out_cfg->log_level);
|
||
xmlFree(txt);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* persistence 部分 */
|
||
void persist_load(xmlNodePtr *root, AppConfig *out_cfg){
|
||
xmlNodePtr pers = find_child(*root, "persistence");
|
||
if (pers) {
|
||
xmlNodePtr type_node = find_child(pers, "type");
|
||
if (type_node) {
|
||
xmlChar *txt = xmlNodeGetContent(type_node);
|
||
if (txt) {
|
||
parse_persistence((char *)txt, &out_cfg->persistence);
|
||
xmlFree(txt);
|
||
}
|
||
}
|
||
|
||
xmlNodePtr dir_node = find_child(pers, "dir");
|
||
if (dir_node) {
|
||
xmlChar *txt = xmlNodeGetContent(dir_node);
|
||
if (txt) {
|
||
strncpy(out_cfg->persist_dir, (char *)txt, sizeof(out_cfg->persist_dir) - 1);
|
||
out_cfg->persist_dir[sizeof(out_cfg->persist_dir) - 1] = '\0';
|
||
xmlFree(txt);
|
||
}
|
||
}
|
||
|
||
xmlNodePtr wal_node = find_child(pers, "wal");
|
||
if (wal_node) {
|
||
xmlChar *txt = xmlNodeGetContent(wal_node);
|
||
if (txt) {
|
||
strncpy(out_cfg->oplog_file, (char *)txt, sizeof(out_cfg->oplog_file) - 1);
|
||
out_cfg->oplog_file[sizeof(out_cfg->oplog_file) - 1] = '\0';
|
||
xmlFree(txt);
|
||
}
|
||
}
|
||
|
||
xmlNodePtr array_node = find_child(pers, "array");
|
||
if (array_node) {
|
||
xmlChar *txt = xmlNodeGetContent(array_node);
|
||
if (txt) {
|
||
strncpy(out_cfg->array_file, (char *)txt, sizeof(out_cfg->array_file) - 1);
|
||
out_cfg->array_file[sizeof(out_cfg->array_file) - 1] = '\0';
|
||
xmlFree(txt);
|
||
}
|
||
}
|
||
|
||
xmlNodePtr rbtree_node = find_child(pers, "rbtree");
|
||
if (rbtree_node) {
|
||
xmlChar *txt = xmlNodeGetContent(rbtree_node);
|
||
if (txt) {
|
||
strncpy(out_cfg->rbtree_file, (char *)txt, sizeof(out_cfg->rbtree_file) - 1);
|
||
out_cfg->rbtree_file[sizeof(out_cfg->rbtree_file) - 1] = '\0';
|
||
xmlFree(txt);
|
||
}
|
||
}
|
||
|
||
xmlNodePtr hash_node = find_child(pers, "hash");
|
||
if (hash_node) {
|
||
xmlChar *txt = xmlNodeGetContent(hash_node);
|
||
if (txt) {
|
||
strncpy(out_cfg->hash_file, (char *)txt, sizeof(out_cfg->hash_file) - 1);
|
||
out_cfg->hash_file[sizeof(out_cfg->hash_file) - 1] = '\0';
|
||
xmlFree(txt);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* memory 部分 */
|
||
void memory_load(xmlNodePtr *root, AppConfig *out_cfg){
|
||
xmlNodePtr mem = find_child(*root, "memory");
|
||
if (mem) {
|
||
xmlNodePtr alloc_node = find_child(mem, "allocator");
|
||
if (alloc_node) {
|
||
xmlChar *txt = xmlNodeGetContent(alloc_node);
|
||
if (txt) {
|
||
parse_allocator((char *)txt, &out_cfg->allocator);
|
||
xmlFree(txt);
|
||
}
|
||
}
|
||
|
||
xmlNodePtr leakage_node = find_child(mem, "leakage");
|
||
if (leakage_node) {
|
||
xmlChar *txt = xmlNodeGetContent(leakage_node);
|
||
if (txt) {
|
||
parse_leakage((char *)txt, &out_cfg->leak_mode);
|
||
xmlFree(txt);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
int config_load(const char *filename, AppConfig *out_cfg)
|
||
{
|
||
if (!filename || !out_cfg) return -1;
|
||
|
||
set_default_config(out_cfg);
|
||
|
||
int rc = -1;
|
||
xmlDocPtr doc = NULL;
|
||
|
||
int fd = -1;
|
||
void *addr = NULL;
|
||
size_t len = 0;
|
||
|
||
if (read_file_mmap(filename, &addr, &len, &fd) != 0) {
|
||
// read_file_mmap 已经打印错误
|
||
return -1;
|
||
}
|
||
|
||
/*
|
||
* 用 xmlReadMemory 从内存解析。
|
||
* - "UTF-8":你原来指定了 UTF-8;如果希望自动探测,可以传 NULL。
|
||
* - XML_PARSE_NONET:禁用网络访问(防 XXE/外部实体拉取)
|
||
* - XML_PARSE_NOBLANKS:保持你原来的行为
|
||
* 你也可以加 XML_PARSE_NOERROR | XML_PARSE_NOWARNING 减少噪音,但调试阶段不建议。
|
||
*/
|
||
int parse_opts = XML_PARSE_NOBLANKS | XML_PARSE_NONET;
|
||
|
||
|
||
// xmlDocPtr doc = xmlReadFile(filename, "UTF-8", XML_PARSE_NOBLANKS);
|
||
doc = xmlReadMemory((const char *)addr, (int)len, filename, "UTF-8", parse_opts);
|
||
if (!doc) {
|
||
fprintf(stderr, "config_load: failed to read file %s\n", filename);
|
||
goto cleanup;
|
||
}
|
||
|
||
xmlNodePtr root = xmlDocGetRootElement(doc);
|
||
if (!root || xmlStrcmp(root->name, BAD_CAST "config") != 0) {
|
||
fprintf(stderr, "config_load: root element is not <config>\n");
|
||
goto cleanup;
|
||
}
|
||
|
||
server_load(&root, out_cfg);
|
||
|
||
log_load(&root, out_cfg);
|
||
|
||
persist_load(&root, out_cfg);
|
||
|
||
memory_load(&root, out_cfg);
|
||
|
||
rc = 0;
|
||
cleanup:
|
||
if (doc) xmlFreeDoc(doc);
|
||
if (addr && len) munmap(addr, len);
|
||
if (fd >= 0) close(fd);
|
||
return rc;
|
||
}
|