]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/sysctl/sysctl.c
Merge pull request #15840 from Werkov/mkosi-opensuse
[thirdparty/systemd.git] / src / sysctl / sysctl.c
index 1ced48b4db45fbdad5caa2422f57f48bbad64b37..5274cd24b38704f22d0e2de18d113d2e557f8c54 100644 (file)
 
 #include "conf-files.h"
 #include "def.h"
+#include "errno-util.h"
 #include "fd-util.h"
 #include "fileio.h"
+#include "glob-util.h"
 #include "hashmap.h"
 #include "log.h"
 #include "main-func.h"
@@ -48,6 +50,26 @@ 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);
 
+static bool test_prefix(const char *p) {
+        char **i;
+
+        if (strv_isempty(arg_prefixes))
+                return true;
+
+        STRV_FOREACH(i, arg_prefixes) {
+                const char *t;
+
+                t = path_startswith(*i, "/proc/sys/");
+                if (!t)
+                        t = *i;
+
+                if (path_startswith(p, t))
+                        return true;
+        }
+
+        return false;
+}
+
 static Option *option_new(
                 const char *key,
                 const char *value,
@@ -56,7 +78,6 @@ static Option *option_new(
         _cleanup_(option_freep) Option *o = NULL;
 
         assert(key);
-        assert(value);
 
         o = new(Option, 1);
         if (!o)
@@ -64,16 +85,38 @@ static Option *option_new(
 
         *o = (Option) {
                 .key = strdup(key),
-                .value = strdup(value),
+                .value = value ? strdup(value) : NULL,
                 .ignore_failure = ignore_failure,
         };
 
-        if (!o->key || !o->value)
+        if (!o->key)
+                return NULL;
+        if (value && !o->value)
                 return NULL;
 
         return TAKE_PTR(o);
 }
 
+static int sysctl_write_or_warn(const char *key, const char *value, bool ignore_failure) {
+        int r;
+
+        r = sysctl_write(key, value);
+        if (r < 0) {
+                /* If the sysctl is not available in the kernel or we are running with reduced privileges and
+                 * cannot write it, then log about the issue, and proceed without failing. (EROFS is treated
+                 * as a permission problem here, since that's how container managers usually protected their
+                 * sysctls.) In all other cases log an error and make the tool fail. */
+                if (ignore_failure || r == -EROFS || ERRNO_IS_PRIVILEGE(r))
+                        log_debug_errno(r, "Couldn't write '%s' to '%s', ignoring: %m", value, key);
+                else if (r == -ENOENT)
+                        log_info_errno(r, "Couldn't write '%s' to '%s', ignoring: %m", value, key);
+                else
+                        return log_error_errno(r, "Couldn't write '%s' to '%s': %m", value, key);
+        }
+
+        return 0;
+}
+
 static int apply_all(OrderedHashmap *sysctl_options) {
         Option *option;
         Iterator i;
@@ -82,44 +125,60 @@ static int apply_all(OrderedHashmap *sysctl_options) {
         ORDERED_HASHMAP_FOREACH(option, sysctl_options, i) {
                 int k;
 
-                k = sysctl_write(option->key, option->value);
-                if (k < 0) {
-                        /* If the sysctl is not available in the kernel or we are running with reduced
-                         * privileges and cannot write it, then log about the issue at LOG_NOTICE level, and
-                         * proceed without failing. (EROFS is treated as a permission problem here, since
-                         * that's how container managers usually protected their sysctls.) In all other cases
-                         * log an error and make the tool fail. */
+                /* Ignore "negative match" options, they are there only to exclude stuff from globs. */
+                if (!option->value)
+                        continue;
 
-                        if (IN_SET(k, -EPERM, -EACCES, -EROFS, -ENOENT) || option->ignore_failure)
-                                log_notice_errno(k, "Couldn't write '%s' to '%s', ignoring: %m", option->value, option->key);
-                        else {
-                                log_error_errno(k, "Couldn't write '%s' to '%s': %m", option->value, option->key);
-                                if (r == 0)
-                                        r = k;
-                        }
-                }
-        }
+                if (string_is_glob(option->key)) {
+                        _cleanup_strv_free_ char **paths = NULL;
+                        _cleanup_free_ char *pattern = NULL;
+                        char **s;
 
-        return r;
-}
+                        pattern = path_join("/proc/sys", option->key);
+                        if (!pattern)
+                                return log_oom();
 
-static bool test_prefix(const char *p) {
-        char **i;
+                        k = glob_extend(&paths, pattern, GLOB_NOCHECK);
+                        if (k < 0) {
+                                if (option->ignore_failure || ERRNO_IS_PRIVILEGE(k))
+                                        log_debug_errno(k, "Failed to resolve glob '%s', ignoring: %m",
+                                                        option->key);
+                                else {
+                                        log_error_errno(k, "Couldn't resolve glob '%s': %m",
+                                                        option->key);
+                                        if (r == 0)
+                                                r = k;
+                                }
 
-        if (strv_isempty(arg_prefixes))
-                return true;
+                        } else if (strv_isempty(paths))
+                                log_debug("No match for glob: %s", option->key);
 
-        STRV_FOREACH(i, arg_prefixes) {
-                const char *t;
+                        STRV_FOREACH(s, paths) {
+                                const char *key;
 
-                t = path_startswith(*i, "/proc/sys/");
-                if (!t)
-                        t = *i;
-                if (path_startswith(p, t))
-                        return true;
+                                assert_se(key = path_startswith(*s, "/proc/sys"));
+
+                                if (!test_prefix(key))
+                                        continue;
+
+                                if (ordered_hashmap_contains(sysctl_options, key)) {
+                                        log_info("Not setting %s (explicit setting exists).", key);
+                                        continue;
+                                }
+
+                                k = sysctl_write_or_warn(key, option->value, option->ignore_failure);
+                                if (r == 0)
+                                        r = k;
+                        }
+
+                } else {
+                        k = sysctl_write_or_warn(option->key, option->value, option->ignore_failure);
+                        if (r == 0)
+                                r = k;
+                }
         }
 
-        return false;
+        return r;
 }
 
 static int parse_file(OrderedHashmap **sysctl_options, const char *path, bool ignore_enoent) {
@@ -141,7 +200,7 @@ static int parse_file(OrderedHashmap **sysctl_options, const char *path, bool ig
         for (;;) {
                 _cleanup_(option_freep) Option *new_option = NULL;
                 _cleanup_free_ char *l = NULL;
-                bool ignore_failure;
+                bool ignore_failure = false;
                 Option *existing;
                 char *p, *value;
                 int k;
@@ -162,25 +221,35 @@ static int parse_file(OrderedHashmap **sysctl_options, const char *path, bool ig
                         continue;
 
                 value = strchr(p, '=');
-                if (!value) {
-                        log_syntax(NULL, LOG_WARNING, path, c, 0, "Line is not an assignment, ignoring: %s", p);
-                        if (r == 0)
-                                r = -EINVAL;
-                        continue;
-                }
+                if (value) {
+                        if (p[0] == '-') {
+                                ignore_failure = true;
+                                p++;
+                        }
 
-                *value = 0;
-                value++;
+                        *value = 0;
+                        value++;
+                        value = strstrip(value);
 
-                p = strstrip(p);
-                ignore_failure = p[0] == '-';
-                if (ignore_failure)
-                        p++;
+                } else {
+                        if (p[0] == '-')
+                                /* We have a "negative match" option. Let's continue with value==NULL. */
+                                p++;
+                        else {
+                                log_syntax(NULL, LOG_WARNING, path, c, 0,
+                                           "Line is not an assignment, ignoring: %s", p);
+                                if (r == 0)
+                                        r = -EINVAL;
+                                continue;
+                        }
+                }
 
+                p = strstrip(p);
                 p = sysctl_normalize(p);
-                value = strstrip(value);
 
-                if (!test_prefix(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;
 
                 if (ordered_hashmap_ensure_allocated(sysctl_options, &option_hash_ops) < 0)
@@ -188,7 +257,7 @@ static int parse_file(OrderedHashmap **sysctl_options, const char *path, bool ig
 
                 existing = ordered_hashmap_get(*sysctl_options, p);
                 if (existing) {
-                        if (streq(value, existing->value)) {
+                        if (streq_ptr(value, existing->value)) {
                                 existing->ignore_failure = existing->ignore_failure || ignore_failure;
                                 continue;
                         }