reverse mode: support wildcard exclude (--exclude-wildcard)
This adds support for gitignore-like wildcards and exclude patters in reverse mode. It (somewhat) fixes #273: no regexp support, but the syntax should be powerful enough to satisfy most needs. Also, since adding a lot of --exclude options can be tedious, it adds the --exclude-from option to read patterns from a file (or files).
This commit is contained in:
parent
73f9e2374d
commit
3bc100aeb3
@ -78,10 +78,29 @@ harvest enough entropy.
|
|||||||
|
|
||||||
#### -e PATH, -exclude PATH
|
#### -e PATH, -exclude PATH
|
||||||
Only for reverse mode: exclude relative plaintext path from the encrypted
|
Only for reverse mode: exclude relative plaintext path from the encrypted
|
||||||
view. Can be passed multiple times. Example:
|
view, matching only from root of mounted filesystem. Can be passed multiple
|
||||||
|
times. Example:
|
||||||
|
|
||||||
gocryptfs -reverse -exclude Music -exclude Movies /home/user /mnt/user.encrypted
|
gocryptfs -reverse -exclude Music -exclude Movies /home/user /mnt/user.encrypted
|
||||||
|
|
||||||
|
See also `-exclude-wildcard`, `-exclude-from` and the [EXCLUDING FILES](#excluding-files) section.
|
||||||
|
|
||||||
|
#### -ew PATH, -exclude-wildcard PATH
|
||||||
|
Only for reverse mode: exclude paths from the encrypted view, matching anywhere.
|
||||||
|
Wildcards supported. Can be passed multiple times. Example:
|
||||||
|
|
||||||
|
gocryptfs -reverse -exclude-wildcard '*~' /home/user /mnt/user.encrypted
|
||||||
|
|
||||||
|
See also `-exclude`, `-exclude-from` and the [EXCLUDING FILES](#excluding-files) section.
|
||||||
|
|
||||||
|
#### -exclude-from FILE
|
||||||
|
Only for reverse mode: reads exclusion patters (using `-exclude-wildcard` syntax)
|
||||||
|
from a file. Can be passed multiple times. Example:
|
||||||
|
|
||||||
|
gocryptfs -reverse -exclude-from ~/crypt-exclusions /home/user /mnt/user.encrypted
|
||||||
|
|
||||||
|
See also `-exclude`, `-exclude-wildcard` and the [EXCLUDING FILES](#excluding-files) section.
|
||||||
|
|
||||||
#### -exec, -noexec
|
#### -exec, -noexec
|
||||||
Enable (`-exec`) or disable (`-noexec`) executables in a gocryptfs mount
|
Enable (`-exec`) or disable (`-noexec`) executables in a gocryptfs mount
|
||||||
(default: `-exec`). If both are specified, `-noexec` takes precedence.
|
(default: `-exec`). If both are specified, `-noexec` takes precedence.
|
||||||
@ -404,6 +423,67 @@ automated testing as it does not provide any security.
|
|||||||
Stop option parsing. Helpful when CIPHERDIR may start with a
|
Stop option parsing. Helpful when CIPHERDIR may start with a
|
||||||
dash "-".
|
dash "-".
|
||||||
|
|
||||||
|
EXCLUDING FILES
|
||||||
|
===============
|
||||||
|
|
||||||
|
In reverse mode, it is possible to exclude files from the encrypted view, using
|
||||||
|
the `-exclude`, `-exclude-wildcard` and `-exclude-from` options.
|
||||||
|
|
||||||
|
`-exclude` matches complete paths, so `-exclude file.txt` only excludes a file
|
||||||
|
named `file.txt` in the root of the mounted filesystem; files named `file.txt`
|
||||||
|
in subdirectories are still visible. (This option is kept for compatibility
|
||||||
|
with the behavior up to version 1.6.x)
|
||||||
|
|
||||||
|
`-exclude-wildcard` matches files anywhere, so `-exclude-wildcard file.txt`
|
||||||
|
excludes files named `file.txt` in any directory. If you want to match complete
|
||||||
|
paths, you can prefix the filename with a `/`: `-exclude-wildcard /file.txt`
|
||||||
|
excludes only `file.txt` in the root of the mounted filesystem.
|
||||||
|
|
||||||
|
If there are many exclusions, you can use `-exclude-from` to read exclusion
|
||||||
|
patterns from a file. The syntax is that of `-exclude-wildcard`, so use a
|
||||||
|
leading `/` to match complete paths.
|
||||||
|
|
||||||
|
The rules for exclusion are that of [gitignore](https://git-scm.com/docs/gitignore#_pattern_format).
|
||||||
|
In short:
|
||||||
|
|
||||||
|
1. A blank line matches no files, so it can serve as a separator
|
||||||
|
for readability.
|
||||||
|
2. A line starting with `#` serves as a comment. Put a backslash (`\`)
|
||||||
|
in front of the first hash for patterns that begin with a hash.
|
||||||
|
3. Trailing spaces are ignored unless they are quoted with backslash (`\`).
|
||||||
|
4. An optional prefix `!` negates the pattern; any matching file
|
||||||
|
excluded by a previous pattern will become included again. It is not
|
||||||
|
possible to re-include a file if a parent directory of that file is
|
||||||
|
excluded. Put a backslash (`\`) in front of the first `!` for
|
||||||
|
patterns that begin with a literal `!`, for example, `\!important!.txt`.
|
||||||
|
5. If the pattern ends with a slash, it is removed for the purpose of the
|
||||||
|
following description, but it would only find a match with a directory.
|
||||||
|
In other words, `foo/` will match a directory foo and paths underneath it,
|
||||||
|
but will not match a regular file or a symbolic link foo.
|
||||||
|
6. If the pattern does not contain a slash `/`, it is treated as a shell glob
|
||||||
|
pattern and checked for a match against the pathname relative to the
|
||||||
|
root of the mounted filesystem.
|
||||||
|
7. Otherwise, the pattern is treated as a shell glob suitable for
|
||||||
|
consumption by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the
|
||||||
|
pattern will not match a `/` in the pathname. For example,
|
||||||
|
`Documentation/*.html` matches `Documentation/git.html` but not
|
||||||
|
`Documentation/ppc/ppc.html` or `tools/perf/Documentation/perf.html`.
|
||||||
|
8. A leading slash matches the beginning of the pathname. For example,
|
||||||
|
`/*.c` matches `cat-file.c` but not `mozilla-sha1/sha1.c`.
|
||||||
|
9. Two consecutive asterisks (`**`) in patterns matched against full
|
||||||
|
pathname may have special meaning:
|
||||||
|
i. A leading `**` followed by a slash means match in all directories.
|
||||||
|
For example, `**/foo` matches file or directory `foo` anywhere,
|
||||||
|
the same as pattern `foo`. `**/foo/bar` matches file or directory
|
||||||
|
`bar` anywhere that is directly under directory `foo`.
|
||||||
|
ii. A trailing `/**` matches everything inside. For example, `abc/**`
|
||||||
|
matches all files inside directory `abc`, with infinite depth.
|
||||||
|
iii. A slash followed by two consecutive asterisks then a slash matches
|
||||||
|
zero or more directories. For example, `a/**/b` matches `a/b`,
|
||||||
|
`a/x/b`, `a/x/y/b` and so on.
|
||||||
|
iv. Other consecutive asterisks are considered invalid.
|
||||||
|
|
||||||
|
|
||||||
EXAMPLES
|
EXAMPLES
|
||||||
========
|
========
|
||||||
|
|
||||||
|
10
cli_args.go
10
cli_args.go
@ -36,8 +36,8 @@ type argContainer struct {
|
|||||||
memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string
|
memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string
|
||||||
// -extpass can be passed multiple times
|
// -extpass can be passed multiple times
|
||||||
extpass multipleStrings
|
extpass multipleStrings
|
||||||
// For reverse mode, -exclude is available. It can be specified multiple times.
|
// For reverse mode, several ways to specify exclusions. All can be specified multiple times.
|
||||||
exclude multipleStrings
|
exclude, excludeWildcard, excludeFrom multipleStrings
|
||||||
// Configuration file name override
|
// Configuration file name override
|
||||||
config string
|
config string
|
||||||
notifypid, scryptn int
|
notifypid, scryptn int
|
||||||
@ -193,9 +193,13 @@ func parseCliOpts() (args argContainer) {
|
|||||||
flagSet.StringVar(&args.force_owner, "force_owner", "", "uid:gid pair to coerce ownership")
|
flagSet.StringVar(&args.force_owner, "force_owner", "", "uid:gid pair to coerce ownership")
|
||||||
flagSet.StringVar(&args.trace, "trace", "", "Write execution trace to file")
|
flagSet.StringVar(&args.trace, "trace", "", "Write execution trace to file")
|
||||||
|
|
||||||
// -e, --exclude
|
// Exclusion options
|
||||||
flagSet.Var(&args.exclude, "e", "Alias for -exclude")
|
flagSet.Var(&args.exclude, "e", "Alias for -exclude")
|
||||||
flagSet.Var(&args.exclude, "exclude", "Exclude relative path from reverse view")
|
flagSet.Var(&args.exclude, "exclude", "Exclude relative path from reverse view")
|
||||||
|
flagSet.Var(&args.excludeWildcard, "ew", "Alias for -exclude-wildcard")
|
||||||
|
flagSet.Var(&args.excludeWildcard, "exclude-wildcard", "Exclude path from reverse view, supporting wildcards")
|
||||||
|
flagSet.Var(&args.excludeFrom, "exclude-from", "File from which to read exclusion patterns (with -exclude-wildcard syntax)")
|
||||||
|
|
||||||
// -extpass
|
// -extpass
|
||||||
flagSet.Var(&args.extpass, "extpass", "Use external program for the password prompt")
|
flagSet.Var(&args.extpass, "extpass", "Use external program for the password prompt")
|
||||||
|
|
||||||
|
@ -30,6 +30,13 @@ type Args struct {
|
|||||||
SerializeReads bool
|
SerializeReads bool
|
||||||
// Force decode even if integrity check fails (openSSL only)
|
// Force decode even if integrity check fails (openSSL only)
|
||||||
ForceDecode bool
|
ForceDecode bool
|
||||||
// Exclude is a list of paths to make inaccessible
|
// Exclude is a list of paths to make inaccessible, starting match at
|
||||||
|
// the filesystem root
|
||||||
Exclude []string
|
Exclude []string
|
||||||
|
// ExcludeWildcards is a list of paths to make inaccessible, matched
|
||||||
|
// anywhere, and supporting wildcards
|
||||||
|
ExcludeWildcard []string
|
||||||
|
// ExcludeFrom is a list of files from which to read exclusion patterns
|
||||||
|
// (with wildcard syntax)
|
||||||
|
ExcludeFrom []string
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ type FS struct {
|
|||||||
// states
|
// states
|
||||||
dirIVLock sync.RWMutex
|
dirIVLock sync.RWMutex
|
||||||
// Filename encryption helper
|
// Filename encryption helper
|
||||||
nameTransform *nametransform.NameTransform
|
nameTransform nametransform.NameTransformer
|
||||||
// Content encryption helper
|
// Content encryption helper
|
||||||
contentEnc *contentenc.ContentEnc
|
contentEnc *contentenc.ContentEnc
|
||||||
// This lock is used by openWriteOnlyFile() to block concurrent opens while
|
// This lock is used by openWriteOnlyFile() to block concurrent opens while
|
||||||
@ -62,7 +62,7 @@ type FS struct {
|
|||||||
//var _ pathfs.FileSystem = &FS{} // Verify that interface is implemented.
|
//var _ pathfs.FileSystem = &FS{} // Verify that interface is implemented.
|
||||||
|
|
||||||
// NewFS returns a new encrypted FUSE overlay filesystem.
|
// NewFS returns a new encrypted FUSE overlay filesystem.
|
||||||
func NewFS(args Args, c *contentenc.ContentEnc, n *nametransform.NameTransform) *FS {
|
func NewFS(args Args, c *contentenc.ContentEnc, n nametransform.NameTransformer) *FS {
|
||||||
if args.SerializeReads {
|
if args.SerializeReads {
|
||||||
serialize_reads.InitSerializer()
|
serialize_reads.InitSerializer()
|
||||||
}
|
}
|
||||||
@ -399,7 +399,7 @@ func (fs *FS) decryptSymlinkTarget(cData64 string) (string, error) {
|
|||||||
if cData64 == "" {
|
if cData64 == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
cData, err := fs.nameTransform.B64.DecodeString(cData64)
|
cData, err := fs.nameTransform.B64DecodeString(cData64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -472,7 +472,7 @@ func (fs *FS) encryptSymlinkTarget(data string) (cData64 string) {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
cData := fs.contentEnc.EncryptBlock([]byte(data), 0, nil)
|
cData := fs.contentEnc.EncryptBlock([]byte(data), 0, nil)
|
||||||
cData64 = fs.nameTransform.B64.EncodeToString(cData)
|
cData64 = fs.nameTransform.B64EncodeToString(cData)
|
||||||
return cData64
|
return cData64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ func (fs *FS) decryptXattrValue(cData []byte) (data []byte, err error) {
|
|||||||
}
|
}
|
||||||
// This backward compatibility is needed to support old
|
// This backward compatibility is needed to support old
|
||||||
// file systems having xattr values base64-encoded.
|
// file systems having xattr values base64-encoded.
|
||||||
cData, err2 := fs.nameTransform.B64.DecodeString(string(cData))
|
cData, err2 := fs.nameTransform.B64DecodeString(string(cData))
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
// Looks like the value was not base64-encoded, but just corrupt.
|
// Looks like the value was not base64-encoded, but just corrupt.
|
||||||
// Return the original decryption error: err1
|
// Return the original decryption error: err1
|
||||||
|
66
internal/fusefrontend_reverse/excluder.go
Normal file
66
internal/fusefrontend_reverse/excluder.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package fusefrontend_reverse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||||
|
|
||||||
|
"github.com/sabhiram/go-gitignore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// prepareExcluder creates an object to check if paths are excluded
|
||||||
|
// based on the patterns specified in the command line.
|
||||||
|
func (rfs *ReverseFS) prepareExcluder(args fusefrontend.Args) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
rfs.excluder = excluder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getExclusionPatters prepares a list of patterns to be excluded.
|
||||||
|
// Patterns passed in the -exclude command line option are prefixed
|
||||||
|
// with a leading '/' to preserve backwards compatibility (before
|
||||||
|
// wildcard matching was implemented, exclusions always were matched
|
||||||
|
// agains the full path).
|
||||||
|
func getExclusionPatterns(args fusefrontend.Args) []string {
|
||||||
|
patterns := make([]string, len(args.Exclude)+len(args.ExcludeWildcard))
|
||||||
|
// add -exclude
|
||||||
|
for i, p := range args.Exclude {
|
||||||
|
patterns[i] = "/" + p
|
||||||
|
}
|
||||||
|
// add -exclude-wildcard
|
||||||
|
copy(patterns[len(args.Exclude):], args.ExcludeWildcard)
|
||||||
|
// add -exclude-from
|
||||||
|
for _, file := range args.ExcludeFrom {
|
||||||
|
lines, err := getLines(file)
|
||||||
|
if err != nil {
|
||||||
|
tlog.Fatal.Printf("Error reading exclusion patterns: %q", err)
|
||||||
|
os.Exit(exitcodes.ExcludeError)
|
||||||
|
}
|
||||||
|
patterns = append(patterns, lines...)
|
||||||
|
}
|
||||||
|
return patterns
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLines reads a file and splits it into lines
|
||||||
|
func getLines(file string) ([]string, error) {
|
||||||
|
buffer, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return strings.Split(string(buffer), "\n"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isExcludedPlain finds out if the plaintext path "pPath" is
|
||||||
|
// excluded (used when -exclude is passed by the user).
|
||||||
|
func (rfs *ReverseFS) isExcludedPlain(pPath string) bool {
|
||||||
|
return rfs.excluder != nil && rfs.excluder.MatchesPath(pPath)
|
||||||
|
}
|
84
internal/fusefrontend_reverse/excluder_test.go
Normal file
84
internal/fusefrontend_reverse/excluder_test.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package fusefrontend_reverse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShouldNoCreateExcluderIfNoPattersWereSpecified(t *testing.T) {
|
||||||
|
var rfs ReverseFS
|
||||||
|
var args fusefrontend.Args
|
||||||
|
rfs.prepareExcluder(args)
|
||||||
|
if rfs.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 ReverseFS
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +0,0 @@
|
|||||||
package fusefrontend_reverse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func verifyExcluded(t *testing.T, rfs *ReverseFS, paths []string) {
|
|
||||||
for _, p := range paths {
|
|
||||||
if !rfs.isExcluded(p) {
|
|
||||||
t.Errorf("Path %q should be excluded, but is not", p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.Failed() {
|
|
||||||
t.Logf("cExclude = %#v", rfs.cExclude)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: See also the integration tests in
|
|
||||||
// tests/reverse/exclude_test.go
|
|
||||||
func TestIsExcluded(t *testing.T) {
|
|
||||||
var rfs ReverseFS
|
|
||||||
// If the root directory is excluded, all files and subdirs should be excluded
|
|
||||||
// as well
|
|
||||||
rfs.cExclude = []string{""}
|
|
||||||
verifyExcluded(t, &rfs, []string{"", "foo", "foo/bar"})
|
|
||||||
}
|
|
32
internal/fusefrontend_reverse/mocks_test.go
Normal file
32
internal/fusefrontend_reverse/mocks_test.go
Normal file
@ -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() (*ReverseFS, *IgnoreParserMock) {
|
||||||
|
ignorerMock := &IgnoreParserMock{}
|
||||||
|
nameTransformMock := &NameTransformMock{}
|
||||||
|
var rfs ReverseFS
|
||||||
|
rfs.excluder = ignorerMock
|
||||||
|
rfs.nameTransform = nameTransformMock
|
||||||
|
return &rfs, ignorerMock
|
||||||
|
}
|
@ -106,7 +106,7 @@ func (rfs *ReverseFS) findLongnameParent(dir string, dirIV []byte, longname stri
|
|||||||
|
|
||||||
func (rfs *ReverseFS) newNameFile(relPath string) (nodefs.File, fuse.Status) {
|
func (rfs *ReverseFS) newNameFile(relPath string) (nodefs.File, fuse.Status) {
|
||||||
dotName := filepath.Base(relPath) // gocryptfs.longname.XYZ.name
|
dotName := filepath.Base(relPath) // gocryptfs.longname.XYZ.name
|
||||||
longname := dotName[:len(dotName)-len(nametransform.LongNameSuffix)] // gocryptfs.longname.XYZ
|
longname := nametransform.RemoveLongNameSuffix(dotName) // gocryptfs.longname.XYZ
|
||||||
// cipher directory
|
// cipher directory
|
||||||
cDir := nametransform.Dir(relPath)
|
cDir := nametransform.Dir(relPath)
|
||||||
// plain directory
|
// plain directory
|
||||||
|
@ -34,19 +34,16 @@ type reverseFile struct {
|
|||||||
|
|
||||||
var inodeTable syncmap.Map
|
var inodeTable syncmap.Map
|
||||||
|
|
||||||
// newFile decrypts and opens the path "relPath" and returns a reverseFile
|
// newFile receives a ciphered path "relPath" and its corresponding
|
||||||
|
// decrypted path "pRelPath", opens it and returns a reverseFile
|
||||||
// object. The backing file descriptor is always read-only.
|
// object. The backing file descriptor is always read-only.
|
||||||
func (rfs *ReverseFS) newFile(relPath string) (*reverseFile, fuse.Status) {
|
func (rfs *ReverseFS) newFile(relPath string, pRelPath string) (*reverseFile, fuse.Status) {
|
||||||
if rfs.isExcluded(relPath) {
|
if rfs.isExcludedPlain(pRelPath) {
|
||||||
// Excluded paths should have been filtered out beforehand. Better safe
|
// Excluded paths should have been filtered out beforehand. Better safe
|
||||||
// than sorry.
|
// than sorry.
|
||||||
tlog.Warn.Printf("BUG: newFile: received excluded path %q. This should not happen.", relPath)
|
tlog.Warn.Printf("BUG: newFile: received excluded path %q. This should not happen.", relPath)
|
||||||
return nil, fuse.ENOENT
|
return nil, fuse.ENOENT
|
||||||
}
|
}
|
||||||
pRelPath, err := rfs.decryptPath(relPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fuse.ToStatus(err)
|
|
||||||
}
|
|
||||||
dir := filepath.Dir(pRelPath)
|
dir := filepath.Dir(pRelPath)
|
||||||
dirfd, err := syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, dir)
|
dirfd, err := syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2,9 +2,7 @@ package fusefrontend_reverse
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@ -16,13 +14,13 @@ import (
|
|||||||
"github.com/rfjakob/gocryptfs/internal/configfile"
|
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||||
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
||||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||||
"github.com/rfjakob/gocryptfs/internal/ctlsock"
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
|
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
|
||||||
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
||||||
"github.com/rfjakob/gocryptfs/internal/pathiv"
|
"github.com/rfjakob/gocryptfs/internal/pathiv"
|
||||||
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||||
|
|
||||||
|
"github.com/sabhiram/go-gitignore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReverseFS implements the pathfs.FileSystem interface and provides an
|
// ReverseFS implements the pathfs.FileSystem interface and provides an
|
||||||
@ -35,12 +33,11 @@ type ReverseFS struct {
|
|||||||
// Stores configuration arguments
|
// Stores configuration arguments
|
||||||
args fusefrontend.Args
|
args fusefrontend.Args
|
||||||
// Filename encryption helper
|
// Filename encryption helper
|
||||||
nameTransform *nametransform.NameTransform
|
nameTransform nametransform.NameTransformer
|
||||||
// Content encryption helper
|
// Content encryption helper
|
||||||
contentEnc *contentenc.ContentEnc
|
contentEnc *contentenc.ContentEnc
|
||||||
// Relative ciphertext paths to exclude (hide) from the user. Used by -exclude.
|
// Tests wheter a path is excluded (hiden) from the user. Used by -exclude.
|
||||||
// With -plaintextnames, these are relative *plaintext* paths.
|
excluder ignore.IgnoreParser
|
||||||
cExclude []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ pathfs.FileSystem = &ReverseFS{}
|
var _ pathfs.FileSystem = &ReverseFS{}
|
||||||
@ -48,7 +45,7 @@ var _ pathfs.FileSystem = &ReverseFS{}
|
|||||||
// NewFS returns an encrypted FUSE overlay filesystem.
|
// NewFS returns an encrypted FUSE overlay filesystem.
|
||||||
// In this case (reverse mode) the backing directory is plain-text and
|
// In this case (reverse mode) the backing directory is plain-text and
|
||||||
// ReverseFS provides an encrypted view.
|
// ReverseFS provides an encrypted view.
|
||||||
func NewFS(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransform.NameTransform) *ReverseFS {
|
func NewFS(args fusefrontend.Args, c *contentenc.ContentEnc, n nametransform.NameTransformer) *ReverseFS {
|
||||||
initLongnameCache()
|
initLongnameCache()
|
||||||
fs := &ReverseFS{
|
fs := &ReverseFS{
|
||||||
// pathfs.defaultFileSystem returns ENOSYS for all operations
|
// pathfs.defaultFileSystem returns ENOSYS for all operations
|
||||||
@ -58,34 +55,7 @@ func NewFS(args fusefrontend.Args, c *contentenc.ContentEnc, n *nametransform.Na
|
|||||||
nameTransform: n,
|
nameTransform: n,
|
||||||
contentEnc: c,
|
contentEnc: c,
|
||||||
}
|
}
|
||||||
if len(args.Exclude) > 0 {
|
fs.prepareExcluder(args)
|
||||||
for _, dirty := range args.Exclude {
|
|
||||||
clean := ctlsock.SanitizePath(dirty)
|
|
||||||
if clean != dirty {
|
|
||||||
tlog.Warn.Printf("-exclude: non-canonical path %q has been interpreted as %q", dirty, clean)
|
|
||||||
}
|
|
||||||
if clean == "" {
|
|
||||||
tlog.Fatal.Printf("-exclude: excluding the root dir %q makes no sense", clean)
|
|
||||||
os.Exit(exitcodes.ExcludeError)
|
|
||||||
}
|
|
||||||
cPath, err := fs.EncryptPath(clean)
|
|
||||||
if err != nil {
|
|
||||||
tlog.Fatal.Printf("-exclude: EncryptPath %q failed: %v", clean, err)
|
|
||||||
os.Exit(exitcodes.ExcludeError)
|
|
||||||
}
|
|
||||||
fs.cExclude = append(fs.cExclude, cPath)
|
|
||||||
if !fs.args.PlaintextNames {
|
|
||||||
// If we exclude
|
|
||||||
// gocryptfs.longname.3vZ_r3eDPb1_fL3j5VA4rd_bcKWLKT9eaxOVIGK5HFA
|
|
||||||
// we should also exclude
|
|
||||||
// gocryptfs.longname.3vZ_r3eDPb1_fL3j5VA4rd_bcKWLKT9eaxOVIGK5HFA.name
|
|
||||||
if nametransform.IsLongContent(filepath.Base(cPath)) {
|
|
||||||
fs.cExclude = append(fs.cExclude, cPath+nametransform.LongNameSuffix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tlog.Debug.Printf("-exclude: %v -> %v", fs.args.Exclude, fs.cExclude)
|
|
||||||
}
|
|
||||||
return fs
|
return fs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,24 +70,64 @@ func relDir(path string) string {
|
|||||||
return dir
|
return dir
|
||||||
}
|
}
|
||||||
|
|
||||||
// isExcluded finds out if relative ciphertext path "relPath" is excluded
|
// getFileInfo returns information on a ciphertext path "relPath":
|
||||||
// (used when -exclude is passed by the user)
|
// - ftype: file type (as returned by getFileType)
|
||||||
func (rfs *ReverseFS) isExcluded(relPath string) bool {
|
// - excluded: if the path is excluded
|
||||||
for _, e := range rfs.cExclude {
|
// - pPath: if it's not a special file, the decrypted path
|
||||||
// If the root dir is excluded, everything is excluded.
|
// - err: non nil if any error happens
|
||||||
if e == "" {
|
func (rfs *ReverseFS) getFileInfo(relPath string) (ftype fileType, excluded bool, pPath string, err error) {
|
||||||
return true
|
ftype = rfs.getFileType(relPath)
|
||||||
}
|
if ftype == typeConfig {
|
||||||
// This exact path is excluded
|
excluded, pPath, err = false, "", nil
|
||||||
if e == relPath {
|
return
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Files inside an excluded directory are also excluded
|
|
||||||
if strings.HasPrefix(relPath, e+"/") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false
|
if ftype == typeDiriv {
|
||||||
|
parentDir := nametransform.Dir(relPath)
|
||||||
|
_, excluded, _, err = rfs.getFileInfo(parentDir)
|
||||||
|
pPath = ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ftype == typeName {
|
||||||
|
parentDir := nametransform.Dir(relPath)
|
||||||
|
var parentExcluded bool
|
||||||
|
_, parentExcluded, _, err = rfs.getFileInfo(parentDir)
|
||||||
|
if parentExcluded || err != nil {
|
||||||
|
excluded, pPath = parentExcluded, ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
relPath = nametransform.RemoveLongNameSuffix(relPath)
|
||||||
|
}
|
||||||
|
pPath, err = rfs.decryptPath(relPath)
|
||||||
|
excluded = err == nil && rfs.isExcludedPlain(pPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileType int
|
||||||
|
|
||||||
|
// Values returned by getFileType
|
||||||
|
const (
|
||||||
|
// A regular file/directory/symlink
|
||||||
|
typeRegular fileType = iota
|
||||||
|
// A DirIV (gocryptfs.diriv) file
|
||||||
|
typeDiriv
|
||||||
|
// A .name file for a file with a long name
|
||||||
|
typeName
|
||||||
|
// The config file
|
||||||
|
typeConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
// getFileType returns the type of file. Only the name is checked
|
||||||
|
func (rfs *ReverseFS) getFileType(cPath string) fileType {
|
||||||
|
if rfs.isDirIV(cPath) {
|
||||||
|
return typeDiriv
|
||||||
|
}
|
||||||
|
if rfs.isNameFile(cPath) {
|
||||||
|
return typeName
|
||||||
|
}
|
||||||
|
if rfs.isTranslatedConfig(cPath) {
|
||||||
|
return typeConfig
|
||||||
|
}
|
||||||
|
return typeRegular
|
||||||
}
|
}
|
||||||
|
|
||||||
// isDirIV determines if the path points to a gocryptfs.diriv file
|
// isDirIV determines if the path points to a gocryptfs.diriv file
|
||||||
@ -155,14 +165,18 @@ func (rfs *ReverseFS) isTranslatedConfig(relPath string) bool {
|
|||||||
// GetAttr - FUSE call
|
// GetAttr - FUSE call
|
||||||
// "relPath" is the relative ciphertext path
|
// "relPath" is the relative ciphertext path
|
||||||
func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
|
func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
|
||||||
if rfs.isExcluded(relPath) {
|
ftype, excluded, pPath, err := rfs.getFileInfo(relPath)
|
||||||
|
if excluded {
|
||||||
return nil, fuse.ENOENT
|
return nil, fuse.ENOENT
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fuse.ToStatus(err)
|
||||||
|
}
|
||||||
// Handle "gocryptfs.conf"
|
// Handle "gocryptfs.conf"
|
||||||
if rfs.isTranslatedConfig(relPath) {
|
if ftype == typeConfig {
|
||||||
absConfPath, _ := rfs.abs(configfile.ConfReverseName, nil)
|
absConfPath, _ := rfs.abs(configfile.ConfReverseName, nil)
|
||||||
var st syscall.Stat_t
|
var st syscall.Stat_t
|
||||||
err := syscall.Lstat(absConfPath, &st)
|
err = syscall.Lstat(absConfPath, &st)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fuse.ToStatus(err)
|
return nil, fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
@ -177,11 +191,11 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr
|
|||||||
var f nodefs.File
|
var f nodefs.File
|
||||||
var status fuse.Status
|
var status fuse.Status
|
||||||
virtual := false
|
virtual := false
|
||||||
if rfs.isDirIV(relPath) {
|
if ftype == typeDiriv {
|
||||||
virtual = true
|
virtual = true
|
||||||
f, status = rfs.newDirIVFile(relPath)
|
f, status = rfs.newDirIVFile(relPath)
|
||||||
}
|
}
|
||||||
if rfs.isNameFile(relPath) {
|
if ftype == typeName {
|
||||||
virtual = true
|
virtual = true
|
||||||
f, status = rfs.newNameFile(relPath)
|
f, status = rfs.newNameFile(relPath)
|
||||||
}
|
}
|
||||||
@ -197,7 +211,7 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr
|
|||||||
}
|
}
|
||||||
return &a, status
|
return &a, status
|
||||||
}
|
}
|
||||||
dirfd, name, err := rfs.openBackingDir(relPath)
|
dirfd, name, err := rfs.openBackingDir(pPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fuse.ToStatus(err)
|
return nil, fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
@ -239,10 +253,14 @@ func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr
|
|||||||
|
|
||||||
// Access - FUSE call
|
// Access - FUSE call
|
||||||
func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context) fuse.Status {
|
func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context) fuse.Status {
|
||||||
if rfs.isExcluded(relPath) {
|
ftype, excluded, pPath, err := rfs.getFileInfo(relPath)
|
||||||
|
if excluded {
|
||||||
return fuse.ENOENT
|
return fuse.ENOENT
|
||||||
}
|
}
|
||||||
if rfs.isTranslatedConfig(relPath) || rfs.isDirIV(relPath) || rfs.isNameFile(relPath) {
|
if err != nil {
|
||||||
|
return fuse.ToStatus(err)
|
||||||
|
}
|
||||||
|
if ftype != typeRegular {
|
||||||
// access(2) R_OK flag for checking if the file is readable, always 4 as defined in POSIX.
|
// access(2) R_OK flag for checking if the file is readable, always 4 as defined in POSIX.
|
||||||
ROK := uint32(0x4)
|
ROK := uint32(0x4)
|
||||||
// Virtual files can always be read and never written
|
// Virtual files can always be read and never written
|
||||||
@ -251,7 +269,7 @@ func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context)
|
|||||||
}
|
}
|
||||||
return fuse.EPERM
|
return fuse.EPERM
|
||||||
}
|
}
|
||||||
dirfd, name, err := rfs.openBackingDir(relPath)
|
dirfd, name, err := rfs.openBackingDir(pPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fuse.ToStatus(err)
|
return fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
@ -262,19 +280,23 @@ func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context)
|
|||||||
|
|
||||||
// Open - FUSE call
|
// Open - FUSE call
|
||||||
func (rfs *ReverseFS) Open(relPath string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) {
|
func (rfs *ReverseFS) Open(relPath string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) {
|
||||||
if rfs.isExcluded(relPath) {
|
ftype, excluded, pPath, err := rfs.getFileInfo(relPath)
|
||||||
|
if excluded {
|
||||||
return nil, fuse.ENOENT
|
return nil, fuse.ENOENT
|
||||||
}
|
}
|
||||||
if rfs.isTranslatedConfig(relPath) {
|
if err != nil {
|
||||||
|
return nil, fuse.ToStatus(err)
|
||||||
|
}
|
||||||
|
if ftype == typeConfig {
|
||||||
return rfs.loopbackfs.Open(configfile.ConfReverseName, flags, context)
|
return rfs.loopbackfs.Open(configfile.ConfReverseName, flags, context)
|
||||||
}
|
}
|
||||||
if rfs.isDirIV(relPath) {
|
if ftype == typeDiriv {
|
||||||
return rfs.newDirIVFile(relPath)
|
return rfs.newDirIVFile(relPath)
|
||||||
}
|
}
|
||||||
if rfs.isNameFile(relPath) {
|
if ftype == typeName {
|
||||||
return rfs.newNameFile(relPath)
|
return rfs.newNameFile(relPath)
|
||||||
}
|
}
|
||||||
return rfs.newFile(relPath)
|
return rfs.newFile(relPath, pPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rfs *ReverseFS) openDirPlaintextnames(relPath string, entries []fuse.DirEntry) ([]fuse.DirEntry, fuse.Status) {
|
func (rfs *ReverseFS) openDirPlaintextnames(relPath string, entries []fuse.DirEntry) ([]fuse.DirEntry, fuse.Status) {
|
||||||
@ -304,13 +326,16 @@ func (rfs *ReverseFS) openDirPlaintextnames(relPath string, entries []fuse.DirEn
|
|||||||
|
|
||||||
// OpenDir - FUSE readdir call
|
// OpenDir - FUSE readdir call
|
||||||
func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) {
|
func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) {
|
||||||
if rfs.isExcluded(cipherPath) {
|
ftype, excluded, relPath, err := rfs.getFileInfo(cipherPath)
|
||||||
|
if excluded {
|
||||||
return nil, fuse.ENOENT
|
return nil, fuse.ENOENT
|
||||||
}
|
}
|
||||||
relPath, err := rfs.decryptPath(cipherPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fuse.ToStatus(err)
|
return nil, fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
|
if ftype != typeRegular {
|
||||||
|
return nil, fuse.ENOTDIR
|
||||||
|
}
|
||||||
// Read plaintext dir
|
// Read plaintext dir
|
||||||
dirfd, err := syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, filepath.Dir(relPath))
|
dirfd, err := syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, filepath.Dir(relPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -332,9 +357,11 @@ func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.
|
|||||||
if !status.Ok() {
|
if !status.Ok() {
|
||||||
return nil, status
|
return nil, status
|
||||||
}
|
}
|
||||||
entries = rfs.excludeDirEntries(cipherPath, entries)
|
entries = rfs.excludeDirEntries(relPath, entries)
|
||||||
return entries, fuse.OK
|
return entries, fuse.OK
|
||||||
}
|
}
|
||||||
|
// Filter out excluded entries
|
||||||
|
entries = rfs.excludeDirEntries(relPath, entries)
|
||||||
// Allocate maximum possible number of virtual files.
|
// Allocate maximum possible number of virtual files.
|
||||||
// If all files have long names we need a virtual ".name" file for each,
|
// If all files have long names we need a virtual ".name" file for each,
|
||||||
// plus one for gocryptfs.diriv.
|
// plus one for gocryptfs.diriv.
|
||||||
@ -370,24 +397,22 @@ func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.
|
|||||||
}
|
}
|
||||||
// Add virtual files
|
// Add virtual files
|
||||||
entries = append(entries, virtualFiles[:nVirtual]...)
|
entries = append(entries, virtualFiles[:nVirtual]...)
|
||||||
// Filter out excluded entries
|
|
||||||
entries = rfs.excludeDirEntries(cipherPath, entries)
|
|
||||||
return entries, fuse.OK
|
return entries, fuse.OK
|
||||||
}
|
}
|
||||||
|
|
||||||
// excludeDirEntries filters out directory entries that are "-exclude"d.
|
// excludeDirEntries filters out directory entries that are "-exclude"d.
|
||||||
// cDir is the relative ciphertext path to the directory these entries are
|
// pDir is the relative plaintext path to the directory these entries are
|
||||||
// from.
|
// from. The entries should be plaintext files.
|
||||||
func (rfs *ReverseFS) excludeDirEntries(cDir string, entries []fuse.DirEntry) (filtered []fuse.DirEntry) {
|
func (rfs *ReverseFS) excludeDirEntries(pDir string, entries []fuse.DirEntry) (filtered []fuse.DirEntry) {
|
||||||
if rfs.cExclude == nil {
|
if rfs.excluder == nil {
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
filtered = make([]fuse.DirEntry, 0, len(entries))
|
filtered = make([]fuse.DirEntry, 0, len(entries))
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
// filepath.Join handles the case of cipherPath="" correctly:
|
// filepath.Join handles the case of pDir="" correctly:
|
||||||
// Join("", "foo") -> "foo". This does not: cipherPath + "/" + name"
|
// Join("", "foo") -> "foo". This does not: pDir + "/" + name"
|
||||||
p := filepath.Join(cDir, entry.Name)
|
p := filepath.Join(pDir, entry.Name)
|
||||||
if rfs.isExcluded(p) {
|
if rfs.isExcludedPlain(p) {
|
||||||
// Skip file
|
// Skip file
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -402,11 +427,12 @@ func (rfs *ReverseFS) excludeDirEntries(cDir string, entries []fuse.DirEntry) (f
|
|||||||
// it's worth, so we just ignore the path and always return info about the
|
// it's worth, so we just ignore the path and always return info about the
|
||||||
// backing storage root dir.
|
// backing storage root dir.
|
||||||
func (rfs *ReverseFS) StatFs(relPath string) *fuse.StatfsOut {
|
func (rfs *ReverseFS) StatFs(relPath string) *fuse.StatfsOut {
|
||||||
if rfs.isExcluded(relPath) {
|
_, excluded, _, err := rfs.getFileInfo(relPath)
|
||||||
|
if excluded || err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var s syscall.Statfs_t
|
var s syscall.Statfs_t
|
||||||
err := syscall.Statfs(rfs.args.Cipherdir, &s)
|
err = syscall.Statfs(rfs.args.Cipherdir, &s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -417,10 +443,17 @@ func (rfs *ReverseFS) StatFs(relPath string) *fuse.StatfsOut {
|
|||||||
|
|
||||||
// Readlink - FUSE call
|
// Readlink - FUSE call
|
||||||
func (rfs *ReverseFS) Readlink(relPath string, context *fuse.Context) (string, fuse.Status) {
|
func (rfs *ReverseFS) Readlink(relPath string, context *fuse.Context) (string, fuse.Status) {
|
||||||
if rfs.isExcluded(relPath) {
|
ftype, excluded, pPath, err := rfs.getFileInfo(relPath)
|
||||||
|
if excluded {
|
||||||
return "", fuse.ENOENT
|
return "", fuse.ENOENT
|
||||||
}
|
}
|
||||||
dirfd, name, err := rfs.openBackingDir(relPath)
|
if err != nil {
|
||||||
|
return "", fuse.ToStatus(err)
|
||||||
|
}
|
||||||
|
if ftype != typeRegular {
|
||||||
|
return "", fuse.EINVAL
|
||||||
|
}
|
||||||
|
dirfd, name, err := rfs.openBackingDir(pPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fuse.ToStatus(err)
|
return "", fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
@ -436,7 +469,7 @@ func (rfs *ReverseFS) Readlink(relPath string, context *fuse.Context) (string, f
|
|||||||
nonce := pathiv.Derive(relPath, pathiv.PurposeSymlinkIV)
|
nonce := pathiv.Derive(relPath, pathiv.PurposeSymlinkIV)
|
||||||
// Symlinks are encrypted like file contents and base64-encoded
|
// Symlinks are encrypted like file contents and base64-encoded
|
||||||
cBinTarget := rfs.contentEnc.EncryptBlockNonce([]byte(plainTarget), 0, nil, nonce)
|
cBinTarget := rfs.contentEnc.EncryptBlockNonce([]byte(plainTarget), 0, nil, nonce)
|
||||||
cTarget := rfs.nameTransform.B64.EncodeToString(cBinTarget)
|
cTarget := rfs.nameTransform.B64EncodeToString(cBinTarget)
|
||||||
// The kernel will reject a symlink target above 4096 chars and return
|
// The kernel will reject a symlink target above 4096 chars and return
|
||||||
// and I/O error to the user. Better emit the proper error ourselves.
|
// and I/O error to the user. Better emit the proper error ourselves.
|
||||||
if len(cTarget) > syscallcompat.PATH_MAX {
|
if len(cTarget) > syscallcompat.PATH_MAX {
|
||||||
|
124
internal/fusefrontend_reverse/rfs_test.go
Normal file
124
internal/fusefrontend_reverse/rfs_test.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package fusefrontend_reverse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShouldDetectDirIV(t *testing.T) {
|
||||||
|
var rfs ReverseFS
|
||||||
|
ftype := rfs.getFileType("some/path/" + nametransform.DirIVFilename)
|
||||||
|
if ftype != typeDiriv {
|
||||||
|
t.Errorf("Expecting %d, got %d\n", typeDiriv, ftype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldDetectNameFile(t *testing.T) {
|
||||||
|
var rfs ReverseFS
|
||||||
|
ftype := rfs.getFileType("dir1/dir2/gocryptfs.longname.URrM8kgxTKYMgCk4hKk7RO9Lcfr30XQof4L_5bD9Iro=" + nametransform.LongNameSuffix)
|
||||||
|
if ftype != typeName {
|
||||||
|
t.Errorf("Expecting %d, got %d\n", typeName, ftype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldDetectConfigFile(t *testing.T) {
|
||||||
|
var rfs ReverseFS
|
||||||
|
ftype := rfs.getFileType(configfile.ConfDefaultName)
|
||||||
|
if ftype != typeConfig {
|
||||||
|
t.Errorf("Expecting %d, got %d\n", typeConfig, ftype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldDetectRegularFile(t *testing.T) {
|
||||||
|
var rfs ReverseFS
|
||||||
|
ftype := rfs.getFileType("documents/text_file.txt")
|
||||||
|
if ftype != typeRegular {
|
||||||
|
t.Errorf("Expecting %d, got %d\n", typeRegular, ftype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: For path exclusion, see also the integration tests in
|
||||||
|
// tests/reverse/exclude_test.go
|
||||||
|
func TestShouldNotCallIgnoreParserForTranslatedConfig(t *testing.T) {
|
||||||
|
rfs, ignorerMock := createRFSWithMocks()
|
||||||
|
|
||||||
|
ftype, excluded, _, err := rfs.getFileInfo(configfile.ConfDefaultName)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %q\n", err)
|
||||||
|
}
|
||||||
|
if ftype != typeConfig {
|
||||||
|
t.Errorf("Wrong file type, expecting %d, got %d\n", typeConfig, ftype)
|
||||||
|
}
|
||||||
|
if excluded {
|
||||||
|
t.Error("Should not exclude translated config")
|
||||||
|
}
|
||||||
|
if ignorerMock.calledWith != "" {
|
||||||
|
t.Error("Should not call IgnoreParser for translated config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldCheckIfParentIsExcludedForDirIV(t *testing.T) {
|
||||||
|
rfs, ignorerMock := createRFSWithMocks()
|
||||||
|
path := "dir"
|
||||||
|
ignorerMock.toExclude = "mockdecrypt_dir"
|
||||||
|
dirIV := path + "/" + nametransform.DirIVFilename
|
||||||
|
|
||||||
|
ftype, excluded, _, err := rfs.getFileInfo(dirIV)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %q\n", err)
|
||||||
|
}
|
||||||
|
if ftype != typeDiriv {
|
||||||
|
t.Errorf("Wrong file type, expecting %d, got %d\n", typeDiriv, ftype)
|
||||||
|
}
|
||||||
|
if !excluded {
|
||||||
|
t.Error("Should have excluded DirIV based on parent")
|
||||||
|
}
|
||||||
|
if ignorerMock.calledWith != "mockdecrypt_dir" {
|
||||||
|
t.Errorf("Should have checked parent dir, checked %q", ignorerMock.calledWith)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldCheckIfParentIsExcludedForLongName(t *testing.T) {
|
||||||
|
rfs, ignorerMock := createRFSWithMocks()
|
||||||
|
path := "parent"
|
||||||
|
ignorerMock.toExclude = "mockdecrypt_parent"
|
||||||
|
dirIV := path + "/" + "gocryptfs.longname.fake.name"
|
||||||
|
|
||||||
|
ftype, excluded, _, err := rfs.getFileInfo(dirIV)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %q\n", err)
|
||||||
|
}
|
||||||
|
if ftype != typeName {
|
||||||
|
t.Errorf("Wrong file type, expecting %d, got %d\n", typeName, ftype)
|
||||||
|
}
|
||||||
|
if !excluded {
|
||||||
|
t.Error("Should have excluded LongName based on parent")
|
||||||
|
}
|
||||||
|
if ignorerMock.calledWith != "mockdecrypt_parent" {
|
||||||
|
t.Errorf("Should have checked parent dir, checked %q", ignorerMock.calledWith)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldDecryptPathAndReturnTrueForExcludedPath(t *testing.T) {
|
||||||
|
rfs, ignorerMock := createRFSWithMocks()
|
||||||
|
ignorerMock.toExclude = "mockdecrypt_file.txt"
|
||||||
|
|
||||||
|
ftype, excluded, pPath, err := rfs.getFileInfo("file.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %q\n", err)
|
||||||
|
}
|
||||||
|
if ftype != typeRegular {
|
||||||
|
t.Errorf("Wrong file type, expecting %d, got %d\n", typeRegular, ftype)
|
||||||
|
}
|
||||||
|
if !excluded {
|
||||||
|
t.Error("Should have excluded")
|
||||||
|
}
|
||||||
|
if pPath != "mockdecrypt_file.txt" {
|
||||||
|
t.Errorf("Wrong pPath returned, got %q\n", pPath)
|
||||||
|
}
|
||||||
|
if ignorerMock.calledWith != "mockdecrypt_file.txt" {
|
||||||
|
t.Error("Didn't call IgnoreParser with decrypted path")
|
||||||
|
}
|
||||||
|
}
|
@ -96,16 +96,12 @@ func (rfs *ReverseFS) decryptPath(relPath string) (string, error) {
|
|||||||
return pRelPath, nil
|
return pRelPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// openBackingDir decrypt the relative ciphertext path "cRelPath", opens
|
// openBackingDir receives an already decrypted relative path
|
||||||
// the directory that contains the target file/dir and returns the fd to
|
// "pRelPath", opens the directory that contains the target file/dir
|
||||||
// the directory and the decrypted name of the target file.
|
// and returns the fd to the directory and the decrypted name of the
|
||||||
// The fd/name pair is intended for use with fchownat and friends.
|
// target file. The fd/name pair is intended for use with fchownat and
|
||||||
func (rfs *ReverseFS) openBackingDir(cRelPath string) (dirfd int, pName string, err error) {
|
// friends.
|
||||||
// Decrypt relative path
|
func (rfs *ReverseFS) openBackingDir(pRelPath string) (dirfd int, pName string, err error) {
|
||||||
pRelPath, err := rfs.decryptPath(cRelPath)
|
|
||||||
if err != nil {
|
|
||||||
return -1, "", err
|
|
||||||
}
|
|
||||||
// Open directory, safe against symlink races
|
// Open directory, safe against symlink races
|
||||||
pDir := filepath.Dir(pRelPath)
|
pDir := filepath.Dir(pRelPath)
|
||||||
dirfd, err = syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, pDir)
|
dirfd, err = syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, pDir)
|
||||||
|
@ -69,6 +69,13 @@ func IsLongContent(cName string) bool {
|
|||||||
return NameType(cName) == LongNameContent
|
return NameType(cName) == LongNameContent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveLongNameSuffix removes the ".name" suffix from cName, returning the corresponding
|
||||||
|
// content file name.
|
||||||
|
// No check is made if cName actually is a LongNameFilename.
|
||||||
|
func RemoveLongNameSuffix(cName string) string {
|
||||||
|
return cName[:len(cName)-len(LongNameSuffix)]
|
||||||
|
}
|
||||||
|
|
||||||
// ReadLongName - read cName + ".name" from the directory opened as dirfd.
|
// ReadLongName - read cName + ".name" from the directory opened as dirfd.
|
||||||
//
|
//
|
||||||
// Symlink-safe through Openat().
|
// Symlink-safe through Openat().
|
||||||
|
@ -20,3 +20,11 @@ func TestIsLongName(t *testing.T) {
|
|||||||
t.Errorf("False positive")
|
t.Errorf("False positive")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemoveLongNameSuffix(t *testing.T) {
|
||||||
|
filename := "gocryptfs.longname.LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU=.name"
|
||||||
|
content := "gocryptfs.longname.LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU="
|
||||||
|
if RemoveLongNameSuffix(filename) != content {
|
||||||
|
t.Error(".name suffix not removed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,6 +17,17 @@ const (
|
|||||||
NameMax = 255
|
NameMax = 255
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NameTransformer is an interface used to transform filenames.
|
||||||
|
type NameTransformer interface {
|
||||||
|
DecryptName(cipherName string, iv []byte) (string, error)
|
||||||
|
EncryptName(plainName string, iv []byte) string
|
||||||
|
EncryptAndHashName(name string, iv []byte) (string, error)
|
||||||
|
HashLongName(name string) string
|
||||||
|
WriteLongNameAt(dirfd int, hashName string, plainName string) error
|
||||||
|
B64EncodeToString(src []byte) string
|
||||||
|
B64DecodeString(s string) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
// NameTransform is used to transform filenames.
|
// NameTransform is used to transform filenames.
|
||||||
type NameTransform struct {
|
type NameTransform struct {
|
||||||
emeCipher *eme.EMECipher
|
emeCipher *eme.EMECipher
|
||||||
@ -88,3 +99,13 @@ func (n *NameTransform) EncryptName(plainName string, iv []byte) (cipherName64 s
|
|||||||
cipherName64 = n.B64.EncodeToString(bin)
|
cipherName64 = n.B64.EncodeToString(bin)
|
||||||
return cipherName64
|
return cipherName64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// B64EncodeToString returns a Base64-encoded string
|
||||||
|
func (n *NameTransform) B64EncodeToString(src []byte) string {
|
||||||
|
return n.B64.EncodeToString(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// B64DecodeString decodes a Base64-encoded string
|
||||||
|
func (n *NameTransform) B64DecodeString(s string) ([]byte, error) {
|
||||||
|
return n.B64.DecodeString(s)
|
||||||
|
}
|
||||||
|
20
mount.go
20
mount.go
@ -231,15 +231,17 @@ func initFuseFrontend(args *argContainer) (pfs pathfs.FileSystem, wipeKeys func(
|
|||||||
args.allow_other = true
|
args.allow_other = true
|
||||||
}
|
}
|
||||||
frontendArgs := fusefrontend.Args{
|
frontendArgs := fusefrontend.Args{
|
||||||
Cipherdir: args.cipherdir,
|
Cipherdir: args.cipherdir,
|
||||||
PlaintextNames: args.plaintextnames,
|
PlaintextNames: args.plaintextnames,
|
||||||
LongNames: args.longnames,
|
LongNames: args.longnames,
|
||||||
ConfigCustom: args._configCustom,
|
ConfigCustom: args._configCustom,
|
||||||
NoPrealloc: args.noprealloc,
|
NoPrealloc: args.noprealloc,
|
||||||
SerializeReads: args.serialize_reads,
|
SerializeReads: args.serialize_reads,
|
||||||
ForceDecode: args.forcedecode,
|
ForceDecode: args.forcedecode,
|
||||||
ForceOwner: args._forceOwner,
|
ForceOwner: args._forceOwner,
|
||||||
Exclude: args.exclude,
|
Exclude: args.exclude,
|
||||||
|
ExcludeWildcard: args.excludeWildcard,
|
||||||
|
ExcludeFrom: args.excludeFrom,
|
||||||
}
|
}
|
||||||
// confFile is nil when "-zerokey" or "-masterkey" was used
|
// confFile is nil when "-zerokey" or "-masterkey" was used
|
||||||
if confFile != nil {
|
if confFile != nil {
|
||||||
|
@ -15,11 +15,19 @@ const xxx = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|||||||
/*
|
/*
|
||||||
tree exclude_test_fs
|
tree exclude_test_fs
|
||||||
exclude_test_fs/
|
exclude_test_fs/
|
||||||
|
├── bkp1~
|
||||||
├── dir1
|
├── dir1
|
||||||
│ ├── file1
|
│ ├── file1
|
||||||
│ ├── file2
|
│ ├── file2
|
||||||
|
│ ├── exclude
|
||||||
|
│ ├── longbkp1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx~
|
||||||
│ ├── longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
│ ├── longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
│ └── longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
│ ├── longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
│ ├── longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
│ └── subdir1
|
||||||
|
│ ├── exclude
|
||||||
|
│ └── subdir2
|
||||||
|
│ └── exclude
|
||||||
├── dir2
|
├── dir2
|
||||||
│ ├── file
|
│ ├── file
|
||||||
│ ├── longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
│ ├── longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
@ -30,11 +38,13 @@ exclude_test_fs/
|
|||||||
├── file1
|
├── file1
|
||||||
├── file2
|
├── file2
|
||||||
├── longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
├── longdir1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
│ └── file
|
│ └── file1
|
||||||
├── longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
├── longdir2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
│ ├── bkp~
|
||||||
│ └── file
|
│ └── file
|
||||||
├── longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
├── longfile1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
└── longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
├── longfile2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
└── longfile3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func ctlsockEncryptPath(t *testing.T, sock string, path string) string {
|
func ctlsockEncryptPath(t *testing.T, sock string, path string) string {
|
||||||
@ -47,26 +57,46 @@ func ctlsockEncryptPath(t *testing.T, sock string, path string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testExclude(t *testing.T, flag string) {
|
func testExclude(t *testing.T, flag string) {
|
||||||
|
pPatterns := []string{
|
||||||
|
"file1", // matches file1 anywhere
|
||||||
|
"!longdir1" + xxx + "/file1", // ! includes an otherwise file
|
||||||
|
"file2/", // a trailing slash matches only a directory
|
||||||
|
"dir1/file2", // matches file2 inside dir1 anywhere
|
||||||
|
"#file2", // comments are ignored
|
||||||
|
"dir2", // excludes the whole directory
|
||||||
|
"longfile2" + xxx, // matches longfile2 anywhere
|
||||||
|
"/longfile3" + xxx, // a leading / anchors the match at the root
|
||||||
|
"*~", // wildcards are supported
|
||||||
|
"dir1/**/exclude", // ** matches any number of directories
|
||||||
|
}
|
||||||
pOk := []string{
|
pOk := []string{
|
||||||
"file2",
|
"file2",
|
||||||
"dir1/file1",
|
|
||||||
"dir1/longfile1" + xxx,
|
"dir1/longfile1" + xxx,
|
||||||
|
"dir1/longfile3" + xxx,
|
||||||
"longdir1" + xxx,
|
"longdir1" + xxx,
|
||||||
"longdir1" + xxx + "/file",
|
"longdir1" + xxx + "/file1",
|
||||||
|
"longdir2" + xxx + "/file",
|
||||||
"longfile1" + xxx,
|
"longfile1" + xxx,
|
||||||
}
|
}
|
||||||
pExclude := []string{
|
pExclude := []string{
|
||||||
"file1",
|
"bkp1~",
|
||||||
|
"dir1/file1",
|
||||||
"dir1/file2",
|
"dir1/file2",
|
||||||
|
"dir1/exclude",
|
||||||
|
"dir1/longbkp1" + xxx + "~",
|
||||||
"dir1/longfile2" + xxx,
|
"dir1/longfile2" + xxx,
|
||||||
|
"dir1/subdir1/exclude",
|
||||||
|
"dir1/subdir1/subdir2/exclude",
|
||||||
"dir2",
|
"dir2",
|
||||||
"dir2/file",
|
"dir2/file",
|
||||||
"dir2/file/xxx",
|
|
||||||
"dir2/subdir",
|
|
||||||
"dir2/subdir/file",
|
|
||||||
"dir2/longdir1" + xxx + "/file",
|
"dir2/longdir1" + xxx + "/file",
|
||||||
"dir2/longfile." + xxx,
|
"dir2/longfile." + xxx,
|
||||||
|
"dir2/subdir",
|
||||||
|
"dir2/subdir/file",
|
||||||
|
"file1",
|
||||||
|
"longdir2" + xxx + "/bkp~",
|
||||||
"longfile2" + xxx,
|
"longfile2" + xxx,
|
||||||
|
"longfile3" + xxx,
|
||||||
}
|
}
|
||||||
// Mount reverse fs
|
// Mount reverse fs
|
||||||
mnt, err := ioutil.TempDir(test_helpers.TmpDir, "TestExclude")
|
mnt, err := ioutil.TempDir(test_helpers.TmpDir, "TestExclude")
|
||||||
@ -75,7 +105,7 @@ func testExclude(t *testing.T, flag string) {
|
|||||||
}
|
}
|
||||||
sock := mnt + ".sock"
|
sock := mnt + ".sock"
|
||||||
cliArgs := []string{"-reverse", "-extpass", "echo test", "-ctlsock", sock}
|
cliArgs := []string{"-reverse", "-extpass", "echo test", "-ctlsock", sock}
|
||||||
for _, v := range pExclude {
|
for _, v := range pPatterns {
|
||||||
cliArgs = append(cliArgs, flag, v)
|
cliArgs = append(cliArgs, flag, v)
|
||||||
}
|
}
|
||||||
if plaintextnames {
|
if plaintextnames {
|
||||||
@ -120,6 +150,6 @@ func encryptExcludeTestPaths(t *testing.T, socket string, pRelPaths []string) (o
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExclude(t *testing.T) {
|
func TestExclude(t *testing.T) {
|
||||||
testExclude(t, "-exclude")
|
testExclude(t, "-exclude-wildcard")
|
||||||
testExclude(t, "-e")
|
testExclude(t, "-ew")
|
||||||
}
|
}
|
||||||
|
0
tests/reverse/exclude_test_fs/dir1/exclude
Normal file
0
tests/reverse/exclude_test_fs/dir1/exclude
Normal file
0
tests/reverse/exclude_test_fs/dir1/subdir1/exclude
Normal file
0
tests/reverse/exclude_test_fs/dir1/subdir1/exclude
Normal file
Loading…
x
Reference in New Issue
Block a user