mirror of
https://github.com/ish-app/ish.git
synced 2026-01-25 14:06:40 +00:00
402 lines
11 KiB
C
402 lines
11 KiB
C
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "debug.h"
|
|
#include "kernel/calls.h"
|
|
#include "kernel/resource.h"
|
|
#include "kernel/fs.h"
|
|
#include "fs/poll.h"
|
|
#include "fs/fd.h"
|
|
#include "fs/inode.h"
|
|
|
|
struct fd *fd_create(const struct fd_ops *ops) {
|
|
struct fd *fd = malloc(sizeof(struct fd));
|
|
if (fd == NULL)
|
|
return NULL;
|
|
*fd = (struct fd) {};
|
|
fd->ops = ops;
|
|
fd->refcount = 1;
|
|
fd->flags = 0;
|
|
fd->mount = NULL;
|
|
fd->offset = 0;
|
|
list_init(&fd->poll_fds);
|
|
lock_init(&fd->poll_lock);
|
|
lock_init(&fd->lock);
|
|
cond_init(&fd->cond);
|
|
return fd;
|
|
}
|
|
|
|
struct fd *fd_retain(struct fd *fd) {
|
|
fd->refcount++;
|
|
return fd;
|
|
}
|
|
|
|
int fd_close(struct fd *fd) {
|
|
int err = 0;
|
|
if (--fd->refcount == 0) {
|
|
lock(&fd->poll_lock);
|
|
struct poll_fd *poll_fd, *tmp;
|
|
list_for_each_entry_safe(&fd->poll_fds, poll_fd, tmp, polls) {
|
|
lock(&poll_fd->poll->lock);
|
|
list_remove(&poll_fd->polls);
|
|
list_remove(&poll_fd->fds);
|
|
unlock(&poll_fd->poll->lock);
|
|
free(poll_fd);
|
|
}
|
|
unlock(&fd->poll_lock);
|
|
if (fd->ops->close)
|
|
err = fd->ops->close(fd);
|
|
// see comment in close in kernel/fs.h
|
|
if (fd->mount && fd->mount->fs->close && fd->mount->fs->close != fd->ops->close) {
|
|
int new_err = fd->mount->fs->close(fd);
|
|
if (new_err < 0)
|
|
err = new_err;
|
|
}
|
|
|
|
if (fd->inode)
|
|
inode_release(fd->inode);
|
|
if (fd->mount)
|
|
mount_release(fd->mount);
|
|
free(fd);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int fdtable_resize(struct fdtable *table, unsigned size);
|
|
|
|
struct fdtable *fdtable_new(int size) {
|
|
struct fdtable *fdt = malloc(sizeof(struct fdtable));
|
|
if (fdt == NULL)
|
|
return ERR_PTR(_ENOMEM);
|
|
fdt->refcount = 1;
|
|
fdt->size = 0;
|
|
fdt->files = NULL;
|
|
fdt->cloexec = NULL;
|
|
lock_init(&fdt->lock);
|
|
int err = fdtable_resize(fdt, size);
|
|
if (err < 0) {
|
|
free(fdt);
|
|
return ERR_PTR(err);
|
|
}
|
|
return fdt;
|
|
}
|
|
|
|
static int fdtable_close(struct fdtable *table, fd_t f);
|
|
|
|
// FIXME this looks like it has the classic refcount UAF
|
|
void fdtable_release(struct fdtable *table) {
|
|
lock(&table->lock);
|
|
if (--table->refcount == 0) {
|
|
for (fd_t f = 0; (unsigned) f < table->size; f++)
|
|
fdtable_close(table, f);
|
|
free(table->files);
|
|
free(table->cloexec);
|
|
unlock(&table->lock);
|
|
free(table);
|
|
} else {
|
|
unlock(&table->lock);
|
|
}
|
|
}
|
|
|
|
static int fdtable_resize(struct fdtable *table, unsigned size) {
|
|
// currently the only legitimate use of this is to expand the table
|
|
assert(size > table->size);
|
|
|
|
struct fd **files = malloc(sizeof(struct fd *) * size);
|
|
if (files == NULL)
|
|
return _ENOMEM;
|
|
memset(files, 0, sizeof(struct fd *) * size);
|
|
if (table->files)
|
|
memcpy(files, table->files, sizeof(struct fd *) * table->size);
|
|
|
|
bits_t *cloexec = malloc(BITS_SIZE(size));
|
|
if (cloexec == NULL) {
|
|
free(files);
|
|
return _ENOMEM;
|
|
}
|
|
memset(cloexec, 0, BITS_SIZE(size));
|
|
if (table->cloexec)
|
|
memcpy(cloexec, table->cloexec, BITS_SIZE(table->size));
|
|
|
|
free(table->files);
|
|
table->files = files;
|
|
free(table->cloexec);
|
|
table->cloexec = cloexec;
|
|
table->size = size;
|
|
return 0;
|
|
}
|
|
|
|
struct fdtable *fdtable_copy(struct fdtable *table) {
|
|
lock(&table->lock);
|
|
int size = table->size;
|
|
struct fdtable *new_table = fdtable_new(size);
|
|
if (IS_ERR(new_table)) {
|
|
unlock(&table->lock);
|
|
return new_table;
|
|
}
|
|
memcpy(new_table->files, table->files, sizeof(struct fd *) * size);
|
|
for (fd_t f = 0; f < size; f++)
|
|
if (new_table->files[f])
|
|
new_table->files[f]->refcount++;
|
|
memcpy(new_table->cloexec, table->cloexec, BITS_SIZE(size));
|
|
unlock(&table->lock);
|
|
return new_table;
|
|
}
|
|
|
|
static int fdtable_expand(struct fdtable *table, fd_t max) {
|
|
unsigned size = max + 1;
|
|
if (size > rlimit(RLIMIT_NOFILE_))
|
|
return _EMFILE;
|
|
if (table->size >= size)
|
|
return 0;
|
|
return fdtable_resize(table, max + 1);
|
|
}
|
|
|
|
struct fd *fdtable_get(struct fdtable *table, fd_t f) {
|
|
if (f < 0 || (unsigned) f >= current->files->size)
|
|
return NULL;
|
|
return table->files[f];
|
|
}
|
|
|
|
struct fd *f_get(fd_t f) {
|
|
lock(¤t->files->lock);
|
|
struct fd *fd = fdtable_get(current->files, f);
|
|
unlock(¤t->files->lock);
|
|
return fd;
|
|
}
|
|
|
|
static fd_t f_install_start(struct fd *fd, fd_t start) {
|
|
assert(start >= 0);
|
|
struct fdtable *table = current->files;
|
|
unsigned size = rlimit(RLIMIT_NOFILE_);
|
|
if (size > table->size)
|
|
size = table->size;
|
|
|
|
fd_t f;
|
|
for (f = start; (unsigned) f < size; f++)
|
|
if (table->files[f] == NULL)
|
|
break;
|
|
if ((unsigned) f >= size) {
|
|
int err = fdtable_expand(table, f);
|
|
if (err < 0)
|
|
f = err;
|
|
}
|
|
|
|
if (f >= 0) {
|
|
table->files[f] = fd;
|
|
bit_clear(f, table->cloexec);
|
|
} else {
|
|
fd_close(fd);
|
|
}
|
|
return f;
|
|
}
|
|
|
|
fd_t f_install(struct fd *fd, int flags) {
|
|
lock(¤t->files->lock);
|
|
fd_t f = f_install_start(fd, 0);
|
|
if (f >= 0) {
|
|
if (flags & O_CLOEXEC_)
|
|
bit_set(f, current->files->cloexec);
|
|
if (flags & O_NONBLOCK_)
|
|
fd_setflags(fd, O_NONBLOCK_);
|
|
}
|
|
unlock(¤t->files->lock);
|
|
return f;
|
|
}
|
|
|
|
static int fdtable_close(struct fdtable *table, fd_t f) {
|
|
struct fd *fd = fdtable_get(table, f);
|
|
if (fd == NULL)
|
|
return _EBADF;
|
|
if (fd->inode != NULL) // temporary hack for files like sockets that right now don't have inodes but will eventually
|
|
file_lock_remove_owned_by(fd, table);
|
|
int err = fd_close(fd);
|
|
table->files[f] = NULL;
|
|
bit_clear(f, table->cloexec);
|
|
return err;
|
|
}
|
|
|
|
int f_close(fd_t f) {
|
|
lock(¤t->files->lock);
|
|
int err = fdtable_close(current->files, f);
|
|
unlock(¤t->files->lock);
|
|
return err;
|
|
}
|
|
|
|
dword_t sys_close(fd_t f) {
|
|
STRACE("close(%d)", f);
|
|
return f_close(f);
|
|
}
|
|
|
|
void fdtable_do_cloexec(struct fdtable *table) {
|
|
lock(&table->lock);
|
|
for (fd_t f = 0; (unsigned) f < table->size; f++)
|
|
if (bit_test(f, table->cloexec))
|
|
fdtable_close(table, f);
|
|
unlock(&table->lock);
|
|
}
|
|
|
|
#define F_DUPFD_ 0
|
|
#define F_GETFD_ 1
|
|
#define F_SETFD_ 2
|
|
#define F_GETFL_ 3
|
|
#define F_SETFL_ 4
|
|
|
|
#define F_GETLK_ 5
|
|
#define F_SETLK_ 6
|
|
#define F_SETLKW_ 7
|
|
#define F_GETLK64_ 12
|
|
#define F_SETLK64_ 13
|
|
#define F_SETLKW64_ 14
|
|
|
|
#define F_DUPFD_CLOEXEC_ 1030
|
|
|
|
dword_t sys_dup(fd_t f) {
|
|
STRACE("dup(%d)", f);
|
|
struct fd *fd = f_get(f);
|
|
if (fd == NULL)
|
|
return _EBADF;
|
|
fd->refcount++;
|
|
return f_install(fd, 0);
|
|
}
|
|
|
|
dword_t sys_dup3(fd_t f, fd_t new_f, int_t flags) {
|
|
STRACE("dup3(%d, %d, %d)", f, new_f, flags);
|
|
struct fdtable *table = current->files;
|
|
struct fd *fd = f_get(f);
|
|
if (fd == NULL)
|
|
return _EBADF;
|
|
int err = fdtable_expand(table, new_f);
|
|
if (err < 0)
|
|
return err;
|
|
fd_retain(fd);
|
|
f_close(new_f);
|
|
table->files[new_f] = fd;
|
|
if (flags & O_CLOEXEC_)
|
|
bit_set(new_f, table->cloexec);
|
|
return new_f;
|
|
}
|
|
|
|
dword_t sys_dup2(fd_t f, fd_t new_f) {
|
|
return sys_dup3(f, new_f, 0);
|
|
}
|
|
|
|
int fd_getflags(struct fd *fd) {
|
|
if (fd->ops->getflags)
|
|
return fd->ops->getflags(fd);
|
|
return fd->flags;
|
|
}
|
|
|
|
#define FD_ALLOWED_FLAGS (O_APPEND_ | O_NONBLOCK_)
|
|
int fd_setflags(struct fd *fd, int flags) {
|
|
if (fd->ops->setflags)
|
|
return fd->ops->setflags(fd, flags);
|
|
fd->flags = (fd->flags & ~FD_ALLOWED_FLAGS) | (flags & FD_ALLOWED_FLAGS);
|
|
return 0;
|
|
}
|
|
|
|
dword_t sys_fcntl(fd_t f, dword_t cmd, dword_t arg) {
|
|
struct fdtable *table = current->files;
|
|
struct fd *fd = f_get(f);
|
|
if (fd == NULL)
|
|
return _EBADF;
|
|
struct flock32_ flock32;
|
|
struct flock_ flock;
|
|
fd_t new_f;
|
|
int err;
|
|
switch (cmd) {
|
|
case F_DUPFD_:
|
|
STRACE("fcntl(%d, F_DUPFD, %d)", f, arg);
|
|
fd->refcount++;
|
|
return f_install_start(fd, arg);
|
|
|
|
case F_DUPFD_CLOEXEC_:
|
|
STRACE("fcntl(%d, F_DUPFD_CLOEXEC, %d)", f, arg);
|
|
fd->refcount++;
|
|
new_f = f_install_start(fd, arg);
|
|
bit_set(new_f, table->cloexec);
|
|
return new_f;
|
|
|
|
case F_GETFD_:
|
|
STRACE("fcntl(%d, F_GETFD)", f);
|
|
return bit_test(f, table->cloexec);
|
|
case F_SETFD_:
|
|
STRACE("fcntl(%d, F_SETFD, 0x%x)", f, arg);
|
|
if (arg & 1)
|
|
bit_set(f, table->cloexec);
|
|
else
|
|
bit_clear(f, table->cloexec);
|
|
return 0;
|
|
|
|
case F_GETFL_:
|
|
STRACE("fcntl(%d, F_GETFL)", f);
|
|
return fd_getflags(fd);
|
|
case F_SETFL_:
|
|
STRACE("fcntl(%d, F_SETFL, %#x)", f, arg);
|
|
return fd_setflags(fd, arg);
|
|
|
|
case F_GETLK_:
|
|
STRACE("fcntl(%d, F_GETLK, %#x)", f, arg);
|
|
if (user_read(arg, &flock32, sizeof(flock32)))
|
|
return _EFAULT;
|
|
flock.type = flock32.type;
|
|
flock.whence = flock32.whence;
|
|
flock.start = flock32.start;
|
|
flock.len = flock32.len;
|
|
flock.pid = flock32.pid;
|
|
err = fcntl_getlk(fd, &flock);
|
|
if (err >= 0) {
|
|
flock32.type = flock.type;
|
|
flock32.whence = flock.whence;
|
|
flock32.start = flock.start;
|
|
flock32.len = flock.len;
|
|
flock32.pid = flock.pid;
|
|
if (user_write(arg, &flock32, sizeof(flock32)))
|
|
return _EFAULT;
|
|
}
|
|
return err;
|
|
|
|
case F_GETLK64_:
|
|
STRACE("fcntl(%d, F_GETLK64, %#x)", f, arg);
|
|
if (user_read(arg, &flock, sizeof(flock)))
|
|
return _EFAULT;
|
|
err = fcntl_getlk(fd, &flock);
|
|
if (err >= 0)
|
|
if (user_write(arg, &flock, sizeof(flock)))
|
|
return _EFAULT;
|
|
return err;
|
|
|
|
case F_SETLK_:
|
|
case F_SETLKW_:
|
|
STRACE("fcntl(%d, F_SETLK%*s, %#x)", f, cmd == F_SETLKW_, "W", arg);
|
|
if (user_read(arg, &flock32, sizeof(flock32)))
|
|
return _EFAULT;
|
|
flock.type = flock32.type;
|
|
flock.whence = flock32.whence;
|
|
flock.start = flock32.start;
|
|
flock.len = flock32.len;
|
|
flock.pid = flock32.pid;
|
|
return fcntl_setlk(fd, &flock, cmd == F_SETLKW64_);
|
|
|
|
case F_SETLK64_:
|
|
case F_SETLKW64_:
|
|
STRACE("fcntl(%d, F_SETLK%*s64, %#x)", f, cmd == F_SETLKW_, "W", arg);
|
|
if (user_read(arg, &flock, sizeof(flock)))
|
|
return _EFAULT;
|
|
return fcntl_setlk(fd, &flock, cmd == F_SETLKW_);
|
|
|
|
default:
|
|
STRACE("fcntl(%d, %d)", f, cmd);
|
|
return _EINVAL;
|
|
}
|
|
}
|
|
|
|
dword_t sys_fcntl32(fd_t fd, dword_t cmd, dword_t arg) {
|
|
switch (cmd) {
|
|
case F_GETLK64_:
|
|
case F_SETLK64_:
|
|
case F_SETLKW64_:
|
|
return _EINVAL;
|
|
}
|
|
return sys_fcntl(fd, cmd, arg);
|
|
}
|