1
0
mirror of https://github.com/opencontainers/runc.git synced 2025-04-18 19:44:09 +03:00
runc/checkpoint.go
Kir Kolyshkin 9510ffb658 Fix a few staticcheck QF1001 warnings
Like these:

> libcontainer/criu_linux.go:959:3: QF1001: could apply De Morgan's law (staticcheck)
> 		!(req.GetType() == criurpc.CriuReqType_FEATURE_CHECK ||
> 		^
> libcontainer/rootfs_linux.go:360:19: QF1001: could apply De Morgan's law (staticcheck)
> 	if err == nil || !(errors.Is(err, unix.EPERM) || errors.Is(err, unix.EBUSY)) {
> 	                 ^

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-03-25 16:06:44 -07:00

180 lines
6.0 KiB
Go

package main
import (
"errors"
"fmt"
"net"
"os"
"path/filepath"
"strconv"
"github.com/moby/sys/userns"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"golang.org/x/sys/unix"
"github.com/opencontainers/runc/libcontainer"
)
var checkpointCommand = cli.Command{
Name: "checkpoint",
Usage: "checkpoint a running container",
ArgsUsage: `<container-id>
Where "<container-id>" is the name for the instance of the container to be
checkpointed.`,
Description: `The checkpoint command saves the state of the container instance.`,
Flags: []cli.Flag{
cli.StringFlag{Name: "image-path", Value: "", Usage: "path for saving criu image files"},
cli.StringFlag{Name: "work-path", Value: "", Usage: "path for saving work files and logs"},
cli.StringFlag{Name: "parent-path", Value: "", Usage: "path for previous criu image files in pre-dump"},
cli.BoolFlag{Name: "leave-running", Usage: "leave the process running after checkpointing"},
cli.BoolFlag{Name: "tcp-established", Usage: "allow open tcp connections"},
cli.BoolFlag{Name: "tcp-skip-in-flight", Usage: "skip in-flight tcp connections"},
cli.BoolFlag{Name: "link-remap", Usage: "allow one to link unlinked files back when possible"},
cli.BoolFlag{Name: "ext-unix-sk", Usage: "allow external unix sockets"},
cli.BoolFlag{Name: "shell-job", Usage: "allow shell jobs"},
cli.BoolFlag{Name: "lazy-pages", Usage: "use userfaultfd to lazily restore memory pages"},
cli.IntFlag{Name: "status-fd", Value: -1, Usage: "criu writes \\0 to this FD once lazy-pages is ready"},
cli.StringFlag{Name: "page-server", Value: "", Usage: "ADDRESS:PORT of the page server"},
cli.BoolFlag{Name: "file-locks", Usage: "handle file locks, for safety"},
cli.BoolFlag{Name: "pre-dump", Usage: "dump container's memory information only, leave the container running after this"},
cli.StringFlag{Name: "manage-cgroups-mode", Value: "", Usage: "cgroups mode: soft|full|strict|ignore (default: soft)"},
cli.StringSliceFlag{Name: "empty-ns", Usage: "create a namespace, but don't restore its properties"},
cli.BoolFlag{Name: "auto-dedup", Usage: "enable auto deduplication of memory images"},
},
Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
// XXX: Currently this is untested with rootless containers.
if os.Geteuid() != 0 || userns.RunningInUserNS() {
logrus.Warn("runc checkpoint is untested with rootless containers")
}
container, err := getContainer(context)
if err != nil {
return err
}
status, err := container.Status()
if err != nil {
return err
}
if status == libcontainer.Created || status == libcontainer.Stopped {
return fmt.Errorf("Container cannot be checkpointed in %s state", status.String())
}
options, err := criuOptions(context)
if err != nil {
return err
}
err = container.Checkpoint(options)
if err == nil && !options.LeaveRunning && !options.PreDump {
// Destroy the container unless we tell CRIU to keep it.
if err := container.Destroy(); err != nil {
logrus.Warn(err)
}
}
return err
},
}
func prepareImagePaths(context *cli.Context) (string, string, error) {
imagePath := context.String("image-path")
if imagePath == "" {
imagePath = getDefaultImagePath()
}
if err := os.MkdirAll(imagePath, 0o600); err != nil {
return "", "", err
}
parentPath := context.String("parent-path")
if parentPath == "" {
return imagePath, parentPath, nil
}
if filepath.IsAbs(parentPath) {
return "", "", errors.New("--parent-path must be relative")
}
realParent := filepath.Join(imagePath, parentPath)
fi, err := os.Stat(realParent)
if err == nil && !fi.IsDir() {
err = &os.PathError{Path: realParent, Err: unix.ENOTDIR}
}
if err != nil {
return "", "", fmt.Errorf("invalid --parent-path: %w", err)
}
return imagePath, parentPath, nil
}
func criuOptions(context *cli.Context) (*libcontainer.CriuOpts, error) {
imagePath, parentPath, err := prepareImagePaths(context)
if err != nil {
return nil, err
}
opts := &libcontainer.CriuOpts{
ImagesDirectory: imagePath,
WorkDirectory: context.String("work-path"),
ParentImage: parentPath,
LeaveRunning: context.Bool("leave-running"),
TcpEstablished: context.Bool("tcp-established"),
TcpSkipInFlight: context.Bool("tcp-skip-in-flight"),
LinkRemap: context.Bool("link-remap"),
ExternalUnixConnections: context.Bool("ext-unix-sk"),
ShellJob: context.Bool("shell-job"),
FileLocks: context.Bool("file-locks"),
PreDump: context.Bool("pre-dump"),
AutoDedup: context.Bool("auto-dedup"),
LazyPages: context.Bool("lazy-pages"),
StatusFd: context.Int("status-fd"),
LsmProfile: context.String("lsm-profile"),
LsmMountContext: context.String("lsm-mount-context"),
ManageCgroupsMode: context.String("manage-cgroups-mode"),
}
// CRIU options below may or may not be set.
if psOpt := context.String("page-server"); psOpt != "" {
address, port, err := net.SplitHostPort(psOpt)
if err != nil || address == "" || port == "" {
return nil, errors.New("Use --page-server ADDRESS:PORT to specify page server")
}
portInt, err := strconv.Atoi(port)
if err != nil {
return nil, errors.New("Invalid port number")
}
opts.PageServer = libcontainer.CriuPageServerInfo{
Address: address,
Port: int32(portInt),
}
}
// runc doesn't manage network devices and their configuration.
nsmask := unix.CLONE_NEWNET
if context.IsSet("empty-ns") {
namespaceMapping := map[specs.LinuxNamespaceType]int{
specs.NetworkNamespace: unix.CLONE_NEWNET,
}
for _, ns := range context.StringSlice("empty-ns") {
f, exists := namespaceMapping[specs.LinuxNamespaceType(ns)]
if !exists {
return nil, fmt.Errorf("namespace %q is not supported", ns)
}
nsmask |= f
}
}
opts.EmptyNs = uint32(nsmask)
return opts, nil
}