diff --git a/include/core/defs.h b/include/core/defs.h index 0e20201..c7cbd0c 100644 --- a/include/core/defs.h +++ b/include/core/defs.h @@ -534,6 +534,11 @@ int inet_sendto(void *h, char *buf, int n, uint addr, ushort port); int inet_readable(void *h); int inet_hup(void *h); +int inet_shutdown(void *h, int how); +int file_describe(struct file *f, char *buf, int cap); +int proc_fd_mask(int pid, uint64 mask[2]); +int proc_fd_link(int pid, int fd, char *buf, int cap); +const char *ev_kind_name(struct evobj *o); void inet_close(void *h); // proc.c @@ -552,6 +557,8 @@ int sigsend(int pid, int sig); void alarm_arm(struct proc *p, uint after, uint every); uint alarm_left(struct proc *p); void alarm_thread_start(void); +void poll_kick(void); +int poll_wait_tick(void); int sig_default_kills(int sig); int proc_exists(int); int proc_num(void); diff --git a/include/fs/file.h b/include/fs/file.h index 6da4970..7759a7b 100644 --- a/include/fs/file.h +++ b/include/fs/file.h @@ -25,6 +25,11 @@ struct file { int memfd_seg; struct evobj *ev; uint64 off; + // What /proc//fd shows. `ino` names the open thing (one per socket, + // one per pipe); `path` is the absolute path an inode was opened by, as + // written -- symlinks along it are not resolved. kmalloc'd, or 0. + uint64 ino; + char *path; struct file *fnext; // file.c's free list, while ref is 0 }; diff --git a/include/net/socket.h b/include/net/socket.h index 15753d9..ccfdcdf 100644 --- a/include/net/socket.h +++ b/include/net/socket.h @@ -330,6 +330,9 @@ struct socket { // bidirectional connection. struct socket *peer; int peer_closed; + // shutdown(SHUT_WR): this end has said all it will. The peer reads EOF but + // can still answer, which is what a request/response helper depends on. + int wr_shut; char data[SOCKBUF]; uint nread, nwrite; diff --git a/kernel/dev/input.c b/kernel/dev/input.c index 78c8801..228e6c2 100644 --- a/kernel/dev/input.c +++ b/kernel/dev/input.c @@ -156,6 +156,7 @@ input_sync(int dev) return; input_report(dev, EV_SYN, SYN_REPORT, 0); wakeup(&idev[dev]); + poll_kick(); // Xorg polls its devices; a mouse moves at the next tick otherwise } int diff --git a/kernel/fs/exec.c b/kernel/fs/exec.c index 2744fb8..90bcf43 100644 --- a/kernel/fs/exec.c +++ b/kernel/fs/exec.c @@ -574,11 +574,15 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev) // first delivered signal jump to a stale address in the new image. // mmap_next likewise belongs to the old address space; left alone it // could hand out mmap ranges overlapping the new image's layout. + // Ignored signals stay ignored, and the mask is inherited (execve(2)): + // neither points into the old image. `nohup` and every program that ignores + // SIGPIPE before running a helper depend on it. memset(myproc()->vma, 0, sizeof(myproc()->vma)); - memset(myproc()->sig_handler, 0, sizeof(myproc()->sig_handler)); + for (int i = 0; i < 32; i++) + if (myproc()->sig_handler[i] != 1) + myproc()->sig_handler[i] = 0; memset(myproc()->sig_restorer, 0, sizeof(myproc()->sig_restorer)); myproc()->fs_base = 0; - myproc()->signal_mask = 0; myproc()->pending_signals = 0; myproc()->in_signal = 0; myproc()->sigsusp_active = 0; diff --git a/kernel/fs/file.c b/kernel/fs/file.c index a696823..382da87 100644 --- a/kernel/fs/file.c +++ b/kernel/fs/file.c @@ -24,6 +24,7 @@ struct { struct file *free; struct kmem_cache *more; uint n; + uint64 serial; // the last ino handed out } ftable; void @@ -63,6 +64,8 @@ filealloc(void) ftable.n++; } f->ref = 1; + f->ino = ++ftable.serial; + f->path = 0; FSSTAT_TAKE(fsstat.file_used, fsstat.file_peak); // 0 -> 1 // Cleared here rather than left to the caller: every creator sets // readable/writable/off, but only open() sets nonblock, so a struct file @@ -101,12 +104,16 @@ fileclose(struct file *f) lock_close_file(f); ff = *f; f->ref = 0; + f->path = 0; f->type = FD_NONE; f->fnext = ftable.free; ftable.free = f; FSSTAT_DROP(fsstat.file_used); // 1 -> 0 release(&ftable.lock); + if(ff.path) + kmfree(ff.path); + if(ff.type == FD_MEMFD) shm_detach(ff.memfd_seg); // the last mapper frees the pages else if(ff.type == FD_EVENT) @@ -129,6 +136,42 @@ fileclose(struct file *f) } } +// What /proc//fd/N points at, in Linux's spelling: the path, or +// "socket:[ino]", "pipe:[ino]", "anon_inode:[eventpoll]". Returns the length. +// ftable.lock is what keeps `path` alive: fileclose() takes it from the file +// under the lock and frees it after. +int +file_describe(struct file *f, char *buf, int cap) +{ + const char *pre = 0, *s = 0; + int n = 0; + + acquire(&ftable.lock); + if(f->ref > 0){ + switch(f->type){ + case FD_INODE: s = f->path; if(!s) pre = "inode:["; break; + case FD_SOCKET: pre = "socket:["; break; + case FD_PIPE: pre = "pipe:["; break; + case FD_MEMFD: s = "/memfd: (deleted)"; break; + case FD_EVENT: s = ev_kind_name(f->ev); break; + default: break; + } + } + if(pre){ + char num[24]; + int k = 0; + uint64 v = f->type == FD_INODE && f->ip ? f->ip->inum : f->ino; + do { num[k++] = '0' + v % 10; v /= 10; } while(v); + while(*pre && n < cap) buf[n++] = *pre++; + while(k && n < cap) buf[n++] = num[--k]; + if(n < cap) buf[n++] = ']'; + } else if(s){ + while(*s && n < cap) buf[n++] = *s++; + } + release(&ftable.lock); + return n; +} + int filestat(struct file *f, struct stat *st) { diff --git a/kernel/fs/pipe.c b/kernel/fs/pipe.c index f5c67a8..62c5189 100644 --- a/kernel/fs/pipe.c +++ b/kernel/fs/pipe.c @@ -11,24 +11,40 @@ #include "uapi/posix.h" #include "uapi/errno.h" -#define PIPESIZE 512 +// 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 data[PIPESIZE]; + 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 }; -// 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. +// 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) { @@ -42,6 +58,7 @@ pipealloc(struct file **f0, struct file **f1) 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; @@ -55,6 +72,7 @@ pipealloc(struct file **f0, struct file **f1) (*f1)->readable = 0; (*f1)->writable = 1; (*f1)->pipe = p; + (*f1)->ino = (*f0)->ino; // one pipe, as /proc//fd names it return 0; //PAGEBREAK: 20 @@ -81,9 +99,10 @@ pipeclose(struct pipe *p, int writable) } if(p->readopen == 0 && p->writeopen == 0){ release(&p->lock); - kmem_cache_free(pipe_cache, p); + pipe_free(p); } else release(&p->lock); + poll_kick(); // a poller sees the hangup now, not at the tick } //PAGEBREAK: 40 @@ -108,8 +127,12 @@ pipewrite(struct pipe *p, char *addr, int n) return -EPIPE; } - for(i = 0; i < n; i++){ - while(p->nwrite == p->nread + PIPESIZE){ //DOC: pipewrite-full + // 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); @@ -120,12 +143,32 @@ pipewrite(struct pipe *p, char *addr, int n) 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 } - p->data[p->nwrite++ % PIPESIZE] = addr[i]; + // 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; } @@ -142,13 +185,19 @@ piperead(struct pipe *p, char *addr, int n) } 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]; + 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; } @@ -168,8 +217,8 @@ pipe_poll(struct pipe *p, int readable, int writable) if(writable){ if(p->readopen == 0) rev |= POLLERR; - else if(p->nwrite < p->nread + PIPESIZE) - rev |= POLLOUT; + 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; diff --git a/kernel/fs/procfs.c b/kernel/fs/procfs.c index 584a226..069d9a4 100644 --- a/kernel/fs/procfs.c +++ b/kernel/fs/procfs.c @@ -43,18 +43,22 @@ // identity of the object. // // 1 the root, /proc +// 2 /proc/self // PROCFS_GLOBAL | id a global file, id being the PROC_* minor that // kernel/dev/procfs.c already uses // pid << 8 the directory /proc/ // (pid << 8) | fileid a file inside it +// (pid << 8) | 0x80 | fd /proc//fd/ // -// pid is bounded by NPROC and file ids by PF_MAX, so the ranges cannot -// collide. Shifting by 8 rather than packing tightly keeps the numbers -// readable in a debugger, which matters more than density here. +// File ids stay below 0x80 and fds below 128 (NOFILE). pids are not bounded +// by NPROC -- they count up -- so the global bit sits far above any pid << 8; +// at 0x10000 it was /proc/256. Shifting by 8 rather than packing tightly keeps +// the numbers readable in a debugger, which matters more than density here. // --------------------------------------------------------------------------- #define PROCFS_ROOT 1ULL -#define PROCFS_GLOBAL 0x10000ULL +#define PROCFS_SELF 2ULL +#define PROCFS_GLOBAL (1ULL << 40) #define PF_DIR 0 // the directory itself #define PF_STAT 1 @@ -63,10 +67,14 @@ #define PF_STATM 4 #define PF_EXE 5 #define PF_SCHEDSTAT 6 -#define PF_MAX 6 +#define PF_FD 7 // the directory /proc//fd +#define PF_MAX 7 +#define PF_FDENT 0x80 // | fd: one link in it #define INO_PID(ino) ((int)((ino) >> 8)) #define INO_FILE(ino) ((int)((ino) & 0xFF)) +#define INO_IS_FDENT(ino) (!INO_IS_GLOBAL(ino) && (INO_FILE(ino) & PF_FDENT)) +#define INO_FD(ino) (INO_FILE(ino) & 0x7F) #define INO_IS_GLOBAL(ino) (((ino) & PROCFS_GLOBAL) != 0) // The global files, by the name they appear under and the PROC_* id that @@ -121,6 +129,9 @@ static const struct global_file pidfiles[] = { // inventing a wait time that is not measured would be worse than saying // something true in its place. { "schedstat", PF_SCHEDSTAT }, + // One symlink per open descriptor, named by its number and pointing at the + // path it was opened by, or at "socket:[ino]" and the like. + { "fd", PF_FD }, }; #define NPIDFILES ((int)(sizeof(pidfiles) / sizeof(pidfiles[0]))) @@ -267,6 +278,15 @@ gen_file(uint64 ino, char *buf, int cap) if (INO_IS_GLOBAL(ino)) return proc_gen_global((int)(ino & 0xFF), buf, cap); + // Whoever is asking: the one file whose content depends on the reader. + if (ino == PROCFS_SELF) { + sbputi(&s, myproc()->pid); + return s.n; + } + if (INO_IS_FDENT(ino)) { + int n = proc_fd_link(INO_PID(ino), INO_FD(ino), buf, cap); + return n > 0 ? n : 0; + } if (proc_snapshot_pid(INO_PID(ino), &pi) < 0) return 0; // it exited between lookup and read @@ -310,7 +330,8 @@ procfs_iload(struct inode *ip) ip->showmode = 1; ip->atime = ip->mtime = ip->ctime = 0; - if (ino == PROCFS_ROOT || (!INO_IS_GLOBAL(ino) && INO_FILE(ino) == PF_DIR)) { + if (ino == PROCFS_ROOT || + (!INO_IS_GLOBAL(ino) && (INO_FILE(ino) == PF_DIR || INO_FILE(ino) == PF_FD))) { ip->type = T_DIR; ip->size = 0; ip->nlink = 2; @@ -329,8 +350,12 @@ procfs_iload(struct inode *ip) // symlink whose size is 0 resolves to the empty string -- which is worse // than the regular file was, because it looks like it worked. Generating it // here costs one small render per stat() of one file. - if (!INO_IS_GLOBAL(ino) && INO_FILE(ino) == PF_EXE) { - char path[64]; + // /proc/self and /proc//fd/N are links too. Their targets can change + // under a cached inode, which is why readlink() does not trust this size + // for procfs. + if (ino == PROCFS_SELF || INO_IS_FDENT(ino) || + (!INO_IS_GLOBAL(ino) && INO_FILE(ino) == PF_EXE)) { + char path[256]; int len = gen_file(ino, path, sizeof(path)); ip->type = T_SYMLINK; ip->size = len > 0 ? (uint64)len : 0; @@ -469,8 +494,19 @@ procfs_dirlookup(struct inode *dp, char *name, uint *poff) if (namecmp(name, ".") == 0) return iget(dp->dev, dp->inum); - if (namecmp(name, "..") == 0) - return iget(dp->dev, PROCFS_ROOT); // /proc//.. is /proc + if (namecmp(name, "..") == 0) // /proc//.. is /proc + return iget(dp->dev, INO_FILE(dp->inum) == PF_FD ? (dp->inum & ~0xFFULL) + : PROCFS_ROOT); + + // Inside /proc//fd/: a number that is an open descriptor. + if (dp->inum != PROCFS_ROOT && INO_FILE(dp->inum) == PF_FD) { + char tmp[4]; + int fd = name_to_pid(name); + if (fd < 0 || fd >= NOFILE || fd >= 128 || + proc_fd_link(INO_PID(dp->inum), fd, tmp, sizeof(tmp)) < 0) + return 0; + return iget(dp->dev, (dp->inum & ~0xFFULL) | PF_FDENT | fd); + } // Inside /proc// if (dp->inum != PROCFS_ROOT) { @@ -480,6 +516,9 @@ procfs_dirlookup(struct inode *dp, char *name, uint *poff) return 0; } + if (namecmp(name, "self") == 0) + return iget(dp->dev, PROCFS_SELF); + // In /proc itself: a number is a process, a word is a global file. pid = name_to_pid(name); if (pid > 0) { @@ -516,6 +555,7 @@ struct linux_dirent64 { #define DT_DIR_ 4 #define DT_REG_ 8 +#define DT_LNK_ 10 static int emit(char *out, int cap, int used, uint64 ino, int type, const char *name) @@ -574,17 +614,30 @@ procfs_getdents(struct inode *dp, char *out, int cap, uint64 *off) if (dp->inum == PROCFS_ROOT) { EMIT(PROCFS_ROOT, DT_DIR_, "."); EMIT(PROCFS_ROOT, DT_DIR_, ".."); + EMIT(PROCFS_SELF, DT_LNK_, "self"); for (i = 0; i < NGLOBALS; i++) EMIT(PROCFS_GLOBAL | globals[i].id, DT_REG_, globals[i].name); for (i = 0; proc_snapshot(i, &pi) == 0; i++) { utoa((uint)pi.pid, name); EMIT(((uint64)pi.pid << 8) | PF_DIR, DT_DIR_, name); } + } else if (INO_FILE(dp->inum) == PF_FD) { + uint64 mask[2]; + EMIT(dp->inum, DT_DIR_, "."); + EMIT(dp->inum & ~0xFFULL, DT_DIR_, ".."); + if (proc_fd_mask(INO_PID(dp->inum), mask) == 0) + for (i = 0; i < 128; i++) + if (mask[i / 64] & (1ULL << (i % 64))) { + utoa((uint)i, name); + EMIT((dp->inum & ~0xFFULL) | PF_FDENT | i, DT_LNK_, name); + } } else { EMIT(dp->inum, DT_DIR_, "."); EMIT(PROCFS_ROOT, DT_DIR_, ".."); for (i = 0; i < NPIDFILES; i++) - EMIT((dp->inum & ~0xFFULL) | pidfiles[i].id, DT_REG_, pidfiles[i].name); + EMIT((dp->inum & ~0xFFULL) | pidfiles[i].id, + pidfiles[i].id == PF_FD ? DT_DIR_ : pidfiles[i].id == PF_EXE ? DT_LNK_ : DT_REG_, + pidfiles[i].name); } full: diff --git a/kernel/net/inet.c b/kernel/net/inet.c index 499cf81..0e43fd9 100644 --- a/kernel/net/inet.c +++ b/kernel/net/inet.c @@ -115,6 +115,7 @@ struct inet_sock { int connected; int conn_failed; int peer_closed; // FIN seen, or the connection died + int wr_shut; // shutdown(SHUT_WR): our FIN is sent int listening; // Connections lwIP has completed but nobody has accept()ed yet. @@ -194,6 +195,7 @@ net_wake(void) netwait_seq++; release(&netwait_lock); wakeup(&netwait_seq); + poll_kick(); // and poll/select/epoll waiters, not at the tick } /* ------------------------------------------------------------------ */ @@ -958,7 +960,7 @@ inet_sendto(void *h, char *buf, int n, uint addr, ushort port) uint seen; lwip_sys_lock(); - if (is->peer_closed || is->tpcb == 0) { + if (is->peer_closed || is->wr_shut || is->tpcb == 0) { lwip_sys_unlock(); // Same contract as a pipe with no reader, which is what the AF_UNIX side // does: the signal first, then the error. @@ -1005,6 +1007,32 @@ inet_write(void *h, char *buf, int n) return inet_sendto(h, buf, n, 0, 0); } +// shutdown(2) on a TCP socket. SHUT_WR sends our FIN and the peer can keep +// sending; SHUT_RD is local, reads see EOF. The pcb is never freed here -- +// tcp_shutdown(pcb, 1, 1) would free it -- so close() still owns it. +int +inet_shutdown(void *h, int how) +{ + struct inet_sock *is = h; + + if (is->type != SOCK_STREAM) + return 0; + lwip_sys_lock(); + if (is->tpcb == 0 || is->listening || !is->connected) { + lwip_sys_unlock(); + return -ENOTCONN; + } + if ((how == SHUT_WR || how == SHUT_RDWR) && !is->wr_shut) { + is->wr_shut = 1; + tcp_shutdown(is->tpcb, 0, 1); + } + if (how == SHUT_RD || how == SHUT_RDWR) + is->peer_closed = 1; + lwip_sys_unlock(); + net_wake(); + return 0; +} + int inet_readable(void *h) { diff --git a/kernel/net/unix.c b/kernel/net/unix.c index 6a5036d..fca12e9 100644 --- a/kernel/net/unix.c +++ b/kernel/net/unix.c @@ -230,6 +230,7 @@ sockclose(struct socket *s) s->ref = 0; wakeup(&s->nqueue); // an accept() blocked on this listener release(&stab.lock); + poll_kick(); // the peer's poll() sees the hangup now } void @@ -459,6 +460,7 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout // on a full buffer that this call has just made room in. wakeup(&s->nwrite); release(&stab.lock); + poll_kick(); // a writer polling for room // Outside the lock, because fileclose can sleep. if(fout){ @@ -520,7 +522,7 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf) // look for it. Same contract as a pipe with no reader: a signal, then an // error. A process that writes to a dead connection and is not told about // it spins forever believing it is making progress. - if(s->peer == 0){ + if(s->peer == 0 || s->wr_shut){ release(&stab.lock); sigsend(myproc()->pid, SIGPIPE); return -EPIPE; @@ -569,6 +571,9 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf) wakeup(&s->peer->nread); release(&stab.lock); + // poll() and epoll waiters, now rather than at the next tick: an X client + // and its server talk over these, a request and a reply at a time. + poll_kick(); return i; } @@ -854,6 +859,7 @@ sockconnect(struct socket *s, uint dev, uint64 ino) wakeup(&srv->nqueue); // an accept() may be blocked on this release(&stab.lock); + poll_kick(); // or a listener in poll(), as Xorg's is // Deliberately does *not* wait for accept(). On Unix a queued connection is // already established: the client may write, and the bytes sit in the @@ -872,6 +878,9 @@ sockconnect(struct socket *s, uint dev, uint64 ino) int sock_shutdown(struct socket *s, int how) { + if(s->domain == AF_INET) + return inet_shutdown(s->inet, how); + acquire(&stab.lock); if(s->state != SS_CONNECTED){ release(&stab.lock); @@ -882,9 +891,20 @@ sock_shutdown(struct socket *s, int how) s->peer_closed = 1; wakeup(&s->nread); } - if(how == SHUT_WR || how == SHUT_RDWR) + if(how == SHUT_RDWR) orphan_peer(s); + else if(how == SHUT_WR){ + // Half-close: the peer sees EOF once it has read what is there, and the + // link stays, so its reply still reaches us. Cutting the link here made + // the reply a SIGPIPE -- busybox wget does exactly this to ssl_client. + s->wr_shut = 1; + if(s->peer){ + s->peer->peer_closed = 1; + wakeup(&s->peer->nread); + } + } release(&stab.lock); + poll_kick(); return 0; } diff --git a/kernel/proc/proc.c b/kernel/proc/proc.c index c9a1ac4..bde72f0 100644 --- a/kernel/proc/proc.c +++ b/kernel/proc/proc.c @@ -1907,6 +1907,50 @@ proc_snapshot_pid(int pid, struct procinfo *out) return -1; } +// /proc//fd: which descriptors are open, and what one points at. The +// ptable lock keeps the process from being reaped -- exit() closes its files +// first -- and file_describe() holds the file steady itself. +int +proc_fd_mask(int pid, uint64 mask[2]) +{ + struct proc *p; + int r = -1; + + mask[0] = mask[1] = 0; + acquire(&ptable.lock); + for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) { + if (p->state == UNUSED || p->pid != pid) + continue; + for (int fd = 0; fd < NOFILE && fd < 128; fd++) + if (p->ofile[fd]) + mask[fd / 64] |= 1ULL << (fd % 64); + r = 0; + break; + } + release(&ptable.lock); + return r; +} + +int +proc_fd_link(int pid, int fd, char *buf, int cap) +{ + struct proc *p; + int r = -1; + + if (fd < 0 || fd >= NOFILE) + return -1; + acquire(&ptable.lock); + for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) { + if (p->state == UNUSED || p->pid != pid) + continue; + if (p->ofile[fd]) + r = file_describe(p->ofile[fd], buf, cap); + break; + } + release(&ptable.lock); + return r; +} + int proc_num(void) { struct proc *p; diff --git a/kernel/sys/sys_event.c b/kernel/sys/sys_event.c index 6846177..dd0b0d3 100644 --- a/kernel/sys/sys_event.c +++ b/kernel/sys/sys_event.c @@ -113,19 +113,13 @@ clock_ns(int clock) return sec * 1000000000ULL + usec * 1000ULL; } -// One tick, or until a signal or kill makes waiting pointless. +// One tick, a poll_kick, or a signal or kill that makes waiting pointless: +// poll's wait, so data arriving wakes an epoll_wait as it wakes a poll. +// Returns 1 for a tick, 0 for a kick, -EINTR. static int ev_block(void) { - struct proc *p = myproc(); - if (p->killed || (p->pending_signals & ~p->signal_mask)) - return -EINTR; - acquire(&tickslock); - uint t0 = ticks; - while (ticks == t0 && !p->killed && !(p->pending_signals & ~p->signal_mask)) - sleep(&ticks, &tickslock); - release(&tickslock); - return 0; + return poll_wait_tick(); } static int @@ -178,6 +172,19 @@ ev_arg(int n, int kind, struct file **pf) return f->ev; } +// The name Linux gives each kind in /proc//fd. +const char * +ev_kind_name(struct evobj *o) +{ + switch (o ? o->kind : 0) { + case EV_EVENTFD: return "anon_inode:[eventfd]"; + case EV_TIMERFD: return "anon_inode:[timerfd]"; + case EV_SIGNALFD: return "anon_inode:[signalfd]"; + case EV_EPOLL: return "anon_inode:[eventpoll]"; + } + return "anon_inode:[event]"; +} + // Called by fileclose() for the last reference. void ev_close(struct evobj *o) @@ -714,11 +721,12 @@ epoll_wait_common(int timeout_ms) got = ep_ready(o, buf, max); if (got > 0 || ticks_left == 0) break; - if (ev_block() < 0) { + int w = ev_block(); + if (w < 0) { got = -EINTR; break; } - if (ticks_left > 0) + if (w > 0 && ticks_left > 0) ticks_left--; } if (got > 0 && copy_to_user(uevents, buf, got * sizeof(*buf)) < 0) diff --git a/kernel/sys/sys_fs.c b/kernel/sys/sys_fs.c index 8ba18f9..4305d47 100644 --- a/kernel/sys/sys_fs.c +++ b/kernel/sys/sys_fs.c @@ -13,6 +13,7 @@ #include "uapi/posix.h" #include "core/syscall.h" #include "core/uaccess.h" +#include "core/kmem.h" #include "uapi/errno.h" #include "fs/schfs.h" /* SCHFS_BLKSIZE, SCHFS_NAME_MAX — statfs reports them */ #include "gfx/gfx.h" /* gfx_cols/gfx_rows — TIOCGWINSZ reports them */ @@ -139,6 +140,53 @@ fdalloc(struct file *f) return -1; } +// Remember the absolute path `f` was opened by, for /proc//fd. `path` is +// joined to the directory it was resolved from -- the cwd, or dirfd's own +// remembered path -- and `.`, `..` and repeated slashes are folded out. This +// is lexical: a symlink along the way stays in the name. Nothing depends on +// it being there, so running out of memory just leaves it unset. +static void +file_setpath(struct file *f, int dirfd, const char *path) +{ + const char *base = ""; + if (path[0] != '/') { + struct file *d; + if (dirfd == AT_FDCWD) + base = myproc()->cwd_path; + else if (dirfd >= 0 && dirfd < NOFILE && (d = myproc()->ofile[dirfd]) && d->path) + base = d->path; + else + return; + } + char *buf = kalloc(); + if (!buf) return; + int n = 0; // buf is "/a/b", no trailing slash + for (int pass = 0; pass < 2; pass++) { + const char *s = pass ? path : base; + for (;;) { + while (*s == '/') s++; + const char *e = s; + while (*e && *e != '/') e++; + int len = (int)(e - s); + if (len == 0) break; + if (len == 2 && s[0] == '.' && s[1] == '.') { + while (n > 0 && buf[--n] != '/') ; + } else if (!(len == 1 && s[0] == '.')) { + if (n + 1 + len >= PGSIZE) { kfree(buf); return; } + buf[n++] = '/'; + memmove(buf + n, s, len); + n += len; + } + s = e; + } + } + if (n == 0) buf[n++] = '/'; + buf[n] = 0; + if ((f->path = kmalloc(n + 1)) != 0) + memmove(f->path, buf, n + 1); + kfree(buf); +} + // Resolves a dirfd (AT_FDCWD or a real fd) to the inode a relative path // should be resolved from. 0 on an invalid dirfd. static struct inode * @@ -602,6 +650,7 @@ addr_t sys_open(void) } iunlock(ip); end_op(); f->type = FD_INODE; f->ip = ip; + file_setpath(f, AT_FDCWD, path); f->off = (omode & O_APPEND) ? ip->size : 0; f->readable = !(omode & O_WRONLY); f->writable = (omode & O_WRONLY) || (omode & O_RDWR); @@ -659,6 +708,7 @@ addr_t sys_openat(void) uint64 start = (omode & O_APPEND) ? ip->size : 0; iunlock(ip); end_op(); f->type = FD_INODE; f->ip = ip; f->off = start; + file_setpath(f, dirfd, path); f->readable = !(omode & O_WRONLY); f->writable = (omode & O_WRONLY) || (omode & O_RDWR); f->nonblock = (omode & O_NONBLOCK) != 0; @@ -697,6 +747,19 @@ addr_t sys_fstat(void) } return 0; } + // A socket, a pipe or an anonymous inode: Linux answers with its type and + // an inode number, which is what `ls -l /proc//fd` and every program + // asking "is my stdin a socket?" look at. ino matches /proc//fd. + if (f->type != FD_INODE && f->type != FD_MEMFD && myproc()->syscall_map) { + struct linux_stat *ls = (struct linux_stat *)st; + memset(ls, 0, sizeof(*ls)); + ls->st_mode = f->type == FD_SOCKET ? LINUX_S_IFSOCK | 0777 + : f->type == FD_PIPE ? LINUX_S_IFIFO | 0600 : 0600; + ls->st_ino = f->ino; + ls->st_nlink = 1; + ls->st_blksize = PGSIZE; + return 0; + } if (f->type != FD_INODE) return -EINVAL; ilock(f->ip); if (myproc()->syscall_map) stati_linux(f->ip, (struct linux_stat*)st); @@ -1229,10 +1292,14 @@ addr_t sys_readlink(void) if (!(ip = namei(path))) return -ENOENT; ilock(ip); if (ip->type != T_SYMLINK) { iunlockput(ip); return -EINVAL; } - int n = (int)ip->size < bufsiz ? (int)ip->size : bufsiz; - if (readi(ip, 0, (uint64)buf, 0, n) != n) { iunlockput(ip); return -EIO; } + // A /proc link can change after its inode was loaded -- an fd dup2()ed + // over -- so its size is not trusted: the read says how long it is. + int proc = ip->dev == PROCFS_DEV; + int n = (proc || (int)ip->size > bufsiz) ? bufsiz : (int)ip->size; + int got = readi(ip, 0, (uint64)buf, 0, n); + if (got < 0 || (!proc && got != n)) { iunlockput(ip); return -EIO; } iunlockput(ip); - return n; + return got; } // ----------------------------------------------------------------------- @@ -2090,10 +2157,14 @@ addr_t sys_readlinkat(void) if (!(ip = at_namei(dirfd, path))) return -ENOENT; ilock(ip); if (ip->type != T_SYMLINK) { iunlockput(ip); return -EINVAL; } - int n = (int)ip->size < bufsiz ? (int)ip->size : bufsiz; - if (readi(ip, 0, (uint64)buf, 0, n) != n) { iunlockput(ip); return -EIO; } + // A /proc link can change after its inode was loaded -- an fd dup2()ed + // over -- so its size is not trusted: the read says how long it is. + int proc = ip->dev == PROCFS_DEV; + int n = (proc || (int)ip->size > bufsiz) ? bufsiz : (int)ip->size; + int got = readi(ip, 0, (uint64)buf, 0, n); + if (got < 0 || (!proc && got != n)) { iunlockput(ip); return -EIO; } iunlockput(ip); - return n; + return got; } addr_t sys_fchmodat(void) { return 0; } // stub diff --git a/kernel/sys/sys_net.c b/kernel/sys/sys_net.c index fbef77f..fcf2038 100644 --- a/kernel/sys/sys_net.c +++ b/kernel/sys/sys_net.c @@ -656,9 +656,8 @@ sock_name(int peer) SYSCALL_DEFINE(getsockname) { return sock_name(0); } SYSCALL_DEFINE(getpeername) { return sock_name(1); } -// shutdown(2). Half-close is not modelled -- there is one buffer per -// direction and no per-direction flag -- so SHUT_WR and SHUT_RDWR both mean -// "the peer sees end-of-file", which is the behaviour programs depend on. +// shutdown(2). SHUT_WR is a half-close: the peer reads EOF and can still +// write back (see sock_shutdown). SYSCALL_DEFINE(sockshutdown) { struct socket *s; diff --git a/kernel/sys/sys_poll.c b/kernel/sys/sys_poll.c index a28da29..cdcfac3 100644 --- a/kernel/sys/sys_poll.c +++ b/kernel/sys/sys_poll.c @@ -24,22 +24,47 @@ #define POLL_MAX_FDS 64 -// Sleep one timer tick, or until killed/signaled. Returns 0 to keep -// waiting, -EINTR if the wait must be abandoned. -static int -poll_block_one_tick(void) +// Bumped, under tickslock, by anything that can make a descriptor ready +// sooner than the next tick: see poll_kick(). +static uint poll_seq; + +// Something a poller may be waiting for has happened. The wait below sleeps +// on the tick channel, so this wakes it now rather than at the tick. +// +// Without it every poll() that found nothing slept out the rest of a 10 ms +// tick whatever arrived in the meantime. busybox's wget polls before each +// read, and downloaded at 85 KB/s -- a segment per tick -- where apk, which +// blocks in read() and is woken by the data, managed 5.8 MB/s over the same +// connection. Called from the network stack's wakeup; anything else that +// makes a file readable can call it too, and until it does it is no worse +// than it was. +void +poll_kick(void) { - if (myproc()->killed) - return -EINTR; - if (myproc()->pending_signals & ~myproc()->signal_mask) + acquire(&tickslock); + poll_seq++; + release(&tickslock); + wakeup(&ticks); +} + +// Sleep one timer tick, or until a poll_kick, or until killed/signaled. +// Returns 1 if a tick went by, 0 if it was a kick -- which must not count +// against a timeout, or a timed poll on a busy network would end early -- +// and -EINTR if the wait must be abandoned. epoll waits with it too. +int +poll_wait_tick(void) +{ + struct proc *p = myproc(); + if (p->killed || (p->pending_signals & ~p->signal_mask)) return -EINTR; acquire(&tickslock); - uint t0 = ticks; - while (ticks == t0 && !myproc()->killed && - !(myproc()->pending_signals & ~myproc()->signal_mask)) + uint t0 = ticks, s0 = poll_seq; + while (ticks == t0 && poll_seq == s0 && !p->killed && + !(p->pending_signals & ~p->signal_mask)) sleep(&ticks, &tickslock); + int ticked = ticks != t0; release(&tickslock); - return 0; + return ticked; } // Shared core: fds[] already in kernel memory. timeout_ticks < 0 means @@ -67,9 +92,10 @@ do_poll(struct pollfd *fds, int nfds, int timeout_ticks) } if (nready > 0 || timeout_ticks == 0) return nready; - if (poll_block_one_tick() < 0) + int w = poll_wait_tick(); + if (w < 0) return -EINTR; - if (timeout_ticks > 0) + if (w > 0 && timeout_ticks > 0) timeout_ticks--; } } @@ -215,9 +241,10 @@ sys_select(void) } return nready; } - if (poll_block_one_tick() < 0) + int w = poll_wait_tick(); + if (w < 0) return -EINTR; - if (timeout_ticks > 0) + if (w > 0 && timeout_ticks > 0) timeout_ticks--; } } @@ -287,9 +314,10 @@ sys_pselect6(void) } return nready; } - if (poll_block_one_tick() < 0) + int w = poll_wait_tick(); + if (w < 0) return -EINTR; - if (timeout_ticks > 0) + if (w > 0 && timeout_ticks > 0) timeout_ticks--; } }