Mercurial > vim
view src/bufwrite.c @ 34790:1104d85c7dcc v9.1.0269
patch 9.1.0269: Test for TextChanged is still flaky with ASAN
Commit: https://github.com/vim/vim/commit/e9ff79a7c9affea970f50de2aa65f62080b55323
Author: zeertzjq <zeertzjq@outlook.com>
Date: Fri Apr 5 20:07:39 2024 +0200
patch 9.1.0269: Test for TextChanged is still flaky with ASAN
Problem: Test for TextChanged is still flaky with ASAN.
Solution: Don't index the result of readfile().
(zeertzjq)
It turns out that with ASAN the file may become empty during a write
even if it's non-empty both before and after the write, in which case
indexing the result of readfile() will error, so use join() instead.
Also don't delete the file halfway the test, just in case it may cause
errors on the next read.
closes: #14421
Signed-off-by: zeertzjq <zeertzjq@outlook.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Fri, 05 Apr 2024 20:15:06 +0200 |
parents | dcfbfe57141c |
children | 34a2b628cba5 |
line wrap: on
line source
/* vi:set ts=8 sts=4 sw=4 noet: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ /* * bufwrite.c: functions for writing a buffer */ #include "vim.h" #if defined(HAVE_UTIME) && defined(HAVE_UTIME_H) # include <utime.h> // for struct utimbuf #endif #define SMALLBUFSIZE 256 // size of emergency write buffer /* * Structure to pass arguments from buf_write() to buf_write_bytes(). */ struct bw_info { int bw_fd; // file descriptor char_u *bw_buf; // buffer with data to be written int bw_len; // length of data int bw_flags; // FIO_ flags #ifdef FEAT_CRYPT buf_T *bw_buffer; // buffer being written int bw_finish; // finish encrypting #endif char_u bw_rest[CONV_RESTLEN]; // not converted bytes int bw_restlen; // nr of bytes in bw_rest[] int bw_first; // first write call char_u *bw_conv_buf; // buffer for writing converted chars size_t bw_conv_buflen; // size of bw_conv_buf int bw_conv_error; // set for conversion error linenr_T bw_conv_error_lnum; // first line with error or zero linenr_T bw_start_lnum; // line number at start of buffer #ifdef USE_ICONV iconv_t bw_iconv_fd; // descriptor for iconv() or -1 #endif }; /* * Convert a Unicode character to bytes. * Return TRUE for an error, FALSE when it's OK. */ static int ucs2bytes( unsigned c, // in: character char_u **pp, // in/out: pointer to result int flags) // FIO_ flags { char_u *p = *pp; int error = FALSE; int cc; if (flags & FIO_UCS4) { if (flags & FIO_ENDIAN_L) { *p++ = c; *p++ = (c >> 8); *p++ = (c >> 16); *p++ = (c >> 24); } else { *p++ = (c >> 24); *p++ = (c >> 16); *p++ = (c >> 8); *p++ = c; } } else if (flags & (FIO_UCS2 | FIO_UTF16)) { if (c >= 0x10000) { if (flags & FIO_UTF16) { // Make two words, ten bits of the character in each. First // word is 0xd800 - 0xdbff, second one 0xdc00 - 0xdfff c -= 0x10000; if (c >= 0x100000) error = TRUE; cc = ((c >> 10) & 0x3ff) + 0xd800; if (flags & FIO_ENDIAN_L) { *p++ = cc; *p++ = ((unsigned)cc >> 8); } else { *p++ = ((unsigned)cc >> 8); *p++ = cc; } c = (c & 0x3ff) + 0xdc00; } else error = TRUE; } if (flags & FIO_ENDIAN_L) { *p++ = c; *p++ = (c >> 8); } else { *p++ = (c >> 8); *p++ = c; } } else // Latin1 { if (c >= 0x100) { error = TRUE; *p++ = 0xBF; } else *p++ = c; } *pp = p; return error; } /* * Call write() to write a number of bytes to the file. * Handles encryption and 'encoding' conversion. * * Return FAIL for failure, OK otherwise. */ static int buf_write_bytes(struct bw_info *ip) { int wlen; char_u *buf = ip->bw_buf; // data to write int len = ip->bw_len; // length of data int flags = ip->bw_flags; // extra flags // Skip conversion when writing the crypt magic number or the BOM. if (!(flags & FIO_NOCONVERT)) { char_u *p; unsigned c; int n; if (flags & FIO_UTF8) { // Convert latin1 in the buffer to UTF-8 in the file. p = ip->bw_conv_buf; // translate to buffer for (wlen = 0; wlen < len; ++wlen) p += utf_char2bytes(buf[wlen], p); buf = ip->bw_conv_buf; len = (int)(p - ip->bw_conv_buf); } else if (flags & (FIO_UCS4 | FIO_UTF16 | FIO_UCS2 | FIO_LATIN1)) { // Convert UTF-8 bytes in the buffer to UCS-2, UCS-4, UTF-16 or // Latin1 chars in the file. if (flags & FIO_LATIN1) p = buf; // translate in-place (can only get shorter) else p = ip->bw_conv_buf; // translate to buffer for (wlen = 0; wlen < len; wlen += n) { if (wlen == 0 && ip->bw_restlen != 0) { int l; // Use remainder of previous call. Append the start of // buf[] to get a full sequence. Might still be too // short! l = CONV_RESTLEN - ip->bw_restlen; if (l > len) l = len; mch_memmove(ip->bw_rest + ip->bw_restlen, buf, (size_t)l); n = utf_ptr2len_len(ip->bw_rest, ip->bw_restlen + l); if (n > ip->bw_restlen + len) { // We have an incomplete byte sequence at the end to // be written. We can't convert it without the // remaining bytes. Keep them for the next call. if (ip->bw_restlen + len > CONV_RESTLEN) return FAIL; ip->bw_restlen += len; break; } if (n > 1) c = utf_ptr2char(ip->bw_rest); else c = ip->bw_rest[0]; if (n >= ip->bw_restlen) { n -= ip->bw_restlen; ip->bw_restlen = 0; } else { ip->bw_restlen -= n; mch_memmove(ip->bw_rest, ip->bw_rest + n, (size_t)ip->bw_restlen); n = 0; } } else { n = utf_ptr2len_len(buf + wlen, len - wlen); if (n > len - wlen) { // We have an incomplete byte sequence at the end to // be written. We can't convert it without the // remaining bytes. Keep them for the next call. if (len - wlen > CONV_RESTLEN) return FAIL; ip->bw_restlen = len - wlen; mch_memmove(ip->bw_rest, buf + wlen, (size_t)ip->bw_restlen); break; } if (n > 1) c = utf_ptr2char(buf + wlen); else c = buf[wlen]; } if (ucs2bytes(c, &p, flags) && !ip->bw_conv_error) { ip->bw_conv_error = TRUE; ip->bw_conv_error_lnum = ip->bw_start_lnum; } if (c == NL) ++ip->bw_start_lnum; } if (flags & FIO_LATIN1) len = (int)(p - buf); else { buf = ip->bw_conv_buf; len = (int)(p - ip->bw_conv_buf); } } #ifdef MSWIN else if (flags & FIO_CODEPAGE) { // Convert UTF-8 or codepage to UCS-2 and then to MS-Windows // codepage. char_u *from; size_t fromlen; char_u *to; int u8c; BOOL bad = FALSE; int needed; if (ip->bw_restlen > 0) { // Need to concatenate the remainder of the previous call and // the bytes of the current call. Use the end of the // conversion buffer for this. fromlen = len + ip->bw_restlen; from = ip->bw_conv_buf + ip->bw_conv_buflen - fromlen; mch_memmove(from, ip->bw_rest, (size_t)ip->bw_restlen); mch_memmove(from + ip->bw_restlen, buf, (size_t)len); } else { from = buf; fromlen = len; } to = ip->bw_conv_buf; if (enc_utf8) { // Convert from UTF-8 to UCS-2, to the start of the buffer. // The buffer has been allocated to be big enough. while (fromlen > 0) { n = (int)utf_ptr2len_len(from, (int)fromlen); if (n > (int)fromlen) // incomplete byte sequence break; u8c = utf_ptr2char(from); *to++ = (u8c & 0xff); *to++ = (u8c >> 8); fromlen -= n; from += n; } // Copy remainder to ip->bw_rest[] to be used for the next // call. if (fromlen > CONV_RESTLEN) { // weird overlong sequence ip->bw_conv_error = TRUE; return FAIL; } mch_memmove(ip->bw_rest, from, fromlen); ip->bw_restlen = (int)fromlen; } else { // Convert from enc_codepage to UCS-2, to the start of the // buffer. The buffer has been allocated to be big enough. ip->bw_restlen = 0; needed = MultiByteToWideChar(enc_codepage, MB_ERR_INVALID_CHARS, (LPCSTR)from, (int)fromlen, NULL, 0); if (needed == 0) { // When conversion fails there may be a trailing byte. needed = MultiByteToWideChar(enc_codepage, MB_ERR_INVALID_CHARS, (LPCSTR)from, (int)fromlen - 1, NULL, 0); if (needed == 0) { // Conversion doesn't work. ip->bw_conv_error = TRUE; return FAIL; } // Save the trailing byte for the next call. ip->bw_rest[0] = from[fromlen - 1]; ip->bw_restlen = 1; } needed = MultiByteToWideChar(enc_codepage, MB_ERR_INVALID_CHARS, (LPCSTR)from, (int)(fromlen - ip->bw_restlen), (LPWSTR)to, needed); if (needed == 0) { // Safety check: Conversion doesn't work. ip->bw_conv_error = TRUE; return FAIL; } to += needed * 2; } fromlen = to - ip->bw_conv_buf; buf = to; # ifdef CP_UTF8 // VC 4.1 doesn't define CP_UTF8 if (FIO_GET_CP(flags) == CP_UTF8) { // Convert from UCS-2 to UTF-8, using the remainder of the // conversion buffer. Fails when out of space. for (from = ip->bw_conv_buf; fromlen > 1; fromlen -= 2) { u8c = *from++; u8c += (*from++ << 8); to += utf_char2bytes(u8c, to); if (to + 6 >= ip->bw_conv_buf + ip->bw_conv_buflen) { ip->bw_conv_error = TRUE; return FAIL; } } len = (int)(to - buf); } else # endif { // Convert from UCS-2 to the codepage, using the remainder of // the conversion buffer. If the conversion uses the default // character "0", the data doesn't fit in this encoding, so // fail. len = WideCharToMultiByte(FIO_GET_CP(flags), 0, (LPCWSTR)ip->bw_conv_buf, (int)fromlen / sizeof(WCHAR), (LPSTR)to, (int)(ip->bw_conv_buflen - fromlen), 0, &bad); if (bad) { ip->bw_conv_error = TRUE; return FAIL; } } } #endif #ifdef MACOS_CONVERT else if (flags & FIO_MACROMAN) { // Convert UTF-8 or latin1 to Apple MacRoman. char_u *from; size_t fromlen; if (ip->bw_restlen > 0) { // Need to concatenate the remainder of the previous call and // the bytes of the current call. Use the end of the // conversion buffer for this. fromlen = len + ip->bw_restlen; from = ip->bw_conv_buf + ip->bw_conv_buflen - fromlen; mch_memmove(from, ip->bw_rest, (size_t)ip->bw_restlen); mch_memmove(from + ip->bw_restlen, buf, (size_t)len); } else { from = buf; fromlen = len; } if (enc2macroman(from, fromlen, ip->bw_conv_buf, &len, ip->bw_conv_buflen, ip->bw_rest, &ip->bw_restlen) == FAIL) { ip->bw_conv_error = TRUE; return FAIL; } buf = ip->bw_conv_buf; } #endif #ifdef USE_ICONV if (ip->bw_iconv_fd != (iconv_t)-1) { const char *from; size_t fromlen; char *to; size_t tolen; // Convert with iconv(). if (ip->bw_restlen > 0) { char *fp; // Need to concatenate the remainder of the previous call and // the bytes of the current call. Use the end of the // conversion buffer for this. fromlen = len + ip->bw_restlen; fp = (char *)ip->bw_conv_buf + ip->bw_conv_buflen - fromlen; mch_memmove(fp, ip->bw_rest, (size_t)ip->bw_restlen); mch_memmove(fp + ip->bw_restlen, buf, (size_t)len); from = fp; tolen = ip->bw_conv_buflen - fromlen; } else { from = (const char *)buf; fromlen = len; tolen = ip->bw_conv_buflen; } to = (char *)ip->bw_conv_buf; if (ip->bw_first) { size_t save_len = tolen; // output the initial shift state sequence (void)iconv(ip->bw_iconv_fd, NULL, NULL, &to, &tolen); // There is a bug in iconv() on Linux (which appears to be // wide-spread) which sets "to" to NULL and messes up "tolen". if (to == NULL) { to = (char *)ip->bw_conv_buf; tolen = save_len; } ip->bw_first = FALSE; } // If iconv() has an error or there is not enough room, fail. if ((iconv(ip->bw_iconv_fd, (void *)&from, &fromlen, &to, &tolen) == (size_t)-1 && ICONV_ERRNO != ICONV_EINVAL) || fromlen > CONV_RESTLEN) { ip->bw_conv_error = TRUE; return FAIL; } // copy remainder to ip->bw_rest[] to be used for the next call. if (fromlen > 0) mch_memmove(ip->bw_rest, (void *)from, fromlen); ip->bw_restlen = (int)fromlen; buf = ip->bw_conv_buf; len = (int)((char_u *)to - ip->bw_conv_buf); } #endif } if (ip->bw_fd < 0) // Only checking conversion, which is OK if we get here. return OK; #ifdef FEAT_CRYPT if (flags & FIO_ENCRYPTED) { // Encrypt the data. Do it in-place if possible, otherwise use an // allocated buffer. # ifdef CRYPT_NOT_INPLACE if (crypt_works_inplace(ip->bw_buffer->b_cryptstate)) { # endif crypt_encode_inplace(ip->bw_buffer->b_cryptstate, buf, len, ip->bw_finish); # ifdef CRYPT_NOT_INPLACE } else { char_u *outbuf; len = crypt_encode_alloc(curbuf->b_cryptstate, buf, len, &outbuf, ip->bw_finish); if (len == 0) return OK; // Crypt layer is buffering, will flush later. wlen = write_eintr(ip->bw_fd, outbuf, len); vim_free(outbuf); return (wlen < len) ? FAIL : OK; } # endif } #endif wlen = write_eintr(ip->bw_fd, buf, len); return (wlen < len) ? FAIL : OK; } /* * Check modification time of file, before writing to it. * The size isn't checked, because using a tool like "gzip" takes care of * using the same timestamp but can't set the size. */ static int check_mtime(buf_T *buf, stat_T *st) { if (buf->b_mtime_read != 0 && time_differs(st, buf->b_mtime_read, buf->b_mtime_read_ns)) { msg_scroll = TRUE; // don't overwrite messages here msg_silent = 0; // must give this prompt // don't use emsg() here, don't want to flush the buffers msg_attr(_("WARNING: The file has been changed since reading it!!!"), HL_ATTR(HLF_E)); if (ask_yesno((char_u *)_("Do you really want to write to it"), TRUE) == 'n') return FAIL; msg_scroll = FALSE; // always overwrite the file message now } return OK; } /* * Generate a BOM in "buf[4]" for encoding "name". * Return the length of the BOM (zero when no BOM). */ static int make_bom(char_u *buf, char_u *name) { int flags; char_u *p; flags = get_fio_flags(name); // Can't put a BOM in a non-Unicode file. if (flags == FIO_LATIN1 || flags == 0) return 0; if (flags == FIO_UTF8) // UTF-8 { buf[0] = 0xef; buf[1] = 0xbb; buf[2] = 0xbf; return 3; } p = buf; (void)ucs2bytes(0xfeff, &p, flags); return (int)(p - buf); } #ifdef UNIX static void set_file_time( char_u *fname, time_t atime, // access time time_t mtime) // modification time { # if defined(HAVE_UTIME) && defined(HAVE_UTIME_H) struct utimbuf buf; buf.actime = atime; buf.modtime = mtime; (void)utime((char *)fname, &buf); # else # if defined(HAVE_UTIMES) struct timeval tvp[2]; tvp[0].tv_sec = atime; tvp[0].tv_usec = 0; tvp[1].tv_sec = mtime; tvp[1].tv_usec = 0; # ifdef NeXT (void)utimes((char *)fname, tvp); # else (void)utimes((char *)fname, (const struct timeval *)&tvp); # endif # endif # endif } #endif // UNIX char * new_file_message(void) { return shortmess(SHM_NEW) ? _("[New]") : _("[New File]"); } /* * buf_write() - write to file "fname" lines "start" through "end" * * We do our own buffering here because fwrite() is so slow. * * If "forceit" is true, we don't care for errors when attempting backups. * In case of an error everything possible is done to restore the original * file. But when "forceit" is TRUE, we risk losing it. * * When "reset_changed" is TRUE and "append" == FALSE and "start" == 1 and * "end" == curbuf->b_ml.ml_line_count, reset curbuf->b_changed. * * This function must NOT use NameBuff (because it's called by autowrite()). * * return FAIL for failure, OK otherwise */ int buf_write( buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_T end, exarg_T *eap, // for forced 'ff' and 'fenc', can be // NULL! int append, // append to the file int forceit, int reset_changed, int filtering) { int fd; char_u *backup = NULL; int backup_copy = FALSE; // copy the original file? int dobackup; char_u *ffname; char_u *wfname = NULL; // name of file to write to char_u *s; char_u *ptr; char_u c; int len; linenr_T lnum; long nchars; char_u *errmsg = NULL; int errmsg_allocated = FALSE; char_u *errnum = NULL; char_u *buffer; char_u smallbuf[SMALLBUFSIZE]; char_u *backup_ext; int bufsize; long perm; // file permissions int retval = OK; int newfile = FALSE; // TRUE if file doesn't exist yet int msg_save = msg_scroll; int overwriting; // TRUE if writing over original int no_eol = FALSE; // no end-of-line written int device = FALSE; // writing to a device stat_T st_old; int prev_got_int = got_int; int checking_conversion; int file_readonly = FALSE; // overwritten file is read-only #if defined(UNIX) // XXX fix me sometime? int made_writable = FALSE; // 'w' bit has been set #endif // writing everything int whole = (start == 1 && end == buf->b_ml.ml_line_count); linenr_T old_line_count = buf->b_ml.ml_line_count; int attr; int fileformat; int write_bin; struct bw_info write_info; // info for buf_write_bytes() int converted = FALSE; int notconverted = FALSE; char_u *fenc; // effective 'fileencoding' char_u *fenc_tofree = NULL; // allocated "fenc" int wb_flags = 0; #ifdef HAVE_ACL vim_acl_T acl = NULL; // ACL copied from original file to // backup or new file #endif #ifdef FEAT_PERSISTENT_UNDO int write_undo_file = FALSE; context_sha256_T sha_ctx; #endif unsigned int bkc = get_bkc_value(buf); pos_T orig_start = buf->b_op_start; pos_T orig_end = buf->b_op_end; if (fname == NULL || *fname == NUL) // safety check return FAIL; if (buf->b_ml.ml_mfp == NULL) { // This can happen during startup when there is a stray "w" in the // vimrc file. emsg(_(e_empty_buffer)); return FAIL; } // Disallow writing from .exrc and .vimrc in current directory for // security reasons. if (check_secure()) return FAIL; // Avoid a crash for a long name. if (STRLEN(fname) >= MAXPATHL) { emsg(_(e_name_too_long)); return FAIL; } // must init bw_conv_buf and bw_iconv_fd before jumping to "fail" write_info.bw_conv_buf = NULL; write_info.bw_conv_error = FALSE; write_info.bw_conv_error_lnum = 0; write_info.bw_restlen = 0; #ifdef USE_ICONV write_info.bw_iconv_fd = (iconv_t)-1; #endif #ifdef FEAT_CRYPT write_info.bw_buffer = buf; write_info.bw_finish = FALSE; #endif // After writing a file changedtick changes but we don't want to display // the line. ex_no_reprint = TRUE; // If there is no file name yet, use the one for the written file. // BF_NOTEDITED is set to reflect this (in case the write fails). // Don't do this when the write is for a filter command. // Don't do this when appending. // Only do this when 'cpoptions' contains the 'F' flag. if (buf->b_ffname == NULL && reset_changed && whole && buf == curbuf && !bt_nofilename(buf) && !filtering && (!append || vim_strchr(p_cpo, CPO_FNAMEAPP) != NULL) && vim_strchr(p_cpo, CPO_FNAMEW) != NULL) { if (set_rw_fname(fname, sfname) == FAIL) return FAIL; buf = curbuf; // just in case autocmds made "buf" invalid } if (sfname == NULL) sfname = fname; // For Unix: Use the short file name whenever possible. // Avoids problems with networks and when directory names are changed. // Don't do this for MS-DOS, a "cd" in a sub-shell may have moved us to // another directory, which we don't detect ffname = fname; // remember full fname #ifdef UNIX fname = sfname; #endif if (buf->b_ffname != NULL && fnamecmp(ffname, buf->b_ffname) == 0) overwriting = TRUE; else overwriting = FALSE; if (exiting) settmode(TMODE_COOK); // when exiting allow typeahead now ++no_wait_return; // don't wait for return yet // Set '[ and '] marks to the lines to be written. buf->b_op_start.lnum = start; buf->b_op_start.col = 0; buf->b_op_end.lnum = end; buf->b_op_end.col = 0; { aco_save_T aco; int buf_ffname = FALSE; int buf_sfname = FALSE; int buf_fname_f = FALSE; int buf_fname_s = FALSE; int did_cmd = FALSE; int nofile_err = FALSE; int empty_memline = (buf->b_ml.ml_mfp == NULL); bufref_T bufref; // Apply PRE autocommands. // Set curbuf to the buffer to be written. // Careful: The autocommands may call buf_write() recursively! if (ffname == buf->b_ffname) buf_ffname = TRUE; if (sfname == buf->b_sfname) buf_sfname = TRUE; if (fname == buf->b_ffname) buf_fname_f = TRUE; if (fname == buf->b_sfname) buf_fname_s = TRUE; // Set curwin/curbuf to buf and save a few things. aucmd_prepbuf(&aco, buf); if (curbuf != buf) { // Could not find a window for "buf". Doing more might cause // problems, better bail out. return FAIL; } set_bufref(&bufref, buf); if (append) { if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEAPPENDCMD, sfname, sfname, FALSE, curbuf, eap))) { if (overwriting && bt_nofilename(curbuf)) nofile_err = TRUE; else apply_autocmds_exarg(EVENT_FILEAPPENDPRE, sfname, sfname, FALSE, curbuf, eap); } } else if (filtering) { apply_autocmds_exarg(EVENT_FILTERWRITEPRE, NULL, sfname, FALSE, curbuf, eap); } else if (reset_changed && whole) { int was_changed = curbufIsChanged(); did_cmd = apply_autocmds_exarg(EVENT_BUFWRITECMD, sfname, sfname, FALSE, curbuf, eap); if (did_cmd) { if (was_changed && !curbufIsChanged()) { // Written everything correctly and BufWriteCmd has reset // 'modified': Correct the undo information so that an // undo now sets 'modified'. u_unchanged(curbuf); u_update_save_nr(curbuf); } } else { if (overwriting && bt_nofilename(curbuf)) nofile_err = TRUE; else apply_autocmds_exarg(EVENT_BUFWRITEPRE, sfname, sfname, FALSE, curbuf, eap); } } else { if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEWRITECMD, sfname, sfname, FALSE, curbuf, eap))) { if (overwriting && bt_nofilename(curbuf)) nofile_err = TRUE; else apply_autocmds_exarg(EVENT_FILEWRITEPRE, sfname, sfname, FALSE, curbuf, eap); } } // restore curwin/curbuf and a few other things aucmd_restbuf(&aco); // In three situations we return here and don't write the file: // 1. the autocommands deleted or unloaded the buffer. // 2. The autocommands abort script processing. // 3. If one of the "Cmd" autocommands was executed. if (!bufref_valid(&bufref)) buf = NULL; if (buf == NULL || (buf->b_ml.ml_mfp == NULL && !empty_memline) || did_cmd || nofile_err #ifdef FEAT_EVAL || aborting() #endif ) { if (buf != NULL && (cmdmod.cmod_flags & CMOD_LOCKMARKS)) { // restore the original '[ and '] positions buf->b_op_start = orig_start; buf->b_op_end = orig_end; } --no_wait_return; msg_scroll = msg_save; if (nofile_err) semsg(_(e_no_matching_autocommands_for_buftype_str_buffer), curbuf->b_p_bt); if (nofile_err #ifdef FEAT_EVAL || aborting() #endif ) // An aborting error, interrupt or exception in the // autocommands. return FAIL; if (did_cmd) { if (buf == NULL) // The buffer was deleted. We assume it was written // (can't retry anyway). return OK; if (overwriting) { // Assume the buffer was written, update the timestamp. ml_timestamp(buf); if (append) buf->b_flags &= ~BF_NEW; else buf->b_flags &= ~BF_WRITE_MASK; } if (reset_changed && buf->b_changed && !append && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) // Buffer still changed, the autocommands didn't work // properly. return FAIL; return OK; } #ifdef FEAT_EVAL if (!aborting()) #endif emsg(_(e_autocommands_deleted_or_unloaded_buffer_to_be_written)); return FAIL; } // The autocommands may have changed the number of lines in the file. // When writing the whole file, adjust the end. // When writing part of the file, assume that the autocommands only // changed the number of lines that are to be written (tricky!). if (buf->b_ml.ml_line_count != old_line_count) { if (whole) // write all end = buf->b_ml.ml_line_count; else if (buf->b_ml.ml_line_count > old_line_count) // more lines end += buf->b_ml.ml_line_count - old_line_count; else // less lines { end -= old_line_count - buf->b_ml.ml_line_count; if (end < start) { --no_wait_return; msg_scroll = msg_save; emsg(_(e_autocommands_changed_number_of_lines_in_unexpected_way)); return FAIL; } } } // The autocommands may have changed the name of the buffer, which may // be kept in fname, ffname and sfname. if (buf_ffname) ffname = buf->b_ffname; if (buf_sfname) sfname = buf->b_sfname; if (buf_fname_f) fname = buf->b_ffname; if (buf_fname_s) fname = buf->b_sfname; } if (cmdmod.cmod_flags & CMOD_LOCKMARKS) { // restore the original '[ and '] positions buf->b_op_start = orig_start; buf->b_op_end = orig_end; } #ifdef FEAT_NETBEANS_INTG if (netbeans_active() && isNetbeansBuffer(buf)) { if (whole) { // b_changed can be 0 after an undo, but we still need to write // the buffer to NetBeans. if (buf->b_changed || isNetbeansModified(buf)) { --no_wait_return; // may wait for return now msg_scroll = msg_save; netbeans_save_buffer(buf); // no error checking... return retval; } else { errnum = (char_u *)"E656: "; errmsg = (char_u *)_(e_netbeans_disallows_writes_of_unmodified_buffers); buffer = NULL; goto fail; } } else { errnum = (char_u *)"E657: "; errmsg = (char_u *)_(e_partial_writes_disallowed_for_netbeans_buffers); buffer = NULL; goto fail; } } #endif if (shortmess(SHM_OVER) && !exiting) msg_scroll = FALSE; // overwrite previous file message else msg_scroll = TRUE; // don't overwrite previous file message if (!filtering) filemess(buf, #ifndef UNIX sfname, #else fname, #endif (char_u *)"", 0); // show that we are busy msg_scroll = FALSE; // always overwrite the file message now buffer = alloc(WRITEBUFSIZE); if (buffer == NULL) // can't allocate big buffer, use small // one (to be able to write when out of // memory) { buffer = smallbuf; bufsize = SMALLBUFSIZE; } else bufsize = WRITEBUFSIZE; // Get information about original file (if there is one). #if defined(UNIX) st_old.st_dev = 0; st_old.st_ino = 0; perm = -1; if (mch_stat((char *)fname, &st_old) < 0) newfile = TRUE; else { perm = st_old.st_mode; if (!S_ISREG(st_old.st_mode)) // not a file { if (S_ISDIR(st_old.st_mode)) { errnum = (char_u *)"E502: "; errmsg = (char_u *)_(e_is_a_directory); goto fail; } if (mch_nodetype(fname) != NODE_WRITABLE) { errnum = (char_u *)"E503: "; errmsg = (char_u *)_(e_is_not_file_or_writable_device); goto fail; } // It's a device of some kind (or a fifo) which we can write to // but for which we can't make a backup. device = TRUE; newfile = TRUE; perm = -1; } } #else // !UNIX // Check for a writable device name. c = mch_nodetype(fname); if (c == NODE_OTHER) { errnum = (char_u *)"E503: "; errmsg = (char_u *)_(e_is_not_file_or_writable_device); goto fail; } if (c == NODE_WRITABLE) { # if defined(MSWIN) // MS-Windows allows opening a device, but we will probably get stuck // trying to write to it. if (!p_odev) { errnum = (char_u *)"E796: "; errmsg = (char_u *)_(e_writing_to_device_disabled_with_opendevice_option); goto fail; } # endif device = TRUE; newfile = TRUE; perm = -1; } else { perm = mch_getperm(fname); if (perm < 0) newfile = TRUE; else if (mch_isdir(fname)) { errnum = (char_u *)"E502: "; errmsg = (char_u *)_(e_is_a_directory); goto fail; } if (overwriting) (void)mch_stat((char *)fname, &st_old); } #endif // !UNIX if (!device && !newfile) { // Check if the file is really writable (when renaming the file to // make a backup we won't discover it later). file_readonly = check_file_readonly(fname, (int)perm); if (!forceit && file_readonly) { if (vim_strchr(p_cpo, CPO_FWRITE) != NULL) { errnum = (char_u *)"E504: "; errmsg = (char_u *)_(e_is_read_only_cannot_override_W_in_cpoptions); } else { errnum = (char_u *)"E505: "; errmsg = (char_u *)_(e_is_read_only_add_bang_to_override); } goto fail; } // Check if the timestamp hasn't changed since reading the file. if (overwriting) { retval = check_mtime(buf, &st_old); if (retval == FAIL) goto fail; } } #ifdef HAVE_ACL // For systems that support ACL: get the ACL from the original file. if (!newfile) acl = mch_get_acl(fname); #endif // If 'backupskip' is not empty, don't make a backup for some files. dobackup = (p_wb || p_bk || *p_pm != NUL); if (dobackup && *p_bsk != NUL && match_file_list(p_bsk, sfname, ffname)) dobackup = FALSE; // Save the value of got_int and reset it. We don't want a previous // interruption cancel writing, only hitting CTRL-C while writing should // abort it. prev_got_int = got_int; got_int = FALSE; // Mark the buffer as 'being saved' to prevent changed buffer warnings buf->b_saving = TRUE; // If we are not appending or filtering, the file exists, and the // 'writebackup', 'backup' or 'patchmode' option is set, need a backup. // When 'patchmode' is set also make a backup when appending. // // Do not make any backup, if 'writebackup' and 'backup' are both switched // off. This helps when editing large files on almost-full disks. if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) { #if defined(UNIX) || defined(MSWIN) stat_T st; #endif if ((bkc & BKC_YES) || append) // "yes" backup_copy = TRUE; #if defined(UNIX) || defined(MSWIN) else if ((bkc & BKC_AUTO)) // "auto" { int i; # ifdef UNIX // Don't rename the file when: // - it's a hard link // - it's a symbolic link // - we don't have write permission in the directory // - we can't set the owner/group of the new file if (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 # ifndef HAVE_FCHOWN || st.st_uid != st_old.st_uid || st.st_gid != st_old.st_gid # endif ) backup_copy = TRUE; else # else # ifdef MSWIN // On NTFS file systems hard links are possible. if (mch_is_linked(fname)) backup_copy = TRUE; else # endif # endif { // Check if we can create a file and set the owner/group to // the ones from the original file. // First find a file name that doesn't exist yet (use some // arbitrary numbers). STRCPY(IObuff, fname); fd = -1; for (i = 4913; ; i += 123) { sprintf((char *)gettail(IObuff), "%d", i); if (mch_lstat((char *)IObuff, &st) < 0) { fd = mch_open((char *)IObuff, O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm); if (fd < 0 && errno == EEXIST) // If the same file name is created by another // process between lstat() and open(), find another // name. continue; break; } } if (fd < 0) // can't write in directory backup_copy = TRUE; else { # ifdef UNIX # ifdef HAVE_FCHOWN vim_ignored = fchown(fd, st_old.st_uid, st_old.st_gid); # endif if (mch_stat((char *)IObuff, &st) < 0 || st.st_uid != st_old.st_uid || st.st_gid != st_old.st_gid || (long)st.st_mode != perm) backup_copy = TRUE; # endif // Close the file before removing it, on MS-Windows we // can't delete an open file. close(fd); mch_remove(IObuff); # ifdef MSWIN // MS-Windows may trigger a virus scanner to open the // file, we can't delete it then. Keep trying for half a // second. { int try; for (try = 0; try < 10; ++try) { if (mch_lstat((char *)IObuff, &st) < 0) break; ui_delay(50L, TRUE); // wait 50 msec mch_remove(IObuff); } } # endif } } } // Break symlinks and/or hardlinks if we've been asked to. if ((bkc & BKC_BREAKSYMLINK) || (bkc & BKC_BREAKHARDLINK)) { # ifdef UNIX int lstat_res; lstat_res = mch_lstat((char *)fname, &st); // Symlinks. if ((bkc & BKC_BREAKSYMLINK) && lstat_res == 0 && st.st_ino != st_old.st_ino) backup_copy = FALSE; // Hardlinks. if ((bkc & BKC_BREAKHARDLINK) && st_old.st_nlink > 1 && (lstat_res != 0 || st.st_ino == st_old.st_ino)) backup_copy = FALSE; # else # if defined(MSWIN) // Symlinks. if ((bkc & BKC_BREAKSYMLINK) && mch_is_symbolic_link(fname)) backup_copy = FALSE; // Hardlinks. if ((bkc & BKC_BREAKHARDLINK) && mch_is_hard_link(fname)) backup_copy = FALSE; # endif # endif } #endif // make sure we have a valid backup extension to use if (*p_bex == NUL) backup_ext = (char_u *)".bak"; else backup_ext = p_bex; if (backup_copy && (fd = mch_open((char *)fname, O_RDONLY | O_EXTRA, 0)) >= 0) { int bfd; char_u *copybuf, *wp; int some_error = FALSE; stat_T st_new; char_u *dirp; char_u *rootname; #if defined(UNIX) || defined(MSWIN) char_u *p; #endif #if defined(UNIX) int did_set_shortname; mode_t umask_save; #endif copybuf = alloc(WRITEBUFSIZE + 1); if (copybuf == NULL) { some_error = TRUE; // out of memory goto nobackup; } // Try to make the backup in each directory in the 'bdir' option. // // Unix semantics has it, that we may have a writable file, // that cannot be recreated with a simple open(..., O_CREAT, ) e.g: // - the directory is not writable, // - the file may be a symbolic link, // - the file may belong to another user/group, etc. // // For these reasons, the existing writable file must be truncated // and reused. Creation of a backup COPY will be attempted. dirp = p_bdir; while (*dirp) { #ifdef UNIX st_new.st_ino = 0; st_new.st_dev = 0; st_new.st_gid = 0; #endif // Isolate one directory name, using an entry in 'bdir'. (void)copy_option_part(&dirp, copybuf, WRITEBUFSIZE, ","); #if defined(UNIX) || defined(MSWIN) p = copybuf + STRLEN(copybuf); if (after_pathsep(copybuf, p) && p[-1] == p[-2]) // Ends with '//', use full path if ((p = make_percent_swname(copybuf, fname)) != NULL) { backup = modname(p, backup_ext, FALSE); vim_free(p); } #endif rootname = get_file_in_dir(fname, copybuf); if (rootname == NULL) { some_error = TRUE; // out of memory goto nobackup; } #if defined(UNIX) did_set_shortname = FALSE; #endif // May try twice if 'shortname' not set. for (;;) { // Make the backup file name. if (backup == NULL) backup = buf_modname((buf->b_p_sn || buf->b_shortname), rootname, backup_ext, FALSE); if (backup == NULL) { vim_free(rootname); some_error = TRUE; // out of memory goto nobackup; } // Check if backup file already exists. if (mch_stat((char *)backup, &st_new) >= 0) { #ifdef UNIX // Check if backup file is same as original file. // May happen when modname() gave the same file back. // E.g. silly link, or file name-length reached. // If we don't check here, we either ruin the file // when copying or erase it after writing. jw. if (st_new.st_dev == st_old.st_dev && st_new.st_ino == st_old.st_ino) { VIM_CLEAR(backup); // no backup file to delete // may try again with 'shortname' set if (!(buf->b_shortname || buf->b_p_sn)) { buf->b_shortname = TRUE; did_set_shortname = TRUE; continue; } // setting shortname didn't help if (did_set_shortname) buf->b_shortname = FALSE; break; } #endif // If we are not going to keep the backup file, don't // delete an existing one, try to use another name. // Change one character, just before the extension. if (!p_bk) { wp = backup + STRLEN(backup) - 1 - STRLEN(backup_ext); if (wp < backup) // empty file name ??? wp = backup; *wp = 'z'; while (*wp > 'a' && mch_stat((char *)backup, &st_new) >= 0) --*wp; // They all exist??? Must be something wrong. if (*wp == 'a') VIM_CLEAR(backup); } } break; } vim_free(rootname); // Try to create the backup file if (backup != NULL) { // remove old backup, if present mch_remove(backup); // Open with O_EXCL to avoid the file being created while // we were sleeping (symlink hacker attack?). Reset umask // if possible to avoid mch_setperm() below. #ifdef UNIX umask_save = umask(0); #endif bfd = mch_open((char *)backup, O_WRONLY|O_CREAT|O_EXTRA|O_EXCL|O_NOFOLLOW, perm & 0777); #ifdef UNIX (void)umask(umask_save); #endif if (bfd < 0) VIM_CLEAR(backup); else { // Set file protection same as original file, but // strip s-bit. Only needed if umask() wasn't used // above. #ifndef UNIX (void)mch_setperm(backup, perm & 0777); #else // Try to set the group of the backup same as the // original file. If this fails, set the protection // bits for the group same as the protection bits for // others. if (st_new.st_gid != st_old.st_gid # ifdef HAVE_FCHOWN // sequent-ptx lacks fchown() && fchown(bfd, (uid_t)-1, st_old.st_gid) != 0 # endif ) mch_setperm(backup, (perm & 0707) | ((perm & 07) << 3)); # if defined(HAVE_SELINUX) || defined(HAVE_SMACK) mch_copy_sec(fname, backup); # endif # ifdef FEAT_XATTR mch_copy_xattr(fname, backup); # endif #endif // copy the file. write_info.bw_fd = bfd; write_info.bw_buf = copybuf; write_info.bw_flags = FIO_NOCONVERT; while ((write_info.bw_len = read_eintr(fd, copybuf, WRITEBUFSIZE)) > 0) { if (buf_write_bytes(&write_info) == FAIL) { errmsg = (char_u *)_(e_cant_write_to_backup_file_add_bang_to_override); break; } ui_breakcheck(); if (got_int) { errmsg = (char_u *)_(e_interrupted); break; } } if (close(bfd) < 0 && errmsg == NULL) errmsg = (char_u *)_(e_close_error_for_backup_file_add_bang_to_write_anyway); if (write_info.bw_len < 0) errmsg = (char_u *)_(e_cant_read_file_for_backup_add_bang_to_write_anyway); #ifdef UNIX set_file_time(backup, st_old.st_atime, st_old.st_mtime); #endif #ifdef HAVE_ACL mch_set_acl(backup, acl); #endif #if defined(HAVE_SELINUX) || defined(HAVE_SMACK) mch_copy_sec(fname, backup); #endif #ifdef FEAT_XATTR mch_copy_xattr(fname, backup); #endif #ifdef MSWIN (void)mch_copy_file_attribute(fname, backup); #endif break; } } } nobackup: close(fd); // ignore errors for closing read file vim_free(copybuf); if (backup == NULL && errmsg == NULL) errmsg = (char_u *)_(e_cannot_create_backup_file_add_bang_to_write_anyway); // ignore errors when forceit is TRUE if ((some_error || errmsg != NULL) && !forceit) { retval = FAIL; goto fail; } errmsg = NULL; } else { char_u *dirp; char_u *p; char_u *rootname; // Make a backup by renaming the original file. // If 'cpoptions' includes the "W" flag, we don't want to // overwrite a read-only file. But rename may be possible // anyway, thus we need an extra check here. if (file_readonly && vim_strchr(p_cpo, CPO_FWRITE) != NULL) { errnum = (char_u *)"E504: "; errmsg = (char_u *)_(e_is_read_only_cannot_override_W_in_cpoptions); goto fail; } // Form the backup file name - change path/fo.o.h to // path/fo.o.h.bak Try all directories in 'backupdir', first one // that works is used. dirp = p_bdir; while (*dirp) { // Isolate one directory name and make the backup file name. (void)copy_option_part(&dirp, IObuff, IOSIZE, ","); #if defined(UNIX) || defined(MSWIN) p = IObuff + STRLEN(IObuff); if (after_pathsep(IObuff, p) && p[-1] == p[-2]) // path ends with '//', use full path if ((p = make_percent_swname(IObuff, fname)) != NULL) { backup = modname(p, backup_ext, FALSE); vim_free(p); } #endif if (backup == NULL) { rootname = get_file_in_dir(fname, IObuff); if (rootname == NULL) backup = NULL; else { backup = buf_modname( (buf->b_p_sn || buf->b_shortname), rootname, backup_ext, FALSE); vim_free(rootname); } } if (backup != NULL) { // If we are not going to keep the backup file, don't // delete an existing one, try to use another name. // Change one character, just before the extension. if (!p_bk && mch_getperm(backup) >= 0) { p = backup + STRLEN(backup) - 1 - STRLEN(backup_ext); if (p < backup) // empty file name ??? p = backup; *p = 'z'; while (*p > 'a' && mch_getperm(backup) >= 0) --*p; // They all exist??? Must be something wrong! if (*p == 'a') VIM_CLEAR(backup); } } if (backup != NULL) { // Delete any existing backup and move the current version // to the backup. For safety, we don't remove the backup // until the write has finished successfully. And if the // 'backup' option is set, leave it around. // If the renaming of the original file to the backup file // works, quit here. if (vim_rename(fname, backup) == 0) break; VIM_CLEAR(backup); // don't do the rename below } } if (backup == NULL && !forceit) { errmsg = (char_u *)_(e_cant_make_backup_file_add_bang_to_write_anyway); goto fail; } } } #if defined(UNIX) // When using ":w!" and the file was read-only: make it writable if (forceit && perm >= 0 && !(perm & 0200) && st_old.st_uid == getuid() && vim_strchr(p_cpo, CPO_FWRITE) == NULL) { perm |= 0200; (void)mch_setperm(fname, perm); made_writable = TRUE; } #endif // When using ":w!" and writing to the current file, 'readonly' makes no // sense, reset it, unless 'Z' appears in 'cpoptions'. if (forceit && overwriting && vim_strchr(p_cpo, CPO_KEEPRO) == NULL) { buf->b_p_ro = FALSE; need_maketitle = TRUE; // set window title later status_redraw_all(); // redraw status lines later } if (end > buf->b_ml.ml_line_count) end = buf->b_ml.ml_line_count; if (buf->b_ml.ml_flags & ML_EMPTY) start = end + 1; // If the original file is being overwritten, there is a small chance that // we crash in the middle of writing. Therefore the file is preserved now. // This makes all block numbers positive so that recovery does not need // the original file. // Don't do this if there is a backup file and we are exiting. if (reset_changed && !newfile && overwriting && !(exiting && backup != NULL)) { ml_preserve(buf, FALSE); if (got_int) { errmsg = (char_u *)_(e_interrupted); goto restore_backup; } } #ifdef VMS vms_remove_version(fname); // remove version #endif // Default: write the file directly. May write to a temp file for // multi-byte conversion. wfname = fname; // Check for forced 'fileencoding' from "++opt=val" argument. if (eap != NULL && eap->force_enc != 0) { fenc = eap->cmd + eap->force_enc; fenc = enc_canonize(fenc); fenc_tofree = fenc; } else fenc = buf->b_p_fenc; // Check if the file needs to be converted. converted = need_conversion(fenc); // Check if UTF-8 to UCS-2/4 or Latin1 conversion needs to be done. Or // Latin1 to Unicode conversion. This is handled in buf_write_bytes(). // Prepare the flags for it and allocate bw_conv_buf when needed. if (converted && (enc_utf8 || STRCMP(p_enc, "latin1") == 0)) { wb_flags = get_fio_flags(fenc); if (wb_flags & (FIO_UCS2 | FIO_UCS4 | FIO_UTF16 | FIO_UTF8)) { // Need to allocate a buffer to translate into. if (wb_flags & (FIO_UCS2 | FIO_UTF16 | FIO_UTF8)) write_info.bw_conv_buflen = bufsize * 2; else // FIO_UCS4 write_info.bw_conv_buflen = bufsize * 4; write_info.bw_conv_buf = alloc(write_info.bw_conv_buflen); if (write_info.bw_conv_buf == NULL) end = 0; } } #ifdef MSWIN if (converted && wb_flags == 0 && (wb_flags = get_win_fio_flags(fenc)) != 0) { // Convert UTF-8 -> UCS-2 and UCS-2 -> DBCS. Worst-case * 4: write_info.bw_conv_buflen = bufsize * 4; write_info.bw_conv_buf = alloc(write_info.bw_conv_buflen); if (write_info.bw_conv_buf == NULL) end = 0; } #endif #ifdef MACOS_CONVERT if (converted && wb_flags == 0 && (wb_flags = get_mac_fio_flags(fenc)) != 0) { write_info.bw_conv_buflen = bufsize * 3; write_info.bw_conv_buf = alloc(write_info.bw_conv_buflen); if (write_info.bw_conv_buf == NULL) end = 0; } #endif #if defined(FEAT_EVAL) || defined(USE_ICONV) if (converted && wb_flags == 0) { # ifdef USE_ICONV // Use iconv() conversion when conversion is needed and it's not done // internally. write_info.bw_iconv_fd = (iconv_t)my_iconv_open(fenc, enc_utf8 ? (char_u *)"utf-8" : p_enc); if (write_info.bw_iconv_fd != (iconv_t)-1) { // We're going to use iconv(), allocate a buffer to convert in. write_info.bw_conv_buflen = bufsize * ICONV_MULT; write_info.bw_conv_buf = alloc(write_info.bw_conv_buflen); if (write_info.bw_conv_buf == NULL) end = 0; write_info.bw_first = TRUE; } # ifdef FEAT_EVAL else # endif # endif # ifdef FEAT_EVAL // When the file needs to be converted with 'charconvert' after // writing, write to a temp file instead and let the conversion // overwrite the original file. if (*p_ccv != NUL) { wfname = vim_tempname('w', FALSE); if (wfname == NULL) // Can't write without a tempfile! { errmsg = (char_u *)_(e_cant_find_temp_file_for_writing); goto restore_backup; } } # endif } #endif if (converted && wb_flags == 0 #ifdef USE_ICONV && write_info.bw_iconv_fd == (iconv_t)-1 # endif # ifdef FEAT_EVAL && wfname == fname # endif ) { if (!forceit) { errmsg = (char_u *)_(e_cannot_convert_add_bang_to_write_without_conversion); goto restore_backup; } notconverted = TRUE; } // If conversion is taking place, we may first pretend to write and check // for conversion errors. Then loop again to write for real. // When not doing conversion this writes for real right away. for (checking_conversion = TRUE; ; checking_conversion = FALSE) { // There is no need to check conversion when: // - there is no conversion // - we make a backup file, that can be restored in case of conversion // failure. if (!converted || dobackup) checking_conversion = FALSE; if (checking_conversion) { // Make sure we don't write anything. fd = -1; write_info.bw_fd = fd; } else { #ifdef HAVE_FTRUNCATE # define TRUNC_ON_OPEN 0 #else # define TRUNC_ON_OPEN O_TRUNC #endif // 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 | TRUNC_ON_OPEN)) , perm < 0 ? 0666 : (perm & 0777))) < 0) { // A forced write will try to create a new file if the old one // 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 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 *)_(e_cant_open_linked_file_for_writing); else #endif { errmsg = (char_u *)_(e_cant_open_file_for_writing); 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: { 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) { // There is a small chance that we removed the // original, try to move the copy in its place. // This may not work if the vim_rename() fails. // In that case we leave the copy around. // 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 (!newfile && mch_stat((char *)fname, &st) < 0) end = 0; } if (wfname != fname) vim_free(wfname); goto fail; } write_info.bw_fd = fd; #if defined(UNIX) { stat_T st; // Double check we are writing the intended file before making // any changes. if (overwriting && (!dobackup || backup_copy) && fname == wfname && perm >= 0 && mch_fstat(fd, &st) == 0 && st.st_ino != st_old.st_ino) { close(fd); errmsg = (char_u *)_(e_file_changed_while_writing); goto fail; } } #endif #ifdef HAVE_FTRUNCATE if (!append) vim_ignored = ftruncate(fd, (off_t)0); #endif #if defined(MSWIN) if (backup != NULL && overwriting && !append) (void)mch_copy_file_attribute(backup, wfname); if (!overwriting && !append) { if (buf->b_ffname != NULL) (void)mch_copy_file_attribute(buf->b_ffname, wfname); // Should copy resource fork } #endif #ifdef FEAT_CRYPT 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 } errmsg = NULL; 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; // 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; #ifdef FEAT_PERSISTENT_UNDO write_undo_file = (buf->b_p_udf && overwriting && !append && !filtering # ifdef CRYPT_NOT_INPLACE // writing undo file requires // crypt_encode_inplace() && (buf->b_cryptstate == NULL || crypt_works_inplace(buf->b_cryptstate)) # endif && reset_changed && !checking_conversion); # ifdef CRYPT_NOT_INPLACE // remove undo file if encrypting it is not possible if (buf->b_p_udf && overwriting && !append && !filtering && !checking_conversion && buf->b_cryptstate != NULL && !crypt_works_inplace(buf->b_cryptstate)) u_undofile_reset_and_delete(buf); # endif if (write_undo_file) // Prepare for computing the hash value of the text. sha256_start(&sha_ctx); #endif write_info.bw_len = bufsize; write_info.bw_flags = wb_flags; 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; #ifdef FEAT_CRYPT if (write_info.bw_fd > 0 && lnum == end && (write_info.bw_flags & FIO_ENCRYPTED) && *buf->b_p_key != NUL && !filtering && *ptr == NUL) write_info.bw_finish = TRUE; #endif if (buf_write_bytes(&write_info) == FAIL) { end = 0; // write error: break loop break; } nchars += bufsize; s = buffer; len = 0; write_info.bw_start_lnum = lnum; } // write failed or last line has no EOL: stop here if (end == 0 || (lnum == end && (write_bin || !buf->b_p_fixeol) && ((write_bin && 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) { 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. # ifndef MIN // Older DECC compiler for VAX doesn't define MIN() # define MIN(a, b) ((a) < (b) ? (a) : (b)) # endif 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; #ifdef FEAT_CRYPT if (write_info.bw_fd > 0 && lnum >= end && (write_info.bw_flags & FIO_ENCRYPTED) && *buf->b_p_key != NUL && !filtering) write_info.bw_finish = TRUE; #endif if (buf_write_bytes(&write_info) == FAIL) end = 0; // write error nchars += len; } if (!buf->b_p_fixeol && buf->b_p_eof) { // write trailing CTRL-Z (void)write_eintr(write_info.bw_fd, "\x1a", 1); nchars++; } // 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 we started writing, finish writing. Also when an error was // encountered. if (!checking_conversion) { #if defined(UNIX) && defined(HAVE_FSYNC) // On many journaling 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 && vim_fsync(fd) != 0 && !device) { errmsg = (char_u *)_(e_fsync_failed); end = 0; } #endif #if defined(HAVE_SELINUX) || defined(HAVE_SMACK) || defined(FEAT_XATTR) // Probably need to set the security context. if (!backup_copy) { #if defined(HAVE_SELINUX) || defined(HAVE_SMACK) mch_copy_sec(backup, wfname); #endif #ifdef FEAT_XATTR mch_copy_xattr(backup, wfname); #endif } #endif #ifdef UNIX // When creating a new file, set its owner/group to that of the // original file. Get the new device and inode number. 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) { // changing owner might not be possible vim_ignored = fchown(fd, st_old.st_uid, -1); // if changing group fails clear the group permissions if (fchown(fd, -1, st_old.st_gid) == -1 && perm > 0) perm &= ~070; } # endif buf_setino(buf); } else if (!buf->b_dev_valid) // Set the inode when creating a new file. buf_setino(buf); #endif #ifdef UNIX if (made_writable) perm &= ~0200; // reset 'w' bit for security reasons #endif #ifdef HAVE_FCHMOD // set permission of new file same as old file if (perm >= 0) (void)mch_fsetperm(fd, perm); #endif if (close(fd) != 0) { errmsg = (char_u *)_(e_close_failed); end = 0; } #ifndef HAVE_FCHMOD // set permission of new file same as old file if (perm >= 0) (void)mch_setperm(wfname, perm); #endif #ifdef HAVE_ACL // Probably need to set the ACL before changing the user (can't set the // ACL on a file the user doesn't own). // 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 the mode of the file. It's non-trivial for us to discover // whether we're in that situation, so we simply always re-set the ACL. # ifndef HAVE_SOLARIS_ZFS_ACL if (!backup_copy) # endif mch_set_acl(wfname, acl); #endif #ifdef FEAT_CRYPT if (buf->b_cryptstate != NULL) { crypt_free_state(buf->b_cryptstate); buf->b_cryptstate = NULL; } #endif #if defined(FEAT_EVAL) 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) { write_info.bw_conv_error = TRUE; end = 0; } } mch_remove(wfname); vim_free(wfname); } #endif } if (end == 0) { // Error encountered. if (errmsg == NULL) { if (write_info.bw_conv_error) { if (write_info.bw_conv_error_lnum == 0) errmsg = (char_u *)_(e_write_error_conversion_failed_make_fenc_empty_to_override); else { errmsg_allocated = TRUE; errmsg = alloc(300); vim_snprintf((char *)errmsg, 300, _(e_write_error_conversion_failed_in_line_nr_make_fenc_empty_to_override), (long)write_info.bw_conv_error_lnum); } } else if (got_int) errmsg = (char_u *)_(e_interrupted); else errmsg = (char_u *)_(e_write_error_file_system_full); } // If we have a backup file, try to put it in place of the new file, // because the new file is probably corrupt. This avoids losing the // original file when trying to make a backup when writing the file a // second time. // When "backup_copy" is set we need to copy the backup over the new // file. Otherwise rename the backup file. // If this is OK, don't give the extra warning message. if (backup != NULL) { if (backup_copy) { // This may take a while, if we were interrupted let the user // know we got the message. if (got_int) { msg(_(e_interrupted)); out_flush(); } if ((fd = mch_open((char *)backup, O_RDONLY | O_EXTRA, 0)) >= 0) { if ((write_info.bw_fd = mch_open((char *)fname, O_WRONLY | O_CREAT | O_TRUNC | O_EXTRA, perm & 0777)) >= 0) { // copy the file. write_info.bw_buf = smallbuf; write_info.bw_flags = FIO_NOCONVERT; while ((write_info.bw_len = read_eintr(fd, smallbuf, SMALLBUFSIZE)) > 0) if (buf_write_bytes(&write_info) == FAIL) break; if (close(write_info.bw_fd) >= 0 && write_info.bw_len == 0) end = 1; // success } close(fd); // ignore errors for closing read file } } else { if (vim_rename(backup, fname) == 0) end = 1; } } goto fail; } lnum -= start; // compute number of written lines --no_wait_return; // may wait for return now #if !(defined(UNIX) || defined(VMS)) fname = sfname; // use shortname now, for the messages #endif if (!filtering) { msg_add_fname(buf, fname); // put fname in IObuff with quotes c = FALSE; if (write_info.bw_conv_error) { STRCAT(IObuff, _(" CONVERSION ERROR")); c = TRUE; if (write_info.bw_conv_error_lnum != 0) vim_snprintf_add((char *)IObuff, IOSIZE, _(" in line %ld;"), (long)write_info.bw_conv_error_lnum); } else if (notconverted) { STRCAT(IObuff, _("[NOT converted]")); c = TRUE; } else if (converted) { STRCAT(IObuff, _("[converted]")); c = TRUE; } if (device) { STRCAT(IObuff, _("[Device]")); c = TRUE; } else if (newfile) { STRCAT(IObuff, new_file_message()); c = TRUE; } if (no_eol) { msg_add_eol(); c = TRUE; } // may add [unix/dos/mac] if (msg_add_fileformat(fileformat)) c = TRUE; #ifdef FEAT_CRYPT if (wb_flags & FIO_ENCRYPTED) { crypt_append_msg(buf); c = TRUE; } #endif msg_add_lines(c, (long)lnum, nchars); // add line/char count if (!shortmess(SHM_WRITE)) { if (append) STRCAT(IObuff, shortmess(SHM_WRI) ? _(" [a]") : _(" appended")); else STRCAT(IObuff, shortmess(SHM_WRI) ? _(" [w]") : _(" written")); } set_keep_msg((char_u *)msg_trunc_attr((char *)IObuff, FALSE, 0), 0); } // When written everything correctly: reset 'modified'. Unless not // writing to the original file and '+' is not in 'cpoptions'. if (reset_changed && whole && !append && !write_info.bw_conv_error && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) { unchanged(buf, TRUE, FALSE); // b:changedtick may be incremented in unchanged() but that should not // trigger a TextChanged event. if (buf->b_last_changedtick + 1 == CHANGEDTICK(buf)) buf->b_last_changedtick = CHANGEDTICK(buf); u_unchanged(buf); u_update_save_nr(buf); } // If written to the current file, update the timestamp of the swap file // and reset the BF_WRITE_MASK flags. Also sets buf->b_mtime. if (overwriting) { ml_timestamp(buf); if (append) buf->b_flags &= ~BF_NEW; else buf->b_flags &= ~BF_WRITE_MASK; } // If we kept a backup until now, and we are in patch mode, then we make // the backup file our 'original' file. if (*p_pm && dobackup) { char *org = (char *)buf_modname((buf->b_p_sn || buf->b_shortname), fname, p_pm, FALSE); if (backup != NULL) { stat_T st; // If the original file does not exist yet // the current backup file becomes the original file if (org == NULL) emsg(_(e_patchmode_cant_save_original_file)); else if (mch_stat(org, &st) < 0) { vim_rename(backup, (char_u *)org); VIM_CLEAR(backup); // don't delete the file #ifdef UNIX set_file_time((char_u *)org, st_old.st_atime, st_old.st_mtime); #endif } } // If there is no backup file, remember that a (new) file was // created. else { int empty_fd; if (org == NULL || (empty_fd = mch_open(org, O_CREAT | O_EXTRA | O_EXCL | O_NOFOLLOW, perm < 0 ? 0666 : (perm & 0777))) < 0) emsg(_(e_patchmode_cant_touch_empty_original_file)); else close(empty_fd); } if (org != NULL) { mch_setperm((char_u *)org, mch_getperm(fname) & 0777); vim_free(org); } } // Remove the backup unless 'backup' option is set or there was a // conversion error. if (!p_bk && backup != NULL && !write_info.bw_conv_error && mch_remove(backup) != 0) emsg(_(e_cant_delete_backup_file)); goto nofail; // Finish up. We get here either after failure or success. fail: --no_wait_return; // may wait for return now nofail: // Done saving, we accept changed buffer warnings again buf->b_saving = FALSE; vim_free(backup); if (buffer != smallbuf) vim_free(buffer); vim_free(fenc_tofree); vim_free(write_info.bw_conv_buf); #ifdef USE_ICONV if (write_info.bw_iconv_fd != (iconv_t)-1) { iconv_close(write_info.bw_iconv_fd); write_info.bw_iconv_fd = (iconv_t)-1; } #endif #ifdef HAVE_ACL mch_free_acl(acl); #endif if (errmsg != NULL) { int numlen = errnum != NULL ? (int)STRLEN(errnum) : 0; attr = HL_ATTR(HLF_E); // set highlight for error messages msg_add_fname(buf, #ifndef UNIX sfname #else fname #endif ); // put file name in IObuff with quotes if (STRLEN(IObuff) + STRLEN(errmsg) + numlen >= IOSIZE) IObuff[IOSIZE - STRLEN(errmsg) - numlen - 1] = NUL; // If the error message has the form "is ...", put the error number in // front of the file name. if (errnum != NULL) { STRMOVE(IObuff + numlen, IObuff); mch_memmove(IObuff, errnum, (size_t)numlen); } STRCAT(IObuff, errmsg); emsg((char *)IObuff); if (errmsg_allocated) vim_free(errmsg); retval = FAIL; if (end == 0) { msg_puts_attr(_("\nWARNING: Original file may be lost or damaged\n"), attr | MSG_HIST); msg_puts_attr(_("don't quit the editor until the file is successfully written!"), attr | MSG_HIST); // Update the timestamp to avoid an "overwrite changed file" // prompt when writing again. if (mch_stat((char *)fname, &st_old) >= 0) { buf_store_time(buf, &st_old, fname); buf->b_mtime_read = buf->b_mtime; buf->b_mtime_read_ns = buf->b_mtime_ns; } } } msg_scroll = msg_save; #ifdef FEAT_PERSISTENT_UNDO // When writing the whole file and 'undofile' is set, also write the undo // file. if (retval == OK && write_undo_file) { char_u hash[UNDO_HASH_SIZE]; sha256_finish(&sha_ctx, hash); u_write_undo(NULL, FALSE, buf, hash); } #endif #ifdef FEAT_EVAL if (!should_abort(retval)) #else if (!got_int) #endif { aco_save_T aco; curbuf->b_no_eol_lnum = 0; // in case it was set by the previous read // Apply POST autocommands. // Careful: The autocommands may call buf_write() recursively! // Only do this when a window was found for "buf". aucmd_prepbuf(&aco, buf); if (curbuf == buf) { if (append) apply_autocmds_exarg(EVENT_FILEAPPENDPOST, fname, fname, FALSE, curbuf, eap); else if (filtering) apply_autocmds_exarg(EVENT_FILTERWRITEPOST, NULL, fname, FALSE, curbuf, eap); else if (reset_changed && whole) apply_autocmds_exarg(EVENT_BUFWRITEPOST, fname, fname, FALSE, curbuf, eap); else apply_autocmds_exarg(EVENT_FILEWRITEPOST, fname, fname, FALSE, curbuf, eap); // restore curwin/curbuf and a few other things aucmd_restbuf(&aco); } #ifdef FEAT_EVAL if (aborting()) // autocmds may abort script processing retval = FALSE; #endif } #ifdef FEAT_VIMINFO // Make sure marks will be written out to the viminfo file later, even when // the file is new. curbuf->b_marks_read = TRUE; #endif got_int |= prev_got_int; return retval; }