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 while test $restarts -gt 0 ; do
: > "${status_file}" : > "${status_file}"
( (
FAILED=0
if ! ( if ! (
set -o pipefail set -o pipefail
eval "$cmd" 2>&1 | tee -a "$output_file" eval "$cmd" 2>&1 | tee -a "$output_file"

View File

@ -7915,6 +7915,12 @@ writefile({list}, {fname} [, {flags}])
appended to the file: > appended to the file: >
:call writefile(["foo"], "event.log", "a") :call writefile(["foo"], "event.log", "a")
:call writefile(["bar"], "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. All NL characters are replaced with a NUL character.
Inserting CR characters needs to be done before passing {list} 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 mode, so it may be undesirable in some situations. Be warned that
turning this off increases the chances of data loss after a crash. 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'* *'nogdefault'* *'nogd'*
'gdefault' 'gd' boolean (default off) 'gdefault' 'gd' boolean (default off)
global global

View File

@ -2724,7 +2724,7 @@ fileinfo (
else else
name = curbuf->b_ffname; name = curbuf->b_ffname;
home_replace(shorthelp ? curbuf : NULL, name, p, 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", vim_snprintf_add((char *)buffer, IOSIZE, "\"%s%s%s%s%s%s",
@ -2889,7 +2889,7 @@ void maketitle(void)
buf[off++] = ' '; buf[off++] = ' ';
buf[off++] = '('; buf[off++] = '(';
home_replace(curbuf, curbuf->b_ffname, 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 #ifdef BACKSLASH_IN_FILENAME
/* avoid "c:/name" to be reduced to "c" */ /* avoid "c:/name" to be reduced to "c" */
if (isalpha(buf[off]) && buf[off + 1] == ':') 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 binary = false;
bool append = false; bool append = false;
bool do_fsync = !!p_fs;
if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_UNKNOWN) {
const char *const flags = tv_get_string_chk(&argvars[2]); const char *const flags = tv_get_string_chk(&argvars[2]);
if (flags == NULL) { if (flags == NULL) {
return; return;
} }
if (strchr(flags, 'b')) { for (const char *p = flags; *p; p++) {
binary = true; switch (*p) {
} case 'b': { binary = true; break; }
if (strchr(flags, 'a')) { case 'a': { append = true; break; }
append = true; 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)) { if (write_list(&fp, argvars[0].vval.v_list, binary)) {
rettv->vval.v_number = 0; 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"), emsgf(_("E80: Error when closing file %s: %s"),
fname, os_strerror(error)); 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; int msg_scroll_save;
if (msg_silent != 0) if (msg_silent != 0) {
return; return;
msg_add_fname(buf, name); /* put file name in IObuff with quotes */ }
/* If it's extremely long, truncate it. */ add_quoted_fname((char *)IObuff, IOSIZE - 80, buf, (const char *)name);
if (STRLEN(IObuff) > IOSIZE - 80) xstrlcat((char *)IObuff, (const char *)s, IOSIZE);
IObuff[IOSIZE - 80] = NUL; // For the first message may have to start a new line.
STRCAT(IObuff, s); // For further ones overwrite the previous one, reset msg_scroll before
/* // calling filemess().
* 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; msg_scroll_save = msg_scroll;
if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0)
msg_scroll = FALSE; msg_scroll = FALSE;
@ -1800,8 +1796,8 @@ failed:
} }
if (!filtering && !(flags & READ_DUMMY)) { if (!filtering && !(flags & READ_DUMMY)) {
msg_add_fname(curbuf, sfname); /* fname in IObuff with quotes */ add_quoted_fname((char *)IObuff, IOSIZE, curbuf, (const char *)sfname);
c = FALSE; c = false;
#ifdef UNIX #ifdef UNIX
# ifdef S_ISFIFO # ifdef S_ISFIFO
@ -2258,9 +2254,16 @@ buf_write (
int len; int len;
linenr_T lnum; linenr_T lnum;
long nchars; long nchars;
char_u *errmsg = NULL; #define SET_ERRMSG_NUM(num, msg) \
int errmsg_allocated = FALSE; errnum = num, errmsg = msg, errmsgarg = 0
char_u *errnum = NULL; #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 *buffer;
char_u smallbuf[SMBUFSIZE]; char_u smallbuf[SMBUFSIZE];
char_u *backup_ext; char_u *backup_ext;
@ -2282,7 +2285,6 @@ buf_write (
/* writing everything */ /* writing everything */
int whole = (start == 1 && end == buf->b_ml.ml_line_count); int whole = (start == 1 && end == buf->b_ml.ml_line_count);
linenr_T old_line_count = buf->b_ml.ml_line_count; linenr_T old_line_count = buf->b_ml.ml_line_count;
int attr;
int fileformat; int fileformat;
int write_bin; int write_bin;
struct bw_info write_info; /* info for buf_write_bytes() */ struct bw_info write_info; /* info for buf_write_bytes() */
@ -2577,13 +2579,11 @@ buf_write (
perm = file_info_old.stat.st_mode; perm = file_info_old.stat.st_mode;
if (!S_ISREG(file_info_old.stat.st_mode)) { /* not a file */ if (!S_ISREG(file_info_old.stat.st_mode)) { /* not a file */
if (S_ISDIR(file_info_old.stat.st_mode)) { if (S_ISDIR(file_info_old.stat.st_mode)) {
errnum = (char_u *)"E502: "; SET_ERRMSG_NUM("E502", _("is a directory"));
errmsg = (char_u *)_("is a directory");
goto fail; goto fail;
} }
if (os_nodetype((char *)fname) != NODE_WRITABLE) { if (os_nodetype((char *)fname) != NODE_WRITABLE) {
errnum = (char_u *)"E503: "; SET_ERRMSG_NUM("E503", _("is not a file or writable device"));
errmsg = (char_u *)_("is not a file or writable device");
goto fail; goto fail;
} }
/* It's a device of some kind (or a fifo) which we can write to /* 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); c = os_nodetype((char *)fname);
if (c == NODE_OTHER) { if (c == NODE_OTHER) {
errnum = (char_u *)"E503: "; SET_ERRMSG_NUM("E503", _("is not a file or writable device"));
errmsg = (char_u *)_("is not a file or writable device");
goto fail; goto fail;
} }
if (c == NODE_WRITABLE) { if (c == NODE_WRITABLE) {
@ -2612,8 +2611,7 @@ buf_write (
if (perm < 0) { if (perm < 0) {
newfile = true; newfile = true;
} else if (os_isdir(fname)) { } else if (os_isdir(fname)) {
errnum = (char_u *)"E502: "; SET_ERRMSG_NUM("E502", _("is a directory"));
errmsg = (char_u *)_("is a directory");
goto fail; goto fail;
} }
if (overwriting) { if (overwriting) {
@ -2632,11 +2630,9 @@ buf_write (
if (!forceit && file_readonly) { if (!forceit && file_readonly) {
if (vim_strchr(p_cpo, CPO_FWRITE) != NULL) { if (vim_strchr(p_cpo, CPO_FWRITE) != NULL) {
errnum = (char_u *)"E504: "; SET_ERRMSG_NUM("E504", _(err_readonly));
errmsg = (char_u *)_(err_readonly);
} else { } else {
errnum = (char_u *)"E505: "; SET_ERRMSG_NUM("E505", _("is read-only (add ! to override)"));
errmsg = (char_u *)_("is read-only (add ! to override)");
} }
goto fail; goto fail;
} }
@ -2904,23 +2900,27 @@ buf_write (
while ((write_info.bw_len = read_eintr(fd, copybuf, while ((write_info.bw_len = read_eintr(fd, copybuf,
BUFSIZE)) > 0) { BUFSIZE)) > 0) {
if (buf_write_bytes(&write_info) == FAIL) { if (buf_write_bytes(&write_info) == FAIL) {
errmsg = (char_u *)_( SET_ERRMSG(_(
"E506: Can't write to backup file (add ! to override)"); "E506: Can't write to backup file (add ! to override)"));
break; break;
} }
os_breakcheck(); os_breakcheck();
if (got_int) { if (got_int) {
errmsg = (char_u *)_(e_interr); SET_ERRMSG(_(e_interr));
break; break;
} }
} }
if (close(bfd) < 0 && errmsg == NULL) int error;
errmsg = (char_u *)_( if ((error = os_close(bfd)) != 0 && errmsg == NULL) {
"E507: Close error for backup file (add ! to override)"); SET_ERRMSG_ARG(_("E507: Close error for backup file "
if (write_info.bw_len < 0) "(add ! to override): %s"),
errmsg = (char_u *)_( error);
"E508: Can't read file for backup (add ! to override)"); }
if (write_info.bw_len < 0) {
SET_ERRMSG(_(
"E508: Can't read file for backup (add ! to override)"));
}
#ifdef UNIX #ifdef UNIX
set_file_time(backup, set_file_time(backup,
file_info_old.stat.st_atim.tv_sec, file_info_old.stat.st_atim.tv_sec,
@ -2937,18 +2937,19 @@ buf_write (
} }
} }
nobackup: nobackup:
close(fd); /* ignore errors for closing read file */ os_close(fd); // Ignore errors for closing read file.
xfree(copybuf); xfree(copybuf);
if (backup == NULL && errmsg == NULL) if (backup == NULL && errmsg == NULL) {
errmsg = (char_u *)_( SET_ERRMSG(_(
"E509: Cannot create backup file (add ! to override)"); "E509: Cannot create backup file (add ! to override)"));
/* ignore errors when forceit is TRUE */ }
// Ignore errors when forceit is TRUE.
if ((some_error || errmsg != NULL) && !forceit) { if ((some_error || errmsg != NULL) && !forceit) {
retval = FAIL; retval = FAIL;
goto fail; goto fail;
} }
errmsg = NULL; SET_ERRMSG(NULL);
} else { } else {
char_u *dirp; char_u *dirp;
char_u *p; char_u *p;
@ -2963,8 +2964,7 @@ nobackup:
* anyway, thus we need an extra check here. * anyway, thus we need an extra check here.
*/ */
if (file_readonly && vim_strchr(p_cpo, CPO_FWRITE) != NULL) { if (file_readonly && vim_strchr(p_cpo, CPO_FWRITE) != NULL) {
errnum = (char_u *)"E504: "; SET_ERRMSG_NUM("E504", _(err_readonly));
errmsg = (char_u *)_(err_readonly);
goto fail; goto fail;
} }
@ -3028,7 +3028,7 @@ nobackup:
} }
} }
if (backup == NULL && !forceit) { 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; goto fail;
} }
} }
@ -3069,7 +3069,7 @@ nobackup:
&& !(exiting && backup != NULL)) { && !(exiting && backup != NULL)) {
ml_preserve(buf, FALSE); ml_preserve(buf, FALSE);
if (got_int) { if (got_int) {
errmsg = (char_u *)_(e_interr); SET_ERRMSG(_(e_interr));
goto restore_backup; goto restore_backup;
} }
} }
@ -3140,8 +3140,8 @@ nobackup:
*/ */
if (*p_ccv != NUL) { if (*p_ccv != NUL) {
wfname = vim_tempname(); wfname = vim_tempname();
if (wfname == NULL) { /* Can't write without a tempfile! */ if (wfname == NULL) { // Can't write without a tempfile!
errmsg = (char_u *)_("E214: Can't find temp file for writing"); SET_ERRMSG(_("E214: Can't find temp file for writing"));
goto restore_backup; goto restore_backup;
} }
} }
@ -3153,8 +3153,8 @@ nobackup:
&& wfname == fname && wfname == fname
) { ) {
if (!forceit) { if (!forceit) {
errmsg = (char_u *)_( SET_ERRMSG(_(
"E213: Cannot convert (add ! to write without conversion)"); "E213: Cannot convert (add ! to write without conversion)"));
goto restore_backup; goto restore_backup;
} }
notconverted = TRUE; notconverted = TRUE;
@ -3189,11 +3189,10 @@ nobackup:
if ((!newfile && os_fileinfo_hardlinks(&file_info) > 1) if ((!newfile && os_fileinfo_hardlinks(&file_info) > 1)
|| (os_fileinfo_link((char *)fname, &file_info) || (os_fileinfo_link((char *)fname, &file_info)
&& !os_fileinfo_id_equal(&file_info, &file_info_old))) { && !os_fileinfo_id_equal(&file_info, &file_info_old))) {
errmsg = (char_u *)_("E166: Can't open linked file for writing"); SET_ERRMSG(_("E166: Can't open linked file for writing"));
} else } else {
#endif #endif
{ SET_ERRMSG_ARG(_("E212: Can't open file for writing: %s"), fd);
errmsg = (char_u *)_("E212: Can't open file for writing");
if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL
&& perm >= 0) { && perm >= 0) {
#ifdef UNIX #ifdef UNIX
@ -3211,7 +3210,9 @@ nobackup:
os_remove((char *)wfname); os_remove((char *)wfname);
continue; continue;
} }
#ifdef UNIX
} }
#endif
} }
restore_backup: restore_backup:
@ -3253,7 +3254,7 @@ restore_backup:
xfree(wfname); xfree(wfname);
goto fail; goto fail;
} }
errmsg = NULL; SET_ERRMSG(NULL);
write_info.bw_fd = fd; write_info.bw_fd = fd;
@ -3373,7 +3374,6 @@ restore_backup:
nchars += len; nchars += len;
} }
#if defined(UNIX)
// On many journalling file systems there is a bug that causes both the // 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 // 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 // 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 // For a device do try the fsync() but don't complain if it does not work
// (could be a pipe). // (could be a pipe).
// If the 'fsync' option is FALSE, don't fsync(). Useful for laptops. // If the 'fsync' option is FALSE, don't fsync(). Useful for laptops.
if (p_fs && os_fsync(fd) != 0 && !device) { int error;
errmsg = (char_u *)_("E667: Fsync failed"); if (p_fs && (error = os_fsync(fd)) != 0 && !device) {
SET_ERRMSG_ARG(_("E667: Fsync failed: %s"), error);
end = 0; end = 0;
} }
#endif
#ifdef HAVE_SELINUX #ifdef HAVE_SELINUX
/* Probably need to set the security context. */ /* Probably need to set the security context. */
@ -3416,8 +3416,8 @@ restore_backup:
} }
#endif #endif
if (close(fd) != 0) { if ((error = os_close(fd)) != 0) {
errmsg = (char_u *)_("E512: Close failed"); SET_ERRMSG_ARG(_("E512: Close failed: %s"), error);
end = 0; end = 0;
} }
@ -3454,21 +3454,24 @@ restore_backup:
if (end == 0) { if (end == 0) {
if (errmsg == NULL) { if (errmsg == NULL) {
if (write_info.bw_conv_error) { if (write_info.bw_conv_error) {
if (write_info.bw_conv_error_lnum == 0) if (write_info.bw_conv_error_lnum == 0) {
errmsg = (char_u *)_( SET_ERRMSG(_(
"E513: write error, conversion failed (make 'fenc' empty to override)"); "E513: write error, conversion failed "
else { "(make 'fenc' empty to override)"));
errmsg_allocated = TRUE; } else {
errmsg = xmalloc(300); errmsg_allocated = true;
vim_snprintf((char *)errmsg, 300, SET_ERRMSG(xmalloc(300));
_("E513: write error, conversion failed in line %" PRId64 vim_snprintf(
errmsg, 300,
_("E513: write error, conversion failed in line %" PRIdLINENR
" (make 'fenc' empty to override)"), " (make 'fenc' empty to override)"),
(int64_t)write_info.bw_conv_error_lnum); write_info.bw_conv_error_lnum);
} }
} else if (got_int) } else if (got_int) {
errmsg = (char_u *)_(e_interr); SET_ERRMSG(_(e_interr));
else } else {
errmsg = (char_u *)_("E514: write error (file system full?)"); SET_ERRMSG(_("E514: write error (file system full?)"));
}
} }
/* /*
@ -3523,8 +3526,8 @@ restore_backup:
fname = sfname; /* use shortname now, for the messages */ fname = sfname; /* use shortname now, for the messages */
#endif #endif
if (!filtering) { if (!filtering) {
msg_add_fname(buf, fname); /* put fname in IObuff with quotes */ add_quoted_fname((char *)IObuff, IOSIZE, buf, (const char *)fname);
c = FALSE; c = false;
if (write_info.bw_conv_error) { if (write_info.bw_conv_error) {
STRCAT(IObuff, _(" CONVERSION ERROR")); STRCAT(IObuff, _(" CONVERSION ERROR"));
c = TRUE; c = TRUE;
@ -3673,33 +3676,32 @@ nofail:
#endif #endif
if (errmsg != NULL) { if (errmsg != NULL) {
int numlen = errnum != NULL ? (int)STRLEN(errnum) : 0; // - 100 to save some space for further error message
attr = hl_attr(HLF_E); /* set highlight for error messages */
msg_add_fname(buf,
#ifndef UNIX #ifndef UNIX
sfname add_quoted_fname((char *)IObuff, IOSIZE - 100, buf, (const char *)sfname);
#else #else
fname add_quoted_fname((char *)IObuff, IOSIZE - 100, buf, (const char *)fname);
#endif #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) { if (errnum != NULL) {
STRMOVE(IObuff + numlen, IObuff); if (errmsgarg != 0) {
memmove(IObuff, errnum, (size_t)numlen); 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); if (errmsg_allocated) {
emsg(IObuff);
if (errmsg_allocated)
xfree(errmsg); xfree(errmsg);
}
retval = FAIL; retval = FAIL;
if (end == 0) { 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"), MSG_PUTS_ATTR(_("\nWARNING: Original file may be lost or damaged\n"),
attr | MSG_HIST); attr | MSG_HIST);
MSG_PUTS_ATTR(_( MSG_PUTS_ATTR(_(
"don't quit the editor until the file is successfully written!"), "don't quit the editor until the file is successfully written!"),
attr | MSG_HIST); attr | MSG_HIST);
@ -3759,6 +3761,9 @@ nofail:
got_int |= prev_got_int; got_int |= prev_got_int;
return retval; 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; return OK;
} }
/* /// Put file name into the specified buffer with quotes
* Put file name into IObuff with quotes. ///
*/ /// Replaces home directory at the start with `~`.
void msg_add_fname(buf_T *buf, char_u *fname) ///
/// @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) if (fname == NULL) {
fname = (char_u *)"-stdin-"; fname = "-stdin-";
home_replace(buf, fname, IObuff + 1, IOSIZE - 4, TRUE); }
IObuff[0] = '"'; ret_buf[0] = '"';
STRCAT(IObuff, "\" "); 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. /// 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 /// Print an error message with unknown number of arguments
bool emsgf(const char *const fmt, ...) bool emsgf(const char *const fmt, ...)
{ {
static char errbuf[IOSIZE];
if (emsg_not_now()) { if (emsg_not_now()) {
return true; return true;
} }
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
vim_vsnprintf((char *) IObuff, IOSIZE, fmt, ap, NULL); vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap, NULL);
va_end(ap); va_end(ap);
return emsg(IObuff); return emsg((const char_u *)errbuf);
} }
static void msg_emsgf_event(void **argv) 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 dstlen Maximum length of the result
/// @param one If true, only replace one file name, including spaces and commas /// @param one If true, only replace one file name, including spaces and commas
/// in the file name /// 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 dirlen = 0, envlen = 0;
size_t len; 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 the file is a help file, remove the path completely.
*/ */
if (buf != NULL && buf->b_help) { if (buf != NULL && buf->b_help) {
STRCPY(dst, path_tail(src)); xstrlcpy((char *)dst, (char *)path_tail(src), dstlen);
return; return;
} }
@ -809,7 +810,7 @@ char_u * home_replace_save(buf_T *buf, char_u *src) FUNC_ATTR_NONNULL_RET
len += STRLEN(src); len += STRLEN(src);
} }
char_u *dst = xmalloc(len); char_u *dst = xmalloc(len);
home_replace(buf, src, dst, (int)len, true); home_replace(buf, src, dst, len, true);
return dst; 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 /// Close file and free its buffer
/// ///
/// @param[in,out] fp File to close. /// @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. /// @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 flush_error = (do_fsync ? file_fsync(fp) : file_flush(fp));
const int error2 = os_close(fp->fd); const int close_error = os_close(fp->fd);
rbuffer_free(fp->rv); rbuffer_free(fp->rv);
if (error2 != 0) { if (close_error != 0) {
return error2; return close_error;
} }
return error; return flush_error;
} }
/// Close and free file obtained using file_open_new() /// Close and free file obtained using file_open_new()
/// ///
/// @param[in,out] fp File to close. /// @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. /// @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); xfree(fp);
return ret; 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 /// @return pointer just past the last path separator (empty string, if fname
/// ends in a slash), or empty string if fname is NULL. /// 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 FUNC_ATTR_NONNULL_RET
{ {
if (fname == NULL) { if (fname == NULL) {
return (char_u *)""; return (char_u *)"";
} }
char_u *tail = get_past_head(fname); const char_u *tail = get_past_head(fname);
char_u *p = tail; const char_u *p = tail;
// Find last part of path. // Find last part of path.
while (*p != NUL) { while (*p != NUL) {
if (vim_ispathsep_nocolon(*p)) { if (vim_ispathsep_nocolon(*p)) {
@ -100,7 +100,7 @@ char_u *path_tail(char_u *fname)
} }
mb_ptr_adv(p); mb_ptr_adv(p);
} }
return tail; return (char_u *)tail;
} }
/// Get pointer to tail of "fname", including path separators. /// 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. /// Get a pointer to one character past the head of a path name.
/// Unix: after "/"; Win: after "c:\" /// Unix: after "/"; Win: after "c:\"
/// If there is no head, path is returned. /// 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 #ifdef WIN32
// May skip "c:" // May skip "c:"
@ -189,7 +189,7 @@ char_u *get_past_head(char_u *path)
++retval; ++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 /// Wrapper for closing file descriptors
static void close_file(void *cookie) static void close_file(void *cookie)
{ {
const int error = file_free(cookie); const int error = file_free(cookie, true);
if (error != 0) { if (error != 0) {
emsgf(_(SERR "System error while closing ShaDa file: %s"), emsgf(_(SERR "System error while closing ShaDa file: %s"),
os_strerror(error)); os_strerror(error));

View File

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

View File

@ -1,15 +1,28 @@
local helpers = require('test.functional.helpers')(after_each) local helpers = require('test.functional.helpers')(after_each)
local lfs = require('lfs')
local eq, eval, clear, write_file, execute, source, insert = local eq, eval, clear, write_file, execute, source, insert =
helpers.eq, helpers.eval, helpers.clear, helpers.write_file, helpers.eq, helpers.eval, helpers.clear, helpers.write_file,
helpers.execute, helpers.source, helpers.insert 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 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() describe(':write', function()
local function cleanup() local function cleanup()
os.remove('test_bkc_file.txt') os.remove('test_bkc_file.txt')
os.remove('test_bkc_link.txt') os.remove('test_bkc_link.txt')
os.remove('test_fifo') os.remove('test_fifo')
os.remove(fname)
os.remove(fname_bak)
os.remove(fname_broken)
end end
before_each(function() before_each(function()
clear() clear()
@ -63,4 +76,34 @@ describe(':write', function()
eq(text.."\n", fifo:read("*all")) eq(text.."\n", fifo:read("*all"))
fifo:close() fifo:close()
end) 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) end)

View File

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

View File

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