1 /** 2 * Copyright: Copyright Jason White, 2014-2016 3 * License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 * Authors: Jason White 5 * 6 * Synopsis: 7 * --- 8 * // Creates a 1 GiB file containing random data. 9 * import io.file; 10 * import std.parallelism : parallel; 11 * auto f = File("big_random_file.dat", FileFlags.writeNew); 12 * f.length = 1024^^3; // 1 GiB 13 * 14 * auto map = f.memoryMap!size_t(Access.write); 15 * foreach (i, ref e; parallel(map[])) 16 * e = uniform!"[]"(size_t.min, size_t.max); 17 * --- 18 */ 19 module io.file.mmap; 20 21 public import io.file.stream; 22 23 version (Posix) 24 { 25 import core.sys.posix.sys.mman; 26 27 // Converts file access flags to POSIX protection flags. 28 private @property int protectionFlags(Access access) pure nothrow 29 { 30 int flags = PROT_NONE; 31 if (access & Access.read) flags |= PROT_READ; 32 if (access & Access.write) flags |= PROT_WRITE; 33 if (access & Access.execute) flags |= PROT_EXEC; 34 return flags; 35 } 36 } 37 else version (Windows) 38 { 39 import core.sys.windows.windows; 40 41 enum FILE_MAP_EXECUTE = 0x0020; 42 43 // Converts file access flags to Windows protection flags. 44 private @property DWORD protectionFlags(Access access) pure nothrow 45 { 46 switch (access) 47 { 48 case Access.read: return PAGE_READONLY; 49 case Access.write: return PAGE_READWRITE; 50 case Access.readWrite: return PAGE_READWRITE; 51 case Access.read | Access.execute: return PAGE_EXECUTE_READ; 52 case Access.write | Access.execute: return PAGE_EXECUTE_READWRITE; 53 case Access.readWrite | Access.execute: return PAGE_EXECUTE_READWRITE; 54 default: return PAGE_READONLY; 55 } 56 } 57 58 // Converts file access flags to Windows MapViewOfFileEx flags 59 private @property DWORD mapViewFlags(Access access) pure nothrow 60 { 61 DWORD flags = 0; 62 if (access & Access.read) flags |= FILE_MAP_READ; 63 if (access & Access.write) flags |= FILE_MAP_WRITE; 64 if (access & Access.execute) flags |= FILE_MAP_EXECUTE; 65 return flags; 66 } 67 } 68 else 69 { 70 static assert(false, "Not implemented on this platform."); 71 } 72 73 /** 74 * A memory mapped file. This essentially allows a file to be used as if it were 75 * a slice of memory. For many use cases, it is a very efficient means of 76 * accessing a file. 77 */ 78 private struct MemoryMapImpl(T) 79 { 80 // Memory mapped data. 81 T[] data; 82 83 alias data this; 84 85 version (Windows) 86 private HANDLE fileMap = null; 87 88 /** 89 * Maps the contents of the specified file into memory. 90 * 91 * Params: 92 * file = Open file to be mapped. The file may be closed after being 93 * mapped to memory. The file must not be a terminal or a pipe. It 94 * must have random access capabilities. 95 * access = Access flags of the memory region. Read-only access by 96 * default. 97 * length = Length of the memory map in number of $(D T). If 0, the length 98 * is taken to be the size of the file. 0 by default. 99 * start = Offset within the file to start the mapping in bytes. 0 by 100 * default. 101 * share = If true, changes are visible to other processes. If false, 102 * changes are not visible to other processes and are never 103 * written back to the file. True by default. 104 * address = The preferred memory address to map the file to. This is just 105 * a hint, the system is may or may not use this address. If 106 * null, the system chooses an appropriate address. Null by 107 * default. 108 * 109 * Throws: SysException if the memory map could not be created. 110 */ 111 this(File file, Access access = Access.read, size_t length = 0, 112 long start = 0, bool share = true, void* address = null) 113 { 114 import std.conv : to; 115 116 if (length == 0) 117 length = (file.length - start).to!size_t / T.sizeof; 118 119 version (Posix) 120 { 121 int flags = share ? MAP_SHARED : MAP_PRIVATE; 122 123 auto p = cast(T*)mmap( 124 address, // Preferred base address 125 length * T.sizeof, // Length of the memory map 126 access.protectionFlags, // Protection flags 127 flags, // Mapping flags 128 file.handle, // File descriptor 129 cast(off_t)start // Offset within the file 130 ); 131 132 sysEnforce(p != MAP_FAILED, "Failed to map file to memory"); 133 134 data = p[0 .. length]; 135 } 136 else version (Windows) 137 { 138 immutable ULARGE_INTEGER maxSize = 139 {QuadPart: cast(ulong)(length * T.sizeof)}; 140 141 // Create the file mapping object 142 fileMap = CreateFileMappingW( 143 file.handle, // File handle 144 null, // Security attributes 145 access.protectionFlags, // Page protection flags 146 maxSize.HighPart, // Maximum size (high-order bytes) 147 maxSize.LowPart, // Maximum size (low-order bytes) 148 null // Optional name to give the object 149 ); 150 151 sysEnforce(fileMap, "Failed to create file mapping object"); 152 153 scope(failure) CloseHandle(fileMap); 154 155 immutable ULARGE_INTEGER offset = {QuadPart: cast(ulong)start}; 156 157 // Create a view into the file mapping 158 auto p = cast(T*)MapViewOfFileEx( 159 fileMap, // File mapping object 160 access.mapViewFlags, // Desired access 161 offset.HighPart, // File offset (high-order bytes) 162 offset.LowPart, // File offset (low-order bytes) 163 length * T.sizeof, // Number of bytes to map 164 address, // Preferred base address 165 ); 166 167 sysEnforce(p, "Failed to map file to memory"); 168 169 data = p[0 .. length]; 170 } 171 } 172 173 /** 174 * Unmaps the file from memory and writes back any changes to the file 175 * system. 176 */ 177 ~this() 178 { 179 if (data is null) return; 180 181 version (Posix) 182 { 183 sysEnforce( 184 munmap(data.ptr, data.length * T.sizeof) == 0, 185 "Failed to unmap memory" 186 ); 187 } 188 else version (Windows) 189 { 190 sysEnforce( 191 UnmapViewOfFile(data.ptr) != 0, 192 "Failed to unmap memory" 193 ); 194 sysEnforce( 195 CloseHandle(fileMap), 196 "Failed to close file map object handle" 197 ); 198 } 199 } 200 201 /** 202 * Synchronously writes any pending changes to the file on the file system. 203 */ 204 void flush() 205 { 206 version (Posix) 207 { 208 sysEnforce( 209 msync(data.ptr, data.length * T.sizeof, MS_SYNC) == 0, 210 "Failed to flush memory map" 211 ); 212 } 213 else version (Windows) 214 { 215 // TODO: Make this synchronous 216 sysEnforce( 217 FlushViewOfFile(data.ptr, data.length * T.sizeof) != 0, 218 "Failed to flush memory map" 219 ); 220 } 221 } 222 223 /** 224 * Asynchronously writes any pending changes to the file on the file system. 225 */ 226 void flushAsync() 227 { 228 version (Posix) 229 { 230 sysEnforce( 231 msync(data.ptr, data.length * T.sizeof, MS_ASYNC) == 0, 232 "Failed to flush memory map" 233 ); 234 } 235 else version (Windows) 236 { 237 sysEnforce( 238 FlushViewOfFile(data.ptr, data.length * T.sizeof) != 0, 239 "Failed to flush memory map" 240 ); 241 } 242 } 243 244 // Disable appends. It is possible to use mremap() on Linux to extend (or 245 // contract) the length of the map. However, this is not a portable feature. 246 @disable void opOpAssign(string op = "~")(const(T)[] rhs); 247 } 248 249 import std.typecons; 250 alias MemoryMap(T) = RefCounted!(MemoryMapImpl!T, RefCountedAutoInitialize.no); 251 252 /** 253 * Convenience function for creating a memory map. 254 */ 255 auto memoryMap(T)(File file, Access access = Access.read, 256 size_t length = 0, long start = 0, bool share = true, 257 void* address = null) 258 { 259 return MemoryMap!T(file, access, length, start, share, address); 260 } 261 262 /// 263 unittest 264 { 265 auto tf = testFile(); 266 267 immutable newData = "The quick brown fox jumps over the lazy dog."; 268 269 // Modify the file 270 { 271 auto f = File(tf.name, FileFlags.readWriteEmpty); 272 f.length = newData.length; 273 274 auto map = f.memoryMap!char(Access.readWrite); 275 assert(map.length == newData.length); 276 277 map[] = newData[]; 278 279 assert(map[0 .. newData.length] == newData[]); 280 } 281 282 // Read the file back in 283 { 284 auto f = File(tf.name, FileFlags.readExisting); 285 auto map = f.memoryMap!char(Access.read); 286 assert(map.length == newData.length); 287 assert(map[0 .. newData.length] == newData[]); 288 } 289 } 290 291 unittest 292 { 293 import std.range : ElementType; 294 static assert(is(ElementType!(MemoryMap!size_t) == size_t)); 295 } 296 297 unittest 298 { 299 import std.exception; 300 301 auto tf = testFile(); 302 303 auto f = File(tf.name, FileFlags.readWriteEmpty); 304 assert(f.length == 0); 305 assert(collectException!SysException(f.memoryMap!char(Access.readWrite))); 306 } 307 308 unittest 309 { 310 import io.file.temp; 311 312 immutable int[] data = [4, 8, 15, 16, 23, 42]; 313 314 auto f = tempFile.file; 315 f.length = data.length * int.sizeof; 316 317 auto map = f.memoryMap!int(Access.readWrite); 318 map[] = data; 319 assert(map[] == data); 320 assert(map ~ [100, 200] == data ~ [100, 200]); 321 } 322 323 unittest 324 { 325 import io.file.temp; 326 import std.parallelism, std.random; 327 328 immutable N = 1024; 329 330 auto f = tempFile.file; 331 f.length = size_t.sizeof * N; 332 333 auto map = f.memoryMap!size_t(Access.readWrite); 334 assert(map.length == N); 335 336 foreach (i, ref e; parallel(map[])) 337 e = uniform!"[]"(size_t.min, size_t.max); 338 }