v2api/reverse: finish -exclude
Tests pass now.
This commit is contained in:
parent
15b0b4a5fd
commit
94e8fc12ea
@ -2,6 +2,7 @@ package fusefrontend_reverse
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@ -15,12 +16,9 @@ import (
|
||||
// prepareExcluder creates an object to check if paths are excluded
|
||||
// based on the patterns specified in the command line.
|
||||
func prepareExcluder(args fusefrontend.Args) *ignore.GitIgnore {
|
||||
if len(args.Exclude) == 0 && len(args.ExcludeWildcard) == 0 && len(args.ExcludeFrom) == 0 {
|
||||
return nil
|
||||
}
|
||||
patterns := getExclusionPatterns(args)
|
||||
if len(patterns) == 0 {
|
||||
panic(patterns)
|
||||
log.Panic(patterns)
|
||||
}
|
||||
excluder, err := ignore.CompileIgnoreLines(patterns...)
|
||||
if err != nil {
|
||||
|
@ -9,14 +9,6 @@ import (
|
||||
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
|
||||
)
|
||||
|
||||
func TestShouldNoCreateExcluderIfNoPattersWereSpecified(t *testing.T) {
|
||||
var args fusefrontend.Args
|
||||
excluder := prepareExcluder(args)
|
||||
if excluder != nil {
|
||||
t.Error("Should not have created excluder")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldPrefixExcludeValuesWithSlash(t *testing.T) {
|
||||
var args fusefrontend.Args
|
||||
args.Exclude = []string{"file1", "dir1/file2.txt"}
|
||||
|
@ -26,8 +26,7 @@ type Node struct {
|
||||
|
||||
// Lookup - FUSE call for discovering a file.
|
||||
func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
|
||||
dirfd := int(-1)
|
||||
pName := ""
|
||||
var d *dirfdPlus
|
||||
t := n.lookupFileType(cName)
|
||||
if t == typeDiriv {
|
||||
// gocryptfs.diriv
|
||||
@ -40,14 +39,15 @@ func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch
|
||||
return n.lookupConf(ctx, out)
|
||||
} else if t == typeReal {
|
||||
// real file
|
||||
dirfd, pName, errno = n.prepareAtSyscall(cName)
|
||||
d, errno = n.prepareAtSyscall(cName)
|
||||
//fmt.Printf("Lookup: prepareAtSyscall -> d=%#v, errno=%d\n", d, errno)
|
||||
if errno != 0 {
|
||||
return
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
defer syscall.Close(d.dirfd)
|
||||
}
|
||||
// Get device number and inode number into `st`
|
||||
st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW)
|
||||
st, err := syscallcompat.Fstatat2(d.dirfd, d.pName, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err != nil {
|
||||
return nil, fs.ToErrno(err)
|
||||
}
|
||||
@ -55,7 +55,7 @@ func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch
|
||||
ch = n.newChild(ctx, st, out)
|
||||
// Translate ciphertext size in `out.Attr.Size` to plaintext size
|
||||
if t == typeReal {
|
||||
n.translateSize(dirfd, cName, pName, &out.Attr)
|
||||
n.translateSize(d.dirfd, cName, d.pName, &out.Attr)
|
||||
}
|
||||
return ch, 0
|
||||
}
|
||||
@ -69,13 +69,13 @@ func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut)
|
||||
return f.(fs.FileGetattrer).Getattr(ctx, out)
|
||||
}
|
||||
|
||||
dirfd, pName, errno := n.prepareAtSyscall("")
|
||||
d, errno := n.prepareAtSyscall("")
|
||||
if errno != 0 {
|
||||
return
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
defer syscall.Close(d.dirfd)
|
||||
|
||||
st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW)
|
||||
st, err := syscallcompat.Fstatat2(d.dirfd, d.pName, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err != nil {
|
||||
return fs.ToErrno(err)
|
||||
}
|
||||
@ -87,7 +87,7 @@ func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut)
|
||||
|
||||
// Translate ciphertext size in `out.Attr.Size` to plaintext size
|
||||
cName := filepath.Base(n.Path())
|
||||
n.translateSize(dirfd, cName, pName, &out.Attr)
|
||||
n.translateSize(d.dirfd, cName, d.pName, &out.Attr)
|
||||
|
||||
if rn.args.ForceOwner != nil {
|
||||
out.Owner = *rn.args.ForceOwner
|
||||
@ -99,27 +99,26 @@ func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut)
|
||||
//
|
||||
// Symlink-safe through openBackingDir() + Readlinkat().
|
||||
func (n *Node) Readlink(ctx context.Context) (out []byte, errno syscall.Errno) {
|
||||
dirfd, pName, errno := n.prepareAtSyscall("")
|
||||
d, errno := n.prepareAtSyscall("")
|
||||
if errno != 0 {
|
||||
return
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
defer syscall.Close(d.dirfd)
|
||||
|
||||
cName := filepath.Base(n.Path())
|
||||
return n.readlink(dirfd, cName, pName)
|
||||
return n.readlink(d.dirfd, d.cName, d.pName)
|
||||
}
|
||||
|
||||
// Open - FUSE call. Open already-existing file.
|
||||
//
|
||||
// Symlink-safe through Openat().
|
||||
func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
|
||||
dirfd, pName, errno := n.prepareAtSyscall("")
|
||||
d, errno := n.prepareAtSyscall("")
|
||||
if errno != 0 {
|
||||
return
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
defer syscall.Close(d.dirfd)
|
||||
|
||||
fd, err := syscallcompat.Openat(dirfd, pName, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
|
||||
fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
|
||||
if err != nil {
|
||||
errno = fs.ToErrno(err)
|
||||
return
|
||||
|
@ -23,15 +23,15 @@ import (
|
||||
// This function is symlink-safe through use of openBackingDir() and
|
||||
// ReadDirIVAt().
|
||||
func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.Errno) {
|
||||
dirfd, cName, errno := n.prepareAtSyscall("")
|
||||
d, errno := n.prepareAtSyscall("")
|
||||
if errno != 0 {
|
||||
return
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
defer syscall.Close(d.dirfd)
|
||||
|
||||
// Read plaintext directory
|
||||
var entries []fuse.DirEntry
|
||||
fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
|
||||
fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
|
||||
if err != nil {
|
||||
return nil, fs.ToErrno(err)
|
||||
}
|
||||
@ -42,21 +42,20 @@ func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.
|
||||
}
|
||||
|
||||
rn := n.rootNode()
|
||||
|
||||
// Filter out excluded entries
|
||||
entries = rn.excludeDirEntries(d, entries)
|
||||
|
||||
if rn.args.PlaintextNames {
|
||||
return n.readdirPlaintextnames(entries)
|
||||
}
|
||||
|
||||
// Filter out excluded entries
|
||||
//TODO
|
||||
//entries = rfs.excludeDirEntries(relPath, entries)
|
||||
|
||||
// Virtual files: at least one gocryptfs.diriv file
|
||||
virtualFiles := []fuse.DirEntry{
|
||||
{Mode: virtualFileMode, Name: nametransform.DirIVFilename},
|
||||
}
|
||||
|
||||
cipherPath := n.Path()
|
||||
dirIV := pathiv.Derive(cipherPath, pathiv.PurposeDirIV)
|
||||
dirIV := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)
|
||||
// Encrypt names
|
||||
for i := range entries {
|
||||
var cName string
|
||||
|
@ -47,6 +47,20 @@ func (n *Node) rootNode() *RootNode {
|
||||
return n.Root().Operations().(*RootNode)
|
||||
}
|
||||
|
||||
// dirfdPlus gets filled out as we gather information about a node
|
||||
type dirfdPlus struct {
|
||||
// fd to the directory, opened with O_DIRECTORY|O_PATH
|
||||
dirfd int
|
||||
// Relative plaintext path
|
||||
pPath string
|
||||
// Plaintext basename: filepath.Base(pPath)
|
||||
pName string
|
||||
// Relative ciphertext path
|
||||
cPath string
|
||||
// Ciphertext basename: filepath.Base(cPath)
|
||||
cName string
|
||||
}
|
||||
|
||||
// prepareAtSyscall returns a (dirfd, cName) pair that can be used
|
||||
// with the "___at" family of system calls (openat, fstatat, unlinkat...) to
|
||||
// access the backing encrypted directory.
|
||||
@ -54,16 +68,23 @@ func (n *Node) rootNode() *RootNode {
|
||||
// If you pass a `child` file name, the (dirfd, cName) pair will refer to
|
||||
// a child of this node.
|
||||
// If `child` is empty, the (dirfd, cName) pair refers to this node itself.
|
||||
func (n *Node) prepareAtSyscall(child string) (dirfd int, pName string, errno syscall.Errno) {
|
||||
p := n.Path()
|
||||
func (n *Node) prepareAtSyscall(child string) (d *dirfdPlus, errno syscall.Errno) {
|
||||
cPath := n.Path()
|
||||
if child != "" {
|
||||
p = filepath.Join(p, child)
|
||||
cPath = filepath.Join(cPath, child)
|
||||
}
|
||||
rn := n.rootNode()
|
||||
dirfd, pName, err := rn.openBackingDir(p)
|
||||
dirfd, pPath, err := rn.openBackingDir(cPath)
|
||||
if err != nil {
|
||||
errno = fs.ToErrno(err)
|
||||
}
|
||||
d = &dirfdPlus{
|
||||
dirfd: dirfd,
|
||||
pPath: pPath,
|
||||
pName: filepath.Base(pPath),
|
||||
cPath: cPath,
|
||||
cName: filepath.Base(cPath),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -91,28 +112,32 @@ func (n *Node) isRoot() bool {
|
||||
}
|
||||
|
||||
func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
|
||||
dirfd, pName1, errno := n.prepareAtSyscall("")
|
||||
d, errno := n.prepareAtSyscall("")
|
||||
if errno != 0 {
|
||||
return
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
defer syscall.Close(d.dirfd)
|
||||
|
||||
// Find the file the gocryptfs.longname.XYZ.name file belongs to in the
|
||||
// directory listing
|
||||
fd, err := syscallcompat.Openat(dirfd, pName1, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
|
||||
fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
|
||||
if err != nil {
|
||||
errno = fs.ToErrno(err)
|
||||
return
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
diriv := pathiv.Derive(n.Path(), pathiv.PurposeDirIV)
|
||||
diriv := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)
|
||||
rn := n.rootNode()
|
||||
pName, cFullname, errno := rn.findLongnameParent(fd, diriv, nameFile)
|
||||
if errno != 0 {
|
||||
return
|
||||
}
|
||||
if rn.isExcludedPlain(filepath.Join(d.cPath, pName)) {
|
||||
errno = syscall.EPERM
|
||||
return
|
||||
}
|
||||
// Get attrs from parent file
|
||||
st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW)
|
||||
st, err := syscallcompat.Fstatat2(fd, pName, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err != nil {
|
||||
errno = fs.ToErrno(err)
|
||||
return
|
||||
@ -132,17 +157,17 @@ func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fus
|
||||
|
||||
// lookupDiriv returns a new Inode for a gocryptfs.diriv file inside `n`.
|
||||
func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
|
||||
dirfd, pName, errno := n.prepareAtSyscall("")
|
||||
d, errno := n.prepareAtSyscall("")
|
||||
if errno != 0 {
|
||||
return
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW)
|
||||
defer syscall.Close(d.dirfd)
|
||||
st, err := syscallcompat.Fstatat2(d.dirfd, d.pName, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err != nil {
|
||||
errno = fs.ToErrno(err)
|
||||
return
|
||||
}
|
||||
content := pathiv.Derive(n.Path(), pathiv.PurposeDirIV)
|
||||
content := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)
|
||||
var vf *VirtualMemNode
|
||||
vf, errno = n.newVirtualMemNode(content, st, inoTagDirIV)
|
||||
if errno != 0 {
|
||||
|
@ -2,12 +2,16 @@ package fusefrontend_reverse
|
||||
|
||||
import (
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
||||
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
|
||||
@ -28,7 +32,7 @@ type RootNode struct {
|
||||
// Content encryption helper
|
||||
contentEnc *contentenc.ContentEnc
|
||||
// Tests whether a path is excluded (hidden) from the user. Used by -exclude.
|
||||
excluder *ignore.GitIgnore
|
||||
excluder ignore.IgnoreParser
|
||||
// inoMap translates inode numbers from different devices to unique inode
|
||||
// numbers.
|
||||
inoMap *inomap.InoMap
|
||||
@ -38,17 +42,23 @@ type RootNode struct {
|
||||
// In this case (reverse mode) the backing directory is plain-text and
|
||||
// ReverseFS provides an encrypted view.
|
||||
func NewRootNode(args fusefrontend.Args, c *contentenc.ContentEnc, n nametransform.NameTransformer) *RootNode {
|
||||
return &RootNode{
|
||||
rn := &RootNode{
|
||||
args: args,
|
||||
nameTransform: n,
|
||||
contentEnc: c,
|
||||
inoMap: inomap.New(),
|
||||
excluder: prepareExcluder(args),
|
||||
}
|
||||
if len(args.Exclude) > 0 || len(args.ExcludeWildcard) > 0 || len(args.ExcludeFrom) > 0 {
|
||||
rn.excluder = prepareExcluder(args)
|
||||
}
|
||||
return rn
|
||||
}
|
||||
|
||||
// You can pass either gocryptfs.longname.XYZ.name or gocryptfs.longname.XYZ.
|
||||
func (rn *RootNode) findLongnameParent(fd int, diriv []byte, longname string) (pName string, cFullName string, errno syscall.Errno) {
|
||||
defer func() {
|
||||
tlog.Debug.Printf("findLongnameParent: %d %x %q -> %q %q %d\n", fd, diriv, longname, pName, cFullName, errno)
|
||||
}()
|
||||
if strings.HasSuffix(longname, nametransform.LongNameSuffix) {
|
||||
longname = nametransform.RemoveLongNameSuffix(longname)
|
||||
}
|
||||
@ -84,3 +94,24 @@ func (rn *RootNode) findLongnameParent(fd int, diriv []byte, longname string) (p
|
||||
func (rn *RootNode) isExcludedPlain(pPath string) bool {
|
||||
return rn.excluder != nil && rn.excluder.MatchesPath(pPath)
|
||||
}
|
||||
|
||||
// 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 (rn *RootNode) excludeDirEntries(d *dirfdPlus, entries []fuse.DirEntry) (filtered []fuse.DirEntry) {
|
||||
if rn.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(d.pPath, entry.Name)
|
||||
if rn.isExcludedPlain(p) {
|
||||
// Skip file
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, entry)
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
@ -100,25 +100,24 @@ func (rn *RootNode) decryptPath(cPath string) (string, error) {
|
||||
// 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 (rn *RootNode) openBackingDir(cPath string) (dirfd int, pName string, err error) {
|
||||
func (rn *RootNode) openBackingDir(cPath string) (dirfd int, pPath string, err error) {
|
||||
defer func() {
|
||||
tlog.Debug.Printf("openBackingDir %q -> %d %q %v\n", cPath, dirfd, pName, err)
|
||||
tlog.Debug.Printf("openBackingDir %q -> %d %q %v\n", cPath, dirfd, pPath, err)
|
||||
}()
|
||||
dirfd = -1
|
||||
pRelPath, err := rn.decryptPath(cPath)
|
||||
pPath, err = rn.decryptPath(cPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if rn.isExcludedPlain(pRelPath) {
|
||||
if rn.isExcludedPlain(pPath) {
|
||||
err = syscall.EPERM
|
||||
return
|
||||
}
|
||||
// Open directory, safe against symlink races
|
||||
pDir := filepath.Dir(pRelPath)
|
||||
pDir := filepath.Dir(pPath)
|
||||
dirfd, err = syscallcompat.OpenDirNofollow(rn.args.Cipherdir, pDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pName = filepath.Base(pRelPath)
|
||||
return dirfd, pName, nil
|
||||
return dirfd, pPath, nil
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ func TestSymlinkDentrySize(t *testing.T) {
|
||||
|
||||
fi, err := os.Lstat(mnt + "/" + symlinkResponse.Result)
|
||||
if err != nil {
|
||||
t.Errorf("Lstat: %v", err)
|
||||
t.Fatalf("Lstat: %v", err)
|
||||
}
|
||||
|
||||
target, err := os.Readlink(mnt + "/" + symlinkResponse.Result)
|
||||
|
@ -118,6 +118,7 @@ func testExclude(t *testing.T, flag string) {
|
||||
cExclude := encryptExcludeTestPaths(t, sock, pExclude)
|
||||
// Check that "excluded" paths are not there and "ok" paths are there
|
||||
for _, v := range cExclude {
|
||||
t.Logf("File %q should be invisible", v)
|
||||
if test_helpers.VerifyExistence(t, mnt+"/"+v) {
|
||||
t.Errorf("File %q is visible, but should be excluded", v)
|
||||
}
|
||||
@ -126,6 +127,7 @@ func testExclude(t *testing.T, flag string) {
|
||||
}
|
||||
}
|
||||
for _, v := range cOk {
|
||||
t.Logf("File %q should be visible", v)
|
||||
if !test_helpers.VerifyExistence(t, mnt+"/"+v) {
|
||||
t.Errorf("File %q is hidden, but should be visible", v)
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ func findIno(dir string, ino uint64) string {
|
||||
// ├── gocryptfs.longname.e31v1ax4h_F0l4jhlN8kCjaWWMq8rO9VVBZ15IYsV50 <---- child
|
||||
// └── gocryptfs.longname.e31v1ax4h_F0l4jhlN8kCjaWWMq8rO9VVBZ15IYsV50.name <---- name
|
||||
//
|
||||
// And verifies that the inode numbers match what we expect.
|
||||
// It verifies that the inode numbers match what we expect.
|
||||
func TestVirtualFileIno(t *testing.T) {
|
||||
if plaintextnames {
|
||||
t.Skip("plaintextnames mode does not have virtual files")
|
||||
@ -111,7 +111,7 @@ func TestVirtualFileIno(t *testing.T) {
|
||||
var st2 syscall.Stat_t
|
||||
err = syscall.Lstat(encryptedParent+"/"+entry, &st2)
|
||||
if err != nil {
|
||||
t.Logf("stat %q: %v", entry, err)
|
||||
t.Errorf("stat %q: %v", entry, err)
|
||||
continue
|
||||
}
|
||||
if entry == "gocryptfs.diriv" {
|
||||
|
Loading…
Reference in New Issue
Block a user