diff --git a/README b/README index 51a78bd2a1..30bb9ee511 100644 --- a/README +++ b/README @@ -219,7 +219,7 @@ REQUIREMENTS: libacl (optional) libbpf >= 0.1.0 (optional), >= 1.4.0 is required for using GCC as a bpf compiler - libfdisk >= 2.32 (from util-linux) (optional) + libfdisk >= 2.35 (from util-linux) (optional) libselinux >= 2.1.9 (optional) libapparmor >= 2.13 (optional) libxenctrl >= 4.9 (optional) diff --git a/man/repart.d.xml b/man/repart.d.xml index 028929ac9e..8d53805bb7 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -652,6 +652,39 @@ + + BlockDeviceReplace= + + Takes a path to a mountpoint. Its filesystem's backing device will be replaced + with the newly created partition. This works only for btrfs filesystems. This option is ignored for already existing partitions. + + If after moving the filesystem, an error happens before the writing of the partition table is + finished, there will be an attempt to move the filesystem back to its original device. But if that + attempt also fails, the filesystem might be living on a partition that does not exist in the + partition table and will be lost on reboot. This feature is intended to save initially volatile + mounted filesystems into the new disk with no important data, like a live boot system. They might + need to be recreated after a failure writing the partition table. + + This option is not compatible with . + + + + + + VolumeName= + + If an encrypted partition is created through Encrypt=, and it will + stay activated after systemd-repart is done (using + BlockDeviceReplace=), then the name of the device mapper volume created will use + the name specified by VolumeName=. + + The value must be a valid filename. If not specified, it will default to the value of + VolumeLabel= (which could be derived from Label=) if + valid. + + + + Encrypt= diff --git a/meson.build b/meson.build index d5405995db..2e78b359e6 100644 --- a/meson.build +++ b/meson.build @@ -1076,7 +1076,7 @@ conf.set10('HAVE_LIBMOUNT', have) libmount_cflags = libmount.partial_dependency(includes: true, compile_args: true) libfdisk = dependency('fdisk', - version : '>= 2.32', + version : '>= 2.35', required : get_option('fdisk')) libfdisk_cflags = libfdisk.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_LIBFDISK', libfdisk.found()) diff --git a/src/repart/repart.c b/src/repart/repart.c index 168092d43a..331b31b789 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include +#include #include #include #include @@ -250,6 +252,7 @@ typedef enum ProgressPhase { PROGRESS_LOADING_DEFINITIONS, PROGRESS_LOADING_TABLE, PROGRESS_OPENING_COPY_BLOCK_SOURCES, + PROGRESS_OPENING_BLOCK_DEVICE_REPLACE_SOURCES, PROGRESS_ACQUIRING_PARTITION_LABELS, PROGRESS_MINIMIZING, PROGRESS_PLACING, @@ -260,6 +263,7 @@ typedef enum ProgressPhase { PROGRESS_ADJUSTING_PARTITION, PROGRESS_WRITING_TABLE, PROGRESS_REREADING_TABLE, + PROGRESS_REPLACING_DEVICE, _PROGRESS_PHASE_MAX, _PROGRESS_PHASE_INVALID = -EINVAL, } ProgressPhase; @@ -423,6 +427,27 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(subvolume_hash_ops, char, path_has typedef struct Context Context; +typedef struct { + uint64_t devid; + int mountpoint_fd; + int source_fd; + char *source_path; + uint64_t source_size; + bool done; +} BtrfsReplacement; + +static BtrfsReplacement* btrfs_replacement_free(BtrfsReplacement *b) { + if (!b) + return NULL; + + safe_close(b->mountpoint_fd); + safe_close(b->source_fd); + free(b->source_path); + return mfree(b); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(BtrfsReplacement*, btrfs_replacement_free); + typedef struct Partition { Context *context; @@ -468,6 +493,9 @@ typedef struct Partition { uint64_t copy_blocks_done; char *format; + char *block_device_replace; + BtrfsReplacement *btrfs_replaced; + char *volume_name; char **exclude_files_source; char **exclude_files_target; char **make_directories; @@ -544,6 +572,7 @@ struct Context { uint64_t start, end, total; struct fdisk_context *fdisk_context; + int fdisk_context_fd; uint64_t sector_size, grain_size, default_fs_sector_size; sd_id128_t seed; @@ -567,6 +596,8 @@ struct Context { bool defer_partitions_factory_reset; sd_varlink *link; /* If 'more' is used on the Varlink call, we'll send progress info over this link */ + + bool needs_rescan; }; static const char *empty_mode_table[_EMPTY_MODE_MAX] = { @@ -627,19 +658,21 @@ static const char *minimize_mode_table[_MINIMIZE_MODE_MAX] = { }; static const char *progress_phase_table[_PROGRESS_PHASE_MAX] = { - [PROGRESS_LOADING_DEFINITIONS] = "loading-definitions", - [PROGRESS_LOADING_TABLE] = "loading-table", - [PROGRESS_OPENING_COPY_BLOCK_SOURCES] = "opening-copy-block-sources", - [PROGRESS_ACQUIRING_PARTITION_LABELS] = "acquiring-partition-labels", - [PROGRESS_MINIMIZING] = "minimizing", - [PROGRESS_PLACING] = "placing", - [PROGRESS_WIPING_DISK] = "wiping-disk", - [PROGRESS_WIPING_PARTITION] = "wiping-partition", - [PROGRESS_COPYING_PARTITION] = "copying-partition", - [PROGRESS_FORMATTING_PARTITION] = "formatting-partition", - [PROGRESS_ADJUSTING_PARTITION] = "adjusting-partition", - [PROGRESS_WRITING_TABLE] = "writing-table", - [PROGRESS_REREADING_TABLE] = "rereading-table", + [PROGRESS_LOADING_DEFINITIONS] = "loading-definitions", + [PROGRESS_LOADING_TABLE] = "loading-table", + [PROGRESS_OPENING_COPY_BLOCK_SOURCES] = "opening-copy-block-sources", + [PROGRESS_OPENING_BLOCK_DEVICE_REPLACE_SOURCES] = "opening-block-device-replace-sources", + [PROGRESS_ACQUIRING_PARTITION_LABELS] = "acquiring-partition-labels", + [PROGRESS_MINIMIZING] = "minimizing", + [PROGRESS_PLACING] = "placing", + [PROGRESS_WIPING_DISK] = "wiping-disk", + [PROGRESS_WIPING_PARTITION] = "wiping-partition", + [PROGRESS_COPYING_PARTITION] = "copying-partition", + [PROGRESS_FORMATTING_PARTITION] = "formatting-partition", + [PROGRESS_ADJUSTING_PARTITION] = "adjusting-partition", + [PROGRESS_WRITING_TABLE] = "writing-table", + [PROGRESS_REREADING_TABLE] = "rereading-table", + [PROGRESS_REPLACING_DEVICE] = "replacing-device", }; static uint64_t determine_grain_size(uint64_t sector_size) { @@ -804,6 +837,9 @@ static Partition* partition_free(Partition *p) { safe_close(p->copy_blocks_fd); free(p->format); + free(p->block_device_replace); + btrfs_replacement_free(p->btrfs_replaced); + free(p->volume_name); strv_free(p->exclude_files_source); strv_free(p->exclude_files_target); strv_free(p->make_directories); @@ -848,6 +884,9 @@ static void partition_foreignize(Partition *p) { p->copy_blocks_root = NULL; p->format = mfree(p->format); + p->block_device_replace = mfree(p->block_device_replace); + p->btrfs_replaced = btrfs_replacement_free(p->btrfs_replaced); + p->volume_name = mfree(p->volume_name); p->exclude_files_source = strv_free(p->exclude_files_source); p->exclude_files_target = strv_free(p->exclude_files_target); p->make_directories = strv_free(p->make_directories); @@ -948,6 +987,7 @@ static Context* context_new( .empty = empty, .dry_run = dry_run, .backing_fd = -EBADF, + .fdisk_context_fd = -EBADF, }; return context; @@ -977,6 +1017,7 @@ static Context* context_free(Context *context) { if (context->fdisk_context) sym_fdisk_unref_context(context->fdisk_context); + safe_close(context->fdisk_context_fd); safe_close(context->backing_fd); if (context->node_is_our_file) @@ -1156,6 +1197,8 @@ static uint64_t partition_min_size(const Context *context, const Partition *p) { if (p->copy_blocks_size != UINT64_MAX) assert_se(INC_SAFE(&d, round_up_size(p->copy_blocks_size, context->grain_size))); + else if (p->btrfs_replaced) + assert_se(INC_SAFE(&d, round_up_size(p->btrfs_replaced->source_size, context->grain_size))); else if (p->format || p->encrypt != ENCRYPT_OFF) { uint64_t f; @@ -2919,6 +2962,8 @@ static int partition_read_definition( { "Partition", "AddValidateFS", config_parse_tristate, 0, &p->add_validatefs }, { "Partition", "FileSystemSectorSize", config_parse_fs_sector_size, 0, &p->fs_sector_size }, { "Partition", "Discard", config_parse_tristate, 0, &p->discard }, + { "Partition", "BlockDeviceReplace", config_parse_path, 0, &p->block_device_replace }, + { "Partition", "VolumeName", config_parse_string, CONFIG_PARSE_STRING_SAFE, &p->volume_name }, {} }; _cleanup_free_ char *filename = NULL; @@ -2969,6 +3014,34 @@ static int partition_read_definition( return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "Format=/CopyFiles=/MakeDirectories=/MakeSymlinks= and CopyBlocks= cannot be combined, refusing."); + if (p->block_device_replace && (p->format || partition_needs_populate(p))) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "Format=/CopyFiles=/MakeDirectories=/MakeSymlinks= and BlockDeviceReplace= cannot be combined, refusing."); + + if ((p->copy_blocks_path || p->copy_blocks_auto) && p->block_device_replace) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "CopyBlocks= and BlockDeviceReplace= cannot be combined, refusing."); + + if (p->block_device_replace && arg_offline == 1) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "BlockDeviceReplace= is incompatible with --offline=yes, refusing."); + + if (p->volume_name && !filename_is_valid(p->volume_name)) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "VolumeName= has an invalid filename value, refusing."); + + if (p->volume_name && strlen(p->volume_name) > (DM_NAME_LEN - 1)) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "VolumeName= is too long, refusing."); + + if (p->volume_name && p->encrypt == ENCRYPT_OFF) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "VolumeName= requires Encrypt= to be enabled, refusing."); + + if (p->volume_name && !p->block_device_replace) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "VolumeName= requires BlockDeviceReplace= to be set, refusing."); + if (partition_needs_populate(p) && streq_ptr(p->format, "swap")) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "Format=swap and CopyFiles=/MakeDirectories=/MakeSymlinks= cannot be combined, refusing."); @@ -2978,7 +3051,7 @@ static int partition_read_definition( if (p->type.designator == PARTITION_SWAP) format = "swap"; - else if (partition_needs_populate(p) || (p->encrypt != ENCRYPT_OFF && !(p->copy_blocks_path || p->copy_blocks_auto))) + else if (partition_needs_populate(p) || (p->encrypt != ENCRYPT_OFF && !(p->copy_blocks_path || p->copy_blocks_auto || p->block_device_replace))) /* Pick "vfat" as file system for esp and xbootldr partitions, otherwise default to "ext4". */ format = IN_SET(p->type.designator, PARTITION_ESP, PARTITION_XBOOTLDR) ? "vfat" : "ext4"; @@ -3162,16 +3235,17 @@ static int find_verity_sibling(Context *context, Partition *p, VerityMode mode, return 0; } -static int context_open_and_lock_backing_fd(const char *node, int operation, int *backing_fd) { +static int context_open_and_lock_backing_fd(const char *node, int operation, int mode, int *backing_fd) { _cleanup_close_ int fd = -EBADF; assert(node); assert(backing_fd); + assert(IN_SET(mode, O_RDONLY, O_RDWR)); if (*backing_fd >= 0) return 0; - fd = open(node, O_RDONLY|O_CLOEXEC); + fd = open(node, mode|O_CLOEXEC); if (fd < 0) return log_error_errno(errno, "Failed to open device '%s': %m", node); @@ -3264,7 +3338,7 @@ static int context_copy_from_one(Context *context, const char *src) { assert(src); - r = context_open_and_lock_backing_fd(src, LOCK_SH, &fd); + r = context_open_and_lock_backing_fd(src, LOCK_SH, O_RDONLY, &fd); if (r < 0) return r; @@ -3674,7 +3748,12 @@ static int context_load_fallback_metrics(Context *context) { return 1; /* Starting from scratch */ } +static int context_open_mode(Context *context) { + return ASSERT_PTR(context)->dry_run ? O_RDONLY : O_RDWR; +} + static int context_load_partition_table(Context *context) { + _cleanup_close_ int fd = -EBADF; _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL; _cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL; uint64_t left_boundary = UINT64_MAX, first_lba, last_lba, nsectors; @@ -3688,6 +3767,7 @@ static int context_load_partition_table(Context *context) { assert(context); assert(context->node); assert(!context->fdisk_context); + assert(context->fdisk_context_fd < 0); assert(!context->free_areas); assert(context->start == UINT64_MAX); assert(context->end == UINT64_MAX); @@ -3709,6 +3789,7 @@ static int context_load_partition_table(Context *context) { r = context_open_and_lock_backing_fd( context->node, context->dry_run ? LOCK_SH : LOCK_EX, + context_open_mode(context), &context->backing_fd); if (r < 0) return r; @@ -3739,11 +3820,15 @@ static int context_load_partition_table(Context *context) { if (r < 0) return log_error_errno(r, "Failed to set sector size: %m"); - /* libfdisk doesn't have an API to operate on arbitrary fds, hence reopen the fd going via the - * /proc/self/fd/ magic path if we have an existing fd. Open the original file otherwise. */ - r = sym_fdisk_assign_device( + if (context->backing_fd < 0) { + fd = open(context->node, context_open_mode(context)|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open backing node '%s': %m", context->node); + } + r = sym_fdisk_assign_device_by_fd( c, - context->backing_fd >= 0 ? FORMAT_PROC_FD_PATH(context->backing_fd) : context->node, + context->backing_fd >= 0 ? context->backing_fd : fd, + context->node, context->dry_run); if (r == -EINVAL && arg_size_auto) { struct stat st; @@ -3752,7 +3837,7 @@ static int context_load_partition_table(Context *context) { * it if automatic sizing is requested. */ if (context->backing_fd < 0) - r = stat(context->node, &st); + r = fstat(fd, &st); else r = fstat(context->backing_fd, &st); if (r < 0) @@ -3775,6 +3860,7 @@ static int context_load_partition_table(Context *context) { /* If we have no fd referencing the device yet, make a copy of the fd now, so that we have one */ r = context_open_and_lock_backing_fd(FORMAT_PROC_FD_PATH(sym_fdisk_get_devfd(c)), context->dry_run ? LOCK_SH : LOCK_EX, + context_open_mode(context), &context->backing_fd); if (r < 0) return r; @@ -4047,6 +4133,7 @@ add_initial_free_area: context->default_fs_sector_size = fs_secsz; context->grain_size = grainsz; context->fdisk_context = TAKE_PTR(c); + context->fdisk_context_fd = TAKE_FD(fd); return from_scratch; } @@ -4103,6 +4190,7 @@ static void context_unload_partition_table(Context *context) { sym_fdisk_unref_context(context->fdisk_context); context->fdisk_context = NULL; } + context->fdisk_context_fd = safe_close(context->fdisk_context_fd); context_free_free_areas(context); } @@ -4900,6 +4988,7 @@ typedef struct DecryptedPartitionTarget { int fd; char *dm_name; char *volume; + bool keep; struct crypt_device *device; } DecryptedPartitionTarget; @@ -4912,11 +5001,14 @@ static DecryptedPartitionTarget* decrypted_partition_target_free(DecryptedPartit safe_close(t->fd); - /* udev or so might access out block device in the background while we are done. Let's hence - * force detach the volume. We sync'ed before, hence this should be safe. */ - r = sym_crypt_deactivate_by_name(t->device, t->dm_name, CRYPT_DEACTIVATE_FORCE); - if (r < 0) - log_warning_errno(r, "Failed to deactivate LUKS device, ignoring: %m"); + if (!t->keep) { + /* udev or so might access our block device in the background while we are done. Let's hence + * force detach the volume. We sync'ed before, hence this should be safe. */ + r = sym_crypt_deactivate_by_name(t->device, t->dm_name, CRYPT_DEACTIVATE_FORCE); + if (r < 0) + log_warning_errno(r, "Failed to deactivate LUKS device, ignoring: %m"); + } else + log_debug("Keeping encrypted device '%s' open.", t->dm_name); sym_crypt_free(t->device); free(t->dm_name); @@ -4926,8 +5018,30 @@ static DecryptedPartitionTarget* decrypted_partition_target_free(DecryptedPartit return NULL; } +/* BlockPartition represents partitions that have been created with BLKPG */ +typedef struct { + int fd; + int whole_fd; /* not owned */ + int nr; + uint64_t offset; + uint64_t size; + char *node; +} BlockPartition; + +static BlockPartition* block_partition_free(BlockPartition *p) { + if (!p) + return NULL; + + safe_close(p->fd); + free(p->node); + return mfree(p); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(BlockPartition*, block_partition_free); + typedef struct { LoopDevice *loop; + BlockPartition *block_partition; int fd; char *path; int whole_fd; @@ -4936,7 +5050,7 @@ typedef struct { static int partition_target_fd(PartitionTarget *t) { assert(t); - assert(t->loop || t->fd >= 0 || t->whole_fd >= 0); + assert(t->loop || t->fd >= 0 || t->whole_fd >= 0 || t->block_partition); if (t->decrypted) return t->decrypted->fd; @@ -4944,6 +5058,9 @@ static int partition_target_fd(PartitionTarget *t) { if (t->loop) return t->loop->fd; + if (t->block_partition) + return t->block_partition->fd; + if (t->fd >= 0) return t->fd; @@ -4952,7 +5069,7 @@ static int partition_target_fd(PartitionTarget *t) { static const char* partition_target_path(PartitionTarget *t) { assert(t); - assert(t->loop || t->path); + assert(t->loop || t->path || t->block_partition); if (t->decrypted) return t->decrypted->volume; @@ -4960,6 +5077,9 @@ static const char* partition_target_path(PartitionTarget *t) { if (t->loop) return t->loop->node; + if (t->block_partition) + return t->block_partition->node; + return t->path; } @@ -4971,6 +5091,7 @@ static PartitionTarget* partition_target_free(PartitionTarget *t) { loop_device_unref(t->loop); safe_close(t->fd); unlink_and_free(t->path); + block_partition_free(t->block_partition); return mfree(t); } @@ -5060,20 +5181,81 @@ static int partition_target_prepare( return 0; } - /* Loopback block devices are not only useful to turn regular files into block devices, but - * also to cut out sections of block devices into new block devices. */ - 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 && 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); + r = blockdev_partscan_enabled_fd(whole_fd); + if (r > 0) { + _cleanup_close_ int dev_fd = -EBADF; + _cleanup_free_ char *part_node = NULL; + _cleanup_(block_partition_freep) BlockPartition *b = NULL; + int nr; + + /* blkpg takes int for partition numbers */ + if (p->partno >= INT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Partition number %" PRIu64 " is too large for blkpg.", p->partno); + nr = p->partno + 1; + + part_node = sym_fdisk_partname(context->node, p->partno + 1); + if (!part_node) + return log_oom(); + + /* There is no corresponding call to block_device_remove_partition because we want to + * keep them alive if we succeed, and the rescan will remove them if possible if + * there is an error before writing the partition table. + */ + r = block_device_add_partition(whole_fd, part_node, nr, p->offset, size); + if (r < 0) + return log_error_errno(r, "Failed to create new partition '%s': %m", part_node); + + context->needs_rescan = true; + + dev_fd = open(part_node, O_RDWR|O_CLOEXEC|O_NOCTTY); + if (dev_fd < 0) { + r = -errno; + int q = block_device_remove_partition(whole_fd, part_node, nr); + if (q < 0) + log_warning_errno(q, "Error while removing block device partition '%s', ignoring: %m", part_node); + return log_error_errno(r, "Failed to open new partition '%s': %m", part_node); + } + + /* No need to flock for udev, the whole disk fd is already locked. */ + + b = new(BlockPartition, 1); + if (!b) { + r = block_device_remove_partition(whole_fd, part_node, nr); + if (r < 0) + log_warning_errno(r, "Error while removing block device partition '%s', ignoring: %m", part_node); + + return log_oom(); + } + + *b = (BlockPartition) { + .fd = TAKE_FD(dev_fd), + .whole_fd = whole_fd, + .nr = nr, + .offset = p->offset, + .size = size, + .node = TAKE_PTR(part_node), + }; + + t->block_partition = TAKE_PTR(b); *ret = TAKE_PTR(t); return 0; - } + } else { + if (!IN_SET(r, 0, -ENOTBLK)) + log_warning_errno(r, "Could not detect whether the device can be partitioned, assuming it cannot be: %m"); + /* Loopback block devices are not only useful to turn regular files into block devices, but + * also to cut out sections of block devices into new block devices. */ + r = loop_device_make(whole_fd, O_RDWR, p->offset, size, context->sector_size, /* loop_flags= */ 0, LOCK_EX, &d); + 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); + *ret = TAKE_PTR(t); + return 0; + } - log_debug_errno(r, "No access to loop devices, falling back to a regular file"); + log_debug_errno(r, "No access to loop devices, falling back to a regular file"); + } } /* If we can't allocate a loop device, let's write to a regular file that we copy into the final @@ -5100,6 +5282,11 @@ static int partition_target_grow(PartitionTarget *t, uint64_t size) { r = loop_device_refresh_size(t->loop, UINT64_MAX, size); if (r < 0) return log_error_errno(r, "Failed to refresh loopback device size: %m"); + } else if (t->block_partition) { + r = block_device_resize_partition(t->block_partition->whole_fd, t->block_partition->nr, t->block_partition->offset, size); + if (r < 0) + return log_error_errno(r, "Failed to resize partition %d: %m", t->block_partition->nr); + t->block_partition->size = size; } else if (t->fd >= 0) { if (ftruncate(t->fd, size) < 0) return log_error_errno(errno, "Failed to grow '%s' to %s by truncation: %m", @@ -5127,6 +5314,9 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget r = loop_device_sync(t->loop); if (r < 0) return log_error_errno(r, "Failed to sync loopback device: %m"); + } else if (t->block_partition) { + if (fsync(t->block_partition->fd) < 0) + return log_error_errno(errno, "Failed to sync blkpg partition: %m"); } else if (t->fd >= 0) { struct stat st; @@ -5185,7 +5375,7 @@ static size_t dmcrypt_proper_key_size(Partition *p) { } } -static int partition_encrypt(Context *context, Partition *p, PartitionTarget *target, bool offline) { +static int partition_encrypt(Context *context, Partition *p, PartitionTarget *target, bool offline, bool temporary) { #if HAVE_LIBCRYPTSETUP #if HAVE_TPM2 _cleanup_(erase_and_freep) char *base64_encoded = NULL; @@ -5247,7 +5437,13 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta if (ftruncate(fileno(h), luks_params.sector_size) < 0) return log_error_errno(errno, "Failed to grow temporary LUKS header file: %m"); } else { - if (asprintf(&dm_name, "luks-repart-%08" PRIx64, random_u64()) < 0) + if (!temporary && p->volume_name) + dm_name = strdup(p->volume_name); + else if (!temporary && filename_is_valid(vl)) + dm_name = strdup(vl); + else + dm_name = asprintf_safe("luks-repart-%08" PRIx64, random_u64()); + if (!dm_name) return log_oom(); vol = path_join("/dev/mapper/", dm_name); @@ -6105,8 +6301,8 @@ static int context_copy_blocks(Context *context) { if (r < 0) return r; - if (p->encrypt != ENCRYPT_OFF && t->loop) { - r = partition_encrypt(context, p, t, /* offline= */ false); + if (p->encrypt != ENCRYPT_OFF && (t->loop || t->block_partition)) { + r = partition_encrypt(context, p, t, /* offline= */ false, /* temporary= */ true); if (r < 0) return r; } @@ -6129,8 +6325,8 @@ static int context_copy_blocks(Context *context) { log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path); - if (p->encrypt != ENCRYPT_OFF && !t->loop) { - r = partition_encrypt(context, p, t, /* offline= */ true); + if (p->encrypt != ENCRYPT_OFF && !t->loop && !t->block_partition) { + r = partition_encrypt(context, p, t, /* offline= */ true, /* temporary= */ true); if (r < 0) return r; } @@ -7064,6 +7260,102 @@ static int finalize_extra_mkfs_options(const Partition *p, const char *root, cha return 0; } +static int context_block_device_replace(Context *context) { + int r; + + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) { + _cleanup_(partition_target_freep) PartitionTarget *t = NULL; + + if (p->dropped) + continue; + + if (PARTITION_EXISTS(p)) + continue; + + if (!p->btrfs_replaced) + continue; + + if (partition_defer(context, p)) + continue; + + assert(!p->btrfs_replaced->done); + + (void) context_notify(context, PROGRESS_REPLACING_DEVICE, p->definition_path, UINT_MAX); + + assert(p->offset != UINT64_MAX); + assert(p->new_size != UINT64_MAX); + + r = partition_target_prepare(context, p, + p->new_size, + /* need_path= */ true, + &t); + if (r < 0) + return r; + + if (p->encrypt != ENCRYPT_OFF) { + r = partition_encrypt(context, p, t, /* offline= */ false, /* temporary= */ false); + if (r < 0) + return log_error_errno(r, "Failed to encrypt device: %m"); + } + + log_info("Replacing partition %" PRIu64 ".", p->partno); + + /* btrfs_replace calls a synchronous ioctl and will return when replace is finished */ + r = btrfs_replace(p->btrfs_replaced->mountpoint_fd, p->btrfs_replaced->devid, partition_target_path(t)); + if (r < 0) + return log_error_errno(r, "Failed to replace btrfs device on partition %" PRIu64 ": %m", p->partno); + + p->btrfs_replaced->done = true; + + if (t->decrypted) + t->decrypted->keep = true; + + log_info("Successfully replaced partition %" PRIu64 ".", p->partno); + } + + return 0; +} + +static void context_btrfs_replace_resize(Context *context) { + int r; + + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) { + if (!p->btrfs_replaced) + continue; + + if (!p->btrfs_replaced->done) + continue; + + r = btrfs_resize_max(p->btrfs_replaced->mountpoint_fd, p->btrfs_replaced->devid); + if (r < 0) + log_warning_errno(r, "Could not resize btrfs filesystem moved to partition %" PRIu64 ", proceeding without resizing: %m", p->partno); + else + log_info("Successfully resized partition %" PRIu64 ".", p->partno); + } +} + +static void context_btrfs_replace_back(Context *context) { + int r; + + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) { + if (!p->btrfs_replaced) + continue; + + if (!p->btrfs_replaced->done) + continue; + + r = btrfs_replace(p->btrfs_replaced->mountpoint_fd, p->btrfs_replaced->devid, p->btrfs_replaced->source_path); + if (r < 0) + log_warning_errno(r, "Could not move back btrfs filesystem from partition %" PRIu64 ", leaving it on new device: %m", p->partno); + } +} + static int context_mkfs(Context *context) { int r; @@ -7111,12 +7403,12 @@ static int context_mkfs(Context *context) { if (r < 0) return r; - if (p->encrypt != ENCRYPT_OFF && t->loop) { + if (p->encrypt != ENCRYPT_OFF && (t->loop || t->block_partition)) { r = partition_target_grow(t, p->new_size); if (r < 0) return r; - r = partition_encrypt(context, p, t, /* offline= */ false); + r = partition_encrypt(context, p, t, /* offline= */ false, /* temporary= */ true); if (r < 0) return log_error_errno(r, "Failed to encrypt device: %m"); } @@ -7128,10 +7420,10 @@ static int context_mkfs(Context *context) { * we need to set up the final directory tree beforehand. */ if (partition_needs_populate(p) && - (!t->loop || fstype_is_ro(p->format) || (streq_ptr(p->format, "btrfs") && p->compression))) { + ((!t->loop && !t->block_partition) || fstype_is_ro(p->format) || (streq_ptr(p->format, "btrfs") && p->compression))) { 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 or block partition access is required to populate %s filesystems.", p->format); r = partition_populate_directory(context, p, &root); @@ -7161,7 +7453,7 @@ static int context_mkfs(Context *context) { * 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) { + if (t->fd >= 0 && t->path && !t->loop && !t->block_partition) { safe_close(t->fd); t->fd = open(t->path, O_RDWR|O_CLOEXEC); if (t->fd < 0) @@ -7170,21 +7462,21 @@ static int context_mkfs(Context *context) { log_info("Successfully formatted future partition %" PRIu64 ".", p->partno); - /* If we're writing to a loop device, we can now mount the empty filesystem and populate it. */ + /* If we're writing to a loop device or BLKPG partition, we can now mount the empty filesystem and populate it. */ if (partition_needs_populate(p) && !root) { - assert(t->loop); + assert(t->loop || t->block_partition); r = partition_populate_filesystem(context, p, partition_target_path(t)); if (r < 0) return r; } - if (p->encrypt != ENCRYPT_OFF && !t->loop) { + if (p->encrypt != ENCRYPT_OFF && !t->loop && !t->block_partition) { r = partition_target_grow(t, p->new_size); if (r < 0) return r; - r = partition_encrypt(context, p, t, /* offline= */ true); + r = partition_encrypt(context, p, t, /* offline= */ true, /* temporary= */ true); if (r < 0) return log_error_errno(r, "Failed to encrypt device: %m"); } @@ -8130,9 +8422,34 @@ static int context_find_esp_offset(Context *context, uint64_t *ret) { return 0; } +static int context_partscan(Context *context) { + int capable, r; + + assert(context); + + context->needs_rescan = false; + + capable = blockdev_partscan_enabled_fd(sym_fdisk_get_devfd(context->fdisk_context)); + if (capable == -ENOTBLK) + log_debug("Not telling kernel to reread partition table, since we are not operating on a block device."); + else if (capable < 0) + return log_error_errno(capable, "Failed to check if block device supports partition scanning: %m"); + else if (capable > 0) { + log_info("Informing kernel about changed partitions..."); + (void) context_notify(context, PROGRESS_REREADING_TABLE, /* object= */ NULL, UINT_MAX); + + r = reread_partition_table_fd(sym_fdisk_get_devfd(context->fdisk_context), /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to reread partition table: %m"); + } else + log_notice("Not telling kernel to reread partition table, because selected image does not support kernel partition block devices."); + + return 0; +} + static int context_write_partition_table(Context *context) { _cleanup_(fdisk_unref_tablep) struct fdisk_table *original_table = NULL; - int capable, r; + int r; assert(context); @@ -8178,46 +8495,56 @@ static int context_write_partition_table(Context *context) { * gaps between partitions, just to be sure. */ r = context_wipe_and_discard(context); if (r < 0) - return r; + goto error; r = context_copy_blocks(context); if (r < 0) - return r; + goto error; r = context_mkfs(context); if (r < 0) - return r; + goto error; + + /* We are now moving destructively btrfs filesystems into the disk before we have written the + * partitions. This is OK because the main use case is that the btrfs filesystems moved are initially + * volatile (in ram disk for example) with little data to save. But we do not want to finish the gpt + * table in case we lose power and reboot and try to boot that incomplete disk. + */ + r = context_block_device_replace(context); + if (r < 0) + goto error; r = context_mangle_partitions(context); if (r < 0) - return r; + goto error; log_info("Writing new partition table."); (void) context_notify(context, PROGRESS_WRITING_TABLE, /* object= */ NULL, UINT_MAX); r = sym_fdisk_write_disklabel(context->fdisk_context); + if (r < 0) { + r = log_error_errno(r, "Failed to write partition table: %m"); + goto error; + } + + context_btrfs_replace_resize(context); + + r = context_partscan(context); if (r < 0) - return log_error_errno(r, "Failed to write partition table: %m"); - - capable = blockdev_partscan_enabled_fd(sym_fdisk_get_devfd(context->fdisk_context)); - if (capable == -ENOTBLK) - log_debug("Not telling kernel to reread partition table, since we are not operating on a block device."); - else if (capable < 0) - return log_error_errno(capable, "Failed to check if block device supports partition scanning: %m"); - else if (capable > 0) { - log_info("Informing kernel about changed partitions..."); - (void) context_notify(context, PROGRESS_REREADING_TABLE, /* object= */ NULL, UINT_MAX); - - r = reread_partition_table_fd(sym_fdisk_get_devfd(context->fdisk_context), /* flags= */ 0); - if (r < 0) - return log_error_errno(r, "Failed to reread partition table: %m"); - } else - log_notice("Not telling kernel to reread partition table, because selected image does not support kernel partition block devices."); + return r; log_info("Partition table written."); return 0; + + error: + context_btrfs_replace_back(context); + + if (context->needs_rescan) + (void) context_partscan(context); + + return r; } static int context_write_eltorito(Context *context) { @@ -8740,9 +9067,6 @@ static int context_open_copy_block_paths( assert(context); - if (!context->partitions) - return 0; - LIST_FOREACH(partitions, p, context->partitions) { _cleanup_close_ int source_fd = -EBADF; _cleanup_free_ char *opened = NULL; @@ -8851,6 +9175,72 @@ static int context_open_copy_block_paths( return 0; } +static int context_open_btrfs_filesystems(Context *context) { + int r; + + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) { + _cleanup_(btrfs_replacement_freep) BtrfsReplacement *replacement = NULL; + + if (p->dropped) + continue; + + if (PARTITION_EXISTS(p)) + continue; + + if (!p->block_device_replace) + continue; + + if (partition_defer(context, p)) + continue; + + (void) context_notify(context, PROGRESS_OPENING_BLOCK_DEVICE_REPLACE_SOURCES, p->definition_path, UINT_MAX); + + replacement = new(BtrfsReplacement, 1); + if (!replacement) + return log_oom(); + + *replacement = (BtrfsReplacement) { + .mountpoint_fd = -EBADF, + .source_fd = -EBADF, + }; + + replacement->mountpoint_fd = xopenat(AT_FDCWD, p->block_device_replace, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (replacement->mountpoint_fd < 0) + return log_error_errno(replacement->mountpoint_fd, "Failed to open mountpoint %s for btrfs filesystem: %m", p->block_device_replace); + + r = fd_is_fs_type(replacement->mountpoint_fd, BTRFS_SUPER_MAGIC); + if (r < 0) + return log_error_errno(r, "Failed to check filesystem for mountpoint %s: %m", p->block_device_replace); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Mountpoint %s is not a btrfs filesystem", p->block_device_replace); + + r = btrfs_get_block_device_at_full(replacement->mountpoint_fd, "", &replacement->devid, &replacement->source_path, /* ret= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to find device id for btrfs filesystem: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Btrfs filesystem has multiple devices."); + + /* We need to keep the source device open otherwise, it might be collected. */ + replacement->source_fd = open(replacement->source_path, O_RDONLY|O_CLOEXEC); + if (replacement->source_fd < 0) + return log_error_errno(errno, "Failed to open source device %s: %m", replacement->source_path); + + r = fd_verify_block(replacement->source_fd); + if (r < 0) + return log_error_errno(r, "Device %s is not a block device: %m", replacement->source_path); + + r = blockdev_get_device_size(replacement->source_fd, &replacement->source_size); + if (r < 0) + return log_error_errno(r, "Failed to get device size %s: %m", replacement->source_path); + + p->btrfs_replaced = TAKE_PTR(replacement); + } + + return 0; +} + static int fd_apparent_size(int fd, uint64_t *ret) { off_t initial = 0; uint64_t size = 0; @@ -10433,13 +10823,21 @@ static int acquire_root_devno( assert(ret); assert(ret_fd); - fd = chase_and_open(p, root, CHASE_PREFIX_ROOT, mode, &found_path); + fd = chase_and_open(p, root, CHASE_PREFIX_ROOT, (mode & ~O_ACCMODE_STRICT) | O_RDONLY, &found_path); if (fd < 0) return fd; if (fstat(fd, &st) < 0) return -errno; + if ((S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)) && (mode & O_ACCMODE_STRICT) != O_RDONLY) { + _cleanup_close_ int new_fd = fd_reopen(fd, mode); + if (new_fd < 0) + return new_fd; + + close_and_replace(fd, new_fd); + } + if (S_ISREG(st.st_mode)) { *ret = TAKE_PTR(found_path); *ret_fd = TAKE_FD(fd); @@ -10499,7 +10897,7 @@ static int acquire_root_devno( static int find_root(Context *context) { _cleanup_free_ char *device = NULL; - int r; + int r, open_flags = O_CLOEXEC|context_open_mode(context); assert(context); @@ -10515,7 +10913,7 @@ static int find_root(Context *context) { if (!s) return log_oom(); - fd = xopenat_full(AT_FDCWD, arg_node, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOFOLLOW, XO_NOCOW, 0666); + fd = xopenat_full(AT_FDCWD, arg_node, open_flags|O_CREAT|O_EXCL|O_NOFOLLOW, XO_NOCOW, 0666); if (fd < 0) return log_error_errno(fd, "Failed to create '%s': %m", arg_node); @@ -10527,7 +10925,7 @@ static int find_root(Context *context) { /* Note that we don't specify a root argument here: if the user explicitly configured a node * we'll take it relative to the host, not the image */ - r = acquire_root_devno(arg_node, NULL, O_RDONLY|O_CLOEXEC, &context->node, &context->backing_fd); + r = acquire_root_devno(arg_node, NULL, open_flags, &context->node, &context->backing_fd); if (r == -EUCLEAN) return btrfs_log_dev_root(LOG_ERR, r, arg_node); if (r < 0) @@ -10549,7 +10947,7 @@ static int find_root(Context *context) { FOREACH_STRING(p, "/", "/usr") { - r = acquire_root_devno(p, arg_root, O_RDONLY|O_DIRECTORY|O_CLOEXEC, &context->node, + r = acquire_root_devno(p, arg_root, open_flags|O_DIRECTORY, &context->node, &context->backing_fd); if (r < 0) { if (r == -EUCLEAN) @@ -10562,7 +10960,7 @@ static int find_root(Context *context) { } else if (r < 0) return log_error_errno(r, "Failed to read symlink /run/systemd/volatile-root: %m"); else { - r = acquire_root_devno(device, NULL, O_RDONLY|O_CLOEXEC, &context->node, &context->backing_fd); + r = acquire_root_devno(device, NULL, open_flags, &context->node, &context->backing_fd); if (r == -EUCLEAN) return btrfs_log_dev_root(LOG_ERR, r, device); if (r < 0) @@ -11002,6 +11400,10 @@ static int vl_method_run( if (r < 0) return r; + r = context_open_btrfs_filesystems(context); + if (r < 0) + return r; + r = context_acquire_partition_uuids_and_labels(context); if (r < 0) return r; @@ -11299,6 +11701,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = context_open_btrfs_filesystems(context); + if (r < 0) + return r; + /* Make sure each partition has a unique UUID and unique label */ r = context_acquire_partition_uuids_and_labels(context); if (r < 0) diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 33d6af3942..daa67e6f5c 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -23,6 +23,7 @@ #include "rm-rf.h" #include "sparse-endian.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-util.h" #include "time-util.h" @@ -95,14 +96,20 @@ int btrfs_subvol_get_read_only_fd(int fd) { return !!(flags & BTRFS_SUBVOL_RDONLY); } -int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { +int btrfs_get_block_device_at_full(int dir_fd, const char *path, uint64_t *ret_devid, char **ret_path, dev_t *ret) { struct btrfs_ioctl_fs_info_args fsi = {}; _cleanup_close_ int fd = -EBADF; uint64_t id; int r; + /* + * Returns: + * ret_devid - the device id in the filesystem for the returned block device + * ret_path - the path to the returned block device + * ret - the returned block device + */ + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); - assert(ret); fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); if (fd < 0) @@ -119,7 +126,12 @@ int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { /* We won't do this for btrfs RAID */ if (fsi.num_devices != 1) { - *ret = 0; + if (ret_devid) + *ret_devid = 0; + if (ret_path) + *ret_path = NULL; + if (ret) + *ret = 0; return 0; } @@ -128,6 +140,7 @@ int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { .devid = id, }; struct stat st; + _cleanup_free_ char *device_path = NULL; if (ioctl(fd, BTRFS_IOC_DEV_INFO, &di) < 0) { if (errno == ENODEV) @@ -155,7 +168,16 @@ int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { if (major(st.st_rdev) == 0) return -ENODEV; - *ret = st.st_rdev; + device_path = strdup((char*) di.path); + if (!device_path) + return -ENOMEM; + + if (ret_path) + *ret_path = TAKE_PTR(device_path); + if (ret) + *ret = st.st_rdev; + if (ret_devid) + *ret_devid = id; return 1; } @@ -2122,3 +2144,50 @@ int btrfs_get_file_physical_offset_fd(int fd, uint64_t *ret) { return -ENODATA; } + +int btrfs_replace(int fdmntpnt, uint64_t device_id, const char *target) { + struct btrfs_ioctl_dev_replace_args replace = { + .cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START, + .result = UINT64_MAX, + .start = { + .srcdevid = device_id, + .cont_reading_from_srcdev_mode = BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS, + }, + }; + + assert(fdmntpnt >= 0); + assert(target); + + if (strlen(target) >= sizeof(replace.start.tgtdev_name)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Path to the btrfs replace target is too long"); + strncpy((char *)replace.start.tgtdev_name, target, sizeof(replace.start.tgtdev_name)); + + if (ioctl(fdmntpnt, BTRFS_IOC_DEV_REPLACE, &replace) < 0) + return -errno; + + switch (replace.result) { + case BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR: + break; + case BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED: + return log_debug_errno(SYNTHETIC_ERRNO(ECANCELED), "btrfs replace was not started"); + case BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED: + return log_debug_errno(SYNTHETIC_ERRNO(EALREADY), "btrfs replace was already started on this device"); + case BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS: + return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "btrfs scrub is in progress"); + default: + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "An unknown btrfs error status occurred"); + } + + return 0; +} + +int btrfs_resize_max(int fdmntpnt, uint64_t devid) { + struct btrfs_ioctl_vol_args args = {}; + + assert(fdmntpnt >= 0); + + assert_cc(STRLEN(":max") + DECIMAL_STR_MAX(uint64_t) + 1 <= sizeof(args.name)); + xsprintf(args.name, "%" PRIu64 ":max", devid); + + return RET_NERRNO(ioctl(fdmntpnt, BTRFS_IOC_RESIZE, &args)); +} diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h index afa54cde4c..da7c897945 100644 --- a/src/shared/btrfs-util.h +++ b/src/shared/btrfs-util.h @@ -49,7 +49,10 @@ static inline int btrfs_is_subvol(const char *path) { return btrfs_is_subvol_at(AT_FDCWD, path); } -int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret); +int btrfs_get_block_device_at_full(int dir_fd, const char *path, uint64_t *ret_devid, char **ret_path, dev_t *ret); +static inline int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { + return btrfs_get_block_device_at_full(dir_fd, path, NULL, NULL, ret); +} static inline int btrfs_get_block_device(const char *path, dev_t *ret) { return btrfs_get_block_device_at(AT_FDCWD, path, ret); } @@ -133,3 +136,6 @@ bool btrfs_might_be_subvol(const struct stat *st) _pure_; int btrfs_forget_device(const char *path); int btrfs_get_file_physical_offset_fd(int fd, uint64_t *ret); + +int btrfs_replace(int fdmntpnt, uint64_t device_id, const char *target); +int btrfs_resize_max(int fdmntpnt, uint64_t devid); diff --git a/src/shared/fdisk-util.c b/src/shared/fdisk-util.c index 5eaa91160a..00f1bfeaea 100644 --- a/src/shared/fdisk-util.c +++ b/src/shared/fdisk-util.c @@ -23,6 +23,7 @@ DLSYM_PROTOTYPE(fdisk_apply_table) = NULL; DLSYM_PROTOTYPE(fdisk_ask_get_type) = NULL; DLSYM_PROTOTYPE(fdisk_ask_string_set_result) = NULL; DLSYM_PROTOTYPE(fdisk_assign_device) = NULL; +DLSYM_PROTOTYPE(fdisk_assign_device_by_fd) = NULL; DLSYM_PROTOTYPE(fdisk_create_disklabel) = NULL; DLSYM_PROTOTYPE(fdisk_delete_partition) = NULL; DLSYM_PROTOTYPE(fdisk_get_devfd) = NULL; @@ -97,6 +98,7 @@ int dlopen_fdisk(int log_level) { DLSYM_ARG(fdisk_ask_get_type), DLSYM_ARG(fdisk_ask_string_set_result), DLSYM_ARG(fdisk_assign_device), + DLSYM_ARG(fdisk_assign_device_by_fd), DLSYM_ARG(fdisk_create_disklabel), DLSYM_ARG(fdisk_delete_partition), DLSYM_ARG(fdisk_get_devfd), diff --git a/src/shared/fdisk-util.h b/src/shared/fdisk-util.h index bdac11c507..66620c9c1c 100644 --- a/src/shared/fdisk-util.h +++ b/src/shared/fdisk-util.h @@ -14,6 +14,7 @@ extern DLSYM_PROTOTYPE(fdisk_apply_table); extern DLSYM_PROTOTYPE(fdisk_ask_get_type); extern DLSYM_PROTOTYPE(fdisk_ask_string_set_result); extern DLSYM_PROTOTYPE(fdisk_assign_device); +extern DLSYM_PROTOTYPE(fdisk_assign_device_by_fd); extern DLSYM_PROTOTYPE(fdisk_create_disklabel); extern DLSYM_PROTOTYPE(fdisk_delete_partition); extern DLSYM_PROTOTYPE(fdisk_get_devfd); diff --git a/test/units/TEST-58-REPART.sh b/test/units/TEST-58-REPART.sh index df6f935c98..319b682298 100755 --- a/test/units/TEST-58-REPART.sh +++ b/test/units/TEST-58-REPART.sh @@ -2132,6 +2132,97 @@ EOF losetup -d "$loop" } +testcase_block_device_replace() { + if [[ "$OFFLINE" == "yes" ]]; then + return 0 + fi + + if ! command -v btrfs >/dev/null; then + echo "btrfs not found, skipping." + return 0 + fi + + if ! command -v mkfs.btrfs >/dev/null; then + echo "mkfs.btrfs not found, skipping." + return 0 + fi + + local defs imgs btrfs_mntpoint_plain btrfs_mntpoint_encrypted + local loop loop_btrfs_plain loop_btrfs_encrypted + local encrypted_device + + btrfs_mntpoint_plain="$(mktemp --directory "/tmp/test-repart.btrfs-mntpoint-plain.XXXXXXXXXX")" + btrfs_mntpoint_encrypted="$(mktemp --directory "/tmp/test-repart.btrfs-mntpoint-encrypted.XXXXXXXXXX")" + defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")" + imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")" + # shellcheck disable=SC2064 + trap "rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + chmod 0755 "$defs" + + truncate --size 500M "${imgs}/btrfs-plain" + mkfs.btrfs "${imgs}/btrfs-plain" + loop_btrfs_plain="$(losetup --show --find "$imgs/btrfs-plain")" + # shellcheck disable=SC2064 + trap "losetup -d '${loop_btrfs_plain}'; rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + + mount "${loop_btrfs_plain}" "${btrfs_mntpoint_plain}" + echo tada >"${btrfs_mntpoint_plain}/magic-plain" + + # shellcheck disable=SC2064 + trap "umount '${btrfs_mntpoint_plain}'; losetup -d '${loop_btrfs_plain}'; rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + + truncate --size 500M "${imgs}/btrfs-encrypted" + mkfs.btrfs "${imgs}/btrfs-encrypted" + loop_btrfs_encrypted="$(losetup --show --find "$imgs/btrfs-encrypted")" + # shellcheck disable=SC2064 + trap "losetup -d '${loop_btrfs_encrypted}'; umount '${btrfs_mntpoint_plain}'; losetup -d '${loop_btrfs_plain}'; rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + + mount "${loop_btrfs_encrypted}" "${btrfs_mntpoint_encrypted}" + echo tada >"${btrfs_mntpoint_encrypted}/magic-encrypted" + + # shellcheck disable=SC2064 + trap "umount '${btrfs_mntpoint_encrypted}'; losetup -d '${loop_btrfs_encrypted}'; umount '${btrfs_mntpoint_plain}'; losetup -d '${loop_btrfs_plain}'; rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + + truncate --size 2G "${imgs}/img" + + tee "$defs/01-plain.conf" <