]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: Add BlockDeviceReplace=
authorValentin David <me@valentindavid.com>
Thu, 12 Mar 2026 22:14:52 +0000 (23:14 +0100)
committerValentin David <me@valentindavid.com>
Tue, 12 May 2026 16:31:25 +0000 (18:31 +0200)
BlockDeviceReplace=/mntpnt will move the btrfs filesystem from mount point to
the partition created. After moving, it will resize to take the whole
partition.

This is useful for OS installers that move a live system into a disk and
do not require a reboot.

man/repart.d.xml
src/repart/repart.c
src/shared/btrfs-util.c
src/shared/btrfs-util.h

index 028929ac9e842915bd219cb1575f853c5b47833e..598cdc2310dcb20970a7e365560653c7200c8a39 100644 (file)
         <xi:include href="version-info.xml" xpointer="v256"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>BlockDeviceReplace=</varname></term>
+
+        <listitem><para>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.</para>
+
+        <para>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.</para>
+
+        <para>This option is not compatible with <option>--offline=yes</option>.</para>
+
+        <xi:include href="version-info.xml" xpointer="v261"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>Encrypt=</varname></term>
 
index a2958f3212f1298819db71dccd8d4d11b6d4f262..1620cc3530b5ad38d6a3c9b7584b9afe1540d918 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include <fcntl.h>
+#include <linux/magic.h>
 #include <sys/file.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
@@ -250,6 +251,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 +262,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 +426,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 +492,8 @@ typedef struct Partition {
         uint64_t copy_blocks_done;
 
         char *format;
+        char *block_device_replace;
+        BtrfsReplacement *btrfs_replaced;
         char **exclude_files_source;
         char **exclude_files_target;
         char **make_directories;
@@ -630,19 +656,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) {
@@ -807,6 +835,8 @@ 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);
         strv_free(p->exclude_files_source);
         strv_free(p->exclude_files_target);
         strv_free(p->make_directories);
@@ -851,6 +881,8 @@ 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->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);
@@ -1161,6 +1193,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;
 
@@ -2924,6 +2958,7 @@ 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    },
                 {}
         };
         _cleanup_free_ char *filename = NULL;
@@ -2974,6 +3009,18 @@ 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 (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.");
@@ -2983,7 +3030,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";
 
@@ -7186,6 +7233,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);
+                        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;
 
@@ -8335,6 +8478,15 @@ static int context_write_partition_table(Context *context) {
         if (r < 0)
                 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)
                 goto error;
@@ -8349,6 +8501,8 @@ static int context_write_partition_table(Context *context) {
                 goto error;
         }
 
+        context_btrfs_replace_resize(context);
+
         r = context_partscan(context);
         if (r < 0)
                 return r;
@@ -8358,6 +8512,8 @@ static int context_write_partition_table(Context *context) {
         return 0;
 
  error:
+        context_btrfs_replace_back(context);
+
         if (context->needs_rescan)
                 (void) context_partscan(context);
 
@@ -8884,9 +9040,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;
@@ -8995,6 +9148,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;
@@ -11154,6 +11373,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;
@@ -11451,6 +11674,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)
index 33d6af3942ec6712ce01d248c8fdc6ae4e2a5285..daa67e6f5cfe32adfa59ff3bc7820310a67974f7 100644 (file)
@@ -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));
+}
index afa54cde4c434c71c3fb43f4012ca4d3b4e5e93a..da7c89794542e64eeb1d010bd2906d1c261e2839 100644 (file)
@@ -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);