Remove the "frozen" bit from dirents.
Frozen was to lock down changes to the host filesystem for hostFS. Now that hostFS is gone, it can be removed. PiperOrigin-RevId: 301907923
This commit is contained in:
parent
238e80fe38
commit
57d9bd922b
|
@ -17,7 +17,6 @@ package fs
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
@ -121,9 +120,6 @@ type Dirent struct {
|
||||||
// deleted may be set atomically when removed.
|
// deleted may be set atomically when removed.
|
||||||
deleted int32
|
deleted int32
|
||||||
|
|
||||||
// frozen indicates this entry can't walk to unknown nodes.
|
|
||||||
frozen bool
|
|
||||||
|
|
||||||
// mounted is true if Dirent is a mount point, similar to include/linux/dcache.h:DCACHE_MOUNTED.
|
// mounted is true if Dirent is a mount point, similar to include/linux/dcache.h:DCACHE_MOUNTED.
|
||||||
mounted bool
|
mounted bool
|
||||||
|
|
||||||
|
@ -253,8 +249,7 @@ func (d *Dirent) IsNegative() bool {
|
||||||
return d.Inode == nil
|
return d.Inode == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hashChild will hash child into the children list of its new parent d, carrying over
|
// hashChild will hash child into the children list of its new parent d.
|
||||||
// any "frozen" state from d.
|
|
||||||
//
|
//
|
||||||
// Returns (*WeakRef, true) if hashing child caused a Dirent to be unhashed. The caller must
|
// Returns (*WeakRef, true) if hashing child caused a Dirent to be unhashed. The caller must
|
||||||
// validate the returned unhashed weak reference. Common cases:
|
// validate the returned unhashed weak reference. Common cases:
|
||||||
|
@ -282,9 +277,6 @@ func (d *Dirent) hashChild(child *Dirent) (*refs.WeakRef, bool) {
|
||||||
d.IncRef()
|
d.IncRef()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carry over parent's frozen state.
|
|
||||||
child.frozen = d.frozen
|
|
||||||
|
|
||||||
return d.hashChildParentSet(child)
|
return d.hashChildParentSet(child)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,38 +392,6 @@ func (d *Dirent) MountRoot() *Dirent {
|
||||||
return mountRoot
|
return mountRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
// Freeze prevents this dirent from walking to more nodes. Freeze is applied
|
|
||||||
// recursively to all children.
|
|
||||||
//
|
|
||||||
// If this particular Dirent represents a Virtual node, then Walks and Creates
|
|
||||||
// may proceed as before.
|
|
||||||
//
|
|
||||||
// Freeze can only be called before the application starts running, otherwise
|
|
||||||
// the root it might be out of sync with the application root if modified by
|
|
||||||
// sys_chroot.
|
|
||||||
func (d *Dirent) Freeze() {
|
|
||||||
d.mu.Lock()
|
|
||||||
defer d.mu.Unlock()
|
|
||||||
if d.frozen {
|
|
||||||
// Already frozen.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.frozen = true
|
|
||||||
|
|
||||||
// Take a reference when freezing.
|
|
||||||
for _, w := range d.children {
|
|
||||||
if child := w.Get(); child != nil {
|
|
||||||
// NOTE: We would normally drop the reference here. But
|
|
||||||
// instead we're hanging on to it.
|
|
||||||
ch := child.(*Dirent)
|
|
||||||
ch.Freeze()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop all expired weak references.
|
|
||||||
d.flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// descendantOf returns true if the receiver dirent is equal to, or a
|
// descendantOf returns true if the receiver dirent is equal to, or a
|
||||||
// descendant of, the argument dirent.
|
// descendant of, the argument dirent.
|
||||||
//
|
//
|
||||||
|
@ -524,11 +484,6 @@ func (d *Dirent) walk(ctx context.Context, root *Dirent, name string, walkMayUnl
|
||||||
w.Drop()
|
w.Drop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we allowed to do the lookup?
|
|
||||||
if d.frozen && !d.Inode.IsVirtual() {
|
|
||||||
return nil, syscall.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slow path: load the InodeOperations into memory. Since this is a hot path and the lookup may be
|
// Slow path: load the InodeOperations into memory. Since this is a hot path and the lookup may be
|
||||||
// expensive, if possible release the lock and re-acquire it.
|
// expensive, if possible release the lock and re-acquire it.
|
||||||
if walkMayUnlock {
|
if walkMayUnlock {
|
||||||
|
@ -659,11 +614,6 @@ func (d *Dirent) Create(ctx context.Context, root *Dirent, name string, flags Fi
|
||||||
return nil, syscall.EEXIST
|
return nil, syscall.EEXIST
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we frozen?
|
|
||||||
if d.frozen && !d.Inode.IsVirtual() {
|
|
||||||
return nil, syscall.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try the create. We need to trust the file system to return EEXIST (or something
|
// Try the create. We need to trust the file system to return EEXIST (or something
|
||||||
// that will translate to EEXIST) if name already exists.
|
// that will translate to EEXIST) if name already exists.
|
||||||
file, err := d.Inode.Create(ctx, d, name, flags, perms)
|
file, err := d.Inode.Create(ctx, d, name, flags, perms)
|
||||||
|
@ -727,11 +677,6 @@ func (d *Dirent) genericCreate(ctx context.Context, root *Dirent, name string, c
|
||||||
return syscall.EEXIST
|
return syscall.EEXIST
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we frozen?
|
|
||||||
if d.frozen && !d.Inode.IsVirtual() {
|
|
||||||
return syscall.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove any negative Dirent. We've already asserted above with d.exists
|
// Remove any negative Dirent. We've already asserted above with d.exists
|
||||||
// that the only thing remaining here can be a negative Dirent.
|
// that the only thing remaining here can be a negative Dirent.
|
||||||
if w, ok := d.children[name]; ok {
|
if w, ok := d.children[name]; ok {
|
||||||
|
@ -862,49 +807,6 @@ func (d *Dirent) GetDotAttrs(root *Dirent) (DentAttr, DentAttr) {
|
||||||
return dot, dot
|
return dot, dot
|
||||||
}
|
}
|
||||||
|
|
||||||
// readdirFrozen returns readdir results based solely on the frozen children.
|
|
||||||
func (d *Dirent) readdirFrozen(root *Dirent, offset int64, dirCtx *DirCtx) (int64, error) {
|
|
||||||
// Collect attrs for "." and "..".
|
|
||||||
attrs := make(map[string]DentAttr)
|
|
||||||
names := []string{".", ".."}
|
|
||||||
attrs["."], attrs[".."] = d.GetDotAttrs(root)
|
|
||||||
|
|
||||||
// Get info from all children.
|
|
||||||
d.mu.Lock()
|
|
||||||
defer d.mu.Unlock()
|
|
||||||
for name, w := range d.children {
|
|
||||||
if child := w.Get(); child != nil {
|
|
||||||
defer child.DecRef()
|
|
||||||
|
|
||||||
// Skip negative children.
|
|
||||||
if child.(*Dirent).IsNegative() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
sattr := child.(*Dirent).Inode.StableAttr
|
|
||||||
attrs[name] = DentAttr{
|
|
||||||
Type: sattr.Type,
|
|
||||||
InodeID: sattr.InodeID,
|
|
||||||
}
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(names)
|
|
||||||
|
|
||||||
if int(offset) >= len(names) {
|
|
||||||
return offset, nil
|
|
||||||
}
|
|
||||||
names = names[int(offset):]
|
|
||||||
for _, name := range names {
|
|
||||||
if err := dirCtx.DirEmit(name, attrs[name]); err != nil {
|
|
||||||
return offset, err
|
|
||||||
}
|
|
||||||
offset++
|
|
||||||
}
|
|
||||||
return offset, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DirIterator is an open directory containing directory entries that can be read.
|
// DirIterator is an open directory containing directory entries that can be read.
|
||||||
type DirIterator interface {
|
type DirIterator interface {
|
||||||
// IterateDir emits directory entries by calling dirCtx.EmitDir, beginning
|
// IterateDir emits directory entries by calling dirCtx.EmitDir, beginning
|
||||||
|
@ -964,10 +866,6 @@ func direntReaddir(ctx context.Context, d *Dirent, it DirIterator, root *Dirent,
|
||||||
return offset, nil
|
return offset, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.frozen {
|
|
||||||
return d.readdirFrozen(root, offset, dirCtx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect attrs for "." and "..".
|
// Collect attrs for "." and "..".
|
||||||
dot, dotdot := d.GetDotAttrs(root)
|
dot, dotdot := d.GetDotAttrs(root)
|
||||||
|
|
||||||
|
@ -1068,11 +966,6 @@ func (d *Dirent) mount(ctx context.Context, inode *Inode) (newChild *Dirent, err
|
||||||
return nil, syserror.EINVAL
|
return nil, syserror.EINVAL
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we frozen?
|
|
||||||
if d.parent.frozen && !d.parent.Inode.IsVirtual() {
|
|
||||||
return nil, syserror.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dirent that'll replace d.
|
// Dirent that'll replace d.
|
||||||
//
|
//
|
||||||
// Note that NewDirent returns with one reference taken; the reference
|
// Note that NewDirent returns with one reference taken; the reference
|
||||||
|
@ -1101,11 +994,6 @@ func (d *Dirent) unmount(ctx context.Context, replacement *Dirent) error {
|
||||||
return syserror.ENOENT
|
return syserror.ENOENT
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we frozen?
|
|
||||||
if d.parent.frozen && !d.parent.Inode.IsVirtual() {
|
|
||||||
return syserror.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remount our former child in its place.
|
// Remount our former child in its place.
|
||||||
//
|
//
|
||||||
// As replacement used to be our child, it must already have the right
|
// As replacement used to be our child, it must already have the right
|
||||||
|
@ -1135,11 +1023,6 @@ func (d *Dirent) Remove(ctx context.Context, root *Dirent, name string, dirPath
|
||||||
unlock := d.lockDirectory()
|
unlock := d.lockDirectory()
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
// Are we frozen?
|
|
||||||
if d.frozen && !d.Inode.IsVirtual() {
|
|
||||||
return syscall.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to walk to the node.
|
// Try to walk to the node.
|
||||||
child, err := d.walk(ctx, root, name, false /* may unlock */)
|
child, err := d.walk(ctx, root, name, false /* may unlock */)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1201,11 +1084,6 @@ func (d *Dirent) RemoveDirectory(ctx context.Context, root *Dirent, name string)
|
||||||
unlock := d.lockDirectory()
|
unlock := d.lockDirectory()
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
// Are we frozen?
|
|
||||||
if d.frozen && !d.Inode.IsVirtual() {
|
|
||||||
return syscall.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for dots.
|
// Check for dots.
|
||||||
if name == "." {
|
if name == "." {
|
||||||
// Rejected as the last component by rmdir(2).
|
// Rejected as the last component by rmdir(2).
|
||||||
|
@ -1519,15 +1397,6 @@ func Rename(ctx context.Context, root *Dirent, oldParent *Dirent, oldName string
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we frozen?
|
|
||||||
// TODO(jamieliu): Is this the right errno?
|
|
||||||
if oldParent.frozen && !oldParent.Inode.IsVirtual() {
|
|
||||||
return syscall.ENOENT
|
|
||||||
}
|
|
||||||
if newParent.frozen && !newParent.Inode.IsVirtual() {
|
|
||||||
return syscall.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do we have general permission to remove from oldParent and
|
// Do we have general permission to remove from oldParent and
|
||||||
// create/replace in newParent?
|
// create/replace in newParent?
|
||||||
if err := oldParent.Inode.CheckPermission(ctx, PermMask{Write: true, Execute: true}); err != nil {
|
if err := oldParent.Inode.CheckPermission(ctx, PermMask{Write: true, Execute: true}); err != nil {
|
||||||
|
|
|
@ -175,90 +175,6 @@ func TestReaddirRevalidation(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestReaddirOverlayFrozen tests that calling Readdir on an overlay file with
|
|
||||||
// a frozen dirent tree does not make Readdir calls to the underlying files.
|
|
||||||
// This is a regression test for b/114808269.
|
|
||||||
func TestReaddirOverlayFrozen(t *testing.T) {
|
|
||||||
ctx := contexttest.Context(t)
|
|
||||||
|
|
||||||
// Create an overlay with two directories, each with two files.
|
|
||||||
upper := newTestRamfsDir(ctx, []dirContent{{name: "upper-file1"}, {name: "upper-file2"}}, nil)
|
|
||||||
lower := newTestRamfsDir(ctx, []dirContent{{name: "lower-file1"}, {name: "lower-file2"}}, nil)
|
|
||||||
overlayInode := fs.NewTestOverlayDir(ctx, upper, lower, false)
|
|
||||||
|
|
||||||
// Set that overlay as the root.
|
|
||||||
root := fs.NewDirent(ctx, overlayInode, "root")
|
|
||||||
ctx = &rootContext{
|
|
||||||
Context: ctx,
|
|
||||||
root: root,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that calling Readdir on the root now returns all 4 files (2
|
|
||||||
// from each layer in the overlay).
|
|
||||||
rootFile, err := root.Inode.GetFile(ctx, root, fs.FileFlags{Read: true})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("root.Inode.GetFile failed: %v", err)
|
|
||||||
}
|
|
||||||
defer rootFile.DecRef()
|
|
||||||
ser := &fs.CollectEntriesSerializer{}
|
|
||||||
if err := rootFile.Readdir(ctx, ser); err != nil {
|
|
||||||
t.Fatalf("rootFile.Readdir failed: %v", err)
|
|
||||||
}
|
|
||||||
if got, want := ser.Order, []string{".", "..", "lower-file1", "lower-file2", "upper-file1", "upper-file2"}; !reflect.DeepEqual(got, want) {
|
|
||||||
t.Errorf("Readdir got names %v, want %v", got, want)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Readdir should have been called on upper and lower.
|
|
||||||
upperDir := upper.InodeOperations.(*dir)
|
|
||||||
lowerDir := lower.InodeOperations.(*dir)
|
|
||||||
if !upperDir.ReaddirCalled {
|
|
||||||
t.Errorf("upperDir.ReaddirCalled got %v, want true", upperDir.ReaddirCalled)
|
|
||||||
}
|
|
||||||
if !lowerDir.ReaddirCalled {
|
|
||||||
t.Errorf("lowerDir.ReaddirCalled got %v, want true", lowerDir.ReaddirCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset.
|
|
||||||
upperDir.ReaddirCalled = false
|
|
||||||
lowerDir.ReaddirCalled = false
|
|
||||||
|
|
||||||
// Take references on "upper-file1" and "lower-file1", pinning them in
|
|
||||||
// the dirent tree.
|
|
||||||
for _, name := range []string{"upper-file1", "lower-file1"} {
|
|
||||||
if _, err := root.Walk(ctx, root, name); err != nil {
|
|
||||||
t.Fatalf("root.Walk(%q) failed: %v", name, err)
|
|
||||||
}
|
|
||||||
// Don't drop a reference on the returned dirent so that it
|
|
||||||
// will stay in the tree.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Freeze the dirent tree.
|
|
||||||
root.Freeze()
|
|
||||||
|
|
||||||
// Seek back to the beginning of the file.
|
|
||||||
if _, err := rootFile.Seek(ctx, fs.SeekSet, 0); err != nil {
|
|
||||||
t.Fatalf("error seeking to beginning of directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calling Readdir on the root now will return only the pinned
|
|
||||||
// children.
|
|
||||||
ser = &fs.CollectEntriesSerializer{}
|
|
||||||
if err := rootFile.Readdir(ctx, ser); err != nil {
|
|
||||||
t.Fatalf("rootFile.Readdir failed: %v", err)
|
|
||||||
}
|
|
||||||
if got, want := ser.Order, []string{".", "..", "lower-file1", "upper-file1"}; !reflect.DeepEqual(got, want) {
|
|
||||||
t.Errorf("Readdir got names %v, want %v", got, want)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Readdir should NOT have been called on upper or lower.
|
|
||||||
if upperDir.ReaddirCalled {
|
|
||||||
t.Errorf("upperDir.ReaddirCalled got %v, want false", upperDir.ReaddirCalled)
|
|
||||||
}
|
|
||||||
if lowerDir.ReaddirCalled {
|
|
||||||
t.Errorf("lowerDir.ReaddirCalled got %v, want false", lowerDir.ReaddirCalled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type rootContext struct {
|
type rootContext struct {
|
||||||
context.Context
|
context.Context
|
||||||
root *fs.Dirent
|
root *fs.Dirent
|
||||||
|
|
|
@ -273,19 +273,6 @@ func (mns *MountNamespace) DecRef() {
|
||||||
mns.DecRefWithDestructor(mns.destroy)
|
mns.DecRefWithDestructor(mns.destroy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Freeze freezes the entire mount tree.
|
|
||||||
func (mns *MountNamespace) Freeze() {
|
|
||||||
mns.mu.Lock()
|
|
||||||
defer mns.mu.Unlock()
|
|
||||||
|
|
||||||
// We only want to freeze Dirents with active references, not Dirents referenced
|
|
||||||
// by a mount's MountSource.
|
|
||||||
mns.flushMountSourceRefsLocked()
|
|
||||||
|
|
||||||
// Freeze the entire shebang.
|
|
||||||
mns.root.Freeze()
|
|
||||||
}
|
|
||||||
|
|
||||||
// withMountLocked prevents further walks to `node`, because `node` is about to
|
// withMountLocked prevents further walks to `node`, because `node` is about to
|
||||||
// be a mount point.
|
// be a mount point.
|
||||||
func (mns *MountNamespace) withMountLocked(node *Dirent, fn func() error) error {
|
func (mns *MountNamespace) withMountLocked(node *Dirent, fn func() error) error {
|
||||||
|
|
Loading…
Reference in New Issue