aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2016-06-24 09:18:26 -0400
committerGitHub <noreply@github.com>2016-06-24 09:18:26 -0400
commit47a15d0256170e9ce33cda23d8f0b39451fa7129 (patch)
treec18ff2bd259ba0bc25f397d1e48853f05dd43c18 /src
parent65af001f2bcc35f19d64b4d2c1dbcd46d87432e8 (diff)
parent4741c8b234eeaeac839b689d6316528ecc78a934 (diff)
downloadrneovim-47a15d0256170e9ce33cda23d8f0b39451fa7129.tar.gz
rneovim-47a15d0256170e9ce33cda23d8f0b39451fa7129.tar.bz2
rneovim-47a15d0256170e9ce33cda23d8f0b39451fa7129.zip
Merge #4865 from ZyX-I/file-buffered-read
Use buffered reading/writing for ShaDa files
Diffstat (limited to 'src')
-rw-r--r--src/nvim/os/fileio.c319
-rw-r--r--src/nvim/os/fileio.h72
-rw-r--r--src/nvim/os/fs.c286
-rw-r--r--src/nvim/rbuffer.c2
-rw-r--r--src/nvim/shada.c272
5 files changed, 722 insertions, 229 deletions
diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c
new file mode 100644
index 0000000000..6cee102305
--- /dev/null
+++ b/src/nvim/os/fileio.c
@@ -0,0 +1,319 @@
+/// @file fileio.c
+///
+/// Buffered reading/writing to a file. Unlike fileio.c this is not dealing with
+/// Neovim stuctures for buffer, with autocommands, etc: just fopen/fread/fwrite
+/// replacement.
+
+#include <unistd.h>
+#include <assert.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <fcntl.h>
+
+#include "auto/config.h"
+
+#ifdef HAVE_SYS_UIO_H
+# include <sys/uio.h>
+#endif
+
+#include <uv.h>
+
+#include "nvim/os/fileio.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 "os/fileio.c.generated.h"
+#endif
+
+/// 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. 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\*).
+///
+/// @return Error code (@see os_strerror()) or 0.
+int file_open(FileDescriptor *const ret_fp, const char *const fname,
+ const int flags, const int mode)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ int os_open_flags = 0;
+ int fd;
+ TriState wr = kNone;
+#define FLAG(flags, flag, fcntl_flags, wrval, cond) \
+ do { \
+ if (flags & flag) { \
+ os_open_flags |= fcntl_flags; \
+ assert(cond); \
+ if (wrval != kNone) { \
+ wr = wrval; \
+ } \
+ } \
+ } while (0)
+ FLAG(flags, kFileWriteOnly, O_WRONLY, kTrue, true);
+ FLAG(flags, kFileCreateOnly, O_CREAT|O_EXCL|O_WRONLY, kTrue, true);
+ FLAG(flags, kFileCreate, O_CREAT|O_WRONLY, kTrue, !(flags & kFileCreateOnly));
+ FLAG(flags, kFileTruncate, O_TRUNC|O_WRONLY, kTrue,
+ !(flags & kFileCreateOnly));
+ FLAG(flags, kFileReadOnly, O_RDONLY, kFalse, wr != kTrue);
+#ifdef O_NOFOLLOW
+ FLAG(flags, kFileNoSymlink, O_NOFOLLOW, kNone, true);
+#endif
+#undef FLAG
+
+ fd = os_open(fname, os_open_flags, mode);
+
+ if (fd < 0) {
+ return fd;
+ }
+
+ ret_fp->wr = (wr == kTrue);
+ ret_fp->fd = fd;
+ ret_fp->eof = false;
+ ret_fp->rv = rbuffer_new(kRWBufferSize);
+ 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;
+}
+
+/// Like file_open(), but allocate and return ret_fp
+///
+/// @param[out] error Error code, @see os_strerror(). Is set to zero on
+/// success.
+/// @param[in] fname File name to open.
+/// @param[in] flags Flags, @see FileOpenFlags.
+/// @param[in] mode Permissions for the newly created file (ignored if flags
+/// does not have FILE_CREATE\*).
+///
+/// @return [allocated] Opened file or NULL in case of error.
+FileDescriptor *file_open_new(int *const error, const char *const fname,
+ const int flags, const int mode)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ FileDescriptor *const fp = xmalloc(sizeof(*fp));
+ if ((*error = file_open(fp, fname, flags, mode)) != 0) {
+ xfree(fp);
+ return NULL;
+ }
+ return fp;
+}
+
+/// Close file and free its buffer
+///
+/// @param[in,out] fp File to close.
+///
+/// @return 0 or error code.
+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;
+ }
+ return error;
+}
+
+/// Close and free file obtained using file_open_new()
+///
+/// @param[in,out] fp File to close.
+///
+/// @return 0 or error code.
+int file_free(FileDescriptor *const fp) FUNC_ATTR_NONNULL_ALL
+{
+ const int ret = file_close(fp);
+ xfree(fp);
+ return ret;
+}
+
+/// Flush file modifications to disk
+///
+/// @param[in,out] fp File to work with.
+///
+/// @return 0 or error code.
+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[kRWBufferSize];
+
+/// 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);
+ if (rbuffer_size(rv) == 0) {
+ return;
+ }
+ const size_t read_bytes = rbuffer_read(rv, writebuf, kRWBufferSize);
+ 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.
+/// @param[out] ret_buf Buffer to read to. Must not be NULL.
+/// @param[in] size Number of bytes to read. Buffer must have at least ret_buf
+/// bytes.
+///
+/// @return error_code (< 0) or number of bytes read.
+ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,
+ const size_t size)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ 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);
+#ifdef HAVE_READV
+ // If there is readv() syscall, then take an opportunity to populate
+ // both target buffer and RBuffer at once, …
+ size_t write_count;
+ struct iovec iov[] = {
+ { .iov_base = buf, .iov_len = read_remaining },
+ { .iov_base = rbuffer_write_ptr(rv, &write_count),
+ .iov_len = kRWBufferSize },
+ };
+ assert(write_count == kRWBufferSize);
+ 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));
+ read_remaining = 0;
+ } else {
+ buf += (size_t)r_ret;
+ read_remaining -= (size_t)r_ret;
+ }
+ } else if (r_ret < 0) {
+ return r_ret;
+ }
+#else
+ if (read_remaining >= kRWBufferSize) {
+ // …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;
+ }
+ } else {
+ size_t write_count;
+ const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof,
+ rbuffer_write_ptr(rv, &write_count),
+ kRWBufferSize);
+ assert(write_count == kRWBufferSize);
+ if (r_ret > 0) {
+ rbuffer_produced(rv, (size_t)r_ret);
+ } else if (r_ret < 0) {
+ return r_ret;
+ }
+ }
+#endif
+ }
+ }
+ return (ptrdiff_t)(size - read_remaining);
+}
+
+/// Write to a file
+///
+/// @param[in] fd File descriptor to write to.
+/// @param[in] buf Data to write. May be NULL if size is zero.
+/// @param[in] size Amount of bytes to write.
+///
+/// @return Number of bytes written or libuv error code (< 0).
+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)
+{
+ 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[kRWBufferSize];
+
+/// Skip some bytes
+///
+/// This is like `fseek(fp, size, SEEK_CUR)`, but actual implementation simply
+/// reads to a buffer and discards the result.
+ptrdiff_t file_skip(FileDescriptor *const fp, const size_t size)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(!fp->wr);
+ size_t read_bytes = 0;
+ do {
+ const ptrdiff_t new_read_bytes = file_read(
+ fp, skipbuf, MIN(size - read_bytes, sizeof(skipbuf)));
+ if (new_read_bytes < 0) {
+ return new_read_bytes;
+ } else if (new_read_bytes == 0) {
+ break;
+ }
+ read_bytes += (size_t)new_read_bytes;
+ } while (read_bytes < size && !file_eof(fp));
+
+ return (ptrdiff_t)read_bytes;
+}
diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h
new file mode 100644
index 0000000000..2cffd5c851
--- /dev/null
+++ b/src/nvim/os/fileio.h
@@ -0,0 +1,72 @@
+#ifndef NVIM_OS_FILEIO_H
+#define NVIM_OS_FILEIO_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "nvim/func_attr.h"
+#include "nvim/rbuffer.h"
+
+/// Structure used to read from/write to file
+typedef struct {
+ int fd; ///< File descriptor.
+ int _error; ///< Error code for use with RBuffer callbacks or zero.
+ RBuffer *rv; ///< Read or write buffer.
+ bool wr; ///< True if file is in write mode.
+ bool eof; ///< True if end of file was encountered.
+} FileDescriptor;
+
+/// file_open() flags
+typedef enum {
+ kFileReadOnly = 1, ///< Open file read-only. Default.
+ kFileCreate = 2, ///< Create file if it does not exist yet.
+ ///< Implies kFileWriteOnly.
+ kFileWriteOnly = 4, ///< Open file for writing only.
+ ///< Cannot be used with kFileReadOnly.
+ kFileNoSymlink = 8, ///< Do not allow symbolic links.
+ kFileCreateOnly = 16, ///< Only create the file, failing if it already
+ ///< exists. Implies kFileWriteOnly. Cannot be used
+ ///< with kFileCreate.
+ kFileTruncate = 32, ///< Truncate the file if it exists.
+ ///< Implies kFileWriteOnly. Cannot be used with
+ ///< kFileCreateOnly.
+} FileOpenFlags;
+
+static inline bool file_eof(const FileDescriptor *const fp)
+ REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL;
+
+/// Check whether end of file was encountered
+///
+/// @param[in] fp File to check.
+///
+/// @return true if it was, false if it was not or read operation was never
+/// performed.
+static inline bool file_eof(const FileDescriptor *const fp)
+{
+ return fp->eof && rbuffer_size(fp->rv) == 0;
+}
+
+static inline int file_fd(const FileDescriptor *const fp)
+ REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL;
+
+/// Return the file descriptor associated with the FileDescriptor structure
+///
+/// @param[in] fp File to check.
+///
+/// @return File descriptor.
+static inline int file_fd(const FileDescriptor *const fp)
+{
+ return fp->fd;
+}
+
+enum {
+ /// Read or write buffer size
+ ///
+ /// Currently equal to (IOSIZE - 1), but they do not need to be connected.
+ kRWBufferSize = 1024
+};
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/fileio.h.generated.h"
+#endif
+#endif // NVIM_OS_FILEIO_H
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 08122828bd..49c42cb63d 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -1,14 +1,26 @@
// fs.c -- filesystem access
#include <stdbool.h>
-
+#include <stddef.h>
#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
#include <fcntl.h>
+#include <errno.h>
+
+#include "auto/config.h"
+
+#ifdef HAVE_SYS_UIO_H
+# include <sys/uio.h>
+#endif
+
+#include <uv.h>
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/ascii.h"
#include "nvim/memory.h"
#include "nvim/message.h"
+#include "nvim/assert.h"
#include "nvim/misc1.h"
#include "nvim/misc2.h"
#include "nvim/path.h"
@@ -18,6 +30,20 @@
# include "os/fs.c.generated.h"
#endif
+#define RUN_UV_FS_FUNC(ret, func, ...) \
+ do { \
+ bool did_try_to_free = false; \
+uv_call_start: {} \
+ uv_fs_t req; \
+ ret = func(&fs_loop, &req, __VA_ARGS__); \
+ uv_fs_req_cleanup(&req); \
+ if (ret == UV_ENOMEM && !did_try_to_free) { \
+ try_to_free_memory(); \
+ did_try_to_free = true; \
+ goto uv_call_start; \
+ } \
+ } while (0)
+
// Many fs functions from libuv return that value on success.
static const int kLibuvSuccess = 0;
static uv_loop_t fs_loop;
@@ -325,13 +351,190 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath)
int os_open(const char* path, int flags, int mode)
FUNC_ATTR_NONNULL_ALL
{
- uv_fs_t open_req;
- int r = uv_fs_open(&fs_loop, &open_req, path, flags, mode, NULL);
- uv_fs_req_cleanup(&open_req);
- // r is the same as open_req.result (except for OOM: then only r is set).
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_open, path, flags, mode, NULL);
+ return r;
+}
+
+/// Close a file
+///
+/// @return 0 or libuv error code on failure.
+int os_close(const int fd)
+{
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_close, fd, NULL);
return r;
}
+/// Read from a file
+///
+/// Handles EINTR and ENOMEM, but not other errors.
+///
+/// @param[in] fd File descriptor to read from.
+/// @param[out] ret_eof Is set to true if EOF was encountered, otherwise set
+/// to false. Initial value is ignored.
+/// @param[out] ret_buf Buffer to write to. May be NULL if size is zero.
+/// @param[in] size Amount of bytes to read.
+///
+/// @return Number of bytes read or libuv error code (< 0).
+ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf,
+ const size_t size)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ *ret_eof = false;
+ if (ret_buf == NULL) {
+ assert(size == 0);
+ return 0;
+ }
+ size_t read_bytes = 0;
+ bool did_try_to_free = false;
+ while (read_bytes != size) {
+ const ptrdiff_t cur_read_bytes = read(fd, ret_buf + read_bytes,
+ size - read_bytes);
+ if (cur_read_bytes > 0) {
+ read_bytes += (size_t)cur_read_bytes;
+ assert(read_bytes <= size);
+ }
+ if (cur_read_bytes < 0) {
+#ifdef HAVE_UV_TRANSLATE_SYS_ERROR
+ const int error = uv_translate_sys_error(errno);
+#else
+ const int error = -errno;
+ STATIC_ASSERT(-EINTR == UV_EINTR, "Need to translate error codes");
+ STATIC_ASSERT(-EAGAIN == UV_EAGAIN, "Need to translate error codes");
+ STATIC_ASSERT(-ENOMEM == UV_ENOMEM, "Need to translate error codes");
+#endif
+ errno = 0;
+ if (error == UV_EINTR || error == UV_EAGAIN) {
+ continue;
+ } else if (error == UV_ENOMEM && !did_try_to_free) {
+ try_to_free_memory();
+ did_try_to_free = true;
+ continue;
+ } else {
+ return (ptrdiff_t)error;
+ }
+ }
+ if (cur_read_bytes == 0) {
+ *ret_eof = true;
+ break;
+ }
+ }
+ return (ptrdiff_t)read_bytes;
+}
+
+#ifdef HAVE_READV
+/// Read from a file to multiple buffers at once
+///
+/// Wrapper for readv().
+///
+/// @param[in] fd File descriptor to read from.
+/// @param[out] ret_eof Is set to true if EOF was encountered, otherwise set
+/// to false. Initial value is ignored.
+/// @param[out] iov Description of buffers to write to. Note: this description
+/// may change, it is incorrect to use data it points to after
+/// os_readv().
+/// @param[in] iov_size Number of buffers in iov.
+ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size)
+ FUNC_ATTR_NONNULL_ALL
+{
+ *ret_eof = false;
+ size_t read_bytes = 0;
+ bool did_try_to_free = false;
+ size_t toread = 0;
+ for (size_t i = 0; i < iov_size; i++) {
+ // Overflow, trying to read too much data
+ assert(toread <= SIZE_MAX - iov[i].iov_len);
+ toread += iov[i].iov_len;
+ }
+ while (read_bytes < toread && iov_size && !*ret_eof) {
+ ptrdiff_t cur_read_bytes = readv(fd, iov, (int)iov_size);
+ if (toread && cur_read_bytes == 0) {
+ *ret_eof = true;
+ }
+ if (cur_read_bytes > 0) {
+ read_bytes += (size_t)cur_read_bytes;
+ while (iov_size && cur_read_bytes) {
+ if (cur_read_bytes < (ptrdiff_t)iov->iov_len) {
+ iov->iov_len -= (size_t)cur_read_bytes;
+ iov->iov_base = (char *)iov->iov_base + cur_read_bytes;
+ cur_read_bytes = 0;
+ } else {
+ cur_read_bytes -= (ptrdiff_t)iov->iov_len;
+ iov_size--;
+ iov++;
+ }
+ }
+ } else if (cur_read_bytes < 0) {
+#ifdef HAVE_UV_TRANSLATE_SYS_ERROR
+ const int error = uv_translate_sys_error(errno);
+#else
+ const int error = -errno;
+ STATIC_ASSERT(-EINTR == UV_EINTR, "Need to translate error codes");
+ STATIC_ASSERT(-EAGAIN == UV_EAGAIN, "Need to translate error codes");
+ STATIC_ASSERT(-ENOMEM == UV_ENOMEM, "Need to translate error codes");
+#endif
+ errno = 0;
+ if (error == UV_EINTR || error == UV_EAGAIN) {
+ continue;
+ } else if (error == UV_ENOMEM && !did_try_to_free) {
+ try_to_free_memory();
+ did_try_to_free = true;
+ continue;
+ } else {
+ return (ptrdiff_t)error;
+ }
+ }
+ }
+ return (ptrdiff_t)read_bytes;
+}
+#endif // HAVE_READV
+
+/// Write to a file
+///
+/// @param[in] fd File descriptor to write to.
+/// @param[in] buf Data to write. May be NULL if size is zero.
+/// @param[in] size Amount of bytes to write.
+///
+/// @return Number of bytes written or libuv error code (< 0).
+ptrdiff_t os_write(const int fd, const char *const buf, const size_t size)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (buf == NULL) {
+ assert(size == 0);
+ return 0;
+ }
+ size_t written_bytes = 0;
+ while (written_bytes != size) {
+ const ptrdiff_t cur_written_bytes = write(fd, buf + written_bytes,
+ size - written_bytes);
+ if (cur_written_bytes > 0) {
+ written_bytes += (size_t)cur_written_bytes;
+ }
+ if (cur_written_bytes < 0) {
+#ifdef HAVE_UV_TRANSLATE_SYS_ERROR
+ const int error = uv_translate_sys_error(errno);
+#else
+ const int error = -errno;
+ STATIC_ASSERT(-EINTR == UV_EINTR, "Need to translate error codes");
+ STATIC_ASSERT(-EAGAIN == UV_EAGAIN, "Need to translate error codes");
+ // According to the man page open() may fail with ENOMEM, but write()
+ // can’t.
+#endif
+ errno = 0;
+ if (error == UV_EINTR || error == UV_EAGAIN) {
+ continue;
+ } else {
+ return error;
+ }
+ }
+ if (cur_written_bytes == 0) {
+ return UV_UNKNOWN;
+ }
+ }
+ return (ptrdiff_t)written_bytes;
+}
+
/// Flushes file modifications to disk.
///
/// @param fd the file descriptor of the file to flush to disk.
@@ -339,9 +542,8 @@ int os_open(const char* path, int flags, int mode)
/// @return `0` on success, a libuv error code on failure.
int os_fsync(int fd)
{
- uv_fs_t fsync_req;
- int r = uv_fs_fsync(&fs_loop, &fsync_req, fd, NULL);
- uv_fs_req_cleanup(&fsync_req);
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_fsync, fd, NULL);
return r;
}
@@ -379,16 +581,9 @@ int32_t os_getperm(const char_u *name)
int os_setperm(const char_u *name, int perm)
FUNC_ATTR_NONNULL_ALL
{
- uv_fs_t request;
- int result = uv_fs_chmod(&fs_loop, &request,
- (const char*)name, perm, NULL);
- uv_fs_req_cleanup(&request);
-
- if (result == kLibuvSuccess) {
- return OK;
- }
-
- return FAIL;
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_chmod, (const char *)name, perm, NULL);
+ return (r == kLibuvSuccess ? OK : FAIL);
}
/// Changes the ownership of the file referred to by the open file descriptor.
@@ -397,13 +592,11 @@ int os_setperm(const char_u *name, int perm)
///
/// @note If the `owner` or `group` is specified as `-1`, then that ID is not
/// changed.
-int os_fchown(int file_descriptor, uv_uid_t owner, uv_gid_t group)
+int os_fchown(int fd, uv_uid_t owner, uv_gid_t group)
{
- uv_fs_t request;
- int result = uv_fs_fchown(&fs_loop, &request, file_descriptor,
- owner, group, NULL);
- uv_fs_req_cleanup(&request);
- return result;
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_fchown, fd, owner, group, NULL);
+ return r;
}
/// Check if a file exists.
@@ -422,9 +615,8 @@ bool os_file_exists(const char_u *name)
bool os_file_is_readable(const char *name)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- uv_fs_t req;
- int r = uv_fs_access(&fs_loop, &req, name, R_OK, NULL);
- uv_fs_req_cleanup(&req);
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_access, name, R_OK, NULL);
return (r == 0);
}
@@ -436,9 +628,8 @@ bool os_file_is_readable(const char *name)
int os_file_is_writable(const char *name)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- uv_fs_t req;
- int r = uv_fs_access(&fs_loop, &req, name, W_OK, NULL);
- uv_fs_req_cleanup(&req);
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_access, name, W_OK, NULL);
if (r == 0) {
return os_isdir((char_u *)name) ? 2 : 1;
}
@@ -451,16 +642,10 @@ int os_file_is_writable(const char *name)
int os_rename(const char_u *path, const char_u *new_path)
FUNC_ATTR_NONNULL_ALL
{
- uv_fs_t request;
- int result = uv_fs_rename(&fs_loop, &request,
- (const char *)path, (const char *)new_path, NULL);
- uv_fs_req_cleanup(&request);
-
- if (result == kLibuvSuccess) {
- return OK;
- }
-
- return FAIL;
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_rename, (const char *)path, (const char *)new_path,
+ NULL);
+ return (r == kLibuvSuccess ? OK : FAIL);
}
/// Make a directory.
@@ -469,10 +654,9 @@ int os_rename(const char_u *path, const char_u *new_path)
int os_mkdir(const char *path, int32_t mode)
FUNC_ATTR_NONNULL_ALL
{
- uv_fs_t request;
- int result = uv_fs_mkdir(&fs_loop, &request, path, mode, NULL);
- uv_fs_req_cleanup(&request);
- return result;
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_mkdir, path, mode, NULL);
+ return r;
}
/// Make a directory, with higher levels when needed
@@ -554,10 +738,9 @@ int os_mkdtemp(const char *template, char *path)
int os_rmdir(const char *path)
FUNC_ATTR_NONNULL_ALL
{
- uv_fs_t request;
- int result = uv_fs_rmdir(&fs_loop, &request, path, NULL);
- uv_fs_req_cleanup(&request);
- return result;
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_rmdir, path, NULL);
+ return r;
}
/// Opens a directory.
@@ -599,10 +782,9 @@ void os_closedir(Directory *dir)
int os_remove(const char *path)
FUNC_ATTR_NONNULL_ALL
{
- uv_fs_t request;
- int result = uv_fs_unlink(&fs_loop, &request, path, NULL);
- uv_fs_req_cleanup(&request);
- return result;
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_unlink, path, NULL);
+ return r;
}
/// Get the file information for a given path
diff --git a/src/nvim/rbuffer.c b/src/nvim/rbuffer.c
index b3805a3a28..d4cc8c0d5d 100644
--- a/src/nvim/rbuffer.c
+++ b/src/nvim/rbuffer.c
@@ -153,7 +153,7 @@ void rbuffer_consumed(RBuffer *buf, size_t count)
// Higher level functions for copying from/to RBuffer instances and data
// pointers
-size_t rbuffer_write(RBuffer *buf, char *src, size_t src_size)
+size_t rbuffer_write(RBuffer *buf, const char *src, size_t src_size)
FUNC_ATTR_NONNULL_ALL
{
size_t size = src_size;
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 380d955f63..b5921eb810 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -5,7 +5,6 @@
#include <stdint.h>
#include <inttypes.h>
#include <errno.h>
-#include <fcntl.h>
#include <assert.h>
#include <msgpack.h>
@@ -36,6 +35,7 @@
#include "nvim/version.h"
#include "nvim/path.h"
#include "nvim/fileio.h"
+#include "nvim/os/fileio.h"
#include "nvim/strings.h"
#include "nvim/quickfix.h"
#include "nvim/eval/encode.h"
@@ -409,7 +409,7 @@ typedef struct sd_read_def {
ShaDaFileSkipper skip; ///< Function used to skip some bytes.
void *cookie; ///< Data describing object read from.
bool eof; ///< True if reader reached end of file.
- char *error; ///< Error message in case of error.
+ const char *error; ///< Error message in case of error.
uintmax_t fpos; ///< Current position (amount of bytes read since
///< reader structure initialization). May overflow.
vimconv_T sd_conv; ///< Structure used for converting encodings of some
@@ -433,7 +433,7 @@ typedef struct sd_write_def {
ShaDaFileWriter write; ///< Writer function.
ShaDaWriteCloser close; ///< Close function.
void *cookie; ///< Data describing object written to.
- char *error; ///< Error message in case of error.
+ const char *error; ///< Error message in case of error.
vimconv_T sd_conv; ///< Structure used for converting encodings of some
///< items.
} ShaDaWriteDef;
@@ -666,38 +666,14 @@ static ptrdiff_t read_file(ShaDaReadDef *const sd_reader, void *const dest,
const size_t size)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- size_t read_bytes = 0;
- bool did_try_to_free = false;
- const int fd = (int)(intptr_t) sd_reader->cookie;
- while (read_bytes != size) {
- const ptrdiff_t cur_read_bytes = read(fd, ((char *) dest) + read_bytes,
- size - read_bytes);
- if (cur_read_bytes > 0) {
- read_bytes += (size_t) cur_read_bytes;
- sd_reader->fpos += (uintmax_t) cur_read_bytes;
- assert(read_bytes <= size);
- }
- if (cur_read_bytes < 0) {
- if (errno == EINTR || errno == EAGAIN) {
- errno = 0;
- continue;
- } else if (errno == ENOMEM && !did_try_to_free) {
- try_to_free_memory();
- did_try_to_free = true;
- errno = 0;
- continue;
- } else {
- sd_reader->error = strerror(errno);
- errno = 0;
- return -1;
- }
- }
- if (cur_read_bytes == 0) {
- sd_reader->eof = true;
- break;
- }
+ const ptrdiff_t ret = file_read(sd_reader->cookie, dest, size);
+ sd_reader->eof = file_eof(sd_reader->cookie);
+ if (ret < 0) {
+ sd_reader->error = os_strerror((int)ret);
+ return -1;
}
- return (ptrdiff_t) read_bytes;
+ sd_reader->fpos += (size_t)ret;
+ return ret;
}
/// Read one character
@@ -720,50 +696,26 @@ static ptrdiff_t write_file(ShaDaWriteDef *const sd_writer,
const size_t size)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- size_t written_bytes = 0;
- const int fd = (int)(intptr_t) sd_writer->cookie;
- while (written_bytes != size) {
- const ptrdiff_t cur_written_bytes = write(fd, (char *) dest + written_bytes,
- size - written_bytes);
- if (cur_written_bytes > 0) {
- written_bytes += (size_t) cur_written_bytes;
- }
- if (cur_written_bytes < 0) {
- if (errno == EINTR || errno == EAGAIN) {
- errno = 0;
- continue;
- } else {
- sd_writer->error = strerror(errno);
- errno = 0;
- return -1;
- }
- }
- if (cur_written_bytes == 0) {
- sd_writer->error = "Zero bytes written.";
- return -1;
- }
+ const ptrdiff_t ret = file_write(sd_writer->cookie, dest, size);
+ if (ret < 0) {
+ sd_writer->error = os_strerror((int)ret);
+ return -1;
}
- return (ptrdiff_t) written_bytes;
+ return ret;
}
/// Wrapper for closing file descriptors opened for reading
static void close_sd_reader(ShaDaReadDef *const sd_reader)
FUNC_ATTR_NONNULL_ALL
{
- close_file((int)(intptr_t) sd_reader->cookie);
+ close_file(sd_reader->cookie);
}
/// Wrapper for closing file descriptors opened for writing
static void close_sd_writer(ShaDaWriteDef *const sd_writer)
FUNC_ATTR_NONNULL_ALL
{
- const int fd = (int)(intptr_t) sd_writer->cookie;
- if (os_fsync(fd) < 0) {
- emsgf(_(SERR "System error while synchronizing ShaDa file: %s"),
- os_strerror(errno));
- errno = 0;
- }
- close_file(fd);
+ close_file(sd_writer->cookie);
}
/// Wrapper for read that reads to IObuff and ignores bytes read
@@ -779,19 +731,20 @@ static int sd_reader_skip_read(ShaDaReadDef *const sd_reader,
const size_t offset)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- size_t read_bytes = 0;
- do {
- ptrdiff_t new_read_bytes = sd_reader->read(
- sd_reader, IObuff, (size_t) (offset - read_bytes > IOSIZE
- ? IOSIZE
- : offset - read_bytes));
- if (new_read_bytes == -1) {
- return FAIL;
+ const ptrdiff_t skip_bytes = file_skip(sd_reader->cookie, offset);
+ if (skip_bytes < 0) {
+ sd_reader->error = os_strerror((int)skip_bytes);
+ return FAIL;
+ } else if (skip_bytes != (ptrdiff_t)offset) {
+ assert(skip_bytes < (ptrdiff_t)offset);
+ sd_reader->eof = file_eof(sd_reader->cookie);
+ if (!sd_reader->eof) {
+ sd_reader->error = _("too few bytes read");
}
- read_bytes += (size_t) new_read_bytes;
- } while (read_bytes < offset && !sd_reader->eof);
-
- return (read_bytes == offset ? OK : FAIL);
+ return FAIL;
+ }
+ sd_reader->fpos += (size_t)skip_bytes;
+ return OK;
}
/// Wrapper for read that can be used when lseek cannot be used
@@ -824,37 +777,6 @@ static ShaDaReadResult sd_reader_skip(ShaDaReadDef *const sd_reader,
return kSDReadStatusSuccess;
}
-/// Wrapper for opening file descriptors
-///
-/// All arguments are passed to os_open().
-///
-/// @return file descriptor or libuv error on failure.
-static int open_file(const char *const fname, const int flags, const int mode)
- FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
-{
- bool did_try_to_free = false;
- int fd;
-open_file_start:
- fd = os_open(fname, flags, mode);
-
- if (fd < 0) {
- if (fd == UV_ENOENT) {
- return fd;
- }
- if (fd == UV_ENOMEM && !did_try_to_free) {
- try_to_free_memory();
- did_try_to_free = true;
- goto open_file_start;
- }
- if (fd != UV_EEXIST) {
- emsgf(_(SERR "System error while opening ShaDa file %s: %s"),
- fname, os_strerror(fd));
- }
- return fd;
- }
- return fd;
-}
-
/// Open ShaDa file for reading
///
/// @param[in] fname File name to open.
@@ -865,11 +787,7 @@ static int open_shada_file_for_reading(const char *const fname,
ShaDaReadDef *sd_reader)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
- const intptr_t fd = (intptr_t) open_file(fname, O_RDONLY, 0);
-
- if (fd < 0) {
- return (int) fd;
- }
+ int error;
*sd_reader = (ShaDaReadDef) {
.read = &read_file,
@@ -878,8 +796,11 @@ static int open_shada_file_for_reading(const char *const fname,
.error = NULL,
.eof = false,
.fpos = 0,
- .cookie = (void *) fd,
+ .cookie = file_open_new(&error, fname, kFileReadOnly, 0),
};
+ if (sd_reader->cookie == NULL) {
+ return error;
+ }
convert_setup(&sd_reader->sd_conv, "utf-8", p_enc);
@@ -887,18 +808,12 @@ static int open_shada_file_for_reading(const char *const fname,
}
/// Wrapper for closing file descriptors
-static void close_file(int fd)
+static void close_file(void *cookie)
{
-close_file_start:
- if (close(fd) == -1) {
- if (errno == EINTR) {
- errno = 0;
- goto close_file_start;
- } else {
- emsgf(_(SERR "System error while closing ShaDa file: %s"),
- strerror(errno));
- errno = 0;
- }
+ const int error = file_free(cookie);
+ if (error != 0) {
+ emsgf(_(SERR "System error while closing ShaDa file: %s"),
+ os_strerror(error));
}
}
@@ -978,7 +893,7 @@ static int shada_read_file(const char *const file, const int flags)
}
if (of_ret != 0) {
- if (of_ret == UV_ENOENT && (flags & kShaDaMissingError)) {
+ if (of_ret != UV_ENOENT || (flags & kShaDaMissingError)) {
emsgf(_(SERR "System error while opening ShaDa file %s for reading: %s"),
fname, os_strerror(of_ret));
}
@@ -1421,7 +1336,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
}
}
if (!op_register_set(cur_entry.data.reg.name, (yankreg_T) {
- .y_array = (char_u **) cur_entry.data.reg.contents,
+ .y_array = (char_u **)cur_entry.data.reg.contents,
.y_size = cur_entry.data.reg.contents_size,
.y_type = cur_entry.data.reg.type,
.y_width = (colnr_T) cur_entry.data.reg.width,
@@ -2968,17 +2883,23 @@ int shada_write_file(const char *const file, bool nomerge)
char *const fname = shada_filename(file);
char *tempname = NULL;
- ShaDaWriteDef sd_writer = (ShaDaWriteDef) {
+ ShaDaWriteDef sd_writer = {
.write = &write_file,
.close = &close_sd_writer,
.error = NULL,
};
- ShaDaReadDef sd_reader;
-
- intptr_t fd;
+ ShaDaReadDef sd_reader = { .close = NULL };
if (!nomerge) {
- if (open_shada_file_for_reading(fname, &sd_reader) != 0) {
+ int error;
+ if ((error = open_shada_file_for_reading(fname, &sd_reader)) != 0) {
+ if (error != UV_ENOENT) {
+ emsgf(_(SERR "System error while opening ShaDa file %s for reading "
+ "to merge before writing it: %s"),
+ fname, os_strerror(error));
+ // Try writing the file even if opening it emerged any issues besides
+ // file not existing: maybe writing will succeed nevertheless.
+ }
nomerge = true;
goto shada_write_file_nomerge;
}
@@ -2996,15 +2917,11 @@ int shada_write_file(const char *const file, bool nomerge)
// 2: Make sure that user can always read and write the result.
// 3: If somebody happened to delete the file after it was opened for
// reading use u=rw permissions.
-shada_write_file_open:
- fd = (intptr_t) open_file(tempname, O_CREAT|O_WRONLY|O_NOFOLLOW|O_EXCL,
- perm);
- if (fd < 0) {
- if (fd == UV_EEXIST
-#ifdef ELOOP
- || fd == UV_ELOOP
-#endif
- ) {
+shada_write_file_open: {}
+ sd_writer.cookie = file_open_new(
+ &error, tempname, kFileCreateOnly|kFileNoSymlink, perm);
+ if (sd_writer.cookie == NULL) {
+ if (error == UV_EEXIST || error == UV_ELOOP) {
// File already exists, try another name
char *const wp = tempname + strlen(tempname) - 1;
if (*wp == 'z') {
@@ -3014,11 +2931,16 @@ shada_write_file_open:
fname);
xfree(fname);
xfree(tempname);
+ assert(sd_reader.close != NULL);
+ sd_reader.close(&sd_reader);
return FAIL;
} else {
(*wp)++;
goto shada_write_file_open;
}
+ } else {
+ emsgf(_(SERR "System error while opening temporary ShaDa file %s "
+ "for writing: %s"), tempname, os_strerror(error));
}
}
}
@@ -3042,23 +2964,29 @@ shada_write_file_nomerge: {}
}
*tail = tail_save;
}
- fd = (intptr_t) open_file(fname, O_CREAT|O_WRONLY|O_TRUNC,
- 0600);
- }
-
- if (p_verbose > 0) {
- verbose_enter();
- smsg(_("Writing ShaDa file \"%s\""), fname);
- verbose_leave();
+ int error;
+ sd_writer.cookie = file_open_new(&error, fname, kFileCreate|kFileTruncate,
+ 0600);
+ if (sd_writer.cookie == NULL) {
+ emsgf(_(SERR "System error while opening ShaDa file %s for writing: %s"),
+ fname, os_strerror(error));
+ }
}
- if (fd < 0) {
+ if (sd_writer.cookie == NULL) {
xfree(fname);
xfree(tempname);
+ if (sd_reader.close != NULL) {
+ sd_reader.close(&sd_reader);
+ }
return FAIL;
}
- sd_writer.cookie = (void *) fd;
+ if (p_verbose > 0) {
+ verbose_enter();
+ smsg(_("Writing ShaDa file \"%s\""), fname);
+ verbose_leave();
+ }
convert_setup(&sd_writer.sd_conv, p_enc, "utf-8");
@@ -3066,15 +2994,11 @@ shada_write_file_nomerge: {}
? NULL
: &sd_reader));
assert(sw_ret != kSDWriteIgnError);
-#ifndef UNIX
- sd_writer.close(&sd_writer);
-#endif
if (!nomerge) {
sd_reader.close(&sd_reader);
bool did_remove = false;
if (sw_ret == kSDWriteSuccessfull) {
#ifdef UNIX
- bool closed = false;
// For Unix we check the owner of the file. It's not very nice to
// overwrite a user’s viminfo file after a "su root", with a
// viminfo file that the user can't read.
@@ -3083,16 +3007,15 @@ shada_write_file_nomerge: {}
if (getuid() == ROOT_UID) {
if (old_info.stat.st_uid != ROOT_UID
|| old_info.stat.st_gid != getgid()) {
- const uv_uid_t old_uid = (uv_uid_t) old_info.stat.st_uid;
- const uv_gid_t old_gid = (uv_gid_t) old_info.stat.st_gid;
- const int fchown_ret = os_fchown((int) fd, old_uid, old_gid);
- sd_writer.close(&sd_writer);
+ const uv_uid_t old_uid = (uv_uid_t)old_info.stat.st_uid;
+ const uv_gid_t old_gid = (uv_gid_t)old_info.stat.st_gid;
+ const int fchown_ret = os_fchown(file_fd(sd_writer.cookie),
+ old_uid, old_gid);
if (fchown_ret != 0) {
EMSG3(_(RNERR "Failed setting uid and gid for file %s: %s"),
tempname, os_strerror(fchown_ret));
goto shada_write_file_did_not_remove;
}
- closed = true;
}
} else if (!(old_info.stat.st_uid == getuid()
? (old_info.stat.st_mode & 0200)
@@ -3100,13 +3023,9 @@ shada_write_file_nomerge: {}
? (old_info.stat.st_mode & 0020)
: (old_info.stat.st_mode & 0002)))) {
EMSG2(_("E137: ShaDa file is not writable: %s"), fname);
- sd_writer.close(&sd_writer);
goto shada_write_file_did_not_remove;
}
}
- if (!closed) {
- sd_writer.close(&sd_writer);
- }
#endif
if (vim_rename(tempname, fname) == -1) {
EMSG3(_(RNERR "Can't rename ShaDa file from %s to %s!"),
@@ -3133,6 +3052,7 @@ shada_write_file_did_not_remove:
}
xfree(tempname);
}
+ sd_writer.close(&sd_writer);
xfree(fname);
return OK;
@@ -3262,20 +3182,20 @@ static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader,
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
const ptrdiff_t read_bytes = sd_reader->read(sd_reader, buffer, length);
- (void) read_bytes;
- if (sd_reader->error != NULL) {
- emsgf(_(SERR "System error while reading ShaDa file: %s"),
- sd_reader->error);
- return kSDReadStatusReadError;
- } else if (sd_reader->eof) {
- emsgf(_(RCERR "Error while reading ShaDa file: "
- "last entry specified that it occupies %" PRIu64 " bytes, "
- "but file ended earlier"),
- (uint64_t) length);
- return kSDReadStatusNotShaDa;
+ if (read_bytes != (ptrdiff_t)length) {
+ if (sd_reader->error != NULL) {
+ emsgf(_(SERR "System error while reading ShaDa file: %s"),
+ sd_reader->error);
+ return kSDReadStatusReadError;
+ } else {
+ emsgf(_(RCERR "Error while reading ShaDa file: "
+ "last entry specified that it occupies %" PRIu64 " bytes, "
+ "but file ended earlier"),
+ (uint64_t)length);
+ return kSDReadStatusNotShaDa;
+ }
}
- assert(read_bytes >= 0 && (size_t) read_bytes == length);
return kSDReadStatusSuccess;
}