1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-23 21:35:11 +03:00

repart: add CopyBlocks=auto support

When using systemd-repart as an installer that replicates the install
medium on another medium it is useful to reference the root
partition/usr partition or verity data that is currently booted, in
particular in A/B scenarios where we have two copies and want to
reference the one we currently use. Let's add a CopyBlocks=auto for this
case: for a partition that uses that we'll copy a suitable partition
from the host.

CopyBlocks=auto finds the partition to copy like this: based on the
configured partition type uuid we determine the usual mount point (i.e.
for the /usr partition type we determine /usr/, and so on). We then
figure out the block device behind that path, through dm-verity and
dm-crypt if necessary. Finally, we compare the partition type uuid of
the partition found that way with the one we are supposed to fill and
only use it if it matches (the latter is primarily important on
dm-verity setups where a volume is likely backed by two partitions and
we need to find the right one).

This is particularly fun to use in conjunction with --image= (where
we'll restrict the device search onto the specify device, for security
reasons), as this allows "duplicating" an image like this:

    # systemd-repart --image=source.raw --empty=create --size=auto target.raw

If the right repart data is embedded into "source.raw" this will be able
to create and initialize a partition table on target.raw that carrries
all needed partitions, and will stream the source's file systems onto it
as configured.
This commit is contained in:
Lennart Poettering 2021-03-23 14:12:38 +01:00
parent e81acfd251
commit 5c08da586f
2 changed files with 446 additions and 42 deletions

View File

@ -422,12 +422,25 @@
<varlistentry>
<term><varname>CopyBlocks=</varname></term>
<listitem><para>Takes a path to a regular file, block device node or directory. If specified and the
partition is newly created the data from the specified path is written to the newly created
partition, on the block level. If a directory is specified the backing block device of the file
system the directory is on is determined and the data read directly from that. This option is useful
to efficiently replicate existing file systems on the block level on a new partition, for example to
build a simple OS installer or OS image builder.</para>
<listitem><para>Takes a path to a regular file, block device node or directory, or the special value
<literal>auto</literal>. If specified and the partition is newly created, the data from the specified
path is written to the newly created partition, on the block level. If a directory is specified, the
backing block device of the file system the directory is on is determined, and the data read directly
from that. This option is useful to efficiently replicate existing file systems onto new partitions
on the block level — for example to build a simple OS installer or an OS image builder.</para>
<para>If the special value <literal>auto</literal> is specified, the source to copy from is
automatically picked up from the running system (or the image specified with
<option>--image=</option> — if used). A partition that matches both the configured partition type (as
declared with <varname>Type=</varname> above), and the currently mounted directory appropriate for
that partition type is determined. For example, if the partition type is set to
<literal>root</literal> the partition backing the root directory (<filename>/</filename>) is used as
source to copy from — if its partition type is set to <literal>root</literal> as well. If the
declared type is <literal>usr</literal> the partition backing <filename>/usr/</filename> is used as
source to copy blocks from — if its partition type is set to <literal>usr</literal> too. The logic is
capable of automatically tracking down the the backing partitions for encrypted and Verity-enabled
volumes. <literal>CopyBlocks=auto</literal> is useful for implementing "self-replicating" systems,
i.e. systems that are their own installer.</para>
<para>The file specified here must have a size that is a multiple of the basic block size 512 and not
be empty. If this option is used, the size allocation algorithm is slightly altered: the partition is

View File

@ -26,6 +26,7 @@
#include "conf-parser.h"
#include "cryptsetup-util.h"
#include "def.h"
#include "dirent-util.h"
#include "efivars.h"
#include "errno-util.h"
#include "fd-util.h"
@ -44,6 +45,7 @@
#include "mkdir.h"
#include "mkfs-util.h"
#include "mount-util.h"
#include "mountpoint-util.h"
#include "parse-argument.h"
#include "parse-util.h"
#include "path-util.h"
@ -157,6 +159,7 @@ struct Partition {
FreeArea *allocated_to_area;
char *copy_blocks_path;
bool copy_blocks_auto;
int copy_blocks_fd;
uint64_t copy_blocks_size;
@ -1162,6 +1165,53 @@ static int config_parse_copy_files(
return 0;
}
static int config_parse_copy_blocks(
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 *d = NULL;
Partition *partition = data;
int r;
assert(rvalue);
assert(partition);
if (isempty(rvalue)) {
partition->copy_blocks_path = mfree(partition->copy_blocks_path);
partition->copy_blocks_auto = false;
return 0;
}
if (streq(rvalue, "auto")) {
partition->copy_blocks_path = mfree(partition->copy_blocks_path);
partition->copy_blocks_auto = true;
return 0;
}
r = specifier_printf(rvalue, specifier_table, NULL, &d);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to expand specifiers in CopyBlocks= source path, ignoring: %s", rvalue);
return 0;
}
r = path_simplify_and_warn(d, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue);
if (r < 0)
return 0;
free_and_replace(partition->copy_blocks_path, d);
partition->copy_blocks_auto = false;
return 0;
}
static int config_parse_make_dirs(
const char *unit,
const char *filename,
@ -1216,22 +1266,22 @@ static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_encrypt, encrypt_mode,
static int partition_read_definition(Partition *p, const char *path) {
ConfigTableItem table[] = {
{ "Partition", "Type", config_parse_type, 0, &p->type_uuid },
{ "Partition", "Label", config_parse_label, 0, &p->new_label },
{ "Partition", "UUID", config_parse_id128, 0, &p->new_uuid },
{ "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_path, 0, &p->copy_blocks_path },
{ "Partition", "Format", config_parse_fstype, 0, &p->format },
{ "Partition", "CopyFiles", config_parse_copy_files, 0, p },
{ "Partition", "MakeDirectories", config_parse_make_dirs, 0, p },
{ "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt },
{ "Partition", "Type", config_parse_type, 0, &p->type_uuid },
{ "Partition", "Label", config_parse_label, 0, &p->new_label },
{ "Partition", "UUID", config_parse_id128, 0, &p->new_uuid },
{ "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 },
{ "Partition", "MakeDirectories", config_parse_make_dirs, 0, p },
{ "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt },
{}
};
int r;
@ -1257,15 +1307,16 @@ static int partition_read_definition(Partition *p, const char *path) {
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"Type= not defined, refusing.");
if (p->copy_blocks_path && (p->format || !strv_isempty(p->copy_files) || !strv_isempty(p->make_directories)))
if ((p->copy_blocks_path || p->copy_blocks_auto) &&
(p->format || !strv_isempty(p->copy_files) || !strv_isempty(p->make_directories)))
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"Format= and CopyBlocks= cannot be combined, refusing.");
"Format=/CopyFiles=/MakeDirectories= and CopyBlocks= cannot be combined, refusing.");
if ((!strv_isempty(p->copy_files) || !strv_isempty(p->make_directories)) && streq_ptr(p->format, "swap"))
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"Format=swap and CopyFiles= cannot be combined, refusing.");
if (!p->format && (!strv_isempty(p->copy_files) || !strv_isempty(p->make_directories) || (p->encrypt != ENCRYPT_OFF && !p->copy_blocks_path))) {
if (!p->format && (!strv_isempty(p->copy_files) || !strv_isempty(p->make_directories) || (p->encrypt != ENCRYPT_OFF && !(p->copy_blocks_path || p->copy_blocks_auto)))) {
/* Pick "ext4" as file system if we are configured to copy files or encrypt the device */
p->format = strdup("ext4");
if (!p->format)
@ -3459,7 +3510,311 @@ static int context_can_factory_reset(Context *context) {
return false;
}
static int context_open_copy_block_paths(Context *context) {
static int resolve_copy_blocks_auto_candidate(
dev_t partition_devno,
sd_id128_t partition_type_uuid,
dev_t restrict_devno,
sd_id128_t *ret_uuid) {
_cleanup_(blkid_free_probep) blkid_probe b = NULL;
_cleanup_free_ char *p = NULL;
_cleanup_close_ int fd = -1;
const char *pttype, *t;
sd_id128_t pt_parsed, u;
blkid_partition pp;
dev_t whole_devno;
blkid_partlist pl;
struct stat st;
int r;
/* Checks if the specified partition has the specified GPT type UUID, and is located on the specified
* 'restrict_devno' device. The type check is particularly relevant if we have Verity volume which is
* backed by two separate partitions: the data and the hash partitions, and we need to find the right
* one of the two. */
r = block_get_whole_disk(partition_devno, &whole_devno);
if (r < 0)
return log_error_errno(
r,
"Unable to determine containing block device of partition %u:%u: %m",
major(partition_devno), minor(partition_devno));
if (restrict_devno != (dev_t) -1 &&
restrict_devno != whole_devno)
return log_error_errno(
SYNTHETIC_ERRNO(EPERM),
"Partition %u:%u is located outside of block device %u:%u, refusing.",
major(partition_devno), minor(partition_devno),
major(restrict_devno), minor(restrict_devno));
r = device_path_make_major_minor(S_IFBLK, whole_devno, &p);
if (r < 0)
return log_error_errno(r, "Failed to convert block device to device node path: %m");
fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
if (fd < 0)
return log_error_errno(r, "Failed to open '%s': %m", p);
if (fstat(fd, &st) < 0)
return log_error_errno(r, "Failed to stat '%s': %m", p);
if (!S_ISBLK(st.st_mode) || st.st_rdev != whole_devno)
return log_error_errno(
SYNTHETIC_ERRNO(EPERM),
"Opened and determined block device don't match, refusing.");
b = blkid_new_probe();
if (!b)
return log_oom();
errno = 0;
r = blkid_probe_set_device(b, fd, 0, 0);
if (r != 0)
return log_error_errno(errno_or_else(ENOMEM), "Failed to open block device '%s': %m", p);
(void) blkid_probe_enable_partitions(b, 1);
(void) blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
errno = 0;
r = blkid_do_safeprobe(b);
if (IN_SET(r, -2, 1)) { /* nothing found or ambiguous result */
log_debug("Didn't find partition table on block device '%s'.", p);
return false;
}
if (r != 0)
return log_error_errno(errno_or_else(EIO), "Unable to probe for partition table of '%s': %m", p);
(void) blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL);
if (!streq_ptr(pttype, "gpt")) {
log_debug("Didn't find a GPT partition table on '%s'.", p);
return false;
}
errno = 0;
pl = blkid_probe_get_partitions(b);
if (!pl)
return log_error_errno(errno_or_else(EIO), "Unable read partition table of '%s': %m", p);
errno = 0;
pp = blkid_partlist_devno_to_partition(pl, partition_devno);
if (!pp) {
log_debug("Partition %u:%u has no matching partition table entry on '%s'.",
major(partition_devno), minor(partition_devno), p);
return false;
}
t = blkid_partition_get_type_string(pp);
if (isempty(t)) {
log_debug("Partition %u:%u has no type on '%s'.",
major(partition_devno), minor(partition_devno), p);
return false;
}
r = sd_id128_from_string(t, &pt_parsed);
if (r < 0) {
log_debug_errno(r, "Failed to parse partition type \"%s\": %m", t);
return false;
}
if (!sd_id128_equal(pt_parsed, partition_type_uuid)) {
log_debug("Partition %u:%u has non-matching partition type " SD_ID128_FORMAT_STR " (needed: " SD_ID128_FORMAT_STR "), ignoring.",
major(partition_devno), minor(partition_devno),
SD_ID128_FORMAT_VAL(pt_parsed), SD_ID128_FORMAT_VAL(partition_type_uuid));
return false;
}
t = blkid_partition_get_uuid(pp);
if (isempty(t)) {
log_debug("Partition %u:%u has no UUID.",
major(partition_devno), minor(partition_devno));
return false;
}
r = sd_id128_from_string(t, &u);
if (r < 0) {
log_debug_errno(r, "Failed to parse partition UUID \"%s\": %m", t);
return false;
}
log_debug("Automatically found partition %u:%u of right type " SD_ID128_FORMAT_STR ".",
major(partition_devno), minor(partition_devno),
SD_ID128_FORMAT_VAL(pt_parsed));
if (ret_uuid)
*ret_uuid = u;
return true;
}
static int find_backing_devno(
const char *path,
const char *root,
dev_t *ret) {
_cleanup_free_ char *resolved = NULL;
int r;
assert(path);
r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &resolved, NULL);
if (r < 0)
return r;
r = path_is_mount_point(resolved, NULL, 0);
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. */
return -ENOENT;
r = get_block_device(resolved, ret);
if (r < 0)
return r;
if (r == 0) /* Not backed by physical file system, we can't use this */
return -ENOENT;
return 0;
}
static int resolve_copy_blocks_auto(
sd_id128_t type_uuid,
const char *root,
dev_t restrict_devno,
char **ret_path,
sd_id128_t *ret_uuid) {
const char *try1 = NULL, *try2 = NULL;
char p[SYS_BLOCK_PATH_MAX("/slaves")];
_cleanup_(closedirp) DIR *d = NULL;
sd_id128_t found_uuid = SD_ID128_NULL;
dev_t devno, found = 0;
int r;
assert(ret_path);
/* Enforce some security restrictions: CopyBlocks=auto should not be an avenue to get outside of the
* --root=/--image= confinement. Specifically, refuse CopyBlocks= in combination with --root= at all,
* and restrict block device references in the --image= case to loopback block device we set up.
*
* restrict_devno contain the dev_t of the loop back device we operate on in case of --image=, and
* thus declares which device (and its partition subdevices) we shall limit access to. If
* restrict_devno is zero no device probing access shall be allowed at all (used for --root=) and if
* it is (dev_t) -1 then free access shall be allowed (if neither switch is used). */
if (restrict_devno == 0)
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
"Automatic discovery of backing block devices not permitted in --root= mode, refusing.");
/* Handles CopyBlocks=auto, and finds the right source partition to copy from. We look for matching
* partitions in the host, using the appropriate directory as key and ensuring that the partition
* type matches. */
if (gpt_partition_type_is_root(type_uuid))
try1 = "/";
else if (gpt_partition_type_is_usr(type_uuid))
try1 = "/usr/";
else if (gpt_partition_type_is_root_verity(type_uuid))
try1 = "/";
else if (gpt_partition_type_is_usr_verity(type_uuid))
try1 = "/usr/";
else if (sd_id128_equal(type_uuid, GPT_ESP)) {
try1 = "/efi/";
try2 = "/boot/";
} else if (sd_id128_equal(type_uuid, GPT_XBOOTLDR))
try1 = "/boot/";
else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Partition type " SD_ID128_FORMAT_STR " not supported from automatic source block device discovery.",
SD_ID128_FORMAT_VAL(type_uuid));
r = find_backing_devno(try1, root, &devno);
if (r == -ENOENT && try2)
r = find_backing_devno(try2, root, &devno);
if (r < 0)
return log_error_errno(r, "Failed to resolve automatic CopyBlocks= path for partition type " SD_ID128_FORMAT_STR ", sorry: %m",
SD_ID128_FORMAT_VAL(type_uuid));
xsprintf_sys_block_path(p, "/slaves", devno);
d = opendir(p);
if (d) {
struct dirent *de;
for (;;) {
_cleanup_free_ char *q = NULL, *t = NULL;
sd_id128_t u;
dev_t sl;
errno = 0;
de = readdir_no_dot(d);
if (!de) {
if (errno != 0)
return log_error_errno(errno, "Failed to read directory '%s': %m", p);
break;
}
if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN))
continue;
q = path_join(p, de->d_name, "/dev");
if (!q)
return log_oom();
r = read_one_line_file(q, &t);
if (r < 0)
return log_error_errno(r, "Failed to read %s: %m", q);
r = parse_dev(t, &sl);
if (r < 0) {
log_debug_errno(r, "Failed to parse %s, ignoring: %m", q);
continue;
}
if (major(sl) == 0) {
log_debug_errno(r, "Device backing %s is special, ignoring: %m", q);
continue;
}
r = resolve_copy_blocks_auto_candidate(sl, type_uuid, restrict_devno, &u);
if (r < 0)
return r;
if (r > 0) {
/* We found a matching one! */
if (found != 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
"Multiple matching partitions found, refusing.");
found = sl;
found_uuid = u;
}
}
} else if (errno != ENOENT)
return log_error_errno(errno, "Failed open %s: %m", p);
else {
r = resolve_copy_blocks_auto_candidate(devno, type_uuid, restrict_devno, &found_uuid);
if (r < 0)
return r;
if (r > 0)
found = devno;
}
if (found == 0)
return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
"Unable to automatically discover suitable partition to copy blocks from.");
r = device_path_make_major_minor(S_IFBLK, found, ret_path);
if (r < 0)
return log_error_errno(r, "Failed to convert dev_t to device node path: %m");
if (ret_uuid)
*ret_uuid = found_uuid;
return 0;
}
static int context_open_copy_block_paths(
Context *context,
const char *root,
dev_t restrict_devno) {
Partition *p;
int r;
@ -3467,6 +3822,8 @@ static int context_open_copy_block_paths(Context *context) {
LIST_FOREACH(partitions, p, context->partitions) {
_cleanup_close_ int source_fd = -1;
_cleanup_free_ char *opened = NULL;
sd_id128_t uuid = SD_ID128_NULL;
uint64_t size;
struct stat st;
@ -3476,16 +3833,39 @@ static int context_open_copy_block_paths(Context *context) {
if (PARTITION_EXISTS(p)) /* Never copy over partitions that already exist! */
continue;
if (!p->copy_blocks_path)
if (p->copy_blocks_path) {
source_fd = chase_symlinks_and_open(p->copy_blocks_path, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NONBLOCK, &opened);
if (source_fd < 0)
return log_error_errno(source_fd, "Failed to open '%s': %m", p->copy_blocks_path);
if (fstat(source_fd, &st) < 0)
return log_error_errno(errno, "Failed to stat block copy file '%s': %m", opened);
if (!S_ISREG(st.st_mode) && restrict_devno != (dev_t) -1)
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
"Copying from block device node is not permitted in --image=/--root= mode, refusing.");
} else if (p->copy_blocks_auto) {
r = resolve_copy_blocks_auto(p->type_uuid, root, restrict_devno, &opened, &uuid);
if (r < 0)
return r;
source_fd = open(opened, O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (source_fd < 0)
return log_error_errno(errno, "Failed to open automatically determined source block copy device '%s': %m", opened);
if (fstat(source_fd, &st) < 0)
return log_error_errno(errno, "Failed to stat block copy file '%s': %m", opened);
/* If we found it automatically, it must be a block device, let's enforce that */
if (!S_ISBLK(st.st_mode))
return log_error_errno(SYNTHETIC_ERRNO(EBADF),
"Automatically detected source block copy device '%s' is not a block device, refusing: %m", opened);
} else
continue;
source_fd = open(p->copy_blocks_path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (source_fd < 0)
return log_error_errno(errno, "Failed to open block copy file '%s': %m", p->copy_blocks_path);
if (fstat(source_fd, &st) < 0)
return log_error_errno(errno, "Failed to stat block copy file '%s': %m", p->copy_blocks_path);
if (S_ISDIR(st.st_mode)) {
_cleanup_free_ char *bdev = NULL;
@ -3500,14 +3880,14 @@ static int context_open_copy_block_paths(Context *context) {
r = btrfs_get_block_device_fd(source_fd, &devt);
if (r == -EUCLEAN)
return btrfs_log_dev_root(LOG_ERR, r, p->copy_blocks_path);
return btrfs_log_dev_root(LOG_ERR, r, opened);
if (r < 0)
return log_error_errno(r, "Unable to determine backing block device of '%s': %m", p->copy_blocks_path);
return log_error_errno(r, "Unable to determine backing block device of '%s': %m", opened);
r = device_path_make_major_minor(S_IFBLK, devt, &bdev);
}
if (r < 0)
return log_error_errno(r, "Failed to determine block device path for block device backing '%s': %m", p->copy_blocks_path);
return log_error_errno(r, "Failed to determine block device path for block device backing '%s': %m", opened);
safe_close(source_fd);
@ -3528,15 +3908,21 @@ static int context_open_copy_block_paths(Context *context) {
if (ioctl(source_fd, BLKGETSIZE64, &size) != 0)
return log_error_errno(errno, "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", p->copy_blocks_path);
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);
if (size <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File to copy bytes from '%s' has zero size, refusing.", p->copy_blocks_path);
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File to copy bytes from '%s' has zero size, refusing.", opened);
if (size % 512 != 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File to copy bytes from '%s' has size that is not multiple of 512, refusing.", p->copy_blocks_path);
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File to copy bytes from '%s' has size that is not multiple of 512, refusing.", opened);
p->copy_blocks_fd = TAKE_FD(source_fd);
p->copy_blocks_size = size;
free_and_replace(p->copy_blocks_path, opened);
/* When copying from an existing partition copy that partitions UUID if none is configured explicitly */
if (sd_id128_is_null(p->new_uuid) && !sd_id128_is_null(uuid))
p->new_uuid = uuid;
}
return 0;
@ -3970,7 +4356,7 @@ static int acquire_root_devno(
if (S_ISBLK(st.st_mode)) {
/* Refuse referencing explicit block devices if a root dir is specified, after all we should
* be able to leave the image the root path constraints us to. */
* not be able to leave the image the root path constrains us to. */
if (root)
return -EPERM;
@ -4399,7 +4785,12 @@ static int run(int argc, char *argv[]) {
return r;
/* Open all files to copy blocks from now, since we want to take their size into consideration */
r = context_open_copy_block_paths(context);
r = context_open_copy_block_paths(
context,
arg_root,
loop_device ? loop_device->devno : /* if --image= is specified, only allow partitions on the loopback device*/
arg_root && !arg_image ? 0 : /* if --root= is specified, don't accept any block device */
(dev_t) -1); /* if neither is specified, make no restrictions */
if (r < 0)
return r;