libc: io: implement file io buffering and internal locking for concurrency
This commit is contained in:
+299
-46
@@ -1,76 +1,329 @@
|
||||
#include "file.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <mango/futex.h>
|
||||
#include <mango/log.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define DEFAULT_BUFFER_SIZE 4096
|
||||
#define OP_READ 1
|
||||
#define OP_WRITE 2
|
||||
|
||||
#define FILE_UNUSABLE(f) (((f)->f_flags & (FILE_EOF | FILE_ERR)) != 0)
|
||||
#define FILE_UNUSABLE(f) (((f)->f_flags & (FILE_EOF | FILE_ERR)) != 0)
|
||||
|
||||
int __libc_file_refill(struct __opaque_file *f)
|
||||
static long clear_buf(struct __opaque_file *f)
|
||||
{
|
||||
f->f_buf_datalen = 0;
|
||||
f->f_buf_readptr = 0;
|
||||
__libc_ringbuf_clear(&f->f_buf);
|
||||
lseek(f->f_fd, f->f_seek, SEEK_SET);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (f->f_fd < 0) {
|
||||
return EBADF;
|
||||
static long refill_buf(struct __opaque_file *f)
|
||||
{
|
||||
kern_tracef("refill");
|
||||
char *buf = NULL;
|
||||
size_t available = 0;
|
||||
long r = 0;
|
||||
size_t nr_read = 0;
|
||||
|
||||
while (1) {
|
||||
__libc_ringbuf_get_write_buffer(&f->f_buf, &buf, &available);
|
||||
if (available == 0) {
|
||||
__libc_ringbuf_put_write_buffer(
|
||||
&f->f_buf,
|
||||
&buf,
|
||||
&available);
|
||||
break;
|
||||
}
|
||||
|
||||
kern_tracef("read(%d, %p, %zu)", f->f_fd, buf, available);
|
||||
r = read(f->f_fd, buf, available);
|
||||
if (r <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
available = r;
|
||||
nr_read += r;
|
||||
__libc_ringbuf_put_write_buffer(&f->f_buf, &buf, &available);
|
||||
}
|
||||
|
||||
if (f->f_flags & FILE_ERR) {
|
||||
return EIO;
|
||||
if (nr_read > 0) {
|
||||
return nr_read;
|
||||
}
|
||||
|
||||
if (f->f_flags & FILE_EOF) {
|
||||
return SUCCESS;
|
||||
if (r < 0) {
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (!f->f_buf) {
|
||||
f->f_buf_max = DEFAULT_BUFFER_SIZE;
|
||||
f->f_buf = malloc(f->f_buf_max);
|
||||
if (!f->f_buf) {
|
||||
f->f_buf_max = 0;
|
||||
return ENOMEM;
|
||||
return nr_read;
|
||||
}
|
||||
|
||||
static long flush_buf(struct __opaque_file *f)
|
||||
{
|
||||
const char *buf = NULL;
|
||||
size_t available = 0;
|
||||
long r = 0;
|
||||
size_t nr_written = 0;
|
||||
|
||||
while (1) {
|
||||
__libc_ringbuf_get_read_buffer(&f->f_buf, &buf, &available);
|
||||
if (available == 0) {
|
||||
__libc_ringbuf_put_read_buffer(
|
||||
&f->f_buf,
|
||||
&buf,
|
||||
&available);
|
||||
break;
|
||||
}
|
||||
|
||||
r = write(f->f_fd, buf, available);
|
||||
kern_tracef(
|
||||
"write(%d, %p, %zu) = %ld",
|
||||
f->f_fd,
|
||||
buf,
|
||||
available,
|
||||
r);
|
||||
if (r < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
available = r;
|
||||
nr_written += r;
|
||||
__libc_ringbuf_put_read_buffer(&f->f_buf, &buf, &available);
|
||||
}
|
||||
|
||||
if (nr_written > 0) {
|
||||
return nr_written;
|
||||
}
|
||||
|
||||
if (r < 0) {
|
||||
return -errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __libc_file_lock(struct __opaque_file *f)
|
||||
{
|
||||
kern_futex_t expected = 0;
|
||||
while (1) {
|
||||
kern_tracef("lock=%u (%p)", f->f_lock, f);
|
||||
expected = 0;
|
||||
if (__atomic_compare_exchange_n(
|
||||
&f->f_lock,
|
||||
&expected,
|
||||
1,
|
||||
0,
|
||||
__ATOMIC_ACQUIRE,
|
||||
__ATOMIC_ACQUIRE)) {
|
||||
kern_tracef("locked=%u (%p)", f->f_lock, f);
|
||||
return;
|
||||
}
|
||||
|
||||
kern_tracef("wait=%u (%p)", expected, f);
|
||||
futex_wait(&f->f_lock, 1, FUTEX_PRIVATE);
|
||||
}
|
||||
}
|
||||
|
||||
void __libc_file_unlock(struct __opaque_file *f)
|
||||
{
|
||||
f->f_lock = 0;
|
||||
futex_wake(&f->f_lock, 1, FUTEX_PRIVATE);
|
||||
kern_tracef("unlocked=%u (%p)", f->f_lock, f);
|
||||
}
|
||||
|
||||
static long read_buf(struct __opaque_file *f, void *out, size_t count)
|
||||
{
|
||||
kern_tracef("read_buf(%p, %zu)", out, count);
|
||||
char *dest = out;
|
||||
size_t nr_read = 0;
|
||||
long r = 0;
|
||||
|
||||
while (nr_read < count) {
|
||||
r = __libc_ringbuf_read(
|
||||
&f->f_buf,
|
||||
dest + nr_read,
|
||||
count - nr_read);
|
||||
if (r < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
nr_read += r;
|
||||
|
||||
if (r == 0) {
|
||||
r = refill_buf(f);
|
||||
if (r <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long r = read(f->f_fd, f->f_buf, f->f_buf_max);
|
||||
if (r < 0) {
|
||||
return -r;
|
||||
return r;
|
||||
}
|
||||
|
||||
f->f_buf_datalen = r;
|
||||
return SUCCESS;
|
||||
return nr_read;
|
||||
}
|
||||
|
||||
int __libc_file_read(struct __opaque_file *f, void *out, size_t count)
|
||||
static long read_nobuf(struct __opaque_file *f, void *out, size_t count)
|
||||
{
|
||||
if (f->f_fd < 0) {
|
||||
return EBADF;
|
||||
}
|
||||
|
||||
if (f->f_flags & FILE_ERR) {
|
||||
return EIO;
|
||||
}
|
||||
|
||||
if (f->f_flags & FILE_EOF) {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
size_t available = f->f_buf_datalen - f->f_buf_readptr;
|
||||
if (count > available) {
|
||||
count = available;
|
||||
}
|
||||
|
||||
memcpy(out, f->f_buf + f->f_buf_readptr, count);
|
||||
|
||||
f->f_buf_readptr += count;
|
||||
|
||||
return count;
|
||||
kern_tracef("read_nobuf");
|
||||
return read(f->f_fd, out, count);
|
||||
}
|
||||
|
||||
size_t __libc_file_available(struct __opaque_file *f)
|
||||
long __libc_file_read(struct __opaque_file *f, void *out, size_t count)
|
||||
{
|
||||
return f->f_buf_datalen - f->f_buf_readptr;
|
||||
long ret = 0;
|
||||
if (f->f_prev != OP_READ) {
|
||||
ret = flush_buf(f);
|
||||
clear_buf(f);
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
return __set_errno(ret);
|
||||
}
|
||||
|
||||
switch (f->f_buffer_mode) {
|
||||
case _IOFBF:
|
||||
case _IOLBF:
|
||||
ret = read_buf(f, out, count);
|
||||
break;
|
||||
case _IONBF:
|
||||
ret = read_nobuf(f, out, count);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
f->f_prev = OP_READ;
|
||||
|
||||
if (ret < 0) {
|
||||
f->f_flags |= FILE_ERR;
|
||||
return __set_errno(-ret);
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
f->f_flags |= FILE_EOF;
|
||||
}
|
||||
|
||||
f->f_seek += ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long write_fullbuf(struct __opaque_file *f, const void *p, size_t count)
|
||||
{
|
||||
const char *src = p;
|
||||
size_t nr_written = 0;
|
||||
long r = 0;
|
||||
|
||||
while (nr_written < count) {
|
||||
r = __libc_ringbuf_write(
|
||||
&f->f_buf,
|
||||
src + nr_written,
|
||||
count - nr_written);
|
||||
if (r < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
nr_written += r;
|
||||
|
||||
if (r == 0) {
|
||||
r = flush_buf(f);
|
||||
if (r <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (r < 0) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return nr_written;
|
||||
}
|
||||
|
||||
static long write_linebuf(
|
||||
struct __opaque_file *f,
|
||||
const void *out,
|
||||
size_t count)
|
||||
{
|
||||
const char *s = out;
|
||||
size_t nr_written = 0;
|
||||
long r = 0;
|
||||
|
||||
while (nr_written < count) {
|
||||
char c = s[nr_written];
|
||||
|
||||
r = __libc_ringbuf_write(&f->f_buf, &c, 1);
|
||||
if (r < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
nr_written += r;
|
||||
|
||||
if (r == 0 || c == '\n') {
|
||||
r = flush_buf(f);
|
||||
if (r < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (r < 0) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return nr_written;
|
||||
}
|
||||
|
||||
static long write_nobuf(struct __opaque_file *f, const void *out, size_t count)
|
||||
{
|
||||
return write(f->f_fd, out, count);
|
||||
}
|
||||
|
||||
long __libc_file_write(struct __opaque_file *f, const void *buf, size_t count)
|
||||
{
|
||||
if (f->f_lock != 1) {
|
||||
kern_tracef("file is not locked!!!");
|
||||
}
|
||||
long ret = 0;
|
||||
if (f->f_prev != OP_WRITE) {
|
||||
ret = clear_buf(f);
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
return __set_errno(ret);
|
||||
}
|
||||
|
||||
switch (f->f_buffer_mode) {
|
||||
case _IOFBF:
|
||||
ret = write_fullbuf(f, buf, count);
|
||||
break;
|
||||
case _IOLBF:
|
||||
ret = write_linebuf(f, buf, count);
|
||||
break;
|
||||
case _IONBF:
|
||||
ret = write_nobuf(f, buf, count);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
f->f_prev = OP_WRITE;
|
||||
|
||||
if (ret < 0) {
|
||||
f->f_flags |= FILE_ERR;
|
||||
return __set_errno(-ret);
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
f->f_flags |= FILE_EOF;
|
||||
}
|
||||
|
||||
f->f_seek += ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user