diff --git a/kernel-src/arch/x86-64/syscall.asm b/kernel-src/arch/x86-64/syscall.asm index dd44f043..97a93354 100644 --- a/kernel-src/arch/x86-64/syscall.asm +++ b/kernel-src/arch/x86-64/syscall.asm @@ -93,6 +93,7 @@ extern syscall_sigpending extern syscall_killthread extern syscall_shutdown extern syscall_nice +extern syscall_flock syscalltab: dq syscall_print dq syscall_mmap @@ -188,7 +189,8 @@ dq syscall_sigpending dq syscall_killthread dq syscall_shutdown dq syscall_nice -syscallcount equ 94 +dq syscall_flock +syscallcount equ 95 section .text global arch_syscall_entry ; on entry: diff --git a/kernel-src/arch/x86-64/syscalllog.c b/kernel-src/arch/x86-64/syscalllog.c index b997b190..8855e3d1 100644 --- a/kernel-src/arch/x86-64/syscalllog.c +++ b/kernel-src/arch/x86-64/syscalllog.c @@ -5,7 +5,7 @@ #ifdef SYSCALL_LOGGING -#define SYSCALL_COUNT 94 +#define SYSCALL_COUNT 95 #define LOGSTR(x) arch_e9_puts(x) static char *name[] = { @@ -102,7 +102,8 @@ static char *name[] = { "sigpending", "killthread", "shutdown", - "nice" + "nice", + "flock" }; static char *args[] = { @@ -199,7 +200,8 @@ static char *args[] = { "sigset %p", // sigpending "pid %d tid %d signal %d", // killthread "how %d", // shutdown - "off %d" // nice + "off %d", // nice + "fd %d op %d" // flock }; #endif diff --git a/kernel-src/fs/devfs.c b/kernel-src/fs/devfs.c index 4374a4bd..2589a012 100644 --- a/kernel-src/fs/devfs.c +++ b/kernel-src/fs/devfs.c @@ -442,6 +442,14 @@ static int devfs_lock(vnode_t *vnode) { static int devfs_unlock(vnode_t *vnode) { return 0; } + +static int devfs_advlock(vnode_t *node, int op, advlock_t *advlock) { + devnode_t *devnode = (devnode_t *)node; + if (devnode->master) + devnode = devnode->master; + + return vfs_advlock((vnode_t *)devnode, op, advlock); +} static int devfs_enodev() { return ENODEV; @@ -479,6 +487,7 @@ static vops_t vnops = { .putpage = devfs_putpage, .getpage = devfs_getpage, .sync = devfs_sync, + .advlock = devfs_advlock, .lock = devfs_lock, .unlock = devfs_unlock }; diff --git a/kernel-src/fs/ext2.c b/kernel-src/fs/ext2.c index a8be6402..de7d710a 100644 --- a/kernel-src/fs/ext2.c +++ b/kernel-src/fs/ext2.c @@ -1823,6 +1823,10 @@ static int ext2_unlock(vnode_t *vnode) { return 0; } +static int ext2_advlock(vnode_t *node, int op, advlock_t *advlock) { + return vfs_advlock(node, op, advlock); +} + static int ext2_inactive(vnode_t *vnode) { ext2node_t *node = (ext2node_t *)vnode; ext2fs_t *fs = (ext2fs_t *)vnode->vfs; @@ -2010,6 +2014,7 @@ static vops_t vnops = { .getpage = ext2_getpage, .putpage = ext2_putpage, .sync = ext2_sync, + .advlock = ext2_advlock, .lock = ext2_lock, .unlock = ext2_unlock }; diff --git a/kernel-src/fs/file.c b/kernel-src/fs/file.c index f23966e0..77d125b3 100644 --- a/kernel-src/fs/file.c +++ b/kernel-src/fs/file.c @@ -20,6 +20,7 @@ static bool ctor(scache_t *cache, void *obj) { file->offset = 0; file->flags = 0; file->mode = 0; + file->advlock = NULL; file->refcount = 1; return true; } @@ -39,12 +40,18 @@ static void cleanfile(file_t *file) { VOP_RELEASE(file->vnode); } - file->vnode = NULL; file->refcount = 1; file->offset = 0; file->flags = 0; file->mode = 0; MUTEX_INIT(&file->mutex); + if (file->advlock) { + // try unlocking it if applicable + VOP_ADVLOCK(file->vnode, ADVLOCK_UNLOCK, file->advlock); + ADVLOCK_UNREF(file->advlock); + file->advlock = NULL; + } + file->vnode = NULL; slab_free(filecache, file); } diff --git a/kernel-src/fs/pipefs.c b/kernel-src/fs/pipefs.c index 3aa2ccdb..41d958ff 100644 --- a/kernel-src/fs/pipefs.c +++ b/kernel-src/fs/pipefs.c @@ -364,6 +364,10 @@ static int pipefs_unlock(vnode_t *vnode) { return 0; } +static int pipefs_advlock(vnode_t *node, int op, advlock_t *advlock) { + return vfs_advlock(node, op, advlock); +} + static int pipefs_enodev() { return ENODEV; } @@ -392,6 +396,7 @@ static vops_t vnops = { .putpage = pipefs_enodev, .getpage = pipefs_enodev, .sync = pipefs_enodev, + .advlock = pipefs_advlock, .lock = pipefs_lock, .unlock = pipefs_unlock }; diff --git a/kernel-src/fs/sockfs.c b/kernel-src/fs/sockfs.c index a3af5902..c18f797b 100644 --- a/kernel-src/fs/sockfs.c +++ b/kernel-src/fs/sockfs.c @@ -229,6 +229,10 @@ static int sockfs_unlock(vnode_t *) { return 0; } +static int sockfs_advlock(vnode_t *node, int op, advlock_t *advlock) { + return vfs_advlock(node, op, advlock); +} + static int sockfs_enodev() { return ENODEV; } @@ -258,6 +262,7 @@ static vops_t vnops = { .putpage = sockfs_enodev, .getpage = sockfs_enodev, .sync = sockfs_enodev, + .advlock = sockfs_advlock, .lock = sockfs_lock, .unlock = sockfs_unlock }; diff --git a/kernel-src/fs/tmpfs.c b/kernel-src/fs/tmpfs.c index 7481086a..f4f7a56d 100644 --- a/kernel-src/fs/tmpfs.c +++ b/kernel-src/fs/tmpfs.c @@ -503,6 +503,10 @@ static int tmpfs_sync(vnode_t *node) { return 0; } +static int tmpfs_advlock(vnode_t *node, int op, advlock_t *advlock) { + return vfs_advlock(node, op, advlock); +} + static int tmpfs_lock(vnode_t *node) { MUTEX_ACQUIRE(&node->lock); return 0; @@ -542,6 +546,7 @@ static vops_t vnops = { .getpage = tmpfs_getpage, .putpage = tmpfs_putpage, .sync = tmpfs_sync, + .advlock = tmpfs_advlock, .lock = tmpfs_lock, .unlock = tmpfs_unlock }; diff --git a/kernel-src/fs/vfs.c b/kernel-src/fs/vfs.c index 1e0d7fa4..58290498 100644 --- a/kernel-src/fs/vfs.c +++ b/kernel-src/fs/vfs.c @@ -12,6 +12,7 @@ #include #include #include +#include #define PATHNAME_MAX 512 #define MAXLINKDEPTH 64 @@ -21,6 +22,7 @@ vnode_t *vfsroot; static mutex_t listlock; static vfs_t *vfslist; +static scache_t *advlock_cache; static cred_t *getcred() { if (current_thread() == NULL || current_thread()->proc == NULL) @@ -50,6 +52,15 @@ static vops_t vnops = { .unlock = noop }; +static bool advlock_ctor(scache_t *cache, void *_obj) { + advlock_t *obj = _obj; + obj->type = ADVLOCK_UNLOCK; + obj->shared_count = 0; + obj->refcount = 1; + EVENT_INITHEADER(&obj->unlock_event); + return true; +} + void vfs_init() { __assert(hashtable_init(&fstable, 20) == 0); vfsroot = alloc(sizeof(vnode_t)); @@ -58,10 +69,21 @@ void vfs_init() { vfsroot->type = V_TYPE_DIR; vfsroot->refcount = 1; vfsroot->ops = &vnops; + advlock_cache = slab_newcache(sizeof(advlock_t), 0, advlock_ctor, NULL); + __assert(advlock_cache); } INIT_ROUTINE_DEFINE(vfs, INIT_ROUTINE_FLAGS_NONE, vfs_init, bsp_early); +advlock_t *advlock_allocate(void) { + return slab_allocate(advlock_cache); +} + +void advlock_free(advlock_t *advlock) { + advlock->refcount = 1; + slab_free(advlock_cache, advlock); +} + int vfs_register(vfsops_t *ops, char *name) { return hashtable_set(&fstable, ops, name, strlen(name), true); } @@ -166,6 +188,140 @@ int vfs_close(vnode_t *node, int flags) { return err; } +static void adv_unlock(vnode_t *vnode, int op, advlock_t *lock) { + MUTEX_ACQUIRE(&vnode->adv_mutex); + + switch (lock->type) { + // is this lock not attached anywhere? + case ADVLOCK_UNLOCK: + goto leave; + case ADVLOCK_EXCLUSIVE: + __assert(vnode->advlock == lock); + + EVENT_SIGNAL(&lock->unlock_event); + ADVLOCK_UNREF(lock); + vnode->advlock = NULL; + + lock->type = ADVLOCK_UNLOCK; + + break; + case ADVLOCK_SHARED: + __assert(vnode->advlock && vnode->advlock->type == ADVLOCK_SHARED); + lock->type = ADVLOCK_UNLOCK; + + if (--vnode->advlock->shared_count == 0) { + // we do not change the type as this will be destroyed + // (shared locks allocate an extra advlock) + EVENT_SIGNAL(&vnode->advlock->unlock_event); + ADVLOCK_UNREF(vnode->advlock); + vnode->advlock = NULL; + } + } + + leave: + MUTEX_RELEASE(&vnode->adv_mutex); +} + +static int adv_lock(vnode_t *vnode, int op, advlock_t *lock, bool non_blocking) { + retry: + MUTEX_ACQUIRE(&vnode->adv_mutex); + int error = 0; + + // do we have to convert the lock? + if ((lock->type == ADVLOCK_EXCLUSIVE && op == ADVLOCK_SHARED) || + (lock->type == ADVLOCK_SHARED && op == ADVLOCK_EXCLUSIVE)) { + error = EDEADLK; + goto leave; + } + + // have we already taken it? + if (lock->type == op) { + goto leave; + } + + if ((op == ADVLOCK_EXCLUSIVE && vnode->advlock && vnode->advlock != lock) || + (op == ADVLOCK_SHARED && vnode->advlock && vnode->advlock->type == ADVLOCK_EXCLUSIVE) + ) { + // can't take lock, sleep + if (non_blocking) { + error = EWOULDBLOCK; + goto leave; + } + + advlock_t *sleeping_on = vnode->advlock; + ADVLOCK_REF(sleeping_on); + + eventlistener_t listener; + EVENT_INITLISTENER(&listener); + EVENT_ATTACH(&listener, &sleeping_on->unlock_event); + + MUTEX_RELEASE(&vnode->adv_mutex); + + error = EVENT_WAIT(&listener, 0); + EVENT_DETACHALL(&listener); + + ADVLOCK_UNREF(sleeping_on); + + // interrupted by a signal? + if (error) + return error; + + goto retry; + } + + // can take the lock! + lock->type = op; + + if (vnode->advlock == NULL) { + // unlocked advlock + if (op == ADVLOCK_SHARED) { + // shared locks allocate a dummy one + vnode->advlock = advlock_allocate(); + if (vnode->advlock == NULL) { + error = ENOMEM; + goto leave; + } + } else { + // exclusive lock + vnode->advlock = lock; + ADVLOCK_REF(vnode->advlock); + } + } else { + // shared lock on shared advlock + ++vnode->advlock->shared_count; + } + + leave: + MUTEX_RELEASE(&vnode->adv_mutex); + return error; +} + +// expected to be called from inside filesystem specific code +// adv_mutex is expected to be unlocked +// returns 0 if successful lock, EWOULDBLOCK if unsucessful and non-blocking +// a ADVLOCK_UNLOCK operation decreases the shared count or releases an exclusive lock if applicable, waking all waiters in case of a successful unlock +// a ADVLOCK_SHARED operation takes an unlocked lock or increments the shared count of a shared one +// a ADVLOCK_EXCLUSIVE operation either places the lock or sleeps waiting to do so +int vfs_advlock(vnode_t *node, int op, advlock_t *lock) { + int error = 0; + int non_blocking = op & ADVLOCK_NON_BLOCKING; + op &= ~ADVLOCK_NON_BLOCKING; + + if (op == ADVLOCK_UNLOCK) { + adv_unlock(node, op, lock); + } else { + retry: + error = adv_lock(node, op, lock, non_blocking); + if (error == EDEADLK) { + // EDEADLK is a way of adv_lock signaling that the lock should be converted, and thus must be unlocked and then relocked non-atomically + adv_unlock(node, ADVLOCK_UNLOCK, lock); + goto retry; + } + } + + return error; +} + // if node is not NULL, then a reference is kept and the newnode is returned in node // ref is expected to be unlocked. // the new node is returned locked diff --git a/kernel-src/include/kernel/file.h b/kernel-src/include/kernel/file.h index 6d71e5ad..0f2fb2cd 100644 --- a/kernel-src/include/kernel/file.h +++ b/kernel-src/include/kernel/file.h @@ -4,6 +4,7 @@ #include #include #include +#include typedef struct file_t { vnode_t *vnode; @@ -12,6 +13,7 @@ typedef struct file_t { mode_t mode; uintmax_t offset; int flags; + advlock_t *advlock; } file_t; typedef struct fd_t { diff --git a/kernel-src/include/kernel/poll.h b/kernel-src/include/kernel/poll.h index 15ab46b1..3cd7c7ba 100644 --- a/kernel-src/include/kernel/poll.h +++ b/kernel-src/include/kernel/poll.h @@ -31,7 +31,8 @@ typedef struct polldata { } polldata_t; #define POLL_INITHEADER(x) \ - SPINLOCK_INIT((x)->lock); + SPINLOCK_INIT((x)->lock); \ + (x)->data = NULL; int poll_initdesc(polldesc_t *, size_t size); void poll_add(pollheader_t *, polldata_t *, int events); diff --git a/kernel-src/include/kernel/vfs.h b/kernel-src/include/kernel/vfs.h index 6d3673be..6024e75e 100644 --- a/kernel-src/include/kernel/vfs.h +++ b/kernel-src/include/kernel/vfs.h @@ -9,6 +9,7 @@ #include #include #include +#include #define V_ATTR_MODE 1 #define V_ATTR_UID 2 @@ -71,6 +72,21 @@ typedef struct vfs_t { int flags; } vfs_t; +#define ADVLOCK_SHARED 1 +#define ADVLOCK_EXCLUSIVE 2 +#define ADVLOCK_NON_BLOCKING 4 +#define ADVLOCK_UNLOCK 8 + +typedef struct advlock_t { + int type; + int refcount; + int shared_count; + eventheader_t unlock_event; +} advlock_t; + +#define ADVLOCK_REF(x) __atomic_fetch_add(&(x)->refcount, 1, __ATOMIC_SEQ_CST); +#define ADVLOCK_UNREF(x) if (__atomic_sub_fetch(&(x)->refcount, 1, __ATOMIC_SEQ_CST) == 0) advlock_free(x); + #define V_FLAGS_ROOT 1 #define V_TYPE_REGULAR 0 @@ -105,6 +121,9 @@ typedef struct vnode_t { mutex_t pages_mutex; struct page_t *pages; + + mutex_t adv_mutex; + advlock_t *advlock; } vnode_t; typedef struct vfsops_t { @@ -143,6 +162,7 @@ typedef struct vops_t { int (*getpage)(vnode_t *node, uintmax_t offset, struct page_t *page); int (*putpage)(vnode_t *node, uintmax_t offset, struct page_t *page); int (*sync)(vnode_t *node); + int (*advlock)(vnode_t *node, int op, advlock_t *advlock); int (*lock)(vnode_t *node); int (*unlock)(vnode_t *node); } vops_t; @@ -164,6 +184,8 @@ typedef struct vops_t { MUTEX_INIT(&(vn)->lock); \ MUTEX_INIT(&(vn)->size_lock); \ MUTEX_INIT(&(vn)->pages_mutex); \ + MUTEX_INIT(&(vn)->adv_mutex); \ + (vn)->advlock = NULL; \ (vn)->refcount = 1; \ (vn)->flags = f; \ (vn)->type = t; \ @@ -201,6 +223,7 @@ typedef struct vops_t { #define VOP_GETPAGE(v, o, p) (v)->ops->getpage(v, o, p) #define VOP_PUTPAGE(v, o, p) (v)->ops->putpage(v, o, p) #define VOP_SYNC(v) (v)->ops->sync(v) +#define VOP_ADVLOCK(v, o, a) (v)->ops->advlock(v, o, a) #define VOP_HOLD(v) __atomic_add_fetch(&(v)->refcount, 1, __ATOMIC_SEQ_CST) #define VOP_RELEASE(v) {\ if (__atomic_sub_fetch(&(v)->refcount, 1, __ATOMIC_SEQ_CST) == 0) {\ @@ -211,6 +234,9 @@ typedef struct vops_t { extern vnode_t *vfsroot; +advlock_t *advlock_allocate(void); +void advlock_free(advlock_t *advlock); + void vfs_init(); int vfs_mount(vnode_t *backing, vnode_t *pathref, char *path, char *name, void *data); int vfs_register(vfsops_t *vfsops, char *name); @@ -226,6 +252,7 @@ int vfs_link(vnode_t *destref, char *destpath, vnode_t *linkref, char *linkpath, int vfs_rename(vnode_t *srcref, char *srcpath, vnode_t *dstref, char *dstpath, int flags); int vfs_unlink(vnode_t *ref, char *path); int vfs_pollstub(vnode_t *node, struct polldata *, int events); +int vfs_advlock(vnode_t *node, int op, advlock_t *lock); void vfs_inactive(vnode_t *node); #define VFS_LOOKUP_PARENT 0x20000000 diff --git a/kernel-src/sys/syscalls/flock.c b/kernel-src/sys/syscalls/flock.c new file mode 100644 index 00000000..10a32619 --- /dev/null +++ b/kernel-src/sys/syscalls/flock.c @@ -0,0 +1,74 @@ +#include +#include +#include + +syscallret_t syscall_flock(context_t *, int fd, int op_flag) { + syscallret_t ret = { + .ret = -1 + }; + + int op = op_flag &= ~ADVLOCK_NON_BLOCKING; + switch (op) { + case ADVLOCK_UNLOCK: + case ADVLOCK_SHARED: + case ADVLOCK_EXCLUSIVE: + break; + default: + ret.errno = EINVAL; + return ret; + } + + file_t *file = fd_get(fd); + if (file == NULL) { + ret.errno = EBADF; + return ret; + } + + MUTEX_ACQUIRE(&file->mutex); + + advlock_t *advlock = file->advlock; + if (advlock) + ADVLOCK_REF(advlock); + + if (op == ADVLOCK_UNLOCK && advlock == NULL) { + // trying to unlock a lock which does not exist, just leave + ret.errno = 0; + ret.ret = 0; + goto leave; + } + + if (op != ADVLOCK_UNLOCK && advlock == NULL) { + // locking and the lock does not exist yet, allocate it + advlock = advlock_allocate(); + if (advlock == NULL) { + ret.errno = ENOMEM; + goto leave; + } + + file->advlock = advlock; + advlock->refcount = 2; + } + + vnode_t *vn = file->vnode; + VOP_HOLD(vn); + + // release the file so that it can be closed etc in case we block + MUTEX_RELEASE(&file->mutex); + fd_release(file); + + ret.errno = VOP_ADVLOCK(vn, op_flag, advlock); + ret.ret = ret.errno ? -1 : 0; + + VOP_RELEASE(vn); + ADVLOCK_UNREF(advlock); + + return ret; + + leave: + if (advlock) + ADVLOCK_UNREF(advlock); + MUTEX_RELEASE(&file->mutex); + fd_release(file); + + return ret; +} diff --git a/patches/mlibc/jinx-working-patch.patch b/patches/mlibc/jinx-working-patch.patch index ff28debe..ec650797 100644 --- a/patches/mlibc/jinx-working-patch.patch +++ b/patches/mlibc/jinx-working-patch.patch @@ -84,7 +84,7 @@ index d5ba7b2..a86353f 100644 long error = syscall(SYSCALL_ARCHCTL, &ret, func, (uint64_t)arg); if(error) diff --git mlibc-clean/sysdeps/astral/generic/generic.cpp mlibc-workdir/sysdeps/astral/generic/generic.cpp -index bb85dba..a541845 100644 +index bb85dba..5e9e21a 100644 --- mlibc-clean/sysdeps/astral/generic/generic.cpp +++ mlibc-workdir/sysdeps/astral/generic/generic.cpp @@ -31,6 +31,12 @@ namespace mlibc { @@ -100,10 +100,15 @@ index bb85dba..a541845 100644 int sys_getsockopt(int fd, int layer, int number, void *__restrict buffer, socklen_t *__restrict size) { (void)size; -@@ -40,6 +46,18 @@ namespace mlibc { +@@ -40,6 +46,23 @@ namespace mlibc { return 0; } ++ int sys_flock(int fd, int options) { ++ long r; ++ return syscall(SYSCALL_FLOCK, &r, fd, options); ++ } ++ + int sys_nice(int nice, int *ret) { + long r = -1; + long e = syscall(SYSCALL_NICE, &r, nice); @@ -119,7 +124,7 @@ index bb85dba..a541845 100644 int sys_tgkill(int pid, int tid, int sig) { long ret; return syscall(SYSCALL_KILLTHREAD, &ret, pid, tid, sig); -@@ -161,7 +179,18 @@ namespace mlibc { +@@ -161,7 +184,18 @@ namespace mlibc { *bytes_written = writec; return error; } @@ -139,7 +144,7 @@ index bb85dba..a541845 100644 #ifndef MLIBC_BUILDING_RTLD typedef struct { -@@ -376,6 +405,12 @@ namespace mlibc { +@@ -376,6 +410,12 @@ namespace mlibc { return ret; } @@ -152,7 +157,7 @@ index bb85dba..a541845 100644 #ifndef MLIBC_BUILDING_RTLD [[noreturn]] void sys_thread_exit() { -@@ -771,7 +806,7 @@ namespace mlibc { +@@ -771,7 +811,7 @@ namespace mlibc { int sys_tcb_set(void *pointer) { long r; @@ -179,15 +184,16 @@ index d738e02..7e4f2a1 100644 #ifdef __cplusplus extern "C" { diff --git mlibc-clean/sysdeps/astral/include/astral/syscall.h mlibc-workdir/sysdeps/astral/include/astral/syscall.h -index 5f42d62..09a5d5f 100644 +index 5f42d62..6fbb253 100644 --- mlibc-clean/sysdeps/astral/include/astral/syscall.h +++ mlibc-workdir/sysdeps/astral/include/astral/syscall.h -@@ -96,6 +96,8 @@ +@@ -96,6 +96,9 @@ #define SYSCALL_SIGTIMEDWAIT 89 #define SYSCALL_SIGPENDING 90 #define SYSCALL_KILLTHREAD 91 +#define SYSCALL_SHUTDOWN 92 +#define SYSCALL_NICE 93 ++#define SYSCALL_FLOCK 94 #ifndef __MLIBC_ABI_ONLY