root/mmc.c
/*DEFINITIONS
This source file includes following definitions.- mmc_map
- mmc_unmap
- mmc_cleanup
- panic
- really_unmap
- mmc_term
- check_hash_size
- add_hash
- find_hash
- hash
- mmc_logstats
1 /* mmc.c - mmap cache 2 ** 3 ** Copyright (c) 1998,2001,2014 by Jef Poskanzer <jef@mail.acme.com>. 4 ** Copyright (c) 2023 by Amelia Zabardast Ziabari <ame@psianesia.org>. 5 ** All rights reserved. 6 ** 7 ** Redistribution and use in source and binary forms, with or without 8 ** modification, are permitted provided that the following conditions 9 ** are met: 10 ** 1. Redistributions of source code must retain the above copyright 11 ** notice, this list of conditions and the following disclaimer. 12 ** 2. Redistributions in binary form must reproduce the above copyright 13 ** notice, this list of conditions and the following disclaimer in the 14 ** documentation and/or other materials provided with the distribution. 15 ** 16 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 ** SUCH DAMAGE. 27 */ 28 29 #include "config.h" 30 31 #include <sys/types.h> 32 #include <sys/stat.h> 33 #include <sys/time.h> 34 #include <stdlib.h> 35 #include <unistd.h> 36 #include <stdio.h> 37 #include <time.h> 38 #include <fcntl.h> 39 #include <syslog.h> 40 #include <errno.h> 41 42 #ifdef HAVE_MMAP 43 #include <sys/mman.h> 44 #endif /* HAVE_MMAP */ 45 46 #include "mmc.h" 47 #include "libhttpd.h" 48 49 #ifndef HAVE_INT64T 50 typedef long long int64_t; 51 #endif 52 53 54 /* Defines. */ 55 #ifndef DEFAULT_EXPIRE_AGE 56 #define DEFAULT_EXPIRE_AGE 600 57 #endif 58 #ifndef DESIRED_FREE_COUNT 59 #define DESIRED_FREE_COUNT 100 60 #endif 61 #ifndef DESIRED_MAX_MAPPED_FILES 62 #define DESIRED_MAX_MAPPED_FILES 2000 63 #endif 64 #ifndef DESIRED_MAX_MAPPED_BYTES 65 #define DESIRED_MAX_MAPPED_BYTES 1000000000 66 #endif 67 #ifndef INITIAL_HASH_SIZE 68 #define INITIAL_HASH_SIZE (1 << 10) 69 #endif 70 71 #ifndef MAX 72 #define MAX(a,b) ((a)>(b)?(a):(b)) 73 #endif 74 #ifndef MIN 75 #define MIN(a,b) ((a)<(b)?(a):(b)) 76 #endif 77 78 79 /* The Map struct. */ 80 typedef struct MapStruct { 81 ino_t ino; 82 dev_t dev; 83 off_t size; 84 time_t ct; 85 int refcount; 86 time_t reftime; 87 void* addr; 88 unsigned int hash; 89 int hash_idx; 90 struct MapStruct* next; 91 } Map; 92 93 94 /* Globals. */ 95 static Map* maps = (Map*) 0; 96 static Map* free_maps = (Map*) 0; 97 static int alloc_count = 0, map_count = 0, free_count = 0; 98 static Map** hash_table = (Map**) 0; 99 static int hash_size; 100 static unsigned int hash_mask; 101 static time_t expire_age = DEFAULT_EXPIRE_AGE; 102 static off_t mapped_bytes = 0; 103 104 105 106 /* Forwards. */ 107 static void panic( void ); 108 static void really_unmap( Map** mm ); 109 static int check_hash_size( void ); 110 static int add_hash( Map* m ); 111 static Map* find_hash( ino_t ino, dev_t dev, off_t size, time_t ct ); 112 static unsigned int hash( ino_t ino, dev_t dev, off_t size, time_t ct ); 113 114 115 void* 116 mmc_map( char* filename, struct stat* sbP, struct timeval* nowP ) 117 { 118 time_t now; 119 struct stat sb; 120 Map* m; 121 int fd; 122 123 /* Stat the file, if necessary. */ 124 if ( sbP != (struct stat*) 0 ) 125 sb = *sbP; 126 else 127 { 128 if ( stat( filename, &sb ) != 0 ) 129 { 130 syslog( LOG_ERR, "stat - %m" ); 131 return (void*) 0; 132 } 133 } 134 135 /* Get the current time, if necessary. */ 136 if ( nowP != (struct timeval*) 0 ) 137 now = nowP->tv_sec; 138 else 139 now = time( (time_t*) 0 ); 140 141 /* See if we have it mapped already, via the hash table. */ 142 if ( check_hash_size() < 0 ) 143 { 144 syslog( LOG_ERR, "check_hash_size() failure" ); 145 return (void*) 0; 146 } 147 m = find_hash( sb.st_ino, sb.st_dev, sb.st_size, sb.st_ctime ); 148 if ( m != (Map*) 0 ) 149 { 150 /* Yep. Just return the existing map */ 151 ++m->refcount; 152 m->reftime = now; 153 return m->addr; 154 } 155 156 /* Open the file. */ 157 fd = open( filename, O_RDONLY | O_NONBLOCK ); 158 if ( fd < 0 ) 159 { 160 syslog( LOG_ERR, "open - %m" ); 161 return (void*) 0; 162 } 163 164 /* Find a free Map entry or make a new one. */ 165 if ( free_maps != (Map*) 0 ) 166 { 167 m = free_maps; 168 free_maps = m->next; 169 --free_count; 170 } 171 else 172 { 173 m = (Map*) malloc( sizeof(Map) ); 174 if ( m == (Map*) 0 ) 175 { 176 (void) close( fd ); 177 syslog( LOG_ERR, "out of memory allocating a Map" ); 178 return (void*) 0; 179 } 180 ++alloc_count; 181 } 182 183 /* Fill in the Map entry. */ 184 m->ino = sb.st_ino; 185 m->dev = sb.st_dev; 186 m->size = sb.st_size; 187 m->ct = sb.st_ctime; 188 m->refcount = 1; 189 m->reftime = now; 190 191 /* Avoid doing anything for zero-length files; some systems don't like 192 ** to mmap them, other systems dislike mallocing zero bytes. 193 */ 194 if ( m->size == 0 ) 195 m->addr = (void*) 1; /* arbitrary non-NULL address */ 196 else 197 { 198 size_t size_size = (size_t) m->size; /* loses on files >2GB */ 199 #ifdef HAVE_MMAP 200 /* Map the file into memory. */ 201 m->addr = mmap( 0, size_size, PROT_READ, MAP_PRIVATE, fd, 0 ); 202 if ( m->addr == (void*) -1 && errno == ENOMEM ) 203 { 204 /* Ooo, out of address space. Free all unreferenced maps 205 ** and try again. 206 */ 207 panic(); 208 m->addr = mmap( 0, size_size, PROT_READ, MAP_PRIVATE, fd, 0 ); 209 } 210 if ( m->addr == (void*) -1 ) 211 { 212 syslog( LOG_ERR, "mmap - %m" ); 213 (void) close( fd ); 214 free(m); 215 --alloc_count; 216 return (void*) 0; 217 } 218 #else /* HAVE_MMAP */ 219 /* Read the file into memory. */ 220 m->addr = (void*) malloc( size_size ); 221 if ( m->addr == (void*) 0 ) 222 { 223 /* Ooo, out of memory. Free all unreferenced maps 224 ** and try again. 225 */ 226 panic(); 227 m->addr = (void*) malloc( size_size ); 228 } 229 if ( m->addr == (void*) 0 ) 230 { 231 syslog( LOG_ERR, "out of memory storing a file" ); 232 (void) close( fd ); 233 free(m); 234 --alloc_count; 235 return (void*) 0; 236 } 237 if ( httpd_read_fully( fd, m->addr, size_size ) != m->size ) 238 { 239 syslog( LOG_ERR, "read - %m" ); 240 (void) close( fd ); 241 free(m->addr); 242 free(m); 243 --alloc_count; 244 return (void*) 0; 245 } 246 #endif /* HAVE_MMAP */ 247 } 248 (void) close( fd ); 249 250 /* Put the Map into the hash table. */ 251 if ( add_hash( m ) < 0 ) 252 { 253 syslog( LOG_ERR, "add_hash() failure" ); 254 #ifndef HAVE_MMAP 255 free(m->addr); 256 #endif 257 free(m); 258 --alloc_count; 259 return (void*) 0; 260 } 261 262 /* Put the Map on the active list. */ 263 m->next = maps; 264 maps = m; 265 ++map_count; 266 267 /* Update the total byte count. */ 268 mapped_bytes += m->size; 269 270 /* And return the address. */ 271 return m->addr; 272 } 273 274 275 void 276 mmc_unmap( void* addr, struct stat* sbP, struct timeval* nowP ) 277 { 278 Map* m = (Map*) 0; 279 280 /* Find the Map entry for this address. First try a hash. */ 281 if ( sbP != (struct stat*) 0 ) 282 { 283 m = find_hash( sbP->st_ino, sbP->st_dev, sbP->st_size, sbP->st_ctime ); 284 if ( m != (Map*) 0 && m->addr != addr ) 285 m = (Map*) 0; 286 } 287 /* If that didn't work, try a full search. */ 288 if ( m == (Map*) 0 ) 289 for ( m = maps; m != (Map*) 0; m = m->next ) 290 if ( m->addr == addr ) 291 break; 292 if ( m == (Map*) 0 ) 293 syslog( LOG_ERR, "mmc_unmap failed to find entry!" ); 294 else if ( m->refcount <= 0 ) 295 syslog( LOG_ERR, "mmc_unmap found zero or negative refcount!" ); 296 else 297 { 298 --m->refcount; 299 if ( nowP != (struct timeval*) 0 ) 300 m->reftime = nowP->tv_sec; 301 else 302 m->reftime = time( (time_t*) 0 ); 303 } 304 } 305 306 307 void 308 mmc_cleanup( struct timeval* nowP ) 309 { 310 time_t now; 311 Map** mm; 312 Map* m; 313 314 /* Get the current time, if necessary. */ 315 if ( nowP != (struct timeval*) 0 ) 316 now = nowP->tv_sec; 317 else 318 now = time( (time_t*) 0 ); 319 320 /* Really unmap any unreferenced entries older than the age limit. */ 321 for ( mm = &maps; *mm != (Map*) 0; ) 322 { 323 m = *mm; 324 if ( m->refcount == 0 && now - m->reftime >= expire_age ) 325 really_unmap( mm ); 326 else 327 mm = &(*mm)->next; 328 } 329 330 /* Adjust the age limit if there are too many bytes mapped, or 331 ** too many or too few files mapped. 332 */ 333 if ( mapped_bytes > DESIRED_MAX_MAPPED_BYTES ) 334 expire_age = MAX( ( expire_age * 2 ) / 3, DEFAULT_EXPIRE_AGE / 10 ); 335 else if ( map_count > DESIRED_MAX_MAPPED_FILES ) 336 expire_age = MAX( ( expire_age * 2 ) / 3, DEFAULT_EXPIRE_AGE / 10 ); 337 else if ( map_count < DESIRED_MAX_MAPPED_FILES / 2 ) 338 expire_age = MIN( ( expire_age * 5 ) / 4, DEFAULT_EXPIRE_AGE * 3 ); 339 340 /* Really free excess blocks on the free list. */ 341 while ( free_count > DESIRED_FREE_COUNT ) 342 { 343 m = free_maps; 344 free_maps = m->next; 345 --free_count; 346 free(m); 347 --alloc_count; 348 } 349 } 350 351 352 static void 353 panic( void ) 354 { 355 Map** mm; 356 Map* m; 357 358 syslog( LOG_ERR, "mmc panic - freeing all unreferenced maps" ); 359 360 /* Really unmap all unreferenced entries. */ 361 for ( mm = &maps; *mm != (Map*) 0; ) 362 { 363 m = *mm; 364 if ( m->refcount == 0 ) 365 really_unmap( mm ); 366 else 367 mm = &(*mm)->next; 368 } 369 } 370 371 372 static void 373 really_unmap( Map** mm ) 374 { 375 Map* m; 376 377 m = *mm; 378 if ( m->size != 0 ) 379 { 380 #ifdef HAVE_MMAP 381 if ( munmap( m->addr, m->size ) < 0 ) 382 syslog( LOG_ERR, "munmap - %m" ); 383 #else /* HAVE_MMAP */ 384 free(m->addr); 385 #endif /* HAVE_MMAP */ 386 } 387 /* Update the total byte count. */ 388 mapped_bytes -= m->size; 389 /* And move the Map to the free list. */ 390 *mm = m->next; 391 --map_count; 392 m->next = free_maps; 393 free_maps = m; 394 ++free_count; 395 /* This will sometimes break hash chains, but that's harmless; the 396 ** unmapping code that searches the hash table knows to keep searching. 397 */ 398 hash_table[m->hash_idx] = (Map*) 0; 399 } 400 401 402 void 403 mmc_term( void ) 404 { 405 Map* m; 406 407 while ( maps != (Map*) 0 ) 408 really_unmap( &maps ); 409 while ( free_maps != (Map*) 0 ) 410 { 411 m = free_maps; 412 free_maps = m->next; 413 --free_count; 414 free(m); 415 --alloc_count; 416 } 417 } 418 419 420 /* Make sure the hash table is big enough. */ 421 static int 422 check_hash_size( void ) 423 { 424 int i; 425 Map* m; 426 427 /* Are we just starting out? */ 428 if ( hash_table == (Map**) 0 ) 429 { 430 hash_size = INITIAL_HASH_SIZE; 431 hash_mask = hash_size - 1; 432 } 433 /* Is it at least three times bigger than the number of entries? */ 434 else if ( hash_size >= map_count * 3 ) 435 return 0; 436 else 437 { 438 /* No, got to expand. */ 439 free(hash_table); 440 /* Double the hash size until it's big enough. */ 441 do 442 { 443 hash_size = hash_size << 1; 444 } 445 while ( hash_size < map_count * 6 ); 446 hash_mask = hash_size - 1; 447 } 448 /* Make the new table. */ 449 hash_table = (Map**) malloc( hash_size * sizeof(Map*) ); 450 if ( hash_table == (Map**) 0 ) 451 return -1; 452 /* Clear it. */ 453 for ( i = 0; i < hash_size; ++i ) 454 hash_table[i] = (Map*) 0; 455 /* And rehash all entries. */ 456 for ( m = maps; m != (Map*) 0; m = m->next ) 457 if ( add_hash( m ) < 0 ) 458 return -1; 459 return 0; 460 } 461 462 463 static int 464 add_hash( Map* m ) 465 { 466 unsigned int h, he, i; 467 468 h = hash( m->ino, m->dev, m->size, m->ct ); 469 he = ( h + hash_size - 1 ) & hash_mask; 470 for ( i = h; ; i = ( i + 1 ) & hash_mask ) 471 { 472 if ( hash_table[i] == (Map*) 0 ) 473 { 474 hash_table[i] = m; 475 m->hash = h; 476 m->hash_idx = i; 477 return 0; 478 } 479 if ( i == he ) 480 break; 481 } 482 return -1; 483 } 484 485 486 static Map* 487 find_hash( ino_t ino, dev_t dev, off_t size, time_t ct ) 488 { 489 unsigned int h, he, i; 490 Map* m; 491 492 h = hash( ino, dev, size, ct ); 493 he = ( h + hash_size - 1 ) & hash_mask; 494 for ( i = h; ; i = ( i + 1 ) & hash_mask ) 495 { 496 m = hash_table[i]; 497 if ( m == (Map*) 0 ) 498 break; 499 if ( m->hash == h && m->ino == ino && m->dev == dev && 500 m->size == size && m->ct == ct ) 501 return m; 502 if ( i == he ) 503 break; 504 } 505 return (Map*) 0; 506 } 507 508 509 static unsigned int 510 hash( ino_t ino, dev_t dev, off_t size, time_t ct ) 511 { 512 unsigned int h = 177573; 513 514 h ^= ino; 515 h += h << 5; 516 h ^= dev; 517 h += h << 5; 518 h ^= size; 519 h += h << 5; 520 h ^= ct; 521 522 return h & hash_mask; 523 } 524 525 526 /* Generate debugging statistics syslog message. */ 527 void 528 mmc_logstats( long secs ) 529 { 530 (void) secs; /* XXX: gcc */ 531 532 syslog( 533 LOG_NOTICE, " map cache - %d allocated, %d active (%lld bytes), " 534 "%d free; hash size: %d; expire age: %lld", 535 alloc_count, map_count, (long long) mapped_bytes, free_count, hash_size, 536 (long long) expire_age ); 537 if ( map_count + free_count != alloc_count ) 538 syslog( LOG_ERR, "map counts don't add up!" ); 539 }
/*