libclamav/fmap.c
cdf2a0ce
 /*
e1cbc270
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
79942398
  *  Copyright (C) 2009-2013 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
 
01eebc13
 #include <stdint.h>
cdf2a0ce
 #include <sys/types.h>
 #include <sys/stat.h>
34c71837
 #include <string.h>
01eebc13
 #include <libgen.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
 
60d8d2c3
 #include "clamav.h"
cdf2a0ce
 #include "others.h"
01eebc13
 #include "str.h"
db84a7e2
 
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
f99c892c
 /* 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);
 
8618abc1
 /* vvvvv POSIX STUFF BELOW vvvvv */
8371b827
 static off_t pread_cb(void *handle, void *buf, size_t count, off_t offset)
 {
     return pread((int)(ssize_t)handle, buf, count, offset);
 }
 
288057e9
 fmap_t *fmap_check_empty(int fd, off_t offset, size_t len, int *empty)
 {
a2a004df
     STATBUF st;
2672a22a
     fmap_t *m;
 
     *empty = 0;
288057e9
     if (FSTAT(fd, &st)) {
         cli_warnmsg("fmap: fstat failed\n");
         return NULL;
2672a22a
     }
 
288057e9
     if (!len) len = st.st_size - offset; /* bound checked later */
     if (!len) {
         cli_dbgmsg("fmap: attempted void mapping\n");
         *empty = 1;
         return NULL;
2672a22a
     }
288057e9
     if (!CLI_ISCONTAINED(0, st.st_size, offset, len)) {
         cli_warnmsg("fmap: attempted oof mapping\n");
         return NULL;
2672a22a
     }
288057e9
     m = cl_fmap_open_handle((void *)(ssize_t)fd, offset, len, pread_cb, 1);
2672a22a
     if (!m)
288057e9
         return NULL;
     m->mtime        = st.st_mtime;
2672a22a
     m->handle_is_fd = 1;
     return m;
 }
 #else
 /* vvvvv WIN32 STUFF BELOW vvvvv */
288057e9
 static void unmap_win32(fmap_t *m)
 { /* WIN32 */
2672a22a
     UnmapViewOfFile(m->data);
     CloseHandle(m->mh);
     free((void *)m);
 }
 
288057e9
 fmap_t *fmap_check_empty(int fd, off_t offset, size_t len, int *empty)
 { /* WIN32 */
2672a22a
     unsigned int pages, mapsz, hdrsz;
     int pgsz = cli_getpagesize();
a2a004df
     STATBUF st;
2672a22a
     fmap_t *m;
     const void *data;
     HANDLE fh;
     HANDLE mh;
 
     *empty = 0;
288057e9
     if (FSTAT(fd, &st)) {
         cli_warnmsg("fmap: fstat failed\n");
         return NULL;
2672a22a
     }
288057e9
     if (offset < 0 || offset != fmap_align_to(offset, pgsz)) {
         cli_warnmsg("fmap: attempted mapping with unaligned offset\n");
         return NULL;
2672a22a
     }
288057e9
     if (!len) len = st.st_size - offset; /* bound checked later */
     if (!len) {
         cli_dbgmsg("fmap: attempted void mapping\n");
         *empty = 1;
         return NULL;
2672a22a
     }
288057e9
     if (!CLI_ISCONTAINED(0, st.st_size, offset, len)) {
         cli_warnmsg("fmap: attempted oof mapping\n");
         return NULL;
2672a22a
     }
 
     pages = fmap_align_items(len, pgsz);
     hdrsz = fmap_align_to(sizeof(fmap_t), pgsz);
 
288057e9
     if ((fh = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE) {
         cli_errmsg("fmap: cannot get a valid handle for descriptor %d\n", fd);
         return NULL;
2672a22a
     }
288057e9
     if (!(mh = CreateFileMapping(fh, NULL, PAGE_READONLY, (DWORD)((len >> 31) >> 1), (DWORD)len, NULL))) {
         cli_errmsg("fmap: cannot create a map of descriptor %d\n", fd);
         CloseHandle(fh);
         return NULL;
2672a22a
     }
288057e9
     if (!(data = MapViewOfFile(mh, FILE_MAP_READ, (DWORD)((offset >> 31) >> 1), (DWORD)(offset), len))) {
         cli_errmsg("fmap: cannot map file descriptor %d\n", fd);
         CloseHandle(mh);
         CloseHandle(fh);
         return NULL;
2672a22a
     }
288057e9
     if (!(m = cl_fmap_open_memory(data, len))) {
         cli_errmsg("fmap: cannot allocate fmap_t\n", fd);
         CloseHandle(mh);
         CloseHandle(fh);
         return NULL;
2672a22a
     }
288057e9
     m->handle       = (void *)(size_t)fd;
2672a22a
     m->handle_is_fd = 1;
288057e9
     m->fh           = fh;
     m->mh           = mh;
     m->unmap        = unmap_win32;
2672a22a
     return m;
 }
 #endif /* _WIN32 */
 
 /* vvvvv SHARED STUFF BELOW vvvvv */
8618abc1
 
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 */
288057e9
 #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
 
d2bfc86a
 static const void *handle_need(fmap_t *m, size_t at, size_t len, int lock);
 static void handle_unneed_off(fmap_t *m, size_t at, size_t len);
 static const void *handle_need_offstr(fmap_t *m, size_t at, size_t len_hint);
 static const void *handle_gets(fmap_t *m, char *dst, size_t *at, size_t max_len);
 static void unmap_mmap(fmap_t *m);
 static void unmap_malloc(fmap_t *m);
6372c0dc
 
89c638eb
 extern cl_fmap_t *cl_fmap_open_handle(void *handle, size_t offset, size_t len,
288057e9
                                       clcb_pread pread_cb, int use_aging)
6372c0dc
 {
     unsigned int pages, mapsz, hdrsz;
     cl_fmap_t *m;
     int pgsz = cli_getpagesize();
 
288057e9
     if ((off_t)offset < 0 || offset != fmap_align_to(offset, pgsz)) {
         cli_warnmsg("fmap: attempted mapping with unaligned offset\n");
         return NULL;
cdf2a0ce
     }
288057e9
     if (!len) {
         cli_dbgmsg("fmap: attempted void mapping\n");
         return NULL;
cdf2a0ce
     }
6372c0dc
     if (offset >= len) {
288057e9
         cli_warnmsg("fmap: attempted oof mapping\n");
         return NULL;
cdf2a0ce
     }
8618abc1
 
cdf2a0ce
     pages = fmap_align_items(len, pgsz);
288057e9
     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;
6372c0dc
 
 #ifndef ANONYMOUS_MAP
     use_aging = 0;
 #endif
4b4d7846
 #ifdef ANONYMOUS_MAP
6372c0dc
     if (use_aging) {
288057e9
         fmap_lock;
         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) {
             m = NULL;
         } else {
2de5c29b
 #if HAVE_MADVISE
288057e9
             madvise((void *)m, mapsz, MADV_RANDOM | MADV_DONTFORK);
2de5c29b
 #endif /* madvise */
288057e9
             /* fault the header while we still have the lock - we DO context switch here a lot here :@ */
             memset(fmap_bitmap, 0, sizeof(uint32_t) * pages);
         }
         fmap_unlock;
d92f955f
     }
4b4d7846
 #endif /* ANONYMOUS_MAP */
6372c0dc
     if (!use_aging) {
288057e9
         m = (fmap_t *)cli_malloc(mapsz);
         if (!(m)) {
             cli_warnmsg("fmap: map allocation failed\n");
             return NULL;
         }
         memset(m, 0, hdrsz);
     }
     if (!m) {
25a64ec0
         cli_warnmsg("fmap: map allocation failed\n");
         return NULL;
     }
288057e9
     m->handle          = handle;
     m->pread_cb        = pread_cb;
     m->aging           = use_aging;
     m->offset          = offset;
     m->nested_offset   = 0;
     m->len             = len; /* m->nested_offset + m->len = m->real_len */
     m->real_len        = len;
     m->pages           = pages;
     m->hdrsz           = hdrsz;
     m->pgsz            = pgsz;
     m->paged           = 0;
047e50be
     m->dont_cache_flag = 0;
288057e9
     m->unmap           = use_aging ? unmap_mmap : unmap_malloc;
     m->need            = handle_need;
     m->need_offstr     = handle_need_offstr;
     m->gets            = handle_gets;
     m->unneed_off      = handle_unneed_off;
cdf2a0ce
     return m;
 }
db84a7e2
 
288057e9
 static void fmap_aging(fmap_t *m)
 {
4b4d7846
 #ifdef ANONYMOUS_MAP
288057e9
     if (!m->aging) return;
     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;
 
         for (i = 0; i < m->pages; i++) {
             uint32_t s = fmap_bitmap[i];
             if ((s & (FM_MASK_PAGED | FM_MASK_LOCKED)) == FM_MASK_PAGED) {
                 /* page is paged and not locked: dec age */
                 if (s & FM_MASK_COUNT) fmap_bitmap[i]--;
                 /* and make it available for unpaging */
 
                 if (!avail) {
                     freeme[0] = i;
                     avail++;
                 } else {
                     /* Insert sort onto a stack'd array - same performance as quickselect */
                     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) {
                             freeme[insert_to + 1] = freeme[insert_to];
                             if (!insert_to--) break;
                         }
                         freeme[insert_to + 1] = i;
                         if (avail <= maxavail) avail++;
                     }
                 }
             }
         }
         if (avail) { /* at least one page is paged and not locked */
             char *lastpage  = NULL;
             char *firstpage = NULL;
             for (i = 0; i < avail; i++) {
                 char *pptr = (char *)m + freeme[i] * m->pgsz + m->hdrsz;
                 /* we mark the page as seen */
                 fmap_bitmap[freeme[i]] = FM_MASK_SEEN;
                 /* and we mmap the page over so the kernel knows there's nothing good in there */
                 /* reduce number of mmap calls: if pages are adjacent only do 1 mmap call */
                 if (lastpage && pptr == lastpage) {
                     lastpage = pptr + m->pgsz;
                     continue;
                 }
                 if (!lastpage) {
                     firstpage = pptr;
                     lastpage  = pptr + m->pgsz;
                     continue;
                 }
                 fmap_lock;
                 if (mmap(firstpage, lastpage - firstpage, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | ANONYMOUS_MAP, -1, 0) == MAP_FAILED)
                     cli_dbgmsg("fmap_aging: kernel hates you\n");
                 fmap_unlock;
                 firstpage = pptr;
                 lastpage  = pptr + m->pgsz;
             }
             if (lastpage) {
                 fmap_lock;
                 if (mmap(firstpage, lastpage - firstpage, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | ANONYMOUS_MAP, -1, 0) == MAP_FAILED)
                     cli_dbgmsg("fmap_aging: kernel hates you\n");
                 fmap_unlock;
             }
             m->paged -= avail;
         }
2a6cd5b0
     }
478fb3ff
 #endif
2a6cd5b0
 }
 
288057e9
 static int fmap_readpage(fmap_t *m, unsigned int first_page, unsigned int count, unsigned int lock_count)
 {
f3552035
     size_t readsz = 0, eintr_off;
288057e9
     char *pptr    = NULL, errtxt[256];
d92f955f
     uint32_t s;
9d2a56bc
     unsigned int i, page = first_page, force_read = 0;
 
27aa881b
     fmap_lock;
288057e9
     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 */
         volatile char faultme;
         faultme = ((char *)m)[(first_page + i) * m->pgsz + m->hdrsz];
5a078ca9
     }
27aa881b
     fmap_unlock;
288057e9
     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;
         } else if ((s = fmap_bitmap[page]) & FM_MASK_PAGED) {
             /* 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 */
                     fmap_bitmap[page]++;
                 } else /* page not currently locked: set lock count = 1 */
                     fmap_bitmap[page] = 1 | FM_MASK_LOCKED | FM_MASK_PAGED;
             } else {
                 /* we don't want locking */
                 if (!(s & FM_MASK_LOCKED)) {
                     /* page is not locked: we reset aging to max */
                     fmap_bitmap[page] = FM_MASK_PAGED | FM_MASK_COUNT;
                 }
             }
             if (!pptr) continue;
             force_read = 1;
         }
 
         if (force_read) {
             /* we have some pending reads to perform */
             if (m->handle_is_fd) {
                 unsigned int j;
                 int _fd = (int)(ptrdiff_t)m->handle;
                 for (j = first_page; j < page; j++) {
                     if (fmap_bitmap[j] & FM_MASK_SEEN) {
                         /* page we've seen before: check mtime */
                         STATBUF st;
                         if (FSTAT(_fd, &st)) {
                             cli_strerror(errno, errtxt, sizeof(errtxt));
                             cli_warnmsg("fmap_readpage: fstat failed: %s\n", errtxt);
                             return 1;
                         }
                         if (m->mtime != st.st_mtime) {
                             cli_warnmsg("fmap_readpage: file changed as we read it\n");
                             return 1;
                         }
                         break;
                     }
                 }
             }
 
             eintr_off = 0;
             while (readsz) {
                 ssize_t got;
                 off_t target_offset = eintr_off + m->offset + (first_page * m->pgsz);
                 got                 = m->pread_cb(m->handle, pptr, readsz, target_offset);
 
                 if (got < 0 && errno == EINTR)
                     continue;
 
                 if (got > 0) {
                     pptr += got;
                     eintr_off += got;
                     readsz -= got;
                     continue;
                 }
 
                 if (got < 0) {
                     cli_strerror(errno, errtxt, sizeof(errtxt));
                     cli_errmsg("fmap_readpage: pread error: %s\n", errtxt);
                 } else {
                     cli_warnmsg("fmap_readpage: pread fail: asked for %lu bytes @ offset %lu, got %lu\n", (long unsigned int)readsz, (long unsigned int)target_offset, (long unsigned int)got);
                 }
                 return 1;
             }
 
             pptr       = NULL;
             force_read = 0;
             readsz     = 0;
             continue;
         }
 
         /* 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;
         }
         if ((page == m->pages - 1) && (m->real_len % m->pgsz))
             readsz += m->real_len % m->pgsz;
         else
             readsz += m->pgsz;
         if (lock) /* lock requested: set paged, lock page and set lock count to 1 */
             fmap_bitmap[page] = FM_MASK_PAGED | FM_MASK_LOCKED | 1;
         else /* no locking: set paged and set aging to max */
             fmap_bitmap[page] = FM_MASK_PAGED | FM_MASK_COUNT;
         m->paged++;
2a6cd5b0
     }
db84a7e2
     return 0;
 }
 
288057e9
 static const void *handle_need(fmap_t *m, size_t at, size_t len, int lock)
 {
9a4da6af
     unsigned int first_page, last_page, lock_count;
db84a7e2
     char *ret;
 
288057e9
     if (!len)
         return NULL;
db84a7e2
 
87f76399
     at += m->nested_offset;
288057e9
     if (!CLI_ISCONTAINED(0, m->real_len, at, len))
         return NULL;
f52b5724
 
     fmap_aging(m);
 
db84a7e2
     first_page = fmap_which_page(m, at);
288057e9
     last_page  = fmap_which_page(m, at + len - 1);
     lock_count = (lock != 0) * (last_page - first_page + 1);
dd48b9ee
 #ifdef READAHED_PAGES
     last_page += READAHED_PAGES;
288057e9
     if (last_page >= m->pages) last_page = m->pages - 1;
dd48b9ee
 #endif
db84a7e2
 
288057e9
     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
 }
 
288057e9
 static void fmap_unneed_page(fmap_t *m, unsigned int page)
 {
cf069964
     uint32_t s = fmap_bitmap[page];
f52b5724
 
288057e9
     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 */
             fmap_bitmap[page]--;
         else if (s == 1) /* only one lock left: unlock and begin aging */
             fmap_bitmap[page] = FM_MASK_COUNT | FM_MASK_PAGED;
         else
             cli_errmsg("fmap_unneed: inconsistent map state\n");
         return;
2a6cd5b0
     }
     cli_warnmsg("fmap_unneed: unneed on a unlocked page\n");
     return;
 }
 
288057e9
 static void handle_unneed_off(fmap_t *m, size_t at, size_t len)
 {
1ae65c29
     unsigned int i, first_page, last_page;
288057e9
     if (!m->aging) return;
     if (!len) {
         cli_warnmsg("fmap_unneed: attempted void unneed\n");
         return;
1ae65c29
     }
 
b15a70ab
     at += m->nested_offset;
288057e9
     if (!CLI_ISCONTAINED(0, m->real_len, at, len)) {
         cli_warnmsg("fmap: attempted oof unneed\n");
         return;
1ae65c29
     }
f52b5724
 
1ae65c29
     first_page = fmap_which_page(m, at);
288057e9
     last_page  = fmap_which_page(m, at + len - 1);
1ae65c29
 
288057e9
     for (i = first_page; i <= last_page; i++) {
         fmap_unneed_page(m, i);
1ae65c29
     }
 }
 
d2bfc86a
 static void unmap_mmap(fmap_t *m)
 {
4b4d7846
 #ifdef ANONYMOUS_MAP
d2bfc86a
     size_t len = m->pages * m->pgsz + m->hdrsz;
     fmap_lock;
a63683c1
     if (munmap((void *)m, len) == -1) /* munmap() failed */
da6f6a82
         cli_warnmsg("funmap: unable to unmap memory segment at address: %p with length: %zu\n", (void *)m, len);
d2bfc86a
     fmap_unlock;
478fb3ff
 #endif
c124d71a
 }
9a4da6af
 
288057e9
 static void unmap_malloc(fmap_t *m)
 {
d2bfc86a
     free((void *)m);
 }
 
288057e9
 static const void *handle_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
 
288057e9
     if (!len_hint || len_hint > m->real_len - at)
         len_hint = m->real_len - at;
9a4da6af
 
288057e9
     if (!CLI_ISCONTAINED(0, m->real_len, at, len_hint))
         return NULL;
9a4da6af
 
     fmap_aging(m);
 
     first_page = fmap_which_page(m, at);
288057e9
     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;
             scansz = MIN(len_hint, m->pgsz - scanat);
         } else {
             scanat = 0;
             scansz = MIN(len_hint, m->pgsz);
         }
         len_hint -= scansz;
         if (memchr(&thispage[scanat], 0, scansz))
             return ptr;
     }
     for (i = first_page; i <= last_page; i++)
         fmap_unneed_page(m, i);
9a4da6af
     return NULL;
 }
5a078ca9
 
288057e9
 static const void *handle_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;
288057e9
     size_t len = MIN(max_len - 1, m->real_len - *at), fullen = len;
5a078ca9
 
288057e9
     if (!len || !CLI_ISCONTAINED(0, m->real_len, *at, len))
         return NULL;
5a078ca9
 
     fmap_aging(m);
 
     first_page = fmap_which_page(m, *at);
288057e9
     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;
5a078ca9
     } else {
288057e9
         memcpy(dst, src, fullen);
         dst[fullen] = '\0';
         *at += fullen;
5a078ca9
     }
     return dst;
 }
8618abc1
 
2672a22a
 /* vvvvv MEMORY STUFF BELOW vvvvv */
8618abc1
 
2672a22a
 static const void *mem_need(fmap_t *m, size_t at, size_t len, int lock);
06db9d7d
 static void mem_unneed_off(fmap_t *m, size_t at, size_t len);
 static const void *mem_need_offstr(fmap_t *m, size_t at, size_t len_hint);
 static const void *mem_gets(fmap_t *m, char *dst, size_t *at, size_t max_len);
 
89c638eb
 extern cl_fmap_t *cl_fmap_open_memory(const void *start, size_t len)
 {
288057e9
     int pgsz     = cli_getpagesize();
89c638eb
     cl_fmap_t *m = cli_calloc(1, sizeof(*m));
     if (!m) {
288057e9
         cli_warnmsg("fmap: map allocation failed\n");
         return NULL;
     }
     m->data        = start;
     m->len         = len;
     m->real_len    = len;
     m->pgsz        = pgsz;
     m->pages       = fmap_align_items(len, pgsz);
     m->unmap       = unmap_malloc;
     m->need        = mem_need;
89c638eb
     m->need_offstr = mem_need_offstr;
288057e9
     m->gets        = mem_gets;
     m->unneed_off  = mem_unneed_off;
81e57728
     return m;
89c638eb
 }
 
288057e9
 static const void *mem_need(fmap_t *m, size_t at, size_t len, int lock)
 { /* WIN32 */
cd94be7a
     UNUSEDPARAM(lock);
288057e9
     if (!len) {
         return NULL;
81e57728
     }
87f76399
     at += m->nested_offset;
288057e9
     if (!CLI_ISCONTAINED(0, m->real_len, at, len)) {
         return NULL;
81e57728
     }
 
8618abc1
     return (void *)((char *)m->data + at);
 }
 
cd94be7a
 static void mem_unneed_off(fmap_t *m, size_t at, size_t len)
 {
     UNUSEDPARAM(m);
     UNUSEDPARAM(at);
     UNUSEDPARAM(len);
 }
8618abc1
 
288057e9
 static const void *mem_need_offstr(fmap_t *m, size_t at, size_t len_hint)
 {
8618abc1
     char *ptr = (char *)m->data + at;
 
288057e9
     if (!len_hint || len_hint > m->real_len - at)
         len_hint = m->real_len - at;
8618abc1
 
288057e9
     if (!CLI_ISCONTAINED(0, m->real_len, at, len_hint))
         return NULL;
8618abc1
 
288057e9
     if (memchr(ptr, 0, len_hint))
         return (void *)ptr;
8618abc1
     return NULL;
 }
 
288057e9
 static const void *mem_gets(fmap_t *m, char *dst, size_t *at, size_t max_len)
 {
8618abc1
     char *src = (char *)m->data + *at, *endptr = NULL;
288057e9
     size_t len = MIN(max_len - 1, m->real_len - *at);
8618abc1
 
288057e9
     if (!len || !CLI_ISCONTAINED(0, m->real_len, *at, len))
         return NULL;
8618abc1
 
288057e9
     if ((endptr = memchr(src, '\n', len))) {
         endptr++;
         memcpy(dst, src, endptr - src);
         dst[endptr - src] = '\0';
         *at += endptr - src;
8618abc1
     } else {
288057e9
         memcpy(dst, src, len);
         dst[len] = '\0';
         *at += len;
8618abc1
     }
     return dst;
 }
 
288057e9
 fmap_t *fmap(int fd, off_t offset, size_t len)
 {
320157e8
     int unused;
     return fmap_check_empty(fd, offset, len, &unused);
 }
 
288057e9
 static inline unsigned int fmap_align_items(unsigned int sz, unsigned int al)
 {
b0fc218f
     return sz / al + (sz % al != 0);
 }
 
288057e9
 static inline unsigned int fmap_align_to(unsigned int sz, unsigned int al)
 {
b0fc218f
     return al * fmap_align_items(sz, al);
 }
 
288057e9
 static inline unsigned int fmap_which_page(fmap_t *m, size_t at)
 {
b0fc218f
     return at / m->pgsz;
 }
b4d884d9
 
288057e9
 cl_error_t fmap_dump_to_file(fmap_t *map, const char *filepath, const char *tmpdir, char **outname, int *outfd, size_t start_offset, size_t end_offset)
32ba85d5
 {
01eebc13
     cl_error_t ret = CL_EARG;
 
288057e9
     char *filebase = NULL;
     char *prefix   = NULL;
01eebc13
 
288057e9
     char *tmpname = NULL;
     int tmpfd     = -1;
01eebc13
 
     size_t pos = 0, len = 0, bytes_remaining = 0, write_size = 0;
 
     if ((start_offset > map->real_len) || (end_offset < start_offset)) {
         cli_dbgmsg("fmap_dump_to_file: Invalid offset arguments: start %zu, end %zu\n", start_offset, end_offset);
         return ret;
     }
 
288057e9
     pos             = start_offset;
     end_offset      = MIN(end_offset, map->real_len);
01eebc13
     bytes_remaining = end_offset - start_offset;
 
     /* Create a filename prefix that includes the original filename, if available */
     if (filepath != NULL) {
         if (CL_SUCCESS != cli_basename(filepath, strlen(filepath), &filebase)) {
             cli_dbgmsg("fmap_dump_to_file: Unable to determine basename from filepath.\n");
         } else if ((start_offset != 0) && (end_offset != map->real_len)) {
             /* If we're only dumping a portion of the file, inlcude the offsets in the prefix,...
 			 * e.g. tmp filename will become something like:  filebase.500-1200.<randhex> */
             uint32_t prefix_len = strlen(filebase) + 1 + SIZE_T_CHARLEN + 1 + SIZE_T_CHARLEN + 1;
288057e9
             prefix              = malloc(prefix_len);
01eebc13
             if (NULL == prefix) {
                 cli_errmsg("fmap_dump_to_file: Failed to allocate memory for tempfile prefix.\n");
                 if (NULL != filebase)
                     free(filebase);
                 return CL_EMEM;
             }
             snprintf(prefix, prefix_len, "%s.%zu-%zu", filebase, start_offset, end_offset);
19f8f7c1
 
             free(filebase);
             filebase = NULL;
01eebc13
         } else {
             /* Else if we're dumping the whole thing, use the filebase as the prefix */
288057e9
             prefix   = filebase;
19f8f7c1
             filebase = NULL;
01eebc13
         }
     }
32ba85d5
 
     cli_dbgmsg("fmap_dump_to_file: dumping fmap not backed by file...\n");
01eebc13
     ret = cli_gentempfd_with_prefix(tmpdir, prefix, &tmpname, &tmpfd);
     if (ret != CL_SUCCESS) {
32ba85d5
         cli_dbgmsg("fmap_dump_to_file: failed to generate temporary file.\n");
19f8f7c1
         if (NULL != prefix) {
01eebc13
             free(prefix);
             prefix = NULL;
         }
32ba85d5
         return ret;
     }
 
19f8f7c1
     if (NULL != prefix) {
01eebc13
         free(prefix);
         prefix = NULL;
     }
 
32ba85d5
     do {
288057e9
         const char *b;
         len        = 0;
01eebc13
         write_size = MIN(BUFSIZ, bytes_remaining);
 
         b = fmap_need_off_once_len(map, pos, write_size, &len);
32ba85d5
         pos += len;
01eebc13
         if (b && (len > 0)) {
6c03dc5d
             if (cli_writen(tmpfd, b, len) != len) {
32ba85d5
                 cli_warnmsg("fmap_dump_to_file: write failed to %s!\n", tmpname);
                 close(tmpfd);
                 unlink(tmpname);
                 free(tmpname);
                 return CL_EWRITE;
             }
         }
01eebc13
         if (len <= bytes_remaining) {
             bytes_remaining -= len;
         } else {
             bytes_remaining = 0;
         }
     } while ((len > 0) && (bytes_remaining > 0));
32ba85d5
 
01eebc13
     if (lseek(tmpfd, 0, SEEK_SET) == -1) {
32ba85d5
         cli_dbgmsg("fmap_dump_to_file: lseek failed\n");
     }
 
     *outname = tmpname;
288057e9
     *outfd   = tmpfd;
32ba85d5
     return CL_SUCCESS;
 }
 
288057e9
 int fmap_fd(fmap_t *m)
b4d884d9
 {
cd94be7a
     int fd;
5611c67f
     if (!m->handle_is_fd)
01eebc13
         return -1;
8efbf4a0
     fd = (int)(ptrdiff_t)m->handle;
b4d884d9
     lseek(fd, 0, SEEK_SET);
     return fd;
 }
b3a8f998
 
 extern void cl_fmap_close(cl_fmap_t *map)
 {
     funmap(map);
 }