From: Adrian Vovk Date: Fri, 26 May 2023 04:47:47 +0000 (-0400) Subject: sysupdate.d: Add way to drop binaries into $BOOT X-Git-Tag: v254-rc1~295 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0470f919831b3cc66bbf6239f97bf5b998504ab0;p=thirdparty%2Fsystemd.git sysupdate.d: Add way to drop binaries into $BOOT As described in the BLS, we should place binaries into the XBOOTLDR directory if it is available, otherwise into the ESP. Thus, we might need to put binaries into /boot or into /efi depending on the existence of the XBOOTLDR partition. With this change, we introduce a new PathRelativeTo= config option that makes this functionality possible --- diff --git a/man/sysupdate.d.xml b/man/sysupdate.d.xml index c4cdd7971b8..260c260f988 100644 --- a/man/sysupdate.d.xml +++ b/man/sysupdate.d.xml @@ -81,8 +81,8 @@ Finally, a file https://download.example.com/foobarOS_47.efi.xz (a unified kernel, as per Boot Loader - Specification Type #2) should be downloaded, decompressed and written to the ESP file system, - i.e. to EFI/Linux/foobarOS_47.efi in the ESP. + Specification Type #2) should be downloaded, decompressed and written to the $BOOT file system, + i.e. to EFI/Linux/foobarOS_47.efi in the ESP or XBOOTLDR partition. The version-independent generalization of this would be (using the special marker @@ -98,7 +98,7 @@ set to foobarOS_@v_verity. A transfer of a file https://download.example.com/foobarOS_@v.efi.xz - → a local file /efi/EFI/Linux/foobarOS_@v.efi. + → a local file $BOOT/EFI/Linux/foobarOS_@v.efi. An update can only complete if the relevant URLs provide their resources for the same version, @@ -569,6 +569,23 @@ systemd-repart8. + + PathRelativeTo= + + Specifies what partition Path= should be relative to. Takes one of + root, esp, xbootldr, or boot. + If unspecified, defaults to root. + + If set to boot, the specified Path= will be resolved + relative to the mount point of the $BOOT partition (i.e. the ESP or XBOOTLDR), as defined by the + Boot Loader + Specification. + + The values esp, xbootldr, and + boot are only supported when Type= is set to + regular-file or directory. + + MatchPattern= @@ -820,7 +837,8 @@ MatchPattern=foobarOS_@v.efi.xz [Target] Type=regular-file -Path=/efi/EFI/Linux +Path=/EFI/Linux +PathRelativeTo=boot MatchPattern=foobarOS_@v+@l-@d.efi \ foobarOS_@v+@l.efi \ foobarOS_@v.efi @@ -829,12 +847,12 @@ TriesLeft=3 TriesDone=0 InstancesMax=2 - The above installs a unified kernel image into the ESP (which is mounted to - /efi/), as per Boot - Loader Specification Type #2. This defines three possible patterns for the names of the - kernel images, as per Automatic Boot - Assessment, and ensures when installing new kernels, they are set up with 3 tries left. No - more than two parallel kernels are kept. + The above installs a unified kernel image into the $BOOT partition, as per + Boot Loader + Specification Type #2. This defines three possible patterns for the names of the kernel + images, as per Automatic Boot Assessment, + and ensures when installing new kernels, they are set up with 3 tries left. No more than two parallel + kernels are kept. With this setup the web server would serve the following files, for a hypothetical version 7 of the OS: diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c index 201b37528a8..30973a360df 100644 --- a/src/sysupdate/sysupdate-resource.c +++ b/src/sysupdate/sysupdate-resource.c @@ -13,6 +13,7 @@ #include "env-util.h" #include "fd-util.h" #include "fileio.h" +#include "find-esp.h" #include "glyph-util.h" #include "gpt.h" #include "hexdecoct.h" @@ -520,6 +521,12 @@ int resource_resolve_path( assert(rr); + if (IN_SET(rr->path_relative_to, PATH_RELATIVE_TO_ESP, PATH_RELATIVE_TO_XBOOTLDR, PATH_RELATIVE_TO_BOOT) && + !IN_SET(rr->type, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Paths relative to %s are only allowed for regular-file or directory resources.", + path_relative_to_to_string(rr->path_relative_to)); + if (rr->path_auto) { struct stat orig_root_stats; @@ -595,12 +602,31 @@ int resource_resolve_path( r = get_block_device_harder_fd(fd, &d); - } else if (RESOURCE_IS_FILESYSTEM(rr->type) && root) { - _cleanup_free_ char *resolved = NULL; + } else if (RESOURCE_IS_FILESYSTEM(rr->type)) { + _cleanup_free_ char *resolved = NULL, *relative_to = NULL; + ChaseFlags chase_flags = CHASE_PREFIX_ROOT; + + if (rr->path_relative_to == PATH_RELATIVE_TO_ROOT) { + relative_to = strdup(empty_to_root(root)); + if (!relative_to) + return log_oom(); + } else { /* boot, esp, or xbootldr */ + r = 0; + if (IN_SET(rr->path_relative_to, PATH_RELATIVE_TO_BOOT, PATH_RELATIVE_TO_XBOOTLDR)) + r = find_xbootldr_and_warn(root, NULL, /* unprivileged_mode= */ -1, &relative_to, NULL, NULL); + if (r == -ENOKEY || rr->path_relative_to == PATH_RELATIVE_TO_ESP) + r = find_esp_and_warn(root, NULL, -1, &relative_to, NULL, NULL, NULL, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to resolve $BOOT: %m"); + log_debug("Resolved $BOOT to '%s'", relative_to); + + /* Since this partition is read from EFI, there should be no symlinks */ + chase_flags |= CHASE_PROHIBIT_SYMLINKS; + } - r = chase(rr->path, root, CHASE_PREFIX_ROOT, &resolved, NULL); + r = chase(rr->path, relative_to, chase_flags, &resolved, NULL); if (r < 0) - return log_error_errno(r, "Failed to resolve '%s': %m", rr->path); + return log_error_errno(r, "Failed to resolve '%s' (relative to '%s'): %m", rr->path, relative_to); free_and_replace(rr->path, resolved); return 0; @@ -641,3 +667,12 @@ static const char *resource_type_table[_RESOURCE_TYPE_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP(resource_type, ResourceType); + +static const char *path_relative_to_table[_PATH_RELATIVE_TO_MAX] = { + [PATH_RELATIVE_TO_ROOT] = "root", + [PATH_RELATIVE_TO_ESP] = "esp", + [PATH_RELATIVE_TO_XBOOTLDR] = "xbootldr", + [PATH_RELATIVE_TO_BOOT] = "boot", +}; + +DEFINE_STRING_TABLE_LOOKUP(path_relative_to, PathRelativeTo); diff --git a/src/sysupdate/sysupdate-resource.h b/src/sysupdate/sysupdate-resource.h index 3209988c24f..8bf19a45ec3 100644 --- a/src/sysupdate/sysupdate-resource.h +++ b/src/sysupdate/sysupdate-resource.h @@ -66,12 +66,24 @@ static inline bool RESOURCE_IS_URL(ResourceType t) { RESOURCE_URL_FILE); } +typedef enum PathRelativeTo { + /* Please make sure to folow the naming of the corresponding PartitionDesignator enum values, + * where this makes sense, like for the following three. */ + PATH_RELATIVE_TO_ROOT, + PATH_RELATIVE_TO_ESP, + PATH_RELATIVE_TO_XBOOTLDR, + PATH_RELATIVE_TO_BOOT, /* Refers to $BOOT from the BLS. No direct counterpart in PartitionDesignator */ + _PATH_RELATIVE_TO_MAX, + _PATH_RELATIVE_TO_INVALID = -EINVAL, +} PathRelativeTo; + struct Resource { ResourceType type; /* Where to look for instances, and what to match precisely */ char *path; bool path_auto; /* automatically find root path (only available if target resource, not source resource) */ + PathRelativeTo path_relative_to; char **patterns; GptPartitionType partition_type; bool partition_type_set; @@ -94,3 +106,6 @@ int resource_resolve_path(Resource *rr, const char *root, const char *node); ResourceType resource_type_from_string(const char *s) _pure_; const char *resource_type_to_string(ResourceType t) _const_; + +PathRelativeTo path_relative_to_from_string(const char *s) _pure_; +const char *path_relative_to_to_string(PathRelativeTo r) _const_; diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c index f7009315a2e..bbc3a5bcaa0 100644 --- a/src/sysupdate/sysupdate-transfer.c +++ b/src/sysupdate/sysupdate-transfer.c @@ -297,7 +297,6 @@ static int config_parse_resource_path( const char *rvalue, void *data, void *userdata) { - _cleanup_free_ char *resolved = NULL; Resource *rr = ASSERT_PTR(data); int r; @@ -327,6 +326,9 @@ static int config_parse_resource_path( static DEFINE_CONFIG_PARSE_ENUM(config_parse_resource_type, resource_type, ResourceType, "Invalid resource type"); +static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_resource_path_relto, path_relative_to, PathRelativeTo, + PATH_RELATIVE_TO_ROOT, "Invalid PathRelativeTo= value"); + static int config_parse_resource_ptype( const char *unit, const char *filename, @@ -418,27 +420,29 @@ int transfer_read_definition(Transfer *t, const char *path) { assert(path); ConfigTableItem table[] = { - { "Transfer", "MinVersion", config_parse_min_version, 0, &t->min_version }, - { "Transfer", "ProtectVersion", config_parse_protect_version, 0, &t->protected_versions }, - { "Transfer", "Verify", config_parse_bool, 0, &t->verify }, - { "Source", "Type", config_parse_resource_type, 0, &t->source.type }, - { "Source", "Path", config_parse_resource_path, 0, &t->source }, - { "Source", "MatchPattern", config_parse_resource_pattern, 0, &t->source.patterns }, - { "Target", "Type", config_parse_resource_type, 0, &t->target.type }, - { "Target", "Path", config_parse_resource_path, 0, &t->target }, - { "Target", "MatchPattern", config_parse_resource_pattern, 0, &t->target.patterns }, - { "Target", "MatchPartitionType", config_parse_resource_ptype, 0, &t->target }, - { "Target", "PartitionUUID", config_parse_partition_uuid, 0, t }, - { "Target", "PartitionFlags", config_parse_partition_flags, 0, t }, - { "Target", "PartitionNoAuto", config_parse_tristate, 0, &t->no_auto }, - { "Target", "PartitionGrowFileSystem", config_parse_tristate, 0, &t->growfs }, - { "Target", "ReadOnly", config_parse_tristate, 0, &t->read_only }, - { "Target", "Mode", config_parse_mode, 0, &t->mode }, - { "Target", "TriesLeft", config_parse_uint64, 0, &t->tries_left }, - { "Target", "TriesDone", config_parse_uint64, 0, &t->tries_done }, - { "Target", "InstancesMax", config_parse_instances_max, 0, &t->instances_max }, - { "Target", "RemoveTemporary", config_parse_bool, 0, &t->remove_temporary }, - { "Target", "CurrentSymlink", config_parse_current_symlink, 0, &t->current_symlink }, + { "Transfer", "MinVersion", config_parse_min_version, 0, &t->min_version }, + { "Transfer", "ProtectVersion", config_parse_protect_version, 0, &t->protected_versions }, + { "Transfer", "Verify", config_parse_bool, 0, &t->verify }, + { "Source", "Type", config_parse_resource_type, 0, &t->source.type }, + { "Source", "Path", config_parse_resource_path, 0, &t->source }, + { "Source", "PathRelativeTo", config_parse_resource_path_relto, 0, &t->source.path_relative_to }, + { "Source", "MatchPattern", config_parse_resource_pattern, 0, &t->source.patterns }, + { "Target", "Type", config_parse_resource_type, 0, &t->target.type }, + { "Target", "Path", config_parse_resource_path, 0, &t->target }, + { "Target", "PathRelativeTo", config_parse_resource_path_relto, 0, &t->target.path_relative_to }, + { "Target", "MatchPattern", config_parse_resource_pattern, 0, &t->target.patterns }, + { "Target", "MatchPartitionType", config_parse_resource_ptype, 0, &t->target }, + { "Target", "PartitionUUID", config_parse_partition_uuid, 0, t }, + { "Target", "PartitionFlags", config_parse_partition_flags, 0, t }, + { "Target", "PartitionNoAuto", config_parse_tristate, 0, &t->no_auto }, + { "Target", "PartitionGrowFileSystem", config_parse_tristate, 0, &t->growfs }, + { "Target", "ReadOnly", config_parse_tristate, 0, &t->read_only }, + { "Target", "Mode", config_parse_mode, 0, &t->mode }, + { "Target", "TriesLeft", config_parse_uint64, 0, &t->tries_left }, + { "Target", "TriesDone", config_parse_uint64, 0, &t->tries_done }, + { "Target", "InstancesMax", config_parse_instances_max, 0, &t->instances_max }, + { "Target", "RemoveTemporary", config_parse_bool, 0, &t->remove_temporary }, + { "Target", "CurrentSymlink", config_parse_current_symlink, 0, &t->current_symlink }, {} }; diff --git a/test/units/testsuite-72.sh b/test/units/testsuite-72.sh index 9effc982bae..63d1988a849 100755 --- a/test/units/testsuite-72.sh +++ b/test/units/testsuite-72.sh @@ -30,6 +30,7 @@ size=2048, type=2c7357ed-ebd2-46d9-aec1-23d437ec2bf5, name=_empty EOF rm -rf /var/tmp/72-dirs +mkdir -p /var/tmp/72-dirs rm -rf /var/tmp/72-defs mkdir -p /var/tmp/72-defs @@ -74,6 +75,30 @@ MatchPattern=dir-@v InstancesMax=3 EOF +cat >/var/tmp/72-defs/04-fourth.conf <<"EOF" +[Source] +Type=regular-file +Path=/var/tmp/72-source +MatchPattern=uki-@v.efi + +[Target] +Type=regular-file +Path=/EFI/Linux +PathRelativeTo=boot +MatchPattern=uki_@v+@l-@d.efi \ + uki_@v+@l.efi \ + uki_@v.efi +Mode=0444 +TriesLeft=3 +TriesDone=0 +InstancesMax=2 +EOF + +rm -rf /var/tmp/72-esp /var/tmp/72-xbootldr +mkdir -p /var/tmp/72-esp/EFI/Linux /var/tmp/72-xbootldr/EFI/Linux +export SYSTEMD_ESP_PATH=/var/tmp/72-esp +export SYSTEMD_XBOOTLDR_PATH=/var/tmp/72-xbootldr + rm -rf /var/tmp/72-source mkdir -p /var/tmp/72-source @@ -83,13 +108,16 @@ new_version() { dd if=/dev/urandom of="/var/tmp/72-source/part2-$1.raw" bs=1024 count=1024 gzip -k -f "/var/tmp/72-source/part2-$1.raw" + # Create a random "UKI" payload + echo $RANDOM >"/var/tmp/72-source/uki-$1.efi" + + # Create tarball of a directory mkdir -p "/var/tmp/72-source/dir-$1" echo $RANDOM >"/var/tmp/72-source/dir-$1/foo.txt" echo $RANDOM >"/var/tmp/72-source/dir-$1/bar.txt" - tar --numeric-owner -C "/var/tmp/72-source/dir-$1/" -czf "/var/tmp/72-source/dir-$1.tar.gz" . - ( cd /var/tmp/72-source/ && sha256sum part* dir-*.tar.gz >SHA256SUMS ) + ( cd /var/tmp/72-source/ && sha256sum uki* part* dir-*.tar.gz >SHA256SUMS ) } update_now() { @@ -103,8 +131,16 @@ update_now() { verify_version() { # Expects: version ID + sector offset of both partitions to compare + + # Check the partitions dd if=/var/tmp/72-joined.raw bs=1024 skip="$2" count=1024 | cmp "/var/tmp/72-source/part1-$1.raw" dd if=/var/tmp/72-joined.raw bs=1024 skip="$3" count=1024 | cmp "/var/tmp/72-source/part2-$1.raw" + + # Check the UKI + cmp "/var/tmp/72-source/uki-$1.efi" "/var/tmp/72-xbootldr/EFI/Linux/uki_$1+3-0.efi" + test -z "$(ls -A /var/tmp/72-esp/EFI/Linux)" + + # Check the directories cmp "/var/tmp/72-source/dir-$1/foo.txt" /var/tmp/72-dirs/current/foo.txt cmp "/var/tmp/72-source/dir-$1/bar.txt" /var/tmp/72-dirs/current/bar.txt } @@ -123,6 +159,7 @@ verify_version v2 2048 4096 new_version v3 update_now verify_version v3 1024 3072 +test ! -f "/var/tmp/72-xbootldr/EFI/Linux/uki_v1+3-0.efi" # Create fourth version, and update through a file:// URL. This should be # almost as good as testing HTTP, but is simpler for us to set up. file:// is @@ -163,7 +200,7 @@ update_now verify_version v4 2048 4096 rm /var/tmp/72-joined.raw -rm -r /var/tmp/72-dirs /var/tmp/72-defs /var/tmp/72-source +rm -r /var/tmp/72-{dirs,defs,source,xbootldr,esp} echo OK >/testok