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 }