diff options
| -rw-r--r-- | config/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | config/config.h.in | 1 | ||||
| -rw-r--r-- | src/nvim/file.c | 164 | ||||
| -rw-r--r-- | src/nvim/file.h | 59 | ||||
| -rw-r--r-- | src/nvim/os/fs.c | 213 | ||||
| -rw-r--r-- | src/nvim/shada.c | 224 | 
6 files changed, 463 insertions, 199 deletions
| diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index e794a8c5b9..e1e90c6a9d 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -33,6 +33,7 @@ check_function_exists(fseeko HAVE_FSEEKO)  check_function_exists(getpwent HAVE_GETPWENT)  check_function_exists(getpwnam HAVE_GETPWNAM)  check_function_exists(getpwuid HAVE_GETPWUID) +check_function_exists(uv_translate_sys_error HAVE_UV_TRANSLATE_SYS_ERROR)  if(Iconv_FOUND)    set(HAVE_ICONV 1) diff --git a/config/config.h.in b/config/config.h.in index 867278de0d..7f16fd1928 100644 --- a/config/config.h.in +++ b/config/config.h.in @@ -30,6 +30,7 @@  #cmakedefine HAVE_PUTENV_S  #cmakedefine HAVE_PWD_H  #cmakedefine HAVE_READLINK +#cmakedefine HAVE_UV_TRANSLATE_SYS_ERROR  // TODO: add proper cmake check  // #define HAVE_SELINUX 1  #cmakedefine HAVE_SETENV diff --git a/src/nvim/file.c b/src/nvim/file.c new file mode 100644 index 0000000000..bc230ecf00 --- /dev/null +++ b/src/nvim/file.c @@ -0,0 +1,164 @@ +/// @file file.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 <stddef.h> +#include <stdbool.h> + +#include <uv.h> + +#include "nvim/file.h" +#include "nvim/memory.h" +#include "nvim/os/os.h" +#include "nvim/globals.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "file.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. +/// @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 fd; + +  fd = os_open(fname, flags, mode); + +  if (fd < 0) { +    return fd; +  } + +  ret_fp->fd = fd; +  ret_fp->eof = false; +  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 +/// +/// @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); +  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 +{ +  return os_fsync(fp->fd); +} + +/// 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 +{ +  return os_read(fp->fd, &fp->eof, ret_buf, size); +} + +/// 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) +{ +  return os_write(fp->fd, buf, size); +} + +/// Buffer used for skipping. Its contents is undefined and should never be +/// used. +static char skipbuf[IOSIZE]; + +/// 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 +{ +  size_t read_bytes = 0; +  do { +    ptrdiff_t new_read_bytes = file_read( +        fp, skipbuf, (size_t)(size - read_bytes > sizeof(skipbuf) +                              ? sizeof(skipbuf) +                              : size - read_bytes)); +    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 && !fp->eof); + +  return (ptrdiff_t)read_bytes; +} diff --git a/src/nvim/file.h b/src/nvim/file.h new file mode 100644 index 0000000000..0aa98e0def --- /dev/null +++ b/src/nvim/file.h @@ -0,0 +1,59 @@ +#ifndef NVIM_FILE_H +#define NVIM_FILE_H + +#include <stdbool.h> +#include <stddef.h> +#include <fcntl.h> + +#include "nvim/func_attr.h" + +/// Structure used to read from/write to file +typedef struct { +  int fd;  ///< File descriptor. +  bool eof;  ///< True if end of file was encountered. +} FileDescriptor; + +/// file_open() flags +typedef enum { +  FILE_READ_ONLY = O_RDONLY,  ///< Open file read-only. +  FILE_CREATE = O_CREAT,  ///< Create file if it does not exist yet. +  FILE_WRITE_ONLY = O_WRONLY,  ///< Open file for writing only. +#ifdef O_NOFOLLOW +  FILE_NOSYMLINK = O_NOFOLLOW,  ///< Do not allow symbolic links. +#else +  FILE_NOSYMLINK = 0, +#endif +  FILE_CREATE_ONLY = O_CREAT|O_EXCL,  ///< Only create the file, failing +                                      ///< if it already exists. +  FILE_TRUNCATE = O_TRUNC,  ///< Truncate the file if it exists. +} FileOpenFlags; + +/// 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) +  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +  FUNC_ATTR_ALWAYS_INLINE +{ +  return fp->eof && rbuffer_size(fp->rv) == 0; +} + +/// 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) +  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +  FUNC_ATTR_ALWAYS_INLINE +{ +  return fp->fd; +} + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "file.h.generated.h" +#endif +#endif  // NVIM_FILE_H diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 08122828bd..1fd3987b97 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -1,14 +1,20 @@  // 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 <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 +24,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 +345,123 @@ 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; +} + +/// 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 +469,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 +508,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 +519,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 +542,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 +555,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 +569,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 +581,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 +665,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 +709,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/shada.c b/src/nvim/shada.c index 380d955f63..a8efaeb7cf 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/file.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,31 @@ 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) { +  const int error = file_fsync(sd_writer->cookie); +  if (error < 0) {      emsgf(_(SERR "System error while synchronizing ShaDa file: %s"), -          os_strerror(errno)); -    errno = 0; +          os_strerror(error));    } -  close_file(fd); +  close_file(sd_writer->cookie);  }  /// Wrapper for read that reads to IObuff and ignores bytes read @@ -779,19 +736,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 +782,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 +792,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 +801,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, FILE_READ_ONLY, 0),    }; +  if (sd_reader->cookie == NULL) { +    return error; +  }    convert_setup(&sd_reader->sd_conv, "utf-8", p_enc); @@ -887,18 +813,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 +898,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));      } @@ -2975,10 +2895,16 @@ int shada_write_file(const char *const file, bool nomerge)    };    ShaDaReadDef sd_reader; -  intptr_t fd; -    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 +2922,12 @@ 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, FILE_CREATE_ONLY|FILE_NOSYMLINK|FILE_WRITE_ONLY, +        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') { @@ -3019,6 +2942,9 @@ shada_write_file_open:            (*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 +2968,26 @@ 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, FILE_CREATE|FILE_WRITE_ONLY|FILE_TRUNCATE, 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);      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"); @@ -3085,7 +3014,8 @@ shada_write_file_nomerge: {}                || 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); +            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"), | 
