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:
771
src/fileio.c
771
src/fileio.c
@@ -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)
|
||||||
{
|
{
|
||||||
|
@@ -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
|
||||||
|
@@ -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,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user