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