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