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