Annotation of embedaddon/thttpd/mmc.c, revision 1.1.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>