2018-04-01 14:25:10 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2018-07-01 19:14:00 +02:00
|
|
|
"bytes"
|
2018-04-01 21:23:32 +02:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2018-04-02 16:56:29 +02:00
|
|
|
"strings"
|
2018-04-03 21:19:44 +02:00
|
|
|
"sync"
|
2018-04-01 21:23:32 +02:00
|
|
|
"syscall"
|
|
|
|
|
2020-07-18 23:41:27 +02:00
|
|
|
"github.com/hanwen/go-fuse/v2/fs"
|
2020-05-17 14:18:23 +02:00
|
|
|
"github.com/hanwen/go-fuse/v2/fuse"
|
2018-04-01 21:23:32 +02:00
|
|
|
|
|
|
|
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
|
|
|
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
|
|
|
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
2018-04-01 14:25:10 +02:00
|
|
|
)
|
|
|
|
|
2018-04-01 21:23:32 +02:00
|
|
|
type fsckObj struct {
|
2020-07-18 23:41:27 +02:00
|
|
|
rootNode *fusefrontend.RootNode
|
2018-04-02 18:32:30 +02:00
|
|
|
// List of corrupt files
|
|
|
|
corruptList []string
|
2019-01-04 04:53:55 +01:00
|
|
|
// List of skipped files
|
|
|
|
skippedList []string
|
2018-04-03 21:19:44 +02:00
|
|
|
// Protects corruptList
|
2019-01-04 04:53:55 +01:00
|
|
|
listLock sync.Mutex
|
2018-07-01 16:24:02 +02:00
|
|
|
// stop a running watchMitigatedCorruptions thread
|
|
|
|
watchDone chan struct{}
|
2018-08-15 14:02:05 +02:00
|
|
|
// Inode numbers of hard-linked files (Nlink > 1) that we have already checked
|
|
|
|
seenInodes map[uint64]struct{}
|
2018-04-02 18:32:30 +02:00
|
|
|
}
|
|
|
|
|
2019-01-04 04:53:55 +01:00
|
|
|
func runsAsRoot() bool {
|
|
|
|
return syscall.Geteuid() == 0
|
|
|
|
}
|
|
|
|
|
2018-04-02 18:32:30 +02:00
|
|
|
func (ck *fsckObj) markCorrupt(path string) {
|
2019-01-04 04:53:55 +01:00
|
|
|
ck.listLock.Lock()
|
2018-04-02 18:32:30 +02:00
|
|
|
ck.corruptList = append(ck.corruptList, path)
|
2019-01-04 04:53:55 +01:00
|
|
|
ck.listLock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ck *fsckObj) markSkipped(path string) {
|
|
|
|
ck.listLock.Lock()
|
|
|
|
ck.skippedList = append(ck.skippedList, path)
|
|
|
|
ck.listLock.Unlock()
|
2018-04-01 21:23:32 +02:00
|
|
|
}
|
|
|
|
|
2018-12-27 12:03:00 +01:00
|
|
|
// Watch for mitigated corruptions that occur during OpenDir()
|
2018-07-01 16:24:02 +02:00
|
|
|
func (ck *fsckObj) watchMitigatedCorruptionsOpenDir(path string) {
|
|
|
|
for {
|
|
|
|
select {
|
2020-07-18 23:41:27 +02:00
|
|
|
case item := <-ck.rootNode.MitigatedCorruptions:
|
2018-07-01 16:24:02 +02:00
|
|
|
fmt.Printf("fsck: corrupt entry in dir %q: %q\n", path, item)
|
|
|
|
ck.markCorrupt(filepath.Join(path, item))
|
|
|
|
case <-ck.watchDone:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-01 21:23:32 +02:00
|
|
|
// Recursively check dir for corruption
|
2020-07-18 23:41:27 +02:00
|
|
|
func (ck *fsckObj) dir(n *fusefrontend.Node) {
|
|
|
|
path := n.Path()
|
|
|
|
tlog.Debug.Printf("ck.dir %q\n")
|
|
|
|
ck.xattrs(n)
|
2018-07-01 16:24:02 +02:00
|
|
|
// Run OpenDir and catch transparently mitigated corruptions
|
|
|
|
go ck.watchMitigatedCorruptionsOpenDir(path)
|
2020-07-18 23:41:27 +02:00
|
|
|
entries, errno := n.Readdir(nil)
|
2018-07-01 16:24:02 +02:00
|
|
|
ck.watchDone <- struct{}{}
|
|
|
|
// Also catch non-mitigated corruptions
|
2020-07-18 23:41:27 +02:00
|
|
|
if errno != 0 {
|
|
|
|
fmt.Printf("fsck: error opening dir %q: %v\n", n, errno)
|
|
|
|
if errno == syscall.EACCES && !runsAsRoot() {
|
2019-01-04 04:53:55 +01:00
|
|
|
ck.markSkipped(path)
|
|
|
|
} else {
|
|
|
|
ck.markCorrupt(path)
|
|
|
|
}
|
2018-04-01 21:23:32 +02:00
|
|
|
return
|
|
|
|
}
|
2020-07-18 23:41:27 +02:00
|
|
|
for entries.HasNext() {
|
|
|
|
entry, errno := entries.Next()
|
|
|
|
if errno != 0 {
|
|
|
|
fmt.Printf("fsck: dirstream error: %v\n", errno)
|
|
|
|
break
|
|
|
|
}
|
2018-04-01 21:23:32 +02:00
|
|
|
if entry.Name == "." || entry.Name == ".." {
|
|
|
|
continue
|
|
|
|
}
|
2020-07-18 23:41:27 +02:00
|
|
|
tmp, errno := n.Lookup(nil, entry.Name, &fuse.EntryOut{})
|
|
|
|
if errno != 0 {
|
|
|
|
ck.markCorrupt(filepath.Join(path, entry.Name))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
nextPath := tmp.Operations().(*fusefrontend.Node)
|
2018-04-01 21:23:32 +02:00
|
|
|
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:
|
2018-04-02 20:25:59 +02:00
|
|
|
fmt.Printf("fsck: unhandled file type %x\n", filetype)
|
2018-04-01 21:23:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-18 23:41:27 +02:00
|
|
|
func (ck *fsckObj) symlink(n *fusefrontend.Node) {
|
|
|
|
_, errno := n.Readlink(nil)
|
|
|
|
if errno != 0 {
|
|
|
|
path := n.Path()
|
2018-04-02 18:32:30 +02:00
|
|
|
ck.markCorrupt(path)
|
2020-07-18 23:41:27 +02:00
|
|
|
fmt.Printf("fsck: error reading symlink %q: %v\n", path, errno)
|
2018-04-01 21:23:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-27 12:03:00 +01:00
|
|
|
// Watch for mitigated corruptions that occur during Read()
|
2018-07-01 16:24:02 +02:00
|
|
|
func (ck *fsckObj) watchMitigatedCorruptionsRead(path string) {
|
|
|
|
for {
|
|
|
|
select {
|
2020-07-18 23:41:27 +02:00
|
|
|
case item := <-ck.rootNode.MitigatedCorruptions:
|
2018-07-01 16:24:02 +02:00
|
|
|
fmt.Printf("fsck: corrupt file %q (inode %s)\n", path, item)
|
|
|
|
ck.markCorrupt(path)
|
|
|
|
case <-ck.watchDone:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check file for corruption
|
2020-07-18 23:41:27 +02:00
|
|
|
func (ck *fsckObj) file(n *fusefrontend.Node) {
|
|
|
|
path := n.Path()
|
2018-07-15 11:34:30 +02:00
|
|
|
tlog.Debug.Printf("ck.file %q\n", path)
|
2020-07-18 23:41:27 +02:00
|
|
|
var attr fuse.AttrOut
|
|
|
|
errno := n.Getattr(nil, nil, &attr)
|
|
|
|
if errno != 0 {
|
2018-08-15 14:02:05 +02:00
|
|
|
ck.markCorrupt(path)
|
2020-07-18 23:41:27 +02:00
|
|
|
fmt.Printf("fsck: error stating file %q: %v\n", path, errno)
|
2018-08-15 14:02:05 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if attr.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)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ck.seenInodes[attr.Ino] = struct{}{}
|
|
|
|
}
|
2020-07-18 23:41:27 +02:00
|
|
|
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() {
|
2019-01-04 04:53:55 +01:00
|
|
|
ck.markSkipped(path)
|
|
|
|
} else {
|
|
|
|
ck.markCorrupt(path)
|
|
|
|
}
|
2018-04-01 21:23:32 +02:00
|
|
|
return
|
|
|
|
}
|
2020-07-18 23:41:27 +02:00
|
|
|
f := tmp.(*fusefrontend.File2)
|
|
|
|
defer f.Release(nil)
|
2019-01-04 20:23:01 +01:00
|
|
|
// 128 kiB of zeros
|
2018-07-01 19:14:00 +02:00
|
|
|
allZero := make([]byte, fuse.MAX_KERNEL_WRITE)
|
2018-04-01 21:23:32 +02:00
|
|
|
buf := make([]byte, fuse.MAX_KERNEL_WRITE)
|
|
|
|
var off int64
|
2018-07-01 16:24:02 +02:00
|
|
|
// Read() through the whole file and catch transparently mitigated corruptions
|
|
|
|
go ck.watchMitigatedCorruptionsRead(path)
|
|
|
|
defer func() { ck.watchDone <- struct{}{} }()
|
2018-04-01 21:23:32 +02:00
|
|
|
for {
|
2018-07-15 11:34:30 +02:00
|
|
|
tlog.Debug.Printf("ck.file: read %d bytes from offset %d\n", len(buf), off)
|
2020-07-18 23:41:27 +02:00
|
|
|
result, errno := f.Read(nil, buf, off)
|
|
|
|
if errno != 0 {
|
2018-04-02 18:32:30 +02:00
|
|
|
ck.markCorrupt(path)
|
2020-07-18 23:41:27 +02:00
|
|
|
fmt.Printf("fsck: error reading file %q (inum %d): %v\n", path, inum(f), errno)
|
2018-04-01 21:23:32 +02:00
|
|
|
return
|
|
|
|
}
|
2019-01-04 20:23:01 +01:00
|
|
|
n := result.Size()
|
2018-04-01 21:23:32 +02:00
|
|
|
// EOF
|
2019-01-04 20:23:01 +01:00
|
|
|
if n == 0 {
|
2018-04-01 21:23:32 +02:00
|
|
|
return
|
|
|
|
}
|
2019-01-04 20:23:01 +01:00
|
|
|
off += int64(n)
|
2018-07-01 19:14:00 +02:00
|
|
|
// If we seem to be in the middle of a file hole, try to skip to the next
|
|
|
|
// data section.
|
2019-01-04 20:23:01 +01:00
|
|
|
data := buf[:n]
|
|
|
|
if bytes.Equal(data, allZero) {
|
2018-07-15 11:34:30 +02:00
|
|
|
tlog.Debug.Printf("ck.file: trying to skip file hole\n")
|
2020-07-18 23:41:27 +02:00
|
|
|
nextOff, err := f.SeekData(off)
|
2018-07-01 19:14:00 +02:00
|
|
|
if err == nil {
|
|
|
|
off = nextOff
|
|
|
|
}
|
|
|
|
}
|
2018-04-01 21:23:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-27 12:03:00 +01:00
|
|
|
// Watch for mitigated corruptions that occur during ListXAttr()
|
2018-07-01 16:24:02 +02:00
|
|
|
func (ck *fsckObj) watchMitigatedCorruptionsListXAttr(path string) {
|
|
|
|
for {
|
|
|
|
select {
|
2020-07-18 23:41:27 +02:00
|
|
|
case item := <-ck.rootNode.MitigatedCorruptions:
|
2018-07-01 16:24:02 +02:00
|
|
|
fmt.Printf("fsck: corrupt xattr name on file %q: %q\n", path, item)
|
|
|
|
ck.markCorrupt(path + " xattr:" + item)
|
|
|
|
case <-ck.watchDone:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-02 20:25:59 +02:00
|
|
|
// Check xattrs on file/dir at path
|
2020-07-18 23:41:27 +02:00
|
|
|
func (ck *fsckObj) xattrs(n *fusefrontend.Node) {
|
2018-07-01 16:24:02 +02:00
|
|
|
// Run ListXAttr() and catch transparently mitigated corruptions
|
2020-07-18 23:41:27 +02:00
|
|
|
path := n.Path()
|
2018-07-01 16:24:02 +02:00
|
|
|
go ck.watchMitigatedCorruptionsListXAttr(path)
|
2020-07-18 23:41:27 +02:00
|
|
|
listBuf := make([]byte, 1024*1024)
|
|
|
|
cnt, errno := n.Listxattr(nil, listBuf)
|
2018-07-01 16:24:02 +02:00
|
|
|
ck.watchDone <- struct{}{}
|
|
|
|
// Also catch non-mitigated corruptions
|
2020-07-18 23:41:27 +02:00
|
|
|
if errno != 0 {
|
|
|
|
fmt.Printf("fsck: error listing xattrs on %q: %v\n", path, errno)
|
2018-04-02 20:25:59 +02:00
|
|
|
ck.markCorrupt(path)
|
|
|
|
return
|
|
|
|
}
|
2020-07-18 23:41:27 +02:00
|
|
|
if cnt == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Drop final trailing NULL byte
|
|
|
|
cnt--
|
|
|
|
listBuf = listBuf[:cnt]
|
|
|
|
attrs := bytes.Split(listBuf, []byte{0})
|
2018-04-02 20:25:59 +02:00
|
|
|
for _, a := range attrs {
|
2020-07-18 23:41:27 +02:00
|
|
|
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() {
|
2019-01-04 04:53:55 +01:00
|
|
|
ck.markSkipped(path)
|
|
|
|
} else {
|
|
|
|
ck.markCorrupt(path)
|
|
|
|
}
|
2018-04-02 20:25:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-01 14:25:10 +02:00
|
|
|
func fsck(args *argContainer) {
|
2018-04-01 21:23:32 +02:00
|
|
|
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)
|
2020-07-18 23:41:27 +02:00
|
|
|
fs.NewNodeFS(pfs, &fs.Options{})
|
|
|
|
rn := pfs.(*fusefrontend.RootNode)
|
|
|
|
rn.MitigatedCorruptions = make(chan string)
|
2018-04-01 21:23:32 +02:00
|
|
|
ck := fsckObj{
|
2020-07-18 23:41:27 +02:00
|
|
|
rootNode: rn,
|
2018-08-15 14:02:05 +02:00
|
|
|
watchDone: make(chan struct{}),
|
|
|
|
seenInodes: make(map[uint64]struct{}),
|
2018-04-01 21:23:32 +02:00
|
|
|
}
|
2020-07-18 23:41:27 +02:00
|
|
|
ck.dir(&rn.Node)
|
2018-04-02 18:32:30 +02:00
|
|
|
wipeKeys()
|
2019-01-04 04:53:55 +01:00
|
|
|
if len(ck.corruptList) == 0 && len(ck.skippedList) == 0 {
|
2018-06-27 22:17:41 +02:00
|
|
|
tlog.Info.Printf("fsck summary: no problems found\n")
|
2018-04-02 18:32:30 +02:00
|
|
|
return
|
|
|
|
}
|
2019-01-04 04:53:55 +01:00
|
|
|
if len(ck.skippedList) > 0 {
|
|
|
|
tlog.Warn.Printf("fsck: re-run this program as root to check all files!\n")
|
|
|
|
}
|
|
|
|
fmt.Printf("fsck summary: %d corrupt files, %d files skipped\n", len(ck.corruptList), len(ck.skippedList))
|
2018-04-02 18:32:30 +02:00
|
|
|
os.Exit(exitcodes.FsckErrors)
|
2018-04-01 14:25:10 +02:00
|
|
|
}
|
2018-04-02 16:56:29 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2018-07-23 22:34:41 +02:00
|
|
|
|
2020-07-18 23:41:27 +02:00
|
|
|
func inum(f *fusefrontend.File2) uint64 {
|
|
|
|
var a fuse.AttrOut
|
|
|
|
f.Getattr(nil, &a)
|
2018-07-23 22:34:41 +02:00
|
|
|
return a.Ino
|
|
|
|
}
|