Re-create mount points during restore

Mount points are created during container initialization and may
not exist if the container is restored using the original image.
Mark these directories and recreate them upon restore.

PiperOrigin-RevId: 644514801
This commit is contained in:
Fabricio Voznika
2024-06-18 14:40:37 -07:00
committed by gVisor bot
parent 6d021e7eb2
commit 5734bf093c
4 changed files with 124 additions and 4 deletions
+35 -4
View File
@@ -21,6 +21,7 @@ import (
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/fsutil"
"gvisor.dev/gvisor/pkg/lisafs"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
@@ -531,19 +532,49 @@ func (fs *filesystem) restoreRoot(ctx context.Context, opts *vfs.CompleteRestore
func (d *dentry) restoreFile(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
switch dt := d.impl.(type) {
case *lisafsDentry:
inode, err := d.parent.Load().impl.(*lisafsDentry).controlFD.Walk(ctx, d.name)
controlFD := d.parent.Load().impl.(*lisafsDentry).controlFD
inode, err := controlFD.Walk(ctx, d.name)
if err != nil {
return err
if !dt.isDir() || !dt.forMountpoint {
return err
}
// Recreate directories that were created during volume mounting, since
// during restore we don't attempt to remount them.
inode, err = controlFD.MkdirAt(ctx, d.name, linux.FileMode(d.mode.Load()), lisafs.UID(d.uid.Load()), lisafs.GID(d.gid.Load()))
if err != nil {
return err
}
}
return dt.restoreFile(ctx, &inode, opts)
case *directfsDentry:
controlFD := d.parent.Load().impl.(*directfsDentry).controlFD
childFD, err := tryOpen(func(flags int) (int, error) {
return unix.Openat(d.parent.Load().impl.(*directfsDentry).controlFD, d.name, flags, 0)
n, err := unix.Openat(controlFD, d.name, flags, 0)
return n, err
})
if err != nil {
return err
if !dt.isDir() || !dt.forMountpoint {
return err
}
// Recreate directories that were created during volume mounting, since
// during restore we don't attempt to remount them.
if err := unix.Mkdirat(controlFD, d.name, d.mode.Load()); err != nil {
return err
}
// Try again...
childFD, err = tryOpen(func(flags int) (int, error) {
return unix.Openat(controlFD, d.name, flags, 0)
})
if err != nil {
return err
}
}
return dt.restoreFile(ctx, childFD, opts)
default:
panic("unknown dentry implementation")
}
+1
View File
@@ -842,6 +842,7 @@ func (fs *filesystem) MkdirAt(ctx context.Context, rp *vfs.ResolvingPath, opts v
if fs.opts.interop != InteropModeShared {
parent.incLinks()
}
child.forMountpoint = opts.ForSyntheticMountpoint
return child, nil
}
+5
View File
@@ -996,6 +996,11 @@ type dentry struct {
// a more in-depth discussion on this matter).
watches vfs.Watches
// forMountpoint marks directories that were created for mount points during
// container startup. This is used during restore, in case these mount points
// need to be recreated.
forMountpoint bool
// impl is the specific dentry implementation for non-synthetic dentries.
// impl is immutable.
//
+83
View File
@@ -1278,6 +1278,89 @@ func TestCheckpointRestoreExecKilled(t *testing.T) {
}
}
// TestCheckpointRestoreCreateMountPoint tests that mountpoints created during
// container creation are re-created after checkpoint/restore.
func TestCheckpointRestoreCreateMountPoint(t *testing.T) {
dir, err := os.MkdirTemp(testutil.TmpDir(), "checkpoint-test")
if err != nil {
t.Fatalf("os.MkdirTemp() failed: %v", err)
}
defer os.RemoveAll(dir)
if err := os.Chmod(dir, 0777); err != nil {
t.Fatalf("error chmoding file: %q, %v", dir, err)
}
spec, conf := sleepSpecConf(t)
mountDest := filepath.Join(dir, "/foo-dir")
spec.Mounts = append(spec.Mounts, specs.Mount{
Destination: mountDest,
Type: "tmpfs",
})
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
t.Fatalf("error setting up container: %v", err)
}
defer cleanup()
// Create and start the container.
args := Args{
ID: testutil.RandomContainerID(),
Spec: spec,
BundleDir: bundleDir,
}
cont, err := New(conf, args)
if err != nil {
t.Fatalf("error creating container: %v", err)
}
defer cont.Destroy()
if err := cont.Start(conf); err != nil {
t.Fatalf("error starting container: %v", err)
}
if err := waitForProcessCount(cont, 1); err != nil {
t.Fatal(err)
}
// Check that mount point was created.
if ws, err := execute(conf, cont, "/usr/bin/test", "-d", mountDest); err != nil {
t.Fatal(err)
} else if ws != 0 {
t.Fatalf("directory was not re-created upon restore, ws: %v", ws)
}
// Checkpoint running container; save state into new file.
if err := cont.Checkpoint(dir, false, statefile.Options{Compression: statefile.CompressionLevelDefault}, pgalloc.SaveOpts{}); err != nil {
t.Fatalf("error checkpointing container to file: %v", err)
}
// Remove directory created by the container.
if err := os.RemoveAll(mountDest); err != nil {
t.Fatalf("error removing mount point directory: %v", err)
}
// Destroy the original container to restore it in place.
cont.Destroy()
cont = nil
cont2, err := New(conf, args)
if err != nil {
t.Fatalf("error creating container: %v", err)
}
defer cont2.Destroy()
if err := cont2.Restore(conf, dir, false /* direct */); err != nil {
t.Fatalf("error restoring container: %v", err)
}
// Check that mount point was re-created after restore.
if ws, err := execute(conf, cont2, "/usr/bin/test", "-d", mountDest); err != nil {
t.Fatal(err)
} else if ws != 0 {
t.Fatalf("directory was not re-created upon restore, ws: %v", ws)
}
}
// TestUnixDomainSockets checks that Checkpoint/Restore works in cases
// with filesystem Unix Domain Socket use.
func TestUnixDomainSockets(t *testing.T) {