Bug Summary

File:.build-ci/../libnvme/src/nvme/registry.c
Warning:line 400, column 8
Potential leak of memory pointed to by 'seqnum'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-redhat-linux-gnu -O3 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name registry.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/__w/nvme-cli/nvme-cli/.build-ci -fcoverage-compilation-dir=/__w/nvme-cli/nvme-cli/.build-ci -resource-dir /usr/bin/../lib/clang/22 -include /__w/nvme-cli/nvme-cli/.build-ci/nvme-config.h -I libnvme/src/libnvme3.so.1.0.0.p -I libnvme/src -I ../libnvme/src -I ccan -I ../ccan -I . -I .. -D _FILE_OFFSET_BITS=64 -D _GNU_SOURCE -U NDEBUG -internal-isystem /usr/bin/../lib/clang/22/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/16/../../../../x86_64-redhat-linux/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -std=gnu11 -ferror-limit 19 -fvisibility=hidden -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcolor-diagnostics -vectorize-loops -vectorize-slp -analyzer-opt-analyze-headers -analyzer-output=html -faddrsig -fdwarf2-cfi-asm -o /__w/nvme-cli/nvme-cli/.build-ci/scan-results/2026-10-24-125112-639-1 -x c ../libnvme/src/nvme/registry.c
1// SPDX-License-Identifier: LGPL-2.1-or-later
2/*
3 * This file is part of libnvme.
4 * Copyright (c) 2026 Dell Technologies Inc. or its subsidiaries.
5 *
6 * Authors: Martin Belanger <martin.belanger@dell.com>
7 */
8
9#include <dirent.h>
10#include <errno(*__errno_location ()).h>
11#include <fcntl.h>
12#include <limits.h>
13#include <stdbool.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <sys/stat.h>
18#include <sys/types.h>
19#include <unistd.h>
20
21#include <shared/compiler-attributes-util.h>
22#include <shared/fs-util.h>
23#include <shared/io-util.h>
24
25#include "cleanup.h"
26#include "private.h"
27#include "private-fabrics.h"
28#include "registry.h"
29
30#define REGISTRY_DIR_DEFAULT"/run" "/nvme/registry" RUNDIR"/run" "/nvme/registry"
31
32/*
33 * Root directory of the registry. In production this is REGISTRY_DIR_DEFAULT.
34 * A ctx test sandbox (libnvme_set_test_base_dir())
35 * reroots it to <test_base_dir>/registry; that path is built once and cached in
36 * a static. The cache is safe because the sandbox is a single-process test
37 * feature (see the matching note in exclusion.c): a process is either
38 * all-production or all-test, so the first call fixes the value for the run.
39 */
40static const char *registry_dir(struct libnvme_global_ctx *ctx)
41{
42 static const char *cache;
43 static char buf[PATH_MAX4096];
44 int n;
45
46 if (cache)
47 return cache;
48 if (!ctx->test_base_dir) {
49 cache = REGISTRY_DIR_DEFAULT"/run" "/nvme/registry";
50 return cache;
51 }
52 /* Sandbox: test_base_dir is a validated /tmp path; always fits. */
53 n = snprintf(buf, sizeof(buf), "%s/registry", ctx->test_base_dir);
54 cache = (n > 0 && (size_t)n < sizeof(buf)) ? buf : REGISTRY_DIR_DEFAULT"/run" "/nvme/registry";
55 return cache;
56}
57
58/*
59 * Build "<registry_dir>/<device>" (when @attr is NULL) or
60 * "<registry_dir>/<device>/<attr>". Returns a newly allocated string the
61 * caller must free, or NULL on allocation failure.
62 */
63static char *registry_path(struct libnvme_global_ctx *ctx, const char *device,
64 const char *attr)
65{
66 char *path = NULL((void*)0);
67 int n;
68
69 if (attr)
70 n = asprintf(&path, "%s/%s/%s", registry_dir(ctx),
71 device, attr);
72 else
73 n = asprintf(&path, "%s/%s", registry_dir(ctx), device);
74 return n < 0 ? NULL((void*)0) : path;
75}
76
77static bool_Bool valid_device(const char *device)
78{
79 const char *p;
80
81 if (!device || strncmp(device, "nvme", 4) != 0)
82 return false0;
83 p = device + 4;
84 if (!*p)
85 return false0;
86 for (; *p; p++) {
87 if (*p < '0' || *p > '9')
88 return false0;
89 }
90 return true1;
91}
92
93static bool_Bool valid_attr(const char *attr)
94{
95 const char *p;
96
97 if (!attr || !*attr)
98 return false0;
99 for (p = attr; *p; p++) {
100 if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
101 (*p >= '0' && *p <= '9') || *p == '_' || *p == '-')
102 continue;
103 return false0;
104 }
105 return true1;
106}
107
108/*
109 * read() up to @count bytes into @buf, retrying on EINTR. Returns the number
110 * of bytes read (possibly short, at EOF) or a negative errno.
111 */
112static ssize_t read_full(int fd, char *buf, size_t count)
113{
114 size_t off = 0;
115
116 while (off < count) {
117 ssize_t r = read(fd, buf + off, count - off);
118
119 if (r < 0) {
120 if (errno(*__errno_location ()) == EINTR4)
121 continue;
122 return -errno(*__errno_location ());
123 }
124 if (r == 0)
125 break;
126 off += r;
127 }
128 return off;
129}
130
131/*
132 * Read the attribute file open at @fd into a newly allocated, NUL-terminated
133 * string with the trailing newline stripped. On success sets *out (the caller
134 * frees) and returns 0. Returns -ENOENT for an empty file, or a negative
135 * errno on failure.
136 */
137static int read_attr_fd(int fd, char **out)
138{
139 struct stat st;
140 char *val;
141 ssize_t n;
142
143 if (fstat(fd, &st) < 0)
144 return -errno(*__errno_location ());
145 if (st.st_size == 0)
146 return -ENOENT2;
147
148 val = malloc(st.st_size + 1);
149 if (!val)
150 return -ENOMEM12;
151
152 n = read_full(fd, val, st.st_size);
153 if (n < 0) {
154 free(val);
155 return n;
156 }
157
158 while (n > 0 && (val[n - 1] == '\n' || val[n - 1] == '\r'))
159 n--;
160 val[n] = '\0';
161 *out = val;
162 return 0;
163}
164
165/*
166 * Write @value atomically to the attribute file @attr inside @dir_path; a NULL
167 * @value removes the attribute.
168 *
169 * Atomicity comes from never mutating the live file in place: @value is
170 * written to a temp file that is then rename(2)'d onto @attr. rename(2) is an
171 * atomic replace within a filesystem, so a concurrent reader always sees
172 * either the complete old file or the complete new one -- never a half-written
173 * value, and with no locking. The directory fsync makes the rename durable
174 * across a crash. The random temp name and O_CLOEXEC from
175 * shr_mkstemp() are secondary: they prevent name prediction / TOCTOU
176 * on the temp file and avoid leaking the fd across an exec.
177 *
178 * Steps: mkstemp <attr>.tmp.XXXXXX -> fchmod 0644 -> write @value -> rename
179 * onto <attr> -> fsync directory.
180 */
181static int write_attr_atomic(int dir_fd, const char *dir_path,
182 const char *attr, const char *value)
183{
184 __cleanup_free__attribute__((cleanup(shr_freep))) char *tmp_path = NULL((void*)0);
185 __cleanup_free__attribute__((cleanup(shr_freep))) char *final_path = NULL((void*)0);
186 int fd, ret;
187
188 if (!valid_attr(attr))
189 return -EINVAL22;
190
191 if (!value) {
192 if (unlinkat(dir_fd, attr, 0) < 0 && errno(*__errno_location ()) != ENOENT2)
193 return -errno(*__errno_location ());
194 fsync(dir_fd);
195 return 0;
196 }
197
198 if (asprintf(&final_path, "%s/%s", dir_path, attr) < 0)
199 return -ENOMEM12;
200 if (asprintf(&tmp_path, "%s/%s.tmp.XXXXXX", dir_path, attr) < 0)
201 return -ENOMEM12;
202
203 fd = shr_mkstemp(tmp_path);
204 if (fd < 0)
205 return fd;
206
207 /* the temp file is created with 0600; open it to the registry world. */
208 if (fchmod(fd, 0644) < 0) {
209 ret = -errno(*__errno_location ());
210 goto err;
211 }
212
213 ret = shr_write_all(fd, value, strlen(value));
214 if (ret)
215 goto err;
216 ret = shr_write_all(fd, "\n", 1);
217 if (ret)
218 goto err;
219 close(fd);
220
221 if (rename(tmp_path, final_path) < 0) {
222 ret = -errno(*__errno_location ());
223 unlink(tmp_path);
224 return ret;
225 }
226
227 /* Flush the rename to stable storage. */
228 fsync(dir_fd);
229 return 0;
230
231err:
232 close(fd);
233 unlink(tmp_path);
234 return ret;
235}
236
237/*
238 * Read the kernel's global uevent sequence number from
239 * /sys/kernel/uevent_seqnum. This monotonic counter is used to stamp a
240 * registry entry at connect time so the udev REMOVE rule can tell a stale
241 * entry apart from a recycled-and-live one without relying on event ordering
242 * (see 70-nvmf-registry.rules). Returns a newly allocated decimal string the
243 * caller must free, or NULL on any failure -- the stamp is best-effort, and a
244 * missing stamp simply makes the rule fall back to its device-existence guard.
245 */
246static char *read_uevent_seqnum(void)
247{
248 char buf[32];
249 ssize_t n;
250 int fd;
251
252 fd = open("/sys/kernel/uevent_seqnum", O_RDONLY00 | O_CLOEXEC02000000);
253 if (fd
22.1
'fd' is >= 0
< 0)
23
Taking false branch
254 return NULL((void*)0);
255 n = read_full(fd, buf, sizeof(buf) - 1);
256 close(fd);
257 if (n <= 0)
24
Assuming 'n' is > 0
258 return NULL((void*)0);
259
260 /* Strip the trailing newline the kernel appends. */
261 while (n
24.1
'n' is > 0
> 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r'))
25
Assuming the condition is false
26
Loop condition is false. Execution continues on line 263
262 n--;
263 if (n
26.1
'n' is not equal to 0
== 0)
27
Taking false branch
264 return NULL((void*)0);
265 buf[n] = '\0';
266
267 return strdup(buf);
28
Memory is allocated
268}
269
270/*
271 * Remove a registry entry directory and its (flat) attribute files. Shared by
272 * libnvmf_registry_delete() and the create_instance() error/cleanup paths.
273 * Returns 0 on success, -ENOENT if @path does not exist, or a negative errno.
274 */
275static int delete_dir(const char *path)
276{
277 struct dirent *de;
278 int dir_fd, ret = 0;
279 DIR *d;
280
281 d = opendir(path);
282 if (!d)
283 return -errno(*__errno_location ());
284
285 dir_fd = dirfd(d);
286 if (dir_fd < 0) {
287 ret = -errno(*__errno_location ());
288 closedir(d);
289 return ret;
290 }
291
292 while ((de = readdir(d)) != NULL((void*)0)) {
293 if (de->d_name[0] == '.')
294 continue;
295 if (unlinkat(dir_fd, de->d_name, 0) < 0 && errno(*__errno_location ()) != ENOENT2)
296 ret = -errno(*__errno_location ());
297 }
298 closedir(d);
299
300 if (ret)
301 return ret;
302
303 if (rmdir(path) < 0 && errno(*__errno_location ()) != ENOENT2)
304 return -errno(*__errno_location ());
305 return 0;
306}
307
308/*
309 * Atomically remove a *visible* registry entry: rename it to a hidden sibling
310 * so it vanishes from observers in a single step, then purge the renamed copy
311 * at leisure. This keeps a reader (nvme registry list, another orchestrator)
312 * from ever seeing a half-emptied entry. Returns 0 on success, -ENOENT if
313 * @path does not exist, or a negative errno.
314 *
315 * The udev REMOVE rule deletes entries with a plain "rm -rf", which is not
316 * atomic; this guarantee therefore covers only library-driven deletes.
317 */
318static int delete_dir_atomic(struct libnvme_global_ctx *ctx, const char *path)
319{
320 __cleanup_free__attribute__((cleanup(shr_freep))) char *trash = NULL((void*)0);
321
322 if (asprintf(&trash, "%s/.trash.XXXXXX", registry_dir(ctx)) < 0)
323 return -ENOMEM12;
324 if (!mkdtemp(trash))
325 return -errno(*__errno_location ());
326
327 /*
328 * rename() is the atomic check-and-act: don't pre-check existence (a
329 * TOCTOU on @path) — just attempt the move. If @path is absent (the
330 * common case, e.g. no stale entry to replace) it fails with ENOENT and
331 * we drop the empty temp dir we created.
332 */
333 if (rename(path, trash) < 0) {
334 int ret = -errno(*__errno_location ());
335
336 rmdir(trash);
337 return ret;
338 }
339 return delete_dir(trash);
340}
341
342/*
343 * Create the registry entry for @device holding the seqnum stamp and
344 * @attr=@value.
345 *
346 * The entry is built in a temporary directory and rename()'d into place, so it
347 * is only ever observed complete or absent — never as a directory that exists
348 * but is missing its seqnum stamp. Without this, the udev REMOVE rule could
349 * read a just-created entry between the mkdir and the seqnum write, see no
350 * stamp, and delete the live entry the library is still populating.
351 *
352 * With @replace, any pre-existing entry is removed first. Otherwise -EEXIST
353 * is returned if an entry already exists.
354 */
355static int create_entry(struct libnvme_global_ctx *ctx, const char *device,
356 const char *attr, const char *value, bool_Bool replace)
357{
358 __cleanup_free__attribute__((cleanup(shr_freep))) char *tmp_dir = NULL((void*)0);
359 __cleanup_free__attribute__((cleanup(shr_freep))) char *final_path = NULL((void*)0);
360 __cleanup_free__attribute__((cleanup(shr_freep))) char *seqnum = NULL((void*)0);
361 int dir_fd, ret;
362
363 final_path = registry_path(ctx, device, NULL((void*)0));
364 if (!final_path)
13
Assuming 'final_path' is non-null
14
Taking false branch
365 return -ENOMEM12;
366
367 /* The registry root must exist before mkdtemp() can run inside it. */
368 ret = shr_mkdir_p(registry_dir(ctx), 0755);
369 if (ret)
15
Assuming 'ret' is 0
16
Taking false branch
370 return ret;
371
372 if (asprintf(&tmp_dir, "%s/.%s.tmp.XXXXXX",
17
Assuming the condition is false
18
Taking false branch
373 registry_dir(ctx), device) < 0)
374 return -ENOMEM12;
375 if (!mkdtemp(tmp_dir))
19
Taking false branch
376 return -errno(*__errno_location ());
377
378 /* mkdtemp() creates it 0700; widen to the registry world. */
379 if (chmod(tmp_dir, 0755) < 0) {
20
Taking false branch
380 ret = -errno(*__errno_location ());
381 goto err;
382 }
383
384 dir_fd = open(tmp_dir, O_RDONLY00 | O_DIRECTORY0200000 | O_CLOEXEC02000000);
385 if (dir_fd
20.1
'dir_fd' is >= 0
< 0) {
21
Taking false branch
386 ret = -errno(*__errno_location ());
387 goto err;
388 }
389
390 /*
391 * Stamp the entry with the current uevent sequence number for the udev
392 * REMOVE rule (see read_uevent_seqnum()). Best-effort: a missing stamp
393 * just makes the rule fall back to its device-existence guard, so don't
394 * fail the create over it.
395 */
396 seqnum = read_uevent_seqnum();
22
Calling 'read_uevent_seqnum'
29
Returned allocated memory
397 if (seqnum)
30
Assuming 'seqnum' is non-null
31
Taking true branch
398 write_attr_atomic(dir_fd, tmp_dir, "seqnum", seqnum);
399
400 ret = write_attr_atomic(dir_fd, tmp_dir, attr, value);
32
Potential leak of memory pointed to by 'seqnum'
401 close(dir_fd);
402 if (ret)
403 goto err;
404
405 /*
406 * Reveal the fully-populated entry atomically. rename() onto a
407 * non-empty directory fails, so a stale entry has to be removed first.
408 */
409 if (replace)
410 delete_dir_atomic(ctx, final_path);
411 if (rename(tmp_dir, final_path) < 0) {
412 ret = -errno(*__errno_location ());
413 if (ret == -ENOTEMPTY39)
414 ret = -EEXIST17;
415 goto err;
416 }
417 return 0;
418
419err:
420 delete_dir(tmp_dir); /* best-effort: don't leak the temp directory */
421 return ret;
422}
423
424/*
425 * libnvmf_registry_create_instance - Write a registry entry for a freshly
426 * connected controller. Internal; called from the connect path in fabrics.c
427 * once the kernel returns instance=N. Always overwrites any pre-existing
428 * entry: instance recycling means an old nvmeN/ directory is stale by
429 * definition.
430 */
431int libnvmf_registry_create_instance(struct libnvme_global_ctx *ctx,
432 int instance, const char *owner)
433{
434 char device[32];
435
436 snprintf(device, sizeof(device), "nvme%d", instance);
437
438 return create_entry(ctx, device, "owner", owner, true1);
439}
440
441int libnvmf_registry_delete_instance(struct libnvme_global_ctx *ctx,
442 int instance)
443{
444 char device[32];
445 int ret;
446
447 snprintf(device, sizeof(device), "nvme%d", instance);
448 ret = libnvmf_registry_delete(ctx, device);
449 return (ret == -ENOENT2) ? 0 : ret;
450}
451
452__shr_public__attribute__((visibility("default"))) int libnvmf_registry_retrieve(struct libnvme_global_ctx *ctx,
453 const char *device,
454 const char *attr, char **value)
455{
456 __cleanup_free__attribute__((cleanup(shr_freep))) char *path = NULL((void*)0);
457 int fd, ret;
458
459 if (!ctx)
460 return -EINVAL22;
461 if (!device || !attr || !value)
462 return -EINVAL22;
463 if (!valid_device(device))
464 return -EINVAL22;
465
466 path = registry_path(ctx, device, attr);
467 if (!path)
468 return -ENOMEM12;
469
470 fd = open(path, O_RDONLY00 | O_CLOEXEC02000000);
471 if (fd < 0)
472 return -errno(*__errno_location ());
473
474 ret = read_attr_fd(fd, value);
475 close(fd);
476 return ret;
477}
478
479__shr_public__attribute__((visibility("default"))) int libnvmf_registry_attr_equal(struct libnvme_global_ctx *ctx,
480 const char *device,
481 const char *attr,
482 const char *value)
483{
484 char *stored = NULL((void*)0);
485 int rc;
486
487 if (!ctx)
488 return -EINVAL22;
489
490 rc = libnvmf_registry_retrieve(ctx, device, attr, &stored);
491 if (rc < 0 && rc != -ENOENT2)
492 return rc;
493 if (!stored)
494 rc = value ? 1 : 0;
495 else
496 rc = (value && !strcmp(stored, value)) ? 0 : 1;
497 free(stored);
498 return rc;
499}
500
501__shr_public__attribute__((visibility("default"))) int libnvmf_registry_update(struct libnvme_global_ctx *ctx,
502 const char *device,
503 const char *attr, const char *value)
504{
505 __cleanup_free__attribute__((cleanup(shr_freep))) char *dir_path = NULL((void*)0);
506 int dir_fd, ret;
507
508 if (!ctx)
1
Assuming 'ctx' is non-null
509 return -EINVAL22;
510 if (!device || !valid_device(device))
2
Assuming 'device' is non-null
3
Assuming the condition is false
4
Taking false branch
511 return -EINVAL22;
512 if (!valid_attr(attr))
5
Taking false branch
513 return -EINVAL22;
514
515 dir_path = registry_path(ctx, device, NULL((void*)0));
516 if (!dir_path)
6
Assuming 'dir_path' is non-null
7
Taking false branch
517 return -ENOMEM12;
518
519 dir_fd = open(dir_path, O_RDONLY00 | O_DIRECTORY0200000 | O_CLOEXEC02000000);
520 if (dir_fd
7.1
'dir_fd' is < 0
< 0 && errno(*__errno_location ()) == ENOENT2) {
8
Assuming the condition is true
9
Taking true branch
521 /* Nothing to remove from an entry that does not exist. */
522 if (!value)
10
Assuming 'value' is non-null
11
Taking false branch
523 return 0;
524
525 /*
526 * A new entry needs its seqnum stamp for the udev REMOVE rule,
527 * so create it the same way as on connect.
528 */
529 ret = create_entry(ctx, device, attr, value, false0);
12
Calling 'create_entry'
530 if (ret != -EEXIST17)
531 return ret;
532
533 /* A concurrent writer created the entry first; update it. */
534 dir_fd = open(dir_path, O_RDONLY00 | O_DIRECTORY0200000 | O_CLOEXEC02000000);
535 }
536 if (dir_fd < 0)
537 return -errno(*__errno_location ());
538
539 ret = write_attr_atomic(dir_fd, dir_path, attr, value);
540 close(dir_fd);
541 return ret;
542}
543
544__shr_public__attribute__((visibility("default"))) int libnvmf_registry_delete(struct libnvme_global_ctx *ctx,
545 const char *device)
546{
547 __cleanup_free__attribute__((cleanup(shr_freep))) char *path = NULL((void*)0);
548
549 if (!ctx)
550 return -EINVAL22;
551 if (!device || !valid_device(device))
552 return -EINVAL22;
553
554 path = registry_path(ctx, device, NULL((void*)0));
555 if (!path)
556 return -ENOMEM12;
557
558 return delete_dir_atomic(ctx, path);
559}
560
561__shr_public__attribute__((visibility("default"))) int libnvmf_registry_device_for_each(
562 struct libnvme_global_ctx *ctx,
563 int (*callback)(const char *device, void *user_data),
564 void *user_data)
565{
566 char dev_path[NAME_MAX255 + 6]; /* "/dev/" + name + NUL */
567 struct dirent *de;
568 int dir_fd;
569 DIR *d;
570 int ret = 0;
571
572 if (!ctx)
573 return -EINVAL22;
574 if (!callback)
575 return -EINVAL22;
576
577 d = opendir(registry_dir(ctx));
578 if (!d) {
579 if (errno(*__errno_location ()) == ENOENT2)
580 return 0;
581 return -errno(*__errno_location ());
582 }
583
584 dir_fd = dirfd(d);
585 if (dir_fd < 0) {
586 ret = -errno(*__errno_location ());
587 closedir(d);
588 return ret;
589 }
590
591 while ((de = readdir(d)) != NULL((void*)0)) {
592 if (de->d_name[0] == '.')
593 continue;
594
595 /*
596 * Only visit directories. Use stat() as a fallback when
597 * d_type is DT_UNKNOWN (e.g. on some network filesystems).
598 */
599 if (de->d_type != DT_DIRDT_DIR) {
600 struct stat st;
601
602 if (de->d_type != DT_UNKNOWNDT_UNKNOWN)
603 continue;
604 /*
605 * Resolve relative to the open directory rather than
606 * building an absolute path: avoids a fixed-size path
607 * buffer (and the format-truncation it invites).
608 */
609 if (fstatat(dir_fd, de->d_name, &st, 0) < 0 ||
610 !S_ISDIR(st.st_mode)((((st.st_mode)) & 0170000) == (0040000)))
611 continue;
612 }
613
614 /* Stale-entry check: skip if the device node is gone. */
615 snprintf(dev_path, sizeof(dev_path), "/dev/%s", de->d_name);
616 if (access(dev_path, F_OK0) != 0)
617 continue;
618
619 ret = callback(de->d_name, user_data);
620 if (ret)
621 break;
622 }
623 closedir(d);
624 return ret;
625}
626
627__shr_public__attribute__((visibility("default"))) int libnvmf_registry_attr_for_each(
628 struct libnvme_global_ctx *ctx,
629 const char *device,
630 void (*callback)(const char *attr, const char *value,
631 void *user_data),
632 void *user_data)
633{
634 __cleanup_free__attribute__((cleanup(shr_freep))) char *dir_path = NULL((void*)0);
635 struct dirent *de;
636 int dir_fd;
637 DIR *d;
638
639 if (!ctx)
640 return -EINVAL22;
641 if (!device || !callback)
642 return -EINVAL22;
643 if (!valid_device(device))
644 return -EINVAL22;
645
646 dir_path = registry_path(ctx, device, NULL((void*)0));
647 if (!dir_path)
648 return -ENOMEM12;
649
650 d = opendir(dir_path);
651 if (!d)
652 return -errno(*__errno_location ());
653
654 dir_fd = dirfd(d);
655 if (dir_fd < 0) {
656 int ret = -errno(*__errno_location ());
657
658 closedir(d);
659 return ret;
660 }
661
662 while ((de = readdir(d)) != NULL((void*)0)) {
663 __cleanup_free__attribute__((cleanup(shr_freep))) char *val = NULL((void*)0);
664 int fd, rc;
665
666 if (de->d_name[0] == '.')
667 continue;
668 /* Skip in-flight tmp files from concurrent writers. */
669 if (strstr(de->d_name, ".tmp.")_Generic (0 ? (de->d_name) : (void *) 1, const void *: (const
char *) (strstr (de->d_name, ".tmp.")), default: strstr (
de->d_name, ".tmp."))
)
670 continue;
671
672 fd = openat(dir_fd, de->d_name, O_RDONLY00 | O_CLOEXEC02000000);
673 if (fd < 0)
674 continue; /* device may have been removed concurrently */
675
676 rc = read_attr_fd(fd, &val);
677 close(fd);
678 if (rc == 0)
679 callback(de->d_name, val, user_data);
680 }
681 closedir(d);
682 return 0;
683}