diff options
| -rw-r--r-- | src/nvim/getchar.c | 94 | ||||
| -rw-r--r-- | src/nvim/getchar.h | 24 | ||||
| -rw-r--r-- | src/nvim/globals.h | 6 | ||||
| -rw-r--r-- | src/nvim/main.c | 40 | ||||
| -rw-r--r-- | src/nvim/message.c | 10 | ||||
| -rw-r--r-- | src/nvim/os/fileio.c | 45 | ||||
| -rw-r--r-- | src/nvim/os/fileio.h | 3 | ||||
| -rw-r--r-- | src/nvim/os/fs.c | 50 | ||||
| -rw-r--r-- | test/functional/core/main_spec.lua | 131 | ||||
| -rw-r--r-- | test/helpers.lua | 53 | ||||
| -rw-r--r-- | test/includes/pre/sys/stat.h | 4 | ||||
| -rw-r--r-- | test/unit/helpers.lua | 2 | ||||
| -rw-r--r-- | test/unit/os/fileio_spec.lua | 8 | ||||
| -rw-r--r-- | test/unit/os/fs_spec.lua | 22 | ||||
| -rw-r--r-- | test/unit/preprocess.lua | 58 | 
15 files changed, 398 insertions, 152 deletions
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 03929a58b6..98164b2653 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -48,8 +48,14 @@  #include "nvim/event/loop.h"  #include "nvim/os/input.h"  #include "nvim/os/os.h" +#include "nvim/os/fileio.h"  #include "nvim/api/private/handle.h" + +/// Index in scriptin +static int curscript = 0; +FileDescriptor *scriptin[NSCRIPT] = { NULL }; +  /*   * These buffers are used for storing:   * - stuffed characters: A command that is translated into another command. @@ -1243,10 +1249,13 @@ openscript (      ++curscript;    /* use NameBuff for expanded name */    expand_env(name, NameBuff, MAXPATHL); -  if ((scriptin[curscript] = mch_fopen((char *)NameBuff, READBIN)) == NULL) { -    EMSG2(_(e_notopen), name); -    if (curscript) -      --curscript; +  int error; +  if ((scriptin[curscript] = file_open_new(&error, (char *)NameBuff, +                                           kFileReadOnly, 0)) == NULL) { +    emsgf(_(e_notopen_2), name, os_strerror(error)); +    if (curscript) { +      curscript--; +    }      return;    }    save_typebuf(); @@ -1296,7 +1305,7 @@ static void closescript(void)    free_typebuf();    typebuf = saved_typebuf[curscript]; -  fclose(scriptin[curscript]); +  file_free(scriptin[curscript], false);    scriptin[curscript] = NULL;    if (curscript > 0)      --curscript; @@ -2336,9 +2345,8 @@ inchar (      int tb_change_cnt  )  { -  int len = 0;                      /* init for GCC */ -  int retesc = FALSE;               /* return ESC with gotint */ -  int script_char; +  int len = 0;  // Init for GCC. +  int retesc = false;  // Return ESC with gotint.    if (wait_time == -1L || wait_time > 100L) {      // flush output before waiting @@ -2356,45 +2364,38 @@ inchar (    }    undo_off = FALSE;                 /* restart undo now */ -  /* -   * Get a character from a script file if there is one. -   * If interrupted: Stop reading script files, close them all. -   */ -  script_char = -1; -  while (scriptin[curscript] != NULL && script_char < 0 -         && !ignore_script -         ) { - - -    if (got_int || (script_char = getc(scriptin[curscript])) < 0) { -      /* Reached EOF. -       * Careful: closescript() frees typebuf.tb_buf[] and buf[] may -       * point inside typebuf.tb_buf[].  Don't use buf[] after this! */ +  // Get a character from a script file if there is one. +  // If interrupted: Stop reading script files, close them all. +  ptrdiff_t read_size = -1; +  while (scriptin[curscript] != NULL && read_size <= 0 && !ignore_script) { +    char script_char; +    if (got_int +        || (read_size = file_read(scriptin[curscript], &script_char, 1)) != 1) { +      // Reached EOF or some error occurred. +      // Careful: closescript() frees typebuf.tb_buf[] and buf[] may +      // point inside typebuf.tb_buf[].  Don't use buf[] after this!        closescript(); -      /* -       * When reading script file is interrupted, return an ESC to get -       * back to normal mode. -       * Otherwise return -1, because typebuf.tb_buf[] has changed. -       */ -      if (got_int) -        retesc = TRUE; -      else +      // When reading script file is interrupted, return an ESC to get +      // back to normal mode. +      // Otherwise return -1, because typebuf.tb_buf[] has changed. +      if (got_int) { +        retesc = true; +      } else {          return -1; +      }      } else {        buf[0] = (char_u)script_char;        len = 1;      }    } -  if (script_char < 0) {        /* did not get a character from script */ -    /* -     * If we got an interrupt, skip all previously typed characters and -     * return TRUE if quit reading script file. -     * Stop reading typeahead when a single CTRL-C was read, -     * fill_input_buf() returns this when not able to read from stdin. -     * Don't use buf[] here, closescript() may have freed typebuf.tb_buf[] -     * and buf may be pointing inside typebuf.tb_buf[]. -     */ +  if (read_size <= 0) {  // Did not get a character from script. +    // If we got an interrupt, skip all previously typed characters and +    // return TRUE if quit reading script file. +    // Stop reading typeahead when a single CTRL-C was read, +    // fill_input_buf() returns this when not able to read from stdin. +    // Don't use buf[] here, closescript() may have freed typebuf.tb_buf[] +    // and buf may be pointing inside typebuf.tb_buf[].      if (got_int) {  #define DUM_LEN MAXMAPLEN * 3 + 3        char_u dum[DUM_LEN + 1]; @@ -2407,21 +2408,18 @@ inchar (        return retesc;      } -    /* -     * Always flush the output characters when getting input characters -     * from the user. -     */ +    // Always flush the output characters when getting input characters +    // from the user.      ui_flush(); -    /* -     * Fill up to a third of the buffer, because each character may be -     * tripled below. -     */ +    // Fill up to a third of the buffer, because each character may be +    // tripled below.      len = os_inchar(buf, maxlen / 3, (int)wait_time, tb_change_cnt);    } -  if (typebuf_changed(tb_change_cnt)) +  if (typebuf_changed(tb_change_cnt)) {      return 0; +  }    return fix_input_buffer(buf, len);  } diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index e634273e0d..38a2e75663 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -1,24 +1,30 @@  #ifndef NVIM_GETCHAR_H  #define NVIM_GETCHAR_H +#include "nvim/os/fileio.h"  #include "nvim/types.h"  #include "nvim/buffer_defs.h"  #include "nvim/ex_cmds_defs.h" -/// Values for "noremap" argument of ins_typebuf().  Also used for -/// map->m_noremap and menu->noremap[]. -/// @addtogroup REMAP_VALUES -/// @{ -#define REMAP_YES       0       ///< allow remapping -#define REMAP_NONE      -1      ///< no remapping -#define REMAP_SCRIPT    -2      ///< remap script-local mappings only -#define REMAP_SKIP      -3      ///< no remapping for first char -/// @} +/// Values for "noremap" argument of ins_typebuf() +/// +/// Also used for map->m_noremap and menu->noremap[]. +enum { +  REMAP_YES = 0,  ///< Allow remapping. +  REMAP_NONE = -1,  ///< No remapping. +  REMAP_SCRIPT = -2,  ///< Remap script-local mappings only. +  REMAP_SKIP = -3,  ///< No remapping for first char. +} RemapValues;  #define KEYLEN_PART_KEY -1      /* keylen value for incomplete key-code */  #define KEYLEN_PART_MAP -2      /* keylen value for incomplete mapping */  #define KEYLEN_REMOVED  9999    /* keylen value for removed sequence */ +/// Maximum number of streams to read script from +enum { NSCRIPT = 15 }; + +/// Streams to read script from +extern FileDescriptor *scriptin[NSCRIPT];  #ifdef INCLUDE_GENERATED_DECLARATIONS  # include "getchar.h.generated.h" diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 89d93310a6..56790bc89b 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -834,10 +834,7 @@ EXTERN int do_redraw INIT(= FALSE);         /* extra redraw once */  EXTERN int need_highlight_changed INIT(= true);  EXTERN char *used_shada_file INIT(= NULL);  // name of the ShaDa file to use -#define NSCRIPT 15 -EXTERN FILE     *scriptin[NSCRIPT];         /* streams to read script from */ -EXTERN int curscript INIT(= 0);             /* index in scriptin[] */ -EXTERN FILE     *scriptout INIT(= NULL);    /* stream to write script to */ +EXTERN FILE *scriptout INIT(= NULL);  ///< Stream to write script to.  // volatile because it is used in a signal handler.  EXTERN volatile int got_int INIT(= false);  // set to true when interrupt @@ -1085,6 +1082,7 @@ EXTERN char_u e_norange[] INIT(= N_("E481: No range allowed"));  EXTERN char_u e_noroom[] INIT(= N_("E36: Not enough room"));  EXTERN char_u e_notmp[] INIT(= N_("E483: Can't get temp file name"));  EXTERN char_u e_notopen[] INIT(= N_("E484: Can't open file %s")); +EXTERN char_u e_notopen_2[] INIT(= N_("E484: Can't open file %s: %s"));  EXTERN char_u e_notread[] INIT(= N_("E485: Can't read file %s"));  EXTERN char_u e_nowrtmsg[] INIT(= N_(          "E37: No write since last change (add ! to override)")); diff --git a/src/nvim/main.c b/src/nvim/main.c index b7a4caaab1..a4ed868af1 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -787,7 +787,8 @@ static void command_line_scan(mparm_T *parmp)              mch_exit(0);            } else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) {              FileDescriptor fp; -            const int fof_ret = file_open_fd(&fp, STDOUT_FILENO, true); +            const int fof_ret = file_open_fd(&fp, STDOUT_FILENO, +                                             kFileWriteOnly);              msgpack_packer *p = msgpack_packer_new(&fp, msgpack_file_write);              if (fof_ret != 0) { @@ -1085,17 +1086,36 @@ static void command_line_scan(mparm_T *parmp)            case 's':               /* "-s {scriptin}" read from script file */              if (scriptin[0] != NULL) {  scripterror: -              mch_errmsg(_("Attempt to open script file again: \"")); -              mch_errmsg(argv[-1]); -              mch_errmsg(" "); -              mch_errmsg(argv[0]); -              mch_errmsg("\"\n"); +              vim_snprintf((char *)IObuff, IOSIZE, +                           _("Attempt to open script file again: \"%s %s\"\n"), +                           argv[-1], argv[0]); +              mch_errmsg((const char *)IObuff);                mch_exit(2);              } -            if ((scriptin[0] = mch_fopen(argv[0], READBIN)) == NULL) { -              mch_errmsg(_("Cannot open for reading: \"")); -              mch_errmsg(argv[0]); -              mch_errmsg("\"\n"); +            int error; +            if (STRCMP(argv[0], "-") == 0) { +              const int stdin_dup_fd = os_dup(STDIN_FILENO); +#ifdef WIN32 +              // On Windows, replace the original stdin with the +              // console input handle. +              close(STDIN_FILENO); +              const HANDLE conin_handle = +                CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, +                           FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL, +                           OPEN_EXISTING, 0, (HANDLE)NULL); +              const int conin_fd = _open_osfhandle(conin_handle, _O_RDONLY); +              assert(conin_fd == STDIN_FILENO); +#endif +              FileDescriptor *const stdin_dup = file_open_fd_new( +                  &error, stdin_dup_fd, kFileReadOnly|kFileNonBlocking); +              assert(stdin_dup != NULL); +              scriptin[0] = stdin_dup; +            } else if ((scriptin[0] = file_open_new( +                &error, argv[0], kFileReadOnly|kFileNonBlocking, 0)) == NULL) { +              vim_snprintf((char *)IObuff, IOSIZE, +                           _("Cannot open for reading: \"%s\": %s\n"), +                           argv[0], os_strerror(error)); +              mch_errmsg((const char *)IObuff);                mch_exit(2);              }              save_typebuf(); diff --git a/src/nvim/message.c b/src/nvim/message.c index 04528629c7..3a3c9cb167 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -2351,10 +2351,9 @@ static int do_more_prompt(int typed_char)   * yet.  When stderr can't be used, collect error messages until the GUI has   * started and they can be displayed in a message box.   */ -void mch_errmsg(char *str) +void mch_errmsg(const char *const str) +  FUNC_ATTR_NONNULL_ALL  { -  int len; -  #ifdef UNIX    /* On Unix use stderr if it's a tty.     * When not going to start the GUI also use stderr. @@ -2368,14 +2367,13 @@ void mch_errmsg(char *str)    /* avoid a delay for a message that isn't there */    emsg_on_display = FALSE; -  len = (int)STRLEN(str) + 1; +  const size_t len = strlen(str) + 1;    if (error_ga.ga_data == NULL) {      ga_set_growsize(&error_ga, 80);      error_ga.ga_itemsize = 1;    }    ga_grow(&error_ga, len); -  memmove((char_u *)error_ga.ga_data + error_ga.ga_len, -      (char_u *)str, len); +  memmove(error_ga.ga_data + error_ga.ga_len, str, len);  #ifdef UNIX    /* remove CR characters, they are displayed */    { diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index a95adc86b6..ccf35fd57c 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -83,7 +83,7 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname,    if (fd < 0) {      return fd;    } -  return file_open_fd(ret_fp, fd, (wr == kTrue)); +  return file_open_fd(ret_fp, fd, flags);  }  /// Wrap file descriptor with FileDescriptor structure @@ -94,14 +94,23 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname,  /// @param[out]  ret_fp  Address where information needed for reading from or  ///                      writing to a file is saved  /// @param[in]  fd  File descriptor to wrap. -/// @param[in]  wr  True if fd is opened for writing only, false if it is read -///                 only. +/// @param[in]  flags  Flags, @see FileOpenFlags. Currently reading from and +///                    writing to the file at once is not supported, so either +///                    FILE_WRITE_ONLY or FILE_READ_ONLY is required.  ///  /// @return Error code (@see os_strerror()) or 0. Currently always returns 0. -int file_open_fd(FileDescriptor *const ret_fp, const int fd, const bool wr) +int file_open_fd(FileDescriptor *const ret_fp, const int fd, +                 const int flags)    FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT  { -  ret_fp->wr = wr; +  ret_fp->wr = !!(flags & (kFileCreate +                           |kFileCreateOnly +                           |kFileTruncate +                           |kFileAppend +                           |kFileWriteOnly)); +  ret_fp->non_blocking = !!(flags & kFileNonBlocking); +  // Non-blocking writes not supported currently. +  assert(!ret_fp->wr || !ret_fp->non_blocking);    ret_fp->fd = fd;    ret_fp->eof = false;    ret_fp->rv = rbuffer_new(kRWBufferSize); @@ -138,15 +147,17 @@ FileDescriptor *file_open_new(int *const error, const char *const fname,  ///  /// @param[out]  error  Error code, or 0 on success. @see os_strerror()  /// @param[in]  fd  File descriptor to wrap. -/// @param[in]  wr  True if fd is opened for writing only, false if it is read -///                 only. +/// @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_fd_new(int *const error, const int fd, const bool wr) +FileDescriptor *file_open_fd_new(int *const error, const int fd, +                                 const int flags)    FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT  {    FileDescriptor *const fp = xmalloc(sizeof(*fp)); -  if ((*error = file_open_fd(fp, fd, wr)) != 0) { +  if ((*error = file_open_fd(fp, fd, flags)) != 0) {      xfree(fp);      return NULL;    } @@ -244,7 +255,8 @@ static void file_rb_write_full_cb(RBuffer *const rv, FileDescriptor *const fp)      return;    }    const size_t read_bytes = rbuffer_read(rv, writebuf, kRWBufferSize); -  const ptrdiff_t wres = os_write(fp->fd, writebuf, read_bytes); +  const ptrdiff_t wres = os_write(fp->fd, writebuf, read_bytes, +                                  fp->non_blocking);    if (wres != (ptrdiff_t)read_bytes) {      if (wres >= 0) {        fp->_error = UV_EIO; @@ -270,6 +282,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,    char *buf = ret_buf;    size_t read_remaining = size;    RBuffer *const rv = fp->rv; +  bool called_read = false;    while (read_remaining) {      const size_t rv_size = rbuffer_size(rv);      if (rv_size > 0) { @@ -277,7 +290,9 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,        buf += rsize;        read_remaining -= rsize;      } -    if (fp->eof) { +    if (fp->eof +        // Allow only at most one os_read[v] call. +        || (called_read && fp->non_blocking)) {        break;      }      if (read_remaining) { @@ -294,7 +309,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,        };        assert(write_count == kRWBufferSize);        const ptrdiff_t r_ret = os_readv(fp->fd, &fp->eof, iov, -                                       ARRAY_SIZE(iov)); +                                       ARRAY_SIZE(iov), fp->non_blocking);        if (r_ret > 0) {          if (r_ret > (ptrdiff_t)read_remaining) {            rbuffer_produced(rv, (size_t)(r_ret - (ptrdiff_t)read_remaining)); @@ -310,7 +325,8 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,        if (read_remaining >= kRWBufferSize) {          // …otherwise leave RBuffer empty and populate only target buffer,          // because filtering information through rbuffer will be more syscalls. -        const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, buf, read_remaining); +        const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, buf, read_remaining, +                                        fp->non_blocking);          if (r_ret >= 0) {            read_remaining -= (size_t)r_ret;            return (ptrdiff_t)(size - read_remaining); @@ -321,7 +337,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,          size_t write_count;          const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof,                                          rbuffer_write_ptr(rv, &write_count), -                                        kRWBufferSize); +                                        kRWBufferSize, fp->non_blocking);          assert(write_count == kRWBufferSize);          if (r_ret > 0) {            rbuffer_produced(rv, (size_t)r_ret); @@ -330,6 +346,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf,          }        }  #endif +      called_read = true;      }    }    return (ptrdiff_t)(size - read_remaining); diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h index 0b55cc695f..7c53cd4f07 100644 --- a/src/nvim/os/fileio.h +++ b/src/nvim/os/fileio.h @@ -14,6 +14,7 @@ typedef struct {    RBuffer *rv;  ///< Read or write buffer.    bool wr;  ///< True if file is in write mode.    bool eof;  ///< True if end of file was encountered. +  bool non_blocking;  ///< True if EAGAIN should not restart syscalls.  } FileDescriptor;  /// file_open() flags @@ -32,6 +33,8 @@ typedef enum {                         ///< kFileCreateOnly.    kFileAppend = 64,  ///< Append to the file. Implies kFileWriteOnly. Cannot                       ///< be used with kFileCreateOnly. +  kFileNonBlocking = 128,  ///< Do not restart read() or write() syscall if +                           ///< EAGAIN was encountered.  } FileOpenFlags;  static inline bool file_eof(const FileDescriptor *const fp) diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index b7c2714296..0414794d01 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -436,6 +436,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. @@ -445,10 +468,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; @@ -468,7 +492,9 @@ ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf,      if (cur_read_bytes < 0) {        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(); @@ -498,7 +524,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; @@ -531,7 +561,9 @@ ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size)      } else if (cur_read_bytes < 0) {        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(); @@ -551,9 +583,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) { @@ -571,7 +605,9 @@ ptrdiff_t os_write(const int fd, const char *const buf, const size_t size)      if (cur_written_bytes < 0) {        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; diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua new file mode 100644 index 0000000000..2c4d110f0f --- /dev/null +++ b/test/functional/core/main_spec.lua @@ -0,0 +1,131 @@ +local lfs = require('lfs') +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local eq = helpers.eq +local feed = helpers.feed +local eval = helpers.eval +local clear = helpers.clear +local funcs = helpers.funcs +local nvim_prog = helpers.nvim_prog +local write_file = helpers.write_file + +local function nvim_prog_abs() +  -- system(['build/bin/nvim']) does not work for whatever reason. It needs to +  -- either be executable searched in $PATH or something starting with / or ./. +  if nvim_prog:match('[/\\]') then +    return funcs.fnamemodify(nvim_prog, ':p') +  else +    return nvim_prog +  end +end + +describe('Command-line option', function() +  describe('-s', function() +    local fname = 'Xtest-functional-core-main-s' +    local fname_2 = fname .. '.2' +    local nonexistent_fname = fname .. '.nonexistent' +    local dollar_fname = '$' .. fname +    before_each(function() +      clear() +      os.remove(fname) +      os.remove(dollar_fname) +    end) +    after_each(function() +      os.remove(fname) +      os.remove(dollar_fname) +    end) +    it('treats - as stdin', function() +      eq(nil, lfs.attributes(fname)) +      funcs.system( +        {nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless', +         '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', +         '-s', '-', fname}, +        {':call setline(1, "42")', ':wqall!', ''}) +      eq(0, eval('v:shell_error')) +      local attrs = lfs.attributes(fname) +      eq(#('42\n'), attrs.size) +    end) +    it('does not expand $VAR', function() +      eq(nil, lfs.attributes(fname)) +      eq(true, not not dollar_fname:find('%$%w+')) +      write_file(dollar_fname, ':call setline(1, "100500")\n:wqall!\n') +      funcs.system( +        {nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless', +         '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', +         '-s', dollar_fname, fname}) +      eq(0, eval('v:shell_error')) +      local attrs = lfs.attributes(fname) +      eq(#('100500\n'), attrs.size) +    end) +    it('does not crash after reading from stdin in non-headless mode', function() +      if helpers.pending_win32(pending) then return end +      local screen = Screen.new(40, 8) +      screen:attach() +      funcs.termopen({ +        nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', +         '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', +         '-s', '-' +      }) +      screen:expect([[ +        ^                                        | +        {1:~                                       }| +        {1:~                                       }| +        {1:~                                       }| +        {1:~                                       }| +        {2:[No Name]             0,0-1          All}| +                                                | +                                                | +      ]], { +        [1] = {foreground = 4210943, special = Screen.colors.Grey0}, +        [2] = {special = Screen.colors.Grey0, bold = true, reverse = true} +      }) +      feed('i:cq<CR><C-\\><C-n>') +      screen:expect([[ +        ^                                        | +        [Process exited 1]                      | +                                                | +                                                | +                                                | +                                                | +                                                | +                                                | +      ]]) +      --[=[ Example of incorrect output: +      screen:expect([[ +        ^nvim: /var/tmp/portage/dev-libs/libuv-1.| +        10.2/work/libuv-1.10.2/src/unix/core.c:5| +        19: uv__close: Assertion `fd > STDERR_FI| +        LENO' failed.                           | +                                                | +        [Process exited 6]                      | +                                                | +                                                | +      ]]) +      ]=] +    end) +    it('errors out when trying to use nonexistent file with -s', function() +      eq( +        'Cannot open for reading: "'..nonexistent_fname..'": no such file or directory\n', +        funcs.system( +          {nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless', +           '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', +           '--cmd', 'language C', +           '-s', nonexistent_fname})) +      eq(2, eval('v:shell_error')) +    end) +    it('errors out when trying to use -s twice', function() +      write_file(fname, ':call setline(1, "1")\n:wqall!\n') +      write_file(dollar_fname, ':call setline(1, "2")\n:wqall!\n') +      eq( +        'Attempt to open script file again: "-s '..dollar_fname..'"\n', +        funcs.system( +          {nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless', +           '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', +           '--cmd', 'language C', +           '-s', fname, '-s', dollar_fname, fname_2})) +      eq(2, eval('v:shell_error')) +      eq(nil, lfs.attributes(fname_2)) +    end) +  end) +end) diff --git a/test/helpers.lua b/test/helpers.lua index e0645d083f..efc0e911f1 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -1,6 +1,38 @@  local assert = require('luassert')  local lfs = require('lfs') +local quote_me = '[^.%w%+%-%@%_%/]' -- complement (needn't quote) +local function shell_quote(str) +  if string.find(str, quote_me) or str == '' then +    return '"' .. str:gsub('[$%%"\\]', '\\%0') .. '"' +  else +    return str +  end +end + +local function argss_to_cmd(...) +  local cmd = '' +  for i = 1, select('#', ...) do +    local arg = select(i, ...) +    if type(arg) == 'string' then +      cmd = cmd .. ' ' ..shell_quote(arg) +    else +      for _, subarg in ipairs(arg) do +        cmd = cmd .. ' ' .. shell_quote(subarg) +      end +    end +  end +  return cmd +end + +local function popen_r(...) +  return io.popen(argss_to_cmd(...), 'r') +end + +local function popen_w(...) +  return io.popen(argss_to_cmd(...), 'w') +end +  local check_logs_useless_lines = {    ['Warning: noted but unhandled ioctl']=1,    ['could cause spurious value errors to appear']=2, @@ -121,7 +153,7 @@ local uname = (function()        return platform      end -    local status, f = pcall(io.popen, "uname -s") +    local status, f = pcall(popen_r, 'uname', '-s')      if status then        platform = f:read("*l")        f:close() @@ -253,7 +285,7 @@ local function check_cores(app, force)  end  local function which(exe) -  local pipe = io.popen('which ' .. exe, 'r') +  local pipe = popen_r('which', exe)    local ret = pipe:read('*a')    pipe:close()    if ret == '' then @@ -263,6 +295,19 @@ local function which(exe)    end  end +local function repeated_read_cmd(...) +  for _ = 1, 10 do +    local stream = popen_r(...) +    local ret = stream:read('*a') +    stream:close() +    if ret then +      return ret +    end +  end +  print('ERROR: Failed to execute ' .. argss_to_cmd(...) .. ': nil return after 10 attempts') +  return nil +end +  local function shallowcopy(orig)    if type(orig) ~= 'table' then      return orig @@ -569,6 +614,7 @@ end  return {    REMOVE_THIS = REMOVE_THIS, +  argss_to_cmd = argss_to_cmd,    check_cores = check_cores,    check_logs = check_logs,    concat_tables = concat_tables, @@ -590,6 +636,9 @@ return {    mergedicts_copy = mergedicts_copy,    neq = neq,    ok = ok, +  popen_r = popen_r, +  popen_w = popen_w, +  repeated_read_cmd = repeated_read_cmd,    shallowcopy = shallowcopy,    table_flatten = table_flatten,    tmpname = tmpname, diff --git a/test/includes/pre/sys/stat.h b/test/includes/pre/sys/stat.h index c6cac80913..1cb811d579 100644 --- a/test/includes/pre/sys/stat.h +++ b/test/includes/pre/sys/stat.h @@ -1,4 +1,6 @@ -#define _GNU_SOURCE +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif  #include <sys/stat.h>  static const mode_t kS_IFMT = S_IFMT; diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 5cc2be50b1..f8143a0125 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -779,7 +779,7 @@ local function gen_itp(it)  end  local function cppimport(path) -  return cimport(Paths.test_include_path .. '/' .. path) +  return cimport(Paths.test_source_path .. '/test/includes/pre/' .. path)  end  cimport('./src/nvim/types.h', './src/nvim/main.h', './src/nvim/os/time.h') diff --git a/test/unit/os/fileio_spec.lua b/test/unit/os/fileio_spec.lua index d9c98e8afa..4d58a8934e 100644 --- a/test/unit/os/fileio_spec.lua +++ b/test/unit/os/fileio_spec.lua @@ -113,7 +113,7 @@ end  describe('file_open_fd', function()    itp('can use file descriptor returned by os_open for reading', function()      local fd = m.os_open(file1, m.kO_RDONLY, 0) -    local err, fp = file_open_fd(fd, false) +    local err, fp = file_open_fd(fd, m.kFileReadOnly)      eq(0, err)      eq({#fcontents, fcontents}, {file_read(fp, #fcontents)})      eq(0, m.file_close(fp, false)) @@ -121,7 +121,7 @@ describe('file_open_fd', function()    itp('can use file descriptor returned by os_open for writing', function()      eq(nil, lfs.attributes(filec))      local fd = m.os_open(filec, m.kO_WRONLY + m.kO_CREAT, 384) -    local err, fp = file_open_fd(fd, true) +    local err, fp = file_open_fd(fd, m.kFileWriteOnly)      eq(0, err)      eq(4, file_write(fp, 'test'))      eq(0, m.file_close(fp, false)) @@ -133,7 +133,7 @@ end)  describe('file_open_fd_new', function()    itp('can use file descriptor returned by os_open for reading', function()      local fd = m.os_open(file1, m.kO_RDONLY, 0) -    local err, fp = file_open_fd_new(fd, false) +    local err, fp = file_open_fd_new(fd, m.kFileReadOnly)      eq(0, err)      eq({#fcontents, fcontents}, {file_read(fp, #fcontents)})      eq(0, m.file_free(fp, false)) @@ -141,7 +141,7 @@ describe('file_open_fd_new', function()    itp('can use file descriptor returned by os_open for writing', function()      eq(nil, lfs.attributes(filec))      local fd = m.os_open(filec, m.kO_WRONLY + m.kO_CREAT, 384) -    local err, fp = file_open_fd_new(fd, true) +    local err, fp = file_open_fd_new(fd, m.kFileWriteOnly)      eq(0, err)      eq(4, file_write(fp, 'test'))      eq(0, m.file_free(fp, false)) diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua index 78455ee324..ae6dfe6423 100644 --- a/test/unit/os/fs_spec.lua +++ b/test/unit/os/fs_spec.lua @@ -390,7 +390,7 @@ describe('fs.c', function()          buf = ffi.new('char[?]', size + 1, ('\0'):rep(size))        end        local eof = ffi.new('bool[?]', 1, {true}) -      local ret2 = fs.os_read(fd, eof, buf, size) +      local ret2 = fs.os_read(fd, eof, buf, size, false)        local ret1 = eof[0]        local ret3 = ''        if buf ~= nil then @@ -408,7 +408,7 @@ describe('fs.c', function()        end        local iov = ffi.new('struct iovec[?]', #sizes, bufs)        local eof = ffi.new('bool[?]', 1, {true}) -      local ret2 = fs.os_readv(fd, eof, iov, #sizes) +      local ret2 = fs.os_readv(fd, eof, iov, #sizes, false)        local ret1 = eof[0]        local ret3 = {}        for i = 1,#sizes do @@ -418,7 +418,7 @@ describe('fs.c', function()        return ret1, ret2, ret3      end      local function os_write(fd, data) -      return fs.os_write(fd, data, data and #data or 0) +      return fs.os_write(fd, data, data and #data or 0, false)      end      describe('os_path_exists', function() @@ -491,6 +491,22 @@ describe('fs.c', function()        end)      end) +    describe('os_dup', function() +      itp('returns new file descriptor', function() +        local dup0 = fs.os_dup(0) +        local dup1 = fs.os_dup(1) +        local dup2 = fs.os_dup(2) +        local tbl = {[0]=true, [1]=true, [2]=true, +                     [tonumber(dup0)]=true, [tonumber(dup1)]=true, +                     [tonumber(dup2)]=true} +        local i = 0 +        for _, _ in pairs(tbl) do +          i = i + 1 +        end +        eq(i, 6)  -- All fds must be unique +      end) +    end) +      describe('os_open', function()        local new_file = 'test_new_file'        local existing_file = 'unit-test-directory/test_existing.file' diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua index 363358d134..1073855a7d 100644 --- a/test/unit/preprocess.lua +++ b/test/unit/preprocess.lua @@ -2,6 +2,10 @@  -- windows, will probably need quite a bit of adjustment to run there.  local ffi = require("ffi") +local global_helpers = require('test.helpers') + +local argss_to_cmd = global_helpers.argss_to_cmd +local repeated_read_cmd = global_helpers.repeated_read_cmd  local ccs = {} @@ -22,15 +26,6 @@ table.insert(ccs, {path = {"/usr/bin/env", "gcc-4.7"}, type = "gcc"})  table.insert(ccs, {path = {"/usr/bin/env", "clang"}, type = "clang"})  table.insert(ccs, {path = {"/usr/bin/env", "icc"}, type = "gcc"}) -local quote_me = '[^.%w%+%-%@%_%/]' -- complement (needn't quote) -local function shell_quote(str) -  if string.find(str, quote_me) or str == '' then -    return "'" .. string.gsub(str, "'", [['"'"']]) .. "'" -  else -    return str -  end -end -  -- parse Makefile format dependencies into a Lua table  local function parse_make_deps(deps)    -- remove line breaks and line concatenators @@ -149,16 +144,6 @@ function Gcc:add_to_include_path(...)    end  end -local function argss_to_cmd(...) -  local cmd = '' -  for i = 1, select('#', ...) do -    for _, arg in ipairs(select(i, ...)) do -      cmd = cmd .. ' ' .. shell_quote(arg) -    end -  end -  return cmd -end -  -- returns a list of the headers files upon which this file relies  function Gcc:dependencies(hdr)    local cmd = argss_to_cmd(self.path, {'-M', hdr}) .. ' 2>&1' @@ -172,29 +157,15 @@ function Gcc:dependencies(hdr)    end  end -local function repeated_call(...) -  local cmd = argss_to_cmd(...) -  for _ = 1, 10 do -    local stream = io.popen(cmd) -    local ret = stream:read('*a') -    stream:close() -    if ret then -      return ret -    end -  end -  print('ERROR: preprocess.lua: Failed to execute ' .. cmd .. ': nil return after 10 attempts') -  return nil -end -  function Gcc:filter_standard_defines(defines)    if not self.standard_defines then      local pseudoheader_fname = 'tmp_empty_pseudoheader.h'      local pseudoheader_file = io.open(pseudoheader_fname, 'w')      pseudoheader_file:close() -    local standard_defines = repeated_call(self.path, -                                           self.preprocessor_extra_flags, -                                           self.get_defines_extra_flags, -                                           {pseudoheader_fname}) +    local standard_defines = repeated_read_cmd(self.path, +                                               self.preprocessor_extra_flags, +                                               self.get_defines_extra_flags, +                                               {pseudoheader_fname})      os.remove(pseudoheader_fname)      self.standard_defines = {}      for line in standard_defines:gmatch('[^\n]+') do @@ -223,9 +194,9 @@ function Gcc:preprocess(previous_defines, ...)    pseudoheader_file:flush()    pseudoheader_file:close() -  local defines = repeated_call(self.path, self.preprocessor_extra_flags, -                                self.get_defines_extra_flags, -                                {pseudoheader_fname}) +  local defines = repeated_read_cmd(self.path, self.preprocessor_extra_flags, +                                    self.get_defines_extra_flags, +                                    {pseudoheader_fname})    defines = self:filter_standard_defines(defines)    -- lfs = require("lfs") @@ -234,9 +205,10 @@ function Gcc:preprocess(previous_defines, ...)    -- io.stderr\write("CWD: #{lfs.currentdir!}\n")    -- io.stderr\write("CMD: #{cmd}\n") -  local declarations = repeated_call(self.path, self.preprocessor_extra_flags, -                                     self.get_declarations_extra_flags, -                                     {pseudoheader_fname}) +  local declarations = repeated_read_cmd(self.path, +                                         self.preprocessor_extra_flags, +                                         self.get_declarations_extra_flags, +                                         {pseudoheader_fname})    os.remove(pseudoheader_fname)  | 
