diff options
Diffstat (limited to 'src/nvim/os/fs.c')
-rw-r--r-- | src/nvim/os/fs.c | 197 |
1 files changed, 170 insertions, 27 deletions
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 05f0f53c63..49a74cf0d1 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -2,6 +2,7 @@ #include <stdbool.h> #include <assert.h> +#include <fcntl.h> #include "nvim/os/os.h" #include "nvim/os/os_defs.h" @@ -59,6 +60,23 @@ int os_dirname(char_u *buf, size_t len) return OK; } +/// 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) + FUNC_ATTR_NONNULL_ALL +{ + uv_fs_t request; + if (uv_fs_lstat(&fs_loop, &request, (char *)name, NULL) != kLibuvSuccess) { + return false; + } + if (S_ISLNK(request.statbuf.st_mode)) { + return false; + } else { + return S_ISDIR(request.statbuf.st_mode); + } +} + /// Check if the given path is a directory or not. /// /// @return `true` if `fname` is a directory. @@ -77,10 +95,76 @@ bool os_isdir(const char_u *name) return true; } +/// 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) +{ +#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 + + uv_stat_t statbuf; + if (os_stat(name, &statbuf) == 0) { + return NODE_NORMAL; + } + +#ifndef WIN32 + // libuv does not handle BLK and DIR in uv_handle_type. + // Related: https://github.com/joyent/libuv/pull/1421 + if (S_ISREG(statbuf.st_mode) || S_ISDIR(statbuf.st_mode)) { + return NODE_NORMAL; + } + if (S_ISBLK(statbuf.st_mode)) { // block device isn't writable + return NODE_OTHER; + } +#endif + + // 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 + case UV_UNKNOWN_HANDLE: + default: +#ifdef WIN32 + nodetype = NODE_NORMAL; +#else + nodetype = NODE_WRITABLE; // Everything else is writable? +#endif + break; + } + + close(fd); + return nodetype; +} + /// Checks if the given path represents an executable file. /// -/// @param[in] name The name of the executable. +/// @param[in] name Name of the executable. /// @param[out] abspath Path of the executable, if found and not `NULL`. +/// @param[in] use_path If 'false', only check if "name" is executable /// /// @return `true` if `name` is executable and /// - can be found in $PATH, @@ -88,14 +172,18 @@ bool os_isdir(const char_u *name) /// - is absolute. /// /// @return `false` otherwise. -bool os_can_exe(const char_u *name, char_u **abspath) +bool os_can_exe(const char_u *name, char_u **abspath, bool use_path) FUNC_ATTR_NONNULL_ARG(1) { - // If it's an absolute or relative path don't need to use $PATH. - if (path_is_absolute_path(name) || - (name[0] == '.' && (name[1] == '/' || - (name[1] == '.' && name[2] == '/')))) { - if (is_executable(name)) { + // 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)) { if (abspath != NULL) { *abspath = save_absolute_path(name); } @@ -140,24 +228,33 @@ 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 = getenv("PATH"); - // PATH environment variable does not exist or is empty. - if (path == NULL || *path == NUL) { + const char *path = os_getenv("PATH"); + if (path == NULL) { return false; } 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); +#endif + char_u *buf = xmalloc(buf_len); // Walk through all entries in $PATH to check if "name" exists there and // is an executable file. for (;; ) { - const char *e = xstrchrnul(path, ':'); + 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, (int)buf_len); + append_path((char *) buf, (const char *) name, buf_len); if (is_executable(buf)) { // Check if the caller asked for a copy of the path. @@ -170,7 +267,39 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath) return true; } - if (*e != ':') { +#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; + } + + if (*ext_end != ENV_SEPCHAR) { + break; + } + ext = ext_end; + } +#endif + + if (*e != ENV_SEPCHAR) { // End of $PATH without finding any executable called name. xfree(buf); return false; @@ -186,13 +315,13 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath) /// Opens or creates a file and returns a non-negative integer representing /// the lowest-numbered unused file descriptor, for use in subsequent system -/// calls (read, write, lseek, fcntl, etc.). If the operation fails, `-errno` -/// is returned, and no file is created or modified. +/// calls (read, write, lseek, fcntl, etc.). If the operation fails, a libuv +/// error code is returned, and no file is created or modified. /// /// @param flags Bitwise OR of flags defined in <fcntl.h> /// @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 negative `errno` on failure +/// @return file descriptor, or libuv error code on failure int os_open(const char* path, int flags, int mode) FUNC_ATTR_NONNULL_ALL { @@ -203,30 +332,44 @@ int os_open(const char* path, int flags, int mode) return r; } +/// Flushes file modifications to disk. +/// +/// @param fd the file descriptor of the file to flush to disk. +/// +/// @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); + return r; +} + /// Get stat information for a file. /// -/// @return OK on success, FAIL if a failure occurred. -static bool os_stat(const char *name, uv_stat_t *statbuf) +/// @return libuv return code. +static int os_stat(const char *name, uv_stat_t *statbuf) FUNC_ATTR_NONNULL_ALL { uv_fs_t request; int result = uv_fs_stat(&fs_loop, &request, name, NULL); *statbuf = request.statbuf; uv_fs_req_cleanup(&request); - return (result == kLibuvSuccess); + return result; } /// Get the file permissions for a given file. /// -/// @return `-1` when `name` doesn't exist. +/// @return libuv error code on error. int32_t os_getperm(const char_u *name) FUNC_ATTR_NONNULL_ALL { uv_stat_t statbuf; - if (os_stat((char *)name, &statbuf)) { + int stat_result = os_stat((char *)name, &statbuf); + if (stat_result == kLibuvSuccess) { return (int32_t)statbuf.st_mode; } else { - return -1; + return stat_result; } } @@ -271,7 +414,7 @@ bool os_file_exists(const char_u *name) FUNC_ATTR_NONNULL_ALL { uv_stat_t statbuf; - return os_stat((char *)name, &statbuf); + return os_stat((char *)name, &statbuf) == kLibuvSuccess; } /// Check if a file is readable. @@ -323,7 +466,7 @@ int os_rename(const char_u *path, const char_u *new_path) /// Make a directory. /// -/// @return `0` for success, -errno for failure. +/// @return `0` for success, libuv error code for failure. int os_mkdir(const char *path, int32_t mode) FUNC_ATTR_NONNULL_ALL { @@ -343,7 +486,7 @@ int os_mkdir(const char *path, int32_t mode) /// failed to create. I.e. it will contain dir or any /// of the higher level directories. /// -/// @return `0` for success, -errno for failure. +/// @return `0` for success, libuv error code for failure. int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_dir) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT @@ -471,7 +614,7 @@ int os_remove(const char *path) bool os_fileinfo(const char *path, FileInfo *file_info) FUNC_ATTR_NONNULL_ALL { - return os_stat(path, &(file_info->stat)); + return os_stat(path, &(file_info->stat)) == kLibuvSuccess; } /// Get the file information for a given path without following links @@ -573,7 +716,7 @@ bool os_fileid(const char *path, FileID *file_id) FUNC_ATTR_NONNULL_ALL { uv_stat_t statbuf; - if (os_stat(path, &statbuf)) { + if (os_stat(path, &statbuf) == kLibuvSuccess) { file_id->inode = statbuf.st_ino; file_id->device_id = statbuf.st_dev; return true; |