From 45bd01544109ec3bafd0cc42b06a05d01702faf2 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 7 Aug 2026 22:45:26 +0900 Subject: [PATCH] sysctl: add --save= option to save loaded settings This may be useful for saving sysctl settings provided on the command line, via standard input, or with --inline. --- src/sysctl/sysctl.c | 123 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 3 deletions(-) diff --git a/src/sysctl/sysctl.c b/src/sysctl/sysctl.c index f14f885773..3a7f64fc14 100644 --- a/src/sysctl/sysctl.c +++ b/src/sysctl/sysctl.c @@ -10,16 +10,20 @@ #include "constants.h" #include "creds-util.h" #include "errno-util.h" +#include "fs-util.h" #include "glob-util.h" #include "hashmap.h" #include "log.h" #include "main-func.h" +#include "mkdir.h" +#include "options.h" #include "pager.h" #include "path-util.h" #include "pretty-print.h" #include "string-util.h" #include "strv.h" #include "sysctl-util.h" +#include "tmpfile-util.h" #include "verbs.h" static char **arg_prefixes = NULL; @@ -28,8 +32,10 @@ static bool arg_strict = false; static bool arg_verify = false; static bool arg_inline = false; static PagerFlags arg_pager_flags = 0; +static char *arg_save_file = NULL; STATIC_DESTRUCTOR_REGISTER(arg_prefixes, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_save_file, freep); COMMAND( "systemd-sysctl\0", @@ -226,6 +232,13 @@ static int apply_all(OrderedHashmap *sysctl_options) { return r; } +static void filter_sysctl_options(OrderedHashmap *sysctl_options) { + SysctlOption *option; + ORDERED_HASHMAP_FOREACH(option, sysctl_options) + if (!string_is_glob(option->key) && !test_prefix(option->key)) + sysctl_option_free(ordered_hashmap_remove(sysctl_options, option->key)); +} + 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; @@ -262,9 +275,19 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool 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; + if (arg_save_file) { + /* When saving the loaded rules, refuse unsafe characters, especially newlines, in keys + * and values. Otherwise, the saved file may not be parsed correctly. */ + if (!string_is_safe(key, STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS) || + (value && !string_is_safe(value, STRING_ALLOW_EMPTY | STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS))) + return log_syntax(NULL, LOG_WARNING, fname, line, SYNTHETIC_ERRNO(EINVAL), + "Ignoring unsafe key and/or value for '%s'.", key); + } else { + /* We can't filter out globs at this point, we'll need to do that later. + * Keep them when saving the loaded rules, and filter them after writing. */ + if (!string_is_glob(key) && !test_prefix(key)) + return 0; + } SysctlOption *existing = ordered_hashmap_get(*sysctl_options, key); if (existing) { @@ -325,7 +348,82 @@ static int cat_config(char **files) { return cat_files(NULL, files, arg_cat_flags); } +static int save_sysctl_options(OrderedHashmap *sysctl_options, const char *filename) { + int r; + + assert(filename); + + if (ordered_hashmap_isempty(sysctl_options)) { + log_info("No sysctl settings loaded, not creating '%s'.", filename); + return 0; + } + + r = mkdir_p("/run/sysctl.d/", 0755); + if (r < 0) + return log_error_errno(r, "Failed to create directory '/run/sysctl.d/': %m"); + + _cleanup_free_ char *path = path_join("/run/sysctl.d/", filename); + if (!path) + return log_oom(); + + _cleanup_fclose_ FILE *f = NULL; + _cleanup_(unlink_and_freep) char *temp_path = NULL; + r = fopen_temporary(path, &f, &temp_path); + if (r < 0) + return log_error_errno(r, "Failed to open a temporary file for '%s': %m", path); + + if (fchmod(fileno(f), 0644) < 0) + return log_error_errno(errno, "Failed to chmod '%s': %m", temp_path); + + fputs("# This is generated by systemd-sysctl.\n", f); + + SysctlOption *option; + ORDERED_HASHMAP_FOREACH(option, sysctl_options) + fprintf(f, "%s%s%s%s\n", + option->ignore_failure || !option->value ? "-" : "", + option->key, + option->value ? " = " : "", + strempty(option->value)); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to flush '%s': %m", temp_path); + + r = conservative_rename(temp_path, path); + if (r < 0) + return log_error_errno(r, "Failed to rename '%s' to '%s': %m", temp_path, path); + + temp_path = mfree(temp_path); + return 0; +} + +static int parse_conf_filename(const char *src, const char *option, char **dest) { + assert(option); + assert(dest); + + if (isempty(src)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use an empty filename for %s.", option); + + if (startswith(src, ".")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Dot-prefixed filenames are not allowed for %s.", option); + + _cleanup_free_ char *name = NULL; + if (!endswith(src, ".conf")) + name = strjoin(src, ".conf"); + else + name = strdup(src); + if (!name) + return log_oom(); + + if (!filename_is_valid(name)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid file name '%s' specified for %s.", name, option); + + return free_and_replace(*dest, name); +} + static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; + assert(argc >= 0); assert(argv); assert(remaining_args); @@ -391,6 +489,13 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { arg_inline = true; break; + OPTION_LONG("save", "FILENAME", + "Write loaded settings to the specified file"): + r = parse_conf_filename(opts.arg, "--save=", &arg_save_file); + if (r < 0) + return r; + break; + OPTION_COMMON_INTROSPECT_CLI: return introspect_cli(SD_JSON_FORMAT_OFF); } @@ -405,6 +510,10 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--inline requires positional arguments."); + if (arg_save_file && strv_isempty(*remaining_args)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--save= requires positional arguments."); + return 1; } @@ -437,6 +546,14 @@ static int run(int argc, char *argv[]) { } else RET_GATHER(r, parse_file(&sysctl_options, *arg, false)); } + + if (arg_save_file) { + r = save_sysctl_options(sysctl_options, arg_save_file); + if (r < 0) + return r; + + filter_sysctl_options(sysctl_options); + } } else { _cleanup_strv_free_ char **files = NULL;