write_viminfo(char_u *file, int forceit)
{
    char_u	*fname;
    FILE	*fp_in = NULL;	/* input viminfo file, if any */
    FILE	*fp_out = NULL;	/* output viminfo file */
    char_u	*tempname = NULL;	/* name of temp viminfo file */
    stat_T	st_new;		/* mch_stat() of potential new file */
#if defined(UNIX) || defined(VMS)
    mode_t	umask_save;
#endif
#ifdef UNIX
    int		shortname = FALSE;	/* use 8.3 file name */
    stat_T	st_old;		/* mch_stat() of existing viminfo file */
#endif
#ifdef WIN3264
    int		hidden = FALSE;
#endif

    if (no_viminfo())
	return;

    fname = viminfo_filename(file);	/* may set to default if NULL */
    if (fname == NULL)
	return;

    fp_in = mch_fopen((char *)fname, READBIN);
    if (fp_in == NULL)
    {
	int fd;

	/* if it does exist, but we can't read it, don't try writing */
	if (mch_stat((char *)fname, &st_new) == 0)
	    goto end;

	/* Create the new .viminfo non-accessible for others, because it may
	 * contain text from non-accessible documents. It is up to the user to
	 * widen access (e.g. to a group). This may also fail if there is a
	 * race condition, then just give up. */
	fd = mch_open((char *)fname,
			    O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600);
	if (fd < 0)
	    goto end;
	fp_out = fdopen(fd, WRITEBIN);
    }
    else
    {
	/*
	 * There is an existing viminfo file.  Create a temporary file to
	 * write the new viminfo into, in the same directory as the
	 * existing viminfo file, which will be renamed once all writing is
	 * successful.
	 */
#ifdef UNIX
	/*
	 * For Unix we check the owner of the file.  It's not very nice to
	 * overwrite a user's viminfo file after a "su root", with a
	 * viminfo file that the user can't read.
	 */
	st_old.st_dev = (dev_t)0;
	st_old.st_ino = 0;
	st_old.st_mode = 0600;
	if (mch_stat((char *)fname, &st_old) == 0
		&& getuid() != ROOT_UID
		&& !(st_old.st_uid == getuid()
			? (st_old.st_mode & 0200)
			: (st_old.st_gid == getgid()
				? (st_old.st_mode & 0020)
				: (st_old.st_mode & 0002))))
	{
	    int	tt = msg_didany;

	    /* avoid a wait_return for this message, it's annoying */
	    semsg(_("E137: Viminfo file is not writable: %s"), fname);
	    msg_didany = tt;
	    fclose(fp_in);
	    goto end;
	}
#endif
#ifdef WIN3264
	/* Get the file attributes of the existing viminfo file. */
	hidden = mch_ishidden(fname);
#endif

	/*
	 * Make tempname, find one that does not exist yet.
	 * Beware of a race condition: If someone logs out and all Vim
	 * instances exit at the same time a temp file might be created between
	 * stat() and open().  Use mch_open() with O_EXCL to avoid that.
	 * May try twice: Once normal and once with shortname set, just in
	 * case somebody puts his viminfo file in an 8.3 filesystem.
	 */
	for (;;)
	{
	    int		next_char = 'z';
	    char_u	*wp;

	    tempname = buf_modname(
#ifdef UNIX
				    shortname,
#else
				    FALSE,
#endif
				    fname,
#ifdef VMS
				    (char_u *)"-tmp",
#else
				    (char_u *)".tmp",
#endif
				    FALSE);
	    if (tempname == NULL)		/* out of memory */
		break;

	    /*
	     * Try a series of names.  Change one character, just before
	     * the extension.  This should also work for an 8.3
	     * file name, when after adding the extension it still is
	     * the same file as the original.
	     */
	    wp = tempname + STRLEN(tempname) - 5;
	    if (wp < gettail(tempname))	    /* empty file name? */
		wp = gettail(tempname);
	    for (;;)
	    {
		/*
		 * Check if tempfile already exists.  Never overwrite an
		 * existing file!
		 */
		if (mch_stat((char *)tempname, &st_new) == 0)
		{
#ifdef UNIX
		    /*
		     * Check if tempfile is same as original file.  May happen
		     * when modname() gave the same file back.  E.g.  silly
		     * link, or file name-length reached.  Try again with
		     * shortname set.
		     */
		    if (!shortname && st_new.st_dev == st_old.st_dev
						&& st_new.st_ino == st_old.st_ino)
		    {
			VIM_CLEAR(tempname);
			shortname = TRUE;
			break;
		    }
#endif
		}
		else
		{
		    /* Try creating the file exclusively.  This may fail if
		     * another Vim tries to do it at the same time. */
#ifdef VMS
		    /* fdopen() fails for some reason */
		    umask_save = umask(077);
		    fp_out = mch_fopen((char *)tempname, WRITEBIN);
		    (void)umask(umask_save);
#else
		    int	fd;

		    /* Use mch_open() to be able to use O_NOFOLLOW and set file
		     * protection:
		     * Unix: same as original file, but strip s-bit.  Reset
		     * umask to avoid it getting in the way.
		     * Others: r&w for user only. */
# ifdef UNIX
		    umask_save = umask(0);
		    fd = mch_open((char *)tempname,
			    O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW,
					(int)((st_old.st_mode & 0777) | 0600));
		    (void)umask(umask_save);
# else
		    fd = mch_open((char *)tempname,
			     O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600);
# endif
		    if (fd < 0)
		    {
			fp_out = NULL;
# ifdef EEXIST
			/* Avoid trying lots of names while the problem is lack
			 * of premission, only retry if the file already
			 * exists. */
			if (errno != EEXIST)
			    break;
# endif
		    }
		    else
			fp_out = fdopen(fd, WRITEBIN);
#endif /* VMS */
		    if (fp_out != NULL)
			break;
		}

		/* Assume file exists, try again with another name. */
		if (next_char == 'a' - 1)
		{
		    /* They all exist?  Must be something wrong! Don't write
		     * the viminfo file then. */
		    semsg(_("E929: Too many viminfo temp files, like %s!"),
								     tempname);
		    break;
		}
		*wp = next_char;
		--next_char;
	    }

	    if (tempname != NULL)
		break;
	    /* continue if shortname was set */
	}

#if defined(UNIX) && defined(HAVE_FCHOWN)
	if (tempname != NULL && fp_out != NULL)
	{
		stat_T	tmp_st;

	    /*
	     * Make sure the original owner can read/write the tempfile and
	     * otherwise preserve permissions, making sure the group matches.
	     */
	    if (mch_stat((char *)tempname, &tmp_st) >= 0)
	    {
		if (st_old.st_uid != tmp_st.st_uid)
		    /* Changing the owner might fail, in which case the
		     * file will now owned by the current user, oh well. */
		    vim_ignored = fchown(fileno(fp_out), st_old.st_uid, -1);
		if (st_old.st_gid != tmp_st.st_gid
			&& fchown(fileno(fp_out), -1, st_old.st_gid) == -1)
		    /* can't set the group to what it should be, remove
		     * group permissions */
		    (void)mch_setperm(tempname, 0600);
	    }
	    else
		/* can't stat the file, set conservative permissions */
		(void)mch_setperm(tempname, 0600);
	}
#endif
    }

    /*
     * Check if the new viminfo file can be written to.
     */
    if (fp_out == NULL)
    {
	semsg(_("E138: Can't write viminfo file %s!"),
		       (fp_in == NULL || tempname == NULL) ? fname : tempname);
	if (fp_in != NULL)
	    fclose(fp_in);
	goto end;
    }

    if (p_verbose > 0)
    {
	verbose_enter();
	smsg(_("Writing viminfo file \"%s\""), fname);
	verbose_leave();
    }

    viminfo_errcnt = 0;
    do_viminfo(fp_in, fp_out, forceit ? 0 : (VIF_WANT_INFO | VIF_WANT_MARKS));

    if (fclose(fp_out) == EOF)
	++viminfo_errcnt;

    if (fp_in != NULL)
    {
	fclose(fp_in);

	/* In case of an error keep the original viminfo file.  Otherwise
	 * rename the newly written file.  Give an error if that fails. */
	if (viminfo_errcnt == 0)
	{
	    if (vim_rename(tempname, fname) == -1)
	    {
		++viminfo_errcnt;
		semsg(_("E886: Can't rename viminfo file to %s!"), fname);
	    }
# ifdef WIN3264
	    /* If the viminfo file was hidden then also hide the new file. */
	    else if (hidden)
		mch_hide(fname);
# endif
	}
	if (viminfo_errcnt > 0)
	    mch_remove(tempname);
    }

end:
    vim_free(fname);
    vim_free(tempname);
}