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