dir_unix.go raw
1 //go:build !windows && !plan9 && !js && !wasip1
2 // +build !windows,!plan9,!js,!wasip1
3
4 /*
5 * SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
6 * SPDX-License-Identifier: Apache-2.0
7 */
8
9 package badger
10
11 import (
12 "fmt"
13 "os"
14 "path/filepath"
15
16 "golang.org/x/sys/unix"
17
18 "github.com/dgraph-io/badger/v4/y"
19 )
20
21 // directoryLockGuard holds a lock on a directory and a pid file inside. The pid file isn't part
22 // of the locking mechanism, it's just advisory.
23 type directoryLockGuard struct {
24 // File handle on the directory, which we've flocked.
25 f *os.File
26 // The absolute path to our pid file.
27 path string
28 // Was this a shared lock for a read-only database?
29 readOnly bool
30 }
31
32 // acquireDirectoryLock gets a lock on the directory (using flock). If
33 // this is not read-only, it will also write our pid to
34 // dirPath/pidFileName for convenience.
35 func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (
36 *directoryLockGuard, error) {
37 // Convert to absolute path so that Release still works even if we do an unbalanced
38 // chdir in the meantime.
39 absPidFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName))
40 if err != nil {
41 return nil, y.Wrapf(err, "cannot get absolute path for pid lock file")
42 }
43 f, err := os.Open(dirPath)
44 if err != nil {
45 return nil, y.Wrapf(err, "cannot open directory %q", dirPath)
46 }
47 opts := unix.LOCK_EX | unix.LOCK_NB
48 if readOnly {
49 opts = unix.LOCK_SH | unix.LOCK_NB
50 }
51
52 err = unix.Flock(int(f.Fd()), opts)
53 if err != nil {
54 f.Close()
55 return nil, y.Wrapf(err,
56 "Cannot acquire directory lock on %q. Another process is using this Badger database.",
57 dirPath)
58 }
59
60 if !readOnly {
61 // Yes, we happily overwrite a pre-existing pid file. We're the
62 // only read-write badger process using this directory.
63 err = os.WriteFile(absPidFilePath, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0666)
64 if err != nil {
65 f.Close()
66 return nil, y.Wrapf(err,
67 "Cannot write pid file %q", absPidFilePath)
68 }
69 }
70 return &directoryLockGuard{f, absPidFilePath, readOnly}, nil
71 }
72
73 // Release deletes the pid file and releases our lock on the directory.
74 func (guard *directoryLockGuard) release() error {
75 var err error
76 if !guard.readOnly {
77 // It's important that we remove the pid file first.
78 err = os.Remove(guard.path)
79 }
80
81 if closeErr := guard.f.Close(); err == nil {
82 err = closeErr
83 }
84 guard.path = ""
85 guard.f = nil
86
87 return err
88 }
89
90 // openDir opens a directory for syncing.
91 func openDir(path string) (*os.File, error) { return os.Open(path) }
92
93 // When you create or delete a file, you have to ensure the directory entry for the file is synced
94 // in order to guarantee the file is visible (if the system crashes). (See the man page for fsync,
95 // or see https://github.com/coreos/etcd/issues/6368 for an example.)
96 func syncDir(dir string) error {
97 f, err := openDir(dir)
98 if err != nil {
99 return y.Wrapf(err, "While opening directory: %s.", dir)
100 }
101
102 err = f.Sync()
103 closeErr := f.Close()
104 if err != nil {
105 return y.Wrapf(err, "While syncing directory: %s.", dir)
106 }
107 return y.Wrapf(closeErr, "While closing directory: %s.", dir)
108 }
109