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 "devnum-util.h"
11 #include "dirent-util.h"
15 #include "glyph-util.h"
17 #include "hexdecoct.h"
18 #include "import-util.h"
20 #include "process-util.h"
21 #include "sort-util.h"
22 #include "string-table.h"
23 #include "sysupdate-cache.h"
24 #include "sysupdate-instance.h"
25 #include "sysupdate-pattern.h"
26 #include "sysupdate-resource.h"
27 #include "sysupdate.h"
30 void resource_destroy(Resource
*rr
) {
34 strv_free(rr
->patterns
);
36 for (size_t i
= 0; i
< rr
->n_instances
; i
++)
37 instance_free(rr
->instances
[i
]);
41 static int resource_add_instance(
44 const InstanceMetadata
*f
,
55 if (!GREEDY_REALLOC(rr
->instances
, rr
->n_instances
+ 1))
58 r
= instance_new(rr
, path
, f
, &i
);
62 rr
->instances
[rr
->n_instances
++] = i
;
70 static int resource_load_from_directory(
74 _cleanup_(closedirp
) DIR *d
= NULL
;
78 assert(IN_SET(rr
->type
, RESOURCE_TAR
, RESOURCE_REGULAR_FILE
, RESOURCE_DIRECTORY
, RESOURCE_SUBVOLUME
));
79 assert(IN_SET(m
, S_IFREG
, S_IFDIR
));
81 d
= opendir(rr
->path
);
83 if (errno
== ENOENT
) {
84 log_debug("Directory %s does not exist, not loading any resources.", rr
->path
);
88 return log_error_errno(errno
, "Failed to open directory '%s': %m", rr
->path
);
92 _cleanup_(instance_metadata_destroy
) InstanceMetadata extracted_fields
= INSTANCE_METADATA_NULL
;
93 _cleanup_free_
char *joined
= NULL
;
99 de
= readdir_no_dot(d
);
102 return log_error_errno(errno
, "Failed to read directory '%s': %m", rr
->path
);
106 switch (de
->d_type
) {
126 if (fstatat(dirfd(d
), de
->d_name
, &st
, AT_NO_AUTOMOUNT
) < 0) {
127 if (errno
== ENOENT
) /* Gone by now? */
130 return log_error_errno(errno
, "Failed to stat %s/%s: %m", rr
->path
, de
->d_name
);
133 if ((st
.st_mode
& S_IFMT
) != m
)
136 r
= pattern_match_many(rr
->patterns
, de
->d_name
, &extracted_fields
);
138 return log_error_errno(r
, "Failed to match pattern: %m");
142 joined
= path_join(rr
->path
, de
->d_name
);
146 r
= resource_add_instance(rr
, joined
, &extracted_fields
, &instance
);
150 /* Inherit these from the source, if not explicitly overwritten */
151 if (instance
->metadata
.mtime
== USEC_INFINITY
)
152 instance
->metadata
.mtime
= timespec_load(&st
.st_mtim
) ?: USEC_INFINITY
;
154 if (instance
->metadata
.mode
== MODE_INVALID
)
155 instance
->metadata
.mode
= st
.st_mode
& 0775; /* mask out world-writability and suid and stuff, for safety */
161 static int resource_load_from_blockdev(Resource
*rr
) {
162 _cleanup_(fdisk_unref_contextp
) struct fdisk_context
*c
= NULL
;
163 _cleanup_(fdisk_unref_tablep
) struct fdisk_table
*t
= NULL
;
169 c
= fdisk_new_context();
173 r
= fdisk_assign_device(c
, rr
->path
, /* readonly= */ true);
175 return log_error_errno(r
, "Failed to open device '%s': %m", rr
->path
);
177 if (!fdisk_is_labeltype(c
, FDISK_DISKLABEL_GPT
))
178 return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON
), "Disk %s has no GPT disk label, not suitable.", rr
->path
);
180 r
= fdisk_get_partitions(c
, &t
);
182 return log_error_errno(r
, "Failed to acquire partition table: %m");
184 n_partitions
= fdisk_table_get_nents(t
);
185 for (size_t i
= 0; i
< n_partitions
; i
++) {
186 _cleanup_(instance_metadata_destroy
) InstanceMetadata extracted_fields
= INSTANCE_METADATA_NULL
;
187 _cleanup_(partition_info_destroy
) PartitionInfo pinfo
= PARTITION_INFO_NULL
;
190 r
= read_partition_info(c
, t
, i
, &pinfo
);
193 if (r
== 0) /* not assigned */
196 /* Check if partition type matches */
197 if (rr
->partition_type_set
&& !sd_id128_equal(pinfo
.type
, rr
->partition_type
))
200 /* A label of "_empty" means "not used so far" for us */
201 if (streq_ptr(pinfo
.label
, "_empty")) {
206 r
= pattern_match_many(rr
->patterns
, pinfo
.label
, &extracted_fields
);
208 return log_error_errno(r
, "Failed to match pattern: %m");
212 r
= resource_add_instance(rr
, pinfo
.device
, &extracted_fields
, &instance
);
216 instance
->partition_info
= pinfo
;
217 pinfo
= (PartitionInfo
) PARTITION_INFO_NULL
;
219 /* Inherit data from source if not configured explicitly */
220 if (!instance
->metadata
.partition_uuid_set
) {
221 instance
->metadata
.partition_uuid
= instance
->partition_info
.uuid
;
222 instance
->metadata
.partition_uuid_set
= true;
225 if (!instance
->metadata
.partition_flags_set
) {
226 instance
->metadata
.partition_flags
= instance
->partition_info
.flags
;
227 instance
->metadata
.partition_flags_set
= true;
230 if (instance
->metadata
.read_only
< 0)
231 instance
->metadata
.read_only
= instance
->partition_info
.read_only
;
237 static int download_manifest(
239 bool verify_signature
,
243 _cleanup_free_
char *buffer
= NULL
, *suffixed_url
= NULL
;
244 _cleanup_(close_pairp
) int pfd
[2] = { -1, -1 };
245 _cleanup_fclose_
FILE *manifest
= NULL
;
254 /* Download a SHA256SUMS file as manifest */
256 r
= import_url_append_component(url
, "SHA256SUMS", &suffixed_url
);
258 return log_error_errno(r
, "Failed to append SHA256SUMS to URL: %m");
260 if (pipe2(pfd
, O_CLOEXEC
) < 0)
261 return log_error_errno(errno
, "Failed to allocate pipe: %m");
263 log_info("%s Acquiring manifest file %s%s", special_glyph(SPECIAL_GLYPH_DOWNLOAD
),
264 suffixed_url
, special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
266 r
= safe_fork("(sd-pull)", FORK_RESET_SIGNALS
|FORK_DEATHSIG
|FORK_LOG
, &pid
);
272 const char *cmdline
[] = {
275 "--direct", /* just download the specified URL, don't download anything else */
276 "--verify", verify_signature
? "signature" : "no", /* verify the manifest file */
278 "-", /* write to stdout */
282 pfd
[0] = safe_close(pfd
[0]);
284 r
= rearrange_stdio(-1, pfd
[1], STDERR_FILENO
);
286 log_error_errno(r
, "Failed to rearrange stdin/stdout: %m");
290 (void) unsetenv("NOTIFY_SOCKET");
291 execv(pull_binary_path(), (char *const*) cmdline
);
292 log_error_errno(errno
, "Failed to execute %s tool: %m", pull_binary_path());
296 pfd
[1] = safe_close(pfd
[1]);
298 /* We'll first load the entire manifest into memory before parsing it. That's because the
299 * systemd-pull tool can validate the download only after its completion, but still pass the data to
300 * us as it runs. We thus need to check the return value of the process *before* parsing, to be
301 * reasonably safe. */
303 manifest
= fdopen(pfd
[0], "r");
305 return log_error_errno(errno
, "Failed allocate FILE object for manifest file: %m");
309 r
= read_full_stream(manifest
, &buffer
, &size
);
311 return log_error_errno(r
, "Failed to read manifest file from child: %m");
313 manifest
= safe_fclose(manifest
);
315 r
= wait_for_terminate_and_check("(sd-pull)", pid
, WAIT_LOG
);
321 *ret_buffer
= TAKE_PTR(buffer
);
327 static int resource_load_from_web(
330 Hashmap
**web_cache
) {
332 size_t manifest_size
= 0, left
= 0;
333 _cleanup_free_
char *buf
= NULL
;
334 const char *manifest
, *p
;
341 ci
= web_cache
? web_cache_get_item(*web_cache
, rr
->path
, verify
) : NULL
;
343 log_debug("Manifest web cache hit for %s.", rr
->path
);
345 manifest
= (char*) ci
->data
;
346 manifest_size
= ci
->size
;
348 log_debug("Manifest web cache miss for %s.", rr
->path
);
350 r
= download_manifest(rr
->path
, verify
, &buf
, &manifest_size
);
357 if (memchr(manifest
, 0, manifest_size
))
358 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Manifest file has embedded NUL byte, refusing.");
359 if (!utf8_is_valid_n(manifest
, manifest_size
))
360 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Manifest file is not valid UTF-8, refusing.");
363 left
= manifest_size
;
366 _cleanup_(instance_metadata_destroy
) InstanceMetadata extracted_fields
= INSTANCE_METADATA_NULL
;
367 _cleanup_free_
char *fn
= NULL
;
368 _cleanup_free_
void *h
= NULL
;
373 /* 64 character hash + separator + filename + newline */
375 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Corrupt manifest at line %zu, refusing.", line_nr
);
378 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "File names with escapes not supported in manifest at line %zu, refusing.", line_nr
);
380 r
= unhexmem(p
, 64, &h
, &hlen
);
382 return log_error_errno(r
, "Failed to parse digest at manifest line %zu, refusing.", line_nr
);
387 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Missing space separator at manifest line %zu, refusing.", line_nr
);
390 if (!IN_SET(*p
, '*', ' '))
391 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Missing binary/text input marker at manifest line %zu, refusing.", line_nr
);
394 e
= memchr(p
, '\n', left
);
396 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Truncated manifest file at line %zu, refusing.", line_nr
);
398 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Empty filename specified at manifest line %zu, refusing.", line_nr
);
400 fn
= strndup(p
, e
- p
);
404 if (!filename_is_valid(fn
))
405 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid filename specified at manifest line %zu, refusing.", line_nr
);
406 if (string_has_cc(fn
, NULL
))
407 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Filename contains control characters at manifest line %zu, refusing.", line_nr
);
409 r
= pattern_match_many(rr
->patterns
, fn
, &extracted_fields
);
411 return log_error_errno(r
, "Failed to match pattern: %m");
413 _cleanup_free_
char *path
= NULL
;
415 r
= import_url_append_component(rr
->path
, fn
, &path
);
417 return log_error_errno(r
, "Failed to build instance URL: %m");
419 r
= resource_add_instance(rr
, path
, &extracted_fields
, &instance
);
423 assert(hlen
== sizeof(instance
->metadata
.sha256sum
));
425 if (instance
->metadata
.sha256sum_set
) {
426 if (memcmp(instance
->metadata
.sha256sum
, h
, hlen
) != 0)
427 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "SHA256 sum parsed from filename and manifest don't match at line %zu, refusing.", line_nr
);
429 memcpy(instance
->metadata
.sha256sum
, h
, hlen
);
430 instance
->metadata
.sha256sum_set
= true;
440 if (!ci
&& web_cache
) {
441 r
= web_cache_add_item(web_cache
, rr
->path
, verify
, manifest
, manifest_size
);
443 log_debug_errno(r
, "Failed to add manifest '%s' to cache, ignoring: %m", rr
->path
);
445 log_debug("Added manifest '%s' to cache.", rr
->path
);
451 static int instance_cmp(Instance
*const*a
, Instance
*const*b
) {
458 assert((*a
)->metadata
.version
);
459 assert((*b
)->metadata
.version
);
461 /* Newest version at the beginning */
462 r
= strverscmp_improved((*a
)->metadata
.version
, (*b
)->metadata
.version
);
466 /* Instances don't have to be uniquely named (uniqueness on partition tables is not enforced at all,
467 * and since we allow multiple matching patterns not even in directories they are unique). Hence
468 * let's order by path as secondary ordering key. */
469 return path_compare((*a
)->path
, (*b
)->path
);
472 int resource_load_instances(Resource
*rr
, bool verify
, Hashmap
**web_cache
) {
480 case RESOURCE_REGULAR_FILE
:
481 r
= resource_load_from_directory(rr
, S_IFREG
);
484 case RESOURCE_DIRECTORY
:
485 case RESOURCE_SUBVOLUME
:
486 r
= resource_load_from_directory(rr
, S_IFDIR
);
489 case RESOURCE_PARTITION
:
490 r
= resource_load_from_blockdev(rr
);
493 case RESOURCE_URL_FILE
:
494 case RESOURCE_URL_TAR
:
495 r
= resource_load_from_web(rr
, verify
, web_cache
);
499 assert_not_reached();
504 typesafe_qsort(rr
->instances
, rr
->n_instances
, instance_cmp
);
508 Instance
* resource_find_instance(Resource
*rr
, const char *version
) {
510 .metadata
.version
= (char*) version
,
513 return typesafe_bsearch(&k
, rr
->instances
, rr
->n_instances
, instance_cmp
);
516 int resource_resolve_path(
521 _cleanup_free_
char *p
= NULL
;
529 /* NB: we don't actually check the backing device of the root fs "/", but of "/usr", in order
530 * to support environments where the root fs is a tmpfs, and the OS itself placed exclusively
533 if (rr
->type
!= RESOURCE_PARTITION
)
534 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
535 "Automatic root path discovery only supported for partition resources.");
537 if (node
) { /* If --image= is specified, directly use the loopback device */
538 r
= free_and_strdup_warn(&rr
->path
, node
);
546 return log_error_errno(SYNTHETIC_ERRNO(EPERM
),
547 "Block device is not allowed when using --root= mode.");
549 r
= get_block_device_harder("/usr/", &d
);
551 } else if (rr
->type
== RESOURCE_PARTITION
) {
552 _cleanup_close_
int fd
= -1, real_fd
= -1;
553 _cleanup_free_
char *resolved
= NULL
;
556 r
= chase_symlinks(rr
->path
, root
, CHASE_PREFIX_ROOT
, &resolved
, &fd
);
558 return log_error_errno(r
, "Failed to resolve '%s': %m", rr
->path
);
560 if (fstat(fd
, &st
) < 0)
561 return log_error_errno(errno
, "Failed to stat '%s': %m", resolved
);
563 if (S_ISBLK(st
.st_mode
) && root
)
564 return log_error_errno(SYNTHETIC_ERRNO(EPERM
), "When using --root= or --image= access to device nodes is prohibited.");
566 if (S_ISREG(st
.st_mode
) || S_ISBLK(st
.st_mode
)) {
567 /* Not a directory, hence no need to find backing block device for the path */
568 free_and_replace(rr
->path
, resolved
);
572 if (!S_ISDIR(st
.st_mode
))
573 return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR
), "Target path '%s' does not refer to regular file, directory or block device, refusing.", rr
->path
);
575 if (node
) { /* If --image= is specified all file systems are backed by the same loopback device, hence shortcut things. */
576 r
= free_and_strdup_warn(&rr
->path
, node
);
583 real_fd
= fd_reopen(fd
, O_RDONLY
|O_CLOEXEC
|O_DIRECTORY
);
585 return log_error_errno(real_fd
, "Failed to convert O_PATH file descriptor for %s to regular file descriptor: %m", rr
->path
);
587 r
= get_block_device_harder_fd(fd
, &d
);
589 } else if (RESOURCE_IS_FILESYSTEM(rr
->type
) && root
) {
590 _cleanup_free_
char *resolved
= NULL
;
592 r
= chase_symlinks(rr
->path
, root
, CHASE_PREFIX_ROOT
, &resolved
, NULL
);
594 return log_error_errno(r
, "Failed to resolve '%s': %m", rr
->path
);
596 free_and_replace(rr
->path
, resolved
);
599 return 0; /* Otherwise assume there's nothing to resolve */
602 return log_error_errno(r
, "Failed to determine block device of file system: %m");
604 r
= block_get_whole_disk(d
, &d
);
606 return log_error_errno(r
, "Failed to find whole disk device for partition backing file system: %m");
608 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
609 "File system is not placed on a partition block device, cannot determine whole block device backing root file system.");
611 r
= device_path_make_canonical(S_IFBLK
, d
, &p
);
616 log_info("Automatically discovered block device '%s' from '%s'.", p
, rr
->path
);
618 log_info("Automatically discovered root block device '%s'.", p
);
620 free_and_replace(rr
->path
, p
);
624 static const char *resource_type_table
[_RESOURCE_TYPE_MAX
] = {
625 [RESOURCE_URL_FILE
] = "url-file",
626 [RESOURCE_URL_TAR
] = "url-tar",
627 [RESOURCE_TAR
] = "tar",
628 [RESOURCE_PARTITION
] = "partition",
629 [RESOURCE_REGULAR_FILE
] = "regular-file",
630 [RESOURCE_DIRECTORY
] = "directory",
631 [RESOURCE_SUBVOLUME
] = "subvolume",
634 DEFINE_STRING_TABLE_LOOKUP(resource_type
, ResourceType
);