forked from TrueCloudLab/rclone
We enable symlink support using the --links command line switch. When symlink support is enabled, the mount backends will translate the name of the vfs symlinks files (truncating their rclonelink suffix). Also, operations like rename, symlink etc does not needs the rclonelink suffix, it is handled internally to pass it to the underlying low level VFS. When symlink support is disabled, Symlink and Readlink functions will transparently manage ".rclonelink" files as regular files. Fixes #2975
345 lines
11 KiB
Go
345 lines
11 KiB
Go
package vfstest
|
|
|
|
import (
|
|
"os"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/vfs"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestFileModTime tests mod times on files
|
|
func TestFileModTime(t *testing.T) {
|
|
run.skipIfNoFUSE(t)
|
|
|
|
run.createFile(t, "file", "123")
|
|
|
|
mtime := time.Date(2012, time.November, 18, 17, 32, 31, 0, time.UTC)
|
|
err := run.os.Chtimes(run.path("file"), mtime, mtime)
|
|
require.NoError(t, err)
|
|
|
|
info, err := run.os.Stat(run.path("file"))
|
|
require.NoError(t, err)
|
|
|
|
// avoid errors because of timezone differences
|
|
assert.Equal(t, info.ModTime().Unix(), mtime.Unix())
|
|
|
|
run.rm(t, "file")
|
|
}
|
|
|
|
// run.os.Create without opening for write too
|
|
func osCreate(name string) (vfs.OsFiler, error) {
|
|
return run.os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
|
}
|
|
|
|
// run.os.Create with append
|
|
func osAppend(name string) (vfs.OsFiler, error) {
|
|
return run.os.OpenFile(name, os.O_WRONLY|os.O_APPEND, 0666)
|
|
}
|
|
|
|
// TestFileModTimeWithOpenWriters tests mod time on open files
|
|
func TestFileModTimeWithOpenWriters(t *testing.T) {
|
|
run.skipIfNoFUSE(t)
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("Skipping test on Windows")
|
|
}
|
|
|
|
mtime := time.Date(2012, time.November, 18, 17, 32, 31, 0, time.UTC)
|
|
filepath := run.path("cp-archive-test")
|
|
|
|
f, err := osCreate(filepath)
|
|
require.NoError(t, err)
|
|
|
|
_, err = f.Write([]byte{104, 105})
|
|
require.NoError(t, err)
|
|
|
|
err = run.os.Chtimes(filepath, mtime, mtime)
|
|
require.NoError(t, err)
|
|
|
|
err = f.Close()
|
|
require.NoError(t, err)
|
|
|
|
run.waitForWriters()
|
|
|
|
info, err := run.os.Stat(filepath)
|
|
require.NoError(t, err)
|
|
|
|
// avoid errors because of timezone differences
|
|
assert.Equal(t, info.ModTime().Unix(), mtime.Unix())
|
|
|
|
run.rm(t, "cp-archive-test")
|
|
}
|
|
|
|
// TestSymlinks tests all the api of the VFS / Mount symlinks support
|
|
func TestSymlinks(t *testing.T) {
|
|
run.skipIfNoFUSE(t)
|
|
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("Skipping test on Windows")
|
|
}
|
|
|
|
{
|
|
// VFS only implements os.Stat, which return information to target for symlinks, getting symlink information would require os.Lstat implementation.
|
|
// We will not bother to add Lstat implemented, but in the test we can just call os.Lstat which return the information needed when !useVFS
|
|
|
|
// this is a link to a directory
|
|
// ldl, _ := os.Lstat("/tmp/kkk/link_dir")
|
|
// ld, _ := os.Stat("/tmp/kkk/link_dir")
|
|
|
|
// LINK_DIR: Lrwxrwxrwx, false <-> drwxr-xr-x, true
|
|
// fs.Logf(nil, "LINK_DIR: %v, %v <-> %v, %v", ldl.Mode(), ldl.IsDir(), ld.Mode(), ld.IsDir())
|
|
|
|
// This is a link to a regular file
|
|
// lfl, _ := os.Lstat("/tmp/kkk/link_file")
|
|
// lf, _ := os.Stat("/tmp/kkk/link_file")
|
|
|
|
// LINK_FILE: Lrwxrwxrwx, false <-> -rw-r--r--, false
|
|
// fs.Logf(nil, "LINK_FILE: %v, %v <-> %v, %v", lfl.Mode(), lfl.IsDir(), lf.Mode(), lf.IsDir())
|
|
}
|
|
|
|
suffix := ""
|
|
|
|
if run.useVFS || !run.vfsOpt.Links {
|
|
suffix = fs.LinkSuffix
|
|
}
|
|
|
|
fs.Logf(nil, "Links: %v, useVFS: %v, suffix: %v", run.vfsOpt.Links, run.useVFS, suffix)
|
|
|
|
run.mkdir(t, "dir1")
|
|
run.mkdir(t, "dir1/sub1dir1")
|
|
run.createFile(t, "dir1/file1", "potato")
|
|
|
|
run.mkdir(t, "dir2")
|
|
run.mkdir(t, "dir2/sub1dir2")
|
|
run.createFile(t, "dir2/file1", "chicken")
|
|
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
|
|
|
|
// Link to a file
|
|
run.relativeSymlink(t, "dir1/file1", "dir1file1_link"+suffix)
|
|
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|dir1file1_link"+suffix+" 10")
|
|
|
|
if run.vfsOpt.Links {
|
|
if run.useVFS {
|
|
run.checkMode(t, "dir1file1_link"+suffix, run.vfsOpt.LinkPerms, run.vfsOpt.LinkPerms)
|
|
} else {
|
|
run.checkMode(t, "dir1file1_link"+suffix, run.vfsOpt.LinkPerms, run.vfsOpt.FilePerms)
|
|
}
|
|
} else {
|
|
run.checkMode(t, "dir1file1_link"+suffix, run.vfsOpt.FilePerms, run.vfsOpt.FilePerms)
|
|
}
|
|
|
|
assert.Equal(t, "dir1/file1", run.readlink(t, "dir1file1_link"+suffix))
|
|
|
|
if !run.useVFS && run.vfsOpt.Links {
|
|
assert.Equal(t, "potato", run.readFile(t, "dir1file1_link"+suffix))
|
|
|
|
err := writeFile(run.path("dir1file1_link"+suffix), []byte("carrot"), 0600)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "carrot", run.readFile(t, "dir1file1_link"+suffix))
|
|
assert.Equal(t, "carrot", run.readFile(t, "dir1/file1"))
|
|
} else {
|
|
assert.Equal(t, "dir1/file1", run.readFile(t, "dir1file1_link"+suffix))
|
|
}
|
|
|
|
err := run.os.Rename(run.path("dir1file1_link"+suffix), run.path("dir1file1_link")+"_bla"+suffix)
|
|
require.NoError(t, err)
|
|
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|dir1file1_link_bla"+suffix+" 10")
|
|
|
|
assert.Equal(t, "dir1/file1", run.readlink(t, "dir1file1_link_bla"+suffix))
|
|
|
|
run.rm(t, "dir1file1_link_bla"+suffix)
|
|
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
|
|
|
|
// Link to a dir
|
|
run.relativeSymlink(t, "dir1", "dir1_link"+suffix)
|
|
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|dir1_link"+suffix+" 4")
|
|
|
|
if run.vfsOpt.Links {
|
|
if run.useVFS {
|
|
run.checkMode(t, "dir1_link"+suffix, run.vfsOpt.LinkPerms, run.vfsOpt.LinkPerms)
|
|
} else {
|
|
run.checkMode(t, "dir1_link"+suffix, run.vfsOpt.LinkPerms, run.vfsOpt.DirPerms)
|
|
}
|
|
} else {
|
|
run.checkMode(t, "dir1_link"+suffix, run.vfsOpt.FilePerms, run.vfsOpt.FilePerms)
|
|
}
|
|
|
|
assert.Equal(t, "dir1", run.readlink(t, "dir1_link"+suffix))
|
|
|
|
fh, err := run.os.OpenFile(run.path("dir1_link"+suffix), os.O_WRONLY, 0600)
|
|
|
|
if !run.useVFS && run.vfsOpt.Links {
|
|
require.Error(t, err)
|
|
|
|
dirLinksEntries := make(dirMap)
|
|
run.readLocal(t, dirLinksEntries, "dir1_link"+suffix)
|
|
|
|
assert.Equal(t, 2, len(dirLinksEntries))
|
|
|
|
dir1Entries := make(dirMap)
|
|
run.readLocal(t, dir1Entries, "dir1")
|
|
|
|
assert.Equal(t, 2, len(dir1Entries))
|
|
} else {
|
|
require.NoError(t, err)
|
|
// Don't care about the result, in some cache mode the file can't be opened for writing, so closing would trigger an err
|
|
_ = fh.Close()
|
|
|
|
assert.Equal(t, "dir1", run.readFile(t, "dir1_link"+suffix))
|
|
}
|
|
|
|
err = run.os.Rename(run.path("dir1_link"+suffix), run.path("dir1_link")+"_bla"+suffix)
|
|
require.NoError(t, err)
|
|
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|dir1_link_bla"+suffix+" 4")
|
|
|
|
assert.Equal(t, "dir1", run.readlink(t, "dir1_link_bla"+suffix))
|
|
|
|
run.rm(t, "dir1_link_bla"+suffix) // run.rmdir works fine as well
|
|
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
|
|
|
|
// Corner case #1 - We do not allow creating regular and symlink files having the same name (ie, test.txt and test.txt.rclonelink)
|
|
|
|
// Symlink first, then regular
|
|
{
|
|
link1Name := "link1.txt" + suffix
|
|
|
|
run.relativeSymlink(t, "dir1/file1", link1Name)
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1.txt"+suffix+" 10")
|
|
|
|
fh, err = run.os.OpenFile(run.path("link1.txt"), os.O_WRONLY|os.O_CREATE, run.vfsOpt.FilePerms)
|
|
|
|
// On real mount with links enabled, that open the symlink target as expected, else that fails to create a new file
|
|
if !run.useVFS && run.vfsOpt.Links {
|
|
assert.Equal(t, true, err == nil)
|
|
// Don't care about the result, in some cache mode the file can't be opened for writing, so closing would trigger an err
|
|
_ = fh.Close()
|
|
} else {
|
|
assert.Equal(t, true, err != nil)
|
|
}
|
|
|
|
run.rm(t, link1Name)
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
|
|
}
|
|
|
|
// Regular first, then symlink
|
|
{
|
|
link1Name := "link1.txt" + suffix
|
|
|
|
run.createFile(t, "link1.txt", "")
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1.txt 0")
|
|
|
|
err = run.os.Symlink(".", run.path(link1Name))
|
|
assert.Equal(t, true, err != nil)
|
|
|
|
run.rm(t, "link1.txt")
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
|
|
}
|
|
|
|
// Corner case #2 - We do not allow creating directory and symlink file having the same name (ie, test and test.rclonelink)
|
|
|
|
// Symlink first, then directory
|
|
{
|
|
link1Name := "link1" + suffix
|
|
|
|
run.relativeSymlink(t, ".", link1Name)
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1"+suffix+" 1")
|
|
|
|
err = run.os.Mkdir(run.path("link1"), run.vfsOpt.DirPerms)
|
|
assert.Equal(t, true, err != nil)
|
|
|
|
run.rm(t, link1Name)
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
|
|
}
|
|
|
|
// Directory first, then symlink
|
|
{
|
|
link1Name := "link1" + suffix
|
|
|
|
run.mkdir(t, "link1")
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1/")
|
|
|
|
err = run.os.Symlink(".", run.path(link1Name))
|
|
assert.Equal(t, true, err != nil)
|
|
|
|
run.rm(t, "link1")
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
|
|
}
|
|
|
|
// Corner case #3 - We do not allow moving directory or file having the same name in a target (ie, test and test.rclonelink)
|
|
|
|
// Move symlink -> regular file
|
|
{
|
|
link1Name := "link1.txt" + suffix
|
|
|
|
run.relativeSymlink(t, ".", link1Name)
|
|
run.createFile(t, "dir1/link1.txt", "")
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1.txt"+suffix+" 1|dir1/link1.txt 0")
|
|
|
|
err = run.os.Rename(run.path(link1Name), run.path("dir1/"+link1Name))
|
|
assert.Equal(t, true, err != nil)
|
|
|
|
run.rm(t, link1Name)
|
|
run.rm(t, "dir1/link1.txt")
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
|
|
}
|
|
|
|
// Move regular file -> symlink
|
|
{
|
|
link1Name := "link1.txt" + suffix
|
|
|
|
run.createFile(t, "link1.txt", "")
|
|
run.relativeSymlink(t, ".", "dir1/"+link1Name)
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1.txt 0|dir1/link1.txt"+suffix+" 1")
|
|
|
|
err = run.os.Rename(run.path("link1.txt"), run.path("dir1/link1.txt"))
|
|
assert.Equal(t, true, err != nil)
|
|
|
|
run.rm(t, "link1.txt")
|
|
run.rm(t, "dir1/"+link1Name)
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
|
|
}
|
|
|
|
// Move symlink -> directory
|
|
{
|
|
link1Name := "link1" + suffix
|
|
|
|
run.relativeSymlink(t, ".", link1Name)
|
|
run.mkdir(t, "dir1/link1")
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1"+suffix+" 1|dir1/link1/")
|
|
|
|
err = run.os.Rename(run.path(link1Name), run.path("dir1/"+link1Name))
|
|
assert.Equal(t, true, err != nil)
|
|
|
|
run.rm(t, link1Name)
|
|
run.rm(t, "dir1/link1")
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
|
|
}
|
|
|
|
// Move directory -> symlink
|
|
{
|
|
link1Name := "dir1/link1" + suffix
|
|
|
|
run.mkdir(t, "link1")
|
|
run.relativeSymlink(t, ".", link1Name)
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7|link1/|dir1/link1"+suffix+" 1")
|
|
|
|
err = run.os.Rename(run.path("link1"), run.path("dir1/link1"))
|
|
assert.Equal(t, true, err != nil)
|
|
|
|
run.rm(t, "link1")
|
|
run.rm(t, link1Name)
|
|
run.checkDir(t, "dir1/|dir1/sub1dir1/|dir1/file1 6|dir2/|dir2/sub1dir2/|dir2/file1 7")
|
|
}
|
|
}
|