From dec21ccf63777801ec52af018c0cd8e38d9c079a Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Thu, 16 Nov 2017 09:31:33 +0000 Subject: [PATCH] vfs, cmount: make truncate work properly in the presence or otherwise of open files --- cmd/cmount/fs.go | 24 +++----------- vfs/file.go | 80 ++++++++++++++++++++++++++++++++++++++++------- vfs/read_write.go | 15 +++++---- vfs/write.go | 24 ++++++++++++-- 4 files changed, 104 insertions(+), 39 deletions(-) diff --git a/cmd/cmount/fs.go b/cmd/cmount/fs.go index 0c827935e..01bfc0fcd 100644 --- a/cmd/cmount/fs.go +++ b/cmd/cmount/fs.go @@ -319,28 +319,14 @@ func (fsys *FS) Truncate(path string, size int64, fh uint64) (errc int) { if errc != 0 { return errc } - // Read the size so far - var currentSize int64 + var err error if handle != nil { - fi, err := handle.Stat() - if err != nil { - return translateError(err) - } - currentSize = fi.Size() + err = handle.Truncate(size) } else { - currentSize = node.Size() + err = node.Truncate(size) } - fs.Debugf(path, "truncate to %d, currentSize %d", size, currentSize) - if currentSize != size { - var err error - if handle != nil { - err = handle.Truncate(size) - } else { - err = node.Truncate(size) - } - if err != nil { - return translateError(err) - } + if err != nil { + return translateError(err) } return 0 } diff --git a/vfs/file.go b/vfs/file.go index c15992445..827614656 100644 --- a/vfs/file.go +++ b/vfs/file.go @@ -19,7 +19,7 @@ type File struct { mu sync.RWMutex // protects the following o fs.Object // NB o may be nil if file is being written leaf string // leaf name of the object - writers int // number of writers for this file + writers []Handle // writers for this file pendingModTime time.Time // will be applied once o becomes available, i.e. after file was written } @@ -90,10 +90,28 @@ func (f *File) rename(d *Dir, o fs.Object) { f.mu.Unlock() } -// addWriters increments or decrements the writers -func (f *File) addWriters(n int) { +// addWriter adds a write handle to the file +func (f *File) addWriter(h Handle) { f.mu.Lock() - f.writers += n + f.writers = append(f.writers, h) + f.mu.Unlock() +} + +// delWriter removes a write handle from the file +func (f *File) delWriter(h Handle) { + f.mu.Lock() + 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:]...) + } else { + fs.Debugf(f.o, "File.delWriter couldn't find handle") + } f.mu.Unlock() } @@ -106,7 +124,7 @@ func (f *File) ModTime() (modTime time.Time) { 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 f.o == nil || f.writers != 0 { + if f.o == nil || len(f.writers) != 0 { if !f.pendingModTime.IsZero() { return f.pendingModTime } @@ -124,7 +142,7 @@ func (f *File) Size() int64 { defer f.mu.Unlock() // if o is nil it isn't valid yet or there are writers, so return the size so far - if f.o == nil || f.writers != 0 { + if f.o == nil || len(f.writers) != 0 { return atomic.LoadInt64(&f.size) } return f.o.Size() @@ -188,6 +206,13 @@ func (f *File) setObject(o fs.Object) { f.d.addObject(f) } +// exists returns whether the file exists already +func (f *File) exists() bool { + f.mu.Lock() + defer f.mu.Unlock() + return f.o != nil +} + // Wait for f.o to become non nil for a short time returning it or an // error. Use when opening a read handle. // @@ -196,12 +221,12 @@ func (f *File) waitForValidObject() (o fs.Object, err error) { for i := 0; i < 50; i++ { f.mu.Lock() o = f.o - writers := f.writers + nwriters := len(f.writers) f.mu.Unlock() if o != nil { return o, nil } - if writers == 0 { + if nwriters == 0 { return nil, errors.New("can't open file - writer failed") } time.Sleep(100 * time.Millisecond) @@ -383,9 +408,40 @@ func (f *File) Open(flags int) (fd Handle, err error) { } // Truncate changes the size of the named file. -func (f *File) Truncate(size int64) error { - if f.d.vfs.Opt.CacheMode >= CacheModeWrites { +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) + f.mu.Unlock() + + // If have writers then call truncate for each writer + if len(writers) != 0 { + fs.Debugf(f.o, "Truncating %d file handles", len(writers)) + for _, h := range writers { + truncateErr := h.Truncate(size) + if truncateErr != nil { + err = truncateErr + } + } + return err } - // FIXME - return ENOSYS + fs.Debugf(f.o, "Truncating file") + + // 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) + } + return nil } diff --git a/vfs/read_write.go b/vfs/read_write.go index ed7cfea7b..b8a0f2bab 100644 --- a/vfs/read_write.go +++ b/vfs/read_write.go @@ -52,6 +52,12 @@ func newRWFileHandle(d *Dir, f *File, remote string, flags int) (fh *RWFileHandl flags: flags, osPath: osPath, } + + rdwrMode := fh.flags & accessModeMask + if rdwrMode != os.O_RDONLY { + fh.file.addWriter(fh) + } + return fh, nil } @@ -63,8 +69,6 @@ func (fh *RWFileHandle) openPending(truncate bool) (err error) { return nil } - rdwrMode := fh.flags & accessModeMask - // if not truncating the file, need to read it first if fh.flags&os.O_TRUNC == 0 && !truncate { // Fetch the file if it hasn't changed @@ -92,9 +96,6 @@ func (fh *RWFileHandle) openPending(truncate bool) (err error) { fh.file.setSize(0) } - if rdwrMode != os.O_RDONLY { - fh.file.addWriters(1) - } fs.Debugf(fh.remote, "Opening cached copy with flags=%s", decodeOpenFlags(fh.flags)) fd, err := os.OpenFile(fh.osPath, fh.flags|os.O_CREATE, 0600) if err != nil { @@ -140,6 +141,9 @@ func (fh *RWFileHandle) close() (err error) { } fh.closed = true rdwrMode := fh.flags & accessModeMask + if rdwrMode != os.O_RDONLY { + fh.file.delWriter(fh) + } if !fh.opened { // If read only then return if rdwrMode == os.O_RDONLY { @@ -157,7 +161,6 @@ func (fh *RWFileHandle) close() (err error) { } } if rdwrMode != os.O_RDONLY { - fh.file.addWriters(-1) fi, err := fh.File.Stat() if err != nil { fs.Errorf(fh.remote, "Failed to stat cache file: %v", err) diff --git a/vfs/write.go b/vfs/write.go index fd61fc856..84f61fb07 100644 --- a/vfs/write.go +++ b/vfs/write.go @@ -49,7 +49,7 @@ func newWriteFileHandle(d *Dir, f *File, remote string) (*WriteFileHandle, error fh.o = o fh.result <- err }() - fh.file.addWriters(1) + fh.file.addWriter(fh) fh.file.setSize(0) return fh, nil } @@ -129,6 +129,11 @@ func (fh *WriteFileHandle) Write(p []byte) (n int, err error) { return fh.writeAt(p, fh.offset) } +// WriteString a string to the file +func (fh *WriteFileHandle) WriteString(s string) (n int, err error) { + return fh.Write([]byte(s)) +} + // Offset returns the offset of the file pointer func (fh *WriteFileHandle) Offset() (offset int64) { fh.mu.Lock() @@ -145,7 +150,7 @@ func (fh *WriteFileHandle) close() error { return ECLOSED } fh.closed = true - fh.file.addWriters(-1) + fh.file.delWriter(fh) writeCloseErr := fh.pipeWriter.Close() err := <-fh.result if err == nil { @@ -228,3 +233,18 @@ func (fh *WriteFileHandle) Stat() (os.FileInfo, error) { defer fh.mu.Unlock() return fh.file, nil } + +// Truncate file to given size +func (fh *WriteFileHandle) Truncate(size int64) (err error) { + fh.mu.Lock() + defer fh.mu.Unlock() + if fh.closed { + return ECLOSED + } + if size != fh.offset { + fs.Errorf(fh.remote, "Truncate: Can't change size without cache") + return EPERM + } + // File is correct size + return nil +}