fs: Initial VFS port
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/atomic.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/pipe.h>
|
||||
#include <zinnia/poll.h>
|
||||
#include <zinnia/ringbuf.h>
|
||||
#include <zinnia/sched.h>
|
||||
#include <zinnia/usercopy.h>
|
||||
#include <zinnia/vfs.h>
|
||||
#include <uapi/ioctls.h>
|
||||
#include "internal.h"
|
||||
|
||||
constexpr size_t PIPE_CAPACITY = 0x10000;
|
||||
|
||||
struct pipe {
|
||||
mutex_t lock;
|
||||
struct ringbuf buffer;
|
||||
size_t readers;
|
||||
size_t writers;
|
||||
uint64_t read_opens;
|
||||
uint64_t write_opens;
|
||||
struct poll_event read_event;
|
||||
struct poll_event write_event;
|
||||
struct poll_event open_event;
|
||||
};
|
||||
|
||||
static const struct vfs_file_ops pipe_file_ops;
|
||||
|
||||
static errno_t pipe_new(struct pipe** out) {
|
||||
struct pipe* pipe = mem_alloc(sizeof(*pipe), 0);
|
||||
if (!pipe)
|
||||
return ENOMEM;
|
||||
|
||||
errno_t status = ringbuf_init(&pipe->buffer, PIPE_CAPACITY);
|
||||
if (status) {
|
||||
mem_free(pipe);
|
||||
return status;
|
||||
}
|
||||
|
||||
mutex_init(&pipe->lock);
|
||||
poll_event_init(&pipe->read_event);
|
||||
poll_event_init(&pipe->write_event);
|
||||
poll_event_init(&pipe->open_event);
|
||||
*out = pipe;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pipe_destroy(struct pipe* pipe) {
|
||||
ringbuf_fini(&pipe->buffer);
|
||||
mem_free(pipe);
|
||||
}
|
||||
|
||||
static bool file_readable(struct vfs_file* file) {
|
||||
return atomic_load(&file->flags, ATOMIC_RELAXED) & O_RDONLY;
|
||||
}
|
||||
|
||||
static bool file_writable(struct vfs_file* file) {
|
||||
return atomic_load(&file->flags, ATOMIC_RELAXED) & O_WRONLY;
|
||||
}
|
||||
|
||||
static bool file_nonblocking(struct vfs_file* file) {
|
||||
return atomic_load(&file->flags, ATOMIC_RELAXED) & O_NONBLOCK;
|
||||
}
|
||||
|
||||
static void notify_all(struct pipe* pipe) {
|
||||
poll_event_notify(&pipe->read_event);
|
||||
poll_event_notify(&pipe->write_event);
|
||||
poll_event_notify(&pipe->open_event);
|
||||
}
|
||||
|
||||
static uint64_t attach_locked(struct pipe* pipe, struct vfs_file* file) {
|
||||
file->ops = &pipe_file_ops;
|
||||
file->priv = pipe;
|
||||
vfs_stream_open(file);
|
||||
|
||||
if (file_readable(file)) {
|
||||
pipe->readers++;
|
||||
pipe->read_opens++;
|
||||
}
|
||||
if (file_writable(file)) {
|
||||
pipe->writers++;
|
||||
pipe->write_opens++;
|
||||
}
|
||||
return file_readable(file) ? pipe->write_opens : pipe->read_opens;
|
||||
}
|
||||
|
||||
static void pipe_release(struct vfs_file* file) {
|
||||
struct pipe* pipe = file->priv;
|
||||
|
||||
mutex_lock(&pipe->lock);
|
||||
if (file_readable(file))
|
||||
pipe->readers--;
|
||||
if (file_writable(file))
|
||||
pipe->writers--;
|
||||
mutex_unlock(&pipe->lock);
|
||||
|
||||
notify_all(pipe);
|
||||
}
|
||||
|
||||
static errno_t pipe_read(struct vfs_file* file, iovec_iter_t* buffer, uint64_t offset, ssize_t* out_read) {
|
||||
struct pipe* pipe = file->priv;
|
||||
const size_t want = iovec_iter_len(buffer);
|
||||
|
||||
struct poll_table table;
|
||||
poll_table_init(&table);
|
||||
poll_wait(&table, &pipe->read_event);
|
||||
|
||||
errno_t status = 0;
|
||||
size_t read = 0;
|
||||
mutex_lock(&pipe->lock);
|
||||
while (true) {
|
||||
if (ringbuf_len(&pipe->buffer)) {
|
||||
status = ringbuf_read_iter(&pipe->buffer, buffer, want, &read);
|
||||
if (read)
|
||||
poll_event_notify(&pipe->write_event);
|
||||
break;
|
||||
}
|
||||
if (pipe->writers == 0)
|
||||
break;
|
||||
if (file_nonblocking(file)) {
|
||||
status = EAGAIN;
|
||||
break;
|
||||
}
|
||||
if (poll_table_sleep(&table, &pipe->lock, nullptr) == EINTR) {
|
||||
status = ERESTART;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&pipe->lock);
|
||||
poll_table_fini(&table);
|
||||
|
||||
*out_read = read;
|
||||
return status;
|
||||
}
|
||||
|
||||
static errno_t pipe_write(struct vfs_file* file, iovec_iter_t* buffer, uint64_t offset, ssize_t* out_written) {
|
||||
struct pipe* pipe = file->priv;
|
||||
const size_t total = iovec_iter_len(buffer);
|
||||
const bool atomic = total <= __PIPE_BUF;
|
||||
|
||||
struct poll_table table;
|
||||
poll_table_init(&table);
|
||||
poll_wait(&table, &pipe->write_event);
|
||||
|
||||
errno_t status = 0;
|
||||
size_t done = 0;
|
||||
mutex_lock(&pipe->lock);
|
||||
while (done < total) {
|
||||
if (pipe->readers == 0) {
|
||||
status = EPIPE;
|
||||
break;
|
||||
}
|
||||
|
||||
const size_t remaining = total - done;
|
||||
const size_t space = ringbuf_space(&pipe->buffer);
|
||||
if (space && (!atomic || space >= remaining)) {
|
||||
size_t written;
|
||||
status = ringbuf_write_iter(&pipe->buffer, buffer, remaining, &written);
|
||||
done += written;
|
||||
if (written)
|
||||
poll_event_notify(&pipe->read_event);
|
||||
if (status)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file_nonblocking(file)) {
|
||||
status = EAGAIN;
|
||||
break;
|
||||
}
|
||||
if (poll_table_sleep(&table, &pipe->lock, nullptr) == EINTR) {
|
||||
status = ERESTART;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&pipe->lock);
|
||||
poll_table_fini(&table);
|
||||
|
||||
*out_written = done;
|
||||
return done ? 0 : status;
|
||||
}
|
||||
|
||||
static errno_t pipe_ioctl(struct vfs_file* file, uintptr_t request, uintptr_t arg, uintptr_t* out_result) {
|
||||
struct pipe* pipe = file->priv;
|
||||
|
||||
switch ((uint32_t)request) {
|
||||
case __FIONREAD: {
|
||||
mutex_lock(&pipe->lock);
|
||||
int len = ringbuf_len(&pipe->buffer);
|
||||
mutex_unlock(&pipe->lock);
|
||||
return usercopy_write((__user void*)arg, &len, sizeof(len)) ? 0 : EFAULT;
|
||||
}
|
||||
default:
|
||||
return ENOTTY;
|
||||
}
|
||||
}
|
||||
|
||||
static enum poll_flags pipe_poll(struct vfs_file* file, enum poll_flags events, struct poll_table* table) {
|
||||
struct pipe* pipe = file->priv;
|
||||
|
||||
if (file_readable(file))
|
||||
poll_wait(table, &pipe->read_event);
|
||||
if (file_writable(file))
|
||||
poll_wait(table, &pipe->write_event);
|
||||
|
||||
enum poll_flags revents = 0;
|
||||
mutex_lock(&pipe->lock);
|
||||
if (file_readable(file)) {
|
||||
if (ringbuf_len(&pipe->buffer))
|
||||
revents |= POLLIN | POLLRDNORM;
|
||||
if (pipe->writers == 0)
|
||||
revents |= POLLHUP;
|
||||
}
|
||||
if (file_writable(file)) {
|
||||
if (ringbuf_space(&pipe->buffer) >= __PIPE_BUF)
|
||||
revents |= POLLOUT | POLLWRNORM;
|
||||
if (pipe->readers == 0)
|
||||
revents |= POLLERR;
|
||||
}
|
||||
mutex_unlock(&pipe->lock);
|
||||
return revents;
|
||||
}
|
||||
|
||||
static const struct vfs_file_ops pipe_file_ops = {
|
||||
.read = pipe_read,
|
||||
.write = pipe_write,
|
||||
.ioctl = pipe_ioctl,
|
||||
.poll = pipe_poll,
|
||||
.release = pipe_release,
|
||||
};
|
||||
|
||||
errno_t pipe_create(
|
||||
enum open_flags flags,
|
||||
const struct ident* identity,
|
||||
struct vfs_file** out_read,
|
||||
struct vfs_file** out_write
|
||||
) {
|
||||
struct vfs_node* node;
|
||||
errno_t status = vfs_create_anon_inode(&pipe_file_ops, VNODE_FIFO, 0600, identity, &node);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
status = pipe_new(&node->pipe);
|
||||
if (status) {
|
||||
vfs_node_unref(node);
|
||||
return status;
|
||||
}
|
||||
|
||||
struct pipe* pipe = node->pipe;
|
||||
const enum open_flags extra = flags & O_NONBLOCK;
|
||||
struct vfs_file* read_end;
|
||||
struct vfs_file* write_end;
|
||||
status = vfs_file_new(node, nullptr, O_RDONLY | extra, &read_end);
|
||||
if (!status) {
|
||||
mutex_lock(&pipe->lock);
|
||||
attach_locked(pipe, read_end);
|
||||
mutex_unlock(&pipe->lock);
|
||||
|
||||
status = vfs_file_new(node, nullptr, O_WRONLY | extra, &write_end);
|
||||
if (status)
|
||||
vfs_file_unref(read_end);
|
||||
}
|
||||
if (!status) {
|
||||
mutex_lock(&pipe->lock);
|
||||
attach_locked(pipe, write_end);
|
||||
mutex_unlock(&pipe->lock);
|
||||
}
|
||||
vfs_node_unref(node);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
*out_read = read_end;
|
||||
*out_write = write_end;
|
||||
return 0;
|
||||
}
|
||||
|
||||
errno_t vfs_fifo_open(struct vfs_node* node, struct vfs_file* file) {
|
||||
const bool readable = file_readable(file);
|
||||
const bool writable = file_writable(file);
|
||||
const bool nonblock = atomic_load(&file->flags, ATOMIC_RELAXED) & O_NONBLOCK;
|
||||
if (!readable && !writable)
|
||||
return EINVAL;
|
||||
|
||||
errno_t status = 0;
|
||||
mutex_lock(&node->lock);
|
||||
if (!node->pipe)
|
||||
status = pipe_new(&node->pipe);
|
||||
struct pipe* pipe = node->pipe;
|
||||
mutex_unlock(&node->lock);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
mutex_lock(&pipe->lock);
|
||||
if (writable && !readable && nonblock && pipe->readers == 0) {
|
||||
mutex_unlock(&pipe->lock);
|
||||
return ENXIO;
|
||||
}
|
||||
const uint64_t peer_opens = attach_locked(pipe, file);
|
||||
mutex_unlock(&pipe->lock);
|
||||
notify_all(pipe);
|
||||
|
||||
if (nonblock || (readable && writable))
|
||||
return 0;
|
||||
|
||||
struct poll_table table;
|
||||
poll_table_init(&table);
|
||||
poll_wait(&table, &pipe->open_event);
|
||||
|
||||
mutex_lock(&pipe->lock);
|
||||
while (true) {
|
||||
const size_t peers = readable ? pipe->writers : pipe->readers;
|
||||
const uint64_t opens = readable ? pipe->write_opens : pipe->read_opens;
|
||||
if (peers || opens != peer_opens)
|
||||
break;
|
||||
if (poll_table_sleep(&table, &pipe->lock, nullptr) == EINTR) {
|
||||
status = ERESTART;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&pipe->lock);
|
||||
poll_table_fini(&table);
|
||||
return status;
|
||||
}
|
||||
Reference in New Issue
Block a user