Files
2026-10-25 18:35:03 +01:00

326 lines
8.6 KiB
C

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