mirror of
https://github.com/rclone/rclone.git
synced 2024-12-01 12:55:19 +01:00
6e683b4359
Before this change, renaming an open file when using the VFS cache was delayed until the file was closed. This meant that the file was not readable after a rename even though it is was in the cache. After this change we rename the local cache file and the in memory cache, delaying only the rename of the file in object storage. See: https://forum.rclone.org/t/xen-orchestra-ebadf-bad-file-descriptor-write/13104
330 lines
7.4 KiB
Go
330 lines
7.4 KiB
Go
package vfs
|
|
|
|
import (
|
|
"context"
|
|
"io/ioutil"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fstest"
|
|
"github.com/rclone/rclone/fstest/mockfs"
|
|
"github.com/rclone/rclone/fstest/mockobject"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func fileCreate(t *testing.T, r *fstest.Run, mode CacheMode) (*VFS, *File, fstest.Item) {
|
|
opt := DefaultOpt
|
|
opt.CacheMode = mode
|
|
vfs := New(r.Fremote, &opt)
|
|
|
|
file1 := r.WriteObject(context.Background(), "dir/file1", "file1 contents", t1)
|
|
fstest.CheckItems(t, r.Fremote, file1)
|
|
|
|
node, err := vfs.Stat("dir/file1")
|
|
require.NoError(t, err)
|
|
require.True(t, node.Mode().IsRegular())
|
|
|
|
return vfs, node.(*File), file1
|
|
}
|
|
|
|
func TestFileMethods(t *testing.T) {
|
|
r := fstest.NewRun(t)
|
|
defer r.Finalise()
|
|
vfs, file, _ := fileCreate(t, r, CacheModeOff)
|
|
|
|
// String
|
|
assert.Equal(t, "dir/file1", file.String())
|
|
assert.Equal(t, "<nil *File>", (*File)(nil).String())
|
|
|
|
// IsDir
|
|
assert.Equal(t, false, file.IsDir())
|
|
|
|
// IsFile
|
|
assert.Equal(t, true, file.IsFile())
|
|
|
|
// Mode
|
|
assert.Equal(t, vfs.Opt.FilePerms, file.Mode())
|
|
|
|
// Name
|
|
assert.Equal(t, "file1", file.Name())
|
|
|
|
// Path
|
|
assert.Equal(t, "dir/file1", file.Path())
|
|
|
|
// Sys
|
|
assert.Equal(t, nil, file.Sys())
|
|
|
|
// Inode
|
|
assert.NotEqual(t, uint64(0), file.Inode())
|
|
|
|
// Node
|
|
assert.Equal(t, file, file.Node())
|
|
|
|
// ModTime
|
|
assert.WithinDuration(t, t1, file.ModTime(), r.Fremote.Precision())
|
|
|
|
// Size
|
|
assert.Equal(t, int64(14), file.Size())
|
|
|
|
// Sync
|
|
assert.NoError(t, file.Sync())
|
|
|
|
// DirEntry
|
|
assert.Equal(t, file.o, file.DirEntry())
|
|
|
|
// Dir
|
|
assert.Equal(t, file.d, file.Dir())
|
|
|
|
// VFS
|
|
assert.Equal(t, vfs, file.VFS())
|
|
}
|
|
|
|
func TestFileSetModTime(t *testing.T) {
|
|
r := fstest.NewRun(t)
|
|
if !canSetModTime(t, r) {
|
|
return
|
|
}
|
|
defer r.Finalise()
|
|
vfs, file, file1 := fileCreate(t, r, CacheModeOff)
|
|
|
|
err := file.SetModTime(t2)
|
|
require.NoError(t, err)
|
|
|
|
file1.ModTime = t2
|
|
fstest.CheckItems(t, r.Fremote, file1)
|
|
|
|
vfs.Opt.ReadOnly = true
|
|
err = file.SetModTime(t2)
|
|
assert.Equal(t, EROFS, err)
|
|
}
|
|
|
|
func fileCheckContents(t *testing.T, file *File) {
|
|
fd, err := file.Open(os.O_RDONLY)
|
|
require.NoError(t, err)
|
|
|
|
contents, err := ioutil.ReadAll(fd)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "file1 contents", string(contents))
|
|
|
|
require.NoError(t, fd.Close())
|
|
}
|
|
|
|
func TestFileOpenRead(t *testing.T) {
|
|
r := fstest.NewRun(t)
|
|
defer r.Finalise()
|
|
_, file, _ := fileCreate(t, r, CacheModeOff)
|
|
|
|
fileCheckContents(t, file)
|
|
}
|
|
|
|
func TestFileOpenReadUnknownSize(t *testing.T) {
|
|
var (
|
|
contents = []byte("file contents")
|
|
remote = "file.txt"
|
|
ctx = context.Background()
|
|
)
|
|
|
|
// create a mock object which returns size -1
|
|
o := mockobject.New(remote).WithContent(contents, mockobject.SeekModeNone)
|
|
o.SetUnknownSize(true)
|
|
assert.Equal(t, int64(-1), o.Size())
|
|
|
|
// add it to a mock fs
|
|
f := mockfs.NewFs("test", "root")
|
|
f.AddObject(o)
|
|
testObj, err := f.NewObject(ctx, remote)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(-1), testObj.Size())
|
|
|
|
// create a VFS from that mockfs
|
|
vfs := New(f, nil)
|
|
|
|
// find the file
|
|
node, err := vfs.Stat(remote)
|
|
require.NoError(t, err)
|
|
require.True(t, node.IsFile())
|
|
file := node.(*File)
|
|
|
|
// open it
|
|
fd, err := file.openRead()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(0), fd.Size())
|
|
|
|
// check the contents are not empty even though size is empty
|
|
gotContents, err := ioutil.ReadAll(fd)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, contents, gotContents)
|
|
t.Logf("gotContents = %q", gotContents)
|
|
|
|
// check that file size has been updated
|
|
assert.Equal(t, int64(len(contents)), fd.Size())
|
|
|
|
require.NoError(t, fd.Close())
|
|
}
|
|
|
|
func TestFileOpenWrite(t *testing.T) {
|
|
r := fstest.NewRun(t)
|
|
defer r.Finalise()
|
|
vfs, file, _ := fileCreate(t, r, CacheModeOff)
|
|
|
|
fd, err := file.openWrite(os.O_WRONLY | os.O_TRUNC)
|
|
require.NoError(t, err)
|
|
|
|
newContents := []byte("this is some new contents")
|
|
n, err := fd.Write(newContents)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, len(newContents), n)
|
|
require.NoError(t, fd.Close())
|
|
|
|
assert.Equal(t, int64(25), file.Size())
|
|
|
|
vfs.Opt.ReadOnly = true
|
|
_, err = file.openWrite(os.O_WRONLY | os.O_TRUNC)
|
|
assert.Equal(t, EROFS, err)
|
|
}
|
|
|
|
func TestFileRemove(t *testing.T) {
|
|
r := fstest.NewRun(t)
|
|
defer r.Finalise()
|
|
vfs, file, _ := fileCreate(t, r, CacheModeOff)
|
|
|
|
err := file.Remove()
|
|
require.NoError(t, err)
|
|
|
|
fstest.CheckItems(t, r.Fremote)
|
|
|
|
vfs.Opt.ReadOnly = true
|
|
err = file.Remove()
|
|
assert.Equal(t, EROFS, err)
|
|
}
|
|
|
|
func TestFileRemoveAll(t *testing.T) {
|
|
r := fstest.NewRun(t)
|
|
defer r.Finalise()
|
|
vfs, file, _ := fileCreate(t, r, CacheModeOff)
|
|
|
|
err := file.RemoveAll()
|
|
require.NoError(t, err)
|
|
|
|
fstest.CheckItems(t, r.Fremote)
|
|
|
|
vfs.Opt.ReadOnly = true
|
|
err = file.RemoveAll()
|
|
assert.Equal(t, EROFS, err)
|
|
}
|
|
|
|
func TestFileOpen(t *testing.T) {
|
|
r := fstest.NewRun(t)
|
|
defer r.Finalise()
|
|
_, file, _ := fileCreate(t, r, CacheModeOff)
|
|
|
|
fd, err := file.Open(os.O_RDONLY)
|
|
require.NoError(t, err)
|
|
_, ok := fd.(*ReadFileHandle)
|
|
assert.True(t, ok)
|
|
require.NoError(t, fd.Close())
|
|
|
|
fd, err = file.Open(os.O_WRONLY)
|
|
assert.NoError(t, err)
|
|
_, ok = fd.(*WriteFileHandle)
|
|
assert.True(t, ok)
|
|
require.NoError(t, fd.Close())
|
|
|
|
fd, err = file.Open(os.O_RDWR)
|
|
assert.NoError(t, err)
|
|
_, ok = fd.(*WriteFileHandle)
|
|
assert.True(t, ok)
|
|
|
|
fd, err = file.Open(3)
|
|
assert.Equal(t, EPERM, err)
|
|
}
|
|
|
|
func testFileRename(t *testing.T, mode CacheMode) {
|
|
r := fstest.NewRun(t)
|
|
defer r.Finalise()
|
|
vfs, file, item := fileCreate(t, r, mode)
|
|
|
|
rootDir, err := vfs.Root()
|
|
require.NoError(t, err)
|
|
|
|
// check file in cache
|
|
if mode != CacheModeOff {
|
|
// read contents to get file in cache
|
|
fileCheckContents(t, file)
|
|
assert.True(t, vfs.cache.exists(item.Path))
|
|
}
|
|
|
|
dir := file.Dir()
|
|
|
|
// start with "dir/file1"
|
|
fstest.CheckItems(t, r.Fremote, item)
|
|
|
|
// rename file to "newLeaf"
|
|
err = dir.Rename("file1", "newLeaf", rootDir)
|
|
require.NoError(t, err)
|
|
|
|
item.Path = "newLeaf"
|
|
fstest.CheckItems(t, r.Fremote, item)
|
|
|
|
// check file in cache
|
|
if mode != CacheModeOff {
|
|
assert.True(t, vfs.cache.exists(item.Path))
|
|
}
|
|
|
|
// check file exists in the vfs layer at its new name
|
|
_, err = vfs.Stat("newLeaf")
|
|
require.NoError(t, err)
|
|
|
|
// rename it back to "dir/file1"
|
|
err = rootDir.Rename("newLeaf", "file1", dir)
|
|
require.NoError(t, err)
|
|
|
|
item.Path = "dir/file1"
|
|
fstest.CheckItems(t, r.Fremote, item)
|
|
|
|
// check file in cache
|
|
if mode != CacheModeOff {
|
|
assert.True(t, vfs.cache.exists(item.Path))
|
|
}
|
|
|
|
// now try renaming it with the file open
|
|
// first open it and write to it but dont close it
|
|
fd, err := file.Open(os.O_WRONLY | os.O_TRUNC)
|
|
require.NoError(t, err)
|
|
newContents := []byte("this is some new contents")
|
|
_, err = fd.Write(newContents)
|
|
require.NoError(t, err)
|
|
|
|
// rename file to "newLeaf"
|
|
err = dir.Rename("file1", "newLeaf", rootDir)
|
|
require.NoError(t, err)
|
|
newItem := fstest.NewItem("newLeaf", string(newContents), item.ModTime)
|
|
|
|
// check file has been renamed immediately in the cache
|
|
if mode != CacheModeOff {
|
|
assert.True(t, vfs.cache.exists("newLeaf"))
|
|
}
|
|
|
|
// check file exists in the vfs layer at its new name
|
|
_, err = vfs.Stat("newLeaf")
|
|
require.NoError(t, err)
|
|
|
|
// Close the file
|
|
require.NoError(t, fd.Close())
|
|
|
|
// Check file has now been renamed on the remote
|
|
item.Path = "newLeaf"
|
|
fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{newItem}, nil, fs.ModTimeNotSupported)
|
|
}
|
|
|
|
func TestFileRename(t *testing.T) {
|
|
t.Run("CacheModeOff", func(t *testing.T) {
|
|
testFileRename(t, CacheModeOff)
|
|
})
|
|
t.Run("CacheModeFull", func(t *testing.T) {
|
|
testFileRename(t, CacheModeFull)
|
|
})
|
|
}
|