phobos/std/mmfile.d
Roman Kashitsyn 6f130cf4e2 std.mmfile: correct the documented type of thrown exceptions
MmFile.this claims that it throws std.file.FileException.
However, that never happens in practice:
  * On POSIX, ErrnoException is thrown (due to use of errnoEnforce).
  * On Windows, WindowsException is thrown (due to use of wenforce).

This change updates the documentation to reflect real types of
exceptions thrown by the constructors.

Another alternative is to change the MmFile itself to throw
FileException as it claims to do, but this will break backward
compatibility with existing programs.
2020-12-31 09:25:30 +01:00

728 lines
20 KiB
D

// Written in the D programming language.
/**
* Read and write memory mapped files.
* Copyright: Copyright The D Language Foundation 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 The D Language Foundation 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.winbase;
import core.sys.windows.winnt;
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:
* - On POSIX, $(REF ErrnoException, std, exception).
* - On Windows, $(REF WindowsException, std, windows, syserror).
*/
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;
final 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;
}
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:
* - On POSIX, $(REF ErrnoException, std, exception).
* - On Windows, $(REF WindowsException, std, windows, syserror).
*/
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;
final 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;
}
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;
final 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;
}
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;
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;
}
/**
* Forwards `length`.
*/
alias opDollar = length;
/**
* 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 (Posix)
{
import core.sys.posix.unistd;
win = cast(size_t) sysconf(_SC_PAGESIZE);
}
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 // https://issues.dlang.org/show_bug.cgi?id=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);
}
// https://issues.dlang.org/show_bug.cgi?id=14994
// https://issues.dlang.org/show_bug.cgi?id=14995
@system unittest
{
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));
}
@system unittest
{
MmFile shar = new MmFile(null, MmFile.Mode.readWrite, 10, null, 0);
void[] output = shar[0 .. $];
}