123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470 |
- package vfs
- import (
- "fmt"
- "io"
- "os"
- "sync"
- "github.com/rclone/rclone/fs"
- "github.com/rclone/rclone/fs/log"
- "github.com/rclone/rclone/vfs/vfscache"
- )
- // RWFileHandle is a handle that can be open for read and write.
- //
- // It will be open to a temporary file which, when closed, will be
- // transferred to the remote.
- type RWFileHandle struct {
- // read only variables
- file *File
- d *Dir
- flags int // open flags
- item *vfscache.Item // cached file item
- // read write variables protected by mutex
- mu sync.Mutex
- offset int64 // file pointer offset
- closed bool // set if handle has been closed
- opened bool
- writeCalled bool // if any Write() methods have been called
- }
- // Lock performs Unix locking, not supported
- func (fh *RWFileHandle) Lock() error {
- return os.ErrInvalid
- }
- // Unlock performs Unix unlocking, not supported
- func (fh *RWFileHandle) Unlock() error {
- return os.ErrInvalid
- }
- func newRWFileHandle(d *Dir, f *File, flags int) (fh *RWFileHandle, err error) {
- defer log.Trace(f.Path(), "")("err=%v", &err)
- // get an item to represent this from the cache
- item := d.vfs.cache.Item(f.Path())
- exists := f.exists() || (item.Exists() && !item.WrittenBack())
- // if O_CREATE and O_EXCL are set and if path already exists, then return EEXIST
- if flags&(os.O_CREATE|os.O_EXCL) == os.O_CREATE|os.O_EXCL && exists {
- return nil, EEXIST
- }
- fh = &RWFileHandle{
- file: f,
- d: d,
- flags: flags,
- item: item,
- }
- // truncate immediately if O_TRUNC is set or O_CREATE is set and file doesn't exist
- if !fh.readOnly() && (fh.flags&os.O_TRUNC != 0 || (fh.flags&os.O_CREATE != 0 && !exists)) {
- err = fh.Truncate(0)
- if err != nil {
- return nil, fmt.Errorf("cache open with O_TRUNC: failed to truncate: %w", err)
- }
- // we definitely need to write back the item even if we don't write to it
- item.Dirty()
- }
- if !fh.readOnly() {
- fh.file.addWriter(fh)
- }
- return fh, nil
- }
- // readOnly returns whether flags say fh is read only
- func (fh *RWFileHandle) readOnly() bool {
- return (fh.flags & accessModeMask) == os.O_RDONLY
- }
- // writeOnly returns whether flags say fh is write only
- func (fh *RWFileHandle) writeOnly() bool {
- return (fh.flags & accessModeMask) == os.O_WRONLY
- }
- // openPending opens the file if there is a pending open
- //
- // call with the lock held
- func (fh *RWFileHandle) openPending() (err error) {
- if fh.opened {
- return nil
- }
- defer log.Trace(fh.logPrefix(), "")("err=%v", &err)
- fh.file.muRW.Lock()
- defer fh.file.muRW.Unlock()
- o := fh.file.getObject()
- err = fh.item.Open(o)
- if err != nil {
- return fmt.Errorf("open RW handle failed to open cache file: %w", err)
- }
- size := fh._size() // update size in file and read size
- if fh.flags&os.O_APPEND != 0 {
- fh.offset = size
- fs.Debugf(fh.logPrefix(), "open at offset %d", fh.offset)
- } else {
- fh.offset = 0
- }
- fh.opened = true
- fh.d.addObject(fh.file) // make sure the directory has this object in it now
- return nil
- }
- // String converts it to printable
- func (fh *RWFileHandle) String() string {
- if fh == nil {
- return "<nil *RWFileHandle>"
- }
- if fh.file == nil {
- return "<nil *RWFileHandle.file>"
- }
- return fh.file.String() + " (rw)"
- }
- // Node returns the Node associated with this - satisfies Noder interface
- func (fh *RWFileHandle) Node() Node {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- return fh.file
- }
- // updateSize updates the size of the file if necessary
- //
- // Must be called with fh.mu held
- func (fh *RWFileHandle) updateSize() {
- // If read only or not opened then ignore
- if fh.readOnly() || !fh.opened {
- return
- }
- size := fh._size()
- fh.file.setSize(size)
- }
- // close the file handle returning EBADF if it has been
- // closed already.
- //
- // Must be called with fh.mu held.
- //
- // Note that we leave the file around in the cache on error conditions
- // to give the user a chance to recover it.
- func (fh *RWFileHandle) close() (err error) {
- defer log.Trace(fh.logPrefix(), "")("err=%v", &err)
- fh.file.muRW.Lock()
- defer fh.file.muRW.Unlock()
- if fh.closed {
- return ECLOSED
- }
- fh.closed = true
- fh.updateSize()
- if fh.opened {
- err = fh.item.Close(fh.file.setObject)
- fh.opened = false
- } else {
- // apply any pending mod times if any
- _ = fh.file.applyPendingModTime()
- }
- if !fh.readOnly() {
- fh.file.delWriter(fh)
- }
- return err
- }
- // Close closes the file
- func (fh *RWFileHandle) Close() error {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- return fh.close()
- }
- // Flush is called each time the file or directory is closed.
- // Because there can be multiple file descriptors referring to a
- // single opened file, Flush can be called multiple times.
- func (fh *RWFileHandle) Flush() error {
- fh.mu.Lock()
- fs.Debugf(fh.logPrefix(), "RWFileHandle.Flush")
- fh.updateSize()
- fh.mu.Unlock()
- return nil
- }
- // Release is called when we are finished with the file handle
- //
- // It isn't called directly from userspace so the error is ignored by
- // the kernel
- func (fh *RWFileHandle) Release() error {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- fs.Debugf(fh.logPrefix(), "RWFileHandle.Release")
- if fh.closed {
- // Don't return an error if called twice
- return nil
- }
- err := fh.close()
- if err != nil {
- fs.Errorf(fh.logPrefix(), "RWFileHandle.Release error: %v", err)
- }
- return err
- }
- // _size returns the size of the underlying file and also sets it in
- // the owning file
- //
- // call with the lock held
- func (fh *RWFileHandle) _size() int64 {
- size, err := fh.item.GetSize()
- if err != nil {
- o := fh.file.getObject()
- if o != nil {
- size = o.Size()
- } else {
- fs.Errorf(fh.logPrefix(), "Couldn't read size of file")
- size = 0
- }
- }
- fh.file.setSize(size)
- return size
- }
- // Size returns the size of the underlying file
- func (fh *RWFileHandle) Size() int64 {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- return fh._size()
- }
- // Stat returns info about the file
- func (fh *RWFileHandle) Stat() (os.FileInfo, error) {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- return fh.file, nil
- }
- // _readAt bytes from the file at off
- //
- // if release is set then it releases the mutex just before doing the IO
- //
- // call with lock held
- func (fh *RWFileHandle) _readAt(b []byte, off int64, release bool) (n int, err error) {
- defer log.Trace(fh.logPrefix(), "size=%d, off=%d", len(b), off)("n=%d, err=%v", &n, &err)
- if fh.closed {
- return n, ECLOSED
- }
- if fh.writeOnly() {
- return n, EBADF
- }
- if off >= fh._size() {
- return n, io.EOF
- }
- if err = fh.openPending(); err != nil {
- return n, err
- }
- if release {
- // Do the writing with fh.mu unlocked
- fh.mu.Unlock()
- }
- n, err = fh.item.ReadAt(b, off)
- if release {
- fh.mu.Lock()
- }
- return n, err
- }
- // ReadAt bytes from the file at off
- func (fh *RWFileHandle) ReadAt(b []byte, off int64) (n int, err error) {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- return fh._readAt(b, off, true)
- }
- // Read bytes from the file
- func (fh *RWFileHandle) Read(b []byte) (n int, err error) {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- n, err = fh._readAt(b, fh.offset, false)
- fh.offset += int64(n)
- return n, err
- }
- // Seek to new file position
- func (fh *RWFileHandle) Seek(offset int64, whence int) (ret int64, err error) {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- if fh.closed {
- return 0, ECLOSED
- }
- if !fh.opened && offset == 0 && whence != 2 {
- return 0, nil
- }
- if err = fh.openPending(); err != nil {
- return ret, err
- }
- switch whence {
- case io.SeekStart:
- fh.offset = 0
- case io.SeekEnd:
- fh.offset = fh._size()
- }
- fh.offset += offset
- // we don't check the offset - the next Read will
- return fh.offset, nil
- }
- // _writeAt bytes to the file at off
- //
- // if release is set then it releases the mutex just before doing the IO
- //
- // call with lock held
- func (fh *RWFileHandle) _writeAt(b []byte, off int64, release bool) (n int, err error) {
- defer log.Trace(fh.logPrefix(), "size=%d, off=%d", len(b), off)("n=%d, err=%v", &n, &err)
- if fh.closed {
- return n, ECLOSED
- }
- if fh.readOnly() {
- return n, EBADF
- }
- if err = fh.openPending(); err != nil {
- return n, err
- }
- if fh.flags&os.O_APPEND != 0 {
- // From open(2): Before each write(2), the file offset is
- // positioned at the end of the file, as if with lseek(2).
- size := fh._size()
- fh.offset = size
- off = fh.offset
- }
- fh.writeCalled = true
- if release {
- // Do the writing with fh.mu unlocked
- fh.mu.Unlock()
- }
- n, err = fh.item.WriteAt(b, off)
- if release {
- fh.mu.Lock()
- }
- if err != nil {
- return n, err
- }
- _ = fh._size()
- return n, err
- }
- // WriteAt bytes to the file at off
- func (fh *RWFileHandle) WriteAt(b []byte, off int64) (n int, err error) {
- fh.mu.Lock()
- n, err = fh._writeAt(b, off, true)
- if fh.flags&os.O_APPEND != 0 {
- fh.offset += int64(n)
- }
- fh.mu.Unlock()
- return n, err
- }
- // Write bytes to the file
- func (fh *RWFileHandle) Write(b []byte) (n int, err error) {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- n, err = fh._writeAt(b, fh.offset, false)
- fh.offset += int64(n)
- return n, err
- }
- // WriteString a string to the file
- func (fh *RWFileHandle) WriteString(s string) (n int, err error) {
- return fh.Write([]byte(s))
- }
- // Truncate file to given size
- //
- // Call with mutex held
- func (fh *RWFileHandle) _truncate(size int64) (err error) {
- if size == fh._size() {
- return nil
- }
- fh.file.setSize(size)
- return fh.item.Truncate(size)
- }
- // Truncate file to given size
- func (fh *RWFileHandle) Truncate(size int64) (err error) {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- if fh.closed {
- return ECLOSED
- }
- if err = fh.openPending(); err != nil {
- return err
- }
- return fh._truncate(size)
- }
- // Sync commits the current contents of the file to stable storage. Typically,
- // this means flushing the file system's in-memory copy of recently written
- // data to disk.
- func (fh *RWFileHandle) Sync() error {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- if fh.closed {
- return ECLOSED
- }
- if !fh.opened {
- return nil
- }
- if fh.readOnly() {
- return nil
- }
- return fh.item.Sync()
- }
- func (fh *RWFileHandle) logPrefix() string {
- return fmt.Sprintf("%s(%p)", fh.file.Path(), fh)
- }
- // Chdir changes the current working directory to the file, which must
- // be a directory.
- func (fh *RWFileHandle) Chdir() error {
- return ENOSYS
- }
- // Chmod changes the mode of the file to mode.
- func (fh *RWFileHandle) Chmod(mode os.FileMode) error {
- return ENOSYS
- }
- // Chown changes the numeric uid and gid of the named file.
- func (fh *RWFileHandle) Chown(uid, gid int) error {
- return ENOSYS
- }
- // Fd returns the integer Unix file descriptor referencing the open file.
- func (fh *RWFileHandle) Fd() uintptr {
- return 0xdeadbeef // FIXME
- }
- // Name returns the name of the file from the underlying Object.
- func (fh *RWFileHandle) Name() string {
- return fh.file.String()
- }
- // Readdir reads the contents of the directory associated with file.
- func (fh *RWFileHandle) Readdir(n int) ([]os.FileInfo, error) {
- return nil, ENOSYS
- }
- // Readdirnames reads the contents of the directory associated with file.
- func (fh *RWFileHandle) Readdirnames(n int) (names []string, err error) {
- return nil, ENOSYS
- }
|