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 diff --git a/tools/lkl/cptofs.c b/tools/lkl/cptofs.c index aa11939c8097bf..c629ab501b4993 100644 --- a/tools/lkl/cptofs.c +++ b/tools/lkl/cptofs.c @@ -30,9 +30,12 @@ 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)"}, + /* 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; @@ -69,6 +73,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': @@ -80,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 @@ -111,6 +122,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 +164,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,20 +178,20 @@ 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); - 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; @@ -196,20 +229,20 @@ static int write_dst(int fd, char *buf, int len) return ret; } -static void close_src(int fd) +static int fsync_dst(int fd) { - if (cptofs) - close(fd); - else - lkl_sys_close(fd); -} + int ret; -static void close_dst(int fd) -{ if (cptofs) - lkl_sys_close(fd); + ret = lkl_sys_fsync(fd); else - close(fd); + 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) @@ -224,11 +257,17 @@ 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)); + if (len < 0) { + ret = len; + goto out; + } if (len > 0) { ptr = buf; @@ -242,16 +281,13 @@ 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); } - - if (len < 0) - ret = len; - } while (len > 0); + ret = fsync_dst(fd_dst); out: close_src(fd_src); close_dst(fd_dst); @@ -328,22 +364,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 ? strerror(errno) : lkl_strerror(ret)); - return ret; - } + path, cptofs ? lkl_strerror(ret) : strerror(errno)); } - if (ret) - fprintf(stderr, "unable to create directory %s: %s\n", - path, cptofs ? strerror(errno) : lkl_strerror(ret)); - return ret; } @@ -372,23 +409,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 ? strerror(errno) : lkl_strerror(ret)); - return ret; - } + path, cptofs ? lkl_strerror(ret) : strerror(errno)); } - if (ret) - fprintf(stderr, "unable to symlink '%s' with target '%s': %s\n", - path, target, cptofs ? lkl_strerror(ret) : - strerror(errno)); - return ret; } @@ -628,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]; @@ -651,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)); @@ -667,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<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; diff --git a/tools/lkl/tests/cptofs.sh b/tools/lkl/tests/cptofs.sh new file mode 100755 index 00000000000000..c9a236ba250965 --- /dev/null +++ b/tools/lkl/tests/cptofs.sh @@ -0,0 +1,90 @@ +#!/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 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") + 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" + + 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() +{ + 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..3aa3807c2eee87 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,10 @@ 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', + 'cptofs.sh -t xfs', 'config', 'hijack-test.sh', 'LKL_HIJACK_ZPOLINE=1 hijack-test.sh',