]>
git.gir.st - ttxd.git/blob - src/thttpd-2.27/mmc.c
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: %ld",
527 alloc_count
, map_count
, (long long) mapped_bytes
, free_count
, hash_size
,
529 if ( map_count
+ free_count
!= alloc_count
)
530 syslog( LOG_ERR
, "map counts don't add up!" );