// 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); }