/* tmp.c */

/* Author:
 *	Steve Kirkendall
 *	14407 SW Teal Blvd. #C
 *	Beaverton, OR 97005
 *	kirkenda@cs.pdx.edu
 */


/* This file contains functions which create & readback a TMPFILE */


#include "config.h"
#include "vi.h"
#if TOS
# include <stat.h>
#else
# if OSK
#  include "osk.h"
# else
#  if AMIGA
#   include "amistat.h"
#  else
#   include <sys/stat.h>
#  endif
# endif
#endif
#if TURBOC
# include <process.h>
#endif

#ifndef NO_MODELINES
static void do_modelines(l, stop)
	long	l;	/* line number to start at */
	long	stop;	/* line number to stop at */
{
	char	*str;	/* used to scan through the line */
	char	*start;	/* points to the start of the line */
	char	buf[80];

	/* if modelines are disabled, then do nothing */
	if (!*o_modelines)
	{
		return;
	}

	/* for each line... */
	for (; l <= stop; l++)
	{
		/* for each position in the line.. */
		for (str = fetchline(l); *str; str++)
		{
			/* if it is the start of a modeline command... */
			if ((str[0] == 'e' && str[1] == 'x'
			  || str[0] == 'v' && str[1] == 'i')
			  && str[2] == ':')
			{
				start = str += 3;

				/* find the end */
				for (str = start + strlen(start); *--str != ':'; )
				{
				}

				/* if it is a well-formed modeline, execute it */
				if (str > start && str - start < sizeof buf)
				{
					strncpy(buf, start, (int)(str - start));
					exstring(buf, str - start, '\\');
					break;
				}
			}
		}
	}
}
#endif


/* The FAIL() macro prints an error message and then exits. */
#define FAIL(why,arg)	mode = MODE_EX; msg(why, arg); endwin(); exit(9)

/* This is the name of the temp file */
static char	tmpname[80];

/* This function creates the temp file and copies the original file into it.
 * Returns if successful, or stops execution if it fails.
 */
int tmpstart(filename)
	char		*filename; /* name of the original file */
{
	int		origfd;	/* fd used for reading the original file */
	struct stat	statb;	/* stat buffer, used to examine inode */
	REG BLK		*this;	/* pointer to the current block buffer */
	REG BLK		*next;	/* pointer to the next block buffer */
	int		inbuf;	/* number of characters in a buffer */
	int		nread;	/* number of bytes read */
	REG int		j, k;
	int		i;
	long		nbytes;

	/* switching to a different file certainly counts as a change */
	changes++;
	redraw(MARK_UNSET, FALSE);

	/* open the original file for reading */
	*origname = '\0';
	if (filename && *filename)
	{
		strcpy(origname, filename);
		origfd = open(origname, O_RDONLY);
		if (origfd < 0 && errno != ENOENT)
		{
			msg("Can't open \"%s\"", origname);
			return tmpstart("");
		}
		if (origfd >= 0)
		{
			if (stat(origname, &statb) < 0)
			{
				FAIL("Can't stat \"%s\"", origname);
			}
#if TOS
			if (origfd >= 0 && (statb.st_mode & S_IJDIR))
#else
# if OSK
			if (origfd >= 0 && (statb.st_mode & S_IFDIR))
# else
			if (origfd >= 0 && (statb.st_mode & S_IFMT) != S_IFREG)
# endif
#endif
			{
				msg("\"%s\" is not a regular file", origname);
				return tmpstart("");
			}
		}
		else
		{
			stat(".", &statb);
		}
		if (origfd >= 0)
		{
			origtime = statb.st_mtime;
#if OSK
			if (*o_readonly || !(statb.st_mode &
				  ((getuid() >> 16) == 0 ? S_IOWRITE | S_IWRITE :
				  ((statb.st_gid != (getuid() >> 16) ? S_IOWRITE : S_IWRITE)))))
#endif
#if AMIGA || MSDOS || (TOS && defined(__GNUC__))
			if (*o_readonly || !(statb.st_mode & S_IWRITE))
#endif
#if TOS && !defined(__GNUC__)
			if (*o_readonly || (statb.st_mode & S_IJRON))
#endif
#if ANY_UNIX
			if (*o_readonly || !(statb.st_mode &
				  ((geteuid() == 0) ? 0222 :
				  ((statb.st_uid != geteuid() ? 0022 : 0200)))))
#endif
#if VMS
			if (*o_readonly)
#endif
			{
				setflag(file, READONLY);
			}
		}
		else
		{
			origtime = 0L;
		}
	}
	else
	{
		setflag(file, NOFILE);
		origfd = -1;
		origtime = 0L;
		stat(".", &statb);
	}

	/* make a name for the tmp file */
	tmpnum++;
#if MSDOS || TOS
	/* MS-Dos doesn't allow multiple slashes, but supports drives
	 * with current directories.
	 * This relies on TMPNAME beginning with "%s\\"!!!!
	 */
	strcpy(tmpname, o_directory);
	if ((i = strlen(tmpname)) && !strchr(":/\\", tmpname[i-1]))
		tmpname[i++]=SLASH;
	sprintf(tmpname+i, TMPNAME+3, getpid(), tmpnum);
#else
	sprintf(tmpname, TMPNAME, o_directory, getpid(), tmpnum);
#endif

	/* make sure nobody else is editing the same file */
	if (access(tmpname, 0) == 0)
	{
		FAIL("Temp file \"%s\" already exists?", tmpname);
	}

	/* create the temp file */
#if ANY_UNIX
	close(creat(tmpname, 0600));		/* only we can read it */
#else
	close(creat(tmpname, FILEPERMS));	/* anybody body can read it, alas */
#endif
	tmpfd = open(tmpname, O_RDWR | O_BINARY);
	if (tmpfd < 0)
	{
		FAIL("Can't create temp file... Does directory \"%s\" exist?", o_directory);
		return 1;
	}

	/* allocate space for the header in the file */
	write(tmpfd, hdr.c, (unsigned)BLKSIZE);
	write(tmpfd, tmpblk.c, (unsigned)BLKSIZE);

#ifndef NO_RECYCLE
	/* initialize the block allocator */
	/* This must already be done here, before the first attempt
	 * to write to the new file! GB */
	garbage();
#endif

	/* initialize lnum[] */
	for (i = 1; i < MAXBLKS; i++)
	{
		lnum[i] = INFINITY;
	}
	lnum[0] = 0;

	/* if there is no original file, then create a 1-line file */
	if (origfd < 0)
	{
		hdr.n[0] = 0;	/* invalid inode# denotes new file */

		this = blkget(1); 	/* get the new text block */
		strcpy(this->c, "\n");	/* put a line in it */

		lnum[1] = 1L;	/* block 1 ends with line 1 */
		nlines = 1L;	/* there is 1 line in the file */
		nbytes = 1L;

		if (*origname)
		{
			msg("\"%s\" [NEW FILE]  1 line, 1 char", origname);
		}
		else
		{
			msg("\"[NO FILE]\"  1 line, 1 char");
		}
	}
	else /* there is an original file -- read it in */
	{
		nbytes = nlines = 0;

		/* preallocate 1 "next" buffer */
		i = 1;
		next = blkget(i);
		inbuf = 0;

		/* loop, moving blocks from orig to tmp */
		for (;;)
		{
			/* "next" buffer becomes "this" buffer */
			this = next;

			/* read [more] text into this block */
			nread = tread(origfd, &this->c[inbuf], BLKSIZE - 1 - inbuf);
			if (nread < 0)
			{
				close(origfd);
				close(tmpfd);
				tmpfd = -1;
				unlink(tmpname);
				FAIL("Error reading \"%s\"", origname);
			}

			/* convert NUL characters to something else */
			for (j = k = inbuf; k < inbuf + nread; k++)
			{
				if (!this->c[k])
				{
					setflag(file, HADNUL);
					this->c[j++] = 0x80;
				}
#ifndef CRUNCH
				else if (*o_beautify && this->c[k] < ' ' && this->c[k] > 0)
				{
					if (this->c[k] == '\t'
					 || this->c[k] == '\n'
					 || this->c[k] == '\f')
					{
						this->c[j++] = this->c[k];
					}
					else if (this->c[k] == '\b')
					{
						/* delete '\b', but complain */
						setflag(file, HADBS);
					}
					/* else silently delete control char */
				}
#endif
				else
				{
					this->c[j++] = this->c[k];
				}
			}
			inbuf = j;

			/* if the buffer is empty, quit */
			if (inbuf == 0)
			{
				goto FoundEOF;
			}

#if MSDOS || TOS
/* BAH! MS text mode read fills inbuf, then compresses eliminating \r
   but leaving garbage at end of buf. The same is true for TURBOC. GB. */

			memset(this->c + inbuf, '\0', BLKSIZE - inbuf);
#endif

			/* search backward for last newline */
			for (k = inbuf; --k >= 0 && this->c[k] != '\n';)
			{
			}
			if (k++ < 0)
			{
				if (inbuf >= BLKSIZE - 1)
				{
					k = 80;
				}
				else
				{
					k = inbuf;
				}
			}

			/* allocate next buffer */
			next = blkget(++i);

			/* move fragmentary last line to next buffer */
			inbuf -= k;
			for (j = 0; k < BLKSIZE; j++, k++)
			{
				next->c[j] = this->c[k];
				this->c[k] = 0;
			}

			/* if necessary, add a newline to this buf */
			for (k = BLKSIZE - inbuf; --k >= 0 && !this->c[k]; )
			{
			}
			if (this->c[k] != '\n')
			{
				setflag(file, ADDEDNL);
				this->c[k + 1] = '\n';
			}

			/* count the lines in this block */
			for (k = 0; k < BLKSIZE && this->c[k]; k++)
			{
				if (this->c[k] == '\n')
				{
					nlines++;
				}
				nbytes++;
			}
			if(i >= MAXBLKS) {
				FAIL("Too many blocks: %d.", i);
			}
			lnum[i - 1] = nlines;
		}
FoundEOF:

		/* if this is a zero-length file, add 1 line */
		if (nlines == 0)
		{
			this = blkget(1); 	/* get the new text block */
			strcpy(this->c, "\n");	/* put a line in it */

			lnum[1] = 1;	/* block 1 ends with line 1 */
			nlines = 1;	/* there is 1 line in the file */
			nbytes = 1;
		}

#if MSDOS || TOS
		/* each line has an extra CR that we didn't count yet */
		nbytes += nlines;
#endif

		/* report the number of lines in the file */
		msg("\"%s\" %s %ld line%s, %ld char%s",
			origname,
			(tstflag(file, READONLY) ? "[READONLY]" : ""),
			nlines,
			nlines == 1 ? "" : "s",
			nbytes,
			nbytes == 1 ? "" : "s");
	}

	/* initialize the cursor to start of line 1 */
	cursor = MARK_FIRST;

	/* close the original file */
	close(origfd);

	/* any other messages? */
	if (tstflag(file, HADNUL))
	{
		msg("This file contained NULs.  They've been changed to \\x80 chars");
	}
	if (tstflag(file, ADDEDNL))
	{
		msg("Newline characters have been inserted to break up long lines");
	}
#ifndef CRUNCH
	if (tstflag(file, HADBS))
	{
		msg("Backspace characters deleted due to ':set beautify'");
	}
#endif

	storename(origname);

#ifndef NO_MODELINES
	if (nlines > 10)
	{
		do_modelines(1L, 5L);
		do_modelines(nlines - 4L, nlines);
	}
	else
	{
		do_modelines(1L, nlines);
	}
#endif

	/* force all blocks out onto the disk, to support file recovery */
	blksync();

	return 0;
}



/* This function copies the temp file back onto an original file.
 * Returns TRUE if successful, or FALSE if the file could NOT be saved.
 */
int tmpsave(filename, bang)
	char	*filename;	/* the name to save it to */
	int	bang;		/* forced write? */
{
	int		fd;	/* fd of the file we're writing to */
	REG int		len;	/* length of a text block */
	REG BLK		*this;	/* a text block */
	long		bytes;	/* byte counter */
	REG int		i;

	/* if no filename is given, assume the original file name */
	if (!filename || !*filename)
	{
		filename = origname;
	}

	/* if still no file name, then fail */
	if (!*filename)
	{
		msg("Don't know a name for this file -- NOT WRITTEN");
		return FALSE;
	}

	/* can't rewrite a READONLY file */
#if AMIGA
	if (!strcmp(filename, origname) && tstflag(file, READONLY) && !bang)
#else
	if (!strcmp(filename, origname) && *o_readonly && !bang)
#endif
	{
		msg("\"%s\" [READONLY] -- NOT WRITTEN", filename);
		return FALSE;
	}

	/* open the file */
	if (*filename == '>' && filename[1] == '>')
	{
		filename += 2;
		while (*filename == ' ' || *filename == '\t')
		{
			filename++;
		}
#ifdef O_APPEND
		fd = open(filename, O_WRONLY|O_APPEND);
#else
		fd = open(filename, O_WRONLY);
		lseek(fd, 0L, 2);
#endif
	}
	else
	{
		/* either the file must not exist, or it must be the original
		 * file, or we must have a bang, or "writeany" must be set.
		 */
		if (strcmp(filename, origname) && access(filename, 0) == 0 && !bang
#ifndef CRUNCH
		    && !*o_writeany
#endif
				   )
		{
			msg("File already exists - Use :w! to overwrite");
			return FALSE;
		}
#if VMS
		/* Create a new VMS version of this file. */
		{ 
		char *strrchr(), *ptr = strrchr(filename,';');
		if (ptr) *ptr = '\0';  /* Snip off any ;number in the name */
		}
#endif
		fd = creat(filename, FILEPERMS);
	}
	if (fd < 0)
	{
		msg("Can't write to \"%s\" -- NOT WRITTEN", filename);
		return FALSE;
	}

	/* write each text block to the file */
	bytes = 0L;
	for (i = 1; i < MAXBLKS && (this = blkget(i)) && this->c[0]; i++)
	{
		for (len = 0; len < BLKSIZE && this->c[len]; len++)
		{
		}
		if (twrite(fd, this->c, len) < len)
		{
			msg("Trouble writing to \"%s\"", filename);
			if (!strcmp(filename, origname))
			{
				setflag(file, MODIFIED);
			}
			close(fd);
			return FALSE;
		}
		bytes += len;
	}

	/* reset the "modified" flag, but not the "undoable" flag */
	clrflag(file, MODIFIED);
	significant = FALSE;

	/* report lines & characters */
#if MSDOS || TOS
	bytes += nlines; /* for the inserted carriage returns */
#endif
	msg("Wrote \"%s\"  %ld lines, %ld characters", filename, nlines, bytes);

	/* close the file */
	close(fd);

	return TRUE;
}


/* This function deletes the temporary file.  If the file has been modified
 * and "bang" is FALSE, then it returns FALSE without doing anything; else
 * it returns TRUE.
 *
 * If the "autowrite" option is set, then instead of returning FALSE when
 * the file has been modified and "bang" is false, it will call tmpend().
 */
int tmpabort(bang)
	int	bang;
{
	/* if there is no file, return successfully */
	if (tmpfd < 0)
	{
		return TRUE;
	}

	/* see if we must return FALSE -- can't quit */
	if (!bang && tstflag(file, MODIFIED))
	{
		/* if "autowrite" is set, then act like tmpend() */
		if (*o_autowrite)
			return tmpend(bang);
		else
			return FALSE;
	}

	/* delete the tmp file */
	cutswitch();
	strcpy(prevorig, origname);
	prevline = markline(cursor);
	*origname = '\0';
	origtime = 0L;
	blkinit();
	nlines = 0;
	initflags();
	return TRUE;
}

/* This function saves the file if it has been modified, and then deletes
 * the temporary file. Returns TRUE if successful, or FALSE if the file
 * needs to be saved but can't be.  When it returns FALSE, it will not have
 * deleted the tmp file, either.
 */
int tmpend(bang)
	int	bang;
{
	/* save the file if it has been modified */
	if (tstflag(file, MODIFIED) && !tmpsave((char *)0, FALSE) && !bang)
	{
		return FALSE;
	}

	/* delete the tmp file */
	tmpabort(TRUE);

	return TRUE;
}


/* If the tmp file has been changed, then this function will force those
 * changes to be written to the disk, so that the tmp file will survive a
 * system crash or power failure.
 */
#if AMIGA || MSDOS || TOS
sync()
{
	/* MS-DOS and TOS don't flush their buffers until the file is closed,
	 * so here we close the tmp file and then immediately reopen it.
	 */
	close(tmpfd);
	tmpfd = open(tmpname, O_RDWR | O_BINARY);
	return 0;
}
#endif


/* This function stores the file's name in the second block of the temp file.
 * SLEAZE ALERT!  SLEAZE ALERT!  The "tmpblk" buffer is probably being used
 * to store the arguments to a command, so we can't use it here.  Instead,
 * we'll borrow the buffer that is used for "shift-U".
 */
storename(name)
	char	*name;	/* the name of the file - normally origname */
{
#ifndef CRUNCH
	int	len;
	char	*ptr;
#endif

	/* we're going to clobber the U_text buffer, so reset U_line */
	U_line = 0L;

	if (!name)
	{
		strncpy(U_text, "", BLKSIZE);
		U_text[1] = 127;
	}
#ifndef CRUNCH
	else if (*name != SLASH)
	{
		/* get the directory name */
		ptr = getcwd(U_text, BLKSIZE);
		if (ptr != U_text)
		{
			strcpy(U_text, ptr);
		}

		/* append a slash to the directory name */
		len = strlen(U_text);
		U_text[len++] = SLASH;

		/* append the filename, padded with heaps o' NULs */
		strncpy(U_text + len, *name ? name : "foo", BLKSIZE - len);
	}
#endif
	else
	{
		/* copy the filename into U_text */
		strncpy(U_text, *name ? name : "foo", BLKSIZE);
	}

	if (tmpfd >= 0)
	{
		/* write the name out to second block of the temp file */
		lseek(tmpfd, (long)BLKSIZE, 0);
		write(tmpfd, U_text, (unsigned)BLKSIZE);
	}
	return 0;
}



/* This function handles deadly signals.  It restores sanity to the terminal
 * preserves the current temp file, and deletes any old temp files.
 */
int deathtrap(sig)
	int	sig;	/* the deadly signal that we caught */
{
	char	*why;

	/* restore the terminal's sanity */
	endwin();

#ifdef CRUNCH
	why = "-Elvis died";
#else
	/* give a more specific description of how Elvis died */
	switch (sig)
	{
# ifdef SIGHUP
	  case SIGHUP:	why = "-the modem lost its carrier";		break;
# endif
# ifndef DEBUG
#  ifdef SIGILL
	  case SIGILL:	why = "-Elvis hit an illegal instruction";	break;
#  endif
#  ifdef SIGBUS
	  case SIGBUS:	why = "-Elvis had a bus error";			break;
#  endif
#  if defined(SIGSEGV) && !defined(TOS)
	  case SIGSEGV:	why = "-Elvis had a segmentation violation";	break;
#  endif
#  ifdef SIGSYS
	  case SIGSYS:	why = "-Elvis munged a system call";		break;
#  endif
# endif /* !DEBUG */
# ifdef SIGPIPE
	  case SIGPIPE:	why = "-the pipe reader died";			break;
# endif
# ifdef SIGTERM
	  case SIGTERM:	why = "-Elvis was terminated";			break;
# endif
# if !MINIX
#  ifdef SIGUSR1
	  case SIGUSR1:	why = "-Elvis was killed via SIGUSR1";		break;
#  endif
#  ifdef SIGUSR2
	  case SIGUSR2:	why = "-Elvis was killed via SIGUSR2";		break;
#  endif
# endif
	  default:	why = "-Elvis died";				break;
	}
#endif

	/* if we had a temp file going, then preserve it */
	if (tmpnum > 0 && tmpfd >= 0)
	{
		close(tmpfd);
		sprintf(tmpblk.c, "%s \"%s\" %s", PRESERVE, why, tmpname);
		system(tmpblk.c);
	}

	/* delete any old temp files */
	cutend();

	/* exit with the proper exit status */
	exit(sig);
}
