flock, fcntl record locks
There were none: fcntl answered F_SETLK with EINVAL and flock was not a system call. Alpine's apk, which runs unmodified on SchOS through Alpine's own musl, locks its database before reading it and stopped there. kernel/fs/lock.c keeps one table of advisory locks keyed by (dev, inum): flock() for the whole file, owned by the open file description; F_SETLK/F_SETLKW/F_GETLK for byte ranges, owned by the process and released when it closes any descriptor on the file or exits; and the F_OFD_* forms, ranges owned by the description. flock and record locks do not see each other, as on Linux. Waiters sleep until any release and are interrupted by a signal. cmd/locktest checks each between a parent and a child, including range splitting, F_GETLK's report, and a blocked waiter being woken. shell.nix gains pkg-config, meson, ninja, m4, bison, flex and xz, for the X ports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
4afaebf739
commit
6f3175d8a5
11 changed files with 562 additions and 2 deletions
152
cmd/locktest/main.c
Normal file
152
cmd/locktest/main.c
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
// locktest: flock() and fcntl() record locks, each property a program
|
||||
// relies on checked between a parent and a child -- the only way to see a
|
||||
// lock is to be someone it keeps out.
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int bad;
|
||||
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (!(cond)) { printf("locktest: FAIL " __VA_ARGS__); printf("\n"); bad++; } \
|
||||
} while (0)
|
||||
|
||||
#define PATH "/tmp/locktest"
|
||||
|
||||
// Run `fn` in a child and return its exit status.
|
||||
static int
|
||||
child(int (*fn)(void))
|
||||
{
|
||||
pid_t p = fork();
|
||||
if (p == 0)
|
||||
_exit(fn());
|
||||
int st = 0;
|
||||
waitpid(p, &st, 0);
|
||||
return WIFEXITED(st) ? WEXITSTATUS(st) : 99;
|
||||
}
|
||||
|
||||
static int
|
||||
try_flock_ex(void)
|
||||
{
|
||||
int fd = open(PATH, O_RDWR);
|
||||
return flock(fd, LOCK_EX | LOCK_NB) == 0 ? 0 : (errno == EWOULDBLOCK ? 1 : 2);
|
||||
}
|
||||
|
||||
static int
|
||||
try_flock_sh(void)
|
||||
{
|
||||
int fd = open(PATH, O_RDWR);
|
||||
return flock(fd, LOCK_SH | LOCK_NB) == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
static int
|
||||
set(int fd, int cmd, short type, off_t start, off_t len)
|
||||
{
|
||||
struct flock fl = { .l_type = type, .l_whence = SEEK_SET,
|
||||
.l_start = start, .l_len = len };
|
||||
return fcntl(fd, cmd, &fl);
|
||||
}
|
||||
|
||||
static int
|
||||
try_range_10_19(void)
|
||||
{
|
||||
int fd = open(PATH, O_RDWR);
|
||||
return set(fd, F_SETLK, F_WRLCK, 10, 10) == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
static int
|
||||
try_range_50_59(void)
|
||||
{
|
||||
int fd = open(PATH, O_RDWR);
|
||||
return set(fd, F_SETLK, F_WRLCK, 50, 10) == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
static int
|
||||
wait_range_10_19(void)
|
||||
{
|
||||
int fd = open(PATH, O_RDWR);
|
||||
return set(fd, F_SETLKW, F_WRLCK, 10, 10) == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
static int
|
||||
lock_and_exit(void)
|
||||
{
|
||||
int fd = open(PATH, O_RDWR);
|
||||
return set(fd, F_SETLK, F_WRLCK, 0, 0) == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
int fd = open(PATH, O_RDWR | O_CREAT | O_TRUNC, 0644);
|
||||
CHECK(fd >= 0, "open: %s", strerror(errno));
|
||||
write(fd, "0123456789012345678901234567890123456789", 40);
|
||||
|
||||
// flock: exclusive keeps everyone out, shared lets shared in.
|
||||
CHECK(flock(fd, LOCK_EX) == 0, "flock LOCK_EX");
|
||||
CHECK(child(try_flock_ex) == 1, "a second LOCK_EX is refused with EWOULDBLOCK");
|
||||
CHECK(flock(fd, LOCK_SH) == 0, "convert to LOCK_SH");
|
||||
CHECK(child(try_flock_sh) == 0, "LOCK_SH alongside LOCK_SH");
|
||||
CHECK(child(try_flock_ex) == 1, "LOCK_EX against LOCK_SH");
|
||||
CHECK(flock(fd, LOCK_UN) == 0 && child(try_flock_ex) == 0, "LOCK_UN releases");
|
||||
|
||||
// Closing the last descriptor on the description drops a flock.
|
||||
int fd2 = open(PATH, O_RDWR);
|
||||
CHECK(flock(fd2, LOCK_EX) == 0, "flock on a second description");
|
||||
close(fd2);
|
||||
CHECK(child(try_flock_ex) == 0, "close releases flock");
|
||||
|
||||
// flock and fcntl locks do not see each other.
|
||||
CHECK(flock(fd, LOCK_EX) == 0 && child(try_range_10_19) == 0,
|
||||
"fcntl ignores flock");
|
||||
flock(fd, LOCK_UN);
|
||||
|
||||
// Ranges: a lock on 0..39 with 15..16 unlocked is two locks.
|
||||
CHECK(set(fd, F_SETLK, F_WRLCK, 0, 40) == 0, "F_SETLK 0..39");
|
||||
CHECK(child(try_range_50_59) == 0, "outside the range is free");
|
||||
CHECK(child(try_range_10_19) == 1, "inside the range is not");
|
||||
CHECK(set(fd, F_SETLK, F_UNLCK, 10, 10) == 0, "unlock 10..19");
|
||||
CHECK(child(try_range_10_19) == 0, "the hole is free");
|
||||
|
||||
struct flock q = { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 5 };
|
||||
pid_t p = fork();
|
||||
if (p == 0) {
|
||||
int cfd = open(PATH, O_RDWR);
|
||||
_exit(fcntl(cfd, F_GETLK, &q) == 0 && q.l_type == F_WRLCK &&
|
||||
q.l_pid == getppid() && q.l_start == 0 && q.l_len == 10 ? 0 : 1);
|
||||
}
|
||||
int st;
|
||||
waitpid(p, &st, 0);
|
||||
CHECK(WIFEXITED(st) && WEXITSTATUS(st) == 0, "F_GETLK reports the holder");
|
||||
|
||||
// A process's record locks go when it closes *any* descriptor on the file.
|
||||
int fd3 = open(PATH, O_RDONLY);
|
||||
close(fd3);
|
||||
CHECK(child(try_range_10_19) == 0 && child(try_range_50_59) == 0,
|
||||
"closing another descriptor releases the process's locks");
|
||||
|
||||
// A blocked waiter is woken when the lock goes.
|
||||
CHECK(set(fd, F_SETLK, F_WRLCK, 0, 40) == 0, "relock");
|
||||
p = fork();
|
||||
if (p == 0)
|
||||
_exit(wait_range_10_19());
|
||||
usleep(100000);
|
||||
CHECK(waitpid(p, &st, WNOHANG) == 0, "F_SETLKW waits");
|
||||
set(fd, F_SETLK, F_UNLCK, 0, 0);
|
||||
waitpid(p, &st, 0);
|
||||
CHECK(WIFEXITED(st) && WEXITSTATUS(st) == 0, "F_SETLKW is woken by the unlock");
|
||||
|
||||
// And they go when the process exits.
|
||||
CHECK(child(lock_and_exit) == 0 && set(fd, F_SETLK, F_WRLCK, 0, 0) == 0,
|
||||
"exit releases record locks");
|
||||
|
||||
close(fd);
|
||||
unlink(PATH);
|
||||
printf(bad ? "locktest: %d failed\n" : "locktest: ok\n", bad);
|
||||
return bad != 0;
|
||||
}
|
||||
Loading…
Reference in a new issue