/*
 * BASIC by Phil Cockcroft
 */
#include        "bas.h"

#ifndef	MSDOS
#ifndef	FD_CLOEXEC
#include <sys/ioctl.h>
#endif
#endif

#include <sys/stat.h>

/*
 *      This file contains all the routines to implement terminal
 *    like files.
 */
#ifndef	O_RDWR
#define	O_RDWR		2
#endif
#ifndef	O_RDONLY
#define	O_RDONLY	0
#endif
#ifndef	O_WRONLY
#define	O_WRONLY	1
#endif
 
#if BLOCKSIZ > MAX_STR
#define	MAX_BLOCKSIZ BLOCKSIZ
#else
#define	MAX_BLOCKSIZ MAX_STR
#endif

/*
 *      setupfiles is called only once, it finds out how many files are
 *    required and allocates buffers for them. It will also execute
 *    'silly' programs that are given as parameters.
 */

#ifdef	__STDC__
static	void	runfile(int), f_flush(filebufp);
static	void	close_1(filebufp);
static	void	blrset(int);
static	void	bfdcheck(int);
#else
static	void	runfile(), f_flush();
static	void	close_1();
static	void	blrset();
static	void	bfdcheck();
#endif

void
setupfiles(argc,argv)
char    **argv;
{
	register char    *q;
	register int    fp;
	register int     nfiles=MAXFILES;

#ifdef  NOEDIT
	noedit=1;
#endif
	while(argc > 1 ){
		q = *++argv;
		if(*q++ !='-')
			break;
		if(ispnumber(q)){
			nfiles= atoi(q);
			if(nfiles<0 || nfiles > MAXFILES)
				nfiles=MAXFILES;
		}
		else if(*q=='x')
			noedit=1;
		else if(*q=='e')
			noedit=0;
		argc--;
	}
	maxfiles = nfiles;
	ncurfiles = 0;
		/* code added to execute silly programs */
	if(argc <= 1)
		return;
	if((fp=open(*argv,O_RDONLY))!=-1)
		runfile(fp);
	prints("file not found\n");
	_exit(1);
}

/*
 *      This routine executes silly programs. It has to load up
 *    the program and then simulate the environment as is usually seen
 *    in main. It works....
 */

static	void
runfile(fp)
int	fp;
{
	static	int    firsttime = 1; /* flag to say that we are just loading */
	register lpoint p;

	setupmyterm();          /* set up terminal - now done after files */
	program = 0;
	if(setexit() == ERR_RESET){	/* the file at the moment */
		drop_fns();
		execute();
	}
	if(!firsttime)          /* an error or cntrl-c */
		VOID quit();
	firsttime=0;
	readfi(fp, (lpoint)0, 0);
	clear();
	lp_fd = -1;
	p= program;	/* is this needed - yes */
	if(!p)
		VOID quit();
	stocurlin=p;
	point= p->lin;
	elsecount=0;
			/* go and run it */
	execute();
}

/* commands implemented are :-
	open / creat
	close
	input
	print
*/

/* syntax of commands :-
	open "filename" for input as <filedesc>
	open "filename" [for output] as <filedesc>
	close <filedesc> ,[<filedesc>]
	input #<filedesc> , v1 , v2 , v3 ....
	print #<filedesc> , v1 , v2 , v3 ....
	*/

/* format of file buffers    added 17-12-81
	struct  {
		int     filedes;        / * Unix file descriptor
		int     userfiledes;    / * name by which it is used
		int     posn;           / * position of cursor in file
		int     dev;            / * dev and inode are used to
		int     inode;          / * stop r/w to same file
		int     use;            / * r/w etc. + other info
		int     nleft;          / * number of characters in buffer
		char    buf[BLOCKSIZ];  / * the actual buffer
		} file_buffer ;

	The file_buffers are stored between the end of initialised data
      and fendcore. uses sbrk() at start up.

	At start up there are two buffer spaces allocated.
*/

/*
 *      The 'open' command it allocates file descriptors and buffer
 *    space then sets about opening the file and checking weather the
 *    the file is opened already and then checks to see if that file
 *    was opened for reading or writing.  It stops files being read and
 *    written at the same time
 */

#ifndef __FreeBSD__
long	lseek();
	/* To phil C		phil@gmrs.isar.de
	   From Julian S	jhs@freebsd.org
	   Date 950813
	FreeBSD current has 
	off_t    lseek __P((int, off_t, int));
	& reports
	/usr/include/unistd.h:82: previous declaration of `lseek'
	however you might want a more general ifndef BSD or similar perhaps ?
	*/
#endif

int
bfopen()
{
	register struct filebuf *p;
	register struct filebuf *q;
	register int     c;
	itype	i;
	int     append=0;
	itype   bl = 0;
	int     mode= _READ;
	struct  stat    inod;
	int	type_set = 0;
	int	is_random = 0;
	int	bsize = BLOCKSIZ;
	STR	st;

	st = stringeval();
	NULL_TERMINATE(st);
	c=getch();
	if(c== FOR){
		type_set = 1;
		c=getch();
		if(c== OUTPUT)
			mode = _WRITE;
		else if(c== APPEND){
			append++;
			mode = _WRITE;
		}
		else if(c== TERMINAL)
			mode = _TERMINAL;
		else if(c == RANDOM){
			bl = BLOCKSIZ;
			is_random++;
		}
		else if(c != INPUT)
			error(SYNTAX);
		c=getch();
	}
	if(c!= AS)
		error(SYNTAX);
	i=evalint();
	if(i<1 || i>MAXFILES)
		error(29);
	if(getch() == ','){
		if(getch() != RECORDSIZ)
			error(SYNTAX);
		bl = evalint();
		/*
		 * We should be able to have a blocked file with
		 * a string that is at maximum length mapping it.
		 */
		if(bl <= 0 ||  bl > MAX_BLOCKSIZ)
			error(10);
		bsize = bl;
	}
	else
		point--;
	check();

/* here we have mode set. i is the file descriptor 1-9
   now check to see if already allocated then allocate the descriptor
   and open file etc. */

	bfdcheck(i);

	if(ncurfiles >= maxfiles)	/* out of file descriptors */
		error(31);
	p = (filebufp)mmalloc((ival)(sizeof(struct filebuf) + bsize));
	p->bufsiz = bsize;
	p->filedes=0;
	p->userfiledes=0;
	p->use=0;
	p->nleft=0;
	p->next = filestart;
	filestart = p;
	ncurfiles++;

/*   code to check to see if file is open twice */

	if(stat(st->strval,&inod)!= -1){
		if( (inod.st_mode & S_IFMT) == S_IFDIR)
			if(mode== _READ )  /* cannot deal with directories */
				error(15);
			else
				error(14);
		for(q = filestart ; q ; q = q->next)
			if(q->userfiledes && q->inodnumber== inod.st_ino &&
						q->device== inod.st_dev){
				if(mode== _READ ){
					if( q->use & mode )
						break;
					error(15);
				}
				else
					error(14);
			}
	}

	if((!type_set && bl) || is_random){
#ifdef	O_CREAT
		p->filedes = open(st->strval, O_CREAT|O_RDWR, 0644);
		if(p->filedes < 0)
			error(15);
#else
		if( (p->filedes = open(st->strval, O_RDWR)) < 0){
			if((p->filedes = creat(st->strval, 0644)) < 0)
				error(15);
			(void) close(p->filedes);
			if( (p->filedes = open(st->strval, O_RDWR)) < 0)
				error(15);
		}
#endif
		mode = _READ|_WRITE;
	}
	else if(mode == _READ){
		if( (p->filedes=open(st->strval, O_RDONLY))== -1)
			error(15);
	}
	else  if(mode == _TERMINAL){
		if(bl)
			error(15);
		if((p->filedes = open(st->strval, O_RDWR)) == -1)
			error(15);
		mode |= _READ | _WRITE;
	}
	else  {
		if(append){
			p->filedes=open(st->strval, O_WRONLY);
			VOID lseek(p->filedes, 0L, 2);
		}
		if(!append || p->filedes== -1)
			if((p->filedes=creat(st->strval, 0644))== -1)
				error(14);
	}
	FREE_STR(st);
	p->posn = 0;
	VOID fstat(p->filedes,&inod);
#ifdef	FD_CLOEXEC
	VOID fcntl(p->filedes, F_SETFD, FD_CLOEXEC);
#else
#ifdef  FIOCLEX
	VOID ioctl(p->filedes, FIOCLEX, 0);  /* close on exec */
#endif
#endif
	p->device= inod.st_dev;         /* fill in all relevent details */
	p->inodnumber= inod.st_ino;
	p->userfiledes= (short)i;
	if(bl)
		mode |= _BLOCKED;
	p->nleft=0;
	p->use = (short)mode;
	normret;
}

static	void
bfdcheck(userfd)
int	userfd;
{
	register filebufp p;
	register filebufp q = 0;	/* only for lint */

	for(p = filestart ; p ; p = q){
		q = p->next;
		if(p->userfiledes == 0){
			p->use = 0;
			p->filedes = -1;
			close_1(p);
		} else if(userfd == p->userfiledes)
			error(29);
	}
}

/*      the 'close' command it runs through the list of file descriptors
 *    and flushes all buffers and closes the file and clears all
 *    relevent entry in the structure
 */

int
fclosef()
{
	register int	c;

	c = getch();
	point--;
	if(istermin(c)){
		closeall();
		normret;
	}
	do{
		close_1(getf(evalint(),(_READ | _WRITE) ));
	} while(getch() == ',');
	point--;
	normret;
}

static	void
close_1(p)
register filebufp p;
{
	register filebufp op;

	if(p->use & _BLOCKED)
		kill_fstrs(p->buf, p->buf + p->bufsiz);
	else if(p->use & _WRITE )
		f_flush(p);
	if(p->filedes != -1)
		VOID close(p->filedes);
	
	if(p == filestart)
		filestart = p->next;
	else {
		for(op = filestart ; op ; op = op->next)
			if(op->next == p)
				break;
		if(!op)		/* a can't happen */
			return;
		op->next = p->next;
	}
	ncurfiles--;
	mfree( (MEMP)p);
}

/*      the 'eof' maths function eof is true if writting to the file
 *    or if the _EOF flag is set.
 */

void
eofl()
{
	register struct filebuf *p;
	struct	stat	statbuf;

	p=getf(evalint(),(_READ | _WRITE) );
	vartype= IVAL;
	if(p->use & _EOF){
		res.i = -1;
		return;
	}
	res.i =0;
	if( (p->use & (_BLOCKED|_READ)) == (_BLOCKED|_READ)){
		VOID fstat(p->filedes, &statbuf);
		if(lseek(p->filedes, 0L, 1) <= statbuf.st_size - p->bufsiz)
			return;
	}
	else if((p->use & _WRITE) == 0){
		if(p->nleft)
			return;
		p->posn = 0;
		if( (p->nleft= read(p->filedes,p->buf,p->bufsiz)) > 0)
			return;
		p->nleft=0;
	}
	p->use |= _EOF;
	res.i = -1;
}

/*      the 'posn' maths function returns the current 'virtual' cursor
 *    in the file. If the file descriptor is zero then the screen
 *    cursor is accessed.
 */

void
fposn()
{
	register struct filebuf *p;
	register itype	i;

	i=evalint();
	vartype= IVAL;
	if(!i){
		res.i = (itype)cursor;
		return;
	}
	p=getf( (ival)i,(_READ | _WRITE) );
	if(p->use & _WRITE)
		res.i = (itype)p->posn;
	else
		res.i = 0;
}

/*      getf() returns a pointer to a file buffer structure. with the
 *    relevent file descriptor and with the relevent access permissions
 */

struct  filebuf *
getf(i,j)
register ival	i;     /* file descriptor */
register int	j;     /* access permission */
{
	register struct filebuf *p;

	if(i == 0)
		error(29);
	j &= ( _READ | _WRITE ) ;
	for(p= filestart ; p ; p = p->next)
		if(p->userfiledes==i && ( p->use & j) )
			return(p);
	error(29);      /* unknown file descriptor */
	return(0);	/* not reached */
}

/*      flushes the file pointed to by p */

static	void
f_flush(p)
register struct filebuf *p;
{

	if(p->nleft){
		if(write(p->filedes,p->buf,p->nleft) != p->nleft){
			p->nleft = 0;
			c_error(60);
		}
		p->nleft=0;
	}
}

/*      will flush all files , for use in 'shell' and in quit */

void
flushall()
{
	register struct filebuf *p;
	for(p = filestart ; p ; p = p->next)
		if(p->nleft && ( p->use & _WRITE ) ){
			if(write(p->filedes,p->buf,p->nleft) != p->nleft){
				p->nleft = 0;
				c_error(60);
			}
			p->nleft=0;
		}
}

/*      closes all files and clears the relevent bits of info
 *    used in clear and new.
 */

void
closeall()
{
	register struct filebuf *p;

	flushall();
	while( (p = filestart) != 0)
		close_1(p);
}

/*      write to a file , same as write in parameters (see print )
 */

int
putfile(p,q,i)
register struct filebuf *p;
CHAR   *q;
register int     i;
{
	register ival	j;

	if(i <= 0)
		return(0);
	do {
		j = p->bufsiz - p->nleft;
		if(j >= i)
			j = i;
		if(!j){
			f_flush(p);
			continue;
		}
		VOID strmov(p->buf + p->nleft, q, j);
		p->nleft += j;
		q += j;
	}while( (i -= j) > 0);
	if(p->use & _TERMINAL)
		f_flush(p);
	return(0);
}

/* gets a line into q (MAX 512 or j) from file p terminating with '\n'
 * or _EOF returns number of characters read.
 */

int
fin1ch(p)
register filebufp p;
{
	if(p->use & _TERMINAL)          /* kludge for terminal files */
		p->use &= ~_EOF;
	else if(p->use & _EOF)
		error(30);              /* end of file */

	if(p->use & _BLOCKED)
		error(29);

	if(!p->nleft){
		p->posn = 0;
		if( (p->nleft= read(p->filedes,p->buf,p->bufsiz)) <=0){
			p->nleft=0;     /* a read error */
			p->use |= _EOF; /* or end of file */
			error(30);
			/*NOTREACHED*/
		}
	}
	p->nleft--;
	return(UC(p->buf[p->posn++]));
}

/*
 * random file access mechanism
 */

#define	SPAD	' '

int
blset()
{
	blrset(1);
	normret;
}

int
brset()
{
	blrset(0);
	normret;
}

static	void
blrset(pad)
int	pad;
{
	register stringp sp;
	register STR	st;
	register ival	i;

	sp = (stringp)getname(0);
	if(vartype != SVAL)
		error(2);
	if( (curentry->flags & IS_FSTRING) == 0)
		error(23);
	if(getch() != '=')
		error(4);
	st = stringeval();
	check();
	i = sp->len - st->strlen;
	if(i > 0){
		if(!pad){
			set_mem(sp->str, i, SPAD);
			VOID strmov(sp->str + i, st->strval, st->strlen);
		}
		else
			set_mem(strmov(sp->str,st->strval,st->strlen), i, SPAD);
	}
	else
		VOID strmov(sp->str, st->strval, sp->len);
	FREE_STR(st);
}

int
bfield()
{
	filebufp fp;
	struct	entry	*ep;
	register stringp	sp;
	register itype	il;
	ival	bsiz = 0;
	register CHAR	*p;

	if(getch() != '#')
		error(SYNTAX);
	fp = getf(evalint(), (_READ|_WRITE));
	if((fp->use & _BLOCKED) == 0)
		error(29);
	if(getch() != ',')
		error(SYNTAX);
	p = fp->buf;
	do{
		il = evalint();
		if(il <= 0 || il > MAX_STR)
			error(10);
		if(il + bsiz > fp->bufsiz)
			error(12);
		if(getch() != AS)
			error(SYNTAX);
		sp = (stringp)getname(0);
		if(vartype != SVAL || (ep = curentry)->dimens ||
			(ep->flags & (IS_LOCAL|IS_COMMON)) != 0)
			error(2);
		/*
		 * magic time
		 */
		if( (ep->flags & IS_FSTRING) == 0 && sp->str != 0)
			mfree( (MEMP)sp->str);
		ep->flags |= IS_FSTRING;
		sp->str = p;
		set_mem(p, (ival)il, SPAD);
		sp->len = il;
		p += il;
		bsiz += il;
	}while(getch() == ',');
	point--;
	normret;
}

int
bput()
{
	register filebufp fp;
	register itype	pos = -1;
	register int	c;

	if(getch() != '#')
		error(SYNTAX);
	fp = getf(evalint(), _WRITE);
	if((fp->use & _BLOCKED) == 0)
		error(52);

	if( (c = getch()) == ',' || c == RECORD){
		if(c == ',' && getch() != RECORD)
			point--;
		pos = evalint();
		if(pos < 0)
			pos = 0;
	}
	else
		point--;
	check();


	fp->nleft = fp->bufsiz;
	if(pos >= 0)
		VOID lseek(fp->filedes, (long)pos * fp->bufsiz, 0);
	fp->use &= ~_EOF;
	f_flush(fp);
	normret;
}

int
bget()
{
	register filebufp fp;
	register ival	pos = -1;
	register int	c;

	if(getch() != '#')
		error(SYNTAX);
	fp = getf(evalint(), _READ);
	if((fp->use & _BLOCKED) == 0)
		error(52);
	if((c = getch()) == ',' || c == RECORD){
		if(c == ',' && getch() != RECORD)
			point--;
		pos = evalint();
		if(pos <= 0)
			pos = 0;
	}
	else
		point--;
	check();
	if(pos >= 0){
		VOID lseek(fp->filedes, (long)pos * fp->bufsiz, 0);
		fp->use &= ~_EOF;
	}
	else if(fp->use & _EOF)
		error(30);
	if(read(fp->filedes,fp->buf,fp->bufsiz) != fp->bufsiz){
		fp->use |= _EOF;
		error(30);
	}
	normret;
}


syntax highlighted by Code2HTML, v. 0.9.1