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