/**************************************************************
 *
 *	CRISP - Custom Reduced Instruction Set Programmers Editor
 *
 *	(C) Paul Fox, 1989
 *
 *    Please See COPYRIGHT notice.
 *
 **************************************************************/

# include	"list.h"
# include	"gdir.h"
# include	<errno.h>

static DIR	*dirp = NULL;	/* Used by find_file & file_pattern */
static	char	file_buf[DIRSIZ+1];	/* Pattern to match.	*/
static	char	dir_buf[128];		/* Directory name for above. */

/**********************************************************************/
/*   Prototypes							      */
/**********************************************************************/
char *strtok();
void	edit_file2 PROTO((char *, int));
char	*filename();
char	*bname();
char	*strrchr();
void	output_file();
int	readin();
int	writeout();
int	rdonly();
int	fbackupfile();
int	copy_file();
void	set_backup();
void	file_pattern();
int	stat();

extern	int do_backups;
extern  int current_umask;

void
output_file()
{	WINDOW	*wp;
	BUFFER	*bp;
	char	buf[BUFSIZ];
	char	*cp = get_arg1("Enter new output file name: ", buf, sizeof buf);
	char	*fname;

	acc_assign_int((long) -1L);
	if (cp == NULL)
		return;
	fname = filename(cp);

	for (bp = bheadp; bp; bp = bp->b_bufp)
		if (bp != curbp &&
		    strcmp(bp->b_fname, fname) == 0) {
			errorf("Duplicate buffer name.");
			return;
			}
	chk_free((void *) curbp->b_fname);
	curbp->b_fname = strdup(fname);
	for (wp = wheadp; wp; wp = wp->w_wndp)
		if (wp->w_bufp == curbp) {
			w_title(wp, bname(fname), "");
			wp->w_flag |= WFHARD;
			}
	update();
	acc_assign_int((long) 1L);
}
void
read_file()
{	char	buf[BUFSIZ];
	char	*cp = get_arg1("File to read: ", buf, sizeof buf);
	int	perms_mode = curbp->b_mode;
	int	read_only = curbp->b_flag & BFRO;
	char **files;
	int	j;
	long	val = 0;

	acc_assign_int(0L);

	if (cp == NULL || rdonly())
		return;
	if ((files = shell_expand(cp)) == NULL) {
		errorf("Name expansion error.");
		return;
		}
	for (j = 0; files[j]; j++) {
		if (files[j][0])
			val = insertfile(curbp, filename(files[j]), FALSE, EDIT_NORMAL);
		chk_free(files[j]);
		}
	acc_assign_int(val);
	chk_free((void *) files);
	curbp->b_mode = perms_mode;
	curbp->b_flag &= ~BFRO;
	curbp->b_flag |= read_only;
	
	/***********************************************/
	/*   If  we've  got  a  window, then recenter  */
	/*   the screen.			       */
	/***********************************************/
	if (curwp && curwp->w_bufp == curbp) {
		argv[1].l_flags = F_INT;
		argv[1].l_int = *cur_line - curwp->w_h / 2;
		set_top_left();
		}
}

int
readin(bp, fname, flags)
BUFFER	*bp;
char *fname; 
int	flags;
{
	register int            status;
	register WINDOW         *wp;

	if (bclear(bp) != TRUE)
		return TRUE;
	status = insertfile(bp, fname, TRUE, flags);
	bp->b_flag &= ~BFCHG;
	bp->b_flag |= BFREAD;
	if (status == 0)
		bp->b_flag |= BFBAK;
	for (wp = wheadp; wp; wp = wp->w_wndp) {
		if (wp->w_bufp == bp) {
			wp->w_top_line = 1;
			wp->w_line  = 1;
			wp->w_col  = 1;
			}
		}
	return status;
}
int
insertfile(bp, fname, inserting, flags)
BUFFER	*bp;
char 	*fname;
int	inserting;
int	flags;
{	extern int startup_flag;
	extern int rdonly_mode;
	
	register WINDOW *wp;
	BUFFER          *saved_bp = curbp;
	struct stat sbuf;
	int	fd;

	bp->b_mode = 0666;			/* Default mode for new files*/

	if ((fd = open(fname, OPEN_R_BINARY | O_RDONLY)) < 0) {
		/***********************************************/
		/*   Cause global errno to be set.	       */
		/***********************************************/
		system_call(-1);
		if ((curbp->b_flag & BF_SYSBUF) == 0) {
# if !CR_DOS
			extern int sys_nerr;
			extern char *sys_errlist[];
# endif
			if (inserting && errno == ENOENT) {
				if (!startup_flag)
					infof("New file (unable to open %s).", bname(fname));
				curbp->b_flag |= BF_NEW_FILE;
				}
			else
				infof("%s: %s", (errno < 0 || errno >= sys_nerr) ?
					"Unknown error" : sys_errlist[errno], bname(fname));
			}			
		return -1;
		}
	stat(fname, &sbuf);
	
	bp->b_mode = sbuf.st_mode;
	bp->b_mtime = sbuf.st_mtime;

	if (sbuf.st_mode & S_IEXEC)
		bp->b_flag |= BFEXEC;
	
	curbp = bp;
	set_hooked();
	lreadin_file(fd, sbuf.st_size, fname, flags);
	close(fd);
	if ((sbuf.st_mode & S_IWRITE) == 0 || rdonly_mode)
		bp->b_flag |= BFRO;
	bp->b_flag |= BFCHG;

	for (wp=wheadp; wp!=NULL; wp=wp->w_wndp)
		if (wp->w_bufp == curbp)
			wp->w_flag |= WFHARD;
	curbp = saved_bp;
	return 0;
}
/**********************************************************************/
/*   Converts a filename into a buffer name.			      */
/**********************************************************************/
char	*
bname(name)
char	*name;
{
	char	*cp1 = strrchr(name, '/');
	if (cp1)
		return cp1+1;
# if	defined(VMS)
	if ((cp1 = strrchr(name, ']')) != NULL)
		return cp1+1;
	if ((cp1 = strrchr(name, ':')) != NULL)
		return cp1+1;
# endif
	return name;
}
/**********************************************************************/
/*   Primitive  to  write a buffer away. Also called on exit. Return  */
/*   -1 if any errors occur so that the exit can be avoided.	      */
/**********************************************************************/
int
write_buffer() 
{	char	*fnam = argv[1].l_flags == F_NULL 
			? curbp->b_fname : get_str(1);
	int	append = argv[2].l_flags == F_INT ? argv[2].l_int : 0;
	register int    s;

	acc_assign_int((long) 0L);
	if (rdonly())
		return 0;

	if (argv[1].l_flags == NULL && curbp->b_anchor) {
		write_block();
		return 0;
		}

	/***********************************************/
	/*   Dont  save  file  if its a normal buffer	*/
	/*   and  there  arent  any changes; if its a	*/
	/*   system  buffer  save  it anyway, because	*/
	/*   we  dont  keep  track  of 'b_nummod' for	*/
	/*   system buffers.				*/
	/***********************************************/
	if (curbp->b_nummod == 0 && 
	    (curbp->b_flag & (BF_SYSBUF|BF_NEW_FILE)) == 0) {
		infof("File has not been modified -- not written.");
		return 0;
		}
	if (*fnam == NULL) {
		acc_assign_int((long) -1L);
		infof("No file name");
		return -1;
	}
	/***********************************************/
	/*   Turn  off  new file flag as we write the  */
	/*   file  away,  otherwise  we  keep letting  */
	/*   user save the file.		       */
	/***********************************************/
	curbp->b_flag &= ~BF_NEW_FILE;
# if !defined(VMS)
	if (argv[1].l_flags == F_NULL && do_backups && curbp->b_flag & BFBAK) {
		s = fbackupfile(curbp->b_fname, curbp->b_mode);
		if (s == FALSE
		&& (s=eyesno("Backup error, save anyway")) != TRUE)
			return -1;
		}
# endif
	if ((s=writeout(curbp, fnam, argv[1].l_flags == F_NULL, append)) == TRUE) {
		if (argv[1].l_flags == F_NULL) {
			curbp->b_flag &= ~(BFCHG | BFBAK);
			curbp->b_nummod = 0;
			}
		return 0;
		}
	acc_assign_int((long) 0L);
	return -1;
}
int
writeout(bp, fn, undo_term, append) 
register BUFFER *bp; 
char *fn; 
int	undo_term;
int	append;
{
	register LINE   *lp;
	FILE	*fp;
	unsigned long	nlines = bp->b_numlines;
	unsigned long	line;
	BUFFER	*saved_bp = curbp;
	struct stat sbuf;
	int	need_cr = bp->b_flag & BF_CR_MODE;
	int	need_lf = (bp->b_flag & BFBINARY) == 0;
	
	/***********************************************/
	/*   If  file  has  been modified then take a  */
	/*   copy of the new permissions.	       */
	/***********************************************/
	if (stat(fn, &sbuf) >= 0)
		bp->b_mode = sbuf.st_mode;

	if ((fp = fopen(fn, append ? "a" : "w")) == NULL) {
		system_call(-1);
		errorf("Cannot write file: fn");
		return FALSE;
		}

	infof("Writing ...");
	curbp = bp;
	lp = linep(1);
	for (line = 1; line < bp->b_numlines; line++) {
		if (llength(lp)) {
			if (fwrite((char *) ltext(lp), llength(lp), 1, fp) != 1) {
				system_call(-1);
				errorf("File I/O error");
				(void) fclose(fp);
				return FALSE;
				}
			}
		if (need_cr)
			putc('\r', fp);
		if (need_lf)
			putc('\n', fp);
		percentage(line, nlines, "Writing", bp->b_fname);
		lp = lforw(lp);
		}
	(void) fclose(fp);
	infof("Write successful.");
	if (undo_term) {
		renumber_lines(bp);
		u_terminate();
		}
	(void) chmod(fn, bp->b_mode);
	curbp = saved_bp;
	return TRUE;
}
void
edit_file()
{
	char	buf1[256];
	register LIST	*lp = argv[1].l_list;
	LISTV	result;
	int	type;
	int	flags = EDIT_NORMAL;
	
	acc_assign_int(1L);
	if (argv[1].l_flags == F_NULL) {
		if (get_arg1("Edit file: ", buf1, sizeof buf1 - 1) == NULL) {
			struct stat sbuf;
			if (stat(curbp->b_fname, &sbuf) >= 0 && 
				(sbuf.st_mode & S_IWRITE) == 0)
				curbp->b_flag |= BFRO;
			else
				curbp->b_flag &= ~BFRO;
			acc_assign_int(0L);
			return;
			}
		edit_file2(buf1, flags);
		return;
		}
	for ( ; lp && *lp != F_HALT; lp = next_atom(lp)) {
		type = eval(lp, &result);
		switch (type) {
		  case F_INT:
		  	flags = result.l_int;
			break;
		  case F_STR:
		  case F_LIT:
		  	edit_file2(result.l_str, flags);
			break;
		  case F_RSTR:
		  	edit_file2(result.l_ref->r_ptr, flags);
			break;
		  }
		}
}
void
edit_file2(file_name, flags)
char	*file_name;
int	flags;
{
	char	buf[256];
	int j;
	char	*tmpf;
	char	**files;
	char	*cp, *cp1;

	strcpy(buf, file_name);
	cp1 = buf;
	while (cp1) {
		cp = strtok(cp1, " \t");
		cp1 = strtok((char *) NULL, "\n");
		if (cp == NULL || (files = shell_expand(cp)) == NULL) {
			acc_assign_int(0L);
			errorf("Name expansion error.");
			cp = cp1;
			continue;
			}
		for (j = 0; files[j]; j++) {
			if (files[j][0]) {
				tmpf = strdup(filename(files[j]));
				if (atomic_fileedit(tmpf, flags))
					acc_assign_int(0L);
				chk_free((void *) tmpf);
				}
			chk_free(files[j]);
			}
		chk_free((void *) files);
		cp = cp1;
		}
}
int
atomic_fileedit(fbuf, flags)
char	*fbuf;
int	flags;
{
	register BUFFER *bp;
	char	*cp;
	long	ret = 1;

	/***********************************************/
	/*   Get    buffer    pointer    to    buffer  */
	/*   containing   named  file.  If  we  don't  */
	/*   already  have  one  then  create  a  new  */
	/*   buffer.				       */
	/***********************************************/
	if ((bp = bfind(fbuf, TRUE)) == NULL) 
		return -1;
	showbuffer(bp, curwp);
	curbp = bp;
	if ((bp->b_flag & BFREAD) == 0) {
		int	saved_undo = bp->b_flag & BF_NO_UNDO;
		bp->b_flag |= BF_NO_UNDO;
		bp->b_flag |= BFREAD;
		if (readin(bp, fbuf, flags) < 0)
			ret = -1;
		renumber_lines(bp);
		bp->b_flag = (bp->b_flag & ~BF_NO_UNDO) | saved_undo;
		for (cp = fbuf+strlen(fbuf)-1; cp > fbuf; cp--)
			if (*cp == '.' || *cp == '/')
				break;
		if (cp <= fbuf || *cp != '.')
			cp = "default";
		/***********************************************/
		/*   Execute   a  macro  based  on  the  file  */
		/*   extension   to  set  options  e.g.  tabs  */
		/*   etc.  For  compatability  we  look for a  */
		/*   macro  called  '.c',  '.h'  etc. For the  */
		/*   new   version   these  macro  names  are  */
		/*   invalid,  so  we  will  look for a macro  */
		/*   called  '_extension'.  This  way  we can  */
		/*   keep  all  the tab settings together for  */
		/*   the properties macro.		       */
		/***********************************************/
		if (lookup_macro(cp))
			str_exec(cp);
		else
			str_exec("_extension");
		trigger(REG_NEW);
		}
	trigger(REG_EDIT);
	acc_assign_int(ret);
	return 0;
}
int
rdonly()
{
	if (curbp->b_flag & BFRO) {
		errorf("File is read only");
		return -1;
		}
	return 0;
}
int
second_passed()
{	long	time();
	static long last_time = 0;
	long tnow = time((long *) 0);

	if (tnow == last_time)
		return FALSE;
	last_time = tnow;
	return TRUE;
}
void
percentage(a, b, str, str1)
unsigned long	a, b;
char	*str, *str1;
{	static	long	pc = 0;
	static int counter;
	long	l = (a * 100) / b;
	char	buf[BUFSIZ];

	if (pc == l || l == 0)
		return;

	/***********************************************/
	/*   If  the  percentage  is changing quickly  */
	/*   then  try  and avoid slugging the system  */
	/*   by  calling  the  time()  system call in  */
	/*   second_passed().			       */
	/***********************************************/
	if (counter++ & 127)
		return;
	if (second_passed() == FALSE)
		return;
	pc = l;
	sprintf(buf, "[%s %%s%%s: %ld%%%% done]", str, pc);
	infof_truncated(buf, str1);
}

/**********************************************************************/
/*   Function  called  to  remove  the  named  file.  The file isn't  */
/*   actually  removed.  If  the  BVERSION  environment  variable is  */
/*   set,  then  we  keep  that  number of old versions of the file,  */
/*   and  remove  the  last  one.  If  BVERSION  is  not set then we  */
/*   simply  delete  the  old  file,  depending on remove_flag. This  */
/*   attempts  to  emulate  VMS  version  feature  but  using static  */
/*   version numbers.						      */
/**********************************************************************/
void
backup_level(file_name, remove_flag)
char	*file_name;
int	remove_flag;
{
# if	defined(VMS)
	if (remove_flag)
		unlink(file_name);
	return;
# else
	char	*last_part;
	char	*cp;
	int	bversion = 0;
	int	i;
	char	tmp1[BUFSIZ];
	char	tmp2[BUFSIZ];

	cp = ggetenv("BVERSION");
	if (cp == NULL || (bversion = atoi(cp)) <= 1) {
		if (remove_flag)
			unlink(file_name);
		return;
		}
		
	/***********************************************/
	/*   There MUST be a '/' in the filename.      */
	/***********************************************/
	last_part = strrchr(file_name, '/');
	i = last_part - file_name;

	/***********************************************/
	/*   Copy .../8/file to .../9/file, etc.       */
	/***********************************************/
	strcpy(tmp1, file_name);
	strcpy(tmp2, file_name);
	while (bversion-- > 0) {
		int	j;
		if (bversion == 0)
			sprintf(tmp1 + i, "/%s", last_part + 1);
		else
			sprintf(tmp1 + i, "/%d/%s", bversion - 1, last_part + 1);
		sprintf(tmp2 + i, "/%d", bversion);
		j = strlen(tmp2);
		sprintf(tmp2 + j, "/%s", last_part + 1);
		unlink(tmp2);
		/***********************************************/
		/*   Move  file  from  one dir to another. If  */
		/*   we  fail  because the dir doesn't exist,  */
		/*   try and make it.			       */
		/***********************************************/
		if (rename(tmp1, tmp2) < 0 && errno == ENOENT) {
			tmp2[j] = 0;
			mkdir(tmp2, 0755);
			sprintf(tmp2 + i, "/%d/%s", bversion, last_part + 1);
			rename(tmp1, tmp2);
			}
		}
# endif
}
/*
 * Rename the file "fname" into a backup
 * copy. 
 */
int
fbackupfile(fname, perms)
char    *fname;
int	perms;
{
	char	nname[BUFSIZ];
	char	*cp;
	char	*np;
	char	*file_name;
	struct stat stat_buf;

	file_name = strrchr(fname, '/');
	if (file_name)
		file_name++;
	else
		file_name = fname;

	/*-----------------------------------------
	 *   If file has more than one link to it,
	 *   dont try and link the file to the backup
	 *   directory otherwise we lose the link information
	 *   and confuse the user.
	 *-----------------------------------------*/
	if (stat(fname, &stat_buf) < 0)
		stat_buf.st_nlink = 1+1;
	if (stat_buf.st_nlink == 1) {
		/*-----------------------------------------
	 	*   See if we can link file to backup
	 	*   directory.
	 	*-----------------------------------------*/
		for (cp = ggetenv("BBACKUP"); cp && *cp; ) {
			for (np = nname; *cp && *cp != ';' && *cp != ':'; )
				*np++ = *cp++;
			*np++ = '/';
			if (*cp)
				cp++;
			strcpy(np, file_name);
			backup_level(nname, TRUE);
			if (link(fname, nname) >= 0 && unlink(fname) >= 0)
				return TRUE;
			}
		}
	/*-----------------------------------------
	 *   See if we can copy file to backup
	 *   directory.
	 *-----------------------------------------*/
	for (cp = ggetenv("BBACKUP"); cp && *cp; ) {
		for (np = nname; *cp && *cp != ';' && *cp != ':'; )
			*np++ = *cp++;
		*np++ = '/';
		if (*cp)
			cp++;
		strcpy(np, file_name);
		backup_level(nname, FALSE);
		if (copy_file(fname, nname, perms) == FALSE)
			continue;
		return TRUE;
		}
		
	/***********************************************/
	/*   Copying   file   to   backup   directory  */
	/*   failed   (maybe  because  directory  not  */
	/*   writable  or  BBACKUP not set. So now we  */
	/*   try   the  old  way  of  adding  a  .bak  */
	/*   extension.				       */
	/***********************************************/
	strcpy(nname, fname);
# if defined(ONLY_ONE_EXTENSION)
	if ((cp = strrchr(nname, '.')) != NULL)
		*cp = NULL;
# endif
	strcat(nname, ".bak");
# if defined(MSDOS)
	return copy_file(fname, nname, perms);
# else
	if (stat_buf.st_nlink != 1)
		return copy_file(fname, nname, perms);
	(void) unlink(nname);                   /* Ignore errors.       */

	/* no rename on System V, so do it dangerous way. */
	if ( link(fname, nname) < 0 || unlink(fname) < 0 )
		return (FALSE);
	return (TRUE);
# endif
}
int
copy_file(fname, nname, perms)
char	*fname;
char	*nname;
int	perms;
{	int	fd1, fd2;
	char	buf[BUFSIZ];
	int	n;

	if ((fd1 = open(nname, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0)
		return FALSE;
	if ((fd2 = open(fname, O_RDONLY)) < 0) {
		close(fd1);
		unlink(nname);
		return TRUE;
		}
	chmod(nname, perms);
	while ((n = read(fd2, buf, sizeof buf)) > 0)
		write(fd1, buf, n);
	close(fd1);
	close(fd2);
	return TRUE;
}
void
set_backup()
{
	acc_assign_int((long) do_backups);
	if (argv[1].l_flags == F_NULL)
		do_backups = !do_backups;
	else
		do_backups = argv[1].l_int;
	infof("Backups will %sbe created.", do_backups ? "" : "not ");
}
void
find_file()
{
	struct dirent	*de;
	struct stat stat_buf;
	char	buf[256];
	
	acc_assign_int(0L);
	if (dirp == NULL)
		return;

	while ((de = readdir(dirp)) != (struct dirent *) NULL) {
		if (!wild_match(de->d_name, file_buf))
			continue;
		strcpy(buf, dir_buf);
		strcat(buf, de->d_name);
		stat(buf, &stat_buf);
		if (argv[1].l_flags != F_NULL)
			str_assign(argv[1].l_sym, de->d_name);
		argv_assign(2, (long) stat_buf.st_size);
		argv_assign(3, (long) stat_buf.st_mtime);
		argv_assign(4, (long) stat_buf.st_ctime);
		argv_assign(5, (long) stat_buf.st_mode);
		acc_assign_int(1L);
		return;
		}
}
void
file_pattern()
{	char	*cp = get_str(1);
	char	*filename;
	char	*strrchr();
	char	buf[BUFSIZ];
	
	while (*cp == ' ')
		cp++;
	strncpy(buf, cp, sizeof buf - 1);
	cp = buf;
	if (dirp)
		closedir(dirp);
		
	/***********************************************/
	/*   Isolate directory name.		       */
	/***********************************************/
	strncpy(file_buf, cp, sizeof file_buf - 1);
	filename = strrchr(cp, '/');
	if (filename == NULL) {
		dirp = opendir(".");
		filename = cp;
# if CR_DOS
		if (cp[0] && cp[1] == ':')
			filename += 2;
# endif
		strcpy(dir_buf, "./");
		}
	else {
		*filename++ = NULL;
		dirp = opendir(cp);
		strcpy(dir_buf, cp);
		strcat(dir_buf, "/");
		}
	strncpy(file_buf, filename, sizeof file_buf - 1);
}
void
do_access()
{
	long	ret;

	ret = (long) system_call(access(get_str(1), argv[2].l_int));
	acc_assign_int(ret);
}
void
do_mkdir()
{	int	mode = 0755;
	int	ret;

	if (argv[2].l_flags == F_INT)
		mode = (int) argv[2].l_int;
		
	ret = system_call(mkdir(get_str(1), mode));
	acc_assign_int(ret);
}
void
do_rename()
{	int	ret;

	ret = rename(get_str(1), get_str(2));
	system_call(ret);
	acc_assign_int((long) ret);
}
void
do_chmod()
{	int	ret;

	ret = chmod(argv[1].l_int, get_str(2));
	system_call(ret);
	acc_assign_int((long) ret);
}
void
do_umask()
{
	if (argv[1].l_flags == F_INT) {
		current_umask = (int) argv[1].l_int;
		umask(current_umask);
		}
	acc_assign_int((long) current_umask);
}
