xref: /netbsd-src/external/bsd/ntp/dist/sntp/libopts/text_mmap.c (revision eabc0478de71e4e011a5b4e0392741e01d491794)
1*eabc0478Schristos /*	$NetBSD: text_mmap.c,v 1.6 2024/08/18 20:47:25 christos Exp $	*/
2abb0f93cSkardel 
3f003fb54Skardel /**
48585484eSchristos  * @file text_mmap.c
5abb0f93cSkardel  *
68585484eSchristos  * Map a text file, ensuring the text always has an ending NUL byte.
7abb0f93cSkardel  *
88585484eSchristos  * @addtogroup autoopts
98585484eSchristos  * @{
108585484eSchristos  */
118585484eSchristos /*
12abb0f93cSkardel  *  This file is part of AutoOpts, a companion to AutoGen.
13abb0f93cSkardel  *  AutoOpts is free software.
14*eabc0478Schristos  *  AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
15abb0f93cSkardel  *
16abb0f93cSkardel  *  AutoOpts is available under any one of two licenses.  The license
17abb0f93cSkardel  *  in use must be one of these two and the choice is under the control
18abb0f93cSkardel  *  of the user of the license.
19abb0f93cSkardel  *
20abb0f93cSkardel  *   The GNU Lesser General Public License, version 3 or later
21abb0f93cSkardel  *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
22abb0f93cSkardel  *
23abb0f93cSkardel  *   The Modified Berkeley Software Distribution License
24abb0f93cSkardel  *      See the file "COPYING.mbsd"
25abb0f93cSkardel  *
268585484eSchristos  *  These files have the following sha256 sums:
27abb0f93cSkardel  *
288585484eSchristos  *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
298585484eSchristos  *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
308585484eSchristos  *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
31abb0f93cSkardel  */
328585484eSchristos #if defined(HAVE_MMAP)
33abb0f93cSkardel #  ifndef      MAP_ANONYMOUS
34abb0f93cSkardel #    ifdef     MAP_ANON
35abb0f93cSkardel #      define  MAP_ANONYMOUS   MAP_ANON
36abb0f93cSkardel #    endif
37abb0f93cSkardel #  endif
38abb0f93cSkardel 
398585484eSchristos #  if ! defined(MAP_ANONYMOUS) && ! defined(HAVE_DEV_ZERO)
408585484eSchristos      /*
418585484eSchristos       * We must have either /dev/zero or anonymous mapping for
428585484eSchristos       * this to work.
438585484eSchristos       */
448585484eSchristos #    undef HAVE_MMAP
458585484eSchristos 
468585484eSchristos #  else
478585484eSchristos #    ifdef _SC_PAGESIZE
488585484eSchristos #      define GETPAGESIZE() sysconf(_SC_PAGESIZE)
498585484eSchristos #    else
508585484eSchristos #      define GETPAGESIZE() getpagesize()
518585484eSchristos #    endif
528585484eSchristos #  endif
538585484eSchristos #endif
548585484eSchristos 
55abb0f93cSkardel /*
56abb0f93cSkardel  *  Some weird systems require that a specifically invalid FD number
57abb0f93cSkardel  *  get passed in as an argument value.  Which value is that?  Well,
58abb0f93cSkardel  *  as everybody knows, if open(2) fails, it returns -1, so that must
59abb0f93cSkardel  *  be the value.  :)
60abb0f93cSkardel  */
61abb0f93cSkardel #define AO_INVALID_FD  -1
62abb0f93cSkardel 
63abb0f93cSkardel #define FILE_WRITABLE(_prt,_flg) \
64abb0f93cSkardel         (   (_prt & PROT_WRITE) \
65abb0f93cSkardel          && ((_flg & (MAP_SHARED|MAP_PRIVATE)) == MAP_SHARED))
665d681e99Schristos #define MAP_FAILED_PTR (VOIDP(MAP_FAILED))
67abb0f93cSkardel 
688585484eSchristos /**
698585484eSchristos  * Load the contents of a text file.  There are two separate implementations,
708585484eSchristos  * depending up on whether mmap(3) is available.
718585484eSchristos  *
728585484eSchristos  *  If not available, malloc the file length plus one byte.  Read it in
738585484eSchristos  *  and NUL terminate.
748585484eSchristos  *
758585484eSchristos  *  If available, first check to see if the text file size is a multiple of a
768585484eSchristos  *  page size.  If it is, map the file size plus an extra page from either
778585484eSchristos  *  anonymous memory or from /dev/zero.  Then map the file text on top of the
788585484eSchristos  *  first pages of the anonymous/zero pages.  Otherwise, just map the file
798585484eSchristos  *  because there will be NUL bytes provided at the end.
808585484eSchristos  *
818585484eSchristos  * @param mapinfo a structure holding everything we need to know
828585484eSchristos  *        about the mapping.
838585484eSchristos  *
848585484eSchristos  * @param pzFile name of the file, for error reporting.
858585484eSchristos  */
868585484eSchristos static void
878585484eSchristos load_text_file(tmap_info_t * mapinfo, char const * pzFile)
888585484eSchristos {
898585484eSchristos #if ! defined(HAVE_MMAP)
908585484eSchristos     mapinfo->txt_data = AGALOC(mapinfo->txt_size+1, "file text");
918585484eSchristos     if (mapinfo->txt_data == NULL) {
928585484eSchristos         mapinfo->txt_errno = ENOMEM;
938585484eSchristos         return;
948585484eSchristos     }
958585484eSchristos 
968585484eSchristos     {
978585484eSchristos         size_t sz = mapinfo->txt_size;
988585484eSchristos         char * pz = mapinfo->txt_data;
998585484eSchristos 
1008585484eSchristos         while (sz > 0) {
1018585484eSchristos             ssize_t rdct = read(mapinfo->txt_fd, pz, sz);
1028585484eSchristos             if (rdct <= 0) {
1038585484eSchristos                 mapinfo->txt_errno = errno;
1048585484eSchristos                 fserr_warn("libopts", "read", pzFile);
1058585484eSchristos                 free(mapinfo->txt_data);
1068585484eSchristos                 return;
1078585484eSchristos             }
1088585484eSchristos 
1098585484eSchristos             pz += rdct;
1108585484eSchristos             sz -= rdct;
1118585484eSchristos         }
1128585484eSchristos 
1138585484eSchristos         *pz = NUL;
1148585484eSchristos     }
1158585484eSchristos 
1168585484eSchristos     mapinfo->txt_errno   = 0;
1178585484eSchristos 
1188585484eSchristos #else /* HAVE mmap */
1198585484eSchristos     size_t const pgsz = (size_t)GETPAGESIZE();
1208585484eSchristos     void * map_addr   = NULL;
1218585484eSchristos 
1228585484eSchristos     (void)pzFile;
1238585484eSchristos 
1248585484eSchristos     mapinfo->txt_full_size = (mapinfo->txt_size + pgsz) & ~(pgsz - 1);
1258585484eSchristos     if (mapinfo->txt_full_size == (mapinfo->txt_size + pgsz)) {
1268585484eSchristos         /*
1278585484eSchristos          * The text is a multiple of a page boundary.  We must map an
1288585484eSchristos          * extra page so the text ends with a NUL.
1298585484eSchristos          */
1308585484eSchristos #if defined(MAP_ANONYMOUS)
1318585484eSchristos         map_addr = mmap(NULL, mapinfo->txt_full_size, PROT_READ|PROT_WRITE,
1328585484eSchristos                         MAP_ANONYMOUS|MAP_PRIVATE, AO_INVALID_FD, 0);
1338585484eSchristos #else
1348585484eSchristos         mapinfo->txt_zero_fd = open("/dev/zero", O_RDONLY);
1358585484eSchristos 
1368585484eSchristos         if (mapinfo->txt_zero_fd == AO_INVALID_FD) {
1378585484eSchristos             mapinfo->txt_errno = errno;
1388585484eSchristos             return;
1398585484eSchristos         }
1408585484eSchristos         map_addr = mmap(NULL, mapinfo->txt_full_size, PROT_READ|PROT_WRITE,
1418585484eSchristos                         MAP_PRIVATE, mapinfo->txt_zero_fd, 0);
1428585484eSchristos #endif
1438585484eSchristos         if (map_addr == MAP_FAILED_PTR) {
1448585484eSchristos             mapinfo->txt_errno = errno;
1458585484eSchristos             return;
1468585484eSchristos         }
1478585484eSchristos         mapinfo->txt_flags |= MAP_FIXED;
1488585484eSchristos     }
1498585484eSchristos 
1508585484eSchristos     mapinfo->txt_data =
1518585484eSchristos         mmap(map_addr, mapinfo->txt_size, mapinfo->txt_prot,
1528585484eSchristos              mapinfo->txt_flags, mapinfo->txt_fd, 0);
1538585484eSchristos 
1548585484eSchristos     if (mapinfo->txt_data == MAP_FAILED_PTR)
1558585484eSchristos         mapinfo->txt_errno = errno;
1568585484eSchristos #endif /* HAVE_MMAP */
1578585484eSchristos }
1588585484eSchristos 
1598585484eSchristos /**
1608585484eSchristos  * Make sure all the parameters are correct:  we have a file name that
1618585484eSchristos  * is a text file that we can read.
1628585484eSchristos  *
1638585484eSchristos  * @param fname the text file to map
1648585484eSchristos  * @param prot  the memory protections requested (read/write/etc.)
1658585484eSchristos  * @param flags mmap flags
1668585484eSchristos  * @param mapinfo a structure holding everything we need to know
1678585484eSchristos  *        about the mapping.
1688585484eSchristos  */
1698585484eSchristos static void
1708585484eSchristos validate_mmap(char const * fname, int prot, int flags, tmap_info_t * mapinfo)
1718585484eSchristos {
1728585484eSchristos     memset(mapinfo, 0, sizeof(*mapinfo));
1738585484eSchristos #if defined(HAVE_MMAP) && ! defined(MAP_ANONYMOUS)
1748585484eSchristos     mapinfo->txt_zero_fd = AO_INVALID_FD;
1758585484eSchristos #endif
1768585484eSchristos     mapinfo->txt_fd      = AO_INVALID_FD;
1778585484eSchristos     mapinfo->txt_prot    = prot;
1788585484eSchristos     mapinfo->txt_flags   = flags;
1798585484eSchristos 
1808585484eSchristos     /*
1818585484eSchristos      *  Map mmap flags and protections into open flags and do the open.
1828585484eSchristos      */
1838585484eSchristos     {
1848585484eSchristos         /*
1858585484eSchristos          *  See if we will be updating the file.  If we can alter the memory
1868585484eSchristos          *  and if we share the data and we are *not* copy-on-writing the data,
1878585484eSchristos          *  then our updates will show in the file, so we must open with
1888585484eSchristos          *  write access.
1898585484eSchristos          */
190*eabc0478Schristos         int o_flag =
191*eabc0478Schristos #ifdef _WIN32
192*eabc0478Schristos             _O_BINARY |
193*eabc0478Schristos #endif
194*eabc0478Schristos             FILE_WRITABLE(prot, flags) ? O_RDWR : O_RDONLY;
1958585484eSchristos 
1968585484eSchristos         /*
1978585484eSchristos          *  If you're not sharing the file and you are writing to it,
1988585484eSchristos          *  then don't let anyone else have access to the file.
1998585484eSchristos          */
2008585484eSchristos         if (((flags & MAP_SHARED) == 0) && (prot & PROT_WRITE))
2018585484eSchristos             o_flag |= O_EXCL;
2028585484eSchristos 
2038585484eSchristos         mapinfo->txt_fd = open(fname, o_flag);
2048585484eSchristos         if (mapinfo->txt_fd < 0) {
2058585484eSchristos             mapinfo->txt_errno = errno;
2068585484eSchristos             mapinfo->txt_fd = AO_INVALID_FD;
2078585484eSchristos             return;
2088585484eSchristos         }
2098585484eSchristos     }
2108585484eSchristos 
2118585484eSchristos     /*
2128585484eSchristos      *  Make sure we can stat the regular file.  Save the file size.
2138585484eSchristos      */
2148585484eSchristos     {
2158585484eSchristos         struct stat sb;
2168585484eSchristos         if (fstat(mapinfo->txt_fd, &sb) != 0) {
2178585484eSchristos             mapinfo->txt_errno = errno;
2188585484eSchristos             close(mapinfo->txt_fd);
2198585484eSchristos             return;
2208585484eSchristos         }
2218585484eSchristos 
2228585484eSchristos         if (! S_ISREG(sb.st_mode)) {
2238585484eSchristos             mapinfo->txt_errno = errno = EINVAL;
2248585484eSchristos             close(mapinfo->txt_fd);
2258585484eSchristos             return;
2268585484eSchristos         }
2278585484eSchristos 
2288585484eSchristos         mapinfo->txt_size = (size_t)sb.st_size;
2298585484eSchristos     }
2308585484eSchristos 
2318585484eSchristos     if (mapinfo->txt_fd == AO_INVALID_FD)
2328585484eSchristos         mapinfo->txt_errno = errno;
2338585484eSchristos }
2348585484eSchristos 
2358585484eSchristos /**
2368585484eSchristos  * Close any files opened by the mapping.
2378585484eSchristos  *
2388585484eSchristos  * @param mi a structure holding everything we need to know about the map.
2398585484eSchristos  */
2408585484eSchristos static void
2418585484eSchristos close_mmap_files(tmap_info_t * mi)
2428585484eSchristos {
2438585484eSchristos     if (mi->txt_fd == AO_INVALID_FD)
2448585484eSchristos         return;
2458585484eSchristos 
2468585484eSchristos     close(mi->txt_fd);
2478585484eSchristos     mi->txt_fd = AO_INVALID_FD;
2488585484eSchristos 
2498585484eSchristos #if defined(HAVE_MMAP) && ! defined(MAP_ANONYMOUS)
2508585484eSchristos     if (mi->txt_zero_fd == AO_INVALID_FD)
2518585484eSchristos         return;
2528585484eSchristos 
2538585484eSchristos     close(mi->txt_zero_fd);
2548585484eSchristos     mi->txt_zero_fd = AO_INVALID_FD;
2558585484eSchristos #endif
2568585484eSchristos }
2578585484eSchristos 
258abb0f93cSkardel /*=export_func  text_mmap
259abb0f93cSkardel  * private:
260abb0f93cSkardel  *
261abb0f93cSkardel  * what:  map a text file with terminating NUL
262abb0f93cSkardel  *
263abb0f93cSkardel  * arg:   char const *,  pzFile,  name of the file to map
264abb0f93cSkardel  * arg:   int,           prot,    mmap protections (see mmap(2))
265abb0f93cSkardel  * arg:   int,           flags,   mmap flags (see mmap(2))
266abb0f93cSkardel  * arg:   tmap_info_t *, mapinfo, returned info about the mapping
267abb0f93cSkardel  *
268abb0f93cSkardel  * ret-type:   void *
269abb0f93cSkardel  * ret-desc:   The mmaped data address
270abb0f93cSkardel  *
271abb0f93cSkardel  * doc:
272abb0f93cSkardel  *
273abb0f93cSkardel  * This routine will mmap a file into memory ensuring that there is at least
274abb0f93cSkardel  * one @file{NUL} character following the file data.  It will return the
275abb0f93cSkardel  * address where the file contents have been mapped into memory.  If there is a
2768585484eSchristos  * problem, then it will return @code{MAP_FAILED} and set @code{errno}
277abb0f93cSkardel  * appropriately.
278abb0f93cSkardel  *
2798585484eSchristos  * The named file does not exist, @code{stat(2)} will set @code{errno} as it
2808585484eSchristos  * will.  If the file is not a regular file, @code{errno} will be
281abb0f93cSkardel  * @code{EINVAL}.  At that point, @code{open(2)} is attempted with the access
282abb0f93cSkardel  * bits set appropriately for the requested @code{mmap(2)} protections and flag
2838585484eSchristos  * bits.  On failure, @code{errno} will be set according to the documentation
2848585484eSchristos  * for @code{open(2)}.  If @code{mmap(2)} fails, @code{errno} will be set as
285abb0f93cSkardel  * that routine sets it.  If @code{text_mmap} works to this point, a valid
286abb0f93cSkardel  * address will be returned, but there may still be ``issues''.
287abb0f93cSkardel  *
288abb0f93cSkardel  * If the file size is not an even multiple of the system page size, then
2898585484eSchristos  * @code{text_map} will return at this point and @code{errno} will be zero.
290abb0f93cSkardel  * Otherwise, an anonymous map is attempted.  If not available, then an attempt
291abb0f93cSkardel  * is made to @code{mmap(2)} @file{/dev/zero}.  If any of these fail, the
292abb0f93cSkardel  * address of the file's data is returned, bug @code{no} @file{NUL} characters
293abb0f93cSkardel  * are mapped after the end of the data.
294abb0f93cSkardel  *
295abb0f93cSkardel  * see: mmap(2), open(2), stat(2)
296abb0f93cSkardel  *
297abb0f93cSkardel  * err: Any error code issued by mmap(2), open(2), stat(2) is possible.
298abb0f93cSkardel  *      Additionally, if the specified file is not a regular file, then
299abb0f93cSkardel  *      errno will be set to @code{EINVAL}.
300abb0f93cSkardel  *
301abb0f93cSkardel  * example:
302abb0f93cSkardel  * #include <mylib.h>
303abb0f93cSkardel  * tmap_info_t mi;
304abb0f93cSkardel  * int no_nul;
305abb0f93cSkardel  * void * data = text_mmap("file", PROT_WRITE, MAP_PRIVATE, &mi);
306abb0f93cSkardel  * if (data == MAP_FAILED) return;
307abb0f93cSkardel  * no_nul = (mi.txt_size == mi.txt_full_size);
308abb0f93cSkardel  * << use the data >>
309abb0f93cSkardel  * text_munmap(&mi);
310abb0f93cSkardel =*/
311abb0f93cSkardel void *
3128585484eSchristos text_mmap(char const * pzFile, int prot, int flags, tmap_info_t * mi)
313abb0f93cSkardel {
3148585484eSchristos     validate_mmap(pzFile, prot, flags, mi);
3158585484eSchristos     if (mi->txt_errno != 0)
316abb0f93cSkardel         return MAP_FAILED_PTR;
317abb0f93cSkardel 
3188585484eSchristos     load_text_file(mi, pzFile);
319abb0f93cSkardel 
3208585484eSchristos     if (mi->txt_errno == 0)
3218585484eSchristos         return mi->txt_data;
322abb0f93cSkardel 
3238585484eSchristos     close_mmap_files(mi);
324abb0f93cSkardel 
3258585484eSchristos     errno = mi->txt_errno;
3268585484eSchristos     mi->txt_data = MAP_FAILED_PTR;
3278585484eSchristos     return mi->txt_data;
328abb0f93cSkardel }
329abb0f93cSkardel 
330abb0f93cSkardel 
331abb0f93cSkardel /*=export_func  text_munmap
332abb0f93cSkardel  * private:
333abb0f93cSkardel  *
334abb0f93cSkardel  * what:  unmap the data mapped in by text_mmap
335abb0f93cSkardel  *
336abb0f93cSkardel  * arg:   tmap_info_t *, mapinfo, info about the mapping
337abb0f93cSkardel  *
338abb0f93cSkardel  * ret-type:   int
3398585484eSchristos  * ret-desc:   -1 or 0.  @code{errno} will have the error code.
340abb0f93cSkardel  *
341abb0f93cSkardel  * doc:
342abb0f93cSkardel  *
343abb0f93cSkardel  * This routine will unmap the data mapped in with @code{text_mmap} and close
344abb0f93cSkardel  * the associated file descriptors opened by that function.
345abb0f93cSkardel  *
346abb0f93cSkardel  * see: munmap(2), close(2)
347abb0f93cSkardel  *
348abb0f93cSkardel  * err: Any error code issued by munmap(2) or close(2) is possible.
349abb0f93cSkardel =*/
350abb0f93cSkardel int
3518585484eSchristos text_munmap(tmap_info_t * mi)
352abb0f93cSkardel {
3538585484eSchristos     errno = 0;
3548585484eSchristos 
355abb0f93cSkardel #ifdef HAVE_MMAP
3568585484eSchristos     (void)munmap(mi->txt_data, mi->txt_full_size);
357abb0f93cSkardel 
358*eabc0478Schristos #else // don't HAVE_MMAP
359abb0f93cSkardel     /*
360abb0f93cSkardel      *  IF the memory is writable *AND* it is not private (copy-on-write)
361abb0f93cSkardel      *     *AND* the memory is "sharable" (seen by other processes)
3628585484eSchristos      *  THEN rewrite the data.  Emulate mmap visibility.
363abb0f93cSkardel      */
3648585484eSchristos     if (  FILE_WRITABLE(mi->txt_prot, mi->txt_flags)
365*eabc0478Schristos        && (lseek(mi->txt_fd, 0, SEEK_SET) >= 0) )
3668585484eSchristos         write(mi->txt_fd, mi->txt_data, mi->txt_size);
367abb0f93cSkardel 
3688585484eSchristos     free(mi->txt_data);
369abb0f93cSkardel #endif /* HAVE_MMAP */
3708585484eSchristos 
3718585484eSchristos     mi->txt_errno = errno;
3728585484eSchristos     close_mmap_files(mi);
3738585484eSchristos 
3748585484eSchristos     return mi->txt_errno;
375abb0f93cSkardel }
376abb0f93cSkardel 
3778585484eSchristos /** @}
3788585484eSchristos  *
379abb0f93cSkardel  * Local Variables:
380abb0f93cSkardel  * mode: C
381abb0f93cSkardel  * c-file-style: "stroustrup"
382abb0f93cSkardel  * indent-tabs-mode: nil
383abb0f93cSkardel  * End:
384abb0f93cSkardel  * end of autoopts/text_mmap.c */
385