1290001Sglebius/**
2290001Sglebius * @file text_mmap.c
3290001Sglebius *
4290001Sglebius * Map a text file, ensuring the text always has an ending NUL byte.
5290001Sglebius *
6290001Sglebius * @addtogroup autoopts
7290001Sglebius * @{
8290001Sglebius */
9181834Sroberto/*
10290001Sglebius *  This file is part of AutoOpts, a companion to AutoGen.
11290001Sglebius *  AutoOpts is free software.
12290001Sglebius *  AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved
13181834Sroberto *
14290001Sglebius *  AutoOpts is available under any one of two licenses.  The license
15290001Sglebius *  in use must be one of these two and the choice is under the control
16290001Sglebius *  of the user of the license.
17290001Sglebius *
18290001Sglebius *   The GNU Lesser General Public License, version 3 or later
19290001Sglebius *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
20290001Sglebius *
21290001Sglebius *   The Modified Berkeley Software Distribution License
22290001Sglebius *      See the file "COPYING.mbsd"
23290001Sglebius *
24290001Sglebius *  These files have the following sha256 sums:
25290001Sglebius *
26290001Sglebius *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
27290001Sglebius *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
28290001Sglebius *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
29181834Sroberto */
30290001Sglebius#if defined(HAVE_MMAP)
31290001Sglebius#  ifndef      MAP_ANONYMOUS
32290001Sglebius#    ifdef     MAP_ANON
33290001Sglebius#      define  MAP_ANONYMOUS   MAP_ANON
34290001Sglebius#    endif
35290001Sglebius#  endif
36181834Sroberto
37290001Sglebius#  if ! defined(MAP_ANONYMOUS) && ! defined(HAVE_DEV_ZERO)
38290001Sglebius     /*
39290001Sglebius      * We must have either /dev/zero or anonymous mapping for
40290001Sglebius      * this to work.
41290001Sglebius      */
42290001Sglebius#    undef HAVE_MMAP
43290001Sglebius
44290001Sglebius#  else
45290001Sglebius#    ifdef _SC_PAGESIZE
46290001Sglebius#      define GETPAGESIZE() sysconf(_SC_PAGESIZE)
47290001Sglebius#    else
48290001Sglebius#      define GETPAGESIZE() getpagesize()
49290001Sglebius#    endif
50181834Sroberto#  endif
51181834Sroberto#endif
52181834Sroberto
53181834Sroberto/*
54181834Sroberto *  Some weird systems require that a specifically invalid FD number
55181834Sroberto *  get passed in as an argument value.  Which value is that?  Well,
56181834Sroberto *  as everybody knows, if open(2) fails, it returns -1, so that must
57181834Sroberto *  be the value.  :)
58181834Sroberto */
59181834Sroberto#define AO_INVALID_FD  -1
60181834Sroberto
61181834Sroberto#define FILE_WRITABLE(_prt,_flg) \
62181834Sroberto        (   (_prt & PROT_WRITE) \
63181834Sroberto         && ((_flg & (MAP_SHARED|MAP_PRIVATE)) == MAP_SHARED))
64290001Sglebius#define MAP_FAILED_PTR (VOIDP(MAP_FAILED))
65181834Sroberto
66290001Sglebius/**
67290001Sglebius * Load the contents of a text file.  There are two separate implementations,
68290001Sglebius * depending up on whether mmap(3) is available.
69181834Sroberto *
70290001Sglebius *  If not available, malloc the file length plus one byte.  Read it in
71290001Sglebius *  and NUL terminate.
72181834Sroberto *
73290001Sglebius *  If available, first check to see if the text file size is a multiple of a
74290001Sglebius *  page size.  If it is, map the file size plus an extra page from either
75290001Sglebius *  anonymous memory or from /dev/zero.  Then map the file text on top of the
76290001Sglebius *  first pages of the anonymous/zero pages.  Otherwise, just map the file
77290001Sglebius *  because there will be NUL bytes provided at the end.
78181834Sroberto *
79290001Sglebius * @param mapinfo a structure holding everything we need to know
80290001Sglebius *        about the mapping.
81181834Sroberto *
82290001Sglebius * @param pzFile name of the file, for error reporting.
83290001Sglebius */
84290001Sglebiusstatic void
85290001Sglebiusload_text_file(tmap_info_t * mapinfo, char const * pzFile)
86181834Sroberto{
87290001Sglebius#if ! defined(HAVE_MMAP)
88290001Sglebius    mapinfo->txt_data = AGALOC(mapinfo->txt_size+1, "file text");
89290001Sglebius    if (mapinfo->txt_data == NULL) {
90290001Sglebius        mapinfo->txt_errno = ENOMEM;
91290001Sglebius        return;
92290001Sglebius    }
93181834Sroberto
94181834Sroberto    {
95290001Sglebius        size_t sz = mapinfo->txt_size;
96290001Sglebius        char * pz = mapinfo->txt_data;
97181834Sroberto
98290001Sglebius        while (sz > 0) {
99290001Sglebius            ssize_t rdct = read(mapinfo->txt_fd, pz, sz);
100290001Sglebius            if (rdct <= 0) {
101290001Sglebius                mapinfo->txt_errno = errno;
102290001Sglebius                fserr_warn("libopts", "read", pzFile);
103290001Sglebius                free(mapinfo->txt_data);
104290001Sglebius                return;
105290001Sglebius            }
106290001Sglebius
107290001Sglebius            pz += rdct;
108290001Sglebius            sz -= rdct;
109181834Sroberto        }
110181834Sroberto
111290001Sglebius        *pz = NUL;
112181834Sroberto    }
113181834Sroberto
114290001Sglebius    mapinfo->txt_errno   = 0;
115290001Sglebius
116290001Sglebius#else /* HAVE mmap */
117290001Sglebius    size_t const pgsz = (size_t)GETPAGESIZE();
118290001Sglebius    void * map_addr   = NULL;
119290001Sglebius
120290001Sglebius    (void)pzFile;
121290001Sglebius
122290001Sglebius    mapinfo->txt_full_size = (mapinfo->txt_size + pgsz) & ~(pgsz - 1);
123290001Sglebius    if (mapinfo->txt_full_size == (mapinfo->txt_size + pgsz)) {
124290001Sglebius        /*
125290001Sglebius         * The text is a multiple of a page boundary.  We must map an
126290001Sglebius         * extra page so the text ends with a NUL.
127290001Sglebius         */
128290001Sglebius#if defined(MAP_ANONYMOUS)
129290001Sglebius        map_addr = mmap(NULL, mapinfo->txt_full_size, PROT_READ|PROT_WRITE,
130290001Sglebius                        MAP_ANONYMOUS|MAP_PRIVATE, AO_INVALID_FD, 0);
131290001Sglebius#else
132290001Sglebius        mapinfo->txt_zero_fd = open("/dev/zero", O_RDONLY);
133290001Sglebius
134290001Sglebius        if (mapinfo->txt_zero_fd == AO_INVALID_FD) {
135290001Sglebius            mapinfo->txt_errno = errno;
136290001Sglebius            return;
137290001Sglebius        }
138290001Sglebius        map_addr = mmap(NULL, mapinfo->txt_full_size, PROT_READ|PROT_WRITE,
139290001Sglebius                        MAP_PRIVATE, mapinfo->txt_zero_fd, 0);
140290001Sglebius#endif
141290001Sglebius        if (map_addr == MAP_FAILED_PTR) {
142290001Sglebius            mapinfo->txt_errno = errno;
143290001Sglebius            return;
144290001Sglebius        }
145290001Sglebius        mapinfo->txt_flags |= MAP_FIXED;
146290001Sglebius    }
147290001Sglebius
148290001Sglebius    mapinfo->txt_data =
149290001Sglebius        mmap(map_addr, mapinfo->txt_size, mapinfo->txt_prot,
150290001Sglebius             mapinfo->txt_flags, mapinfo->txt_fd, 0);
151290001Sglebius
152290001Sglebius    if (mapinfo->txt_data == MAP_FAILED_PTR)
153290001Sglebius        mapinfo->txt_errno = errno;
154290001Sglebius#endif /* HAVE_MMAP */
155290001Sglebius}
156290001Sglebius
157290001Sglebius/**
158290001Sglebius * Make sure all the parameters are correct:  we have a file name that
159290001Sglebius * is a text file that we can read.
160290001Sglebius *
161290001Sglebius * @param fname the text file to map
162290001Sglebius * @param prot  the memory protections requested (read/write/etc.)
163290001Sglebius * @param flags mmap flags
164290001Sglebius * @param mapinfo a structure holding everything we need to know
165290001Sglebius *        about the mapping.
166290001Sglebius */
167290001Sglebiusstatic void
168290001Sglebiusvalidate_mmap(char const * fname, int prot, int flags, tmap_info_t * mapinfo)
169290001Sglebius{
170290001Sglebius    memset(mapinfo, 0, sizeof(*mapinfo));
171290001Sglebius#if defined(HAVE_MMAP) && ! defined(MAP_ANONYMOUS)
172290001Sglebius    mapinfo->txt_zero_fd = AO_INVALID_FD;
173290001Sglebius#endif
174290001Sglebius    mapinfo->txt_fd      = AO_INVALID_FD;
175290001Sglebius    mapinfo->txt_prot    = prot;
176290001Sglebius    mapinfo->txt_flags   = flags;
177290001Sglebius
178181834Sroberto    /*
179181834Sroberto     *  Map mmap flags and protections into open flags and do the open.
180181834Sroberto     */
181181834Sroberto    {
182181834Sroberto        /*
183181834Sroberto         *  See if we will be updating the file.  If we can alter the memory
184181834Sroberto         *  and if we share the data and we are *not* copy-on-writing the data,
185181834Sroberto         *  then our updates will show in the file, so we must open with
186181834Sroberto         *  write access.
187181834Sroberto         */
188290001Sglebius        int o_flag = FILE_WRITABLE(prot, flags) ? O_RDWR : O_RDONLY;
189181834Sroberto
190181834Sroberto        /*
191181834Sroberto         *  If you're not sharing the file and you are writing to it,
192181834Sroberto         *  then don't let anyone else have access to the file.
193181834Sroberto         */
194181834Sroberto        if (((flags & MAP_SHARED) == 0) && (prot & PROT_WRITE))
195181834Sroberto            o_flag |= O_EXCL;
196181834Sroberto
197290001Sglebius        mapinfo->txt_fd = open(fname, o_flag);
198290001Sglebius        if (mapinfo->txt_fd < 0) {
199290001Sglebius            mapinfo->txt_errno = errno;
200290001Sglebius            mapinfo->txt_fd = AO_INVALID_FD;
201290001Sglebius            return;
202290001Sglebius        }
203181834Sroberto    }
204181834Sroberto
205181834Sroberto    /*
206290001Sglebius     *  Make sure we can stat the regular file.  Save the file size.
207181834Sroberto     */
208181834Sroberto    {
209290001Sglebius        struct stat sb;
210290001Sglebius        if (fstat(mapinfo->txt_fd, &sb) != 0) {
211290001Sglebius            mapinfo->txt_errno = errno;
212290001Sglebius            close(mapinfo->txt_fd);
213290001Sglebius            return;
214290001Sglebius        }
215181834Sroberto
216290001Sglebius        if (! S_ISREG(sb.st_mode)) {
217290001Sglebius            mapinfo->txt_errno = errno = EINVAL;
218290001Sglebius            close(mapinfo->txt_fd);
219290001Sglebius            return;
220181834Sroberto        }
221181834Sroberto
222290001Sglebius        mapinfo->txt_size = (size_t)sb.st_size;
223181834Sroberto    }
224181834Sroberto
225290001Sglebius    if (mapinfo->txt_fd == AO_INVALID_FD)
226290001Sglebius        mapinfo->txt_errno = errno;
227290001Sglebius}
228181834Sroberto
229290001Sglebius/**
230290001Sglebius * Close any files opened by the mapping.
231290001Sglebius *
232290001Sglebius * @param mi a structure holding everything we need to know about the map.
233290001Sglebius */
234290001Sglebiusstatic void
235290001Sglebiusclose_mmap_files(tmap_info_t * mi)
236290001Sglebius{
237290001Sglebius    if (mi->txt_fd == AO_INVALID_FD)
238290001Sglebius        return;
239181834Sroberto
240290001Sglebius    close(mi->txt_fd);
241290001Sglebius    mi->txt_fd = AO_INVALID_FD;
242181834Sroberto
243290001Sglebius#if defined(HAVE_MMAP) && ! defined(MAP_ANONYMOUS)
244290001Sglebius    if (mi->txt_zero_fd == AO_INVALID_FD)
245290001Sglebius        return;
246181834Sroberto
247290001Sglebius    close(mi->txt_zero_fd);
248290001Sglebius    mi->txt_zero_fd = AO_INVALID_FD;
249290001Sglebius#endif
250290001Sglebius}
251181834Sroberto
252290001Sglebius/*=export_func  text_mmap
253290001Sglebius * private:
254290001Sglebius *
255290001Sglebius * what:  map a text file with terminating NUL
256290001Sglebius *
257290001Sglebius * arg:   char const *,  pzFile,  name of the file to map
258290001Sglebius * arg:   int,           prot,    mmap protections (see mmap(2))
259290001Sglebius * arg:   int,           flags,   mmap flags (see mmap(2))
260290001Sglebius * arg:   tmap_info_t *, mapinfo, returned info about the mapping
261290001Sglebius *
262290001Sglebius * ret-type:   void *
263290001Sglebius * ret-desc:   The mmaped data address
264290001Sglebius *
265290001Sglebius * doc:
266290001Sglebius *
267290001Sglebius * This routine will mmap a file into memory ensuring that there is at least
268290001Sglebius * one @file{NUL} character following the file data.  It will return the
269290001Sglebius * address where the file contents have been mapped into memory.  If there is a
270290001Sglebius * problem, then it will return @code{MAP_FAILED} and set @code{errno}
271290001Sglebius * appropriately.
272290001Sglebius *
273290001Sglebius * The named file does not exist, @code{stat(2)} will set @code{errno} as it
274290001Sglebius * will.  If the file is not a regular file, @code{errno} will be
275290001Sglebius * @code{EINVAL}.  At that point, @code{open(2)} is attempted with the access
276290001Sglebius * bits set appropriately for the requested @code{mmap(2)} protections and flag
277290001Sglebius * bits.  On failure, @code{errno} will be set according to the documentation
278290001Sglebius * for @code{open(2)}.  If @code{mmap(2)} fails, @code{errno} will be set as
279290001Sglebius * that routine sets it.  If @code{text_mmap} works to this point, a valid
280290001Sglebius * address will be returned, but there may still be ``issues''.
281290001Sglebius *
282290001Sglebius * If the file size is not an even multiple of the system page size, then
283290001Sglebius * @code{text_map} will return at this point and @code{errno} will be zero.
284290001Sglebius * Otherwise, an anonymous map is attempted.  If not available, then an attempt
285290001Sglebius * is made to @code{mmap(2)} @file{/dev/zero}.  If any of these fail, the
286290001Sglebius * address of the file's data is returned, bug @code{no} @file{NUL} characters
287290001Sglebius * are mapped after the end of the data.
288290001Sglebius *
289290001Sglebius * see: mmap(2), open(2), stat(2)
290290001Sglebius *
291290001Sglebius * err: Any error code issued by mmap(2), open(2), stat(2) is possible.
292290001Sglebius *      Additionally, if the specified file is not a regular file, then
293290001Sglebius *      errno will be set to @code{EINVAL}.
294290001Sglebius *
295290001Sglebius * example:
296290001Sglebius * #include <mylib.h>
297290001Sglebius * tmap_info_t mi;
298290001Sglebius * int no_nul;
299290001Sglebius * void * data = text_mmap("file", PROT_WRITE, MAP_PRIVATE, &mi);
300290001Sglebius * if (data == MAP_FAILED) return;
301290001Sglebius * no_nul = (mi.txt_size == mi.txt_full_size);
302290001Sglebius * << use the data >>
303290001Sglebius * text_munmap(&mi);
304290001Sglebius=*/
305290001Sglebiusvoid *
306290001Sglebiustext_mmap(char const * pzFile, int prot, int flags, tmap_info_t * mi)
307290001Sglebius{
308290001Sglebius    validate_mmap(pzFile, prot, flags, mi);
309290001Sglebius    if (mi->txt_errno != 0)
310290001Sglebius        return MAP_FAILED_PTR;
311181834Sroberto
312290001Sglebius    load_text_file(mi, pzFile);
313181834Sroberto
314290001Sglebius    if (mi->txt_errno == 0)
315290001Sglebius        return mi->txt_data;
316181834Sroberto
317290001Sglebius    close_mmap_files(mi);
318181834Sroberto
319290001Sglebius    errno = mi->txt_errno;
320290001Sglebius    mi->txt_data = MAP_FAILED_PTR;
321290001Sglebius    return mi->txt_data;
322181834Sroberto}
323181834Sroberto
324181834Sroberto
325181834Sroberto/*=export_func  text_munmap
326181834Sroberto * private:
327181834Sroberto *
328181834Sroberto * what:  unmap the data mapped in by text_mmap
329181834Sroberto *
330290001Sglebius * arg:   tmap_info_t *, mapinfo, info about the mapping
331181834Sroberto *
332181834Sroberto * ret-type:   int
333290001Sglebius * ret-desc:   -1 or 0.  @code{errno} will have the error code.
334181834Sroberto *
335181834Sroberto * doc:
336181834Sroberto *
337181834Sroberto * This routine will unmap the data mapped in with @code{text_mmap} and close
338181834Sroberto * the associated file descriptors opened by that function.
339181834Sroberto *
340181834Sroberto * see: munmap(2), close(2)
341181834Sroberto *
342181834Sroberto * err: Any error code issued by munmap(2) or close(2) is possible.
343181834Sroberto=*/
344181834Srobertoint
345290001Sglebiustext_munmap(tmap_info_t * mi)
346181834Sroberto{
347181834Sroberto    errno = 0;
348181834Sroberto
349290001Sglebius#ifdef HAVE_MMAP
350290001Sglebius    (void)munmap(mi->txt_data, mi->txt_full_size);
351181834Sroberto
352290001Sglebius#else  /* don't HAVE_MMAP */
353181834Sroberto    /*
354181834Sroberto     *  IF the memory is writable *AND* it is not private (copy-on-write)
355181834Sroberto     *     *AND* the memory is "sharable" (seen by other processes)
356290001Sglebius     *  THEN rewrite the data.  Emulate mmap visibility.
357181834Sroberto     */
358290001Sglebius    if (   FILE_WRITABLE(mi->txt_prot, mi->txt_flags)
359290001Sglebius        && (lseek(mi->txt_fd, 0, SEEK_SET) >= 0) ) {
360290001Sglebius        write(mi->txt_fd, mi->txt_data, mi->txt_size);
361181834Sroberto    }
362181834Sroberto
363290001Sglebius    free(mi->txt_data);
364290001Sglebius#endif /* HAVE_MMAP */
365181834Sroberto
366290001Sglebius    mi->txt_errno = errno;
367290001Sglebius    close_mmap_files(mi);
368290001Sglebius
369290001Sglebius    return mi->txt_errno;
370181834Sroberto}
371181834Sroberto
372290001Sglebius/** @}
373290001Sglebius *
374181834Sroberto * Local Variables:
375181834Sroberto * mode: C
376181834Sroberto * c-file-style: "stroustrup"
377181834Sroberto * indent-tabs-mode: nil
378181834Sroberto * End:
379181834Sroberto * end of autoopts/text_mmap.c */
380