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