github.com/pingcap/badger@v1.5.1-0.20230103063557-828f39b09b6d/dir_unix.go (about) 1 // +build !windows 2 3 /* 4 * Copyright 2017 Dgraph Labs, Inc. and Contributors 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package badger 20 21 import ( 22 "fmt" 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 27 "github.com/pingcap/errors" 28 "golang.org/x/sys/unix" 29 ) 30 31 // directoryLockGuard holds a lock on a directory and a pid file inside. The pid file isn't part 32 // of the locking mechanism, it's just advisory. 33 type directoryLockGuard struct { 34 // File handle on the directory, which we've flocked. 35 f *os.File 36 // The absolute path to our pid file. 37 path string 38 // Was this a shared lock for a read-only database? 39 readOnly bool 40 } 41 42 // acquireDirectoryLock gets a lock on the directory (using flock). If 43 // this is not read-only, it will also write our pid to 44 // dirPath/pidFileName for convenience. 45 func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (*directoryLockGuard, error) { 46 // Convert to absolute path so that Release still works even if we do an unbalanced 47 // chdir in the meantime. 48 absPidFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName)) 49 if err != nil { 50 return nil, errors.Wrap(err, "cannot get absolute path for pid lock file") 51 } 52 f, err := os.Open(dirPath) 53 if err != nil { 54 return nil, errors.Wrapf(err, "cannot open directory %q", dirPath) 55 } 56 opts := unix.LOCK_EX | unix.LOCK_NB 57 if readOnly { 58 opts = unix.LOCK_SH | unix.LOCK_NB 59 } 60 61 err = unix.Flock(int(f.Fd()), opts) 62 if err != nil { 63 f.Close() 64 return nil, errors.Wrapf(err, 65 "Cannot acquire directory lock on %q. Another process is using this Badger database.", 66 dirPath) 67 } 68 69 if !readOnly { 70 // Yes, we happily overwrite a pre-existing pid file. We're the 71 // only read-write badger process using this directory. 72 err = ioutil.WriteFile(absPidFilePath, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0666) 73 if err != nil { 74 f.Close() 75 return nil, errors.Wrapf(err, 76 "Cannot write pid file %q", absPidFilePath) 77 } 78 } 79 return &directoryLockGuard{f, absPidFilePath, readOnly}, nil 80 } 81 82 // Release deletes the pid file and releases our lock on the directory. 83 func (guard *directoryLockGuard) release() error { 84 var err error 85 if !guard.readOnly { 86 // It's important that we remove the pid file first. 87 err = os.Remove(guard.path) 88 } 89 90 if closeErr := guard.f.Close(); err == nil { 91 err = closeErr 92 } 93 guard.path = "" 94 guard.f = nil 95 96 return err 97 } 98 99 // openDir opens a directory for syncing. 100 func openDir(path string) (*os.File, error) { return os.Open(path) }