SchOS/kernel/fs/file.c
Nexoniarz d0f7acf9eb Initial commit: Schunix as it stands before the SchOS rename
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.
2026-08-09 13:47:01 +02:00

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;
}