Commit message: patch 8.0.1263: others can read the swap file if a user is careless

Problem:    Others can read the swap file if a user is careless with his
            primary group.
Solution:   If the group permission allows for reading but the world
            permissions doesn't, make sure the group is right.
CVE: CVE-2017-17087
CWE: CWE-668
Vulnerability description: fileio.c in Vim prior to 8.0.1263 sets the group ownership of a .swp file to the editor's primary group (which may be different from the group ownership of the original file), which allows local users to obtain sensitive information by leveraging an applicable group membership, as demonstrated by /etc/shadow owned by root:shadow mode 0640, but /etc/.shadow.swp owned by root:users mode 0640, a different vulnerability than CVE-2017-1000382.

Project: vim
File: fileio.c

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 */
    stat_T	    st_old;
    int		    prev_got_int = got_int;
    int		    checking_conversion;
    int		    file_readonly = FALSE;  /* overwritten file is read-only */
    static char	    *err_readonly = "is read-only (cannot override: \"W\" in 'cpoptions')";
#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);
#ifdef FEAT_AUTOCMD
    linenr_T	    old_line_count = buf->b_ml.ml_line_count;
#endif
    int		    attr;
    int		    fileformat;
    int		    write_bin;
    struct bw_info  write_info;		/* info for buf_write_bytes() */
#ifdef FEAT_MBYTE
    int		    converted = FALSE;
    int		    notconverted = FALSE;
    char_u	    *fenc;		/* effective 'fileencoding' */
    char_u	    *fenc_tofree = NULL; /* allocated "fenc" */
#endif
#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
#ifdef FEAT_PERSISTENT_UNDO
    int		    write_undo_file = FALSE;
    context_sha256_T sha_ctx;
#endif
    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;
    }

#ifdef FEAT_MBYTE
    /* 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
#endif
#ifdef FEAT_CRYPT
    write_info.bw_buffer = buf;
#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
#ifdef FEAT_QUICKFIX
	    && !bt_nofile(buf)
#endif
	    && !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;

#ifdef FEAT_AUTOCMD
    {
	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);
	set_bufref(&bufref, buf);

	if (append)
	{
	    if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEAPPENDCMD,
					 sfname, sfname, FALSE, curbuf, eap)))
	    {
#ifdef FEAT_QUICKFIX
		if (overwriting && bt_nofile(curbuf))
		    nofile_err = TRUE;
		else
#endif
		    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
	    {
#ifdef FEAT_QUICKFIX
		if (overwriting && bt_nofile(curbuf))
		    nofile_err = TRUE;
		else
#endif
		    apply_autocmds_exarg(EVENT_BUFWRITEPRE,
					  sfname, sfname, FALSE, curbuf, eap);
	    }
	}
	else
	{
	    if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEWRITECMD,
					 sfname, sfname, FALSE, curbuf, eap)))
	    {
#ifdef FEAT_QUICKFIX
		if (overwriting && bt_nofile(curbuf))
		    nofile_err = TRUE;
		else
#endif
		    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
				       )
	{
	    --no_wait_return;
	    msg_scroll = msg_save;
	    if (nofile_err)
		EMSG(_("E676: No matching autocommands for acwrite buffer"));

	    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(_("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;
    }
#endif

#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 *)_("NetBeans disallows writes of unmodified buffers");
		buffer = NULL;
		goto fail;
	    }
	}
	else
	{
	    errnum = (char_u *)"E657: ";
	    errmsg = (char_u *)_("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(BUFSIZE);
    if (buffer == NULL)		    /* can't allocate big buffer, use small
				     * one (to be able to write when out of
				     * memory) */
    {
	buffer = smallbuf;
	bufsize = SMBUFSIZE;
    }
    else
	bufsize = BUFSIZE;

    /*
     * 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 *)_("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)
    {
# 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 *)_("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 *)_("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 *)_(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, &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);
#ifdef FEAT_WILDIGN
    if (dobackup && *p_bsk != NUL && match_file_list(p_bsk, sfname, ffname))
	dobackup = FALSE;
#endif

    /*
     * 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(WIN32)
	stat_T	    st;
#endif

	if ((bkc & BKC_YES) || append)	/* "yes" */
	    backup_copy = TRUE;
#if defined(UNIX) || defined(WIN32)
	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 WIN32
	    /* 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);
		for (i = 4913; ; i += 123)
		{
		    sprintf((char *)gettail(IObuff), "%d", i);
		    if (mch_lstat((char *)IObuff, &st) < 0)
			break;
		}
		fd = mch_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
#  ifdef HAVE_FCHOWN
		    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(WIN32)
	    /* 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)
	    int		did_set_shortname;
#endif

	    copybuf = alloc(BUFSIZE + 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, BUFSIZE, ",");
		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 backup file name.
		     */
		    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_free(backup);
			    backup = NULL;	/* 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_free(backup);
				backup = NULL;
			    }
			}
		    }
		    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?) */
		    bfd = mch_open((char *)backup,
				O_WRONLY|O_CREAT|O_EXTRA|O_EXCL|O_NOFOLLOW,
								 perm & 0777);
		    if (bfd < 0)
		    {
			vim_free(backup);
			backup = NULL;
		    }
		    else
		    {
			/* set file protection same as original file, but
			 * strip s-bit */
			(void)mch_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 (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
#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;
			    }
			    ui_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, 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
			break;
		    }
		}
	    }
    nobackup:
	    close(fd);		/* ignore errors for closing read file */
	    vim_free(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 = 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_free(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;

		    vim_free(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) && 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;
#ifdef FEAT_TITLE
	need_maketitle = TRUE;	    /* set window title later */
#endif
	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;
	}
    }

#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;

#ifdef FEAT_MBYTE
    /* 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
			   = lalloc((long_u)write_info.bw_conv_buflen, TRUE);
	    if (write_info.bw_conv_buf == NULL)
		end = 0;
	}
    }

# ifdef WIN3264
    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
			    = lalloc((long_u)write_info.bw_conv_buflen, TRUE);
	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
			    = lalloc((long_u)write_info.bw_conv_buflen, TRUE);
	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
			   = lalloc((long_u)write_info.bw_conv_buflen, TRUE);
	    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 *)_("E214: Can't 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 *)_("E213: Cannot convert (add ! to write without conversion)");
	    goto restore_backup;
	}
	notconverted = TRUE;
    }
#endif

    /*
     * 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.
	 */
#ifdef FEAT_MBYTE
	if (!converted || dobackup)
#endif
	    checking_conversion = FALSE;

	if (checking_conversion)
	{
	    /* Make sure we don't write anything. */
	    fd = -1;
	    write_info.bw_fd = fd;
	}
	else
	{
	    /*
	     * Open the file "wfname" for writing.
	     * We may try to open the file twice: If we can't write to the file
	     * and forceit is TRUE we delete the existing file and try to
	     * create a new one. If this still fails we may have lost the
	     * original file!  (this may happen when the user reached his
	     * quotum for number of files).
	     * Appending will fail if the file does not exist and forceit is
	     * FALSE.
	     */
	    while ((fd = mch_open((char *)wfname, O_WRONLY | O_EXTRA | (append
				? (forceit ? (O_APPEND | O_CREAT) : O_APPEND)
				: (O_CREAT | O_TRUNC))
				, perm < 0 ? 0666 : (perm & 0777))) < 0)
	    {
		/*
		 * 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 *)_("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 (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;
		}

#ifdef FEAT_MBYTE
		if (wfname != fname)
		    vim_free(wfname);
#endif
		goto fail;
	    }
	    write_info.bw_fd = fd;

#if defined(WIN3264)
	    if (backup != NULL && overwriting && !append)
	    {
		if (backup_copy)
		    (void)mch_copy_file_attribute(wfname, backup);
		else
		    (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;

#ifdef FEAT_MBYTE
	/*
	 * The BOM is written just after the encryption magic number.
	 * Skip it when appending and the file already existed, the BOM only
	 * makes sense at the start of the file.
	 */
	if (buf->b_p_bomb && !write_bin && (!append || perm < 0))
	{
	    write_info.bw_len = make_bom(buffer, fenc);
	    if (write_info.bw_len > 0)
	    {
		/* don't convert, do encryption */
		write_info.bw_flags = FIO_NOCONVERT | wb_flags;
		if (buf_write_bytes(&write_info) == FAIL)
		    end = 0;
		else
		    nchars += write_info.bw_len;
	    }
	}
	write_info.bw_start_lnum = start;
#endif

#ifdef FEAT_PERSISTENT_UNDO
	write_undo_file = (buf->b_p_udf
			    && overwriting
			    && !append
			    && !filtering
			    && reset_changed
			    && !checking_conversion);
	if (write_undo_file)
	    /* Prepare for computing the hash value of the text. */
	    sha256_start(&sha_ctx);
#endif

	write_info.bw_len = bufsize;
#ifdef HAS_BW_FLAGS
	write_info.bw_flags = wb_flags;
#endif
	fileformat = get_fileformat_force(buf, eap);
	s = buffer;
	len = 0;
	for (lnum = start; lnum <= end; ++lnum)
	{
	    /*
	     * The next while loop is done once for each character written.
	     * Keep it fast!
	     */
	    ptr = ml_get_buf(buf, lnum, FALSE) - 1;
#ifdef FEAT_PERSISTENT_UNDO
	    if (write_undo_file)
		sha256_update(&sha_ctx, ptr + 1,
					      (UINT32_T)(STRLEN(ptr + 1) + 1));
#endif
	    while ((c = *++ptr) != NUL)
	    {
		if (c == NL)
		    *s = NUL;		/* replace newlines with NULs */
		else if (c == CAR && fileformat == EOL_MAC)
		    *s = NL;		/* Mac: replace CRs with NLs */
		else
		    *s = c;
		++s;
		if (++len != bufsize)
		    continue;
		if (buf_write_bytes(&write_info) == FAIL)
		{
		    end = 0;		/* write error: break loop */
		    break;
		}
		nchars += bufsize;
		s = buffer;
		len = 0;
#ifdef FEAT_MBYTE
		write_info.bw_start_lnum = lnum;
#endif
	    }
	    /* write failed or last line has no EOL: stop here */
	    if (end == 0
		    || (lnum == end
			&& (write_bin || !buf->b_p_fixeol)
			&& (lnum == buf->b_no_eol_lnum
			    || (lnum == buf->b_ml.ml_line_count
							   && !buf->b_p_eol))))
	    {
		++lnum;			/* written the line, count it */
		no_eol = TRUE;
		break;
	    }
	    if (fileformat == EOL_UNIX)
		*s++ = NL;
	    else
	    {
		*s++ = CAR;		    /* EOL_MAC or EOL_DOS: write CR */
		if (fileformat == EOL_DOS)  /* write CR-NL */
		{
		    if (++len == bufsize)
		    {
			if (buf_write_bytes(&write_info) == FAIL)
			{
			    end = 0;	/* write error: break loop */
			    break;
			}
			nchars += bufsize;
			s = buffer;
			len = 0;
		    }
		    *s++ = NL;
		}
	    }
	    if (++len == bufsize && end)
	    {
		if (buf_write_bytes(&write_info) == FAIL)
		{
		    end = 0;		/* write error: break loop */
		    break;
		}
		nchars += bufsize;
		s = buffer;
		len = 0;

		ui_breakcheck();
		if (got_int)
		{
		    end = 0;		/* Interrupted, break loop */
		    break;
		}
	    }
#ifdef VMS
	    /*
	     * On VMS there is a problem: newlines get added when writing
	     * blocks at a time. Fix it by writing a line at a time.
	     * This is much slower!
	     * Explanation: VAX/DECC RTL insists that records in some RMS
	     * structures end with a newline (carriage return) character, and
	     * if they don't it adds one.
	     * With other RMS structures it works perfect without this fix.
	     */
	    if (buf->b_fab_rfm == FAB$C_VFC
		    || ((buf->b_fab_rat & (FAB$M_FTN | FAB$M_CR)) != 0))
	    {
		int b2write;

		buf->b_fab_mrs = (buf->b_fab_mrs == 0
			? MIN(4096, bufsize)
			: MIN(buf->b_fab_mrs, bufsize));

		b2write = len;
		while (b2write > 0)
		{
		    write_info.bw_len = MIN(b2write, buf->b_fab_mrs);
		    if (buf_write_bytes(&write_info) == FAIL)
		    {
			end = 0;
			break;
		    }
		    b2write -= MIN(b2write, buf->b_fab_mrs);
		}
		write_info.bw_len = bufsize;
		nchars += len;
		s = buffer;
		len = 0;
	    }
#endif
	}
	if (len > 0 && end > 0)
	{
	    write_info.bw_len = len;
	    if (buf_write_bytes(&write_info) == FAIL)
		end = 0;		    /* write error */
	    nchars += len;
	}

	/* Stop when writing done or an error was encountered. */
	if (!checking_conversion || end == 0)
	    break;

	/* If no error happened until now, writing should be ok, so loop to
	 * really write the buffer. */
    }

    /* If we started writing, finish writing. Also when an error was
     * encountered. */
    if (!checking_conversion)
    {
#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

#if defined(HAVE_SELINUX) || defined(HAVE_SMACK)
	/* 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)
	{
# ifdef HAVE_FCHOWN
	    stat_T	st;

	    /* don't change the owner when it's already OK, some systems remove
	     * permission or ACL stuff */
	    if (mch_stat((char *)wfname, &st) < 0
		    || st.st_uid != st_old.st_uid
		    || st.st_gid != st_old.st_gid)
	    {
		ignored = fchown(fd, st_old.st_uid, st_old.st_gid);
		if (perm >= 0)	/* set permission again, may have changed */
		    (void)mch_setperm(wfname, perm);
	    }
# endif
	    buf_setino(buf);
	}
	else if (!buf->b_dev_valid)
	    /* Set the inode when creating a new file. */
	    buf_setino(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)mch_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).
	 * 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_MBYTE) && 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)
	{
#ifdef FEAT_MBYTE
	    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 = alloc(300);
		    vim_snprintf((char *)errmsg, 300, _("E513: write error, conversion failed in line %ld (make 'fenc' empty to override)"),
					 (long)write_info.bw_conv_error_lnum);
		}
	    }
	    else
#endif
		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));
		    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;
#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) || 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;
#ifdef FEAT_MBYTE
	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;
	}
#endif
	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;
#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(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
#ifdef FEAT_MBYTE
	    && !write_info.bw_conv_error
#endif
	    && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)
	    )
    {
	unchanged(buf, TRUE);
#ifdef FEAT_AUTOCMD
	/* b:changedtick is always incremented in unchanged() but that
	 * should not trigger a TextChanged event. */
	if (last_changedtick + 1 == CHANGEDTICK(buf)
					       && last_changedtick_buf == buf)
	    last_changedtick = CHANGEDTICK(buf);
#endif
	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(_("E205: Patchmode: can't save original file"));
	    else if (mch_stat(org, &st) < 0)
	    {
		vim_rename(backup, (char_u *)org);
		vim_free(backup);	    /* don't delete the file */
		backup = NULL;
#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(_("E206: patchmode: can't 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
     */
    if (!p_bk && backup != NULL && mch_remove(backup) != 0)
	EMSG(_("E207: Can't delete backup file"));

#ifdef FEAT_SUN_WORKSHOP
    if (usingSunWorkShop)
	workshop_file_saved((char *) ffname);
#endif

    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);
#ifdef FEAT_MBYTE
    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
#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(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;
	    }
	}
    }
    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_AUTOCMD
#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!
	 */
	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);

#ifdef FEAT_EVAL
	if (aborting())	    /* autocmds may abort script processing */
	    retval = FALSE;
#endif
    }
#endif

    got_int |= prev_got_int;

    return retval;
}

Project: vim
File: fileio.c

ex_doautoall(exarg_T *eap)
{
    int		retval;
    aco_save_T	aco;
    buf_T	*buf;
    bufref_T	bufref;
    char_u	*arg = eap->arg;
    int		call_do_modelines = check_nomodeline(&arg);
    int		did_aucmd;

    /*
     * This is a bit tricky: For some commands curwin->w_buffer needs to be
     * equal to curbuf, but for some buffers there may not be a window.
     * So we change the buffer for the current window for a moment.  This
     * gives problems when the autocommands make changes to the list of
     * buffers or windows...
     */
    FOR_ALL_BUFFERS(buf)
    {
	if (buf->b_ml.ml_mfp != NULL)
	{
	    /* find a window for this buffer and save some values */
	    aucmd_prepbuf(&aco, buf);
	    set_bufref(&bufref, buf);

	    /* execute the autocommands for this buffer */
	    retval = do_doautocmd(arg, FALSE, &did_aucmd);

	    if (call_do_modelines && did_aucmd)
	    {
		/* Execute the modeline settings, but don't set window-local
		 * options if we are using the current window for another
		 * buffer. */
		do_modelines(curwin == aucmd_win ? OPT_NOWIN : 0);
	    }

	    /* restore the current window */
	    aucmd_restbuf(&aco);

	    /* stop if there is some error or buffer was deleted */
	    if (retval == FAIL || !bufref_valid(&bufref))
		break;
	}
    }

    check_cursor();	    /* just in case lines got deleted */
}

Project: vim
File: fileio.c

msg_add_fname(buf_T *buf, char_u *fname)
{
    if (fname == NULL)
	fname = (char_u *)"-stdin-";
    home_replace(buf, fname, IObuff + 1, IOSIZE - 4, TRUE);
    IObuff[0] = '"';
    STRCAT(IObuff, "\" ");
}

Project: vim
File: fileio.c

delete_recursive(char_u *name)
{
    int result = 0;
    char_u	**files;
    int		file_count;
    int		i;
    char_u	*exp;

    /* A symbolic link to a directory itself is deleted, not the directory it
     * points to. */
    if (
# if defined(UNIX) || defined(WIN32)
	 mch_isrealdir(name)
# else
	 mch_isdir(name)
# endif
	    )
    {
	vim_snprintf((char *)NameBuff, MAXPATHL, "%s/*", name);
	exp = vim_strsave(NameBuff);
	if (exp == NULL)
	    return -1;
	if (gen_expand_wildcards(1, &exp, &file_count, &files,
	      EW_DIR|EW_FILE|EW_SILENT|EW_ALLLINKS|EW_DODOT|EW_EMPTYOK) == OK)
	{
	    for (i = 0; i < file_count; ++i)
		if (delete_recursive(files[i]) != 0)
		    result = -1;
	    FreeWild(file_count, files);
	}
	else
	    result = -1;
	vim_free(exp);
	(void)mch_rmdir(name);
    }
    else
	result = mch_remove(name) == 0 ? 0 : -1;

    return result;
}

Project: vim
File: fileio.c

msg_add_fileformat(int eol_type)
{
#ifndef USE_CRNL
    if (eol_type == EOL_DOS)
    {
	STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[dos]") : _("[dos format]"));
	return TRUE;
    }
#endif
#ifndef USE_CR
    if (eol_type == EOL_MAC)
    {
	STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[mac]") : _("[mac format]"));
	return TRUE;
    }
#endif
#if defined(USE_CRNL) || defined(USE_CR)
    if (eol_type == EOL_UNIX)
    {
	STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[unix]") : _("[unix format]"));
	return TRUE;
    }
#endif
    return FALSE;
}

Project: vim
File: fileio.c

do_augroup(char_u *arg, int del_group)
{
    int	    i;

    if (del_group)
    {
	if (*arg == NUL)
	    EMSG(_(e_argreq));
	else
	    au_del_group(arg);
    }
    else if (STRICMP(arg, "end") == 0)   /* ":aug end": back to group 0 */
	current_augroup = AUGROUP_DEFAULT;
    else if (*arg)		    /* ":aug xxx": switch to group xxx */
    {
	i = au_new_group(arg);
	if (i != AUGROUP_ERROR)
	    current_augroup = i;
    }
    else			    /* ":aug": list the group names */
    {
	msg_start();
	for (i = 0; i < augroups.ga_len; ++i)
	{
	    if (AUGROUP_NAME(i) != NULL)
	    {
		msg_puts(AUGROUP_NAME(i));
		msg_puts((char_u *)"  ");
	    }
	}
	msg_clr_eos();
	msg_end();
    }
}

Project: vim
File: fileio.c

need_conversion(char_u *fenc)
{
    int		same_encoding;
    int		enc_flags;
    int		fenc_flags;

    if (*fenc == NUL || STRCMP(p_enc, fenc) == 0)
    {
	same_encoding = TRUE;
	fenc_flags = 0;
    }
    else
    {
	/* Ignore difference between "ansi" and "latin1", "ucs-4" and
	 * "ucs-4be", etc. */
	enc_flags = get_fio_flags(p_enc);
	fenc_flags = get_fio_flags(fenc);
	same_encoding = (enc_flags != 0 && fenc_flags == enc_flags);
    }
    if (same_encoding)
    {
	/* Specified encoding matches with 'encoding'.  This requires
	 * conversion when 'encoding' is Unicode but not UTF-8. */
	return enc_unicode != 0;
    }

    /* Encodings differ.  However, conversion is not needed when 'enc' is any
     * Unicode encoding and the file is UTF-8. */
    return !(enc_utf8 && fenc_flags == FIO_UTF8);
}

Project: vim
File: fileio.c

event_name2nr(char_u *start, char_u **end)
{
    char_u	*p;
    int		i;
    int		len;

    /* the event name ends with end of line, '|', a blank or a comma */
    for (p = start; *p && !VIM_ISWHITE(*p) && *p != ',' && *p != '|'; ++p)
	;
    for (i = 0; event_names[i].name != NULL; ++i)
    {
	len = (int)STRLEN(event_names[i].name);
	if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0)
	    break;
    }
    if (*p == ',')
	++p;
    *end = p;
    if (event_names[i].name == NULL)
	return NUM_EVENTS;
    return event_names[i].event;
}

Project: vim
File: fileio.c

check_marks_read(void)
{
    if (!curbuf->b_marks_read && get_viminfo_parameter('\'') > 0
						  && curbuf->b_ffname != NULL)
	read_viminfo(NULL, VIF_WANT_MARKS);

    /* Always set b_marks_read; needed when 'viminfo' is changed to include
     * the ' parameter after opening a buffer. */
    curbuf->b_marks_read = TRUE;
}

Project: vim
File: fileio.c

shorten_fname(char_u *full_path, char_u *dir_name)
{
    int		len;
    char_u	*p;

    if (full_path == NULL)
	return NULL;
    len = (int)STRLEN(dir_name);
    if (fnamencmp(dir_name, full_path, len) == 0)
    {
	p = full_path + len;
#if defined(MSWIN)
	/*
	 * MSWIN: when a file is in the root directory, dir_name will end in a
	 * slash, since C: by itself does not define a specific dir. In this
	 * case p may already be correct. <negri>
	 */
	if (!((len > 2) && (*(p - 2) == ':')))
#endif
	{
	    if (vim_ispathsep(*p))
		++p;
#ifndef VMS   /* the path separator is always part of the path */
	    else
		p = NULL;
#endif
	}
    }
#if defined(MSWIN)
    /*
     * When using a file in the current drive, remove the drive name:
     * "A:\dir\file" -> "\dir\file".  This helps when moving a session file on
     * a floppy from "A:\dir" to "B:\dir".
     */
    else if (len > 3
	    && TOUPPER_LOC(full_path[0]) == TOUPPER_LOC(dir_name[0])
	    && full_path[1] == ':'
	    && vim_ispathsep(full_path[2]))
	p = full_path + 2;
#endif
    else
	p = NULL;
    return p;
}

Project: vim
File: fileio.c

msg_add_lines(
    int	    insert_space,
    long    lnum,
    off_T   nchars)
{
    char_u  *p;

    p = IObuff + STRLEN(IObuff);

    if (insert_space)
	*p++ = ' ';
    if (shortmess(SHM_LINES))
	vim_snprintf((char *)p, IOSIZE - (p - IObuff),
		"%ldL, %lldC", lnum, (varnumber_T)nchars);
    else
    {
	if (lnum == 1)
	    STRCPY(p, _("1 line, "));
	else
	    sprintf((char *)p, _("%ld lines, "), lnum);
	p += STRLEN(p);
	if (nchars == 1)
	    STRCPY(p, _("1 character"));
	else
	    vim_snprintf((char *)p, IOSIZE - (p - IObuff),
		    _("%lld characters"), (varnumber_T)nchars);
    }
}

Project: vim
File: fileio.c

time_differs(long t1, long t2)
{
#if defined(__linux__) || defined(MSWIN)
    /* On a FAT filesystem, esp. under Linux, there are only 5 bits to store
     * the seconds.  Since the roundoff is done when flushing the inode, the
     * time may change unexpectedly by one second!!! */
    return (t1 - t2 > 1 || t2 - t1 > 1);
#else
    return (t1 != t2);
#endif
}

Project: vim
File: fileio.c

buf_check_timestamp(
    buf_T	*buf,
    int		focus UNUSED)	/* called for GUI focus event */
{
    stat_T	st;
    int		stat_res;
    int		retval = 0;
    char_u	*path;
    char_u	*tbuf;
    char	*mesg = NULL;
    char	*mesg2 = "";
    int		helpmesg = FALSE;
    int		reload = FALSE;
    char	*reason;
#if defined(FEAT_CON_DIALOG) || defined(FEAT_GUI_DIALOG)
    int		can_reload = FALSE;
#endif
    off_T	orig_size = buf->b_orig_size;
    int		orig_mode = buf->b_orig_mode;
#ifdef FEAT_GUI
    int		save_mouse_correct = need_mouse_correct;
#endif
#ifdef FEAT_AUTOCMD
    static int	busy = FALSE;
    int		n;
    char_u	*s;
    bufref_T	bufref;

    set_bufref(&bufref, buf);
#endif

    /* If there is no file name, the buffer is not loaded, 'buftype' is
     * set, we are in the middle of a save or being called recursively: ignore
     * this buffer. */
    if (buf->b_ffname == NULL
	    || buf->b_ml.ml_mfp == NULL
	    || *buf->b_p_bt != NUL
	    || buf->b_saving
#ifdef FEAT_AUTOCMD
	    || busy
#endif
#ifdef FEAT_NETBEANS_INTG
	    || isNetbeansBuffer(buf)
#endif
#ifdef FEAT_TERMINAL
	    || buf->b_term != NULL
#endif
	    )
	return 0;

    if (       !(buf->b_flags & BF_NOTEDITED)
	    && buf->b_mtime != 0
	    && ((stat_res = mch_stat((char *)buf->b_ffname, &st)) < 0
		|| time_differs((long)st.st_mtime, buf->b_mtime)
		|| st.st_size != buf->b_orig_size
#ifdef HAVE_ST_MODE
		|| (int)st.st_mode != buf->b_orig_mode
#else
		|| mch_getperm(buf->b_ffname) != buf->b_orig_mode
#endif
		))
    {
	retval = 1;

	/* set b_mtime to stop further warnings (e.g., when executing
	 * FileChangedShell autocmd) */
	if (stat_res < 0)
	{
	    buf->b_mtime = 0;
	    buf->b_orig_size = 0;
	    buf->b_orig_mode = 0;
	}
	else
	    buf_store_time(buf, &st, buf->b_ffname);

	/* Don't do anything for a directory.  Might contain the file
	 * explorer. */
	if (mch_isdir(buf->b_fname))
	    ;

	/*
	 * If 'autoread' is set, the buffer has no changes and the file still
	 * exists, reload the buffer.  Use the buffer-local option value if it
	 * was set, the global option value otherwise.
	 */
	else if ((buf->b_p_ar >= 0 ? buf->b_p_ar : p_ar)
				       && !bufIsChanged(buf) && stat_res >= 0)
	    reload = TRUE;
	else
	{
	    if (stat_res < 0)
		reason = "deleted";
	    else if (bufIsChanged(buf))
		reason = "conflict";
	    else if (orig_size != buf->b_orig_size || buf_contents_changed(buf))
		reason = "changed";
	    else if (orig_mode != buf->b_orig_mode)
		reason = "mode";
	    else
		reason = "time";

#ifdef FEAT_AUTOCMD
	    /*
	     * Only give the warning if there are no FileChangedShell
	     * autocommands.
	     * Avoid being called recursively by setting "busy".
	     */
	    busy = TRUE;
# ifdef FEAT_EVAL
	    set_vim_var_string(VV_FCS_REASON, (char_u *)reason, -1);
	    set_vim_var_string(VV_FCS_CHOICE, (char_u *)"", -1);
# endif
	    ++allbuf_lock;
	    n = apply_autocmds(EVENT_FILECHANGEDSHELL,
				      buf->b_fname, buf->b_fname, FALSE, buf);
	    --allbuf_lock;
	    busy = FALSE;
	    if (n)
	    {
		if (!bufref_valid(&bufref))
		    EMSG(_("E246: FileChangedShell autocommand deleted buffer"));
# ifdef FEAT_EVAL
		s = get_vim_var_str(VV_FCS_CHOICE);
		if (STRCMP(s, "reload") == 0 && *reason != 'd')
		    reload = TRUE;
		else if (STRCMP(s, "ask") == 0)
		    n = FALSE;
		else
# endif
		    return 2;
	    }
	    if (!n)
#endif
	    {
		if (*reason == 'd')
		    mesg = _("E211: File \"%s\" no longer available");
		else
		{
		    helpmesg = TRUE;
#if defined(FEAT_CON_DIALOG) || defined(FEAT_GUI_DIALOG)
		    can_reload = TRUE;
#endif
		    /*
		     * Check if the file contents really changed to avoid
		     * giving a warning when only the timestamp was set (e.g.,
		     * checked out of CVS).  Always warn when the buffer was
		     * changed.
		     */
		    if (reason[2] == 'n')
		    {
			mesg = _("W12: Warning: File \"%s\" has changed and the buffer was changed in Vim as well");
			mesg2 = _("See \":help W12\" for more info.");
		    }
		    else if (reason[1] == 'h')
		    {
			mesg = _("W11: Warning: File \"%s\" has changed since editing started");
			mesg2 = _("See \":help W11\" for more info.");
		    }
		    else if (*reason == 'm')
		    {
			mesg = _("W16: Warning: Mode of file \"%s\" has changed since editing started");
			mesg2 = _("See \":help W16\" for more info.");
		    }
		    else
			/* Only timestamp changed, store it to avoid a warning
			 * in check_mtime() later. */
			buf->b_mtime_read = buf->b_mtime;
		}
	    }
	}

    }
    else if ((buf->b_flags & BF_NEW) && !(buf->b_flags & BF_NEW_W)
						&& vim_fexists(buf->b_ffname))
    {
	retval = 1;
	mesg = _("W13: Warning: File \"%s\" has been created after editing started");
	buf->b_flags |= BF_NEW_W;
#if defined(FEAT_CON_DIALOG) || defined(FEAT_GUI_DIALOG)
	can_reload = TRUE;
#endif
    }

    if (mesg != NULL)
    {
	path = home_replace_save(buf, buf->b_fname);
	if (path != NULL)
	{
	    if (!helpmesg)
		mesg2 = "";
	    tbuf = alloc((unsigned)(STRLEN(path) + STRLEN(mesg)
							+ STRLEN(mesg2) + 2));
	    sprintf((char *)tbuf, mesg, path);
#ifdef FEAT_EVAL
	    /* Set warningmsg here, before the unimportant and output-specific
	     * mesg2 has been appended. */
	    set_vim_var_string(VV_WARNINGMSG, tbuf, -1);
#endif
#if defined(FEAT_CON_DIALOG) || defined(FEAT_GUI_DIALOG)
	    if (can_reload)
	    {
		if (*mesg2 != NUL)
		{
		    STRCAT(tbuf, "\n");
		    STRCAT(tbuf, mesg2);
		}
		if (do_dialog(VIM_WARNING, (char_u *)_("Warning"), tbuf,
			  (char_u *)_("&OK\n&Load File"), 1, NULL, TRUE) == 2)
		    reload = TRUE;
	    }
	    else
#endif
	    if (State > NORMAL_BUSY || (State & CMDLINE) || already_warned)
	    {
		if (*mesg2 != NUL)
		{
		    STRCAT(tbuf, "; ");
		    STRCAT(tbuf, mesg2);
		}
		EMSG(tbuf);
		retval = 2;
	    }
	    else
	    {
# ifdef FEAT_AUTOCMD
		if (!autocmd_busy)
# endif
		{
		    msg_start();
		    msg_puts_attr(tbuf, HL_ATTR(HLF_E) + MSG_HIST);
		    if (*mesg2 != NUL)
			msg_puts_attr((char_u *)mesg2,
						   HL_ATTR(HLF_W) + MSG_HIST);
		    msg_clr_eos();
		    (void)msg_end();
		    if (emsg_silent == 0)
		    {
			out_flush();
# ifdef FEAT_GUI
			if (!focus)
# endif
			    /* give the user some time to think about it */
			    ui_delay(1000L, TRUE);

			/* don't redraw and erase the message */
			redraw_cmdline = FALSE;
		    }
		}
		already_warned = TRUE;
	    }

	    vim_free(path);
	    vim_free(tbuf);
	}
    }

    if (reload)
    {
	/* Reload the buffer. */
	buf_reload(buf, orig_mode);
#ifdef FEAT_PERSISTENT_UNDO
	if (buf->b_p_udf && buf->b_ffname != NULL)
	{
	    char_u	    hash[UNDO_HASH_SIZE];
	    buf_T	    *save_curbuf = curbuf;

	    /* Any existing undo file is unusable, write it now. */
	    curbuf = buf;
	    u_compute_hash(hash);
	    u_write_undo(NULL, FALSE, buf, hash);
	    curbuf = save_curbuf;
	}
#endif
    }

#ifdef FEAT_AUTOCMD
    /* Trigger FileChangedShell when the file was changed in any way. */
    if (bufref_valid(&bufref) && retval != 0)
	(void)apply_autocmds(EVENT_FILECHANGEDSHELLPOST,
				      buf->b_fname, buf->b_fname, FALSE, buf);
#endif
#ifdef FEAT_GUI
    /* restore this in case an autocommand has set it; it would break
     * 'mousefocus' */
    need_mouse_correct = save_mouse_correct;
#endif

    return retval;
}

Project: vim
File: fileio.c

filemess(
    buf_T	*buf,
    char_u	*name,
    char_u	*s,
    int		attr)
{
    int		msg_scroll_save;

    if (msg_silent != 0)
	return;
    msg_add_fname(buf, name);	    /* put file name in IObuff with quotes */
    /* If it's extremely long, truncate it. */
    if (STRLEN(IObuff) > IOSIZE - 80)
	IObuff[IOSIZE - 80] = NUL;
    STRCAT(IObuff, s);
    /*
     * For the first message may have to start a new line.
     * For further ones overwrite the previous one, reset msg_scroll before
     * calling filemess().
     */
    msg_scroll_save = msg_scroll;
    if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0)
	msg_scroll = FALSE;
    if (!msg_scroll)	/* wait a bit when overwriting an error msg */
	check_for_delay(FALSE);
    msg_start();
    msg_scroll = msg_scroll_save;
    msg_scrolled_ign = TRUE;
    /* may truncate the message to avoid a hit-return prompt */
    msg_outtrans_attr(msg_may_trunc(FALSE, IObuff), attr);
    msg_clr_eos();
    out_flush();
    msg_scrolled_ign = FALSE;
}

Project: vim
File: fileio.c

read_eintr(int fd, void *buf, size_t bufsize)
{
    long ret;

    for (;;)
    {
	ret = vim_read(fd, buf, bufsize);
	if (ret >= 0 || errno != EINTR)
	    break;
    }
    return ret;
}

Project: vim
File: fileio.c

check_for_cryptkey(
    char_u	*cryptkey,	/* previous encryption key or NULL */
    char_u	*ptr,		/* pointer to read bytes */
    long	*sizep,		/* length of read bytes */
    off_T	*filesizep,	/* nr of bytes used from file */
    int		newfile,	/* editing a new buffer */
    char_u	*fname,		/* file name to display */
    int		*did_ask)	/* flag: whether already asked for key */
{
    int method = crypt_method_nr_from_magic((char *)ptr, *sizep);
    int b_p_ro = curbuf->b_p_ro;

    if (method >= 0)
    {
	/* Mark the buffer as read-only until the decryption has taken place.
	 * Avoids accidentally overwriting the file with garbage. */
	curbuf->b_p_ro = TRUE;

	/* Set the cryptmethod local to the buffer. */
	crypt_set_cm_option(curbuf, method);
	if (cryptkey == NULL && !*did_ask)
	{
	    if (*curbuf->b_p_key)
		cryptkey = curbuf->b_p_key;
	    else
	    {
		/* When newfile is TRUE, store the typed key in the 'key'
		 * option and don't free it.  bf needs hash of the key saved.
		 * Don't ask for the key again when first time Enter was hit.
		 * Happens when retrying to detect encoding. */
		smsg((char_u *)_(need_key_msg), fname);
		msg_scroll = TRUE;
		crypt_check_method(method);
		cryptkey = crypt_get_key(newfile, FALSE);
		*did_ask = TRUE;

		/* check if empty key entered */
		if (cryptkey != NULL && *cryptkey == NUL)
		{
		    if (cryptkey != curbuf->b_p_key)
			vim_free(cryptkey);
		    cryptkey = NULL;
		}
	    }
	}

	if (cryptkey != NULL)
	{
	    int header_len;

	    curbuf->b_cryptstate = crypt_create_from_header(
						       method, cryptkey, ptr);
	    crypt_set_cm_option(curbuf, method);

	    /* Remove cryptmethod specific header from the text. */
	    header_len = crypt_get_header_len(method);
	    if (*sizep <= header_len)
		/* invalid header, buffer can't be encrypted */
		return NULL;
	    *filesizep += header_len;
	    *sizep -= header_len;
	    mch_memmove(ptr, ptr + header_len, (size_t)*sizep);

	    /* Restore the read-only flag. */
	    curbuf->b_p_ro = b_p_ro;
	}
    }
    /* When starting to edit a new file which does not have encryption, clear
     * the 'key' option, except when starting up (called with -x argument) */
    else if (newfile && *curbuf->b_p_key != NUL && !starting)
	set_option_value((char_u *)"key", 0L, (char_u *)"", OPT_LOCAL);

    return cryptkey;
}

Project: vim
File: fileio.c

vim_rename(char_u *from, char_u *to)
{
    int		fd_in;
    int		fd_out;
    int		n;
    char	*errmsg = NULL;
    char	*buffer;
#ifdef AMIGA
    BPTR	flock;
#endif
    stat_T	st;
    long	perm;
#ifdef HAVE_ACL
    vim_acl_T	acl;		/* ACL from original file */
#endif
    int		use_tmp_file = FALSE;

    /*
     * When the names are identical, there is nothing to do.  When they refer
     * to the same file (ignoring case and slash/backslash differences) but
     * the file name differs we need to go through a temp file.
     */
    if (fnamecmp(from, to) == 0)
    {
	if (p_fic && STRCMP(gettail(from), gettail(to)) != 0)
	    use_tmp_file = TRUE;
	else
	    return 0;
    }

    /*
     * Fail if the "from" file doesn't exist.  Avoids that "to" is deleted.
     */
    if (mch_stat((char *)from, &st) < 0)
	return -1;

#ifdef UNIX
    {
	stat_T	st_to;

	/* It's possible for the source and destination to be the same file.
	 * This happens when "from" and "to" differ in case and are on a FAT32
	 * filesystem.  In that case go through a temp file name. */
	if (mch_stat((char *)to, &st_to) >= 0
		&& st.st_dev == st_to.st_dev
		&& st.st_ino == st_to.st_ino)
	    use_tmp_file = TRUE;
    }
#endif
#ifdef WIN3264
    {
	BY_HANDLE_FILE_INFORMATION info1, info2;

	/* It's possible for the source and destination to be the same file.
	 * In that case go through a temp file name.  This makes rename("foo",
	 * "./foo") a no-op (in a complicated way). */
	if (win32_fileinfo(from, &info1) == FILEINFO_OK
		&& win32_fileinfo(to, &info2) == FILEINFO_OK
		&& info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber
		&& info1.nFileIndexHigh == info2.nFileIndexHigh
		&& info1.nFileIndexLow == info2.nFileIndexLow)
	    use_tmp_file = TRUE;
    }
#endif

    if (use_tmp_file)
    {
	char	tempname[MAXPATHL + 1];

	/*
	 * Find a name that doesn't exist and is in the same directory.
	 * Rename "from" to "tempname" and then rename "tempname" to "to".
	 */
	if (STRLEN(from) >= MAXPATHL - 5)
	    return -1;
	STRCPY(tempname, from);
	for (n = 123; n < 99999; ++n)
	{
	    sprintf((char *)gettail((char_u *)tempname), "%d", n);
	    if (mch_stat(tempname, &st) < 0)
	    {
		if (mch_rename((char *)from, tempname) == 0)
		{
		    if (mch_rename(tempname, (char *)to) == 0)
			return 0;
		    /* Strange, the second step failed.  Try moving the
		     * file back and return failure. */
		    mch_rename(tempname, (char *)from);
		    return -1;
		}
		/* If it fails for one temp name it will most likely fail
		 * for any temp name, give up. */
		return -1;
	    }
	}
	return -1;
    }

    /*
     * Delete the "to" file, this is required on some systems to make the
     * mch_rename() work, on other systems it makes sure that we don't have
     * two files when the mch_rename() fails.
     */

#ifdef AMIGA
    /*
     * With MSDOS-compatible filesystems (crossdos, messydos) it is possible
     * that the name of the "to" file is the same as the "from" file, even
     * though the names are different. To avoid the chance of accidentally
     * deleting the "from" file (horror!) we lock it during the remove.
     *
     * When used for making a backup before writing the file: This should not
     * happen with ":w", because startscript() should detect this problem and
     * set buf->b_shortname, causing modname() to return a correct ".bak" file
     * name.  This problem does exist with ":w filename", but then the
     * original file will be somewhere else so the backup isn't really
     * important. If autoscripting is off the rename may fail.
     */
    flock = Lock((UBYTE *)from, (long)ACCESS_READ);
#endif
    mch_remove(to);
#ifdef AMIGA
    if (flock)
	UnLock(flock);
#endif

    /*
     * First try a normal rename, return if it works.
     */
    if (mch_rename((char *)from, (char *)to) == 0)
	return 0;

    /*
     * Rename() failed, try copying the file.
     */
    perm = mch_getperm(from);
#ifdef HAVE_ACL
    /* For systems that support ACL: get the ACL from the original file. */
    acl = mch_get_acl(from);
#endif
    fd_in = mch_open((char *)from, O_RDONLY|O_EXTRA, 0);
    if (fd_in == -1)
    {
#ifdef HAVE_ACL
	mch_free_acl(acl);
#endif
	return -1;
    }

    /* Create the new file with same permissions as the original. */
    fd_out = mch_open((char *)to,
		       O_CREAT|O_EXCL|O_WRONLY|O_EXTRA|O_NOFOLLOW, (int)perm);
    if (fd_out == -1)
    {
	close(fd_in);
#ifdef HAVE_ACL
	mch_free_acl(acl);
#endif
	return -1;
    }

    buffer = (char *)alloc(BUFSIZE);
    if (buffer == NULL)
    {
	close(fd_out);
	close(fd_in);
#ifdef HAVE_ACL
	mch_free_acl(acl);
#endif
	return -1;
    }

    while ((n = read_eintr(fd_in, buffer, BUFSIZE)) > 0)
	if (write_eintr(fd_out, buffer, n) != n)
	{
	    errmsg = _("E208: Error writing to \"%s\"");
	    break;
	}

    vim_free(buffer);
    close(fd_in);
    if (close(fd_out) < 0)
	errmsg = _("E209: Error closing \"%s\"");
    if (n < 0)
    {
	errmsg = _("E210: Error reading \"%s\"");
	to = from;
    }
#ifndef UNIX	    /* for Unix mch_open() already set the permission */
    mch_setperm(to, perm);
#endif
#ifdef HAVE_ACL
    mch_set_acl(to, acl);
    mch_free_acl(acl);
#endif
#if defined(HAVE_SELINUX) || defined(HAVE_SMACK)
    mch_copy_sec(from, to);
#endif
    if (errmsg != NULL)
    {
	EMSG2(errmsg, to);
	return -1;
    }
    mch_remove(from);
    return 0;
}

Project: vim
File: fileio.c

find_end_event(
    char_u  *arg,
    int	    have_group)	    /* TRUE when group name was found */
{
    char_u  *pat;
    char_u  *p;

    if (*arg == '*')
    {
	if (arg[1] && !VIM_ISWHITE(arg[1]))
	{
	    EMSG2(_("E215: Illegal character after *: %s"), arg);
	    return NULL;
	}
	pat = arg + 1;
    }
    else
    {
	for (pat = arg; *pat && *pat != '|' && !VIM_ISWHITE(*pat); pat = p)
	{
	    if ((int)event_name2nr(pat, &p) >= (int)NUM_EVENTS)
	    {
		if (have_group)
		    EMSG2(_("E216: No such event: %s"), pat);
		else
		    EMSG2(_("E216: No such group or event: %s"), pat);
		return NULL;
	    }
	}
    }
    return pat;
}

Project: vim
File: fileio.c

readfile_charconvert(
    char_u	*fname,		/* name of input file */
    char_u	*fenc,		/* converted from */
    int		*fdp)		/* in/out: file descriptor of file */
{
    char_u	*tmpname;
    char_u	*errmsg = NULL;

    tmpname = vim_tempname('r', FALSE);
    if (tmpname == NULL)
	errmsg = (char_u *)_("Can't find temp file for conversion");
    else
    {
	close(*fdp);		/* close the input file, ignore errors */
	*fdp = -1;
	if (eval_charconvert(fenc, enc_utf8 ? (char_u *)"utf-8" : p_enc,
						      fname, tmpname) == FAIL)
	    errmsg = (char_u *)_("Conversion with 'charconvert' failed");
	if (errmsg == NULL && (*fdp = mch_open((char *)tmpname,
						  O_RDONLY | O_EXTRA, 0)) < 0)
	    errmsg = (char_u *)_("can't read output of 'charconvert'");
    }

    if (errmsg != NULL)
    {
	/* Don't use emsg(), it breaks mappings, the retry with
	 * another type of conversion might still work. */
	MSG(errmsg);
	if (tmpname != NULL)
	{
	    mch_remove(tmpname);	/* delete converted file */
	    vim_free(tmpname);
	    tmpname = NULL;
	}
    }

    /* If the input file is closed, open it (caller should check for error). */
    if (*fdp < 0)
	*fdp = mch_open((char *)fname, O_RDONLY | O_EXTRA, 0);

    return tmpname;
}

Project: vim
File: fileio.c

aucmd_restbuf(
    aco_save_T	*aco)		/* structure holding saved values */
{
    int dummy;

    if (aco->use_aucmd_win)
    {
	--curbuf->b_nwindows;
	/* Find "aucmd_win", it can't be closed, but it may be in another tab
	 * page. Do not trigger autocommands here. */
	block_autocmds();
	if (curwin != aucmd_win)
	{
	    tabpage_T	*tp;
	    win_T	*wp;

	    FOR_ALL_TAB_WINDOWS(tp, wp)
	    {
		if (wp == aucmd_win)
		{
		    if (tp != curtab)
			goto_tabpage_tp(tp, TRUE, TRUE);
		    win_goto(aucmd_win);
		    goto win_found;
		}
	    }
	}
win_found:

	/* Remove the window and frame from the tree of frames. */
	(void)winframe_remove(curwin, &dummy, NULL);
	win_remove(curwin, NULL);
	aucmd_win_used = FALSE;
	last_status(FALSE);	    /* may need to remove last status line */

	if (!valid_tabpage_win(curtab))
	    /* no valid window in current tabpage */
	    close_tabpage(curtab);

	restore_snapshot(SNAP_AUCMD_IDX, FALSE);
	(void)win_comp_pos();   /* recompute window positions */
	unblock_autocmds();

	if (win_valid(aco->save_curwin))
	    curwin = aco->save_curwin;
	else
	    /* Hmm, original window disappeared.  Just use the first one. */
	    curwin = firstwin;
#ifdef FEAT_EVAL
	vars_clear(&aucmd_win->w_vars->dv_hashtab);  /* free all w: variables */
	hash_init(&aucmd_win->w_vars->dv_hashtab);   /* re-use the hashtab */
#endif
	curbuf = curwin->w_buffer;

	vim_free(globaldir);
	globaldir = aco->globaldir;

	/* the buffer contents may have changed */
	check_cursor();
	if (curwin->w_topline > curbuf->b_ml.ml_line_count)
	{
	    curwin->w_topline = curbuf->b_ml.ml_line_count;
#ifdef FEAT_DIFF
	    curwin->w_topfill = 0;
#endif
	}
#if defined(FEAT_GUI)
	/* Hide the scrollbars from the aucmd_win and update. */
	gui_mch_enable_scrollbar(&aucmd_win->w_scrollbars[SBAR_LEFT], FALSE);
	gui_mch_enable_scrollbar(&aucmd_win->w_scrollbars[SBAR_RIGHT], FALSE);
	gui_may_update_scrollbars();
#endif
    }
    else
    {
	/* restore curwin */
	if (win_valid(aco->save_curwin))
	{
	    /* Restore the buffer which was previously edited by curwin, if
	     * it was changed, we are still the same window and the buffer is
	     * valid. */
	    if (curwin == aco->new_curwin
		    && curbuf != aco->new_curbuf.br_buf
		    && bufref_valid(&aco->new_curbuf)
		    && aco->new_curbuf.br_buf->b_ml.ml_mfp != NULL)
	    {
# if defined(FEAT_SYN_HL) || defined(FEAT_SPELL)
		if (curwin->w_s == &curbuf->b_s)
		    curwin->w_s = &aco->new_curbuf.br_buf->b_s;
# endif
		--curbuf->b_nwindows;
		curbuf = aco->new_curbuf.br_buf;
		curwin->w_buffer = curbuf;
		++curbuf->b_nwindows;
	    }

	    curwin = aco->save_curwin;
	    curbuf = curwin->w_buffer;
	    /* In case the autocommand move the cursor to a position that that
	     * not exist in curbuf. */
	    check_cursor();
	}
    }
}

Project: vim
File: fileio.c

has_cursorhold(void)
{
    return (first_autopat[(int)(get_real_state() == NORMAL_BUSY
			    ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI)] != NULL);
}