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