From a907590638aa1ffc6efaa4269f4e6f48641f9bd8 Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 29 Sep 2026 10:40:58 +1000 Subject: [PATCH 01/14] lkl/cptofs: clean up some error handling Close src fd on dst open failure. Close dst fd on chown failure. Signed-off-by: David Disseldorp --- tools/lkl/cptofs.c | 47 +++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/tools/lkl/cptofs.c b/tools/lkl/cptofs.c index aa11939c8097bf..75ceac8dd02568 100644 --- a/tools/lkl/cptofs.c +++ b/tools/lkl/cptofs.c @@ -111,6 +111,22 @@ static struct argp argp_cpfromfs = { static int searchdir(const char *fs_path, const char *path, const char *match, uid_t owner, gid_t group); +static void close_src(int fd) +{ + if (cptofs) + close(fd); + else + lkl_sys_close(fd); +} + +static void close_dst(int fd) +{ + if (cptofs) + lkl_sys_close(fd); + else + close(fd); +} + static int open_src(const char *path) { int fd; @@ -137,6 +153,12 @@ static int open_dst(const char *path, int mode, uid_t owner, gid_t group) else fd = open(path, O_RDWR | O_TRUNC | O_CREAT, mode); + if (fd < 0) { + fprintf(stderr, "unable to open file %s for writing: %s\n", + path, cptofs ? lkl_strerror(fd) : strerror(errno)); + return -1; + } + if (owner != (uid_t)-1 && group != (gid_t)-1) { if (cptofs) ret = lkl_sys_fchown(fd, owner, group); @@ -145,14 +167,11 @@ static int open_dst(const char *path, int mode, uid_t owner, gid_t group) if (ret) { fprintf(stderr, "unable to set owner/group on %s: %s\n", path, cptofs ? lkl_strerror(ret) : strerror(errno)); + close_dst(fd); return -1; } } - if (fd < 0) - fprintf(stderr, "unable to open file %s for writing: %s\n", - path, cptofs ? lkl_strerror(fd) : strerror(errno)); - if (cla.selinux && cptofs) { ret = lkl_sys_fsetxattr(fd, "security.selinux", cla.selinux, strlen(cla.selinux), 0); @@ -196,22 +215,6 @@ static int write_dst(int fd, char *buf, int len) return ret; } -static void close_src(int fd) -{ - if (cptofs) - close(fd); - else - lkl_sys_close(fd); -} - -static void close_dst(int fd) -{ - if (cptofs) - lkl_sys_close(fd); - else - close(fd); -} - static int copy_file(const char *src, const char *dst, int mode, uid_t owner, gid_t group) { long len, to_write, wrote; @@ -224,8 +227,10 @@ static int copy_file(const char *src, const char *dst, int mode, uid_t owner, gi return fd_src; fd_dst = open_dst(dst, mode, owner, group); - if (fd_dst < 0) + if (fd_dst < 0) { + close_src(fd_src); return fd_dst; + } do { len = read_src(fd_src, buf, sizeof(buf)); From e3e0553bf140a43afcab0a0e4e1ef3dd5f4509cf Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 29 Sep 2026 10:54:43 +1000 Subject: [PATCH 02/14] lkl/cptofs: fsync copy destination copy_file() doesn't check close() return values. Perform an explicit fsync() of the copy destination to catch any writeback errors. Signed-off-by: David Disseldorp --- tools/lkl/cptofs.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/tools/lkl/cptofs.c b/tools/lkl/cptofs.c index 75ceac8dd02568..75658c61f47bfd 100644 --- a/tools/lkl/cptofs.c +++ b/tools/lkl/cptofs.c @@ -215,6 +215,22 @@ static int write_dst(int fd, char *buf, int len) return ret; } +static int fsync_dst(int fd) +{ + int ret; + + if (cptofs) + ret = lkl_sys_fsync(fd); + else + ret = fsync(fd); + + if (ret < 0) + fprintf(stderr, "error syncing file: %s\n", + cptofs ? lkl_strerror(ret) : strerror(errno)); + + return ret; +} + static int copy_file(const char *src, const char *dst, int mode, uid_t owner, gid_t group) { long len, to_write, wrote; @@ -234,6 +250,10 @@ static int copy_file(const char *src, const char *dst, int mode, uid_t owner, gi do { len = read_src(fd_src, buf, sizeof(buf)); + if (len < 0) { + ret = len; + goto out; + } if (len > 0) { ptr = buf; @@ -251,12 +271,9 @@ static int copy_file(const char *src, const char *dst, int mode, uid_t owner, gi } while (to_write > 0); } - - if (len < 0) - ret = len; - } while (len > 0); + ret = fsync_dst(fd_dst); out: close_src(fd_src); close_dst(fd_dst); From cd687e0969de0f4e6e3c1c4844582d866b123a15 Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 29 Sep 2026 13:24:25 +1000 Subject: [PATCH 03/14] lkl/cptofs: return selinux attr error for cpfromfs selinux attrs provided by -s are only handled when the copy destination is LKL based (cptofs). Indicate this in the usage message and return an error if cpfromfs is provided -s instead of ignoring it. It may make sense to add corresponding host fsetxattr() calls, but the security namespace is privileged, which will likely cause issues. Signed-off-by: David Disseldorp --- tools/lkl/cptofs.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/lkl/cptofs.c b/tools/lkl/cptofs.c index 75658c61f47bfd..dfd2333b199a39 100644 --- a/tools/lkl/cptofs.c +++ b/tools/lkl/cptofs.c @@ -30,7 +30,7 @@ static struct argp_option options[] = { "path to the filesystem image - mandatory"}, {"owner", 'o', "int", 0, "owner of the destination files"}, {"group", 'g', "int", 0, "group of the destination files"}, - {"selinux", 's', "string", 0, "selinux attributes for destination"}, + {"selinux", 's', "string", 0, "selinux attribute for LKL destinations"}, {"mb", 'm', "int", 0, "amount of memory to allocate in MB (default: 100)"}, {0}, @@ -69,6 +69,10 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) cla->fsimg_path = arg; break; case 's': + if (!cptofs) { + fprintf(stderr, "-s is only supported with an LKL destination\n"); + return ARGP_ERR_UNKNOWN; + } cla->selinux = arg; break; case 'o': From ea9019ab43219a942151be9568e0d56ffe240c68 Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 29 Sep 2026 16:17:01 +1000 Subject: [PATCH 04/14] lkl/build: install a cpfromfs -> cptofs symlink cptofs behaves differently depending on whether argv[0] includes cptofs or not, i.e. when run as cpfromfs. install (from coreutils) resolves symlinks during binary installation, so add an explicit ln call to the install rule instead of adding cpfromfs to progs-y and reusing the existing build rule. Signed-off-by: David Disseldorp --- tools/lkl/Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/lkl/Makefile b/tools/lkl/Makefile index 7708058ab141f8..dc21e83d5be868 100644 --- a/tools/lkl/Makefile +++ b/tools/lkl/Makefile @@ -183,10 +183,14 @@ libraries_install: $(libs-y:%=$(OUTPUT)%$(SOSUF)) $(OUTPUT)liblkl.a install -d $(DESTDIR)$(LIBDIR) ; \ install -m 644 $^ $(DESTDIR)$(LIBDIR) +# install symlink for cptofs; argv[0] switches copy direction programs_install: $(progs-y:%=$(OUTPUT)%$(EXESUF)) $(call QUIET_INSTALL, programs) \ install -d $(DESTDIR)$(BINDIR) ; \ install -m 755 $^ $(DESTDIR)$(BINDIR) + $(Q)if [ -f $(DESTDIR)$(BINDIR)/cptofs$(EXESUF) ]; then \ + [ -e $(DESTDIR)$(BINDIR)/cpfromfs$(EXESUF) ] || ln -s cptofs$(EXESUF) $(DESTDIR)$(BINDIR)/cpfromfs$(EXESUF); \ + fi install: headers_install libraries_install programs_install From 5ba29df5e037447ee7d07b396e119a90c9ff0367 Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 29 Sep 2026 18:06:42 +1000 Subject: [PATCH 05/14] lkl/cptofs: propagate selinux setattr errors If the user explicitly requests an copy destination selinux attribute via -s, then we should fail if the fsetattr returns an error. Signed-off-by: David Disseldorp --- tools/lkl/cptofs.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/lkl/cptofs.c b/tools/lkl/cptofs.c index dfd2333b199a39..5381f17c24a761 100644 --- a/tools/lkl/cptofs.c +++ b/tools/lkl/cptofs.c @@ -179,9 +179,12 @@ static int open_dst(const char *path, int mode, uid_t owner, gid_t group) if (cla.selinux && cptofs) { ret = lkl_sys_fsetxattr(fd, "security.selinux", cla.selinux, strlen(cla.selinux), 0); - if (ret) + if (ret) { fprintf(stderr, "unable to set selinux attribute on %s: %s\n", path, lkl_strerror(ret)); + close_dst(fd); + return -1; + } } return fd; From 55cc40296a94a44d9fd94c4919f8993f6809aa70 Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 29 Sep 2026 18:13:57 +1000 Subject: [PATCH 06/14] lkl/cptofs: fix copy_file short write handling On short write, copy_file() currently attempts to write any remainder from a source buffer address *after* the full read length. I've not actually triggered this code-path, but it's a theoretical buffer overrun. Fix it by incrementing the buffer address by the amount written instead of the read amount. Link: https://github.com/lkl/linux/issues/646 Signed-off-by: David Disseldorp --- tools/lkl/cptofs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/lkl/cptofs.c b/tools/lkl/cptofs.c index 5381f17c24a761..759e9f09d7d65e 100644 --- a/tools/lkl/cptofs.c +++ b/tools/lkl/cptofs.c @@ -274,7 +274,7 @@ static int copy_file(const char *src, const char *dst, int mode, uid_t owner, gi } to_write -= wrote; - ptr += len; + ptr += wrote; } while (to_write > 0); } From b9b7c4e409ceccd8f24ac163ab34b0cf1f27c1de Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Wed, 30 Sep 2026 15:38:21 +1000 Subject: [PATCH 07/14] lkl/cptofs: fix mkdir/symlink_dst strerror output With cptofs the destination is LKL, so lkl_strerror(ret) should be used instead of strerror(errno). Vice-versa for cpfromfs. Signed-off-by: David Disseldorp --- tools/lkl/cptofs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/lkl/cptofs.c b/tools/lkl/cptofs.c index 759e9f09d7d65e..772596b139dca8 100644 --- a/tools/lkl/cptofs.c +++ b/tools/lkl/cptofs.c @@ -364,14 +364,14 @@ static int mkdir_dst(const char *path, unsigned int mode, uid_t owner, gid_t gro ret = chown(path, owner, group); if (ret) { fprintf(stderr, "unable to chown directory %s: %s\n", - path, cptofs ? strerror(errno) : lkl_strerror(ret)); + path, cptofs ? lkl_strerror(ret) : strerror(errno)); return ret; } } if (ret) fprintf(stderr, "unable to create directory %s: %s\n", - path, cptofs ? strerror(errno) : lkl_strerror(ret)); + path, cptofs ? lkl_strerror(ret) : strerror(errno)); return ret; } @@ -408,7 +408,7 @@ static int symlink_dst(const char *path, const char *target, uid_t owner, gid_t ret = lchown(path, owner, group); if (ret) { fprintf(stderr, "unable to chown symlink %s: %s\n", - path, cptofs ? strerror(errno) : lkl_strerror(ret)); + path, cptofs ? lkl_strerror(ret) : strerror(errno)); return ret; } } From 65025dc955d43cecc65eed56a819954c37248ac2 Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Wed, 30 Sep 2026 16:20:18 +1000 Subject: [PATCH 08/14] lkl/cptofs: don't chown if mkdir/symlink creaton fails mkdir_dst() already squashes EEXIST errors, so chown will continue to be triggered in that case. Signed-off-by: David Disseldorp --- tools/lkl/cptofs.c | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/tools/lkl/cptofs.c b/tools/lkl/cptofs.c index 772596b139dca8..a9720c6e00e943 100644 --- a/tools/lkl/cptofs.c +++ b/tools/lkl/cptofs.c @@ -357,22 +357,23 @@ static int mkdir_dst(const char *path, unsigned int mode, uid_t owner, gid_t gro if (ret < 0 && errno == EEXIST) ret = 0; } + + if (ret) { + fprintf(stderr, "unable to create directory %s: %s\n", + path, cptofs ? lkl_strerror(ret) : strerror(errno)); + return ret; + } + if (owner != (uid_t)-1 || group != (gid_t)-1) { if (cptofs) ret = lkl_sys_chown(path, owner, group); else ret = chown(path, owner, group); - if (ret) { + if (ret) fprintf(stderr, "unable to chown directory %s: %s\n", path, cptofs ? lkl_strerror(ret) : strerror(errno)); - return ret; - } } - if (ret) - fprintf(stderr, "unable to create directory %s: %s\n", - path, cptofs ? lkl_strerror(ret) : strerror(errno)); - return ret; } @@ -401,23 +402,23 @@ static int symlink_dst(const char *path, const char *target, uid_t owner, gid_t else ret = symlink(target, path); + if (ret) { + fprintf(stderr, "unable to symlink '%s' with target '%s': %s\n", + path, target, cptofs ? lkl_strerror(ret) : + strerror(errno)); + return ret; + } + if (owner != (uid_t)-1 || group != (gid_t)-1) { if (cptofs) ret = lkl_sys_fchownat(AT_FDCWD, path, owner, group, AT_SYMLINK_NOFOLLOW); else ret = lchown(path, owner, group); - if (ret) { + if (ret) fprintf(stderr, "unable to chown symlink %s: %s\n", path, cptofs ? lkl_strerror(ret) : strerror(errno)); - return ret; - } } - if (ret) - fprintf(stderr, "unable to symlink '%s' with target '%s': %s\n", - path, target, cptofs ? lkl_strerror(ret) : - strerror(errno)); - return ret; } From 206bdc7d6b25f2f6c3d0c43950cf5d0141c9dd76 Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Wed, 30 Sep 2026 22:21:03 +1000 Subject: [PATCH 09/14] lkl/tests: add basic cptofs coverage The new cptofs.sh wrapper attempts basic file and directory tree copies between lkl and host via cptofs and cpfromfs. Signed-off-by: David Disseldorp --- tools/lkl/tests/cptofs.sh | 84 +++++++++++++++++++++++++++++++++++++++ tools/lkl/tests/run.py | 12 ++++-- 2 files changed, 92 insertions(+), 4 deletions(-) create mode 100755 tools/lkl/tests/cptofs.sh diff --git a/tools/lkl/tests/cptofs.sh b/tools/lkl/tests/cptofs.sh new file mode 100755 index 00000000000000..6ba49f45c2b937 --- /dev/null +++ b/tools/lkl/tests/cptofs.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-only + +script_dir=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd) + +source $script_dir/test.sh +source $script_dir/fs.sh + +_usage() +{ + echo "usage: cptofs.sh -t " + exit 1 +} + +cptofs_file() +{ + set -e + + local tdir="$(lkl_test_cmd mktemp -d)" + local cptofs=("${script_dir}/../cptofs" -p -t "$fstype" -i "$file") + local cpfromfs=("${tdir}/cpfromfs" -p -t "$fstype" -i "$file") + lkl_test_cmd ln -s "${script_dir}/../cptofs" "${tdir}/cpfromfs" + + lkl_test_cmd mkdir -p "${tdir}/round-trip" + lkl_test_cmd echo "data" > ${tdir}/data + lkl_test_cmd "${cptofs[@]}" "${tdir}/data" / + lkl_test_cmd "${cpfromfs[@]}" /data "${tdir}/round-trip/" + lkl_test_cmd diff "${tdir}/data" "${tdir}/round-trip/data" + lkl_test_cmd rm -rf "$tdir" +} + +cptofs_tree() +{ + set -e + + local tdir="$(lkl_test_cmd mktemp -d)" + local cptofs=("${script_dir}/../cptofs" -p -t "$fstype" -i "$file") + local cpfromfs=("${tdir}/cpfromfs" -p -t "$fstype" -i "$file") + lkl_test_cmd ln -s "${script_dir}/../cptofs" "${tdir}/cpfromfs" + + lkl_test_cmd mkdir -p "${tdir}/round-trip" + lkl_test_cmd mkdir -p "${tdir}/tree/full/of/stuff" + lkl_test_cmd echo "data" > "${tdir}/tree/data" + lkl_test_cmd echo "more" > "${tdir}/tree/full/more" + lkl_test_cmd echo "here" > "${tdir}/tree/full/of/here" + lkl_test_cmd "${cptofs[@]}" "${tdir}/tree" / + lkl_test_cmd "${cpfromfs[@]}" /tree "${tdir}/round-trip" + lkl_test_cmd diff -r "${tdir}/tree" "${tdir}/round-trip/tree" + lkl_test_cmd rm -rf "$tdir" +} + +if [ "$1" = "-t" ]; then + shift + fstype=$1 + shift + [ -z "$fstype" ] && _usage + [ -n "$1" ] && _usage +else + _usage +fi + +if [ "$LKL_HOST_CONFIG_ARGP" != "y" ]; then + lkl_test_plan 0 "cptofs $fstype" + echo "cptofs not available" + exit 0 +fi + +if [ -z $(which mkfs.$fstype) ]; then + lkl_test_plan 0 "cptofs $fstype" + echo "no mkfs.$fstype command" + exit 0 +fi + +if [ -z $(which diff) ]; then + lkl_test_plan 0 "cptofs $fstype" + echo "no diff command" + exit 0 +fi + +lkl_test_plan 4 "disk $fstype" +lkl_test_run 1 prepfsimg $fstype +lkl_test_run 2 cptofs_file +lkl_test_run 3 cptofs_tree +lkl_test_run 4 cleanfsimg diff --git a/tools/lkl/tests/run.py b/tools/lkl/tests/run.py index bdea6225df73fd..ef143ffcb9bd7d 100755 --- a/tools/lkl/tests/run.py +++ b/tools/lkl/tests/run.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License +# SPDX-License-Identifier: GPL-2.0-only # # Author: Octavian Purdila # @@ -63,6 +60,13 @@ def end(self, obj): 'lklfuse.sh -t btrfs', 'lklfuse.sh -t vfat', 'lklfuse.sh -t xfs', + 'cptofs.sh -t ext4', + 'cptofs.sh -t btrfs', + 'cptofs.sh -t vfat', + # FIXME: seeing virtio-blk I/O errors with xfs + # I/O error, dev vda, sector 307246 op 0x1:(WRITE) flags 0x5800 phys_seg 1 prio class 0 + # I/O error, dev vda, sector 309806 op 0x1:(WRITE) flags 0x1800 phys_seg 1 prio class 0 + 'cptofs.sh -t xfs', 'config', 'hijack-test.sh', 'LKL_HIJACK_ZPOLINE=1 hijack-test.sh', From 424131873d7ee58e847c00d2beb6223ba49b2e9d Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 6 Oct 2026 23:33:01 +1100 Subject: [PATCH 10/14] lkl/cptofs: add explicit ro option for read-only img open The existing behavior of opening the image read-only for cpfromfs can cause XFS mount failures, e.g. if log replay is required. The existing cptofs.sh -t xfs test cases trigger this. Signed-off-by: David Disseldorp --- tools/lkl/cptofs.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/lkl/cptofs.c b/tools/lkl/cptofs.c index a9720c6e00e943..b954d1698cbf20 100644 --- a/tools/lkl/cptofs.c +++ b/tools/lkl/cptofs.c @@ -33,6 +33,9 @@ static struct argp_option options[] = { {"selinux", 's', "string", 0, "selinux attribute for LKL destinations"}, {"mb", 'm', "int", 0, "amount of memory to allocate in MB (default: 100)"}, + /* image ro is separate to mount flags. cpfromfs sets LKL_MS_RDONLY */ + {"ro", 'r', 0, 0, + "open image read-only; may cause failures due to e.g. XFS log replay"}, {0}, }; @@ -47,6 +50,7 @@ static struct cl_args { uid_t owner; gid_t group; int mb; + int ro; } cla; static int cptofs; @@ -84,6 +88,9 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) case 'm': cla->mb = atoi(arg); break; + case 'r': + cla->ro = 1; + break; case ARGP_KEY_ARG: // Capture all remaining arguments in our paths array and stop // parsing here. We treat the last one as the destination and @@ -658,7 +665,7 @@ int copy_one(const char *src, const char *mpoint, const char *dst, uid_t owner, int main(int argc, char **argv) { - struct lkl_disk disk; + struct lkl_disk disk = {}; long ret, umount_ret; int i; char mpoint[32]; @@ -681,7 +688,7 @@ int main(int argc, char **argv) if (!cla.printk) lkl_host_ops.print = NULL; - disk.fd = open(cla.fsimg_path, cptofs ? O_RDWR : O_RDONLY); + disk.fd = open(cla.fsimg_path, cla.ro ? O_RDONLY : O_RDWR); if (disk.fd < 0) { fprintf(stderr, "can't open fsimg %s: %s\n", cla.fsimg_path, strerror(errno)); From 2b08b8342bb74047c967c1468776d034d5fe02bc Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 6 Oct 2026 23:39:26 +1100 Subject: [PATCH 11/14] Revert "cptofs: remount as read-only prior to exiting" This reverts commit dd6a274d00b1f911cdd64f28ae60ac6c7855c437. This remount isn't enough to avoid a dirty journal on unmount. With cpfromfs now mounting read-write by default, it should be possible to remove this attempted workaround. If a clean journal is really needed, then we could consider going down the fsfreeze path of other utilities such as Dracut. Signed-off-by: David Disseldorp --- tools/lkl/cptofs.c | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/tools/lkl/cptofs.c b/tools/lkl/cptofs.c index b954d1698cbf20..7f90c14d5877f6 100644 --- a/tools/lkl/cptofs.c +++ b/tools/lkl/cptofs.c @@ -734,27 +734,6 @@ int main(int argc, char **argv) break; } - /* FIXME: Remouting as readonly seems to be required to make sure the filessytem is cleanly umounted. Otherwise the journal will be still dirty... */ - char dev_str[] = { "/dev/xxxxxxxx" }; - - snprintf(dev_str, sizeof(dev_str), "/dev/%08x", disk_id); - for (;;) { - ret = lkl_sys_mount(dev_str, mpoint, (char *)cla.fsimg_type, LKL_MS_RDONLY|LKL_MS_REMOUNT, NULL); - if (ret == 0) - break; - if (ret == -EBUSY) { - struct __lkl__kernel_timespec ts = { - .tv_sec = 1, - .tv_nsec = 0, - }; - lkl_sys_nanosleep(&ts, NULL); - continue; - } else if (ret < 0) { - fprintf(stderr, "cannot remount mount disk read-only: %s\n", lkl_strerror(ret)); - break; - } - } - umount_ret = lkl_umount_dev(disk_id, cla.part, 0, 1000); if (ret == 0) ret = umount_ret; From bdba01906dd3bb593afa156283c03db333ea73db Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 6 Oct 2026 23:46:57 +1100 Subject: [PATCH 12/14] lkl/tests: add cptofs --ro case Ensure that write fails if the image is opened read-only via the new --ro cptofs/cpfromfs parameter. Signed-off-by: David Disseldorp --- tools/lkl/tests/cptofs.sh | 6 ++++++ tools/lkl/tests/run.py | 3 --- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/lkl/tests/cptofs.sh b/tools/lkl/tests/cptofs.sh index 6ba49f45c2b937..c9a236ba250965 100755 --- a/tools/lkl/tests/cptofs.sh +++ b/tools/lkl/tests/cptofs.sh @@ -16,6 +16,7 @@ cptofs_file() { set -e + local ret=$TEST_FAILURE local tdir="$(lkl_test_cmd mktemp -d)" local cptofs=("${script_dir}/../cptofs" -p -t "$fstype" -i "$file") local cpfromfs=("${tdir}/cpfromfs" -p -t "$fstype" -i "$file") @@ -26,7 +27,12 @@ cptofs_file() lkl_test_cmd "${cptofs[@]}" "${tdir}/data" / lkl_test_cmd "${cpfromfs[@]}" /data "${tdir}/round-trip/" lkl_test_cmd diff "${tdir}/data" "${tdir}/round-trip/data" + + set +e + lkl_test_cmd echo "read-only should cause cptofs failure" > ${tdir}/new + lkl_test_cmd "${cptofs[@]}" "-ro" "${tdir}/new" / || ret=$TEST_SUCCESS lkl_test_cmd rm -rf "$tdir" + return $ret } cptofs_tree() diff --git a/tools/lkl/tests/run.py b/tools/lkl/tests/run.py index ef143ffcb9bd7d..3aa3807c2eee87 100755 --- a/tools/lkl/tests/run.py +++ b/tools/lkl/tests/run.py @@ -63,9 +63,6 @@ def end(self, obj): 'cptofs.sh -t ext4', 'cptofs.sh -t btrfs', 'cptofs.sh -t vfat', - # FIXME: seeing virtio-blk I/O errors with xfs - # I/O error, dev vda, sector 307246 op 0x1:(WRITE) flags 0x5800 phys_seg 1 prio class 0 - # I/O error, dev vda, sector 309806 op 0x1:(WRITE) flags 0x1800 phys_seg 1 prio class 0 'cptofs.sh -t xfs', 'config', 'hijack-test.sh', From b210f600364bedd3cdaadd7aa1906e998d6c594e Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Wed, 7 Oct 2026 23:38:11 +1100 Subject: [PATCH 13/14] lkl/virtio_blk: add lkl_disk_add_feature Allow for passing of virtio feature flags. Signed-off-by: David Disseldorp --- tools/lkl/include/lkl.h | 12 ++++++++++++ tools/lkl/lib/virtio_blk.c | 10 +++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tools/lkl/include/lkl.h b/tools/lkl/include/lkl.h index eaf6c4ceebc929..58c8a703fd5f30 100644 --- a/tools/lkl/include/lkl.h +++ b/tools/lkl/include/lkl.h @@ -375,6 +375,18 @@ struct lkl_disk { */ int lkl_disk_add(struct lkl_disk *disk); +/** + * lkl_disk_add_feature - add a new disk with feature bits provided + * + * This is the same as lkl_disk_add() except that it allows for virtio_blk + * feature bits to provided by the caller. + * + * @disk - the host disk handle + * @virtio_blk_features - VIRTIO_BLK_F_ flags + * @returns a disk id (0 is valid) or a strictly negative value in case of error + */ +int lkl_disk_add_feature(struct lkl_disk *disk, uint64_t virtio_blk_features); + /** * lkl_disk_remove - remove a disk * diff --git a/tools/lkl/lib/virtio_blk.c b/tools/lkl/lib/virtio_blk.c index d8bf38bf8eddc3..88385df1e259c8 100644 --- a/tools/lkl/lib/virtio_blk.c +++ b/tools/lkl/lib/virtio_blk.c @@ -67,8 +67,7 @@ static struct virtio_dev_ops blk_ops = { .enqueue = blk_enqueue, }; - -int lkl_disk_add(struct lkl_disk *disk) +int lkl_disk_add_feature(struct lkl_disk *disk, uint64_t virtio_blk_features) { struct virtio_blk_dev *dev; unsigned long long capacity; @@ -82,7 +81,7 @@ int lkl_disk_add(struct lkl_disk *disk) dev->dev.device_id = LKL_VIRTIO_ID_BLOCK; dev->dev.vendor_id = 0; - dev->dev.device_features = 0; + dev->dev.device_features = virtio_blk_features; dev->dev.config_gen = 0; dev->dev.config_data = &dev->config; dev->dev.config_len = sizeof(dev->config); @@ -112,6 +111,11 @@ int lkl_disk_add(struct lkl_disk *disk) return ret; } +int lkl_disk_add(struct lkl_disk *disk) +{ + return lkl_disk_add_feature(disk, 0); +} + int lkl_disk_remove(struct lkl_disk disk) { struct virtio_blk_dev *dev; From fafdd1a9db601376bd0752f211f09e3dfe8d1f73 Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Wed, 7 Oct 2026 23:40:09 +1100 Subject: [PATCH 14/14] lkl/cptofs: use LKL_VIRTIO_BLK_F_RO for read-only disks This ensures that the lkl guest block layer properly tracks the device as read-only, instead of passing write requests through to host, which subsequently fail with EBADF. Signed-off-by: David Disseldorp --- tools/lkl/cptofs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/lkl/cptofs.c b/tools/lkl/cptofs.c index 7f90c14d5877f6..c629ab501b4993 100644 --- a/tools/lkl/cptofs.c +++ b/tools/lkl/cptofs.c @@ -704,7 +704,7 @@ int main(int argc, char **argv) goto out_close; } - ret = lkl_disk_add(&disk); + ret = lkl_disk_add_feature(&disk, cla.ro ? 1<