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