fsck: add initial implementation

Most corruption cases except xattr should be covered.
With test filesystem.

The output is still pretty ugly. xattr support will
be added in the next commits.
This commit is contained in:
Jakob Unterwurzacher 2018-04-01 21:23:32 +02:00
parent fb06c65ee9
commit f28d85fad5
20 changed files with 171 additions and 18 deletions

101
fsck.go
View File

@ -1,9 +1,104 @@
package main package main
import ( import (
"log" "fmt"
"os"
"path/filepath"
"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"
) )
func fsck(args *argContainer) { type fsckObj struct {
log.Panic("Not yet implemented") fs *fusefrontend.FS
errorCount int
}
// Recursively check dir for corruption
func (ck *fsckObj) dir(path string) {
//fmt.Printf("ck.dir %q\n", path)
entries, status := ck.fs.OpenDir(path, nil)
if !status.Ok() {
fmt.Printf("fsck: error opening dir %q: %v\n", path, status)
ck.errorCount++
return
}
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: unhandle file type %x\n", filetype)
}
}
}
func (ck *fsckObj) symlink(path string) {
_, status := ck.fs.Readlink(path, nil)
if !status.Ok() {
fmt.Printf("fsck: error reading symlink %q: %v\n", path, status)
ck.errorCount++
}
}
// check file for corruption
func (ck *fsckObj) file(path string) {
//fmt.Printf("ck.file %q\n", path)
f, status := ck.fs.Open(path, syscall.O_RDONLY, nil)
if !status.Ok() {
fmt.Printf("fsck: error opening file %q: %v\n", path, status)
ck.errorCount++
return
}
defer f.Release()
buf := make([]byte, fuse.MAX_KERNEL_WRITE)
var off int64
for {
result, status := f.Read(buf, off)
if !status.Ok() {
fmt.Printf("fsck: error reading file %q at offset %d: %v\n", path, off, status)
ck.errorCount++
return
}
// EOF
if result.Size() == 0 {
return
}
off += int64(result.Size())
}
}
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)
defer wipeKeys()
fs := pfs.(*fusefrontend.FS)
ck := fsckObj{
fs: fs,
}
ck.dir("")
fmt.Printf("fsck: found %d problems\n", ck.errorCount)
if ck.errorCount != 0 {
os.Exit(exitcodes.FsckErrors)
}
} }

View File

@ -61,6 +61,8 @@ const (
// Profiler - error occoured when trying to write cpu or memory profile or // Profiler - error occoured when trying to write cpu or memory profile or
// execution trace // execution trace
Profiler = 25 Profiler = 25
// FsckErrors - the filesystem check found errors
FsckErrors = 26
) )
// Err wraps an error with an associated numeric exit code // Err wraps an error with an associated numeric exit code

View File

@ -287,5 +287,6 @@ func main() {
// "-fsck" // "-fsck"
if args.fsck { if args.fsck {
fsck(&args) fsck(&args)
os.Exit(0)
} }
} }

View File

@ -18,18 +18,6 @@ import (
var testPw = []byte("test") var testPw = []byte("test")
// Extract the exit code from an error value that was returned from
// exec.Run()
func extractExitCode(err error) int {
if err == nil {
return 0
}
// OMG this is convoluted
err2 := err.(*exec.ExitError)
code := err2.Sys().(syscall.WaitStatus).ExitStatus()
return code
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
test_helpers.ResetTmpDir(false) test_helpers.ResetTmpDir(false)
r := m.Run() r := m.Run()
@ -344,7 +332,7 @@ func TestMountPasswordIncorrect(t *testing.T) {
cDir := test_helpers.InitFS(t) // Create filesystem with password "test" cDir := test_helpers.InitFS(t) // Create filesystem with password "test"
pDir := cDir + ".mnt" pDir := cDir + ".mnt"
err := test_helpers.Mount(cDir, pDir, false, "-extpass", "echo WRONG", "-wpanic=false") err := test_helpers.Mount(cDir, pDir, false, "-extpass", "echo WRONG", "-wpanic=false")
exitCode := extractExitCode(err) exitCode := test_helpers.ExtractCmdExitCode(err)
if exitCode != exitcodes.PasswordIncorrect { if exitCode != exitcodes.PasswordIncorrect {
t.Errorf("want=%d, got=%d", exitcodes.PasswordIncorrect, exitCode) t.Errorf("want=%d, got=%d", exitcodes.PasswordIncorrect, exitCode)
} }
@ -373,7 +361,7 @@ func TestPasswdPasswordIncorrect(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
err = cmd.Wait() err = cmd.Wait()
exitCode := extractExitCode(err) exitCode := test_helpers.ExtractCmdExitCode(err)
if exitCode != exitcodes.PasswordIncorrect { if exitCode != exitcodes.PasswordIncorrect {
t.Errorf("want=%d, got=%d", exitcodes.PasswordIncorrect, exitCode) t.Errorf("want=%d, got=%d", exitcodes.PasswordIncorrect, exitCode)
} }
@ -444,7 +432,7 @@ func TestMultipleOperationFlags(t *testing.T) {
//t.Logf("testing %v", args) //t.Logf("testing %v", args)
cmd := exec.Command(test_helpers.GocryptfsBinary, args...) cmd := exec.Command(test_helpers.GocryptfsBinary, args...)
err := cmd.Run() err := cmd.Run()
exitCode := extractExitCode(err) exitCode := test_helpers.ExtractCmdExitCode(err)
if exitCode != exitcodes.Usage { if exitCode != exitcodes.Usage {
t.Fatalf("this should have failed with code %d, but returned %d", t.Fatalf("this should have failed with code %d, but returned %d",
exitcodes.Usage, exitCode) exitcodes.Usage, exitCode)

View File

@ -0,0 +1 @@
RFPnVN8r1HjIrFVJ8PffC7ObzAIeBx3DQh8FbgvmbT8Ho8mU

Binary file not shown.

View File

@ -0,0 +1,2 @@
f—`Κα
{g*@wΉ6£

View File

@ -0,0 +1,20 @@
{
"Creator": "gocryptfs v1.4.4-13-ga4f3a7d-dirty",
"EncryptedKey": "yfnIx9uKv2ZX80KXOlfb4fWws3RNqvcjsx/Ajr0x4pRfg8NLqhWRpEUWGk8NSdVFXKWVDgdhSoYkbfVnFXl07g==",
"ScryptObject": {
"Salt": "R78m123zJxxO6uU1bg6/0azppry1FoGdH1/Op1xFq+4=",
"N": 65536,
"R": 8,
"P": 1,
"KeyLen": 32
},
"Version": 2,
"FeatureFlags": [
"GCMIV128",
"HKDF",
"DirIV",
"EMENames",
"LongNames",
"Raw64"
]
}

View File

@ -0,0 +1,2 @@
ε8]ΓjM
βPgηDση

View File

@ -0,0 +1 @@
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

View File

@ -0,0 +1 @@
Qso5-4WJ2iAxF674mUarvuNbIMTLSJLqfEh3Chq3I_Rm2sY2

Binary file not shown.

26
tests/fsck/fsck_test.go Normal file
View File

@ -0,0 +1,26 @@
package fsck
import (
"os/exec"
"strings"
"testing"
"github.com/rfjakob/gocryptfs/internal/exitcodes"
"github.com/rfjakob/gocryptfs/tests/test_helpers"
)
func TestBrokenFsV14(t *testing.T) {
cmd := exec.Command(test_helpers.GocryptfsBinary, "-fsck", "-extpass", "echo test", "broken_fs_v1.4")
outBin, err := cmd.CombinedOutput()
out := string(outBin)
t.Log(out)
code := test_helpers.ExtractCmdExitCode(err)
if code != exitcodes.FsckErrors {
t.Errorf("wrong exit code, have=%d want=%d", code, exitcodes.FsckErrors)
}
lines := strings.Split(out, "\n")
summaryLine := lines[len(lines)-2]
if summaryLine != "fsck: found 5 problems" {
t.Errorf("wrong summary line: %q", summaryLine)
}
}

2
tests/fsck/run_fsck.bash Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
exec ../../gocryptfs -fsck -extpass "echo test" broken_fs_v1.4

View File

@ -400,3 +400,15 @@ func QueryCtlSock(t *testing.T, socketPath string, req ctlsock.RequestStruct) (r
json.Unmarshal(buf, &response) json.Unmarshal(buf, &response)
return response return response
} }
// Extract the exit code from an error value that was returned from
// exec / cmd.Run()
func ExtractCmdExitCode(err error) int {
if err == nil {
return 0
}
// OMG this is convoluted
err2 := err.(*exec.ExitError)
code := err2.Sys().(syscall.WaitStatus).ExitStatus()
return code
}