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-2015 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 = FILE_WRITABLE(prot, flags) ? O_RDWR : O_RDONLY; 189 190 /* 191 * If you're not sharing the file and you are writing to it, 192 * then don't let anyone else have access to the file. 193 */ 194 if (((flags & MAP_SHARED) == 0) && (prot & PROT_WRITE)) 195 o_flag |= O_EXCL; 196 197 mapinfo->txt_fd = open(fname, o_flag); 198 if (mapinfo->txt_fd < 0) { 199 mapinfo->txt_errno = errno; 200 mapinfo->txt_fd = AO_INVALID_FD; 201 return; 202 } 203 } 204 205 /* 206 * Make sure we can stat the regular file. Save the file size. 207 */ 208 { 209 struct stat sb; 210 if (fstat(mapinfo->txt_fd, &sb) != 0) { 211 mapinfo->txt_errno = errno; 212 close(mapinfo->txt_fd); 213 return; 214 } 215 216 if (! S_ISREG(sb.st_mode)) { 217 mapinfo->txt_errno = errno = EINVAL; 218 close(mapinfo->txt_fd); 219 return; 220 } 221 222 mapinfo->txt_size = (size_t)sb.st_size; 223 } 224 225 if (mapinfo->txt_fd == AO_INVALID_FD) 226 mapinfo->txt_errno = errno; 227 } 228 229 /** 230 * Close any files opened by the mapping. 231 * 232 * @param mi a structure holding everything we need to know about the map. 233 */ 234 static void 235 close_mmap_files(tmap_info_t * mi) 236 { 237 if (mi->txt_fd == AO_INVALID_FD) 238 return; 239 240 close(mi->txt_fd); 241 mi->txt_fd = AO_INVALID_FD; 242 243 #if defined(HAVE_MMAP) && ! defined(MAP_ANONYMOUS) 244 if (mi->txt_zero_fd == AO_INVALID_FD) 245 return; 246 247 close(mi->txt_zero_fd); 248 mi->txt_zero_fd = AO_INVALID_FD; 249 #endif 250 } 251 252 /*=export_func text_mmap 253 * private: 254 * 255 * what: map a text file with terminating NUL 256 * 257 * arg: char const *, pzFile, name of the file to map 258 * arg: int, prot, mmap protections (see mmap(2)) 259 * arg: int, flags, mmap flags (see mmap(2)) 260 * arg: tmap_info_t *, mapinfo, returned info about the mapping 261 * 262 * ret-type: void * 263 * ret-desc: The mmaped data address 264 * 265 * doc: 266 * 267 * This routine will mmap a file into memory ensuring that there is at least 268 * one @file{NUL} character following the file data. It will return the 269 * address where the file contents have been mapped into memory. If there is a 270 * problem, then it will return @code{MAP_FAILED} and set @code{errno} 271 * appropriately. 272 * 273 * The named file does not exist, @code{stat(2)} will set @code{errno} as it 274 * will. If the file is not a regular file, @code{errno} will be 275 * @code{EINVAL}. At that point, @code{open(2)} is attempted with the access 276 * bits set appropriately for the requested @code{mmap(2)} protections and flag 277 * bits. On failure, @code{errno} will be set according to the documentation 278 * for @code{open(2)}. If @code{mmap(2)} fails, @code{errno} will be set as 279 * that routine sets it. If @code{text_mmap} works to this point, a valid 280 * address will be returned, but there may still be ``issues''. 281 * 282 * If the file size is not an even multiple of the system page size, then 283 * @code{text_map} will return at this point and @code{errno} will be zero. 284 * Otherwise, an anonymous map is attempted. If not available, then an attempt 285 * is made to @code{mmap(2)} @file{/dev/zero}. If any of these fail, the 286 * address of the file's data is returned, bug @code{no} @file{NUL} characters 287 * are mapped after the end of the data. 288 * 289 * see: mmap(2), open(2), stat(2) 290 * 291 * err: Any error code issued by mmap(2), open(2), stat(2) is possible. 292 * Additionally, if the specified file is not a regular file, then 293 * errno will be set to @code{EINVAL}. 294 * 295 * example: 296 * #include <mylib.h> 297 * tmap_info_t mi; 298 * int no_nul; 299 * void * data = text_mmap("file", PROT_WRITE, MAP_PRIVATE, &mi); 300 * if (data == MAP_FAILED) return; 301 * no_nul = (mi.txt_size == mi.txt_full_size); 302 * << use the data >> 303 * text_munmap(&mi); 304 =*/ 305 void * 306 text_mmap(char const * pzFile, int prot, int flags, tmap_info_t * mi) 307 { 308 validate_mmap(pzFile, prot, flags, mi); 309 if (mi->txt_errno != 0) 310 return MAP_FAILED_PTR; 311 312 load_text_file(mi, pzFile); 313 314 if (mi->txt_errno == 0) 315 return mi->txt_data; 316 317 close_mmap_files(mi); 318 319 errno = mi->txt_errno; 320 mi->txt_data = MAP_FAILED_PTR; 321 return mi->txt_data; 322 } 323 324 325 /*=export_func text_munmap 326 * private: 327 * 328 * what: unmap the data mapped in by text_mmap 329 * 330 * arg: tmap_info_t *, mapinfo, info about the mapping 331 * 332 * ret-type: int 333 * ret-desc: -1 or 0. @code{errno} will have the error code. 334 * 335 * doc: 336 * 337 * This routine will unmap the data mapped in with @code{text_mmap} and close 338 * the associated file descriptors opened by that function. 339 * 340 * see: munmap(2), close(2) 341 * 342 * err: Any error code issued by munmap(2) or close(2) is possible. 343 =*/ 344 int 345 text_munmap(tmap_info_t * mi) 346 { 347 errno = 0; 348 349 #ifdef HAVE_MMAP 350 (void)munmap(mi->txt_data, mi->txt_full_size); 351 352 #else /* don't HAVE_MMAP */ 353 /* 354 * IF the memory is writable *AND* it is not private (copy-on-write) 355 * *AND* the memory is "sharable" (seen by other processes) 356 * THEN rewrite the data. Emulate mmap visibility. 357 */ 358 if ( FILE_WRITABLE(mi->txt_prot, mi->txt_flags) 359 && (lseek(mi->txt_fd, 0, SEEK_SET) >= 0) ) { 360 write(mi->txt_fd, mi->txt_data, mi->txt_size); 361 } 362 363 free(mi->txt_data); 364 #endif /* HAVE_MMAP */ 365 366 mi->txt_errno = errno; 367 close_mmap_files(mi); 368 369 return mi->txt_errno; 370 } 371 372 /** @} 373 * 374 * Local Variables: 375 * mode: C 376 * c-file-style: "stroustrup" 377 * indent-tabs-mode: nil 378 * End: 379 * end of autoopts/text_mmap.c */ 380