3 ** Copyright © 1998,2001,2014 by Jef Poskanzer <jef@mail.acme.com>.
4 ** All rights reserved.
6 ** Redistribution and use in source and binary forms, with or without
7 ** modification, are permitted provided that the following conditions
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.
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
30 #include <sys/types.h>
43 #endif /* HAVE_MMAP */
49 typedef long long int64_t;
54 #ifndef DEFAULT_EXPIRE_AGE
55 #define DEFAULT_EXPIRE_AGE 600
57 #ifndef DESIRED_FREE_COUNT
58 #define DESIRED_FREE_COUNT 100
60 #ifndef DESIRED_MAX_MAPPED_FILES
61 #define DESIRED_MAX_MAPPED_FILES 2000
63 #ifndef DESIRED_MAX_MAPPED_BYTES
64 #define DESIRED_MAX_MAPPED_BYTES 1000000000
66 #ifndef INITIAL_HASH_SIZE
67 #define INITIAL_HASH_SIZE (1 << 10)
71 #define MAX(a,b) ((a)>(b)?(a):(b))
74 #define MIN(a,b) ((a)<(b)?(a):(b))
79 typedef struct MapStruct {
89 struct MapStruct* next;
94 static Map* maps = (Map*) 0;
95 static Map* free_maps = (Map*) 0;
96 static int alloc_count = 0, map_count = 0, free_count = 0;
97 static Map** hash_table = (Map**) 0;
99 static unsigned int hash_mask;
100 static time_t expire_age = DEFAULT_EXPIRE_AGE;
101 static off_t mapped_bytes = 0;
106 static void panic( void );
107 static void really_unmap( Map** mm );
108 static int check_hash_size( void );
109 static int add_hash( Map* m );
110 static Map* find_hash( ino_t ino, dev_t dev, off_t size, time_t ct );
111 static unsigned int hash( ino_t ino, dev_t dev, off_t size, time_t ct );
115 mmc_map( char* filename, struct stat* sbP, struct timeval* nowP )
122 /* Stat the file, if necessary. */
123 if ( sbP != (struct stat*) 0 )
127 if ( stat( filename, &sb ) != 0 )
129 syslog( LOG_ERR, "stat - %m" );
134 /* Get the current time, if necessary. */
135 if ( nowP != (struct timeval*) 0 )
138 now = time( (time_t*) 0 );
140 /* See if we have it mapped already, via the hash table. */
141 if ( check_hash_size() < 0 )
143 syslog( LOG_ERR, "check_hash_size() failure" );
146 m = find_hash( sb.st_ino, sb.st_dev, sb.st_size, sb.st_ctime );
149 /* Yep. Just return the existing map */
156 fd = open( filename, O_RDONLY );
159 syslog( LOG_ERR, "open - %m" );
163 /* Find a free Map entry or make a new one. */
164 if ( free_maps != (Map*) 0 )
172 m = (Map*) malloc( sizeof(Map) );
176 syslog( LOG_ERR, "out of memory allocating a Map" );
182 /* Fill in the Map entry. */
185 m->size = sb.st_size;
190 /* Avoid doing anything for zero-length files; some systems don't like
191 ** to mmap them, other systems dislike mallocing zero bytes.
194 m->addr = (void*) 1; /* arbitrary non-NULL address */
197 size_t size_size = (size_t) m->size; /* loses on files >2GB */
199 /* Map the file into memory. */
200 m->addr = mmap( 0, size_size, PROT_READ, MAP_PRIVATE, fd, 0 );
201 if ( m->addr == (void*) -1 && errno == ENOMEM )
203 /* Ooo, out of address space. Free all unreferenced maps
207 m->addr = mmap( 0, size_size, PROT_READ, MAP_PRIVATE, fd, 0 );
209 if ( m->addr == (void*) -1 )
211 syslog( LOG_ERR, "mmap - %m" );
217 #else /* HAVE_MMAP */
218 /* Read the file into memory. */
219 m->addr = (void*) malloc( size_size );
220 if ( m->addr == (void*) 0 )
222 /* Ooo, out of memory. Free all unreferenced maps
226 m->addr = (void*) malloc( size_size );
228 if ( m->addr == (void*) 0 )
230 syslog( LOG_ERR, "out of memory storing a file" );
236 if ( httpd_read_fully( fd, m->addr, size_size ) != size_size )
238 syslog( LOG_ERR, "read - %m" );
244 #endif /* HAVE_MMAP */
248 /* Put the Map into the hash table. */
249 if ( add_hash( m ) < 0 )
251 syslog( LOG_ERR, "add_hash() failure" );
257 /* Put the Map on the active list. */
262 /* Update the total byte count. */
263 mapped_bytes += m->size;
265 /* And return the address. */
271 mmc_unmap( void* addr, struct stat* sbP, struct timeval* nowP )
275 /* Find the Map entry for this address. First try a hash. */
276 if ( sbP != (struct stat*) 0 )
278 m = find_hash( sbP->st_ino, sbP->st_dev, sbP->st_size, sbP->st_ctime );
279 if ( m != (Map*) 0 && m->addr != addr )
282 /* If that didn't work, try a full search. */
284 for ( m = maps; m != (Map*) 0; m = m->next )
285 if ( m->addr == addr )
288 syslog( LOG_ERR, "mmc_unmap failed to find entry!" );
289 else if ( m->refcount <= 0 )
290 syslog( LOG_ERR, "mmc_unmap found zero or negative refcount!" );
294 if ( nowP != (struct timeval*) 0 )
295 m->reftime = nowP->tv_sec;
297 m->reftime = time( (time_t*) 0 );
303 mmc_cleanup( struct timeval* nowP )
309 /* Get the current time, if necessary. */
310 if ( nowP != (struct timeval*) 0 )
313 now = time( (time_t*) 0 );
315 /* Really unmap any unreferenced entries older than the age limit. */
316 for ( mm = &maps; *mm != (Map*) 0; )
319 if ( m->refcount == 0 && now - m->reftime >= expire_age )
325 /* Adjust the age limit if there are too many bytes mapped, or
326 ** too many or too few files mapped.
328 if ( mapped_bytes > DESIRED_MAX_MAPPED_BYTES )
329 expire_age = MAX( ( expire_age * 2 ) / 3, DEFAULT_EXPIRE_AGE / 10 );
330 else if ( map_count > DESIRED_MAX_MAPPED_FILES )
331 expire_age = MAX( ( expire_age * 2 ) / 3, DEFAULT_EXPIRE_AGE / 10 );
332 else if ( map_count < DESIRED_MAX_MAPPED_FILES / 2 )
333 expire_age = MIN( ( expire_age * 5 ) / 4, DEFAULT_EXPIRE_AGE * 3 );
335 /* Really free excess blocks on the free list. */
336 while ( free_count > DESIRED_FREE_COUNT )
353 syslog( LOG_ERR, "mmc panic - freeing all unreferenced maps" );
355 /* Really unmap all unreferenced entries. */
356 for ( mm = &maps; *mm != (Map*) 0; )
359 if ( m->refcount == 0 )
368 really_unmap( Map** mm )
376 if ( munmap( m->addr, m->size ) < 0 )
377 syslog( LOG_ERR, "munmap - %m" );
378 #else /* HAVE_MMAP */
379 free( (void*) m->addr );
380 #endif /* HAVE_MMAP */
382 /* Update the total byte count. */
383 mapped_bytes -= m->size;
384 /* And move the Map to the free list. */
390 /* This will sometimes break hash chains, but that's harmless; the
391 ** unmapping code that searches the hash table knows to keep searching.
393 hash_table[m->hash_idx] = (Map*) 0;
402 while ( maps != (Map*) 0 )
403 really_unmap( &maps );
404 while ( free_maps != (Map*) 0 )
415 /* Make sure the hash table is big enough. */
417 check_hash_size( void )
422 /* Are we just starting out? */
423 if ( hash_table == (Map**) 0 )
425 hash_size = INITIAL_HASH_SIZE;
426 hash_mask = hash_size - 1;
428 /* Is it at least three times bigger than the number of entries? */
429 else if ( hash_size >= map_count * 3 )
433 /* No, got to expand. */
434 free( (void*) hash_table );
435 /* Double the hash size until it's big enough. */
438 hash_size = hash_size << 1;
440 while ( hash_size < map_count * 6 );
441 hash_mask = hash_size - 1;
443 /* Make the new table. */
444 hash_table = (Map**) malloc( hash_size * sizeof(Map*) );
445 if ( hash_table == (Map**) 0 )
448 for ( i = 0; i < hash_size; ++i )
449 hash_table[i] = (Map*) 0;
450 /* And rehash all entries. */
451 for ( m = maps; m != (Map*) 0; m = m->next )
452 if ( add_hash( m ) < 0 )
461 unsigned int h, he, i;
463 h = hash( m->ino, m->dev, m->size, m->ct );
464 he = ( h + hash_size - 1 ) & hash_mask;
465 for ( i = h; ; i = ( i + 1 ) & hash_mask )
467 if ( hash_table[i] == (Map*) 0 )
482 find_hash( ino_t ino, dev_t dev, off_t size, time_t ct )
484 unsigned int h, he, i;
487 h = hash( ino, dev, size, ct );
488 he = ( h + hash_size - 1 ) & hash_mask;
489 for ( i = h; ; i = ( i + 1 ) & hash_mask )
494 if ( m->hash == h && m->ino == ino && m->dev == dev &&
495 m->size == size && m->ct == ct )
505 hash( ino_t ino, dev_t dev, off_t size, time_t ct )
507 unsigned int h = 177573;
517 return h & hash_mask;
521 /* Generate debugging statistics syslog message. */
523 mmc_logstats( long secs )
526 LOG_NOTICE, " map cache - %d allocated, %d active (%lld bytes), %d free; hash size: %d; expire age: %lld",
527 alloc_count, map_count, (long long) mapped_bytes, free_count, hash_size,
528 (long long) expire_age );
529 if ( map_count + free_count != alloc_count )
530 syslog( LOG_ERR, "map counts don't add up!" );