diff --git a/backend/local/local.go b/backend/local/local.go index 573ffa0ab..f01493a3b 100644 --- a/backend/local/local.go +++ b/backend/local/local.go @@ -651,10 +651,17 @@ type localOpenFile struct { o *Object // object that is open in io.ReadCloser // handle we are wrapping hash *hash.MultiHasher // currently accumulating hashes + fd *os.File // file object reference } // Read bytes from the object - see io.Reader func (file *localOpenFile) Read(p []byte) (n int, err error) { + // Check if file has the same size and modTime + fi, err := file.fd.Stat() + if file.o.size != fi.Size() || file.o.modTime != fi.ModTime() { + return 0, errors.New("can't copy - source file is being updated") + } + n, err = file.in.Read(p) if n > 0 { // Hash routines never return an error @@ -713,6 +720,7 @@ func (o *Object) Open(options ...fs.OpenOption) (in io.ReadCloser, err error) { o: o, in: wrappedFd, hash: hash, + fd: fd, } return in, nil } diff --git a/backend/local/local_internal_test.go b/backend/local/local_internal_test.go index f8160cf98..9ac99e7f7 100644 --- a/backend/local/local_internal_test.go +++ b/backend/local/local_internal_test.go @@ -1,11 +1,23 @@ package local import ( + "os" + "path" "testing" + "time" + "github.com/ncw/rclone/fs/hash" + "github.com/ncw/rclone/fstest" + "github.com/ncw/rclone/lib/readers" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +// TestMain drives the tests +func TestMain(m *testing.M) { + fstest.TestMain(m) +} + func TestMapper(t *testing.T) { m := newMapper() assert.Equal(t, m.m, map[string]string{}) @@ -17,4 +29,34 @@ func TestMapper(t *testing.T) { }) assert.Equal(t, "potato", m.Load("potato")) assert.Equal(t, "-r?'a´o¨", m.Load("-r'áö")) + + // Test copy with source file that's updating + r := fstest.NewRun(t) + defer r.Finalise() + filePath := "sub dir/local test" + r.WriteFile(filePath, "content", time.Now()) + + fd, err := os.Open(path.Join(r.LocalName, filePath)) + if err != nil { + t.Fatalf("failed opening file %q: %v", filePath, err) + } + + fi, err := fd.Stat() + o := &Object{size: fi.Size(), modTime: fi.ModTime()} + wrappedFd := readers.NewLimitedReadCloser(fd, -1) + hash, err := hash.NewMultiHasherTypes(hash.Supported) + in := localOpenFile{ + o: o, + in: wrappedFd, + hash: hash, + fd: fd, + } + + buf := make([]byte, 1) + _, err = in.Read(buf) + require.NoError(t, err) + + r.WriteFile(filePath, "content updated", time.Now()) + _, err = in.Read(buf) + require.Errorf(t, err, "can't copy - source file is being updated") }