diff options
Diffstat (limited to 'lib/pure/memfiles.nim')
-rw-r--r-- | lib/pure/memfiles.nim | 563 |
1 files changed, 470 insertions, 93 deletions
diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index 3f56d2465..8eec551c4 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -1,7 +1,7 @@ # # -# Nimrod's Runtime Library -# (c) Copyright 2012 Nimrod Contributors +# Nim's Runtime Library +# (c) Copyright 2015 Nim Contributors # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -11,37 +11,167 @@ ## ## 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). when defined(windows): - import winlean + import std/winlean + when defined(nimPreviewSlimSystem): + import std/widestrs elif defined(posix): - import posix + import std/posix else: {.error: "the memfiles module is not supported on your operating system!".} -import os +import std/streams +import std/oserrors + +when defined(nimPreviewSlimSystem): + import std/[syncio, assertions] + + +proc newEIO(msg: string): ref IOError = + new(result) + result.msg = msg + +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. ] + if newFileSize < 0 or newFileSize == oldSize: + return + when defined(windows): + var sizeHigh = int32(newFileSize shr 32) + let sizeLow = int32(newFileSize and 0xffffffff) + let status = setFilePointer(fh, sizeLow, addr(sizeHigh), FILE_BEGIN) + let lastErr = osLastError() + if (status == INVALID_SET_FILE_POINTER and lastErr.int32 != NO_ERROR) or + setEndOfFile(fh) == 0: + result = lastErr + else: + if newFileSize > oldSize: # grow the file + var e: cint # posix_fallocate truncates up when needed. + when declared(posix_fallocate): + while (e = posix_fallocate(fh, 0, newFileSize); e == EINTR): + discard + if e in [EINVAL, EOPNOTSUPP] and ftruncate(fh, newFileSize) == -1: + result = osLastError() # fallback arguable; Most portable BUT allows SEGV + elif e != 0: + result = osLastError() + else: # shrink the file + if ftruncate(fh.cint, newFileSize) == -1: + result = osLastError() 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 + 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: int - mapHandle: int + 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 + handle*: cint ## **Caution**: Posix specific public field. + flags: cint ## **Caution**: Platform specific private field. -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. +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.") + + var 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()) + 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, <p+size)` of the mapped file `f`. + ## All changes are written back to the file system, if `f` was opened + ## with write access. + ## + ## `size` must be of exactly the size that was requested + ## via `mapMem`. + when defined(windows): + if unmapViewOfFile(p) == 0: raiseOSError(osLastError()) + else: + if munmap(p, size) != 0: raiseOSError(osLastError()) + + +proc open*(filename: string, mode: FileMode = fmRead, + mappedSize = -1, offset = 0, newFileSize = -1, + allowRemap = false, mapFlags = cint(-1)): MemFile = + ## opens a memory mapped file. If this fails, `OSError` is raised. + ## + ## `newFileSize` can only be set if the file does not exist and is opened + ## with write access (e.g., with fmReadWrite). + ## + ##`mappedSize` and `offset` + ## can be used to map only a slice of the file. + ## + ## `offset` must be multiples of the PAGE SIZE of your OS + ## (usually 4K or 8K but is unique to your OS) + ## + ## `allowRemap` only needs to be true if you want to call `mapMem` on + ## the resulting MemFile; else file handles are not kept open. + ## + ## `mapFlags` allows callers to override default choices for memory mapping + ## flags with a bitwise mask of a variety of likely platform-specific flags + ## which may be ignored or even cause `open` to fail if misspecified. + ## + ## Example: + ## + ## ```nim + ## var + ## mm, mm_full, mm_half: MemFile + ## + ## mm = memfiles.open("/tmp/test.mmap", mode = fmWrite, newFileSize = 1024) # Create a new file + ## mm.close() + ## + ## # Read the whole file, would fail if newFileSize was set + ## mm_full = memfiles.open("/tmp/test.mmap", mode = fmReadWrite, mappedSize = -1) + ## + ## # Read the first 512 bytes + ## mm_half = memfiles.open("/tmp/test.mmap", mode = fmReadWrite, mappedSize = 512) + ## ``` # The file can be resized only when write mode is used: + if mode == fmAppend: + raise newEIO("The append mode is not supported.") + assert newFileSize == -1 or mode != fmRead var readonly = mode == fmRead @@ -50,132 +180,213 @@ proc open*(filename: string, mode: TFileMode = fmRead, result.size = 0 when defined(windows): - template fail(msg: expr) = + let desiredAccess = GENERIC_READ + let shareMode = FILE_SHARE_READ + let flags = FILE_FLAG_RANDOM_ACCESS + + template fail(errCode: OSErrorCode, msg: untyped) = rollback() - if result.fHandle != 0: discard CloseHandle(result.fHandle) - if result.mapHandle != 0: discard CloseHandle(result.mapHandle) - OSError() + if result.fHandle != 0: discard closeHandle(result.fHandle) + if result.mapHandle != 0: discard closeHandle(result.mapHandle) + raiseOSError(errCode) # return false - #raise newException(EIO, msg) + #raise newException(IOError, msg) - template callCreateFile(winApiProc, filename: expr): expr = + template callCreateFile(winApiProc, filename): untyped = winApiProc( filename, - if readonly: GENERIC_READ else: GENERIC_ALL, - FILE_SHARE_READ, + # GENERIC_ALL != (GENERIC_READ or GENERIC_WRITE) + if readonly: desiredAccess else: desiredAccess or GENERIC_WRITE, + if readonly: shareMode else: shareMode or FILE_SHARE_WRITE, nil, if newFileSize != -1: CREATE_ALWAYS else: OPEN_EXISTING, - if readonly: FILE_ATTRIBUTE_READONLY else: FILE_ATTRIBUTE_TEMPORARY, + if readonly: FILE_ATTRIBUTE_READONLY or flags + else: FILE_ATTRIBUTE_NORMAL or flags, 0) - when useWinUnicode: - var f = allocWideCString(filename) - result.fHandle = callCreateFile(CreateFileW, f) - dealloc f - else: - result.fHandle = callCreateFile(CreateFileA, filename) + result.fHandle = callCreateFile(createFileW, newWideCString(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) + fail(osLastError(), "error opening file") - 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" + if (let e = setFileSize(result.fHandle.FileHandle, newFileSize); + e != 0.OSErrorCode): fail(e, "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.mapHandle = createFileMappingW( result.fHandle, nil, if readonly: PAGE_READONLY else: PAGE_READWRITE, 0, 0, nil) if result.mapHandle == 0: - fail "error creating mapping" + fail(osLastError(), "error creating mapping") - result.mem = MapViewOfFileEx( + result.mem = mapViewOfFileEx( result.mapHandle, - if readonly: FILE_MAP_READ else: FILE_MAP_WRITE, + if readonly: FILE_MAP_READ else: FILE_MAP_READ or 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" + fail(osLastError(), "error mapping view") var hi, low: int32 - low = GetFileSize(result.fHandle, addr(hi)) + low = getFileSize(result.fHandle, addr(hi)) if low == INVALID_FILE_SIZE: - fail "error getting file size" + fail(osLastError(), "error getting file size") else: - var fileSize = (int64(hi) shr 32) or low + var fileSize = (int64(hi) shl 32) or int64(uint32(low)) if mappedSize != -1: result.size = min(fileSize, mappedSize).int else: result.size = fileSize.int + result.wasOpened = true + if not allowRemap and result.fHandle != INVALID_HANDLE_VALUE: + if closeHandle(result.fHandle) != 0: + result.fHandle = INVALID_HANDLE_VALUE + else: - template fail(msg: expr) = + template fail(errCode: OSErrorCode, msg: string) = rollback() - if result.handle != 0: discard close(result.handle) - OSError() - - var flags = if readonly: O_RDONLY else: O_RDWR + if result.handle != -1: discard close(result.handle) + raiseOSError(errCode) + + var flags = (if readonly: O_RDONLY else: O_RDWR) or O_CLOEXEC if newFileSize != -1: flags = flags or O_CREAT or O_TRUNC + var permissionsMode = S_IRUSR or S_IWUSR + result.handle = open(filename, flags, permissionsMode) + if result.handle != -1: + if (let e = setFileSize(result.handle.FileHandle, newFileSize); + e != 0.OSErrorCode): fail(e, "error setting file size") + else: + result.handle = open(filename, flags) - 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" + fail(osLastError(), "error opening file") - if mappedSize != -1: - result.size = mappedSize - else: - var stat: Tstat + if mappedSize != -1: #XXX Logic here differs from `when windows` branch .. + result.size = mappedSize #.. which always fstats&Uses min(mappedSize, st). + else: # if newFileSize!=-1: result.size=newFileSize # if trust setFileSize + var stat: Stat #^^.. BUT some FSes (eg. Linux HugeTLBfs) round to 2MiB. if fstat(result.handle, stat) != -1: - # XXX: Hmm, this could be unsafe - # Why is mmap taking int anyway? - result.size = int(stat.st_size) + result.size = stat.st_size.int # int may be 32-bit-unsafe for 2..<4 GiB else: - fail "error getting file size" + fail(osLastError(), "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) + result.flags = if mapFlags == cint(-1): MAP_SHARED else: mapFlags + # Ensure exactly one of MAP_PRIVATE cr MAP_SHARED is set + if int(result.flags and MAP_PRIVATE) == 0: + result.flags = result.flags or MAP_SHARED + let pr = if readonly: PROT_READ else: PROT_READ or PROT_WRITE + result.mem = mmap(nil, result.size, pr, result.flags, result.handle, offset) if result.mem == cast[pointer](MAP_FAILED): - fail "file mapping failed" + fail(osLastError(), "file mapping failed") + + if not allowRemap and result.handle != -1: + if close(result.handle) == 0: + result.handle = -1 -proc close*(f: var TMemFile) = +proc flush*(f: var MemFile; attempts: Natural = 3) = + ## Flushes `f`'s buffer for the number of attempts equal to `attempts`. + ## If were errors an exception `OSError` will be raised. + var res = false + var lastErr: OSErrorCode + when defined(windows): + for i in 1..attempts: + res = flushViewOfFile(f.mem, 0) != 0 + if res: + break + lastErr = osLastError() + if lastErr != ERROR_LOCK_VIOLATION.OSErrorCode: + raiseOSError(lastErr) + else: + for i in 1..attempts: + res = msync(f.mem, f.size, MS_SYNC or MS_INVALIDATE) == 0 + if res: + break + lastErr = osLastError() + if lastErr != EBUSY.OSErrorCode: + raiseOSError(lastErr, "error flushing mapping") + +proc resize*(f: var MemFile, newFileSize: int) {.raises: [IOError, OSError].} = + ## Resize & re-map the file underlying an `allowRemap MemFile`. If the OS/FS + ## supports it, file space is reserved to ensure room for new virtual pages. + ## Caller should wait often enough for `flush` to finish to limit use of + ## system RAM for write buffering, perhaps just prior to this call. + ## **Note**: this assumes the entire file is mapped read-write at offset 0. + ## Also, the value of `.mem` will probably change. + if newFileSize < 1: # Q: include system/bitmasks & use PageSize ? + raise newException(IOError, "Cannot resize MemFile to < 1 byte") + when defined(windows): + if not f.wasOpened: + raise newException(IOError, "Cannot resize unopened MemFile") + if f.fHandle == INVALID_HANDLE_VALUE: + raise newException(IOError, + "Cannot resize MemFile opened with allowRemap=false") + if unmapViewOfFile(f.mem) == 0 or closeHandle(f.mapHandle) == 0: # Un-do map + raiseOSError(osLastError()) + if newFileSize != f.size: # Seek to size & `setEndOfFile` => 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()) + if (let m = mapViewOfFileEx(f.mapHandle, FILE_MAP_READ or FILE_MAP_WRITE, + 0, 0, WinSizeT(newFileSize), nil); 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: + if (let e = setFileSize(f.handle.FileHandle, newFileSize, f.size); + 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: "<sys/mman.h>".} + let newAddr = mremap(f.mem, csize_t(f.size), csize_t(newFileSize), 1.cint) + if newAddr == cast[pointer](MAP_FAILED): + raiseOSError(osLastError()) + else: + if munmap(f.mem, f.size) != 0: + raiseOSError(osLastError()) + let newAddr = mmap(nil, newFileSize, PROT_READ or PROT_WRITE, + f.flags, f.handle, 0) + if newAddr == cast[pointer](MAP_FAILED): + raiseOSError(osLastError()) + f.mem = newAddr + f.size = newFileSize + +proc close*(f: var MemFile) = ## 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 + var lastErr: OSErrorCode 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 + if f.wasOpened: + error = unmapViewOfFile(f.mem) == 0 + if not error: + error = closeHandle(f.mapHandle) == 0 + if not error and f.fHandle != INVALID_HANDLE_VALUE: + discard closeHandle(f.fHandle) + f.fHandle = INVALID_HANDLE_VALUE + if error: + lastErr = osLastError() else: - if f.handle != 0: - error = munmap(f.mem, f.size) != 0 + error = munmap(f.mem, f.size) != 0 + lastErr = osLastError() + if f.handle != -1: error = (close(f.handle) != 0) or error f.size = 0 @@ -184,8 +395,174 @@ proc close*(f: var TMemFile) = when defined(windows): f.fHandle = 0 f.mapHandle = 0 + f.wasOpened = false else: - f.handle = 0 - - if error: OSError() + f.handle = -1 + + if error: raiseOSError(lastErr) + +type MemSlice* = object ## represent slice of a MemFile for iteration over delimited lines/records + data*: pointer + size*: int + +proc `==`*(x, y: MemSlice): bool = + ## Compare a pair of MemSlice for strict equality. + result = (x.size == y.size and equalMem(x.data, y.data, x.size)) + +proc `$`*(ms: MemSlice): string {.inline.} = + ## Return a Nim string built from a MemSlice. + result.setLen(ms.size) + copyMem(result.cstring, ms.data, ms.size) + +iterator memSlices*(mfile: MemFile, delim = '\l', eat = '\r'): MemSlice {.inline.} = + ## Iterates over \[optional `eat`] `delim`-delimited slices in MemFile `mfile`. + ## + ## Default parameters parse lines ending in either Unix(\\l) or Windows(\\r\\l) + ## style on on a line-by-line basis. I.e., not every line needs the same ending. + ## Unlike readLine(File) & lines(File), archaic MacOS9 \\r-delimited lines + ## are not supported as a third option for each line. Such archaic MacOS9 + ## files can be handled by passing delim='\\r', eat='\\0', though. + ## + ## Delimiters are not part of the returned slice. A final, unterminated line + ## or record is returned just like any other. + ## + ## Non-default delimiters can be passed to allow iteration over other sorts + ## of "line-like" variable length records. Pass eat='\\0' to be strictly + ## `delim`-delimited. (Eating an optional prefix equal to '\\0' is not + ## supported.) + ## + ## This zero copy, memchr-limited interface is probably the fastest way to + ## iterate over line-like records in a file. However, returned (data,size) + ## objects are not Nim strings, bounds checked Nim arrays, or even terminated + ## C strings. So, care is required to access the data (e.g., think C mem* + ## functions, not str* functions). + ## + ## Example: + ## ```nim + ## var count = 0 + ## for slice in memSlices(memfiles.open("foo")): + ## if slice.size > 0 and cast[cstring](slice.data)[0] != '#': + ## inc(count) + ## echo count + ## ``` + + proc c_memchr(cstr: pointer, c: char, n: csize_t): pointer {. + importc: "memchr", header: "<string.h>".} + proc `-!`(p, q: pointer): int {.inline.} = return cast[int](p) -% cast[int](q) + var ms: MemSlice + var ending: pointer + ms.data = mfile.mem + var remaining = mfile.size + while remaining > 0: + ending = c_memchr(ms.data, delim, csize_t(remaining)) + if ending == nil: # unterminated final slice + ms.size = remaining # Weird case..check eat? + yield ms + break + ms.size = ending -! ms.data # delim is NOT included + if eat != '\0' and ms.size > 0 and cast[cstring](ms.data)[ms.size - 1] == eat: + dec(ms.size) # trim pre-delim char + yield ms + ms.data = cast[pointer](cast[int](ending) +% 1) # skip delim + remaining = mfile.size - (ms.data -! mfile.mem) + +iterator lines*(mfile: MemFile, buf: var string, delim = '\l', + eat = '\r'): string {.inline.} = + ## Replace contents of passed buffer with each new line, like + ## `readLine(File) <syncio.html#readLine,File,string>`_. + ## `delim`, `eat`, and delimiting logic is exactly as for `memSlices + ## <#memSlices.i,MemFile,char,char>`_, but Nim strings are returned. + ## + ## Example: + ## ```nim + ## var buffer: string = "" + ## for line in lines(memfiles.open("foo"), buffer): + ## echo line + ## ``` + + for ms in memSlices(mfile, delim, eat): + setLen(buf, ms.size) + if ms.size > 0: + copyMem(addr buf[0], ms.data, ms.size) + yield buf + +iterator lines*(mfile: MemFile, delim = '\l', eat = '\r'): string {.inline.} = + ## Return each line in a file as a Nim string, like + ## `lines(File) <syncio.html#lines.i,File>`_. + ## `delim`, `eat`, and delimiting logic is exactly as for `memSlices + ## <#memSlices.i,MemFile,char,char>`_, but Nim strings are returned. + ## + ## Example: + ## ```nim + ## for line in lines(memfiles.open("foo")): + ## echo line + ## ``` + + var buf = newStringOfCap(80) + for line in lines(mfile, buf, delim, eat): + yield buf + +type + MemMapFileStream* = ref MemMapFileStreamObj ## a stream that encapsulates a `MemFile` + MemMapFileStreamObj* = object of Stream + mf: MemFile + mode: FileMode + pos: ByteAddress + +proc mmsClose(s: Stream) = + MemMapFileStream(s).pos = -1 + close(MemMapFileStream(s).mf) + +proc mmsFlush(s: Stream) = flush(MemMapFileStream(s).mf) + +proc mmsAtEnd(s: Stream): bool = (MemMapFileStream(s).pos >= MemMapFileStream(s).mf.size) or + (MemMapFileStream(s).pos < 0) + +proc mmsSetPosition(s: Stream, pos: int) = + if pos > MemMapFileStream(s).mf.size or pos < 0: + raise newEIO("cannot set pos in stream") + MemMapFileStream(s).pos = pos + +proc mmsGetPosition(s: Stream): int = MemMapFileStream(s).pos + +proc mmsPeekData(s: Stream, buffer: pointer, bufLen: int): int = + let startAddress = cast[int](MemMapFileStream(s).mf.mem) + let p = cast[int](MemMapFileStream(s).pos) + let l = min(bufLen, MemMapFileStream(s).mf.size - p) + moveMem(buffer, cast[pointer](startAddress + p), l) + result = l + +proc mmsReadData(s: Stream, buffer: pointer, bufLen: int): int = + result = mmsPeekData(s, buffer, bufLen) + inc(MemMapFileStream(s).pos, result) + +proc mmsWriteData(s: Stream, buffer: pointer, bufLen: int) = + if MemMapFileStream(s).mode == fmRead: + raise newEIO("cannot write to read-only stream") + let size = MemMapFileStream(s).mf.size + if MemMapFileStream(s).pos + bufLen > size: + raise newEIO("cannot write to stream") + let p = cast[int](MemMapFileStream(s).mf.mem) + + cast[int](MemMapFileStream(s).pos) + moveMem(cast[pointer](p), buffer, bufLen) + inc(MemMapFileStream(s).pos, bufLen) +proc newMemMapFileStream*(filename: string, mode: FileMode = fmRead, + fileSize: int = -1): MemMapFileStream = + ## creates a new stream from the file named `filename` with the mode `mode`. + ## Raises ## `OSError` if the file cannot be opened. See the `system + ## <system.html>`_ module for a list of available FileMode enums. + ## `fileSize` can only be set if the file does not exist and is opened + ## with write access (e.g., with fmReadWrite). + var mf: MemFile = open(filename, mode, newFileSize = fileSize) + new(result) + result.mode = mode + result.mf = mf + result.closeImpl = mmsClose + result.atEndImpl = mmsAtEnd + result.setPositionImpl = mmsSetPosition + result.getPositionImpl = mmsGetPosition + result.readDataImpl = mmsReadData + result.peekDataImpl = mmsPeekData + result.writeDataImpl = mmsWriteData + result.flushImpl = mmsFlush |