From 15b0b4a5fd268b421ddc347e4417b2538a540922 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sat, 15 Aug 2020 16:08:16 +0200 Subject: [PATCH] v2api/reverse: start wiring up -exclude functionality Exclude in readdir is missing. --- internal/fusefrontend_reverse/excluder.go | 20 +++-- .../fusefrontend_reverse/excluder_test.go | 83 +++++++++++++++++++ internal/fusefrontend_reverse/mocks_test.go | 32 +++++++ internal/fusefrontend_reverse/root_node.go | 10 ++- internal/fusefrontend_reverse/rpath.go | 4 + 5 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 internal/fusefrontend_reverse/excluder_test.go create mode 100644 internal/fusefrontend_reverse/mocks_test.go diff --git a/internal/fusefrontend_reverse/excluder.go b/internal/fusefrontend_reverse/excluder.go index 337c3d2..b6cb961 100644 --- a/internal/fusefrontend_reverse/excluder.go +++ b/internal/fusefrontend_reverse/excluder.go @@ -15,15 +15,19 @@ import ( // prepareExcluder creates an object to check if paths are excluded // based on the patterns specified in the command line. func prepareExcluder(args fusefrontend.Args) *ignore.GitIgnore { - if len(args.Exclude) > 0 || len(args.ExcludeWildcard) > 0 || len(args.ExcludeFrom) > 0 { - excluder, err := ignore.CompileIgnoreLines(getExclusionPatterns(args)...) - if err != nil { - tlog.Fatal.Printf("Error compiling exclusion rules: %q", err) - os.Exit(exitcodes.ExcludeError) - } - return excluder + if len(args.Exclude) == 0 && len(args.ExcludeWildcard) == 0 && len(args.ExcludeFrom) == 0 { + return nil } - return nil + patterns := getExclusionPatterns(args) + if len(patterns) == 0 { + panic(patterns) + } + excluder, err := ignore.CompileIgnoreLines(patterns...) + if err != nil { + tlog.Fatal.Printf("Error compiling exclusion rules: %v", err) + os.Exit(exitcodes.ExcludeError) + } + return excluder } // getExclusionPatters prepares a list of patterns to be excluded. diff --git a/internal/fusefrontend_reverse/excluder_test.go b/internal/fusefrontend_reverse/excluder_test.go new file mode 100644 index 0000000..47b430a --- /dev/null +++ b/internal/fusefrontend_reverse/excluder_test.go @@ -0,0 +1,83 @@ +package fusefrontend_reverse + +import ( + "io/ioutil" + "os" + "reflect" + "testing" + + "github.com/rfjakob/gocryptfs/internal/fusefrontend" +) + +func TestShouldNoCreateExcluderIfNoPattersWereSpecified(t *testing.T) { + var args fusefrontend.Args + excluder := prepareExcluder(args) + if excluder != nil { + t.Error("Should not have created excluder") + } +} + +func TestShouldPrefixExcludeValuesWithSlash(t *testing.T) { + var args fusefrontend.Args + args.Exclude = []string{"file1", "dir1/file2.txt"} + args.ExcludeWildcard = []string{"*~", "build/*.o"} + + expected := []string{"/file1", "/dir1/file2.txt", "*~", "build/*.o"} + + patterns := getExclusionPatterns(args) + if !reflect.DeepEqual(patterns, expected) { + t.Errorf("expected %q, got %q", expected, patterns) + } +} + +func TestShouldReadExcludePatternsFromFiles(t *testing.T) { + tmpfile1, err := ioutil.TempFile("", "excludetest") + if err != nil { + t.Fatal(err) + } + exclude1 := tmpfile1.Name() + defer os.Remove(exclude1) + defer tmpfile1.Close() + + tmpfile2, err := ioutil.TempFile("", "excludetest") + if err != nil { + t.Fatal(err) + } + exclude2 := tmpfile2.Name() + defer os.Remove(exclude2) + defer tmpfile2.Close() + + tmpfile1.WriteString("file1.1\n") + tmpfile1.WriteString("file1.2\n") + tmpfile2.WriteString("file2.1\n") + tmpfile2.WriteString("file2.2\n") + + var args fusefrontend.Args + args.ExcludeWildcard = []string{"cmdline1"} + args.ExcludeFrom = []string{exclude1, exclude2} + + // An empty string is returned for the last empty line + // It's ignored when the patterns are actually compiled + expected := []string{"cmdline1", "file1.1", "file1.2", "", "file2.1", "file2.2", ""} + + patterns := getExclusionPatterns(args) + if !reflect.DeepEqual(patterns, expected) { + t.Errorf("expected %q, got %q", expected, patterns) + } +} + +func TestShouldReturnFalseIfThereAreNoExclusions(t *testing.T) { + var rfs RootNode + if rfs.isExcludedPlain("any/path") { + t.Error("Should not exclude any path if no exclusions were specified") + } +} + +func TestShouldCallIgnoreParserToCheckExclusion(t *testing.T) { + rfs, ignorerMock := createRFSWithMocks() + + rfs.isExcludedPlain("some/path") + if ignorerMock.calledWith != "some/path" { + t.Error("Failed to call IgnoreParser") + } +} diff --git a/internal/fusefrontend_reverse/mocks_test.go b/internal/fusefrontend_reverse/mocks_test.go new file mode 100644 index 0000000..2d14c1d --- /dev/null +++ b/internal/fusefrontend_reverse/mocks_test.go @@ -0,0 +1,32 @@ +package fusefrontend_reverse + +import ( + "github.com/rfjakob/gocryptfs/internal/nametransform" +) + +type IgnoreParserMock struct { + toExclude string + calledWith string +} + +func (parser *IgnoreParserMock) MatchesPath(f string) bool { + parser.calledWith = f + return f == parser.toExclude +} + +type NameTransformMock struct { + nametransform.NameTransform +} + +func (n *NameTransformMock) DecryptName(cipherName string, iv []byte) (string, error) { + return "mockdecrypt_" + cipherName, nil +} + +func createRFSWithMocks() (*RootNode, *IgnoreParserMock) { + ignorerMock := &IgnoreParserMock{} + nameTransformMock := &NameTransformMock{} + var rfs RootNode + rfs.excluder = ignorerMock + rfs.nameTransform = nameTransformMock + return &rfs, ignorerMock +} diff --git a/internal/fusefrontend_reverse/root_node.go b/internal/fusefrontend_reverse/root_node.go index 4297ecf..4346306 100644 --- a/internal/fusefrontend_reverse/root_node.go +++ b/internal/fusefrontend_reverse/root_node.go @@ -27,8 +27,8 @@ type RootNode struct { nameTransform nametransform.NameTransformer // Content encryption helper contentEnc *contentenc.ContentEnc - // Tests whether a path is excluded (hiden) from the user. Used by -exclude. - excluder ignore.IgnoreParser + // Tests whether a path is excluded (hidden) from the user. Used by -exclude. + excluder *ignore.GitIgnore // inoMap translates inode numbers from different devices to unique inode // numbers. inoMap *inomap.InoMap @@ -78,3 +78,9 @@ func (rn *RootNode) findLongnameParent(fd int, diriv []byte, longname string) (p } return } + +// isExcludedPlain finds out if the plaintext path "pPath" is +// excluded (used when -exclude is passed by the user). +func (rn *RootNode) isExcludedPlain(pPath string) bool { + return rn.excluder != nil && rn.excluder.MatchesPath(pPath) +} diff --git a/internal/fusefrontend_reverse/rpath.go b/internal/fusefrontend_reverse/rpath.go index 2ac65be..1e44638 100644 --- a/internal/fusefrontend_reverse/rpath.go +++ b/internal/fusefrontend_reverse/rpath.go @@ -109,6 +109,10 @@ func (rn *RootNode) openBackingDir(cPath string) (dirfd int, pName string, err e if err != nil { return } + if rn.isExcludedPlain(pRelPath) { + err = syscall.EPERM + return + } // Open directory, safe against symlink races pDir := filepath.Dir(pRelPath) dirfd, err = syscallcompat.OpenDirNofollow(rn.args.Cipherdir, pDir)