Coverage Report

Created: 2026-04-29 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/systemd/src/shared/vpick.c
Line
Count
Source
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 "log.h"
11
#include "parse-util.h"
12
#include "path-util.h"
13
#include "recurse-dir.h"
14
#include "stat-util.h"
15
#include "string-util.h"
16
#include "vpick.h"
17
18
0
void pick_result_done(PickResult *p) {
19
0
        assert(p);
20
21
0
        free(p->path);
22
0
        safe_close(p->fd);
23
0
        free(p->version);
24
25
0
        *p = PICK_RESULT_NULL;
26
0
}
27
28
0
int pick_result_compare(const PickResult *a, const PickResult *b, PickFlags flags) {
29
0
        int d;
30
31
0
        assert(a);
32
0
        assert(b);
33
34
        /* Returns > 0 if 'a' is the better pick, < 0 if 'b' is the better pick, 0 if they are equal. */
35
36
        /* Prefer entries with tries left over those without */
37
0
        if (FLAGS_SET(flags, PICK_TRIES))
38
0
                d = CMP(a->tries_left != 0, b->tries_left != 0);
39
0
        else
40
0
                d = 0;
41
42
        /* Prefer newer versions */
43
0
        if (d == 0)
44
0
                d = strverscmp_improved(a->version, b->version);
45
46
0
        if (FLAGS_SET(flags, PICK_ARCHITECTURE)) {
47
                /* Prefer native architectures over non-native architectures */
48
0
                if (d == 0)
49
0
                        d = CMP(a->architecture == native_architecture(), b->architecture == native_architecture());
50
51
                /* Prefer secondary architectures over other architectures */
52
0
#ifdef ARCHITECTURE_SECONDARY
53
0
                if (d == 0)
54
0
                        d = CMP(a->architecture == ARCHITECTURE_SECONDARY, b->architecture == ARCHITECTURE_SECONDARY);
55
0
#endif
56
0
        }
57
58
        /* Prefer entries with more tries left */
59
0
        if (FLAGS_SET(flags, PICK_TRIES)) {
60
0
                if (d == 0)
61
0
                        d = CMP(a->tries_left, b->tries_left);
62
63
                /* Prefer entries with fewer attempts done so far */
64
0
                if (d == 0)
65
0
                        d = -CMP(a->tries_done, b->tries_done);
66
0
        }
67
68
        /* Finally, just compare the filenames as strings */
69
0
        if (d == 0)
70
0
                d = path_compare_filename(a->path, b->path);
71
72
0
        return d;
73
0
}
74
75
static int format_fname(
76
                const PickFilter *filter,
77
                PickFlags flags,
78
0
                char **ret) {
79
80
0
        _cleanup_free_ char *fn = NULL;
81
0
        int r;
82
83
0
        assert(filter);
84
0
        assert(ret);
85
86
0
        if (FLAGS_SET(flags, PICK_TRIES) || !filter->version) /* Underspecified? */
87
0
                return -ENOEXEC;
88
89
        /* The format for names we match goes like this:
90
         *
91
         *        <basename><suffix>
92
         *  or:
93
         *        <basename>_<version><suffix>
94
         *  or:
95
         *        <basename>_<version>_<architecture><suffix>
96
         *  or:
97
         *        <basename>_<architecture><suffix>
98
         *
99
         * (Note that basename can be empty, in which case the leading "_" is suppressed)
100
         *
101
         * Examples: foo.raw, foo_1.3-7.raw, foo_1.3-7_x86-64.raw, foo_x86-64.raw
102
         *
103
         * Why use "_" as separator here? Primarily because it is not used by Semver 2.0. In RPM it is used
104
         * for "unsortable" versions, i.e. doesn't show up in "sortable" versions, which we matter for this
105
         * usecase here. In Debian the underscore is not allowed (and it uses it itself for separating
106
         * fields).
107
         *
108
         * This is very close to Debian's way to name packages, but allows arbitrary suffixes, and makes the
109
         * architecture field redundant.
110
         *
111
         * Compare with RPM's "NEVRA" concept. Here we have "BVAS" (basename, version, architecture, suffix).
112
         */
113
114
0
        if (filter->basename) {
115
0
                fn = strdup(filter->basename);
116
0
                if (!fn)
117
0
                        return -ENOMEM;
118
0
        }
119
120
0
        if (filter->version) {
121
0
                if (isempty(fn)) {
122
0
                        r = free_and_strdup(&fn, filter->version);
123
0
                        if (r < 0)
124
0
                                return r;
125
0
                } else if (!strextend(&fn, "_", filter->version))
126
0
                        return -ENOMEM;
127
0
        }
128
129
0
        if (FLAGS_SET(flags, PICK_ARCHITECTURE) && filter->architecture >= 0) {
130
0
                const char *as = ASSERT_PTR(architecture_to_string(filter->architecture));
131
0
                if (isempty(fn)) {
132
0
                        r = free_and_strdup(&fn, as);
133
0
                        if (r < 0)
134
0
                                return r;
135
0
                } else if (!strextend(&fn, "_", as))
136
0
                        return -ENOMEM;
137
0
        }
138
139
0
        if (!isempty(filter->suffix))
140
0
                if (!strextend(&fn, filter->suffix))
141
0
                        return -ENOMEM;
142
143
0
        if (!filename_is_valid(fn))
144
0
                return -EINVAL;
145
146
0
        *ret = TAKE_PTR(fn);
147
0
        return 0;
148
0
}
149
150
0
static int errno_from_mode(uint32_t type_mask, mode_t found) {
151
        /* Returns the most appropriate error code if we are lookging for an inode of type of those in the
152
         * 'type_mask' but found 'found' instead.
153
         *
154
         * type_mask is a mask of 1U << DT_REG, 1U << DT_DIR, … flags, while found is a S_IFREG, S_IFDIR, …
155
         * mode value. */
156
157
0
        if (type_mask == 0) /* type doesn't matter */
158
0
                return 0;
159
160
0
        if (BIT_SET(type_mask, IFTODT(found)))
161
0
                return 0;
162
163
0
        if (type_mask == (UINT32_C(1) << DT_BLK))
164
0
                return -ENOTBLK;
165
0
        if (type_mask == (UINT32_C(1) << DT_DIR))
166
0
                return -ENOTDIR;
167
0
        if (type_mask == (UINT32_C(1) << DT_SOCK))
168
0
                return -ENOTSOCK;
169
170
0
        if (S_ISLNK(found))
171
0
                return -ELOOP;
172
0
        if (S_ISDIR(found))
173
0
                return -EISDIR;
174
175
0
        return -EBADF;
176
0
}
177
178
static int pin_choice(
179
                const char *toplevel_path,
180
                int toplevel_fd,
181
                const char *inode_path,
182
                int _inode_fd, /* we always take ownership of the fd, even on failure */
183
                unsigned tries_left,
184
                unsigned tries_done,
185
                const PickFilter *filter,
186
                PickFlags flags,
187
0
                PickResult *ret) {
188
189
0
        _cleanup_close_ int inode_fd = TAKE_FD(_inode_fd);
190
0
        _cleanup_free_ char *resolved_path = NULL;
191
0
        int r;
192
193
0
        assert(toplevel_fd >= 0 || IN_SET(toplevel_fd, AT_FDCWD, XAT_FDROOT));
194
0
        assert(inode_path);
195
0
        assert(filter);
196
0
        assert(ret);
197
198
0
        if (inode_fd < 0 || FLAGS_SET(flags, PICK_RESOLVE)) {
199
0
                r = chaseat(toplevel_fd,
200
0
                            toplevel_fd,
201
0
                            inode_path,
202
0
                            /* flags= */ 0,
203
0
                            FLAGS_SET(flags, PICK_RESOLVE) ? &resolved_path : NULL,
204
0
                            inode_fd < 0 ? &inode_fd : NULL);
205
0
                if (r < 0)
206
0
                        return r;
207
208
0
                if (resolved_path)
209
0
                        inode_path = resolved_path;
210
0
        }
211
212
0
        struct stat st;
213
0
        if (fstat(inode_fd, &st) < 0)
214
0
                return log_debug_errno(errno, "Failed to stat discovered inode '%s%s': %m",
215
0
                                       empty_to_root(toplevel_path), skip_leading_slash(inode_path));
216
217
0
        if (filter->type_mask != 0 && !BIT_SET(filter->type_mask, IFTODT(st.st_mode))) {
218
0
                log_debug("Inode '%s/%s' has wrong type, found '%s'.",
219
0
                          empty_to_root(toplevel_path), skip_leading_slash(inode_path),
220
0
                          inode_type_to_string(st.st_mode));
221
0
                *ret = PICK_RESULT_NULL;
222
0
                return 0;
223
0
        }
224
225
0
        _cleanup_(pick_result_done) PickResult result = {
226
0
                .fd = TAKE_FD(inode_fd),
227
0
                .st = st,
228
0
                .architecture = filter->architecture,
229
0
                .tries_left = tries_left,
230
0
                .tries_done = tries_done,
231
0
        };
232
233
0
        result.path = strdup(inode_path);
234
0
        if (!result.path)
235
0
                return log_oom_debug();
236
237
0
        r = strdup_to(&result.version, filter->version);
238
0
        if (r < 0)
239
0
                return r;
240
241
0
        *ret = TAKE_PICK_RESULT(result);
242
0
        return 1;
243
0
}
244
245
0
static int parse_tries(const char *s, unsigned *ret_tries_left, unsigned *ret_tries_done) {
246
0
        unsigned left, done;
247
0
        size_t n;
248
249
0
        assert(s);
250
0
        assert(ret_tries_left);
251
0
        assert(ret_tries_done);
252
253
0
        if (s[0] != '+')
254
0
                goto nomatch;
255
256
0
        s++;
257
258
0
        n = strspn(s, DIGITS);
259
0
        if (n == 0)
260
0
                goto nomatch;
261
262
0
        if (s[n] == 0) {
263
0
                if (safe_atou(s, &left) < 0)
264
0
                        goto nomatch;
265
266
0
                done = 0;
267
0
        } else if (s[n] == '-') {
268
0
                _cleanup_free_ char *c = NULL;
269
270
0
                c = strndup(s, n);
271
0
                if (!c)
272
0
                        return -ENOMEM;
273
274
0
                if (safe_atou(c, &left) < 0)
275
0
                        goto nomatch;
276
277
0
                s += n + 1;
278
279
0
                if (!in_charset(s, DIGITS))
280
0
                        goto nomatch;
281
282
0
                if (safe_atou(s, &done) < 0)
283
0
                        goto nomatch;
284
0
        } else
285
0
                goto nomatch;
286
287
0
        *ret_tries_left = left;
288
0
        *ret_tries_done = done;
289
0
        return 1;
290
291
0
nomatch:
292
0
        *ret_tries_left = *ret_tries_done = UINT_MAX;
293
0
        return 0;
294
0
}
295
296
0
static bool architecture_matches(const PickFilter *filter, Architecture a) {
297
0
        assert(filter);
298
299
0
        if (filter->architecture >= 0)
300
0
                return a == filter->architecture;
301
302
0
        if (a == native_architecture())
303
0
                return true;
304
305
0
#ifdef ARCHITECTURE_SECONDARY
306
0
        if (a == ARCHITECTURE_SECONDARY)
307
0
                return true;
308
0
#endif
309
310
0
        return a == _ARCHITECTURE_INVALID;
311
0
}
312
313
static int make_choice(
314
                const char *toplevel_path,
315
                int toplevel_fd,
316
                const char *inode_path,
317
                int _inode_fd, /* we always take ownership of the fd, even on failure */
318
                const PickFilter *filter,
319
                PickFlags flags,
320
0
                PickResult *ret) {
321
322
0
        _cleanup_close_ int inode_fd = TAKE_FD(_inode_fd);
323
0
        int r;
324
325
0
        assert(toplevel_fd >= 0 || IN_SET(toplevel_fd, AT_FDCWD, XAT_FDROOT));
326
0
        assert(inode_path);
327
0
        assert(filter);
328
0
        assert(ret);
329
330
0
        if (inode_fd < 0) {
331
0
                r = chaseat(toplevel_fd, toplevel_fd, inode_path, /* flags= */ 0, NULL, &inode_fd);
332
0
                if (r < 0)
333
0
                        return r;
334
0
        }
335
336
        /* Maybe the filter is fully specified? Then we can generate the file name directly */
337
0
        _cleanup_free_ char *j = NULL;
338
0
        r = format_fname(filter, flags, &j);
339
0
        if (r >= 0) {
340
0
                _cleanup_free_ char *object_path = NULL;
341
342
                /* Yay! This worked! */
343
0
                _cleanup_free_ char *p = path_join(inode_path, j);
344
0
                if (!p)
345
0
                        return log_oom_debug();
346
347
0
                _cleanup_close_ int object_fd = -EBADF;
348
0
                r = chaseat(toplevel_fd, toplevel_fd, p, /* flags= */ 0, &object_path, &object_fd);
349
0
                if (r == -ENOENT) {
350
0
                        *ret = PICK_RESULT_NULL;
351
0
                        return 0;
352
0
                }
353
0
                if (r < 0)
354
0
                        return log_debug_errno(r, "Failed to open '%s/%s': %m",
355
0
                                               empty_to_root(toplevel_path), skip_leading_slash(p));
356
357
0
                return pin_choice(
358
0
                                toplevel_path,
359
0
                                toplevel_fd,
360
0
                                FLAGS_SET(flags, PICK_RESOLVE) ? object_path : p,
361
0
                                TAKE_FD(object_fd), /* unconditionally pass ownership of the fd */
362
0
                                /* tries_left= */ UINT_MAX,
363
0
                                /* tries_done= */ UINT_MAX,
364
0
                                filter,
365
0
                                flags & ~PICK_RESOLVE,
366
0
                                ret);
367
368
0
        } else if (r != -ENOEXEC)
369
0
                return log_debug_errno(r, "Failed to format file name: %m");
370
371
        /* Underspecified, so we do our enumeration dance */
372
373
        /* Convert O_PATH to a regular directory fd */
374
0
        _cleanup_close_ int dir_fd = fd_reopen(inode_fd, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
375
0
        if (dir_fd < 0)
376
0
                return log_debug_errno(dir_fd, "Failed to reopen '%s/%s' as directory: %m",
377
0
                                       empty_to_root(toplevel_path), skip_leading_slash(inode_path));
378
379
0
        _cleanup_free_ DirectoryEntries *de = NULL;
380
0
        r = readdir_all(dir_fd, 0, &de);
381
0
        if (r < 0)
382
0
                return log_debug_errno(r, "Failed to read directory '%s/%s': %m",
383
0
                                       empty_to_root(toplevel_path), skip_leading_slash(inode_path));
384
385
0
        _cleanup_(pick_result_done) PickResult best = PICK_RESULT_NULL;
386
387
0
        FOREACH_ARRAY(entry, de->entries, de->n_entries) {
388
0
                unsigned found_tries_done = UINT_MAX, found_tries_left = UINT_MAX;
389
0
                _cleanup_free_ char *dname = NULL;
390
0
                char *e;
391
392
0
                dname = strdup((*entry)->d_name);
393
0
                if (!dname)
394
0
                        return log_oom_debug();
395
396
0
                if (!isempty(filter->basename)) {
397
0
                        e = startswith(dname, filter->basename);
398
0
                        if (!e)
399
0
                                continue;
400
401
0
                        if (e[0] != '_')
402
0
                                continue;
403
404
0
                        e++;
405
0
                } else
406
0
                        e = dname;
407
408
0
                if (!isempty(filter->suffix)) {
409
0
                        char *sfx = endswith(e, filter->suffix);
410
0
                        if (!sfx)
411
0
                                continue;
412
413
0
                        *sfx = 0;
414
0
                }
415
416
0
                if (FLAGS_SET(flags, PICK_TRIES)) {
417
0
                        char *plus = strrchr(e, '+');
418
0
                        if (plus) {
419
0
                                r = parse_tries(plus, &found_tries_left, &found_tries_done);
420
0
                                if (r < 0)
421
0
                                        return r;
422
0
                                if (r > 0) /* Found and parsed, now chop off */
423
0
                                        *plus = 0;
424
0
                        }
425
0
                }
426
427
0
                Architecture a = _ARCHITECTURE_INVALID;
428
0
                if (FLAGS_SET(flags, PICK_ARCHITECTURE)) {
429
0
                        char *underscore = strrchr(e, '_');
430
431
0
                        if (underscore)
432
0
                                a = architecture_from_string(underscore + 1);
433
434
0
                        if (!architecture_matches(filter, a)) {
435
0
                                log_debug("Found entry with architecture '%s' which is not what we are looking for, ignoring entry.",
436
0
                                          a < 0 ? "any" : architecture_to_string(a));
437
0
                                continue;
438
0
                        }
439
440
                        /* Chop off architecture from string */
441
0
                        if (underscore)
442
0
                                *underscore = 0;
443
0
                }
444
445
0
                if (!version_is_valid(e)) {
446
0
                        log_debug("Version string '%s' of entry '%s' is invalid, ignoring entry.", e, (*entry)->d_name);
447
0
                        continue;
448
0
                }
449
450
0
                if (filter->version && !streq(filter->version, e)) {
451
0
                        log_debug("Found entry with version string '%s', but was looking for '%s', ignoring entry.", e, filter->version);
452
0
                        continue;
453
0
                }
454
455
0
                _cleanup_free_ char *p = path_join(inode_path, (*entry)->d_name);
456
0
                if (!p)
457
0
                        return log_oom_debug();
458
459
0
                _cleanup_(pick_result_done) PickResult found = PICK_RESULT_NULL;
460
0
                r = pin_choice(toplevel_path,
461
0
                               toplevel_fd,
462
0
                               p,
463
0
                               /* _inode_fd= */ -EBADF,
464
0
                               found_tries_left,
465
0
                               found_tries_done,
466
0
                               &(const PickFilter) {
467
0
                                       .type_mask = filter->type_mask,
468
0
                                       .basename = filter->basename,
469
0
                                       .version = empty_to_null(e),
470
0
                                       .architecture = a,
471
0
                                       .suffix = filter->suffix,
472
0
                               },
473
0
                               flags,
474
0
                               &found);
475
0
                if (r == 0)
476
0
                        continue;
477
0
                if (r < 0)
478
0
                        return r;
479
480
0
                if (!best.path) {
481
0
                        best = TAKE_PICK_RESULT(found);
482
0
                        continue;
483
0
                }
484
485
0
                if (pick_result_compare(&found, &best, flags) <= 0) {
486
0
                        log_debug("Found entry '%s' but previously found entry '%s' matches better, hence skipping entry.", found.path, best.path);
487
0
                        continue;
488
0
                }
489
490
0
                pick_result_done(&best);
491
0
                best = TAKE_PICK_RESULT(found);
492
0
        }
493
494
0
        if (!best.path) { /* Everything was good, but we didn't find any suitable entry */
495
0
                *ret = PICK_RESULT_NULL;
496
0
                return 0;
497
0
        }
498
499
0
        *ret = TAKE_PICK_RESULT(best);
500
0
        return 1;
501
0
}
502
503
static int path_pick_one(
504
                const char *toplevel_path,
505
                int toplevel_fd,
506
                const char *path,
507
                const PickFilter *filter,
508
                PickFlags flags,
509
0
                PickResult *ret) {
510
511
0
        _cleanup_free_ char *filter_bname = NULL, *dir = NULL, *parent = NULL, *fname = NULL;
512
0
        const char *filter_suffix = NULL, *enumeration_path;
513
0
        uint32_t filter_type_mask;
514
0
        int r;
515
516
0
        assert(toplevel_fd >= 0 || IN_SET(toplevel_fd, AT_FDCWD, XAT_FDROOT));
517
0
        assert(path);
518
0
        assert(filter);
519
0
        assert(ret);
520
521
        /* Given a path, resolve .v/ subdir logic (if used!), and returns the choice made. This supports
522
         * three ways to be called:
523
         *
524
         * • with a path referring a directory of any name, and filter→basename *explicitly* specified, in
525
         *   which case we'll use a pattern "<filter→basename>_*<filter→suffix>" on the directory's files.
526
         *
527
         * • with no filter→basename explicitly specified and a path referring to a directory named in format
528
         *   "<somestring><filter→suffix>.v" . In this case the filter basename to search for inside the dir
529
         *   is derived from the directory name. Example: "/foo/bar/baz.suffix.v" → we'll search for
530
         *   "/foo/bar/baz.suffix.v/baz_*.suffix".
531
         *
532
         * • with a path whose penultimate component ends in ".v/". In this case the final component of the
533
         *   path refers to the pattern. Example: "/foo/bar/baz.v/waldo__.suffix" → we'll search for
534
         *   "/foo/bar/baz.v/waldo_*.suffix".
535
         */
536
537
        /* Explicit basename specified, then shortcut things and do .v mode regardless of the path name. */
538
0
        if (filter->basename)
539
0
                return make_choice(
540
0
                                toplevel_path,
541
0
                                toplevel_fd,
542
0
                                path,
543
0
                                /* inode_fd= */ -EBADF,
544
0
                                filter,
545
0
                                flags,
546
0
                                ret);
547
548
0
        r = path_extract_filename(path, &fname);
549
0
        if (r < 0) {
550
0
                if (r != -EADDRNOTAVAIL) /* root dir or "." */
551
0
                        return r;
552
553
                /* If there's no path element we can derive a pattern off, the don't */
554
0
                goto bypass;
555
0
        }
556
557
        /* Remember if the path ends in a dash suffix */
558
0
        bool slash_suffix = r == O_DIRECTORY;
559
560
0
        const char *e = endswith(fname, ".v");
561
0
        if (e) {
562
                /* So a path in the form /foo/bar/baz.v is specified. In this case our search basename is
563
                 * "baz", possibly with a suffix chopped off if there's one specified. */
564
0
                filter_bname = strndup(fname, e - fname);
565
0
                if (!filter_bname)
566
0
                        return -ENOMEM;
567
568
                /* Chop off suffix, if specified */
569
0
                if (!isempty(filter->suffix)) {
570
0
                        char *f = endswith(filter_bname, filter->suffix);
571
0
                        if (f)
572
0
                                *f = 0;
573
0
                }
574
575
0
                filter_suffix = filter->suffix;
576
0
                filter_type_mask = filter->type_mask;
577
578
0
                enumeration_path = path;
579
0
        } else {
580
                /* The path does not end in '.v', hence see if the last element is a pattern. */
581
582
0
                char *wildcard = strrstr(fname, "___");
583
0
                if (!wildcard)
584
0
                        goto bypass; /* Not a pattern, then bypass */
585
586
                /* We found the '___' wildcard, hence everything after it is our filter suffix, and
587
                 * everything before is our filter basename */
588
0
                *wildcard = 0;
589
0
                filter_suffix = empty_to_null(wildcard + 3);
590
591
0
                filter_bname = TAKE_PTR(fname);
592
593
0
                r = path_extract_directory(path, &dir);
594
0
                if (r < 0) {
595
0
                        if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL)) /* only filename specified (no dir), or root or "." */
596
0
                                return r;
597
598
0
                        goto bypass; /* No dir extractable, can't check if parent ends in ".v" */
599
0
                }
600
601
0
                r = path_extract_filename(dir, &parent);
602
0
                if (r < 0) {
603
0
                        if (r != -EADDRNOTAVAIL) /* root dir or "." */
604
0
                                return r;
605
606
0
                        goto bypass; /* Cannot extract fname from parent dir, can't check if it ends in ".v" */
607
0
                }
608
609
0
                e = endswith(parent, ".v");
610
0
                if (!e)
611
0
                        goto bypass; /* Doesn't end in ".v", shortcut */
612
613
0
                filter_type_mask = filter->type_mask;
614
0
                if (slash_suffix) {
615
                        /* If the pattern is suffixed by a / then we are looking for directories apparently. */
616
0
                        if (filter_type_mask != 0 && !BIT_SET(filter_type_mask, DT_DIR))
617
0
                                return log_debug_errno(SYNTHETIC_ERRNO(errno_from_mode(filter_type_mask, S_IFDIR)),
618
0
                                                       "Specified pattern ends in '/', but not looking for directories, refusing.");
619
0
                        filter_type_mask = UINT32_C(1) << DT_DIR;
620
0
                }
621
622
0
                enumeration_path = dir;
623
0
        }
624
625
0
        return make_choice(
626
0
                        toplevel_path,
627
0
                        toplevel_fd,
628
0
                        enumeration_path,
629
0
                        /* inode_fd= */ -EBADF,
630
0
                        &(const PickFilter) {
631
0
                                .type_mask = filter_type_mask,
632
0
                                .basename = filter_bname,
633
0
                                .version = filter->version,
634
0
                                .architecture = filter->architecture,
635
0
                                .suffix = filter_suffix,
636
0
                        },
637
0
                        flags,
638
0
                        ret);
639
640
0
bypass:
641
        /* Don't make any choice, but just use the passed path literally */
642
0
        return pin_choice(
643
0
                        toplevel_path,
644
0
                        toplevel_fd,
645
0
                        path,
646
0
                        /* inode_fd= */ -EBADF,
647
0
                        /* tries_left= */ UINT_MAX,
648
0
                        /* tries_done= */ UINT_MAX,
649
0
                        filter,
650
0
                        flags,
651
0
                        ret);
652
0
}
653
654
int path_pick(const char *toplevel_path,
655
              int toplevel_fd,
656
              const char *path,
657
              const PickFilter filters[],
658
              size_t n_filters,
659
              PickFlags flags,
660
0
              PickResult *ret) {
661
662
0
        _cleanup_(pick_result_done) PickResult best = PICK_RESULT_NULL;
663
0
        int r;
664
665
0
        assert(toplevel_fd >= 0 || IN_SET(toplevel_fd, AT_FDCWD, XAT_FDROOT));
666
0
        assert(path);
667
0
        assert(filters || n_filters == 0);
668
0
        assert(ret);
669
670
        /* Iterate through all filters and pick the best result */
671
0
        for (size_t i = 0; i < n_filters; i++) {
672
0
                _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
673
674
0
                r = path_pick_one(toplevel_path, toplevel_fd, path, &filters[i], flags, &result);
675
0
                if (r < 0)
676
0
                        return r;
677
0
                if (r == 0)
678
0
                        continue;
679
680
0
                if (!best.path) {
681
0
                        best = TAKE_PICK_RESULT(result);
682
0
                        continue;
683
0
                }
684
685
0
                if (pick_result_compare(&result, &best, flags) <= 0) {
686
0
                        log_debug("Found entry '%s' but previously found entry '%s' matches better, hence skipping entry.",
687
0
                                  result.path, best.path);
688
0
                        continue;
689
0
                }
690
691
0
                pick_result_done(&best);
692
0
                best = TAKE_PICK_RESULT(result);
693
0
        }
694
695
0
        if (!best.path) {
696
0
                *ret = PICK_RESULT_NULL;
697
0
                return 0;
698
0
        }
699
700
0
        *ret = TAKE_PICK_RESULT(best);
701
0
        return 1;
702
0
}
703
704
int path_pick_update_warn(
705
                char **path,
706
                const PickFilter filters[],
707
                size_t n_filters,
708
                PickFlags flags,
709
0
                PickResult *ret_result) {
710
711
0
        _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
712
0
        int r;
713
714
0
        assert(path);
715
0
        assert(*path);
716
0
        assert(filters || n_filters == 0);
717
718
        /* This updates the first argument if needed! */
719
720
0
        r = path_pick(/* toplevel_path= */ NULL,
721
0
                      /* toplevel_fd= */ AT_FDCWD,
722
0
                      *path,
723
0
                      filters,
724
0
                      n_filters,
725
0
                      flags,
726
0
                      &result);
727
0
        if (r == -ENOENT) {
728
0
                log_debug("Path '%s' doesn't exist, leaving as is.", *path);
729
730
0
                if (ret_result)
731
0
                        *ret_result = PICK_RESULT_NULL;
732
0
                return 0;
733
0
        }
734
0
        if (r < 0)
735
0
                return log_error_errno(r, "Failed to pick version on path '%s': %m", *path);
736
0
        if (r == 0)
737
0
                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No matching entries in versioned directory '%s' found.", *path);
738
739
0
        log_debug("Resolved versioned directory pattern '%s' to file '%s' as version '%s'.", result.path, *path, strna(result.version));
740
741
0
        if (ret_result) {
742
0
                r = free_and_strdup_warn(path, result.path);
743
0
                if (r < 0)
744
0
                        return r;
745
746
0
                *ret_result = TAKE_PICK_RESULT(result);
747
0
        } else
748
0
                free_and_replace(*path, result.path);
749
750
0
        return 1;
751
0
}
752
753
0
int path_uses_vpick(const char *path) {
754
0
        _cleanup_free_ char *dir = NULL, *parent = NULL, *fname = NULL;
755
0
        int r;
756
757
0
        assert(path);
758
759
0
        r = path_extract_filename(path, &fname);
760
0
        if (r == -EADDRNOTAVAIL)
761
0
                return 0;
762
0
        if (r < 0)
763
0
                return r;
764
765
        /* ...PATH/NAME.SUFFIX.v */
766
0
        if (endswith(fname, ".v"))
767
0
                return 1;
768
769
        /* ...PATH.v/NAME___.SUFFIX */
770
0
        if (!strrstr(fname, "___"))
771
0
                return 0;
772
773
0
        r = path_extract_directory(path, &dir);
774
0
        if (IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL)) /* only filename specified (no dir), or root or "." */
775
0
                return 0;
776
0
        if (r < 0)
777
0
                return r;
778
779
0
        r = path_extract_filename(dir, &parent);
780
0
        if (r == -EADDRNOTAVAIL)
781
0
                return 0;
782
0
        if (r < 0)
783
0
                return r;
784
785
0
        return !!endswith(parent, ".v");
786
0
}
787
788
const PickFilter pick_filter_image_raw[1] = {
789
        {
790
                .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK),
791
                .architecture = _ARCHITECTURE_INVALID,
792
                .suffix = ".raw",
793
        },
794
};
795
796
const PickFilter pick_filter_image_dir[1] = {
797
        {
798
                .type_mask = UINT32_C(1) << DT_DIR,
799
                .architecture = _ARCHITECTURE_INVALID,
800
        },
801
};
802
803
const PickFilter pick_filter_image_mstack[1] = {
804
        {
805
                .type_mask = UINT32_C(1) << DT_DIR,
806
                .architecture = _ARCHITECTURE_INVALID,
807
                .suffix = ".mstack",
808
        },
809
};