diff --git a/NOTES b/NOTES deleted file mode 100644 index 967d3aa..0000000 --- a/NOTES +++ /dev/null @@ -1,17 +0,0 @@ - - - ===0=== - | d|g | - |1|2|3| - ======= - | | | - ------ | ------- - | | | -===1=== ===2=== ===3=== -|a|b|c| |d|e|f| |g|h|i| -|-|-|-| |-|-|-| |-|-|-| -|*|*|*| |*|*|*| |*|*|*| -|*|*|*| |*|*|*| |*|*|*| -|*|*|*| |*|*|*| |*|*|*| - - diff --git a/db.go b/db.go index ed6f176..035a310 100644 --- a/db.go +++ b/db.go @@ -19,11 +19,9 @@ const maxMmapStep = 1 << 30 // 1GB // All data access is performed through transactions which can be obtained through the DB. // All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called. type DB struct { - os _os - syscall _syscall path string - file file - metafile file + file *os.File + metafile *os.File data []byte meta0 *meta meta1 *meta @@ -60,15 +58,6 @@ func (db *DB) Open(path string, mode os.FileMode) error { db.metalock.Lock() defer db.metalock.Unlock() - // Initialize OS/Syscall references. - // These are overridden by mocks during some tests. - if db.os == nil { - db.os = &sysos{} - } - if db.syscall == nil { - db.syscall = &syssyscall{} - } - // Exit if the database is currently open. if db.opened { return ErrDatabaseOpen @@ -76,11 +65,11 @@ func (db *DB) Open(path string, mode os.FileMode) error { // Open data file and separate sync handler for metadata writes. db.path = path - if db.file, err = db.os.OpenFile(db.path, os.O_RDWR|os.O_CREATE, mode); err != nil { + if db.file, err = os.OpenFile(db.path, os.O_RDWR|os.O_CREATE, mode); err != nil { db.close() return err } - if db.metafile, err = db.os.OpenFile(db.path, os.O_RDWR|os.O_SYNC, mode); err != nil { + if db.metafile, err = os.OpenFile(db.path, os.O_RDWR|os.O_SYNC, mode); err != nil { db.close() return err } @@ -132,7 +121,9 @@ func (db *DB) mmap(minsz int) error { } // Unmap existing data before continuing. - db.munmap() + if err := db.munmap(); err != nil { + return err + } info, err := db.file.Stat() if err != nil { @@ -149,7 +140,7 @@ func (db *DB) mmap(minsz int) error { size = db.mmapSize(size) // Memory-map the data file as a byte slice. - if db.data, err = db.syscall.Mmap(int(db.file.Fd()), 0, size, syscall.PROT_READ, syscall.MAP_SHARED); err != nil { + if db.data, err = syscall.Mmap(int(db.file.Fd()), 0, size, syscall.PROT_READ, syscall.MAP_SHARED); err != nil { return err } @@ -169,13 +160,14 @@ func (db *DB) mmap(minsz int) error { } // munmap unmaps the data file from memory. -func (db *DB) munmap() { +func (db *DB) munmap() error { if db.data != nil { - if err := db.syscall.Munmap(db.data); err != nil { - panic("unmap error: " + err.Error()) + if err := syscall.Munmap(db.data); err != nil { + return fmt.Errorf("unmap error: " + err.Error()) } db.data = nil } + return nil } // mmapSize determines the appropriate size for the mmap given the current size @@ -200,7 +192,7 @@ func (db *DB) mmapSize(size int) int { // init creates a new database file and initializes its meta pages. func (db *DB) init() error { // Set the page size to the OS page size. - db.pageSize = db.os.Getpagesize() + db.pageSize = os.Getpagesize() // Create two meta pages on a buffer. buf := make([]byte, db.pageSize*4) @@ -243,20 +235,38 @@ func (db *DB) init() error { // Close releases all database resources. // All transactions must be closed before closing the database. -func (db *DB) Close() { +func (db *DB) Close() error { db.metalock.Lock() defer db.metalock.Unlock() - db.close() + return db.close() } -func (db *DB) close() { +func (db *DB) close() error { db.opened = false - // TODO(benbjohnson): Undo everything in Open(). db.freelist = nil db.path = "" - db.munmap() + // Close the mmap. + if err := db.munmap(); err != nil { + return err + } + + // Close file handles. + if db.file != nil { + if err := db.file.Close(); err != nil { + return fmt.Errorf("db file close error: %s", err) + } + db.file = nil + } + if db.metafile != nil { + if err := db.metafile.Close(); err != nil { + return fmt.Errorf("db metafile close error: %s", err) + } + db.metafile = nil + } + + return nil } // Tx creates a read-only transaction. diff --git a/db_test.go b/db_test.go index d7db679..04abd75 100644 --- a/db_test.go +++ b/db_test.go @@ -1,19 +1,14 @@ package bolt import ( - "io" "io/ioutil" "math/rand" "os" "strconv" "strings" - "syscall" "testing" - "time" - "unsafe" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) // Ensure that a database can be opened without error. @@ -55,106 +50,6 @@ func TestDBReopen(t *testing.T) { }) } -// Ensure that the database returns an error if the file handle cannot be open. -func TestDBOpenFileError(t *testing.T) { - withMockDB(func(db *DB, mockos *mockos, mocksyscall *mocksyscall, path string) { - exp := &os.PathError{} - mockos.On("OpenFile", path, os.O_RDWR|os.O_CREATE, os.FileMode(0666)).Return((*mockfile)(nil), exp) - err := db.Open(path, 0666) - assert.Equal(t, err, exp) - }) -} - -// Ensure that the database returns an error if the meta file handle cannot be open. -func TestDBOpenMetaFileError(t *testing.T) { - withMockDB(func(db *DB, mockos *mockos, mocksyscall *mocksyscall, path string) { - exp := &os.PathError{} - mockos.On("OpenFile", path, os.O_RDWR|os.O_CREATE, os.FileMode(0666)).Return(&mockfile{}, nil) - mockos.On("OpenFile", path, os.O_RDWR|os.O_SYNC, os.FileMode(0666)).Return((*mockfile)(nil), exp) - err := db.Open(path, 0666) - assert.Equal(t, err, exp) - }) -} - -// Ensure that write errors to the meta file handler during initialization are returned. -func TestDBMetaInitWriteError(t *testing.T) { - withMockDB(func(db *DB, mockos *mockos, mocksyscall *mocksyscall, path string) { - // Mock the file system. - file, metafile := &mockfile{}, &mockfile{} - mockos.On("OpenFile", path, os.O_RDWR|os.O_CREATE, os.FileMode(0666)).Return(file, nil) - mockos.On("OpenFile", path, os.O_RDWR|os.O_SYNC, os.FileMode(0666)).Return(metafile, nil) - mockos.On("Getpagesize").Return(0x10000) - file.On("Stat").Return(&mockfileinfo{"", 0, 0666, time.Now(), false, nil}, nil) - metafile.On("WriteAt", mock.Anything, int64(0)).Return(0, io.ErrShortWrite) - - // Open the database. - err := db.Open(path, 0666) - assert.Equal(t, err, io.ErrShortWrite) - }) -} - -// Ensure that a database that is too small returns an error. -func TestDBFileTooSmall(t *testing.T) { - withMockDB(func(db *DB, mockos *mockos, mocksyscall *mocksyscall, path string) { - file, metafile := &mockfile{}, &mockfile{} - mockos.On("OpenFile", path, os.O_RDWR|os.O_CREATE, os.FileMode(0666)).Return(file, nil) - mockos.On("OpenFile", path, os.O_RDWR|os.O_SYNC, os.FileMode(0666)).Return(metafile, nil) - mockos.On("Getpagesize").Return(0x1000) - file.On("Stat").Return(&mockfileinfo{"", 0, 0666, time.Now(), false, nil}, nil) - metafile.On("WriteAt", mock.Anything, int64(0)).Return(0, nil) - err := db.Open(path, 0666) - assert.Equal(t, err, &Error{"file size too small", nil}) - }) -} - -// Ensure that stat errors during mmap get returned. -func TestDBMmapStatError(t *testing.T) { - withMockDB(func(db *DB, mockos *mockos, mocksyscall *mocksyscall, path string) { - exp := &os.PathError{} - file, metafile := &mockfile{}, &mockfile{} - mockos.On("OpenFile", path, os.O_RDWR|os.O_CREATE, os.FileMode(0666)).Return(file, nil) - mockos.On("OpenFile", path, os.O_RDWR|os.O_SYNC, os.FileMode(0666)).Return(metafile, nil) - mockos.On("Getpagesize").Return(0x1000) - file.On("ReadAt", mock.Anything, int64(0)).Return(0, nil) - file.On("Stat").Return((*mockfileinfo)(nil), exp) - metafile.On("WriteAt", mock.Anything, int64(0)).Return(0, nil) - err := db.Open(path, 0666) - assert.Equal(t, err, &Error{"stat error", exp}) - }) -} - -// Ensure that corrupt meta0 page errors get returned. -func TestDBCorruptMeta0(t *testing.T) { - withMockDB(func(db *DB, mockos *mockos, mocksyscall *mocksyscall, path string) { - var m meta - m.magic = magic - m.version = version - m.pageSize = 0x8000 - - // Create a file with bad magic. - b := make([]byte, 0x10000) - p0, p1 := (*page)(unsafe.Pointer(&b[0x0000])), (*page)(unsafe.Pointer(&b[0x8000])) - p0.meta().magic = 0 - p0.meta().version = version - p1.meta().magic = magic - p1.meta().version = version - - // Mock file access. - file, metafile := &mockfile{}, &mockfile{} - mockos.On("OpenFile", path, os.O_RDWR|os.O_CREATE, os.FileMode(0666)).Return(file, nil) - mockos.On("OpenFile", path, os.O_RDWR|os.O_SYNC, os.FileMode(0666)).Return(metafile, nil) - mockos.On("Getpagesize").Return(0x10000) - file.On("ReadAt", mock.Anything, int64(0)).Return(0, nil) - file.On("Stat").Return(&mockfileinfo{"", 0x10000, 0666, time.Now(), false, nil}, nil) - metafile.On("WriteAt", mock.Anything, int64(0)).Return(0, nil) - mocksyscall.On("Mmap", 0, int64(0), 0x10000, syscall.PROT_READ, syscall.MAP_SHARED).Return(b, nil) - - // Open the database. - err := db.Open(path, 0666) - assert.Equal(t, err, &Error{"meta error", ErrInvalid}) - }) -} - // Ensure that a database cannot open a transaction when it's not open. func TestDBTxErrDatabaseNotOpen(t *testing.T) { withDB(func(db *DB, path string) { @@ -357,15 +252,6 @@ func withDB(fn func(*DB, string)) { fn(&db, path) } -// withMockDB executes a function with a database reference and a mock filesystem. -func withMockDB(fn func(*DB, *mockos, *mocksyscall, string)) { - os, syscall := &mockos{}, &mocksyscall{} - var db DB - db.os = os - db.syscall = syscall - fn(&db, os, syscall, "/mock/db") -} - // withOpenDB executes a function with an already opened database. func withOpenDB(fn func(*DB, string)) { withDB(func(db *DB, path string) { diff --git a/os.go b/os.go deleted file mode 100644 index 47515eb..0000000 --- a/os.go +++ /dev/null @@ -1,27 +0,0 @@ -package bolt - -import ( - "os" -) - -type _os interface { - OpenFile(name string, flag int, perm os.FileMode) (file file, err error) - Getpagesize() int -} - -type file interface { - Fd() uintptr - ReadAt(b []byte, off int64) (n int, err error) - Stat() (fi os.FileInfo, err error) - WriteAt(b []byte, off int64) (n int, err error) -} - -type sysos struct{} - -func (o *sysos) OpenFile(name string, flag int, perm os.FileMode) (file file, err error) { - return os.OpenFile(name, flag, perm) -} - -func (o *sysos) Getpagesize() int { - return os.Getpagesize() -} diff --git a/os_test.go b/os_test.go deleted file mode 100644 index bb28352..0000000 --- a/os_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package bolt - -import ( - "os" - "time" - - "github.com/stretchr/testify/mock" -) - -type mockos struct { - mock.Mock -} - -func (m *mockos) OpenFile(name string, flag int, perm os.FileMode) (file file, err error) { - args := m.Called(name, flag, perm) - return args.Get(0).(*mockfile), args.Error(1) -} - -func (m *mockos) Stat(name string) (fi os.FileInfo, err error) { - args := m.Called(name) - return args.Get(0).(os.FileInfo), args.Error(1) -} - -func (m *mockos) Getpagesize() int { - args := m.Called() - return args.Int(0) -} - -type mockfile struct { - mock.Mock - fd uintptr -} - -func (m *mockfile) Fd() uintptr { - return m.fd -} - -func (m *mockfile) ReadAt(b []byte, off int64) (n int, err error) { - args := m.Called(b, off) - return args.Int(0), args.Error(1) -} - -func (m *mockfile) Stat() (os.FileInfo, error) { - args := m.Called() - return args.Get(0).(os.FileInfo), args.Error(1) -} - -func (m *mockfile) WriteAt(b []byte, off int64) (n int, err error) { - args := m.Called(b, off) - return args.Int(0), args.Error(1) -} - -type mockfileinfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time - isDir bool - sys interface{} -} - -func (m *mockfileinfo) Name() string { - return m.name -} - -func (m *mockfileinfo) Size() int64 { - return m.size -} - -func (m *mockfileinfo) Mode() os.FileMode { - return m.mode -} - -func (m *mockfileinfo) ModTime() time.Time { - return m.modTime -} - -func (m *mockfileinfo) IsDir() bool { - return m.isDir -} - -func (m *mockfileinfo) Sys() interface{} { - return m.sys -} diff --git a/syscall_darwin.go b/syscall_darwin.go deleted file mode 100644 index cb9a20c..0000000 --- a/syscall_darwin.go +++ /dev/null @@ -1,20 +0,0 @@ -package bolt - -import ( - "syscall" -) - -type _syscall interface { - Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) - Munmap([]byte) error -} - -type syssyscall struct{} - -func (o *syssyscall) Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) { - return syscall.Mmap(fd, offset, length, prot, flags) -} - -func (o *syssyscall) Munmap(b []byte) error { - return syscall.Munmap(b) -} diff --git a/syscall_darwin_test.go b/syscall_darwin_test.go deleted file mode 100644 index 6b468f6..0000000 --- a/syscall_darwin_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package bolt - -import ( - "github.com/stretchr/testify/mock" -) - -type mocksyscall struct { - mock.Mock -} - -func (m *mocksyscall) Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) { - args := m.Called(fd, offset, length, prot, flags) - return args.Get(0).([]byte), args.Error(1) -} - -func (m *mocksyscall) Munmap(b []byte) error { - args := m.Called(b) - return args.Error(0) -} diff --git a/syscall_linux.go b/syscall_linux.go deleted file mode 100644 index 65daad4..0000000 --- a/syscall_linux.go +++ /dev/null @@ -1,21 +0,0 @@ -package bolt - -import ( - "syscall" -) - -type _syscall interface { - Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) - Munmap([]byte) error -} - -type syssyscall struct{} - -func (o *syssyscall) Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) { - // err = (EACCES, EBADF, EINVAL, ENODEV, ENOMEM, ENXIO, EOVERFLOW) - return syscall.Mmap(fd, offset, length, prot, flags) -} - -func (o *syssyscall) Munmap(b []byte) error { - return syscall.Munmap(b) -} diff --git a/syscall_linux_test.go b/syscall_linux_test.go deleted file mode 100644 index 6b468f6..0000000 --- a/syscall_linux_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package bolt - -import ( - "github.com/stretchr/testify/mock" -) - -type mocksyscall struct { - mock.Mock -} - -func (m *mocksyscall) Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) { - args := m.Called(fd, offset, length, prot, flags) - return args.Get(0).([]byte), args.Error(1) -} - -func (m *mocksyscall) Munmap(b []byte) error { - args := m.Called(b) - return args.Error(0) -}