Mercurial > projects > ldc
diff lphobos/std/mmfile.d @ 473:373489eeaf90
Applied downs' lphobos update
author | Tomas Lindquist Olsen <tomas.l.olsen@gmail.com> |
---|---|
date | Mon, 04 Aug 2008 19:28:49 +0200 |
parents | |
children | 88e23f8c2354 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lphobos/std/mmfile.d Mon Aug 04 19:28:49 2008 +0200 @@ -0,0 +1,648 @@ +/* Copyright 2004-2005 by Digital Mars + * Written by Walter Bright and Matthew Wilson + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, in both source and binary form, subject to the following + * restrictions: + * + * - The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * - Altered source versions must be plainly marked as such, and must not + * be misrepresented as being the original software. + * - This notice may not be removed or altered from any source + * distribution. + * + */ + +/** + * Read and write memory mapped files. + * Macros: + * WIKI=Phobos/StdMmfile + */ + +/* NOTE: This file has been patched from the original DMD distribution to + work with the GDC compiler. + + Modified by David Friedman, September 2004 +*/ + +module std.mmfile; + +private import std.c.stdio; +private import std.c.stdlib; +private import std.string; + +//debug = MMFILE; + +version (Win32) +{ + private import std.c.windows.windows; + private import std.utf; + + private uint dwVersion; + + static this() + { // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sysinfo/base/getversion.asp + dwVersion = GetVersion(); + } + + private const bool Have_MMFile = true; // private for now... +} +else version (Unix) +{ + version (/*GNU_Unix_Have_MMap*/Unix) + { + version(linux) { + import std.c.linux.linux; + alias std.c.linux.linux unix; + } else { + private import std.c.unix.unix; + alias std.c.unix.unix unix; + } + + version = unix_mm; + private const bool Have_MMFile = true; + } + else + { + private const bool Have_MMFile = false; + } + +} +else +{ + private const bool Have_MMFile = false; + // Can't simply fail because std.stream imports this module. + //static assert(0); +} + +static if (Have_MMFile) +{ + private import std.file; + private import std.path; +} + +/** + * 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(char[] filename) + { + this(filename, Mode.Read, 0, null); + } + + /** + * 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(char[] filename, Mode mode, ulong size, void* address, + size_t window = 0) + { + this.filename = filename; + this.mMode = mode; + this.window = window; + this.address = address; + + version (Win32) + { + void* p; + uint dwDesiredAccess2; + uint dwShareMode; + uint dwCreationDisposition; + uint flProtect; + + if (dwVersion & 0x80000000 && (dwVersion & 0xFF) == 3) + { + throw new FileException(filename, + "Win32s does not implement mm files"); + } + + 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: + if (dwVersion & 0x80000000) + { + throw new FileException(filename, + "Win9x does not implement copy on write"); + } + 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) + { + if (useWfuncs) + { + auto namez = std.utf.toUTF16z(filename); + hFile = CreateFileW(namez, + dwDesiredAccess2, + dwShareMode, + null, + dwCreationDisposition, + FILE_ATTRIBUTE_NORMAL, + cast(HANDLE)null); + } + else + { + auto namez = std.file.toMBSz(filename); + hFile = CreateFileA(namez, + dwDesiredAccess2, + dwShareMode, + null, + dwCreationDisposition, + FILE_ATTRIBUTE_NORMAL, + cast(HANDLE)null); + } + if (hFile == INVALID_HANDLE_VALUE) + goto err1; + } + else + hFile = null; + + int hi = cast(int)(size>>32); + hFileMap = CreateFileMappingA(hFile, null, flProtect, hi, cast(uint)size, null); + if (hFileMap == null) // mapping failed + goto err1; + + if (size == 0) + { + uint sizehi; + uint sizelow = GetFileSize(hFile,&sizehi); + 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); + if (!p) goto err1; + data = p[0 .. initial_map]; + + debug (MMFILE) printf("MmFile.this(): p = %p, size = %d\n", p, size); + return; + + err1: + if (hFileMap != null) + CloseHandle(hFileMap); + hFileMap = null; + + if (hFile != INVALID_HANDLE_VALUE) + CloseHandle(hFile); + hFile = INVALID_HANDLE_VALUE; + + errNo(); + } + else version (unix_mm) + { + char* namez = toStringz(filename); + 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 = 0660; + break; + + case Mode.ReadWrite: + flags = MAP_SHARED; + prot = PROT_READ | PROT_WRITE; + oflag = O_CREAT | O_RDWR; + fmode = 0660; + break; + + case Mode.ReadCopyOnWrite: + flags = MAP_PRIVATE; + prot = PROT_READ | PROT_WRITE; + oflag = O_RDWR; + fmode = 0; + break; + + default: + assert(0); + } + + if (filename.length) + { + struct_stat statbuf; + + fd = unix.open(namez, oflag, fmode); + if (fd == -1) + { + // printf("\topen error, errno = %d\n",getErrno()); + errNo(); + } + + if (unix.fstat(fd, &statbuf)) + { + //printf("\tfstat error, errno = %d\n",getErrno()); + unix.close(fd); + errNo(); + } + + if (prot & PROT_WRITE && size > statbuf.st_size) + { + // Need to make the file size bytes big + unix.lseek(fd, cast(off_t)(size - 1), SEEK_SET); + char c = 0; + unix.write(fd, &c, 1); + } + else if (prot & PROT_READ && size == 0) + size = statbuf.st_size; + } + else + { + fd = -1; + flags |= MAP_ANONYMOUS; + } + 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) + unix.close(fd); + errNo(); + } + + data = p[0 .. initial_map]; + } + else static if (! Have_MMFile) + { + throw new FileException("This system does support memory mapped files"); + } + else + { + static assert(0); + } + } + + /** + * Flushes pending output and closes the memory mapped file. + */ + ~this() + { + debug (MMFILE) printf("MmFile.~this()\n"); + unmap(); + version (Win32) + { + if (hFileMap != null && CloseHandle(hFileMap) != TRUE) + errNo(); + hFileMap = null; + + if (hFile != INVALID_HANDLE_VALUE && CloseHandle(hFile) != TRUE) + errNo(); + hFile = INVALID_HANDLE_VALUE; + } + else version (unix_mm) + { + if (fd != -1 && unix.close(fd) == -1) + errNo(); + fd = -1; + } + else static if (! Have_MMFile) + { + } + else + { + static assert(0); + } + data = null; + } + + /* Flush any pending output. + */ + void flush() + { + debug (MMFILE) printf("MmFile.flush()\n"); + version (Win32) + { + FlushViewOfFile(data.ptr, data.length); + } + else version (unix_mm) + { + int i; + + i = msync(cast(void*)data, data.length, MS_SYNC); // sys/mman.h + if (i != 0) + errNo(); + } + else static if (! Have_MMFile) + { + } + else + { + static assert(0); + } + } + + /** + * Gives size in bytes of the memory mapped file. + */ + ulong length() + { + 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) { + /* Note that under Windows 95, UnmapViewOfFile() seems to return + * random values, not TRUE or FALSE. + */ + if (data && UnmapViewOfFile(data.ptr) == FALSE && + (dwVersion & 0x80000000) == 0) + errNo(); + } else version (unix_mm) { + if (data && munmap(cast(void*)data, data.length) != 0) + errNo(); + } + 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); + if (!p) errNo(); + } else version (unix_mm) { + p = mmap(address, len, prot, flags, fd, cast(off_t)start); + if (p == MAP_FAILED) errNo(); + } + 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: + char[] filename; + void[] data; + ulong start; + size_t window; + ulong size; + Mode mMode; + void* address; + + version (Win32) + { + HANDLE hFile = INVALID_HANDLE_VALUE; + HANDLE hFileMap = null; + uint dwDesiredAccess; + } + else version (unix_mm) + { + int fd; + int prot; + int flags; + int fmode; + } + else static if (! Have_MMFile) + { + } + else + { + static assert(0); + } + + // Report error, where errno gives the error number + void errNo() + { + version (Win32) + { + throw new FileException(filename, GetLastError()); + } + else version (Unix) + { + throw new FileException(filename, getErrno()); + } + else static if (! Have_MMFile) + { + throw new FileException(filename, "MMFile unsupported"); + } + else + { + static assert(0); + } + } +} + +unittest { + static if (Have_MMFile) + { + const size_t K = 1024; + size_t win = 64*K; // assume the page size is 64K + version(Win32) { + /+ these aren't defined in std.c.windows.windows so let's use the default + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + win = sysinfo.dwAllocationGranularity; + +/ + } else version (Unix) { + // getpagesize() is not defined in the unix D headers so use the guess + } + MmFile mf = new MmFile("testing.txt",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[length-1] == 0 ); + mf[100*K-1] = cast(ubyte)'b'; + data2 = cast(ubyte[])mf[21*K .. 100*K]; + assert( data2.length == 79*K ); + assert( data2[length-1] == 'b' ); + delete mf; + std.file.remove("testing.txt"); + } +}