diff mbox series

generic/732: add F_OFD_GETLK/F_UNLCK testing

Message ID 20231004173042.222644-1-stsp2@yandex.ru (mailing list archive)
State New, archived
Headers show
Series generic/732: add F_OFD_GETLK/F_UNLCK testing | expand

Commit Message

Stas Sergeev Oct. 4, 2023, 5:30 p.m. UTC
F_UNLCK can be used with F_OFD_GETLK to get the locks from
particular fd. This patch adds a new test, generic/732,
to check that functionality.

The following options are added to t_ofd_locks:
-u selects F_UNLCK for testing
-G sets the unix socket name for fd receive
-S sets the unix socket name for fd send

Test works as follows:
- locker process sets the lock and sends an fd via unix socket
- lock-tester process receives an fd and uses F_UNLCK to check the lock

CC: fstests@vger.kernel.org
CC: Murphy Zhou <xzhou@redhat.com>
CC: Jeff Layton <jlayton@kernel.org>
CC: Zorro Lang <zlang@redhat.com>

Signed-off-by: Stas Sergeev <stsp2@yandex.ru>
---
 common/ofdlk          |  61 +++++++++++++++
 common/rc             |   4 +-
 src/t_ofd_locks.c     | 169 +++++++++++++++++++++++++++++++++++++++---
 tests/generic/478     |  59 +--------------
 tests/generic/732     |  60 +++++++++++++++
 tests/generic/732.out |  16 ++++
 6 files changed, 298 insertions(+), 71 deletions(-)
 create mode 100644 common/ofdlk
 create mode 100755 tests/generic/732
 create mode 100644 tests/generic/732.out

Comments

Jeff Layton Oct. 4, 2023, 5:43 p.m. UTC | #1
On Wed, 2023-10-04 at 22:30 +0500, Stas Sergeev wrote:
> F_UNLCK can be used with F_OFD_GETLK to get the locks from
> particular fd. This patch adds a new test, generic/732,
> to check that functionality.
> 
> The following options are added to t_ofd_locks:
> -u selects F_UNLCK for testing
> -G sets the unix socket name for fd receive
> -S sets the unix socket name for fd send
> 
> Test works as follows:
> - locker process sets the lock and sends an fd via unix socket
> - lock-tester process receives an fd and uses F_UNLCK to check the lock
> 
> CC: fstests@vger.kernel.org
> CC: Murphy Zhou <xzhou@redhat.com>
> CC: Jeff Layton <jlayton@kernel.org>
> CC: Zorro Lang <zlang@redhat.com>
> 
> Signed-off-by: Stas Sergeev <stsp2@yandex.ru>
> ---
>  common/ofdlk          |  61 +++++++++++++++
>  common/rc             |   4 +-
>  src/t_ofd_locks.c     | 169 +++++++++++++++++++++++++++++++++++++++---
>  tests/generic/478     |  59 +--------------
>  tests/generic/732     |  60 +++++++++++++++
>  tests/generic/732.out |  16 ++++
>  6 files changed, 298 insertions(+), 71 deletions(-)
>  create mode 100644 common/ofdlk
>  create mode 100755 tests/generic/732
>  create mode 100644 tests/generic/732.out
> 
> diff --git a/common/ofdlk b/common/ofdlk
> new file mode 100644
> index 00000000..0cdd2250
> --- /dev/null
> +++ b/common/ofdlk
> @@ -0,0 +1,61 @@
> +#
> +# Common functions for OFD lock tests
> +#
> +
> +mk_sem()

These are too generically named for functions that sit in common. Can
you rename these to something more distinct?

> +{
> +	SEMID=$(ipcmk -S 2 | cut -d ":" -f 2 | tr -d '[:space:]')
> +	if [ -z "$SEMID" ]; then
> +		echo "ipcmk failed"
> +		exit 1
> +	fi
> +	SEMKEY=$(ipcs -s | grep $SEMID | cut -d " " -f 1)
> +	if [ -z "$SEMKEY" ]; then
> +		echo "ipcs failed"
> +		exit 1
> +	fi
> +}
> +
> +rm_sem()
> +{
> +	ipcrm -s $SEMID 2>/dev/null
> +}
> +
> +do_test()
> +{
> +	local soptions
> +	local goptions
> +	# print options and getlk output for debug
> +	echo $* >> $seqres.full 2>&1
> +	mk_sem
> +	soptions="$1 -K $SEMKEY"
> +	goptions="$2 -K $SEMKEY"
> +	# -s : do setlk
> +	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
> +	# -g : do getlk
> +	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
> +		tee -a $seqres.full
> +	wait $!
> +	rm_sem
> +
> +	mk_sem
> +	# add -F to clone with CLONE_FILES
> +	soptions="$1 -F -K $SEMKEY"
> +	goptions="$2 -K $SEMKEY"
> +	# with -F, new locks are always file to place
> +	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
> +	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
> +		tee -a $seqres.full
> +	wait $!
> +	rm_sem
> +
> +	mk_sem
> +	# add -d to dup and close
> +	soptions="$1 -d -K $SEMKEY"
> +	goptions="$2 -K $SEMKEY"
> +	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
> +	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
> +		tee -a $seqres.full
> +	wait $!
> +	rm_sem
> +}
> diff --git a/common/rc b/common/rc
> index 1618ded5..1111fc12 100644
> --- a/common/rc
> +++ b/common/rc
> @@ -4222,8 +4222,8 @@ _require_ofd_locks()
>  	# EINVAL will be returned.
>  	_require_test_program "t_ofd_locks"
>  	touch $TEST_DIR/ofd_testfile
> -	$here/src/t_ofd_locks -t $TEST_DIR/ofd_testfile > /dev/null 2>&1
> -	[ $? -eq 22 ] && _notrun "Require OFD locks support"
> +	$here/src/t_ofd_locks -t $1 $TEST_DIR/ofd_testfile > /dev/null 2>&1
> +	[ $? -eq 22 ] && _notrun "Require OFD locks support $2"
>  }
> 
> 

Instead of adding extra parameters to this function, it would be better
to just add a new _require_ofd_getlk_unlck() function that does what's
needed for this test. There may be other testcases that require that
functionality and its more self-documenting for later users.

>  
>  _require_test_lsattr()
> diff --git a/src/t_ofd_locks.c b/src/t_ofd_locks.c
> index 58cb0959..8d63b80b 100644
> --- a/src/t_ofd_locks.c
> +++ b/src/t_ofd_locks.c
> @@ -14,6 +14,8 @@
>  #include <sys/wait.h>
>  #include <sys/ipc.h>
>  #include <sys/sem.h>
> +#include <sys/socket.h>
> +#include <sys/un.h>
>  
>  /*
>   * In distributions that do not have these macros ready in glibc-headers,
> @@ -134,10 +136,13 @@ static void usage(char *arg0)
>  	printf("\t-F : clone with CLONE_FILES in setlk to setup test condition\n");
>  	printf("\t-d : dup and close in setlk\n");
>  	printf("\twithout both -F/d, use clone without CLONE_FILES\n");
> -	printf("\t-r/-w : set/get rdlck/wrlck\n");
> +	printf("\t-r/-w/-u : set/get rdlck/wrlck/unlck\n");
>  	printf("\t-o num : offset start to lock, default 0\n");
>  	printf("\t-l num : lock length, default 10\n");
>  	printf("\t-R/-W : open file RDONLY/RDWR\n\n");
> +	printf("\t-G path : unix socket path for fd reception\n\n");
> +	printf("\t-S path : unix socket path for fd sending\n\n");
> +	printf("\t-t : test run, try to perform lock and report\n\n");
>  	printf("\tUsually we run a setlk routine in background and then\n");
>  	printf("\trun a getlk routine to check. They must be paired, or\n");
>  	printf("\ttest will hang.\n\n");
> @@ -164,6 +169,117 @@ static int child_fn(void* p)
>  	return 0;
>  }
>  
> +static int connect_unix(const char *sname)
> +{
> +	struct sockaddr_un addr = { .sun_family = AF_UNIX };
> +
> +	int unix_sock = socket(AF_UNIX, SOCK_DGRAM, 0);
> +	if (unix_sock == -1)
> +		return -1;
> +
> +	strncpy(addr.sun_path, sname, sizeof(addr.sun_path) - 1);
> +	if (connect(unix_sock, (struct sockaddr *)&addr, sizeof(addr)) == -1)
> +	{
> +		close(unix_sock);
> +		perror("connect()");
> +		return -1;
> +	}
> +
> +	return unix_sock;
> +}
> +
> +static int send_fd(const char *sname, int fd)
> +{
> +	int ret;
> +	int unix_sock;
> +	struct iovec iov = {.iov_base = ":)", // Must send at least one byte
> +			    .iov_len = 2};
> +
> +	union {
> +		char buf[CMSG_SPACE(sizeof(fd))];
> +		struct cmsghdr align;
> +	} u;
> +
> +	struct msghdr msg = {.msg_iov = &iov,
> +			     .msg_iovlen = 1,
> +			     .msg_control = u.buf,
> +			     .msg_controllen = sizeof(u.buf)};
> +
> +	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
> +	*cmsg = (struct cmsghdr){.cmsg_level = SOL_SOCKET,
> +				 .cmsg_type = SCM_RIGHTS,
> +				 .cmsg_len = CMSG_LEN(sizeof(fd))};
> +
> +	memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
> +
> +	unix_sock = connect_unix(sname);
> +	if (unix_sock == -1) {
> +		perror("sendmsg()");
> +		return -1;
> +	}
> +	ret = sendmsg(unix_sock, &msg, 0);
> +	close(unix_sock);
> +	if (ret == -1)
> +		perror("sendmsg()");
> +	return ret;
> +}
> +
> +static int bind_unix(const char *sname)
> +{
> +	struct sockaddr_storage storage = {};
> +	struct sockaddr_un *addr;
> +	int sock;
> +
> +	sock = socket(AF_UNIX, SOCK_DGRAM, 0);
> +	if (sock == -1)
> +		return -1;
> +	addr = (struct sockaddr_un *)&storage;
> +	addr->sun_family = AF_UNIX;
> +	strncpy(addr->sun_path, sname, sizeof(addr->sun_path) - 1);
> +	unlink(sname);
> +	if (bind(sock, (struct sockaddr *)addr, SUN_LEN(addr)) == -1) {
> +		perror("bind()");
> +		close(sock);
> +		return -1;
> +	}
> +	return sock;
> +}
> +
> +static int recv_fd(int sock)
> +{
> +	struct msghdr msg;
> +	struct cmsghdr *cmsghdr;
> +	struct iovec iov;
> +	ssize_t nbytes;
> +	int *p;
> +	char buf[CMSG_SPACE(sizeof(int))], c[16];
> +	struct cmsghdr *cmsgp;
> +
> +	iov.iov_base = &c;
> +	iov.iov_len = sizeof(c);
> +	cmsghdr = (struct cmsghdr *)buf;
> +	cmsghdr->cmsg_len = CMSG_LEN(sizeof(int));
> +	cmsghdr->cmsg_level = SOL_SOCKET;
> +	cmsghdr->cmsg_type = SCM_RIGHTS;
> +	msg.msg_name = NULL;
> +	msg.msg_namelen = 0;
> +	msg.msg_iov = &iov;
> +	msg.msg_iovlen = 1;
> +	msg.msg_control = cmsghdr;
> +	msg.msg_controllen = CMSG_LEN(sizeof(int));
> +	msg.msg_flags = 0;
> +
> +	nbytes = recvmsg(sock, &msg, 0);
> +	if (nbytes == -1)
> +		return -1;
> +
> +	cmsgp = CMSG_FIRSTHDR(&msg);
> +	p = (int *)CMSG_DATA(cmsgp);
> +	if (!p)
> +		return -1;
> +	return *p;
> +}
> +
>  int main(int argc, char **argv)
>  {
>  	int posix = 0;
> @@ -184,10 +300,12 @@ int main(int argc, char **argv)
>  	struct semid_ds sem_ds;
>  	struct sembuf sop;
>  	int opt, ret;
> +	int rcv_sock = -1;
> +	const char *snd_sock_name = NULL;
>  
>  	//avoid libcap errno bug
>  	errno = 0;
> -	while((opt = getopt(argc, argv, "sgrwo:l:PRWtFdK:")) != -1) {
> +	while((opt = getopt(argc, argv, "sgrwuo:l:PRWtFdK:G:S:")) != -1) {
>  		switch(opt) {
>  		case 's':
>  			lock_cmd = 1;
> @@ -201,6 +319,9 @@ int main(int argc, char **argv)
>  		case 'w':
>  			lock_rw = 1;
>  			break;
> +		case 'u':
> +			lock_rw = 2;
> +			break;
>  		case 'o':
>  			lock_start = atoi(optarg);
>  			break;
> @@ -228,6 +349,12 @@ int main(int argc, char **argv)
>  		case 'K':
>  			semkey = strtol(optarg, NULL, 16);
>  			break;
> +		case 'G':
> +			rcv_sock = bind_unix(optarg);
> +			break;
> +		case 'S':
> +			snd_sock_name = optarg;
> +			break;
>  		default:
>  			usage(argv[0]);
>  			return -1;
> @@ -256,17 +383,27 @@ int main(int argc, char **argv)
>  		getlk_macro = F_GETLK;
>  	}
>  
> -	if (lock_rw == 1)
> -		flk.l_type = F_WRLCK;
> -	else
> +	switch (lock_rw) {
> +	case 0:
>  		flk.l_type = F_RDLCK;
> +		break;
> +	case 1:
> +		flk.l_type = F_WRLCK;
> +		break;
> +	case 2:
> +		flk.l_type = F_UNLCK;
> +		break;
> +	}
>  
> -	if (open_rw == 0)
> -		fd = open(argv[optind], O_RDONLY);
> -	else
> -		fd = open(argv[optind], O_RDWR);
> -	if (fd == -1)
> -		err_exit("open", errno);
> +	if (rcv_sock == -1) {
> +		if (open_rw == 0) {
> +			fd = open(argv[optind], O_RDONLY);
> +		} else {
> +			fd = open(argv[optind], O_RDWR);
> +		}
> +		if (fd == -1)
> +			err_exit("open", errno);
> +	}
>  
>  	/*
>  	 * In a testun, we do a fcntl getlk call and exit
> @@ -322,6 +459,9 @@ int main(int argc, char **argv)
>  		if (fcntl(fd, setlk_macro, &flk) < 0)
>  			err_exit("setlkw", errno);
>  
> +		if (snd_sock_name)
> +			send_fd(snd_sock_name, fd);
> +
>  		if (use_dup == 1) {
>  			/* dup fd and close the newfd */
>  			int dfd = dup(fd);
> @@ -400,6 +540,13 @@ int main(int argc, char **argv)
>  		if (semtimedop(semid, &sop, 1, &ts) == -1)
>  			err_exit("wait sem0 0", errno);
>  
> +		if (rcv_sock != -1) {
> +			fd = recv_fd(rcv_sock);
> +			close(rcv_sock);
> +			if (fd == -1)
> +				err_exit("SCM_RIGHTS", errno);
> +		}
> +
>  		if (fcntl(fd, getlk_macro, &flk) < 0)
>  			err_exit("getlk", errno);
>  
> diff --git a/tests/generic/478 b/tests/generic/478
> index 419acc94..580c260e 100755
> --- a/tests/generic/478
> +++ b/tests/generic/478
> @@ -88,6 +88,7 @@ _begin_fstest auto quick
>  
>  # Import common functions.
>  . ./common/filter
> +. ./common/ofdlk
>  
>  # Modify as appropriate.
>  _supported_fs generic
> @@ -99,64 +100,6 @@ _require_ofd_locks
>  $XFS_IO_PROG -f -c "pwrite -S 0xFF 0 4096" \
>  	$TEST_DIR/testfile >> $seqres.full 2>&1
>  
> -mk_sem()
> -{
> -	SEMID=$(ipcmk -S 2 | cut -d ":" -f 2 | tr -d '[:space:]')
> -	if [ -z "$SEMID" ]; then
> -		echo "ipcmk failed"
> -		exit 1
> -	fi
> -	SEMKEY=$(ipcs -s | grep $SEMID | cut -d " " -f 1)
> -	if [ -z "$SEMKEY" ]; then
> -		echo "ipcs failed"
> -		exit 1
> -	fi
> -}
> -
> -rm_sem()
> -{
> -	ipcrm -s $SEMID 2>/dev/null
> -}
> -
> -do_test()
> -{
> -	local soptions
> -	local goptions
> -	# print options and getlk output for debug
> -	echo $* >> $seqres.full 2>&1
> -	mk_sem
> -	soptions="$1 -K $SEMKEY"
> -	goptions="$2 -K $SEMKEY"
> -	# -s : do setlk
> -	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
> -	# -g : do getlk
> -	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
> -		tee -a $seqres.full
> -	wait $!
> -	rm_sem
> -
> -	mk_sem
> -	# add -F to clone with CLONE_FILES
> -	soptions="$1 -F -K $SEMKEY"
> -	goptions="$2 -K $SEMKEY"
> -	# with -F, new locks are always file to place
> -	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
> -	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
> -		tee -a $seqres.full
> -	wait $!
> -	rm_sem
> -
> -	mk_sem
> -	# add -d to dup and close
> -	soptions="$1 -d -K $SEMKEY"
> -	goptions="$2 -K $SEMKEY"
> -	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
> -	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
> -		tee -a $seqres.full
> -	wait $!
> -	rm_sem
> -}
> -
>  # Always setlk at range [0,9], getlk at range [0,9] [5,24] or [20,29].
>  # To open file RDONLY or RDWR should not break the locks.
>  # POSIX locks should be released after closed fd, so it wont conflict
> diff --git a/tests/generic/732 b/tests/generic/732
> new file mode 100755
> index 00000000..78f5bd44
> --- /dev/null
> +++ b/tests/generic/732
> @@ -0,0 +1,60 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) 2023 stsp2@yandex.ru
> +#
> +# FS QA Test 732
> +#
> +# Test OFD lock's F_UNLCK extension.
> +#
> +. ./common/preamble
> +_begin_fstest auto quick
> +
> +# Import common functions.
> +. ./common/filter
> +. ./common/ofdlk
> +
> +# Modify as appropriate.
> +_supported_fs generic
> +_require_test
> +_require_ofd_locks -gu "F_OFD_GETLK/F_UNLCK extension"
> +
> +# real QA test starts here
> +# prepare a 4k testfile in TEST_DIR
> +$XFS_IO_PROG -f -c "pwrite -S 0xFF 0 4096" \
> +	$TEST_DIR/testfile >> $seqres.full 2>&1
> +
> +# Always setlk at range [0,9], getlk at range [0,9] [5,24] or [20,29].
> +# To open file RDONLY or RDWR should not break the locks.
> +# POSIX locks should be released after closed fd, so it wont conflict
> +# with other locks in tests
> +
> +# -P : operate posix lock
> +# -w : operate on F_WRLCK
> +# -r : operate on F_RDLCK
> +# -R : open file RDONLY
> +# -W : open file RDWR
> +# -o : file offset where the lock starts
> +# -l : lock length
> +# -F : clone with CLONE_FILES in setlk
> +# -d : dup and close in setlk
> +
> +# setlk wrlck [0,9], getlk wrlck [0,9], expect
> +#    + wrlck when CLONE_FILES not set
> +#    + unlck when CLONE_FILES set
> +#    + wrlck when dup & close
> +
> +# setlk wrlck [0,9], getlk unlck [0,9]
> +do_test "-s -w -o 0 -l 10 -W -S /tmp/fstests.sock" "-g -u -o 0 -l 10 -W -G /tmp/fstests.sock" "wrlck" "wrlck" "wrlck"
> +# setlk wrlck [0,9], getlk unlck [20,29]
> +do_test "-s -w -o 0 -l 10 -S /tmp/fstests.sock" "-g -u -o 20 -l 10 -G /tmp/fstests.sock" "unlck" "unlck" "unlck"
> +
> +# setlk rdlck [0,9], getlk unlck [0,9], open RDONLY
> +do_test "-s -r -o 0 -l 10 -R -S /tmp/fstests.sock" "-g -u -o 0 -l 10 -R -G /tmp/fstests.sock" "rdlck" "rdlck" "rdlck"
> +# setlk posix rdlck [0,9], getlk unlck [5,24], open RDONLY
> +do_test "-s -r -o 0 -l 10 -R -S /tmp/fstests.sock" "-g -u -o 5 -l 20 -R -G /tmp/fstests.sock" "rdlck" "rdlck" "rdlck"
> +# setlk rdlck [0,9], getlk unlck [20,29], open RDONLY
> +do_test "-s -r -o 0 -l 10 -R -S /tmp/fstests.sock" "-g -u -o 20 -l 10 -R -G /tmp/fstests.sock" "unlck" "unlck" "unlck"
> +
> +# success, all done
> +status=0
> +exit
> diff --git a/tests/generic/732.out b/tests/generic/732.out
> new file mode 100644
> index 00000000..98996904
> --- /dev/null
> +++ b/tests/generic/732.out
> @@ -0,0 +1,16 @@
> +QA output created by 732
> +get wrlck
> +get wrlck
> +get wrlck
> +lock could be placed
> +lock could be placed
> +lock could be placed
> +get rdlck
> +get rdlck
> +get rdlck
> +get rdlck
> +get rdlck
> +get rdlck
> +lock could be placed
> +lock could be placed
> +lock could be placed


I think the rest looks good though. Thanks for doing this.

Cheers,
diff mbox series

Patch

diff --git a/common/ofdlk b/common/ofdlk
new file mode 100644
index 00000000..0cdd2250
--- /dev/null
+++ b/common/ofdlk
@@ -0,0 +1,61 @@ 
+#
+# Common functions for OFD lock tests
+#
+
+mk_sem()
+{
+	SEMID=$(ipcmk -S 2 | cut -d ":" -f 2 | tr -d '[:space:]')
+	if [ -z "$SEMID" ]; then
+		echo "ipcmk failed"
+		exit 1
+	fi
+	SEMKEY=$(ipcs -s | grep $SEMID | cut -d " " -f 1)
+	if [ -z "$SEMKEY" ]; then
+		echo "ipcs failed"
+		exit 1
+	fi
+}
+
+rm_sem()
+{
+	ipcrm -s $SEMID 2>/dev/null
+}
+
+do_test()
+{
+	local soptions
+	local goptions
+	# print options and getlk output for debug
+	echo $* >> $seqres.full 2>&1
+	mk_sem
+	soptions="$1 -K $SEMKEY"
+	goptions="$2 -K $SEMKEY"
+	# -s : do setlk
+	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
+	# -g : do getlk
+	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
+		tee -a $seqres.full
+	wait $!
+	rm_sem
+
+	mk_sem
+	# add -F to clone with CLONE_FILES
+	soptions="$1 -F -K $SEMKEY"
+	goptions="$2 -K $SEMKEY"
+	# with -F, new locks are always file to place
+	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
+	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
+		tee -a $seqres.full
+	wait $!
+	rm_sem
+
+	mk_sem
+	# add -d to dup and close
+	soptions="$1 -d -K $SEMKEY"
+	goptions="$2 -K $SEMKEY"
+	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
+	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
+		tee -a $seqres.full
+	wait $!
+	rm_sem
+}
diff --git a/common/rc b/common/rc
index 1618ded5..1111fc12 100644
--- a/common/rc
+++ b/common/rc
@@ -4222,8 +4222,8 @@  _require_ofd_locks()
 	# EINVAL will be returned.
 	_require_test_program "t_ofd_locks"
 	touch $TEST_DIR/ofd_testfile
-	$here/src/t_ofd_locks -t $TEST_DIR/ofd_testfile > /dev/null 2>&1
-	[ $? -eq 22 ] && _notrun "Require OFD locks support"
+	$here/src/t_ofd_locks -t $1 $TEST_DIR/ofd_testfile > /dev/null 2>&1
+	[ $? -eq 22 ] && _notrun "Require OFD locks support $2"
 }
 
 _require_test_lsattr()
diff --git a/src/t_ofd_locks.c b/src/t_ofd_locks.c
index 58cb0959..8d63b80b 100644
--- a/src/t_ofd_locks.c
+++ b/src/t_ofd_locks.c
@@ -14,6 +14,8 @@ 
 #include <sys/wait.h>
 #include <sys/ipc.h>
 #include <sys/sem.h>
+#include <sys/socket.h>
+#include <sys/un.h>
 
 /*
  * In distributions that do not have these macros ready in glibc-headers,
@@ -134,10 +136,13 @@  static void usage(char *arg0)
 	printf("\t-F : clone with CLONE_FILES in setlk to setup test condition\n");
 	printf("\t-d : dup and close in setlk\n");
 	printf("\twithout both -F/d, use clone without CLONE_FILES\n");
-	printf("\t-r/-w : set/get rdlck/wrlck\n");
+	printf("\t-r/-w/-u : set/get rdlck/wrlck/unlck\n");
 	printf("\t-o num : offset start to lock, default 0\n");
 	printf("\t-l num : lock length, default 10\n");
 	printf("\t-R/-W : open file RDONLY/RDWR\n\n");
+	printf("\t-G path : unix socket path for fd reception\n\n");
+	printf("\t-S path : unix socket path for fd sending\n\n");
+	printf("\t-t : test run, try to perform lock and report\n\n");
 	printf("\tUsually we run a setlk routine in background and then\n");
 	printf("\trun a getlk routine to check. They must be paired, or\n");
 	printf("\ttest will hang.\n\n");
@@ -164,6 +169,117 @@  static int child_fn(void* p)
 	return 0;
 }
 
+static int connect_unix(const char *sname)
+{
+	struct sockaddr_un addr = { .sun_family = AF_UNIX };
+
+	int unix_sock = socket(AF_UNIX, SOCK_DGRAM, 0);
+	if (unix_sock == -1)
+		return -1;
+
+	strncpy(addr.sun_path, sname, sizeof(addr.sun_path) - 1);
+	if (connect(unix_sock, (struct sockaddr *)&addr, sizeof(addr)) == -1)
+	{
+		close(unix_sock);
+		perror("connect()");
+		return -1;
+	}
+
+	return unix_sock;
+}
+
+static int send_fd(const char *sname, int fd)
+{
+	int ret;
+	int unix_sock;
+	struct iovec iov = {.iov_base = ":)", // Must send at least one byte
+			    .iov_len = 2};
+
+	union {
+		char buf[CMSG_SPACE(sizeof(fd))];
+		struct cmsghdr align;
+	} u;
+
+	struct msghdr msg = {.msg_iov = &iov,
+			     .msg_iovlen = 1,
+			     .msg_control = u.buf,
+			     .msg_controllen = sizeof(u.buf)};
+
+	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+	*cmsg = (struct cmsghdr){.cmsg_level = SOL_SOCKET,
+				 .cmsg_type = SCM_RIGHTS,
+				 .cmsg_len = CMSG_LEN(sizeof(fd))};
+
+	memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
+
+	unix_sock = connect_unix(sname);
+	if (unix_sock == -1) {
+		perror("sendmsg()");
+		return -1;
+	}
+	ret = sendmsg(unix_sock, &msg, 0);
+	close(unix_sock);
+	if (ret == -1)
+		perror("sendmsg()");
+	return ret;
+}
+
+static int bind_unix(const char *sname)
+{
+	struct sockaddr_storage storage = {};
+	struct sockaddr_un *addr;
+	int sock;
+
+	sock = socket(AF_UNIX, SOCK_DGRAM, 0);
+	if (sock == -1)
+		return -1;
+	addr = (struct sockaddr_un *)&storage;
+	addr->sun_family = AF_UNIX;
+	strncpy(addr->sun_path, sname, sizeof(addr->sun_path) - 1);
+	unlink(sname);
+	if (bind(sock, (struct sockaddr *)addr, SUN_LEN(addr)) == -1) {
+		perror("bind()");
+		close(sock);
+		return -1;
+	}
+	return sock;
+}
+
+static int recv_fd(int sock)
+{
+	struct msghdr msg;
+	struct cmsghdr *cmsghdr;
+	struct iovec iov;
+	ssize_t nbytes;
+	int *p;
+	char buf[CMSG_SPACE(sizeof(int))], c[16];
+	struct cmsghdr *cmsgp;
+
+	iov.iov_base = &c;
+	iov.iov_len = sizeof(c);
+	cmsghdr = (struct cmsghdr *)buf;
+	cmsghdr->cmsg_len = CMSG_LEN(sizeof(int));
+	cmsghdr->cmsg_level = SOL_SOCKET;
+	cmsghdr->cmsg_type = SCM_RIGHTS;
+	msg.msg_name = NULL;
+	msg.msg_namelen = 0;
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	msg.msg_control = cmsghdr;
+	msg.msg_controllen = CMSG_LEN(sizeof(int));
+	msg.msg_flags = 0;
+
+	nbytes = recvmsg(sock, &msg, 0);
+	if (nbytes == -1)
+		return -1;
+
+	cmsgp = CMSG_FIRSTHDR(&msg);
+	p = (int *)CMSG_DATA(cmsgp);
+	if (!p)
+		return -1;
+	return *p;
+}
+
 int main(int argc, char **argv)
 {
 	int posix = 0;
@@ -184,10 +300,12 @@  int main(int argc, char **argv)
 	struct semid_ds sem_ds;
 	struct sembuf sop;
 	int opt, ret;
+	int rcv_sock = -1;
+	const char *snd_sock_name = NULL;
 
 	//avoid libcap errno bug
 	errno = 0;
-	while((opt = getopt(argc, argv, "sgrwo:l:PRWtFdK:")) != -1) {
+	while((opt = getopt(argc, argv, "sgrwuo:l:PRWtFdK:G:S:")) != -1) {
 		switch(opt) {
 		case 's':
 			lock_cmd = 1;
@@ -201,6 +319,9 @@  int main(int argc, char **argv)
 		case 'w':
 			lock_rw = 1;
 			break;
+		case 'u':
+			lock_rw = 2;
+			break;
 		case 'o':
 			lock_start = atoi(optarg);
 			break;
@@ -228,6 +349,12 @@  int main(int argc, char **argv)
 		case 'K':
 			semkey = strtol(optarg, NULL, 16);
 			break;
+		case 'G':
+			rcv_sock = bind_unix(optarg);
+			break;
+		case 'S':
+			snd_sock_name = optarg;
+			break;
 		default:
 			usage(argv[0]);
 			return -1;
@@ -256,17 +383,27 @@  int main(int argc, char **argv)
 		getlk_macro = F_GETLK;
 	}
 
-	if (lock_rw == 1)
-		flk.l_type = F_WRLCK;
-	else
+	switch (lock_rw) {
+	case 0:
 		flk.l_type = F_RDLCK;
+		break;
+	case 1:
+		flk.l_type = F_WRLCK;
+		break;
+	case 2:
+		flk.l_type = F_UNLCK;
+		break;
+	}
 
-	if (open_rw == 0)
-		fd = open(argv[optind], O_RDONLY);
-	else
-		fd = open(argv[optind], O_RDWR);
-	if (fd == -1)
-		err_exit("open", errno);
+	if (rcv_sock == -1) {
+		if (open_rw == 0) {
+			fd = open(argv[optind], O_RDONLY);
+		} else {
+			fd = open(argv[optind], O_RDWR);
+		}
+		if (fd == -1)
+			err_exit("open", errno);
+	}
 
 	/*
 	 * In a testun, we do a fcntl getlk call and exit
@@ -322,6 +459,9 @@  int main(int argc, char **argv)
 		if (fcntl(fd, setlk_macro, &flk) < 0)
 			err_exit("setlkw", errno);
 
+		if (snd_sock_name)
+			send_fd(snd_sock_name, fd);
+
 		if (use_dup == 1) {
 			/* dup fd and close the newfd */
 			int dfd = dup(fd);
@@ -400,6 +540,13 @@  int main(int argc, char **argv)
 		if (semtimedop(semid, &sop, 1, &ts) == -1)
 			err_exit("wait sem0 0", errno);
 
+		if (rcv_sock != -1) {
+			fd = recv_fd(rcv_sock);
+			close(rcv_sock);
+			if (fd == -1)
+				err_exit("SCM_RIGHTS", errno);
+		}
+
 		if (fcntl(fd, getlk_macro, &flk) < 0)
 			err_exit("getlk", errno);
 
diff --git a/tests/generic/478 b/tests/generic/478
index 419acc94..580c260e 100755
--- a/tests/generic/478
+++ b/tests/generic/478
@@ -88,6 +88,7 @@  _begin_fstest auto quick
 
 # Import common functions.
 . ./common/filter
+. ./common/ofdlk
 
 # Modify as appropriate.
 _supported_fs generic
@@ -99,64 +100,6 @@  _require_ofd_locks
 $XFS_IO_PROG -f -c "pwrite -S 0xFF 0 4096" \
 	$TEST_DIR/testfile >> $seqres.full 2>&1
 
-mk_sem()
-{
-	SEMID=$(ipcmk -S 2 | cut -d ":" -f 2 | tr -d '[:space:]')
-	if [ -z "$SEMID" ]; then
-		echo "ipcmk failed"
-		exit 1
-	fi
-	SEMKEY=$(ipcs -s | grep $SEMID | cut -d " " -f 1)
-	if [ -z "$SEMKEY" ]; then
-		echo "ipcs failed"
-		exit 1
-	fi
-}
-
-rm_sem()
-{
-	ipcrm -s $SEMID 2>/dev/null
-}
-
-do_test()
-{
-	local soptions
-	local goptions
-	# print options and getlk output for debug
-	echo $* >> $seqres.full 2>&1
-	mk_sem
-	soptions="$1 -K $SEMKEY"
-	goptions="$2 -K $SEMKEY"
-	# -s : do setlk
-	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
-	# -g : do getlk
-	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
-		tee -a $seqres.full
-	wait $!
-	rm_sem
-
-	mk_sem
-	# add -F to clone with CLONE_FILES
-	soptions="$1 -F -K $SEMKEY"
-	goptions="$2 -K $SEMKEY"
-	# with -F, new locks are always file to place
-	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
-	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
-		tee -a $seqres.full
-	wait $!
-	rm_sem
-
-	mk_sem
-	# add -d to dup and close
-	soptions="$1 -d -K $SEMKEY"
-	goptions="$2 -K $SEMKEY"
-	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
-	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
-		tee -a $seqres.full
-	wait $!
-	rm_sem
-}
-
 # Always setlk at range [0,9], getlk at range [0,9] [5,24] or [20,29].
 # To open file RDONLY or RDWR should not break the locks.
 # POSIX locks should be released after closed fd, so it wont conflict
diff --git a/tests/generic/732 b/tests/generic/732
new file mode 100755
index 00000000..78f5bd44
--- /dev/null
+++ b/tests/generic/732
@@ -0,0 +1,60 @@ 
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2023 stsp2@yandex.ru
+#
+# FS QA Test 732
+#
+# Test OFD lock's F_UNLCK extension.
+#
+. ./common/preamble
+_begin_fstest auto quick
+
+# Import common functions.
+. ./common/filter
+. ./common/ofdlk
+
+# Modify as appropriate.
+_supported_fs generic
+_require_test
+_require_ofd_locks -gu "F_OFD_GETLK/F_UNLCK extension"
+
+# real QA test starts here
+# prepare a 4k testfile in TEST_DIR
+$XFS_IO_PROG -f -c "pwrite -S 0xFF 0 4096" \
+	$TEST_DIR/testfile >> $seqres.full 2>&1
+
+# Always setlk at range [0,9], getlk at range [0,9] [5,24] or [20,29].
+# To open file RDONLY or RDWR should not break the locks.
+# POSIX locks should be released after closed fd, so it wont conflict
+# with other locks in tests
+
+# -P : operate posix lock
+# -w : operate on F_WRLCK
+# -r : operate on F_RDLCK
+# -R : open file RDONLY
+# -W : open file RDWR
+# -o : file offset where the lock starts
+# -l : lock length
+# -F : clone with CLONE_FILES in setlk
+# -d : dup and close in setlk
+
+# setlk wrlck [0,9], getlk wrlck [0,9], expect
+#    + wrlck when CLONE_FILES not set
+#    + unlck when CLONE_FILES set
+#    + wrlck when dup & close
+
+# setlk wrlck [0,9], getlk unlck [0,9]
+do_test "-s -w -o 0 -l 10 -W -S /tmp/fstests.sock" "-g -u -o 0 -l 10 -W -G /tmp/fstests.sock" "wrlck" "wrlck" "wrlck"
+# setlk wrlck [0,9], getlk unlck [20,29]
+do_test "-s -w -o 0 -l 10 -S /tmp/fstests.sock" "-g -u -o 20 -l 10 -G /tmp/fstests.sock" "unlck" "unlck" "unlck"
+
+# setlk rdlck [0,9], getlk unlck [0,9], open RDONLY
+do_test "-s -r -o 0 -l 10 -R -S /tmp/fstests.sock" "-g -u -o 0 -l 10 -R -G /tmp/fstests.sock" "rdlck" "rdlck" "rdlck"
+# setlk posix rdlck [0,9], getlk unlck [5,24], open RDONLY
+do_test "-s -r -o 0 -l 10 -R -S /tmp/fstests.sock" "-g -u -o 5 -l 20 -R -G /tmp/fstests.sock" "rdlck" "rdlck" "rdlck"
+# setlk rdlck [0,9], getlk unlck [20,29], open RDONLY
+do_test "-s -r -o 0 -l 10 -R -S /tmp/fstests.sock" "-g -u -o 20 -l 10 -R -G /tmp/fstests.sock" "unlck" "unlck" "unlck"
+
+# success, all done
+status=0
+exit
diff --git a/tests/generic/732.out b/tests/generic/732.out
new file mode 100644
index 00000000..98996904
--- /dev/null
+++ b/tests/generic/732.out
@@ -0,0 +1,16 @@ 
+QA output created by 732
+get wrlck
+get wrlck
+get wrlck
+lock could be placed
+lock could be placed
+lock could be placed
+get rdlck
+get rdlck
+get rdlck
+get rdlck
+get rdlck
+get rdlck
+lock could be placed
+lock could be placed
+lock could be placed