summary refs log blame commit diff stats
path: root/lib/pure/memfiles.nim
blob: 8c404abe84554f08fd0128636f4a648ff4cdad1e (plain) (tree)
1
2
3
4
5
6
7
8
9


                                     
                                               




                                                   

                                           

                                                              

                      
                


                    
                                                                             
 
         

    
                                                                



                                                                          






                          





                                                                            
 
                                                         



                                            

                    

                        
                                                     
                


                                                                     
                    
                                   











                                                                            
                                                                            

                                                            
 
                                              
                                               





                                                    
                                                                          
                                             
                                 
                                                                              
                                             
                                                
 

                                                                
                                          
                          


                                                      
                             
                                                   
 
                                 
                       





                                                      
                         
                                               

                      
                                               
                                
                                                    

                                              

                                                                      
 
       
                                                     
                
                                                         
                      





                                                  

                                         

                                             
                                               

                         
                                                     
                                                      

                        
                              
         
                     
                                          

                                        
                                       
           
                                                      
 
                      
          
                  

                                                           
                    

             
                                               
                                                
 


                                                                           

                   
                           


                                         



                                                      

                     
                             











                                             
                            
 
#
#
#            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(errCode: TOSErrorCode, msg: expr) =
      rollback()
      if result.fHandle != 0: discard closeHandle(result.fHandle)
      if result.mapHandle != 0: discard closeHandle(result.mapHandle)
      osError(errCode)
      # 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(osLastError(), "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)
      let lastErr = osLastError()
      if (status == INVALID_SET_FILE_POINTER and lastErr.int32 != NO_ERROR) or
         (setEndOfFile(result.fHandle) == 0):
        fail(lastErr, "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(osLastError(), "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(osLastError(), "error mapping view")

    var hi, low: int32
    low = getFileSize(result.fHandle, addr(hi))
    if low == INVALID_FILE_SIZE:
      fail(osLastError(), "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(errCode: TOSErrorCode, msg: expr) =
      rollback()
      if result.handle != 0: discard close(result.handle)
      osError(errCode)
  
    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(osLastError(), "error opening file")

    if newFileSize != -1:
      if ftruncate(result.handle, newFileSize) == -1:
        fail(osLastError(), "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(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)

    if result.mem == cast[pointer](MAP_FAILED):
      fail(osLastError(), "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
  var lastErr: TOSErrorCode

  when defined(windows):
    if f.fHandle != INVALID_HANDLE_VALUE:
      lastErr = osLastError()
      error = unmapViewOfFile(f.mem) == 0
      error = (closeHandle(f.mapHandle) == 0) or error
      error = (closeHandle(f.fHandle) == 0) or error
  else:
    if f.handle != 0:
      lastErr = osLastError()
      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(lastErr)