/********************************************************************/
/**			Microsoft LAN Manager			   **/
/**		  Copyright(c) Microsoft Corp., 1987-1990	   **/
/********************************************************************/

/***
 *  stats.c 
 *	Functions to display and clear network statistics
 *
 *  History:
 *	mm/dd/yy, who, comment
 *	06/12/87, agh, new code
 *	03/21/89, kms, 1.2 changes
 *	02/20/91, danhi, change to use lm 16/32 mapping layer
 *      08/22/92, chuckc, fixed to match lastest rdr stats structure
 */

/* Include files */

#define INCL_NOCOMMON
#include <os2.h>
#include <netcons.h>
#include <neterr.h>
#include <apperr.h>
#include <apperr2.h>
#include <service.h>
#include "netlib0.h"
#include <lui.h>
#include <netstats.h>
#include <wksta.h>
#include <stdlib.h>
#include <stdio.h>
#include "port1632.h"
#include "netcmds.h"
#include "nettext.h"

/* Constants */

#define KBYTES 1024	/* By fiat of Darryl, this is 1024,
			 * the techie version of "K". -paulc
			 */

#define DLWBUFSIZE  22	/* buffer big enough to represent a 64-bit unsigned int
			 * LONG number in base 10
			 */
#define STATS_UNKNOWN	0xFFFFFFFF

/*
 * macros
 */

/*  This code rounds to the NEARest 2^^10   JONN 6/13/90    */
/*  Does not check for high-byte overflow; do not call when */
/*	(lo) == (hi) == 0xffffffff			    */
#define DLW_DIVIDE_1K(hi,lo)	if ((lo) & (0x1 << 9)) \
	    { \
		(lo) += (0x1 << 9); \
		if ((lo) == 0x0) \
		    (hi) += 0x1; \
	    } \
	    (lo) = ( (hi) << 22) | ((lo) >> 10); \
	    (hi) = (hi) >> 10


/* Static variables */

static TCHAR stat_fmt3[] = TEXT("  %-*.*ws%lu\r\n");
static TCHAR stat_fmt4[] = TEXT("  %-*.*ws%ws\r\n");

/* The list of services for which stats are collected */

static TCHAR * allowed_svc[] = {
    SERVICE_WORKSTATION,
    SERVICE_SERVER,
    NULL };

/* Forward declarations */

VOID NEAR stats_headers(TCHAR *, USHORT, TCHAR FAR **);
TCHAR * format_dlword(ULONG, ULONG, TCHAR *);
VOID revstr_add(TCHAR FAR *, TCHAR FAR *);
VOID SrvPrintStat(TCHAR *,TCHAR *,USHORT,USHORT,ULONG);
VOID WksPrintStat(TCHAR *,TCHAR *,USHORT,USHORT,ULONG);
VOID WksPrintLargeInt(TCHAR *, USHORT, USHORT, ULONG, ULONG) ;


#define W_MSG_BYTES_RECEIVED 	    	0
#define W_MSG_SMBS_RECEIVED		1
#define W_MSG_BYTES_TRANSMITTED		2
#define W_MSG_SMBS_TRANSMITTED		3
#define W_MSG_READ_OPS			4
#define W_MSG_WRITE_OPS			5
#define W_MSG_RAW_READS_DENIED		6
#define W_MSG_RAW_WRITES_DENIED		7
#define W_MSG_NETWORK_ERRORS		8
#define W_MSG_TOTAL_CONNECTS		9
#define W_MSG_RECONNECTS		10
#define W_MSG_SRV_DISCONNECTS		11
#define W_MSG_SESSIONS			12
#define W_MSG_HUNG_SESSIONS		13
#define W_MSG_FAILED_SESSIONS		14
#define W_MSG_FAILED_OPS		15
#define W_MSG_USE_COUNT			16
#define W_MSG_FAILED_USE_COUNT		17
#define W_MSG_GEN_UNKNOWN		18

static MESSAGE WkstaMsgList[] = {
    {APE2_STATS_BYTES_RECEIVED,		NULL},
    {APE2_STATS_SMBS_RECEIVED,		NULL},
    {APE2_STATS_BYTES_TRANSMITTED,	NULL},
    {APE2_STATS_SMBS_TRANSMITTED,	NULL},
    {APE2_STATS_READ_OPS, 		NULL},
    {APE2_STATS_WRITE_OPS,		NULL},
    {APE2_STATS_RAW_READS_DENIED,	NULL},
    {APE2_STATS_RAW_WRITES_DENIED,	NULL},
    {APE2_STATS_NETWORK_ERRORS,		NULL},
    {APE2_STATS_TOTAL_CONNECTS,		NULL},
    {APE2_STATS_RECONNECTS, 		NULL},
    {APE2_STATS_SRV_DISCONNECTS, 	NULL},
    {APE2_STATS_SESSIONS,		NULL},
    {APE2_STATS_HUNG_SESSIONS, 		NULL},
    {APE2_STATS_FAILED_SESSIONS, 	NULL},
    {APE2_STATS_FAILED_OPS, 		NULL},
    {APE2_STATS_USE_COUNT, 		NULL},
    {APE2_STATS_FAILED_USE_COUNT, 	NULL},
    {APE2_GEN_UNKNOWN,			NULL},
};

#define WKSTAMSGSIZE	    (sizeof(WkstaMsgList) / sizeof(WkstaMsgList[0]))

#define S_MSG_STATS_S_ACCEPTED	    0
#define S_MSG_STATS_S_TIMEDOUT	    (S_MSG_STATS_S_ACCEPTED + 1)
#define S_MSG_STATS_ERROREDOUT	    (S_MSG_STATS_S_TIMEDOUT + 1)
#define S_MSG_STATS_B_SENT	    (S_MSG_STATS_ERROREDOUT + 1)
#define S_MSG_STATS_B_RECEIVED	    (S_MSG_STATS_B_SENT + 1)
#define S_MSG_STATS_RESPONSE	    (S_MSG_STATS_B_RECEIVED + 1)
#define S_MSG_STATS_NETIO_ERR	    (S_MSG_STATS_RESPONSE + 1)
#define S_MSG_STATS_SYSTEM_ERR	    (S_MSG_STATS_NETIO_ERR + 1)
#define S_MSG_STATS_PERM_ERR	    (S_MSG_STATS_SYSTEM_ERR + 1)
#define S_MSG_STATS_PASS_ERR	    (S_MSG_STATS_PERM_ERR + 1)
#define S_MSG_STATS_FILES_ACC	    (S_MSG_STATS_PASS_ERR + 1)
#define S_MSG_STATS_COMM_ACC	    (S_MSG_STATS_FILES_ACC + 1)
#define S_MSG_STATS_PRINT_ACC	    (S_MSG_STATS_COMM_ACC + 1)
#define S_MSG_STATS_BIGBUF	    (S_MSG_STATS_PRINT_ACC + 1)
#define S_MSG_STATS_REQBUF	    (S_MSG_STATS_BIGBUF + 1)
#define S_MSG_GEN_UNKNOWN	    (S_MSG_STATS_REQBUF + 1)

static MESSAGE ServerMsgList[] = {
    {APE2_STATS_S_ACCEPTED,	NULL},
    {APE2_STATS_S_TIMEDOUT,	NULL},
    {APE2_STATS_ERROREDOUT,	NULL},
    {APE2_STATS_B_SENT, 	NULL},
    {APE2_STATS_B_RECEIVED,	NULL},
    {APE2_STATS_RESPONSE,	NULL},
    {APE2_STATS_NETIO_ERR,	NULL},
    {APE2_STATS_SYSTEM_ERR,	NULL},
    {APE2_STATS_PERM_ERR,	NULL},
    {APE2_STATS_PASS_ERR,	NULL},
    {APE2_STATS_FILES_ACC,	NULL},
    {APE2_STATS_COMM_ACC,	NULL},
    {APE2_STATS_PRINT_ACC,	NULL},
    {APE2_STATS_BIGBUF, 	NULL},
    {APE2_STATS_REQBUF, 	NULL},
    {APE2_GEN_UNKNOWN,		NULL},
};

#define SRVMSGSIZE	(sizeof(ServerMsgList) / sizeof(ServerMsgList[0]))




/***
 *  stats_display ()
 *	Displays the list of installed services that have stats
 *
 */
VOID stats_display(VOID)
{
    USHORT		    err;		/* API return status */
    TCHAR FAR *		    pBuffer;
    USHORT2ULONG	    _read;	/* num entries read by API */
    USHORT2ULONG	    i;
    USHORT		    j;
    int 		    printed = 0;
    struct service_info_2 FAR *  info_list_entry;

    if (err = MNetServiceEnum(
			    NULL,
			    2,
			    (LPBYTE*)&pBuffer,
			    &_read))
	ErrorExit(err);

    if (_read == 0)
	EmptyExit();

    InfoPrint(APE_StatsHeader);

    for (i=0, info_list_entry = (struct service_info_2 FAR *) pBuffer;
	 i < _read; i++, info_list_entry++)
    {
	for (j = 0 ;  allowed_svc[j] ; j++)
	{
	    if (!(_tcscmp(allowed_svc[j], info_list_entry->svci2_name)) )
	    {
		WriteToCon(TEXT("   %Fws"), info_list_entry->svci2_display_name);
		PrintNL();
		break;
	    }
	}
    }

    PrintNL();
    NetApiBufferFree(pBuffer);

    InfoSuccess();
}

/*
 * generic stats entry point. based on the service name, it will
 * call the correct worker function. it tries to map a display name to a 
 * key name, and then looks for that keyname in a list of 'known' services
 * that we may special case. note that if a display name cannot be mapped,
 * we use it as a key name. this ensures old batch files are not broken.
 */
VOID stats_generic_display(TCHAR *service)
{
    TCHAR *keyname ;
    UINT  type ;

    keyname = MapServiceDisplayToKey(service) ;

    type = FindKnownService(keyname) ;

    switch (type)
    {
	case  KNOWN_SVC_WKSTA :
	    stats_wksta_display() ;
	    break ;
	case  KNOWN_SVC_SERVER :
	    stats_server_display() ;
	    break ;
  	default:
	    help_help(0, USAGE_ONLY) ;
	    break ;
    }
}


/***
 *  stats_server_display()
 *	Display server statistics
 *
 *  Args:
 *	none
 *
 *  Returns:
 *	nothing - success
 *	exit 2 - command failed
 */
VOID stats_server_display(VOID)
{
    struct stat_server_0 FAR * stats_entry;
    USHORT	maxmsglen;
    TCHAR	dlwbuf[DLWBUFSIZE];
    TCHAR	time_buf[30];

    /* get the text we'll need */
    GetMessageList(SRVMSGSIZE, ServerMsgList, &maxmsglen);

#ifdef DEBUG
    WriteToCon(TEXT("stats_server_display: Got message list\r\n"));
#endif

    maxmsglen += 5;

    start_autostart(txt_SERVICE_FILE_SRV);

    /*
     * stats_headers() will call the NetStatisticsGetInfo call
     */
    stats_headers(txt_SERVICE_FILE_SRV, APE2_STATS_SERVER,
	(TCHAR FAR **) & stats_entry);

    UnicodeCtime(&stats_entry->sts0_start, time_buf, 30);

    InfoPrintInsTxt(APE2_STATS_SINCE, time_buf);
    PrintNL();

    SrvPrintStat(fmtULONG, fmtNPSZ, maxmsglen, S_MSG_STATS_S_ACCEPTED,
		stats_entry->sts0_sopens);

    SrvPrintStat(fmtULONG, fmtNPSZ, maxmsglen, S_MSG_STATS_S_TIMEDOUT,
		stats_entry->sts0_stimedout);

    SrvPrintStat(fmtULONG, fmtNPSZ, maxmsglen, S_MSG_STATS_ERROREDOUT,
		stats_entry->sts0_serrorout);

    PrintNL();

    if( stats_entry->sts0_bytessent_high == STATS_UNKNOWN &&
	stats_entry->sts0_bytessent_low == STATS_UNKNOWN )
    {
	WriteToCon(fmtNPSZ, 0, maxmsglen,
		PaddedString(maxmsglen,ServerMsgList[S_MSG_STATS_B_SENT].msg_text,NULL),
		ServerMsgList[S_MSG_GEN_UNKNOWN].msg_text);
    }
    else
    {
	DLW_DIVIDE_1K(stats_entry->sts0_bytessent_high,
	      stats_entry->sts0_bytessent_low);

	WriteToCon(fmtNPSZ, 0, maxmsglen,
	    PaddedString(maxmsglen,ServerMsgList[S_MSG_STATS_B_SENT].msg_text,NULL),
	    format_dlword(stats_entry->sts0_bytessent_high,
			  stats_entry->sts0_bytessent_low,
			  dlwbuf));
    }

    if( stats_entry->sts0_bytesrcvd_high == STATS_UNKNOWN &&
	stats_entry->sts0_bytesrcvd_low == STATS_UNKNOWN )
    {
	WriteToCon(fmtNPSZ, 0, maxmsglen,
		PaddedString(maxmsglen,ServerMsgList[S_MSG_STATS_B_RECEIVED].msg_text,NULL),
		ServerMsgList[S_MSG_GEN_UNKNOWN].msg_text);
    }
    else
    {
	DLW_DIVIDE_1K(stats_entry->sts0_bytesrcvd_high,
	      stats_entry->sts0_bytesrcvd_low);

	WriteToCon(fmtNPSZ, 0, maxmsglen,
	    PaddedString(maxmsglen,ServerMsgList[S_MSG_STATS_B_RECEIVED].msg_text,NULL),
	    format_dlword(stats_entry->sts0_bytesrcvd_high,
			  stats_entry->sts0_bytesrcvd_low,
			  dlwbuf));
    }

    PrintNL();

    SrvPrintStat(fmtULONG, fmtNPSZ, maxmsglen, S_MSG_STATS_RESPONSE,
		stats_entry->sts0_avresponse);

    PrintNL();


    SrvPrintStat(fmtULONG, fmtNPSZ, maxmsglen, S_MSG_STATS_SYSTEM_ERR,
		stats_entry->sts0_syserrors);

    SrvPrintStat(fmtULONG, fmtNPSZ, maxmsglen, S_MSG_STATS_PERM_ERR,
		stats_entry->sts0_permerrors);

    SrvPrintStat(fmtULONG, fmtNPSZ, maxmsglen, S_MSG_STATS_PASS_ERR,
		stats_entry->sts0_pwerrors);

    PrintNL();

    SrvPrintStat(fmtULONG, fmtNPSZ, maxmsglen, S_MSG_STATS_FILES_ACC,
		stats_entry->sts0_fopens);

    SrvPrintStat(fmtULONG, fmtNPSZ, maxmsglen, S_MSG_STATS_COMM_ACC,
		stats_entry->sts0_devopens);

    SrvPrintStat(fmtULONG, fmtNPSZ, maxmsglen, S_MSG_STATS_PRINT_ACC,
		stats_entry->sts0_jobsqueued);

    PrintNL();
    InfoPrint(APE2_STATS_BUFCOUNT);

    SrvPrintStat(stat_fmt3,
		stat_fmt4,
		(USHORT) (maxmsglen - 2),
		S_MSG_STATS_BIGBUF,
		stats_entry->sts0_bigbufneed);

    SrvPrintStat(stat_fmt3,
		stat_fmt4,
		(USHORT) (maxmsglen - 2),
		S_MSG_STATS_REQBUF,
		stats_entry->sts0_reqbufneed);
    PrintNL() ;

    NetApiBufferFree((TCHAR FAR *) stats_entry);

    InfoSuccess();
}


/***
 *  stats_wksta_display()
 *	Display wksta statistics
 *
 *  Args:
 *	none
 *
 *  Returns:
 *	nothing - success
 *	exit 2 - command failed
 */
VOID stats_wksta_display(VOID)
{
    struct stat_workstation_0 FAR * stats_entry;
    USHORT	    maxmsglen;
    USHORT	    err;
    unsigned int    entry_unknown = FALSE;/* TRUE if a stat which is summed to
					     create another stat is unknown. */
    ULONG	    total_connects;       /* for totaling connections. */
    DWORD           start_time ;
    TCHAR	    time_buf[64];         /* for displaying time */

    /* get the text we'll need */
    GetMessageList(WKSTAMSGSIZE, WkstaMsgList, &maxmsglen);

    maxmsglen += 5;

    start_autostart(txt_SERVICE_REDIR);

    /*
     * stats_headers() will call the NetStatisticsGetInfo call
     */
    stats_headers(txt_SERVICE_REDIR, APE2_STATS_WKSTA,
	(TCHAR FAR **) & stats_entry);

    /*
     * display the time its been running. if system reports a time
     * outside the expressible range (shouldnt happen), we claim
     * it is unknown.
     */
    if (MTimeToSecsSince1970(&stats_entry->StatisticsStartTime,
			     &start_time) != NERR_Success)
	
    {
        if (err = LUI_GetMsg(time_buf, 64, APE2_GEN_UNKNOWN))
            ErrorExit(err);
    }
    else {
        UnicodeCtime(&start_time, time_buf, 30);
    }
    InfoPrintInsTxt(APE2_STATS_SINCE, time_buf);
    PrintNL();


    /*
     * now print the actual stats
     */
    WksPrintLargeInt(stat_fmt4, maxmsglen, W_MSG_BYTES_RECEIVED,
		     stats_entry->BytesReceived.HighPart,
		     stats_entry->BytesReceived.LowPart ) ;
    WksPrintLargeInt(stat_fmt4, maxmsglen, W_MSG_SMBS_RECEIVED,
		     stats_entry->SmbsReceived.HighPart,
		     stats_entry->SmbsReceived.LowPart ) ;
    WksPrintLargeInt(stat_fmt4, maxmsglen, W_MSG_BYTES_TRANSMITTED,
		     stats_entry->BytesTransmitted.HighPart,
		     stats_entry->BytesTransmitted.LowPart ) ;
    WksPrintLargeInt(stat_fmt4, maxmsglen, W_MSG_SMBS_TRANSMITTED,
		     stats_entry->SmbsTransmitted.HighPart,
		     stats_entry->SmbsTransmitted.LowPart ) ;
    WksPrintStat(stat_fmt3, stat_fmt4, maxmsglen,
	    W_MSG_READ_OPS, stats_entry->ReadOperations ) ;
    WksPrintStat(stat_fmt3, stat_fmt4, maxmsglen,
	    W_MSG_WRITE_OPS, stats_entry->WriteOperations ) ;
    WksPrintStat(stat_fmt3, stat_fmt4, maxmsglen,
	    W_MSG_RAW_READS_DENIED, stats_entry->RawReadsDenied ) ;
    WksPrintStat(stat_fmt3, stat_fmt4, maxmsglen,
	    W_MSG_RAW_WRITES_DENIED, stats_entry->RawWritesDenied ) ;

    PrintNL() ;

    WksPrintStat(stat_fmt3, stat_fmt4, maxmsglen,
	    W_MSG_NETWORK_ERRORS, stats_entry->NetworkErrors ) ;

    total_connects = stats_entry->CoreConnects +
                     stats_entry->Lanman20Connects +
                     stats_entry->Lanman21Connects +
                     stats_entry->LanmanNtConnects ;
    WksPrintStat(stat_fmt3, stat_fmt4, maxmsglen,
	    W_MSG_TOTAL_CONNECTS, total_connects ) ;

    WksPrintStat(stat_fmt3, stat_fmt4, maxmsglen,
	    W_MSG_RECONNECTS, stats_entry->Reconnects ) ;
    WksPrintStat(stat_fmt3, stat_fmt4, maxmsglen,
	    W_MSG_SRV_DISCONNECTS, stats_entry->ServerDisconnects ) ;

    PrintNL() ;

    WksPrintStat(stat_fmt3, stat_fmt4, maxmsglen,
	    W_MSG_SESSIONS, stats_entry->Sessions ) ;
    WksPrintStat(stat_fmt3, stat_fmt4, maxmsglen,
	    W_MSG_HUNG_SESSIONS, stats_entry->HungSessions ) ;
    WksPrintStat(stat_fmt3, stat_fmt4, maxmsglen,
	    W_MSG_FAILED_SESSIONS, stats_entry->FailedSessions ) ;
    WksPrintStat(stat_fmt3, stat_fmt4, maxmsglen,
	    W_MSG_FAILED_OPS, 
		stats_entry->InitiallyFailedOperations + 
		stats_entry->FailedCompletionOperations ) ;
    WksPrintStat(stat_fmt3, stat_fmt4, maxmsglen,
	    W_MSG_USE_COUNT, stats_entry->UseCount ) ;
    WksPrintStat(stat_fmt3, stat_fmt4, maxmsglen,
	    W_MSG_FAILED_USE_COUNT, stats_entry->FailedUseCount ) ;
    PrintNL() ;

    NetApiBufferFree((TCHAR FAR *) stats_entry);

    InfoSuccess();
}



#ifndef NTENV

/***
 *  stats_clear
 *	Clear network statistics log
 *
 *  Args:
 *	service -- name of service whose stats we're clearing
 *
 *  Returns:
 *	0 - success
 *	exit 2 - command failed
 */
VOID stats_clear(TCHAR *  service)
{
    USHORT		    err;		/* API return status */
    TCHAR FAR *		    pBuffer;

    if (service)
    {
	if (err = MNetStatisticsGet(NULL, service, 0L, 0,
				    (ULONG)STATSOPT_CLR,
				    &pBuffer))
	    ErrorExit(err);
	NetApiBufferFree(pBuffer);
	InfoSuccess();
    }
    else
    {
	if (err = MNetStatisticsGet(NULL, SERVICE_WORKSTATION, 0L, 0,
				    (ULONG)STATSOPT_CLR,
				    &pBuffer))
	    ErrorExit(err);

	NetApiBufferFree(pBuffer);

	err = MNetStatisticsGet(NULL, SERVICE_SERVER, 0L, 0,
				    (ULONG)STATSOPT_CLR,
				    &pBuffer);
	if( err && ( err != NERR_ServerNotStarted ) )
	    ErrorExit(err);

	NetApiBufferFree(pBuffer);
	InfoSuccess();
    }
}


#endif




/***
 *  stats_headers()
 *	Display statistics headers
 *	BigBuf contains the stats_info_struct on return
 *
 *  Args:
 *	none
 *
 *  Returns:
 *	nothing - success
 *	exit 2 - command failed
 */
VOID NEAR stats_headers(TCHAR * service, USHORT headermsg,
    TCHAR FAR ** ppBuffer)
{
    USHORT		    err;		/* API return status */
    TCHAR		    cname[MAX_PATH+1];

    struct wksta_info_10 FAR *	    wksta_entry;

    /* get cname name for display */

    if (err = MNetWkstaGetInfo(NULL,
				10,
				(LPBYTE*)&wksta_entry))
	*cname = NULLC;
    else
    {
	_tcscpy(cname, wksta_entry->wki10_computername);
    }

#ifdef DEBUG
    WriteToCon(TEXT("About to call NetStatisticsGet2, service == %Fws\r\n"),
	    (TCHAR FAR *) service);
#endif

    NetApiBufferFree((TCHAR FAR *) wksta_entry);

    if (err = MNetStatisticsGet(NULL,
				service,
				0L,
				0,
				0L,
				(LPBYTE*)ppBuffer))
	ErrorExit(err);

#ifdef DEBUG
    WriteToCon(TEXT("stats_headers: MNetStatisticsGet succeeded\r\n"));
#endif

    InfoPrintInsTxt(headermsg, cname);
    PrintNL();

}


/*
 * format_dlword --
 *
 * This function takes a 64-bit number and writes its base-10 representation
 * into a string.
 *
 * Much magic occurs within this function, so beware. We do a lot of string-
 * reversing and addition-by-hand in order to get it to work.
 *
 *  ENTRY
 *	high	- high 32 bits
 *	low	- low 32 bits
 *	buf	- buffer to put it into
 *
 *  RETURNS
 *	pointer to buffer if successful
 */

TCHAR * format_dlword(ULONG high, ULONG low, TCHAR * buf)
{
    TCHAR addend[DLWBUFSIZE];  /* REVERSED power of two */
    TCHAR copy[DLWBUFSIZE];
    int i = 0;

    ultow(low, buf, 10);    /* the low part is easy */
    _tcsrev(buf);	    /* and reverse it */

    /* set up addend with rep. of 2^32 */
    ultow(0xFFFFFFFF, addend, 10);  /* 2^32 -1 */
    _tcsrev(addend);		    /* reversed, and will stay this way */
    revstr_add(addend, TEXT("1"));	    /* and add one == 2^32 */

    /* addend will contain the reverse-ASCII base-10 rep. of 2^(i+32) */

    /* now, we loop through each digit of the high longword */
    while (TRUE) {
	/* if this bit is set, add in its base-10 rep */
	if (high & 1)
	    revstr_add(buf,addend);

	/* move on to next bit */
	high >>= 1;

	/* if no more digits in high, bag out */
	if (!high)
	    break;

	/* we increment i, and double addend */
	i++;
	_tcscpy(copy, addend);
	revstr_add(addend,copy); /* i.e. add it to itself */

    }

    _tcsrev(buf);
    return buf;
}



/*
 * revstr_add --
 *
 *  This function will add together reversed ASCII representations of
 *  base-10 numbers.
 *
 *  Examples:	"2" + "2" = "4" "9" + "9" = "81"
 *
 *  This handles arbitrarily large numbers.
 *
 *  ENTRY
 *
 *  source	- number to add in
 *  target	- we add source to this
 *
 *  EXIT
 *  target	- contains sum of entry values of source and target
 *
 */

VOID
revstr_add(TCHAR FAR * target, TCHAR FAR * source)
{
    register TCHAR   accum;
    register TCHAR   target_digit;
    unsigned int    carrybit = 0;
    unsigned int    srcstrlen;
    unsigned int    i;

    srcstrlen = _tcslen(source);

    for (i = 0; (i < srcstrlen) || carrybit; ++i) {

	/* add in the source digit */
	accum =  (i < srcstrlen) ? (TCHAR) (source[i] - '0') : (TCHAR) 0;

	/* add in the target digit, or '0' if we hit null term */
	target_digit = target[i];
	accum += (target_digit) ? target_digit : '0';

	/* add in the carry bit */
	accum += (TCHAR) carrybit;

	/* do a carry out, if necessary */
	if (accum > '9') {
	    carrybit = 1;
	    accum -= 10;
	}
	else
	    carrybit = 0;

	/* if we're expanding the string, must put in a new null term */
	if (!target_digit)
	    target[i+1] = NULLC;

	/* and write out the digit */
	target[i] = accum;
    }

}



/*** SrvPrintStat - Print a server stat
 *
 */
VOID SrvPrintStat( TCHAR *deffmt, TCHAR *unkfmt, USHORT len,
		    USHORT msg, ULONG _stat)
{
	    if( _stat == STATS_UNKNOWN )
		WriteToCon( unkfmt, 0, len,
		    PaddedString(len,ServerMsgList[msg].msg_text,NULL),
		    ServerMsgList[S_MSG_GEN_UNKNOWN].msg_text);
	    else
		WriteToCon( deffmt, 0, len,
		    PaddedString(len,ServerMsgList[msg].msg_text,NULL),
		    _stat );
}


/*** WksPrintStat - Print a workstation stat
 *
 */
VOID WksPrintStat(
	TCHAR *deffmt,
	TCHAR *unkfmt,
	USHORT len,
	USHORT msg,
	ULONG _stat)
{

    if( _stat == STATS_UNKNOWN )
	WriteToCon( unkfmt, 0, len,
	    PaddedString(len,WkstaMsgList[msg].msg_text,NULL),
	    WkstaMsgList[W_MSG_GEN_UNKNOWN].msg_text);
    else
	WriteToCon( deffmt, 0, len,
	    PaddedString(len,WkstaMsgList[msg].msg_text,NULL),
		_stat );
}

/*** WksPrintLargeInt - Print a LARGE_INTEGER statistic
 *
 */
VOID WksPrintLargeInt(
	TCHAR *format,
	USHORT maxmsglen,
	USHORT msgnum,
	ULONG high,
	ULONG low)
{
    TCHAR dlwbuf[DLWBUFSIZE];

    WriteToCon(format, 0, maxmsglen,
	   PaddedString(maxmsglen,WkstaMsgList[msgnum].msg_text,NULL),
	   format_dlword(high, low, dlwbuf));
}

