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) {