file,os/fs,shada: Separate opening, closing, writing and reading files

Moves low-level functions handling to os/fs.c. Adds file.c with a proxy
interface.

Target: while leaving syscalls handling is os.c (partially handled by libuv),
add buffering for reading and writing to file.c.
This commit is contained in:
ZyX 2016-06-01 22:57:52 +03:00
parent 65af001f2b
commit 11dda658d6
6 changed files with 463 additions and 199 deletions

View File

@ -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)

View File

@ -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

164
src/nvim/file.c Normal file
View File

@ -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;
}

59
src/nvim/file.h Normal file
View File

@ -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

View File

@ -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()
// cant.
#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

View File

@ -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,8 +2968,19 @@ 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, 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 (sd_writer.cookie == NULL) {
xfree(fname);
xfree(tempname);
return FAIL;
}
if (p_verbose > 0) {
@ -3052,14 +2989,6 @@ 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
@ -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"),