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:
Nexoniarz 2026-10-25 06:40:28 +01:00
commit 3113eff7fe
9 changed files with 590 additions and 9 deletions

View file

@ -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;