You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

144 lines
3.8 KiB

  1. package bolt
  2. import (
  3. "fmt"
  4. "os"
  5. "syscall"
  6. "time"
  7. "unsafe"
  8. )
  9. // LockFileEx code derived from golang build filemutex_windows.go @ v1.5.1
  10. var (
  11. modkernel32 = syscall.NewLazyDLL("kernel32.dll")
  12. procLockFileEx = modkernel32.NewProc("LockFileEx")
  13. procUnlockFileEx = modkernel32.NewProc("UnlockFileEx")
  14. )
  15. const (
  16. lockExt = ".lock"
  17. // see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
  18. flagLockExclusive = 2
  19. flagLockFailImmediately = 1
  20. // see https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
  21. errLockViolation syscall.Errno = 0x21
  22. )
  23. func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
  24. r, _, err := procLockFileEx.Call(uintptr(h), uintptr(flags), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)))
  25. if r == 0 {
  26. return err
  27. }
  28. return nil
  29. }
  30. func unlockFileEx(h syscall.Handle, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
  31. r, _, err := procUnlockFileEx.Call(uintptr(h), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)), 0)
  32. if r == 0 {
  33. return err
  34. }
  35. return nil
  36. }
  37. // fdatasync flushes written data to a file descriptor.
  38. func fdatasync(db *DB) error {
  39. return db.file.Sync()
  40. }
  41. // flock acquires an advisory lock on a file descriptor.
  42. func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
  43. // Create a separate lock file on windows because a process
  44. // cannot share an exclusive lock on the same file. This is
  45. // needed during Tx.WriteTo().
  46. f, err := os.OpenFile(db.path+lockExt, os.O_CREATE, mode)
  47. if err != nil {
  48. return err
  49. }
  50. db.lockfile = f
  51. var t time.Time
  52. for {
  53. // If we're beyond our timeout then return an error.
  54. // This can only occur after we've attempted a flock once.
  55. if t.IsZero() {
  56. t = time.Now()
  57. } else if timeout > 0 && time.Since(t) > timeout {
  58. return ErrTimeout
  59. }
  60. var flag uint32 = flagLockFailImmediately
  61. if exclusive {
  62. flag |= flagLockExclusive
  63. }
  64. err := lockFileEx(syscall.Handle(db.lockfile.Fd()), flag, 0, 1, 0, &syscall.Overlapped{})
  65. if err == nil {
  66. return nil
  67. } else if err != errLockViolation {
  68. return err
  69. }
  70. // Wait for a bit and try again.
  71. time.Sleep(50 * time.Millisecond)
  72. }
  73. }
  74. // funlock releases an advisory lock on a file descriptor.
  75. func funlock(db *DB) error {
  76. err := unlockFileEx(syscall.Handle(db.lockfile.Fd()), 0, 1, 0, &syscall.Overlapped{})
  77. db.lockfile.Close()
  78. os.Remove(db.path + lockExt)
  79. return err
  80. }
  81. // mmap memory maps a DB's data file.
  82. // Based on: https://github.com/edsrzf/mmap-go
  83. func mmap(db *DB, sz int) error {
  84. if !db.readOnly {
  85. // Truncate the database to the size of the mmap.
  86. if err := db.file.Truncate(int64(sz)); err != nil {
  87. return fmt.Errorf("truncate: %s", err)
  88. }
  89. }
  90. // Open a file mapping handle.
  91. sizelo := uint32(sz >> 32)
  92. sizehi := uint32(sz) & 0xffffffff
  93. h, errno := syscall.CreateFileMapping(syscall.Handle(db.file.Fd()), nil, syscall.PAGE_READONLY, sizelo, sizehi, nil)
  94. if h == 0 {
  95. return os.NewSyscallError("CreateFileMapping", errno)
  96. }
  97. // Create the memory map.
  98. addr, errno := syscall.MapViewOfFile(h, syscall.FILE_MAP_READ, 0, 0, uintptr(sz))
  99. if addr == 0 {
  100. return os.NewSyscallError("MapViewOfFile", errno)
  101. }
  102. // Close mapping handle.
  103. if err := syscall.CloseHandle(syscall.Handle(h)); err != nil {
  104. return os.NewSyscallError("CloseHandle", err)
  105. }
  106. // Convert to a byte array.
  107. db.data = ((*[maxMapSize]byte)(unsafe.Pointer(addr)))
  108. db.datasz = sz
  109. return nil
  110. }
  111. // munmap unmaps a pointer from a file.
  112. // Based on: https://github.com/edsrzf/mmap-go
  113. func munmap(db *DB) error {
  114. if db.data == nil {
  115. return nil
  116. }
  117. addr := (uintptr)(unsafe.Pointer(&db.data[0]))
  118. if err := syscall.UnmapViewOfFile(addr); err != nil {
  119. return os.NewSyscallError("UnmapViewOfFile", err)
  120. }
  121. return nil
  122. }