in internal/fs/fs.go [1919:2036]
func (fs *fileSystem) RmDir(
// When rm -r or os.RemoveAll call is made, the following calls are made in order
// 1. RmDir (only in the case of os.RemoveAll)
// 2. Unlink all nested files,
// 3. lookupInode call on implicit directory
// 4. Rmdir on the directory.
//
// When type cache ttl is set, we construct an implicitDir even though one doesn't
// exist on GCS (https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/internal/fs/inode/dir.go#L452),
// and thus, we get rmDir call to GCSFuse.
// Whereas when ttl is zero, lookupInode call itself fails and RmDir is not called
// because object is not present in GCS.
ctx context.Context,
op *fuseops.RmDirOp) (err error) {
if fs.newConfig.FileSystem.IgnoreInterrupts {
// When ignore interrupts config is set, we are creating a new context not
// cancellable by parent context.
var cancel context.CancelFunc
ctx, cancel = util.IsolateContextFromParentContext(ctx)
defer cancel()
}
// Find the parent.
fs.mu.Lock()
parent := fs.dirInodeOrDie(op.Parent)
fs.mu.Unlock()
// Find or create the child inode, locked.
child, err := fs.lookUpOrCreateChildInode(ctx, parent, op.Name)
if err != nil {
return
}
// Set up a function that throws away the lookup count increment that we
// implicitly did above (since we're not handing the child back to the
// kernel) and unlocks the child, but only once. Ensure it is called at least
// once in case we exit early.
childCleanedUp := false
cleanUpAndUnlockChild := func() {
if !childCleanedUp {
childCleanedUp = true
fs.unlockAndDecrementLookupCount(child, 1)
}
}
defer cleanUpAndUnlockChild()
// Is the child a directory?
childDir, ok := child.(inode.DirInode)
if !ok {
err = fuse.ENOTDIR
return
}
// Ensure that the child directory is empty.
//
// Yes, this is not atomic with the delete below. See here for discussion:
//
// https://github.com/GoogleCloudPlatform/gcsfuse/issues/9
//
//
// Check for local file entries.
fs.mu.Lock()
localFileEntries := childDir.LocalFileEntries(fs.localFileInodes)
fs.mu.Unlock()
// Are there any local entries?
if len(localFileEntries) != 0 {
err = fuse.ENOTEMPTY
return
}
// Check for entries on GCS.
var tok string
for {
var entries []fuseutil.Dirent
entries, tok, err = childDir.ReadEntries(ctx, tok)
if err != nil {
err = fmt.Errorf("ReadEntries: %w", err)
return err
}
if fs.kernelListCacheTTL > 0 {
// Clear kernel list cache after removing a directory. This ensures remote
// GCS files are included in future directory listings for unlinking.
childDir.InvalidateKernelListCache()
}
// Are there any entries?
if len(entries) != 0 {
err = fuse.ENOTEMPTY
return
}
// Are we done listing?
if tok == "" {
break
}
}
// We are done with the child.
cleanUpAndUnlockChild()
// Delete the backing object.
fs.mu.Lock()
_, isImplicitDir := fs.implicitDirInodes[child.Name()]
fs.mu.Unlock()
parent.Lock()
err = parent.DeleteChildDir(ctx, op.Name, isImplicitDir, childDir)
parent.Unlock()
if err != nil {
err = fmt.Errorf("DeleteChildDir: %w", err)
return err
}
return
}