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#      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#define AO_INVALID_FD  -1
60
61#define FILE_WRITABLE(_prt,_flg) \
62        (   (_prt & PROT_WRITE) \
63         && ((_flg & (MAP_SHARED|MAP_PRIVATE)) == MAP_SHARED))
64#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 */
84static void
85load_text_file(tmap_info_t * mapinfo, char const * pzFile)
86{
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    size_t const pgsz = (size_t)GETPAGESIZE();
118    void * map_addr   = NULL;
119
120    (void)pzFile;
121
122    mapinfo->txt_full_size = (mapinfo->txt_size + pgsz) & ~(pgsz - 1);
123    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#if defined(MAP_ANONYMOUS)
129        map_addr = mmap(NULL, mapinfo->txt_full_size, PROT_READ|PROT_WRITE,
130                        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        if (map_addr == MAP_FAILED_PTR) {
142            mapinfo->txt_errno = errno;
143            return;
144        }
145        mapinfo->txt_flags |= MAP_FIXED;
146    }
147
148    mapinfo->txt_data =
149        mmap(map_addr, mapinfo->txt_size, mapinfo->txt_prot,
150             mapinfo->txt_flags, mapinfo->txt_fd, 0);
151
152    if (mapinfo->txt_data == MAP_FAILED_PTR)
153        mapinfo->txt_errno = errno;
154#endif /* HAVE_MMAP */
155}
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 */
167static void
168validate_mmap(char const * fname, int prot, int flags, tmap_info_t * mapinfo)
169{
170    memset(mapinfo, 0, sizeof(*mapinfo));
171#if defined(HAVE_MMAP) && ! defined(MAP_ANONYMOUS)
172    mapinfo->txt_zero_fd = AO_INVALID_FD;
173#endif
174    mapinfo->txt_fd      = AO_INVALID_FD;
175    mapinfo->txt_prot    = prot;
176    mapinfo->txt_flags   = flags;
177
178    /*
179     *  Map mmap flags and protections into open flags and do the open.
180     */
181    {
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        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        if (((flags & MAP_SHARED) == 0) && (prot & PROT_WRITE))
195            o_flag |= O_EXCL;
196
197        mapinfo->txt_fd = open(fname, o_flag);
198        if (mapinfo->txt_fd < 0) {
199            mapinfo->txt_errno = errno;
200            mapinfo->txt_fd = AO_INVALID_FD;
201            return;
202        }
203    }
204
205    /*
206     *  Make sure we can stat the regular file.  Save the file size.
207     */
208    {
209        struct stat sb;
210        if (fstat(mapinfo->txt_fd, &sb) != 0) {
211            mapinfo->txt_errno = errno;
212            close(mapinfo->txt_fd);
213            return;
214        }
215
216        if (! S_ISREG(sb.st_mode)) {
217            mapinfo->txt_errno = errno = EINVAL;
218            close(mapinfo->txt_fd);
219            return;
220        }
221
222        mapinfo->txt_size = (size_t)sb.st_size;
223    }
224
225    if (mapinfo->txt_fd == AO_INVALID_FD)
226        mapinfo->txt_errno = errno;
227}
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 */
234static void
235close_mmap_files(tmap_info_t * mi)
236{
237    if (mi->txt_fd == AO_INVALID_FD)
238        return;
239
240    close(mi->txt_fd);
241    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}
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=*/
305void *
306text_mmap(char const * pzFile, int prot, int flags, tmap_info_t * mi)
307{
308    validate_mmap(pzFile, prot, flags, mi);
309    if (mi->txt_errno != 0)
310        return MAP_FAILED_PTR;
311
312    load_text_file(mi, pzFile);
313
314    if (mi->txt_errno == 0)
315        return mi->txt_data;
316
317    close_mmap_files(mi);
318
319    errno = mi->txt_errno;
320    mi->txt_data = MAP_FAILED_PTR;
321    return mi->txt_data;
322}
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=*/
344int
345text_munmap(tmap_info_t * mi)
346{
347    errno = 0;
348
349#ifdef HAVE_MMAP
350    (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    mi->txt_errno = errno;
367    close_mmap_files(mi);
368
369    return mi->txt_errno;
370}
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 */
380