/*static char *SCCSID = "@(#)startlw.c	6.1 90/11/18";*/
/*static char *SCCSID = "@(#)startlw.c      13.5 90/02/07";*/

#ifdef ERROR_HANDLER
/* BUGBUG: Make this file internationalizable. */
#endif /* ERROR_HANDLER */
/*
 * BUGBUG: Find another way of getting invoked, so that multiple calls don't
 *	   happen.
 */

/*
 * This file contains the Lazy Writer (LW) daemon.  This code is contained
 * in a DLL (StartLW.DLL).  The oridinal 1 function from this DLL is invoked
 * automatically by the PM shell.
 *
 * Actually, this function is called multiple times, and we only want to
 * bother doing anything the first time, so we've got a global flag (fFirstTime)
 * which is used to keep the second through Nth invocations from wasting time.
 * (Because the DosFSCtl call will be harmless in the other invocations, we
 * don't bother with a 100% perfect mechanism for preventing the other
 * invocations.)
 *
 * FUNCTIONS DEFINED IN THIS FILE -
 *
 *	Global		Local
 *	---------	-------------
 *	StartLazyWriter
#ifdef DEBUG
 *			DebugNum
 *			DebugStr
#endif
 *			LWThread
#ifdef ERROR_HANDLER
 *			StartLWError
 *			WaitForEnterKey
 *			WriteNum
 *			WriteStr
#endif
 *
 * PORTABILITY -
 *	Runs in OS/2 protected mode only.
 *	Requires Microsoft C v5.1 or later.
 *	Must be compiled with CL -Au (SS!=DS, reload DS) parameter.
 *	Debug version uses stderr, DosWrite(), and generates English messages.
 * HISTORY
 *	29-Jan-90 by JR (John Rogers, JohnRo@Microsoft)
 *	    - PTR B710646: don't set entire process as time-critical.
 *	19-Sep-89 by JR (John Rogers, JohnRo@Microsoft)
 *	    - Avoid warnings from Microsoft C 5.2 (on slash-slash comments).
 *	    - Changed format and usage of SCCSID.
 *	16-May-1989 by JR (John Rogers, JohnRo@Microsoft)
 *	    - ifdef'ed error handling, which seems to cause HMQ errors under PM.
 *	    - Changed STACK_SIZE from 4096 to 2048.
 *	15-May-1989 by JR (John Rogers, JohnRo@Microsoft)
 *	    - Changed to use small model (changed to use EXPENTRY, which has
 *	      _loadds keyboard).	BUGBUG - blows up with /Aw!!!!
 *	    - Changed to use popup for error messages.
 *	    - PTR B783792: don't kill caller's thread accidently.
 *	    - Changed WriteNum to not use C runtime routines.
 *	    - Other minor opimizations.
 *	    - Added FORCE_ERROR #ifdef, to allow testing of error handling.
 *	07-May-1989 by JR (John Rogers, JohnRo@Microsoft)
 *	    - Fixed fFirstTime usage.
 *	    - Changed to use STATIC #define for local functions.
 *	    - Changed most functions to "NEAR" (smaller code size).
 *	    - Delete FarStrLen (changed to use strlen()).
 *	    - Minor changes to debug stuff.
 *	05-May-1989 by JR (John Rogers, JohnRo@Microsoft)
 *	    - Changed DLL name from Cache.DLL to StartLW.DLL.
 *	    - Corrected declarations of WriteNum() and WriteStr() (not FAR).
 *	04-May-1989 by JR (John Rogers, JohnRo@Microsoft)
 *	    - Added DEBUG_BEEP option (DEBUG uses stderr, DEBUG_BEEP doesn't).
 *	    - Changed DEBUG option to use stderr.
 *	    - Added first time flag to speed up multiple invocations.
 *	02-May-1989 by JR (John Rogers, JohnRo@Microsoft)
 *	    - Created StartLW.c from scratch.
 */

/*
 * Define DEBUG if you want status messages sent to standard error.
 *	(NOTE: DEBUG messages just disappear under PM shell.)
 * Define DEBUG_BEEP if you want beeps (doesn't require stderr).
 * Define ERROR_HANDLER if you want to try handling errors with VioPopUp.
 *	(I've had problems with this -- JR.)
 * Define FORCE_ERROR if you want to test the error handling code.
 *	(You must #define ERROR_HANDLER too for FORCE_ERROR.)
 * (You can define more than one of these if you like.)
 */
#if defined(FORCE_ERROR) && !defined(ERROR_HANDLER)
   #err You must define ERROR_HANDLER too!
#endif /* defined... */

/*
 * Include files:
 */
#ifdef DEBUG
#define INCL_DOSDATETIME
#endif /* DEBUG */

#define INCL_DOSPROCESS
#ifdef ERROR_HANDLER
#define INCL_KBD
#define INCL_VIO
#endif /* ERROR_HANDLER */
#include <os2.h>			/* DosAllocSeg(), USHORT, etc. */
#include <string.h>			/* strlen(). */
#include "const.h"			/* STATIC; needed by "misc.h". */
#include "misc.h"			/* FSC_LW_BOTH. */

/*
 * Definitions of constants:
 */
#define STD_ERR_HANDLE	( (HFILE) 2)	/* File handle for stderr. */

#define RM_FH		1		/* Route via open file handle	      */
#define RM_PATH 	2		/* Route via pathname		      */
#define RM_FSD		3		/* Route via FSD name		      */

#define STACK_SIZE	2048		/* Size of child's stack in bytes. */

#ifdef DEBUG_BEEP
#define NOTE_MIDDLE_A	440		/* 440 hertz is middle "A" note. */
#define NOTE_HIGH_A	880		/* 880 hertz is high "A" note. */
#define NOTE_LENGTH	500		/* Note length = 500 milliseconds. */
#endif /* DEBUG_BEEP */

/*
 *  Global variable(s):
 */
INT __acrtused = 0;			/* Tell linker we don't want runtime. */
STATIC BOOL fFirstTime = TRUE;		/* Set to false after first time. */
STATIC PCHAR pcFSDName = "HPFS";	/* FSD name for DosFSCtl() calls. */

/*
 * Function prototypes (in the order they appear in this file):
 *	StartLazyWriter and LWThread must be "far" (that's what their callers
 *	are expecting).  All others can be "near", for small code size.
 */
VOID EXPENTRY StartLazyWriter(VOID);
#ifdef DEBUG
STATIC VOID NEAR PASCAL DebugNum(USHORT);
STATIC VOID NEAR PASCAL DebugStr(char *);
#endif /* DEBUG */
STATIC VOID FAR PASCAL LWThread(VOID);
#ifdef RAHEAD
STATIC VOID FAR PASCAL RAThread(VOID);
#endif /* RAHEAD */
#ifdef ERROR_HANDLER
STATIC VOID NEAR PASCAL StartLWError(char *, USHORT);
STATIC VOID NEAR PASCAL WaitForEnterKey(VOID);
STATIC VOID NEAR PASCAL WriteNum(USHORT);
STATIC VOID NEAR PASCAL WriteStr(char *);
#endif /* ERROR_HANDLER */

/*
 * FUNCTION -
 *	This routine is used to create the thread for the lazy writer deamon.
 * ENTRY -
 *	StartLazyWriter is executed as part of the dynlink initialization,
 *	as an automatically called global initialzation routine.  There are no
 *	parameters or other inputs.
 *
 *	The debug version of StartLazyWriter() requires that stderr be open and
 *	writable.
 * EXITS -
 *	StartLazyWriter() returns to its caller.  There is no indication of
 *	failure or success.  The deamon thread is left executing.
 */
VOID EXPENTRY
StartLazyWriter(void)
{
	PBYTE	pbChildStack;		/* Pointer to child's stack. */
	SEL	selChildStack;		/* Selector for child's stack. */
	TID	tidChild;		/* Child's thread ID. */
	USHORT	usRetCode;		/* DosCreateThread() return code. */

#ifdef DEBUG
	DebugStr(
	"StartLazyWriter: Lazy writer startup routine has been invoked.\n\r");
#endif /* DEBUG */
#ifdef DEBUG_BEEP
	(void) DosBeep(NOTE_MIDDLE_A, NOTE_LENGTH);
#endif /* DEBUG_BEEP */

	/*
	 * Check if we've already been invoked.  If so, don't do anything.
	 * Note that this check is not perfect - it's possible that multiple
	 * threads could get past the "if" statement before any of them hit
	 * the assignment statement.  However, the effect of multiple threads
	 * getting through isn't major: the second through Nth will just get
	 * rejected when they attempt to do the DosFSCtl.  This is purely an
	 * optimization, so it isn't worth protecting the flag with a semaphore
	 * or something.
	 */
	if (fFirstTime == FALSE)
		return;
	fFirstTime = FALSE;

#ifdef FORCE_ERROR
	StartLWError("StartLazyWriter: Forcing error (debug only)",
		(USHORT) 149);	/* Any number, printed as return code. */
#endif /* FORCE_ERROR */

	/* Allocate memory for stack. */
	usRetCode = DosAllocSeg(
		STACK_SIZE,		/* usSize: bytes wanted. */
		& selChildStack,	/* psel: pointer to selector. */
		(USHORT) 0);		/* fAlloc: unshared, nondiscardable */
	if (usRetCode != 0) {
#ifdef ERROR_HANDLER
		StartLWError("StartLazyWriter: Unable to allocate stack",
			usRetCode);
#endif /* ERROR_HANDLER */
		return;
	}
#ifdef DEBUG
	DebugStr("StartLazyWriter: Done allocating stack.\n\r");
#endif /* DEBUG */

	/* Set pointer to top of child's stack. */
	pbChildStack = MAKEP(selChildStack, STACK_SIZE);

	/* Create the child's thread, and invoke the child. */
	usRetCode = DosCreateThread(
		LWThread,	/* pfnFunction - function to invoke. */
		& tidChild,	/* ptidThread - child's thread ID. */
		pbChildStack);	/* pbThrdStack - ptr to thread's stack. */
#ifdef DEBUG
	DebugStr("StartLazyWriter: Invoked child, thread ID (decimal)=");
	DebugNum((USHORT) tidChild);
	DebugStr(".\n\r");
#endif /* DEBUG */
#ifdef ERROR_HANDLER
	if (usRetCode != 0)
		StartLWError("StartLazyWriter: Error creating thread",
			usRetCode);
#endif /* ERROR_HANDLER */

#ifdef RAHEAD
	/* Allocate memory for stack. */
	usRetCode = DosAllocSeg(
		STACK_SIZE,		/* usSize - bytes wanted. */
		& selChildStack,	/* psel - pointer to selector. */
		(USHORT) 0);	/* fAlloc - nonsharable, nondiscardable */
	if (usRetCode != 0) {
		return;
	}

	/* Set pointer to top of child's stack. */
	pbChildStack = MAKEP(selChildStack, STACK_SIZE);

	/* Create the child's thread, and invoke the child. */
	usRetCode = DosCreateThread(
		RAThread,	/* pfnFunction - function to invoke. */
		& tidChild,	/* ptidThread - child's thread ID. */
		pbChildStack);	/* pbThrdStack - ptr to thread's stack. */
#endif

	/*
	 * Time to go back home.  Allow the thread to continue forever.
	 * (Note: Must not do exit() or DosExit(EXIT_PROCESS) here, because
	 * these will kill the thread we just created.)
	 */
	return;

} /* StartLazyWriter() */


#ifdef DEBUG
/*
 * FUNCTION -
 *	This routine writes a number to a handle, using only OS/2 functions.
 *	This is a recursive routine.
 * ENTRY -
 *	Input arguments are the handle to do output to, followed by the number
 *	(an unsigned short) to be written.  The file handle must be open and
 *	writable.
 * EXITS -
 *	No return value.
 */
STATIC VOID NEAR PASCAL
DebugNum(USHORT usNum)
{
	CHAR	chDigit;		/* A digit to be written. */
	USHORT	cbActual;		/* Actual number written. */

	if (usNum >= 10)
		/* Call ourselves to do high digits. */
		DebugNum(usNum / 10);

	/* Display the low digit. */
	chDigit = (char) ( (usNum % 10) + '0' );  /* Low digit. */
	(void) DosWrite(
		STD_ERR_HANDLE, 	/* file handle */
		(PVOID) & chDigit,	/* pointer to buffer */
		1,			/* number of bytes to write */
		& cbActual);		/* Actual number written. */

} /* DebugNum() */


/*
 * FUNCTION -
 *	This routine writes a string to a handle, using only OS/2 functions.
 * ENTRY -
 *	Input arguments are the handle to do output to, followed by the string
 *	to be written.	The file handle must be open and writable.  Note that
 *	DosWrite does not convert newlines (\n) to carriage-return and line-feed
 *	pairs, so each \n in the input should be followed by \r (return).
 * EXITS -
 *	This routine does not return anything.
 */
STATIC VOID NEAR PASCAL
DebugStr(char * pszMessage)
{
	USHORT	cbActual;		/* actual number written */
	USHORT	cbStrLen;		/* number of bytes to write */

	/* Compute length of string... */
	cbStrLen = strlen(pszMessage);

	/* Display the string.	Ignore return code. */
	(void) DosWrite(
		STD_ERR_HANDLE, 	/* file handle */
		(PVOID) (PSZ) pszMessage,	/* pointer to buffer */
		cbStrLen,		/* number of bytes to write */
		(PUSHORT) & cbActual);	/* actual number of bytes written */

} /* DebugStr() */
#endif /* DEBUG */


/*
 * FUNCTION -
 *	This routine is the code which actually invokes the lazy writer
 *	cacheing functions.
 * ENTRY -
 *	LWThread() is started as a thread by StartLazyWriter().  There are no
 *	parameters or other inputs.
 *
 *	The debug version of LWThread() requires that stderr be open and
 *	writable.
 * EXITS -
 *	This routine should never exit.
 */
STATIC VOID FAR PASCAL
LWThread(void)
{
#ifdef DEBUG
	ULONG	ulTenSeconds = 10 * 1000;    /* 10 seconds in milliseconds. */
#endif /* DEBUG */
	USHORT	usDataAreaLenOut;	/* Pointer to data area len returned. */
	USHORT	usErr;			/* Error/return code. */
	USHORT	usParmLenOut;		/* Pointer to parm list len returned. */

#ifdef DEBUG
	DebugStr("LWThread: Started up OK.\n\r");
	(void) DosSleep(ulTenSeconds);	/* Delay for a bit, for debugging. */
	DebugStr("LWThread: After delay.\n\r");
#endif /* DEBUG */
#ifdef DEBUG_BEEP
	(void) DosBeep(NOTE_HIGH_A, NOTE_LENGTH);
#endif /* DEBUG_BEEP */
#ifdef FORCE_ERROR
	StartLWError("LWThread: Forcing error (debug only)",
		(USHORT) 100);	/* Any number, printed as return code. */
#endif /* FORCE_ERROR */

	/*
	 * Place ourselves as a low-TC process, with minimum priority + 1.
	 * (The read-ahead thread, if any, will get minimum priority.)
	 */
	usErr = DosSetPrty (PRTYS_THREAD,	/* Scope: thread only. */
			    PRTYC_TIMECRITICAL, /* Priority class. */
			    1,			/* Priority level (minimum+1).*/
			    0); 		/* Thread ID (0=current). */
#ifdef DEBUG
	DebugStr("LWThread: Done setting priority.\n\r");
#endif /* DEBUG */
	if (usErr != 0) {
#ifdef ERROR_HANDLER
		StartLWError("DosSetPrty returned error", usErr);
#endif /* ERROR_HANDLER */
		/* Kill thread (but not process). */
		DosExit(
			EXIT_THREAD,	/* fTerminate - this thread only. */
			usErr); 	/* usExitCode - error. */
	} /* if */

	/*
	 * Invoke the Lazy Writer file system control (FSCTL) operation.
	 * This should never return (but see exceptions listed below).
	 */
	usErr = DosFSCtl(
		NULL,		/* Pointer to data area. */
		0,		/* Data area len (input). */
		&usDataAreaLenOut,	/* Pointer to data area len returned. */
		NULL,		/* Pointer to parm list. */
		0,		/* Parm list len (input). */
		&usParmLenOut,	/* Pointer to parm list len returned. */
		FSC_LW_BOTH,	/* Function code: lazy writer daemon start. */
		pcFSDName,	/* File system driver name. */
		-1,		/* File handle (must be -1 for RM_FSD). */
		RM_FSD, 	/* Route method: file system driver. */
		0L);		/* Reserved: must be zero. */
	/*
	 * OK, we've just done an FSCtl.  If all goes well, it should never
	 * return.  However, if it does return, that doesn't necessarily mean
	 * that there's an error.  It could be (1) another thread is already
	 * running lazy writer, (2) somebody turned lazy writer off, or (3)
	 * an actual error.  In the first two cases, we should get a return
	 * code of 0.
	 */
#ifdef DEBUG
	DebugStr("LWThread: DosFSCtl returned.\n\r");
#endif /* DEBUG */

#ifdef ERROR_HANDLER
	if (usErr != 0)
		StartLWError("DosFSCtl both returned error", usErr);
#endif /* ERROR_HANDLER */

	/* Time to go back home.  Errors have already been handled elsewhere. */
	DosExit(
		EXIT_THREAD,		/* fTerminate - this thread only. */
		usErr); 		/* usExitCode - already set. */

} /* LWThread() */


#ifdef ERROR_HANDLER
/*
 * FUNCTION -
 *	This routine reports an initilialization error (using a popup).
 *	The user is requsted to press enter, to make sure he/she has read the
 *	message.  Most possible errors are ignored, as there's not much
 *	else we can do about them.
 * ENTRY -
 *	Input arguments are message (string) and return code (unsigned short).
 * EXITS -
 *	This routine returns to its caller.
 */
STATIC VOID NEAR PASCAL
StartLWError(char * pszMessage, USHORT usReturnCode)
{
	HVIO	hvio = 0;		/* No advanced video here. */
	USHORT	fPopUp = VP_OPAQUE | VP_WAIT;	/* Flags for type of popup: */
					/* Create our own, wait for previous. */
	USHORT	usColumn;		/* Original cursor column. */
	USHORT	usRow;			/* Original cursor row. */

	/* Find out where cursor is now (be polite to end user). */
	(void) VioGetCurPos(
		& usRow,		/* PUSHORT - row. */
		& usColumn,		/* PUSHORT - column. */
		hvio);			/* HVIO - advanced video handle. */

	/* Open up a popup window. */
	(void) VioPopUp(
		& fPopUp,		/* PUSHORT - popup type flags. */
		hvio);			/* HVIO - advanced video handle. */

	/* Write error message to screen. */
	WriteStr(
	  "***Cache daemon dynlink (StartLW.DLL) initialization error:\n\r...");
	WriteStr(pszMessage);
	WriteStr("\n\r...Return code = ");
	WriteNum(usReturnCode);
	WriteStr("\n\r");

	/* Tell user how severe this error is. */
	WriteStr(
	    "Note: This condition will only affect the system's speed.\n\r...");

	/* Wait for user to press a key. */
	WriteStr("Press enter to continue.\n\r");
	WaitForEnterKey();

	/*
	 * End the popup.  This clears the popup from the screen but doesn't
	 * reposition the cursor.
	 */
	(void) VioEndPopUp(hvio);

	/* Now, put cursor back where it was. */
	(void) VioSetCurPos(
		usRow,			/* USHORT - row. */
		usColumn,		/* USHORT - column. */
		hvio);			/* HVIO - advanced video handle. */

} /* StartLWError() */


/*
 * BUGBUG: Possible errors are just ignored in this routine.  Problem?
 */
STATIC VOID NEAR PASCAL
WaitForEnterKey(VOID)
{
	CHAR		achBuffer[1];	/* Single-character buffer. */
	HKBD		hkbd = 0;	/* Handle for keyboard (default). */
	STRINGINBUF	kbsiBuf;	/* String length structure. */

	/*
	 * No need to open the keyboard (default keyboard is open), and
	 * no need to get the focus (already done when the popup was
	 * created).
	 */

	/* Flush the buffer, just in case. */
	(void) KbdFlushBuffer(hkbd);

	/* Get a key. */
	kbsiBuf.cb = sizeof(achBuffer);
	(void) KbdStringIn(
		achBuffer,	/* Pointer to input buffer. */
		& kbsiBuf,	/* Address of length structure. */
		IO_WAIT,	/* Flag saying to wait for key. */
		hkbd);		/* Handle for keyboard. */

} /* WaitForEnterKey() */


/*
 * FUNCTION -
 *	This routine writes a number to a handle, using only VIO functions.
 *	This is a recursive routine.
 * ENTRY -
 *	Input argument is the number (an unsigned short) to be written.
 *	Standard VIO handle must be writable.
 * EXITS -
 *	No return value.
 */
STATIC VOID NEAR PASCAL
WriteNum(USHORT usNum)
{
	CHAR	chDigit;		/* A digit to be written. */

	if (usNum >= 10)
		/* Call ourselves to do high digits. */
		WriteNum(usNum / 10);

	/* Display the low digit. */
	chDigit = (char) ( (usNum % 10) + '0' );  /* Low digit. */
	(void) VioWrtTTY(
		(PCH) & chDigit,	/* Pointer to buffer. */
		(USHORT) 1,		/* Number of bytes to write. */
		(HVIO) 0);		/* Video handle (none). */

} /* WriteNum() */


/*
 * FUNCTION -
 *	This routine writes a string to a handle, using only VIO functions.
 * ENTRY -
 *	WriteStr is passed the string to be open.  The default VIO handle
 *	must be open.  Note that
 *	VIO does not convert newlines (\n) to carriage-return and line-feed
 *	pairs, so each \n in the input should be followed by \r (return).
 * EXITS -
 *	This routine does not return anything.
 */
STATIC VOID NEAR PASCAL
WriteStr(char * pszMessage)
{
	USHORT	cbStrLen;		/* number of bytes to write */

	/* Compute length of string... */
	cbStrLen = strlen(pszMessage);

	/* Display the string.	Ignore return code. */
	(void) VioWrtTTY(
		(PCH) pszMessage,	/* Pointer to buffer. */
		cbStrLen,		/* number of bytes to write */
		(HVIO) 0);		/* Video handle (none). */

} /* WriteStr() */
#endif /* ERROR_HANDLER */



#ifdef RAHEAD
STATIC VOID FAR PASCAL
RAThread(void)
{
	USHORT	usDataAreaLenOut;	/* Pointer to data area len returned. */
	USHORT	usErr;			/* Error/return code. */
	USHORT	usParmLenOut;		/* Pointer to parm list len returned. */

	/* Place ourselves as a low-TC process, with prio < LW. */
	usErr = DosSetPrty (PRTYS_THREAD,	/* Scope: thread only. */
			    PRTYC_TIMECRITICAL, /* Priority class. */
			    0,			/* Priority level (min). */
			    0); 		/* Thread ID (0=current). */
	if (usErr != 0) {
		/* Kill thread (but not process). */
		DosExit(
			EXIT_THREAD,	/* fTerminate - this thread only. */
			usErr); 	/* usExitCode - error. */
	} /* if */

	/*
	 * Invoke the Read Ahead file system control (FSCTL) operation.
	 * This should never return (but see exceptions listed below).
	 */
	usErr = DosFSCtl(
		NULL,		/* Pointer to data area. */
		0,		/* Data area len (input). */
		&usDataAreaLenOut,	/* Pointer to data area len returned. */
		NULL,		/* Pointer to parm list. */
		0,		/* Parm list len (input). */
		&usParmLenOut,	/* Pointer to parm list len returned. */
		FSC_RAHEAD,	/* Function code: lazy writer daemon start. */
		pcFSDName,	/* File system driver name. */
		-1,		/* File handle (must be -1 for RM_FSD). */
		RM_FSD, 	/* Route method: file system driver. */
		0L);		/* Reserved: must be zero. */
	/*
	 * OK, we've just done an FSCtl.  If all goes well, it should never
	 * return.  However, if it does return, that doesn't necessarily mean
	 * that there's an error.  It could be (1) another thread is already
	 * running read ahead, or (2) an actual error.	In the first case,
	 * we should get a return code of 0.
	 */

	/* Time to go back home.  Errors have already been handled elsewhere. */
	DosExit(
		EXIT_THREAD,		/* fTerminate - this thread only. */
		usErr); 		/* usExitCode - already set. */

} /* RAThread() */
#endif
