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:
parent
e81acfd251
commit
5c08da586f
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user