Coverage Report

Created: 2023-05-19 06:16

/src/ntp-dev/sntp/libopts/text_mmap.c
Line
Count
Source (jump to first uncovered line)
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-2015 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 = FILE_WRITABLE(prot, flags) ? O_RDWR : O_RDONLY;
189
190
        /*
191
         *  If you're not sharing the file and you are writing to it,
192
         *  then don't let anyone else have access to the file.
193
         */
194
0
        if (((flags & MAP_SHARED) == 0) && (prot & PROT_WRITE))
195
0
            o_flag |= O_EXCL;
196
197
0
        mapinfo->txt_fd = open(fname, o_flag);
198
0
        if (mapinfo->txt_fd < 0) {
199
0
            mapinfo->txt_errno = errno;
200
0
            mapinfo->txt_fd = AO_INVALID_FD;
201
0
            return;
202
0
        }
203
0
    }
204
205
    /*
206
     *  Make sure we can stat the regular file.  Save the file size.
207
     */
208
0
    {
209
0
        struct stat sb;
210
0
        if (fstat(mapinfo->txt_fd, &sb) != 0) {
211
0
            mapinfo->txt_errno = errno;
212
0
            close(mapinfo->txt_fd);
213
0
            return;
214
0
        }
215
216
0
        if (! S_ISREG(sb.st_mode)) {
217
0
            mapinfo->txt_errno = errno = EINVAL;
218
0
            close(mapinfo->txt_fd);
219
0
            return;
220
0
        }
221
222
0
        mapinfo->txt_size = (size_t)sb.st_size;
223
0
    }
224
225
0
    if (mapinfo->txt_fd == AO_INVALID_FD)
226
0
        mapinfo->txt_errno = errno;
227
0
}
228
229
/**
230
 * Close any files opened by the mapping.
231
 *
232
 * @param mi a structure holding everything we need to know about the map.
233
 */
234
static void
235
close_mmap_files(tmap_info_t * mi)
236
0
{
237
0
    if (mi->txt_fd == AO_INVALID_FD)
238
0
        return;
239
240
0
    close(mi->txt_fd);
241
0
    mi->txt_fd = AO_INVALID_FD;
242
243
#if defined(HAVE_MMAP) && ! defined(MAP_ANONYMOUS)
244
    if (mi->txt_zero_fd == AO_INVALID_FD)
245
        return;
246
247
    close(mi->txt_zero_fd);
248
    mi->txt_zero_fd = AO_INVALID_FD;
249
#endif
250
0
}
251
252
/*=export_func  text_mmap
253
 * private:
254
 *
255
 * what:  map a text file with terminating NUL
256
 *
257
 * arg:   char const *,  pzFile,  name of the file to map
258
 * arg:   int,           prot,    mmap protections (see mmap(2))
259
 * arg:   int,           flags,   mmap flags (see mmap(2))
260
 * arg:   tmap_info_t *, mapinfo, returned info about the mapping
261
 *
262
 * ret-type:   void *
263
 * ret-desc:   The mmaped data address
264
 *
265
 * doc:
266
 *
267
 * This routine will mmap a file into memory ensuring that there is at least
268
 * one @file{NUL} character following the file data.  It will return the
269
 * address where the file contents have been mapped into memory.  If there is a
270
 * problem, then it will return @code{MAP_FAILED} and set @code{errno}
271
 * appropriately.
272
 *
273
 * The named file does not exist, @code{stat(2)} will set @code{errno} as it
274
 * will.  If the file is not a regular file, @code{errno} will be
275
 * @code{EINVAL}.  At that point, @code{open(2)} is attempted with the access
276
 * bits set appropriately for the requested @code{mmap(2)} protections and flag
277
 * bits.  On failure, @code{errno} will be set according to the documentation
278
 * for @code{open(2)}.  If @code{mmap(2)} fails, @code{errno} will be set as
279
 * that routine sets it.  If @code{text_mmap} works to this point, a valid
280
 * address will be returned, but there may still be ``issues''.
281
 *
282
 * If the file size is not an even multiple of the system page size, then
283
 * @code{text_map} will return at this point and @code{errno} will be zero.
284
 * Otherwise, an anonymous map is attempted.  If not available, then an attempt
285
 * is made to @code{mmap(2)} @file{/dev/zero}.  If any of these fail, the
286
 * address of the file's data is returned, bug @code{no} @file{NUL} characters
287
 * are mapped after the end of the data.
288
 *
289
 * see: mmap(2), open(2), stat(2)
290
 *
291
 * err: Any error code issued by mmap(2), open(2), stat(2) is possible.
292
 *      Additionally, if the specified file is not a regular file, then
293
 *      errno will be set to @code{EINVAL}.
294
 *
295
 * example:
296
 * #include <mylib.h>
297
 * tmap_info_t mi;
298
 * int no_nul;
299
 * void * data = text_mmap("file", PROT_WRITE, MAP_PRIVATE, &mi);
300
 * if (data == MAP_FAILED) return;
301
 * no_nul = (mi.txt_size == mi.txt_full_size);
302
 * << use the data >>
303
 * text_munmap(&mi);
304
=*/
305
void *
306
text_mmap(char const * pzFile, int prot, int flags, tmap_info_t * mi)
307
0
{
308
0
    validate_mmap(pzFile, prot, flags, mi);
309
0
    if (mi->txt_errno != 0)
310
0
        return MAP_FAILED_PTR;
311
312
0
    load_text_file(mi, pzFile);
313
314
0
    if (mi->txt_errno == 0)
315
0
        return mi->txt_data;
316
317
0
    close_mmap_files(mi);
318
319
0
    errno = mi->txt_errno;
320
0
    mi->txt_data = MAP_FAILED_PTR;
321
0
    return mi->txt_data;
322
0
}
323
324
325
/*=export_func  text_munmap
326
 * private:
327
 *
328
 * what:  unmap the data mapped in by text_mmap
329
 *
330
 * arg:   tmap_info_t *, mapinfo, info about the mapping
331
 *
332
 * ret-type:   int
333
 * ret-desc:   -1 or 0.  @code{errno} will have the error code.
334
 *
335
 * doc:
336
 *
337
 * This routine will unmap the data mapped in with @code{text_mmap} and close
338
 * the associated file descriptors opened by that function.
339
 *
340
 * see: munmap(2), close(2)
341
 *
342
 * err: Any error code issued by munmap(2) or close(2) is possible.
343
=*/
344
int
345
text_munmap(tmap_info_t * mi)
346
0
{
347
0
    errno = 0;
348
349
0
#ifdef HAVE_MMAP
350
0
    (void)munmap(mi->txt_data, mi->txt_full_size);
351
352
#else  /* don't HAVE_MMAP */
353
    /*
354
     *  IF the memory is writable *AND* it is not private (copy-on-write)
355
     *     *AND* the memory is "sharable" (seen by other processes)
356
     *  THEN rewrite the data.  Emulate mmap visibility.
357
     */
358
    if (   FILE_WRITABLE(mi->txt_prot, mi->txt_flags)
359
        && (lseek(mi->txt_fd, 0, SEEK_SET) >= 0) ) {
360
        write(mi->txt_fd, mi->txt_data, mi->txt_size);
361
    }
362
363
    free(mi->txt_data);
364
#endif /* HAVE_MMAP */
365
366
0
    mi->txt_errno = errno;
367
0
    close_mmap_files(mi);
368
369
0
    return mi->txt_errno;
370
0
}
371
372
/** @}
373
 *
374
 * Local Variables:
375
 * mode: C
376
 * c-file-style: "stroustrup"
377
 * indent-tabs-mode: nil
378
 * End:
379
 * end of autoopts/text_mmap.c */