github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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 "golang.org/x/sys/unix" 24 "github.com/SagerNet/gvisor/pkg/log" 25 "github.com/SagerNet/gvisor/runsc/config" 26 "github.com/SagerNet/gvisor/runsc/container" 27 "github.com/SagerNet/gvisor/runsc/flag" 28 "github.com/SagerNet/gvisor/runsc/specutils" 29 ) 30 31 // File containing the container's saved image/state within the given image-path's directory. 32 const checkpointFileName = "checkpoint.img" 33 34 // Checkpoint implements subcommands.Command for the "checkpoint" command. 35 type Checkpoint struct { 36 imagePath string 37 leaveRunning bool 38 } 39 40 // Name implements subcommands.Command.Name. 41 func (*Checkpoint) Name() string { 42 return "checkpoint" 43 } 44 45 // Synopsis implements subcommands.Command.Synopsis. 46 func (*Checkpoint) Synopsis() string { 47 return "checkpoint current state of container (experimental)" 48 } 49 50 // Usage implements subcommands.Command.Usage. 51 func (*Checkpoint) Usage() string { 52 return `checkpoint [flags] <container id> - save current state of container. 53 ` 54 } 55 56 // SetFlags implements subcommands.Command.SetFlags. 57 func (c *Checkpoint) SetFlags(f *flag.FlagSet) { 58 f.StringVar(&c.imagePath, "image-path", "", "directory path to saved container image") 59 f.BoolVar(&c.leaveRunning, "leave-running", false, "restart the container after checkpointing") 60 61 // Unimplemented flags necessary for compatibility with docker. 62 var wp string 63 f.StringVar(&wp, "work-path", "", "ignored") 64 } 65 66 // Execute implements subcommands.Command.Execute. 67 func (c *Checkpoint) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { 68 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 Fatalf("loading container: %v", err) 81 } 82 83 if c.imagePath == "" { 84 Fatalf("image-path flag must be provided") 85 } 86 87 if err := os.MkdirAll(c.imagePath, 0755); err != nil { 88 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 Fatalf("os.OpenFile(%q) failed: %v", fullImagePath, err) 97 } 98 defer file.Close() 99 100 if err := cont.Checkpoint(file); err != nil { 101 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 Fatalf("setting bundleDir") 119 } 120 121 spec, err := specutils.ReadSpec(bundleDir, conf) 122 if err != nil { 123 Fatalf("reading spec: %v", err) 124 } 125 126 specutils.LogSpec(spec) 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 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 Fatalf("restoring container: %v", err) 144 } 145 defer cont.Destroy() 146 147 if err := cont.Restore(spec, conf, fullImagePath); err != nil { 148 Fatalf("starting container: %v", err) 149 } 150 151 ws, err := cont.Wait() 152 if err != nil { 153 Fatalf("Error waiting for container: %v", err) 154 } 155 *waitStatus = ws 156 157 return subcommands.ExitSuccess 158 }