An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
294 lines
7.3 KiB
C
294 lines
7.3 KiB
C
#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;
|
|
}
|