Skip to content

Commit 6e683b4

Browse files
committed
vfs: fix rename of open files when using the VFS cache
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
1 parent 241921c commit 6e683b4

File tree

3 files changed

+146
-32
lines changed

3 files changed

+146
-32
lines changed

vfs/file.go

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -137,55 +137,75 @@ func (f *File) applyPendingRename() {
137137
func (f *File) rename(ctx context.Context, destDir *Dir, newName string) error {
138138
f.mu.RLock()
139139
d := f.d
140+
o := f.o
141+
oldPendingRenameFun := f.pendingRenameFun
140142
f.mu.RUnlock()
143+
141144
if features := d.f.Features(); features.Move == nil && features.Copy == nil {
142145
err := errors.Errorf("Fs %q can't rename files (no server side Move or Copy)", d.f)
143146
fs.Errorf(f.Path(), "Dir.Rename error: %v", err)
144147
return err
145148
}
146149

150+
newPath := path.Join(destDir.path, newName)
151+
147152
renameCall := func(ctx context.Context) error {
148-
newPath := path.Join(destDir.path, newName)
153+
f.mu.RLock()
154+
o := f.o
155+
f.mu.RUnlock()
156+
if o == nil {
157+
return errors.New("Cannot rename: file object is not available")
158+
}
159+
160+
// chain rename calls if any
161+
if oldPendingRenameFun != nil {
162+
err := oldPendingRenameFun(ctx)
163+
if err != nil {
164+
return err
165+
}
166+
}
167+
168+
// do the move of the remote object
149169
dstOverwritten, _ := d.f.NewObject(ctx, newPath)
150-
newObject, err := operations.Move(ctx, d.f, dstOverwritten, newPath, f.o)
170+
newObject, err := operations.Move(ctx, d.f, dstOverwritten, newPath, o)
151171
if err != nil {
152172
fs.Errorf(f.Path(), "File.Rename error: %v", err)
153173
return err
154174
}
155175

156-
// Rename in the cache too if it exists
157-
if f.d.vfs.Opt.CacheMode >= CacheModeWrites && f.d.vfs.cache.exists(f.Path()) {
158-
if err := f.d.vfs.cache.rename(f.Path(), newPath); err != nil {
159-
fs.Infof(f.Path(), "File.Rename failed in Cache: %v", err)
160-
}
161-
}
162-
163176
// newObject can be nil here for example if --dry-run
164177
if newObject == nil {
165178
err = errors.New("rename failed: nil object returned")
166179
fs.Errorf(f.Path(), "File.Rename %v", err)
167180
return err
168181
}
169182
// Update the node with the new details
170-
fs.Debugf(f.o, "Updating file with %v %p", newObject, f)
183+
fs.Debugf(o, "Updating file with %v %p", newObject, f)
171184
// f.rename(destDir, newObject)
172185
f.mu.Lock()
173186
f.o = newObject
174-
f.d = destDir
175-
f.leaf = path.Base(newObject.Remote())
176187
f.pendingRenameFun = nil
177188
f.mu.Unlock()
178189
return nil
179190
}
180191

181-
f.mu.RLock()
192+
// Rename in the cache if it exists
193+
if f.d.vfs.Opt.CacheMode != CacheModeOff && f.d.vfs.cache.exists(f.Path()) {
194+
if err := f.d.vfs.cache.rename(f.Path(), newPath); err != nil {
195+
fs.Infof(f.Path(), "File.Rename failed in Cache: %v", err)
196+
}
197+
}
198+
199+
// rename the file object
200+
f.mu.Lock()
201+
f.d = destDir
202+
f.leaf = newName
182203
writing := f._writingInProgress()
183-
f.mu.RUnlock()
204+
f.mu.Unlock()
205+
184206
if writing {
185-
fs.Debugf(f.o, "File is currently open, delaying rename %p", f)
207+
fs.Debugf(o, "File is currently open, delaying rename %p", f)
186208
f.mu.Lock()
187-
f.d = destDir
188-
f.leaf = newName
189209
f.pendingRenameFun = renameCall
190210
f.mu.Unlock()
191211
return nil

vfs/file_test.go

Lines changed: 108 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@ import (
66
"os"
77
"testing"
88

9+
"github.com/rclone/rclone/fs"
910
"github.com/rclone/rclone/fstest"
1011
"github.com/rclone/rclone/fstest/mockfs"
1112
"github.com/rclone/rclone/fstest/mockobject"
1213
"github.com/stretchr/testify/assert"
1314
"github.com/stretchr/testify/require"
1415
)
1516

16-
func fileCreate(t *testing.T, r *fstest.Run) (*VFS, *File, fstest.Item) {
17-
vfs := New(r.Fremote, nil)
17+
func fileCreate(t *testing.T, r *fstest.Run, mode CacheMode) (*VFS, *File, fstest.Item) {
18+
opt := DefaultOpt
19+
opt.CacheMode = mode
20+
vfs := New(r.Fremote, &opt)
1821

1922
file1 := r.WriteObject(context.Background(), "dir/file1", "file1 contents", t1)
2023
fstest.CheckItems(t, r.Fremote, file1)
@@ -29,7 +32,7 @@ func fileCreate(t *testing.T, r *fstest.Run) (*VFS, *File, fstest.Item) {
2932
func TestFileMethods(t *testing.T) {
3033
r := fstest.NewRun(t)
3134
defer r.Finalise()
32-
vfs, file, _ := fileCreate(t, r)
35+
vfs, file, _ := fileCreate(t, r, CacheModeOff)
3336

3437
// String
3538
assert.Equal(t, "dir/file1", file.String())
@@ -84,7 +87,7 @@ func TestFileSetModTime(t *testing.T) {
8487
return
8588
}
8689
defer r.Finalise()
87-
vfs, file, file1 := fileCreate(t, r)
90+
vfs, file, file1 := fileCreate(t, r, CacheModeOff)
8891

8992
err := file.SetModTime(t2)
9093
require.NoError(t, err)
@@ -97,12 +100,8 @@ func TestFileSetModTime(t *testing.T) {
97100
assert.Equal(t, EROFS, err)
98101
}
99102

100-
func TestFileOpenRead(t *testing.T) {
101-
r := fstest.NewRun(t)
102-
defer r.Finalise()
103-
_, file, _ := fileCreate(t, r)
104-
105-
fd, err := file.openRead()
103+
func fileCheckContents(t *testing.T, file *File) {
104+
fd, err := file.Open(os.O_RDONLY)
106105
require.NoError(t, err)
107106

108107
contents, err := ioutil.ReadAll(fd)
@@ -112,6 +111,14 @@ func TestFileOpenRead(t *testing.T) {
112111
require.NoError(t, fd.Close())
113112
}
114113

114+
func TestFileOpenRead(t *testing.T) {
115+
r := fstest.NewRun(t)
116+
defer r.Finalise()
117+
_, file, _ := fileCreate(t, r, CacheModeOff)
118+
119+
fileCheckContents(t, file)
120+
}
121+
115122
func TestFileOpenReadUnknownSize(t *testing.T) {
116123
var (
117124
contents = []byte("file contents")
@@ -160,7 +167,7 @@ func TestFileOpenReadUnknownSize(t *testing.T) {
160167
func TestFileOpenWrite(t *testing.T) {
161168
r := fstest.NewRun(t)
162169
defer r.Finalise()
163-
vfs, file, _ := fileCreate(t, r)
170+
vfs, file, _ := fileCreate(t, r, CacheModeOff)
164171

165172
fd, err := file.openWrite(os.O_WRONLY | os.O_TRUNC)
166173
require.NoError(t, err)
@@ -181,7 +188,7 @@ func TestFileOpenWrite(t *testing.T) {
181188
func TestFileRemove(t *testing.T) {
182189
r := fstest.NewRun(t)
183190
defer r.Finalise()
184-
vfs, file, _ := fileCreate(t, r)
191+
vfs, file, _ := fileCreate(t, r, CacheModeOff)
185192

186193
err := file.Remove()
187194
require.NoError(t, err)
@@ -196,7 +203,7 @@ func TestFileRemove(t *testing.T) {
196203
func TestFileRemoveAll(t *testing.T) {
197204
r := fstest.NewRun(t)
198205
defer r.Finalise()
199-
vfs, file, _ := fileCreate(t, r)
206+
vfs, file, _ := fileCreate(t, r, CacheModeOff)
200207

201208
err := file.RemoveAll()
202209
require.NoError(t, err)
@@ -211,7 +218,7 @@ func TestFileRemoveAll(t *testing.T) {
211218
func TestFileOpen(t *testing.T) {
212219
r := fstest.NewRun(t)
213220
defer r.Finalise()
214-
_, file, _ := fileCreate(t, r)
221+
_, file, _ := fileCreate(t, r, CacheModeOff)
215222

216223
fd, err := file.Open(os.O_RDONLY)
217224
require.NoError(t, err)
@@ -233,3 +240,90 @@ func TestFileOpen(t *testing.T) {
233240
fd, err = file.Open(3)
234241
assert.Equal(t, EPERM, err)
235242
}
243+
244+
func testFileRename(t *testing.T, mode CacheMode) {
245+
r := fstest.NewRun(t)
246+
defer r.Finalise()
247+
vfs, file, item := fileCreate(t, r, mode)
248+
249+
rootDir, err := vfs.Root()
250+
require.NoError(t, err)
251+
252+
// check file in cache
253+
if mode != CacheModeOff {
254+
// read contents to get file in cache
255+
fileCheckContents(t, file)
256+
assert.True(t, vfs.cache.exists(item.Path))
257+
}
258+
259+
dir := file.Dir()
260+
261+
// start with "dir/file1"
262+
fstest.CheckItems(t, r.Fremote, item)
263+
264+
// rename file to "newLeaf"
265+
err = dir.Rename("file1", "newLeaf", rootDir)
266+
require.NoError(t, err)
267+
268+
item.Path = "newLeaf"
269+
fstest.CheckItems(t, r.Fremote, item)
270+
271+
// check file in cache
272+
if mode != CacheModeOff {
273+
assert.True(t, vfs.cache.exists(item.Path))
274+
}
275+
276+
// check file exists in the vfs layer at its new name
277+
_, err = vfs.Stat("newLeaf")
278+
require.NoError(t, err)
279+
280+
// rename it back to "dir/file1"
281+
err = rootDir.Rename("newLeaf", "file1", dir)
282+
require.NoError(t, err)
283+
284+
item.Path = "dir/file1"
285+
fstest.CheckItems(t, r.Fremote, item)
286+
287+
// check file in cache
288+
if mode != CacheModeOff {
289+
assert.True(t, vfs.cache.exists(item.Path))
290+
}
291+
292+
// now try renaming it with the file open
293+
// first open it and write to it but dont close it
294+
fd, err := file.Open(os.O_WRONLY | os.O_TRUNC)
295+
require.NoError(t, err)
296+
newContents := []byte("this is some new contents")
297+
_, err = fd.Write(newContents)
298+
require.NoError(t, err)
299+
300+
// rename file to "newLeaf"
301+
err = dir.Rename("file1", "newLeaf", rootDir)
302+
require.NoError(t, err)
303+
newItem := fstest.NewItem("newLeaf", string(newContents), item.ModTime)
304+
305+
// check file has been renamed immediately in the cache
306+
if mode != CacheModeOff {
307+
assert.True(t, vfs.cache.exists("newLeaf"))
308+
}
309+
310+
// check file exists in the vfs layer at its new name
311+
_, err = vfs.Stat("newLeaf")
312+
require.NoError(t, err)
313+
314+
// Close the file
315+
require.NoError(t, fd.Close())
316+
317+
// Check file has now been renamed on the remote
318+
item.Path = "newLeaf"
319+
fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{newItem}, nil, fs.ModTimeNotSupported)
320+
}
321+
322+
func TestFileRename(t *testing.T) {
323+
t.Run("CacheModeOff", func(t *testing.T) {
324+
testFileRename(t, CacheModeOff)
325+
})
326+
t.Run("CacheModeFull", func(t *testing.T) {
327+
testFileRename(t, CacheModeFull)
328+
})
329+
}

vfs/read_write_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,7 @@ func TestRWFileModTimeWithOpenWriters(t *testing.T) {
698698
}
699699
}
700700

701-
func TestCacheRename(t *testing.T) {
701+
func TestRWCacheRename(t *testing.T) {
702702
r := fstest.NewRun(t)
703703
defer r.Finalise()
704704

0 commit comments

Comments
 (0)