summaryrefslogtreecommitdiff
path: root/internal/fusefrontend/node_prepare_syscall.go
blob: 283096f08c6db4ee8fa88c9aa84583094ae04e88 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package fusefrontend

import (
	"sync/atomic"
	"syscall"

	"github.com/rfjakob/gocryptfs/internal/tlog"

	"github.com/hanwen/go-fuse/v2/fs"

	"github.com/rfjakob/gocryptfs/internal/nametransform"
	"github.com/rfjakob/gocryptfs/internal/syscallcompat"
)

// 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 child file.
func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno syscall.Errno) {
	if child == "" {
		tlog.Warn.Printf("BUG: prepareAtSyscall: child=%q, should have called prepareAtSyscallMyself", child)
		return n.prepareAtSyscallMyself()
	}

	rn := n.rootNode()

	// All filesystem operations go through here, so this is a good place
	// to reset the idle marker.
	atomic.StoreUint32(&rn.IsIdle, 0)

	if n.IsRoot() && rn.isFiltered(child) {
		return -1, "", syscall.EPERM
	}

	var encryptName func(int, string, []byte) (string, error)
	if !rn.args.PlaintextNames {
		encryptName = func(dirfd int, child string, iv []byte) (cName string, err error) {
			// Badname allowed, try to determine filenames
			if rn.nameTransform.HaveBadnamePatterns() {
				return rn.nameTransform.EncryptAndHashBadName(child, iv, dirfd)
			}
			return rn.nameTransform.EncryptAndHashName(child, iv)
		}
	}

	// Cache lookup
	var iv []byte
	dirfd, iv = rn.dirCache.Lookup(n)
	if dirfd > 0 {
		if rn.args.PlaintextNames {
			return dirfd, child, 0
		}
		var err error
		cName, err = encryptName(dirfd, child, iv)
		if err != nil {
			syscall.Close(dirfd)
			return -1, "", fs.ToErrno(err)
		}
		return
	}

	// Slowpath: Open ourselves & read diriv
	parentDirfd, myCName, errno := n.prepareAtSyscallMyself()
	if errno != 0 {
		return
	}
	defer syscall.Close(parentDirfd)

	dirfd, err := syscallcompat.Openat(parentDirfd, myCName, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)

	// Cache store
	if !rn.args.PlaintextNames {
		var err error
		iv, err = nametransform.ReadDirIVAt(dirfd)
		if err != nil {
			syscall.Close(dirfd)
			return -1, "", fs.ToErrno(err)
		}
	}
	rn.dirCache.Store(n, dirfd, iv)

	if rn.args.PlaintextNames {
		return dirfd, child, 0
	}

	cName, err = encryptName(dirfd, child, iv)
	if err != nil {
		syscall.Close(dirfd)
		return -1, "", fs.ToErrno(err)
	}

	return
}

func (n *Node) prepareAtSyscallMyself() (dirfd int, cName string, errno syscall.Errno) {
	dirfd = -1

	// Handle root node
	if n.IsRoot() {
		var err error
		rn := n.rootNode()
		// Open cipherdir (following symlinks)
		dirfd, err = syscallcompat.Open(rn.args.Cipherdir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
		if err != nil {
			return -1, "", fs.ToErrno(err)
		}
		return dirfd, ".", 0
	}

	// Otherwise convert to prepareAtSyscall of parent node
	myName, p1 := n.Parent()
	if p1 == nil || myName == "" {
		errno = syscall.ENOENT
		return
	}
	parent := toNode(p1.Operations())
	return parent.prepareAtSyscall(myName)
}