/************************************************************/
/* Windows Write, Copyright 1985-1992 Microsoft Corporation */
/************************************************************/

/* file.c -- WRITE file interface functions */
/* It is important that the external interface to this module be entirely
   at the level of "fn's", not "osfn's" and/or "rfn's".  An intermodule call
   may cause our files to be closed, and this is the only module internally
   capable of compensating for this */

#define NOGDICAPMASKS
#define NOVIRTUALKEYCODES
#define NOWINMESSAGES
#define NOWINSTYLES
#define NOSYSMETRICS
#define NOMENUS
#define NOICON
#define NOKEYSTATE
#define NOSYSCOMMANDS
#define NORASTEROPS
#define NOSHOWWINDOW
#define NOSYSMETRICS
#define NOATOM
#define NOBITMAP
#define NOBRUSH
#define NOCLIPBOARD
#define NOCOLOR
#define NOCREATESTRUCT
#define NOCTLMGR
#define NODRAWTEXT
#define NOFONT
#define NOGDI
#define NOHDC
#define NOMEMMGR
#define NOMENUS
#define NOMETAFILE
#define NOMSG
#define NOPEN
#define NOPOINT
#define NORECT
#define NOREGION
#define NOSCROLL
#define NOSOUND
#define NOTEXTMETRIC
#define NOWH
#define NOWINOFFSETS
#define NOWNDCLASS
#define NOCOMM
#include <windows.h>

#include "mw.h"
#include "doslib.h"
#include "docdefs.h"
#include "filedefs.h"
#define NOSTRUNDO
#include "str.h"
#include "debug.h"


extern int						vfDiskFull;
extern int						vfSysFull;
extern int						vfnWriting;
extern CHAR						(*rgbp)[cbSector];
extern typeTS					tsMruRfn;
extern struct BPS				*mpibpbps;
extern int						ibpMax;
extern struct FCB				(**hpfnfcb)[];
extern typeTS					tsMruBps;
extern struct ERFN				dnrfn[rfnMax];
extern int						iibpHashMax;
extern CHAR						*rgibpHash;
extern int						rfnMac;
extern int						ferror;
extern CHAR						szWriteDocPrompt[];
extern CHAR						szScratchFilePrompt[];
extern CHAR						szSaveFilePrompt[];


#ifdef CKSM
#ifdef DEBUG
extern unsigned					(**hpibpcksm) [];
extern int						ibpCksmMax;
#endif
#endif


#define IibpHash(fn,pn) ((int) ((fn + 1) * (pn + 1)) & 077777) % iibpHashMax


#ifdef DEBUG
#define STATIC
#else
#define STATIC static
#define ErrorWithMsg( idpmt, szModule )			Error( idpmt )
#define DiskErrorWithMsg( idpmt, szModule )		DiskError( idpmt )
#endif


#define osfnNil (-1)

STATIC int near  RfnGrab( void );
STATIC int near  FpeSeekFnPn( int, typePN );
STATIC typeOSFN near  OsfnEnsureValid( int );
STATIC typeOSFN near  OsfnReopenFn( int );
STATIC CHAR *(near SzPromptFromFn( int ));

/* The following debug flags are used to initiate, during debug, specific
   low-level disk errors */

#ifdef DEBUG
int vfFakeReadErr = 0;
int vfFakeWriteErr = 0;
int vfFakeOpenErr = 0;
#endif


#ifdef CKSM
#ifdef DEBUG
unsigned CksmFromIbp( ibp )
int ibp;
{
 int cb = mpibpbps [ibp].cch;
 unsigned cksm = 0;
 CHAR *pb;

 Assert( ibp >= 0 && ibp < ibpCksmMax );
 Assert( mpibpbps [ibp].fn != fnNil );
 Assert( !mpibpbps [ibp].fDirty );

 pb = rgbp [ibp];
 while (cb-- > 0)
	cksm += *(pb++);

 return cksm;
}
#endif
#endif



typeFC FcMacFromUnformattedFn( fn )
int fn;
{	/* Obtain fcMac for passed unformatted fn by seeking to the file's end.
       If it fails, return -1
       If it succeeds, return the fcMac */
 typeFC fcMac;
 typeOSFN osfn;

 Assert( (fn != fnNil) && (!(**hpfnfcb) [fn].fFormatted) );

 if ((osfn = OsfnEnsureValid( fn )) == osfnNil)
	return (typeFC) -1;
 else
	{
	if ( FIsErrDwSeek( fcMac=DwSeekDw( osfn, 0L, SF_END )))
		{
		if (fcMac == fpeBadHndError)
			{	/* Windows closed the file for us */
			if ( ((osfn = OsfnReopenFn( fn )) != osfnNil) &&
                 !FIsErrDwSeek( fcMac = DwSeekDw( osfn, 0L, SF_END )) )
					/* Successfully re-opened file */
				return fcMac;
			}
		if (FIsCaughtDwSeekErr( fcMac ))
				/* Suppress reporting of error -- Windows did it */
			ferror = TRUE;
		DiskErrorWithMsg(IDPMTSDE, " FcMacFromUnformattedFn");
		return (typeFC) -1;
		}
	}

return fcMac;
}




IbpLru(ibpStarting)
int ibpStarting;
/*
		Description:	Find least recently used BPS (Buffer page) starting
						at slot ibpStarting.
		Returns:		Number of the least recently used buffer slot.
*/
{
		int ibp, ibpLru = 0;
		typeTS ts, tsLru;
		struct BPS *pbps = &mpibpbps[ibpStarting];

		tsLru = -1;		/* Since time stamps are unsigned ints, -1 */
						/* is largest time stamp. */
		for(ibp = ibpStarting; ibp < ibpMax; ibp++, pbps++)
				{
				ts = pbps->ts - (tsMruBps + 1);
				/* The time stamp can conceivably wrap around and thus a */
				/* simple < or > comparison between time stamps cannot be */
				/* used to determine which is more or less recently used. */
				/* The above statement normalizes time stamps so that the */
				/* most recently used is FFFF and the others fall between */
				/* 0 and FFFE.	This method makes the assumption that */
				/* time stamps older than 2^16 clicks of the time stamp */
				/* counter have long since disappeared.  Otherwise, such */
				/* ancients would appear to be what they are not. */
				if (ts <= tsLru) {tsLru = ts; ibpLru = ibp;}
				}
		return(ibpLru);
}




int IbpMakeValid(fn, pn)
int fn;
typePN pn;
{ /*
		Description:	Get page pn of file fn into memory.
						Assume not already in memory.
		Returns:		Bp index (buffer slot #) where the page resides
						in memory.
  */
#define cbpClump		4

#define cpnAlign		4
#define ALIGN

int ibpT, cbpRead;
int ibp, iibpHash, ibpPrev;
typePN pnT;
register struct BPS *pbps;
int ibpNew;
int cch;
int fFileFlushed;
#ifdef ALIGN
int dibpAlign;
#endif

#ifdef DEBUG
 int cbpReadT;
 CheckIbp();
#endif /* DEBUG */
 Assert(fn != fnNil);

/* page is not currently in memory */

/* We will try to read in cbpClump Buffer Pages beginning with the
   least recently used */

 /* Pick best starting slot for read based on a least-recently-used scheme */

 if (vfnWriting != fnNil)
		/* Currently saving file, favor adjacent slots for fn being written  */
	ibpNew = IbpWriting( fn );
 else
		/* If reading from the scratch file, do not use the first
           cpbMustKeep slots. This is necessary to prevent a disk full
           condition from being catastrophic. */
	ibpNew = IbpLru( (fn == fnScratch) ? cbpMustKeep : 0 );


 /* Empty the slots by flushing out ALL buffers holding pieces of their fn's */
 /* Compute cbpRead */
 pbps = &mpibpbps[ ibpNew ];
 for ( cbpRead = 0;  cbpRead < min( cbpClump, ibpMax - ibpNew );
                                                       cbpRead++, pbps++ )
	{
#ifdef CKSM
#ifdef DEBUG
	int ibpT = pbps - mpibpbps;

	if (!pbps->fDirty && pbps->fn != fnNil)
		Assert( (**hpibpcksm) [ibpT] == CksmFromIbp( ibpT ) );
#endif
#endif
	if (pbps->fDirty && pbps->fn != fnNil)
		if (!FFlushFn( pbps->fn ))
			break;
	}

 /* If a flush failed, cbpRead was reduced. If it was reduced to 0, this
	is serious. If the file was not fnScratch, we can consider it flushed
	even though the flush failed, because upper-level procedures will detect
	the error and cancel the operation. If it was fnScratch, we must obtain
	a new slot for the page we are trying to read */
 if (cbpRead == 0)
	{
	if (pbps->fn == fnScratch)
		ibpNew = IbpFindSlot( fn );
	cbpRead++;
	}
 else
	{	/* Restrict cbpRead according to the following:
				(1) If we are reading in the text area, we don't want to
					free pages for stuff past the end of the text area
					that we know CchPageIn won't give us
				(2) If we are reading in the properties area, we only want to
					read one page, because FetchCp depends on us not trashing
					the MRU page */
	struct FCB *pfcb = &(**hpfnfcb)[fn];

	if (pfcb->fFormatted && fn != fnScratch && pn >= pfcb->pnChar)
		cbpRead = 1;
	else
		{
		typePN cbpValid = (pfcb->fcMac - (pn * cfcPage)) / cfcPage;

		cbpRead = min( cbpRead, max( 1, cbpValid ) );
		}
	}

#ifdef ALIGN
     /* Align the read request on an even sector boundary, for speed */
 dibpAlign = (pn % cpnAlign);
 if (cbpRead > dibpAlign)
		/* We are reading enough pages to cover the desired one */
	pn -= dibpAlign;
 else
	dibpAlign = 0;
#endif

	/* Remove flushed slots from their hash chains */
 for ( pbps = &mpibpbps[ ibpT = ibpNew + cbpRead - 1 ];
       ibpT >= ibpNew;
       ibpT--, pbps-- )
	if (pbps->fn != fnNil)
		FreeBufferPage( pbps->fn, pbps->pn );

	/* Free slots holding any existing copies of pages to be read */
#ifdef	DBCS				/* was in KKBUGFIX */
/*	In #else code ,If pn= 8000H(= -32768), pnT can not be littler than pn */
 for ( pnT = pn + cbpRead; pnT > pn; pnT-- )		// Assume cbpRead > 0
	FreeBufferPage( fn, pnT-1 );
#else
 for ( pnT = pn + cbpRead - 1; (int)pnT >= (int)pn; pnT-- )
	FreeBufferPage( fn, pnT );
#endif


	/* Read contents of file page(s) into buffer slot(s) */
 cch = CchPageIn( fn, pn, rgbp[ibpNew], cbpRead );

#ifdef DEBUG
 cbpReadT = cbpRead;
#endif

	/* Fill in bps records for as many bytes as were read, but always
       at least one record (to support PnAlloc). If we reached the end of the
       file, the unfilled bps slots are left free */
 pbps = &mpibpbps[ ibpT = ibpNew ];
 do
	{
	pbps->fn = fn;
	pbps->pn = pn;
	pbps->ts = ++tsMruBps; /* mark page as MRUsed */
	pbps->fDirty = false;
	pbps->cch = min( cch, cbSector );
	pbps->ibpHashNext = ibpNil;

	cch = max( cch - cbSector, 0 );

	/* put in new hash table entry for fn,pn */
	iibpHash = IibpHash(fn, pn);
	ibp = rgibpHash[iibpHash];
	ibpPrev = ibpNil;
	while (ibp != ibpNil)
		{
		ibpPrev = ibp;
		ibp = mpibpbps[ibp].ibpHashNext;
		}
	if (ibpPrev == ibpNil)
		rgibpHash[iibpHash] = ibpT;
	else
		mpibpbps[ibpPrev].ibpHashNext = ibpT;

	pn++;  ibpT++;	pbps++;

	}	while ( (--cbpRead > 0) && (cch > 0) );

#ifdef CKSM
#ifdef DEBUG
	/* Compute checksums for newly read pages */

 {
 int ibp;

 for ( ibp = ibpNew; ibp < ibpNew + cbpReadT; ibp++ )
	if (mpibpbps [ibp].fn != fnNil && !mpibpbps [ibp].fDirty)
		(**hpibpcksm) [ibp] = CksmFromIbp( ibp );
 }
	CheckIbp();
#endif /* DEBUG */
#endif

#ifdef ALIGN
	return (ibpNew + dibpAlign);
#else
	return (ibpNew);
#endif
} /* end of I b p M a k e V a l i d  */




FreeBufferPage( fn, pn )
int fn;
int pn;
{		/* Free buffer page holding page pn of file fn if there is one */
		/* Flushes fn if page is dirty */
 int iibp = IibpHash( fn, pn );
 int ibp = rgibpHash[ iibp ];
 int ibpPrev = ibpNil;

 Assert( fn != fnNil );
 while (ibp != ibpNil)
	{
	struct BPS *pbps=&mpibpbps[ ibp ];

	if ( (pbps->fn == fn) && (pbps->pn == pn ) )
		{	/* Found it. Remove this page from the chain & mark it free */

		if (pbps->fDirty)
			FFlushFn( fn );
#ifdef CKSM
#ifdef DEBUG
		else	/* Page has not been trashed while in memory */
			{
			Assert( (**hpibpcksm) [ibp] == CksmFromIbp( ibp ) );
			}
#endif
#endif

		if (ibpPrev == ibpNil)
				/* First entry in hash chain */
			rgibpHash [ iibp ] = pbps->ibpHashNext;
		else
			mpibpbps[ ibpPrev ].ibpHashNext = pbps->ibpHashNext;

		pbps->fn = fnNil;
		pbps->fDirty = FALSE;
			/* Mark the page not recently used */
		pbps->ts = tsMruBps - (ibpMax * 4);
			/* Mark pages that are on even clump boundaries a bit less recently
               used so they are favored in new allocations */
		if (ibp % cbpClump == 0)
			--(pbps->ts);
		pbps->ibpHashNext = ibpNil;
		}
	ibpPrev = ibp;
	ibp = pbps->ibpHashNext;
	}
#ifdef DEBUG
	CheckIbp();
#endif
}




int CchPageIn(fn, pn, rgbBuf, cpnRead)
int fn;
typePN pn;
CHAR rgbBuf[];
int cpnRead;
{ /*
		Description:	Read a cpnRead pages of file fn from disk into rgbBuf.
						Have already determined that page is not resident
						in the buffer.
		Returns:		Number of valid chars read (zero or positive #).
 */
		struct FCB *pfcb = &(**hpfnfcb)[fn];
		typeFC fcMac = pfcb->fcMac;
		typeFC fcPage = pn * cfcPage;
		int dfc;
		int fCharFormatInfo;			/* if reading Format info part of */
										/* file then = TRUE, text part of */
										/* file then = FALSE; */

			/* No reads > 32767 bytes, so dfc can be int */
		Assert( cpnRead <= 32767 / cbSector );

			/* Don't try to read beyond pnMac */
		if (cpnRead > pfcb->pnMac - pn)
			cpnRead = pfcb->pnMac - pn;

		dfc = cpnRead * (int)cfcPage;

		if (pn >= pfcb->pnMac)	/* are we trying to read beyond eof? */
				{
				return 0;		/* Nothing to read */
				}
		else if (pfcb->fFormatted && fn != fnScratch
                 && fn != vfnWriting	/* Since pnChar is zero in this case */
                 && pn >= pfcb->pnChar)
				{ /* reading character format info portion of file */
				fCharFormatInfo = TRUE;
				}
		else /* reading text portion of file */
				{ /* get dfc (cch) from fcMac */
				typeFC dfcT = fcMac - fcPage;

				fCharFormatInfo = FALSE;
				if (dfcT < dfc)
					dfc = (int) dfcT;
				else if (dfc <= fc0)	/* Nothing to read, so let's avoid disk access. */
						{
						return 0;
						}
				}

		return CchReadAtPage( fn, pn, rgbBuf, (int)dfc, fCharFormatInfo );
}




CchReadAtPage( fn, pn, rgb, dfc, fSeriousErr )
int fn;
typePN pn;
CHAR rgb[];
int dfc;
int fSeriousErr;
{ /*
		Description:	Read dfc bytes of file fn starting at page pn
						into rgb
		Returns:		Number of valid chars read (zero or positive #)
		Errors:			Returns # of chars read; ferror & vfDiskFull are
						set (in DiskError) if an error occurs.
		Notes:			Caller is responsible for assuring that the page
						range read is reasonable wrt the fn
  */

 typeOSFN osfn;
 int fpeSeek;
 int fpeRead;
 int fCaught;		/* Whether error was reported by DOS */

#ifdef DEBUG

 if (vfFakeReadErr)
	{
	dfc = 0;
	goto ReportErr;
	}
#endif

 if (!FIsErrFpe( fpeSeek = FpeSeekFnPn( fn, pn )))
	{
	osfn = OsfnEnsureValid( fn );

#ifdef DEBUG
#ifdef DFILE
	CommSzSz( "Read from file: ", &(**(**hpfnfcb)[fn].hszFile)[0] );
#endif
#endif

	if ((fpeRead = CchReadDoshnd( osfn, (CHAR FAR *)rgb, dfc )) == dfc)
		{	/* Read succeeded */
		return dfc;
		}
	else
		{
			/* Should be guaranteed that file was not closed because of seek */
		Assert( fpeRead != fpeBadHndError );

		fCaught = FIsCaughtFpe( fpeRead );
		}
	}
 else
	fCaught = FIsCaughtFpe( fpeSeek );

	/* unable to set position or read */
 if ((fn == fnScratch) || (fSeriousErr))
	{    /* unrecoverable error: either can't read from scratch */
         /* file or unable to read format info (FIB or format pages) part of */
         /* of some file. */
	dfc = 0;
	goto ReportErr;
	}
 else                    /* serious disk error on file (recoverable).*/
	{
	int cchRead = (FIsErrFpe(fpeSeek) || FIsErrFpe(fpeRead)) ? 0 : fpeRead;
	CHAR *pch = &rgb[cchRead];
	int cch = dfc - cchRead;

	/* If the positioning failed or the read failed
       for a reason other than disk full, completely
       fill the buffer with 'X's.  Otherwise, just
       fill the disputed portion (diff between #char
       requested and #char read) with 'X's */

	while (cch-- > 0)
		*pch++ = 'X';

ReportErr:
	if (fCaught)
			/* Suppress reporting of the error -- Windows reported it */
		ferror = TRUE;

	if	(pn != 0)
			/* Report error if not reading FIB */
		DiskErrorWithMsg(IDPMTSDE, " CchReadAtPage");

	return (int)dfc;
	/* recovery is accomplished: 1) Upper level procedures do not
       see any change in the world, 2) the worst thing that
       happens is that the user sees some 'X's on the screen. */
	}
}




AlignFn(fn, cch, fEven)
int fn, cch;
{ /* Make sure we have cch contiguous chars in fn */
/* if fEven, make sure fcMac is even */
		struct FCB *pfcb = &(**hpfnfcb)[fn];
		typeFC fcMac = pfcb->fcMac;
		typePN pn;
		typeFC fcFirstPage;

		pn = fcMac / cfcPage;
		fcFirstPage = (pn + 1) * cfcPage;

		Assert(cch <= cfcPage);

		if (fEven && (fcMac & 1) != 0)
				++cch;

		if (fcFirstPage - fcMac < cch)
				{
				struct BPS *pbps = &mpibpbps[IbpEnsureValid(fn, pn++)];
				pbps->cch = cfcPage;
				pbps->fDirty = true;
				fcMac = pfcb->fcMac = fcFirstPage;
				}

		if (fEven && (fcMac & 1) != 0)
				{
				struct BPS *pbps = &mpibpbps[IbpEnsureValid(fn, pn)];
				pbps->cch++;
				pbps->fDirty = true;
				pfcb->fcMac++;
				}
} /* end of  A l i g n F n	*/




/***	OsfnEnsureValid - ensure that file fn is open
 *
 *
 */

STATIC typeOSFN near OsfnEnsureValid(fn)
int fn;
{ /*
		Description:	Ensure that file fn is open (really)
		Returns:		operating system file number (osfnNil if error)
 */
		struct FCB *pfcb = &(**hpfnfcb)[fn];
		int rfn = pfcb->rfn;

		if (rfn == rfnNil)
			{ /* file doesn't have a rfn - ie. it is not opened */
#ifdef DEBUG
#ifdef DFILE
			CommSzSz( pfcb->fOpened ? "Re-opening file " :
                                      "Opening file", **pfcb->hszFile );
#endif
			if (vfFakeOpenErr || !FAccessFn( fn, dtyNormal ))
#else
			if (!FAccessFn( fn, dtyNormal ))
#endif
				{  /* unrecoverable error - unable to open file */
				DiskErrorWithMsg(IDPMTSDE, " OsfnEnsureValid");
				return osfnNil;
				}
			rfn = pfcb->rfn;
			Assert( (rfn >= 0) && (rfn < rfnMac) );
			}
		return dnrfn[rfn].osfn;
}
/* end of  O s F n E n s u r e V a l i d  */




STATIC int near FpeSeekFnPn( fn, pn )
int fn;
typePN	pn;
{	/* Seek to page pn of file fn
       return err code on failure, fpeNoErr on success
       Leaves file open (if no error occurs)
       Recovers from the case in which windows closed the file for us
     */
 typeOSFN osfn;
 long dwSeekReq;
 long dwSeekAct;

#ifdef DEBUG
#ifdef DFILE
 CommSzSz( "Seeking within file ", **(**hpfnfcb)[fn].hszFile );
#endif
#endif

 //osfn = OsfnEnsureValid( fn );
 if ((osfn = OsfnEnsureValid( fn )) == osfnNil)
	return fpeNoAccError;
 dwSeekReq = (long) pn * (long) cbSector;
 dwSeekAct = DwSeekDw( osfn, dwSeekReq, SF_BEGINNING);

 if ( ((int) dwSeekAct) == fpeBadHndError )
	{	/* Windows closed the file for us -- must reopen it */
	if ((osfn = OsfnReopenFn( fn )) == osfnNil)
		return fpeNoAccError;
	else
		dwSeekAct = DwSeekDw( osfn, dwSeekReq, SF_BEGINNING );
	}

 return (dwSeekAct >= 0) ? fpeNoErr : (int) dwSeekAct;
}




int FFlushFn(fn)
int fn;
{ /*
		Description:	Write all dirty pages of fn to disk.
						Sets vfSysFull = TRUE if disk full error occurred
						while flushing fnScratch.  Otherwise, a disk full
						error causes vfDiskFull = TRUE.
						Serious disk errors cause vfDiskError = TRUE
						Only the pages which actually made it to disk are
						marked as non-dirty.
		Returns:		TRUE if successful, FALSE if Disk full error
						while writing pages to disk.  Any other error
						is unrecoverable, ie. go back to main loop.
						To avoid extraneous error messages, the following
						two entry conditions cause FFlush to immediately
						return FALSE:
                         - If vfSysFull = TRUE and fn = fnScratch
                         - If vfDiskFull = TRUE and fn = vfnWriting
 */
int ibp;
typeOSFN osfn;
int fpe;
int cchWritten;
int cchAskWrite;
struct BPS *pbps;

Assert( fn != fnNil );

if ((vfSysFull) && (fn == fnScratch)) return (FALSE);
if ((vfDiskFull) && (fn == vfnWriting)) return (FALSE);

for (ibp = 0, pbps = mpibpbps; ibp < ibpMax; )
	{
	if (pbps->fn == fn && pbps->fDirty)
		{
		typePN pn = pbps->pn;
		int cbp = 0;
		CHAR *pch = (CHAR *)rgbp[ibp];
		struct BPS *pbpsStart = &mpibpbps[ibp];


		/* Coalesce all consecutive pages for a single write */
		do
			{
			/* taken out 11/7/84 - can't mark scratch file
               page as non dirty if chance that it will never
               get written out (write fails - disk full)
               pbps->fDirty = false;  mark page as clean */
			++ibp, ++cbp, ++pbps;
			}  while (ibp < ibpMax && pbps->fn == fn && pbps->pn == pn + cbp);

		/* Now do the write, checking for out of disk space */
		Scribble(3, 'W');

		cchAskWrite = (int)cbSector * (cbp - 1) + (pbps - 1)->cch;
		cchWritten = cchDiskHardError;	/* assure hard error if seek fails */

#ifdef DEBUG
		if (vfFakeWriteErr)
			goto SeriousError;
		else
#endif
		if ( FIsErrFpe( FpeSeekFnPn( fn, pn )) ||
             ((osfn = OsfnEnsureValid( fn )) == osfnNil) ||
#ifdef DEBUG
#ifdef DFILE
             (CommSzSz( "Writing to file: ", &(**(**hpfnfcb)[fn].hszFile)[0] ),
#endif
#endif
             (( cchWritten = CchWriteDoshnd( osfn, (CHAR FAR *)pch,
                                             cchAskWrite )) != cchAskWrite))
#ifdef DEBUG
#ifdef DFILE
                                                                             )
#endif
#endif
			{
				/* Should be guaranteed that windows did not close the file
                   since we have not called intermodule since the seek */
			Assert( cchWritten != fpeBadHndError );

			/* Seek or Write error */
			if ( !FIsErrCchDisk(cchWritten) )
				{    /* serious but recoverable disk error */
                     /* Ran out of disk space; write failed */
				if (fn == fnScratch)
					vfSysFull = fTrue;
				vfDiskFull = fTrue;
				DiskErrorWithMsg(IDPMTDFULL, " FFlushFn");
				return(FALSE);
				}
			else	/* cause of error is not disk full */
				{  /* unrecov. disk error */
#ifdef DEBUG
SeriousError:
#endif
				DiskErrorWithMsg(IDPMTSDE2, " FFlushFn");
				return FALSE;
				}
			}
		Diag(CommSzNumNum("      cchWritten, cchAskWrite ",cchWritten,cchAskWrite));

			/* ---- write was successful ---- */
			Scribble(3, ' ');
			while (cbp-- > 0)
				{    /* mark pages actually copied to disk as non dirty */
				(pbpsStart++)->fDirty = false;
#ifdef CKSM
#ifdef DEBUG
				{
				int ibpT = pbpsStart - 1  - mpibpbps;
					/* Recompute checksums for pages which are now clean */
				(**hpibpcksm) [ibpT] = CksmFromIbp( ibpT );
				}
#endif
#endif
				}
			}
		else
			{
			++ibp;
			++pbps;
			}
	}
return (TRUE);
}


#ifdef DEBUG
CheckIbp()
	{
	/* Walk through the rgibpHash and the mpibpbps structure to make sure all of
	the links are right. */

	/* 10/11/85 - Added extra Assert ( FALSE ) so we get the messages instead
                  of a freeze-up on systems not connected to a COM port.
                  Ignoring the assertion will produce the RIP with the ibp info */
	extern int fIbpCheck;
	int rgfibp[255];
	int ibp;
	int ibpHash;
	int iibp;
	static BOOL bAsserted=FALSE;

	if (fIbpCheck && !bAsserted)
		{
		if (!(ibpMax < 256))
		{
			Assert(0);
			bAsserted=TRUE;
			return;
		}

		bltc(rgfibp, false, ibpMax);

		/* Are there any circular links in mpibpbps? */
		for (iibp = 0; iibp < iibpHashMax; iibp++)
			{
			if ((ibpHash = rgibpHash[iibp]) != ibpNil)
				{
				if (!(ibpHash < ibpMax))
				{
					Assert(0);
					bAsserted=TRUE;
					return;
				}
				if (rgfibp[ibpHash])
					{
					/* Each entry in rgibpHash should point to an unique chain.
					*/
					Assert(0);
					bAsserted=TRUE;
#if DUGSTUB
					FatalExit(0x100 | ibp);
#endif
					return;
					}
				else
					{
					rgfibp[ibpHash] = true;
					while ((ibpHash = mpibpbps[ibpHash].ibpHashNext) != ibpNil)
						{
						if (!(ibpHash < ibpMax))
						{
							Assert(0);
							bAsserted=TRUE;
							return;
						}
						if (rgfibp[ibpHash])
							{
							/* The chain should be non-circular and unique. */
							Assert( FALSE );
							bAsserted=TRUE;
#if DUGSTUB
							FatalExit(0x200 | ibpHash);
#endif
							return;
							}
						rgfibp[ibpHash] = true;
						}
					}
				}
			}

		/* All chains not pointed to by rgibpHash should be nil. */
		for (ibp = 0; ibp < ibpMax; ibp++)
			{
			if (!rgfibp[ibp])
				{
				if (mpibpbps[ibp].fn != fnNil)
					{
					Assert( FALSE );
					bAsserted=TRUE;
#if DUGSTUB
					FatalExit(0x400 | mpibpbps[ibp].fn);
#endif
					return;
					}
				if (mpibpbps[ibp].ibpHashNext != ibpNil)
					{
					Assert( FALSE );
					bAsserted=TRUE;
#if DUGSTUB
					FatalExit(0x300 | ibp);
#endif
					return;
					}
				}
			}
		}
	}
#endif /* DEBUG */


/* Formerly fileOC.c -- file open and close routines */


/***		SetRfnMac - set usable # of rfn slots
 *
 *	ENTRY: crfn - desired # of rfn slots
 *	EXIT:  (global) rfnMac - set to crfn, if possible
 *
 *	The ability to adjust the usable # of rfn slots is a new addition in
 *	Windows Word.  The two things that it accomplishes are:
 *
 *			(1) Gives the ability for Word to scale back its need for
 *				DOS file handles if not enough are available
 *			(2) Permits Word to attempt to grab more file handles than usual
 *				when this would help performance (in particular, during
 *				Transfer Save, when the original, scratch, and write files
 *				are most commonly open)
 *
 */

SetRfnMac( crfn )
int crfn;
{
 int rfn;

 Assert( (crfn > 0) && (crfn <= rfnMax) );
 Assert( (sizeof (struct ERFN) & 1) == 0);	/* ERFN must be even for blt */

 if (crfn > rfnMac)
	{	/* Add rfn slots */

	for ( rfn = rfnMac; rfn < crfn; rfn++ )
		dnrfn [rfn].fn = fnNil;		/* These will get used next (see RfnGrab)*/
	rfnMac = crfn;
	}

 else	/* Lose Rfn slots (keep the most recently used one(s)) */
	while ( rfnMac > crfn )
		{
		int rfnLru=RfnGrab();
		int fn;

		if ( (rfnLru != --rfnMac) && ((fn = dnrfn [rfnMac].fn) != fnNil) )
			{
			extern int fnMac;

			Assert( fn >= 0 && fn < fnMac );
			(**hpfnfcb) [fn].rfn = rfnLru;
			blt( &dnrfn [rfnMac], &dnrfn [rfnLru],
                                     sizeof(struct ERFN)/sizeof(int) );
			}
		}
}





/*========================================================*/
STATIC int near RfnGrab()
{ /*
		Description:	Allocate the least recently used rfn (real file #)
						slot for a new file.
		Returns:		rfn slot number. */

 int rfn = 0, rfnLru = 0;
 typeTS ts, tsLru;
 struct ERFN *perfn = &dnrfn[rfn];

 /* Time stamp algorithm akin to method used with Bps. */
 /* See IbpLru in file.c for comments. */

 tsLru = -1;     /* max unsigned number */
 for ( rfn = 0; rfn < rfnMac ; rfn++, perfn++ )
	{
	ts = perfn->ts - (tsMruRfn + 1);
	if (perfn->fn == fnNil)
		ts = ts - rfnMac;

		/* cluge: If slot is unused, give it a lower ts. */
		/* This ensures that an occupied slot can never be */
		/* swapped out if a empty one exists. */
	if (ts <= tsLru)
		{
		tsLru = ts;
		rfnLru = rfn;
		}
	}

 if (dnrfn [rfnLru].fn != fnNil)
	CloseRfn( rfnLru );
 return rfnLru;
}




CloseFn( fn )
int fn;
{	/* Close file fn if it is currently open */
 int rfn;

 if (fn == fnNil)
	return;

 if (((rfn = (**hpfnfcb)[fn].rfn) != rfnNil) && (rfn != rfnFree))
	CloseRfn( rfn );
}




OpenEveryHardFn()
{	/* For each fn representing a file on nonremoveable media,
       try to open it.	It is not guaranteed that any or all such files
       will be open on return from this routine -- we are merely attempting
       to assert our ownership of these files on a network by keeping them open */

 extern int fnMac;
 int fn;
 struct FCB *pfcb;

#ifdef DFILE
extern int docCur;
CommSzNum("OpenEveryHardFn: docCur ", docCur);
#endif

 for ( fn = 0, pfcb = &(**hpfnfcb) [0] ; fn < fnMac; fn++, pfcb++ )
	{
/* Bryanl 3/26/87: only call FAccessFn if rfn == rfnNil, to prevent
   multiple opens of the same file, which fail if the sharer is loaded */
#ifdef DFILE
	{
	char rgch[100];
	if (pfcb->rfn == rfnNil && ((POFSTRUCT)(pfcb->rgbOpenFileBuf))->fFixedDisk)
		{
		wsprintf(rgch,"					fn %d, hszFile %s \n\r",fn,(LPSTR)(**pfcb->hszFile));
		CommSz(rgch);
		wsprintf(rgch,"                        OFSTR   %s \n\r", (LPSTR)(((POFSTRUCT)pfcb->rgbOpenFileBuf)->szPathName));
		CommSz(rgch);
		}
	else
		{
		wsprintf(rgch,"					fn %d, not accessing, sz %s\n\r", fn, (LPSTR) (LPSTR)(**pfcb->hszFile));
		CommSz(rgch);
		wsprintf(rgch,"                        OFSTR   %s \n\r", (LPSTR)(((POFSTRUCT)pfcb->rgbOpenFileBuf)->szPathName));
		CommSz(rgch);
		}
	}
#endif
	if (pfcb->rfn == rfnNil && ((POFSTRUCT)(pfcb->rgbOpenFileBuf))->fFixedDisk)
		{	/* fn refers to a file on nonremoveable media */
		FAccessFn( fn, dtyNormal );
		}
	}
}




STATIC typeOSFN near OsfnReopenFn( fn )
int fn;
{	/* Reopen file fn after it was automatically closed by Windows due
       to disk swap. State is: fn has an rfn but rfn's osfn has been
       made a "bad handle" */

 struct FCB *pfcb = &(**hpfnfcb) [fn];
 int rfn = pfcb->rfn;
 typeOSFN osfn;
 WORD wOpen;

 Assert( fn != fnNil );
 Assert( rfn != rfnNil );
 Assert( pfcb->fOpened);

	/* Only files on floppies are automatically closed */
 Assert( ! ((POFSTRUCT)(pfcb->rgbOpenFileBuf))->fFixedDisk );

#ifdef DEBUG
#ifdef DFILE
 CommSzSz( "Opening after WINDOWS close: ", **pfcb->hszFile );
#endif
#endif

 wOpen = OF_REOPEN | OF_PROMPT | OF_CANCEL | OF_SHARE_DENY_WRITE |
              ((pfcb->mdFile == mdBinary) ? OF_READWRITE : OF_READ );

 SetErrorMode(1);
 osfn = OpenFile( (LPSTR) SzPromptFromFn( fn ),
                  (LPOFSTRUCT) pfcb->rgbOpenFileBuf, wOpen );
 SetErrorMode(0);

 if (osfn == -1)
	return osfnNil;
 else
	{
	dnrfn[ rfn ].osfn = osfn;
	}
 return osfn;
}




FAccessFn( fn, dty)
int fn;
int  dty;
{ /*	Description:	Access file which is not currently opened.
						Open file and make an appropriate entry in the
						rfn table.	Put the rfn into (**hpfnfcb)[fn].rfn.
		Returns:		TRUE on success, FALSE on failure
  */
extern int vwDosVersion;
extern HANDLE hParentWw;
extern HWND vhWndMsgBoxParent;

int rfn;
register struct FCB *pfcb = &(**hpfnfcb)[fn];
typeOSFN osfn;
int wOpen;

#ifdef DEBUG
int junk;

 Assert(FValidFile(**pfcb->hszFile, CchSz(**pfcb->hszFile)-1, &junk));

#ifdef DFILE
 {
 char rgch[100];

 CommSzSz("FAccessFn: ", pfcb->fOpened ? SzPromptFromFn( fn ) : &(**pfcb->hszFile)[0]);
 wsprintf(rgch, "  * OFSTR before %s \n\r", (LPSTR)(((POFSTRUCT)pfcb->rgbOpenFileBuf)->szPathName));
 CommSz(rgch);
 }
#endif
#endif /*DEBUG*/

 if ((**pfcb->hszFile)[0] == 0)  /* if file name field is blank, */
	return FALSE;			/* unable to open file */

 wOpen = /*OF_PROMPT + OF_CANCEL + (6.21.91) v-dougk bug #6910 */
						(((pfcb->mdFile == mdBinary) ? 
							OF_READWRITE : OF_READ) | 
								OF_SHARE_DENY_WRITE);
 if (pfcb->fOpened)
	wOpen += OF_REOPEN;
 else if (pfcb->fSearchPath)
	wOpen += OF_VERIFY;

 if ((vwDosVersion & 0x7F) >= 2)
	{
	WORD da;

	if ((vwDosVersion & 0x7F) >= 3)
			/* Above DOS 3, set attributes to deny access if the sharer is in */
		wOpen += bSHARE_DENYRDWR;

	if ( ( (pfcb->mdFile == mdBinary) && (!pfcb->fOpened)) &&
         ((da = DaGetFileModeSz( &(**pfcb->hszFile) [0] )) != DA_NIL) &&
         (da & DA_READONLY) )
		{	/* This is here because the Novell net does not allow us to test
               for read-only files by opening in read/write mode -- it lets
               us open them anyway! */
		goto TryReadOnly;
		}
	}

 for ( ;; )
	{
			/* OpenFile's first parm is a filename when opening for the
               first time, a prompt on successive occasions (OF_REOPEN) */

	SetErrorMode(1);
	osfn = OpenFile( pfcb->fOpened ?
                           (LPSTR) SzPromptFromFn( fn ) :
                           (LPSTR) &(**pfcb->hszFile)[0],
                     (LPOFSTRUCT) &pfcb->rgbOpenFileBuf[0],
                     wOpen );
	SetErrorMode(0);

	if (osfn != -1)		/* Note != -1: osfn is unsigned */
		{    /* Opened file OK */
#ifdef DFILE		
		{
		char rgch[100];
		wsprintf(rgch, "  * OFSTR now  %s \n\r", (LPSTR)(((POFSTRUCT)(**hpfnfcb) [fn].rgbOpenFileBuf)->szPathName));
		CommSz(rgch);
		}
#endif

		if (!pfcb->fOpened)
			{	/* First time through: OpenFile may have given us a
                   different name for the file */

			CHAR szT [cchMaxFile];
			CHAR (**hsz) [];

#if WINVER >= 0x300            
			/* Currently: FNormSzFile  *TAKES*   an OEM sz, and
                                      *RETURNS*  an ANSI sz ..pault */
#endif

			if (FNormSzFile( szT, ((POFSTRUCT)pfcb->rgbOpenFileBuf)->szPathName,
                             dty ) &&
                       WCompSz( szT, &(**pfcb->hszFile) [0] ) != 0 &&
                       FnFromSz( szT ) == fnNil &&
                       !FNoHeap( hsz = HszCreate( szT )))
				{
					/* Yes, indeed, the name OpenFile gave us was different.
                       Put the normalized version into the fcb entry */

				FreeH( (**hpfnfcb) [fn].hszFile );	/* HEAP MOVEMENT */
				(**hpfnfcb) [fn].hszFile = hsz;
				}
			}
		break;	/* We succeeded; break out of the loop */
		}
	else
		{	/* Open failed -- try read-only; don't prompt this time */
		if ( (pfcb->mdFile == mdBinary) && (!pfcb->fOpened) )
			{	/* Failed as read/write; try read-only */

			/* Check for sharing violation */

			if (((vwDosVersion & 0x7F) >= 3) &&
				(((POFSTRUCT) pfcb->rgbOpenFileBuf)->nErrCode == nErrNoAcc))
				{
				if ( DosxError() == dosxSharing )
                  {
                  extern int vfInitializing;
                  int fT = vfInitializing;

                  vfInitializing = FALSE;	/* Report this err, even during inz */
				{
					char szMsg[cchMaxSz];
					MergeStrings (IDPMTCantShare, **pfcb->hszFile, szMsg);
					IdPromptBoxSz(vhWndMsgBoxParent ? vhWndMsgBoxParent : hParentWw, szMsg, MB_OK|MB_ICONEXCLAMATION);
				}
                  vfInitializing = fT;
                  return FALSE;
                  }
				}

TryReadOnly:
			pfcb->mdFile = mdBinRO;
			wOpen = OF_READ;
			if (pfcb->fOpened)
				wOpen += OF_REOPEN;
#ifdef ENABLE
			else if (pfcb->fSearchPath)
               wOpen += OF_VERIFY;
#endif
			}
		else
			{
				if ((**hpfnfcb)[fn].fDisableRead)
				/* already reported */
				{
					ferror = TRUE;
					return FALSE;
				}
				else
				{	/* Could not find file in place specified */
				char szMsg[cchMaxSz];
				extern int ferror;
				extern int vfInitializing;
				int fT = vfInitializing;
				BOOL bRetry=TRUE;
				extern struct DOD      (**hpdocdod)[];
				extern int         docCur;

				/* get user to put file back */
				MergeStrings (IDPMTFileNotFound, **pfcb->hszFile, szMsg);

				vfInitializing = FALSE;   /* Report this err, even during inz */

				/* 
					This is frippin insidious.	MessageBox yields and allows us to get
					into here again before it has even been issued!
				*/

				(**hpdocdod)[docCur].fDisplayable = FALSE; // block redraws

				/* if we're being called from a message box, then use it
                   as the parent window, otherwise use main write window */
				if (IdPromptBoxSz(vhWndMsgBoxParent ? vhWndMsgBoxParent : hParentWw, 
                              szMsg, MB_OKCANCEL | MB_ICONEXCLAMATION | MB_APPLMODAL)
                      == IDCANCEL)
				{
					vfInitializing = fT;
					(**hpfnfcb)[fn].fDisableRead = TRUE;
					ferror = TRUE; /* need to flag */
					bRetry = FALSE;
				}

				(**hpdocdod)[docCur].fDisplayable = TRUE; // unblock redraws

				vfInitializing = fT;

				if (!bRetry)
					return FALSE;
				}
			}
		}
	}

 pfcb->fOpened = TRUE;

 rfn = RfnGrab();
 {
 struct ERFN *perfn = &dnrfn [rfn];

 perfn->osfn = osfn;
 perfn->fn   =	fn;
 perfn->ts   = ++tsMruRfn;     /* mark Rfn as MRused */
 }
 (**hpfnfcb) [fn].rfn = rfn;
 (**hpfnfcb) [fn].fDisableRead = FALSE;
 return TRUE;
}



FCreateFile( szFile, fn )	/* returns szFile in ANSI ..pault */
CHAR *szFile;
int fn;
{		/*	Create a new, unique file.
			Return the name in szFile.
			Returns TRUE on success, FALSE on failure.
			Leaves the filename in (**hpfnfcb)[fn].hszFile (if successful),
			the rfn in (**hpfnfcb)[fn].rfn.
			If szFile begins "X:...", creates the file on the specified drive;
			otherwise, creates the file on a drive of Windows' choice */

	extern CHAR szExtDoc[];
	CHAR (**hsz)[];
	CHAR szFileT [cchMaxFile];

	if (!GetTempFileName(szFile[0] | ((szFile[1] == ':') ? TF_FORCEDRIVE : 0),
                         (LPSTR)(szExtDoc+1), 0, (LPSTR)szFileT) )
		{    /* can't create file */
		DiskErrorWithMsg( IDPMTSDE, " RfnCreate" );

		/* recovery is accomplished: only FnCreateSz calls FCreateFile.
           FnCreateSz returns nil if FCreateFile returns FALSE.  All of
               FnCreateSz's callers check for nil */
		return FALSE;

		}

	/* Succeeded in creating file */

	FNormSzFile( szFile, szFileT, dtyNormal );

	if ( FNoHeap( hsz = HszCreate( (PCH)szFile )))
		return FALSE;

	(**hpfnfcb) [fn].hszFile = hsz;

	if ( !FAccessFn( fn, dtyNormal ) )
		return FALSE;

	return TRUE;
} /* end of  F C r e a t e F i l e	*/



FEnsureOnLineFn( fn )
int fn;
{		/* Ensure that file fn is on line (i.e. on a disk that is accessible).
           Return TRUE if we were able to guarantee this, FALSE if not */
 int rfn;

 Assert( fn != fnNil );

 if ( ((POFSTRUCT)(**hpfnfcb) [fn].rgbOpenFileBuf)->fFixedDisk )
		/* If it's on nonremovable media, we know it's on line */
	return TRUE;

 /* If it's open, must close and re-open, cause windows might have closed it */
 if ((rfn = (**hpfnfcb) [fn].rfn) != rfnNil)
	CloseRfn( rfn );

 return FAccessFn( fn, dtyNormal );
}




typePN PnAlloc(fn)
int fn;
{ /* Allocate the next page of file fn */
		typePN pn;
		struct BPS *pbps;
		struct FCB *pfcb = &(**hpfnfcb)[fn];

		AlignFn(fn, (int)cfcPage, false);
		pn = pfcb->fcMac / cfcPage;
		pbps = &mpibpbps[IbpEnsureValid(fn, pn)];
		pbps->cch = cfcPage;
		pbps->fDirty = true;
		pfcb->fcMac += cfcPage;
		pfcb->pnMac = pn + 1;
		return pn;
}




STATIC CHAR  *(near SzPromptFromFn( fn ))
int fn;
{
 extern int vfnSaving;
 CHAR *pch;

 Assert( fn != fnNil );

 if (fn == vfnSaving)
	pch = szSaveFilePrompt;
 else if (fn == fnScratch)
	pch = szScratchFilePrompt;
 else
	pch = szWriteDocPrompt;

 return pch;
}