Annotation of embedaddon/thttpd/mmc.c, revision 1.1
1.1 ! misho 1: /* mmc.c - mmap cache
! 2: **
! 3: ** Copyright © 1998,2001 by Jef Poskanzer <jef@mail.acme.com>.
! 4: ** All rights reserved.
! 5: **
! 6: ** Redistribution and use in source and binary forms, with or without
! 7: ** modification, are permitted provided that the following conditions
! 8: ** are met:
! 9: ** 1. Redistributions of source code must retain the above copyright
! 10: ** notice, this list of conditions and the following disclaimer.
! 11: ** 2. Redistributions in binary form must reproduce the above copyright
! 12: ** notice, this list of conditions and the following disclaimer in the
! 13: ** documentation and/or other materials provided with the distribution.
! 14: **
! 15: ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
! 16: ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
! 17: ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
! 18: ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
! 19: ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
! 20: ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
! 21: ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
! 22: ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
! 23: ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
! 24: ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
! 25: ** SUCH DAMAGE.
! 26: */
! 27:
! 28: #include "config.h"
! 29:
! 30: #include <sys/types.h>
! 31: #include <sys/stat.h>
! 32: #include <sys/time.h>
! 33: #include <stdlib.h>
! 34: #include <unistd.h>
! 35: #include <stdio.h>
! 36: #include <time.h>
! 37: #include <fcntl.h>
! 38: #include <syslog.h>
! 39: #include <errno.h>
! 40:
! 41: #ifdef HAVE_MMAP
! 42: #include <sys/mman.h>
! 43: #endif /* HAVE_MMAP */
! 44:
! 45: #include "mmc.h"
! 46: #include "libhttpd.h"
! 47:
! 48: #ifndef HAVE_INT64T
! 49: typedef long long int64_t;
! 50: #endif
! 51:
! 52:
! 53: /* Defines. */
! 54: #ifndef DEFAULT_EXPIRE_AGE
! 55: #define DEFAULT_EXPIRE_AGE 600
! 56: #endif
! 57: #ifndef DESIRED_FREE_COUNT
! 58: #define DESIRED_FREE_COUNT 100
! 59: #endif
! 60: #ifndef DESIRED_MAX_MAPPED_FILES
! 61: #define DESIRED_MAX_MAPPED_FILES 2000
! 62: #endif
! 63: #ifndef DESIRED_MAX_MAPPED_BYTES
! 64: #define DESIRED_MAX_MAPPED_BYTES 1000000000
! 65: #endif
! 66: #ifndef INITIAL_HASH_SIZE
! 67: #define INITIAL_HASH_SIZE (1 << 10)
! 68: #endif
! 69:
! 70: #ifndef MAX
! 71: #define MAX(a,b) ((a)>(b)?(a):(b))
! 72: #endif
! 73: #ifndef MIN
! 74: #define MIN(a,b) ((a)<(b)?(a):(b))
! 75: #endif
! 76:
! 77:
! 78: /* The Map struct. */
! 79: typedef struct MapStruct {
! 80: ino_t ino;
! 81: dev_t dev;
! 82: off_t size;
! 83: time_t ctime;
! 84: int refcount;
! 85: time_t reftime;
! 86: #ifdef USE_SENDFILE
! 87: int fd;
! 88: #endif
! 89: void* addr;
! 90: unsigned int hash;
! 91: int hash_idx;
! 92: struct MapStruct* next;
! 93: } Map;
! 94:
! 95:
! 96: /* Globals. */
! 97: static Map* maps = (Map*) 0;
! 98: static Map* free_maps = (Map*) 0;
! 99: static int alloc_count = 0, map_count = 0, free_count = 0;
! 100: static Map** hash_table = (Map**) 0;
! 101: static int hash_size;
! 102: static unsigned int hash_mask;
! 103: static time_t expire_age = DEFAULT_EXPIRE_AGE;
! 104: static off_t mapped_bytes = 0;
! 105:
! 106:
! 107:
! 108: /* Forwards. */
! 109: static void panic( void );
! 110: static void really_unmap( Map** mm );
! 111: static int check_hash_size( void );
! 112: static int add_hash( Map* m );
! 113: static Map* find_hash( ino_t ino, dev_t dev, off_t size, time_t ctime );
! 114: static unsigned int hash( ino_t ino, dev_t dev, off_t size, time_t ctime );
! 115:
! 116:
! 117: void*
! 118: mmc_map( char* filename, struct stat* sbP, struct timeval* nowP )
! 119: {
! 120: time_t now;
! 121: struct stat sb;
! 122: Map* m;
! 123: int fd;
! 124:
! 125: /* Stat the file, if necessary. */
! 126: if ( sbP != (struct stat*) 0 )
! 127: sb = *sbP;
! 128: else
! 129: {
! 130: if ( stat( filename, &sb ) != 0 )
! 131: {
! 132: syslog( LOG_ERR, "stat - %m" );
! 133: return (void*) 0;
! 134: }
! 135: }
! 136:
! 137: /* Get the current time, if necessary. */
! 138: if ( nowP != (struct timeval*) 0 )
! 139: now = nowP->tv_sec;
! 140: else
! 141: now = time( (time_t*) 0 );
! 142:
! 143: /* See if we have it mapped already, via the hash table. */
! 144: if ( check_hash_size() < 0 )
! 145: {
! 146: syslog( LOG_ERR, "check_hash_size() failure" );
! 147: return (void*) 0;
! 148: }
! 149: m = find_hash( sb.st_ino, sb.st_dev, sb.st_size, sb.st_ctime );
! 150: if ( m != (Map*) 0 )
! 151: {
! 152: /* Yep. Just return the existing map */
! 153: ++m->refcount;
! 154: m->reftime = now;
! 155: #ifdef USE_SENDFILE
! 156: return (&m->fd);
! 157: #else
! 158: return m->addr;
! 159: #endif
! 160: }
! 161:
! 162: /* Open the file. */
! 163: fd = open( filename, O_RDONLY );
! 164: if ( fd < 0 )
! 165: {
! 166: syslog( LOG_ERR, "open - %m" );
! 167: return (void*) 0;
! 168: }
! 169:
! 170: /* Find a free Map entry or make a new one. */
! 171: if ( free_maps != (Map*) 0 )
! 172: {
! 173: m = free_maps;
! 174: free_maps = m->next;
! 175: --free_count;
! 176: }
! 177: else
! 178: {
! 179: m = (Map*) malloc( sizeof(Map) );
! 180: if ( m == (Map*) 0 )
! 181: {
! 182: (void) close( fd );
! 183: syslog( LOG_ERR, "out of memory allocating a Map" );
! 184: return (void*) 0;
! 185: }
! 186: ++alloc_count;
! 187: }
! 188:
! 189: /* Fill in the Map entry. */
! 190: m->ino = sb.st_ino;
! 191: m->dev = sb.st_dev;
! 192: m->size = sb.st_size;
! 193: m->ctime = sb.st_ctime;
! 194: m->refcount = 1;
! 195: m->reftime = now;
! 196:
! 197: /* Avoid doing anything for zero-length files; some systems don't like
! 198: ** to mmap them, other systems dislike mallocing zero bytes.
! 199: */
! 200: if ( m->size == 0 )
! 201: m->addr = (void*) 1; /* arbitrary non-NULL address */
! 202: else
! 203: {
! 204: size_t size_size = (size_t) m->size; /* loses on files >2GB */
! 205: #ifdef USE_SENDFILE
! 206: m->fd = fd;
! 207: #elif defined(HAVE_MMAP)
! 208: /* Map the file into memory. */
! 209: m->addr = mmap( 0, size_size, PROT_READ, MAP_PRIVATE, fd, 0 );
! 210: if ( m->addr == (void*) -1 && errno == ENOMEM )
! 211: {
! 212: /* Ooo, out of address space. Free all unreferenced maps
! 213: ** and try again.
! 214: */
! 215: panic();
! 216: m->addr = mmap( 0, size_size, PROT_READ, MAP_PRIVATE, fd, 0 );
! 217: }
! 218: if ( m->addr == (void*) -1 )
! 219: {
! 220: syslog( LOG_ERR, "mmap - %m" );
! 221: (void) close( fd );
! 222: free( (void*) m );
! 223: --alloc_count;
! 224: return (void*) 0;
! 225: }
! 226: #else /* HAVE_MMAP */
! 227: /* Read the file into memory. */
! 228: m->addr = (void*) malloc( size_size );
! 229: if ( m->addr == (void*) 0 )
! 230: {
! 231: /* Ooo, out of memory. Free all unreferenced maps
! 232: ** and try again.
! 233: */
! 234: panic();
! 235: m->addr = (void*) malloc( size_size );
! 236: }
! 237: if ( m->addr == (void*) 0 )
! 238: {
! 239: syslog( LOG_ERR, "out of memory storing a file" );
! 240: (void) close( fd );
! 241: free( (void*) m );
! 242: --alloc_count;
! 243: return (void*) 0;
! 244: }
! 245: if ( httpd_read_fully( fd, m->addr, size_size ) != size_size )
! 246: {
! 247: syslog( LOG_ERR, "read - %m" );
! 248: (void) close( fd );
! 249: free( (void*) m );
! 250: --alloc_count;
! 251: return (void*) 0;
! 252: }
! 253: #endif /* HAVE_MMAP */
! 254: }
! 255: #ifndef USE_SENDFILE
! 256: (void) close( fd );
! 257: #endif /* !USE_SENDFILE */
! 258: /* Put the Map into the hash table. */
! 259: if ( add_hash( m ) < 0 )
! 260: {
! 261: syslog( LOG_ERR, "add_hash() failure" );
! 262: free( (void*) m );
! 263: --alloc_count;
! 264: return (void*) 0;
! 265: }
! 266:
! 267: /* Put the Map on the active list. */
! 268: m->next = maps;
! 269: maps = m;
! 270: ++map_count;
! 271:
! 272: /* Update the total byte count. */
! 273: mapped_bytes += m->size;
! 274:
! 275: #ifdef USE_SENDFILE
! 276: return (&m->fd);
! 277: #else
! 278: /* And return the address. */
! 279: return m->addr;
! 280: #endif
! 281: }
! 282:
! 283:
! 284: void
! 285: mmc_unmap( void* addr, struct stat* sbP, struct timeval* nowP )
! 286: {
! 287: Map* m = (Map*) 0;
! 288:
! 289: /* Find the Map entry for this address. First try a hash. */
! 290: if ( sbP != (struct stat*) 0 )
! 291: {
! 292: m = find_hash( sbP->st_ino, sbP->st_dev, sbP->st_size, sbP->st_ctime );
! 293: #ifndef USE_SENDFILE
! 294: if ( m != (Map*) 0 && m->addr != addr )
! 295: m = (Map*) 0;
! 296: #endif
! 297: }
! 298: #ifndef USE_SENDFILE
! 299: /* If that didn't work, try a full search. */
! 300: if ( m == (Map*) 0 )
! 301: for ( m = maps; m != (Map*) 0; m = m->next )
! 302: if ( m->addr == addr )
! 303: break;
! 304: #endif
! 305: if ( m == (Map*) 0 )
! 306: syslog( LOG_ERR, "mmc_unmap failed to find entry!" );
! 307: else if ( m->refcount <= 0 )
! 308: syslog( LOG_ERR, "mmc_unmap found zero or negative refcount!" );
! 309: else
! 310: {
! 311: --m->refcount;
! 312: if ( nowP != (struct timeval*) 0 )
! 313: m->reftime = nowP->tv_sec;
! 314: else
! 315: m->reftime = time( (time_t*) 0 );
! 316: }
! 317: }
! 318:
! 319:
! 320: void
! 321: mmc_cleanup( struct timeval* nowP )
! 322: {
! 323: time_t now;
! 324: Map** mm;
! 325: Map* m;
! 326:
! 327: /* Get the current time, if necessary. */
! 328: if ( nowP != (struct timeval*) 0 )
! 329: now = nowP->tv_sec;
! 330: else
! 331: now = time( (time_t*) 0 );
! 332:
! 333: /* Really unmap any unreferenced entries older than the age limit. */
! 334: for ( mm = &maps; *mm != (Map*) 0; )
! 335: {
! 336: m = *mm;
! 337: if ( m->refcount == 0 && now - m->reftime >= expire_age )
! 338: really_unmap( mm );
! 339: else
! 340: mm = &(*mm)->next;
! 341: }
! 342:
! 343: /* Adjust the age limit if there are too many bytes mapped, or
! 344: ** too many or too few files mapped.
! 345: */
! 346: if ( mapped_bytes > DESIRED_MAX_MAPPED_BYTES )
! 347: expire_age = MAX( ( expire_age * 2 ) / 3, DEFAULT_EXPIRE_AGE / 10 );
! 348: else if ( map_count > DESIRED_MAX_MAPPED_FILES )
! 349: expire_age = MAX( ( expire_age * 2 ) / 3, DEFAULT_EXPIRE_AGE / 10 );
! 350: else if ( map_count < DESIRED_MAX_MAPPED_FILES / 2 )
! 351: expire_age = MIN( ( expire_age * 5 ) / 4, DEFAULT_EXPIRE_AGE * 3 );
! 352:
! 353: /* Really free excess blocks on the free list. */
! 354: while ( free_count > DESIRED_FREE_COUNT )
! 355: {
! 356: m = free_maps;
! 357: free_maps = m->next;
! 358: --free_count;
! 359: free( (void*) m );
! 360: --alloc_count;
! 361: }
! 362: }
! 363:
! 364:
! 365: static void
! 366: panic( void )
! 367: {
! 368: Map** mm;
! 369: Map* m;
! 370:
! 371: syslog( LOG_ERR, "mmc panic - freeing all unreferenced maps" );
! 372:
! 373: /* Really unmap all unreferenced entries. */
! 374: for ( mm = &maps; *mm != (Map*) 0; )
! 375: {
! 376: m = *mm;
! 377: if ( m->refcount == 0 )
! 378: really_unmap( mm );
! 379: else
! 380: mm = &(*mm)->next;
! 381: }
! 382: }
! 383:
! 384:
! 385: static void
! 386: really_unmap( Map** mm )
! 387: {
! 388: Map* m;
! 389:
! 390: m = *mm;
! 391: if ( m->size != 0 )
! 392: {
! 393: #ifdef USE_SENDFILE
! 394: close(m->fd);
! 395: #elif defined(HAVE_MMAP)
! 396: if ( munmap( m->addr, m->size ) < 0 )
! 397: syslog( LOG_ERR, "munmap - %m" );
! 398: #else /* HAVE_MMAP */
! 399: free( (void*) m->addr );
! 400: #endif /* HAVE_MMAP */
! 401: }
! 402: /* Update the total byte count. */
! 403: mapped_bytes -= m->size;
! 404: /* And move the Map to the free list. */
! 405: *mm = m->next;
! 406: --map_count;
! 407: m->next = free_maps;
! 408: free_maps = m;
! 409: ++free_count;
! 410: /* This will sometimes break hash chains, but that's harmless; the
! 411: ** unmapping code that searches the hash table knows to keep searching.
! 412: */
! 413: hash_table[m->hash_idx] = (Map*) 0;
! 414: }
! 415:
! 416:
! 417: void
! 418: mmc_destroy( void )
! 419: {
! 420: Map* m;
! 421:
! 422: while ( maps != (Map*) 0 )
! 423: really_unmap( &maps );
! 424: while ( free_maps != (Map*) 0 )
! 425: {
! 426: m = free_maps;
! 427: free_maps = m->next;
! 428: --free_count;
! 429: free( (void*) m );
! 430: --alloc_count;
! 431: }
! 432: }
! 433:
! 434:
! 435: /* Make sure the hash table is big enough. */
! 436: static int
! 437: check_hash_size( void )
! 438: {
! 439: int i;
! 440: Map* m;
! 441:
! 442: /* Are we just starting out? */
! 443: if ( hash_table == (Map**) 0 )
! 444: {
! 445: hash_size = INITIAL_HASH_SIZE;
! 446: hash_mask = hash_size - 1;
! 447: }
! 448: /* Is it at least three times bigger than the number of entries? */
! 449: else if ( hash_size >= map_count * 3 )
! 450: return 0;
! 451: else
! 452: {
! 453: /* No, got to expand. */
! 454: free( (void*) hash_table );
! 455: /* Double the hash size until it's big enough. */
! 456: do
! 457: {
! 458: hash_size = hash_size << 1;
! 459: }
! 460: while ( hash_size < map_count * 6 );
! 461: hash_mask = hash_size - 1;
! 462: }
! 463: /* Make the new table. */
! 464: hash_table = (Map**) malloc( hash_size * sizeof(Map*) );
! 465: if ( hash_table == (Map**) 0 )
! 466: return -1;
! 467: /* Clear it. */
! 468: for ( i = 0; i < hash_size; ++i )
! 469: hash_table[i] = (Map*) 0;
! 470: /* And rehash all entries. */
! 471: for ( m = maps; m != (Map*) 0; m = m->next )
! 472: if ( add_hash( m ) < 0 )
! 473: return -1;
! 474: return 0;
! 475: }
! 476:
! 477:
! 478: static int
! 479: add_hash( Map* m )
! 480: {
! 481: unsigned int h, he, i;
! 482:
! 483: h = hash( m->ino, m->dev, m->size, m->ctime );
! 484: he = ( h + hash_size - 1 ) & hash_mask;
! 485: for ( i = h; ; i = ( i + 1 ) & hash_mask )
! 486: {
! 487: if ( hash_table[i] == (Map*) 0 )
! 488: {
! 489: hash_table[i] = m;
! 490: m->hash = h;
! 491: m->hash_idx = i;
! 492: return 0;
! 493: }
! 494: if ( i == he )
! 495: break;
! 496: }
! 497: return -1;
! 498: }
! 499:
! 500:
! 501: static Map*
! 502: find_hash( ino_t ino, dev_t dev, off_t size, time_t ctime )
! 503: {
! 504: unsigned int h, he, i;
! 505: Map* m;
! 506:
! 507: h = hash( ino, dev, size, ctime );
! 508: he = ( h + hash_size - 1 ) & hash_mask;
! 509: for ( i = h; ; i = ( i + 1 ) & hash_mask )
! 510: {
! 511: m = hash_table[i];
! 512: if ( m == (Map*) 0 )
! 513: break;
! 514: if ( m->hash == h && m->ino == ino && m->dev == dev &&
! 515: m->size == size && m->ctime == ctime )
! 516: return m;
! 517: if ( i == he )
! 518: break;
! 519: }
! 520: return (Map*) 0;
! 521: }
! 522:
! 523:
! 524: static unsigned int
! 525: hash( ino_t ino, dev_t dev, off_t size, time_t ctime )
! 526: {
! 527: unsigned int h = 177573;
! 528:
! 529: h ^= ino;
! 530: h += h << 5;
! 531: h ^= dev;
! 532: h += h << 5;
! 533: h ^= size;
! 534: h += h << 5;
! 535: h ^= ctime;
! 536:
! 537: return h & hash_mask;
! 538: }
! 539:
! 540:
! 541: /* Generate debugging statistics syslog message. */
! 542: void
! 543: mmc_logstats( long secs )
! 544: {
! 545: syslog(
! 546: LOG_INFO, " map cache - %d allocated, %d active (%lld bytes), %d free; hash size: %d; expire age: %ld",
! 547: alloc_count, map_count, (int64_t) mapped_bytes, free_count, hash_size,
! 548: expire_age );
! 549: if ( map_count + free_count != alloc_count )
! 550: syslog( LOG_ERR, "map counts don't add up!" );
! 551: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>