neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

fs.c (40297B)


      1 // fs.c -- filesystem access
      2 #include <assert.h>
      3 #include <errno.h>
      4 #include <fcntl.h>
      5 #include <stdbool.h>
      6 #include <stddef.h>
      7 #include <stdint.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <sys/stat.h>
     12 #include <sys/types.h>
     13 #include <uv.h>
     14 
     15 #ifdef MSWIN
     16 # include <io.h>
     17 # include <shlobj.h>
     18 #endif
     19 
     20 #include "auto/config.h"
     21 #include "nvim/os/fs.h"
     22 #include "nvim/os/os_defs.h"
     23 
     24 #if defined(HAVE_ACL)
     25 # ifdef HAVE_SYS_ACL_H
     26 #  include <sys/acl.h>
     27 # endif
     28 # ifdef HAVE_SYS_ACCESS_H
     29 #  include <sys/access.h>
     30 # endif
     31 #endif
     32 
     33 #ifdef HAVE_XATTR
     34 # include <sys/xattr.h>
     35 #endif
     36 
     37 #include "nvim/api/private/helpers.h"
     38 #include "nvim/ascii_defs.h"
     39 #include "nvim/errors.h"
     40 #include "nvim/gettext_defs.h"
     41 #include "nvim/globals.h"
     42 #include "nvim/log.h"
     43 #include "nvim/macros_defs.h"
     44 #include "nvim/memory.h"
     45 #include "nvim/message.h"
     46 #include "nvim/option_vars.h"
     47 #include "nvim/os/os.h"
     48 #include "nvim/path.h"
     49 #include "nvim/types_defs.h"
     50 #include "nvim/ui.h"
     51 #include "nvim/vim_defs.h"
     52 
     53 #ifdef HAVE_SYS_UIO_H
     54 # include <sys/uio.h>
     55 #endif
     56 
     57 #ifdef MSWIN
     58 # include "nvim/mbyte.h"
     59 # include "nvim/option.h"
     60 # include "nvim/os/os_win_console.h"
     61 # include "nvim/strings.h"
     62 #endif
     63 
     64 #include "os/fs.c.generated.h"
     65 
     66 #ifdef HAVE_XATTR
     67 static const char e_xattr_erange[]
     68  = N_("E1506: Buffer too small to copy xattr value or key");
     69 static const char e_xattr_e2big[]
     70  = N_("E1508: Size of the extended attribute value is larger than the maximum size allowed");
     71 static const char e_xattr_other[]
     72  = N_("E1509: Error occurred when reading or writing extended attribute");
     73 #endif
     74 
     75 #define RUN_UV_FS_FUNC(ret, func, ...) \
     76  do { \
     77    uv_fs_t req; \
     78    ret = func(NULL, &req, __VA_ARGS__); \
     79    uv_fs_req_cleanup(&req); \
     80  } while (0)
     81 
     82 // Many fs functions from libuv return that value on success.
     83 static const int kLibuvSuccess = 0;
     84 
     85 /// Changes the current directory to `path`.
     86 ///
     87 /// @return 0 on success, or negative error code.
     88 int os_chdir(const char *path)
     89  FUNC_ATTR_NONNULL_ALL
     90 {
     91  if (p_verbose >= 5) {
     92    verbose_enter();
     93    smsg(0, "chdir(%s)", path);
     94    verbose_leave();
     95  }
     96  int err = uv_chdir(path);
     97  if (err == 0) {
     98    ui_call_chdir(cstr_as_string(path));
     99  }
    100  return err;
    101 }
    102 
    103 /// Get the name of current directory.
    104 ///
    105 /// @param buf Buffer to store the directory name.
    106 /// @param len Length of `buf`.
    107 /// @return `OK` for success, `FAIL` for failure.
    108 int os_dirname(char *buf, size_t len)
    109  FUNC_ATTR_NONNULL_ALL
    110 {
    111  int error_number;
    112  if ((error_number = uv_cwd(buf, &len)) != kLibuvSuccess) {
    113    xstrlcpy(buf, uv_strerror(error_number), len);
    114    return FAIL;
    115  }
    116  return OK;
    117 }
    118 
    119 /// Check if the given path is a directory and not a symlink to a directory.
    120 /// @return `true` if `name` is a directory and NOT a symlink to a directory.
    121 ///         `false` if `name` is not a directory or if an error occurred.
    122 bool os_isrealdir(const char *name)
    123  FUNC_ATTR_NONNULL_ALL
    124 {
    125  uv_fs_t request;
    126  if (uv_fs_lstat(NULL, &request, name, NULL) != kLibuvSuccess) {
    127    return false;
    128  }
    129  if (S_ISLNK(request.statbuf.st_mode)) {
    130    return false;
    131  }
    132  return S_ISDIR(request.statbuf.st_mode);
    133 }
    134 
    135 /// Check if the given path exists and is a directory.
    136 ///
    137 /// @return `true` if `name` is a directory.
    138 bool os_isdir(const char *name)
    139  FUNC_ATTR_NONNULL_ALL
    140 {
    141  int32_t mode = os_getperm(name);
    142  if (mode < 0) {
    143    return false;
    144  }
    145 
    146  return S_ISDIR(mode);
    147 }
    148 
    149 /// Check what `name` is:
    150 /// @return NODE_NORMAL: file or directory (or doesn't exist)
    151 ///         NODE_WRITABLE: writable device, socket, fifo, etc.
    152 ///         NODE_OTHER: non-writable things
    153 int os_nodetype(const char *name)
    154  FUNC_ATTR_NONNULL_ALL
    155 {
    156 #ifndef MSWIN  // Unix
    157  uv_stat_t statbuf;
    158  if (0 != os_stat(name, &statbuf)) {
    159    return NODE_NORMAL;  // File doesn't exist.
    160  }
    161  // uv_handle_type does not distinguish BLK and DIR.
    162  //    Related: https://github.com/joyent/libuv/pull/1421
    163  if (S_ISREG(statbuf.st_mode) || S_ISDIR(statbuf.st_mode)) {
    164    return NODE_NORMAL;
    165  }
    166  if (S_ISBLK(statbuf.st_mode)) {  // block device isn't writable
    167    return NODE_OTHER;
    168  }
    169  // Everything else is writable?
    170  // buf_write() expects NODE_WRITABLE for char device /dev/stderr.
    171  return NODE_WRITABLE;
    172 #else  // Windows
    173  // Edge case from Vim os_win32.c:
    174  // We can't open a file with a name "\\.\con" or "\\.\prn", trying to read
    175  // from it later will cause Vim to hang. Thus return NODE_WRITABLE here.
    176  if (strncmp(name, "\\\\.\\", 4) == 0) {
    177    return NODE_WRITABLE;
    178  }
    179 
    180  // Vim os_win32.c:mch_nodetype does (since 7.4.015):
    181  //    wn = enc_to_utf16(name, NULL);
    182  //    hFile = CreatFile(wn, ...)
    183  // to get a HANDLE. Whereas libuv just calls _get_osfhandle() on the fd we
    184  // give it. But uv_fs_open later calls fs__capture_path which does a similar
    185  // utf8-to-utf16 dance and saves us the hassle.
    186 
    187  // macOS: os_open(/dev/stderr) would return UV_EACCES.
    188  int fd = os_open(name, O_RDONLY
    189 # ifdef O_NONBLOCK
    190                   | O_NONBLOCK
    191 # endif
    192                   , 0);
    193  if (fd < 0) {  // open() failed.
    194    return NODE_NORMAL;
    195  }
    196  int guess = uv_guess_handle(fd);
    197  if (close(fd) == -1) {
    198    ELOG("close(%d) failed. name='%s'", fd, name);
    199  }
    200 
    201  switch (guess) {
    202  case UV_TTY:          // FILE_TYPE_CHAR
    203    return NODE_WRITABLE;
    204  case UV_FILE:         // FILE_TYPE_DISK
    205    return NODE_NORMAL;
    206  case UV_NAMED_PIPE:   // not handled explicitly in Vim os_win32.c
    207  case UV_UDP:          // unix only
    208  case UV_TCP:          // unix only
    209  case UV_UNKNOWN_HANDLE:
    210  default:
    211    return NODE_OTHER;  // Vim os_win32.c default
    212  }
    213 #endif
    214 }
    215 
    216 /// Gets the absolute path of the currently running executable.
    217 /// May fail if procfs is missing. #6734
    218 /// @see path_exepath
    219 ///
    220 /// @param[out] buffer Full path to the executable.
    221 /// @param[in]  size   Size of `buffer`.
    222 ///
    223 /// @return 0 on success, or libuv error code.
    224 int os_exepath(char *buffer, size_t *size)
    225  FUNC_ATTR_NONNULL_ALL
    226 {
    227  return uv_exepath(buffer, size);
    228 }
    229 
    230 /// Checks if the file `name` is executable.
    231 ///
    232 /// @param[in]  name     Filename to check.
    233 /// @param[out,allocated] abspath  Returns resolved exe path, if not NULL.
    234 /// @param[in] use_path  Also search $PATH.
    235 ///
    236 /// @return true if `name` is executable and
    237 ///   - can be found in $PATH,
    238 ///   - is relative to current dir or
    239 ///   - is absolute.
    240 ///
    241 /// @return `false` otherwise.
    242 bool os_can_exe(const char *name, char **abspath, bool use_path)
    243  FUNC_ATTR_NONNULL_ARG(1)
    244 {
    245  if (!use_path || gettail_dir(name) != name) {
    246 #ifdef MSWIN
    247    return is_executable_ext(name, abspath);
    248 #else
    249    // Must have path separator, cannot execute files in the current directory.
    250    return ((use_path || gettail_dir(name) != name)
    251            && is_executable(name, abspath));
    252 #endif
    253    return false;
    254  }
    255 
    256  return is_executable_in_path(name, abspath);
    257 }
    258 
    259 /// Returns true if `name` is an executable file.
    260 ///
    261 /// @param[in]            name     Filename to check.
    262 /// @param[out,allocated] abspath  Returns full exe path, if not NULL.
    263 static bool is_executable(const char *name, char **abspath)
    264  FUNC_ATTR_NONNULL_ARG(1)
    265 {
    266  int32_t mode = os_getperm(name);
    267 
    268  if (mode < 0) {
    269    return false;
    270  }
    271 
    272 #ifdef MSWIN
    273  // Windows does not have exec bit; just check if the file exists and is not
    274  // a directory.
    275  const bool ok = S_ISREG(mode);
    276 #else
    277  int r = -1;
    278  if (S_ISREG(mode)) {
    279    RUN_UV_FS_FUNC(r, uv_fs_access, name, X_OK, NULL);
    280  }
    281  const bool ok = (r == 0);
    282 #endif
    283  if (ok && abspath != NULL) {
    284    *abspath = save_abs_path(name);
    285  }
    286  return ok;
    287 }
    288 
    289 #ifdef MSWIN
    290 /// Checks if file `name` is executable under any of these conditions:
    291 /// - extension is in $PATHEXT and `name` is executable
    292 /// - result of any $PATHEXT extension appended to `name` is executable
    293 static bool is_executable_ext(const char *name, char **abspath)
    294  FUNC_ATTR_NONNULL_ARG(1)
    295 {
    296  const bool is_unix_shell = strstr(path_tail(p_sh), "powershell") == NULL
    297                             && strstr(path_tail(p_sh), "pwsh") == NULL
    298                             && strstr(path_tail(p_sh), "sh") != NULL;
    299  char *nameext = strrchr(name, '.');
    300  size_t nameext_len = nameext ? strlen(nameext) : 0;
    301  xstrlcpy(os_buf, name, sizeof(os_buf));
    302  char *buf_end = xstrchrnul(os_buf, NUL);
    303  const char *pathext = os_getenv_noalloc("PATHEXT");
    304  if (!pathext) {
    305    pathext = ".com;.exe;.bat;.cmd";
    306  }
    307  const char *ext = pathext;
    308  while (*ext) {
    309    // If $PATHEXT itself contains dot:
    310    if (ext[0] == '.' && (ext[1] == NUL || ext[1] == ENV_SEPCHAR)) {
    311      if (is_executable(name, abspath)) {
    312        return true;
    313      }
    314      // Skip it.
    315      ext++;
    316      if (*ext) {
    317        ext++;
    318      }
    319      continue;
    320    }
    321 
    322    const char *ext_end = ext;
    323    size_t ext_len =
    324      copy_option_part((char **)&ext_end, buf_end,
    325                       sizeof(os_buf) - (size_t)(buf_end - os_buf), ENV_SEPSTR);
    326    if (ext_len != 0) {
    327      bool in_pathext = nameext_len == ext_len
    328                        && 0 == mb_strnicmp(nameext, ext, ext_len);
    329 
    330      if (((in_pathext || is_unix_shell) && is_executable(name, abspath))
    331          || is_executable(os_buf, abspath)) {
    332        return true;
    333      }
    334    }
    335    ext = ext_end;
    336  }
    337  return false;
    338 }
    339 #else
    340 # define is_executable_ext is_executable
    341 #endif
    342 
    343 /// Checks if a file is in `$PATH` and is executable.
    344 ///
    345 /// @param[in]  name  Filename to check.
    346 /// @param[out] abspath  Returns resolved executable path, if not NULL.
    347 ///
    348 /// @return `true` if `name` is an executable inside `$PATH`.
    349 static bool is_executable_in_path(const char *name, char **abspath)
    350  FUNC_ATTR_NONNULL_ARG(1)
    351 {
    352  char *path_env = os_getenv("PATH");
    353  if (path_env == NULL) {
    354    return false;
    355  }
    356 
    357 #ifdef MSWIN
    358  char *path = NULL;
    359  if (!os_env_exists("NoDefaultCurrentDirectoryInExePath", false)
    360      && strstr(path_tail(p_sh), "cmd.exe") != NULL) {
    361    // Prepend ".;" to $PATH.
    362    size_t pathlen = strlen(path_env);
    363    path = xmallocz(pathlen + 2);
    364    memcpy(path, "." ENV_SEPSTR, 2);
    365    memcpy(path + 2, path_env, pathlen);
    366  } else {
    367    path = xstrdup(path_env);
    368  }
    369 #else
    370  char *path = xstrdup(path_env);
    371 #endif
    372 
    373  const size_t bufsize = strlen(name) + strlen(path) + 2;
    374  char *buf = xmalloc(bufsize);
    375 
    376  // Walk through all entries in $PATH to check if "name" exists there and
    377  // is an executable file.
    378  char *p = path;
    379  bool rv = false;
    380  while (true) {
    381    char *e = xstrchrnul(p, ENV_SEPCHAR);
    382 
    383    // Combine the $PATH segment with `name`.
    384    xmemcpyz(buf, p, (size_t)(e - p));
    385    (void)append_path(buf, name, bufsize);
    386 
    387    if (is_executable_ext(buf, abspath)) {
    388      rv = true;
    389      goto end;
    390    }
    391 
    392    if (*e != ENV_SEPCHAR) {
    393      // End of $PATH without finding any executable called name.
    394      goto end;
    395    }
    396 
    397    p = e + 1;
    398  }
    399 
    400 end:
    401  xfree(buf);
    402  xfree(path);
    403  xfree(path_env);
    404  return rv;
    405 }
    406 
    407 /// Opens or creates a file and returns a non-negative integer representing
    408 /// the lowest-numbered unused file descriptor, for use in subsequent system
    409 /// calls (read, write, lseek, fcntl, etc.). If the operation fails, a libuv
    410 /// error code is returned, and no file is created or modified.
    411 ///
    412 /// @param path Filename
    413 /// @param flags Bitwise OR of flags defined in <fcntl.h>
    414 /// @param mode Permissions for the newly-created file (IGNORED if 'flags' is
    415 ///        not `O_CREAT` or `O_TMPFILE`), subject to the current umask
    416 /// @return file descriptor, or negative error code on failure
    417 int os_open(const char *path, int flags, int mode)
    418 {
    419  if (path == NULL) {  // uv_fs_open asserts on NULL. #7561
    420    return UV_EINVAL;
    421  }
    422  int r;
    423  RUN_UV_FS_FUNC(r, uv_fs_open, path, flags, mode, NULL);
    424  return r;
    425 }
    426 
    427 /// Compatibility wrapper conforming to fopen(3).
    428 ///
    429 /// Windows: works with UTF-16 filepaths by delegating to libuv (os_open).
    430 ///
    431 /// Future: remove this, migrate callers to os/fileio.c ?
    432 ///         But file_open_fd does not support O_RDWR yet.
    433 ///
    434 /// @param path  Filename
    435 /// @param flags  String flags, one of { r w a r+ w+ a+ rb wb ab }
    436 /// @return FILE pointer, or NULL on error.
    437 FILE *os_fopen(const char *path, const char *flags)
    438 {
    439  assert(flags != NULL && strlen(flags) > 0 && strlen(flags) <= 2);
    440  int iflags = 0;
    441  // Per table in fopen(3) manpage.
    442  if (flags[1] == NUL || flags[1] == 'b') {
    443    switch (flags[0]) {
    444    case 'r':
    445      iflags = O_RDONLY;
    446      break;
    447    case 'w':
    448      iflags = O_WRONLY | O_CREAT | O_TRUNC;
    449      break;
    450    case 'a':
    451      iflags = O_WRONLY | O_CREAT | O_APPEND;
    452      break;
    453    default:
    454      abort();
    455    }
    456 #ifdef MSWIN
    457    if (flags[1] == 'b') {
    458      iflags |= O_BINARY;
    459    }
    460 #endif
    461  } else {
    462    // char 0 must be one of ('r','w','a').
    463    // char 1 is always '+' ('b' is handled above).
    464    assert(flags[1] == '+');
    465    switch (flags[0]) {
    466    case 'r':
    467      iflags = O_RDWR;
    468      break;
    469    case 'w':
    470      iflags = O_RDWR | O_CREAT | O_TRUNC;
    471      break;
    472    case 'a':
    473      iflags = O_RDWR | O_CREAT | O_APPEND;
    474      break;
    475    default:
    476      abort();
    477    }
    478  }
    479  // Per fopen(3) manpage: default to 0666, it will be umask-adjusted.
    480  int fd = os_open(path, iflags, 0666);
    481  if (fd < 0) {
    482    return NULL;
    483  }
    484  return fdopen(fd, flags);
    485 }
    486 
    487 /// Sets file descriptor `fd` to close-on-exec (Unix) or non-inheritable (Windows).
    488 ///
    489 /// @return -1 if failed to set, 0 otherwise.
    490 int os_set_cloexec(const int fd)
    491 {
    492 #ifdef HAVE_FD_CLOEXEC
    493  int e;
    494  int fdflags = fcntl(fd, F_GETFD);
    495  if (fdflags < 0) {
    496    e = errno;
    497    ELOG("Failed to get flags on descriptor %d: %s", fd, strerror(e));
    498    errno = e;
    499    return -1;
    500  }
    501  if ((fdflags & FD_CLOEXEC) == 0
    502      && fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC) == -1) {
    503    e = errno;
    504    ELOG("Failed to set CLOEXEC on descriptor %d: %s", fd, strerror(e));
    505    errno = e;
    506    return -1;
    507  }
    508  return 0;
    509 #elif defined(MSWIN)
    510  HANDLE h = (HANDLE)_get_osfhandle(fd);
    511  if (h == INVALID_HANDLE_VALUE
    512      || !SetHandleInformation(h, HANDLE_FLAG_INHERIT, 0)) {
    513    return -1;
    514  }
    515  return 0;
    516 #else
    517  return -1;
    518 #endif
    519 }
    520 
    521 /// Close a file
    522 ///
    523 /// @return 0 or libuv error code on failure.
    524 int os_close(const int fd)
    525 {
    526  int r;
    527  RUN_UV_FS_FUNC(r, uv_fs_close, fd, NULL);
    528  return r;
    529 }
    530 
    531 /// Duplicate file descriptor
    532 ///
    533 /// @param[in]  fd  File descriptor to duplicate.
    534 ///
    535 /// @return New file descriptor or libuv error code (< 0).
    536 int os_dup(const int fd)
    537  FUNC_ATTR_WARN_UNUSED_RESULT
    538 {
    539  int ret;
    540 os_dup_dup:
    541  ret = dup(fd);
    542  if (ret < 0) {
    543    const int error = os_translate_sys_error(errno);
    544    errno = 0;
    545    if (error == UV_EINTR) {
    546      goto os_dup_dup;
    547    } else {
    548      return error;
    549    }
    550  }
    551  return ret;
    552 }
    553 
    554 /// Open the file descriptor for stdin.
    555 int os_open_stdin_fd(void)
    556 {
    557  int stdin_dup_fd;
    558  if (stdin_fd > 0) {
    559    stdin_dup_fd = stdin_fd;
    560  } else {
    561    stdin_dup_fd = os_dup(STDIN_FILENO);
    562 #ifdef MSWIN
    563    // Replace the original stdin with the console input handle.
    564    os_replace_stdin_to_conin();
    565 #endif
    566  }
    567  return stdin_dup_fd;
    568 }
    569 
    570 /// Read from a file
    571 ///
    572 /// Handles EINTR, but not other errors.
    573 ///
    574 /// @param[in]  fd  File descriptor to read from.
    575 /// @param[out]  ret_eof  Is set to true if EOF was encountered, otherwise set
    576 ///                       to false. Initial value is ignored.
    577 /// @param[out]  ret_buf  Buffer to write to. May be NULL if size is zero.
    578 /// @param[in]  size  Amount of bytes to read.
    579 /// @param[in]  non_blocking  Do not restart syscall if EAGAIN was encountered.
    580 ///
    581 /// @return Number of bytes read or libuv error code (< 0).
    582 ptrdiff_t os_read(const int fd, bool *const ret_eof, char *const ret_buf, const size_t size,
    583                  const bool non_blocking)
    584  FUNC_ATTR_WARN_UNUSED_RESULT
    585 {
    586  *ret_eof = false;
    587  if (ret_buf == NULL) {
    588    assert(size == 0);
    589    return 0;
    590  }
    591  size_t read_bytes = 0;
    592  while (read_bytes != size) {
    593    assert(size >= read_bytes);
    594    const ptrdiff_t cur_read_bytes = read(fd, ret_buf + read_bytes,
    595                                          IO_COUNT(size - read_bytes));
    596    if (cur_read_bytes > 0) {
    597      read_bytes += (size_t)cur_read_bytes;
    598    }
    599    if (cur_read_bytes < 0) {
    600      const int error = os_translate_sys_error(errno);
    601      errno = 0;
    602      if (non_blocking && error == UV_EAGAIN) {
    603        break;
    604      } else if (error == UV_EINTR || error == UV_EAGAIN) {
    605        continue;
    606      } else {
    607        return (ptrdiff_t)error;
    608      }
    609    }
    610    if (cur_read_bytes == 0) {
    611      *ret_eof = true;
    612      break;
    613    }
    614  }
    615  return (ptrdiff_t)read_bytes;
    616 }
    617 
    618 #ifdef HAVE_READV
    619 /// Read from a file to multiple buffers at once
    620 ///
    621 /// Wrapper for readv().
    622 ///
    623 /// @param[in]  fd  File descriptor to read from.
    624 /// @param[out]  ret_eof  Is set to true if EOF was encountered, otherwise set
    625 ///                       to false. Initial value is ignored.
    626 /// @param[out]  iov  Description of buffers to write to. Note: this description
    627 ///                   may change, it is incorrect to use data it points to after
    628 ///                   os_readv().
    629 /// @param[in]  iov_size  Number of buffers in iov.
    630 /// @param[in]  non_blocking  Do not restart syscall if EAGAIN was encountered.
    631 ///
    632 /// @return Number of bytes read or libuv error code (< 0).
    633 ptrdiff_t os_readv(const int fd, bool *const ret_eof, struct iovec *iov, size_t iov_size,
    634                   const bool non_blocking)
    635  FUNC_ATTR_NONNULL_ALL
    636 {
    637  *ret_eof = false;
    638  size_t read_bytes = 0;
    639  size_t toread = 0;
    640  for (size_t i = 0; i < iov_size; i++) {
    641    // Overflow, trying to read too much data
    642    assert(toread <= SIZE_MAX - iov[i].iov_len);
    643    toread += iov[i].iov_len;
    644  }
    645  while (read_bytes < toread && iov_size && !*ret_eof) {
    646    ptrdiff_t cur_read_bytes = readv(fd, iov, (int)iov_size);
    647    if (cur_read_bytes == 0) {
    648      *ret_eof = true;
    649    }
    650    if (cur_read_bytes > 0) {
    651      read_bytes += (size_t)cur_read_bytes;
    652      while (iov_size && cur_read_bytes) {
    653        if (cur_read_bytes < (ptrdiff_t)iov->iov_len) {
    654          iov->iov_len -= (size_t)cur_read_bytes;
    655          iov->iov_base = (char *)iov->iov_base + cur_read_bytes;
    656          cur_read_bytes = 0;
    657        } else {
    658          cur_read_bytes -= (ptrdiff_t)iov->iov_len;
    659          iov_size--;
    660          iov++;
    661        }
    662      }
    663    } else if (cur_read_bytes < 0) {
    664      const int error = os_translate_sys_error(errno);
    665      errno = 0;
    666      if (non_blocking && error == UV_EAGAIN) {
    667        break;
    668      } else if (error == UV_EINTR || error == UV_EAGAIN) {
    669        continue;
    670      } else {
    671        return (ptrdiff_t)error;
    672      }
    673    }
    674  }
    675  return (ptrdiff_t)read_bytes;
    676 }
    677 #endif  // HAVE_READV
    678 
    679 /// Write to a file
    680 ///
    681 /// @param[in]  fd  File descriptor to write to.
    682 /// @param[in]  buf  Data to write. May be NULL if size is zero.
    683 /// @param[in]  size  Amount of bytes to write.
    684 /// @param[in]  non_blocking  Do not restart syscall if EAGAIN was encountered.
    685 ///
    686 /// @return Number of bytes written or libuv error code (< 0).
    687 ptrdiff_t os_write(const int fd, const char *const buf, const size_t size, const bool non_blocking)
    688  FUNC_ATTR_WARN_UNUSED_RESULT
    689 {
    690  if (buf == NULL) {
    691    assert(size == 0);
    692    return 0;
    693  }
    694  size_t written_bytes = 0;
    695  while (written_bytes != size) {
    696    assert(size >= written_bytes);
    697    const ptrdiff_t cur_written_bytes = write(fd, buf + written_bytes,
    698                                              IO_COUNT(size - written_bytes));
    699    if (cur_written_bytes > 0) {
    700      written_bytes += (size_t)cur_written_bytes;
    701    }
    702    if (cur_written_bytes < 0) {
    703      const int error = os_translate_sys_error(errno);
    704      errno = 0;
    705      if (non_blocking && error == UV_EAGAIN) {
    706        break;
    707      } else if (error == UV_EINTR || error == UV_EAGAIN) {
    708        continue;
    709      } else {
    710        return error;
    711      }
    712    }
    713    if (cur_written_bytes == 0) {
    714      return UV_UNKNOWN;
    715    }
    716  }
    717  return (ptrdiff_t)written_bytes;
    718 }
    719 
    720 /// Copies a file from `path` to `new_path`.
    721 ///
    722 /// @see http://docs.libuv.org/en/v1.x/fs.html#c.uv_fs_copyfile
    723 ///
    724 /// @param path Path of file to be copied
    725 /// @param path_new Path of new file
    726 /// @param flags Bitwise OR of flags defined in <uv.h>
    727 /// @return 0 on success, or libuv error code on failure.
    728 int os_copy(const char *path, const char *new_path, int flags)
    729 {
    730  int r;
    731  RUN_UV_FS_FUNC(r, uv_fs_copyfile, path, new_path, flags, NULL);
    732  return r;
    733 }
    734 
    735 /// Flushes file modifications to disk.
    736 ///
    737 /// @param fd the file descriptor of the file to flush to disk.
    738 ///
    739 /// @return 0 on success, or libuv error code on failure.
    740 int os_fsync(int fd)
    741 {
    742  int r;
    743  RUN_UV_FS_FUNC(r, uv_fs_fsync, fd, NULL);
    744  g_stats.fsync++;
    745  return r;
    746 }
    747 
    748 /// Get stat information for a file.
    749 ///
    750 /// @return libuv return code, or -errno
    751 static int os_stat(const char *name, uv_stat_t *statbuf)
    752  FUNC_ATTR_NONNULL_ARG(2)
    753 {
    754  if (!name) {
    755    return UV_EINVAL;
    756  }
    757  uv_fs_t request;
    758  int result = uv_fs_stat(NULL, &request, name, NULL);
    759  if (result == kLibuvSuccess) {
    760    *statbuf = request.statbuf;
    761  }
    762  uv_fs_req_cleanup(&request);
    763  return result;
    764 }
    765 
    766 /// Get the file permissions for a given file.
    767 ///
    768 /// @return libuv error code on error.
    769 int32_t os_getperm(const char *name)
    770 {
    771  uv_stat_t statbuf;
    772  int stat_result = os_stat(name, &statbuf);
    773  if (stat_result == kLibuvSuccess) {
    774    return (int32_t)statbuf.st_mode;
    775  }
    776  return stat_result;
    777 }
    778 
    779 /// Set the permission of a file.
    780 ///
    781 /// @return `OK` for success, `FAIL` for failure.
    782 int os_setperm(const char *const name, int perm)
    783  FUNC_ATTR_NONNULL_ALL
    784 {
    785  int r;
    786  RUN_UV_FS_FUNC(r, uv_fs_chmod, name, perm, NULL);
    787  return (r == kLibuvSuccess ? OK : FAIL);
    788 }
    789 
    790 #ifdef HAVE_XATTR
    791 /// Copy extended attributes from_file to to_file
    792 void os_copy_xattr(const char *from_file, const char *to_file)
    793 {
    794  if (from_file == NULL) {
    795    return;
    796  }
    797 
    798  // get the length of the extended attributes
    799  ssize_t size = listxattr((char *)from_file, NULL, 0);
    800  // not supported or no attributes to copy
    801  if (size <= 0) {
    802    return;
    803  }
    804  char *xattr_buf = xmalloc((size_t)size);
    805  size = listxattr(from_file, xattr_buf, (size_t)size);
    806  ssize_t tsize = size;
    807 
    808  errno = 0;
    809 
    810  ssize_t max_vallen = 0;
    811  char *val = NULL;
    812  const char *errmsg = NULL;
    813 
    814  for (int round = 0; round < 2; round++) {
    815    char *key = xattr_buf;
    816    if (round == 1) {
    817      size = tsize;
    818    }
    819 
    820    while (size > 0) {
    821      ssize_t vallen = getxattr(from_file, key, val, round ? (size_t)max_vallen : 0);
    822      // only set the attribute in the second round
    823      if (vallen >= 0 && round
    824          && setxattr(to_file, key, val, (size_t)vallen, 0) == 0) {
    825        //
    826      } else if (errno) {
    827        switch (errno) {
    828        case E2BIG:
    829          errmsg = e_xattr_e2big;
    830          goto error_exit;
    831        case ENOTSUP:
    832        case EACCES:
    833        case EPERM:
    834          break;
    835        case ERANGE:
    836          errmsg = e_xattr_erange;
    837          goto error_exit;
    838        default:
    839          errmsg = e_xattr_other;
    840          goto error_exit;
    841        }
    842      }
    843 
    844      if (round == 0 && vallen > max_vallen) {
    845        max_vallen = vallen;
    846      }
    847 
    848      // add one for terminating null
    849      ssize_t keylen = (ssize_t)strlen(key) + 1;
    850      size -= keylen;
    851      key += keylen;
    852    }
    853    if (round) {
    854      break;
    855    }
    856 
    857    val = xmalloc((size_t)max_vallen + 1);
    858  }
    859 error_exit:
    860  xfree(xattr_buf);
    861  xfree(val);
    862 
    863  if (errmsg != NULL) {
    864    emsg(_(errmsg));
    865  }
    866 }
    867 #endif
    868 
    869 // Return a pointer to the ACL of file "fname" in allocated memory.
    870 // Return NULL if the ACL is not available for whatever reason.
    871 vim_acl_T os_get_acl(const char *fname)
    872 {
    873  vim_acl_T ret = NULL;
    874  return ret;
    875 }
    876 
    877 // Set the ACL of file "fname" to "acl" (unless it's NULL).
    878 void os_set_acl(const char *fname, vim_acl_T aclent)
    879 {
    880  if (aclent == NULL) {
    881    return;
    882  }
    883 }
    884 
    885 void os_free_acl(vim_acl_T aclent)
    886 {
    887  if (aclent == NULL) {
    888    return;
    889  }
    890 }
    891 
    892 #ifdef UNIX
    893 /// Checks if the current user owns a file.
    894 ///
    895 /// Uses both uv_fs_stat() and uv_fs_lstat() via os_fileinfo() and
    896 /// os_fileinfo_link() respectively for extra security.
    897 bool os_file_owned(const char *fname)
    898  FUNC_ATTR_NONNULL_ALL
    899 {
    900  uid_t uid = getuid();
    901  FileInfo finfo;
    902  bool file_owned = os_fileinfo(fname, &finfo) && finfo.stat.st_uid == uid;
    903  bool link_owned = os_fileinfo_link(fname, &finfo) && finfo.stat.st_uid == uid;
    904  return file_owned && link_owned;
    905 }
    906 #else
    907 bool os_file_owned(const char *fname)
    908 {
    909  return true;  // TODO(justinmk): Windows. #8244
    910 }
    911 #endif
    912 
    913 /// Changes the owner and group of a file, like chown(2).
    914 ///
    915 /// @return 0 on success, or libuv error code on failure.
    916 ///
    917 /// @note If `owner` or `group` is -1, then that ID is not changed.
    918 int os_chown(const char *path, uv_uid_t owner, uv_gid_t group)
    919 {
    920  int r;
    921  RUN_UV_FS_FUNC(r, uv_fs_chown, path, owner, group, NULL);
    922  return r;
    923 }
    924 
    925 /// Changes the owner and group of the file referred to by the open file
    926 /// descriptor, like fchown(2).
    927 ///
    928 /// @return 0 on success, or libuv error code on failure.
    929 ///
    930 /// @note If `owner` or `group` is -1, then that ID is not changed.
    931 int os_fchown(int fd, uv_uid_t owner, uv_gid_t group)
    932 {
    933  int r;
    934  RUN_UV_FS_FUNC(r, uv_fs_fchown, fd, owner, group, NULL);
    935  return r;
    936 }
    937 
    938 /// Check if a path exists.
    939 ///
    940 /// @return `true` if `path` exists
    941 bool os_path_exists(const char *path)
    942 {
    943  uv_stat_t statbuf;
    944  return os_stat(path, &statbuf) == kLibuvSuccess;
    945 }
    946 
    947 /// Sets file access and modification times.
    948 ///
    949 /// @see POSIX utime(2)
    950 ///
    951 /// @param path   File path.
    952 /// @param atime  Last access time.
    953 /// @param mtime  Last modification time.
    954 ///
    955 /// @return 0 on success, or negative error code.
    956 int os_file_settime(const char *path, double atime, double mtime)
    957 {
    958  int r;
    959  RUN_UV_FS_FUNC(r, uv_fs_utime, path, atime, mtime, NULL);
    960  return r;
    961 }
    962 
    963 /// Check if a file is readable.
    964 ///
    965 /// @return true if `name` is readable, otherwise false.
    966 bool os_file_is_readable(const char *name)
    967  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
    968 {
    969  int r;
    970  RUN_UV_FS_FUNC(r, uv_fs_access, name, R_OK, NULL);
    971  return (r == 0);
    972 }
    973 
    974 /// Check if a file is writable.
    975 ///
    976 /// @return `0` if `name` is not writable,
    977 /// @return `1` if `name` is writable,
    978 /// @return `2` for a directory which we have rights to write into.
    979 int os_file_is_writable(const char *name)
    980  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
    981 {
    982  int r;
    983  RUN_UV_FS_FUNC(r, uv_fs_access, name, W_OK, NULL);
    984  if (r == 0) {
    985    return os_isdir(name) ? 2 : 1;
    986  }
    987  return 0;
    988 }
    989 
    990 /// Rename a file or directory.
    991 ///
    992 /// @return `OK` for success, `FAIL` for failure.
    993 int os_rename(const char *path, const char *new_path)
    994  FUNC_ATTR_NONNULL_ALL
    995 {
    996  int r;
    997  RUN_UV_FS_FUNC(r, uv_fs_rename, path, new_path, NULL);
    998  return (r == kLibuvSuccess ? OK : FAIL);
    999 }
   1000 
   1001 /// Make a directory.
   1002 ///
   1003 /// @return `0` for success, libuv error code for failure.
   1004 int os_mkdir(const char *path, int32_t mode)
   1005  FUNC_ATTR_NONNULL_ALL
   1006 {
   1007  int r;
   1008  RUN_UV_FS_FUNC(r, uv_fs_mkdir, path, mode, NULL);
   1009  return r;
   1010 }
   1011 
   1012 /// Make a directory, with higher levels when needed
   1013 ///
   1014 /// @param[in]  dir  Directory to create.
   1015 /// @param[in]  mode  Permissions for the newly-created directory.
   1016 /// @param[out]  failed_dir  If it failed to create directory, then this
   1017 ///                          argument is set to an allocated string containing
   1018 ///                          the name of the directory which os_mkdir_recurse
   1019 ///                          failed to create. I.e. it will contain dir or any
   1020 ///                          of the higher level directories.
   1021 /// @param[out]  created     Set to the full name of the first created directory.
   1022 ///                          It will be NULL until that happens.
   1023 ///
   1024 /// @return `0` for success, libuv error code for failure.
   1025 int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_dir,
   1026                     char **const created)
   1027  FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT
   1028 {
   1029  // Get end of directory name in "dir".
   1030  // We're done when it's "/" or "c:/".
   1031  const size_t dirlen = strlen(dir);
   1032  char *const curdir = xmemdupz(dir, dirlen);
   1033  char *const past_head = get_past_head(curdir);
   1034  char *e = curdir + dirlen;
   1035  const char *const real_end = e;
   1036  const char past_head_save = *past_head;
   1037  while (!os_isdir(curdir)) {
   1038    e = path_tail_with_sep(curdir);
   1039    if (e <= past_head) {
   1040      *past_head = NUL;
   1041      break;
   1042    }
   1043    *e = NUL;
   1044  }
   1045  while (e != real_end) {
   1046    if (e > past_head) {
   1047      *e = PATHSEP;
   1048    } else {
   1049      *past_head = past_head_save;
   1050    }
   1051    const size_t component_len = strlen(e);
   1052    e += component_len;
   1053    if (e == real_end
   1054        && memcnt(e - component_len, PATHSEP, component_len) == component_len) {
   1055      // Path ends with something like "////". Ignore this.
   1056      break;
   1057    }
   1058    int ret;
   1059    if ((ret = os_mkdir(curdir, mode)) != 0) {
   1060      *failed_dir = curdir;
   1061      return ret;
   1062    } else if (created != NULL && *created == NULL) {
   1063      *created = FullName_save(curdir, false);
   1064    }
   1065  }
   1066  xfree(curdir);
   1067  return 0;
   1068 }
   1069 
   1070 /// Create the parent directory of a file if it does not exist
   1071 ///
   1072 /// @param[in] fname Full path of the file name whose parent directories
   1073 ///                  we want to create
   1074 /// @param[in] mode  Permissions for the newly-created directory.
   1075 ///
   1076 /// @return `0` for success, libuv error code for failure.
   1077 int os_file_mkdir(char *fname, int32_t mode)
   1078  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
   1079 {
   1080  if (!dir_of_file_exists(fname)) {
   1081    char *tail = path_tail_with_sep(fname);
   1082    char *last_char = tail + strlen(tail) - 1;
   1083    if (vim_ispathsep(*last_char)) {
   1084      emsg(_(e_noname));
   1085      return -1;
   1086    }
   1087    char c = *tail;
   1088    *tail = NUL;
   1089    int r;
   1090    char *failed_dir;
   1091    if (((r = os_mkdir_recurse(fname, mode, &failed_dir, NULL)) < 0)) {
   1092      semsg(_(e_mkdir), failed_dir, os_strerror(r));
   1093      xfree(failed_dir);
   1094    }
   1095    *tail = c;
   1096    return r;
   1097  }
   1098  return 0;
   1099 }
   1100 
   1101 /// Create a unique temporary directory.
   1102 ///
   1103 /// @param[in] templ Template of the path to the directory with XXXXXX
   1104 ///                  which would be replaced by random chars.
   1105 /// @param[out] path Path to created directory for success, undefined for
   1106 ///                  failure.
   1107 /// @return `0` for success, non-zero for failure.
   1108 int os_mkdtemp(const char *templ, char *path)
   1109  FUNC_ATTR_NONNULL_ALL
   1110 {
   1111  uv_fs_t request;
   1112  int result = uv_fs_mkdtemp(NULL, &request, templ, NULL);
   1113  if (result == kLibuvSuccess) {
   1114    xstrlcpy(path, request.path, TEMP_FILE_PATH_MAXLEN);
   1115  }
   1116  uv_fs_req_cleanup(&request);
   1117  return result;
   1118 }
   1119 
   1120 /// Remove a directory.
   1121 ///
   1122 /// @return `0` for success, non-zero for failure.
   1123 int os_rmdir(const char *path)
   1124  FUNC_ATTR_NONNULL_ALL
   1125 {
   1126  int r;
   1127  RUN_UV_FS_FUNC(r, uv_fs_rmdir, path, NULL);
   1128  return r;
   1129 }
   1130 
   1131 /// Opens a directory.
   1132 /// @param[out] dir   The Directory object.
   1133 /// @param      path  Path to the directory.
   1134 /// @returns true if dir contains one or more items, false if not or an error
   1135 ///          occurred.
   1136 bool os_scandir(Directory *dir, const char *path)
   1137  FUNC_ATTR_NONNULL_ALL
   1138 {
   1139  int r = uv_fs_scandir(NULL, &dir->request, path, 0, NULL);
   1140  if (r < 0) {
   1141    os_closedir(dir);
   1142  }
   1143  return r >= 0;
   1144 }
   1145 
   1146 /// Increments the directory pointer.
   1147 /// @param dir  The Directory object.
   1148 /// @returns a pointer to the next path in `dir` or `NULL`.
   1149 const char *os_scandir_next(Directory *dir)
   1150  FUNC_ATTR_NONNULL_ALL
   1151 {
   1152  int err = uv_fs_scandir_next(&dir->request, &dir->ent);
   1153  return err != UV_EOF ? dir->ent.name : NULL;
   1154 }
   1155 
   1156 /// Frees memory associated with `os_scandir()`.
   1157 /// @param dir  The directory.
   1158 void os_closedir(Directory *dir)
   1159  FUNC_ATTR_NONNULL_ALL
   1160 {
   1161  uv_fs_req_cleanup(&dir->request);
   1162 }
   1163 
   1164 /// Remove a file.
   1165 ///
   1166 /// @return `0` for success, non-zero for failure.
   1167 int os_remove(const char *path)
   1168  FUNC_ATTR_NONNULL_ALL
   1169 {
   1170  int r;
   1171  RUN_UV_FS_FUNC(r, uv_fs_unlink, path, NULL);
   1172  return r;
   1173 }
   1174 
   1175 /// Get the file information for a given path
   1176 ///
   1177 /// @param path Path to the file.
   1178 /// @param[out] file_info Pointer to a FileInfo to put the information in.
   1179 /// @return `true` on success, `false` for failure.
   1180 bool os_fileinfo(const char *path, FileInfo *file_info)
   1181  FUNC_ATTR_NONNULL_ARG(2)
   1182 {
   1183  CLEAR_POINTER(file_info);
   1184  return os_stat(path, &(file_info->stat)) == kLibuvSuccess;
   1185 }
   1186 
   1187 /// Get the file information for a given path without following links
   1188 ///
   1189 /// @param path Path to the file.
   1190 /// @param[out] file_info Pointer to a FileInfo to put the information in.
   1191 /// @return `true` on success, `false` for failure.
   1192 bool os_fileinfo_link(const char *path, FileInfo *file_info)
   1193  FUNC_ATTR_NONNULL_ARG(2)
   1194 {
   1195  CLEAR_POINTER(file_info);
   1196  if (path == NULL) {
   1197    return false;
   1198  }
   1199  uv_fs_t request;
   1200  bool ok = uv_fs_lstat(NULL, &request, path, NULL) == kLibuvSuccess;
   1201  if (ok) {
   1202    file_info->stat = request.statbuf;
   1203  }
   1204  uv_fs_req_cleanup(&request);
   1205  return ok;
   1206 }
   1207 
   1208 /// Get the file information for a given file descriptor
   1209 ///
   1210 /// @param file_descriptor File descriptor of the file.
   1211 /// @param[out] file_info Pointer to a FileInfo to put the information in.
   1212 /// @return `true` on success, `false` for failure.
   1213 bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
   1214  FUNC_ATTR_NONNULL_ALL
   1215 {
   1216  uv_fs_t request;
   1217  CLEAR_POINTER(file_info);
   1218  bool ok = uv_fs_fstat(NULL,
   1219                        &request,
   1220                        file_descriptor,
   1221                        NULL) == kLibuvSuccess;
   1222  if (ok) {
   1223    file_info->stat = request.statbuf;
   1224  }
   1225  uv_fs_req_cleanup(&request);
   1226  return ok;
   1227 }
   1228 
   1229 /// Compare the inodes of two FileInfos
   1230 ///
   1231 /// @return `true` if the two FileInfos represent the same file.
   1232 bool os_fileinfo_id_equal(const FileInfo *file_info_1, const FileInfo *file_info_2)
   1233  FUNC_ATTR_NONNULL_ALL
   1234 {
   1235  return file_info_1->stat.st_ino == file_info_2->stat.st_ino
   1236         && file_info_1->stat.st_dev == file_info_2->stat.st_dev;
   1237 }
   1238 
   1239 /// Get the `FileID` of a `FileInfo`
   1240 ///
   1241 /// @param file_info Pointer to the `FileInfo`
   1242 /// @param[out] file_id Pointer to a `FileID`
   1243 void os_fileinfo_id(const FileInfo *file_info, FileID *file_id)
   1244  FUNC_ATTR_NONNULL_ALL
   1245 {
   1246  file_id->inode = file_info->stat.st_ino;
   1247  file_id->device_id = file_info->stat.st_dev;
   1248 }
   1249 
   1250 /// Get the inode of a `FileInfo`
   1251 ///
   1252 /// @deprecated Use `FileID` instead, this function is only needed in memline.c
   1253 /// @param file_info Pointer to the `FileInfo`
   1254 /// @return the inode number
   1255 uint64_t os_fileinfo_inode(const FileInfo *file_info)
   1256  FUNC_ATTR_NONNULL_ALL
   1257 {
   1258  return file_info->stat.st_ino;
   1259 }
   1260 
   1261 /// Get the size of a file from a `FileInfo`.
   1262 ///
   1263 /// @return filesize in bytes.
   1264 uint64_t os_fileinfo_size(const FileInfo *file_info)
   1265  FUNC_ATTR_NONNULL_ALL
   1266 {
   1267  return file_info->stat.st_size;
   1268 }
   1269 
   1270 /// Get the number of hardlinks from a `FileInfo`.
   1271 ///
   1272 /// @return number of hardlinks.
   1273 uint64_t os_fileinfo_hardlinks(const FileInfo *file_info)
   1274  FUNC_ATTR_NONNULL_ALL
   1275 {
   1276  return file_info->stat.st_nlink;
   1277 }
   1278 
   1279 /// Get the blocksize from a `FileInfo`.
   1280 ///
   1281 /// @return blocksize in bytes.
   1282 uint64_t os_fileinfo_blocksize(const FileInfo *file_info)
   1283  FUNC_ATTR_NONNULL_ALL
   1284 {
   1285  return file_info->stat.st_blksize;
   1286 }
   1287 
   1288 /// Get the `FileID` for a given path
   1289 ///
   1290 /// @param path Path to the file.
   1291 /// @param[out] file_info Pointer to a `FileID` to fill in.
   1292 /// @return `true` on success, `false` for failure.
   1293 bool os_fileid(const char *path, FileID *file_id)
   1294  FUNC_ATTR_NONNULL_ALL
   1295 {
   1296  uv_stat_t statbuf;
   1297  if (os_stat(path, &statbuf) == kLibuvSuccess) {
   1298    file_id->inode = statbuf.st_ino;
   1299    file_id->device_id = statbuf.st_dev;
   1300    return true;
   1301  }
   1302  return false;
   1303 }
   1304 
   1305 /// Check if two `FileID`s are equal
   1306 ///
   1307 /// @param file_id_1 Pointer to first `FileID`
   1308 /// @param file_id_2 Pointer to second `FileID`
   1309 /// @return `true` if the two `FileID`s represent te same file.
   1310 bool os_fileid_equal(const FileID *file_id_1, const FileID *file_id_2)
   1311  FUNC_ATTR_NONNULL_ALL
   1312 {
   1313  return file_id_1->inode == file_id_2->inode
   1314         && file_id_1->device_id == file_id_2->device_id;
   1315 }
   1316 
   1317 /// Check if a `FileID` is equal to a `FileInfo`
   1318 ///
   1319 /// @param file_id Pointer to a `FileID`
   1320 /// @param file_info Pointer to a `FileInfo`
   1321 /// @return `true` if the `FileID` and the `FileInfo` represent te same file.
   1322 bool os_fileid_equal_fileinfo(const FileID *file_id, const FileInfo *file_info)
   1323  FUNC_ATTR_NONNULL_ALL
   1324 {
   1325  return file_id->inode == file_info->stat.st_ino
   1326         && file_id->device_id == file_info->stat.st_dev;
   1327 }
   1328 
   1329 /// Return the canonicalized absolute pathname.
   1330 ///
   1331 /// @param[in] name Filename to be canonicalized.
   1332 /// @param[out] buf Buffer to store the canonicalized values.
   1333 ///                 If it is NULL, memory is allocated. In that case, the caller
   1334 ///                 should deallocate this buffer.
   1335 /// @param[in] len  The length of the buffer.
   1336 ///
   1337 /// @return pointer to the buf on success, or NULL.
   1338 char *os_realpath(const char *name, char *buf, size_t len)
   1339  FUNC_ATTR_NONNULL_ARG(1)
   1340 {
   1341  uv_fs_t request;
   1342  int result = uv_fs_realpath(NULL, &request, name, NULL);
   1343  if (result == kLibuvSuccess) {
   1344    if (buf == NULL) {
   1345      buf = xmalloc(len);
   1346    }
   1347    xstrlcpy(buf, request.ptr, len);
   1348  }
   1349  uv_fs_req_cleanup(&request);
   1350  return result == kLibuvSuccess ? buf : NULL;
   1351 }
   1352 
   1353 #ifdef MSWIN
   1354 /// When "fname" is the name of a shortcut (*.lnk) resolve the file it points
   1355 /// to and return that name in allocated memory.
   1356 /// Otherwise NULL is returned.
   1357 char *os_resolve_shortcut(const char *fname)
   1358  FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
   1359 {
   1360  HRESULT hr;
   1361  IPersistFile *ppf = NULL;
   1362  OLECHAR wsz[MAX_PATH];
   1363  char *rfname = NULL;
   1364  IShellLinkW *pslw = NULL;
   1365  WIN32_FIND_DATAW ffdw;
   1366 
   1367  // Check if the file name ends in ".lnk". Avoid calling CoCreateInstance(),
   1368  // it's quite slow.
   1369  if (fname == NULL) {
   1370    return rfname;
   1371  }
   1372  const size_t len = strlen(fname);
   1373  if (len <= 4 || STRNICMP(fname + len - 4, ".lnk", 4) != 0) {
   1374    return rfname;
   1375  }
   1376 
   1377  CoInitialize(NULL);
   1378 
   1379  // create a link manager object and request its interface
   1380  hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
   1381                        &IID_IShellLinkW, (void **)&pslw);
   1382  if (hr == S_OK) {
   1383    wchar_t *p;
   1384    const int r = utf8_to_utf16(fname, -1, &p);
   1385    if (r != 0) {
   1386      semsg("utf8_to_utf16 failed: %d", r);
   1387    } else if (p != NULL) {
   1388      // Get a pointer to the IPersistFile interface.
   1389      hr = pslw->lpVtbl->QueryInterface(pslw, &IID_IPersistFile, (void **)&ppf);
   1390      if (hr != S_OK) {
   1391        goto shortcut_errorw;
   1392      }
   1393 
   1394      // "load" the name and resolve the link
   1395      hr = ppf->lpVtbl->Load(ppf, p, STGM_READ);
   1396      if (hr != S_OK) {
   1397        goto shortcut_errorw;
   1398      }
   1399 
   1400 # if 0  // This makes Vim wait a long time if the target does not exist.
   1401      hr = pslw->lpVtbl->Resolve(pslw, NULL, SLR_NO_UI);
   1402      if (hr != S_OK) {
   1403        goto shortcut_errorw;
   1404      }
   1405 # endif
   1406 
   1407      // Get the path to the link target.
   1408      ZeroMemory(wsz, MAX_PATH * sizeof(wchar_t));
   1409      hr = pslw->lpVtbl->GetPath(pslw, wsz, MAX_PATH, &ffdw, 0);
   1410      if (hr == S_OK && wsz[0] != NUL) {
   1411        const int r2 = utf16_to_utf8(wsz, -1, &rfname);
   1412        if (r2 != 0) {
   1413          semsg("utf16_to_utf8 failed: %d", r2);
   1414        }
   1415      }
   1416 
   1417 shortcut_errorw:
   1418      xfree(p);
   1419      goto shortcut_end;
   1420    }
   1421  }
   1422 
   1423 shortcut_end:
   1424  // Release all interface pointers (both belong to the same object)
   1425  if (ppf != NULL) {
   1426    ppf->lpVtbl->Release(ppf);
   1427  }
   1428  if (pslw != NULL) {
   1429    pslw->lpVtbl->Release(pslw);
   1430  }
   1431 
   1432  CoUninitialize();
   1433  return rfname;
   1434 }
   1435 
   1436 # define IS_PATH_SEP(c) ((c) == L'\\' || (c) == L'/')
   1437 /// Returns true if the path contains a reparse point (junction or symbolic
   1438 /// link). Otherwise false in returned.
   1439 bool os_is_reparse_point_include(const char *path)
   1440 {
   1441  wchar_t *p, *q, *utf16_path;
   1442  wchar_t buf[MAX_PATH];
   1443  DWORD attr;
   1444  bool result = false;
   1445 
   1446  const int r = utf8_to_utf16(path, -1, &utf16_path);
   1447  if (r != 0) {
   1448    semsg("utf8_to_utf16 failed: %d", r);
   1449    return false;
   1450  }
   1451 
   1452  p = utf16_path;
   1453  if (isalpha((uint8_t)p[0]) && p[1] == L':' && IS_PATH_SEP(p[2])) {
   1454    p += 3;
   1455  } else if (IS_PATH_SEP(p[0]) && IS_PATH_SEP(p[1])) {
   1456    p += 2;
   1457  }
   1458 
   1459  while (*p != L'\0') {
   1460    q = wcspbrk(p, L"\\/");
   1461    if (q == NULL) {
   1462      p = q = utf16_path + wcslen(utf16_path);
   1463    } else {
   1464      p = q + 1;
   1465    }
   1466    if (q - utf16_path >= MAX_PATH) {
   1467      break;
   1468    }
   1469    wcsncpy(buf, utf16_path, (size_t)(q - utf16_path));
   1470    buf[q - utf16_path] = L'\0';
   1471    attr = GetFileAttributesW(buf);
   1472    if (attr != INVALID_FILE_ATTRIBUTES
   1473        && (attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
   1474      result = true;
   1475      break;
   1476    }
   1477  }
   1478  xfree(utf16_path);
   1479  return result;
   1480 }
   1481 #endif