/* vi:ts=4:sw=4
 *
 * VIM - Vi IMproved
 *
 * Code Contributions By:	Bram Moolenaar			mool@oce.nl
 *							Tim Thompson			twitch!tjt
 *							Tony Andrews			onecom!wldrdg!tony 
 *							G. R. (Fred) Walter		watmath!watcgl!grwalter 
 */

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

/*
 * special feature of this version: NUL characters in the file are
 * replaced by newline characters in memory. This allows us to edit
 * binary files!
 */

#ifdef MSDOS
# include <io.h>
#endif

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "param.h"
#include "fcntl.h"
#ifdef JP
#include "jp.h"
extern num_jis0201r();
#endif

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

static int	noendofline = FALSE;	/* Set to TRUE when last line has no
															EOL in binary mode */

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

static int  write_buf __ARGS((int, char *, int));
static void do_mlines __ARGS((void));

	void
filemess(name, s)
	char		*name;
	char		*s;
{
	smsg("\"%s\" %s", ((name == NULL) ? "" : name), s);
}

/*
 * Read lines from file 'fname' into the buffer after line 'from'.
 *
 * 1. We allocate blocks with m_blockalloc, 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 appendline().
 *
 * (caller must check that fname != NULL)
 */
	int
readfile(fname, sfname, from, newfile)
	char		   *fname;
	char		   *sfname;
	linenr_t		from;
	int				newfile;
{
#ifdef UNIX
	int 				fd = -1;
#else
	int 				fd;
#endif
	register u_char 	c;
	register linenr_t	lnum = from;
	register u_char 	*ptr = NULL;			/* pointer into read buffer */
	register u_char		*buffer = NULL;			/* read buffer */
#ifdef JP
	long		size;
#else
	register long		size;
#endif
	register u_char		*p;
	long				filesize;
#define UNKNOWN		0x0fffffff					/* file size is unknown */
	linenr_t			linecnt = line_count;
	int					incomplete = FALSE; 	/* was the last line incomplete? */
	int 				error = 0;				/* read errors encountered */
	long				linerest = 0;			/* remaining characters in line */
	long				filerest;				/* remaining characters in file */
	int					firstpart = TRUE;		/* reading first part */
#ifdef UNIX
	int					perm;
#endif
	int					textmode = p_tx;		/* accept CR-LF for line break */
#ifdef MDOMAIN
	int					isurl, total, count;
#endif

	if (sfname == NULL)
		sfname = fname;
	/*
	 * Use the short filename whenever possible.
	 * Avoids problems with networks and when directory names are changed.
	 */
	if (!did_cd)
		fname = sfname;

	if (bufempty())		/* special case: buffer has no lines */
		linecnt = 0;

#ifdef MDOMAIN
	/* check whether fname is URL or not */
	count = 0;
	isurl = is_url(fname);
	if (!isurl) {
#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_VERSION
	if (perm >= 0 && !S_ISREG(perm))				/* not a regular file */
#else
	if (perm >= 0 && (perm & S_IFMT) != S_IFREG)	/* not a regular file */
#endif
	{
#ifdef _POSIX_VERSION
		if (S_ISDIR(perm))
#else
		if ((perm & S_IFMT) == S_IFDIR)
#endif
			filemess(fname, "is a directory");
		else
			filemess(fname, "is not a file");
#ifdef JP
		JP_FCODE = JP_NEW;
#endif
		return TRUE;
	}
#endif
# ifdef MDOMAIN
	}
	else
		perm = 0;
# endif


#ifdef MDOMAIN
	if (isurl)
	{
		p_ro = FALSE;
		if ((fd = url_open(fname, O_RDONLY, &total)) == -1)
		{
			filemess(fname, " not read.");
#ifdef JP
			JP_FCODE = JP_NEW;
#endif
			return TRUE;
		}
	}
	else
#endif
	if (
#ifdef UNIX
		!(perm & 0200) ||						/* root's way to check RO */
#endif
		(fd = open(fname, O_RDWR)) == -1)		/* cannot open r/w */
	{
		if ((fd = open(fname, O_RDONLY)) == -1) /* cannot open at all */
		{
#ifdef MSDOS
		/*
		 * The screen may be messed up by the "insert disk
		 * in drive b: and hit return" message
		 */
			updateScreen(CLEAR);
#endif

#ifndef UNIX
		/*
		 * On MSDOS and Amiga we can't open a directory, check here.
		 */
			if (isdir(fname) > 0)
			{
				filemess(fname, "is a directory");
				p_ro = TRUE;
			}
			else
#endif
				if (newfile)
				{
					filemess(fname, "[New File]");
					if (!readonlymode)
						p_ro = FALSE;
				}

#ifdef JP
			JP_FCODE = JP_NEW;
#endif
			return TRUE;
		}
		if (newfile)						/* set file readonly */
			p_ro = TRUE;
	}
	else if (newfile && !readonlymode)		/* set file not readonly */
		p_ro = FALSE;

	if (newfile)
		noendofline = FALSE;

#ifdef MDOMAIN
	if (isurl)
		filesize = total;
	else
	{
#endif
	if ((filesize = lseek(fd, 0L, 2)) < 0)	/* get length of file */
		filesize = UNKNOWN;
	lseek(fd, 0L, 0);
#ifdef MDOMAIN
	}
#endif

#ifdef JP
	reset_jcount();						/* prepair convert kanji code */
#endif

	filemess(fname, "");					/* show that we are busy */

	for (filerest = filesize; !error && !got_int && filerest != 0; breakcheck())
	{
		/*
		 * We allocate as much space for the file as we can get, plus
		 * space for the old line, one NUL in front and one NUL at the tail.
		 * The amount is limited by the fact that read() only can read
		 * upto max_unsigned characters (and other things).
		 * If we don't know the file size, just get one Kbyte.
		 */
		if (filesize >= UNKNOWN)
			size = 1024;
#if defined(MSDOS) && !defined(WIN32)
		else if (filerest > 0xff00L)
			size = 0xff00L;
#endif
		else if (filerest < 10)
			size = 10;
		else
			size = filerest;

		for ( ; size >= 10; size /= 2)
		{
			if ((buffer = (u_char *)m_blockalloc((u_long)(size + linerest + 4), FALSE))
						!= NULL)
				break;
		}
		if (buffer == NULL)
		{
			emsg(e_outofmem);
			error = 1;
			break;
		}
		buffer[0] = NUL;	/* make sure there is a NUL in front of the first line */
		++buffer;
		if (linerest)		/* copy characters from the previous buffer */
		{
			ptr -= linerest;
			memmove((char *)buffer, (char *)ptr, linerest);
			memset((char *)ptr, 1, linerest);	/* fill with non-NULs */
			ptr[linerest - 1] = NUL;			/* add a NUL on the end */
			free_line((char *)ptr);				/* free the space we don't use */
		}
		ptr = buffer + linerest;
		
		if ((size = (unsigned)read(fd, (char *)ptr, (size_t)size)) <= 0)
		{
			error = 2;
			break;
		}
		if (filesize >= UNKNOWN)			/* if we don't know the file size */
			filesize += size;				/* .. count the number of characters */
		else								/* .. otherwise */
			filerest -= size;				/* .. compute the remaining length */

		/*
		 * 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 && p_tx != textmode)
					{
						p_tx = textmode;
						paramchanged("tx");
					}
					break;
				}
		}

		--ptr;
		/*
		 * This loop is executed once for every character read.
		 * Keep it fast!
		 */
		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
			{
				*ptr = NUL;		/* end of line */
				if (textmode && ptr[-1] == CR)	/* remove CR */
					ptr[-1] = NUL;
#ifdef JP
				if (JP_READ != JP_NONE)
				{
					char	tmp[IOSIZE];
					int		n, l, min;
					int		tmp_jse;

					tmp_jse = p_jse;
					p_jse = TRUE;
					n = kanjiconvsfrom(buffer, l = ptr - buffer,
											tmp, IOSIZE, NULL, JP_READ, NULL);
					p_jse = tmp_jse;
					if (n < 0)
					{
						emsg2("Line %d: Too many JISX0201 right char./line.", lnum);
						error = 1;
						break;
					}
					min = n < l ? n : l;
					memmove(buffer, tmp, min);
					buffer[min] = NUL;
					if (!appendline(lnum, (char *)buffer))
					{
						error = 1;
						break;
					}
					if (n > l)
					{
						int		lorg;
						char	*lptr;

						lorg = Curpos.lnum;
						Curpos.lnum = lnum + 1;
						if (!canincrease(n - l))
						{
							error = 1;
							break;
						}
						lptr = nr2ptr(Curpos.lnum);
						memmove(lptr + l, tmp + l, n - l);
						lptr[n] = NUL;
						Curpos.lnum = lorg;
					}
				}
				else
#endif
				if (!appendline(lnum, (char *)buffer))
				{
					error = 1;
					break;
				}
				++lnum;
				buffer = ptr + 1;
			}
		}
		linerest = ptr - buffer;
		firstpart = FALSE;
	}
	if (lnum != from && !newfile)	/* added at least one line */
		CHANGED;
	if (error != 1 && linerest != 0)
	{
		/*
		 * If we get EOF in the middle of a line, note the fact and
		 * complete the line ourselves.
		 */
		incomplete = TRUE;
		if (newfile && p_bin)		/* remember for when writing */
			noendofline = TRUE;
		*ptr = NUL;
		if (!appendline(lnum, (char *)buffer))
			error = 1;
		else if (!newfile)
			CHANGED;
	}
	if (error == 2 && filesize >= UNKNOWN)	/* no error, just EOF encountered */
	{
		filesize -= UNKNOWN;
		error = 0;
	}

	close(fd);

#ifdef MSDOS		/* the screen may be messed up by the "insert disk
							in drive b: and hit return" message */
	updateScreen(CLEAR);
#endif

#ifdef JP			/* guess kanji code */
	{
		char jcode;

		jcode = judge_jcode(JP_READ);

		if (newfile && JP_READ != JP_NONE)
			if ((JP_FCODE = jcode) == JP_NONE)
				JP_FCODE = JP_NEW;

		if (JP_READ == JP_ANY  && jcode == JP_SJIS)
		{
			p_ro = TRUE;
			emsg("SJIS file: use ',' for the 1st char. of jmask");
		}
		if (JP_READ == JP_SANY && jcode == JP_EUC)
		{
			p_ro = TRUE;
			emsg("EUC file: use '.' for the 1st char. of jmask");
		}

		if (num_jis0201r())
		{
			p_ro = TRUE;
			emsg("Hankaku Kana ---> Zenkaku Katakana");
		}
	}
#endif
#if defined(JPFEP) || defined(FEPCTRL)
	if (p_ja)
	{
		p_ji = guess_jiauto(p_ji, p_ja);
#ifdef JPFEP
		switch(*p_ji)
		{
			case 'j':
				KanjiInput = TRUE;
				goto setmode;
			case 'a':
				KanjiInput = FALSE;
setmode:
				fep_mode_switch(KanjiInput);
		}
#else /* if FEPCTRL */
		if (p_fc)
			switch(*p_ji)
			{
				case 'j':
					fep_force_on();
					break;
				case 'a':
					fep_force_off();
					break;
			}
		fep_off();
#endif
	}
#endif

	if (got_int)
	{
		filemess(fname, e_interr);
		return FALSE;			/* an interrupt isn't really an error */
	}

	linecnt = line_count - linecnt;
	smsg(
#ifdef JP
# ifdef JPFEP
	"\"%s\" %s%s%s%s%ld line%s, %ld character%s [%c%c]",
# else
	"\"%s\" %s%s%s%s%ld line%s, %ld character%s [%c]",
# endif
#else
	"\"%s\" %s%s%s%s%ld line%s, %ld character%s",
#endif
			fname,
			p_ro ? "[readonly] " : "",
			incomplete ? "[Incomplete last line] " : "",
			error ? "[READ ERRORS] " : "",
#ifdef MSDOS
			textmode ? "" : "[notextmode] ",
#else
			textmode ? "[textmode] " : "",
#endif
			(long)linecnt, plural((long)linecnt),
			filesize, plural(filesize)
#ifdef JP
			, JP_FCODE
#endif
#ifdef JPFEP
			, *p_ji
#endif
			);

	if (error && newfile)	/* with errors we should not write the file */
	{
		p_ro = TRUE;
		paramchanged("ro");
	}

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

	if (newfile)		/* edit a new file: read mode from lines */
		do_mlines();
	if (from < line_count)
	{
		Curpos.lnum = from + 1;	/* put cursor at first new line */
		Curpos.col = 0;
	}

	return FALSE;
}

/*
 * writeit - 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 whole is TRUE and start == 1 and end == line_count, reset Changed.
 */
	int
writeit(fname, sfname, start, end, append, forceit, whole)
	char			*fname;
	char			*sfname;
	linenr_t		start, end;
	int				append;
	int				forceit;
	int				whole;
{
	int 				fd;
	char			   *backup = NULL;
	register char	   *s;
	register u_char	   *ptr;
	register u_char		c;
	register int		len;
	register linenr_t	lnum;
	long				nchars;
	char				*errmsg = NULL;
	char				*buffer;
	char				smallbuf[SBUFSIZE];
	int					bufsize;
	long 				perm = -1;			/* file permissions */
	int					retval = TRUE;
	int					newfile = FALSE;	/* TRUE if file does not exist yet */
#ifdef UNIX
	struct stat			old;
	int					made_writable = FALSE;	/* 'w' bit has been set */
#endif
#ifdef AMIGA
	BPTR				flock;
#endif
#ifdef MDOMAIN
	int					total;
#endif

	if (fname == NULL || *fname == NUL)		/* safety check */
		return FALSE;
	if (sfname == NULL)
		sfname = fname;
	/*
	 * Use the short filename whenever possible.
	 * Avoids problems with networks and when directory names are changed.
	 */
	if (!did_cd)
		fname = sfname;

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

	if (exiting)
		settmode(0);			/* when exiting allow typahead now */

	filemess(fname, "");		/* show that we are busy */

	buffer = alloc(BUFSIZE);
	if (buffer == NULL)			/* can't allocate big buffer, use small one */
	{
		buffer = smallbuf;
		bufsize = SBUFSIZE;
	}
	else
		bufsize = BUFSIZE;

#ifdef UNIX
		/* get information about original file (if there is one) */
	old.st_dev = old.st_ino = 0;
#ifdef MDOMAIN
	if (is_url(fname))
		newfile=TRUE;
	else
#endif
	if (stat(fname, &old))
		newfile = TRUE;
	else
	{
#ifdef _POSIX_VERSION
		if (!S_ISREG(old.st_mode))      		/* not a file */
#else
		if ((old.st_mode & S_IFMT) != S_IFREG)	/* not a file */
#endif
		{
#ifdef _POSIX_VERSION
			if (S_ISDIR(old.st_mode))
#else
			if ((old.st_mode & S_IFMT) == S_IFDIR)
#endif
				errmsg = "is a directory";
			else
				errmsg = "is not a file";
			goto fail;
		}
		perm = old.st_mode;
	}
/*
 * If we are not appending, the file exists, and the 'writebackup' or
 * 'backup' option is set, try to make a backup copy of the file.
 */
	if (!append && perm >= 0 && (p_wb || p_bk) &&
					(fd = open(fname, O_RDONLY)) >= 0)
	{
		int				bfd, buflen;
		char			buf[BUFSIZE + 1], *wp;
		int				some_error = FALSE;
		struct stat		new;

		new.st_dev = new.st_ino = 0;

		/*
		 * 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.
		 */
		if ((backup = modname(fname, ".bak")) == NULL)
		{
			some_error = TRUE;
			goto nobackup;
		}			
		if (!stat(backup, &new) &&
					new.st_dev == old.st_dev && new.st_ino == old.st_ino)
		{
			/*
			 * 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.
			 */
			errmsg = "Invalid backup file (use ! to override)";
			free(backup);
			backup = NULL;	/* there is no backup file to delete */
			goto nobackup;
		}
		remove(backup);		/* remove old backup, if present */
		if ((bfd = open(backup, O_WRONLY | O_CREAT, 0666)) < 0)
		{
			/* 
			 * oops, no write/create permission here?
			 * try again in p_bdir directory. 
			 */
			for (wp = fname + strlen(fname); wp >= fname; wp--)
				if (*wp == '/')
					break;
			++wp;
			sprintf(buf, "%s/%s", p_bdir, wp);
			free(backup);
			if ((backup = modname(buf, ".bak")) == NULL)
			{
				some_error = TRUE;
				goto nobackup;
			}
			if (!stat(backup, &new) &&
						new.st_dev == old.st_dev && new.st_ino == old.st_ino)
			{
				errmsg = "Invalid backup file (use ! to override)";
				free(backup);
				backup = NULL;	/* there is no backup file to delete */
				goto nobackup;
			}
			remove(backup);
			if ((bfd = open(backup, O_WRONLY | O_CREAT, 0666)) < 0)
			{
				free(backup);
				backup = NULL;	/* there is no backup file to delete */
				errmsg = "Can't make backup file (use ! to override)";
				goto nobackup;
			}
		}
		/* set file protection same as original file, but strip s-bit */
		setperm(backup, perm & 0777);

		/* copy the file. */
		while ((buflen = read(fd, buf, BUFSIZE)) > 0)
		{
			if (write_buf(bfd, buf, buflen) == -1)
			{
				errmsg = "Can't write to backup file (use ! to override)";
				goto writeerr;
			}
		}
writeerr:
		close(bfd);
		if (buflen < 0)
			errmsg = "Can't read file for backup (use ! to override)";
nobackup:
		close(fd);
	/* ignore errors when forceit is TRUE */
		if ((some_error || errmsg) && !forceit)
		{
			retval = FALSE;
			goto fail;
		}
		errmsg = NULL;
	}
		/* if forceit and the file was read-only: make it writable */
	if (forceit && (old.st_uid == getuid()) && perm >= 0 && !(perm & 0200))
 	{
		perm |= 0200;	
		setperm(fname, perm);
		made_writable = TRUE;
			/* if we are writing to the current file, readonly makes no sense */
		if (fname == Filename || fname == sFilename)
			p_ro = FALSE;
 	}
#else /* UNIX */

/*
 * 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 (isdir(fname) > 0)
	{
		errmsg = "is a directory";
		goto fail;
	}
	if (!append && perm >= 0 && (p_wb || p_bk))
	{
		/*
		 * Form the backup file name - change path/fo.o.h to path/fo.o.h.bak
		 */
		backup = modname(fname, ".bak");
		if (backup == NULL)
		{
			if (!forceit)
				goto fail;
		}
		else
		{
			/*
			 * 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 thisfile_sn, 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
			remove(backup);
#ifdef AMIGA
			if (flock)
				UnLock(flock);
#endif
			len = rename(fname, backup);
			if (len != 0)
			{
				if (forceit)
				{
					free(backup);	/* don't do the rename below */
					backup = NULL;
				}
				else
				{
					errmsg = "Can't make backup file (use ! to override)";
					goto fail;
				}
			}
		}
	}
#endif /* UNIX */

		/* 
		 * 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.
		 */
#ifdef MDOMAIN
	if (is_url(fname))
	{
		if ((fd = url_open(fname, O_WRONLY, &total)) == -1)
			goto fail;
	}
	else
#endif
	while ((fd = open(fname, O_WRONLY | (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 remove() will fail.
		 */
		if (!errmsg)
		{
			errmsg = "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 (old.st_uid != getuid() || old.st_gid != getgid())
					perm &= 0777;
#endif /* UNIX */
				if (!append)		/* don't remove when appending */
					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)
		{
#ifdef UNIX
			struct stat st;

			/*
			 * There is a small chance that we removed the original, try
			 * to move the copy in its place.
			 * This won't work if the backup is in another file system!
			 * In that case we leave the copy around.
			 */
			if (stat(fname, &st) < 0)	/* file does not exist */
				rename(backup, fname);	/* put the copy in its place */
			if (stat(fname, &st) >= 0)	/* original file does exist */
				remove(backup);			/* throw away the copy */
#else
 			rename(backup, fname);	/* try to put the original file back */
#endif
		}
 		goto fail;
 	}
	errmsg = NULL;

	if (end > line_count)
		end = line_count;
	len = 0;
	s = buffer;
	nchars = 0;
	for (lnum = start; lnum <= end; ++lnum)
	{
#ifdef JP
		char	*tmpptr, *orgptr;

		orgptr = nr2ptr(lnum);
		if (JP_FCODE == JP_NONE)
			tmpptr = orgptr;
		else
			tmpptr = kanjiconvsto(orgptr, JP_FCODE);
		ptr = (u_char *)tmpptr - 1;
#else  /* JP */
		ptr = (u_char *)nr2ptr(lnum) - 1;
#endif /* JP */
		/*
		 * The next while loop is done once for each character written.
		 * Keep it fast!
		 */
		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) == -1)
			{
				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 || (p_bin && lnum == line_count && noendofline))
			break;
		if (p_tx)		/* write CR-NL */
		{
			*s = CR;
			++s;
			if (++len == bufsize)
			{
				if (write_buf(fd, buffer, bufsize) == -1)
				{
					end = 0;				/* write error: break loop */
					break;
				}
				nchars += bufsize;
				s = buffer;
				len = 0;
			}
		}
#ifdef JP
		if (tmpptr != orgptr)
			free_line(tmpptr);
#endif
		*s = NL;
		++s;
		if (++len == bufsize && end)
		{
			if (write_buf(fd, buffer, bufsize) == -1)
			{
				end = 0;				/* write error: break loop */
				break;
			}
			nchars += bufsize;
			s = buffer;
			len = 0;
		}
	}
	if (len && end)
	{
		if (write_buf(fd, buffer, len) == -1)
			end = 0;				/* write error */
		nchars += len;
	}

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

	if (end == 0)
	{
		errmsg = "write error (file system full?)";
		goto fail;
	}

#ifdef MSDOS		/* the screen may be messed up by the "insert disk
							in drive b: and hit return" message */
	if (!exiting)
		updateScreen(CLEAR);
#endif

	lnum -= start;		/* compute number of written lines */
	smsg("\"%s\"%s%s %ld line%s, %ld character%s",
			fname,
			newfile ? " [New File]" : " ",
#ifdef MSDOS
			p_tx ? "" : "[notextmode]",
#else
			p_tx ? "[textmode]" : "",
#endif
			(long)lnum, plural((long)lnum),
			nchars, plural(nchars));
	if (whole && start == 1 && end == line_count)	/* when written everything */
	{
		UNCHANGED;
		NotEdited = FALSE;
		startscript();		/* re-start auto script file */
	}

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

fail:
#ifdef MSDOS		/* the screen may be messed up by the "insert disk
							in drive b: and hit return" message */
	updateScreen(CLEAR);
#endif
nofail:

	free((char *) backup);
	free(buffer);

	if (errmsg != NULL)
	{
		filemess(fname, errmsg);
		retval = FALSE;
	}
	return retval;
}

/*
 * write_buf: call write() to write a buffer
 */
	static int
write_buf(fd, buf, len)
	int		fd;
	char	*buf;
	int		len;
{
	int		wlen;

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

/*
 * do_mlines() - process mode lines for the current file
 *
 * Returns immediately if the "ml" parameter isn't set.
 */
static void 	chk_mline __ARGS((linenr_t));

	static void
do_mlines()
{
	linenr_t		lnum;
	int 			nmlines;

	if (!p_ml || (nmlines = (int)p_mls) == 0)
		return;

	for (lnum = 1; lnum <= line_count && lnum <= nmlines; ++lnum)
		chk_mline(lnum);

	for (lnum = line_count; lnum > 0 && lnum > nmlines &&
							lnum > line_count - nmlines; --lnum)
		chk_mline(lnum);
}

/*
 * chk_mline() - check a single line for a mode string
 */
	static void
chk_mline(lnum)
	linenr_t lnum;
{
	register char	*s;
	register char	*e;
	char			*cs;			/* local copy of any modeline found */
	char			prev;
	int				end;

	prev = ' ';
	for (s = nr2ptr(lnum); *s != NUL; ++s)
	{
		if (isspace((unsigned char)prev) && (strncmp(s, "vi:", (size_t)3) == 0 || strncmp(s, "ex:", (size_t)3) == 0 || strncmp(s, "vim:", (size_t)4) == 0))
		{
			do
				++s;
			while (s[-1] != ':');
			s = cs = strsave(s);
			if (cs == NULL)
				break;
			end = FALSE;
			while (end == FALSE)
			{
				while (*s == ' ' || *s == TAB)
					++s;
				if (*s == NUL)
					break;
				for (e = s; (*e != ':' || *(e - 1) == '\\') && *e != NUL; ++e)
					;
				if (*e == NUL)
					end = TRUE;
				*e = NUL;
				if (strncmp(s, "set ", (size_t)4) == 0) /* "vi:set opt opt opt: foo" */
				{
					doset(s + 4);
					break;
				}
				if (doset(s))		/* stop if error found */
					break;
				s = e + 1;
			}
			free(cs);
			break;
		}
		prev = *s;
	}
}


syntax highlighted by Code2HTML, v. 0.9.1