/* vi:set ts=4 sw=4:
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 */

/*
 * fileio.c: read from and write to a file
 */

#if defined MSDOS  ||  defined WIN32
# include <io.h>		/* for lseek(), must be before vim.h */
#endif

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "option.h"
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif

#ifdef LATTICE
# include <proto/dos.h>		/* for Lock() and UnLock() */
#endif

#define BUFSIZE		8192			/* size of normal write buffer */
#define SBUFSIZE	256				/* size of emergency write buffer */

#ifdef VIMINFO
static void check_marks_read __ARGS((void));
#endif
static void msg_add_fname __ARGS((BUF *, char_u *));
static int msg_add_textmode __ARGS((int));
static void msg_add_lines __ARGS((int, long, long));
static void msg_add_eol __ARGS((void));
static int  write_buf __ARGS((int, char_u *, int));

static linenr_t	write_no_eol_lnum = 0; 	/* non-zero lnum when last line of
										   next binary write should not have
										   an end-of-line */

	void
filemess(buf, name, s)
	BUF			*buf;
	char_u		*name;
	char_u		*s;
{
	msg_add_fname(buf, name);		/* put file name in IObuff with quotes */
	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_start();
	msg_outtrans(IObuff);
	stop_highlight();
	msg_clr_eos();
	flushbuf();
}

/*
 * Read lines from file 'fname' into the buffer after line 'from'.
 *
 * 1. We allocate blocks with lalloc, as big as possible.
 * 2. Each block is filled with characters from the file with a single read().
 * 3. The lines are inserted in the buffer with ml_append().
 *
 * (caller must check that fname != NULL)
 *
 * lines_to_skip is the number of lines that must be skipped
 * lines_to_read is the number of lines that are appended
 * When not recovering lines_to_skip is 0 and lines_to_read MAXLNUM.
 *
 * return FAIL for failure, OK otherwise
 */
	int
readfile(fname, sfname, from, newfile, lines_to_skip, lines_to_read, filtering)
	char_u		   *fname;
	char_u		   *sfname;
	linenr_t		from;
	int				newfile;
	linenr_t		lines_to_skip;
	linenr_t		lines_to_read;
	int				filtering;
{
	int 				fd;
	register char_u 	c;
	register linenr_t	lnum = from;
	register char_u 	*ptr = NULL;			/* pointer into read buffer */
	register char_u		*buffer = NULL;			/* read buffer */
	char_u				*new_buffer = NULL;		/* init to shut up gcc */
	char_u				*line_start = NULL;		/* init to shut up gcc */
	colnr_t				len;
	register long		size;
	register char_u		*p;
	long				filesize = 0;
	int					split = 0;				/* number of split lines */
#define UNKNOWN		0x0fffffff					/* file size is unknown */
	linenr_t			linecnt;
	int 				error = FALSE;			/* errors encountered */
	int					tx_error = FALSE;		/* textmode, but no CR */
	long				linerest = 0;			/* remaining chars in line */
	int					firstpart = TRUE;		/* reading first part */
#ifdef UNIX
	int					perm;
#endif
	int					textmode;				/* accept CR-LF linebreak */
	struct stat			st;
	int					file_readonly;
	linenr_t			skip_count = lines_to_skip;
	linenr_t			read_count = lines_to_read;
	int					msg_save = msg_scroll;
	linenr_t			read_no_eol_lnum = 0; 	/* non-zero lnum when last
												   line of last read was
												   missing the eol */


#ifdef AUTOCMD
	write_no_eol_lnum = 0;		/* in case it was set by the previous read */
#endif

	/*
	 * If there is no file name yet, use the one for the read file.
	 * b_notedited is set to reflect this.
	 * Don't do this for a read from a filter.
	 * Only do this when 'cpoptions' contains the 'f' flag.
	 */
	if (curbuf->b_filename == NULL && !filtering &&
										vim_strchr(p_cpo, CPO_FNAMER) != NULL)
	{
		if (setfname(fname, sfname, FALSE) == OK)
			curbuf->b_notedited = TRUE;
	}

	if (shortmess(SHM_OVER) || curbuf->b_help)
		msg_scroll = FALSE;		/* overwrite previous file message */
	else
		msg_scroll = TRUE;		/* don't overwrite previous file message */
	if (sfname == NULL)
		sfname = fname;
	/*
	 * For Unix: Use the short filename 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.
	 */
#if defined(UNIX) || defined(__EMX__)
	if (!did_cd)
		fname = sfname;
#endif

#ifdef UNIX
	/*
	 * On Unix it is possible to read a directory, so we have to
	 * check for it before the open().
	 */
	perm = getperm(fname);
# ifdef _POSIX_SOURCE
	if (perm >= 0 && !S_ISREG(perm)	 				/* not a regular file ... */
#  ifdef S_ISFIFO
				  && !S_ISFIFO(perm)				/* ... or fifo or socket */
#  endif
										   )
# else
	if (perm >= 0 && (perm & S_IFMT) != S_IFREG		/* not a regular file ... */
#  ifdef S_IFIFO
				  && (perm & S_IFMT) != S_IFIFO		/* ... or fifo ... */
#  endif
#  ifdef S_IFSOCK
				  && (perm & S_IFMT) != S_IFSOCK	/* ... or socket */
#  endif
											)
# endif
	{
# ifdef _POSIX_SOURCE
		if (S_ISDIR(perm))
# else
		if ((perm & S_IFMT) == S_IFDIR)
# endif
			filemess(curbuf, fname, (char_u *)"is a directory");
		else
			filemess(curbuf, fname, (char_u *)"is not a file");
		msg_scroll = msg_save;
		return FAIL;
	}
#endif

	/*
	 * When opening a new file we take the readonly flag from the file.
	 * Default is r/w, can be set to r/o below.
	 * Don't reset it when in readonly mode
	 */
	if (newfile && !readonlymode)			/* default: set file not readonly */
		curbuf->b_p_ro = FALSE;

	if (newfile)
	{
		if (stat((char *)fname, &st) >= 0)	/* remember time of file */
		{
			curbuf->b_mtime = st.st_mtime;
			curbuf->b_mtime_read = st.st_mtime;
#ifdef UNIX
			/*
			 * Set the protection bits of the swap file equal to the original
			 * file. This makes it possible for others to read the name of the
			 * original file from the swapfile.
			 */
			if (curbuf->b_ml.ml_mfp->mf_fname != NULL)
				(void)setperm(curbuf->b_ml.ml_mfp->mf_fname,
												  (st.st_mode & 0777) | 0600);
#endif
		}
		else
		{
			curbuf->b_mtime = 0;
			curbuf->b_mtime_read = 0;
		}
	}

/*
 * for UNIX: check readonly with perm and access()
 * for MSDOS and Amiga: check readonly by trying to open the file for writing
 */
	file_readonly = FALSE;
#if defined(UNIX) || defined(DJGPP) || defined(__EMX__)
	if (
# ifdef UNIX
		!(perm & 0222) ||
# endif
							access((char *)fname, W_OK))
		file_readonly = TRUE;
	fd = open((char *)fname, O_RDONLY | O_EXTRA);
#else
	if (!newfile || readonlymode || (fd =
								   open((char *)fname, O_RDWR | O_EXTRA)) < 0)
	{
		file_readonly = TRUE;
		fd = open((char *)fname, O_RDONLY | O_EXTRA);	/* try to open ro */
	}
#endif

	if (fd < 0)					 /* cannot open at all */
	{
#ifndef UNIX
		int		isdir_f;
#endif
		msg_scroll = msg_save;
#ifndef UNIX
	/*
	 * On MSDOS and Amiga we can't open a directory, check here.
	 */
		isdir_f = (mch_isdir(fname));
		/* replace with short name now, for the messages */
		if (!did_cd)
			fname = sfname;
		if (isdir_f)
			filemess(curbuf, fname, (char_u *)"is a directory");
		else
#endif
			if (newfile)
			{
#ifdef UNIX
				if (perm < 0)
#endif
				{
					filemess(curbuf, fname, (char_u *)"[New File]");
#ifdef AUTOCMD
					apply_autocmds(EVENT_BUFNEWFILE, fname, fname, FALSE);
#endif
					return OK;		/* a new file is not an error */
				}
#ifdef UNIX
				else
					filemess(curbuf, fname, (char_u *)"[Permission Denied]");
#endif
			}

		return FAIL;
	}

	/*
	 * Only set the 'ro' flag for readonly files the first time they are
	 * loaded.
	 * Help files always get readonly mode
	 */
	if ((newfile && file_readonly) || curbuf->b_help)
		curbuf->b_p_ro = TRUE;

	if (newfile)
		curbuf->b_p_eol = TRUE;

#ifndef UNIX
	/* replace with short name now, for the messages */
	if (!did_cd)
		fname = sfname;
#endif
	++no_wait_return;							/* don't wait for return yet */

	/*
	 * Set '[ mark to the line above where the lines go (line 1 if zero).
	 */
	curbuf->b_op_start.lnum = ((from == 0) ? 1 : from);
	curbuf->b_op_start.col = 0;

#ifdef AUTOCMD
	{
		int	m = msg_scroll;
		int n = msg_scrolled;
		BUF	*old_curbuf = curbuf;

		/*
		 * The file must be closed again, the autocommands may want to change
		 * the file before reading it.
		 */
		close(fd);			/* ignore errors */

		/*
		 * The output from the autocommands should not overwrite anything and
		 * should not be overwritten: Set msg_scroll, restore its value if no
		 * output was done.
		 */
		msg_scroll = TRUE;
		if (filtering)
			apply_autocmds(EVENT_FILTERREADPRE, NULL, fname, FALSE);
		else if (newfile)
			apply_autocmds(EVENT_BUFREADPRE, NULL, fname, FALSE);
		else
			apply_autocmds(EVENT_FILEREADPRE, fname, fname, FALSE);
		if (msg_scrolled == n)
			msg_scroll = m;

		/*
		 * Don't allow the autocommands to change the current buffer.
		 * Try to re-open the file.
		 */
		if (curbuf != old_curbuf ||
						   (fd = open((char *)fname, O_RDONLY | O_EXTRA)) < 0)
		{
			--no_wait_return;
			msg_scroll = msg_save;
			if (fd < 0)
				EMSG("*ReadPre autocommands made the file unreadable");
			else
				EMSG("*ReadPre autocommands must not change current buffer");
			return FAIL;
		}
	}
#endif

	if (!recoverymode && !filtering)
		filemess(curbuf, fname, (char_u *)"");	/* show that we are busy */

	msg_scroll = FALSE;							/* overwrite the file message */

	/*
	 * Set textmode and linecnt now, before the "retry" caused by 'textauto'
	 * and after the autocommands, which may change them.
	 */
	textmode = curbuf->b_p_tx;
	linecnt = curbuf->b_ml.ml_line_count;

retry:
	while (!error && !got_int)
	{
		/*
		 * We allocate as much space for the file as we can get, plus
		 * space for the old line plus room for one terminating NUL.
		 * The amount is limited by the fact that read() only can read
		 * upto max_unsigned characters (and other things).
		 */
#if SIZEOF_INT <= 2
		if (linerest >= 0x7ff0)
		{
			++split;
			*ptr = NL;				/* split line by inserting a NL */
			size = 1;
		}
		else
#endif
		{
#if SIZEOF_INT > 2
			size = 0x10000L;				/* use buffer >= 64K */
#else
			size = 0x7ff0L - linerest;		/* limit buffer to 32K */
#endif

			for ( ; size >= 10; size >>= 1)
			{
				if ((new_buffer = lalloc((long_u)(size + linerest + 1), FALSE)) != NULL)
					break;
			}
			if (new_buffer == NULL)
			{
				do_outofmem_msg();
				error = TRUE;
				break;
			}
			if (linerest)		/* copy characters from the previous buffer */
				vim_memmove(new_buffer, ptr - linerest, (size_t)linerest);
			vim_free(buffer);
			buffer = new_buffer;
			ptr = buffer + linerest;
			line_start = buffer;
			
			if ((size = read(fd, (char *)ptr, (size_t)size)) <= 0)
			{
				if (size < 0)				/* read error */
					error = TRUE;
				break;
			}
			filesize += size;				/* count the number of characters */

			/*
			 * when reading the first part of a file: guess EOL type
			 */
			if (firstpart && p_ta)
			{
				for (p = ptr; p < ptr + size; ++p)
					if (*p == NL)
					{
						if (p > ptr && p[-1] == CR)	/* found CR-NL */
							textmode = TRUE;
						else						/* found a single NL */
							textmode = FALSE;
							/* if editing a new file: may set p_tx */
						if (newfile)
							curbuf->b_p_tx = textmode;
						break;
					}
			}
		}

		firstpart = FALSE;

		/*
		 * This loop is executed once for every character read.
		 * Keep it fast!
		 */
		--ptr;
		while (++ptr, --size >= 0)
		{
			if ((c = *ptr) != NUL && c != NL)	/* catch most common case */
				continue;
			if (c == NUL)
				*ptr = NL;		/* NULs are replaced by newlines! */
			else
			{
				if (skip_count == 0)
				{
					*ptr = NUL;		/* end of line */
					len = ptr - line_start + 1;
					if (textmode)
					{
						if (ptr[-1] == CR)	/* remove CR */
						{
							ptr[-1] = NUL;
							--len;
						}
						/*
						 * Reading in textmode, but no CR-LF found!
						 * When 'textauto' set, delete all the lines read so
						 * far and start all over again.
						 * Otherwise give an error message later.
						 */
						else if (!tx_error)
						{
							if (p_ta && lseek(fd, 0L, SEEK_SET) == 0)
							{
								while (lnum > from)
									ml_delete(lnum--, FALSE);
								textmode = FALSE;
								if (newfile)
									curbuf->b_p_tx = FALSE;
								linerest = 0;
								filesize = 0;
								skip_count = lines_to_skip;
								read_count = lines_to_read;
								goto retry;
							}
							else
								tx_error = TRUE;
						}
					}
					if (ml_append(lnum, line_start, len, newfile) == FAIL)
					{
						error = TRUE;
						break;
					}
					++lnum;
					if (--read_count == 0)
					{
						error = TRUE;		/* break loop */
						line_start = ptr;	/* nothing left to write */
						break;
					}
				}
				else
					--skip_count;
				line_start = ptr + 1;
			}
		}
		linerest = ptr - line_start;
		mch_breakcheck();
	}

	/* not an error, max. number of lines reached */
	if (error && read_count == 0)
		error = FALSE;

	/*
	 * If we get EOF in the middle of a line, note the fact and
	 * complete the line ourselves.
	 * In textmode ignore a trailing CTRL-Z, unless 'binary' set.
	 */
	if (!error && !got_int && linerest != 0 &&
			!(!curbuf->b_p_bin && textmode &&
					*line_start == Ctrl('Z') && ptr == line_start + 1))
	{
		if (newfile)				/* remember for when writing */
			curbuf->b_p_eol = FALSE;
		*ptr = NUL;
		if (ml_append(lnum, line_start,
						(colnr_t)(ptr - line_start + 1), newfile) == FAIL)
			error = TRUE;
		else
			read_no_eol_lnum = ++lnum;
	}
	if (lnum != from && !newfile)	/* added at least one line */
		CHANGED;

	close(fd);						/* errors are ignored */
	vim_free(buffer);

	--no_wait_return;				/* may wait for return now */

	/* in recovery mode everything but autocommands are skipped */
	if (!recoverymode)
	{

		/* need to delete the last line, which comes from the empty buffer */
		if (newfile && !(curbuf->b_ml.ml_flags & ML_EMPTY))
		{
			ml_delete(curbuf->b_ml.ml_line_count, FALSE);
			--linecnt;
		}
		linecnt = curbuf->b_ml.ml_line_count - linecnt;
		if (filesize == 0)
			linecnt = 0;
		if (!newfile)
			mark_adjust(from + 1, MAXLNUM, (long)linecnt, 0L);

		if (got_int)
		{
			filemess(curbuf, fname, e_interr);
			msg_scroll = msg_save;
#ifdef VIMINFO
			check_marks_read();
#endif /* VIMINFO */
			return OK;			/* an interrupt isn't really an error */
		}

		if (!filtering)
		{
			msg_add_fname(curbuf, fname);	/* fname in IObuff with quotes */
			c = FALSE;

#ifdef UNIX
# ifdef S_ISFIFO
			if (S_ISFIFO(perm))						/* fifo or socket */
			{
				STRCAT(IObuff, "[fifo/socket]");
				c = TRUE;
			}
# else
#  ifdef S_IFIFO
			if ((perm & S_IFMT) == S_IFIFO)			/* fifo */
			{
				STRCAT(IObuff, "[fifo]");
				c = TRUE;
			}
#  endif
#  ifdef S_IFSOCK
			if ((perm & S_IFMT) == S_IFSOCK)		/* or socket */
			{
				STRCAT(IObuff, "[socket]");
				c = TRUE;
			}
#  endif
# endif
#endif
			if (curbuf->b_p_ro)
			{
				STRCAT(IObuff, shortmess(SHM_RO) ? "[RO]" : "[readonly]");
				c = TRUE;
			}
			if (read_no_eol_lnum)
			{
				msg_add_eol();
				c = TRUE;
			}
			if (tx_error)
			{
				STRCAT(IObuff, "[CR missing]");
				c = TRUE;
			}
			if (split)
			{
				STRCAT(IObuff, "[long lines split]");
				c = TRUE;
			}
			if (error)
			{
				STRCAT(IObuff, "[READ ERRORS]");
				c = TRUE;
			}
			if (msg_add_textmode(textmode))
				c = TRUE;
			msg_add_lines(c, (long)linecnt, filesize);
			msg_trunc(IObuff);
		}

		if (error && newfile)	/* with errors we should not write the file */
			curbuf->b_p_ro = TRUE;

		u_clearline();		/* cannot use "U" command after adding lines */

		if (from < curbuf->b_ml.ml_line_count)
		{
			curwin->w_cursor.lnum = from + 1;	/* cursor at first new line */
			beginline(TRUE);					/* on first non-blank */
		}

		/*
		 * Set '[ and '] marks to the newly read lines.
		 */
		curbuf->b_op_start.lnum = from + 1;
		curbuf->b_op_start.col = 0;
		curbuf->b_op_end.lnum = from + linecnt;
		curbuf->b_op_end.col = 0;
	}
	msg_scroll = msg_save;

#ifdef VIMINFO
	/*
	 * Get the marks before executing autocommands, so they can be used there.
	 */
	check_marks_read();
#endif /* VIMINFO */

#ifdef AUTOCMD
	{
		int	m = msg_scroll;
		int n = msg_scrolled;

		/*
		 * Trick: We remember if the last line of the read didn't have
		 * an eol for when writing it again.  This is required for
		 * ":autocmd FileReadPost *.gz set bin|'[,']!gunzip" to work.
		 */
		write_no_eol_lnum = read_no_eol_lnum;

		/*
		 * The output from the autocommands should not overwrite anything and
		 * should not be overwritten: Set msg_scroll, restore its value if no
		 * output was done.
		 */
		msg_scroll = TRUE;
		if (filtering)
			apply_autocmds(EVENT_FILTERREADPOST, NULL, fname, FALSE);
		else if (newfile)
			apply_autocmds(EVENT_BUFREADPOST, NULL, fname, FALSE);
		else
			apply_autocmds(EVENT_FILEREADPOST, fname, fname, FALSE);
		if (msg_scrolled == n)
			msg_scroll = m;
	}
#endif

	if (recoverymode && error)
		return FAIL;
	return OK;
}

#ifdef VIMINFO
	static void
check_marks_read()
{
	if (!curbuf->b_marks_read && get_viminfo_parameter('\'') > 0)
	{
		read_viminfo(NULL, FALSE, TRUE, FALSE);
		curbuf->b_marks_read = TRUE;
	}
}
#endif /* VIMINFO */

/*
 * 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 (jw).
 * In case of an error everything possible is done to restore the original file.
 * But when forceit is TRUE, we risk loosing it.
 * When reset_changed is TRUE 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, fname, sfname, start, end, append, forceit,
													  reset_changed, filtering)
	BUF				*buf;
	char_u			*fname;
	char_u			*sfname;
	linenr_t		start, end;
	int				append;
	int				forceit;
	int				reset_changed;
	int				filtering;
{
	int 				fd;
	char_u			   *backup = NULL;
	char_u			   *ffname;
#ifdef AUTOCMD
	BUF				   *save_buf;
#endif
	register char_u	   *s;
	register char_u	   *ptr;
	register char_u		c;
	register int		len;
	register linenr_t	lnum;
	long				nchars;
	char_u				*errmsg = NULL;
	char_u				*buffer;
	char_u				smallbuf[SBUFSIZE];
	char_u				*backup_ext;
	int					bufsize;
	long 				perm = -1;			/* 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 */
#if defined(UNIX) || defined(__EMX__XX) /*XXX fix me sometime? */
	struct stat			st_old;
	int					made_writable = FALSE;	/* 'w' bit has been set */
#endif
#ifdef AMIGA
	BPTR				flock;
#endif
											/* writing everything */
	int					whole = (start == 1 && end == buf->b_ml.ml_line_count);
#ifdef AUTOCMD
	linenr_t			old_line_count = buf->b_ml.ml_line_count;
#endif

	if (fname == NULL || *fname == NUL)		/* safety check */
		return FAIL;

	/*
	 * If there is no file name yet, use the one for the written file.
	 * b_notedited is set to reflect this (in case the write fails).
	 * Don't do this when the write is for a filter command.
	 * Only do this when 'cpoptions' contains the 'f' flag.
	 */
	if (reset_changed && whole && buf == curbuf &&
								   curbuf->b_filename == NULL && !filtering &&
										vim_strchr(p_cpo, CPO_FNAMEW) != NULL)
	{
		if (setfname(fname, sfname, FALSE) == OK)
			curbuf->b_notedited = TRUE;
	}

	if (sfname == NULL)
		sfname = fname;
	/*
	 * For Unix: Use the short filename 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
	if (!did_cd)
		fname = sfname;
#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 (buf->b_filename != NULL && fnamecmp(ffname, buf->b_filename) == 0)
		overwriting = TRUE;
	else
		overwriting = FALSE;

	/*
	 * Disallow writing from .exrc and .vimrc in current directory for
	 * security reasons.
	 */
	if (secure)
	{
		secure = 2;
		emsg(e_curdir);
		return FAIL;
	}

	if (exiting)
		settmode(0);				/* when exiting allow typahead 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 AUTOCMD
	/*
	 * Apply PRE aucocommands.
	 * Set curbuf to the buffer to be written.
	 * Careful: The autocommands may call buf_write() recursively!
	 */
	save_buf = curbuf;
	curbuf = buf;
	curwin->w_buffer = buf;
	if (append)
		apply_autocmds(EVENT_FILEAPPENDPRE, fname, fname, FALSE);
	else if (filtering)
		apply_autocmds(EVENT_FILTERWRITEPRE, NULL, fname, FALSE);
	else if (reset_changed && whole)
		apply_autocmds(EVENT_BUFWRITEPRE, fname, fname, FALSE);
	else
		apply_autocmds(EVENT_FILEWRITEPRE, fname, fname, FALSE);
	/*
	 * If the autocommands deleted or unloaded the buffer, give an error
	 * message.
	 */
	if (!buf_valid(buf) || buf->b_ml.ml_mfp == NULL)
	{
		--no_wait_return;
		msg_scroll = msg_save;
		EMSG("Autocommands deleted or unloaded buffer to be written");
		return FAIL;
	}
	/*
	 * If the autocommands didn't change the current buffer, go back to the
	 * original current buffer, if it still exists.
	 */
	if (curbuf == buf && buf_valid(save_buf))
	{
		curbuf = save_buf;
		curwin->w_buffer = save_buf;
	}
	
	/*
	 * 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)											/* writing 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("Autocommand changed number of lines in unexpected way");
				return FAIL;
			}
		}
	}
#endif

	if (shortmess(SHM_OVER))
		msg_scroll = FALSE;			/* overwrite previous file message */
	else
		msg_scroll = TRUE;			/* don't overwrite previous file message */
	if (!filtering)
		filemess(buf,
#ifndef UNIX
				did_cd ? fname : sfname,
#else
				fname,
#endif
					(char_u *)"");	/* 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 = SBUFSIZE;
	}
	else
		bufsize = BUFSIZE;

#if defined(UNIX) && !defined(ARCHIE)
		/* get information about original file (if there is one) */
	st_old.st_dev = st_old.st_ino = 0;
	if (stat((char *)fname, &st_old))
		newfile = TRUE;
	else
	{
#ifdef _POSIX_SOURCE
		if (!S_ISREG(st_old.st_mode))      		/* not a file */
#else
		if ((st_old.st_mode & S_IFMT) != S_IFREG)	/* not a file */
#endif
		{
#ifdef _POSIX_SOURCE
			if (S_ISDIR(st_old.st_mode))
#else
			if ((st_old.st_mode & S_IFMT) == S_IFDIR)
#endif
				errmsg = (char_u *)"is a directory";
			else
				errmsg = (char_u *)"is not a file";
			goto fail;
		}
		if (buf->b_mtime_read != 0 &&
						  buf->b_mtime_read != st_old.st_mtime && overwriting)
		{
			msg_scroll = TRUE;		/* don't overwrite messages here */
			(void)set_highlight('e');	/* set highlight for error messages */
			msg_highlight = TRUE;
			/* don't use emsg() here, don't want to flush the buffers */
			MSG("WARNING: The file has been changed since reading it!!!");
			if (ask_yesno((char_u *)"Do you really want to write to it",
																 TRUE) == 'n')
			{
				retval = FAIL;
				goto fail;
			}
			msg_scroll = FALSE;		/* always overwrite the file message now */
		}
		perm = st_old.st_mode;
	}
/*
 * If we are not appending, the file exists, and the 'writebackup', 'backup'
 * or 'patchmode' option is set, try to make a backup copy of the file.
 */
	if (!append && perm >= 0 && (p_wb || p_bk || *p_pm != NUL) &&
						  (fd = open((char *)fname, O_RDONLY | O_EXTRA)) >= 0)
	{
		int				bfd, buflen;
		char_u			copybuf[BUFSIZE + 1], *wp;
		int				some_error = FALSE;
		struct stat		st_new;
		char_u			*dirp;
		char_u			*rootname;
#ifndef SHORT_FNAME
		int				did_set_shortname;
#endif

		/*
		 * 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)
		{
			st_new.st_dev = st_new.st_ino = 0;
			st_new.st_gid = 0;

			/*
			 * 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;
			}


#ifndef SHORT_FNAME
			did_set_shortname = FALSE;
#endif

			/*
			 * May try twice if 'shortname' not set.
			 */
			for (;;)
			{
				/*
				 * Make backup file name.
				 */
				backup = buf_modname(
#ifdef SHORT_FNAME
										TRUE,
#else
										(buf->b_p_sn || buf->b_shortname),
#endif
														rootname, backup_ext);
				if (backup == NULL)
				{
					some_error = TRUE;			/* out of memory */
					vim_free(rootname);
					goto nobackup;
				}

				/*
				 * Check if backup file already exists.
				 */
				if (!stat((char *)backup, &st_new))
				{
					/*
					 * Check if backup file is same as original file.
					 * May happen when modname gave the same file back.
					 * E.g. silly link, or filename-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;	/* there is no backup file to delete */
#ifndef SHORT_FNAME
						/*
						 * 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;
#endif
						break;
					}

					/*
					 * 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' && !stat((char *)backup, &st_new))
							--*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 */
				vim_remove(backup);
				bfd = open((char *)backup, O_WRONLY | O_CREAT | O_EXTRA, 0666);
				if (bfd < 0)
				{
					vim_free(backup);
					backup = NULL;
				}
				else
				{
					/* set file protection same as original file, but strip
					 * s-bit */
					(void)setperm(backup, perm & 0777);

					/*
					 * 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, -1, st_old.st_gid) != 0)
#else
								chown((char *)backup, -1, st_old.st_gid) != 0)
#endif
						setperm(backup, (perm & 0707) | ((perm & 07) << 3));

					/* copy the file. */
					while ((buflen = read(fd, (char *)copybuf, BUFSIZE)) > 0)
					{
						if (write_buf(bfd, copybuf, buflen) == FAIL)
						{
							errmsg = (char_u *)"Can't write to backup file (use ! to override)";
							break;
						}
					}
					if (close(bfd) < 0 && errmsg == NULL)
						errmsg = (char_u *)"Close error for backup file (use ! to override)";
					if (buflen < 0)
						errmsg = (char_u *)"Can't read file for backup (use ! to override)";
					break;
				}
			}
		}
nobackup:
		close(fd);				/* ignore errors for closing read file */

		if (backup == NULL && errmsg == NULL)
			errmsg = (char_u *)"Cannot create backup file (use ! to override)";
		/* ignore errors when forceit is TRUE */
		if ((some_error || errmsg) && !forceit)
		{
			retval = FAIL;
			goto fail;
		}
		errmsg = NULL;
	}
	/* When using ":w!" and the file was read-only: make it writable */
	if (forceit && (st_old.st_uid == getuid()) && perm >= 0 && !(perm & 0200))
 	{
		perm |= 0200;	
		(void)setperm(fname, perm);
		made_writable = TRUE;
	}

#else /* end of UNIX, start of the rest */

/*
 * If we are not appending, the file exists, and the 'writebackup' or
 * 'backup' option is set, make a backup.
 * Do not make any backup, if "writebackup" and "backup" are 
 * both switched off. This helps when editing large files on
 * almost-full disks. (jw)
 */
	perm = getperm(fname);
	if (perm < 0)
		newfile = TRUE;
	else if (mch_isdir(fname))
	{
		errmsg = (char_u *)"is a directory";
		goto fail;
	}
	if (!append && perm >= 0 && (p_wb || p_bk || *p_pm != NUL))
	{
		char_u			*dirp;
		char_u			*p;
		char_u			*rootname;

		/*
		 * 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(
#ifdef SHORT_FNAME
										TRUE,
#else
										(buf->b_p_sn || buf->b_shortname),
#endif
														rootname, backup_ext);
				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 && getperm(backup) >= 0)
				{
					p = backup + STRLEN(backup) - 1 - STRLEN(backup_ext);
					if (p < backup)		/* empty file name ??? */
						p = backup;
					*p = 'z';
					while (*p > 'a' && 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.
				 */
#ifdef AMIGA
				/*
				 * With MSDOS-compatible filesystems (crossdos, messydos) it is
				 * possible that the name of the backup file is the same as the
				 * original file. To avoid the chance of accidently deleting the
				 * original file (horror!) we lock it during the remove.
				 * This should not happen with ":w", because startscript()
				 * should detect this problem and set buf->b_shortname,
				 * causing modname to return a correct ".bak" filename. 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 *)fname, (long)ACCESS_READ);
#endif
				vim_remove(backup);
#ifdef AMIGA
				if (flock)
					UnLock(flock);
#endif
				/*
				 * 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 *)"Can't make backup file (use ! to override)";
			goto fail;
		}
	}
#endif /* UNIX */

	/* When using ":w!" and writing to the current file, readonly makes no
	 * sense, reset it */
	if (forceit && overwriting)
		buf->b_p_ro = FALSE;

	/*
	 * 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 && !otherfile(ffname) &&
											!(exiting && backup != NULL))
		ml_preserve(buf, FALSE);

	/* 
	 * 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 = open((char *)fname, O_WRONLY | O_EXTRA | (append ?
					(forceit ? (O_APPEND | O_CREAT) : O_APPEND) :
					(O_CREAT | O_TRUNC)), 0666)) < 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 vim_remove() will fail.
		 */
		if (!errmsg)
		{
			errmsg = (char_u *)"Can't open file for writing";
			if (forceit)
			{
#ifdef UNIX
				/* we write to the file, thus it should be marked
													writable after all */
				perm |= 0200;		
				made_writable = TRUE;
				if (st_old.st_uid != getuid() || st_old.st_gid != getgid())
					perm &= 0777;
#endif /* UNIX */
				if (!append)		/* don't remove when appending */
					vim_remove(fname);
				continue;
			}
		}
/*
 * 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)
		{
#ifdef UNIX
			struct stat st;

			/*
			 * 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.
			 */
			 							/* file does not exist */
			if (stat((char *)fname, &st) < 0)
										/* put the copy in its place */
				vim_rename(backup, fname);
										/* original file does exist */
			if (stat((char *)fname, &st) >= 0)
				vim_remove(backup);	/* throw away the copy */
#else
										/* try to put the original file back */
 			vim_rename(backup, fname);
#endif
		}
 		goto fail;
 	}
	errmsg = NULL;

	if (end > buf->b_ml.ml_line_count)
		end = buf->b_ml.ml_line_count;
	len = 0;
	s = buffer;
	nchars = 0;
	if (buf->b_ml.ml_flags & ML_EMPTY)
		start = end + 1;
	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;
		while ((c = *++ptr) != NUL)
		{
			if (c == NL)
				*s = NUL;		/* replace newlines with NULs */
			else
				*s = c;
			++s;
			if (++len != bufsize)
				continue;
			if (write_buf(fd, buffer, bufsize) == FAIL)
			{
				end = 0;				/* write error: break loop */
				break;
			}
			nchars += bufsize;
			s = buffer;
			len = 0;
		}
			/* write failed or last line has no EOL: stop here */
		if (end == 0 || (lnum == end && buf->b_p_bin &&
												(lnum == write_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 (buf->b_p_tx)		/* write CR-NL */
		{
			*s = CR;
			++s;
			if (++len == bufsize)
			{
				if (write_buf(fd, buffer, bufsize) == FAIL)
				{
					end = 0;				/* write error: break loop */
					break;
				}
				nchars += bufsize;
				s = buffer;
				len = 0;
			}
		}
		*s = NL;
		++s;
		if (++len == bufsize && end)
		{
			if (write_buf(fd, buffer, bufsize) == FAIL)
			{
				end = 0;				/* write error: break loop */
				break;
			}
			nchars += bufsize;
			s = buffer;
			len = 0;
		}
	}
	if (len && end)
	{
		if (write_buf(fd, buffer, len) == FAIL)
			end = 0;				/* write error */
		nchars += len;
	}

	if (close(fd) != 0)
	{
		errmsg = (char_u *)"Close failed";
		goto fail;
	}
#ifdef UNIX
	if (made_writable)
		perm &= ~0200;			/* reset 'w' bit for security reasons */
#endif
	if (perm >= 0)
		(void)setperm(fname, perm);	/* set permissions of new file same as old file */

	if (end == 0)
	{
		errmsg = (char_u *)"write error (file system full?)";
		/*
		 * If we have a backup file, try to put it in place of the new file,
		 * because it is probably corrupt. This avoids loosing the original
		 * file when trying to make a backup when writing the file a second
		 * time.
		 * For unix this means copying the backup over the new file.
		 * For others this means renaming the backup file.
		 * If this is OK, don't give the extra warning message.
		 */
		if (backup != NULL)
		{
#ifdef UNIX
			char_u		copybuf[BUFSIZE + 1];
			int			bfd, buflen;

			if ((bfd = open((char *)backup, O_RDONLY | O_EXTRA)) >= 0)
			{
				if ((fd = open((char *)fname,
						  O_WRONLY | O_CREAT | O_TRUNC | O_EXTRA, 0666)) >= 0)
				{
					/* copy the file. */
					while ((buflen = read(bfd, (char *)copybuf, BUFSIZE)) > 0)
						if (write_buf(fd, copybuf, buflen) == FAIL)
							break;
					if (close(fd) >= 0 && buflen == 0)	/* success */
						end = 1;
				}
				close(bfd);		/* ignore errors for closing read file */
			}
#else
			if (vim_rename(backup, fname) == 0)
				end = 1;
#endif
		}
		goto fail;
	}

	lnum -= start;			/* compute number of written lines */
	--no_wait_return;		/* may wait for return now */

#ifndef UNIX
	/* use shortname now, for the messages */
	if (!did_cd)
		fname = sfname;
#endif
	if (!filtering)
	{
		msg_add_fname(buf, fname);		/* put fname in IObuff with quotes */
		c = FALSE;
		if (newfile)
		{
			STRCAT(IObuff, shortmess(SHM_NEW) ? "[New]" : "[New File]");
			c = TRUE;
		}
		if (no_eol)
		{
			msg_add_eol();
			c = TRUE;
		}
		if (msg_add_textmode(buf->b_p_tx))		/* may add [textmode] */
			c = TRUE;
		msg_add_lines(c, (long)lnum, nchars);	/* add line/char count */
		if (!shortmess(SHM_WRITE))
			STRCAT(IObuff, shortmess(SHM_WRI) ? " [w]" : " written");

		msg_trunc(IObuff);
	}

	if (reset_changed && whole)			/* when written everything */
	{
		UNCHANGED(buf);
		u_unchanged(buf);
	}

	/*
	 * If written to the current file, update the timestamp of the swap file
	 * and reset the 'notedited' flag. Also sets buf->b_mtime.
	 */
	if (!exiting && overwriting)
	{
		ml_timestamp(buf);
		buf->b_notedited = FALSE;
	}

	/*
	 * 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)
	{
	    char *org = (char *)buf_modname(
#ifdef SHORT_FNAME
										TRUE,
#else
										(buf->b_p_sn || buf->b_shortname),
#endif
																 fname, p_pm);

		if (backup != NULL)
		{
		    struct stat st;

			/*
			 * If the original file does not exist yet
			 * the current backup file becomes the original file
			 */
		    if (org == NULL)
				EMSG("patchmode: can't save original file");
			else if (stat(org, &st) < 0)
			{
			    vim_rename(backup, (char_u *)org);
				vim_free(backup);			/* don't delete the file */
				backup = NULL;
			}
		}
	    /*
		 * If there is no backup file, remember that a (new) file was
		 * created.
		 */
	    else
		{
		    int empty_fd;

			if (org == NULL || (empty_fd =
									  open(org, O_CREAT | O_EXTRA, 0666)) < 0)
			  EMSG("patchmode: can't touch empty original file");
		    else
			  close(empty_fd);
		}
	    if (org != NULL)
		{
		    setperm((char_u *)org, getperm(fname) & 0777);
			vim_free(org);
		}
	}

	/*
	 * Remove the backup unless 'backup' option is set
	 */
	if (!p_bk && backup != NULL && vim_remove(backup) != 0)
		EMSG("Can't delete backup file");
	
	goto nofail;

fail:
	--no_wait_return;		/* may wait for return now */
nofail:

	vim_free(backup);
	if (buffer != smallbuf)
		vim_free(buffer);

	if (errmsg != NULL)
	{
		/* can't use emsg() here, do something alike */
		if (p_eb)
			beep_flush();			/* also includes flush_buffers() */
		else
			flush_buffers(FALSE);	/* flush internal buffers */
		(void)set_highlight('e');	/* set highlight mode for error messages */
		start_highlight();
		filemess(buf,
#ifndef UNIX
						did_cd ? fname : sfname,
#else
						fname,
#endif
													errmsg);
		retval = FAIL;
		if (end == 0)
		{
			MSG_OUTSTR("\nWARNING: Original file may be lost or damaged\n");
			MSG_OUTSTR("don't quit the editor until the file is successfully written!");
		}
	}
	msg_scroll = msg_save;

#ifdef AUTOCMD
	write_no_eol_lnum = 0;		/* in case it was set by the previous read */

	/*
	 * Apply POST autocommands.
	 * Careful: The autocommands may call buf_write() recursively!
	 */
	save_buf = curbuf;
	curbuf = buf;
	curwin->w_buffer = buf;
	if (append)
		apply_autocmds(EVENT_FILEAPPENDPOST, fname, fname, FALSE);
	else if (filtering)
		apply_autocmds(EVENT_FILTERWRITEPOST, NULL, fname, FALSE);
	else if (reset_changed && whole)
		apply_autocmds(EVENT_BUFWRITEPOST, fname, fname, FALSE);
	else
		apply_autocmds(EVENT_FILEWRITEPOST, fname, fname, FALSE);
	/*
	 * If the autocommands didn't change the current buffer, go back to the
	 * original current buffer, if it still exists.
	 */
	if (curbuf == buf && buf_valid(save_buf))
	{
		curbuf = save_buf;
		curwin->w_buffer = save_buf;
	}
#endif

	return retval;
}

/*
 * Put file name into IObuff with quotes.
 */
	static void
msg_add_fname(buf, fname)
	BUF		*buf;
	char_u	*fname;
{
		/* careful: home_replace calls vim_getenv(), which also uses IObuff! */
	home_replace(buf, fname, IObuff + 1, IOSIZE - 1);
	IObuff[0] = '"';
	STRCAT(IObuff, "\" ");
}

/*
 * Append message for text mode to IObuff.
 * Return TRUE if something appended.
 */
	static int
msg_add_textmode(textmode)
	int		textmode;
{
#ifdef USE_CRNL
	if (!textmode)
	{
		STRCAT(IObuff, shortmess(SHM_TEXT) ? "[notx]" : "[notextmode]");
		return TRUE;
	}
#else
	if (textmode)
	{
		STRCAT(IObuff, shortmess(SHM_TEXT) ? "[tx]" : "[textmode]");
		return TRUE;
	}
#endif
	return FALSE;
}

/*
 * Append line and character count to IObuff.
 */
	static void
msg_add_lines(insert_space, lnum, nchars)
	int		insert_space;
	long	lnum;
	long	nchars;
{
	char_u	*p;

	p = IObuff + STRLEN(IObuff);

	if (insert_space)
		*p++ = ' ';
	if (shortmess(SHM_LINES))
		sprintf((char *)p, "%ldL, %ldC", lnum, nchars);
	else
		sprintf((char *)p, "%ld line%s, %ld character%s",
			lnum, plural(lnum),
			nchars, plural(nchars));
}

/*
 * Append message for missing line separator to IObuff.
 */
	static void
msg_add_eol()
{
	STRCAT(IObuff, shortmess(SHM_LAST) ? "[noeol]" : "[Incomplete last line]");
}

/*
 * write_buf: call write() to write a buffer
 *
 * return FAIL for failure, OK otherwise
 */
	static int
write_buf(fd, buf, len)
	int		fd;
	char_u	*buf;
	int		len;
{
	int		wlen;

	while (len)
	{
		wlen = write(fd, (char *)buf, (size_t)len);
		if (wlen <= 0)				/* error! */
			return FAIL;
		len -= wlen;
		buf += wlen;
	}
	return OK;
}

/*
 * add extention to filename - change path/fo.o.h to path/fo.o.h.ext or
 * fo_o_h.ext for MSDOS or when shortname option set.
 *
 * Assumed that fname is a valid name found in the filesystem we assure that
 * the return value is a different name and ends in 'ext'.
 * "ext" MUST be at most 4 characters long if it starts with a dot, 3
 * characters otherwise.
 * Space for the returned name is allocated, must be freed later.
 */

	char_u *
modname(fname, ext)
	char_u *fname, *ext;
{
	return buf_modname(
#ifdef SHORT_FNAME
						TRUE,
#else
						(curbuf->b_p_sn || curbuf->b_shortname),
#endif
																  fname, ext);
}

	char_u *
buf_modname(shortname, fname, ext)
	int		shortname;				/* use 8.3 filename */
	char_u	*fname, *ext;
{
	char_u				*retval;
	register char_u 	*s;
	register char_u		*e;
	register char_u		*ptr;
	register int		fnamelen, extlen;

	extlen = STRLEN(ext);

	/*
	 * if there is no filename we must get the name of the current directory
	 * (we need the full path in case :cd is used)
	 */
	if (fname == NULL || *fname == NUL)
	{
		retval = alloc((unsigned)(MAXPATHL + extlen + 3));
		if (retval == NULL)
			return NULL;
		if (mch_dirname(retval, MAXPATHL) == FAIL ||
											 (fnamelen = STRLEN(retval)) == 0)
		{
			vim_free(retval);
			return NULL;
		}
		if (!ispathsep(retval[fnamelen - 1]))
		{
			retval[fnamelen++] = PATHSEP;
			retval[fnamelen] = NUL;
		}
	}
	else
	{
		fnamelen = STRLEN(fname);
		retval = alloc((unsigned)(fnamelen + extlen + 2));
		if (retval == NULL)
			return NULL;
		STRCPY(retval, fname);
	}

	/*
	 * search backwards until we hit a '/', '\' or ':' replacing all '.'
	 * by '_' for MSDOS or when shortname option set and ext starts with a dot.
	 * Then truncate what is after the '/', '\' or ':' to 8 characters for
	 * MSDOS and 26 characters for AMIGA, a lot more for UNIX.
	 */
	for (ptr = retval + fnamelen; ptr >= retval; ptr--)
	{
		if (*ext == '.'
#ifdef USE_LONG_FNAME
					&& (!USE_LONG_FNAME || shortname)
#else
# ifndef SHORT_FNAME
					&& shortname
# endif
#endif
																)
			if (*ptr == '.')	/* replace '.' by '_' */
				*ptr = '_';
		if (ispathsep(*ptr))
			break;
	}
	ptr++;

	/* the filename has at most BASENAMELEN characters. */
#ifndef SHORT_FNAME
	if (STRLEN(ptr) > (unsigned)BASENAMELEN)
		ptr[BASENAMELEN] = '\0';
#endif

	s = ptr + STRLEN(ptr);

	/*
	 * For 8.3 filenames we may have to reduce the length.
	 */
#ifdef USE_LONG_FNAME
	if (!USE_LONG_FNAME || shortname)
#else
# ifndef SHORT_FNAME
	if (shortname)
# endif
#endif
	{
		/*
		 * If there is no file name, and the extension starts with '.', put a
		 * '_' before the dot, because just ".ext" is invalid.
		 */
		if (fname == NULL || *fname == NUL)
		{
			if (*ext == '.')
				*s++ = '_';
		}
		/*
		 * If the extension starts with '.', truncate the base name at 8
		 * characters
		 */
		else if (*ext == '.')
		{
			if (s - ptr > (size_t)8)
			{
				s = ptr + 8;
				*s = '\0';
			}
		}
		/*
		 * If the extension doesn't start with '.', and the file name
		 * doesn't have an extension yet, append a '.'
		 */
		else if ((e = vim_strchr(ptr, '.')) == NULL)
			*s++ = '.';
		/*
		 * If If the extension doesn't start with '.', and there already is an
		 * extension, it may need to be tructated
		 */
		else if ((int)STRLEN(e) + extlen > 4)
			s = e + 4 - extlen;
	}
#ifdef OS2
	/*
	 * If there is no file name, and the extension starts with '.', put a
	 * '_' before the dot, because just ".ext" may be invalid if it's on a
	 * FAT partition, and on HPFS it doesn't matter.
	 */
	else if ((fname == NULL || *fname == NUL) && *ext == '.')
		*s++ = '_';
#endif

	/*
	 * Append the extention.
	 * ext can start with '.' and cannot exceed 3 more characters.
	 */
	STRCPY(s, ext);

	/*
	 * Check that, after appending the extension, the file name is really
	 * different.
	 */
	if (fname != NULL && STRCMP(fname, retval) == 0)
	{
		/* we search for a character that can be replaced by '_' */
		while (--s >= ptr)
		{
			if (*s != '_')
			{
				*s = '_';
				break;
			}
		}
		if (s < ptr)	/* fname was "________.<ext>" how tricky! */
			*ptr = 'v';
	}
	return retval;
}

/* vim_fgets();
 *
 * Like fgets(), but if the file line is too long, it is truncated and the
 * rest of the line is thrown away.  Returns TRUE for end-of-file.
 * Note: do not pass IObuff as the buffer since this is used to read and
 * discard the extra part of any long lines.
 */
	int
vim_fgets(buf, size, fp)
	char_u		*buf;
	int			size;
	FILE		*fp;
{
	char *eof;

	buf[size - 2] = NUL;
	eof = fgets((char *)buf, size, fp);
	if (buf[size - 2] != NUL && buf[size - 2] != '\n')
	{
		buf[size - 1] = NUL;		/* Truncate the line */

		/* Now throw away the rest of the line: */
		do
		{
			IObuff[IOSIZE - 2] = NUL;
			fgets((char *)IObuff, IOSIZE, fp);
		} while (IObuff[IOSIZE - 2] != NUL && IObuff[IOSIZE - 2] != '\n');
	}
	return (eof == NULL);
}

/*
 * rename() only works if both files are on the same file system, this
 * function will (attempts to?) copy the file across if rename fails -- webb
 * Return -1 for failure, 0 for success.
 */
	int
vim_rename(from, to)
	char_u *from;
	char_u *to;
{
	int		fd_in;
	int		fd_out;
	int		n;
	char 	*errmsg = NULL;

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

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

	/*
	 * Rename() failed, try copying the file.
	 */
	fd_in = open((char *)from, O_RDONLY | O_EXTRA);
	if (fd_in == -1)
		return -1;
	fd_out = open((char *)to, O_CREAT | O_TRUNC | O_WRONLY | O_EXTRA, 0666);
	if (fd_out == -1)
	{
		close(fd_in);
		return -1;
	}
	while ((n = read(fd_in, (char *)IObuff, (size_t)IOSIZE)) > 0)
		if (write(fd_out, (char *)IObuff, (size_t)n) != n)
		{
			errmsg = "writing to";
			break;
		}
	close(fd_in);
	if (close(fd_out) < 0)
		errmsg = "closing";
	if (n < 0)
	{
		errmsg = "reading";
		to = from;
	}
	if (errmsg != NULL)
	{
		sprintf((char *)IObuff, "Error %s '%s'", errmsg, to);
		emsg(IObuff);
		return -1;
	}
	vim_remove(from);
	return 0;
}

/*
 * Check if any not hidden buffer has been changed.
 * Postpone the check if there are characters in the stuff buffer, a global
 * command is being executed, a mapping is being executed or an autocommand is
 * busy.
 */
	void
check_timestamps()
{
	BUF		*buf;

	if (!stuff_empty() || global_busy || !typebuf_typed()
#ifdef AUTOCMD
						|| autocmd_busy
#endif
										)
		need_check_timestamps = TRUE;			/* check later */
	else
	{
		++no_wait_return;
		for (buf = firstbuf; buf != NULL; buf = buf->b_next)
			buf_check_timestamp(buf);
		--no_wait_return;
		need_check_timestamps = FALSE;
	}
}

/*
 * Check if buffer "buf" has been changed.
 */
	void
buf_check_timestamp(buf)
	BUF		*buf;
{
	struct stat		st;
	char_u			*path;

	if (	buf->b_filename != NULL &&
			buf->b_ml.ml_mfp != NULL &&
			!buf->b_notedited &&
			buf->b_mtime != 0 &&
			stat((char *)buf->b_filename, &st) >= 0 &&
			buf->b_mtime != st.st_mtime)
	{
		path = home_replace_save(buf, buf->b_xfilename);
		if (path != NULL)
		{
			EMSG2("Warning: File \"%s\" has changed since editing started",
																path);
			buf->b_mtime = st.st_mtime;
			vim_free(path);
		}
	}
}

/*
 * Adjust the line with missing eol, used for the next write.
 * Used for do_filter(), when the input lines for the filter are deleted.
 */
	void
write_lnum_adjust(offset)
	linenr_t	offset;
{
	if (write_no_eol_lnum)				/* only if there is a missing eol */
		write_no_eol_lnum += offset;
}

#ifdef USE_TMPNAM
extern char		*tmpnam __ARGS((char *));
#else
extern char		*mktemp __ARGS((char *));
#endif

/*
 * vim_tempname(): Return a unique name that can be used for a temp file.
 *
 * The temp file is NOT created.
 *
 * The returned pointer is to allocated memory.
 * The returned pointer is NULL if no valid name was found.
 */
	char_u	*
vim_tempname(extra_char)
	int		extra_char;		/* character to use in the name instead of '?' */
{
#ifdef USE_TMPNAM
	char_u			itmp[L_tmpnam];		/* use tmpnam() */
#else
	char_u			itmp[TEMPNAMELEN];
#endif
#if defined(TEMPDIRNAMES) || !defined(USE_TMPNAM)
	char_u			*p;
#endif

#if defined(TEMPDIRNAMES)
	static char		*(tempdirs[]) = {TEMPDIRNAMES};
	static int		first_dir = 0;
	int				first_try = TRUE;
	int				i;

	/*
	 * Try a few places to put the temp file.
	 * To avoid waisting time with non-existing environment variables and
	 * directories, they are skipped next time.
	 */
	for (i = first_dir; i < sizeof(tempdirs) / sizeof(char *); ++i)
	{
		/* expand $TMP, leave room for '/', "v?XXXXXX" and NUL */
		expand_env((char_u *)tempdirs[i], itmp, TEMPNAMELEN - 10);
		if (mch_isdir(itmp))			/* directory exists */
		{
			if (first_try)
				first_dir = i;			/* start here next time */
			first_try = FALSE;
#ifdef __EMX__
			/*
			 * if $TMP contains a forward slash (perhaps because we're using
			 * bash or tcsh, right Stefan?), don't add a backslash to the
			 * directory before tacking on the filename; use a forward slash!
			 * I first tried adding 2 backslashes, but somehow that didn't
			 * work (something in the EMX system() ate them, I think).
			 */
			if (vim_strchr(itmp, '/'))
				STRCAT(itmp, "/");
			else
#endif
				STRCAT(itmp, PATHSEPSTR);
			STRCAT(itmp, TEMPNAME);
			if ((p = vim_strchr(itmp, '?')) != NULL)
				*p = extra_char;
			if (*mktemp((char *)itmp) == NUL)
				continue;
			return strsave(itmp);
		}
	}
	return NULL;
#else

# ifdef USE_TMPNAM
	/* tmpnam() will make its own name */
	if (*tmpnam((char *)itmp) == NUL)
# else
	STRCPY(itmp, TEMPNAME);
	if ((p = vim_strchr(itmp, '?')) != NULL)
		*p = extra_char;
	if (*mktemp((char *)itmp) == NUL)
# endif
		return NULL;
	return strsave(itmp);
#endif
}
