mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-01 02:42:05 +00:00
190 lines
5.5 KiB
Nim
190 lines
5.5 KiB
Nim
#
|
|
#
|
|
# Nimrod's Runtime Library
|
|
# (c) Copyright 2012 Nimrod Contributors
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
## :Authors: Zahary Karadjov, Andreas Rumpf
|
|
##
|
|
## This module provides support for `memory mapped files`:idx:
|
|
## (Posix's `mmap`:idx:) on the different operating systems.
|
|
|
|
when defined(windows):
|
|
import winlean
|
|
elif defined(posix):
|
|
import posix
|
|
else:
|
|
{.error: "the memfiles module is not supported on your operating system!".}
|
|
|
|
import os
|
|
|
|
type
|
|
TMemFile* = object {.pure.} ## represents a memory mapped file
|
|
mem*: pointer ## a pointer to the memory mapped file. The pointer
|
|
## can be used directly to change the contents of the
|
|
## file, if it was opened with write access.
|
|
size*: int ## size of the memory mapped file
|
|
|
|
when defined(windows):
|
|
fHandle: int
|
|
mapHandle: int
|
|
else:
|
|
handle: cint
|
|
|
|
proc open*(filename: string, mode: TFileMode = fmRead,
|
|
mappedSize = -1, offset = 0, newFileSize = -1): TMemFile =
|
|
## opens a memory mapped file. If this fails, ``EOS`` is raised.
|
|
## `newFileSize` can only be set if the file is not opened with ``fmRead``
|
|
## access. `mappedSize` and `offset` can be used to map only a slice of
|
|
## the file.
|
|
|
|
# The file can be resized only when write mode is used:
|
|
assert newFileSize == -1 or mode != fmRead
|
|
var readonly = mode == fmRead
|
|
|
|
template rollback =
|
|
result.mem = nil
|
|
result.size = 0
|
|
|
|
when defined(windows):
|
|
template fail(msg: expr) =
|
|
rollback()
|
|
if result.fHandle != 0: discard CloseHandle(result.fHandle)
|
|
if result.mapHandle != 0: discard CloseHandle(result.mapHandle)
|
|
OSError()
|
|
# return false
|
|
#raise newException(EIO, msg)
|
|
|
|
template callCreateFile(winApiProc, filename: expr): expr =
|
|
winApiProc(
|
|
filename,
|
|
if readonly: GENERIC_READ else: GENERIC_ALL,
|
|
FILE_SHARE_READ,
|
|
nil,
|
|
if newFileSize != -1: CREATE_ALWAYS else: OPEN_EXISTING,
|
|
if readonly: FILE_ATTRIBUTE_READONLY else: FILE_ATTRIBUTE_TEMPORARY,
|
|
0)
|
|
|
|
when useWinUnicode:
|
|
result.fHandle = callCreateFile(CreateFileW, newWideCString(filename))
|
|
else:
|
|
result.fHandle = callCreateFile(CreateFileA, filename)
|
|
|
|
if result.fHandle == INVALID_HANDLE_VALUE:
|
|
fail "error opening file"
|
|
|
|
if newFileSize != -1:
|
|
var
|
|
sizeHigh = int32(newFileSize shr 32)
|
|
sizeLow = int32(newFileSize and 0xffffffff)
|
|
|
|
var status = SetFilePointer(result.fHandle, sizeLow, addr(sizeHigh),
|
|
FILE_BEGIN)
|
|
if (status == INVALID_SET_FILE_POINTER and GetLastError() != NO_ERROR) or
|
|
(SetEndOfFile(result.fHandle) == 0):
|
|
fail "error setting file size"
|
|
|
|
# since the strings are always 'nil', we simply always call
|
|
# CreateFileMappingW which should be slightly faster anyway:
|
|
result.mapHandle = CreateFileMappingW(
|
|
result.fHandle, nil,
|
|
if readonly: PAGE_READONLY else: PAGE_READWRITE,
|
|
0, 0, nil)
|
|
|
|
if result.mapHandle == 0:
|
|
fail "error creating mapping"
|
|
|
|
result.mem = MapViewOfFileEx(
|
|
result.mapHandle,
|
|
if readonly: FILE_MAP_READ else: FILE_MAP_WRITE,
|
|
int32(offset shr 32),
|
|
int32(offset and 0xffffffff),
|
|
if mappedSize == -1: 0 else: mappedSize,
|
|
nil)
|
|
|
|
if result.mem == nil:
|
|
fail "error mapping view"
|
|
|
|
var hi, low: int32
|
|
low = GetFileSize(result.fHandle, addr(hi))
|
|
if low == INVALID_FILE_SIZE:
|
|
fail "error getting file size"
|
|
else:
|
|
var fileSize = (int64(hi) shr 32) or low
|
|
if mappedSize != -1: result.size = min(fileSize, mappedSize).int
|
|
else: result.size = fileSize.int
|
|
|
|
else:
|
|
template fail(msg: expr) =
|
|
rollback()
|
|
if result.handle != 0: discard close(result.handle)
|
|
OSError()
|
|
|
|
var flags = if readonly: O_RDONLY else: O_RDWR
|
|
|
|
if newFileSize != -1:
|
|
flags = flags or O_CREAT or O_TRUNC
|
|
|
|
result.handle = open(filename, flags)
|
|
if result.handle == -1:
|
|
# XXX: errno is supposed to be set here
|
|
# Is there an exception that wraps it?
|
|
fail "error opening file"
|
|
|
|
if newFileSize != -1:
|
|
if ftruncate(result.handle, newFileSize) == -1:
|
|
fail "error setting file size"
|
|
|
|
if mappedSize != -1:
|
|
result.size = mappedSize
|
|
else:
|
|
var stat: Tstat
|
|
if fstat(result.handle, stat) != -1:
|
|
# XXX: Hmm, this could be unsafe
|
|
# Why is mmap taking int anyway?
|
|
result.size = int(stat.st_size)
|
|
else:
|
|
fail "error getting file size"
|
|
|
|
result.mem = mmap(
|
|
nil,
|
|
result.size,
|
|
if readonly: PROT_READ else: PROT_READ or PROT_WRITE,
|
|
if readonly: MAP_PRIVATE else: MAP_SHARED,
|
|
result.handle,
|
|
offset)
|
|
|
|
if result.mem == cast[pointer](MAP_FAILED):
|
|
fail "file mapping failed"
|
|
|
|
proc close*(f: var TMemFile) =
|
|
## closes the memory mapped file `f`. All changes are written back to the
|
|
## file system, if `f` was opened with write access.
|
|
|
|
var error = false
|
|
|
|
when defined(windows):
|
|
if f.fHandle != INVALID_HANDLE_VALUE:
|
|
error = UnmapViewOfFile(f.mem) == 0
|
|
error = (CloseHandle(f.mapHandle) == 0) or error
|
|
error = (CloseHandle(f.fHandle) == 0) or error
|
|
else:
|
|
if f.handle != 0:
|
|
error = munmap(f.mem, f.size) != 0
|
|
error = (close(f.handle) != 0) or error
|
|
|
|
f.size = 0
|
|
f.mem = nil
|
|
|
|
when defined(windows):
|
|
f.fHandle = 0
|
|
f.mapHandle = 0
|
|
else:
|
|
f.handle = 0
|
|
|
|
if error: OSError()
|
|
|