// LOCKS_EXCLUDED(fs.mu) func (fs *fileSystem) Rename( ctx context.Context, op *fuseops.RenameOp) (err error) { // Find the old and new parents. fs.mu.Lock() oldParent := fs.inodes[op.OldParent].(inode.DirInode) newParent := fs.inodes[op.NewParent].(inode.DirInode) fs.mu.Unlock() // Find the object in the old location. oldParent.Lock() lr, err := oldParent.LookUpChild(ctx, op.OldName) oldParent.Unlock() if err != nil { err = fmt.Errorf("LookUpChild: %v", err) return } if !lr.Exists() { err = fuse.ENOENT return } // We don't support renaming directories. if inode.IsDirName(lr.FullName) { err = fuse.ENOSYS return } // Clone into the new location. newParent.Lock() _, err = newParent.CloneToChildFile( ctx, op.NewName, lr.Object) newParent.Unlock() if err != nil { err = fmt.Errorf("CloneToChildFile: %v", err) return } // Delete behind. Make sure to delete exactly the generation we cloned, in // case the referent of the name has changed in the meantime. oldParent.Lock() err = oldParent.DeleteChildFile( ctx, op.OldName, lr.Object.Generation, &lr.Object.MetaGeneration) oldParent.Unlock() if err != nil { err = fmt.Errorf("DeleteChildFile: %v", err) return } return }
// Attempt to find an inode for the given name, backed by the supplied object // record (or nil for implicit directories). Create one if one has never yet // existed and, if the record is non-nil, the record is newer than any inode // we've yet recorded. // // If the record is stale (i.e. some newer inode exists), return nil. In this // case, the caller may obtain a fresh record and try again. Otherwise, // increment the inode's lookup count and return it locked. // // UNLOCK_FUNCTION(fs.mu) // LOCK_FUNCTION(in) func (fs *fileSystem) lookUpOrCreateInodeIfNotStale( name string, o *gcs.Object) (in inode.Inode) { // Sanity check. if o != nil && name != o.Name { panic(fmt.Sprintf("Name mismatch: %q vs. %q", name, o.Name)) } // Ensure that no matter which inode we return, we increase its lookup count // on the way out. defer func() { if in != nil { in.IncrementLookupCount() } }() // Handle implicit directories. if o == nil { if !inode.IsDirName(name) { panic(fmt.Sprintf("Unexpected name for an implicit directory: %q", name)) } // If we don't have an entry, create one. var ok bool in, ok = fs.implicitDirInodes[name] if !ok { in = fs.mintInode(name, nil) fs.implicitDirInodes[in.Name()] = in.(inode.DirInode) } fs.mu.Unlock() in.Lock() return } oGen := inode.Generation{ Object: o.Generation, Metadata: o.MetaGeneration, } // Retry loop for the stale index entry case below. On entry, we hold fs.mu // but no inode lock. for { // Look at the current index entry. existingInode, ok := fs.generationBackedInodes[o.Name] // If we have no existing record for this name, mint an inode and return it. if !ok { in = fs.mintInode(o.Name, o) fs.generationBackedInodes[in.Name()] = in.(inode.GenerationBackedInode) fs.mu.Unlock() in.Lock() return } // Otherwise we need to grab the inode lock to find out if this is our // inode, our record is stale, or the inode is stale. We must exclude // concurrent actions on the inode to get a definitive answer. // // Drop the file system lock and acquire the inode lock. fs.mu.Unlock() existingInode.Lock() // Have we found the correct inode? cmp := oGen.Compare(existingInode.SourceGeneration()) if cmp == 0 { in = existingInode return } // Are we stale? if cmp == -1 { existingInode.Unlock() return } // We've observed that the record is newer than the existing inode, while // holding the inode lock, excluding concurrent actions by the inode (in // particular concurrent calls to Sync, which changes generation numbers). // This means we've proven that the record cannot have been caused by the // inode's actions, and therefore it is not the inode we want. // // Re-acquire the file system lock. If the index entry still points at // existingInode, we have proven we can replace it with an entry for a a // newly-minted inode. fs.mu.Lock() if fs.generationBackedInodes[o.Name] == existingInode { in = fs.mintInode(o.Name, o) fs.generationBackedInodes[in.Name()] = in.(inode.GenerationBackedInode) fs.mu.Unlock() existingInode.Unlock() in.Lock() return } // The index entry has been changed in the meantime, so there may be a new // inode that we have to contend with. Go around and try again. existingInode.Unlock() } }
// Implementation detail of lookUpOrCreateInodeIfNotStale; do not use outside // of that function. // // LOCKS_REQUIRED(fs.mu) func (fs *fileSystem) mintInode(name string, o *gcs.Object) (in inode.Inode) { // Choose an ID. id := fs.nextInodeID fs.nextInodeID++ // Create the inode. switch { // Explicit directories case o != nil && inode.IsDirName(o.Name): in = inode.NewExplicitDirInode( id, o, fuseops.InodeAttributes{ Uid: fs.uid, Gid: fs.gid, Mode: fs.dirMode, }, fs.implicitDirs, fs.dirTypeCacheTTL, fs.bucket, fs.mtimeClock, fs.cacheClock) // Implicit directories case inode.IsDirName(name): in = inode.NewDirInode( id, name, fuseops.InodeAttributes{ Uid: fs.uid, Gid: fs.gid, Mode: fs.dirMode, }, fs.implicitDirs, fs.dirTypeCacheTTL, fs.bucket, fs.mtimeClock, fs.cacheClock) case inode.IsSymlink(o): in = inode.NewSymlinkInode( id, o, fuseops.InodeAttributes{ Uid: fs.uid, Gid: fs.gid, Mode: fs.fileMode | os.ModeSymlink, }) default: in = inode.NewFileInode( id, o, fuseops.InodeAttributes{ Uid: fs.uid, Gid: fs.gid, Mode: fs.fileMode, }, fs.bucket, fs.syncer, fs.tempDir, fs.mtimeClock) } // Place it in our map of IDs to inodes. fs.inodes[in.ID()] = in return }
func (fs *fileSystem) checkInvariants() { ////////////////////////////////// // inodes ////////////////////////////////// // INVARIANT: For all keys k, fuseops.RootInodeID <= k < nextInodeID for id, _ := range fs.inodes { if id < fuseops.RootInodeID || id >= fs.nextInodeID { panic(fmt.Sprintf("Illegal inode ID: %v", id)) } } // INVARIANT: For all keys k, inodes[k].ID() == k for id, in := range fs.inodes { if in.ID() != id { panic(fmt.Sprintf("ID mismatch: %v vs. %v", in.ID(), id)) } } // INVARIANT: inodes[fuseops.RootInodeID] is missing or of type inode.DirInode // // The missing case is when we've received a forget request for the root // inode, while unmounting. switch in := fs.inodes[fuseops.RootInodeID].(type) { case nil: case inode.DirInode: default: panic(fmt.Sprintf("Unexpected type for root: %v", reflect.TypeOf(in))) } // INVARIANT: For all v, if IsDirName(v.Name()) then v is inode.DirInode for _, in := range fs.inodes { if inode.IsDirName(in.Name()) { _, ok := in.(inode.DirInode) if !ok { panic(fmt.Sprintf( "Unexpected inode type for name \"%s\": %v", in.Name(), reflect.TypeOf(in))) } } } ////////////////////////////////// // generationBackedInodes ////////////////////////////////// // INVARIANT: For each k/v, v.Name() == k for k, v := range fs.generationBackedInodes { if !(v.Name() == k) { panic(fmt.Sprintf( "Unexpected name: \"%s\" vs. \"%s\"", v.Name(), k)) } } // INVARIANT: For each value v, inodes[v.ID()] == v for _, v := range fs.generationBackedInodes { if fs.inodes[v.ID()] != v { panic(fmt.Sprintf( "Mismatch for ID %v: %p %p", v.ID(), fs.inodes[v.ID()], v)) } } ////////////////////////////////// // implicitDirInodes ////////////////////////////////// // INVARIANT: For each k/v, v.Name() == k for k, v := range fs.implicitDirInodes { if !(v.Name() == k) { panic(fmt.Sprintf( "Unexpected name: \"%s\" vs. \"%s\"", v.Name(), k)) } } // INVARIANT: For each value v, inodes[v.ID()] == v for _, v := range fs.implicitDirInodes { if fs.inodes[v.ID()] != v { panic(fmt.Sprintf( "Mismatch for ID %v: %p %p", v.ID(), fs.inodes[v.ID()], v)) } } // INVARIANT: For each value v, v is not ExplicitDirInode for _, v := range fs.implicitDirInodes { if _, ok := v.(inode.ExplicitDirInode); ok { panic(fmt.Sprintf( "Unexpected implicit dir inode %d, type %T", v.ID(), v)) } } // INVARIANT: For each in in inodes such that in is DirInode but not // ExplicitDirInode, implicitDirInodes[d.Name()] == d for _, in := range fs.inodes { _, dir := in.(inode.DirInode) _, edir := in.(inode.ExplicitDirInode) if dir && !edir { if !(fs.implicitDirInodes[in.Name()] == in) { panic(fmt.Sprintf( "implicitDirInodes mismatch: %q %p %p", in.Name(), fs.implicitDirInodes[in.Name()], in)) } } } ////////////////////////////////// // handles ////////////////////////////////// // INVARIANT: All values are of type *dirHandle or *handle.FileHandle for _, h := range fs.handles { switch h.(type) { case *dirHandle: case *handle.FileHandle: default: panic(fmt.Sprintf("Unexpected handle type: %T", h)) } } ////////////////////////////////// // nextHandleID ////////////////////////////////// // INVARIANT: For all keys k in handles, k < nextHandleID for k, _ := range fs.handles { if k >= fs.nextHandleID { panic(fmt.Sprintf("Illegal handle ID: %v", k)) } } }