From c507dc1aca322935d6949c5c80afc6ff9dc7bce6 Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Sun, 11 Oct 2026 17:04:06 +0000 Subject: [PATCH 1/3] libnvme: do not merge identical controllers when scanning the topology libnvme_ctrl_alloc() looks for an existing controller object matching the transport parameters of the controller being scanned. Controllers which match but carry a different name are skipped and the search continues. When the search ends without a result, the last skipped controller was used anyway: if (!c) c = p; This was harmless as long as the lookup created a new controller when no match was found, so that c was never NULL. Since the lookup only finds existing controllers, the fallback reuses and renames the object of a different controller and the create path below is never reached. As a result, multiple connections with identical parameters, e.g. loop controllers or controllers connected with --duplicate-connect, collapse into a single controller object. Only one of them is visible in the tree and 'nvme disconnect -n ' leaves the others connected. Drop the fallback so that a new controller object is created instead. Add a sysfs fixture with two identical loop controllers. Signed-off-by: Daniel Wagner --- libnvme/src/nvme/tree.c | 3 --- .../sysfs/data/tree-loop-duplicate-hostnqn.out | 1 + .../tests/sysfs/data/tree-loop-duplicate.out | 7 +++++++ .../tests/sysfs/data/tree-loop-duplicate.tar.xz | Bin 0 -> 23484 bytes libnvme/tests/sysfs/meson.build | 1 + 5 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 libnvme/tests/sysfs/data/tree-loop-duplicate-hostnqn.out create mode 100644 libnvme/tests/sysfs/data/tree-loop-duplicate.out create mode 100644 libnvme/tests/sysfs/data/tree-loop-duplicate.tar.xz diff --git a/libnvme/src/nvme/tree.c b/libnvme/src/nvme/tree.c index 0c93aa71d2..b9741bab6f 100644 --- a/libnvme/src/nvme/tree.c +++ b/libnvme/src/nvme/tree.c @@ -1457,9 +1457,6 @@ int libnvme_ctrl_alloc(struct libnvme_global_ctx *ctx, struct libnvme_subsystem } } while (c); - if (!c) - c = p; - if (!c) { ret = libnvme_subsystem_create_ctrl(s, ¶ms, &c); if (ret) { diff --git a/libnvme/tests/sysfs/data/tree-loop-duplicate-hostnqn.out b/libnvme/tests/sysfs/data/tree-loop-duplicate-hostnqn.out new file mode 100644 index 0000000000..897d966b99 --- /dev/null +++ b/libnvme/tests/sysfs/data/tree-loop-duplicate-hostnqn.out @@ -0,0 +1 @@ +resolved host: hostnqn=nqn.2014-08.org.nvmexpress:uuid:066d3885-3e3f-498c-b6e8-695c71636a13 hostid=84a30b8e-670d-4d8f-a006-f85d146ca9fa diff --git a/libnvme/tests/sysfs/data/tree-loop-duplicate.out b/libnvme/tests/sysfs/data/tree-loop-duplicate.out new file mode 100644 index 0000000000..0ca5e4db77 --- /dev/null +++ b/libnvme/tests/sysfs/data/tree-loop-duplicate.out @@ -0,0 +1,7 @@ +host nqn.2014-08.org.nvmexpress:uuid:066d3885-3e3f-498c-b6e8-695c71636a13 hostid=84a30b8e-670d-4d8f-a006-f85d146ca9fa + subsystem nvme-subsys0 nqn.2014-08.org.example:dup + ns 1 nvme0n1 + path nvme0c0n1 ana=optimized numa=- qdepth=0 + ctrl nvme0 transport=loop + path nvme0c1n1 ana=optimized numa=- qdepth=0 + ctrl nvme1 transport=loop diff --git a/libnvme/tests/sysfs/data/tree-loop-duplicate.tar.xz b/libnvme/tests/sysfs/data/tree-loop-duplicate.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..c0e65d455c48280b27bf75b0142a6bc868132347 GIT binary patch literal 23484 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 Date: Sun, 11 Oct 2026 17:12:52 +0000 Subject: [PATCH 2/3] nvme-print-json: print an empty address for controllers without one Controllers which do not report an address, e.g. loop controllers, return NULL from libnvme_ctrl_get_address() and the JSON printers emit "Address":null, while other missing sysfs attributes are printed as an empty string. Add obj_add_ctrl_address() which prints an empty string in this case and use it for all controller addresses. Signed-off-by: Daniel Wagner --- src/nvme-print-json.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/nvme-print-json.c b/src/nvme-print-json.c index a6c54aaa21..dd35491241 100644 --- a/src/nvme-print-json.c +++ b/src/nvme-print-json.c @@ -113,6 +113,14 @@ static void obj_add_prix64(struct json_object *o, const char *k, uint64_t v) obj_add_str(o, k, str); } +static void obj_add_ctrl_address(struct json_object *o, struct libnvme_ctrl *c) +{ + const char *address = libnvme_ctrl_get_address(c); + + /* controllers without an address, e.g. loop, don't report one */ + obj_add_str(o, "Address", address ? address : ""); +} + static void obj_add_ctrl_address_details(struct json_object *o, const char *k, struct libnvme_ctrl *c) { @@ -2859,7 +2867,7 @@ static unsigned int json_print_nvme_subsystem_multipath(struct libnvme_subsystem path_attrs = json_create_object(); obj_add_str(path_attrs, "Name", libnvme_ctrl_get_name(c)); obj_add_str(path_attrs, "Transport", libnvme_ctrl_get_transport(c)); - obj_add_str(path_attrs, "Address", libnvme_ctrl_get_address(c)); + obj_add_ctrl_address(path_attrs, c); obj_add_ctrl_address_details(path_attrs, "AddressDetails", c); obj_add_str(path_attrs, "State", libnvme_ctrl_get_state(c)); obj_add_str(path_attrs, "ANAState", ana_state); @@ -2881,7 +2889,7 @@ static void json_print_nvme_subsystem_ctrls(struct libnvme_subsystem *s, path_attrs = json_create_object(); obj_add_str(path_attrs, "Name", libnvme_ctrl_get_name(c)); obj_add_str(path_attrs, "Transport", libnvme_ctrl_get_transport(c)); - obj_add_str(path_attrs, "Address", libnvme_ctrl_get_address(c)); + obj_add_ctrl_address(path_attrs, c); obj_add_ctrl_address_details(path_attrs, "AddressDetails", c); obj_add_str(path_attrs, "State", libnvme_ctrl_get_state(c)); array_add_obj(paths, path_attrs); @@ -4890,7 +4898,7 @@ static void json_print_detail_list_multipath(struct libnvme_subsystem *s, obj_add_str(jpath, "ModelNumber", model); obj_add_str(jpath, "Firmware", firmware); obj_add_str(jpath, "Transport", libnvme_ctrl_get_transport(c)); - obj_add_str(jpath, "Address", libnvme_ctrl_get_address(c)); + obj_add_ctrl_address(jpath, c); obj_add_ctrl_address_details(jpath, "AddressDetails", c); obj_add_str(jpath, "Slot", slot ? slot : ""); @@ -4930,7 +4938,7 @@ static void json_print_detail_list(struct libnvme_subsystem *s, struct json_obje obj_add_str(jctrl, "ModelNumber", model); obj_add_str(jctrl, "Firmware", firmware); obj_add_str(jctrl, "Transport", libnvme_ctrl_get_transport(c)); - obj_add_str(jctrl, "Address", libnvme_ctrl_get_address(c)); + obj_add_ctrl_address(jctrl, c); obj_add_ctrl_address_details(jctrl, "AddressDetails", c); obj_add_str(jctrl, "Slot", slot ? slot : ""); @@ -5053,7 +5061,7 @@ static void json_detail_list(struct libnvme_global_ctx *ctx) obj_add_str(jctrl, "ModelNumber", model); obj_add_str(jctrl, "Firmware", firmware); obj_add_str(jctrl, "Transport", libnvme_ctrl_get_transport(c)); - obj_add_str(jctrl, "Address", libnvme_ctrl_get_address(c)); + obj_add_ctrl_address(jctrl, c); obj_add_ctrl_address_details(jctrl, "AddressDetails", c); obj_add_str(jctrl, "Slot", slot); @@ -5262,7 +5270,7 @@ static unsigned int json_subsystem_topology_multipath(struct libnvme_subsystem * ctrl_attrs = json_create_object(); obj_add_str(ctrl_attrs, "Name", libnvme_ctrl_get_name(c)); obj_add_str(ctrl_attrs, "Transport", libnvme_ctrl_get_transport(c)); - obj_add_str(ctrl_attrs, "Address", libnvme_ctrl_get_address(c)); + obj_add_ctrl_address(ctrl_attrs, c); obj_add_ctrl_address_details(ctrl_attrs, "AddressDetails", c); obj_add_str(ctrl_attrs, "State", libnvme_ctrl_get_state(c)); array_add_obj(ctrls, ctrl_attrs); @@ -5300,8 +5308,7 @@ static void json_print_nvme_subsystem_topology(struct libnvme_subsystem *s, libnvme_ctrl_get_name(c)); obj_add_str(ctrl_attrs, "Transport", libnvme_ctrl_get_transport(c)); - obj_add_str(ctrl_attrs, "Address", - libnvme_ctrl_get_address(c)); + obj_add_ctrl_address(ctrl_attrs, c); obj_add_ctrl_address_details(ctrl_attrs, "AddressDetails", c); obj_add_str(ctrl_attrs, "State", libnvme_ctrl_get_state(c)); From bad2691fa586a674c140be88cfddb608271fc77a Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Sun, 11 Oct 2026 17:33:35 +0000 Subject: [PATCH 3/3] libnvme/registry: stamp entries created by libnvmf_registry_update() Entries written on connect are built in a temporary directory together with the seqnum stamp the udev REMOVE rule relies on and renamed into place. libnvmf_registry_update() created a missing entry with a plain mkdir() instead, so the entry had no stamp and was briefly visible without any attribute. An orchestrator registering a controller it connected through the public API therefore lost the protection against a recycled instance number. Factor out the creation of an entry from libnvmf_registry_create_instance() and use it in libnvmf_registry_update() when the entry does not exist yet. Removing an attribute of a missing entry no longer creates an empty entry. Signed-off-by: Daniel Wagner --- libnvme/src/nvme/registry.c | 125 ++++++++++++++++++------------------ libnvme/src/nvme/registry.h | 6 +- libnvme/tests/registry.c | 89 +++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 64 deletions(-) diff --git a/libnvme/src/nvme/registry.c b/libnvme/src/nvme/registry.c index d719827165..29c30a1f06 100644 --- a/libnvme/src/nvme/registry.c +++ b/libnvme/src/nvme/registry.c @@ -74,25 +74,6 @@ static char *registry_path(struct libnvme_global_ctx *ctx, const char *device, return n < 0 ? NULL : path; } -static int ensure_device_dir(struct libnvme_global_ctx *ctx, const char *device) -{ - __cleanup_free char *path = NULL; - int ret; - - ret = shr_mkdir_p(registry_dir(ctx), 0755); - if (ret) - return ret; - - path = registry_path(ctx, device, NULL); - if (!path) - return -ENOMEM; - - /* EEXIST is success: two concurrent creates race to the same result. */ - if (mkdir(path, 0755) < 0 && errno != EEXIST) - return -errno; - return 0; -} - static bool valid_device(const char *device) { const char *p; @@ -253,33 +234,6 @@ static int write_attr_atomic(int dir_fd, const char *dir_path, return ret; } -/* - * Open the directory for @device (creating it if needed) and write @attr=@value - * atomically. Shared by the create and update paths. - */ -static int write_device_attr(struct libnvme_global_ctx *ctx, const char *device, - const char *attr, const char *value) -{ - __cleanup_free char *dir_path = NULL; - int dir_fd, ret; - - ret = ensure_device_dir(ctx, device); - if (ret) - return ret; - - dir_path = registry_path(ctx, device, NULL); - if (!dir_path) - return -ENOMEM; - - dir_fd = open(dir_path, O_RDONLY | O_DIRECTORY | O_CLOEXEC); - if (dir_fd < 0) - return -errno; - - ret = write_attr_atomic(dir_fd, dir_path, attr, value); - close(dir_fd); - return ret; -} - /* * Read the kernel's global uevent sequence number from * /sys/kernel/uevent_seqnum. This monotonic counter is used to stamp a @@ -386,29 +340,26 @@ static int delete_dir_atomic(struct libnvme_global_ctx *ctx, const char *path) } /* - * libnvmf_registry_create_instance - Write a registry entry for a freshly - * connected controller. Internal; called from the connect path in fabrics.c - * once the kernel returns instance=N. Always overwrites any pre-existing - * entry: instance recycling means an old nvmeN/ directory is stale by - * definition. + * Create the registry entry for @device holding the seqnum stamp and + * @attr=@value. * * The entry is built in a temporary directory and rename()'d into place, so it * is only ever observed complete or absent — never as a directory that exists * but is missing its seqnum stamp. Without this, the udev REMOVE rule could * read a just-created entry between the mkdir and the seqnum write, see no * stamp, and delete the live entry the library is still populating. + * + * With @replace, any pre-existing entry is removed first. Otherwise -EEXIST + * is returned if an entry already exists. */ -int libnvmf_registry_create_instance(struct libnvme_global_ctx *ctx, - int instance, const char *owner) +static int create_entry(struct libnvme_global_ctx *ctx, const char *device, + const char *attr, const char *value, bool replace) { __cleanup_free char *tmp_dir = NULL; __cleanup_free char *final_path = NULL; __cleanup_free char *seqnum = NULL; - char device[32]; int dir_fd, ret; - snprintf(device, sizeof(device), "nvme%d", instance); - final_path = registry_path(ctx, device, NULL); if (!final_path) return -ENOMEM; @@ -446,19 +397,21 @@ int libnvmf_registry_create_instance(struct libnvme_global_ctx *ctx, if (seqnum) write_attr_atomic(dir_fd, tmp_dir, "seqnum", seqnum); - ret = write_attr_atomic(dir_fd, tmp_dir, "owner", owner); + ret = write_attr_atomic(dir_fd, tmp_dir, attr, value); close(dir_fd); if (ret) goto err; /* - * Reveal the fully-populated entry atomically. Instance recycling - * means any pre-existing nvmeN/ is stale, so remove it first: rename() - * onto a non-empty directory fails. + * Reveal the fully-populated entry atomically. rename() onto a + * non-empty directory fails, so a stale entry has to be removed first. */ - delete_dir_atomic(ctx, final_path); + if (replace) + delete_dir_atomic(ctx, final_path); if (rename(tmp_dir, final_path) < 0) { ret = -errno; + if (ret == -ENOTEMPTY) + ret = -EEXIST; goto err; } return 0; @@ -468,6 +421,23 @@ int libnvmf_registry_create_instance(struct libnvme_global_ctx *ctx, return ret; } +/* + * libnvmf_registry_create_instance - Write a registry entry for a freshly + * connected controller. Internal; called from the connect path in fabrics.c + * once the kernel returns instance=N. Always overwrites any pre-existing + * entry: instance recycling means an old nvmeN/ directory is stale by + * definition. + */ +int libnvmf_registry_create_instance(struct libnvme_global_ctx *ctx, + int instance, const char *owner) +{ + char device[32]; + + snprintf(device, sizeof(device), "nvme%d", instance); + + return create_entry(ctx, device, "owner", owner, true); +} + int libnvmf_registry_delete_instance(struct libnvme_global_ctx *ctx, int instance) { @@ -532,12 +502,43 @@ __shr_public int libnvmf_registry_update(struct libnvme_global_ctx *ctx, const char *device, const char *attr, const char *value) { + __cleanup_free char *dir_path = NULL; + int dir_fd, ret; + if (!ctx) return -EINVAL; if (!device || !valid_device(device)) return -EINVAL; + if (!valid_attr(attr)) + return -EINVAL; + + dir_path = registry_path(ctx, device, NULL); + if (!dir_path) + return -ENOMEM; + + dir_fd = open(dir_path, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (dir_fd < 0 && errno == ENOENT) { + /* Nothing to remove from an entry that does not exist. */ + if (!value) + return 0; + + /* + * A new entry needs its seqnum stamp for the udev REMOVE rule, + * so create it the same way as on connect. + */ + ret = create_entry(ctx, device, attr, value, false); + if (ret != -EEXIST) + return ret; - return write_device_attr(ctx, device, attr, value); + /* A concurrent writer created the entry first; update it. */ + dir_fd = open(dir_path, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + } + if (dir_fd < 0) + return -errno; + + ret = write_attr_atomic(dir_fd, dir_path, attr, value); + close(dir_fd); + return ret; } __shr_public int libnvmf_registry_delete(struct libnvme_global_ctx *ctx, diff --git a/libnvme/src/nvme/registry.h b/libnvme/src/nvme/registry.h index 8c46cd231c..0e5960bc36 100644 --- a/libnvme/src/nvme/registry.h +++ b/libnvme/src/nvme/registry.h @@ -64,8 +64,10 @@ int libnvmf_registry_attr_equal(struct libnvme_global_ctx *ctx, * @value: New attribute value, or NULL to remove the attribute file * * Writes the attribute value to the controller's registry entry using an - * atomic tmp-file rename. Creates the entry directory if it does not exist. - * If @value is NULL, the attribute file is removed instead. + * atomic tmp-file rename. If the entry does not exist, it is created + * atomically the same way as on connect, including the best-effort seqnum + * stamp for the udev cleanup rule. If @value is NULL, the attribute file is + * removed instead; a missing entry is left alone in this case. * * Return: 0 on success, negative errno from underlying system calls otherwise. */ diff --git a/libnvme/tests/registry.c b/libnvme/tests/registry.c index d00103db6a..8a87f9f6a3 100644 --- a/libnvme/tests/registry.c +++ b/libnvme/tests/registry.c @@ -191,6 +191,90 @@ static bool test_update_and_retrieve(struct libnvme_global_ctx *ctx) return pass; } +/* + * An entry created by libnvmf_registry_update() must look like one created on + * connect: it carries the seqnum stamp for the udev cleanup rule and appears + * atomically. Removing an attribute of a missing entry must not create one. + */ +static bool test_update_creates_entry(struct libnvme_global_ctx *ctx) +{ + char *value = NULL; + char path[512]; + struct stat st; + bool pass = true; + int ret; + + printf("test_update_creates_entry:\n"); + + ret = libnvmf_registry_update(ctx, "nvme6", "owner", NULL); + snprintf(path, sizeof(path), "%s/nvme6", regdir); + if (ret || stat(path, &st) == 0) { + printf(" - removing from a missing entry returned %d or created it [FAIL]\n", + ret); + pass = false; + } else { + printf(" - removing from a missing entry is a no-op [PASS]\n"); + } + + ret = libnvmf_registry_update(ctx, "nvme6", "owner", "udisks"); + if (ret) { + printf(" - update returned %d [FAIL]\n", ret); + return false; + } + + /* best-effort, see test_create() */ + if (access("/sys/kernel/uevent_seqnum", R_OK) == 0) { + ret = libnvmf_registry_retrieve(ctx, "nvme6", "seqnum", &value); + if (ret || !value || !*value) { + printf(" - no seqnum stamp, ret=%d [FAIL]\n", ret); + pass = false; + } else { + printf(" - seqnum='%s' [PASS]\n", value); + } + free(value); + value = NULL; + } + + /* updating an existing entry keeps the stamp */ + ret = libnvmf_registry_update(ctx, "nvme6", "note", "hello"); + if (ret) { + printf(" - second update returned %d [FAIL]\n", ret); + pass = false; + } + ret = libnvmf_registry_retrieve(ctx, "nvme6", "owner", &value); + if (ret || !value || strcmp(value, "udisks") != 0) { + printf(" - owner lost after second update [FAIL]\n"); + pass = false; + } else { + printf(" - owner='%s' kept [PASS]\n", value); + } + free(value); + + { + struct dirent *de; + bool leftover = false; + DIR *d = opendir(regdir); + + if (d) { + while ((de = readdir(d)) != NULL) { + if (strstr(de->d_name, ".tmp.")) { + printf(" - leftover '%s' [FAIL]\n", + de->d_name); + leftover = true; + } + } + closedir(d); + } + if (leftover) + pass = false; + else + printf(" - no leftover temp dir [PASS]\n"); + } + + libnvmf_registry_delete(ctx, "nvme6"); + return pass; +} + static bool test_special_chars(struct libnvme_global_ctx *ctx) { /* @@ -344,6 +428,10 @@ static void collect_attr(const char *attr, const char *value, void *user_data) { struct attr_result *r = user_data; + /* the best-effort seqnum stamp, see test_create() */ + if (!strcmp(attr, "seqnum")) + return; + if (r->count < 8) { snprintf(r->keys[r->count], sizeof(r->keys[0]), "%s", attr); snprintf(r->values[r->count], sizeof(r->values[0]), "%s", value); @@ -539,6 +627,7 @@ int main(int argc, char *argv[]) pass &= test_create(ctx); pass &= test_update_and_retrieve(ctx); + pass &= test_update_creates_entry(ctx); pass &= test_special_chars(ctx); pass &= test_delete(ctx); pass &= test_retrieve_missing(ctx);