aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/fusefrontend/node.go31
-rw-r--r--internal/fusefrontend/node_api_check.go1
-rw-r--r--internal/fusefrontend/root_node.go20
3 files changed, 52 insertions, 0 deletions
diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go
index 5753053..bce4ea8 100644
--- a/internal/fusefrontend/node.go
+++ b/internal/fusefrontend/node.go
@@ -178,3 +178,34 @@ func (n *Node) Unlink(ctx context.Context, name string) syscall.Errno {
}
return fs.ToErrno(err)
}
+
+// Readlink - FUSE call.
+//
+// Symlink-safe through openBackingDir() + Readlinkat().
+func (n *Node) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
+ rn := n.rootNode()
+ p := n.path()
+ if rn.isFiltered(p) {
+ return nil, syscall.EPERM
+ }
+ dirfd, cName, err := rn.openBackingDir(p)
+ if err != nil {
+ return nil, fs.ToErrno(err)
+ }
+ defer syscall.Close(dirfd)
+
+ cTarget, err := syscallcompat.Readlinkat(dirfd, cName)
+ if err != nil {
+ return nil, fs.ToErrno(err)
+ }
+ if rn.args.PlaintextNames {
+ return []byte(cTarget), 0
+ }
+ // Symlinks are encrypted like file contents (GCM) and base64-encoded
+ target, err := rn.decryptSymlinkTarget(cTarget)
+ if err != nil {
+ tlog.Warn.Printf("Readlink %q: decrypting target failed: %v", cName, err)
+ return nil, syscall.EIO
+ }
+ return []byte(target), 0
+}
diff --git a/internal/fusefrontend/node_api_check.go b/internal/fusefrontend/node_api_check.go
index dcf62d3..dd2fd88 100644
--- a/internal/fusefrontend/node_api_check.go
+++ b/internal/fusefrontend/node_api_check.go
@@ -12,3 +12,4 @@ var _ = (fs.NodeCreater)((*Node)(nil))
var _ = (fs.NodeMkdirer)((*Node)(nil))
var _ = (fs.NodeRmdirer)((*Node)(nil))
var _ = (fs.NodeUnlinker)((*Node)(nil))
+var _ = (fs.NodeReadlinker)((*Node)(nil))
diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go
index be82851..7565018 100644
--- a/internal/fusefrontend/root_node.go
+++ b/internal/fusefrontend/root_node.go
@@ -117,3 +117,23 @@ func (rn *RootNode) isFiltered(path string) bool {
// are exclusive
return false
}
+
+// decryptSymlinkTarget: "cData64" is base64-decoded and decrypted
+// like file contents (GCM).
+// The empty string decrypts to the empty string.
+//
+// This function does not do any I/O and is hence symlink-safe.
+func (rn *RootNode) decryptSymlinkTarget(cData64 string) (string, error) {
+ if cData64 == "" {
+ return "", nil
+ }
+ cData, err := rn.nameTransform.B64DecodeString(cData64)
+ if err != nil {
+ return "", err
+ }
+ data, err := rn.contentEnc.DecryptBlock([]byte(cData), 0, nil)
+ if err != nil {
+ return "", err
+ }
+ return string(data), nil
+}