ftp.nice.ch/pub/next/unix/network/www/analog.NIHS.bs.gnutar.gz#/analog/hash.c

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

/*** analog 1.9beta ***/
/* Please read Readme.html, or http://www.statslab.cam.ac.uk/~sret1/analog/  */

#include "analhea2.h"

/*** hash.c; the functions which do all the work in the hash tables. ***/

/*** The first few are all exactly the same, adding a certain number of
  requests and a certain number of bytes to the hash entry for that item,
  creating a new hash entry if none existed before. ***/

extern void *xmalloc();    /* in utils.c */
                           /* used in so many functions, I declare it here */

void hashadd(struct genstruct *objhead[], int hashsize, char *name, int reqs,
	     double bytes, flag last7q, int *totalobjs,
	     int *totalobjs7, int *totalnew7, flag al)
{
  extern int magicno();             /* in utils.c */

  int magicnumber;
  flag finished;
  struct genstruct *p, *lastp, *prevp;

  /* First calculate name's "magic number" */

  magicnumber = magicno(name, hashsize);

  /* Now look through the magicnumber'th list for that URL */

  finished = FALSE;
  p = (objhead[magicnumber]);
  lastp = p;
  prevp = p;
  while (p -> name != NULL && !finished) {
    if (STREQ(p -> name, name)) {   /* then done */
      p -> reqs += reqs;
      p -> bytes += bytes;
      if (last7q && !(p -> last7)) {
	(*totalobjs7)++;
	p -> last7 = TRUE;
      }
      if (!last7q && !(p -> pre7)) {
	p -> pre7 = TRUE;
	(*totalnew7)--;     /* Must have p -> last7, so have wrongly
			       counted it as new in last 7 */
      }
      /* keep the list in rough (not exact) order of number of
	 accesses for quicker searching; particularly useful if
	 the HASHSIZE is set too low. */
      if (p -> reqs > lastp -> reqs) {
	if (prevp == lastp) {   /* iff p is 2nd in the list */
	  objhead[magicnumber] = p;
	  lastp -> next = p -> next;
	  p -> next = lastp;
	}
	else {  /* p is 3rd or later in the list */
	  prevp -> next = p;
	  lastp -> next = p -> next;
	  p -> next = lastp;
	}
      }
      finished = TRUE;
    }
    else {      /* look at the next one */
      prevp = lastp;
      lastp = p;
      p = p -> next;
    }
  }
  
  if (!finished) {   /* reached the end of the list without success; new one */
    (*totalobjs)++;
    p -> name = (char *)xmalloc((size_t)((int)strlen(name) + 1));
    strcpy(p -> name, name);
    p -> reqs = reqs;
    p -> bytes = bytes;
    p -> aliasdone = al;
    if (last7q) {
      (*totalobjs7)++;
      (*totalnew7)++;
      p -> last7 = TRUE;
      p -> pre7 = FALSE;
    }
    else {
      p -> last7 = FALSE;
      p -> pre7 = TRUE;
    }
    p -> next = (struct genstruct *)xmalloc(sizeof(struct genstruct));
    p -> next -> name = NULL;
  }
}

/*** The domain hashadd function is different from the others, because we
  already know all domains, so there need be no clashes in the hash table. ***/

void domhashadd(char *hostn, int reqs, double bytes)
{
  extern int hosttodomcode();           /* in alias.c */
  extern flag subdomadd();              /* in this file */
  extern int magicno();                 /* in utils.c */

  extern struct domain *domainhead[], *subdomhead[], *wildsubdomhead,
                       *nwildsubdomhead;
  extern int debug;

  int domcode;   /* domcode is as explained at the bit where we read the
		    domains in. It's the equivalent of magic number for
		    the other hashadd functions. */
  int magicnumber;  /* for subdomains */
  flag finished;
  struct domain *domp;
  char *tempp;
  char tempchar;

  domcode = hosttodomcode(hostn);
  if (domcode == DOMHASHSIZE - 2 && debug >= 2)
    fprintf(stderr, "U: %s\n", hostn);

  domainhead[domcode] -> reqs += reqs;
  domainhead[domcode] -> bytes += bytes;

  /* Next run through the list of wild subdomains. There are two cases;
     numerical and ordinary subdomains */

  if (!isdigit(hostn[(int)strlen(hostn) - 1])) {  /* non-numerical */

    for (domp = wildsubdomhead; domp -> id != NULL; domp = domp -> next) {
      if(STREQ(domp -> id,
	       hostn + MAX((int)strlen(hostn) - (int)strlen(domp -> id), 0))) {
	/* run back to just after the previous . (or the initial character) */
	tempp = hostn + MAX((int)strlen(hostn) - (int)strlen(domp -> id), 0);
	while (tempp != hostn && *(tempp - 1) != '.')
	  tempp--;
	/* now add that one to the list of subdoms; it will get looked at
	   in the next stage */
	subdomadd(tempp, "?");
      }
    }

    /* now run through the subdomains this hostname could belong to and see
       if any of them are required to be analysed */

    tempp = strrchr(hostn, '.');  /* the final dot */

    if (tempp != NULL) {

      while (tempp != hostn) {
	tempp--;
	while (tempp != hostn && *(tempp - 1) != '.')
	  tempp--;
	magicnumber = magicno(tempp, SUBDOMHASHSIZE);
	finished = OFF;
	for (domp = subdomhead[magicnumber]; domp -> name != NULL && !finished;
	     domp = domp -> next) {
	  if (STREQ(domp -> id, tempp)) {
	    domp -> reqs += reqs;
	    domp -> bytes += bytes;
	    finished = ON;
	  }
	}
      }
    }

  }   /* end non-numerical subdomains; now do numerical ones */

  else {

    /* wild numerical subdomains */

    for (domp = nwildsubdomhead; domp -> id != NULL; domp = domp -> next) {
      if(strncmp(domp -> id, hostn, (size_t)(int)strlen(domp -> id)) == 0) {
	/* run to the next . (or the end) */
	tempp = hostn + (int)strlen(domp -> id);
	while (*tempp != '.' && *tempp != '\0')
	  tempp++;
	/* now add that one to the subdoms */
	tempchar = *tempp;
	*tempp = '\0';   /* temporarily trucate the string after the subdom */
	subdomadd(hostn, "?");
	*tempp = tempchar;
      }
    }

    /* and now run through the ordinary subdoms for this numerical host */

    tempp = hostn + (int)strlen(hostn);

    while (tempp != hostn) {
      while (tempp != hostn && *tempp != '.' && *tempp != '\0')
	tempp--;
      if (tempp != hostn) {
	tempchar = *tempp;
	*tempp = '\0';   /* temporarily trucate the string again */
	magicnumber = magicno(hostn, SUBDOMHASHSIZE);
	finished = OFF;
	for (domp = subdomhead[magicnumber]; domp -> name != NULL && !finished;
	     domp = domp -> next) {
	  if (STREQ(domp -> id, hostn)) {
	    domp -> reqs += reqs;
	    domp -> bytes += bytes;
	    finished = ON;
	  }
	}
	*tempp = tempchar;
	tempp--;
      }
    }
  }

}

/*** Add a new subdomain to the list of subdomains ***/

flag subdomadd(char *id, char *name)
{
  extern char *reversehostname(); /* in alias.c */

  extern struct domain *subdomhead[SUBDOMHASHSIZE];
  extern struct domain *wildsubdomhead, *nwildsubdomhead;

  struct domain *domp, *domnextp;
  int magicnumber;
  flag numeric;       /* whether it's a numerical subdomain */

  if (id[0] == '?')  /* we don't want it */
    ;

  else if (id[0] == '*') {  /* put at start wild list (order doesn't matter) */
    domnextp = wildsubdomhead;
    wildsubdomhead = (struct domain *)xmalloc(sizeof(struct domain));
    wildsubdomhead -> next = domnextp;
    wildsubdomhead -> id = xmalloc((size_t)((int)strlen(id)));
    strcpy(wildsubdomhead -> id, id + 1);
  }

  else if (id[(int)strlen(id) - 1] == '*') {
    domnextp = nwildsubdomhead;
    nwildsubdomhead = (struct domain *)xmalloc(sizeof(struct domain));
    nwildsubdomhead -> next = domnextp;
    nwildsubdomhead -> id = xmalloc((size_t)((int)strlen(id)));
    strncpy(nwildsubdomhead -> id, id, (size_t)((int)strlen(id) - 1));
    *(nwildsubdomhead -> id + (int)strlen(id) - 1) = '\0';
  }

  else if (id[0] == '%') {   /* token representing "all numerical domains" */
    domnextp = nwildsubdomhead;
    nwildsubdomhead = (struct domain *)xmalloc(sizeof(struct domain));
    nwildsubdomhead -> next = domnextp;
    nwildsubdomhead -> id = xmalloc(1);
    nwildsubdomhead -> id[0] = '\0';
  }

  else {
    magicnumber = magicno(id, SUBDOMHASHSIZE);
    domp = subdomhead[magicnumber];

    /* look through that list and slot it in alphabetically
       (for quicker finding than random if it's repeated later) */

    if (!isdigit(id[(int)strlen(id) - 1])) {
      reversehostname(id);
      numeric = FALSE;
    }
    else
      numeric = TRUE;

    /* if it goes at the beginning of the list, slot it in there */
    if (domp -> name == NULL || strcmp(domp -> revid, id) > 0) {
      domnextp = domp;
      domp = (struct domain *)xmalloc(sizeof(struct domain));
      subdomhead[magicnumber] = domp;
      domp -> revid = xmalloc((size_t)((int)strlen(id) + 1));
      strcpy(domp -> revid, id);
      domp -> id = xmalloc((size_t)((int)strlen(id) + 1));
      if (!numeric)
	reversehostname(id);
      strcpy(domp -> id, id);
      domp -> name = xmalloc((size_t)((int)strlen(name) + 1));
      strcpy(domp -> name, name);
      domp -> reqs = 0;
      domp -> bytes = 0;
      domp -> next = domnextp;
      return(TRUE);
    }
    else if (strcmp(domp -> revid, id) == 0) {
      if (!numeric)
	reversehostname(id);
      return(FALSE);
    }
    else {   /* run to right place in alphabet */
      while (domp -> next -> name != NULL &&
	     strcmp(domp -> next -> revid, id) < 0)
	domp = domp -> next;
      if (domp -> next -> name != NULL &&
	  strcmp(domp -> next -> revid, id) == 0) {
	if (!numeric)
	  reversehostname(id);   /* so as to leave it unchanged on exit */
	return(FALSE);
      }
      else {
	domnextp = domp -> next;
	domp -> next = (struct domain *)xmalloc(sizeof(struct domain));
	domp = domp -> next;
	domp -> revid = xmalloc((size_t)((int)strlen(id) + 1));
	strcpy(domp -> revid, id);
	domp -> id = xmalloc((size_t)((int)strlen(id) + 1));
	if (!numeric)
	  reversehostname(id);
	strcpy(domp -> id, id);
	domp -> name = xmalloc((size_t)((int)strlen(name) + 1));
	strcpy(domp -> name, name);
	domp -> reqs = 0;
	domp -> bytes = 0;
	domp -> next = domnextp;
	return(TRUE);
      }
    }
  }
}

/*** Check if we want a certain referer, and add it ***/

void addref(char *fromurl, char *filename, double bytes, flag last7q,
	    flag filemaskq)    /* last ON if extern one ON and not yet done */
{
  extern flag refmaskq;
  extern struct genstruct *refhead[];
  extern struct include *wantrefhead, *wantfilehead;

  flag wantthisone;
  int tempint;

  if (refmaskq)
    wantthisone = (doaliasref(fromurl) != -1) &&
      included(fromurl, wantrefhead);
  if (wantthisone && filemaskq)
    wantthisone = included(filename, wantfilehead);
  if (wantthisone)
    hashadd(refhead, REFHASHSIZE, fromurl, 1, bytes, last7q,
	    &tempint, &tempint, &tempint, OFF);
}

/*** Process a browser line, and add it to the lists ***/
/* (Little to do, so we don't use a browser alias function) */

void addbrowser(char *browser, double bytes, flag last7q)
{
  extern flag bq, Bq;
  extern struct genstruct *browhead[], *fullbrowhead[];

  char *c;
  int tempint;

  /* cut (illegal) "via"s (e.g., via proxy gateway or via Harvest cache) */
  if ((c = strstr(browser, " via ")) != NULL) {
    while (*c != ' ' && c != browser)
      c--;
    *(c + 1) = '\0';
  }

  /* add to full browser report */
  if (Bq)
    hashadd(fullbrowhead, FULLBROWHASHSIZE, browser, 1, bytes,
	    last7q, &tempint, &tempint, &tempint, OFF);

  if (bq) {
    /* generate shortened name */
    if ((c = strchr(browser, '/')) != NULL)
      *c = '\0';

    /* two special cases */
    if (STREQ(browser, "Mozilla"))
      strcpy(browser, "Netscape");
    else if (strstr(browser, "Mosaic") != NULL ||
	     strstr(browser, "mosaic") != NULL)
      strcpy(browser, "Mosaic");

    /* and add to browser summary */
    hashadd(browhead, BROWHASHSIZE, browser, 1, bytes,
	    last7q, &tempint, &tempint, &tempint, OFF);
  }
}

/*** And one to add an error to the list of errors ***/

void adderr(char *errstr)
{
  extern char *strtoupper();       /* in utils.c */

  extern char errs[NO_ERRS][MAXERRLENGTH];
  extern int errors[NO_ERRS];
  extern int debug;

  char e1[MAXLINELENGTH], e2[MAXSTRINGLENGTH];
  int i;
  flag done = OFF;

  for (i = 0; !done; i++) {
    strcpy(e1, errstr);
    strcpy(e2, errs[i]);
    if (strstr(strtoupper(e1), strtoupper(e2)) != NULL) {
      done = ON;
      errors[i]++;
      if (debug >= 2 && i == NO_ERRS - 1)  /* unknown error type */
	fprintf(stderr, "E: %s\n", errstr);
    }
  }
}

/*** Next a function to do an approx count of the number of distinct hosts ***/
/* First some utilities for it */

flag approxhostfilled(char *space, int i)
{    /* whether there is already a 1 at entry i of the table */
  int j, k;

  j = i / 8;
  k = i % 8;

  return((*(space + j) >> k) & 1);
}

void approxhostfill(char *space, int i)
{    /* put a 1 at entry i; ASSUMES IT IS CURRENTLY EMPTY */
  int j, k;

  j = i / 8;
  k = i % 8;
  *(space + j) += (1 << k);

}

void approxhosthashadd(char *hostn, flag last7q)
{
  extern char *approxhostspace, *approxhostspace7;
  extern int approxhostsize;
  extern int no_hosts, no_hosts7, no_new_hosts7;
  extern flag q7;

  int magicnumber1, magicnumber2, magicnumber3, magicnumber4;
  flag seen1, seen2, seen3, seen4; /* whether we'd already seen that number */
  flag seen17, seen27, seen37, seen47;  /* ditto in last 7 days */
  flag seen, seen7;        /* whether we've seen all 4 numbers before */
  register int i;
  /* NB Note approxhostfill assumes empty; be careful about two equal magic
     numbers for some host */

  magicnumber1 = 0;
  for (i = 0; hostn[i] != '\0'; i++) {
    magicnumber1 = 101 * magicnumber1 + hostn[i];
    if (magicnumber1 < 0)
      magicnumber1 = -magicnumber1;
    while (magicnumber1 >= 8 * approxhostsize)
      magicnumber1 -= 8 * approxhostsize;
  }
  seen1 = approxhostfilled(approxhostspace, magicnumber1);
  if (!seen1 && (!q7 || !last7q))  /* if q7 only pre-last7 go here;
				      if !q7 everything does */
    approxhostfill(approxhostspace, magicnumber1);
  if (q7) {
    seen17 = approxhostfilled(approxhostspace7, magicnumber1);
    if (!seen17 && last7q)
      approxhostfill(approxhostspace7, magicnumber1);
  }

  magicnumber2 = 0;
  for (i--; i >= 0; i--) {
    magicnumber2 = 101 * magicnumber2 + hostn[i];
    if (magicnumber2 < 0)
      magicnumber2 = -magicnumber2;
    while (magicnumber2 >= 8 * approxhostsize)
      magicnumber2 -= 8 * approxhostsize;
  }
  seen2 = approxhostfilled(approxhostspace, magicnumber2);
  if (!seen2 && (!q7 || !last7q))
    approxhostfill(approxhostspace, magicnumber2);
  if (q7) {
    seen27 = approxhostfilled(approxhostspace7, magicnumber2);
    if (!seen27 && last7q)
      approxhostfill(approxhostspace7, magicnumber2);
  }

  magicnumber3 = 0;
  for (i = 0; hostn[i] != '\0'; i++) {
    magicnumber3 = 103 * magicnumber3 + hostn[i];
    if (magicnumber3 < 0)
      magicnumber3 = -magicnumber3;
    while (magicnumber3 >= 8 * approxhostsize)
      magicnumber3 -= 8 * approxhostsize;
  }
  seen3 = approxhostfilled(approxhostspace, magicnumber3);
  if (!seen3 && (!q7 || !last7q))
    approxhostfill(approxhostspace, magicnumber3);
  if (q7) {
    seen37 = approxhostfilled(approxhostspace7, magicnumber3);
    if (!seen37 && last7q)
      approxhostfill(approxhostspace7, magicnumber3);
  }

  magicnumber4 = 0;
  for (i--; i >= 0; i--) {
    magicnumber4 = 103 * magicnumber4 + hostn[i];
    if (magicnumber4 < 0)
      magicnumber4 = -magicnumber4;
    while (magicnumber4 >= 8 * approxhostsize)
      magicnumber4 -= 8 * approxhostsize;
  }
  seen4 = approxhostfilled(approxhostspace, magicnumber4);
  if (!seen4 && (!q7 || !last7q))
    approxhostfill(approxhostspace, magicnumber4);
  if (q7) {
    seen47 = approxhostfilled(approxhostspace7, magicnumber4);
    if (!seen47 && last7q)
      approxhostfill(approxhostspace7, magicnumber4);
    seen7 = seen17 && seen27 && seen37 && seen47;
  }

  seen = seen1 && seen2 && seen3 && seen4;

  if (!q7) {
    if (!seen)   /* new host */
      ++no_hosts;
  }

  else {    /* q7 */
    if (!seen && !seen7) {
      ++no_hosts;
      if (last7q) {
	++no_hosts7;
	++no_new_hosts7;
      }
    }
    else if (!seen && seen7 && !last7q)  /* it wasn't really a new host 7 */
      --no_new_hosts7;
    else if (seen && !seen7 && last7q)
      ++no_hosts7;

  }    /* end else (= if q7) */

}   /* end approxhosthashadd() */

/*** Next the functions to do with dates ***/
/* add to the number of requests for a particular month */
void addmonthlydata(int year, int monthno, int reqs, double bytes)
{
  extern struct monthly *firstm, *lastm;
  extern struct timestruct firsttime, lasttime;
  extern flag mback;

  struct monthly *mp;
  int i;

  mp = mback?lastm:firstm;

  for (i = mback?(lasttime.year - year):(year - firsttime.year); i > 0; i--)
    mp = mp -> next;       /* run to the right year */

  mp -> reqs[monthno] += reqs;   /* and add to the right month in that year */
  mp -> bytes[monthno] += bytes;

}

/* add to the number of requests for a particular day */
void adddailydata(int year, int monthno, int date, int reqs, double bytes)
{
  extern struct daily *firstd, *lastd;
  extern struct timestruct firsttime, lasttime;
  extern flag Dback;

  struct daily *dp;
  int i;

  dp = Dback?lastd:firstd;

  for (i = Dback?((lasttime.year - year) * 12 + lasttime.monthno - monthno):
       ((year - firsttime.year) * 12 + monthno - firsttime.monthno);
       i > 0; i--)
    dp = dp -> next;       /* run to the right month */

  dp -> reqs[date - 1] += reqs;  /* and add to the right date */
  dp -> bytes[date - 1] += bytes;

}

/* add to the number of requests for a particular hour */
void addhourlydata(int year, int monthno, int date, int hr, int reqs,
		   double bytes)
{
  extern struct hourly *firsth, *lasth;
  extern struct timestruct firsttime, lasttime;
  extern flag Hback;

  struct hourly *hp;
  int i;

  hp = Hback?lasth:firsth;

  for (i = Hback?minsbetween(date, monthno, year, 0, 0, lasttime.date,
			     lasttime.monthno, lasttime.year, 0, 0) / 1440:
       minsbetween(firsttime.date, firsttime.monthno, firsttime.year, 0, 0,
		   date, monthno, year, 0, 0) / 1440;
       i > 0; i--)
    hp = hp -> next;       /* run to the right day */

  hp -> reqs[hr] += reqs;   /* and add to the right hour */
  hp -> bytes[hr] += bytes;

}

/* add to the number of requests for a particular week */
void addweeklydata(int year, int monthno, int date, int reqs, double bytes)
{
  extern int minsbetween();     /* in utils.c */

  extern struct weekly *firstw, *lastw;
  extern flag Wback;

  struct weekly *wp;

  wp = Wback?lastw:firstw;

  while(minsbetween(wp -> start.date, wp -> start.monthno, wp -> start.year,
		    0, 0, date, monthno, year, 0, 0) * (Wback?(-1):1)
	>= (Wback?1:10080))
    wp = wp -> next;   /* run to right week (when 0 <= minsbetween < 10080) */

  wp -> reqs += reqs;            /* and add to its requests */
  wp -> bytes += bytes;

}

/* and a function to co-ordinate all the date cataloguing */
void datehash(int year, int monthno, int date, int hr, int min,
	      long thistimecode, int reqs, double bytes)
{
  extern long timecode();        /* in utils.c */

  extern flag mq, dq, Dq, Wq, Hq;
  extern flag mback, Dback, Wback, Hback;
  extern struct timestruct firsttime, lasttime;
  extern struct monthly *firstm, *lastm;
  extern struct daily *firstd, *lastd;
  extern struct weekly *firstw, *lastw;
  extern struct hourly *firsth, *lasth;
  extern int monthlength[];
  extern int dailyreq[], hourlyreq[];
  extern double dailybytes[], hourlybytes[];

  int day;
  struct monthly *tempmp;
  struct daily *tempdp;
  struct weekly *tempwp;
  struct hourly *temphp;
  int i, j;

  if (mq) {

    if (year <= firsttime.year) {  /* then we might need a new lot of months */
      for (i = firsttime.year - year; i > 0; i--) {
	tempmp = firstm;
	firstm = (struct monthly *)xmalloc(sizeof(struct monthly));
	if (mback) {
	  tempmp -> next = firstm;
	  firstm -> next = NULL;
	}
	else
	  firstm -> next = tempmp;
	for (j = 0; j < 12; j++) {
	  firstm -> reqs[j] = 0;
	  firstm -> bytes[j] = 0.0;
	}
      }
      firstm -> reqs[monthno] += reqs;    /* add to this month */
      firstm -> bytes[monthno] += bytes;  /* (whether or not newly created) */
    }

    else if (year >= lasttime.year) {     /* similarly */
      for (i = year - lasttime.year; i > 0; i--) {
	tempmp = lastm;
	lastm = (struct monthly *)xmalloc(sizeof(struct monthly));
	if (mback)
	  lastm -> next = tempmp;
	else {
	  tempmp -> next = lastm;
	  lastm -> next = NULL;
	}
	for (j = 0; j < 12; j++) {
	  lastm -> reqs[j] = 0;
	  lastm -> bytes[j] = 0.0;
	}
      }
      lastm -> reqs[monthno] += reqs;
      lastm -> bytes[monthno] += bytes;
    }

    else   /* NB we will never get here if logfile in chron. order */
      addmonthlydata(year, monthno, reqs, bytes);

  }   /* end if (mq) */

  if (Dq) {

    if (year * 12 + monthno <= firsttime.year * 12 + firsttime.monthno) {
      /* then we might need a new lot of days */
      for (i = (firsttime.year - year) * 12 +
	   firsttime.monthno - monthno; i > 0; i--) {
	tempdp = firstd;
	firstd = (struct daily *)xmalloc(sizeof(struct daily));
	if (Dback) {
	  tempdp -> next = firstd;
	  firstd -> next = NULL;
	}
	else
	  firstd -> next = tempdp;
	for (j = 0; j < 31; j++) {
	  firstd -> reqs[j] = 0;
	  firstd -> bytes[j] = 0.0;
	}
      }
      firstd -> reqs[date - 1] += reqs;
      firstd -> bytes[date - 1] += bytes;
    }
    
    else
      if (year * 12 + monthno >= lasttime.year * 12 + lasttime.monthno) {
	for (i = (year - lasttime.year) * 12 - lasttime.monthno + monthno;
	     i > 0; i--) {
	  tempdp = lastd;
	  lastd = (struct daily *)xmalloc(sizeof(struct daily));
	  if (Dback)
	    lastd -> next = tempdp;
	  else {
	    tempdp -> next = lastd;
	    lastd -> next = NULL;
	  }
	  for (j = 0; j < 31; j++) {
	    lastd -> reqs[j] = 0;
	    lastd -> bytes[j] = 0.0;
	  }
	}
	lastd -> reqs[date - 1] += reqs;
	lastd -> bytes[date - 1] += bytes;
      }

      else
	adddailydata(year, monthno, date, reqs, bytes);

  }   /* end if (mq) */

  if (Hq) {

    if (year * /* 12 * 31 */ 372 + monthno * 31 + date <=
	firsttime.year * 372 + firsttime.monthno * 31 + firsttime.date) {
      /* then we might need a new lot of hours */
      for (i = minsbetween(date, monthno, year, 0, 0, firsttime.date,
			   firsttime.monthno, firsttime.year, 0, 0) / 1440;
	   i > 0; i--) {
	temphp = firsth;
	firsth = (struct hourly *)xmalloc(sizeof(struct hourly));
	if (Hback) {
	  temphp -> next = firsth;
	  firsth -> next = NULL;
	}
	else
	  firsth -> next = temphp;
	for (j = 0; j < 24; j++) {
	  firsth -> reqs[j] = 0;
	  firsth -> bytes[j] = 0.0;
	}
      }
      firsth -> reqs[hr] += reqs;
      firsth -> bytes[hr] += bytes;
    }
    
    else
      if (year * 372 + monthno * 31 + date
	  >= lasttime.year * 372 + lasttime.monthno * 31 + lasttime.date) {
	for (i = minsbetween(lasttime.date, lasttime.monthno, lasttime.year,
			     0, 0, date, monthno, year, 0, 0) / 1440;
	     i > 0; i--) {
	  temphp = lasth;
	  lasth = (struct hourly *)xmalloc(sizeof(struct hourly));
	  if (Hback)
	    lasth -> next = temphp;
	  else {
	    temphp -> next = lasth;
	    lasth -> next = NULL;
	  }
	  for (j = 0; j < 24; j++) {
	    lasth -> reqs[j] = 0;
	    lasth -> bytes[j] = 0.0;
	  }
	}
	lasth -> reqs[hr] += reqs;
	lasth -> bytes[hr] += bytes;
      }

      else
	addhourlydata(year, monthno, date, hr, reqs, bytes);

  }   /* end if (mq) */

  if (Wq) {
    if (thistimecode < firstw -> start.code) {   /* new week needed */
      while (thistimecode < firstw -> start.code) {
	tempwp = firstw;
	firstw = (struct weekly *)xmalloc(sizeof(struct weekly));
	if (Wback) {
	  tempwp -> next = firstw;
	  firstw -> next = NULL;
	}
	else
	  firstw -> next = tempwp;
	firstw -> reqs = 0;
	firstw -> bytes = 0.0;
	firstw -> start = tempwp -> start;
	firstw -> start.date -= 7;
	if (firstw -> start.date <= 0) {
	  firstw -> start.monthno--;
	  if (firstw -> start.monthno == -1) {
	    firstw -> start.monthno = 11;
	    firstw -> start.year--;
	  }
	  firstw -> start.date = monthlength[firstw -> start.monthno] +
	    firstw -> start.date +
	      ISLEAPFEB(firstw -> start.monthno, firstw -> start.year);
	}
	firstw -> start.code = timecode(firstw -> start.date,
					firstw -> start.monthno,
					firstw -> start.year, 0, 0);
      }
      firstw -> reqs += reqs;
      firstw -> bytes += bytes;
    }

    else if (thistimecode >= lastw -> start.code) {
      while (minsbetween(lastw -> start.date, lastw -> start.monthno,
			 lastw -> start.year, 0, 0,  /* 10080m = 1w */
			 date, monthno, year, 0, 0) >= 10080) {
	tempwp = lastw;
	lastw = (struct weekly *)xmalloc(sizeof(struct weekly));
	if (Wback)
	  lastw -> next = tempwp;
	else {
	  tempwp -> next = lastw;
	  lastw -> next = NULL;
	}
	lastw -> reqs = 0;
	lastw -> bytes = 0.0;
	lastw -> start = tempwp -> start;
	lastw -> start.date += 7;
	if (lastw -> start.date > monthlength[lastw -> start.monthno] +
	    ISLEAPFEB(lastw -> start.monthno, lastw -> start.year)) {
	  lastw -> start.date -= monthlength[lastw -> start.monthno] +
	    ISLEAPFEB(lastw -> start.monthno, lastw -> start.year);
	  lastw -> start.monthno++;
	  if (lastw -> start.monthno == 12) {
	    lastw -> start.monthno = 0;
	    lastw -> start.year++;
	  }
	}
	lastw -> start.code = timecode(lastw -> start.date,
				       lastw -> start.monthno,
				       lastw -> start.year, 0, 0);
      }
      lastw -> reqs += reqs;
      lastw -> bytes += bytes;
    }
    
    else  /* again, only used if logfile not chronological */
      addweeklydata(year, monthno, date, reqs, bytes);

  }   /* end if (Wq) */

  if (dq) {
    day = dayofdate(date, monthno, year);
    dailyreq[day] += reqs;
    dailybytes[day] += bytes;
  }
  hourlyreq[hr] += reqs;  /* no need to bother checking hq */
  hourlybytes[hr] += bytes;
                            

  if (thistimecode < firsttime.code) {
    firsttime.date = date;
    firsttime.monthno = monthno;
    firsttime.year = year;
    firsttime.hr = hr;
    firsttime.min = min;
    firsttime.code = thistimecode;
  }

  if (thistimecode > lasttime.code) {
    lasttime.date = date;
    lasttime.monthno = monthno;
    lasttime.year = year;
    lasttime.hr = hr;
    lasttime.min = min;
    lasttime.code = thistimecode;
  }
}

/*** Now some routines to sort the various reports ready for printing ***/
/* First a simple bubblesort for the errors */
void errsort(int errorder[NO_ERRS])
{
  extern int errors[NO_ERRS];

  int i, j, tempint;

  for (i = 0; i < NO_ERRS; i++)
    errorder[i] = i;

  for (i = NO_ERRS - 2; i >= 0; i--) {
    for (j = 0; j <= i; j++) {
      if (errors[errorder[j]] < errors[errorder[j + 1]]) {
	tempint = errorder[j];
	errorder[j] = errorder[j + 1];
	errorder[j + 1] = tempint;
      }
    }
  }
}

double bytefloor(double bytes, char str[])
{
  double ans;
  int last;

  last = MAX((int)strlen(str) - 1, 0);

  if (str[last] == '%') {
    str[last] = '\0';
    ans = bytes * atof(str) / 100.0;
    str[last] = '%';
  }

  else if (str[last] == 'k' || str[last] == 'K') {
    str[last] = '\0';
    ans = atof(str) * 1024.0;
    str[last] = 'k';
  }
  else if (str[last] == 'M' || str[last] == 'm') {
    str[last] = '\0';
    ans = atof(str) * 1048576.0;
    str[last] = 'M';
  }
  else if (str[last] == 'G' || str[last] == 'g') {
    str[last] = '\0';
    ans = atof(str) * 1073741824.0;
    str[last] = 'G';
  }
  else if (str[last] == 'T' || str[last] == 't') {
    str[last] = '\0';
    ans = atof(str) * 1099511627776.0;
    str[last] = 'T';
  }

  else
    ans = atof(str);

  return(ans);
}

int reqfloor(int reqs, char str[])
{
  int ans;
  int last;

  last = MAX((int)strlen(str) - 1, 0);

  if (str[last] == '%') {
    str[last] = '\0';
    ans = (int)((double)reqs * atof(str) / 100.0);
    str[last] = '%';
  }

  else
    ans = atoi(str);

  return(ans);
}

/* a function to sort the generic reports */
struct genstruct *gensort(struct genstruct *objhead[], int hashsize,
			  int sortby, char minreqstr[], char minbytestr[],
			  flag pagesonly,  /* for URLs, list only pages? */
			  flag alphahost,  /* an alphabetical hostsort? */
			  int *maxreqs, double *maxbytes, int *maxlength)
{
  extern char *reversehostname();    /* in alias.c */
  extern flag included();            /* in alias.c */
  extern int hoststrcmp();           /* in utils.c */

  extern struct include *ispagehead;
  extern double total_bytes;
  extern int total_succ_reqs;

  flag wantit = ON;    /* whether we want a particular item */
  struct genstruct *sorthead;   /* build up the sort in this list
				(and return it at the end) */
  struct genstruct *p, *p2, *nextp, *lastp;
  int onlist;          /* the list we are processing */
  flag finished;       /* whether we've finished with a particular entry */
  double floorb = 0.0; /* floor for bytes (for byte sorting) */
  int floorr = 0;      /* floor for requests (for other sorting) */
  int outnumber;       /* the number of items to be displayed */

  int i;


  /* first calculate the floor */

  if (sortby == BYBYTES)
    floorb = bytefloor(total_bytes, minbytestr);
  else
    floorr = reqfloor(total_succ_reqs, minreqstr);

  outnumber = 0;
  sorthead = (struct genstruct *)xmalloc(sizeof(struct genstruct));
  sorthead -> name = NULL;           /* as marker */
  onlist = 0;                        /* the list we are on */
  p = objhead[0];                 /* starting at list 0 */
  for ( ; onlist < hashsize; p = nextp)  {
                                     /* run through all the objects */
    if (p -> name == NULL)    {   /* then finished this list */
      nextp = objhead[++onlist];  /* so look at the next list */
    }
    else {
      if (pagesonly)   /* only for URLs */  /* if not, wantit is always ON */
	wantit = included(p -> name, ispagehead);
      if ((sortby == BYBYTES && p -> bytes < floorb) ||
	  (sortby != BYBYTES && p -> reqs < floorr) ||
	  (!wantit)) {  /* we don't want it */
	nextp = p -> next;
      }
      else {
	outnumber++;
	*maxreqs = MAX(p -> reqs, *maxreqs);
	*maxbytes = MAX(p -> bytes, *maxbytes);
	if (alphahost) {  /* some special things for alphabetical host sort */
	  *maxlength = MAX((int)strlen(p -> name), *maxlength);
	  if (!isdigit(p -> name[(int)strlen(p -> name) - 1]))
	    reversehostname(p -> name);
	}
	if ((sorthead -> name == NULL) ||
	    (sortby == RANDOMLY) ||
	    (sortby == BYBYTES && p -> bytes > sorthead -> bytes) ||
	    (sortby == BYREQUESTS && p -> reqs > sorthead -> reqs) ||
	    (sortby == ALPHABETICAL && !alphahost &&
	     strcmp(p -> name, sorthead -> name) < 0) ||
	    (sortby == ALPHABETICAL && alphahost &&
	     hoststrcmp(p -> name, sorthead -> name) < 0)) {
	  /* if it's before the first item currently on the list, slot it in */
	  nextp = p -> next;   /* the next one we're going to look at */
	  p -> next = sorthead;
	  sorthead = p;
	}
	else {                   /* otherwise compare with the ones so far */
	  finished = OFF;
	  lastp = sorthead;
	  if (floorb < 0.0)
	    i = (int)ROUND(floorb);
	  else if (floorr < 0)
	    i = floorr;
	  else
	    i = 1;
	  for (p2 = sorthead -> next;
	       p2 -> name != NULL && (!finished) && (i++) != -1;
	       p2 = p2 -> next) {
	    if ((sortby == BYBYTES && p -> bytes > p2 -> bytes) ||
		(sortby == BYREQUESTS && p -> reqs > p2 -> reqs) ||
		(sortby == ALPHABETICAL && !alphahost &&
		 strcmp(p -> name, p2 -> name) < 0) ||
		(sortby == ALPHABETICAL && alphahost &&
		 hoststrcmp(p -> name, p2 -> name) < 0)) {
	  /* if p comes before p2 in the chosen ordering, slot it in */
	      nextp = p -> next;
	      p -> next = p2;
	      lastp -> next = p;
	      finished = ON;
	    }
	    lastp = p2;
	  }
	  if (!finished) {         /* we've reached the end of the list; */
	                           /* slot it in at the end */
	    nextp = p -> next;
	    p -> next = p2;
	    p2 -> name = NULL;
	    lastp -> next = p;
	  }
	}
      }
    }
    
    p = nextp;   /* so, on to the next one */

  }        /* end for running through all objects */

  if (floorb < 0.0 && outnumber <= -(int)ROUND(floorb) ||
      floorr < 0 && outnumber <= -floorr) {
                          /* -ve floor & there are at most that many objects */
    strcpy(minreqstr, "0");   /* signal to output() that we are printing all */
    strcpy(minbytestr, "0");
  }

  return(sorthead);

}    /* end gensort */

/*** Again, the domain report is a bit different because the
   structure is stored differently ***/

int domsort(void)
{
  extern int onumber;
  extern struct domain *domainhead[];
  extern int domsortby;
  extern char domminreqstr[];
  extern char domminbytestr[];
  extern double total_bytes;
  extern int total_succ_reqs;
  extern int dom_max_reqs;
  extern double dom_max_bytes;

  int i, j, k;
  int firstdom;  /* the numerical index of the first domain; we return this */
  int domnextj;
  struct domain *domp, *domlastp;
  int floorr;
  double floorb;
  flag finished;

  if (domsortby == BYBYTES)
    floorb = bytefloor(total_bytes, domminbytestr);
  else
    floorr = reqfloor(total_succ_reqs, domminreqstr);

  onumber = 0;
  firstdom = DOMHASHSIZE - 2; /* start with unknown domains at front of list */
  if ((domsortby == BYBYTES && (domainhead[firstdom] -> reqs == 0 ||
				domainhead[firstdom] -> bytes < floorb)) ||
      (domsortby != BYBYTES && domainhead[firstdom] -> reqs < floorr))
    /* then we don't want it; set marker */
    domainhead[firstdom] -> reqs = -1;
  dom_max_reqs = domainhead[firstdom] -> reqs;
  dom_max_bytes = domainhead[firstdom] -> bytes;
  domainhead[firstdom] -> nexti = -1;
  j = DOMHASHSIZE - 1; /* the domain we are on; start with numerical domains */
  while (j >= 0) {              /* run through all the domains */
    domp = domainhead[j];
    domnextj = domp -> nexti;
                            /* the one we're going to look at after this one */
    if (!((domsortby == BYBYTES &&
	   (domp -> reqs == 0 || domp -> bytes < floorb)) ||
	  (domsortby != BYBYTES && domp -> reqs < floorr))) {
                                   /* else we don't want it */
      onumber++;
      dom_max_reqs = MAX(domp -> reqs, dom_max_reqs);
      dom_max_bytes = MAX(domp -> bytes, dom_max_bytes);
      if ((domsortby == BYBYTES &&
	   domp -> bytes > domainhead[firstdom] -> bytes) ||
	  (domsortby == BYREQUESTS &&
	   domp -> reqs > domainhead[firstdom] -> reqs) ||
	  (domsortby == ALPHABETICAL &&
	   strcmp(domp -> id, domainhead[firstdom] -> id) < 0)) {
	/* if it's before the first item currently on the list, slot it in */
	domp -> nexti = firstdom;
	firstdom = j;
      }
      else {        /* otherwise compare with the ones so far */
	finished = OFF;
	domlastp = domainhead[firstdom];
	if (floorb < 0.0)
	  k = (int)ROUND(floorb);
	else if (floorr < 0)
	  k = floorr;
	else
	  k = 1;
	for (i = domainhead[firstdom] -> nexti;
	     i >= 0 && (!finished) && (k++) != -1;
	     i = domainhead[i] -> nexti) {
	  if ((domsortby == BYBYTES &&
	       domp -> bytes > domainhead[i] -> bytes) ||
	      (domsortby == BYREQUESTS &&
	       domp -> reqs > domainhead[i] -> reqs) ||
	      (domsortby == ALPHABETICAL &&
	       strcmp(domp -> id, domainhead[i] -> id) < 0)) {
	    /* if domp comes before domp2 in the chosen ordering, slot it in */
	    domp -> nexti = i;
	    domlastp -> nexti = j;
	    finished = ON;
	  }
	  domlastp = domainhead[i];
	}
	if (!finished) {
	  domp -> nexti = -1;   /* meaning, last item on the list */
	  domlastp -> nexti = j;
	}
      }
    }
    
    j = domnextj;   /* so, on to the next one */
	
  }        /* end while j >= 0 */

  if (floorb < 0.0 && onumber <= -(int)ROUND(floorb) ||
      floorr < 0 && onumber <= -floorr) {
    strcpy(domminreqstr, "0");
    strcpy(domminbytestr, "0");
  }

  return(firstdom);

}   /* end domsort */

/*** Finally, sort subdomains into the domain report ***/

void subdomsort(void)
{
  extern int hosttodomcode();          /* in alias.c */
  extern int hoststrcmp();             /* in utils.c */

  extern int subonumber;
  extern struct domain *domainhead[], *subdomhead[];
  extern int domsortby;
  extern char subdomminbytestr[], subdomminreqstr[];
  extern double total_bytes;
  extern int total_succ_reqs;

  struct domain *subdomp, *subdomnextp, *domp, *domnextp;
  int floorr;
  double floorb;
  int onlist;
  int domcode;

  if (domsortby == BYBYTES)
    floorb = bytefloor(total_bytes, subdomminbytestr);
  else
    floorr = reqfloor(total_succ_reqs, subdomminreqstr);

  onlist = 0;
  subonumber = 0;
  subdomp = subdomhead[0];              /* starting at list 0 */
  for ( ; onlist < SUBDOMHASHSIZE; subdomp = subdomnextp)  {
                                        /* run through all the subdoms */
    if (subdomp -> name == NULL) {      /* then finished this list */
      subdomnextp = subdomhead[++onlist];  /* so look at the next list */
    }
    else if ((domsortby == BYBYTES && subdomp -> bytes >= floorb) ||
	     (domsortby != BYBYTES && subdomp -> reqs >= floorr)) {
      subdomnextp = subdomp -> next;
      subonumber++;
      domcode = hosttodomcode(subdomp -> id);
      if (domcode != DOMHASHSIZE - 2) {
	domp = domainhead[domcode];   /* now run through that domain's
					  subdomains */
	while (domp -> next -> name != NULL &&
	       hoststrcmp(domp -> next -> revid, subdomp -> revid) < 0)
	  domp = domp -> next;  /* run to right place in alphabet */
	domnextp = domp -> next;  /* then slot it in */
	domp -> next = subdomp;
	subdomp -> next = domnextp;
      }
    }
    else
      subdomnextp = subdomp -> next;
  }
}

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