diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/config/config.go | 14 | ||||
-rw-r--r-- | libpod/container_api.go | 7 | ||||
-rw-r--r-- | libpod/container_internal.go | 40 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 98 | ||||
-rw-r--r-- | libpod/diff.go | 44 | ||||
-rw-r--r-- | libpod/oci.go | 6 | ||||
-rw-r--r-- | libpod/oci_conmon_linux.go | 22 | ||||
-rw-r--r-- | libpod/oci_missing.go | 5 | ||||
-rw-r--r-- | libpod/runtime.go | 20 |
9 files changed, 181 insertions, 75 deletions
diff --git a/libpod/config/config.go b/libpod/config/config.go index 0e867a50e..6240bccb0 100644 --- a/libpod/config/config.go +++ b/libpod/config/config.go @@ -448,20 +448,27 @@ func NewConfig(userConfigPath string) (*Config, error) { if err != nil { return nil, errors.Wrapf(err, "error reading user config %q", userConfigPath) } - if err := cgroupV2Check(userConfigPath, config); err != nil { - return nil, errors.Wrapf(err, "error rewriting configuration file %s", userConfigPath) - } } // Now, check if the user can access system configs and merge them if needed. if configs, err := systemConfigs(); err != nil { return nil, errors.Wrapf(err, "error finding config on system") } else { + migrated := false for _, path := range configs { systemConfig, err := readConfigFromFile(path) if err != nil { return nil, errors.Wrapf(err, "error reading system config %q", path) } + // Handle CGroups v2 configuration migration. + // Migrate only the first config, and do it before + // merging. + if !migrated { + if err := cgroupV2Check(path, systemConfig); err != nil { + return nil, errors.Wrapf(err, "error rewriting configuration file %s", userConfigPath) + } + migrated = true + } // Merge the it into the config. Any unset field in config will be // over-written by the systemConfig. if err := config.mergeConfig(systemConfig); err != nil { @@ -564,6 +571,7 @@ func (c *Config) checkCgroupsAndLogger() { // TODO Once runc has support for cgroups, this function should be removed. func cgroupV2Check(configPath string, tmpConfig *Config) error { if !tmpConfig.CgroupCheck && rootless.IsRootless() { + logrus.Debugf("Rewriting %s for CGroup v2 upgrade", configPath) cgroupsV2, err := cgroups.IsCgroup2UnifiedMode() if err != nil { return err diff --git a/libpod/container_api.go b/libpod/container_api.go index 153a1d628..5168dbc68 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -594,7 +594,12 @@ func (c *Container) Cleanup(ctx context.Context) error { // If we didn't restart, we perform a normal cleanup - // Check if we have active exec sessions + // Reap exec sessions first. + if err := c.reapExecSessions(); err != nil { + return err + } + + // Check if we have active exec sessions after reaping. if len(c.state.ExecSessions) != 0 { return errors.Wrapf(define.ErrCtrStateInvalid, "container %s has active exec sessions, refusing to clean up", c.ID()) } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 1e8a8a580..37801162a 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1749,6 +1749,11 @@ func (c *Container) checkReadyForRemoval() error { return errors.Wrapf(define.ErrCtrStateInvalid, "cannot remove container %s as it is %s - running or paused containers cannot be removed without force", c.ID(), c.state.State.String()) } + // Reap exec sessions + if err := c.reapExecSessions(); err != nil { + return err + } + if len(c.state.ExecSessions) != 0 { return errors.Wrapf(define.ErrCtrStateInvalid, "cannot remove container %s as it has active exec sessions", c.ID()) } @@ -1855,3 +1860,38 @@ func (c *Container) checkExitFile() error { // Read the exit file to get our stopped time and exit code. return c.handleExitFile(exitFile, info) } + +// Reap dead exec sessions +func (c *Container) reapExecSessions() error { + // Instead of saving once per iteration, use a defer to do it once at + // the end. + var lastErr error + needSave := false + for id := range c.state.ExecSessions { + alive, err := c.ociRuntime.ExecUpdateStatus(c, id) + if err != nil { + if lastErr != nil { + logrus.Errorf("Error reaping exec sessions for container %s: %v", c.ID(), lastErr) + } + lastErr = err + continue + } + if !alive { + // Clean up lingering files and remove the exec session + if err := c.ociRuntime.ExecContainerCleanup(c, id); err != nil { + return errors.Wrapf(err, "error cleaning up container %s exec session %s files", c.ID(), id) + } + delete(c.state.ExecSessions, id) + needSave = true + } + } + if needSave { + if err := c.save(); err != nil { + if lastErr != nil { + logrus.Errorf("Error reaping exec sessions for container %s: %v", c.ID(), lastErr) + } + lastErr = err + } + } + return lastErr +} diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 1b0570998..6ec06943f 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -593,22 +593,68 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) (err error) // Get root file-system changes included in the checkpoint archive rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar") + deleteFilesList := filepath.Join(c.bundlePath(), "deleted.files") if !ignoreRootfs { - rootfsDiffFile, err := os.Create(rootfsDiffPath) - if err != nil { - return errors.Wrapf(err, "error creating root file-system diff file %q", rootfsDiffPath) - } - tarStream, err := c.runtime.GetDiffTarStream("", c.ID()) + // To correctly track deleted files, let's go through the output of 'podman diff' + tarFiles, err := c.runtime.GetDiff("", c.ID()) if err != nil { return errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath) } - _, err = io.Copy(rootfsDiffFile, tarStream) - if err != nil { - return errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath) + var rootfsIncludeFiles []string + var deletedFiles []string + + for _, file := range tarFiles { + 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(c.state.Mountpoint, &archive.TarOptions{ + Compression: archive.Uncompressed, + IncludeSourceDir: true, + IncludeFiles: rootfsIncludeFiles, + }) + if err != nil { + return errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath) + } + rootfsDiffFile, err := os.Create(rootfsDiffPath) + if err != nil { + return errors.Wrapf(err, "error creating root file-system diff file %q", rootfsDiffPath) + } + defer rootfsDiffFile.Close() + _, err = io.Copy(rootfsDiffFile, rootfsTar) + if err != nil { + return err + } + + includeFiles = append(includeFiles, "rootfs-diff.tar") + } + + if len(deletedFiles) > 0 { + formatJSON, err := json.MarshalIndent(deletedFiles, "", " ") + if err != nil { + return errors.Wrapf(err, "error creating delete files list file %q", deleteFilesList) + } + if err := ioutil.WriteFile(deleteFilesList, formatJSON, 0600); err != nil { + return errors.Wrapf(err, "error creating delete files list file %q", deleteFilesList) + } + + includeFiles = append(includeFiles, "deleted.files") } - tarStream.Close() - rootfsDiffFile.Close() - includeFiles = append(includeFiles, "rootfs-diff.tar") } input, err := archive.TarWithOptions(c.bundlePath(), &archive.TarOptions{ @@ -637,6 +683,7 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) (err error) } os.Remove(rootfsDiffPath) + os.Remove(deleteFilesList) return nil } @@ -941,10 +988,35 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti if err != nil { return errors.Wrapf(err, "Failed to open root file-system diff file %s", rootfsDiffPath) } + defer rootfsDiffFile.Close() if err := c.runtime.ApplyDiffTarStream(c.ID(), rootfsDiffFile); err != nil { return errors.Wrapf(err, "Failed to apply root file-system diff file %s", rootfsDiffPath) } - rootfsDiffFile.Close() + } + deletedFilesPath := filepath.Join(c.bundlePath(), "deleted.files") + if _, err := os.Stat(deletedFilesPath); err == nil { + deletedFilesFile, err := os.Open(deletedFilesPath) + if err != nil { + return errors.Wrapf(err, "Failed to open deleted files file %s", deletedFilesPath) + } + defer deletedFilesFile.Close() + + var deletedFiles []string + deletedFilesJSON, err := ioutil.ReadAll(deletedFilesFile) + if err != nil { + return errors.Wrapf(err, "Failed to read deleted files file %s", deletedFilesPath) + } + if err := json.Unmarshal(deletedFilesJSON, &deletedFiles); err != nil { + return errors.Wrapf(err, "Failed to read deleted files file %s", deletedFilesPath) + } + for _, deleteFile := range deletedFiles { + // Using RemoveAll as deletedFiles, which is generated from 'podman diff' + // lists completely deleted directories as a single entry: 'D /root'. + err = os.RemoveAll(filepath.Join(c.state.Mountpoint, deleteFile)) + if err != nil { + return errors.Wrapf(err, "Failed to delete file %s from container %s during restore", deletedFilesPath, c.ID()) + } + } } } @@ -965,7 +1037,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti if err != nil { logrus.Debugf("Non-fatal: removal of checkpoint directory (%s) failed: %v", c.CheckpointPath(), err) } - cleanup := [...]string{"restore.log", "dump.log", "stats-dump", "stats-restore", "network.status", "rootfs-diff.tar"} + cleanup := [...]string{"restore.log", "dump.log", "stats-dump", "stats-restore", "network.status", "rootfs-diff.tar", "deleted.files"} for _, del := range cleanup { file := filepath.Join(c.bundlePath(), del) err = os.Remove(file) diff --git a/libpod/diff.go b/libpod/diff.go index 925bda927..baa4d6ad7 100644 --- a/libpod/diff.go +++ b/libpod/diff.go @@ -1,7 +1,6 @@ package libpod import ( - "archive/tar" "io" "github.com/containers/libpod/libpod/layers" @@ -47,49 +46,6 @@ func (r *Runtime) GetDiff(from, to string) ([]archive.Change, error) { return rchanges, err } -// skipFileInTarAchive is an archive.TarModifierFunc function -// which tells archive.ReplaceFileTarWrapper to skip files -// from the tarstream -func skipFileInTarAchive(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) { - return nil, nil, nil -} - -// GetDiffTarStream returns the differences between the two images, layers, or containers. -// It is the same functionality as GetDiff() except that it returns a tarstream -func (r *Runtime) GetDiffTarStream(from, to string) (io.ReadCloser, error) { - toLayer, err := r.getLayerID(to) - if err != nil { - return nil, err - } - fromLayer := "" - if from != "" { - fromLayer, err = r.getLayerID(from) - if err != nil { - return nil, err - } - } - rc, err := r.store.Diff(fromLayer, toLayer, nil) - if err != nil { - return nil, err - } - - // Skip files in the tar archive which are listed - // in containerMounts map. Just as in the GetDiff() - // function from above - filterMap := make(map[string]archive.TarModifierFunc) - for key := range containerMounts { - filterMap[key[1:]] = skipFileInTarAchive - // In the tarstream directories always include a trailing '/'. - // For simplicity this duplicates every entry from - // containerMounts with a trailing '/', as containerMounts - // does not use trailing '/' for directories. - filterMap[key[1:]+"/"] = skipFileInTarAchive - } - - filteredTarStream := archive.ReplaceFileTarWrapper(rc, filterMap) - return filteredTarStream, nil -} - // ApplyDiffTarStream applies the changes stored in 'diff' to the layer 'to' func (r *Runtime) ApplyDiffTarStream(to string, diff io.Reader) error { toLayer, err := r.getLayerID(to) diff --git a/libpod/oci.go b/libpod/oci.go index 9e761788e..05a2f37db 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -23,9 +23,6 @@ type OCIRuntime interface { // CreateContainer creates the container in the OCI runtime. CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error // UpdateContainerStatus updates the status of the given container. - // It includes a switch for whether to perform a hard query of the - // runtime. If unset, the exit file (if supported by the implementation) - // will be used. UpdateContainerStatus(ctr *Container) error // StartContainer starts the given container. StartContainer(ctr *Container) error @@ -59,6 +56,9 @@ type OCIRuntime interface { // If timeout is 0, SIGKILL will be sent immediately, and SIGTERM will // be omitted. ExecStopContainer(ctr *Container, sessionID string, timeout uint) error + // ExecUpdateStatus checks the status of a given exec session. + // Returns true if the session is still running, or false if it exited. + ExecUpdateStatus(ctr *Container, sessionID string) (bool, error) // ExecContainerCleanup cleans up after an exec session exits. // It removes any files left by the exec session that are no longer // needed, including the attach socket. diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index 026b13129..37aa71cbb 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -687,6 +687,28 @@ func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, t return nil } +// ExecUpdateStatus checks if the given exec session is still running. +func (r *ConmonOCIRuntime) ExecUpdateStatus(ctr *Container, sessionID string) (bool, error) { + session, ok := ctr.state.ExecSessions[sessionID] + if !ok { + // TODO This should probably be a separate error + return false, errors.Wrapf(define.ErrInvalidArg, "no exec session with ID %s found in container %s", sessionID, ctr.ID()) + } + + logrus.Debugf("Checking status of container %s exec session %s", ctr.ID(), sessionID) + + // Is the session dead? + // Ping the PID with signal 0 to see if it still exists. + if err := unix.Kill(session.PID, 0); err != nil { + if err == unix.ESRCH { + return false, nil + } + return false, errors.Wrapf(err, "error pinging container %s exec session %s PID %d with signal 0", ctr.ID(), sessionID, session.PID) + } + + return true, nil +} + // ExecCleanupContainer cleans up files created when a command is run via // ExecContainer. This includes the attach socket for the exec session. func (r *ConmonOCIRuntime) ExecContainerCleanup(ctr *Container, sessionID string) error { diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go index d4524cd34..0faa1805b 100644 --- a/libpod/oci_missing.go +++ b/libpod/oci_missing.go @@ -120,6 +120,11 @@ func (r *MissingRuntime) ExecStopContainer(ctr *Container, sessionID string, tim return r.printError() } +// ExecUpdateStatus is not available as the runtime is missing. +func (r *MissingRuntime) ExecUpdateStatus(ctr *Container, sessionID string) (bool, error) { + return false, r.printError() +} + // ExecContainerCleanup is not available as the runtime is missing func (r *MissingRuntime) ExecContainerCleanup(ctr *Container, sessionID string) error { return r.printError() diff --git a/libpod/runtime.go b/libpod/runtime.go index 3873079ce..001d850b0 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -691,24 +691,22 @@ func (r *Runtime) Info() ([]define.InfoData, error) { } info = append(info, define.InfoData{Type: "store", Data: storeInfo}) - reg, err := sysreg.GetRegistries() - if err != nil { - return nil, errors.Wrapf(err, "error getting registries") - } registries := make(map[string]interface{}) - registries["search"] = reg - - ireg, err := sysreg.GetInsecureRegistries() + data, err := sysreg.GetRegistriesData() if err != nil { return nil, errors.Wrapf(err, "error getting registries") } - registries["insecure"] = ireg - - breg, err := sysreg.GetBlockedRegistries() + for _, reg := range data { + registries[reg.Prefix] = reg + } + regs, err := sysreg.GetRegistries() if err != nil { return nil, errors.Wrapf(err, "error getting registries") } - registries["blocked"] = breg + if len(regs) > 0 { + registries["search"] = regs + } + info = append(info, define.InfoData{Type: "registries", Data: registries}) return info, nil } |