Merge pull request #611 from ddiss/lklfuse_flock

lklfuse: support exclusive locks to avoid duplicate mounts
This commit is contained in:
Octavian Purdila
2025-07-04 07:43:21 -07:00
committed by GitHub
5 changed files with 56 additions and 14 deletions

View File

@@ -39,7 +39,9 @@ OPTIONS
-o part=parition mount <partition>.
-o ro open file read-only.
-o ro open block-device read-only.
-o lock=<file> only mount after taking an exclusive lock on <file>.
-o opts=options Linux kernel mount <options> (use \\ to escape , and =).

View File

@@ -6,6 +6,7 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -127,7 +127,7 @@ setup_backend()
;;
*)
echo "don't know how to setup backend $1"
return $TEST_FAILED
return $TEST_FAILURE
;;
esac
}