github.com/scottcagno/storage@v1.8.0/pkg/mmap/mmap_windows_amd64.go (about)

     1  package mmap
     2  
     3  import (
     4  	"math"
     5  	"os"
     6  	"reflect"
     7  	"runtime"
     8  	"syscall"
     9  	"unsafe"
    10  )
    11  
    12  // Mapping is a mapping of the file into the memory.
    13  type Mapping struct {
    14  	mapping
    15  	// hProcess specifies the descriptor of the current process.
    16  	hProcess syscall.Handle
    17  	// hFile specifies the descriptor of the mapped file.
    18  	hFile syscall.Handle
    19  	// hMappedFile specifies the descriptor of the mapping object provided by the operation system.
    20  	hMappedFile syscall.Handle
    21  	// alignedAddress specifies the start address of the the mapped memory
    22  	// aligned by the memory page size.
    23  	alignedAddress uintptr
    24  	// alignedLength specifies the length of the mapped memory, in bytes,
    25  	// aligned by the memory page size.
    26  	alignedLength uintptr
    27  	// locked specifies whether the mapped memory is locked.
    28  	locked bool
    29  }
    30  
    31  // Open opens and returns a new mapping of the given file into the memory.
    32  // The given file descriptor will be duplicated. It means that
    33  // if the parent file will be closed the mapping will still be valid.
    34  // Actual offset and length may be different than the given
    35  // by the reason of aligning to the memory page size.
    36  func Open(fd uintptr, offset int64, length uintptr, mode Mode, flags Flag) (*Mapping, error) {
    37  
    38  	// Using int64 (off_t) for the offset and uintptr (size_t) for the length
    39  	// by the reason of the compatibility.
    40  	if offset < 0 {
    41  		return nil, ErrBadOffset
    42  	}
    43  	if length > uintptr(MaxInt) {
    44  		return nil, ErrBadLength
    45  	}
    46  
    47  	m := &Mapping{}
    48  	prot := uint32(syscall.PAGE_READONLY)
    49  	access := uint32(syscall.FILE_MAP_READ)
    50  	switch mode {
    51  	case ModeReadOnly:
    52  		// NOOP
    53  	case ModeReadWrite:
    54  		prot = syscall.PAGE_READWRITE
    55  		access = syscall.FILE_MAP_WRITE
    56  		m.writable = true
    57  	case ModeWriteCopy:
    58  		prot = syscall.PAGE_WRITECOPY
    59  		access = syscall.FILE_MAP_COPY
    60  		m.writable = true
    61  	default:
    62  		return nil, ErrBadMode
    63  	}
    64  	if flags&FlagExecutable != 0 {
    65  		prot <<= 4
    66  		access |= syscall.FILE_MAP_EXECUTE
    67  		m.executable = true
    68  	}
    69  
    70  	// The separate file handle is needed to avoid errors on the mapped file external closing.
    71  	var err error
    72  	m.hProcess, err = syscall.GetCurrentProcess()
    73  	if err != nil {
    74  		return nil, os.NewSyscallError("GetCurrentProcess", err)
    75  	}
    76  	err = syscall.DuplicateHandle(
    77  		m.hProcess, syscall.Handle(fd),
    78  		m.hProcess, &m.hFile,
    79  		0, true, syscall.DUPLICATE_SAME_ACCESS,
    80  	)
    81  	if err != nil {
    82  		return nil, os.NewSyscallError("DuplicateHandle", err)
    83  	}
    84  
    85  	// The mapping address range must be aligned by the memory page size.
    86  	pageSize := int64(os.Getpagesize())
    87  	if pageSize < 0 {
    88  		return nil, os.NewSyscallError("getpagesize", syscall.EINVAL)
    89  	}
    90  	outerOffset := offset / pageSize
    91  	innerOffset := offset % pageSize
    92  	// ASSERT: uintptr is of the 64-bit length on the amd64 architecture.
    93  	m.alignedLength = uintptr(innerOffset) + length
    94  
    95  	maxSize := uint64(outerOffset) + uint64(m.alignedLength)
    96  	maxSizeHigh := uint32(maxSize >> 32)
    97  	maxSizeLow := uint32(maxSize & uint64(math.MaxUint32))
    98  	m.hMappedFile, err = syscall.CreateFileMapping(m.hFile, nil, prot, maxSizeHigh, maxSizeLow, nil)
    99  	if err != nil {
   100  		return nil, os.NewSyscallError("CreateFileMappedFile", err)
   101  	}
   102  	fileOffset := uint64(outerOffset)
   103  	fileOffsetHigh := uint32(fileOffset >> 32)
   104  	fileOffsetLow := uint32(fileOffset & uint64(math.MaxUint32))
   105  	m.alignedAddress, err = syscall.MapViewOfFile(
   106  		m.hMappedFile, access,
   107  		fileOffsetHigh, fileOffsetLow, m.alignedLength,
   108  	)
   109  	if err != nil {
   110  		return nil, os.NewSyscallError("MapViewOfFile", err)
   111  	}
   112  	m.address = m.alignedAddress + uintptr(innerOffset)
   113  
   114  	// Wrapping the mapped memory by the byte slice.
   115  	slice := reflect.SliceHeader{}
   116  	slice.Data = m.address
   117  	slice.Len = int(length)
   118  	slice.Cap = slice.Len
   119  	m.memory = *(*[]byte)(unsafe.Pointer(&slice))
   120  
   121  	runtime.SetFinalizer(m, (*Mapping).Close)
   122  	return m, nil
   123  }
   124  
   125  // Lock locks the mapped memory pages.
   126  // All pages that contain a part of the mapping address range
   127  // are guaranteed to be resident in RAM when the call returns successfully.
   128  // The pages are guaranteed to stay in RAM until later unlocked.
   129  // It may need to increase process memory limits for operation success.
   130  // See working set on Windows and rlimit on Linux for details.
   131  func (m *Mapping) Lock() error {
   132  	if m.memory == nil {
   133  		return ErrClosed
   134  	}
   135  	if m.locked {
   136  		return ErrLocked
   137  	}
   138  	if err := syscall.VirtualLock(m.alignedAddress, m.alignedLength); err != nil {
   139  		return os.NewSyscallError("VirtualLock", err)
   140  	}
   141  	m.locked = true
   142  	return nil
   143  }
   144  
   145  // Unlock unlocks the previously locked mapped memory pages.
   146  func (m *Mapping) Unlock() error {
   147  	if m.memory == nil {
   148  		return ErrClosed
   149  	}
   150  	if !m.locked {
   151  		return ErrNotLocked
   152  	}
   153  	if err := syscall.VirtualUnlock(m.alignedAddress, m.alignedLength); err != nil {
   154  		return os.NewSyscallError("VirtualUnlock", err)
   155  	}
   156  	m.locked = false
   157  	return nil
   158  }
   159  
   160  // Sync synchronizes the mapped memory with the underlying file.
   161  func (m *Mapping) Sync() error {
   162  	if m.memory == nil {
   163  		return ErrClosed
   164  	}
   165  	if !m.writable {
   166  		return ErrReadOnly
   167  	}
   168  	if err := syscall.FlushViewOfFile(m.alignedAddress, m.alignedLength); err != nil {
   169  		return os.NewSyscallError("FlushViewOfFile", err)
   170  	}
   171  	if err := syscall.FlushFileBuffers(m.hFile); err != nil {
   172  		return os.NewSyscallError("FlushFileBuffers", err)
   173  	}
   174  	return nil
   175  }
   176  
   177  // Close closes this mapping and frees all resources associated with it.
   178  // Mapped memory will be synchronized with the underlying file and unlocked automatically.
   179  // Close implements the io.Closer interface.
   180  func (m *Mapping) Close() error {
   181  	if m.memory == nil {
   182  		return ErrClosed
   183  	}
   184  	var errs []error
   185  	if m.writable {
   186  		if err := m.Sync(); err != nil {
   187  			errs = append(errs, err)
   188  		}
   189  	}
   190  	if m.locked {
   191  		if err := m.Unlock(); err != nil {
   192  			errs = append(errs, err)
   193  		}
   194  	}
   195  	if err := syscall.UnmapViewOfFile(m.alignedAddress); err != nil {
   196  		errs = append(errs, os.NewSyscallError("UnmapViewOfFile", err))
   197  	}
   198  	if err := syscall.CloseHandle(m.hMappedFile); err != nil {
   199  		errs = append(errs, os.NewSyscallError("CloseHandle", err))
   200  	}
   201  	if err := syscall.CloseHandle(m.hFile); err != nil {
   202  		errs = append(errs, os.NewSyscallError("CloseHandle", err))
   203  	}
   204  	*m = Mapping{}
   205  	runtime.SetFinalizer(m, nil)
   206  	if len(errs) > 0 {
   207  		return errs[0]
   208  	}
   209  	return nil
   210  }