diff options
Diffstat (limited to 'src/partition')
-rw-r--r-- | src/partition/growfs.c | 20 | ||||
-rw-r--r-- | src/partition/meson.build | 1 | ||||
-rw-r--r-- | src/partition/repart.c | 782 |
3 files changed, 666 insertions, 137 deletions
diff --git a/src/partition/growfs.c b/src/partition/growfs.c index 62f3ee6..c1ee18a 100644 --- a/src/partition/growfs.c +++ b/src/partition/growfs.c @@ -55,8 +55,9 @@ static int resize_crypt_luks_device(dev_t devno, const char *fstype, dev_t main_ return log_error_errno(r, "Failed to open main block device " DEVNUM_FORMAT_STR ": %m", DEVNUM_FORMAT_VAL(main_devno)); - if (ioctl(main_devfd, BLKGETSIZE64, &size) != 0) - return log_error_errno(errno, "Failed to query size of \"%s\" (before resize): %m", + r = blockdev_get_device_size(main_devfd, &size); + if (r < 0) + return log_error_errno(r, "Failed to query size of \"%s\" (before resize): %m", main_devpath); log_debug("%s is %"PRIu64" bytes", main_devpath, size); @@ -83,9 +84,9 @@ static int resize_crypt_luks_device(dev_t devno, const char *fstype, dev_t main_ if (r < 0) return log_error_errno(r, "crypt_resize() of %s failed: %m", devpath); - if (ioctl(main_devfd, BLKGETSIZE64, &size) != 0) - log_warning_errno(errno, "Failed to query size of \"%s\" (after resize): %m", - devpath); + r = blockdev_get_device_size(main_devfd, &size); + if (r < 0) + log_warning_errno(r, "Failed to query size of \"%s\" (after resize): %m", devpath); else log_debug("%s is now %"PRIu64" bytes", main_devpath, size); @@ -223,11 +224,11 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - r = path_is_mount_point(arg_target, NULL, 0); + r = path_is_mount_point(arg_target); if (r < 0) return log_error_errno(r, "Failed to check if \"%s\" is a mount point: %m", arg_target); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "\"%s\" is not a mount point: %m", arg_target); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "\"%s\" is not a mount point.", arg_target); mountfd = open(arg_target, O_RDONLY|O_CLOEXEC|O_DIRECTORY); if (mountfd < 0) @@ -250,8 +251,9 @@ static int run(int argc, char *argv[]) { return log_error_errno(r, "Failed to open block device " DEVNUM_FORMAT_STR ": %m", DEVNUM_FORMAT_VAL(devno)); - if (ioctl(devfd, BLKGETSIZE64, &size) != 0) - return log_error_errno(errno, "Failed to query size of \"%s\": %m", devpath); + r = blockdev_get_device_size(devfd, &size); + if (r < 0) + return log_error_errno(r, "Failed to query size of \"%s\": %m", devpath); log_debug("Resizing \"%s\" to %"PRIu64" bytes...", arg_target, size); diff --git a/src/partition/meson.build b/src/partition/meson.build index 78cde2f..52e1368 100644 --- a/src/partition/meson.build +++ b/src/partition/meson.build @@ -33,7 +33,6 @@ executables += [ 'c_args' : '-DSTANDALONE', 'link_with' : [ libbasic, - libbasic_gcrypt, libshared_fdisk, libshared_static, libsystemd_static, diff --git a/src/partition/repart.c b/src/partition/repart.c index 4fabe1b..6f67d46 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -128,7 +128,7 @@ typedef enum FilterPartitionType { static EmptyMode arg_empty = EMPTY_UNSET; static bool arg_dry_run = true; -static const char *arg_node = NULL; +static char *arg_node = NULL; static char *arg_root = NULL; static char *arg_image = NULL; static char **arg_definitions = NULL; @@ -146,6 +146,8 @@ static bool arg_legend = true; static void *arg_key = NULL; static size_t arg_key_size = 0; static EVP_PKEY *arg_private_key = NULL; +static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; +static char *arg_private_key_source = NULL; static X509 *arg_certificate = NULL; static char *arg_tpm2_device = NULL; static uint32_t arg_tpm2_seal_key_handle = 0; @@ -168,12 +170,16 @@ static int arg_offline = -1; static char **arg_copy_from = NULL; static char *arg_copy_source = NULL; static char *arg_make_ddi = NULL; +static char *arg_generate_fstab = NULL; +static char *arg_generate_crypttab = NULL; +STATIC_DESTRUCTOR_REGISTER(arg_node, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key, EVP_PKEY_freep); +STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_certificate, X509_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device_key, freep); @@ -185,6 +191,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_copy_from, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_copy_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_make_ddi, freep); +STATIC_DESTRUCTOR_REGISTER(arg_generate_fstab, freep); +STATIC_DESTRUCTOR_REGISTER(arg_generate_crypttab, freep); typedef struct FreeArea FreeArea; @@ -214,6 +222,39 @@ typedef enum MinimizeMode { _MINIMIZE_MODE_INVALID = -EINVAL, } MinimizeMode; +typedef struct PartitionMountPoint { + char *where; + char *options; +} PartitionMountPoint; + +static void partition_mountpoint_free_many(PartitionMountPoint *f, size_t n) { + assert(f || n == 0); + + FOREACH_ARRAY(i, f, n) { + free(i->where); + free(i->options); + } + + free(f); +} + +typedef struct PartitionEncryptedVolume { + char *name; + char *keyfile; + char *options; +} PartitionEncryptedVolume; + +static PartitionEncryptedVolume* partition_encrypted_volume_free(PartitionEncryptedVolume *c) { + if (!c) + return NULL; + + free(c->name); + free(c->keyfile); + free(c->options); + + return mfree(c); +} + typedef struct Partition { char *definition_path; char **drop_in_files; @@ -259,6 +300,7 @@ typedef struct Partition { char **exclude_files_target; char **make_directories; char **subvolumes; + char *default_subvolume; EncryptMode encrypt; VerityMode verity; char *verity_match_key; @@ -276,6 +318,11 @@ typedef struct Partition { char *split_name_format; char *split_path; + PartitionMountPoint *mountpoints; + size_t n_mountpoints; + + PartitionEncryptedVolume *encrypted_volume; + struct Partition *siblings[_VERITY_MODE_MAX]; LIST_FIELDS(struct Partition, partitions); @@ -425,6 +472,12 @@ static Partition* partition_free(Partition *p) { free(p->split_name_format); unlink_and_free(p->split_path); + partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints); + p->mountpoints = NULL; + p->n_mountpoints = 0; + + partition_encrypted_volume_free(p->encrypted_volume); + return mfree(p); } @@ -460,6 +513,12 @@ static void partition_foreignize(Partition *p) { p->read_only = -1; p->growfs = -1; p->verity = VERITY_OFF; + + partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints); + p->mountpoints = NULL; + p->n_mountpoints = 0; + + p->encrypted_volume = partition_encrypted_volume_free(p->encrypted_volume); } static bool partition_type_exclude(const GptPartitionType *type) { @@ -1610,6 +1669,41 @@ static int config_parse_make_dirs( } } +static int config_parse_default_subvolume( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char **subvol = ASSERT_PTR(data); + _cleanup_free_ char *p = NULL; + int r; + + if (isempty(rvalue)) { + *subvol = mfree(*subvol); + return 0; + } + + r = specifier_printf(rvalue, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &p); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to expand specifiers in DefaultSubvolume= parameter, ignoring: %s", rvalue); + return 0; + } + + r = path_simplify_and_warn(p, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue); + if (r < 0) + return 0; + + return free_and_replace(*subvol, p); +} + static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_encrypt, encrypt_mode, EncryptMode, ENCRYPT_OFF, "Invalid encryption mode"); static int config_parse_gpt_flags( @@ -1677,41 +1771,164 @@ static int config_parse_uuid( return 0; } +static int config_parse_mountpoint( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *where = NULL, *options = NULL; + Partition *p = ASSERT_PTR(data); + int r; + + if (isempty(rvalue)) { + partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints); + return 0; + } + + const char *q = rvalue; + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_UNQUOTE, + &where, &options); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + if (r < 1) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Too few arguments in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + if (!isempty(q)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Too many arguments in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + + r = path_simplify_and_warn(where, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue); + if (r < 0) + return 0; + + if (!GREEDY_REALLOC(p->mountpoints, p->n_mountpoints + 1)) + return log_oom(); + + p->mountpoints[p->n_mountpoints++] = (PartitionMountPoint) { + .where = TAKE_PTR(where), + .options = TAKE_PTR(options), + }; + + return 0; +} + +static int config_parse_encrypted_volume( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *volume = NULL, *keyfile = NULL, *options = NULL; + Partition *p = ASSERT_PTR(data); + int r; + + if (isempty(rvalue)) { + p->encrypted_volume = mfree(p->encrypted_volume); + return 0; + } + + const char *q = rvalue; + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_UNQUOTE, + &volume, &keyfile, &options); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + if (r < 1) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Too few arguments in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + if (!isempty(q)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Too many arguments in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + + if (!filename_is_valid(volume)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Volume name %s is not valid, ignoring", volume); + return 0; + } + + partition_encrypted_volume_free(p->encrypted_volume); + + p->encrypted_volume = new(PartitionEncryptedVolume, 1); + if (!p->encrypted_volume) + return log_oom(); + + *p->encrypted_volume = (PartitionEncryptedVolume) { + .name = TAKE_PTR(volume), + .keyfile = TAKE_PTR(keyfile), + .options = TAKE_PTR(options), + }; + + return 0; +} + static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF, "Invalid verity mode"); static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_minimize, minimize_mode, MinimizeMode, MINIMIZE_OFF, "Invalid minimize mode"); static int partition_read_definition(Partition *p, const char *path, const char *const *conf_file_dirs) { ConfigTableItem table[] = { - { "Partition", "Type", config_parse_type, 0, &p->type }, - { "Partition", "Label", config_parse_label, 0, &p->new_label }, - { "Partition", "UUID", config_parse_uuid, 0, p }, - { "Partition", "Priority", config_parse_int32, 0, &p->priority }, - { "Partition", "Weight", config_parse_weight, 0, &p->weight }, - { "Partition", "PaddingWeight", config_parse_weight, 0, &p->padding_weight }, - { "Partition", "SizeMinBytes", config_parse_size4096, -1, &p->size_min }, - { "Partition", "SizeMaxBytes", config_parse_size4096, 1, &p->size_max }, - { "Partition", "PaddingMinBytes", config_parse_size4096, -1, &p->padding_min }, - { "Partition", "PaddingMaxBytes", config_parse_size4096, 1, &p->padding_max }, - { "Partition", "FactoryReset", config_parse_bool, 0, &p->factory_reset }, - { "Partition", "CopyBlocks", config_parse_copy_blocks, 0, p }, - { "Partition", "Format", config_parse_fstype, 0, &p->format }, - { "Partition", "CopyFiles", config_parse_copy_files, 0, &p->copy_files }, - { "Partition", "ExcludeFiles", config_parse_exclude_files, 0, &p->exclude_files_source }, - { "Partition", "ExcludeFilesTarget", config_parse_exclude_files, 0, &p->exclude_files_target }, - { "Partition", "MakeDirectories", config_parse_make_dirs, 0, &p->make_directories }, - { "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt }, - { "Partition", "Verity", config_parse_verity, 0, &p->verity }, - { "Partition", "VerityMatchKey", config_parse_string, 0, &p->verity_match_key }, - { "Partition", "Flags", config_parse_gpt_flags, 0, &p->gpt_flags }, - { "Partition", "ReadOnly", config_parse_tristate, 0, &p->read_only }, - { "Partition", "NoAuto", config_parse_tristate, 0, &p->no_auto }, - { "Partition", "GrowFileSystem", config_parse_tristate, 0, &p->growfs }, - { "Partition", "SplitName", config_parse_string, 0, &p->split_name_format }, - { "Partition", "Minimize", config_parse_minimize, 0, &p->minimize }, - { "Partition", "Subvolumes", config_parse_make_dirs, 0, &p->subvolumes }, - { "Partition", "VerityDataBlockSizeBytes", config_parse_block_size, 0, &p->verity_data_block_size }, - { "Partition", "VerityHashBlockSizeBytes", config_parse_block_size, 0, &p->verity_hash_block_size }, + { "Partition", "Type", config_parse_type, 0, &p->type }, + { "Partition", "Label", config_parse_label, 0, &p->new_label }, + { "Partition", "UUID", config_parse_uuid, 0, p }, + { "Partition", "Priority", config_parse_int32, 0, &p->priority }, + { "Partition", "Weight", config_parse_weight, 0, &p->weight }, + { "Partition", "PaddingWeight", config_parse_weight, 0, &p->padding_weight }, + { "Partition", "SizeMinBytes", config_parse_size4096, -1, &p->size_min }, + { "Partition", "SizeMaxBytes", config_parse_size4096, 1, &p->size_max }, + { "Partition", "PaddingMinBytes", config_parse_size4096, -1, &p->padding_min }, + { "Partition", "PaddingMaxBytes", config_parse_size4096, 1, &p->padding_max }, + { "Partition", "FactoryReset", config_parse_bool, 0, &p->factory_reset }, + { "Partition", "CopyBlocks", config_parse_copy_blocks, 0, p }, + { "Partition", "Format", config_parse_fstype, 0, &p->format }, + { "Partition", "CopyFiles", config_parse_copy_files, 0, &p->copy_files }, + { "Partition", "ExcludeFiles", config_parse_exclude_files, 0, &p->exclude_files_source }, + { "Partition", "ExcludeFilesTarget", config_parse_exclude_files, 0, &p->exclude_files_target }, + { "Partition", "MakeDirectories", config_parse_make_dirs, 0, &p->make_directories }, + { "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt }, + { "Partition", "Verity", config_parse_verity, 0, &p->verity }, + { "Partition", "VerityMatchKey", config_parse_string, 0, &p->verity_match_key }, + { "Partition", "Flags", config_parse_gpt_flags, 0, &p->gpt_flags }, + { "Partition", "ReadOnly", config_parse_tristate, 0, &p->read_only }, + { "Partition", "NoAuto", config_parse_tristate, 0, &p->no_auto }, + { "Partition", "GrowFileSystem", config_parse_tristate, 0, &p->growfs }, + { "Partition", "SplitName", config_parse_string, 0, &p->split_name_format }, + { "Partition", "Minimize", config_parse_minimize, 0, &p->minimize }, + { "Partition", "Subvolumes", config_parse_make_dirs, 0, &p->subvolumes }, + { "Partition", "DefaultSubvolume", config_parse_default_subvolume, 0, &p->default_subvolume }, + { "Partition", "VerityDataBlockSizeBytes", config_parse_block_size, 0, &p->verity_data_block_size }, + { "Partition", "VerityHashBlockSizeBytes", config_parse_block_size, 0, &p->verity_hash_block_size }, + { "Partition", "MountPoint", config_parse_mountpoint, 0, p }, + { "Partition", "EncryptedVolume", config_parse_encrypted_volume, 0, p }, {} }; int r; @@ -1780,20 +1997,20 @@ static int partition_read_definition(Partition *p, const char *path, const char if (p->minimize != MINIMIZE_OFF && !p->format && p->verity != VERITY_HASH) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Minimize= can only be enabled if Format= or Verity=hash are set"); + "Minimize= can only be enabled if Format= or Verity=hash are set."); if (p->minimize == MINIMIZE_BEST && (p->format && !fstype_is_ro(p->format)) && p->verity != VERITY_HASH) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Minimize=best can only be used with read-only filesystems or Verity=hash"); + "Minimize=best can only be used with read-only filesystems or Verity=hash."); if ((!strv_isempty(p->copy_files) || !strv_isempty(p->make_directories)) && !mkfs_supports_root_option(p->format) && geteuid() != 0) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EPERM), - "Need to be root to populate %s filesystems with CopyFiles=/MakeDirectories=", + "Need to be root to populate %s filesystems with CopyFiles=/MakeDirectories=.", p->format); if (p->format && fstype_is_ro(p->format) && strv_isempty(p->copy_files) && strv_isempty(p->make_directories)) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Cannot format %s filesystem without source files, refusing", p->format); + "Cannot format %s filesystem without source files, refusing.", p->format); if (p->verity != VERITY_OFF || p->encrypt != ENCRYPT_OFF) { r = dlopen_cryptsetup(); @@ -1804,39 +2021,47 @@ static int partition_read_definition(Partition *p, const char *path, const char if (p->verity != VERITY_OFF && !p->verity_match_key) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "VerityMatchKey= must be set if Verity=%s", verity_mode_to_string(p->verity)); + "VerityMatchKey= must be set if Verity=%s.", verity_mode_to_string(p->verity)); if (p->verity == VERITY_OFF && p->verity_match_key) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "VerityMatchKey= can only be set if Verity= is not \"%s\"", + "VerityMatchKey= can only be set if Verity= is not \"%s\".", verity_mode_to_string(p->verity)); if (IN_SET(p->verity, VERITY_HASH, VERITY_SIG) && (p->copy_files || p->copy_blocks_path || p->copy_blocks_auto || p->format || p->make_directories)) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "CopyBlocks=/CopyFiles=/Format=/MakeDirectories= cannot be used with Verity=%s", + "CopyBlocks=/CopyFiles=/Format=/MakeDirectories= cannot be used with Verity=%s.", verity_mode_to_string(p->verity)); if (p->verity != VERITY_OFF && p->encrypt != ENCRYPT_OFF) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Encrypting verity hash/data partitions is not supported"); + "Encrypting verity hash/data partitions is not supported."); if (p->verity == VERITY_SIG && !arg_private_key) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Verity signature partition requested but no private key provided (--private-key=)"); + "Verity signature partition requested but no private key provided (--private-key=)."); if (p->verity == VERITY_SIG && !arg_certificate) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Verity signature partition requested but no PEM certificate provided (--certificate=)"); + "Verity signature partition requested but no PEM certificate provided (--certificate=)."); if (p->verity == VERITY_SIG && (p->size_min != UINT64_MAX || p->size_max != UINT64_MAX)) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "SizeMinBytes=/SizeMaxBytes= cannot be used with Verity=%s", + "SizeMinBytes=/SizeMaxBytes= cannot be used with Verity=%s.", verity_mode_to_string(p->verity)); if (!strv_isempty(p->subvolumes) && arg_offline > 0) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EOPNOTSUPP), - "Subvolumes= cannot be used with --offline=yes"); + "Subvolumes= cannot be used with --offline=yes."); + + if (p->default_subvolume && arg_offline > 0) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EOPNOTSUPP), + "DefaultSubvolume= cannot be used with --offline=yes."); + + if (p->default_subvolume && !path_strv_contains(p->subvolumes, p->default_subvolume)) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "DefaultSubvolume= must be one of the paths in Subvolumes=."); /* Verity partitions are read only, let's imply the RO flag hence, unless explicitly configured otherwise. */ if ((IN_SET(p->type.designator, @@ -1940,7 +2165,7 @@ static int determine_current_padding( assert(ret); if (!fdisk_partition_has_end(p)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition has no end!"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition has no end."); offset = fdisk_partition_get_end(p); assert(offset < UINT64_MAX); @@ -1955,7 +2180,7 @@ static int determine_current_padding( q = fdisk_table_get_partition(t, i); if (!q) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); if (fdisk_partition_is_used(q) <= 0) continue; @@ -2041,7 +2266,7 @@ static int context_copy_from_one(Context *context, const char *src) { p = fdisk_table_get_partition(t, i); if (!p) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); if (fdisk_partition_is_used(p) <= 0) continue; @@ -2191,21 +2416,21 @@ static int context_read_definitions(Context *context) { if (r == -ENXIO) { if (mode != VERITY_SIG) return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), - "Missing verity %s partition for verity %s partition with VerityMatchKey=%s", + "Missing verity %s partition for verity %s partition with VerityMatchKey=%s.", verity_mode_to_string(mode), verity_mode_to_string(p->verity), p->verity_match_key); } else if (r == -ENOTUNIQ) return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), - "Multiple verity %s partitions found for verity %s partition with VerityMatchKey=%s", + "Multiple verity %s partitions found for verity %s partition with VerityMatchKey=%s.", verity_mode_to_string(mode), verity_mode_to_string(p->verity), p->verity_match_key); else if (r < 0) return log_syntax(NULL, LOG_ERR, p->definition_path, 1, r, - "Failed to find verity %s partition for verity %s partition with VerityMatchKey=%s", + "Failed to find verity %s partition for verity %s partition with VerityMatchKey=%s.", verity_mode_to_string(mode), verity_mode_to_string(p->verity), p->verity_match_key); if (q) { if (q->priority != p->priority) return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), - "Priority mismatch (%i != %i) for verity sibling partitions with VerityMatchKey=%s", + "Priority mismatch (%i != %i) for verity sibling partitions with VerityMatchKey=%s.", p->priority, q->priority, p->verity_match_key); p->siblings[mode] = q; @@ -2226,8 +2451,7 @@ static int context_read_definitions(Context *context) { if (dp->minimize == MINIMIZE_OFF && !(dp->copy_blocks_path || dp->copy_blocks_auto)) return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), - "Minimize= set for verity hash partition but data partition does " - "not set CopyBlocks= or Minimize="); + "Minimize= set for verity hash partition but data partition does not set CopyBlocks= or Minimize=."); } @@ -2336,7 +2560,7 @@ static int context_load_partition_table(Context *context) { return log_error_errno(errno, "Failed to stat %s: %m", context->node); if (IN_SET(arg_empty, EMPTY_REQUIRE, EMPTY_FORCE, EMPTY_CREATE) && S_ISREG(st.st_mode)) - /* Don't probe sector size from partition table if we are supposed to strat from an empty disk */ + /* Don't probe sector size from partition table if we are supposed to start from an empty disk */ fs_secsz = ssz = 512; else { /* Auto-detect sector size if not specified. */ @@ -2506,7 +2730,7 @@ static int context_load_partition_table(Context *context) { p = fdisk_table_get_partition(t, i); if (!p) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); if (fdisk_partition_is_used(p) <= 0) continue; @@ -3149,8 +3373,8 @@ static int context_dump(Context *context, bool late) { if (r < 0) return r; - /* Make sure we only write the partition bar once, even if we're writing the partition table twice to - * communicate roothashes. */ + /* Only write the partition bar once, even if we're writing the partition table twice to communicate + * roothashes. */ if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) && !late) { putc('\n', stdout); @@ -3556,6 +3780,11 @@ static int prepare_temporary_file(PartitionTarget *t, uint64_t size) { return 0; } +static bool loop_device_error_is_fatal(const Partition *p, int r) { + assert(p); + return arg_offline == 0 || (r != -ENOENT && !ERRNO_IS_PRIVILEGE(r)) || !strv_isempty(p->subvolumes) || p->default_subvolume; +} + static int partition_target_prepare( Context *context, Partition *p, @@ -3595,7 +3824,7 @@ static int partition_target_prepare( if (arg_offline <= 0) { r = loop_device_make(whole_fd, O_RDWR, p->offset, size, context->sector_size, 0, LOCK_EX, &d); - if (r < 0 && (arg_offline == 0 || (r != -ENOENT && !ERRNO_IS_PRIVILEGE(r)) || !strv_isempty(p->subvolumes))) + if (r < 0 && loop_device_error_is_fatal(p, r)) return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno); if (r >= 0) { t->loop = TAKE_PTR(d); @@ -3669,7 +3898,7 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget if (st.st_size > (off_t) p->new_size) return log_error_errno(SYNTHETIC_ERRNO(ENOSPC), - "Partition %" PRIu64 "'s contents (%s) don't fit in the partition (%s)", + "Partition %" PRIu64 "'s contents (%s) don't fit in the partition (%s).", p->partno, FORMAT_BYTES(st.st_size), FORMAT_BYTES(p->new_size)); r = copy_bytes(t->fd, whole_fd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_FSYNC); @@ -3700,7 +3929,9 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta .flags = CRYPT_REENCRYPT_INITIALIZE_ONLY|CRYPT_REENCRYPT_MOVE_FIRST_SEGMENT, }; _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; +#if HAVE_TPM2 _cleanup_(erase_and_freep) char *base64_encoded = NULL; +#endif _cleanup_fclose_ FILE *h = NULL; _cleanup_free_ char *hp = NULL, *vol = NULL, *dm_name = NULL; const char *passphrase = NULL; @@ -3792,17 +4023,15 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) { #if HAVE_TPM2 + _cleanup_(iovec_done) struct iovec pubkey = {}, blob = {}, srk = {}; + _cleanup_(iovec_done_erase) struct iovec secret = {}; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - _cleanup_(erase_and_freep) void *secret = NULL; - _cleanup_free_ void *pubkey = NULL; - _cleanup_free_ void *blob = NULL, *srk_buf = NULL; - size_t secret_size, blob_size, pubkey_size = 0, srk_buf_size = 0; ssize_t base64_encoded_size; int keyslot; TPM2Flags flags = 0; if (arg_tpm2_public_key_pcr_mask != 0) { - r = tpm2_load_pcr_public_key(arg_tpm2_public_key, &pubkey, &pubkey_size); + r = tpm2_load_pcr_public_key(arg_tpm2_public_key, &pubkey.iov_base, &pubkey.iov_len); if (r < 0) { if (arg_tpm2_public_key || r != -ENOENT) return log_error_errno(r, "Failed to read TPM PCR public key: %m"); @@ -3813,8 +4042,8 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta } TPM2B_PUBLIC public; - if (pubkey) { - r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public); + if (iovec_is_set(&pubkey)) { + r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public); if (r < 0) return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); } @@ -3871,7 +4100,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta r = tpm2_calculate_sealing_policy( arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, - pubkey ? &public : NULL, + iovec_is_set(&pubkey) ? &public : NULL, /* use_pin= */ false, arg_tpm2_pcrlock ? &pcrlock_policy : NULL, &policy); @@ -3883,25 +4112,25 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta arg_tpm2_seal_key_handle, &device_key_public, /* attributes= */ NULL, - /* secret= */ NULL, /* secret_size= */ 0, + /* secret= */ NULL, &policy, /* pin= */ NULL, - &secret, &secret_size, - &blob, &blob_size, - &srk_buf, &srk_buf_size); + &secret, + &blob, + &srk); else r = tpm2_seal(tpm2_context, arg_tpm2_seal_key_handle, &policy, /* pin= */ NULL, - &secret, &secret_size, - &blob, &blob_size, + &secret, + &blob, /* ret_primary_alg= */ NULL, - &srk_buf, &srk_buf_size); + &srk); if (r < 0) return log_error_errno(r, "Failed to seal to TPM2: %m"); - base64_encoded_size = base64mem(secret, secret_size, &base64_encoded); + base64_encoded_size = base64mem(secret.iov_base, secret.iov_len, &base64_encoded); if (base64_encoded_size < 0) return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m"); @@ -3923,13 +4152,14 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta keyslot, hash_pcr_mask, hash_pcr_bank, - pubkey, pubkey_size, + &pubkey, arg_tpm2_public_key_pcr_mask, /* primary_alg= */ 0, - blob, blob_size, - policy.buffer, policy.size, - NULL, 0, /* no salt because tpm2_seal has no pin */ - srk_buf, srk_buf_size, + &blob, + &IOVEC_MAKE(policy.buffer, policy.size), + /* salt= */ NULL, /* no salt because tpm2_seal has no pin */ + &srk, + &pcrlock_policy.nv_handle, flags, &v); if (r < 0) @@ -4032,7 +4262,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta return 0; #else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "libcryptsetup is not supported or is missing required symbols, cannot encrypt: %m"); + "libcryptsetup is not supported or is missing required symbols, cannot encrypt."); #endif } @@ -4060,7 +4290,7 @@ static int partition_format_verity_hash( if (PARTITION_EXISTS(p)) /* Never format existing partitions */ return 0; - /* Minimized partitions will use the copy blocks logic so let's make sure to skip those here. */ + /* Minimized partitions will use the copy blocks logic so skip those here. */ if (p->copy_blocks_fd >= 0) return 0; @@ -4153,7 +4383,7 @@ static int partition_format_verity_hash( return 0; #else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot setup verity hashes: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot setup verity hashes."); #endif } @@ -4190,12 +4420,11 @@ static int sign_verity_roothash( return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s", ERR_error_string(ERR_get_error(), NULL)); - ret_signature->iov_base = TAKE_PTR(sig); - ret_signature->iov_len = sigsz; + *ret_signature = IOVEC_MAKE(TAKE_PTR(sig), sigsz); return 0; #else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot setup verity signature: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot setup verity signature."); #endif } @@ -4250,7 +4479,7 @@ static int partition_format_verity_sig(Context *context, Partition *p) { return log_error_errno(r, "Failed to format verity signature JSON object: %m"); if (strlen(text)+1 > p->new_size) - return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Verity signature too long for partition: %m"); + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Verity signature too long for partition."); r = strgrowpad0(&text, p->new_size); if (r < 0) @@ -4547,6 +4776,30 @@ static int make_subvolumes_set( return 0; } +static usec_t epoch_or_infinity(void) { + static usec_t cache; + static bool cached = false; + uint64_t epoch; + int r; + + if (cached) + return cache; + + r = secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch); + if (r >= 0) { + if (epoch <= UINT64_MAX / USEC_PER_SEC) { /* Overflow check */ + cached = true; + return (cache = epoch * USEC_PER_SEC); + } + r = -ERANGE; + } + if (r != -ENXIO) + log_debug_errno(r, "Failed to parse $SOURCE_DATE_EPOCH, ignoring: %m"); + + cached = true; + return (cache = USEC_INFINITY); +} + static int do_copy_files(Context *context, Partition *p, const char *root) { int r; @@ -4582,6 +4835,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { _cleanup_hashmap_free_ Hashmap *denylist = NULL; _cleanup_set_free_ Set *subvolumes_by_source_inode = NULL; _cleanup_close_ int sfd = -EBADF, pfd = -EBADF, tfd = -EBADF; + usec_t ts = epoch_or_infinity(); r = make_copy_files_denylist(context, p, *source, *target, &denylist); if (r < 0) @@ -4620,7 +4874,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { if (r < 0) return log_error_errno(r, "Failed to extract directory from '%s': %m", *target); - r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, p->subvolumes); + r = mkdir_p_root_full(root, dn, UID_INVALID, GID_INVALID, 0755, ts, p->subvolumes); if (r < 0) return log_error_errno(r, "Failed to create parent directory '%s': %m", dn); @@ -4660,7 +4914,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { if (r < 0) return log_error_errno(r, "Failed to extract directory from '%s': %m", *target); - r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, p->subvolumes); + r = mkdir_p_root_full(root, dn, UID_INVALID, GID_INVALID, 0755, ts, p->subvolumes); if (r < 0) return log_error_errno(r, "Failed to create parent directory: %m"); @@ -4679,6 +4933,14 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { (void) copy_xattr(sfd, NULL, tfd, NULL, COPY_ALL_XATTRS); (void) copy_access(sfd, tfd); (void) copy_times(sfd, tfd, 0); + + if (ts != USEC_INFINITY) { + struct timespec tspec; + timespec_store(&tspec, ts); + + if (futimens(pfd, (const struct timespec[2]) { TIMESPEC_OMIT, tspec }) < 0) + return -errno; + } } } @@ -4692,7 +4954,7 @@ static int do_make_directories(Partition *p, const char *root) { assert(root); STRV_FOREACH(d, p->make_directories) { - r = mkdir_p_root(root, *d, UID_INVALID, GID_INVALID, 0755, p->subvolumes); + r = mkdir_p_root_full(root, *d, UID_INVALID, GID_INVALID, 0755, epoch_or_infinity(), p->subvolumes); if (r < 0) return log_error_errno(r, "Failed to create directory '%s' in file system: %m", *d); } @@ -4700,6 +4962,27 @@ static int do_make_directories(Partition *p, const char *root) { return 0; } +static int set_default_subvolume(Partition *p, const char *root) { + _cleanup_free_ char *path = NULL; + int r; + + assert(p); + assert(root); + + if (!p->default_subvolume) + return 0; + + path = path_join(root, p->default_subvolume); + if (!path) + return log_oom(); + + r = btrfs_subvol_make_default(path); + if (r < 0) + return log_error_errno(r, "Failed to make '%s' the default subvolume: %m", p->default_subvolume); + + return 0; +} + static bool partition_needs_populate(Partition *p) { assert(p); return !strv_isempty(p->copy_files) || !strv_isempty(p->make_directories); @@ -4774,6 +5057,9 @@ static int partition_populate_filesystem(Context *context, Partition *p, const c if (do_make_directories(p, fs) < 0) _exit(EXIT_FAILURE); + if (set_default_subvolume(p, fs) < 0) + _exit(EXIT_FAILURE); + r = syncfs_path(AT_FDCWD, fs); if (r < 0) { log_error_errno(r, "Failed to synchronize written files: %m"); @@ -4808,7 +5094,7 @@ static int context_mkfs(Context *context) { if (!p->format) continue; - /* Minimized partitions will use the copy blocks logic so let's make sure to skip those here. */ + /* Minimized partitions will use the copy blocks logic so skip those here. */ if (p->copy_blocks_fd >= 0) continue; @@ -4819,7 +5105,7 @@ static int context_mkfs(Context *context) { assert(p->new_size != UINT64_MAX); assert(p->new_size >= (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0)); - /* If we're doing encryption, we make sure we keep free space at the end which is required + /* If we're doing encryption, keep free space at the end which is required * for cryptsetup's offline encryption. */ r = partition_target_prepare(context, p, p->new_size - (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0), @@ -4868,8 +5154,8 @@ static int context_mkfs(Context *context) { return r; /* The mkfs binary we invoked might have removed our temporary file when we're not operating - * on a loop device, so let's make sure we open the file again to make sure our file - * descriptor points to any potential new file. */ + * on a loop device, so open the file again to make sure our file descriptor points to actual + * new file. */ if (t->fd >= 0 && t->path && !t->loop) { safe_close(t->fd); @@ -5469,7 +5755,7 @@ static int split_name_resolve(Context *context) { continue; return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), - "%s and %s have the same resolved split name \"%s\", refusing", + "%s and %s have the same resolved split name \"%s\", refusing.", p->definition_path, q->definition_path, p->split_path); } } @@ -5829,7 +6115,7 @@ static int find_backing_devno( if (r < 0) return r; - r = path_is_mount_point(resolved, NULL, 0); + r = path_is_mount_point(resolved); if (r < 0) return r; if (r == 0) /* Not a mount point, then it's not a partition of its own, let's not automatically use it. */ @@ -6060,10 +6346,11 @@ static int context_open_copy_block_paths( if (S_ISREG(st.st_mode)) size = st.st_size; else if (S_ISBLK(st.st_mode)) { - if (ioctl(source_fd, BLKGETSIZE64, &size) != 0) - return log_error_errno(errno, "Failed to determine size of block device to copy from: %m"); + r = blockdev_get_device_size(source_fd, &size); + if (r < 0) + return log_error_errno(r, "Failed to determine size of block device to copy from: %m"); } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path to copy blocks from '%s' is not a regular file, block device or directory, refusing: %m", opened); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path to copy blocks from '%s' is not a regular file, block device or directory, refusing.", opened); if (size <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File to copy bytes from '%s' has zero size, refusing.", opened); @@ -6130,6 +6417,177 @@ static int fd_apparent_size(int fd, uint64_t *ret) { return 0; } +static bool need_fstab_one(const Partition *p) { + assert(p); + + if (p->dropped) + return false; + + if (!p->format) + return false; + + if (p->n_mountpoints == 0) + return false; + + return true; +} + +static bool need_fstab(const Context *context) { + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) + if (need_fstab_one(p)) + return true; + + return false; +} + +static int context_fstab(Context *context) { + _cleanup_(unlink_and_freep) char *t = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *path = NULL; + int r; + + assert(context); + + if (!arg_generate_fstab) + return false; + + if (!need_fstab(context)) { + log_notice("MountPoint= is not specified for any eligible partitions, not generating %s", + arg_generate_fstab); + return 0; + } + + path = path_join(arg_copy_source, arg_generate_fstab); + if (!path) + return log_oom(); + + r = fopen_tmpfile_linkable(path, O_WRONLY|O_CLOEXEC, &t, &f); + if (r < 0) + return log_error_errno(r, "Failed to open temporary file for %s: %m", path); + + fprintf(f, "# Automatically generated by systemd-repart\n\n"); + + LIST_FOREACH(partitions, p, context->partitions) { + _cleanup_free_ char *what = NULL, *options = NULL; + + if (!need_fstab_one(p)) + continue; + + what = strjoin("UUID=", SD_ID128_TO_UUID_STRING(p->fs_uuid)); + if (!what) + return log_oom(); + + FOREACH_ARRAY(mountpoint, p->mountpoints, p->n_mountpoints) { + r = partition_pick_mount_options( + p->type.designator, + p->format, + /* rw= */ true, + /* discard= */ !IN_SET(p->type.designator, PARTITION_ESP, PARTITION_XBOOTLDR), + &options, + NULL); + if (r < 0) + return r; + + if (!strextend_with_separator(&options, ",", mountpoint->options)) + return log_oom(); + + fprintf(f, "%s %s %s %s 0 %i\n", + what, + mountpoint->where, + p->format, + options, + p->type.designator == PARTITION_ROOT ? 1 : 2); + } + } + + r = flink_tmpfile(f, t, path, 0); + if (r < 0) + return log_error_errno(r, "Failed to link temporary file to %s: %m", path); + + log_info("%s written.", path); + + return 0; +} + +static bool need_crypttab_one(const Partition *p) { + assert(p); + + if (p->dropped) + return false; + + if (p->encrypt == ENCRYPT_OFF) + return false; + + if (!p->encrypted_volume) + return false; + + return true; +} + +static bool need_crypttab(Context *context) { + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) + if (need_crypttab_one(p)) + return true; + + return false; +} + +static int context_crypttab(Context *context) { + _cleanup_(unlink_and_freep) char *t = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *path = NULL; + int r; + + assert(context); + + if (!arg_generate_crypttab) + return false; + + if (!need_crypttab(context)) { + log_notice("EncryptedVolume= is not specified for any eligible partitions, not generating %s", + arg_generate_crypttab); + return 0; + } + + path = path_join(arg_copy_source, arg_generate_crypttab); + if (!path) + return log_oom(); + + r = fopen_tmpfile_linkable(path, O_WRONLY|O_CLOEXEC, &t, &f); + if (r < 0) + return log_error_errno(r, "Failed to open temporary file for %s: %m", path); + + fprintf(f, "# Automatically generated by systemd-repart\n\n"); + + LIST_FOREACH(partitions, p, context->partitions) { + _cleanup_free_ char *volume = NULL; + + if (!need_crypttab_one(p)) + continue; + + if (!p->encrypted_volume->name && asprintf(&volume, "luks-%s", SD_ID128_TO_UUID_STRING(p->luks_uuid)) < 0) + return log_oom(); + + fprintf(f, "%s UUID=%s %s %s\n", + p->encrypted_volume->name ?: volume, + SD_ID128_TO_UUID_STRING(p->luks_uuid), + isempty(p->encrypted_volume->keyfile) ? "-" : p->encrypted_volume->keyfile, + strempty(p->encrypted_volume->options)); + } + + r = flink_tmpfile(f, t, path, 0); + if (r < 0) + return log_error_errno(r, "Failed to link temporary file to %s: %m", path); + + log_info("%s written.", path); + + return 0; +} + static int context_minimize(Context *context) { const char *vt = NULL; int r; @@ -6197,7 +6655,7 @@ static int context_minimize(Context *context) { if (arg_offline <= 0) { r = loop_device_make(fd, O_RDWR, 0, UINT64_MAX, context->sector_size, 0, LOCK_EX, &d); - if (r < 0 && (arg_offline == 0 || (r != -ENOENT && !ERRNO_IS_PRIVILEGE(r)) || !strv_isempty(p->subvolumes))) + if (r < 0 && loop_device_error_is_fatal(p, r)) return log_error_errno(r, "Failed to make loopback device of %s: %m", temp); } @@ -6211,7 +6669,7 @@ static int context_minimize(Context *context) { if (!d || fstype_is_ro(p->format)) { if (!mkfs_supports_root_option(p->format)) return log_error_errno(SYNTHETIC_ERRNO(ENODEV), - "Loop device access is required to populate %s filesystems", + "Loop device access is required to populate %s filesystems.", p->format); r = partition_populate_directory(context, p, &root); @@ -6293,10 +6751,10 @@ static int context_minimize(Context *context) { d = loop_device_unref(d); /* Erase the previous filesystem first. */ - if (ftruncate(fd, 0)) + if (ftruncate(fd, 0) < 0) return log_error_errno(errno, "Failed to erase temporary file: %m"); - if (ftruncate(fd, fsz)) + if (ftruncate(fd, fsz) < 0) return log_error_errno(errno, "Failed to truncate temporary file to %s: %m", FORMAT_BYTES(fsz)); if (arg_offline <= 0) { @@ -6459,8 +6917,14 @@ static int help(void) { " Specify disk image dissection policy\n" " --definitions=DIR Find partition definitions in specified directory\n" " --key-file=PATH Key to use when encrypting partitions\n" - " --private-key=PATH Private key to use when generating verity roothash\n" - " signatures\n" + " --private-key=PATH|URI\n" + " Private key to use when generating verity roothash\n" + " signatures, or an engine or provider specific\n" + " designation if --private-key-source= is used\n" + " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" + " Specify how to use KEY for --private-key=. Allows\n" + " an OpenSSL engine/provider to be used when generating\n" + " verity roothash signatures\n" " --certificate=PATH PEM certificate to use when generating verity\n" " roothash signatures\n" " --tpm2-device=PATH Path to TPM2 device node to use\n" @@ -6496,6 +6960,10 @@ static int help(void) { " -S --make-ddi=sysext Make a system extension DDI\n" " -C --make-ddi=confext Make a configuration extension DDI\n" " -P --make-ddi=portable Make a portable service DDI\n" + " --generate-fstab=PATH\n" + " Write fstab configuration to the given path\n" + " --generate-crypttab=PATH\n" + " Write crypttab configuration to the given path\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -6506,6 +6974,7 @@ static int help(void) { } static int parse_argv(int argc, char *argv[]) { + _cleanup_free_ char *private_key = NULL; enum { ARG_VERSION = 0x100, @@ -6526,6 +6995,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_JSON, ARG_KEY_FILE, ARG_PRIVATE_KEY, + ARG_PRIVATE_KEY_SOURCE, ARG_CERTIFICATE, ARG_TPM2_DEVICE, ARG_TPM2_DEVICE_KEY, @@ -6544,6 +7014,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_OFFLINE, ARG_COPY_FROM, ARG_MAKE_DDI, + ARG_GENERATE_FSTAB, + ARG_GENERATE_CRYPTTAB, }; static const struct option options[] = { @@ -6566,6 +7038,7 @@ static int parse_argv(int argc, char *argv[]) { { "json", required_argument, NULL, ARG_JSON }, { "key-file", required_argument, NULL, ARG_KEY_FILE }, { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, + { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, { "certificate", required_argument, NULL, ARG_CERTIFICATE }, { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, { "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY }, @@ -6584,6 +7057,8 @@ static int parse_argv(int argc, char *argv[]) { { "copy-from", required_argument, NULL, ARG_COPY_FROM }, { "copy-source", required_argument, NULL, 's' }, { "make-ddi", required_argument, NULL, ARG_MAKE_DDI }, + { "generate-fstab", required_argument, NULL, ARG_GENERATE_FSTAB }, + { "generate-crypttab", required_argument, NULL, ARG_GENERATE_CRYPTTAB }, {} }; @@ -6751,24 +7226,20 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_PRIVATE_KEY: { - _cleanup_(erase_and_freep) char *k = NULL; - size_t n = 0; - - r = read_full_file_full( - AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX, - READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, - NULL, - &k, &n); + r = free_and_strdup_warn(&private_key, optarg); if (r < 0) - return log_error_errno(r, "Failed to read key file '%s': %m", optarg); + return r; + break; + } - EVP_PKEY_free(arg_private_key); - arg_private_key = NULL; - r = parse_private_key(k, n, &arg_private_key); + case ARG_PRIVATE_KEY_SOURCE: + r = parse_openssl_key_source_argument( + optarg, + &arg_private_key_source, + &arg_private_key_source_type); if (r < 0) return r; break; - } case ARG_CERTIFICATE: { _cleanup_free_ char *cert = NULL; @@ -6903,7 +7374,7 @@ static int parse_argv(int argc, char *argv[]) { case ARG_ARCHITECTURE: r = architecture_from_string(optarg); if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid architecture '%s'", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid architecture '%s'.", optarg); arg_architecture = r; break; @@ -6967,6 +7438,18 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_GENERATE_FSTAB: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_generate_fstab); + if (r < 0) + return r; + break; + + case ARG_GENERATE_CRYPTTAB: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_generate_crypttab); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -7037,7 +7520,7 @@ static int parse_argv(int argc, char *argv[]) { /* By default operate on /sysusr/ or /sysroot/ when invoked in the initrd. We prefer the * former, if it is mounted, so that we have deterministic behaviour on systems where /usr/ * is vendor-supplied but the root fs formatted on first boot. */ - r = path_is_mount_point("/sysusr/usr", NULL, 0); + r = path_is_mount_point("/sysusr/usr"); if (r <= 0) { if (r < 0 && r != -ENOENT) log_debug_errno(r, "Unable to determine whether /sysusr/usr is a mount point, assuming it is not: %m"); @@ -7049,7 +7532,11 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); } - arg_node = argc > optind ? argv[optind] : NULL; + if (argc > optind) { + arg_node = strdup(argv[optind]); + if (!arg_node) + return log_oom(); + } if (IN_SET(arg_empty, EMPTY_FORCE, EMPTY_REQUIRE, EMPTY_CREATE) && !arg_node && !arg_image) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -7097,6 +7584,39 @@ static int parse_argv(int argc, char *argv[]) { *p = gpt_partition_type_override_architecture(*p, arg_architecture); } + if (private_key && arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { + _cleanup_(erase_and_freep) char *k = NULL; + size_t n = 0; + + r = read_full_file_full( + AT_FDCWD, private_key, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &k, &n); + if (r < 0) + return log_error_errno(r, "Failed to read key file '%s': %m", private_key); + + r = parse_private_key(k, n, &arg_private_key); + if (r < 0) + return r; + } else if (private_key && + IN_SET(arg_private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER)) { + /* This must happen after parse_x509_certificate() is called above, otherwise + * signing later will get stuck as the parsed private key won't have the + * certificate, so this block cannot be inline in ARG_PRIVATE_KEY. */ + r = openssl_load_key_from_token( + arg_private_key_source_type, + arg_private_key_source, + private_key, + &arg_private_key); + if (r < 0) + return log_error_errno( + r, + "Failed to load key '%s' from OpenSSL private key source %s: %m", + private_key, + arg_private_key_source); + } + return 1; } @@ -7301,7 +7821,7 @@ static int find_root(Context *context) { if (r == -EUCLEAN) return btrfs_log_dev_root(LOG_ERR, r, p); if (r != -ENODEV) - return log_error_errno(r, "Failed to determine backing device of %s: %m", p); + return log_error_errno(r, "Failed to determine backing device of %s%s: %m", strempty(arg_root), p); } else return 0; } @@ -7383,8 +7903,9 @@ static int resize_backing_fd( assert(loop_device); - if (ioctl(*fd, BLKGETSIZE64, ¤t_size) < 0) - return log_error_errno(errno, "Failed to determine size of block device %s: %m", node); + r = blockdev_get_device_size(*fd, ¤t_size); + if (r < 0) + return log_error_errno(r, "Failed to determine size of block device %s: %m", node); } else { r = stat_verify_regular(&st); if (r < 0) @@ -7518,9 +8039,7 @@ static int run(int argc, char *argv[]) { bool node_is_our_loop = false; int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) @@ -7551,7 +8070,8 @@ static int run(int argc, char *argv[]) { DISSECT_IMAGE_GPT_ONLY | DISSECT_IMAGE_RELAX_VAR_CHECK | DISSECT_IMAGE_USR_NO_ROOT | - DISSECT_IMAGE_REQUIRE_ROOT, + DISSECT_IMAGE_REQUIRE_ROOT | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, /* ret_dir_fd= */ NULL, &loop_device); @@ -7596,7 +8116,7 @@ static int run(int argc, char *argv[]) { if (!d) return log_oom(); - r = search_and_access(d, F_OK, arg_root, CONF_PATHS_USR_STRV("systemd/repart/definitions"), &dp); + r = search_and_access(d, F_OK, NULL, CONF_PATHS_STRV("systemd/repart/definitions"), &dp); if (r < 0) return log_error_errno(r, "DDI type '%s' is not defined: %m", arg_make_ddi); @@ -7679,6 +8199,14 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = context_fstab(context); + if (r < 0) + return r; + + r = context_crypttab(context); + if (r < 0) + return r; + r = context_minimize(context); if (r < 0) return r; |