Files
systemd/src/shared/conf-parser.h
2025-05-25 03:28:50 +09:00

497 lines
24 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <syslog.h>
#include "alloc-util.h"
#include "conf-parser-forward.h"
#include "forward.h"
#include "log.h"
/* An abstract parser for simple, line based, shallow configuration files consisting of variable assignments only. */
typedef enum ConfigParseFlags {
CONFIG_PARSE_RELAXED = 1 << 0, /* Do not warn about unknown non-extension fields */
CONFIG_PARSE_WARN = 1 << 1, /* Emit non-debug messages */
} ConfigParseFlags;
/* Wraps information for parsing a specific configuration variable, to
* be stored in a simple array */
typedef struct ConfigTableItem {
const char *section; /* Section */
const char *lvalue; /* Name of the variable */
ConfigParserCallback parse; /* Function that is called to parse the variable's value */
int ltype; /* Distinguish different variables passed to the same callback */
void *data; /* Where to store the variable's data */
} ConfigTableItem;
/* Wraps information for parsing a specific configuration variable, to
* be stored in a gperf perfect hashtable */
typedef struct ConfigPerfItem {
const char *section_and_lvalue; /* Section + "." + name of the variable */
ConfigParserCallback parse; /* Function that is called to parse the variable's value */
int ltype; /* Distinguish different variables passed to the same callback */
size_t offset; /* Offset where to store data, from the beginning of userdata */
} ConfigPerfItem;
/* Prototype for a low-level gperf lookup function */
typedef const ConfigPerfItem* (*ConfigPerfItemLookup)(const char *section_and_lvalue, GPERF_LEN_TYPE length);
/* Prototype for a generic high-level lookup function */
typedef int (*ConfigItemLookup)(
const void *table,
const char *section,
const char *lvalue,
ConfigParserCallback *ret_func,
int *ret_ltype,
void **ret_data,
void *userdata);
/* Linear table search implementation of ConfigItemLookup, based on
* ConfigTableItem arrays */
int config_item_table_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *ret_func, int *ret_ltype, void **ret_data, void *userdata);
/* gperf implementation of ConfigItemLookup, based on gperf
* ConfigPerfItem tables */
int config_item_perf_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *ret_func, int *ret_ltype, void **ret_data, void *userdata);
int config_parse(
const char *unit,
const char *filename,
FILE *f,
const char *sections, /* nulstr */
ConfigItemLookup lookup,
const void *table,
ConfigParseFlags flags,
void *userdata,
struct stat *ret_stat); /* possibly NULL */
int config_parse_many(
const char* const* conf_files, /* possibly empty */
const char* const* conf_file_dirs,
const char *dropin_dirname,
const char *root,
const char *sections, /* nulstr */
ConfigItemLookup lookup,
const void *table,
ConfigParseFlags flags,
void *userdata,
Hashmap **ret_stats_by_path, /* possibly NULL */
char ***ret_drop_in_files); /* possibly NULL */
int config_parse_standard_file_with_dropins_full(
const char *root,
const char *main_file, /* A path like "systemd/frobnicator.conf" */
const char *sections,
ConfigItemLookup lookup,
const void *table,
ConfigParseFlags flags,
void *userdata,
Hashmap **ret_stats_by_path, /* possibly NULL */
char ***ret_dropin_files); /* possibly NULL */
static inline int config_parse_standard_file_with_dropins(
const char *main_file, /* A path like "systemd/frobnicator.conf" */
const char *sections, /* nulstr */
ConfigItemLookup lookup,
const void *table,
ConfigParseFlags flags,
void *userdata) {
return config_parse_standard_file_with_dropins_full(
/* root= */ NULL,
main_file,
sections,
lookup,
table,
flags,
userdata,
/* ret_stats_by_path= */ NULL,
/* ret_dropin_files= */ NULL);
}
int config_get_stats_by_path(
const char *suffix,
const char *root,
unsigned flags,
const char* const* dirs,
bool check_dropins,
Hashmap **ret);
int hashmap_put_stats_by_path(Hashmap **stats_by_path, const char *path, const struct stat *st);
bool stats_by_path_equal(Hashmap *a, Hashmap *b);
typedef struct ConfigSectionParser {
ConfigParserCallback parser;
int ltype;
size_t offset;
} ConfigSectionParser;
int config_section_parse(
const ConfigSectionParser *parsers,
size_t n_parsers,
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *userdata);
static inline ConfigSection* config_section_free(ConfigSection *cs) {
return mfree(cs);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(ConfigSection*, config_section_free);
int config_section_new(const char *filename, unsigned line, ConfigSection **ret);
void config_section_hash_func(const ConfigSection *c, struct siphash *state);
int config_section_compare_func(const ConfigSection *x, const ConfigSection *y);
extern const struct hash_ops config_section_hash_ops;
int hashmap_by_section_find_unused_line(Hashmap *entries_by_section, const char *filename, unsigned *ret);
int ordered_hashmap_by_section_find_unused_line(OrderedHashmap *entries_by_section, const char *filename, unsigned *ret);
static inline bool section_is_invalid(ConfigSection *section) {
/* If this returns false, then it does _not_ mean the section is valid. */
if (!section)
return false;
return section->invalid;
}
#define log_section_full_errno_zerook(section, level, error, ...) \
({ \
const ConfigSection *_s = (section); \
log_syntax(/* unit = */ NULL, \
level, \
_s ? _s->filename : NULL, \
_s ? _s->line : 0, \
error, \
__VA_ARGS__); \
})
#define log_section_full_errno(section, level, error, ...) \
({ \
int _error = (error); \
ASSERT_NON_ZERO(_error); \
log_section_full_errno_zerook(section, level, _error, __VA_ARGS__); \
})
#define log_section_full(section, level, fmt, ...) \
({ \
if (BUILD_MODE_DEVELOPER) \
assert(!strstr(fmt, "%m")); \
(void) log_section_full_errno_zerook(section, level, 0, fmt, ##__VA_ARGS__); \
})
#define log_section_debug(section, ...) log_section_full(section, LOG_DEBUG, __VA_ARGS__)
#define log_section_info(section, ...) log_section_full(section, LOG_INFO, __VA_ARGS__)
#define log_section_notice(section, ...) log_section_full(section, LOG_NOTICE, __VA_ARGS__)
#define log_section_warning(section, ...) log_section_full(section, LOG_WARNING, __VA_ARGS__)
#define log_section_error(section, ...) log_section_full(section, LOG_ERR, __VA_ARGS__)
#define log_section_debug_errno(section, error, ...) log_section_full_errno(section, LOG_DEBUG, error, __VA_ARGS__)
#define log_section_info_errno(section, error, ...) log_section_full_errno(section, LOG_INFO, error, __VA_ARGS__)
#define log_section_notice_errno(section, error, ...) log_section_full_errno(section, LOG_NOTICE, error, __VA_ARGS__)
#define log_section_warning_errno(section, error, ...) log_section_full_errno(section, LOG_WARNING, error, __VA_ARGS__)
#define log_section_error_errno(section, error, ...) log_section_full_errno(section, LOG_ERR, error, __VA_ARGS__)
CONFIG_PARSER_PROTOTYPE(config_parse_int);
CONFIG_PARSER_PROTOTYPE(config_parse_unsigned);
CONFIG_PARSER_PROTOTYPE(config_parse_long);
CONFIG_PARSER_PROTOTYPE(config_parse_uint8);
CONFIG_PARSER_PROTOTYPE(config_parse_uint16);
CONFIG_PARSER_PROTOTYPE(config_parse_uint32);
CONFIG_PARSER_PROTOTYPE(config_parse_int32);
CONFIG_PARSER_PROTOTYPE(config_parse_uint64);
CONFIG_PARSER_PROTOTYPE(config_parse_double);
CONFIG_PARSER_PROTOTYPE(config_parse_iec_size);
CONFIG_PARSER_PROTOTYPE(config_parse_si_uint64);
CONFIG_PARSER_PROTOTYPE(config_parse_iec_uint64);
CONFIG_PARSER_PROTOTYPE(config_parse_iec_uint64_infinity);
CONFIG_PARSER_PROTOTYPE(config_parse_bool);
CONFIG_PARSER_PROTOTYPE(config_parse_uint32_flag);
CONFIG_PARSER_PROTOTYPE(config_parse_uint32_invert_flag);
CONFIG_PARSER_PROTOTYPE(config_parse_id128);
CONFIG_PARSER_PROTOTYPE(config_parse_tristate);
CONFIG_PARSER_PROTOTYPE(config_parse_string);
CONFIG_PARSER_PROTOTYPE(config_parse_dns_name);
CONFIG_PARSER_PROTOTYPE(config_parse_hostname);
CONFIG_PARSER_PROTOTYPE(config_parse_path);
CONFIG_PARSER_PROTOTYPE(config_parse_strv);
CONFIG_PARSER_PROTOTYPE(config_parse_sec);
CONFIG_PARSER_PROTOTYPE(config_parse_sec_def_infinity);
CONFIG_PARSER_PROTOTYPE(config_parse_sec_def_unset);
CONFIG_PARSER_PROTOTYPE(config_parse_nsec);
CONFIG_PARSER_PROTOTYPE(config_parse_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_warn_compat);
CONFIG_PARSER_PROTOTYPE(config_parse_log_facility);
CONFIG_PARSER_PROTOTYPE(config_parse_log_level);
CONFIG_PARSER_PROTOTYPE(config_parse_signal);
CONFIG_PARSER_PROTOTYPE(config_parse_personality);
CONFIG_PARSER_PROTOTYPE(config_parse_permille);
CONFIG_PARSER_PROTOTYPE(config_parse_ifname);
CONFIG_PARSER_PROTOTYPE(config_parse_ifnames);
CONFIG_PARSER_PROTOTYPE(config_parse_ip_port);
CONFIG_PARSER_PROTOTYPE(config_parse_mtu);
CONFIG_PARSER_PROTOTYPE(config_parse_rlimit);
CONFIG_PARSER_PROTOTYPE(config_parse_vlanprotocol);
CONFIG_PARSER_PROTOTYPE(config_parse_hw_addr);
CONFIG_PARSER_PROTOTYPE(config_parse_hw_addrs);
CONFIG_PARSER_PROTOTYPE(config_parse_ether_addr);
CONFIG_PARSER_PROTOTYPE(config_parse_ether_addrs);
CONFIG_PARSER_PROTOTYPE(config_parse_in_addr_non_null);
CONFIG_PARSER_PROTOTYPE(config_parse_in_addr_data);
CONFIG_PARSER_PROTOTYPE(config_parse_in_addr_prefix);
CONFIG_PARSER_PROTOTYPE(config_parse_percent);
CONFIG_PARSER_PROTOTYPE(config_parse_permyriad);
CONFIG_PARSER_PROTOTYPE(config_parse_pid);
CONFIG_PARSER_PROTOTYPE(config_parse_sec_fix_0);
CONFIG_PARSER_PROTOTYPE(config_parse_timezone);
CONFIG_PARSER_PROTOTYPE(config_parse_calendar);
CONFIG_PARSER_PROTOTYPE(config_parse_ip_protocol);
CONFIG_PARSER_PROTOTYPE(config_parse_loadavg);
typedef enum Disabled {
DISABLED_CONFIGURATION,
DISABLED_LEGACY,
DISABLED_EXPERIMENTAL,
} Disabled;
typedef enum ConfigParseStringFlags {
CONFIG_PARSE_STRING_SAFE = 1 << 0,
CONFIG_PARSE_STRING_ASCII = 1 << 1,
CONFIG_PARSE_STRING_SAFE_AND_ASCII = CONFIG_PARSE_STRING_SAFE | CONFIG_PARSE_STRING_ASCII,
} ConfigParseStringFlags;
#define DEFINE_CONFIG_PARSE(function, parser) \
CONFIG_PARSER_PROTOTYPE(function) { \
int *i = data, r; \
\
assert(filename); \
assert(lvalue); \
assert(rvalue); \
assert(data); \
\
r = parser(rvalue); \
if (r < 0) \
return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); \
\
*i = r; \
return 1; \
}
#define DEFINE_CONFIG_PARSE_PTR(function, parser, type) \
CONFIG_PARSER_PROTOTYPE(function) { \
type *i = ASSERT_PTR(data); \
int r; \
\
assert(filename); \
assert(lvalue); \
assert(rvalue); \
\
r = parser(rvalue, i); \
if (r < 0) \
return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); \
\
return 1; \
}
#define DEFINE_CONFIG_PARSE_ENUM_FULL(function, from_string, type) \
CONFIG_PARSER_PROTOTYPE(function) { \
type *i = data, x; \
\
assert(filename); \
assert(lvalue); \
assert(rvalue); \
assert(data); \
\
x = from_string(rvalue); \
if (x < 0) \
return log_syntax_parse_error(unit, filename, line, x, lvalue, rvalue); \
\
*i = x; \
return 1; \
}
#define DEFINE_CONFIG_PARSE_ENUM(function, name, type) \
DEFINE_CONFIG_PARSE_ENUM_FULL(function, name##_from_string, type)
#define DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(function, name, type, default_value) \
CONFIG_PARSER_PROTOTYPE(function) { \
type *i = data, x; \
\
assert(filename); \
assert(lvalue); \
assert(rvalue); \
assert(data); \
\
if (isempty(rvalue)) { \
*i = default_value; \
return 1; \
} \
\
x = name##_from_string(rvalue); \
if (x < 0) \
return log_syntax_parse_error(unit, filename, line, x, lvalue, rvalue); \
\
*i = x; \
return 1; \
}
#define DEFINE_CONFIG_PARSE_ENUMV(function, name, type, invalid) \
CONFIG_PARSER_PROTOTYPE(function) { \
type **enums = ASSERT_PTR(data); \
_cleanup_free_ type *xs = NULL; \
size_t n = 0; \
int r; \
\
assert(lvalue); \
\
for (const char *p = rvalue;;) { \
_cleanup_free_ char *en = NULL; \
type x; \
\
r = extract_first_word(&p, &en, NULL, 0); \
if (r < 0) \
return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); \
if (r == 0) \
break; \
\
x = name##_from_string(en); \
if (x < 0) { \
log_syntax(unit, LOG_WARNING, filename, line, x, \
"Failed to parse %s in %s=, ignoring.", \
en, lvalue); \
continue; \
} \
\
FOREACH_ARRAY(i, xs, n) \
if (*i == x) { \
log_syntax(unit, LOG_NOTICE, filename, line, 0, \
"Duplicate entry %s in %s=, ignoring.", \
en, lvalue); \
x = invalid; \
break; \
} \
\
if (x == invalid) \
continue; \
\
/* Allocate one more for the trailing 'invalid'. */ \
if (!GREEDY_REALLOC(xs, n + 2)) \
return log_oom(); \
\
xs[n++] = x; \
} \
\
if (n <= 0) { \
/* An empty string, or invalid values only. */ \
*enums = mfree(*enums); \
return 1; \
} \
\
/* Terminate with 'invalid' */ \
xs[n] = invalid; \
free_and_replace(*enums, xs); \
return 1; \
}
int config_parse_unsigned_bounded(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
const char *rvalue,
unsigned min,
unsigned max,
bool ignoring,
unsigned *ret);
static inline int config_parse_uint32_bounded(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
const char *rvalue,
uint32_t min,
uint32_t max,
bool ignoring,
uint32_t *ret) {
unsigned t;
int r;
r = config_parse_unsigned_bounded(
unit, filename, line, section, section_line, lvalue, rvalue,
min, max, ignoring,
&t);
if (r <= 0)
return r;
assert(t <= UINT32_MAX);
*ret = t;
return 1;
}
static inline int config_parse_uint16_bounded(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
const char *rvalue,
uint16_t min,
uint16_t max,
bool ignoring,
uint16_t *ret) {
unsigned t;
int r;
r = config_parse_unsigned_bounded(
unit, filename, line, section, section_line, lvalue, rvalue,
min, max, ignoring,
&t);
if (r <= 0)
return r;
assert(t <= UINT16_MAX);
*ret = t;
return 1;
}
static inline int config_parse_uint8_bounded(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
const char *rvalue,
uint8_t min,
uint8_t max,
bool ignoring,
uint8_t *ret) {
unsigned t;
int r;
r = config_parse_unsigned_bounded(
unit, filename, line, section, section_line, lvalue, rvalue,
min, max, ignoring,
&t);
if (r <= 0)
return r;
assert(t <= UINT8_MAX);
*ret = t;
return 1;
}