mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
fs: Allow overlays to revalidate files from the upper fs.
Previously, an overlay would panic if either the upper or lower fs required revalidation for a given Dirent. Now, we allow revalidation from the upper file, but not the lower. If a cached overlay inode does need revalidation (because the upper needs revalidation), then the entire overlay Inode will be discarded and a new overlay Inode will be built with a fresh copy of the upper file. As a side effect of this change, Revalidate must take an Inode instead of a Dirent, since an overlay needs to revalidate individual Inodes. PiperOrigin-RevId: 208293638 Change-Id: Ic8f8d1ffdc09114721745661a09522b54420c5f1
This commit is contained in:
committed by
Shentubot
parent
ae6f092fe1
commit
a2ec391dfb
@@ -193,6 +193,18 @@ interface. It multiplexes between upper and lower directory memory mappings and
|
||||
stores a copy of memory references so they can be transferred to the upper
|
||||
directory `fs.Mappable` when the file is copied up.
|
||||
|
||||
The lower filesystem in an overlay may contain another (nested) overlay, but the
|
||||
upper filesystem may not contain another overlay. In other words, nested
|
||||
overlays form a tree structure that only allows branching in the lower
|
||||
filesystem.
|
||||
|
||||
Caching decisions in the overlay are delegated to the upper filesystem, meaning
|
||||
that the Keep and Revalidate methods on the overlay return the same values as
|
||||
the upper filesystem. A small wrinkle is that the lower filesystem is not
|
||||
allowed to return `true` from Revalidate, as the overlay can not reload inodes
|
||||
from the lower filesystem. A lower filesystem that does return `true` from
|
||||
Revalidate will trigger a panic.
|
||||
|
||||
The `fs.Inode` also holds a reference to a `fs.MountedFilesystem` that
|
||||
normalizes across the mounted filesystem state of the upper and lower
|
||||
directories.
|
||||
|
||||
@@ -503,7 +503,7 @@ func (d *Dirent) walk(ctx context.Context, root *Dirent, name string, walkMayUnl
|
||||
//
|
||||
// We never allow the file system to revalidate mounts, that could cause them
|
||||
// to unexpectedly drop out before umount.
|
||||
if cd.mounted || !cd.Inode.MountSource.Revalidate(ctx, cd) {
|
||||
if cd.mounted || !cd.Inode.MountSource.Revalidate(ctx, cd.Inode) {
|
||||
// Good to go. This is the fast-path.
|
||||
return cd, nil
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"gvisor.googlesource.com/gvisor/pkg/sentry/context"
|
||||
"gvisor.googlesource.com/gvisor/pkg/sentry/context/contexttest"
|
||||
"gvisor.googlesource.com/gvisor/pkg/sentry/fs"
|
||||
ramfstest "gvisor.googlesource.com/gvisor/pkg/sentry/fs/ramfs/test"
|
||||
)
|
||||
|
||||
func TestReaddir(t *testing.T) {
|
||||
@@ -48,7 +49,7 @@ func TestReaddir(t *testing.T) {
|
||||
{name: "a"},
|
||||
{name: "b"},
|
||||
}, nil), /* lower */
|
||||
),
|
||||
false /* revalidate */),
|
||||
names: []string{".", "..", "a", "b"},
|
||||
},
|
||||
{
|
||||
@@ -59,7 +60,7 @@ func TestReaddir(t *testing.T) {
|
||||
{name: "b"},
|
||||
}, nil), /* upper */
|
||||
nil, /* lower */
|
||||
),
|
||||
false /* revalidate */),
|
||||
names: []string{".", "..", "a", "b"},
|
||||
},
|
||||
{
|
||||
@@ -67,11 +68,11 @@ func TestReaddir(t *testing.T) {
|
||||
dir: fs.NewTestOverlayDir(ctx,
|
||||
newTestRamfsDir(ctx, []dirContent{
|
||||
{name: "a"},
|
||||
}, nil), /* lower */
|
||||
}, nil), /* upper */
|
||||
newTestRamfsDir(ctx, []dirContent{
|
||||
{name: "b"},
|
||||
}, nil), /* lower */
|
||||
),
|
||||
false /* revalidate */),
|
||||
names: []string{".", "..", "a", "b"},
|
||||
},
|
||||
{
|
||||
@@ -79,11 +80,11 @@ func TestReaddir(t *testing.T) {
|
||||
dir: fs.NewTestOverlayDir(ctx,
|
||||
newTestRamfsDir(ctx, []dirContent{
|
||||
{name: "a"},
|
||||
}, []string{"b"}), /* lower */
|
||||
}, []string{"b"}), /* upper */
|
||||
newTestRamfsDir(ctx, []dirContent{
|
||||
{name: "c"},
|
||||
}, nil), /* lower */
|
||||
),
|
||||
false /* revalidate */),
|
||||
names: []string{".", "..", "a", "c"},
|
||||
},
|
||||
{
|
||||
@@ -91,12 +92,12 @@ func TestReaddir(t *testing.T) {
|
||||
dir: fs.NewTestOverlayDir(ctx,
|
||||
newTestRamfsDir(ctx, []dirContent{
|
||||
{name: "a"},
|
||||
}, []string{"b"}), /* lower */
|
||||
}, []string{"b"}), /* upper */
|
||||
newTestRamfsDir(ctx, []dirContent{
|
||||
{name: "b"}, /* will be masked */
|
||||
{name: "c"},
|
||||
}, nil), /* lower */
|
||||
),
|
||||
false /* revalidate */),
|
||||
names: []string{".", "..", "a", "c"},
|
||||
},
|
||||
} {
|
||||
@@ -120,6 +121,59 @@ func TestReaddir(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReaddirRevalidation(t *testing.T) {
|
||||
ctx := contexttest.Context(t)
|
||||
ctx = &rootContext{
|
||||
Context: ctx,
|
||||
root: fs.NewDirent(newTestRamfsDir(ctx, nil, nil), "root"),
|
||||
}
|
||||
|
||||
// Create an overlay with two directories, each with one file.
|
||||
upper := newTestRamfsDir(ctx, []dirContent{{name: "a"}}, nil)
|
||||
lower := newTestRamfsDir(ctx, []dirContent{{name: "b"}}, nil)
|
||||
overlay := fs.NewTestOverlayDir(ctx, upper, lower, true /* revalidate */)
|
||||
|
||||
// Get a handle to the dirent in the upper filesystem so that we can
|
||||
// modify it without going through the dirent.
|
||||
upperDir := upper.InodeOperations.(*dir).InodeOperations.(*ramfstest.Dir)
|
||||
|
||||
// Check that overlay returns the files from both upper and lower.
|
||||
openDir, err := overlay.GetFile(ctx, fs.NewDirent(overlay, "stub"), fs.FileFlags{Read: true})
|
||||
if err != nil {
|
||||
t.Fatalf("GetFile got error %v, want nil", err)
|
||||
}
|
||||
ser := &fs.CollectEntriesSerializer{}
|
||||
if err := openDir.Readdir(ctx, ser); err != nil {
|
||||
t.Fatalf("Readdir got error %v, want nil", err)
|
||||
}
|
||||
got, want := ser.Order, []string{".", "..", "a", "b"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("Readdir got names %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// Remove "a" from the upper and add "c".
|
||||
if err := upperDir.Remove(ctx, upper, "a"); err != nil {
|
||||
t.Fatalf("error removing child: %v", err)
|
||||
}
|
||||
upperDir.AddChild(ctx, "c", fs.NewInode(ramfstest.NewFile(ctx, fs.FilePermissions{}),
|
||||
upper.MountSource, fs.StableAttr{Type: fs.RegularFile}))
|
||||
|
||||
// Seek to beginning of the directory and do the readdir again.
|
||||
if _, err := openDir.Seek(ctx, fs.SeekSet, 0); err != nil {
|
||||
t.Fatalf("error seeking to beginning of dir: %v", err)
|
||||
}
|
||||
ser = &fs.CollectEntriesSerializer{}
|
||||
if err := openDir.Readdir(ctx, ser); err != nil {
|
||||
t.Fatalf("Readdir got error %v, want nil", err)
|
||||
}
|
||||
|
||||
// Readdir should return the updated children.
|
||||
got, want = ser.Order, []string{".", "..", "b", "c"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("Readdir got names %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
type rootContext struct {
|
||||
context.Context
|
||||
root *fs.Dirent
|
||||
|
||||
@@ -145,12 +145,12 @@ func (s *session) Destroy() {
|
||||
s.conn.Close()
|
||||
}
|
||||
|
||||
// Revalidate returns true if the cache policy is does not allow for VFS caching.
|
||||
func (s *session) Revalidate(ctx context.Context, d *fs.Dirent) bool {
|
||||
// Revalidate implements MountSource.Revalidate.
|
||||
func (s *session) Revalidate(ctx context.Context, i *fs.Inode) bool {
|
||||
return s.cachePolicy.revalidateDirent()
|
||||
}
|
||||
|
||||
// TakeRefs takes an extra reference on dirent if possible.
|
||||
// Keep implements MountSource.Keep.
|
||||
func (s *session) Keep(d *fs.Dirent) bool {
|
||||
return s.cachePolicy.keepDirent(d.Inode)
|
||||
}
|
||||
|
||||
@@ -57,6 +57,10 @@ func overlayLookup(ctx context.Context, parent *overlayEntry, inode *Inode, name
|
||||
var upperInode *Inode
|
||||
var lowerInode *Inode
|
||||
|
||||
// We must remember whether the upper fs returned a negative dirent,
|
||||
// because it is only safe to return one if the upper did.
|
||||
var negativeUpperChild bool
|
||||
|
||||
// Does the parent directory exist in the upper file system?
|
||||
if parent.upper != nil {
|
||||
// First check if a file object exists in the upper file system.
|
||||
@@ -70,7 +74,9 @@ func overlayLookup(ctx context.Context, parent *overlayEntry, inode *Inode, name
|
||||
return nil, err
|
||||
}
|
||||
if child != nil {
|
||||
if !child.IsNegative() {
|
||||
if child.IsNegative() {
|
||||
negativeUpperChild = true
|
||||
} else {
|
||||
upperInode = child.Inode
|
||||
upperInode.IncRef()
|
||||
}
|
||||
@@ -81,7 +87,18 @@ func overlayLookup(ctx context.Context, parent *overlayEntry, inode *Inode, name
|
||||
if overlayHasWhiteout(parent.upper, name) {
|
||||
if upperInode == nil {
|
||||
parent.copyMu.RUnlock()
|
||||
return NewNegativeDirent(name), nil
|
||||
if negativeUpperChild {
|
||||
// If the upper fs returnd a negative
|
||||
// Dirent, then the upper is OK with
|
||||
// that negative Dirent being cached in
|
||||
// the Dirent tree, so we can return
|
||||
// one from the overlay.
|
||||
return NewNegativeDirent(name), nil
|
||||
}
|
||||
// Upper fs is not OK with a negative Dirent
|
||||
// being cached in the Dirent tree, so don't
|
||||
// return one.
|
||||
return nil, syserror.ENOENT
|
||||
}
|
||||
entry, err := newOverlayEntry(ctx, upperInode, nil, false)
|
||||
if err != nil {
|
||||
@@ -127,9 +144,14 @@ func overlayLookup(ctx context.Context, parent *overlayEntry, inode *Inode, name
|
||||
|
||||
// Was all of this for naught?
|
||||
if upperInode == nil && lowerInode == nil {
|
||||
// Return a negative Dirent indicating that nothing was found.
|
||||
parent.copyMu.RUnlock()
|
||||
return NewNegativeDirent(name), nil
|
||||
// We can only return a negative dirent if the upper returned
|
||||
// one as well. See comments above regarding negativeUpperChild
|
||||
// for more info.
|
||||
if negativeUpperChild {
|
||||
return NewNegativeDirent(name), nil
|
||||
}
|
||||
return nil, syserror.ENOENT
|
||||
}
|
||||
|
||||
// Did we find a lower Inode? Remember this because we may decide we don't
|
||||
@@ -568,10 +590,19 @@ func overlayHandleOps(o *overlayEntry) HandleOperations {
|
||||
}
|
||||
|
||||
// NewTestOverlayDir returns an overlay Inode for tests.
|
||||
func NewTestOverlayDir(ctx context.Context, upper *Inode, lower *Inode) *Inode {
|
||||
//
|
||||
// If `revalidate` is true, then the upper filesystem will require
|
||||
// revalidation.
|
||||
func NewTestOverlayDir(ctx context.Context, upper, lower *Inode, revalidate bool) *Inode {
|
||||
fs := &overlayFilesystem{}
|
||||
var upperMsrc *MountSource
|
||||
if revalidate {
|
||||
upperMsrc = NewRevalidatingMountSource(fs, MountSourceFlags{})
|
||||
} else {
|
||||
upperMsrc = NewNonCachingMountSource(fs, MountSourceFlags{})
|
||||
}
|
||||
msrc := NewMountSource(&overlayMountSourceOperations{
|
||||
upper: NewNonCachingMountSource(fs, MountSourceFlags{}),
|
||||
upper: upperMsrc,
|
||||
lower: NewNonCachingMountSource(fs, MountSourceFlags{}),
|
||||
}, fs, MountSourceFlags{})
|
||||
overlay := &overlayEntry{
|
||||
|
||||
@@ -35,7 +35,6 @@ func TestLookup(t *testing.T) {
|
||||
name string
|
||||
|
||||
// Want from lookup.
|
||||
err error
|
||||
found bool
|
||||
hasUpper bool
|
||||
hasLower bool
|
||||
@@ -50,7 +49,7 @@ func TestLookup(t *testing.T) {
|
||||
dir: false,
|
||||
},
|
||||
}, nil), /* lower */
|
||||
),
|
||||
false /* revalidate */),
|
||||
name: "a",
|
||||
found: true,
|
||||
hasUpper: false,
|
||||
@@ -66,7 +65,7 @@ func TestLookup(t *testing.T) {
|
||||
},
|
||||
}, nil), /* upper */
|
||||
nil, /* lower */
|
||||
),
|
||||
false /* revalidate */),
|
||||
name: "a",
|
||||
found: true,
|
||||
hasUpper: true,
|
||||
@@ -87,7 +86,7 @@ func TestLookup(t *testing.T) {
|
||||
dir: false,
|
||||
},
|
||||
}, nil), /* lower */
|
||||
),
|
||||
false /* revalidate */),
|
||||
name: "a",
|
||||
found: true,
|
||||
hasUpper: false,
|
||||
@@ -108,7 +107,7 @@ func TestLookup(t *testing.T) {
|
||||
dir: false,
|
||||
},
|
||||
}, nil), /* lower */
|
||||
),
|
||||
false /* revalidate */),
|
||||
name: "a",
|
||||
found: true,
|
||||
hasUpper: true,
|
||||
@@ -129,7 +128,7 @@ func TestLookup(t *testing.T) {
|
||||
dir: false,
|
||||
},
|
||||
}, nil), /* lower */
|
||||
),
|
||||
false /* revalidate */),
|
||||
name: "a",
|
||||
found: true,
|
||||
hasUpper: true,
|
||||
@@ -150,7 +149,7 @@ func TestLookup(t *testing.T) {
|
||||
dir: true,
|
||||
},
|
||||
}, nil), /* lower */
|
||||
),
|
||||
false /* revalidate */),
|
||||
name: "a",
|
||||
found: true,
|
||||
hasUpper: true,
|
||||
@@ -166,7 +165,7 @@ func TestLookup(t *testing.T) {
|
||||
dir: false,
|
||||
},
|
||||
}, nil), /* lower */
|
||||
),
|
||||
false /* revalidate */),
|
||||
name: "a",
|
||||
found: false,
|
||||
hasUpper: false,
|
||||
@@ -182,7 +181,7 @@ func TestLookup(t *testing.T) {
|
||||
dir: false,
|
||||
},
|
||||
}, nil), /* lower */
|
||||
),
|
||||
false /* revalidate */),
|
||||
name: "a",
|
||||
found: true,
|
||||
hasUpper: false,
|
||||
@@ -191,13 +190,14 @@ func TestLookup(t *testing.T) {
|
||||
} {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
dirent, err := test.dir.Lookup(ctx, test.name)
|
||||
if err != test.err {
|
||||
t.Fatalf("lookup got error %v, want %v", err, test.err)
|
||||
}
|
||||
if test.found && dirent.IsNegative() {
|
||||
t.Fatalf("lookup expected to find %q, got negative dirent", test.name)
|
||||
if test.found && (err == syserror.ENOENT || dirent.IsNegative()) {
|
||||
t.Fatalf("lookup %q expected to find positive dirent, got dirent %v err %v", test.name, dirent, err)
|
||||
}
|
||||
if !test.found {
|
||||
if err != syserror.ENOENT && !dirent.IsNegative() {
|
||||
t.Errorf("lookup %q expected to return ENOENT or negative dirent, got dirent %v err %v", test.name, dirent, err)
|
||||
}
|
||||
// Nothing more to check.
|
||||
return
|
||||
}
|
||||
if hasUpper := dirent.Inode.TestHasUpperFS(); hasUpper != test.hasUpper {
|
||||
@@ -210,6 +210,95 @@ func TestLookup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookupRevalidation(t *testing.T) {
|
||||
// File name used in the tests.
|
||||
fileName := "foofile"
|
||||
ctx := contexttest.Context(t)
|
||||
for _, tc := range []struct {
|
||||
// Test description.
|
||||
desc string
|
||||
|
||||
// Upper and lower fs for the overlay.
|
||||
upper *fs.Inode
|
||||
lower *fs.Inode
|
||||
|
||||
// Whether the upper requires revalidation.
|
||||
revalidate bool
|
||||
|
||||
// Whether we should get the same dirent on second lookup.
|
||||
wantSame bool
|
||||
}{
|
||||
{
|
||||
desc: "file from upper with no revalidation",
|
||||
upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
|
||||
lower: newTestRamfsDir(ctx, nil, nil),
|
||||
revalidate: false,
|
||||
wantSame: true,
|
||||
},
|
||||
{
|
||||
desc: "file from upper with revalidation",
|
||||
upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
|
||||
lower: newTestRamfsDir(ctx, nil, nil),
|
||||
revalidate: true,
|
||||
wantSame: false,
|
||||
},
|
||||
{
|
||||
desc: "file from lower with no revalidation",
|
||||
upper: newTestRamfsDir(ctx, nil, nil),
|
||||
lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
|
||||
revalidate: false,
|
||||
wantSame: true,
|
||||
},
|
||||
{
|
||||
desc: "file from lower with revalidation",
|
||||
upper: newTestRamfsDir(ctx, nil, nil),
|
||||
lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
|
||||
revalidate: true,
|
||||
// The file does not exist in the upper, so we do not
|
||||
// need to revalidate it.
|
||||
wantSame: true,
|
||||
},
|
||||
{
|
||||
desc: "file from upper and lower with no revalidation",
|
||||
upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
|
||||
lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
|
||||
revalidate: false,
|
||||
wantSame: true,
|
||||
},
|
||||
{
|
||||
desc: "file from upper and lower with revalidation",
|
||||
upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
|
||||
lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
|
||||
revalidate: true,
|
||||
wantSame: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
root := fs.NewDirent(newTestRamfsDir(ctx, nil, nil), "root")
|
||||
ctx = &rootContext{
|
||||
Context: ctx,
|
||||
root: root,
|
||||
}
|
||||
overlay := fs.NewDirent(fs.NewTestOverlayDir(ctx, tc.upper, tc.lower, tc.revalidate), "overlay")
|
||||
// Lookup the file twice through the overlay.
|
||||
first, err := overlay.Walk(ctx, root, fileName)
|
||||
if err != nil {
|
||||
t.Fatalf("overlay.Walk(%q) failed: %v", fileName, err)
|
||||
}
|
||||
second, err := overlay.Walk(ctx, root, fileName)
|
||||
if err != nil {
|
||||
t.Fatalf("overlay.Walk(%q) failed: %v", fileName, err)
|
||||
}
|
||||
|
||||
if tc.wantSame && first != second {
|
||||
t.Errorf("dirent lookup got different dirents, wanted same\nfirst=%+v\nsecond=%+v", first, second)
|
||||
} else if !tc.wantSame && first == second {
|
||||
t.Errorf("dirent lookup got the same dirent, wanted different: %+v", first)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type dir struct {
|
||||
fs.InodeOperations
|
||||
|
||||
@@ -231,6 +320,10 @@ type dirContent struct {
|
||||
dir bool
|
||||
}
|
||||
|
||||
func newTestRamfsInode(ctx context.Context, msrc *fs.MountSource) *fs.Inode {
|
||||
return fs.NewInode(ramfstest.NewFile(ctx, fs.FilePermissions{}), msrc, fs.StableAttr{Type: fs.RegularFile})
|
||||
}
|
||||
|
||||
func newTestRamfsDir(ctx context.Context, contains []dirContent, negative []string) *fs.Inode {
|
||||
msrc := fs.NewCachingMountSource(nil, fs.MountSourceFlags{})
|
||||
contents := make(map[string]*fs.Inode)
|
||||
@@ -238,7 +331,7 @@ func newTestRamfsDir(ctx context.Context, contains []dirContent, negative []stri
|
||||
if c.dir {
|
||||
contents[c.name] = newTestRamfsDir(ctx, nil, nil)
|
||||
} else {
|
||||
contents[c.name] = fs.NewInode(ramfstest.NewFile(ctx, fs.FilePermissions{}), msrc, fs.StableAttr{Type: fs.RegularFile})
|
||||
contents[c.name] = newTestRamfsInode(ctx, msrc)
|
||||
}
|
||||
}
|
||||
dops := ramfstest.NewDir(ctx, contents, fs.FilePermissions{
|
||||
|
||||
@@ -68,7 +68,7 @@ func NewMockMountSource(cache *DirentCache) *MountSource {
|
||||
}
|
||||
|
||||
// Revalidate implements fs.MountSourceOperations.Revalidate.
|
||||
func (n *MockMountSourceOps) Revalidate(context.Context, *Dirent) bool {
|
||||
func (n *MockMountSourceOps) Revalidate(context.Context, *Inode) bool {
|
||||
return n.revalidate
|
||||
}
|
||||
|
||||
|
||||
+20
-8
@@ -27,10 +27,10 @@ import (
|
||||
// DirentOperations provide file systems greater control over how long a Dirent stays pinned
|
||||
// in core. Implementations must not take Dirent.mu.
|
||||
type DirentOperations interface {
|
||||
// Revalidate returns true if the Dirent is stale and its
|
||||
// Revalidate returns true if the Inode is stale and its
|
||||
// InodeOperations needs to be reloaded. Revalidate will never be
|
||||
// called on a Dirent that is mounted.
|
||||
Revalidate(ctx context.Context, dirent *Dirent) bool
|
||||
// called on a Inode that is mounted.
|
||||
Revalidate(ctx context.Context, inode *Inode) bool
|
||||
|
||||
// Keep returns true if the Dirent should be kept in memory for as long
|
||||
// as possible beyond any active references.
|
||||
@@ -249,7 +249,8 @@ func (msrc *MountSource) FlushDirentRefs() {
|
||||
// aggressively. Filesystem may be nil if there is no backing filesystem.
|
||||
func NewCachingMountSource(filesystem Filesystem, flags MountSourceFlags) *MountSource {
|
||||
return NewMountSource(&SimpleMountSourceOperations{
|
||||
keep: true,
|
||||
keep: true,
|
||||
revalidate: false,
|
||||
}, filesystem, flags)
|
||||
}
|
||||
|
||||
@@ -257,7 +258,17 @@ func NewCachingMountSource(filesystem Filesystem, flags MountSourceFlags) *Mount
|
||||
// Filesystem may be nil if there is no backing filesystem.
|
||||
func NewNonCachingMountSource(filesystem Filesystem, flags MountSourceFlags) *MountSource {
|
||||
return NewMountSource(&SimpleMountSourceOperations{
|
||||
keep: false,
|
||||
keep: false,
|
||||
revalidate: false,
|
||||
}, filesystem, flags)
|
||||
}
|
||||
|
||||
// NewRevalidatingMountSource returns a generic mount that will cache dirents,
|
||||
// but will revalidate them on each lookup.
|
||||
func NewRevalidatingMountSource(filesystem Filesystem, flags MountSourceFlags) *MountSource {
|
||||
return NewMountSource(&SimpleMountSourceOperations{
|
||||
keep: true,
|
||||
revalidate: true,
|
||||
}, filesystem, flags)
|
||||
}
|
||||
|
||||
@@ -265,12 +276,13 @@ func NewNonCachingMountSource(filesystem Filesystem, flags MountSourceFlags) *Mo
|
||||
//
|
||||
// +stateify savable
|
||||
type SimpleMountSourceOperations struct {
|
||||
keep bool
|
||||
keep bool
|
||||
revalidate bool
|
||||
}
|
||||
|
||||
// Revalidate implements MountSourceOperations.Revalidate.
|
||||
func (*SimpleMountSourceOperations) Revalidate(context.Context, *Dirent) bool {
|
||||
return false
|
||||
func (smo *SimpleMountSourceOperations) Revalidate(context.Context, *Inode) bool {
|
||||
return smo.revalidate
|
||||
}
|
||||
|
||||
// Keep implements MountSourceOperations.Keep.
|
||||
|
||||
@@ -14,10 +14,13 @@
|
||||
|
||||
package fs
|
||||
|
||||
import "gvisor.googlesource.com/gvisor/pkg/sentry/context"
|
||||
import (
|
||||
"gvisor.googlesource.com/gvisor/pkg/sentry/context"
|
||||
)
|
||||
|
||||
// overlayMountSourceOperations implements MountSourceOperations for an overlay
|
||||
// mount point.
|
||||
// mount point. The upper filesystem determines the caching behavior of the
|
||||
// overlay.
|
||||
//
|
||||
// +stateify savable
|
||||
type overlayMountSourceOperations struct {
|
||||
@@ -34,19 +37,33 @@ func newOverlayMountSource(upper, lower *MountSource, flags MountSourceFlags) *M
|
||||
}, &overlayFilesystem{}, flags)
|
||||
}
|
||||
|
||||
// Revalidate panics if the upper or lower MountSource require that dirent be
|
||||
// revalidated. Otherwise always returns false.
|
||||
func (o *overlayMountSourceOperations) Revalidate(ctx context.Context, dirent *Dirent) bool {
|
||||
if o.upper.Revalidate(ctx, dirent) || o.lower.Revalidate(ctx, dirent) {
|
||||
panic("an overlay cannot revalidate file objects")
|
||||
// Revalidate implements MountSourceOperations.Revalidate for an overlay by
|
||||
// delegating to the upper filesystem's Revalidate method. We cannot reload
|
||||
// files from the lower filesystem, so we panic if the lower filesystem's
|
||||
// Revalidate method returns true.
|
||||
func (o *overlayMountSourceOperations) Revalidate(ctx context.Context, inode *Inode) bool {
|
||||
if inode.overlay == nil {
|
||||
panic("overlay cannot revalidate inode that is not an overlay")
|
||||
}
|
||||
return false
|
||||
|
||||
// Should we bother checking this, or just ignore?
|
||||
if inode.overlay.lower != nil && o.lower.Revalidate(ctx, inode.overlay.lower) {
|
||||
panic("an overlay cannot revalidate file objects from the lower fs")
|
||||
}
|
||||
|
||||
if inode.overlay.upper == nil {
|
||||
// Nothing to revalidate.
|
||||
return false
|
||||
}
|
||||
|
||||
// Does the upper require revalidation?
|
||||
return o.upper.Revalidate(ctx, inode.overlay.upper)
|
||||
}
|
||||
|
||||
// Keep returns true if either upper or lower MountSource require that the
|
||||
// dirent be kept in memory.
|
||||
// Keep implements MountSourceOperations by delegating to the upper
|
||||
// filesystem's Keep method.
|
||||
func (o *overlayMountSourceOperations) Keep(dirent *Dirent) bool {
|
||||
return o.upper.Keep(dirent) || o.lower.Keep(dirent)
|
||||
return o.upper.Keep(dirent)
|
||||
}
|
||||
|
||||
// ResetInodeMappings propagates the call to both upper and lower MountSource.
|
||||
|
||||
@@ -88,10 +88,11 @@ func isXattrOverlay(name string) bool {
|
||||
// Preconditions:
|
||||
//
|
||||
// - upper and lower must be non-nil.
|
||||
// - upper must not be an overlay.
|
||||
// - lower should not expose character devices, pipes, or sockets, because
|
||||
// copying up these types of files is not supported.
|
||||
// - upper and lower must not require that file objects be revalidated.
|
||||
// - upper and lower must not have dynamic file/directory content.
|
||||
// - lower must not require that file objects be revalidated.
|
||||
// - lower must not have dynamic file/directory content.
|
||||
func NewOverlayRoot(ctx context.Context, upper *Inode, lower *Inode, flags MountSourceFlags) (*Inode, error) {
|
||||
if !IsDir(upper.StableAttr) {
|
||||
return nil, fmt.Errorf("upper Inode is not a directory")
|
||||
@@ -99,6 +100,9 @@ func NewOverlayRoot(ctx context.Context, upper *Inode, lower *Inode, flags Mount
|
||||
if !IsDir(lower.StableAttr) {
|
||||
return nil, fmt.Errorf("lower Inode is not a directory")
|
||||
}
|
||||
if upper.overlay != nil {
|
||||
return nil, fmt.Errorf("cannot nest overlay in upper file of another overlay")
|
||||
}
|
||||
|
||||
msrc := newOverlayMountSource(upper.MountSource, lower.MountSource, flags)
|
||||
overlay, err := newOverlayEntry(ctx, upper, lower, true)
|
||||
|
||||
@@ -82,7 +82,7 @@ type superOperations struct{}
|
||||
// Slave entries are dropped from dir when their master is closed, so an
|
||||
// existing slave Dirent in the tree is not sufficient to guarantee that it
|
||||
// still exists on the filesystem.
|
||||
func (superOperations) Revalidate(context.Context, *fs.Dirent) bool {
|
||||
func (superOperations) Revalidate(context.Context, *fs.Inode) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user