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 - <