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:
Nexoniarz 2026-10-24 12:22:36 +02:00
commit 6f3175d8a5
11 changed files with 562 additions and 2 deletions

View file

@ -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_DRIVERS += $(BUILD)/kernel/drivers/usb/hub/usb_hub.o
KERN_FS = $(addprefix $(BUILD)/kernel/fs/, \ 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 # 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 # 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/. # Third-party software does NOT go here — see ports/.
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
ULIB = $(addprefix $(BUILD)/lib/, start.o glue.o usys.o) 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)) 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 \ $(MUSLPREFIX)/lib/crt1.o $(MUSLPREFIX)/lib/crti.o $(BUILD)/cmd/xready.o \
-L$(MUSLPREFIX)/lib -lc $(PORTEND) -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 $(BUILD)/cmd/libdynhello.so: cmd/dynhello/lib.c $(MUSLPREFIX)/lib/libc.so
@mkdir -p $(BUILD)/cmd @mkdir -p $(BUILD)/cmd
$(DYNCC) -fPIC -c $< -o $(BUILD)/cmd/libdynhello.o $(DYNCC) -fPIC -c $< -o $(BUILD)/cmd/libdynhello.o

152
cmd/locktest/main.c Normal file
View 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;
}

View file

@ -110,6 +110,12 @@ struct file* filealloc(void);
int fdalloc(struct file*); int fdalloc(struct file*);
// sys_event.c: eventfd, timerfd, signalfd and epoll, as files // 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; struct evobj;
void ev_close(struct evobj *); void ev_close(struct evobj *);
int ev_poll(struct file *, int events); int ev_poll(struct file *, int events);

View file

@ -37,6 +37,7 @@
#define EPIPE 32 // Broken pipe #define EPIPE 32 // Broken pipe
#define ERANGE 34 // Math result not representable #define ERANGE 34 // Math result not representable
#define ENAMETOOLONG 36 // File name too long #define ENAMETOOLONG 36 // File name too long
#define ENOLCK 37 // No record locks available
#define ENOSYS 38 // Function not implemented #define ENOSYS 38 // Function not implemented
#define ENOTEMPTY 39 // Directory not empty #define ENOTEMPTY 39 // Directory not empty
#define ELOOP 40 // Too many symbolic links encountered #define ELOOP 40 // Too many symbolic links encountered

View file

@ -39,5 +39,12 @@
#define F_GETFL 3 #define F_GETFL 3
#define F_SETFL 4 #define F_SETFL 4
#define F_DUPFD_CLOEXEC 1030 // Linux value #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 #define FD_CLOEXEC 1

View file

@ -96,6 +96,9 @@ fileclose(struct file *f)
release(&ftable.lock); release(&ftable.lock);
return; 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; ff = *f;
f->ref = 0; f->ref = 0;
f->type = FD_NONE; f->type = FD_NONE;

351
kernel/fs/lock.c Normal file
View 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);
}

View file

@ -657,6 +657,9 @@ exit(int status)
} }
} }
// Record locks belong to the process, not to any one descriptor.
lock_exit(myproc()->pid);
begin_op(); begin_op();
iput(myproc()->cwd); iput(myproc()->cwd);
end_op(); end_op();

View file

@ -332,10 +332,20 @@ addr_t sys_close(void)
if (argfd(0, &fd, &f) < 0) return -EBADF; if (argfd(0, &fd, &f) < 0) return -EBADF;
myproc()->ofile[fd] = 0; myproc()->ofile[fd] = 0;
myproc()->ofile_flags[fd] = 0; myproc()->ofile_flags[fd] = 0;
lock_close_fd(f);
fileclose(f); fileclose(f);
return 0; 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 addr_t
sys_fcntl(void) sys_fcntl(void)
{ {
@ -374,6 +384,13 @@ sys_fcntl(void)
case F_SETFL: case F_SETFL:
f->nonblock = (arg & O_NONBLOCK) != 0; f->nonblock = (arg & O_NONBLOCK) != 0;
return 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; return -EINVAL;
} }

View file

@ -295,6 +295,7 @@ SYSCALL(202, 213, epoll_create)
SYSCALL(203, 281, epoll_pwait) SYSCALL(203, 281, epoll_pwait)
SYSCALL(204, 282, signalfd) SYSCALL(204, 282, signalfd)
SYSCALL(205, 284, eventfd) SYSCALL(205, 284, eventfd)
SYSCALL(206, 73, flock)
// --- mounts --- // --- mounts ---
SYSCALL(185, 165, mount) SYSCALL(185, 165, mount)

View file

@ -38,6 +38,18 @@ pkgs.mkShell {
xorriso # make iso xorriso # make iso
mtools # ESP staging mtools # ESP staging
curl gnutar # fetching ports 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 = '' shellHook = ''