SchOS/kernel/fs/pipe.c
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:05:12 +02:00

176 lines
4.2 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"
#define PIPESIZE 512
struct pipe {
struct spinlock lock;
char data[PIPESIZE];
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
};
// A pipe is about 560 bytes and used to come from kalloc(), which is a whole
// 4096-byte page -- so every `|` in a shell command burned three and a half
// kilobytes it never touched. This is what kmem_cache exists for, and the
// cache is created on first use rather than at boot so a kernel that never
// opens a pipe holds no page for one.
static struct kmem_cache *pipe_cache;
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;
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;
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);
kmem_cache_free(pipe_cache, p);
} else
release(&p->lock);
}
//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;
}
for(i = 0; i < n; i++){
while(p->nwrite == p->nread + PIPESIZE){ //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);
sleep(&p->nwrite, &p->lock); //DOC: pipewrite-sleep
}
p->data[p->nwrite++ % PIPESIZE] = addr[i];
}
wakeup(&p->nread); //DOC: pipewrite-wakeup1
release(&p->lock);
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;
}
sleep(&p->nread, &p->lock); //DOC: piperead-sleep
}
for(i = 0; i < n; i++){ //DOC: piperead-copy
if(p->nread == p->nwrite)
break;
addr[i] = p->data[p->nread++ % PIPESIZE];
}
wakeup(&p->nwrite); //DOC: piperead-wakeup
release(&p->lock);
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(p->nwrite < p->nread + PIPESIZE)
rev |= POLLOUT;
}
release(&p->lock);
return rev;
}