Bug Summary

File:.build-ci/../libnvme/src/nvme/registry.c
Warning:line 335, column 7
The 1st argument to 'unlinkat' is -1 but should be a valid file descriptor or AT_FDCWD

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 .. -I shared -I ../shared -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=gnu99 -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-08-08-045408-589-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 <compiler-attributes.h>
22#include <fs-util.h>
23#include <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 int ensure_device_dir(struct libnvme_global_ctx *ctx, const char *device)
78{
79 __cleanup_free__attribute__((cleanup(shr_freep))) char *path = NULL((void*)0);
80 int ret;
81
82 ret = shr_mkdir_p(registry_dir(ctx), 0755);
83 if (ret)
84 return ret;
85
86 path = registry_path(ctx, device, NULL((void*)0));
87 if (!path)
88 return -ENOMEM12;
89
90 /* EEXIST is success: two concurrent creates race to the same result. */
91 if (mkdir(path, 0755) < 0 && errno(*__errno_location ()) != EEXIST17)
92 return -errno(*__errno_location ());
93 return 0;
94}
95
96static bool_Bool valid_device(const char *device)
97{
98 const char *p;
99
100 if (!device || strncmp(device, "nvme", 4) != 0)
101 return false0;
102 p = device + 4;
103 if (!*p)
104 return false0;
105 for (; *p; p++) {
106 if (*p < '0' || *p > '9')
107 return false0;
108 }
109 return true1;
110}
111
112static bool_Bool valid_attr(const char *attr)
113{
114 const char *p;
115
116 if (!attr || !*attr)
117 return false0;
118 for (p = attr; *p; p++) {
119 if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
120 (*p >= '0' && *p <= '9') || *p == '_' || *p == '-')
121 continue;
122 return false0;
123 }
124 return true1;
125}
126
127/*
128 * read() up to @count bytes into @buf, retrying on EINTR. Returns the number
129 * of bytes read (possibly short, at EOF) or a negative errno.
130 */
131static ssize_t read_full(int fd, char *buf, size_t count)
132{
133 size_t off = 0;
134
135 while (off < count) {
136 ssize_t r = read(fd, buf + off, count - off);
137
138 if (r < 0) {
139 if (errno(*__errno_location ()) == EINTR4)
140 continue;
141 return -errno(*__errno_location ());
142 }
143 if (r == 0)
144 break;
145 off += r;
146 }
147 return off;
148}
149
150/*
151 * Read the attribute file open at @fd into a newly allocated, NUL-terminated
152 * string with the trailing newline stripped. On success sets *out (the caller
153 * frees) and returns 0. Returns -ENOENT for an empty file, or a negative
154 * errno on failure.
155 */
156static int read_attr_fd(int fd, char **out)
157{
158 struct stat st;
159 char *val;
160 ssize_t n;
161
162 if (fstat(fd, &st) < 0)
163 return -errno(*__errno_location ());
164 if (st.st_size == 0)
165 return -ENOENT2;
166
167 val = malloc(st.st_size + 1);
168 if (!val)
169 return -ENOMEM12;
170
171 n = read_full(fd, val, st.st_size);
172 if (n < 0) {
173 free(val);
174 return n;
175 }
176
177 while (n > 0 && (val[n - 1] == '\n' || val[n - 1] == '\r'))
178 n--;
179 val[n] = '\0';
180 *out = val;
181 return 0;
182}
183
184/*
185 * Write @value atomically to the attribute file @attr inside @dir_path; a NULL
186 * @value removes the attribute.
187 *
188 * Atomicity comes from never mutating the live file in place: @value is
189 * written to a temp file that is then rename(2)'d onto @attr. rename(2) is an
190 * atomic replace within a filesystem, so a concurrent reader always sees
191 * either the complete old file or the complete new one -- never a half-written
192 * value, and with no locking. The directory fsync makes the rename durable
193 * across a crash. The random temp name and O_CLOEXEC from
194 * shr_mkstemp() are secondary: they prevent name prediction / TOCTOU
195 * on the temp file and avoid leaking the fd across an exec.
196 *
197 * Steps: mkstemp <attr>.tmp.XXXXXX -> fchmod 0644 -> write @value -> rename
198 * onto <attr> -> fsync directory.
199 */
200static int write_attr_atomic(int dir_fd, const char *dir_path,
201 const char *attr, const char *value)
202{
203 __cleanup_free__attribute__((cleanup(shr_freep))) char *tmp_path = NULL((void*)0);
204 __cleanup_free__attribute__((cleanup(shr_freep))) char *final_path = NULL((void*)0);
205 int fd, ret;
206
207 if (!valid_attr(attr))
208 return -EINVAL22;
209
210 if (!value) {
211 if (unlinkat(dir_fd, attr, 0) < 0 && errno(*__errno_location ()) != ENOENT2)
212 return -errno(*__errno_location ());
213 fsync(dir_fd);
214 return 0;
215 }
216
217 if (asprintf(&final_path, "%s/%s", dir_path, attr) < 0)
218 return -ENOMEM12;
219 if (asprintf(&tmp_path, "%s/%s.tmp.XXXXXX", dir_path, attr) < 0)
220 return -ENOMEM12;
221
222 fd = shr_mkstemp(tmp_path);
223 if (fd < 0)
224 return fd;
225
226 /* the temp file is created with 0600; open it to the registry world. */
227 if (fchmod(fd, 0644) < 0) {
228 ret = -errno(*__errno_location ());
229 goto err;
230 }
231
232 ret = shr_write_all(fd, value, strlen(value));
233 if (ret)
234 goto err;
235 ret = shr_write_all(fd, "\n", 1);
236 if (ret)
237 goto err;
238 close(fd);
239
240 if (rename(tmp_path, final_path) < 0) {
241 ret = -errno(*__errno_location ());
242 unlink(tmp_path);
243 return ret;
244 }
245
246 /* Flush the rename to stable storage. */
247 fsync(dir_fd);
248 return 0;
249
250err:
251 close(fd);
252 unlink(tmp_path);
253 return ret;
254}
255
256/*
257 * Open the directory for @device (creating it if needed) and write @attr=@value
258 * atomically. Shared by the create and update paths.
259 */
260static int write_device_attr(struct libnvme_global_ctx *ctx, const char *device,
261 const char *attr, const char *value)
262{
263 __cleanup_free__attribute__((cleanup(shr_freep))) char *dir_path = NULL((void*)0);
264 int dir_fd, ret;
265
266 ret = ensure_device_dir(ctx, device);
267 if (ret)
268 return ret;
269
270 dir_path = registry_path(ctx, device, NULL((void*)0));
271 if (!dir_path)
272 return -ENOMEM12;
273
274 dir_fd = open(dir_path, O_RDONLY00 | O_DIRECTORY0200000 | O_CLOEXEC02000000);
275 if (dir_fd < 0)
276 return -errno(*__errno_location ());
277
278 ret = write_attr_atomic(dir_fd, dir_path, attr, value);
279 close(dir_fd);
280 return ret;
281}
282
283/*
284 * Read the kernel's global uevent sequence number from
285 * /sys/kernel/uevent_seqnum. This monotonic counter is used to stamp a
286 * registry entry at connect time so the udev REMOVE rule can tell a stale
287 * entry apart from a recycled-and-live one without relying on event ordering
288 * (see 70-nvmf-registry.rules). Returns a newly allocated decimal string the
289 * caller must free, or NULL on any failure -- the stamp is best-effort, and a
290 * missing stamp simply makes the rule fall back to its device-existence guard.
291 */
292static char *read_uevent_seqnum(void)
293{
294 char buf[32];
295 ssize_t n;
296 int fd;
297
298 fd = open("/sys/kernel/uevent_seqnum", O_RDONLY00 | O_CLOEXEC02000000);
299 if (fd < 0)
300 return NULL((void*)0);
301 n = read_full(fd, buf, sizeof(buf) - 1);
302 close(fd);
303 if (n <= 0)
304 return NULL((void*)0);
305
306 /* Strip the trailing newline the kernel appends. */
307 while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r'))
308 n--;
309 if (n == 0)
310 return NULL((void*)0);
311 buf[n] = '\0';
312
313 return strdup(buf);
314}
315
316/*
317 * Remove a registry entry directory and its (flat) attribute files. Shared by
318 * libnvmf_registry_delete() and the create_instance() error/cleanup paths.
319 * Returns 0 on success, -ENOENT if @path does not exist, or a negative errno.
320 */
321static int delete_dir(const char *path)
322{
323 struct dirent *de;
324 int dir_fd, ret = 0;
325 DIR *d;
326
327 d = opendir(path);
14
Assuming that 'opendir' is successful
328 if (!d
14.1
'd' is non-null
)
15
Taking false branch
329 return -errno(*__errno_location ());
330
331 dir_fd = dirfd(d);
16
Assuming that 'dirfd' fails
17
Value assigned to 'dir_fd'
332 while ((de = readdir(d)) != NULL((void*)0)) {
18
Assuming the condition is true
19
Loop condition is true. Entering loop body
333 if (de->d_name[0] == '.')
20
Assuming the condition is false
334 continue;
335 if (unlinkat(dir_fd, de->d_name, 0) < 0 && errno(*__errno_location ()) != ENOENT2)
21
The 1st argument to 'unlinkat' is -1 but should be a valid file descriptor or AT_FDCWD
336 ret = -errno(*__errno_location ());
337 }
338 closedir(d);
339
340 if (ret)
341 return ret;
342
343 if (rmdir(path) < 0 && errno(*__errno_location ()) != ENOENT2)
344 return -errno(*__errno_location ());
345 return 0;
346}
347
348/*
349 * Atomically remove a *visible* registry entry: rename it to a hidden sibling
350 * so it vanishes from observers in a single step, then purge the renamed copy
351 * at leisure. This keeps a reader (nvme registry list, another orchestrator)
352 * from ever seeing a half-emptied entry. Returns 0 on success, -ENOENT if
353 * @path does not exist, or a negative errno.
354 *
355 * The udev REMOVE rule deletes entries with a plain "rm -rf", which is not
356 * atomic; this guarantee therefore covers only library-driven deletes.
357 */
358static int delete_dir_atomic(struct libnvme_global_ctx *ctx, const char *path)
359{
360 __cleanup_free__attribute__((cleanup(shr_freep))) char *trash = NULL((void*)0);
361
362 if (asprintf(&trash, "%s/.trash.XXXXXX", registry_dir(ctx)) < 0)
8
Assuming the condition is false
9
Taking false branch
363 return -ENOMEM12;
364 if (!mkdtemp(trash))
10
Taking false branch
365 return -errno(*__errno_location ());
366
367 /*
368 * rename() is the atomic check-and-act: don't pre-check existence (a
369 * TOCTOU on @path) — just attempt the move. If @path is absent (the
370 * common case, e.g. no stale entry to replace) it fails with ENOENT and
371 * we drop the empty temp dir we created.
372 */
373 if (rename(path, trash) < 0) {
11
Assuming the condition is false
12
Taking false branch
374 int ret = -errno(*__errno_location ());
375
376 rmdir(trash);
377 return ret;
378 }
379 return delete_dir(trash);
13
Calling 'delete_dir'
380}
381
382/*
383 * libnvmf_registry_create_instance - Write a registry entry for a freshly
384 * connected controller. Internal; called from the connect path in fabrics.c
385 * once the kernel returns instance=N. Always overwrites any pre-existing
386 * entry: instance recycling means an old nvmeN/ directory is stale by
387 * definition.
388 *
389 * The entry is built in a temporary directory and rename()'d into place, so it
390 * is only ever observed complete or absent — never as a directory that exists
391 * but is missing its seqnum stamp. Without this, the udev REMOVE rule could
392 * read a just-created entry between the mkdir and the seqnum write, see no
393 * stamp, and delete the live entry the library is still populating.
394 */
395int libnvmf_registry_create_instance(struct libnvme_global_ctx *ctx,
396 int instance, const char *owner)
397{
398 __cleanup_free__attribute__((cleanup(shr_freep))) char *tmp_dir = NULL((void*)0);
399 __cleanup_free__attribute__((cleanup(shr_freep))) char *final_path = NULL((void*)0);
400 __cleanup_free__attribute__((cleanup(shr_freep))) char *seqnum = NULL((void*)0);
401 char device[32];
402 int dir_fd, ret;
403
404 snprintf(device, sizeof(device), "nvme%d", instance);
405
406 final_path = registry_path(ctx, device, NULL((void*)0));
407 if (!final_path)
408 return -ENOMEM12;
409
410 /* The registry root must exist before mkdtemp() can run inside it. */
411 ret = shr_mkdir_p(registry_dir(ctx), 0755);
412 if (ret)
413 return ret;
414
415 if (asprintf(&tmp_dir, "%s/.%s.tmp.XXXXXX",
416 registry_dir(ctx), device) < 0)
417 return -ENOMEM12;
418 if (!mkdtemp(tmp_dir))
419 return -errno(*__errno_location ());
420
421 /* mkdtemp() creates it 0700; widen to the registry world. */
422 if (chmod(tmp_dir, 0755) < 0) {
423 ret = -errno(*__errno_location ());
424 goto err;
425 }
426
427 dir_fd = open(tmp_dir, O_RDONLY00 | O_DIRECTORY0200000 | O_CLOEXEC02000000);
428 if (dir_fd < 0) {
429 ret = -errno(*__errno_location ());
430 goto err;
431 }
432
433 /*
434 * Stamp the entry with the current uevent sequence number for the udev
435 * REMOVE rule (see read_uevent_seqnum()). Best-effort: a missing stamp
436 * just makes the rule fall back to its device-existence guard, so don't
437 * fail the create over it.
438 */
439 seqnum = read_uevent_seqnum();
440 if (seqnum)
441 write_attr_atomic(dir_fd, tmp_dir, "seqnum", seqnum);
442
443 ret = write_attr_atomic(dir_fd, tmp_dir, "owner", owner);
444 close(dir_fd);
445 if (ret)
446 goto err;
447
448 /*
449 * Reveal the fully-populated entry atomically. Instance recycling
450 * means any pre-existing nvmeN/ is stale, so remove it first: rename()
451 * onto a non-empty directory fails.
452 */
453 delete_dir_atomic(ctx, final_path);
454 if (rename(tmp_dir, final_path) < 0) {
455 ret = -errno(*__errno_location ());
456 goto err;
457 }
458 return 0;
459
460err:
461 delete_dir(tmp_dir); /* best-effort: don't leak the temp directory */
462 return ret;
463}
464
465int libnvmf_registry_delete_instance(struct libnvme_global_ctx *ctx,
466 int instance)
467{
468 char device[32];
469 int ret;
470
471 snprintf(device, sizeof(device), "nvme%d", instance);
472 ret = libnvmf_registry_delete(ctx, device);
1
Calling 'libnvmf_registry_delete'
473 return (ret == -ENOENT2) ? 0 : ret;
474}
475
476__shr_public__attribute__((visibility("default"))) int libnvmf_registry_retrieve(struct libnvme_global_ctx *ctx,
477 const char *device,
478 const char *attr, char **value)
479{
480 __cleanup_free__attribute__((cleanup(shr_freep))) char *path = NULL((void*)0);
481 int fd, ret;
482
483 if (!ctx)
484 return -EINVAL22;
485 if (!device || !attr || !value)
486 return -EINVAL22;
487 if (!valid_device(device))
488 return -EINVAL22;
489
490 path = registry_path(ctx, device, attr);
491 if (!path)
492 return -ENOMEM12;
493
494 fd = open(path, O_RDONLY00 | O_CLOEXEC02000000);
495 if (fd < 0)
496 return -errno(*__errno_location ());
497
498 ret = read_attr_fd(fd, value);
499 close(fd);
500 return ret;
501}
502
503__shr_public__attribute__((visibility("default"))) int libnvmf_registry_attr_equal(struct libnvme_global_ctx *ctx,
504 const char *device,
505 const char *attr,
506 const char *value)
507{
508 char *stored = NULL((void*)0);
509 int rc;
510
511 if (!ctx)
512 return -EINVAL22;
513
514 rc = libnvmf_registry_retrieve(ctx, device, attr, &stored);
515 if (rc < 0 && rc != -ENOENT2)
516 return rc;
517 if (!stored)
518 rc = value ? 1 : 0;
519 else
520 rc = (value && !strcmp(stored, value)) ? 0 : 1;
521 free(stored);
522 return rc;
523}
524
525__shr_public__attribute__((visibility("default"))) int libnvmf_registry_update(struct libnvme_global_ctx *ctx,
526 const char *device,
527 const char *attr, const char *value)
528{
529 if (!ctx)
530 return -EINVAL22;
531 if (!device || !valid_device(device))
532 return -EINVAL22;
533
534 return write_device_attr(ctx, device, attr, value);
535}
536
537__shr_public__attribute__((visibility("default"))) int libnvmf_registry_delete(struct libnvme_global_ctx *ctx,
538 const char *device)
539{
540 __cleanup_free__attribute__((cleanup(shr_freep))) char *path = NULL((void*)0);
541
542 if (!ctx)
2
Assuming 'ctx' is non-null
543 return -EINVAL22;
544 if (!device
2.1
'device' is non-null
|| !valid_device(device))
3
Assuming the condition is false
4
Taking false branch
545 return -EINVAL22;
546
547 path = registry_path(ctx, device, NULL((void*)0));
548 if (!path)
5
Assuming 'path' is non-null
6
Taking false branch
549 return -ENOMEM12;
550
551 return delete_dir_atomic(ctx, path);
7
Calling 'delete_dir_atomic'
552}
553
554__shr_public__attribute__((visibility("default"))) int libnvmf_registry_device_for_each(
555 struct libnvme_global_ctx *ctx,
556 void (*callback)(const char *device, void *user_data),
557 void *user_data)
558{
559 char dev_path[NAME_MAX255 + 6]; /* "/dev/" + name + NUL */
560 struct dirent *de;
561 DIR *d;
562
563 if (!ctx)
564 return -EINVAL22;
565 if (!callback)
566 return -EINVAL22;
567
568 d = opendir(registry_dir(ctx));
569 if (!d) {
570 if (errno(*__errno_location ()) == ENOENT2)
571 return 0;
572 return -errno(*__errno_location ());
573 }
574
575 while ((de = readdir(d)) != NULL((void*)0)) {
576 if (de->d_name[0] == '.')
577 continue;
578
579 /*
580 * Only visit directories. Use stat() as a fallback when
581 * d_type is DT_UNKNOWN (e.g. on some network filesystems).
582 */
583 if (de->d_type != DT_DIRDT_DIR) {
584 struct stat st;
585
586 if (de->d_type != DT_UNKNOWNDT_UNKNOWN)
587 continue;
588 /*
589 * Resolve relative to the open directory rather than
590 * building an absolute path: avoids a fixed-size path
591 * buffer (and the format-truncation it invites).
592 */
593 if (fstatat(dirfd(d), de->d_name, &st, 0) < 0 ||
594 !S_ISDIR(st.st_mode)((((st.st_mode)) & 0170000) == (0040000)))
595 continue;
596 }
597
598 /* Stale-entry check: skip if the device node is gone. */
599 snprintf(dev_path, sizeof(dev_path), "/dev/%s", de->d_name);
600 if (access(dev_path, F_OK0) != 0)
601 continue;
602
603 callback(de->d_name, user_data);
604 }
605 closedir(d);
606 return 0;
607}
608
609__shr_public__attribute__((visibility("default"))) int libnvmf_registry_attr_for_each(
610 struct libnvme_global_ctx *ctx,
611 const char *device,
612 void (*callback)(const char *attr, const char *value,
613 void *user_data),
614 void *user_data)
615{
616 __cleanup_free__attribute__((cleanup(shr_freep))) char *dir_path = NULL((void*)0);
617 struct dirent *de;
618 int dir_fd;
619 DIR *d;
620
621 if (!ctx)
622 return -EINVAL22;
623 if (!device || !callback)
624 return -EINVAL22;
625 if (!valid_device(device))
626 return -EINVAL22;
627
628 dir_path = registry_path(ctx, device, NULL((void*)0));
629 if (!dir_path)
630 return -ENOMEM12;
631
632 d = opendir(dir_path);
633 if (!d)
634 return -errno(*__errno_location ());
635
636 dir_fd = dirfd(d);
637 while ((de = readdir(d)) != NULL((void*)0)) {
638 __cleanup_free__attribute__((cleanup(shr_freep))) char *val = NULL((void*)0);
639 int fd, rc;
640
641 if (de->d_name[0] == '.')
642 continue;
643 /* Skip in-flight tmp files from concurrent writers. */
644 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."))
)
645 continue;
646
647 fd = openat(dir_fd, de->d_name, O_RDONLY00 | O_CLOEXEC02000000);
648 if (fd < 0)
649 continue; /* device may have been removed concurrently */
650
651 rc = read_attr_fd(fd, &val);
652 close(fd);
653 if (rc == 0)
654 callback(de->d_name, val, user_data);
655 }
656 closedir(d);
657 return 0;
658}