#include "core/types.h" #include "core/defs.h" #include "uapi/stat.h" #include "core/param.h" #include "fs/fs.h" #include "core/spinlock.h" #include "core/sleeplock.h" #include "fs/file.h" #include "fs/fsstat.h" #include "uapi/posix.h" #include "uapi/errno.h" #include "uapi/inotify.h" #include "core/kmem.h" #include "dev/pty.h" extern struct devsw devsw[]; // Open file descriptions, shared by every process. NFILE of them in the // kernel image and more from a slab cache once those are all open; a closed // one goes on the free list. This was NFILE and then ENFILE, which a few // processes each holding a hundred descriptors -- an editor, a build, a // shell -- were already a third of the way to. struct { struct spinlock lock; struct file file[NFILE]; struct file *free; struct kmem_cache *more; uint n; uint64 serial; // the last ino handed out } ftable; void fileinit(void) { initlock(&ftable.lock, "ftable"); for(int i = NFILE - 1; i >= 0; i--){ ftable.file[i].fnext = ftable.free; ftable.free = &ftable.file[i]; } ftable.n = NFILE; } uint file_slots(void) { return ftable.n; } struct file* filealloc(void) { struct file *f; acquire(&ftable.lock); if((f = ftable.free) != 0){ ftable.free = f->fnext; } else { // The slab allocator takes only its own lock and kalloc's. if(ftable.more == 0) ftable.more = kmem_cache_create("file", sizeof(struct file)); if(ftable.more == 0 || (f = kmem_cache_alloc(ftable.more)) == 0){ release(&ftable.lock); return 0; } memset(f, 0, sizeof *f); 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 // recycled from a non-blocking device would hand its flag to the next // pipe or socket that happened to land in this slot. f->nonblock = 0; release(&ftable.lock); return f; } struct file* filedup(struct file *f) { acquire(&ftable.lock); if(f->ref < 1) panic("filedup"); f->ref++; release(&ftable.lock); return f; } void fileclose(struct file *f) { struct file ff; acquire(&ftable.lock); if(f->ref < 1) panic("fileclose"); if(--f->ref > 0){ release(&ftable.lock); return; } // Its flock() and OFD locks go with it, before the slot can be handed to // someone else: they are keyed by this address. 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); // Before the path goes: the directory's watchers want the name. if(ff.type == FD_INODE && ff.writable && ff.ip && ff.ip->type == T_FILE) inotify_file(&ff, IN_CLOSE_WRITE); 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) ev_close(ff.ev); else if(ff.type == FD_PIPE) pipeclose(ff.pipe, ff.writable); else if(ff.type == FD_SOCKET) sockclose(ff.sock); else if(ff.type == FD_INODE){ // Give a device a chance to put the hardware back before the inode goes. // Done here rather than in exit() because a program that closes the // framebuffer without exiting has finished with it just as thoroughly. if(ff.ip && ff.ip->type == T_DEV && ff.ip->major >= 0 && ff.ip->major < NDEV && devsw[ff.ip->major].close) devsw[ff.ip->major].close(ff.ip); begin_op(); iput(ff.ip); end_op(); } } // 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) { // A memfd has no inode, so the size is the only field with an answer. It is // the field that matters: mmap length and ftruncate round-trips are checked // against it, and a caller that fstat()s to find out how big the buffer is // gets the truth. if(f->type == FD_MEMFD){ memset(st, 0, sizeof(*st)); st->type = T_FILE; st->size = memfd_size(f->memfd_seg); st->nlink = 1; return 0; } if(f->type == FD_INODE){ ilock(f->ip); stati(f->ip, st); iunlock(f->ip); return 0; } // An eventfd, timerfd, signalfd or epoll set: Linux calls these anonymous // inodes, and fstat succeeds on them with nothing much to say. A program // that fstat()s every descriptor it is handed should not fail on one. if(f->type == FD_EVENT){ memset(st, 0, sizeof(*st)); st->nlink = 1; return 0; } return -1; } int fileread(struct file *f, char *addr, int n) { int r; if(f->readable == 0) return -1; if(f->type == FD_EVENT) return ev_read(f, addr, n); // A memfd is a file: read() and write() move bytes in and out of the shared // pages, and the descriptor carries its own offset like any other. mmap is // what it is usually for, but a descriptor that refuses read() is a surprise // nobody needs. if(f->type == FD_MEMFD){ r = memfd_rw(f->memfd_seg, 1, (uint64)addr, f->off, n, 0); if(r > 0) f->off += r; return r; } // O_NONBLOCK on a pipe or a socket. Reported here rather than inside // piperead/sockread so that the blocking implementations stay unchanged: // this is a check of the same condition their sleep loops wait on, made one // level up. Writes still block -- fcntl documents that. if(f->type == FD_PIPE){ // POLLHUP counts as readable here, and leaving it out was a bug: a pipe // whose writers have all gone is not "nothing yet", it is end of file, and // the answer to a read is 0. Told EAGAIN instead, a caller that polls and // reads spins forever waiting for an end that has already happened. // sockreadable() has always included the equivalent condition. if(f->nonblock && !(pipe_poll(f->pipe, f->readable, f->writable) & (POLLIN | POLLHUP))) return -EAGAIN; return piperead(f->pipe, addr, n); } if(f->type == FD_SOCKET){ if(f->nonblock && !sockreadable(f->sock)) return -EAGAIN; return sockread(f->sock, addr, n); } if(f->type == FD_INODE){ ilock(f->ip); if(f->ip->type == 3){ if(f->ip->major < 0 || f->ip->major >= NDEV || !devsw[f->ip->major].read){ iunlock(f->ip); return -1; } // O_NONBLOCK. Without this a game that opens an input device // non-blocking and drains it once per frame stops dead on the first // frame -- it blocks in read() waiting for a key, and the result looks // exactly like a hung renderer rather than a blocking read. if(f->nonblock && devsw[f->ip->major].ready && !devsw[f->ip->major].ready(f->ip)){ iunlock(f->ip); return -EAGAIN; } r = devsw[f->ip->major].read(f->ip, f->off, addr, n); if(r > 0) f->off += r; iunlock(f->ip); return r; } if((f->ip->rwmode & 2) == 0){ cprintf("SchOS: permission denied (read)\n"); iunlock(f->ip); return -1; } // The "recycle bin" check that used to sit here is gone. // // It made a file that still existed, still had a name and still had blocks // refuse every read with a printf -- a state no POSIX program is prepared // for. It was reachable only through `delete`, a NO_LINUX syscall from the // third-party filesystem this file derives from, which the decision to keep // only Linux numbering had already orphaned. // // The on-disk `showmode` field stays for now: removing it changes the inode // layout, and this whole file is being replaced by SchFS. It is simply // never consulted. if((r = readi(f->ip, 0, (uint64)addr, f->off, n)) > 0) f->off += r; iunlock(f->ip); return r; } panic("fileread"); } int filewrite(struct file *f, char *addr, int n) { int r; if(f->writable == 0) return -1; if(f->type == FD_EVENT) return ev_write(f, addr, n); if(f->type == FD_MEMFD){ r = memfd_rw(f->memfd_seg, 1, (uint64)addr, f->off, n, 1); if(r > 0) f->off += r; return r; } if(f->type == FD_PIPE) return pipewrite(f->pipe, addr, n); if(f->type == FD_SOCKET) return sockwrite(f->sock, addr, n); if(f->type == FD_INODE){ ilock(f->ip); if(f->ip->type == 3){ if(f->ip->major < 0 || f->ip->major >= NDEV || !devsw[f->ip->major].write){ iunlock(f->ip); return -1; } r = devsw[f->ip->major].write(f->ip, f->off, addr, n); if(r > 0) f->off += r; iunlock(f->ip); return r; } if((f->ip->rwmode & 1) == 0){ cprintf("SchOS: permission denied (write)\n"); iunlock(f->ip); return -1; } // Recycle-bin check removed here too; see the note in fileread(). iunlock(f->ip); // How much may go in one call is the filesystem's business. v1 is bounded // by its log; SchFS is not bounded at all and would rather have the lot, // because it makes one transaction out of whatever it is given. uint max = f->ip->ops->max_write; int i = 0; while(i < n){ int n1 = n - i; if(max && (uint)n1 > max) n1 = (int)max; begin_op(); ilock(f->ip); if ((r = writei(f->ip, 1, (uint64)(addr + i), f->off, n1)) > 0) f->off += r; iunlock(f->ip); end_op(); if(r < 0) break; i += r; // Short means full. v1 and SchFS never wrote short, which is why this // was a panic; FAT does, when the volume or its root directory fills. // write(2) reports what went in, and the next call gets the error. if(r != n1) break; } if(i > 0){ inotify_file(f, IN_MODIFY); return i; } return r < 0 ? r : -1; } panic("filewrite"); } // poll(2) readiness for one open file. Returns a POLL* bitmask restricted // to the requested events (plus error/hangup conditions, which POSIX says // are always reported). Pipes and the console tty have real state; other // inodes (regular files, block-ish devices) are always ready, per POSIX. int file_poll(struct file *f, int events) { if(f->type == FD_EVENT) return ev_poll(f, events); if(f->type == FD_PIPE) return pipe_poll(f->pipe, f->readable, f->writable) & (events | POLLERR | POLLHUP); if(f->type == FD_SOCKET){ int rev = 0; if(sockreadable(f->sock)) rev |= POLLIN; // A stream socket is writable whenever it has a peer. The buffer can // still be full, in which case the write blocks -- same latitude poll() // has always had, and the same answer a pipe gives. if(sock_hup(f->sock)) rev |= POLLHUP; else rev |= POLLOUT; return rev & (events | POLLERR | POLLHUP); } if(f->type == FD_INODE){ // An input device is only readable when an event is waiting. Reporting it // always-ready -- which is what the generic answer below does -- turns a // poll loop into a busy loop that blocks in read(). if(f->ip && f->ip->type == T_DEV && f->ip->major == INPUT_MAJOR){ int rev = 0; if((events & POLLIN) && f->readable && input_ready(f->ip->minor)) rev |= POLLIN; if(events & POLLOUT) rev |= POLLOUT; return rev; } if(f->ip && f->ip->type == T_DEV && (f->ip->major == PTM_MAJOR || f->ip->major == PTS_MAJOR)) return pty_poll(f->ip, events, f->readable, f->writable); // /dev/dsp is writable when the driver has room. ALSA's OSS plugin waits // in poll() for exactly this between periods. if(f->ip && f->ip->type == T_DEV && f->ip->major == AUDIO_MAJOR) return (events & POLLOUT) && f->writable && dsp_writable() ? POLLOUT : 0; if(f->ip && f->ip->type == T_DEV && f->ip->major == CONSOLE){ int rev = 0; if((events & POLLIN) && f->readable && console_input_ready(f->ip ? f->ip->minor : 1)) rev |= POLLIN; if((events & POLLOUT) && f->writable) rev |= POLLOUT; return rev; } return events & (POLLIN | POLLOUT); } return POLLNVAL; }