| // Written in the D programming language. |
| |
| /** |
| * Read and write memory mapped files. |
| * Copyright: Copyright Digital Mars 2004 - 2009. |
| * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). |
| * Authors: $(HTTP digitalmars.com, Walter Bright), |
| * Matthew Wilson |
| * Source: $(PHOBOSSRC std/_mmfile.d) |
| * |
| * $(SCRIPT inhibitQuickIndex = 1;) |
| */ |
| /* Copyright Digital Mars 2004 - 2009. |
| * Distributed under the Boost Software License, Version 1.0. |
| * (See accompanying file LICENSE_1_0.txt or copy at |
| * http://www.boost.org/LICENSE_1_0.txt) |
| */ |
| module std.mmfile; |
| |
| import core.stdc.errno; |
| import core.stdc.stdio; |
| import core.stdc.stdlib; |
| import std.conv, std.exception, std.stdio; |
| import std.file; |
| import std.path; |
| import std.string; |
| |
| import std.internal.cstring; |
| |
| //debug = MMFILE; |
| |
| version (Windows) |
| { |
| import core.sys.windows.windows; |
| import std.utf; |
| import std.windows.syserror; |
| } |
| else version (Posix) |
| { |
| import core.sys.posix.fcntl; |
| import core.sys.posix.sys.mman; |
| import core.sys.posix.sys.stat; |
| import core.sys.posix.unistd; |
| } |
| else |
| { |
| static assert(0); |
| } |
| |
| /** |
| * MmFile objects control the memory mapped file resource. |
| */ |
| class MmFile |
| { |
| /** |
| * The mode the memory mapped file is opened with. |
| */ |
| enum Mode |
| { |
| read, /// Read existing file |
| readWriteNew, /// Delete existing file, write new file |
| readWrite, /// Read/Write existing file, create if not existing |
| readCopyOnWrite, /// Read/Write existing file, copy on write |
| } |
| |
| /** |
| * Open memory mapped file filename for reading. |
| * File is closed when the object instance is deleted. |
| * Throws: |
| * std.file.FileException |
| */ |
| this(string filename) |
| { |
| this(filename, Mode.read, 0, null); |
| } |
| |
| version (linux) this(File file, Mode mode = Mode.read, ulong size = 0, |
| void* address = null, size_t window = 0) |
| { |
| // Save a copy of the File to make sure the fd stays open. |
| this.file = file; |
| this(file.fileno, mode, size, address, window); |
| } |
| |
| version (linux) private this(int fildes, Mode mode, ulong size, |
| void* address, size_t window) |
| { |
| int oflag; |
| int fmode; |
| |
| switch (mode) |
| { |
| case Mode.read: |
| flags = MAP_SHARED; |
| prot = PROT_READ; |
| oflag = O_RDONLY; |
| fmode = 0; |
| break; |
| |
| case Mode.readWriteNew: |
| assert(size != 0); |
| flags = MAP_SHARED; |
| prot = PROT_READ | PROT_WRITE; |
| oflag = O_CREAT | O_RDWR | O_TRUNC; |
| fmode = octal!660; |
| break; |
| |
| case Mode.readWrite: |
| flags = MAP_SHARED; |
| prot = PROT_READ | PROT_WRITE; |
| oflag = O_CREAT | O_RDWR; |
| fmode = octal!660; |
| break; |
| |
| case Mode.readCopyOnWrite: |
| flags = MAP_PRIVATE; |
| prot = PROT_READ | PROT_WRITE; |
| oflag = O_RDWR; |
| fmode = 0; |
| break; |
| |
| default: |
| assert(0); |
| } |
| |
| fd = fildes; |
| |
| // Adjust size |
| stat_t statbuf = void; |
| errnoEnforce(fstat(fd, &statbuf) == 0); |
| if (prot & PROT_WRITE && size > statbuf.st_size) |
| { |
| // Need to make the file size bytes big |
| lseek(fd, cast(off_t)(size - 1), SEEK_SET); |
| char c = 0; |
| core.sys.posix.unistd.write(fd, &c, 1); |
| } |
| else if (prot & PROT_READ && size == 0) |
| size = statbuf.st_size; |
| this.size = size; |
| |
| // Map the file into memory! |
| size_t initial_map = (window && 2*window<size) |
| ? 2*window : cast(size_t) size; |
| auto p = mmap(address, initial_map, prot, flags, fd, 0); |
| if (p == MAP_FAILED) |
| { |
| errnoEnforce(false, "Could not map file into memory"); |
| } |
| data = p[0 .. initial_map]; |
| } |
| |
| /** |
| * Open memory mapped file filename in mode. |
| * File is closed when the object instance is deleted. |
| * Params: |
| * filename = name of the file. |
| * If null, an anonymous file mapping is created. |
| * mode = access mode defined above. |
| * size = the size of the file. If 0, it is taken to be the |
| * size of the existing file. |
| * address = the preferred address to map the file to, |
| * although the system is not required to honor it. |
| * If null, the system selects the most convenient address. |
| * window = preferred block size of the amount of data to map at one time |
| * with 0 meaning map the entire file. The window size must be a |
| * multiple of the memory allocation page size. |
| * Throws: |
| * std.file.FileException |
| */ |
| this(string filename, Mode mode, ulong size, void* address, |
| size_t window = 0) |
| { |
| this.filename = filename; |
| this.mMode = mode; |
| this.window = window; |
| this.address = address; |
| |
| version (Windows) |
| { |
| void* p; |
| uint dwDesiredAccess2; |
| uint dwShareMode; |
| uint dwCreationDisposition; |
| uint flProtect; |
| |
| switch (mode) |
| { |
| case Mode.read: |
| dwDesiredAccess2 = GENERIC_READ; |
| dwShareMode = FILE_SHARE_READ; |
| dwCreationDisposition = OPEN_EXISTING; |
| flProtect = PAGE_READONLY; |
| dwDesiredAccess = FILE_MAP_READ; |
| break; |
| |
| case Mode.readWriteNew: |
| assert(size != 0); |
| dwDesiredAccess2 = GENERIC_READ | GENERIC_WRITE; |
| dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; |
| dwCreationDisposition = CREATE_ALWAYS; |
| flProtect = PAGE_READWRITE; |
| dwDesiredAccess = FILE_MAP_WRITE; |
| break; |
| |
| case Mode.readWrite: |
| dwDesiredAccess2 = GENERIC_READ | GENERIC_WRITE; |
| dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; |
| dwCreationDisposition = OPEN_ALWAYS; |
| flProtect = PAGE_READWRITE; |
| dwDesiredAccess = FILE_MAP_WRITE; |
| break; |
| |
| case Mode.readCopyOnWrite: |
| dwDesiredAccess2 = GENERIC_READ | GENERIC_WRITE; |
| dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; |
| dwCreationDisposition = OPEN_EXISTING; |
| flProtect = PAGE_WRITECOPY; |
| dwDesiredAccess = FILE_MAP_COPY; |
| break; |
| |
| default: |
| assert(0); |
| } |
| |
| if (filename != null) |
| { |
| hFile = CreateFileW(filename.tempCStringW(), |
| dwDesiredAccess2, |
| dwShareMode, |
| null, |
| dwCreationDisposition, |
| FILE_ATTRIBUTE_NORMAL, |
| cast(HANDLE) null); |
| wenforce(hFile != INVALID_HANDLE_VALUE, "CreateFileW"); |
| } |
| else |
| hFile = INVALID_HANDLE_VALUE; |
| |
| scope(failure) |
| { |
| if (hFile != INVALID_HANDLE_VALUE) |
| { |
| CloseHandle(hFile); |
| hFile = INVALID_HANDLE_VALUE; |
| } |
| } |
| |
| int hi = cast(int)(size >> 32); |
| hFileMap = CreateFileMappingW(hFile, null, flProtect, |
| hi, cast(uint) size, null); |
| wenforce(hFileMap, "CreateFileMapping"); |
| scope(failure) |
| { |
| CloseHandle(hFileMap); |
| hFileMap = null; |
| } |
| |
| if (size == 0 && filename != null) |
| { |
| uint sizehi; |
| uint sizelow = GetFileSize(hFile, &sizehi); |
| wenforce(sizelow != INVALID_FILE_SIZE || GetLastError() != ERROR_SUCCESS, |
| "GetFileSize"); |
| size = (cast(ulong) sizehi << 32) + sizelow; |
| } |
| this.size = size; |
| |
| size_t initial_map = (window && 2*window<size) |
| ? 2*window : cast(size_t) size; |
| p = MapViewOfFileEx(hFileMap, dwDesiredAccess, 0, 0, |
| initial_map, address); |
| wenforce(p, "MapViewOfFileEx"); |
| data = p[0 .. initial_map]; |
| |
| debug (MMFILE) printf("MmFile.this(): p = %p, size = %d\n", p, size); |
| } |
| else version (Posix) |
| { |
| void* p; |
| int oflag; |
| int fmode; |
| |
| switch (mode) |
| { |
| case Mode.read: |
| flags = MAP_SHARED; |
| prot = PROT_READ; |
| oflag = O_RDONLY; |
| fmode = 0; |
| break; |
| |
| case Mode.readWriteNew: |
| assert(size != 0); |
| flags = MAP_SHARED; |
| prot = PROT_READ | PROT_WRITE; |
| oflag = O_CREAT | O_RDWR | O_TRUNC; |
| fmode = octal!660; |
| break; |
| |
| case Mode.readWrite: |
| flags = MAP_SHARED; |
| prot = PROT_READ | PROT_WRITE; |
| oflag = O_CREAT | O_RDWR; |
| fmode = octal!660; |
| break; |
| |
| case Mode.readCopyOnWrite: |
| flags = MAP_PRIVATE; |
| prot = PROT_READ | PROT_WRITE; |
| oflag = O_RDWR; |
| fmode = 0; |
| break; |
| |
| default: |
| assert(0); |
| } |
| |
| if (filename.length) |
| { |
| fd = .open(filename.tempCString(), oflag, fmode); |
| errnoEnforce(fd != -1, "Could not open file "~filename); |
| |
| stat_t statbuf; |
| if (fstat(fd, &statbuf)) |
| { |
| //printf("\tfstat error, errno = %d\n", errno); |
| .close(fd); |
| fd = -1; |
| errnoEnforce(false, "Could not stat file "~filename); |
| } |
| |
| if (prot & PROT_WRITE && size > statbuf.st_size) |
| { |
| // Need to make the file size bytes big |
| .lseek(fd, cast(off_t)(size - 1), SEEK_SET); |
| char c = 0; |
| core.sys.posix.unistd.write(fd, &c, 1); |
| } |
| else if (prot & PROT_READ && size == 0) |
| size = statbuf.st_size; |
| } |
| else |
| { |
| fd = -1; |
| version (CRuntime_Glibc) import core.sys.linux.sys.mman : MAP_ANON; |
| flags |= MAP_ANON; |
| } |
| this.size = size; |
| size_t initial_map = (window && 2*window<size) |
| ? 2*window : cast(size_t) size; |
| p = mmap(address, initial_map, prot, flags, fd, 0); |
| if (p == MAP_FAILED) |
| { |
| if (fd != -1) |
| { |
| .close(fd); |
| fd = -1; |
| } |
| errnoEnforce(false, "Could not map file "~filename); |
| } |
| |
| data = p[0 .. initial_map]; |
| } |
| else |
| { |
| static assert(0); |
| } |
| } |
| |
| /** |
| * Flushes pending output and closes the memory mapped file. |
| */ |
| ~this() |
| { |
| debug (MMFILE) printf("MmFile.~this()\n"); |
| unmap(); |
| data = null; |
| version (Windows) |
| { |
| wenforce(hFileMap == null || CloseHandle(hFileMap) == TRUE, |
| "Could not close file handle"); |
| hFileMap = null; |
| |
| wenforce(!hFile || hFile == INVALID_HANDLE_VALUE |
| || CloseHandle(hFile) == TRUE, |
| "Could not close handle"); |
| hFile = INVALID_HANDLE_VALUE; |
| } |
| else version (Posix) |
| { |
| version (linux) |
| { |
| if (file !is File.init) |
| { |
| // The File destructor will close the file, |
| // if it is the only remaining reference. |
| return; |
| } |
| } |
| errnoEnforce(fd == -1 || fd <= 2 |
| || .close(fd) != -1, |
| "Could not close handle"); |
| fd = -1; |
| } |
| else |
| { |
| static assert(0); |
| } |
| } |
| |
| /* Flush any pending output. |
| */ |
| void flush() |
| { |
| debug (MMFILE) printf("MmFile.flush()\n"); |
| version (Windows) |
| { |
| FlushViewOfFile(data.ptr, data.length); |
| } |
| else version (Posix) |
| { |
| int i; |
| i = msync(cast(void*) data, data.length, MS_SYNC); // sys/mman.h |
| errnoEnforce(i == 0, "msync failed"); |
| } |
| else |
| { |
| static assert(0); |
| } |
| } |
| |
| /** |
| * Gives size in bytes of the memory mapped file. |
| */ |
| @property ulong length() const |
| { |
| debug (MMFILE) printf("MmFile.length()\n"); |
| return size; |
| } |
| |
| /** |
| * Read-only property returning the file mode. |
| */ |
| Mode mode() |
| { |
| debug (MMFILE) printf("MmFile.mode()\n"); |
| return mMode; |
| } |
| |
| /** |
| * Returns entire file contents as an array. |
| */ |
| void[] opSlice() |
| { |
| debug (MMFILE) printf("MmFile.opSlice()\n"); |
| return opSlice(0,size); |
| } |
| |
| /** |
| * Returns slice of file contents as an array. |
| */ |
| void[] opSlice(ulong i1, ulong i2) |
| { |
| debug (MMFILE) printf("MmFile.opSlice(%lld, %lld)\n", i1, i2); |
| ensureMapped(i1,i2); |
| size_t off1 = cast(size_t)(i1-start); |
| size_t off2 = cast(size_t)(i2-start); |
| return data[off1 .. off2]; |
| } |
| |
| /** |
| * Returns byte at index i in file. |
| */ |
| ubyte opIndex(ulong i) |
| { |
| debug (MMFILE) printf("MmFile.opIndex(%lld)\n", i); |
| ensureMapped(i); |
| size_t off = cast(size_t)(i-start); |
| return (cast(ubyte[]) data)[off]; |
| } |
| |
| /** |
| * Sets and returns byte at index i in file to value. |
| */ |
| ubyte opIndexAssign(ubyte value, ulong i) |
| { |
| debug (MMFILE) printf("MmFile.opIndex(%lld, %d)\n", i, value); |
| ensureMapped(i); |
| size_t off = cast(size_t)(i-start); |
| return (cast(ubyte[]) data)[off] = value; |
| } |
| |
| |
| // return true if the given position is currently mapped |
| private int mapped(ulong i) |
| { |
| debug (MMFILE) printf("MmFile.mapped(%lld, %lld, %d)\n", i,start, |
| data.length); |
| return i >= start && i < start+data.length; |
| } |
| |
| // unmap the current range |
| private void unmap() |
| { |
| debug (MMFILE) printf("MmFile.unmap()\n"); |
| version (Windows) |
| { |
| wenforce(!data.ptr || UnmapViewOfFile(data.ptr) != FALSE, "UnmapViewOfFile"); |
| } |
| else |
| { |
| errnoEnforce(!data.ptr || munmap(cast(void*) data, data.length) == 0, |
| "munmap failed"); |
| } |
| data = null; |
| } |
| |
| // map range |
| private void map(ulong start, size_t len) |
| { |
| debug (MMFILE) printf("MmFile.map(%lld, %d)\n", start, len); |
| void* p; |
| if (start+len > size) |
| len = cast(size_t)(size-start); |
| version (Windows) |
| { |
| uint hi = cast(uint)(start >> 32); |
| p = MapViewOfFileEx(hFileMap, dwDesiredAccess, hi, cast(uint) start, len, address); |
| wenforce(p, "MapViewOfFileEx"); |
| } |
| else |
| { |
| p = mmap(address, len, prot, flags, fd, cast(off_t) start); |
| errnoEnforce(p != MAP_FAILED); |
| } |
| data = p[0 .. len]; |
| this.start = start; |
| } |
| |
| // ensure a given position is mapped |
| private void ensureMapped(ulong i) |
| { |
| debug (MMFILE) printf("MmFile.ensureMapped(%lld)\n", i); |
| if (!mapped(i)) |
| { |
| unmap(); |
| if (window == 0) |
| { |
| map(0,cast(size_t) size); |
| } |
| else |
| { |
| ulong block = i/window; |
| if (block == 0) |
| map(0,2*window); |
| else |
| map(window*(block-1),3*window); |
| } |
| } |
| } |
| |
| // ensure a given range is mapped |
| private void ensureMapped(ulong i, ulong j) |
| { |
| debug (MMFILE) printf("MmFile.ensureMapped(%lld, %lld)\n", i, j); |
| if (!mapped(i) || !mapped(j-1)) |
| { |
| unmap(); |
| if (window == 0) |
| { |
| map(0,cast(size_t) size); |
| } |
| else |
| { |
| ulong iblock = i/window; |
| ulong jblock = (j-1)/window; |
| if (iblock == 0) |
| { |
| map(0,cast(size_t)(window*(jblock+2))); |
| } |
| else |
| { |
| map(window*(iblock-1),cast(size_t)(window*(jblock-iblock+3))); |
| } |
| } |
| } |
| } |
| |
| private: |
| string filename; |
| void[] data; |
| ulong start; |
| size_t window; |
| ulong size; |
| Mode mMode; |
| void* address; |
| version (linux) File file; |
| |
| version (Windows) |
| { |
| HANDLE hFile = INVALID_HANDLE_VALUE; |
| HANDLE hFileMap = null; |
| uint dwDesiredAccess; |
| } |
| else version (Posix) |
| { |
| int fd; |
| int prot; |
| int flags; |
| int fmode; |
| } |
| else |
| { |
| static assert(0); |
| } |
| |
| // Report error, where errno gives the error number |
| // void errNo() |
| // { |
| // version (Windows) |
| // { |
| // throw new FileException(filename, GetLastError()); |
| // } |
| // else version (linux) |
| // { |
| // throw new FileException(filename, errno); |
| // } |
| // else |
| // { |
| // static assert(0); |
| // } |
| // } |
| } |
| |
| @system unittest |
| { |
| import core.memory : GC; |
| import std.file : deleteme; |
| |
| const size_t K = 1024; |
| size_t win = 64*K; // assume the page size is 64K |
| version (Windows) |
| { |
| /+ these aren't defined in core.sys.windows.windows so let's use default |
| SYSTEM_INFO sysinfo; |
| GetSystemInfo(&sysinfo); |
| win = sysinfo.dwAllocationGranularity; |
| +/ |
| } |
| else version (linux) |
| { |
| // getpagesize() is not defined in the unix D headers so use the guess |
| } |
| string test_file = std.file.deleteme ~ "-testing.txt"; |
| MmFile mf = new MmFile(test_file,MmFile.Mode.readWriteNew, |
| 100*K,null,win); |
| ubyte[] str = cast(ubyte[])"1234567890"; |
| ubyte[] data = cast(ubyte[]) mf[0 .. 10]; |
| data[] = str[]; |
| assert( mf[0 .. 10] == str ); |
| data = cast(ubyte[]) mf[50 .. 60]; |
| data[] = str[]; |
| assert( mf[50 .. 60] == str ); |
| ubyte[] data2 = cast(ubyte[]) mf[20*K .. 60*K]; |
| assert( data2.length == 40*K ); |
| assert( data2[$-1] == 0 ); |
| mf[100*K-1] = cast(ubyte)'b'; |
| data2 = cast(ubyte[]) mf[21*K .. 100*K]; |
| assert( data2.length == 79*K ); |
| assert( data2[$-1] == 'b' ); |
| |
| destroy(mf); |
| GC.free(&mf); |
| |
| std.file.remove(test_file); |
| // Create anonymous mapping |
| auto test = new MmFile(null, MmFile.Mode.readWriteNew, 1024*1024, null); |
| } |
| |
| version (linux) |
| @system unittest // Issue 14868 |
| { |
| import std.file : deleteme; |
| import std.typecons : scoped; |
| |
| // Test retaining ownership of File/fd |
| |
| auto fn = std.file.deleteme ~ "-testing.txt"; |
| scope(exit) std.file.remove(fn); |
| File(fn, "wb").writeln("Testing!"); |
| scoped!MmFile(File(fn)); |
| |
| // Test that unique ownership of File actually leads to the fd being closed |
| |
| auto f = File(fn); |
| auto fd = f.fileno; |
| { |
| auto mf = scoped!MmFile(f); |
| f = File.init; |
| } |
| assert(.close(fd) == -1); |
| } |
| |
| @system unittest // Issue 14994, 14995 |
| { |
| import std.file : deleteme; |
| import std.typecons : scoped; |
| |
| // Zero-length map may or may not be valid on OSX and NetBSD |
| version (OSX) |
| import std.exception : verifyThrown = collectException; |
| version (NetBSD) |
| import std.exception : verifyThrown = collectException; |
| else |
| import std.exception : verifyThrown = assertThrown; |
| |
| auto fn = std.file.deleteme ~ "-testing.txt"; |
| scope(exit) std.file.remove(fn); |
| verifyThrown(scoped!MmFile(fn, MmFile.Mode.readWrite, 0, null)); |
| } |