From 3113eff7fe1c20a979e741a73af090df446bfcc4 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Sun, 25 Oct 2026 06:40:28 +0100 Subject: [PATCH] inotify, faccessat2, fsync for Linux programs, and D-Bus credentials What installing and starting Epiphany ran into: - inotify: inotify_init/init1, add_watch, rm_watch, as a new kind of event descriptor. The filesystem reports creates, deletes, renames (with a cookie pairing MOVED_FROM and MOVED_TO), writes, close-after-write, truncation and attribute changes, to watches on the inode and on its directory with the name. ONESHOT, MASK_ADD, MASK_CREATE, ONLYDIR, DONT_FOLLOW, IGNORED on removal, Q_OVERFLOW, and Linux's coalescing of repeated events. The hooks cost one load when nothing is watched. Without it GLib found no file monitor and GNOME programs aborted. - FIONREAD on inotify descriptors and pipes (inotifywait sizes its buffer by it). - fsync and fdatasync had no Linux numbers since the first commit: GLib's atomic writes, gtk-update-icon-cache and dbus-uuidgen all got ENOSYS. - faccessat2, the flags-taking faccessat musl uses for any flags. - sendmsg accepts SCM_CREDENTIALS when they are the sender's own, as Linux checks; GDBus sends them in every handshake and was refused with EINVAL. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4 --- include/core/defs.h | 7 + include/uapi/inotify.h | 29 +++ include/uapi/posix.h | 1 + kernel/fs/file.c | 8 +- kernel/fs/pipe.c | 10 + kernel/sys/sys_event.c | 415 ++++++++++++++++++++++++++++++++++++++++- kernel/sys/sys_fs.c | 90 +++++++++ kernel/sys/sys_net.c | 27 ++- kernel/sys/table.inc | 12 +- 9 files changed, 590 insertions(+), 9 deletions(-) create mode 100644 include/uapi/inotify.h diff --git a/include/core/defs.h b/include/core/defs.h index ab77726..eb8f639 100644 --- a/include/core/defs.h +++ b/include/core/defs.h @@ -799,6 +799,13 @@ uint64 memfd_size(int id); int memfd_truncate(int id, uint64 len); int memfd_seals(int id, int add); int dsp_writable(void); +// inotify (kernel/sys/sys_event.c), and the lookup it needs (sys_fs.c) +void inotify_event(uint dev, uint64 inum, uint32 mask, const char *name, uint32 cookie); +void inotify_file(struct file *f, uint32 mask); +uint32 inotify_cookie(void); +int ev_fionread(struct evobj *o); +int pipe_avail(struct pipe *p); +int path_identity(char *path, int follow, int onlydir, uint *dev, uint64 *inum); int memfd_rw(int id, int user, uint64 buf, uint64 off, int n, int writing); void shm_detach(int id); diff --git a/include/uapi/inotify.h b/include/uapi/inotify.h new file mode 100644 index 0000000..11b609d --- /dev/null +++ b/include/uapi/inotify.h @@ -0,0 +1,29 @@ +// inotify(7) event masks, Linux's values. +#ifndef UAPI_INOTIFY_H +#define UAPI_INOTIFY_H + +#define IN_ACCESS 0x00000001 +#define IN_MODIFY 0x00000002 +#define IN_ATTRIB 0x00000004 +#define IN_CLOSE_WRITE 0x00000008 +#define IN_CLOSE_NOWRITE 0x00000010 +#define IN_OPEN 0x00000020 +#define IN_MOVED_FROM 0x00000040 +#define IN_MOVED_TO 0x00000080 +#define IN_CREATE 0x00000100 +#define IN_DELETE 0x00000200 +#define IN_DELETE_SELF 0x00000400 +#define IN_MOVE_SELF 0x00000800 +#define IN_ALL_EVENTS 0x00000fff +#define IN_UNMOUNT 0x00002000 +#define IN_Q_OVERFLOW 0x00004000 +#define IN_IGNORED 0x00008000 +#define IN_ONLYDIR 0x01000000 +#define IN_DONT_FOLLOW 0x02000000 +#define IN_EXCL_UNLINK 0x04000000 +#define IN_MASK_CREATE 0x10000000 +#define IN_MASK_ADD 0x20000000 +#define IN_ISDIR 0x40000000 +#define IN_ONESHOT 0x80000000 + +#endif diff --git a/include/uapi/posix.h b/include/uapi/posix.h index d39fe7c..bf6e3f9 100644 --- a/include/uapi/posix.h +++ b/include/uapi/posix.h @@ -354,6 +354,7 @@ struct statfs { #define AT_FDCWD (-100) #define AT_REMOVEDIR 0x200 #define AT_SYMLINK_NOFOLLOW 0x100 +#define AT_EACCESS 0x200 #define AT_EMPTY_PATH 0x1000 #define AT_NO_AUTOMOUNT 0x800 diff --git a/kernel/fs/file.c b/kernel/fs/file.c index 36d915c..d35feaa 100644 --- a/kernel/fs/file.c +++ b/kernel/fs/file.c @@ -9,6 +9,7 @@ #include "fs/fsstat.h" #include "uapi/posix.h" #include "uapi/errno.h" +#include "uapi/inotify.h" #include "core/kmem.h" #include "dev/pty.h" @@ -112,6 +113,9 @@ fileclose(struct file *f) FSSTAT_DROP(fsstat.file_used); // 1 -> 0 release(&ftable.lock); + // Before the path goes: the directory's watchers want the name. + if(ff.type == FD_INODE && ff.writable && ff.ip && ff.ip->type == T_FILE) + inotify_file(&ff, IN_CLOSE_WRITE); if(ff.path) kmfree(ff.path); @@ -375,8 +379,10 @@ filewrite(struct file *f, char *addr, int n) if(r != n1) break; } - if(i > 0) + if(i > 0){ + inotify_file(f, IN_MODIFY); return i; + } return r < 0 ? r : -1; } panic("filewrite"); diff --git a/kernel/fs/pipe.c b/kernel/fs/pipe.c index 87194a2..5b37b02 100644 --- a/kernel/fs/pipe.c +++ b/kernel/fs/pipe.c @@ -231,3 +231,13 @@ pipe_poll(struct pipe *p, int readable, int writable) release(&p->lock); return rev; } + +// Bytes waiting to be read, for FIONREAD. +int +pipe_avail(struct pipe *p) +{ + acquire(&p->lock); + int n = (int)(p->nwrite - p->nread); + release(&p->lock); + return n; +} diff --git a/kernel/sys/sys_event.c b/kernel/sys/sys_event.c index 1b7c814..630ec9c 100644 --- a/kernel/sys/sys_event.c +++ b/kernel/sys/sys_event.c @@ -1,5 +1,5 @@ -// eventfd, timerfd, signalfd and epoll: the descriptors an event loop is -// built from. +// eventfd, timerfd, signalfd, epoll and inotify: the descriptors an event +// loop is built from. // // Four kinds of file with no inode behind them, all FD_EVENT, told apart by // struct evobj's kind. Each one is a counter or a set that read() drains and @@ -30,8 +30,11 @@ #include "uapi/fcntl.h" #include "uapi/stat.h" #include "uapi/errno.h" +#include "uapi/inotify.h" -enum { EV_EVENTFD = 1, EV_TIMERFD, EV_SIGNALFD, EV_EPOLL }; +enum { EV_EVENTFD = 1, EV_TIMERFD, EV_SIGNALFD, EV_EPOLL, EV_INOTIFY }; + +struct inotify; // Linux's flag values, which are O_NONBLOCK and O_CLOEXEC for every one of // these: one spelling per meaning, so they are tested as such. @@ -98,6 +101,7 @@ struct evobj { } tfd; struct { uint64 mask; } sfd; // kernel bit layout, as signal_mask struct { int n; struct epitem *items; } ep; + struct inotify *in; }; }; @@ -181,14 +185,21 @@ ev_kind_name(struct evobj *o) case EV_TIMERFD: return "anon_inode:[timerfd]"; case EV_SIGNALFD: return "anon_inode:[signalfd]"; case EV_EPOLL: return "anon_inode:[eventpoll]"; + case EV_INOTIFY: return "anon_inode:inotify"; } return "anon_inode:[event]"; } +static void in_free(struct inotify *in); +static int in_pending(struct inotify *in); +static int in_read(struct file *f, struct inotify *in, char *addr, int n); + // Called by fileclose() for the last reference. void ev_close(struct evobj *o) { + if (o->kind == EV_INOTIFY) + in_free(o->in); if (o->kind == EV_EPOLL && o->ep.items) kfree((char *)o->ep.items); kmfree(o); @@ -243,6 +254,10 @@ ev_poll(struct file *f, int events) if (myproc()->pending_signals & o->sfd.mask) rev |= POLLIN; break; + case EV_INOTIFY: + if (in_pending(o->in)) + rev |= POLLIN; + break; } releasesleep(&o->lock); @@ -260,6 +275,9 @@ ev_read(struct file *f, char *addr, int n) { struct evobj *o = f->ev; + if (o->kind == EV_INOTIFY) + return in_read(f, o->in, addr, n); + for (;;) { acquiresleep(&o->lock); switch (o->kind) { @@ -751,3 +769,394 @@ sys_epoll_pwait(void) { return sys_epoll_wait(); } + +// --- inotify ------------------------------------------------------------------- +// +// Watches name an inode by device and number. The filesystem calls +// inotify_event() as it creates, removes, renames and changes things -- for +// the inode itself, and for the directory it is in, with the name -- and every +// watch on that inode gets the event queued on its descriptor. +// +// The hooks cost one load when nothing is watched anywhere, which is the +// usual case. GLib watches a directory for every GSettings and file monitor +// it keeps, and without inotify at all it found no file monitor and GNOME +// programs aborted at start. + +// The masks are in uapi/inotify.h. + +struct inwatch { + int wd; + uint dev; + uint64 inum; + uint32 mask; +}; + +// One page for the instance and its watches, one for the queue. +#define IN_QBYTES PGSIZE +#define IN_MAXW ((int)((PGSIZE - 64) / sizeof(struct inwatch))) + +struct inotify { + struct inotify *next; // in_list + int nw, next_wd; + int qlen, overflowed; + char *q; + struct inwatch w[]; +}; + +struct in_event { + int wd; + uint32 mask, cookie, len; +}; + +static struct spinlock in_lock; +static int in_lock_ready; +static struct inotify *in_list; +static int in_nwatch; // across every instance: the fast path +static uint32 in_cookie; + +static void +in_lock_once(void) +{ + if (!in_lock_ready) { + initlock(&in_lock, "inotify"); + in_lock_ready = 1; + } +} + +// Queue one event. in_lock held. Linux's padding: the name, its NUL, and +// zeros up to a multiple of the header's size. +static void +in_queue(struct inotify *in, int wd, uint32 mask, uint32 cookie, const char *name) +{ + int nlen = name ? strlen(name) + 1 : 0; + int pad = nlen ? (nlen + 15) & ~15 : 0; + int need = (int)sizeof(struct in_event) + pad; + struct in_event e; + + // Room is kept for one overflow event, which says "you missed some". + if (in->qlen + need > IN_QBYTES - (int)sizeof(struct in_event)) { + if (!in->overflowed) { + e.wd = -1; e.mask = IN_Q_OVERFLOW; e.cookie = 0; e.len = 0; + memmove(in->q + in->qlen, &e, sizeof(e)); + in->qlen += sizeof(e); + in->overflowed = 1; + } + return; + } + e.wd = wd; e.mask = mask; e.cookie = cookie; e.len = (uint32)pad; + // The same event as the last one queued is not queued twice, as on Linux: + // a file written in a hundred pieces is one IN_MODIFY until someone reads. + if (in->qlen >= need && !name && pad == 0) { + struct in_event last; + memmove(&last, in->q + in->qlen - sizeof(last), sizeof(last)); + if (last.wd == wd && last.mask == mask && last.cookie == cookie && last.len == 0) + return; + } + memmove(in->q + in->qlen, &e, sizeof(e)); + if (pad) { + memset(in->q + in->qlen + sizeof(e), 0, pad); + memmove(in->q + in->qlen + sizeof(e), name, nlen - 1); + } + in->qlen += need; +} + +static void +in_drop_watch(struct inotify *in, int i) +{ + in_queue(in, in->w[i].wd, IN_IGNORED, 0, 0); + in->w[i] = in->w[--in->nw]; + in_nwatch--; +} + +uint32 +inotify_cookie(void) +{ + in_lock_once(); + acquire(&in_lock); + uint32 c = ++in_cookie; + release(&in_lock); + return c; +} + +// Something happened to inode (dev, inum): to the inode itself when name is +// null, or to the entry `name` in it, a directory. Safe with inode locks +// held; takes a spinlock and nothing else. +void +inotify_event(uint dev, uint64 inum, uint32 mask, const char *name, uint32 cookie) +{ + struct inotify *in; + int queued = 0; + + if (in_nwatch == 0) + return; + in_lock_once(); + acquire(&in_lock); + for (in = in_list; in; in = in->next) { + for (int i = 0; i < in->nw; i++) { + struct inwatch *w = &in->w[i]; + if (w->dev != dev || w->inum != inum) + continue; + if (w->mask & mask & IN_ALL_EVENTS) { + in_queue(in, w->wd, mask, cookie, name); + queued = 1; + if (w->mask & IN_ONESHOT) { + in_drop_watch(in, i--); + continue; + } + } + // The watched thing is gone; so is the watch, and the reader is told. + if (!name && (mask & IN_DELETE_SELF)) { + in_drop_watch(in, i--); + queued = 1; + } + } + } + release(&in_lock); + if (queued) + poll_kick(); +} + +// An event on an open file, for the file and for its directory. The +// directory's needs the name, which only the path the file was opened by +// gives -- looked up again here, and only when something is being watched. +void +inotify_file(struct file *f, uint32 mask) +{ + char name[VFS_NAME_MAX + 1]; + struct inode *dp; + + if (in_nwatch == 0 || f->type != FD_INODE || !f->ip) + return; + inotify_event(f->ip->dev, f->ip->inum, mask, 0, 0); + if (!f->path) + return; + begin_op(); + if ((dp = nameiparent(f->path, name)) != 0) { + inotify_event(dp->dev, dp->inum, mask, name, 0); + iput(dp); + } + end_op(); +} + +static int +in_pending(struct inotify *in) +{ + in_lock_once(); + acquire(&in_lock); + int r = in->qlen > 0; + release(&in_lock); + return r; +} + +static void +in_free(struct inotify *in) +{ + struct inotify **pp; + + in_lock_once(); + acquire(&in_lock); + for (pp = &in_list; *pp; pp = &(*pp)->next) + if (*pp == in) { + *pp = in->next; + break; + } + in_nwatch -= in->nw; + release(&in_lock); + kfree(in->q); + kfree((char *)in); +} + +// As many whole events as fit; EINVAL if not even the first does. +static int +in_read(struct file *f, struct inotify *in, char *addr, int n) +{ + char *tmp; + int take; + + for (;;) { + in_lock_once(); + acquire(&in_lock); + if (in->qlen > 0) + break; + release(&in_lock); + if (f->nonblock) + return -EAGAIN; + if (ev_block() < 0) + return -EINTR; + } + take = 0; + while (take < in->qlen) { + struct in_event e; + memmove(&e, in->q + take, sizeof(e)); + int sz = (int)sizeof(e) + (int)e.len; + if (take + sz > n) + break; + take += sz; + } + if (take == 0) { + release(&in_lock); + return -EINVAL; + } + release(&in_lock); + // Copied out of the queue under the lock into a page of our own, and from + // there to the reader without it: copy_to_user may fault. + if ((tmp = kalloc()) == 0) + return -ENOMEM; + acquire(&in_lock); + memmove(tmp, in->q, take); + memmove(in->q, in->q + take, in->qlen - take); + in->qlen -= take; + if (in->qlen == 0) + in->overflowed = 0; + release(&in_lock); + int r = copy_to_user((addr_t)addr, tmp, take) < 0 ? -EFAULT : take; + kfree(tmp); + return r; +} + +static int +in_create(int flags) +{ + struct evobj *o; + struct inotify *in; + int fd; + + if (flags & ~(O_NONBLOCK | O_CLOEXEC)) + return -EINVAL; + if ((o = ev_new(EV_INOTIFY)) == 0) + return -ENOMEM; + if ((in = (struct inotify *)kalloc()) == 0) { + kmfree(o); + return -ENOMEM; + } + memset(in, 0, PGSIZE); + if ((in->q = kalloc()) == 0) { + kfree((char *)in); + kmfree(o); + return -ENOMEM; + } + in->next_wd = 1; + o->in = in; + // ev_open frees o on failure; past filealloc it does so through fileclose, + // and ev_close frees the instance too. + if ((fd = ev_open(o, flags, 1, 0)) < 0) { + if (fd == -ENFILE) { + kfree(in->q); + kfree((char *)in); + } + return fd; + } + in_lock_once(); + acquire(&in_lock); + in->next = in_list; + in_list = in; + release(&in_lock); + return fd; +} + +addr_t sys_inotify_init(void) { return in_create(0); } + +addr_t +sys_inotify_init1(void) +{ + int flags; + argint(0, &flags); + return in_create(flags); +} + +addr_t +sys_inotify_add_watch(void) +{ + struct evobj *o; + struct inotify *in; + char *path; + uint32 mask; + int err = 0, wd = -1; + uint dev; + uint64 inum; + + if ((o = ev_arg(0, EV_INOTIFY, 0)) == 0) + return -EBADF; + in = o->in; + if (argstr(1, &path) < 0) + return -EFAULT; + argint(2, (int *)&mask); + if (!(mask & IN_ALL_EVENTS)) + return -EINVAL; + if ((mask & IN_MASK_ADD) && (mask & IN_MASK_CREATE)) + return -EINVAL; + if ((err = path_identity(path, !(mask & IN_DONT_FOLLOW), + (mask & IN_ONLYDIR) != 0, &dev, &inum)) < 0) + return err; + + mask &= IN_ALL_EVENTS | IN_ONESHOT | IN_EXCL_UNLINK | IN_MASK_ADD; + in_lock_once(); + acquire(&in_lock); + for (int i = 0; i < in->nw; i++) { + if (in->w[i].dev == dev && in->w[i].inum == inum) { + if (mask & IN_MASK_CREATE) { + release(&in_lock); + return -EEXIST; + } + in->w[i].mask = (mask & IN_MASK_ADD) ? in->w[i].mask | (mask & ~IN_MASK_ADD) + : mask; + wd = in->w[i].wd; + release(&in_lock); + return wd; + } + } + if (in->nw >= IN_MAXW) { + release(&in_lock); + return -ENOSPC; + } + wd = in->next_wd++; + in->w[in->nw].wd = wd; + in->w[in->nw].dev = dev; + in->w[in->nw].inum = inum; + in->w[in->nw].mask = mask & ~IN_MASK_ADD; + in->nw++; + in_nwatch++; + release(&in_lock); + return wd; +} + +addr_t +sys_inotify_rm_watch(void) +{ + struct evobj *o; + struct inotify *in; + int wd; + + if ((o = ev_arg(0, EV_INOTIFY, 0)) == 0) + return -EBADF; + in = o->in; + argint(1, &wd); + in_lock_once(); + acquire(&in_lock); + for (int i = 0; i < in->nw; i++) { + if (in->w[i].wd == wd) { + in_drop_watch(in, i); + release(&in_lock); + poll_kick(); + return 0; + } + } + release(&in_lock); + return -EINVAL; +} + +// FIONREAD: bytes a read would return, for the kinds where that is a number +// worth asking. inotifywait sizes its read buffer by it, and stopped at +// "Not a tty" without. +int +ev_fionread(struct evobj *o) +{ + if (o->kind == EV_INOTIFY) { + in_lock_once(); + acquire(&in_lock); + int n = o->in->qlen; + release(&in_lock); + return n; + } + return -ENOTTY; +} diff --git a/kernel/sys/sys_fs.c b/kernel/sys/sys_fs.c index 645406d..54baf20 100644 --- a/kernel/sys/sys_fs.c +++ b/kernel/sys/sys_fs.c @@ -15,6 +15,7 @@ #include "core/uaccess.h" #include "core/kmem.h" #include "uapi/errno.h" +#include "uapi/inotify.h" #include "fs/schfs.h" /* SCHFS_BLKSIZE, SCHFS_NAME_MAX — statfs reports them */ #include "gfx/gfx.h" /* gfx_cols/gfx_rows — TIOCGWINSZ reports them */ #include "dev/vt.h" /* the terminals the TIOC and VT_ ioctls act on */ @@ -370,10 +371,31 @@ createat_x(int dirfd, char *path, short type, short major, short minor, if (err) *err = r == -EINVAL ? EINVAL : ENOSPC; return 0; } + inotify_event(dp->dev, dp->inum, IN_CREATE | (type == T_DIR ? IN_ISDIR : 0), name, 0); iunlockput(dp); return ip; } +// The device and inode a path names, for inotify's watches. 0, or -errno. +int +path_identity(char *path, int follow, int onlydir, uint *dev, uint64 *inum) +{ + struct inode *ip; + int err = ENOENT, r = 0; + + begin_op(); + ip = follow ? at_nameif(AT_FDCWD, path, &err) : at_namei(AT_FDCWD, path); + if (!ip) { end_op(); return -err; } + ilock(ip); + if (onlydir && ip->type != T_DIR) + r = -ENOTDIR; + *dev = ip->dev; + *inum = ip->inum; + iunlockput(ip); + end_op(); + return r; +} + static struct inode* create(char *path, short type, short major, short minor) { @@ -1079,6 +1101,8 @@ addr_t sys_link(void) if (dp->dev != ip->dev) { iunlockput(dp); err = EXDEV; goto bad_link; } if (dp->ops->no_hardlinks) { iunlockput(dp); err = EPERM; goto bad_link; } if (dirlink(dp, name, ip->inum) < 0) { iunlockput(dp); err = EEXIST; goto bad_link; } + inotify_event(dp->dev, dp->inum, IN_CREATE, name, 0); + inotify_event(ip->dev, ip->inum, IN_ATTRIB, 0, 0); iunlockput(dp); iput(ip); end_op(); return 0; bad_link: @@ -1107,6 +1131,17 @@ create_sock(char *path) return create(path, T_SOCK, 0, 0); } +// A name removed from dp: IN_DELETE there, and for the inode itself either +// IN_ATTRIB (a link went) or IN_DELETE_SELF (the last one did). Before the +// caller drops nlink, so the last link is nlink == 1 here. +static void +inotify_unlinked(struct inode *dp, struct inode *ip, char *name) +{ + inotify_event(dp->dev, dp->inum, IN_DELETE | (ip->type == T_DIR ? IN_ISDIR : 0), name, 0); + inotify_event(ip->dev, ip->inum, ip->nlink <= 1 || ip->type == T_DIR + ? IN_DELETE_SELF : IN_ATTRIB, 0, 0); +} + // The body of unlink(2), by path. Factored out so that a failed bind can take // its own node back out without going through the syscall entry, and so there // is one implementation of what unlink means rather than two. @@ -1129,6 +1164,7 @@ unlink_path(char *path) if (ip->type == T_DIR && dirempty(ip) != 1) { iunlockput(ip); err = ENOTEMPTY; goto bad; } if (dirunlink(dp, name, off) < 0) { iunlockput(ip); err = EIO; goto bad; } if (ip->type == T_DIR) { dp->nlink--; iupdate(dp); } + inotify_unlinked(dp, ip, name); iunlockput(dp); ip->nlink--; iupdate(ip); iunlockput(ip); return 0; bad: @@ -1185,6 +1221,7 @@ addr_t sys_ftruncate(void) begin_op(); ilock(f->ip); itrunc_to(f->ip, (uint64)length); iunlock(f->ip); end_op(); + inotify_file(f, IN_MODIFY); return 0; } @@ -1200,6 +1237,7 @@ addr_t sys_truncate(void) if (!(ip = at_nameif(AT_FDCWD, path, &err))) { end_op(); return -err; } ilock(ip); itrunc_to(ip, (uint64)length); + inotify_event(ip->dev, ip->inum, IN_MODIFY, 0, 0); iunlockput(ip); end_op(); return 0; } @@ -1255,6 +1293,8 @@ do_rename(int olddirfd, char *oldpath, int newdirfd, char *newpath) if (dp_new) iput(dp_new); goto bad_rename; } + uint new_dev = dp_new->dev; // for inotify, after dp_new is put + uint64 new_inum = dp_new->inum; // Clear the destination, if anything is there. ilock(dp_new); @@ -1280,6 +1320,8 @@ do_rename(int olddirfd, char *oldpath, int newdirfd, char *newpath) iunlockput(ip_dst); iunlockput(dp_new); iput(dp_old); err = EIO; goto bad_rename; } + if (ip_dst->nlink <= 1) + inotify_event(ip_dst->dev, ip_dst->inum, IN_DELETE_SELF, 0, 0); ip_dst->nlink--; iupdate(ip_dst); iunlockput(ip_dst); } @@ -1297,6 +1339,12 @@ do_rename(int olddirfd, char *oldpath, int newdirfd, char *newpath) if (dirunlink(dp_old, oldname, off) < 0) { iunlockput(dp_old); err = EIO; goto bad_rename; } + { + uint32 cookie = inotify_cookie(); + inotify_event(dp_old->dev, dp_old->inum, IN_MOVED_FROM, oldname, cookie); + inotify_event(new_dev, new_inum, IN_MOVED_TO, newname, cookie); + inotify_event(ip->dev, ip->inum, IN_MOVE_SELF, 0, 0); + } iunlockput(dp_old); ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip); end_op(); return 0; @@ -1575,6 +1623,12 @@ addr_t sys_ioctl(void) } if (request == 0x5452) // FIOASYNC return 0; + if (request == FIONREAD && (f->type == FD_EVENT || f->type == FD_PIPE)) { + int n = f->type == FD_PIPE ? pipe_avail(f->pipe) : ev_fionread(f->ev); + if (n < 0) + return n; + return put_user(n, argp) < 0 ? -EFAULT : 0; + } // /dev/fb0 (major 2) — geometry queries. This has to come before the // catch-all below, which answers every non-tty ioctl with 0: a program @@ -1895,6 +1949,7 @@ chmod_inode(struct inode *ip, int mode) if (ip->mode == 0) ip->mode = 01000000; // "no bits", as distinct from "never set" iupdate(ip); + inotify_event(ip->dev, ip->inum, IN_ATTRIB, 0, 0); iunlock(ip); return 0; } @@ -2062,6 +2117,7 @@ addr_t sys_utimensat(void) } ip->ctime = (uint)now; iupdate(ip); + inotify_event(ip->dev, ip->inum, IN_ATTRIB, 0, 0); iunlockput(ip); end_op(); return 0; @@ -2249,6 +2305,7 @@ addr_t sys_unlinkat(void) if (ip->type == T_DIR && dirempty(ip) != 1) { err = ENOTEMPTY; goto badip; } if (dirunlink(dp, name, off) < 0) { err = EIO; goto badip; } if (ip->type == T_DIR) { dp->nlink--; iupdate(dp); } + inotify_unlinked(dp, ip, name); iunlockput(dp); ip->nlink--; iupdate(ip); iunlockput(ip); end_op(); return 0; @@ -2292,6 +2349,8 @@ addr_t sys_linkat(void) dirlink(dp, name, ip->inum) < 0) { iunlockput(dp); goto bad_linkat; } + inotify_event(dp->dev, dp->inum, IN_CREATE, name, 0); + inotify_event(ip->dev, ip->inum, IN_ATTRIB, 0, 0); iunlockput(dp); iput(ip); end_op(); return 0; bad_linkat: @@ -2365,6 +2424,37 @@ addr_t sys_faccessat(void) return 0; } +// faccessat2: faccessat with the flags argument the original never had. musl +// sends any faccessat with flags here, and without it GLib's every "does this +// file exist" (F_OK, AT_SYMLINK_NOFOLLOW) failed with EINVAL. The mode is not +// checked, as in faccessat above; AT_EACCESS changes nothing for the same +// reason. +addr_t sys_faccessat2(void) +{ + int dirfd, mode, flags, err = ENOENT; + char *path; + struct inode *ip; + + argint(0, &dirfd); + if (argstr(1, &path) < 0) return -EFAULT; + argint(2, &mode); + argint(3, &flags); + if (flags & ~(AT_SYMLINK_NOFOLLOW | AT_EACCESS | AT_EMPTY_PATH)) + return -EINVAL; + if ((flags & AT_EMPTY_PATH) && path[0] == '\0') { + if (dirfd < 0 || dirfd >= NOFILE || !myproc()->files->ofile[dirfd]) + return -EBADF; + return 0; + } + begin_op(); + ip = (flags & AT_SYMLINK_NOFOLLOW) ? at_namei(dirfd, path) + : at_nameif(dirfd, path, &err); + if (!ip) { end_op(); return -err; } + iput(ip); + end_op(); + return 0; +} + addr_t sys_fstatat(void) { int dirfd, flags; diff --git a/kernel/sys/sys_net.c b/kernel/sys/sys_net.c index 0be29f3..359bda6 100644 --- a/kernel/sys/sys_net.c +++ b/kernel/sys/sys_net.c @@ -900,10 +900,31 @@ scm_gather(struct msghdr *m, struct file **fs, int *nfp) } fs[(*nfp)++] = filedup(f); } + } else if (c->cmsg_level == SOL_SOCKET && c->cmsg_type == 2 /* SCM_CREDENTIALS */) { + // Credentials, which GDBus sends with the first byte of every + // D-Bus connection. Checked as Linux checks them -- they must be + // the sender's own, or the sender root -- and then not delivered: + // nothing here sets SO_PASSCRED to receive them, and the D-Bus + // daemon asks SO_PEERCRED instead, which answers the same question + // from the kernel's side. Refusing them made every GNOME program + // fail its D-Bus handshake ("Error sending credentials"). + struct { int pid; uint uid, gid; } cr; + struct proc *me = myproc(); + if (c->cmsg_len < CMSG_LEN(sizeof(cr))) { + r = -EINVAL; + goto fail; + } + memmove(&cr, CMSG_DATA(c), sizeof(cr)); + if (me->euid != 0 && + (cr.pid != me->tgid || + (cr.uid != me->uid && cr.uid != me->euid) || + (cr.gid != me->gid && cr.gid != me->egid))) { + r = -EPERM; + goto fail; + } } else if (c->cmsg_level == SOL_SOCKET) { - // SCM_CREDENTIALS and friends. Refused rather than ignored: a - // caller that asked to pass credentials and was told yes would - // believe the receiver got them. + // Anything else. Refused rather than ignored: a caller that asked + // for something and was told yes would believe it happened. r = -EINVAL; goto fail; } diff --git a/kernel/sys/table.inc b/kernel/sys/table.inc index a38ee7e..3550cef 100644 --- a/kernel/sys/table.inc +++ b/kernel/sys/table.inc @@ -213,8 +213,11 @@ SYSCALL(127, 92, chown) SYSCALL(128, 93, fchown) SYSCALL(129, 94, lchown) SYSCALL(130, 84, rmdir) -SYSCALL(131, NO_LINUX, fsync) -SYSCALL(132, NO_LINUX, fdatasync) +// fsync and fdatasync had no Linux numbers from the first commit on, and +// every Linux program that saves a file carefully got ENOSYS: GLib's atomic +// file writes, gtk-update-icon-cache, dbus-uuidgen, apk's triggers. +SYSCALL(131, 74, fsync) +SYSCALL(132, 75, fdatasync) SYSCALL(133, 162, sync) SYSCALL(134, 17, pread) SYSCALL(135, 18, pwrite) @@ -300,6 +303,11 @@ SYSCALL(207, 51, getsockname) SYSCALL(208, 52, getpeername) SYSCALL(209, 125, capget) SYSCALL(210, 126, capset) +SYSCALL(211, 439, faccessat2) +SYSCALL(212, 253, inotify_init) +SYSCALL(213, 294, inotify_init1) +SYSCALL(214, 254, inotify_add_watch) +SYSCALL(215, 255, inotify_rm_watch) // --- mounts --- SYSCALL(185, 165, mount)