#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 "core/kmem.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; } 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; 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; } ff = *f; f->ref = 0; f->type = FD_NONE; f->fnext = ftable.free; ftable.free = f; FSSTAT_DROP(fsstat.file_used); // 1 -> 0 release(&ftable.lock); if(ff.type == FD_MEMFD) shm_detach(ff.memfd_seg); // the last mapper frees the pages 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(); } } 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; } return -1; } int fileread(struct file *f, char *addr, int n) { int r; if(f->readable == 0) return -1; // 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_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) 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_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 == 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; }