diff options
| -rw-r--r-- | Documentation/MANPAGE.md | 8 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | fsck.go | 52 | ||||
| -rw-r--r-- | internal/fusefrontend/file.go | 2 | ||||
| -rw-r--r-- | internal/fusefrontend/fs.go | 18 | ||||
| -rw-r--r-- | internal/fusefrontend/fs_dir.go | 2 | ||||
| -rw-r--r-- | internal/fusefrontend/xattr.go | 1 | 
7 files changed, 80 insertions, 5 deletions
| diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md index e20b786..1c7e7b9 100644 --- a/Documentation/MANPAGE.md +++ b/Documentation/MANPAGE.md @@ -19,6 +19,9 @@ SYNOPSIS  #### Change password  `gocryptfs -passwd [OPTIONS] CIPHERDIR` +#### Check consistency +`gocryptfs -fsck [OPTIONS] CIPHERDIR` +  DESCRIPTION  =========== @@ -96,6 +99,10 @@ that uses built-in Go crypto.  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  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 @@ -351,6 +358,7 @@ EXIT CODES  22: password is empty (on "-init")    23: could not read gocryptfs.conf    24: could not write gocryptfs.conf (on "-init" or "-password")   +26: fsck found errors    other: please check the error message  SEE ALSO @@ -158,6 +158,8 @@ vNEXT, in progress    ([#218](https://github.com/rfjakob/gocryptfs/issues/218))  * Support extended attributes (xattr) in forward mode    ([#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  * Overwrite secrets in memory with zeros as soon as possible @@ -6,6 +6,7 @@ import (  	"path/filepath"  	"sort"  	"strings" +	"sync"  	"syscall"  	"github.com/hanwen/go-fuse/fuse" @@ -19,17 +20,34 @@ 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) @@ -80,6 +98,19 @@ func (ck *fsckObj) file(path string) {  	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() { @@ -97,7 +128,20 @@ func (ck *fsckObj) file(path string) {  // 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) @@ -120,19 +164,17 @@ func fsck(args *argContainer) {  	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") +		fmt.Printf("fsck summary: no problems found\n")  		return  	} -	fmt.Printf("fsck summary: found %d corrupt files:\n", len(ck.corruptList)) -	for _, path := range ck.corruptList { -		fmt.Printf("  %q\n", path) -	} +	fmt.Printf("fsck summary: %d corrupt files\n", len(ck.corruptList))  	os.Exit(exitcodes.FsckErrors)  } diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go index af13170..72319e9 100644 --- a/internal/fusefrontend/file.go +++ b/internal/fusefrontend/file.go @@ -4,6 +4,7 @@ package fusefrontend  import (  	"bytes" +	"fmt"  	"io"  	"log"  	"os" @@ -99,6 +100,7 @@ func (f *file) readFileID() ([]byte, error) {  		if err == io.EOF && n != 0 {  			tlog.Warn.Printf("readFileID %d: incomplete file, got %d instead of %d bytes",  				f.qIno.Ino, n, readLen) +			f.fs.reportCorruptItem(fmt.Sprint(f.qIno.Ino))  		}  		return nil, err  	} diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index 5f84541..8a3935f 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -38,6 +38,11 @@ type FS struct {  	// This lock is used by openWriteOnlyFile() to block concurrent opens while  	// it relaxes the permissions on a file.  	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. @@ -601,3 +606,16 @@ func (fs *FS) Access(path string, mode uint32, context *fuse.Context) (code fuse  	}  	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 +	} +} diff --git a/internal/fusefrontend/fs_dir.go b/internal/fusefrontend/fs_dir.go index e13afed..089429e 100644 --- a/internal/fusefrontend/fs_dir.go +++ b/internal/fusefrontend/fs_dir.go @@ -326,6 +326,7 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f  			if err != nil {  				tlog.Warn.Printf("OpenDir %q: invalid entry %q: Could not read .name: %v",  					cDirName, cName, err) +				fs.reportCorruptItem(cName)  				errorCount++  				continue  			} @@ -338,6 +339,7 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f  		if err != nil {  			tlog.Warn.Printf("OpenDir %q: invalid entry %q: %v",  				cDirName, cName, err) +			fs.reportCorruptItem(cName)  			if runtime.GOOS == "darwin" && cName == dsStoreName {  				// MacOS creates lots of these files. Log the warning but don't  				// increment errorCount - does not warrant returning EIO. diff --git a/internal/fusefrontend/xattr.go b/internal/fusefrontend/xattr.go index 1d628b7..faaebd4 100644 --- a/internal/fusefrontend/xattr.go +++ b/internal/fusefrontend/xattr.go @@ -115,6 +115,7 @@ func (fs *FS) ListXAttr(path string, context *fuse.Context) ([]string, fuse.Stat  		name, err := fs.decryptXattrName(curName)  		if err != nil {  			tlog.Warn.Printf("ListXAttr: invalid xattr name %q: %v", curName, err) +			fs.reportCorruptItem(curName)  			continue  		}  		names = append(names, name) | 
