diff options
Diffstat (limited to 'src/nvim/os/fs.c')
-rw-r--r-- | src/nvim/os/fs.c | 662 |
1 files changed, 492 insertions, 170 deletions
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index d12d34d595..9a4391a0ae 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -1,9 +1,11 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + // 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> @@ -22,10 +24,13 @@ #include "nvim/message.h" #include "nvim/assert.h" #include "nvim/misc1.h" -#include "nvim/misc2.h" #include "nvim/path.h" #include "nvim/strings.h" +#ifdef WIN32 +#include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8 +#endif + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/fs.c.generated.h" #endif @@ -56,9 +61,9 @@ void fs_init(void) } -/// Change to the given directory. +/// Changes the current directory to `path`. /// -/// @return `0` on success, a libuv error code on failure. +/// @return 0 on success, or negative error code. int os_chdir(const char *path) FUNC_ATTR_NONNULL_ALL { @@ -89,11 +94,11 @@ int os_dirname(char_u *buf, size_t len) /// Check if the given path is a directory and not a symlink to a directory. /// @return `true` if `name` is a directory and NOT a symlink to a directory. /// `false` if `name` is not a directory or if an error occurred. -bool os_isrealdir(const char_u *name) +bool os_isrealdir(const char *name) FUNC_ATTR_NONNULL_ALL { uv_fs_t request; - if (uv_fs_lstat(&fs_loop, &request, (char *)name, NULL) != kLibuvSuccess) { + if (uv_fs_lstat(&fs_loop, &request, name, NULL) != kLibuvSuccess) { return false; } if (S_ISLNK(request.statbuf.st_mode)) { @@ -105,11 +110,11 @@ bool os_isrealdir(const char_u *name) /// Check if the given path is a directory or not. /// -/// @return `true` if `fname` is a directory. +/// @return `true` if `name` is a directory. bool os_isdir(const char_u *name) FUNC_ATTR_NONNULL_ALL { - int32_t mode = os_getperm(name); + int32_t mode = os_getperm((const char *)name); if (mode < 0) { return false; } @@ -121,28 +126,38 @@ bool os_isdir(const char_u *name) return true; } +/// Check if the given path is a directory and is executable. +/// Gives the same results as `os_isdir()` on Windows. +/// +/// @return `true` if `name` is a directory and executable. +bool os_isdir_executable(const char *name) + FUNC_ATTR_NONNULL_ALL +{ + int32_t mode = os_getperm((const char *)name); + if (mode < 0) { + return false; + } + +#ifdef WIN32 + return (S_ISDIR(mode)); +#else + return (S_ISDIR(mode) && (S_IXUSR & mode)); +#endif +} + /// Check what `name` is: /// @return NODE_NORMAL: file or directory (or doesn't exist) /// NODE_WRITABLE: writable device, socket, fifo, etc. /// NODE_OTHER: non-writable things int os_nodetype(const char *name) + FUNC_ATTR_NONNULL_ALL { -#ifdef WIN32 - // Edge case from Vim os_win32.c: - // We can't open a file with a name "\\.\con" or "\\.\prn", trying to read - // from it later will cause Vim to hang. Thus return NODE_WRITABLE here. - if (STRNCMP(name, "\\\\.\\", 4) == 0) { - return NODE_WRITABLE; - } -#endif - +#ifndef WIN32 // Unix uv_stat_t statbuf; if (0 != os_stat(name, &statbuf)) { return NODE_NORMAL; // File doesn't exist. } - -#ifndef WIN32 - // libuv does not handle BLK and DIR in uv_handle_type. + // uv_handle_type does not distinguish BLK and DIR. // Related: https://github.com/joyent/libuv/pull/1421 if (S_ISREG(statbuf.st_mode) || S_ISDIR(statbuf.st_mode)) { return NODE_NORMAL; @@ -150,40 +165,65 @@ int os_nodetype(const char *name) if (S_ISBLK(statbuf.st_mode)) { // block device isn't writable return NODE_OTHER; } -#endif + // Everything else is writable? + // buf_write() expects NODE_WRITABLE for char device /dev/stderr. + return NODE_WRITABLE; +#else // Windows + // Edge case from Vim os_win32.c: + // We can't open a file with a name "\\.\con" or "\\.\prn", trying to read + // from it later will cause Vim to hang. Thus return NODE_WRITABLE here. + if (STRNCMP(name, "\\\\.\\", 4) == 0) { + return NODE_WRITABLE; + } - // Vim os_win32.c:mch_nodetype does this (since patch 7.4.015): - // if (enc_codepage >= 0 && (int)GetACP() != enc_codepage) { - // wn = enc_to_utf16(name, NULL); - // hFile = CreatFile(wn, ...) - // to get a HANDLE. But libuv just calls win32's _get_osfhandle() on the fd we - // give it. uv_fs_open calls fs__capture_path which does a similar dance and - // saves us the hassle. - - int nodetype = NODE_WRITABLE; - int fd = os_open(name, O_RDONLY, 0); - switch(uv_guess_handle(fd)) { - case UV_TTY: // FILE_TYPE_CHAR - nodetype = NODE_WRITABLE; - break; - case UV_FILE: // FILE_TYPE_DISK - nodetype = NODE_NORMAL; - break; - case UV_NAMED_PIPE: // not handled explicitly in Vim os_win32.c - case UV_UDP: // unix only - case UV_TCP: // unix only + // Vim os_win32.c:mch_nodetype does (since 7.4.015): + // wn = enc_to_utf16(name, NULL); + // hFile = CreatFile(wn, ...) + // to get a HANDLE. Whereas libuv just calls _get_osfhandle() on the fd we + // give it. But uv_fs_open later calls fs__capture_path which does a similar + // utf8-to-utf16 dance and saves us the hassle. + + // macOS: os_open(/dev/stderr) would return UV_EACCES. + int fd = os_open(name, O_RDONLY +# ifdef O_NONBLOCK + | O_NONBLOCK +# endif + , 0); + if (fd < 0) { // open() failed. + return NODE_NORMAL; + } + int guess = uv_guess_handle(fd); + if (close(fd) == -1) { + ELOG("close(%d) failed. name='%s'", fd, name); + } + + switch (guess) { + case UV_TTY: // FILE_TYPE_CHAR + return NODE_WRITABLE; + case UV_FILE: // FILE_TYPE_DISK + return NODE_NORMAL; + case UV_NAMED_PIPE: // not handled explicitly in Vim os_win32.c + case UV_UDP: // unix only + case UV_TCP: // unix only case UV_UNKNOWN_HANDLE: default: -#ifdef WIN32 - nodetype = NODE_NORMAL; -#else - nodetype = NODE_WRITABLE; // Everything else is writable? -#endif - break; + return NODE_OTHER; // Vim os_win32.c default } +#endif +} - close(fd); - return nodetype; +/// Gets the absolute path of the currently running executable. +/// May fail if procfs is missing. #6734 +/// @see path_exepath +/// +/// @param[out] buffer Full path to the executable. +/// @param[in] size Size of `buffer`. +/// +/// @return 0 on success, or libuv error code. +int os_exepath(char *buffer, size_t *size) + FUNC_ATTR_NONNULL_ALL +{ + return uv_exepath(buffer, size); } /// Checks if the given path represents an executable file. @@ -201,49 +241,87 @@ int os_nodetype(const char *name) bool os_can_exe(const char_u *name, char_u **abspath, bool use_path) FUNC_ATTR_NONNULL_ARG(1) { - // when use_path is false or if it's an absolute or relative path don't - // need to use $PATH. - if (!use_path || path_is_absolute_path(name) - || (name[0] == '.' - && (name[1] == '/' - || (name[1] == '.' && name[2] == '/')))) { - // There must be a path separator, files in the current directory - // can't be executed - if (gettail_dir(name) != name && is_executable(name)) { + bool no_path = !use_path || path_is_absolute(name); +#ifndef WIN32 + // If the filename is "qualified" (relative or absolute) do not check $PATH. + no_path |= (name[0] == '.' + && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))); +#endif + + if (no_path) { +#ifdef WIN32 + const char *pathext = os_getenv("PATHEXT"); + if (!pathext) { + pathext = ".com;.exe;.bat;.cmd"; + } + bool ok = is_executable((char *)name) || is_executable_ext((char *)name, + pathext); +#else + // Must have path separator, cannot execute files in the current directory. + const bool ok = ((const char_u *)gettail_dir((const char *)name) != name + && is_executable((char *)name)); +#endif + if (ok) { if (abspath != NULL) { - *abspath = save_absolute_path(name); + *abspath = save_abs_path(name); } - return true; } - return false; } return is_executable_in_path(name, abspath); } -// Return true if "name" is an executable file, false if not or it doesn't -// exist. -static bool is_executable(const char_u *name) +/// Returns true if `name` is an executable file. +static bool is_executable(const char *name) FUNC_ATTR_NONNULL_ALL { - int32_t mode = os_getperm(name); + int32_t mode = os_getperm((const char *)name); if (mode < 0) { return false; } -#if WIN32 +#ifdef WIN32 // Windows does not have exec bit; just check if the file exists and is not // a directory. return (S_ISREG(mode)); #else return (S_ISREG(mode) && (S_IXUSR & mode)); #endif +} + +#ifdef WIN32 +/// Appends file extensions from `pathext` to `name` and returns true if any +/// such combination is executable. +static bool is_executable_ext(char *name, const char *pathext) + FUNC_ATTR_NONNULL_ALL +{ + xstrlcpy(os_buf, name, sizeof(os_buf)); + char *buf_end = xstrchrnul(os_buf, '\0'); + for (const char *ext = pathext; *ext; ext++) { + // Skip the extension if there is no suffix after a '.'. + if (ext[0] == '.' && (ext[1] == '\0' || ext[1] == ENV_SEPCHAR)) { + ext++; + continue; + } + + const char *ext_end = xstrchrnul(ext, ENV_SEPCHAR); + STRLCPY(buf_end, ext, ext_end - ext + 1); + if (is_executable(os_buf)) { + return true; + } + + if (*ext_end != ENV_SEPCHAR) { + break; + } + ext = ext_end; + } return false; } +#endif /// Checks if a file is inside the `$PATH` and is executable. /// @@ -254,89 +332,69 @@ static bool is_executable(const char_u *name) static bool is_executable_in_path(const char_u *name, char_u **abspath) FUNC_ATTR_NONNULL_ARG(1) { - const char *path = os_getenv("PATH"); - if (path == NULL) { + const char *path_env = os_getenv("PATH"); + if (path_env == NULL) { return false; } - size_t buf_len = STRLEN(name) + STRLEN(path) + 2; +#ifdef WIN32 + // Prepend ".;" to $PATH. + size_t pathlen = strlen(path_env); + char *path = memcpy(xmallocz(pathlen + 3), "." ENV_SEPSTR, 2); + memcpy(path + 2, path_env, pathlen); +#else + char *path = xstrdup(path_env); +#endif + + size_t buf_len = STRLEN(name) + strlen(path) + 2; #ifdef WIN32 const char *pathext = os_getenv("PATHEXT"); if (!pathext) { pathext = ".com;.exe;.bat;.cmd"; } - - buf_len += STRLEN(pathext); + buf_len += strlen(pathext); #endif - char_u *buf = xmalloc(buf_len); + char *buf = xmalloc(buf_len); // Walk through all entries in $PATH to check if "name" exists there and // is an executable file. + char *p = path; + bool rv = false; for (;; ) { - const char *e = xstrchrnul(path, ENV_SEPCHAR); - - // Glue together the given directory from $PATH with name and save into - // buf. - STRLCPY(buf, path, e - path + 1); - append_path((char *) buf, (const char *) name, buf_len); - - if (is_executable(buf)) { - // Check if the caller asked for a copy of the path. - if (abspath != NULL) { - *abspath = save_absolute_path(buf); - } + char *e = xstrchrnul(p, ENV_SEPCHAR); - xfree(buf); - - return true; - } + // Combine the $PATH segment with `name`. + STRLCPY(buf, p, e - p + 1); + append_path(buf, (char *)name, buf_len); #ifdef WIN32 - // Try appending file extensions from $PATHEXT to the name. - char *buf_end = xstrchrnul((char *)buf, '\0'); - for (const char *ext = pathext; *ext; ext++) { - // Skip the extension if there is no suffix after a '.'. - if (ext[0] == '.' && (ext[1] == '\0' || ext[1] == ';')) { - *ext++; - - continue; - } - - const char *ext_end = xstrchrnul(ext, ENV_SEPCHAR); - STRLCPY(buf_end, ext, ext_end - ext + 1); - - if (is_executable(buf)) { - // Check if the caller asked for a copy of the path. - if (abspath != NULL) { - *abspath = save_absolute_path(buf); - } - - xfree(buf); - - return true; + bool ok = is_executable(buf) || is_executable_ext(buf, pathext); +#else + bool ok = is_executable(buf); +#endif + if (ok) { + if (abspath != NULL) { // Caller asked for a copy of the path. + *abspath = save_abs_path((char_u *)buf); } - if (*ext_end != ENV_SEPCHAR) { - break; - } - ext = ext_end; + rv = true; + goto end; } -#endif if (*e != ENV_SEPCHAR) { // End of $PATH without finding any executable called name. - xfree(buf); - return false; + goto end; } - path = e + 1; + p = e + 1; } - // We should never get to this point. - assert(false); - return false; +end: + xfree(buf); + xfree(path); + return rv; } /// Opens or creates a file and returns a non-negative integer representing @@ -348,14 +406,45 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath) /// @param mode Permissions for the newly-created file (IGNORED if 'flags' is /// not `O_CREAT` or `O_TMPFILE`), subject to the current umask /// @return file descriptor, or libuv error code on failure -int os_open(const char* path, int flags, int mode) - FUNC_ATTR_NONNULL_ALL +int os_open(const char *path, int flags, int mode) { + if (path == NULL) { // uv_fs_open asserts on NULL. #7561 + return UV_EINVAL; + } int r; RUN_UV_FS_FUNC(r, uv_fs_open, path, flags, mode, NULL); return r; } +/// Sets file descriptor `fd` to close-on-exec. +// +// @return -1 if failed to set, 0 otherwise. +int os_set_cloexec(const int fd) +{ +#ifdef HAVE_FD_CLOEXEC + int e; + int fdflags = fcntl(fd, F_GETFD); + if (fdflags < 0) { + e = errno; + ELOG("Failed to get flags on descriptor %d: %s", fd, strerror(e)); + errno = e; + return -1; + } + if ((fdflags & FD_CLOEXEC) == 0 + && fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC) == -1) { + e = errno; + ELOG("Failed to set CLOEXEC on descriptor %d: %s", fd, strerror(e)); + errno = e; + return -1; + } + return 0; +#endif + + // No FD_CLOEXEC flag. On Windows, the file should have been opened with + // O_NOINHERIT anyway. + return -1; +} + /// Close a file /// /// @return 0 or libuv error code on failure. @@ -366,6 +455,29 @@ int os_close(const int fd) return r; } +/// Duplicate file descriptor +/// +/// @param[in] fd File descriptor to duplicate. +/// +/// @return New file descriptor or libuv error code (< 0). +int os_dup(const int fd) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + int ret; +os_dup_dup: + ret = dup(fd); + if (ret < 0) { + const int error = os_translate_sys_error(errno); + errno = 0; + if (error == UV_EINTR) { + goto os_dup_dup; + } else { + return error; + } + } + return ret; +} + /// Read from a file /// /// Handles EINTR and ENOMEM, but not other errors. @@ -375,10 +487,11 @@ int os_close(const int fd) /// 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. +/// @param[in] non_blocking Do not restart syscall if EAGAIN was encountered. /// /// @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) +ptrdiff_t os_read(const int fd, bool *const ret_eof, char *const ret_buf, + const size_t size, const bool non_blocking) FUNC_ATTR_WARN_UNUSED_RESULT { *ret_eof = false; @@ -389,23 +502,18 @@ ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf, size_t read_bytes = 0; bool did_try_to_free = false; while (read_bytes != size) { + assert(size >= read_bytes); const ptrdiff_t cur_read_bytes = read(fd, ret_buf + read_bytes, - size - read_bytes); + IO_COUNT(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 + const int error = os_translate_sys_error(errno); errno = 0; - if (error == UV_EINTR || error == UV_EAGAIN) { + if (non_blocking && error == UV_EAGAIN) { + break; + } else if (error == UV_EINTR || error == UV_EAGAIN) { continue; } else if (error == UV_ENOMEM && !did_try_to_free) { try_to_free_memory(); @@ -435,7 +543,11 @@ ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf, /// 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) +/// @param[in] non_blocking Do not restart syscall if EAGAIN was encountered. +/// +/// @return Number of bytes read or libuv error code (< 0). +ptrdiff_t os_readv(const int fd, bool *const ret_eof, struct iovec *iov, + size_t iov_size, const bool non_blocking) FUNC_ATTR_NONNULL_ALL { *ret_eof = false; @@ -449,7 +561,7 @@ ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size) } 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) { + if (cur_read_bytes == 0) { *ret_eof = true; } if (cur_read_bytes > 0) { @@ -466,16 +578,11 @@ ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size) } } } 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 + const int error = os_translate_sys_error(errno); errno = 0; - if (error == UV_EINTR || error == UV_EAGAIN) { + if (non_blocking && error == UV_EAGAIN) { + break; + } else if (error == UV_EINTR || error == UV_EAGAIN) { continue; } else if (error == UV_ENOMEM && !did_try_to_free) { try_to_free_memory(); @@ -495,9 +602,11 @@ ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size) /// @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. +/// @param[in] non_blocking Do not restart syscall if EAGAIN was encountered. /// /// @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) +ptrdiff_t os_write(const int fd, const char *const buf, const size_t size, + const bool non_blocking) FUNC_ATTR_WARN_UNUSED_RESULT { if (buf == NULL) { @@ -506,23 +615,18 @@ ptrdiff_t os_write(const int fd, const char *const buf, const size_t size) } size_t written_bytes = 0; while (written_bytes != size) { + assert(size >= written_bytes); const ptrdiff_t cur_written_bytes = write(fd, buf + written_bytes, - size - written_bytes); + IO_COUNT(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 + const int error = os_translate_sys_error(errno); errno = 0; - if (error == UV_EINTR || error == UV_EAGAIN) { + if (non_blocking && error == UV_EAGAIN) { + break; + } else if (error == UV_EINTR || error == UV_EAGAIN) { continue; } else { return error; @@ -544,15 +648,19 @@ int os_fsync(int fd) { int r; RUN_UV_FS_FUNC(r, uv_fs_fsync, fd, NULL); + g_stats.fsync++; return r; } /// Get stat information for a file. /// -/// @return libuv return code. +/// @return libuv return code, or -errno static int os_stat(const char *name, uv_stat_t *statbuf) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ARG(2) { + if (!name) { + return UV_EINVAL; + } uv_fs_t request; int result = uv_fs_stat(&fs_loop, &request, name, NULL); *statbuf = request.statbuf; @@ -563,11 +671,10 @@ static int os_stat(const char *name, uv_stat_t *statbuf) /// Get the file permissions for a given file. /// /// @return libuv error code on error. -int32_t os_getperm(const char_u *name) - FUNC_ATTR_NONNULL_ALL +int32_t os_getperm(const char *name) { uv_stat_t statbuf; - int stat_result = os_stat((char *)name, &statbuf); + int stat_result = os_stat(name, &statbuf); if (stat_result == kLibuvSuccess) { return (int32_t)statbuf.st_mode; } else { @@ -578,11 +685,11 @@ int32_t os_getperm(const char_u *name) /// Set the permission of a file. /// /// @return `OK` for success, `FAIL` for failure. -int os_setperm(const char_u *name, int perm) +int os_setperm(const char *const name, int perm) FUNC_ATTR_NONNULL_ALL { int r; - RUN_UV_FS_FUNC(r, uv_fs_chmod, (const char *)name, perm, NULL); + RUN_UV_FS_FUNC(r, uv_fs_chmod, name, perm, NULL); return (r == kLibuvSuccess ? OK : FAIL); } @@ -603,7 +710,6 @@ int os_fchown(int fd, uv_uid_t owner, uv_gid_t group) /// /// @return `true` if `path` exists bool os_path_exists(const char_u *path) - FUNC_ATTR_NONNULL_ALL { uv_stat_t statbuf; return os_stat((char *)path, &statbuf) == kLibuvSuccess; @@ -793,7 +899,7 @@ int os_remove(const char *path) /// @param[out] file_info Pointer to a FileInfo to put the information in. /// @return `true` on success, `false` for failure. bool os_fileinfo(const char *path, FileInfo *file_info) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ARG(2) { return os_stat(path, &(file_info->stat)) == kLibuvSuccess; } @@ -804,8 +910,11 @@ bool os_fileinfo(const char *path, FileInfo *file_info) /// @param[out] file_info Pointer to a FileInfo to put the information in. /// @return `true` on success, `false` for failure. bool os_fileinfo_link(const char *path, FileInfo *file_info) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ARG(2) { + if (path == NULL) { + return false; + } uv_fs_t request; int result = uv_fs_lstat(&fs_loop, &request, path, NULL); file_info->stat = request.statbuf; @@ -923,10 +1032,223 @@ bool os_fileid_equal(const FileID *file_id_1, const FileID *file_id_2) /// @param file_info Pointer to a `FileInfo` /// @return `true` if the `FileID` and the `FileInfo` represent te same file. bool os_fileid_equal_fileinfo(const FileID *file_id, - const FileInfo *file_info) + const FileInfo *file_info) FUNC_ATTR_NONNULL_ALL { return file_id->inode == file_info->stat.st_ino && file_id->device_id == file_info->stat.st_dev; } +#ifdef WIN32 +# include <shlobj.h> + +/// When "fname" is the name of a shortcut (*.lnk) resolve the file it points +/// to and return that name in allocated memory. +/// Otherwise NULL is returned. +char *os_resolve_shortcut(const char *fname) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC +{ + HRESULT hr; + IPersistFile *ppf = NULL; + OLECHAR wsz[MAX_PATH]; + char *rfname = NULL; + IShellLinkW *pslw = NULL; + WIN32_FIND_DATAW ffdw; + + // Check if the file name ends in ".lnk". Avoid calling CoCreateInstance(), + // it's quite slow. + if (fname == NULL) { + return rfname; + } + const size_t len = strlen(fname); + if (len <= 4 || STRNICMP(fname + len - 4, ".lnk", 4) != 0) { + return rfname; + } + + CoInitialize(NULL); + + // create a link manager object and request its interface + hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + &IID_IShellLinkW, (void **)&pslw); + if (hr == S_OK) { + wchar_t *p; + const int conversion_result = utf8_to_utf16(fname, &p); + if (conversion_result != 0) { + EMSG2("utf8_to_utf16 failed: %d", conversion_result); + } + + if (p != NULL) { + // Get a pointer to the IPersistFile interface. + hr = pslw->lpVtbl->QueryInterface( + pslw, &IID_IPersistFile, (void **)&ppf); + if (hr != S_OK) { + goto shortcut_errorw; + } + + // "load" the name and resolve the link + hr = ppf->lpVtbl->Load(ppf, p, STGM_READ); + if (hr != S_OK) { + goto shortcut_errorw; + } + +# if 0 // This makes Vim wait a long time if the target does not exist. + hr = pslw->lpVtbl->Resolve(pslw, NULL, SLR_NO_UI); + if (hr != S_OK) { + goto shortcut_errorw; + } +# endif + + // Get the path to the link target. + ZeroMemory(wsz, MAX_PATH * sizeof(wchar_t)); + hr = pslw->lpVtbl->GetPath(pslw, wsz, MAX_PATH, &ffdw, 0); + if (hr == S_OK && wsz[0] != NUL) { + const int conversion_result = utf16_to_utf8(wsz, &rfname); + if (conversion_result != 0) { + EMSG2("utf16_to_utf8 failed: %s", uv_strerror(conversion_result)); + } + } + +shortcut_errorw: + xfree(p); + goto shortcut_end; + } + } + +shortcut_end: + // Release all interface pointers (both belong to the same object) + if (ppf != NULL) { + ppf->lpVtbl->Release(ppf); + } + if (pslw != NULL) { + pslw->lpVtbl->Release(pslw); + } + + CoUninitialize(); + return rfname; +} + +#endif + +int os_translate_sys_error(int sys_errno) +{ +#ifdef HAVE_UV_TRANSLATE_SYS_ERROR + return uv_translate_sys_error(sys_errno); +#elif defined(WIN32) + // TODO(equalsraf): libuv does not yet expose uv_translate_sys_error() + // in its public API, include a version here until it can be used. + // See https://github.com/libuv/libuv/issues/79 +# ifndef ERROR_SYMLINK_NOT_SUPPORTED +# define ERROR_SYMLINK_NOT_SUPPORTED 1464 +# endif + + if (sys_errno <= 0) { + return sys_errno; // If < 0 then it's already a libuv error + } + + switch (sys_errno) { + case ERROR_NOACCESS: return UV_EACCES; + case WSAEACCES: return UV_EACCES; + case ERROR_ADDRESS_ALREADY_ASSOCIATED: return UV_EADDRINUSE; + case WSAEADDRINUSE: return UV_EADDRINUSE; + case WSAEADDRNOTAVAIL: return UV_EADDRNOTAVAIL; + case WSAEAFNOSUPPORT: return UV_EAFNOSUPPORT; + case WSAEWOULDBLOCK: return UV_EAGAIN; + case WSAEALREADY: return UV_EALREADY; + case ERROR_INVALID_FLAGS: return UV_EBADF; + case ERROR_INVALID_HANDLE: return UV_EBADF; + case ERROR_LOCK_VIOLATION: return UV_EBUSY; + case ERROR_PIPE_BUSY: return UV_EBUSY; + case ERROR_SHARING_VIOLATION: return UV_EBUSY; + case ERROR_OPERATION_ABORTED: return UV_ECANCELED; + case WSAEINTR: return UV_ECANCELED; + case ERROR_NO_UNICODE_TRANSLATION: return UV_ECHARSET; + case ERROR_CONNECTION_ABORTED: return UV_ECONNABORTED; + case WSAECONNABORTED: return UV_ECONNABORTED; + case ERROR_CONNECTION_REFUSED: return UV_ECONNREFUSED; + case WSAECONNREFUSED: return UV_ECONNREFUSED; + case ERROR_NETNAME_DELETED: return UV_ECONNRESET; + case WSAECONNRESET: return UV_ECONNRESET; + case ERROR_ALREADY_EXISTS: return UV_EEXIST; + case ERROR_FILE_EXISTS: return UV_EEXIST; + case ERROR_BUFFER_OVERFLOW: return UV_EFAULT; + case WSAEFAULT: return UV_EFAULT; + case ERROR_HOST_UNREACHABLE: return UV_EHOSTUNREACH; + case WSAEHOSTUNREACH: return UV_EHOSTUNREACH; + case ERROR_INSUFFICIENT_BUFFER: return UV_EINVAL; + case ERROR_INVALID_DATA: return UV_EINVAL; + case ERROR_INVALID_PARAMETER: return UV_EINVAL; + case ERROR_SYMLINK_NOT_SUPPORTED: return UV_EINVAL; + case WSAEINVAL: return UV_EINVAL; + case WSAEPFNOSUPPORT: return UV_EINVAL; + case WSAESOCKTNOSUPPORT: return UV_EINVAL; + case ERROR_BEGINNING_OF_MEDIA: return UV_EIO; + case ERROR_BUS_RESET: return UV_EIO; + case ERROR_CRC: return UV_EIO; + case ERROR_DEVICE_DOOR_OPEN: return UV_EIO; + case ERROR_DEVICE_REQUIRES_CLEANING: return UV_EIO; + case ERROR_DISK_CORRUPT: return UV_EIO; + case ERROR_EOM_OVERFLOW: return UV_EIO; + case ERROR_FILEMARK_DETECTED: return UV_EIO; + case ERROR_GEN_FAILURE: return UV_EIO; + case ERROR_INVALID_BLOCK_LENGTH: return UV_EIO; + case ERROR_IO_DEVICE: return UV_EIO; + case ERROR_NO_DATA_DETECTED: return UV_EIO; + case ERROR_NO_SIGNAL_SENT: return UV_EIO; + case ERROR_OPEN_FAILED: return UV_EIO; + case ERROR_SETMARK_DETECTED: return UV_EIO; + case ERROR_SIGNAL_REFUSED: return UV_EIO; + case WSAEISCONN: return UV_EISCONN; + case ERROR_CANT_RESOLVE_FILENAME: return UV_ELOOP; + case ERROR_TOO_MANY_OPEN_FILES: return UV_EMFILE; + case WSAEMFILE: return UV_EMFILE; + case WSAEMSGSIZE: return UV_EMSGSIZE; + case ERROR_FILENAME_EXCED_RANGE: return UV_ENAMETOOLONG; + case ERROR_NETWORK_UNREACHABLE: return UV_ENETUNREACH; + case WSAENETUNREACH: return UV_ENETUNREACH; + case WSAENOBUFS: return UV_ENOBUFS; + case ERROR_BAD_PATHNAME: return UV_ENOENT; + case ERROR_DIRECTORY: return UV_ENOENT; + case ERROR_FILE_NOT_FOUND: return UV_ENOENT; + case ERROR_INVALID_NAME: return UV_ENOENT; + case ERROR_INVALID_DRIVE: return UV_ENOENT; + case ERROR_INVALID_REPARSE_DATA: return UV_ENOENT; + case ERROR_MOD_NOT_FOUND: return UV_ENOENT; + case ERROR_PATH_NOT_FOUND: return UV_ENOENT; + case WSAHOST_NOT_FOUND: return UV_ENOENT; + case WSANO_DATA: return UV_ENOENT; + case ERROR_NOT_ENOUGH_MEMORY: return UV_ENOMEM; + case ERROR_OUTOFMEMORY: return UV_ENOMEM; + case ERROR_CANNOT_MAKE: return UV_ENOSPC; + case ERROR_DISK_FULL: return UV_ENOSPC; + case ERROR_EA_TABLE_FULL: return UV_ENOSPC; + case ERROR_END_OF_MEDIA: return UV_ENOSPC; + case ERROR_HANDLE_DISK_FULL: return UV_ENOSPC; + case ERROR_NOT_CONNECTED: return UV_ENOTCONN; + case WSAENOTCONN: return UV_ENOTCONN; + case ERROR_DIR_NOT_EMPTY: return UV_ENOTEMPTY; + case WSAENOTSOCK: return UV_ENOTSOCK; + case ERROR_NOT_SUPPORTED: return UV_ENOTSUP; + case ERROR_BROKEN_PIPE: return UV_EOF; + case ERROR_ACCESS_DENIED: return UV_EPERM; + case ERROR_PRIVILEGE_NOT_HELD: return UV_EPERM; + case ERROR_BAD_PIPE: return UV_EPIPE; + case ERROR_NO_DATA: return UV_EPIPE; + case ERROR_PIPE_NOT_CONNECTED: return UV_EPIPE; + case WSAESHUTDOWN: return UV_EPIPE; + case WSAEPROTONOSUPPORT: return UV_EPROTONOSUPPORT; + case ERROR_WRITE_PROTECT: return UV_EROFS; + case ERROR_SEM_TIMEOUT: return UV_ETIMEDOUT; + case WSAETIMEDOUT: return UV_ETIMEDOUT; + case ERROR_NOT_SAME_DEVICE: return UV_EXDEV; + case ERROR_INVALID_FUNCTION: return UV_EISDIR; + case ERROR_META_EXPANSION_TOO_LONG: return UV_E2BIG; + default: return UV_UNKNOWN; + } +#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"); + return error; +#endif +} |