// 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> 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 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= 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)); }