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  */
84 static void
load_text_file(tmap_info_t * mapinfo,char const * pzFile)85 load_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  */
167 static void
validate_mmap(char const * fname,int prot,int flags,tmap_info_t * mapinfo)168 validate_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  */
234 static void
close_mmap_files(tmap_info_t * mi)235 close_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 =*/
305 void *
text_mmap(char const * pzFile,int prot,int flags,tmap_info_t * mi)306 text_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 =*/
344 int
text_munmap(tmap_info_t * mi)345 text_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