2017-10-28 21:01:34 +02:00
|
|
|
package vfs
|
2017-05-02 23:35:07 +02:00
|
|
|
|
|
|
|
import (
|
2019-06-17 10:34:30 +02:00
|
|
|
"context"
|
2017-10-25 11:00:26 +02:00
|
|
|
"os"
|
2017-05-09 12:29:02 +02:00
|
|
|
"path"
|
2017-05-02 23:35:07 +02:00
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2019-07-28 19:47:38 +02:00
|
|
|
"github.com/rclone/rclone/fs"
|
|
|
|
"github.com/rclone/rclone/fs/log"
|
|
|
|
"github.com/rclone/rclone/fs/operations"
|
2020-02-28 15:44:15 +01:00
|
|
|
"github.com/rclone/rclone/vfs/vfscommon"
|
2017-05-02 23:35:07 +02:00
|
|
|
)
|
|
|
|
|
2020-04-14 18:55:18 +02:00
|
|
|
// The File object is tightly coupled to the Dir object. Since they
|
|
|
|
// both have locks there is plenty of potential for deadlocks. In
|
|
|
|
// order to mitigate this, we use the following conventions
|
|
|
|
//
|
2020-04-14 19:14:24 +02:00
|
|
|
// File may **only** call these methods from Dir with the File lock
|
|
|
|
// held.
|
2020-04-14 18:55:18 +02:00
|
|
|
//
|
2020-04-14 19:14:24 +02:00
|
|
|
// Dir.Fs
|
|
|
|
// Dir.VFS
|
|
|
|
//
|
|
|
|
// (As these are read only and do not need to take the Dir mutex.)
|
|
|
|
//
|
|
|
|
// File may **not** call any other Dir methods with the File lock
|
|
|
|
// held. This preserves total lock ordering and makes File subordinate
|
|
|
|
// to Dir as far as locking is concerned, preventing deadlocks.
|
|
|
|
//
|
|
|
|
// File may **not** read any members of Dir directly.
|
2020-04-14 18:55:18 +02:00
|
|
|
|
2017-05-02 23:35:07 +02:00
|
|
|
// File represents a file
|
|
|
|
type File struct {
|
2019-11-06 22:48:43 +01:00
|
|
|
inode uint64 // inode number - read only
|
2018-02-18 14:12:26 +01:00
|
|
|
size int64 // size of file - read and written with atomic int64 - must be 64 bit aligned
|
|
|
|
|
2019-11-06 22:48:43 +01:00
|
|
|
mu sync.RWMutex // protects the following
|
|
|
|
d *Dir // parent directory
|
2020-04-14 18:55:18 +02:00
|
|
|
dPath string // path of parent directory. NB dir rename means all Files are flushed
|
2019-06-17 10:34:30 +02:00
|
|
|
o fs.Object // NB o may be nil if file is being written
|
|
|
|
leaf string // leaf name of the object
|
|
|
|
rwOpenCount int // number of open files on this handle
|
|
|
|
writers []Handle // writers for this file
|
|
|
|
nwriters int32 // len(writers) which is read/updated with atomic
|
|
|
|
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
|
|
|
|
pendingRenameFun func(ctx context.Context) error // will be run/renamed after all writers close
|
2019-10-15 02:57:15 +02:00
|
|
|
appendMode bool // file was opened with O_APPEND
|
2018-02-18 14:12:26 +01:00
|
|
|
|
2018-03-15 21:36:48 +01:00
|
|
|
muRW sync.Mutex // synchonize RWFileHandle.openPending(), RWFileHandle.close() and File.Remove
|
2017-05-02 23:35:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// newFile creates a new File
|
2019-11-25 12:31:44 +01:00
|
|
|
//
|
|
|
|
// o may be nil
|
2020-04-14 18:55:18 +02:00
|
|
|
func newFile(d *Dir, dPath string, o fs.Object, leaf string) *File {
|
2019-11-25 12:31:44 +01:00
|
|
|
f := &File{
|
2017-05-02 23:35:07 +02:00
|
|
|
d: d,
|
2020-04-14 18:55:18 +02:00
|
|
|
dPath: dPath,
|
2017-05-02 23:35:07 +02:00
|
|
|
o: o,
|
2017-05-09 12:29:02 +02:00
|
|
|
leaf: leaf,
|
2017-10-29 18:37:54 +01:00
|
|
|
inode: newInode(),
|
2017-05-02 23:35:07 +02:00
|
|
|
}
|
2019-11-25 12:31:44 +01:00
|
|
|
if o != nil {
|
|
|
|
f.size = o.Size()
|
|
|
|
}
|
|
|
|
return f
|
2017-05-02 23:35:07 +02:00
|
|
|
}
|
|
|
|
|
2017-05-09 12:29:02 +02:00
|
|
|
// String converts it to printable
|
|
|
|
func (f *File) String() string {
|
|
|
|
if f == nil {
|
|
|
|
return "<nil *File>"
|
|
|
|
}
|
2017-11-18 12:47:21 +01:00
|
|
|
return f.Path()
|
2017-05-09 12:29:02 +02:00
|
|
|
}
|
|
|
|
|
2017-05-02 23:35:07 +02:00
|
|
|
// IsFile returns true for File - satisfies Node interface
|
|
|
|
func (f *File) IsFile() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2017-10-25 11:00:26 +02:00
|
|
|
// IsDir returns false for File - satisfies Node interface
|
|
|
|
func (f *File) IsDir() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mode bits of the file or directory - satisfies Node interface
|
|
|
|
func (f *File) Mode() (mode os.FileMode) {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
2019-10-15 02:57:15 +02:00
|
|
|
mode = f.d.vfs.Opt.FilePerms
|
|
|
|
if f.appendMode {
|
|
|
|
mode |= os.ModeAppend
|
|
|
|
}
|
|
|
|
return mode
|
2017-10-25 11:00:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Name (base) of the directory - satisfies Node interface
|
|
|
|
func (f *File) Name() (name string) {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
2017-10-29 22:14:05 +01:00
|
|
|
return f.leaf
|
2017-10-25 11:00:26 +02:00
|
|
|
}
|
|
|
|
|
2020-01-19 13:52:48 +01:00
|
|
|
// _path returns the full path of the file
|
|
|
|
// use when lock is held
|
|
|
|
func (f *File) _path() string {
|
2020-04-14 18:55:18 +02:00
|
|
|
return path.Join(f.dPath, f.leaf)
|
2020-01-19 13:52:48 +01:00
|
|
|
}
|
|
|
|
|
2017-11-18 12:47:21 +01:00
|
|
|
// Path returns the full path of the file
|
|
|
|
func (f *File) Path() string {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
2020-04-14 18:55:18 +02:00
|
|
|
dPath, leaf := f.dPath, f.leaf
|
2020-04-02 22:14:45 +02:00
|
|
|
f.mu.RUnlock()
|
2020-04-14 18:55:18 +02:00
|
|
|
return path.Join(dPath, leaf)
|
2017-11-18 12:47:21 +01:00
|
|
|
}
|
|
|
|
|
2019-12-09 15:25:54 +01:00
|
|
|
// osPath returns the full path of the file in the cache in OS format
|
|
|
|
func (f *File) osPath() string {
|
2020-02-28 15:44:15 +01:00
|
|
|
return f.d.vfs.cache.ToOSPath(f.Path())
|
2019-12-09 15:25:54 +01:00
|
|
|
}
|
|
|
|
|
2017-10-25 11:00:26 +02:00
|
|
|
// Sys returns underlying data source (can be nil) - satisfies Node interface
|
|
|
|
func (f *File) Sys() interface{} {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-05-02 23:35:07 +02:00
|
|
|
// Inode returns the inode number - satisfies Node interface
|
|
|
|
func (f *File) Inode() uint64 {
|
|
|
|
return f.inode
|
|
|
|
}
|
|
|
|
|
|
|
|
// Node returns the Node assocuated with this - satisfies Noder interface
|
|
|
|
func (f *File) Node() Node {
|
|
|
|
return f
|
|
|
|
}
|
|
|
|
|
2018-05-24 20:45:11 +02:00
|
|
|
// applyPendingRename runs a previously set rename operation if there are no
|
|
|
|
// more remaining writers. Call without lock held.
|
|
|
|
func (f *File) applyPendingRename() {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
2018-05-24 20:45:11 +02:00
|
|
|
fun := f.pendingRenameFun
|
2019-11-06 22:48:43 +01:00
|
|
|
writing := f._writingInProgress()
|
|
|
|
f.mu.RUnlock()
|
|
|
|
if fun == nil || writing {
|
2018-05-24 20:45:11 +02:00
|
|
|
return
|
|
|
|
}
|
2020-04-15 13:17:28 +02:00
|
|
|
fs.Debugf(f.Path(), "Running delayed rename now")
|
2019-06-17 10:34:30 +02:00
|
|
|
if err := fun(context.TODO()); err != nil {
|
2018-05-29 19:19:17 +02:00
|
|
|
fs.Errorf(f.Path(), "delayed File.Rename error: %v", err)
|
|
|
|
}
|
2018-05-24 20:45:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// rename attempts to immediately rename a file if there are no open writers.
|
|
|
|
// Otherwise it will queue the rename operation on the remote until no writers
|
|
|
|
// remain.
|
2019-06-17 10:34:30 +02:00
|
|
|
func (f *File) rename(ctx context.Context, destDir *Dir, newName string) error {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
d := f.d
|
2019-12-05 13:58:24 +01:00
|
|
|
oldPendingRenameFun := f.pendingRenameFun
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RUnlock()
|
2019-12-05 13:58:24 +01:00
|
|
|
|
2020-04-14 19:14:24 +02:00
|
|
|
if features := d.Fs().Features(); features.Move == nil && features.Copy == nil {
|
|
|
|
err := errors.Errorf("Fs %q can't rename files (no server side Move or Copy)", d.Fs())
|
2018-05-24 20:45:11 +02:00
|
|
|
fs.Errorf(f.Path(), "Dir.Rename error: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-04-14 18:55:18 +02:00
|
|
|
// File.mu is unlocked here to call Dir.Path()
|
|
|
|
newPath := path.Join(destDir.Path(), newName)
|
2019-12-05 13:58:24 +01:00
|
|
|
|
2019-06-17 10:34:30 +02:00
|
|
|
renameCall := func(ctx context.Context) error {
|
2019-12-05 13:58:24 +01:00
|
|
|
// chain rename calls if any
|
|
|
|
if oldPendingRenameFun != nil {
|
|
|
|
err := oldPendingRenameFun(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-13 18:37:54 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
o := f.o
|
|
|
|
f.mu.RUnlock()
|
|
|
|
if o == nil {
|
|
|
|
return errors.New("Cannot rename: file object is not available")
|
|
|
|
}
|
|
|
|
if o.Remote() == newPath {
|
|
|
|
return nil // no need to rename
|
|
|
|
}
|
|
|
|
|
2019-12-05 13:58:24 +01:00
|
|
|
// do the move of the remote object
|
2020-04-14 19:14:24 +02:00
|
|
|
dstOverwritten, _ := d.Fs().NewObject(ctx, newPath)
|
|
|
|
newObject, err := operations.Move(ctx, d.Fs(), dstOverwritten, newPath, o)
|
2018-05-24 20:45:11 +02:00
|
|
|
if err != nil {
|
|
|
|
fs.Errorf(f.Path(), "File.Rename error: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
2019-08-26 14:43:11 +02:00
|
|
|
|
2019-01-14 13:07:25 +01:00
|
|
|
// newObject can be nil here for example if --dry-run
|
|
|
|
if newObject == nil {
|
|
|
|
err = errors.New("rename failed: nil object returned")
|
|
|
|
fs.Errorf(f.Path(), "File.Rename %v", err)
|
|
|
|
return err
|
|
|
|
}
|
2018-05-24 20:45:11 +02:00
|
|
|
// Update the node with the new details
|
2020-04-15 13:17:28 +02:00
|
|
|
fs.Debugf(f.Path(), "Updating file with %v %p", newObject, f)
|
2018-05-24 20:45:11 +02:00
|
|
|
// f.rename(destDir, newObject)
|
|
|
|
f.mu.Lock()
|
|
|
|
f.o = newObject
|
|
|
|
f.pendingRenameFun = nil
|
|
|
|
f.mu.Unlock()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-12-05 13:58:24 +01:00
|
|
|
// Rename in the cache if it exists
|
2020-02-28 15:44:15 +01:00
|
|
|
if f.d.vfs.cache != nil && f.d.vfs.cache.Exists(f.Path()) {
|
|
|
|
if err := f.d.vfs.cache.Rename(f.Path(), newPath); err != nil {
|
2019-12-05 13:58:24 +01:00
|
|
|
fs.Infof(f.Path(), "File.Rename failed in Cache: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// rename the file object
|
2020-04-14 18:55:18 +02:00
|
|
|
dPath := destDir.Path()
|
2019-12-05 13:58:24 +01:00
|
|
|
f.mu.Lock()
|
|
|
|
f.d = destDir
|
2020-04-14 18:55:18 +02:00
|
|
|
f.dPath = dPath
|
2019-12-05 13:58:24 +01:00
|
|
|
f.leaf = newName
|
2019-11-06 22:48:43 +01:00
|
|
|
writing := f._writingInProgress()
|
2019-12-05 13:58:24 +01:00
|
|
|
f.mu.Unlock()
|
|
|
|
|
2019-11-06 22:48:43 +01:00
|
|
|
if writing {
|
2020-04-15 13:17:28 +02:00
|
|
|
fs.Debugf(f.Path(), "File is currently open, delaying rename %p", f)
|
2018-05-24 20:45:11 +02:00
|
|
|
f.mu.Lock()
|
|
|
|
f.pendingRenameFun = renameCall
|
|
|
|
f.mu.Unlock()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-06-17 10:34:30 +02:00
|
|
|
return renameCall(ctx)
|
2017-05-02 23:35:07 +02:00
|
|
|
}
|
|
|
|
|
2017-11-16 10:31:33 +01:00
|
|
|
// addWriter adds a write handle to the file
|
|
|
|
func (f *File) addWriter(h Handle) {
|
2017-05-02 23:35:07 +02:00
|
|
|
f.mu.Lock()
|
2017-11-16 10:31:33 +01:00
|
|
|
f.writers = append(f.writers, h)
|
2018-03-09 10:49:41 +01:00
|
|
|
atomic.AddInt32(&f.nwriters, 1)
|
2018-02-18 14:12:26 +01:00
|
|
|
if _, ok := h.(*RWFileHandle); ok {
|
|
|
|
f.readWriters++
|
|
|
|
}
|
2017-11-16 10:31:33 +01:00
|
|
|
f.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// delWriter removes a write handle from the file
|
2018-02-18 14:12:26 +01:00
|
|
|
func (f *File) delWriter(h Handle, modifiedCacheFile bool) (lastWriterAndModified bool) {
|
2017-11-16 10:31:33 +01:00
|
|
|
f.mu.Lock()
|
2019-11-06 22:48:43 +01:00
|
|
|
defer f.applyPendingRename()
|
2018-02-18 14:12:26 +01:00
|
|
|
defer f.mu.Unlock()
|
2017-11-16 10:31:33 +01:00
|
|
|
var found = -1
|
|
|
|
for i := range f.writers {
|
|
|
|
if f.writers[i] == h {
|
|
|
|
found = i
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if found >= 0 {
|
|
|
|
f.writers = append(f.writers[:found], f.writers[found+1:]...)
|
2018-03-09 10:49:41 +01:00
|
|
|
atomic.AddInt32(&f.nwriters, -1)
|
2017-11-16 10:31:33 +01:00
|
|
|
} else {
|
2020-04-15 13:17:28 +02:00
|
|
|
fs.Debugf(f._path(), "File.delWriter couldn't find handle")
|
2017-11-16 10:31:33 +01:00
|
|
|
}
|
2018-02-18 14:12:26 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-03-02 16:58:41 +01:00
|
|
|
// addRWOpen should be called by ReadWriteHandle when they have
|
|
|
|
// actually opened the file for read or write.
|
|
|
|
func (f *File) addRWOpen() {
|
|
|
|
f.mu.Lock()
|
|
|
|
f.rwOpenCount++
|
|
|
|
f.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// delRWOpen should be called by ReadWriteHandle when they have closed
|
|
|
|
// an actually opene file for read or write.
|
|
|
|
func (f *File) delRWOpen() {
|
|
|
|
f.mu.Lock()
|
|
|
|
f.rwOpenCount--
|
|
|
|
f.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// rwOpens returns how many active open ReadWriteHandles there are.
|
|
|
|
// Note that file handles which are in pending open state aren't
|
|
|
|
// counted.
|
|
|
|
func (f *File) rwOpens() int {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
2018-03-02 16:58:41 +01:00
|
|
|
return f.rwOpenCount
|
|
|
|
}
|
|
|
|
|
2018-02-18 14:12:26 +01:00
|
|
|
// finishWriterClose resets the readWriterClosing flag
|
|
|
|
func (f *File) finishWriterClose() {
|
|
|
|
f.mu.Lock()
|
|
|
|
f.readWriterClosing = false
|
2017-05-02 23:35:07 +02:00
|
|
|
f.mu.Unlock()
|
2018-05-24 20:45:11 +02:00
|
|
|
f.applyPendingRename()
|
2017-05-02 23:35:07 +02:00
|
|
|
}
|
|
|
|
|
2017-11-18 12:57:40 +01:00
|
|
|
// activeWriters returns the number of writers on the file
|
2018-03-09 10:49:41 +01:00
|
|
|
//
|
|
|
|
// Note that we don't take the mutex here. If we do then we can get a
|
|
|
|
// deadlock.
|
2017-11-18 12:57:40 +01:00
|
|
|
func (f *File) activeWriters() int {
|
2018-03-09 10:49:41 +01:00
|
|
|
return int(atomic.LoadInt32(&f.nwriters))
|
2017-11-18 12:57:40 +01:00
|
|
|
}
|
|
|
|
|
2017-10-25 11:00:26 +02:00
|
|
|
// ModTime returns the modified time of the file
|
|
|
|
//
|
|
|
|
// if NoModTime is set then it returns the mod time of the directory
|
|
|
|
func (f *File) ModTime() (modTime time.Time) {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
2020-04-14 18:55:18 +02:00
|
|
|
d, o, pendingModTime := f.d, f.o, f.pendingModTime
|
|
|
|
f.mu.RUnlock()
|
2017-10-25 11:00:26 +02:00
|
|
|
|
2020-04-14 18:55:18 +02:00
|
|
|
if d.vfs.Opt.NoModTime {
|
|
|
|
return d.ModTime()
|
2017-10-25 11:00:26 +02:00
|
|
|
}
|
2020-04-14 18:55:18 +02:00
|
|
|
if !pendingModTime.IsZero() {
|
|
|
|
return pendingModTime
|
2019-11-11 16:56:59 +01:00
|
|
|
}
|
2020-04-14 18:55:18 +02:00
|
|
|
if o == nil {
|
|
|
|
return d.ModTime()
|
2019-11-11 16:56:59 +01:00
|
|
|
}
|
2020-04-14 18:55:18 +02:00
|
|
|
return o.ModTime(context.TODO())
|
2017-10-25 11:00:26 +02:00
|
|
|
}
|
|
|
|
|
2018-01-31 16:23:01 +01:00
|
|
|
// nonNegative returns 0 if i is -ve, i otherwise
|
|
|
|
func nonNegative(i int64) int64 {
|
|
|
|
if i >= 0 {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2017-10-25 11:00:26 +02:00
|
|
|
// Size of the file
|
|
|
|
func (f *File) Size() int64 {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
2017-10-25 11:00:26 +02:00
|
|
|
|
2017-05-02 23:35:07 +02:00
|
|
|
// if o is nil it isn't valid yet or there are writers, so return the size so far
|
2019-11-06 22:48:43 +01:00
|
|
|
if f._writingInProgress() {
|
2017-10-25 11:00:26 +02:00
|
|
|
return atomic.LoadInt64(&f.size)
|
2017-05-02 23:35:07 +02:00
|
|
|
}
|
2018-01-31 16:23:01 +01:00
|
|
|
return nonNegative(f.o.Size())
|
2017-05-02 23:35:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetModTime sets the modtime for the file
|
|
|
|
func (f *File) SetModTime(modTime time.Time) error {
|
2017-10-29 12:00:56 +01:00
|
|
|
if f.d.vfs.Opt.ReadOnly {
|
2017-05-11 14:15:51 +02:00
|
|
|
return EROFS
|
|
|
|
}
|
2017-05-02 23:35:07 +02:00
|
|
|
f.mu.Lock()
|
|
|
|
defer f.mu.Unlock()
|
|
|
|
|
|
|
|
f.pendingModTime = modTime
|
|
|
|
|
2018-02-18 14:12:26 +01:00
|
|
|
// Only update the ModTime when there are no writers, setObject will do it
|
2019-11-06 22:48:43 +01:00
|
|
|
if !f._writingInProgress() {
|
|
|
|
return f._applyPendingModTime()
|
2017-05-02 23:35:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// queue up for later, hoping f.o becomes available
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-06 22:48:43 +01:00
|
|
|
// Apply a pending mod time
|
2020-04-15 13:17:28 +02:00
|
|
|
// Call with the mutex held
|
2019-11-06 22:48:43 +01:00
|
|
|
func (f *File) _applyPendingModTime() error {
|
2017-05-02 23:35:07 +02:00
|
|
|
if f.pendingModTime.IsZero() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-06 22:48:43 +01:00
|
|
|
defer func() { f.pendingModTime = time.Time{} }()
|
|
|
|
|
2017-05-02 23:35:07 +02:00
|
|
|
if f.o == nil {
|
|
|
|
return errors.New("Cannot apply ModTime, file object is not available")
|
|
|
|
}
|
|
|
|
|
2020-01-19 13:52:48 +01:00
|
|
|
// set the time of the file in the cache
|
2020-02-28 15:44:15 +01:00
|
|
|
if f.d.vfs.cache != nil {
|
|
|
|
f.d.vfs.cache.SetModTime(f._path(), f.pendingModTime)
|
2020-01-19 13:52:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// set the time of the object
|
2019-06-17 10:34:30 +02:00
|
|
|
err := f.o.SetModTime(context.TODO(), f.pendingModTime)
|
2017-05-02 23:35:07 +02:00
|
|
|
switch err {
|
|
|
|
case nil:
|
2020-04-15 13:17:28 +02:00
|
|
|
fs.Debugf(f._path(), "File._applyPendingModTime OK")
|
2017-06-13 14:58:39 +02:00
|
|
|
case fs.ErrorCantSetModTime, fs.ErrorCantSetModTimeWithoutDelete:
|
2017-05-02 23:35:07 +02:00
|
|
|
// do nothing, in order to not break "touch somefile" if it exists already
|
|
|
|
default:
|
2020-04-15 13:17:28 +02:00
|
|
|
fs.Debugf(f._path(), "File._applyPendingModTime error: %v", err)
|
2017-05-02 23:35:07 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-06 22:48:43 +01:00
|
|
|
// _writingInProgress returns true of there are any open writers
|
|
|
|
// Call with read lock held
|
|
|
|
func (f *File) _writingInProgress() bool {
|
2018-05-24 20:45:11 +02:00
|
|
|
return f.o == nil || len(f.writers) != 0 || f.readWriterClosing
|
|
|
|
}
|
|
|
|
|
2017-05-02 23:35:07 +02:00
|
|
|
// Update the size while writing
|
|
|
|
func (f *File) setSize(n int64) {
|
|
|
|
atomic.StoreInt64(&f.size, n)
|
|
|
|
}
|
|
|
|
|
2017-11-18 12:47:21 +01:00
|
|
|
// Update the object when written and add it to the directory
|
2017-05-02 23:35:07 +02:00
|
|
|
func (f *File) setObject(o fs.Object) {
|
|
|
|
f.mu.Lock()
|
|
|
|
f.o = o
|
2019-11-06 22:48:43 +01:00
|
|
|
_ = f._applyPendingModTime()
|
2018-12-03 22:51:39 +01:00
|
|
|
f.mu.Unlock()
|
|
|
|
|
2020-04-14 18:55:18 +02:00
|
|
|
// Release File.mu before calling Dir method
|
2017-10-26 18:21:03 +02:00
|
|
|
f.d.addObject(f)
|
2017-05-02 23:35:07 +02:00
|
|
|
}
|
|
|
|
|
2018-03-01 16:51:05 +01:00
|
|
|
// Update the object but don't update the directory cache - for use by
|
|
|
|
// the directory cache
|
|
|
|
func (f *File) setObjectNoUpdate(o fs.Object) {
|
|
|
|
f.mu.Lock()
|
|
|
|
f.o = o
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.Unlock()
|
2018-03-01 16:51:05 +01:00
|
|
|
}
|
|
|
|
|
2018-03-01 16:50:23 +01:00
|
|
|
// Get the current fs.Object - may be nil
|
|
|
|
func (f *File) getObject() fs.Object {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
2018-03-01 16:50:23 +01:00
|
|
|
return f.o
|
|
|
|
}
|
|
|
|
|
2017-11-16 10:31:33 +01:00
|
|
|
// exists returns whether the file exists already
|
|
|
|
func (f *File) exists() bool {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
2017-11-16 10:31:33 +01:00
|
|
|
return f.o != nil
|
|
|
|
}
|
|
|
|
|
2017-05-02 23:35:07 +02:00
|
|
|
// Wait for f.o to become non nil for a short time returning it or an
|
2017-11-06 22:38:52 +01:00
|
|
|
// error. Use when opening a read handle.
|
2017-05-02 23:35:07 +02:00
|
|
|
//
|
|
|
|
// Call without the mutex held
|
|
|
|
func (f *File) waitForValidObject() (o fs.Object, err error) {
|
|
|
|
for i := 0; i < 50; i++ {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
2017-05-02 23:35:07 +02:00
|
|
|
o = f.o
|
2017-11-16 10:31:33 +01:00
|
|
|
nwriters := len(f.writers)
|
2018-02-18 14:12:26 +01:00
|
|
|
wclosing := f.readWriterClosing
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RUnlock()
|
2017-05-02 23:35:07 +02:00
|
|
|
if o != nil {
|
|
|
|
return o, nil
|
|
|
|
}
|
2018-02-18 14:12:26 +01:00
|
|
|
if nwriters == 0 && !wclosing {
|
2017-05-02 23:35:07 +02:00
|
|
|
return nil, errors.New("can't open file - writer failed")
|
|
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
}
|
|
|
|
return nil, ENOENT
|
|
|
|
}
|
|
|
|
|
2017-12-06 17:38:19 +01:00
|
|
|
// openRead open the file for read
|
|
|
|
func (f *File) openRead() (fh *ReadFileHandle, err error) {
|
2017-05-02 23:35:07 +02:00
|
|
|
// if o is nil it isn't valid yet
|
2018-03-01 16:50:23 +01:00
|
|
|
_, err = f.waitForValidObject()
|
2017-05-02 23:35:07 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-04-15 13:17:28 +02:00
|
|
|
// fs.Debugf(f.Path(), "File.openRead")
|
2017-05-02 23:35:07 +02:00
|
|
|
|
2018-03-01 16:50:23 +01:00
|
|
|
fh, err = newReadFileHandle(f)
|
2017-05-02 23:35:07 +02:00
|
|
|
if err != nil {
|
2020-04-15 13:17:28 +02:00
|
|
|
fs.Debugf(f.Path(), "File.openRead failed: %v", err)
|
2017-05-02 23:35:07 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return fh, nil
|
|
|
|
}
|
|
|
|
|
2017-12-06 17:38:19 +01:00
|
|
|
// openWrite open the file for write
|
|
|
|
func (f *File) openWrite(flags int) (fh *WriteFileHandle, err error) {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
d := f.d
|
|
|
|
f.mu.RUnlock()
|
|
|
|
|
|
|
|
if d.vfs.Opt.ReadOnly {
|
2017-05-11 14:15:51 +02:00
|
|
|
return nil, EROFS
|
|
|
|
}
|
2020-04-15 13:17:28 +02:00
|
|
|
// fs.Debugf(f.Path(), "File.openWrite")
|
2017-05-02 23:35:07 +02:00
|
|
|
|
2019-11-06 22:48:43 +01:00
|
|
|
fh, err = newWriteFileHandle(d, f, f.Path(), flags)
|
2017-05-02 23:35:07 +02:00
|
|
|
if err != nil {
|
2020-04-15 13:17:28 +02:00
|
|
|
fs.Debugf(f.Path(), "File.openWrite failed: %v", err)
|
2017-05-02 23:35:07 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return fh, nil
|
|
|
|
}
|
|
|
|
|
2017-12-06 17:38:19 +01:00
|
|
|
// openRW open the file for read and write using a temporay file
|
2017-11-06 22:38:52 +01:00
|
|
|
//
|
|
|
|
// It uses the open flags passed in.
|
2017-12-06 17:38:19 +01:00
|
|
|
func (f *File) openRW(flags int) (fh *RWFileHandle, err error) {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
d := f.d
|
|
|
|
f.mu.RUnlock()
|
|
|
|
|
2017-12-06 17:38:19 +01:00
|
|
|
// FIXME chunked
|
2019-11-06 22:48:43 +01:00
|
|
|
if flags&accessModeMask != os.O_RDONLY && d.vfs.Opt.ReadOnly {
|
2017-11-06 22:38:52 +01:00
|
|
|
return nil, EROFS
|
|
|
|
}
|
2020-04-15 13:17:28 +02:00
|
|
|
// fs.Debugf(f.Path(), "File.openRW")
|
2017-11-06 22:38:52 +01:00
|
|
|
|
2019-12-09 15:25:54 +01:00
|
|
|
fh, err = newRWFileHandle(d, f, flags)
|
2017-11-06 22:38:52 +01:00
|
|
|
if err != nil {
|
2020-04-15 13:17:28 +02:00
|
|
|
fs.Debugf(f.Path(), "File.openRW failed: %v", err)
|
2017-11-06 22:38:52 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return fh, nil
|
|
|
|
}
|
|
|
|
|
2017-11-18 16:48:49 +01:00
|
|
|
// Sync the file
|
2017-05-02 23:35:07 +02:00
|
|
|
//
|
|
|
|
// Note that we don't do anything except return OK
|
2017-11-18 16:48:49 +01:00
|
|
|
func (f *File) Sync() error {
|
2017-05-02 23:35:07 +02:00
|
|
|
return nil
|
|
|
|
}
|
2017-10-26 17:55:40 +02:00
|
|
|
|
|
|
|
// Remove the file
|
|
|
|
func (f *File) Remove() error {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
d := f.d
|
|
|
|
f.mu.RUnlock()
|
|
|
|
|
|
|
|
if d.vfs.Opt.ReadOnly {
|
2017-10-26 17:55:40 +02:00
|
|
|
return EROFS
|
|
|
|
}
|
2018-12-21 14:55:06 +01:00
|
|
|
f.muRW.Lock() // muRW must be locked before mu to avoid
|
|
|
|
f.mu.Lock() // deadlock in RWFileHandle.openPending and .close
|
2017-10-29 22:14:05 +01:00
|
|
|
if f.o != nil {
|
2019-06-17 10:34:30 +02:00
|
|
|
err := f.o.Remove(context.TODO())
|
2017-10-29 22:14:05 +01:00
|
|
|
if err != nil {
|
2020-04-15 13:17:28 +02:00
|
|
|
fs.Debugf(f._path(), "File.Remove file error: %v", err)
|
2018-12-03 22:51:39 +01:00
|
|
|
f.mu.Unlock()
|
2018-12-21 14:55:06 +01:00
|
|
|
f.muRW.Unlock()
|
2017-10-29 22:14:05 +01:00
|
|
|
return err
|
|
|
|
}
|
2017-10-26 17:55:40 +02:00
|
|
|
}
|
2018-12-03 22:51:39 +01:00
|
|
|
f.mu.Unlock()
|
2018-12-21 14:55:06 +01:00
|
|
|
f.muRW.Unlock()
|
2018-12-03 22:51:39 +01:00
|
|
|
|
2017-10-26 17:55:40 +02:00
|
|
|
// Remove the item from the directory listing
|
2020-04-14 18:55:18 +02:00
|
|
|
// called with File.mu released
|
2019-11-06 22:48:43 +01:00
|
|
|
d.delObject(f.Name())
|
2017-11-27 20:48:25 +01:00
|
|
|
// Remove the object from the cache
|
2020-02-28 15:44:15 +01:00
|
|
|
if d.vfs.cache != nil {
|
|
|
|
d.vfs.cache.Remove(f.Path())
|
2017-11-27 20:48:25 +01:00
|
|
|
}
|
2017-10-26 17:55:40 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveAll the file - same as remove for files
|
|
|
|
func (f *File) RemoveAll() error {
|
|
|
|
return f.Remove()
|
|
|
|
}
|
2017-10-26 18:02:48 +02:00
|
|
|
|
2017-10-29 22:14:05 +01:00
|
|
|
// DirEntry returns the underlying fs.DirEntry - may be nil
|
2017-10-26 18:02:48 +02:00
|
|
|
func (f *File) DirEntry() (entry fs.DirEntry) {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
2017-10-26 18:02:48 +02:00
|
|
|
return f.o
|
|
|
|
}
|
2017-10-29 12:00:56 +01:00
|
|
|
|
|
|
|
// Dir returns the directory this file is in
|
|
|
|
func (f *File) Dir() *Dir {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
2017-10-29 12:00:56 +01:00
|
|
|
return f.d
|
|
|
|
}
|
|
|
|
|
|
|
|
// VFS returns the instance of the VFS
|
|
|
|
func (f *File) VFS() *VFS {
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
2017-10-29 12:00:56 +01:00
|
|
|
return f.d.vfs
|
|
|
|
}
|
2017-10-30 11:14:39 +01:00
|
|
|
|
2020-04-14 19:03:45 +02:00
|
|
|
// Fs returns the underlying Fs for the file
|
|
|
|
func (f *File) Fs() fs.Fs {
|
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
2020-04-14 19:14:24 +02:00
|
|
|
return f.d.Fs()
|
2020-04-14 19:03:45 +02:00
|
|
|
}
|
|
|
|
|
2017-10-30 11:14:39 +01:00
|
|
|
// Open a file according to the flags provided
|
2017-11-06 22:38:52 +01:00
|
|
|
//
|
|
|
|
// O_RDONLY open the file read-only.
|
|
|
|
// O_WRONLY open the file write-only.
|
|
|
|
// O_RDWR open the file read-write.
|
|
|
|
//
|
|
|
|
// O_APPEND append data to the file when writing.
|
|
|
|
// O_CREATE create a new file if none exists.
|
|
|
|
// O_EXCL used with O_CREATE, file must not exist
|
|
|
|
// O_SYNC open for synchronous I/O.
|
|
|
|
// O_TRUNC if possible, truncate file when opene
|
|
|
|
//
|
|
|
|
// We ignore O_SYNC and O_EXCL
|
2017-10-30 11:14:39 +01:00
|
|
|
func (f *File) Open(flags int) (fd Handle, err error) {
|
2020-04-15 13:17:28 +02:00
|
|
|
defer log.Trace(f.Path(), "flags=%s", decodeOpenFlags(flags))("fd=%v, err=%v", &fd, &err)
|
2017-11-06 22:38:52 +01:00
|
|
|
var (
|
|
|
|
write bool // if set need write support
|
|
|
|
read bool // if set need read support
|
2018-02-23 23:39:28 +01:00
|
|
|
rdwrMode = flags & accessModeMask
|
2017-11-06 22:38:52 +01:00
|
|
|
)
|
|
|
|
|
2018-02-23 23:39:28 +01:00
|
|
|
// http://pubs.opengroup.org/onlinepubs/7908799/xsh/open.html
|
|
|
|
// The result of using O_TRUNC with O_RDONLY is undefined.
|
|
|
|
// Linux seems to truncate the file, but we prefer to return EINVAL
|
|
|
|
if rdwrMode == os.O_RDONLY && flags&os.O_TRUNC != 0 {
|
|
|
|
return nil, EINVAL
|
|
|
|
}
|
|
|
|
|
2017-11-06 22:38:52 +01:00
|
|
|
// Figure out the read/write intents
|
2017-10-30 11:14:39 +01:00
|
|
|
switch {
|
|
|
|
case rdwrMode == os.O_RDONLY:
|
|
|
|
read = true
|
2017-11-06 22:38:52 +01:00
|
|
|
case rdwrMode == os.O_WRONLY:
|
|
|
|
write = true
|
2017-10-30 11:14:39 +01:00
|
|
|
case rdwrMode == os.O_RDWR:
|
2017-11-06 22:38:52 +01:00
|
|
|
read = true
|
|
|
|
write = true
|
2017-10-30 11:14:39 +01:00
|
|
|
default:
|
2020-04-15 13:17:28 +02:00
|
|
|
fs.Debugf(f.Path(), "Can't figure out how to open with flags: 0x%X", flags)
|
2017-10-31 16:33:08 +01:00
|
|
|
return nil, EPERM
|
2017-10-30 11:14:39 +01:00
|
|
|
}
|
2017-11-06 22:38:52 +01:00
|
|
|
|
2017-12-06 17:38:19 +01:00
|
|
|
// If append is set then set read to force openRW
|
2017-11-06 22:38:52 +01:00
|
|
|
if flags&os.O_APPEND != 0 {
|
|
|
|
read = true
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.Lock()
|
2019-10-15 02:57:15 +02:00
|
|
|
f.appendMode = true
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.Unlock()
|
2017-11-06 22:38:52 +01:00
|
|
|
}
|
|
|
|
|
2017-12-06 17:38:19 +01:00
|
|
|
// If truncate is set then set write to force openRW
|
2017-11-06 22:38:52 +01:00
|
|
|
if flags&os.O_TRUNC != 0 {
|
|
|
|
write = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open the correct sort of handle
|
2019-11-06 22:48:43 +01:00
|
|
|
f.mu.RLock()
|
|
|
|
d := f.d
|
|
|
|
f.mu.RUnlock()
|
|
|
|
CacheMode := d.vfs.Opt.CacheMode
|
2020-02-28 15:44:15 +01:00
|
|
|
if CacheMode >= vfscommon.CacheModeMinimal && (d.vfs.cache.Opens(f.Path()) > 0 || d.vfs.cache.Exists(f.Path())) {
|
2018-02-18 14:12:26 +01:00
|
|
|
fd, err = f.openRW(flags)
|
|
|
|
} else if read && write {
|
2020-02-28 15:44:15 +01:00
|
|
|
if CacheMode >= vfscommon.CacheModeMinimal {
|
2017-12-06 17:38:19 +01:00
|
|
|
fd, err = f.openRW(flags)
|
2017-11-06 22:38:52 +01:00
|
|
|
} else {
|
2017-11-16 11:55:24 +01:00
|
|
|
// Open write only and hope the user doesn't
|
|
|
|
// want to read. If they do they will get an
|
|
|
|
// EPERM plus an Error log.
|
2017-12-06 17:38:19 +01:00
|
|
|
fd, err = f.openWrite(flags)
|
2017-11-06 22:38:52 +01:00
|
|
|
}
|
|
|
|
} else if write {
|
2020-02-28 15:44:15 +01:00
|
|
|
if CacheMode >= vfscommon.CacheModeWrites {
|
2017-12-06 17:38:19 +01:00
|
|
|
fd, err = f.openRW(flags)
|
2017-11-06 22:38:52 +01:00
|
|
|
} else {
|
2017-12-06 17:38:19 +01:00
|
|
|
fd, err = f.openWrite(flags)
|
2017-11-06 22:38:52 +01:00
|
|
|
}
|
|
|
|
} else if read {
|
2020-02-28 15:44:15 +01:00
|
|
|
if CacheMode >= vfscommon.CacheModeFull {
|
2017-12-06 17:38:19 +01:00
|
|
|
fd, err = f.openRW(flags)
|
2017-11-06 22:38:52 +01:00
|
|
|
} else {
|
2017-12-06 17:38:19 +01:00
|
|
|
fd, err = f.openRead()
|
2017-11-06 22:38:52 +01:00
|
|
|
}
|
2017-10-30 11:14:39 +01:00
|
|
|
} else {
|
2020-04-15 13:17:28 +02:00
|
|
|
fs.Debugf(f.Path(), "Can't figure out how to open with flags: 0x%X", flags)
|
2017-11-06 22:38:52 +01:00
|
|
|
return nil, EPERM
|
2017-10-30 11:14:39 +01:00
|
|
|
}
|
2019-11-03 13:23:58 +01:00
|
|
|
// if creating a file, add the file to the directory
|
|
|
|
if err == nil && flags&os.O_CREATE != 0 {
|
2020-04-14 18:55:18 +02:00
|
|
|
// called without File.mu held
|
2019-11-06 22:48:43 +01:00
|
|
|
d.addObject(f)
|
2019-11-03 13:23:58 +01:00
|
|
|
}
|
2017-10-30 11:14:39 +01:00
|
|
|
return fd, err
|
|
|
|
}
|
2017-11-06 22:38:52 +01:00
|
|
|
|
|
|
|
// Truncate changes the size of the named file.
|
2017-11-16 10:31:33 +01:00
|
|
|
func (f *File) Truncate(size int64) (err error) {
|
|
|
|
// make a copy of fh.writers with the lock held then unlock so
|
|
|
|
// we can call other file methods.
|
|
|
|
f.mu.Lock()
|
|
|
|
writers := make([]Handle, len(f.writers))
|
|
|
|
copy(writers, f.writers)
|
2019-11-06 22:48:43 +01:00
|
|
|
o := f.o
|
2017-11-16 10:31:33 +01:00
|
|
|
f.mu.Unlock()
|
|
|
|
|
2018-02-18 14:12:26 +01:00
|
|
|
// FIXME: handle closing writer
|
|
|
|
|
2017-11-16 10:31:33 +01:00
|
|
|
// If have writers then call truncate for each writer
|
|
|
|
if len(writers) != 0 {
|
2020-04-15 13:17:28 +02:00
|
|
|
fs.Debugf(f.Path(), "Truncating %d file handles", len(writers))
|
2017-11-16 10:31:33 +01:00
|
|
|
for _, h := range writers {
|
|
|
|
truncateErr := h.Truncate(size)
|
|
|
|
if truncateErr != nil {
|
|
|
|
err = truncateErr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
2019-02-12 18:27:04 +01:00
|
|
|
|
|
|
|
// If no writers, and size is already correct then all done
|
2019-11-06 22:48:43 +01:00
|
|
|
if o.Size() == size {
|
2019-02-12 18:27:04 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-15 13:17:28 +02:00
|
|
|
fs.Debugf(f.Path(), "Truncating file")
|
2017-11-16 10:31:33 +01:00
|
|
|
|
|
|
|
// Otherwise if no writers then truncate the file by opening
|
|
|
|
// the file and truncating it.
|
|
|
|
flags := os.O_WRONLY
|
|
|
|
if size == 0 {
|
|
|
|
flags |= os.O_TRUNC
|
|
|
|
}
|
|
|
|
fh, err := f.Open(flags)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer fs.CheckClose(fh, &err)
|
|
|
|
if size != 0 {
|
|
|
|
return fh.Truncate(size)
|
2017-11-06 22:38:52 +01:00
|
|
|
}
|
2017-11-16 10:31:33 +01:00
|
|
|
return nil
|
2017-11-06 22:38:52 +01:00
|
|
|
}
|