/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 */ |