0
0
mirror of https://github.com/vim/vim.git synced 2025-09-29 04:34:16 -04:00

patch 8.0.0685: when conversion fails written file may be truncated

Problem:    When making backups is disabled and conversion with iconv fails
            the written file is truncated. (Luo Chen)
Solution:   First try converting the file and write the file only when it did
            not fail. (partly by Christian Brabandt)
This commit is contained in:
Bram Moolenaar
2017-06-27 22:11:51 +02:00
parent 28b238225a
commit e6bf655bc4
3 changed files with 437 additions and 354 deletions

View File

@@ -3166,6 +3166,7 @@ buf_write(
int device = FALSE; /* writing to a device */ int device = FALSE; /* writing to a device */
stat_T st_old; stat_T st_old;
int prev_got_int = got_int; int prev_got_int = got_int;
int checking_conversion;
int file_readonly = FALSE; /* overwritten file is read-only */ int file_readonly = FALSE; /* overwritten file is read-only */
static char *err_readonly = "is read-only (cannot override: \"W\" in 'cpoptions')"; static char *err_readonly = "is read-only (cannot override: \"W\" in 'cpoptions')";
#if defined(UNIX) /*XXX fix me sometime? */ #if defined(UNIX) /*XXX fix me sometime? */
@@ -4344,433 +4345,491 @@ buf_write(
#endif #endif
/* /*
* Open the file "wfname" for writing. * If conversion is taking place, we may first pretend to write and check
* We may try to open the file twice: If we can't write to the * for conversion errors. Then loop again to write for real.
* file and forceit is TRUE we delete the existing file and try to create * When not doing conversion this writes for real right away.
* a new one. If this still fails we may have lost the original file!
* (this may happen when the user reached his quotum for number of files).
* Appending will fail if the file does not exist and forceit is FALSE.
*/ */
while ((fd = mch_open((char *)wfname, O_WRONLY | O_EXTRA | (append for (checking_conversion = TRUE; ; checking_conversion = FALSE)
? (forceit ? (O_APPEND | O_CREAT) : O_APPEND)
: (O_CREAT | O_TRUNC))
, perm < 0 ? 0666 : (perm & 0777))) < 0)
{ {
/* /*
* A forced write will try to create a new file if the old one is * There is no need to check conversion when:
* still readonly. This may also happen when the directory is * - there is no conversion
* read-only. In that case the mch_remove() will fail. * - we make a backup file, that can be restored in case of conversion
* failure.
*/ */
if (errmsg == NULL) #ifdef FEAT_MBYTE
{ if (!converted || dobackup)
#ifdef UNIX
stat_T st;
/* Don't delete the file when it's a hard or symbolic link. */
if ((!newfile && st_old.st_nlink > 1)
|| (mch_lstat((char *)fname, &st) == 0
&& (st.st_dev != st_old.st_dev
|| st.st_ino != st_old.st_ino)))
errmsg = (char_u *)_("E166: Can't open linked file for writing");
else
#endif #endif
checking_conversion = FALSE;
if (checking_conversion)
{
/* Make sure we don't write anything. */
fd = -1;
write_info.bw_fd = fd;
}
else
{
/*
* Open the file "wfname" for writing.
* We may try to open the file twice: If we can't write to the file
* and forceit is TRUE we delete the existing file and try to
* create a new one. If this still fails we may have lost the
* original file! (this may happen when the user reached his
* quotum for number of files).
* Appending will fail if the file does not exist and forceit is
* FALSE.
*/
while ((fd = mch_open((char *)wfname, O_WRONLY | O_EXTRA | (append
? (forceit ? (O_APPEND | O_CREAT) : O_APPEND)
: (O_CREAT | O_TRUNC))
, perm < 0 ? 0666 : (perm & 0777))) < 0)
{ {
errmsg = (char_u *)_("E212: Can't open file for writing"); /*
if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL * A forced write will try to create a new file if the old one
&& perm >= 0) * is still readonly. This may also happen when the directory
* is read-only. In that case the mch_remove() will fail.
*/
if (errmsg == NULL)
{ {
#ifdef UNIX #ifdef UNIX
/* we write to the file, thus it should be marked stat_T st;
writable after all */
if (!(perm & 0200)) /* Don't delete the file when it's a hard or symbolic link.
made_writable = TRUE; */
perm |= 0200; if ((!newfile && st_old.st_nlink > 1)
if (st_old.st_uid != getuid() || st_old.st_gid != getgid()) || (mch_lstat((char *)fname, &st) == 0
perm &= 0777; && (st.st_dev != st_old.st_dev
|| st.st_ino != st_old.st_ino)))
errmsg = (char_u *)_("E166: Can't open linked file for writing");
else
#endif #endif
if (!append) /* don't remove when appending */ {
mch_remove(wfname); errmsg = (char_u *)_("E212: Can't open file for writing");
continue; if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL
&& perm >= 0)
{
#ifdef UNIX
/* we write to the file, thus it should be marked
writable after all */
if (!(perm & 0200))
made_writable = TRUE;
perm |= 0200;
if (st_old.st_uid != getuid()
|| st_old.st_gid != getgid())
perm &= 0777;
#endif
if (!append) /* don't remove when appending */
mch_remove(wfname);
continue;
}
}
} }
}
}
restore_backup: restore_backup:
{
stat_T st;
/*
* If we failed to open the file, we don't need a backup. Throw it
* away. If we moved or removed the original file try to put the
* backup in its place.
*/
if (backup != NULL && wfname == fname)
{
if (backup_copy)
{ {
stat_T st;
/* /*
* There is a small chance that we removed the original, * If we failed to open the file, we don't need a backup.
* try to move the copy in its place. * Throw it away. If we moved or removed the original file
* This may not work if the vim_rename() fails. * try to put the backup in its place.
* In that case we leave the copy around.
*/ */
/* If file does not exist, put the copy in its place */ if (backup != NULL && wfname == fname)
if (mch_stat((char *)fname, &st) < 0) {
vim_rename(backup, fname); if (backup_copy)
/* if original file does exist throw away the copy */ {
if (mch_stat((char *)fname, &st) >= 0) /*
mch_remove(backup); * There is a small chance that we removed the
} * original, try to move the copy in its place.
else * This may not work if the vim_rename() fails.
{ * In that case we leave the copy around.
/* try to put the original file back */ */
vim_rename(backup, fname); /* If file does not exist, put the copy in its
} * place */
} if (mch_stat((char *)fname, &st) < 0)
vim_rename(backup, fname);
/* if original file does exist throw away the copy
*/
if (mch_stat((char *)fname, &st) >= 0)
mch_remove(backup);
}
else
{
/* try to put the original file back */
vim_rename(backup, fname);
}
}
/* if original file no longer exists give an extra warning */ /* if original file no longer exists give an extra warning
if (!newfile && mch_stat((char *)fname, &st) < 0) */
end = 0; if (!newfile && mch_stat((char *)fname, &st) < 0)
} end = 0;
}
#ifdef FEAT_MBYTE #ifdef FEAT_MBYTE
if (wfname != fname) if (wfname != fname)
vim_free(wfname); vim_free(wfname);
#endif #endif
goto fail; goto fail;
} }
errmsg = NULL; write_info.bw_fd = fd;
#if defined(MACOS_CLASSIC) || defined(WIN3264) #if defined(MACOS_CLASSIC) || defined(WIN3264)
/* TODO: Is it need for MACOS_X? (Dany) */ /* TODO: Is it need for MACOS_X? (Dany) */
/* /*
* On macintosh copy the original files attributes (i.e. the backup) * On macintosh copy the original files attributes (i.e. the backup)
* This is done in order to preserve the resource fork and the * This is done in order to preserve the resource fork and the
* Finder attribute (label, comments, custom icons, file creator) * Finder attribute (label, comments, custom icons, file creator)
*/ */
if (backup != NULL && overwriting && !append) if (backup != NULL && overwriting && !append)
{ {
if (backup_copy) if (backup_copy)
(void)mch_copy_file_attribute(wfname, backup); (void)mch_copy_file_attribute(wfname, backup);
else else
(void)mch_copy_file_attribute(backup, wfname); (void)mch_copy_file_attribute(backup, wfname);
} }
if (!overwriting && !append) if (!overwriting && !append)
{ {
if (buf->b_ffname != NULL) if (buf->b_ffname != NULL)
(void)mch_copy_file_attribute(buf->b_ffname, wfname); (void)mch_copy_file_attribute(buf->b_ffname, wfname);
/* Should copy resource fork */ /* Should copy resource fork */
} }
#endif #endif
write_info.bw_fd = fd;
#ifdef FEAT_CRYPT #ifdef FEAT_CRYPT
if (*buf->b_p_key != NUL && !filtering) if (*buf->b_p_key != NUL && !filtering)
{
char_u *header;
int header_len;
buf->b_cryptstate = crypt_create_for_writing(crypt_get_method_nr(buf),
buf->b_p_key, &header, &header_len);
if (buf->b_cryptstate == NULL || header == NULL)
end = 0;
else
{
/* Write magic number, so that Vim knows how this file is
* encrypted when reading it back. */
write_info.bw_buf = header;
write_info.bw_len = header_len;
write_info.bw_flags = FIO_NOCONVERT;
if (buf_write_bytes(&write_info) == FAIL)
end = 0;
wb_flags |= FIO_ENCRYPTED;
vim_free(header);
}
}
#endif
write_info.bw_buf = buffer;
nchars = 0;
/* use "++bin", "++nobin" or 'binary' */
if (eap != NULL && eap->force_bin != 0)
write_bin = (eap->force_bin == FORCE_BIN);
else
write_bin = buf->b_p_bin;
#ifdef FEAT_MBYTE
/*
* The BOM is written just after the encryption magic number.
* Skip it when appending and the file already existed, the BOM only makes
* sense at the start of the file.
*/
if (buf->b_p_bomb && !write_bin && (!append || perm < 0))
{
write_info.bw_len = make_bom(buffer, fenc);
if (write_info.bw_len > 0)
{
/* don't convert, do encryption */
write_info.bw_flags = FIO_NOCONVERT | wb_flags;
if (buf_write_bytes(&write_info) == FAIL)
end = 0;
else
nchars += write_info.bw_len;
}
}
write_info.bw_start_lnum = start;
#endif
#ifdef FEAT_PERSISTENT_UNDO
write_undo_file = (buf->b_p_udf && overwriting && !append
&& !filtering && reset_changed);
if (write_undo_file)
/* Prepare for computing the hash value of the text. */
sha256_start(&sha_ctx);
#endif
write_info.bw_len = bufsize;
#ifdef HAS_BW_FLAGS
write_info.bw_flags = wb_flags;
#endif
fileformat = get_fileformat_force(buf, eap);
s = buffer;
len = 0;
for (lnum = start; lnum <= end; ++lnum)
{
/*
* The next while loop is done once for each character written.
* Keep it fast!
*/
ptr = ml_get_buf(buf, lnum, FALSE) - 1;
#ifdef FEAT_PERSISTENT_UNDO
if (write_undo_file)
sha256_update(&sha_ctx, ptr + 1, (UINT32_T)(STRLEN(ptr + 1) + 1));
#endif
while ((c = *++ptr) != NUL)
{
if (c == NL)
*s = NUL; /* replace newlines with NULs */
else if (c == CAR && fileformat == EOL_MAC)
*s = NL; /* Mac: replace CRs with NLs */
else
*s = c;
++s;
if (++len != bufsize)
continue;
if (buf_write_bytes(&write_info) == FAIL)
{ {
end = 0; /* write error: break loop */ char_u *header;
break; int header_len;
}
nchars += bufsize; buf->b_cryptstate = crypt_create_for_writing(
s = buffer; crypt_get_method_nr(buf),
len = 0; buf->b_p_key, &header, &header_len);
#ifdef FEAT_MBYTE if (buf->b_cryptstate == NULL || header == NULL)
write_info.bw_start_lnum = lnum; end = 0;
#endif else
}
/* write failed or last line has no EOL: stop here */
if (end == 0
|| (lnum == end
&& (write_bin || !buf->b_p_fixeol)
&& (lnum == buf->b_no_eol_lnum
|| (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol))))
{
++lnum; /* written the line, count it */
no_eol = TRUE;
break;
}
if (fileformat == EOL_UNIX)
*s++ = NL;
else
{
*s++ = CAR; /* EOL_MAC or EOL_DOS: write CR */
if (fileformat == EOL_DOS) /* write CR-NL */
{
if (++len == bufsize)
{ {
/* Write magic number, so that Vim knows how this file is
* encrypted when reading it back. */
write_info.bw_buf = header;
write_info.bw_len = header_len;
write_info.bw_flags = FIO_NOCONVERT;
if (buf_write_bytes(&write_info) == FAIL) if (buf_write_bytes(&write_info) == FAIL)
{ end = 0;
end = 0; /* write error: break loop */ wb_flags |= FIO_ENCRYPTED;
break; vim_free(header);
}
nchars += bufsize;
s = buffer;
len = 0;
} }
*s++ = NL;
} }
#endif
} }
if (++len == bufsize && end) errmsg = NULL;
{
if (buf_write_bytes(&write_info) == FAIL)
{
end = 0; /* write error: break loop */
break;
}
nchars += bufsize;
s = buffer;
len = 0;
ui_breakcheck(); write_info.bw_buf = buffer;
if (got_int) nchars = 0;
{
end = 0; /* Interrupted, break loop */ /* use "++bin", "++nobin" or 'binary' */
break; if (eap != NULL && eap->force_bin != 0)
} write_bin = (eap->force_bin == FORCE_BIN);
} else
#ifdef VMS write_bin = buf->b_p_bin;
#ifdef FEAT_MBYTE
/* /*
* On VMS there is a problem: newlines get added when writing blocks * The BOM is written just after the encryption magic number.
* at a time. Fix it by writing a line at a time. * Skip it when appending and the file already existed, the BOM only
* This is much slower! * makes sense at the start of the file.
* Explanation: VAX/DECC RTL insists that records in some RMS
* structures end with a newline (carriage return) character, and if
* they don't it adds one.
* With other RMS structures it works perfect without this fix.
*/ */
if (buf->b_fab_rfm == FAB$C_VFC if (buf->b_p_bomb && !write_bin && (!append || perm < 0))
|| ((buf->b_fab_rat & (FAB$M_FTN | FAB$M_CR)) != 0))
{ {
int b2write; write_info.bw_len = make_bom(buffer, fenc);
if (write_info.bw_len > 0)
buf->b_fab_mrs = (buf->b_fab_mrs == 0
? MIN(4096, bufsize)
: MIN(buf->b_fab_mrs, bufsize));
b2write = len;
while (b2write > 0)
{ {
write_info.bw_len = MIN(b2write, buf->b_fab_mrs); /* don't convert, do encryption */
write_info.bw_flags = FIO_NOCONVERT | wb_flags;
if (buf_write_bytes(&write_info) == FAIL)
end = 0;
else
nchars += write_info.bw_len;
}
}
write_info.bw_start_lnum = start;
#endif
#ifdef FEAT_PERSISTENT_UNDO
write_undo_file = (buf->b_p_udf
&& overwriting
&& !append
&& !filtering
&& reset_changed
&& !checking_conversion);
if (write_undo_file)
/* Prepare for computing the hash value of the text. */
sha256_start(&sha_ctx);
#endif
write_info.bw_len = bufsize;
#ifdef HAS_BW_FLAGS
write_info.bw_flags = wb_flags;
#endif
fileformat = get_fileformat_force(buf, eap);
s = buffer;
len = 0;
for (lnum = start; lnum <= end; ++lnum)
{
/*
* The next while loop is done once for each character written.
* Keep it fast!
*/
ptr = ml_get_buf(buf, lnum, FALSE) - 1;
#ifdef FEAT_PERSISTENT_UNDO
if (write_undo_file)
sha256_update(&sha_ctx, ptr + 1,
(UINT32_T)(STRLEN(ptr + 1) + 1));
#endif
while ((c = *++ptr) != NUL)
{
if (c == NL)
*s = NUL; /* replace newlines with NULs */
else if (c == CAR && fileformat == EOL_MAC)
*s = NL; /* Mac: replace CRs with NLs */
else
*s = c;
++s;
if (++len != bufsize)
continue;
if (buf_write_bytes(&write_info) == FAIL) if (buf_write_bytes(&write_info) == FAIL)
{ {
end = 0; end = 0; /* write error: break loop */
break; break;
} }
b2write -= MIN(b2write, buf->b_fab_mrs); nchars += bufsize;
} s = buffer;
write_info.bw_len = bufsize; len = 0;
nchars += len; #ifdef FEAT_MBYTE
s = buffer; write_info.bw_start_lnum = lnum;
len = 0;
}
#endif #endif
} }
if (len > 0 && end > 0) /* write failed or last line has no EOL: stop here */
{ if (end == 0
write_info.bw_len = len; || (lnum == end
if (buf_write_bytes(&write_info) == FAIL) && (write_bin || !buf->b_p_fixeol)
end = 0; /* write error */ && (lnum == buf->b_no_eol_lnum
nchars += len; || (lnum == buf->b_ml.ml_line_count
&& !buf->b_p_eol))))
{
++lnum; /* written the line, count it */
no_eol = TRUE;
break;
}
if (fileformat == EOL_UNIX)
*s++ = NL;
else
{
*s++ = CAR; /* EOL_MAC or EOL_DOS: write CR */
if (fileformat == EOL_DOS) /* write CR-NL */
{
if (++len == bufsize)
{
if (buf_write_bytes(&write_info) == FAIL)
{
end = 0; /* write error: break loop */
break;
}
nchars += bufsize;
s = buffer;
len = 0;
}
*s++ = NL;
}
}
if (++len == bufsize && end)
{
if (buf_write_bytes(&write_info) == FAIL)
{
end = 0; /* write error: break loop */
break;
}
nchars += bufsize;
s = buffer;
len = 0;
ui_breakcheck();
if (got_int)
{
end = 0; /* Interrupted, break loop */
break;
}
}
#ifdef VMS
/*
* On VMS there is a problem: newlines get added when writing
* blocks at a time. Fix it by writing a line at a time.
* This is much slower!
* Explanation: VAX/DECC RTL insists that records in some RMS
* structures end with a newline (carriage return) character, and
* if they don't it adds one.
* With other RMS structures it works perfect without this fix.
*/
if (buf->b_fab_rfm == FAB$C_VFC
|| ((buf->b_fab_rat & (FAB$M_FTN | FAB$M_CR)) != 0))
{
int b2write;
buf->b_fab_mrs = (buf->b_fab_mrs == 0
? MIN(4096, bufsize)
: MIN(buf->b_fab_mrs, bufsize));
b2write = len;
while (b2write > 0)
{
write_info.bw_len = MIN(b2write, buf->b_fab_mrs);
if (buf_write_bytes(&write_info) == FAIL)
{
end = 0;
break;
}
b2write -= MIN(b2write, buf->b_fab_mrs);
}
write_info.bw_len = bufsize;
nchars += len;
s = buffer;
len = 0;
}
#endif
}
if (len > 0 && end > 0)
{
write_info.bw_len = len;
if (buf_write_bytes(&write_info) == FAIL)
end = 0; /* write error */
nchars += len;
}
/* Stop when writing done or an error was encountered. */
if (!checking_conversion || end == 0)
break;
/* If no error happened until now, writing should be ok, so loop to
* really write the buffer. */
} }
#if defined(UNIX) && defined(HAVE_FSYNC) /* If we started writing, finish writing. Also when an error was
/* On many journalling file systems there is a bug that causes both the * encountered. */
* original and the backup file to be lost when halting the system right if (!checking_conversion)
* after writing the file. That's because only the meta-data is
* journalled. Syncing the file slows down the system, but assures it has
* been written to disk and we don't lose it.
* 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 && fsync(fd) != 0 && !device)
{ {
errmsg = (char_u *)_("E667: Fsync failed"); #if defined(UNIX) && defined(HAVE_FSYNC)
end = 0; /*
} * 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
* journalled. Syncing the file slows down the system, but assures it
* has been written to disk and we don't lose it.
* 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 && fsync(fd) != 0 && !device)
{
errmsg = (char_u *)_("E667: Fsync failed");
end = 0;
}
#endif #endif
#if defined(HAVE_SELINUX) || defined(HAVE_SMACK) #if defined(HAVE_SELINUX) || defined(HAVE_SMACK)
/* Probably need to set the security context. */ /* Probably need to set the security context. */
if (!backup_copy) if (!backup_copy)
mch_copy_sec(backup, wfname); mch_copy_sec(backup, wfname);
#endif #endif
#ifdef UNIX #ifdef UNIX
/* When creating a new file, set its owner/group to that of the original /* When creating a new file, set its owner/group to that of the
* file. Get the new device and inode number. */ * original file. Get the new device and inode number. */
if (backup != NULL && !backup_copy) if (backup != NULL && !backup_copy)
{
# ifdef HAVE_FCHOWN
stat_T st;
/* don't change the owner when it's already OK, some systems remove
* permission or ACL stuff */
if (mch_stat((char *)wfname, &st) < 0
|| st.st_uid != st_old.st_uid
|| st.st_gid != st_old.st_gid)
{ {
ignored = fchown(fd, st_old.st_uid, st_old.st_gid); # ifdef HAVE_FCHOWN
if (perm >= 0) /* set permission again, may have changed */ stat_T st;
(void)mch_setperm(wfname, perm);
} /* don't change the owner when it's already OK, some systems remove
* permission or ACL stuff */
if (mch_stat((char *)wfname, &st) < 0
|| st.st_uid != st_old.st_uid
|| st.st_gid != st_old.st_gid)
{
ignored = fchown(fd, st_old.st_uid, st_old.st_gid);
if (perm >= 0) /* set permission again, may have changed */
(void)mch_setperm(wfname, perm);
}
# endif # endif
buf_setino(buf); buf_setino(buf);
} }
else if (!buf->b_dev_valid) else if (!buf->b_dev_valid)
/* Set the inode when creating a new file. */ /* Set the inode when creating a new file. */
buf_setino(buf); buf_setino(buf);
#endif #endif
if (close(fd) != 0) if (close(fd) != 0)
{ {
errmsg = (char_u *)_("E512: Close failed"); errmsg = (char_u *)_("E512: Close failed");
end = 0; end = 0;
} }
#ifdef UNIX #ifdef UNIX
if (made_writable) if (made_writable)
perm &= ~0200; /* reset 'w' bit for security reasons */ perm &= ~0200; /* reset 'w' bit for security reasons */
#endif #endif
if (perm >= 0) /* set perm. of new file same as old file */ if (perm >= 0) /* set perm. of new file same as old file */
(void)mch_setperm(wfname, perm); (void)mch_setperm(wfname, perm);
#ifdef HAVE_ACL #ifdef HAVE_ACL
/* /*
* Probably need to set the ACL before changing the user (can't set the * Probably need to set the ACL before changing the user (can't set the
* ACL on a file the user doesn't own). * ACL on a file the user doesn't own).
* On Solaris, with ZFS and the aclmode property set to "discard" (the * On Solaris, with ZFS and the aclmode property set to "discard" (the
* default), chmod() discards all part of a file's ACL that don't represent * default), chmod() discards all part of a file's ACL that don't
* the mode of the file. It's non-trivial for us to discover whether we're * represent the mode of the file. It's non-trivial for us to discover
* in that situation, so we simply always re-set the ACL. * whether we're in that situation, so we simply always re-set the ACL.
*/ */
# ifndef HAVE_SOLARIS_ZFS_ACL # ifndef HAVE_SOLARIS_ZFS_ACL
if (!backup_copy) if (!backup_copy)
# endif # endif
mch_set_acl(wfname, acl); mch_set_acl(wfname, acl);
#endif #endif
#ifdef FEAT_CRYPT #ifdef FEAT_CRYPT
if (buf->b_cryptstate != NULL) if (buf->b_cryptstate != NULL)
{ {
crypt_free_state(buf->b_cryptstate); crypt_free_state(buf->b_cryptstate);
buf->b_cryptstate = NULL; buf->b_cryptstate = NULL;
} }
#endif #endif
#if defined(FEAT_MBYTE) && defined(FEAT_EVAL) #if defined(FEAT_MBYTE) && defined(FEAT_EVAL)
if (wfname != fname) if (wfname != fname)
{
/*
* The file was written to a temp file, now it needs to be converted
* with 'charconvert' to (overwrite) the output file.
*/
if (end != 0)
{ {
if (eval_charconvert(enc_utf8 ? (char_u *)"utf-8" : p_enc, fenc, /*
wfname, fname) == FAIL) * The file was written to a temp file, now it needs to be
* converted with 'charconvert' to (overwrite) the output file.
*/
if (end != 0)
{ {
write_info.bw_conv_error = TRUE; if (eval_charconvert(enc_utf8 ? (char_u *)"utf-8" : p_enc,
end = 0; fenc, wfname, fname) == FAIL)
{
write_info.bw_conv_error = TRUE;
end = 0;
}
} }
mch_remove(wfname);
vim_free(wfname);
} }
mch_remove(wfname);
vim_free(wfname);
}
#endif #endif
}
if (end == 0) if (end == 0)
{ {
/*
* Error encountered.
*/
if (errmsg == NULL) if (errmsg == NULL)
{ {
#ifdef FEAT_MBYTE #ifdef FEAT_MBYTE
@@ -5690,6 +5749,10 @@ buf_write_bytes(struct bw_info *ip)
} }
#endif /* FEAT_MBYTE */ #endif /* FEAT_MBYTE */
if (ip->bw_fd < 0)
/* Only checking conversion, which is OK if we get here. */
return OK;
#ifdef FEAT_CRYPT #ifdef FEAT_CRYPT
if (flags & FIO_ENCRYPTED) if (flags & FIO_ENCRYPTED)
{ {

View File

@@ -31,3 +31,21 @@ func Test_writefile_fails_gently()
call assert_fails('call writefile([], [])', 'E730:') call assert_fails('call writefile([], [])', 'E730:')
endfunc endfunc
func Test_writefile_fails_conversion()
if !has('multi_byte') || !has('iconv')
return
endif
set nobackup nowritebackup
new
let contents = ["line one", "line two"]
call writefile(contents, 'Xfile')
edit Xfile
call setline(1, ["first line", "cannot convert \u010b", "third line"])
call assert_fails('write ++enc=cp932')
call assert_equal(contents, readfile('Xfile'))
call delete('Xfile')
bwipe!
set backup& writebackup&
endfunc

View File

@@ -764,6 +764,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
685,
/**/ /**/
684, 684,
/**/ /**/