forked from TrueCloudLab/rclone
parent
358c1fbac9
commit
62e72801be
4 changed files with 202 additions and 127 deletions
64
vfs/file.go
64
vfs/file.go
|
@ -14,14 +14,21 @@ import (
|
||||||
|
|
||||||
// File represents a file
|
// File represents a file
|
||||||
type File struct {
|
type File struct {
|
||||||
inode uint64 // inode number
|
inode uint64 // inode number
|
||||||
size int64 // size of file - read and written with atomic int64 - must be 64 bit aligned
|
size int64 // size of file - read and written with atomic int64 - must be 64 bit aligned
|
||||||
d *Dir // parent directory - read only
|
d *Dir // parent directory - read only
|
||||||
mu sync.RWMutex // protects the following
|
|
||||||
o fs.Object // NB o may be nil if file is being written
|
mu sync.Mutex // protects the following
|
||||||
leaf string // leaf name of the object
|
o fs.Object // NB o may be nil if file is being written
|
||||||
writers []Handle // writers for this file
|
leaf string // leaf name of the object
|
||||||
pendingModTime time.Time // will be applied once o becomes available, i.e. after file was written
|
writers []Handle // writers for this file
|
||||||
|
readWriters int // how many RWFileHandle are open for writing
|
||||||
|
readWriterClosing bool // is a RWFileHandle currently cosing?
|
||||||
|
modified bool // has the cache file be modified by a RWFileHandle?
|
||||||
|
pendingModTime time.Time // will be applied once o becomes available, i.e. after file was written
|
||||||
|
|
||||||
|
muClose sync.Mutex // synchonize RWFileHandle.close()
|
||||||
|
muOpen sync.Mutex // synchonize RWFileHandle.openPending()
|
||||||
}
|
}
|
||||||
|
|
||||||
// newFile creates a new File
|
// newFile creates a new File
|
||||||
|
@ -95,12 +102,16 @@ func (f *File) rename(d *Dir, o fs.Object) {
|
||||||
func (f *File) addWriter(h Handle) {
|
func (f *File) addWriter(h Handle) {
|
||||||
f.mu.Lock()
|
f.mu.Lock()
|
||||||
f.writers = append(f.writers, h)
|
f.writers = append(f.writers, h)
|
||||||
|
if _, ok := h.(*RWFileHandle); ok {
|
||||||
|
f.readWriters++
|
||||||
|
}
|
||||||
f.mu.Unlock()
|
f.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// delWriter removes a write handle from the file
|
// delWriter removes a write handle from the file
|
||||||
func (f *File) delWriter(h Handle) {
|
func (f *File) delWriter(h Handle, modifiedCacheFile bool) (lastWriterAndModified bool) {
|
||||||
f.mu.Lock()
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
var found = -1
|
var found = -1
|
||||||
for i := range f.writers {
|
for i := range f.writers {
|
||||||
if f.writers[i] == h {
|
if f.writers[i] == h {
|
||||||
|
@ -113,6 +124,24 @@ func (f *File) delWriter(h Handle) {
|
||||||
} else {
|
} else {
|
||||||
fs.Debugf(f.o, "File.delWriter couldn't find handle")
|
fs.Debugf(f.o, "File.delWriter couldn't find handle")
|
||||||
}
|
}
|
||||||
|
if _, ok := h.(*RWFileHandle); ok {
|
||||||
|
f.readWriters--
|
||||||
|
}
|
||||||
|
f.readWriterClosing = true
|
||||||
|
if modifiedCacheFile {
|
||||||
|
f.modified = true
|
||||||
|
}
|
||||||
|
lastWriterAndModified = len(f.writers) == 0 && f.modified
|
||||||
|
if lastWriterAndModified {
|
||||||
|
f.modified = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// finishWriterClose resets the readWriterClosing flag
|
||||||
|
func (f *File) finishWriterClose() {
|
||||||
|
f.mu.Lock()
|
||||||
|
f.readWriterClosing = false
|
||||||
f.mu.Unlock()
|
f.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +161,7 @@ func (f *File) ModTime() (modTime time.Time) {
|
||||||
|
|
||||||
if !f.d.vfs.Opt.NoModTime {
|
if !f.d.vfs.Opt.NoModTime {
|
||||||
// if o is nil it isn't valid yet or there are writers, so return the size so far
|
// if o is nil it isn't valid yet or there are writers, so return the size so far
|
||||||
if f.o == nil || len(f.writers) != 0 {
|
if f.o == nil || len(f.writers) != 0 || f.readWriterClosing {
|
||||||
if !f.pendingModTime.IsZero() {
|
if !f.pendingModTime.IsZero() {
|
||||||
return f.pendingModTime
|
return f.pendingModTime
|
||||||
}
|
}
|
||||||
|
@ -158,7 +187,7 @@ func (f *File) Size() int64 {
|
||||||
defer f.mu.Unlock()
|
defer f.mu.Unlock()
|
||||||
|
|
||||||
// if o is nil it isn't valid yet or there are writers, so return the size so far
|
// if o is nil it isn't valid yet or there are writers, so return the size so far
|
||||||
if f.o == nil || len(f.writers) != 0 {
|
if f.o == nil || len(f.writers) != 0 || f.readWriterClosing {
|
||||||
return atomic.LoadInt64(&f.size)
|
return atomic.LoadInt64(&f.size)
|
||||||
}
|
}
|
||||||
return nonNegative(f.o.Size())
|
return nonNegative(f.o.Size())
|
||||||
|
@ -174,7 +203,8 @@ func (f *File) SetModTime(modTime time.Time) error {
|
||||||
|
|
||||||
f.pendingModTime = modTime
|
f.pendingModTime = modTime
|
||||||
|
|
||||||
if f.o != nil {
|
// Only update the ModTime when there are no writers, setObject will do it
|
||||||
|
if f.o != nil && len(f.writers) == 0 && !f.readWriterClosing {
|
||||||
return f.applyPendingModTime()
|
return f.applyPendingModTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,11 +268,12 @@ func (f *File) waitForValidObject() (o fs.Object, err error) {
|
||||||
f.mu.Lock()
|
f.mu.Lock()
|
||||||
o = f.o
|
o = f.o
|
||||||
nwriters := len(f.writers)
|
nwriters := len(f.writers)
|
||||||
|
wclosing := f.readWriterClosing
|
||||||
f.mu.Unlock()
|
f.mu.Unlock()
|
||||||
if o != nil {
|
if o != nil {
|
||||||
return o, nil
|
return o, nil
|
||||||
}
|
}
|
||||||
if nwriters == 0 {
|
if nwriters == 0 && !wclosing {
|
||||||
return nil, errors.New("can't open file - writer failed")
|
return nil, errors.New("can't open file - writer failed")
|
||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
@ -400,7 +431,10 @@ func (f *File) Open(flags int) (fd Handle, err error) {
|
||||||
|
|
||||||
// Open the correct sort of handle
|
// Open the correct sort of handle
|
||||||
CacheMode := f.d.vfs.Opt.CacheMode
|
CacheMode := f.d.vfs.Opt.CacheMode
|
||||||
if read && write {
|
CacheItem := f.d.vfs.cache.get(f.Path())
|
||||||
|
if CacheMode >= CacheModeMinimal && CacheItem.opens > 0 {
|
||||||
|
fd, err = f.openRW(flags)
|
||||||
|
} else if read && write {
|
||||||
if CacheMode >= CacheModeMinimal {
|
if CacheMode >= CacheModeMinimal {
|
||||||
fd, err = f.openRW(flags)
|
fd, err = f.openRW(flags)
|
||||||
} else {
|
} else {
|
||||||
|
@ -437,6 +471,8 @@ func (f *File) Truncate(size int64) (err error) {
|
||||||
copy(writers, f.writers)
|
copy(writers, f.writers)
|
||||||
f.mu.Unlock()
|
f.mu.Unlock()
|
||||||
|
|
||||||
|
// FIXME: handle closing writer
|
||||||
|
|
||||||
// If have writers then call truncate for each writer
|
// If have writers then call truncate for each writer
|
||||||
if len(writers) != 0 {
|
if len(writers) != 0 {
|
||||||
fs.Debugf(f.o, "Truncating %d file handles", len(writers))
|
fs.Debugf(f.o, "Truncating %d file handles", len(writers))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package vfs
|
package vfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -69,6 +70,14 @@ func newRWFileHandle(d *Dir, f *File, remote string, flags int) (fh *RWFileHandl
|
||||||
fh.file.addWriter(fh)
|
fh.file.addWriter(fh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// truncate or create files immediately to prepare the cache
|
||||||
|
if fh.flags&os.O_TRUNC != 0 || fh.flags&(os.O_CREATE) != 0 && !f.exists() {
|
||||||
|
if err := fh.openPending(false); err != nil {
|
||||||
|
fh.file.delWriter(fh, false)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return fh, nil
|
return fh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,29 +89,42 @@ func (fh *RWFileHandle) openPending(truncate bool) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheFileOpenFlags := fh.flags
|
fh.file.muOpen.Lock()
|
||||||
|
defer fh.file.muOpen.Unlock()
|
||||||
|
|
||||||
|
var fd *os.File
|
||||||
|
cacheFileOpenFlags := fh.flags
|
||||||
// if not truncating the file, need to read it first
|
// if not truncating the file, need to read it first
|
||||||
if fh.flags&os.O_TRUNC == 0 && !truncate {
|
if fh.flags&os.O_TRUNC == 0 && !truncate {
|
||||||
// Fetch the file if it hasn't changed
|
// try to open a exising cache file
|
||||||
// FIXME retries
|
fd, err = os.OpenFile(fh.osPath, cacheFileOpenFlags&^os.O_CREATE, 0600)
|
||||||
err = operations.CopyFile(fh.d.vfs.cache.f, fh.d.vfs.f, fh.remote, fh.remote)
|
if os.IsNotExist(err) {
|
||||||
if err != nil {
|
// Fetch the file if it hasn't changed
|
||||||
// if the object wasn't found AND O_CREATE is set then...
|
// FIXME retries
|
||||||
cause := errors.Cause(err)
|
err = operations.CopyFile(fh.d.vfs.cache.f, fh.d.vfs.f, fh.remote, fh.remote)
|
||||||
notFound := cause == fs.ErrorObjectNotFound || cause == fs.ErrorDirNotFound
|
if err != nil {
|
||||||
if notFound {
|
// if the object wasn't found AND O_CREATE is set then...
|
||||||
// Remove cached item if there is one
|
cause := errors.Cause(err)
|
||||||
rmErr := os.Remove(fh.osPath)
|
notFound := cause == fs.ErrorObjectNotFound || cause == fs.ErrorDirNotFound
|
||||||
if rmErr != nil && !os.IsNotExist(rmErr) {
|
if notFound {
|
||||||
return errors.Wrap(rmErr, "open RW handle failed to delete stale cache file")
|
// Remove cached item if there is one
|
||||||
|
rmErr := os.Remove(fh.osPath)
|
||||||
|
if rmErr != nil && !os.IsNotExist(rmErr) {
|
||||||
|
return errors.Wrap(rmErr, "open RW handle failed to delete stale cache file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if notFound && fh.flags&os.O_CREATE != 0 {
|
||||||
|
// ...ignore error as we are about to create the file
|
||||||
|
fh.file.setSize(0)
|
||||||
|
fh.writeCalled = true
|
||||||
|
} else {
|
||||||
|
return errors.Wrap(err, "open RW handle failed to cache file")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if notFound && fh.flags&os.O_CREATE != 0 {
|
} else if err != nil {
|
||||||
// ...ignore error as we are about to create the file
|
return errors.Wrap(err, "cache open file failed")
|
||||||
} else {
|
} else {
|
||||||
return errors.Wrap(err, "open RW handle failed to cache file")
|
fs.Debugf(fh.logPrefix(), "Opened existing cached copy with flags=%s", decodeOpenFlags(fh.flags))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Set the size to 0 since we are truncating and flag we need to write it back
|
// Set the size to 0 since we are truncating and flag we need to write it back
|
||||||
|
@ -127,10 +149,12 @@ func (fh *RWFileHandle) openPending(truncate bool) (err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.Debugf(fh.remote, "Opening cached copy with flags=%s", decodeOpenFlags(fh.flags))
|
if fd == nil {
|
||||||
fd, err := os.OpenFile(fh.osPath, cacheFileOpenFlags, 0600)
|
fs.Debugf(fh.logPrefix(), "Opening cached copy with flags=%s", decodeOpenFlags(fh.flags))
|
||||||
if err != nil {
|
fd, err = os.OpenFile(fh.osPath, cacheFileOpenFlags, 0600)
|
||||||
return errors.Wrap(err, "cache open file failed")
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "cache open file failed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fh.File = fd
|
fh.File = fd
|
||||||
fh.opened = true
|
fh.opened = true
|
||||||
|
@ -166,85 +190,74 @@ func (fh *RWFileHandle) Node() Node {
|
||||||
// Note that we leave the file around in the cache on error conditions
|
// Note that we leave the file around in the cache on error conditions
|
||||||
// to give the user a chance to recover it.
|
// to give the user a chance to recover it.
|
||||||
func (fh *RWFileHandle) close() (err error) {
|
func (fh *RWFileHandle) close() (err error) {
|
||||||
defer log.Trace(fh.remote, "")("err=%v", &err)
|
defer log.Trace(fh.logPrefix(), "")("err=%v", &err)
|
||||||
|
fh.file.muClose.Lock()
|
||||||
|
defer fh.file.muClose.Unlock()
|
||||||
|
|
||||||
if fh.closed {
|
if fh.closed {
|
||||||
return ECLOSED
|
return ECLOSED
|
||||||
}
|
}
|
||||||
fh.closed = true
|
fh.closed = true
|
||||||
defer fh.d.vfs.cache.close(fh.remote)
|
defer fh.d.vfs.cache.close(fh.remote)
|
||||||
rdwrMode := fh.flags & accessModeMask
|
rdwrMode := fh.flags & accessModeMask
|
||||||
if rdwrMode != os.O_RDONLY {
|
writer := rdwrMode != os.O_RDONLY
|
||||||
// leave writer open until file is transferred
|
|
||||||
defer fh.file.delWriter(fh)
|
// If read only then return
|
||||||
|
if !fh.opened && rdwrMode == os.O_RDONLY {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if !fh.opened {
|
|
||||||
// If read only then return
|
copy := false
|
||||||
if rdwrMode == os.O_RDONLY {
|
if writer {
|
||||||
return nil
|
copy = fh.file.delWriter(fh, fh.modified())
|
||||||
}
|
defer fh.file.finishWriterClose()
|
||||||
// If we aren't creating or truncating the file then
|
}
|
||||||
// we haven't modified it so don't need to transfer it
|
|
||||||
if fh.flags&(os.O_CREATE|os.O_TRUNC) == 0 {
|
// If we aren't creating or truncating the file then
|
||||||
return nil
|
// we haven't modified it so don't need to transfer it
|
||||||
}
|
if fh.flags&(os.O_CREATE|os.O_TRUNC) != 0 {
|
||||||
// Otherwise open the file
|
|
||||||
// FIXME this could be more efficient
|
|
||||||
if err := fh.openPending(false); err != nil {
|
if err := fh.openPending(false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rdwrMode != os.O_RDONLY {
|
|
||||||
|
if writer && fh.opened {
|
||||||
fi, err := fh.File.Stat()
|
fi, err := fh.File.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.Errorf(fh.remote, "Failed to stat cache file: %v", err)
|
fs.Errorf(fh.logPrefix(), "Failed to stat cache file: %v", err)
|
||||||
} else {
|
} else {
|
||||||
fh.file.setSize(fi.Size())
|
fh.file.setSize(fi.Size())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the underlying file
|
// Close the underlying file
|
||||||
err = fh.File.Close()
|
if fh.opened {
|
||||||
if err != nil {
|
err = fh.File.Close()
|
||||||
return err
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "failed to close cache file")
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME measure whether we actually did any writes or not -
|
if copy {
|
||||||
// no writes means no transfer?
|
// Transfer the temp file to the remote
|
||||||
if rdwrMode == os.O_RDONLY && fh.flags&os.O_TRUNC == 0 {
|
// FIXME retries
|
||||||
fs.Debugf(fh.remote, "read only and not truncating so not transferring")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If write hasn't been called and we aren't creating or
|
|
||||||
// truncating the file then we haven't modified it so don't
|
|
||||||
// need to transfer it
|
|
||||||
if !fh.writeCalled && fh.flags&(os.O_CREATE|os.O_TRUNC) == 0 {
|
|
||||||
fs.Debugf(fh.remote, "not modified so not transferring")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transfer the temp file to the remote
|
|
||||||
// FIXME retries
|
|
||||||
if fh.d.vfs.Opt.CacheMode < CacheModeFull {
|
|
||||||
err = operations.MoveFile(fh.d.vfs.f, fh.d.vfs.cache.f, fh.remote, fh.remote)
|
|
||||||
} else {
|
|
||||||
err = operations.CopyFile(fh.d.vfs.f, fh.d.vfs.cache.f, fh.remote, fh.remote)
|
err = operations.CopyFile(fh.d.vfs.f, fh.d.vfs.cache.f, fh.remote, fh.remote)
|
||||||
}
|
if err != nil {
|
||||||
if err != nil {
|
err = errors.Wrap(err, "failed to transfer file from cache to remote")
|
||||||
err = errors.Wrap(err, "failed to transfer file from cache to remote")
|
fs.Errorf(fh.logPrefix(), "%v", err)
|
||||||
fs.Errorf(fh.remote, "%v", err)
|
return err
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME get MoveFile to return this object
|
o, err := fh.d.vfs.f.NewObject(fh.remote)
|
||||||
o, err := fh.d.vfs.f.NewObject(fh.remote)
|
if err != nil {
|
||||||
if err != nil {
|
err = errors.Wrap(err, "failed to find object after transfer to remote")
|
||||||
err = errors.Wrap(err, "failed to find object after transfer to remote")
|
fs.Errorf(fh.logPrefix(), "%v", err)
|
||||||
fs.Errorf(fh.remote, "%v", err)
|
return err
|
||||||
return err
|
}
|
||||||
|
fh.file.setObject(o)
|
||||||
|
fs.Debugf(o, "transferred to remote")
|
||||||
}
|
}
|
||||||
fh.file.setObject(o)
|
|
||||||
fs.Debugf(o, "transferred to remote")
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -256,6 +269,24 @@ func (fh *RWFileHandle) Close() error {
|
||||||
return fh.close()
|
return fh.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fh *RWFileHandle) modified() bool {
|
||||||
|
rdwrMode := fh.flags & accessModeMask
|
||||||
|
// no writes means no transfer?
|
||||||
|
if rdwrMode == os.O_RDONLY && fh.flags&os.O_TRUNC == 0 {
|
||||||
|
fs.Debugf(fh.logPrefix(), "read only and not truncating so not transferring")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If write hasn't been called and we aren't creating or
|
||||||
|
// truncating the file then we haven't modified it so don't
|
||||||
|
// need to transfer it
|
||||||
|
if !fh.writeCalled && fh.flags&(os.O_CREATE|os.O_TRUNC) == 0 {
|
||||||
|
fs.Debugf(fh.logPrefix(), "not modified so not transferring")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Flush is called each time the file or directory is closed.
|
// Flush is called each time the file or directory is closed.
|
||||||
// Because there can be multiple file descriptors referring to a
|
// Because there can be multiple file descriptors referring to a
|
||||||
// single opened file, Flush can be called multiple times.
|
// single opened file, Flush can be called multiple times.
|
||||||
|
@ -266,26 +297,26 @@ func (fh *RWFileHandle) Flush() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if fh.closed {
|
if fh.closed {
|
||||||
fs.Debugf(fh.remote, "RWFileHandle.Flush nothing to do")
|
fs.Debugf(fh.logPrefix(), "RWFileHandle.Flush nothing to do")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// fs.Debugf(fh.remote, "RWFileHandle.Flush")
|
// fs.Debugf(fh.logPrefix(), "RWFileHandle.Flush")
|
||||||
if !fh.opened {
|
if !fh.opened {
|
||||||
fs.Debugf(fh.remote, "RWFileHandle.Flush ignoring flush on unopened handle")
|
fs.Debugf(fh.logPrefix(), "RWFileHandle.Flush ignoring flush on unopened handle")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If Write hasn't been called then ignore the Flush - Release
|
// If Write hasn't been called then ignore the Flush - Release
|
||||||
// will pick it up
|
// will pick it up
|
||||||
if !fh.writeCalled {
|
if !fh.writeCalled {
|
||||||
fs.Debugf(fh.remote, "RWFileHandle.Flush ignoring flush on unwritten handle")
|
fs.Debugf(fh.logPrefix(), "RWFileHandle.Flush ignoring flush on unwritten handle")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err := fh.close()
|
err := fh.close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.Errorf(fh.remote, "RWFileHandle.Flush error: %v", err)
|
fs.Errorf(fh.logPrefix(), "RWFileHandle.Flush error: %v", err)
|
||||||
} else {
|
} else {
|
||||||
// fs.Debugf(fh.remote, "RWFileHandle.Flush OK")
|
// fs.Debugf(fh.logPrefix(), "RWFileHandle.Flush OK")
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -298,15 +329,15 @@ func (fh *RWFileHandle) Release() error {
|
||||||
fh.mu.Lock()
|
fh.mu.Lock()
|
||||||
defer fh.mu.Unlock()
|
defer fh.mu.Unlock()
|
||||||
if fh.closed {
|
if fh.closed {
|
||||||
fs.Debugf(fh.remote, "RWFileHandle.Release nothing to do")
|
fs.Debugf(fh.logPrefix(), "RWFileHandle.Release nothing to do")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fs.Debugf(fh.remote, "RWFileHandle.Release closing")
|
fs.Debugf(fh.logPrefix(), "RWFileHandle.Release closing")
|
||||||
err := fh.close()
|
err := fh.close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.Errorf(fh.remote, "RWFileHandle.Release error: %v", err)
|
fs.Errorf(fh.logPrefix(), "RWFileHandle.Release error: %v", err)
|
||||||
} else {
|
} else {
|
||||||
// fs.Debugf(fh.remote, "RWFileHandle.Release OK")
|
// fs.Debugf(fh.logPrefix(), "RWFileHandle.Release OK")
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -429,7 +460,6 @@ func (fh *RWFileHandle) WriteString(s string) (n int, err error) {
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
return n, err
|
return n, err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate file to given size
|
// Truncate file to given size
|
||||||
|
@ -464,3 +494,7 @@ func (fh *RWFileHandle) Sync() error {
|
||||||
}
|
}
|
||||||
return fh.File.Sync()
|
return fh.File.Sync()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fh *RWFileHandle) logPrefix() string {
|
||||||
|
return fmt.Sprintf("%s(%p)", fh.remote, fh)
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,12 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func cleanup(t *testing.T, r *fstest.Run, vfs *VFS) {
|
||||||
|
assert.NoError(t, vfs.CleanUp())
|
||||||
|
vfs.Shutdown()
|
||||||
|
r.Finalise()
|
||||||
|
}
|
||||||
|
|
||||||
// Open a file for write
|
// Open a file for write
|
||||||
func rwHandleCreateReadOnly(t *testing.T, r *fstest.Run) (*VFS, *RWFileHandle) {
|
func rwHandleCreateReadOnly(t *testing.T, r *fstest.Run) (*VFS, *RWFileHandle) {
|
||||||
vfs := New(r.Fremote, nil)
|
vfs := New(r.Fremote, nil)
|
||||||
|
@ -53,8 +59,8 @@ func rwReadString(t *testing.T, fh *RWFileHandle, n int) string {
|
||||||
|
|
||||||
func TestRWFileHandleMethodsRead(t *testing.T) {
|
func TestRWFileHandleMethodsRead(t *testing.T) {
|
||||||
r := fstest.NewRun(t)
|
r := fstest.NewRun(t)
|
||||||
defer r.Finalise()
|
vfs, fh := rwHandleCreateReadOnly(t, r)
|
||||||
_, fh := rwHandleCreateReadOnly(t, r)
|
defer cleanup(t, r, vfs)
|
||||||
|
|
||||||
// String
|
// String
|
||||||
assert.Equal(t, "dir/file1 (rw)", fh.String())
|
assert.Equal(t, "dir/file1 (rw)", fh.String())
|
||||||
|
@ -101,8 +107,8 @@ func TestRWFileHandleMethodsRead(t *testing.T) {
|
||||||
|
|
||||||
func TestRWFileHandleSeek(t *testing.T) {
|
func TestRWFileHandleSeek(t *testing.T) {
|
||||||
r := fstest.NewRun(t)
|
r := fstest.NewRun(t)
|
||||||
defer r.Finalise()
|
vfs, fh := rwHandleCreateReadOnly(t, r)
|
||||||
_, fh := rwHandleCreateReadOnly(t, r)
|
defer cleanup(t, r, vfs)
|
||||||
|
|
||||||
assert.Equal(t, "0", rwReadString(t, fh, 1))
|
assert.Equal(t, "0", rwReadString(t, fh, 1))
|
||||||
|
|
||||||
|
@ -140,8 +146,8 @@ func TestRWFileHandleSeek(t *testing.T) {
|
||||||
|
|
||||||
func TestRWFileHandleReadAt(t *testing.T) {
|
func TestRWFileHandleReadAt(t *testing.T) {
|
||||||
r := fstest.NewRun(t)
|
r := fstest.NewRun(t)
|
||||||
defer r.Finalise()
|
vfs, fh := rwHandleCreateReadOnly(t, r)
|
||||||
_, fh := rwHandleCreateReadOnly(t, r)
|
defer cleanup(t, r, vfs)
|
||||||
|
|
||||||
// read from start
|
// read from start
|
||||||
buf := make([]byte, 1)
|
buf := make([]byte, 1)
|
||||||
|
@ -191,8 +197,8 @@ func TestRWFileHandleReadAt(t *testing.T) {
|
||||||
|
|
||||||
func TestRWFileHandleFlushRead(t *testing.T) {
|
func TestRWFileHandleFlushRead(t *testing.T) {
|
||||||
r := fstest.NewRun(t)
|
r := fstest.NewRun(t)
|
||||||
defer r.Finalise()
|
vfs, fh := rwHandleCreateReadOnly(t, r)
|
||||||
_, fh := rwHandleCreateReadOnly(t, r)
|
defer cleanup(t, r, vfs)
|
||||||
|
|
||||||
// Check Flush does nothing if read not called
|
// Check Flush does nothing if read not called
|
||||||
err := fh.Flush()
|
err := fh.Flush()
|
||||||
|
@ -221,8 +227,8 @@ func TestRWFileHandleFlushRead(t *testing.T) {
|
||||||
|
|
||||||
func TestRWFileHandleReleaseRead(t *testing.T) {
|
func TestRWFileHandleReleaseRead(t *testing.T) {
|
||||||
r := fstest.NewRun(t)
|
r := fstest.NewRun(t)
|
||||||
defer r.Finalise()
|
vfs, fh := rwHandleCreateReadOnly(t, r)
|
||||||
_, fh := rwHandleCreateReadOnly(t, r)
|
defer cleanup(t, r, vfs)
|
||||||
|
|
||||||
// Read data
|
// Read data
|
||||||
buf := make([]byte, 256)
|
buf := make([]byte, 256)
|
||||||
|
@ -245,8 +251,8 @@ func TestRWFileHandleReleaseRead(t *testing.T) {
|
||||||
|
|
||||||
func TestRWFileHandleMethodsWrite(t *testing.T) {
|
func TestRWFileHandleMethodsWrite(t *testing.T) {
|
||||||
r := fstest.NewRun(t)
|
r := fstest.NewRun(t)
|
||||||
defer r.Finalise()
|
|
||||||
vfs, fh := rwHandleCreateWriteOnly(t, r)
|
vfs, fh := rwHandleCreateWriteOnly(t, r)
|
||||||
|
defer cleanup(t, r, vfs)
|
||||||
|
|
||||||
// String
|
// String
|
||||||
assert.Equal(t, "file1 (rw)", fh.String())
|
assert.Equal(t, "file1 (rw)", fh.String())
|
||||||
|
@ -320,8 +326,8 @@ func TestRWFileHandleMethodsWrite(t *testing.T) {
|
||||||
|
|
||||||
func TestRWFileHandleWriteAt(t *testing.T) {
|
func TestRWFileHandleWriteAt(t *testing.T) {
|
||||||
r := fstest.NewRun(t)
|
r := fstest.NewRun(t)
|
||||||
defer r.Finalise()
|
|
||||||
vfs, fh := rwHandleCreateWriteOnly(t, r)
|
vfs, fh := rwHandleCreateWriteOnly(t, r)
|
||||||
|
defer cleanup(t, r, vfs)
|
||||||
|
|
||||||
offset := func() int64 {
|
offset := func() int64 {
|
||||||
n, err := fh.Seek(0, 1)
|
n, err := fh.Seek(0, 1)
|
||||||
|
@ -331,7 +337,8 @@ func TestRWFileHandleWriteAt(t *testing.T) {
|
||||||
|
|
||||||
// Preconditions
|
// Preconditions
|
||||||
assert.Equal(t, int64(0), offset())
|
assert.Equal(t, int64(0), offset())
|
||||||
assert.False(t, fh.writeCalled)
|
assert.True(t, fh.opened)
|
||||||
|
assert.True(t, fh.writeCalled)
|
||||||
|
|
||||||
// Write the data
|
// Write the data
|
||||||
n, err := fh.WriteAt([]byte("hello**"), 0)
|
n, err := fh.WriteAt([]byte("hello**"), 0)
|
||||||
|
@ -366,8 +373,8 @@ func TestRWFileHandleWriteAt(t *testing.T) {
|
||||||
|
|
||||||
func TestRWFileHandleWriteNoWrite(t *testing.T) {
|
func TestRWFileHandleWriteNoWrite(t *testing.T) {
|
||||||
r := fstest.NewRun(t)
|
r := fstest.NewRun(t)
|
||||||
defer r.Finalise()
|
|
||||||
vfs, fh := rwHandleCreateWriteOnly(t, r)
|
vfs, fh := rwHandleCreateWriteOnly(t, r)
|
||||||
|
defer cleanup(t, r, vfs)
|
||||||
|
|
||||||
// Close the file without writing to it
|
// Close the file without writing to it
|
||||||
err := fh.Close()
|
err := fh.Close()
|
||||||
|
@ -395,13 +402,11 @@ func TestRWFileHandleWriteNoWrite(t *testing.T) {
|
||||||
|
|
||||||
func TestRWFileHandleFlushWrite(t *testing.T) {
|
func TestRWFileHandleFlushWrite(t *testing.T) {
|
||||||
r := fstest.NewRun(t)
|
r := fstest.NewRun(t)
|
||||||
defer r.Finalise()
|
vfs, fh := rwHandleCreateWriteOnly(t, r)
|
||||||
_, fh := rwHandleCreateWriteOnly(t, r)
|
defer cleanup(t, r, vfs)
|
||||||
|
|
||||||
// Check Flush does nothing if write not called
|
// Check that the file has been create and is open
|
||||||
err := fh.Flush()
|
assert.True(t, fh.opened)
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.False(t, fh.closed)
|
|
||||||
|
|
||||||
// Write some data
|
// Write some data
|
||||||
n, err := fh.Write([]byte("hello"))
|
n, err := fh.Write([]byte("hello"))
|
||||||
|
@ -421,8 +426,8 @@ func TestRWFileHandleFlushWrite(t *testing.T) {
|
||||||
|
|
||||||
func TestRWFileHandleReleaseWrite(t *testing.T) {
|
func TestRWFileHandleReleaseWrite(t *testing.T) {
|
||||||
r := fstest.NewRun(t)
|
r := fstest.NewRun(t)
|
||||||
defer r.Finalise()
|
vfs, fh := rwHandleCreateWriteOnly(t, r)
|
||||||
_, fh := rwHandleCreateWriteOnly(t, r)
|
defer cleanup(t, r, vfs)
|
||||||
|
|
||||||
// Write some data
|
// Write some data
|
||||||
n, err := fh.Write([]byte("hello"))
|
n, err := fh.Write([]byte("hello"))
|
||||||
|
@ -515,9 +520,9 @@ func testRWFileHandleOpenTest(t *testing.T, vfs *VFS, test *openTest) {
|
||||||
|
|
||||||
func TestRWFileHandleOpenTests(t *testing.T) {
|
func TestRWFileHandleOpenTests(t *testing.T) {
|
||||||
r := fstest.NewRun(t)
|
r := fstest.NewRun(t)
|
||||||
defer r.Finalise()
|
|
||||||
|
|
||||||
vfs := New(r.Fremote, nil)
|
vfs := New(r.Fremote, nil)
|
||||||
|
defer cleanup(t, r, vfs)
|
||||||
|
|
||||||
vfs.Opt.CacheMode = CacheModeFull
|
vfs.Opt.CacheMode = CacheModeFull
|
||||||
for _, test := range openTests {
|
for _, test := range openTests {
|
||||||
testRWFileHandleOpenTest(t, vfs, &test)
|
testRWFileHandleOpenTest(t, vfs, &test)
|
||||||
|
|
|
@ -181,7 +181,7 @@ func (fh *WriteFileHandle) close() (err error) {
|
||||||
}
|
}
|
||||||
fh.closed = true
|
fh.closed = true
|
||||||
// leave writer open until file is transferred
|
// leave writer open until file is transferred
|
||||||
defer fh.file.delWriter(fh)
|
defer fh.file.delWriter(fh, false)
|
||||||
// If file not opened and not safe to truncate then then leave file intact
|
// If file not opened and not safe to truncate then then leave file intact
|
||||||
if !fh.opened && !fh.safeToTruncate() {
|
if !fh.opened && !fh.safeToTruncate() {
|
||||||
return nil
|
return nil
|
||||||
|
|
Loading…
Reference in a new issue