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  }