# # # Nim's Runtime Library # (c) Copyright 2015 Nim 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. ## ## It also provides some fast iterators over lines in text files (or ## other "line-like", variable length, delimited records). const nimUseFallBack = defined(nintendoswitch) or defined(nimMemfileFallback) when defined(windows): import std/winlean when defined(nimPreviewSlimSystem): import std/widestrs elif defined(posix): when not nimUseFallBack: import std/posix else: {.error: "the memfiles module is not supported on your operating system!".} import std/streams import std/oserrors when defined(nimPreviewSlimSystem): import std/[syncio, assertions] elif nimUseFallBack: import std/syncio from system/ansi_c import c_memchr proc newEIO(msg: string): ref IOError = result = (ref IOError)(msg: msg) when not nimUseFallBack: proc setFileSize(fh: FileHandle, newFileSize = -1, oldSize = -1): OSErrorCode = ## Set the size of open file pointed to by `fh` to `newFileSize` if != -1, ## allocating | freeing space from the file system. This routine returns the ## last OSErrorCode found rather than raising to support old rollback/clean-up ## code style. [ Should maybe move to std/osfiles. ] result = OSErrorCode(0) if newFileSize < 0 or newFileSize == oldSize: return result when defined(windows): var sizeHigh = int32(newFileSize shr 32) let sizeLow = int32(newFileSize and 0xffffffff) let status = setFilePointer(Handle fh, sizeLow, addr(sizeHigh), FILE_BEGIN) let lastErr = osLastError() if (status == INVALID_SET_FILE_POINTER and lastErr.int32 != NO_ERROR) or setEndOfFile(Handle fh) == 0: result = lastErr else: if newFileSize > oldSize: # grow the file var e: cint = cint(0) # posix_fallocate truncates up when needed. when declared(posix_fallocate): while (e = posix_fallocate(fh, 0, newFileSize); e == EINTR): discard if e == EINVAL or e == EOPNOTSUPP or e == ENOSYS: # fallback arguable; Most portable BUT allows SEGV if ftruncate(fh, newFileSize) == -1: result = osLastError() else: discard elif e != 0: result = osLastError() else: # shrink the file if ftruncate(fh.cint, newFileSize) == -1: result = osLastError() type MemFile* = object ## 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*: Handle ## **Caution**: Windows specific public field to allow ## even more low level trickery. mapHandle*: Handle ## **Caution**: Windows specific public field. wasOpened*: bool ## **Caution**: Windows specific public field. else: handle*: cint ## **Caution**: Posix specific public field. flags: cint ## **Caution**: Platform specific private field. when nimUseFallBack: backing: string path: string readonly: bool allowRemap: bool when nimUseFallBack: proc fallbackMappedSize(backingLen, mappedSize, offset: int): int = if mappedSize < -1: raise newEIO("mappedSize cannot be less than -1") if offset < 0 or offset > backingLen: raise newEIO("offset out of bounds") if mappedSize == -1: result = backingLen - offset else: result = min(mappedSize, backingLen - offset) proc setFallbackView(m: var MemFile, mappedSize, offset: int) = m.size = fallbackMappedSize(m.backing.len, mappedSize, offset) if m.size > 0: m.mem = cast[pointer](addr m.backing[offset]) else: m.mem = nil proc openFallbackMemFile(filename: string, mode: FileMode, mappedSize, offset, newFileSize: int, allowRemap: bool): MemFile = result = MemFile( handle: -1, flags: 0, path: filename, readonly: mode == fmRead, allowRemap: allowRemap ) if newFileSize != -1: result.backing = newString(newFileSize) else: result.backing = readFile(filename) setFallbackView(result, mappedSize, offset) proc mapMemFallback(m: var MemFile, mode: FileMode, mappedSize, offset: int): pointer = if not m.allowRemap: raise newException(IOError, "Cannot remap MemFile opened with allowRemap=false") if mode != fmRead and m.readonly: raise newEIO("cannot write to read-only mapping") let size = fallbackMappedSize(m.backing.len, mappedSize, offset) if size > 0: result = cast[pointer](addr m.backing[offset]) else: result = nil proc flushFallback(m: var MemFile) = if m.readonly or m.path.len == 0: return writeFile(m.path, m.backing) proc resizeFallback(m: var MemFile, newFileSize: int) = if m.readonly: raise newException(IOError, "Cannot resize read-only MemFile") if not m.allowRemap: raise newException(IOError, "Cannot resize MemFile opened with allowRemap=false") if m.size != m.backing.len: raise newException(IOError, "Cannot resize partial MemFile") let oldLen = m.backing.len m.backing.setLen(newFileSize) for i in oldLen ..< newFileSize: m.backing[i] = '\0' setFallbackView(m, newFileSize, 0) proc closeFallback(m: var MemFile) = if not m.readonly: flushFallback(m) m.mem = nil m.size = 0 m.handle = -1 m.flags = 0 m.backing = "" m.path = "" m.readonly = false m.allowRemap = false proc mapMem*(m: var MemFile, mode: FileMode = fmRead, mappedSize = -1, offset = 0, mapFlags = cint(-1)): pointer = ## returns a pointer to a mapped portion of MemFile `m` ## ## `mappedSize` of `-1` maps to the whole file, and ## `offset` must be multiples of the PAGE SIZE of your OS if mode == fmAppend: raise newEIO("The append mode is not supported.") let readonly = mode == fmRead when defined(windows): result = mapViewOfFileEx( m.mapHandle, if readonly: FILE_MAP_READ else: FILE_MAP_READ or FILE_MAP_WRITE, int32(offset shr 32), int32(offset and 0xffffffff), WinSizeT(if mappedSize == -1: 0 else: mappedSize), nil) if result == nil: raiseOSError(osLastError()) elif nimUseFallBack: result = mapMemFallback(m, mode, mappedSize, offset) else: assert mappedSize > 0 m.flags = if mapFlags == cint(-1): MAP_SHARED else: mapFlags #Ensure exactly one of MAP_PRIVATE cr MAP_SHARED is set if int(m.flags and MAP_PRIVATE) == 0: m.flags = m.flags or MAP_SHARED result = mmap( nil, mappedSize, if readonly: PROT_READ else: PROT_READ or PROT_WRITE, m.flags, m.handle, offset) if result == cast[pointer](MAP_FAILED): raiseOSError(osLastError()) proc unmapMem*(f: var MemFile, p: pointer, size: int) = ## unmaps the memory region `(p,
allocated.
if (let e = setFileSize(f.fHandle.FileHandle, newFileSize);
e != 0.OSErrorCode): raiseOSError(e)
f.mapHandle = createFileMappingW(f.fHandle, nil, PAGE_READWRITE, 0,0,nil)
if f.mapHandle == 0: # Re-do map
raiseOSError(osLastError())
let m = mapViewOfFileEx(f.mapHandle, FILE_MAP_READ or FILE_MAP_WRITE,
0, 0, WinSizeT(newFileSize), nil)
if m != nil:
f.mem = m
f.size = newFileSize
else:
raiseOSError(osLastError())
elif defined(posix):
if f.handle == -1:
raise newException(IOError,
"Cannot resize MemFile opened with allowRemap=false")
if newFileSize != f.size:
let e = setFileSize(f.handle.FileHandle, newFileSize, f.size)
if e != 0.OSErrorCode: raiseOSError(e)
when defined(linux): #Maybe NetBSD, too?
# On Linux this can be over 100 times faster than a munmap,mmap cycle.
proc mremap(old: pointer; oldSize, newSize: csize_t; flags: cint):
pointer {.importc: "mremap", header: "