1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
/* Copyright © 2014 Brandon L Black <blblack@gmail.com>
*
* This file is part of gdnsd.
*
* gdnsd is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* gdnsd 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 gdnsd. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <config.h>
#include <gdnsd/file.h>
#include <gdnsd/compiler.h>
#include <gdnsd/alloc.h>
#include <gdnsd/log.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
// Avoid mmap for smaller files (<1MB)
#define SIZE_CUTOFF_MMAP 1048576U
struct gdnsd_fmap_s_ {
void* buf;
size_t len;
bool is_mapped;
};
gdnsd_fmap_t* gdnsd_fmap_new(const char* fn, const bool seq, const bool mod)
{
const int fd = open(fn, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
log_err("Cannot open '%s' for reading: %s", fn, logf_errno());
return NULL;
}
struct stat st;
if (fstat(fd, &st) < 0) {
log_err("Cannot fstat '%s': %s", fn, logf_errno());
close(fd);
return NULL;
}
// S_ISREG won't fail on symlink here, because this is fstat()
// and the earlier open() didn't use O_NOFOLLOW.
if (!S_ISREG(st.st_mode) || st.st_size < 0) {
log_err("'%s' is not a regular file", fn);
close(fd);
return NULL;
}
const size_t len = (size_t)st.st_size;
char* mapbuf = NULL;
bool is_mapped = false;
if (len >= SIZE_CUTOFF_MMAP) {
const int prot = mod ? (PROT_READ | PROT_WRITE) : PROT_READ;
const int flags = mod ? MAP_PRIVATE : MAP_SHARED;
mapbuf = mmap(NULL, len, prot, flags, fd, 0);
if (mapbuf == MAP_FAILED) {
log_err("Cannot mmap '%s': %s", fn, logf_errno());
close(fd);
return NULL;
}
int advice = POSIX_MADV_WILLNEED;
if (seq)
advice |= POSIX_MADV_SEQUENTIAL;
else
advice |= POSIX_MADV_RANDOM;
(void)posix_madvise(mapbuf, len, advice);
is_mapped = true;
} else if (len) {
mapbuf = xmalloc(len);
size_t have_slurped = 0;
do {
ssize_t readrv = read(fd, mapbuf + have_slurped, len - have_slurped);
if (unlikely(readrv < 0)) {
if (errno == EINTR)
continue;
log_err("read of '%s' failed: %s", fn, logf_errno());
free(mapbuf);
close(fd);
return NULL;
}
have_slurped += (size_t)readrv;
} while (have_slurped < len);
} else {
// we don't want callers to have to care about cases where this call
// was successful but the buffer pointer is NULL due to len == 0, so
// allocate a 1-byte buffer containing a NUL for these cases.
mapbuf = xcalloc(1);
}
if (close(fd))
log_err("Cannot close '%s', continuing anyways: %s", fn, logf_errno());
gdnsd_fmap_t* fmap = xmalloc(sizeof(*fmap));
fmap->buf = mapbuf;
fmap->len = len;
fmap->is_mapped = is_mapped;
return fmap;
}
void* gdnsd_fmap_get_buf(const gdnsd_fmap_t* fmap)
{
gdnsd_assert(fmap->buf);
return fmap->buf;
}
size_t gdnsd_fmap_get_len(const gdnsd_fmap_t* fmap)
{
gdnsd_assert(fmap->buf);
return fmap->len;
}
bool gdnsd_fmap_delete(gdnsd_fmap_t* fmap)
{
gdnsd_assert(fmap->buf);
bool rv = false; // true == error
if (fmap->is_mapped) {
if (munmap(fmap->buf, fmap->len)) {
log_err("Cannot munmap() %p: %s", fmap->buf, logf_errno());
rv = true;
}
} else {
free(fmap->buf);
}
free(fmap);
return rv;
}
|