diff --git a/Documentation/lkl/lklfuse.rst b/Documentation/lkl/lklfuse.rst index f3ac736473de1e..a519644c90892c 100644 --- a/Documentation/lkl/lklfuse.rst +++ b/Documentation/lkl/lklfuse.rst @@ -39,7 +39,9 @@ OPTIONS -o part=parition mount . --o ro open file read-only. +-o ro open block-device read-only. + +-o lock= only mount after taking an exclusive lock on . -o opts=options Linux kernel mount (use \\ to escape , and =). diff --git a/tools/lkl/lklfuse.c b/tools/lkl/lklfuse.c index cde7f4351f970d..ac40f16b2fc387 100644 --- a/tools/lkl/lklfuse.c +++ b/tools/lkl/lklfuse.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,7 @@ struct lklfuse { const char *log; const char *type; const char *opts; + const char *lock; struct lkl_disk disk; int disk_id; int part; @@ -46,6 +48,7 @@ static struct fuse_opt lklfuse_opts[] = { LKLFUSE_OPT("mb=%d", mb, 0), LKLFUSE_OPT("opts=%s", opts, 0), LKLFUSE_OPT("part=%d", part, 0), + LKLFUSE_OPT("lock=%s", lock, 0), FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP), FUSE_OPT_KEY("-V", KEY_VERSION), @@ -58,7 +61,7 @@ static struct fuse_opt lklfuse_opts[] = { static void usage(void) { printf( -"usage: lklfuse file mountpoint [options]\n" +"usage: lklfuse block-device mountpoint [options]\n" "\n" "general options:\n" " -o opt,[opt...] mount options\n" @@ -70,7 +73,8 @@ static void usage(void) " -o type=fstype filesystem type\n" " -o mb=memory amount of memory to allocate in MB (default: 64)\n" " -o part=parition partition to mount\n" -" -o ro open file read-only\n" +" -o ro open block-device read-only\n" +" -o lock=FILE only mount after taking an exclusive lock on FILE\n" " -o opts=options mount options (use \\ to escape , and =)\n" ); } @@ -791,7 +795,7 @@ int main(int argc, char **argv) struct fuse_cmdline_opts cli_opts; struct fuse *fuse; struct stat st; - int ret; + int ret, lockfd = -1; if (fuse_opt_parse(&args, &lklfuse, lklfuse_opts, lklfuse_opt_proc)) return 1; @@ -801,6 +805,23 @@ int main(int argc, char **argv) return 1; } + if (lklfuse.lock) { + lockfd = open(lklfuse.lock, O_RDWR | O_CREAT, 0644); + if (lockfd < 0) { + fprintf(stderr, "failed to open %s: %s\n", + lklfuse.lock, strerror(errno)); + return 1; + } + + ret = flock(lockfd, LOCK_EX | LOCK_NB); + if (ret < 0) { + fprintf(stderr, "unable to exclusively lock %s: %s\n", + lklfuse.lock, strerror(errno)); + return 2; + } + /* lock dropped when lockfd is closed on program exit */ + } + if (fuse_parse_cmdline(&args, &cli_opts)) return 1; diff --git a/tools/lkl/systemd/lklfuse-mount@.service b/tools/lkl/systemd/lklfuse-mount@.service index abbd3afa9f4b58..ac13b4b2c79388 100644 --- a/tools/lkl/systemd/lklfuse-mount@.service +++ b/tools/lkl/systemd/lklfuse-mount@.service @@ -6,6 +6,7 @@ Requires=modprobe@fuse.service [Service] RuntimeDirectory=lklfuse-%i +StateDirectory=lklfuse/fsid-mutex # The "allow_other" mount option permits fuse mount access by users other than # the lklfuse user, and requires a "user_allow_other" setting in fuse3.conf Environment=LKLFUSE_ARGS="-s -oallow_other" @@ -18,8 +19,11 @@ EnvironmentFile=-/etc/lklfuse.conf # run as unprivileged user User=lklfuse Group=lklfuse -ExecCondition=/bin/bash -c "udevadm info -q env -x --property=ID_FS_TYPE -n \"%I\" > ${RUNTIME_DIRECTORY}/udev.env" -ExecStart=/bin/bash -c ". ${RUNTIME_DIRECTORY}/udev.env; rm ${RUNTIME_DIRECTORY}/udev.env; /usr/bin/lklfuse -f -ofsname=\"/dev/%I\",subtype=\"lkl.$ID_FS_TYPE\",type=\"$ID_FS_TYPE\" $LKLFUSE_ARGS \"/dev/%I\" $RUNTIME_DIRECTORY" +ExecCondition=/bin/bash -xc "udevadm info -q env -x --property=ID_FS_TYPE,ID_FS_UUID -n \"%I\" > ${RUNTIME_DIRECTORY}/udev.env" +# Use an ID_FS_UUID based lock file to avoid duplicate mounts. +# If udev doesn't provide an id then use a static noid path, ensuring lock +# conflict with any other id-less mount. +ExecStart=/bin/bash -xc ". ${RUNTIME_DIRECTORY}/udev.env; rm ${RUNTIME_DIRECTORY}/udev.env; /usr/bin/lklfuse -f -ofsname=\"/dev/%I\",subtype=\"lkl.$ID_FS_TYPE\",type=\"$ID_FS_TYPE\",lock=\"${STATE_DIRECTORY}/${ID_FS_UUID:-noid}\" $LKLFUSE_ARGS \"/dev/%I\" ${RUNTIME_DIRECTORY}" [Install] WantedBy=default.target diff --git a/tools/lkl/tests/lklfuse.sh b/tools/lkl/tests/lklfuse.sh index 22495322dd81be..2961aace8e3369 100755 --- a/tools/lkl/tests/lklfuse.sh +++ b/tools/lkl/tests/lklfuse.sh @@ -8,7 +8,6 @@ cleanup() { set -e - sleep 1 if type -P fusermount3 > /dev/null; then fusermount3 -u $dir else @@ -18,7 +17,6 @@ cleanup() rmdir $dir } - # $1 - disk image # $2 - fstype function prepfs() @@ -33,9 +31,10 @@ function prepfs() # $1 - disk image # $2 - mount point # $3 - filesystem type +# $4 - lock file lklfuse_mount() { - ${script_dir}/../lklfuse $1 $2 -o type=$3 + ${script_dir}/../lklfuse $1 $2 -o type=$3,lock=$4 } # $1 - mount point @@ -74,6 +73,21 @@ lklfuse_stressng() --sync-file-bytes 10m } +# $1 - disk image +# $2 - filesystem type +# $3 - lock file +lklfuse_lock_conflict() +{ + local ret=$TEST_FAILURE unused_mnt=`mktemp -d` + + set +e + # assume lklfuse already running with same lock file, causing lock conflict + ${script_dir}/../lklfuse -f $1 $unused_mnt -o type=$2,lock=$3 + [ $? -eq 2 ] && ret=$TEST_SUCCESS + rmdir "$unused_mnt" + return $ret +} + if [ "$1" = "-t" ]; then shift fstype=$1 @@ -102,18 +116,19 @@ if [ -z $(which mkfs.$fstype) ]; then exit 0 fi - file=`mktemp` dir=`mktemp -d` +lock_file="$file" trap cleanup EXIT -lkl_test_plan 4 "lklfuse $fstype" +lkl_test_plan 5 "lklfuse $fstype" lkl_test_run 1 prepfs $file $fstype -lkl_test_run 2 lklfuse_mount $file $dir $fstype +lkl_test_run 2 lklfuse_mount $file $dir $fstype $lock_file lkl_test_run 3 lklfuse_basic $dir # stress-ng returns 2 with no apparent failures so skip it for now #lkl_test_run 4 lklfuse_stressng $dir $fstype +lkl_test_run 4 lklfuse_lock_conflict $file $fstype $lock_file trap : EXIT -lkl_test_run 4 cleanup +lkl_test_run 5 cleanup diff --git a/tools/lkl/tests/net.sh b/tools/lkl/tests/net.sh index 55e4fb6df8a810..05bb2843a9d439 100755 --- a/tools/lkl/tests/net.sh +++ b/tools/lkl/tests/net.sh @@ -127,7 +127,7 @@ setup_backend() ;; *) echo "don't know how to setup backend $1" - return $TEST_FAILED + return $TEST_FAILURE ;; esac }