diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index cc055c72..cabfdd2e 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -540,35 +540,32 @@ func (fs *FS) Link(oldPath string, newPath string, context *fuse.Context) (code if fs.isFiltered(newPath) { return fuse.EPERM } - cOldPath, err := fs.getBackingPath(oldPath) + oldDirFd, cOldName, err := fs.openBackingPath(oldPath) if err != nil { return fuse.ToStatus(err) } - cNewPath, err := fs.getBackingPath(newPath) + defer oldDirFd.Close() + newDirFd, cNewName, err := fs.openBackingPath(newPath) if err != nil { return fuse.ToStatus(err) } - // Handle long file name - cNewName := filepath.Base(cNewPath) - if nametransform.IsLongContent(cNewName) { - dirfd, err := os.Open(filepath.Dir(cNewPath)) - if err != nil { - return fuse.ToStatus(err) - } - defer dirfd.Close() - err = fs.nameTransform.WriteLongName(dirfd, cNewName, newPath) + defer newDirFd.Close() + // Handle long file name (except in PlaintextNames mode) + if !fs.args.PlaintextNames && nametransform.IsLongContent(cNewName) { + err = fs.nameTransform.WriteLongName(newDirFd, cNewName, newPath) if err != nil { return fuse.ToStatus(err) } - // TODO Use syscall.Linkat once it is available in Go (it is not in Go - // 1.6). - err = syscall.Link(cOldPath, cNewPath) + // Create "gocryptfs.longfile." link + err = syscallcompat.Linkat(int(oldDirFd.Fd()), cOldName, int(newDirFd.Fd()), cNewName, 0) if err != nil { - nametransform.DeleteLongName(dirfd, cNewName) + nametransform.DeleteLongName(newDirFd, cNewName) } - return fuse.ToStatus(err) + } else { + // Create regular link + err = syscallcompat.Linkat(int(oldDirFd.Fd()), cOldName, int(newDirFd.Fd()), cNewName, 0) } - return fuse.ToStatus(os.Link(cOldPath, cNewPath)) + return fuse.ToStatus(err) } // Access implements pathfs.Filesystem. diff --git a/internal/syscallcompat/sys_common.go b/internal/syscallcompat/sys_common.go index 896aaf73..21823efd 100644 --- a/internal/syscallcompat/sys_common.go +++ b/internal/syscallcompat/sys_common.go @@ -38,3 +38,8 @@ func Faccessat(dirfd int, path string, mode uint32) error { } return unix.Faccessat(dirfd, path, mode, 0) } + +// Linkat exists both in Linux and in MacOS 10.10+. +func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flags int) (err error) { + return unix.Linkat(olddirfd, oldpath, newdirfd, newpath, flags) +} diff --git a/tests/matrix/matrix_test.go b/tests/matrix/matrix_test.go index 41948614..90cf55e1 100644 --- a/tests/matrix/matrix_test.go +++ b/tests/matrix/matrix_test.go @@ -812,3 +812,26 @@ func TestSymlink(t *testing.T) { t.Fatal(err) } } + +// Make sure the Link call works with paths starting with "gocryptfs.longname." +func TestLink(t *testing.T) { + target := test_helpers.DefaultPlainDir + "/linktarget" + f, err := os.Create(target) + if err != nil { + t.Fatal(err) + } + f.Close() + path := test_helpers.DefaultPlainDir + "/gocryptfs.longname.XXX" + err = syscall.Link(target, path) + if err != nil { + t.Fatal(err) + } + err = os.Remove(target) + if err != nil { + t.Fatal(err) + } + err = os.Remove(path) + if err != nil { + t.Fatal(err) + } +}