aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bolt_unix.go8
-rw-r--r--bolt_windows.go2
-rw-r--r--db.go33
-rw-r--r--db_test.go17
4 files changed, 35 insertions, 25 deletions
diff --git a/bolt_unix.go b/bolt_unix.go
index cc958d4..8107f4a 100644
--- a/bolt_unix.go
+++ b/bolt_unix.go
@@ -11,7 +11,7 @@ import (
)
// flock acquires an advisory lock on a file descriptor.
-func flock(f *os.File, timeout time.Duration) error {
+func flock(f *os.File, exclusive bool, timeout time.Duration) error {
var t time.Time
for {
// If we're beyond our timeout then return an error.
@@ -21,9 +21,13 @@ func flock(f *os.File, timeout time.Duration) error {
} else if timeout > 0 && time.Since(t) > timeout {
return ErrTimeout
}
+ flag := syscall.LOCK_SH
+ if exclusive {
+ flag = syscall.LOCK_EX
+ }
// Otherwise attempt to obtain an exclusive lock.
- err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
+ err := syscall.Flock(int(f.Fd()), flag|syscall.LOCK_NB)
if err == nil {
return nil
} else if err != syscall.EWOULDBLOCK {
diff --git a/bolt_windows.go b/bolt_windows.go
index cfece39..783b633 100644
--- a/bolt_windows.go
+++ b/bolt_windows.go
@@ -16,7 +16,7 @@ func fdatasync(db *DB) error {
}
// flock acquires an advisory lock on a file descriptor.
-func flock(f *os.File, _ time.Duration) error {
+func flock(f *os.File, _ bool, _ time.Duration) error {
return nil
}
diff --git a/db.go b/db.go
index 5cf0533..5ae35cc 100644
--- a/db.go
+++ b/db.go
@@ -140,14 +140,8 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
db.MaxBatchSize = DefaultMaxBatchSize
db.MaxBatchDelay = DefaultMaxBatchDelay
- // Get file stats.
- s, err := os.Stat(path)
flag := os.O_RDWR
- if err != nil && !os.IsNotExist(err) {
- return nil, err
- } else if err == nil && (s.Mode().Perm()&0222) == 0 {
- // remove www from mode as well.
- mode ^= (mode & 0222)
+ if options.ReadOnly {
flag = os.O_RDONLY
db.readOnly = true
// Ignore truncations.
@@ -156,21 +150,26 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
// Open data file and separate sync handler for metadata writes.
db.path = path
+ var err error
if db.file, err = os.OpenFile(db.path, flag|os.O_CREATE, mode); err != nil {
_ = db.close()
return nil, err
}
- // No need to lock read-only file.
if !db.readOnly {
db.ops.Truncate = db.file.Truncate
- // Lock file so that other processes using Bolt cannot use the database
- // at the same time. This would cause corruption since the two processes
- // would write meta pages and free pages separately.
- if err := flock(db.file, options.Timeout); err != nil {
- _ = db.close()
- return nil, err
- }
+ }
+
+ // Lock file so that other processes using Bolt in read-write mode cannot
+ // use the database at the same time. This would cause corruption since
+ // the two processes would write meta pages and free pages separately.
+ // The database file is locked exclusively (only one process can grab the lock)
+ // if !options.ReadOnly.
+ // The database file is locked using the shared lock (more than one process may
+ // hold a lock at the same time) otherwise (options.ReadOnly is set).
+ if err := flock(db.file, !db.readOnly, options.Timeout); err != nil {
+ _ = db.close()
+ return nil, err
}
// Default values for test hooks
@@ -660,6 +659,10 @@ type Options struct {
// Sets the DB.NoGrowSync flag before memory mapping the file.
NoGrowSync bool
+
+ // Open database in read-only mode. Uses flock(..., LOCK_SH |LOCK_NB) to
+ // grab a shared lock (UNIX).
+ ReadOnly bool
}
// DefaultOptions represent the options used if nil options are passed into Open().
diff --git a/db_test.go b/db_test.go
index b3d41f3..64eb923 100644
--- a/db_test.go
+++ b/db_test.go
@@ -224,7 +224,9 @@ func TestDB_Open_FileTooSmall(t *testing.T) {
equals(t, errors.New("file size too small"), err)
}
-// Ensure that a database can be opened in read-only mode.
+// Ensure that a database can be opened in read-only mode by multiple processes
+// and that a database can not be opened in read-write mode and in read-only
+// mode at the same time.
func TestOpen_ReadOnly(t *testing.T) {
var bucket = []byte(`bucket`)
var key = []byte(`key`)
@@ -243,14 +245,15 @@ func TestOpen_ReadOnly(t *testing.T) {
assert(t, !db.IsReadOnly(), "")
ok(t, err)
ok(t, db.Close())
- // Make it read-only.
- ok(t, os.Chmod(path, 0444))
- // Open again.
- db0, err := bolt.Open(path, 0666, nil)
+ // Open in read-only mode.
+ db0, err := bolt.Open(path, 0666, &bolt.Options{ReadOnly: true})
ok(t, err)
defer db0.Close()
- // And again.
- db1, err := bolt.Open(path, 0666, nil)
+ // Try opening in regular mode.
+ _, err = bolt.Open(path, 0666, &bolt.Options{Timeout: time.Millisecond * 100})
+ assert(t, err != nil, "")
+ // And again (in read-only mode).
+ db1, err := bolt.Open(path, 0666, &bolt.Options{ReadOnly: true})
ok(t, err)
defer db1.Close()
for _, db := range []*bolt.DB{db0, db1} {