aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/file.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/file.c')
-rw-r--r--src/nvim/file.c139
1 files changed, 134 insertions, 5 deletions
diff --git a/src/nvim/file.c b/src/nvim/file.c
index bc230ecf00..262343fe29 100644
--- a/src/nvim/file.c
+++ b/src/nvim/file.c
@@ -5,26 +5,39 @@
/// replacement.
#include <unistd.h>
+#include <assert.h>
#include <stddef.h>
#include <stdbool.h>
+#include "auto/config.h"
+
+#ifdef HAVE_SYS_UIO_H
+# include <sys/uio.h>
+#endif
+
#include <uv.h>
#include "nvim/file.h"
#include "nvim/memory.h"
#include "nvim/os/os.h"
#include "nvim/globals.h"
+#include "nvim/rbuffer.h"
+#include "nvim/macros.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "file.c.generated.h"
#endif
+#define RWBUFSIZE (IOSIZE - 1)
+
/// Open file
///
/// @param[out] ret_fp Address where information needed for reading from or
/// writing to a file is saved
/// @param[in] fname File name to open.
-/// @param[in] flags Flags, @see FileOpenFlags.
+/// @param[in] flags Flags, @see FileOpenFlags. Currently reading from and
+/// writing to the file at once is not supported, so either
+/// FILE_WRITE_ONLY or FILE_READ_ONLY is required.
/// @param[in] mode Permissions for the newly created file (ignored if flags
/// does not have FILE_CREATE\*).
///
@@ -41,8 +54,15 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname,
return fd;
}
+ ret_fp->wr = (bool)(!!(flags & FILE_WRITE_ONLY));
ret_fp->fd = fd;
ret_fp->eof = false;
+ ret_fp->rv = rbuffer_new(RWBUFSIZE);
+ ret_fp->_error = 0;
+ if (ret_fp->wr) {
+ ret_fp->rv->data = ret_fp;
+ ret_fp->rv->full_cb = (rbuffer_callback)&file_rb_write_full_cb;
+ }
return 0;
}
@@ -68,7 +88,7 @@ FileDescriptor *file_open_new(int *const error, const char *const fname,
return fp;
}
-/// Close file
+/// Close file and free its buffer
///
/// @param[in,out] fp File to close.
///
@@ -77,6 +97,7 @@ int file_close(FileDescriptor *const fp) FUNC_ATTR_NONNULL_ALL
{
const int error = file_fsync(fp);
const int error2 = os_close(fp->fd);
+ rbuffer_free(fp->rv);
if (error2 != 0) {
return error2;
}
@@ -103,9 +124,45 @@ int file_free(FileDescriptor *const fp) FUNC_ATTR_NONNULL_ALL
int file_fsync(FileDescriptor *const fp)
FUNC_ATTR_NONNULL_ALL
{
+ if (!fp->wr) {
+ return 0;
+ }
+ file_rb_write_full_cb(fp->rv, fp);
+ if (fp->_error != 0) {
+ const int error = fp->_error;
+ fp->_error = 0;
+ return error;
+ }
return os_fsync(fp->fd);
}
+/// Buffer used for writing
+///
+/// Like IObuff, but allows file_\* callers not to care about spoiling it.
+static char writebuf[RWBUFSIZE];
+
+/// Function run when RBuffer is full when writing to a file
+///
+/// Actually does writing to the file, may also be invoked directly.
+///
+/// @param[in,out] rv RBuffer instance used.
+/// @param[in,out] fp File to work with.
+static void file_rb_write_full_cb(RBuffer *const rv, FileDescriptor *const fp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(fp->wr);
+ assert(rv->data == (void *)fp);
+ const size_t read_bytes = rbuffer_read(rv, writebuf, RWBUFSIZE);
+ const ptrdiff_t wres = os_write(fp->fd, writebuf, read_bytes);
+ if (wres != (ptrdiff_t)read_bytes) {
+ if (wres >= 0) {
+ fp->_error = UV_EIO;
+ } else {
+ fp->_error = (int)wres;
+ }
+ }
+}
+
/// Read from file
///
/// @param[in,out] fp File to work with.
@@ -118,7 +175,69 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,
const size_t size)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- return os_read(fp->fd, &fp->eof, ret_buf, size);
+ assert(!fp->wr);
+ char *buf = ret_buf;
+ size_t read_remaining = size;
+ RBuffer *const rv = fp->rv;
+ while (read_remaining) {
+ const size_t rv_size = rbuffer_size(rv);
+ if (rv_size > 0) {
+ const size_t rsize = rbuffer_read(rv, buf, MIN(rv_size, read_remaining));
+ buf += rsize;
+ read_remaining -= rsize;
+ }
+ if (fp->eof) {
+ break;
+ }
+ if (read_remaining) {
+ assert(rbuffer_size(rv) == 0);
+ rbuffer_reset(rv);
+ if (read_remaining >= RWBUFSIZE) {
+#ifdef HAVE_READV
+ // If there is readv() syscall, then take an opportunity to populate
+ // both target buffer and RBuffer at once, …
+ size_t read_count;
+ struct iovec iov[] = {
+ { .iov_base = buf, .iov_len = read_remaining },
+ { .iov_base = rbuffer_read_ptr(rv, &read_count), .iov_len = RWBUFSIZE
+ },
+ };
+ assert(read_count == RWBUFSIZE);
+ const ptrdiff_t r_ret = os_readv(fp->fd, &fp->eof, iov,
+ ARRAY_SIZE(iov));
+ if (r_ret > 0) {
+ if (r_ret > (ptrdiff_t)read_remaining) {
+ rbuffer_produced(rv, (size_t)(r_ret - (ptrdiff_t)read_remaining));
+ }
+ } else if (r_ret < 0) {
+ return r_ret;
+ }
+#else
+ // …otherwise leave RBuffer empty and populate only target buffer,
+ // because filtering information through rbuffer will be more syscalls.
+ const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, buf, read_remaining);
+ if (r_ret >= 0) {
+ read_remaining -= (size_t)r_ret;
+ return (ptrdiff_t)(size - read_remaining);
+ } else if (r_ret < 0) {
+ return r_ret;
+ }
+#endif
+ } else {
+ size_t write_count;
+ const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof,
+ rbuffer_write_ptr(rv, &write_count),
+ RWBUFSIZE);
+ assert(write_count == RWBUFSIZE);
+ if (r_ret > 0) {
+ rbuffer_produced(rv, (size_t)r_ret);
+ } else if (r_ret < 0) {
+ return r_ret;
+ }
+ }
+ }
+ }
+ return (ptrdiff_t)(size - read_remaining);
}
/// Write to a file
@@ -132,12 +251,21 @@ ptrdiff_t file_write(FileDescriptor *const fp, const char *const buf,
const size_t size)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
{
- return os_write(fp->fd, buf, size);
+ assert(fp->wr);
+ const size_t written = rbuffer_write(fp->rv, buf, size);
+ if (fp->_error != 0) {
+ const int error = fp->_error;
+ fp->_error = 0;
+ return error;
+ } else if (written != size) {
+ return UV_EIO;
+ }
+ return (ptrdiff_t)written;
}
/// Buffer used for skipping. Its contents is undefined and should never be
/// used.
-static char skipbuf[IOSIZE];
+static char skipbuf[RWBUFSIZE];
/// Skip some bytes
///
@@ -146,6 +274,7 @@ static char skipbuf[IOSIZE];
ptrdiff_t file_skip(FileDescriptor *const fp, const size_t size)
FUNC_ATTR_NONNULL_ALL
{
+ assert(!fp->wr);
size_t read_bytes = 0;
do {
ptrdiff_t new_read_bytes = file_read(