This patch-set implements 2 small extensions to the current F_OFD_GETLK, allowing it to gather more information than it currently returns.
First extension allows to use F_UNLCK on query, which currently returns EINVAL. Instead it can be used to query the locks on a particular fd - something that is not currently possible. The basic idea is that on F_OFD_GETLK, F_UNLCK would "conflict" with (or query) any types of the lock on the same fd, and ignore any locks on other fds.
Use-cases:
1. CRIU-alike scenario when you want to read the locking info from an fd for the later reconstruction. This can now be done by setting l_start and l_len to 0 to cover entire file range, and do F_OFD_GETLK. In the loop you need to advance l_start past the returned lock ranges, to eventually collect all locked ranges.
2. Implementing the lock checking/enforcing policy. Say you want to implement an "auditor" module in your program, that checks that the I/O is done only after the proper locking is applied on a file region. In this case you need to know if the particular region is locked on that fd, and if so - with what type of the lock. If you would do that currently (without this extension) then you can only check for the write locks, and for that you need to probe the lock on your fd and then open the same file via nother fd and probe there. That way you can identify the write lock on a particular fd, but such trick is non-atomic and complex. As for finding out the read lock on a particular fd - impossible. This extension allows to do such queries without any extra efforts.
3. Implementing the mandatory locking policy. Suppose you want to make a policy where the write lock inhibits any unlocked readers and writers. Currently you need to check if the write lock is present on some other fd, and if it is not there - allow the I/O operation. But because the write lock can appear at any moment, you need to do that under some global lock, which can be released only when the I/O operation is finished. With the proposed extension you can instead just check the write lock on your own fd first, and if it is there - allow the I/O operation on that fd without using any global lock. Only if there is no write lock on this fd, then you need to take global lock and check for a write lock on other fds.
The second patch implements another extension. Currently F_OFD_GETLK returns -1 in the l_pid member. This patch removes the code that writes -1 there, so that the proper pid is returned. I am not sure why it was decided to deliberately hide the owner's pid. It may be needed in case you want to send some message to the offending locker, like eg SIGKILL.
The third patch adds a test-case for OFD locks. It tests both the generic things and the proposed extensions.
Stas Sergeev (3): fs/locks: F_UNLCK extension for F_OFD_GETLK fd/locks: allow get the lock owner by F_OFD_GETLK selftests: add OFD lock tests
fs/locks.c | 25 +++- tools/testing/selftests/locking/Makefile | 2 + tools/testing/selftests/locking/ofdlocks.c | 135 +++++++++++++++++++++ 3 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 tools/testing/selftests/locking/ofdlocks.c
CC: Jeff Layton jlayton@kernel.org CC: Chuck Lever chuck.lever@oracle.com CC: Alexander Viro viro@zeniv.linux.org.uk CC: Christian Brauner brauner@kernel.org CC: linux-fsdevel@vger.kernel.org CC: linux-kernel@vger.kernel.org CC: Shuah Khan shuah@kernel.org CC: linux-kselftest@vger.kernel.org
Test the basic locking stuff on 2 fds: multiple read locks, conflicts between read and write locks, use of len==0 for queries. Also test for pid and F_UNLCK F_OFD_GETLK extensions.
Signed-off-by: Stas Sergeev stsp2@yandex.ru
CC: Shuah Khan shuah@kernel.org CC: linux-kernel@vger.kernel.org CC: linux-kselftest@vger.kernel.org CC: Jeff Layton jlayton@kernel.org CC: Chuck Lever chuck.lever@oracle.com CC: Alexander Viro viro@zeniv.linux.org.uk CC: Christian Brauner brauner@kernel.org CC: linux-fsdevel@vger.kernel.org
--- tools/testing/selftests/locking/Makefile | 2 + tools/testing/selftests/locking/ofdlocks.c | 138 +++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 tools/testing/selftests/locking/ofdlocks.c
diff --git a/tools/testing/selftests/locking/Makefile b/tools/testing/selftests/locking/Makefile index 6e7761ab3536..a83ced1626de 100644 --- a/tools/testing/selftests/locking/Makefile +++ b/tools/testing/selftests/locking/Makefile @@ -7,4 +7,6 @@ all:
TEST_PROGS := ww_mutex.sh
+TEST_GEN_PROGS := ofdlocks + include ../lib.mk diff --git a/tools/testing/selftests/locking/ofdlocks.c b/tools/testing/selftests/locking/ofdlocks.c new file mode 100644 index 000000000000..1cff350e2c81 --- /dev/null +++ b/tools/testing/selftests/locking/ofdlocks.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include <fcntl.h> +#include <assert.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include "../kselftest.h" + +static int lock_set(int fd, struct flock *fl) +{ + int ret; + + fl->l_pid = 0; // needed for OFD locks + fl->l_whence = SEEK_SET; + ret = fcntl(fd, F_OFD_SETLK, fl); + if (ret) + perror("fcntl()"); + return ret; +} + +static int lock_get(int fd, struct flock *fl) +{ + int ret; + + fl->l_pid = 0; // needed for OFD locks + fl->l_whence = SEEK_SET; + ret = fcntl(fd, F_OFD_GETLK, fl); + if (ret) + perror("fcntl()"); + return ret; +} + +int main(void) +{ + int rc; + struct flock fl, fl2; + int fd = open("/tmp/aa", O_RDWR | O_CREAT | O_EXCL, 0600); + int fd2 = open("/tmp/aa", O_RDONLY); + + unlink("aa"); + assert(fd != -1); + assert(fd2 != -1); + ksft_print_msg("[INFO] opened fds %i %i\n", fd, fd2); + + /* Set some read lock */ + fl.l_type = F_RDLCK; + fl.l_start = 5; + fl.l_len = 3; + rc = lock_set(fd, &fl); + if (rc == 0) { + ksft_print_msg + ("[SUCCESS] set OFD read lock on first fd\n"); + } else { + ksft_print_msg("[FAIL] to set OFD read lock on first fd\n"); + return -1; + } + /* Make sure read locks do not conflict on different fds. */ + fl.l_type = F_RDLCK; + fl.l_start = 5; + fl.l_len = 1; + rc = lock_get(fd2, &fl); + if (rc != 0) + return -1; + if (fl.l_type != F_UNLCK) { + ksft_print_msg("[FAIL] read locks conflicted\n"); + return -1; + } + /* Make sure read/write locks do conflict on different fds. */ + fl.l_type = F_WRLCK; + fl.l_start = 5; + fl.l_len = 1; + rc = lock_get(fd2, &fl); + if (rc != 0) + return -1; + if (fl.l_type != F_UNLCK) { + ksft_print_msg + ("[SUCCESS] read and write locks conflicted\n"); + } else { + ksft_print_msg + ("[SUCCESS] read and write locks not conflicted\n"); + return -1; + } + /* Get info about the lock on first fd. */ + fl.l_type = F_UNLCK; + fl.l_start = 5; + fl.l_len = 1; + rc = lock_get(fd, &fl); + if (rc != 0) { + ksft_print_msg + ("[FAIL] F_OFD_GETLK with F_UNLCK not supported\n"); + return -1; + } + if (fl.l_type != F_UNLCK) { + if (fl.l_pid != getpid()) { + ksft_print_msg + ("[FAIL] F_OFD_GETLK does not return pid, %i\n", + fl.l_pid); + return -1; + } + ksft_print_msg + ("[SUCCESS] F_UNLCK test returns: locked, type %i pid %i len %zi\n", + fl.l_type, fl.l_pid, fl.l_len); + } else { + ksft_print_msg + ("[FAIL] F_OFD_GETLK with F_UNLCK did not return lock info\n"); + return -1; + } + /* Try the same but by locking everything by len==0. */ + fl2.l_type = F_UNLCK; + fl2.l_start = 0; + fl2.l_len = 0; + rc = lock_get(fd, &fl2); + if (rc != 0) { + ksft_print_msg + ("[FAIL] F_OFD_GETLK with F_UNLCK not supported\n"); + return -1; + } + if (memcmp(&fl, &fl2, sizeof(fl))) { + ksft_print_msg + ("[FAIL] F_UNLCK test returns: locked, type %i pid %i len %zi\n", + fl.l_type, fl.l_pid, fl.l_len); + return -1; + } + ksft_print_msg("[SUCCESS] F_UNLCK with len==0 returned the same\n"); + /* Get info about the lock on second fd - no locks on it. */ + fl.l_type = F_UNLCK; + fl.l_start = 0; + fl.l_len = 0; + lock_get(fd2, &fl); + if (fl.l_type != F_UNLCK) { + ksft_print_msg + ("[FAIL] F_OFD_GETLK with F_UNLCK return lock info from another fd\n"); + return -1; + } + return 0; +}
On Tue, 2023-06-20 at 14:55 +0500, Stas Sergeev wrote:
Test the basic locking stuff on 2 fds: multiple read locks, conflicts between read and write locks, use of len==0 for queries. Also test for pid and F_UNLCK F_OFD_GETLK extensions.
Signed-off-by: Stas Sergeev stsp2@yandex.ru
CC: Shuah Khan shuah@kernel.org CC: linux-kernel@vger.kernel.org CC: linux-kselftest@vger.kernel.org CC: Jeff Layton jlayton@kernel.org CC: Chuck Lever chuck.lever@oracle.com CC: Alexander Viro viro@zeniv.linux.org.uk CC: Christian Brauner brauner@kernel.org CC: linux-fsdevel@vger.kernel.org
tools/testing/selftests/locking/Makefile | 2 + tools/testing/selftests/locking/ofdlocks.c | 138 +++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 tools/testing/selftests/locking/ofdlocks.c
diff --git a/tools/testing/selftests/locking/Makefile b/tools/testing/selftests/locking/Makefile index 6e7761ab3536..a83ced1626de 100644 --- a/tools/testing/selftests/locking/Makefile +++ b/tools/testing/selftests/locking/Makefile @@ -7,4 +7,6 @@ all: TEST_PROGS := ww_mutex.sh +TEST_GEN_PROGS := ofdlocks
include ../lib.mk diff --git a/tools/testing/selftests/locking/ofdlocks.c b/tools/testing/selftests/locking/ofdlocks.c new file mode 100644 index 000000000000..1cff350e2c81 --- /dev/null +++ b/tools/testing/selftests/locking/ofdlocks.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE +#include <fcntl.h> +#include <assert.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include "../kselftest.h"
+static int lock_set(int fd, struct flock *fl) +{
- int ret;
- fl->l_pid = 0; // needed for OFD locks
- fl->l_whence = SEEK_SET;
- ret = fcntl(fd, F_OFD_SETLK, fl);
- if (ret)
perror("fcntl()");
- return ret;
+}
+static int lock_get(int fd, struct flock *fl) +{
- int ret;
- fl->l_pid = 0; // needed for OFD locks
- fl->l_whence = SEEK_SET;
- ret = fcntl(fd, F_OFD_GETLK, fl);
- if (ret)
perror("fcntl()");
- return ret;
+}
+int main(void) +{
- int rc;
- struct flock fl, fl2;
- int fd = open("/tmp/aa", O_RDWR | O_CREAT | O_EXCL, 0600);
- int fd2 = open("/tmp/aa", O_RDONLY);
- unlink("aa");
- assert(fd != -1);
- assert(fd2 != -1);
- ksft_print_msg("[INFO] opened fds %i %i\n", fd, fd2);
- /* Set some read lock */
- fl.l_type = F_RDLCK;
- fl.l_start = 5;
- fl.l_len = 3;
- rc = lock_set(fd, &fl);
- if (rc == 0) {
ksft_print_msg
("[SUCCESS] set OFD read lock on first fd\n");
- } else {
ksft_print_msg("[FAIL] to set OFD read lock on first fd\n");
return -1;
- }
- /* Make sure read locks do not conflict on different fds. */
- fl.l_type = F_RDLCK;
- fl.l_start = 5;
- fl.l_len = 1;
- rc = lock_get(fd2, &fl);
- if (rc != 0)
return -1;
- if (fl.l_type != F_UNLCK) {
ksft_print_msg("[FAIL] read locks conflicted\n");
return -1;
- }
- /* Make sure read/write locks do conflict on different fds. */
- fl.l_type = F_WRLCK;
- fl.l_start = 5;
- fl.l_len = 1;
- rc = lock_get(fd2, &fl);
- if (rc != 0)
return -1;
- if (fl.l_type != F_UNLCK) {
ksft_print_msg
("[SUCCESS] read and write locks conflicted\n");
- } else {
ksft_print_msg
("[SUCCESS] read and write locks not conflicted\n");
return -1;
- }
- /* Get info about the lock on first fd. */
- fl.l_type = F_UNLCK;
- fl.l_start = 5;
- fl.l_len = 1;
- rc = lock_get(fd, &fl);
- if (rc != 0) {
ksft_print_msg
("[FAIL] F_OFD_GETLK with F_UNLCK not supported\n");
return -1;
- }
- if (fl.l_type != F_UNLCK) {
if (fl.l_pid != getpid()) {
ksft_print_msg
("[FAIL] F_OFD_GETLK does not return pid, %i\n",
fl.l_pid);
return -1;
}
A selftest seems like a reasonable thing to add. The above check will need to be fixed to not expect a real pid on a OFD lock, of course.
ksft_print_msg
("[SUCCESS] F_UNLCK test returns: locked, type %i pid %i len %zi\n",
fl.l_type, fl.l_pid, fl.l_len);
- } else {
ksft_print_msg
("[FAIL] F_OFD_GETLK with F_UNLCK did not return lock info\n");
return -1;
- }
- /* Try the same but by locking everything by len==0. */
- fl2.l_type = F_UNLCK;
- fl2.l_start = 0;
- fl2.l_len = 0;
- rc = lock_get(fd, &fl2);
- if (rc != 0) {
ksft_print_msg
("[FAIL] F_OFD_GETLK with F_UNLCK not supported\n");
return -1;
- }
- if (memcmp(&fl, &fl2, sizeof(fl))) {
ksft_print_msg
("[FAIL] F_UNLCK test returns: locked, type %i pid %i len %zi\n",
fl.l_type, fl.l_pid, fl.l_len);
return -1;
- }
- ksft_print_msg("[SUCCESS] F_UNLCK with len==0 returned the same\n");
- /* Get info about the lock on second fd - no locks on it. */
- fl.l_type = F_UNLCK;
- fl.l_start = 0;
- fl.l_len = 0;
- lock_get(fd2, &fl);
- if (fl.l_type != F_UNLCK) {
ksft_print_msg
("[FAIL] F_OFD_GETLK with F_UNLCK return lock info from another fd\n");
return -1;
- }
- return 0;
+}
linux-kselftest-mirror@lists.linaro.org