From bf92e21113c9b2d2ff4c18423ff3deb10a1ce29e Mon Sep 17 00:00:00 2001 From: Adrian Reber Date: Thu, 25 Feb 2021 15:34:12 +0000 Subject: Move checkpoint/restore code to pkg/checkpoint/crutils To be able to reuse common checkpoint/restore functions this commit moves code to pkg/checkpoint/crutils. This commit has not functional changes. It only moves code around. [NO TESTS NEEDED] - only moving code around Signed-off-by: Adrian Reber --- pkg/checkpoint/crutils/checkpoint_restore_utils.go | 191 +++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 pkg/checkpoint/crutils/checkpoint_restore_utils.go (limited to 'pkg/checkpoint/crutils') diff --git a/pkg/checkpoint/crutils/checkpoint_restore_utils.go b/pkg/checkpoint/crutils/checkpoint_restore_utils.go new file mode 100644 index 000000000..53ff55865 --- /dev/null +++ b/pkg/checkpoint/crutils/checkpoint_restore_utils.go @@ -0,0 +1,191 @@ +package crutils + +import ( + "io" + "os" + "os/exec" + "path/filepath" + + metadata "github.com/checkpoint-restore/checkpointctl/lib" + "github.com/containers/storage/pkg/archive" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/pkg/errors" +) + +// This file mainly exist to make the checkpoint/restore functions +// available for other users. One possible candidate would be CRI-O. + +// CRImportCheckpointWithoutConfig imports the checkpoint archive (input) +// into the directory destination without "config.dump" and "spec.dump" +func CRImportCheckpointWithoutConfig(destination, input string) error { + archiveFile, err := os.Open(input) + if err != nil { + return errors.Wrapf(err, "Failed to open checkpoint archive %s for import", input) + } + + defer archiveFile.Close() + options := &archive.TarOptions{ + ExcludePatterns: []string{ + // Import everything else besides the container config + metadata.ConfigDumpFile, + metadata.SpecDumpFile, + }, + } + if err = archive.Untar(archiveFile, destination, options); err != nil { + return errors.Wrapf(err, "Unpacking of checkpoint archive %s failed", input) + } + + return nil +} + +// CRRemoveDeletedFiles loads the list of deleted files and if +// it exists deletes all files listed. +func CRRemoveDeletedFiles(id, baseDirectory, containerRootDirectory string) error { + deletedFiles, _, err := metadata.ReadContainerCheckpointDeletedFiles(baseDirectory) + if os.IsNotExist(errors.Unwrap(errors.Unwrap(err))) { + // No files to delete. Just return + return nil + } + + if err != nil { + return errors.Wrapf(err, "failed to read deleted files file") + } + + for _, deleteFile := range deletedFiles { + // Using RemoveAll as deletedFiles, which is generated from 'podman diff' + // lists completely deleted directories as a single entry: 'D /root'. + if err := os.RemoveAll(filepath.Join(containerRootDirectory, deleteFile)); err != nil { + return errors.Wrapf(err, "failed to delete files from container %s during restore", id) + } + } + + return nil +} + +// CRApplyRootFsDiffTar applies the tar archive found in baseDirectory with the +// root file system changes on top of containerRootDirectory +func CRApplyRootFsDiffTar(baseDirectory, containerRootDirectory string) error { + rootfsDiffPath := filepath.Join(baseDirectory, metadata.RootFsDiffTar) + if _, err := os.Stat(rootfsDiffPath); err != nil { + // Only do this if a rootfs-diff.tar actually exists + return nil + } + + rootfsDiffFile, err := os.Open(rootfsDiffPath) + if err != nil { + return errors.Wrap(err, "failed to open root file-system diff file") + } + defer rootfsDiffFile.Close() + + if err := archive.Untar(rootfsDiffFile, containerRootDirectory, nil); err != nil { + return errors.Wrapf(err, "failed to apply root file-system diff file %s", rootfsDiffPath) + } + + return nil +} + +// CRCreateRootFsDiffTar goes through the 'changes' and can create two files: +// * metadata.RootFsDiffTar will contain all new and changed files +// * metadata.DeletedFilesFile will contain a list of deleted files +// With these two files it is possible to restore the container file system to the same +// state it was during checkpointing. +// Changes to directories (owner, mode) are not handled. +func CRCreateRootFsDiffTar(changes *[]archive.Change, mountPoint, destination string) (includeFiles []string, err error) { + if len(*changes) == 0 { + return includeFiles, nil + } + + var rootfsIncludeFiles []string + var deletedFiles []string + + rootfsDiffPath := filepath.Join(destination, metadata.RootFsDiffTar) + + for _, file := range *changes { + if file.Kind == archive.ChangeAdd { + rootfsIncludeFiles = append(rootfsIncludeFiles, file.Path) + continue + } + if file.Kind == archive.ChangeDelete { + deletedFiles = append(deletedFiles, file.Path) + continue + } + fileName, err := os.Stat(file.Path) + if err != nil { + continue + } + if !fileName.IsDir() && file.Kind == archive.ChangeModify { + rootfsIncludeFiles = append(rootfsIncludeFiles, file.Path) + continue + } + } + + if len(rootfsIncludeFiles) > 0 { + rootfsTar, err := archive.TarWithOptions(mountPoint, &archive.TarOptions{ + Compression: archive.Uncompressed, + IncludeSourceDir: true, + IncludeFiles: rootfsIncludeFiles, + }) + if err != nil { + return includeFiles, errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath) + } + rootfsDiffFile, err := os.Create(rootfsDiffPath) + if err != nil { + return includeFiles, errors.Wrapf(err, "error creating root file-system diff file %q", rootfsDiffPath) + } + defer rootfsDiffFile.Close() + if _, err = io.Copy(rootfsDiffFile, rootfsTar); err != nil { + return includeFiles, err + } + + includeFiles = append(includeFiles, metadata.RootFsDiffTar) + } + + if len(deletedFiles) == 0 { + return includeFiles, nil + } + + if _, err := metadata.WriteJSONFile(deletedFiles, destination, metadata.DeletedFilesFile); err != nil { + return includeFiles, nil + } + + includeFiles = append(includeFiles, metadata.DeletedFilesFile) + + return includeFiles, nil +} + +// CRCreateFileWithLabel creates an empty file and sets the corresponding ('fileLabel') +// SELinux label on the file. +// This is necessary for CRIU log files because CRIU infects the processes in +// the container with a 'parasite' and this will also try to write to the log files +// from the context of the container processes. +func CRCreateFileWithLabel(directory, fileName, fileLabel string) error { + logFileName := filepath.Join(directory, fileName) + + logFile, err := os.OpenFile(logFileName, os.O_CREATE, 0o600) + if err != nil { + return errors.Wrapf(err, "failed to create file %q", logFileName) + } + defer logFile.Close() + if err = label.SetFileLabel(logFileName, fileLabel); err != nil { + return errors.Wrapf(err, "failed to label file %q", logFileName) + } + + return nil +} + +// CRRuntimeSupportsCheckpointRestore tests if the given runtime at 'runtimePath' +// supports checkpointing. The checkpoint restore interface has no definition +// but crun implements all commands just as runc does. Whathh runc does it the +// official definition of the checkpoint/restore interface. +func CRRuntimeSupportsCheckpointRestore(runtimePath string) bool { + // Check if the runtime implements checkpointing. Currently only + // runc's and crun's checkpoint/restore implementation is supported. + cmd := exec.Command(runtimePath, "checkpoint", "--help") + if err := cmd.Start(); err != nil { + return false + } + if err := cmd.Wait(); err == nil { + return true + } + return false +} -- cgit v1.2.3-54-g00ecf