From f28d85fad599ffaef9a8e1f353911c81a6605d2f Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sun, 1 Apr 2018 21:23:32 +0200 Subject: [PATCH] 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. --- fsck.go | 101 +++++++++++++++++- internal/exitcodes/exitcodes.go | 2 + main.go | 1 + tests/cli/cli_test.go | 18 +--- .../broken_fs_v1.4/GUvJFSfy7S1AXUdy4pDRLw | 1 + .../mWEr9JLch2FW40qhbnPgpg | 0 .../broken_fs_v1.4/OtrNpznB8aMTKPi6bopM2g | Bin 0 -> 63 bytes .../_y58usbKXq_YRPMKfC3TNw | 0 .../PnkpLqHimGudw4C3jFY-Yw/gocryptfs.diriv | 2 + tests/fsck/broken_fs_v1.4/gocryptfs.conf | 20 ++++ tests/fsck/broken_fs_v1.4/gocryptfs.diriv | 2 + tests/fsck/broken_fs_v1.4/invalid_file_name.3 | 0 tests/fsck/broken_fs_v1.4/invalid_file_name_2 | 0 .../broken_fs_v1.4/invalid_file_name____1 | 0 .../broken_fs_v1.4/qOA8a4yuvgbMFpz7277R8A | 1 + .../broken_fs_v1.4/s-P7PcQDUcVkoeMDnC3EYA | 1 + .../broken_fs_v1.4/vDKs8a7UtM3PmEKk9wlPcA | Bin 0 -> 77 bytes tests/fsck/fsck_test.go | 26 +++++ tests/fsck/run_fsck.bash | 2 + tests/test_helpers/helpers.go | 12 +++ 20 files changed, 171 insertions(+), 18 deletions(-) create mode 120000 tests/fsck/broken_fs_v1.4/GUvJFSfy7S1AXUdy4pDRLw create mode 100644 tests/fsck/broken_fs_v1.4/K2m0E6qzIfoLkVZJanoUiQ/mWEr9JLch2FW40qhbnPgpg create mode 100644 tests/fsck/broken_fs_v1.4/OtrNpznB8aMTKPi6bopM2g create mode 100644 tests/fsck/broken_fs_v1.4/PnkpLqHimGudw4C3jFY-Yw/_y58usbKXq_YRPMKfC3TNw create mode 100644 tests/fsck/broken_fs_v1.4/PnkpLqHimGudw4C3jFY-Yw/gocryptfs.diriv create mode 100644 tests/fsck/broken_fs_v1.4/gocryptfs.conf create mode 100644 tests/fsck/broken_fs_v1.4/gocryptfs.diriv create mode 100644 tests/fsck/broken_fs_v1.4/invalid_file_name.3 create mode 100644 tests/fsck/broken_fs_v1.4/invalid_file_name_2 create mode 100644 tests/fsck/broken_fs_v1.4/invalid_file_name____1 create mode 100644 tests/fsck/broken_fs_v1.4/qOA8a4yuvgbMFpz7277R8A create mode 120000 tests/fsck/broken_fs_v1.4/s-P7PcQDUcVkoeMDnC3EYA create mode 100644 tests/fsck/broken_fs_v1.4/vDKs8a7UtM3PmEKk9wlPcA create mode 100644 tests/fsck/fsck_test.go create mode 100755 tests/fsck/run_fsck.bash diff --git a/fsck.go b/fsck.go index 645cf91..7741d19 100644 --- a/fsck.go +++ b/fsck.go @@ -1,9 +1,104 @@ package main 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) { - log.Panic("Not yet implemented") +type fsckObj struct { + 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) + } } diff --git a/internal/exitcodes/exitcodes.go b/internal/exitcodes/exitcodes.go index 209656c..36f2aae 100644 --- a/internal/exitcodes/exitcodes.go +++ b/internal/exitcodes/exitcodes.go @@ -61,6 +61,8 @@ const ( // Profiler - error occoured when trying to write cpu or memory profile or // execution trace Profiler = 25 + // FsckErrors - the filesystem check found errors + FsckErrors = 26 ) // Err wraps an error with an associated numeric exit code diff --git a/main.go b/main.go index ff137c4..a69ec9e 100644 --- a/main.go +++ b/main.go @@ -287,5 +287,6 @@ func main() { // "-fsck" if args.fsck { fsck(&args) + os.Exit(0) } } diff --git a/tests/cli/cli_test.go b/tests/cli/cli_test.go index 2cdaf37..2f27ec7 100644 --- a/tests/cli/cli_test.go +++ b/tests/cli/cli_test.go @@ -18,18 +18,6 @@ import ( 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) { test_helpers.ResetTmpDir(false) r := m.Run() @@ -344,7 +332,7 @@ func TestMountPasswordIncorrect(t *testing.T) { cDir := test_helpers.InitFS(t) // Create filesystem with password "test" pDir := cDir + ".mnt" err := test_helpers.Mount(cDir, pDir, false, "-extpass", "echo WRONG", "-wpanic=false") - exitCode := extractExitCode(err) + exitCode := test_helpers.ExtractCmdExitCode(err) if exitCode != exitcodes.PasswordIncorrect { t.Errorf("want=%d, got=%d", exitcodes.PasswordIncorrect, exitCode) } @@ -373,7 +361,7 @@ func TestPasswdPasswordIncorrect(t *testing.T) { t.Fatal(err) } err = cmd.Wait() - exitCode := extractExitCode(err) + exitCode := test_helpers.ExtractCmdExitCode(err) if exitCode != exitcodes.PasswordIncorrect { t.Errorf("want=%d, got=%d", exitcodes.PasswordIncorrect, exitCode) } @@ -444,7 +432,7 @@ func TestMultipleOperationFlags(t *testing.T) { //t.Logf("testing %v", args) cmd := exec.Command(test_helpers.GocryptfsBinary, args...) err := cmd.Run() - exitCode := extractExitCode(err) + exitCode := test_helpers.ExtractCmdExitCode(err) if exitCode != exitcodes.Usage { t.Fatalf("this should have failed with code %d, but returned %d", exitcodes.Usage, exitCode) diff --git a/tests/fsck/broken_fs_v1.4/GUvJFSfy7S1AXUdy4pDRLw b/tests/fsck/broken_fs_v1.4/GUvJFSfy7S1AXUdy4pDRLw new file mode 120000 index 0000000..7bbd005 --- /dev/null +++ b/tests/fsck/broken_fs_v1.4/GUvJFSfy7S1AXUdy4pDRLw @@ -0,0 +1 @@ +RFPnVN8r1HjIrFVJ8PffC7ObzAIeBx3DQh8FbgvmbT8Ho8mU \ No newline at end of file diff --git a/tests/fsck/broken_fs_v1.4/K2m0E6qzIfoLkVZJanoUiQ/mWEr9JLch2FW40qhbnPgpg b/tests/fsck/broken_fs_v1.4/K2m0E6qzIfoLkVZJanoUiQ/mWEr9JLch2FW40qhbnPgpg new file mode 100644 index 0000000..e69de29 diff --git a/tests/fsck/broken_fs_v1.4/OtrNpznB8aMTKPi6bopM2g b/tests/fsck/broken_fs_v1.4/OtrNpznB8aMTKPi6bopM2g new file mode 100644 index 0000000000000000000000000000000000000000..dab7c69a8278bcdf34d0eb92ba0cab70da662c04 GIT binary patch literal 63 zcmV-F0KoqM0x1s-oWd$zBlx@uf^kT8zW<#t2B6Gt`I}Q!aBJ7r#K;TBwF66R^!<^n Vf&yBUt!t$&)mZlM{+t>MU@4I09-#mL literal 0 HcmV?d00001 diff --git a/tests/fsck/broken_fs_v1.4/PnkpLqHimGudw4C3jFY-Yw/_y58usbKXq_YRPMKfC3TNw b/tests/fsck/broken_fs_v1.4/PnkpLqHimGudw4C3jFY-Yw/_y58usbKXq_YRPMKfC3TNw new file mode 100644 index 0000000..e69de29 diff --git a/tests/fsck/broken_fs_v1.4/PnkpLqHimGudw4C3jFY-Yw/gocryptfs.diriv b/tests/fsck/broken_fs_v1.4/PnkpLqHimGudw4C3jFY-Yw/gocryptfs.diriv new file mode 100644 index 0000000..178763a --- /dev/null +++ b/tests/fsck/broken_fs_v1.4/PnkpLqHimGudw4C3jFY-Yw/gocryptfs.diriv @@ -0,0 +1,2 @@ +f—`Êá +{g*@w¹6£ \ No newline at end of file diff --git a/tests/fsck/broken_fs_v1.4/gocryptfs.conf b/tests/fsck/broken_fs_v1.4/gocryptfs.conf new file mode 100644 index 0000000..cedf571 --- /dev/null +++ b/tests/fsck/broken_fs_v1.4/gocryptfs.conf @@ -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" + ] +} diff --git a/tests/fsck/broken_fs_v1.4/gocryptfs.diriv b/tests/fsck/broken_fs_v1.4/gocryptfs.diriv new file mode 100644 index 0000000..d7bbcda --- /dev/null +++ b/tests/fsck/broken_fs_v1.4/gocryptfs.diriv @@ -0,0 +1,2 @@ +å8]ÃjM +âPg¡çDóç \ No newline at end of file diff --git a/tests/fsck/broken_fs_v1.4/invalid_file_name.3 b/tests/fsck/broken_fs_v1.4/invalid_file_name.3 new file mode 100644 index 0000000..e69de29 diff --git a/tests/fsck/broken_fs_v1.4/invalid_file_name_2 b/tests/fsck/broken_fs_v1.4/invalid_file_name_2 new file mode 100644 index 0000000..e69de29 diff --git a/tests/fsck/broken_fs_v1.4/invalid_file_name____1 b/tests/fsck/broken_fs_v1.4/invalid_file_name____1 new file mode 100644 index 0000000..e69de29 diff --git a/tests/fsck/broken_fs_v1.4/qOA8a4yuvgbMFpz7277R8A b/tests/fsck/broken_fs_v1.4/qOA8a4yuvgbMFpz7277R8A new file mode 100644 index 0000000..8621a03 --- /dev/null +++ b/tests/fsck/broken_fs_v1.4/qOA8a4yuvgbMFpz7277R8A @@ -0,0 +1 @@ +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX diff --git a/tests/fsck/broken_fs_v1.4/s-P7PcQDUcVkoeMDnC3EYA b/tests/fsck/broken_fs_v1.4/s-P7PcQDUcVkoeMDnC3EYA new file mode 120000 index 0000000..158f38a --- /dev/null +++ b/tests/fsck/broken_fs_v1.4/s-P7PcQDUcVkoeMDnC3EYA @@ -0,0 +1 @@ +Qso5-4WJ2iAxF674mUarvuNbIMTLSJLqfEh3Chq3I_Rm2sY2 \ No newline at end of file diff --git a/tests/fsck/broken_fs_v1.4/vDKs8a7UtM3PmEKk9wlPcA b/tests/fsck/broken_fs_v1.4/vDKs8a7UtM3PmEKk9wlPcA new file mode 100644 index 0000000000000000000000000000000000000000..1dff1a1e46ff9a7f7b02bed545117b3e03249f2f GIT binary patch literal 77 zcmV-T0J8r80(W*G#64!|!Qdr7Q3oPQ6jDx)Z=sUlq=6f7r5R?=XBP*KN@Crt^k!<% jvH;+NtXBfYm&k9eJM#Twchsi@QPN}D7FYn8q#>>f8p$Is literal 0 HcmV?d00001 diff --git a/tests/fsck/fsck_test.go b/tests/fsck/fsck_test.go new file mode 100644 index 0000000..7506636 --- /dev/null +++ b/tests/fsck/fsck_test.go @@ -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) + } +} diff --git a/tests/fsck/run_fsck.bash b/tests/fsck/run_fsck.bash new file mode 100755 index 0000000..9637381 --- /dev/null +++ b/tests/fsck/run_fsck.bash @@ -0,0 +1,2 @@ +#!/bin/bash +exec ../../gocryptfs -fsck -extpass "echo test" broken_fs_v1.4 diff --git a/tests/test_helpers/helpers.go b/tests/test_helpers/helpers.go index 5c0319e..f92fb79 100644 --- a/tests/test_helpers/helpers.go +++ b/tests/test_helpers/helpers.go @@ -400,3 +400,15 @@ func QueryCtlSock(t *testing.T, socketPath string, req ctlsock.RequestStruct) (r json.Unmarshal(buf, &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 +}