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
11
Makefile
11
Makefile
|
|
@ -55,7 +55,7 @@ KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/storage/usb_storage.o
|
|||
KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/hub/usb_hub.o
|
||||
|
||||
KERN_FS = $(addprefix $(BUILD)/kernel/fs/, \
|
||||
exec.o file.o fs.o log.o pipe.o procfs.o devfs.o sysfs.o)
|
||||
exec.o file.o fs.o log.o pipe.o procfs.o devfs.o sysfs.o lock.o)
|
||||
|
||||
# SchFS. Every one of these is the very same source the host tools compile, so
|
||||
# the kernel and mkschfs cannot disagree about the on-disk format: there is one
|
||||
|
|
@ -128,7 +128,7 @@ KERN_NOENTRY = $(filter-out $(BUILD)/kernel/arch/x86_64/entry.o, $(KERNOBJS))
|
|||
# Third-party software does NOT go here — see ports/.
|
||||
# -----------------------------------------------------------------------
|
||||
ULIB = $(addprefix $(BUILD)/lib/, start.o glue.o usys.o)
|
||||
CMDS = schinit schinitd fbinfo shmtest socktest inputtest nettest reflink schattr ping chvt loadkeys sigtest memfdtest pcitest vgpud schdbg schdbgd resolvconf httpd schmode netecho ldlsd tone hwreport dynhello evtest xready
|
||||
CMDS = schinit schinitd fbinfo shmtest socktest inputtest nettest reflink schattr ping chvt loadkeys sigtest memfdtest pcitest vgpud schdbg schdbgd resolvconf httpd schmode netecho ldlsd tone hwreport dynhello evtest xready locktest
|
||||
CMDBINS = $(addprefix $(BUILD)/cmd/, $(CMDS))
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
|
@ -1073,6 +1073,13 @@ $(BUILD)/cmd/xready: cmd/xready/main.c $(MUSLPREFIX)/lib/libc.so $(LINUXHDR)
|
|||
$(MUSLPREFIX)/lib/crt1.o $(MUSLPREFIX)/lib/crti.o $(BUILD)/cmd/xready.o \
|
||||
-L$(MUSLPREFIX)/lib -lc $(PORTEND)
|
||||
|
||||
$(BUILD)/cmd/locktest: cmd/locktest/main.c $(MUSLPREFIX)/lib/libc.so
|
||||
@mkdir -p $(BUILD)/cmd
|
||||
$(DYNCC) -fno-pic -c $< -o $(BUILD)/cmd/locktest.o
|
||||
$(DYNCC) -no-pie -nostdlib $(DYNLD) -o $@ \
|
||||
$(MUSLPREFIX)/lib/crt1.o $(MUSLPREFIX)/lib/crti.o $(BUILD)/cmd/locktest.o \
|
||||
-L$(MUSLPREFIX)/lib -lc $(PORTEND)
|
||||
|
||||
$(BUILD)/cmd/libdynhello.so: cmd/dynhello/lib.c $(MUSLPREFIX)/lib/libc.so
|
||||
@mkdir -p $(BUILD)/cmd
|
||||
$(DYNCC) -fPIC -c $< -o $(BUILD)/cmd/libdynhello.o
|
||||
|
|
|
|||
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;
|
||||
}
|
||||
|
|
@ -110,6 +110,12 @@ struct file* filealloc(void);
|
|||
int fdalloc(struct file*);
|
||||
|
||||
// sys_event.c: eventfd, timerfd, signalfd and epoll, as files
|
||||
// kernel/fs/lock.c
|
||||
int lock_flock(struct file *f, int op);
|
||||
int lock_fcntl(struct file *f, int cmd, addr_t arg);
|
||||
void lock_close_fd(struct file *f);
|
||||
void lock_close_file(struct file *f);
|
||||
void lock_exit(int pid);
|
||||
struct evobj;
|
||||
void ev_close(struct evobj *);
|
||||
int ev_poll(struct file *, int events);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
#define EPIPE 32 // Broken pipe
|
||||
#define ERANGE 34 // Math result not representable
|
||||
#define ENAMETOOLONG 36 // File name too long
|
||||
#define ENOLCK 37 // No record locks available
|
||||
#define ENOSYS 38 // Function not implemented
|
||||
#define ENOTEMPTY 39 // Directory not empty
|
||||
#define ELOOP 40 // Too many symbolic links encountered
|
||||
|
|
|
|||
|
|
@ -39,5 +39,12 @@
|
|||
#define F_GETFL 3
|
||||
#define F_SETFL 4
|
||||
#define F_DUPFD_CLOEXEC 1030 // Linux value
|
||||
// Record locks; see kernel/fs/lock.c.
|
||||
#define F_GETLK 5
|
||||
#define F_SETLK 6
|
||||
#define F_SETLKW 7
|
||||
#define F_OFD_GETLK 36
|
||||
#define F_OFD_SETLK 37
|
||||
#define F_OFD_SETLKW 38
|
||||
|
||||
#define FD_CLOEXEC 1
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -657,6 +657,9 @@ exit(int status)
|
|||
}
|
||||
}
|
||||
|
||||
// Record locks belong to the process, not to any one descriptor.
|
||||
lock_exit(myproc()->pid);
|
||||
|
||||
begin_op();
|
||||
iput(myproc()->cwd);
|
||||
end_op();
|
||||
|
|
|
|||
|
|
@ -332,10 +332,20 @@ addr_t sys_close(void)
|
|||
if (argfd(0, &fd, &f) < 0) return -EBADF;
|
||||
myproc()->ofile[fd] = 0;
|
||||
myproc()->ofile_flags[fd] = 0;
|
||||
lock_close_fd(f);
|
||||
fileclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t sys_flock(void)
|
||||
{
|
||||
struct file *f;
|
||||
int op;
|
||||
if (argfd(0, 0, &f) < 0) return -EBADF;
|
||||
if (argint(1, &op) < 0) return -EINVAL;
|
||||
return lock_flock(f, op);
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_fcntl(void)
|
||||
{
|
||||
|
|
@ -374,6 +384,13 @@ sys_fcntl(void)
|
|||
case F_SETFL:
|
||||
f->nonblock = (arg & O_NONBLOCK) != 0;
|
||||
return 0;
|
||||
case F_GETLK: case F_SETLK: case F_SETLKW:
|
||||
case F_OFD_GETLK: case F_OFD_SETLK: case F_OFD_SETLKW: {
|
||||
// A pointer, which argint() above would have cut to 32 bits.
|
||||
uint64 a;
|
||||
if (argaddr(2, &a) < 0) return -EFAULT;
|
||||
return lock_fcntl(f, cmd, (addr_t)a);
|
||||
}
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -295,6 +295,7 @@ SYSCALL(202, 213, epoll_create)
|
|||
SYSCALL(203, 281, epoll_pwait)
|
||||
SYSCALL(204, 282, signalfd)
|
||||
SYSCALL(205, 284, eventfd)
|
||||
SYSCALL(206, 73, flock)
|
||||
|
||||
// --- mounts ---
|
||||
SYSCALL(185, 165, mount)
|
||||
|
|
|
|||
12
shell.nix
12
shell.nix
|
|
@ -38,6 +38,18 @@ pkgs.mkShell {
|
|||
xorriso # make iso
|
||||
mtools # ESP staging
|
||||
curl gnutar # fetching ports
|
||||
xz # ...some of which are .tar.xz
|
||||
|
||||
# The X ports (docs/plan/xorg.md): half of them configure with autotools
|
||||
# and half with meson, and all of them ask pkg-config for each other.
|
||||
# libxcb generates its protocol code with python; libX11 and xkbcomp need
|
||||
# m4 and bison at build time.
|
||||
pkg-config
|
||||
meson
|
||||
ninja
|
||||
gnum4
|
||||
bison
|
||||
flex
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
|
|
|
|||
Loading…
Reference in a new issue