fsck: report skipped corrupt files
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
This commit is contained in:
parent
8b443c8484
commit
4e5783591f
|
@ -19,6 +19,9 @@ SYNOPSIS
|
||||||
#### Change password
|
#### Change password
|
||||||
`gocryptfs -passwd [OPTIONS] CIPHERDIR`
|
`gocryptfs -passwd [OPTIONS] CIPHERDIR`
|
||||||
|
|
||||||
|
#### Check consistency
|
||||||
|
`gocryptfs -fsck [OPTIONS] CIPHERDIR`
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
@ -96,6 +99,10 @@ that uses built-in Go crypto.
|
||||||
|
|
||||||
Setting this option forces the filesystem to read-only and noexec.
|
Setting this option forces the filesystem to read-only and noexec.
|
||||||
|
|
||||||
|
#### -fsck
|
||||||
|
Check CIPHERDIR for consistency. If corruption is found, the
|
||||||
|
exit code is 26.
|
||||||
|
|
||||||
#### -fsname string
|
#### -fsname string
|
||||||
Override the filesystem name (first column in df -T). Can also be
|
Override the filesystem name (first column in df -T). Can also be
|
||||||
passed as "-o fsname=" and is equivalent to libfuse's option of the
|
passed as "-o fsname=" and is equivalent to libfuse's option of the
|
||||||
|
@ -351,6 +358,7 @@ EXIT CODES
|
||||||
22: password is empty (on "-init")
|
22: password is empty (on "-init")
|
||||||
23: could not read gocryptfs.conf
|
23: could not read gocryptfs.conf
|
||||||
24: could not write gocryptfs.conf (on "-init" or "-password")
|
24: could not write gocryptfs.conf (on "-init" or "-password")
|
||||||
|
26: fsck found errors
|
||||||
other: please check the error message
|
other: please check the error message
|
||||||
|
|
||||||
SEE ALSO
|
SEE ALSO
|
||||||
|
|
|
@ -158,6 +158,8 @@ vNEXT, in progress
|
||||||
([#218](https://github.com/rfjakob/gocryptfs/issues/218))
|
([#218](https://github.com/rfjakob/gocryptfs/issues/218))
|
||||||
* Support extended attributes (xattr) in forward mode
|
* Support extended attributes (xattr) in forward mode
|
||||||
([#217](https://github.com/rfjakob/gocryptfs/issues/217))
|
([#217](https://github.com/rfjakob/gocryptfs/issues/217))
|
||||||
|
* Add `-fsck` function
|
||||||
|
([#191](https://github.com/rfjakob/gocryptfs/issues/191))
|
||||||
|
|
||||||
v1.4.4, 2018-03-18
|
v1.4.4, 2018-03-18
|
||||||
* Overwrite secrets in memory with zeros as soon as possible
|
* Overwrite secrets in memory with zeros as soon as possible
|
||||||
|
|
52
fsck.go
52
fsck.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/hanwen/go-fuse/fuse"
|
"github.com/hanwen/go-fuse/fuse"
|
||||||
|
@ -19,17 +20,34 @@ type fsckObj struct {
|
||||||
fs *fusefrontend.FS
|
fs *fusefrontend.FS
|
||||||
// List of corrupt files
|
// List of corrupt files
|
||||||
corruptList []string
|
corruptList []string
|
||||||
|
// Protects corruptList
|
||||||
|
corruptListLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ck *fsckObj) markCorrupt(path string) {
|
func (ck *fsckObj) markCorrupt(path string) {
|
||||||
|
ck.corruptListLock.Lock()
|
||||||
ck.corruptList = append(ck.corruptList, path)
|
ck.corruptList = append(ck.corruptList, path)
|
||||||
|
ck.corruptListLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively check dir for corruption
|
// Recursively check dir for corruption
|
||||||
func (ck *fsckObj) dir(path string) {
|
func (ck *fsckObj) dir(path string) {
|
||||||
//fmt.Printf("ck.dir %q\n", path)
|
//fmt.Printf("ck.dir %q\n", path)
|
||||||
ck.xattrs(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)
|
entries, status := ck.fs.OpenDir(path, nil)
|
||||||
|
done <- struct{}{}
|
||||||
if !status.Ok() {
|
if !status.Ok() {
|
||||||
ck.markCorrupt(path)
|
ck.markCorrupt(path)
|
||||||
fmt.Printf("fsck: error opening dir %q: %v\n", path, status)
|
fmt.Printf("fsck: error opening dir %q: %v\n", path, status)
|
||||||
|
@ -80,6 +98,19 @@ func (ck *fsckObj) file(path string) {
|
||||||
defer f.Release()
|
defer f.Release()
|
||||||
buf := make([]byte, fuse.MAX_KERNEL_WRITE)
|
buf := make([]byte, fuse.MAX_KERNEL_WRITE)
|
||||||
var off int64
|
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 {
|
for {
|
||||||
result, status := f.Read(buf, off)
|
result, status := f.Read(buf, off)
|
||||||
if !status.Ok() {
|
if !status.Ok() {
|
||||||
|
@ -97,7 +128,20 @@ func (ck *fsckObj) file(path string) {
|
||||||
|
|
||||||
// Check xattrs on file/dir at path
|
// Check xattrs on file/dir at path
|
||||||
func (ck *fsckObj) xattrs(path string) {
|
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)
|
attrs, status := ck.fs.ListXAttr(path, nil)
|
||||||
|
done <- struct{}{}
|
||||||
if !status.Ok() {
|
if !status.Ok() {
|
||||||
fmt.Printf("fsck: error listing xattrs on %q: %v\n", path, status)
|
fmt.Printf("fsck: error listing xattrs on %q: %v\n", path, status)
|
||||||
ck.markCorrupt(path)
|
ck.markCorrupt(path)
|
||||||
|
@ -120,19 +164,17 @@ func fsck(args *argContainer) {
|
||||||
args.allow_other = false
|
args.allow_other = false
|
||||||
pfs, wipeKeys := initFuseFrontend(args)
|
pfs, wipeKeys := initFuseFrontend(args)
|
||||||
fs := pfs.(*fusefrontend.FS)
|
fs := pfs.(*fusefrontend.FS)
|
||||||
|
fs.CorruptItems = make(chan string)
|
||||||
ck := fsckObj{
|
ck := fsckObj{
|
||||||
fs: fs,
|
fs: fs,
|
||||||
}
|
}
|
||||||
ck.dir("")
|
ck.dir("")
|
||||||
wipeKeys()
|
wipeKeys()
|
||||||
if len(ck.corruptList) == 0 {
|
if len(ck.corruptList) == 0 {
|
||||||
fmt.Printf("fsck summary: no problems found")
|
fmt.Printf("fsck summary: no problems found\n")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("fsck summary: found %d corrupt files:\n", len(ck.corruptList))
|
fmt.Printf("fsck summary: %d corrupt files\n", len(ck.corruptList))
|
||||||
for _, path := range ck.corruptList {
|
|
||||||
fmt.Printf(" %q\n", path)
|
|
||||||
}
|
|
||||||
os.Exit(exitcodes.FsckErrors)
|
os.Exit(exitcodes.FsckErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ package fusefrontend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
@ -99,6 +100,7 @@ func (f *file) readFileID() ([]byte, error) {
|
||||||
if err == io.EOF && n != 0 {
|
if err == io.EOF && n != 0 {
|
||||||
tlog.Warn.Printf("readFileID %d: incomplete file, got %d instead of %d bytes",
|
tlog.Warn.Printf("readFileID %d: incomplete file, got %d instead of %d bytes",
|
||||||
f.qIno.Ino, n, readLen)
|
f.qIno.Ino, n, readLen)
|
||||||
|
f.fs.reportCorruptItem(fmt.Sprint(f.qIno.Ino))
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,11 @@ type FS struct {
|
||||||
// This lock is used by openWriteOnlyFile() to block concurrent opens while
|
// This lock is used by openWriteOnlyFile() to block concurrent opens while
|
||||||
// it relaxes the permissions on a file.
|
// it relaxes the permissions on a file.
|
||||||
openWriteOnlyLock sync.RWMutex
|
openWriteOnlyLock sync.RWMutex
|
||||||
|
// CorruptItems is filled with file or xattr names that have been
|
||||||
|
// skipped (ignored) because they were corrupt. This is used by fsck
|
||||||
|
// to inform the user.
|
||||||
|
// Use the reportCorruptItem() function to push an item.
|
||||||
|
CorruptItems chan string
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ pathfs.FileSystem = &FS{} // Verify that interface is implemented.
|
var _ pathfs.FileSystem = &FS{} // Verify that interface is implemented.
|
||||||
|
@ -601,3 +606,16 @@ func (fs *FS) Access(path string, mode uint32, context *fuse.Context) (code fuse
|
||||||
}
|
}
|
||||||
return fuse.ToStatus(syscall.Access(cPath, mode))
|
return fuse.ToStatus(syscall.Access(cPath, mode))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *FS) reportCorruptItem(item string) {
|
||||||
|
if fs.CorruptItems == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case fs.CorruptItems <- item:
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
tlog.Warn.Printf("BUG: reportCorruptItem: timeout")
|
||||||
|
//debug.PrintStack()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -326,6 +326,7 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Warn.Printf("OpenDir %q: invalid entry %q: Could not read .name: %v",
|
tlog.Warn.Printf("OpenDir %q: invalid entry %q: Could not read .name: %v",
|
||||||
cDirName, cName, err)
|
cDirName, cName, err)
|
||||||
|
fs.reportCorruptItem(cName)
|
||||||
errorCount++
|
errorCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -338,6 +339,7 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Warn.Printf("OpenDir %q: invalid entry %q: %v",
|
tlog.Warn.Printf("OpenDir %q: invalid entry %q: %v",
|
||||||
cDirName, cName, err)
|
cDirName, cName, err)
|
||||||
|
fs.reportCorruptItem(cName)
|
||||||
if runtime.GOOS == "darwin" && cName == dsStoreName {
|
if runtime.GOOS == "darwin" && cName == dsStoreName {
|
||||||
// MacOS creates lots of these files. Log the warning but don't
|
// MacOS creates lots of these files. Log the warning but don't
|
||||||
// increment errorCount - does not warrant returning EIO.
|
// increment errorCount - does not warrant returning EIO.
|
||||||
|
|
|
@ -115,6 +115,7 @@ func (fs *FS) ListXAttr(path string, context *fuse.Context) ([]string, fuse.Stat
|
||||||
name, err := fs.decryptXattrName(curName)
|
name, err := fs.decryptXattrName(curName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Warn.Printf("ListXAttr: invalid xattr name %q: %v", curName, err)
|
tlog.Warn.Printf("ListXAttr: invalid xattr name %q: %v", curName, err)
|
||||||
|
fs.reportCorruptItem(curName)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
|
|
Loading…
Reference in New Issue