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