• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 14895667988

07 May 2025 08:57PM UTC coverage: 72.225% (-0.007%) from 72.232%
14895667988

push

github

yuwata
network: log_link_message_debug_errno() automatically append %m if necessary

Follow-up for d28746ef5.
Fixes CID#1609753.

0 of 1 new or added line in 1 file covered. (0.0%)

20297 existing lines in 338 files now uncovered.

297407 of 411780 relevant lines covered (72.22%)

695716.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.83
/src/shared/vpick.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <sys/stat.h>
4

5
#include "alloc-util.h"
6
#include "architecture.h"
7
#include "bitfield.h"
8
#include "chase.h"
9
#include "fd-util.h"
10
#include "fs-util.h"
11
#include "log.h"
12
#include "parse-util.h"
13
#include "path-util.h"
14
#include "recurse-dir.h"
15
#include "string-util.h"
16
#include "strv.h"
17
#include "vpick.h"
18

19
void pick_result_done(PickResult *p) {
14,604✔
20
        assert(p);
14,604✔
21

22
        free(p->path);
14,604✔
23
        safe_close(p->fd);
14,604✔
24
        free(p->version);
14,604✔
25

26
        *p = PICK_RESULT_NULL;
14,604✔
27
}
14,604✔
28

29
static int format_fname(
159✔
30
                const PickFilter *filter,
31
                PickFlags flags,
32
                char **ret) {
33

34
        _cleanup_free_ char *fn = NULL;
159✔
35
        int r;
159✔
36

37
        assert(filter);
159✔
38
        assert(ret);
159✔
39

40
        if (FLAGS_SET(flags, PICK_TRIES) || !filter->version) /* Underspecified? */
159✔
41
                return -ENOEXEC;
UNCOV
42
        if (strv_length(filter->suffix) > 1) /* suffix is not deterministic? */
×
43
                return -ENOEXEC;
44

45
        /* The format for names we match goes like this:
46
         *
47
         *        <basename><suffix>
48
         *  or:
49
         *        <basename>_<version><suffix>
50
         *  or:
51
         *        <basename>_<version>_<architecture><suffix>
52
         *  or:
53
         *        <basename>_<architecture><suffix>
54
         *
55
         * (Note that basename can be empty, in which case the leading "_" is suppressed)
56
         *
57
         * Examples: foo.raw, foo_1.3-7.raw, foo_1.3-7_x86-64.raw, foo_x86-64.raw
58
         *
59
         * Why use "_" as separator here? Primarily because it is not used by Semver 2.0. In RPM it is used
60
         * for "unsortable" versions, i.e. doesn't show up in "sortable" versions, which we matter for this
61
         * usecase here. In Debian the underscore is not allowed (and it uses it itself for separating
62
         * fields).
63
         *
64
         * This is very close to Debian's way to name packages, but allows arbitrary suffixes, and makes the
65
         * architecture field redundant.
66
         *
67
         * Compare with RPM's "NEVRA" concept. Here we have "BVAS" (basename, version, architecture, suffix).
68
         */
69

UNCOV
70
        if (filter->basename) {
×
UNCOV
71
                fn = strdup(filter->basename);
×
UNCOV
72
                if (!fn)
×
73
                        return -ENOMEM;
74
        }
75

76
        if (filter->version) {
×
UNCOV
77
                if (isempty(fn)) {
×
78
                        r = free_and_strdup(&fn, filter->version);
×
UNCOV
79
                        if (r < 0)
×
80
                                return r;
UNCOV
81
                } else if (!strextend(&fn, "_", filter->version))
×
82
                        return -ENOMEM;
83
        }
84

85
        if (FLAGS_SET(flags, PICK_ARCHITECTURE) && filter->architecture >= 0) {
×
86
                const char *as = ASSERT_PTR(architecture_to_string(filter->architecture));
×
UNCOV
87
                if (isempty(fn)) {
×
88
                        r = free_and_strdup(&fn, as);
×
UNCOV
89
                        if (r < 0)
×
90
                                return r;
UNCOV
91
                } else if (!strextend(&fn, "_", as))
×
92
                        return -ENOMEM;
93
        }
94

UNCOV
95
        if (!strv_isempty(filter->suffix))
×
96
                if (!strextend(&fn, filter->suffix[0]))
×
97
                        return -ENOMEM;
98

99
        if (!filename_is_valid(fn))
×
100
                return -EINVAL;
101

UNCOV
102
        *ret = TAKE_PTR(fn);
×
UNCOV
103
        return 0;
×
104
}
105

106
static int errno_from_mode(uint32_t type_mask, mode_t found) {
5✔
107
        /* Returns the most appropriate error code if we are lookging for an inode of type of those in the
108
         * 'type_mask' but found 'found' instead.
109
         *
110
         * type_mask is a mask of 1U << DT_REG, 1U << DT_DIR, … flags, while found is a S_IFREG, S_IFDIR, …
111
         * mode value. */
112

113
        if (type_mask == 0) /* type doesn't matter */
5✔
114
                return 0;
115

116
        if (BIT_SET(type_mask, IFTODT(found)))
5✔
117
                return 0;
118

119
        if (type_mask == (UINT32_C(1) << DT_BLK))
5✔
120
                return -ENOTBLK;
121
        if (type_mask == (UINT32_C(1) << DT_DIR))
5✔
122
                return -ENOTDIR;
123
        if (type_mask == (UINT32_C(1) << DT_SOCK))
3✔
124
                return -ENOTSOCK;
125

126
        if (S_ISLNK(found))
2✔
127
                return -ELOOP;
128
        if (S_ISDIR(found))
2✔
129
                return -EISDIR;
1✔
130

131
        return -EBADF;
132
}
133

134
static int pin_choice(
7,315✔
135
                const char *toplevel_path,
136
                int toplevel_fd,
137
                const char *inode_path,
138
                int _inode_fd, /* we always take ownership of the fd, even on failure */
139
                unsigned tries_left,
140
                unsigned tries_done,
141
                const PickFilter *filter,
142
                PickFlags flags,
143
                PickResult *ret) {
144

145
        _cleanup_close_ int inode_fd = TAKE_FD(_inode_fd);
7,315✔
146
        _cleanup_free_ char *resolved_path = NULL;
7,315✔
147
        int r;
7,315✔
148

149
        assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD);
7,315✔
150
        assert(inode_path);
7,315✔
151
        assert(filter);
7,315✔
152
        assert(ret);
7,315✔
153

154
        if (inode_fd < 0 || FLAGS_SET(flags, PICK_RESOLVE)) {
7,315✔
155
                r = chaseat(toplevel_fd,
14,566✔
156
                            inode_path,
157
                            CHASE_AT_RESOLVE_IN_ROOT,
158
                            FLAGS_SET(flags, PICK_RESOLVE) ? &resolved_path : NULL,
7,283✔
159
                            inode_fd < 0 ? &inode_fd : NULL);
160
                if (r < 0)
7,283✔
161
                        return r;
162

163
                if (resolved_path)
6,376✔
164
                        inode_path = resolved_path;
5,905✔
165
        }
166

167
        struct stat st;
6,408✔
168
        if (fstat(inode_fd, &st) < 0)
6,408✔
UNCOV
169
                return log_debug_errno(errno, "Failed to stat discovered inode '%s%s': %m",
×
170
                                       empty_to_root(toplevel_path), skip_leading_slash(inode_path));
171

172
        if (filter->type_mask != 0 &&
6,408✔
173
            !BIT_SET(filter->type_mask, IFTODT(st.st_mode)))
6,384✔
174
                return log_debug_errno(
5✔
175
                                SYNTHETIC_ERRNO(errno_from_mode(filter->type_mask, st.st_mode)),
176
                                "Inode '%s/%s' has wrong type, found '%s'.",
177
                                empty_to_root(toplevel_path), skip_leading_slash(inode_path),
178
                                inode_type_to_string(st.st_mode));
179

180
        _cleanup_(pick_result_done) PickResult result = {
6,403✔
181
                .fd = TAKE_FD(inode_fd),
6,403✔
182
                .st = st,
183
                .architecture = filter->architecture,
6,403✔
184
                .tries_left = tries_left,
185
                .tries_done = tries_done,
186
        };
187

188
        result.path = strdup(inode_path);
6,403✔
189
        if (!result.path)
6,403✔
UNCOV
190
                return log_oom_debug();
×
191

192
        r = strdup_to(&result.version, filter->version);
6,403✔
193
        if (r < 0)
6,403✔
194
                return r;
195

196
        *ret = TAKE_PICK_RESULT(result);
6,403✔
197
        return 1;
6,403✔
198
}
199

200
static int parse_tries(const char *s, unsigned *ret_tries_left, unsigned *ret_tries_done) {
31✔
201
        unsigned left, done;
31✔
202
        size_t n;
31✔
203

204
        assert(s);
31✔
205
        assert(ret_tries_left);
31✔
206
        assert(ret_tries_done);
31✔
207

208
        if (s[0] != '+')
31✔
UNCOV
209
                goto nomatch;
×
210

211
        s++;
31✔
212

213
        n = strspn(s, DIGITS);
31✔
214
        if (n == 0)
31✔
215
                goto nomatch;
×
216

217
        if (s[n] == 0) {
31✔
218
                if (safe_atou(s, &left) < 0)
×
UNCOV
219
                        goto nomatch;
×
220

UNCOV
221
                done = 0;
×
222
        } else if (s[n] == '-') {
31✔
223
                _cleanup_free_ char *c = NULL;
31✔
224

225
                c = strndup(s, n);
31✔
226
                if (!c)
31✔
227
                        return -ENOMEM;
×
228

229
                if (safe_atou(c, &left) < 0)
31✔
UNCOV
230
                        goto nomatch;
×
231

232
                s += n + 1;
31✔
233

234
                if (!in_charset(s, DIGITS))
31✔
235
                        goto nomatch;
×
236

237
                if (safe_atou(s, &done) < 0)
31✔
UNCOV
238
                        goto nomatch;
×
239
        } else
UNCOV
240
                goto nomatch;
×
241

242
        *ret_tries_left = left;
31✔
243
        *ret_tries_done = done;
31✔
244
        return 1;
31✔
245

UNCOV
246
nomatch:
×
UNCOV
247
        *ret_tries_left = *ret_tries_done = UINT_MAX;
×
UNCOV
248
        return 0;
×
249
}
250

251
static int make_choice(
161✔
252
                const char *toplevel_path,
253
                int toplevel_fd,
254
                const char *inode_path,
255
                int _inode_fd, /* we always take ownership of the fd, even on failure */
256
                const PickFilter *filter,
257
                PickFlags flags,
258
                PickResult *ret) {
259

260
        static const Architecture local_architectures[] = {
161✔
261
                /* In order of preference */
262
                native_architecture(),
263
#ifdef ARCHITECTURE_SECONDARY
264
                ARCHITECTURE_SECONDARY,
265
#endif
266
                _ARCHITECTURE_INVALID, /* accept any arch, as last resort */
267
        };
268

269
        _cleanup_free_ DirectoryEntries *de = NULL;
322✔
270
        _cleanup_free_ char *best_version = NULL, *best_filename = NULL, *p = NULL, *j = NULL;
161✔
271
        _cleanup_close_ int dir_fd = -EBADF, object_fd = -EBADF, inode_fd = TAKE_FD(_inode_fd);
483✔
272
        const Architecture *architectures;
161✔
273
        unsigned best_tries_left = UINT_MAX, best_tries_done = UINT_MAX;
161✔
274
        size_t n_architectures, best_architecture_index = SIZE_MAX;
161✔
275
        int r;
161✔
276

277
        assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD);
161✔
278
        assert(inode_path);
161✔
279
        assert(filter);
161✔
280
        assert(ret);
161✔
281

282
        if (inode_fd < 0) {
161✔
283
                r = chaseat(toplevel_fd, inode_path, CHASE_AT_RESOLVE_IN_ROOT, NULL, &inode_fd);
161✔
284
                if (r < 0)
161✔
285
                        return r;
286
        }
287

288
        /* Maybe the filter is fully specified? Then we can generate the file name directly */
289
        r = format_fname(filter, flags, &j);
159✔
290
        if (r >= 0) {
159✔
291
                _cleanup_free_ char *object_path = NULL;
×
292

293
                /* Yay! This worked! */
UNCOV
294
                p = path_join(inode_path, j);
×
295
                if (!p)
×
296
                        return log_oom_debug();
×
297

298
                r = chaseat(toplevel_fd, p, CHASE_AT_RESOLVE_IN_ROOT, &object_path, &object_fd);
×
UNCOV
299
                if (r == -ENOENT) {
×
300
                        *ret = PICK_RESULT_NULL;
×
301
                        return 0;
×
302
                }
UNCOV
303
                if (r < 0)
×
304
                        return log_debug_errno(r, "Failed to open '%s/%s': %m",
×
305
                                               empty_to_root(toplevel_path), skip_leading_slash(p));
306

307
                return pin_choice(
×
308
                                toplevel_path,
309
                                toplevel_fd,
UNCOV
310
                                FLAGS_SET(flags, PICK_RESOLVE) ? object_path : p,
×
UNCOV
311
                                TAKE_FD(object_fd), /* unconditionally pass ownership of the fd */
×
312
                                /* tries_left= */ UINT_MAX,
313
                                /* tries_done= */ UINT_MAX,
314
                                filter,
UNCOV
315
                                flags & ~PICK_RESOLVE,
×
316
                                ret);
317

318
        } else if (r != -ENOEXEC)
159✔
UNCOV
319
                return log_debug_errno(r, "Failed to format file name: %m");
×
320

321
        /* Underspecified, so we do our enumeration dance */
322

323
        /* Convert O_PATH to a regular directory fd */
324
        dir_fd = fd_reopen(inode_fd, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
159✔
325
        if (dir_fd < 0)
159✔
UNCOV
326
                return log_debug_errno(dir_fd, "Failed to reopen '%s/%s' as directory: %m",
×
327
                                       empty_to_root(toplevel_path), skip_leading_slash(inode_path));
328

329
        r = readdir_all(dir_fd, 0, &de);
159✔
330
        if (r < 0)
159✔
UNCOV
331
                return log_debug_errno(r, "Failed to read directory '%s/%s': %m",
×
332
                                       empty_to_root(toplevel_path), skip_leading_slash(inode_path));
333

334
        if (filter->architecture < 0) {
159✔
335
                architectures = local_architectures;
336
                n_architectures = ELEMENTSOF(local_architectures);
337
        } else {
338
                architectures = &filter->architecture;
16✔
339
                n_architectures = 1;
16✔
340
        }
341

342
        FOREACH_ARRAY(entry, de->entries, de->n_entries) {
521✔
343
                unsigned found_tries_done = UINT_MAX, found_tries_left = UINT_MAX;
362✔
344
                _cleanup_free_ char *dname = NULL;
362✔
345
                size_t found_architecture_index = SIZE_MAX;
362✔
346
                const char *e;
362✔
347

348
                dname = strdup((*entry)->d_name);
362✔
349
                if (!dname)
362✔
UNCOV
350
                        return log_oom_debug();
×
351

352
                if (!isempty(filter->basename)) {
362✔
353
                        e = startswith(dname, filter->basename);
362✔
354
                        if (!e)
362✔
355
                                continue;
33✔
356

357
                        if (e[0] != '_')
329✔
UNCOV
358
                                continue;
×
359

360
                        e++;
329✔
361
                } else
362
                        e = dname;
363

364
                if (!strv_isempty(filter->suffix)) {
329✔
365
                        char *sfx = endswith_strv(e, filter->suffix);
298✔
366
                        if (!sfx)
298✔
UNCOV
367
                                continue;
×
368

369
                        *sfx = 0;
298✔
370
                }
371

372
                if (FLAGS_SET(flags, PICK_TRIES)) {
329✔
373
                        char *plus = strrchr(e, '+');
329✔
374
                        if (plus) {
329✔
375
                                r = parse_tries(plus, &found_tries_left, &found_tries_done);
31✔
376
                                if (r < 0)
31✔
377
                                        return r;
378
                                if (r > 0) /* Found and parsed, now chop off */
31✔
379
                                        *plus = 0;
31✔
380
                        }
381
                }
382

383
                if (FLAGS_SET(flags, PICK_ARCHITECTURE)) {
329✔
384
                        char *underscore = strrchr(e, '_');
329✔
385
                        Architecture a;
329✔
386

387
                        a = underscore ? architecture_from_string(underscore + 1) : _ARCHITECTURE_INVALID;
329✔
388

389
                        for (size_t i = 0; i < n_architectures; i++)
858✔
390
                                if (architectures[i] == a) {
767✔
391
                                        found_architecture_index = i;
392
                                        break;
393
                                }
394

395
                        if (found_architecture_index == SIZE_MAX) { /* No matching arch found */
329✔
396
                                log_debug("Found entry with architecture '%s' which is not what we are looking for, ignoring entry.", a < 0 ? "any" : architecture_to_string(a));
91✔
397
                                continue;
91✔
398
                        }
399

400
                        /* Chop off architecture from string */
401
                        if (underscore)
238✔
402
                                *underscore = 0;
44✔
403
                }
404

405
                if (!version_is_valid(e)) {
238✔
UNCOV
406
                        log_debug("Version string '%s' of entry '%s' is invalid, ignoring entry.", e, (*entry)->d_name);
×
UNCOV
407
                        continue;
×
408
                }
409

410
                if (filter->version && !streq(filter->version, e)) {
238✔
411
                        log_debug("Found entry with version string '%s', but was looking for '%s', ignoring entry.", e, filter->version);
16✔
412
                        continue;
16✔
413
                }
414

415
                if (best_filename) { /* Already found one matching entry? Then figure out the better one */
222✔
416
                        int d = 0;
70✔
417

418
                        /* First, prefer entries with tries left over those without */
419
                        if (FLAGS_SET(flags, PICK_TRIES))
70✔
420
                                d = CMP(found_tries_left != 0, best_tries_left != 0);
70✔
421

422
                        /* Second, prefer newer versions */
423
                        if (d == 0)
63✔
424
                                d = strverscmp_improved(e, best_version);
62✔
425

426
                        /* Third, prefer native architectures over secondary architectures */
427
                        if (d == 0 &&
62✔
428
                            FLAGS_SET(flags, PICK_ARCHITECTURE) &&
429
                            found_architecture_index != SIZE_MAX && best_architecture_index != SIZE_MAX)
4✔
430
                                d = -CMP(found_architecture_index, best_architecture_index);
4✔
431

432
                        /* Fourth, prefer entries with more tries left */
433
                        if (FLAGS_SET(flags, PICK_TRIES)) {
70✔
434
                                if (d == 0)
70✔
435
                                        d = CMP(found_tries_left, best_tries_left);
×
436

437
                                /* Fifth, prefer entries with fewer attempts done so far */
UNCOV
438
                                if (d == 0)
×
UNCOV
439
                                        d = -CMP(found_tries_done, best_tries_done);
×
440
                        }
441

442
                        /* Last, just compare the filenames as strings */
UNCOV
443
                        if (d == 0)
×
UNCOV
444
                                d = strcmp((*entry)->d_name, best_filename);
×
445

446
                        if (d < 0) {
70✔
447
                                log_debug("Found entry '%s' but previously found entry '%s' matches better, hence skipping entry.", (*entry)->d_name, best_filename);
60✔
448
                                continue;
60✔
449
                        }
450
                }
451

452
                r = free_and_strdup_warn(&best_version, e);
162✔
453
                if (r < 0)
162✔
454
                        return r;
455

456
                r = free_and_strdup_warn(&best_filename, (*entry)->d_name);
162✔
457
                if (r < 0)
162✔
458
                        return r;
459

460
                best_architecture_index = found_architecture_index;
162✔
461
                best_tries_left = found_tries_left;
162✔
462
                best_tries_done = found_tries_done;
162✔
463
        }
464

465
        if (!best_filename) { /* Everything was good, but we didn't find any suitable entry */
159✔
466
                *ret = PICK_RESULT_NULL;
7✔
467
                return 0;
7✔
468
        }
469

470
        p = path_join(inode_path, best_filename);
152✔
471
        if (!p)
152✔
UNCOV
472
                return log_oom_debug();
×
473

474
        object_fd = openat(dir_fd, best_filename, O_CLOEXEC|O_PATH);
152✔
475
        if (object_fd < 0)
152✔
UNCOV
476
                return log_debug_errno(errno, "Failed to open '%s/%s': %m",
×
477
                                       empty_to_root(toplevel_path), skip_leading_slash(inode_path));
478

479
        return pin_choice(
304✔
480
                        toplevel_path,
481
                        toplevel_fd,
482
                        p,
483
                        TAKE_FD(object_fd),
152✔
484
                        best_tries_left,
485
                        best_tries_done,
486
                        &(const PickFilter) {
456✔
487
                                .type_mask = filter->type_mask,
152✔
488
                                .basename = filter->basename,
152✔
489
                                .version = empty_to_null(best_version),
152✔
490
                                .architecture = best_architecture_index != SIZE_MAX ? architectures[best_architecture_index] : _ARCHITECTURE_INVALID,
152✔
491
                                .suffix = filter->suffix,
152✔
492
                        },
493
                        flags,
494
                        ret);
495
}
496

497
int path_pick(
7,324✔
498
                const char *toplevel_path,
499
                int toplevel_fd,
500
                const char *path,
501
                const PickFilter *filter,
502
                PickFlags flags,
503
                PickResult *ret) {
504

505
        _cleanup_free_ char *filter_bname = NULL, *dir = NULL, *parent = NULL, *fname = NULL;
7,324✔
506
        char * const *filter_suffix_strv = NULL;
7,324✔
507
        const char *filter_suffix = NULL, *enumeration_path;
7,324✔
508
        uint32_t filter_type_mask;
7,324✔
509
        int r;
7,324✔
510

511
        assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD);
7,324✔
512
        assert(path);
7,324✔
513
        assert(filter);
7,324✔
514
        assert(ret);
7,324✔
515

516
        /* Given a path, resolve .v/ subdir logic (if used!), and returns the choice made. This supports
517
         * three ways to be called:
518
         *
519
         * • with a path referring a directory of any name, and filter→basename *explicitly* specified, in
520
         *   which case we'll use a pattern "<filter→basename>_*<filter→suffix>" on the directory's files.
521
         *
522
         * • with no filter→basename explicitly specified and a path referring to a directory named in format
523
         *   "<somestring><filter→suffix>.v" . In this case the filter basename to search for inside the dir
524
         *   is derived from the directory name. Example: "/foo/bar/baz.suffix.v" → we'll search for
525
         *   "/foo/bar/baz.suffix.v/baz_*.suffix".
526
         *
527
         * • with a path whose penultimate component ends in ".v/". In this case the final component of the
528
         *   path refers to the pattern. Example: "/foo/bar/baz.v/waldo__.suffix" → we'll search for
529
         *   "/foo/bar/baz.v/waldo_*.suffix".
530
         */
531

532
        /* Explicit basename specified, then shortcut things and do .v mode regardless of the path name. */
533
        if (filter->basename)
7,324✔
534
                return make_choice(
3✔
535
                                toplevel_path,
536
                                toplevel_fd,
537
                                path,
538
                                /* inode_fd= */ -EBADF,
539
                                filter,
540
                                flags,
541
                                ret);
542

543
        r = path_extract_filename(path, &fname);
7,321✔
544
        if (r < 0) {
7,321✔
545
                if (r != -EADDRNOTAVAIL) /* root dir or "." */
1✔
546
                        return r;
547

548
                /* If there's no path element we can derive a pattern off, the don't */
549
                goto bypass;
1✔
550
        }
551

552
        /* Remember if the path ends in a dash suffix */
553
        bool slash_suffix = r == O_DIRECTORY;
7,320✔
554

555
        const char *e = endswith(fname, ".v");
7,320✔
556
        if (e) {
7,320✔
557
                /* So a path in the form /foo/bar/baz.v is specified. In this case our search basename is
558
                 * "baz", possibly with a suffix chopped off if there's one specified. */
559
                filter_bname = strndup(fname, e - fname);
157✔
560
                if (!filter_bname)
157✔
561
                        return -ENOMEM;
562

563
                /* Chop off suffix, if specified */
564
                char *f = endswith_strv(filter_bname, filter->suffix);
157✔
565
                if (f)
157✔
566
                        *f = 0;
132✔
567

568
                filter_suffix_strv = filter->suffix;
157✔
569
                filter_type_mask = filter->type_mask;
157✔
570

571
                enumeration_path = path;
157✔
572
        } else {
573
                /* The path does not end in '.v', hence see if the last element is a pattern. */
574

575
                char *wildcard = strrstr(fname, "___");
7,163✔
576
                if (!wildcard)
7,163✔
577
                        goto bypass; /* Not a pattern, then bypass */
7,162✔
578

579
                /* We found the '___' wildcard, hence everything after it is our filter suffix, and
580
                 * everything before is our filter basename */
581
                *wildcard = 0;
1✔
582
                filter_suffix = empty_to_null(wildcard + 3);
1✔
583

584
                filter_bname = TAKE_PTR(fname);
1✔
585

586
                r = path_extract_directory(path, &dir);
1✔
587
                if (r < 0) {
1✔
588
                        if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL)) /* only filename specified (no dir), or root or "." */
×
589
                                return r;
590

UNCOV
591
                        goto bypass; /* No dir extractable, can't check if parent ends in ".v" */
×
592
                }
593

594
                r = path_extract_filename(dir, &parent);
1✔
595
                if (r < 0) {
1✔
596
                        if (r != -EADDRNOTAVAIL) /* root dir or "." */
×
597
                                return r;
598

UNCOV
599
                        goto bypass; /* Cannot extract fname from parent dir, can't check if it ends in ".v" */
×
600
                }
601

602
                e = endswith(parent, ".v");
1✔
603
                if (!e)
1✔
UNCOV
604
                        goto bypass; /* Doesn't end in ".v", shortcut */
×
605

606
                filter_type_mask = filter->type_mask;
1✔
607
                if (slash_suffix) {
1✔
608
                        /* If the pattern is suffixed by a / then we are looking for directories apparently. */
UNCOV
609
                        if (filter_type_mask != 0 && !BIT_SET(filter_type_mask, DT_DIR))
×
UNCOV
610
                                return log_debug_errno(SYNTHETIC_ERRNO(errno_from_mode(filter_type_mask, S_IFDIR)),
×
611
                                                       "Specified pattern ends in '/', but not looking for directories, refusing.");
612
                        filter_type_mask = UINT32_C(1) << DT_DIR;
613
                }
614

615
                enumeration_path = dir;
1✔
616
        }
617

618
        return make_choice(
316✔
619
                        toplevel_path,
620
                        toplevel_fd,
621
                        enumeration_path,
622
                        /* inode_fd= */ -EBADF,
623
                        &(const PickFilter) {
158✔
624
                                .type_mask = filter_type_mask,
625
                                .basename = filter_bname,
626
                                .version = filter->version,
158✔
627
                                .architecture = filter->architecture,
158✔
628
                                .suffix = filter_suffix_strv ?: STRV_MAKE(filter_suffix),
158✔
629
                        },
630
                        flags,
631
                        ret);
632

633
bypass:
7,163✔
634
        /* Don't make any choice, but just use the passed path literally */
635
        return pin_choice(
7,163✔
636
                        toplevel_path,
637
                        toplevel_fd,
638
                        path,
639
                        /* inode_fd= */ -EBADF,
640
                        /* tries_left= */ UINT_MAX,
641
                        /* tries_done= */ UINT_MAX,
642
                        filter,
643
                        flags,
644
                        ret);
645
}
646

647
int path_pick_update_warn(
468✔
648
                char **path,
649
                const PickFilter *filter,
650
                PickFlags flags,
651
                PickResult *ret_result) {
652

653
        _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
468✔
654
        int r;
468✔
655

656
        assert(path);
468✔
657
        assert(*path);
468✔
658
        assert(filter);
468✔
659

660
        /* This updates the first argument if needed! */
661

662
        r = path_pick(/* toplevel_path= */ NULL,
468✔
663
                      /* toplevel_fd= */ AT_FDCWD,
664
                      *path,
665
                      filter,
666
                      flags,
667
                      &result);
668
        if (r == -ENOENT) {
468✔
669
                log_debug("Path '%s' doesn't exist, leaving as is.", *path);
4✔
670

671
                if (ret_result)
4✔
672
                        *ret_result = PICK_RESULT_NULL;
4✔
673
                return 0;
4✔
674
        }
675
        if (r < 0)
464✔
UNCOV
676
                return log_error_errno(r, "Failed to pick version on path '%s': %m", *path);
×
677
        if (r == 0)
464✔
UNCOV
678
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No matching entries in versioned directory '%s' found.", *path);
×
679

680
        log_debug("Resolved versioned directory pattern '%s' to file '%s' as version '%s'.", result.path, *path, strna(result.version));
928✔
681

682
        if (ret_result) {
464✔
683
                r = free_and_strdup_warn(path, result.path);
370✔
684
                if (r < 0)
370✔
685
                        return r;
686

687
                *ret_result = TAKE_PICK_RESULT(result);
370✔
688
        } else
689
                free_and_replace(*path, result.path);
94✔
690

691
        return 1;
692
}
693

694
const PickFilter pick_filter_image_raw = {
695
        .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK),
696
        .architecture = _ARCHITECTURE_INVALID,
697
        .suffix = STRV_MAKE(".raw"),
698
};
699

700
const PickFilter pick_filter_image_dir = {
701
        .type_mask = UINT32_C(1) << DT_DIR,
702
        .architecture = _ARCHITECTURE_INVALID,
703
};
704

705
const PickFilter pick_filter_image_any = {
706
        .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) | (UINT32_C(1) << DT_DIR),
707
        .architecture = _ARCHITECTURE_INVALID,
708
        .suffix = STRV_MAKE(".raw", ""),
709
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc