diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml
index 16b789869f..1799961527 100644
--- a/man/systemd-repart.xml
+++ b/man/systemd-repart.xml
@@ -428,6 +428,18 @@
x86-64.
+
+ BOOL
+
+ Instructs systemd-repart to build the image offline. Takes a
+ boolean or auto. Defaults to auto. If enabled, the image is
+ built without using loop devices. This is useful to build images unprivileged or when loop devices
+ are not available. If disabled, the image is always built using loop devices. If
+ auto, systemd-repart will build the image online if possible
+ and fall back to building the image offline if loop devices are not available or cannot be accessed
+ due to missing permissions.
+
+
diff --git a/src/partition/repart.c b/src/partition/repart.c
index 89f7211bfd..78bd06a537 100644
--- a/src/partition/repart.c
+++ b/src/partition/repart.c
@@ -152,6 +152,7 @@ static size_t arg_n_defer_partitions = 0;
static uint64_t arg_sector_size = 0;
static ImagePolicy *arg_image_policy = NULL;
static Architecture arg_architecture = _ARCHITECTURE_INVALID;
+static int arg_offline = -1;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
@@ -3253,13 +3254,17 @@ static int partition_target_prepare(
/* Loopback block devices are not only useful to turn regular files into block devices, but
* also to cut out sections of block devices into new block devices. */
- r = loop_device_make(whole_fd, O_RDWR, p->offset, size, 0, 0, LOCK_EX, &d);
- if (r < 0 && r != -ENOENT && !ERRNO_IS_PRIVILEGE(r))
- return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno);
- if (r >= 0) {
- t->loop = TAKE_PTR(d);
- *ret = TAKE_PTR(t);
- return 0;
+ if (arg_offline <= 0) {
+ r = loop_device_make(whole_fd, O_RDWR, p->offset, size, 0, 0, LOCK_EX, &d);
+ if (r < 0 && (arg_offline == 0 || (r != -ENOENT && !ERRNO_IS_PRIVILEGE(r))))
+ return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno);
+ if (r >= 0) {
+ t->loop = TAKE_PTR(d);
+ *ret = TAKE_PTR(t);
+ return 0;
+ }
+
+ log_debug_errno(r, "No access to loop devices, falling back to a regular file");
}
/* If we can't allocate a loop device, let's write to a regular file that we copy into the final
@@ -3267,8 +3272,6 @@ static int partition_target_prepare(
* reflinking support, we can take advantage of this and just reflink the result into the image.
*/
- log_debug_errno(r, "No access to loop devices, falling back to a regular file");
-
r = prepare_temporary_file(t, size);
if (r < 0)
return r;
@@ -5852,6 +5855,7 @@ static int help(void) {
" but don't populate them yet\n"
" --sector-size=SIZE Set the logical sector size for the image\n"
" --architecture=ARCH Set the generic architecture for the image\n"
+ " --offline=BOOL Whether to build the image offline\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
@@ -5894,6 +5898,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_SECTOR_SIZE,
ARG_SKIP_PARTITIONS,
ARG_ARCHITECTURE,
+ ARG_OFFLINE,
};
static const struct option options[] = {
@@ -5927,6 +5932,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "defer-partitions", required_argument, NULL, ARG_DEFER_PARTITIONS },
{ "sector-size", required_argument, NULL, ARG_SECTOR_SIZE },
{ "architecture", required_argument, NULL, ARG_ARCHITECTURE },
+ { "offline", required_argument, NULL, ARG_OFFLINE },
{}
};
@@ -6235,6 +6241,19 @@ static int parse_argv(int argc, char *argv[]) {
arg_architecture = r;
break;
+ case ARG_OFFLINE:
+ if (streq(optarg, "auto"))
+ arg_offline = -1;
+ else {
+ r = parse_boolean_argument("--offline=", optarg, NULL);
+ if (r < 0)
+ return r;
+
+ arg_offline = r;
+ }
+
+ break;
+
case '?':
return -EINVAL;
diff --git a/test/units/testsuite-58.sh b/test/units/testsuite-58.sh
index 521584892f..6ddb96d26e 100755
--- a/test/units/testsuite-58.sh
+++ b/test/units/testsuite-58.sh
@@ -96,17 +96,18 @@ testcase_basic() {
local loop volume
defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")"
- imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
chmod a+rx "$defs"
echo "*** 1. create an empty image ***"
- runas testuser systemd-repart --empty=create \
- --size=1G \
- --seed="$seed" \
- "$imgs/zzz"
+ systemd-repart --offline="$OFFLINE" \
+ --empty=create \
+ --size=1G \
+ --seed="$seed" \
+ "$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@@ -140,11 +141,13 @@ SizeMaxBytes=64M
PaddingMinBytes=92M
EOF
- runas testuser systemd-repart --definitions="$defs" \
- --dry-run=no \
- --seed="$seed" \
- --include-partitions=home,swap \
- "$imgs/zzz"
+ systemd-repart --offline="$OFFLINE" \
+ --definitions="$defs" \
+ --dry-run=no \
+ --seed="$seed" \
+ --include-partitions=home,swap \
+ --offline="$OFFLINE" \
+ "$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@@ -157,12 +160,13 @@ last-lba: 2097118
$imgs/zzz1 : start= 2048, size= 1775576, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\"
$imgs/zzz2 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\""
- runas testuser systemd-repart --definitions="$defs" \
- --dry-run=no \
- --seed="$seed" \
- --empty=force \
- --defer-partitions=home,root \
- "$imgs/zzz"
+ systemd-repart --offline="$OFFLINE" \
+ --definitions="$defs" \
+ --dry-run=no \
+ --seed="$seed" \
+ --empty=force \
+ --defer-partitions=home,root \
+ "$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@@ -174,10 +178,11 @@ first-lba: 2048
last-lba: 2097118
$imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\""
- runas testuser systemd-repart --definitions="$defs" \
- --dry-run=no \
- --seed="$seed" \
- "$imgs/zzz"
+ systemd-repart --offline="$OFFLINE" \
+ --definitions="$defs" \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@@ -210,10 +215,11 @@ EOF
echo "Label=ignored_label" >>"$defs/home.conf"
echo "UUID=b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" >>"$defs/home.conf"
- runas testuser systemd-repart --definitions="$defs" \
- --dry-run=no \
- --seed="$seed" \
- "$imgs/zzz"
+ systemd-repart --offline="$OFFLINE" \
+ --definitions="$defs" \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@@ -231,11 +237,12 @@ $imgs/zzz5 : start= 1908696, size= 188416, type=0FC63DAF-8483-4772-8E79
echo "*** 4. Resizing to 2G ***"
- runas testuser systemd-repart --definitions="$defs" \
- --size=2G \
- --dry-run=no \
- --seed="$seed" \
- "$imgs/zzz"
+ systemd-repart --offline="$OFFLINE" \
+ --definitions="$defs" \
+ --size=2G \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@@ -263,11 +270,12 @@ UUID=2a1d97e1d0a346cca26eadc643926617
CopyBlocks=$imgs/block-copy
EOF
- runas testuser systemd-repart --definitions="$defs" \
- --size=3G \
- --dry-run=no \
- --seed="$seed" \
- "$imgs/zzz"
+ systemd-repart --offline="$OFFLINE" \
+ --definitions="$defs" \
+ --size=3G \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@@ -299,15 +307,12 @@ CopyFiles=$defs:/def
SizeMinBytes=48M
EOF
- # CopyFiles will fail if we try to chown the target file to root.
- # Make the files owned by the user so that the invocation below works.
- chown testuser -R "$defs"
-
- runas testuser systemd-repart --definitions="$defs" \
- --size=auto \
- --dry-run=no \
- --seed="$seed" \
- "$imgs/zzz"
+ systemd-repart --offline="$OFFLINE" \
+ --definitions="$defs" \
+ --size=auto \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
@@ -350,7 +355,7 @@ testcase_dropin() {
local defs imgs output
defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")"
- imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
chmod a+rx "$defs"
@@ -374,11 +379,12 @@ EOF
Label=label2
EOF
- output=$(runas testuser systemd-repart --definitions="$defs" \
- --empty=create \
- --size=100M \
- --json=pretty \
- "$imgs/zzz")
+ output=$(systemd-repart --offline="$OFFLINE" \
+ --definitions="$defs" \
+ --empty=create \
+ --size=100M \
+ --json=pretty \
+ "$imgs/zzz")
diff -u <(echo "$output") - <= 512 and <= PAGE_SIZE, and
# must be powers of 2. Which leaves exactly four different ones to test on
# typical hardware