4e5783591f
OpenDir and ListXAttr skip over corrupt entries, readFileID treats files the are too small as empty. This improves usability in the face of corruption, but hides the problem in a log message instead of putting it in the return code. Create a channel to report these corruptions to fsck so it can report them to the user. Also update the manpage and the changelog with the -fsck option. Closes https://github.com/rfjakob/gocryptfs/issues/191
194 lines
4.3 KiB
Go
194 lines
4.3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/hanwen/go-fuse/fuse"
|
|
|
|
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
|
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
|
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
|
)
|
|
|
|
type fsckObj struct {
|
|
fs *fusefrontend.FS
|
|
// List of corrupt files
|
|
corruptList []string
|
|
// Protects corruptList
|
|
corruptListLock sync.Mutex
|
|
}
|
|
|
|
func (ck *fsckObj) markCorrupt(path string) {
|
|
ck.corruptListLock.Lock()
|
|
ck.corruptList = append(ck.corruptList, path)
|
|
ck.corruptListLock.Unlock()
|
|
}
|
|
|
|
// Recursively check dir for corruption
|
|
func (ck *fsckObj) dir(path string) {
|
|
//fmt.Printf("ck.dir %q\n", path)
|
|
ck.xattrs(path)
|
|
done := make(chan struct{})
|
|
go func() {
|
|
for {
|
|
select {
|
|
case item := <-ck.fs.CorruptItems:
|
|
fmt.Printf("fsck: corrupt entry in dir %q: %q\n", path, item)
|
|
ck.markCorrupt(filepath.Join(path, item))
|
|
case <-done:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
entries, status := ck.fs.OpenDir(path, nil)
|
|
done <- struct{}{}
|
|
if !status.Ok() {
|
|
ck.markCorrupt(path)
|
|
fmt.Printf("fsck: error opening dir %q: %v\n", path, status)
|
|
return
|
|
}
|
|
// Sort alphabetically
|
|
sort.Sort(sortableDirEntries(entries))
|
|
for _, entry := range entries {
|
|
if entry.Name == "." || entry.Name == ".." {
|
|
continue
|
|
}
|
|
nextPath := filepath.Join(path, entry.Name)
|
|
filetype := entry.Mode & syscall.S_IFMT
|
|
//fmt.Printf(" %q %x\n", entry.Name, entry.Mode)
|
|
switch filetype {
|
|
case syscall.S_IFDIR:
|
|
ck.dir(nextPath)
|
|
case syscall.S_IFREG:
|
|
ck.file(nextPath)
|
|
case syscall.S_IFLNK:
|
|
ck.symlink(nextPath)
|
|
case syscall.S_IFIFO, syscall.S_IFSOCK, syscall.S_IFBLK, syscall.S_IFCHR:
|
|
// nothing to check
|
|
default:
|
|
fmt.Printf("fsck: unhandled file type %x\n", filetype)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ck *fsckObj) symlink(path string) {
|
|
_, status := ck.fs.Readlink(path, nil)
|
|
if !status.Ok() {
|
|
ck.markCorrupt(path)
|
|
fmt.Printf("fsck: error reading symlink %q: %v\n", path, status)
|
|
}
|
|
}
|
|
|
|
// check file for corruption
|
|
func (ck *fsckObj) file(path string) {
|
|
//fmt.Printf("ck.file %q\n", path)
|
|
ck.xattrs(path)
|
|
f, status := ck.fs.Open(path, syscall.O_RDONLY, nil)
|
|
if !status.Ok() {
|
|
ck.markCorrupt(path)
|
|
fmt.Printf("fsck: error opening file %q: %v\n", path, status)
|
|
return
|
|
}
|
|
defer f.Release()
|
|
buf := make([]byte, fuse.MAX_KERNEL_WRITE)
|
|
var off int64
|
|
done := make(chan struct{})
|
|
go func() {
|
|
for {
|
|
select {
|
|
case item := <-ck.fs.CorruptItems:
|
|
fmt.Printf("fsck: corrupt file %q (inode %s)\n", path, item)
|
|
ck.markCorrupt(path)
|
|
case <-done:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
defer func() { done <- struct{}{} }()
|
|
for {
|
|
result, status := f.Read(buf, off)
|
|
if !status.Ok() {
|
|
ck.markCorrupt(path)
|
|
fmt.Printf("fsck: error reading file %q at offset %d: %v\n", path, off, status)
|
|
return
|
|
}
|
|
// EOF
|
|
if result.Size() == 0 {
|
|
return
|
|
}
|
|
off += int64(result.Size())
|
|
}
|
|
}
|
|
|
|
// Check xattrs on file/dir at path
|
|
func (ck *fsckObj) xattrs(path string) {
|
|
done := make(chan struct{})
|
|
go func() {
|
|
for {
|
|
select {
|
|
case item := <-ck.fs.CorruptItems:
|
|
fmt.Printf("fsck: corrupt xattr name on file %q: %q\n", path, item)
|
|
ck.markCorrupt(path + " xattr:" + item)
|
|
case <-done:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
attrs, status := ck.fs.ListXAttr(path, nil)
|
|
done <- struct{}{}
|
|
if !status.Ok() {
|
|
fmt.Printf("fsck: error listing xattrs on %q: %v\n", path, status)
|
|
ck.markCorrupt(path)
|
|
return
|
|
}
|
|
for _, a := range attrs {
|
|
_, status := ck.fs.GetXAttr(path, a, nil)
|
|
if !status.Ok() {
|
|
fmt.Printf("fsck: error reading xattr %q from %q: %v\n", a, path, status)
|
|
ck.markCorrupt(path)
|
|
}
|
|
}
|
|
}
|
|
|
|
func fsck(args *argContainer) {
|
|
if args.reverse {
|
|
tlog.Fatal.Printf("Running -fsck with -reverse is not supported")
|
|
os.Exit(exitcodes.Usage)
|
|
}
|
|
args.allow_other = false
|
|
pfs, wipeKeys := initFuseFrontend(args)
|
|
fs := pfs.(*fusefrontend.FS)
|
|
fs.CorruptItems = make(chan string)
|
|
ck := fsckObj{
|
|
fs: fs,
|
|
}
|
|
ck.dir("")
|
|
wipeKeys()
|
|
if len(ck.corruptList) == 0 {
|
|
fmt.Printf("fsck summary: no problems found\n")
|
|
return
|
|
}
|
|
fmt.Printf("fsck summary: %d corrupt files\n", len(ck.corruptList))
|
|
os.Exit(exitcodes.FsckErrors)
|
|
}
|
|
|
|
type sortableDirEntries []fuse.DirEntry
|
|
|
|
func (s sortableDirEntries) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
func (s sortableDirEntries) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
|
|
func (s sortableDirEntries) Less(i, j int) bool {
|
|
return strings.Compare(s[i].Name, s[j].Name) < 0
|
|
}
|