Merge pull request #24944 from DaanDeMeyer/repart-rootless

repart: Add support for running without root privileges
This commit is contained in:
Daan De Meyer 2022-11-16 09:09:06 +01:00 committed by GitHub
commit bb8b8875f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1058 additions and 714 deletions

View file

@ -1325,7 +1325,10 @@ if want_libcryptsetup != 'false' and not skip_deps
foreach ident : ['crypt_set_metadata_size',
'crypt_activate_by_signed_key',
'crypt_token_max']
'crypt_token_max',
'crypt_reencrypt_init_by_passphrase',
'crypt_reencrypt',
'crypt_set_data_offset']
have_ident = have and cc.has_function(
ident,
prefix : '#include <libcryptsetup.h>',

View file

@ -21,6 +21,8 @@ Packages=
coreutils
diffutils
dnsmasq
dosfstools
e2fsprogs
findutils
gcc # For sanitizer libraries
gdb
@ -29,6 +31,7 @@ Packages=
kexec-tools
kmod
less
mtools
nano
nftables
openssl
@ -40,6 +43,7 @@ Packages=
util-linux
valgrind
wireguard-tools
xfsprogs
zsh
BuildPackages=

View file

@ -11,8 +11,10 @@ Distribution=arch
[Content]
Packages=
alsa-lib
btrfs-progs
compsize
dhcp
f2fs-tools
fuse2
gnutls
iproute

View file

@ -9,7 +9,9 @@ Release=testing
[Content]
Packages=
btrfs-progs
cryptsetup-bin
f2fs-tools
fdisk
fuse
gcc # Provides libasan/libubsan

View file

@ -10,9 +10,11 @@ Release=37
[Content]
Packages=
alsa-lib
btrfs-progs
compsize
cryptsetup
dhcp-server
f2fs-tools
fuse
glib2
glibc-minimal-langpack

View file

@ -9,7 +9,9 @@ Release=tumbleweed
[Content]
Packages=
btrfs-progs
dbus-1
f2fs-tools
fuse
gcc # Provides libasan/libubsan
glibc-32bit

View file

@ -10,7 +10,9 @@ Repositories=main,universe
[Content]
Packages=
btrfs-progs
cryptsetup-bin
f2fs-tools
fdisk
fuse
gcc # Provides libasan/libubsan

File diff suppressed because it is too large Load diff

View file

@ -245,7 +245,6 @@ int btrfs_clone_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offs
assert(infd >= 0);
assert(outfd >= 0);
assert(sz > 0);
r = fd_verify_regular(outfd);
if (r < 0)

View file

@ -49,6 +49,18 @@ int (*sym_crypt_token_max)(const char *type);
#endif
crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type);
int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size);
#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE
int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params);
#endif
#if HAVE_CRYPT_REENCRYPT
int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr));
#endif
int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable);
#if HAVE_CRYPT_SET_DATA_OFFSET
int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset);
#endif
int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file);
int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable);
static void cryptsetup_log_glue(int level, const char *msg, void *usrptr) {
@ -234,7 +246,19 @@ int dlopen_cryptsetup(void) {
DLSYM_ARG(crypt_token_max),
#endif
DLSYM_ARG(crypt_token_status),
DLSYM_ARG(crypt_volume_key_get));
DLSYM_ARG(crypt_volume_key_get),
#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE
DLSYM_ARG(crypt_reencrypt_init_by_passphrase),
#endif
#if HAVE_CRYPT_REENCRYPT
DLSYM_ARG(crypt_reencrypt),
#endif
DLSYM_ARG(crypt_metadata_locking),
#if HAVE_CRYPT_SET_DATA_OFFSET
DLSYM_ARG(crypt_set_data_offset),
#endif
DLSYM_ARG(crypt_header_restore),
DLSYM_ARG(crypt_volume_key_keyring));
if (r <= 0)
return r;

View file

@ -64,6 +64,18 @@ static inline int crypt_token_max(_unused_ const char *type) {
#endif
extern crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type);
extern int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size);
#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE
extern int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params);
#endif
#if HAVE_CRYPT_REENCRYPT
extern int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr));
#endif
extern int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable);
#if HAVE_CRYPT_SET_DATA_OFFSET
extern int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset);
#endif
extern int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file);
extern int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, crypt_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, sym_crypt_free, NULL);

View file

@ -2,11 +2,15 @@
#include <unistd.h>
#include "dirent-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "id128-util.h"
#include "mkfs-util.h"
#include "mountpoint-util.h"
#include "path-util.h"
#include "process-util.h"
#include "stat-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "utf8.h"
@ -33,6 +37,10 @@ int mkfs_exists(const char *fstype) {
return true;
}
int mkfs_supports_root_option(const char *fstype) {
return fstype_is_ro(fstype) || STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "vfat");
}
static int mangle_linux_fs_label(const char *s, size_t max_len, char **ret) {
/* Not more than max_len bytes (12 or 16) */
@ -87,6 +95,96 @@ static int mangle_fat_label(const char *s, char **ret) {
return 0;
}
static int setup_userns(uid_t uid, gid_t gid) {
int r;
/* mkfs programs tend to keep ownership intact when bootstrapping themselves from a root directory.
* However, we'd like for the files to be owned by root instead, so we fork off a user namespace and
* inside of it, map the uid/gid of the root directory to root in the user namespace. mkfs programs
* will pick up on this and the files will be owned by root in the generated filesystem. */
r = write_string_filef("/proc/self/uid_map", WRITE_STRING_FILE_DISABLE_BUFFER,
UID_FMT " " UID_FMT " " UID_FMT, 0u, uid, 1u);
if (r < 0)
return log_error_errno(r,
"Failed to write mapping for "UID_FMT" to /proc/self/uid_map: %m",
uid);
r = write_string_file("/proc/self/setgroups", "deny", WRITE_STRING_FILE_DISABLE_BUFFER);
if (r < 0)
return log_error_errno(r, "Failed to write 'deny' to /proc/self/setgroups: %m");
r = write_string_filef("/proc/self/gid_map", WRITE_STRING_FILE_DISABLE_BUFFER,
UID_FMT " " UID_FMT " " UID_FMT, 0u, gid, 1u);
if (r < 0)
return log_error_errno(r,
"Failed to write mapping for "UID_FMT" to /proc/self/gid_map: %m",
gid);
return 0;
}
static int do_mcopy(const char *node, const char *root) {
_cleanup_strv_free_ char **argv = NULL;
_cleanup_closedir_ DIR *rootdir = NULL;
struct stat st;
int r;
assert(node);
assert(root);
/* Return early if there's nothing to copy. */
if (dir_is_empty(root, /*ignore_hidden_or_backup=*/ false))
return 0;
argv = strv_new("mcopy", "-b", "-s", "-p", "-Q", "-n", "-m", "-i", node);
if (!argv)
return log_oom();
/* mcopy copies the top level directory instead of everything in it so we have to pass all
* the subdirectories to mcopy instead to end up with the correct directory structure. */
rootdir = opendir(root);
if (!rootdir)
return log_error_errno(errno, "Failed to open directory '%s'", root);
FOREACH_DIRENT(de, rootdir, return -errno) {
char *p = path_join(root, de->d_name);
if (!p)
return log_oom();
r = strv_consume(&argv, TAKE_PTR(p));
if (r < 0)
return log_oom();
}
r = strv_extend(&argv, "::");
if (r < 0)
return log_oom();
if (stat(root, &st) < 0)
return log_error_errno(errno, "Failed to stat '%s': %m", root);
r = safe_fork("(mcopy)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|FORK_NEW_USERNS, NULL);
if (r < 0)
return r;
if (r == 0) {
r = setup_userns(st.st_uid, st.st_gid);
if (r < 0)
_exit(EXIT_FAILURE);
/* Avoid failures caused by mismatch in expectations between mkfs.vfat and mcopy by disabling
* the stricter mcopy checks using MTOOLS_SKIP_CHECK. */
execvpe("mcopy", argv, STRV_MAKE("MTOOLS_SKIP_CHECK=1"));
log_error_errno(errno, "Failed to execute mcopy: %m");
_exit(EXIT_FAILURE);
}
return 0;
}
int make_filesystem(
const char *node,
const char *fstype,
@ -96,7 +194,9 @@ int make_filesystem(
bool discard) {
_cleanup_free_ char *mkfs = NULL, *mangled_label = NULL;
_cleanup_strv_free_ char **argv = NULL;
char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {};
struct stat st;
int r;
assert(node);
@ -128,9 +228,9 @@ int make_filesystem(
"Don't know how to create read-only file system '%s', refusing.",
fstype);
} else {
if (root)
if (root && !mkfs_supports_root_option(fstype))
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Populating with source tree is only supported for read-only filesystems");
"Populating with source tree is not supported for %s", fstype);
r = mkfs_exists(fstype);
if (r < 0)
return log_error_errno(r, "Failed to determine whether mkfs binary for %s exists: %m", fstype);
@ -169,101 +269,151 @@ int make_filesystem(
if (isempty(vol_id))
assert_se(sd_id128_to_uuid_string(uuid, vol_id));
r = safe_fork("(mkfs)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR, NULL);
/* When changing this conditional, also adjust the log statement below. */
if (streq(fstype, "ext2")) {
argv = strv_new(mkfs,
"-q",
"-L", label,
"-U", vol_id,
"-I", "256",
"-m", "0",
"-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
node);
if (!argv)
return log_oom();
if (root) {
r = strv_extend_strv(&argv, STRV_MAKE("-d", root), false);
if (r < 0)
return log_oom();
}
} else if (STR_IN_SET(fstype, "ext3", "ext4")) {
argv = strv_new(mkfs,
"-q",
"-L", label,
"-U", vol_id,
"-I", "256",
"-O", "has_journal",
"-m", "0",
"-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
node);
if (root) {
r = strv_extend_strv(&argv, STRV_MAKE("-d", root), false);
if (r < 0)
return log_oom();
}
} else if (streq(fstype, "btrfs")) {
argv = strv_new(mkfs,
"-q",
"-L", label,
"-U", vol_id,
node);
if (!argv)
return log_oom();
if (!discard) {
r = strv_extend(&argv, "--nodiscard");
if (r < 0)
return log_oom();
}
if (root) {
r = strv_extend_strv(&argv, STRV_MAKE("-r", root), false);
if (r < 0)
return log_oom();
}
} else if (streq(fstype, "f2fs")) {
argv = strv_new(mkfs,
"-q",
"-g", /* "default options" */
"-f", /* force override, without this it doesn't seem to want to write to an empty partition */
"-l", label,
"-U", vol_id,
"-t", one_zero(discard),
node);
} else if (streq(fstype, "xfs")) {
const char *j;
j = strjoina("uuid=", vol_id);
argv = strv_new(mkfs,
"-q",
"-L", label,
"-m", j,
"-m", "reflink=1",
node);
if (!argv)
return log_oom();
if (!discard) {
r = strv_extend(&argv, "-K");
if (r < 0)
return log_oom();
}
} else if (streq(fstype, "vfat"))
argv = strv_new(mkfs,
"-i", vol_id,
"-n", label,
"-F", "32", /* yes, we force FAT32 here */
node);
else if (streq(fstype, "swap"))
/* TODO: add --quiet here if
* https://github.com/util-linux/util-linux/issues/1499 resolved. */
argv = strv_new(mkfs,
"-L", label,
"-U", vol_id,
node);
else if (streq(fstype, "squashfs"))
argv = strv_new(mkfs,
root, node,
"-quiet",
"-noappend");
else
/* Generic fallback for all other file systems */
argv = strv_new(mkfs, node);
if (!argv)
return log_oom();
if (root && stat(root, &st) < 0)
return log_error_errno(errno, "Failed to stat %s: %m", root);
r = safe_fork("(mkfs)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|(root ? FORK_NEW_USERNS : 0), NULL);
if (r < 0)
return r;
if (r == 0) {
/* Child */
/* When changing this conditional, also adjust the log statement below. */
if (streq(fstype, "ext2"))
(void) execlp(mkfs, mkfs,
"-q",
"-L", label,
"-U", vol_id,
"-I", "256",
"-m", "0",
"-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
node, NULL);
if (root) {
r = setup_userns(st.st_uid, st.st_gid);
if (r < 0)
_exit(EXIT_FAILURE);
}
else if (STR_IN_SET(fstype, "ext3", "ext4"))
(void) execlp(mkfs, mkfs,
"-q",
"-L", label,
"-U", vol_id,
"-I", "256",
"-O", "has_journal",
"-m", "0",
"-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
node, NULL);
else if (streq(fstype, "btrfs")) {
(void) execlp(mkfs, mkfs,
"-q",
"-L", label,
"-U", vol_id,
node,
discard ? NULL : "--nodiscard",
NULL);
} else if (streq(fstype, "f2fs")) {
(void) execlp(mkfs, mkfs,
"-q",
"-g", /* "default options" */
"-f", /* force override, without this it doesn't seem to want to write to an empty partition */
"-l", label,
"-U", vol_id,
"-t", one_zero(discard),
node,
NULL);
} else if (streq(fstype, "xfs")) {
const char *j;
j = strjoina("uuid=", vol_id);
(void) execlp(mkfs, mkfs,
"-q",
"-L", label,
"-m", j,
"-m", "reflink=1",
node,
discard ? NULL : "-K",
NULL);
} else if (streq(fstype, "vfat"))
(void) execlp(mkfs, mkfs,
"-i", vol_id,
"-n", label,
"-F", "32", /* yes, we force FAT32 here */
node, NULL);
else if (streq(fstype, "swap"))
/* TODO: add --quiet here if
* https://github.com/util-linux/util-linux/issues/1499 resolved. */
(void) execlp(mkfs, mkfs,
"-L", label,
"-U", vol_id,
node, NULL);
else if (streq(fstype, "squashfs"))
(void) execlp(mkfs, mkfs,
root, node,
"-quiet",
"-noappend",
NULL);
else
/* Generic fallback for all other file systems */
(void) execlp(mkfs, mkfs, node, NULL);
execvp(mkfs, argv);
log_error_errno(errno, "Failed to execute %s: %m", mkfs);
_exit(EXIT_FAILURE);
}
if (root && streq(fstype, "vfat")) {
r = do_mcopy(node, root);
if (r < 0)
return r;
}
if (STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "f2fs", "xfs", "vfat", "swap"))
log_info("%s successfully formatted as %s (label \"%s\", uuid %s)",
node, fstype, label, vol_id);

View file

@ -9,4 +9,6 @@
int mkfs_exists(const char *fstype);
int mkfs_supports_root_option(const char *fstype);
int make_filesystem(const char *node, const char *fstype, const char *label, const char *root, sd_id128_t uuid, bool discard);

View file

@ -12,13 +12,15 @@ TEST_FORCE_NEWIMAGE=1
test_append_files() {
if ! get_bool "${TEST_NO_QEMU:=}"; then
install_dmevent
if command -v openssl >/dev/null 2>&1; then
inst_binary openssl
fi
instmods dm_verity =md
generate_module_dependencies
image_install -o /sbin/mksquashfs
fi
inst_binary mcopy
if command -v openssl >/dev/null 2>&1; then
inst_binary openssl
fi
}
do_test "$@"

View file

@ -1310,6 +1310,11 @@ install_missing_libraries() {
inst_simple "${path}/engines-3/capi.so" || true
inst_simple "${path}/engines-3/loader_attic.so" || true
inst_simple "${path}/engines-3/padlock.so" || true
# Binaries from mtools depend on the gconv modules to translate between codepages. Because there's no
# pkg-config file for these, we copy every gconv/ directory we can find in /usr/lib and /usr/lib64.
# shellcheck disable=SC2046
inst_recursive $(find /usr/lib* -name gconv 2>/dev/null)
}
cleanup_loopdev() {

View file

@ -3,6 +3,13 @@
set -eux
set -o pipefail
runas() {
declare userid=$1
shift
# shellcheck disable=SC2016
su "$userid" -s /bin/sh -c 'XDG_RUNTIME_DIR=/run/user/$UID exec "$@"' -- sh "$@"
}
if ! command -v systemd-repart &>/dev/null; then
echo "no systemd-repart" >/skipped
exit 0
@ -89,17 +96,17 @@ test_basic() {
local defs imgs output
local loop volume
defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
# 1. create an empty image
systemd-repart --empty=create \
--size=1G \
--seed="$seed" \
"$imgs/zzz"
runas testuser systemd-repart --empty=create \
--size=1G \
--seed="$seed" \
"$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@ -133,11 +140,11 @@ SizeMaxBytes=64M
PaddingMinBytes=92M
EOF
systemd-repart --definitions="$defs" \
--dry-run=no \
--seed="$seed" \
--include-partitions=home,swap \
"$imgs/zzz"
runas testuser systemd-repart --definitions="$defs" \
--dry-run=no \
--seed="$seed" \
--include-partitions=home,swap \
"$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@ -150,11 +157,11 @@ last-lba: 2097118
$imgs/zzz1 : start= 2048, size= 591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\"
$imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\""
systemd-repart --definitions="$defs" \
--dry-run=no \
--seed="$seed" \
--exclude-partitions=root \
"$imgs/zzz"
runas testuser systemd-repart --definitions="$defs" \
--dry-run=no \
--seed="$seed" \
--exclude-partitions=root \
"$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@ -167,10 +174,10 @@ last-lba: 2097118
$imgs/zzz1 : start= 2048, size= 591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\"
$imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\""
systemd-repart --definitions="$defs" \
--dry-run=no \
--seed="$seed" \
"$imgs/zzz"
runas testuser systemd-repart --definitions="$defs" \
--dry-run=no \
--seed="$seed" \
"$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@ -203,10 +210,10 @@ EOF
echo "Label=ignored_label" >>"$defs/home.conf"
echo "UUID=b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" >>"$defs/home.conf"
systemd-repart --definitions="$defs" \
--dry-run=no \
--seed="$seed" \
"$imgs/zzz"
runas testuser systemd-repart --definitions="$defs" \
--dry-run=no \
--seed="$seed" \
"$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@ -224,11 +231,11 @@ $imgs/zzz5 : start= 1908696, size= 188416, type=0FC63DAF-8483-4772-8E79
# 4. Resizing to 2G
systemd-repart --definitions="$defs" \
--size=2G \
--dry-run=no \
--seed="$seed" \
"$imgs/zzz"
runas testuser systemd-repart --definitions="$defs" \
--size=2G \
--dry-run=no \
--seed="$seed" \
"$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@ -256,11 +263,11 @@ UUID=2a1d97e1d0a346cca26eadc643926617
CopyBlocks=$imgs/block-copy
EOF
systemd-repart --definitions="$defs" \
--size=3G \
--dry-run=no \
--seed="$seed" \
"$imgs/zzz"
runas testuser systemd-repart --definitions="$defs" \
--size=3G \
--dry-run=no \
--seed="$seed" \
"$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@ -279,11 +286,6 @@ $imgs/zzz6 : start= 4194264, size= 2097152, type=0FC63DAF-8483-4772-8E79
cmp --bytes=$((4096*10240)) --ignore-initial=0:$((512*4194264)) "$imgs/block-copy" "$imgs/zzz"
if systemd-detect-virt --quiet --container; then
echo "Skipping encrypt tests in container."
return
fi
# 6. Testing Format=/Encrypt=/CopyFiles=
cat >"$defs/extra3.conf" <<EOF
@ -297,11 +299,11 @@ CopyFiles=$defs:/def
SizeMinBytes=48M
EOF
systemd-repart --definitions="$defs" \
--size=auto \
--dry-run=no \
--seed="$seed" \
"$imgs/zzz"
runas testuser systemd-repart --definitions="$defs" \
--size=auto \
--dry-run=no \
--seed="$seed" \
"$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@ -319,6 +321,11 @@ $imgs/zzz5 : start= 1908696, size= 2285568, type=0FC63DAF-8483-4772-8E79
$imgs/zzz6 : start= 4194264, size= 2097152, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=2A1D97E1-D0A3-46CC-A26E-ADC643926617, name=\"block-copy\"
$imgs/zzz7 : start= 6291416, size= 98304, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=7B93D1F2-595D-4CE3-B0B9-837FBD9E63B0, name=\"luks-format-copy\""
if systemd-detect-virt --quiet --container; then
echo "Skipping encrypt mount tests in container."
return
fi
loop="$(losetup -P --show --find "$imgs/zzz")"
udevadm wait --timeout 60 --settle "${loop:?}"
@ -338,8 +345,8 @@ $imgs/zzz7 : start= 6291416, size= 98304, type=0FC63DAF-8483-4772-8E79
test_dropin() {
local defs imgs output
defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
@ -362,7 +369,11 @@ EOF
Label=label2
EOF
output=$(systemd-repart --definitions="$defs" --empty=create --size=100M --json=pretty "$imgs/zzz")
output=$(runas testuser systemd-repart --definitions="$defs" \
--empty=create \
--size=100M \
--json=pretty \
"$imgs/zzz")
diff -u <(echo "$output") - <<EOF
[
@ -392,8 +403,8 @@ EOF
test_multiple_definitions() {
local defs imgs output
defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
@ -417,7 +428,12 @@ UUID=837c3d67-21b3-478e-be82-7e7f83bf96d3
Label=label2
EOF
output=$(systemd-repart --definitions="$defs/1" --definitions="$defs/2" --empty=create --size=100M --json=pretty "$imgs/zzz")
output=$(runas testuser systemd-repart --definitions="$defs/1" \
--definitions="$defs/2" \
--empty=create \
--size=100M \
--json=pretty \
"$imgs/zzz")
diff -u <(echo "$output") - <<EOF
[
@ -458,13 +474,8 @@ EOF
test_copy_blocks() {
local defs imgs output
if systemd-detect-virt --quiet --container; then
echo "Skipping copy blocks tests in container."
return
fi
defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
@ -493,11 +504,11 @@ Format=ext4
MakeDirectories=/usr /efi
EOF
systemd-repart --definitions="$defs" \
--empty=create \
--size=auto \
--seed="$seed" \
"$imgs/zzz"
runas testuser systemd-repart --definitions="$defs" \
--empty=create \
--size=auto \
--seed="$seed" \
"$imgs/zzz"
output=$(sfdisk --dump "$imgs/zzz")
@ -505,6 +516,11 @@ EOF
assert_in "$imgs/zzz2 : start= 22528, size= 20480, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\"" "$output"
assert_in "$imgs/zzz3 : start= 43008, size= 20480, type=${usr_guid}, uuid=${usr_uuid}, name=\"usr-${architecture}\", attrs=\"GUID:60\"" "$output"
if systemd-detect-virt --quiet --container; then
echo "Skipping second part of copy blocks tests in container."
return
fi
# Then, create another image with CopyBlocks=auto
cat >"$defs/esp.conf" <<EOF
@ -526,6 +542,7 @@ Type=root-${architecture}
CopyBlocks=auto
EOF
# --image needs root privileges so skip runas testuser here.
systemd-repart --definitions="$defs" \
--empty=create \
--size=auto \
@ -539,8 +556,8 @@ EOF
test_unaligned_partition() {
local defs imgs output
defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
@ -551,7 +568,7 @@ test_unaligned_partition() {
Type=root-${architecture}
EOF
truncate -s 10g "$imgs/unaligned"
runas testuser truncate -s 10g "$imgs/unaligned"
sfdisk "$imgs/unaligned" <<EOF
label: gpt
@ -559,10 +576,10 @@ start=2048, size=69044
start=71092, size=3591848
EOF
systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
"$imgs/unaligned"
runas testuser systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
"$imgs/unaligned"
output=$(sfdisk --dump "$imgs/unaligned")
@ -576,8 +593,8 @@ test_issue_21817() {
# testcase for #21817
defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
@ -586,7 +603,7 @@ test_issue_21817() {
Type=root
EOF
truncate -s 100m "$imgs/21817.img"
runas testuser truncate -s 100m "$imgs/21817.img"
sfdisk "$imgs/21817.img" <<EOF
label: gpt
@ -594,11 +611,11 @@ size=50M, type=${root_guid}
,
EOF
systemd-repart --pretty=yes \
--definitions "$imgs" \
--seed="$seed" \
--dry-run=no \
"$imgs/21817.img"
runas testuser systemd-repart --pretty=yes \
--definitions "$imgs" \
--seed="$seed" \
--dry-run=no \
"$imgs/21817.img"
output=$(sfdisk --dump "$imgs/21817.img")
@ -612,8 +629,8 @@ test_issue_24553() {
# testcase for #24553
defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
@ -635,28 +652,28 @@ start=524328, size=14848000, type=${root_guid}, uuid=${root_uuid}, name="root-${
EOF
# 1. Operate on a small image compared with SizeMinBytes=.
truncate -s 8g "$imgs/zzz"
runas testuser truncate -s 8g "$imgs/zzz"
sfdisk "$imgs/zzz" <"$imgs/partscript"
# This should fail, but not trigger assertions.
assert_rc 1 systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
"$imgs/zzz"
assert_rc 1 runas testuser systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
"$imgs/zzz"
output=$(sfdisk --dump "$imgs/zzz")
assert_in "$imgs/zzz2 : start= 524328, size= 14848000, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\"" "$output"
# 2. Operate on an larger image compared with SizeMinBytes=.
rm -f "$imgs/zzz"
truncate -s 12g "$imgs/zzz"
runas testuser truncate -s 12g "$imgs/zzz"
sfdisk "$imgs/zzz" <"$imgs/partscript"
# This should succeed.
systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
"$imgs/zzz"
runas testuser systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
"$imgs/zzz"
output=$(sfdisk --dump "$imgs/zzz")
assert_in "$imgs/zzz2 : start= 524328, size= 24641456, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\"" "$output"
@ -678,14 +695,14 @@ Priority=10
EOF
rm -f "$imgs/zzz"
truncate -s 8g "$imgs/zzz"
runas testuser truncate -s 8g "$imgs/zzz"
sfdisk "$imgs/zzz" <"$imgs/partscript"
# This should also succeed, but root is not extended.
systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
"$imgs/zzz"
runas testuser systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
"$imgs/zzz"
output=$(sfdisk --dump "$imgs/zzz")
assert_in "$imgs/zzz2 : start= 524328, size= 14848000, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\"" "$output"
@ -693,14 +710,14 @@ EOF
# 4. Multiple partitions with Priority= (large disk)
rm -f "$imgs/zzz"
truncate -s 12g "$imgs/zzz"
runas testuser truncate -s 12g "$imgs/zzz"
sfdisk "$imgs/zzz" <"$imgs/partscript"
# This should also succeed, and root is extended.
systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
"$imgs/zzz"
runas testuser systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
"$imgs/zzz"
output=$(sfdisk --dump "$imgs/zzz")
assert_in "$imgs/zzz2 : start= 524328, size= 20971520, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\"" "$output"
@ -710,8 +727,8 @@ EOF
test_zero_uuid() {
local defs imgs output
defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
@ -723,12 +740,12 @@ Type=root-${architecture}
UUID=null
EOF
systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
--empty=create \
--size=auto \
"$imgs/zero"
runas testuser systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
--empty=create \
--size=auto \
"$imgs/zero"
output=$(sfdisk --dump "$imgs/zero")
@ -738,13 +755,8 @@ EOF
test_verity() {
local defs imgs output
if systemd-detect-virt --quiet --container; then
echo "Skipping verity test in container."
return
fi
defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
@ -786,25 +798,36 @@ CN = Common Name
emailAddress = test@email.com
EOF
openssl req -config "$defs/verity.openssl.cnf" -new -x509 -newkey rsa:1024 -keyout "$defs/verity.key" -out "$defs/verity.crt" -days 365 -nodes
runas testuser openssl req -config "$defs/verity.openssl.cnf" \
-new -x509 \
-newkey rsa:1024 \
-keyout "$defs/verity.key" \
-out "$defs/verity.crt" \
-days 365 \
-nodes
mkdir -p /run/verity.d
ln -s "$defs/verity.crt" /run/verity.d/ok.crt
output=$(systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
--empty=create \
--size=auto \
--json=pretty \
--private-key="$defs/verity.key" \
--certificate="$defs/verity.crt" \
"$imgs/verity")
output=$(runas testuser systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
--empty=create \
--size=auto \
--json=pretty \
--private-key="$defs/verity.key" \
--certificate="$defs/verity.crt" \
"$imgs/verity")
roothash=$(jq -r ".[] | select(.type == \"root-${architecture}-verity\") | .roothash" <<< "$output")
# Check that we can dissect, mount and unmount a repart verity image. (and that the image UUID is deterministic)
if systemd-detect-virt --quiet --container; then
echo "Skipping verity test dissect part in container."
return
fi
systemd-dissect "$imgs/verity" --root-hash "$roothash"
systemd-dissect "$imgs/verity" --root-hash "$roothash" --json=short | grep -q '"imageUuid":"1d2ce291-7cce-4f7d-bc83-fdb49ad74ebd"'
systemd-dissect "$imgs/verity" --root-hash "$roothash" -M "$imgs/mnt"
@ -814,14 +837,9 @@ EOF
test_issue_24786() {
local defs imgs root output
if systemd-detect-virt --quiet --container; then
echo "Skipping verity test in container."
return
fi
defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
root="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
root="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs' '$root'" RETURN
@ -841,14 +859,19 @@ Type=usr-${architecture}
CopyFiles=/usr:/
EOF
output=$(systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
--empty=create \
--size=auto \
--json=pretty \
--root="$root" \
"$imgs/zzz")
output=$(runas testuser systemd-repart --definitions="$defs" \
--seed="$seed" \
--dry-run=no \
--empty=create \
--size=auto \
--json=pretty \
--root="$root" \
"$imgs/zzz")
if systemd-detect-virt --quiet --container; then
echo "Skipping issue 24786 test loop/mount parts in container."
return
fi
loop=$(losetup -P --show -f "$imgs/zzz")
udevadm wait --timeout 60 --settle "${loop:?}"
@ -953,6 +976,8 @@ EOF
truncate -s 100m "$imgs/$sector.img"
loop=$(losetup -b "$sector" -P --show -f "$imgs/$sector.img" )
udevadm wait --timeout 60 --settle "${loop:?}"
# This operates on a loop device which we don't support doing without root privileges so we skip runas
# here.
systemd-repart --pretty=yes \
--definitions="$defs" \
--seed="$seed" \