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