1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "alloc-util.h"
8 #include "blockdev-util.h"
9 #include "chase-symlinks.h"
10 #include "device-util.h"
11 #include "devnum-util.h"
12 #include "dirent-util.h"
16 #include "glyph-util.h"
18 #include "hexdecoct.h"
19 #include "import-util.h"
21 #include "process-util.h"
22 #include "sort-util.h"
23 #include "string-table.h"
24 #include "sysupdate-cache.h"
25 #include "sysupdate-instance.h"
26 #include "sysupdate-pattern.h"
27 #include "sysupdate-resource.h"
28 #include "sysupdate.h"
31 void resource_destroy(Resource
*rr
) {
35 strv_free(rr
->patterns
);
37 for (size_t i
= 0; i
< rr
->n_instances
; i
++)
38 instance_free(rr
->instances
[i
]);
42 static int resource_add_instance(
45 const InstanceMetadata
*f
,
56 if (!GREEDY_REALLOC(rr
->instances
, rr
->n_instances
+ 1))
59 r
= instance_new(rr
, path
, f
, &i
);
63 rr
->instances
[rr
->n_instances
++] = i
;
71 static int resource_load_from_directory(
75 _cleanup_(closedirp
) DIR *d
= NULL
;
79 assert(IN_SET(rr
->type
, RESOURCE_TAR
, RESOURCE_REGULAR_FILE
, RESOURCE_DIRECTORY
, RESOURCE_SUBVOLUME
));
80 assert(IN_SET(m
, S_IFREG
, S_IFDIR
));
82 d
= opendir(rr
->path
);
84 if (errno
== ENOENT
) {
85 log_debug("Directory %s does not exist, not loading any resources.", rr
->path
);
89 return log_error_errno(errno
, "Failed to open directory '%s': %m", rr
->path
);
93 _cleanup_(instance_metadata_destroy
) InstanceMetadata extracted_fields
= INSTANCE_METADATA_NULL
;
94 _cleanup_free_
char *joined
= NULL
;
100 de
= readdir_no_dot(d
);
103 return log_error_errno(errno
, "Failed to read directory '%s': %m", rr
->path
);
107 switch (de
->d_type
) {
127 if (fstatat(dirfd(d
), de
->d_name
, &st
, AT_NO_AUTOMOUNT
) < 0) {
128 if (errno
== ENOENT
) /* Gone by now? */
131 return log_error_errno(errno
, "Failed to stat %s/%s: %m", rr
->path
, de
->d_name
);
134 if ((st
.st_mode
& S_IFMT
) != m
)
137 r
= pattern_match_many(rr
->patterns
, de
->d_name
, &extracted_fields
);
139 return log_error_errno(r
, "Failed to match pattern: %m");
143 joined
= path_join(rr
->path
, de
->d_name
);
147 r
= resource_add_instance(rr
, joined
, &extracted_fields
, &instance
);
151 /* Inherit these from the source, if not explicitly overwritten */
152 if (instance
->metadata
.mtime
== USEC_INFINITY
)
153 instance
->metadata
.mtime
= timespec_load(&st
.st_mtim
) ?: USEC_INFINITY
;
155 if (instance
->metadata
.mode
== MODE_INVALID
)
156 instance
->metadata
.mode
= st
.st_mode
& 0775; /* mask out world-writability and suid and stuff, for safety */
162 static int resource_load_from_blockdev(Resource
*rr
) {
163 _cleanup_(fdisk_unref_contextp
) struct fdisk_context
*c
= NULL
;
164 _cleanup_(fdisk_unref_tablep
) struct fdisk_table
*t
= NULL
;
170 c
= fdisk_new_context();
174 r
= fdisk_assign_device(c
, rr
->path
, /* readonly= */ true);
176 return log_error_errno(r
, "Failed to open device '%s': %m", rr
->path
);
178 if (!fdisk_is_labeltype(c
, FDISK_DISKLABEL_GPT
))
179 return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON
), "Disk %s has no GPT disk label, not suitable.", rr
->path
);
181 r
= fdisk_get_partitions(c
, &t
);
183 return log_error_errno(r
, "Failed to acquire partition table: %m");
185 n_partitions
= fdisk_table_get_nents(t
);
186 for (size_t i
= 0; i
< n_partitions
; i
++) {
187 _cleanup_(instance_metadata_destroy
) InstanceMetadata extracted_fields
= INSTANCE_METADATA_NULL
;
188 _cleanup_(partition_info_destroy
) PartitionInfo pinfo
= PARTITION_INFO_NULL
;
191 r
= read_partition_info(c
, t
, i
, &pinfo
);
194 if (r
== 0) /* not assigned */
197 /* Check if partition type matches */
198 if (rr
->partition_type_set
&& !sd_id128_equal(pinfo
.type
, rr
->partition_type
.uuid
))
201 /* A label of "_empty" means "not used so far" for us */
202 if (streq_ptr(pinfo
.label
, "_empty")) {
207 r
= pattern_match_many(rr
->patterns
, pinfo
.label
, &extracted_fields
);
209 return log_error_errno(r
, "Failed to match pattern: %m");
213 r
= resource_add_instance(rr
, pinfo
.device
, &extracted_fields
, &instance
);
217 instance
->partition_info
= pinfo
;
218 pinfo
= (PartitionInfo
) PARTITION_INFO_NULL
;
220 /* Inherit data from source if not configured explicitly */
221 if (!instance
->metadata
.partition_uuid_set
) {
222 instance
->metadata
.partition_uuid
= instance
->partition_info
.uuid
;
223 instance
->metadata
.partition_uuid_set
= true;
226 if (!instance
->metadata
.partition_flags_set
) {
227 instance
->metadata
.partition_flags
= instance
->partition_info
.flags
;
228 instance
->metadata
.partition_flags_set
= true;
231 if (instance
->metadata
.read_only
< 0)
232 instance
->metadata
.read_only
= instance
->partition_info
.read_only
;
238 static int download_manifest(
240 bool verify_signature
,
244 _cleanup_free_
char *buffer
= NULL
, *suffixed_url
= NULL
;
245 _cleanup_(close_pairp
) int pfd
[2] = PIPE_EBADF
;
246 _cleanup_fclose_
FILE *manifest
= NULL
;
255 /* Download a SHA256SUMS file as manifest */
257 r
= import_url_append_component(url
, "SHA256SUMS", &suffixed_url
);
259 return log_error_errno(r
, "Failed to append SHA256SUMS to URL: %m");
261 if (pipe2(pfd
, O_CLOEXEC
) < 0)
262 return log_error_errno(errno
, "Failed to allocate pipe: %m");
264 log_info("%s Acquiring manifest file %s%s", special_glyph(SPECIAL_GLYPH_DOWNLOAD
),
265 suffixed_url
, special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
267 r
= safe_fork("(sd-pull)", FORK_RESET_SIGNALS
|FORK_DEATHSIG
|FORK_LOG
, &pid
);
273 const char *cmdline
[] = {
276 "--direct", /* just download the specified URL, don't download anything else */
277 "--verify", verify_signature
? "signature" : "no", /* verify the manifest file */
279 "-", /* write to stdout */
283 pfd
[0] = safe_close(pfd
[0]);
285 r
= rearrange_stdio(-EBADF
, pfd
[1], STDERR_FILENO
);
287 log_error_errno(r
, "Failed to rearrange stdin/stdout: %m");
291 (void) unsetenv("NOTIFY_SOCKET");
292 execv(pull_binary_path(), (char *const*) cmdline
);
293 log_error_errno(errno
, "Failed to execute %s tool: %m", pull_binary_path());
297 pfd
[1] = safe_close(pfd
[1]);
299 /* We'll first load the entire manifest into memory before parsing it. That's because the
300 * systemd-pull tool can validate the download only after its completion, but still pass the data to
301 * us as it runs. We thus need to check the return value of the process *before* parsing, to be
302 * reasonably safe. */
304 manifest
= fdopen(pfd
[0], "r");
306 return log_error_errno(errno
, "Failed allocate FILE object for manifest file: %m");
310 r
= read_full_stream(manifest
, &buffer
, &size
);
312 return log_error_errno(r
, "Failed to read manifest file from child: %m");
314 manifest
= safe_fclose(manifest
);
316 r
= wait_for_terminate_and_check("(sd-pull)", pid
, WAIT_LOG
);
322 *ret_buffer
= TAKE_PTR(buffer
);
328 static int resource_load_from_web(
331 Hashmap
**web_cache
) {
333 size_t manifest_size
= 0, left
= 0;
334 _cleanup_free_
char *buf
= NULL
;
335 const char *manifest
, *p
;
342 ci
= web_cache
? web_cache_get_item(*web_cache
, rr
->path
, verify
) : NULL
;
344 log_debug("Manifest web cache hit for %s.", rr
->path
);
346 manifest
= (char*) ci
->data
;
347 manifest_size
= ci
->size
;
349 log_debug("Manifest web cache miss for %s.", rr
->path
);
351 r
= download_manifest(rr
->path
, verify
, &buf
, &manifest_size
);
358 if (memchr(manifest
, 0, manifest_size
))
359 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Manifest file has embedded NUL byte, refusing.");
360 if (!utf8_is_valid_n(manifest
, manifest_size
))
361 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Manifest file is not valid UTF-8, refusing.");
364 left
= manifest_size
;
367 _cleanup_(instance_metadata_destroy
) InstanceMetadata extracted_fields
= INSTANCE_METADATA_NULL
;
368 _cleanup_free_
char *fn
= NULL
;
369 _cleanup_free_
void *h
= NULL
;
374 /* 64 character hash + separator + filename + newline */
376 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Corrupt manifest at line %zu, refusing.", line_nr
);
379 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "File names with escapes not supported in manifest at line %zu, refusing.", line_nr
);
381 r
= unhexmem(p
, 64, &h
, &hlen
);
383 return log_error_errno(r
, "Failed to parse digest at manifest line %zu, refusing.", line_nr
);
388 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Missing space separator at manifest line %zu, refusing.", line_nr
);
391 if (!IN_SET(*p
, '*', ' '))
392 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Missing binary/text input marker at manifest line %zu, refusing.", line_nr
);
395 e
= memchr(p
, '\n', left
);
397 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Truncated manifest file at line %zu, refusing.", line_nr
);
399 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Empty filename specified at manifest line %zu, refusing.", line_nr
);
401 fn
= strndup(p
, e
- p
);
405 if (!filename_is_valid(fn
))
406 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid filename specified at manifest line %zu, refusing.", line_nr
);
407 if (string_has_cc(fn
, NULL
))
408 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Filename contains control characters at manifest line %zu, refusing.", line_nr
);
410 r
= pattern_match_many(rr
->patterns
, fn
, &extracted_fields
);
412 return log_error_errno(r
, "Failed to match pattern: %m");
414 _cleanup_free_
char *path
= NULL
;
416 r
= import_url_append_component(rr
->path
, fn
, &path
);
418 return log_error_errno(r
, "Failed to build instance URL: %m");
420 r
= resource_add_instance(rr
, path
, &extracted_fields
, &instance
);
424 assert(hlen
== sizeof(instance
->metadata
.sha256sum
));
426 if (instance
->metadata
.sha256sum_set
) {
427 if (memcmp(instance
->metadata
.sha256sum
, h
, hlen
) != 0)
428 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "SHA256 sum parsed from filename and manifest don't match at line %zu, refusing.", line_nr
);
430 memcpy(instance
->metadata
.sha256sum
, h
, hlen
);
431 instance
->metadata
.sha256sum_set
= true;
441 if (!ci
&& web_cache
) {
442 r
= web_cache_add_item(web_cache
, rr
->path
, verify
, manifest
, manifest_size
);
444 log_debug_errno(r
, "Failed to add manifest '%s' to cache, ignoring: %m", rr
->path
);
446 log_debug("Added manifest '%s' to cache.", rr
->path
);
452 static int instance_cmp(Instance
*const*a
, Instance
*const*b
) {
459 assert((*a
)->metadata
.version
);
460 assert((*b
)->metadata
.version
);
462 /* Newest version at the beginning */
463 r
= strverscmp_improved((*a
)->metadata
.version
, (*b
)->metadata
.version
);
467 /* Instances don't have to be uniquely named (uniqueness on partition tables is not enforced at all,
468 * and since we allow multiple matching patterns not even in directories they are unique). Hence
469 * let's order by path as secondary ordering key. */
470 return path_compare((*a
)->path
, (*b
)->path
);
473 int resource_load_instances(Resource
*rr
, bool verify
, Hashmap
**web_cache
) {
481 case RESOURCE_REGULAR_FILE
:
482 r
= resource_load_from_directory(rr
, S_IFREG
);
485 case RESOURCE_DIRECTORY
:
486 case RESOURCE_SUBVOLUME
:
487 r
= resource_load_from_directory(rr
, S_IFDIR
);
490 case RESOURCE_PARTITION
:
491 r
= resource_load_from_blockdev(rr
);
494 case RESOURCE_URL_FILE
:
495 case RESOURCE_URL_TAR
:
496 r
= resource_load_from_web(rr
, verify
, web_cache
);
500 assert_not_reached();
505 typesafe_qsort(rr
->instances
, rr
->n_instances
, instance_cmp
);
509 Instance
* resource_find_instance(Resource
*rr
, const char *version
) {
511 .metadata
.version
= (char*) version
,
514 return typesafe_bsearch(&k
, rr
->instances
, rr
->n_instances
, instance_cmp
);
517 int resource_resolve_path(
522 _cleanup_free_
char *p
= NULL
;
529 struct stat orig_root_stats
;
531 /* NB: If the root mount has been replaced by some form of volatile file system (overlayfs),
532 * the original root block device node is symlinked in /run/systemd/volatile-root. Let's
533 * follow that link here. If that doesn't exist, we check the backing device of "/usr". We
534 * don't actually check the backing device of the root fs "/", in order to support
535 * environments where the root fs is a tmpfs, and the OS itself placed exclusively in
538 if (rr
->type
!= RESOURCE_PARTITION
)
539 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
540 "Automatic root path discovery only supported for partition resources.");
542 if (node
) { /* If --image= is specified, directly use the loopback device */
543 r
= free_and_strdup_warn(&rr
->path
, node
);
551 return log_error_errno(SYNTHETIC_ERRNO(EPERM
),
552 "Block device is not allowed when using --root= mode.");
554 r
= stat("/run/systemd/volatile-root", &orig_root_stats
);
556 if (errno
== ENOENT
) /* volatile-root not found */
557 r
= get_block_device_harder("/usr/", &d
);
559 return log_error_errno(r
, "Failed to stat /run/systemd/volatile-root: %m");
560 } else if (!S_ISBLK(orig_root_stats
.st_mode
)) /* symlink was present but not block device */
561 return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK
), "/run/systemd/volatile-root is not linked to a block device.");
562 else /* symlink was present and a block device */
563 d
= orig_root_stats
.st_rdev
;
565 } else if (rr
->type
== RESOURCE_PARTITION
) {
566 _cleanup_close_
int fd
= -EBADF
, real_fd
= -EBADF
;
567 _cleanup_free_
char *resolved
= NULL
;
570 r
= chase_symlinks(rr
->path
, root
, CHASE_PREFIX_ROOT
, &resolved
, &fd
);
572 return log_error_errno(r
, "Failed to resolve '%s': %m", rr
->path
);
574 if (fstat(fd
, &st
) < 0)
575 return log_error_errno(errno
, "Failed to stat '%s': %m", resolved
);
577 if (S_ISBLK(st
.st_mode
) && root
)
578 return log_error_errno(SYNTHETIC_ERRNO(EPERM
), "When using --root= or --image= access to device nodes is prohibited.");
580 if (S_ISREG(st
.st_mode
) || S_ISBLK(st
.st_mode
)) {
581 /* Not a directory, hence no need to find backing block device for the path */
582 free_and_replace(rr
->path
, resolved
);
586 if (!S_ISDIR(st
.st_mode
))
587 return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR
), "Target path '%s' does not refer to regular file, directory or block device, refusing.", rr
->path
);
589 if (node
) { /* If --image= is specified all file systems are backed by the same loopback device, hence shortcut things. */
590 r
= free_and_strdup_warn(&rr
->path
, node
);
597 real_fd
= fd_reopen(fd
, O_RDONLY
|O_CLOEXEC
|O_DIRECTORY
);
599 return log_error_errno(real_fd
, "Failed to convert O_PATH file descriptor for %s to regular file descriptor: %m", rr
->path
);
601 r
= get_block_device_harder_fd(fd
, &d
);
603 } else if (RESOURCE_IS_FILESYSTEM(rr
->type
) && root
) {
604 _cleanup_free_
char *resolved
= NULL
;
606 r
= chase_symlinks(rr
->path
, root
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
608 return log_error_errno(r
, "Failed to resolve '%s': %m", rr
->path
);
610 free_and_replace(rr
->path
, resolved
);
613 return 0; /* Otherwise assume there's nothing to resolve */
616 return log_error_errno(r
, "Failed to determine block device of file system: %m");
618 r
= block_get_whole_disk(d
, &d
);
620 return log_error_errno(r
, "Failed to find whole disk device for partition backing file system: %m");
622 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
623 "File system is not placed on a partition block device, cannot determine whole block device backing root file system.");
625 r
= devname_from_devnum(S_IFBLK
, d
, &p
);
630 log_info("Automatically discovered block device '%s' from '%s'.", p
, rr
->path
);
632 log_info("Automatically discovered root block device '%s'.", p
);
634 free_and_replace(rr
->path
, p
);
638 static const char *resource_type_table
[_RESOURCE_TYPE_MAX
] = {
639 [RESOURCE_URL_FILE
] = "url-file",
640 [RESOURCE_URL_TAR
] = "url-tar",
641 [RESOURCE_TAR
] = "tar",
642 [RESOURCE_PARTITION
] = "partition",
643 [RESOURCE_REGULAR_FILE
] = "regular-file",
644 [RESOURCE_DIRECTORY
] = "directory",
645 [RESOURCE_SUBVOLUME
] = "subvolume",
648 DEFINE_STRING_TABLE_LOOKUP(resource_type
, ResourceType
);