blob: 453e2ec74e2c5a4d89a74a7e5bcfb0b7a7f9626a [file] [log] [blame]
// 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));
}