diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/nvim/file.c | 139 | ||||
| -rw-r--r-- | src/nvim/file.h | 4 | ||||
| -rw-r--r-- | src/nvim/os/fs.c | 73 | ||||
| -rw-r--r-- | src/nvim/rbuffer.c | 2 | ||||
| -rw-r--r-- | src/nvim/shada.c | 40 | 
5 files changed, 225 insertions, 33 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( diff --git a/src/nvim/file.h b/src/nvim/file.h index 0aa98e0def..c6def673c2 100644 --- a/src/nvim/file.h +++ b/src/nvim/file.h @@ -6,10 +6,14 @@  #include <fcntl.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; diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 1fd3987b97..19d4cbc440 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -7,6 +7,12 @@  #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" @@ -417,6 +423,73 @@ ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf,    return (ptrdiff_t) read_bytes;  } +#ifdef HAVE_READV +/// Read from a file to a 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. 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 a8efaeb7cf..130d8bbfae 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -715,11 +715,6 @@ static void close_sd_reader(ShaDaReadDef *const sd_reader)  static void close_sd_writer(ShaDaWriteDef *const sd_writer)    FUNC_ATTR_NONNULL_ALL  { -  const int error = file_fsync(sd_writer->cookie); -  if (error < 0) { -    emsgf(_(SERR "System error while synchronizing ShaDa file: %s"), -          os_strerror(error)); -  }    close_file(sd_writer->cookie);  } @@ -2995,15 +2990,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. @@ -3016,13 +3007,11 @@ shada_write_file_nomerge: {}              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); -            sd_writer.close(&sd_writer);              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) @@ -3030,13 +3019,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!"), @@ -3063,6 +3048,7 @@ shada_write_file_did_not_remove:      }      xfree(tempname);    } +  sd_writer.close(&sd_writer);    xfree(fname);    return OK; @@ -3192,20 +3178,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;  } | 
