2
* Copyright (C) 2009 Sourcefire, Inc.
4
* Authors: aCaB <acab@clamav.net>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License version 2 as
8
* published by the Free Software Foundation.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21
/* an mmap "replacement" which doesn't suck */
24
#include "clamav-config.h"
27
#include <sys/types.h>
34
#ifdef HAVE_SYS_MMAN_H
46
static inline unsigned int fmap_align_items(unsigned int sz, unsigned int al);
47
static inline unsigned int fmap_align_to(unsigned int sz, unsigned int al);
48
static inline unsigned int fmap_which_page(fmap_t *m, size_t at);
51
/* vvvvv POSIX STUFF BELOW vvvvv */
53
#define FM_MASK_COUNT 0x3fffffff
54
#define FM_MASK_PAGED 0x40000000
55
#define FM_MASK_SEEN 0x80000000
56
#define FM_MASK_LOCKED FM_MASK_SEEN
58
00 - not seen - not paged - N/A
59
01 - N/A - paged - not locked
60
10 - seen - not paged - N/A
61
11 - N/A - paged - locked
64
/* FIXME: tune this stuff */
65
#define UNPAGE_THRSHLD_LO 4*1024*1024
66
#define UNPAGE_THRSHLD_HI 8*1024*1024
67
#define READAHEAD_PAGES 8
69
#if defined(HAVE_MMAP) && defined(C_LINUX) && defined(CL_THREAD_SAFE)
72
Relieve some stress on mmap_sem.
73
When mmap_sem is heavily hammered, the scheduler
74
tends to fail to wake us up properly.
76
pthread_mutex_t fmap_mutex = PTHREAD_MUTEX_INITIALIZER;
77
#define fmap_lock pthread_mutex_lock(&fmap_mutex)
78
#define fmap_unlock pthread_mutex_unlock(&fmap_mutex);
85
#define MADV_DONTFORK 0
88
#define fmap_bitmap (&m->placeholder_for_bitmap)
90
/* pread proto here in order to avoid the use of XOPEN and BSD_SOURCE
91
which may in turn prevent some mmap constants to be defined */
92
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
94
fmap_t *fmap(int fd, off_t offset, size_t len) {
95
unsigned int pages, mapsz, hdrsz;
96
unsigned short dumb = 1;
97
int pgsz = cli_getpagesize();
102
cli_warnmsg("fmap: fstat failed\n");
105
if(offset < 0 || offset != fmap_align_to(offset, pgsz)) {
106
cli_warnmsg("fmap: attempted mapping with unaligned offset\n");
109
if(!len) len = st.st_size - offset; /* bound checked later */
111
cli_warnmsg("fmap: attempted void mapping\n");
114
if(!CLI_ISCONTAINED(0, st.st_size, offset, len)) {
115
cli_warnmsg("fmap: attempted oof mapping\n");
119
pages = fmap_align_items(len, pgsz);
120
hdrsz = fmap_align_to(sizeof(fmap_t) + (pages-1) * sizeof(uint32_t), pgsz); /* fmap_t includes 1 bitmap slot, hence (pages-1) */
121
mapsz = pages * pgsz + hdrsz;
124
if ((m = (fmap_t *)mmap(NULL, mapsz, PROT_READ | PROT_WRITE, MAP_PRIVATE|/*FIXME: MAP_POPULATE is ~8% faster but more memory intensive */ANONYMOUS_MAP, -1, 0)) == MAP_FAILED) {
129
madvise((void *)m, mapsz, MADV_RANDOM|MADV_DONTFORK);
132
#else /* ! HAVE_MMAP */
133
m = (fmap_t *)cli_malloc(mapsz);
134
#endif /* HAVE_MMAP */
136
cli_warnmsg("fmap: map allocation failed\n");
140
/* fault the header while we still have the lock - we DO context switch here a lot here :@ */
141
memset(fmap_bitmap, 0, sizeof(uint32_t) * pages);
145
m->mtime = st.st_mtime;
152
m->dont_cache_flag = 0;
157
static void fmap_aging(fmap_t *m) {
160
if(m->paged * m->pgsz > UNPAGE_THRSHLD_HI) { /* we alloc'd too much */
161
unsigned int i, avail = 0, freeme[2048], maxavail = MIN(sizeof(freeme)/sizeof(*freeme), m->paged - UNPAGE_THRSHLD_LO / m->pgsz) - 1;
163
for(i=0; i<m->pages; i++) {
164
uint32_t s = fmap_bitmap[i];
165
if((s & (FM_MASK_PAGED | FM_MASK_LOCKED)) == FM_MASK_PAGED ) {
166
/* page is paged and not locked: dec age */
167
if(s & FM_MASK_COUNT) fmap_bitmap[i]--;
168
/* and make it available for unpaging */
174
/* Insert sort onto a stack'd array - same performance as quickselect */
175
unsigned int insert_to = MIN(maxavail, avail) - 1, age = fmap_bitmap[i] & FM_MASK_COUNT;
176
if(avail <= maxavail || (fmap_bitmap[freeme[maxavail]] & FM_MASK_COUNT) > age) {
177
while((fmap_bitmap[freeme[insert_to]] & FM_MASK_COUNT) > age) {
178
freeme[insert_to + 1] = freeme[insert_to];
179
if(!insert_to--) break;
181
freeme[insert_to + 1] = i;
182
if(avail <= maxavail) avail++;
187
if(avail) { /* at least one page is paged and not locked */
188
for(i=0; i<avail; i++) {
189
char *pptr = (char *)m + freeme[i] * m->pgsz + m->hdrsz;
190
/* we mark the page as seen */
191
fmap_bitmap[freeme[i]] = FM_MASK_SEEN;
192
/* and we mmap the page over so the kernel knows there's nothing good in there */
194
if(mmap(pptr, m->pgsz, PROT_READ | PROT_WRITE, MAP_FIXED|MAP_PRIVATE|ANONYMOUS_MAP, -1, 0) == MAP_FAILED)
195
cli_warnmsg("fmap_aging: kernel hates you\n");
205
static int fmap_readpage(fmap_t *m, unsigned int first_page, unsigned int count, unsigned int lock_count) {
206
size_t readsz = 0, got;
209
unsigned int i, page = first_page, force_read = 0;
212
for(i=0; i<count; i++) { /* prefault */
213
/* Not worth checking if the page is already paged, just ping each */
214
/* Also not worth reusing the loop below */
215
volatile char faultme;
216
faultme = ((char *)m)[(first_page+i) * m->pgsz + m->hdrsz];
219
for(i=0; i<=count; i++, page++) {
226
/* we count one page too much to flush pending reads */
227
if(!pptr) return 0; /* if we have any */
229
} else if((s=fmap_bitmap[page]) & FM_MASK_PAGED) {
230
/* page already paged */
232
/* we want locking */
233
if(s & FM_MASK_LOCKED) {
234
/* page already locked */
236
if(s == FM_MASK_COUNT) { /* lock count already at max: fial! */
237
cli_errmsg("fmap_readpage: lock count exceeded\n");
240
/* acceptable lock count: inc lock count */
242
} else /* page not currently locked: set lock count = 1 */
243
fmap_bitmap[page] = 1 | FM_MASK_LOCKED | FM_MASK_PAGED;
245
/* we don't want locking */
246
if(!(s & FM_MASK_LOCKED)) {
247
/* page is not locked: we reset aging to max */
248
fmap_bitmap[page] = FM_MASK_PAGED | FM_MASK_COUNT;
256
/* we have some pending reads to perform */
258
for(j=first_page; j<page; j++) {
259
if(fmap_bitmap[j] & FM_MASK_SEEN) {
260
/* page we've seen before: check mtime */
262
if(fstat(m->fd, &st)) {
263
cli_warnmsg("fmap_readpage: fstat failed\n");
266
if(m->mtime != st.st_mtime) {
267
cli_warnmsg("fmap_readpage: file changed as we read it\n");
274
if((got=pread(m->fd, pptr, readsz, m->offset + first_page * m->pgsz)) != readsz) {
275
cli_warnmsg("pread fail: page %u pages %u map-offset %lu - asked for %lu bytes, got %lu\n", first_page, m->pages, (long unsigned int)m->offset, (long unsigned int)readsz, (long unsigned int)got);
284
/* page is not already paged */
286
/* set a new start for pending reads if we don't have one */
287
pptr = (char *)m + page * m->pgsz + m->hdrsz;
290
if((page == m->pages - 1) && (m->len % m->pgsz))
291
readsz += m->len % m->pgsz;
294
if(lock) /* lock requested: set paged, lock page and set lock count to 1 */
295
fmap_bitmap[page] = FM_MASK_PAGED | FM_MASK_LOCKED | 1;
296
else /* no locking: set paged and set aging to max */
297
fmap_bitmap[page] = FM_MASK_PAGED | FM_MASK_COUNT;
304
static void *fmap_need(fmap_t *m, size_t at, size_t len, int lock) {
305
unsigned int first_page, last_page, lock_count;
311
if(!CLI_ISCONTAINED(0, m->len, at, len)) {
312
cli_dbgmsg("fmap: attempted oof need\n");
318
first_page = fmap_which_page(m, at);
319
last_page = fmap_which_page(m, at + len - 1);
320
lock_count = (lock!=0) * (last_page-first_page+1);
321
#ifdef READAHED_PAGES
322
last_page += READAHED_PAGES;
323
if(last_page >= m->pages) last_page = m->pages - 1;
326
if(fmap_readpage(m, first_page, last_page-first_page+1, lock_count))
330
ret += at + m->hdrsz;
334
void *fmap_need_off(fmap_t *m, size_t at, size_t len) {
335
return fmap_need(m, at, len, 1);
337
void *fmap_need_off_once(fmap_t *m, size_t at, size_t len) {
338
return fmap_need(m, at, len, 0);
340
void *fmap_need_ptr(fmap_t *m, void *ptr, size_t len) {
341
return fmap_need_off(m, (char *)ptr - (char *)m - m->hdrsz, len);
343
void *fmap_need_ptr_once(fmap_t *m, void *ptr, size_t len) {
344
return fmap_need_off_once(m, (char *)ptr - (char *)m - m->hdrsz, len);
346
void *fmap_need_str(fmap_t *m, void *ptr, size_t len_hint) {
347
size_t at = (char *)ptr - (char *)m - m->hdrsz;
348
return fmap_need_offstr(m, at, len_hint);
351
static void fmap_unneed_page(fmap_t *m, unsigned int page) {
352
uint32_t s = fmap_bitmap[page];
354
if((s & (FM_MASK_PAGED | FM_MASK_LOCKED)) == (FM_MASK_PAGED | FM_MASK_LOCKED)) {
355
/* page is paged and locked: check lock count */
357
if(s > 1) /* locked more than once: dec lock count */
359
else if (s == 1) /* only one lock left: unlock and begin aging */
360
fmap_bitmap[page] = FM_MASK_COUNT | FM_MASK_PAGED;
362
cli_errmsg("fmap_unneed: inconsistent map state\n");
365
cli_warnmsg("fmap_unneed: unneed on a unlocked page\n");
369
void fmap_unneed_off(fmap_t *m, size_t at, size_t len) {
370
unsigned int i, first_page, last_page;
373
cli_warnmsg("fmap_unneed: attempted void unneed\n");
377
if(!CLI_ISCONTAINED(0, m->len, at, len)) {
378
cli_warnmsg("fmap: attempted oof unneed\n");
382
first_page = fmap_which_page(m, at);
383
last_page = fmap_which_page(m, at + len - 1);
385
for(i=first_page; i<=last_page; i++) {
386
fmap_unneed_page(m, i);
390
void fmap_unneed_ptr(fmap_t *m, void *ptr, size_t len) {
391
fmap_unneed_off(m, (char *)ptr - (char *)m - m->hdrsz, len);
394
void funmap(fmap_t *m) {
397
size_t len = m->pages * m->pgsz + m->hdrsz;
399
munmap((void *)m, len);
406
void *fmap_need_offstr(fmap_t *m, size_t at, size_t len_hint) {
407
unsigned int i, first_page, last_page;
408
void *ptr = (void *)((char *)m + m->hdrsz + at);
410
if(!len_hint || len_hint > m->len - at)
411
len_hint = m->len - at;
413
if(!CLI_ISCONTAINED(0, m->len, at, len_hint))
418
first_page = fmap_which_page(m, at);
419
last_page = fmap_which_page(m, at + len_hint - 1);
421
for(i=first_page; i<=last_page; i++) {
422
char *thispage = (char *)m + m->hdrsz + i * m->pgsz;
423
unsigned int scanat, scansz;
425
if(fmap_readpage(m, i, 1, 1)) {
429
if(i == first_page) {
430
scanat = at % m->pgsz;
431
scansz = MIN(len_hint, m->pgsz - scanat);
434
scansz = MIN(len_hint, m->pgsz);
437
if(memchr(&thispage[scanat], 0, scansz))
440
for(i=first_page; i<=last_page; i++)
441
fmap_unneed_page(m, i);
446
void *fmap_gets(fmap_t *m, char *dst, size_t *at, size_t max_len) {
447
unsigned int i, first_page, last_page;
448
char *src = (void *)((char *)m + m->hdrsz + *at), *endptr = NULL;
449
size_t len = MIN(max_len-1, m->len - *at), fullen = len;
451
if(!len || !CLI_ISCONTAINED(0, m->len, *at, len))
456
first_page = fmap_which_page(m, *at);
457
last_page = fmap_which_page(m, *at + len - 1);
459
for(i=first_page; i<=last_page; i++) {
460
char *thispage = (char *)m + m->hdrsz + i * m->pgsz;
461
unsigned int scanat, scansz;
463
if(fmap_readpage(m, i, 1, 0))
466
if(i == first_page) {
467
scanat = *at % m->pgsz;
468
scansz = MIN(len, m->pgsz - scanat);
471
scansz = MIN(len, m->pgsz);
475
if((endptr = memchr(&thispage[scanat], '\n', scansz))) {
481
memcpy(dst, src, endptr - src);
482
dst[endptr - src] = '\0';
485
memcpy(dst, src, fullen);
492
/* ^^^^^ POSIX STUFF AVOVE ^^^^^ */
496
/* vvvvv WIN32 STUFF BELOW vvvvv */
498
fmap_t *fmap(int fd, off_t offset, size_t len) { /* WIN32 */
499
unsigned int pages, mapsz, hdrsz;
500
unsigned short dumb = 1;
501
int pgsz = cli_getpagesize();
506
cli_warnmsg("fmap: fstat failed\n");
509
if(offset < 0 || offset != fmap_align_to(offset, pgsz)) {
510
cli_warnmsg("fmap: attempted mapping with unaligned offset\n");
513
if(!len) len = st.st_size - offset; /* bound checked later */
515
cli_warnmsg("fmap: attempted void mapping\n");
518
if(!CLI_ISCONTAINED(0, st.st_size, offset, len)) {
519
cli_warnmsg("fmap: attempted oof mapping\n");
523
pages = fmap_align_items(len, pgsz);
524
hdrsz = fmap_align_to(sizeof(fmap_t), pgsz);
526
if(!(m = (fmap_t *)cli_malloc(sizeof(fmap_t)))) {
527
cli_errmsg("fmap: canot allocate fmap_t\n", fd);
530
if((m->fh = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE) {
531
cli_errmsg("fmap: cannot get a valid handle for descriptor %d\n", fd);
535
if(!(m->mh = CreateFileMapping(m->fh, NULL, PAGE_READONLY, (DWORD)((len>>31)>>1), (DWORD)len, NULL))) {
536
cli_errmsg("fmap: cannot create a map of descriptor %d\n", fd);
540
if(!(m->data = MapViewOfFile(m->mh, FILE_MAP_READ, (DWORD)((offset>>31)>>1), (DWORD)(offset), len))) {
541
cli_errmsg("fmap: cannot map file descriptor %d\n", fd);
548
m->mtime = st.st_mtime;
555
m->dont_cache_flag = 0;
559
void funmap(fmap_t *m) { /* WIN32 */
560
UnmapViewOfFile(m->data);
565
static void *fmap_need(fmap_t *m, size_t at, size_t len) { /* WIN32 */
566
if(!CLI_ISCONTAINED(0, m->len, at, len)) {
567
cli_dbgmsg("fmap: attempted oof need\n");
572
return (void *)((char *)m->data + at);
575
void *fmap_need_off(fmap_t *m, size_t at, size_t len) { /* WIN32 */
576
return fmap_need(m, at, len);
578
void *fmap_need_off_once(fmap_t *m, size_t at, size_t len) { /* WIN32 */
579
return fmap_need(m, at, len);
581
void *fmap_need_ptr(fmap_t *m, void *ptr, size_t len) { /* WIN32 */
582
return fmap_need(m, (char *)ptr - (char *)m->data, len);
584
void *fmap_need_ptr_once(fmap_t *m, void *ptr, size_t len) { /* WIN32 */
585
return fmap_need(m, (char *)ptr - (char *)m->data, len);
587
void fmap_unneed_off(fmap_t *m, size_t at, size_t len) { /* WIN32 */
589
void fmap_unneed_ptr(fmap_t *m, void *ptr, size_t len) { /* WIN32 */
592
void *fmap_need_offstr(fmap_t *m, size_t at, size_t len_hint) { /* WIN32 */
593
char *ptr = (char *)m->data + at;
595
if(!len_hint || len_hint > m->len - at)
596
len_hint = m->len - at;
598
if(!CLI_ISCONTAINED(0, m->len, at, len_hint))
601
if(memchr(ptr, 0, len_hint))
606
void *fmap_need_str(fmap_t *m, void *ptr, size_t len_hint) { /* WIN32 */
607
size_t at = (char *)ptr - (char *)m->data;
608
return fmap_need_offstr(m, at, len_hint);
611
void *fmap_gets(fmap_t *m, char *dst, size_t *at, size_t max_len) { /* WIN32 */
612
char *src = (char *)m->data + *at, *endptr = NULL;
613
size_t len = MIN(max_len-1, m->len - *at);
615
if(!len || !CLI_ISCONTAINED(0, m->len, *at, len))
618
if((endptr = memchr(src, '\n', len))) {
620
memcpy(dst, src, endptr - src);
621
dst[endptr - src] = '\0';
624
memcpy(dst, src, len);
634
/* vvvvv SHARED STUFF BELOW vvvvv */
636
int fmap_readn(fmap_t *m, void *dst, size_t at, size_t len) {
641
if(len > m->len - at)
643
src = fmap_need_off_once(m, at, len);
646
memcpy(dst, src, len);
650
static inline unsigned int fmap_align_items(unsigned int sz, unsigned int al) {
651
return sz / al + (sz % al != 0);
654
static inline unsigned int fmap_align_to(unsigned int sz, unsigned int al) {
655
return al * fmap_align_items(sz, al);
658
static inline unsigned int fmap_which_page(fmap_t *m, size_t at) {