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