memfiles.nim 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Nim Contributors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## :Authors: Zahary Karadjov, Andreas Rumpf
  10. ##
  11. ## This module provides support for `memory mapped files`:idx:
  12. ## (Posix's `mmap`:idx:) on the different operating systems.
  13. ##
  14. ## It also provides some fast iterators over lines in text files (or
  15. ## other "line-like", variable length, delimited records).
  16. when defined(windows):
  17. import std/winlean
  18. when defined(nimPreviewSlimSystem):
  19. import std/widestrs
  20. elif defined(posix):
  21. import std/posix
  22. else:
  23. {.error: "the memfiles module is not supported on your operating system!".}
  24. import std/streams
  25. import std/oserrors
  26. when defined(nimPreviewSlimSystem):
  27. import std/[syncio, assertions]
  28. from system/ansi_c import c_memchr
  29. proc newEIO(msg: string): ref IOError =
  30. result = (ref IOError)(msg: msg)
  31. proc setFileSize(fh: FileHandle, newFileSize = -1, oldSize = -1): OSErrorCode =
  32. ## Set the size of open file pointed to by `fh` to `newFileSize` if != -1,
  33. ## allocating | freeing space from the file system. This routine returns the
  34. ## last OSErrorCode found rather than raising to support old rollback/clean-up
  35. ## code style. [ Should maybe move to std/osfiles. ]
  36. result = OSErrorCode(0)
  37. if newFileSize < 0 or newFileSize == oldSize:
  38. return result
  39. when defined(windows):
  40. var sizeHigh = int32(newFileSize shr 32)
  41. let sizeLow = int32(newFileSize and 0xffffffff)
  42. let status = setFilePointer(Handle fh, sizeLow, addr(sizeHigh), FILE_BEGIN)
  43. let lastErr = osLastError()
  44. if (status == INVALID_SET_FILE_POINTER and lastErr.int32 != NO_ERROR) or
  45. setEndOfFile(Handle fh) == 0:
  46. result = lastErr
  47. else:
  48. if newFileSize > oldSize: # grow the file
  49. var e: cint = cint(0) # posix_fallocate truncates up when needed.
  50. when declared(posix_fallocate):
  51. while (e = posix_fallocate(fh, 0, newFileSize); e == EINTR):
  52. discard
  53. if (e == EINVAL or e == EOPNOTSUPP) and ftruncate(fh, newFileSize) == -1:
  54. result = osLastError() # fallback arguable; Most portable BUT allows SEGV
  55. elif e != 0:
  56. result = osLastError()
  57. else: # shrink the file
  58. if ftruncate(fh.cint, newFileSize) == -1:
  59. result = osLastError()
  60. type
  61. MemFile* = object ## represents a memory mapped file
  62. mem*: pointer ## a pointer to the memory mapped file. The pointer
  63. ## can be used directly to change the contents of the
  64. ## file, if it was opened with write access.
  65. size*: int ## size of the memory mapped file
  66. when defined(windows):
  67. fHandle*: Handle ## **Caution**: Windows specific public field to allow
  68. ## even more low level trickery.
  69. mapHandle*: Handle ## **Caution**: Windows specific public field.
  70. wasOpened*: bool ## **Caution**: Windows specific public field.
  71. else:
  72. handle*: cint ## **Caution**: Posix specific public field.
  73. flags: cint ## **Caution**: Platform specific private field.
  74. proc mapMem*(m: var MemFile, mode: FileMode = fmRead,
  75. mappedSize = -1, offset = 0, mapFlags = cint(-1)): pointer =
  76. ## returns a pointer to a mapped portion of MemFile `m`
  77. ##
  78. ## `mappedSize` of `-1` maps to the whole file, and
  79. ## `offset` must be multiples of the PAGE SIZE of your OS
  80. if mode == fmAppend:
  81. raise newEIO("The append mode is not supported.")
  82. var readonly = mode == fmRead
  83. when defined(windows):
  84. result = mapViewOfFileEx(
  85. m.mapHandle,
  86. if readonly: FILE_MAP_READ else: FILE_MAP_READ or FILE_MAP_WRITE,
  87. int32(offset shr 32),
  88. int32(offset and 0xffffffff),
  89. WinSizeT(if mappedSize == -1: 0 else: mappedSize),
  90. nil)
  91. if result == nil:
  92. raiseOSError(osLastError())
  93. else:
  94. assert mappedSize > 0
  95. m.flags = if mapFlags == cint(-1): MAP_SHARED else: mapFlags
  96. #Ensure exactly one of MAP_PRIVATE cr MAP_SHARED is set
  97. if int(m.flags and MAP_PRIVATE) == 0:
  98. m.flags = m.flags or MAP_SHARED
  99. result = mmap(
  100. nil,
  101. mappedSize,
  102. if readonly: PROT_READ else: PROT_READ or PROT_WRITE,
  103. m.flags,
  104. m.handle, offset)
  105. if result == cast[pointer](MAP_FAILED):
  106. raiseOSError(osLastError())
  107. proc unmapMem*(f: var MemFile, p: pointer, size: int) =
  108. ## unmaps the memory region `(p, <p+size)` of the mapped file `f`.
  109. ## All changes are written back to the file system, if `f` was opened
  110. ## with write access.
  111. ##
  112. ## `size` must be of exactly the size that was requested
  113. ## via `mapMem`.
  114. when defined(windows):
  115. if unmapViewOfFile(p) == 0: raiseOSError(osLastError())
  116. else:
  117. if munmap(p, size) != 0: raiseOSError(osLastError())
  118. proc open*(filename: string, mode: FileMode = fmRead,
  119. mappedSize = -1, offset = 0, newFileSize = -1,
  120. allowRemap = false, mapFlags = cint(-1)): MemFile =
  121. ## opens a memory mapped file. If this fails, `OSError` is raised.
  122. ##
  123. ## `newFileSize` can only be set if the file does not exist and is opened
  124. ## with write access (e.g., with fmReadWrite).
  125. ##
  126. ##`mappedSize` and `offset`
  127. ## can be used to map only a slice of the file.
  128. ##
  129. ## `offset` must be multiples of the PAGE SIZE of your OS
  130. ## (usually 4K or 8K but is unique to your OS)
  131. ##
  132. ## `allowRemap` only needs to be true if you want to call `mapMem` on
  133. ## the resulting MemFile; else file handles are not kept open.
  134. ##
  135. ## `mapFlags` allows callers to override default choices for memory mapping
  136. ## flags with a bitwise mask of a variety of likely platform-specific flags
  137. ## which may be ignored or even cause `open` to fail if misspecified.
  138. ##
  139. ## Example:
  140. ##
  141. ## ```nim
  142. ## var
  143. ## mm, mm_full, mm_half: MemFile
  144. ##
  145. ## mm = memfiles.open("/tmp/test.mmap", mode = fmWrite, newFileSize = 1024) # Create a new file
  146. ## mm.close()
  147. ##
  148. ## # Read the whole file, would fail if newFileSize was set
  149. ## mm_full = memfiles.open("/tmp/test.mmap", mode = fmReadWrite, mappedSize = -1)
  150. ##
  151. ## # Read the first 512 bytes
  152. ## mm_half = memfiles.open("/tmp/test.mmap", mode = fmReadWrite, mappedSize = 512)
  153. ## ```
  154. result = default(MemFile)
  155. # The file can be resized only when write mode is used:
  156. if mode == fmAppend:
  157. raise newEIO("The append mode is not supported.")
  158. assert newFileSize == -1 or mode != fmRead
  159. var readonly = mode == fmRead
  160. template rollback =
  161. result.mem = nil
  162. result.size = 0
  163. when defined(windows):
  164. let desiredAccess = GENERIC_READ
  165. let shareMode = FILE_SHARE_READ
  166. let flags = FILE_FLAG_RANDOM_ACCESS
  167. template fail(errCode: OSErrorCode, msg: untyped) =
  168. rollback()
  169. if result.fHandle != 0: discard closeHandle(result.fHandle)
  170. if result.mapHandle != 0: discard closeHandle(result.mapHandle)
  171. raiseOSError(errCode)
  172. # return false
  173. #raise newException(IOError, msg)
  174. template callCreateFile(winApiProc, filename): untyped =
  175. winApiProc(
  176. filename,
  177. # GENERIC_ALL != (GENERIC_READ or GENERIC_WRITE)
  178. if readonly: desiredAccess else: desiredAccess or GENERIC_WRITE,
  179. if readonly: shareMode else: shareMode or FILE_SHARE_WRITE,
  180. nil,
  181. if newFileSize != -1: CREATE_ALWAYS else: OPEN_EXISTING,
  182. if readonly: FILE_ATTRIBUTE_READONLY or flags
  183. else: FILE_ATTRIBUTE_NORMAL or flags,
  184. 0)
  185. result.fHandle = callCreateFile(createFileW, newWideCString(filename))
  186. if result.fHandle == INVALID_HANDLE_VALUE:
  187. fail(osLastError(), "error opening file")
  188. if (let e = setFileSize(result.fHandle.FileHandle, newFileSize);
  189. e != 0.OSErrorCode): fail(e, "error setting file size")
  190. # since the strings are always 'nil', we simply always call
  191. # CreateFileMappingW which should be slightly faster anyway:
  192. result.mapHandle = createFileMappingW(
  193. result.fHandle, nil,
  194. if readonly: PAGE_READONLY else: PAGE_READWRITE,
  195. 0, 0, nil)
  196. if result.mapHandle == 0:
  197. fail(osLastError(), "error creating mapping")
  198. result.mem = mapViewOfFileEx(
  199. result.mapHandle,
  200. if readonly: FILE_MAP_READ else: FILE_MAP_READ or FILE_MAP_WRITE,
  201. int32(offset shr 32),
  202. int32(offset and 0xffffffff),
  203. if mappedSize == -1: 0 else: mappedSize,
  204. nil)
  205. if result.mem == nil:
  206. fail(osLastError(), "error mapping view")
  207. var hi, low: int32
  208. low = getFileSize(result.fHandle, addr(hi))
  209. if low == INVALID_FILE_SIZE:
  210. fail(osLastError(), "error getting file size")
  211. else:
  212. var fileSize = (int64(hi) shl 32) or int64(uint32(low))
  213. if mappedSize != -1: result.size = min(fileSize, mappedSize).int
  214. else: result.size = fileSize.int
  215. result.wasOpened = true
  216. if not allowRemap and result.fHandle != INVALID_HANDLE_VALUE:
  217. if closeHandle(result.fHandle) != 0:
  218. result.fHandle = INVALID_HANDLE_VALUE
  219. else:
  220. template fail(errCode: OSErrorCode, msg: string) =
  221. rollback()
  222. if result.handle != -1: discard close(result.handle)
  223. raiseOSError(errCode)
  224. var flags = (if readonly: O_RDONLY else: O_RDWR) or O_CLOEXEC
  225. if newFileSize != -1:
  226. flags = flags or O_CREAT or O_TRUNC
  227. var permissionsMode = S_IRUSR or S_IWUSR
  228. result.handle = open(filename, flags, permissionsMode)
  229. if result.handle != -1:
  230. if (let e = setFileSize(result.handle.FileHandle, newFileSize);
  231. e != 0.OSErrorCode): fail(e, "error setting file size")
  232. else:
  233. result.handle = open(filename, flags)
  234. if result.handle == -1:
  235. fail(osLastError(), "error opening file")
  236. if mappedSize != -1: # XXX Logic here differs from `when windows` branch ..
  237. result.size = mappedSize # .. which always fstats&Uses min(mappedSize, st).
  238. else: # if newFileSize!=-1: result.size=newFileSize # if trust setFileSize
  239. var stat: Stat = default(Stat) # ^^.. BUT some FSes (eg. Linux HugeTLBfs) round to 2MiB.
  240. if fstat(result.handle, stat) != -1:
  241. result.size = stat.st_size.int # int may be 32-bit-unsafe for 2..<4 GiB
  242. else:
  243. fail(osLastError(), "error getting file size")
  244. result.flags = if mapFlags == cint(-1): MAP_SHARED else: mapFlags
  245. # Ensure exactly one of MAP_PRIVATE cr MAP_SHARED is set
  246. if int(result.flags and MAP_PRIVATE) == 0:
  247. result.flags = result.flags or MAP_SHARED
  248. let pr = if readonly: PROT_READ else: PROT_READ or PROT_WRITE
  249. result.mem = mmap(nil, result.size, pr, result.flags, result.handle, offset)
  250. if result.mem == cast[pointer](MAP_FAILED):
  251. fail(osLastError(), "file mapping failed")
  252. if not allowRemap and result.handle != -1:
  253. if close(result.handle) == 0:
  254. result.handle = -1
  255. proc flush*(f: var MemFile; attempts: Natural = 3) =
  256. ## Flushes `f`'s buffer for the number of attempts equal to `attempts`.
  257. ## If were errors an exception `OSError` will be raised.
  258. var res = false
  259. var lastErr: OSErrorCode
  260. when defined(windows):
  261. for i in 1..attempts:
  262. res = flushViewOfFile(f.mem, 0) != 0
  263. if res:
  264. break
  265. lastErr = osLastError()
  266. if lastErr != ERROR_LOCK_VIOLATION.OSErrorCode:
  267. raiseOSError(lastErr)
  268. else:
  269. for i in 1..attempts:
  270. res = msync(f.mem, f.size, MS_SYNC or MS_INVALIDATE) == 0
  271. if res:
  272. break
  273. lastErr = osLastError()
  274. if lastErr != EBUSY.OSErrorCode:
  275. raiseOSError(lastErr, "error flushing mapping")
  276. proc resize*(f: var MemFile, newFileSize: int) {.raises: [IOError, OSError].} =
  277. ## Resize & re-map the file underlying an `allowRemap MemFile`. If the OS/FS
  278. ## supports it, file space is reserved to ensure room for new virtual pages.
  279. ## Caller should wait often enough for `flush` to finish to limit use of
  280. ## system RAM for write buffering, perhaps just prior to this call.
  281. ## **Note**: this assumes the entire file is mapped read-write at offset 0.
  282. ## Also, the value of `.mem` will probably change.
  283. if newFileSize < 1: # Q: include system/bitmasks & use PageSize ?
  284. raise newException(IOError, "Cannot resize MemFile to < 1 byte")
  285. when defined(windows):
  286. if not f.wasOpened:
  287. raise newException(IOError, "Cannot resize unopened MemFile")
  288. if f.fHandle == INVALID_HANDLE_VALUE:
  289. raise newException(IOError,
  290. "Cannot resize MemFile opened with allowRemap=false")
  291. if unmapViewOfFile(f.mem) == 0 or closeHandle(f.mapHandle) == 0: # Un-do map
  292. raiseOSError(osLastError())
  293. if newFileSize != f.size: # Seek to size & `setEndOfFile` => allocated.
  294. if (let e = setFileSize(f.fHandle.FileHandle, newFileSize);
  295. e != 0.OSErrorCode): raiseOSError(e)
  296. f.mapHandle = createFileMappingW(f.fHandle, nil, PAGE_READWRITE, 0,0,nil)
  297. if f.mapHandle == 0: # Re-do map
  298. raiseOSError(osLastError())
  299. let m = mapViewOfFileEx(f.mapHandle, FILE_MAP_READ or FILE_MAP_WRITE,
  300. 0, 0, WinSizeT(newFileSize), nil)
  301. if m != nil:
  302. f.mem = m
  303. f.size = newFileSize
  304. else:
  305. raiseOSError(osLastError())
  306. elif defined(posix):
  307. if f.handle == -1:
  308. raise newException(IOError,
  309. "Cannot resize MemFile opened with allowRemap=false")
  310. if newFileSize != f.size:
  311. let e = setFileSize(f.handle.FileHandle, newFileSize, f.size)
  312. if e != 0.OSErrorCode: raiseOSError(e)
  313. when defined(linux): #Maybe NetBSD, too?
  314. # On Linux this can be over 100 times faster than a munmap,mmap cycle.
  315. proc mremap(old: pointer; oldSize, newSize: csize_t; flags: cint):
  316. pointer {.importc: "mremap", header: "<sys/mman.h>".}
  317. let newAddr = mremap(f.mem, csize_t(f.size), csize_t(newFileSize), 1.cint)
  318. if newAddr == cast[pointer](MAP_FAILED):
  319. raiseOSError(osLastError())
  320. else:
  321. if munmap(f.mem, f.size) != 0:
  322. raiseOSError(osLastError())
  323. let newAddr = mmap(nil, newFileSize, PROT_READ or PROT_WRITE,
  324. f.flags, f.handle, 0)
  325. if newAddr == cast[pointer](MAP_FAILED):
  326. raiseOSError(osLastError())
  327. f.mem = newAddr
  328. f.size = newFileSize
  329. proc close*(f: var MemFile) =
  330. ## closes the memory mapped file `f`. All changes are written back to the
  331. ## file system, if `f` was opened with write access.
  332. var error = false
  333. var lastErr: OSErrorCode
  334. when defined(windows):
  335. if f.wasOpened:
  336. error = unmapViewOfFile(f.mem) == 0
  337. if not error:
  338. error = closeHandle(f.mapHandle) == 0
  339. if not error and f.fHandle != INVALID_HANDLE_VALUE:
  340. discard closeHandle(f.fHandle)
  341. f.fHandle = INVALID_HANDLE_VALUE
  342. if error:
  343. lastErr = osLastError()
  344. else:
  345. error = munmap(f.mem, f.size) != 0
  346. lastErr = osLastError()
  347. if f.handle != -1:
  348. error = (close(f.handle) != 0) or error
  349. f.size = 0
  350. f.mem = nil
  351. when defined(windows):
  352. f.fHandle = 0
  353. f.mapHandle = 0
  354. f.wasOpened = false
  355. else:
  356. f.handle = -1
  357. if error: raiseOSError(lastErr)
  358. type
  359. MemSlice* = object ## represent slice of a MemFile for iteration over delimited lines/records
  360. data*: pointer
  361. size*: int
  362. proc `==`*(x, y: MemSlice): bool =
  363. ## Compare a pair of MemSlice for strict equality.
  364. result = (x.size == y.size and equalMem(x.data, y.data, x.size))
  365. proc `$`*(ms: MemSlice): string {.inline.} =
  366. ## Return a Nim string built from a MemSlice.
  367. result = newString(ms.size)
  368. copyMem(result.cstring, ms.data, ms.size)
  369. iterator memSlices*(mfile: MemFile, delim = '\l', eat = '\r'): MemSlice {.inline.} =
  370. ## Iterates over \[optional `eat`] `delim`-delimited slices in MemFile `mfile`.
  371. ##
  372. ## Default parameters parse lines ending in either Unix(\\l) or Windows(\\r\\l)
  373. ## style on on a line-by-line basis. I.e., not every line needs the same ending.
  374. ## Unlike readLine(File) & lines(File), archaic MacOS9 \\r-delimited lines
  375. ## are not supported as a third option for each line. Such archaic MacOS9
  376. ## files can be handled by passing delim='\\r', eat='\\0', though.
  377. ##
  378. ## Delimiters are not part of the returned slice. A final, unterminated line
  379. ## or record is returned just like any other.
  380. ##
  381. ## Non-default delimiters can be passed to allow iteration over other sorts
  382. ## of "line-like" variable length records. Pass eat='\\0' to be strictly
  383. ## `delim`-delimited. (Eating an optional prefix equal to '\\0' is not
  384. ## supported.)
  385. ##
  386. ## This zero copy, memchr-limited interface is probably the fastest way to
  387. ## iterate over line-like records in a file. However, returned (data,size)
  388. ## objects are not Nim strings, bounds checked Nim arrays, or even terminated
  389. ## C strings. So, care is required to access the data (e.g., think C mem*
  390. ## functions, not str* functions).
  391. ##
  392. ## Example:
  393. ## ```nim
  394. ## var count = 0
  395. ## for slice in memSlices(memfiles.open("foo")):
  396. ## if slice.size > 0 and cast[cstring](slice.data)[0] != '#':
  397. ## inc(count)
  398. ## echo count
  399. ## ```
  400. proc `-!`(p, q: pointer): int {.inline.} = return cast[int](p) -% cast[int](q)
  401. var ending: pointer
  402. var ms = MemSlice(data: mfile.mem, size: 0)
  403. var remaining = mfile.size
  404. while remaining > 0:
  405. ending = c_memchr(ms.data, cint(delim), csize_t(remaining))
  406. if ending == nil: # unterminated final slice
  407. ms.size = remaining # Weird case..check eat?
  408. yield ms
  409. break
  410. ms.size = ending -! ms.data # delim is NOT included
  411. if eat != '\0' and ms.size > 0 and cast[cstring](ms.data)[ms.size - 1] == eat:
  412. dec(ms.size) # trim pre-delim char
  413. yield ms
  414. ms.data = cast[pointer](cast[int](ending) +% 1) # skip delim
  415. remaining = mfile.size - (ms.data -! mfile.mem)
  416. iterator lines*(mfile: MemFile, buf: var string, delim = '\l',
  417. eat = '\r'): string {.inline.} =
  418. ## Replace contents of passed buffer with each new line, like
  419. ## `readLine(File) <syncio.html#readLine,File,string>`_.
  420. ## `delim`, `eat`, and delimiting logic is exactly as for `memSlices
  421. ## <#memSlices.i,MemFile,char,char>`_, but Nim strings are returned.
  422. ##
  423. ## Example:
  424. ## ```nim
  425. ## var buffer: string = ""
  426. ## for line in lines(memfiles.open("foo"), buffer):
  427. ## echo line
  428. ## ```
  429. for ms in memSlices(mfile, delim, eat):
  430. setLen(buf, ms.size)
  431. if ms.size > 0:
  432. copyMem(addr buf[0], ms.data, ms.size)
  433. yield buf
  434. iterator lines*(mfile: MemFile, delim = '\l', eat = '\r'): string {.inline.} =
  435. ## Return each line in a file as a Nim string, like
  436. ## `lines(File) <syncio.html#lines.i,File>`_.
  437. ## `delim`, `eat`, and delimiting logic is exactly as for `memSlices
  438. ## <#memSlices.i,MemFile,char,char>`_, but Nim strings are returned.
  439. ##
  440. ## Example:
  441. ## ```nim
  442. ## for line in lines(memfiles.open("foo")):
  443. ## echo line
  444. ## ```
  445. var buf = newStringOfCap(80)
  446. for line in lines(mfile, buf, delim, eat):
  447. yield buf
  448. type
  449. MemMapFileStream* = ref MemMapFileStreamObj ## a stream that encapsulates a `MemFile`
  450. MemMapFileStreamObj* = object of Stream
  451. mf: MemFile
  452. mode: FileMode
  453. pos: int
  454. proc mmsClose(s: Stream) =
  455. MemMapFileStream(s).pos = -1
  456. close(MemMapFileStream(s).mf)
  457. proc mmsFlush(s: Stream) = flush(MemMapFileStream(s).mf)
  458. proc mmsAtEnd(s: Stream): bool = (MemMapFileStream(s).pos >= MemMapFileStream(s).mf.size) or
  459. (MemMapFileStream(s).pos < 0)
  460. proc mmsSetPosition(s: Stream, pos: int) =
  461. if pos > MemMapFileStream(s).mf.size or pos < 0:
  462. raise newEIO("cannot set pos in stream")
  463. MemMapFileStream(s).pos = pos
  464. proc mmsGetPosition(s: Stream): int = MemMapFileStream(s).pos
  465. proc mmsPeekData(s: Stream, buffer: pointer, bufLen: int): int =
  466. let startAddress = cast[int](MemMapFileStream(s).mf.mem)
  467. let p = cast[int](MemMapFileStream(s).pos)
  468. let l = min(bufLen, MemMapFileStream(s).mf.size - p)
  469. moveMem(buffer, cast[pointer](startAddress + p), l)
  470. result = l
  471. proc mmsReadData(s: Stream, buffer: pointer, bufLen: int): int =
  472. result = mmsPeekData(s, buffer, bufLen)
  473. inc(MemMapFileStream(s).pos, result)
  474. proc mmsWriteData(s: Stream, buffer: pointer, bufLen: int) =
  475. if MemMapFileStream(s).mode == fmRead:
  476. raise newEIO("cannot write to read-only stream")
  477. let size = MemMapFileStream(s).mf.size
  478. if MemMapFileStream(s).pos + bufLen > size:
  479. raise newEIO("cannot write to stream")
  480. let p = cast[int](MemMapFileStream(s).mf.mem) +
  481. cast[int](MemMapFileStream(s).pos)
  482. moveMem(cast[pointer](p), buffer, bufLen)
  483. inc(MemMapFileStream(s).pos, bufLen)
  484. proc newMemMapFileStream*(filename: string, mode: FileMode = fmRead,
  485. fileSize: int = -1): MemMapFileStream =
  486. ## creates a new stream from the file named `filename` with the mode `mode`.
  487. ## Raises ## `OSError` if the file cannot be opened. See the `system
  488. ## <system.html>`_ module for a list of available FileMode enums.
  489. ## `fileSize` can only be set if the file does not exist and is opened
  490. ## with write access (e.g., with fmReadWrite).
  491. var mf: MemFile = open(filename, mode, newFileSize = fileSize)
  492. result = MemMapFileStream(
  493. mode: mode,
  494. mf: mf,
  495. closeImpl: mmsClose,
  496. atEndImpl: mmsAtEnd,
  497. setPositionImpl: mmsSetPosition,
  498. getPositionImpl: mmsGetPosition,
  499. readDataImpl: mmsReadData,
  500. peekDataImpl: mmsPeekData,
  501. writeDataImpl: mmsWriteData,
  502. flushImpl: mmsFlush
  503. )