File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / thttpd / mmc.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 17:21:13 2012 UTC (12 years, 3 months ago) by misho
Branches: thttpd, MAIN
CVS tags: v2_25b, HEAD
thttpd

    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>