1290001Sglebius/** 2290001Sglebius * @file text_mmap.c 3290001Sglebius * 4290001Sglebius * Map a text file, ensuring the text always has an ending NUL byte. 5290001Sglebius * 6290001Sglebius * @addtogroup autoopts 7290001Sglebius * @{ 8290001Sglebius */ 9181834Sroberto/* 10290001Sglebius * This file is part of AutoOpts, a companion to AutoGen. 11290001Sglebius * AutoOpts is free software. 12290001Sglebius * AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved 13181834Sroberto * 14290001Sglebius * AutoOpts is available under any one of two licenses. The license 15290001Sglebius * in use must be one of these two and the choice is under the control 16290001Sglebius * of the user of the license. 17290001Sglebius * 18290001Sglebius * The GNU Lesser General Public License, version 3 or later 19290001Sglebius * See the files "COPYING.lgplv3" and "COPYING.gplv3" 20290001Sglebius * 21290001Sglebius * The Modified Berkeley Software Distribution License 22290001Sglebius * See the file "COPYING.mbsd" 23290001Sglebius * 24290001Sglebius * These files have the following sha256 sums: 25290001Sglebius * 26290001Sglebius * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3 27290001Sglebius * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3 28290001Sglebius * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd 29181834Sroberto */ 30290001Sglebius#if defined(HAVE_MMAP) 31290001Sglebius# ifndef MAP_ANONYMOUS 32290001Sglebius# ifdef MAP_ANON 33290001Sglebius# define MAP_ANONYMOUS MAP_ANON 34290001Sglebius# endif 35290001Sglebius# endif 36181834Sroberto 37290001Sglebius# if ! defined(MAP_ANONYMOUS) && ! defined(HAVE_DEV_ZERO) 38290001Sglebius /* 39290001Sglebius * We must have either /dev/zero or anonymous mapping for 40290001Sglebius * this to work. 41290001Sglebius */ 42290001Sglebius# undef HAVE_MMAP 43290001Sglebius 44290001Sglebius# else 45290001Sglebius# ifdef _SC_PAGESIZE 46290001Sglebius# define GETPAGESIZE() sysconf(_SC_PAGESIZE) 47290001Sglebius# else 48290001Sglebius# define GETPAGESIZE() getpagesize() 49290001Sglebius# endif 50181834Sroberto# endif 51181834Sroberto#endif 52181834Sroberto 53181834Sroberto/* 54181834Sroberto * Some weird systems require that a specifically invalid FD number 55181834Sroberto * get passed in as an argument value. Which value is that? Well, 56181834Sroberto * as everybody knows, if open(2) fails, it returns -1, so that must 57181834Sroberto * be the value. :) 58181834Sroberto */ 59181834Sroberto#define AO_INVALID_FD -1 60181834Sroberto 61181834Sroberto#define FILE_WRITABLE(_prt,_flg) \ 62181834Sroberto ( (_prt & PROT_WRITE) \ 63181834Sroberto && ((_flg & (MAP_SHARED|MAP_PRIVATE)) == MAP_SHARED)) 64290001Sglebius#define MAP_FAILED_PTR (VOIDP(MAP_FAILED)) 65181834Sroberto 66290001Sglebius/** 67290001Sglebius * Load the contents of a text file. There are two separate implementations, 68290001Sglebius * depending up on whether mmap(3) is available. 69181834Sroberto * 70290001Sglebius * If not available, malloc the file length plus one byte. Read it in 71290001Sglebius * and NUL terminate. 72181834Sroberto * 73290001Sglebius * If available, first check to see if the text file size is a multiple of a 74290001Sglebius * page size. If it is, map the file size plus an extra page from either 75290001Sglebius * anonymous memory or from /dev/zero. Then map the file text on top of the 76290001Sglebius * first pages of the anonymous/zero pages. Otherwise, just map the file 77290001Sglebius * because there will be NUL bytes provided at the end. 78181834Sroberto * 79290001Sglebius * @param mapinfo a structure holding everything we need to know 80290001Sglebius * about the mapping. 81181834Sroberto * 82290001Sglebius * @param pzFile name of the file, for error reporting. 83290001Sglebius */ 84290001Sglebiusstatic void 85290001Sglebiusload_text_file(tmap_info_t * mapinfo, char const * pzFile) 86181834Sroberto{ 87290001Sglebius#if ! defined(HAVE_MMAP) 88290001Sglebius mapinfo->txt_data = AGALOC(mapinfo->txt_size+1, "file text"); 89290001Sglebius if (mapinfo->txt_data == NULL) { 90290001Sglebius mapinfo->txt_errno = ENOMEM; 91290001Sglebius return; 92290001Sglebius } 93181834Sroberto 94181834Sroberto { 95290001Sglebius size_t sz = mapinfo->txt_size; 96290001Sglebius char * pz = mapinfo->txt_data; 97181834Sroberto 98290001Sglebius while (sz > 0) { 99290001Sglebius ssize_t rdct = read(mapinfo->txt_fd, pz, sz); 100290001Sglebius if (rdct <= 0) { 101290001Sglebius mapinfo->txt_errno = errno; 102290001Sglebius fserr_warn("libopts", "read", pzFile); 103290001Sglebius free(mapinfo->txt_data); 104290001Sglebius return; 105290001Sglebius } 106290001Sglebius 107290001Sglebius pz += rdct; 108290001Sglebius sz -= rdct; 109181834Sroberto } 110181834Sroberto 111290001Sglebius *pz = NUL; 112181834Sroberto } 113181834Sroberto 114290001Sglebius mapinfo->txt_errno = 0; 115290001Sglebius 116290001Sglebius#else /* HAVE mmap */ 117290001Sglebius size_t const pgsz = (size_t)GETPAGESIZE(); 118290001Sglebius void * map_addr = NULL; 119290001Sglebius 120290001Sglebius (void)pzFile; 121290001Sglebius 122290001Sglebius mapinfo->txt_full_size = (mapinfo->txt_size + pgsz) & ~(pgsz - 1); 123290001Sglebius if (mapinfo->txt_full_size == (mapinfo->txt_size + pgsz)) { 124290001Sglebius /* 125290001Sglebius * The text is a multiple of a page boundary. We must map an 126290001Sglebius * extra page so the text ends with a NUL. 127290001Sglebius */ 128290001Sglebius#if defined(MAP_ANONYMOUS) 129290001Sglebius map_addr = mmap(NULL, mapinfo->txt_full_size, PROT_READ|PROT_WRITE, 130290001Sglebius MAP_ANONYMOUS|MAP_PRIVATE, AO_INVALID_FD, 0); 131290001Sglebius#else 132290001Sglebius mapinfo->txt_zero_fd = open("/dev/zero", O_RDONLY); 133290001Sglebius 134290001Sglebius if (mapinfo->txt_zero_fd == AO_INVALID_FD) { 135290001Sglebius mapinfo->txt_errno = errno; 136290001Sglebius return; 137290001Sglebius } 138290001Sglebius map_addr = mmap(NULL, mapinfo->txt_full_size, PROT_READ|PROT_WRITE, 139290001Sglebius MAP_PRIVATE, mapinfo->txt_zero_fd, 0); 140290001Sglebius#endif 141290001Sglebius if (map_addr == MAP_FAILED_PTR) { 142290001Sglebius mapinfo->txt_errno = errno; 143290001Sglebius return; 144290001Sglebius } 145290001Sglebius mapinfo->txt_flags |= MAP_FIXED; 146290001Sglebius } 147290001Sglebius 148290001Sglebius mapinfo->txt_data = 149290001Sglebius mmap(map_addr, mapinfo->txt_size, mapinfo->txt_prot, 150290001Sglebius mapinfo->txt_flags, mapinfo->txt_fd, 0); 151290001Sglebius 152290001Sglebius if (mapinfo->txt_data == MAP_FAILED_PTR) 153290001Sglebius mapinfo->txt_errno = errno; 154290001Sglebius#endif /* HAVE_MMAP */ 155290001Sglebius} 156290001Sglebius 157290001Sglebius/** 158290001Sglebius * Make sure all the parameters are correct: we have a file name that 159290001Sglebius * is a text file that we can read. 160290001Sglebius * 161290001Sglebius * @param fname the text file to map 162290001Sglebius * @param prot the memory protections requested (read/write/etc.) 163290001Sglebius * @param flags mmap flags 164290001Sglebius * @param mapinfo a structure holding everything we need to know 165290001Sglebius * about the mapping. 166290001Sglebius */ 167290001Sglebiusstatic void 168290001Sglebiusvalidate_mmap(char const * fname, int prot, int flags, tmap_info_t * mapinfo) 169290001Sglebius{ 170290001Sglebius memset(mapinfo, 0, sizeof(*mapinfo)); 171290001Sglebius#if defined(HAVE_MMAP) && ! defined(MAP_ANONYMOUS) 172290001Sglebius mapinfo->txt_zero_fd = AO_INVALID_FD; 173290001Sglebius#endif 174290001Sglebius mapinfo->txt_fd = AO_INVALID_FD; 175290001Sglebius mapinfo->txt_prot = prot; 176290001Sglebius mapinfo->txt_flags = flags; 177290001Sglebius 178181834Sroberto /* 179181834Sroberto * Map mmap flags and protections into open flags and do the open. 180181834Sroberto */ 181181834Sroberto { 182181834Sroberto /* 183181834Sroberto * See if we will be updating the file. If we can alter the memory 184181834Sroberto * and if we share the data and we are *not* copy-on-writing the data, 185181834Sroberto * then our updates will show in the file, so we must open with 186181834Sroberto * write access. 187181834Sroberto */ 188290001Sglebius int o_flag = FILE_WRITABLE(prot, flags) ? O_RDWR : O_RDONLY; 189181834Sroberto 190181834Sroberto /* 191181834Sroberto * If you're not sharing the file and you are writing to it, 192181834Sroberto * then don't let anyone else have access to the file. 193181834Sroberto */ 194181834Sroberto if (((flags & MAP_SHARED) == 0) && (prot & PROT_WRITE)) 195181834Sroberto o_flag |= O_EXCL; 196181834Sroberto 197290001Sglebius mapinfo->txt_fd = open(fname, o_flag); 198290001Sglebius if (mapinfo->txt_fd < 0) { 199290001Sglebius mapinfo->txt_errno = errno; 200290001Sglebius mapinfo->txt_fd = AO_INVALID_FD; 201290001Sglebius return; 202290001Sglebius } 203181834Sroberto } 204181834Sroberto 205181834Sroberto /* 206290001Sglebius * Make sure we can stat the regular file. Save the file size. 207181834Sroberto */ 208181834Sroberto { 209290001Sglebius struct stat sb; 210290001Sglebius if (fstat(mapinfo->txt_fd, &sb) != 0) { 211290001Sglebius mapinfo->txt_errno = errno; 212290001Sglebius close(mapinfo->txt_fd); 213290001Sglebius return; 214290001Sglebius } 215181834Sroberto 216290001Sglebius if (! S_ISREG(sb.st_mode)) { 217290001Sglebius mapinfo->txt_errno = errno = EINVAL; 218290001Sglebius close(mapinfo->txt_fd); 219290001Sglebius return; 220181834Sroberto } 221181834Sroberto 222290001Sglebius mapinfo->txt_size = (size_t)sb.st_size; 223181834Sroberto } 224181834Sroberto 225290001Sglebius if (mapinfo->txt_fd == AO_INVALID_FD) 226290001Sglebius mapinfo->txt_errno = errno; 227290001Sglebius} 228181834Sroberto 229290001Sglebius/** 230290001Sglebius * Close any files opened by the mapping. 231290001Sglebius * 232290001Sglebius * @param mi a structure holding everything we need to know about the map. 233290001Sglebius */ 234290001Sglebiusstatic void 235290001Sglebiusclose_mmap_files(tmap_info_t * mi) 236290001Sglebius{ 237290001Sglebius if (mi->txt_fd == AO_INVALID_FD) 238290001Sglebius return; 239181834Sroberto 240290001Sglebius close(mi->txt_fd); 241290001Sglebius mi->txt_fd = AO_INVALID_FD; 242181834Sroberto 243290001Sglebius#if defined(HAVE_MMAP) && ! defined(MAP_ANONYMOUS) 244290001Sglebius if (mi->txt_zero_fd == AO_INVALID_FD) 245290001Sglebius return; 246181834Sroberto 247290001Sglebius close(mi->txt_zero_fd); 248290001Sglebius mi->txt_zero_fd = AO_INVALID_FD; 249290001Sglebius#endif 250290001Sglebius} 251181834Sroberto 252290001Sglebius/*=export_func text_mmap 253290001Sglebius * private: 254290001Sglebius * 255290001Sglebius * what: map a text file with terminating NUL 256290001Sglebius * 257290001Sglebius * arg: char const *, pzFile, name of the file to map 258290001Sglebius * arg: int, prot, mmap protections (see mmap(2)) 259290001Sglebius * arg: int, flags, mmap flags (see mmap(2)) 260290001Sglebius * arg: tmap_info_t *, mapinfo, returned info about the mapping 261290001Sglebius * 262290001Sglebius * ret-type: void * 263290001Sglebius * ret-desc: The mmaped data address 264290001Sglebius * 265290001Sglebius * doc: 266290001Sglebius * 267290001Sglebius * This routine will mmap a file into memory ensuring that there is at least 268290001Sglebius * one @file{NUL} character following the file data. It will return the 269290001Sglebius * address where the file contents have been mapped into memory. If there is a 270290001Sglebius * problem, then it will return @code{MAP_FAILED} and set @code{errno} 271290001Sglebius * appropriately. 272290001Sglebius * 273290001Sglebius * The named file does not exist, @code{stat(2)} will set @code{errno} as it 274290001Sglebius * will. If the file is not a regular file, @code{errno} will be 275290001Sglebius * @code{EINVAL}. At that point, @code{open(2)} is attempted with the access 276290001Sglebius * bits set appropriately for the requested @code{mmap(2)} protections and flag 277290001Sglebius * bits. On failure, @code{errno} will be set according to the documentation 278290001Sglebius * for @code{open(2)}. If @code{mmap(2)} fails, @code{errno} will be set as 279290001Sglebius * that routine sets it. If @code{text_mmap} works to this point, a valid 280290001Sglebius * address will be returned, but there may still be ``issues''. 281290001Sglebius * 282290001Sglebius * If the file size is not an even multiple of the system page size, then 283290001Sglebius * @code{text_map} will return at this point and @code{errno} will be zero. 284290001Sglebius * Otherwise, an anonymous map is attempted. If not available, then an attempt 285290001Sglebius * is made to @code{mmap(2)} @file{/dev/zero}. If any of these fail, the 286290001Sglebius * address of the file's data is returned, bug @code{no} @file{NUL} characters 287290001Sglebius * are mapped after the end of the data. 288290001Sglebius * 289290001Sglebius * see: mmap(2), open(2), stat(2) 290290001Sglebius * 291290001Sglebius * err: Any error code issued by mmap(2), open(2), stat(2) is possible. 292290001Sglebius * Additionally, if the specified file is not a regular file, then 293290001Sglebius * errno will be set to @code{EINVAL}. 294290001Sglebius * 295290001Sglebius * example: 296290001Sglebius * #include <mylib.h> 297290001Sglebius * tmap_info_t mi; 298290001Sglebius * int no_nul; 299290001Sglebius * void * data = text_mmap("file", PROT_WRITE, MAP_PRIVATE, &mi); 300290001Sglebius * if (data == MAP_FAILED) return; 301290001Sglebius * no_nul = (mi.txt_size == mi.txt_full_size); 302290001Sglebius * << use the data >> 303290001Sglebius * text_munmap(&mi); 304290001Sglebius=*/ 305290001Sglebiusvoid * 306290001Sglebiustext_mmap(char const * pzFile, int prot, int flags, tmap_info_t * mi) 307290001Sglebius{ 308290001Sglebius validate_mmap(pzFile, prot, flags, mi); 309290001Sglebius if (mi->txt_errno != 0) 310290001Sglebius return MAP_FAILED_PTR; 311181834Sroberto 312290001Sglebius load_text_file(mi, pzFile); 313181834Sroberto 314290001Sglebius if (mi->txt_errno == 0) 315290001Sglebius return mi->txt_data; 316181834Sroberto 317290001Sglebius close_mmap_files(mi); 318181834Sroberto 319290001Sglebius errno = mi->txt_errno; 320290001Sglebius mi->txt_data = MAP_FAILED_PTR; 321290001Sglebius return mi->txt_data; 322181834Sroberto} 323181834Sroberto 324181834Sroberto 325181834Sroberto/*=export_func text_munmap 326181834Sroberto * private: 327181834Sroberto * 328181834Sroberto * what: unmap the data mapped in by text_mmap 329181834Sroberto * 330290001Sglebius * arg: tmap_info_t *, mapinfo, info about the mapping 331181834Sroberto * 332181834Sroberto * ret-type: int 333290001Sglebius * ret-desc: -1 or 0. @code{errno} will have the error code. 334181834Sroberto * 335181834Sroberto * doc: 336181834Sroberto * 337181834Sroberto * This routine will unmap the data mapped in with @code{text_mmap} and close 338181834Sroberto * the associated file descriptors opened by that function. 339181834Sroberto * 340181834Sroberto * see: munmap(2), close(2) 341181834Sroberto * 342181834Sroberto * err: Any error code issued by munmap(2) or close(2) is possible. 343181834Sroberto=*/ 344181834Srobertoint 345290001Sglebiustext_munmap(tmap_info_t * mi) 346181834Sroberto{ 347181834Sroberto errno = 0; 348181834Sroberto 349290001Sglebius#ifdef HAVE_MMAP 350290001Sglebius (void)munmap(mi->txt_data, mi->txt_full_size); 351181834Sroberto 352290001Sglebius#else /* don't HAVE_MMAP */ 353181834Sroberto /* 354181834Sroberto * IF the memory is writable *AND* it is not private (copy-on-write) 355181834Sroberto * *AND* the memory is "sharable" (seen by other processes) 356290001Sglebius * THEN rewrite the data. Emulate mmap visibility. 357181834Sroberto */ 358290001Sglebius if ( FILE_WRITABLE(mi->txt_prot, mi->txt_flags) 359290001Sglebius && (lseek(mi->txt_fd, 0, SEEK_SET) >= 0) ) { 360290001Sglebius write(mi->txt_fd, mi->txt_data, mi->txt_size); 361181834Sroberto } 362181834Sroberto 363290001Sglebius free(mi->txt_data); 364290001Sglebius#endif /* HAVE_MMAP */ 365181834Sroberto 366290001Sglebius mi->txt_errno = errno; 367290001Sglebius close_mmap_files(mi); 368290001Sglebius 369290001Sglebius return mi->txt_errno; 370181834Sroberto} 371181834Sroberto 372290001Sglebius/** @} 373290001Sglebius * 374181834Sroberto * Local Variables: 375181834Sroberto * mode: C 376181834Sroberto * c-file-style: "stroustrup" 377181834Sroberto * indent-tabs-mode: nil 378181834Sroberto * End: 379181834Sroberto * end of autoopts/text_mmap.c */ 380