SchOS/kernel/fs/pipe.c
Nexoniarz 3113eff7fe 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
2026-10-25 06:40:28 +01:00

243 lines
6.4 KiB
C

#include "core/types.h"
#include "core/defs.h"
#include "core/param.h"
#include "arch/mmu.h"
#include "core/proc.h"
#include "fs/fs.h"
#include "core/spinlock.h"
#include "core/sleeplock.h"
#include "fs/file.h"
#include "core/kmem.h"
#include "uapi/posix.h"
#include "uapi/errno.h"
// 64 KB, as on Linux, in pages taken the first time each is written.
//
// It was 512 bytes in the struct. That is xv6's size, and it made every pipe a
// handoff per half kilobyte: busybox's wget does TLS in a helper process that
// talks to it over pipes, a TLS record is 16 KB, and an HTTPS download that
// should have taken nine seconds was still going after a minute and a half. A
// shell pipeline that only ever moves a line still costs one page.
#define PIPESIZE 65536
#define PIPE_PAGES (PIPESIZE / PGSIZE)
#define PIPE_BUF 4096 // writes up to this size are not interleaved
struct pipe {
struct spinlock lock;
char *page[PIPE_PAGES];
uint nread; // number of bytes read
uint nwrite; // number of bytes written
int readopen; // read fd is still open
int writeopen; // write fd is still open
};
// The struct is small and comes from a cache; the buffer is pages. The cache
// is created on first use so a kernel that never opens a pipe holds no page
// for one.
static struct kmem_cache *pipe_cache;
static void
pipe_free(struct pipe *p)
{
for(int i = 0; i < PIPE_PAGES; i++)
if(p->page[i])
kfree(p->page[i]);
kmem_cache_free(pipe_cache, p);
}
int
pipealloc(struct file **f0, struct file **f1)
{
struct pipe *p;
p = 0;
*f0 = *f1 = 0;
if((*f0 = filealloc()) == 0 || (*f1 = filealloc()) == 0)
goto bad;
if(!pipe_cache && !(pipe_cache = kmem_cache_create("pipe", sizeof(*p))))
goto bad;
if((p = kmem_cache_alloc(pipe_cache)) == 0)
goto bad;
memset(p->page, 0, sizeof(p->page));
p->readopen = 1;
p->writeopen = 1;
p->nwrite = 0;
p->nread = 0;
initlock(&p->lock, "pipe");
(*f0)->type = FD_PIPE;
(*f0)->readable = 1;
(*f0)->writable = 0;
(*f0)->pipe = p;
(*f1)->type = FD_PIPE;
(*f1)->readable = 0;
(*f1)->writable = 1;
(*f1)->pipe = p;
(*f1)->ino = (*f0)->ino; // one pipe, as /proc/<pid>/fd names it
return 0;
//PAGEBREAK: 20
bad:
if(p)
kmem_cache_free(pipe_cache, p);
if(*f0)
fileclose(*f0);
if(*f1)
fileclose(*f1);
return -1;
}
void
pipeclose(struct pipe *p, int writable)
{
acquire(&p->lock);
if(writable){
p->writeopen = 0;
wakeup(&p->nread);
} else {
p->readopen = 0;
wakeup(&p->nwrite);
}
if(p->readopen == 0 && p->writeopen == 0){
release(&p->lock);
pipe_free(p);
} else
release(&p->lock);
poll_kick(); // a poller sees the hangup now, not at the tick
}
//PAGEBREAK: 40
int
pipewrite(struct pipe *p, char *addr, int n)
{
int i;
acquire(&p->lock);
// Writing to a pipe nobody is reading is SIGPIPE and EPIPE, in that order.
//
// This returned a bare -1 with no signal and no errno, so `yes | head -2`
// came back with whatever errno happened to be lying around -- "Invalid
// argument" -- instead of quietly ending the way every shell pipeline is
// built to. Checked up front as well as in the blocking loop below: the
// common case is that head has already exited by the time the next write
// arrives, and the pipe is nowhere near full.
if(p->readopen == 0){
release(&p->lock);
sigsend(myproc()->pid, SIGPIPE);
return -EPIPE;
}
// A write of PIPE_BUF or less goes in whole or waits for room, so two
// writers' records never interleave; a larger one goes in as room appears.
uint want_room = (n <= PIPE_BUF) ? (uint)n : 1;
i = 0;
while(i < n){
while(PIPESIZE - (p->nwrite - p->nread) < want_room){ //DOC: pipewrite-full
if(p->readopen == 0){
release(&p->lock);
sigsend(myproc()->pid, SIGPIPE);
return i > 0 ? i : -EPIPE; // a short write is still a write
}
if(myproc()->killed){
release(&p->lock);
return -1;
}
wakeup(&p->nread);
release(&p->lock);
poll_kick();
acquire(&p->lock);
if(PIPESIZE - (p->nwrite - p->nread) >= want_room)
break;
sleep(&p->nwrite, &p->lock); //DOC: pipewrite-sleep
}
// One run: up to the end of this page, of what is left, of the room.
uint off = p->nwrite % PIPESIZE;
uint run = PGSIZE - off % PGSIZE;
uint room = PIPESIZE - (p->nwrite - p->nread);
if(run > (uint)(n - i)) run = (uint)(n - i);
if(run > room) run = room;
char **pg = &p->page[off / PGSIZE];
if(*pg == 0 && (*pg = kalloc()) == 0){
release(&p->lock);
return i > 0 ? i : -ENOMEM;
}
memmove(*pg + off % PGSIZE, addr + i, run);
p->nwrite += run;
i += run;
want_room = 1;
}
wakeup(&p->nread); //DOC: pipewrite-wakeup1
release(&p->lock);
poll_kick();
return n;
}
int
piperead(struct pipe *p, char *addr, int n)
{
int i;
acquire(&p->lock);
while(p->nread == p->nwrite && p->writeopen){ //DOC: pipe-empty
if(myproc()->killed){
release(&p->lock);
return -1;
}
if(sig_attention(myproc())){ // stopped, or a handler wants to run
release(&p->lock);
int e = sig_blocking_check();
if(e)
return e;
acquire(&p->lock);
continue;
}
sleep(&p->nread, &p->lock); //DOC: piperead-sleep
}
i = 0;
while(i < n && p->nread != p->nwrite){ //DOC: piperead-copy
uint off = p->nread % PIPESIZE;
uint run = PGSIZE - off % PGSIZE;
if(run > (uint)(n - i)) run = (uint)(n - i);
if(run > p->nwrite - p->nread) run = p->nwrite - p->nread;
memmove(addr + i, p->page[off / PGSIZE] + off % PGSIZE, run);
p->nread += run;
i += run;
}
wakeup(&p->nwrite); //DOC: piperead-wakeup
release(&p->lock);
poll_kick(); // a writer polling for room
return i;
}
// Readiness for poll(2)/select(2). Reports state, doesn't block.
int
pipe_poll(struct pipe *p, int readable, int writable)
{
int rev = 0;
acquire(&p->lock);
if(readable){
if(p->nread != p->nwrite)
rev |= POLLIN;
if(p->writeopen == 0)
rev |= POLLHUP;
}
if(writable){
if(p->readopen == 0)
rev |= POLLERR;
else if(PIPESIZE - (p->nwrite - p->nread) >= PIPE_BUF)
rev |= POLLOUT; // room for a whole PIPE_BUF write, as on Linux
}
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;
}