diff options
Diffstat (limited to 'internal/fusefrontend_reverse')
| -rw-r--r-- | internal/fusefrontend_reverse/ctlsock_interface.go | 38 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/excluder.go | 66 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/excluder_test.go | 84 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/mocks_test.go | 32 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/r_node.go | 20 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/reverse_longnames.go | 126 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rfile.go | 209 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rfs.go | 472 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rfs_test.go | 124 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rpath.go | 113 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rpath_cache.go | 45 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/virtualfile.go | 110 | 
12 files changed, 0 insertions, 1439 deletions
| diff --git a/internal/fusefrontend_reverse/ctlsock_interface.go b/internal/fusefrontend_reverse/ctlsock_interface.go deleted file mode 100644 index f7b8afd..0000000 --- a/internal/fusefrontend_reverse/ctlsock_interface.go +++ /dev/null @@ -1,38 +0,0 @@ -package fusefrontend_reverse - -import ( -	"path/filepath" -	"strings" - -	"golang.org/x/sys/unix" - -	"github.com/rfjakob/gocryptfs/internal/ctlsocksrv" -	"github.com/rfjakob/gocryptfs/internal/pathiv" -) - -var _ ctlsocksrv.Interface = &ReverseFS{} // Verify that interface is implemented. - -// EncryptPath implements ctlsock.Backend. -// This is used for the control socket and for the "-exclude" logic. -func (rfs *ReverseFS) EncryptPath(plainPath string) (string, error) { -	if rfs.args.PlaintextNames || plainPath == "" { -		return plainPath, nil -	} -	cipherPath := "" -	parts := strings.Split(plainPath, "/") -	for _, part := range parts { -		dirIV := pathiv.Derive(cipherPath, pathiv.PurposeDirIV) -		encryptedPart := rfs.nameTransform.EncryptName(part, dirIV) -		if rfs.args.LongNames && len(encryptedPart) > unix.NAME_MAX { -			encryptedPart = rfs.nameTransform.HashLongName(encryptedPart) -		} -		cipherPath = filepath.Join(cipherPath, encryptedPart) -	} -	return cipherPath, nil -} - -// DecryptPath implements ctlsock.Backend -func (rfs *ReverseFS) DecryptPath(cipherPath string) (string, error) { -	p, err := rfs.decryptPath(cipherPath) -	return p, err -} diff --git a/internal/fusefrontend_reverse/excluder.go b/internal/fusefrontend_reverse/excluder.go deleted file mode 100644 index c44816c..0000000 --- a/internal/fusefrontend_reverse/excluder.go +++ /dev/null @@ -1,66 +0,0 @@ -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 -// against 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) -} diff --git a/internal/fusefrontend_reverse/excluder_test.go b/internal/fusefrontend_reverse/excluder_test.go deleted file mode 100644 index e19b99f..0000000 --- a/internal/fusefrontend_reverse/excluder_test.go +++ /dev/null @@ -1,84 +0,0 @@ -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") -	} -} diff --git a/internal/fusefrontend_reverse/mocks_test.go b/internal/fusefrontend_reverse/mocks_test.go deleted file mode 100644 index fd2f940..0000000 --- a/internal/fusefrontend_reverse/mocks_test.go +++ /dev/null @@ -1,32 +0,0 @@ -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 -} diff --git a/internal/fusefrontend_reverse/r_node.go b/internal/fusefrontend_reverse/r_node.go deleted file mode 100644 index 58ce664..0000000 --- a/internal/fusefrontend_reverse/r_node.go +++ /dev/null @@ -1,20 +0,0 @@ -package fusefrontend_reverse - -import ( -	"github.com/hanwen/go-fuse/v2/fs" - -	"github.com/rfjakob/gocryptfs/internal/contentenc" -	"github.com/rfjakob/gocryptfs/internal/fusefrontend" -	"github.com/rfjakob/gocryptfs/internal/nametransform" -) - -// RNode is a file or directory in the filesystem tree -// in a `gocryptfs -reverse` mount. -type RNode struct { -	fs.Inode -} - -func NewRootNode(args fusefrontend.Args, c *contentenc.ContentEnc, n nametransform.NameTransformer) *RNode { -	// TODO -	return &RNode{} -} diff --git a/internal/fusefrontend_reverse/reverse_longnames.go b/internal/fusefrontend_reverse/reverse_longnames.go deleted file mode 100644 index f1576d2..0000000 --- a/internal/fusefrontend_reverse/reverse_longnames.go +++ /dev/null @@ -1,126 +0,0 @@ -package fusefrontend_reverse - -import ( -	"log" -	"path/filepath" -	"sync" -	"syscall" -	"time" - -	"golang.org/x/sys/unix" - -	"github.com/hanwen/go-fuse/v2/fuse" -	"github.com/hanwen/go-fuse/v2/fuse/nodefs" - -	"github.com/rfjakob/gocryptfs/internal/nametransform" -	"github.com/rfjakob/gocryptfs/internal/pathiv" -	"github.com/rfjakob/gocryptfs/internal/syscallcompat" -	"github.com/rfjakob/gocryptfs/internal/tlog" -) - -const ( -	// File names are padded to 16-byte multiples, encrypted and -	// base64-encoded. We can encode at most 176 bytes to stay below the 255 -	// bytes limit: -	// * base64(176 bytes) = 235 bytes -	// * base64(192 bytes) = 256 bytes (over 255!) -	// But the PKCS#7 padding is at least one byte. This means we can only use -	// 175 bytes for the file name. -	shortNameMax = 175 -) - -// longnameParentCache maps dir+"/"+longname to plaintextname. -// Yes, the combination of relative plaintext dir path and encrypted -// longname is strange, but works fine as a map index. -var longnameParentCache map[string]string -var longnameCacheLock sync.Mutex - -// Very simple cache cleaner: Nuke it every hour -func longnameCacheCleaner() { -	for { -		time.Sleep(time.Hour) -		longnameCacheLock.Lock() -		longnameParentCache = map[string]string{} -		longnameCacheLock.Unlock() -	} -} - -func initLongnameCache() { -	if longnameParentCache != nil { -		return -	} -	longnameParentCache = map[string]string{} -	go longnameCacheCleaner() -} - -// findLongnameParent converts "longname" = "gocryptfs.longname.XYZ" to the -// plaintext name. "dir" = relative plaintext path to the directory the -// longname file is in, "dirIV" = directory IV of the directory. -func (rfs *ReverseFS) findLongnameParent(dir string, dirIV []byte, longname string) (plaintextName string, err error) { -	longnameCacheLock.Lock() -	hit := longnameParentCache[dir+"/"+longname] -	longnameCacheLock.Unlock() -	if hit != "" { -		return hit, nil -	} -	dirfd, err := syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, filepath.Dir(dir)) -	if err != nil { -		tlog.Warn.Printf("findLongnameParent: OpenDirNofollow failed: %v\n", err) -		return "", err -	} -	fd, err := syscallcompat.Openat(dirfd, filepath.Base(dir), syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) -	syscall.Close(dirfd) -	if err != nil { -		tlog.Warn.Printf("findLongnameParent: Openat failed: %v\n", err) -		return "", err -	} -	dirEntries, err := syscallcompat.Getdents(fd) -	syscall.Close(fd) -	if err != nil { -		tlog.Warn.Printf("findLongnameParent: Getdents failed: %v\n", err) -		return "", err -	} -	longnameCacheLock.Lock() -	defer longnameCacheLock.Unlock() -	for _, entry := range dirEntries { -		plaintextName := entry.Name -		if len(plaintextName) <= shortNameMax { -			continue -		} -		cName := rfs.nameTransform.EncryptName(plaintextName, dirIV) -		if len(cName) <= unix.NAME_MAX { -			// Entry should have been skipped by the "continue" above -			log.Panic("logic error or wrong shortNameMax constant?") -		} -		hName := rfs.nameTransform.HashLongName(cName) -		longnameParentCache[dir+"/"+hName] = plaintextName -		if longname == hName { -			hit = plaintextName -		} -	} -	if hit == "" { -		return "", syscall.ENOENT -	} -	return hit, nil -} - -func (rfs *ReverseFS) newNameFile(relPath string) (nodefs.File, fuse.Status) { -	dotName := filepath.Base(relPath)                       // gocryptfs.longname.XYZ.name -	longname := nametransform.RemoveLongNameSuffix(dotName) // gocryptfs.longname.XYZ -	// cipher directory -	cDir := nametransform.Dir(relPath) -	// plain directory -	pDir, err := rfs.decryptPath(cDir) -	if err != nil { -		return nil, fuse.ToStatus(err) -	} -	dirIV := pathiv.Derive(cDir, pathiv.PurposeDirIV) -	// plain name -	pName, err := rfs.findLongnameParent(pDir, dirIV, longname) -	if err != nil { -		return nil, fuse.ToStatus(err) -	} -	content := []byte(rfs.nameTransform.EncryptName(pName, dirIV)) -	parentFile := filepath.Join(pDir, pName) -	return rfs.newVirtualFile(content, rfs.args.Cipherdir, parentFile, inoTagNameFile) -} diff --git a/internal/fusefrontend_reverse/rfile.go b/internal/fusefrontend_reverse/rfile.go deleted file mode 100644 index 68f7309..0000000 --- a/internal/fusefrontend_reverse/rfile.go +++ /dev/null @@ -1,209 +0,0 @@ -package fusefrontend_reverse - -import ( -	"bytes" -	"io" -	"os" -	"path/filepath" -	"syscall" - -	// In newer Go versions, this has moved to just "sync/syncmap". -	"golang.org/x/sync/syncmap" - -	"github.com/hanwen/go-fuse/v2/fuse" -	"github.com/hanwen/go-fuse/v2/fuse/nodefs" - -	"github.com/rfjakob/gocryptfs/internal/contentenc" -	"github.com/rfjakob/gocryptfs/internal/pathiv" -	"github.com/rfjakob/gocryptfs/internal/syscallcompat" -	"github.com/rfjakob/gocryptfs/internal/tlog" -) - -type reverseFile struct { -	// Embed nodefs.defaultFile for a ENOSYS implementation of all methods -	nodefs.File -	// Backing FD -	fd *os.File -	// File header (contains the IV) -	header contentenc.FileHeader -	// IV for block 0 -	block0IV []byte -	// Content encryption helper -	contentEnc *contentenc.ContentEnc -} - -var inodeTable syncmap.Map - -// 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. -func (rfs *ReverseFS) newFile(relPath string, pRelPath string) (*reverseFile, fuse.Status) { -	if rfs.isExcludedPlain(pRelPath) { -		// Excluded paths should have been filtered out beforehand. Better safe -		// than sorry. -		tlog.Warn.Printf("BUG: newFile: received excluded path %q. This should not happen.", relPath) -		return nil, fuse.ENOENT -	} -	dir := filepath.Dir(pRelPath) -	dirfd, err := syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, dir) -	if err != nil { -		return nil, fuse.ToStatus(err) -	} -	fd, err := syscallcompat.Openat(dirfd, filepath.Base(pRelPath), syscall.O_RDONLY|syscall.O_NOFOLLOW, 0) -	syscall.Close(dirfd) -	if err != nil { -		return nil, fuse.ToStatus(err) -	} -	var st syscall.Stat_t -	err = syscall.Fstat(fd, &st) -	if err != nil { -		tlog.Warn.Printf("newFile: Fstat error: %v", err) -		syscall.Close(fd) -		return nil, fuse.ToStatus(err) -	} -	// Reject access if the file descriptor does not refer to a regular file. -	var a fuse.Attr -	a.FromStat(&st) -	if !a.IsRegular() { -		tlog.Warn.Printf("ino%d: newFile: not a regular file", st.Ino) -		syscall.Close(fd) -		return nil, fuse.ToStatus(syscall.EACCES) -	} -	// See if we have that inode number already in the table -	// (even if Nlink has dropped to 1) -	var derivedIVs pathiv.FileIVs -	v, found := inodeTable.Load(st.Ino) -	if found { -		tlog.Debug.Printf("ino%d: newFile: found in the inode table", st.Ino) -		derivedIVs = v.(pathiv.FileIVs) -	} else { -		derivedIVs = pathiv.DeriveFile(relPath) -		// Nlink > 1 means there is more than one path to this file. -		// Store the derived values so we always return the same data, -		// regardless of the path that is used to access the file. -		// This means that the first path wins. -		if st.Nlink > 1 { -			v, found = inodeTable.LoadOrStore(st.Ino, derivedIVs) -			if found { -				// Another thread has stored a different value before we could. -				derivedIVs = v.(pathiv.FileIVs) -			} else { -				tlog.Debug.Printf("ino%d: newFile: Nlink=%d, stored in the inode table", st.Ino, st.Nlink) -			} -		} -	} -	header := contentenc.FileHeader{ -		Version: contentenc.CurrentVersion, -		ID:      derivedIVs.ID, -	} -	return &reverseFile{ -		File:       nodefs.NewDefaultFile(), -		fd:         os.NewFile(uintptr(fd), pRelPath), -		header:     header, -		block0IV:   derivedIVs.Block0IV, -		contentEnc: rfs.contentEnc, -	}, fuse.OK -} - -// GetAttr - FUSE call -// Triggered by fstat() from userspace -func (rf *reverseFile) GetAttr(*fuse.Attr) fuse.Status { -	tlog.Debug.Printf("reverseFile.GetAttr fd=%d\n", rf.fd.Fd()) -	// The kernel should fall back to stat() -	return fuse.ENOSYS -} - -// encryptBlocks - encrypt "plaintext" into a number of ciphertext blocks. -// "plaintext" must already be block-aligned. -func (rf *reverseFile) encryptBlocks(plaintext []byte, firstBlockNo uint64, fileID []byte, block0IV []byte) []byte { -	inBuf := bytes.NewBuffer(plaintext) -	var outBuf bytes.Buffer -	bs := int(rf.contentEnc.PlainBS()) -	for blockNo := firstBlockNo; inBuf.Len() > 0; blockNo++ { -		inBlock := inBuf.Next(bs) -		iv := pathiv.BlockIV(block0IV, blockNo) -		outBlock := rf.contentEnc.EncryptBlockNonce(inBlock, blockNo, fileID, iv) -		outBuf.Write(outBlock) -	} -	return outBuf.Bytes() -} - -// readBackingFile: read from the backing plaintext file, encrypt it, return the -// ciphertext. -// "off" ... ciphertext offset (must be >= HEADER_LEN) -// "length" ... ciphertext length -func (rf *reverseFile) readBackingFile(off uint64, length uint64) (out []byte, err error) { -	blocks := rf.contentEnc.ExplodeCipherRange(off, length) - -	// Read the backing plaintext in one go -	alignedOffset, alignedLength := contentenc.JointPlaintextRange(blocks) -	plaintext := make([]byte, int(alignedLength)) -	n, err := rf.fd.ReadAt(plaintext, int64(alignedOffset)) -	if err != nil && err != io.EOF { -		tlog.Warn.Printf("readBackingFile: ReadAt: %s", err.Error()) -		return nil, err -	} -	// Truncate buffer down to actually read bytes -	plaintext = plaintext[0:n] - -	// Encrypt blocks -	ciphertext := rf.encryptBlocks(plaintext, blocks[0].BlockNo, rf.header.ID, rf.block0IV) - -	// Crop down to the relevant part -	lenHave := len(ciphertext) -	skip := blocks[0].Skip -	endWant := int(skip + length) -	if lenHave > endWant { -		out = ciphertext[skip:endWant] -	} else if lenHave > int(skip) { -		out = ciphertext[skip:lenHave] -	} // else: out stays empty, file was smaller than the requested offset - -	return out, nil -} - -// Read - FUSE call -func (rf *reverseFile) Read(buf []byte, ioff int64) (resultData fuse.ReadResult, status fuse.Status) { -	length := uint64(len(buf)) -	off := uint64(ioff) -	var out bytes.Buffer -	var header []byte - -	// Synthesize file header -	if off < contentenc.HeaderLen { -		header = rf.header.Pack() -		// Truncate to requested part -		end := int(off) + len(buf) -		if end > len(header) { -			end = len(header) -		} -		header = header[off:end] -		// Write into output buffer and adjust offsets -		out.Write(header) -		hLen := uint64(len(header)) -		off += hLen -		length -= hLen -	} - -	// Read actual file data -	if length > 0 { -		fileData, err := rf.readBackingFile(off, length) -		if err != nil { -			return nil, fuse.ToStatus(err) -		} -		if len(fileData) == 0 { -			// If we could not read any actual data, we also don't want to -			// return the file header. An empty file stays empty in encrypted -			// form. -			return nil, fuse.OK -		} -		out.Write(fileData) -	} - -	return fuse.ReadResultData(out.Bytes()), fuse.OK -} - -// Release - FUSE call, close file -func (rf *reverseFile) Release() { -	rf.fd.Close() -} diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go deleted file mode 100644 index 9f7d1f6..0000000 --- a/internal/fusefrontend_reverse/rfs.go +++ /dev/null @@ -1,472 +0,0 @@ -package fusefrontend_reverse - -import ( -	"fmt" -	"path/filepath" -	"syscall" - -	"golang.org/x/sys/unix" - -	"github.com/hanwen/go-fuse/v2/fuse" -	"github.com/hanwen/go-fuse/v2/fuse/nodefs" -	"github.com/hanwen/go-fuse/v2/fuse/pathfs" - -	"github.com/rfjakob/gocryptfs/internal/configfile" -	"github.com/rfjakob/gocryptfs/internal/contentenc" -	"github.com/rfjakob/gocryptfs/internal/cryptocore" -	"github.com/rfjakob/gocryptfs/internal/fusefrontend" -	"github.com/rfjakob/gocryptfs/internal/inomap" -	"github.com/rfjakob/gocryptfs/internal/nametransform" -	"github.com/rfjakob/gocryptfs/internal/pathiv" -	"github.com/rfjakob/gocryptfs/internal/syscallcompat" -	"github.com/rfjakob/gocryptfs/internal/tlog" - -	"github.com/sabhiram/go-gitignore" -) - -// ReverseFS implements the pathfs.FileSystem interface and provides an -// encrypted view of a plaintext directory. -type ReverseFS struct { -	// Embed pathfs.defaultFileSystem for a ENOSYS implementation of all methods -	pathfs.FileSystem -	// pathfs.loopbackFileSystem, see go-fuse/fuse/pathfs/loopback.go -	loopbackfs pathfs.FileSystem -	// Stores configuration arguments -	args fusefrontend.Args -	// Filename encryption helper -	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 -	// inoMap translates inode numbers from different devices to unique inode -	// numbers. -	inoMap *inomap.InoMap -} - -var _ pathfs.FileSystem = &ReverseFS{} - -// NewFS returns an encrypted FUSE overlay filesystem. -// In this case (reverse mode) the backing directory is plain-text and -// ReverseFS provides an encrypted view. -func NewFS(args fusefrontend.Args, c *contentenc.ContentEnc, n nametransform.NameTransformer) *ReverseFS { -	initLongnameCache() -	fs := &ReverseFS{ -		// pathfs.defaultFileSystem returns ENOSYS for all operations -		FileSystem:    pathfs.NewDefaultFileSystem(), -		loopbackfs:    pathfs.NewLoopbackFileSystem(args.Cipherdir), -		args:          args, -		nameTransform: n, -		contentEnc:    c, -		inoMap:        inomap.New(), -	} -	fs.prepareExcluder(args) -	return fs -} - -// relDir is identical to filepath.Dir excepts that it returns "" when -// filepath.Dir would return ".". -// In the FUSE API, the root directory is called "", and we actually want that. -func relDir(path string) string { -	dir := filepath.Dir(path) -	if dir == "." { -		return "" -	} -	return dir -} - -// getFileInfo returns information on a ciphertext path "relPath": -// - ftype: file type (as returned by getFileType) -// - excluded: if the path is excluded -// - pPath: if it's not a special file, the decrypted path -// - err: non nil if any error happens -func (rfs *ReverseFS) getFileInfo(relPath string) (ftype fileType, excluded bool, pPath string, err error) { -	ftype = rfs.getFileType(relPath) -	if ftype == typeConfig { -		excluded, pPath, err = false, "", nil -		return -	} -	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 gocryptfs.longname.*.name file for a file with a long name -	typeName -	// The config file gocryptfs.conf -	typeConfig -) - -// getFileType returns the type of file (one of the fileType constants above). -func (rfs *ReverseFS) getFileType(cPath string) fileType { -	if !rfs.args.PlaintextNames { -		cName := filepath.Base(cPath) -		// Is it a gocryptfs.diriv file? -		if cName == nametransform.DirIVFilename { -			return typeDiriv -		} -		// Is it a gocryptfs.longname.*.name file? -		if t := nametransform.NameType(cName); t == nametransform.LongNameFilename { -			return typeName -		} -	} -	if rfs.isTranslatedConfig(cPath) { -		return typeConfig -	} -	return typeRegular -} - -// isTranslatedConfig returns true if the default config file name is in use -// and the ciphertext path is "gocryptfs.conf". -// "gocryptfs.conf" then maps to ".gocryptfs.reverse.conf" in the plaintext -// directory. -func (rfs *ReverseFS) isTranslatedConfig(relPath string) bool { -	if rfs.args.ConfigCustom { -		return false -	} -	if relPath == configfile.ConfDefaultName { -		return true -	} -	return false -} - -// GetAttr - FUSE call -// "relPath" is the relative ciphertext path -func (rfs *ReverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) { -	ftype, excluded, pPath, err := rfs.getFileInfo(relPath) -	if excluded { -		return nil, fuse.ENOENT -	} -	if err != nil { -		return nil, fuse.ToStatus(err) -	} -	// Handle "gocryptfs.conf" -	if ftype == typeConfig { -		absConfPath, _ := rfs.abs(configfile.ConfReverseName, nil) -		var st syscall.Stat_t -		err = syscall.Lstat(absConfPath, &st) -		if err != nil { -			return nil, fuse.ToStatus(err) -		} -		rfs.inoMap.TranslateStat(&st) -		var a fuse.Attr -		a.FromStat(&st) -		if rfs.args.ForceOwner != nil { -			a.Owner = *rfs.args.ForceOwner -		} -		return &a, fuse.OK -	} -	// Handle virtual files (gocryptfs.diriv, *.name) -	var f nodefs.File -	var status fuse.Status -	virtual := false -	if ftype == typeDiriv { -		virtual = true -		f, status = rfs.newDirIVFile(relPath) -	} -	if ftype == typeName { -		virtual = true -		f, status = rfs.newNameFile(relPath) -	} -	if virtual { -		if !status.Ok() { -			tlog.Warn.Printf("GetAttr %q: newXFile failed: %v\n", relPath, status) -			return nil, status -		} -		var a fuse.Attr -		status = f.GetAttr(&a) -		if rfs.args.ForceOwner != nil { -			a.Owner = *rfs.args.ForceOwner -		} -		return &a, status -	} -	// Normal file / directory -	dirfd, name, err := rfs.openBackingDir(pPath) -	if err != nil { -		return nil, fuse.ToStatus(err) -	} -	// Stat the backing file/dir using Fstatat -	var st syscall.Stat_t -	{ -		var st2 unix.Stat_t -		err = syscallcompat.Fstatat(dirfd, name, &st2, unix.AT_SYMLINK_NOFOLLOW) -		syscall.Close(dirfd) -		if err != nil { -			return nil, fuse.ToStatus(err) -		} -		st = syscallcompat.Unix2syscall(st2) -	} -	rfs.inoMap.TranslateStat(&st) -	var a fuse.Attr -	a.FromStat(&st) -	// Calculate encrypted file size -	if a.IsRegular() { -		a.Size = rfs.contentEnc.PlainSizeToCipherSize(a.Size) -	} else if a.IsSymlink() { -		var linkTarget string -		var readlinkStatus fuse.Status - -		linkTarget, readlinkStatus = rfs.Readlink(relPath, context) -		if !readlinkStatus.Ok() { -			return nil, readlinkStatus -		} - -		a.Size = uint64(len(linkTarget)) -	} -	if rfs.args.ForceOwner != nil { -		a.Owner = *rfs.args.ForceOwner -	} -	return &a, fuse.OK -} - -// Access - FUSE call -func (rfs *ReverseFS) Access(relPath string, mode uint32, context *fuse.Context) fuse.Status { -	ftype, excluded, pPath, err := rfs.getFileInfo(relPath) -	if excluded { -		return fuse.ENOENT -	} -	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. -		ROK := uint32(0x4) -		// Virtual files can always be read and never written -		if mode == ROK || mode == 0 { -			return fuse.OK -		} -		return fuse.EPERM -	} -	dirfd, name, err := rfs.openBackingDir(pPath) -	if err != nil { -		return fuse.ToStatus(err) -	} -	err = syscallcompat.Faccessat(dirfd, name, mode) -	syscall.Close(dirfd) -	return fuse.ToStatus(err) -} - -// Open - FUSE call -func (rfs *ReverseFS) Open(relPath string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { -	ftype, excluded, pPath, err := rfs.getFileInfo(relPath) -	if excluded { -		return nil, fuse.ENOENT -	} -	if err != nil { -		return nil, fuse.ToStatus(err) -	} -	if ftype == typeConfig { -		return rfs.loopbackfs.Open(configfile.ConfReverseName, flags, context) -	} -	if ftype == typeDiriv { -		return rfs.newDirIVFile(relPath) -	} -	if ftype == typeName { -		return rfs.newNameFile(relPath) -	} -	return rfs.newFile(relPath, pPath) -} - -func (rfs *ReverseFS) openDirPlaintextnames(relPath string, entries []fuse.DirEntry) ([]fuse.DirEntry, fuse.Status) { -	if relPath != "" || rfs.args.ConfigCustom { -		return entries, fuse.OK -	} -	// We are in the root dir and the default config file name -	// ".gocryptfs.reverse.conf" is used. We map it to "gocryptfs.conf". -	dupe := -1 -	status := fuse.OK -	for i := range entries { -		if entries[i].Name == configfile.ConfReverseName { -			entries[i].Name = configfile.ConfDefaultName -		} else if entries[i].Name == configfile.ConfDefaultName { -			dupe = i -		} -	} -	if dupe >= 0 { -		// Warn the user loudly: The gocryptfs.conf_NAME_COLLISION file will -		// throw ENOENT errors that are hard to miss. -		tlog.Warn.Printf("The file %q is mapped to %q and shadows another file. Please rename %q in directory %q.", -			configfile.ConfReverseName, configfile.ConfDefaultName, configfile.ConfDefaultName, rfs.args.Cipherdir) -		entries[dupe].Name = "gocryptfs.conf_NAME_COLLISION_" + fmt.Sprintf("%d", cryptocore.RandUint64()) -	} -	return entries, status -} - -// OpenDir - FUSE readdir call -func (rfs *ReverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { -	ftype, excluded, relPath, err := rfs.getFileInfo(cipherPath) -	if excluded { -		return nil, fuse.ENOENT -	} -	if err != nil { -		return nil, fuse.ToStatus(err) -	} -	if ftype != typeRegular { -		return nil, fuse.ENOTDIR -	} -	// Read plaintext dir -	dirfd, err := syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, filepath.Dir(relPath)) -	if err != nil { -		return nil, fuse.ToStatus(err) -	} -	fd, err := syscallcompat.Openat(dirfd, filepath.Base(relPath), syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) -	syscall.Close(dirfd) -	if err != nil { -		return nil, fuse.ToStatus(err) -	} -	entries, err := syscallcompat.Getdents(fd) -	syscall.Close(fd) -	if err != nil { -		return nil, fuse.ToStatus(err) -	} -	if rfs.args.PlaintextNames { -		var status fuse.Status -		entries, status = rfs.openDirPlaintextnames(cipherPath, entries) -		if !status.Ok() { -			return nil, status -		} -		entries = rfs.excludeDirEntries(relPath, entries) -		return entries, fuse.OK -	} -	// Filter out excluded entries -	entries = rfs.excludeDirEntries(relPath, entries) -	// Allocate maximum possible number of virtual files. -	// If all files have long names we need a virtual ".name" file for each, -	// plus one for gocryptfs.diriv. -	virtualFiles := make([]fuse.DirEntry, len(entries)+1) -	// Virtual gocryptfs.diriv file -	virtualFiles[0] = fuse.DirEntry{ -		Mode: virtualFileMode, -		Name: nametransform.DirIVFilename, -	} -	// Actually used entries -	nVirtual := 1 - -	// Encrypt names -	dirIV := pathiv.Derive(cipherPath, pathiv.PurposeDirIV) -	for i := range entries { -		var cName string -		// ".gocryptfs.reverse.conf" in the root directory is mapped to "gocryptfs.conf" -		if cipherPath == "" && entries[i].Name == configfile.ConfReverseName && -			!rfs.args.ConfigCustom { -			cName = configfile.ConfDefaultName -		} else { -			cName = rfs.nameTransform.EncryptName(entries[i].Name, dirIV) -			if len(cName) > unix.NAME_MAX { -				cName = rfs.nameTransform.HashLongName(cName) -				dotNameFile := fuse.DirEntry{ -					Mode: virtualFileMode, -					Name: cName + nametransform.LongNameSuffix, -				} -				virtualFiles[nVirtual] = dotNameFile -				nVirtual++ -			} -		} -		entries[i].Name = cName -	} -	// Add virtual files -	entries = append(entries, virtualFiles[:nVirtual]...) -	return entries, fuse.OK -} - -// excludeDirEntries filters out directory entries that are "-exclude"d. -// pDir is the relative plaintext path to the directory these entries are -// from. The entries should be plaintext files. -func (rfs *ReverseFS) excludeDirEntries(pDir string, entries []fuse.DirEntry) (filtered []fuse.DirEntry) { -	if rfs.excluder == nil { -		return entries -	} -	filtered = make([]fuse.DirEntry, 0, len(entries)) -	for _, entry := range entries { -		// filepath.Join handles the case of pDir="" correctly: -		// Join("", "foo") -> "foo". This does not: pDir + "/" + name" -		p := filepath.Join(pDir, entry.Name) -		if rfs.isExcludedPlain(p) { -			// Skip file -			continue -		} -		filtered = append(filtered, entry) -	} -	return filtered -} - -// StatFs - FUSE call. Returns information about the filesystem (free space -// etc). -// Securing statfs against symlink races seems to be more trouble than -// it's worth, so we just ignore the path and always return info about the -// backing storage root dir. -func (rfs *ReverseFS) StatFs(relPath string) *fuse.StatfsOut { -	_, excluded, _, err := rfs.getFileInfo(relPath) -	if excluded || err != nil { -		return nil -	} -	var s syscall.Statfs_t -	err = syscall.Statfs(rfs.args.Cipherdir, &s) -	if err != nil { -		return nil -	} -	out := &fuse.StatfsOut{} -	out.FromStatfsT(&s) -	return out -} - -// Readlink - FUSE call -func (rfs *ReverseFS) Readlink(relPath string, context *fuse.Context) (string, fuse.Status) { -	ftype, excluded, pPath, err := rfs.getFileInfo(relPath) -	if excluded { -		return "", fuse.ENOENT -	} -	if err != nil { -		return "", fuse.ToStatus(err) -	} -	if ftype != typeRegular { -		return "", fuse.EINVAL -	} -	dirfd, name, err := rfs.openBackingDir(pPath) -	if err != nil { -		return "", fuse.ToStatus(err) -	} -	// read the link target using Readlinkat -	plainTarget, err := syscallcompat.Readlinkat(dirfd, name) -	syscall.Close(dirfd) -	if err != nil { -		return "", fuse.ToStatus(err) -	} -	if rfs.args.PlaintextNames { -		return plainTarget, fuse.OK -	} -	nonce := pathiv.Derive(relPath, pathiv.PurposeSymlinkIV) -	// Symlinks are encrypted like file contents and base64-encoded -	cBinTarget := rfs.contentEnc.EncryptBlockNonce([]byte(plainTarget), 0, nil, nonce) -	cTarget := rfs.nameTransform.B64EncodeToString(cBinTarget) -	// 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. -	if len(cTarget) > syscallcompat.PATH_MAX { -		return "", fuse.Status(syscall.ENAMETOOLONG) -	} -	return cTarget, fuse.OK -} diff --git a/internal/fusefrontend_reverse/rfs_test.go b/internal/fusefrontend_reverse/rfs_test.go deleted file mode 100644 index eccaab9..0000000 --- a/internal/fusefrontend_reverse/rfs_test.go +++ /dev/null @@ -1,124 +0,0 @@ -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") -	} -} diff --git a/internal/fusefrontend_reverse/rpath.go b/internal/fusefrontend_reverse/rpath.go deleted file mode 100644 index 2da8379..0000000 --- a/internal/fusefrontend_reverse/rpath.go +++ /dev/null @@ -1,113 +0,0 @@ -package fusefrontend_reverse - -import ( -	"encoding/base64" -	"path/filepath" -	"strings" -	"syscall" - -	"github.com/rfjakob/gocryptfs/internal/nametransform" -	"github.com/rfjakob/gocryptfs/internal/pathiv" -	"github.com/rfjakob/gocryptfs/internal/syscallcompat" -	"github.com/rfjakob/gocryptfs/internal/tlog" -) - -// abs basically returns storage dir + "/" + relPath. -// It takes an error parameter so it can directly wrap decryptPath like this: -// a, err := rfs.abs(rfs.decryptPath(relPath)) -// abs never generates an error on its own. In other words, abs(p, nil) never -// fails. -func (rfs *ReverseFS) abs(relPath string, err error) (string, error) { -	if err != nil { -		return "", err -	} -	return filepath.Join(rfs.args.Cipherdir, relPath), nil -} - -// rDecryptName decrypts the ciphertext name "cName", given the dirIV of the -// directory "cName" lies in. The relative plaintext path to the directory -// "pDir" is used if a "gocryptfs.longname.XYZ.name" must be resolved. -func (rfs *ReverseFS) rDecryptName(cName string, dirIV []byte, pDir string) (pName string, err error) { -	nameType := nametransform.NameType(cName) -	if nameType == nametransform.LongNameNone { -		pName, err = rfs.nameTransform.DecryptName(cName, dirIV) -		if err != nil { -			// We get lots of decrypt requests for names like ".Trash" that -			// are invalid base64. Convert them to ENOENT so the correct -			// error gets returned to the user. -			if _, ok := err.(base64.CorruptInputError); ok { -				return "", syscall.ENOENT -			} -			// Stat attempts on the link target of encrypted symlinks. -			// These are always valid base64 but the length is not a -			// multiple of 16. -			if err == syscall.EBADMSG { -				return "", syscall.ENOENT -			} -			return "", err -		} -	} else if nameType == nametransform.LongNameContent { -		pName, err = rfs.findLongnameParent(pDir, dirIV, cName) -		if err != nil { -			return "", err -		} -	} else { -		// It makes no sense to decrypt a ".name" file. This is a virtual file -		// that has no representation in the plaintext filesystem. ".name" -		// files should have already been handled in virtualfile.go. -		tlog.Warn.Printf("rDecryptName: cannot decrypt virtual file %q", cName) -		return "", syscall.EINVAL -	} -	return pName, nil -} - -// decryptPath decrypts a relative ciphertext path to a relative plaintext -// path. -func (rfs *ReverseFS) decryptPath(relPath string) (string, error) { -	if rfs.args.PlaintextNames || relPath == "" { -		return relPath, nil -	} -	// Check if the parent dir is in the cache -	cDir := nametransform.Dir(relPath) -	dirIV, pDir := rPathCache.lookup(cDir) -	if dirIV != nil { -		cName := filepath.Base(relPath) -		pName, err := rfs.rDecryptName(cName, dirIV, pDir) -		if err != nil { -			return "", err -		} -		return filepath.Join(pDir, pName), nil -	} -	parts := strings.Split(relPath, "/") -	var transformedParts []string -	for i := range parts { -		// Start at the top and recurse -		currentCipherDir := filepath.Join(parts[:i]...) -		currentPlainDir := filepath.Join(transformedParts[:i]...) -		dirIV = pathiv.Derive(currentCipherDir, pathiv.PurposeDirIV) -		transformedPart, err := rfs.rDecryptName(parts[i], dirIV, currentPlainDir) -		if err != nil { -			return "", err -		} -		transformedParts = append(transformedParts, transformedPart) -	} -	pRelPath := filepath.Join(transformedParts...) -	rPathCache.store(cDir, dirIV, nametransform.Dir(pRelPath)) -	return pRelPath, nil -} - -// openBackingDir receives an already decrypted relative path -// "pRelPath", opens the directory that contains the target file/dir -// and returns the fd to the directory and the decrypted name of the -// target file. The fd/name pair is intended for use with fchownat and -// friends. -func (rfs *ReverseFS) openBackingDir(pRelPath string) (dirfd int, pName string, err error) { -	// Open directory, safe against symlink races -	pDir := filepath.Dir(pRelPath) -	dirfd, err = syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, pDir) -	if err != nil { -		return -1, "", err -	} -	pName = filepath.Base(pRelPath) -	return dirfd, pName, nil -} diff --git a/internal/fusefrontend_reverse/rpath_cache.go b/internal/fusefrontend_reverse/rpath_cache.go deleted file mode 100644 index 221f578..0000000 --- a/internal/fusefrontend_reverse/rpath_cache.go +++ /dev/null @@ -1,45 +0,0 @@ -package fusefrontend_reverse - -import ( -	"sync" -) - -// rPathCacheContainer is a simple one entry path cache. Because the dirIV -// is generated deterministically from the directory path, there is no need -// to ever invalidate entries. -type rPathCacheContainer struct { -	sync.Mutex -	// Relative ciphertext path to the directory -	cPath string -	// Relative plaintext path -	pPath string -	// Directory IV of the directory -	dirIV []byte -} - -// lookup relative ciphertext path "cPath". Returns dirIV, relative -// plaintext path. -func (c *rPathCacheContainer) lookup(cPath string) ([]byte, string) { -	c.Lock() -	defer c.Unlock() -	if cPath == c.cPath { -		// hit -		return c.dirIV, c.pPath -	} -	// miss -	return nil, "" -} - -// store - write entry for the directory at relative ciphertext path "cPath" -// into the cache. -// "dirIV" = directory IV of the directory, "pPath" = relative plaintext path -func (c *rPathCacheContainer) store(cPath string, dirIV []byte, pPath string) { -	c.Lock() -	defer c.Unlock() -	c.cPath = cPath -	c.dirIV = dirIV -	c.pPath = pPath -} - -// rPathCache: see rPathCacheContainer above for a detailed description -var rPathCache rPathCacheContainer diff --git a/internal/fusefrontend_reverse/virtualfile.go b/internal/fusefrontend_reverse/virtualfile.go deleted file mode 100644 index f258e7f..0000000 --- a/internal/fusefrontend_reverse/virtualfile.go +++ /dev/null @@ -1,110 +0,0 @@ -package fusefrontend_reverse - -import ( -	"log" -	"path/filepath" -	"syscall" - -	"golang.org/x/sys/unix" - -	"github.com/hanwen/go-fuse/v2/fuse" -	"github.com/hanwen/go-fuse/v2/fuse/nodefs" - -	"github.com/rfjakob/gocryptfs/internal/inomap" -	"github.com/rfjakob/gocryptfs/internal/nametransform" -	"github.com/rfjakob/gocryptfs/internal/pathiv" -	"github.com/rfjakob/gocryptfs/internal/syscallcompat" -	"github.com/rfjakob/gocryptfs/internal/tlog" -) - -const ( -	// virtualFileMode is the mode to use for virtual files (gocryptfs.diriv and -	// *.name). They are always readable, as stated in func Access -	virtualFileMode = syscall.S_IFREG | 0444 -	// We use inomap's `Tag` feature to generate unique inode numbers for -	// virtual files. These are the tags we use. -	inoTagDirIV    = 1 -	inoTagNameFile = 2 -) - -func (rfs *ReverseFS) newDirIVFile(cRelPath string) (nodefs.File, fuse.Status) { -	cDir := nametransform.Dir(cRelPath) -	dir, err := rfs.decryptPath(cDir) -	if err != nil { -		return nil, fuse.ToStatus(err) -	} -	iv := pathiv.Derive(cDir, pathiv.PurposeDirIV) -	return rfs.newVirtualFile(iv, rfs.args.Cipherdir, dir, inoTagDirIV) -} - -type virtualFile struct { -	// Embed nodefs.defaultFile for a ENOSYS implementation of all methods -	nodefs.File -	// pointer to parent filesystem -	rfs *ReverseFS -	// file content -	content []byte -	// backing directory -	cipherdir string -	// path to a parent file (relative to cipherdir) -	parentFile string -	// inomap `Tag`. -	// Depending on the file type, either `inoTagDirIV` or `inoTagNameFile`. -	inoTag uint8 -} - -// newVirtualFile creates a new in-memory file that does not have a representation -// on disk. "content" is the file content. Timestamps and file owner are copied -// from "parentFile" (plaintext path relative to "cipherdir"). -// For a "gocryptfs.diriv" file, you would use the parent directory as -// "parentFile". -func (rfs *ReverseFS) newVirtualFile(content []byte, cipherdir string, parentFile string, inoTag uint8) (nodefs.File, fuse.Status) { -	if inoTag == 0 { -		log.Panicf("BUG: inoTag for virtual file is zero - this will cause ino collisions!") -	} -	return &virtualFile{ -		File:       nodefs.NewDefaultFile(), -		rfs:        rfs, -		content:    content, -		cipherdir:  cipherdir, -		parentFile: parentFile, -		inoTag:     inoTag, -	}, fuse.OK -} - -// Read - FUSE call -func (f *virtualFile) Read(buf []byte, off int64) (resultData fuse.ReadResult, status fuse.Status) { -	if off >= int64(len(f.content)) { -		return nil, fuse.OK -	} -	end := int(off) + len(buf) -	if end > len(f.content) { -		end = len(f.content) -	} -	return fuse.ReadResultData(f.content[off:end]), fuse.OK -} - -// GetAttr - FUSE call -func (f *virtualFile) GetAttr(a *fuse.Attr) fuse.Status { -	dir := filepath.Dir(f.parentFile) -	dirfd, err := syscallcompat.OpenDirNofollow(f.cipherdir, dir) -	if err != nil { -		return fuse.ToStatus(err) -	} -	defer syscall.Close(dirfd) -	name := filepath.Base(f.parentFile) -	var st2 unix.Stat_t -	err = syscallcompat.Fstatat(dirfd, name, &st2, unix.AT_SYMLINK_NOFOLLOW) -	if err != nil { -		tlog.Debug.Printf("GetAttr: Fstatat %q: %v\n", f.parentFile, err) -		return fuse.ToStatus(err) -	} -	st := syscallcompat.Unix2syscall(st2) -	q := inomap.NewQIno(uint64(st.Dev), f.inoTag, uint64(st.Ino)) -	st.Ino = f.rfs.inoMap.Translate(q) -	st.Size = int64(len(f.content)) -	st.Mode = virtualFileMode -	st.Nlink = 1 -	a.FromStat(&st) -	return fuse.OK -} | 
