main.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. package criu
  2. import (
  3. "errors"
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "strconv"
  8. "syscall"
  9. "github.com/checkpoint-restore/go-criu/v5/rpc"
  10. "google.golang.org/protobuf/proto"
  11. )
  12. // Criu struct
  13. type Criu struct {
  14. swrkCmd *exec.Cmd
  15. swrkSk *os.File
  16. swrkPath string
  17. }
  18. // MakeCriu returns the Criu object required for most operations
  19. func MakeCriu() *Criu {
  20. return &Criu{
  21. swrkPath: "criu",
  22. }
  23. }
  24. // SetCriuPath allows setting the path to the CRIU binary
  25. // if it is in a non standard location
  26. func (c *Criu) SetCriuPath(path string) {
  27. c.swrkPath = path
  28. }
  29. // Prepare sets up everything for the RPC communication to CRIU
  30. func (c *Criu) Prepare() error {
  31. fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_SEQPACKET, 0)
  32. if err != nil {
  33. return err
  34. }
  35. cln := os.NewFile(uintptr(fds[0]), "criu-xprt-cln")
  36. syscall.CloseOnExec(fds[0])
  37. srv := os.NewFile(uintptr(fds[1]), "criu-xprt-srv")
  38. defer srv.Close()
  39. args := []string{"swrk", strconv.Itoa(fds[1])}
  40. // #nosec G204
  41. cmd := exec.Command(c.swrkPath, args...)
  42. err = cmd.Start()
  43. if err != nil {
  44. cln.Close()
  45. return err
  46. }
  47. c.swrkCmd = cmd
  48. c.swrkSk = cln
  49. return nil
  50. }
  51. // Cleanup cleans up
  52. func (c *Criu) Cleanup() {
  53. if c.swrkCmd != nil {
  54. c.swrkSk.Close()
  55. c.swrkSk = nil
  56. _ = c.swrkCmd.Wait()
  57. c.swrkCmd = nil
  58. }
  59. }
  60. func (c *Criu) sendAndRecv(reqB []byte) ([]byte, int, error) {
  61. cln := c.swrkSk
  62. _, err := cln.Write(reqB)
  63. if err != nil {
  64. return nil, 0, err
  65. }
  66. respB := make([]byte, 2*4096)
  67. n, err := cln.Read(respB)
  68. if err != nil {
  69. return nil, 0, err
  70. }
  71. return respB, n, nil
  72. }
  73. func (c *Criu) doSwrk(reqType rpc.CriuReqType, opts *rpc.CriuOpts, nfy Notify) error {
  74. resp, err := c.doSwrkWithResp(reqType, opts, nfy, nil)
  75. if err != nil {
  76. return err
  77. }
  78. respType := resp.GetType()
  79. if respType != reqType {
  80. return errors.New("unexpected CRIU RPC response")
  81. }
  82. return nil
  83. }
  84. func (c *Criu) doSwrkWithResp(reqType rpc.CriuReqType, opts *rpc.CriuOpts, nfy Notify, features *rpc.CriuFeatures) (*rpc.CriuResp, error) {
  85. var resp *rpc.CriuResp
  86. req := rpc.CriuReq{
  87. Type: &reqType,
  88. Opts: opts,
  89. }
  90. if nfy != nil {
  91. opts.NotifyScripts = proto.Bool(true)
  92. }
  93. if features != nil {
  94. req.Features = features
  95. }
  96. if c.swrkCmd == nil {
  97. err := c.Prepare()
  98. if err != nil {
  99. return nil, err
  100. }
  101. defer c.Cleanup()
  102. }
  103. for {
  104. reqB, err := proto.Marshal(&req)
  105. if err != nil {
  106. return nil, err
  107. }
  108. respB, respS, err := c.sendAndRecv(reqB)
  109. if err != nil {
  110. return nil, err
  111. }
  112. resp = &rpc.CriuResp{}
  113. err = proto.Unmarshal(respB[:respS], resp)
  114. if err != nil {
  115. return nil, err
  116. }
  117. if !resp.GetSuccess() {
  118. return resp, fmt.Errorf("operation failed (msg:%s err:%d)",
  119. resp.GetCrErrmsg(), resp.GetCrErrno())
  120. }
  121. respType := resp.GetType()
  122. if respType != rpc.CriuReqType_NOTIFY {
  123. break
  124. }
  125. if nfy == nil {
  126. return resp, errors.New("unexpected notify")
  127. }
  128. notify := resp.GetNotify()
  129. switch notify.GetScript() {
  130. case "pre-dump":
  131. err = nfy.PreDump()
  132. case "post-dump":
  133. err = nfy.PostDump()
  134. case "pre-restore":
  135. err = nfy.PreRestore()
  136. case "post-restore":
  137. err = nfy.PostRestore(notify.GetPid())
  138. case "network-lock":
  139. err = nfy.NetworkLock()
  140. case "network-unlock":
  141. err = nfy.NetworkUnlock()
  142. case "setup-namespaces":
  143. err = nfy.SetupNamespaces(notify.GetPid())
  144. case "post-setup-namespaces":
  145. err = nfy.PostSetupNamespaces()
  146. case "post-resume":
  147. err = nfy.PostResume()
  148. default:
  149. err = nil
  150. }
  151. if err != nil {
  152. return resp, err
  153. }
  154. req = rpc.CriuReq{
  155. Type: &respType,
  156. NotifySuccess: proto.Bool(true),
  157. }
  158. }
  159. return resp, nil
  160. }
  161. // Dump dumps a process
  162. func (c *Criu) Dump(opts *rpc.CriuOpts, nfy Notify) error {
  163. return c.doSwrk(rpc.CriuReqType_DUMP, opts, nfy)
  164. }
  165. // Restore restores a process
  166. func (c *Criu) Restore(opts *rpc.CriuOpts, nfy Notify) error {
  167. return c.doSwrk(rpc.CriuReqType_RESTORE, opts, nfy)
  168. }
  169. // PreDump does a pre-dump
  170. func (c *Criu) PreDump(opts *rpc.CriuOpts, nfy Notify) error {
  171. return c.doSwrk(rpc.CriuReqType_PRE_DUMP, opts, nfy)
  172. }
  173. // StartPageServer starts the page server
  174. func (c *Criu) StartPageServer(opts *rpc.CriuOpts) error {
  175. return c.doSwrk(rpc.CriuReqType_PAGE_SERVER, opts, nil)
  176. }
  177. // StartPageServerChld starts the page server and returns PID and port
  178. func (c *Criu) StartPageServerChld(opts *rpc.CriuOpts) (int, int, error) {
  179. resp, err := c.doSwrkWithResp(rpc.CriuReqType_PAGE_SERVER_CHLD, opts, nil, nil)
  180. if err != nil {
  181. return 0, 0, err
  182. }
  183. return int(resp.Ps.GetPid()), int(resp.Ps.GetPort()), nil
  184. }
  185. // GetCriuVersion executes the VERSION RPC call and returns the version
  186. // as an integer. Major * 10000 + Minor * 100 + SubLevel
  187. func (c *Criu) GetCriuVersion() (int, error) {
  188. resp, err := c.doSwrkWithResp(rpc.CriuReqType_VERSION, nil, nil, nil)
  189. if err != nil {
  190. return 0, err
  191. }
  192. if resp.GetType() != rpc.CriuReqType_VERSION {
  193. return 0, fmt.Errorf("Unexpected CRIU RPC response")
  194. }
  195. version := int(*resp.GetVersion().MajorNumber) * 10000
  196. version += int(*resp.GetVersion().MinorNumber) * 100
  197. if resp.GetVersion().Sublevel != nil {
  198. version += int(*resp.GetVersion().Sublevel)
  199. }
  200. if resp.GetVersion().Gitid != nil {
  201. // taken from runc: if it is a git release -> increase minor by 1
  202. version -= (version % 100)
  203. version += 100
  204. }
  205. return version, nil
  206. }
  207. // IsCriuAtLeast checks if the version is at least the same
  208. // as the parameter version
  209. func (c *Criu) IsCriuAtLeast(version int) (bool, error) {
  210. criuVersion, err := c.GetCriuVersion()
  211. if err != nil {
  212. return false, err
  213. }
  214. if criuVersion >= version {
  215. return true, nil
  216. }
  217. return false, nil
  218. }