/* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include #include #include #include #include #include "alloc-util.h" #include "hashmap.h" #include "log.h" #include "macro.h" #include "time-util.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; /* Argument list for parsers of specific configuration settings. */ #define CONFIG_PARSER_ARGUMENTS \ const char *unit, \ const char *filename, \ unsigned line, \ const char *section, \ unsigned section_line, \ const char *lvalue, \ int ltype, \ const char *rvalue, \ void *data, \ void *userdata /* Prototype for a parser for a specific configuration setting */ typedef int (*ConfigParserCallback)(CONFIG_PARSER_ARGUMENTS); /* A macro declaring a function prototype, following the typedef above, simply because it's so cumbersomely long * otherwise. (And current emacs gets irritatingly slow when editing files that contain lots of very long function * prototypes on the same screen…) */ #define CONFIG_PARSER_PROTOTYPE(name) int name(CONFIG_PARSER_ARGUMENTS) /* 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); typedef struct ConfigSection { unsigned line; bool invalid; char filename[]; } ConfigSection; 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( HashmapBase *entries_by_section, const char *filename, unsigned *ret); static inline int hashmap_by_section_find_unused_line( Hashmap *entries_by_section, const char *filename, unsigned *ret) { return _hashmap_by_section_find_unused_line(HASHMAP_BASE(entries_by_section), filename, ret); } static inline int ordered_hashmap_by_section_find_unused_line( OrderedHashmap *entries_by_section, const char *filename, unsigned *ret) { return _hashmap_by_section_find_unused_line(HASHMAP_BASE(entries_by_section), filename, 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 DEFINE_SECTION_CLEANUP_FUNCTIONS(type, free_func) \ static inline type* free_func##_or_set_invalid(type *p) { \ assert(p); \ \ if (p->section) \ p->section->invalid = true; \ else \ free_func(p); \ return NULL; \ } \ DEFINE_TRIVIAL_CLEANUP_FUNC(type*, free_func); \ DEFINE_TRIVIAL_CLEANUP_FUNC(type*, free_func##_or_set_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); 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; }