The OpenD Programming Language

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