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>