github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libfs/file.go (about)

     1  // Copyright 2017 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libfs
     6  
     7  import (
     8  	"bytes"
     9  	"io"
    10  	"sync"
    11  	"sync/atomic"
    12  
    13  	"github.com/keybase/client/go/kbfs/kbfsmd"
    14  	"github.com/keybase/client/go/kbfs/libkbfs"
    15  	"github.com/keybase/client/go/protocol/keybase1"
    16  	"github.com/pkg/errors"
    17  	billy "gopkg.in/src-d/go-billy.v4"
    18  )
    19  
    20  // File is a wrapper around a libkbfs.Node that implements the
    21  // billy.File interface.
    22  type File struct {
    23  	fs *FS
    24  	// NOTE: If filename ever becomes mutable, we should have a way to keep
    25  	// lockID constant.
    26  	filename string
    27  	node     libkbfs.Node
    28  	readOnly bool
    29  	offset   int64
    30  
    31  	lockedLock sync.Mutex
    32  	locked     bool
    33  }
    34  
    35  var _ billy.File = (*File)(nil)
    36  
    37  // Name implements the billy.File interface for File.
    38  func (f *File) Name() string {
    39  	return f.filename
    40  }
    41  
    42  func (f *File) updateOffset(origOffset, advanceBytes int64) {
    43  	// If there are two concurrent Write calls at the same time, it's
    44  	// not well-defined what the offset should be after.  Just set it
    45  	// to what this call thinks it should be and let the application
    46  	// sort things out.
    47  	_ = atomic.SwapInt64(&f.offset, origOffset+advanceBytes)
    48  }
    49  
    50  // Write implements the billy.File interface for File.
    51  func (f *File) Write(p []byte) (n int, err error) {
    52  	if f.readOnly {
    53  		return 0, errors.New("Trying to write a read-only file")
    54  	}
    55  
    56  	origOffset := atomic.LoadInt64(&f.offset)
    57  	err = f.fs.config.KBFSOps().Write(f.fs.ctx, f.node, p, origOffset)
    58  	if err != nil {
    59  		return 0, err
    60  	}
    61  
    62  	f.updateOffset(origOffset, int64(len(p)))
    63  	return len(p), nil
    64  }
    65  
    66  // Read implements the billy.File interface for File.
    67  func (f *File) Read(p []byte) (n int, err error) {
    68  	origOffset := atomic.LoadInt64(&f.offset)
    69  	readBytes, err := f.fs.config.KBFSOps().Read(
    70  		f.fs.ctx, f.node, p, origOffset)
    71  	if err != nil {
    72  		return 0, err
    73  	}
    74  
    75  	if readBytes == 0 {
    76  		return 0, io.EOF
    77  	}
    78  
    79  	f.updateOffset(origOffset, readBytes)
    80  	return int(readBytes), nil
    81  }
    82  
    83  // ReadAt implements the billy.File interface for File.
    84  func (f *File) ReadAt(p []byte, off int64) (n int, err error) {
    85  	// ReadAt doesn't affect the underlying offset.
    86  	readBytes, err := f.fs.config.KBFSOps().Read(f.fs.ctx, f.node, p, off)
    87  	if err != nil {
    88  		return 0, err
    89  	}
    90  	if int(readBytes) < len(p) {
    91  		// ReadAt is more strict than Read; it requires a real error
    92  		// if someone tries to read such that it won't fill the
    93  		// buffer.  But just wrap an io.EOF in the error so that
    94  		// folderBranchOps can figure it out, when calling from a
    95  		// `Read()` implementation.
    96  		return 0, errors.Wrapf(
    97  			io.EOF, "Could only read %d (not %d) bytes", readBytes, len(p))
    98  	}
    99  
   100  	return int(readBytes), nil
   101  }
   102  
   103  // Seek implements the billy.File interface for File.
   104  func (f *File) Seek(offset int64, whence int) (n int64, err error) {
   105  	newOffset := offset
   106  	switch whence {
   107  	case io.SeekStart:
   108  	case io.SeekCurrent:
   109  		origOffset := atomic.LoadInt64(&f.offset)
   110  		newOffset = origOffset + offset
   111  	case io.SeekEnd:
   112  		ei, err := f.fs.config.KBFSOps().Stat(f.fs.ctx, f.node)
   113  		if err != nil {
   114  			return 0, err
   115  		}
   116  		newOffset = int64(ei.Size) + offset
   117  	}
   118  	if newOffset < 0 {
   119  		return 0, errors.Errorf("Cannot seek to offset %d", newOffset)
   120  	}
   121  
   122  	_ = atomic.SwapInt64(&f.offset, newOffset)
   123  	return newOffset, nil
   124  }
   125  
   126  // Close implements the billy.File interface for File.
   127  func (f *File) Close() error {
   128  	err := f.Unlock()
   129  	if err != nil {
   130  		return err
   131  	}
   132  	f.node = nil
   133  	return nil
   134  }
   135  
   136  func (f *File) getLockID() keybase1.LockID {
   137  	// If we ever change this lock ID format, we must first come up with a
   138  	// transition plan and then upgrade all clients before transitioning.
   139  	return keybase1.LockIDFromBytes(
   140  		bytes.Join([][]byte{
   141  			f.fs.GetLockNamespace(),
   142  			[]byte(f.Name()),
   143  		}, []byte{'/'}))
   144  }
   145  
   146  // Lock implements the billy.File interface for File.
   147  func (f *File) Lock() (err error) {
   148  	done := make(chan struct{})
   149  	f.fs.sendEvents(FSEvent{
   150  		EventType: FSEventLock,
   151  		File:      f,
   152  		Done:      done,
   153  	})
   154  	defer close(done)
   155  	f.lockedLock.Lock()
   156  	defer f.lockedLock.Unlock()
   157  	if f.locked {
   158  		return nil
   159  	}
   160  	defer func() {
   161  		if err == nil {
   162  			f.locked = true
   163  		}
   164  	}()
   165  
   166  	// First, sync all and ask journal to flush all existing writes.
   167  	err = f.fs.SyncAll()
   168  	if err != nil {
   169  		return err
   170  	}
   171  	jManager, err := libkbfs.GetJournalManager(f.fs.config)
   172  	if err == nil {
   173  		if err = jManager.FinishSingleOp(f.fs.ctx,
   174  			f.fs.root.GetFolderBranch().Tlf, nil, f.fs.priority); err != nil {
   175  			return err
   176  		}
   177  	}
   178  
   179  	// Now, sync up with the server, while making sure a lock is held by us. If
   180  	// lock taking fails, RPC layer retries automatically.
   181  	lockID := f.getLockID()
   182  	return f.fs.config.KBFSOps().SyncFromServer(f.fs.ctx,
   183  		f.fs.root.GetFolderBranch(), &lockID)
   184  }
   185  
   186  // Unlock implements the billy.File interface for File.
   187  func (f *File) Unlock() (err error) {
   188  	f.lockedLock.Lock()
   189  	defer f.lockedLock.Unlock()
   190  	if !f.locked {
   191  		return nil
   192  	}
   193  
   194  	// Send the event only if f.locked == true.
   195  	done := make(chan struct{})
   196  	f.fs.sendEvents(FSEvent{
   197  		EventType: FSEventUnlock,
   198  		File:      f,
   199  		Done:      done,
   200  	})
   201  	defer close(done)
   202  
   203  	defer func() {
   204  		if err == nil {
   205  			f.locked = false
   206  		}
   207  	}()
   208  
   209  	err = f.fs.SyncAll()
   210  	if err != nil {
   211  		return err
   212  	}
   213  	jManager, err := libkbfs.GetJournalManager(f.fs.config)
   214  	if err == nil {
   215  		jStatus, _ := jManager.JournalStatus(f.fs.root.GetFolderBranch().Tlf)
   216  		if jStatus.RevisionStart == kbfsmd.RevisionUninitialized {
   217  			// Journal MDs are all flushed and we haven't made any
   218  			// more writes.  Calling FinishSingleOp won't make it to
   219  			// the server, so we make a naked request to server just
   220  			// to release the lock.
   221  			return f.fs.config.MDServer().ReleaseLock(f.fs.ctx,
   222  				f.fs.root.GetFolderBranch().Tlf, f.getLockID())
   223  		}
   224  	} else {
   225  		jManager = nil
   226  	}
   227  
   228  	if f.fs.config.Mode().IsSingleOp() {
   229  		if jManager != nil {
   230  			err = jManager.FinishSingleOp(f.fs.ctx,
   231  				f.fs.root.GetFolderBranch().Tlf, &keybase1.LockContext{
   232  					RequireLockID:       f.getLockID(),
   233  					ReleaseAfterSuccess: true,
   234  				}, f.fs.priority)
   235  			if err != nil {
   236  				return err
   237  			}
   238  		}
   239  	} else {
   240  		if jManager != nil {
   241  			err = jManager.WaitForCompleteFlush(
   242  				f.fs.ctx, f.fs.root.GetFolderBranch().Tlf)
   243  			if err != nil {
   244  				return err
   245  			}
   246  		}
   247  
   248  		f.fs.log.CDebugf(f.fs.ctx, "Releasing the lock")
   249  
   250  		// Need to explicitly release the lock from the server. If
   251  		// single-op mode isn't enabled, then the journal will be
   252  		// flushing on its own without waiting for the call to
   253  		// `FinishSingleOp`. That means the journal can already be
   254  		// completely flushed by the time `FinishSingleOp` is called,
   255  		// and it will be a no-op. It won't have made any call to the
   256  		// server to release the lock, so we have to do it explicitly
   257  		// here.
   258  		err = f.fs.config.MDServer().ReleaseLock(
   259  			f.fs.ctx, f.fs.root.GetFolderBranch().Tlf, f.getLockID())
   260  		if err != nil {
   261  			return err
   262  		}
   263  	}
   264  	return nil
   265  }
   266  
   267  // Truncate implements the billy.File interface for File.
   268  func (f *File) Truncate(size int64) error {
   269  	return f.fs.config.KBFSOps().Truncate(f.fs.ctx, f.node, uint64(size))
   270  }
   271  
   272  // GetNode returns the libkbfs.Node associated with this file.
   273  func (f *File) GetNode() libkbfs.Node {
   274  	return f.node
   275  }