storage: fs: implement read-only mode (closes #111)
diff --git a/leveldb/bench_test.go b/leveldb/bench_test.go
index 91b4267..5c587b9 100644
--- a/leveldb/bench_test.go
+++ b/leveldb/bench_test.go
@@ -90,7 +90,7 @@
ro: &opt.ReadOptions{},
wo: &opt.WriteOptions{},
}
- p.stor, err = storage.OpenFile(benchDB)
+ p.stor, err = storage.OpenFile(benchDB, false)
if err != nil {
b.Fatal("cannot open stor: ", err)
}
diff --git a/leveldb/db.go b/leveldb/db.go
index b81baf7..537addb 100644
--- a/leveldb/db.go
+++ b/leveldb/db.go
@@ -211,7 +211,7 @@
// The returned DB instance is goroutine-safe.
// The DB must be closed after use, by calling Close method.
func OpenFile(path string, o *opt.Options) (db *DB, err error) {
- stor, err := storage.OpenFile(path)
+ stor, err := storage.OpenFile(path, o.GetReadOnly())
if err != nil {
return
}
@@ -261,7 +261,7 @@
// The returned DB instance is goroutine-safe.
// The DB must be closed after use, by calling Close method.
func RecoverFile(path string, o *opt.Options) (db *DB, err error) {
- stor, err := storage.OpenFile(path)
+ stor, err := storage.OpenFile(path, false)
if err != nil {
return
}
diff --git a/leveldb/db_test.go b/leveldb/db_test.go
index e37cbc9..63592c0 100644
--- a/leveldb/db_test.go
+++ b/leveldb/db_test.go
@@ -1877,7 +1877,7 @@
defer os.RemoveAll(dbpath)
for i := 0; i < 3; i++ {
- stor, err := storage.OpenFile(dbpath)
+ stor, err := storage.OpenFile(dbpath, false)
if err != nil {
t.Fatalf("(%d) cannot open storage: %s", i, err)
}
diff --git a/leveldb/storage/file_storage.go b/leveldb/storage/file_storage.go
index e70eb64..cbe1dc1 100644
--- a/leveldb/storage/file_storage.go
+++ b/leveldb/storage/file_storage.go
@@ -19,7 +19,10 @@
"time"
)
-var errFileOpen = errors.New("leveldb/storage: file still open")
+var (
+ errFileOpen = errors.New("leveldb/storage: file still open")
+ errReadOnly = errors.New("leveldb/storage: storage is read-only")
+)
type fileLock interface {
release() error
@@ -30,26 +33,27 @@
}
func (lock *fileStorageLock) Release() {
- fs := lock.fs
- fs.mu.Lock()
- defer fs.mu.Unlock()
- if fs.slock == lock {
- fs.slock = nil
+ if lock.fs != nil {
+ lock.fs.mu.Lock()
+ defer lock.fs.mu.Unlock()
+ if lock.fs.slock == lock {
+ lock.fs.slock = nil
+ }
}
- return
}
const logSizeThreshold = 1024 * 1024 // 1 MiB
// fileStorage is a file-system backed storage.
type fileStorage struct {
- path string
+ path string
+ readOnly bool
mu sync.Mutex
flock fileLock
slock *fileStorageLock
logw *os.File
- logSize int
+ logSize int64
buf []byte
// Opened file counter; if open < 0 means closed.
open int
@@ -61,12 +65,20 @@
// same path will fail.
//
// The storage must be closed after use, by calling Close method.
-func OpenFile(path string) (Storage, error) {
- if err := os.MkdirAll(path, 0755); err != nil {
+func OpenFile(path string, readOnly bool) (Storage, error) {
+ if fi, err := os.Stat(path); err == nil {
+ if !fi.IsDir() {
+ return nil, fmt.Errorf("leveldb/storage: open %s: not a directory", path)
+ }
+ } else if os.IsNotExist(err) && !readOnly {
+ if err := os.MkdirAll(path, 0755); err != nil {
+ return nil, err
+ }
+ } else {
return nil, err
}
- flock, err := newFileLock(filepath.Join(path, "LOCK"))
+ flock, err := newFileLock(filepath.Join(path, "LOCK"), readOnly)
if err != nil {
return nil, err
}
@@ -77,17 +89,29 @@
}
}()
- logw, err := os.OpenFile(filepath.Join(path, "LOG"), os.O_WRONLY|os.O_CREATE, 0644)
- if err != nil {
- return nil, err
- }
- logSize, err := logw.Seek(0, os.SEEK_END)
- if err != nil {
- logw.Close()
- return nil, err
+ var (
+ logw *os.File
+ logSize int64
+ )
+ if !readOnly {
+ logw, err = os.OpenFile(filepath.Join(path, "LOG"), os.O_WRONLY|os.O_CREATE, 0644)
+ if err != nil {
+ return nil, err
+ }
+ logSize, err = logw.Seek(0, os.SEEK_END)
+ if err != nil {
+ logw.Close()
+ return nil, err
+ }
}
- fs := &fileStorage{path: path, flock: flock, logw: logw, logSize: int(logSize)}
+ fs := &fileStorage{
+ path: path,
+ readOnly: readOnly,
+ flock: flock,
+ logw: logw,
+ logSize: logSize,
+ }
runtime.SetFinalizer(fs, (*fileStorage).Close)
return fs, nil
}
@@ -98,6 +122,9 @@
if fs.open < 0 {
return nil, ErrClosed
}
+ if fs.readOnly {
+ return &fileStorageLock{}, nil
+ }
if fs.slock != nil {
return nil, ErrLocked
}
@@ -106,7 +133,7 @@
}
func itoa(buf []byte, i int, wid int) []byte {
- var u uint = uint(i)
+ u := uint(i)
if u == 0 && wid <= 1 {
return append(buf, '0')
}
@@ -166,23 +193,30 @@
}
func (fs *fileStorage) Log(str string) {
- t := time.Now()
- fs.mu.Lock()
- defer fs.mu.Unlock()
- if fs.open < 0 {
- return
+ if !fs.readOnly {
+ t := time.Now()
+ fs.mu.Lock()
+ defer fs.mu.Unlock()
+ if fs.open < 0 {
+ return
+ }
+ fs.doLog(t, str)
}
- fs.doLog(t, str)
}
func (fs *fileStorage) log(str string) {
- fs.doLog(time.Now(), str)
+ if !fs.readOnly {
+ fs.doLog(time.Now(), str)
+ }
}
func (fs *fileStorage) SetMeta(fd FileDesc) (err error) {
if !FileDescOk(fd) {
return ErrInvalidFile
}
+ if fs.readOnly {
+ return errReadOnly
+ }
fs.mu.Lock()
defer fs.mu.Unlock()
@@ -297,18 +331,20 @@
}
return
}
- // Rename pending CURRENT file to an effective CURRENT.
- if pend {
- path := fmt.Sprintf("%s.%d", filepath.Join(fs.path, "CURRENT"), fd.Num)
- if err := rename(path, filepath.Join(fs.path, "CURRENT")); err != nil {
- fs.log(fmt.Sprintf("CURRENT.%d -> CURRENT: %v", fd.Num, err))
+ if !fs.readOnly {
+ // Rename pending CURRENT file to an effective CURRENT.
+ if pend {
+ path := fmt.Sprintf("%s.%d", filepath.Join(fs.path, "CURRENT"), fd.Num)
+ if err := rename(path, filepath.Join(fs.path, "CURRENT")); err != nil {
+ fs.log(fmt.Sprintf("CURRENT.%d -> CURRENT: %v", fd.Num, err))
+ }
}
- }
- // Remove obsolete or incomplete pending CURRENT files.
- for _, name := range rem {
- path := filepath.Join(fs.path, name)
- if err := os.Remove(path); err != nil {
- fs.log(fmt.Sprintf("remove %s: %v", name, err))
+ // Remove obsolete or incomplete pending CURRENT files.
+ for _, name := range rem {
+ path := filepath.Join(fs.path, name)
+ if err := os.Remove(path); err != nil {
+ fs.log(fmt.Sprintf("remove %s: %v", name, err))
+ }
}
}
return
@@ -368,6 +404,9 @@
if !FileDescOk(fd) {
return nil, ErrInvalidFile
}
+ if fs.readOnly {
+ return nil, errReadOnly
+ }
fs.mu.Lock()
defer fs.mu.Unlock()
@@ -386,6 +425,9 @@
if !FileDescOk(fd) {
return ErrInvalidFile
}
+ if fs.readOnly {
+ return errReadOnly
+ }
fs.mu.Lock()
defer fs.mu.Unlock()
@@ -413,6 +455,9 @@
if oldfd == newfd {
return nil
}
+ if fs.readOnly {
+ return errReadOnly
+ }
fs.mu.Lock()
defer fs.mu.Unlock()
diff --git a/leveldb/storage/file_storage_plan9.go b/leveldb/storage/file_storage_plan9.go
index 42940d7..bab62bf 100644
--- a/leveldb/storage/file_storage_plan9.go
+++ b/leveldb/storage/file_storage_plan9.go
@@ -19,8 +19,21 @@
return fl.f.Close()
}
-func newFileLock(path string) (fl fileLock, err error) {
- f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, os.ModeExclusive|0644)
+func newFileLock(path string, readOnly bool) (fl fileLock, err error) {
+ var (
+ flag int
+ perm os.FileMode
+ )
+ if readOnly {
+ flag = os.O_RDONLY
+ } else {
+ flag = os.O_RDWR
+ perm = os.ModeExclusive
+ }
+ f, err := os.OpenFile(path, flag, perm)
+ if os.IsNotExist(err) {
+ f, err = os.OpenFile(path, flag|os.O_CREATE, perm|0644)
+ }
if err != nil {
return
}
diff --git a/leveldb/storage/file_storage_solaris.go b/leveldb/storage/file_storage_solaris.go
index 102031b..79901ee 100644
--- a/leveldb/storage/file_storage_solaris.go
+++ b/leveldb/storage/file_storage_solaris.go
@@ -18,18 +18,27 @@
}
func (fl *unixFileLock) release() error {
- if err := setFileLock(fl.f, false); err != nil {
+ if err := setFileLock(fl.f, false, false); err != nil {
return err
}
return fl.f.Close()
}
-func newFileLock(path string) (fl fileLock, err error) {
- f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
+func newFileLock(path string, readOnly bool) (fl fileLock, err error) {
+ var flag int
+ if readOnly {
+ flag = os.O_RDONLY
+ } else {
+ flag = os.O_RDWR
+ }
+ f, err := os.OpenFile(path, flag, 0)
+ if os.IsNotExist(err) {
+ f, err = os.OpenFile(path, flag|os.O_CREATE, 0644)
+ }
if err != nil {
return
}
- err = setFileLock(f, true)
+ err = setFileLock(f, readOnly, true)
if err != nil {
f.Close()
return
@@ -38,7 +47,7 @@
return
}
-func setFileLock(f *os.File, lock bool) error {
+func setFileLock(f *os.File, readOnly, lock bool) error {
flock := syscall.Flock_t{
Type: syscall.F_UNLCK,
Start: 0,
@@ -46,7 +55,11 @@
Whence: 1,
}
if lock {
- flock.Type = syscall.F_WRLCK
+ if readOnly {
+ flock.Type = syscall.F_RDLCK
+ } else {
+ flock.Type = syscall.F_WRLCK
+ }
}
return syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &flock)
}
diff --git a/leveldb/storage/file_storage_test.go b/leveldb/storage/file_storage_test.go
index f88011c..7a77f28 100644
--- a/leveldb/storage/file_storage_test.go
+++ b/leveldb/storage/file_storage_test.go
@@ -88,24 +88,18 @@
}
func TestFileStorage_Locking(t *testing.T) {
- path := filepath.Join(os.TempDir(), fmt.Sprintf("goleveldbtestfd-%d", os.Getuid()))
-
- _, err := os.Stat(path)
- if err == nil {
- err = os.RemoveAll(path)
- if err != nil {
- t.Fatal("RemoveAll: got error: ", err)
- }
+ path := filepath.Join(os.TempDir(), fmt.Sprintf("goleveldb-testrwlock-%d", os.Getuid()))
+ if err := os.RemoveAll(path); err != nil && !os.IsNotExist(err) {
+ t.Fatal("RemoveAll: got error: ", err)
}
+ defer os.RemoveAll(path)
- p1, err := OpenFile(path)
+ p1, err := OpenFile(path, false)
if err != nil {
t.Fatal("OpenFile(1): got error: ", err)
}
- defer os.RemoveAll(path)
-
- p2, err := OpenFile(path)
+ p2, err := OpenFile(path, false)
if err != nil {
t.Logf("OpenFile(2): got error: %s (expected)", err)
} else {
@@ -116,7 +110,7 @@
p1.Close()
- p3, err := OpenFile(path)
+ p3, err := OpenFile(path, false)
if err != nil {
t.Fatal("OpenFile(3): got error: ", err)
}
@@ -138,3 +132,45 @@
t.Fatal("storage lock failed(2): ", err)
}
}
+
+func TestFileStorage_ReadOnlyLocking(t *testing.T) {
+ path := filepath.Join(os.TempDir(), fmt.Sprintf("goleveldb-testrolock-%d", os.Getuid()))
+ if err := os.RemoveAll(path); err != nil && !os.IsNotExist(err) {
+ t.Fatal("RemoveAll: got error: ", err)
+ }
+ defer os.RemoveAll(path)
+
+ p1, err := OpenFile(path, false)
+ if err != nil {
+ t.Fatal("OpenFile(1): got error: ", err)
+ }
+
+ _, err = OpenFile(path, true)
+ if err != nil {
+ t.Logf("OpenFile(2): got error: %s (expected)", err)
+ } else {
+ t.Fatal("OpenFile(2): expect error")
+ }
+
+ p1.Close()
+
+ p3, err := OpenFile(path, true)
+ if err != nil {
+ t.Fatal("OpenFile(3): got error: ", err)
+ }
+
+ p4, err := OpenFile(path, true)
+ if err != nil {
+ t.Fatal("OpenFile(4): got error: ", err)
+ }
+
+ _, err = OpenFile(path, false)
+ if err != nil {
+ t.Logf("OpenFile(5): got error: %s (expected)", err)
+ } else {
+ t.Fatal("OpenFile(2): expect error")
+ }
+
+ p3.Close()
+ p4.Close()
+}
diff --git a/leveldb/storage/file_storage_unix.go b/leveldb/storage/file_storage_unix.go
index 6eb3274..7e29915 100644
--- a/leveldb/storage/file_storage_unix.go
+++ b/leveldb/storage/file_storage_unix.go
@@ -18,18 +18,27 @@
}
func (fl *unixFileLock) release() error {
- if err := setFileLock(fl.f, false); err != nil {
+ if err := setFileLock(fl.f, false, false); err != nil {
return err
}
return fl.f.Close()
}
-func newFileLock(path string) (fl fileLock, err error) {
- f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
+func newFileLock(path string, readOnly bool) (fl fileLock, err error) {
+ var flag int
+ if readOnly {
+ flag = os.O_RDONLY
+ } else {
+ flag = os.O_RDWR
+ }
+ f, err := os.OpenFile(path, flag, 0)
+ if os.IsNotExist(err) {
+ f, err = os.OpenFile(path, flag|os.O_CREATE, 0644)
+ }
if err != nil {
return
}
- err = setFileLock(f, true)
+ err = setFileLock(f, readOnly, true)
if err != nil {
f.Close()
return
@@ -38,10 +47,14 @@
return
}
-func setFileLock(f *os.File, lock bool) error {
+func setFileLock(f *os.File, readOnly, lock bool) error {
how := syscall.LOCK_UN
if lock {
- how = syscall.LOCK_EX
+ if readOnly {
+ how = syscall.LOCK_SH
+ } else {
+ how = syscall.LOCK_EX
+ }
}
return syscall.Flock(int(f.Fd()), how|syscall.LOCK_NB)
}
diff --git a/leveldb/storage/file_storage_windows.go b/leveldb/storage/file_storage_windows.go
index 50c3c45..899335f 100644
--- a/leveldb/storage/file_storage_windows.go
+++ b/leveldb/storage/file_storage_windows.go
@@ -29,12 +29,22 @@
return syscall.Close(fl.fd)
}
-func newFileLock(path string) (fl fileLock, err error) {
+func newFileLock(path string, readOnly bool) (fl fileLock, err error) {
pathp, err := syscall.UTF16PtrFromString(path)
if err != nil {
return
}
- fd, err := syscall.CreateFile(pathp, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.CREATE_ALWAYS, syscall.FILE_ATTRIBUTE_NORMAL, 0)
+ var access, shareMode uint32
+ if readOnly {
+ access = syscall.GENERIC_READ
+ shareMode = syscall.FILE_SHARE_READ
+ } else {
+ access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
+ }
+ fd, err := syscall.CreateFile(pathp, access, shareMode, nil, syscall.OPEN_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0)
+ if err == syscall.ERROR_FILE_NOT_FOUND {
+ fd, err = syscall.CreateFile(pathp, access, shareMode, nil, syscall.OPEN_ALWAYS, syscall.FILE_ATTRIBUTE_NORMAL, 0)
+ }
if err != nil {
return
}
@@ -47,9 +57,8 @@
if r1 == 0 {
if e1 != 0 {
return error(e1)
- } else {
- return syscall.EINVAL
}
+ return syscall.EINVAL
}
return nil
}
diff --git a/leveldb/testutil/storage.go b/leveldb/testutil/storage.go
index f3e4456..1d9163e 100644
--- a/leveldb/testutil/storage.go
+++ b/leveldb/testutil/storage.go
@@ -669,7 +669,7 @@
storageMu.Unlock()
path = filepath.Join(os.TempDir(), fmt.Sprintf("goleveldb-test%d0%d0%d", os.Getuid(), os.Getpid(), num))
if _, err := os.Stat(path); os.IsNotExist(err) {
- stor, err = storage.OpenFile(path)
+ stor, err = storage.OpenFile(path, false)
ExpectWithOffset(1, err).NotTo(HaveOccurred(), "creating storage at %s", path)
break
}
diff --git a/manualtest/dbstress/main.go b/manualtest/dbstress/main.go
index 7fe639d..cf8466e 100644
--- a/manualtest/dbstress/main.go
+++ b/manualtest/dbstress/main.go
@@ -318,7 +318,7 @@
runtime.GOMAXPROCS(runtime.NumCPU())
os.RemoveAll(dbPath)
- stor, err := storage.OpenFile(dbPath)
+ stor, err := storage.OpenFile(dbPath, false)
if err != nil {
log.Fatal(err)
}
diff --git a/manualtest/filelock/main.go b/manualtest/filelock/main.go
index 3bed1f3..192951f 100644
--- a/manualtest/filelock/main.go
+++ b/manualtest/filelock/main.go
@@ -49,7 +49,7 @@
fmt.Println("Child flag set.")
}
- stor, err := storage.OpenFile(filename)
+ stor, err := storage.OpenFile(filename, false)
if err != nil {
fmt.Printf("Could not open storage: %s", err)
os.Exit(10)