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