/*static char *SCCSID = "@(#)cache.c	6.1 90/11/18";*/
/*static char *SCCSID = "@(#)cache.c 13.9 90/02/21";*/

/***	cache.c - Ring 3 code for lazy write daemon
 *
 *	cache.exe is used to give a thread of execution to pinball for periodic
 *	flushing of the cache.
 *
 *	HISTORY
 *	    31-Jan-90 by JR (John Rogers, JohnRo@Microsoft)
 *		- PTR B710646: don't set entire process as time-critical.
 */

#define INCL_DOSPROCESS
#include <os2def.h>
#include <bse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <basemid.h>
#include "lw.h"
#include "const.h"
#include "misc.h"
#include "pbmacros.h"
#include "message.h"


/* used for checking bounds on command line arguments */
#define ULONG_LIMIT "4294967295"

#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 SHIFT(c,v)	{(c)--; (v)++;}

/*
 * declaration for COMPUTMSG needs cdecl parameter protocol.	*
 *   Without cdecl, parameter order is incorrect.		*
 *   comsub.h prototype does not have this so not just included *
 */
extern unsigned cdecl COMPUTMSG (CHAR **, unsigned, unsigned, CHAR *, unsigned,
				unsigned, CHAR *, unsigned);



/*  Prototype declarations */

void pascal GetParms (LWPARMS *pLWParms);
void pascal SetParms (LWPARMS *pLWParms);
void LWDaemon (void);
void StopDaemon (void);
void pascal Usage (CHAR *szCmd);
void pascal Display (LWPARMS *pLWParms);
ULONG pascal Ascii_2_ULong(CHAR *num_str, CHAR *cmd_str);

USHORT usParmLenOut;		/* pointer to parm list len returned */
USHORT usDataAreaLenOut;	/* pointer to data area len returned */
PCHAR pcFSDName = "HPFS";	/* fsd name used for dosfsctlcalls */

UCHAR msgfile[] = "OSO001.MSG";

UCHAR *msg_parms[3];
UCHAR outbuf[10];  /* Make buffer for COMPUTMSG parameter */


#define NO_PARM_MESSAGE(msg)	COMPUTMSG (NULL, 0, (msg), msgfile,\
				     STDERR_CODE, NO_RESPONSE, NULL, 0)

#define PARM_MESSAGE(num_parms, msg) \
			COMPUTMSG (msg_parms, (num_parms), (msg), msgfile,\
			     STDOUT_CODE, NO_RESPONSE, NULL, 0)


/**	GetParms - retrieve current LW Parms
 *
 *	pLWParms    pointer to LWParms packet to be filled in
 */
void pascal
GetParms (LWPARMS *pLWParms)
{
#ifndef DEBUG2
    USHORT usErr;

    usErr = DosFSCtl ((PVOID) pLWParms, /* data area */
		      sizeof (LWPARMS), /* data area len in */
		      &usDataAreaLenOut,/* pointer to data area len returned */
		      NULL,		/* parm list */
		      0,		/* parm list len in */
		      &usParmLenOut,	/* pointer to parm list len returned */
		      FSC_LW_GET,	/* function code */
		      pcFSDName,	/* route name */
		      -1,		/* file handle */
		      RM_FSD,		/* route method */
		      0L);		/* reserved */

    if (usErr != 0) {
	/* Set array of parameters to point to buffer with parameter as string */
	msg_parms[0] = outbuf;

	/* Convert number to string */
	sprintf(outbuf, "%5u", usErr);

	/* Print message using built array and number of parameters indicated */
	PARM_MESSAGE(1, MSG_HPFS_GET_RETURNED);
    }
#else
    printf ("GetParms:\n");
    pLWParms;
#endif
} /* end GetParms() */



/**	SetParms - set up new LW Parms
 *
 *	pLWParms    pointer to LWParms packet containing new data
 */
void pascal
SetParms (LWPARMS *pLWParms)
{
#ifndef DEBUG2
    USHORT usErr;

    usErr = DosFSCtl (NULL,
		      0,
		      &usDataAreaLenOut,/* pointer to data area len returned */
		      (PVOID) pLWParms,
		      sizeof (LWPARMS),
		      &usParmLenOut,	/* pointer to parm list len returned */
		      FSC_LW_SET,
		      pcFSDName,
		      -1,
		      RM_FSD,
		      0L);
    if (usErr != 0) {
	/* Set array of parameters to point to buffer with parameter as string */
	msg_parms[0] = outbuf;

	/* Convert number to string */
	sprintf(outbuf, "%5u", usErr);

	/* Print message using built array and number of parameters indicated */
	PARM_MESSAGE(1, MSG_HPFS_SET_RETURNED);
    }
#else
    printf ("SetParms:\n");
    pLWParms;
#endif
} /* end SetParms() */




/**	LWDaemon - lazy write daemon
 *
 *	LWDaemon is a simple loop, blocking until flushing is necessary
 *	and the flushing as appropriate.
 */
void
LWDaemon ()
{
#ifndef DEBUG2
    USHORT	usErr;

    /*
     * Place ourselves as a low-TC process; reserve minimum TC prio for
     * read ahead thread.
     */
    usErr = DosSetPrty (PRTYS_THREAD,		/* Scope: thread only. */
			PRTYC_TIMECRITICAL,	/* Priority class. */
			1,			/* Priority level (minimum+1).*/
			0);			/* Thread ID (0=current). */
    if (usErr != 0) {
	/* Set array of parameters to point to buffer with parameter as string */
	msg_parms[0] = outbuf;

	/* Convert number to string */
	sprintf(outbuf, "%5u", usErr);

	/* Print message using built array and number of parameters indicated */
	PARM_MESSAGE(1, MSG_HPFS_SETPRTYRETURNED);
    }

    usErr = DosFSCtl (NULL,
		      0,
		      &usDataAreaLenOut,/* pointer to data area len returned */
		      NULL,
		      0,
		      &usParmLenOut,	/* pointer to parm list len returned */
		      FSC_LW_BOTH,
		      pcFSDName,
		      -1,
		      RM_FSD,
		      0L);
    if (usErr != 0) {
	/* Set array of parameters to point to buffer with parameter as string */
	msg_parms[0] = outbuf;

	/* Convert number to string */
	sprintf(outbuf, "%5u", usErr);

	/* Print message using built array and number of parameters indicated */
	PARM_MESSAGE(1, MSG_HPFS_BOTH_RETURNED);
    }
#else
    printf ("LWDaemon:\n");
#endif
} /* end LWDaemon() */


#ifdef RAHEAD

/**	RAThread - read ahead daemon
 *
 *	A simple loop, blocking as long as there are no pending requests
 *	do read ahead as appropriate.
 */

STATIC VOID FAR PASCAL
RAThread(void)
{
	USHORT	usDataAreaLenOut;	/* Dummy		*/
	USHORT	usErr;			/* Error/return code	*/
	USHORT	usParmLenOut;		/* Dummy		*/

	/* Place ourselves as a low-TC process, with prio < LW. */
	usErr = DosSetPrty (PRTYS_THREAD,	/* Scope: thread only. */
			    PRTYC_TIMECRITICAL, /* Priority class. */
			    0,			/* Priority level (minimum). */
			    0);			/* Thread ID (0=current). */
	if (usErr == 0) {

	    /* 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,	       /* Func code: read ahead daemon start*/
		    pcFSDName,	       /* File system driver name	    */
		    -1,		       /* File handle (-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



void
StopDaemon ()
{
#ifndef DEBUG2
    USHORT usErr;

    usErr = DosFSCtl (NULL,
		      0,
		      &usDataAreaLenOut,/* pointer to data area len returned */
		      NULL,
		      0,
		      &usParmLenOut,	/* pointer to parm list len returned */
		      FSC_LW_STOP,
		      pcFSDName,
		      -1,
		      RM_FSD,
		      0L);
    if (usErr != 0) {
	/* Set array of parameters to point to buffer with parameter as string */
	msg_parms[0] = outbuf;

	/* Convert number to string */
	sprintf(outbuf, "%5u", usErr);

	/* Print message using built array and number of parameters indicated */
	PARM_MESSAGE(1, MSG_HPFS_STOP_RETURNED);
    }
#else
    printf ("StopDaemon:\n");
#endif
} /* end StopDaemon() */




/**	Display - display current settings
 *
 *	pLWParms    pointer to packet of current values
 */
void pascal
Display (LWPARMS *pLWParms)
{
    UCHAR  outbuf1[10];
    UCHAR  outbuf2[10];
    UCHAR  outbuf3[10];

    /* Set array of parameters to point to buffer with parameter as string */
    msg_parms[0] = outbuf;
    msg_parms[1] = outbuf1;
    msg_parms[2] = outbuf2;
    msg_parms[3] = outbuf3;

    /* Convert number to string */
    sprintf(outbuf, "%8u",  pLWParms -> msIdle);
    sprintf(outbuf1, "%8u", pLWParms -> msDataAge);
    sprintf(outbuf2, "%8u", pLWParms -> msIdleBuf);
    sprintf(outbuf3, "%8u", pLWParms->cbuf * ((SPB * SECSIZE) / 1024));

    /* Print message using built array and number of parameters indicated */
    PARM_MESSAGE(4, MSG_HPFS_MSEC_SET);

#ifdef DEBUG
    printf ("          DirtyMax: %8u buffers\n", pLWParms->cbufDirtyMax);
    printf ("        WriteCache: %8lu bytes\n", pLWParms->cbWriteCache);
#endif
    if (usDataAreaLenOut > (USHORT)FIELDOFFSET(LWPARMS, fLWDaemon) ){
	/*
	 *  LWParms contains lazy writer status.
	 *
	 */
	if (pLWParms->fLWDaemon)
	    NO_PARM_MESSAGE(MSG_HPFS_LAZY_ON);
	else
	    NO_PARM_MESSAGE(MSG_HPFS_LAZY_OFF);
    }
} /* end Display() */




void pascal
Usage (szCmd)
CHAR *szCmd;
{
   /* Set array of paramaters to point to buffer with paramater as string */
   msg_parms[0] = szCmd;

   PARM_MESSAGE(1, MSG_HPFS_OPTIONS);

#ifdef DEBUG
   printf("\t[/WriteCache:n] set WriteCache to n bytes\n");
   printf("\t[/DirtyMax:n]   set DirtyMax to n buffers\n");
#endif

   exit (1);

} /* end Usage() */




/**	Ascii_2_ULong - convert ascii string to valid unsigned long
 *
 *	num_str  - string to be converted to ULONG value.
 *	cmd_str  - name of command (argv[0])
 */
ULONG pascal
Ascii_2_ULong(CHAR *num_str, CHAR *cmd_str)
{
    int  i, len, max_len;


    /* strip off leading zeros */
    while(*num_str == '0')
       num_str++;

    len = strlen(num_str);

    /* if no value specified... */
    if(len == 0) {
       printf(":%s <- expecting positive numeric value\n", num_str);
       Usage(cmd_str);
    } /* end if */

    max_len = strlen(ULONG_LIMIT);

    /* make sure it's all digits */
    for(i = 0; i < len; i++) {

       /* if non-digit found ... */
       if(!isdigit(num_str[i])) {
	  printf("%s <- expecting positive numeric value\n", num_str);
	  Usage(cmd_str);
       } /* end if */
    } /* end for */

    /* if the value is too large...(prevent overflow) */
    if( (len > max_len) ||
	((len == max_len) && (strcmp(ULONG_LIMIT, num_str) < 0)) ) {
       printf("%s <- value too large\n", num_str);
       Usage(cmd_str);
    } /* end if */

    /* else return the converted value */
    else
       return(atol(num_str));

} /* end Ascii_2_ULong() */




struct {
    CHAR *szswitch;			/*  name of switch		      */
    USHORT ofield;			/*  offset in LWPARMS of field	      */
    USHORT cchField;			/*  length of destination field	      */
    } swtab[] = {

	{
	    "-DISKIDLE",
	    FIELDOFFSET (LWPARMS, msIdle),
	    FIELDSIZE (LWPARMS, msIdle)
	},
	{
	    "-MAXAGE",
	    FIELDOFFSET (LWPARMS, msDataAge),
	    FIELDSIZE (LWPARMS, msDataAge)
	},
	{
	    "-BUFFERIDLE",
	    FIELDOFFSET (LWPARMS, msIdleBuf),
	    FIELDSIZE (LWPARMS, msIdleBuf)
	},
	{
	    "-WRITECACHE",
	    FIELDOFFSET (LWPARMS, cbWriteCache),
	    FIELDSIZE (LWPARMS, cbWriteCache)
	},
	{
	    "-DIRTYMAX",
	    FIELDOFFSET (LWPARMS, cbufDirtyMax),
	    FIELDSIZE (LWPARMS, cbufDirtyMax)
	},
	{
	    NULL,
	    0
	}


}; /* end of swtab[] */




#define MAXCMDLEN 80

void cdecl main (INT argc, CHAR *argv[]);

void cdecl
main (argc, argv)
INT argc;
CHAR *argv[];

{
    LWPARMS   lw;
    USHORT    i, Other_Opts;
    ULONG     ul;
    CHAR      *szCmd, *pc, *pEnd;


    /* save a pointer to the command name for use later */
    szCmd = *argv;

    /* point to the token after the command name */
    SHIFT (argc, argv);

    /* get the existing settings */
    GetParms (&lw);

    /* if there are no command line parameters then display lw parameters */
    if (argc == 0) {
	Display (&lw);
	exit (0);
    } /* end if */

    /* parse through the parameter list */
    Other_Opts = FALSE;
    while (argc != 0) {

	pEnd = pc = argv [0];

	/* force the switch char to be '-' */
	if (*pc == '/') {
	    *pc = '-';
	} /* end if */
	if (*pc != '-') {
	    Usage (szCmd);
	} /* end if */

	/* convert everything to upper case */
	strupr (pc);

	/* advance pEnd to the end of the token or to the ':' */
	while (*pEnd && (*pEnd != ':') )
	   *(pEnd++);

	/* if there wasn't a ':' in the token... */
	if (*pEnd == '\0')
	   Usage(szCmd);

	/* else advance pEnd to the char just after the ':' */
	else {
	   *pEnd = '\0';  /* separate pEnd from pc */
	   *(pEnd++);
	} /* end else */

	/* check for the special case of turning lazy on or off */
	if (!strcmp (pc, "-LAZY")) {

	   /* if they specified more than the "Lazy" option ... */
	    if( (argc > 1) || (Other_Opts) ) {
		printf("\"LAZY\" not valid with other options\n");
		Usage (szCmd);
	    } /* end if */

	    /* test to see if we are turning it on or off */
	    if (!strcmp (pEnd, "ON")) {

#ifdef RAHEAD
		/* Create seperate thread for doing read aheads */
		{
		    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   */

		    /* Allocate memory for stack */
		    usRetCode = DosAllocSeg(
			2048,		    /* bytes wanted		    */
			& selChildStack,    /* pointer to selector	    */
			(USHORT) 0);	    /* nonsharable, nondiscardable  */

		    if (usRetCode == 0) {

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

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


#ifdef DEBUG
		printf ("starting lazy write daemon\n");
#endif
		LWDaemon ();
#ifdef DEBUG
		printf ("back from lazy write daemon\n");
#endif
	   }  /* end if */

	   /* kill the lw daemon */
	   else if (!strcmp (pEnd, "OFF")) {
	       StopDaemon ();
#ifdef DEBUG
	       printf ("killing lazy write daemon\n");
#endif
	   } /* end else if */

	   else
	       Usage (szCmd);

	} /* end if */

	/* else handle the other options */
	else {

	   Other_Opts = TRUE;

	   /* get the offsets for this token option from the sw table */
	   for (i = 0; swtab[i].szswitch != NULL; i++) {
	      if (!strcmp (pc, swtab[i].szswitch))
		 break;
	   } /* end for */

	   /* if the option isn't in the table... */
	   if (swtab[i].szswitch == NULL)
	      Usage (szCmd);

	   /* get the numeric value associated with the token */
	   ul = Ascii_2_ULong(pEnd, szCmd);

	   /* is the token associated with a short or long value? */
	   switch (swtab[i].cchField) {

	      /* handle SHORT */
	      case sizeof (USHORT):

		 /* if the value is too large for ushort... */
		 if (ul > 0xffff) {
		     printf ("%s <- value too large\n", pEnd);
		     Usage (szCmd);
		 } /* end if */
		 else {
		    *(USHORT *) ((CHAR *)&lw + swtab[i].ofield) = (USHORT) ul;
#ifdef DEBUG
		    printf ("got a short %u\n", (USHORT) ul);
#endif
		    break;
		 } /* end else */


	      /* handle LONG */
	      case sizeof (ULONG):

		 *(ULONG *) ((CHAR *)&lw + swtab[i].ofield) = ul;
#ifdef DEBUG
		 printf ("got a long %lu\n", ul);
#endif
		 break;


	      default:
		 fprintf (stderr, "UNKNOWN SIZE OF FIELD\n");
		 exit (1);

	   } /* end switch */
	} /* end else */

	/* get the next token:value combo */
	SHIFT (argc, argv);

    } /* end while */


    /* save the new parameter settings */
    SetParms (&lw);
    exit (0);

} /* end main() */




#ifdef STATISTICS
/**	ClearStatistics - display current filesystem statistics
 */
void ClearStatistics ()
{
    USHORT usErr;

    usErr = DosFSCtl (NULL,
		      0,
		      &usDataAreaLenOut,/* pointer to data area len returned */
		      NULL,
		      0,
		      &usParmLenOut,	/* pointer to parm list len returned */
		      FSC_CLEAR,
		      pcFSDName,
		      -1,
		      RM_FSD,
		      0L);

    if (usErr != 0) {
	/* Set array of parameters to point to buffer with parameter as string */
	msg_parms[0] = outbuf;

	/* Convert number to string */
	sprintf(outbuf, "%5u", usErr);

	/* Print message using built array and number of parameters indicated */
	PARM_MESSAGE(1, MSG_HPFS_GET_RETURNED);
    }
}

/**	Statistics - display current filesystem statistics
 */
void Statistics ()
{
    USHORT usErr;
    USHORT i;

    usErr = DosAllocSeg (0x0000, (PSEL) & SELECTOROF (pfsstat), 0x0000);
    if (usErr != 0) {
	/* Set array of parameters to point to buffer with parameter as string */
	msg_parms[0] = outbuf;

	/* Convert number to string */
	sprintf(outbuf, "%5u", usErr);

	/* Print message using built array and number of parameters indicated */
	PARM_MESSAGE(1, MSG_HPFS_ALCSG_RETURNED);
    }
    OFFSETOF (pfsstat) = 0;

    usErr = DosFSCtl ((PVOID) pfsstat,
		      sizeof (FSSTAT),
		      &usDataAreaLenOut,/* pointer to data area len returned */
		      NULL,
		      0,
		      &usParmLenOut,	/* pointer to parm list len returned */
		      FSC_GETSTAT,
		      pcFSDName,
		      -1,
		      RM_FSD,
		      0L);
    if (usErr != 0) {
	/* Set array of parameters to point to buffer with parameter as string */
	msg_parms[0] = outbuf;

	/* Convert number to string */
	sprintf(outbuf, "%5u", usErr);

	/* Print message using built array and number of parameters indicated */
	PARM_MESSAGE(1, MSG_HPFS_GET_RETURNED);
    }

    printf ("%9lu count of OPEN calls\n", pfsstat->ST_OPEN);
    printf ("%9lu count of CLOSE calls\n", pfsstat->ST_CLOSE);
    printf ("%9lu count of READ calls\n", pfsstat->ST_READ);
    printf ("%9lu count of WRITE calls\n", pfsstat->ST_WRITE);
    printf ("%9lu count of DELETE calls\n", pfsstat->ST_DEL);

    printf ("%9lu count of SEEK calls\n", pfsstat->ST_SEEK);
    printf ("%9lu count of FINDF calls\n", pfsstat->ST_FINDF);
    printf ("%9lu count of FINDN calls\n", pfsstat->ST_FINDN);
    printf ("%9lu count of disk reads\n", pfsstat->ST_RD);

    printf ("%9lu count of disk writes\n", pfsstat->ST_WR);
    printf ("%9lu count of cache read hits\n", pfsstat->ST_CRD);
    printf ("%9lu count of cache write hits\n", pfsstat->ST_CWD);
    printf ("%9lu Getinbuf was successful\n", pfsstat->ST_GIB);

    printf ("%9lu invalid LSD hints\n", pfsstat->ST_INVAL);
    printf ("%9lu valid LSD hints\n", pfsstat->ST_VALID);
    printf ("%9lu directory relocated\n", pfsstat->ST_RDEH);
    printf ("%9lu directory adjusted\n", pfsstat->ST_RDEM);

    printf ("%9lu count of SFB calls\n", pfsstat->ST_SFB);
    printf ("%9lu count of VBR calls\n", pfsstat->ST_VBR);
    printf ("%9lu count of CBR calls\n", pfsstat->ST_CBR);
    printf ("%9lu count of AddExt calls\n", pfsstat->ST_AEX);
    printf ("%9lu count of allocation block splits\n", pfsstat->ST_ALSP);

    printf ("%9lu files extended\n", pfsstat->ST_EFA);
    printf ("%9lu FLW buffers written\n", pfsstat->ST_FLW);
    printf ("%9lu bitmap read valid\n", pfsstat->ST_BRV);
    printf ("%9lu bitmap read invalid\n", pfsstat->ST_BRI);

    printf ("%9lu blocked in LSD\n", pfsstat->ST_BLSD);
    printf ("%9lu blcoked in rdb\n", pfsstat->ST_BRDB);
    printf ("%9lu GFB interlock\n", pfsstat->ST_GFBI);
    printf ("%9lu gfb waits\n", pfsstat->ST_GFBW);

    printf ("%9lu long writes\n", pfsstat->ST_LWR);

    printf ("%9lu directory locked forced restart\n", pfsstat->ST_DLRS);

    printf ("%9lu singletons output by lazy IO\n", pfsstat->ST_LWBW);
    printf ("%9lu clean blocks found by lazy IO\n", pfsstat->ST_CLN);
    printf ("%9lu wakeups caused by lazy IO blocks\n", pfsstat->ST_LWW);

    printf ("LW Block Histogram:");
    for (i = 0; i < LWBUFCT*SPB ; i++)
	printf (" %lu", pfsstat->ST_LWBLK[i]);

    printf ("\nRead Histogram:");
    for (i = 0; i < 64; i++)
	printf (" %lu", pfsstat->ST_RSIZ[i]);

    printf ("\nWrite Histogram:");
    for (i = 0; i < 64; i++)
	printf (" %lu", pfsstat->ST_WSIZ[i]);

    printf ("\n");
}
#endif
