1 // Written in the D programming language. 2 3 /** 4 * Read and write memory mapped files. 5 * 6 * Memory mapped files are a mechanism in operating systems that allows 7 * file access through virtual memory. After opening a file with `MmFile`, 8 * the contents can be read from or written to with standard slice / pointer operations. 9 * Changes to the memory are automatically reflected in the underlying file. 10 * 11 * Memory mapping can increase I/O performance of large files, compared to buffered 12 * read / write operations from `std.file` and `std.stdio`. However, I/O errors are 13 * not handled as safely: when for example the disk that the file is on gets removed, 14 * reading from it may result in a segfault. 15 * 16 * Copyright: Copyright The D Language Foundation 2004 - 2009. 17 * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 18 * Authors: $(HTTP digitalmars.com, Walter Bright), 19 * Matthew Wilson 20 * References: $(LINK https://en.wikipedia.org/wiki/Memory-mapped_file) 21 * Source: $(PHOBOSSRC std/mmfile.d) 22 * 23 * $(SCRIPT inhibitQuickIndex = 1;) 24 */ 25 /* Copyright The D Language Foundation 2004 - 2009. 26 * Distributed under the Boost Software License, Version 1.0. 27 * (See accompanying file LICENSE_1_0.txt or copy at 28 * http://www.boost.org/LICENSE_1_0.txt) 29 */ 30 module std.mmfile; 31 32 import core.stdc.errno; 33 import core.stdc.stdio; 34 import core.stdc.stdlib; 35 import std.conv, std.exception, std.stdio; 36 import std.file; 37 import std.path; 38 import std.string; 39 40 import std.internal.cstring; 41 42 //debug = MMFILE; 43 44 version (Windows) 45 { 46 import core.sys.windows.winbase; 47 import core.sys.windows.winnt; 48 import std.utf; 49 import std.windows.syserror; 50 } 51 else version (Posix) 52 { 53 import core.sys.posix.fcntl; 54 import core.sys.posix.sys.mman; 55 import core.sys.posix.sys.stat; 56 import core.sys.posix.unistd; 57 } 58 else 59 { 60 static assert(0); 61 } 62 63 /** 64 * MmFile objects control the memory mapped file resource. 65 */ 66 class MmFile 67 { 68 /** 69 * The mode the memory mapped file is opened with. 70 */ 71 enum Mode 72 { 73 read, /// Read existing file 74 readWriteNew, /// Delete existing file, write new file 75 readWrite, /// Read/Write existing file, create if not existing 76 readCopyOnWrite, /// Read/Write existing file, copy on write 77 } 78 79 /** 80 * Open memory mapped file filename for reading. 81 * File is closed when the object instance is deleted. 82 * Throws: 83 * - On POSIX, $(REF ErrnoException, std, exception). 84 * - On Windows, $(REF WindowsException, std, windows, syserror). 85 */ 86 this(string filename) scope 87 { 88 this(filename, Mode.read, 0, null); 89 } 90 91 version (linux) this(File file, Mode mode = Mode.read, ulong size = 0, 92 void* address = null, size_t window = 0) scope 93 { 94 // Save a copy of the File to make sure the fd stays open. 95 this.file = file; 96 this(file.fileno, mode, size, address, window); 97 } 98 99 version (linux) private this(int fildes, Mode mode, ulong size, 100 void* address, size_t window) scope 101 { 102 int oflag; 103 int fmode; 104 105 final switch (mode) 106 { 107 case Mode.read: 108 flags = MAP_SHARED; 109 prot = PROT_READ; 110 oflag = O_RDONLY; 111 fmode = 0; 112 break; 113 114 case Mode.readWriteNew: 115 assert(size != 0); 116 flags = MAP_SHARED; 117 prot = PROT_READ | PROT_WRITE; 118 oflag = O_CREAT | O_RDWR | O_TRUNC; 119 fmode = octal!660; 120 break; 121 122 case Mode.readWrite: 123 flags = MAP_SHARED; 124 prot = PROT_READ | PROT_WRITE; 125 oflag = O_CREAT | O_RDWR; 126 fmode = octal!660; 127 break; 128 129 case Mode.readCopyOnWrite: 130 flags = MAP_PRIVATE; 131 prot = PROT_READ | PROT_WRITE; 132 oflag = O_RDWR; 133 fmode = 0; 134 break; 135 } 136 137 fd = fildes; 138 139 // Adjust size 140 stat_t statbuf = void; 141 errnoEnforce(fstat(fd, &statbuf) == 0); 142 if (prot & PROT_WRITE && size > statbuf.st_size) 143 { 144 // Need to make the file size bytes big 145 lseek(fd, cast(off_t)(size - 1), SEEK_SET); 146 char c = 0; 147 core.sys.posix.unistd.write(fd, &c, 1); 148 } 149 else if (prot & PROT_READ && size == 0) 150 size = statbuf.st_size; 151 this.size = size; 152 153 // Map the file into memory! 154 size_t initial_map = (window && 2*window<size) 155 ? 2*window : cast(size_t) size; 156 auto p = mmap(address, initial_map, prot, flags, fd, 0); 157 if (p == MAP_FAILED) 158 { 159 errnoEnforce(false, "Could not map file into memory"); 160 } 161 data = p[0 .. initial_map]; 162 } 163 164 /** 165 * Open memory mapped file filename in mode. 166 * File is closed when the object instance is deleted. 167 * Params: 168 * filename = name of the file. 169 * If null, an anonymous file mapping is created. 170 * mode = access mode defined above. 171 * size = the size of the file. If 0, it is taken to be the 172 * size of the existing file. 173 * address = the preferred address to map the file to, 174 * although the system is not required to honor it. 175 * If null, the system selects the most convenient address. 176 * window = preferred block size of the amount of data to map at one time 177 * with 0 meaning map the entire file. The window size must be a 178 * multiple of the memory allocation page size. 179 * Throws: 180 * - On POSIX, $(REF ErrnoException, std, exception). 181 * - On Windows, $(REF WindowsException, std, windows, syserror). 182 */ 183 this(string filename, Mode mode, ulong size, void* address, 184 size_t window = 0) scope 185 { 186 this.filename = filename; 187 this.mMode = mode; 188 this.window = window; 189 this.address = address; 190 191 version (Windows) 192 { 193 void* p; 194 uint dwDesiredAccess2; 195 uint dwShareMode; 196 uint dwCreationDisposition; 197 uint flProtect; 198 199 final switch (mode) 200 { 201 case Mode.read: 202 dwDesiredAccess2 = GENERIC_READ; 203 dwShareMode = FILE_SHARE_READ; 204 dwCreationDisposition = OPEN_EXISTING; 205 flProtect = PAGE_READONLY; 206 dwDesiredAccess = FILE_MAP_READ; 207 break; 208 209 case Mode.readWriteNew: 210 assert(size != 0); 211 dwDesiredAccess2 = GENERIC_READ | GENERIC_WRITE; 212 dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; 213 dwCreationDisposition = CREATE_ALWAYS; 214 flProtect = PAGE_READWRITE; 215 dwDesiredAccess = FILE_MAP_WRITE; 216 break; 217 218 case Mode.readWrite: 219 dwDesiredAccess2 = GENERIC_READ | GENERIC_WRITE; 220 dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; 221 dwCreationDisposition = OPEN_ALWAYS; 222 flProtect = PAGE_READWRITE; 223 dwDesiredAccess = FILE_MAP_WRITE; 224 break; 225 226 case Mode.readCopyOnWrite: 227 dwDesiredAccess2 = GENERIC_READ | GENERIC_WRITE; 228 dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; 229 dwCreationDisposition = OPEN_EXISTING; 230 flProtect = PAGE_WRITECOPY; 231 dwDesiredAccess = FILE_MAP_COPY; 232 break; 233 } 234 235 if (filename != null) 236 { 237 hFile = CreateFileW(filename.tempCStringW(), 238 dwDesiredAccess2, 239 dwShareMode, 240 null, 241 dwCreationDisposition, 242 FILE_ATTRIBUTE_NORMAL, 243 cast(HANDLE) null); 244 wenforce(hFile != INVALID_HANDLE_VALUE, "CreateFileW"); 245 } 246 else 247 hFile = INVALID_HANDLE_VALUE; 248 249 scope(failure) 250 { 251 if (hFile != INVALID_HANDLE_VALUE) 252 { 253 CloseHandle(hFile); 254 hFile = INVALID_HANDLE_VALUE; 255 } 256 } 257 258 int hi = cast(int)(size >> 32); 259 hFileMap = CreateFileMappingW(hFile, null, flProtect, 260 hi, cast(uint) size, null); 261 wenforce(hFileMap, "CreateFileMapping"); 262 scope(failure) 263 { 264 CloseHandle(hFileMap); 265 hFileMap = null; 266 } 267 268 if (size == 0 && filename != null) 269 { 270 uint sizehi; 271 uint sizelow = GetFileSize(hFile, &sizehi); 272 wenforce(sizelow != INVALID_FILE_SIZE || GetLastError() != ERROR_SUCCESS, 273 "GetFileSize"); 274 size = (cast(ulong) sizehi << 32) + sizelow; 275 } 276 this.size = size; 277 278 size_t initial_map = (window && 2*window<size) 279 ? 2*window : cast(size_t) size; 280 p = MapViewOfFileEx(hFileMap, dwDesiredAccess, 0, 0, 281 initial_map, address); 282 wenforce(p, "MapViewOfFileEx"); 283 data = p[0 .. initial_map]; 284 285 debug (MMFILE) printf("MmFile.this(): p = %p, size = %d\n", p, size); 286 } 287 else version (Posix) 288 { 289 void* p; 290 int oflag; 291 int fmode; 292 293 final switch (mode) 294 { 295 case Mode.read: 296 flags = MAP_SHARED; 297 prot = PROT_READ; 298 oflag = O_RDONLY; 299 fmode = 0; 300 break; 301 302 case Mode.readWriteNew: 303 assert(size != 0); 304 flags = MAP_SHARED; 305 prot = PROT_READ | PROT_WRITE; 306 oflag = O_CREAT | O_RDWR | O_TRUNC; 307 fmode = octal!660; 308 break; 309 310 case Mode.readWrite: 311 flags = MAP_SHARED; 312 prot = PROT_READ | PROT_WRITE; 313 oflag = O_CREAT | O_RDWR; 314 fmode = octal!660; 315 break; 316 317 case Mode.readCopyOnWrite: 318 flags = MAP_PRIVATE; 319 prot = PROT_READ | PROT_WRITE; 320 oflag = O_RDWR; 321 fmode = 0; 322 break; 323 } 324 325 if (filename.length) 326 { 327 fd = .open(filename.tempCString(), oflag, fmode); 328 errnoEnforce(fd != -1, "Could not open file "~filename); 329 330 stat_t statbuf; 331 if (fstat(fd, &statbuf)) 332 { 333 //printf("\tfstat error, errno = %d\n", errno); 334 .close(fd); 335 fd = -1; 336 errnoEnforce(false, "Could not stat file "~filename); 337 } 338 339 if (prot & PROT_WRITE && size > statbuf.st_size) 340 { 341 // Need to make the file size bytes big 342 .lseek(fd, cast(off_t)(size - 1), SEEK_SET); 343 char c = 0; 344 core.sys.posix.unistd.write(fd, &c, 1); 345 } 346 else if (prot & PROT_READ && size == 0) 347 size = statbuf.st_size; 348 } 349 else 350 { 351 fd = -1; 352 flags |= MAP_ANON; 353 } 354 this.size = size; 355 size_t initial_map = (window && 2*window<size) 356 ? 2*window : cast(size_t) size; 357 p = mmap(address, initial_map, prot, flags, fd, 0); 358 if (p == MAP_FAILED) 359 { 360 if (fd != -1) 361 { 362 .close(fd); 363 fd = -1; 364 } 365 errnoEnforce(false, "Could not map file "~filename); 366 } 367 368 data = p[0 .. initial_map]; 369 } 370 else 371 { 372 static assert(0); 373 } 374 } 375 376 /** 377 * Flushes pending output and closes the memory mapped file. 378 */ 379 ~this() scope 380 { 381 debug (MMFILE) printf("MmFile.~this()\n"); 382 unmap(); 383 data = null; 384 version (Windows) 385 { 386 wenforce(hFileMap == null || CloseHandle(hFileMap) == TRUE, 387 "Could not close file handle"); 388 hFileMap = null; 389 390 wenforce(!hFile || hFile == INVALID_HANDLE_VALUE 391 || CloseHandle(hFile) == TRUE, 392 "Could not close handle"); 393 hFile = INVALID_HANDLE_VALUE; 394 } 395 else version (Posix) 396 { 397 version (linux) 398 { 399 if (file !is File.init) 400 { 401 // The File destructor will close the file, 402 // if it is the only remaining reference. 403 return; 404 } 405 } 406 errnoEnforce(fd == -1 || fd <= 2 407 || .close(fd) != -1, 408 "Could not close handle"); 409 fd = -1; 410 } 411 else 412 { 413 static assert(0); 414 } 415 } 416 417 /* Flush any pending output. 418 */ 419 void flush() 420 { 421 debug (MMFILE) printf("MmFile.flush()\n"); 422 version (Windows) 423 { 424 FlushViewOfFile(data.ptr, data.length); 425 FlushFileBuffers(hFile); 426 } 427 else version (Posix) 428 { 429 int i; 430 i = msync(cast(void*) data, data.length, MS_SYNC); // sys/mman.h 431 errnoEnforce(i == 0, "msync failed"); 432 } 433 else 434 { 435 static assert(0); 436 } 437 } 438 439 /** 440 * Gives size in bytes of the memory mapped file. 441 */ 442 @property ulong length() const 443 { 444 debug (MMFILE) printf("MmFile.length()\n"); 445 return size; 446 } 447 448 /** 449 * Forwards `length`. 450 */ 451 alias opDollar = length; 452 453 /** 454 * Read-only property returning the file mode. 455 */ 456 Mode mode() 457 { 458 debug (MMFILE) printf("MmFile.mode()\n"); 459 return mMode; 460 } 461 462 /** 463 * Returns entire file contents as an array. 464 */ 465 void[] opSlice() 466 { 467 debug (MMFILE) printf("MmFile.opSlice()\n"); 468 return opSlice(0,size); 469 } 470 471 /** 472 * Returns slice of file contents as an array. 473 */ 474 void[] opSlice(ulong i1, ulong i2) 475 { 476 debug (MMFILE) printf("MmFile.opSlice(%lld, %lld)\n", i1, i2); 477 ensureMapped(i1,i2); 478 size_t off1 = cast(size_t)(i1-start); 479 size_t off2 = cast(size_t)(i2-start); 480 return data[off1 .. off2]; 481 } 482 483 /** 484 * Returns byte at index i in file. 485 */ 486 ubyte opIndex(ulong i) 487 { 488 debug (MMFILE) printf("MmFile.opIndex(%lld)\n", i); 489 ensureMapped(i); 490 size_t off = cast(size_t)(i-start); 491 return (cast(ubyte[]) data)[off]; 492 } 493 494 /** 495 * Sets and returns byte at index i in file to value. 496 */ 497 ubyte opIndexAssign(ubyte value, ulong i) 498 { 499 debug (MMFILE) printf("MmFile.opIndex(%lld, %d)\n", i, value); 500 ensureMapped(i); 501 size_t off = cast(size_t)(i-start); 502 return (cast(ubyte[]) data)[off] = value; 503 } 504 505 506 // return true if the given position is currently mapped 507 private int mapped(ulong i) 508 { 509 debug (MMFILE) printf("MmFile.mapped(%lld, %lld, %d)\n", i,start, 510 data.length); 511 return i >= start && i < start+data.length; 512 } 513 514 // unmap the current range 515 private void unmap() 516 { 517 debug (MMFILE) printf("MmFile.unmap()\n"); 518 version (Windows) 519 { 520 wenforce(!data.ptr || UnmapViewOfFile(data.ptr) != FALSE, "UnmapViewOfFile"); 521 } 522 else 523 { 524 errnoEnforce(!data.ptr || munmap(cast(void*) data, data.length) == 0, 525 "munmap failed"); 526 } 527 data = null; 528 } 529 530 // map range 531 private void map(ulong start, size_t len) 532 { 533 debug (MMFILE) printf("MmFile.map(%lld, %d)\n", start, len); 534 void* p; 535 if (start+len > size) 536 len = cast(size_t)(size-start); 537 version (Windows) 538 { 539 uint hi = cast(uint)(start >> 32); 540 p = MapViewOfFileEx(hFileMap, dwDesiredAccess, hi, cast(uint) start, len, address); 541 wenforce(p, "MapViewOfFileEx"); 542 } 543 else 544 { 545 p = mmap(address, len, prot, flags, fd, cast(off_t) start); 546 errnoEnforce(p != MAP_FAILED); 547 } 548 data = p[0 .. len]; 549 this.start = start; 550 } 551 552 // ensure a given position is mapped 553 private void ensureMapped(ulong i) 554 { 555 debug (MMFILE) printf("MmFile.ensureMapped(%lld)\n", i); 556 if (!mapped(i)) 557 { 558 unmap(); 559 if (window == 0) 560 { 561 map(0,cast(size_t) size); 562 } 563 else 564 { 565 ulong block = i/window; 566 if (block == 0) 567 map(0,2*window); 568 else 569 map(window*(block-1),3*window); 570 } 571 } 572 } 573 574 // ensure a given range is mapped 575 private void ensureMapped(ulong i, ulong j) 576 { 577 debug (MMFILE) printf("MmFile.ensureMapped(%lld, %lld)\n", i, j); 578 if (!mapped(i) || !mapped(j-1)) 579 { 580 unmap(); 581 if (window == 0) 582 { 583 map(0,cast(size_t) size); 584 } 585 else 586 { 587 ulong iblock = i/window; 588 ulong jblock = (j-1)/window; 589 if (iblock == 0) 590 { 591 map(0,cast(size_t)(window*(jblock+2))); 592 } 593 else 594 { 595 map(window*(iblock-1),cast(size_t)(window*(jblock-iblock+3))); 596 } 597 } 598 } 599 } 600 601 private: 602 string filename; 603 void[] data; 604 ulong start; 605 size_t window; 606 ulong size; 607 Mode mMode; 608 void* address; 609 version (linux) File file; 610 611 version (Windows) 612 { 613 HANDLE hFile = INVALID_HANDLE_VALUE; 614 HANDLE hFileMap = null; 615 uint dwDesiredAccess; 616 } 617 else version (Posix) 618 { 619 int fd; 620 int prot; 621 int flags; 622 int fmode; 623 } 624 else 625 { 626 static assert(0); 627 } 628 } 629 630 /// Read an existing file 631 @system unittest 632 { 633 import std.file; 634 std.file.write(deleteme, "hello"); // deleteme is a temporary filename 635 scope(exit) remove(deleteme); 636 637 // Use a scope class so the file will be closed at the end of this function 638 scope mmfile = new MmFile(deleteme); 639 640 assert(mmfile.length == "hello".length); 641 642 // Access file contents with the slice operator 643 // This is typed as `void[]`, so cast to `char[]` or `ubyte[]` to use it 644 const data = cast(const(char)[]) mmfile[]; 645 646 // At this point, the file content may not have been read yet. 647 // In that case, the following memory access will intentionally 648 // trigger a page fault, causing the kernel to load the file contents 649 assert(data[0 .. 5] == "hello"); 650 } 651 652 /// Write a new file 653 @system unittest 654 { 655 import std.file; 656 scope(exit) remove(deleteme); 657 658 scope mmfile = new MmFile(deleteme, MmFile.Mode.readWriteNew, 5, null); 659 assert(mmfile.length == 5); 660 661 auto data = cast(ubyte[]) mmfile[]; 662 663 // This write to memory will be reflected in the file contents 664 data[] = '\n'; 665 666 mmfile.flush(); 667 668 assert(std.file.read(deleteme) == "\n\n\n\n\n"); 669 } 670 671 @system unittest 672 { 673 import core.memory : GC; 674 import std.file : deleteme; 675 676 const size_t K = 1024; 677 size_t win = 64*K; // assume the page size is 64K 678 version (Windows) 679 { 680 /+ these aren't defined in core.sys.windows.windows so let's use default 681 SYSTEM_INFO sysinfo; 682 GetSystemInfo(&sysinfo); 683 win = sysinfo.dwAllocationGranularity; 684 +/ 685 } 686 else version (Posix) 687 { 688 import core.sys.posix.unistd; 689 win = cast(size_t) sysconf(_SC_PAGESIZE); 690 } 691 string test_file = std.file.deleteme ~ "-testing.txt"; 692 MmFile mf = new MmFile(test_file,MmFile.Mode.readWriteNew, 693 100*K,null,win); 694 ubyte[] str = cast(ubyte[])"1234567890"; 695 ubyte[] data = cast(ubyte[]) mf[0 .. 10]; 696 data[] = str[]; 697 assert( mf[0 .. 10] == str ); 698 data = cast(ubyte[]) mf[50 .. 60]; 699 data[] = str[]; 700 assert( mf[50 .. 60] == str ); 701 ubyte[] data2 = cast(ubyte[]) mf[20*K .. 60*K]; 702 assert( data2.length == 40*K ); 703 assert( data2[$-1] == 0 ); 704 mf[100*K-1] = cast(ubyte)'b'; 705 data2 = cast(ubyte[]) mf[21*K .. 100*K]; 706 assert( data2.length == 79*K ); 707 assert( data2[$-1] == 'b' ); 708 709 destroy(mf); 710 711 std.file.remove(test_file); 712 // Create anonymous mapping 713 auto test = new MmFile(null, MmFile.Mode.readWriteNew, 1024*1024, null); 714 } 715 716 version (linux) 717 @system unittest // https://issues.dlang.org/show_bug.cgi?id=14868 718 { 719 import std.file : deleteme; 720 import std.typecons : scoped; 721 722 // Test retaining ownership of File/fd 723 724 auto fn = std.file.deleteme ~ "-testing.txt"; 725 scope(exit) std.file.remove(fn); 726 File(fn, "wb").writeln("Testing!"); 727 scoped!MmFile(File(fn)); 728 729 // Test that unique ownership of File actually leads to the fd being closed 730 731 auto f = File(fn); 732 auto fd = f.fileno; 733 { 734 auto mf = scoped!MmFile(f); 735 f = File.init; 736 } 737 assert(.close(fd) == -1); 738 } 739 740 // https://issues.dlang.org/show_bug.cgi?id=14994 741 // https://issues.dlang.org/show_bug.cgi?id=14995 742 @system unittest 743 { 744 import std.file : deleteme; 745 import std.typecons : scoped; 746 747 // Zero-length map may or may not be valid on OSX and NetBSD 748 version (OSX) 749 import std.exception : verifyThrown = collectException; 750 version (NetBSD) 751 import std.exception : verifyThrown = collectException; 752 else 753 import std.exception : verifyThrown = assertThrown; 754 755 auto fn = std.file.deleteme ~ "-testing.txt"; 756 scope(exit) std.file.remove(fn); 757 verifyThrown(scoped!MmFile(fn, MmFile.Mode.readWrite, 0, null)); 758 } 759 760 @system unittest 761 { 762 MmFile shar = new MmFile(null, MmFile.Mode.readWrite, 10, null, 0); 763 void[] output = shar[0 .. $]; 764 } 765 766 @system unittest 767 { 768 import std.file : deleteme; 769 auto name = std.file.deleteme ~ "-test.tmp"; 770 scope(exit) std.file.remove(name); 771 772 std.file.write(name, "abcd"); 773 { 774 scope MmFile mmf = new MmFile(name); 775 string p; 776 777 assert(mmf[0] == 'a'); 778 p = cast(string) mmf[]; 779 assert(p[1] == 'b'); 780 p = cast(string) mmf[0 .. 4]; 781 assert(p[2] == 'c'); 782 } 783 { 784 scope MmFile mmf = new MmFile(name, MmFile.Mode.read, 0, null); 785 string p; 786 787 assert(mmf[0] == 'a'); 788 p = cast(string) mmf[]; 789 assert(mmf.length == 4); 790 assert(p[1] == 'b'); 791 p = cast(string) mmf[0 .. 4]; 792 assert(p[2] == 'c'); 793 } 794 std.file.remove(name); 795 { 796 scope MmFile mmf = new MmFile(name, MmFile.Mode.readWriteNew, 4, null); 797 char[] p = cast(char[]) mmf[]; 798 p[] = "1234"; 799 mmf[3] = '5'; 800 assert(mmf[2] == '3'); 801 assert(mmf[3] == '5'); 802 } 803 { 804 string p = cast(string) std.file.read(name); 805 assert(p[] == "1235"); 806 } 807 { 808 scope MmFile mmf = new MmFile(name, MmFile.Mode.readWriteNew, 4, null); 809 char[] p = cast(char[]) mmf[]; 810 p[] = "5678"; 811 mmf[3] = '5'; 812 assert(mmf[2] == '7'); 813 assert(mmf[3] == '5'); 814 assert(cast(string) mmf[] == "5675"); 815 } 816 { 817 string p = cast(string) std.file.read(name); 818 assert(p[] == "5675"); 819 } 820 { 821 scope MmFile mmf = new MmFile(name, MmFile.Mode.readWrite, 4, null); 822 char[] p = cast(char[]) mmf[]; 823 assert(cast(char[]) mmf[] == "5675"); 824 p[] = "9102"; 825 mmf[2] = '5'; 826 assert(cast(string) mmf[] == "9152"); 827 } 828 { 829 string p = cast(string) std.file.read(name); 830 assert(p[] == "9152"); 831 } 832 std.file.remove(name); 833 { 834 scope MmFile mmf = new MmFile(name, MmFile.Mode.readWrite, 4, null); 835 char[] p = cast(char[]) mmf[]; 836 p[] = "abcd"; 837 mmf[2] = '5'; 838 assert(cast(string) mmf[] == "ab5d"); 839 } 840 { 841 string p = cast(string) std.file.read(name); 842 assert(p[] == "ab5d"); 843 } 844 { 845 scope MmFile mmf = new MmFile(name, MmFile.Mode.readCopyOnWrite, 4, null); 846 char[] p = cast(char[]) mmf[]; 847 assert(cast(string) mmf[] == "ab5d"); 848 p[] = "9102"; 849 mmf[2] = '5'; 850 assert(cast(string) mmf[] == "9152"); 851 } 852 { 853 string p = cast(string) std.file.read(name); 854 assert(p[] == "ab5d"); 855 } 856 }