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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
c77841ce3d
commit
3113eff7fe
9 changed files with 590 additions and 9 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue