1 /*******************************************************************************
2 
3         copyright:      Copyright (c) 2004 Kris Bell. All rights reserved
4 
5         license:        BSD style: $(LICENSE)
6 
7         version:        Initial release: March 2004
8 
9         author:         Kris
10 
11 *******************************************************************************/
12 
13 module tango.io.device.FileMap;
14 
15 private import tango.sys.Common;
16 
17 private import tango.io.device.File,
18                tango.io.device.Array;
19 
20 /*******************************************************************************
21 
22         External declarations.
23 
24 *******************************************************************************/
25 
26 version (Win32)
27          private extern (Windows)
28                         {
29                         BOOL   UnmapViewOfFile    (LPCVOID);
30                         BOOL   FlushViewOfFile    (LPCVOID, DWORD);
31                         LPVOID MapViewOfFile      (HANDLE, DWORD, DWORD, DWORD, DWORD);
32                         HANDLE CreateFileMappingA (HANDLE, LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCTSTR);
33                         }
34 
35 version (Posix)
36          private import tango.stdc.posix.sys.mman;
37 
38 
39 /*******************************************************************************
40 
41 *******************************************************************************/
42 
43 class FileMap : Array
44 {
45         private MappedFile file;
46 
47         /***********************************************************************
48 
49                 Construct a FileMap upon the given path.
50 
51                 You should use resize() to setup the available
52                 working space.
53 
54         ***********************************************************************/
55 
56         this (const(char[]) path, File.Style style = File.ReadWriteOpen)
57         {
58                 file = new MappedFile (path, style);
59                 super (file.map);
60         }
61 
62         /***********************************************************************
63 
64                 Resize the file and return the remapped content. Usage of
65                 map() is not required following this call.
66 
67         ***********************************************************************/
68 
69         final ubyte[] resize (long size)
70         {
71                 auto ret = file.resize (size);
72                 super.assign (ret);
73                 return ret;
74         }
75 
76         /***********************************************************************
77 
78                 Release external resources.
79 
80         ***********************************************************************/
81 
82         override void close ()
83         {
84                 super.close();
85                 if (file)
86                     file.close();
87                 file = null;
88         }
89 }
90 
91 
92 /*******************************************************************************
93 
94 *******************************************************************************/
95 
96 class MappedFile
97 {
98         private File host;
99 
100         /***********************************************************************
101 
102                 Construct a FileMap upon the given path.
103 
104                 You should use resize() to setup the available
105                 working space.
106 
107         ***********************************************************************/
108 
109         this (const(char[]) path, File.Style style = File.ReadWriteOpen)
110         {
111                 host = new File (path, style);
112         }
113 
114         /***********************************************************************
115 
116         ***********************************************************************/
117 
118         @property final long length ()
119         {
120                 return host.length;
121         }
122 
123         /***********************************************************************
124 
125         ***********************************************************************/
126 
127         @property final const(char)[] path ()
128         {
129                 return host.toString();
130         }
131 
132         /***********************************************************************
133 
134                 Resize the file and return the remapped content. Usage of
135                 map() is not required following this call.
136 
137         ***********************************************************************/
138 
139         final ubyte[] resize (long size)
140         {
141                 host.truncate (size);
142                 return map;
143         }
144 
145         /***********************************************************************
146 
147         ***********************************************************************/
148 
149         version (Win32)
150         {
151                 private void*   base;            // array pointer
152                 private HANDLE  mmFile;          // mapped file
153 
154                 /***************************************************************
155 
156                         Return a slice representing file content as a
157                         memory-mapped array.
158 
159                 ***************************************************************/
160 
161                 @property final ubyte[] map ()
162                 {
163                         DWORD flags;
164 
165                         // be wary of redundant references
166                         if (base)
167                             reset();
168 
169                         // can only do 32bit mapping on 32bit platform
170                         auto size = cast(size_t) host.length;
171                         auto access = host.style.access;
172 
173                         flags = PAGE_READONLY;
174                         if (access & host.Access.Write)
175                             flags = PAGE_READWRITE;
176 
177                         auto handle = cast(HANDLE) host.fileHandle;
178                         mmFile = CreateFileMappingA (handle, null, flags, 0, 0, null);
179                         if (mmFile is null)
180                             host.error();
181 
182                         flags = FILE_MAP_READ;
183                         if (access & host.Access.Write)
184                             flags |= FILE_MAP_WRITE;
185 
186                         base = MapViewOfFile (mmFile, flags, 0, 0, 0);
187                         if (base is null)
188                             host.error();
189 
190                         return (cast(ubyte*) base) [0 .. size];
191                 }
192 
193                 /***************************************************************
194 
195                         Release this mapping without flushing.
196 
197                 ***************************************************************/
198 
199                 final void close ()
200                 {
201                         reset();
202                         if (host)
203                             host.close();
204                         host = null;
205                 }
206 
207                 /***************************************************************
208 
209                 ***************************************************************/
210 
211                 private void reset ()
212                 {
213                         if (base)
214                             UnmapViewOfFile (base);
215 
216                         if (mmFile)
217                             CloseHandle (mmFile);
218 
219                         mmFile = null;
220                         base = null;
221                 }
222 
223                 /***************************************************************
224 
225                         Flush dirty content out to the drive. This
226                         fails with error 33 if the file content is
227                         virgin. Opening a file for ReadWriteExists
228                         followed by a flush() will cause this.
229 
230                 ***************************************************************/
231 
232                 MappedFile flush ()
233                 {
234                         // flush all dirty pages
235                         if (! FlushViewOfFile (base, 0))
236                               host.error();
237                         return this;
238                 }
239         }
240 
241         /***********************************************************************
242 
243         ***********************************************************************/
244 
245         version (Posix)
246         {
247                 // Linux code: not yet tested on other POSIX systems.
248                 private void*   base;           // array pointer
249                 private size_t  size;           // length of file
250 
251                 /***************************************************************
252 
253                         Return a slice representing file content as a
254                         memory-mapped array. Use this to remap content
255                         each time the file size is changed.
256 
257                 ***************************************************************/
258 
259                 @property final ubyte[] map ()
260                 {
261                         // be wary of redundant references
262                         if (base)
263                             reset();
264 
265                         // can only do 32bit mapping on 32bit platform
266                         size = cast (size_t) host.length;
267 
268                         // Make sure the mapping attributes are consistant with
269                         // the File attributes.
270                         int flags = MAP_SHARED;
271                         int protection = PROT_READ;
272                         auto access = host.style.access;
273                         if (access & host.Access.Write)
274                             protection |= PROT_WRITE;
275 
276                         base = mmap (null, size, protection, flags, host.fileHandle, 0);
277                         if (base is MAP_FAILED)
278                            {
279                            base = null;
280                            host.error();
281                            }
282 
283                         return (cast(ubyte*) base) [0 .. size];
284                 }
285 
286                 /***************************************************************
287 
288                         Release this mapped buffer without flushing.
289 
290                 ***************************************************************/
291 
292                 final void close ()
293                 {
294                         reset();
295                         if (host)
296                             host.close();
297                         host = null;
298                 }
299 
300                 /***************************************************************
301 
302                 ***************************************************************/
303 
304                 private void reset ()
305                 {
306                         // NOTE: When a process ends, all mmaps belonging to that process
307                         //       are automatically unmapped by system (Linux).
308                         //       On the other hand, this is NOT the case when the related
309                         //       file descriptor is closed.  This function unmaps explicitly.
310                         if (base)
311                             if (munmap (base, size))
312                                 host.error();
313 
314                         base = null;
315                 }
316 
317                 /***************************************************************
318 
319                         Flush dirty content out to the drive.
320 
321                 ***************************************************************/
322 
323                 final MappedFile flush ()
324                 {
325                         // MS_ASYNC: delayed flush; equivalent to "add-to-queue"
326                         // MS_SYNC: function flushes file immediately; no return until flush complete
327                         // MS_INVALIDATE: invalidate all mappings of the same file (shared)
328 
329                         if (msync (base, size, MS_SYNC | MS_INVALIDATE))
330                             host.error();
331                         return this;
332                 }
333         }
334 }
335 
336 
337 /*******************************************************************************
338 
339 *******************************************************************************/
340 
341 debug (FileMap)
342 {
343         import tango.io.Path;
344 
345         void main()
346         {
347                 auto file = new MappedFile ("foo.map");
348                 auto heap = file.resize (1_000_000);
349 
350                 auto file1 = new MappedFile ("foo1.map");
351                 auto heap1 = file1.resize (1_000_000);
352 
353                 file.close();
354                 remove ("foo.map");
355 
356                 file1.close();
357                 remove ("foo1.map");
358         }
359 }