Created
November 9, 2015 17:13
-
-
Save mhinz/76f01ea6d1f632223957 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * 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[SMBUFSIZE]; | |
| 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 */ | |
| int prev_got_int = got_int; | |
| bool file_readonly = false; /* overwritten file is read-only */ | |
| static char *err_readonly = | |
| "is read-only (cannot override: \"W\" in 'cpoptions')"; | |
| #if defined(UNIX) | |
| 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" */ | |
| #ifdef HAS_BW_FLAGS | |
| int wb_flags = 0; | |
| #endif | |
| #ifdef HAVE_ACL | |
| vim_acl_T acl = NULL; /* ACL copied from original file to | |
| backup or new file */ | |
| #endif | |
| int write_undo_file = FALSE; | |
| context_sha256_T sha_ctx; | |
| unsigned int bkc = get_bkc_value(buf); | |
| 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_emptybuf)); | |
| 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_longname)); | |
| 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 | |
| /* 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_nofile(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; | |
| ++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); | |
| /* | |
| * 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 (append) { | |
| if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEAPPENDCMD, | |
| sfname, sfname, FALSE, curbuf, eap))) { | |
| if (overwriting && bt_nofile(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_nofile(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_nofile(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 (!buf_valid(buf)) | |
| buf = NULL; | |
| if (buf == NULL || (buf->b_ml.ml_mfp == NULL && !empty_memline) | |
| || did_cmd || nofile_err | |
| || aborting() | |
| ) { | |
| --no_wait_return; | |
| msg_scroll = msg_save; | |
| if (nofile_err) | |
| EMSG(_("E676: No matching autocommands for acwrite buffer")); | |
| if (nofile_err | |
| || aborting() | |
| ) | |
| /* 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; | |
| } | |
| if (!aborting()) | |
| EMSG(_("E203: 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(_("E204: Autocommand 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 (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 = verbose_try_malloc(BUFSIZE); | |
| // can't allocate big buffer, use small one (to be able to write when out of | |
| // memory) | |
| if (buffer == NULL) { | |
| buffer = smallbuf; | |
| bufsize = SMBUFSIZE; | |
| } else | |
| bufsize = BUFSIZE; | |
| /* | |
| * Get information about original file (if there is one). | |
| */ | |
| FileInfo file_info_old; | |
| #if defined(UNIX) | |
| perm = -1; | |
| if (!os_fileinfo((char *)fname, &file_info_old)) { | |
| newfile = TRUE; | |
| } else { | |
| perm = file_info_old.stat.st_mode; | |
| if (!S_ISREG(file_info_old.stat.st_mode)) { /* not a file */ | |
| if (S_ISDIR(file_info_old.stat.st_mode)) { | |
| errnum = (char_u *)"E502: "; | |
| errmsg = (char_u *)_("is a directory"); | |
| goto fail; | |
| } | |
| if (mch_nodetype(fname) != NODE_WRITABLE) { | |
| errnum = (char_u *)"E503: "; | |
| errmsg = (char_u *)_("is not a 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 *)_("is not a file or writable device"); | |
| goto fail; | |
| } | |
| if (c == NODE_WRITABLE) { | |
| device = TRUE; | |
| newfile = TRUE; | |
| perm = -1; | |
| } else { | |
| perm = os_getperm(fname); | |
| if (perm < 0) | |
| newfile = TRUE; | |
| else if (os_isdir(fname)) { | |
| errnum = (char_u *)"E502: "; | |
| errmsg = (char_u *)_("is a directory"); | |
| goto fail; | |
| } | |
| if (overwriting) { | |
| os_fileinfo((char *)fname, &file_info_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 = !os_file_is_writable((char *)fname); | |
| if (!forceit && file_readonly) { | |
| if (vim_strchr(p_cpo, CPO_FWRITE) != NULL) { | |
| errnum = (char_u *)"E504: "; | |
| errmsg = (char_u *)_(err_readonly); | |
| } else { | |
| errnum = (char_u *)"E505: "; | |
| errmsg = (char_u *)_("is read-only (add ! to override)"); | |
| } | |
| goto fail; | |
| } | |
| /* | |
| * Check if the timestamp hasn't changed since reading the file. | |
| */ | |
| if (overwriting) { | |
| retval = check_mtime(buf, &file_info_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) { | |
| FileInfo file_info; | |
| if ((bkc & BKC_YES) || append) { /* "yes" */ | |
| backup_copy = TRUE; | |
| } 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 | |
| */ | |
| if (os_fileinfo_hardlinks(&file_info_old) > 1 | |
| || !os_fileinfo_link((char *)fname, &file_info) | |
| || !os_fileinfo_id_equal(&file_info, &file_info_old)) { | |
| backup_copy = TRUE; | |
| } else | |
| # 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); | |
| for (i = 4913;; i += 123) { | |
| sprintf((char *)path_tail(IObuff), "%d", i); | |
| if (!os_fileinfo_link((char *)IObuff, &file_info)) { | |
| break; | |
| } | |
| } | |
| fd = os_open((char *)IObuff, | |
| O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm); | |
| if (fd < 0) /* can't write in directory */ | |
| backup_copy = TRUE; | |
| else { | |
| # ifdef UNIX | |
| os_fchown(fd, file_info_old.stat.st_uid, file_info_old.stat.st_gid); | |
| if (!os_fileinfo((char *)IObuff, &file_info) | |
| || file_info.stat.st_uid != file_info_old.stat.st_uid | |
| || file_info.stat.st_gid != file_info_old.stat.st_gid | |
| || (long)file_info.stat.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); | |
| os_remove((char *)IObuff); | |
| } | |
| } | |
| } | |
| /* | |
| * Break symlinks and/or hardlinks if we've been asked to. | |
| */ | |
| if ((bkc & BKC_BREAKSYMLINK) || (bkc & BKC_BREAKHARDLINK)) { | |
| # ifdef UNIX | |
| bool file_info_link_ok = os_fileinfo_link((char *)fname, &file_info); | |
| /* Symlinks. */ | |
| if ((bkc & BKC_BREAKSYMLINK) | |
| && file_info_link_ok | |
| && !os_fileinfo_id_equal(&file_info, &file_info_old)) { | |
| backup_copy = FALSE; | |
| } | |
| /* Hardlinks. */ | |
| if ((bkc & BKC_BREAKHARDLINK) | |
| && os_fileinfo_hardlinks(&file_info_old) > 1 | |
| && (!file_info_link_ok | |
| || os_fileinfo_id_equal(&file_info, &file_info_old))) { | |
| backup_copy = FALSE; | |
| } | |
| # 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 = os_open((char *)fname, O_RDONLY, 0)) >= 0) { | |
| int bfd; | |
| char_u *copybuf, *wp; | |
| int some_error = FALSE; | |
| char_u *dirp; | |
| char_u *rootname; | |
| copybuf = verbose_try_malloc(BUFSIZE + 1); | |
| if (copybuf == NULL) { | |
| // out of memory | |
| some_error = TRUE; | |
| 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) { | |
| /* | |
| * Isolate one directory name, using an entry in 'bdir'. | |
| */ | |
| (void)copy_option_part(&dirp, copybuf, BUFSIZE, ","); | |
| rootname = get_file_in_dir(fname, copybuf); | |
| if (rootname == NULL) { | |
| some_error = TRUE; /* out of memory */ | |
| goto nobackup; | |
| } | |
| FileInfo file_info_new; | |
| { | |
| /* | |
| * Make backup file name. | |
| */ | |
| backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE); | |
| if (backup == NULL) { | |
| xfree(rootname); | |
| some_error = TRUE; /* out of memory */ | |
| goto nobackup; | |
| } | |
| /* | |
| * Check if backup file already exists. | |
| */ | |
| if (os_fileinfo((char *)backup, &file_info_new)) { | |
| if (os_fileinfo_id_equal(&file_info_new, &file_info_old)) { | |
| /* | |
| * Backup file is same as original file. | |
| * May happen when modname() gave the same file back (e.g. silly | |
| * link). If we don't check here, we either ruin the file when | |
| * copying or erase it after writing. | |
| */ | |
| xfree(backup); | |
| backup = NULL; /* no backup file to delete */ | |
| } else if (!p_bk) { | |
| /* | |
| * We are not going to keep the backup file, so don't | |
| * delete an existing one, and try to use another name instead. | |
| * Change one character, just before the extension. | |
| */ | |
| wp = backup + STRLEN(backup) - 1 - STRLEN(backup_ext); | |
| if (wp < backup) { /* empty file name ??? */ | |
| wp = backup; | |
| } | |
| *wp = 'z'; | |
| while (*wp > 'a' | |
| && os_fileinfo((char *)backup, &file_info_new)) { | |
| --*wp; | |
| } | |
| /* They all exist??? Must be something wrong. */ | |
| if (*wp == 'a') { | |
| xfree(backup); | |
| backup = NULL; | |
| } | |
| } | |
| } | |
| } | |
| xfree(rootname); | |
| /* | |
| * Try to create the backup file | |
| */ | |
| if (backup != NULL) { | |
| /* remove old backup, if present */ | |
| os_remove((char *)backup); | |
| /* Open with O_EXCL to avoid the file being created while | |
| * we were sleeping (symlink hacker attack?) */ | |
| bfd = os_open((char *)backup, | |
| O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW, | |
| perm & 0777); | |
| if (bfd < 0) { | |
| xfree(backup); | |
| backup = NULL; | |
| } else { | |
| /* set file protection same as original file, but | |
| * strip s-bit */ | |
| (void)os_setperm(backup, perm & 0777); | |
| #ifdef UNIX | |
| /* | |
| * 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 (file_info_new.stat.st_gid != file_info_old.stat.st_gid | |
| && os_fchown(bfd, -1, file_info_old.stat.st_gid) != 0) { | |
| os_setperm(backup, (perm & 0707) | ((perm & 07) << 3)); | |
| } | |
| # ifdef HAVE_SELINUX | |
| mch_copy_sec(fname, backup); | |
| # endif | |
| #endif | |
| /* | |
| * copy the file. | |
| */ | |
| write_info.bw_fd = bfd; | |
| write_info.bw_buf = copybuf; | |
| #ifdef HAS_BW_FLAGS | |
| write_info.bw_flags = FIO_NOCONVERT; | |
| #endif | |
| while ((write_info.bw_len = read_eintr(fd, copybuf, | |
| BUFSIZE)) > 0) { | |
| if (buf_write_bytes(&write_info) == FAIL) { | |
| errmsg = (char_u *)_( | |
| "E506: Can't write to backup file (add ! to override)"); | |
| break; | |
| } | |
| os_breakcheck(); | |
| if (got_int) { | |
| errmsg = (char_u *)_(e_interr); | |
| break; | |
| } | |
| } | |
| if (close(bfd) < 0 && errmsg == NULL) | |
| errmsg = (char_u *)_( | |
| "E507: Close error for backup file (add ! to override)"); | |
| if (write_info.bw_len < 0) | |
| errmsg = (char_u *)_( | |
| "E508: Can't read file for backup (add ! to override)"); | |
| #ifdef UNIX | |
| set_file_time(backup, | |
| file_info_old.stat.st_atim.tv_sec, | |
| file_info_old.stat.st_mtim.tv_sec); | |
| #endif | |
| #ifdef HAVE_ACL | |
| mch_set_acl(backup, acl); | |
| #endif | |
| #ifdef HAVE_SELINUX | |
| mch_copy_sec(fname, backup); | |
| #endif | |
| break; | |
| } | |
| } | |
| } | |
| nobackup: | |
| close(fd); /* ignore errors for closing read file */ | |
| xfree(copybuf); | |
| if (backup == NULL && errmsg == NULL) | |
| errmsg = (char_u *)_( | |
| "E509: Cannot create backup file (add ! to override)"); | |
| /* ignore errors when forceit is TRUE */ | |
| if ((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 *)_(err_readonly); | |
| 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, ","); | |
| rootname = get_file_in_dir(fname, IObuff); | |
| if (rootname == NULL) | |
| backup = NULL; | |
| else { | |
| backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE); | |
| xfree(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 && os_file_exists(backup)) { | |
| p = backup + STRLEN(backup) - 1 - STRLEN(backup_ext); | |
| if (p < backup) /* empty file name ??? */ | |
| p = backup; | |
| *p = 'z'; | |
| while (*p > 'a' && os_file_exists(backup)) | |
| --*p; | |
| /* They all exist??? Must be something wrong! */ | |
| if (*p == 'a') { | |
| xfree(backup); | |
| backup = NULL; | |
| } | |
| } | |
| } | |
| 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; | |
| xfree(backup); /* don't do the rename below */ | |
| backup = NULL; | |
| } | |
| } | |
| if (backup == NULL && !forceit) { | |
| errmsg = (char_u *)_("E510: Can't make backup file (add ! to override)"); | |
| goto fail; | |
| } | |
| } | |
| } | |
| #if defined(UNIX) | |
| /* When using ":w!" and the file was read-only: make it writable */ | |
| if (forceit && perm >= 0 && !(perm & 0200) | |
| && file_info_old.stat.st_uid == getuid() | |
| && vim_strchr(p_cpo, CPO_FWRITE) == NULL) { | |
| perm |= 0200; | |
| (void)os_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_interr); | |
| goto restore_backup; | |
| } | |
| } | |
| /* 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 = verbose_try_malloc(write_info.bw_conv_buflen); | |
| if (!write_info.bw_conv_buf) { | |
| end = 0; | |
| } | |
| } | |
| } | |
| 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 = verbose_try_malloc(write_info.bw_conv_buflen); | |
| if (!write_info.bw_conv_buf) { | |
| end = 0; | |
| } | |
| write_info.bw_first = TRUE; | |
| } else | |
| # endif | |
| /* | |
| * 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(); | |
| if (wfname == NULL) { /* Can't write without a tempfile! */ | |
| errmsg = (char_u *)_("E214: Can't find temp file for writing"); | |
| goto restore_backup; | |
| } | |
| } | |
| } | |
| if (converted && wb_flags == 0 | |
| # ifdef USE_ICONV | |
| && write_info.bw_iconv_fd == (iconv_t)-1 | |
| # endif | |
| && wfname == fname | |
| ) { | |
| if (!forceit) { | |
| errmsg = (char_u *)_( | |
| "E213: Cannot convert (add ! to write without conversion)"); | |
| goto restore_backup; | |
| } | |
| notconverted = TRUE; | |
| } | |
| /* | |
| * 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 = os_open((char *)wfname, O_WRONLY | (append | |
| ? (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 | |
| * still readonly. This may also happen when the directory is | |
| * read-only. In that case the os_remove() will fail. | |
| */ | |
| if (errmsg == NULL) { | |
| #ifdef UNIX | |
| FileInfo file_info; | |
| /* Don't delete the file when it's a hard or symbolic link. */ | |
| if ((!newfile && os_fileinfo_hardlinks(&file_info) > 1) | |
| || (os_fileinfo_link((char *)fname, &file_info) | |
| && !os_fileinfo_id_equal(&file_info, &file_info_old))) { | |
| errmsg = (char_u *)_("E166: Can't open linked file for writing"); | |
| } else | |
| #endif | |
| { | |
| errmsg = (char_u *)_("E212: Can't 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 (file_info_old.stat.st_uid != getuid() | |
| || file_info_old.stat.st_gid != getgid()) { | |
| perm &= 0777; | |
| } | |
| #endif | |
| if (!append) /* don't remove when appending */ | |
| os_remove((char *)wfname); | |
| continue; | |
| } | |
| } | |
| } | |
| restore_backup: | |
| { | |
| /* | |
| * 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 (!os_file_exists(fname)) { | |
| vim_rename(backup, fname); | |
| } | |
| /* if original file does exist throw away the copy */ | |
| if (os_file_exists(fname)) { | |
| os_remove((char *)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 && !os_file_exists(fname)) { | |
| end = 0; | |
| } | |
| } | |
| if (wfname != fname) | |
| xfree(wfname); | |
| goto fail; | |
| } | |
| errmsg = NULL; | |
| write_info.bw_fd = fd; | |
| 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; | |
| /* | |
| * Skip the BOM 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 */ | |
| 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; | |
| 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); | |
| 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; | |
| if (write_undo_file) | |
| sha256_update(&sha_ctx, ptr + 1, (uint32_t)(STRLEN(ptr + 1) + 1)); | |
| 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 */ | |
| 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 | |
| && (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; | |
| os_breakcheck(); | |
| if (got_int) { | |
| end = 0; /* Interrupted, break loop */ | |
| break; | |
| } | |
| } | |
| } | |
| if (len > 0 && end > 0) { | |
| write_info.bw_len = len; | |
| if (buf_write_bytes(&write_info) == FAIL) | |
| end = 0; /* write error */ | |
| nchars += len; | |
| } | |
| #if defined(UNIX) && defined(HAVE_FSYNC) | |
| /* 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 | |
| #ifdef HAVE_SELINUX | |
| /* Probably need to set the security context. */ | |
| if (!backup_copy) | |
| mch_copy_sec(backup, wfname); | |
| #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) { | |
| /* don't change the owner when it's already OK, some systems remove | |
| * permission or ACL stuff */ | |
| FileInfo file_info; | |
| if (!os_fileinfo((char *)wfname, &file_info) | |
| || file_info.stat.st_uid != file_info_old.stat.st_uid | |
| || file_info.stat.st_gid != file_info_old.stat.st_gid) { | |
| os_fchown(fd, file_info_old.stat.st_uid, file_info_old.stat.st_gid); | |
| if (perm >= 0) /* set permission again, may have changed */ | |
| (void)os_setperm(wfname, perm); | |
| } | |
| buf_set_file_id(buf); | |
| } else if (!buf->file_id_valid) { | |
| // Set the file_id when creating a new file. | |
| buf_set_file_id(buf); | |
| } | |
| #endif | |
| if (close(fd) != 0) { | |
| errmsg = (char_u *)_("E512: Close failed"); | |
| end = 0; | |
| } | |
| #ifdef UNIX | |
| if (made_writable) | |
| perm &= ~0200; /* reset 'w' bit for security reasons */ | |
| #endif | |
| if (perm >= 0) /* set perm. of new file same as old file */ | |
| (void)os_setperm(wfname, perm); | |
| #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). */ | |
| if (!backup_copy) | |
| mch_set_acl(wfname, acl); | |
| #endif | |
| 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; | |
| } | |
| } | |
| os_remove((char *)wfname); | |
| xfree(wfname); | |
| } | |
| if (end == 0) { | |
| if (errmsg == NULL) { | |
| if (write_info.bw_conv_error) { | |
| if (write_info.bw_conv_error_lnum == 0) | |
| errmsg = (char_u *)_( | |
| "E513: write error, conversion failed (make 'fenc' empty to override)"); | |
| else { | |
| errmsg_allocated = TRUE; | |
| errmsg = xmalloc(300); | |
| vim_snprintf((char *)errmsg, 300, | |
| _("E513: write error, conversion failed in line %" PRId64 | |
| " (make 'fenc' empty to override)"), | |
| (int64_t)write_info.bw_conv_error_lnum); | |
| } | |
| } else if (got_int) | |
| errmsg = (char_u *)_(e_interr); | |
| else | |
| errmsg = (char_u *)_("E514: write error (file system full?)"); | |
| } | |
| /* | |
| * 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_interr)); | |
| ui_flush(); | |
| } | |
| if ((fd = os_open((char *)backup, O_RDONLY, 0)) >= 0) { | |
| if ((write_info.bw_fd = os_open((char *)fname, | |
| O_WRONLY | O_CREAT | O_TRUNC, | |
| perm & 0777)) >= 0) { | |
| /* copy the file. */ | |
| write_info.bw_buf = smallbuf; | |
| #ifdef HAS_BW_FLAGS | |
| write_info.bw_flags = FIO_NOCONVERT; | |
| #endif | |
| while ((write_info.bw_len = read_eintr(fd, smallbuf, | |
| SMBUFSIZE)) > 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) | |
| 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 %" PRId64 ";"), | |
| (int64_t)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, shortmess(SHM_NEW) ? _("[New]") : _("[New File]")); | |
| c = TRUE; | |
| } | |
| if (no_eol) { | |
| msg_add_eol(); | |
| c = TRUE; | |
| } | |
| /* may add [unix/dos/mac] */ | |
| if (msg_add_fileformat(fileformat)) | |
| c = TRUE; | |
| 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(msg_trunc_attr(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); | |
| /* buf->b_changedtick is always incremented in unchanged() but that | |
| * should not trigger a TextChanged event. */ | |
| if (last_changedtick + 1 == buf->b_changedtick | |
| && last_changedtick_buf == buf) { | |
| last_changedtick = buf->b_changedtick; | |
| } | |
| 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 = modname((char *)fname, (char *)p_pm, FALSE); | |
| if (backup != NULL) { | |
| /* | |
| * If the original file does not exist yet | |
| * the current backup file becomes the original file | |
| */ | |
| if (org == NULL) | |
| EMSG(_("E205: Patchmode: can't save original file")); | |
| else if (!os_file_exists((char_u *)org)) { | |
| vim_rename(backup, (char_u *)org); | |
| xfree(backup); /* don't delete the file */ | |
| backup = NULL; | |
| #ifdef UNIX | |
| set_file_time((char_u *)org, | |
| file_info_old.stat.st_atim.tv_sec, | |
| file_info_old.stat.st_mtim.tv_sec); | |
| #endif | |
| } | |
| } | |
| /* | |
| * If there is no backup file, remember that a (new) file was | |
| * created. | |
| */ | |
| else { | |
| int empty_fd; | |
| if (org == NULL | |
| || (empty_fd = os_open(org, | |
| O_CREAT | O_EXCL | O_NOFOLLOW, | |
| perm < 0 ? 0666 : (perm & 0777))) < 0) | |
| EMSG(_("E206: patchmode: can't touch empty original file")); | |
| else | |
| close(empty_fd); | |
| } | |
| if (org != NULL) { | |
| os_setperm((char_u *)org, os_getperm(fname) & 0777); | |
| xfree(org); | |
| } | |
| } | |
| /* | |
| * Remove the backup unless 'backup' option is set | |
| */ | |
| if (!p_bk && backup != NULL && os_remove((char *)backup) != 0) | |
| EMSG(_("E207: Can't 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; | |
| xfree(backup); | |
| if (buffer != smallbuf) | |
| xfree(buffer); | |
| xfree(fenc_tofree); | |
| xfree(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); | |
| memmove(IObuff, errnum, (size_t)numlen); | |
| } | |
| STRCAT(IObuff, errmsg); | |
| emsg(IObuff); | |
| if (errmsg_allocated) | |
| xfree(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 (os_fileinfo((char *)fname, &file_info_old)) { | |
| buf_store_file_info(buf, &file_info_old); | |
| buf->b_mtime_read = buf->b_mtime; | |
| } | |
| } | |
| } | |
| msg_scroll = msg_save; | |
| /* | |
| * 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); | |
| } | |
| if (!should_abort(retval)) { | |
| 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! | |
| */ | |
| aucmd_prepbuf(&aco, 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); | |
| if (aborting()) /* autocmds may abort script processing */ | |
| retval = FALSE; | |
| } | |
| got_int |= prev_got_int; | |
| return retval; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment