mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
f66ace4cfa
go-criu v8.3.0 switches to protobuf-go-lite, which helps to remove google.golang.org/protobuf dependency from here, reducing the runc binary size from ~16M to ~14M. The only missing piece is proto.String, proto.Bool, proto.Int32 etc. helpers that return a pointer to a given variable. Those are replaced by a generic mkPtr, which in turn is to be replaced by the new builtin once Go < 1.26 is no longer supported. Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
340 lines
7.5 KiB
Go
340 lines
7.5 KiB
Go
package criu
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"sort"
|
|
"strconv"
|
|
"syscall"
|
|
|
|
proto "github.com/checkpoint-restore/go-criu/v8/internal/proto"
|
|
"github.com/checkpoint-restore/go-criu/v8/rpc"
|
|
)
|
|
|
|
// extraFilesStartFd is the first fd number assigned to cmd.ExtraFiles by os/exec.
|
|
// As documented in os/exec: "entry i becomes file descriptor 3+i"
|
|
const extraFilesStartFd = 3
|
|
|
|
// Criu struct
|
|
type Criu struct {
|
|
swrkCmd *exec.Cmd
|
|
swrkSk *os.File
|
|
swrkPath string
|
|
inheritFds map[string]*os.File
|
|
}
|
|
|
|
// MakeCriu returns the Criu object required for most operations
|
|
func MakeCriu() *Criu {
|
|
return &Criu{
|
|
swrkPath: "criu",
|
|
}
|
|
}
|
|
|
|
// SetCriuPath allows setting the path to the CRIU binary
|
|
// if it is in a non standard location
|
|
func (c *Criu) SetCriuPath(path string) {
|
|
c.swrkPath = path
|
|
}
|
|
|
|
// AddInheritFd registers a file descriptor to be passed to CRIU.
|
|
// If opts.InheritFd is not set for an operation, it will be populated
|
|
// from these registrations using the same key order.
|
|
func (c *Criu) AddInheritFd(key string, file *os.File) {
|
|
if c.inheritFds == nil {
|
|
c.inheritFds = make(map[string]*os.File)
|
|
}
|
|
c.inheritFds[key] = file
|
|
}
|
|
|
|
func (c *Criu) inheritFdKeys() []string {
|
|
if len(c.inheritFds) == 0 {
|
|
return nil
|
|
}
|
|
keys := make([]string, 0, len(c.inheritFds))
|
|
for k := range c.inheritFds {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
return keys
|
|
}
|
|
|
|
func (c *Criu) ensureInheritFd(opts *rpc.CriuOpts) {
|
|
if opts == nil || len(opts.GetInheritFd()) > 0 || len(c.inheritFds) == 0 {
|
|
return
|
|
}
|
|
keys := c.inheritFdKeys()
|
|
if len(keys) == 0 {
|
|
return
|
|
}
|
|
opts.InheritFd = make([]*rpc.InheritFd, 0, len(keys))
|
|
for i, key := range keys {
|
|
fd := int32(extraFilesStartFd + i)
|
|
opts.InheritFd = append(opts.InheritFd, &rpc.InheritFd{
|
|
Key: proto.Ptr(key),
|
|
Fd: proto.Ptr(fd),
|
|
})
|
|
}
|
|
}
|
|
|
|
// Prepare sets up everything for the RPC communication to CRIU
|
|
func (c *Criu) Prepare() error {
|
|
return c.doPrepare(nil)
|
|
}
|
|
|
|
func (c *Criu) doPrepare(opts *rpc.CriuOpts) error {
|
|
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_SEQPACKET, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cln := os.NewFile(uintptr(fds[0]), "criu-xprt-cln")
|
|
syscall.CloseOnExec(fds[0])
|
|
srv := os.NewFile(uintptr(fds[1]), "criu-xprt-srv")
|
|
defer func() { _ = srv.Close() }()
|
|
|
|
args := []string{"swrk", strconv.Itoa(fds[1])}
|
|
// #nosec G204
|
|
cmd := exec.Command(c.swrkPath, args...)
|
|
|
|
// Collect file descriptors to pass to child
|
|
inheritKeys := c.inheritFdKeys()
|
|
extraFiles := make([]*os.File, 0, len(inheritKeys))
|
|
|
|
// Add fds from AddInheritFd (sorted for stable ordering)
|
|
for _, k := range inheritKeys {
|
|
extraFiles = append(extraFiles, c.inheritFds[k])
|
|
}
|
|
|
|
c.ensureInheritFd(opts)
|
|
|
|
cmd.ExtraFiles = extraFiles
|
|
|
|
err = cmd.Start()
|
|
if err != nil {
|
|
_ = cln.Close()
|
|
return err
|
|
}
|
|
|
|
c.swrkCmd = cmd
|
|
c.swrkSk = cln
|
|
|
|
return nil
|
|
}
|
|
|
|
// Cleanup cleans up
|
|
func (c *Criu) Cleanup() error {
|
|
var errs []error
|
|
if c.swrkCmd != nil {
|
|
if err := c.swrkSk.Close(); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
c.swrkSk = nil
|
|
if err := c.swrkCmd.Wait(); err != nil {
|
|
errs = append(errs, fmt.Errorf("criu swrk failed: %w", err))
|
|
}
|
|
c.swrkCmd = nil
|
|
}
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
func (c *Criu) sendAndRecv(reqB []byte) ([]byte, int, error) {
|
|
cln := c.swrkSk
|
|
_, err := cln.Write(reqB)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
respB := make([]byte, 2*4096)
|
|
n, err := cln.Read(respB)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return respB, n, nil
|
|
}
|
|
|
|
func (c *Criu) doSwrk(reqType rpc.CriuReqType, opts *rpc.CriuOpts, nfy Notify) error {
|
|
resp, err := c.doSwrkWithResp(reqType, opts, nfy, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
respType := resp.GetType()
|
|
if respType != reqType {
|
|
return errors.New("unexpected CRIU RPC response")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Criu) doSwrkWithResp(reqType rpc.CriuReqType, opts *rpc.CriuOpts, nfy Notify, features *rpc.CriuFeatures) (resp *rpc.CriuResp, retErr error) {
|
|
c.ensureInheritFd(opts)
|
|
|
|
req := rpc.CriuReq{
|
|
Type: &reqType,
|
|
Opts: opts,
|
|
}
|
|
|
|
if nfy != nil {
|
|
opts.NotifyScripts = proto.Ptr(true)
|
|
}
|
|
|
|
if features != nil {
|
|
req.Features = features
|
|
}
|
|
|
|
if c.swrkCmd == nil {
|
|
err := c.doPrepare(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func() {
|
|
// append any cleanup errors to the returned error
|
|
err := c.Cleanup()
|
|
if err != nil {
|
|
retErr = errors.Join(retErr, err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
for {
|
|
reqB, err := req.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
respB, respS, err := c.sendAndRecv(reqB)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp = &rpc.CriuResp{}
|
|
err = resp.UnmarshalVT(respB[:respS])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !resp.GetSuccess() {
|
|
return resp, fmt.Errorf("operation failed (msg:%s err:%d)",
|
|
resp.GetCrErrmsg(), resp.GetCrErrno())
|
|
}
|
|
|
|
respType := resp.GetType()
|
|
if respType != rpc.CriuReqType_NOTIFY {
|
|
break
|
|
}
|
|
if nfy == nil {
|
|
return resp, errors.New("unexpected notify")
|
|
}
|
|
|
|
notify := resp.GetNotify()
|
|
switch notify.GetScript() {
|
|
case "pre-dump":
|
|
err = nfy.PreDump()
|
|
case "post-dump":
|
|
err = nfy.PostDump()
|
|
case "pre-restore":
|
|
err = nfy.PreRestore()
|
|
case "post-restore":
|
|
err = nfy.PostRestore(notify.GetPid())
|
|
case "network-lock":
|
|
err = nfy.NetworkLock()
|
|
case "network-unlock":
|
|
err = nfy.NetworkUnlock()
|
|
case "setup-namespaces":
|
|
err = nfy.SetupNamespaces(notify.GetPid())
|
|
case "post-setup-namespaces":
|
|
err = nfy.PostSetupNamespaces()
|
|
case "post-resume":
|
|
err = nfy.PostResume()
|
|
default:
|
|
err = nil
|
|
}
|
|
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
|
|
req = rpc.CriuReq{
|
|
Type: &respType,
|
|
NotifySuccess: proto.Ptr(true),
|
|
}
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// Dump dumps a process
|
|
func (c *Criu) Dump(opts *rpc.CriuOpts, nfy Notify) error {
|
|
return c.doSwrk(rpc.CriuReqType_DUMP, opts, nfy)
|
|
}
|
|
|
|
// Restore restores a process
|
|
func (c *Criu) Restore(opts *rpc.CriuOpts, nfy Notify) error {
|
|
return c.doSwrk(rpc.CriuReqType_RESTORE, opts, nfy)
|
|
}
|
|
|
|
// PreDump does a pre-dump
|
|
func (c *Criu) PreDump(opts *rpc.CriuOpts, nfy Notify) error {
|
|
return c.doSwrk(rpc.CriuReqType_PRE_DUMP, opts, nfy)
|
|
}
|
|
|
|
// StartPageServer starts the page server
|
|
func (c *Criu) StartPageServer(opts *rpc.CriuOpts) error {
|
|
return c.doSwrk(rpc.CriuReqType_PAGE_SERVER, opts, nil)
|
|
}
|
|
|
|
// StartPageServerChld starts the page server and returns PID and port
|
|
func (c *Criu) StartPageServerChld(opts *rpc.CriuOpts) (int, int, error) {
|
|
resp, err := c.doSwrkWithResp(rpc.CriuReqType_PAGE_SERVER_CHLD, opts, nil, nil)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
return int(resp.GetPs().GetPid()), int(resp.GetPs().GetPort()), nil
|
|
}
|
|
|
|
// GetCriuVersion executes the VERSION RPC call and returns the version
|
|
// as an integer. Major * 10000 + Minor * 100 + SubLevel
|
|
func (c *Criu) GetCriuVersion() (int, error) {
|
|
resp, err := c.doSwrkWithResp(rpc.CriuReqType_VERSION, nil, nil, nil)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if resp.GetType() != rpc.CriuReqType_VERSION {
|
|
return 0, errors.New("unexpected CRIU RPC response")
|
|
}
|
|
|
|
version := resp.GetVersion().GetMajorNumber() * 10000
|
|
version += resp.GetVersion().GetMinorNumber() * 100
|
|
if resp.GetVersion().GetSublevel() != 0 {
|
|
version += resp.GetVersion().GetSublevel()
|
|
}
|
|
|
|
if resp.GetVersion().GetGitid() != "" {
|
|
// taken from runc: if it is a git release -> increase minor by 1
|
|
version -= (version % 100)
|
|
version += 100
|
|
}
|
|
|
|
return int(version), nil
|
|
}
|
|
|
|
// IsCriuAtLeast checks if the version is at least the same
|
|
// as the parameter version
|
|
func (c *Criu) IsCriuAtLeast(version int) (bool, error) {
|
|
criuVersion, err := c.GetCriuVersion()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if criuVersion >= version {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|