ftp.nice.ch/pub/next/unix/network/system/cap.5.0.s.tar.gz#/cap_5.0/applications/aufs/afpserver.c

This is afpserver.c in view mode; [Download] [Up]

/*
 * $Author: cck $ $Date: 88/05/13 09:23:25 $
 * $Header: afpserver.c,v 1.27 88/05/13 09:23:25 cck Rel $
 * $Revision: 1.27 $
*/

/*
 * afpserver.c - Appletalk Filing Protocol Server Level Routines
 *
 * AppleTalk package for UNIX (4.2 BSD).
 *
 * Copyright (c) 1986,1987,1988 by The Trustees of Columbia University in the
 * City of New York.
 *
 * Edit History:
 *
 *  March 1987     Schilit	Created.
 *
 */

/*
 * Various server support routines:
 *
 *  SrvrInfo()
 *  SrvrRegister()
 *  SrvrSetTrace()
 *
 * Non OS dependant support routines:
 *
 *  FPLogin()
 *  FPLoginCont()
 *  FPLogout()
 *  FPGetSrvrParms()
 *  FPMapID() 
 *  FPMapName()
 *
 */

#include <stdio.h>
#include <ctype.h>
#include <sys/param.h>
#ifndef _TYPES
 /* assume included by param.h */
# include <sys/types.h>
#endif
#include <sys/time.h>
#include <sys/resource.h>
#include <netat/appletalk.h>
#include <netat/afp.h>
#include <netat/afpcmd.h>
#include "afpntoh.h"
#include "afps.h"
#ifdef USETIMES
# include <sys/times.h>
#endif

/* consistency */
#ifdef USETIMES
# define NORUSAGE
#endif

typedef struct {
  int   dsp_cmd;
  PFE   dsp_rtn;
  char *dsp_name;
  int	dsp_flg;
#define DSPF_DMPIN  01
#define DSPF_DMPOUT 02
#define DSPF_DOESSPWRITE 0x4	/* needs more args.. */
#define DSPF_DMPBOTH (DSPF_DMPIN | DSPF_DMPOUT)
  int   dsp_cnt;
#ifndef NORUSAGE
  struct timeval dsp_utime;
  struct timeval dsp_stime;
#endif
#ifdef USETIMES
  time_t dsp_utime;
  time_t dsp_stime;
#endif
} AFPDispEntry;

private char *afpmtyp = "Unix";

#define AFPVERSZ (4*16)			/* room for IndStr hold all versions */
/* define 1 more than needed just in case */
struct afpversionstruct {
  char *version_name;
  int version;
} afpversions[] = {
  {"AFPVersion 1.0", AFPVersion1DOT0},
  {"AFPVersion 1.1", AFPVersion1DOT1},
  {"AFPVersion 2.0", AFPVersion2DOT0},
  {NULL, AFPVersionUnknown}
};

#define MAX_USE_UAM 4			/* maximum number at one time */
#define NUAM "No User Authent"
#define CUAM "Cleartxt passwrd"
#define RUAM "Randnum exchange"
private int numuam = 0;
struct afpuamstruct {
  char *uamname;
  int uam;
};
private struct afpuamstruct afpuams[MAX_USE_UAM];

private byte uamflags[] = {
    0,					/* no flags for "No User Authent" */
    UAMP_USER|UAMP_PASS|UAMP_ZERO,	/* user and password for cleartext */
					/* zero means put a zero to even */
					/* out packet between user & passwd */
    UAMP_USER				/* user only in randnum */
					/* (not implemented) */
    };

#define AFPUAMSZ (MAX_USE_UAM*17+1)	/* room for IndStr */


private AFPDispEntry *DispTab[AFPMaxCmd+1];

private AFPDispEntry Entries[] = {
  {AFPByteRangeLock,FPByteRangeLock,"ByteRangeLock",0,0},
  {AFPChgPasswd, FPChgPasswd, "ChangePassword", 0, 0},
  {AFPCloseVol,FPCloseVol,"CloseVol",0,0},
  {AFPCloseDir,FPCloseDir,"CloseDir",0,0},
  {AFPCloseFork,FPCloseFork,"CloseFork",0,0},
  {AFPCopyFile,FPCopyFile,"CopyFile",0,0},
  {AFPCreateDir,FPCreateDir,"CreateDir",0,0},
  {AFPCreateFile,FPCreateFile,"CreateFile",0,0},
  {AFPDelete,FPDelete,"Delete",0,0},
  {AFPEnumerate,FPEnumerate,"Enumerate",0,0},
  {AFPFlush,FPFlush,"Flush",0,0},
  {AFPFlushFork,FPFlushFork,"FlushFork",0,0},
  {AFPGetForkParms,FPGetForkParms,"GetForkParms",0,0},
  {AFPGetSrvrParms,FPGetSrvrParms,"GetSrvrParms",0,0},
  {AFPGetVolParms,FPGetVolParms,"GetVolParms",0,0},
  {AFPLogin,FPLogin,"Login",0,0},
  {AFPLoginCont,FPLoginCont,"LoginCont",0,0},
  {AFPLogout,FPLogout,"Logout",0,0},
  {AFPMapID,FPMapID,"MapID",0,0},
  {AFPMapName,FPMapName,"MapName",0,0},
  {AFPMove,FPMove,"Move",0,0},
  {AFPOpenVol,FPOpenVol,"OpenVol",0,0},
  {AFPOpenDir,FPOpenDir,"OpenDir",0,0},
  {AFPOpenFork,FPOpenFork,"OpenFork",0,0},
  {AFPRead,FPRead,"Read",0,0},
  {AFPRename,FPRename,"Rename",0,0},
  {AFPSetDirParms,FPSetDirParms,"SetDirParms",0,0},
  {AFPSetFileParms,FPSetFileParms,"SetFileParms",0,0},
  {AFPSetForkParms,FPSetForkParms,"SetForkParms",0,0},
  {AFPSetVolParms,FPSetVolParms,"SetVolParms",0,0},
  {AFPWrite,FPWrite,"Write",DSPF_DOESSPWRITE,0},
  {AFPGetFileDirParms,FPGetFileDirParms,"GetFileDirParms",0,0},
  {AFPSetFileDirParms,FPSetFileDirParms,"SetFileDirParms",0,0},
  {AFPOpenDT,FPOpenDT,"OpenDT",0,0},
  {AFPCloseDT,FPCloseDT,"CloseDT",0,0},
  {AFPGetIcon,FPGetIcon,"GetIcon",0,0},
  {AFPGetIconInfo,FPGetIconInfo,"GetIconInfo",0,0},
  {AFPAddAPPL,FPAddAPPL,"AddAPPL",0,0},
  {AFPRmvAPPL,FPRmvAPPL,"RmvAPPL",0,0},
  {AFPGetAPPL,FPGetAPPL,"GetAPPL",0,0},
  {AFPAddComment,FPAddComment,"AddComment",0,0},
  {AFPRmvComment,FPRmvComment,"RmvComment",0,0},
  {AFPGetComment,FPGetComment,"GetComment",0,0},
  {AFPAddIcon,FPAddIcon,"AddIcon",DSPF_DOESSPWRITE,0}
};

#define NumEntries (sizeof(Entries)/sizeof(AFPDispEntry))

private DumpBuf(), clockstart(), clockend();

IniServer()			/* ini disp entries */
{
  int i;

  for (i=0; i < AFPMaxCmd; i++)		/* clear all pointers */
    DispTab[i] = (AFPDispEntry *) 0;	/* in dispatch table */
    
  for (i=0; i < NumEntries; i++) {	/* for each entry add into dispatch */
    DispTab[Entries[i].dsp_cmd] = &Entries[i];
#ifndef NORUSAGE
    Entries[i].dsp_utime.tv_sec = 0;
    Entries[i].dsp_utime.tv_usec = 0;
    Entries[i].dsp_stime.tv_sec = 0;
    Entries[i].dsp_stime.tv_usec = 0;
#endif
#ifdef USETIMES
    Entries[i].dsp_utime = 0;
    Entries[i].dsp_stime = 0;    
#endif
  }

  InitOSFI();			/* init finder file info */
  ECacheInit();			/* init afposenum cache */
  InitIconCache();		/* init afpdt cache */
  InitDID();			/* init directory stuff  */
}

private int loggedin = FALSE;

OSErr
SrvrDispatch(pkt,len,rsp,rlen,cno,reqref)
byte *pkt,*rsp;
int len,*rlen;
int cno;
ReqRefNumType reqref;
{
  AFPDispEntry *d; 
  int err;
  byte *p;
  byte cmd;
  

  *rlen = 0;
  cmd = *pkt;
  if ((d = DispTab[cmd]) == (AFPDispEntry *) 0) {
    printf("SrvrDispatch: Unknown cmd=%d (%x)\n",cmd,cmd);
    DumpBuf(pkt,len,"Unknown Cmd",cmd);
    return(aeCallNotSupported);    
  }

  if (DBSRV)
    printf("SrvrDispatch cmd=%s (%d 0x%x), len=%d\n",
	   d->dsp_name,cmd,cmd,len);


  d->dsp_cnt++;			/* increment counter */

  if (d->dsp_flg & DSPF_DMPIN)
   DumpBuf(pkt,len,d->dsp_name,cmd);

  if (statflg)
    clockstart();

  if (!loggedin && d->dsp_cmd != AFPLogin && d->dsp_cmd != AFPLoginCont) {
    log("SrvrDispath: Security. Cmd before login %s\n",d->dsp_name);
    err = aeMiscErr;
  } else {
    if (d->dsp_flg & DSPF_DOESSPWRITE) 
      err = (*d->dsp_rtn)(pkt,len,rsp,rlen,cno,reqref);
    else
      err = (*d->dsp_rtn)(pkt,len,rsp,rlen);
  }

  if (statflg) 
    clockend(d);

  if (d->dsp_flg & DSPF_DMPOUT)
    DumpBuf(rsp,*rlen,d->dsp_name,cmd);
  return(err);
}

#define PERLINE 20

/*
 * If you can understand this, then you are far better person than I...
 * This code is convoluted unecessarily, but it really isn't important
 * code, so...
 *
*/
private 
DumpBuf(p,l,s,c)
byte *p;
int l;
char *s;
byte c;
{
  int ln,ll,left;
  int lll;

  printf("DmpBuf for %s (%d 0x%x) length = %d\n",s,c,c,l);
  if (l > 1000)
    lll = 1000;
  else
    lll = l;

  for (ll = 0; ll < lll; ll += PERLINE) {
    
    printf(" %06d ",ll);
    left = (lll-ll < PERLINE ? lll-ll : PERLINE);
    for (ln=0; ln < left; ln++)
      printf("%02x ",(unsigned int) p[ln]);

    printf("\n        ");
    for (ln=0; ln < left; ln++)
      if ((p[ln] < 0200) && isprint(p[ln]))
	printf("%2c ",p[ln]);
      else
	printf(" . ");
    printf("\n");
    p += PERLINE;
  }
  if (lll < l)
    printf(" ... too large to print ... ");
}

 
/*ARGSUSED*/
OSErr
FPMapName(p,l,r,rl)
byte *p,*r;
int *rl,l;
{
  char name[MAXPSTR];
  OSErr err;
  sdword id;
  MNPPtr mnp = (MNPPtr) p;

  cpyp2cstr(name,mnp->mpn_name);	/* copy into c string */
  err = OSMapName(mnp->mpn_fcn,name,&id);
  if (err != noErr)
    return(err);
  PackDWord(id,r);		/* pack long format */
  *rl = 4;			/* length of reply */
  return(noErr);
}


OSErr
FPMapID(p,l,r,rl)
byte *p,*r;			/* packet and response */
int *rl,l;			/* response length */
{
  char name[MAXPSTR];
  OSErr err;
  MapIDPkt mip;

  ntohPackX(PsMapID,p,l,(byte *) &mip);
  err = OSMapID(mip.mpi_fcn,name,mip.mpi_id);
  if (err != noErr)
    return(err);
  cpyc2pstr(r,name);		/* copy to pascal string for return */
  *rl = strlen(name)+1;		/* return length of reply packet */
  return(noErr);
}


/*
 * Begin login code
 *
 * Login methods assume we are "forked" from main server!!!
 *
*/

/*
 * map a uam string to internal uam number
 *
*/
private int
mapuamstr(uamstr)
char *uamstr;
{
  int i;

  if (uamstr == NULL)
    return(UAM_UNKNOWN);
  for (i = 0; i < numuam; i++) 
    /* do it case insenstive */
    if (strcmpci(uamstr, afpuams[i].uamname) == 0) 
      return(afpuams[i].uam);
  return(UAM_UNKNOWN);
}

private int
mapverstr(verstr)
char *verstr;
{
  struct afpversionstruct *av;

  for (av = afpversions; av->version_name != NULL ; av++) 
    if (strcmpci(verstr, av->version_name) == 0)
      return(av->version);
  return(AFPVersionUnknown);
}

export void
allowguestid(gn)
char *gn;
{
  if (!setguestid(gn))
    return;
  afpuams[numuam].uamname = NUAM;
  afpuams[numuam].uam = UAM_ANON;
  numuam++;
}

export void
allowcleartext()
{
  afpuams[numuam].uamname = CUAM;
  afpuams[numuam].uam = UAM_CLEAR;
  numuam++;
}

export void
allowrandnum(pw)
char *pw;
{
  if (!setpasswdfile(pw))
    return;
  afpuams[numuam].uamname = RUAM;
  afpuams[numuam].uam = UAM_RANDNUM;
  numuam++;
}

/* follow vars are used for randnum exchange */
private LoginReplyPkt rne = {	/* initialize to minimize */
  0x0,				/* possiblity of bad transaction */
  0xffff,			/* when starting */
  {0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8}
};
private char username[50];	/* some reasonable length */

/*ARGSUSED*/
OSErr
FPLogin(p,l,r,rl)
byte *p,*r;
int l,*rl;
{
  LoginPkt lg;
  char upwd[MAXPASSWD+1];
  int uam, i, vers;
  OSErr err;

  if (loggedin)			/* already logged in */
    return(aeMiscErr);

  (void) ntohPackX(PsLogin,p,l,(byte *) &lg);

  /* locate user authentication method and unpack accordingly */

  if ((vers = mapverstr(lg.log_ver)) == AFPVersionUnknown) {
    log("Bad version %s", lg.log_ver);
    return(aeBadVersNum);
  }
  /* mactime <-> unix time is different for afp 1.1 */
  /* this calldown is to afpcmd */
  InitPackTime(vers == AFPVersion1DOT0);

  if ((uam = mapuamstr(lg.log_uam)) == UAM_UNKNOWN) {	/* map the uam */
    log("BAD UAM %s in connection attempt", lg.log_uam);
    return(aeBadUAM);			/* no... forget it */
  }

  (void) ntohPackXbitmap(PsLogin,p,l,(byte *) &lg, uamflags[uam]);
  if (uam == UAM_RANDNUM) {
    if ((err = OSLoginRand(lg.log_user)) != noErr)
      return(err);
    for (i = 0; i < 8; i++)
      rne.logr_randnum[i] = (byte)OSRandom();
    strcpy(username, lg.log_user); /* remember it */
    rne.logr_flag = UAMP_RAND|UAMP_INUM;
    rne.logr_idnum = OSRandom(); /* some random number */
    *rl = htonPackXbitmap(PsLoginReply, (byte *)&rne, r, rne.logr_flag);
    return(aeAuthContinue);
  }

  if (uamflags[uam] & UAMP_PASS) {
    bcopy(lg.log_passwd,upwd,MAXPASSWD);	/* copy the password */
    upwd[MAXPASSWD] = '\0';		/* make sure null terminated */
  }
  if (DBSRV) {
    printf("Login ver=%s, uam=%s, user=%s, pwd=%s\n",
	    lg.log_ver,lg.log_uam,
	  ((uamflags[uam] & UAMP_USER) ? (char *)lg.log_user : "<anonymous>"),
	    ((uamflags[uam] & UAMP_PASS) ? upwd : "no passwd"));
  }

  err = OSLogin((char *)lg.log_user, upwd, NULL, uam);
  if (err == noErr)
    loggedin = TRUE;
  return(err);
}  

/*ARGSUSED*/
OSErr
FPLoginCont(p,l,r,rl)
byte *p,*r;
int l,*rl;
{
  LoginContPkt lcp;
  OSErr err;

  ntohPackXbitmap(PsLoginCont, p, l, &lcp, UAMP_INUM); /* get id num */
  if (lcp.lgc_idno != rne.logr_idnum)
    return(aeParamErr);		/* what else to do? */
  /* everything okay, so unpack encrypted passwd */
  ntohPackXbitmap(PsLoginCont, p, l, &lcp, UAMP_INUM|UAMP_ENCR);
  if (DBSRV) {
    printf("Login Randnum exchange for %s\n",username );
  }
  err = OSLogin(username, lcp.lgc_encrypted, rne.logr_randnum, UAM_RANDNUM);
  if (err == noErr)
    loggedin = TRUE;
  return(err);
}

OSErr
FPChgPasswd(p, l, r, rl)
byte *p, *r;
int l, *rl;
{
  ChgPasswdPkt cpp;
  ChgPasswdReplyPkt cprp;
  int uam;
  OSErr err;

  (void)ntohPackX(PsChangePassword, p, l, (byte *)&cpp);
  if ((uam = mapuamstr(cpp.cp_uam)) == UAM_UNKNOWN) {
    log("BAD UAM %s in change password attempt", cpp.cp_uam);
    return(aeBadUAM);			/* no... forget it */
  }
  if (uam == UAM_ANON)
    return(aeBadUAM);
  if (DBSRV) {
    printf("ChangePassword uam=%s, user=%s\n",
	   cpp.cp_uam, cpp.cp_user);
  }
  err = OSChangePassword(cpp.cp_user, cpp.cp_oldpass, cpp.cp_newpass, uam);
  if (err != noErr || uam != UAM_CLEAR)
    return(err);
  /* only here if noErr in change and cleartext */
  bzero(&cprp, sizeof(cprp));	/* make sure zero first */
  cprp.cpr_cmd = 0;
  cprp.cpr_zero = 0;
  strcpy(cprp.cpr_uam, cpp.cp_uam); /* copy uam */
  bcopy(cpp.cp_newpass, cprp.cpr_newpass, sizeof(cprp.cpr_newpass));
  *rl = htonPackX(PsChangePasswordReply, (byte *)&cprp, r);
  return(noErr);
}

/*
 * AFP2.0: get user information 
*/
FPGetUserInfo(p, l, r, rl)
byte *p, *r;
int l, *rl;
{
  GetUserInfoPkt guip;
  GetUserInfoReplyPkt guirp;
  OSErr err;

  (void)ntohPackX(PsGetUserInfo, p, l, (byte *)&guip);
  guirp.guir_bitmap = guip.gui_bitmap;
  err=OSGetUserInfo((guip.gui_flag & GUI_THIS_USER)!=0,guip.gui_userid,&guirp);
  if (err != noErr)
    return(err);
  *rl=htonPackXbitmap(PsGetUserInfoReply,(byte *)&guirp,r,guirp.guir_bitmap);
  return(noErr);
}

#ifdef notdef
int IndStrPack(d,s)
byte *d;
char *s[];
{
  IniIndStr(d);				/* init indexed string */
  for (; *s != NULL; s++)		/* copy entries from array */
    AddIndStr(*s,d);			/*  adding each array element */
  return(IndStrLen(d));			/* return length */
}
#endif

int 
GetSrvrInfo(r,sname,icon,iconsize)
byte *r;
char *sname;
byte icon[];
int iconsize;
{
  GetSrvrInfoReplyPkt sr;
  int i,len;
  byte avobuf[AFPVERSZ],uamobuf[AFPUAMSZ];
  OPTRType avo,uamo,vicono;

  strcpy(sr.sr_machtype,afpmtyp);
  cpyc2pstr(sr.sr_servername,sname);
  sr.sr_flags = SupportsFPCopyFile;

  vicono.optr_loc = icon;
  vicono.optr_len = iconsize;
  sr.sr_vicono = (char *) &vicono;

  IniIndStr(avobuf);
  for (i = 0; afpversions[i].version_name != NULL; i++)
    AddIndStr(afpversions[i].version_name, avobuf);
  avo.optr_len = IndStrLen(avobuf);
  avo.optr_loc = avobuf;
  sr.sr_avo = (byte *) &avo;

  IniIndStr(uamobuf);
  for (i=0 ; i < numuam; i++)
    AddIndStr(afpuams[i].uamname, uamobuf);
  uamo.optr_len = IndStrLen(uamobuf);
  uamo.optr_loc = uamobuf;
  sr.sr_uamo = (byte *) &uamo;
  return(htonPackX(ProtoSRP,(byte *) &sr,r));
}

PrtSrvrInfo(pkt,pktl)
byte *pkt;
int pktl;
{
  GetSrvrInfoReplyPkt sr;
  char name[MAXSNAM+1];

  ntohPackX(ProtoSRP,pkt,pktl,(byte *) &sr); /* unpack from net */
  cpyp2cstr(name,sr.sr_servername);

  printf("PrtSrvrInfo Packet length is %d\n",pktl);
  printf("  Machine Type = %s\n",sr.sr_machtype);
  printf("  Server Name  = %s\n",name);
  printf("  Flags = %d\n",sr.sr_flags);
  printf("  AFP Versions Supported:\n");
  PrtIndStr(sr.sr_avo);
  printf("  User Authentication Methods:\n");
  PrtIndStr(sr.sr_uamo);
  printf("  Icon %s present\n",(sr.sr_vicono == 0) ? "is not" : "is");
}

/*ARGSUSED*/
OSErr
FPLogout(p,l,r,rl)
char *p,*r;
int l,*rl;
{
  printf("FPLogout\n");
  return(noErr);
}

/*
 * OSErr FPGetSrvrParms(byte *p,byte *r,int *rl)
 *
 * This call is used to retrieve server-level parameters (for now only
 * the volume names)
 *
 */

/*ARGSUSED*/
OSErr
FPGetSrvrParms(p,l,r,rl)
byte *p,*r;
int l,*rl;
{
  extern PackEntry ProtoGSPRP[];
  GetSrvrParmsReplyPkt gpr;
  byte *up;
  int cnt;

  gpr.gspr_time = CurTime();
  gpr.gspr_nvols = VCount();
  cnt = htonPackX(ProtoGSPRP, (byte *)&gpr, r);
  cnt += VMakeVList(r+cnt);	/* add vols, return count */
  *rl = cnt;			/* pkt length */
  return(noErr);		/* no error */
}

/*
 * void SrvrSetTrace(char *tstr)
 *
 * Set trace of command packets on input, output, or both.  Tstr 
 * is a string containing items of the form:
 *
 *	[I|O|B]CmdName [I|O|B]CmdName ...
 *
 * The first character of an item specifies if you want the packet
 * displayed on input, output, or both input and output. The rest
 * of the item is the name of the command, Enumerate or AddAPPL for
 * example.
 *
 */

int SetPktTrace(tstr)
char *tstr;
{
  int flg,i;
  char cmdbuf[40],*cp,*opt,c;
  
  while (*tstr != '\0') {
    while (*tstr == ' ' || *tstr == '\t')
      tstr++;

    if (*tstr == '\0')
      break;
    
    switch (c = *tstr++) {
    case 'I': case 'i':
      flg = DSPF_DMPIN; opt = "input"; break;
    case 'O': case 'o':
      flg = DSPF_DMPOUT; opt = "output"; break;
    case 'B': case 'b':
      flg = DSPF_DMPBOTH; opt = "input and output"; break;
    default:
      fprintf(stderr,"SetPktTrace: I, O, or B required.  %c unknown\n",c);
      return(FALSE);
    }

    cp = cmdbuf;
    while (*tstr != ' ' && *tstr != '\t' && *tstr != '\0')
      *cp++ = *tstr++;
    *cp++ = '\0';
    for (i=0; i < NumEntries; i++)
      if (strcmpci(cmdbuf,Entries[i].dsp_name) == 0) {
	fprintf(stderr,"SetPktTrace: Tracing %s on %s\n",
		Entries[i].dsp_name,opt);
	Entries[i].dsp_flg = flg;
	break;
      }
    if (i >= NumEntries) {
      fprintf(stderr,"SetPktTrace: Unknown command %s\n",cmdbuf);
      return(FALSE);
    }
  }
  return(TRUE);
}

#ifndef NORUSAGE
private 
acctime(b,a,s)
struct timeval *b,*a,*s;
{
  s->tv_sec += a->tv_sec - b->tv_sec;
  s->tv_usec += a->tv_usec - b->tv_usec;
  if (s->tv_usec < 0) {
    s->tv_sec--;
    s->tv_usec += uSEC;
  }
  if (s->tv_sec > uSEC) {
    s->tv_sec++;
    s->tv_usec -= uSEC;
  }
}

private
printt(s,tv)
char *s;
struct timeval *tv;
{
  printf("%3d.%06d %s ",tv->tv_sec,tv->tv_usec,s);
}

private struct rusage before;

private
clockstart()
{
  getrusage(RUSAGE_SELF,&before);
}

private
clockend(d)
AFPDispEntry *d; 
{
  struct rusage after;

  getrusage(RUSAGE_SELF,&after);

  acctime(&before.ru_utime,&after.ru_utime,&d->dsp_utime);
  acctime(&before.ru_stime,&after.ru_stime,&d->dsp_stime);
}
#endif

#ifdef USETIMES
private struct tms before;

private
clockstart()
{
  (void)times(&before);
}

private
clockend(d)
AFPDispEntry *d; 
{
  struct tms after;

  (void)times(&after);

  d->dsp_utime += after.tms_utime - before.tms_utime;
  d->dsp_stime += after.tms_stime - before.tms_stime;
  d->dsp_utime += after.tms_cutime - before.tms_cutime;
  d->dsp_stime += after.tms_cstime - before.tms_cstime;
}

#ifndef HZ			/* in case not defined... */
# define HZ 60
#endif
private
printt(s,tv)
char *s;
time_t *tv;
{
  float pval;
  pval = ((float)*tv)/((float)HZ);
  printf("%.06f %s ",pval,s);
}

#endif

SrvrPrintStats()
{
  int i;

  printf("\nServer Statistics:\n");
  for (i=0; i < NumEntries; i++)
    if (Entries[i].dsp_cnt != 0) {
      printf("  %20s Count=%5d ",Entries[i].dsp_name,
	     Entries[i].dsp_cnt);
      printt("Usr",&Entries[i].dsp_utime);
      printt("Sys",&Entries[i].dsp_stime);
      printf("\n");
    }
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.