Files
podman/pkg/domain/infra/abi/system.go
Matthew Heon 259c79963f Improve robustness of podman system reset
Firstly, reset is now managed by the runtime itself as a part of
initialization. This ensures that it can be used even with
runtimes that would otherwise fail to be created - most notably,
when the user has changed a core path
(runroot/root/tmpdir/staticdir).

Secondly, we now attempt a best-effort removal even if the store
completely fails to be configured.

Third, we now hold the alive lock for the entire reset operation.
This ensures that no other Podman process can start while we are
running a system reset, and removes any possibility of a race
where a user tries to create containers or pull images while we
are trying to perform a reset.

[NO NEW TESTS NEEDED] we do not test reset last I checked.

Fixes #9075

Signed-off-by: Matthew Heon <mheon@redhat.com>
2022-06-03 12:54:08 -04:00

389 lines
11 KiB
Go

package abi
import (
"context"
"fmt"
"net/url"
"os"
"os/exec"
"path/filepath"
"github.com/containers/common/pkg/cgroups"
"github.com/containers/common/pkg/config"
cutil "github.com/containers/common/pkg/util"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/domain/entities/reports"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/pkg/util"
"github.com/containers/podman/v4/utils"
"github.com/containers/storage"
"github.com/containers/storage/pkg/unshare"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
)
func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) {
info, err := ic.Libpod.Info()
if err != nil {
return nil, err
}
info.Host.RemoteSocket = &define.RemoteSocket{Path: ic.Libpod.RemoteURI()}
// `podman system connection add` invokes podman via ssh to fill in connection string. Here
// we are reporting the default systemd activation socket path as we cannot know if a future
// service may be run with another URI.
if ic.Libpod.RemoteURI() == "" {
xdg := "/run"
if path, err := util.GetRuntimeDir(); err != nil {
// Info is as good as we can guess...
return info, err
} else if path != "" {
xdg = path
}
uri := url.URL{
Scheme: "unix",
Path: filepath.Join(xdg, "podman", "podman.sock"),
}
ic.Libpod.SetRemoteURI(uri.String())
info.Host.RemoteSocket.Path = uri.Path
}
uri, err := url.Parse(ic.Libpod.RemoteURI())
if err != nil {
return nil, err
}
if uri.Scheme == "unix" {
_, err := os.Stat(uri.Path)
info.Host.RemoteSocket.Exists = err == nil
} else {
info.Host.RemoteSocket.Exists = true
}
return info, err
}
func (ic *ContainerEngine) SetupRootless(_ context.Context, noMoveProcess bool) error {
// do it only after podman has already re-execed and running with uid==0.
hasCapSysAdmin, err := unshare.HasCapSysAdmin()
if err != nil {
return err
}
if hasCapSysAdmin {
ownsCgroup, err := cgroups.UserOwnsCurrentSystemdCgroup()
if err != nil {
logrus.Infof("Failed to detect the owner for the current cgroup: %v", err)
}
if !ownsCgroup {
conf, err := ic.Config(context.Background())
if err != nil {
return err
}
runsUnderSystemd := utils.RunsOnSystemd()
unitName := fmt.Sprintf("podman-%d.scope", os.Getpid())
if runsUnderSystemd || conf.Engine.CgroupManager == config.SystemdCgroupsManager {
if err := utils.RunUnderSystemdScope(os.Getpid(), "user.slice", unitName); err != nil {
logrus.Debugf("Failed to add podman to systemd sandbox cgroup: %v", err)
}
}
}
return nil
}
tmpDir, err := ic.Libpod.TmpDir()
if err != nil {
return err
}
pausePidPath, err := util.GetRootlessPauseProcessPidPathGivenDir(tmpDir)
if err != nil {
return errors.Wrapf(err, "could not get pause process pid file path")
}
became, ret, err := rootless.TryJoinPauseProcess(pausePidPath)
if err != nil {
return err
}
if became {
os.Exit(ret)
}
if noMoveProcess {
return nil
}
// if there is no pid file, try to join existing containers, and create a pause process.
ctrs, err := ic.Libpod.GetRunningContainers()
if err != nil {
logrus.Error(err.Error())
os.Exit(1)
}
paths := []string{}
for _, ctr := range ctrs {
paths = append(paths, ctr.Config().ConmonPidFile)
}
became, ret, err = rootless.TryJoinFromFilePaths(pausePidPath, true, paths)
utils.MovePauseProcessToScope(pausePidPath)
if err != nil {
logrus.Error(errors.Wrapf(err, "invalid internal status, try resetting the pause process with %q", os.Args[0]+" system migrate"))
os.Exit(1)
}
if became {
os.Exit(ret)
}
return nil
}
// SystemPrune removes unused data from the system. Pruning pods, containers, volumes and images.
func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.SystemPruneOptions) (*entities.SystemPruneReport, error) {
var systemPruneReport = new(entities.SystemPruneReport)
filters := []string{}
for k, v := range options.Filters {
filters = append(filters, fmt.Sprintf("%s=%s", k, v[0]))
}
reclaimedSpace := (uint64)(0)
found := true
for found {
found = false
podPruneReport, err := ic.prunePodHelper(ctx)
if err != nil {
return nil, err
}
if len(podPruneReport) > 0 {
found = true
}
systemPruneReport.PodPruneReport = append(systemPruneReport.PodPruneReport, podPruneReport...)
// TODO: Figure out cleaner way to handle all of the different PruneOptions
containerPruneOptions := entities.ContainerPruneOptions{}
containerPruneOptions.Filters = (url.Values)(options.Filters)
containerPruneReports, err := ic.ContainerPrune(ctx, containerPruneOptions)
if err != nil {
return nil, err
}
reclaimedSpace += reports.PruneReportsSize(containerPruneReports)
systemPruneReport.ContainerPruneReports = append(systemPruneReport.ContainerPruneReports, containerPruneReports...)
imagePruneOptions := entities.ImagePruneOptions{
All: options.All,
Filter: filters,
}
imageEngine := ImageEngine{Libpod: ic.Libpod}
imagePruneReports, err := imageEngine.Prune(ctx, imagePruneOptions)
reclaimedSpace += reports.PruneReportsSize(imagePruneReports)
if err != nil {
return nil, err
}
if len(imagePruneReports) > 0 {
found = true
}
systemPruneReport.ImagePruneReports = append(systemPruneReport.ImagePruneReports, imagePruneReports...)
if options.Volume {
volumePruneOptions := entities.VolumePruneOptions{}
volumePruneOptions.Filters = (url.Values)(options.Filters)
volumePruneReport, err := ic.VolumePrune(ctx, volumePruneOptions)
if err != nil {
return nil, err
}
if len(volumePruneReport) > 0 {
found = true
}
reclaimedSpace += reports.PruneReportsSize(volumePruneReport)
systemPruneReport.VolumePruneReports = append(systemPruneReport.VolumePruneReports, volumePruneReport...)
}
}
systemPruneReport.ReclaimedSpace = reclaimedSpace
return systemPruneReport, nil
}
func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.SystemDfOptions) (*entities.SystemDfReport, error) {
var (
dfImages = []*entities.SystemDfImageReport{}
)
imageStats, err := ic.Libpod.LibimageRuntime().DiskUsage(ctx)
if err != nil {
return nil, err
}
for _, stat := range imageStats {
report := entities.SystemDfImageReport{
Repository: stat.Repository,
Tag: stat.Tag,
ImageID: stat.ID,
Created: stat.Created,
Size: stat.Size,
SharedSize: stat.SharedSize,
UniqueSize: stat.UniqueSize,
Containers: stat.Containers,
}
dfImages = append(dfImages, &report)
}
// Get Containers and iterate them
cons, err := ic.Libpod.GetAllContainers()
if err != nil {
return nil, err
}
dfContainers := make([]*entities.SystemDfContainerReport, 0, len(cons))
for _, c := range cons {
iid, _ := c.Image()
state, err := c.State()
if err != nil {
return nil, errors.Wrapf(err, "Failed to get state of container %s", c.ID())
}
conSize, err := c.RootFsSize()
if err != nil {
if errors.Cause(err) == storage.ErrContainerUnknown {
logrus.Error(errors.Wrapf(err, "Failed to get root file system size of container %s", c.ID()))
} else {
return nil, errors.Wrapf(err, "Failed to get root file system size of container %s", c.ID())
}
}
rwsize, err := c.RWSize()
if err != nil {
if errors.Cause(err) == storage.ErrContainerUnknown {
logrus.Error(errors.Wrapf(err, "Failed to get read/write size of container %s", c.ID()))
} else {
return nil, errors.Wrapf(err, "Failed to get read/write size of container %s", c.ID())
}
}
report := entities.SystemDfContainerReport{
ContainerID: c.ID(),
Image: iid,
Command: c.Command(),
LocalVolumes: len(c.UserVolumes()),
RWSize: rwsize,
Size: conSize,
Created: c.CreatedTime(),
Status: state.String(),
Names: c.Name(),
}
dfContainers = append(dfContainers, &report)
}
// Get volumes and iterate them
vols, err := ic.Libpod.GetAllVolumes()
if err != nil {
return nil, err
}
running, err := ic.Libpod.GetRunningContainers()
if err != nil {
return nil, err
}
runningContainers := make([]string, 0, len(running))
for _, c := range running {
runningContainers = append(runningContainers, c.ID())
}
dfVolumes := make([]*entities.SystemDfVolumeReport, 0, len(vols))
var reclaimableSize uint64
for _, v := range vols {
var consInUse int
mountPoint, err := v.MountPoint()
if err != nil {
return nil, err
}
if mountPoint == "" {
// We can't get any info on this volume, as it's not
// mounted.
// TODO: fix this.
continue
}
volSize, err := util.SizeOfPath(mountPoint)
if err != nil {
return nil, err
}
inUse, err := v.VolumeInUse()
if err != nil {
return nil, err
}
if len(inUse) == 0 {
reclaimableSize += volSize
}
for _, viu := range inUse {
if cutil.StringInSlice(viu, runningContainers) {
consInUse++
}
}
report := entities.SystemDfVolumeReport{
VolumeName: v.Name(),
Links: consInUse,
Size: int64(volSize),
ReclaimableSize: int64(reclaimableSize),
}
dfVolumes = append(dfVolumes, &report)
}
return &entities.SystemDfReport{
Images: dfImages,
Containers: dfContainers,
Volumes: dfVolumes,
}, nil
}
func (se *SystemEngine) Reset(ctx context.Context) error {
return nil
}
func (se *SystemEngine) Renumber(ctx context.Context, flags *pflag.FlagSet, config *entities.PodmanConfig) error {
return nil
}
func (se SystemEngine) Migrate(ctx context.Context, flags *pflag.FlagSet, config *entities.PodmanConfig, options entities.SystemMigrateOptions) error {
return nil
}
func (se SystemEngine) Shutdown(ctx context.Context) {
if err := se.Libpod.Shutdown(false); err != nil {
logrus.Error(err)
}
}
func unshareEnv(graphroot, runroot string) []string {
return append(os.Environ(), "_CONTAINERS_USERNS_CONFIGURED=done",
fmt.Sprintf("CONTAINERS_GRAPHROOT=%s", graphroot),
fmt.Sprintf("CONTAINERS_RUNROOT=%s", runroot))
}
func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options entities.SystemUnshareOptions) error {
unshare := func() error {
cmd := exec.Command(args[0], args[1:]...)
cmd.Env = unshareEnv(ic.Libpod.StorageConfig().GraphRoot, ic.Libpod.StorageConfig().RunRoot)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
if options.RootlessNetNS {
rootlessNetNS, err := ic.Libpod.GetRootlessNetNs(true)
if err != nil {
return err
}
// Make sure to unlock, unshare can run for a long time.
rootlessNetNS.Lock.Unlock()
// We do not want to cleanup the netns after unshare.
// The problem is that we cannot know if we need to cleanup and
// secondly unshare should allow user to setup the namespace with
// special things, e.g. potentially macvlan or something like that.
return rootlessNetNS.Do(unshare)
}
return unshare()
}
func (ic ContainerEngine) Version(ctx context.Context) (*entities.SystemVersionReport, error) {
var report entities.SystemVersionReport
v, err := define.GetVersion()
if err != nil {
return nil, err
}
report.Client = &v
return &report, err
}