1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "architecture.h"
9 #include "parse-util.h"
10 #include "path-util.h"
11 #include "recurse-dir.h"
14 void pick_result_done(PickResult
*p
) {
21 *p
= PICK_RESULT_NULL
;
24 static int format_fname(
25 const PickFilter
*filter
,
29 _cleanup_free_
char *fn
= NULL
;
35 if (FLAGS_SET(flags
, PICK_TRIES
) || !filter
->version
) /* Underspecified? */
38 /* The format for names we match goes like this:
42 * <basename>_<version><suffix>
44 * <basename>_<version>_<architecture><suffix>
46 * <basename>_<architecture><suffix>
48 * (Note that basename can be empty, in which case the leading "_" is suppressed)
50 * Examples: foo.raw, foo_1.3-7.raw, foo_1.3-7_x86-64.raw, foo_x86-64.raw
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
57 * This is very close to Debian's way to name packages, but allows arbitrary suffixes, and makes the
58 * architecture field redundant.
60 * Compare with RPM's "NEVRA" concept. Here we have "BVAS" (basename, version, architecture, suffix).
63 if (filter
->basename
) {
64 fn
= strdup(filter
->basename
);
69 if (filter
->version
) {
71 r
= free_and_strdup(&fn
, filter
->version
);
74 } else if (!strextend(&fn
, "_", filter
->version
))
78 if (FLAGS_SET(flags
, PICK_ARCHITECTURE
) && filter
->architecture
>= 0) {
79 const char *as
= ASSERT_PTR(architecture_to_string(filter
->architecture
));
81 r
= free_and_strdup(&fn
, as
);
84 } else if (!strextend(&fn
, "_", as
))
88 if (filter
->suffix
&& !strextend(&fn
, filter
->suffix
))
91 if (!filename_is_valid(fn
))
98 static int errno_from_mode(uint32_t type_mask
, mode_t found
) {
99 /* Returns the most appropriate error code if we are lookging for an inode of type of those in the
100 * 'type_mask' but found 'found' instead.
102 * type_mask is a mask of 1U << DT_REG, 1U << DT_DIR, … flags, while found is a S_IFREG, S_IFDIR, …
105 if (type_mask
== 0) /* type doesn't matter */
108 if (FLAGS_SET(type_mask
, UINT32_C(1) << IFTODT(found
)))
111 if (type_mask
== (UINT32_C(1) << DT_BLK
))
113 if (type_mask
== (UINT32_C(1) << DT_DIR
))
115 if (type_mask
== (UINT32_C(1) << DT_SOCK
))
126 static int pin_choice(
127 const char *toplevel_path
,
129 const char *inode_path
,
130 int _inode_fd
, /* we always take ownership of the fd, even on failure */
133 const PickFilter
*filter
,
137 _cleanup_close_
int inode_fd
= TAKE_FD(_inode_fd
);
138 _cleanup_free_
char *resolved_path
= NULL
;
141 assert(toplevel_fd
>= 0 || toplevel_fd
== AT_FDCWD
);
146 if (inode_fd
< 0 || FLAGS_SET(flags
, PICK_RESOLVE
)) {
147 r
= chaseat(toplevel_fd
,
149 CHASE_AT_RESOLVE_IN_ROOT
,
150 FLAGS_SET(flags
, PICK_RESOLVE
) ? &resolved_path
: 0,
151 inode_fd
< 0 ? &inode_fd
: NULL
);
156 inode_path
= resolved_path
;
160 if (fstat(inode_fd
, &st
) < 0)
161 return log_debug_errno(errno
, "Failed to stat discovered inode '%s': %m", prefix_roota(toplevel_path
, inode_path
));
163 if (filter
->type_mask
!= 0 &&
164 !FLAGS_SET(filter
->type_mask
, UINT32_C(1) << IFTODT(st
.st_mode
)))
165 return log_debug_errno(
166 SYNTHETIC_ERRNO(errno_from_mode(filter
->type_mask
, st
.st_mode
)),
167 "Inode '%s' has wrong type, found '%s'.",
168 prefix_roota(toplevel_path
, inode_path
),
169 inode_type_to_string(st
.st_mode
));
171 _cleanup_(pick_result_done
) PickResult result
= {
172 .fd
= TAKE_FD(inode_fd
),
174 .architecture
= filter
->architecture
,
175 .tries_left
= tries_left
,
176 .tries_done
= tries_done
,
179 result
.path
= strdup(inode_path
);
181 return log_oom_debug();
183 if (filter
->version
) {
184 result
.version
= strdup(filter
->version
);
186 return log_oom_debug();
189 *ret
= TAKE_PICK_RESULT(result
);
193 static int parse_tries(const char *s
, unsigned *ret_tries_left
, unsigned *ret_tries_done
) {
198 assert(ret_tries_left
);
199 assert(ret_tries_done
);
206 n
= strspn(s
, DIGITS
);
211 if (safe_atou(s
, &left
) < 0)
215 } else if (s
[n
] == '-') {
216 _cleanup_free_
char *c
= NULL
;
222 if (safe_atou(c
, &left
) < 0)
227 if (!in_charset(s
, DIGITS
))
230 if (safe_atou(s
, &done
) < 0)
235 *ret_tries_left
= left
;
236 *ret_tries_done
= done
;
240 *ret_tries_left
= *ret_tries_done
= UINT_MAX
;
244 static int make_choice(
245 const char *toplevel_path
,
247 const char *inode_path
,
248 int _inode_fd
, /* we always take ownership of the fd, even on failure */
249 const PickFilter
*filter
,
253 static const Architecture local_architectures
[] = {
254 /* In order of preference */
255 native_architecture(),
256 #ifdef ARCHITECTURE_SECONDARY
257 ARCHITECTURE_SECONDARY
,
259 _ARCHITECTURE_INVALID
, /* accept any arch, as last resort */
262 _cleanup_free_ DirectoryEntries
*de
= NULL
;
263 _cleanup_free_
char *best_version
= NULL
, *best_filename
= NULL
, *p
= NULL
, *j
= NULL
;
264 _cleanup_close_
int dir_fd
= -EBADF
, object_fd
= -EBADF
, inode_fd
= TAKE_FD(_inode_fd
);
265 const Architecture
*architectures
;
266 unsigned best_tries_left
= UINT_MAX
, best_tries_done
= UINT_MAX
;
267 size_t n_architectures
, best_architecture_index
= SIZE_MAX
;
270 assert(toplevel_fd
>= 0 || toplevel_fd
== AT_FDCWD
);
276 r
= chaseat(toplevel_fd
, inode_path
, CHASE_AT_RESOLVE_IN_ROOT
, NULL
, &inode_fd
);
281 /* Maybe the filter is fully specified? Then we can generate the file name directly */
282 r
= format_fname(filter
, flags
, &j
);
284 _cleanup_free_
char *object_path
= NULL
;
286 /* Yay! This worked! */
287 p
= path_join(inode_path
, j
);
289 return log_oom_debug();
291 r
= chaseat(toplevel_fd
, p
, CHASE_AT_RESOLVE_IN_ROOT
, &object_path
, &object_fd
);
294 return log_debug_errno(r
, "Failed to open '%s': %m", prefix_roota(toplevel_path
, p
));
296 *ret
= PICK_RESULT_NULL
;
303 FLAGS_SET(flags
, PICK_RESOLVE
) ? object_path
: p
,
304 TAKE_FD(object_fd
), /* unconditionally pass ownership of the fd */
305 /* tries_left= */ UINT_MAX
,
306 /* tries_done= */ UINT_MAX
,
308 flags
& ~PICK_RESOLVE
,
311 } else if (r
!= -ENOEXEC
)
312 return log_debug_errno(r
, "Failed to format file name: %m");
314 /* Underspecified, so we do our enumeration dance */
316 /* Convert O_PATH to a regular directory fd */
317 dir_fd
= fd_reopen(inode_fd
, O_DIRECTORY
|O_RDONLY
|O_CLOEXEC
);
319 return log_debug_errno(dir_fd
, "Failed to reopen '%s' as directory: %m", prefix_roota(toplevel_path
, inode_path
));
321 r
= readdir_all(dir_fd
, 0, &de
);
323 return log_debug_errno(r
, "Failed to read directory '%s': %m", prefix_roota(toplevel_path
, inode_path
));
325 if (filter
->architecture
< 0) {
326 architectures
= local_architectures
;
327 n_architectures
= ELEMENTSOF(local_architectures
);
329 architectures
= &filter
->architecture
;
333 FOREACH_ARRAY(entry
, de
->entries
, de
->n_entries
) {
334 unsigned found_tries_done
= UINT_MAX
, found_tries_left
= UINT_MAX
;
335 _cleanup_free_
char *dname
= NULL
;
336 size_t found_architecture_index
= SIZE_MAX
;
339 dname
= strdup((*entry
)->d_name
);
341 return log_oom_debug();
343 if (!isempty(filter
->basename
)) {
344 e
= startswith(dname
, filter
->basename
);
355 if (!isempty(filter
->suffix
)) {
356 char *sfx
= endswith(e
, filter
->suffix
);
363 if (FLAGS_SET(flags
, PICK_TRIES
)) {
364 char *plus
= strrchr(e
, '+');
366 r
= parse_tries(plus
, &found_tries_left
, &found_tries_done
);
369 if (r
> 0) /* Found and parsed, now chop off */
374 if (FLAGS_SET(flags
, PICK_ARCHITECTURE
)) {
375 char *underscore
= strrchr(e
, '_');
378 a
= underscore
? architecture_from_string(underscore
+ 1) : _ARCHITECTURE_INVALID
;
380 for (size_t i
= 0; i
< n_architectures
; i
++)
381 if (architectures
[i
] == a
) {
382 found_architecture_index
= i
;
386 if (found_architecture_index
== SIZE_MAX
) { /* No matching arch found */
387 log_debug("Found entry with architecture '%s' which is not what we are looking for, ignoring entry.", a
< 0 ? "any" : architecture_to_string(a
));
391 /* Chop off architecture from string */
396 if (!version_is_valid(e
)) {
397 log_debug("Version string '%s' of entry '%s' is invalid, ignoring entry.", e
, (*entry
)->d_name
);
401 if (filter
->version
&& !streq(filter
->version
, e
)) {
402 log_debug("Found entry with version string '%s', but was looking for '%s', ignoring entry.", e
, filter
->version
);
406 if (best_filename
) { /* Already found one matching entry? Then figure out the better one */
409 /* First, prefer entries with tries left over those without */
410 if (FLAGS_SET(flags
, PICK_TRIES
))
411 d
= CMP(found_tries_left
!= 0, best_tries_left
!= 0);
413 /* Second, prefer newer versions */
415 d
= strverscmp_improved(e
, best_version
);
417 /* Third, prefer native architectures over secondary architectures */
419 FLAGS_SET(flags
, PICK_ARCHITECTURE
) &&
420 found_architecture_index
!= SIZE_MAX
&& best_architecture_index
!= SIZE_MAX
)
421 d
= -CMP(found_architecture_index
, best_architecture_index
);
423 /* Fourth, prefer entries with more tries left */
424 if (FLAGS_SET(flags
, PICK_TRIES
)) {
426 d
= CMP(found_tries_left
, best_tries_left
);
428 /* Fifth, prefer entries with fewer attempts done so far */
430 d
= -CMP(found_tries_done
, best_tries_done
);
433 /* Last, just compare the filenames as strings */
435 d
= strcmp((*entry
)->d_name
, best_filename
);
438 log_debug("Found entry '%s' but previously found entry '%s' matches better, hence skipping entry.", (*entry
)->d_name
, best_filename
);
443 r
= free_and_strdup_warn(&best_version
, e
);
447 r
= free_and_strdup_warn(&best_filename
, (*entry
)->d_name
);
451 best_architecture_index
= found_architecture_index
;
452 best_tries_left
= found_tries_left
;
453 best_tries_done
= found_tries_done
;
456 if (!best_filename
) { /* Everything was good, but we didn't find any suitable entry */
457 *ret
= PICK_RESULT_NULL
;
461 p
= path_join(inode_path
, best_filename
);
463 return log_oom_debug();
465 object_fd
= openat(dir_fd
, best_filename
, O_CLOEXEC
|O_PATH
);
467 return log_debug_errno(errno
, "Failed to open '%s': %m", prefix_roota(toplevel_path
, p
));
476 &(const PickFilter
) {
477 .type_mask
= filter
->type_mask
,
478 .basename
= filter
->basename
,
479 .version
= empty_to_null(best_version
),
480 .architecture
= best_architecture_index
!= SIZE_MAX
? architectures
[best_architecture_index
] : _ARCHITECTURE_INVALID
,
481 .suffix
= filter
->suffix
,
488 const char *toplevel_path
,
491 const PickFilter
*filter
,
495 _cleanup_free_
char *filter_bname
= NULL
, *dir
= NULL
, *parent
= NULL
, *fname
= NULL
;
496 const char *filter_suffix
, *enumeration_path
;
497 uint32_t filter_type_mask
;
500 assert(toplevel_fd
>= 0 || toplevel_fd
== AT_FDCWD
);
505 /* Given a path, resolve .v/ subdir logic (if used!), and returns the choice made. This supports
506 * three ways to be called:
508 * • with a path referring a directory of any name, and filter→basename *explicitly* specified, in
509 * which case we'll use a pattern "<filter→basename>_*<filter→suffix>" on the directory's files.
511 * • with no filter→basename explicitly specified and a path referring to a directory named in format
512 * "<somestring><filter→suffix>.v" . In this case the filter basename to search for inside the dir
513 * is derived from the directory name. Example: "/foo/bar/baz.suffix.v" → we'll search for
514 * "/foo/bar/baz.suffix.v/baz_*.suffix".
516 * • with a path whose penultimate component ends in ".v/". In this case the final component of the
517 * path refers to the pattern. Example: "/foo/bar/baz.v/waldo__.suffix" → we'll search for
518 * "/foo/bar/baz.v/waldo_*.suffix".
521 /* Explicit basename specified, then shortcut things and do .v mode regardless of the path name. */
522 if (filter
->basename
)
527 /* inode_fd= */ -EBADF
,
532 r
= path_extract_filename(path
, &fname
);
534 if (r
!= -EADDRNOTAVAIL
) /* root dir or "." */
537 /* If there's no path element we can derive a pattern off, the don't */
541 /* Remember if the path ends in a dash suffix */
542 bool slash_suffix
= r
== O_DIRECTORY
;
544 const char *e
= endswith(fname
, ".v");
546 /* So a path in the form /foo/bar/baz.v is specified. In this case our search basename is
547 * "baz", possibly with a suffix chopped off if there's one specified. */
548 filter_bname
= strndup(fname
, e
- fname
);
552 if (filter
->suffix
) {
553 /* Chop off suffix, if specified */
554 char *f
= endswith(filter_bname
, filter
->suffix
);
559 filter_suffix
= filter
->suffix
;
560 filter_type_mask
= filter
->type_mask
;
562 enumeration_path
= path
;
564 /* The path does not end in '.v', hence see if the last element is a pattern. */
566 char *wildcard
= strrstr(fname
, "___");
568 goto bypass
; /* Not a pattern, then bypass */
570 /* We found the '___' wildcard, hence everything after it is our filter suffix, and
571 * everything before is our filter basename */
573 filter_suffix
= empty_to_null(wildcard
+ 3);
575 filter_bname
= TAKE_PTR(fname
);
577 r
= path_extract_directory(path
, &dir
);
579 if (!IN_SET(r
, -EDESTADDRREQ
, -EADDRNOTAVAIL
)) /* only filename specified (no dir), or root or "." */
582 goto bypass
; /* No dir extractable, can't check if parent ends in ".v" */
585 r
= path_extract_filename(dir
, &parent
);
587 if (r
!= -EADDRNOTAVAIL
) /* root dir or "." */
590 goto bypass
; /* Cannot extract fname from parent dir, can't check if it ends in ".v" */
593 e
= endswith(parent
, ".v");
595 goto bypass
; /* Doesn't end in ".v", shortcut */
597 filter_type_mask
= filter
->type_mask
;
599 /* If the pattern is suffixed by a / then we are looking for directories apparently. */
600 if (filter_type_mask
!= 0 && !FLAGS_SET(filter_type_mask
, UINT32_C(1) << DT_DIR
))
601 return log_debug_errno(SYNTHETIC_ERRNO(errno_from_mode(filter_type_mask
, S_IFDIR
)),
602 "Specified pattern ends in '/', but not looking for directories, refusing.");
603 filter_type_mask
= UINT32_C(1) << DT_DIR
;
606 enumeration_path
= dir
;
613 /* inode_fd= */ -EBADF
,
614 &(const PickFilter
) {
615 .type_mask
= filter_type_mask
,
616 .basename
= filter_bname
,
617 .version
= filter
->version
,
618 .architecture
= filter
->architecture
,
619 .suffix
= filter_suffix
,
625 /* Don't make any choice, but just use the passed path literally */
630 /* inode_fd= */ -EBADF
,
631 /* tries_left= */ UINT_MAX
,
632 /* tries_done= */ UINT_MAX
,
638 int path_pick_update_warn(
640 const PickFilter
*filter
,
642 PickResult
*ret_result
) {
644 _cleanup_(pick_result_done
) PickResult result
= PICK_RESULT_NULL
;
651 /* This updates the first argument if needed! */
653 r
= path_pick(/* toplevel_path= */ NULL
,
654 /* toplevel_fd= */ AT_FDCWD
,
660 log_debug("Path '%s' doesn't exist, leaving as is.", *path
);
661 *ret_result
= PICK_RESULT_NULL
;
665 return log_error_errno(r
, "Failed to pick version on path '%s': %m", *path
);
667 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
), "No matching entries in versioned directory '%s' found.", *path
);
669 log_debug("Resolved versioned directory pattern '%s' to file '%s' as version '%s'.", result
.path
, *path
, strna(result
.version
));
672 r
= free_and_strdup_warn(path
, result
.path
);
676 *ret_result
= TAKE_PICK_RESULT(result
);
678 free_and_replace(*path
, result
.path
);
683 const PickFilter pick_filter_image_raw
= {
684 .type_mask
= (UINT32_C(1) << DT_REG
) | (UINT32_C(1) << DT_BLK
),
685 .architecture
= _ARCHITECTURE_INVALID
,
689 const PickFilter pick_filter_image_dir
= {
690 .type_mask
= UINT32_C(1) << DT_DIR
,
691 .architecture
= _ARCHITECTURE_INVALID
,