aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/fusefrontend/node.go49
-rw-r--r--internal/fusefrontend/node_api_check.go2
-rw-r--r--internal/fusefrontend/root_node.go14
3 files changed, 64 insertions, 1 deletions
diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go
index 2f5f3ec..aa9bd51 100644
--- a/internal/fusefrontend/node.go
+++ b/internal/fusefrontend/node.go
@@ -400,3 +400,52 @@ func (n *Node) Link(ctx context.Context, target fs.InodeEmbedder, name string, o
inode = n.newChild(ctx, st, out)
return inode, 0
}
+
+// Symlink - FUSE call. Create a symlink.
+//
+// Symlink-safe through use of Symlinkat.
+func (n *Node) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (inode *fs.Inode, errno syscall.Errno) {
+ dirfd, cName, errno := n.prepareAtSyscall(name)
+ if errno != 0 {
+ return
+ }
+ defer syscall.Close(dirfd)
+
+ // Make sure context is nil if we don't want to preserve the owner
+ rn := n.rootNode()
+ if !rn.args.PreserveOwner {
+ ctx = nil
+ }
+
+ cTarget := target
+ if !rn.args.PlaintextNames {
+ // Symlinks are encrypted like file contents (GCM) and base64-encoded
+ cTarget = rn.encryptSymlinkTarget(target)
+ }
+ // Create ".name" file to store long file name (except in PlaintextNames mode)
+ var err error
+ ctx2 := toFuseCtx(ctx)
+ if !rn.args.PlaintextNames && nametransform.IsLongContent(cName) {
+ err = rn.nameTransform.WriteLongNameAt(dirfd, cName, name)
+ if err != nil {
+ errno = fs.ToErrno(err)
+ return
+ }
+ // Create "gocryptfs.longfile." symlink
+ err = syscallcompat.SymlinkatUser(cTarget, dirfd, cName, ctx2)
+ if err != nil {
+ nametransform.DeleteLongNameAt(dirfd, cName)
+ }
+ } else {
+ // Create symlink
+ err = syscallcompat.SymlinkatUser(cTarget, dirfd, cName, ctx2)
+ }
+
+ st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW)
+ if err != nil {
+ errno = fs.ToErrno(err)
+ return
+ }
+ inode = n.newChild(ctx, st, out)
+ return inode, 0
+}
diff --git a/internal/fusefrontend/node_api_check.go b/internal/fusefrontend/node_api_check.go
index a59971f..b798332 100644
--- a/internal/fusefrontend/node_api_check.go
+++ b/internal/fusefrontend/node_api_check.go
@@ -19,6 +19,7 @@ var _ = (fs.NodeSetattrer)((*Node)(nil))
var _ = (fs.NodeStatfser)((*Node)(nil))
var _ = (fs.NodeMknoder)((*Node)(nil))
var _ = (fs.NodeLinker)((*Node)(nil))
+var _ = (fs.NodeSymlinker)((*Node)(nil))
/* TODO
var _ = (fs.NodeGetxattrer)((*Node)(nil))
@@ -26,6 +27,5 @@ var _ = (fs.NodeSetxattrer)((*Node)(nil))
var _ = (fs.NodeRemovexattrer)((*Node)(nil))
var _ = (fs.NodeListxattrer)((*Node)(nil))
var _ = (fs.NodeCopyFileRanger)((*Node)(nil))
-var _ = (fs.NodeSymlinker)((*Node)(nil))
var _ = (fs.NodeRenamer)((*Node)(nil))
*/
diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go
index cf23169..7ceeb06 100644
--- a/internal/fusefrontend/root_node.go
+++ b/internal/fusefrontend/root_node.go
@@ -245,3 +245,17 @@ func (rn *RootNode) openBackingDir(relPath string) (dirfd int, cName string, err
}
return dirfd, cName, nil
}
+
+// encryptSymlinkTarget: "data" is encrypted like file contents (GCM)
+// and base64-encoded.
+// The empty string encrypts to the empty string.
+//
+// Symlink-safe because it does not do any I/O.
+func (rn *RootNode) encryptSymlinkTarget(data string) (cData64 string) {
+ if data == "" {
+ return ""
+ }
+ cData := rn.contentEnc.EncryptBlock([]byte(data), 0, nil)
+ cData64 = rn.nameTransform.B64EncodeToString(cData)
+ return cData64
+}