# # # 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: var f = allocWideCString(filename) result.fHandle = callCreateFile(CreateFileW, f) dealloc f 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()