|
|
@@ -9,10 +9,12 @@ package storage
|
|
|
import (
|
|
|
"errors"
|
|
|
"fmt"
|
|
|
+ "io"
|
|
|
"io/ioutil"
|
|
|
"os"
|
|
|
"path/filepath"
|
|
|
"runtime"
|
|
|
+ "sort"
|
|
|
"strconv"
|
|
|
"strings"
|
|
|
"sync"
|
|
|
@@ -42,6 +44,30 @@ func (lock *fileStorageLock) Unlock() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+type int64Slice []int64
|
|
|
+
|
|
|
+func (p int64Slice) Len() int { return len(p) }
|
|
|
+func (p int64Slice) Less(i, j int) bool { return p[i] < p[j] }
|
|
|
+func (p int64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
|
+
|
|
|
+func writeFileSynced(filename string, data []byte, perm os.FileMode) error {
|
|
|
+ f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ n, err := f.Write(data)
|
|
|
+ if err == nil && n < len(data) {
|
|
|
+ err = io.ErrShortWrite
|
|
|
+ }
|
|
|
+ if err1 := f.Sync(); err == nil {
|
|
|
+ err = err1
|
|
|
+ }
|
|
|
+ if err1 := f.Close(); err == nil {
|
|
|
+ err = err1
|
|
|
+ }
|
|
|
+ return err
|
|
|
+}
|
|
|
+
|
|
|
const logSizeThreshold = 1024 * 1024 // 1 MiB
|
|
|
|
|
|
// fileStorage is a file-system backed storage.
|
|
|
@@ -60,7 +86,7 @@ type fileStorage struct {
|
|
|
day int
|
|
|
}
|
|
|
|
|
|
-// OpenFile returns a new filesytem-backed storage implementation with the given
|
|
|
+// OpenFile returns a new filesystem-backed storage implementation with the given
|
|
|
// path. This also acquire a file lock, so any subsequent attempt to open the
|
|
|
// same path will fail.
|
|
|
//
|
|
|
@@ -189,7 +215,8 @@ func (fs *fileStorage) doLog(t time.Time, str string) {
|
|
|
// write
|
|
|
fs.buf = append(fs.buf, []byte(str)...)
|
|
|
fs.buf = append(fs.buf, '\n')
|
|
|
- fs.logw.Write(fs.buf)
|
|
|
+ n, _ := fs.logw.Write(fs.buf)
|
|
|
+ fs.logSize += int64(n)
|
|
|
}
|
|
|
|
|
|
func (fs *fileStorage) Log(str string) {
|
|
|
@@ -210,7 +237,46 @@ func (fs *fileStorage) log(str string) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (fs *fileStorage) SetMeta(fd FileDesc) (err error) {
|
|
|
+func (fs *fileStorage) setMeta(fd FileDesc) error {
|
|
|
+ content := fsGenName(fd) + "\n"
|
|
|
+ // Check and backup old CURRENT file.
|
|
|
+ currentPath := filepath.Join(fs.path, "CURRENT")
|
|
|
+ if _, err := os.Stat(currentPath); err == nil {
|
|
|
+ b, err := ioutil.ReadFile(currentPath)
|
|
|
+ if err != nil {
|
|
|
+ fs.log(fmt.Sprintf("backup CURRENT: %v", err))
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if string(b) == content {
|
|
|
+ // Content not changed, do nothing.
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if err := writeFileSynced(currentPath+".bak", b, 0644); err != nil {
|
|
|
+ fs.log(fmt.Sprintf("backup CURRENT: %v", err))
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ } else if !os.IsNotExist(err) {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ path := fmt.Sprintf("%s.%d", filepath.Join(fs.path, "CURRENT"), fd.Num)
|
|
|
+ if err := writeFileSynced(path, []byte(content), 0644); err != nil {
|
|
|
+ fs.log(fmt.Sprintf("create CURRENT.%d: %v", fd.Num, err))
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ // Replace CURRENT file.
|
|
|
+ if err := rename(path, currentPath); err != nil {
|
|
|
+ fs.log(fmt.Sprintf("rename CURRENT.%d: %v", fd.Num, err))
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ // Sync root directory.
|
|
|
+ if err := syncDir(fs.path); err != nil {
|
|
|
+ fs.log(fmt.Sprintf("syncDir: %v", err))
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (fs *fileStorage) SetMeta(fd FileDesc) error {
|
|
|
if !FileDescOk(fd) {
|
|
|
return ErrInvalidFile
|
|
|
}
|
|
|
@@ -223,44 +289,10 @@ func (fs *fileStorage) SetMeta(fd FileDesc) (err error) {
|
|
|
if fs.open < 0 {
|
|
|
return ErrClosed
|
|
|
}
|
|
|
- defer func() {
|
|
|
- if err != nil {
|
|
|
- fs.log(fmt.Sprintf("CURRENT: %v", err))
|
|
|
- }
|
|
|
- }()
|
|
|
- path := fmt.Sprintf("%s.%d", filepath.Join(fs.path, "CURRENT"), fd.Num)
|
|
|
- w, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
|
|
- if err != nil {
|
|
|
- return
|
|
|
- }
|
|
|
- _, err = fmt.Fprintln(w, fsGenName(fd))
|
|
|
- if err != nil {
|
|
|
- fs.log(fmt.Sprintf("write CURRENT.%d: %v", fd.Num, err))
|
|
|
- return
|
|
|
- }
|
|
|
- if err = w.Sync(); err != nil {
|
|
|
- fs.log(fmt.Sprintf("flush CURRENT.%d: %v", fd.Num, err))
|
|
|
- return
|
|
|
- }
|
|
|
- if err = w.Close(); err != nil {
|
|
|
- fs.log(fmt.Sprintf("close CURRENT.%d: %v", fd.Num, err))
|
|
|
- return
|
|
|
- }
|
|
|
- if err != nil {
|
|
|
- return
|
|
|
- }
|
|
|
- if err = rename(path, filepath.Join(fs.path, "CURRENT")); err != nil {
|
|
|
- fs.log(fmt.Sprintf("rename CURRENT.%d: %v", fd.Num, err))
|
|
|
- return
|
|
|
- }
|
|
|
- // Sync root directory.
|
|
|
- if err = syncDir(fs.path); err != nil {
|
|
|
- fs.log(fmt.Sprintf("syncDir: %v", err))
|
|
|
- }
|
|
|
- return
|
|
|
+ return fs.setMeta(fd)
|
|
|
}
|
|
|
|
|
|
-func (fs *fileStorage) GetMeta() (fd FileDesc, err error) {
|
|
|
+func (fs *fileStorage) GetMeta() (FileDesc, error) {
|
|
|
fs.mu.Lock()
|
|
|
defer fs.mu.Unlock()
|
|
|
if fs.open < 0 {
|
|
|
@@ -268,7 +300,7 @@ func (fs *fileStorage) GetMeta() (fd FileDesc, err error) {
|
|
|
}
|
|
|
dir, err := os.Open(fs.path)
|
|
|
if err != nil {
|
|
|
- return
|
|
|
+ return FileDesc{}, err
|
|
|
}
|
|
|
names, err := dir.Readdirnames(0)
|
|
|
// Close the dir first before checking for Readdirnames error.
|
|
|
@@ -276,94 +308,134 @@ func (fs *fileStorage) GetMeta() (fd FileDesc, err error) {
|
|
|
fs.log(fmt.Sprintf("close dir: %v", ce))
|
|
|
}
|
|
|
if err != nil {
|
|
|
- return
|
|
|
- }
|
|
|
- // Find latest CURRENT file.
|
|
|
- var rem []string
|
|
|
- var pend bool
|
|
|
- var cerr error
|
|
|
- for _, name := range names {
|
|
|
- if strings.HasPrefix(name, "CURRENT") {
|
|
|
- pend1 := len(name) > 7
|
|
|
- var pendNum int64
|
|
|
- // Make sure it is valid name for a CURRENT file, otherwise skip it.
|
|
|
- if pend1 {
|
|
|
- if name[7] != '.' || len(name) < 9 {
|
|
|
- fs.log(fmt.Sprintf("skipping %s: invalid file name", name))
|
|
|
- continue
|
|
|
- }
|
|
|
- var e1 error
|
|
|
- if pendNum, e1 = strconv.ParseInt(name[8:], 10, 0); e1 != nil {
|
|
|
- fs.log(fmt.Sprintf("skipping %s: invalid file num: %v", name, e1))
|
|
|
- continue
|
|
|
- }
|
|
|
+ return FileDesc{}, err
|
|
|
+ }
|
|
|
+ // Try this in order:
|
|
|
+ // - CURRENT.[0-9]+ ('pending rename' file, descending order)
|
|
|
+ // - CURRENT
|
|
|
+ // - CURRENT.bak
|
|
|
+ //
|
|
|
+ // Skip corrupted file or file that point to a missing target file.
|
|
|
+ type currentFile struct {
|
|
|
+ name string
|
|
|
+ fd FileDesc
|
|
|
+ }
|
|
|
+ tryCurrent := func(name string) (*currentFile, error) {
|
|
|
+ b, err := ioutil.ReadFile(filepath.Join(fs.path, name))
|
|
|
+ if err != nil {
|
|
|
+ if os.IsNotExist(err) {
|
|
|
+ err = os.ErrNotExist
|
|
|
}
|
|
|
- path := filepath.Join(fs.path, name)
|
|
|
- r, e1 := os.OpenFile(path, os.O_RDONLY, 0)
|
|
|
- if e1 != nil {
|
|
|
- return FileDesc{}, e1
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ var fd FileDesc
|
|
|
+ if len(b) < 1 || b[len(b)-1] != '\n' || !fsParseNamePtr(string(b[:len(b)-1]), &fd) {
|
|
|
+ fs.log(fmt.Sprintf("%s: corrupted content: %q", name, b))
|
|
|
+ err := &ErrCorrupted{
|
|
|
+ Err: errors.New("leveldb/storage: corrupted or incomplete CURRENT file"),
|
|
|
}
|
|
|
- b, e1 := ioutil.ReadAll(r)
|
|
|
- if e1 != nil {
|
|
|
- r.Close()
|
|
|
- return FileDesc{}, e1
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if _, err := os.Stat(filepath.Join(fs.path, fsGenName(fd))); err != nil {
|
|
|
+ if os.IsNotExist(err) {
|
|
|
+ fs.log(fmt.Sprintf("%s: missing target file: %s", name, fd))
|
|
|
+ err = os.ErrNotExist
|
|
|
}
|
|
|
- var fd1 FileDesc
|
|
|
- if len(b) < 1 || b[len(b)-1] != '\n' || !fsParseNamePtr(string(b[:len(b)-1]), &fd1) {
|
|
|
- fs.log(fmt.Sprintf("skipping %s: corrupted or incomplete", name))
|
|
|
- if pend1 {
|
|
|
- rem = append(rem, name)
|
|
|
- }
|
|
|
- if !pend1 || cerr == nil {
|
|
|
- metaFd, _ := fsParseName(name)
|
|
|
- cerr = &ErrCorrupted{
|
|
|
- Fd: metaFd,
|
|
|
- Err: errors.New("leveldb/storage: corrupted or incomplete meta file"),
|
|
|
- }
|
|
|
- }
|
|
|
- } else if pend1 && pendNum != fd1.Num {
|
|
|
- fs.log(fmt.Sprintf("skipping %s: inconsistent pending-file num: %d vs %d", name, pendNum, fd1.Num))
|
|
|
- rem = append(rem, name)
|
|
|
- } else if fd1.Num < fd.Num {
|
|
|
- fs.log(fmt.Sprintf("skipping %s: obsolete", name))
|
|
|
- if pend1 {
|
|
|
- rem = append(rem, name)
|
|
|
- }
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return ¤tFile{name: name, fd: fd}, nil
|
|
|
+ }
|
|
|
+ tryCurrents := func(names []string) (*currentFile, error) {
|
|
|
+ var (
|
|
|
+ cur *currentFile
|
|
|
+ // Last corruption error.
|
|
|
+ lastCerr error
|
|
|
+ )
|
|
|
+ for _, name := range names {
|
|
|
+ var err error
|
|
|
+ cur, err = tryCurrent(name)
|
|
|
+ if err == nil {
|
|
|
+ break
|
|
|
+ } else if err == os.ErrNotExist {
|
|
|
+ // Fallback to the next file.
|
|
|
+ } else if isCorrupted(err) {
|
|
|
+ lastCerr = err
|
|
|
+ // Fallback to the next file.
|
|
|
} else {
|
|
|
- fd = fd1
|
|
|
- pend = pend1
|
|
|
+ // In case the error is due to permission, etc.
|
|
|
+ return nil, err
|
|
|
}
|
|
|
- if err := r.Close(); err != nil {
|
|
|
- fs.log(fmt.Sprintf("close %s: %v", name, err))
|
|
|
+ }
|
|
|
+ if cur == nil {
|
|
|
+ err := os.ErrNotExist
|
|
|
+ if lastCerr != nil {
|
|
|
+ err = lastCerr
|
|
|
}
|
|
|
+ return nil, err
|
|
|
}
|
|
|
+ return cur, nil
|
|
|
}
|
|
|
- // Don't remove any files if there is no valid CURRENT file.
|
|
|
- if fd.Zero() {
|
|
|
- if cerr != nil {
|
|
|
- err = cerr
|
|
|
- } else {
|
|
|
- err = os.ErrNotExist
|
|
|
+
|
|
|
+ // Try 'pending rename' files.
|
|
|
+ var nums []int64
|
|
|
+ for _, name := range names {
|
|
|
+ if strings.HasPrefix(name, "CURRENT.") && name != "CURRENT.bak" {
|
|
|
+ i, err := strconv.ParseInt(name[8:], 10, 64)
|
|
|
+ if err == nil {
|
|
|
+ nums = append(nums, i)
|
|
|
+ }
|
|
|
}
|
|
|
- return
|
|
|
}
|
|
|
- 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))
|
|
|
- }
|
|
|
+ var (
|
|
|
+ pendCur *currentFile
|
|
|
+ pendErr = os.ErrNotExist
|
|
|
+ pendNames []string
|
|
|
+ )
|
|
|
+ if len(nums) > 0 {
|
|
|
+ sort.Sort(sort.Reverse(int64Slice(nums)))
|
|
|
+ pendNames = make([]string, len(nums))
|
|
|
+ for i, num := range nums {
|
|
|
+ pendNames[i] = fmt.Sprintf("CURRENT.%d", num)
|
|
|
}
|
|
|
- // 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))
|
|
|
+ pendCur, pendErr = tryCurrents(pendNames)
|
|
|
+ if pendErr != nil && pendErr != os.ErrNotExist && !isCorrupted(pendErr) {
|
|
|
+ return FileDesc{}, pendErr
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Try CURRENT and CURRENT.bak.
|
|
|
+ curCur, curErr := tryCurrents([]string{"CURRENT", "CURRENT.bak"})
|
|
|
+ if curErr != nil && curErr != os.ErrNotExist && !isCorrupted(curErr) {
|
|
|
+ return FileDesc{}, curErr
|
|
|
+ }
|
|
|
+
|
|
|
+ // pendCur takes precedence, but guards against obsolete pendCur.
|
|
|
+ if pendCur != nil && (curCur == nil || pendCur.fd.Num > curCur.fd.Num) {
|
|
|
+ curCur = pendCur
|
|
|
+ }
|
|
|
+
|
|
|
+ if curCur != nil {
|
|
|
+ // Restore CURRENT file to proper state.
|
|
|
+ if !fs.readOnly && (curCur.name != "CURRENT" || len(pendNames) != 0) {
|
|
|
+ // Ignore setMeta errors, however don't delete obsolete files if we
|
|
|
+ // catch error.
|
|
|
+ if err := fs.setMeta(curCur.fd); err == nil {
|
|
|
+ // Remove 'pending rename' files.
|
|
|
+ for _, name := range pendNames {
|
|
|
+ if err := os.Remove(filepath.Join(fs.path, name)); err != nil {
|
|
|
+ fs.log(fmt.Sprintf("remove %s: %v", name, err))
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+ return curCur.fd, nil
|
|
|
}
|
|
|
- return
|
|
|
+
|
|
|
+ // Nothing found.
|
|
|
+ if isCorrupted(pendErr) {
|
|
|
+ return FileDesc{}, pendErr
|
|
|
+ }
|
|
|
+ return FileDesc{}, curErr
|
|
|
}
|
|
|
|
|
|
func (fs *fileStorage) List(ft FileType) (fds []FileDesc, err error) {
|