File: file.c

package info (click to toggle)
gdnsd 3.8.3-2.1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,584 kB
  • sloc: ansic: 30,669; sh: 4,872; perl: 1,047; makefile: 401; pascal: 108
file content (147 lines) | stat: -rw-r--r-- 4,167 bytes parent folder | download | duplicates (3)
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;
}