diff options
Diffstat (limited to 'src/nvim/file.c')
-rw-r--r-- | src/nvim/file.c | 139 |
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( |