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
243 lines
6.4 KiB
C
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;
|
|
}
|