2009-12-14 Miguel de Icaza <miguel@novell.com>
[mono.git] / mcs / class / System.Core / System.IO.MemoryMappedFiles / MemoryMappedFile.cs
1 //
2 // MemoryMappedFile.cs
3 //
4 // Authors:
5 //      Zoltan Varga (vargaz@gmail.com)
6 //
7 // Copyright (C) 2009, Novell, Inc (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 #if NET_4_0
30
31 using System;
32 using System.IO;
33 using System.Collections.Generic;
34 using Microsoft.Win32.SafeHandles;
35 using Mono.Unix.Native;
36 using Mono.Unix;
37 using System.Runtime.InteropServices;
38
39 namespace System.IO.MemoryMappedFiles
40 {
41         public class MemoryMappedFile : IDisposable {
42                 MemoryMappedFileAccess fileAccess;
43                 string name;
44                 long fileCapacity;
45
46                 //
47                 // We allow the use of either the FileStream/keepOpen combo
48                 // or a Unix file descriptor.  This way we avoid the dependency on
49                 // Mono's io-layer having the Unix file descriptors mapped to
50                 // the same io-layer handle
51                 //
52                 FileStream stream;
53                 bool keepOpen;
54                 int unix_fd;
55                 
56                 public static MemoryMappedFile CreateFromFile (FileStream fileStream)
57                 {
58                         if (fileStream == null)
59                                 throw new ArgumentNullException ("fileStream");
60
61                         return new MemoryMappedFile () {
62                                 stream = fileStream,
63                                 fileAccess = MemoryMappedFileAccess.ReadWrite
64                         };
65                 }
66
67                 public static MemoryMappedFile CreateFromFile (string path)
68                 {
69                         return CreateFromFile (path, FileMode.Open, null, 0, MemoryMappedFileAccess.ReadWrite);
70                 }
71
72
73                 public static MemoryMappedFile CreateFromFile (string path, FileMode mode)
74                 {
75                         return CreateFromFile (path, mode, null, 0, MemoryMappedFileAccess.ReadWrite);
76                 }
77
78                 public static MemoryMappedFile CreateFromFile (string path, FileMode mode, string mapName)
79                 {
80                         return CreateFromFile (path, mode, mapName, 0, MemoryMappedFileAccess.ReadWrite);
81                 }
82
83                 public static MemoryMappedFile CreateFromFile (string path, FileMode mode, string mapName, long capacity)
84                 {
85                         return CreateFromFile (path, mode, mapName, capacity, MemoryMappedFileAccess.ReadWrite);
86                 }
87
88                 //
89                 // Turns the FileMode into the first half of open(2) flags
90                 //
91                 static OpenFlags ToUnixMode (FileMode mode)
92                 {
93                         switch (mode){
94                         case FileMode.CreateNew:
95                                 return OpenFlags.O_CREAT | OpenFlags.O_EXCL;
96                                 
97                         case FileMode.Create:
98                                 return OpenFlags.O_CREAT | OpenFlags.O_TRUNC;
99                                 
100                         case FileMode.OpenOrCreate:
101                                 return OpenFlags.O_CREAT;
102                                 
103                         case FileMode.Truncate:
104                                 return OpenFlags.O_TRUNC;
105                                 
106                         case FileMode.Append:
107                                 return OpenFlags.O_APPEND;
108                         default:
109                         case FileMode.Open:
110                                 return 0;
111                         }
112                 }
113
114                 //
115                 // Turns the MemoryMappedFileAccess into the second half of open(2) flags
116                 //
117                 static OpenFlags ToUnixMode (MemoryMappedFileAccess access)
118                 {
119                         switch (access){
120                         case MemoryMappedFileAccess.CopyOnWrite:
121                         case MemoryMappedFileAccess.ReadWriteExecute:
122                         case MemoryMappedFileAccess.ReadWrite:
123                                 return OpenFlags.O_RDWR;
124                                 
125                         case MemoryMappedFileAccess.Write:
126                                 return OpenFlags.O_WRONLY;
127
128                         case MemoryMappedFileAccess.ReadExecute:
129                         case MemoryMappedFileAccess.Read:
130                         default:
131                                 return OpenFlags.O_RDONLY;
132                         }
133                 }
134
135                 public static MemoryMappedFile CreateFromFile (string path, FileMode mode, string mapName, long capacity, MemoryMappedFileAccess access)
136                 {
137                         if (path == null)
138                                 throw new ArgumentNullException ("path");
139                         if (path.Length == 0)
140                                 throw new ArgumentException ("path");
141                         if (mapName != null && mapName.Length == 0)
142                                 throw new ArgumentException ("mapName");
143
144                         int fd;
145                         if (MonoUtil.IsUnix){
146                                 Stat buf;
147                                 if (Syscall.stat (path, out buf) == -1)
148                                         UnixMarshal.ThrowExceptionForLastError ();
149
150                                 if ((capacity == 0 && buf.st_size == 0) || (capacity > buf.st_size))
151                                         throw new ArgumentException ("capacity");
152                                 
153                                 fd = Syscall.open (path, ToUnixMode (mode) | ToUnixMode (access), FilePermissions.DEFFILEMODE);
154
155                                 if (fd == -1)
156                                         UnixMarshal.ThrowExceptionForLastError ();
157                         } else
158                                 throw new NotImplementedException ();
159                         
160                         return new MemoryMappedFile () {
161                                 unix_fd = fd,
162                                 fileAccess = access,
163                                 name = mapName,
164                                 fileCapacity = capacity
165                         };
166                 }
167
168                 public static void ConfigureUnixFD (IntPtr handle, HandleInheritability h)
169                 {
170                         // TODO: Mono.Posix is lacking O_CLOEXEC definitions for fcntl.
171                 }
172
173
174                 [DllImport("kernel32.dll", SetLastError = true)]
175                 static extern bool SetHandleInformation (IntPtr hObject, int dwMask, int dwFlags);
176                 public static void ConfigureWindowsFD (IntPtr handle, HandleInheritability h)
177                 {
178                         SetHandleInformation (handle, 1 /* FLAG_INHERIT */, h == HandleInheritability.None ? 0 : 1);
179                 }
180
181                 [MonoLimitation ("memoryMappedFileSecurity is currently ignored")]
182                 public static MemoryMappedFile CreateFromFile (FileStream fileStream, string mapName, long capacity, MemoryMappedFileAccess access,
183                                                                MemoryMappedFileSecurity memoryMappedFileSecurity, HandleInheritability inheritability,
184                                                                bool leaveOpen)
185                 {
186                         if (fileStream == null)
187                                 throw new ArgumentNullException ("fileStream");
188                         if (mapName != null && mapName.Length == 0)
189                                 throw new ArgumentException ("mapName");
190                         if ((capacity == 0 && fileStream.Length == 0) || (capacity > fileStream.Length))
191                                 throw new ArgumentException ("capacity");
192
193                         if (MonoUtil.IsUnix)
194                                 ConfigureUnixFD (fileStream.Handle, inheritability);
195                         else
196                                 ConfigureWindowsFD (fileStream.Handle, inheritability);
197                                 
198                         return new MemoryMappedFile () {
199                                 stream = fileStream,
200                                 fileAccess = access,
201                                 name = mapName,
202                                 fileCapacity = capacity,
203                                 keepOpen = leaveOpen
204                         };
205                 }
206
207                 [MonoLimitation ("CreateNew requires that mapName be a file name on Unix")]
208                 public static MemoryMappedFile CreateNew (string mapName, long capacity)
209                 {
210                         return CreateNew (mapName, capacity, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.DelayAllocatePages, null, 0);
211                 }
212
213                 [MonoLimitation ("CreateNew requires that mapName be a file name on Unix")]
214                 public static MemoryMappedFile CreateNew (string mapName, long capacity, MemoryMappedFileAccess access) 
215                 {
216                         return CreateNew (mapName, capacity, access, MemoryMappedFileOptions.DelayAllocatePages, null, 0);
217                 }
218
219                 [MonoLimitation ("CreateNew requires that mapName be a file name on Unix; options and memoryMappedFileSecurity are ignored")]
220                 public static MemoryMappedFile CreateNew (string mapName, long capacity, MemoryMappedFileAccess access,
221                                                           MemoryMappedFileOptions options, MemoryMappedFileSecurity memoryMappedFileSecurity,
222                                                           HandleInheritability handleInheritability)
223                 {
224                         return CreateFromFile (mapName, FileMode.CreateNew, mapName, capacity, access);
225                 }
226
227                 [MonoTODO]
228                         public static MemoryMappedFile CreateOrOpen (string mapName, long capacity) {
229                         throw new NotImplementedException ();
230                 }
231
232                 [MonoTODO]
233                         public static MemoryMappedFile CreateOrOpen (string mapName, long capacity, MemoryMappedFileAccess access) {
234                         throw new NotImplementedException ();
235                 }
236
237                 /*
238                 [MonoTODO]
239                         public static MemoryMappedFile CreateOrOpen (string mapName, long capacity, MemoryMappedFileAccess access, MemoryMappedFileOptions options, MemoryMappedFileSecurity memoryMappedFileSecurity, HandleInheritability handleInheritability) {
240                         throw new NotImplementedException ();
241                 }
242                 */
243
244                 public MemoryMappedViewStream CreateViewStream ()
245                 {
246                         return CreateViewStream (0, 0);
247                 }
248
249                 public MemoryMappedViewStream CreateViewStream (long offset, long size)
250                 {
251                         return CreateViewStream (offset, size, MemoryMappedFileAccess.ReadWrite);
252                 }
253
254                 public MemoryMappedViewStream CreateViewStream (long offset, long size, MemoryMappedFileAccess access)
255                 {
256                         return new MemoryMappedViewStream (stream != null ? (int)stream.Handle : unix_fd, offset, size, access);
257                 }
258
259                 public MemoryMappedViewAccessor CreateViewAccessor ()
260                 {
261                         return CreateViewAccessor (0, 0);
262                 }
263
264                 public MemoryMappedViewAccessor CreateViewAccessor (long offset, long size)
265                 {
266                         return CreateViewAccessor (offset, size, MemoryMappedFileAccess.ReadWrite);
267                 }
268
269                 public MemoryMappedViewAccessor CreateViewAccessor (long offset, long size, MemoryMappedFileAccess access)
270                 {
271                         int file_handle = stream != null ? (int) stream.Handle : unix_fd;
272                         
273                         return new MemoryMappedViewAccessor (file_handle, offset, size, access);
274                 }
275
276                 MemoryMappedFile ()
277                 {
278                 }
279
280                 public void Dispose ()
281                 {
282                         Dispose (true);
283                 }
284
285                 protected virtual void Dispose (bool disposing)
286                 {
287                         if (disposing){
288                                 if (stream != null){
289                                         if (keepOpen == false)
290                                                 stream.Close ();
291                                         unix_fd = -1;
292                                 }
293                                 if (unix_fd != -1)
294                                         Syscall.close (unix_fd);
295                                 unix_fd = -1;
296                                 stream = null;
297                         }
298                 }
299                 
300                 [MonoTODO]
301                 public SafeMemoryMappedFileHandle SafeMemoryMappedFileHandle {
302                         get {
303                                 throw new NotImplementedException ();
304                         }
305                 }
306
307                 static int pagesize;
308
309                 static MmapProts ToUnixProts (MemoryMappedFileAccess access)
310                 {
311                         MmapProts prots;
312                         
313                         switch (access){
314                         case MemoryMappedFileAccess.ReadWrite:
315                                 return MmapProts.PROT_WRITE | MmapProts.PROT_READ;
316                                 break;
317                                 
318                         case MemoryMappedFileAccess.Write:
319                                 return MmapProts.PROT_WRITE;
320                                 
321                         case MemoryMappedFileAccess.CopyOnWrite:
322                                 return MmapProts.PROT_WRITE | MmapProts.PROT_READ;
323                                 
324                         case MemoryMappedFileAccess.ReadExecute:
325                                 return MmapProts.PROT_EXEC;
326                                 
327                         case MemoryMappedFileAccess.ReadWriteExecute:
328                                 return MmapProts.PROT_WRITE | MmapProts.PROT_READ | MmapProts.PROT_EXEC;
329                                 
330                         case MemoryMappedFileAccess.Read:
331                         default:
332                                 return MmapProts.PROT_READ;
333                         }
334                 }
335
336                 internal static unsafe void MapPosix (int file_handle, long offset, long size, MemoryMappedFileAccess access, out IntPtr map_addr, out int offset_diff)
337                 {
338                         if (pagesize == 0)
339                                 pagesize = Syscall.getpagesize ();
340
341                         Stat buf;
342                         Syscall.fstat (file_handle, out buf);
343                         long fsize = buf.st_size;
344
345                         if (size == 0 || size > fsize)
346                                 size = fsize;
347                         
348                         // Align offset
349                         long real_offset = offset & ~(pagesize - 1);
350
351                         offset_diff = (int)(offset - real_offset);
352
353                         // FIXME: Need to determine the unix fd for the file, Handle is only
354                         // equal to it by accident
355                         //
356                         // The new API no longer uses FileStream everywhere, but exposes instead
357                         // the filename (with one exception), we could move this API to use
358                         // file descriptors instead of the FileStream plus its Handle.
359                         //
360                         map_addr = Syscall.mmap (IntPtr.Zero, (ulong) size,
361                                                  ToUnixProts (access),
362                                                  access == MemoryMappedFileAccess.CopyOnWrite ? MmapFlags.MAP_PRIVATE : MmapFlags.MAP_SHARED,
363                                                  file_handle, real_offset);
364
365                         if (map_addr == (IntPtr)(-1))
366                                 throw new IOException ("mmap failed for fd#" + file_handle + "(" + offset + ", " + size + ")");
367                 }
368
369                 internal static bool UnmapPosix (IntPtr map_addr, ulong map_size)
370                 {
371                         return Syscall.munmap (map_addr, map_size) == 0;
372                 }
373
374         }
375 }
376
377 #endif