summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fsck.go219
-rw-r--r--mount.go5
-rw-r--r--tests/fsck/fsck_test.go9
3 files changed, 135 insertions, 98 deletions
diff --git a/fsck.go b/fsck.go
index 671d5d0..6d9ca4a 100644
--- a/fsck.go
+++ b/fsck.go
@@ -3,23 +3,27 @@ package main
import (
"bytes"
"fmt"
- "log"
+ "io"
+ "io/ioutil"
"os"
+ "os/signal"
"path/filepath"
"strings"
"sync"
"syscall"
- "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/rfjakob/gocryptfs/internal/exitcodes"
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
+ "github.com/rfjakob/gocryptfs/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/internal/tlog"
)
type fsckObj struct {
rootNode *fusefrontend.RootNode
+ // mnt is the mountpoint of the temporary mount
+ mnt string
// List of corrupt files
corruptList []string
// List of skipped files
@@ -30,6 +34,8 @@ type fsckObj struct {
watchDone chan struct{}
// Inode numbers of hard-linked files (Nlink > 1) that we have already checked
seenInodes map[uint64]struct{}
+ // abort the running fsck operation? Checked in a few long-running loops.
+ abort bool
}
func runsAsRoot() bool {
@@ -48,6 +54,10 @@ func (ck *fsckObj) markSkipped(path string) {
ck.listLock.Unlock()
}
+func (ck *fsckObj) abs(relPath string) (absPath string) {
+ return filepath.Join(ck.mnt, relPath)
+}
+
// Watch for mitigated corruptions that occur during OpenDir()
func (ck *fsckObj) watchMitigatedCorruptionsOpenDir(path string) {
for {
@@ -62,40 +72,45 @@ func (ck *fsckObj) watchMitigatedCorruptionsOpenDir(path string) {
}
// Recursively check dir for corruption
-func (ck *fsckObj) dir(n *fusefrontend.Node) {
- path := n.Path()
- tlog.Debug.Printf("ck.dir %q\n")
- ck.xattrs(n)
+func (ck *fsckObj) dir(relPath string) {
+ tlog.Debug.Printf("ck.dir %q\n", relPath)
+ ck.xattrs(relPath)
// Run OpenDir and catch transparently mitigated corruptions
- go ck.watchMitigatedCorruptionsOpenDir(path)
- entries, errno := n.Readdir(nil)
+ go ck.watchMitigatedCorruptionsOpenDir(relPath)
+ f, err := os.Open(ck.abs(relPath))
ck.watchDone <- struct{}{}
- // Also catch non-mitigated corruptions
- if errno != 0 {
- fmt.Printf("fsck: error opening dir %q: %v\n", n, errno)
- if errno == syscall.EACCES && !runsAsRoot() {
- ck.markSkipped(path)
+ if err != nil {
+ fmt.Printf("fsck: error opening dir %q: %v\n", relPath, err)
+ if err == os.ErrPermission && !runsAsRoot() {
+ ck.markSkipped(relPath)
} else {
- ck.markCorrupt(path)
+ ck.markCorrupt(relPath)
}
return
}
- for entries.HasNext() {
- entry, errno := entries.Next()
- if errno != 0 {
- fmt.Printf("fsck: dirstream error: %v\n", errno)
- break
+ go ck.watchMitigatedCorruptionsOpenDir(relPath)
+ entries, err := f.Readdirnames(0)
+ ck.watchDone <- struct{}{}
+ if err != nil {
+ fmt.Printf("fsck: error reading dir %q: %v\n", relPath, err)
+ ck.markCorrupt(relPath)
+ return
+ }
+ for _, entry := range entries {
+ if ck.abort {
+ return
}
- if entry.Name == "." || entry.Name == ".." {
+ if entry == "." || entry == ".." {
continue
}
- tmp, errno := n.Lookup(nil, entry.Name, &fuse.EntryOut{})
- if errno != 0 {
- ck.markCorrupt(filepath.Join(path, entry.Name))
+ nextPath := filepath.Join(relPath, entry)
+ var st syscall.Stat_t
+ err := syscall.Lstat(ck.abs(nextPath), &st)
+ if err != nil {
+ ck.markCorrupt(filepath.Join(relPath, entry))
continue
}
- nextPath := tmp.Operations().(*fusefrontend.Node)
- filetype := entry.Mode & syscall.S_IFMT
+ filetype := st.Mode & syscall.S_IFMT
//fmt.Printf(" %q %x\n", entry.Name, entry.Mode)
switch filetype {
case syscall.S_IFDIR:
@@ -112,12 +127,11 @@ func (ck *fsckObj) dir(n *fusefrontend.Node) {
}
}
-func (ck *fsckObj) symlink(n *fusefrontend.Node) {
- _, errno := n.Readlink(nil)
- if errno != 0 {
- path := n.Path()
- ck.markCorrupt(path)
- fmt.Printf("fsck: error reading symlink %q: %v\n", path, errno)
+func (ck *fsckObj) symlink(relPath string) {
+ _, err := os.Readlink(ck.abs(relPath))
+ if err != nil {
+ ck.markCorrupt(relPath)
+ fmt.Printf("fsck: error reading symlink %q: %v\n", relPath, err)
}
}
@@ -135,55 +149,55 @@ func (ck *fsckObj) watchMitigatedCorruptionsRead(path string) {
}
// Check file for corruption
-func (ck *fsckObj) file(n *fusefrontend.Node) {
- path := n.Path()
- tlog.Debug.Printf("ck.file %q\n", path)
- var attr fuse.AttrOut
- errno := n.Getattr(nil, nil, &attr)
- if errno != 0 {
- ck.markCorrupt(path)
- fmt.Printf("fsck: error stating file %q: %v\n", path, errno)
+func (ck *fsckObj) file(relPath string) {
+ tlog.Debug.Printf("ck.file %q\n", relPath)
+ var st syscall.Stat_t
+ err := syscall.Lstat(ck.abs(relPath), &st)
+ if err != nil {
+ ck.markCorrupt(relPath)
+ fmt.Printf("fsck: error stating file %q: %v\n", relPath, err)
return
}
- if attr.Nlink > 1 {
+ if st.Nlink > 1 {
// Due to hard links, we may have already checked this file.
- if _, ok := ck.seenInodes[attr.Ino]; ok {
- tlog.Debug.Printf("ck.file : skipping %q (inode number %d already seen)\n", path, attr.Ino)
+ if _, ok := ck.seenInodes[st.Ino]; ok {
+ tlog.Debug.Printf("ck.file : skipping %q (inode number %d already seen)\n", relPath, st.Ino)
return
}
- ck.seenInodes[attr.Ino] = struct{}{}
+ ck.seenInodes[st.Ino] = struct{}{}
}
- ck.xattrs(n)
- tmp, _, errno := n.Open(nil, syscall.O_RDONLY)
- if errno != 0 {
- fmt.Printf("fsck: error opening file %q: %v\n", path, errno)
- if errno == syscall.EACCES && !runsAsRoot() {
- ck.markSkipped(path)
+ ck.xattrs(relPath)
+ f, err := os.Open(ck.abs(relPath))
+ if err != nil {
+ fmt.Printf("fsck: error opening file %q: %v\n", relPath, err)
+ if err == os.ErrPermission && !runsAsRoot() {
+ ck.markSkipped(relPath)
} else {
- ck.markCorrupt(path)
+ ck.markCorrupt(relPath)
}
return
}
- f := tmp.(*fusefrontend.File2)
- defer f.Release(nil)
+ defer f.Close()
// 128 kiB of zeros
allZero := make([]byte, fuse.MAX_KERNEL_WRITE)
buf := make([]byte, fuse.MAX_KERNEL_WRITE)
var off int64
// Read() through the whole file and catch transparently mitigated corruptions
- go ck.watchMitigatedCorruptionsRead(path)
+ go ck.watchMitigatedCorruptionsRead(relPath)
defer func() { ck.watchDone <- struct{}{} }()
for {
+ if ck.abort {
+ return
+ }
tlog.Debug.Printf("ck.file: read %d bytes from offset %d\n", len(buf), off)
- result, errno := f.Read(nil, buf, off)
- if errno != 0 {
- ck.markCorrupt(path)
- fmt.Printf("fsck: error reading file %q (inum %d): %v\n", path, inum(f), errno)
+ n, err := f.ReadAt(buf, off)
+ if err != nil && err != io.EOF {
+ ck.markCorrupt(relPath)
+ fmt.Printf("fsck: error reading file %q (inum %d): %v\n", relPath, inum(f), err)
return
}
- n := result.Size()
// EOF
- if n == 0 {
+ if err == io.EOF {
return
}
off += int64(n)
@@ -192,7 +206,8 @@ func (ck *fsckObj) file(n *fusefrontend.Node) {
data := buf[:n]
if bytes.Equal(data, allZero) {
tlog.Debug.Printf("ck.file: trying to skip file hole\n")
- nextOff, err := f.SeekData(off)
+ const SEEK_DATA = 3
+ nextOff, err := syscall.Seek(int(f.Fd()), off, SEEK_DATA)
if err == nil {
off = nextOff
}
@@ -214,35 +229,25 @@ func (ck *fsckObj) watchMitigatedCorruptionsListXAttr(path string) {
}
// Check xattrs on file/dir at path
-func (ck *fsckObj) xattrs(n *fusefrontend.Node) {
+func (ck *fsckObj) xattrs(relPath string) {
// Run ListXAttr() and catch transparently mitigated corruptions
- path := n.Path()
- go ck.watchMitigatedCorruptionsListXAttr(path)
- listBuf := make([]byte, 1024*1024)
- cnt, errno := n.Listxattr(nil, listBuf)
+ go ck.watchMitigatedCorruptionsListXAttr(relPath)
+ attrs, err := syscallcompat.Llistxattr(ck.abs(relPath))
ck.watchDone <- struct{}{}
- // Also catch non-mitigated corruptions
- if errno != 0 {
- fmt.Printf("fsck: error listing xattrs on %q: %v\n", path, errno)
- ck.markCorrupt(path)
- return
- }
- if cnt == 0 {
+ if err != nil {
+ fmt.Printf("fsck: error listing xattrs on %q: %v\n", relPath, err)
+ ck.markCorrupt(relPath)
return
}
- // Drop final trailing NULL byte
- cnt--
- listBuf = listBuf[:cnt]
- attrs := bytes.Split(listBuf, []byte{0})
+ // Try to read all xattr values
for _, a := range attrs {
- getBuf := make([]byte, 1024*1024)
- _, errno := n.Getxattr(nil, string(a), getBuf)
- if errno != 0 {
- fmt.Printf("fsck: error reading xattr %q from %q: %v\n", a, path, errno)
- if errno == syscall.EACCES && !runsAsRoot() {
- ck.markSkipped(path)
+ _, err := syscallcompat.Lgetxattr(ck.abs(relPath), a)
+ if err != nil {
+ fmt.Printf("fsck: error reading xattr %q from %q: %v\n", a, relPath, err)
+ if err == syscall.EACCES && !runsAsRoot() {
+ ck.markSkipped(relPath)
} else {
- ck.markCorrupt(path)
+ ck.markCorrupt(relPath)
}
}
}
@@ -254,21 +259,45 @@ func fsck(args *argContainer) {
os.Exit(exitcodes.Usage)
}
args.allow_other = false
- pfs, wipeKeys := initFuseFrontend(args)
- opts := fs.Options{
- // Enable go-fuse warnings
- Logger: log.New(os.Stderr, "go-fuse: ", 0),
+ var err error
+ args.mountpoint, err = ioutil.TempDir("", "gocryptfs.fsck.")
+ if err != nil {
+ tlog.Fatal.Printf("fsck: TmpDir: %v", err)
+ os.Exit(exitcodes.MountPoint)
}
- fs.NewNodeFS(pfs, &opts)
+ pfs, wipeKeys := initFuseFrontend(args)
rn := pfs.(*fusefrontend.RootNode)
rn.MitigatedCorruptions = make(chan string)
ck := fsckObj{
+ mnt: args.mountpoint,
rootNode: rn,
watchDone: make(chan struct{}),
seenInodes: make(map[uint64]struct{}),
}
- ck.dir(&rn.Node)
+ // Mount
+ srv := initGoFuse(pfs, args)
+ // Handle SIGINT & SIGTERM
+ ch := make(chan os.Signal, 1)
+ signal.Notify(ch, os.Interrupt)
+ signal.Notify(ch, syscall.SIGTERM)
+ go func() {
+ <-ch
+ ck.abort = true
+ }()
+ defer func() {
+ err = srv.Unmount()
+ if err != nil {
+ tlog.Warn.Printf("failed to unmount %q: %v", ck.mnt, err)
+ }
+ }()
+ // Recursively check the root dir
+ ck.dir("")
+ // Report results
wipeKeys()
+ if ck.abort {
+ tlog.Info.Printf("fsck: aborted")
+ return
+ }
if len(ck.corruptList) == 0 && len(ck.skippedList) == 0 {
tlog.Info.Printf("fsck summary: no problems found\n")
return
@@ -294,8 +323,12 @@ func (s sortableDirEntries) Less(i, j int) bool {
return strings.Compare(s[i].Name, s[j].Name) < 0
}
-func inum(f *fusefrontend.File2) uint64 {
- var a fuse.AttrOut
- f.Getattr(nil, &a)
- return a.Ino
+func inum(f *os.File) uint64 {
+ var st syscall.Stat_t
+ err := syscall.Fstat(int(f.Fd()), &st)
+ if err != nil {
+ tlog.Warn.Printf("inum: fstat failed: %v", err)
+ return 0
+ }
+ return st.Ino
}
diff --git a/mount.go b/mount.go
index 693d0c0..e8fa720 100644
--- a/mount.go
+++ b/mount.go
@@ -221,7 +221,7 @@ func setOpenFileLimit() {
}
}
-// initFuseFrontend - initialize gocryptfs/fusefrontend
+// initFuseFrontend - initialize gocryptfs/internal/fusefrontend
// Calls os.Exit on errors
func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys func()) {
var err error
@@ -326,6 +326,9 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
return rootNode, func() { cCore.Wipe() }
}
+// initGoFuse calls into go-fuse to mount `rootNode` on `args.mountpoint`.
+// The mountpoint is ready to use when the functions returns.
+// On error, it calls os.Exit and does not return.
func initGoFuse(rootNode fs.InodeEmbedder, args *argContainer) *fuse.Server {
var fuseOpts *fs.Options
sec := time.Second
diff --git a/tests/fsck/fsck_test.go b/tests/fsck/fsck_test.go
index f56e21c..3aaba9e 100644
--- a/tests/fsck/fsck_test.go
+++ b/tests/fsck/fsck_test.go
@@ -6,6 +6,7 @@ import (
"os/exec"
"runtime"
"strings"
+ "syscall"
"testing"
"time"
@@ -98,8 +99,8 @@ func TestTerabyteFile(t *testing.T) {
pDir := cDir + ".mnt"
test_helpers.MountOrFatal(t, cDir, pDir, "-extpass", "echo test")
defer test_helpers.UnmountErr(pDir)
- exabyteFile := pDir + "/exabyteFile"
- fd, err := os.Create(exabyteFile)
+ veryBigFile := pDir + "/veryBigFile"
+ fd, err := os.Create(veryBigFile)
if err != nil {
t.Fatal(err)
}
@@ -119,8 +120,8 @@ func TestTerabyteFile(t *testing.T) {
cmd.Stdout = os.Stdout
cmd.Start()
timer := time.AfterFunc(10*time.Second, func() {
- cmd.Process.Kill()
- t.Fatalf("timeout")
+ t.Error("timeout, sending SIGINT")
+ syscall.Kill(cmd.Process.Pid, syscall.SIGINT)
})
cmd.Wait()
timer.Stop()