xref: /freebsd/contrib/ntp/sntp/libopts/text_mmap.c (revision 19fae0f66023a97a9b464b3beeeabb2081f575b3)
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 #      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
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
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 =
189 #ifdef _WIN32
190             _O_BINARY |
191 #endif
192             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         if (((flags & MAP_SHARED) == 0) && (prot & PROT_WRITE))
199             o_flag |= O_EXCL;
200 
201         mapinfo->txt_fd = open(fname, o_flag);
202         if (mapinfo->txt_fd < 0) {
203             mapinfo->txt_errno = errno;
204             mapinfo->txt_fd = AO_INVALID_FD;
205             return;
206         }
207     }
208 
209     /*
210      *  Make sure we can stat the regular file.  Save the file size.
211      */
212     {
213         struct stat sb;
214         if (fstat(mapinfo->txt_fd, &sb) != 0) {
215             mapinfo->txt_errno = errno;
216             close(mapinfo->txt_fd);
217             return;
218         }
219 
220         if (! S_ISREG(sb.st_mode)) {
221             mapinfo->txt_errno = errno = EINVAL;
222             close(mapinfo->txt_fd);
223             return;
224         }
225 
226         mapinfo->txt_size = (size_t)sb.st_size;
227     }
228 
229     if (mapinfo->txt_fd == AO_INVALID_FD)
230         mapinfo->txt_errno = errno;
231 }
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 {
241     if (mi->txt_fd == AO_INVALID_FD)
242         return;
243 
244     close(mi->txt_fd);
245     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 }
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 {
312     validate_mmap(pzFile, prot, flags, mi);
313     if (mi->txt_errno != 0)
314         return MAP_FAILED_PTR;
315 
316     load_text_file(mi, pzFile);
317 
318     if (mi->txt_errno == 0)
319         return mi->txt_data;
320 
321     close_mmap_files(mi);
322 
323     errno = mi->txt_errno;
324     mi->txt_data = MAP_FAILED_PTR;
325     return mi->txt_data;
326 }
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 {
351     errno = 0;
352 
353 #ifdef HAVE_MMAP
354     (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     mi->txt_errno = errno;
370     close_mmap_files(mi);
371 
372     return mi->txt_errno;
373 }
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 */
383