From cc63b6025b423e1d89388c0b3dbae3e67cb0436b Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Tue, 22 Aug 2023 16:21:43 +0200 Subject: [PATCH 01/64] libmultipath: sysfs_set_scsi_tmo: do nothing for ACT_DRY_RUN "multipath -d" might change sysfs timeouts of SCSI devices. Make sure it doesn't. Signed-off-by: Martin Wilck Cc: Jehan Singh Reviewed-by: Benjamin Marzinski --- libmultipath/configure.c | 4 ++-- libmultipath/discovery.c | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libmultipath/configure.c b/libmultipath/configure.c index 9513baae3..029fbbd27 100644 --- a/libmultipath/configure.c +++ b/libmultipath/configure.c @@ -1193,13 +1193,13 @@ int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid, if (cmpp) mpp->queue_mode = cmpp->queue_mode; + if (cmd == CMD_DRY_RUN && mpp->action == ACT_UNDEF) + mpp->action = ACT_DRY_RUN; if (setup_map(mpp, ¶ms, vecs)) { remove_map(mpp, vecs->pathvec, NULL); continue; } - if (cmd == CMD_DRY_RUN) - mpp->action = ACT_DRY_RUN; if (mpp->action == ACT_UNDEF) select_action(mpp, curmp, force_reload == FORCE_RELOAD_YES ? 1 : 0); diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c index e4de48e72..84ce5fe72 100644 --- a/libmultipath/discovery.c +++ b/libmultipath/discovery.c @@ -857,6 +857,9 @@ sysfs_set_scsi_tmo (struct config *conf, struct multipath *mpp) bool warn_dev_loss = false; bool warn_fast_io_fail = false; + if (mpp->action == ACT_DRY_RUN || mpp->action == ACT_REJECT) + return 0; + if (mpp->no_path_retry > 0) { uint64_t no_path_retry_tmo = (uint64_t)mpp->no_path_retry * conf->checkint; From 1bca05f232e8dd39a3b78751ee70d51b815ea8eb Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Tue, 22 Aug 2023 21:36:11 +0200 Subject: [PATCH 02/64] libmultipath: add alias_already_taken() Factor out a trivial helper function. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index c0139a2ee..83ded8860 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "debug.h" #include "util.h" @@ -109,30 +110,35 @@ scan_devname(const char *alias, const char *prefix) return n; } -static int -id_already_taken(int id, const char *prefix, const char *map_wwid) +static bool alias_already_taken(const char *alias, const char *map_wwid) { - STRBUF_ON_STACK(buf); - const char *alias; - - if (append_strbuf_str(&buf, prefix) < 0 || - format_devname(&buf, id) < 0) - return 0; - alias = get_strbuf_str(&buf); if (dm_map_present(alias)) { char wwid[WWID_SIZE]; /* If both the name and the wwid match, then it's fine.*/ if (dm_get_uuid(alias, wwid, sizeof(wwid)) == 0 && strncmp(map_wwid, wwid, sizeof(wwid)) == 0) - return 0; - condlog(3, "%s: alias '%s' already taken, but not in bindings file. reselecting alias", map_wwid, alias); - return 1; + return false; + condlog(3, "%s: alias '%s' already taken, but not in bindings file. reselecting alias", + map_wwid, alias); + return true; } - return 0; + return false; } +static bool id_already_taken(int id, const char *prefix, const char *map_wwid) +{ + STRBUF_ON_STACK(buf); + const char *alias; + + if (append_strbuf_str(&buf, prefix) < 0 || + format_devname(&buf, id) < 0) + return false; + + alias = get_strbuf_str(&buf); + return alias_already_taken(alias, map_wwid); +} /* * Returns: 0 if matching entry in WWIDs file found From a5aa010fe1cc667f4e0eea4647caee2065a53c14 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Tue, 22 Aug 2023 22:00:58 +0200 Subject: [PATCH 03/64] libmultipath: unify use_existing_alias() and get_user_friendly_alias() These functions are only called from select_alias(). The logic is more obvious when unified in a single function. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 82 ++++++++++++------------------------------ libmultipath/alias.h | 9 ++--- libmultipath/propsel.c | 19 +++++----- 3 files changed, 34 insertions(+), 76 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index 83ded8860..68f5d848b 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -329,13 +329,13 @@ allocate_binding(int fd, const char *wwid, int id, const char *prefix) return alias; } -char * -use_existing_alias (const char *wwid, const char *file, const char *alias_old, - const char *prefix, int bindings_read_only) +char *get_user_friendly_alias(const char *wwid, const char *file, const char *alias_old, + const char *prefix, bool bindings_read_only) { char *alias = NULL; int id = 0; int fd, can_write; + bool new_binding = false; char buff[WWID_SIZE]; FILE *f; @@ -349,6 +349,10 @@ use_existing_alias (const char *wwid, const char *file, const char *alias_old, close(fd); return NULL; } + + if (!strlen(alias_old)) + goto new_alias; + /* lookup the binding. if it exists, the wwid will be in buff * either way, id contains the id for the alias */ @@ -358,14 +362,14 @@ use_existing_alias (const char *wwid, const char *file, const char *alias_old, /* if buff is our wwid, it's already * allocated correctly */ - if (strcmp(buff, wwid) == 0) + if (strcmp(buff, wwid) == 0) { alias = strdup(alias_old); - else { - alias = NULL; + goto out; + } else { condlog(0, "alias %s already bound to wwid %s, cannot reuse", alias_old, buff); + goto new_alias; } - goto out; } id = lookup_binding(f, wwid, &alias, NULL, 0); @@ -377,8 +381,15 @@ use_existing_alias (const char *wwid, const char *file, const char *alias_old, /* allocate the existing alias in the bindings file */ id = scan_devname(alias_old, prefix); - if (id <= 0) - goto out; + +new_alias: + if (id <= 0) { + id = lookup_binding(f, wwid, &alias, prefix, 1); + if (id <= 0) + goto out; + else + new_binding = true; + } if (fflush(f) != 0) { condlog(0, "cannot fflush bindings file stream : %s", @@ -388,8 +399,9 @@ use_existing_alias (const char *wwid, const char *file, const char *alias_old, if (can_write && !bindings_read_only) { alias = allocate_binding(fd, wwid, id, prefix); - condlog(0, "Allocated existing binding [%s] for WWID [%s]", - alias, wwid); + if (alias && !new_binding) + condlog(2, "Allocated existing binding [%s] for WWID [%s]", + alias, wwid); } out: @@ -399,54 +411,6 @@ use_existing_alias (const char *wwid, const char *file, const char *alias_old, return alias; } -char * -get_user_friendly_alias(const char *wwid, const char *file, const char *prefix, - int bindings_read_only) -{ - char *alias; - int fd, id; - FILE *f; - int can_write; - - if (!wwid || *wwid == '\0') { - condlog(3, "Cannot find binding for empty WWID"); - return NULL; - } - - fd = open_file(file, &can_write, bindings_file_header); - if (fd < 0) - return NULL; - - f = fdopen(fd, "r"); - if (!f) { - condlog(0, "cannot fdopen on bindings file descriptor : %s", - strerror(errno)); - close(fd); - return NULL; - } - - id = lookup_binding(f, wwid, &alias, prefix, 1); - if (id < 0) { - fclose(f); - return NULL; - } - - pthread_cleanup_push(free, alias); - - if (fflush(f) != 0) { - condlog(0, "cannot fflush bindings file stream : %s", - strerror(errno)); - free(alias); - alias = NULL; - } else if (can_write && !bindings_read_only && !alias) - alias = allocate_binding(fd, wwid, id, prefix); - - fclose(f); - - pthread_cleanup_pop(0); - return alias; -} - int get_user_friendly_wwid(const char *alias, char *buff, const char *file) { diff --git a/libmultipath/alias.h b/libmultipath/alias.h index dbc950c47..fa3322337 100644 --- a/libmultipath/alias.h +++ b/libmultipath/alias.h @@ -2,13 +2,10 @@ #define _ALIAS_H int valid_alias(const char *alias); -char *get_user_friendly_alias(const char *wwid, const char *file, - const char *prefix, - int bindings_readonly); int get_user_friendly_wwid(const char *alias, char *buff, const char *file); -char *use_existing_alias (const char *wwid, const char *file, - const char *alias_old, - const char *prefix, int bindings_read_only); +char *get_user_friendly_alias(const char *wwid, const char *file, + const char *alias_old, + const char *prefix, bool bindings_read_only); struct config; int check_alias_settings(const struct config *); diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index d6bce1293..354e883f3 100644 --- a/libmultipath/propsel.c +++ b/libmultipath/propsel.c @@ -401,19 +401,16 @@ int select_alias(struct config *conf, struct multipath * mp) select_alias_prefix(conf, mp); - if (strlen(mp->alias_old) > 0) { - mp->alias = use_existing_alias(mp->wwid, conf->bindings_file, - mp->alias_old, mp->alias_prefix, - conf->bindings_read_only); - memset (mp->alias_old, 0, WWID_SIZE); - origin = "(setting: using existing alias)"; - } + mp->alias = get_user_friendly_alias(mp->wwid, conf->bindings_file, + mp->alias_old, mp->alias_prefix, + conf->bindings_read_only); - if (mp->alias == NULL) { - mp->alias = get_user_friendly_alias(mp->wwid, - conf->bindings_file, mp->alias_prefix, conf->bindings_read_only); + if (mp->alias && !strncmp(mp->alias, mp->alias_old, WWID_SIZE)) + origin = "(setting: using existing alias)"; + else if (mp->alias) origin = "(setting: user_friendly_name)"; - } + memset (mp->alias_old, 0, WWID_SIZE); + out: if (mp->alias == NULL) { mp->alias = strdup(mp->wwid); From 34404088df9faa0e82262248168cf39186bdb8b9 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Tue, 22 Aug 2023 22:23:29 +0200 Subject: [PATCH 04/64] libmultipath: never allocate an alias that's already taken If the bindings file is changed in a way that multipathd can't handle (e.g. by swapping the aliases of two maps), multipathd must not try to re-use an alias that is already used by another map. Creating or renaming a map with such an alias will fail. We already avoid this for some cases, but not for all. Fix it. Signed-off-by: Martin Wilck Cc: David Bond Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 31 +++++++++++++++++++++++-------- tests/alias.c | 2 +- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index 68f5d848b..3e3dfe98c 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -120,7 +120,7 @@ static bool alias_already_taken(const char *alias, const char *map_wwid) if (dm_get_uuid(alias, wwid, sizeof(wwid)) == 0 && strncmp(map_wwid, wwid, sizeof(wwid)) == 0) return false; - condlog(3, "%s: alias '%s' already taken, but not in bindings file. reselecting alias", + condlog(3, "%s: alias '%s' already taken, reselecting alias", map_wwid, alias); return true; } @@ -359,12 +359,11 @@ char *get_user_friendly_alias(const char *wwid, const char *file, const char *al rlookup_binding(f, buff, alias_old); if (strlen(buff) > 0) { - /* if buff is our wwid, it's already - * allocated correctly - */ + /* If buff is our wwid, it's already allocated correctly. */ if (strcmp(buff, wwid) == 0) { alias = strdup(alias_old); goto out; + } else { condlog(0, "alias %s already bound to wwid %s, cannot reuse", alias_old, buff); @@ -372,19 +371,35 @@ char *get_user_friendly_alias(const char *wwid, const char *file, const char *al } } - id = lookup_binding(f, wwid, &alias, NULL, 0); + /* + * Look for an existing alias in the bindings file. + * Pass prefix = NULL, so lookup_binding() won't try to allocate a new id. + */ + lookup_binding(f, wwid, &alias, NULL, 0); if (alias) { - condlog(3, "Use existing binding [%s] for WWID [%s]", - alias, wwid); + if (alias_already_taken(alias, wwid)) { + free(alias); + alias = NULL; + } else + condlog(3, "Use existing binding [%s] for WWID [%s]", + alias, wwid); goto out; } - /* allocate the existing alias in the bindings file */ + /* alias_old is already taken by our WWID, update bindings file. */ id = scan_devname(alias_old, prefix); new_alias: if (id <= 0) { + /* + * no existing alias was provided, or allocating it + * failed. Try a new one. + */ id = lookup_binding(f, wwid, &alias, prefix, 1); + if (id == 0 && alias_already_taken(alias, wwid)) { + free(alias); + alias = NULL; + } if (id <= 0) goto out; else diff --git a/tests/alias.c b/tests/alias.c index 3ca6c28bb..11f209e03 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -398,7 +398,7 @@ static void mock_self_alias(const char *alias, const char *wwid) will_return(__wrap_dm_get_uuid, wwid); } -#define USED_STR(alias_str, wwid_str) wwid_str ": alias '" alias_str "' already taken, but not in bindings file. reselecting alias\n" +#define USED_STR(alias_str, wwid_str) wwid_str ": alias '" alias_str "' already taken, reselecting alias\n" static void mock_failed_alias(const char *alias, char *msg) { From f6be4bc86d1ff0c7a67df168392be757744119dd Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Tue, 22 Aug 2023 22:30:16 +0200 Subject: [PATCH 05/64] libmultipath: lookup_binding: add comment about the algorithm When I read this code, I always get confused. Adding comments to explain the algorithm. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index 3e3dfe98c..9e9ac5635 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -172,6 +172,41 @@ lookup_binding(FILE *f, const char *map_wwid, char **map_alias, alias = strtok_r(buf, " \t", &saveptr); if (!alias) /* blank line */ continue; + + /* + * Find an unused index - explanation of the algorithm + * + * ID: 1 = mpatha, 2 = mpathb, ... + * + * We assume the bindings are unsorted. The only constraint + * is that no ID occurs more than once. IDs that occur in the + * bindings are called "used". + * + * We call the list 1,2,3,..., exactly in this order, the list + * of "expected" IDs. The variable "id" always holds the next + * "expected" ID, IOW the last "expected" ID encountered plus 1. + * Thus all IDs below "id" are known to be used. However, at the + * end of the loop, the value of "id" isn't necessarily unused. + * + * "smallest_bigger_id" is the smallest used ID that was + * encountered while it was larger than the next "expected" ID + * at that iteration. Let X be some used ID. If all IDs below X + * are used and encountered in the right sequence before X, "id" + * will be > X when the loop ends. Otherwise, X was encountered + * "out of order", the condition (X > id) holds when X is + * encountered, and "smallest_bigger_id" will be set to X; i.e. + * it will be less or equal than X when the loop ends. + * + * At the end of the loop, (id < smallest_bigger_id) means that + * the value of "id" had been encountered neither in order nor + * out of order, and is thus unused. (id >= smallest_bigger_id) + * means that "id"'s value is in use. In this case, we play safe + * and use "biggest_id + 1" as the next value to try. + * + * biggest_id is always > smallest_bigger_id, except in the + * "perfectly ordered" case. + */ + curr_id = scan_devname(alias, prefix); if (curr_id == id) { if (id < INT_MAX) From a5d8526984ee6c4f5c8b2aa87f90835a3db48a1b Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 23 Aug 2023 22:56:41 +0200 Subject: [PATCH 06/64] multipath-tools test: simplify debugging for condlog mismatch If there's a mismatch between expected and actual log message, print both messages. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- tests/test-log.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test-log.c b/tests/test-log.c index c17458723..635169994 100644 --- a/tests/test-log.c +++ b/tests/test-log.c @@ -16,12 +16,14 @@ void __wrap_dlog (int prio, const char * fmt, ...) va_list ap; char *expected; - check_expected(prio); va_start(ap, fmt); vsnprintf(buff, MAX_MSG_SIZE, fmt, ap); va_end(ap); fprintf(stderr, "%s(%d): %s", __func__, prio, buff); expected = mock_ptr_type(char *); + if (memcmp(expected, buff, strlen(expected))) + fprintf(stderr, "%s(expected): %s", __func__, expected); + check_expected(prio); assert_memory_equal(buff, expected, strlen(expected)); } From 92e9f634db185798646164ef920ec7fe094f180d Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 23 Aug 2023 22:57:29 +0200 Subject: [PATCH 07/64] multipath-tools tests: add tests for get_user_friendly_alias() Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- tests/alias.c | 441 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 441 insertions(+) diff --git a/tests/alias.c b/tests/alias.c index 11f209e03..7e443b060 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -81,6 +81,35 @@ int __wrap_dm_get_uuid(const char *name, char *uuid, int uuid_len) return ret; } +#define TEST_FDNO 1234 +#define TEST_FPTR ((FILE *) 0xaffe) + +int __wrap_open_file(const char *file, int *can_write, const char *header) +{ + int cw = mock_type(int); + + *can_write = cw; + return TEST_FDNO; +} + +FILE *__wrap_fdopen(int fd, const char *mode) +{ + assert_int_equal(fd, TEST_FDNO); + return TEST_FPTR; +} + +int __wrap_fflush(FILE *f) +{ + assert_ptr_equal(f, TEST_FPTR); + return 0; +} + +int __wrap_fclose(FILE *f) +{ + assert_ptr_equal(f, TEST_FPTR); + return 0; +} + /* strbuf wrapper for the old format_devname() */ static int __format_devname(char *name, int id, size_t len, const char *prefix) { @@ -399,6 +428,22 @@ static void mock_self_alias(const char *alias, const char *wwid) } #define USED_STR(alias_str, wwid_str) wwid_str ": alias '" alias_str "' already taken, reselecting alias\n" +#define NOMATCH_STR(alias_str) ("No matching alias [" alias_str "] in bindings file.\n") +#define FOUND_STR(alias_str, wwid_str) \ + "Found matching wwid [" wwid_str "] in bindings file." \ + " Setting alias to " alias_str "\n" +#define FOUND_ALIAS_STR(alias_str, wwid_str) \ + "Found matching alias [" alias_str "] in bindings file." \ + " Setting wwid to " wwid_str "\n" +#define NOMATCH_WWID_STR(wwid_str) ("No matching wwid [" wwid_str "] in bindings file.\n") +#define NEW_STR(alias_str, wwid_str) ("Created new binding [" alias_str "] for WWID [" wwid_str "]\n") +#define EXISTING_STR(alias_str, wwid_str) ("Use existing binding [" alias_str "] for WWID [" wwid_str "]\n") +#define ALLOC_STR(alias_str, wwid_str) ("Allocated existing binding [" alias_str "] for WWID [" wwid_str "]\n") +#define BINDING_STR(alias_str, wwid_str) (alias_str " " wwid_str "\n") +#define BOUND_STR(alias_str, wwid_str) ("alias "alias_str " already bound to wwid " wwid_str ", cannot reuse") +#define ERR_STR(alias_str, wwid_str) ("ERROR: old alias [" alias_str "] for wwid [" wwid_str "] is used by other map\n") +#define REUSE_STR(alias_str, wwid_str) ("alias " alias_str " already bound to wwid " wwid_str ", cannot reuse\n") +#define NOMORE_STR "no more available user_friendly_names\n" static void mock_failed_alias(const char *alias, char *msg) { @@ -421,6 +466,24 @@ static void mock_used_alias(const char *alias, char *msg) expect_condlog(3, msg); } +static void mock_bindings_file(const char *content, int match_line) +{ + static char cnt[1024]; + char *token; + int i; + + assert_in_range(strlcpy(cnt, content, sizeof(cnt)), 0, sizeof(cnt) - 1); + + for (token = strtok(cnt, "\n"), i = 0; + token && *token; + token = strtok(NULL, "\n"), i++) { + will_return(__wrap_fgets, token); + if (match_line == i) + return; + } + will_return(__wrap_fgets, NULL); +} + static void lb_empty(void **state) { int rc; @@ -1147,6 +1210,382 @@ static int test_allocate_binding(void) return cmocka_run_group_tests(tests, NULL, NULL); } +#define mock_allocate_binding(alias, wwid) \ + do { \ + static const char ln[] = BINDING_STR(alias, wwid); \ + \ + will_return(__wrap_lseek, 0); \ + expect_value(__wrap_write, count, strlen(ln)); \ + expect_string(__wrap_write, buf, ln); \ + will_return(__wrap_write, strlen(ln)); \ + expect_condlog(3, NEW_STR(alias, wwid)); \ + } while (0) + +static void gufa_empty_new_rw(void **state) { + char *alias; + + will_return(__wrap_open_file, true); + + will_return(__wrap_fgets, NULL); + mock_unused_alias("MPATHa"); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); + + mock_allocate_binding("MPATHa", "WWID0"); + alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", false); + assert_string_equal(alias, "MPATHa"); + free(alias); +} + +static void gufa_empty_new_ro_1(void **state) { + char *alias; + will_return(__wrap_open_file, false); + will_return(__wrap_fgets, NULL); + mock_unused_alias("MPATHa"); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); + + alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", false); + assert_ptr_equal(alias, NULL); +} + +static void gufa_empty_new_ro_2(void **state) { + char *alias; + + will_return(__wrap_open_file, true); + + will_return(__wrap_fgets, NULL); + mock_unused_alias("MPATHa"); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); + + alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", true); + assert_ptr_equal(alias, NULL); +} + +static void gufa_match_a_unused(void **state) { + char *alias; + + will_return(__wrap_open_file, true); + + will_return(__wrap_fgets, BINDING_STR("MPATHa", "WWID0")); + expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); + mock_unused_alias("MPATHa"); + + alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", true); + assert_string_equal(alias, "MPATHa"); + free(alias); +} + +static void gufa_match_a_self(void **state) { + char *alias; + + will_return(__wrap_open_file, true); + + will_return(__wrap_fgets, BINDING_STR("MPATHa", "WWID0")); + expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); + mock_self_alias("MPATHa", "WWID0"); + + alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", true); + assert_string_equal(alias, "MPATHa"); + free(alias); +} + +static void gufa_match_a_used(void **state) { + char *alias; + + will_return(__wrap_open_file, true); + + will_return(__wrap_fgets, BINDING_STR("MPATHa", "WWID0")); + expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); + mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID0")); + + alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", true); + assert_ptr_equal(alias, NULL); +} + +static void gufa_nomatch_a_c(void **state) { + char *alias; + will_return(__wrap_open_file, true); + + mock_bindings_file("MPATHa WWID0\n" + "MPATHc WWID2", + -1); + mock_unused_alias("MPATHb"); + expect_condlog(3, NOMATCH_WWID_STR("WWID1")); + + mock_allocate_binding("MPATHb", "WWID1"); + + alias = get_user_friendly_alias("WWID1", "x", "", "MPATH", false); + assert_string_equal(alias, "MPATHb"); + free(alias); +} + +static void gufa_nomatch_c_a(void **state) { + char *alias; + will_return(__wrap_open_file, true); + + mock_bindings_file("MPATHc WWID2\n" + "MPATHa WWID0", + -1); + mock_unused_alias("MPATHb"); + expect_condlog(3, NOMATCH_WWID_STR("WWID1")); + + mock_allocate_binding("MPATHb", "WWID1"); + + alias = get_user_friendly_alias("WWID1", "x", "", "MPATH", false); + assert_string_equal(alias, "MPATHb"); + free(alias); +} + +static void gufa_nomatch_c_b(void **state) { + char *alias; + will_return(__wrap_open_file, true); + + mock_bindings_file("MPATHc WWID2\n" + "MPATHb WWID1\n", + -1); + mock_unused_alias("MPATHa"); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); + + mock_allocate_binding("MPATHa", "WWID0"); + + alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", false); + assert_string_equal(alias, "MPATHa"); + free(alias); +} + +static void gufa_nomatch_c_b_used(void **state) { + char *alias; + will_return(__wrap_open_file, true); + + mock_bindings_file("MPATHc WWID2\n" + "MPATHb WWID1", + -1); + mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID4")); + expect_condlog(3, NOMATCH_WWID_STR("WWID4")); + mock_unused_alias("MPATHd"); + + mock_allocate_binding("MPATHd", "WWID4"); + + alias = get_user_friendly_alias("WWID4", "x", "", "MPATH", false); + assert_string_equal(alias, "MPATHd"); + free(alias); +} + +static void gufa_nomatch_b_f_a(void **state) { + char *alias; + will_return(__wrap_open_file, true); + + mock_bindings_file("MPATHb WWID1\n" + "MPATHf WWID6\n" + "MPATHa WWID0\n", + -1); + expect_condlog(3, NOMATCH_WWID_STR("WWID7")); + mock_unused_alias("MPATHg"); + + mock_allocate_binding("MPATHg", "WWID7"); + + alias = get_user_friendly_alias("WWID7", "x", "", "MPATH", false); + assert_string_equal(alias, "MPATHg"); + free(alias); +} + +static void gufa_old_empty(void **state) { + char *alias; + will_return(__wrap_open_file, true); + + /* rlookup_binding for ALIAS */ + will_return(__wrap_fgets, NULL); + expect_condlog(3, NOMATCH_STR("MPATHz")); + + /* lookup_binding */ + will_return(__wrap_fgets, NULL); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); + + mock_allocate_binding("MPATHz", "WWID0"); + expect_condlog(2, ALLOC_STR("MPATHz", "WWID0")); + + alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); + assert_string_equal(alias, "MPATHz"); + free(alias); +} + +static void gufa_old_match(void **state) { + char *alias; + will_return(__wrap_open_file, true); + + mock_bindings_file("MPATHb WWID1\n" + "MPATHz WWID0", + 1); + expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID0")); + + alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); + assert_string_equal(alias, "MPATHz"); + free(alias); +} + +static void gufa_old_match_other(void **state) { + char *alias; + static const char bindings[] = "MPATHz WWID9"; + + will_return(__wrap_open_file, true); + + mock_bindings_file(bindings, 0); + expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID9")); + expect_condlog(0, REUSE_STR("MPATHz", "WWID9")); + + mock_bindings_file(bindings, -1); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); + mock_unused_alias("MPATHa"); + + mock_allocate_binding("MPATHa", "WWID0"); + + alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); + assert_string_equal(alias, "MPATHa"); + free(alias); +} + +static void gufa_old_match_other_used(void **state) { + char *alias; + static const char bindings[] = "MPATHz WWID9"; + + will_return(__wrap_open_file, true); + + mock_bindings_file(bindings, 0); + expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID9")); + expect_condlog(0, REUSE_STR("MPATHz", "WWID9")); + + mock_bindings_file(bindings, -1); + mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID0")); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); + mock_unused_alias("MPATHb"); + + mock_allocate_binding("MPATHb", "WWID0"); + + alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); + assert_string_equal(alias, "MPATHb"); + free(alias); +} + +static void gufa_old_match_other_wwidmatch(void **state) { + char *alias; + static const char bindings[] = ("MPATHz WWID9\n" + "MPATHc WWID2"); + will_return(__wrap_open_file, true); + + mock_bindings_file(bindings, 0); + expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID9")); + expect_condlog(0, REUSE_STR("MPATHz", "WWID9")); + + mock_bindings_file(bindings, 1); + expect_condlog(3, FOUND_STR("MPATHc", "WWID2")); + mock_unused_alias("MPATHc"); + + alias = get_user_friendly_alias("WWID2", "x", "MPATHz", "MPATH", false); + assert_string_equal(alias, "MPATHc"); + free(alias); +} + +static void gufa_old_match_other_wwidmatch_used(void **state) { + char *alias; + static const char bindings[] = ("MPATHz WWID9\n" + "MPATHc WWID2"); + + will_return(__wrap_open_file, true); + + mock_bindings_file(bindings, 0); + expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID9")); + expect_condlog(0, REUSE_STR("MPATHz", "WWID9")); + + mock_bindings_file(bindings, 1); + expect_condlog(3, FOUND_STR("MPATHc", "WWID2")); + mock_used_alias("MPATHc", USED_STR("MPATHc", "WWID2")); + + alias = get_user_friendly_alias("WWID2", "x", "MPATHz", "MPATH", false); + assert_ptr_equal(alias, NULL); +} + +static void gufa_old_nomatch_wwidmatch(void **state) { + char *alias; + static const char bindings[] = "MPATHa WWID0"; + + will_return(__wrap_open_file, true); + + mock_bindings_file(bindings, -1); + expect_condlog(3, NOMATCH_STR("MPATHz")); + + mock_bindings_file(bindings, 0); + expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); + mock_unused_alias("MPATHa"); + expect_condlog(3, EXISTING_STR("MPATHa", "WWID0")); + + alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); + assert_string_equal(alias, "MPATHa"); + free(alias); +} + +static void gufa_old_nomatch_wwidmatch_used(void **state) { + char *alias; + static const char bindings[] = "MPATHa WWID0"; + will_return(__wrap_open_file, true); + + mock_bindings_file(bindings, -1); + expect_condlog(3, NOMATCH_STR("MPATHz")); + + mock_bindings_file(bindings, 0); + expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); + mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID0")); + + alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); + assert_ptr_equal(alias, NULL); +} + +static void gufa_old_nomatch_nowwidmatch(void **state) { + char *alias; + static const char bindings[] = "MPATHb WWID1"; + + will_return(__wrap_open_file, true); + + mock_bindings_file(bindings, -1); + expect_condlog(3, NOMATCH_STR("MPATHz")); + + mock_bindings_file(bindings, -1); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); + + mock_allocate_binding("MPATHz", "WWID0"); + expect_condlog(2, ALLOC_STR("MPATHz", "WWID0")); + + alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); + assert_string_equal(alias, "MPATHz"); + free(alias); +} + +static int test_get_user_friendly_alias() +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(gufa_empty_new_rw), + cmocka_unit_test(gufa_empty_new_ro_1), + cmocka_unit_test(gufa_empty_new_ro_2), + cmocka_unit_test(gufa_match_a_unused), + cmocka_unit_test(gufa_match_a_self), + cmocka_unit_test(gufa_match_a_used), + cmocka_unit_test(gufa_nomatch_a_c), + cmocka_unit_test(gufa_nomatch_c_a), + cmocka_unit_test(gufa_nomatch_c_b), + cmocka_unit_test(gufa_nomatch_c_b_used), + cmocka_unit_test(gufa_nomatch_b_f_a), + cmocka_unit_test(gufa_old_empty), + cmocka_unit_test(gufa_old_match), + cmocka_unit_test(gufa_old_match_other), + cmocka_unit_test(gufa_old_match_other_used), + cmocka_unit_test(gufa_old_match_other_wwidmatch), + cmocka_unit_test(gufa_old_match_other_wwidmatch_used), + cmocka_unit_test(gufa_old_nomatch_wwidmatch), + cmocka_unit_test(gufa_old_nomatch_wwidmatch_used), + cmocka_unit_test(gufa_old_nomatch_nowwidmatch), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} + int main(void) { int ret = 0; @@ -1157,6 +1596,8 @@ int main(void) ret += test_lookup_binding(); ret += test_rlookup_binding(); ret += test_allocate_binding(); + ret += test_allocate_binding(); + ret += test_get_user_friendly_alias(); return ret; } From 0ad8ab9f4fda7a3d520e6a438ce471b196d1ec06 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 24 Aug 2023 10:40:32 +0200 Subject: [PATCH 08/64] multipath-tools test: consistent use of macros in alias test Use the macros introduced with the tests for get_user_friendly_alias() also in the previously existing tests. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- tests/alias.c | 80 ++++++++++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/tests/alias.c b/tests/alias.c index 7e443b060..427b2814b 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -490,7 +490,7 @@ static void lb_empty(void **state) char *alias; will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching wwid [WWID0] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, NULL, 0); assert_int_equal(rc, 1); assert_ptr_equal(alias, NULL); @@ -503,7 +503,7 @@ static void lb_empty_unused(void **state) will_return(__wrap_fgets, NULL); mock_unused_alias("MPATHa"); - expect_condlog(3, "No matching wwid [WWID0] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 1); assert_ptr_equal(alias, NULL); @@ -518,7 +518,7 @@ static void lb_empty_failed(void **state) will_return(__wrap_fgets, NULL); mock_failed_alias("MPATHa", USED_STR("MPATHa", "WWID0")); mock_unused_alias("MPATHb"); - expect_condlog(3, "No matching wwid [WWID0] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); @@ -533,7 +533,7 @@ static void lb_empty_1_used(void **state) will_return(__wrap_fgets, NULL); mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID0")); mock_unused_alias("MPATHb"); - expect_condlog(3, "No matching wwid [WWID0] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); @@ -548,7 +548,7 @@ static void lb_empty_1_used_self(void **state) will_return(__wrap_fgets, NULL); mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID0")); mock_self_alias("MPATHb", "WWID0"); - expect_condlog(3, "No matching wwid [WWID0] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); @@ -561,8 +561,7 @@ static void lb_match_a(void **state) char *alias; will_return(__wrap_fgets, "MPATHa WWID0\n"); - expect_condlog(3, "Found matching wwid [WWID0] in bindings file." - " Setting alias to MPATHa\n"); + expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 0); assert_int_equal(rc, 0); assert_ptr_not_equal(alias, NULL); @@ -577,7 +576,7 @@ static void lb_nomatch_a(void **state) will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching wwid [WWID1] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID1")); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 0); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); @@ -590,7 +589,7 @@ static void lb_nomatch_a_bad_check(void **state) will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, NULL); - expect_condlog(0, "no more available user_friendly_names\n"); + expect_condlog(0, NOMORE_STR); rc = lookup_binding(NULL, "WWID1", &alias, NULL, 1); assert_int_equal(rc, -1); assert_ptr_equal(alias, NULL); @@ -604,7 +603,7 @@ static void lb_nomatch_a_unused(void **state) will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, NULL); mock_unused_alias("MPATHb"); - expect_condlog(3, "No matching wwid [WWID1] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID1")); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); @@ -622,7 +621,7 @@ static void lb_nomatch_a_3_used_failed_self(void **state) mock_used_alias("MPATHd", USED_STR("MPATHd", "WWID1")); mock_failed_alias("MPATHe", USED_STR("MPATHe", "WWID1")); mock_self_alias("MPATHf", "WWID1"); - expect_condlog(3, "No matching wwid [WWID1] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID1")); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1); assert_int_equal(rc, 6); assert_ptr_equal(alias, NULL); @@ -635,8 +634,7 @@ static void do_lb_match_c(void **state, int check_if_taken) will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, "MPATHc WWID1\n"); - expect_condlog(3, "Found matching wwid [WWID1] in bindings file." - " Setting alias to MPATHc\n"); + expect_condlog(3, FOUND_STR("MPATHc", "WWID1")); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", check_if_taken); assert_int_equal(rc, 0); assert_ptr_not_equal(alias, NULL); @@ -662,7 +660,7 @@ static void lb_nomatch_a_c(void **state) will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, "MPATHc WWID1\n"); will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); @@ -677,7 +675,7 @@ static void lb_nomatch_a_d_unused(void **state) will_return(__wrap_fgets, "MPATHd WWID1\n"); will_return(__wrap_fgets, NULL); mock_unused_alias("MPATHb"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); @@ -693,7 +691,7 @@ static void lb_nomatch_a_d_1_used(void **state) will_return(__wrap_fgets, NULL); mock_used_alias("MPATHb", USED_STR("MPATHb", "WWID2")); mock_unused_alias("MPATHc"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); @@ -710,7 +708,7 @@ static void lb_nomatch_a_d_2_used(void **state) mock_used_alias("MPATHb", USED_STR("MPATHb", "WWID2")); mock_used_alias("MPATHc", USED_STR("MPATHc", "WWID2")); mock_unused_alias("MPATHe"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 5); assert_ptr_equal(alias, NULL); @@ -728,7 +726,7 @@ static void lb_nomatch_a_d_3_used(void **state) mock_used_alias("MPATHc", USED_STR("MPATHc", "WWID2")); mock_used_alias("MPATHe", USED_STR("MPATHe", "WWID2")); mock_unused_alias("MPATHf"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 6); assert_ptr_equal(alias, NULL); @@ -742,7 +740,7 @@ static void lb_nomatch_c_a(void **state) will_return(__wrap_fgets, "MPATHc WWID1\n"); will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); @@ -758,7 +756,7 @@ static void lb_nomatch_d_a_unused(void **state) will_return(__wrap_fgets, "MPATHd WWID0\n"); will_return(__wrap_fgets, NULL); mock_unused_alias("MPATHb"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); @@ -775,7 +773,7 @@ static void lb_nomatch_d_a_1_used(void **state) will_return(__wrap_fgets, NULL); mock_used_alias("MPATHb", USED_STR("MPATHb", "WWID2")); mock_unused_alias("MPATHe"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 5); assert_ptr_equal(alias, NULL); @@ -790,7 +788,7 @@ static void lb_nomatch_a_b(void **state) will_return(__wrap_fgets, "MPATHz WWID26\n"); will_return(__wrap_fgets, "MPATHb WWID1\n"); will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); @@ -806,7 +804,7 @@ static void lb_nomatch_a_b_bad(void **state) will_return(__wrap_fgets, "MPATHb\n"); will_return(__wrap_fgets, NULL); expect_condlog(3, "Ignoring malformed line 3 in bindings file\n"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); @@ -823,7 +821,7 @@ static void lb_nomatch_a_b_bad_self(void **state) will_return(__wrap_fgets, NULL); expect_condlog(3, "Ignoring malformed line 3 in bindings file\n"); mock_self_alias("MPATHc", "WWID2"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); @@ -838,7 +836,7 @@ static void lb_nomatch_b_a(void **state) will_return(__wrap_fgets, "MPATHz WWID26\n"); will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); assert_int_equal(rc, 27); assert_ptr_equal(alias, NULL); @@ -857,7 +855,7 @@ static void lb_nomatch_b_a_3_used(void **state) mock_used_alias("MPATHab", USED_STR("MPATHab", "WWID2")); mock_used_alias("MPATHac", USED_STR("MPATHac", "WWID2")); mock_unused_alias("MPATHad"); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 30); assert_ptr_equal(alias, NULL); @@ -873,7 +871,7 @@ static void do_lb_nomatch_int_max(void **state, int check_if_taken) will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX " WWIDMAX\n"); will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, NULL); - expect_condlog(0, "no more available user_friendly_names\n"); + expect_condlog(0, NOMORE_STR); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", check_if_taken); assert_int_equal(rc, -1); assert_ptr_equal(alias, NULL); @@ -898,7 +896,7 @@ static void lb_nomatch_int_max_used(void **state) will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX " WWIDMAX\n"); will_return(__wrap_fgets, NULL); mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID2")); - expect_condlog(0, "no more available user_friendly_names\n"); + expect_condlog(0, NOMORE_STR); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, -1); assert_ptr_equal(alias, NULL); @@ -913,7 +911,7 @@ static void lb_nomatch_int_max_m1(void **state) will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n"); will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); assert_int_equal(rc, INT_MAX); assert_ptr_equal(alias, NULL); @@ -929,7 +927,7 @@ static void lb_nomatch_int_max_m1_used(void **state) will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, NULL); mock_used_alias("MPATH" MPATH_ID_INT_MAX, USED_STR("MPATH" MPATH_ID_INT_MAX, "WWID2")); - expect_condlog(0, "no more available user_friendly_names\n"); + expect_condlog(0, NOMORE_STR); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, -1); assert_ptr_equal(alias, NULL); @@ -945,7 +943,7 @@ static void lb_nomatch_int_max_m1_1_used(void **state) will_return(__wrap_fgets, NULL); mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID2")); mock_unused_alias("MPATH" MPATH_ID_INT_MAX); - expect_condlog(3, "No matching wwid [WWID2] in bindings file.\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, INT_MAX); assert_ptr_equal(alias, NULL); @@ -961,7 +959,7 @@ static void lb_nomatch_int_max_m1_2_used(void **state) will_return(__wrap_fgets, NULL); mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID2")); mock_used_alias("MPATH" MPATH_ID_INT_MAX, USED_STR("MPATH" MPATH_ID_INT_MAX, "WWID2")); - expect_condlog(0, "no more available user_friendly_names\n"); + expect_condlog(0, NOMORE_STR); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, -1); assert_ptr_equal(alias, NULL); @@ -1017,7 +1015,7 @@ static void rl_empty(void **state) buf[0] = '\0'; will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching alias [MPATHa] in bindings file.\n"); + expect_condlog(3, NOMATCH_STR("MPATHa")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, -1); assert_string_equal(buf, ""); @@ -1030,8 +1028,7 @@ static void rl_match_a(void **state) buf[0] = '\0'; will_return(__wrap_fgets, "MPATHa WWID0\n"); - expect_condlog(3, "Found matching alias [MPATHa] in bindings file. " - "Setting wwid to WWID0\n"); + expect_condlog(3, FOUND_ALIAS_STR("MPATHa", "WWID0")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, 0); assert_string_equal(buf, "WWID0"); @@ -1045,7 +1042,7 @@ static void rl_nomatch_a(void **state) buf[0] = '\0'; will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, NULL); - expect_condlog(3, "No matching alias [MPATHb] in bindings file.\n"); + expect_condlog(3, NOMATCH_STR("MPATHb")); rc = rlookup_binding(NULL, buf, "MPATHb"); assert_int_equal(rc, -1); assert_string_equal(buf, ""); @@ -1060,7 +1057,7 @@ static void rl_malformed_a(void **state) will_return(__wrap_fgets, "MPATHa \n"); will_return(__wrap_fgets, NULL); expect_condlog(3, "Ignoring malformed line 1 in bindings file\n"); - expect_condlog(3, "No matching alias [MPATHa] in bindings file.\n"); + expect_condlog(3, NOMATCH_STR("MPATHa")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, -1); assert_string_equal(buf, ""); @@ -1080,7 +1077,7 @@ static void rl_overlong_a(void **state) will_return(__wrap_fgets, line); will_return(__wrap_fgets, NULL); expect_condlog(3, "Ignoring too large wwid at 1 in bindings file\n"); - expect_condlog(3, "No matching alias [MPATHa] in bindings file.\n"); + expect_condlog(3, NOMATCH_STR("MPATHa")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, -1); assert_string_equal(buf, ""); @@ -1095,8 +1092,7 @@ static void rl_match_b(void **state) will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, "MPATHz WWID26\n"); will_return(__wrap_fgets, "MPATHb WWID2\n"); - expect_condlog(3, "Found matching alias [MPATHb] in bindings file. " - "Setting wwid to WWID2\n"); + expect_condlog(3, FOUND_ALIAS_STR("MPATHb", "WWID2")); rc = rlookup_binding(NULL, buf, "MPATHb"); assert_int_equal(rc, 0); assert_string_equal(buf, "WWID2"); @@ -1125,7 +1121,7 @@ static void al_a(void **state) expect_value(__wrap_write, count, strlen(ln)); expect_string(__wrap_write, buf, ln); will_return(__wrap_write, strlen(ln)); - expect_condlog(3, "Created new binding [MPATHa] for WWID [WWIDa]\n"); + expect_condlog(3, NEW_STR("MPATHa", "WWIDa")); alias = allocate_binding(0, "WWIDa", 1, "MPATH"); assert_ptr_not_equal(alias, NULL); @@ -1142,7 +1138,7 @@ static void al_zz(void **state) expect_value(__wrap_write, count, strlen(ln)); expect_string(__wrap_write, buf, ln); will_return(__wrap_write, strlen(ln)); - expect_condlog(3, "Created new binding [MPATHzz] for WWID [WWIDzz]\n"); + expect_condlog(3, NEW_STR("MPATHzz", "WWIDzz")); alias = allocate_binding(0, "WWIDzz", 26*26 + 26, "MPATH"); assert_ptr_not_equal(alias, NULL); From ce43798502eabf53864966399f3d051aa8868f04 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 24 Aug 2023 10:49:32 +0200 Subject: [PATCH 09/64] multipath-tools tests: convert mock_{failed,used}_alias to macros This way we can further improve readability of the individual test cases. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- tests/alias.c | 92 +++++++++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/tests/alias.c b/tests/alias.c index 427b2814b..a32b43e88 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -445,26 +445,26 @@ static void mock_self_alias(const char *alias, const char *wwid) #define REUSE_STR(alias_str, wwid_str) ("alias " alias_str " already bound to wwid " wwid_str ", cannot reuse\n") #define NOMORE_STR "no more available user_friendly_names\n" -static void mock_failed_alias(const char *alias, char *msg) -{ - expect_string(__wrap_dm_map_present, str, alias); - will_return(__wrap_dm_map_present, 1); - expect_string(__wrap_dm_get_uuid, name, alias); - expect_value(__wrap_dm_get_uuid, uuid_len, WWID_SIZE); - will_return(__wrap_dm_get_uuid, 1); - expect_condlog(3, msg); -} +#define mock_failed_alias(alias, wwid) \ + do { \ + expect_string(__wrap_dm_map_present, str, alias); \ + will_return(__wrap_dm_map_present, 1); \ + expect_string(__wrap_dm_get_uuid, name, alias); \ + expect_value(__wrap_dm_get_uuid, uuid_len, WWID_SIZE); \ + will_return(__wrap_dm_get_uuid, 1); \ + expect_condlog(3, USED_STR(alias, wwid)); \ + } while (0) -static void mock_used_alias(const char *alias, char *msg) -{ - expect_string(__wrap_dm_map_present, str, alias); - will_return(__wrap_dm_map_present, 1); - expect_string(__wrap_dm_get_uuid, name, alias); - expect_value(__wrap_dm_get_uuid, uuid_len, WWID_SIZE); - will_return(__wrap_dm_get_uuid, 0); - will_return(__wrap_dm_get_uuid, "WWID_USED"); - expect_condlog(3, msg); -} +#define mock_used_alias(alias, wwid) \ + do { \ + expect_string(__wrap_dm_map_present, str, alias); \ + will_return(__wrap_dm_map_present, 1); \ + expect_string(__wrap_dm_get_uuid, name, alias); \ + expect_value(__wrap_dm_get_uuid, uuid_len, WWID_SIZE); \ + will_return(__wrap_dm_get_uuid, 0); \ + will_return(__wrap_dm_get_uuid, "WWID_USED"); \ + expect_condlog(3, USED_STR(alias, wwid)); \ + } while(0) static void mock_bindings_file(const char *content, int match_line) { @@ -516,7 +516,7 @@ static void lb_empty_failed(void **state) char *alias; will_return(__wrap_fgets, NULL); - mock_failed_alias("MPATHa", USED_STR("MPATHa", "WWID0")); + mock_failed_alias("MPATHa", "WWID0"); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); @@ -531,7 +531,7 @@ static void lb_empty_1_used(void **state) char *alias; will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID0")); + mock_used_alias("MPATHa", "WWID0"); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); @@ -546,7 +546,7 @@ static void lb_empty_1_used_self(void **state) char *alias; will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID0")); + mock_used_alias("MPATHa", "WWID0"); mock_self_alias("MPATHb", "WWID0"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); @@ -616,10 +616,10 @@ static void lb_nomatch_a_3_used_failed_self(void **state) will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHb", USED_STR("MPATHb", "WWID1")); - mock_used_alias("MPATHc", USED_STR("MPATHc", "WWID1")); - mock_used_alias("MPATHd", USED_STR("MPATHd", "WWID1")); - mock_failed_alias("MPATHe", USED_STR("MPATHe", "WWID1")); + mock_used_alias("MPATHb", "WWID1"); + mock_used_alias("MPATHc", "WWID1"); + mock_used_alias("MPATHd", "WWID1"); + mock_failed_alias("MPATHe", "WWID1"); mock_self_alias("MPATHf", "WWID1"); expect_condlog(3, NOMATCH_WWID_STR("WWID1")); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1); @@ -689,7 +689,7 @@ static void lb_nomatch_a_d_1_used(void **state) will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, "MPATHd WWID1\n"); will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHb", USED_STR("MPATHb", "WWID2")); + mock_used_alias("MPATHb", "WWID2"); mock_unused_alias("MPATHc"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); @@ -705,8 +705,8 @@ static void lb_nomatch_a_d_2_used(void **state) will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, "MPATHd WWID1\n"); will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHb", USED_STR("MPATHb", "WWID2")); - mock_used_alias("MPATHc", USED_STR("MPATHc", "WWID2")); + mock_used_alias("MPATHb", "WWID2"); + mock_used_alias("MPATHc", "WWID2"); mock_unused_alias("MPATHe"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); @@ -722,9 +722,9 @@ static void lb_nomatch_a_d_3_used(void **state) will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, "MPATHd WWID1\n"); will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHb", USED_STR("MPATHb", "WWID2")); - mock_used_alias("MPATHc", USED_STR("MPATHc", "WWID2")); - mock_used_alias("MPATHe", USED_STR("MPATHe", "WWID2")); + mock_used_alias("MPATHb", "WWID2"); + mock_used_alias("MPATHc", "WWID2"); + mock_used_alias("MPATHe", "WWID2"); mock_unused_alias("MPATHf"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); @@ -771,7 +771,7 @@ static void lb_nomatch_d_a_1_used(void **state) will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, "MPATHd WWID0\n"); will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHb", USED_STR("MPATHb", "WWID2")); + mock_used_alias("MPATHb", "WWID2"); mock_unused_alias("MPATHe"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); @@ -851,9 +851,9 @@ static void lb_nomatch_b_a_3_used(void **state) will_return(__wrap_fgets, "MPATHz WWID26\n"); will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHaa", USED_STR("MPATHaa", "WWID2")); - mock_used_alias("MPATHab", USED_STR("MPATHab", "WWID2")); - mock_used_alias("MPATHac", USED_STR("MPATHac", "WWID2")); + mock_used_alias("MPATHaa", "WWID2"); + mock_used_alias("MPATHab", "WWID2"); + mock_used_alias("MPATHac", "WWID2"); mock_unused_alias("MPATHad"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); @@ -895,7 +895,7 @@ static void lb_nomatch_int_max_used(void **state) will_return(__wrap_fgets, "MPATHb WWID1\n"); will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX " WWIDMAX\n"); will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID2")); + mock_used_alias("MPATHa", "WWID2"); expect_condlog(0, NOMORE_STR); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, -1); @@ -926,7 +926,7 @@ static void lb_nomatch_int_max_m1_used(void **state) will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n"); will_return(__wrap_fgets, "MPATHa WWID0\n"); will_return(__wrap_fgets, NULL); - mock_used_alias("MPATH" MPATH_ID_INT_MAX, USED_STR("MPATH" MPATH_ID_INT_MAX, "WWID2")); + mock_used_alias("MPATH" MPATH_ID_INT_MAX, "WWID2"); expect_condlog(0, NOMORE_STR); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, -1); @@ -941,7 +941,7 @@ static void lb_nomatch_int_max_m1_1_used(void **state) will_return(__wrap_fgets, "MPATHb WWID1\n"); will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n"); will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID2")); + mock_used_alias("MPATHa", "WWID2"); mock_unused_alias("MPATH" MPATH_ID_INT_MAX); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); @@ -957,8 +957,8 @@ static void lb_nomatch_int_max_m1_2_used(void **state) will_return(__wrap_fgets, "MPATHb WWID1\n"); will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n"); will_return(__wrap_fgets, NULL); - mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID2")); - mock_used_alias("MPATH" MPATH_ID_INT_MAX, USED_STR("MPATH" MPATH_ID_INT_MAX, "WWID2")); + mock_used_alias("MPATHa", "WWID2"); + mock_used_alias("MPATH" MPATH_ID_INT_MAX, "WWID2"); expect_condlog(0, NOMORE_STR); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, -1); @@ -1291,7 +1291,7 @@ static void gufa_match_a_used(void **state) { will_return(__wrap_fgets, BINDING_STR("MPATHa", "WWID0")); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); - mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID0")); + mock_used_alias("MPATHa", "WWID0"); alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", true); assert_ptr_equal(alias, NULL); @@ -1355,7 +1355,7 @@ static void gufa_nomatch_c_b_used(void **state) { mock_bindings_file("MPATHc WWID2\n" "MPATHb WWID1", -1); - mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID4")); + mock_used_alias("MPATHa", "WWID4"); expect_condlog(3, NOMATCH_WWID_STR("WWID4")); mock_unused_alias("MPATHd"); @@ -1450,7 +1450,7 @@ static void gufa_old_match_other_used(void **state) { expect_condlog(0, REUSE_STR("MPATHz", "WWID9")); mock_bindings_file(bindings, -1); - mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID0")); + mock_used_alias("MPATHa", "WWID0"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_unused_alias("MPATHb"); @@ -1493,7 +1493,7 @@ static void gufa_old_match_other_wwidmatch_used(void **state) { mock_bindings_file(bindings, 1); expect_condlog(3, FOUND_STR("MPATHc", "WWID2")); - mock_used_alias("MPATHc", USED_STR("MPATHc", "WWID2")); + mock_used_alias("MPATHc", "WWID2"); alias = get_user_friendly_alias("WWID2", "x", "MPATHz", "MPATH", false); assert_ptr_equal(alias, NULL); @@ -1528,7 +1528,7 @@ static void gufa_old_nomatch_wwidmatch_used(void **state) { mock_bindings_file(bindings, 0); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); - mock_used_alias("MPATHa", USED_STR("MPATHa", "WWID0")); + mock_used_alias("MPATHa", "WWID0"); alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); assert_ptr_equal(alias, NULL); From 25e5ba770f0d7c36354415a636b11a58ff64640b Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 24 Aug 2023 11:13:44 +0200 Subject: [PATCH 10/64] multipath-tools test: use mock_bindings_file() consistently Further improve test readablity. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- tests/alias.c | 178 +++++++++++++++++++++----------------------------- 1 file changed, 76 insertions(+), 102 deletions(-) diff --git a/tests/alias.c b/tests/alias.c index a32b43e88..f334f928a 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -489,7 +489,7 @@ static void lb_empty(void **state) int rc; char *alias; - will_return(__wrap_fgets, NULL); + mock_bindings_file("", -1); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, NULL, 0); assert_int_equal(rc, 1); @@ -501,7 +501,7 @@ static void lb_empty_unused(void **state) int rc; char *alias; - will_return(__wrap_fgets, NULL); + mock_bindings_file("", -1); mock_unused_alias("MPATHa"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); @@ -515,7 +515,7 @@ static void lb_empty_failed(void **state) int rc; char *alias; - will_return(__wrap_fgets, NULL); + mock_bindings_file("", -1); mock_failed_alias("MPATHa", "WWID0"); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); @@ -530,7 +530,7 @@ static void lb_empty_1_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, NULL); + mock_bindings_file("", -1); mock_used_alias("MPATHa", "WWID0"); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); @@ -545,7 +545,7 @@ static void lb_empty_1_used_self(void **state) int rc; char *alias; - will_return(__wrap_fgets, NULL); + mock_bindings_file("", -1); mock_used_alias("MPATHa", "WWID0"); mock_self_alias("MPATHb", "WWID0"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); @@ -560,7 +560,7 @@ static void lb_match_a(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); + mock_bindings_file("MPATHa WWID0\n", 0); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 0); assert_int_equal(rc, 0); @@ -574,8 +574,7 @@ static void lb_nomatch_a(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n", -1); expect_condlog(3, NOMATCH_WWID_STR("WWID1")); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 0); assert_int_equal(rc, 2); @@ -587,8 +586,7 @@ static void lb_nomatch_a_bad_check(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n", -1); expect_condlog(0, NOMORE_STR); rc = lookup_binding(NULL, "WWID1", &alias, NULL, 1); assert_int_equal(rc, -1); @@ -600,8 +598,7 @@ static void lb_nomatch_a_unused(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n", -1); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID1")); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1); @@ -614,8 +611,7 @@ static void lb_nomatch_a_3_used_failed_self(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n", -1); mock_used_alias("MPATHb", "WWID1"); mock_used_alias("MPATHc", "WWID1"); mock_used_alias("MPATHd", "WWID1"); @@ -632,8 +628,8 @@ static void do_lb_match_c(void **state, int check_if_taken) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHc WWID1\n"); + mock_bindings_file("MPATHa WWID0\n" + "MPATHc WWID1", 1); expect_condlog(3, FOUND_STR("MPATHc", "WWID1")); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", check_if_taken); assert_int_equal(rc, 0); @@ -657,9 +653,8 @@ static void lb_nomatch_a_c(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHc WWID1\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n" + "MPATHc WWID1", -1); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); assert_int_equal(rc, 2); @@ -671,9 +666,8 @@ static void lb_nomatch_a_d_unused(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHd WWID1\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n" + "MPATHd WWID1", -1); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); @@ -686,9 +680,8 @@ static void lb_nomatch_a_d_1_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHd WWID1\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n" + "MPATHd WWID1", -1); mock_used_alias("MPATHb", "WWID2"); mock_unused_alias("MPATHc"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); @@ -702,9 +695,8 @@ static void lb_nomatch_a_d_2_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHd WWID1\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n" + "MPATHd WWID1", -1); mock_used_alias("MPATHb", "WWID2"); mock_used_alias("MPATHc", "WWID2"); mock_unused_alias("MPATHe"); @@ -719,9 +711,8 @@ static void lb_nomatch_a_d_3_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHd WWID1\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n" + "MPATHd WWID1", -1); mock_used_alias("MPATHb", "WWID2"); mock_used_alias("MPATHc", "WWID2"); mock_used_alias("MPATHe", "WWID2"); @@ -737,9 +728,8 @@ static void lb_nomatch_c_a(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHc WWID1\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHc WWID1\n" + "MPATHa WWID0\n", -1); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); assert_int_equal(rc, 2); @@ -751,10 +741,9 @@ static void lb_nomatch_d_a_unused(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHc WWID1\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHd WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHc WWID1\n" + "MPATHa WWID0\n" + "MPATHd WWID0\n", -1); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); @@ -767,10 +756,9 @@ static void lb_nomatch_d_a_1_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHc WWID1\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHd WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHc WWID1\n" + "MPATHa WWID0\n" + "MPATHd WWID0\n", -1); mock_used_alias("MPATHb", "WWID2"); mock_unused_alias("MPATHe"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); @@ -784,10 +772,9 @@ static void lb_nomatch_a_b(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHz WWID26\n"); - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n" + "MPATHz WWID26\n" + "MPATHb WWID1\n", -1); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); assert_int_equal(rc, 3); @@ -799,10 +786,9 @@ static void lb_nomatch_a_b_bad(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHz WWID26\n"); - will_return(__wrap_fgets, "MPATHb\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n" + "MPATHz WWID26\n" + "MPATHb\n", -1); expect_condlog(3, "Ignoring malformed line 3 in bindings file\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); @@ -815,10 +801,9 @@ static void lb_nomatch_a_b_bad_self(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHz WWID26\n"); - will_return(__wrap_fgets, "MPATHb\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n" + "MPATHz WWID26\n" + "MPATHb\n", -1); expect_condlog(3, "Ignoring malformed line 3 in bindings file\n"); mock_self_alias("MPATHc", "WWID2"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); @@ -832,10 +817,9 @@ static void lb_nomatch_b_a(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATHz WWID26\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHb WWID1\n" + "MPATHz WWID26\n" + "MPATHa WWID0\n", -1); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); assert_int_equal(rc, 27); @@ -847,10 +831,9 @@ static void lb_nomatch_b_a_3_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATHz WWID26\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHb WWID1\n" + "MPATHz WWID26\n" + "MPATHa WWID0\n", -1); mock_used_alias("MPATHaa", "WWID2"); mock_used_alias("MPATHab", "WWID2"); mock_used_alias("MPATHac", "WWID2"); @@ -867,10 +850,9 @@ static void do_lb_nomatch_int_max(void **state, int check_if_taken) int rc; char *alias; - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX " WWIDMAX\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHb WWID1\n" + "MPATH" MPATH_ID_INT_MAX " WWIDMAX\n" + "MPATHa WWID0\n", -1); expect_condlog(0, NOMORE_STR); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", check_if_taken); assert_int_equal(rc, -1); @@ -892,9 +874,8 @@ static void lb_nomatch_int_max_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX " WWIDMAX\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHb WWID1\n" + "MPATH" MPATH_ID_INT_MAX " WWIDMAX\n", -1); mock_used_alias("MPATHa", "WWID2"); expect_condlog(0, NOMORE_STR); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); @@ -907,10 +888,9 @@ static void lb_nomatch_int_max_m1(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHb WWID1\n" + "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n" + "MPATHa WWID0\n", -1); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); assert_int_equal(rc, INT_MAX); @@ -922,10 +902,9 @@ static void lb_nomatch_int_max_m1_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n"); - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHb WWID1\n" + "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n" + "MPATHa WWID0\n", -1); mock_used_alias("MPATH" MPATH_ID_INT_MAX, "WWID2"); expect_condlog(0, NOMORE_STR); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); @@ -938,9 +917,8 @@ static void lb_nomatch_int_max_m1_1_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHb WWID1\n" + "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n", -1); mock_used_alias("MPATHa", "WWID2"); mock_unused_alias("MPATH" MPATH_ID_INT_MAX); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); @@ -954,9 +932,8 @@ static void lb_nomatch_int_max_m1_2_used(void **state) int rc; char *alias; - will_return(__wrap_fgets, "MPATHb WWID1\n"); - will_return(__wrap_fgets, "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHb WWID1\n" + "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n", -1); mock_used_alias("MPATHa", "WWID2"); mock_used_alias("MPATH" MPATH_ID_INT_MAX, "WWID2"); expect_condlog(0, NOMORE_STR); @@ -1014,7 +991,7 @@ static void rl_empty(void **state) char buf[WWID_SIZE]; buf[0] = '\0'; - will_return(__wrap_fgets, NULL); + mock_bindings_file("", -1); expect_condlog(3, NOMATCH_STR("MPATHa")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, -1); @@ -1027,7 +1004,7 @@ static void rl_match_a(void **state) char buf[WWID_SIZE]; buf[0] = '\0'; - will_return(__wrap_fgets, "MPATHa WWID0\n"); + mock_bindings_file("MPATHa WWID0\n", 0); expect_condlog(3, FOUND_ALIAS_STR("MPATHa", "WWID0")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, 0); @@ -1040,8 +1017,7 @@ static void rl_nomatch_a(void **state) char buf[WWID_SIZE]; buf[0] = '\0'; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa WWID0\n", -1); expect_condlog(3, NOMATCH_STR("MPATHb")); rc = rlookup_binding(NULL, buf, "MPATHb"); assert_int_equal(rc, -1); @@ -1054,8 +1030,7 @@ static void rl_malformed_a(void **state) char buf[WWID_SIZE]; buf[0] = '\0'; - will_return(__wrap_fgets, "MPATHa \n"); - will_return(__wrap_fgets, NULL); + mock_bindings_file("MPATHa \n", -1); expect_condlog(3, "Ignoring malformed line 1 in bindings file\n"); expect_condlog(3, NOMATCH_STR("MPATHa")); rc = rlookup_binding(NULL, buf, "MPATHa"); @@ -1074,8 +1049,7 @@ static void rl_overlong_a(void **state) snprintf(line + sizeof(line) - 2, 2, "\n"); buf[0] = '\0'; - will_return(__wrap_fgets, line); - will_return(__wrap_fgets, NULL); + mock_bindings_file(line, -1); expect_condlog(3, "Ignoring too large wwid at 1 in bindings file\n"); expect_condlog(3, NOMATCH_STR("MPATHa")); rc = rlookup_binding(NULL, buf, "MPATHa"); @@ -1089,9 +1063,9 @@ static void rl_match_b(void **state) char buf[WWID_SIZE]; buf[0] = '\0'; - will_return(__wrap_fgets, "MPATHa WWID0\n"); - will_return(__wrap_fgets, "MPATHz WWID26\n"); - will_return(__wrap_fgets, "MPATHb WWID2\n"); + mock_bindings_file("MPATHa WWID0\n" + "MPATHz WWID26\n" + "MPATHb WWID2\n", 2); expect_condlog(3, FOUND_ALIAS_STR("MPATHb", "WWID2")); rc = rlookup_binding(NULL, buf, "MPATHb"); assert_int_equal(rc, 0); @@ -1222,7 +1196,7 @@ static void gufa_empty_new_rw(void **state) { will_return(__wrap_open_file, true); - will_return(__wrap_fgets, NULL); + mock_bindings_file("", -1); mock_unused_alias("MPATHa"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); @@ -1235,7 +1209,7 @@ static void gufa_empty_new_rw(void **state) { static void gufa_empty_new_ro_1(void **state) { char *alias; will_return(__wrap_open_file, false); - will_return(__wrap_fgets, NULL); + mock_bindings_file("", -1); mock_unused_alias("MPATHa"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); @@ -1248,7 +1222,7 @@ static void gufa_empty_new_ro_2(void **state) { will_return(__wrap_open_file, true); - will_return(__wrap_fgets, NULL); + mock_bindings_file("", -1); mock_unused_alias("MPATHa"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); @@ -1261,7 +1235,7 @@ static void gufa_match_a_unused(void **state) { will_return(__wrap_open_file, true); - will_return(__wrap_fgets, BINDING_STR("MPATHa", "WWID0")); + mock_bindings_file("MPATHa WWID0", 0); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_unused_alias("MPATHa"); @@ -1275,7 +1249,7 @@ static void gufa_match_a_self(void **state) { will_return(__wrap_open_file, true); - will_return(__wrap_fgets, BINDING_STR("MPATHa", "WWID0")); + mock_bindings_file("MPATHa WWID0", 0); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_self_alias("MPATHa", "WWID0"); @@ -1289,7 +1263,7 @@ static void gufa_match_a_used(void **state) { will_return(__wrap_open_file, true); - will_return(__wrap_fgets, BINDING_STR("MPATHa", "WWID0")); + mock_bindings_file("MPATHa WWID0", 0); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_used_alias("MPATHa", "WWID0"); @@ -1389,11 +1363,11 @@ static void gufa_old_empty(void **state) { will_return(__wrap_open_file, true); /* rlookup_binding for ALIAS */ - will_return(__wrap_fgets, NULL); + mock_bindings_file("", -1); expect_condlog(3, NOMATCH_STR("MPATHz")); /* lookup_binding */ - will_return(__wrap_fgets, NULL); + mock_bindings_file("", -1); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_allocate_binding("MPATHz", "WWID0"); From 930941b246c0039ccdd7221487cb0cfe3a0fba31 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Tue, 22 Aug 2023 15:32:17 +0200 Subject: [PATCH 11/64] libmultipath: add global variable for current bindings Add a variable global_bindings that holds the currently active vector of bindings. This variable is freed at program exit. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 11 +++++++++-- libmultipath/alias.h | 1 + libmultipath/libmultipath.version | 1 + multipath/main.c | 2 ++ multipathd/main.c | 1 + 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index 9e9ac5635..dd363fd8e 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -511,6 +511,7 @@ static void _free_binding(struct binding *bdg) * an abstract type. */ typedef struct _vector Bindings; +static Bindings global_bindings = { .allocated = 0 }; static void free_bindings(Bindings *bindings) { @@ -522,6 +523,11 @@ static void free_bindings(Bindings *bindings) vector_reset(bindings); } +void cleanup_bindings(void) +{ + free_bindings(&global_bindings); +} + enum { BINDING_EXISTS, BINDING_CONFLICT, @@ -751,7 +757,6 @@ int check_alias_settings(const struct config *conf) pthread_cleanup_pop(1); pthread_cleanup_pop(1); - pthread_cleanup_push_cast(free_bindings, &bindings); fd = open_file(conf->bindings_file, &can_write, BINDINGS_FILE_HEADER); if (fd != -1) { FILE *file = fdopen(fd, "r"); @@ -771,6 +776,8 @@ int check_alias_settings(const struct config *conf) close(fd); } } - pthread_cleanup_pop(1); + + cleanup_bindings(); + global_bindings = bindings; return rc; } diff --git a/libmultipath/alias.h b/libmultipath/alias.h index fa3322337..37b49d9c7 100644 --- a/libmultipath/alias.h +++ b/libmultipath/alias.h @@ -9,5 +9,6 @@ char *get_user_friendly_alias(const char *wwid, const char *file, struct config; int check_alias_settings(const struct config *); +void cleanup_bindings(void); #endif /* _ALIAS_H */ diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version index a7b8c337d..ddd302f5e 100644 --- a/libmultipath/libmultipath.version +++ b/libmultipath/libmultipath.version @@ -64,6 +64,7 @@ global: checker_name; checker_state_name; check_foreign; + cleanup_bindings; cleanup_lock; coalesce_paths; count_active_paths; diff --git a/multipath/main.c b/multipath/main.c index b78f31625..45e9745f3 100644 --- a/multipath/main.c +++ b/multipath/main.c @@ -843,6 +843,8 @@ main (int argc, char *argv[]) conf->force_sync = 1; if (atexit(cleanup_vecs)) condlog(1, "failed to register cleanup handler for vecs: %m"); + if (atexit(cleanup_bindings)) + condlog(1, "failed to register cleanup handler for bindings: %m"); while ((arg = getopt(argc, argv, ":adDcChl::eFfM:v:p:b:BrR:itTquUwW")) != EOF ) { switch(arg) { case 'v': diff --git a/multipathd/main.c b/multipathd/main.c index 2e02a5481..214ed4ae9 100644 --- a/multipathd/main.c +++ b/multipathd/main.c @@ -3325,6 +3325,7 @@ static void cleanup_child(void) { cleanup_threads(); cleanup_vecs(); + cleanup_bindings(); if (poll_dmevents) cleanup_dmevent_waiter(); From d41f47a4ee494990446bce4503802e0779930449 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Tue, 22 Aug 2023 15:37:15 +0200 Subject: [PATCH 12/64] libmultipath: rename fix_bindings_file() to update_bindings_file() We will use this function in a more generic way, give it a more generic name. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index dd363fd8e..0aac23936 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -595,8 +595,8 @@ static int write_bindings_file(const Bindings *bindings, int fd) return 0; } -static int fix_bindings_file(const struct config *conf, - const Bindings *bindings) +static int update_bindings_file(const struct config *conf, + const Bindings *bindings) { int rc; int fd = -1; @@ -766,7 +766,7 @@ int check_alias_settings(const struct config *conf) rc = _check_bindings_file(conf, file, &bindings); pthread_cleanup_pop(1); if (rc == -1 && can_write && !conf->bindings_read_only) - rc = fix_bindings_file(conf, &bindings); + rc = update_bindings_file(conf, &bindings); else if (rc == -1) condlog(0, "ERROR: bad settings in read-only bindings file %s", conf->bindings_file); From bdec0437e038fa95dd444a513c9e83cf3781f3e0 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Tue, 22 Aug 2023 16:54:54 +0200 Subject: [PATCH 13/64] libmultipath: alias.c: move bindings related code up No code changes, just moving code. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 239 ++++++++++++++++++++++--------------------- 1 file changed, 120 insertions(+), 119 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index 0aac23936..7af403da5 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "debug.h" #include "util.h" @@ -51,6 +52,125 @@ static const char bindings_file_header[] = BINDINGS_FILE_HEADER; +struct binding { + char *alias; + char *wwid; +}; + +/* + * Perhaps one day we'll implement this more efficiently, thus use + * an abstract type. + */ +typedef struct _vector Bindings; +static Bindings global_bindings = { .allocated = 0 }; + +enum { + BINDING_EXISTS, + BINDING_CONFLICT, + BINDING_ADDED, + BINDING_DELETED, + BINDING_NOTFOUND, + BINDING_ERROR, +}; + +static void _free_binding(struct binding *bdg) +{ + free(bdg->wwid); + free(bdg->alias); + free(bdg); +} + +static int add_binding(Bindings *bindings, const char *alias, const char *wwid) +{ + struct binding *bdg; + int i, cmp = 0; + + /* + * Keep the bindings array sorted by alias. + * Optimization: Search backwards, assuming that the bindings file is + * sorted already. + */ + vector_foreach_slot_backwards(bindings, bdg, i) { + if ((cmp = strcmp(bdg->alias, alias)) <= 0) + break; + } + + /* Check for exact match */ + if (i >= 0 && cmp == 0) + return strcmp(bdg->wwid, wwid) ? + BINDING_CONFLICT : BINDING_EXISTS; + + i++; + bdg = calloc(1, sizeof(*bdg)); + if (bdg) { + bdg->wwid = strdup(wwid); + bdg->alias = strdup(alias); + if (bdg->wwid && bdg->alias && + vector_insert_slot(bindings, i, bdg)) + return BINDING_ADDED; + else + _free_binding(bdg); + } + + return BINDING_ERROR; +} + +static int write_bindings_file(const Bindings *bindings, int fd) +{ + struct binding *bnd; + STRBUF_ON_STACK(line); + int i; + + if (write(fd, BINDINGS_FILE_HEADER, sizeof(BINDINGS_FILE_HEADER) - 1) + != sizeof(BINDINGS_FILE_HEADER) - 1) + return -1; + + vector_foreach_slot(bindings, bnd, i) { + int len; + + if ((len = print_strbuf(&line, "%s %s\n", + bnd->alias, bnd->wwid)) < 0) + return -1; + if (write(fd, get_strbuf_str(&line), len) != len) + return -1; + truncate_strbuf(&line, 0); + } + return 0; +} + +static int update_bindings_file(const struct config *conf, + const Bindings *bindings) +{ + int rc; + int fd = -1; + char tempname[PATH_MAX]; + mode_t old_umask; + + if (safe_sprintf(tempname, "%s.XXXXXX", conf->bindings_file)) + return -1; + /* coverity: SECURE_TEMP */ + old_umask = umask(0077); + if ((fd = mkstemp(tempname)) == -1) { + condlog(1, "%s: mkstemp: %m", __func__); + return -1; + } + umask(old_umask); + pthread_cleanup_push(cleanup_fd_ptr, &fd); + rc = write_bindings_file(bindings, fd); + pthread_cleanup_pop(1); + if (rc == -1) { + condlog(1, "failed to write new bindings file %s", + tempname); + unlink(tempname); + return rc; + } + if ((rc = rename(tempname, conf->bindings_file)) == -1) + condlog(0, "%s: rename: %m", __func__); + else + condlog(1, "updated bindings file %s", conf->bindings_file); + return rc; +} + int valid_alias(const char *alias) { @@ -494,25 +614,6 @@ get_user_friendly_wwid(const char *alias, char *buff, const char *file) return 0; } -struct binding { - char *alias; - char *wwid; -}; - -static void _free_binding(struct binding *bdg) -{ - free(bdg->wwid); - free(bdg->alias); - free(bdg); -} - -/* - * Perhaps one day we'll implement this more efficiently, thus use - * an abstract type. - */ -typedef struct _vector Bindings; -static Bindings global_bindings = { .allocated = 0 }; - static void free_bindings(Bindings *bindings) { struct binding *bdg; @@ -528,106 +629,6 @@ void cleanup_bindings(void) free_bindings(&global_bindings); } -enum { - BINDING_EXISTS, - BINDING_CONFLICT, - BINDING_ADDED, - BINDING_DELETED, - BINDING_NOTFOUND, - BINDING_ERROR, -}; - -static int add_binding(Bindings *bindings, const char *alias, const char *wwid) -{ - struct binding *bdg; - int i, cmp = 0; - - /* - * Keep the bindings array sorted by alias. - * Optimization: Search backwards, assuming that the bindings file is - * sorted already. - */ - vector_foreach_slot_backwards(bindings, bdg, i) { - if ((cmp = strcmp(bdg->alias, alias)) <= 0) - break; - } - - /* Check for exact match */ - if (i >= 0 && cmp == 0) - return strcmp(bdg->wwid, wwid) ? - BINDING_CONFLICT : BINDING_EXISTS; - - i++; - bdg = calloc(1, sizeof(*bdg)); - if (bdg) { - bdg->wwid = strdup(wwid); - bdg->alias = strdup(alias); - if (bdg->wwid && bdg->alias && - vector_insert_slot(bindings, i, bdg)) - return BINDING_ADDED; - else - _free_binding(bdg); - } - - return BINDING_ERROR; -} - -static int write_bindings_file(const Bindings *bindings, int fd) -{ - struct binding *bnd; - STRBUF_ON_STACK(line); - int i; - - if (write(fd, BINDINGS_FILE_HEADER, sizeof(BINDINGS_FILE_HEADER) - 1) - != sizeof(BINDINGS_FILE_HEADER) - 1) - return -1; - - vector_foreach_slot(bindings, bnd, i) { - int len; - - if ((len = print_strbuf(&line, "%s %s\n", - bnd->alias, bnd->wwid)) < 0) - return -1; - if (write(fd, get_strbuf_str(&line), len) != len) - return -1; - truncate_strbuf(&line, 0); - } - return 0; -} - -static int update_bindings_file(const struct config *conf, - const Bindings *bindings) -{ - int rc; - int fd = -1; - char tempname[PATH_MAX]; - mode_t old_umask; - - if (safe_sprintf(tempname, "%s.XXXXXX", conf->bindings_file)) - return -1; - /* coverity: SECURE_TEMP */ - old_umask = umask(0077); - if ((fd = mkstemp(tempname)) == -1) { - condlog(1, "%s: mkstemp: %m", __func__); - return -1; - } - umask(old_umask); - pthread_cleanup_push(cleanup_fd_ptr, &fd); - rc = write_bindings_file(bindings, fd); - pthread_cleanup_pop(1); - if (rc == -1) { - condlog(1, "failed to write new bindings file %s", - tempname); - unlink(tempname); - return rc; - } - if ((rc = rename(tempname, conf->bindings_file)) == -1) - condlog(0, "%s: rename: %m", __func__); - else - condlog(1, "updated bindings file %s", conf->bindings_file); - return rc; -} - static int _check_bindings_file(const struct config *conf, FILE *file, Bindings *bindings) { From 5e9fcecc852832c1c74065c070b0c1fc584abf20 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 24 Aug 2023 15:53:49 +0200 Subject: [PATCH 14/64] libmultipath: update_bindings_file: take filename argument This function just uses the file name, no other configuration parameters. Also, pass the Bindings argument first to use the same convention as the other functions in this file. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index 7af403da5..9bd3875e5 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -138,15 +138,15 @@ static int write_bindings_file(const Bindings *bindings, int fd) return 0; } -static int update_bindings_file(const struct config *conf, - const Bindings *bindings) +static int update_bindings_file(const Bindings *bindings, + const char *bindings_file) { int rc; int fd = -1; char tempname[PATH_MAX]; mode_t old_umask; - if (safe_sprintf(tempname, "%s.XXXXXX", conf->bindings_file)) + if (safe_sprintf(tempname, "%s.XXXXXX", bindings_file)) return -1; /* coverity: SECURE_TEMP */ old_umask = umask(0077); @@ -164,10 +164,10 @@ static int update_bindings_file(const struct config *conf, unlink(tempname); return rc; } - if ((rc = rename(tempname, conf->bindings_file)) == -1) + if ((rc = rename(tempname, bindings_file)) == -1) condlog(0, "%s: rename: %m", __func__); else - condlog(1, "updated bindings file %s", conf->bindings_file); + condlog(1, "updated bindings file %s", bindings_file); return rc; } @@ -767,7 +767,7 @@ int check_alias_settings(const struct config *conf) rc = _check_bindings_file(conf, file, &bindings); pthread_cleanup_pop(1); if (rc == -1 && can_write && !conf->bindings_read_only) - rc = update_bindings_file(conf, &bindings); + rc = update_bindings_file(&bindings, conf->bindings_file); else if (rc == -1) condlog(0, "ERROR: bad settings in read-only bindings file %s", conf->bindings_file); From 6599d8b480fb013c05cad7020bd2c774fa94df43 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 24 Aug 2023 15:54:45 +0200 Subject: [PATCH 15/64] libmultipath: update_bindings_file: use a single write() Save code and syscalls by assembling the content in memory first. write() may return less bytes written than expected. Deal with it. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index 9bd3875e5..92f90f054 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -118,22 +118,30 @@ static int add_binding(Bindings *bindings, const char *alias, const char *wwid) static int write_bindings_file(const Bindings *bindings, int fd) { struct binding *bnd; - STRBUF_ON_STACK(line); + STRBUF_ON_STACK(content); int i; + size_t len; - if (write(fd, BINDINGS_FILE_HEADER, sizeof(BINDINGS_FILE_HEADER) - 1) - != sizeof(BINDINGS_FILE_HEADER) - 1) + if (__append_strbuf_str(&content, BINDINGS_FILE_HEADER, + sizeof(BINDINGS_FILE_HEADER) - 1) == -1) return -1; vector_foreach_slot(bindings, bnd, i) { - int len; - - if ((len = print_strbuf(&line, "%s %s\n", - bnd->alias, bnd->wwid)) < 0) + if (print_strbuf(&content, "%s %s\n", + bnd->alias, bnd->wwid) < 0) return -1; - if (write(fd, get_strbuf_str(&line), len) != len) + } + len = get_strbuf_len(&content); + while (len > 0) { + ssize_t n = write(fd, get_strbuf_str(&content), len); + + if (n < 0) + return n; + else if (n == 0) { + condlog(2, "%s: short write", __func__); return -1; - truncate_strbuf(&line, 0); + } + len -= n; } return 0; } From 6b41fb6db932c1aba773d4a35f83c668ace0fdb0 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 24 Aug 2023 22:33:39 +0200 Subject: [PATCH 16/64] libmultipath: update_bindings_file: don't log temp file name The name of the temp file is unlikely to be helpful for users, and hard to predict in the unit test. Omit it. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index 92f90f054..afa5879e5 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -167,8 +167,7 @@ static int update_bindings_file(const Bindings *bindings, rc = write_bindings_file(bindings, fd); pthread_cleanup_pop(1); if (rc == -1) { - condlog(1, "failed to write new bindings file %s", - tempname); + condlog(1, "failed to write new bindings file"); unlink(tempname); return rc; } From a591f3132fcb4ce7969f2cf9e2ac290b9b3a3841 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 24 Aug 2023 21:17:25 +0200 Subject: [PATCH 17/64] libmultipath: alias.c: factor out read_binding() This way we can test the parsing of input lines from the bindings file more easily. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 58 ++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index afa5879e5..ecf4a2ac1 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -636,6 +636,43 @@ void cleanup_bindings(void) free_bindings(&global_bindings); } +enum { + READ_BINDING_OK, + READ_BINDING_SKIP, +}; + +static int read_binding(char *line, unsigned int linenr, char **alias, + char **wwid) { + char *c, *saveptr; + + c = strpbrk(line, "#\n\r"); + if (c) + *c = '\0'; + + *alias = strtok_r(line, " \t", &saveptr); + if (!*alias) /* blank line */ + return READ_BINDING_SKIP; + + *wwid = strtok_r(NULL, " \t", &saveptr); + if (!*wwid) { + condlog(1, "invalid line %u in bindings file, missing WWID", + linenr); + return READ_BINDING_SKIP; + } + if (strlen(*wwid) > WWID_SIZE - 1) { + condlog(3, + "Ignoring too large wwid at %u in bindings file", + linenr); + return READ_BINDING_SKIP; + } + c = strtok_r(NULL, " \t", &saveptr); + if (c) + /* This is non-fatal */ + condlog(1, "invalid line %d in bindings file, extra args \"%s\"", + linenr, c); + return READ_BINDING_OK; +} + static int _check_bindings_file(const struct config *conf, FILE *file, Bindings *bindings) { @@ -647,27 +684,12 @@ static int _check_bindings_file(const struct config *conf, FILE *file, pthread_cleanup_push(cleanup_free_ptr, &line); while ((n = getline(&line, &line_len, file)) >= 0) { - char *c, *alias, *wwid, *saveptr; + char *alias, *wwid; const char *mpe_wwid; - linenr++; - c = strpbrk(line, "#\n\r"); - if (c) - *c = '\0'; - alias = strtok_r(line, " \t", &saveptr); - if (!alias) /* blank line */ - continue; - wwid = strtok_r(NULL, " \t", &saveptr); - if (!wwid) { - condlog(1, "invalid line %d in bindings file, missing WWID", - linenr); + if (read_binding(line, ++linenr, &alias, &wwid) + == READ_BINDING_SKIP) continue; - } - c = strtok_r(NULL, " \t", &saveptr); - if (c) - /* This is non-fatal */ - condlog(1, "invalid line %d in bindings file, extra args \"%s\"", - linenr, c); mpe_wwid = get_mpe_wwid(conf->mptable, alias); if (mpe_wwid && strcmp(mpe_wwid, wwid)) { From e2d4fa2e2afebad666a559aaa8267e96206744f1 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Tue, 22 Aug 2023 21:14:51 +0200 Subject: [PATCH 18/64] libmultipath: keep bindings in memory Rather than opening the bindings file every time we must retrieve a binding, keep the contents in memory and write the file only if additions have been made. This simplifies the code, and should speed up alias lookups significantly. As a side effect, the aliases will be stored sorted by alias, which changes the way aliases are allocated if there are unused "holes" in the sequence of aliases. For example, if the bindings file contains mpathb, mpathy, and mpatha, in this order, the next new alias used to be mpathz and is now mpathc. Another side effect is that multipathd will not automatically pick up changes to the bindings file at runtime without a reconfigure operation. It is questionable whether these on-the-fly changes were a good idea in the first place, as inconsistent configurations may easily come to pass. It desired, it would be feasible to implement automatic update of the bindings using the existing inotify approach. The new implementation of get_user_friendly_alias() is slightly different than before. The logic is summarized in a comment in the code. Unit tests will be provided that illustrate the changes. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 351 ++++++++++++++++----------------------- libmultipath/alias.h | 2 +- libmultipath/configure.c | 3 +- 3 files changed, 144 insertions(+), 212 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index ecf4a2ac1..d65637494 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -50,8 +50,6 @@ "# alias wwid\n" \ "#\n" -static const char bindings_file_header[] = BINDINGS_FILE_HEADER; - struct binding { char *alias; char *wwid; @@ -80,6 +78,45 @@ static void _free_binding(struct binding *bdg) free(bdg); } +static const struct binding *get_binding_for_alias(const Bindings *bindings, + const char *alias) +{ + const struct binding *bdg; + int i; + + if (!alias) + return NULL; + vector_foreach_slot(bindings, bdg, i) { + if (!strncmp(bdg->alias, alias, WWID_SIZE)) { + condlog(3, "Found matching alias [%s] in bindings file." + " Setting wwid to %s", alias, bdg->wwid); + return bdg; + } + } + + condlog(3, "No matching alias [%s] in bindings file.", alias); + return NULL; +} + +static const struct binding *get_binding_for_wwid(const Bindings *bindings, + const char *wwid) +{ + const struct binding *bdg; + int i; + + if (!wwid) + return NULL; + vector_foreach_slot(bindings, bdg, i) { + if (!strncmp(bdg->wwid, wwid, WWID_SIZE)) { + condlog(3, "Found matching wwid [%s] in bindings file." + " Setting alias to %s", wwid, bdg->alias); + return bdg; + } + } + condlog(3, "No matching wwid [%s] in bindings file.", wwid); + return NULL; +} + static int add_binding(Bindings *bindings, const char *alias, const char *wwid) { struct binding *bdg; @@ -115,6 +152,24 @@ static int add_binding(Bindings *bindings, const char *alias, const char *wwid) return BINDING_ERROR; } +static int delete_binding(Bindings *bindings, const char *wwid) +{ + struct binding *bdg; + int i; + + vector_foreach_slot(bindings, bdg, i) { + if (!strncmp(bdg->wwid, wwid, WWID_SIZE)) { + _free_binding(bdg); + break; + } + } + if (i >= VECTOR_SIZE(bindings)) + return BINDING_NOTFOUND; + + vector_del_slot(bindings, i); + return BINDING_DELETED; +} + static int write_bindings_file(const Bindings *bindings, int fd) { struct binding *bnd; @@ -267,38 +322,15 @@ static bool id_already_taken(int id, const char *prefix, const char *map_wwid) return alias_already_taken(alias, map_wwid); } -/* - * Returns: 0 if matching entry in WWIDs file found - * -1 if an error occurs - * >0 a free ID that could be used for the WWID at hand - * *map_alias is set to a freshly allocated string with the matching alias if - * the function returns 0, or to NULL otherwise. - */ -static int -lookup_binding(FILE *f, const char *map_wwid, char **map_alias, - const char *prefix, int check_if_taken) +int get_free_id(const Bindings *bindings, const char *prefix, const char *map_wwid) { - char buf[LINE_MAX]; - unsigned int line_nr = 0; - int id = 1; + const struct binding *bdg; + int i, id = 1; int biggest_id = 1; int smallest_bigger_id = INT_MAX; - *map_alias = NULL; - - rewind(f); - while (fgets(buf, LINE_MAX, f)) { - const char *alias, *wwid; - char *c, *saveptr; - int curr_id; - - line_nr++; - c = strpbrk(buf, "#\n\r"); - if (c) - *c = '\0'; - alias = strtok_r(buf, " \t", &saveptr); - if (!alias) /* blank line */ - continue; + vector_foreach_slot(bindings, bdg, i) { + int curr_id = scan_devname(bdg->alias, prefix); /* * Find an unused index - explanation of the algorithm @@ -333,8 +365,6 @@ lookup_binding(FILE *f, const char *map_wwid, char **map_alias, * biggest_id is always > smallest_bigger_id, except in the * "perfectly ordered" case. */ - - curr_id = scan_devname(alias, prefix); if (curr_id == id) { if (id < INT_MAX) id++; @@ -345,36 +375,15 @@ lookup_binding(FILE *f, const char *map_wwid, char **map_alias, } if (curr_id > biggest_id) biggest_id = curr_id; + if (curr_id > id && curr_id < smallest_bigger_id) smallest_bigger_id = curr_id; - wwid = strtok_r(NULL, " \t", &saveptr); - if (!wwid){ - condlog(3, - "Ignoring malformed line %u in bindings file", - line_nr); - continue; - } - if (strcmp(wwid, map_wwid) == 0){ - condlog(3, "Found matching wwid [%s] in bindings file." - " Setting alias to %s", wwid, alias); - *map_alias = strdup(alias); - if (*map_alias == NULL) { - condlog(0, "Cannot copy alias from bindings " - "file: out of memory"); - return -1; - } - return 0; - } - } - if (!prefix && check_if_taken) - id = -1; - if (id >= smallest_bigger_id) { - if (biggest_id < INT_MAX) - id = biggest_id + 1; - else - id = -1; } - if (id > 0 && check_if_taken) { + + if (id >= smallest_bigger_id) + id = biggest_id < INT_MAX ? biggest_id + 1 : -1; + + if (id > 0) { while(id_already_taken(id, prefix, map_wwid)) { if (id == INT_MAX) { id = -1; @@ -391,64 +400,17 @@ lookup_binding(FILE *f, const char *map_wwid, char **map_alias, } } } - if (id < 0) { + + if (id < 0) condlog(0, "no more available user_friendly_names"); - return -1; - } else - condlog(3, "No matching wwid [%s] in bindings file.", map_wwid); return id; } -static int -rlookup_binding(FILE *f, char *buff, const char *map_alias) -{ - char line[LINE_MAX]; - unsigned int line_nr = 0; - - buff[0] = '\0'; - - while (fgets(line, LINE_MAX, f)) { - char *c, *saveptr; - const char *alias, *wwid; - - line_nr++; - c = strpbrk(line, "#\n\r"); - if (c) - *c = '\0'; - alias = strtok_r(line, " \t", &saveptr); - if (!alias) /* blank line */ - continue; - wwid = strtok_r(NULL, " \t", &saveptr); - if (!wwid){ - condlog(3, - "Ignoring malformed line %u in bindings file", - line_nr); - continue; - } - if (strlen(wwid) > WWID_SIZE - 1) { - condlog(3, - "Ignoring too large wwid at %u in bindings file", line_nr); - continue; - } - if (strcmp(alias, map_alias) == 0){ - condlog(3, "Found matching alias [%s] in bindings file." - " Setting wwid to %s", alias, wwid); - strlcpy(buff, wwid, WWID_SIZE); - return 0; - } - } - condlog(3, "No matching alias [%s] in bindings file.", map_alias); - - return -1; -} - static char * -allocate_binding(int fd, const char *wwid, int id, const char *prefix) +allocate_binding(const char *filename, const char *wwid, int id, const char *prefix) { STRBUF_ON_STACK(buf); - off_t offset; - ssize_t len; - char *alias, *c; + char *alias; if (id <= 0) { condlog(0, "%s: cannot allocate new binding for id %d", @@ -460,164 +422,135 @@ allocate_binding(int fd, const char *wwid, int id, const char *prefix) format_devname(&buf, id) == -1) return NULL; - if (print_strbuf(&buf, " %s\n", wwid) < 0) - return NULL; + alias = steal_strbuf_str(&buf); - offset = lseek(fd, 0, SEEK_END); - if (offset < 0){ - condlog(0, "Cannot seek to end of bindings file : %s", - strerror(errno)); + if (add_binding(&global_bindings, alias, wwid) != BINDING_ADDED) { + condlog(0, "%s: cannot allocate new binding %s for %s", + __func__, alias, wwid); + free(alias); return NULL; } - len = get_strbuf_len(&buf); - alias = steal_strbuf_str(&buf); - - if (write(fd, alias, len) != len) { - condlog(0, "Cannot write binding to bindings file : %s", - strerror(errno)); - /* clear partial write */ - if (ftruncate(fd, offset)) - condlog(0, "Cannot truncate the header : %s", - strerror(errno)); + if (update_bindings_file(&global_bindings, filename) == -1) { + condlog(1, "%s: deleting binding %s for %s", __func__, alias, wwid); + delete_binding(&global_bindings, wwid); free(alias); return NULL; } - c = strchr(alias, ' '); - if (c) - *c = '\0'; condlog(3, "Created new binding [%s] for WWID [%s]", alias, wwid); return alias; } +/* + * get_user_friendly_alias() action table + * + * The table shows the various cases, the actions taken, and the CI + * functions from tests/alias.c that represent them. + * + * - O: old alias given + * - A: old alias in table (y: yes, correct WWID; X: yes, wrong WWID) + * - W: wwid in table + * + * | No | O | A | W | action | function gufa_X | + * |----+---+---+---+--------------------------------------------+------------------------------| + * | 1 | n | - | n | get new alias | nomatch_Y | + * | 2 | n | - | y | use alias from bindings | match_a_Y | + * | 3 | y | n | n | add binding for old alias | old_nomatch_nowwidmatch | + * | 4 | y | n | y | use alias from bindings (avoid duplicates) | old_nomatch_wwidmatch | + * | 5 | y | y | n | [ impossible ] | - | + * | 6 | y | y | y | use old alias == alias from bindings | old_match | + * | 7 | y | X | n | get new alias | old_match_other | + * | 8 | y | X | y | use alias from bindings | old_match_other_wwidmatch | + * + * Notes: + * - "use alias from bindings" means that the alias from the bindings file will + * be tried; if it is in use, the alias selection will fail. No other + * bindings will be attempted. + * - "get new alias" fails if all aliases are used up, or if writing the + * bindings file fails. + * - if "alias_old" is set, it can't be bound to a different map. alias_old is + * initialized in find_existing_alias() by scanning the mpvec. We trust + * that the mpvec corrcectly represents kernel state. + */ + char *get_user_friendly_alias(const char *wwid, const char *file, const char *alias_old, const char *prefix, bool bindings_read_only) { char *alias = NULL; int id = 0; - int fd, can_write; bool new_binding = false; - char buff[WWID_SIZE]; - FILE *f; - - fd = open_file(file, &can_write, bindings_file_header); - if (fd < 0) - return NULL; - - f = fdopen(fd, "r"); - if (!f) { - condlog(0, "cannot fdopen on bindings file descriptor"); - close(fd); - return NULL; - } + const struct binding *bdg; - if (!strlen(alias_old)) + if (!*alias_old) goto new_alias; - /* lookup the binding. if it exists, the wwid will be in buff - * either way, id contains the id for the alias - */ - rlookup_binding(f, buff, alias_old); - - if (strlen(buff) > 0) { - /* If buff is our wwid, it's already allocated correctly. */ - if (strcmp(buff, wwid) == 0) { + /* See if there's a binding matching both alias_old and wwid */ + bdg = get_binding_for_alias(&global_bindings, alias_old); + if (bdg) { + if (!strcmp(bdg->wwid, wwid)) { alias = strdup(alias_old); goto out; - } else { condlog(0, "alias %s already bound to wwid %s, cannot reuse", - alias_old, buff); + alias_old, bdg->wwid); goto new_alias; } } - /* - * Look for an existing alias in the bindings file. - * Pass prefix = NULL, so lookup_binding() won't try to allocate a new id. - */ - lookup_binding(f, wwid, &alias, NULL, 0); - if (alias) { - if (alias_already_taken(alias, wwid)) { - free(alias); - alias = NULL; - } else + /* allocate the existing alias in the bindings file */ + id = scan_devname(alias_old, prefix); + +new_alias: + /* Check for existing binding of WWID */ + bdg = get_binding_for_wwid(&global_bindings, wwid); + if (bdg) { + if (!alias_already_taken(bdg->alias, wwid)) { condlog(3, "Use existing binding [%s] for WWID [%s]", - alias, wwid); + bdg->alias, wwid); + alias = strdup(bdg->alias); + } goto out; } - /* alias_old is already taken by our WWID, update bindings file. */ - id = scan_devname(alias_old, prefix); - -new_alias: if (id <= 0) { /* * no existing alias was provided, or allocating it * failed. Try a new one. */ - id = lookup_binding(f, wwid, &alias, prefix, 1); - if (id == 0 && alias_already_taken(alias, wwid)) { - free(alias); - alias = NULL; - } + id = get_free_id(&global_bindings, prefix, wwid); if (id <= 0) goto out; else new_binding = true; } - if (fflush(f) != 0) { - condlog(0, "cannot fflush bindings file stream : %s", - strerror(errno)); - goto out; - } + if (!bindings_read_only && id > 0) + alias = allocate_binding(file, wwid, id, prefix); - if (can_write && !bindings_read_only) { - alias = allocate_binding(fd, wwid, id, prefix); - if (alias && !new_binding) - condlog(2, "Allocated existing binding [%s] for WWID [%s]", - alias, wwid); - } + if (alias && !new_binding) + condlog(2, "Allocated existing binding [%s] for WWID [%s]", + alias, wwid); out: - pthread_cleanup_push(free, alias); - fclose(f); - pthread_cleanup_pop(0); return alias; } -int -get_user_friendly_wwid(const char *alias, char *buff, const char *file) +int get_user_friendly_wwid(const char *alias, char *buff) { - int fd, unused; - FILE *f; + const struct binding *bdg; if (!alias || *alias == '\0') { condlog(3, "Cannot find binding for empty alias"); return -1; } - fd = open_file(file, &unused, bindings_file_header); - if (fd < 0) - return -1; - - f = fdopen(fd, "r"); - if (!f) { - condlog(0, "cannot fdopen on bindings file descriptor : %s", - strerror(errno)); - close(fd); + bdg = get_binding_for_alias(&global_bindings, alias); + if (!bdg) { + *buff = '\0'; return -1; } - - rlookup_binding(f, buff, alias); - if (!strlen(buff)) { - fclose(f); - return -1; - } - - fclose(f); + strlcpy(buff, bdg->wwid, WWID_SIZE); return 0; } diff --git a/libmultipath/alias.h b/libmultipath/alias.h index 37b49d9c7..5ef6720b8 100644 --- a/libmultipath/alias.h +++ b/libmultipath/alias.h @@ -2,7 +2,7 @@ #define _ALIAS_H int valid_alias(const char *alias); -int get_user_friendly_wwid(const char *alias, char *buff, const char *file); +int get_user_friendly_wwid(const char *alias, char *buff); char *get_user_friendly_alias(const char *wwid, const char *file, const char *alias_old, const char *prefix, bool bindings_read_only); diff --git a/libmultipath/configure.c b/libmultipath/configure.c index 029fbbd27..d80949039 100644 --- a/libmultipath/configure.c +++ b/libmultipath/configure.c @@ -1378,8 +1378,7 @@ static int _get_refwwid(enum mpath_cmds cmd, const char *dev, refwwid = tmpwwid; /* or may be a binding */ - else if (get_user_friendly_wwid(dev, tmpwwid, - conf->bindings_file) == 0) + else if (get_user_friendly_wwid(dev, tmpwwid) == 0) refwwid = tmpwwid; /* or may be an alias */ From f51c2139a1fe0d1827862a4b56e7d8ba540ae401 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 25 Aug 2023 17:55:55 +0200 Subject: [PATCH 19/64] multipath-tools tests: fix alias tests The different implementation of get_user_friendly_alias() and its helpers necessitates changes in the unit tests. It would be nice if it didn't, but the unit tests are too closely bound to the implementation to make this possible. - The bindings table is held in memory in alphabetically sorted order, which may change the result of looking for free alias IDs if the entries in the bindings file were unordered initially. In particular, if only a small number of bindings exists, "holes" in the file will be detected more easily. But because the sort order of the aliases differs from simple alphabetic sorting ("mpathz" precedes "mpathaa"), a bindings file that contains all bindings from "a" to "aa" (or more) will appear unsorted. As an extra check, some of the unit tests deliberately use a different implementation of add_binding() that does not order the bindings table. - Broken lines in the bindings file never make it to the in-memory representation. An alias that appeard "used" because it occurred in a broken line will not appear used any more. Warnings about malformed lines will only be printed while the bindings file is read, not from get_user_friendly_alias(). - The match_line argument of mock_bindings_file() is obsolete. - lookup_binding() and rlookup_binding() have been removed from libmultipath. They are now emulated in the unit test code. - lookup_binding() didn't check for used alias in all cases previously, but it does now. - prefix != NULL and check_if_taken == false is not supported any more in lookup_binding(). - allocate_binding() uses a very different sequence of systems calls now, as it's implemented using update_bindings_file(). In particular, it's now more difficult to predict the content of the write() call that creates the bindings file. See comments for __wrap_write(). - some unit tests for get_user_friendly_alias() had to call mock_bindings_file() twice, because the old implementation would read the file twice (first rlookup_binding() and then lookup_binding()). This is not necessary any more. - The unit tests need a teardown function to clear the bindings table in memory. - Minor changes are necessary because of changed ordering of the log messages. Previously, lookup_binding() combined check for an existing entry and the search for a new ID. The new algorithm does this in two separate steps and tests for used aliases in between, which causes a change in the order in which log messages are emitted. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- tests/alias.c | 957 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 614 insertions(+), 343 deletions(-) diff --git a/tests/alias.c b/tests/alias.c index f334f928a..50a21ecf3 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -3,10 +3,12 @@ #include #include #include +#include "strbuf.h" #include "util.h" #include "alias.h" #include "test-log.h" #include +#include #include "globals.c" #include "../libmultipath/alias.c" @@ -20,18 +22,6 @@ #define MPATH_ID_INT_MAX_p1 "fxshrxx" #endif -void __wrap_rewind(FILE *stream) -{} - -char *__wrap_fgets(char *buf, int n, FILE *stream) -{ - char *val = mock_ptr_type(char *); - if (!val) - return NULL; - strlcpy(buf, val, n); - return buf; -} - static int __set_errno(int err) { if (err >= 0) { @@ -43,23 +33,44 @@ static int __set_errno(int err) } } -off_t __wrap_lseek(int fd, off_t offset, int whence) +/* + * allocate_binding -> write_bindings_file() writes the entire file, i.e. the + * header, any pre-existing bindings, and the new binding. The complete content + * depends on history and is different to predict here. Therefore we check only + * the newly added binding. Because add_binding() sorts entries, this new + * binding isn't necessarily the last one; receive it from will_return() and + * search for it with strstr(). + * If the string to be written doesn't start with the bindings file + * header, it's a test of a partial write. + */ +ssize_t __wrap_write(int fd, const void *buf, size_t count) { - return __set_errno(mock_type(int)); + const char *binding, *start; +#if DEBUG_WRITE + fprintf(stderr, "%s: %zx exp %zx\n===\n%s\n===\n", __func__, strlen(buf), + count, (const char *)buf); +#endif + if (!strncmp((const char *)buf, BINDINGS_FILE_HEADER, + sizeof(BINDINGS_FILE_HEADER) - 1)) + start = (const char *)buf + sizeof(BINDINGS_FILE_HEADER) - 1; + else + start = buf; + binding = mock_ptr_type(char *); + start = strstr(start, binding); + check_expected(count); + assert_ptr_not_equal(start, NULL); + return __set_errno(mock_type(int)); } -ssize_t __wrap_write(int fd, const void *buf, size_t count) +int __wrap_rename(const char *old, const char *new) { - check_expected(count); - check_expected(buf); return __set_errno(mock_type(int)); } -int __wrap_ftruncate(int fd, off_t length) +int __wrap_mkstemp(char *template) { - check_expected(length); - return __set_errno(mock_type(int)); + return 10; } int __wrap_dm_map_present(const char * str) @@ -84,32 +95,6 @@ int __wrap_dm_get_uuid(const char *name, char *uuid, int uuid_len) #define TEST_FDNO 1234 #define TEST_FPTR ((FILE *) 0xaffe) -int __wrap_open_file(const char *file, int *can_write, const char *header) -{ - int cw = mock_type(int); - - *can_write = cw; - return TEST_FDNO; -} - -FILE *__wrap_fdopen(int fd, const char *mode) -{ - assert_int_equal(fd, TEST_FDNO); - return TEST_FPTR; -} - -int __wrap_fflush(FILE *f) -{ - assert_ptr_equal(f, TEST_FPTR); - return 0; -} - -int __wrap_fclose(FILE *f) -{ - assert_ptr_equal(f, TEST_FPTR); - return 0; -} - /* strbuf wrapper for the old format_devname() */ static int __format_devname(char *name, int id, size_t len, const char *prefix) { @@ -466,22 +451,85 @@ static void mock_self_alias(const char *alias, const char *wwid) expect_condlog(3, USED_STR(alias, wwid)); \ } while(0) -static void mock_bindings_file(const char *content, int match_line) +static int add_binding_unsorted(Bindings *bindings, + const char *alias, const char *wwid) +{ + struct binding *bdg = calloc(1, sizeof(*bdg)); + + if (!bdg) + return -1; + bdg->wwid = strdup(wwid); + bdg->alias = strdup(alias); + if (!bdg->wwid || !bdg->alias || !vector_alloc_slot(bindings)) { + free(bdg->alias); + free(bdg->wwid); + free(bdg); + return BINDING_ERROR; + } + vector_set_slot(bindings, bdg); + return BINDING_ADDED; +} + +static void __mock_bindings_file(const char *content, + int (*add)(Bindings *, const char *, const char *)) { - static char cnt[1024]; - char *token; + char *cnt __attribute__((cleanup(cleanup_charp))) = NULL; + char *token, *savep = NULL; int i; - assert_in_range(strlcpy(cnt, content, sizeof(cnt)), 0, sizeof(cnt) - 1); + cnt = strdup(content); + assert_ptr_not_equal(cnt, NULL); - for (token = strtok(cnt, "\n"), i = 0; + for (token = strtok_r(cnt, "\n", &savep), i = 0; token && *token; - token = strtok(NULL, "\n"), i++) { - will_return(__wrap_fgets, token); - if (match_line == i) - return; + token = strtok_r(NULL, "\n", &savep), i++) { + char *alias, *wwid; + int rc; + + if (read_binding(token, i + 1, &alias, &wwid) + == READ_BINDING_SKIP) + continue; + + rc = add(&global_bindings, alias, wwid); + assert_int_equal(rc, BINDING_ADDED); } - will_return(__wrap_fgets, NULL); +} + +static void mock_bindings_file(const char *content) { + return __mock_bindings_file(content, add_binding); +} + +static void mock_bindings_file_unsorted(const char *content) { + return __mock_bindings_file(content, add_binding_unsorted); +} + +static int teardown_bindings(void **state) +{ + cleanup_bindings(); + return 0; +} + +static int lookup_binding(FILE *dummy, const char *wwid, char **alias, + const char *prefix, int check_if_taken) +{ + const struct binding *bdg; + int id; + + /* + * get_free_id() always checks if aliases are taken. + * Therefore if prefix is non-null, check_if_taken must be true. + */ + assert_true(!prefix || check_if_taken); + *alias = NULL; + bdg = get_binding_for_wwid(&global_bindings, wwid); + if (bdg) { + *alias = strdup(bdg->alias); + return 0; + } else if (!prefix && check_if_taken) + return -1; + + id = get_free_id(&global_bindings, prefix, wwid); + return id; } static void lb_empty(void **state) @@ -489,7 +537,7 @@ static void lb_empty(void **state) int rc; char *alias; - mock_bindings_file("", -1); + mock_bindings_file(""); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, NULL, 0); assert_int_equal(rc, 1); @@ -501,7 +549,7 @@ static void lb_empty_unused(void **state) int rc; char *alias; - mock_bindings_file("", -1); + mock_bindings_file(""); mock_unused_alias("MPATHa"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); @@ -515,10 +563,10 @@ static void lb_empty_failed(void **state) int rc; char *alias; - mock_bindings_file("", -1); + mock_bindings_file(""); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_failed_alias("MPATHa", "WWID0"); mock_unused_alias("MPATHb"); - expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); @@ -530,10 +578,10 @@ static void lb_empty_1_used(void **state) int rc; char *alias; - mock_bindings_file("", -1); + mock_bindings_file(""); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_used_alias("MPATHa", "WWID0"); mock_unused_alias("MPATHb"); - expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); @@ -545,10 +593,10 @@ static void lb_empty_1_used_self(void **state) int rc; char *alias; - mock_bindings_file("", -1); + mock_bindings_file(""); + expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_used_alias("MPATHa", "WWID0"); mock_self_alias("MPATHb", "WWID0"); - expect_condlog(3, NOMATCH_WWID_STR("WWID0")); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); @@ -560,9 +608,9 @@ static void lb_match_a(void **state) int rc; char *alias; - mock_bindings_file("MPATHa WWID0\n", 0); + mock_bindings_file("MPATHa WWID0\n"); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); - rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 0); + rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); assert_int_equal(rc, 0); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHa"); @@ -574,9 +622,10 @@ static void lb_nomatch_a(void **state) int rc; char *alias; - mock_bindings_file("MPATHa WWID0\n", -1); + mock_bindings_file("MPATHa WWID0\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID1")); - rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 0); + mock_unused_alias("MPATHb"); + rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); } @@ -586,8 +635,8 @@ static void lb_nomatch_a_bad_check(void **state) int rc; char *alias; - mock_bindings_file("MPATHa WWID0\n", -1); - expect_condlog(0, NOMORE_STR); + mock_bindings_file("MPATHa WWID0\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID1")); rc = lookup_binding(NULL, "WWID1", &alias, NULL, 1); assert_int_equal(rc, -1); assert_ptr_equal(alias, NULL); @@ -598,7 +647,7 @@ static void lb_nomatch_a_unused(void **state) int rc; char *alias; - mock_bindings_file("MPATHa WWID0\n", -1); + mock_bindings_file("MPATHa WWID0\n"); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID1")); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1); @@ -611,27 +660,27 @@ static void lb_nomatch_a_3_used_failed_self(void **state) int rc; char *alias; - mock_bindings_file("MPATHa WWID0\n", -1); + mock_bindings_file("MPATHa WWID0\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID1")); mock_used_alias("MPATHb", "WWID1"); mock_used_alias("MPATHc", "WWID1"); mock_used_alias("MPATHd", "WWID1"); mock_failed_alias("MPATHe", "WWID1"); mock_self_alias("MPATHf", "WWID1"); - expect_condlog(3, NOMATCH_WWID_STR("WWID1")); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1); assert_int_equal(rc, 6); assert_ptr_equal(alias, NULL); } -static void do_lb_match_c(void **state, int check_if_taken) +static void do_lb_match_c(void **state) { int rc; char *alias; mock_bindings_file("MPATHa WWID0\n" - "MPATHc WWID1", 1); + "MPATHc WWID1"); expect_condlog(3, FOUND_STR("MPATHc", "WWID1")); - rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", check_if_taken); + rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1); assert_int_equal(rc, 0); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHc"); @@ -640,12 +689,12 @@ static void do_lb_match_c(void **state, int check_if_taken) static void lb_match_c(void **state) { - do_lb_match_c(state, 0); + do_lb_match_c(state); } static void lb_match_c_check(void **state) { - do_lb_match_c(state, 1); + do_lb_match_c(state); } static void lb_nomatch_a_c(void **state) @@ -654,9 +703,10 @@ static void lb_nomatch_a_c(void **state) char *alias; mock_bindings_file("MPATHa WWID0\n" - "MPATHc WWID1", -1); + "MPATHc WWID1"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); + mock_unused_alias("MPATHb"); + rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); } @@ -667,7 +717,7 @@ static void lb_nomatch_a_d_unused(void **state) char *alias; mock_bindings_file("MPATHa WWID0\n" - "MPATHd WWID1", -1); + "MPATHd WWID1"); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); @@ -681,10 +731,10 @@ static void lb_nomatch_a_d_1_used(void **state) char *alias; mock_bindings_file("MPATHa WWID0\n" - "MPATHd WWID1", -1); + "MPATHd WWID1"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_used_alias("MPATHb", "WWID2"); mock_unused_alias("MPATHc"); - expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); @@ -696,11 +746,11 @@ static void lb_nomatch_a_d_2_used(void **state) char *alias; mock_bindings_file("MPATHa WWID0\n" - "MPATHd WWID1", -1); + "MPATHd WWID1"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_used_alias("MPATHb", "WWID2"); mock_used_alias("MPATHc", "WWID2"); mock_unused_alias("MPATHe"); - expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 5); assert_ptr_equal(alias, NULL); @@ -712,12 +762,12 @@ static void lb_nomatch_a_d_3_used(void **state) char *alias; mock_bindings_file("MPATHa WWID0\n" - "MPATHd WWID1", -1); + "MPATHd WWID1"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_used_alias("MPATHb", "WWID2"); mock_used_alias("MPATHc", "WWID2"); mock_used_alias("MPATHe", "WWID2"); mock_unused_alias("MPATHf"); - expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 6); assert_ptr_equal(alias, NULL); @@ -729,9 +779,10 @@ static void lb_nomatch_c_a(void **state) char *alias; mock_bindings_file("MPATHc WWID1\n" - "MPATHa WWID0\n", -1); + "MPATHa WWID0\n"); + mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); + rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); } @@ -743,7 +794,7 @@ static void lb_nomatch_d_a_unused(void **state) mock_bindings_file("MPATHc WWID1\n" "MPATHa WWID0\n" - "MPATHd WWID0\n", -1); + "MPATHd WWID0\n"); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); @@ -758,10 +809,10 @@ static void lb_nomatch_d_a_1_used(void **state) mock_bindings_file("MPATHc WWID1\n" "MPATHa WWID0\n" - "MPATHd WWID0\n", -1); + "MPATHd WWID0\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); mock_used_alias("MPATHb", "WWID2"); mock_unused_alias("MPATHe"); - expect_condlog(3, NOMATCH_WWID_STR("WWID2")); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 5); assert_ptr_equal(alias, NULL); @@ -774,9 +825,10 @@ static void lb_nomatch_a_b(void **state) mock_bindings_file("MPATHa WWID0\n" "MPATHz WWID26\n" - "MPATHb WWID1\n", -1); + "MPATHb WWID1\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); + mock_unused_alias("MPATHc"); + rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); } @@ -786,13 +838,19 @@ static void lb_nomatch_a_b_bad(void **state) int rc; char *alias; + expect_condlog(1, "invalid line 3 in bindings file, missing WWID\n"); + /* + * The broken line will be ignored when constructing the bindings vector. + * Thus in lookup_binding() MPATHb is never encountered, + * and MPATHb appears usable. + */ mock_bindings_file("MPATHa WWID0\n" "MPATHz WWID26\n" - "MPATHb\n", -1); - expect_condlog(3, "Ignoring malformed line 3 in bindings file\n"); + "MPATHb\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); - assert_int_equal(rc, 3); + mock_unused_alias("MPATHb"); + rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); + assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); } @@ -801,84 +859,200 @@ static void lb_nomatch_a_b_bad_self(void **state) int rc; char *alias; + expect_condlog(1, "invalid line 3 in bindings file, missing WWID\n"); mock_bindings_file("MPATHa WWID0\n" "MPATHz WWID26\n" - "MPATHb\n", -1); - expect_condlog(3, "Ignoring malformed line 3 in bindings file\n"); - mock_self_alias("MPATHc", "WWID2"); + "MPATHb\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); + mock_self_alias("MPATHb", "WWID2"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); - assert_int_equal(rc, 3); + assert_int_equal(rc, 2); assert_ptr_equal(alias, NULL); } -static void lb_nomatch_b_a(void **state) +static void lb_nomatch_b_z_a(void **state) { int rc; char *alias; + /* + * add_bindings() sorts alphabetically. Therefore get_free_id() + * finds MPATHc as a free entry. + */ mock_bindings_file("MPATHb WWID1\n" "MPATHz WWID26\n" - "MPATHa WWID0\n", -1); + "MPATHa WWID0\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); - assert_int_equal(rc, 27); + mock_unused_alias("MPATHc"); + rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); + assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); } -static void lb_nomatch_b_a_3_used(void **state) +static void lb_nomatch_b_aa_a(void **state) { int rc; char *alias; + /* + * add_bindings() sorts alphabetically. ("a", "aa", b"). + * The get_free_id() algorithm finds the "hole" after "b". + */ mock_bindings_file("MPATHb WWID1\n" "MPATHz WWID26\n" - "MPATHa WWID0\n", -1); - mock_used_alias("MPATHaa", "WWID2"); - mock_used_alias("MPATHab", "WWID2"); - mock_used_alias("MPATHac", "WWID2"); - mock_unused_alias("MPATHad"); + "MPATHa WWID0\n"); expect_condlog(3, NOMATCH_WWID_STR("WWID2")); + mock_unused_alias("MPATHc"); rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); - assert_int_equal(rc, 30); + assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); } -#ifdef MPATH_ID_INT_MAX -static void do_lb_nomatch_int_max(void **state, int check_if_taken) +static void fill_bindings(struct strbuf *buf, int start, int end) +{ + int i; + + for (i = start; i <= end; i++) { + print_strbuf(buf, "MPATH"); + format_devname(buf, i + 1); + print_strbuf(buf, " WWID%d\n", i); + } +} + +static void lb_nomatch_b_a_aa(void **state) +{ + int rc; + char *alias; + STRBUF_ON_STACK(buf); + + /* + * add_bindings() sorts alphabetically. ("a", "aa", "ab", "b", "c", ...) + * lookup_binding finds MPATHac as next free entry. + */ + fill_bindings(&buf, 0, 26); + mock_bindings_file(get_strbuf_str(&buf)); + expect_condlog(3, NOMATCH_WWID_STR("WWID28")); + mock_unused_alias("MPATHab"); + rc = lookup_binding(NULL, "WWID28", &alias, "MPATH", 1); + assert_int_equal(rc, 28); + assert_ptr_equal(alias, NULL); +} + +static void lb_nomatch_b_a_aa_zz(void **state) +{ + int rc, i; + char *alias; + STRBUF_ON_STACK(buf); + + /* + * add_bindings() sorts alphabetically. ("a", "aa", "ab", "b", "c", ...) + * lookup_binding finds MPATHaaa as next free entry, because MPATHaa is + * found before MPATHb, and MPATHzz was in the bindings, too. + */ + for (i = 0; i <= 26; i++) { + print_strbuf(&buf, "MPATH"); + format_devname(&buf, i + 1); + print_strbuf(&buf, " WWID%d\n", i); + } + print_strbuf(&buf, "MPATHzz WWID676\n"); + mock_bindings_file(get_strbuf_str(&buf)); + expect_condlog(3, NOMATCH_WWID_STR("WWID703")); + mock_unused_alias("MPATHaaa"); + rc = lookup_binding(NULL, "WWID703", &alias, "MPATH", 1); + assert_int_equal(rc, 703); + assert_ptr_equal(alias, NULL); +} + +static void lb_nomatch_b_z_a_unsorted(void **state) +{ + int rc; + char *alias; + + /* + * With unsorted bindings (shouldn't happen normally), get_free_id() + * plays safe and returns MPATHaa as first free entry. + */ + mock_bindings_file_unsorted("MPATHb WWID1\n" + "MPATHz WWID26\n" + "MPATHa WWID0\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); + mock_unused_alias("MPATHaa"); + rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); + assert_int_equal(rc, 27); + assert_ptr_equal(alias, NULL); +} + +static void lb_nomatch_b_a(void **state) { int rc; char *alias; mock_bindings_file("MPATHb WWID1\n" - "MPATH" MPATH_ID_INT_MAX " WWIDMAX\n" - "MPATHa WWID0\n", -1); - expect_condlog(0, NOMORE_STR); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", check_if_taken); - assert_int_equal(rc, -1); + "MPATHa WWID0\n"); + expect_condlog(3, NOMATCH_WWID_STR("WWID2")); + mock_unused_alias("MPATHc"); + rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); + assert_int_equal(rc, 3); assert_ptr_equal(alias, NULL); } -static void lb_nomatch_int_max(void **state) +static void lb_nomatch_b_a_3_used(void **state) { - do_lb_nomatch_int_max(state, 0); + int rc; + char *alias; + STRBUF_ON_STACK(buf); + + fill_bindings(&buf, 0, 26); + mock_bindings_file(get_strbuf_str(&buf)); + expect_condlog(3, NOMATCH_WWID_STR("WWID31")); + mock_used_alias("MPATHab", "WWID31"); + mock_used_alias("MPATHac", "WWID31"); + mock_used_alias("MPATHad", "WWID31"); + mock_unused_alias("MPATHae"); + rc = lookup_binding(NULL, "WWID31", &alias, "MPATH", 1); + assert_int_equal(rc, 31); + assert_ptr_equal(alias, NULL); } -static void lb_nomatch_int_max_check(void **state) +#ifdef MPATH_ID_INT_MAX +/* + * The bindings will be sorted by alias, alphabetically, which is not + * the same as the "numeric" sort order for user-friendly aliases. + * get_free_id() selects the highest used ID + 1 if an unsorted entry + * is encountered in the bindings table and it's id is equal to the + * next "expected" id. This happens if all IDs from "a" to "aa" are + * in the table. If the INT_MAX entry is in the table, too, it will + * overflow. + */ +static void lb_nomatch_int_max(void **state) { - do_lb_nomatch_int_max(state, 1); + int rc; + char *alias; + STRBUF_ON_STACK(buf); + + fill_bindings(&buf, 0, 26); + print_strbuf(&buf, "MPATH%s WWIDMAX\n", MPATH_ID_INT_MAX); + mock_bindings_file(get_strbuf_str(&buf)); + expect_condlog(3, NOMATCH_WWID_STR("WWIDNOMORE")); + expect_condlog(0, NOMORE_STR); + rc = lookup_binding(NULL, "WWIDNOMORE", &alias, "MPATH", 1); + assert_int_equal(rc, -1); + assert_ptr_equal(alias, NULL); } static void lb_nomatch_int_max_used(void **state) { int rc; char *alias; + STRBUF_ON_STACK(buf); - mock_bindings_file("MPATHb WWID1\n" - "MPATH" MPATH_ID_INT_MAX " WWIDMAX\n", -1); - mock_used_alias("MPATHa", "WWID2"); + fill_bindings(&buf, 1, 26); + print_strbuf(&buf, "MPATH%s WWIDMAX\n", MPATH_ID_INT_MAX); + mock_bindings_file(get_strbuf_str(&buf)); + expect_condlog(3, NOMATCH_WWID_STR("WWIDNOMORE")); + mock_used_alias("MPATHa", "WWIDNOMORE"); expect_condlog(0, NOMORE_STR); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); + rc = lookup_binding(NULL, "WWIDNOMORE", &alias, "MPATH", 1); assert_int_equal(rc, -1); assert_ptr_equal(alias, NULL); } @@ -887,12 +1061,14 @@ static void lb_nomatch_int_max_m1(void **state) { int rc; char *alias; + STRBUF_ON_STACK(buf); - mock_bindings_file("MPATHb WWID1\n" - "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n" - "MPATHa WWID0\n", -1); - expect_condlog(3, NOMATCH_WWID_STR("WWID2")); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 0); + fill_bindings(&buf, 0, 26); + print_strbuf(&buf, "MPATH%s WWIDMAXM1\n", MPATH_ID_INT_MAX_m1); + mock_bindings_file(get_strbuf_str(&buf)); + expect_condlog(3, NOMATCH_WWID_STR("WWIDMAX")); + mock_unused_alias("MPATH" MPATH_ID_INT_MAX); + rc = lookup_binding(NULL, "WWIDMAX", &alias, "MPATH", 1); assert_int_equal(rc, INT_MAX); assert_ptr_equal(alias, NULL); } @@ -901,13 +1077,15 @@ static void lb_nomatch_int_max_m1_used(void **state) { int rc; char *alias; + STRBUF_ON_STACK(buf); - mock_bindings_file("MPATHb WWID1\n" - "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n" - "MPATHa WWID0\n", -1); - mock_used_alias("MPATH" MPATH_ID_INT_MAX, "WWID2"); + fill_bindings(&buf, 0, 26); + print_strbuf(&buf, "MPATH%s WWIDMAXM1\n", MPATH_ID_INT_MAX_m1); + mock_bindings_file(get_strbuf_str(&buf)); + expect_condlog(3, NOMATCH_WWID_STR("WWIDMAX")); + mock_used_alias("MPATH" MPATH_ID_INT_MAX, "WWIDMAX"); expect_condlog(0, NOMORE_STR); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); + rc = lookup_binding(NULL, "WWIDMAX", &alias, "MPATH", 1); assert_int_equal(rc, -1); assert_ptr_equal(alias, NULL); } @@ -916,13 +1094,15 @@ static void lb_nomatch_int_max_m1_1_used(void **state) { int rc; char *alias; + STRBUF_ON_STACK(buf); - mock_bindings_file("MPATHb WWID1\n" - "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n", -1); - mock_used_alias("MPATHa", "WWID2"); + fill_bindings(&buf, 1, 26); + print_strbuf(&buf, "MPATH%s WWIDMAXM1\n", MPATH_ID_INT_MAX_m1); + mock_bindings_file(get_strbuf_str(&buf)); + expect_condlog(3, NOMATCH_WWID_STR("WWIDMAX")); + mock_used_alias("MPATHa", "WWIDMAX"); mock_unused_alias("MPATH" MPATH_ID_INT_MAX); - expect_condlog(3, NOMATCH_WWID_STR("WWID2")); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); + rc = lookup_binding(NULL, "WWIDMAX", &alias, "MPATH", 1); assert_int_equal(rc, INT_MAX); assert_ptr_equal(alias, NULL); } @@ -931,13 +1111,17 @@ static void lb_nomatch_int_max_m1_2_used(void **state) { int rc; char *alias; + STRBUF_ON_STACK(buf); - mock_bindings_file("MPATHb WWID1\n" - "MPATH" MPATH_ID_INT_MAX_m1 " WWIDMAX\n", -1); - mock_used_alias("MPATHa", "WWID2"); - mock_used_alias("MPATH" MPATH_ID_INT_MAX, "WWID2"); + fill_bindings(&buf, 1, 26); + print_strbuf(&buf, "MPATH%s WWIDMAXM1\n", MPATH_ID_INT_MAX_m1); + mock_bindings_file(get_strbuf_str(&buf)); + + expect_condlog(3, NOMATCH_WWID_STR("WWIDMAX")); + mock_used_alias("MPATHa", "WWIDMAX"); + mock_used_alias("MPATH" MPATH_ID_INT_MAX, "WWIDMAX"); expect_condlog(0, NOMORE_STR); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); + rc = lookup_binding(NULL, "WWIDMAX", &alias, "MPATH", 1); assert_int_equal(rc, -1); assert_ptr_equal(alias, NULL); } @@ -946,52 +1130,68 @@ static void lb_nomatch_int_max_m1_2_used(void **state) static int test_lookup_binding(void) { const struct CMUnitTest tests[] = { - cmocka_unit_test(lb_empty), - cmocka_unit_test(lb_empty_unused), - cmocka_unit_test(lb_empty_failed), - cmocka_unit_test(lb_empty_1_used), - cmocka_unit_test(lb_empty_1_used_self), - cmocka_unit_test(lb_match_a), - cmocka_unit_test(lb_nomatch_a), - cmocka_unit_test(lb_nomatch_a_bad_check), - cmocka_unit_test(lb_nomatch_a_unused), - cmocka_unit_test(lb_nomatch_a_3_used_failed_self), - cmocka_unit_test(lb_match_c), - cmocka_unit_test(lb_match_c_check), - cmocka_unit_test(lb_nomatch_a_c), - cmocka_unit_test(lb_nomatch_a_d_unused), - cmocka_unit_test(lb_nomatch_a_d_1_used), - cmocka_unit_test(lb_nomatch_a_d_2_used), - cmocka_unit_test(lb_nomatch_a_d_3_used), - cmocka_unit_test(lb_nomatch_c_a), - cmocka_unit_test(lb_nomatch_d_a_unused), - cmocka_unit_test(lb_nomatch_d_a_1_used), - cmocka_unit_test(lb_nomatch_a_b), - cmocka_unit_test(lb_nomatch_a_b_bad), - cmocka_unit_test(lb_nomatch_a_b_bad_self), - cmocka_unit_test(lb_nomatch_b_a), - cmocka_unit_test(lb_nomatch_b_a_3_used), + cmocka_unit_test_teardown(lb_empty, teardown_bindings), + cmocka_unit_test_teardown(lb_empty_unused, teardown_bindings), + cmocka_unit_test_teardown(lb_empty_failed, teardown_bindings), + cmocka_unit_test_teardown(lb_empty_1_used, teardown_bindings), + cmocka_unit_test_teardown(lb_empty_1_used_self, teardown_bindings), + cmocka_unit_test_teardown(lb_match_a, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_a, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_a_bad_check, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_a_unused, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_a_3_used_failed_self, teardown_bindings), + cmocka_unit_test_teardown(lb_match_c, teardown_bindings), + cmocka_unit_test_teardown(lb_match_c_check, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_a_c, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_a_d_unused, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_a_d_1_used, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_a_d_2_used, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_a_d_3_used, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_c_a, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_d_a_unused, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_d_a_1_used, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_a_b, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_a_b_bad, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_a_b_bad_self, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_b_z_a, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_b_aa_a, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_b_a_aa, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_b_a_aa_zz, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_b_z_a_unsorted, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_b_a, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_b_a_3_used, teardown_bindings), #ifdef MPATH_ID_INT_MAX - cmocka_unit_test(lb_nomatch_int_max), - cmocka_unit_test(lb_nomatch_int_max_check), - cmocka_unit_test(lb_nomatch_int_max_used), - cmocka_unit_test(lb_nomatch_int_max_m1), - cmocka_unit_test(lb_nomatch_int_max_m1_used), - cmocka_unit_test(lb_nomatch_int_max_m1_1_used), - cmocka_unit_test(lb_nomatch_int_max_m1_2_used), + cmocka_unit_test_teardown(lb_nomatch_int_max, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_int_max_used, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_int_max_m1, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_int_max_m1_used, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_int_max_m1_1_used, teardown_bindings), + cmocka_unit_test_teardown(lb_nomatch_int_max_m1_2_used, teardown_bindings), #endif }; return cmocka_run_group_tests(tests, NULL, NULL); } +static int rlookup_binding(FILE *dummy, char *buf, const char *alias) { + + const struct binding *bdg; + + bdg = get_binding_for_alias(&global_bindings, alias); + if (!bdg) { + return -1; + } + strlcpy(buf, bdg->wwid, WWID_SIZE); + return 0; +} + static void rl_empty(void **state) { int rc; char buf[WWID_SIZE]; buf[0] = '\0'; - mock_bindings_file("", -1); + mock_bindings_file(""); expect_condlog(3, NOMATCH_STR("MPATHa")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, -1); @@ -1004,7 +1204,7 @@ static void rl_match_a(void **state) char buf[WWID_SIZE]; buf[0] = '\0'; - mock_bindings_file("MPATHa WWID0\n", 0); + mock_bindings_file("MPATHa WWID0\n"); expect_condlog(3, FOUND_ALIAS_STR("MPATHa", "WWID0")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, 0); @@ -1017,7 +1217,7 @@ static void rl_nomatch_a(void **state) char buf[WWID_SIZE]; buf[0] = '\0'; - mock_bindings_file("MPATHa WWID0\n", -1); + mock_bindings_file("MPATHa WWID0\n"); expect_condlog(3, NOMATCH_STR("MPATHb")); rc = rlookup_binding(NULL, buf, "MPATHb"); assert_int_equal(rc, -1); @@ -1030,8 +1230,8 @@ static void rl_malformed_a(void **state) char buf[WWID_SIZE]; buf[0] = '\0'; - mock_bindings_file("MPATHa \n", -1); - expect_condlog(3, "Ignoring malformed line 1 in bindings file\n"); + expect_condlog(1, "invalid line 1 in bindings file, missing WWID\n"); + mock_bindings_file("MPATHa \n"); expect_condlog(3, NOMATCH_STR("MPATHa")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, -1); @@ -1049,8 +1249,8 @@ static void rl_overlong_a(void **state) snprintf(line + sizeof(line) - 2, 2, "\n"); buf[0] = '\0'; - mock_bindings_file(line, -1); expect_condlog(3, "Ignoring too large wwid at 1 in bindings file\n"); + mock_bindings_file(line); expect_condlog(3, NOMATCH_STR("MPATHa")); rc = rlookup_binding(NULL, buf, "MPATHa"); assert_int_equal(rc, -1); @@ -1065,7 +1265,7 @@ static void rl_match_b(void **state) buf[0] = '\0'; mock_bindings_file("MPATHa WWID0\n" "MPATHz WWID26\n" - "MPATHb WWID2\n", 2); + "MPATHb WWID2\n"); expect_condlog(3, FOUND_ALIAS_STR("MPATHb", "WWID2")); rc = rlookup_binding(NULL, buf, "MPATHb"); assert_int_equal(rc, 0); @@ -1075,31 +1275,41 @@ static void rl_match_b(void **state) static int test_rlookup_binding(void) { const struct CMUnitTest tests[] = { - cmocka_unit_test(rl_empty), - cmocka_unit_test(rl_match_a), - cmocka_unit_test(rl_nomatch_a), - cmocka_unit_test(rl_malformed_a), - cmocka_unit_test(rl_overlong_a), - cmocka_unit_test(rl_match_b), + cmocka_unit_test_teardown(rl_empty, teardown_bindings), + cmocka_unit_test_teardown(rl_match_a, teardown_bindings), + cmocka_unit_test_teardown(rl_nomatch_a, teardown_bindings), + cmocka_unit_test_teardown(rl_malformed_a, teardown_bindings), + cmocka_unit_test_teardown(rl_overlong_a, teardown_bindings), + cmocka_unit_test_teardown(rl_match_b, teardown_bindings), }; return cmocka_run_group_tests(tests, NULL, NULL); } +void check_bindings_size(int n) +{ + /* avoid -Waddress problem */ + Bindings *bindings = &global_bindings; + + assert_int_equal(VECTOR_SIZE(bindings), n); +} + static void al_a(void **state) { static const char ln[] = "MPATHa WWIDa\n"; char *alias; - will_return(__wrap_lseek, 0); - expect_value(__wrap_write, count, strlen(ln)); - expect_string(__wrap_write, buf, ln); - will_return(__wrap_write, strlen(ln)); + expect_value(__wrap_write, count, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); + will_return(__wrap_write, ln); + will_return(__wrap_write, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); + will_return(__wrap_rename, 0); + expect_condlog(1, "updated bindings file foo"); expect_condlog(3, NEW_STR("MPATHa", "WWIDa")); - alias = allocate_binding(0, "WWIDa", 1, "MPATH"); + alias = allocate_binding("foo", "WWIDa", 1, "MPATH"); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHa"); + check_bindings_size(1); free(alias); } @@ -1108,15 +1318,17 @@ static void al_zz(void **state) static const char ln[] = "MPATHzz WWIDzz\n"; char *alias; - will_return(__wrap_lseek, 0); - expect_value(__wrap_write, count, strlen(ln)); - expect_string(__wrap_write, buf, ln); - will_return(__wrap_write, strlen(ln)); + expect_value(__wrap_write, count, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); + will_return(__wrap_write, ln); + will_return(__wrap_write, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); + will_return(__wrap_rename, 0); + expect_condlog(1, "updated bindings file foo"); expect_condlog(3, NEW_STR("MPATHzz", "WWIDzz")); - alias = allocate_binding(0, "WWIDzz", 26*26 + 26, "MPATH"); + alias = allocate_binding("foo", "WWIDzz", 26*26 + 26, "MPATH"); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHzz"); + check_bindings_size(1); free(alias); } @@ -1127,6 +1339,7 @@ static void al_0(void **state) expect_condlog(0, "allocate_binding: cannot allocate new binding for id 0\n"); alias = allocate_binding(0, "WWIDa", 0, "MPATH"); assert_ptr_equal(alias, NULL); + check_bindings_size(0); } static void al_m2(void **state) @@ -1136,67 +1349,133 @@ static void al_m2(void **state) expect_condlog(0, "allocate_binding: cannot allocate new binding for id -2\n"); alias = allocate_binding(0, "WWIDa", -2, "MPATH"); assert_ptr_equal(alias, NULL); + check_bindings_size(0); +} + +static void al_write_partial(void **state) +{ + static const char ln[] = "MPATHa WWIDa\n"; + char *alias; + + expect_value(__wrap_write, count, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); + will_return(__wrap_write, ln); + will_return(__wrap_write, strlen(BINDINGS_FILE_HEADER) + strlen(ln) - 1); + expect_value(__wrap_write, count, 1); + will_return(__wrap_write, ln + sizeof(ln) - 2); + will_return(__wrap_write, 1); + will_return(__wrap_rename, 0); + expect_condlog(1, "updated bindings file foo"); + expect_condlog(3, "Created new binding [MPATHa] for WWID [WWIDa]\n"); + + alias = allocate_binding("foo", "WWIDa", 1, "MPATH"); + assert_ptr_not_equal(alias, NULL); + assert_string_equal(alias, "MPATHa"); + check_bindings_size(1); + free(alias); } -static void al_lseek_err(void **state) +static void al_write_short(void **state) { + static const char ln[] = "MPATHa WWIDa\n"; char *alias; - will_return(__wrap_lseek, -ENODEV); - expect_condlog(0, "Cannot seek to end of bindings file : No such device\n"); - alias = allocate_binding(0, "WWIDa", 1, "MPATH"); + expect_value(__wrap_write, count, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); + will_return(__wrap_write, ln); + will_return(__wrap_write, strlen(BINDINGS_FILE_HEADER) + strlen(ln) - 1); + expect_value(__wrap_write, count, 1); + will_return(__wrap_write, ln + sizeof(ln) - 2); + will_return(__wrap_write, 0); + expect_condlog(2, "write_bindings_file: short write"); + expect_condlog(1, "failed to write new bindings file"); + expect_condlog(1, "allocate_binding: deleting binding MPATHa for WWIDa"); + + alias = allocate_binding("foo", "WWIDa", 1, "MPATH"); assert_ptr_equal(alias, NULL); + check_bindings_size(0); } static void al_write_err(void **state) { static const char ln[] = "MPATHa WWIDa\n"; - const int offset = 20; char *alias; - will_return(__wrap_lseek, offset); - expect_value(__wrap_write, count, strlen(ln)); - expect_string(__wrap_write, buf, ln); - will_return(__wrap_write, strlen(ln) - 1); - expect_value(__wrap_ftruncate, length, offset); - will_return(__wrap_ftruncate, 0); - expect_condlog(0, "Cannot write binding to bindings file :"); + expect_value(__wrap_write, count, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); + will_return(__wrap_write, ln); + will_return(__wrap_write, -EPERM); + expect_condlog(1, "failed to write new bindings file"); + expect_condlog(1, "allocate_binding: deleting binding MPATHa for WWIDa"); - alias = allocate_binding(0, "WWIDa", 1, "MPATH"); + alias = allocate_binding("foo", "WWIDa", 1, "MPATH"); assert_ptr_equal(alias, NULL); + check_bindings_size(0); +} + +static void al_rename_err(void **state) +{ + static const char ln[] = "MPATHa WWIDa\n"; + char *alias; + + expect_value(__wrap_write, count, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); + will_return(__wrap_write, ln); + will_return(__wrap_write, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); + will_return(__wrap_rename, -EROFS); + + expect_condlog(0, "update_bindings_file: rename: Read-only file system"); + expect_condlog(1, "allocate_binding: deleting binding MPATHa for WWIDa"); + alias = allocate_binding("foo", "WWIDa", 1, "MPATH"); + assert_ptr_equal(alias, NULL); + check_bindings_size(0); } static int test_allocate_binding(void) { const struct CMUnitTest tests[] = { - cmocka_unit_test(al_a), - cmocka_unit_test(al_zz), - cmocka_unit_test(al_0), - cmocka_unit_test(al_m2), - cmocka_unit_test(al_lseek_err), - cmocka_unit_test(al_write_err), + cmocka_unit_test_teardown(al_a, teardown_bindings), + cmocka_unit_test_teardown(al_zz, teardown_bindings), + cmocka_unit_test_teardown(al_0, teardown_bindings), + cmocka_unit_test_teardown(al_m2, teardown_bindings), + cmocka_unit_test_teardown(al_write_partial, teardown_bindings), + cmocka_unit_test_teardown(al_write_short, teardown_bindings), + cmocka_unit_test_teardown(al_write_err, teardown_bindings), + cmocka_unit_test_teardown(al_rename_err, teardown_bindings), }; return cmocka_run_group_tests(tests, NULL, NULL); } -#define mock_allocate_binding(alias, wwid) \ +#define mock_allocate_binding_err_len(alias, wwid, len, err, msg) \ do { \ static const char ln[] = BINDING_STR(alias, wwid); \ \ - will_return(__wrap_lseek, 0); \ - expect_value(__wrap_write, count, strlen(ln)); \ - expect_string(__wrap_write, buf, ln); \ - will_return(__wrap_write, strlen(ln)); \ - expect_condlog(3, NEW_STR(alias, wwid)); \ + expect_value(__wrap_write, count, \ + strlen(BINDINGS_FILE_HEADER) + (len) + strlen(ln)); \ + will_return(__wrap_write, ln); \ + will_return(__wrap_write, \ + strlen(BINDINGS_FILE_HEADER) + (len) + strlen(ln)); \ + will_return(__wrap_rename, err); \ + if (err == 0) { \ + expect_condlog(1, "updated bindings file x\n"); \ + expect_condlog(3, NEW_STR(alias, wwid)); \ + } else { \ + expect_condlog(0, "update_bindings_file: rename: " msg "\n"); \ + expect_condlog(1, "allocate_binding: deleting binding " \ + alias " for " wwid "\n"); \ + } \ } while (0) +#define mock_allocate_binding_err(alias, wwid, err, msg) \ + mock_allocate_binding_err_len(alias, wwid, 0, err, msg) + +#define mock_allocate_binding(alias, wwid) \ + mock_allocate_binding_err(alias, wwid, 0, "") + +#define mock_allocate_binding_len(alias, wwid, len) \ + mock_allocate_binding_err_len(alias, wwid, len, 0, "") + static void gufa_empty_new_rw(void **state) { char *alias; - will_return(__wrap_open_file, true); - - mock_bindings_file("", -1); + mock_bindings_file(""); mock_unused_alias("MPATHa"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); @@ -1208,10 +1487,11 @@ static void gufa_empty_new_rw(void **state) { static void gufa_empty_new_ro_1(void **state) { char *alias; - will_return(__wrap_open_file, false); - mock_bindings_file("", -1); + + mock_bindings_file(""); mock_unused_alias("MPATHa"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); + mock_allocate_binding_err("MPATHa", "WWID0", -EROFS, "Read-only file system"); alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", false); assert_ptr_equal(alias, NULL); @@ -1220,11 +1500,9 @@ static void gufa_empty_new_ro_1(void **state) { static void gufa_empty_new_ro_2(void **state) { char *alias; - will_return(__wrap_open_file, true); - - mock_bindings_file("", -1); - mock_unused_alias("MPATHa"); + mock_bindings_file(""); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); + mock_unused_alias("MPATHa"); alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", true); assert_ptr_equal(alias, NULL); @@ -1233,11 +1511,10 @@ static void gufa_empty_new_ro_2(void **state) { static void gufa_match_a_unused(void **state) { char *alias; - will_return(__wrap_open_file, true); - - mock_bindings_file("MPATHa WWID0", 0); + mock_bindings_file("MPATHa WWID0"); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_unused_alias("MPATHa"); + expect_condlog(3, EXISTING_STR("MPATHa", "WWID0")); alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", true); assert_string_equal(alias, "MPATHa"); @@ -1247,11 +1524,10 @@ static void gufa_match_a_unused(void **state) { static void gufa_match_a_self(void **state) { char *alias; - will_return(__wrap_open_file, true); - - mock_bindings_file("MPATHa WWID0", 0); + mock_bindings_file("MPATHa WWID0"); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_self_alias("MPATHa", "WWID0"); + expect_condlog(3, EXISTING_STR("MPATHa", "WWID0")); alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", true); assert_string_equal(alias, "MPATHa"); @@ -1261,9 +1537,8 @@ static void gufa_match_a_self(void **state) { static void gufa_match_a_used(void **state) { char *alias; - will_return(__wrap_open_file, true); - mock_bindings_file("MPATHa WWID0", 0); + mock_bindings_file("MPATHa WWID0"); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_used_alias("MPATHa", "WWID0"); @@ -1273,15 +1548,14 @@ static void gufa_match_a_used(void **state) { static void gufa_nomatch_a_c(void **state) { char *alias; - will_return(__wrap_open_file, true); + static const char bindings[] = ("MPATHa WWID0\n" + "MPATHc WWID2\n"); - mock_bindings_file("MPATHa WWID0\n" - "MPATHc WWID2", - -1); + mock_bindings_file(bindings); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID1")); - mock_allocate_binding("MPATHb", "WWID1"); + mock_allocate_binding_len("MPATHb", "WWID1", strlen(bindings)); alias = get_user_friendly_alias("WWID1", "x", "", "MPATH", false); assert_string_equal(alias, "MPATHb"); @@ -1290,15 +1564,14 @@ static void gufa_nomatch_a_c(void **state) { static void gufa_nomatch_c_a(void **state) { char *alias; - will_return(__wrap_open_file, true); + const char bindings[] = ("MPATHc WWID2\n" + "MPATHa WWID0\n"); - mock_bindings_file("MPATHc WWID2\n" - "MPATHa WWID0", - -1); + mock_bindings_file(bindings); mock_unused_alias("MPATHb"); expect_condlog(3, NOMATCH_WWID_STR("WWID1")); - mock_allocate_binding("MPATHb", "WWID1"); + mock_allocate_binding_len("MPATHb", "WWID1", sizeof(bindings) - 1); alias = get_user_friendly_alias("WWID1", "x", "", "MPATH", false); assert_string_equal(alias, "MPATHb"); @@ -1307,15 +1580,14 @@ static void gufa_nomatch_c_a(void **state) { static void gufa_nomatch_c_b(void **state) { char *alias; - will_return(__wrap_open_file, true); + const char bindings[] = ("MPATHc WWID2\n" + "MPATHb WWID1\n"); - mock_bindings_file("MPATHc WWID2\n" - "MPATHb WWID1\n", - -1); - mock_unused_alias("MPATHa"); + mock_bindings_file(bindings); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); + mock_unused_alias("MPATHa"); - mock_allocate_binding("MPATHa", "WWID0"); + mock_allocate_binding_len("MPATHa", "WWID0", sizeof(bindings) - 1); alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", false); assert_string_equal(alias, "MPATHa"); @@ -1324,16 +1596,15 @@ static void gufa_nomatch_c_b(void **state) { static void gufa_nomatch_c_b_used(void **state) { char *alias; - will_return(__wrap_open_file, true); + const char bindings[] = ("MPATHc WWID2\n" + "MPATHb WWID1\n"); - mock_bindings_file("MPATHc WWID2\n" - "MPATHb WWID1", - -1); - mock_used_alias("MPATHa", "WWID4"); + mock_bindings_file(bindings); expect_condlog(3, NOMATCH_WWID_STR("WWID4")); + mock_used_alias("MPATHa", "WWID4"); mock_unused_alias("MPATHd"); - mock_allocate_binding("MPATHd", "WWID4"); + mock_allocate_binding_len("MPATHd", "WWID4", sizeof(bindings) - 1); alias = get_user_friendly_alias("WWID4", "x", "", "MPATH", false); assert_string_equal(alias, "MPATHd"); @@ -1342,32 +1613,59 @@ static void gufa_nomatch_c_b_used(void **state) { static void gufa_nomatch_b_f_a(void **state) { char *alias; - will_return(__wrap_open_file, true); + const char bindings[] = ("MPATHb WWID1\n" + "MPATHf WWID6\n" + "MPATHa WWID0\n"); - mock_bindings_file("MPATHb WWID1\n" - "MPATHf WWID6\n" - "MPATHa WWID0\n", - -1); + mock_bindings_file_unsorted(bindings); expect_condlog(3, NOMATCH_WWID_STR("WWID7")); mock_unused_alias("MPATHg"); - mock_allocate_binding("MPATHg", "WWID7"); + mock_allocate_binding_len("MPATHg", "WWID7", sizeof(bindings) - 1); alias = get_user_friendly_alias("WWID7", "x", "", "MPATH", false); assert_string_equal(alias, "MPATHg"); free(alias); } +static void gufa_nomatch_b_aa_a(void **state) { + char *alias; + STRBUF_ON_STACK(buf); + + fill_bindings(&buf, 0, 26); + mock_bindings_file(get_strbuf_str(&buf)); + expect_condlog(3, NOMATCH_WWID_STR("WWID28")); + mock_unused_alias("MPATHab"); + mock_allocate_binding_len("MPATHab", "WWID28", get_strbuf_len(&buf)); + + alias = get_user_friendly_alias("WWID28", "x", "", "MPATH", false); + assert_string_equal(alias, "MPATHab"); + free(alias); +} + +static void gufa_nomatch_b_f_a_sorted(void **state) { + char *alias; + const char bindings[] = ("MPATHb WWID1\n" + "MPATHf WWID6\n" + "MPATHa WWID0\n"); + + mock_bindings_file(bindings); + expect_condlog(3, NOMATCH_WWID_STR("WWID7")); + mock_unused_alias("MPATHc"); + + mock_allocate_binding_len("MPATHc", "WWID7", sizeof(bindings) - 1); + + alias = get_user_friendly_alias("WWID7", "x", "", "MPATH", false); + assert_string_equal(alias, "MPATHc"); + free(alias); +} + static void gufa_old_empty(void **state) { char *alias; - will_return(__wrap_open_file, true); /* rlookup_binding for ALIAS */ - mock_bindings_file("", -1); + mock_bindings_file(""); expect_condlog(3, NOMATCH_STR("MPATHz")); - - /* lookup_binding */ - mock_bindings_file("", -1); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_allocate_binding("MPATHz", "WWID0"); @@ -1380,11 +1678,9 @@ static void gufa_old_empty(void **state) { static void gufa_old_match(void **state) { char *alias; - will_return(__wrap_open_file, true); mock_bindings_file("MPATHb WWID1\n" - "MPATHz WWID0", - 1); + "MPATHz WWID0"); expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID0")); alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); @@ -1394,19 +1690,15 @@ static void gufa_old_match(void **state) { static void gufa_old_match_other(void **state) { char *alias; - static const char bindings[] = "MPATHz WWID9"; - - will_return(__wrap_open_file, true); + static const char bindings[] = "MPATHz WWID9\n"; - mock_bindings_file(bindings, 0); + mock_bindings_file(bindings); expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID9")); expect_condlog(0, REUSE_STR("MPATHz", "WWID9")); - - mock_bindings_file(bindings, -1); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_unused_alias("MPATHa"); - mock_allocate_binding("MPATHa", "WWID0"); + mock_allocate_binding_len("MPATHa", "WWID0", sizeof(bindings) - 1); alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHa"); @@ -1415,21 +1707,16 @@ static void gufa_old_match_other(void **state) { static void gufa_old_match_other_used(void **state) { char *alias; - static const char bindings[] = "MPATHz WWID9"; + static const char bindings[] = "MPATHz WWID9\n"; - will_return(__wrap_open_file, true); - - mock_bindings_file(bindings, 0); + mock_bindings_file(bindings); expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID9")); expect_condlog(0, REUSE_STR("MPATHz", "WWID9")); - - mock_bindings_file(bindings, -1); - mock_used_alias("MPATHa", "WWID0"); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); + mock_used_alias("MPATHa", "WWID0"); mock_unused_alias("MPATHb"); - mock_allocate_binding("MPATHb", "WWID0"); - + mock_allocate_binding_len("MPATHb", "WWID0", sizeof(bindings) - 1); alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHb"); free(alias); @@ -1439,15 +1726,13 @@ static void gufa_old_match_other_wwidmatch(void **state) { char *alias; static const char bindings[] = ("MPATHz WWID9\n" "MPATHc WWID2"); - will_return(__wrap_open_file, true); - mock_bindings_file(bindings, 0); + mock_bindings_file(bindings); expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID9")); expect_condlog(0, REUSE_STR("MPATHz", "WWID9")); - - mock_bindings_file(bindings, 1); expect_condlog(3, FOUND_STR("MPATHc", "WWID2")); mock_unused_alias("MPATHc"); + expect_condlog(3, EXISTING_STR("MPATHc", "WWID2")); alias = get_user_friendly_alias("WWID2", "x", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHc"); @@ -1459,13 +1744,9 @@ static void gufa_old_match_other_wwidmatch_used(void **state) { static const char bindings[] = ("MPATHz WWID9\n" "MPATHc WWID2"); - will_return(__wrap_open_file, true); - - mock_bindings_file(bindings, 0); + mock_bindings_file(bindings); expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID9")); expect_condlog(0, REUSE_STR("MPATHz", "WWID9")); - - mock_bindings_file(bindings, 1); expect_condlog(3, FOUND_STR("MPATHc", "WWID2")); mock_used_alias("MPATHc", "WWID2"); @@ -1477,12 +1758,8 @@ static void gufa_old_nomatch_wwidmatch(void **state) { char *alias; static const char bindings[] = "MPATHa WWID0"; - will_return(__wrap_open_file, true); - - mock_bindings_file(bindings, -1); + mock_bindings_file(bindings); expect_condlog(3, NOMATCH_STR("MPATHz")); - - mock_bindings_file(bindings, 0); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_unused_alias("MPATHa"); expect_condlog(3, EXISTING_STR("MPATHa", "WWID0")); @@ -1495,12 +1772,9 @@ static void gufa_old_nomatch_wwidmatch(void **state) { static void gufa_old_nomatch_wwidmatch_used(void **state) { char *alias; static const char bindings[] = "MPATHa WWID0"; - will_return(__wrap_open_file, true); - mock_bindings_file(bindings, -1); + mock_bindings_file(bindings); expect_condlog(3, NOMATCH_STR("MPATHz")); - - mock_bindings_file(bindings, 0); expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_used_alias("MPATHa", "WWID0"); @@ -1510,17 +1784,13 @@ static void gufa_old_nomatch_wwidmatch_used(void **state) { static void gufa_old_nomatch_nowwidmatch(void **state) { char *alias; - static const char bindings[] = "MPATHb WWID1"; - - will_return(__wrap_open_file, true); + static const char bindings[] = "MPATHb WWID1\n"; - mock_bindings_file(bindings, -1); + mock_bindings_file(bindings); expect_condlog(3, NOMATCH_STR("MPATHz")); - - mock_bindings_file(bindings, -1); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); - mock_allocate_binding("MPATHz", "WWID0"); + mock_allocate_binding_len("MPATHz", "WWID0", sizeof(bindings) - 1); expect_condlog(2, ALLOC_STR("MPATHz", "WWID0")); alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); @@ -1531,26 +1801,28 @@ static void gufa_old_nomatch_nowwidmatch(void **state) { static int test_get_user_friendly_alias() { const struct CMUnitTest tests[] = { - cmocka_unit_test(gufa_empty_new_rw), - cmocka_unit_test(gufa_empty_new_ro_1), - cmocka_unit_test(gufa_empty_new_ro_2), - cmocka_unit_test(gufa_match_a_unused), - cmocka_unit_test(gufa_match_a_self), - cmocka_unit_test(gufa_match_a_used), - cmocka_unit_test(gufa_nomatch_a_c), - cmocka_unit_test(gufa_nomatch_c_a), - cmocka_unit_test(gufa_nomatch_c_b), - cmocka_unit_test(gufa_nomatch_c_b_used), - cmocka_unit_test(gufa_nomatch_b_f_a), - cmocka_unit_test(gufa_old_empty), - cmocka_unit_test(gufa_old_match), - cmocka_unit_test(gufa_old_match_other), - cmocka_unit_test(gufa_old_match_other_used), - cmocka_unit_test(gufa_old_match_other_wwidmatch), - cmocka_unit_test(gufa_old_match_other_wwidmatch_used), - cmocka_unit_test(gufa_old_nomatch_wwidmatch), - cmocka_unit_test(gufa_old_nomatch_wwidmatch_used), - cmocka_unit_test(gufa_old_nomatch_nowwidmatch), + cmocka_unit_test_teardown(gufa_empty_new_rw, teardown_bindings), + cmocka_unit_test_teardown(gufa_empty_new_ro_1, teardown_bindings), + cmocka_unit_test_teardown(gufa_empty_new_ro_2, teardown_bindings), + cmocka_unit_test_teardown(gufa_match_a_unused, teardown_bindings), + cmocka_unit_test_teardown(gufa_match_a_self, teardown_bindings), + cmocka_unit_test_teardown(gufa_match_a_used, teardown_bindings), + cmocka_unit_test_teardown(gufa_nomatch_a_c, teardown_bindings), + cmocka_unit_test_teardown(gufa_nomatch_c_a, teardown_bindings), + cmocka_unit_test_teardown(gufa_nomatch_c_b, teardown_bindings), + cmocka_unit_test_teardown(gufa_nomatch_c_b_used, teardown_bindings), + cmocka_unit_test_teardown(gufa_nomatch_b_f_a, teardown_bindings), + cmocka_unit_test_teardown(gufa_nomatch_b_aa_a, teardown_bindings), + cmocka_unit_test_teardown(gufa_nomatch_b_f_a_sorted, teardown_bindings), + cmocka_unit_test_teardown(gufa_old_empty, teardown_bindings), + cmocka_unit_test_teardown(gufa_old_match, teardown_bindings), + cmocka_unit_test_teardown(gufa_old_match_other, teardown_bindings), + cmocka_unit_test_teardown(gufa_old_match_other_used, teardown_bindings), + cmocka_unit_test_teardown(gufa_old_match_other_wwidmatch, teardown_bindings), + cmocka_unit_test_teardown(gufa_old_match_other_wwidmatch_used, teardown_bindings), + cmocka_unit_test_teardown(gufa_old_nomatch_wwidmatch, teardown_bindings), + cmocka_unit_test_teardown(gufa_old_nomatch_wwidmatch_used, teardown_bindings), + cmocka_unit_test_teardown(gufa_old_nomatch_nowwidmatch, teardown_bindings), }; return cmocka_run_group_tests(tests, NULL, NULL); @@ -1566,7 +1838,6 @@ int main(void) ret += test_lookup_binding(); ret += test_rlookup_binding(); ret += test_allocate_binding(); - ret += test_allocate_binding(); ret += test_get_user_friendly_alias(); return ret; From c02fdef345361c3b29ea8fedd4248d26b7bfb597 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 25 Aug 2023 20:35:19 +0200 Subject: [PATCH 20/64] libmultipath: dm_get_uuid(): return emtpy UUID for non-existing maps libdevmapper will most probably not return a UUID for non-existing maps anyway. But it's cheap to double-check here. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/devmapper.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libmultipath/devmapper.c b/libmultipath/devmapper.c index 248c37344..9be82f4ee 100644 --- a/libmultipath/devmapper.c +++ b/libmultipath/devmapper.c @@ -706,12 +706,16 @@ dm_get_prefixed_uuid(const char *name, char *uuid, int uuid_len) { struct dm_task *dmt; const char *uuidtmp; + struct dm_info info; int r = 1; dmt = libmp_dm_task_create(DM_DEVICE_INFO); if (!dmt) return 1; + if (uuid_len > 0) + uuid[0] = '\0'; + if (!dm_task_set_name (dmt, name)) goto uuidout; @@ -720,11 +724,13 @@ dm_get_prefixed_uuid(const char *name, char *uuid, int uuid_len) goto uuidout; } + if (!dm_task_get_info(dmt, &info) || + !info.exists) + goto uuidout; + uuidtmp = dm_task_get_uuid(dmt); if (uuidtmp) strlcpy(uuid, uuidtmp, uuid_len); - else - uuid[0] = '\0'; r = 0; uuidout: From 1de72bdd2baf37c079756a1a79f04320103869e8 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 28 Aug 2023 12:26:37 +0200 Subject: [PATCH 21/64] libmultipath: adapt to new semantics of dm_get_uuid() dm_get_uuid() will return 1 for non-existing maps. Thus we don't need to call dm_map_present() any more in alias_already_taken(). This changes our semantics: previously we'd avoid using an alias for which dm_get_uuid() had failed. Now we treat failure in dm_get_uuid() as indication that the map doesn't exist. This is not dangerous because dm_task_get_uuid() cannot fail, and thus the modified dm_get_uuid() will fail if and only if dm_map_present() would return false. This makes the "failed alias" test mostly obsolete, as "failed" is now treated as "unused". Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 25 +++++++++++++------------ tests/alias.c | 32 +++++++------------------------- 2 files changed, 20 insertions(+), 37 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index d65637494..58436ec07 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -295,18 +295,19 @@ scan_devname(const char *alias, const char *prefix) static bool alias_already_taken(const char *alias, const char *map_wwid) { - if (dm_map_present(alias)) { - char wwid[WWID_SIZE]; - - /* If both the name and the wwid match, then it's fine.*/ - if (dm_get_uuid(alias, wwid, sizeof(wwid)) == 0 && - strncmp(map_wwid, wwid, sizeof(wwid)) == 0) - return false; - condlog(3, "%s: alias '%s' already taken, reselecting alias", - map_wwid, alias); - return true; - } - return false; + char wwid[WWID_SIZE]; + + /* If the map doesn't exist, it's fine */ + if (dm_get_uuid(alias, wwid, sizeof(wwid)) != 0) + return false; + + /* If both the name and the wwid match, it's fine.*/ + if (strncmp(map_wwid, wwid, sizeof(wwid)) == 0) + return false; + + condlog(3, "%s: alias '%s' already taken, reselecting alias", + map_wwid, alias); + return true; } static bool id_already_taken(int id, const char *prefix, const char *map_wwid) diff --git a/tests/alias.c b/tests/alias.c index 50a21ecf3..d1cc487b5 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -73,12 +73,6 @@ int __wrap_mkstemp(char *template) return 10; } -int __wrap_dm_map_present(const char * str) -{ - check_expected(str); - return mock_type(int); -} - int __wrap_dm_get_uuid(const char *name, char *uuid, int uuid_len) { int ret; @@ -398,14 +392,13 @@ static int test_scan_devname(void) static void mock_unused_alias(const char *alias) { - expect_string(__wrap_dm_map_present, str, alias); - will_return(__wrap_dm_map_present, 0); + expect_string(__wrap_dm_get_uuid, name, alias); + expect_value(__wrap_dm_get_uuid, uuid_len, WWID_SIZE); + will_return(__wrap_dm_get_uuid, 1); } static void mock_self_alias(const char *alias, const char *wwid) { - expect_string(__wrap_dm_map_present, str, alias); - will_return(__wrap_dm_map_present, 1); expect_string(__wrap_dm_get_uuid, name, alias); expect_value(__wrap_dm_get_uuid, uuid_len, WWID_SIZE); will_return(__wrap_dm_get_uuid, 0); @@ -432,18 +425,13 @@ static void mock_self_alias(const char *alias, const char *wwid) #define mock_failed_alias(alias, wwid) \ do { \ - expect_string(__wrap_dm_map_present, str, alias); \ - will_return(__wrap_dm_map_present, 1); \ expect_string(__wrap_dm_get_uuid, name, alias); \ expect_value(__wrap_dm_get_uuid, uuid_len, WWID_SIZE); \ will_return(__wrap_dm_get_uuid, 1); \ - expect_condlog(3, USED_STR(alias, wwid)); \ } while (0) #define mock_used_alias(alias, wwid) \ do { \ - expect_string(__wrap_dm_map_present, str, alias); \ - will_return(__wrap_dm_map_present, 1); \ expect_string(__wrap_dm_get_uuid, name, alias); \ expect_value(__wrap_dm_get_uuid, uuid_len, WWID_SIZE); \ will_return(__wrap_dm_get_uuid, 0); \ @@ -566,9 +554,8 @@ static void lb_empty_failed(void **state) mock_bindings_file(""); expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_failed_alias("MPATHa", "WWID0"); - mock_unused_alias("MPATHb"); rc = lookup_binding(NULL, "WWID0", &alias, "MPATH", 1); - assert_int_equal(rc, 2); + assert_int_equal(rc, 1); assert_ptr_equal(alias, NULL); free(alias); } @@ -666,9 +653,8 @@ static void lb_nomatch_a_3_used_failed_self(void **state) mock_used_alias("MPATHc", "WWID1"); mock_used_alias("MPATHd", "WWID1"); mock_failed_alias("MPATHe", "WWID1"); - mock_self_alias("MPATHf", "WWID1"); rc = lookup_binding(NULL, "WWID1", &alias, "MPATH", 1); - assert_int_equal(rc, 6); + assert_int_equal(rc, 5); assert_ptr_equal(alias, NULL); } @@ -940,7 +926,7 @@ static void lb_nomatch_b_a_aa(void **state) static void lb_nomatch_b_a_aa_zz(void **state) { - int rc, i; + int rc; char *alias; STRBUF_ON_STACK(buf); @@ -949,11 +935,7 @@ static void lb_nomatch_b_a_aa_zz(void **state) * lookup_binding finds MPATHaaa as next free entry, because MPATHaa is * found before MPATHb, and MPATHzz was in the bindings, too. */ - for (i = 0; i <= 26; i++) { - print_strbuf(&buf, "MPATH"); - format_devname(&buf, i + 1); - print_strbuf(&buf, " WWID%d\n", i); - } + fill_bindings(&buf, 0, 26); print_strbuf(&buf, "MPATHzz WWID676\n"); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWID703")); From 093b6bc902fc2698c08c96ac72ae9e5baec29bc8 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 8 Sep 2023 15:19:54 +0200 Subject: [PATCH 22/64] libmultipath: sort aliases by length and strcmp The current sort order of aliases is alphabetical, which is does not match the actual order of aliases, where "mpathaa" > "mpathz". Change the ordering as follows: first sort by string length, then alphabetically. This will make sure that for aliases with the same prefix, alias order is correct ("mpathaaa" will be sorted after "mpathzz", etc). Even for mixed prefixes, the alias order will be correct for every individual prefix, even though aliases with different prefixes may alternate in the file. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 45 +++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index 58436ec07..af6565b1d 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -117,6 +117,35 @@ static const struct binding *get_binding_for_wwid(const Bindings *bindings, return NULL; } +/* + * Sort order for aliases. + * + * The "numeric" ordering of aliases for a given prefix P is + * Pa, ..., Pz, Paa, ..., Paz, Pba, ... , Pzz, Paaa, ..., Pzzz, Paaaa, ... + * We use the fact that for equal prefix, longer strings are always + * higher than shorter ones. Strings of equal length are sorted alphabetically. + * This is achieved by sorting be length first, then using strcmp(). + * If multiple prefixes are in use, the aliases with a given prefix will + * not necessarily be in a contiguous range of the vector, but they will + * be ordered such that for a given prefix, numercally higher aliases will + * always be sorted after lower ones. + */ +static int alias_compar(const void *p1, const void *p2) +{ + const char *alias1 = *((char * const *)p1); + const char *alias2 = *((char * const *)p2); + + if (alias1 && alias2) { + ssize_t ldif = strlen(alias1) - strlen(alias2); + + if (ldif) + return ldif; + return strcmp(alias1, alias2); + } else + /* Move NULL alias to the end */ + return alias1 ? -1 : alias2 ? 1 : 0; +} + static int add_binding(Bindings *bindings, const char *alias, const char *wwid) { struct binding *bdg; @@ -128,7 +157,7 @@ static int add_binding(Bindings *bindings, const char *alias, const char *wwid) * sorted already. */ vector_foreach_slot_backwards(bindings, bdg, i) { - if ((cmp = strcmp(bdg->alias, alias)) <= 0) + if ((cmp = alias_compar(&bdg->alias, &alias)) <= 0) break; } @@ -657,16 +686,10 @@ static int _check_bindings_file(const struct config *conf, FILE *file, return rc; } -static int alias_compar(const void *p1, const void *p2) +static int mp_alias_compar(const void *p1, const void *p2) { - const char *alias1 = (*(struct mpentry * const *)p1)->alias; - const char *alias2 = (*(struct mpentry * const *)p2)->alias; - - if (alias1 && alias2) - return strcmp(alias1, alias2); - else - /* Move NULL alias to the end */ - return alias1 ? -1 : alias2 ? 1 : 0; + return alias_compar(&((*(struct mpentry * const *)p1)->alias), + &((*(struct mpentry * const *)p2)->alias)); } /* @@ -700,7 +723,7 @@ int check_alias_settings(const struct config *conf) pthread_cleanup_push_cast(free_bindings, &bindings); pthread_cleanup_push(cleanup_vector_free, mptable); - vector_sort(mptable, alias_compar); + vector_sort(mptable, mp_alias_compar); vector_foreach_slot(mptable, mpe, i) { if (!mpe->alias) /* From c7bd10c6b6bfb86946976a71bb536090a265c8c7 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 8 Sep 2023 15:46:02 +0200 Subject: [PATCH 23/64] multipath-tools tests: fix alias test after sort order change Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- tests/alias.c | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/tests/alias.c b/tests/alias.c index d1cc487b5..8ed95d7a3 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -932,16 +932,15 @@ static void lb_nomatch_b_a_aa_zz(void **state) /* * add_bindings() sorts alphabetically. ("a", "aa", "ab", "b", "c", ...) - * lookup_binding finds MPATHaaa as next free entry, because MPATHaa is - * found before MPATHb, and MPATHzz was in the bindings, too. + * lookup_binding finds MPATHab as next free entry. */ fill_bindings(&buf, 0, 26); print_strbuf(&buf, "MPATHzz WWID676\n"); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWID703")); - mock_unused_alias("MPATHaaa"); + mock_unused_alias("MPATHab"); rc = lookup_binding(NULL, "WWID703", &alias, "MPATH", 1); - assert_int_equal(rc, 703); + assert_int_equal(rc, 28); assert_ptr_equal(alias, NULL); } @@ -998,13 +997,8 @@ static void lb_nomatch_b_a_3_used(void **state) #ifdef MPATH_ID_INT_MAX /* - * The bindings will be sorted by alias, alphabetically, which is not - * the same as the "numeric" sort order for user-friendly aliases. - * get_free_id() selects the highest used ID + 1 if an unsorted entry - * is encountered in the bindings table and it's id is equal to the - * next "expected" id. This happens if all IDs from "a" to "aa" are - * in the table. If the INT_MAX entry is in the table, too, it will - * overflow. + * The bindings will be sorted by alias. Therefore we have no chance to + * simulate a "full" table. */ static void lb_nomatch_int_max(void **state) { @@ -1016,9 +1010,9 @@ static void lb_nomatch_int_max(void **state) print_strbuf(&buf, "MPATH%s WWIDMAX\n", MPATH_ID_INT_MAX); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWIDNOMORE")); - expect_condlog(0, NOMORE_STR); + mock_unused_alias("MPATHab"); rc = lookup_binding(NULL, "WWIDNOMORE", &alias, "MPATH", 1); - assert_int_equal(rc, -1); + assert_int_equal(rc, 28); assert_ptr_equal(alias, NULL); } @@ -1049,9 +1043,9 @@ static void lb_nomatch_int_max_m1(void **state) print_strbuf(&buf, "MPATH%s WWIDMAXM1\n", MPATH_ID_INT_MAX_m1); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWIDMAX")); - mock_unused_alias("MPATH" MPATH_ID_INT_MAX); + mock_unused_alias("MPATHab"); rc = lookup_binding(NULL, "WWIDMAX", &alias, "MPATH", 1); - assert_int_equal(rc, INT_MAX); + assert_int_equal(rc, 28); assert_ptr_equal(alias, NULL); } @@ -1065,10 +1059,10 @@ static void lb_nomatch_int_max_m1_used(void **state) print_strbuf(&buf, "MPATH%s WWIDMAXM1\n", MPATH_ID_INT_MAX_m1); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWIDMAX")); - mock_used_alias("MPATH" MPATH_ID_INT_MAX, "WWIDMAX"); - expect_condlog(0, NOMORE_STR); + mock_used_alias("MPATHab", "WWIDMAX"); + mock_unused_alias("MPATHac"); rc = lookup_binding(NULL, "WWIDMAX", &alias, "MPATH", 1); - assert_int_equal(rc, -1); + assert_int_equal(rc, 29); assert_ptr_equal(alias, NULL); } From 17ff9afd24c22745612184e1a7d9032aa3f8b544 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 8 Sep 2023 19:50:51 +0200 Subject: [PATCH 24/64] libmultipath: simplify get_free_id() assuming total ordering If we can assume that the bindings array is totally ordered for every prefix, which the previous patch guarantees, the search for a free ID can be simplified. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 85 ++++++++++---------------------------------- 1 file changed, 18 insertions(+), 67 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index af6565b1d..66e34e31f 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -356,83 +356,34 @@ int get_free_id(const Bindings *bindings, const char *prefix, const char *map_ww { const struct binding *bdg; int i, id = 1; - int biggest_id = 1; - int smallest_bigger_id = INT_MAX; vector_foreach_slot(bindings, bdg, i) { int curr_id = scan_devname(bdg->alias, prefix); - /* - * Find an unused index - explanation of the algorithm - * - * ID: 1 = mpatha, 2 = mpathb, ... - * - * We assume the bindings are unsorted. The only constraint - * is that no ID occurs more than once. IDs that occur in the - * bindings are called "used". - * - * We call the list 1,2,3,..., exactly in this order, the list - * of "expected" IDs. The variable "id" always holds the next - * "expected" ID, IOW the last "expected" ID encountered plus 1. - * Thus all IDs below "id" are known to be used. However, at the - * end of the loop, the value of "id" isn't necessarily unused. - * - * "smallest_bigger_id" is the smallest used ID that was - * encountered while it was larger than the next "expected" ID - * at that iteration. Let X be some used ID. If all IDs below X - * are used and encountered in the right sequence before X, "id" - * will be > X when the loop ends. Otherwise, X was encountered - * "out of order", the condition (X > id) holds when X is - * encountered, and "smallest_bigger_id" will be set to X; i.e. - * it will be less or equal than X when the loop ends. - * - * At the end of the loop, (id < smallest_bigger_id) means that - * the value of "id" had been encountered neither in order nor - * out of order, and is thus unused. (id >= smallest_bigger_id) - * means that "id"'s value is in use. In this case, we play safe - * and use "biggest_id + 1" as the next value to try. - * - * biggest_id is always > smallest_bigger_id, except in the - * "perfectly ordered" case. - */ - if (curr_id == id) { - if (id < INT_MAX) - id++; - else { - id = -1; - break; - } + if (curr_id == -1) + continue; + if (id > curr_id) { + condlog(0, "%s: ERROR: bindings are not sorted", __func__); + return -1; } - if (curr_id > biggest_id) - biggest_id = curr_id; - - if (curr_id > id && curr_id < smallest_bigger_id) - smallest_bigger_id = curr_id; + while (id < curr_id && id_already_taken(id, prefix, map_wwid)) + id++; + if (id < curr_id) + return id; + id++; + if (id <= 0) + break; } - if (id >= smallest_bigger_id) - id = biggest_id < INT_MAX ? biggest_id + 1 : -1; - - if (id > 0) { - while(id_already_taken(id, prefix, map_wwid)) { - if (id == INT_MAX) { - id = -1; - break; - } - id++; - if (id == smallest_bigger_id) { - if (biggest_id == INT_MAX) { - id = -1; - break; - } - if (biggest_id >= smallest_bigger_id) - id = biggest_id + 1; - } - } + for (; id > 0; id++) { + if (!id_already_taken(id, prefix, map_wwid)) + break; } - if (id < 0) + if (id <= 0) { + id = -1; condlog(0, "no more available user_friendly_names"); + } return id; } From 33e045a7a94a9422f98dec25e25a740086f2886d Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 8 Sep 2023 19:58:03 +0200 Subject: [PATCH 25/64] multipath-tools tests: adapt alias tests for total ordering The "unsorted" test fail now, and are removed. The algorithm is now better at finding "gaps". Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- tests/alias.c | 88 ++++++++------------------------------------------- 1 file changed, 14 insertions(+), 74 deletions(-) diff --git a/tests/alias.c b/tests/alias.c index 8ed95d7a3..dff5f93b4 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -439,27 +439,7 @@ static void mock_self_alias(const char *alias, const char *wwid) expect_condlog(3, USED_STR(alias, wwid)); \ } while(0) -static int add_binding_unsorted(Bindings *bindings, - const char *alias, const char *wwid) -{ - struct binding *bdg = calloc(1, sizeof(*bdg)); - - if (!bdg) - return -1; - bdg->wwid = strdup(wwid); - bdg->alias = strdup(alias); - if (!bdg->wwid || !bdg->alias || !vector_alloc_slot(bindings)) { - free(bdg->alias); - free(bdg->wwid); - free(bdg); - return BINDING_ERROR; - } - vector_set_slot(bindings, bdg); - return BINDING_ADDED; -} - -static void __mock_bindings_file(const char *content, - int (*add)(Bindings *, const char *, const char *)) +static void __mock_bindings_file(const char *content) { char *cnt __attribute__((cleanup(cleanup_charp))) = NULL; char *token, *savep = NULL; @@ -478,17 +458,13 @@ static void __mock_bindings_file(const char *content, == READ_BINDING_SKIP) continue; - rc = add(&global_bindings, alias, wwid); + rc = add_binding(&global_bindings, alias, wwid); assert_int_equal(rc, BINDING_ADDED); } } static void mock_bindings_file(const char *content) { - return __mock_bindings_file(content, add_binding); -} - -static void mock_bindings_file_unsorted(const char *content) { - return __mock_bindings_file(content, add_binding_unsorted); + return __mock_bindings_file(content); } static int teardown_bindings(void **state) @@ -861,10 +837,6 @@ static void lb_nomatch_b_z_a(void **state) int rc; char *alias; - /* - * add_bindings() sorts alphabetically. Therefore get_free_id() - * finds MPATHc as a free entry. - */ mock_bindings_file("MPATHb WWID1\n" "MPATHz WWID26\n" "MPATHa WWID0\n"); @@ -880,10 +852,6 @@ static void lb_nomatch_b_aa_a(void **state) int rc; char *alias; - /* - * add_bindings() sorts alphabetically. ("a", "aa", b"). - * The get_free_id() algorithm finds the "hole" after "b". - */ mock_bindings_file("MPATHb WWID1\n" "MPATHz WWID26\n" "MPATHa WWID0\n"); @@ -911,10 +879,6 @@ static void lb_nomatch_b_a_aa(void **state) char *alias; STRBUF_ON_STACK(buf); - /* - * add_bindings() sorts alphabetically. ("a", "aa", "ab", "b", "c", ...) - * lookup_binding finds MPATHac as next free entry. - */ fill_bindings(&buf, 0, 26); mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWID28")); @@ -930,10 +894,6 @@ static void lb_nomatch_b_a_aa_zz(void **state) char *alias; STRBUF_ON_STACK(buf); - /* - * add_bindings() sorts alphabetically. ("a", "aa", "ab", "b", "c", ...) - * lookup_binding finds MPATHab as next free entry. - */ fill_bindings(&buf, 0, 26); print_strbuf(&buf, "MPATHzz WWID676\n"); mock_bindings_file(get_strbuf_str(&buf)); @@ -944,25 +904,6 @@ static void lb_nomatch_b_a_aa_zz(void **state) assert_ptr_equal(alias, NULL); } -static void lb_nomatch_b_z_a_unsorted(void **state) -{ - int rc; - char *alias; - - /* - * With unsorted bindings (shouldn't happen normally), get_free_id() - * plays safe and returns MPATHaa as first free entry. - */ - mock_bindings_file_unsorted("MPATHb WWID1\n" - "MPATHz WWID26\n" - "MPATHa WWID0\n"); - expect_condlog(3, NOMATCH_WWID_STR("WWID2")); - mock_unused_alias("MPATHaa"); - rc = lookup_binding(NULL, "WWID2", &alias, "MPATH", 1); - assert_int_equal(rc, 27); - assert_ptr_equal(alias, NULL); -} - static void lb_nomatch_b_a(void **state) { int rc; @@ -1027,9 +968,9 @@ static void lb_nomatch_int_max_used(void **state) mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWIDNOMORE")); mock_used_alias("MPATHa", "WWIDNOMORE"); - expect_condlog(0, NOMORE_STR); + mock_unused_alias("MPATHab"); rc = lookup_binding(NULL, "WWIDNOMORE", &alias, "MPATH", 1); - assert_int_equal(rc, -1); + assert_int_equal(rc, 28); assert_ptr_equal(alias, NULL); } @@ -1077,9 +1018,9 @@ static void lb_nomatch_int_max_m1_1_used(void **state) mock_bindings_file(get_strbuf_str(&buf)); expect_condlog(3, NOMATCH_WWID_STR("WWIDMAX")); mock_used_alias("MPATHa", "WWIDMAX"); - mock_unused_alias("MPATH" MPATH_ID_INT_MAX); + mock_unused_alias("MPATHab"); rc = lookup_binding(NULL, "WWIDMAX", &alias, "MPATH", 1); - assert_int_equal(rc, INT_MAX); + assert_int_equal(rc, 28); assert_ptr_equal(alias, NULL); } @@ -1095,10 +1036,10 @@ static void lb_nomatch_int_max_m1_2_used(void **state) expect_condlog(3, NOMATCH_WWID_STR("WWIDMAX")); mock_used_alias("MPATHa", "WWIDMAX"); - mock_used_alias("MPATH" MPATH_ID_INT_MAX, "WWIDMAX"); - expect_condlog(0, NOMORE_STR); + mock_used_alias("MPATHab", "WWIDMAX"); + mock_unused_alias("MPATHac"); rc = lookup_binding(NULL, "WWIDMAX", &alias, "MPATH", 1); - assert_int_equal(rc, -1); + assert_int_equal(rc, 29); assert_ptr_equal(alias, NULL); } #endif @@ -1133,7 +1074,6 @@ static int test_lookup_binding(void) cmocka_unit_test_teardown(lb_nomatch_b_aa_a, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_b_a_aa, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_b_a_aa_zz, teardown_bindings), - cmocka_unit_test_teardown(lb_nomatch_b_z_a_unsorted, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_b_a, teardown_bindings), cmocka_unit_test_teardown(lb_nomatch_b_a_3_used, teardown_bindings), #ifdef MPATH_ID_INT_MAX @@ -1593,14 +1533,14 @@ static void gufa_nomatch_b_f_a(void **state) { "MPATHf WWID6\n" "MPATHa WWID0\n"); - mock_bindings_file_unsorted(bindings); + mock_bindings_file(bindings); expect_condlog(3, NOMATCH_WWID_STR("WWID7")); - mock_unused_alias("MPATHg"); + mock_unused_alias("MPATHc"); - mock_allocate_binding_len("MPATHg", "WWID7", sizeof(bindings) - 1); + mock_allocate_binding_len("MPATHc", "WWID7", sizeof(bindings) - 1); alias = get_user_friendly_alias("WWID7", "x", "", "MPATH", false); - assert_string_equal(alias, "MPATHg"); + assert_string_equal(alias, "MPATHc"); free(alias); } From bb06879cb1f3f634e41c88ed5b2a396a85c10f83 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 8 Sep 2023 21:39:44 +0200 Subject: [PATCH 26/64] multipath-tools tests: add test for ordering of bindings As the assignment of free aliases now relies on the bindings being properly sorted, add some unit tests to make sure the sorting algorithm works. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- tests/alias.c | 212 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 209 insertions(+), 3 deletions(-) diff --git a/tests/alias.c b/tests/alias.c index dff5f93b4..7f3ff38a1 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -13,6 +13,9 @@ #include "globals.c" #include "../libmultipath/alias.c" +/* For verbose printing of all aliases in the ordering tests */ +#define ALIAS_DEBUG 0 + #if INT_MAX == 0x7fffffff /* user_friendly_name for map #INT_MAX */ #define MPATH_ID_INT_MAX "fxshrxw" @@ -439,11 +442,12 @@ static void mock_self_alias(const char *alias, const char *wwid) expect_condlog(3, USED_STR(alias, wwid)); \ } while(0) -static void __mock_bindings_file(const char *content) +static void __mock_bindings_file(const char *content, bool conflict_ok) { char *cnt __attribute__((cleanup(cleanup_charp))) = NULL; char *token, *savep = NULL; int i; + uintmax_t values[] = { BINDING_ADDED, BINDING_CONFLICT }; cnt = strdup(content); assert_ptr_not_equal(cnt, NULL); @@ -459,12 +463,12 @@ static void __mock_bindings_file(const char *content) continue; rc = add_binding(&global_bindings, alias, wwid); - assert_int_equal(rc, BINDING_ADDED); + assert_in_set(rc, values, conflict_ok ? 2 : 1); } } static void mock_bindings_file(const char *content) { - return __mock_bindings_file(content); + return __mock_bindings_file(content, false); } static int teardown_bindings(void **state) @@ -1744,6 +1748,207 @@ static int test_get_user_friendly_alias() return cmocka_run_group_tests(tests, NULL, NULL); } +/* Numbers 1-1000, randomly shuffled */ +static const int random_numbers[1000] = { + 694, 977, 224, 178, 841, 818, 914, 549, 831, 942, 263, 834, 919, 800, + 111, 517, 719, 297, 988, 98, 332, 516, 754, 772, 495, 488, 331, 529, + 142, 747, 848, 618, 375, 624, 74, 753, 782, 944, 623, 468, 862, 997, + 417, 258, 298, 774, 673, 904, 883, 766, 867, 400, 11, 950, 14, 784, + 655, 155, 396, 9, 743, 93, 651, 245, 968, 306, 785, 581, 880, 486, + 168, 631, 203, 4, 663, 294, 702, 762, 619, 684, 48, 181, 21, 443, 643, + 863, 1000, 327, 26, 126, 382, 765, 586, 76, 49, 925, 319, 865, 797, + 876, 693, 334, 433, 243, 419, 901, 854, 326, 985, 347, 874, 527, 282, + 290, 380, 167, 95, 3, 257, 936, 60, 426, 227, 345, 577, 492, 467, 580, + 967, 422, 823, 718, 610, 64, 700, 412, 163, 288, 506, 828, 432, 51, + 356, 348, 539, 478, 17, 945, 602, 123, 450, 660, 429, 113, 310, 358, + 512, 758, 508, 19, 542, 304, 286, 446, 918, 723, 333, 603, 731, 978, + 230, 697, 109, 872, 175, 853, 947, 965, 121, 222, 101, 811, 117, 601, + 191, 752, 384, 415, 938, 278, 915, 715, 240, 552, 912, 838, 150, 840, + 627, 29, 636, 464, 861, 481, 992, 249, 934, 82, 368, 724, 807, 593, + 157, 147, 199, 637, 41, 62, 902, 505, 621, 342, 174, 260, 729, 961, + 219, 311, 629, 789, 81, 739, 860, 712, 223, 165, 741, 981, 485, 363, + 346, 709, 125, 369, 279, 634, 399, 162, 193, 769, 149, 314, 868, 612, + 524, 675, 341, 343, 476, 606, 388, 613, 850, 264, 903, 451, 908, 779, + 453, 148, 497, 46, 132, 43, 885, 955, 269, 395, 72, 128, 767, 989, + 929, 423, 742, 55, 13, 79, 924, 182, 295, 563, 668, 169, 974, 154, + 970, 54, 674, 52, 437, 570, 550, 531, 554, 793, 678, 218, 367, 105, + 197, 315, 958, 892, 86, 47, 284, 37, 561, 522, 198, 689, 817, 573, + 877, 201, 803, 501, 881, 546, 530, 523, 780, 579, 953, 135, 23, 620, + 84, 698, 303, 656, 357, 323, 494, 58, 131, 913, 995, 120, 70, 1, 195, + 365, 210, 25, 898, 173, 307, 239, 77, 418, 952, 963, 92, 455, 425, 12, + 536, 161, 328, 933, 401, 251, 735, 725, 362, 322, 557, 681, 302, 53, + 786, 801, 391, 946, 748, 133, 717, 851, 7, 372, 993, 387, 906, 373, + 667, 33, 670, 389, 209, 611, 896, 652, 69, 999, 344, 845, 633, 36, + 487, 192, 180, 45, 640, 427, 707, 805, 188, 152, 905, 217, 30, 252, + 386, 665, 299, 541, 410, 787, 5, 857, 751, 392, 44, 595, 146, 745, + 641, 957, 866, 773, 806, 815, 659, 102, 704, 430, 106, 296, 129, 847, + 130, 990, 669, 236, 225, 680, 159, 213, 438, 189, 447, 600, 232, 594, + 32, 56, 390, 647, 855, 428, 330, 714, 738, 706, 666, 461, 469, 482, + 558, 814, 559, 177, 575, 538, 309, 383, 261, 156, 420, 761, 630, 893, + 10, 116, 940, 844, 71, 377, 662, 312, 520, 244, 143, 759, 119, 186, + 592, 909, 864, 376, 768, 254, 265, 394, 511, 760, 574, 6, 436, 514, + 59, 226, 644, 956, 578, 825, 548, 145, 736, 597, 378, 821, 987, 897, + 354, 144, 722, 895, 589, 503, 826, 498, 543, 617, 763, 231, 808, 528, + 89, 479, 607, 737, 170, 404, 371, 65, 103, 340, 283, 141, 313, 858, + 289, 124, 971, 687, 954, 732, 39, 926, 176, 100, 267, 519, 890, 535, + 276, 448, 27, 457, 899, 385, 184, 275, 770, 544, 614, 449, 160, 658, + 259, 973, 108, 604, 24, 207, 562, 757, 744, 324, 444, 962, 591, 480, + 398, 409, 998, 253, 325, 445, 979, 8, 35, 118, 73, 683, 208, 85, 190, + 791, 408, 871, 657, 179, 18, 556, 496, 475, 20, 894, 484, 775, 889, + 463, 241, 730, 57, 907, 551, 859, 943, 185, 416, 870, 590, 435, 471, + 932, 268, 381, 626, 502, 565, 273, 534, 672, 778, 292, 473, 566, 104, + 172, 285, 832, 411, 329, 628, 397, 472, 271, 910, 711, 690, 969, 585, + 809, 941, 923, 555, 228, 685, 242, 94, 96, 211, 140, 61, 922, 795, + 869, 34, 255, 38, 984, 676, 15, 560, 632, 434, 921, 355, 582, 351, + 212, 200, 819, 960, 649, 852, 75, 771, 361, 996, 238, 316, 720, 671, + 462, 112, 569, 171, 664, 625, 588, 405, 553, 270, 533, 353, 842, 114, + 972, 83, 937, 63, 194, 237, 537, 980, 802, 916, 959, 688, 839, 350, + 917, 650, 545, 615, 151, 352, 686, 726, 266, 509, 439, 491, 935, 608, + 518, 653, 339, 609, 277, 635, 836, 88, 407, 440, 642, 927, 229, 727, + 360, 477, 846, 413, 454, 616, 28, 598, 567, 540, 790, 424, 247, 317, + 746, 911, 798, 321, 547, 248, 734, 829, 220, 138, 756, 500, 691, 196, + 740, 930, 843, 733, 221, 827, 50, 813, 949, 525, 349, 474, 134, 875, + 695, 513, 414, 515, 638, 99, 366, 490, 975, 246, 465, 206, 281, 583, + 256, 587, 749, 2, 951, 679, 215, 364, 458, 402, 646, 991, 335, 982, + 835, 300, 900, 703, 994, 983, 234, 888, 532, 804, 584, 305, 792, 442, + 291, 964, 158, 370, 452, 250, 521, 166, 948, 812, 794, 272, 699, 205, + 183, 507, 301, 920, 781, 233, 824, 137, 489, 833, 887, 966, 856, 78, + 830, 153, 359, 696, 526, 216, 66, 701, 403, 891, 849, 571, 308, 483, + 164, 293, 928, 677, 320, 837, 441, 639, 564, 510, 648, 274, 336, 661, + 878, 777, 816, 976, 493, 810, 67, 87, 91, 187, 882, 986, 80, 22, 499, + 90, 705, 139, 136, 122, 708, 716, 886, 572, 127, 40, 721, 764, 16, + 379, 692, 645, 456, 710, 460, 783, 97, 776, 713, 884, 115, 466, 596, + 374, 406, 110, 568, 68, 214, 622, 470, 107, 504, 682, 31, 421, 576, + 654, 605, 788, 799, 280, 338, 931, 873, 204, 287, 459, 755, 939, 599, + 431, 796, 235, 42, 750, 262, 318, 393, 202, 822, 879, 820, 728, 337, +}; + +static void fill_bindings_random(struct strbuf *buf, int start, int end, + const char *prefix) +{ + int i; + + for (i = start; i < end; i++) { + print_strbuf(buf, "%s", prefix); + format_devname(buf, random_numbers[i]); + print_strbuf(buf, " WWID%d\n", random_numbers[i]); + } +} + +struct random_aliases { + int start; + int end; + const char *prefix; +}; + +static void order_test(int n, const struct random_aliases ra[], bool conflict_ok) +{ + STRBUF_ON_STACK(buf); + int i, j, prev, curr, tmp; + struct binding *bdg; + Bindings *bindings = &global_bindings; + + for (j = 0; j < n; j++) + fill_bindings_random(&buf, ra[j].start, ra[j].end, ra[j].prefix); + __mock_bindings_file(get_strbuf_str(&buf), conflict_ok); + + for (j = 0; j < n; j++) { + bdg = VECTOR_SLOT(bindings, 0); + if (ALIAS_DEBUG && j == 0) + printf("%d: %s\n", 0, bdg->alias); + prev = scan_devname(bdg->alias, ra[j].prefix); + i = 1; + vector_foreach_slot_after(bindings, bdg, i) { + if (ALIAS_DEBUG && j == 0) + printf("%d: %s\n", i, bdg->alias); + tmp = scan_devname(bdg->alias, ra[j].prefix); + if (tmp == -1) + continue; + curr = tmp; + if (prev > 0) { + if (curr <= prev) + printf("ERROR: %d (%s) %d >= %d\n", + i, bdg->alias, prev, curr); + assert_true(curr > prev); + } + prev = curr; + } + } +} + +static void order_01(void **state) +{ + const struct random_aliases ra[] = { + { 0, 1000, "MPATH" }, + }; + + order_test(ARRAY_SIZE(ra), ra, false); +} + +static void order_02(void **state) +{ + const struct random_aliases ra[] = { + { 0, 500, "MPATH" }, + { 200, 700, "mpath" }, + }; + order_test(ARRAY_SIZE(ra), ra, false); +} + +static void order_03(void **state) +{ + const struct random_aliases ra[] = { + { 500, 1000, "MPTH" }, + { 0, 500, "MPATH" }, + }; + order_test(ARRAY_SIZE(ra), ra, false); +} + +static void order_04(void **state) +{ + const struct random_aliases ra[] = { + { 0, 500, "mpa" }, + { 250, 750, "mp" }, + }; + order_test(ARRAY_SIZE(ra), ra, true); +} + +static void order_05(void **state) +{ + const struct random_aliases ra[] = { + { 0, 100, "A" }, + { 0, 100, "B" }, + { 0, 100, "C" }, + { 0, 100, "D" }, + }; + order_test(ARRAY_SIZE(ra), ra, false); +} + +static void order_06(void **state) +{ + const struct random_aliases ra[] = { + { 0, 100, "" }, + { 0, 100, "a" }, + { 0, 100, "aa" }, + { 0, 100, "ab" }, + { 0, 100, "aaa" }, + }; + order_test(ARRAY_SIZE(ra), ra, true); +} + +static int test_bindings_order() +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_teardown(order_01, teardown_bindings), + cmocka_unit_test_teardown(order_02, teardown_bindings), + cmocka_unit_test_teardown(order_03, teardown_bindings), + cmocka_unit_test_teardown(order_04, teardown_bindings), + cmocka_unit_test_teardown(order_05, teardown_bindings), + cmocka_unit_test_teardown(order_06, teardown_bindings), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} + int main(void) { int ret = 0; @@ -1755,6 +1960,7 @@ int main(void) ret += test_rlookup_binding(); ret += test_allocate_binding(); ret += test_get_user_friendly_alias(); + ret += test_bindings_order(); return ret; } From 7eaeb33ae9f75e8165697ef02829136f33ab426e Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 7 Sep 2023 22:22:43 +0200 Subject: [PATCH 27/64] multipathd: watch bindings file with inotify + timestamp Since "libmultipath: keep bindings in memory", we don't re-read the bindings file after every modification. Add a notification mechanism that makes multipathd aware of changes to the bindings file. Because multipathd itself will change the bindings file, it must compare timestamps in order to avoid reading the file repeatedly. Because select_alias() can be called from multiple thread contexts (uxlsnr, uevent handler), we need to add locking for the bindings file. The timestamp must also be protected by a lock, because it can't be read or written atomically. Note: The notification mechanism expects the bindings file to be atomically replaced by rename(2). Changes must be made in a temporary file and applied using rename(2), as in update_bindings_file(). The inotify mechanism deliberately does not listen to close-after-write events that would be generated by editing the bindings file directly. This Note also: new bindings will only be read from add_map_with_path(), i.e. either during reconfigure(), or when a new map is created during runtime. Existing maps will not be renamed if the binding file changes, unless the user runs "multipathd reconfigure". This is not a change wrt the previous code, but it should be mentioned anyway. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 256 +++++++++++++++++++++++++----- libmultipath/alias.h | 3 +- libmultipath/libmultipath.version | 5 + multipathd/uxlsnr.c | 36 ++++- tests/alias.c | 3 + 5 files changed, 256 insertions(+), 47 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index 66e34e31f..964b8a7b4 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "debug.h" #include "util.h" @@ -22,6 +23,7 @@ #include "config.h" #include "devmapper.h" #include "strbuf.h" +#include "time-util.h" /* * significant parts of this file were taken from iscsi-bindings.c of the @@ -50,6 +52,12 @@ "# alias wwid\n" \ "#\n" +/* uatomic access only */ +static int bindings_file_changed = 1; + +static pthread_mutex_t timestamp_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct timespec bindings_last_updated; + struct binding { char *alias; char *wwid; @@ -60,6 +68,9 @@ struct binding { * an abstract type. */ typedef struct _vector Bindings; + +/* Protect global_bindings */ +static pthread_mutex_t bindings_mutex = PTHREAD_MUTEX_INITIALIZER; static Bindings global_bindings = { .allocated = 0 }; enum { @@ -78,6 +89,27 @@ static void _free_binding(struct binding *bdg) free(bdg); } +static void free_bindings(Bindings *bindings) +{ + struct binding *bdg; + int i; + + vector_foreach_slot(bindings, bdg, i) + _free_binding(bdg); + vector_reset(bindings); +} + +static void set_global_bindings(Bindings *bindings) +{ + Bindings old_bindings; + + pthread_mutex_lock(&bindings_mutex); + old_bindings = global_bindings; + global_bindings = *bindings; + pthread_mutex_unlock(&bindings_mutex); + free_bindings(&old_bindings); +} + static const struct binding *get_binding_for_alias(const Bindings *bindings, const char *alias) { @@ -199,7 +231,8 @@ static int delete_binding(Bindings *bindings, const char *wwid) return BINDING_DELETED; } -static int write_bindings_file(const Bindings *bindings, int fd) +static int write_bindings_file(const Bindings *bindings, int fd, + struct timespec *ts) { struct binding *bnd; STRBUF_ON_STACK(content); @@ -227,9 +260,56 @@ static int write_bindings_file(const Bindings *bindings, int fd) } len -= n; } + fsync(fd); + if (ts) { + struct stat st; + + if (fstat(fd, &st) == 0) + *ts = st.st_mtim; + else + clock_gettime(CLOCK_REALTIME_COARSE, ts); + } return 0; } +void handle_bindings_file_inotify(const struct inotify_event *event) +{ + struct config *conf; + const char *base; + bool changed = false; + struct stat st; + struct timespec ts = { 0, 0 }; + int ret; + + if (!(event->mask & IN_MOVED_TO)) + return; + + conf = get_multipath_config(); + base = strrchr(conf->bindings_file, '/'); + changed = base && base > conf->bindings_file && + !strcmp(base + 1, event->name); + ret = stat(conf->bindings_file, &st); + put_multipath_config(conf); + + if (!changed) + return; + + pthread_mutex_lock(×tamp_mutex); + if (ret == 0) { + ts = st.st_mtim; + changed = timespeccmp(&ts, &bindings_last_updated) > 0; + } + pthread_mutex_unlock(×tamp_mutex); + + if (changed) { + uatomic_xchg(&bindings_file_changed, 1); + condlog(3, "%s: bindings file must be re-read, new timestamp: %ld.%06ld", + __func__, (long)ts.tv_sec, (long)ts.tv_nsec / 1000); + } else + condlog(3, "%s: bindings file is up-to-date, timestamp: %ld.%06ld", + __func__, (long)ts.tv_sec, (long)ts.tv_nsec / 1000); +} + static int update_bindings_file(const Bindings *bindings, const char *bindings_file) { @@ -237,6 +317,7 @@ static int update_bindings_file(const Bindings *bindings, int fd = -1; char tempname[PATH_MAX]; mode_t old_umask; + struct timespec ts; if (safe_sprintf(tempname, "%s.XXXXXX", bindings_file)) return -1; @@ -248,7 +329,7 @@ static int update_bindings_file(const Bindings *bindings, } umask(old_umask); pthread_cleanup_push(cleanup_fd_ptr, &fd); - rc = write_bindings_file(bindings, fd); + rc = write_bindings_file(bindings, fd, &ts); pthread_cleanup_pop(1); if (rc == -1) { condlog(1, "failed to write new bindings file"); @@ -257,8 +338,12 @@ static int update_bindings_file(const Bindings *bindings, } if ((rc = rename(tempname, bindings_file)) == -1) condlog(0, "%s: rename: %m", __func__); - else + else { + pthread_mutex_lock(×tamp_mutex); + bindings_last_updated = ts; + pthread_mutex_unlock(×tamp_mutex); condlog(1, "updated bindings file %s", bindings_file); + } return rc; } @@ -387,6 +472,7 @@ int get_free_id(const Bindings *bindings, const char *prefix, const char *map_ww return id; } +/* Called with binding_mutex held */ static char * allocate_binding(const char *filename, const char *wwid, int id, const char *prefix) { @@ -423,6 +509,30 @@ allocate_binding(const char *filename, const char *wwid, int id, const char *pre return alias; } +enum { + BINDINGS_FILE_UP2DATE, + BINDINGS_FILE_READ, + BINDINGS_FILE_ERROR, + BINDINGS_FILE_BAD, +}; + +static int _read_bindings_file(const struct config *conf, Bindings *bindings, + bool force); + +static void read_bindings_file(void) +{ + struct config *conf; + Bindings bindings = {.allocated = 0, }; + int rc; + + conf = get_multipath_config(); + pthread_cleanup_push(put_multipath_config, conf); + rc = _read_bindings_file(conf, &bindings, false); + pthread_cleanup_pop(1); + if (rc == BINDINGS_FILE_READ) + set_global_bindings(&bindings); +} + /* * get_user_friendly_alias() action table * @@ -463,6 +573,11 @@ char *get_user_friendly_alias(const char *wwid, const char *file, const char *al bool new_binding = false; const struct binding *bdg; + read_bindings_file(); + + pthread_mutex_lock(&bindings_mutex); + pthread_cleanup_push(cleanup_mutex, &bindings_mutex); + if (!*alias_old) goto new_alias; @@ -514,40 +629,40 @@ char *get_user_friendly_alias(const char *wwid, const char *file, const char *al alias, wwid); out: + /* unlock bindings_mutex */ + pthread_cleanup_pop(1); return alias; } int get_user_friendly_wwid(const char *alias, char *buff) { const struct binding *bdg; + int rc = -1; if (!alias || *alias == '\0') { condlog(3, "Cannot find binding for empty alias"); return -1; } + read_bindings_file(); + + pthread_mutex_lock(&bindings_mutex); + pthread_cleanup_push(cleanup_mutex, &bindings_mutex); bdg = get_binding_for_alias(&global_bindings, alias); - if (!bdg) { + if (bdg) { + strlcpy(buff, bdg->wwid, WWID_SIZE); + rc = 0; + } else *buff = '\0'; - return -1; - } - strlcpy(buff, bdg->wwid, WWID_SIZE); - return 0; -} - -static void free_bindings(Bindings *bindings) -{ - struct binding *bdg; - int i; - - vector_foreach_slot(bindings, bdg, i) - _free_binding(bdg); - vector_reset(bindings); + pthread_cleanup_pop(1); + return rc; } void cleanup_bindings(void) { + pthread_mutex_lock(&bindings_mutex); free_bindings(&global_bindings); + pthread_mutex_unlock(&bindings_mutex); } enum { @@ -595,7 +710,20 @@ static int _check_bindings_file(const struct config *conf, FILE *file, char *line = NULL; size_t line_len = 0; ssize_t n; - + char header[sizeof(BINDINGS_FILE_HEADER)]; + + header[sizeof(BINDINGS_FILE_HEADER) - 1] = '\0'; + if (fread(header, sizeof(BINDINGS_FILE_HEADER) - 1, 1, file) < 1) { + condlog(2, "%s: failed to read header from %s", __func__, + conf->bindings_file); + fseek(file, 0, SEEK_SET); + rc = -1; + } else if (strcmp(header, BINDINGS_FILE_HEADER)) { + condlog(2, "%s: invalid header in %s", __func__, + conf->bindings_file); + fseek(file, 0, SEEK_SET); + rc = -1; + } pthread_cleanup_push(cleanup_free_ptr, &line); while ((n = getline(&line, &line_len, file)) >= 0) { char *alias, *wwid; @@ -643,6 +771,68 @@ static int mp_alias_compar(const void *p1, const void *p2) &((*(struct mpentry * const *)p2)->alias)); } +static int _read_bindings_file(const struct config *conf, Bindings *bindings, + bool force) +{ + int can_write; + int rc = 0, ret, fd; + FILE *file; + struct stat st; + int has_changed = uatomic_xchg(&bindings_file_changed, 0); + + if (!force) { + if (!has_changed) { + condlog(4, "%s: bindings are unchanged", __func__); + return BINDINGS_FILE_UP2DATE; + } + } + + fd = open_file(conf->bindings_file, &can_write, BINDINGS_FILE_HEADER); + if (fd == -1) + return BINDINGS_FILE_ERROR; + + file = fdopen(fd, "r"); + if (file != NULL) { + condlog(3, "%s: reading %s", __func__, conf->bindings_file); + + pthread_cleanup_push(cleanup_fclose, file); + ret = _check_bindings_file(conf, file, bindings); + if (ret == 0) { + struct timespec ts; + + rc = BINDINGS_FILE_READ; + ret = fstat(fd, &st); + if (ret == 0) + ts = st.st_mtim; + else { + condlog(1, "%s: fstat failed (%m), using current time", __func__); + clock_gettime(CLOCK_REALTIME_COARSE, &ts); + } + pthread_mutex_lock(×tamp_mutex); + bindings_last_updated = ts; + pthread_mutex_unlock(×tamp_mutex); + } else if (ret == -1 && can_write && !conf->bindings_read_only) { + ret = update_bindings_file(bindings, conf->bindings_file); + if (ret == 0) + rc = BINDINGS_FILE_READ; + else + rc = BINDINGS_FILE_BAD; + } else { + condlog(0, "ERROR: bad settings in read-only bindings file %s", + conf->bindings_file); + rc = BINDINGS_FILE_BAD; + } + pthread_cleanup_pop(1); + } else { + condlog(1, "failed to fdopen %s: %m", + conf->bindings_file); + close(fd); + rc = BINDINGS_FILE_ERROR; + } + + return rc; +} + /* * check_alias_settings(): test for inconsistent alias configuration * @@ -661,8 +851,7 @@ static int mp_alias_compar(const void *p1, const void *p2) */ int check_alias_settings(const struct config *conf) { - int can_write; - int rc = 0, i, fd; + int i, rc; Bindings bindings = {.allocated = 0, }; vector mptable = NULL; struct mpentry *mpe; @@ -695,27 +884,12 @@ int check_alias_settings(const struct config *conf) pthread_cleanup_pop(1); pthread_cleanup_pop(1); - fd = open_file(conf->bindings_file, &can_write, BINDINGS_FILE_HEADER); - if (fd != -1) { - FILE *file = fdopen(fd, "r"); - - if (file != NULL) { - pthread_cleanup_push(cleanup_fclose, file); - rc = _check_bindings_file(conf, file, &bindings); - pthread_cleanup_pop(1); - if (rc == -1 && can_write && !conf->bindings_read_only) - rc = update_bindings_file(&bindings, conf->bindings_file); - else if (rc == -1) - condlog(0, "ERROR: bad settings in read-only bindings file %s", - conf->bindings_file); - } else { - condlog(1, "failed to fdopen %s: %m", - conf->bindings_file); - close(fd); - } + rc = _read_bindings_file(conf, &bindings, true); + + if (rc == BINDINGS_FILE_READ) { + set_global_bindings(&bindings); + rc = 0; } - cleanup_bindings(); - global_bindings = bindings; return rc; } diff --git a/libmultipath/alias.h b/libmultipath/alias.h index 5ef6720b8..ca8911f44 100644 --- a/libmultipath/alias.h +++ b/libmultipath/alias.h @@ -10,5 +10,6 @@ char *get_user_friendly_alias(const char *wwid, const char *file, struct config; int check_alias_settings(const struct config *); void cleanup_bindings(void); - +struct inotify_event; +void handle_bindings_file_inotify(const struct inotify_event *event); #endif /* _ALIAS_H */ diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version index ddd302f5e..57e50c120 100644 --- a/libmultipath/libmultipath.version +++ b/libmultipath/libmultipath.version @@ -238,3 +238,8 @@ global: local: *; }; + +LIBMULTIPATH_20.1.0 { +global: + handle_bindings_file_inotify; +}; diff --git a/multipathd/uxlsnr.c b/multipathd/uxlsnr.c index 02e89fb48..d1f8f2343 100644 --- a/multipathd/uxlsnr.c +++ b/multipathd/uxlsnr.c @@ -41,6 +41,7 @@ #include "cli.h" #include "uxlsnr.h" #include "strbuf.h" +#include "alias.h" /* state of client connection */ enum { @@ -190,6 +191,7 @@ void wakeup_cleanup(void *arg) struct watch_descriptors { int conf_wd; int dir_wd; + int mp_wd; /* /etc/multipath; for bindings file */ }; /* failing to set the watch descriptor is o.k. we just miss a warning @@ -200,6 +202,8 @@ static void reset_watch(int notify_fd, struct watch_descriptors *wds, struct config *conf; int dir_reset = 0; int conf_reset = 0; + int mp_reset = 0; + char *bindings_file __attribute__((cleanup(cleanup_charp))) = NULL; if (notify_fd == -1) return; @@ -214,7 +218,10 @@ static void reset_watch(int notify_fd, struct watch_descriptors *wds, conf_reset = 1; if (wds->dir_wd == -1) dir_reset = 1; + if (wds->mp_wd == -1) + mp_reset = 1; } + bindings_file = strdup(conf->bindings_file); put_multipath_config(conf); if (dir_reset) { @@ -235,7 +242,18 @@ static void reset_watch(int notify_fd, struct watch_descriptors *wds, if (wds->conf_wd == -1) condlog(3, "didn't set up notifications on /etc/multipath.conf: %m"); } - return; + if (mp_reset && bindings_file) { + char *slash = strrchr(bindings_file, '/'); + + if (slash && slash > bindings_file) { + *slash = '\0'; + wds->mp_wd = inotify_add_watch(notify_fd, bindings_file, + IN_MOVED_TO|IN_ONLYDIR); + if (wds->mp_wd == -1) + condlog(3, "didn't set up notifications on %s: %m", + bindings_file); + } + } } static void handle_inotify(int fd, struct watch_descriptors *wds) @@ -256,12 +274,13 @@ static void handle_inotify(int fd, struct watch_descriptors *wds) inotify_rm_watch(fd, wds->conf_wd); if (wds->dir_wd != -1) inotify_rm_watch(fd, wds->dir_wd); - wds->conf_wd = wds->dir_wd = -1; + if (wds->mp_wd != -1) + inotify_rm_watch(fd, wds->mp_wd); + wds->conf_wd = wds->dir_wd = wds->mp_wd = -1; } break; } - got_notify = 1; for (ptr = buff; ptr < buff + len; ptr += sizeof(struct inotify_event) + event->len) { event = (const struct inotify_event *) ptr; @@ -273,7 +292,13 @@ static void handle_inotify(int fd, struct watch_descriptors *wds) wds->conf_wd = inotify_add_watch(notify_fd, DEFAULT_CONFIGFILE, IN_CLOSE_WRITE); else if (wds->dir_wd == event->wd) wds->dir_wd = -1; + else if (wds->mp_wd == event->wd) + wds->mp_wd = -1; } + if (wds->mp_wd != -1 && wds->mp_wd == event->wd) + handle_bindings_file_inotify(event); + else + got_notify = 1; } } if (got_notify) @@ -599,7 +624,7 @@ void *uxsock_listen(long ux_sock, void *trigger_data) int max_pfds = MIN_POLLS + POLLFDS_BASE; /* conf->sequence_nr will be 1 when uxsock_listen is first called */ unsigned int sequence_nr = 0; - struct watch_descriptors wds = { .conf_wd = -1, .dir_wd = -1 }; + struct watch_descriptors wds = { .conf_wd = -1, .dir_wd = -1, .mp_wd = -1, }; struct vectors *vecs = trigger_data; condlog(3, "uxsock: startup listener"); @@ -666,7 +691,8 @@ void *uxsock_listen(long ux_sock, void *trigger_data) reset_watch(notify_fd, &wds, &sequence_nr); polls[POLLFD_NOTIFY].fd = notify_fd; - if (notify_fd == -1 || (wds.conf_wd == -1 && wds.dir_wd == -1)) + if (notify_fd == -1 || (wds.conf_wd == -1 && wds.dir_wd == -1 + && wds.mp_wd == -1)) polls[POLLFD_NOTIFY].events = 0; else polls[POLLFD_NOTIFY].events = POLLIN; diff --git a/tests/alias.c b/tests/alias.c index 7f3ff38a1..9ae275673 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -1954,6 +1954,9 @@ int main(void) int ret = 0; init_test_verbosity(3); + /* avoid open_file() call in _read_bindings_file */ + bindings_file_changed = 0; + ret += test_format_devname(); ret += test_scan_devname(); ret += test_lookup_binding(); From 73d3792d468abb96c391723d219a15eb7c25c088 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 8 Sep 2023 19:54:07 +0200 Subject: [PATCH 28/64] multipath-tools tests: mock pthread_mutex_{lock,unlock} If some test fails with a lock held, cmocka doesn't deal well with pthread_cleanup_pop(). Such tests can cause deadlock with the locking primitives in the alias code, because locks don't get properly unlocked. Just mock the lock/unlock functions and generate an error if they weren't paired at the end of the test. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- tests/Makefile | 1 + tests/alias.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/tests/Makefile b/tests/Makefile index c777d07af..7dac8a8f1 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -52,6 +52,7 @@ blacklist-test_LIBDEPS := -ludev vpd-test_OBJDEPS := $(multipathdir)/discovery.o vpd-test_LIBDEPS := -ludev -lpthread -ldl alias-test_TESTDEPS := test-log.o +alias-test_OBJDEPS := $(mpathutildir)/util.o alias-test_LIBDEPS := -lpthread -ldl valid-test_OBJDEPS := $(multipathdir)/valid.o $(multipathdir)/discovery.o valid-test_LIBDEPS := -lmount -ludev -lpthread -ldl diff --git a/tests/alias.c b/tests/alias.c index 9ae275673..94df36d82 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -89,6 +89,47 @@ int __wrap_dm_get_uuid(const char *name, char *uuid, int uuid_len) return ret; } +static int lock_errors; +static int bindings_locked; +static int timestamp_locked; +int __wrap_pthread_mutex_lock(pthread_mutex_t *mutex) +{ + if (mutex == &bindings_mutex) { + if (bindings_locked) { + fprintf(stderr, "%s: bindings_mutex LOCKED\n", __func__); + lock_errors++; + } + bindings_locked = 1; + } else if (mutex == ×tamp_mutex) { + if (timestamp_locked) { + fprintf(stderr, "%s: timestamp_mutex LOCKED\n", __func__); + lock_errors++; + } + timestamp_locked = 1; + } else + fprintf(stderr, "%s called for unknown mutex %p\n", __func__, mutex); + return 0; +} + +int __wrap_pthread_mutex_unlock(pthread_mutex_t *mutex) +{ + if (mutex == &bindings_mutex) { + if (!bindings_locked) { + fprintf(stderr, "%s: bindings_mutex UNLOCKED\n", __func__); + lock_errors++; + } + bindings_locked = 0; + } else if (mutex == ×tamp_mutex) { + if (!timestamp_locked) { + fprintf(stderr, "%s: timestamp_mutex UNLOCKED\n", __func__); + lock_errors++; + } + timestamp_locked = 0; + } else + fprintf(stderr, "%s called for unknown mutex %p\n", __func__, mutex); + return 0; +} + #define TEST_FDNO 1234 #define TEST_FPTR ((FILE *) 0xaffe) @@ -1718,6 +1759,10 @@ static void gufa_old_nomatch_nowwidmatch(void **state) { free(alias); } +static void gufa_check_locking(void **state) { + assert_int_equal(lock_errors, 0); +} + static int test_get_user_friendly_alias() { const struct CMUnitTest tests[] = { @@ -1743,6 +1788,7 @@ static int test_get_user_friendly_alias() cmocka_unit_test_teardown(gufa_old_nomatch_wwidmatch, teardown_bindings), cmocka_unit_test_teardown(gufa_old_nomatch_wwidmatch_used, teardown_bindings), cmocka_unit_test_teardown(gufa_old_nomatch_nowwidmatch, teardown_bindings), + cmocka_unit_test(gufa_check_locking), }; return cmocka_run_group_tests(tests, NULL, NULL); From 98236a2be45d4e1e34debff785a08c3d1f37d0ed Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 8 Sep 2023 22:13:51 +0200 Subject: [PATCH 29/64] multipath-tools Makefile: sanitize paths for configuration files Make the path to multipath.conf configurable, and use the same prefix by default for multipath.conf and multipath/conf.d. For "usr-merged" distributions with immutable /usr, we'll want to have the configuration under a different prefix. This can be achieved by using e.g. make prefix=/usr etc_prefix="" Note that with prefix=/usr, before this patch the code would use /usr/etc/multipath/conf.d, but /etc/multipath.conf. If this (rather inconsistent) behavior is desired, use the following command line: make prefix=/usr configfile=/etc/multipath.conf Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- Makefile.inc | 9 ++++++--- libmultipath/defaults.h | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Makefile.inc b/Makefile.inc index 502cd0f1e..39972d934 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -37,6 +37,8 @@ prefix := exec_prefix := $(prefix) # Prefix for non-essential libraries (libdmmp) usr_prefix := $(prefix) +# Prefix for configfuration files (multipath.conf) +etc_prefix := $(prefix) # Where to install systemd-related files. systemd is usually installed under /usr # Note: some systemd installations use separate "prefix" and "rootprefix". # In this case, override only unitdir to use systemd's "rootprefix" instead of $(systemd_prefix) @@ -54,7 +56,8 @@ usrlibdir := $(usr_prefix)/$(LIB) includedir := $(usr_prefix)/include pkgconfdir := $(usrlibdir)/pkgconfig plugindir := $(prefix)/$(LIB)/multipath -configdir := $(prefix)/etc/multipath/conf.d +configdir := $(etc_prefix)/etc/multipath/conf.d +configfile := $(etc_prefix)/etc/multipath.conf runtimedir := $(if $(shell test -L /var/run -o ! -d /var/run && echo 1),/run,/var/run) devmapper_incdir := $(or $(shell $(PKG_CONFIG) --variable=includedir devmapper),/usr/include) libudev_incdir := $(or $(shell $(PKG_CONFIG) --variable=includedir libudev),/usr/include) @@ -84,8 +87,8 @@ WARNFLAGS := -Werror -Wall -Wextra -Wformat=2 $(WFORMATOVERFLOW) -Werror=implici $(WNOCLOBBERED) -Werror=cast-qual $(ERROR_DISCARDED_QUALIFIERS) $(W_URCU_TYPE_LIMITS) CPPFLAGS := $(FORTIFY_OPT) $(CPPFLAGS) \ -DBIN_DIR=\"$(bindir)\" -DMULTIPATH_DIR=\"$(plugindir)\" \ - -DRUNTIME_DIR=\"$(runtimedir)\" \ - -DCONFIG_DIR=\"$(configdir)\" -DEXTRAVERSION=\"$(EXTRAVERSION)\" -MMD -MP + -DRUNTIME_DIR=\"$(runtimedir)\" -DCONFIG_DIR=\"$(configdir)\" \ + -DDEFAULT_CONFIGFILE=\"$(configfile)\" -DEXTRAVERSION=\"$(EXTRAVERSION)\" -MMD -MP CFLAGS := --std=gnu99 $(CFLAGS) $(OPTFLAGS) $(WARNFLAGS) -pipe BIN_CFLAGS := -fPIE -DPIE LIB_CFLAGS := -fPIC diff --git a/libmultipath/defaults.h b/libmultipath/defaults.h index b3f11d4c5..bc2d6388a 100644 --- a/libmultipath/defaults.h +++ b/libmultipath/defaults.h @@ -66,7 +66,6 @@ #define MAX_DEV_LOSS_TMO UINT_MAX #define DEFAULT_PIDFILE RUNTIME_DIR "/multipathd.pid" #define DEFAULT_SOCKET "/org/kernel/linux/storage/multipathd" -#define DEFAULT_CONFIGFILE "/etc/multipath.conf" #define DEFAULT_BINDINGS_FILE "/etc/multipath/bindings" #define DEFAULT_WWIDS_FILE "/etc/multipath/wwids" #define DEFAULT_PRKEYS_FILE "/etc/multipath/prkeys" From 6d0f917e7814dbfe6e2f3d811b9d4ab5be02cd63 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 8 Sep 2023 22:26:22 +0200 Subject: [PATCH 30/64] multipath-tools: add compile time configuration for "/etc/multipath" Instead of hard-conding "/etc/multipath" as the path for the state files "bindings", "prkeys", and "wwids", make this path configurable via the "statedir" compile-time option. The default is currently still /etc, it might change to /var/lib or similar in the future. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- Makefile.inc | 4 +++- libmultipath/defaults.h | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Makefile.inc b/Makefile.inc index 39972d934..96206b2f8 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -58,6 +58,7 @@ pkgconfdir := $(usrlibdir)/pkgconfig plugindir := $(prefix)/$(LIB)/multipath configdir := $(etc_prefix)/etc/multipath/conf.d configfile := $(etc_prefix)/etc/multipath.conf +statedir := $(etc_prefix)/etc/multipath runtimedir := $(if $(shell test -L /var/run -o ! -d /var/run && echo 1),/run,/var/run) devmapper_incdir := $(or $(shell $(PKG_CONFIG) --variable=includedir devmapper),/usr/include) libudev_incdir := $(or $(shell $(PKG_CONFIG) --variable=includedir libudev),/usr/include) @@ -88,7 +89,8 @@ WARNFLAGS := -Werror -Wall -Wextra -Wformat=2 $(WFORMATOVERFLOW) -Werror=implici CPPFLAGS := $(FORTIFY_OPT) $(CPPFLAGS) \ -DBIN_DIR=\"$(bindir)\" -DMULTIPATH_DIR=\"$(plugindir)\" \ -DRUNTIME_DIR=\"$(runtimedir)\" -DCONFIG_DIR=\"$(configdir)\" \ - -DDEFAULT_CONFIGFILE=\"$(configfile)\" -DEXTRAVERSION=\"$(EXTRAVERSION)\" -MMD -MP + -DDEFAULT_CONFIGFILE=\"$(configfile)\" -DSTATE_DIR=\"$(statedir)\" \ + -DEXTRAVERSION=\"$(EXTRAVERSION)\" -MMD -MP CFLAGS := --std=gnu99 $(CFLAGS) $(OPTFLAGS) $(WARNFLAGS) -pipe BIN_CFLAGS := -fPIE -DPIE LIB_CFLAGS := -fPIC diff --git a/libmultipath/defaults.h b/libmultipath/defaults.h index bc2d6388a..d01f97122 100644 --- a/libmultipath/defaults.h +++ b/libmultipath/defaults.h @@ -66,9 +66,9 @@ #define MAX_DEV_LOSS_TMO UINT_MAX #define DEFAULT_PIDFILE RUNTIME_DIR "/multipathd.pid" #define DEFAULT_SOCKET "/org/kernel/linux/storage/multipathd" -#define DEFAULT_BINDINGS_FILE "/etc/multipath/bindings" -#define DEFAULT_WWIDS_FILE "/etc/multipath/wwids" -#define DEFAULT_PRKEYS_FILE "/etc/multipath/prkeys" +#define DEFAULT_BINDINGS_FILE STATE_DIR "/bindings" +#define DEFAULT_WWIDS_FILE STATE_DIR "/wwids" +#define DEFAULT_PRKEYS_FILE STATE_DIR "/prkeys" #define MULTIPATH_SHM_BASE RUNTIME_DIR "/multipath/" From 0fb7f303a7ac14347b5e8d78299005d7bbba7b74 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 8 Sep 2023 22:48:16 +0200 Subject: [PATCH 31/64] multipath-tools man pages: generate with correct paths Generate the man pages using the compile-time settings for paths to multipath.conf etc. Add a paragraph about the CONFIGDIR (/etc/multipath/conf.d) and the drop-in configuration files in the multipath.conf man page. Also, make sure all generated man pages and other files are correctly removed by "make clean". Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- .gitignore | 4 +++ Makefile.inc | 3 +++ mpathpersist/Makefile | 5 ++-- .../{mpathpersist.8 => mpathpersist.8.in} | 2 +- multipath/Makefile | 13 +++++---- multipath/{multipath.8 => multipath.8.in} | 10 +++---- .../{multipath.conf.5 => multipath.conf.5.in} | 27 ++++++++++++------- multipathd/Makefile | 9 ++++--- multipathd/{multipathd.8 => multipathd.8.in} | 8 +++--- 9 files changed, 49 insertions(+), 32 deletions(-) rename mpathpersist/{mpathpersist.8 => mpathpersist.8.in} (99%) rename multipath/{multipath.8 => multipath.8.in} (97%) rename multipath/{multipath.conf.5 => multipath.conf.5.in} (98%) rename multipathd/{multipathd.8 => multipathd.8.in} (97%) diff --git a/.gitignore b/.gitignore index 535353e51..2986578f4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,11 +13,15 @@ cscope.files cscope.out kpartx/kpartx multipath/multipath +multipath/multipath.8 +multipath/multipath.conf.5 multipath/multipath.rules multipath/tmpfiles.conf multipathd/multipathd +multipathd/multipathd.8 multipathd/multipathc mpathpersist/mpathpersist +mpathpersist/mpathpersist.8 abi.tar.gz abi abi-test diff --git a/Makefile.inc b/Makefile.inc index 96206b2f8..79e521e1b 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -133,3 +133,6 @@ NV_VERSION_SCRIPT = $(DEVLIB:%.so=%-nv.version) @grep -P '^[ \t]+[a-zA-Z_][a-zA-Z0-9_]*;' $< >>$@ @printf 'local:\n\t*;\n};\n' >>$@ +%: %.in + @echo creating $@ + $(Q)sed 's:@CONFIGFILE@:'$(configfile)':g;s:@CONFIGDIR@:'$(configdir)':g;s:@STATE_DIR@:'$(statedir)':g;s:@RUNTIME_DIR@:'$(runtimedir)':g' $< >$@ diff --git a/mpathpersist/Makefile b/mpathpersist/Makefile index f57c105c6..f37494674 100644 --- a/mpathpersist/Makefile +++ b/mpathpersist/Makefile @@ -8,10 +8,11 @@ LIBDEPS += -L$(mpathpersistdir) -lmpathpersist -L$(multipathdir) -lmultipath \ -L$(mpathutildir) -lmpathutil -L$(mpathcmddir) -lmpathcmd -lpthread -ldevmapper -ludev EXEC = mpathpersist +MANPAGES := mpathpersist.8 OBJS = main.o -all: $(EXEC) +all: $(EXEC) $(MANPAGES) $(EXEC): $(OBJS) $(Q)$(CC) $(OBJS) -o $(EXEC) $(LDFLAGS) $(CFLAGS) $(LIBDEPS) @@ -23,7 +24,7 @@ install: $(Q)$(INSTALL_PROGRAM) -m 644 $(EXEC).8 $(DESTDIR)$(mandir)/man8 clean: dep_clean - $(Q)$(RM) core *.o $(EXEC) + $(Q)$(RM) core *.o $(EXEC) $(MANPAGES) include $(wildcard $(OBJS:.o=.d)) diff --git a/mpathpersist/mpathpersist.8 b/mpathpersist/mpathpersist.8.in similarity index 99% rename from mpathpersist/mpathpersist.8 rename to mpathpersist/mpathpersist.8.in index 8d26b37cd..fecef0d63 100644 --- a/mpathpersist/mpathpersist.8 +++ b/mpathpersist/mpathpersist.8.in @@ -31,7 +31,7 @@ mpathpersist \- Manages SCSI persistent reservations on dm multipath devices. . This utility is used to manage SCSI persistent reservations on Device Mapper Multipath devices. To be able to use this functionality, the \fIreservation_key\fR -attribute must be defined in the \fI/etc/multipath.conf\fR file. Otherwise the +attribute must be defined in the \fI@CONFIGFILE@\fR file. Otherwise the \fBmultipathd\fR daemon will not check for persistent reservation for newly discovered paths or reinstated paths. . diff --git a/multipath/Makefile b/multipath/Makefile index 73db991a2..68cb5ce79 100644 --- a/multipath/Makefile +++ b/multipath/Makefile @@ -3,7 +3,9 @@ # include ../Makefile.inc -EXEC := multipath +EXEC := multipath +MANPAGES := multipath.8 multipath.conf.5 +GENERATED := $(MANPAGES) multipath.rules tmpfiles.conf CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) -I$(mpathcmddir) CFLAGS += $(BIN_CFLAGS) @@ -13,7 +15,7 @@ LIBDEPS += -L$(multipathdir) -lmultipath -L$(mpathutildir) -lmpathutil \ OBJS := main.o -all: $(EXEC) multipath.rules tmpfiles.conf +all: $(EXEC) $(GENERATED) $(EXEC): $(OBJS) $(multipathdir)/libmultipath.so $(mpathcmddir)/libmpathcmd.so @echo building $@ because of $? @@ -47,15 +49,12 @@ uninstall: $(Q)$(RM) $(DESTDIR)$(libudevdir)/rules.d/56-multipath.rules $(Q)$(RM) $(DESTDIR)$(mandir)/man8/$(EXEC).8 $(Q)$(RM) $(DESTDIR)$(mandir)/man5/$(EXEC).conf.5 + $(Q)$(RM) $(DESTDIR)$(tmpfilesdir)/multipath.conf clean: dep_clean - $(Q)$(RM) core *.o $(EXEC) multipath.rules tmpfiles.conf + $(Q)$(RM) core *.o $(EXEC) $(GENERATED) include $(wildcard $(OBJS:.o=.d)) dep_clean: $(Q)$(RM) $(OBJS:.o=.d) - -%: %.in - @echo creating $@ - $(Q)sed 's,@RUNTIME_DIR@,$(runtimedir),' $< >$@ diff --git a/multipath/multipath.8 b/multipath/multipath.8.in similarity index 97% rename from multipath/multipath.8 rename to multipath/multipath.8.in index 5fed6df75..348eb220b 100644 --- a/multipath/multipath.8 +++ b/multipath/multipath.8.in @@ -185,7 +185,7 @@ Display the currently used multipathd configuration. .B \-T Display the currently used multipathd configuration, limiting the output to those devices actually present in the system. This can be used a template for -creating \fImultipath.conf\fR. +creating \fI@CONFIGFILE@\fR. . .\" ---------------------------------------------------------------------------- .SH OPTIONS @@ -233,11 +233,11 @@ option from \fBmultipath.conf(5)\fR. .B \-i Ignore WWIDs file when processing devices. If \fIfind_multipaths strict\fR or \fIfind_multipaths no\fR is set in -\fImultipath.conf\fR, multipath only considers devices that are +\fI@CONFIGFILE@\fR, multipath only considers devices that are listed in the WWIDs file. This option overrides that behavior. For other values of \fIfind_multipaths\fR, this option has no effect. See the description of \fIfind_multipaths\fR in -.BR multipath.conf (5). +.BR @CONFIGFILE@ (5). This option should only be used in rare circumstances. . .TP @@ -246,8 +246,8 @@ Treat the bindings file as read only. . .TP .BI \-b " file" -Set \fIuser_friendly_names\fR bindings file location. The default is -\fI/etc/multipath/bindings\fR. +(\fBdeprecated, do not use\fR) Set \fIuser_friendly_names\fR bindings file location. The default is +\fI@STATE_DIR@/bindings\fR. . .TP .B \-q diff --git a/multipath/multipath.conf.5 b/multipath/multipath.conf.5.in similarity index 98% rename from multipath/multipath.conf.5 rename to multipath/multipath.conf.5.in index 93af17dbc..20df2232f 100644 --- a/multipath/multipath.conf.5 +++ b/multipath/multipath.conf.5.in @@ -13,14 +13,14 @@ .SH NAME .\" ---------------------------------------------------------------------------- . -multipath.conf \- multipath daemon configuration file. +@CONFIGFILE@, @CONFIGDIR@/*.conf \- multipath daemon configuration file. . . .\" ---------------------------------------------------------------------------- .SH DESCRIPTION .\" ---------------------------------------------------------------------------- . -.B "/etc/multipath.conf" +.B "@CONFIGFILE@" is the configuration file for the multipath daemon. It is used to overwrite the built-in configuration table of \fBmultipathd\fP. Any line whose first non-white-space character is a '#' is considered @@ -29,6 +29,15 @@ a comment line. Empty lines are ignored. Currently used multipathd configuration can be displayed with the \fBmultipath -t\fR or \fBmultipathd show config\fR command. . +.PP +Additional configuration can be made in drop-in files under +.B @CONFIGDIR@. +Files ending in \fI.conf\fR in this directory are read +in alphabetical order, after reading \fI@CONFIGFILE@\fR. +They use the same syntax as \fI@CONFIGFILE@\fR itself, +and support all sections and keywords. If a keyword occurs in the same section +in multiple files, the last occurence will take precedence over all others. +. . .\" ---------------------------------------------------------------------------- .SH SYNTAX @@ -85,7 +94,7 @@ not mandatory. . .LP .B Note on regular expressions: -The \fImultipath.conf\fR syntax allows many attribute values to be specified as POSIX +The \fI@CONFIGFILE@\fR syntax allows many attribute values to be specified as POSIX Extended Regular Expressions (see \fBregex\fR(7)). These regular expressions are \fBcase sensitive\fR and \fBnot anchored\fR, thus the expression "bar" matches "barbie", "rhabarber", and "wunderbar", but not "Barbie". To avoid unwanted substring @@ -711,7 +720,7 @@ The default is: \fBno\fR .B user_friendly_names If set to .I yes -, using the bindings file \fI/etc/multipath/bindings\fR to assign a persistent +, using the bindings file \fI@STATE_DIR@/bindings\fR to assign a persistent and unique alias to the multipath, in the form of mpath. If set to .I no use the WWID as the alias. In either case this be will @@ -790,7 +799,7 @@ The full pathname of the binding file to be used when the user_friendly_names option is set. .RS .TP -The default is: \fB/etc/multipath/bindings\fR +The default is: \fB@STATE_DIR@/bindings\fR .RE . . @@ -801,7 +810,7 @@ The full pathname of the WWIDs file, which is used by multipath to keep track of the WWIDs for LUNs it has created multipath devices on in the past. .RS .TP -The default is: \fB/etc/multipath/wwids\fR +The default is: \fB@STATE_DIR@/wwids\fR .RE . . @@ -813,7 +822,7 @@ track of the persistent reservation key used for a specific WWID, when \fIreservation_key\fR is set to \fBfile\fR. .RS .TP -The default is: \fB/etc/multipath/prkeys\fR +The default is: \fB@STATE_DIR@/prkeys\fR .RE . . @@ -872,7 +881,7 @@ The default is: \fBno\fR .I yes and the SCSI layer has already attached a hardware_handler to the device, multipath will not force the device to use the hardware_handler specified by -multipath.conf. If the SCSI layer has not attached a hardware handler, +@CONFIGFILE@. If the SCSI layer has not attached a hardware handler, multipath will continue to use its configured hardware handler. .RS .PP @@ -1559,7 +1568,7 @@ given device, the attributes of all matching entries are applied to it. If an attribute is specified in several matching device subsections, later entries take precedence. Thus, entries in files under \fIconfig_dir\fR (in reverse alphabetical order) have the highest precedence, followed by entries -in \fImultipath.conf\fR; the built-in hardware table has the lowest +in \fI@CONFIGFILE@\fR; the built-in hardware table has the lowest precedence. Inside a configuration file, later entries have higher precedence than earlier ones. .LP diff --git a/multipathd/Makefile b/multipathd/Makefile index 0d0146c50..cdba3db17 100644 --- a/multipathd/Makefile +++ b/multipathd/Makefile @@ -1,7 +1,8 @@ include ../Makefile.inc -EXEC := multipathd -CLI := multipathc +EXEC := multipathd +CLI := multipathc +MANPAGES := multipathd.8 CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) -I$(mpathpersistdir) -I$(mpathcmddir) -I$(thirdpartydir) \ $(shell $(PKG_CONFIG) --modversion liburcu 2>/dev/null | \ @@ -42,7 +43,7 @@ ifeq ($(FPIN_SUPPORT),1) OBJS += fpin_handlers.o endif -all : $(EXEC) $(CLI) +all : $(EXEC) $(CLI) $(MANPAGES) $(EXEC): $(OBJS) $(multipathdir)/libmultipath.so $(mpathcmddir)/libmpathcmd.so @echo building $@ because of $? @@ -79,7 +80,7 @@ uninstall: $(Q)$(RM) $(DESTDIR)$(unitdir)/$(EXEC).socket clean: dep_clean - $(Q)$(RM) core *.o $(EXEC) $(CLI) + $(Q)$(RM) core *.o $(EXEC) $(CLI) $(MANPAGES) include $(wildcard $(OBJS:.o=.d) $(CLI_OBJS:.o=.d)) diff --git a/multipathd/multipathd.8 b/multipathd/multipathd.8.in similarity index 97% rename from multipathd/multipathd.8 rename to multipathd/multipathd.8.in index cc72b7754..e98c27fd4 100644 --- a/multipathd/multipathd.8 +++ b/multipathd/multipathd.8.in @@ -155,7 +155,7 @@ Show the format wildcards used in interactive commands taking $format. .TP .B list|show config Show the currently used configuration, derived from default values and values -specified within the configuration file \fI/etc/multipath.conf\fR. +specified within the configuration file \fI@CONFIGFILE@\fR. . .TP .B list|show config local @@ -165,7 +165,7 @@ the devices section to those devices that are actually present in the system. .TP .B list|show blacklist Show the currently used blacklist rules, derived from default values and values -specified within the configuration file \fI/etc/multipath.conf\fR. +specified within the configuration file \fI@CONFIGFILE@\fR. . .TP .B list|show devices @@ -290,13 +290,13 @@ Get the current persistent reservation key associated with $map. .B map|multipath $map setprkey key $key Set the persistent reservation key associated with $map to $key in the \fIprkeys_file\fR. This key will only be used by multipathd if -\fIreservation_key\fR is set to \fBfile\fR in \fI/etc/multipath.conf\fR. +\fIreservation_key\fR is set to \fBfile\fR in \fI@CONFIGFILE@\fR. . .TP .B map|multipath $map unsetprkey Remove the persistent reservation key associated with $map from the \fIprkeys_file\fR. This will only unset the key used by multipathd if -\fIreservation_key\fR is set to \fBfile\fR in \fI/etc/multipath.conf\fR. +\fIreservation_key\fR is set to \fBfile\fR in \fI@CONFIGFILE@\fR. . .TP .B path $path setmarginal From 3948e2b6caf244a4f6eb8793c00ae1fda547d9d0 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 11 Sep 2023 09:30:13 +0200 Subject: [PATCH 32/64] libdmmp/Makefile: fix bug in install section Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libdmmp/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdmmp/Makefile b/libdmmp/Makefile index 078eca8fc..172ba045a 100644 --- a/libdmmp/Makefile +++ b/libdmmp/Makefile @@ -44,7 +44,7 @@ install: $(DESTDIR)$(pkgconfdir)/$(PKGFILE) $(Q)sed -i 's|__INCLUDEDIR__|$(includedir)|g' \ $(DESTDIR)$(pkgconfdir)/$(PKGFILE) - $(Q)$(INSTALL_PROGRAM) -d 755 $(DESTDIR)$(mandir)/man3 + $(Q)$(INSTALL_PROGRAM) -m 755 -d $(DESTDIR)$(mandir)/man3 $(Q)$(INSTALL_PROGRAM) -m 644 -t $(DESTDIR)$(mandir)/man3 docs/man/*.3 uninstall: From 39d56f2259560af3e26554b4120024f231f3d0b9 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 11 Sep 2023 10:22:13 +0200 Subject: [PATCH 33/64] multipath-tools: README.md: improve documentation for compile-time options Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- README.md | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a7f994ae5..679e55bff 100644 --- a/README.md +++ b/README.md @@ -89,9 +89,17 @@ The following variables can be passed to the `make` command line: * `plugindir="/some/path"`: directory where libmultipath plugins (path checkers, prioritizers, and foreign multipath support) will be looked up. This used to be the run-time option `multipath_dir` in earlier versions. - * `configdir="/some/path"` : directory to search for configuration files. + The default is `$(prefix)/$(LIB)/multipath`, where `$(LIB)` is `lib64` on + systems that have `/lib64`, and `lib` otherwise. + * `configfile="/some/path`": The path to the main configuration file. + The defalt is `$(etc_prefix)/etc/multipath.conf`. + * `configdir="/some/path"` : directory to search for additional configuration files. This used to be the run-time option `config_dir` in earlier versions. - The default is `/etc/multipath/conf.d`. + The default is `$(etc_prefix)/etc/multipath/conf.d`. + * `statedir="/some/path"`: The path of the directory where multipath-tools + stores run-time settings that need persist between reboots, such as known + WWIDs, user-friendly names, and persistent reservation keys. + The default is `$(etc_prefix)/etc/multipath`. * `READLINE=libedit` or `READLINE=libreadline`: enable command line history and TAB completion in the interactive mode *(which is entered with `multipathd -k` or `multipathc`)*. The respective development package will be required for building. @@ -119,21 +127,27 @@ The following variables can be passed to the `make` command line: ### Installation Paths * `prefix`: The directory prefix for (almost) all files to be installed. - Distributions may want to set this to `/usr`. - **Note**: for multipath-tools, unlike many other packages, `prefix` - defaults to the empty string, which resolves to the root directory (`/`). + "Usr-merged" distributions[^systemd] may want to set this to `/usr`. The + default is empty (`""`). * `usr_prefix`: where to install those parts of the code that aren't necessary - for booting. You may want to set this to `/usr` if `prefix` is empty. - * `systemd_prefix`: Prefix for systemd-related files. It defaults to `/usr`. - Some systemd installations use separate `prefix` and `rootprefix`. On such - a distribution, set `prefix`, and override `unitdir` to use systemd's - `rootprefix`. + for booting. Non-usr-merged distributions[^systemd] may want to set this to + `/usr`. The default is `$(prefix)`. + * `systemd_prefix`: Prefix for systemd-related files[^systemd]. The default is `/usr`. + * `etc_prefix`: The prefix for configuration files. "Usr-merged" + distributions with immutable `/usr`[^systemd] may want to set this to + `/etc`. The default is `$(prefix)`. * `LIB`: the subdirectory under `prefix` where shared libraries will be installed. By default, the makefile uses `/lib64` if this directory is found on the build system, and `/lib` otherwise. -See also `configdir` and `plugindir` above. See `Makefile.inc` for more -fine-grained control. +The options `configdir`, `plugindir`, `configfile`, and `statedir` above can +be used for setting indvidual paths where the `prefix` variables don't provide +sufficient control. See `Makefile.inc` for even more fine-grained control. + +[^systemd]: Some systemd installations use separate `prefix` and `rootprefix`. + On such a distribution, set `prefix`, and override `unitdir` to use systemd's + `rootprefix`. Recent systemd releases generally require everything to be + installed under `/usr` (so-called "usr-merged" distribution). On "usr- ### Compiler Options From d47a63f7715f22b4cc970b51f05eb9d0fe50a5b0 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 11 Sep 2023 11:36:25 +0200 Subject: [PATCH 34/64] libmultipath: print built-in values for deprecated options In the error messages we print when a deprecated option is encountered, print the compile-time value of the option. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/dict.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/libmultipath/dict.c b/libmultipath/dict.c index f81c84aa0..dace343d4 100644 --- a/libmultipath/dict.c +++ b/libmultipath/dict.c @@ -314,14 +314,16 @@ def_ ## option ## _handler (struct config *conf, vector strvec, \ static int deprecated_handler(struct config *conf, vector strvec, const char *file, int line_nr); -#define declare_deprecated_handler(option) \ +#define declare_deprecated_handler(option, default) \ static int \ deprecated_ ## option ## _handler (struct config *conf, vector strvec, \ const char *file, int line_nr) \ { \ static bool warned; \ if (!warned) { \ - condlog(1, "%s line %d: ignoring deprecated option \"" #option "\"", file, line_nr); \ + condlog(1, "%s line %d: ignoring deprecated option \"" \ + #option "\", using built-in value: \"%s\"", \ + file, line_nr, default); \ warned = true; \ } \ return deprecated_handler(conf, strvec, file, line_nr); \ @@ -2057,11 +2059,11 @@ snprint_deprecated (struct config *conf, struct strbuf *buff, const void * data) } // Deprecated keywords -declare_deprecated_handler(config_dir) -declare_deprecated_handler(disable_changed_wwids) -declare_deprecated_handler(getuid_callout) -declare_deprecated_handler(multipath_dir) -declare_deprecated_handler(pg_timeout) +declare_deprecated_handler(config_dir, CONFIG_DIR) +declare_deprecated_handler(disable_changed_wwids, "yes") +declare_deprecated_handler(getuid_callout, "(not set)") +declare_deprecated_handler(multipath_dir, MULTIPATH_DIR) +declare_deprecated_handler(pg_timeout, "(not set)") /* * If you add or remove a keyword also update multipath/multipath.conf.5 From 9a21c1541b539f884854cbd82a7e7d4bf79a1fc5 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 11 Sep 2023 11:37:37 +0200 Subject: [PATCH 35/64] multipath: add a missing newline Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- multipath/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/multipath/main.c b/multipath/main.c index 45e9745f3..b91289e8b 100644 --- a/multipath/main.c +++ b/multipath/main.c @@ -1025,7 +1025,7 @@ main (int argc, char *argv[]) } if (check_alias_settings(conf)) { - fprintf(stderr, "fatal configuration error, aborting"); + fprintf(stderr, "fatal configuration error, aborting\n"); exit(RTVL_FAIL); } From 58ad93d6990d91a127099fd0169007b6c5a3b784 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 11 Sep 2023 16:03:34 +0200 Subject: [PATCH 36/64] multipath-tools: allow prefixes with and w/o trailing slash Add some logic to Makefile.inc that leads to the same result for "prefix=" and "prefix=/", or "prefix=/usr" and "prefix=/usr/". The logic does not work for multiple trailing slashes. It applies to all XYZ_prefix variables in Makefile.inc. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- Makefile.inc | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/Makefile.inc b/Makefile.inc index 79e521e1b..6e384e686 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -43,22 +43,31 @@ etc_prefix := $(prefix) # Note: some systemd installations use separate "prefix" and "rootprefix". # In this case, override only unitdir to use systemd's "rootprefix" instead of $(systemd_prefix) systemd_prefix := /usr -unitdir := $(systemd_prefix)/lib/systemd/system -tmpfilesdir := $(systemd_prefix)/lib/tmpfiles.d -modulesloaddir := $(systemd_prefix)/lib/modules-load.d -libudevdir := $(systemd_prefix)/lib/udev + +# Make sure all prefix variables end in "/" +append-slash = $(1)$(if $(filter %/,$(1)),,/) +override prefix := $(call append-slash,$(prefix)) +override exec_prefix := $(call append-slash,$(exec_prefix)) +override usr_prefix := $(call append-slash,$(usr_prefix)) +override etc_prefix := $(call append-slash,$(etc_prefix)) +override systemd_prefix := $(call append-slash,$(systemd_prefix)) + +unitdir := $(systemd_prefix)lib/systemd/system +tmpfilesdir := $(systemd_prefix)lib/tmpfiles.d +modulesloaddir := $(systemd_prefix)lib/modules-load.d +libudevdir := $(systemd_prefix)lib/udev udevrulesdir := $(libudevdir)/rules.d -bindir := $(exec_prefix)/sbin -mandir := $(usr_prefix)/share/man +bindir := $(exec_prefix)sbin +mandir := $(usr_prefix)share/man LIB := $(if $(shell test -d /lib64 && echo 1),lib64,lib) -syslibdir := $(prefix)/$(LIB) -usrlibdir := $(usr_prefix)/$(LIB) -includedir := $(usr_prefix)/include +syslibdir := $(prefix)$(LIB) +usrlibdir := $(usr_prefix)$(LIB) +includedir := $(usr_prefix)include pkgconfdir := $(usrlibdir)/pkgconfig -plugindir := $(prefix)/$(LIB)/multipath -configdir := $(etc_prefix)/etc/multipath/conf.d -configfile := $(etc_prefix)/etc/multipath.conf -statedir := $(etc_prefix)/etc/multipath +plugindir := $(prefix)$(LIB)/multipath +configdir := $(etc_prefix)etc/multipath/conf.d +configfile := $(etc_prefix)etc/multipath.conf +statedir := $(etc_prefix)etc/multipath runtimedir := $(if $(shell test -L /var/run -o ! -d /var/run && echo 1),/run,/var/run) devmapper_incdir := $(or $(shell $(PKG_CONFIG) --variable=includedir devmapper),/usr/include) libudev_incdir := $(or $(shell $(PKG_CONFIG) --variable=includedir libudev),/usr/include) From ff42196ef864905700c191c1b3d03ff44812b0d5 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 11 Sep 2023 17:58:13 +0200 Subject: [PATCH 37/64] libmultipath: deprecate bindings_file, wwids_file, prkeys_file The options bindings_file, wwids_file, and prkeys_file have been deprecated since cb4d6db ("libmultipath: deprecate file and directory config options") (multipath-tools 0.8.8). Deprecate and ignore them now. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/alias.c | 43 +++++++++---------- libmultipath/alias.h | 3 +- libmultipath/config.c | 18 -------- libmultipath/config.h | 3 -- libmultipath/dict.c | 39 +++--------------- libmultipath/libmultipath.version | 8 +--- libmultipath/prkey.c | 7 ++-- libmultipath/prkey.h | 7 ++-- libmultipath/propsel.c | 5 +-- libmultipath/wwids.c | 18 ++------ multipath/main.c | 2 +- multipath/multipath.conf.5.in | 23 +++++------ multipathd/uxlsnr.c | 17 +++----- tests/alias.c | 68 +++++++++++++++---------------- 14 files changed, 90 insertions(+), 171 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index 964b8a7b4..e5d3f1512 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -55,6 +55,8 @@ /* uatomic access only */ static int bindings_file_changed = 1; +static const char bindings_file_path[] = DEFAULT_BINDINGS_FILE; + static pthread_mutex_t timestamp_mutex = PTHREAD_MUTEX_INITIALIZER; static struct timespec bindings_last_updated; @@ -274,7 +276,6 @@ static int write_bindings_file(const Bindings *bindings, int fd, void handle_bindings_file_inotify(const struct inotify_event *event) { - struct config *conf; const char *base; bool changed = false; struct stat st; @@ -284,12 +285,9 @@ void handle_bindings_file_inotify(const struct inotify_event *event) if (!(event->mask & IN_MOVED_TO)) return; - conf = get_multipath_config(); - base = strrchr(conf->bindings_file, '/'); - changed = base && base > conf->bindings_file && - !strcmp(base + 1, event->name); - ret = stat(conf->bindings_file, &st); - put_multipath_config(conf); + base = strrchr(bindings_file_path, '/'); + changed = base && !strcmp(base + 1, event->name); + ret = stat(bindings_file_path, &st); if (!changed) return; @@ -310,8 +308,7 @@ void handle_bindings_file_inotify(const struct inotify_event *event) __func__, (long)ts.tv_sec, (long)ts.tv_nsec / 1000); } -static int update_bindings_file(const Bindings *bindings, - const char *bindings_file) +static int update_bindings_file(const Bindings *bindings) { int rc; int fd = -1; @@ -319,7 +316,7 @@ static int update_bindings_file(const Bindings *bindings, mode_t old_umask; struct timespec ts; - if (safe_sprintf(tempname, "%s.XXXXXX", bindings_file)) + if (safe_sprintf(tempname, "%s.XXXXXX", bindings_file_path)) return -1; /* coverity: SECURE_TEMP */ old_umask = umask(0077); @@ -336,13 +333,13 @@ static int update_bindings_file(const Bindings *bindings, unlink(tempname); return rc; } - if ((rc = rename(tempname, bindings_file)) == -1) + if ((rc = rename(tempname, bindings_file_path)) == -1) condlog(0, "%s: rename: %m", __func__); else { pthread_mutex_lock(×tamp_mutex); bindings_last_updated = ts; pthread_mutex_unlock(×tamp_mutex); - condlog(1, "updated bindings file %s", bindings_file); + condlog(1, "updated bindings file %s", bindings_file_path); } return rc; } @@ -474,7 +471,7 @@ int get_free_id(const Bindings *bindings, const char *prefix, const char *map_ww /* Called with binding_mutex held */ static char * -allocate_binding(const char *filename, const char *wwid, int id, const char *prefix) +allocate_binding(const char *wwid, int id, const char *prefix) { STRBUF_ON_STACK(buf); char *alias; @@ -498,7 +495,7 @@ allocate_binding(const char *filename, const char *wwid, int id, const char *pre return NULL; } - if (update_bindings_file(&global_bindings, filename) == -1) { + if (update_bindings_file(&global_bindings) == -1) { condlog(1, "%s: deleting binding %s for %s", __func__, alias, wwid); delete_binding(&global_bindings, wwid); free(alias); @@ -565,7 +562,7 @@ static void read_bindings_file(void) * that the mpvec corrcectly represents kernel state. */ -char *get_user_friendly_alias(const char *wwid, const char *file, const char *alias_old, +char *get_user_friendly_alias(const char *wwid, const char *alias_old, const char *prefix, bool bindings_read_only) { char *alias = NULL; @@ -622,7 +619,7 @@ char *get_user_friendly_alias(const char *wwid, const char *file, const char *al } if (!bindings_read_only && id > 0) - alias = allocate_binding(file, wwid, id, prefix); + alias = allocate_binding(wwid, id, prefix); if (alias && !new_binding) condlog(2, "Allocated existing binding [%s] for WWID [%s]", @@ -715,12 +712,12 @@ static int _check_bindings_file(const struct config *conf, FILE *file, header[sizeof(BINDINGS_FILE_HEADER) - 1] = '\0'; if (fread(header, sizeof(BINDINGS_FILE_HEADER) - 1, 1, file) < 1) { condlog(2, "%s: failed to read header from %s", __func__, - conf->bindings_file); + bindings_file_path); fseek(file, 0, SEEK_SET); rc = -1; } else if (strcmp(header, BINDINGS_FILE_HEADER)) { condlog(2, "%s: invalid header in %s", __func__, - conf->bindings_file); + bindings_file_path); fseek(file, 0, SEEK_SET); rc = -1; } @@ -787,13 +784,13 @@ static int _read_bindings_file(const struct config *conf, Bindings *bindings, } } - fd = open_file(conf->bindings_file, &can_write, BINDINGS_FILE_HEADER); + fd = open_file(bindings_file_path, &can_write, BINDINGS_FILE_HEADER); if (fd == -1) return BINDINGS_FILE_ERROR; file = fdopen(fd, "r"); if (file != NULL) { - condlog(3, "%s: reading %s", __func__, conf->bindings_file); + condlog(3, "%s: reading %s", __func__, bindings_file_path); pthread_cleanup_push(cleanup_fclose, file); ret = _check_bindings_file(conf, file, bindings); @@ -812,20 +809,20 @@ static int _read_bindings_file(const struct config *conf, Bindings *bindings, bindings_last_updated = ts; pthread_mutex_unlock(×tamp_mutex); } else if (ret == -1 && can_write && !conf->bindings_read_only) { - ret = update_bindings_file(bindings, conf->bindings_file); + ret = update_bindings_file(bindings); if (ret == 0) rc = BINDINGS_FILE_READ; else rc = BINDINGS_FILE_BAD; } else { condlog(0, "ERROR: bad settings in read-only bindings file %s", - conf->bindings_file); + bindings_file_path); rc = BINDINGS_FILE_BAD; } pthread_cleanup_pop(1); } else { condlog(1, "failed to fdopen %s: %m", - conf->bindings_file); + bindings_file_path); close(fd); rc = BINDINGS_FILE_ERROR; } diff --git a/libmultipath/alias.h b/libmultipath/alias.h index ca8911f44..629e8d566 100644 --- a/libmultipath/alias.h +++ b/libmultipath/alias.h @@ -3,8 +3,7 @@ int valid_alias(const char *alias); int get_user_friendly_wwid(const char *alias, char *buff); -char *get_user_friendly_alias(const char *wwid, const char *file, - const char *alias_old, +char *get_user_friendly_alias(const char *wwid, const char *alias_old, const char *prefix, bool bindings_read_only); struct config; diff --git a/libmultipath/config.c b/libmultipath/config.c index 7b2075902..b7dbc6f56 100644 --- a/libmultipath/config.c +++ b/libmultipath/config.c @@ -752,15 +752,6 @@ static void _uninit_config(struct config *conf) if (conf->hwhandler) free(conf->hwhandler); - if (conf->bindings_file) - free(conf->bindings_file); - - if (conf->wwids_file) - free(conf->wwids_file); - - if (conf->prkeys_file) - free(conf->prkeys_file); - if (conf->prio_name) free(conf->prio_name); @@ -922,9 +913,6 @@ int _init_config (const char *file, struct config *conf) * internal defaults */ get_sys_max_fds(&conf->max_fds); - conf->bindings_file = set_default(DEFAULT_BINDINGS_FILE); - conf->wwids_file = set_default(DEFAULT_WWIDS_FILE); - conf->prkeys_file = set_default(DEFAULT_PRKEYS_FILE); conf->attribute_flags = 0; conf->reassign_maps = DEFAULT_REASSIGN_MAPS; conf->checkint = CHECKINT_UNDEF; @@ -1078,12 +1066,6 @@ int _init_config (const char *file, struct config *conf) merge_blacklist(conf->elist_wwid); merge_blacklist_device(conf->elist_device); - if (conf->bindings_file == NULL) - conf->bindings_file = set_default(DEFAULT_BINDINGS_FILE); - - if (!conf->bindings_file || !conf->wwids_file || !conf->prkeys_file) - goto out; - libmp_verbosity = conf->verbosity; return 0; out: diff --git a/libmultipath/config.h b/libmultipath/config.h index 0a2c297b2..8c22ce754 100644 --- a/libmultipath/config.h +++ b/libmultipath/config.h @@ -207,9 +207,6 @@ struct config { char * uid_attribute; char * features; char * hwhandler; - char * bindings_file; - char * wwids_file; - char * prkeys_file; char * prio_name; char * prio_args; char * checker_name; diff --git a/libmultipath/dict.c b/libmultipath/dict.c index dace343d4..044067afb 100644 --- a/libmultipath/dict.c +++ b/libmultipath/dict.c @@ -168,27 +168,6 @@ set_arg_str(vector strvec, void *ptr, int count_idx, const char *file, return 0; } -static int -set_path(vector strvec, void *ptr, const char *file, int line_nr) -{ - char **str_ptr = (char **)ptr; - char *old_str = *str_ptr; - - *str_ptr = set_value(strvec); - if (!*str_ptr) { - free(old_str); - return 1; - } - if ((*str_ptr)[0] != '/'){ - condlog(1, "%s line %d, %s is not an absolute path. Ignoring", - file, line_nr, *str_ptr); - free(*str_ptr); - *str_ptr = old_str; - } else - free(old_str); - return 0; -} - static int set_str_noslash(vector strvec, void *ptr, const char *file, int line_nr) { @@ -831,15 +810,6 @@ declare_hw_snprint(user_friendly_names, print_yes_no_undef) declare_mp_handler(user_friendly_names, set_yes_no_undef) declare_mp_snprint(user_friendly_names, print_yes_no_undef) -declare_def_warn_handler(bindings_file, set_path) -declare_def_snprint(bindings_file, print_str) - -declare_def_warn_handler(wwids_file, set_path) -declare_def_snprint(wwids_file, print_str) - -declare_def_warn_handler(prkeys_file, set_path) -declare_def_snprint(prkeys_file, print_str) - declare_def_handler(retain_hwhandler, set_yes_no_undef) declare_def_snprint_defint(retain_hwhandler, print_yes_no_undef, DEFAULT_RETAIN_HWHANDLER) @@ -2064,6 +2034,9 @@ declare_deprecated_handler(disable_changed_wwids, "yes") declare_deprecated_handler(getuid_callout, "(not set)") declare_deprecated_handler(multipath_dir, MULTIPATH_DIR) declare_deprecated_handler(pg_timeout, "(not set)") +declare_deprecated_handler(bindings_file, DEFAULT_BINDINGS_FILE) +declare_deprecated_handler(wwids_file, DEFAULT_WWIDS_FILE) +declare_deprecated_handler(prkeys_file, DEFAULT_PRKEYS_FILE) /* * If you add or remove a keyword also update multipath/multipath.conf.5 @@ -2106,9 +2079,9 @@ init_keywords(vector keywords) install_keyword("fast_io_fail_tmo", &def_fast_io_fail_handler, &snprint_def_fast_io_fail); install_keyword("dev_loss_tmo", &def_dev_loss_handler, &snprint_def_dev_loss); install_keyword("eh_deadline", &def_eh_deadline_handler, &snprint_def_eh_deadline); - install_keyword("bindings_file", &def_bindings_file_handler, &snprint_def_bindings_file); - install_keyword("wwids_file", &def_wwids_file_handler, &snprint_def_wwids_file); - install_keyword("prkeys_file", &def_prkeys_file_handler, &snprint_def_prkeys_file); + install_keyword("bindings_file", &deprecated_bindings_file_handler, &snprint_deprecated); + install_keyword("wwids_file", &deprecated_wwids_file_handler, &snprint_deprecated); + install_keyword("prkeys_file", &deprecated_prkeys_file_handler, &snprint_deprecated); install_keyword("log_checker_err", &def_log_checker_err_handler, &snprint_def_log_checker_err); install_keyword("reservation_key", &def_reservation_key_handler, &snprint_def_reservation_key); install_keyword("all_tg_pt", &def_all_tg_pt_handler, &snprint_def_all_tg_pt); diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version index 57e50c120..8368ef7ae 100644 --- a/libmultipath/libmultipath.version +++ b/libmultipath/libmultipath.version @@ -43,7 +43,7 @@ LIBMPATHCOMMON_1.0.0 { put_multipath_config; }; -LIBMULTIPATH_20.0.0 { +LIBMULTIPATH_21.0.0 { global: /* symbols referenced by multipath and multipathd */ add_foreign; @@ -121,6 +121,7 @@ global: get_used_hwes; get_vpd_sgio; group_by_prio; + handle_bindings_file_inotify; has_dm_info; init_checkers; init_config; @@ -238,8 +239,3 @@ global: local: *; }; - -LIBMULTIPATH_20.1.0 { -global: - handle_bindings_file_inotify; -}; diff --git a/libmultipath/prkey.c b/libmultipath/prkey.c index a215499d3..c66d293b9 100644 --- a/libmultipath/prkey.c +++ b/libmultipath/prkey.c @@ -157,8 +157,7 @@ static int do_prkey(int fd, char *wwid, char *keystr, int cmd) return 0; } -int get_prkey(struct config *conf, struct multipath *mpp, uint64_t *prkey, - uint8_t *sa_flags) +int get_prkey(struct multipath *mpp, uint64_t *prkey, uint8_t *sa_flags) { int fd; int unused; @@ -168,7 +167,7 @@ int get_prkey(struct config *conf, struct multipath *mpp, uint64_t *prkey, if (!strlen(mpp->wwid)) goto out; - fd = open_file(conf->prkeys_file, &unused, PRKEYS_FILE_HEADER); + fd = open_file(DEFAULT_PRKEYS_FILE, &unused, PRKEYS_FILE_HEADER); if (fd < 0) goto out; ret = do_prkey(fd, mpp->wwid, keystr, PRKEY_READ); @@ -201,7 +200,7 @@ int set_prkey(struct config *conf, struct multipath *mpp, uint64_t prkey, sa_flags &= MPATH_F_APTPL_MASK; } - fd = open_file(conf->prkeys_file, &can_write, PRKEYS_FILE_HEADER); + fd = open_file(DEFAULT_PRKEYS_FILE, &can_write, PRKEYS_FILE_HEADER); if (fd < 0) goto out; if (!can_write) { diff --git a/libmultipath/prkey.h b/libmultipath/prkey.h index a16de1065..43afd5e42 100644 --- a/libmultipath/prkey.h +++ b/libmultipath/prkey.h @@ -16,9 +16,8 @@ int print_reservation_key(struct strbuf *buff, struct be64 key, uint8_t flags, int source); int parse_prkey_flags(const char *ptr, uint64_t *prkey, uint8_t *flags); -int set_prkey(struct config *conf, struct multipath *mpp, uint64_t prkey, - uint8_t sa_flags); -int get_prkey(struct config *conf, struct multipath *mpp, uint64_t *prkey, - uint8_t *sa_flags); +int set_prkey(struct config *conf, struct multipath *mpp, + uint64_t prkey, uint8_t sa_flags); +int get_prkey(struct multipath *mpp, uint64_t *prkey, uint8_t *sa_flags); #endif /* _PRKEY_H */ diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c index 354e883f3..44241e2a5 100644 --- a/libmultipath/propsel.c +++ b/libmultipath/propsel.c @@ -401,8 +401,7 @@ int select_alias(struct config *conf, struct multipath * mp) select_alias_prefix(conf, mp); - mp->alias = get_user_friendly_alias(mp->wwid, conf->bindings_file, - mp->alias_old, mp->alias_prefix, + mp->alias = get_user_friendly_alias(mp->wwid, mp->alias_old, mp->alias_prefix, conf->bindings_read_only); if (mp->alias && !strncmp(mp->alias, mp->alias_old, WWID_SIZE)) @@ -992,7 +991,7 @@ int select_reservation_key(struct config *conf, struct multipath *mp) out: if (mp->prkey_source == PRKEY_SOURCE_FILE) { from_file = " (from prkeys file)"; - if (get_prkey(conf, mp, &prkey, &mp->sa_flags) != 0) + if (get_prkey(mp, &prkey, &mp->sa_flags) != 0) put_be64(mp->reservation_key, 0); else put_be64(mp->reservation_key, prkey); diff --git a/libmultipath/wwids.c b/libmultipath/wwids.c index 89bb60caa..591cd09b5 100644 --- a/libmultipath/wwids.c +++ b/libmultipath/wwids.c @@ -94,12 +94,8 @@ replace_wwids(vector mp) struct multipath * mpp; size_t len; int ret = -1; - struct config *conf; - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); - fd = open_file(conf->wwids_file, &can_write, WWIDS_FILE_HEADER); - pthread_cleanup_pop(1); + fd = open_file(DEFAULT_WWIDS_FILE, &can_write, WWIDS_FILE_HEADER); if (fd < 0) goto out; @@ -200,7 +196,6 @@ remove_wwid(char *wwid) { int len, can_write; char *str; int ret = -1; - struct config *conf; len = strlen(wwid) + 4; /* two slashes the newline and a zero byte */ str = malloc(len); @@ -216,10 +211,7 @@ remove_wwid(char *wwid) { goto out; } condlog(3, "removing line '%s' from wwids file", str); - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); - fd = open_file(conf->wwids_file, &can_write, WWIDS_FILE_HEADER); - pthread_cleanup_pop(1); + fd = open_file(DEFAULT_WWIDS_FILE, &can_write, WWIDS_FILE_HEADER); if (fd < 0) { ret = -1; @@ -244,12 +236,8 @@ check_wwids_file(char *wwid, int write_wwid) { int fd, can_write, found, ret; FILE *f; - struct config *conf; - conf = get_multipath_config(); - pthread_cleanup_push(put_multipath_config, conf); - fd = open_file(conf->wwids_file, &can_write, WWIDS_FILE_HEADER); - pthread_cleanup_pop(1); + fd = open_file(DEFAULT_WWIDS_FILE, &can_write, WWIDS_FILE_HEADER); if (fd < 0) return -1; diff --git a/multipath/main.c b/multipath/main.c index b91289e8b..9e1c5052b 100644 --- a/multipath/main.c +++ b/multipath/main.c @@ -856,7 +856,7 @@ main (int argc, char *argv[]) libmp_verbosity = atoi(optarg); break; case 'b': - conf->bindings_file = strdup(optarg); + condlog(1, "option -b ignored"); break; case 'B': conf->bindings_read_only = 1; diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in index 20df2232f..d320a88f3 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -794,35 +794,28 @@ The default is: \fB\fR . .TP .B bindings_file -(Deprecated) This option is deprecated, and will be removed in a future release. -The full pathname of the binding file to be used when the user_friendly_names -option is set. +(Deprecated) This option is not supported any more, and will be ignored. .RS .TP -The default is: \fB@STATE_DIR@/bindings\fR +The compiled-in value is: \fB@STATE_DIR@/bindings\fR .RE . . .TP .B wwids_file -(Deprecated) This option is deprecated, and will be removed in a future release. -The full pathname of the WWIDs file, which is used by multipath to keep track -of the WWIDs for LUNs it has created multipath devices on in the past. +(Deprecated) This option is not supported any more, and will be ignored. .RS .TP -The default is: \fB@STATE_DIR@/wwids\fR +The compiled-in value is: \fB@STATE_DIR@/wwids\fR .RE . . .TP .B prkeys_file -(Deprecated) This option is deprecated, and will be removed in a future release. -The full pathname of the prkeys file, which is used by multipathd to keep -track of the persistent reservation key used for a specific WWID, when -\fIreservation_key\fR is set to \fBfile\fR. +(Deprecated) This option is not supported any more, and will be ignored. .RS .TP -The default is: \fB@STATE_DIR@/prkeys\fR +The compiled-in value is: \fB@STATE_DIR@/prkeys\fR .RE . . @@ -989,6 +982,10 @@ The default is: \fB\fR .TP .B config_dir (Deprecated) This option is not supported any more, and the value is ignored. +.RS +.TP +The compiled-in value is: \fB@CONFIGDIR@\fR +.RE . . .TP diff --git a/multipathd/uxlsnr.c b/multipathd/uxlsnr.c index d1f8f2343..4d6f258c9 100644 --- a/multipathd/uxlsnr.c +++ b/multipathd/uxlsnr.c @@ -203,7 +203,6 @@ static void reset_watch(int notify_fd, struct watch_descriptors *wds, int dir_reset = 0; int conf_reset = 0; int mp_reset = 0; - char *bindings_file __attribute__((cleanup(cleanup_charp))) = NULL; if (notify_fd == -1) return; @@ -221,7 +220,6 @@ static void reset_watch(int notify_fd, struct watch_descriptors *wds, if (wds->mp_wd == -1) mp_reset = 1; } - bindings_file = strdup(conf->bindings_file); put_multipath_config(conf); if (dir_reset) { @@ -242,17 +240,12 @@ static void reset_watch(int notify_fd, struct watch_descriptors *wds, if (wds->conf_wd == -1) condlog(3, "didn't set up notifications on /etc/multipath.conf: %m"); } - if (mp_reset && bindings_file) { - char *slash = strrchr(bindings_file, '/'); - - if (slash && slash > bindings_file) { - *slash = '\0'; - wds->mp_wd = inotify_add_watch(notify_fd, bindings_file, - IN_MOVED_TO|IN_ONLYDIR); - if (wds->mp_wd == -1) + if (mp_reset) { + wds->mp_wd = inotify_add_watch(notify_fd, STATE_DIR, + IN_MOVED_TO|IN_ONLYDIR); + if (wds->mp_wd == -1) condlog(3, "didn't set up notifications on %s: %m", - bindings_file); - } + STATE_DIR); } } diff --git a/tests/alias.c b/tests/alias.c index 94df36d82..f893d1740 100644 --- a/tests/alias.c +++ b/tests/alias.c @@ -1264,10 +1264,10 @@ static void al_a(void **state) will_return(__wrap_write, ln); will_return(__wrap_write, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); will_return(__wrap_rename, 0); - expect_condlog(1, "updated bindings file foo"); + expect_condlog(1, "updated bindings file " DEFAULT_BINDINGS_FILE); expect_condlog(3, NEW_STR("MPATHa", "WWIDa")); - alias = allocate_binding("foo", "WWIDa", 1, "MPATH"); + alias = allocate_binding("WWIDa", 1, "MPATH"); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHa"); check_bindings_size(1); @@ -1283,10 +1283,10 @@ static void al_zz(void **state) will_return(__wrap_write, ln); will_return(__wrap_write, strlen(BINDINGS_FILE_HEADER) + strlen(ln)); will_return(__wrap_rename, 0); - expect_condlog(1, "updated bindings file foo"); + expect_condlog(1, "updated bindings file " DEFAULT_BINDINGS_FILE); expect_condlog(3, NEW_STR("MPATHzz", "WWIDzz")); - alias = allocate_binding("foo", "WWIDzz", 26*26 + 26, "MPATH"); + alias = allocate_binding("WWIDzz", 26*26 + 26, "MPATH"); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHzz"); check_bindings_size(1); @@ -1298,7 +1298,7 @@ static void al_0(void **state) char *alias; expect_condlog(0, "allocate_binding: cannot allocate new binding for id 0\n"); - alias = allocate_binding(0, "WWIDa", 0, "MPATH"); + alias = allocate_binding("WWIDa", 0, "MPATH"); assert_ptr_equal(alias, NULL); check_bindings_size(0); } @@ -1308,7 +1308,7 @@ static void al_m2(void **state) char *alias; expect_condlog(0, "allocate_binding: cannot allocate new binding for id -2\n"); - alias = allocate_binding(0, "WWIDa", -2, "MPATH"); + alias = allocate_binding("WWIDa", -2, "MPATH"); assert_ptr_equal(alias, NULL); check_bindings_size(0); } @@ -1325,10 +1325,10 @@ static void al_write_partial(void **state) will_return(__wrap_write, ln + sizeof(ln) - 2); will_return(__wrap_write, 1); will_return(__wrap_rename, 0); - expect_condlog(1, "updated bindings file foo"); + expect_condlog(1, "updated bindings file " DEFAULT_BINDINGS_FILE); expect_condlog(3, "Created new binding [MPATHa] for WWID [WWIDa]\n"); - alias = allocate_binding("foo", "WWIDa", 1, "MPATH"); + alias = allocate_binding("WWIDa", 1, "MPATH"); assert_ptr_not_equal(alias, NULL); assert_string_equal(alias, "MPATHa"); check_bindings_size(1); @@ -1350,7 +1350,7 @@ static void al_write_short(void **state) expect_condlog(1, "failed to write new bindings file"); expect_condlog(1, "allocate_binding: deleting binding MPATHa for WWIDa"); - alias = allocate_binding("foo", "WWIDa", 1, "MPATH"); + alias = allocate_binding("WWIDa", 1, "MPATH"); assert_ptr_equal(alias, NULL); check_bindings_size(0); } @@ -1366,7 +1366,7 @@ static void al_write_err(void **state) expect_condlog(1, "failed to write new bindings file"); expect_condlog(1, "allocate_binding: deleting binding MPATHa for WWIDa"); - alias = allocate_binding("foo", "WWIDa", 1, "MPATH"); + alias = allocate_binding("WWIDa", 1, "MPATH"); assert_ptr_equal(alias, NULL); check_bindings_size(0); } @@ -1383,7 +1383,7 @@ static void al_rename_err(void **state) expect_condlog(0, "update_bindings_file: rename: Read-only file system"); expect_condlog(1, "allocate_binding: deleting binding MPATHa for WWIDa"); - alias = allocate_binding("foo", "WWIDa", 1, "MPATH"); + alias = allocate_binding("WWIDa", 1, "MPATH"); assert_ptr_equal(alias, NULL); check_bindings_size(0); } @@ -1415,7 +1415,7 @@ static int test_allocate_binding(void) strlen(BINDINGS_FILE_HEADER) + (len) + strlen(ln)); \ will_return(__wrap_rename, err); \ if (err == 0) { \ - expect_condlog(1, "updated bindings file x\n"); \ + expect_condlog(1, "updated bindings file " DEFAULT_BINDINGS_FILE); \ expect_condlog(3, NEW_STR(alias, wwid)); \ } else { \ expect_condlog(0, "update_bindings_file: rename: " msg "\n"); \ @@ -1441,7 +1441,7 @@ static void gufa_empty_new_rw(void **state) { expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_allocate_binding("MPATHa", "WWID0"); - alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", false); + alias = get_user_friendly_alias("WWID0", "", "MPATH", false); assert_string_equal(alias, "MPATHa"); free(alias); } @@ -1454,7 +1454,7 @@ static void gufa_empty_new_ro_1(void **state) { expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_allocate_binding_err("MPATHa", "WWID0", -EROFS, "Read-only file system"); - alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", false); + alias = get_user_friendly_alias("WWID0", "", "MPATH", false); assert_ptr_equal(alias, NULL); } @@ -1465,7 +1465,7 @@ static void gufa_empty_new_ro_2(void **state) { expect_condlog(3, NOMATCH_WWID_STR("WWID0")); mock_unused_alias("MPATHa"); - alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", true); + alias = get_user_friendly_alias("WWID0", "", "MPATH", true); assert_ptr_equal(alias, NULL); } @@ -1477,7 +1477,7 @@ static void gufa_match_a_unused(void **state) { mock_unused_alias("MPATHa"); expect_condlog(3, EXISTING_STR("MPATHa", "WWID0")); - alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", true); + alias = get_user_friendly_alias("WWID0", "", "MPATH", true); assert_string_equal(alias, "MPATHa"); free(alias); } @@ -1490,7 +1490,7 @@ static void gufa_match_a_self(void **state) { mock_self_alias("MPATHa", "WWID0"); expect_condlog(3, EXISTING_STR("MPATHa", "WWID0")); - alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", true); + alias = get_user_friendly_alias("WWID0", "", "MPATH", true); assert_string_equal(alias, "MPATHa"); free(alias); } @@ -1503,7 +1503,7 @@ static void gufa_match_a_used(void **state) { expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_used_alias("MPATHa", "WWID0"); - alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", true); + alias = get_user_friendly_alias("WWID0", "", "MPATH", true); assert_ptr_equal(alias, NULL); } @@ -1518,7 +1518,7 @@ static void gufa_nomatch_a_c(void **state) { mock_allocate_binding_len("MPATHb", "WWID1", strlen(bindings)); - alias = get_user_friendly_alias("WWID1", "x", "", "MPATH", false); + alias = get_user_friendly_alias("WWID1", "", "MPATH", false); assert_string_equal(alias, "MPATHb"); free(alias); } @@ -1534,7 +1534,7 @@ static void gufa_nomatch_c_a(void **state) { mock_allocate_binding_len("MPATHb", "WWID1", sizeof(bindings) - 1); - alias = get_user_friendly_alias("WWID1", "x", "", "MPATH", false); + alias = get_user_friendly_alias("WWID1", "", "MPATH", false); assert_string_equal(alias, "MPATHb"); free(alias); } @@ -1550,7 +1550,7 @@ static void gufa_nomatch_c_b(void **state) { mock_allocate_binding_len("MPATHa", "WWID0", sizeof(bindings) - 1); - alias = get_user_friendly_alias("WWID0", "x", "", "MPATH", false); + alias = get_user_friendly_alias("WWID0", "", "MPATH", false); assert_string_equal(alias, "MPATHa"); free(alias); } @@ -1567,7 +1567,7 @@ static void gufa_nomatch_c_b_used(void **state) { mock_allocate_binding_len("MPATHd", "WWID4", sizeof(bindings) - 1); - alias = get_user_friendly_alias("WWID4", "x", "", "MPATH", false); + alias = get_user_friendly_alias("WWID4", "", "MPATH", false); assert_string_equal(alias, "MPATHd"); free(alias); } @@ -1584,7 +1584,7 @@ static void gufa_nomatch_b_f_a(void **state) { mock_allocate_binding_len("MPATHc", "WWID7", sizeof(bindings) - 1); - alias = get_user_friendly_alias("WWID7", "x", "", "MPATH", false); + alias = get_user_friendly_alias("WWID7", "", "MPATH", false); assert_string_equal(alias, "MPATHc"); free(alias); } @@ -1599,7 +1599,7 @@ static void gufa_nomatch_b_aa_a(void **state) { mock_unused_alias("MPATHab"); mock_allocate_binding_len("MPATHab", "WWID28", get_strbuf_len(&buf)); - alias = get_user_friendly_alias("WWID28", "x", "", "MPATH", false); + alias = get_user_friendly_alias("WWID28", "", "MPATH", false); assert_string_equal(alias, "MPATHab"); free(alias); } @@ -1616,7 +1616,7 @@ static void gufa_nomatch_b_f_a_sorted(void **state) { mock_allocate_binding_len("MPATHc", "WWID7", sizeof(bindings) - 1); - alias = get_user_friendly_alias("WWID7", "x", "", "MPATH", false); + alias = get_user_friendly_alias("WWID7", "", "MPATH", false); assert_string_equal(alias, "MPATHc"); free(alias); } @@ -1632,7 +1632,7 @@ static void gufa_old_empty(void **state) { mock_allocate_binding("MPATHz", "WWID0"); expect_condlog(2, ALLOC_STR("MPATHz", "WWID0")); - alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); + alias = get_user_friendly_alias("WWID0", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHz"); free(alias); } @@ -1644,7 +1644,7 @@ static void gufa_old_match(void **state) { "MPATHz WWID0"); expect_condlog(3, FOUND_ALIAS_STR("MPATHz", "WWID0")); - alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); + alias = get_user_friendly_alias("WWID0", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHz"); free(alias); } @@ -1661,7 +1661,7 @@ static void gufa_old_match_other(void **state) { mock_allocate_binding_len("MPATHa", "WWID0", sizeof(bindings) - 1); - alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); + alias = get_user_friendly_alias("WWID0", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHa"); free(alias); } @@ -1678,7 +1678,7 @@ static void gufa_old_match_other_used(void **state) { mock_unused_alias("MPATHb"); mock_allocate_binding_len("MPATHb", "WWID0", sizeof(bindings) - 1); - alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); + alias = get_user_friendly_alias("WWID0", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHb"); free(alias); } @@ -1695,7 +1695,7 @@ static void gufa_old_match_other_wwidmatch(void **state) { mock_unused_alias("MPATHc"); expect_condlog(3, EXISTING_STR("MPATHc", "WWID2")); - alias = get_user_friendly_alias("WWID2", "x", "MPATHz", "MPATH", false); + alias = get_user_friendly_alias("WWID2", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHc"); free(alias); } @@ -1711,7 +1711,7 @@ static void gufa_old_match_other_wwidmatch_used(void **state) { expect_condlog(3, FOUND_STR("MPATHc", "WWID2")); mock_used_alias("MPATHc", "WWID2"); - alias = get_user_friendly_alias("WWID2", "x", "MPATHz", "MPATH", false); + alias = get_user_friendly_alias("WWID2", "MPATHz", "MPATH", false); assert_ptr_equal(alias, NULL); } @@ -1725,7 +1725,7 @@ static void gufa_old_nomatch_wwidmatch(void **state) { mock_unused_alias("MPATHa"); expect_condlog(3, EXISTING_STR("MPATHa", "WWID0")); - alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); + alias = get_user_friendly_alias("WWID0", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHa"); free(alias); } @@ -1739,7 +1739,7 @@ static void gufa_old_nomatch_wwidmatch_used(void **state) { expect_condlog(3, FOUND_STR("MPATHa", "WWID0")); mock_used_alias("MPATHa", "WWID0"); - alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); + alias = get_user_friendly_alias("WWID0", "MPATHz", "MPATH", false); assert_ptr_equal(alias, NULL); } @@ -1754,7 +1754,7 @@ static void gufa_old_nomatch_nowwidmatch(void **state) { mock_allocate_binding_len("MPATHz", "WWID0", sizeof(bindings) - 1); expect_condlog(2, ALLOC_STR("MPATHz", "WWID0")); - alias = get_user_friendly_alias("WWID0", "x", "MPATHz", "MPATH", false); + alias = get_user_friendly_alias("WWID0", "MPATHz", "MPATH", false); assert_string_equal(alias, "MPATHz"); free(alias); } From 56a82134061295f8ecf17b15da83334c730e8f2e Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Tue, 12 Sep 2023 11:54:47 +0200 Subject: [PATCH 38/64] libmultipath: avoid -Warray-bounds error in uatomic operations The use of uatomic_xchg() in alias.c causes a -Warray-bounds error on distributions using gcc 12, such as Fedora 37. This is a similar error to 2534c4f ("libmultipath: avoid -Warray-bounds error with gcc 12 and musl libc"). This happens only with liburcu 0.13 and earlier, and only with certain gcc versions. See liburcu commit 835b9ab ("Fix: x86 and s390 uatomic: __hp() macro warning with gcc 11"). Enhance the fix for 2534c4f by a adding a workaround for uatomic_xchg(), and introduce the macro URCU_VERSION (originally only used for multipathd) globally. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- Makefile.inc | 2 +- create-config.mk | 5 +++++ libmultipath/alias.c | 5 +++-- libmultipath/lock.h | 21 +++++++++++++-------- multipathd/Makefile | 2 -- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Makefile.inc b/Makefile.inc index 6e384e686..04bfa56ec 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -95,7 +95,7 @@ OPTFLAGS := -O2 -g $(STACKPROT) --param=ssp-buffer-size=4 WARNFLAGS := -Werror -Wall -Wextra -Wformat=2 $(WFORMATOVERFLOW) -Werror=implicit-int \ -Werror=implicit-function-declaration -Werror=format-security \ $(WNOCLOBBERED) -Werror=cast-qual $(ERROR_DISCARDED_QUALIFIERS) $(W_URCU_TYPE_LIMITS) -CPPFLAGS := $(FORTIFY_OPT) $(CPPFLAGS) \ +CPPFLAGS := $(FORTIFY_OPT) $(CPPFLAGS) $(D_URCU_VERSION) \ -DBIN_DIR=\"$(bindir)\" -DMULTIPATH_DIR=\"$(plugindir)\" \ -DRUNTIME_DIR=\"$(runtimedir)\" -DCONFIG_DIR=\"$(configdir)\" \ -DDEFAULT_CONFIGFILE=\"$(configfile)\" -DSTATE_DIR=\"$(statedir)\" \ diff --git a/create-config.mk b/create-config.mk index d1255971a..4d318b964 100644 --- a/create-config.mk +++ b/create-config.mk @@ -73,6 +73,10 @@ TEST_URCU_TYPE_LIMITS = $(shell \ $(CC) -c -Werror=type-limits -o /dev/null -xc - 2>/dev/null \ || echo -Wno-type-limits ) +URCU_VERSION = $(shell \ + $(PKG_CONFIG) --modversion liburcu 2>/dev/null | \ + awk -F. '{ printf("-DURCU_VERSION=0x%06x", 256 * ( 256 * $$1 + $$2) + $$3); }') + DEFINES := ifneq ($(call check_func,dm_task_no_flush,$(devmapper_incdir)/libdevmapper.h),0) @@ -168,6 +172,7 @@ $(TOPDIR)/config.mk: $(multipathdir)/autoconfig.h @echo creating $@ @echo "FPIN_SUPPORT := $(FPIN_SUPPORT)" >$@ @echo "FORTIFY_OPT := $(FORTIFY_OPT)" >>$@ + @echo "D_URCU_VERSION := $(call URCU_VERSION)" >>$@ @echo "SYSTEMD := $(SYSTEMD)" >>$@ @echo "ANA_SUPPORT := $(ANA_SUPPORT)" >>$@ @echo "STACKPROT := $(call TEST_CC_OPTION,-fstack-protector-strong,-fstack-protector)" >>$@ diff --git a/libmultipath/alias.c b/libmultipath/alias.c index e5d3f1512..74431f3ff 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -24,6 +24,7 @@ #include "devmapper.h" #include "strbuf.h" #include "time-util.h" +#include "lock.h" /* * significant parts of this file were taken from iscsi-bindings.c of the @@ -300,7 +301,7 @@ void handle_bindings_file_inotify(const struct inotify_event *event) pthread_mutex_unlock(×tamp_mutex); if (changed) { - uatomic_xchg(&bindings_file_changed, 1); + uatomic_xchg_int(&bindings_file_changed, 1); condlog(3, "%s: bindings file must be re-read, new timestamp: %ld.%06ld", __func__, (long)ts.tv_sec, (long)ts.tv_nsec / 1000); } else @@ -775,7 +776,7 @@ static int _read_bindings_file(const struct config *conf, Bindings *bindings, int rc = 0, ret, fd; FILE *file; struct stat st; - int has_changed = uatomic_xchg(&bindings_file_changed, 0); + int has_changed = uatomic_xchg_int(&bindings_file_changed, 0); if (!force) { if (!has_changed) { diff --git a/libmultipath/lock.h b/libmultipath/lock.h index 9814be76f..ac80d1d85 100644 --- a/libmultipath/lock.h +++ b/libmultipath/lock.h @@ -13,15 +13,20 @@ struct mutex_lock { int waiters; /* uatomic access only */ }; -#if !defined(__GLIBC__) && defined(__GNUC__) && __GNUC__ == 12 +static inline void init_lock(struct mutex_lock *a) +{ + pthread_mutex_init(&a->mutex, NULL); + uatomic_set(&a->waiters, 0); +} + +#if defined(__GNUC__) && __GNUC__ == 12 && URCU_VERSION < 0xe00 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Warray-bounds" #endif -static inline void init_lock(struct mutex_lock *a) +static inline int uatomic_xchg_int(int *ptr, int val) { - pthread_mutex_init(&a->mutex, NULL); - uatomic_set(&a->waiters, 0); + return uatomic_xchg(ptr, val); } static inline void lock(struct mutex_lock *a) @@ -31,6 +36,10 @@ static inline void lock(struct mutex_lock *a) uatomic_dec(&a->waiters); } +#if defined(__GNUC__) && __GNUC__ == 12 && URCU_VERSION < 0xe00 +#pragma GCC diagnostic pop +#endif + static inline int trylock(struct mutex_lock *a) { return pthread_mutex_trylock(&a->mutex); @@ -51,10 +60,6 @@ static inline bool lock_has_waiters(struct mutex_lock *a) return (uatomic_read(&a->waiters) > 0); } -#if !defined(__GLIBC__) && defined(__GNUC__) && __GNUC__ == 12 -#pragma GCC diagnostic pop -#endif - #define lock_cleanup_pop(a) pthread_cleanup_pop(1) void cleanup_lock (void * data); diff --git a/multipathd/Makefile b/multipathd/Makefile index cdba3db17..0ba6ecb78 100644 --- a/multipathd/Makefile +++ b/multipathd/Makefile @@ -5,8 +5,6 @@ CLI := multipathc MANPAGES := multipathd.8 CPPFLAGS += -I$(multipathdir) -I$(mpathutildir) -I$(mpathpersistdir) -I$(mpathcmddir) -I$(thirdpartydir) \ - $(shell $(PKG_CONFIG) --modversion liburcu 2>/dev/null | \ - awk -F. '{ printf("-DURCU_VERSION=0x%06x", 256 * ( 256 * $$1 + $$2) + $$3); }') \ -DBINDIR='"$(bindir)"' $(SYSTEMD_CPPFLAGS) # From 7a108462387673505870da28f41e9808e092c1ce Mon Sep 17 00:00:00 2001 From: Xose Vazquez Perez Date: Fri, 15 Sep 2023 22:22:06 +0200 Subject: [PATCH 39/64] multipath-tools: fix spelling Cc: Martin Wilck Cc: Benjamin Marzinski Cc: Christophe Varoqui Cc: DM-DEVEL ML Signed-off-by: Xose Vazquez Perez Reviewed-by: Martin Wilck --- README.md | 4 ++-- multipath/multipath.conf.5.in | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 679e55bff..524c9fb1a 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ The following variables can be passed to the `make` command line: The default is `$(prefix)/$(LIB)/multipath`, where `$(LIB)` is `lib64` on systems that have `/lib64`, and `lib` otherwise. * `configfile="/some/path`": The path to the main configuration file. - The defalt is `$(etc_prefix)/etc/multipath.conf`. + The default is `$(etc_prefix)/etc/multipath.conf`. * `configdir="/some/path"` : directory to search for additional configuration files. This used to be the run-time option `config_dir` in earlier versions. The default is `$(etc_prefix)/etc/multipath/conf.d`. @@ -141,7 +141,7 @@ The following variables can be passed to the `make` command line: found on the build system, and `/lib` otherwise. The options `configdir`, `plugindir`, `configfile`, and `statedir` above can -be used for setting indvidual paths where the `prefix` variables don't provide +be used for setting individual paths where the `prefix` variables don't provide sufficient control. See `Makefile.inc` for even more fine-grained control. [^systemd]: Some systemd installations use separate `prefix` and `rootprefix`. diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in index d320a88f3..226d00196 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -36,7 +36,7 @@ Files ending in \fI.conf\fR in this directory are read in alphabetical order, after reading \fI@CONFIGFILE@\fR. They use the same syntax as \fI@CONFIGFILE@\fR itself, and support all sections and keywords. If a keyword occurs in the same section -in multiple files, the last occurence will take precedence over all others. +in multiple files, the last occurrence will take precedence over all others. . . .\" ---------------------------------------------------------------------------- From 1cada7780ff84d0eabc51a1ddfb80beff8a45ee7 Mon Sep 17 00:00:00 2001 From: Muneendra Date: Wed, 20 Sep 2023 20:41:15 -0700 Subject: [PATCH 40/64] multipathd: Added support to handle FPIN-Li events for FC-NVMe This patch adds the support to handle FPIN-Li for FC-NVMe. On receiving the FPIN-Li events this patch moves the devices paths which are affected due to link integrity to marginal path groups. The paths which are set to marginal path group will be unset on receiving the RSCN events (mwilck: minor compile fix for 32-bit architectures) Signed-off-by: Muneendra Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- multipathd/fpin_handlers.c | 206 +++++++++++++++++++++++++++---------- 1 file changed, 151 insertions(+), 55 deletions(-) diff --git a/multipathd/fpin_handlers.c b/multipathd/fpin_handlers.c index aa0f63c97..be087ca06 100644 --- a/multipathd/fpin_handlers.c +++ b/multipathd/fpin_handlers.c @@ -60,18 +60,15 @@ static void _udev_device_unref(void *p) /*set/unset the path state to marginal*/ -static int fpin_set_pathstate(struct path *pp, bool set) +static void fpin_set_pathstate(struct path *pp, bool set) { const char *action = set ? "set" : "unset"; - if (!pp || !pp->mpp || !pp->mpp->alias) - return -1; - - condlog(3, "\n%s: %s marginal path %s (fpin)", - action, pp->mpp->alias, pp->dev_t); + condlog(3, "%s: %s marginal path %s (fpin)", + pp->mpp ? pp->mpp->alias : "orphan", action, pp->dev_t); pp->marginal = set; - pp->mpp->fpin_must_reload = true; - return 0; + if (pp->mpp) + pp->mpp->fpin_must_reload = true; } /* This will unset marginal state of a device*/ @@ -82,14 +79,14 @@ static void fpin_path_unsetmarginal(char *devname, struct vectors *vecs) pp = find_path_by_dev(vecs->pathvec, devname); if (!pp) pp = find_path_by_devt(vecs->pathvec, devname); - - fpin_set_pathstate(pp, false); + if (pp) + fpin_set_pathstate(pp, false); } /*This will set the marginal state of a device*/ -static int fpin_path_setmarginal(struct path *pp) +static void fpin_path_setmarginal(struct path *pp) { - return fpin_set_pathstate(pp, true); + fpin_set_pathstate(pp, true); } /* Unsets all the devices in the list from marginal state */ @@ -183,8 +180,8 @@ static void fpin_set_rport_marginal(struct udev_device *rport_dev) udev_device_get_syspath(rport_dev)); } -/*Add the marginal devices info into the list*/ -static void +/*Add the marginal devices info into the list and return 0 on success*/ +static int fpin_add_marginal_dev_info(uint32_t host_num, char *devname) { struct marginal_dev_list *newdev = NULL; @@ -199,65 +196,160 @@ fpin_add_marginal_dev_info(uint32_t host_num, char *devname) list_add_tail(&(newdev->node), &fpin_li_marginal_dev_list_head); pthread_mutex_unlock(&fpin_li_marginal_dev_mutex); - } + } else + return -ENOMEM; + return 0; } /* - * This function goes through the vecs->pathvec, and for - * each path, check that the host number, - * the target WWPN associated with the path matches - * with the els wwpn and sets the path and port state to + * This function compares Transport Address Controller Port pn, + * Host Transport Address Controller Port pn with the els wwpn ,attached_wwpn + * and return 1 (match) or 0 (no match) or a negative error code + */ +static int extract_nvme_addresses_chk_path_pwwn(const char *address, + uint64_t els_wwpn, uint64_t els_attached_wwpn) + +{ + uint64_t traddr; + uint64_t host_traddr; + + /* + * Find the position of "traddr=" and "host_traddr=" + * and the address will be in the below format + * "traddr=nn-0x200400110dff9400:pn-0x200400110dff9400, + * host_traddr=nn-0x200400110dff9400:pn-0x200400110dff9400" + */ + const char *traddr_start = strstr(address, "traddr="); + const char *host_traddr_start = strstr(address, "host_traddr="); + + if (!traddr_start || !host_traddr_start) + return -EINVAL; + + /* Extract traddr pn */ + if (sscanf(traddr_start, "traddr=nn-%*[^:]:pn-%" SCNx64, &traddr) != 1) + return -EINVAL; + + /* Extract host_traddr pn*/ + if (sscanf(host_traddr_start, "host_traddr=nn-%*[^:]:pn-%" SCNx64, + &host_traddr) != 1) + return -EINVAL; + condlog(4, "traddr 0x%" PRIx64 " hosttraddr 0x%" PRIx64 " els_wwpn 0x%" + PRIx64" els_host_traddr 0x%" PRIx64, + traddr, host_traddr, + els_wwpn, els_attached_wwpn); + if ((host_traddr == els_attached_wwpn) && (traddr == els_wwpn)) + return 1; + return 0; +} + +/* + * This function check that the Transport Address Controller Port pn, + * Host Transport Address Controller Port pn associated with the path matches + * with the els wwpn ,attached_wwpn and sets the path state to * Marginal */ -static int fpin_chk_wwn_setpath_marginal(uint16_t host_num, struct vectors *vecs, +static void fpin_check_set_nvme_path_marginal(uint16_t host_num, struct path *pp, + uint64_t els_wwpn, uint64_t attached_wwpn) +{ + struct udev_device *ctl = NULL; + const char *address = NULL; + int ret = 0; + + ctl = udev_device_get_parent_with_subsystem_devtype(pp->udev, "nvme", NULL); + if (ctl == NULL) { + condlog(2, "%s: No parent device for ", pp->dev); + return; + } + address = udev_device_get_sysattr_value(ctl, "address"); + if (!address) { + condlog(2, "%s: unable to get the address ", pp->dev); + return; + } + condlog(4, "\n address %s: dev :%s\n", address, pp->dev); + ret = extract_nvme_addresses_chk_path_pwwn(address, els_wwpn, attached_wwpn); + if (ret <= 0) + return; + ret = fpin_add_marginal_dev_info(host_num, pp->dev); + if (ret < 0) + return; + fpin_path_setmarginal(pp); +} + +/* + * This function check the host number, the target WWPN + * associated with the path matches with the els wwpn and + * sets the path and port state to Marginal + */ +static void fpin_check_set_scsi_path_marginal(uint16_t host_num, struct path *pp, uint64_t els_wwpn) { - struct path *pp; - struct multipath *mpp; - int i, k; char rport_id[42]; const char *value = NULL; struct udev_device *rport_dev = NULL; uint64_t wwpn; int ret = 0; + sprintf(rport_id, "rport-%d:%d-%d", + pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.transport_id); + rport_dev = udev_device_new_from_subsystem_sysname(udev, + "fc_remote_ports", rport_id); + if (!rport_dev) { + condlog(2, "%s: No fc_remote_port device for '%s'", pp->dev, + rport_id); + return; + } + pthread_cleanup_push(_udev_device_unref, rport_dev); + value = udev_device_get_sysattr_value(rport_dev, "port_name"); + if (!value) + goto unref; + + wwpn = strtol(value, NULL, 16); + /* + * If the port wwpn matches sets the path and port state + * to marginal + */ + if (wwpn == els_wwpn) { + ret = fpin_add_marginal_dev_info(host_num, pp->dev); + if (ret < 0) + goto unref; + fpin_path_setmarginal(pp); + fpin_set_rport_marginal(rport_dev); + } +unref: + pthread_cleanup_pop(1); + return; + +} + +/* + * This function goes through the vecs->pathvec, and for + * each path, it checks and sets the path state to marginal + * if the path's associated port wwpn ,hostnum matches with + * els wwnpn ,attached_wwpn + */ +static int fpin_chk_wwn_setpath_marginal(uint16_t host_num, struct vectors *vecs, + uint64_t els_wwpn, uint64_t attached_wwpn) +{ + struct path *pp; + struct multipath *mpp; + int i, k; + int ret = 0; pthread_cleanup_push(cleanup_lock, &vecs->lock); lock(&vecs->lock); pthread_testcancel(); vector_foreach_slot(vecs->pathvec, pp, k) { - /* Checks the host number and also for the SCSI FCP */ - if (pp->bus != SYSFS_BUS_SCSI || pp->sg_id.proto_id != SCSI_PROTOCOL_FCP || host_num != pp->sg_id.host_no) + if (!pp->mpp) continue; - sprintf(rport_id, "rport-%d:%d-%d", - pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.transport_id); - rport_dev = udev_device_new_from_subsystem_sysname(udev, - "fc_remote_ports", rport_id); - if (!rport_dev) { - condlog(2, "%s: No fc_remote_port device for '%s'", pp->dev, - rport_id); - continue; - } - pthread_cleanup_push(_udev_device_unref, rport_dev); - value = udev_device_get_sysattr_value(rport_dev, "port_name"); - if (!value) - goto unref; - - if (value) - wwpn = strtol(value, NULL, 16); - /* - * If the port wwpn matches sets the path and port state - * to marginal - */ - if (wwpn == els_wwpn) { - ret = fpin_path_setmarginal(pp); - if (ret < 0) - goto unref; - fpin_set_rport_marginal(rport_dev); - fpin_add_marginal_dev_info(host_num, pp->dev); + /*checks if the bus type is nvme and the protocol is FC-NVMe*/ + if ((pp->bus == SYSFS_BUS_NVME) && (pp->sg_id.proto_id == NVME_PROTOCOL_FC)) { + fpin_check_set_nvme_path_marginal(host_num, pp, els_wwpn, attached_wwpn); + } else if ((pp->bus == SYSFS_BUS_SCSI) && + (pp->sg_id.proto_id == SCSI_PROTOCOL_FCP) && + (host_num == pp->sg_id.host_no)) { + /* Checks the host number and also for the SCSI FCP */ + fpin_check_set_scsi_path_marginal(host_num, pp, els_wwpn); } -unref: - pthread_cleanup_pop(1); } /* walk backwards because reload_and_sync_map() can remove mpp */ vector_foreach_slot_backwards(vecs->mpvec, mpp, i) { @@ -286,14 +378,18 @@ fpin_parse_li_els_setpath_marginal(uint16_t host_num, struct fc_tlv_desc *tlv, struct fc_fn_li_desc *li_desc = (struct fc_fn_li_desc *)tlv; int count = 0; int ret = 0; + uint64_t attached_wwpn; /* Update the wwn to list */ wwn_count = be32_to_cpu(li_desc->pname_count); - condlog(4, "Got wwn count as %d\n", wwn_count); + attached_wwpn = be64_to_cpu(li_desc->attached_wwpn); + condlog(4, "Got wwn count as %d detecting wwn 0x%" PRIx64 + " attached_wwpn 0x%" PRIx64 "\n", + wwn_count, be64_to_cpu(li_desc->detecting_wwpn), attached_wwpn); for (iter = 0; iter < wwn_count; iter++) { wwpn = be64_to_cpu(li_desc->pname_list[iter]); - ret = fpin_chk_wwn_setpath_marginal(host_num, vecs, wwpn); + ret = fpin_chk_wwn_setpath_marginal(host_num, vecs, wwpn, attached_wwpn); if (ret < 0) condlog(2, "failed to set the path marginal associated with wwpn: 0x%" PRIx64 "\n", wwpn); From d563eebe3b10a52755c7fe5bb0f14e44fdef4b20 Mon Sep 17 00:00:00 2001 From: Marvin Schmidt Date: Thu, 19 Oct 2023 14:17:16 +0200 Subject: [PATCH 41/64] libmpathutil: Remove parse_prkey symbol It was wrongly listed in libmpathutil's version script when it was split off from libmultipath in commit 4e1f20cf93a2 ("libmultipath: split off libmpathutil"), but was never part of libmpathutil LLD 17 failed to link libmpathutil due to this: ``` ld.lld: error: version script assignment of 'LIBMPATHUTIL_1.0' to symbol 'parse_prkey' failed: symbol not defined ``` Remove the `parse_prkey` symbol to fix this Reviewed-by: Martin Wilck --- libmpathutil/libmpathutil.version | 1 - 1 file changed, 1 deletion(-) diff --git a/libmpathutil/libmpathutil.version b/libmpathutil/libmpathutil.version index 68f5ab912..6ebb71867 100644 --- a/libmpathutil/libmpathutil.version +++ b/libmpathutil/libmpathutil.version @@ -113,7 +113,6 @@ LIBMPATHUTIL_1.0 { log_safe; msort; parse_devt; - parse_prkey; process_file; safe_write; set_value; From 49c95bc8966edf684671b12043e7cb25ed2a89dd Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 14 Sep 2023 16:22:11 +0200 Subject: [PATCH 42/64] libmultipath: reduce log level of directio messages The directio checker logs too much at verbosity 3. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/checkers/directio.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libmultipath/checkers/directio.c b/libmultipath/checkers/directio.c index 2f3ece00f..25b238390 100644 --- a/libmultipath/checkers/directio.c +++ b/libmultipath/checkers/directio.c @@ -266,7 +266,7 @@ get_events(struct aio_group *aio_grp, struct timespec *timeout) for (i = 0; i < nr; i++) { struct async_req *req = container_of(events[i].obj, struct async_req, io); - LOG(3, "io finished %lu/%lu", events[i].res, + LOG(4, "io finished %lu/%lu", events[i].res, events[i].res2); /* got an orphaned request */ @@ -283,7 +283,7 @@ get_events(struct aio_group *aio_grp, struct timespec *timeout) } while (nr == 128); /* assume there are more events and try again */ if (nr < 0) - LOG(3, "async io getevents returned %i (errno=%s)", + LOG(4, "async io getevents returned %i (errno=%s)", nr, strerror(errno)); return got_events; @@ -315,7 +315,7 @@ check_state(int fd, struct directio_context *ct, int sync, int timeout_secs) } else { struct iocb *ios[1] = { &ct->req->io }; - LOG(3, "starting new request"); + LOG(4, "starting new request"); memset(&ct->req->io, 0, sizeof(struct iocb)); io_prep_pread(&ct->req->io, fd, ct->req->buf, ct->req->blksize, 0); @@ -360,7 +360,7 @@ check_state(int fd, struct directio_context *ct, int sync, int timeout_secs) ct->running = 0; rc = PATH_DOWN; } else { - LOG(3, "async io pending"); + LOG(4, "async io pending"); rc = PATH_PENDING; } From 77e4ecc721bcb4566305b549f5fb17f443818361 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Tue, 10 Oct 2023 21:55:36 +0200 Subject: [PATCH 43/64] libmultipath: directio: don't reset ct->running after io_cancel() io_cancel() never succeeds, and even if it did, io_getevents() must still be called to reclaim the IO resources from the kernel. Don't pretend the opposite by resetting ct->running, or freeing the memory, before io_getevents() has indicated that the request is finished. In the test code, don't bother about the return value of __wrap_io_cancel(). Signed-off-by: Martin Wilck Reviewed-by: Hannes Reinecke Reviewed-by: Benjamin Marzinski --- libmultipath/checkers/directio.c | 14 ++++---------- tests/directio.c | 27 ++------------------------- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/libmultipath/checkers/directio.c b/libmultipath/checkers/directio.c index 25b238390..83ab29f7e 100644 --- a/libmultipath/checkers/directio.c +++ b/libmultipath/checkers/directio.c @@ -232,15 +232,15 @@ void libcheck_free (struct checker * c) } } - if (ct->running && - (ct->req->state != PATH_PENDING || - io_cancel(ct->aio_grp->ioctx, &ct->req->io, &event) == 0)) + if (ct->running && ct->req->state != PATH_PENDING) ct->running = 0; if (!ct->running) { free(ct->req->buf); free(ct->req); ct->aio_grp->holders--; } else { + /* Currently a no-op */ + io_cancel(ct->aio_grp->ioctx, &ct->req->io, &event); ct->req->state = PATH_REMOVED; list_add(&ct->req->node, &ct->aio_grp->orphans); check_orphaned_group(ct->aio_grp); @@ -351,13 +351,7 @@ check_state(int fd, struct directio_context *ct, int sync, int timeout_secs) LOG(3, "abort check on timeout"); - r = io_cancel(ct->aio_grp->ioctx, &ct->req->io, &event); - /* - * Only reset ct->running if we really - * could abort the pending I/O - */ - if (!r) - ct->running = 0; + io_cancel(ct->aio_grp->ioctx, &ct->req->io, &event); rc = PATH_DOWN; } else { LOG(4, "async io pending"); diff --git a/tests/directio.c b/tests/directio.c index db9643e15..5201d21ae 100644 --- a/tests/directio.c +++ b/tests/directio.c @@ -141,10 +141,9 @@ int __real_io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt); int __wrap_io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt) { #ifdef DIO_TEST_DEV - mock_type(int); return __real_io_cancel(ctx, iocb, evt); #else - return mock_type(int); + return 0; #endif } @@ -439,14 +438,8 @@ static void test_check_state_timeout(void **state) do_libcheck_init(&c, 4096, NULL); aio_grp = get_aio_grp(&c); return_io_getevents_none(); - will_return(__wrap_io_cancel, 0); do_check_state(&c, 1, 30, PATH_DOWN); check_aio_grp(aio_grp, 1, 0); -#ifdef DIO_TEST_DEV - /* io_cancel will return negative value on timeout, so it happens again - * when freeing the checker */ - will_return(__wrap_io_cancel, 0); -#endif libcheck_free(&c); do_libcheck_reset(1); } @@ -468,12 +461,8 @@ static void test_check_state_async_timeout(void **state) return_io_getevents_none(); do_check_state(&c, 0, 3, PATH_PENDING); return_io_getevents_none(); - will_return(__wrap_io_cancel, 0); do_check_state(&c, 0, 3, PATH_DOWN); check_aio_grp(aio_grp, 1, 0); -#ifdef DIO_TEST_DEV - will_return(__wrap_io_cancel, 0); -#endif libcheck_free(&c); do_libcheck_reset(1); } @@ -501,13 +490,8 @@ static void test_free_with_pending(void **state) check_aio_grp(aio_grp, 2, 0); libcheck_free(&c[0]); check_aio_grp(aio_grp, 1, 0); - will_return(__wrap_io_cancel, 0); libcheck_free(&c[1]); -#ifdef DIO_TEST_DEV - check_aio_grp(aio_grp, 1, 1); /* real cancel doesn't remove request */ -#else - check_aio_grp(aio_grp, 0, 0); -#endif + check_aio_grp(aio_grp, 1, 1); /* cancel doesn't remove request */ do_libcheck_reset(1); } @@ -533,7 +517,6 @@ static void test_orphaned_aio_group(void **state) assert_int_equal(i, 1); for (i = 0; i < AIO_GROUP_SIZE; i++) { assert_true(is_checker_running(&c[i])); - will_return(__wrap_io_cancel, -1); if (i == AIO_GROUP_SIZE - 1) { /* remove the orphaned group and create a new one */ will_return(__wrap_io_destroy, 0); @@ -559,12 +542,10 @@ static void test_timeout_cancel_failed(void **state) do_libcheck_init(&c[i], 4096, &reqs[i]); aio_grp = get_aio_grp(c); return_io_getevents_none(); - will_return(__wrap_io_cancel, -1); do_check_state(&c[0], 1, 30, PATH_DOWN); assert_true(is_checker_running(&c[0])); check_aio_grp(aio_grp, 2, 0); return_io_getevents_none(); - will_return(__wrap_io_cancel, -1); do_check_state(&c[0], 1, 30, PATH_DOWN); assert_true(is_checker_running(&c[0])); return_io_getevents_nr(NULL, 1, &reqs[0], &res[0]); @@ -600,7 +581,6 @@ static void test_async_timeout_cancel_failed(void **state) return_io_getevents_none(); do_check_state(&c[1], 0, 2, PATH_PENDING); return_io_getevents_none(); - will_return(__wrap_io_cancel, -1); do_check_state(&c[0], 0, 2, PATH_DOWN); #ifndef DIO_TEST_DEV /* can't pick which even gets returned on real devices */ @@ -608,7 +588,6 @@ static void test_async_timeout_cancel_failed(void **state) do_check_state(&c[1], 0, 2, PATH_UP); #endif return_io_getevents_none(); - will_return(__wrap_io_cancel, -1); do_check_state(&c[0], 0, 2, PATH_DOWN); assert_true(is_checker_running(&c[0])); return_io_getevents_nr(NULL, 2, reqs, res); @@ -637,7 +616,6 @@ static void test_orphan_checker_cleanup(void **state) aio_grp = get_aio_grp(c); return_io_getevents_none(); do_check_state(&c[0], 0, 30, PATH_PENDING); - will_return(__wrap_io_cancel, -1); check_aio_grp(aio_grp, 2, 0); libcheck_free(&c[0]); check_aio_grp(aio_grp, 2, 1); @@ -662,7 +640,6 @@ static void test_orphan_reset_cleanup(void **state) orphan_aio_grp = get_aio_grp(&c); return_io_getevents_none(); do_check_state(&c, 0, 30, PATH_PENDING); - will_return(__wrap_io_cancel, -1); check_aio_grp(orphan_aio_grp, 1, 0); libcheck_free(&c); check_aio_grp(orphan_aio_grp, 1, 1); From 5bf3a159d34ae5f788d2e758f2335bccc0b485a8 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 26 Oct 2023 12:39:15 +0200 Subject: [PATCH 44/64] libmultipath: directio: fix error handling libaio uses a different error return convention than glibc. The error code is not returned in errno, but as the negated return value of the function. Adapt the directio checker code. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/checkers/directio.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/libmultipath/checkers/directio.c b/libmultipath/checkers/directio.c index 83ab29f7e..12b8be494 100644 --- a/libmultipath/checkers/directio.c +++ b/libmultipath/checkers/directio.c @@ -70,6 +70,7 @@ static struct aio_group * add_aio_group(void) { struct aio_group *aio_grp; + int rc; aio_grp = malloc(sizeof(struct aio_group)); if (!aio_grp) @@ -77,9 +78,9 @@ add_aio_group(void) memset(aio_grp, 0, sizeof(struct aio_group)); INIT_LIST_HEAD(&aio_grp->orphans); - if (io_setup(AIO_GROUP_SIZE, &aio_grp->ioctx) != 0) { + if ((rc = io_setup(AIO_GROUP_SIZE, &aio_grp->ioctx)) != 0) { LOG(1, "io_setup failed"); - if (errno == EAGAIN) + if (rc == -EAGAIN) LOG(1, "global number of io events too small. Increase fs.aio-max-nr with sysctl"); free(aio_grp); return NULL; @@ -259,7 +260,6 @@ get_events(struct aio_group *aio_grp, struct timespec *timeout) struct timespec *timep = timeout; do { - errno = 0; nr = io_getevents(aio_grp->ioctx, 1, 128, events, timep); got_events |= (nr > 0); @@ -283,8 +283,7 @@ get_events(struct aio_group *aio_grp, struct timespec *timeout) } while (nr == 128); /* assume there are more events and try again */ if (nr < 0) - LOG(4, "async io getevents returned %i (errno=%s)", - nr, strerror(errno)); + LOG(4, "async io getevents returned %s", strerror(-nr)); return got_events; } @@ -320,8 +319,8 @@ check_state(int fd, struct directio_context *ct, int sync, int timeout_secs) io_prep_pread(&ct->req->io, fd, ct->req->buf, ct->req->blksize, 0); ct->req->state = PATH_PENDING; - if (io_submit(ct->aio_grp->ioctx, 1, ios) != 1) { - LOG(3, "io_submit error %i", errno); + if ((rc = io_submit(ct->aio_grp->ioctx, 1, ios)) != 1) { + LOG(3, "io_submit error %i", -rc); return PATH_UNCHECKED; } } From de5c3b1865d38aac4a7de1ecce7c4c948c076828 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 27 Sep 2023 12:34:08 +0200 Subject: [PATCH 45/64] libmultipath: io_err_stat: don't free aio memory before completion It is wrong to assume that aio data structures can be reused or freed after io_cancel(). io_cancel() will almost always return -EINPROGRESS, anyway. Use the io_starttime field to indicate whether an io event has been completed by the kernel. Make sure no in-flight buffers are freed. Fixes https://github.com/opensvc/multipath-tools/issues/73. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Cc: Li Xiao Keng Cc: Miao Guanqin Cc: Guan Junxiong --- libmultipath/io_err_stat.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/libmultipath/io_err_stat.c b/libmultipath/io_err_stat.c index dc1c2521d..c474c340f 100644 --- a/libmultipath/io_err_stat.c +++ b/libmultipath/io_err_stat.c @@ -111,10 +111,14 @@ static int init_each_dio_ctx(struct dio_ctx *ct, int blksize, return 0; } -static void deinit_each_dio_ctx(struct dio_ctx *ct) +static int deinit_each_dio_ctx(struct dio_ctx *ct) { - if (ct->buf) - free(ct->buf); + if (!ct->buf) + return 0; + if (ct->io_starttime.tv_sec != 0 || ct->io_starttime.tv_nsec != 0) + return 1; + free(ct->buf); + return 0; } static int setup_directio_ctx(struct io_err_stat_path *p) @@ -164,6 +168,7 @@ static int setup_directio_ctx(struct io_err_stat_path *p) static void free_io_err_stat_path(struct io_err_stat_path *p) { int i; + int inflight = 0; if (!p) return; @@ -173,8 +178,13 @@ static void free_io_err_stat_path(struct io_err_stat_path *p) cancel_inflight_io(p); for (i = 0; i < CONCUR_NR_EVENT; i++) - deinit_each_dio_ctx(p->dio_ctx_array + i); - free(p->dio_ctx_array); + inflight += deinit_each_dio_ctx(p->dio_ctx_array + i); + + if (!inflight) + free(p->dio_ctx_array); + else + io_err_stat_log(2, "%s: can't free aio space of %s, %d IOs in flight", + __func__, p->devname, inflight); if (p->fd > 0) close(p->fd); @@ -503,7 +513,7 @@ static int try_to_cancel_timeout_io(struct dio_ctx *ct, struct timespec *t, int rc = PATH_UNCHECKED; int r; - if (ct->io_starttime.tv_sec == 0) + if (ct->io_starttime.tv_sec == 0 && ct->io_starttime.tv_nsec == 0) return rc; timespecsub(t, &ct->io_starttime, &difftime); if (difftime.tv_sec > IOTIMEOUT_SEC) { @@ -514,8 +524,6 @@ static int try_to_cancel_timeout_io(struct dio_ctx *ct, struct timespec *t, if (r) io_err_stat_log(5, "%s: io_cancel error %i", dev, errno); - ct->io_starttime.tv_sec = 0; - ct->io_starttime.tv_nsec = 0; rc = PATH_TIMEOUT; } else { rc = PATH_PENDING; @@ -559,8 +567,6 @@ static void cancel_inflight_io(struct io_err_stat_path *pp) if (r) io_err_stat_log(5, "%s: io_cancel error %d, %i", pp->devname, r, errno); - ct->io_starttime.tv_sec = 0; - ct->io_starttime.tv_nsec = 0; } } From 26dab1986820b1d84c27ea02c5343edd88383842 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 19 Oct 2023 18:53:33 +0200 Subject: [PATCH 46/64] libmultipath: io_err_stat: call io_destroy() inside free_io_err_pathvec() Memory used by aio iocbs must not be freed before either all iocbs have finished, or io_destroy() has been called (which will wait until all iocbs have finished). Don't call cancel_inflight_io() from free_io_err_stat_path(). Rather, cancel directly from free_io_err_pathvec(). This way we make sure that we can't call io_cancel() with an already destroyed ioctx, and that we don't free memory of in-flight requests. The other callers of free_io_err_stat_path() also don't need to call cancel_inflight_io(). In service_paths(), where free_io_err_stat_path() it is called for paths on which all IO has either finished or timed out, hanging requests will already have been cancelled in the try_to_cancel_timeout_io() code path (note that total_time is at least 2 * IO_TIMEOUT_SEC). In the failure case of enqueue_io_err_stat_by_path(), no IO has been submitted yet. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Cc: Li Xiao Keng Cc: Miao Guanqin Cc: Guan Junxiong --- libmultipath/io_err_stat.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/libmultipath/io_err_stat.c b/libmultipath/io_err_stat.c index c474c340f..3f32e32ea 100644 --- a/libmultipath/io_err_stat.c +++ b/libmultipath/io_err_stat.c @@ -175,8 +175,6 @@ static void free_io_err_stat_path(struct io_err_stat_path *p) if (!p->dio_ctx_array) goto free_path; - cancel_inflight_io(p); - for (i = 0; i < CONCUR_NR_EVENT; i++) inflight += deinit_each_dio_ctx(p->dio_ctx_array + i); @@ -221,6 +219,15 @@ static void free_io_err_pathvec(void) pthread_cleanup_push(cleanup_mutex, &io_err_pathvec_lock); if (!io_err_pathvec) goto out; + + /* io_cancel() is a noop, but maybe in the future it won't be */ + vector_foreach_slot(io_err_pathvec, path, i) { + if (path && path->dio_ctx_array) + cancel_inflight_io(path); + } + + /* This blocks until all I/O is finished */ + io_destroy(ioctx); vector_foreach_slot(io_err_pathvec, path, i) free_io_err_stat_path(path); vector_free(io_err_pathvec); @@ -752,5 +759,4 @@ void stop_io_err_stat_thread(void) pthread_join(io_err_stat_thr, NULL); free_io_err_pathvec(); - io_destroy(ioctx); } From bdeba7b3cb0be3ffdaf1961e0f78f4e64fccd5a1 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 26 Oct 2023 12:09:50 +0200 Subject: [PATCH 47/64] libmultipath: io_err_stat: use higher number of aio slots Currently the number of iocbs per path to test is the same as the total number of iocbs in the ioctx. This can easily cause iocb starvation, in particular if some IOs are hanging. In that case io_submit() will fail, and some paths under test will use much less IOs as intended, or in the worst case, none at all. The total number of iocbs reserved in the kernel should be higher. With this patch, we will be able to run the marginal path test for at least NR_IOSTAT_PATHS=32 paths at the same time. This is not an upper limit, because kernel IOCBs can be reused between paths. Increase the log levels of io_setup and io_submit to make it sure we catch problems with this approach. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Cc: Li Xiao Keng Cc: Miao Guanqin Cc: Guan Junxiong --- libmultipath/io_err_stat.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libmultipath/io_err_stat.c b/libmultipath/io_err_stat.c index 3f32e32ea..574900325 100644 --- a/libmultipath/io_err_stat.c +++ b/libmultipath/io_err_stat.c @@ -38,6 +38,7 @@ #define TIMEOUT_NO_IO_NSEC 10000000 /*10ms = 10000000ns*/ #define FLAKY_PATHFAIL_THRESHOLD 2 #define CONCUR_NR_EVENT 32 +#define NR_IOSTAT_PATHS 32 #define PATH_IO_ERR_IN_CHECKING -1 #define PATH_IO_ERR_WAITING_TO_CHECK -2 @@ -477,7 +478,7 @@ static int send_each_async_io(struct dio_ctx *ct, int fd, char *dev) get_monotonic_time(&ct->io_starttime); io_prep_pread(&ct->io, fd, ct->buf, ct->blksize, 0); if (io_submit(ioctx, 1, ios) != 1) { - io_err_stat_log(5, "%s: io_submit error %i", + io_err_stat_log(2, "%s: io_submit error %i", dev, errno); return rc; } @@ -703,8 +704,8 @@ int start_io_err_stat_thread(void *data) if (uatomic_read(&io_err_thread_running) == 1) return 0; - if (io_setup(CONCUR_NR_EVENT, &ioctx) != 0) { - io_err_stat_log(4, "io_setup failed"); + if (io_setup(CONCUR_NR_EVENT * NR_IOSTAT_PATHS, &ioctx) != 0) { + io_err_stat_log(1, "io_setup failed - increase /proc/sys/fs/aio-nr ?"); return 1; } From 12f7d9f543d8e1f593e5d6ea6a437ed7c4ff4127 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 26 Oct 2023 12:33:18 +0200 Subject: [PATCH 48/64] libmultipath: io_err_stat: fix error handling libaio uses a different error return convention than glibc. The error code is not returned in errno, but as the negated return value of the function. Adapt the error handling code in io_err_stat.c. Don't print an error message for failure of io_cancel(), which always returns -EINPRPOGRESS. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmultipath/io_err_stat.c | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/libmultipath/io_err_stat.c b/libmultipath/io_err_stat.c index 574900325..1c5944517 100644 --- a/libmultipath/io_err_stat.c +++ b/libmultipath/io_err_stat.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -469,7 +468,7 @@ static void end_io_err_stat(struct io_err_stat_path *pp) static int send_each_async_io(struct dio_ctx *ct, int fd, char *dev) { - int rc = -1; + int rc; if (ct->io_starttime.tv_nsec == 0 && ct->io_starttime.tv_sec == 0) { @@ -477,15 +476,15 @@ static int send_each_async_io(struct dio_ctx *ct, int fd, char *dev) get_monotonic_time(&ct->io_starttime); io_prep_pread(&ct->io, fd, ct->buf, ct->blksize, 0); - if (io_submit(ioctx, 1, ios) != 1) { - io_err_stat_log(2, "%s: io_submit error %i", - dev, errno); - return rc; + if ((rc = io_submit(ioctx, 1, ios)) != 1) { + io_err_stat_log(2, "%s: io_submit error %s", + dev, strerror(-rc)); + return -1; } - rc = 0; + return 0; } - return rc; + return -1; } static void send_batch_async_ios(struct io_err_stat_path *pp) @@ -530,8 +529,8 @@ static int try_to_cancel_timeout_io(struct dio_ctx *ct, struct timespec *t, io_err_stat_log(5, "%s: abort check on timeout", dev); r = io_cancel(ioctx, ios[0], &event); if (r) - io_err_stat_log(5, "%s: io_cancel error %i", - dev, errno); + io_err_stat_log(5, "%s: io_cancel error %s", + dev, strerror(-r)); rc = PATH_TIMEOUT; } else { rc = PATH_PENDING; @@ -560,7 +559,7 @@ static void poll_async_io_timeout(void) static void cancel_inflight_io(struct io_err_stat_path *pp) { struct io_event event; - int i, r; + int i; for (i = 0; i < CONCUR_NR_EVENT; i++) { struct dio_ctx *ct = pp->dio_ctx_array + i; @@ -571,10 +570,7 @@ static void cancel_inflight_io(struct io_err_stat_path *pp) continue; io_err_stat_log(5, "%s: abort infligh io", pp->devname); - r = io_cancel(ioctx, ios[0], &event); - if (r) - io_err_stat_log(5, "%s: io_cancel error %d, %i", - pp->devname, r, errno); + io_cancel(ioctx, ios[0], &event); } } @@ -610,12 +606,11 @@ static void process_async_ios_event(int timeout_nsecs, char *dev) int i, n; struct timespec timeout = { .tv_nsec = timeout_nsecs }; - errno = 0; pthread_testcancel(); n = io_getevents(ioctx, 1L, CONCUR_NR_EVENT, events, &timeout); if (n < 0) { - io_err_stat_log(3, "%s: async io events returned %d (errno=%s)", - dev, n, strerror(errno)); + io_err_stat_log(3, "%s: io_getevents returned %s", + dev, strerror(-n)); } else { for (i = 0; i < n; i++) handle_async_io_done_event(&events[i]); @@ -704,8 +699,9 @@ int start_io_err_stat_thread(void *data) if (uatomic_read(&io_err_thread_running) == 1) return 0; - if (io_setup(CONCUR_NR_EVENT * NR_IOSTAT_PATHS, &ioctx) != 0) { - io_err_stat_log(1, "io_setup failed - increase /proc/sys/fs/aio-nr ?"); + if ((ret = io_setup(NR_IOSTAT_PATHS * CONCUR_NR_EVENT, &ioctx)) != 0) { + io_err_stat_log(1, "io_setup failed: %s, increase /proc/sys/fs/aio-nr ?", + strerror(-ret)); return 1; } From 187f30fc62b3c75412a86ecfe8111cd31d745f3f Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 26 Oct 2023 16:33:17 +0200 Subject: [PATCH 49/64] multipathd.service: require modprobe@dm_multipath.service if available systemd v245 and newer provides the modprobe@.service unit, which can be used to pull in modules via systemd. This is cleaner than loading dm-multipath unconditionally via modules-load.d. Use it if the detected systemd version support it. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- .gitignore | 1 + Makefile.inc | 4 +++- multipath/Makefile | 6 ++++-- multipathd/Makefile | 4 ++-- multipathd/{multipathd.service => multipathd.service.in} | 4 ++-- 5 files changed, 12 insertions(+), 7 deletions(-) rename multipathd/{multipathd.service => multipathd.service.in} (85%) diff --git a/.gitignore b/.gitignore index 2986578f4..6890e4af2 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ multipath/tmpfiles.conf multipathd/multipathd multipathd/multipathd.8 multipathd/multipathc +multipathd/multipathd.service mpathpersist/mpathpersist mpathpersist/mpathpersist.8 abi.tar.gz diff --git a/Makefile.inc b/Makefile.inc index 04bfa56ec..8655cba6c 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -90,6 +90,8 @@ ORIG_LDFLAGS := $(LDFLAGS) SYSTEMD_CPPFLAGS := $(if $(SYSTEMD),-DUSE_SYSTEMD=$(SYSTEMD)) SYSTEMD_LIBDEPS := $(if $(SYSTEMD),$(if $(shell test $(SYSTEMD) -gt 209 && echo 1),-lsystemd,-lsystemd-daemon)) +MODPROBE_UNIT := $(shell test "0$(SYSTEMD)" -lt 245 2>/dev/null || \ + echo "modprobe@dm_multipath.service") OPTFLAGS := -O2 -g $(STACKPROT) --param=ssp-buffer-size=4 WARNFLAGS := -Werror -Wall -Wextra -Wformat=2 $(WFORMATOVERFLOW) -Werror=implicit-int \ @@ -144,4 +146,4 @@ NV_VERSION_SCRIPT = $(DEVLIB:%.so=%-nv.version) %: %.in @echo creating $@ - $(Q)sed 's:@CONFIGFILE@:'$(configfile)':g;s:@CONFIGDIR@:'$(configdir)':g;s:@STATE_DIR@:'$(statedir)':g;s:@RUNTIME_DIR@:'$(runtimedir)':g' $< >$@ + $(Q)sed 's:@CONFIGFILE@:'$(configfile)':g;s:@CONFIGDIR@:'$(configdir)':g;s:@STATE_DIR@:'$(statedir)':g;s:@RUNTIME_DIR@:'$(runtimedir)':g;s/@MODPROBE_UNIT@/'$(MODPROBE_UNIT)'/g' $< >$@ diff --git a/multipath/Makefile b/multipath/Makefile index 68cb5ce79..0efb9b266 100644 --- a/multipath/Makefile +++ b/multipath/Makefile @@ -27,14 +27,16 @@ install: $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(udevrulesdir) $(Q)$(INSTALL_PROGRAM) -m 644 11-dm-mpath.rules $(DESTDIR)$(udevrulesdir) $(Q)$(INSTALL_PROGRAM) -m 644 multipath.rules $(DESTDIR)$(udevrulesdir)/56-multipath.rules - $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(modulesloaddir) - $(Q)$(INSTALL_PROGRAM) -m 644 modules-load.conf $(DESTDIR)$(modulesloaddir)/multipath.conf $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(tmpfilesdir) $(Q)$(INSTALL_PROGRAM) -m 644 tmpfiles.conf $(DESTDIR)$(tmpfilesdir)/multipath.conf $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(mandir)/man8 $(Q)$(INSTALL_PROGRAM) -m 644 $(EXEC).8 $(DESTDIR)$(mandir)/man8 $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(mandir)/man5 $(Q)$(INSTALL_PROGRAM) -m 644 $(EXEC).conf.5 $(DESTDIR)$(mandir)/man5 + $(Q)$(INSTALL_PROGRAM) -d $(DESTDIR)$(modulesloaddir) +ifeq ($(MODPROBE_UNIT),) + $(Q)$(INSTALL_PROGRAM) -m 644 modules-load.conf $(DESTDIR)$(modulesloaddir)/multipath.conf +endif ifneq ($(SCSI_DH_MODULES_PRELOAD),) $(Q)$(INSTALL_PROGRAM) -m 644 scsi_dh.conf $(DESTDIR)$(modulesloaddir)/scsi_dh.conf $(Q)for _x in $(SCSI_DH_MODULES_PRELOAD); do echo "$$_x"; done \ diff --git a/multipathd/Makefile b/multipathd/Makefile index 0ba6ecb78..997b40cfd 100644 --- a/multipathd/Makefile +++ b/multipathd/Makefile @@ -41,7 +41,7 @@ ifeq ($(FPIN_SUPPORT),1) OBJS += fpin_handlers.o endif -all : $(EXEC) $(CLI) $(MANPAGES) +all : $(EXEC) $(CLI) $(MANPAGES) $(EXEC).service $(EXEC): $(OBJS) $(multipathdir)/libmultipath.so $(mpathcmddir)/libmpathcmd.so @echo building $@ because of $? @@ -78,7 +78,7 @@ uninstall: $(Q)$(RM) $(DESTDIR)$(unitdir)/$(EXEC).socket clean: dep_clean - $(Q)$(RM) core *.o $(EXEC) $(CLI) $(MANPAGES) + $(Q)$(RM) core *.o $(EXEC) $(CLI) $(MANPAGES) $(EXEC).service include $(wildcard $(OBJS:.o=.d) $(CLI_OBJS:.o=.d)) diff --git a/multipathd/multipathd.service b/multipathd/multipathd.service.in similarity index 85% rename from multipathd/multipathd.service rename to multipathd/multipathd.service.in index 5a9cde126..6d03ff710 100644 --- a/multipathd/multipathd.service +++ b/multipathd/multipathd.service.in @@ -2,8 +2,8 @@ Description=Device-Mapper Multipath Device Controller Before=lvm2-activation-early.service Before=local-fs-pre.target blk-availability.service shutdown.target -Wants=systemd-udevd-kernel.socket -After=systemd-udevd-kernel.socket +Wants=systemd-udevd-kernel.socket @MODPROBE_UNIT@ +After=systemd-udevd-kernel.socket @MODPROBE_UNIT@ After=multipathd.socket systemd-remount-fs.service Before=initrd-cleanup.service DefaultDependencies=no From 6fad1464d5bed95193eda42e4024af60daf0eb93 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 20 Oct 2023 16:04:05 +0200 Subject: [PATCH 50/64] libmpathutil: remove systemd_service_enabled() Since c203929 ("multipathd.service: add dependency on systemd-udevd-kernel.socket"), multipathd will start early during boot. Moreover, we recommend using socket activation for multipathd, and if multipathd.socket is enabled, the __mpath_connect() check will succeed anyway. The systemd_service_enabled() test was just "good enough" for standard situations but never robust; it checked for multipathd.wants in "some" directory, which might or might not be the current target, and it would return true even of multipathd.service was masked. Remove this test. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- libmpathutil/libmpathutil.version | 17 +++------ libmpathutil/util.c | 58 ------------------------------- libmpathutil/util.h | 1 - libmultipath/valid.c | 16 ++------- tests/valid.c | 24 ++----------- 5 files changed, 9 insertions(+), 107 deletions(-) diff --git a/libmpathutil/libmpathutil.version b/libmpathutil/libmpathutil.version index 6ebb71867..15ff46763 100644 --- a/libmpathutil/libmpathutil.version +++ b/libmpathutil/libmpathutil.version @@ -93,12 +93,15 @@ local: }; /* symbols referenced internally by libmultipath */ -LIBMPATHUTIL_1.0 { +LIBMPATHUTIL_2.0 { alloc_bitfield; __append_strbuf_str; append_strbuf_quoted; basenamecpy; + cleanup_fd_ptr; cleanup_free_ptr; + cleanup_vector_free; + cleanup_fclose; filepresent; find_keyword; free_keywords; @@ -120,21 +123,9 @@ LIBMPATHUTIL_1.0 { snprint_keyword; steal_strbuf_str; strlcat; - systemd_service_enabled; validate_config_strvec; vector_find_or_add_slot; vector_insert_slot; vector_move_up; vector_sort; }; - -LIBMPATHUTIL_1.1 { -global: - cleanup_fd_ptr; -} LIBMPATHUTIL_1.0; - -LIBMPATHUTIL_1.2 { -global: - cleanup_vector_free; - cleanup_fclose; -} LIBMPATHUTIL_1.0; diff --git a/libmpathutil/util.c b/libmpathutil/util.c index 92f25a50e..9d147fca2 100644 --- a/libmpathutil/util.c +++ b/libmpathutil/util.c @@ -213,64 +213,6 @@ setup_thread_attr(pthread_attr_t *attr, size_t stacksize, int detached) } } -int systemd_service_enabled_in(const char *dev, const char *prefix) -{ - static const char service[] = "multipathd.service"; - char path[PATH_MAX], file[PATH_MAX]; - DIR *dirfd; - struct dirent *d; - int found = 0; - - if (safe_sprintf(path, "%s/systemd/system", prefix)) - return 0; - - condlog(3, "%s: checking for %s in %s", dev, service, path); - - dirfd = opendir(path); - if (dirfd == NULL) - return 0; - - while ((d = readdir(dirfd)) != NULL) { - char *p; - struct stat stbuf; - - if ((strcmp(d->d_name,".") == 0) || - (strcmp(d->d_name,"..") == 0)) - continue; - - if (strlen(d->d_name) < 6) - continue; - - p = d->d_name + strlen(d->d_name) - 6; - if (strcmp(p, ".wants")) - continue; - if (!safe_sprintf(file, "%s/%s/%s", - path, d->d_name, service) - && stat(file, &stbuf) == 0) { - condlog(3, "%s: found %s", dev, file); - found++; - break; - } - } - closedir(dirfd); - - return found; -} - -int systemd_service_enabled(const char *dev) -{ - int found = 0; - - found = systemd_service_enabled_in(dev, "/etc"); - if (!found) - found = systemd_service_enabled_in(dev, "/usr/lib"); - if (!found) - found = systemd_service_enabled_in(dev, "/lib"); - if (!found) - found = systemd_service_enabled_in(dev, "/run"); - return found; -} - static int _linux_version_code; static pthread_once_t _lvc_initialized = PTHREAD_ONCE_INIT; diff --git a/libmpathutil/util.h b/libmpathutil/util.h index 99a471d09..de9fcfdd7 100644 --- a/libmpathutil/util.h +++ b/libmpathutil/util.h @@ -21,7 +21,6 @@ size_t strlcat(char * restrict dst, const char * restrict src, size_t size); dev_t parse_devt(const char *dev_t); char *convert_dev(char *dev, int is_path_device); void setup_thread_attr(pthread_attr_t *attr, size_t stacksize, int detached); -int systemd_service_enabled(const char *dev); int get_linux_version_code(void); int safe_write(int fd, const void *buf, size_t count); void set_max_fds(rlim_t max_fds); diff --git a/libmultipath/valid.c b/libmultipath/valid.c index d4dae3ed5..f2237787d 100644 --- a/libmultipath/valid.c +++ b/libmultipath/valid.c @@ -314,23 +314,11 @@ is_path_valid(const char *name, struct config *conf, struct path *pp, return PATH_IS_VALID_NO_CHECK; } - /* - * "multipath -u" may be run before the daemon is started. In this - * case, systemd might own the socket but might delay multipathd - * startup until some other unit (udev settle!) has finished - * starting. With many LUNs, the listen backlog may be exceeded, which - * would cause connect() to block. This causes udev workers calling - * "multipath -u" to hang, and thus creates a deadlock, until "udev - * settle" times out. To avoid this, call connect() in non-blocking - * mode here, and take EAGAIN as indication for a filled-up systemd - * backlog. - */ - if (check_multipathd) { fd = __mpath_connect(1); if (fd < 0) { - if (errno != EAGAIN && !systemd_service_enabled(name)) { - condlog(3, "multipathd not running or enabled"); + if (errno != EAGAIN) { + condlog(3, "multipathd not running"); return PATH_IS_NOT_VALID; } } else diff --git a/tests/valid.c b/tests/valid.c index 703293246..18a5a7bf8 100644 --- a/tests/valid.c +++ b/tests/valid.c @@ -62,11 +62,6 @@ int __wrap___mpath_connect(int nonblocking) return -1; } -int __wrap_systemd_service_enabled(const char *dev) -{ - return (int)mock_type(bool); -} - /* There's no point in checking the return value here */ int __wrap_mpath_disconnect(int fd) { @@ -216,7 +211,6 @@ enum { enum { CHECK_MPATHD_RUNNING, CHECK_MPATHD_EAGAIN, - CHECK_MPATHD_ENABLED, CHECK_MPATHD_SKIP, }; @@ -232,11 +226,8 @@ static void setup_passing(char *name, char *wwid, unsigned int check_multipathd, else if (check_multipathd == CHECK_MPATHD_EAGAIN) { will_return(__wrap___mpath_connect, false); will_return(__wrap___mpath_connect, EAGAIN); - } else if (check_multipathd == CHECK_MPATHD_ENABLED) { - will_return(__wrap___mpath_connect, false); - will_return(__wrap___mpath_connect, ECONNREFUSED); - will_return(__wrap_systemd_service_enabled, true); } + /* nothing for CHECK_MPATHD_SKIP */ if (stage == STAGE_CHECK_MULTIPATHD) return; @@ -342,19 +333,10 @@ static void test_check_multipathd(void **state) will_return(__wrap_sysfs_is_multipathed, false); will_return(__wrap___mpath_connect, false); will_return(__wrap___mpath_connect, ECONNREFUSED); - will_return(__wrap_systemd_service_enabled, false); + assert_int_equal(is_path_valid(name, &conf, &pp, true), PATH_IS_NOT_VALID); assert_string_equal(pp.dev, name); - /* test pass because service is enabled. fail getting udev */ - memset(&pp, 0, sizeof(pp)); - setup_passing(name, NULL, CHECK_MPATHD_ENABLED, STAGE_CHECK_MULTIPATHD); - will_return(__wrap_udev_device_new_from_subsystem_sysname, false); - will_return(__wrap_udev_device_new_from_subsystem_sysname, - name); - assert_int_equal(is_path_valid(name, &conf, &pp, true), - PATH_IS_ERROR); - assert_string_equal(pp.dev, name); /* test pass because connect returned EAGAIN. fail getting udev */ setup_passing(name, NULL, CHECK_MPATHD_EAGAIN, STAGE_CHECK_MULTIPATHD); will_return(__wrap_udev_device_new_from_subsystem_sysname, false); @@ -533,7 +515,7 @@ static void test_check_uuid_present(void **state) memset(&pp, 0, sizeof(pp)); conf.find_multipaths = FIND_MULTIPATHS_STRICT; - setup_passing(name, wwid, CHECK_MPATHD_ENABLED, STAGE_CHECK_WWIDS); + setup_passing(name, wwid, CHECK_MPATHD_RUNNING, STAGE_CHECK_WWIDS); will_return(__wrap_dm_map_present_by_uuid, 1); will_return(__wrap_dm_map_present_by_uuid, wwid); assert_int_equal(is_path_valid(name, &conf, &pp, true), From 49b0e2f4cd1d56a311c6684c0d13930e8aab9b8e Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 26 Oct 2023 16:46:17 +0200 Subject: [PATCH 51/64] multipath.conf.5: fix typo Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- multipath/multipath.conf.5.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in index 226d00196..2b70b6d55 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -782,7 +782,7 @@ Specify the maximum number of seconds the SCSI layer will spend doing error handling when scsi devices fail. After this timeout the scsi layer will perform a full HBA reset. Setting this may be necessary in cases where the rport is never lost, so \fIfast_io_fail_tmo\fR and \fIdev_loss_tmo\fR will never -trigger, but (frequently do to load) scsi commands still hang. \fBNote:\fR when +trigger, but (frequently due to load) scsi commands still hang. \fBNote:\fR when the scsi error handler performs the HBA reset, all target paths on that HBA will be affected. eh_deadline should only be set in cases where all targets on the affected HBAs are multipathed. From f5769cf457d425f4811bc94c5ff7f7461b9ad73d Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 26 Oct 2023 16:41:20 +0200 Subject: [PATCH 52/64] Makefile.inc, README.md: fix docs for prefix in split-usr case systemd with "split-usr=true" uses rootprefixdir as prefix for units and for udev libraries and rules ("udevlibexecdir" in systemd). Fix the documentation for this case. Also, slightly improve the paragraph about SCSI module loading. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Cc: Xose Vazquez Perez --- Makefile.inc | 6 ++++-- README.md | 25 ++++++++++++++++++------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Makefile.inc b/Makefile.inc index 8655cba6c..a20e2cec4 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -40,8 +40,10 @@ usr_prefix := $(prefix) # Prefix for configfuration files (multipath.conf) etc_prefix := $(prefix) # Where to install systemd-related files. systemd is usually installed under /usr -# Note: some systemd installations use separate "prefix" and "rootprefix". -# In this case, override only unitdir to use systemd's "rootprefix" instead of $(systemd_prefix) +# Note: systemd installations with "split-usr=true" use separate "prefixdir" and +# "rootprefixdir". Our systemd_prefix corresponds to "prefixdir". +# In this case, override only unitdir and libudevdir below to use +# systemd's "rootprefixdir" instead of $(systemd_prefix) systemd_prefix := /usr # Make sure all prefix variables end in "/" diff --git a/README.md b/README.md index 524c9fb1a..25ce96369 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,13 @@ The following variables can be passed to the `make` command line: polling API. For use with pre-5.0 kernels that don't support dmevent polling (but even if you don't use this option, multipath-tools will work with these kernels). + * `SYSTEMD`: The version number of systemd (e.g. "244") to compile the code for. + The default is autodetected, assuming that the systemd version in the build + environment is the same as on the target system. Override the value to + build for a different systemd version, or set it to `""` to build for a + system without systemd. + **Caution:** multipathd without systemd has been largely untested by the + upstream maintainers since at least 2020. * `SCSI_DH_MODULES_PRELOAD="(list)"`: specify a space-separated list of SCSI device handler kernel modules to load early during boot. Some multipath-tools functionality depends on these modules being loaded @@ -122,7 +129,9 @@ The following variables can be passed to the `make` command line: It's especially useful if `scsi_mod` is builtin but `scsi_dh_alua` and other device handler modules are built as modules. If `scsi_mod` itself is compiled as a module, it might make more sense to use a module softdep for the same - purpose. + purpose by creating a `modprobe.d` file like this: + + softdep scsi_mod post: scsi_dh_alua scsi_dh_rdac ### Installation Paths @@ -133,9 +142,9 @@ The following variables can be passed to the `make` command line: for booting. Non-usr-merged distributions[^systemd] may want to set this to `/usr`. The default is `$(prefix)`. * `systemd_prefix`: Prefix for systemd-related files[^systemd]. The default is `/usr`. - * `etc_prefix`: The prefix for configuration files. "Usr-merged" + * `etc_prefix`: The prefix for configuration files. "usr-merged" distributions with immutable `/usr`[^systemd] may want to set this to - `/etc`. The default is `$(prefix)`. + `""`. The default is `$(prefix)`. * `LIB`: the subdirectory under `prefix` where shared libraries will be installed. By default, the makefile uses `/lib64` if this directory is found on the build system, and `/lib` otherwise. @@ -144,10 +153,12 @@ The options `configdir`, `plugindir`, `configfile`, and `statedir` above can be used for setting individual paths where the `prefix` variables don't provide sufficient control. See `Makefile.inc` for even more fine-grained control. -[^systemd]: Some systemd installations use separate `prefix` and `rootprefix`. - On such a distribution, set `prefix`, and override `unitdir` to use systemd's - `rootprefix`. Recent systemd releases generally require everything to be - installed under `/usr` (so-called "usr-merged" distribution). On "usr- +[^systemd]: systemd installations up to v254 which have been built with + `split-usr=true` may use separate `prefixdir` and `rootprefixdir` + directories, where `prefixdir` is a subdirectory of `rootprefixdir`. + multipath-tools' `systemd_prefix` corresponds to systemd's `prefixdir`. + On such distributions, override `unitdir` and `libudevdir` to use systemd's + `rootprefix`: `make libudevdir=/lib/udev unitdir=/lib/systemd/system` ### Compiler Options From 37441cd9b8030c2bce5be9f09891e6f742d8ee26 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 26 Oct 2023 18:05:00 +0200 Subject: [PATCH 53/64] README.md: update mailing list and contributing information Create a "Contributing" section. Move the the current sections related to contributing into that section. Signed-off-by: Martin Wilck Acked-by: Benjamin Marzinski Cc: Xose Vazquez Perez --- README.md | 52 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 25ce96369..5fdebfab0 100644 --- a/README.md +++ b/README.md @@ -42,14 +42,6 @@ Go to: https://github.com/opensvc/multipath-tools/tags Select a release-tag and then click on "zip" or "tar.gz". -Devel code -========== - -To get latest devel code: - - git clone -b queue https://github.com/openSUSE/multipath-tools - - Building multipath-tools ======================== @@ -188,19 +180,45 @@ The following targets are intended for developers only. * `make compile-commands.json` to create input for [clangd](https://clangd.llvm.org/). -Add storage devices -=================== - -Follow the instructions in the `libmultipath/hwtable.c` header. +Contributing +============ +Please send patches or contributions for general discussion about +multipath tools to the mailing list (see below). You can also create +issues or pull requests on +[GitHub](https://github.com/opensvc/multipath-tools). +You will be asked to send your patches to the mailing list +unless your patch is trivial. Mailing list -============ +------------ + +The mailing list for multipath-tools is `dm-devel@lists.linux.dev`. +To subscribe, send an email to `dm-devel+subscribe@lists.linux.dev`. +Mailing list archives are available on +[lore.kernel.org](https://lore.kernel.org/dm-devel/) and +[marc.info](https://marc.info/?l=dm-devel). See also the +[lists.linux.dev home page](https://subspace.kernel.org/lists.linux.dev.html). + +When sending patches to the mailing list, please add a `Signed-off-by:` +tag, and add Benjamin Marzinski and +Martin Wilck to the Cc list. + +Staging area +------------ + +Between releases, the latest reviewed code can be obtained from +[the queue branch](https://github.com/openSUSE/multipath-tools/tree/queue) +in the openSUSE/multipath-tools repository on GitHub. From there, +pull requests for new releases in the master repository are +created roughly every 3 months. -(subscribers-only) -To subscribe and archives: https://listman.redhat.com/mailman/listinfo/dm-devel -Searchable: https://marc.info/?l=dm-devel +Adding new storage devices +-------------------------- +If you want to add special settings for a storage device which is +new on the market, follow the instructions at the top of the +file `libmultipath/hwtable.c`. Changelog ========= @@ -213,7 +231,7 @@ Maintainer ========== Christophe Varoqui -Device-mapper development mailing list +Device-mapper development mailing list Licence From d3e23e5a63fb64d7a11ff7a663c2acf7353bc201 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 26 Oct 2023 19:01:03 +0200 Subject: [PATCH 54/64] README.md: Extend the section about NVMe Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Cc: Xose Vazquez Perez --- README.md | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 5fdebfab0..bfdbd2dee 100644 --- a/README.md +++ b/README.md @@ -273,15 +273,25 @@ To enable ALUA, the following options should be changed: NVMe ==== -To use Device Mapper/multipath-tools with NVMe devices, -if the Native NVMe Multipath subsystem is enabled -( "Y" in `/sys/module/nvme_core/parameters/multipath` ), -it has to be disabled: - -`echo "options nvme_core multipath=N" > /etc/modprobe.d/01-nvme_core-mp.conf`, -regenerate the initramfs (`dracut -f` or `update-initramfs`) and reboot. - -Check that it is disabled(N) with: -`cat /sys/module/nvme_core/parameters/multipath` -or -`systool -m nvme_core -A multipath` + +Using dm-multipath with NVMe +---------------------------- + +NVMe multipath is natively supported by the Linux kernel. If for some reason +you prefer using device mapper multipath with NVMe devices, +you need to disable native multipathing first: + + echo "options nvme_core multipath=N" > /etc/modprobe.d/01-nvme_core-mp.conf + +Afterwards, regenerate the initramfs (`dracut -f` or `update-initramfs`) and reboot. + +Using multipath-tools with native NVMe multipath +------------------------------------------------ + +If native NVMe multipathing is enabled, you can still use multipath-tools +for displaying the topology and some other information about native NVMe +multipath setups. This feature is disabled by default. To enable it, set +`enable_foreign nvme` in the `defaults` section of `multipath.conf`. +Commands like `multipath -ll` will then display information about NVMe +native multipath. This support is read-only; modifying the native multipath +configuration is not supported. From b7ddf4a4979aa7a9da6c2a86a6adb371b9e60005 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 26 Oct 2023 19:10:35 +0200 Subject: [PATCH 55/64] README.md: fix formatting of Changelog section Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski Cc: Xose Vazquez Perez --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bfdbd2dee..db03a33c0 100644 --- a/README.md +++ b/README.md @@ -223,8 +223,8 @@ file `libmultipath/hwtable.c`. Changelog ========= -pre-0.4.5: https://web.archive.org/web/20070309224034/http://christophe.varoqui.free.fr/wiki/wakka.php?wiki=ChangeLog -post-0.4.5: https://github.com/opensvc/multipath-tools/commits/master +* pre-0.4.5: https://web.archive.org/web/20070309224034/http://christophe.varoqui.free.fr/wiki/wakka.php?wiki=ChangeLog +* post-0.4.5: https://github.com/opensvc/multipath-tools/commits/master Maintainer From a4e32b2e00b40effdbe269bff6b425d7e065e9f8 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Thu, 9 Nov 2023 18:46:11 -0500 Subject: [PATCH 56/64] libmultipath: Add max_retries config option This option lets multipath set a scsi disk's max_retries sysfs value. Setting this can be helpful for cases where the path checker succeeds, but IO commands hang and timeout. By default, the SCSI layer will retry IOs 5 times. Reducing this value will allow multipath to retry the IO down another path sooner. Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/config.h | 1 + libmultipath/dict.c | 25 +++++++++++++++++++++ libmultipath/discovery.c | 41 ++++++++++++++++++++++++++++++++++- libmultipath/structs.h | 6 +++++ multipath/multipath.conf.5.in | 14 ++++++++++++ 5 files changed, 86 insertions(+), 1 deletion(-) diff --git a/libmultipath/config.h b/libmultipath/config.h index 8c22ce754..417e5834c 100644 --- a/libmultipath/config.h +++ b/libmultipath/config.h @@ -162,6 +162,7 @@ struct config { int fast_io_fail; unsigned int dev_loss; int eh_deadline; + int max_retries; int log_checker_err; int allow_queueing; int allow_usb_devices; diff --git a/libmultipath/dict.c b/libmultipath/dict.c index 044067afb..e268673fc 100644 --- a/libmultipath/dict.c +++ b/libmultipath/dict.c @@ -1152,6 +1152,30 @@ declare_hw_snprint(eh_deadline, print_undef_off_zero) declare_pc_handler(eh_deadline, set_undef_off_zero) declare_pc_snprint(eh_deadline, print_undef_off_zero) +static int +def_max_retries_handler(struct config *conf, vector strvec, const char *file, + int line_nr) +{ + char * buff; + + buff = set_value(strvec); + if (!buff) + return 1; + + if (strcmp(buff, "off") == 0) + conf->max_retries = MAX_RETRIES_OFF; + else if (strcmp(buff, "0") == 0) + conf->max_retries = MAX_RETRIES_ZERO; + else + do_set_int(strvec, &conf->max_retries, 1, 5, file, line_nr, + buff); + + free(buff); + return 0; +} + +declare_def_snprint(max_retries, print_undef_off_zero) + static int set_pgpolicy(vector strvec, void *ptr, const char *file, int line_nr) { @@ -2079,6 +2103,7 @@ init_keywords(vector keywords) install_keyword("fast_io_fail_tmo", &def_fast_io_fail_handler, &snprint_def_fast_io_fail); install_keyword("dev_loss_tmo", &def_dev_loss_handler, &snprint_def_dev_loss); install_keyword("eh_deadline", &def_eh_deadline_handler, &snprint_def_eh_deadline); + install_keyword("max_retries", &def_max_retries_handler, &snprint_def_max_retries); install_keyword("bindings_file", &deprecated_bindings_file_handler, &snprint_deprecated); install_keyword("wwids_file", &deprecated_wwids_file_handler, &snprint_deprecated); install_keyword("prkeys_file", &deprecated_prkeys_file_handler, &snprint_deprecated); diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c index 84ce5fe72..6fd4dabb9 100644 --- a/libmultipath/discovery.c +++ b/libmultipath/discovery.c @@ -614,6 +614,43 @@ sysfs_set_eh_deadline(struct path *pp) return (ret <= 0); } +static int +sysfs_set_max_retries(struct config *conf, struct path *pp) +{ + struct udev_device *parent; + char value[16]; + STRBUF_ON_STACK(buf); + int ret, len; + + if (conf->max_retries == MAX_RETRIES_UNSET) + return 0; + + if (!pp->udev || pp->sg_id.host_no < 0) + return 1; + + len = sprintf(value, "%d", (conf->max_retries == MAX_RETRIES_OFF)? -1 : + (conf->max_retries == MAX_RETRIES_ZERO)? 0 : + conf->max_retries); + + parent = udev_device_get_parent_with_subsystem_devtype(pp->udev, + "scsi", "scsi_device"); + if (!parent) + return 1; + + if (print_strbuf(&buf, "scsi_disk/%i:%i:%i:%" PRIu64 "/max_retries", + pp->sg_id.host_no, pp->sg_id.channel, + pp->sg_id.scsi_id, pp->sg_id.lun) < 0) + return 1; + + ret = sysfs_attr_set_value(parent, get_strbuf_str(&buf), value, len); + if (len != ret) + log_sysfs_attr_set_value(3, ret, + "%s/%s: failed to set value to %s", + udev_device_get_sysname(parent), + get_strbuf_str(&buf), value); + return (len != ret); +} + static void sysfs_set_rport_tmo(struct multipath *mpp, struct path *pp) { @@ -878,7 +915,8 @@ sysfs_set_scsi_tmo (struct config *conf, struct multipath *mpp) if (pp->dev_loss == DEV_LOSS_TMO_UNSET && pp->fast_io_fail == MP_FAST_IO_FAIL_UNSET && - pp->eh_deadline == EH_DEADLINE_UNSET) + pp->eh_deadline == EH_DEADLINE_UNSET && + conf->max_retries == MAX_RETRIES_UNSET) continue; if (pp->bus != SYSFS_BUS_SCSI) { @@ -886,6 +924,7 @@ sysfs_set_scsi_tmo (struct config *conf, struct multipath *mpp) continue; } sysfs_set_eh_deadline(pp); + sysfs_set_max_retries(conf, pp); if (pp->dev_loss == DEV_LOSS_TMO_UNSET && pp->fast_io_fail == MP_FAST_IO_FAIL_UNSET) diff --git a/libmultipath/structs.h b/libmultipath/structs.h index 17e13ee7c..63551b806 100644 --- a/libmultipath/structs.h +++ b/libmultipath/structs.h @@ -295,6 +295,12 @@ enum eh_deadline_states { EH_DEADLINE_ZERO = UOZ_ZERO, }; +enum max_retries_states { + MAX_RETRIES_UNSET = UOZ_UNDEF, + MAX_RETRIES_OFF = UOZ_OFF, + MAX_RETRIES_ZERO = UOZ_ZERO, +}; + enum recheck_wwid_states { RECHECK_WWID_UNDEF = YNU_UNDEF, RECHECK_WWID_OFF = YNU_NO, diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in index 2b70b6d55..3a3164c00 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -793,6 +793,20 @@ The default is: \fB\fR . . .TP +.B max_retries +Specify the maximum number of times the SCSI layer will retry IO commands for +some types of SCSI errors before returning failure. Setting this can be helpful +for cases where IO commands hang and timeout. By default, the SCSI layer will +retry IOs 5 times. Reducing this value will allow multipath to retry the IO +down another path sooner. Valid values are +\fB0\fR through \fB5\fR. +.RS +.TP +The default is: \fB\fR +.RE +. +. +.TP .B bindings_file (Deprecated) This option is not supported any more, and will be ignored. .RS From 25208bddf38a903d9dae34b91751ec5976b77260 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Thu, 9 Nov 2023 18:46:12 -0500 Subject: [PATCH 57/64] libmutipath: Retain device size if sysfs_get_size fails. When paths are allocated their size is initialized to 0. If they've already set a size, and a future call to sysfs_get_size() fails during the parsing, assume that the size hasn't changed, instead of setting it to 0. All other failures in sysfs_get_size() already retain the existing size. Reviewed-by: Martin Wilck Signed-off-by: Benjamin Marzinski --- libmultipath/sysfs.c | 1 - 1 file changed, 1 deletion(-) diff --git a/libmultipath/sysfs.c b/libmultipath/sysfs.c index c45296af9..ad3d6612e 100644 --- a/libmultipath/sysfs.c +++ b/libmultipath/sysfs.c @@ -175,7 +175,6 @@ sysfs_get_size (struct path *pp, unsigned long long * size) if (r != 1) { condlog(3, "%s: Cannot parse size attribute", pp->dev); - *size = 0; return 1; } From 1f9809e27540988aa97257dffd52462957b7cad7 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Thu, 9 Nov 2023 18:46:13 -0500 Subject: [PATCH 58/64] multipathd: check and update all paths when in cli_resize When resizing a multipath device, make sure that all the paths have been updated to the new size first. Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- multipathd/cli_handlers.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c index c9addfbb5..37a999ac9 100644 --- a/multipathd/cli_handlers.c +++ b/multipathd/cli_handlers.c @@ -845,9 +845,11 @@ cli_resize(void *v, struct strbuf *reply, void *data) char * mapname = get_keyparam(v, KEY_MAP); struct multipath *mpp; int minor; - unsigned long long size; + unsigned long long size = 0; struct pathgroup *pgp; struct path *pp; + unsigned int i, j; + bool mismatch = false; mapname = convert_dev(mapname, 0); condlog(2, "%s: resize map (operator)", mapname); @@ -867,21 +869,24 @@ cli_resize(void *v, struct strbuf *reply, void *data) return 1; } - pgp = VECTOR_SLOT(mpp->pg, 0); - - if (!pgp){ - condlog(0, "%s: couldn't get path group. cannot resize", - mapname); - return 1; + vector_foreach_slot(mpp->pg, pgp, i) { + vector_foreach_slot (pgp->paths, pp, j) { + sysfs_get_size(pp, &pp->size); + if (!pp->size) + continue; + if (!size) + size = pp->size; + else if (pp->size != size) + mismatch = true; + } } - pp = VECTOR_SLOT(pgp->paths, 0); - - if (!pp){ - condlog(0, "%s: couldn't get path. cannot resize", mapname); + if (!size) { + condlog(0, "%s: couldn't get size from sysfs. cannot resize", + mapname); return 1; } - if (!pp->udev || sysfs_get_size(pp, &size)) { - condlog(0, "%s: couldn't get size for sysfs. cannot resize", + if (mismatch) { + condlog(0, "%s: path size not consistent. cannot resize", mapname); return 1; } From 2d465ae35c59d90c939e80b943891dfbfcd95be7 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Thu, 9 Nov 2023 18:46:14 -0500 Subject: [PATCH 59/64] multipathd: move post-reloading commands into resize_map() In preparation for reusing resize_map() in other code, move all code necessary to resize the map to the resize_map() function. Also track if map was removed in the function. Reviewed-by: Martin Wilck Signed-off-by: Benjamin Marzinski --- multipathd/cli_handlers.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c index 37a999ac9..cf75b45ff 100644 --- a/multipathd/cli_handlers.c +++ b/multipathd/cli_handlers.c @@ -835,6 +835,10 @@ static int resize_map(struct multipath *mpp, unsigned long long size, mpp->size = orig_size; return 1; } + if (setup_multipath(vecs, mpp) != 0) + return 2; + sync_map_state(mpp); + return 0; } @@ -848,7 +852,7 @@ cli_resize(void *v, struct strbuf *reply, void *data) unsigned long long size = 0; struct pathgroup *pgp; struct path *pp; - unsigned int i, j; + unsigned int i, j, ret; bool mismatch = false; mapname = convert_dev(mapname, 0); @@ -898,14 +902,12 @@ cli_resize(void *v, struct strbuf *reply, void *data) condlog(3, "%s old size is %llu, new size is %llu", mapname, mpp->size, size); - if (resize_map(mpp, size, vecs) != 0) - return 1; + ret = resize_map(mpp, size, vecs); - if (setup_multipath(vecs, mpp) != 0) - return 1; - sync_map_state(mpp); + if (ret == 2) + condlog(0, "%s: map removed while trying to resize", mapname); - return 0; + return (ret != 0); } static int From 8aac8f69b9c7c6a8de09c035f75d007df8dd4ef9 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Thu, 9 Nov 2023 18:46:15 -0500 Subject: [PATCH 60/64] multipathd: move resize_map() to multipathd/main.c No functional changes. Reviewed-by: Martin Wilck Signed-off-by: Benjamin Marzinski --- multipathd/cli_handlers.c | 29 ----------------------------- multipathd/main.c | 29 +++++++++++++++++++++++++++++ multipathd/main.h | 2 ++ 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c index cf75b45ff..420d75dfe 100644 --- a/multipathd/cli_handlers.c +++ b/multipathd/cli_handlers.c @@ -813,35 +813,6 @@ cli_reload(void *v, struct strbuf *reply, void *data) return reload_and_sync_map(mpp, vecs); } -static int resize_map(struct multipath *mpp, unsigned long long size, - struct vectors * vecs) -{ - char *params __attribute__((cleanup(cleanup_charp))) = NULL; - unsigned long long orig_size = mpp->size; - - mpp->size = size; - update_mpp_paths(mpp, vecs->pathvec); - if (setup_map(mpp, ¶ms, vecs) != 0) { - condlog(0, "%s: failed to setup map for resize : %s", - mpp->alias, strerror(errno)); - mpp->size = orig_size; - return 1; - } - mpp->action = ACT_RESIZE; - mpp->force_udev_reload = 1; - if (domap(mpp, params, 1) == DOMAP_FAIL) { - condlog(0, "%s: failed to resize map : %s", mpp->alias, - strerror(errno)); - mpp->size = orig_size; - return 1; - } - if (setup_multipath(vecs, mpp) != 0) - return 2; - sync_map_state(mpp); - - return 0; -} - static int cli_resize(void *v, struct strbuf *reply, void *data) { diff --git a/multipathd/main.c b/multipathd/main.c index 214ed4ae9..3b4c5b092 100644 --- a/multipathd/main.c +++ b/multipathd/main.c @@ -1530,6 +1530,35 @@ needs_ro_update(struct multipath *mpp, int ro) return true; } +int resize_map(struct multipath *mpp, unsigned long long size, + struct vectors * vecs) +{ + char *params __attribute__((cleanup(cleanup_charp))) = NULL; + unsigned long long orig_size = mpp->size; + + mpp->size = size; + update_mpp_paths(mpp, vecs->pathvec); + if (setup_map(mpp, ¶ms, vecs) != 0) { + condlog(0, "%s: failed to setup map for resize : %s", + mpp->alias, strerror(errno)); + mpp->size = orig_size; + return 1; + } + mpp->action = ACT_RESIZE; + mpp->force_udev_reload = 1; + if (domap(mpp, params, 1) == DOMAP_FAIL) { + condlog(0, "%s: failed to resize map : %s", mpp->alias, + strerror(errno)); + mpp->size = orig_size; + return 1; + } + if (setup_multipath(vecs, mpp) != 0) + return 2; + sync_map_state(mpp); + + return 0; +} + static int uev_update_path (struct uevent *uev, struct vectors * vecs) { diff --git a/multipathd/main.h b/multipathd/main.h index a253d1867..8a178c0be 100644 --- a/multipathd/main.h +++ b/multipathd/main.h @@ -52,4 +52,6 @@ int reload_and_sync_map(struct multipath *mpp, struct vectors *vecs); void handle_path_wwid_change(struct path *pp, struct vectors *vecs); bool check_path_wwid_change(struct path *pp); int finish_path_init(struct path *pp, struct vectors * vecs); +int resize_map(struct multipath *mpp, unsigned long long size, + struct vectors *vecs); #endif /* MAIN_H */ From 981b83ad19a3b38a14bcb50c814a1634e027c9f2 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Thu, 9 Nov 2023 18:46:16 -0500 Subject: [PATCH 61/64] multipathd: Add auto_resize config option This option gives multipathd the ability to automatically resize a device when it detects that all of the path devices have changed. By default it is set to never, and multipathd will continue to work like it always has, where a users must manually resize a multipath device. Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/config.h | 1 + libmultipath/defaults.h | 1 + libmultipath/dict.c | 38 +++++++++++++++++++++++++++++++++++ libmultipath/dict.h | 1 + libmultipath/structs.h | 7 +++++++ multipath/multipath.conf.5.in | 15 ++++++++++++++ multipathd/main.c | 28 ++++++++++++++++++++++++++ 7 files changed, 91 insertions(+) diff --git a/libmultipath/config.h b/libmultipath/config.h index 417e5834c..384193ab0 100644 --- a/libmultipath/config.h +++ b/libmultipath/config.h @@ -202,6 +202,7 @@ struct config { int skip_delegate; unsigned int sequence_nr; int recheck_wwid; + int auto_resize; char * selector; struct _vector uid_attrs; diff --git a/libmultipath/defaults.h b/libmultipath/defaults.h index d01f97122..64b633f26 100644 --- a/libmultipath/defaults.h +++ b/libmultipath/defaults.h @@ -56,6 +56,7 @@ #define DEFAULT_UNKNOWN_FIND_MULTIPATHS_TIMEOUT 1 #define DEFAULT_ALL_TG_PT ALL_TG_PT_OFF #define DEFAULT_RECHECK_WWID RECHECK_WWID_OFF +#define DEFAULT_AUTO_RESIZE AUTO_RESIZE_NEVER /* Enable no foreign libraries by default */ #define DEFAULT_ENABLE_FOREIGN "NONE" diff --git a/libmultipath/dict.c b/libmultipath/dict.c index e268673fc..0a160e925 100644 --- a/libmultipath/dict.c +++ b/libmultipath/dict.c @@ -1664,6 +1664,43 @@ declare_hw_snprint(recheck_wwid, print_yes_no_undef) declare_def_range_handler(uxsock_timeout, DEFAULT_REPLY_TIMEOUT, INT_MAX) +static int +def_auto_resize_handler(struct config *conf, vector strvec, const char *file, + int line_nr) +{ + char * buff; + + buff = set_value(strvec); + if (!buff) + return 1; + + if (strcmp(buff, "never") == 0) + conf->auto_resize = AUTO_RESIZE_NEVER; + else if (strcmp(buff, "grow_only") == 0) + conf->auto_resize = AUTO_RESIZE_GROW_ONLY; + else if (strcmp(buff, "grow_shrink") == 0) + conf->auto_resize = AUTO_RESIZE_GROW_SHRINK; + else + condlog(1, "%s line %d, invalid value for auto_resize: \"%s\"", + file, line_nr, buff); + + free(buff); + return 0; +} + +int +print_auto_resize(struct strbuf *buff, long v) +{ + if (!v) + return 0; + return append_strbuf_quoted(buff, + v == AUTO_RESIZE_GROW_ONLY ? "grow_only" : + v == AUTO_RESIZE_GROW_SHRINK ? "grow_shrink" : + "never"); +} + +declare_def_snprint(auto_resize, print_auto_resize) + static int hw_vpd_vendor_handler(struct config *conf, vector strvec, const char *file, int line_nr) @@ -2140,6 +2177,7 @@ init_keywords(vector keywords) install_keyword("remove_retries", &def_remove_retries_handler, &snprint_def_remove_retries); install_keyword("max_sectors_kb", &def_max_sectors_kb_handler, &snprint_def_max_sectors_kb); install_keyword("ghost_delay", &def_ghost_delay_handler, &snprint_def_ghost_delay); + install_keyword("auto_resize", &def_auto_resize_handler, &snprint_def_auto_resize); install_keyword("find_multipaths_timeout", &def_find_multipaths_timeout_handler, &snprint_def_find_multipaths_timeout); diff --git a/libmultipath/dict.h b/libmultipath/dict.h index 15d9cbac4..7e2dfbe0b 100644 --- a/libmultipath/dict.h +++ b/libmultipath/dict.h @@ -17,4 +17,5 @@ int print_no_path_retry(struct strbuf *buff, long v); int print_undef_off_zero(struct strbuf *buff, long v); int print_dev_loss(struct strbuf *buff, unsigned long v); int print_off_int_undef(struct strbuf *buff, long v); +int print_auto_resize(struct strbuf *buff, long v); #endif /* _DICT_H */ diff --git a/libmultipath/structs.h b/libmultipath/structs.h index 63551b806..a1aac1b43 100644 --- a/libmultipath/structs.h +++ b/libmultipath/structs.h @@ -179,6 +179,13 @@ enum queue_mode_states { QUEUE_MODE_RQ, }; +enum auto_resize_state { + AUTO_RESIZE_UNDEF = 0, + AUTO_RESIZE_NEVER, + AUTO_RESIZE_GROW_ONLY, + AUTO_RESIZE_GROW_SHRINK, +}; + #define PROTOCOL_UNSET -1 enum scsi_protocol { diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in index 3a3164c00..683bdb729 100644 --- a/multipath/multipath.conf.5.in +++ b/multipath/multipath.conf.5.in @@ -1331,6 +1331,21 @@ The default is: \fBno\fR . . .TP +.B auto_resize +Controls when multipathd will automatically resize a multipath device. If set +to \fInever\fR, multipath devices must always be manually resized by either +running \fBmultipathd resize map \fR. If set to \fIgrow_only\fR, when +multipathd detects that all of a multipath device's paths have increased in +size, it will automatically grow the multipath device to the new size. If set +to \fIgrow_shrink\fR, multipathd will also automatically shrink the device +once it detects all of its paths have decreased in size. +.RS +.TP +The default is: \fBnever\fR +.RE +. +. +.TP .B enable_foreign Enables or disables foreign libraries (see section .I FOREIGN MULTIPATH SUPPORT diff --git a/multipathd/main.c b/multipathd/main.c index 3b4c5b092..230c9d100 100644 --- a/multipathd/main.c +++ b/multipathd/main.c @@ -1590,6 +1590,11 @@ uev_update_path (struct uevent *uev, struct vectors * vecs) if (pp) { struct multipath *mpp = pp->mpp; char wwid[WWID_SIZE]; + int auto_resize; + + conf = get_multipath_config(); + auto_resize = conf->auto_resize; + put_multipath_config(conf); if (pp->initialized == INIT_REQUESTED_UDEV) { needs_reinit = 1; @@ -1648,6 +1653,29 @@ uev_update_path (struct uevent *uev, struct vectors * vecs) } } } + if (auto_resize != AUTO_RESIZE_NEVER && + !mpp->wait_for_udev) { + struct pathgroup *pgp; + struct path *pp2; + unsigned int i, j; + unsigned long long orig_size = mpp->size; + + if (!pp->size || pp->size == mpp->size || + (pp->size < mpp->size && + auto_resize == AUTO_RESIZE_GROW_ONLY)) + goto out; + + vector_foreach_slot(mpp->pg, pgp, i) + vector_foreach_slot (pgp->paths, pp2, j) + if (pp2->size && pp2->size != pp->size) + goto out; + retval = resize_map(mpp, pp->size, vecs); + if (retval == 2) + condlog(2, "%s: map removed during resize", pp->dev); + else if (retval == 0) + condlog(2, "%s: resized map from %llu to %llu", + mpp->alias, orig_size, pp->size); + } } out: lock_cleanup_pop(vecs->lock); From 2ab95b4e337f0b08621cda66be050cc81121cf78 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Thu, 2 Nov 2023 18:15:07 -0400 Subject: [PATCH 62/64] libmultipath.version: bump ABI version to 22.0.0 Commits ("libmultipath: Add max_retries config option") and ("multipathd: Add auto_resize config option") changed the size and member offsets of multiple structs. Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- libmultipath/libmultipath.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version index 8368ef7ae..8ab7c8021 100644 --- a/libmultipath/libmultipath.version +++ b/libmultipath/libmultipath.version @@ -43,7 +43,7 @@ LIBMPATHCOMMON_1.0.0 { put_multipath_config; }; -LIBMULTIPATH_21.0.0 { +LIBMULTIPATH_22.0.0 { global: /* symbols referenced by multipath and multipathd */ add_foreign; From 225bac906ee18b848f55ac6c3ef96eb697999d6c Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Mon, 13 Nov 2023 15:04:14 +0100 Subject: [PATCH 63/64] multipath-tools: set usr_prefix to /usr in default configuration In the default configuration (both prefix and usr_prefix unset), we'd install man pages and headers under /share/man and /include, respectively, which is very unusual. Have usr_prefix default to /usr in (the default) case where prefix is empty, and set it equal to /prefix otherwise. Signed-off-by: Martin Wilck Reviewed-by: Benjamin Marzinski --- Makefile.inc | 2 +- README.md | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Makefile.inc b/Makefile.inc index a20e2cec4..6b4543035 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -36,7 +36,7 @@ prefix := # Prefix for binaries exec_prefix := $(prefix) # Prefix for non-essential libraries (libdmmp) -usr_prefix := $(prefix) +usr_prefix := $(if $(prefix),$(prefix),/usr) # Prefix for configfuration files (multipath.conf) etc_prefix := $(prefix) # Where to install systemd-related files. systemd is usually installed under /usr diff --git a/README.md b/README.md index db03a33c0..d4f35f57c 100644 --- a/README.md +++ b/README.md @@ -131,8 +131,7 @@ The following variables can be passed to the `make` command line: "Usr-merged" distributions[^systemd] may want to set this to `/usr`. The default is empty (`""`). * `usr_prefix`: where to install those parts of the code that aren't necessary - for booting. Non-usr-merged distributions[^systemd] may want to set this to - `/usr`. The default is `$(prefix)`. + for booting. The default is `/usr` if `$(prefix)` is empty, and `$(prefix)` otherwise. * `systemd_prefix`: Prefix for systemd-related files[^systemd]. The default is `/usr`. * `etc_prefix`: The prefix for configuration files. "usr-merged" distributions with immutable `/usr`[^systemd] may want to set this to From bbb7478ea0cfeb643324a76e2b95a5e77a3c093a Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 16 Nov 2023 21:59:18 +0100 Subject: [PATCH 64/64] libmultipath: bump version to 0.9.7 --- libmultipath/version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libmultipath/version.h b/libmultipath/version.h index 00a521ad2..9c549c1a7 100644 --- a/libmultipath/version.h +++ b/libmultipath/version.h @@ -20,9 +20,9 @@ #ifndef _VERSION_H #define _VERSION_H -#define VERSION_CODE 0x000906 +#define VERSION_CODE 0x000907 /* MMDDYY, in hex */ -#define DATE_CODE 0x070517 +#define DATE_CODE 0x0B1017 #define PROG "multipath-tools"