From 42046a3fdea2cb7072fec93d1c1eb3eaec5f681e Mon Sep 17 00:00:00 2001 From: Radostin Stoyanov Date: Thu, 13 Nov 2025 13:56:19 +0000 Subject: [PATCH] Enable checkpoint/restore support with gVisor This patch extends Podman to support checkpoint and restore operations with gVisor. In contrast to runc and crun, gVisor (runsc) does not rely on CRIU for checkpointing. Instead, it implements its own checkpoint and restore mechanism while maintaining a CLI interface compatible with runc. There are two main differences that this patch accounts for: "runsc checkpoint --help" exits with code 128 (subcommands.ExitUsageError), and it stores all checkpoint data in a single "checkpoint.img" file. Signed-off-by: Radostin Stoyanov --- libpod/container_internal_common.go | 14 ++++++++++++-- .../crutils/checkpoint_restore_utils.go | 15 ++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/libpod/container_internal_common.go b/libpod/container_internal_common.go index acaa76ef3ae..d40d65a914c 100644 --- a/libpod/container_internal_common.go +++ b/libpod/container_internal_common.go @@ -1569,8 +1569,18 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti // Let's try to stat() CRIU's inventory file. If it does not exist, it makes // no sense to try a restore. This is a minimal check if a checkpoint exists. - if err := fileutils.Exists(filepath.Join(c.CheckpointPath(), "inventory.img")); errors.Is(err, fs.ErrNotExist) { - return nil, 0, fmt.Errorf("a complete checkpoint for this container cannot be found, cannot restore: %w", err) + // For gVisor, the checkpoint image is called "checkpoint.img". + criuInventoryPath := filepath.Join(c.CheckpointPath(), "inventory.img") + gvisorCheckpointPath := filepath.Join(c.CheckpointPath(), "checkpoint.img") + + if err := fileutils.Exists(criuInventoryPath); errors.Is(err, fs.ErrNotExist) { + if err2 := fileutils.Exists(gvisorCheckpointPath); errors.Is(err2, fs.ErrNotExist) { + return nil, 0, fmt.Errorf("a complete checkpoint for this container cannot be found (checked %q and %q): %w", criuInventoryPath, gvisorCheckpointPath, err) + } else if err2 != nil { + return nil, 0, fmt.Errorf("error checking checkpoint file %q: %w", gvisorCheckpointPath, err2) + } + } else if err != nil { + return nil, 0, fmt.Errorf("error checking inventory file %q: %w", criuInventoryPath, err) } if err := crutils.CRCreateFileWithLabel(c.bundlePath(), "restore.log", c.MountLabel()); err != nil { diff --git a/pkg/checkpoint/crutils/checkpoint_restore_utils.go b/pkg/checkpoint/crutils/checkpoint_restore_utils.go index 1709429bad8..1c24e291db7 100644 --- a/pkg/checkpoint/crutils/checkpoint_restore_utils.go +++ b/pkg/checkpoint/crutils/checkpoint_restore_utils.go @@ -216,9 +216,22 @@ func CRRuntimeSupportsCheckpointRestore(runtimePath string) bool { if err := cmd.Start(); err != nil { return false } - if err := cmd.Wait(); err == nil { + + err := cmd.Wait() + if err == nil { + // Exited with status 0 means definitely supported. return true } + + if exitErr, ok := err.(*exec.ExitError); ok { + status := exitErr.ExitCode() + // "runsc checkpoint --help" exits with 128 (subcommands.ExitUsageError) + // and 127 is used for "command not found". + if status == 128 { + return true + } + } + return false }