mirror of
https://github.com/neovim/neovim.git
synced 2024-12-23 20:55:18 -07:00
Merge #4865 from ZyX-I/file-buffered-read
Use buffered reading/writing for ShaDa files
This commit is contained in:
commit
47a15d0256
@ -27,12 +27,15 @@ if(NOT HAVE_SYS_WAIT_H AND UNIX)
|
||||
endif()
|
||||
check_include_files(sys/utsname.h HAVE_SYS_UTSNAME_H)
|
||||
check_include_files(utime.h HAVE_UTIME_H)
|
||||
check_include_files(sys/uio.h HAVE_SYS_UIO_H)
|
||||
|
||||
# Functions
|
||||
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)
|
||||
check_function_exists(readv HAVE_READV)
|
||||
|
||||
if(Iconv_FOUND)
|
||||
set(HAVE_ICONV 1)
|
||||
|
@ -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
|
||||
@ -48,6 +49,13 @@
|
||||
#cmakedefine HAVE_WORKING_LIBINTL
|
||||
#cmakedefine UNIX
|
||||
#cmakedefine USE_FNAME_CASE
|
||||
#cmakedefine HAVE_SYS_UIO_H
|
||||
#ifdef HAVE_SYS_UIO_H
|
||||
#cmakedefine HAVE_READV
|
||||
# ifndef HAVE_READV
|
||||
# undef HAVE_SYS_UIO_H
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#cmakedefine FEAT_TUI
|
||||
|
||||
|
319
src/nvim/os/fileio.c
Normal file
319
src/nvim/os/fileio.c
Normal file
@ -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;
|
||||
}
|
72
src/nvim/os/fileio.h
Normal file
72
src/nvim/os/fileio.h
Normal file
@ -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
|
286
src/nvim/os/fs.c
286
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
|
||||
|
@ -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;
|
||||
|
272
src/nvim/shada.c
272
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,8 +2964,22 @@ shada_write_file_nomerge: {}
|
||||
}
|
||||
*tail = tail_save;
|
||||
}
|
||||
fd = (intptr_t) open_file(fname, O_CREAT|O_WRONLY|O_TRUNC,
|
||||
0600);
|
||||
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 (sd_writer.cookie == NULL) {
|
||||
xfree(fname);
|
||||
xfree(tempname);
|
||||
if (sd_reader.close != NULL) {
|
||||
sd_reader.close(&sd_reader);
|
||||
}
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
if (p_verbose > 0) {
|
||||
@ -3052,29 +2988,17 @@ shada_write_file_nomerge: {}
|
||||
verbose_leave();
|
||||
}
|
||||
|
||||
if (fd < 0) {
|
||||
xfree(fname);
|
||||
xfree(tempname);
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
sd_writer.cookie = (void *) fd;
|
||||
|
||||
convert_setup(&sd_writer.sd_conv, p_enc, "utf-8");
|
||||
|
||||
const ShaDaWriteResult sw_ret = shada_write(&sd_writer, (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;
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ local global_helpers = require('test.helpers')
|
||||
|
||||
local neq = global_helpers.neq
|
||||
local eq = global_helpers.eq
|
||||
local ok = global_helpers.ok
|
||||
|
||||
-- add some standard header locations
|
||||
for _, p in ipairs(Paths.include_paths) do
|
||||
@ -34,7 +35,8 @@ local function filter_complex_blocks(body)
|
||||
if not (string.find(line, "(^)", 1, true) ~= nil
|
||||
or string.find(line, "_ISwupper", 1, true)
|
||||
or string.find(line, "msgpack_zone_push_finalizer")
|
||||
or string.find(line, "msgpack_unpacker_reserve_buffer")) then
|
||||
or string.find(line, "msgpack_unpacker_reserve_buffer")
|
||||
or string.find(line, "inline _Bool")) then
|
||||
result[#result + 1] = line
|
||||
end
|
||||
end
|
||||
@ -156,6 +158,7 @@ return {
|
||||
cimport = cimport,
|
||||
cppimport = cppimport,
|
||||
internalize = internalize,
|
||||
ok = ok,
|
||||
eq = eq,
|
||||
neq = neq,
|
||||
ffi = ffi,
|
||||
|
365
test/unit/os/fileio_spec.lua
Normal file
365
test/unit/os/fileio_spec.lua
Normal file
@ -0,0 +1,365 @@
|
||||
local lfs = require('lfs')
|
||||
|
||||
local helpers = require('test.unit.helpers')
|
||||
|
||||
local eq = helpers.eq
|
||||
local ffi = helpers.ffi
|
||||
local cimport = helpers.cimport
|
||||
|
||||
local m = cimport('./src/nvim/os/fileio.h')
|
||||
|
||||
local fcontents = ''
|
||||
for i = 0, 255 do
|
||||
fcontents = fcontents .. (i == 0 and '\0' or ('%c'):format(i))
|
||||
end
|
||||
fcontents = fcontents:rep(16)
|
||||
|
||||
local dir = 'Xtest-unit-file_spec.d'
|
||||
local file1 = dir .. '/file1.dat'
|
||||
local file2 = dir .. '/file2.dat'
|
||||
local linkf = dir .. '/file.lnk'
|
||||
local linkb = dir .. '/broken.lnk'
|
||||
local filec = dir .. '/created-file.dat'
|
||||
|
||||
before_each(function()
|
||||
lfs.mkdir(dir);
|
||||
|
||||
local f1 = io.open(file1, 'w')
|
||||
f1:write(fcontents)
|
||||
f1:close()
|
||||
|
||||
local f2 = io.open(file2, 'w')
|
||||
f2:write(fcontents)
|
||||
f2:close()
|
||||
|
||||
lfs.link('file1.dat', linkf, true)
|
||||
lfs.link('broken.dat', linkb, true)
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
os.remove(file1)
|
||||
os.remove(file2)
|
||||
os.remove(linkf)
|
||||
os.remove(linkb)
|
||||
os.remove(filec)
|
||||
lfs.rmdir(dir)
|
||||
end)
|
||||
|
||||
local function file_open(fname, flags, mode)
|
||||
local ret2 = ffi.new('FileDescriptor')
|
||||
local ret1 = m.file_open(ret2, fname, flags, mode)
|
||||
return ret1, ret2
|
||||
end
|
||||
|
||||
local function file_open_new(fname, flags, mode)
|
||||
local ret1 = ffi.new('int[?]', 1, {0})
|
||||
local ret2 = ffi.gc(m.file_open_new(ret1, fname, flags, mode), nil)
|
||||
return ret1[0], ret2
|
||||
end
|
||||
|
||||
local function file_write(fp, buf)
|
||||
return m.file_write(fp, buf, #buf)
|
||||
end
|
||||
|
||||
local function file_read(fp, size)
|
||||
local buf = nil
|
||||
if size == nil then
|
||||
size = 0
|
||||
else
|
||||
-- For some reason if length of NUL-bytes-string is the same as `char[?]`
|
||||
-- size luajit garbage collector crashes. But it does not do so in
|
||||
-- os_read[v] tests in os/fs_spec.lua.
|
||||
buf = ffi.new('char[?]', size + 1, ('\0'):rep(size))
|
||||
end
|
||||
local ret1 = m.file_read(fp, buf, size)
|
||||
local ret2 = ''
|
||||
if buf ~= nil then
|
||||
ret2 = ffi.string(buf, size)
|
||||
end
|
||||
return ret1, ret2
|
||||
end
|
||||
|
||||
local function file_fsync(fp)
|
||||
return m.file_fsync(fp)
|
||||
end
|
||||
|
||||
local function file_skip(fp, size)
|
||||
return m.file_skip(fp, size)
|
||||
end
|
||||
|
||||
describe('file_open', function()
|
||||
it('can create a rwx------ file with kFileCreate', function()
|
||||
local err, fp = file_open(filec, m.kFileCreate, 448)
|
||||
eq(0, err)
|
||||
local attrs = lfs.attributes(filec)
|
||||
eq('rwx------', attrs.permissions)
|
||||
eq(0, m.file_close(fp))
|
||||
end)
|
||||
|
||||
it('can create a rw------- file with kFileCreate', function()
|
||||
local err, fp = file_open(filec, m.kFileCreate, 384)
|
||||
eq(0, err)
|
||||
local attrs = lfs.attributes(filec)
|
||||
eq('rw-------', attrs.permissions)
|
||||
eq(0, m.file_close(fp))
|
||||
end)
|
||||
|
||||
it('can create a rwx------ file with kFileCreateOnly', function()
|
||||
local err, fp = file_open(filec, m.kFileCreateOnly, 448)
|
||||
eq(0, err)
|
||||
local attrs = lfs.attributes(filec)
|
||||
eq('rwx------', attrs.permissions)
|
||||
eq(0, m.file_close(fp))
|
||||
end)
|
||||
|
||||
it('can create a rw------- file with kFileCreateOnly', function()
|
||||
local err, fp = file_open(filec, m.kFileCreateOnly, 384)
|
||||
eq(0, err)
|
||||
local attrs = lfs.attributes(filec)
|
||||
eq('rw-------', attrs.permissions)
|
||||
eq(0, m.file_close(fp))
|
||||
end)
|
||||
|
||||
it('fails to open an existing file with kFileCreateOnly', function()
|
||||
local err, _ = file_open(file1, m.kFileCreateOnly, 384)
|
||||
eq(m.UV_EEXIST, err)
|
||||
end)
|
||||
|
||||
it('fails to open an symlink with kFileNoSymlink', function()
|
||||
local err, _ = file_open(linkf, m.kFileNoSymlink, 384)
|
||||
-- err is UV_EMLINK in FreeBSD, but if I use `ok(err == m.UV_ELOOP or err ==
|
||||
-- m.UV_EMLINK)`, then I loose the ability to see actual `err` value.
|
||||
if err ~= m.UV_ELOOP then eq(m.UV_EMLINK, err) end
|
||||
end)
|
||||
|
||||
it('can open an existing file write-only with kFileCreate', function()
|
||||
local err, fp = file_open(file1, m.kFileCreate, 384)
|
||||
eq(0, err)
|
||||
eq(true, fp.wr)
|
||||
eq(0, m.file_close(fp))
|
||||
end)
|
||||
|
||||
it('can open an existing file read-only with zero', function()
|
||||
local err, fp = file_open(file1, 0, 384)
|
||||
eq(0, err)
|
||||
eq(false, fp.wr)
|
||||
eq(0, m.file_close(fp))
|
||||
end)
|
||||
|
||||
it('can open an existing file read-only with kFileReadOnly', function()
|
||||
local err, fp = file_open(file1, m.kFileReadOnly, 384)
|
||||
eq(0, err)
|
||||
eq(false, fp.wr)
|
||||
eq(0, m.file_close(fp))
|
||||
end)
|
||||
|
||||
it('can open an existing file read-only with kFileNoSymlink', function()
|
||||
local err, fp = file_open(file1, m.kFileNoSymlink, 384)
|
||||
eq(0, err)
|
||||
eq(false, fp.wr)
|
||||
eq(0, m.file_close(fp))
|
||||
end)
|
||||
|
||||
it('can truncate an existing file with kFileTruncate', function()
|
||||
local err, fp = file_open(file1, m.kFileTruncate, 384)
|
||||
eq(0, err)
|
||||
eq(true, fp.wr)
|
||||
eq(0, m.file_close(fp))
|
||||
local attrs = lfs.attributes(file1)
|
||||
eq(0, attrs.size)
|
||||
end)
|
||||
|
||||
it('can open an existing file write-only with kFileWriteOnly', function()
|
||||
local err, fp = file_open(file1, m.kFileWriteOnly, 384)
|
||||
eq(0, err)
|
||||
eq(true, fp.wr)
|
||||
eq(0, m.file_close(fp))
|
||||
local attrs = lfs.attributes(file1)
|
||||
eq(4096, attrs.size)
|
||||
end)
|
||||
|
||||
it('fails to create a file with just kFileWriteOnly', function()
|
||||
local err, _ = file_open(filec, m.kFileWriteOnly, 384)
|
||||
eq(m.UV_ENOENT, err)
|
||||
local attrs = lfs.attributes(filec)
|
||||
eq(nil, attrs)
|
||||
end)
|
||||
|
||||
it('can truncate an existing file with kFileTruncate when opening a symlink',
|
||||
function()
|
||||
local err, fp = file_open(linkf, m.kFileTruncate, 384)
|
||||
eq(0, err)
|
||||
eq(true, fp.wr)
|
||||
eq(0, m.file_close(fp))
|
||||
local attrs = lfs.attributes(file1)
|
||||
eq(0, attrs.size)
|
||||
end)
|
||||
|
||||
it('fails to open a directory write-only', function()
|
||||
local err, _ = file_open(dir, m.kFileWriteOnly, 384)
|
||||
eq(m.UV_EISDIR, err)
|
||||
end)
|
||||
|
||||
it('fails to open a broken symbolic link write-only', function()
|
||||
local err, _ = file_open(linkb, m.kFileWriteOnly, 384)
|
||||
eq(m.UV_ENOENT, err)
|
||||
end)
|
||||
|
||||
it('fails to open a broken symbolic link read-only', function()
|
||||
local err, _ = file_open(linkb, m.kFileReadOnly, 384)
|
||||
eq(m.UV_ENOENT, err)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('file_open_new', function()
|
||||
it('can open a file read-only', function()
|
||||
local err, fp = file_open_new(file1, 0, 384)
|
||||
eq(0, err)
|
||||
eq(false, fp.wr)
|
||||
eq(0, m.file_free(fp))
|
||||
end)
|
||||
|
||||
it('fails to open an existing file with kFileCreateOnly', function()
|
||||
local err, fp = file_open_new(file1, m.kFileCreateOnly, 384)
|
||||
eq(m.UV_EEXIST, err)
|
||||
eq(nil, fp)
|
||||
end)
|
||||
end)
|
||||
|
||||
-- file_close is called above, so it is not tested directly
|
||||
|
||||
describe('file_fsync', function()
|
||||
it('can flush writes to disk', function()
|
||||
local err, fp = file_open(filec, m.kFileCreateOnly, 384)
|
||||
eq(0, file_fsync(fp))
|
||||
eq(0, err)
|
||||
eq(0, lfs.attributes(filec).size)
|
||||
local wsize = file_write(fp, 'test')
|
||||
eq(4, wsize)
|
||||
eq(0, lfs.attributes(filec).size)
|
||||
eq(0, file_fsync(fp))
|
||||
eq(wsize, lfs.attributes(filec).size)
|
||||
eq(0, m.file_close(fp))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('file_read', function()
|
||||
it('can read small chunks of input until eof', function()
|
||||
local err, fp = file_open(file1, 0, 384)
|
||||
eq(0, err)
|
||||
eq(false, fp.wr)
|
||||
local shift = 0
|
||||
while shift < #fcontents do
|
||||
local size = 3
|
||||
local exp_err = size
|
||||
local exp_s = fcontents:sub(shift + 1, shift + size)
|
||||
if shift + size >= #fcontents then
|
||||
exp_err = #fcontents - shift
|
||||
exp_s = (fcontents:sub(shift + 1, shift + size)
|
||||
.. (('\0'):rep(size - exp_err)))
|
||||
end
|
||||
eq({exp_err, exp_s}, {file_read(fp, size)})
|
||||
shift = shift + size
|
||||
end
|
||||
eq(0, m.file_close(fp))
|
||||
end)
|
||||
|
||||
it('can read the whole file at once', function()
|
||||
local err, fp = file_open(file1, 0, 384)
|
||||
eq(0, err)
|
||||
eq(false, fp.wr)
|
||||
eq({#fcontents, fcontents}, {file_read(fp, #fcontents)})
|
||||
eq({0, ('\0'):rep(#fcontents)}, {file_read(fp, #fcontents)})
|
||||
eq(0, m.file_close(fp))
|
||||
end)
|
||||
|
||||
it('can read more then 1024 bytes after reading a small chunk', function()
|
||||
local err, fp = file_open(file1, 0, 384)
|
||||
eq(0, err)
|
||||
eq(false, fp.wr)
|
||||
eq({5, fcontents:sub(1, 5)}, {file_read(fp, 5)})
|
||||
eq({#fcontents - 5, fcontents:sub(6) .. (('\0'):rep(5))},
|
||||
{file_read(fp, #fcontents)})
|
||||
eq(0, m.file_close(fp))
|
||||
end)
|
||||
|
||||
it('can read file by 768-byte-chunks', function()
|
||||
local err, fp = file_open(file1, 0, 384)
|
||||
eq(0, err)
|
||||
eq(false, fp.wr)
|
||||
local shift = 0
|
||||
while shift < #fcontents do
|
||||
local size = 768
|
||||
local exp_err = size
|
||||
local exp_s = fcontents:sub(shift + 1, shift + size)
|
||||
if shift + size >= #fcontents then
|
||||
exp_err = #fcontents - shift
|
||||
exp_s = (fcontents:sub(shift + 1, shift + size)
|
||||
.. (('\0'):rep(size - exp_err)))
|
||||
end
|
||||
eq({exp_err, exp_s}, {file_read(fp, size)})
|
||||
shift = shift + size
|
||||
end
|
||||
eq(0, m.file_close(fp))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('file_write', function()
|
||||
it('can write the whole file at once', function()
|
||||
local err, fp = file_open(filec, m.kFileCreateOnly, 384)
|
||||
eq(0, err)
|
||||
eq(true, fp.wr)
|
||||
local wr = file_write(fp, fcontents)
|
||||
eq(#fcontents, wr)
|
||||
eq(0, m.file_close(fp))
|
||||
eq(wr, lfs.attributes(filec).size)
|
||||
eq(fcontents, io.open(filec):read('*a'))
|
||||
end)
|
||||
|
||||
it('can write the whole file by small chunks', function()
|
||||
local err, fp = file_open(filec, m.kFileCreateOnly, 384)
|
||||
eq(0, err)
|
||||
eq(true, fp.wr)
|
||||
local shift = 0
|
||||
while shift < #fcontents do
|
||||
local size = 3
|
||||
local s = fcontents:sub(shift + 1, shift + size)
|
||||
local wr = file_write(fp, s)
|
||||
eq(wr, #s)
|
||||
shift = shift + size
|
||||
end
|
||||
eq(0, m.file_close(fp))
|
||||
eq(#fcontents, lfs.attributes(filec).size)
|
||||
eq(fcontents, io.open(filec):read('*a'))
|
||||
end)
|
||||
|
||||
it('can write the whole file by 768-byte-chunks', function()
|
||||
local err, fp = file_open(filec, m.kFileCreateOnly, 384)
|
||||
eq(0, err)
|
||||
eq(true, fp.wr)
|
||||
local shift = 0
|
||||
while shift < #fcontents do
|
||||
local size = 768
|
||||
local s = fcontents:sub(shift + 1, shift + size)
|
||||
local wr = file_write(fp, s)
|
||||
eq(wr, #s)
|
||||
shift = shift + size
|
||||
end
|
||||
eq(0, m.file_close(fp))
|
||||
eq(#fcontents, lfs.attributes(filec).size)
|
||||
eq(fcontents, io.open(filec):read('*a'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('file_skip', function()
|
||||
it('can skip 3 bytes', function()
|
||||
local err, fp = file_open(file1, 0, 384)
|
||||
eq(0, err)
|
||||
eq(false, fp.wr)
|
||||
eq(3, file_skip(fp, 3))
|
||||
local rd, s = file_read(fp, 3)
|
||||
eq(3, rd)
|
||||
eq(fcontents:sub(4, 6), s)
|
||||
eq(0, m.file_close(fp))
|
||||
end)
|
||||
end)
|
@ -6,6 +6,7 @@ local helpers = require('test.unit.helpers')
|
||||
local cimport = helpers.cimport
|
||||
local cppimport = helpers.cppimport
|
||||
local internalize = helpers.internalize
|
||||
local ok = helpers.ok
|
||||
local eq = helpers.eq
|
||||
local neq = helpers.neq
|
||||
local ffi = helpers.ffi
|
||||
@ -27,6 +28,12 @@ cppimport('sys/stat.h')
|
||||
cppimport('fcntl.h')
|
||||
cppimport('uv-errno.h')
|
||||
|
||||
local s = ''
|
||||
for i = 0, 255 do
|
||||
s = s .. (i == 0 and '\0' or ('%c'):format(i))
|
||||
end
|
||||
local fcontents = s:rep(16)
|
||||
|
||||
local buffer = ""
|
||||
local directory = nil
|
||||
local absolute_executable = nil
|
||||
@ -150,11 +157,11 @@ describe('fs function', function()
|
||||
local function os_can_exe(name)
|
||||
local buf = ffi.new('char *[1]')
|
||||
buf[0] = NULL
|
||||
local ok = fs.os_can_exe(to_cstr(name), buf, true)
|
||||
local ce_ret = fs.os_can_exe(to_cstr(name), buf, true)
|
||||
|
||||
-- When os_can_exe returns true, it must set the path.
|
||||
-- When it returns false, the path must be NULL.
|
||||
if ok then
|
||||
if ce_ret then
|
||||
neq(NULL, buf[0])
|
||||
return internalize(buf[0])
|
||||
else
|
||||
@ -368,6 +375,51 @@ describe('fs function', function()
|
||||
local function os_open(path, flags, mode)
|
||||
return fs.os_open((to_cstr(path)), flags, mode)
|
||||
end
|
||||
local function os_close(fd)
|
||||
return fs.os_close(fd)
|
||||
end
|
||||
-- For some reason if length of NUL-bytes-string is the same as `char[?]`
|
||||
-- size luajit crashes. Though it does not do so in this test suite, better
|
||||
-- be cautios and allocate more elements then needed. I only did this to
|
||||
-- strings.
|
||||
local function os_read(fd, size)
|
||||
local buf = nil
|
||||
if size == nil then
|
||||
size = 0
|
||||
else
|
||||
buf = ffi.new('char[?]', size + 1, ('\0'):rep(size))
|
||||
end
|
||||
local eof = ffi.new('bool[?]', 1, {true})
|
||||
local ret2 = fs.os_read(fd, eof, buf, size)
|
||||
local ret1 = eof[0]
|
||||
local ret3 = ''
|
||||
if buf ~= nil then
|
||||
ret3 = ffi.string(buf, size)
|
||||
end
|
||||
return ret1, ret2, ret3
|
||||
end
|
||||
local function os_readv(fd, sizes)
|
||||
local bufs = {}
|
||||
for i, size in ipairs(sizes) do
|
||||
bufs[i] = {
|
||||
iov_base=ffi.new('char[?]', size + 1, ('\0'):rep(size)),
|
||||
iov_len=size,
|
||||
}
|
||||
end
|
||||
local iov = ffi.new('struct iovec[?]', #sizes, bufs)
|
||||
local eof = ffi.new('bool[?]', 1, {true})
|
||||
local ret2 = fs.os_readv(fd, eof, iov, #sizes)
|
||||
local ret1 = eof[0]
|
||||
local ret3 = {}
|
||||
for i = 1,#sizes do
|
||||
-- Warning: iov may not be used.
|
||||
ret3[i] = ffi.string(bufs[i].iov_base, bufs[i].iov_len)
|
||||
end
|
||||
return ret1, ret2, ret3
|
||||
end
|
||||
local function os_write(fd, data)
|
||||
return fs.os_write(fd, data, data and #data or 0)
|
||||
end
|
||||
|
||||
describe('os_file_exists', function()
|
||||
it('returns false when given a non-existing file', function()
|
||||
@ -432,30 +484,34 @@ describe('fs function', function()
|
||||
end)
|
||||
|
||||
describe('os_open', function()
|
||||
local new_file = 'test_new_file'
|
||||
local existing_file = 'unit-test-directory/test_existing.file'
|
||||
|
||||
before_each(function()
|
||||
io.open('unit-test-directory/test_existing.file', 'w').close()
|
||||
(io.open(existing_file, 'w')):close()
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
os.remove('unit-test-directory/test_existing.file')
|
||||
os.remove('test_new_file')
|
||||
os.remove(existing_file)
|
||||
os.remove(new_file)
|
||||
end)
|
||||
|
||||
local new_file = 'test_new_file'
|
||||
local existing_file = 'unit-test-directory/test_existing.file'
|
||||
|
||||
it('returns UV_ENOENT for O_RDWR on a non-existing file', function()
|
||||
eq(ffi.C.UV_ENOENT, (os_open('non-existing-file', ffi.C.kO_RDWR, 0)))
|
||||
end)
|
||||
|
||||
it('returns non-negative for O_CREAT on a non-existing file', function()
|
||||
it('returns non-negative for O_CREAT on a non-existing file which then can be closed', function()
|
||||
assert_file_does_not_exist(new_file)
|
||||
assert.is_true(0 <= (os_open(new_file, ffi.C.kO_CREAT, 0)))
|
||||
local fd = os_open(new_file, ffi.C.kO_CREAT, 0)
|
||||
assert.is_true(0 <= fd)
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
|
||||
it('returns non-negative for O_CREAT on a existing file', function()
|
||||
it('returns non-negative for O_CREAT on a existing file which then can be closed', function()
|
||||
assert_file_exists(existing_file)
|
||||
assert.is_true(0 <= (os_open(existing_file, ffi.C.kO_CREAT, 0)))
|
||||
local fd = os_open(existing_file, ffi.C.kO_CREAT, 0)
|
||||
assert.is_true(0 <= fd)
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
|
||||
it('returns UV_EEXIST for O_CREAT|O_EXCL on a existing file', function()
|
||||
@ -463,24 +519,181 @@ describe('fs function', function()
|
||||
eq(ffi.C.kUV_EEXIST, (os_open(existing_file, (bit.bor(ffi.C.kO_CREAT, ffi.C.kO_EXCL)), 0)))
|
||||
end)
|
||||
|
||||
it('sets `rwx` permissions for O_CREAT 700', function()
|
||||
it('sets `rwx` permissions for O_CREAT 700 which then can be closed', function()
|
||||
assert_file_does_not_exist(new_file)
|
||||
--create the file
|
||||
os_open(new_file, ffi.C.kO_CREAT, tonumber("700", 8))
|
||||
local fd = os_open(new_file, ffi.C.kO_CREAT, tonumber("700", 8))
|
||||
--verify permissions
|
||||
eq('rwx------', lfs.attributes(new_file)['permissions'])
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
|
||||
it('sets `rw` permissions for O_CREAT 600', function()
|
||||
it('sets `rw` permissions for O_CREAT 600 which then can be closed', function()
|
||||
assert_file_does_not_exist(new_file)
|
||||
--create the file
|
||||
os_open(new_file, ffi.C.kO_CREAT, tonumber("600", 8))
|
||||
local fd = os_open(new_file, ffi.C.kO_CREAT, tonumber("600", 8))
|
||||
--verify permissions
|
||||
eq('rw-------', lfs.attributes(new_file)['permissions'])
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
|
||||
it('returns a non-negative file descriptor for an existing file', function()
|
||||
assert.is_true(0 <= (os_open(existing_file, ffi.C.kO_RDWR, 0)))
|
||||
it('returns a non-negative file descriptor for an existing file which then can be closed', function()
|
||||
local fd = os_open(existing_file, ffi.C.kO_RDWR, 0)
|
||||
assert.is_true(0 <= fd)
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('os_close', function()
|
||||
it('returns EBADF for negative file descriptors', function()
|
||||
eq(ffi.C.UV_EBADF, os_close(-1))
|
||||
eq(ffi.C.UV_EBADF, os_close(-1000))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('os_read', function()
|
||||
local file = 'test-unit-os-fs_spec-os_read.dat'
|
||||
|
||||
before_each(function()
|
||||
local f = io.open(file, 'w')
|
||||
f:write(fcontents)
|
||||
f:close()
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
os.remove(file)
|
||||
end)
|
||||
|
||||
it('can read zero bytes from a file', function()
|
||||
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
||||
ok(fd >= 0)
|
||||
eq({false, 0, ''}, {os_read(fd, nil)})
|
||||
eq({false, 0, ''}, {os_read(fd, 0)})
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
|
||||
it('can read from a file multiple times', function()
|
||||
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
||||
ok(fd >= 0)
|
||||
eq({false, 2, '\000\001'}, {os_read(fd, 2)})
|
||||
eq({false, 2, '\002\003'}, {os_read(fd, 2)})
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
|
||||
it('can read the whole file at once and then report eof', function()
|
||||
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
||||
ok(fd >= 0)
|
||||
eq({false, #fcontents, fcontents}, {os_read(fd, #fcontents)})
|
||||
eq({true, 0, ('\0'):rep(#fcontents)}, {os_read(fd, #fcontents)})
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
|
||||
it('can read the whole file in two calls, one partially', function()
|
||||
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
||||
ok(fd >= 0)
|
||||
eq({false, #fcontents * 3/4, fcontents:sub(1, #fcontents * 3/4)},
|
||||
{os_read(fd, #fcontents * 3/4)})
|
||||
eq({true,
|
||||
(#fcontents * 1/4),
|
||||
fcontents:sub(#fcontents * 3/4 + 1) .. ('\0'):rep(#fcontents * 2/4)},
|
||||
{os_read(fd, #fcontents * 3/4)})
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('os_readv', function()
|
||||
-- Function may be absent
|
||||
if not pcall(function() return fs.os_readv end) then
|
||||
return
|
||||
end
|
||||
local file = 'test-unit-os-fs_spec-os_readv.dat'
|
||||
|
||||
before_each(function()
|
||||
local f = io.open(file, 'w')
|
||||
f:write(fcontents)
|
||||
f:close()
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
os.remove(file)
|
||||
end)
|
||||
|
||||
it('can read zero bytes from a file', function()
|
||||
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
||||
ok(fd >= 0)
|
||||
eq({false, 0, {}}, {os_readv(fd, {})})
|
||||
eq({false, 0, {'', '', ''}}, {os_readv(fd, {0, 0, 0})})
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
|
||||
it('can read from a file multiple times to a differently-sized buffers', function()
|
||||
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
||||
ok(fd >= 0)
|
||||
eq({false, 2, {'\000\001'}}, {os_readv(fd, {2})})
|
||||
eq({false, 5, {'\002\003', '\004\005\006'}}, {os_readv(fd, {2, 3})})
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
|
||||
it('can read the whole file at once and then report eof', function()
|
||||
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
||||
ok(fd >= 0)
|
||||
eq({false,
|
||||
#fcontents,
|
||||
{fcontents:sub(1, #fcontents * 1/4),
|
||||
fcontents:sub(#fcontents * 1/4 + 1, #fcontents * 3/4),
|
||||
fcontents:sub(#fcontents * 3/4 + 1, #fcontents * 15/16),
|
||||
fcontents:sub(#fcontents * 15/16 + 1, #fcontents)}},
|
||||
{os_readv(fd, {#fcontents * 1/4,
|
||||
#fcontents * 2/4,
|
||||
#fcontents * 3/16,
|
||||
#fcontents * 1/16})})
|
||||
eq({true, 0, {'\0'}}, {os_readv(fd, {1})})
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
|
||||
it('can read the whole file in two calls, one partially', function()
|
||||
local fd = os_open(file, ffi.C.kO_RDONLY, 0)
|
||||
ok(fd >= 0)
|
||||
eq({false, #fcontents * 3/4, {fcontents:sub(1, #fcontents * 3/4)}},
|
||||
{os_readv(fd, {#fcontents * 3/4})})
|
||||
eq({true,
|
||||
(#fcontents * 1/4),
|
||||
{fcontents:sub(#fcontents * 3/4 + 1) .. ('\0'):rep(#fcontents * 2/4)}},
|
||||
{os_readv(fd, {#fcontents * 3/4})})
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('os_write', function()
|
||||
-- Function may be absent
|
||||
local file = 'test-unit-os-fs_spec-os_write.dat'
|
||||
|
||||
before_each(function()
|
||||
local f = io.open(file, 'w')
|
||||
f:write(fcontents)
|
||||
f:close()
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
os.remove(file)
|
||||
end)
|
||||
|
||||
it('can write zero bytes to a file', function()
|
||||
local fd = os_open(file, ffi.C.kO_WRONLY, 0)
|
||||
ok(fd >= 0)
|
||||
eq(0, os_write(fd, ''))
|
||||
eq(0, os_write(fd, nil))
|
||||
eq(fcontents, io.open(file, 'r'):read('*a'))
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
|
||||
it('can write some data to a file', function()
|
||||
local fd = os_open(file, ffi.C.kO_WRONLY, 0)
|
||||
ok(fd >= 0)
|
||||
eq(3, os_write(fd, 'abc'))
|
||||
eq(4, os_write(fd, ' def'))
|
||||
eq('abc def' .. fcontents:sub(8), io.open(file, 'r'):read('*a'))
|
||||
eq(0, os_close(fd))
|
||||
end)
|
||||
end)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user