From 5734bf093c6e92ca7a28a78b8ef7c43f850eced6 Mon Sep 17 00:00:00 2001 From: Fabricio Voznika Date: Tue, 18 Jun 2024 14:36:58 -0700 Subject: [PATCH] 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 --- pkg/sentry/fsimpl/gofer/dentry_impl.go | 39 ++++++++++-- pkg/sentry/fsimpl/gofer/filesystem.go | 1 + pkg/sentry/fsimpl/gofer/gofer.go | 5 ++ runsc/container/container_test.go | 83 ++++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 4 deletions(-) diff --git a/pkg/sentry/fsimpl/gofer/dentry_impl.go b/pkg/sentry/fsimpl/gofer/dentry_impl.go index a1a62fe1f..8ebb4bb2c 100644 --- a/pkg/sentry/fsimpl/gofer/dentry_impl.go +++ b/pkg/sentry/fsimpl/gofer/dentry_impl.go @@ -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") } diff --git a/pkg/sentry/fsimpl/gofer/filesystem.go b/pkg/sentry/fsimpl/gofer/filesystem.go index ca31da957..ec60c1401 100644 --- a/pkg/sentry/fsimpl/gofer/filesystem.go +++ b/pkg/sentry/fsimpl/gofer/filesystem.go @@ -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 } diff --git a/pkg/sentry/fsimpl/gofer/gofer.go b/pkg/sentry/fsimpl/gofer/gofer.go index dcdca6f7e..c5a3a76b3 100644 --- a/pkg/sentry/fsimpl/gofer/gofer.go +++ b/pkg/sentry/fsimpl/gofer/gofer.go @@ -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. // diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go index bdebd8c0f..ee08060ee 100644 --- a/runsc/container/container_test.go +++ b/runsc/container/container_test.go @@ -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) {