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>