xref: /netbsd-src/external/bsd/ntp/dist/sntp/libopts/text_mmap.c (revision a5847cc334d9a7029f6352b847e9e8d71a0f9e0c)
1 /*	$NetBSD: text_mmap.c,v 1.1.1.1 2009/12/13 16:57:22 kardel Exp $	*/
2 
3 /*
4  * Id: 14e1f51d1a5a31d8395fdf1e93a07bace1c59e87
5  *
6  * Time-stamp:      "2007-07-04 11:35:49 bkorb"
7  *
8  *  This file is part of AutoOpts, a companion to AutoGen.
9  *  AutoOpts is free software.
10  *  AutoOpts is copyright (c) 1992-2009 by Bruce Korb - all rights reserved
11  *
12  *  AutoOpts is available under any one of two licenses.  The license
13  *  in use must be one of these two and the choice is under the control
14  *  of the user of the license.
15  *
16  *   The GNU Lesser General Public License, version 3 or later
17  *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
18  *
19  *   The Modified Berkeley Software Distribution License
20  *      See the file "COPYING.mbsd"
21  *
22  *  These files have the following md5sums:
23  *
24  *  43b91e8ca915626ed3818ffb1b71248b pkg/libopts/COPYING.gplv3
25  *  06a1a2e4760c90ea5e1dad8dfaac4d39 pkg/libopts/COPYING.lgplv3
26  *  66a5cedaf62c4b2637025f049f9b826f pkg/libopts/COPYING.mbsd
27  */
28 
29 #ifndef MAP_ANONYMOUS
30 #  ifdef   MAP_ANON
31 #  define  MAP_ANONYMOUS   MAP_ANON
32 #  endif
33 #endif
34 
35 /*
36  *  Some weird systems require that a specifically invalid FD number
37  *  get passed in as an argument value.  Which value is that?  Well,
38  *  as everybody knows, if open(2) fails, it returns -1, so that must
39  *  be the value.  :)
40  */
41 #define AO_INVALID_FD  -1
42 
43 #define FILE_WRITABLE(_prt,_flg) \
44         (   (_prt & PROT_WRITE) \
45          && ((_flg & (MAP_SHARED|MAP_PRIVATE)) == MAP_SHARED))
46 #define MAP_FAILED_PTR ((void*)MAP_FAILED)
47 
48 /*=export_func  text_mmap
49  * private:
50  *
51  * what:  map a text file with terminating NUL
52  *
53  * arg:   char const*,  pzFile,  name of the file to map
54  * arg:   int,          prot,    mmap protections (see mmap(2))
55  * arg:   int,          flags,   mmap flags (see mmap(2))
56  * arg:   tmap_info_t*, mapinfo, returned info about the mapping
57  *
58  * ret-type:   void*
59  * ret-desc:   The mmaped data address
60  *
61  * doc:
62  *
63  * This routine will mmap a file into memory ensuring that there is at least
64  * one @file{NUL} character following the file data.  It will return the
65  * address where the file contents have been mapped into memory.  If there is a
66  * problem, then it will return @code{MAP_FAILED} and set @file{errno}
67  * appropriately.
68  *
69  * The named file does not exist, @code{stat(2)} will set @file{errno} as it
70  * will.  If the file is not a regular file, @file{errno} will be
71  * @code{EINVAL}.  At that point, @code{open(2)} is attempted with the access
72  * bits set appropriately for the requested @code{mmap(2)} protections and flag
73  * bits.  On failure, @file{errno} will be set according to the documentation
74  * for @code{open(2)}.  If @code{mmap(2)} fails, @file{errno} will be set as
75  * that routine sets it.  If @code{text_mmap} works to this point, a valid
76  * address will be returned, but there may still be ``issues''.
77  *
78  * If the file size is not an even multiple of the system page size, then
79  * @code{text_map} will return at this point and @file{errno} will be zero.
80  * Otherwise, an anonymous map is attempted.  If not available, then an attempt
81  * is made to @code{mmap(2)} @file{/dev/zero}.  If any of these fail, the
82  * address of the file's data is returned, bug @code{no} @file{NUL} characters
83  * are mapped after the end of the data.
84  *
85  * see: mmap(2), open(2), stat(2)
86  *
87  * err: Any error code issued by mmap(2), open(2), stat(2) is possible.
88  *      Additionally, if the specified file is not a regular file, then
89  *      errno will be set to @code{EINVAL}.
90  *
91  * example:
92  * #include <mylib.h>
93  * tmap_info_t mi;
94  * int no_nul;
95  * void* data = text_mmap( "file", PROT_WRITE, MAP_PRIVATE, &mi );
96  * if (data == MAP_FAILED) return;
97  * no_nul = (mi.txt_size == mi.txt_full_size);
98  * << use the data >>
99  * text_munmap( &mi );
100 =*/
101 void*
102 text_mmap( char const* pzFile, int prot, int flags, tmap_info_t* pMI )
103 {
104     memset( pMI, 0, sizeof(*pMI) );
105 #ifdef HAVE_MMAP
106     pMI->txt_zero_fd = -1;
107 #endif
108     pMI->txt_fd = -1;
109 
110     /*
111      *  Make sure we can stat the regular file.  Save the file size.
112      */
113     {
114         struct stat sb;
115         if (stat( pzFile, &sb ) != 0) {
116             pMI->txt_errno = errno;
117             return MAP_FAILED_PTR;
118         }
119 
120         if (! S_ISREG( sb.st_mode )) {
121             pMI->txt_errno = errno = EINVAL;
122             return MAP_FAILED_PTR;
123         }
124 
125         pMI->txt_size = sb.st_size;
126     }
127 
128     /*
129      *  Map mmap flags and protections into open flags and do the open.
130      */
131     {
132         int o_flag;
133         /*
134          *  See if we will be updating the file.  If we can alter the memory
135          *  and if we share the data and we are *not* copy-on-writing the data,
136          *  then our updates will show in the file, so we must open with
137          *  write access.
138          */
139         if (FILE_WRITABLE(prot,flags))
140             o_flag = O_RDWR;
141         else
142             o_flag = O_RDONLY;
143 
144         /*
145          *  If you're not sharing the file and you are writing to it,
146          *  then don't let anyone else have access to the file.
147          */
148         if (((flags & MAP_SHARED) == 0) && (prot & PROT_WRITE))
149             o_flag |= O_EXCL;
150 
151         pMI->txt_fd = open( pzFile, o_flag );
152     }
153 
154     if (pMI->txt_fd == AO_INVALID_FD) {
155         pMI->txt_errno = errno;
156         return MAP_FAILED_PTR;
157     }
158 
159 #ifdef HAVE_MMAP /* * * * * WITH MMAP * * * * * */
160     /*
161      *  do the mmap.  If we fail, then preserve errno, close the file and
162      *  return the failure.
163      */
164     pMI->txt_data =
165         mmap(NULL, pMI->txt_size+1, prot, flags, pMI->txt_fd, (size_t)0);
166     if (pMI->txt_data == MAP_FAILED_PTR) {
167         pMI->txt_errno = errno;
168         goto fail_return;
169     }
170 
171     /*
172      *  Most likely, everything will turn out fine now.  The only difficult
173      *  part at this point is coping with files with sizes that are a multiple
174      *  of the page size.  Handling that is what this whole thing is about.
175      */
176     pMI->txt_zero_fd = -1;
177     pMI->txt_errno   = 0;
178 
179     {
180         void* pNuls;
181 #ifdef _SC_PAGESIZE
182         size_t pgsz = sysconf(_SC_PAGESIZE);
183 #else
184         size_t pgsz = getpagesize();
185 #endif
186         /*
187          *  Compute the pagesize rounded mapped memory size.
188          *  IF this is not the same as the file size, then there are NUL's
189          *  at the end of the file mapping and all is okay.
190          */
191         pMI->txt_full_size = (pMI->txt_size + (pgsz - 1)) & ~(pgsz - 1);
192         if (pMI->txt_size != pMI->txt_full_size)
193             return pMI->txt_data;
194 
195         /*
196          *  Still here?  We have to remap the trailing inaccessible page
197          *  either anonymously or to /dev/zero.
198          */
199         pMI->txt_full_size += pgsz;
200 #if defined(MAP_ANONYMOUS)
201         pNuls = mmap(
202                 (void*)(((char*)pMI->txt_data) + pMI->txt_size),
203                 pgsz, PROT_READ|PROT_WRITE,
204                 MAP_ANONYMOUS|MAP_FIXED|MAP_PRIVATE, AO_INVALID_FD, (size_t)0);
205 
206         if (pNuls != MAP_FAILED_PTR)
207             return pMI->txt_data;
208 
209         pMI->txt_errno = errno;
210 
211 #elif defined(HAVE_DEV_ZERO)
212         pMI->txt_zero_fd = open( "/dev/zero", O_RDONLY );
213 
214         if (pMI->txt_zero_fd == AO_INVALID_FD) {
215             pMI->txt_errno = errno;
216 
217         } else {
218             pNuls = mmap(
219                     (void*)(((char*)pMI->txt_data) + pMI->txt_size), pgsz,
220                     PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED,
221                     pMI->txt_zero_fd, 0 );
222 
223             if (pNuls != MAP_FAILED_PTR)
224                 return pMI->txt_data;
225 
226             pMI->txt_errno = errno;
227             close( pMI->txt_zero_fd );
228             pMI->txt_zero_fd = -1;
229         }
230 #endif
231 
232         pMI->txt_full_size = pMI->txt_size;
233     }
234 
235     {
236         void* p = AGALOC( pMI->txt_size+1, "file text" );
237         memcpy( p, pMI->txt_data, pMI->txt_size );
238         ((char*)p)[pMI->txt_size] = NUL;
239         munmap(pMI->txt_data, pMI->txt_size );
240         pMI->txt_data = p;
241     }
242     pMI->txt_alloc = 1;
243     return pMI->txt_data;
244 
245 #else /* * * * * * no HAVE_MMAP * * * * * */
246 
247     pMI->txt_data = AGALOC( pMI->txt_size+1, "file text" );
248     if (pMI->txt_data == NULL) {
249         pMI->txt_errno = ENOMEM;
250         goto fail_return;
251     }
252 
253     {
254         size_t sz = pMI->txt_size;
255         char*  pz = pMI->txt_data;
256 
257         while (sz > 0) {
258             ssize_t rdct = read( pMI->txt_fd, pz, sz );
259             if (rdct <= 0) {
260                 pMI->txt_errno = errno;
261                 fprintf( stderr, zFSErrReadFile,
262                          errno, strerror( errno ), pzFile );
263                 free( pMI->txt_data );
264                 goto fail_return;
265             }
266 
267             pz += rdct;
268             sz -= rdct;
269         }
270 
271         *pz = NUL;
272     }
273 
274     /*
275      *  We never need a dummy page mapped in
276      */
277     pMI->txt_zero_fd = -1;
278     pMI->txt_errno   = 0;
279 
280     return pMI->txt_data;
281 
282 #endif /* * * * * * no HAVE_MMAP * * * * * */
283 
284  fail_return:
285     if (pMI->txt_fd >= 0) {
286         close( pMI->txt_fd );
287         pMI->txt_fd = -1;
288     }
289     errno = pMI->txt_errno;
290     pMI->txt_data = MAP_FAILED_PTR;
291     return pMI->txt_data;
292 }
293 
294 
295 /*=export_func  text_munmap
296  * private:
297  *
298  * what:  unmap the data mapped in by text_mmap
299  *
300  * arg:   tmap_info_t*, mapinfo, info about the mapping
301  *
302  * ret-type:   int
303  * ret-desc:   -1 or 0.  @file{errno} will have the error code.
304  *
305  * doc:
306  *
307  * This routine will unmap the data mapped in with @code{text_mmap} and close
308  * the associated file descriptors opened by that function.
309  *
310  * see: munmap(2), close(2)
311  *
312  * err: Any error code issued by munmap(2) or close(2) is possible.
313 =*/
314 int
315 text_munmap( tmap_info_t* pMI )
316 {
317 #ifdef HAVE_MMAP
318     int res = 0;
319     if (pMI->txt_alloc) {
320         /*
321          *  IF the user has write permission and the text is not mapped private,
322          *  then write back any changes.  Hopefully, nobody else has modified
323          *  the file in the mean time.
324          */
325         if (   ((pMI->txt_prot & PROT_WRITE) != 0)
326             && ((pMI->txt_flags & MAP_PRIVATE) == 0))  {
327 
328             if (lseek(pMI->txt_fd, (size_t)0, SEEK_SET) != 0)
329                 goto error_return;
330 
331             res = (write( pMI->txt_fd, pMI->txt_data, pMI->txt_size ) < 0)
332                 ? errno : 0;
333         }
334 
335         AGFREE( pMI->txt_data );
336         errno = res;
337     } else {
338         res = munmap( pMI->txt_data, pMI->txt_full_size );
339     }
340     if (res != 0)
341         goto error_return;
342 
343     res = close( pMI->txt_fd );
344     if (res != 0)
345         goto error_return;
346 
347     pMI->txt_fd = -1;
348     errno = 0;
349     if (pMI->txt_zero_fd != -1) {
350         res = close( pMI->txt_zero_fd );
351         pMI->txt_zero_fd = -1;
352     }
353 
354  error_return:
355     pMI->txt_errno = errno;
356     return res;
357 #else  /* HAVE_MMAP */
358 
359     errno = 0;
360     /*
361      *  IF the memory is writable *AND* it is not private (copy-on-write)
362      *     *AND* the memory is "sharable" (seen by other processes)
363      *  THEN rewrite the data.
364      */
365     if (   FILE_WRITABLE(pMI->txt_prot, pMI->txt_flags)
366         && (lseek( pMI->txt_fd, 0, SEEK_SET ) >= 0) ) {
367         write( pMI->txt_fd, pMI->txt_data, pMI->txt_size );
368     }
369 
370     close( pMI->txt_fd );
371     pMI->txt_fd = -1;
372     pMI->txt_errno = errno;
373     free( pMI->txt_data );
374 
375     return pMI->txt_errno;
376 #endif /* HAVE_MMAP */
377 }
378 
379 /*
380  * Local Variables:
381  * mode: C
382  * c-file-style: "stroustrup"
383  * indent-tabs-mode: nil
384  * End:
385  * end of autoopts/text_mmap.c */
386