#include "types.h" #include "defs.h" #include "stat.h" #include "param.h" #include "fs.h" #include "spinlock.h" #include "sleeplock.h" #include "file.h" #include "posix.h" #include "errno.h" extern struct devsw devsw[]; struct { struct spinlock lock; struct file file[NFILE]; } ftable; void fileinit(void) { initlock(&ftable.lock, "ftable"); } struct file* filealloc(void) { struct file *f; acquire(&ftable.lock); for(f = ftable.file; f < ftable.file + NFILE; f++){ if(f->ref == 0){ f->ref = 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; } } release(&ftable.lock); return 0; } 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; release(&ftable.lock); 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) { 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; // 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){ if(f->nonblock && !(pipe_poll(f->pipe, f->readable, f->writable) & POLLIN)) 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("Schunix: 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_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("Schunix: permission denied (write)\n"); iunlock(f->ip); return -1; } // Recycle-bin check removed here too; see the note in fileread(). iunlock(f->ip); int max = ((LOGSIZE-1-1-2) / 2) * 512; int i = 0; while(i < n){ int n1 = n - i; if(n1 > max) n1 = 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; if(r != n1) panic("short filewrite"); i += r; } return i == n ? n : -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()) rev |= POLLIN; if((events & POLLOUT) && f->writable) rev |= POLLOUT; return rev; } return events & (POLLIN | POLLOUT); } return POLLNVAL; }