dir_plan9.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. /*
  2. * SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
  3. * SPDX-License-Identifier: Apache-2.0
  4. */
  5. package badger
  6. import (
  7. "fmt"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "github.com/dgraph-io/badger/v4/y"
  12. )
  13. // directoryLockGuard holds a lock on a directory and a pid file inside. The pid file isn't part
  14. // of the locking mechanism, it's just advisory.
  15. type directoryLockGuard struct {
  16. // File handle on the directory, which we've locked.
  17. f *os.File
  18. // The absolute path to our pid file.
  19. path string
  20. }
  21. // acquireDirectoryLock gets a lock on the directory.
  22. // It will also write our pid to dirPath/pidFileName for convenience.
  23. // readOnly is not supported on Plan 9.
  24. func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (
  25. *directoryLockGuard, error) {
  26. if readOnly {
  27. return nil, ErrPlan9NotSupported
  28. }
  29. // Convert to absolute path so that Release still works even if we do an unbalanced
  30. // chdir in the meantime.
  31. absPidFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName))
  32. if err != nil {
  33. return nil, y.Wrap(err, "cannot get absolute path for pid lock file")
  34. }
  35. // If the file was unpacked or created by some other program, it might not
  36. // have the ModeExclusive bit set. Set it before we call OpenFile, so that we
  37. // can be confident that a successful OpenFile implies exclusive use.
  38. //
  39. // OpenFile fails if the file ModeExclusive bit set *and* the file is already open.
  40. // So, if the file is closed when the DB crashed, we're fine. When the process
  41. // that was managing the DB crashes, the OS will close the file for us.
  42. //
  43. // This bit of code is copied from Go's lockedfile internal package:
  44. // https://github.com/golang/go/blob/go1.15rc1/src/cmd/go/internal/lockedfile/lockedfile_plan9.go#L58
  45. if fi, err := os.Stat(absPidFilePath); err == nil {
  46. if fi.Mode()&os.ModeExclusive == 0 {
  47. if err := os.Chmod(absPidFilePath, fi.Mode()|os.ModeExclusive); err != nil {
  48. return nil, y.Wrapf(err, "could not set exclusive mode bit")
  49. }
  50. }
  51. } else if !os.IsNotExist(err) {
  52. return nil, err
  53. }
  54. f, err := os.OpenFile(absPidFilePath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666|os.ModeExclusive)
  55. if err != nil {
  56. if isLocked(err) {
  57. return nil, y.Wrapf(err,
  58. "Cannot open pid lock file %q. Another process is using this Badger database",
  59. absPidFilePath)
  60. }
  61. return nil, y.Wrapf(err, "Cannot open pid lock file %q", absPidFilePath)
  62. }
  63. if _, err = fmt.Fprintf(f, "%d\n", os.Getpid()); err != nil {
  64. f.Close()
  65. return nil, y.Wrapf(err, "could not write pid")
  66. }
  67. return &directoryLockGuard{f, absPidFilePath}, nil
  68. }
  69. // Release deletes the pid file and releases our lock on the directory.
  70. func (guard *directoryLockGuard) release() error {
  71. // It's important that we remove the pid file first.
  72. err := os.Remove(guard.path)
  73. if closeErr := guard.f.Close(); err == nil {
  74. err = closeErr
  75. }
  76. guard.path = ""
  77. guard.f = nil
  78. return err
  79. }
  80. // openDir opens a directory for syncing.
  81. func openDir(path string) (*os.File, error) { return os.Open(path) }
  82. // When you create or delete a file, you have to ensure the directory entry for the file is synced
  83. // in order to guarantee the file is visible (if the system crashes). (See the man page for fsync,
  84. // or see https://github.com/coreos/etcd/issues/6368 for an example.)
  85. func syncDir(dir string) error {
  86. f, err := openDir(dir)
  87. if err != nil {
  88. return y.Wrapf(err, "While opening directory: %s.", dir)
  89. }
  90. err = f.Sync()
  91. closeErr := f.Close()
  92. if err != nil {
  93. return y.Wrapf(err, "While syncing directory: %s.", dir)
  94. }
  95. return y.Wrapf(closeErr, "While closing directory: %s.", dir)
  96. }
  97. // Opening an exclusive-use file returns an error.
  98. // The expected error strings are:
  99. //
  100. // - "open/create -- file is locked" (cwfs, kfs)
  101. // - "exclusive lock" (fossil)
  102. // - "exclusive use file already open" (ramfs)
  103. //
  104. // See https://github.com/golang/go/blob/go1.15rc1/src/cmd/go/internal/lockedfile/lockedfile_plan9.go#L16
  105. var lockedErrStrings = [...]string{
  106. "file is locked",
  107. "exclusive lock",
  108. "exclusive use file already open",
  109. }
  110. // Even though plan9 doesn't support the Lock/RLock/Unlock functions to
  111. // manipulate already-open files, IsLocked is still meaningful: os.OpenFile
  112. // itself may return errors that indicate that a file with the ModeExclusive bit
  113. // set is already open.
  114. func isLocked(err error) bool {
  115. s := err.Error()
  116. for _, frag := range lockedErrStrings {
  117. if strings.Contains(s, frag) {
  118. return true
  119. }
  120. }
  121. return false
  122. }