github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/checkpoint/crutils/checkpoint_restore_utils.go (about) 1 package crutils 2 3 import ( 4 "bytes" 5 "io" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "path/filepath" 10 11 metadata "github.com/checkpoint-restore/checkpointctl/lib" 12 "github.com/checkpoint-restore/go-criu/v5/stats" 13 "github.com/containers/storage/pkg/archive" 14 "github.com/opencontainers/selinux/go-selinux/label" 15 "github.com/pkg/errors" 16 ) 17 18 // This file mainly exist to make the checkpoint/restore functions 19 // available for other users. One possible candidate would be CRI-O. 20 21 // CRImportCheckpointWithoutConfig imports the checkpoint archive (input) 22 // into the directory destination without "config.dump" and "spec.dump" 23 func CRImportCheckpointWithoutConfig(destination, input string) error { 24 archiveFile, err := os.Open(input) 25 if err != nil { 26 return errors.Wrapf(err, "Failed to open checkpoint archive %s for import", input) 27 } 28 29 defer archiveFile.Close() 30 options := &archive.TarOptions{ 31 ExcludePatterns: []string{ 32 // Import everything else besides the container config 33 metadata.ConfigDumpFile, 34 metadata.SpecDumpFile, 35 }, 36 } 37 if err = archive.Untar(archiveFile, destination, options); err != nil { 38 return errors.Wrapf(err, "Unpacking of checkpoint archive %s failed", input) 39 } 40 41 return nil 42 } 43 44 // CRImportCheckpointConfigOnly only imports the checkpoint configuration 45 // from the checkpoint archive (input) into the directory destination. 46 // Only the files "config.dump" and "spec.dump" are extracted. 47 func CRImportCheckpointConfigOnly(destination, input string) error { 48 archiveFile, err := os.Open(input) 49 if err != nil { 50 return errors.Wrapf(err, "Failed to open checkpoint archive %s for import", input) 51 } 52 53 defer archiveFile.Close() 54 options := &archive.TarOptions{ 55 // Here we only need the files config.dump and spec.dump 56 ExcludePatterns: []string{ 57 "ctr.log", 58 "artifacts", 59 stats.StatsDump, 60 metadata.RootFsDiffTar, 61 metadata.DeletedFilesFile, 62 metadata.NetworkStatusFile, 63 metadata.CheckpointDirectory, 64 metadata.CheckpointVolumesDirectory, 65 }, 66 } 67 if err = archive.Untar(archiveFile, destination, options); err != nil { 68 return errors.Wrapf(err, "Unpacking of checkpoint archive %s failed", input) 69 } 70 71 return nil 72 } 73 74 // CRRemoveDeletedFiles loads the list of deleted files and if 75 // it exists deletes all files listed. 76 func CRRemoveDeletedFiles(id, baseDirectory, containerRootDirectory string) error { 77 deletedFiles, _, err := metadata.ReadContainerCheckpointDeletedFiles(baseDirectory) 78 if os.IsNotExist(errors.Unwrap(errors.Unwrap(err))) { 79 // No files to delete. Just return 80 return nil 81 } 82 83 if err != nil { 84 return errors.Wrapf(err, "failed to read deleted files file") 85 } 86 87 for _, deleteFile := range deletedFiles { 88 // Using RemoveAll as deletedFiles, which is generated from 'podman diff' 89 // lists completely deleted directories as a single entry: 'D /root'. 90 if err := os.RemoveAll(filepath.Join(containerRootDirectory, deleteFile)); err != nil { 91 return errors.Wrapf(err, "failed to delete files from container %s during restore", id) 92 } 93 } 94 95 return nil 96 } 97 98 // CRApplyRootFsDiffTar applies the tar archive found in baseDirectory with the 99 // root file system changes on top of containerRootDirectory 100 func CRApplyRootFsDiffTar(baseDirectory, containerRootDirectory string) error { 101 rootfsDiffPath := filepath.Join(baseDirectory, metadata.RootFsDiffTar) 102 // Only do this if a rootfs-diff.tar actually exists 103 rootfsDiffFile, err := os.Open(rootfsDiffPath) 104 if err != nil { 105 if errors.Is(err, os.ErrNotExist) { 106 return nil 107 } 108 return errors.Wrap(err, "failed to open root file-system diff file") 109 } 110 defer rootfsDiffFile.Close() 111 112 if err := archive.Untar(rootfsDiffFile, containerRootDirectory, nil); err != nil { 113 return errors.Wrapf(err, "failed to apply root file-system diff file %s", rootfsDiffPath) 114 } 115 116 return nil 117 } 118 119 // CRCreateRootFsDiffTar goes through the 'changes' and can create two files: 120 // * metadata.RootFsDiffTar will contain all new and changed files 121 // * metadata.DeletedFilesFile will contain a list of deleted files 122 // With these two files it is possible to restore the container file system to the same 123 // state it was during checkpointing. 124 // Changes to directories (owner, mode) are not handled. 125 func CRCreateRootFsDiffTar(changes *[]archive.Change, mountPoint, destination string) (includeFiles []string, err error) { 126 if len(*changes) == 0 { 127 return includeFiles, nil 128 } 129 130 var rootfsIncludeFiles []string 131 var deletedFiles []string 132 133 rootfsDiffPath := filepath.Join(destination, metadata.RootFsDiffTar) 134 135 for _, file := range *changes { 136 if file.Kind == archive.ChangeAdd { 137 rootfsIncludeFiles = append(rootfsIncludeFiles, file.Path) 138 continue 139 } 140 if file.Kind == archive.ChangeDelete { 141 deletedFiles = append(deletedFiles, file.Path) 142 continue 143 } 144 fileName, err := os.Stat(file.Path) 145 if err != nil { 146 continue 147 } 148 if !fileName.IsDir() && file.Kind == archive.ChangeModify { 149 rootfsIncludeFiles = append(rootfsIncludeFiles, file.Path) 150 continue 151 } 152 } 153 154 if len(rootfsIncludeFiles) > 0 { 155 rootfsTar, err := archive.TarWithOptions(mountPoint, &archive.TarOptions{ 156 Compression: archive.Uncompressed, 157 IncludeSourceDir: true, 158 IncludeFiles: rootfsIncludeFiles, 159 }) 160 if err != nil { 161 return includeFiles, errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath) 162 } 163 rootfsDiffFile, err := os.Create(rootfsDiffPath) 164 if err != nil { 165 return includeFiles, errors.Wrapf(err, "error creating root file-system diff file %q", rootfsDiffPath) 166 } 167 defer rootfsDiffFile.Close() 168 if _, err = io.Copy(rootfsDiffFile, rootfsTar); err != nil { 169 return includeFiles, err 170 } 171 172 includeFiles = append(includeFiles, metadata.RootFsDiffTar) 173 } 174 175 if len(deletedFiles) == 0 { 176 return includeFiles, nil 177 } 178 179 if _, err := metadata.WriteJSONFile(deletedFiles, destination, metadata.DeletedFilesFile); err != nil { 180 return includeFiles, nil 181 } 182 183 includeFiles = append(includeFiles, metadata.DeletedFilesFile) 184 185 return includeFiles, nil 186 } 187 188 // CRCreateFileWithLabel creates an empty file and sets the corresponding ('fileLabel') 189 // SELinux label on the file. 190 // This is necessary for CRIU log files because CRIU infects the processes in 191 // the container with a 'parasite' and this will also try to write to the log files 192 // from the context of the container processes. 193 func CRCreateFileWithLabel(directory, fileName, fileLabel string) error { 194 logFileName := filepath.Join(directory, fileName) 195 196 logFile, err := os.OpenFile(logFileName, os.O_CREATE, 0o600) 197 if err != nil { 198 return errors.Wrapf(err, "failed to create file %q", logFileName) 199 } 200 defer logFile.Close() 201 if err = label.SetFileLabel(logFileName, fileLabel); err != nil { 202 return errors.Wrapf(err, "failed to label file %q", logFileName) 203 } 204 205 return nil 206 } 207 208 // CRRuntimeSupportsCheckpointRestore tests if the given runtime at 'runtimePath' 209 // supports checkpointing. The checkpoint restore interface has no definition 210 // but crun implements all commands just as runc does. Whathh runc does it the 211 // official definition of the checkpoint/restore interface. 212 func CRRuntimeSupportsCheckpointRestore(runtimePath string) bool { 213 // Check if the runtime implements checkpointing. Currently only 214 // runc's and crun's checkpoint/restore implementation is supported. 215 cmd := exec.Command(runtimePath, "checkpoint", "--help") 216 if err := cmd.Start(); err != nil { 217 return false 218 } 219 if err := cmd.Wait(); err == nil { 220 return true 221 } 222 return false 223 } 224 225 // CRRuntimeSupportsCheckpointRestore tests if the runtime at 'runtimePath' 226 // supports restoring into existing Pods. The runtime needs to support 227 // the CRIU option --lsm-mount-context and the existence of this is checked 228 // by this function. In addition it is necessary to at least have CRIU 3.16. 229 func CRRuntimeSupportsPodCheckpointRestore(runtimePath string) bool { 230 cmd := exec.Command(runtimePath, "restore", "--lsm-mount-context") 231 out, _ := cmd.CombinedOutput() 232 return bytes.Contains(out, []byte("flag needs an argument")) 233 } 234 235 // CRGetRuntimeFromArchive extracts the checkpoint metadata from the 236 // given checkpoint archive and returns the runtime used to create 237 // the given checkpoint archive. 238 func CRGetRuntimeFromArchive(input string) (*string, error) { 239 dir, err := ioutil.TempDir("", "checkpoint") 240 if err != nil { 241 return nil, err 242 } 243 defer os.RemoveAll(dir) 244 245 if err := CRImportCheckpointConfigOnly(dir, input); err != nil { 246 return nil, err 247 } 248 249 // Load config.dump from temporary directory 250 ctrConfig := new(metadata.ContainerConfig) 251 if _, err = metadata.ReadJSONFile(ctrConfig, dir, metadata.ConfigDumpFile); err != nil { 252 return nil, err 253 } 254 255 return &ctrConfig.OCIRuntime, nil 256 }