diff --git a/man/systemd-sysctl.service.xml b/man/systemd-sysctl.service.xml index 720beed8ee..61fe5238b7 100644 --- a/man/systemd-sysctl.service.xml +++ b/man/systemd-sysctl.service.xml @@ -42,8 +42,12 @@ When invoked with no arguments, /usr/lib/systemd/systemd-sysctl applies all directives from configuration files listed in sysctl.d5. - If one or more filenames are passed on the command line, only the directives in these files are - applied. + When invoked with positional arguments, the configuration specified by the command line arguments is + executed. If the string - is specified instead of a filename, the configuration is + read from standard input. If the argument is a file name (without any slashes), all configuration + directories are searched for a matching file and the file found that has the highest priority is + executed. If the argument is a path, that file is used directly without searching the configuration + directories for any other matching file. In addition, option may be used to limit which sysctl settings are applied. @@ -77,6 +81,14 @@ + + + Treat each positional argument as a separate configuration line instead of a file + name. + + + + diff --git a/src/sysctl/sysctl.c b/src/sysctl/sysctl.c index 7ad56b3ae9..d761c7375a 100644 --- a/src/sysctl/sysctl.c +++ b/src/sysctl/sysctl.c @@ -26,6 +26,7 @@ static char **arg_prefixes = NULL; static CatFlags arg_cat_flags = CAT_CONFIG_OFF; static bool arg_strict = false; +static bool arg_inline = false; static PagerFlags arg_pager_flags = 0; STATIC_DESTRUCTOR_REGISTER(arg_prefixes, strv_freep); @@ -36,7 +37,7 @@ typedef struct Option { bool ignore_failure; } Option; -static Option *option_free(Option *o) { +static Option* option_free(Option *o) { if (!o) return NULL; @@ -47,7 +48,10 @@ static Option *option_free(Option *o) { } DEFINE_TRIVIAL_CLEANUP_FUNC(Option*, option_free); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(option_hash_ops, char, string_hash_func, string_compare_func, Option, option_free); +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + option_hash_ops, + char, string_hash_func, string_compare_func, + Option, option_free); static bool test_prefix(const char *p) { if (strv_isempty(arg_prefixes)) @@ -56,7 +60,7 @@ static bool test_prefix(const char *p) { return path_startswith_strv(p, arg_prefixes); } -static Option *option_new( +static Option* option_new( const char *key, const char *value, bool ignore_failure) { @@ -155,14 +159,13 @@ static int apply_glob_option_with_prefix(OrderedHashmap *sysctl_options, Option if (option->ignore_failure || ERRNO_IS_PRIVILEGE(r)) { log_debug_errno(r, "Failed to resolve glob '%s', ignoring: %m", option->key); return 0; - } else - return log_error_errno(r, "Couldn't resolve glob '%s': %m", option->key); + } + + return log_error_errno(r, "Couldn't resolve glob '%s': %m", option->key); } STRV_FOREACH(s, paths) { - const char *key; - - assert_se(key = path_startswith(*s, "/proc/sys")); + const char *key = ASSERT_SE_PTR(path_startswith(*s, "/proc/sys")); if (ordered_hashmap_contains(sysctl_options, key)) { log_debug("Not setting %s (explicit setting exists).", key); @@ -212,100 +215,79 @@ static int apply_all(OrderedHashmap *sysctl_options) { return r; } -static int parse_file(OrderedHashmap **sysctl_options, const char *path, bool ignore_enoent) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *pp = NULL; - unsigned c = 0; +static int parse_line(const char *fname, unsigned line, const char *buffer, bool *invalid_config, void *userdata) { + OrderedHashmap **sysctl_options = ASSERT_PTR(userdata); + _cleanup_free_ char *k = NULL, *v = NULL; + bool ignore_failure = false; int r; - assert(path); - - r = search_and_fopen(path, "re", NULL, (const char**) CONF_PATHS_STRV("sysctl.d"), &f, &pp); - if (r < 0) { - if (ignore_enoent && r == -ENOENT) - return 0; - - return log_error_errno(r, "Failed to open file '%s', ignoring: %m", path); - } - - log_debug("Parsing %s", pp); - for (;;) { - _cleanup_(option_freep) Option *new_option = NULL; - _cleanup_free_ char *l = NULL; - bool ignore_failure = false; - Option *existing; - char *value; - int k; - - k = read_stripped_line(f, LONG_LINE_MAX, &l); - if (k == 0) - break; - if (k < 0) - return log_error_errno(k, "Failed to read file '%s', ignoring: %m", pp); - - c++; - - if (isempty(l)) - continue; - if (strchr(COMMENTS, l[0])) - continue; - - char *p = l; - value = strchr(p, '='); - if (value) { - if (p[0] == '-') { - ignore_failure = true; - p++; - } - - *value = 0; - value++; - value = strstrip(value); - - } else { - if (p[0] == '-') - /* We have a "negative match" option. Let's continue with value==NULL. */ - p++; - else { - log_syntax(NULL, LOG_WARNING, pp, c, 0, - "Line is not an assignment, ignoring: %s", p); - if (r == 0) - r = -EINVAL; - continue; - } + const char *eq = strchr(buffer, '='); + if (eq) { + if (buffer[0] == '-') { + ignore_failure = true; + buffer++; } - p = strstrip(p); - p = sysctl_normalize(p); - - /* We can't filter out globs at this point, we'll need to do that later. */ - if (!string_is_glob(p) && - !test_prefix(p)) - continue; - - existing = ordered_hashmap_get(*sysctl_options, p); - if (existing) { - if (streq_ptr(value, existing->value)) { - existing->ignore_failure = existing->ignore_failure || ignore_failure; - continue; - } - - log_debug("Overwriting earlier assignment of %s at '%s:%u'.", p, pp, c); - option_free(ordered_hashmap_remove(*sysctl_options, p)); - } - - new_option = option_new(p, value, ignore_failure); - if (!new_option) + k = strndup(buffer, eq - buffer); + if (!k) return log_oom(); - k = ordered_hashmap_ensure_put(sysctl_options, &option_hash_ops, new_option->key, new_option); - if (k < 0) - return log_error_errno(k, "Failed to add sysctl variable %s to hashmap: %m", p); + v = strdup(eq + 1); + if (!v) + return log_oom(); - TAKE_PTR(new_option); + } else { + if (buffer[0] == '-') + /* We have a "negative match" option. Let's continue with value==NULL. */ + buffer++; + else + return log_syntax(NULL, LOG_WARNING, fname, line, SYNTHETIC_ERRNO(EINVAL), + "Line is not an assignment, ignoring: %s", buffer); + + k = strdup(buffer); + if (!k) + return log_oom(); } - return r; + const char *key = sysctl_normalize(strstrip(k)), *value = strstrip(v); + + /* We can't filter out globs at this point, we'll need to do that later. */ + if (!string_is_glob(key) && !test_prefix(key)) + return 0; + + Option *existing = ordered_hashmap_get(*sysctl_options, key); + if (existing) { + if (streq_ptr(value, existing->value)) { + existing->ignore_failure = existing->ignore_failure || ignore_failure; + return 0; + } + + log_syntax(NULL, LOG_DEBUG, fname, line, 0, + "Overwriting earlier assignment of '%s'.", key); + option_free(ordered_hashmap_remove(*sysctl_options, key)); + } + + _cleanup_(option_freep) Option *option = option_new(key, value, ignore_failure); + if (!option) + return log_oom(); + + r = ordered_hashmap_ensure_put(sysctl_options, &option_hash_ops, option->key, option); + if (r < 0) + return log_error_errno(r, "Failed to add sysctl variable '%s' to hashmap: %m", key); + + TAKE_PTR(option); + return 0; +} + +static int parse_file(OrderedHashmap **sysctl_options, const char *path, bool ignore_enoent) { + return conf_file_read( + /* root = */ NULL, + (const char**) CONF_PATHS_STRV("sysctl.d"), + path, + parse_line, + sysctl_options, + ignore_enoent, + /* invalid_config = */ NULL); } static int read_credential_lines(OrderedHashmap **sysctl_options) { @@ -323,8 +305,7 @@ static int read_credential_lines(OrderedHashmap **sysctl_options) { if (!j) return log_oom(); - (void) parse_file(sysctl_options, j, /* ignore_enoent= */ true); - return 0; + return parse_file(sysctl_options, j, /* ignore_enoent= */ true); } static int cat_config(char **files) { @@ -341,16 +322,23 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" - "Applies kernel sysctl settings.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" + printf("%1$s [OPTIONS...] [CONFIGURATION FILE...]\n" + "\n%2$sApplies kernel sysctl settings.%4$s\n" + "\n%3$sCommands:%4$s\n" " --cat-config Show configuration files\n" " --tldr Show non-comment parts of configuration\n" + " -h --help Show this help\n" + " --version Show package version\n" + "\n%3$sOptions:%4$s\n" " --prefix=PATH Only apply rules with the specified prefix\n" " --no-pager Do not pipe output into a pager\n" - "\nSee the %s for details.\n", + " --strict Fail on any kind of failures\n" + " --inline Treat arguments as configuration lines\n" + "\nSee the %5$s for details.\n", program_invocation_short_name, + ansi_highlight(), + ansi_underline(), + ansi_normal(), link); return 0; @@ -365,6 +353,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_PREFIX, ARG_NO_PAGER, ARG_STRICT, + ARG_INLINE, }; static const struct option options[] = { @@ -375,6 +364,7 @@ static int parse_argv(int argc, char *argv[]) { { "prefix", required_argument, NULL, ARG_PREFIX }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, { "strict", no_argument, NULL, ARG_STRICT }, + { "inline", no_argument, NULL, ARG_INLINE }, {} }; @@ -430,6 +420,10 @@ static int parse_argv(int argc, char *argv[]) { arg_strict = true; break; + case ARG_INLINE: + arg_inline = true; + break; + case '?': return -EINVAL; @@ -457,11 +451,15 @@ static int run(int argc, char *argv[]) { umask(0022); if (argc > optind) { - r = 0; - - for (int i = optind; i < argc; i++) - RET_GATHER(r, parse_file(&sysctl_options, argv[i], false)); + unsigned pos = 0; + STRV_FOREACH(arg, strv_skip(argv, optind)) { + if (arg_inline) + /* Use (argument):n, where n==1 for the first positional arg */ + RET_GATHER(r, parse_line("(argument)", ++pos, *arg, /* invalid_config = */ NULL, &sysctl_options)); + else + RET_GATHER(r, parse_file(&sysctl_options, *arg, false)); + } } else { _cleanup_strv_free_ char **files = NULL; @@ -478,9 +476,7 @@ static int run(int argc, char *argv[]) { RET_GATHER(r, read_credential_lines(&sysctl_options)); } - RET_GATHER(r, apply_all(sysctl_options)); - - return r; + return RET_GATHER(r, apply_all(sysctl_options)); } DEFINE_MAIN_FUNCTION(run); diff --git a/test/integration-tests/TEST-76-SYSCTL/meson.build b/test/integration-tests/TEST-76-SYSCTL/meson.build deleted file mode 100644 index 8dec5f37e7..0000000000 --- a/test/integration-tests/TEST-76-SYSCTL/meson.build +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -integration_tests += [ - integration_test_template + { - 'name' : fs.name(meson.current_source_dir()), - }, -] diff --git a/test/integration-tests/meson.build b/test/integration-tests/meson.build index 5965f6646c..5d71e87c79 100644 --- a/test/integration-tests/meson.build +++ b/test/integration-tests/meson.build @@ -90,7 +90,6 @@ foreach dirname : [ 'TEST-73-LOCALE', 'TEST-74-AUX-UTILS', 'TEST-75-RESOLVED', - 'TEST-76-SYSCTL', 'TEST-78-SIGQUEUE', 'TEST-79-MEMPRESS', 'TEST-80-NOTIFYACCESS', diff --git a/test/units/TEST-76-SYSCTL.sh b/test/units/TEST-76-SYSCTL.sh deleted file mode 100755 index 04d89b8252..0000000000 --- a/test/units/TEST-76-SYSCTL.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: LGPL-2.1-or-later -set -eux -set -o pipefail - -# shellcheck source=test/units/util.sh -. "$(dirname "$0")"/util.sh - -export SYSTEMD_LOG_LEVEL=debug - -echo "foo.bar=42" >/tmp/foo.conf -assert_rc 0 /usr/lib/systemd/systemd-sysctl /tmp/foo.conf -assert_rc 1 /usr/lib/systemd/systemd-sysctl --strict /tmp/foo.conf - -echo "-foo.foo=42" >/tmp/foo.conf -assert_rc 0 /usr/lib/systemd/systemd-sysctl /tmp/foo.conf -assert_rc 0 /usr/lib/systemd/systemd-sysctl --strict /tmp/foo.conf - -if ! systemd-detect-virt --quiet --container; then - ip link add hoge type dummy - udevadm wait --timeout=30 /sys/class/net/hoge - - cat >/tmp/foo.conf </proc/sys/net/ipv4/conf/hoge/drop_gratuitous_arp - echo 0 >/proc/sys/net/ipv4/conf/hoge/bootp_relay - echo 0 >/proc/sys/net/ipv4/conf/hoge/disable_policy - - assert_rc 0 /usr/lib/systemd/systemd-sysctl --prefix=/net/ipv4/conf/hoge /tmp/foo.conf - assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/drop_gratuitous_arp)" "1" - assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/bootp_relay)" "1" - assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/disable_policy)" "0" -fi - -touch /testok diff --git a/test/units/TEST-87-AUX-UTILS-VM.sysctl.sh b/test/units/TEST-87-AUX-UTILS-VM.sysctl.sh new file mode 100755 index 0000000000..f84621431c --- /dev/null +++ b/test/units/TEST-87-AUX-UTILS-VM.sysctl.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +export SYSTEMD_LOG_LEVEL=debug + +echo "foo.bar=42" >/tmp/foo.conf +assert_rc 0 /usr/lib/systemd/systemd-sysctl /tmp/foo.conf +assert_rc 1 /usr/lib/systemd/systemd-sysctl --strict /tmp/foo.conf + +echo "-foo.foo=42" >/tmp/foo.conf +assert_rc 0 /usr/lib/systemd/systemd-sysctl /tmp/foo.conf +assert_rc 0 /usr/lib/systemd/systemd-sysctl --strict /tmp/foo.conf + +assert_rc 0 /usr/lib/systemd/systemd-sysctl --inline "foo.bar=42" +assert_rc 1 /usr/lib/systemd/systemd-sysctl --inline --strict "foo.bar=42" +assert_rc 0 /usr/lib/systemd/systemd-sysctl --inline -- "-foo.bar=42" +assert_rc 0 /usr/lib/systemd/systemd-sysctl --inline --strict -- "-foo.bar=42" + +/usr/lib/systemd/systemd-sysctl - </tmp/foo.conf </proc/sys/net/ipv4/conf/hoge/drop_gratuitous_arp +echo 0 >/proc/sys/net/ipv4/conf/hoge/bootp_relay +echo 0 >/proc/sys/net/ipv4/conf/hoge/disable_policy + +assert_rc 0 /usr/lib/systemd/systemd-sysctl --prefix=/net/ipv4/conf/hoge /tmp/foo.conf +assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/drop_gratuitous_arp)" "1" +assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/bootp_relay)" "1" +assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/disable_policy)" "0" + +echo 0 >/proc/sys/net/ipv4/conf/hoge/drop_gratuitous_arp +echo 0 >/proc/sys/net/ipv4/conf/hoge/bootp_relay +echo 0 >/proc/sys/net/ipv4/conf/hoge/disable_policy + +assert_rc 0 /usr/lib/systemd/systemd-sysctl --prefix=/net/ipv4/conf/hoge --inline \ + 'net.ipv4.conf.*.drop_gratuitous_arp=1' \ + 'net.ipv4.*.*.bootp_relay=1' \ + 'net.ipv4.aaa.*.disable_policy=1' +assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/drop_gratuitous_arp)" "1" +assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/bootp_relay)" "1" +assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/disable_policy)" "0" + +echo 0 >/proc/sys/net/ipv4/conf/hoge/drop_gratuitous_arp +echo 0 >/proc/sys/net/ipv4/conf/hoge/bootp_relay +echo 0 >/proc/sys/net/ipv4/conf/hoge/disable_policy + +/usr/lib/systemd/systemd-sysctl --prefix=/net/ipv4/conf/hoge - <