aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2018-04-17 10:33:36 +0200
committerGitHub <noreply@github.com>2018-04-17 10:33:36 +0200
commit7a13611ba2033766da8cad73f46362bd01632c2c (patch)
tree170eff47d8e67ad5e2c484697f4bd999abf9844e
parent48967695c441d13bc9faa8aae4bed8d6fb5bdc14 (diff)
parent387fbcd95cade4b0c037d18f404944676a59db09 (diff)
downloadrneovim-7a13611ba2033766da8cad73f46362bd01632c2c.tar.gz
rneovim-7a13611ba2033766da8cad73f46362bd01632c2c.tar.bz2
rneovim-7a13611ba2033766da8cad73f46362bd01632c2c.zip
Merge #8276 'startup: Make -s - read from stdin'
-rw-r--r--src/nvim/getchar.c94
-rw-r--r--src/nvim/getchar.h24
-rw-r--r--src/nvim/globals.h6
-rw-r--r--src/nvim/main.c40
-rw-r--r--src/nvim/message.c10
-rw-r--r--src/nvim/os/fileio.c45
-rw-r--r--src/nvim/os/fileio.h3
-rw-r--r--src/nvim/os/fs.c50
-rw-r--r--test/functional/core/main_spec.lua131
-rw-r--r--test/helpers.lua53
-rw-r--r--test/includes/pre/sys/stat.h4
-rw-r--r--test/unit/helpers.lua2
-rw-r--r--test/unit/os/fileio_spec.lua8
-rw-r--r--test/unit/os/fs_spec.lua22
-rw-r--r--test/unit/preprocess.lua58
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)