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
|
|
@ -96,6 +96,9 @@ fileclose(struct file *f)
|
|||
release(&ftable.lock);
|
||||
return;
|
||||
}
|
||||
// Its flock() and OFD locks go with it, before the slot can be handed to
|
||||
// someone else: they are keyed by this address.
|
||||
lock_close_file(f);
|
||||
ff = *f;
|
||||
f->ref = 0;
|
||||
f->type = FD_NONE;
|
||||
|
|
|
|||
351
kernel/fs/lock.c
Normal file
351
kernel/fs/lock.c
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
// Advisory file locks: flock(2), and fcntl(2)'s record locks.
|
||||
//
|
||||
// Two kinds that Linux keeps entirely separate, and so does this:
|
||||
//
|
||||
// flock the whole file, owned by the open file description -- shared
|
||||
// by dup() and fork(), released when the last descriptor on it
|
||||
// is closed.
|
||||
// fcntl byte ranges. F_SETLK and friends are owned by the *process*
|
||||
// and released when it closes any descriptor on the file, or
|
||||
// exits. F_OFD_SETLK and friends are owned by the open file
|
||||
// description, like flock, but take ranges, like fcntl.
|
||||
//
|
||||
// They existed as nothing at all: fcntl answered F_SETLK with EINVAL and
|
||||
// flock was not a system call, so Alpine's apk -- which takes a lock on its
|
||||
// database before reading it -- stopped with "Unable to lock database:
|
||||
// Function not implemented".
|
||||
//
|
||||
// One table for the machine, a sleeplock-free spinlock around it, and every
|
||||
// waiter sleeping on the table: locks change rarely, and a release wakes
|
||||
// everyone to look again. No deadlock detection. A lock is keyed by the
|
||||
// inode's (dev, inum), not its pointer, because a process lock outlives the
|
||||
// descriptor in the one case this file does not catch -- dup2() over it.
|
||||
|
||||
#include "core/types.h"
|
||||
#include "core/defs.h"
|
||||
#include "core/param.h"
|
||||
#include "arch/mmu.h"
|
||||
#include "core/proc.h"
|
||||
#include "core/spinlock.h"
|
||||
#include "core/sleeplock.h"
|
||||
#include "fs/fs.h"
|
||||
#include "fs/file.h"
|
||||
#include "core/uaccess.h"
|
||||
#include "uapi/errno.h"
|
||||
#include "uapi/fcntl.h"
|
||||
|
||||
#define NLOCK 256
|
||||
|
||||
#define K_FLOCK 1 // whole file, owner a struct file
|
||||
#define K_POSIX 2 // range, owner a pid
|
||||
#define K_OFD 3 // range, owner a struct file
|
||||
|
||||
#define L_RD 0 // F_RDLCK
|
||||
#define L_WR 1 // F_WRLCK
|
||||
#define L_UN 2 // F_UNLCK
|
||||
|
||||
#define END_OF_TIME ((uint64)-1)
|
||||
|
||||
struct flk {
|
||||
int used;
|
||||
int kind;
|
||||
uint dev;
|
||||
uint64 inum;
|
||||
void *ofd; // K_FLOCK, K_OFD
|
||||
int pid; // K_POSIX
|
||||
int type; // L_RD or L_WR
|
||||
uint64 start, end; // inclusive; end END_OF_TIME for "to EOF"
|
||||
};
|
||||
|
||||
static struct {
|
||||
struct spinlock lock;
|
||||
struct flk l[NLOCK];
|
||||
int ready;
|
||||
} lk;
|
||||
|
||||
static void
|
||||
lk_init(void)
|
||||
{
|
||||
if (!lk.ready) {
|
||||
initlock(&lk.lock, "flock");
|
||||
lk.ready = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
same_file(struct flk *a, struct inode *ip)
|
||||
{
|
||||
return a->used && a->dev == ip->dev && a->inum == ip->inum;
|
||||
}
|
||||
|
||||
// flock() conflicts only with flock(); the record kinds only with each
|
||||
// other -- POSIX and OFD locks do see one another, as on Linux.
|
||||
static int
|
||||
same_family(int a, int b)
|
||||
{
|
||||
return (a == K_FLOCK) == (b == K_FLOCK);
|
||||
}
|
||||
|
||||
static int
|
||||
mine(struct flk *a, int kind, void *ofd, int pid)
|
||||
{
|
||||
if (a->kind != kind)
|
||||
return 0;
|
||||
return kind == K_POSIX ? a->pid == pid : a->ofd == ofd;
|
||||
}
|
||||
|
||||
// The first lock that would stop this one, or 0.
|
||||
static struct flk *
|
||||
conflict(struct inode *ip, int kind, void *ofd, int pid, int type,
|
||||
uint64 s, uint64 e)
|
||||
{
|
||||
for (int i = 0; i < NLOCK; i++) {
|
||||
struct flk *a = &lk.l[i];
|
||||
if (!same_file(a, ip) || !same_family(a->kind, kind) ||
|
||||
mine(a, kind, ofd, pid))
|
||||
continue;
|
||||
if (a->end < s || a->start > e)
|
||||
continue;
|
||||
if (a->type == L_WR || type == L_WR)
|
||||
return a;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct flk *
|
||||
slot(void)
|
||||
{
|
||||
for (int i = 0; i < NLOCK; i++)
|
||||
if (!lk.l[i].used)
|
||||
return &lk.l[i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Take [s, e] out of every lock this owner holds on the file, splitting a
|
||||
// lock that straddles it. Returns -ENOLCK if a split needed a slot and there
|
||||
// was none, having changed nothing.
|
||||
static int
|
||||
carve(struct inode *ip, int kind, void *ofd, int pid, uint64 s, uint64 e)
|
||||
{
|
||||
int need = 0, free = 0;
|
||||
|
||||
for (int i = 0; i < NLOCK; i++) {
|
||||
struct flk *a = &lk.l[i];
|
||||
if (!a->used)
|
||||
free++;
|
||||
else if (same_file(a, ip) && mine(a, kind, ofd, pid) &&
|
||||
a->start < s && a->end > e)
|
||||
need++;
|
||||
}
|
||||
if (need > free)
|
||||
return -ENOLCK;
|
||||
|
||||
for (int i = 0; i < NLOCK; i++) {
|
||||
struct flk *a = &lk.l[i];
|
||||
if (!same_file(a, ip) || !mine(a, kind, ofd, pid))
|
||||
continue;
|
||||
if (a->end < s || a->start > e)
|
||||
continue;
|
||||
if (a->start < s && a->end > e) { // the middle goes: two pieces
|
||||
struct flk *b = slot();
|
||||
*b = *a;
|
||||
b->start = e + 1;
|
||||
a->end = s - 1;
|
||||
} else if (a->start < s) {
|
||||
a->end = s - 1;
|
||||
} else if (a->end > e) {
|
||||
a->start = e + 1;
|
||||
} else {
|
||||
a->used = 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
set_lock(struct inode *ip, int kind, void *ofd, int pid, int type,
|
||||
uint64 s, uint64 e, int wait)
|
||||
{
|
||||
lk_init();
|
||||
acquire(&lk.lock);
|
||||
if (type != L_UN) {
|
||||
while (conflict(ip, kind, ofd, pid, type, s, e)) {
|
||||
if (!wait) {
|
||||
release(&lk.lock);
|
||||
return -EAGAIN; // EWOULDBLOCK, the same number
|
||||
}
|
||||
if (myproc()->killed || myproc()->pending_signals) {
|
||||
release(&lk.lock);
|
||||
return -EINTR;
|
||||
}
|
||||
sleep(&lk, &lk.lock);
|
||||
}
|
||||
}
|
||||
int r = carve(ip, kind, ofd, pid, s, e);
|
||||
if (r == 0 && type != L_UN) {
|
||||
struct flk *a = slot();
|
||||
if (!a) {
|
||||
r = -ENOLCK;
|
||||
} else {
|
||||
a->used = 1;
|
||||
a->kind = kind;
|
||||
a->dev = ip->dev;
|
||||
a->inum = ip->inum;
|
||||
a->ofd = ofd;
|
||||
a->pid = pid;
|
||||
a->type = type;
|
||||
a->start = s;
|
||||
a->end = e;
|
||||
}
|
||||
}
|
||||
release(&lk.lock);
|
||||
wakeup(&lk);
|
||||
return r;
|
||||
}
|
||||
|
||||
// Every lock matching (owner, maybe file), gone.
|
||||
static void
|
||||
drop(struct inode *ip, int pid, void *ofd)
|
||||
{
|
||||
int any = 0;
|
||||
|
||||
if (!lk.ready)
|
||||
return;
|
||||
acquire(&lk.lock);
|
||||
for (int i = 0; i < NLOCK; i++) {
|
||||
struct flk *a = &lk.l[i];
|
||||
if (!a->used || (ip && !same_file(a, ip)))
|
||||
continue;
|
||||
if ((pid && a->kind == K_POSIX && a->pid == pid) ||
|
||||
(ofd && a->kind != K_POSIX && a->ofd == ofd)) {
|
||||
a->used = 0;
|
||||
any = 1;
|
||||
}
|
||||
}
|
||||
release(&lk.lock);
|
||||
if (any)
|
||||
wakeup(&lk);
|
||||
}
|
||||
|
||||
// close() of one descriptor: the process's record locks on that file go, as
|
||||
// POSIX requires -- including ones taken through a different descriptor.
|
||||
void
|
||||
lock_close_fd(struct file *f)
|
||||
{
|
||||
if (f->type == FD_INODE && f->ip)
|
||||
drop(f->ip, myproc()->pid, 0);
|
||||
}
|
||||
|
||||
// The open file description itself is going: its flock and OFD locks.
|
||||
void
|
||||
lock_close_file(struct file *f)
|
||||
{
|
||||
drop(0, 0, f);
|
||||
}
|
||||
|
||||
// exit(): every record lock the process holds, on every file.
|
||||
void
|
||||
lock_exit(int pid)
|
||||
{
|
||||
drop(0, pid, 0);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// flock(2)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define LOCK_SH 1
|
||||
#define LOCK_EX 2
|
||||
#define LOCK_NB 4
|
||||
#define LOCK_UN 8
|
||||
|
||||
int
|
||||
lock_flock(struct file *f, int op)
|
||||
{
|
||||
// Nothing to lock but a file; a socket or a pipe accepts it and means
|
||||
// nothing by it, as on Linux.
|
||||
if (f->type != FD_INODE || !f->ip)
|
||||
return 0;
|
||||
|
||||
int type;
|
||||
switch (op & ~LOCK_NB) {
|
||||
case LOCK_SH: type = L_RD; break;
|
||||
case LOCK_EX: type = L_WR; break;
|
||||
case LOCK_UN: type = L_UN; break;
|
||||
default: return -EINVAL;
|
||||
}
|
||||
// Converting a lock is not atomic on Linux either: the old one goes first.
|
||||
if (type != L_UN)
|
||||
set_lock(f->ip, K_FLOCK, f, 0, L_UN, 0, END_OF_TIME, 0);
|
||||
return set_lock(f->ip, K_FLOCK, f, 0, type, 0, END_OF_TIME,
|
||||
!(op & LOCK_NB));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// fcntl(2): F_GETLK, F_SETLK, F_SETLKW and the OFD forms
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct linux_flock {
|
||||
short l_type;
|
||||
short l_whence;
|
||||
int64 l_start;
|
||||
int64 l_len;
|
||||
int l_pid;
|
||||
};
|
||||
|
||||
int
|
||||
lock_fcntl(struct file *f, int cmd, addr_t arg)
|
||||
{
|
||||
struct linux_flock *fl = user_ptr(arg, sizeof *fl);
|
||||
if (!fl)
|
||||
return -EFAULT;
|
||||
if (f->type != FD_INODE || !f->ip)
|
||||
return -EBADF;
|
||||
if (fl->l_type != L_RD && fl->l_type != L_WR && fl->l_type != L_UN)
|
||||
return -EINVAL;
|
||||
if ((fl->l_type == L_RD && !f->readable) || (fl->l_type == L_WR && !f->writable))
|
||||
if (cmd != F_GETLK && cmd != F_OFD_GETLK)
|
||||
return -EBADF;
|
||||
|
||||
int64 base;
|
||||
switch (fl->l_whence) {
|
||||
case 0: base = 0; break;
|
||||
case 1: base = (int64)f->off; break;
|
||||
case 2: base = (int64)f->ip->size; break;
|
||||
default: return -EINVAL;
|
||||
}
|
||||
int64 s = base + fl->l_start, len = fl->l_len;
|
||||
uint64 e;
|
||||
if (len < 0) { // a negative length counts backwards
|
||||
s += len;
|
||||
len = -len;
|
||||
}
|
||||
if (s < 0)
|
||||
return -EINVAL;
|
||||
e = len == 0 ? END_OF_TIME : (uint64)(s + len - 1);
|
||||
|
||||
int ofd = cmd == F_OFD_GETLK || cmd == F_OFD_SETLK || cmd == F_OFD_SETLKW;
|
||||
int kind = ofd ? K_OFD : K_POSIX;
|
||||
int pid = myproc()->pid;
|
||||
|
||||
if (cmd == F_GETLK || cmd == F_OFD_GETLK) {
|
||||
if (ofd && fl->l_pid != 0)
|
||||
return -EINVAL;
|
||||
lk_init();
|
||||
acquire(&lk.lock);
|
||||
struct flk *c = conflict(f->ip, kind, f, pid, fl->l_type, (uint64)s, e);
|
||||
if (c) {
|
||||
fl->l_type = (short)c->type;
|
||||
fl->l_whence = 0;
|
||||
fl->l_start = (int64)c->start;
|
||||
fl->l_len = c->end == END_OF_TIME ? 0 : (int64)(c->end - c->start + 1);
|
||||
fl->l_pid = c->kind == K_POSIX ? c->pid : -1;
|
||||
} else {
|
||||
fl->l_type = L_UN;
|
||||
}
|
||||
release(&lk.lock);
|
||||
return 0;
|
||||
}
|
||||
return set_lock(f->ip, kind, f, pid, fl->l_type, (uint64)s, e,
|
||||
cmd == F_SETLKW || cmd == F_OFD_SETLKW);
|
||||
}
|
||||
Loading…
Reference in a new issue