Annotation of libwww/Library/src/HTCache.c, revision 2.4

2.1       frystyk     1: /*                                                                 HTCache.c
                      2: **     CACHE WRITER
                      3: **
                      4: **     (c) COPYRIGHT MIT 1995.
                      5: **     Please first read the full copyright statement in the file COPYRIGH.
                      6: **
                      7: **     This modules manages the cache
                      8: **
                      9: **      History:
                     10: **         HFN: spawned from HTFwrite
                     11: **         HWL: converted the caching scheme to be hierachical by taking
                     12: **              AL code from Deamon
                     13: **
                     14: */
                     15: 
                     16: /* Library include files */
                     17: #include "tcp.h"
                     18: #include "HTUtils.h"
                     19: #include "HTString.h"
                     20: #include "HTFormat.h"
                     21: #include "HTFWrite.h"
                     22: #include "HTBind.h"
                     23: #include "HTList.h"
                     24: #include "HTParse.h"
                     25: #include "HTCache.h"                                    /* Implemented here */
                     26: 
                     27: /*
                     28: ** The cache limit is the number of files which are kept. Yes, I know,
                     29: ** the amount of disk space would be more relevant. So this may change.
                     30: ** Currently it is preset to 100 but may be changed by the application by
                     31: ** writing into this variable.
                     32: */
                     33: #define CACHE_LIMIT    5                                 /* Number of files */
                     34: 
                     35: #define CACHE_INFO     ".cache_info"
                     36: #define INDEX_FILE     ".cache_dirindex"
                     37: #define WELCOME_FILE   ".cache_welcome"
                     38: #define TMP_SUFFIX     ".cache_tmp"
                     39: #define LOCK_SUFFIX    ".cache_lock"
                     40: 
                     41: typedef struct _HTCacheItem {
                     42:     HTFormat           format;         /* May have many formats per anchor */
                     43:     char *             filename;
                     44:     time_t             start_time;
                     45:     time_t             load_delay;
                     46:     int                        reference_count;
                     47: } HTCacheItem;
                     48: 
                     49: struct _HTStream {
                     50:     CONST HTStreamClass *      isa;
                     51:     FILE *                     fp;
                     52:     HTCacheItem *              cache;
                     53:     HTRequest *                        request;
                     54: };
                     55: 
                     56: PRIVATE BOOL           HTCacheEnable = NO;
                     57: PRIVATE char *         HTCacheRoot = NULL;         /* Destination for cache */
                     58: PRIVATE HTList *       HTCache = NULL;           /* List of cached elements */
                     59: PRIVATE int            HTCacheLimit = CACHE_LIMIT;
                     60: 
2.2       frystyk    61: 
2.1       frystyk    62: /* ------------------------------------------------------------------------- */
2.2       frystyk    63: /*                           GARBAGE COLLECTOR                              */
2.1       frystyk    64: /* ------------------------------------------------------------------------- */
                     65: 
                     66: /*
                     67: **  Removes cache item from disk and corresponding object from list in memory
                     68: */
                     69: PRIVATE void HTCache_remove ARGS1(HTCacheItem *, item)
                     70: {
                     71:     if (HTCache && item) {
                     72:        if (CACHE_TRACE)
                     73:            fprintf(TDEST, "Cache....... Removing %s\n", item->filename);
                     74:        HTList_removeObject(HTCache, item);
                     75:        REMOVE(item->filename);
                     76:        
                     77:        /* HWL 22/9/94: Clean up hierachical file structure */
                     78:        {
                     79:            char * p;
                     80:            while ((p = strrchr(item->filename,'/')) && (p != NULL)){
                     81:                item->filename[p-item->filename] = 0;
                     82:                if (strcmp(item->filename, HTCacheRoot) != 0) {
                     83:                    if (CACHE_TRACE) 
                     84:                        fprintf(TDEST, "rmdir....... %s\n", item->filename);
                     85:                    RMDIR(item->filename); /* fails if directory isn't empty */
                     86:                }
                     87:            }
                     88:        }
                     89:        free(item->filename);
                     90:        free(item);
                     91:     }
                     92: }
                     93: 
                     94: 
                     95: /*
                     96: **  Remove a file from the cache to prevent too many files from being cached
                     97: */
                     98: PRIVATE void limit_cache ARGS1(HTList * , list)
                     99: {
                    100:     HTList * cur = list;
                    101:     HTCacheItem * item;
                    102:     time_t best_delay = 0;   /* time_t in principle can be any arith type */
                    103:     HTCacheItem* best_item = NULL;
                    104: 
                    105:     if (HTList_count(list) < HTCacheLimit) return;   /* Limit not reached */
                    106: 
                    107:     while (NULL != (item = (HTCacheItem*)HTList_nextObject(cur))) {
                    108:         if (best_delay == 0  ||  item->load_delay < best_delay) {
                    109:             best_delay = item->load_delay;
                    110:             best_item = item;
                    111:         }
                    112:     }
                    113:     if (best_item) HTCache_remove(best_item);
                    114: }
                    115: 
                    116: /*
                    117: **     Check that the name we're about to generate doesn't
                    118: **     clash with anything used by the caching system.
                    119: */
                    120: PRIVATE BOOL reserved_name ARGS1(char *, url)
                    121: {
                    122:     char * name = strrchr(url, '/');
                    123:     char * suff = NULL;
                    124: 
                    125:     if (name) name++;
                    126:     else name = url;
                    127: 
                    128:     if (!strcmp(name, CACHE_INFO) ||
                    129:        !strcmp(name, INDEX_FILE) ||
                    130:        !strcmp(name, WELCOME_FILE))
                    131:        return YES;
                    132: 
                    133:     suff = strrchr(name, TMP_SUFFIX[0]);
                    134:     if (suff && !strcmp(suff, TMP_SUFFIX))
                    135:        return YES;
                    136: 
                    137:     suff = strrchr(name, LOCK_SUFFIX[0]);
                    138:     if (suff && !strcmp(suff, LOCK_SUFFIX))
                    139:        return YES;
                    140: 
                    141:     return NO;
                    142: }
                    143: 
                    144: /*
2.2       frystyk   145: **  Removes all cache entries in memory
                    146: */
                    147: PUBLIC void HTCache_clearMem NOARGS
                    148: {
                    149:     HTList *cur=HTCache;
                    150:     HTCacheItem *pres;
                    151:     if (cur) {
                    152:        while ((pres = (HTCacheItem *) HTList_nextObject(cur))) {
                    153:            FREE(pres->filename);
                    154:            free(pres);
                    155:        }
                    156:        HTList_delete(HTCache);
                    157:        HTCache = NULL;
                    158:     }
                    159: }
                    160: 
                    161: /*
                    162: **  Removes all cache entries in memory and on disk
                    163: */
                    164: PUBLIC void HTCache_deleteAll NOARGS
                    165: {
                    166:     HTList *cur=HTCache;
                    167:     HTCacheItem * pres;
                    168:     if (cur) {
                    169:        while ((pres = (HTCacheItem *) HTList_lastObject(cur)))
                    170:            HTCache_remove(pres);
                    171:        HTList_delete(HTCache);
                    172:        HTCache = NULL;
                    173:     }
                    174: }
                    175: 
                    176: /* ------------------------------------------------------------------------- */
                    177: /*                              NAMING SCHEME                               */
                    178: /* ------------------------------------------------------------------------- */
                    179: 
                    180: /*
2.1       frystyk   181: **     Map url to cache file name.
                    182: */
                    183: PRIVATE char * cache_file_name ARGS1(char *, url)
                    184: {
                    185:     char * access = NULL;
                    186:     char * host = NULL;
                    187:     char * path = NULL;
                    188:     char * cfn = NULL;
                    189:     BOOL welcome = NO;
                    190:     BOOL res = NO;
                    191: 
                    192:     if (!url ||  strchr(url, '?')  ||  (res = reserved_name(url))  ||
                    193:        !(access = HTParse(url, "", PARSE_ACCESS)) ||
                    194:        (0 != strcmp(access, "http") &&
                    195:         0 != strcmp(access, "ftp")  &&
                    196:         0 != strcmp(access, "gopher"))) {
                    197: 
                    198:        if (access) free(access);
                    199: 
                    200:        if (res && CACHE_TRACE)
                    201:            fprintf(TDEST,
                    202:                    "Cache....... Clash with reserved name (\"%s\")\n",url);
                    203: 
                    204:        return NULL;
                    205:     }
                    206: 
                    207:     host = HTParse(url, "", PARSE_HOST);
                    208:     path = HTParse(url, "", PARSE_PATH | PARSE_PUNCTUATION);
                    209:     if (path && path[strlen(path)-1] == '/')
                    210:        welcome = YES;
                    211: 
                    212:     cfn = (char*)malloc(strlen(HTCacheRoot) +
                    213:                        strlen(access) +
                    214:                        (host ? strlen(host) : 0) +
                    215:                        (path ? strlen(path) : 0) +
                    216:                        (welcome ? strlen(WELCOME_FILE) : 0) + 3);
                    217:     if (!cfn) outofmem(__FILE__, "cache_file_name");
                    218: 
                    219:     /* Removed extra slash - HF May2,95 */
                    220:     sprintf(cfn, "%s%s/%s%s%s", HTCacheRoot, access, host, path,
                    221:            (welcome ? WELCOME_FILE : ""));
                    222: 
                    223:     FREE(access); FREE(host); FREE(path);
                    224: 
                    225:     /*
                    226:     ** This checks that the last component is not too long.
                    227:     ** It could check all the components, but the last one
                    228:     ** is most important because it could later blow up the
                    229:     ** whole gc when reading cache info files.
                    230:     ** Operating system handles other cases.
                    231:     ** 64 = 42 + 22  and  22 = 42 - 20  :-)
                    232:     ** In other words I just picked some number, it doesn't
                    233:     ** really matter that much.
                    234:     */
                    235:     {
                    236:        char * last = strrchr(cfn, '/');
                    237:        if (!last) last = cfn;
                    238:        if ((int)strlen(last) > 64) {
                    239:            if (CACHE_TRACE)
                    240:                fprintf(TDEST, "Too long.... cache file name \"%s\"\n", cfn);
                    241:            free(cfn);
                    242:            cfn = NULL;
                    243:        }
                    244:     }
                    245:     return cfn;
                    246: }
                    247: 
                    248: 
                    249: /*
                    250: **     Create directory path for cache file
                    251: **
                    252: ** On exit:
                    253: **     return YES
                    254: **             if directories created -- after that caller
                    255: **             can rely on fopen(cfn,"w") succeeding.
                    256: **
                    257: */
                    258: PRIVATE BOOL create_cache_place ARGS1(char *, cfn)
                    259: {
                    260:     struct stat stat_info;
                    261:     char * cur = NULL;
                    262:     BOOL create = NO;
                    263: 
                    264:     if (!cfn  ||  (int)strlen(cfn) <= (int)strlen(HTCacheRoot) + 1)
                    265:        return NO;
                    266: 
                    267:     cur = cfn + strlen(HTCacheRoot) + 1;
                    268: 
                    269:     while ((cur = strchr(cur, '/'))) {
                    270:        *cur = 0;
2.3       frystyk   271:        if (create || STAT(cfn, &stat_info) == -1) {
2.1       frystyk   272:            create = YES;       /* To avoid doing stat()s in vain */
                    273:            if (CACHE_TRACE)
                    274:                fprintf(TDEST,"Cache....... creating cache dir \"%s\"\n",cfn);
                    275:            if (MKDIR(cfn, 0777) < 0) {
                    276:                if (CACHE_TRACE)
                    277:                    fprintf(TDEST,"Cache....... can't create dir `%s\'\n",cfn);
                    278:                return NO;
                    279:            }
                    280:        } else {
                    281:            if (S_ISREG(stat_info.st_mode)) {
                    282:                int len = strlen(cfn);
                    283:                char * tmp1 = (char*)malloc(len + strlen(TMP_SUFFIX) + 1);
                    284:                char * tmp2 = (char*)malloc(len + strlen(INDEX_FILE) + 2);
                    285:                /* time_t t1,t2,t3,t4,t5; */
                    286: 
                    287: 
                    288:                sprintf(tmp1, "%s%s", cfn, TMP_SUFFIX);
                    289:                sprintf(tmp2, "%s/%s", cfn, INDEX_FILE);
                    290: 
                    291:                if (CACHE_TRACE) {
                    292:                    fprintf(TDEST,"Cache....... moving \"%s\" to \"%s\"\n",
                    293:                            cfn,tmp1);
                    294:                    fprintf(TDEST,"and......... creating dir \"%s\"\n",
                    295:                            cfn);
                    296:                    fprintf(TDEST,"and......... moving \"%s\" to \"%s\"\n",
                    297:                            tmp1,tmp2);
                    298:                }
                    299:                rename(cfn,tmp1);
                    300:                (void) MKDIR(cfn, 0777);
                    301:                rename(tmp1,tmp2);
                    302:                free(tmp1);
                    303:                free(tmp2);
                    304:            }
                    305:            else {
                    306:                if (CACHE_TRACE)
                    307:                    fprintf(TDEST,"Cache....... dir \"%s\" already exists\n",
                    308:                            cfn);
                    309:            }
                    310:        }
                    311:        *cur = '/';
                    312:        cur++;
                    313:     }
                    314:     return YES;
                    315: }
                    316: 
                    317: 
                    318: /*     Create a cache path
                    319: **     -------------------
                    320: **     Find a full path name for the cache file and create the path if it
                    321: **     does not already exist. Returns name or NULL
                    322: **     HWL 22/9/94
                    323: **     HWL added support for hierachical structure
                    324: */
                    325: PRIVATE char *HTCache_getName ARGS1(char *, url)
                    326: {
                    327:     char *filename = cache_file_name(url);
                    328:     if (!filename)
                    329:        return NULL;
                    330:     if (create_cache_place(filename))
                    331:        return(filename);
                    332:     return NULL;
                    333: }
                    334: 
                    335: /*
                    336: **  Make a WWW name from a cache name and returns it if OK, else NULL.
                    337: **  The string returned must be freed by the caller.
                    338: **  We keep this function private as we might change the naming scheme for
                    339: **  cache files. Right now it follows the file hierarchi.
                    340: */
                    341: PRIVATE char *HTCache_wwwName ARGS1 (char *, name)
                    342: {
                    343:     char * result = NULL;
                    344:     if (name && *name) {
                    345:        StrAllocCopy(result, "file:");       /* We get an absolute file name */
                    346: #ifdef VMS 
                    347:        /* convert directory name to Unix-style syntax */
2.4     ! frystyk   348:        {
        !           349:            char * disk = strchr (name, ':');
        !           350:            char * dir = strchr (name, '[');
        !           351:            if (disk) {
        !           352:                *disk = '\0';
        !           353:                StrAllocCat(result, "/"); /* needs delimiter */
        !           354:                StrAllocCat(result, name);
        !           355:            }
        !           356:            if (dir) {
        !           357:                char *p;
        !           358:                *dir = '/';     /* Convert leading '[' */
        !           359:                for (p = dir ; *p != ']'; ++p)
        !           360:                    if (*p == '.') *p = '/';
        !           361:                *p = '\0';      /* Cut on final ']' */
        !           362:                StrAllocCat(result, dir);
        !           363:            }
2.1       frystyk   364:        }
                    365: #else  /* not VMS */
                    366: #ifdef WIN32
2.4     ! frystyk   367:        {
        !           368:            char * p = name;                                      /* a colon */
        !           369:            StrAllocCat(result, "/");
        !           370:            while( *p != 0 ) { 
        !           371:                if (*p == '\\')                  /* change to one true slash */
        !           372:                    *p = '/' ;
        !           373:                p++;
        !           374:            }
        !           375:            StrAllocCat(result, name);
2.1       frystyk   376:        }
                    377: #else /* not WIN32 */
                    378:        StrAllocCat (result, name);
                    379: #endif /* not WIN32 */
                    380: #endif /* not VMS */
                    381:     }
                    382:     return result;
                    383: }
                    384: 
2.2       frystyk   385: /* ------------------------------------------------------------------------- */
                    386: /*                           CACHE PARAMETERS                               */
                    387: /* ------------------------------------------------------------------------- */
2.1       frystyk   388: 
                    389: /*     Enable Cache
                    390: **     ------------
                    391: **     If `cache_root' is NULL then reuse old value or use HT_CACHE_ROOT.
                    392: **     An empty string will make '/' as cache root
                    393: */
                    394: PUBLIC BOOL HTCache_enable ARGS1(CONST char *, cache_root)
                    395: {
                    396:     if (cache_root)
                    397:        HTCache_setRoot(cache_root);
                    398:     HTCacheEnable = YES;
                    399:     return YES;
                    400: }
                    401: 
                    402: 
                    403: /*     Disable Cache
                    404: **     ------------
                    405: **     Turns off the cache. Note that the cache can be disabled and enabled
                    406: **     at any time. The cache root is kept and can be reused during the
                    407: **     execution.
                    408: */
                    409: PUBLIC BOOL HTCache_disable NOARGS
                    410: {
                    411:     HTCacheEnable = NO;
                    412:     return YES;
                    413: }
                    414: 
                    415: /*     Is Cache Enabled
                    416: **     ----------------
                    417: **     Returns YES or NO. Also makes sure that we have a root value
                    418: **     (even though it might be invalid)
                    419: */
                    420: PUBLIC BOOL HTCache_isEnabled NOARGS
                    421: {
                    422:     if (!HTSecure && HTCacheEnable) {
                    423:        if (!HTCacheRoot)
                    424:            HTCache_setRoot(NULL);
                    425:        return YES;
                    426:     }
                    427:     return NO;
                    428: }
                    429: 
                    430: 
                    431: /*     Set Cache Root
                    432: **     --------------
                    433: **     If `cache_root' is NULL then the current value (might be a define)
                    434: **     Should we check if the cache_root is actually OK? I think not!
                    435: */
                    436: PUBLIC BOOL HTCache_setRoot ARGS1(CONST char *, cache_root)
                    437: {
                    438:     StrAllocCopy(HTCacheRoot, cache_root ? cache_root : HT_CACHE_ROOT);
                    439:     if (*(HTCacheRoot+strlen(HTCacheRoot)-1) != '/')
                    440:        StrAllocCat(HTCacheRoot, "/");
                    441:     if (CACHE_TRACE)
                    442:        fprintf(TDEST, "Cache Root.. Root set to `%s\'\n", HTCacheRoot);
                    443:     return YES;
                    444: }
                    445: 
                    446: 
                    447: /*     Get Cache Root
                    448: **     --------------
                    449: */
                    450: PUBLIC CONST char * HTCache_getRoot NOARGS
                    451: {
                    452:     return HTCacheRoot;
                    453: }
                    454: 
                    455: /*     Free Cache Root
                    456: **     --------------
                    457: **     For clean up memory
                    458: */
                    459: PUBLIC void HTCache_freeRoot NOARGS
                    460: {
                    461:     FREE(HTCacheRoot);
                    462: }
                    463: 
                    464: /* ------------------------------------------------------------------------- */
2.2       frystyk   465: /*                              CACHE MANAGER                               */
                    466: /* ------------------------------------------------------------------------- */
                    467: 
                    468: /*
                    469: **  Verifies if a cache object exists for this URL and if so returns a URL
                    470: **  for the cached object. It does not verify whether the object is valid or
                    471: **  not, for example it might have expired.
                    472: **
                    473: **  Returns: file name If OK (must be freed by caller)
                    474: **          NULL       If no cache object found
                    475: */
                    476: PUBLIC char * HTCache_getReference ARGS1(char *, url)
                    477: {
                    478:     if (url && HTCache_isEnabled()) {
                    479:        char *fnam = cache_file_name(url);
                    480:        if (fnam) {
                    481:            FILE *fp = fopen(fnam, "r");
                    482:            if (fp) {
                    483:                char *url = HTCache_wwwName(fnam);
                    484:                fclose(fp);
                    485:                if (CACHE_TRACE)
                    486:                    fprintf(TDEST, "Cache....... Object found `%s\'\n", url);
                    487:                free(fnam);
                    488:                return url;
                    489:            } else
                    490:                free(fnam);
                    491:        }
                    492:     }
                    493:     return NULL;
                    494: }
                    495: 
                    496: /*
                    497: **  This function checks whether a document has expired or not.
                    498: **  The check is based on the metainformation passed in the anchor object
                    499: **  The function returns YES or NO.
                    500: */
                    501: PUBLIC BOOL HTCache_isValid ARGS1(HTParentAnchor *, anchor)
                    502: {
                    503:     time_t cur = time(NULL);
                    504:     time_t expires = HTAnchor_expires(anchor);
                    505:     return (expires>0 && cur>0 && expires<cur) ? NO : YES;
                    506: }
                    507: 
                    508: /* ------------------------------------------------------------------------- */
2.1       frystyk   509: /*                          CACHE WRITER STREAM                             */
                    510: /* ------------------------------------------------------------------------- */
                    511: 
                    512: PRIVATE int HTCache_flush ARGS1(HTStream *, me)
                    513: {
                    514:     return (fflush(me->fp) == EOF) ? HT_ERROR : HT_OK;
                    515: }
                    516: 
                    517: PRIVATE int HTCache_putBlock ARGS3(HTStream *, me, CONST char*, s, int, l)
                    518: {
                    519:     int status = (fwrite(s, 1, l, me->fp) != l) ? HT_ERROR : HT_OK;
                    520:     if (l > 1 && status == HT_OK)
                    521:        (void) HTCache_flush(me);
                    522:     return status;
                    523: }
                    524: 
                    525: PRIVATE int HTCache_putChar ARGS2(HTStream *, me, char, c)
                    526: {
                    527:     return HTCache_putBlock(me, &c, 1);
                    528: }
                    529: 
                    530: PRIVATE int HTCache_putString ARGS2(HTStream *, me, CONST char*, s)
                    531: {
                    532:     return HTCache_putBlock(me, s, (int) strlen(s));
                    533: }
                    534: 
                    535: PRIVATE int HTCache_free ARGS1(HTStream *, me)
                    536: {
                    537:     me->cache->load_delay = time(NULL)-me->cache->start_time;
                    538:     fclose(me->fp);
                    539:     free(me);
                    540:     return HT_OK;
                    541: }
                    542: 
                    543: PRIVATE int HTCache_abort ARGS2(HTStream *, me, HTError, e)
                    544: {
                    545:     if (CACHE_TRACE)
                    546:        fprintf(TDEST, "Cache....... ABORTING\n");
                    547:     if (me->fp)
                    548:        fclose(me->fp);
                    549:     if (me->cache)
                    550:        HTCache_remove(me->cache);
                    551:     free(me);
                    552:     return HT_ERROR;
                    553: }
                    554: 
                    555: PRIVATE CONST HTStreamClass HTCacheClass =
                    556: {              
                    557:     "Cache",
                    558:     HTCache_flush,
                    559:     HTCache_free,
                    560:     HTCache_abort,
                    561:     HTCache_putChar,
                    562:     HTCache_putString,
                    563:     HTCache_putBlock
                    564: };
                    565: 
                    566: 
                    567: /*     Cache Writer
                    568: **     ------------------
                    569: **
                    570: */
                    571: PUBLIC HTStream* HTCacheWriter ARGS5(
                    572:        HTRequest *,            request,
                    573:        void *,                 param,
                    574:        HTFormat,               input_format,
                    575:        HTFormat,               output_format,
                    576:        HTStream *,             output_stream)
                    577: 
                    578: {
                    579:     char *fnam;
                    580:     HTStream *me;
                    581:     if (HTSecure) {
                    582:        if (CACHE_TRACE)
                    583:            fprintf(TDEST, "Cache....... No caching in secure mode.\n");
                    584:        return HTBlackHole();
                    585:     }
                    586: 
                    587:     /* Get a file name and open file */
                    588:     if ((fnam = HTCache_getName(HTAnchor_physical(request->anchor))) == NULL)
                    589:        return HTBlackHole();
                    590: 
                    591:     /* Set up the stream */
                    592:     if ((me = (HTStream *) calloc(sizeof(*me), 1)) == NULL)
                    593:        outofmem(__FILE__, "Cache");
                    594:     me->isa = &HTCacheClass;
                    595:     me->request = request;
                    596:     if ((me->fp = fopen(fnam, "w")) == NULL) {
                    597:        if (CACHE_TRACE)
                    598:            fprintf(TDEST, "Cache....... Can't open %s for writing\n", fnam);
                    599:        free(fnam);
                    600:        return HTBlackHole();
                    601:     } else
                    602:        if (CACHE_TRACE)
                    603:            fprintf(TDEST, "Cache....... Creating file %s\n", fnam);
                    604: 
                    605:     /* Set up a cache record */
                    606:     if ((me->cache = (HTCacheItem *) calloc(sizeof(*me->cache), 1)) == NULL)
                    607:        outofmem(__FILE__, "Cache");
                    608:     me->cache->filename = fnam;
                    609:     me->cache->start_time = time(NULL);
                    610:     me->cache->format = input_format;
                    611: 
                    612:     /* Keep a global list of all cache items */
                    613:     if (!HTCache) HTCache = HTList_new();
                    614:     HTList_addObject(HTCache, me->cache);
                    615:     limit_cache(HTCache);               /* Limit number (not size) of files */
                    616:     return me;
                    617: }

Webmaster