dissect-image: support images without rootfs but with /usr/
Let's add support for images that include an /usr/ file system but no root fs. Mount a tmpfs as root for images like this, all controlled by a new flag DISSECT_IMAGE_USR_NO_ROOT. This is useful for entirely stateless images, that come up pristine on every single boot.
This commit is contained in:
+116
-58
@@ -1100,64 +1100,85 @@ int dissect_image(
|
||||
m->partitions[PARTITION_ROOT_SECONDARY_VERITY].found = false;
|
||||
m->partitions[PARTITION_USR_SECONDARY].found = false;
|
||||
m->partitions[PARTITION_USR_SECONDARY_VERITY].found = false;
|
||||
} else {
|
||||
/* No root partition found? Then let's see if ther's one for the secondary architecture. And if not
|
||||
* either, then check if there's a single generic one, and use that. */
|
||||
|
||||
if (m->partitions[PARTITION_ROOT_VERITY].found)
|
||||
} else if (m->partitions[PARTITION_ROOT_VERITY].found)
|
||||
return -EADDRNOTAVAIL; /* Verity found but no matching rootfs? Something is off, refuse. */
|
||||
|
||||
else if (m->partitions[PARTITION_ROOT_SECONDARY].found) {
|
||||
|
||||
/* No root partition found but there's one for the secondary architecture? Then upgrade
|
||||
* secondary arch to first */
|
||||
|
||||
m->partitions[PARTITION_ROOT] = m->partitions[PARTITION_ROOT_SECONDARY];
|
||||
zero(m->partitions[PARTITION_ROOT_SECONDARY]);
|
||||
m->partitions[PARTITION_ROOT_VERITY] = m->partitions[PARTITION_ROOT_SECONDARY_VERITY];
|
||||
zero(m->partitions[PARTITION_ROOT_SECONDARY_VERITY]);
|
||||
|
||||
m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_SECONDARY];
|
||||
zero(m->partitions[PARTITION_USR_SECONDARY]);
|
||||
m->partitions[PARTITION_USR_VERITY] = m->partitions[PARTITION_USR_SECONDARY_VERITY];
|
||||
zero(m->partitions[PARTITION_USR_SECONDARY_VERITY]);
|
||||
|
||||
} else if (m->partitions[PARTITION_ROOT_SECONDARY_VERITY].found)
|
||||
return -EADDRNOTAVAIL; /* as above */
|
||||
|
||||
else if (m->partitions[PARTITION_USR].found) {
|
||||
|
||||
/* Invalidate secondary arch /usr/ if we found the primary arch */
|
||||
m->partitions[PARTITION_USR_SECONDARY].found = false;
|
||||
m->partitions[PARTITION_USR_SECONDARY_VERITY].found = false;
|
||||
|
||||
} else if (m->partitions[PARTITION_USR_VERITY].found)
|
||||
return -EADDRNOTAVAIL; /* as above */
|
||||
|
||||
else if (m->partitions[PARTITION_USR_SECONDARY].found) {
|
||||
|
||||
/* Upgrade secondary arch to primary */
|
||||
m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_SECONDARY];
|
||||
zero(m->partitions[PARTITION_USR_SECONDARY]);
|
||||
m->partitions[PARTITION_USR_VERITY] = m->partitions[PARTITION_USR_SECONDARY_VERITY];
|
||||
zero(m->partitions[PARTITION_USR_SECONDARY_VERITY]);
|
||||
|
||||
} else if (m->partitions[PARTITION_USR_SECONDARY_VERITY].found)
|
||||
return -EADDRNOTAVAIL; /* as above */
|
||||
|
||||
else if (flags & DISSECT_IMAGE_REQUIRE_ROOT) {
|
||||
_cleanup_free_ char *o = NULL;
|
||||
const char *options = NULL;
|
||||
|
||||
/* OK, we found nothing usable, then check if there's a single generic one distro, and use
|
||||
* that. */
|
||||
|
||||
/* If the root hash was set, then we won't fall back to a generic node, because the root hash
|
||||
* decides. */
|
||||
if (verity && verity->root_hash)
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
/* We didn't find a primary architecture root, but we found a primary architecture /usr? Refuse that for now. */
|
||||
if (m->partitions[PARTITION_USR].found || m->partitions[PARTITION_USR_VERITY].found)
|
||||
return -EADDRNOTAVAIL;
|
||||
/* If we didn't find a generic node, then we can't fix this up either */
|
||||
if (!generic_node)
|
||||
return -ENXIO;
|
||||
|
||||
if (m->partitions[PARTITION_ROOT_SECONDARY].found) {
|
||||
/* Upgrade secondary arch to first */
|
||||
m->partitions[PARTITION_ROOT] = m->partitions[PARTITION_ROOT_SECONDARY];
|
||||
zero(m->partitions[PARTITION_ROOT_SECONDARY]);
|
||||
m->partitions[PARTITION_ROOT_VERITY] = m->partitions[PARTITION_ROOT_SECONDARY_VERITY];
|
||||
zero(m->partitions[PARTITION_ROOT_SECONDARY_VERITY]);
|
||||
/* If we didn't find a properly marked root partition, but we did find a single suitable
|
||||
* generic Linux partition, then use this as root partition, if the caller asked for it. */
|
||||
if (multiple_generic)
|
||||
return -ENOTUNIQ;
|
||||
|
||||
m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_SECONDARY];
|
||||
zero(m->partitions[PARTITION_USR_SECONDARY]);
|
||||
m->partitions[PARTITION_USR_VERITY] = m->partitions[PARTITION_USR_SECONDARY_VERITY];
|
||||
zero(m->partitions[PARTITION_USR_SECONDARY_VERITY]);
|
||||
|
||||
} else if (flags & DISSECT_IMAGE_REQUIRE_ROOT) {
|
||||
_cleanup_free_ char *o = NULL;
|
||||
const char *options = NULL;
|
||||
|
||||
/* If the root hash was set, then we won't fall back to a generic node, because the
|
||||
* root hash decides. */
|
||||
if (verity && verity->root_hash)
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
/* If we didn't find a generic node, then we can't fix this up either */
|
||||
if (!generic_node)
|
||||
return -ENXIO;
|
||||
|
||||
/* If we didn't find a properly marked root partition, but we did find a single suitable
|
||||
* generic Linux partition, then use this as root partition, if the caller asked for it. */
|
||||
if (multiple_generic)
|
||||
return -ENOTUNIQ;
|
||||
|
||||
options = mount_options_from_designator(mount_options, PARTITION_ROOT);
|
||||
if (options) {
|
||||
o = strdup(options);
|
||||
if (!o)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
m->partitions[PARTITION_ROOT] = (DissectedPartition) {
|
||||
.found = true,
|
||||
.rw = generic_rw,
|
||||
.partno = generic_nr,
|
||||
.architecture = _ARCHITECTURE_INVALID,
|
||||
.node = TAKE_PTR(generic_node),
|
||||
.uuid = generic_uuid,
|
||||
.mount_options = TAKE_PTR(o),
|
||||
};
|
||||
options = mount_options_from_designator(mount_options, PARTITION_ROOT);
|
||||
if (options) {
|
||||
o = strdup(options);
|
||||
if (!o)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
m->partitions[PARTITION_ROOT] = (DissectedPartition) {
|
||||
.found = true,
|
||||
.rw = generic_rw,
|
||||
.partno = generic_nr,
|
||||
.architecture = _ARCHITECTURE_INVALID,
|
||||
.node = TAKE_PTR(generic_node),
|
||||
.uuid = generic_uuid,
|
||||
.mount_options = TAKE_PTR(o),
|
||||
};
|
||||
}
|
||||
|
||||
/* Refuse if we found a verity partition for /usr but no matching file system partition */
|
||||
@@ -1397,6 +1418,32 @@ static int mount_partition(
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int mount_root_tmpfs(const char *where, uid_t uid_shift, DissectImageFlags flags) {
|
||||
_cleanup_free_ char *options = NULL;
|
||||
int r;
|
||||
|
||||
assert(where);
|
||||
|
||||
/* For images that contain /usr/ but no rootfs, let's mount rootfs as tmpfs */
|
||||
|
||||
if (FLAGS_SET(flags, DISSECT_IMAGE_MKDIR)) {
|
||||
r = mkdir_p(where, 0755);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (uid_is_valid(uid_shift)) {
|
||||
if (asprintf(&options, "uid=" UID_FMT ",gid=" GID_FMT, uid_shift, (gid_t) uid_shift) < 0)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
r = mount_nofollow_verbose(LOG_DEBUG, "rootfs", where, "tmpfs", MS_NODEV, options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, DissectImageFlags flags) {
|
||||
int r, xbootldr_mounted;
|
||||
|
||||
@@ -1413,16 +1460,20 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift,
|
||||
* -EAFNOSUPPORT → File system type not supported or not known
|
||||
*/
|
||||
|
||||
if (!m->partitions[PARTITION_ROOT].found)
|
||||
return -ENXIO;
|
||||
if (!(m->partitions[PARTITION_ROOT].found ||
|
||||
(m->partitions[PARTITION_USR].found && FLAGS_SET(flags, DISSECT_IMAGE_USR_NO_ROOT))))
|
||||
return -ENXIO; /* Require a root fs or at least a /usr/ fs (the latter is subject to a flag of its own) */
|
||||
|
||||
if ((flags & DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY) == 0) {
|
||||
r = mount_partition(m->partitions + PARTITION_ROOT, where, NULL, uid_shift, flags);
|
||||
|
||||
/* First mount the root fs. If there's none we use a tmpfs. */
|
||||
if (m->partitions[PARTITION_ROOT].found)
|
||||
r = mount_partition(m->partitions + PARTITION_ROOT, where, NULL, uid_shift, flags);
|
||||
else
|
||||
r = mount_root_tmpfs(where, uid_shift, flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if ((flags & DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY) == 0) {
|
||||
/* For us mounting root always means mounting /usr as well */
|
||||
r = mount_partition(m->partitions + PARTITION_USR, where, "/usr", uid_shift, flags);
|
||||
if (r < 0)
|
||||
@@ -2305,7 +2356,14 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
|
||||
if (r == 0) {
|
||||
error_pipe[0] = safe_close(error_pipe[0]);
|
||||
|
||||
r = dissected_image_mount(m, t, UID_INVALID, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_MOUNT_ROOT_ONLY|DISSECT_IMAGE_VALIDATE_OS);
|
||||
r = dissected_image_mount(
|
||||
m,
|
||||
t,
|
||||
UID_INVALID,
|
||||
DISSECT_IMAGE_READ_ONLY|
|
||||
DISSECT_IMAGE_MOUNT_ROOT_ONLY|
|
||||
DISSECT_IMAGE_VALIDATE_OS|
|
||||
DISSECT_IMAGE_USR_NO_ROOT);
|
||||
if (r < 0) {
|
||||
/* Let parent know the error */
|
||||
(void) write(error_pipe[1], &r, sizeof(r));
|
||||
|
||||
@@ -87,6 +87,7 @@ typedef enum DissectImageFlags {
|
||||
DISSECT_IMAGE_NO_PARTITION_TABLE = 1 << 12, /* Only recognize single file system images */
|
||||
DISSECT_IMAGE_VERITY_SHARE = 1 << 13, /* When activating a verity device, reuse existing one if already open */
|
||||
DISSECT_IMAGE_MKDIR = 1 << 14, /* Make top-level directory to mount right before mounting, if missing */
|
||||
DISSECT_IMAGE_USR_NO_ROOT = 1 << 15, /* If no root fs is in the image, but /usr is, then allow this (so that we can mount the rootfs as tmpfs or so */
|
||||
} DissectImageFlags;
|
||||
|
||||
struct DissectedImage {
|
||||
|
||||
Reference in New Issue
Block a user