github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/runsc/cmd/checkpoint.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cmd 16 17 import ( 18 "context" 19 "os" 20 "path/filepath" 21 22 "github.com/google/subcommands" 23 "github.com/ttpreport/gvisor-ligolo/pkg/log" 24 "github.com/ttpreport/gvisor-ligolo/runsc/cmd/util" 25 "github.com/ttpreport/gvisor-ligolo/runsc/config" 26 "github.com/ttpreport/gvisor-ligolo/runsc/container" 27 "github.com/ttpreport/gvisor-ligolo/runsc/flag" 28 "github.com/ttpreport/gvisor-ligolo/runsc/specutils" 29 "golang.org/x/sys/unix" 30 ) 31 32 // File containing the container's saved image/state within the given image-path's directory. 33 const checkpointFileName = "checkpoint.img" 34 35 // Checkpoint implements subcommands.Command for the "checkpoint" command. 36 type Checkpoint struct { 37 imagePath string 38 leaveRunning bool 39 } 40 41 // Name implements subcommands.Command.Name. 42 func (*Checkpoint) Name() string { 43 return "checkpoint" 44 } 45 46 // Synopsis implements subcommands.Command.Synopsis. 47 func (*Checkpoint) Synopsis() string { 48 return "checkpoint current state of container (experimental)" 49 } 50 51 // Usage implements subcommands.Command.Usage. 52 func (*Checkpoint) Usage() string { 53 return `checkpoint [flags] <container id> - save current state of container. 54 ` 55 } 56 57 // SetFlags implements subcommands.Command.SetFlags. 58 func (c *Checkpoint) SetFlags(f *flag.FlagSet) { 59 f.StringVar(&c.imagePath, "image-path", "", "directory path to saved container image") 60 f.BoolVar(&c.leaveRunning, "leave-running", false, "restart the container after checkpointing") 61 62 // Unimplemented flags necessary for compatibility with docker. 63 var wp string 64 f.StringVar(&wp, "work-path", "", "ignored") 65 } 66 67 // Execute implements subcommands.Command.Execute. 68 func (c *Checkpoint) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus { 69 if f.NArg() != 1 { 70 f.Usage() 71 return subcommands.ExitUsageError 72 } 73 74 id := f.Arg(0) 75 conf := args[0].(*config.Config) 76 waitStatus := args[1].(*unix.WaitStatus) 77 78 cont, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, container.LoadOpts{}) 79 if err != nil { 80 util.Fatalf("loading container: %v", err) 81 } 82 83 if c.imagePath == "" { 84 util.Fatalf("image-path flag must be provided") 85 } 86 87 if err := os.MkdirAll(c.imagePath, 0755); err != nil { 88 util.Fatalf("making directories at path provided: %v", err) 89 } 90 91 fullImagePath := filepath.Join(c.imagePath, checkpointFileName) 92 93 // Create the image file and open for writing. 94 file, err := os.OpenFile(fullImagePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644) 95 if err != nil { 96 util.Fatalf("os.OpenFile(%q) failed: %v", fullImagePath, err) 97 } 98 defer file.Close() 99 100 if err := cont.Checkpoint(file); err != nil { 101 util.Fatalf("checkpoint failed: %v", err) 102 } 103 104 if !c.leaveRunning { 105 return subcommands.ExitSuccess 106 } 107 108 // TODO(b/110843694): Make it possible to restore into same container. 109 // For now, we can fake it by destroying the container and making a 110 // new container with the same ID. This hack does not work with docker 111 // which uses the container pid to ensure that the restore-container is 112 // actually the same as the checkpoint-container. By restoring into 113 // the same container, we will solve the docker incompatibility. 114 115 // Restore into new container with same ID. 116 bundleDir := cont.BundleDir 117 if bundleDir == "" { 118 util.Fatalf("setting bundleDir") 119 } 120 121 spec, err := specutils.ReadSpec(bundleDir, conf) 122 if err != nil { 123 util.Fatalf("reading spec: %v", err) 124 } 125 126 specutils.LogSpecDebug(spec, conf.OCISeccomp) 127 128 if cont.ConsoleSocket != "" { 129 log.Warningf("ignoring console socket since it cannot be restored") 130 } 131 132 if err := cont.Destroy(); err != nil { 133 util.Fatalf("destroying container: %v", err) 134 } 135 136 contArgs := container.Args{ 137 ID: id, 138 Spec: spec, 139 BundleDir: bundleDir, 140 } 141 cont, err = container.New(conf, contArgs) 142 if err != nil { 143 util.Fatalf("restoring container: %v", err) 144 } 145 defer cont.Destroy() 146 147 if err := cont.Restore(conf, fullImagePath); err != nil { 148 util.Fatalf("starting container: %v", err) 149 } 150 151 ws, err := cont.Wait() 152 if err != nil { 153 util.Fatalf("Error waiting for container: %v", err) 154 } 155 *waitStatus = ws 156 157 return subcommands.ExitSuccess 158 }