]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
udev: check stats of .link files and their drop-in files
authorYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 22 Apr 2022 03:33:15 +0000 (12:33 +0900)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Tue, 3 May 2022 08:26:22 +0000 (10:26 +0200)
Fixes #23128.

src/network/networkd-network.c
src/shared/conf-parser.c
src/shared/conf-parser.h
src/udev/net/link-config.c

index 291707cd93d4931f41e70027ff070b099dd90e2c..c0b8e3893c00d300cdaead02aa39d6a40ab1af30 100644 (file)
@@ -591,28 +591,6 @@ int network_load(Manager *manager, OrderedHashmap **networks) {
         return 0;
 }
 
-static bool stats_by_path_equal(Hashmap *a, Hashmap *b) {
-        struct stat *st_a, *st_b;
-        const char *path;
-
-        assert(a);
-        assert(b);
-
-        if (hashmap_size(a) != hashmap_size(b))
-                return false;
-
-        HASHMAP_FOREACH_KEY(st_a, path, a) {
-                st_b = hashmap_get(b, path);
-                if (!st_b)
-                        return false;
-
-                if (!stat_inode_unmodified(st_a, st_b))
-                        return false;
-        }
-
-        return true;
-}
-
 int network_reload(Manager *manager) {
         OrderedHashmap *new_networks = NULL;
         Network *n, *old;
index 6c105e7fd27fd21b096ff178259e317e55f6879a..a84c47cd26a1d2628b0fec5ca988b09d1b0d48ed 100644 (file)
@@ -34,6 +34,7 @@
 #include "set.h"
 #include "signal-util.h"
 #include "socket-util.h"
+#include "stat-util.h"
 #include "string-util.h"
 #include "strv.h"
 #include "syslog-util.h"
@@ -556,6 +557,27 @@ int config_parse_many_nulstr(
                                        ret_stats_by_path);
 }
 
+static int config_get_dropin_files(
+                const char* const* conf_file_dirs,
+                const char *dropin_dirname,
+                char ***ret) {
+
+        _cleanup_strv_free_ char **dropin_dirs = NULL;
+        const char *suffix;
+        int r;
+
+        assert(conf_file_dirs);
+        assert(dropin_dirname);
+        assert(ret);
+
+        suffix = strjoina("/", dropin_dirname);
+        r = strv_extend_strv_concat(&dropin_dirs, (char**) conf_file_dirs, suffix);
+        if (r < 0)
+                return r;
+
+        return conf_files_list_strv(ret, ".conf", NULL, 0, (const char* const*) dropin_dirs);
+}
+
 /* Parse each config file in the directories specified as strv. */
 int config_parse_many(
                 const char* const* conf_files,
@@ -568,21 +590,126 @@ int config_parse_many(
                 void *userdata,
                 Hashmap **ret_stats_by_path) {
 
-        _cleanup_strv_free_ char **dropin_dirs = NULL;
         _cleanup_strv_free_ char **files = NULL;
-        const char *suffix;
         int r;
 
-        suffix = strjoina("/", dropin_dirname);
-        r = strv_extend_strv_concat(&dropin_dirs, (char**) conf_file_dirs, suffix);
+        assert(conf_file_dirs);
+        assert(dropin_dirname);
+        assert(sections);
+        assert(table);
+
+        r = config_get_dropin_files(conf_file_dirs, dropin_dirname, &files);
+        if (r < 0)
+                return r;
+
+        return config_parse_many_files(conf_files, files, sections, lookup, table, flags, userdata, ret_stats_by_path);
+}
+
+static int config_get_stats_by_path_one(
+                const char* conf_file,
+                const char* const* conf_file_dirs,
+                Hashmap *stats_by_path) {
+
+        _cleanup_strv_free_ char **files = NULL;
+        _cleanup_free_ char *dropin_dirname = NULL;
+        struct stat st;
+        int r;
+
+        assert(conf_file);
+        assert(conf_file_dirs);
+        assert(stats_by_path);
+
+        /* Unlike config_parse(), this does not support stream. */
+
+        r = path_extract_filename(conf_file, &dropin_dirname);
         if (r < 0)
                 return r;
+        if (r == O_DIRECTORY)
+                return -EINVAL;
+
+        if (!strextend(&dropin_dirname, ".d"))
+                return -ENOMEM;
 
-        r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char* const*) dropin_dirs);
+        r = config_get_dropin_files(conf_file_dirs, dropin_dirname, &files);
         if (r < 0)
                 return r;
 
-        return config_parse_many_files(conf_files, files, sections, lookup, table, flags, userdata, ret_stats_by_path);
+        /* First read the main config file. */
+        r = RET_NERRNO(stat(conf_file, &st));
+        if (r >= 0) {
+                r = hashmap_put_stats_by_path(&stats_by_path, conf_file, &st);
+                if (r < 0)
+                        return r;
+        } else if (r != -ENOENT)
+                return r;
+
+        /* Then read all the drop-ins. */
+        STRV_FOREACH(fn, files) {
+                if (stat(*fn, &st) < 0) {
+                        if (errno == ENOENT)
+                                continue;
+
+                        return -errno;
+                }
+
+                r = hashmap_put_stats_by_path(&stats_by_path, *fn, &st);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+int config_get_stats_by_path(
+                const char *suffix,
+                const char *root,
+                unsigned flags,
+                const char* const* dirs,
+                Hashmap **ret) {
+
+        _cleanup_hashmap_free_ Hashmap *stats_by_path = NULL;
+        _cleanup_strv_free_ char **files = NULL;
+        int r;
+
+        assert(suffix);
+        assert(dirs);
+        assert(ret);
+
+        r = conf_files_list_strv(&files, suffix, root, flags, dirs);
+        if (r < 0)
+                return r;
+
+        stats_by_path = hashmap_new(&path_hash_ops_free_free);
+        if (!stats_by_path)
+                return -ENOMEM;
+
+        STRV_FOREACH(f, files) {
+                r = config_get_stats_by_path_one(*f, dirs, stats_by_path);
+                if (r < 0)
+                        return r;
+        }
+
+        *ret = TAKE_PTR(stats_by_path);
+        return 0;
+}
+
+bool stats_by_path_equal(Hashmap *a, Hashmap *b) {
+        struct stat *st_a, *st_b;
+        const char *path;
+
+        if (hashmap_size(a) != hashmap_size(b))
+                return false;
+
+        HASHMAP_FOREACH_KEY(st_a, path, a) {
+                st_b = hashmap_get(b, path);
+                if (!st_b)
+                        return false;
+
+                if (!stat_inode_unmodified(st_a, st_b))
+                        return false;
+        }
+
+        return true;
 }
 
 static void config_section_hash_func(const ConfigSection *c, struct siphash *state) {
index 94778af4588a57cab55e27a9b381f1777d774e18..07b6490165a90d282b71a46bbc432478160a515c 100644 (file)
@@ -114,6 +114,15 @@ int config_parse_many(
                 void *userdata,
                 Hashmap **ret_stats_by_path);   /* possibly NULL */
 
+int config_get_stats_by_path(
+                const char *suffix,
+                const char *root,
+                unsigned flags,
+                const char* const* dirs,
+                Hashmap **ret);
+
+bool stats_by_path_equal(Hashmap *a, Hashmap *b);
+
 typedef struct ConfigSection {
         unsigned line;
         bool invalid;
index 2de172a67a74623bdfaacb01ca98d5e60f2b674a..42745b6c59b7b87eac064c73f75949eaccd11712 100644 (file)
@@ -40,6 +40,7 @@ struct LinkConfigContext {
         LIST_HEAD(LinkConfig, configs);
         int ethtool_fd;
         usec_t network_dirs_ts_usec;
+        Hashmap *stats_by_path;
 };
 
 static LinkConfig* link_config_free(LinkConfig *config) {
@@ -71,6 +72,8 @@ static void link_configs_free(LinkConfigContext *ctx) {
         if (!ctx)
                 return;
 
+        ctx->stats_by_path = hashmap_free(ctx->stats_by_path);
+
         LIST_FOREACH(configs, config, ctx->configs)
                 link_config_free(config);
 }
@@ -207,6 +210,7 @@ static int link_adjust_wol_options(LinkConfig *config) {
 
 int link_load_one(LinkConfigContext *ctx, const char *filename) {
         _cleanup_(link_config_freep) LinkConfig *config = NULL;
+        _cleanup_hashmap_free_ Hashmap *stats_by_path = NULL;
         _cleanup_free_ char *name = NULL;
         const char *dropin_dirname;
         size_t i;
@@ -254,16 +258,23 @@ int link_load_one(LinkConfigContext *ctx, const char *filename) {
         dropin_dirname = strjoina(basename(filename), ".d");
         r = config_parse_many(
                         STRV_MAKE_CONST(filename),
-                        (const char* const*) CONF_PATHS_STRV("systemd/network"),
+                        NETWORK_DIRS,
                         dropin_dirname,
                         "Match\0"
                         "Link\0"
                         "SR-IOV\0",
                         config_item_perf_lookup, link_config_gperf_lookup,
-                        CONFIG_PARSE_WARN, config, NULL);
+                        CONFIG_PARSE_WARN, config, &stats_by_path);
         if (r < 0)
                 return r; /* config_parse_many() logs internally. */
 
+        if (ctx->stats_by_path) {
+                r = hashmap_move(ctx->stats_by_path, stats_by_path);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to save stats of '%s' and its drop-in configs, ignoring: %m", filename);
+        } else
+                ctx->stats_by_path = TAKE_PTR(stats_by_path);
+
         if (net_match_is_empty(&config->match) && !config->conditions) {
                 log_warning("%s: No valid settings found in the [Match] section, ignoring file. "
                             "To match all interfaces, add OriginalName=* in the [Match] section.",
@@ -316,10 +327,9 @@ int link_config_load(LinkConfigContext *ctx) {
         _cleanup_strv_free_ char **files = NULL;
         int r;
 
-        link_configs_free(ctx);
+        assert(ctx);
 
-        /* update timestamp */
-        paths_check_timestamp(NETWORK_DIRS, &ctx->network_dirs_ts_usec, true);
+        link_configs_free(ctx);
 
         r = conf_files_list_strv(&files, ".link", NULL, 0, NETWORK_DIRS);
         if (r < 0)
@@ -332,7 +342,18 @@ int link_config_load(LinkConfigContext *ctx) {
 }
 
 bool link_config_should_reload(LinkConfigContext *ctx) {
-        return paths_check_timestamp(NETWORK_DIRS, &ctx->network_dirs_ts_usec, false);
+        _cleanup_hashmap_free_ Hashmap *stats_by_path = NULL;
+        int r;
+
+        assert(ctx);
+
+        r = config_get_stats_by_path(".link", NULL, 0, NETWORK_DIRS, &stats_by_path);
+        if (r < 0) {
+                log_warning_errno(r, "Failed to get stats of .link files: %m");
+                return true;
+        }
+
+        return !stats_by_path_equal(ctx->stats_by_path, stats_by_path);
 }
 
 Link *link_free(Link *link) {