repart: make COW behavior configurable
systemd-repart currently forces newly created image files into NOCOW mode. That prevents files from being reflinked into the image, making image builds slower and increasing their disk usage on filesystems that support cloning. Add a tristate --cow= option. By default, leave the filesystem or parent directory COW policy unchanged. With --cow=yes, explicitly enable COW; with --cow=no, retain the previous behavior of forcing NOCOW. Add XO_COW as the counterpart to XO_NOCOW so xopenat_full() applies either policy while retaining its normal creation-error cleanup. Document the new option and extend TEST-58-REPART to verify inherited COW and NOCOW policies as well as explicit COW and NOCOW overrides. Compare the unset behavior with the filesystem default so the test also works on nodatacow mounts, and skip it when the inode attribute is unsupported. Signed-off-by: Daan De Meyer <daan@amutable.com>
This commit is contained in:
committed by
Lennart Poettering
parent
d153b49473
commit
28b1e4ea16
@@ -244,6 +244,18 @@
|
||||
<xi:include href="version-info.xml" xpointer="v246"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--cow=<replaceable>BOOL</replaceable></option></term>
|
||||
|
||||
<listitem><para>Controls copy-on-write for newly created image files on filesystems that support it.
|
||||
If true, copy-on-write is enabled; if false, it is disabled. If set to <literal>auto</literal>, the
|
||||
filesystem or parent directory default is left unchanged. Copy-on-write allows files to be reflinked
|
||||
into the image, reducing both build time and disk usage, but may result in fragmentation for images
|
||||
subject to frequent random writes. Defaults to <literal>auto</literal>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--factory-reset=</option></term>
|
||||
|
||||
|
||||
+7
-5
@@ -1183,12 +1183,13 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
|
||||
/* Sockets cannot be open()ed, only pinned via O_PATH. */
|
||||
assert(!FLAGS_SET(xopen_flags, XO_SOCKET) || FLAGS_SET(open_flags, O_PATH));
|
||||
/* XO_TRIGGER_AUTOMOUNT requires O_PATH and does not support creating inodes. XO_SUBVOLUME
|
||||
* requires O_CREAT, and XO_NOCOW needs a writable fd for its chattr ioctl, so neither is
|
||||
* requires O_CREAT, and XO_COW/XO_NOCOW need a writable fd for their chattr ioctl, so none are
|
||||
* compatible with XO_TRIGGER_AUTOMOUNT. */
|
||||
assert(!FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) ||
|
||||
(FLAGS_SET(open_flags, O_PATH) && !FLAGS_SET(open_flags, O_CREAT)));
|
||||
assert(!(FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) && FLAGS_SET(xopen_flags, XO_SUBVOLUME)));
|
||||
assert(!(FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) && FLAGS_SET(xopen_flags, XO_NOCOW)));
|
||||
assert(!(FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) && (xopen_flags & (XO_COW|XO_NOCOW))));
|
||||
assert((xopen_flags & (XO_COW|XO_NOCOW)) != (XO_COW|XO_NOCOW));
|
||||
|
||||
/* Don't specify an access mode if you want auto mode. */
|
||||
assert(!FLAGS_SET(xopen_flags, XO_AUTO_RW_RO) || (open_flags & O_ACCMODE_STRICT) == 0);
|
||||
@@ -1202,7 +1203,8 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
|
||||
*
|
||||
* • If the path is specified NULL or empty, behaves like fd_reopen().
|
||||
*
|
||||
* • If XO_NOCOW is specified will turn on the NOCOW btrfs flag on the file, if available.
|
||||
* • If XO_COW or XO_NOCOW is specified will turn off or on the NOCOW btrfs flag on the file, if
|
||||
* available.
|
||||
*
|
||||
* • if XO_REGULAR is specified will return an error if inode is not a regular file.
|
||||
*
|
||||
@@ -1436,8 +1438,8 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(xopen_flags, XO_NOCOW)) {
|
||||
r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL);
|
||||
if (xopen_flags & (XO_COW|XO_NOCOW)) {
|
||||
r = chattr_fd(fd, FLAGS_SET(xopen_flags, XO_NOCOW) ? FS_NOCOW_FL : 0, FS_NOCOW_FL);
|
||||
if (r < 0 && !ERRNO_IS_IOCTL_NOT_SUPPORTED(r))
|
||||
goto error;
|
||||
}
|
||||
|
||||
+5
-4
@@ -112,10 +112,11 @@ typedef enum XOpenFlags {
|
||||
XO_LABEL = 1 << 0, /* When creating: relabel */
|
||||
XO_SUBVOLUME = 1 << 1, /* When creating as directory: make it a subvolume */
|
||||
XO_NOCOW = 1 << 2, /* Enable NOCOW mode after opening */
|
||||
XO_REGULAR = 1 << 3, /* Fail if the inode is not a regular file */
|
||||
XO_SOCKET = 1 << 4, /* Fail if the inode is not a socket */
|
||||
XO_TRIGGER_AUTOMOUNT = 1 << 5, /* Trigger automounts via open_tree(). Requires O_PATH. */
|
||||
XO_AUTO_RW_RO = 1 << 6, /* Open in O_RDWR mode if possible, O_RDONLY if not */
|
||||
XO_COW = 1 << 3, /* Enable COW mode after opening */
|
||||
XO_REGULAR = 1 << 4, /* Fail if the inode is not a regular file */
|
||||
XO_SOCKET = 1 << 5, /* Fail if the inode is not a socket */
|
||||
XO_TRIGGER_AUTOMOUNT = 1 << 6, /* Trigger automounts via open_tree(). Requires O_PATH. */
|
||||
XO_AUTO_RW_RO = 1 << 7, /* Open in O_RDWR mode if possible, O_RDONLY if not */
|
||||
} XOpenFlags;
|
||||
|
||||
int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode);
|
||||
|
||||
+14
-1
@@ -215,6 +215,7 @@ static char *arg_generate_crypttab = NULL;
|
||||
static Set *arg_verity_settings = NULL;
|
||||
static bool arg_relax_copy_block_security = false;
|
||||
static bool arg_varlink = false;
|
||||
static int arg_cow = -1;
|
||||
static bool arg_eltorito = false;
|
||||
static char *arg_eltorito_system = NULL;
|
||||
static char *arg_eltorito_volume = NULL;
|
||||
@@ -10232,6 +10233,13 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
return r;
|
||||
break;
|
||||
|
||||
OPTION_LONG("cow", "BOOL|auto",
|
||||
"Whether to enable copy-on-write for newly created image files"):
|
||||
r = parse_tristate_argument_with_auto("--cow=", opts.arg, &arg_cow);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
OPTION_LONG("sector-size", "SIZE",
|
||||
"Set the logical sector size for the image"):
|
||||
r = parse_sector_size(opts.arg, &arg_sector_size);
|
||||
@@ -11018,7 +11026,12 @@ static int find_root(Context *context) {
|
||||
if (!s)
|
||||
return log_oom();
|
||||
|
||||
fd = xopenat_full(AT_FDCWD, arg_node, open_flags|O_CREAT|O_EXCL|O_NOFOLLOW, XO_NOCOW, 0666);
|
||||
fd = xopenat_full(
|
||||
AT_FDCWD,
|
||||
arg_node,
|
||||
open_flags|O_CREAT|O_EXCL|O_NOFOLLOW,
|
||||
arg_cow < 0 ? 0 : (arg_cow > 0 ? XO_COW : XO_NOCOW),
|
||||
0666);
|
||||
if (fd < 0)
|
||||
return log_error_errno(fd, "Failed to create '%s': %m", arg_node);
|
||||
|
||||
|
||||
@@ -94,6 +94,108 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
testcase_cow() {
|
||||
local attrs cow_image default_nocow defs image imgs nocow_image probe
|
||||
|
||||
defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")"
|
||||
imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")"
|
||||
# shellcheck disable=SC2064
|
||||
trap "rm -rf '$defs' '$imgs'" RETURN
|
||||
|
||||
# Skip the checks entirely if the underlying filesystem does not support the attribute.
|
||||
if ! chattr -C "$imgs"; then
|
||||
echo "NOCOW is not supported on $imgs, skipping tests"
|
||||
return
|
||||
fi
|
||||
|
||||
probe="$imgs/probe"
|
||||
touch "$probe"
|
||||
if ! chattr +C "$probe"; then
|
||||
echo "NOCOW is not supported on $imgs, skipping tests"
|
||||
return
|
||||
fi
|
||||
if ! chattr -C "$probe"; then
|
||||
echo "COW is not supported on $imgs, skipping tests"
|
||||
return
|
||||
fi
|
||||
rm "$probe"
|
||||
|
||||
chattr +C "$imgs"
|
||||
|
||||
image="$imgs/inherit-nocow.raw"
|
||||
systemd-repart --offline="$OFFLINE" \
|
||||
--definitions="$defs" \
|
||||
--empty=create \
|
||||
--size=16M \
|
||||
--cow=auto \
|
||||
--dry-run=no \
|
||||
"$image"
|
||||
|
||||
attrs="$(lsattr -d -- "$image")"
|
||||
assert_neq "$attrs" ""
|
||||
read -r attrs _ <<<"$attrs"
|
||||
assert_in "C" "$attrs"
|
||||
|
||||
cow_image="$imgs/cow.raw"
|
||||
systemd-repart --offline="$OFFLINE" \
|
||||
--definitions="$defs" \
|
||||
--empty=create \
|
||||
--size=16M \
|
||||
--cow=yes \
|
||||
--dry-run=no \
|
||||
"$cow_image"
|
||||
|
||||
attrs="$(lsattr -d -- "$cow_image")"
|
||||
assert_neq "$attrs" ""
|
||||
read -r attrs _ <<<"$attrs"
|
||||
assert_not_in "C" "$attrs"
|
||||
|
||||
chattr -C "$imgs"
|
||||
|
||||
probe="$imgs/probe"
|
||||
touch "$probe"
|
||||
attrs="$(lsattr -d -- "$probe")"
|
||||
assert_neq "$attrs" ""
|
||||
read -r attrs _ <<<"$attrs"
|
||||
if [[ "$attrs" == *C* ]]; then
|
||||
default_nocow=1
|
||||
else
|
||||
default_nocow=0
|
||||
fi
|
||||
rm "$probe"
|
||||
|
||||
image="$imgs/inherit-cow.raw"
|
||||
systemd-repart --offline="$OFFLINE" \
|
||||
--definitions="$defs" \
|
||||
--empty=create \
|
||||
--size=16M \
|
||||
--dry-run=no \
|
||||
"$image"
|
||||
|
||||
attrs="$(lsattr -d -- "$image")"
|
||||
assert_neq "$attrs" ""
|
||||
read -r attrs _ <<<"$attrs"
|
||||
if (( default_nocow )); then
|
||||
assert_in "C" "$attrs"
|
||||
else
|
||||
assert_not_in "C" "$attrs"
|
||||
fi
|
||||
|
||||
nocow_image="$imgs/nocow.raw"
|
||||
systemd-repart --offline="$OFFLINE" \
|
||||
--definitions="$defs" \
|
||||
--empty=create \
|
||||
--size=16M \
|
||||
--cow=no \
|
||||
--dry-run=no \
|
||||
"$nocow_image"
|
||||
|
||||
attrs="$(lsattr -d -- "$nocow_image")"
|
||||
assert_neq "$attrs" ""
|
||||
read -r attrs _ <<<"$attrs"
|
||||
assert_in "C" "$attrs"
|
||||
}
|
||||
|
||||
testcase_basic() {
|
||||
local defs imgs output
|
||||
local loop volume
|
||||
|
||||
Reference in New Issue
Block a user