Annotation of libwww/Library/src/HTCache.c, revision 2.22
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.
2.22 ! frystyk 6: ** @(#) $Id: HTCache.c,v 2.21 1996/08/24 18:09:47 frystyk Exp $
2.1 frystyk 7: **
8: ** This modules manages the cache
9: **
10: ** History:
11: ** HFN: spawned from HTFwrite
12: ** HWL: converted the caching scheme to be hierachical by taking
13: ** AL code from Deamon
14: **
15: */
16:
17: /* Library include files */
2.15 frystyk 18: #include "sysdep.h"
2.19 frystyk 19: #include "WWWUtil.h"
20: #include "WWWCore.h"
2.22 ! frystyk 21: #include "WWWTrans.h"
! 22: #include "WWWApp.h"
! 23: #include "HTNetMan.h"
2.1 frystyk 24: #include "HTCache.h" /* Implemented here */
25:
2.7 frystyk 26: /* This is the default cache directory: */
2.22 ! frystyk 27: #define HT_CACHE_ROOT "/tmp/w3c-lib"
! 28: #define HT_CACHE_INDEX ".index"
! 29: #define HT_CACHE_META ".meta"
! 30:
! 31: #define HASH_SIZE 67
! 32: #define DUMP_FREQUENCY 10 /* Dump index after 10 loads */
! 33:
! 34: #define MEGA 0x100000L
! 35: #define CACHE_SIZE (20*MEGA) /* Default cache size is 20M */
! 36: #define MIN_CACHE_SIZE (long) (1.1*MEGA) /* Min cache size */
! 37: #define SIZE_BUFFER (1*MEGA) /* Buffer for metainfo and directories */
! 38:
! 39: /* Final states have negative value */
! 40: typedef enum _CacheState {
! 41: CL_ERROR = -3,
! 42: CL_NO_DATA = -2,
! 43: CL_GOT_DATA = -1,
! 44: CL_BEGIN = 0,
! 45: CL_NEED_INDEX,
! 46: CL_NEED_HEAD,
! 47: CL_NEED_BODY,
! 48: CL_NEED_OPEN_FILE,
! 49: CL_NEED_CONTENT
! 50: } CacheState;
! 51:
! 52: /* This is the context structure for the this module */
! 53: typedef struct _cache_info {
! 54: CacheState state; /* Current state of the connection */
! 55: char * local; /* Local representation of file name */
! 56: BOOL meta; /* Are we reading metainformation? */
! 57: struct stat stat_info; /* Contains actual file chosen */
! 58: } cache_info;
! 59:
! 60: struct _HTCache {
! 61: int hash;
! 62: char * url;
! 63: char * cachename;
! 64: long size;
! 65: int hits;
! 66: time_t freshness_lifetime;
! 67: time_t response_time;
! 68: time_t corrected_initial_age;
! 69: BOOL must_revalidate;
! 70: HTRequest * lock;
! 71: };
2.1 frystyk 72:
73: struct _HTStream {
2.15 frystyk 74: const HTStreamClass * isa;
2.1 frystyk 75: FILE * fp;
2.7 frystyk 76: HTCache * cache;
2.1 frystyk 77: HTRequest * request;
2.22 ! frystyk 78: HTChunk * buffer; /* For index reading */
! 79: HTEOLState EOLstate;
! 80: };
! 81:
! 82: struct _HTInputStream {
! 83: const HTInputStreamClass * isa;
2.1 frystyk 84: };
85:
2.22 ! frystyk 86: /* Cache parameters */
! 87: PRIVATE BOOL HTCacheEnable = NO; /* Disabled by default */
2.1 frystyk 88: PRIVATE char * HTCacheRoot = NULL; /* Destination for cache */
2.22 ! frystyk 89: PRIVATE HTExpiresMode HTExpMode = HT_EXPIRES_IGNORE;
! 90: PRIVATE HTDisconnectedMode DisconnectedMode = HT_DISCONNECT_NONE;
2.1 frystyk 91:
2.22 ! frystyk 92: /* List of cache entries */
! 93: PRIVATE HTList ** CacheTable = NULL;
! 94:
! 95: /* Cache size variables */
! 96: PRIVATE long HTCacheSize = CACHE_SIZE;
! 97: PRIVATE long HTTotalSize = 0L;
! 98:
! 99: PRIVATE int new_entries = 0; /* Number of new entries */
2.2 frystyk 100:
2.1 frystyk 101: /* ------------------------------------------------------------------------- */
2.22 ! frystyk 102: /* CACHE GARBAGE COLLECTOR */
2.1 frystyk 103: /* ------------------------------------------------------------------------- */
104:
2.22 ! frystyk 105: PRIVATE BOOL HTCacheGarbage (void)
2.1 frystyk 106: {
2.22 ! frystyk 107: long old_size = HTTotalSize;
! 108: if (CACHE_TRACE) HTTrace("Cache....... Garbage collecting\n");
! 109: if (CacheTable) {
! 110: time_t cur_time = time(NULL);
! 111: HTList * cur;
! 112: int cnt;
! 113:
! 114: /*
! 115: ** Tell the use that we're gc'ing.
! 116: */
2.1 frystyk 117: {
2.22 ! frystyk 118: HTAlertCallback * cbf = HTAlert_find(HT_PROG_GC);
! 119: if (cbf) (*cbf)(NULL, HT_PROG_GC, HT_MSG_NULL,NULL, NULL, NULL);
! 120: }
! 121:
! 122: /*
! 123: ** Walk through and delete all the expired entries. If this is not
! 124: ** sufficient then take the fresh ones which have the lowest cache
! 125: ** hit count. This algorithm could be made a lot fancier by including
! 126: ** the size and also the pain it took to get the document in the first
! 127: ** case. It could also include max_stale.
! 128: */
! 129: for (cnt=0; cnt<HASH_SIZE; cnt++) {
! 130: if ((cur = CacheTable[cnt])) {
! 131: HTCache * pres;
! 132: HTList * old_cur = cur;
! 133: while ((pres = (HTCache *) HTList_nextObject(cur)) != NULL) {
! 134: time_t resident_time = cur_time - pres->response_time;
! 135: time_t current_age = pres->corrected_initial_age +
! 136: resident_time;
! 137: if (pres->freshness_lifetime < current_age) {
! 138: HTCache_remove(pres);
! 139: cur = old_cur;
! 140: } else {
! 141: old_cur = cur;
! 142: }
2.1 frystyk 143: }
144: }
145: }
2.22 ! frystyk 146: if (CACHE_TRACE)
! 147: HTTrace("Cache....... Size reduced from %ld to %ld\n",
! 148: old_size, HTTotalSize);
! 149: return YES;
2.1 frystyk 150: }
2.22 ! frystyk 151: return NO;
2.1 frystyk 152: }
153:
2.22 ! frystyk 154: /* ------------------------------------------------------------------------- */
! 155: /* CACHE INDEX */
! 156: /* ------------------------------------------------------------------------- */
2.1 frystyk 157:
2.22 ! frystyk 158: PRIVATE char * cache_index_name (const char * cache_root)
2.1 frystyk 159: {
2.22 ! frystyk 160: if (cache_root) {
! 161: char * location = NULL;
! 162: if ((location = (char *)
! 163: HT_MALLOC(strlen(cache_root) + strlen(HT_CACHE_INDEX) + 1)) == NULL)
! 164: HT_OUTOFMEM("cache_index_name");
! 165: strcpy(location, cache_root);
! 166: strcat(location, HT_CACHE_INDEX);
! 167: return location;
2.1 frystyk 168: }
2.22 ! frystyk 169: return NULL;
2.1 frystyk 170: }
171:
172: /*
2.22 ! frystyk 173: ** Remove the cache index file
2.1 frystyk 174: */
2.22 ! frystyk 175: PUBLIC BOOL HTCacheIndex_delete (const char * cache_root)
2.1 frystyk 176: {
2.22 ! frystyk 177: if (cache_root) {
! 178: char * index = cache_index_name(cache_root);
! 179: REMOVE(index);
! 180: HT_FREE(index);
2.1 frystyk 181: return YES;
2.22 ! frystyk 182: }
! 183: return NO;
! 184: }
2.1 frystyk 185:
2.22 ! frystyk 186: /*
! 187: ** Walk through the list of cached objects and save them to disk.
! 188: ** We override any existing version but that is normally OK as we have
! 189: ** already read its contents.
! 190: */
! 191: PUBLIC BOOL HTCacheIndex_write (const char * cache_root)
! 192: {
! 193: if (cache_root && CacheTable) {
! 194: char * index = cache_index_name(cache_root);
! 195: FILE * fp = NULL;
! 196: if (CACHE_TRACE) HTTrace("Cache Index. Writing index `%s\'\n", index);
! 197:
! 198: /*
! 199: ** Open the file for writing. Note - we don't take a backup!
! 200: ** This should probably be fixed!
! 201: */
! 202: if (!index) return NO;
! 203: if ((fp = fopen(index, "wb")) == NULL) {
! 204: if (CACHE_TRACE)
! 205: HTTrace("Cache Index. Can't open `%s\' for writing\n", index);
! 206: HT_FREE(index);
! 207: return NO;
! 208: }
2.1 frystyk 209:
2.22 ! frystyk 210: /*
! 211: ** Walk through the list and write it out. The format is really
! 212: ** simple as we keep it all in ASCII
! 213: */
! 214: {
! 215: HTList * cur;
! 216: int cnt;
! 217: for (cnt=0; cnt<HASH_SIZE; cnt++) {
! 218: if ((cur = CacheTable[cnt])) {
! 219: HTCache * pres;
! 220: while ((pres = (HTCache *) HTList_nextObject(cur)) != NULL) {
! 221: if (fprintf(fp, "%s %s %ld %d %d %ld %ld %ld %c\n",
! 222: pres->url, pres->cachename,
! 223: pres->size, pres->hash, pres->hits,
! 224: pres->freshness_lifetime, pres->response_time,
! 225: pres->corrected_initial_age,
! 226: pres->must_revalidate+0x30) < 0) {
! 227: if (CACHE_TRACE)
! 228: HTTrace("Cache Index. Error writing cache index\n");
! 229: return NO;
! 230: }
! 231: }
! 232: }
! 233: }
! 234: }
2.1 frystyk 235:
2.22 ! frystyk 236: /* Done writing */
! 237: fclose(fp);
! 238: HT_FREE(index);
! 239: }
2.1 frystyk 240: return NO;
241: }
242:
243: /*
2.22 ! frystyk 244: ** Load one line of index file
! 245: ** Returns YES if line OK, else NO
2.2 frystyk 246: */
2.22 ! frystyk 247: PRIVATE BOOL HTCacheIndex_parseLine (char * line)
2.2 frystyk 248: {
2.22 ! frystyk 249: HTCache * cache = NULL;
! 250: if (line) {
! 251: char validate;
! 252: if ((cache = (HTCache *) HT_CALLOC(1, sizeof(HTCache))) == NULL)
! 253: HT_OUTOFMEM("HTCacheIndex_parseLine");
! 254:
! 255: /*
! 256: ** Read the line and create the cache object
! 257: */
! 258: {
! 259: char * url = HTNextField(&line);
! 260: char * cachename = HTNextField(&line);
! 261: StrAllocCopy(cache->url, url);
! 262: StrAllocCopy(cache->cachename, cachename);
! 263: }
! 264: if (sscanf(line, "%ld %d %d %ld %ld %ld %c",
! 265: &cache->size, &cache->hash, &cache->hits,
! 266: &cache->freshness_lifetime,
! 267: &cache->response_time,
! 268: &cache->corrected_initial_age,
! 269: &validate) < 0) {
! 270: if (CACHE_TRACE) HTTrace("Cache Index. Error reading cache index\n");
! 271: return NO;
! 272: }
! 273: cache->must_revalidate = validate-0x30;
! 274:
! 275: /*
! 276: ** Create the cache table if not already existent and add the new
! 277: ** entry. Also check that the hash is still within bounds
! 278: */
! 279: if (!CacheTable) {
! 280: if ((CacheTable = (HTList **) HT_CALLOC(HASH_SIZE,
! 281: sizeof(HTList *))) == NULL)
! 282: HT_OUTOFMEM("HTCache_parseLine");
! 283: }
! 284: if (cache->hash >= 0 && cache->hash < HASH_SIZE) {
! 285: int hash = cache->hash;
! 286: if (!CacheTable[hash]) CacheTable[hash] = HTList_new();
! 287: HTList_addObject(CacheTable[hash], (void *) cache);
2.2 frystyk 288: }
2.22 ! frystyk 289:
! 290: /* Update the total cache size */
! 291: HTTotalSize += cache->size;
! 292:
! 293: return YES;
2.2 frystyk 294: }
2.22 ! frystyk 295: return NO;
2.2 frystyk 296: }
297:
298: /*
2.22 ! frystyk 299: ** Folding is either of CF LWS, LF LWS, CRLF LWS
2.2 frystyk 300: */
2.22 ! frystyk 301: PRIVATE int HTCacheIndex_put_block (HTStream * me, const char * b, int l)
2.2 frystyk 302: {
2.22 ! frystyk 303: while (l > 0) {
! 304: if (me->EOLstate == EOL_FCR) {
! 305: if (*b == LF) /* CRLF */
! 306: me->EOLstate = EOL_FLF;
! 307: else if (WHITE(*b)) /* Folding: CR SP */
! 308: me->EOLstate = EOL_DOT;
! 309: else { /* New line */
! 310: HTCacheIndex_parseLine(HTChunk_data(me->buffer));
! 311: me->EOLstate = EOL_BEGIN;
! 312: HTChunk_clear(me->buffer);
! 313: continue;
! 314: }
! 315: } else if (me->EOLstate == EOL_FLF) {
! 316: if (WHITE(*b)) /* Folding: LF SP or CR LF SP */
! 317: me->EOLstate = EOL_DOT;
! 318: else { /* New line */
! 319: HTCacheIndex_parseLine(HTChunk_data(me->buffer));
! 320: me->EOLstate = EOL_BEGIN;
! 321: HTChunk_clear(me->buffer);
! 322: continue;
! 323: }
! 324: } else if (me->EOLstate == EOL_DOT) {
! 325: if (WHITE(*b)) {
! 326: me->EOLstate = EOL_BEGIN;
! 327: HTChunk_putc(me->buffer, ' ');
! 328: } else {
! 329: HTCacheIndex_parseLine(HTChunk_data(me->buffer));
! 330: me->EOLstate = EOL_BEGIN;
! 331: HTChunk_clear(me->buffer);
! 332: continue;
! 333: }
! 334: } else if (*b == CR) {
! 335: me->EOLstate = EOL_FCR;
! 336: } else if (*b == LF) {
! 337: me->EOLstate = EOL_FLF; /* Line found */
! 338: } else
! 339: HTChunk_putc(me->buffer, *b);
! 340: l--; b++;
2.2 frystyk 341: }
2.22 ! frystyk 342: return HT_OK;
2.2 frystyk 343: }
344:
2.22 ! frystyk 345: PRIVATE int HTCacheIndex_put_character (HTStream * me, char c)
! 346: {
! 347: return HTCacheIndex_put_block(me, &c, 1);
! 348: }
2.2 frystyk 349:
2.22 ! frystyk 350: PRIVATE int HTCacheIndex_put_string (HTStream * me, const char * s)
2.1 frystyk 351: {
2.22 ! frystyk 352: return HTCacheIndex_put_block(me, s, (int) strlen(s));
! 353: }
2.1 frystyk 354:
2.22 ! frystyk 355: PRIVATE int HTCacheIndex_flush (HTStream * me)
! 356: {
! 357: if (me) {
! 358: char * flush = HTChunk_data(me->buffer);
! 359: if (flush) HTCacheIndex_parseLine(flush);
! 360: HTChunk_clear(me->buffer);
! 361: }
! 362: return HT_OK;
! 363: }
2.1 frystyk 364:
2.22 ! frystyk 365: PRIVATE int HTCacheIndex_free (HTStream * me)
! 366: {
! 367: if (me) {
! 368: int status = HTCacheIndex_flush(me);
! 369: if (APP_TRACE) HTTrace("Cache Index. FREEING....\n");
! 370: HTChunk_delete(me->buffer);
! 371: HT_FREE(me);
! 372: return status;
! 373: }
! 374: return HT_ERROR;
! 375: }
2.1 frystyk 376:
2.22 ! frystyk 377: PRIVATE int HTCacheIndex_abort (HTStream * me, HTList * e)
! 378: {
! 379: if (me) {
! 380: int status = HT_ERROR;
! 381: if (APP_TRACE) HTTrace("Cache Index. ABORTING...\n");
! 382: HTChunk_delete(me->buffer);
! 383: HT_FREE(me);
! 384: return status;
2.1 frystyk 385: }
2.22 ! frystyk 386: return HT_ERROR;
! 387: }
2.1 frystyk 388:
2.22 ! frystyk 389: /* Structured Object Class
! 390: ** -----------------------
! 391: */
! 392: PRIVATE const HTStreamClass HTCacheIndexClass =
! 393: {
! 394: "CacheIndexParser",
! 395: HTCacheIndex_flush,
! 396: HTCacheIndex_free,
! 397: HTCacheIndex_abort,
! 398: HTCacheIndex_put_character,
! 399: HTCacheIndex_put_string,
! 400: HTCacheIndex_put_block
! 401: };
2.1 frystyk 402:
2.22 ! frystyk 403: PRIVATE HTStream * HTCacheIndexReader (HTRequest * request)
! 404: {
! 405: HTStream * me;
! 406: if ((me = (HTStream *) HT_CALLOC(1, sizeof(HTStream))) == NULL)
! 407: HT_OUTOFMEM("HTCacheIndexs");
! 408: me->isa = &HTCacheIndexClass;
! 409: me->request = request;
! 410: me->buffer = HTChunk_new(512);
! 411: me->EOLstate = EOL_BEGIN;
! 412: return me;
! 413: }
! 414:
! 415: /*
! 416: ** Read the saved set of cached entries from disk. we only allow the index
! 417: ** ro be read when there is no entries in memory. That way we can ensure
! 418: ** consistancy.
! 419: */
! 420: PUBLIC BOOL HTCacheIndex_read (const char * cache_root)
! 421: {
! 422: BOOL status = NO;
! 423: if (cache_root && CacheTable == NULL) {
! 424: char * file = cache_index_name(cache_root);
! 425: char * index = HTParse(file, "cache:", PARSE_ALL);
! 426: HTRequest * request = HTRequest_new();
! 427: HTRequest_setPreemptive(request, YES);
! 428: HTAlert_setInteractive(NO);
! 429: HTRequest_setOutputFormat(request, WWW_SOURCE);
! 430: HTRequest_setOutputStream(request, HTCacheIndexReader(request));
! 431: status = HTLoadAbsolute(index, request);
! 432: HTRequest_delete(request);
! 433: HT_FREE(file);
! 434: HT_FREE(index);
2.1 frystyk 435: }
2.22 ! frystyk 436: return status;
2.1 frystyk 437: }
438:
2.22 ! frystyk 439: /* ------------------------------------------------------------------------- */
! 440: /* CACHE PARAMETERS */
! 441: /* ------------------------------------------------------------------------- */
2.1 frystyk 442:
2.22 ! frystyk 443: PRIVATE BOOL create_cache_root (const char * cache_root)
2.1 frystyk 444: {
445: struct stat stat_info;
2.22 ! frystyk 446: char * loc = NULL;
2.1 frystyk 447: char * cur = NULL;
448: BOOL create = NO;
2.22 ! frystyk 449: if (!cache_root) return NO;
! 450: StrAllocCopy(loc, cache_root); /* Get our own copy */
! 451: cur = loc+1;
2.1 frystyk 452: while ((cur = strchr(cur, '/'))) {
2.22 ! frystyk 453: *cur = '\0';
! 454: if (create || HT_STAT(loc, &stat_info) == -1) {
! 455: create = YES; /* To avoid doing stat()s in vain */
! 456: if (CACHE_TRACE) HTTrace("Cache....... Creating dir `%s\'\n", loc);
! 457: if (MKDIR(loc, 0777) < 0) {
! 458: if (CACHE_TRACE) HTTrace("Cache....... can't create\n");
! 459: HT_FREE(loc);
2.1 frystyk 460: return NO;
461: }
462: } else {
2.22 ! frystyk 463: if (CACHE_TRACE)
! 464: HTTrace("Cache....... dir `%s\' already exists\n", loc);
2.1 frystyk 465: }
2.22 ! frystyk 466: *cur++ = '/';
2.1 frystyk 467: }
2.22 ! frystyk 468: HT_FREE(loc);
2.1 frystyk 469: return YES;
470: }
471:
2.22 ! frystyk 472: /*
! 473: ** If `cache_root' is NULL then the current value (might be a define)
! 474: ** Should we check if the cache_root is actually OK? I think not!
2.1 frystyk 475: */
2.22 ! frystyk 476: PRIVATE BOOL HTCacheMode_setRoot (const char * cache_root)
2.1 frystyk 477: {
2.22 ! frystyk 478: StrAllocCopy(HTCacheRoot, cache_root ? cache_root : HT_CACHE_ROOT);
! 479: if (*(HTCacheRoot+strlen(HTCacheRoot)-1) != '/')
! 480: StrAllocCat(HTCacheRoot, "/");
! 481: if (create_cache_root(HTCacheRoot) == NO) return NO;
! 482: if (CACHE_TRACE) HTTrace("Cache Root.. Root set to `%s\'\n", HTCacheRoot);
! 483: return YES;
2.1 frystyk 484: }
485:
486: /*
2.22 ! frystyk 487: ** Return the value of the cache root. The cache root can only be
! 488: ** set through the HTCacheInit() function
! 489: */
! 490: PUBLIC const char * HTCacheMode_getRoot (void)
! 491: {
! 492: return HTCacheRoot;
2.1 frystyk 493: }
494:
2.22 ! frystyk 495: /*
2.1 frystyk 496: ** If `cache_root' is NULL then reuse old value or use HT_CACHE_ROOT.
497: ** An empty string will make '/' as cache root
2.12 frystyk 498: ** We can only enable the cache if the HTSecure flag is not set. This
499: ** is for example the case if using an application as a telnet shell.
2.1 frystyk 500: */
2.22 ! frystyk 501: PUBLIC BOOL HTCacheInit (const char * cache_root, int size)
2.1 frystyk 502: {
2.22 ! frystyk 503: if (!HTLib_secure() && !HTCacheRoot) {
! 504:
! 505: /*
! 506: ** Find an appropriate root for the cache
! 507: */
! 508: if (HTCacheMode_setRoot(cache_root) != YES) return NO;
! 509:
! 510: /*
! 511: ** Set the max size of the cache
! 512: */
! 513: HTCacheMode_setMaxSize(size);
! 514:
! 515: /*
! 516: ** Look for the cache index and read the contents
! 517: */
! 518: HTCacheIndex_read(HTCacheRoot);
! 519:
! 520: /*
! 521: ** Do caching from now on
! 522: */
2.12 frystyk 523: HTCacheEnable = YES;
524: return YES;
525: }
526: return NO;
2.1 frystyk 527: }
528:
2.22 ! frystyk 529: /*
! 530: ** Turns off the cache and updates entries on disk.
2.1 frystyk 531: */
2.22 ! frystyk 532: PUBLIC BOOL HTCacheTerminate (void)
2.1 frystyk 533: {
2.22 ! frystyk 534: /*
! 535: ** Write the index to file
! 536: */
! 537: HTCacheIndex_write(HTCacheRoot);
! 538:
! 539: /*
! 540: ** Cleanup memory by deleting all HTCache objects
! 541: */
! 542: HTCache_deleteAll();
! 543:
! 544: /*
! 545: ** Don't do anymore caching from now on
! 546: */
! 547: HT_FREE(HTCacheRoot);
2.1 frystyk 548: HTCacheEnable = NO;
549: return YES;
550: }
551:
2.22 ! frystyk 552: /*
! 553: ** The cache can be temporarily suspended by using the enable/disable
! 554: ** flag. This does not prevent the cache from being enabled/disable at
! 555: ** a later point in time.
2.1 frystyk 556: */
2.22 ! frystyk 557: PUBLIC void HTCacheMode_setEnabled (BOOL mode)
! 558: {
! 559: HTCacheEnable = mode;
! 560: }
! 561:
! 562: PUBLIC BOOL HTCacheMode_enabled (void)
2.1 frystyk 563: {
2.12 frystyk 564: return HTCacheEnable;
2.1 frystyk 565: }
566:
2.22 ! frystyk 567: /*
! 568: ** We can set the cache to operate in disconnected mode in which we only
! 569: ** return (valid) responses from the cache. Disconnected mode does not
! 570: ** automatically deliver stale documents as this must be declared
! 571: ** explicitly.
2.1 frystyk 572: */
2.22 ! frystyk 573: PUBLIC void HTCacheMode_setDisconnected (HTDisconnectedMode mode)
2.1 frystyk 574: {
2.22 ! frystyk 575: DisconnectedMode = mode;
2.1 frystyk 576: }
577:
2.22 ! frystyk 578: PUBLIC HTDisconnectedMode HTCacheMode_disconnected (void)
2.1 frystyk 579: {
2.22 ! frystyk 580: return DisconnectedMode;
2.1 frystyk 581: }
582:
2.22 ! frystyk 583: PUBLIC BOOL HTCacheMode_isDisconnected (HTReload mode)
2.1 frystyk 584: {
2.22 ! frystyk 585: return (DisconnectedMode != HT_DISCONNECT_NONE);
2.1 frystyk 586: }
587:
2.7 frystyk 588: /*
589: ** Set the mode for how we handle Expires header from the local history
590: ** list. The following modes are available:
591: **
592: ** HT_EXPIRES_IGNORE : No update in the history list
593: ** HT_EXPIRES_NOTIFY : The user is notified but no reload
594: ** HT_EXPIRES_AUTO : Automatic reload
595: */
2.22 ! frystyk 596: PUBLIC void HTCacheMode_setExpires (HTExpiresMode mode)
2.7 frystyk 597: {
598: HTExpMode = mode;
599: }
600:
2.22 ! frystyk 601: PUBLIC HTExpiresMode HTCacheMode_expires (void)
2.7 frystyk 602: {
603: return HTExpMode;
604: }
605:
2.22 ! frystyk 606: /*
! 607: ** Cache size management. We set the default cache size to 20M.
! 608: ** We set the minimum size to 5M in order not to get into weird
! 609: ** problems while writing the cache. The size is indicated in Mega
! 610: ** bytes
! 611: */
! 612: PUBLIC BOOL HTCacheMode_setMaxSize (int size)
! 613: {
! 614: long new_size = size < 5 ? MIN_CACHE_SIZE : size * MEGA;
! 615: if (new_size < HTTotalSize) HTCacheGarbage();
! 616: HTCacheSize = new_size - SIZE_BUFFER;
! 617: if (CACHE_TRACE) HTTrace("Cache...... Total cache size: %ld\n", new_size);
! 618: return YES;
! 619: }
! 620:
! 621: PUBLIC int HTCacheMode_maxSize (void)
! 622: {
! 623: return HTCacheSize / MEGA;
! 624: }
! 625:
2.7 frystyk 626: /* ------------------------------------------------------------------------- */
2.22 ! frystyk 627: /* CACHE OBJECT */
2.2 frystyk 628: /* ------------------------------------------------------------------------- */
629:
2.22 ! frystyk 630: PRIVATE BOOL free_object (HTCache * me)
! 631: {
! 632: HT_FREE(me->url);
! 633: HT_FREE(me->cachename);
! 634: HT_FREE(me);
! 635: return YES;
! 636: }
! 637:
! 638: PRIVATE BOOL delete_object (HTList * list, HTCache * me)
! 639: {
! 640: if (CACHE_TRACE) HTTrace("Cache....... delete %p from list %p\n",me, list);
! 641: HTList_removeObject(list, (void *) me);
! 642: HTTotalSize -= me->size;
! 643: free_object(me);
! 644: return YES;
! 645: }
! 646:
! 647: /*
! 648: ** Create directory path for cache file
! 649: **
! 650: ** On exit:
! 651: ** return YES
! 652: ** if directories created -- after that caller
! 653: ** can rely on fopen(cfn,"w") succeeding.
! 654: **
! 655: */
! 656: PRIVATE BOOL HTCache_createLocation (HTCache * me)
! 657: {
! 658: if (me && HTCacheRoot) {
! 659: BOOL status = YES;
! 660: char * path = NULL;
! 661: struct stat stat_info;
! 662: if ((path = (char *) HT_MALLOC(strlen(HTCacheRoot) + 10)) == NULL)
! 663: HT_OUTOFMEM("HTCache_createLocation");
! 664: sprintf(path, "%s%d", HTCacheRoot, me->hash);
! 665: if (HT_STAT(path, &stat_info) == -1) {
! 666: if (CACHE_TRACE) HTTrace("Cache....... Create dir `%s\'\n", path);
! 667: if (MKDIR(path, 0777) < 0) {
! 668: if (CACHE_TRACE) HTTrace("Cache....... Can't create...\n");
! 669: status = NO;
! 670: }
! 671: } else {
! 672: if (CACHE_TRACE)
! 673: HTTrace("Cache....... Directory `%s\' already exists\n", path);
! 674: }
! 675: HT_FREE(path);
! 676: return status;
! 677: }
! 678: return NO;
! 679: }
! 680:
! 681: /*
! 682: ** Find a cache filename for this cache object.
! 683: */
! 684: PRIVATE BOOL HTCache_findName (HTCache * me)
! 685: {
! 686: if (me) {
! 687: /*
! 688: ** Create path for this cache entry. We base the cache location on the
! 689: ** hash calculated as a function of the URL. That way, we ensure a
! 690: ** resonably uniform distribution.
! 691: */
! 692: me->cachename = HTGetTmpFileName(NULL);
! 693: return HTCache_createLocation(me);
! 694: }
! 695: return NO;
! 696: }
! 697:
! 698: /*
! 699: ** Create a new cache entry and add it to the list
! 700: */
! 701: PRIVATE HTCache * HTCache_new (HTRequest * request, HTParentAnchor * anchor)
! 702: {
! 703: HTList * list = NULL; /* Current list in cache */
! 704: HTCache * pres = NULL;
! 705: int hash = 0;
! 706: char * url = NULL;
! 707: if (!request || !anchor) {
! 708: if (CORE_TRACE) HTTrace("Cache....... Bad argument\n");
! 709: return NULL;
! 710: }
! 711:
! 712: /* Find a hash for this anchor */
! 713: if ((url = HTAnchor_address((HTAnchor *) anchor))) {
! 714: char * ptr;
! 715: for (ptr=url; *ptr; ptr++)
! 716: hash = (int) ((hash * 3 + (*(unsigned char *) ptr)) % HASH_SIZE);
! 717: if (!CacheTable) {
! 718: if ((CacheTable = (HTList **) HT_CALLOC(HASH_SIZE,
! 719: sizeof(HTList *))) == NULL)
! 720: HT_OUTOFMEM("HTCache_new");
! 721: }
! 722: if (!CacheTable[hash]) CacheTable[hash] = HTList_new();
! 723: list = CacheTable[hash];
! 724: } else
! 725: return NULL;
! 726:
! 727: /* Search the cache */
! 728: {
! 729: HTList * cur = list;
! 730: while ((pres = (HTCache *) HTList_nextObject(cur))) {
! 731: if (!strcmp(pres->url, url)) break;
! 732: }
! 733: }
! 734:
! 735: /* If not found then create new cache object, else use existing one */
! 736: if (!pres) {
! 737: if ((pres = (HTCache *) HT_CALLOC(1, sizeof(HTCache))) == NULL)
! 738: HT_OUTOFMEM("HTCache_new");
! 739: pres->hash = hash;
! 740: pres->url = url;
! 741: HTCache_findName(pres);
! 742: HTList_addObject(list, (void *) pres);
! 743: new_entries++;
! 744: }
! 745:
! 746: if (HTCache_hasLock(pres)) {
! 747: if (CACHE_TRACE) HTTrace("Cache....... Entry %p locked\n");
! 748: return pres;
! 749: }
! 750:
! 751: /*
! 752: ** Calculate the corrected_initial_age of the object. We use the time
! 753: ** when this function is called as the response_time as this is when we
! 754: ** have received the complete response. This may cause a delay if the
! 755: ** reponse header is very big but should not cause any non-correct
! 756: ** behavior.
! 757: */
! 758: pres->response_time = time(NULL);
! 759: {
! 760: time_t apparent_age = HTMAX(0, pres->response_time - HTAnchor_date(anchor));
! 761: time_t corrected_received_age = HTMAX(apparent_age, HTAnchor_age(anchor));
! 762: time_t response_delay = pres->response_time - HTRequest_date(request);
! 763: pres->corrected_initial_age = corrected_received_age + response_delay;
! 764: }
! 765:
! 766: /*
! 767: ** Estimate an expires time using the max-age and expires time. If we
! 768: ** don't have an explicit expires time then set it to 10% of the LM date.
! 769: ** If no LM date is available then use 24 hours.
! 770: */
! 771: {
! 772: time_t freshness_lifetime = HTAnchor_maxAge(anchor);
! 773: if (freshness_lifetime < 0) {
! 774: time_t exp = HTAnchor_expires(anchor);
! 775: if (exp < 0) {
! 776: time_t lm = HTAnchor_lastModified(anchor);
! 777: if (lm < 0)
! 778: freshness_lifetime = 24*3600; /* 24 hours */
! 779: else
! 780: freshness_lifetime = (HTAnchor_date(anchor) - lm) / 10;
! 781: } else
! 782: freshness_lifetime = exp - HTAnchor_date(anchor);
! 783: }
! 784: pres->freshness_lifetime = HTMAX(0, freshness_lifetime);
! 785: }
! 786: if (CACHE_TRACE) {
! 787: HTTrace("Cache....... Received Age %d, corrected %d, freshness lifetime %d\n",
! 788: HTAnchor_age(anchor),
! 789: pres->corrected_initial_age,
! 790: pres->freshness_lifetime);
! 791: }
! 792: pres->must_revalidate = HTAnchor_mustRevalidate(anchor);
! 793: return pres;
! 794: }
! 795:
! 796: /*
! 797: ** Set the size of a cached object. We don't consider the metainformation as
! 798: ** part of the size which is the the reason for why the min cache size should
! 799: ** not be less than 5M. When we set the cache size we also check whether we
! 800: ** should run the gc or not.
! 801: */
! 802: PUBLIC BOOL HTCache_setSize (HTCache * cache, long size)
! 803: {
! 804: if (cache) {
! 805: if (cache->size > 0) HTTotalSize -= cache->size;
! 806: cache->size = size;
! 807: HTTotalSize += size;
! 808: if (CACHE_TRACE) HTTrace("Cache....... Total size %ld\n", HTTotalSize);
! 809: if (HTTotalSize > HTCacheSize) HTCacheGarbage();
! 810: return YES;
! 811: }
! 812: return NO;
! 813: }
! 814:
! 815: PUBLIC long HTCache_size (HTCache * cache)
! 816: {
! 817: return cache ? cache->size : -1;
! 818: }
! 819:
2.2 frystyk 820: /*
821: ** Verifies if a cache object exists for this URL and if so returns a URL
822: ** for the cached object. It does not verify whether the object is valid or
823: ** not, for example it might have expired.
824: **
2.17 frystyk 825: ** Returns: file name If OK (must be freed by caller)
2.2 frystyk 826: ** NULL If no cache object found
827: */
2.22 ! frystyk 828: PUBLIC HTCache * HTCache_find (HTParentAnchor * anchor)
! 829: {
! 830: HTList * list = NULL;
! 831: HTCache * pres = NULL;
! 832:
! 833: /* Find a hash entry for this URL */
! 834: if (HTCacheMode_enabled() && anchor && CacheTable) {
! 835: char * url = HTAnchor_address((HTAnchor *) anchor);
! 836: int hash = 0;
! 837: char * ptr = url;
! 838: for (; *ptr; ptr++)
! 839: hash = (int) ((hash * 3 + (*(unsigned char *) ptr)) % HASH_SIZE);
! 840: if (!CacheTable[hash]) {
! 841: HT_FREE(url);
! 842: return NULL;
! 843: }
! 844: list = CacheTable[hash];
! 845:
! 846: /* Search the cache */
! 847: {
! 848: HTList * cur = list;
! 849: while ((pres = (HTCache *) HTList_nextObject(cur))) {
! 850: if (!strcmp(pres->url, url)) {
! 851: if (CACHE_TRACE) HTTrace("Cache....... Found %p hits %d\n",
! 852: pres, pres->hits);
! 853: break;
! 854: }
! 855: }
! 856: }
! 857: HT_FREE(url);
! 858: }
! 859: return pres;
! 860: }
! 861:
! 862: /* HTCache_delete
! 863: ** --------------
! 864: ** Deletes a cache entry
! 865: */
! 866: PRIVATE BOOL HTCache_delete (HTCache * cache)
2.2 frystyk 867: {
2.22 ! frystyk 868: if (cache && CacheTable) {
! 869: HTList * cur = CacheTable[cache->hash];
! 870: return cur && delete_object(cur, cache);
! 871: }
! 872: return NO;
! 873: }
! 874:
! 875: /* HTCache_deleteAll
! 876: ** -----------------
! 877: ** Destroys all cache entried in memory but does not write anything to
! 878: ** disk
! 879: */
! 880: PUBLIC BOOL HTCache_deleteAll (void)
! 881: {
! 882: if (CacheTable) {
! 883: HTList * cur;
! 884: int cnt;
! 885:
! 886: /* Delete the rest */
! 887: for (cnt=0; cnt<HASH_SIZE; cnt++) {
! 888: if ((cur = CacheTable[cnt])) {
! 889: HTCache * pres;
! 890: while ((pres = (HTCache *) HTList_nextObject(cur)) != NULL)
! 891: free_object(pres);
! 892: }
! 893: HTList_delete(CacheTable[cnt]);
2.2 frystyk 894: }
2.22 ! frystyk 895: HT_FREE(CacheTable);
! 896: HTTotalSize = 0L;
! 897: return YES;
2.2 frystyk 898: }
2.22 ! frystyk 899: return NO;
2.2 frystyk 900: }
901:
902: /*
903: ** This function checks whether a document has expired or not.
904: ** The check is based on the metainformation passed in the anchor object
2.22 ! frystyk 905: ** The function returns the level of validation needed for getting a fresh
! 906: ** version. We also check the cache control directives in the request to
! 907: ** see if they change the freshness discission.
! 908: */
! 909: PUBLIC HTReload HTCache_isFresh (HTCache * cache, HTRequest * request)
! 910: {
! 911: HTAssocList * cc = HTRequest_cacheControl(request);
! 912: if (cache) {
! 913: time_t max_age = -1;
! 914: time_t max_stale = -1;
! 915: time_t min_fresh = -1;
! 916:
! 917: /*
! 918: ** In case this entry is of type "must-revalidate" then we just
! 919: ** go ahead and validate it.
! 920: */
! 921: if (cache->must_revalidate)
! 922: return HT_CACHE_VALIDATE;
! 923: /*
! 924: ** Check whether we have any special constraints like min-fresh in
! 925: ** the cache control
! 926: */
! 927: if (cc) {
! 928: char * token = NULL;
! 929: if ((token = HTAssocList_findObject(cc, "max-age")))
! 930: max_age = atol(token);
! 931: if ((token = HTAssocList_findObject(cc, "max-stale")))
! 932: max_stale = atol(token);
! 933: if ((token = HTAssocList_findObject(cc, "min-fresh")))
! 934: min_fresh = atol(token);
! 935: }
! 936:
! 937: /*
! 938: ** Now do the checking against the age constraints that we've got
! 939: */
! 940: {
! 941: time_t resident_time = time(NULL) - cache->response_time;
! 942: time_t current_age = cache->corrected_initial_age + resident_time;
! 943:
! 944: /*
! 945: ** Check that the max-age, max-stale, and min-fresh directives
! 946: ** given in the request cache control header is followed.
! 947: */
! 948: if (max_age >= 0 && current_age > max_age) {
! 949: if (CACHE_TRACE) HTTrace("Cache....... Max-age validation\n");
! 950: return HT_CACHE_VALIDATE;
! 951: }
! 952: if (min_fresh >= 0 &&
! 953: cache->freshness_lifetime < current_age + min_fresh) {
! 954: if (CACHE_TRACE) HTTrace("Cache....... Min-fresh validation\n");
! 955: return HT_CACHE_VALIDATE;
! 956: }
! 957:
! 958: return (cache->freshness_lifetime +
! 959: (max_stale >= 0 ? max_stale : 0) > current_age) ?
! 960: HT_CACHE_OK : HT_CACHE_VALIDATE;
! 961: }
! 962: }
! 963: return HT_CACHE_FLUSH;
! 964: }
! 965:
! 966: /*
! 967: ** While we are creating a new cache object or while we are validating an
! 968: ** existing one, we must have a lock on the entry so that not other
! 969: ** requests can get to it in the mean while.
! 970: */
! 971: PUBLIC BOOL HTCache_getLock (HTCache * cache, HTRequest * request)
! 972: {
! 973: if (cache && request) {
! 974: if (CACHE_TRACE) HTTrace("Cache....... Locking cache entry %p\n", cache);
! 975: cache->lock = request;
! 976: return YES;
! 977: }
! 978: return NO;
! 979: }
! 980:
! 981: PUBLIC BOOL HTCache_releaseLock (HTCache * cache)
! 982: {
! 983: if (cache) {
! 984: if (CACHE_TRACE) HTTrace("Cache....... Unlocking cache entry %p\n", cache);
! 985: cache->lock = NULL;
! 986: return YES;
! 987: }
! 988: return NO;
! 989: }
! 990:
! 991: PUBLIC BOOL HTCache_hasLock (HTCache * cache)
! 992: {
! 993: return cache && cache->lock;
! 994: }
! 995:
! 996: PUBLIC BOOL HTCache_breakLock (HTCache * cache, HTRequest * request)
! 997: {
! 998: if (cache && cache->lock) {
! 999: if (cache->lock == request) {
! 1000: if (CACHE_TRACE)
! 1001: HTTrace("Cache....... Breaking lock on entry %p\n", cache);
! 1002: cache->lock = NULL;
! 1003: return YES;
! 1004: }
! 1005: }
! 1006: return NO;
! 1007: }
! 1008:
! 1009: /*
! 1010: ** Is we have a valid entry in the cache then we also need a location
! 1011: ** where we can get it. Hopefully, we may be able to access it
! 1012: ** thourgh one of our protocol modules, for example the local file
! 1013: ** module. The name returned is in URL syntax and must be freed by
! 1014: ** the caller
! 1015: */
! 1016: PRIVATE char * HTCache_location (HTCache * cache, BOOL meta)
! 1017: {
! 1018: char * local = NULL;
! 1019: if (cache && HTCacheRoot) {
! 1020: if (meta) {
! 1021: if ((local = (char *) HT_MALLOC(strlen(HTCacheRoot) + 10 +
! 1022: strlen(HT_CACHE_META) +
! 1023: strlen(cache->cachename))) == NULL)
! 1024: HT_OUTOFMEM("HTCache_location");
! 1025: sprintf(local, "%s%d/%s%s", HTCacheRoot, cache->hash, cache->cachename,
! 1026: HT_CACHE_META);
! 1027: } else {
! 1028: if ((local = (char *) HT_MALLOC(strlen(HTCacheRoot) + 10 +
! 1029: strlen(cache->cachename))) == NULL)
! 1030: HT_OUTOFMEM("HTCache_location");
! 1031: sprintf(local, "%s%d/%s", HTCacheRoot, cache->hash, cache->cachename);
! 1032: }
! 1033: }
! 1034: return local;
! 1035: }
! 1036:
! 1037: /*
! 1038: ** Is we have a valid entry in the cache then we also need a location
! 1039: ** where we can get it. Hopefully, we may be able to access it
! 1040: ** thourgh one of our protocol modules, for example the local file
! 1041: ** module. The name returned is in URL syntax and must be freed by
! 1042: ** the caller
! 1043: */
! 1044: PUBLIC char * HTCache_name (HTCache * cache)
! 1045: {
! 1046: if (cache && HTCacheRoot) {
! 1047: char * local = HTCache_location(cache, NO);
! 1048: char * url = HTParse(local, "cache:", PARSE_ALL);
! 1049: HT_FREE(local);
! 1050: return url;
! 1051: }
! 1052: return NULL;
! 1053: }
! 1054:
! 1055: /*
! 1056: ** Remove from memory AND from disk. You must explicitly remove a lock
! 1057: ** before this operation can succeed
2.2 frystyk 1058: */
2.22 ! frystyk 1059: PUBLIC BOOL HTCache_remove (HTCache * cache)
2.2 frystyk 1060: {
2.22 ! frystyk 1061: if (cache && !HTCache_hasLock(cache)) {
! 1062: char * head = HTCache_location(cache, YES);
! 1063: char * body = HTCache_location(cache, NO);
! 1064: REMOVE(head);
! 1065: REMOVE(body);
! 1066: HT_FREE(head);
! 1067: HT_FREE(body);
! 1068: return HTCache_delete(cache);
! 1069: }
! 1070: return NO;
! 1071: }
! 1072:
! 1073: /*
! 1074: ** Walk through the set of headers and write those out that we are allowed
! 1075: ** to store in the cache. We look into the connection header to see what the
! 1076: ** hop-by-hop headers are and also into the cache-control directive to see what
! 1077: ** headers should not be cached.
! 1078: */
! 1079: PRIVATE BOOL meta_write (FILE * fp, HTRequest * request, HTAssocList * headers)
! 1080: {
! 1081: if (fp && request && headers) {
! 1082: HTParentAnchor * anchor = HTRequest_anchor(request);
! 1083: HTAssocList * connection = HTRequest_serverConnection(request);
! 1084: char * nocache = HTAnchor_noCache(anchor);
! 1085:
! 1086: /*
! 1087: ** Check whether either the connection header or the cache control header
! 1088: ** includes header names that we should not cache/
! 1089: */
! 1090: if (connection || nocache) {
! 1091:
! 1092: /* MORE */
! 1093:
! 1094: }
! 1095:
! 1096: /*
! 1097: ** Write out the remaining list of headerss
! 1098: */
! 1099: {
! 1100: HTAssocList * cur = headers;
! 1101: HTAssoc * pres;
! 1102: while ((pres = (HTAssoc *) HTAssocList_nextObject(cur))) {
! 1103: if (fprintf(fp, "%s: %s\n", HTAssoc_name(pres), HTAssoc_value(pres))<0) {
! 1104: if (CACHE_TRACE) HTTrace("Cache....... Error writing metainfo\n");
! 1105: return NO;
! 1106: }
! 1107: }
! 1108: }
! 1109: return YES;
! 1110: }
! 1111: return NO;
2.2 frystyk 1112: }
1113:
1114: /* ------------------------------------------------------------------------- */
2.22 ! frystyk 1115: /* CACHE WRITER */
2.1 frystyk 1116: /* ------------------------------------------------------------------------- */
1117:
2.22 ! frystyk 1118: /*
! 1119: ** Save the metainformation along with the data object. If no headers
! 1120: ** are available then the meta file is empty
! 1121: */
! 1122: PUBLIC BOOL HTCache_write (HTCache * cache, HTRequest * request)
! 1123: {
! 1124: if (cache && request) {
! 1125: BOOL status;
! 1126: FILE * fp;
! 1127: HTAssocList * headers = HTRequest_headers(request);
! 1128: char * name = HTCache_location(cache, YES);
! 1129: if ((fp = fopen(name, "wb")) == NULL) {
! 1130: if (CACHE_TRACE)
! 1131: HTTrace("Cache....... Can't open `%s\' for writing\n", name);
! 1132: HTCache_remove(cache);
! 1133: HT_FREE(name);
! 1134: return NO;
! 1135: }
! 1136: status = meta_write(fp, request, headers);
! 1137: fclose(fp);
! 1138: HT_FREE(name);
! 1139: return status;
! 1140: }
! 1141: return NO;
! 1142: }
! 1143:
! 1144: /*
! 1145: ** Merge metainformation with existing version. This means that we have had a
! 1146: ** successful validation and hence a true cache hit.
! 1147: */
! 1148: PUBLIC BOOL HTCache_update (HTCache * cache, HTAssocList * headers)
! 1149: {
! 1150: if (cache) {
! 1151: cache->hits++;
! 1152:
! 1153: /* MORE */
! 1154:
! 1155: return YES;
! 1156: }
! 1157: return NO;
! 1158: }
! 1159:
! 1160: PUBLIC BOOL HTCache_addHit (HTCache * cache)
! 1161: {
! 1162: if (cache) {
! 1163: cache->hits++;
! 1164: if (CACHE_TRACE) HTTrace("Cache....... Hits for %p is %d\n",
! 1165: cache, cache->hits);
! 1166: return YES;
! 1167: }
! 1168: return NO;
! 1169: }
! 1170:
2.12 frystyk 1171: PRIVATE int HTCache_flush (HTStream * me)
2.1 frystyk 1172: {
1173: return (fflush(me->fp) == EOF) ? HT_ERROR : HT_OK;
1174: }
1175:
2.15 frystyk 1176: PRIVATE int HTCache_putBlock (HTStream * me, const char * s, int l)
2.1 frystyk 1177: {
1178: int status = (fwrite(s, 1, l, me->fp) != l) ? HT_ERROR : HT_OK;
1179: if (l > 1 && status == HT_OK)
1180: (void) HTCache_flush(me);
1181: return status;
1182: }
1183:
2.12 frystyk 1184: PRIVATE int HTCache_putChar (HTStream * me, char c)
2.1 frystyk 1185: {
1186: return HTCache_putBlock(me, &c, 1);
1187: }
1188:
2.15 frystyk 1189: PRIVATE int HTCache_putString (HTStream * me, const char * s)
2.1 frystyk 1190: {
1191: return HTCache_putBlock(me, s, (int) strlen(s));
1192: }
1193:
2.17 frystyk 1194: PRIVATE int HTCache_free (HTStream * me)
2.1 frystyk 1195: {
2.22 ! frystyk 1196: if (me) {
! 1197: if (me->fp) fclose(me->fp);
! 1198: /*
! 1199: ** We are done storing the object body and can update the cache entry.
! 1200: ** Also update the meta information entry on disk as well. When we
! 1201: ** are done we don't need the lock anymore.
! 1202: */
! 1203: if (me->cache) {
! 1204: HTParentAnchor * anchor = HTRequest_anchor(me->request);
! 1205: HTCache_write(me->cache, me->request);
! 1206: HTCache_releaseLock(me->cache);
! 1207:
! 1208: /*
! 1209: ** Set the size and maybe do gc
! 1210: */
! 1211: HTCache_setSize(me->cache, HTAnchor_length(anchor));
! 1212: }
! 1213:
! 1214: /*
! 1215: ** In order not to loose information, we dump the current cache index
! 1216: ** every time we have created DUMP_FREQUENCY new entries
! 1217: */
! 1218: if (new_entries > DUMP_FREQUENCY) {
! 1219: HTCacheIndex_write(HTCacheRoot);
! 1220: new_entries = 0;
! 1221: }
! 1222: HT_FREE(me);
! 1223: }
2.1 frystyk 1224: return HT_OK;
1225: }
1226:
2.12 frystyk 1227: PRIVATE int HTCache_abort (HTStream * me, HTList * e)
2.1 frystyk 1228: {
2.22 ! frystyk 1229: if (CACHE_TRACE) HTTrace("Cache....... ABORTING\n");
! 1230: if (me->fp) fclose(me->fp);
! 1231: if (me->cache) HTCache_remove(me->cache);
2.13 frystyk 1232: HT_FREE(me);
2.1 frystyk 1233: return HT_ERROR;
1234: }
1235:
2.15 frystyk 1236: PRIVATE const HTStreamClass HTCacheClass =
2.1 frystyk 1237: {
1238: "Cache",
1239: HTCache_flush,
2.17 frystyk 1240: HTCache_free,
2.1 frystyk 1241: HTCache_abort,
1242: HTCache_putChar,
1243: HTCache_putString,
1244: HTCache_putBlock
1245: };
1246:
2.22 ! frystyk 1247: PUBLIC HTStream * HTCacheWriter (HTRequest * request,
! 1248: void * param,
! 1249: HTFormat input_format,
! 1250: HTFormat output_format,
! 1251: HTStream * output_stream)
2.1 frystyk 1252: {
2.22 ! frystyk 1253: HTCache * cache = NULL;
! 1254: FILE * fp = NULL;
2.19 frystyk 1255: HTParentAnchor * anchor = HTRequest_anchor(request);
2.12 frystyk 1256: if (!HTCacheEnable) {
2.14 eric 1257: if (CACHE_TRACE) HTTrace("Cache....... Not enabled\n");
2.22 ! frystyk 1258: return NULL;
2.1 frystyk 1259: }
1260:
2.22 ! frystyk 1261: /* Get a new cache entry */
! 1262: if ((cache = HTCache_new(request, anchor)) == NULL) {
! 1263: if (CACHE_TRACE) HTTrace("Cache....... Can't get a cache object\n");
! 1264: return NULL;
! 1265: }
! 1266:
! 1267: /* Test that the cached object is not locked */
! 1268: if (HTCache_hasLock(cache)) {
! 1269: if (HTCache_breakLock(cache, request) == NO) {
! 1270: if (CACHE_TRACE) HTTrace("Cache....... Entry already in use\n");
! 1271: return NULL;
! 1272: }
! 1273: }
! 1274: HTCache_getLock(cache, request);
! 1275:
! 1276: /*
! 1277: ** Test that we can actually write to the cache file. If the entry already
! 1278: ** existed then it will be overridden with the new data.
! 1279: */
! 1280: {
! 1281: char * name = HTCache_location(cache, NO);
! 1282: if ((fp = fopen(name, "wb")) == NULL) {
! 1283: if (CACHE_TRACE)
! 1284: HTTrace("Cache....... Can't open `%s\' for writing\n", name);
! 1285: HTCache_delete(cache);
! 1286: HT_FREE(name);
! 1287: return NULL;
! 1288: } else
! 1289: if (CACHE_TRACE) HTTrace("Cache....... Creating file %s\n", name);
! 1290: HT_FREE(name);
! 1291: }
2.1 frystyk 1292:
1293: /* Set up the stream */
2.22 ! frystyk 1294: {
! 1295: HTStream * me = NULL;
! 1296: if ((me = (HTStream *) HT_CALLOC(1, sizeof(HTStream))) == NULL)
! 1297: HT_OUTOFMEM("Cache");
! 1298: me->isa = &HTCacheClass;
! 1299: me->request = request;
! 1300: me->cache = cache;
! 1301: me->fp = fp;
! 1302: return me;
! 1303: }
! 1304: return NULL;
! 1305: }
! 1306:
! 1307: /* ------------------------------------------------------------------------- */
! 1308: /* CACHE READER */
! 1309: /* ------------------------------------------------------------------------- */
! 1310:
! 1311: /*
! 1312: ** This function closes the connection and frees memory.
! 1313: ** Returns YES on OK, else NO
! 1314: */
! 1315: PRIVATE int CacheCleanup (HTRequest * request, int status)
! 1316: {
! 1317: HTNet * net = HTRequest_net(request);
! 1318: cache_info * cache = (cache_info *) HTNet_context(net);
! 1319: HTStream * input = HTRequest_inputStream(request);
! 1320:
! 1321: /* Free stream with data TO local cache system */
! 1322: if (input) {
! 1323: if (status == HT_INTERRUPTED)
! 1324: (*input->isa->abort)(input, NULL);
! 1325: else
! 1326: (*input->isa->_free)(input);
! 1327: HTRequest_setInputStream(request, NULL);
! 1328: }
! 1329:
! 1330: if (status != HT_IGNORE) {
! 1331: if (cache) {
! 1332: HT_FREE(cache->local);
! 1333: HT_FREE(cache);
! 1334: }
! 1335: HTNet_delete(net, status);
! 1336: } else if (cache) {
! 1337: HT_FREE(cache->local);
! 1338: }
! 1339: return YES;
! 1340: }
! 1341:
! 1342: /*
! 1343: ** This load function loads an object from the cache and puts it to the
! 1344: ** output defined by the request object. For the moment, this load function
! 1345: ** handles the persistent cache as if it was on local file but in fact
! 1346: ** it could be anywhere.
! 1347: **
! 1348: ** Returns HT_ERROR Error has occured in call back
! 1349: ** HT_OK Call back was OK
! 1350: */
! 1351: PUBLIC int HTLoadCache (SOCKET soc, HTRequest * request, SockOps ops)
! 1352: {
! 1353: int status = HT_ERROR;
! 1354: HTNet * net = HTRequest_net(request);
! 1355: HTParentAnchor * anchor = HTRequest_anchor(request);
! 1356: cache_info * cache; /* Specific access information */
! 1357:
! 1358: /*
! 1359: ** Initiate a new cache structure and bind to request structure
! 1360: ** This is actually state CACHE_BEGIN, but it can't be in the state
! 1361: ** machine as we need the structure first.
! 1362: */
! 1363: if (ops == FD_NONE) {
! 1364: if (PROT_TRACE) HTTrace("Load Cache.. Looking for `%s\'\n",
! 1365: HTAnchor_physical(anchor));
! 1366: if ((cache = (cache_info *) HT_CALLOC(1, sizeof(cache_info))) == NULL)
! 1367: HT_OUTOFMEM("HTLoadCACHE");
! 1368: cache->state = CL_BEGIN;
! 1369: HTNet_setContext(net, cache);
! 1370: } if (ops == FD_CLOSE) { /* Interrupted */
! 1371: HTRequest_addError(request, ERR_FATAL, NO, HTERR_INTERRUPTED,
! 1372: NULL, 0, "HTLoadCache");
! 1373: CacheCleanup(request, HT_INTERRUPTED);
! 1374: return HT_OK;
2.1 frystyk 1375: } else
2.22 ! frystyk 1376: cache = (cache_info *) HTNet_context(net); /* Get existing copy */
! 1377:
! 1378: /* Now jump into the machine. We know the state from the previous run */
! 1379: while (1) {
! 1380: switch (cache->state) {
! 1381: case CL_BEGIN:
! 1382: if (HTLib_secure()) {
! 1383: if (PROT_TRACE)
! 1384: HTTrace("Load Cache.. No access to local file system\n");
! 1385: cache->state = CL_ERROR;
! 1386: break;
! 1387: }
! 1388: cache->state = CacheTable ?
! 1389: (HTAnchor_headerParsed(anchor) ? CL_NEED_BODY : CL_NEED_HEAD) :
! 1390: CL_NEED_INDEX;
! 1391: break;
! 1392:
! 1393: case CL_NEED_HEAD:
! 1394: {
! 1395: char * meta = NULL;
! 1396: StrAllocCopy(meta, HTAnchor_physical(anchor));
! 1397: StrAllocCat(meta, HT_CACHE_META);
! 1398: cache->meta = YES;
! 1399: cache->local = HTWWWToLocal(meta, "",
! 1400: HTRequest_userProfile(request));
! 1401: if (cache->local) {
! 1402: if (HT_STAT(cache->local, &cache->stat_info) == -1) {
! 1403: if (PROT_TRACE)
! 1404: HTTrace("Load Cache.. Not found `%s\'\n",cache->local);
! 1405: HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_FOUND,
! 1406: NULL, 0, "HTLoadCache");
! 1407: cache->state = CL_ERROR;
! 1408: break;
! 1409: }
! 1410:
! 1411: /*
! 1412: ** The cache entry may be empty in which case we just return
! 1413: */
! 1414: if (!cache->stat_info.st_size) {
! 1415: HTRequest_addError(request, ERR_FATAL, NO,HTERR_NO_CONTENT,
! 1416: NULL, 0, "HTLoadCache");
! 1417: cache->state = CL_GOT_DATA;
! 1418: } else
! 1419: cache->state = CL_NEED_OPEN_FILE;
! 1420: } else
! 1421: cache->state = CL_ERROR;
! 1422: break;
! 1423: }
! 1424:
! 1425: case CL_NEED_INDEX:
! 1426: case CL_NEED_BODY:
! 1427: cache->local = HTWWWToLocal(HTAnchor_physical(anchor), "",
! 1428: HTRequest_userProfile(request));
! 1429: if (cache->local) {
! 1430: if (HT_STAT(cache->local, &cache->stat_info) == -1) {
! 1431: if (PROT_TRACE)
! 1432: HTTrace("Load Cache.. Not found `%s\'\n", cache->local);
! 1433: HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_FOUND,
! 1434: NULL, 0, "HTLoadCache");
! 1435: cache->state = CL_ERROR;
! 1436: break;
! 1437: }
! 1438:
! 1439: /*
! 1440: ** The cache entry may be empty in which case we just return
! 1441: */
! 1442: if (!cache->stat_info.st_size) {
! 1443: HTRequest_addError(request, ERR_FATAL, NO,HTERR_NO_CONTENT,
! 1444: NULL, 0, "HTLoadCache");
! 1445: cache->state = CL_NO_DATA;
! 1446: } else
! 1447: cache->state = CL_NEED_OPEN_FILE;
! 1448: } else
! 1449: cache->state = CL_ERROR;
! 1450: break;
! 1451:
! 1452: case CL_NEED_OPEN_FILE:
! 1453: status = HTFileOpen(net, cache->local, HT_FT_RDONLY);
! 1454: if (status == HT_OK) {
! 1455: /*
! 1456: ** Create the stream pipe FROM the channel to the application.
! 1457: ** The target for the input stream pipe is set up using the
! 1458: ** stream stack.
! 1459: */
! 1460: if (cache->meta) {
! 1461: HTNet_getInput(net,
! 1462: HTStreamStack(WWW_MIME_HEAD,
! 1463: HTRequest_debugFormat(request),
! 1464: HTRequest_debugStream(request),
! 1465: request, NO), NULL, 0);
! 1466: cache->state = CL_NEED_CONTENT;
! 1467: } else {
! 1468: HTNet_getInput(net,
! 1469: HTStreamStack(HTAnchor_format(anchor),
! 1470: HTRequest_outputFormat(request),
! 1471: HTRequest_outputStream(request),
! 1472: request, YES), NULL, 0);
! 1473: HTRequest_setOutputConnected(request, YES);
! 1474: HTRequest_addError(request, ERR_INFO, NO, HTERR_OK,
! 1475: NULL, 0, "HTLoadCache");
! 1476: cache->state = CL_NEED_CONTENT;
! 1477:
! 1478: #ifndef NO_UNIX_IO
! 1479: /* If we are _not_ using preemptive mode and we are Unix fd's
! 1480: ** then return here to get the same effect as when we are
! 1481: ** connecting to a socket. That way, HTCache acts just like any
! 1482: ** other protocol module even though we are in fact doing
! 1483: ** blocking connect
! 1484: */
! 1485: if (!net->preemptive) {
! 1486: if (PROT_TRACE) HTTrace("Load Cache.. returning\n");
! 1487: HTEvent_register(net->sockfd, request, (SockOps) FD_READ,
! 1488: net->cbf, net->priority);
! 1489: return HT_OK;
! 1490: }
! 1491: #endif
! 1492: }
! 1493: } else if (status == HT_WOULD_BLOCK || status == HT_PENDING)
! 1494: return HT_OK;
! 1495: else {
! 1496: HTRequest_addError(request, ERR_INFO, NO, HTERR_INTERNAL,
! 1497: NULL, 0, "HTLoadCache");
! 1498: cache->state = CL_ERROR; /* Error or interrupt */
! 1499: }
! 1500: break;
! 1501:
! 1502: case CL_NEED_CONTENT:
! 1503: status = (*net->input->isa->read)(net->input);
! 1504: if (status == HT_WOULD_BLOCK)
! 1505: return HT_OK;
! 1506: else if (status == HT_LOADED || status == HT_CLOSED) {
! 1507: cache->state = CL_GOT_DATA;
! 1508: } else {
! 1509: HTRequest_addError(request, ERR_INFO, NO, HTERR_FORBIDDEN,
! 1510: NULL, 0, "HTLoadCache");
! 1511: cache->state = CL_ERROR;
! 1512: }
! 1513: break;
! 1514:
! 1515: case CL_GOT_DATA:
! 1516: if (cache->meta) {
! 1517: CacheCleanup(request, HT_IGNORE);
! 1518: cache->state = CL_NEED_BODY;
! 1519: cache->meta = NO;
! 1520: } else {
! 1521: CacheCleanup(request, HT_LOADED);
! 1522: return HT_OK;
! 1523: }
! 1524: break;
2.1 frystyk 1525:
2.22 ! frystyk 1526: case CL_NO_DATA:
! 1527: CacheCleanup(request, HT_NO_DATA);
! 1528: return HT_OK;
! 1529: break;
! 1530:
! 1531: case CL_ERROR:
! 1532: CacheCleanup(request, HT_ERROR);
! 1533: return HT_OK;
! 1534: break;
! 1535: }
! 1536: } /* End of while(1) */
2.1 frystyk 1537: }
Webmaster