home *** CD-ROM | disk | FTP | other *** search
/ The Datafile PD-CD 3 / PDCD_3.iso / internet / tcpipsrc / h / if / NNTP / c / nntpidhist < prev   
Encoding:
Text File  |  1995-02-21  |  12.0 KB  |  579 lines

  1. /*
  2.  * Title:   nntp/idhist.c
  3.  * Version: 0.01 (02-Feb-1995)
  4.  *
  5.  * Author:  Adam Goodfellow
  6.  * Contact: adam@comptech.demon.co.uk
  7.  *
  8.  * A set of primatives for providing global duplicate article
  9.  * detection for NNTP clients. This version for use in !TCPIPv2
  10.  *
  11.  * Copyright (c) Adam Goodfellow 1995. All Rights Reserved.
  12.  *
  13.  * This software is provided AS-IS.  The author gives no warranty,
  14.  * real or assumed, and takes no responsibility whatsoever for any 
  15.  * use or misuse of this software, or any damage created by its use
  16.  * or misuse.
  17.  *
  18.  * This software may be freely copied and distributed provided that
  19.  * no part of this NOTICE is deleted or edited in any manner.
  20.  */
  21.  
  22. #include <stdio.h>
  23. #include <stdlib.h>
  24. #include <string.h>
  25. #include <ctype.h>
  26. #include <time.h>
  27.  
  28. #include "flex.h"
  29.  
  30. typedef struct idhist_str
  31. {
  32.   char *store;        /* Pointer to ID store memory */
  33.   int next;           /* Next free location in store */
  34.   int start;          /* Start of chain of newly added IDs */
  35.   int end;            /* End of chain of newly added IDs */
  36.   int count;          /* Total number of ID in store */
  37.   int hashsize;       /* Hash table size */
  38. } idhist_str;
  39.  
  40. typedef struct idhist_str *idhistory;
  41.  
  42. time_t time_aton(char *s);
  43. char *time_ntoa(char *buf, time_t t);
  44.  
  45. time_t time_readgmt(void);
  46. void time_timetoordinals(time_t t, struct tm *tp);
  47. time_t time_ordinalstotime(struct tm *tp);
  48.  
  49. idhistory idhist_init(int size);
  50. void idhist_kill(idhistory hist);
  51. int idhist_addid(idhistory hist, char *id, time_t t);
  52. int idhist_checkid(idhistory hist, char *id);
  53. int idhist_save(idhistory hist, char *file, int maxage);
  54. idhistory idhist_load(char *file /*, int maxage */);
  55. char *idhist_trimstr(char *s);
  56. char *idhist_msgid(idhistory hist, int id, char *buf);
  57. int idhist_numids(idhistory hist);
  58.  
  59. static int alignup(int n, int bits);
  60. static int hashof(idhistory hist, char *id);
  61. static int ensure_store(idhistory hist, int n);
  62.  
  63.  
  64. /*
  65.  * The ID store consist of a hash table followed
  66.  * by structures as follows:
  67.  *   int      Next ID in hash chain
  68.  *   int      Next ID in chonological order
  69.  *   time_t   The ID's time stamp
  70.  *   char[]   The actual ID string
  71.  */
  72. #define ID_CHAIN   0      /* Next object on hash chain */
  73. #define ID_NEXT    4      /* Next object as added */
  74. #define ID_TIME    8      /* Time of news feed that this object was fetched */
  75. #define ID_STRING  12     /* Actual ID string without the "<" and ">" */
  76.  
  77. #define ID_HASH(h) (h*4)  /* Another legibility aid */
  78.  
  79. /*
  80.  * Align a number up to a power of 2
  81.  */
  82. static int alignup(int n, int bits)
  83. {
  84.   return n-1+(1<<bits) & (0xffffffff<<bits);
  85. }
  86.  
  87. /*
  88.  * Calculate a hash value of an 
  89.  * article ID string
  90.  */
  91. static int hashof(idhistory hist, char *id)
  92. {
  93.   int h;
  94.  
  95.   for (h=0; *id; id++)
  96.     h = ((h<<6)+*id) % hist->hashsize;
  97.  
  98.   return h;
  99. }
  100.  
  101. /*
  102.  * Initialise the article ID store
  103.  * Hash table size is selected based upon starting 
  104.  * number of IDs given in size
  105.  */
  106. idhistory idhist_init(int size)
  107. {
  108.   int i, *p;
  109.   idhistory hist;
  110.  
  111.   /*
  112.    * Prime numbers for hash table sizes
  113.    */
  114.   int hashsizetab[] = 
  115.       { 0x0101, 0x0209, 0x0407, 0x0805, 0x1003, 0x2011, 0x401b, 0 };
  116.  
  117.   if (hist = (idhistory)malloc(sizeof(idhist_str)), hist==NULL)
  118.     return NULL;
  119.  
  120.   /*
  121.    * From the size (ie number of id in file) work out a suitable
  122.    * hash table size. The size chosen that which is at least 
  123.    * size/2, or the highest in the hash sizes list above.
  124.    */
  125.   for (i=0; hashsizetab[i]!=0 && hashsizetab[i]<size; i++);
  126.   if (i) --i;
  127.   hist->hashsize = hashsizetab[i];
  128.  
  129.   /*
  130.    * Allocate some memory, rounded up to a multiple of 1K
  131.    */
  132.   if (!flex_alloc((flex_ptr)&hist->store, alignup(hist->hashsize*4,10)))
  133.   {
  134.     idhist_kill(hist);
  135.     return NULL;
  136.   }
  137.     
  138.   /*
  139.    * clear the hash table
  140.    */
  141.   for (i = 0, p = (int *)hist->store; i<hist->hashsize; i++)
  142.     p[i] = 0;
  143.  
  144.   /*
  145.    * Set the next store marker and
  146.    * initialise list offsets
  147.    */
  148.   hist->next = hist->hashsize*4;
  149.   hist->start = 0;
  150.   hist->end = 0;
  151.   hist->count = 0;
  152.   
  153.   return hist;
  154. }
  155.  
  156.  
  157. /*
  158.  * Ensure that the total ID store size
  159.  * is at least n.
  160.  */
  161. static int ensure_store(idhistory hist, int n)
  162. {
  163.   if (n>=flex_size((flex_ptr)&hist->store))
  164.     return flex_extend((flex_ptr)&hist->store, alignup(n, 10));
  165.  
  166.   return 1;
  167. }
  168.  
  169.  
  170. /*
  171.  * Add an ID to the store.
  172.  * Returns <0 if ID allready present.
  173.  *         =0 if cat add it
  174.  *         >0 if added the id
  175.  * t is time of news feed starting
  176.  */
  177. int idhist_addid(idhistory hist, char *id, time_t t)
  178. {
  179.   int p, h, n;
  180.  
  181.   if (hist==NULL)
  182.     return 0;
  183.  
  184.   h = hashof(hist, id);
  185.  
  186.   /*
  187.    * Scan an ID chain looking for an ID
  188.    * If a duplicate exists, it is more likely to be near the head
  189.    */
  190.   for (p = *(int *)(hist->store+ID_HASH(h)); p!=0; p = *(int *)(hist->store+p+ID_CHAIN))
  191.   {
  192.     if (!strcmp(id, hist->store+p+ID_STRING))
  193.     {
  194.       /*
  195.        * If time stamp is zero, then update this entry
  196.        * with the given time stamp
  197.        */
  198.       if (*(int *)(hist->store+p+ID_TIME)==0)
  199.       {
  200.         *(int *)(hist->store+p+ID_TIME) = t;
  201.         if (t!=0)
  202.           hist->count += 1;
  203.       }
  204.       return -p;
  205.     }
  206.   }
  207.  
  208.   /*
  209.    * Word aligned length of ID
  210.    */
  211.   n = alignup(strlen(id)+1, 2);
  212.  
  213.   /*
  214.    * Ensure space for ID we are adding
  215.    */
  216.   if (!ensure_store(hist, hist->next+n+ID_STRING))
  217.     return 0;
  218.   
  219.   /*
  220.    * Copy in the ID with a empty chain link
  221.    */
  222.   *(int *)(hist->store+hist->next) = 0;
  223.   strcpy(hist->store+hist->next+ID_STRING,id);
  224.   
  225.   /*
  226.    * Now link into the hash chain
  227.    * Optimisation: usually, a duplicate will be a fairly recently
  228.    * Stored ID, so allways link at head of a chain rather than end
  229.    * This also saves scanning for end of chain
  230.    */
  231.   p = *(int *)(hist->store+ID_HASH(h));
  232.   *(int *)(hist->store+ID_HASH(h)) = hist->next;
  233.  
  234.   if (p)
  235.     *(int *)(hist->store+hist->next+ID_CHAIN) = p;
  236.  
  237.   /*
  238.    * Now link onto global chain and store the time
  239.    * to keep in order when they are saved
  240.    */
  241.   *(int *)(hist->store+hist->next+ID_NEXT) = 0;
  242.   *(int *)(hist->store+hist->next+ID_TIME) = t;
  243.  
  244.   if (!hist->start)
  245.   {
  246.     hist->start = hist->next;
  247.     hist->end = hist->start;
  248.   }
  249.   else
  250.   {
  251.     *(int *)(hist->store+hist->end+ID_NEXT) = hist->next;
  252.     hist->end = hist->next;
  253.   }
  254.  
  255.   /*
  256.    * Update end marker
  257.    * And return original end marker
  258.    */
  259.   p = hist->next;
  260.   hist->next += (n+ID_STRING);
  261.  
  262.   /*
  263.    * Only count IDs with non zero timestamps
  264.    */
  265.   hist->count += (t!=0);
  266.  
  267.   return p;
  268. }
  269.  
  270.  
  271. /*
  272.  * Check if allready have the given ID
  273.  */
  274. int idhist_checkid(idhistory hist, char *id)
  275. {
  276.   int h, p;
  277.  
  278.   if (hist==NULL)
  279.     return 0;
  280.  
  281.   h = hashof(hist, id);
  282.  
  283.   /*
  284.    * Scan an ID chain looking for an ID
  285.    * If a duplicate exists, it is more likely to be near the head
  286.    */
  287.   for (p = *(int *)(hist->store+ID_HASH(h)); p!=0; p = *(int *)(hist->store+p+ID_CHAIN))
  288.   {
  289.     if (!strcmp(id, hist->store+p+ID_STRING))
  290.       return 1;
  291.   }
  292.  
  293.   return 0;
  294. }
  295.  
  296. /*
  297.  * Return the ID string associate with an ID handle
  298.  */
  299. char *idhist_msgid(idhistory hist, int id, char *buf)
  300. {
  301.   int n;
  302.  
  303.   if (!hist || id==0)
  304.     return 0;
  305.   
  306.   if (!buf)
  307.   {
  308.     n = strlen(hist->store+id+ID_STRING);
  309.     if (buf = (char *)malloc(n+1), buf==NULL)
  310.       return NULL;
  311.   }
  312.   
  313.   strcpy(buf, hist->store+id+ID_STRING);
  314.   return buf;
  315. }
  316.  
  317.  
  318. /*
  319.  * Extract IDs from ID store in order they were added
  320.  * and write to the history file.
  321.  */
  322. int idhist_save(idhistory hist, char *file, int maxage)
  323. {
  324.   FILE *fp;
  325.   int p;
  326.   time_t t, last_t = 0;
  327.   time_t oldest;
  328.   int count;
  329.   char buf[256];
  330.   struct tm tm;
  331.   
  332.   if (hist==NULL)
  333.     return 0;
  334.  
  335.   if (fp = fopen(file, "w"), fp==NULL)
  336.     return 0;
  337.     
  338.   /*
  339.    * IDs older than this are expired
  340.    */
  341.   oldest = time_readgmt()-(maxage*60*60);
  342.  
  343.   /*
  344.    * Count the number of IDs that will be written
  345.    */
  346.   count = 0;
  347.   for (p = hist->start; p!=0; p = *(int *)(hist->store+p+ID_NEXT))
  348.   {
  349.     if (*(int *)(hist->store+p+ID_TIME)>=oldest)
  350.     ++count;
  351.   }
  352.  
  353.   /*
  354.    * Write out the number of IDs for hash size selection when re-loading
  355.    */
  356.   fprintf(fp, "# size %d\n", count);
  357.  
  358.   /*
  359.    * Write out IDs and fetch times
  360.    */
  361.   for (p = hist->start; p!=0; p = *(int *)(hist->store+p+ID_NEXT))
  362.   {
  363.     /*
  364.      * If time stamp of this ID not same as previous one
  365.      * write out a new time stamp, unless it is zero
  366.      */
  367.     if (t = *(int *)(hist->store+p+ID_TIME), t>=oldest)
  368.     {
  369.       if (t!=last_t)
  370.       {
  371.         time_timetoordinals(t, &tm);
  372.         fprintf(fp, "# time %.2d%.2d%.2d %.2d%.2d%.2d\n",
  373.                      tm.tm_year, tm.tm_mon+1, tm.tm_mday,
  374.                      tm.tm_hour, tm.tm_min, tm.tm_sec);
  375.         last_t = t;
  376.       }
  377.       /*
  378.        * Dont write out IDs with a zero time stamp
  379.        * or timestamp older than maxage
  380.        */
  381.       if (last_t>=oldest)
  382.       {
  383.         /* Copy string out due to danger of flex block moving
  384.          * when fprintf() is called.
  385.          */
  386.         idhist_msgid(hist, p, buf);
  387.         fprintf(fp, "%s\n", buf);
  388.       }
  389.     }
  390.   }
  391.  
  392.   fclose(fp);
  393.  
  394.   return 1;
  395. }
  396.  
  397. /*
  398.  * Dispose of the ID store
  399.  */
  400. void idhist_kill(idhistory hist)
  401. {
  402.   if (hist)
  403.   {
  404.     if (hist->store!=NULL)
  405.       flex_free((flex_ptr)&hist->store);
  406.     free(hist->store);
  407.   }
  408. }
  409.  
  410.  
  411. /*
  412.  * Load ID store with ids from a file
  413.  * ignoring 'expired' IDs. ie ID older than maxage hours
  414.  */
  415. idhistory idhist_load(char *file /*, int maxage */)
  416. {
  417.   char *p;
  418.   FILE *fp;
  419.   time_t t = 0;
  420. /*
  421.   time_t oldest;
  422. */
  423.   char buf[256];
  424.  
  425.   idhistory hist;
  426.  
  427.   /*
  428.    * IDs older than this are expired
  429.    */
  430. /*
  431.   oldest = time_readgmt()-(maxage*60*60);
  432. */
  433.   if (fp = fopen(file, "r"), fp==NULL)
  434.     return 0;
  435.  
  436.   /*
  437.    * If a new style history, the first line will
  438.    * contain the number if IDs in the history file
  439.    *
  440.    * Use this value to allow init() to work out the has table size.
  441.    * For old history files, this line wont present, so init with a
  442.    * default size.
  443.    *
  444.    * As this size represent the pre-expirey size, then after expirely,
  445.    * the hash table will still usually be adequate to efficiently
  446.    * accomodate new IDs.
  447.    */
  448.   if (fgets(buf, 255, fp)!=NULL && !strncmp(buf+2, "# size ", 7))
  449.   {
  450.     /*
  451.      * New style history
  452.      */
  453.     hist = idhist_init(atoi(buf+7));
  454.   }
  455.   else
  456.   {
  457.     /*
  458.      * Old style history
  459.      */
  460.     rewind(fp);
  461.     hist = idhist_init(0);
  462.   }
  463.  
  464.   if (hist==NULL)
  465.     return NULL;
  466.  
  467.   while (fgets(buf, 255, fp)!=NULL)
  468.   {
  469.     if (*buf=='#')
  470.     {
  471.       if (!strncmp(buf+2, "time ", 5))
  472.         t = time_aton(buf+7);
  473.       /*
  474.        * Old style history file time stamp in the form "# yymmdd hhmmss"
  475.        */
  476.       else
  477.         t = time_aton(buf+2);
  478.     }
  479.     else
  480.     {
  481.       /*
  482.        * Expirey process consists of ignoring IDs that are
  483.        * stamped with a time/date prior to 'oldest'.
  484.        */
  485. /*
  486.       if (t>=oldest)
  487.       {
  488. */
  489.         p = idhist_trimstr(buf);
  490.         if (!idhist_checkid(hist, p))
  491.         {
  492.           if (!idhist_addid(hist, p, t))
  493.           {
  494.             fclose(fp);
  495.             idhist_kill(hist);
  496.             return NULL;
  497.           }
  498.         }
  499. /*
  500.       }
  501. */
  502.     }
  503.   }
  504.  
  505.   fclose(fp);
  506.   
  507.   return hist;
  508. }
  509.  
  510. /*
  511.  * Skip junk at start of ID
  512.  * Strip junk off end of ID
  513.  * Return pointer to start of ID
  514.  * Done in place, so original string is 'corrupted'
  515.  */
  516. char *idhist_trimstr(char *s)
  517. {
  518.   char *p;
  519.   
  520.   if (*s=='<')
  521.     ++s;
  522.     
  523.   for (p = s; *p!='>' && *p>' '; p++);
  524.   *p = '\0';
  525.   
  526.   return s;
  527. }
  528.  
  529. int idhist_numids(idhistory hist)
  530. {
  531.   if (!hist)
  532.     return -1;
  533.     
  534.   return hist->count;
  535. }
  536.  
  537. /*
  538.  * Convert string with time in form yymmdd hhmmss into
  539.  * a ANSI C time_t value
  540.  */
  541. time_t time_aton(char *s)
  542. {
  543.   struct tm tm;
  544.   
  545.   while (*s && !isdigit(*s))
  546.     ++s;
  547.  
  548.   tm.tm_year = (s[0]-'0')*10 + (s[1]-'0');
  549.   tm.tm_mon  = ((s[2]-'0')*10 + (s[3]-'0'))-1;
  550.   tm.tm_mday = (s[4]-'0')*10 + (s[5]-'0');
  551.   tm.tm_hour = (s[7]-'0')*10 + (s[8]-'0');
  552.   tm.tm_min  = (s[9]-'0')*10 + (s[10]-'0');
  553.   tm.tm_sec  = (s[11]-'0')*10 + (s[12]-'0');
  554.  
  555.   tm.tm_isdst = 0;
  556.   tm.tm_wday  = 0;
  557.   tm.tm_yday  = 0;
  558.  
  559.   return time_ordinalstotime(&tm);
  560. }
  561.  
  562. /*
  563.  * Convert time into string yymmdd hhmmss
  564.  * t will usually be in GMT
  565.  * buf should be at least 16 chars
  566.  */
  567. char *time_ntoa(char *buf, time_t t)
  568. {
  569.   struct tm tm;
  570.  
  571.   time_timetoordinals(t, &tm);
  572.  
  573.   sprintf(buf, "%.2d%.2d%.2d %.2d%.2d%.2d",
  574.           tm.tm_year%100, tm.tm_mon+1, tm.tm_mday,
  575.           tm.tm_hour, tm.tm_min, tm.tm_sec);
  576.  
  577.   return buf;
  578. }
  579.