Coverage Report

Created: 2026-02-26 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ntp-dev/sntp/libopts/text_mmap.c
Line
Count
Source
1
/**
2
 * @file text_mmap.c
3
 *
4
 * Map a text file, ensuring the text always has an ending NUL byte.
5
 *
6
 * @addtogroup autoopts
7
 * @{
8
 */
9
/*
10
 *  This file is part of AutoOpts, a companion to AutoGen.
11
 *  AutoOpts is free software.
12
 *  AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
13
 *
14
 *  AutoOpts is available under any one of two licenses.  The license
15
 *  in use must be one of these two and the choice is under the control
16
 *  of the user of the license.
17
 *
18
 *   The GNU Lesser General Public License, version 3 or later
19
 *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
20
 *
21
 *   The Modified Berkeley Software Distribution License
22
 *      See the file "COPYING.mbsd"
23
 *
24
 *  These files have the following sha256 sums:
25
 *
26
 *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
27
 *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
28
 *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
29
 */
30
#if defined(HAVE_MMAP)
31
#  ifndef      MAP_ANONYMOUS
32
#    ifdef     MAP_ANON
33
#      define  MAP_ANONYMOUS   MAP_ANON
34
#    endif
35
#  endif
36
37
#  if ! defined(MAP_ANONYMOUS) && ! defined(HAVE_DEV_ZERO)
38
     /*
39
      * We must have either /dev/zero or anonymous mapping for
40
      * this to work.
41
      */
42
#    undef HAVE_MMAP
43
44
#  else
45
#    ifdef _SC_PAGESIZE
46
0
#      define GETPAGESIZE() sysconf(_SC_PAGESIZE)
47
#    else
48
#      define GETPAGESIZE() getpagesize()
49
#    endif
50
#  endif
51
#endif
52
53
/*
54
 *  Some weird systems require that a specifically invalid FD number
55
 *  get passed in as an argument value.  Which value is that?  Well,
56
 *  as everybody knows, if open(2) fails, it returns -1, so that must
57
 *  be the value.  :)
58
 */
59
0
#define AO_INVALID_FD  -1
60
61
#define FILE_WRITABLE(_prt,_flg) \
62
0
        (   (_prt & PROT_WRITE) \
63
0
         && ((_flg & (MAP_SHARED|MAP_PRIVATE)) == MAP_SHARED))
64
0
#define MAP_FAILED_PTR (VOIDP(MAP_FAILED))
65
66
/**
67
 * Load the contents of a text file.  There are two separate implementations,
68
 * depending up on whether mmap(3) is available.
69
 *
70
 *  If not available, malloc the file length plus one byte.  Read it in
71
 *  and NUL terminate.
72
 *
73
 *  If available, first check to see if the text file size is a multiple of a
74
 *  page size.  If it is, map the file size plus an extra page from either
75
 *  anonymous memory or from /dev/zero.  Then map the file text on top of the
76
 *  first pages of the anonymous/zero pages.  Otherwise, just map the file
77
 *  because there will be NUL bytes provided at the end.
78
 *
79
 * @param mapinfo a structure holding everything we need to know
80
 *        about the mapping.
81
 *
82
 * @param pzFile name of the file, for error reporting.
83
 */
84
static void
85
load_text_file(tmap_info_t * mapinfo, char const * pzFile)
86
0
{
87
#if ! defined(HAVE_MMAP)
88
    mapinfo->txt_data = AGALOC(mapinfo->txt_size+1, "file text");
89
    if (mapinfo->txt_data == NULL) {
90
        mapinfo->txt_errno = ENOMEM;
91
        return;
92
    }
93
94
    {
95
        size_t sz = mapinfo->txt_size;
96
        char * pz = mapinfo->txt_data;
97
98
        while (sz > 0) {
99
            ssize_t rdct = read(mapinfo->txt_fd, pz, sz);
100
            if (rdct <= 0) {
101
                mapinfo->txt_errno = errno;
102
                fserr_warn("libopts", "read", pzFile);
103
                free(mapinfo->txt_data);
104
                return;
105
            }
106
107
            pz += rdct;
108
            sz -= rdct;
109
        }
110
111
        *pz = NUL;
112
    }
113
114
    mapinfo->txt_errno   = 0;
115
116
#else /* HAVE mmap */
117
0
    size_t const pgsz = (size_t)GETPAGESIZE();
118
0
    void * map_addr   = NULL;
119
120
0
    (void)pzFile;
121
122
0
    mapinfo->txt_full_size = (mapinfo->txt_size + pgsz) & ~(pgsz - 1);
123
0
    if (mapinfo->txt_full_size == (mapinfo->txt_size + pgsz)) {
124
        /*
125
         * The text is a multiple of a page boundary.  We must map an
126
         * extra page so the text ends with a NUL.
127
         */
128
0
#if defined(MAP_ANONYMOUS)
129
0
        map_addr = mmap(NULL, mapinfo->txt_full_size, PROT_READ|PROT_WRITE,
130
0
                        MAP_ANONYMOUS|MAP_PRIVATE, AO_INVALID_FD, 0);
131
#else
132
        mapinfo->txt_zero_fd = open("/dev/zero", O_RDONLY);
133
134
        if (mapinfo->txt_zero_fd == AO_INVALID_FD) {
135
            mapinfo->txt_errno = errno;
136
            return;
137
        }
138
        map_addr = mmap(NULL, mapinfo->txt_full_size, PROT_READ|PROT_WRITE,
139
                        MAP_PRIVATE, mapinfo->txt_zero_fd, 0);
140
#endif
141
0
        if (map_addr == MAP_FAILED_PTR) {
142
0
            mapinfo->txt_errno = errno;
143
0
            return;
144
0
        }
145
0
        mapinfo->txt_flags |= MAP_FIXED;
146
0
    }
147
148
0
    mapinfo->txt_data =
149
0
        mmap(map_addr, mapinfo->txt_size, mapinfo->txt_prot,
150
0
             mapinfo->txt_flags, mapinfo->txt_fd, 0);
151
152
0
    if (mapinfo->txt_data == MAP_FAILED_PTR)
153
0
        mapinfo->txt_errno = errno;
154
0
#endif /* HAVE_MMAP */
155
0
}
156
157
/**
158
 * Make sure all the parameters are correct:  we have a file name that
159
 * is a text file that we can read.
160
 *
161
 * @param fname the text file to map
162
 * @param prot  the memory protections requested (read/write/etc.)
163
 * @param flags mmap flags
164
 * @param mapinfo a structure holding everything we need to know
165
 *        about the mapping.
166
 */
167
static void
168
validate_mmap(char const * fname, int prot, int flags, tmap_info_t * mapinfo)
169
0
{
170
0
    memset(mapinfo, 0, sizeof(*mapinfo));
171
#if defined(HAVE_MMAP) && ! defined(MAP_ANONYMOUS)
172
    mapinfo->txt_zero_fd = AO_INVALID_FD;
173
#endif
174
0
    mapinfo->txt_fd      = AO_INVALID_FD;
175
0
    mapinfo->txt_prot    = prot;
176
0
    mapinfo->txt_flags   = flags;
177
178
    /*
179
     *  Map mmap flags and protections into open flags and do the open.
180
     */
181
0
    {
182
        /*
183
         *  See if we will be updating the file.  If we can alter the memory
184
         *  and if we share the data and we are *not* copy-on-writing the data,
185
         *  then our updates will show in the file, so we must open with
186
         *  write access.
187
         */
188
0
        int o_flag =
189
#ifdef _WIN32
190
            _O_BINARY |
191
#endif
192
0
            FILE_WRITABLE(prot, flags) ? O_RDWR : O_RDONLY;
193
194
        /*
195
         *  If you're not sharing the file and you are writing to it,
196
         *  then don't let anyone else have access to the file.
197
         */
198
0
        if (((flags & MAP_SHARED) == 0) && (prot & PROT_WRITE))
199
0
            o_flag |= O_EXCL;
200
201
0
        mapinfo->txt_fd = open(fname, o_flag);
202
0
        if (mapinfo->txt_fd < 0) {
203
0
            mapinfo->txt_errno = errno;
204
0
            mapinfo->txt_fd = AO_INVALID_FD;
205
0
            return;
206
0
        }
207
0
    }
208
209
    /*
210
     *  Make sure we can stat the regular file.  Save the file size.
211
     */
212
0
    {
213
0
        struct stat sb;
214
0
        if (fstat(mapinfo->txt_fd, &sb) != 0) {
215
0
            mapinfo->txt_errno = errno;
216
0
            close(mapinfo->txt_fd);
217
0
            return;
218
0
        }
219
220
0
        if (! S_ISREG(sb.st_mode)) {
221
0
            mapinfo->txt_errno = errno = EINVAL;
222
0
            close(mapinfo->txt_fd);
223
0
            return;
224
0
        }
225
226
0
        mapinfo->txt_size = (size_t)sb.st_size;
227
0
    }
228
229
0
    if (mapinfo->txt_fd == AO_INVALID_FD)
230
0
        mapinfo->txt_errno = errno;
231
0
}
232
233
/**
234
 * Close any files opened by the mapping.
235
 *
236
 * @param mi a structure holding everything we need to know about the map.
237
 */
238
static void
239
close_mmap_files(tmap_info_t * mi)
240
0
{
241
0
    if (mi->txt_fd == AO_INVALID_FD)
242
0
        return;
243
244
0
    close(mi->txt_fd);
245
0
    mi->txt_fd = AO_INVALID_FD;
246
247
#if defined(HAVE_MMAP) && ! defined(MAP_ANONYMOUS)
248
    if (mi->txt_zero_fd == AO_INVALID_FD)
249
        return;
250
251
    close(mi->txt_zero_fd);
252
    mi->txt_zero_fd = AO_INVALID_FD;
253
#endif
254
0
}
255
256
/*=export_func  text_mmap
257
 * private:
258
 *
259
 * what:  map a text file with terminating NUL
260
 *
261
 * arg:   char const *,  pzFile,  name of the file to map
262
 * arg:   int,           prot,    mmap protections (see mmap(2))
263
 * arg:   int,           flags,   mmap flags (see mmap(2))
264
 * arg:   tmap_info_t *, mapinfo, returned info about the mapping
265
 *
266
 * ret-type:   void *
267
 * ret-desc:   The mmaped data address
268
 *
269
 * doc:
270
 *
271
 * This routine will mmap a file into memory ensuring that there is at least
272
 * one @file{NUL} character following the file data.  It will return the
273
 * address where the file contents have been mapped into memory.  If there is a
274
 * problem, then it will return @code{MAP_FAILED} and set @code{errno}
275
 * appropriately.
276
 *
277
 * The named file does not exist, @code{stat(2)} will set @code{errno} as it
278
 * will.  If the file is not a regular file, @code{errno} will be
279
 * @code{EINVAL}.  At that point, @code{open(2)} is attempted with the access
280
 * bits set appropriately for the requested @code{mmap(2)} protections and flag
281
 * bits.  On failure, @code{errno} will be set according to the documentation
282
 * for @code{open(2)}.  If @code{mmap(2)} fails, @code{errno} will be set as
283
 * that routine sets it.  If @code{text_mmap} works to this point, a valid
284
 * address will be returned, but there may still be ``issues''.
285
 *
286
 * If the file size is not an even multiple of the system page size, then
287
 * @code{text_map} will return at this point and @code{errno} will be zero.
288
 * Otherwise, an anonymous map is attempted.  If not available, then an attempt
289
 * is made to @code{mmap(2)} @file{/dev/zero}.  If any of these fail, the
290
 * address of the file's data is returned, bug @code{no} @file{NUL} characters
291
 * are mapped after the end of the data.
292
 *
293
 * see: mmap(2), open(2), stat(2)
294
 *
295
 * err: Any error code issued by mmap(2), open(2), stat(2) is possible.
296
 *      Additionally, if the specified file is not a regular file, then
297
 *      errno will be set to @code{EINVAL}.
298
 *
299
 * example:
300
 * #include <mylib.h>
301
 * tmap_info_t mi;
302
 * int no_nul;
303
 * void * data = text_mmap("file", PROT_WRITE, MAP_PRIVATE, &mi);
304
 * if (data == MAP_FAILED) return;
305
 * no_nul = (mi.txt_size == mi.txt_full_size);
306
 * << use the data >>
307
 * text_munmap(&mi);
308
=*/
309
void *
310
text_mmap(char const * pzFile, int prot, int flags, tmap_info_t * mi)
311
0
{
312
0
    validate_mmap(pzFile, prot, flags, mi);
313
0
    if (mi->txt_errno != 0)
314
0
        return MAP_FAILED_PTR;
315
316
0
    load_text_file(mi, pzFile);
317
318
0
    if (mi->txt_errno == 0)
319
0
        return mi->txt_data;
320
321
0
    close_mmap_files(mi);
322
323
0
    errno = mi->txt_errno;
324
0
    mi->txt_data = MAP_FAILED_PTR;
325
0
    return mi->txt_data;
326
0
}
327
328
329
/*=export_func  text_munmap
330
 * private:
331
 *
332
 * what:  unmap the data mapped in by text_mmap
333
 *
334
 * arg:   tmap_info_t *, mapinfo, info about the mapping
335
 *
336
 * ret-type:   int
337
 * ret-desc:   -1 or 0.  @code{errno} will have the error code.
338
 *
339
 * doc:
340
 *
341
 * This routine will unmap the data mapped in with @code{text_mmap} and close
342
 * the associated file descriptors opened by that function.
343
 *
344
 * see: munmap(2), close(2)
345
 *
346
 * err: Any error code issued by munmap(2) or close(2) is possible.
347
=*/
348
int
349
text_munmap(tmap_info_t * mi)
350
0
{
351
0
    errno = 0;
352
353
0
#ifdef HAVE_MMAP
354
0
    (void)munmap(mi->txt_data, mi->txt_full_size);
355
356
#else // don't HAVE_MMAP
357
    /*
358
     *  IF the memory is writable *AND* it is not private (copy-on-write)
359
     *     *AND* the memory is "sharable" (seen by other processes)
360
     *  THEN rewrite the data.  Emulate mmap visibility.
361
     */
362
    if (  FILE_WRITABLE(mi->txt_prot, mi->txt_flags)
363
       && (lseek(mi->txt_fd, 0, SEEK_SET) >= 0) )
364
        write(mi->txt_fd, mi->txt_data, mi->txt_size);
365
366
    free(mi->txt_data);
367
#endif /* HAVE_MMAP */
368
369
0
    mi->txt_errno = errno;
370
0
    close_mmap_files(mi);
371
372
0
    return mi->txt_errno;
373
0
}
374
375
/** @}
376
 *
377
 * Local Variables:
378
 * mode: C
379
 * c-file-style: "stroustrup"
380
 * indent-tabs-mode: nil
381
 * End:
382
 * end of autoopts/text_mmap.c */