/****************************************************************************/
/* Copyright 2003 MBARI														*/
/****************************************************************************/
/* Summary	: File I/O Routines for OASIS5 using FatFs on PIC32				*/
/* Filename : file.c														*/
/* Author	: Robert Herlien (rah)											*/
/* Project	: OASIS Mooring Replacement (OASIS3)							*/
/* Revision : 1.0															*/
/* Created	: 07/11/2016													*/
/*																			*/
/* MBARI provides this documentation and code "as is", with no warranty,	*/
/* express or implied, of its quality or consistency. It is provided without*/
/* support and without obligation on the part of the Monterey Bay Aquarium	*/
/* Research Institute to assist in its use, correction, modification, or	*/
/* enhancement. This information should not be published or distributed to	*/
/* third parties without specific written permission from MBARI.			*/
/*																			*/
/****************************************************************************/
/* Modification History:													*/
/* 30jan2003 rah - created OASIS3 version									*/
/* 11jul2016 rah - created OASIS5 version from OASIS3 version				*/
/* $Log$
 */
/****************************************************************************/

#include <mbariTypes.h>			/* MBARI type definitions				*/
#include <mbariConst.h>			/* MBARI constants						*/
#include <oasis.h>				/* OASIS controller definitions			*/
#include <file.h>				/* OASIS File I/O Routines				*/
#include <otask.h>				/* OASIS task dispatcher				*/
#include <utils.h>				/* OASIS utility functions				*/
#include <script.h>				/* Scripting Language definitions		*/
#include <debug.h>
#include <FreeRTOS.h>			/* FreeRTOS definitions					*/
#include <semphr.h>				/* FreeRTOS semaphore functions			*/
#include <timer.h>
#include <sem.h>				/* OASIS semaphore definitions			*/
#include <malloc.h>
#include <utils.h>

#include <istdio.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

typedef struct
{
	Nat32		fileKB;
	Nat32		filecount;
	Nat32		dircount;
} DirStruct;

#define LINEBUFLEN		256


/********************************/
/*		External Data			*/
/********************************/

Extern Parm_t	con_dsc;				/* Disconnect character for CONNECT */
Extern Parm_t	xoff_tmout;				/* Timeout for xoff			*/


/********************************/
/*		Module Local Data		*/
/********************************/

MLocal SemaphoreHandle_t	fileSem;
MLocal SemBuf				fileSemBuf;
MLocal char					*pathstr = FILEPATH;
MLocal char					*tmpFileName = "$$$$$$$$.TMP";
	   FATFS				fatfs;

MLocal char  *ferrStr[] =
    { "FR_OK", "FR_DISK_ERR", "FR_INT_ERR", "FR_NOT_READY", "FR_NO_FILE",
      "FR_NO_PATH", "FR_INVALID_NAME", "FR_DENIED", "FR_EXIST",
      "FR_INVALID_OBJECT", "FR_WRITE_PROTECTED", "FR_INVALID_DRIVE",
      "FR_NOT_ENABLED",	"FR_NO_FILESYSTEM", "FR_MKFS_ABORTED",
      "FR_TIMEOUT", "FR_LOCKED", "FR_NOT_ENOUGH_CORE", "FR_TOO_MANY_OPEN_FILES",
      "FR_INVALID_PARAMETER"
    };


/************************************************************************/
/* Function	   : file_init												*/
/* Purpose     : Initialize SD card driver and file I/O system          */
/* Purpose	   : Initialize this module									*/
/* Inputs	   : None													*/
/* Outputs	   : OK, or ERROR if card not inserted						*/
/************************************************************************/
Errno file_init(Void)
{
    FRESULT     fRes;

	sdSpiInit();						/* Initialize SPI Bus			*/

    if ((fRes = f_mount(&fatfs, "0", 1)) != FR_OK)
    {                                               
        xprintf("\r\nError mounting file system, result = %d\r\n", fRes);
		return(ERROR);
    }

	fileSem = semCreateRecursiveMutex(&fileSemBuf);
	sem_name(&fileSemBuf, "fileSem");

	return(OK);

} /* file_init() */


/************************************************************************/
/* Function	   : takeFileSem											*/
/* Purpose	   : Take the fileSem semaphore								*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
Void takeFileSem(Void)
{
	xSemaphoreTake(fileSem, portMAX_DELAY);

} /* takeFileSem() */


/************************************************************************/
/* Function	   : giveFileSem											*/
/* Purpose	   : Give back the fileSem semaphore						*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
Void giveFileSem(Void)
{
	xSemaphoreGive(fileSem);

} /* giveFileSem() */


/************************************************************************/
/* Function    : fperr                                                  */
/* Purpose     : Print file system error string                         */
/* Inputs      : Error number, serial port, string						*/
/* Outputs     : None													*/
/************************************************************************/
Void fperr(int errnum, const char *s)
{
    if (errnum <= sizeof(ferrStr))
    {
        if (s)
            xprintf("%s: %d  %s", s, errnum, ferrStr[errnum]);
        else 
            xprintf("%d  %s", errnum, ferrStr[errnum]);
    }
}


/************************************************************************/
/* Function	   : dirScan												*/
/* Purpose	   : Execute function for every directory entry matching pattern*/
/* Inputs	   : Wildcard pattern to match, function ptr, function parm */
/* Outputs	   : None													*/
/************************************************************************/
Void dirScan(char *matchStr, MBool (*func)(FILINFO *, void *), void *parm)
{
	DIR		dirent;
	FILINFO finfo;
	FRESULT fres;

	if (matchStr == NULL)
		matchStr = "*";
	
	takeFileSem();
	fres = f_findfirst(&dirent, &finfo, pathstr, matchStr);

	while ((fres == FR_OK) && finfo.fname[0])
	{
		giveFileSem();
		if (!(*func)(&finfo, parm))
			break;
		dispatch();
		takeFileSem();
		fres = f_findnext(&dirent, &finfo);
	}

	f_closedir(&dirent);
	giveFileSem();

} /* dirScan() */


/************************************************************************/
/* Function	   : findDataFiles											*/
/* Purpose	   : Function passed to dirScan for file_findLogFiles, to	*/
/*				 find one type of log file (.DAT or .IDX)				*/
/* Inputs	   : Directory entry ptr, FileFound struct to store results */
/* Outputs	   : TRUE (to continue scan)								*/
/* Side Effect : Fills in filesFound									*/
/************************************************************************/
MLocal MBool findDataFiles(FILINFO *finfo, void *parm)
{
	LogBlk		fileNum;
	FileFound	*filesFound = (FileFound *)parm;

//	  dprintf("findDataFiles: found %s\n", finfo->fname);

	if (((finfo->fattrib & (AM_DIR | AM_VOL)) == 0) &&
		((sscanf(finfo->fname, "OAS%lu", &fileNum) > 0)))
	{
		if (fileNum < filesFound->minNum)
			filesFound->minNum = fileNum;

		if (fileNum >= filesFound->maxNum)
		{
			filesFound->maxNum = fileNum;
			filesFound->maxNumSize = finfo->fsize;
		}
	}

	return(TRUE);

} /* findDataFiles() */


/************************************************************************/
/* Function	   : file_findLogFiles										*/
/* Purpose	   : Find all old Log files									*/
/*				 Return LogBlk representing newest and oldest log files */
/* Inputs	   : LogPtr to return LogBlk & LogRecNum of newest log data */
/* Outputs	   : None													*/
/* Comment	   : Assumes caller already owns fileSem					*/
/************************************************************************/
Void file_findLogFiles(LogRecNum *newest, LogRecNum *oldest)
{
	FileFound	datFound = {0xffff, 0, 0};
	FileFound	idxFound = {0xffff, 0, 0};

	dirScan("OAS*.DAT", findDataFiles, &datFound);
	dirScan("OAS*.IDX", findDataFiles, &idxFound);

	*oldest = max(datFound.minNum, idxFound.minNum) << LOG_BLK_SHIFT;

	*newest = max(datFound.maxNum, idxFound.maxNum) << LOG_BLK_SHIFT;

	if (datFound.maxNum == idxFound.maxNum)
		*newest += idxFound.maxNumSize/sizeof(Nat32);
	else
	{
		/* If no index file for latest log, start at next log file		*/
		/* We should probably re-index the latest log here				*/
		*newest += RECS_PER_BLK;
	}

	if (*oldest > *newest)
		*oldest = *newest;

} /* file_findLogFiles() */


/************************************************************************/
/* Function	   : fileOpen												*/
/* Purpose	   : f_open a file											*/
/* Inputs	   : File struct, file name, open mode						*/
/* Outputs	   : FRESULT												*/
/* Comment	   : Assumes caller owns fileSem							*/
/************************************************************************/
MLocal FRESULT fileOpen(FIL *fp, const char *name, BYTE mode)
{
	FRESULT	fres;

	if ((fres = f_open(fp, name, mode)) == FR_OK)
		return(FR_OK);

	xprintf("Could not open %s: %d  %s\n",
			name, fres, (fres <= sizeof(ferrStr)) ? ferrStr[fres] : "");

	return(fres);

} /* fileOpen() */


/************************************************************************/
/* CAUTION on all the usrXxx commands that allocate a FIL structure		*/
/* on the stack -- This is only benign when ffconf.h defines _FS_TINY	*/
/* to be 1.  If _FS_TINY == 0, the FIL structure becomes 512 bytes larger,*/
/* which may be enough to blow the task stack.							*/
/************************************************************************/

/************************************************************************/
/* Function	   : usrType												*/
/* Purpose	   : Implements user-level "type filename" command			*/
/* Inputs	   : Parm mask, file name									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Int16 usrType(Nat16 pmask, char *fileName)
{
	FIL		fp;
	Int16	ch;
	Nat32	xoff_start;
	char	inbuf[32];

	if ((pmask & 1) == 0)
		return(ERROR);

	takeFileSem();

	if (fileOpen(&fp, fileName, FA_READ) != FR_OK)
	{
		giveFileSem();
		return(ERROR);
	}

	while (f_gets(inbuf, sizeof(inbuf), &fp) != NULL)
	{
		giveFileSem();
		xputn(inbuf, strlen(inbuf));

		if ((ch = xgetc_tmout(0)) == XOFF)
		{
			xoff_start = xTaskGetTickCount();
			while ((ch = xgetc_tmout(0)) != XON)
			{	
				if (ch != ERROR)
					return(OK);

				if ((xTaskGetTickCount() - xoff_start) >= (xoff_tmout * TICKS_PER_SEC))
				{
					xprintf("TIMEOUT\n");
					return(OK);
				}
				vTaskDelay(TICKS_PER_SEC/5);
			}
		}
		else if (ch != ERROR)
			return(OK);

		takeFileSem();
	}

	f_close(&fp);
	giveFileSem();
	return(OK);

} /* usrType() */


/************************************************************************/
/* Function	   : usrMore												*/
/* Purpose	   : Implements user-level "more filename" command			*/
/* Inputs	   : Parm mask, file name									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Int16 usrMore(Nat16 pmask, char *fileName)
{
	FIL		fp;
	Int16	ch, linecnt;
	char	*linebuf;

	if ((pmask & 1) == 0)
		return(ERROR);

	if ((linebuf = pmalloc(LINEBUFLEN)) == NULL)
		return(ERROR);

	takeFileSem();

	if (fileOpen(&fp, fileName, FA_READ) != FR_OK)
	{
		giveFileSem();
		free(linebuf);
		return(ERROR);
	}

	linecnt = 0;
	while (f_gets(linebuf, LINEBUFLEN, &fp) != NULL)
	{
		linecnt++;
		giveFileSem();
		xprintf("%04d: ", linecnt);
		xputn(linebuf, strlen(linebuf));

		if ((linecnt%23) == 22)
		{
			xputs("---More---");
			ch = xgetc_tmout(xoff_tmout);
			newline();
			if ((ch == 'q') || (ch == 'Q'))
				return(OK);
		}

		takeFileSem();
	}

	free(linebuf);
	f_close(&fp);
	giveFileSem();
	return(OK);

} /* usrMore() */


/************************************************************************/
/* Function	   : usrCopy												*/
/* Purpose	   : Implements user-level file copy command				*/
/* Inputs	   : Parm mask, file name									*/
/* Outputs	   : OK														*/
/************************************************************************/
Int16 usrCopy(Nat16 pmask, char *src, char *dst)
{
	FIL		srcFp, dstFp;
	char	inbuf[32];
	UINT	br, bw;
	FRESULT	fr;

	if ((pmask & 3) != 3)
		return(ERROR);

	takeFileSem();
  
	if (fileOpen(&srcFp, src, FA_READ) == FR_OK)
	{
		if (fileOpen(&dstFp, dst,
					 (BYTE)(FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_WRITE)) == FR_OK)
		{
			do
			{
				if ((fr = f_read(&srcFp, inbuf, 32, &br)) != FR_OK)
				{
					fperr(fr, "Error in file copy\n");
					break;
				}

				if ((fr = f_write(&dstFp, inbuf, br, &bw)) != FR_OK)
				{
					fperr(fr, "Error in file copy\n");
					break;
				}

				dispatch();

			} while ((bw == br) && (br > 0));

			f_close(&dstFp);
		}

		f_close(&srcFp);
	}

	giveFileSem();
	return(OK);

} /* usrCopy() */


/************************************************************************/
/* Function	   : usrRename												*/
/* Purpose	   : Implements user-level file rename command				*/
/* Inputs	   : Parm mask, file name									*/
/* Outputs	   : OK														*/
/************************************************************************/
Int16 usrRename(Nat16 pmask, char *oldName, char *newName)
{
	FRESULT rtn;

	if ((pmask & 3) != 3)
		return(ERROR);

	takeFileSem();
	rtn = f_rename(oldName, newName);
	if (rtn != FR_OK)
		fperr(rtn, "Error in rename");

	giveFileSem();
	return((rtn == FR_OK) ? OK : ERROR);

} /* usrRename() */


/************************************************************************/
/* Function	   : dirFileDelete											*/
/* Purpose	   : Function passed to dirScan for "DEL" command			*/
/* Inputs	   : Directory entry ptr, unused ptr						*/
/* Outputs	   : TRUE (to continue the scan)							*/
/************************************************************************/
MBool dirFileDelete(FILINFO *de, void *p)
{
	if ((de->fattrib & (AM_DIR | AM_VOL)) == 0)
		f_unlink(de->fname);
	return(TRUE);
}

/************************************************************************/
/* Function	   : usrDelete												*/
/* Purpose	   : Implements user-level file delete						*/
/* Inputs	   : Parm mask, file name									*/
/* Outputs	   : OK														*/
/************************************************************************/
Int16 usrDelete(Nat16 pmask, char *fileName)
{
//	logFlush();
	if (pmask & 1)
		dirScan(fileName, dirFileDelete, NULL);

	return(OK);

} /* usrDelete() */


/************************************************************************/
/* Function	   : usrHexDump												*/
/* Purpose	   : Implements user-level "hexdump filename" command		*/
/* Inputs	   : Parm mask, file name									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Int16 usrHexDump(Nat16 pmask, char *fileName, Parm_t start, Parm_t len)
{
	FIL			fp;
	Nat32		pos, endPos;
	UINT		rtn;
	Nat16		i;
	FRESULT		fr;
	Byte		buf[16];

	if ((pmask & 1) == 0)
		return(ERROR);

	takeFileSem();
	fr = fileOpen(&fp, fileName, FA_READ);

	pos = 0;
	rtn = 16;
	if (pmask & 2)
	{
		pos = start;
		if (fr == FR_OK)
			if ((fr = f_lseek(&fp, pos)) != FR_OK)
				rtn = 0;
	}
	
	giveFileSem();

	if (fr != FR_OK)
	{
		fperr(fr, NULL);
		return(ERROR);
	}

	endPos = (pmask & 4) ? pos + len : NAT32_MAX;

	while ((rtn > 0) && (pos < endPos))
	{
		takeFileSem();
		f_read(&fp, buf, 16, &rtn);
		giveFileSem();

		xprintf("%08lx	", pos);
  
		for (i = 0; i < rtn; i++)
			xprintf("%02x ", buf[i]);

		xputs("	 ");

		for (i = 0; i < rtn; i++)
			xputc(isprint(buf[i]) ? buf[i] : '.');

		newline();
		if ( xgetc_tmout(0) != ERROR )
			break;
		dispatch();
		pos += rtn;
	}

	takeFileSem();
	f_close(&fp);
	giveFileSem();
	return(OK);

} /* usrHexDump() */


/************************************************************************/
/* Function	   : dirFunc												*/
/* Purpose	   : Function passed to dirScan for "DIR" command			*/
/* Inputs	   : Directory entry ptr, DirStruct ptr						*/
/* Outputs	   : TRUE (to continue the scan)							*/
/************************************************************************/
MLocal MBool dirFunc(FILINFO *finfo, void *parm)
{
	DirStruct	*ds = (DirStruct *)parm;

	if ((finfo->fattrib & (AM_VOL | AM_HID)) != 0)
		return(TRUE);

	if (finfo->fattrib & AM_DIR)
		ds->dircount++;
	else
	{
		ds->filecount++;
		ds->fileKB += (finfo->fsize+1023) / 1024;
	}
								
	xprintf("%-14s", finfo->fname);
		
	if (finfo->fattrib & AM_DIR)
		xprintf("	<DIR>		 ");
	else
		xprintf("%14u	", finfo->fsize);

	xprintf("%04d/%02d/%02d %2u:%02u:%02u\n",
			(finfo->fdate>>9 & 0x7f) + 1980,
			(finfo->fdate>>5 & 0x0f),
			finfo->fdate & 0x1f,
			(finfo->ftime>>11) & 0x1f,
			(finfo->ftime>>5) & 0x3f,
			(finfo->ftime & 0x1f) << 1);

	return(kbhit() == 0);
}

/************************************************************************/
/* Function	   : usrDir													*/
/* Purpose	   : Get directory on flash file system						*/
/* Inputs	   : None													*/
/* Outputs	   : OK														*/
/************************************************************************/
Int16 usrDir(Nat16 pmask, char *matchStr)
{
	FRESULT		fr;
	Nat32		volid, freeClust;
	DirStruct	dirStruct;
	FATFS		*fsp;
	char		volname[32];

	takeFileSem();
	fr = f_getlabel("", volname, &volid);
	giveFileSem();

	if (fr == FR_OK)
		xprintf("Volume label is %s, Serial number is %u\r\n",
				volname, volid);

	xprintf(" Directory of %s\n\n", pathstr);

	memset(&dirStruct, 0, sizeof(dirStruct));

	dirScan((pmask & 1) ? matchStr : "*", dirFunc, &dirStruct);

	xprintf("%8u file(s) %14u KB\n", dirStruct.filecount, dirStruct.fileKB);

	if (f_getfree(pathstr, &freeClust, &fsp) == FR_OK)
		xprintf("%8u dir(s)	 %14u KB free\n",
				dirStruct.dircount, 
				freeClust * fsp->csize / 2);

	return(OK);

} /* usrDir() */


/************************************************************************/
/* Function	   : lineEdit												*/
/* Purpose	   : Line editor function									*/
/* Inputs	   : File and line number to edit, function to do editing	*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Int16 lineEdit(char *fileName, Nat32 linenum, Nat32 parm, Void (*linefunc)())
{
	FIL			rfp, wfp;
	Int16		linecnt;
	char		*p;
	char		*linebuf;

	if (findScript(fileName) != NULL)
	{
		xprintf("You cannot edit a running script.	Aborted.\n");
		return(ERROR);
	}

	if ((linebuf = pmalloc(LINEBUFLEN)) == NULL)
		return(ERROR);
	takeFileSem();

	if (fileOpen(&rfp, fileName, FA_READ) != FR_OK)
	{
		xprintf("Could not open %s\n", fileName);
		giveFileSem();
		return(ERROR);
	}

	if (fileOpen(&wfp, tmpFileName,
				 (BYTE)(FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_WRITE)) == FR_OK)
	{
		xprintf("Could not open %s\n", tmpFileName);
		f_close(&rfp);
		giveFileSem();
		return(ERROR);
	}

	for (linecnt = 1; (linecnt < linenum) && 
			 (f_gets(linebuf, LINEBUFLEN, &rfp) != NULL); linecnt++)
	{
		f_puts(linebuf, &wfp);
	}
	
	(*linefunc)(&rfp, &wfp, parm, linebuf);

	while (f_gets(linebuf, sizeof(linebuf), &rfp) != NULL)
		f_puts(linebuf, &wfp);

	f_close(&rfp);
	f_close(&wfp);

	strncpy(linebuf, fileName, LINEBUFLEN);
	if ((p = strchr(linebuf, '.')) == NULL)
		p = linebuf + strlen(linebuf);
	strcpy(p, ".BAK");

	f_unlink(linebuf);
	f_rename(fileName, linebuf);
	f_unlink(fileName);
	f_rename(tmpFileName, fileName);
	giveFileSem();

	return(OK);

} /* lineEdit() */


/************************************************************************/
/* Function	   : delLineFunc, delChrFunc, insertLIneFunc, insertChrFunc */
/* Purpose	   : Functions passed to lineEdit()							*/
/* Inputs	   : Read file ptr, write file ptr, parm, line buffer		*/
/* Outputs	   : None													*/
/************************************************************************/
MLocal Void delLineFunc(FIL *rfp, FIL *wfp, Nat32 parm, char linebuf[])
{
	f_gets(linebuf, LINEBUFLEN, rfp);
}


MLocal Void delChrFunc(FIL *rfp, FILE *wfp, Nat32 parm, char linebuf[])
{
	UINT		br;
	f_read(rfp, linebuf, parm, &br);
}

MLocal Void insertLineFunc(FIL *rfp, FIL *wfp, Nat32 linenum, char linebuf[])
{
	FSIZE_t rpos = f_tell(rfp);

	f_gets(linebuf, LINEBUFLEN, rfp);
	xprintf("%04u: %s", linenum+1, linebuf);
	f_lseek(rfp, rpos);
	xprintf("%04u:	 ", linenum);
	xgets_tmout(linebuf, LINEBUFLEN, xoff_tmout);
	f_printf(wfp, "%s\r\n", linebuf);
}


MLocal Void insertChrFunc(FIL *rfp, FIL *wfp, Nat32 linenum, char linebuf[])
{
	FSIZE_t rpos = f_tell(rfp);

	f_gets(linebuf, LINEBUFLEN, rfp);
	xprintf("%04u: %s", linenum, linebuf);
	f_lseek(rfp, rpos);
	xprintf("%04u: ", linenum);
	xgets_tmout(linebuf, LINEBUFLEN, xoff_tmout);
	f_puts(linebuf, wfp);
}


/************************************************************************/
/* Function	   : deleteLine												*/
/* Purpose	   : Delete a line in a file								*/
/* Inputs	   : Line number, file name									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Int16 deleteLine(Nat16 pmask, Nat32 linenum, char *filename)
{
	if ((pmask & 3) != 3)
		return(ERROR);

	return(lineEdit(filename, linenum, 1, delLineFunc));

} /* deleteLine() */


/************************************************************************/
/* Function	   : deleteChars											*/
/* Purpose	   : Delete chars from a line in a file						*/
/* Inputs	   : Line number, file name, number of chars (default 1)	*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Int16 deleteChars(Nat16 pmask, Nat32 linenum, char *filename, Nat32 numchars)
{
	Nat32 numChars = 1;

	if ((pmask & 3) != 3)
		return(ERROR);

	if (pmask & 4)
		numChars = numchars;

	return(lineEdit(filename, linenum, numChars, delChrFunc));

} /* deleteChars() */


/************************************************************************/
/* Function	   : insertLine												*/
/* Purpose	   : Insert a line in a file								*/
/* Inputs	   : Line number, file name									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Int16 insertLine(Nat16 pmask, Nat32 linenum, char *filename)
{
	if ((pmask & 3) != 3)
		return(ERROR);

	return(lineEdit(filename, linenum, linenum, insertLineFunc));

} /* insertLine() */



/************************************************************************/
/* Function	   : insertChars											*/
/* Purpose	   : Insert chars at the front of a line					*/
/* Inputs	   : Line number, file name									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Int16 insertChars(Nat16 pmask, Nat32 linenum, char *filename)
{
	if ((pmask & 3) != 3)
		return(ERROR);

	return(lineEdit(filename, linenum, linenum, insertChrFunc));

} /* insertChars() */
