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         }
426         else version (Posix)
427         {
428             int i;
429             i = msync(cast(void*) data, data.length, MS_SYNC);   // sys/mman.h
430             errnoEnforce(i == 0, "msync failed");
431         }
432         else
433         {
434             static assert(0);
435         }
436     }
437 
438     /**
439      * Gives size in bytes of the memory mapped file.
440      */
441     @property ulong length() const
442     {
443         debug (MMFILE) printf("MmFile.length()\n");
444         return size;
445     }
446 
447     /**
448      * Forwards `length`.
449      */
450     alias opDollar = length;
451 
452     /**
453      * Read-only property returning the file mode.
454      */
455     Mode mode()
456     {
457         debug (MMFILE) printf("MmFile.mode()\n");
458         return mMode;
459     }
460 
461     /**
462      * Returns entire file contents as an array.
463      */
464     void[] opSlice()
465     {
466         debug (MMFILE) printf("MmFile.opSlice()\n");
467         return opSlice(0,size);
468     }
469 
470     /**
471      * Returns slice of file contents as an array.
472      */
473     void[] opSlice(ulong i1, ulong i2)
474     {
475         debug (MMFILE) printf("MmFile.opSlice(%lld, %lld)\n", i1, i2);
476         ensureMapped(i1,i2);
477         size_t off1 = cast(size_t)(i1-start);
478         size_t off2 = cast(size_t)(i2-start);
479         return data[off1 .. off2];
480     }
481 
482     /**
483      * Returns byte at index i in file.
484      */
485     ubyte opIndex(ulong i)
486     {
487         debug (MMFILE) printf("MmFile.opIndex(%lld)\n", i);
488         ensureMapped(i);
489         size_t off = cast(size_t)(i-start);
490         return (cast(ubyte[]) data)[off];
491     }
492 
493     /**
494      * Sets and returns byte at index i in file to value.
495      */
496     ubyte opIndexAssign(ubyte value, ulong i)
497     {
498         debug (MMFILE) printf("MmFile.opIndex(%lld, %d)\n", i, value);
499         ensureMapped(i);
500         size_t off = cast(size_t)(i-start);
501         return (cast(ubyte[]) data)[off] = value;
502     }
503 
504 
505     // return true if the given position is currently mapped
506     private int mapped(ulong i)
507     {
508         debug (MMFILE) printf("MmFile.mapped(%lld, %lld, %d)\n", i,start,
509                 data.length);
510         return i >= start && i < start+data.length;
511     }
512 
513     // unmap the current range
514     private void unmap()
515     {
516         debug (MMFILE) printf("MmFile.unmap()\n");
517         version (Windows)
518         {
519             wenforce(!data.ptr || UnmapViewOfFile(data.ptr) != FALSE, "UnmapViewOfFile");
520         }
521         else
522         {
523             errnoEnforce(!data.ptr || munmap(cast(void*) data, data.length) == 0,
524                     "munmap failed");
525         }
526         data = null;
527     }
528 
529     // map range
530     private void map(ulong start, size_t len)
531     {
532         debug (MMFILE) printf("MmFile.map(%lld, %d)\n", start, len);
533         void* p;
534         if (start+len > size)
535             len = cast(size_t)(size-start);
536         version (Windows)
537         {
538             uint hi = cast(uint)(start >> 32);
539             p = MapViewOfFileEx(hFileMap, dwDesiredAccess, hi, cast(uint) start, len, address);
540             wenforce(p, "MapViewOfFileEx");
541         }
542         else
543         {
544             p = mmap(address, len, prot, flags, fd, cast(off_t) start);
545             errnoEnforce(p != MAP_FAILED);
546         }
547         data = p[0 .. len];
548         this.start = start;
549     }
550 
551     // ensure a given position is mapped
552     private void ensureMapped(ulong i)
553     {
554         debug (MMFILE) printf("MmFile.ensureMapped(%lld)\n", i);
555         if (!mapped(i))
556         {
557             unmap();
558             if (window == 0)
559             {
560                 map(0,cast(size_t) size);
561             }
562             else
563             {
564                 ulong block = i/window;
565                 if (block == 0)
566                     map(0,2*window);
567                 else
568                     map(window*(block-1),3*window);
569             }
570         }
571     }
572 
573     // ensure a given range is mapped
574     private void ensureMapped(ulong i, ulong j)
575     {
576         debug (MMFILE) printf("MmFile.ensureMapped(%lld, %lld)\n", i, j);
577         if (!mapped(i) || !mapped(j-1))
578         {
579             unmap();
580             if (window == 0)
581             {
582                 map(0,cast(size_t) size);
583             }
584             else
585             {
586                 ulong iblock = i/window;
587                 ulong jblock = (j-1)/window;
588                 if (iblock == 0)
589                 {
590                     map(0,cast(size_t)(window*(jblock+2)));
591                 }
592                 else
593                 {
594                     map(window*(iblock-1),cast(size_t)(window*(jblock-iblock+3)));
595                 }
596             }
597         }
598     }
599 
600 private:
601     string filename;
602     void[] data;
603     ulong  start;
604     size_t window;
605     ulong  size;
606     Mode   mMode;
607     void*  address;
608     version (linux) File file;
609 
610     version (Windows)
611     {
612         HANDLE hFile = INVALID_HANDLE_VALUE;
613         HANDLE hFileMap = null;
614         uint dwDesiredAccess;
615     }
616     else version (Posix)
617     {
618         int fd;
619         int prot;
620         int flags;
621         int fmode;
622     }
623     else
624     {
625         static assert(0);
626     }
627 }
628 
629 /// Read an existing file
630 @system unittest
631 {
632     import std.file;
633     std.file.write(deleteme, "hello"); // deleteme is a temporary filename
634     scope(exit) remove(deleteme);
635 
636     // Use a scope class so the file will be closed at the end of this function
637     scope mmfile = new MmFile(deleteme);
638 
639     assert(mmfile.length == "hello".length);
640 
641     // Access file contents with the slice operator
642     // This is typed as `void[]`, so cast to `char[]` or `ubyte[]` to use it
643     const data = cast(const(char)[]) mmfile[];
644 
645     // At this point, the file content may not have been read yet.
646     // In that case, the following memory access will intentionally
647     // trigger a page fault, causing the kernel to load the file contents
648     assert(data[0 .. 5] == "hello");
649 }
650 
651 /// Write a new file
652 @system unittest
653 {
654     import std.file;
655     scope(exit) remove(deleteme);
656 
657     scope mmfile = new MmFile(deleteme, MmFile.Mode.readWriteNew, 5, null);
658     assert(mmfile.length == 5);
659 
660     auto data = cast(ubyte[]) mmfile[];
661 
662     // This write to memory will be reflected in the file contents
663     data[] = '\n';
664 
665     mmfile.flush();
666 
667     assert(std.file.read(deleteme) == "\n\n\n\n\n");
668 }
669 
670 @system unittest
671 {
672     import core.memory : GC;
673     import std.file : deleteme;
674 
675     const size_t K = 1024;
676     size_t win = 64*K; // assume the page size is 64K
677     version (Windows)
678     {
679         /+ these aren't defined in core.sys.windows.windows so let's use default
680          SYSTEM_INFO sysinfo;
681          GetSystemInfo(&sysinfo);
682          win = sysinfo.dwAllocationGranularity;
683          +/
684     }
685     else version (Posix)
686     {
687         import core.sys.posix.unistd;
688         win = cast(size_t) sysconf(_SC_PAGESIZE);
689     }
690     string test_file = std.file.deleteme ~ "-testing.txt";
691     MmFile mf = new MmFile(test_file,MmFile.Mode.readWriteNew,
692             100*K,null,win);
693     ubyte[] str = cast(ubyte[])"1234567890";
694     ubyte[] data = cast(ubyte[]) mf[0 .. 10];
695     data[] = str[];
696     assert( mf[0 .. 10] == str );
697     data = cast(ubyte[]) mf[50 .. 60];
698     data[] = str[];
699     assert( mf[50 .. 60] == str );
700     ubyte[] data2 = cast(ubyte[]) mf[20*K .. 60*K];
701     assert( data2.length == 40*K );
702     assert( data2[$-1] == 0 );
703     mf[100*K-1] = cast(ubyte)'b';
704     data2 = cast(ubyte[]) mf[21*K .. 100*K];
705     assert( data2.length == 79*K );
706     assert( data2[$-1] == 'b' );
707 
708     destroy(mf);
709 
710     std.file.remove(test_file);
711     // Create anonymous mapping
712     auto test = new MmFile(null, MmFile.Mode.readWriteNew, 1024*1024, null);
713 }
714 
715 version (linux)
716 @system unittest // https://issues.dlang.org/show_bug.cgi?id=14868
717 {
718     import std.file : deleteme;
719     import std.typecons : scoped;
720 
721     // Test retaining ownership of File/fd
722 
723     auto fn = std.file.deleteme ~ "-testing.txt";
724     scope(exit) std.file.remove(fn);
725     File(fn, "wb").writeln("Testing!");
726     scoped!MmFile(File(fn));
727 
728     // Test that unique ownership of File actually leads to the fd being closed
729 
730     auto f = File(fn);
731     auto fd = f.fileno;
732     {
733         auto mf = scoped!MmFile(f);
734         f = File.init;
735     }
736     assert(.close(fd) == -1);
737 }
738 
739 // https://issues.dlang.org/show_bug.cgi?id=14994
740 // https://issues.dlang.org/show_bug.cgi?id=14995
741 @system unittest
742 {
743     import std.file : deleteme;
744     import std.typecons : scoped;
745 
746     // Zero-length map may or may not be valid on OSX and NetBSD
747     version (OSX)
748         import std.exception : verifyThrown = collectException;
749     version (NetBSD)
750         import std.exception : verifyThrown = collectException;
751     else
752         import std.exception : verifyThrown = assertThrown;
753 
754     auto fn = std.file.deleteme ~ "-testing.txt";
755     scope(exit) std.file.remove(fn);
756     verifyThrown(scoped!MmFile(fn, MmFile.Mode.readWrite, 0, null));
757 }
758 
759 @system unittest
760 {
761     MmFile shar = new MmFile(null, MmFile.Mode.readWrite, 10, null, 0);
762     void[] output = shar[0 .. $];
763 }
764 
765 @system unittest
766 {
767     import std.file : deleteme;
768     auto name = std.file.deleteme ~ "-test.tmp";
769     scope(exit) std.file.remove(name);
770 
771     std.file.write(name, "abcd");
772     {
773         scope MmFile mmf = new MmFile(name);
774         string p;
775 
776         assert(mmf[0] == 'a');
777         p = cast(string) mmf[];
778         assert(p[1] == 'b');
779         p = cast(string) mmf[0 .. 4];
780         assert(p[2] == 'c');
781     }
782     {
783         scope MmFile mmf = new MmFile(name, MmFile.Mode.read, 0, null);
784         string p;
785 
786         assert(mmf[0] == 'a');
787         p = cast(string) mmf[];
788         assert(mmf.length == 4);
789         assert(p[1] == 'b');
790         p = cast(string) mmf[0 .. 4];
791         assert(p[2] == 'c');
792     }
793     std.file.remove(name);
794     {
795         scope MmFile mmf = new MmFile(name, MmFile.Mode.readWriteNew, 4, null);
796         char[] p = cast(char[]) mmf[];
797         p[] = "1234";
798         mmf[3] = '5';
799         assert(mmf[2] == '3');
800         assert(mmf[3] == '5');
801     }
802     {
803         string p = cast(string) std.file.read(name);
804         assert(p[] == "1235");
805     }
806     {
807         scope MmFile mmf = new MmFile(name, MmFile.Mode.readWriteNew, 4, null);
808         char[] p = cast(char[]) mmf[];
809         p[] = "5678";
810         mmf[3] = '5';
811         assert(mmf[2] == '7');
812         assert(mmf[3] == '5');
813         assert(cast(string) mmf[] == "5675");
814     }
815     {
816         string p = cast(string) std.file.read(name);
817         assert(p[] == "5675");
818     }
819     {
820         scope MmFile mmf = new MmFile(name, MmFile.Mode.readWrite, 4, null);
821         char[] p = cast(char[]) mmf[];
822         assert(cast(char[]) mmf[] == "5675");
823         p[] = "9102";
824         mmf[2] = '5';
825         assert(cast(string) mmf[] == "9152");
826     }
827     {
828         string p = cast(string) std.file.read(name);
829         assert(p[] == "9152");
830     }
831     std.file.remove(name);
832     {
833         scope MmFile mmf = new MmFile(name, MmFile.Mode.readWrite, 4, null);
834         char[] p = cast(char[]) mmf[];
835         p[] = "abcd";
836         mmf[2] = '5';
837         assert(cast(string) mmf[] == "ab5d");
838     }
839     {
840         string p = cast(string) std.file.read(name);
841         assert(p[] == "ab5d");
842     }
843     {
844         scope MmFile mmf = new MmFile(name, MmFile.Mode.readCopyOnWrite, 4, null);
845         char[] p = cast(char[]) mmf[];
846         assert(cast(string) mmf[] == "ab5d");
847         p[] = "9102";
848         mmf[2] = '5';
849         assert(cast(string) mmf[] == "9152");
850     }
851     {
852         string p = cast(string) std.file.read(name);
853         assert(p[] == "ab5d");
854     }
855 }