root/mmc.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. mmc_map
  2. mmc_unmap
  3. mmc_cleanup
  4. panic
  5. really_unmap
  6. mmc_term
  7. check_hash_size
  8. add_hash
  9. find_hash
  10. hash
  11. 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     }

/* [previous][next][first][last][top][bottom][index][help] */