Merge #6427 from ZyX-I/writefile-allow-omitting-fsync

eval: Make writefile() able to disable fsync()
This commit is contained in:
Justin M. Keyes 2017-04-03 03:54:34 +02:00 committed by GitHub
commit 6afa7d66cd
15 changed files with 270 additions and 159 deletions

View File

@ -69,6 +69,7 @@ run_test_wd() {
while test $restarts -gt 0 ; do
: > "${status_file}"
(
FAILED=0
if ! (
set -o pipefail
eval "$cmd" 2>&1 | tee -a "$output_file"

View File

@ -7915,6 +7915,12 @@ writefile({list}, {fname} [, {flags}])
appended to the file: >
:call writefile(["foo"], "event.log", "a")
:call writefile(["bar"], "event.log", "a")
<
When {flags} contains "S" fsync() call is not used, with "s"
it is used, 'fsync' option applies by default. No fsync()
means that writefile() will finish faster, but writes may be
left in OS buffers and not yet written to disk. Such changes
will disappear if system crashes before OS does writing.
All NL characters are replaced with a NUL character.
Inserting CR characters needs to be done before passing {list}

View File

@ -2740,6 +2740,9 @@ A jump table for the options with a short description can be found at |Q_op|.
mode, so it may be undesirable in some situations. Be warned that
turning this off increases the chances of data loss after a crash.
Currently applies only to writing the buffer with e.g. |:w| and
|writefile()|.
*'gdefault'* *'gd'* *'nogdefault'* *'nogd'*
'gdefault' 'gd' boolean (default off)
global

View File

@ -2724,7 +2724,7 @@ fileinfo (
else
name = curbuf->b_ffname;
home_replace(shorthelp ? curbuf : NULL, name, p,
(int)(IOSIZE - (p - buffer)), TRUE);
(size_t)(IOSIZE - (p - buffer)), true);
}
vim_snprintf_add((char *)buffer, IOSIZE, "\"%s%s%s%s%s%s",
@ -2889,7 +2889,7 @@ void maketitle(void)
buf[off++] = ' ';
buf[off++] = '(';
home_replace(curbuf, curbuf->b_ffname,
buf + off, SPACE_FOR_DIR - off, TRUE);
buf + off, (size_t)(SPACE_FOR_DIR - off), true);
#ifdef BACKSLASH_IN_FILENAME
/* avoid "c:/name" to be reduced to "c" */
if (isalpha(buf[off]) && buf[off + 1] == ':')

View File

@ -17421,16 +17421,24 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool binary = false;
bool append = false;
bool do_fsync = !!p_fs;
if (argvars[2].v_type != VAR_UNKNOWN) {
const char *const flags = tv_get_string_chk(&argvars[2]);
if (flags == NULL) {
return;
}
if (strchr(flags, 'b')) {
binary = true;
}
if (strchr(flags, 'a')) {
append = true;
for (const char *p = flags; *p; p++) {
switch (*p) {
case 'b': { binary = true; break; }
case 'a': { append = true; break; }
case 's': { do_fsync = true; break; }
case 'S': { do_fsync = false; break; }
default: {
// Using %s, p and not %c, *p to preserve multibyte characters
emsgf(_("E5060: Unknown flag: %s"), p);
return;
}
}
}
}
@ -17453,7 +17461,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (write_list(&fp, argvars[0].vval.v_list, binary)) {
rettv->vval.v_number = 0;
}
if ((error = file_close(&fp)) != 0) {
if ((error = file_close(&fp, do_fsync)) != 0) {
emsgf(_("E80: Error when closing file %s: %s"),
fname, os_strerror(error));
}

View File

@ -200,18 +200,14 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr)
{
int msg_scroll_save;
if (msg_silent != 0)
if (msg_silent != 0) {
return;
msg_add_fname(buf, name); /* put file name in IObuff with quotes */
/* If it's extremely long, truncate it. */
if (STRLEN(IObuff) > IOSIZE - 80)
IObuff[IOSIZE - 80] = NUL;
STRCAT(IObuff, s);
/*
* For the first message may have to start a new line.
* For further ones overwrite the previous one, reset msg_scroll before
* calling filemess().
*/
}
add_quoted_fname((char *)IObuff, IOSIZE - 80, buf, (const char *)name);
xstrlcat((char *)IObuff, (const char *)s, IOSIZE);
// For the first message may have to start a new line.
// For further ones overwrite the previous one, reset msg_scroll before
// calling filemess().
msg_scroll_save = msg_scroll;
if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0)
msg_scroll = FALSE;
@ -1800,8 +1796,8 @@ failed:
}
if (!filtering && !(flags & READ_DUMMY)) {
msg_add_fname(curbuf, sfname); /* fname in IObuff with quotes */
c = FALSE;
add_quoted_fname((char *)IObuff, IOSIZE, curbuf, (const char *)sfname);
c = false;
#ifdef UNIX
# ifdef S_ISFIFO
@ -2258,9 +2254,16 @@ buf_write (
int len;
linenr_T lnum;
long nchars;
char_u *errmsg = NULL;
int errmsg_allocated = FALSE;
char_u *errnum = NULL;
#define SET_ERRMSG_NUM(num, msg) \
errnum = num, errmsg = msg, errmsgarg = 0
#define SET_ERRMSG_ARG(msg, error) \
errnum = NULL, errmsg = msg, errmsgarg = error
#define SET_ERRMSG(msg) \
errnum = NULL, errmsg = msg, errmsgarg = 0
const char *errnum = NULL;
char *errmsg = NULL;
int errmsgarg = 0;
bool errmsg_allocated = false;
char_u *buffer;
char_u smallbuf[SMBUFSIZE];
char_u *backup_ext;
@ -2282,7 +2285,6 @@ buf_write (
/* writing everything */
int whole = (start == 1 && end == buf->b_ml.ml_line_count);
linenr_T old_line_count = buf->b_ml.ml_line_count;
int attr;
int fileformat;
int write_bin;
struct bw_info write_info; /* info for buf_write_bytes() */
@ -2577,13 +2579,11 @@ buf_write (
perm = file_info_old.stat.st_mode;
if (!S_ISREG(file_info_old.stat.st_mode)) { /* not a file */
if (S_ISDIR(file_info_old.stat.st_mode)) {
errnum = (char_u *)"E502: ";
errmsg = (char_u *)_("is a directory");
SET_ERRMSG_NUM("E502", _("is a directory"));
goto fail;
}
if (os_nodetype((char *)fname) != NODE_WRITABLE) {
errnum = (char_u *)"E503: ";
errmsg = (char_u *)_("is not a file or writable device");
SET_ERRMSG_NUM("E503", _("is not a file or writable device"));
goto fail;
}
/* It's a device of some kind (or a fifo) which we can write to
@ -2599,8 +2599,7 @@ buf_write (
*/
c = os_nodetype((char *)fname);
if (c == NODE_OTHER) {
errnum = (char_u *)"E503: ";
errmsg = (char_u *)_("is not a file or writable device");
SET_ERRMSG_NUM("E503", _("is not a file or writable device"));
goto fail;
}
if (c == NODE_WRITABLE) {
@ -2612,8 +2611,7 @@ buf_write (
if (perm < 0) {
newfile = true;
} else if (os_isdir(fname)) {
errnum = (char_u *)"E502: ";
errmsg = (char_u *)_("is a directory");
SET_ERRMSG_NUM("E502", _("is a directory"));
goto fail;
}
if (overwriting) {
@ -2632,11 +2630,9 @@ buf_write (
if (!forceit && file_readonly) {
if (vim_strchr(p_cpo, CPO_FWRITE) != NULL) {
errnum = (char_u *)"E504: ";
errmsg = (char_u *)_(err_readonly);
SET_ERRMSG_NUM("E504", _(err_readonly));
} else {
errnum = (char_u *)"E505: ";
errmsg = (char_u *)_("is read-only (add ! to override)");
SET_ERRMSG_NUM("E505", _("is read-only (add ! to override)"));
}
goto fail;
}
@ -2904,23 +2900,27 @@ buf_write (
while ((write_info.bw_len = read_eintr(fd, copybuf,
BUFSIZE)) > 0) {
if (buf_write_bytes(&write_info) == FAIL) {
errmsg = (char_u *)_(
"E506: Can't write to backup file (add ! to override)");
SET_ERRMSG(_(
"E506: Can't write to backup file (add ! to override)"));
break;
}
os_breakcheck();
if (got_int) {
errmsg = (char_u *)_(e_interr);
SET_ERRMSG(_(e_interr));
break;
}
}
if (close(bfd) < 0 && errmsg == NULL)
errmsg = (char_u *)_(
"E507: Close error for backup file (add ! to override)");
if (write_info.bw_len < 0)
errmsg = (char_u *)_(
"E508: Can't read file for backup (add ! to override)");
int error;
if ((error = os_close(bfd)) != 0 && errmsg == NULL) {
SET_ERRMSG_ARG(_("E507: Close error for backup file "
"(add ! to override): %s"),
error);
}
if (write_info.bw_len < 0) {
SET_ERRMSG(_(
"E508: Can't read file for backup (add ! to override)"));
}
#ifdef UNIX
set_file_time(backup,
file_info_old.stat.st_atim.tv_sec,
@ -2937,18 +2937,19 @@ buf_write (
}
}
nobackup:
close(fd); /* ignore errors for closing read file */
os_close(fd); // Ignore errors for closing read file.
xfree(copybuf);
if (backup == NULL && errmsg == NULL)
errmsg = (char_u *)_(
"E509: Cannot create backup file (add ! to override)");
/* ignore errors when forceit is TRUE */
if (backup == NULL && errmsg == NULL) {
SET_ERRMSG(_(
"E509: Cannot create backup file (add ! to override)"));
}
// Ignore errors when forceit is TRUE.
if ((some_error || errmsg != NULL) && !forceit) {
retval = FAIL;
goto fail;
}
errmsg = NULL;
SET_ERRMSG(NULL);
} else {
char_u *dirp;
char_u *p;
@ -2963,8 +2964,7 @@ nobackup:
* anyway, thus we need an extra check here.
*/
if (file_readonly && vim_strchr(p_cpo, CPO_FWRITE) != NULL) {
errnum = (char_u *)"E504: ";
errmsg = (char_u *)_(err_readonly);
SET_ERRMSG_NUM("E504", _(err_readonly));
goto fail;
}
@ -3028,7 +3028,7 @@ nobackup:
}
}
if (backup == NULL && !forceit) {
errmsg = (char_u *)_("E510: Can't make backup file (add ! to override)");
SET_ERRMSG(_("E510: Can't make backup file (add ! to override)"));
goto fail;
}
}
@ -3069,7 +3069,7 @@ nobackup:
&& !(exiting && backup != NULL)) {
ml_preserve(buf, FALSE);
if (got_int) {
errmsg = (char_u *)_(e_interr);
SET_ERRMSG(_(e_interr));
goto restore_backup;
}
}
@ -3140,8 +3140,8 @@ nobackup:
*/
if (*p_ccv != NUL) {
wfname = vim_tempname();
if (wfname == NULL) { /* Can't write without a tempfile! */
errmsg = (char_u *)_("E214: Can't find temp file for writing");
if (wfname == NULL) { // Can't write without a tempfile!
SET_ERRMSG(_("E214: Can't find temp file for writing"));
goto restore_backup;
}
}
@ -3153,8 +3153,8 @@ nobackup:
&& wfname == fname
) {
if (!forceit) {
errmsg = (char_u *)_(
"E213: Cannot convert (add ! to write without conversion)");
SET_ERRMSG(_(
"E213: Cannot convert (add ! to write without conversion)"));
goto restore_backup;
}
notconverted = TRUE;
@ -3189,11 +3189,10 @@ nobackup:
if ((!newfile && os_fileinfo_hardlinks(&file_info) > 1)
|| (os_fileinfo_link((char *)fname, &file_info)
&& !os_fileinfo_id_equal(&file_info, &file_info_old))) {
errmsg = (char_u *)_("E166: Can't open linked file for writing");
} else
SET_ERRMSG(_("E166: Can't open linked file for writing"));
} else {
#endif
{
errmsg = (char_u *)_("E212: Can't open file for writing");
SET_ERRMSG_ARG(_("E212: Can't open file for writing: %s"), fd);
if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL
&& perm >= 0) {
#ifdef UNIX
@ -3211,7 +3210,9 @@ nobackup:
os_remove((char *)wfname);
continue;
}
#ifdef UNIX
}
#endif
}
restore_backup:
@ -3253,7 +3254,7 @@ restore_backup:
xfree(wfname);
goto fail;
}
errmsg = NULL;
SET_ERRMSG(NULL);
write_info.bw_fd = fd;
@ -3373,7 +3374,6 @@ restore_backup:
nchars += len;
}
#if defined(UNIX)
// On many journalling file systems there is a bug that causes both the
// original and the backup file to be lost when halting the system right
// after writing the file. That's because only the meta-data is
@ -3382,11 +3382,11 @@ restore_backup:
// For a device do try the fsync() but don't complain if it does not work
// (could be a pipe).
// If the 'fsync' option is FALSE, don't fsync(). Useful for laptops.
if (p_fs && os_fsync(fd) != 0 && !device) {
errmsg = (char_u *)_("E667: Fsync failed");
int error;
if (p_fs && (error = os_fsync(fd)) != 0 && !device) {
SET_ERRMSG_ARG(_("E667: Fsync failed: %s"), error);
end = 0;
}
#endif
#ifdef HAVE_SELINUX
/* Probably need to set the security context. */
@ -3416,8 +3416,8 @@ restore_backup:
}
#endif
if (close(fd) != 0) {
errmsg = (char_u *)_("E512: Close failed");
if ((error = os_close(fd)) != 0) {
SET_ERRMSG_ARG(_("E512: Close failed: %s"), error);
end = 0;
}
@ -3454,21 +3454,24 @@ restore_backup:
if (end == 0) {
if (errmsg == NULL) {
if (write_info.bw_conv_error) {
if (write_info.bw_conv_error_lnum == 0)
errmsg = (char_u *)_(
"E513: write error, conversion failed (make 'fenc' empty to override)");
else {
errmsg_allocated = TRUE;
errmsg = xmalloc(300);
vim_snprintf((char *)errmsg, 300,
_("E513: write error, conversion failed in line %" PRId64
if (write_info.bw_conv_error_lnum == 0) {
SET_ERRMSG(_(
"E513: write error, conversion failed "
"(make 'fenc' empty to override)"));
} else {
errmsg_allocated = true;
SET_ERRMSG(xmalloc(300));
vim_snprintf(
errmsg, 300,
_("E513: write error, conversion failed in line %" PRIdLINENR
" (make 'fenc' empty to override)"),
(int64_t)write_info.bw_conv_error_lnum);
write_info.bw_conv_error_lnum);
}
} else if (got_int)
errmsg = (char_u *)_(e_interr);
else
errmsg = (char_u *)_("E514: write error (file system full?)");
} else if (got_int) {
SET_ERRMSG(_(e_interr));
} else {
SET_ERRMSG(_("E514: write error (file system full?)"));
}
}
/*
@ -3523,8 +3526,8 @@ restore_backup:
fname = sfname; /* use shortname now, for the messages */
#endif
if (!filtering) {
msg_add_fname(buf, fname); /* put fname in IObuff with quotes */
c = FALSE;
add_quoted_fname((char *)IObuff, IOSIZE, buf, (const char *)fname);
c = false;
if (write_info.bw_conv_error) {
STRCAT(IObuff, _(" CONVERSION ERROR"));
c = TRUE;
@ -3673,33 +3676,32 @@ nofail:
#endif
if (errmsg != NULL) {
int numlen = errnum != NULL ? (int)STRLEN(errnum) : 0;
attr = hl_attr(HLF_E); /* set highlight for error messages */
msg_add_fname(buf,
// - 100 to save some space for further error message
#ifndef UNIX
sfname
add_quoted_fname((char *)IObuff, IOSIZE - 100, buf, (const char *)sfname);
#else
fname
add_quoted_fname((char *)IObuff, IOSIZE - 100, buf, (const char *)fname);
#endif
); /* put file name in IObuff with quotes */
if (STRLEN(IObuff) + STRLEN(errmsg) + numlen >= IOSIZE)
IObuff[IOSIZE - STRLEN(errmsg) - numlen - 1] = NUL;
/* If the error message has the form "is ...", put the error number in
* front of the file name. */
if (errnum != NULL) {
STRMOVE(IObuff + numlen, IObuff);
memmove(IObuff, errnum, (size_t)numlen);
if (errmsgarg != 0) {
emsgf("%s: %s%s: %s", errnum, IObuff, errmsg, os_strerror(errmsgarg));
} else {
emsgf("%s: %s%s", errnum, IObuff, errmsg);
}
} else if (errmsgarg != 0) {
emsgf(errmsg, os_strerror(errmsgarg));
} else {
emsgf(errmsg);
}
STRCAT(IObuff, errmsg);
emsg(IObuff);
if (errmsg_allocated)
if (errmsg_allocated) {
xfree(errmsg);
}
retval = FAIL;
if (end == 0) {
const int attr = hl_attr(HLF_E); // Set highlight for error messages.
MSG_PUTS_ATTR(_("\nWARNING: Original file may be lost or damaged\n"),
attr | MSG_HIST);
attr | MSG_HIST);
MSG_PUTS_ATTR(_(
"don't quit the editor until the file is successfully written!"),
attr | MSG_HIST);
@ -3759,6 +3761,9 @@ nofail:
got_int |= prev_got_int;
return retval;
#undef SET_ERRMSG
#undef SET_ERRMSG_ARG
#undef SET_ERRMSG_NUM
}
/*
@ -3802,16 +3807,25 @@ static int set_rw_fname(char_u *fname, char_u *sfname)
return OK;
}
/*
* Put file name into IObuff with quotes.
*/
void msg_add_fname(buf_T *buf, char_u *fname)
/// Put file name into the specified buffer with quotes
///
/// Replaces home directory at the start with `~`.
///
/// @param[out] ret_buf Buffer to save results to.
/// @param[in] buf_len ret_buf length.
/// @param[in] buf buf_T file name is coming from.
/// @param[in] fname File name to write.
static void add_quoted_fname(char *const ret_buf, const size_t buf_len,
const buf_T *const buf, const char *fname)
FUNC_ATTR_NONNULL_ARG(1)
{
if (fname == NULL)
fname = (char_u *)"-stdin-";
home_replace(buf, fname, IObuff + 1, IOSIZE - 4, TRUE);
IObuff[0] = '"';
STRCAT(IObuff, "\" ");
if (fname == NULL) {
fname = "-stdin-";
}
ret_buf[0] = '"';
home_replace(buf, (const char_u *)fname, (char_u *)ret_buf + 1,
(int)buf_len - 4, true);
xstrlcat(ret_buf, "\" ", buf_len);
}
/// Append message for text mode to IObuff.

View File

@ -573,16 +573,17 @@ void emsg_invreg(int name)
/// Print an error message with unknown number of arguments
bool emsgf(const char *const fmt, ...)
{
static char errbuf[IOSIZE];
if (emsg_not_now()) {
return true;
}
va_list ap;
va_start(ap, fmt);
vim_vsnprintf((char *) IObuff, IOSIZE, fmt, ap, NULL);
vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap, NULL);
va_end(ap);
return emsg(IObuff);
return emsg((const char_u *)errbuf);
}
static void msg_emsgf_event(void **argv)

View File

@ -703,7 +703,8 @@ char *vim_getenv(const char *name)
/// @param dstlen Maximum length of the result
/// @param one If true, only replace one file name, including spaces and commas
/// in the file name
void home_replace(buf_T *buf, char_u *src, char_u *dst, int dstlen, bool one)
void home_replace(const buf_T *const buf, const char_u *src,
char_u *dst, size_t dstlen, bool one)
{
size_t dirlen = 0, envlen = 0;
size_t len;
@ -717,7 +718,7 @@ void home_replace(buf_T *buf, char_u *src, char_u *dst, int dstlen, bool one)
* If the file is a help file, remove the path completely.
*/
if (buf != NULL && buf->b_help) {
STRCPY(dst, path_tail(src));
xstrlcpy((char *)dst, (char *)path_tail(src), dstlen);
return;
}
@ -809,7 +810,7 @@ char_u * home_replace_save(buf_T *buf, char_u *src) FUNC_ATTR_NONNULL_RET
len += STRLEN(src);
}
char_u *dst = xmalloc(len);
home_replace(buf, src, dst, (int)len, true);
home_replace(buf, src, dst, len, true);
return dst;
}

View File

@ -113,27 +113,31 @@ FileDescriptor *file_open_new(int *const error, const char *const fname,
/// Close file and free its buffer
///
/// @param[in,out] fp File to close.
/// @param[in] do_fsync If true, use fsync() to write changes to disk.
///
/// @return 0 or error code.
int file_close(FileDescriptor *const fp) FUNC_ATTR_NONNULL_ALL
int file_close(FileDescriptor *const fp, const bool do_fsync)
FUNC_ATTR_NONNULL_ALL
{
const int error = file_fsync(fp);
const int error2 = os_close(fp->fd);
const int flush_error = (do_fsync ? file_fsync(fp) : file_flush(fp));
const int close_error = os_close(fp->fd);
rbuffer_free(fp->rv);
if (error2 != 0) {
return error2;
if (close_error != 0) {
return close_error;
}
return error;
return flush_error;
}
/// Close and free file obtained using file_open_new()
///
/// @param[in,out] fp File to close.
/// @param[in] do_fsync If true, use fsync() to write changes to disk.
///
/// @return 0 or error code.
int file_free(FileDescriptor *const fp) FUNC_ATTR_NONNULL_ALL
int file_free(FileDescriptor *const fp, const bool do_fsync)
FUNC_ATTR_NONNULL_ALL
{
const int ret = file_close(fp);
const int ret = file_close(fp, do_fsync);
xfree(fp);
return ret;
}

View File

@ -84,15 +84,15 @@ FileComparison path_full_compare(char_u *s1, char_u *s2, int checkname)
///
/// @return pointer just past the last path separator (empty string, if fname
/// ends in a slash), or empty string if fname is NULL.
char_u *path_tail(char_u *fname)
char_u *path_tail(const char_u *fname)
FUNC_ATTR_NONNULL_RET
{
if (fname == NULL) {
return (char_u *)"";
}
char_u *tail = get_past_head(fname);
char_u *p = tail;
const char_u *tail = get_past_head(fname);
const char_u *p = tail;
// Find last part of path.
while (*p != NUL) {
if (vim_ispathsep_nocolon(*p)) {
@ -100,7 +100,7 @@ char_u *path_tail(char_u *fname)
}
mb_ptr_adv(p);
}
return tail;
return (char_u *)tail;
}
/// Get pointer to tail of "fname", including path separators.
@ -174,9 +174,9 @@ const char *path_next_component(const char *fname)
/// Get a pointer to one character past the head of a path name.
/// Unix: after "/"; Win: after "c:\"
/// If there is no head, path is returned.
char_u *get_past_head(char_u *path)
char_u *get_past_head(const char_u *path)
{
char_u *retval = path;
const char_u *retval = path;
#ifdef WIN32
// May skip "c:"
@ -189,7 +189,7 @@ char_u *get_past_head(char_u *path)
++retval;
}
return retval;
return (char_u *)retval;
}
/*

View File

@ -811,7 +811,7 @@ static int open_shada_file_for_reading(const char *const fname,
/// Wrapper for closing file descriptors
static void close_file(void *cookie)
{
const int error = file_free(cookie);
const int error = file_free(cookie, true);
if (error != 0) {
emsgf(_(SERR "System error while closing ShaDa file: %s"),
os_strerror(error));

View File

@ -80,6 +80,13 @@ describe('writefile()', function()
eq('a\0\0\0b', read_file(fname))
end)
it('writes with s and S', function()
eq(0, funcs.writefile({'\na\nb\n'}, fname, 'bs'))
eq('\0a\0b\0', read_file(fname))
eq(0, funcs.writefile({'a\n\n\nb'}, fname, 'bS'))
eq('a\0\0\0b', read_file(fname))
end)
it('correctly overwrites file', function()
eq(0, funcs.writefile({'\na\nb\n'}, fname, 'b'))
eq('\0a\0b\0', read_file(fname))
@ -115,6 +122,8 @@ describe('writefile()', function()
eq('\nE729: using Funcref as a String',
redir_exec(('call writefile(%s)'):format(args:format('function("tr")'))))
end
eq('\nE5060: Unknown flag: «»',
redir_exec(('call writefile([], "%s", "bs«»")'):format(fname)))
eq('TEST', read_file(fname))
end)

View File

@ -1,15 +1,28 @@
local helpers = require('test.functional.helpers')(after_each)
local lfs = require('lfs')
local eq, eval, clear, write_file, execute, source, insert =
helpers.eq, helpers.eval, helpers.clear, helpers.write_file,
helpers.execute, helpers.source, helpers.insert
local redir_exec = helpers.redir_exec
local exc_exec = helpers.exc_exec
local command = helpers.command
local funcs = helpers.funcs
local meths = helpers.meths
if helpers.pending_win32(pending) then return end
local fname = 'Xtest-functional-ex_cmds-write'
local fname_bak = fname .. '~'
local fname_broken = fname_bak .. 'broken'
describe(':write', function()
local function cleanup()
os.remove('test_bkc_file.txt')
os.remove('test_bkc_link.txt')
os.remove('test_fifo')
os.remove(fname)
os.remove(fname_bak)
os.remove(fname_broken)
end
before_each(function()
clear()
@ -63,4 +76,34 @@ describe(':write', function()
eq(text.."\n", fifo:read("*all"))
fifo:close()
end)
it('errors out correctly', function()
command('let $HOME=""')
eq(funcs.fnamemodify('.', ':p:h'), funcs.fnamemodify('.', ':p:h:~'))
-- Message from check_overwrite
eq(('\nE17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'),
redir_exec('write .'))
meths.set_option('writeany', true)
-- Message from buf_write
eq(('\nE502: "." is a directory'),
redir_exec('write .'))
funcs.mkdir(fname_bak)
meths.set_option('backupdir', '.')
meths.set_option('backup', true)
write_file(fname, 'content0')
eq(0, exc_exec('edit ' .. fname))
funcs.setline(1, 'TTY')
eq('Vim(write):E510: Can\'t make backup file (add ! to override)',
exc_exec('write'))
meths.set_option('backup', false)
funcs.setfperm(fname, 'r--------')
eq('Vim(write):E505: "Xtest-functional-ex_cmds-write" is read-only (add ! to override)',
exc_exec('write'))
os.remove(fname)
os.remove(fname_bak)
write_file(fname_bak, 'TTYX')
lfs.link(fname_bak .. ('/xxxxx'):rep(20), fname, true)
eq('Vim(write):E166: Can\'t open linked file for writing',
exc_exec('write!'))
end)
end)

View File

@ -1,8 +1,7 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear, feed, meths = helpers.clear, helpers.feed, helpers.meths
local insert, execute = helpers.insert, helpers.execute
local eq, funcs = helpers.eq, helpers.funcs
local clear, meths = helpers.clear, helpers.meths
local eq = helpers.eq
local command = helpers.command
describe('ui/cursor', function()

View File

@ -98,7 +98,7 @@ describe('file_open', function()
eq(0, err)
local attrs = lfs.attributes(filec)
eq('rwx------', attrs.permissions)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
itp('can create a rw------- file with kFileCreate', function()
@ -106,7 +106,7 @@ describe('file_open', function()
eq(0, err)
local attrs = lfs.attributes(filec)
eq('rw-------', attrs.permissions)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
itp('can create a rwx------ file with kFileCreateOnly', function()
@ -114,7 +114,7 @@ describe('file_open', function()
eq(0, err)
local attrs = lfs.attributes(filec)
eq('rwx------', attrs.permissions)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
itp('can create a rw------- file with kFileCreateOnly', function()
@ -122,7 +122,7 @@ describe('file_open', function()
eq(0, err)
local attrs = lfs.attributes(filec)
eq('rw-------', attrs.permissions)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
itp('fails to open an existing file with kFileCreateOnly', function()
@ -141,35 +141,35 @@ describe('file_open', function()
local err, fp = file_open(file1, m.kFileCreate, 384)
eq(0, err)
eq(true, fp.wr)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
itp('can open an existing file read-only with zero', function()
local err, fp = file_open(file1, 0, 384)
eq(0, err)
eq(false, fp.wr)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
itp('can open an existing file read-only with kFileReadOnly', function()
local err, fp = file_open(file1, m.kFileReadOnly, 384)
eq(0, err)
eq(false, fp.wr)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
itp('can open an existing file read-only with kFileNoSymlink', function()
local err, fp = file_open(file1, m.kFileNoSymlink, 384)
eq(0, err)
eq(false, fp.wr)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
itp('can truncate an existing file with kFileTruncate', function()
local err, fp = file_open(file1, m.kFileTruncate, 384)
eq(0, err)
eq(true, fp.wr)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
local attrs = lfs.attributes(file1)
eq(0, attrs.size)
end)
@ -178,7 +178,7 @@ describe('file_open', function()
local err, fp = file_open(file1, m.kFileWriteOnly, 384)
eq(0, err)
eq(true, fp.wr)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
local attrs = lfs.attributes(file1)
eq(4096, attrs.size)
end)
@ -195,7 +195,7 @@ describe('file_open', function()
local err, fp = file_open(linkf, m.kFileTruncate, 384)
eq(0, err)
eq(true, fp.wr)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
local attrs = lfs.attributes(file1)
eq(0, attrs.size)
end)
@ -221,7 +221,7 @@ describe('file_open_new', function()
local err, fp = file_open_new(file1, 0, 384)
eq(0, err)
eq(false, fp.wr)
eq(0, m.file_free(fp))
eq(0, m.file_free(fp, false))
end)
itp('fails to open an existing file with kFileCreateOnly', function()
@ -231,7 +231,29 @@ describe('file_open_new', function()
end)
end)
-- file_close is called above, so it is not tested directly
describe('file_close', function()
itp('can flush writes to disk also with true argument', function()
local err, fp = file_open(filec, m.kFileCreateOnly, 384)
eq(0, err)
local wsize = file_write(fp, 'test')
eq(4, wsize)
eq(0, lfs.attributes(filec).size)
eq(0, m.file_close(fp, true))
eq(wsize, lfs.attributes(filec).size)
end)
end)
describe('file_free', function()
itp('can flush writes to disk also with true argument', function()
local err, fp = file_open_new(filec, m.kFileCreateOnly, 384)
eq(0, err)
local wsize = file_write(fp, 'test')
eq(4, wsize)
eq(0, lfs.attributes(filec).size)
eq(0, m.file_free(fp, true))
eq(wsize, lfs.attributes(filec).size)
end)
end)
describe('file_fsync', function()
itp('can flush writes to disk', function()
@ -244,7 +266,7 @@ describe('file_fsync', function()
eq(0, lfs.attributes(filec).size)
eq(0, file_fsync(fp))
eq(wsize, lfs.attributes(filec).size)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
end)
@ -259,7 +281,7 @@ describe('file_flush', function()
eq(0, lfs.attributes(filec).size)
eq(0, file_flush(fp))
eq(wsize, lfs.attributes(filec).size)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
end)
@ -281,7 +303,7 @@ describe('file_read', function()
eq({exp_err, exp_s}, {file_read(fp, size)})
shift = shift + size
end
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
itp('can read the whole file at once', function()
@ -290,7 +312,7 @@ describe('file_read', function()
eq(false, fp.wr)
eq({#fcontents, fcontents}, {file_read(fp, #fcontents)})
eq({0, ('\0'):rep(#fcontents)}, {file_read(fp, #fcontents)})
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
itp('can read more then 1024 bytes after reading a small chunk', function()
@ -300,7 +322,7 @@ describe('file_read', function()
eq({5, fcontents:sub(1, 5)}, {file_read(fp, 5)})
eq({#fcontents - 5, fcontents:sub(6) .. (('\0'):rep(5))},
{file_read(fp, #fcontents)})
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
itp('can read file by 768-byte-chunks', function()
@ -320,7 +342,7 @@ describe('file_read', function()
eq({exp_err, exp_s}, {file_read(fp, size)})
shift = shift + size
end
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
end)
@ -331,7 +353,7 @@ describe('file_write', function()
eq(true, fp.wr)
local wr = file_write(fp, fcontents)
eq(#fcontents, wr)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
eq(wr, lfs.attributes(filec).size)
eq(fcontents, io.open(filec):read('*a'))
end)
@ -348,7 +370,7 @@ describe('file_write', function()
eq(wr, #s)
shift = shift + size
end
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
eq(#fcontents, lfs.attributes(filec).size)
eq(fcontents, io.open(filec):read('*a'))
end)
@ -365,7 +387,7 @@ describe('file_write', function()
eq(wr, #s)
shift = shift + size
end
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
eq(#fcontents, lfs.attributes(filec).size)
eq(fcontents, io.open(filec):read('*a'))
end)
@ -380,6 +402,6 @@ describe('file_skip', function()
local rd, s = file_read(fp, 3)
eq(3, rd)
eq(fcontents:sub(4, 6), s)
eq(0, m.file_close(fp))
eq(0, m.file_close(fp, false))
end)
end)