libclamav/fmap.c
cdf2a0ce
 /*
34c71837
  *  Copyright (C) 2009 Sourcefire, Inc.
cdf2a0ce
  *
  *  Authors: aCaB <acab@clamav.net>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
  */
 
 /* an mmap "replacement" which doesn't suck */
 
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include <sys/types.h>
 #include <sys/stat.h>
34c71837
 #include <string.h>
517f7b7a
 #ifdef HAVE_UNISTD_H
cdf2a0ce
 #include <unistd.h>
517f7b7a
 #endif
4b4d7846
 #ifdef ANONYMOUS_MAP
478fb3ff
 #ifdef HAVE_SYS_MMAN_H
c124d71a
 #include <sys/mman.h>
478fb3ff
 #endif
 #endif
3e3b587e
 #include <errno.h>
cdf2a0ce
 
27aa881b
 #ifdef C_LINUX
5a078ca9
 #include <pthread.h>
27aa881b
 #endif
5a078ca9
 
cdf2a0ce
 #include "others.h"
db84a7e2
 #include "cltypes.h"
 
b0fc218f
 static inline unsigned int fmap_align_items(unsigned int sz, unsigned int al);
 static inline unsigned int fmap_align_to(unsigned int sz, unsigned int al);
 static inline unsigned int fmap_which_page(fmap_t *m, size_t at);
8618abc1
 
b0fc218f
 #ifndef _WIN32
8618abc1
 /* vvvvv POSIX STUFF BELOW vvvvv */
 
2a6cd5b0
 #define FM_MASK_COUNT 0x3fffffff
 #define FM_MASK_PAGED 0x40000000
 #define FM_MASK_SEEN 0x80000000
 #define FM_MASK_LOCKED FM_MASK_SEEN
 /* 2 high bits:
 00 - not seen - not paged - N/A
 01 -    N/A   -   paged   - not locked
 10 -   seen   - not paged - N/A
 11 -    N/A   -   paged   - locked
 */
 
f52b5724
 /* FIXME: tune this stuff */
9d2a56bc
 #define UNPAGE_THRSHLD_LO 4*1024*1024
 #define UNPAGE_THRSHLD_HI 8*1024*1024
5a078ca9
 #define READAHEAD_PAGES 8
 
4b4d7846
 #if defined(ANONYMOUS_MAP) && defined(C_LINUX) && defined(CL_THREAD_SAFE)
27aa881b
 /*
    WORKAROUND
    Relieve some stress on mmap_sem.
    When mmap_sem is heavily hammered, the scheduler
    tends to fail to wake us up properly.
 */
5a078ca9
 pthread_mutex_t fmap_mutex = PTHREAD_MUTEX_INITIALIZER;
27aa881b
 #define fmap_lock pthread_mutex_lock(&fmap_mutex)
 #define fmap_unlock pthread_mutex_unlock(&fmap_mutex);
 #else
 #define fmap_lock
 #define fmap_unlock
 #endif
 
2de5c29b
 #ifndef MADV_DONTFORK
 #define MADV_DONTFORK 0
 #endif
 
cf069964
 #define fmap_bitmap (&m->placeholder_for_bitmap)
dd48b9ee
 
27aa881b
 /* pread proto here in order to avoid the use of XOPEN and BSD_SOURCE
    which may in turn prevent some mmap constants to be defined */
 ssize_t pread(int fd, void *buf, size_t count, off_t offset);
cdf2a0ce
 
a0c38197
 
 fmap_t *fmap_check_empty(int fd, off_t offset, size_t len, int *empty) {
047e50be
     unsigned int pages, mapsz, hdrsz;
     unsigned short dumb = 1;
cdf2a0ce
     int pgsz = cli_getpagesize();
     struct stat st;
49cc1e3c
     fmap_t *m;
cdf2a0ce
 
a0c38197
     *empty = 0;
cdf2a0ce
     if(fstat(fd, &st)) {
 	cli_warnmsg("fmap: fstat failed\n");
 	return NULL;
     }
34c71837
     if(offset < 0 || offset != fmap_align_to(offset, pgsz)) {
cdf2a0ce
 	cli_warnmsg("fmap: attempted mapping with unaligned offset\n");
 	return NULL;
     }
     if(!len) len = st.st_size - offset; /* bound checked later */
     if(!len) {
a0c38197
 	cli_dbgmsg("fmap: attempted void mapping\n");
 	*empty = 1;
cdf2a0ce
 	return NULL;
     }
     if(!CLI_ISCONTAINED(0, st.st_size, offset, len)) {
 	cli_warnmsg("fmap: attempted oof mapping\n");
 	return NULL;
     }
8618abc1
 
cdf2a0ce
     pages = fmap_align_items(len, pgsz);
cf069964
     hdrsz = fmap_align_to(sizeof(fmap_t) + (pages-1) * sizeof(uint32_t), pgsz); /* fmap_t includes 1 bitmap slot, hence (pages-1) */
cdf2a0ce
     mapsz = pages * pgsz + hdrsz;
27aa881b
     fmap_lock;
4b4d7846
 #ifdef ANONYMOUS_MAP
49cc1e3c
     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) {
d92f955f
 	m = NULL;
     } else {
 	dumb = 0;
2de5c29b
 #if HAVE_MADVISE
517f7b7a
 	madvise((void *)m, mapsz, MADV_RANDOM|MADV_DONTFORK);
2de5c29b
 #endif /* madvise */
d92f955f
     }
4b4d7846
 #else /* ! ANONYMOUS_MAP */
49cc1e3c
     m = (fmap_t *)cli_malloc(mapsz);
4b4d7846
 #endif /* ANONYMOUS_MAP */
478fb3ff
     if(!m) {
 	cli_warnmsg("fmap: map allocation failed\n");
27aa881b
 	fmap_unlock;
cdf2a0ce
 	return NULL;
     }
d92f955f
     /* fault the header while we still have the lock - we DO context switch here a lot here :@ */
cf069964
     memset(fmap_bitmap, 0, sizeof(uint32_t) * pages);
27aa881b
     fmap_unlock;
cdf2a0ce
     m->fd = fd;
478fb3ff
     m->dumb = dumb;
cdf2a0ce
     m->mtime = st.st_mtime;
     m->offset = offset;
     m->len = len;
     m->pages = pages;
     m->hdrsz = hdrsz;
db84a7e2
     m->pgsz = pgsz;
2a6cd5b0
     m->paged = 0;
047e50be
     m->dont_cache_flag = 0;
cdf2a0ce
     return m;
 }
db84a7e2
 
2a6cd5b0
 
49cc1e3c
 static void fmap_aging(fmap_t *m) {
4b4d7846
 #ifdef ANONYMOUS_MAP
478fb3ff
     if(m->dumb) return;
0ba0bcaf
     if(m->paged * m->pgsz > UNPAGE_THRSHLD_HI) { /* we alloc'd too much */
 	unsigned int i, avail = 0, freeme[2048], maxavail = MIN(sizeof(freeme)/sizeof(*freeme), m->paged - UNPAGE_THRSHLD_LO / m->pgsz) - 1;
 
2a6cd5b0
 	for(i=0; i<m->pages; i++) {
cf069964
 	    uint32_t s = fmap_bitmap[i];
2a6cd5b0
 	    if((s & (FM_MASK_PAGED | FM_MASK_LOCKED)) == FM_MASK_PAGED ) {
 		/* page is paged and not locked: dec age */
cf069964
 		if(s & FM_MASK_COUNT) fmap_bitmap[i]--;
2a6cd5b0
 		/* and make it available for unpaging */
0ba0bcaf
 
 		if(!avail) {
 		    freeme[0] = i;
 		    avail++;
 		} else {
 		    /* Insert sort onto a stack'd array - same performance as quickselect */
cf069964
 		    unsigned int insert_to = MIN(maxavail, avail) - 1, age = fmap_bitmap[i] & FM_MASK_COUNT;
 		    if(avail <= maxavail || (fmap_bitmap[freeme[maxavail]] & FM_MASK_COUNT) > age) {
 			while((fmap_bitmap[freeme[insert_to]] & FM_MASK_COUNT) > age) {
0ba0bcaf
 			    freeme[insert_to + 1] = freeme[insert_to];
 			    if(!insert_to--) break;
 			}
 			freeme[insert_to + 1] = i;
 			if(avail <= maxavail) avail++;
 		    }
 		}
2a6cd5b0
 	    }
 	}
 	if(avail) { /* at least one page is paged and not locked */
 	    for(i=0; i<avail; i++) {
c7f2ca7d
 		char *pptr = (char *)m + freeme[i] * m->pgsz + m->hdrsz;
2a6cd5b0
 		/* we mark the page as seen */
cf069964
 		fmap_bitmap[freeme[i]] = FM_MASK_SEEN;
2a6cd5b0
 		/* and we mmap the page over so the kernel knows there's nothing good in there */
27aa881b
 		fmap_lock;
2a6cd5b0
 		if(mmap(pptr, m->pgsz, PROT_READ | PROT_WRITE, MAP_FIXED|MAP_PRIVATE|ANONYMOUS_MAP, -1, 0) == MAP_FAILED)
098bb0ad
 		    cli_dbgmsg("fmap_aging: kernel hates you\n");
27aa881b
 		fmap_unlock;
0ba0bcaf
 	    }
 	    m->paged -= avail;
2a6cd5b0
 	}
     }
478fb3ff
 #endif
2a6cd5b0
 }
 
 
49cc1e3c
 static int fmap_readpage(fmap_t *m, unsigned int first_page, unsigned int count, unsigned int lock_count) {
3e3b587e
     size_t readsz = 0, eintr_off, got;
     char *pptr = NULL, err[256];
d92f955f
     uint32_t s;
9d2a56bc
     unsigned int i, page = first_page, force_read = 0;
 
27aa881b
     fmap_lock;
d92f955f
     for(i=0; i<count; i++) { /* prefault */
     	/* Not worth checking if the page is already paged, just ping each */
 	/* Also not worth reusing the loop below */
27aa881b
 	volatile char faultme;
 	faultme = ((char *)m)[(first_page+i) * m->pgsz + m->hdrsz];
5a078ca9
     }
27aa881b
     fmap_unlock;
9d2a56bc
     for(i=0; i<=count; i++, page++) {
 	int lock;
 	if(lock_count) {
 	    lock_count--;
 	    lock = 1;
 	} else lock = 0;
 	if(i == count) {
 	    /* we count one page too much to flush pending reads */
 	    if(!pptr) return 0; /* if we have any */
 	    force_read = 1;
cf069964
 	} else if((s=fmap_bitmap[page]) & FM_MASK_PAGED) {
9d2a56bc
 	    /* page already paged */
 	    if(lock) {
 		/* we want locking */
 		if(s & FM_MASK_LOCKED) {
 		    /* page already locked */
 		    s &= FM_MASK_COUNT;
 		    if(s == FM_MASK_COUNT) { /* lock count already at max: fial! */
 			cli_errmsg("fmap_readpage: lock count exceeded\n");
 			return 1;
 		    }
 		    /* acceptable lock count: inc lock count */
cf069964
 		    fmap_bitmap[page]++;
9d2a56bc
 		} else /* page not currently locked: set lock count = 1 */
cf069964
 		    fmap_bitmap[page] = 1 | FM_MASK_LOCKED | FM_MASK_PAGED;
9d2a56bc
 	    } else {
 		/* we don't want locking */
 		if(!(s & FM_MASK_LOCKED)) {
 		    /* page is not locked: we reset aging to max */
cf069964
 		    fmap_bitmap[page] = FM_MASK_PAGED | FM_MASK_COUNT;
1ae65c29
 		}
 	    }
9d2a56bc
 	    if(!pptr) continue;
 	    force_read = 1;
2a6cd5b0
 	}
 
9d2a56bc
 	if(force_read) {
 	    /* we have some pending reads to perform */
 	    unsigned int j;
 	    for(j=first_page; j<page; j++) {
cf069964
 		if(fmap_bitmap[j] & FM_MASK_SEEN) {
9d2a56bc
 		    /* page we've seen before: check mtime */
 		    struct stat st;
 		    if(fstat(m->fd, &st)) {
 			cli_warnmsg("fmap_readpage: fstat failed\n");
 			return 1;
 		    }
 		    if(m->mtime != st.st_mtime) {
 			cli_warnmsg("fmap_readpage: file changed as we read it\n");
 			return 1;
 		    }
 		    break;
 		}
 	    }
 
3e3b587e
 	    eintr_off = 0;
 	    while(readsz) {
 		got=pread(m->fd, pptr, readsz, eintr_off + m->offset + first_page * m->pgsz);
 
 		if(got < 0 && errno == EINTR)
 		    continue;
 
 		if(got > 0) {
 		    pptr += got;
 		    eintr_off += got;
 		    readsz -= got;
 		    continue;
 		}
 
 		if(got <0)
 		    cli_errmsg("fmap_readpage: pread error: %s\n", cli_strerror(errno, err, sizeof(err)));
 		else
 		    cli_warnmsg("fmap_readpage: pread fail: asked for %lu bytes @ offset %lu, got %lu\n", (long unsigned int)readsz, (long unsigned int)(eintr_off + m->offset + first_page * m->pgsz), (long unsigned int)got);
9d2a56bc
 		return 1;
 	    }
3e3b587e
 
9d2a56bc
 	    pptr = NULL;
6ed8f1f8
 	    force_read = 0;
9d2a56bc
 	    readsz = 0;
 	    continue;
2a6cd5b0
 	}
9d2a56bc
 
 	/* page is not already paged */
 	if(!pptr) {
 	    /* set a new start for pending reads if we don't have one */
 	    pptr = (char *)m + page * m->pgsz + m->hdrsz;
 	    first_page = page;
2a6cd5b0
 	}
9d2a56bc
 	if((page == m->pages - 1) && (m->len % m->pgsz))
 	    readsz += m->len % m->pgsz;
 	else
 	    readsz += m->pgsz;
 	if(lock) /* lock requested: set paged, lock page and set lock count to 1 */
cf069964
 	    fmap_bitmap[page] = FM_MASK_PAGED | FM_MASK_LOCKED | 1;
9d2a56bc
 	else /* no locking: set paged and set aging to max */
cf069964
 	    fmap_bitmap[page] = FM_MASK_PAGED | FM_MASK_COUNT;
9d2a56bc
 	m->paged++;
2a6cd5b0
     }
db84a7e2
     return 0;
 }
 
2a6cd5b0
 
49cc1e3c
 static void *fmap_need(fmap_t *m, size_t at, size_t len, int lock) {
9a4da6af
     unsigned int first_page, last_page, lock_count;
db84a7e2
     char *ret;
 
27aa881b
     if(!len)
db84a7e2
 	return NULL;
 
c5afd1cf
     if(!CLI_ISCONTAINED(0, m->len, at, len))
db84a7e2
 	return NULL;
f52b5724
 
     fmap_aging(m);
 
db84a7e2
     first_page = fmap_which_page(m, at);
91a7f81e
     last_page = fmap_which_page(m, at + len - 1);
9d2a56bc
     lock_count = (lock!=0) * (last_page-first_page+1);
dd48b9ee
 #ifdef READAHED_PAGES
     last_page += READAHED_PAGES;
     if(last_page >= m->pages) last_page = m->pages - 1;
 #endif
db84a7e2
 
9d2a56bc
     if(fmap_readpage(m, first_page, last_page-first_page+1, lock_count))
 	return NULL;
2a6cd5b0
 
db84a7e2
     ret = (char *)m;
     ret += at + m->hdrsz;
478fb3ff
     return (void *)ret;
db84a7e2
 }
 
49cc1e3c
 void *fmap_need_off(fmap_t *m, size_t at, size_t len) {
2a6cd5b0
     return fmap_need(m, at, len, 1);
 }
49cc1e3c
 void *fmap_need_off_once(fmap_t *m, size_t at, size_t len) {
2a6cd5b0
     return fmap_need(m, at, len, 0);
 }
49cc1e3c
 void *fmap_need_ptr(fmap_t *m, void *ptr, size_t len) {
303f7b34
     return fmap_need_off(m, (char *)ptr - (char *)m - m->hdrsz, len);
 }
49cc1e3c
 void *fmap_need_ptr_once(fmap_t *m, void *ptr, size_t len) {
2a6cd5b0
     return fmap_need_off_once(m, (char *)ptr - (char *)m - m->hdrsz, len);
 }
49cc1e3c
 void *fmap_need_str(fmap_t *m, void *ptr, size_t len_hint) {
9a4da6af
     size_t at = (char *)ptr - (char *)m - m->hdrsz;
     return fmap_need_offstr(m, at, len_hint);
303f7b34
 }
c124d71a
 
49cc1e3c
 static void fmap_unneed_page(fmap_t *m, unsigned int page) {
cf069964
     uint32_t s = fmap_bitmap[page];
f52b5724
 
2a6cd5b0
     if((s & (FM_MASK_PAGED | FM_MASK_LOCKED)) == (FM_MASK_PAGED | FM_MASK_LOCKED)) {
 	/* page is paged and locked: check lock count */
 	s &= FM_MASK_COUNT;
 	if(s > 1) /* locked more than once: dec lock count */
cf069964
 	    fmap_bitmap[page]--;
2a6cd5b0
 	else if (s == 1) /* only one lock left: unlock and begin aging */
cf069964
 	    fmap_bitmap[page] = FM_MASK_COUNT | FM_MASK_PAGED;
2a6cd5b0
 	else 
 	    cli_errmsg("fmap_unneed: inconsistent map state\n");
 	return;
     }
     cli_warnmsg("fmap_unneed: unneed on a unlocked page\n");
     return;
 }
 
49cc1e3c
 void fmap_unneed_off(fmap_t *m, size_t at, size_t len) {
1ae65c29
     unsigned int i, first_page, last_page;
478fb3ff
     if(m->dumb) return;
1ae65c29
     if(!len) {
 	cli_warnmsg("fmap_unneed: attempted void unneed\n");
 	return;
     }
 
     if(!CLI_ISCONTAINED(0, m->len, at, len)) {
9a4da6af
 	cli_warnmsg("fmap: attempted oof unneed\n");
1ae65c29
 	return;
     }
f52b5724
 
1ae65c29
     first_page = fmap_which_page(m, at);
     last_page = fmap_which_page(m, at + len - 1);
 
     for(i=first_page; i<=last_page; i++) {
 	fmap_unneed_page(m, i);
     }
 }
 
49cc1e3c
 void fmap_unneed_ptr(fmap_t *m, void *ptr, size_t len) {
9a4da6af
     fmap_unneed_off(m, (char *)ptr - (char *)m - m->hdrsz, len);
1ae65c29
 }
 
49cc1e3c
 void funmap(fmap_t *m) {
4b4d7846
 #ifdef ANONYMOUS_MAP
478fb3ff
     if(!m->dumb) {
 	size_t len = m->pages * m->pgsz + m->hdrsz;
27aa881b
 	fmap_lock;
478fb3ff
 	munmap((void *)m, len);
27aa881b
 	fmap_unlock;
478fb3ff
     } else
 #endif
 	free((void *)m);
c124d71a
 }
9a4da6af
 
49cc1e3c
 void *fmap_need_offstr(fmap_t *m, size_t at, size_t len_hint) {
9a4da6af
     unsigned int i, first_page, last_page;
478fb3ff
     void *ptr = (void *)((char *)m + m->hdrsz + at);
9a4da6af
 
     if(!len_hint || len_hint > m->len - at)
 	len_hint = m->len - at;
 
775aee7d
     if(!CLI_ISCONTAINED(0, m->len, at, len_hint))
9a4da6af
 	return NULL;
 
     fmap_aging(m);
 
     first_page = fmap_which_page(m, at);
     last_page = fmap_which_page(m, at + len_hint - 1);
 
     for(i=first_page; i<=last_page; i++) {
 	char *thispage = (char *)m + m->hdrsz + i * m->pgsz;
 	unsigned int scanat, scansz;
 
 	if(fmap_readpage(m, i, 1, 1)) {
 	    last_page = i-1;
 	    break;
 	}
 	if(i == first_page) {
 	    scanat = at % m->pgsz;
5a078ca9
 	    scansz = MIN(len_hint, m->pgsz - scanat);
9a4da6af
 	} else {
 	    scanat = 0;
5a078ca9
 	    scansz = MIN(len_hint, m->pgsz);
9a4da6af
 	}
5a078ca9
 	len_hint -= scansz;
9a4da6af
 	if(memchr(&thispage[scanat], 0, scansz))
 	    return ptr;
     }
     for(i=first_page; i<=last_page; i++)
 	fmap_unneed_page(m, i);
     return NULL;
 }
5a078ca9
 
 
49cc1e3c
 void *fmap_gets(fmap_t *m, char *dst, size_t *at, size_t max_len) {
5a078ca9
     unsigned int i, first_page, last_page;
     char *src = (void *)((char *)m + m->hdrsz + *at), *endptr = NULL;
     size_t len = MIN(max_len-1, m->len - *at), fullen = len;
 
775aee7d
     if(!len || !CLI_ISCONTAINED(0, m->len, *at, len))
5a078ca9
 	return NULL;
 
     fmap_aging(m);
 
     first_page = fmap_which_page(m, *at);
     last_page = fmap_which_page(m, *at + len - 1);
 
     for(i=first_page; i<=last_page; i++) {
 	char *thispage = (char *)m + m->hdrsz + i * m->pgsz;
 	unsigned int scanat, scansz;
 
 	if(fmap_readpage(m, i, 1, 0))
 	    return NULL;
 
 	if(i == first_page) {
 	    scanat = *at % m->pgsz;
 	    scansz = MIN(len, m->pgsz - scanat);
 	} else {
 	    scanat = 0;
 	    scansz = MIN(len, m->pgsz);
 	}
 	len -= scansz;
 
 	if((endptr = memchr(&thispage[scanat], '\n', scansz))) {
 	    endptr++;
 	    break;
 	}
     }
     if(endptr) {
 	memcpy(dst, src, endptr - src);
 	dst[endptr - src] = '\0';
 	*at += endptr - src;
     } else {
 	memcpy(dst, src, fullen);
 	dst[fullen] = '\0';
 	*at += fullen;
     }
     return dst;
 }
8618abc1
 
 /* ^^^^^ POSIX STUFF AVOVE ^^^^^ */
 
b0fc218f
 #else /* _WIN32 */
8618abc1
 
 /* vvvvv WIN32 STUFF BELOW vvvvv */
 
320157e8
 fmap_t *fmap_check_empty(int fd, off_t offset, size_t len, int *empty) { /* WIN32 */
047e50be
     unsigned int pages, mapsz, hdrsz;
     unsigned short dumb = 1;
8618abc1
     int pgsz = cli_getpagesize();
     struct stat st;
     fmap_t *m;
 
320157e8
     *empty = 0;
8618abc1
     if(fstat(fd, &st)) {
 	cli_warnmsg("fmap: fstat failed\n");
 	return NULL;
     }
     if(offset < 0 || offset != fmap_align_to(offset, pgsz)) {
 	cli_warnmsg("fmap: attempted mapping with unaligned offset\n");
 	return NULL;
     }
     if(!len) len = st.st_size - offset; /* bound checked later */
     if(!len) {
320157e8
 	cli_dbgmsg("fmap: attempted void mapping\n");
 	*empty = 1;
8618abc1
 	return NULL;
     }
     if(!CLI_ISCONTAINED(0, st.st_size, offset, len)) {
 	cli_warnmsg("fmap: attempted oof mapping\n");
 	return NULL;
     }
b0fc218f
 
     pages = fmap_align_items(len, pgsz);
775aee7d
     hdrsz = fmap_align_to(sizeof(fmap_t), pgsz);
b0fc218f
 
8618abc1
     if(!(m = (fmap_t *)cli_malloc(sizeof(fmap_t)))) {
 	cli_errmsg("fmap: canot allocate fmap_t\n", fd);
 	return NULL;
     }
b0fc218f
     if((m->fh = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE) {
8618abc1
 	cli_errmsg("fmap: cannot get a valid handle for descriptor %d\n", fd);
 	free(m);
 	return NULL;
     }
775aee7d
     if(!(m->mh = CreateFileMapping(m->fh, NULL, PAGE_READONLY, (DWORD)((len>>31)>>1), (DWORD)len, NULL))) {
8618abc1
 	cli_errmsg("fmap: cannot create a map of descriptor %d\n", fd);
 	free(m);
 	return NULL;
     }
775aee7d
     if(!(m->data = MapViewOfFile(m->mh, FILE_MAP_READ, (DWORD)((offset>>31)>>1), (DWORD)(offset), len))) {
8618abc1
 	cli_errmsg("fmap: cannot map file descriptor %d\n", fd);
 	CloseHandle(m->mh);
 	free(m);
 	return NULL;
     }
     m->fd = fd;
     m->dumb = dumb;
     m->mtime = st.st_mtime;
     m->offset = offset;
     m->len = len;
     m->pages = pages;
     m->hdrsz = hdrsz;
     m->pgsz = pgsz;
     m->paged = 0;
047e50be
     m->dont_cache_flag = 0;
8618abc1
     return m;
 }
 
 void funmap(fmap_t *m) { /* WIN32 */
     UnmapViewOfFile(m->data);
     CloseHandle(m->mh);
     free((void *)m);
 }
 
 static void *fmap_need(fmap_t *m, size_t at, size_t len) { /* WIN32 */
c5afd1cf
     if(!CLI_ISCONTAINED(0, m->len, at, len))
8618abc1
 	return NULL;
c5afd1cf
 
8618abc1
     if(!len)
 	return NULL;
     return (void *)((char *)m->data + at);
 }
 
 void *fmap_need_off(fmap_t *m, size_t at, size_t len) { /* WIN32 */
     return fmap_need(m, at, len);
 }
 void *fmap_need_off_once(fmap_t *m, size_t at, size_t len) { /* WIN32 */
     return fmap_need(m, at, len);
 }
 void *fmap_need_ptr(fmap_t *m, void *ptr, size_t len) { /* WIN32 */
     return fmap_need(m, (char *)ptr - (char *)m->data, len);
 }
 void *fmap_need_ptr_once(fmap_t *m, void *ptr, size_t len) { /* WIN32 */
     return fmap_need(m, (char *)ptr - (char *)m->data, len);
 }
 void fmap_unneed_off(fmap_t *m, size_t at, size_t len) { /* WIN32 */
 }
 void fmap_unneed_ptr(fmap_t *m, void *ptr, size_t len) { /* WIN32 */
 }
 
 void *fmap_need_offstr(fmap_t *m, size_t at, size_t len_hint) { /* WIN32 */
     char *ptr = (char *)m->data + at;
 
     if(!len_hint || len_hint > m->len - at)
 	len_hint = m->len - at;
 
775aee7d
     if(!CLI_ISCONTAINED(0, m->len, at, len_hint))
8618abc1
 	return NULL;
 
     if(memchr(ptr, 0, len_hint))
 	return (void *)ptr;
     return NULL;
 }
 
 void *fmap_need_str(fmap_t *m, void *ptr, size_t len_hint) { /* WIN32 */
     size_t at = (char *)ptr - (char *)m->data;
     return fmap_need_offstr(m, at, len_hint);
 }
 
 void *fmap_gets(fmap_t *m, char *dst, size_t *at, size_t max_len) { /* WIN32 */
     char *src = (char *)m->data + *at, *endptr = NULL;
     size_t len = MIN(max_len-1, m->len - *at);
 
775aee7d
     if(!len || !CLI_ISCONTAINED(0, m->len, *at, len))
8618abc1
 	return NULL;
 
b0fc218f
     if((endptr = memchr(src, '\n', len))) {
8618abc1
 	endptr++;
 	memcpy(dst, src, endptr - src);
 	dst[endptr - src] = '\0';
 	*at += endptr - src;
     } else {
 	memcpy(dst, src, len);
 	dst[len] = '\0';
 	*at += len;
     }
     return dst;
 }
 
b0fc218f
 #endif /* _WIN32 */
8618abc1
 
 
 /* vvvvv SHARED STUFF BELOW vvvvv */
 
320157e8
 fmap_t *fmap(int fd, off_t offset, size_t len) {
     int unused;
     return fmap_check_empty(fd, offset, len, &unused);
 }
 
8618abc1
 int fmap_readn(fmap_t *m, void *dst, size_t at, size_t len) {
     char *src;
 
     if(at > m->len)
 	return -1;
     if(len > m->len - at)
 	len = m->len - at;
     src = fmap_need_off_once(m, at, len);
     if(!src)
 	return -1;
     memcpy(dst, src, len);
     return len;
 }
 
b0fc218f
 static inline unsigned int fmap_align_items(unsigned int sz, unsigned int al) {
     return sz / al + (sz % al != 0);
 }
 
 static inline unsigned int fmap_align_to(unsigned int sz, unsigned int al) {
     return al * fmap_align_items(sz, al);
 }
 
 static inline unsigned int fmap_which_page(fmap_t *m, size_t at) {
     return at / m->pgsz;
 }