diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/container_api.go | 25 | ||||
-rw-r--r-- | libpod/container_config.go | 3 | ||||
-rw-r--r-- | libpod/container_internal.go | 26 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 239 | ||||
-rw-r--r-- | libpod/container_log.go | 8 | ||||
-rw-r--r-- | libpod/networking_linux.go | 30 | ||||
-rw-r--r-- | libpod/oci.go | 30 | ||||
-rw-r--r-- | libpod/oci_conmon_attach_linux.go (renamed from libpod/oci_attach_linux.go) | 40 | ||||
-rw-r--r-- | libpod/oci_conmon_linux.go | 4 | ||||
-rw-r--r-- | libpod/oci_missing.go | 5 | ||||
-rw-r--r-- | libpod/options.go | 29 | ||||
-rw-r--r-- | libpod/pod.go | 27 | ||||
-rw-r--r-- | libpod/pod_api.go | 5 | ||||
-rw-r--r-- | libpod/reset.go | 72 | ||||
-rw-r--r-- | libpod/runtime.go | 62 |
15 files changed, 360 insertions, 245 deletions
diff --git a/libpod/container_api.go b/libpod/container_api.go index d87deb71a..b064d3528 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -123,7 +123,18 @@ func (c *Container) StartAndAttach(ctx context.Context, streams *define.AttachSt // Attach to the container before starting it go func() { - if err := c.attach(streams, keys, resize, true, startedChan, nil); err != nil { + // Start resizing + if c.LogDriver() != define.PassthroughLogging { + registerResizeFunc(resize, c.bundlePath()) + } + + opts := new(AttachOptions) + opts.Streams = streams + opts.DetachKeys = &keys + opts.Start = true + opts.Started = startedChan + + if err := c.ociRuntime.Attach(c, opts); err != nil { attachChan <- err } close(attachChan) @@ -260,8 +271,18 @@ func (c *Container) Attach(streams *define.AttachStreams, keys string, resize <- }() } + // Start resizing + if c.LogDriver() != define.PassthroughLogging { + registerResizeFunc(resize, c.bundlePath()) + } + + opts := new(AttachOptions) + opts.Streams = streams + opts.DetachKeys = &keys + opts.AttachReady = attachRdy + c.newContainerEvent(events.Attach) - return c.attach(streams, keys, resize, false, nil, attachRdy) + return c.ociRuntime.Attach(c, opts) } // HTTPAttach forwards an attach session over a hijacked HTTP session. diff --git a/libpod/container_config.go b/libpod/container_config.go index 30b84adcf..6558f3c89 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -412,6 +412,9 @@ type ContainerMiscConfig struct { InitContainerType string `json:"init_container_type,omitempty"` // PasswdEntry specifies arbitrary data to append to a file. PasswdEntry string `json:"passwd_entry,omitempty"` + // MountAllDevices is an option to indicate whether a privileged container + // will mount all the host's devices + MountAllDevices bool `json:"mountAllDevices"` } // InfraInherit contains the compatible options inheritable from the infra container diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 7494eb3ec..fd451f9ef 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -17,6 +17,7 @@ import ( "github.com/containers/buildah/pkg/overlay" butil "github.com/containers/buildah/util" "github.com/containers/common/libnetwork/etchosts" + "github.com/containers/common/libnetwork/resolvconf" "github.com/containers/common/pkg/cgroups" "github.com/containers/common/pkg/chown" "github.com/containers/common/pkg/config" @@ -986,7 +987,7 @@ func (c *Container) checkDependenciesRunning() ([]string, error) { } func (c *Container) completeNetworkSetup() error { - var outResolvConf []string + var nameservers []string netDisabled, err := c.NetworkDisabled() if err != nil { return err @@ -1000,11 +1001,14 @@ func (c *Container) completeNetworkSetup() error { if err := c.runtime.setupNetNS(c); err != nil { return err } + if err := c.save(); err != nil { + return err + } state := c.state // collect any dns servers that cni tells us to use (dnsname) for _, status := range c.getNetworkStatus() { for _, server := range status.DNSServerIPs { - outResolvConf = append(outResolvConf, fmt.Sprintf("nameserver %s", server)) + nameservers = append(nameservers, server.String()) } } // check if we have a bindmount for /etc/hosts @@ -1020,24 +1024,12 @@ func (c *Container) completeNetworkSetup() error { } // check if we have a bindmount for resolv.conf - resolvBindMount := state.BindMounts["/etc/resolv.conf"] - if len(outResolvConf) < 1 || resolvBindMount == "" || len(c.config.NetNsCtr) > 0 { + resolvBindMount := state.BindMounts[resolvconf.DefaultResolvConf] + if len(nameservers) < 1 || resolvBindMount == "" || len(c.config.NetNsCtr) > 0 { return nil } - // read the existing resolv.conf - b, err := ioutil.ReadFile(resolvBindMount) - if err != nil { - return err - } - for _, line := range strings.Split(string(b), "\n") { - // only keep things that don't start with nameserver from the old - // resolv.conf file - if !strings.HasPrefix(line, "nameserver") { - outResolvConf = append([]string{line}, outResolvConf...) - } - } // write and return - return ioutil.WriteFile(resolvBindMount, []byte(strings.Join(outResolvConf, "\n")), 0644) + return resolvconf.Add(resolvBindMount, nameservers) } // Initialize a container, creating it in the runtime diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index e19d75deb..41c0ac595 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -9,7 +9,6 @@ import ( "io" "io/ioutil" "math" - "net" "os" "os/user" "path" @@ -29,6 +28,7 @@ import ( "github.com/containers/buildah/pkg/overlay" butil "github.com/containers/buildah/util" "github.com/containers/common/libnetwork/etchosts" + "github.com/containers/common/libnetwork/resolvconf" "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/apparmor" "github.com/containers/common/pkg/cgroups" @@ -44,7 +44,6 @@ import ( "github.com/containers/podman/v4/pkg/checkpoint/crutils" "github.com/containers/podman/v4/pkg/criu" "github.com/containers/podman/v4/pkg/lookup" - "github.com/containers/podman/v4/pkg/resolvconf" "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/pkg/util" "github.com/containers/podman/v4/utils" @@ -388,6 +387,37 @@ func lookupHostUser(name string) (*runcuser.ExecUser, error) { return &execUser, nil } +// Internal only function which returns upper and work dir from +// overlay options. +func getOverlayUpperAndWorkDir(options []string) (string, string, error) { + upperDir := "" + workDir := "" + for _, o := range options { + if strings.HasPrefix(o, "upperdir") { + splitOpt := strings.SplitN(o, "=", 2) + if len(splitOpt) > 1 { + upperDir = splitOpt[1] + if upperDir == "" { + return "", "", errors.New("cannot accept empty value for upperdir") + } + } + } + if strings.HasPrefix(o, "workdir") { + splitOpt := strings.SplitN(o, "=", 2) + if len(splitOpt) > 1 { + workDir = splitOpt[1] + if workDir == "" { + return "", "", errors.New("cannot accept empty value for workdir") + } + } + } + } + if (upperDir != "" && workDir == "") || (upperDir == "" && workDir != "") { + return "", "", errors.New("must specify both upperdir and workdir") + } + return upperDir, workDir, nil +} + // Generate spec for a container // Accepts a map of the container's dependencies func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { @@ -407,6 +437,14 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { //nolint:staticcheck g := generate.NewFromSpec(c.config.Spec) + // If the flag to mount all devices is set for a privileged container, add + // all the devices from the host's machine into the container + if c.config.MountAllDevices { + if err := util.AddPrivilegedDevices(&g); err != nil { + return nil, err + } + } + // If network namespace was requested, add it now if c.config.CreateNetNS { if c.config.PostConfigureNetNS { @@ -460,23 +498,9 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { for _, o := range namedVol.Options { if o == "O" { overlayFlag = true - } - if overlayFlag && strings.Contains(o, "upperdir") { - splitOpt := strings.SplitN(o, "=", 2) - if len(splitOpt) > 1 { - upperDir = splitOpt[1] - if upperDir == "" { - return nil, errors.New("cannot accept empty value for upperdir") - } - } - } - if overlayFlag && strings.Contains(o, "workdir") { - splitOpt := strings.SplitN(o, "=", 2) - if len(splitOpt) > 1 { - workDir = splitOpt[1] - if workDir == "" { - return nil, errors.New("cannot accept empty value for workdir") - } + upperDir, workDir, err = getOverlayUpperAndWorkDir(namedVol.Options) + if err != nil { + return nil, err } } } @@ -489,10 +513,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { return nil, err } - if (upperDir != "" && workDir == "") || (upperDir == "" && workDir != "") { - return nil, errors.Wrapf(err, "must specify both upperdir and workdir") - } - overlayOpts = &overlay.Options{RootUID: c.RootUID(), RootGID: c.RootGID(), UpperDirOptionFragment: upperDir, @@ -585,11 +605,22 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { // Add overlay volumes for _, overlayVol := range c.config.OverlayVolumes { + upperDir, workDir, err := getOverlayUpperAndWorkDir(overlayVol.Options) + if err != nil { + return nil, err + } contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID()) if err != nil { return nil, err } - overlayMount, err := overlay.Mount(contentDir, overlayVol.Source, overlayVol.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions()) + overlayOpts := &overlay.Options{RootUID: c.RootUID(), + RootGID: c.RootGID(), + UpperDirOptionFragment: upperDir, + WorkDirOptionFragment: workDir, + GraphOpts: c.runtime.store.GraphOptions(), + } + + overlayMount, err := overlay.MountWithOptions(contentDir, overlayVol.Source, overlayVol.Dest, overlayOpts) if err != nil { return nil, errors.Wrapf(err, "mounting overlay failed %q", overlayVol.Source) } @@ -2284,49 +2315,10 @@ rootless=%d // generateResolvConf generates a containers resolv.conf func (c *Container) generateResolvConf() error { var ( - nameservers []string networkNameServers []string networkSearchDomains []string ) - hostns := true - resolvConf := "/etc/resolv.conf" - for _, namespace := range c.config.Spec.Linux.Namespaces { - if namespace.Type == spec.NetworkNamespace { - hostns = false - if namespace.Path != "" && !strings.HasPrefix(namespace.Path, "/proc/") { - definedPath := filepath.Join("/etc/netns", filepath.Base(namespace.Path), "resolv.conf") - _, err := os.Stat(definedPath) - if err == nil { - resolvConf = definedPath - } else if !os.IsNotExist(err) { - return err - } - } - break - } - } - - contents, err := ioutil.ReadFile(resolvConf) - // resolv.conf doesn't have to exists - if err != nil && !os.IsNotExist(err) { - return err - } - - ns := resolvconf.GetNameservers(contents) - // check if systemd-resolved is used, assume it is used when 127.0.0.53 is the only nameserver - if !hostns && len(ns) == 1 && ns[0] == "127.0.0.53" { - // read the actual resolv.conf file for systemd-resolved - resolvedContents, err := ioutil.ReadFile("/run/systemd/resolve/resolv.conf") - if err != nil { - if !os.IsNotExist(err) { - return errors.Wrapf(err, "detected that systemd-resolved is in use, but could not locate real resolv.conf") - } - } else { - contents = resolvedContents - } - } - netStatus := c.getNetworkStatus() for _, status := range netStatus { if status.DNSServerIPs != nil { @@ -2346,34 +2338,18 @@ func (c *Container) generateResolvConf() error { return err } - // Ensure that the container's /etc/resolv.conf is compatible with its - // network configuration. - resolv, err := resolvconf.FilterResolvDNS(contents, ipv6, !hostns) - if err != nil { - return errors.Wrapf(err, "error parsing host resolv.conf") + nameservers := make([]string, 0, len(c.runtime.config.Containers.DNSServers)+len(c.config.DNSServer)) + nameservers = append(nameservers, c.runtime.config.Containers.DNSServers...) + for _, ip := range c.config.DNSServer { + nameservers = append(nameservers, ip.String()) } - - dns := make([]net.IP, 0, len(c.runtime.config.Containers.DNSServers)+len(c.config.DNSServer)) - for _, i := range c.runtime.config.Containers.DNSServers { - result := net.ParseIP(i) - if result == nil { - return errors.Wrapf(define.ErrInvalidArg, "invalid IP address %s", i) - } - dns = append(dns, result) - } - dns = append(dns, c.config.DNSServer...) // If the user provided dns, it trumps all; then dns masq; then resolv.conf var search []string - switch { - case len(dns) > 0: - // We store DNS servers as net.IP, so need to convert to string - for _, server := range dns { - nameservers = append(nameservers, server.String()) - } - default: - // Make a new resolv.conf + keepHostServers := false + if len(nameservers) == 0 { + keepHostServers = true // first add the nameservers from the networks status - nameservers = append(nameservers, networkNameServers...) + nameservers = networkNameServers // when we add network dns server we also have to add the search domains search = networkSearchDomains // slirp4netns has a built in DNS forwarder. @@ -2385,38 +2361,34 @@ func (c *Container) generateResolvConf() error { nameservers = append(nameservers, slirp4netnsDNS.String()) } } - nameservers = append(nameservers, resolvconf.GetNameservers(resolv.Content)...) } if len(c.config.DNSSearch) > 0 || len(c.runtime.config.Containers.DNSSearches) > 0 { - if !cutil.StringInSlice(".", c.config.DNSSearch) { - search = append(search, c.runtime.config.Containers.DNSSearches...) - search = append(search, c.config.DNSSearch...) - } - } else { - search = append(search, resolvconf.GetSearchDomains(resolv.Content)...) + customSearch := make([]string, 0, len(c.config.DNSSearch)+len(c.runtime.config.Containers.DNSSearches)) + customSearch = append(customSearch, c.runtime.config.Containers.DNSSearches...) + customSearch = append(customSearch, c.config.DNSSearch...) + search = customSearch } - var options []string - if len(c.config.DNSOption) > 0 || len(c.runtime.config.Containers.DNSOptions) > 0 { - options = c.runtime.config.Containers.DNSOptions - options = append(options, c.config.DNSOption...) - } else { - options = resolvconf.GetOptions(resolv.Content) - } + options := make([]string, 0, len(c.config.DNSOption)+len(c.runtime.config.Containers.DNSOptions)) + options = append(options, c.runtime.config.Containers.DNSOptions...) + options = append(options, c.config.DNSOption...) destPath := filepath.Join(c.state.RunDir, "resolv.conf") - if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "container %s", c.ID()) - } - - // Build resolv.conf - if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil { + if err := resolvconf.New(&resolvconf.Params{ + IPv6Enabled: ipv6, + KeepHostServers: keepHostServers, + Nameservers: nameservers, + Namespaces: c.config.Spec.Linux.Namespaces, + Options: options, + Path: destPath, + Searches: search, + }); err != nil { return errors.Wrapf(err, "error building resolv.conf for container %s", c.ID()) } - return c.bindMountRootFile(destPath, "/etc/resolv.conf") + return c.bindMountRootFile(destPath, resolvconf.DefaultResolvConf) } // Check if a container uses IPv6. @@ -2457,31 +2429,13 @@ func (c *Container) addNameserver(ips []string) error { } // Do we have a resolv.conf at all? - path, ok := c.state.BindMounts["/etc/resolv.conf"] + path, ok := c.state.BindMounts[resolvconf.DefaultResolvConf] if !ok { return nil } - // Read in full contents, parse out existing nameservers - contents, err := ioutil.ReadFile(path) - if err != nil { - return err - } - ns := resolvconf.GetNameservers(contents) - options := resolvconf.GetOptions(contents) - search := resolvconf.GetSearchDomains(contents) - - // We could verify that it doesn't already exist - // but extra nameservers shouldn't harm anything. - // Ensure we are the first entry in resolv.conf though, otherwise we - // might be after user-added servers. - ns = append(ips, ns...) - - // We're rewriting the container's resolv.conf as part of this, but we - // hold the container lock, so there should be no risk of parallel - // modification. - if _, err := resolvconf.Build(path, ns, search, options); err != nil { - return errors.Wrapf(err, "error adding new nameserver to container %s resolv.conf", c.ID()) + if err := resolvconf.Add(path, ips); err != nil { + return fmt.Errorf("adding new nameserver to container %s resolv.conf: %w", c.ID(), err) } return nil @@ -2496,34 +2450,13 @@ func (c *Container) removeNameserver(ips []string) error { } // Do we have a resolv.conf at all? - path, ok := c.state.BindMounts["/etc/resolv.conf"] + path, ok := c.state.BindMounts[resolvconf.DefaultResolvConf] if !ok { return nil } - // Read in full contents, parse out existing nameservers - contents, err := ioutil.ReadFile(path) - if err != nil { - return err - } - ns := resolvconf.GetNameservers(contents) - options := resolvconf.GetOptions(contents) - search := resolvconf.GetSearchDomains(contents) - - toRemove := make(map[string]bool) - for _, ip := range ips { - toRemove[ip] = true - } - - newNS := make([]string, 0, len(ns)) - for _, server := range ns { - if !toRemove[server] { - newNS = append(newNS, server) - } - } - - if _, err := resolvconf.Build(path, newNS, search, options); err != nil { - return errors.Wrapf(err, "error removing nameservers from container %s resolv.conf", c.ID()) + if err := resolvconf.Remove(path, ips); err != nil { + return fmt.Errorf("removing nameservers from container %s resolv.conf: %w", c.ID(), err) } return nil diff --git a/libpod/container_log.go b/libpod/container_log.go index 7a9eb2dbf..da6d51670 100644 --- a/libpod/container_log.go +++ b/libpod/container_log.go @@ -75,7 +75,6 @@ func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOption go func() { defer options.WaitGroup.Done() - var partial string for line := range t.Lines { select { case <-ctx.Done(): @@ -89,13 +88,6 @@ func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOption logrus.Errorf("Getting new log line: %v", err) continue } - if nll.Partial() { - partial += nll.Msg - continue - } else if !nll.Partial() && len(partial) > 0 { - nll.Msg = partial + nll.Msg - partial = "" - } nll.CID = c.ID() nll.CName = c.Name() nll.ColorID = colorID diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 37fa9b5f5..ee80b00fe 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -21,6 +21,7 @@ import ( "github.com/containernetworking/plugins/pkg/ns" "github.com/containers/common/libnetwork/etchosts" + "github.com/containers/common/libnetwork/resolvconf" "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/machine" @@ -30,11 +31,10 @@ import ( "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/pkg/namespaces" - "github.com/containers/podman/v4/pkg/resolvconf" "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/utils" "github.com/containers/storage/pkg/lockfile" - spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -526,23 +526,19 @@ func (r *Runtime) GetRootlessNetNs(new bool) (*RootlessNetNS, error) { return nil, errors.Wrapf(err, "failed to determine slirp4netns DNS address from cidr: %s", cidr.String()) } } - conf, err := resolvconf.Get() - if err != nil { - return nil, err - } - conf, err = resolvconf.FilterResolvDNS(conf.Content, netOptions.enableIPv6, true) - if err != nil { - return nil, err - } - searchDomains := resolvconf.GetSearchDomains(conf.Content) - dnsOptions := resolvconf.GetOptions(conf.Content) - nameServers := resolvconf.GetNameservers(conf.Content) - _, err = resolvconf.Build(filepath.Join(rootlessNetNsDir, "resolv.conf"), append([]string{resolveIP.String()}, nameServers...), searchDomains, dnsOptions) - if err != nil { + if err := resolvconf.New(&resolvconf.Params{ + Path: filepath.Join(rootlessNetNsDir, "resolv.conf"), + // fake the netns since we want to filter localhost + Namespaces: []specs.LinuxNamespace{ + {Type: specs.NetworkNamespace}, + }, + IPv6Enabled: netOptions.enableIPv6, + KeepHostServers: true, + Nameservers: []string{resolveIP.String()}, + }); err != nil { return nil, errors.Wrap(err, "failed to create rootless netns resolv.conf") } - // create cni directories to store files // they will be bind mounted to the correct location in a extra mount ns err = os.MkdirAll(filepath.Join(rootlessNetNsDir, persistentCNIDir), 0700) @@ -1089,7 +1085,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e func (c *Container) joinedNetworkNSPath() string { for _, namespace := range c.config.Spec.Linux.Namespaces { - if namespace.Type == spec.NetworkNamespace { + if namespace.Type == specs.NetworkNamespace { return namespace.Path } } diff --git a/libpod/oci.go b/libpod/oci.go index 09f856ac7..90862969c 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -12,9 +12,7 @@ import ( // management logic - e.g., we do not expect it to determine on its own that // calling 'UnpauseContainer()' on a container that is not paused is an error. // The code calling the OCIRuntime will manage this. -// TODO: May want to move the Attach() code under this umbrella. It's highly OCI -// runtime dependent. -// TODO: May want to move the conmon cleanup code here too - it depends on +// TODO: May want to move the conmon cleanup code here - it depends on // Conmon being in use. type OCIRuntime interface { // Name returns the name of the runtime. @@ -52,6 +50,8 @@ type OCIRuntime interface { // UnpauseContainer unpauses the given container. UnpauseContainer(ctr *Container) error + // Attach to a container. + Attach(ctr *Container, params *AttachOptions) error // HTTPAttach performs an attach intended to be transported over HTTP. // For terminal attach, the container's output will be directly streamed // to output; otherwise, STDOUT and STDERR will be multiplexed, with @@ -149,6 +149,30 @@ type OCIRuntime interface { RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) } +// AttachOptions are options used when attached to a container or an exec +// session. +type AttachOptions struct { + // Streams are the streams to attach to. + Streams *define.AttachStreams + // DetachKeys containers the key combination that will detach from the + // attach session. Empty string is assumed as no detach keys - user + // detach is impossible. If unset, defaults from containers.conf will be + // used. + DetachKeys *string + // InitialSize is the initial size of the terminal. Set before the + // attach begins. + InitialSize *define.TerminalSize + // AttachReady signals when the attach has successfully completed and + // streaming has begun. + AttachReady chan<- bool + // Start indicates that the container should be started if it is not + // already running. + Start bool + // Started signals when the container has been successfully started. + // Required if Start is true, unused otherwise. + Started chan<- bool +} + // ExecOptions are options passed into ExecContainer. They control the command // that will be executed and how the exec will proceed. type ExecOptions struct { diff --git a/libpod/oci_attach_linux.go b/libpod/oci_conmon_attach_linux.go index 06f8f8719..155a8fbc3 100644 --- a/libpod/oci_attach_linux.go +++ b/libpod/oci_conmon_attach_linux.go @@ -38,19 +38,28 @@ func openUnixSocket(path string) (*net.UnixConn, error) { return net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: fmt.Sprintf("/proc/self/fd/%d", fd), Net: "unixpacket"}) } -// Attach to the given container -// Does not check if state is appropriate -// started is only required if startContainer is true -func (c *Container) attach(streams *define.AttachStreams, keys string, resize <-chan define.TerminalSize, startContainer bool, started chan bool, attachRdy chan<- bool) error { +// Attach to the given container. +// Does not check if state is appropriate. +// started is only required if startContainer is true. +func (r *ConmonOCIRuntime) Attach(c *Container, params *AttachOptions) error { passthrough := c.LogDriver() == define.PassthroughLogging - if !streams.AttachOutput && !streams.AttachError && !streams.AttachInput && !passthrough { + if params == nil || params.Streams == nil { + return errors.Wrapf(define.ErrInternal, "must provide parameters to Attach") + } + + if !params.Streams.AttachOutput && !params.Streams.AttachError && !params.Streams.AttachInput && !passthrough { return errors.Wrapf(define.ErrInvalidArg, "must provide at least one stream to attach to") } - if startContainer && started == nil { + if params.Start && params.Started == nil { return errors.Wrapf(define.ErrInternal, "started chan not passed when startContainer set") } + keys := config.DefaultDetachKeys + if params.DetachKeys != nil { + keys = *params.DetachKeys + } + detachKeys, err := processDetachKeys(keys) if err != nil { return err @@ -60,7 +69,12 @@ func (c *Container) attach(streams *define.AttachStreams, keys string, resize <- if !passthrough { logrus.Debugf("Attaching to container %s", c.ID()) - registerResizeFunc(resize, c.bundlePath()) + // If we have a resize, do it. + if params.InitialSize != nil { + if err := r.AttachResize(c, *params.InitialSize); err != nil { + return err + } + } attachSock, err := c.AttachSocketPath() if err != nil { @@ -80,22 +94,22 @@ func (c *Container) attach(streams *define.AttachStreams, keys string, resize <- // If starting was requested, start the container and notify when that's // done. - if startContainer { + if params.Start { if err := c.start(); err != nil { return err } - started <- true + params.Started <- true } if passthrough { return nil } - receiveStdoutError, stdinDone := setupStdioChannels(streams, conn, detachKeys) - if attachRdy != nil { - attachRdy <- true + receiveStdoutError, stdinDone := setupStdioChannels(params.Streams, conn, detachKeys) + if params.AttachReady != nil { + params.AttachReady <- true } - return readStdio(conn, streams, receiveStdoutError, stdinDone) + return readStdio(conn, params.Streams, receiveStdoutError, stdinDone) } // Attach to the given container's exec session diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index 6aa7ce6dc..0c1ee61d3 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -411,8 +411,8 @@ func (r *ConmonOCIRuntime) KillContainer(ctr *Container, signal uint, all bool) if err2 := r.UpdateContainerStatus(ctr); err2 != nil { logrus.Infof("Error updating status for container %s: %v", ctr.ID(), err2) } - if ctr.state.State == define.ContainerStateExited { - return nil + if ctr.ensureState(define.ContainerStateStopped, define.ContainerStateExited) { + return define.ErrCtrStateInvalid } return errors.Wrapf(err, "error sending signal to container %s", ctr.ID()) } diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go index 86f54c02e..fd8160830 100644 --- a/libpod/oci_missing.go +++ b/libpod/oci_missing.go @@ -108,6 +108,11 @@ func (r *MissingRuntime) UnpauseContainer(ctr *Container) error { return r.printError() } +// Attach is not available as the runtime is missing +func (r *MissingRuntime) Attach(ctr *Container, params *AttachOptions) error { + return r.printError() +} + // HTTPAttach is not available as the runtime is missing func (r *MissingRuntime) HTTPAttach(ctr *Container, req *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, detachKeys *string, cancel <-chan bool, hijackDone chan<- bool, streamAttach, streamLogs bool) error { return r.printError() diff --git a/libpod/options.go b/libpod/options.go index a02c05537..8b3b07efa 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -435,6 +435,21 @@ func WithDefaultInfraCommand(cmd string) RuntimeOption { } } +// WithReset instructs libpod to reset all storage to factory defaults. +// All containers, pods, volumes, images, and networks will be removed. +// All directories created by Libpod will be removed. +func WithReset() RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + rt.doReset = true + + return nil + } +} + // WithRenumber instructs libpod to perform a lock renumbering while // initializing. This will handle migrations from early versions of libpod with // file locks to newer versions with SHM locking, as well as changes in the @@ -2159,3 +2174,17 @@ func WithPasswdEntry(passwdEntry string) CtrCreateOption { return nil } } + +// WithMountAllDevices sets the option to mount all of a privileged container's +// host devices +func WithMountAllDevices() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.MountAllDevices = true + + return nil + } +} diff --git a/libpod/pod.go b/libpod/pod.go index 3c8dc43d4..108317637 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -178,8 +178,8 @@ func (p *Pod) NetworkMode() string { return infra.NetworkMode() } -// PidMode returns the PID mode given by the user ex: pod, private... -func (p *Pod) PidMode() string { +// Namespace Mode returns the given NS mode provided by the user ex: host, private... +func (p *Pod) NamespaceMode(kind specs.LinuxNamespaceType) string { infra, err := p.runtime.GetContainer(p.state.InfraContainerID) if err != nil { return "" @@ -187,28 +187,7 @@ func (p *Pod) PidMode() string { ctrSpec := infra.config.Spec if ctrSpec != nil && ctrSpec.Linux != nil { for _, ns := range ctrSpec.Linux.Namespaces { - if ns.Type == specs.PIDNamespace { - if ns.Path != "" { - return fmt.Sprintf("ns:%s", ns.Path) - } - return "private" - } - } - return "host" - } - return "" -} - -// PidMode returns the PID mode given by the user ex: pod, private... -func (p *Pod) UserNSMode() string { - infra, err := p.infraContainer() - if err != nil { - return "" - } - ctrSpec := infra.config.Spec - if ctrSpec != nil && ctrSpec.Linux != nil { - for _, ns := range ctrSpec.Linux.Namespaces { - if ns.Type == specs.UserNamespace { + if ns.Type == kind { if ns.Path != "" { return fmt.Sprintf("ns:%s", ns.Path) } diff --git a/libpod/pod_api.go b/libpod/pod_api.go index 1c1e15984..fefe0e329 100644 --- a/libpod/pod_api.go +++ b/libpod/pod_api.go @@ -9,6 +9,7 @@ import ( "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/parallel" "github.com/containers/podman/v4/pkg/rootless" + "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -673,8 +674,8 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) { infraConfig.CPUPeriod = p.CPUPeriod() infraConfig.CPUQuota = p.CPUQuota() infraConfig.CPUSetCPUs = p.ResourceLim().CPU.Cpus - infraConfig.PidNS = p.PidMode() - infraConfig.UserNS = p.UserNSMode() + infraConfig.PidNS = p.NamespaceMode(specs.PIDNamespace) + infraConfig.UserNS = p.NamespaceMode(specs.UserNamespace) namedVolumes, mounts := infra.SortUserVolumes(infra.config.Spec) inspectMounts, err = infra.GetMounts(namedVolumes, infra.config.ImageVolumes, mounts) infraSecurity = infra.GetSecurityOptions() diff --git a/libpod/reset.go b/libpod/reset.go index 28d0ee3f6..30eab50fb 100644 --- a/libpod/reset.go +++ b/libpod/reset.go @@ -17,8 +17,78 @@ import ( "github.com/sirupsen/logrus" ) +// removeAllDirs removes all Podman storage directories. It is intended to be +// used as a backup for reset() when that function cannot be used due to +// failures in initializing libpod. +// It does not expect that all the directories match what is in use by Podman, +// as this is a common failure point for `system reset`. As such, our ability to +// interface with containers and pods is somewhat limited. +// This function assumes that we do not have a working c/storage store. +func (r *Runtime) removeAllDirs() error { + var lastErr error + + // Grab the runtime alive lock. + // This ensures that no other Podman process can run while we are doing + // a reset, so no race conditions with containers/pods/etc being created + // while we are resetting storage. + // TODO: maybe want a helper for getting the path? This is duped from + // runtime.go + runtimeAliveLock := filepath.Join(r.config.Engine.TmpDir, "alive.lck") + aliveLock, err := storage.GetLockfile(runtimeAliveLock) + if err != nil { + logrus.Errorf("Lock runtime alive lock %s: %v", runtimeAliveLock, err) + } else { + aliveLock.Lock() + defer aliveLock.Unlock() + } + + // We do not have a store - so we can't really try and remove containers + // or pods or volumes... + // Try and remove the directories, in hopes that they are unmounted. + // This is likely to fail but it's the best we can do. + + // Volume path + if err := os.RemoveAll(r.config.Engine.VolumePath); err != nil { + lastErr = errors.Wrapf(err, "removing volume path") + } + + // Tmpdir + if err := os.RemoveAll(r.config.Engine.TmpDir); err != nil { + if lastErr != nil { + logrus.Errorf("Reset: %v", lastErr) + } + lastErr = errors.Wrapf(err, "removing tmp dir") + } + + // Runroot + if err := os.RemoveAll(r.storageConfig.RunRoot); err != nil { + if lastErr != nil { + logrus.Errorf("Reset: %v", lastErr) + } + lastErr = errors.Wrapf(err, "removing run root") + } + + // Static dir + if err := os.RemoveAll(r.config.Engine.StaticDir); err != nil { + if lastErr != nil { + logrus.Errorf("Reset: %v", lastErr) + } + lastErr = errors.Wrapf(err, "removing static dir") + } + + // Graph root + if err := os.RemoveAll(r.storageConfig.GraphRoot); err != nil { + if lastErr != nil { + logrus.Errorf("Reset: %v", lastErr) + } + lastErr = errors.Wrapf(err, "removing graph root") + } + + return lastErr +} + // Reset removes all storage -func (r *Runtime) Reset(ctx context.Context) error { +func (r *Runtime) reset(ctx context.Context) error { var timeout *uint pods, err := r.GetAllPods() if err != nil { diff --git a/libpod/runtime.go b/libpod/runtime.go index e268c2d17..6c8a99846 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -96,6 +96,10 @@ type Runtime struct { // This bool is just needed so that we can set it for netavark interface. syslog bool + // doReset indicates that the runtime should perform a system reset. + // All Podman files will be removed. + doReset bool + // doRenumber indicates that the runtime should perform a lock renumber // during initialization. // Once the runtime has been initialized and returned, this variable is @@ -235,6 +239,11 @@ func newRuntimeFromConfig(conf *config.Config, options ...RuntimeOption) (*Runti runtime.config.CheckCgroupsAndAdjustConfig() + // If resetting storage, do *not* return a runtime. + if runtime.doReset { + return nil, nil + } + return runtime, nil } @@ -305,6 +314,13 @@ func makeRuntime(runtime *Runtime) (retErr error) { } runtime.conmonPath = cPath + if runtime.noStore && runtime.doReset { + return errors.Wrapf(define.ErrInvalidArg, "cannot perform system reset if runtime is not creating a store") + } + if runtime.doReset && runtime.doRenumber { + return errors.Wrapf(define.ErrInvalidArg, "cannot perform system reset while renumbering locks") + } + // Make the static files directory if it does not exist if err := os.MkdirAll(runtime.config.Engine.StaticDir, 0700); err != nil { // The directory is allowed to exist @@ -339,6 +355,20 @@ func makeRuntime(runtime *Runtime) (retErr error) { // Grab config from the database so we can reset some defaults dbConfig, err := runtime.state.GetDBConfig() if err != nil { + if runtime.doReset { + // We can at least delete the DB and the static files + // directory. + // Can't safely touch anything else because we aren't + // sure of other directories. + if err := runtime.state.Close(); err != nil { + logrus.Errorf("Closing database connection: %v", err) + } else { + if err := os.RemoveAll(runtime.config.Engine.StaticDir); err != nil { + logrus.Errorf("Removing static files directory %v: %v", runtime.config.Engine.StaticDir, err) + } + } + } + return errors.Wrapf(err, "error retrieving runtime configuration from database") } @@ -372,7 +402,13 @@ func makeRuntime(runtime *Runtime) (retErr error) { // Validate our config against the database, now that we've set our // final storage configuration if err := runtime.state.ValidateDBConfig(runtime); err != nil { - return err + // If we are performing a storage reset: continue on with a + // warning. Otherwise we can't `system reset` after a change to + // the core paths. + if !runtime.doReset { + return err + } + logrus.Errorf("Runtime paths differ from those stored in database, storage reset may not remove all files") } if err := runtime.state.SetNamespace(runtime.config.Engine.Namespace); err != nil { @@ -394,6 +430,14 @@ func makeRuntime(runtime *Runtime) (retErr error) { } else if runtime.noStore { logrus.Debug("No store required. Not opening container store.") } else if err := runtime.configureStore(); err != nil { + // Make a best-effort attempt to clean up if performing a + // storage reset. + if runtime.doReset { + if err := runtime.removeAllDirs(); err != nil { + logrus.Errorf("Removing libpod directories: %v", err) + } + } + return err } defer func() { @@ -575,6 +619,18 @@ func makeRuntime(runtime *Runtime) (retErr error) { return err } + // If we're resetting storage, do it now. + // We will not return a valid runtime. + // TODO: Plumb this context out so it can be set. + if runtime.doReset { + // Mark the runtime as valid, so normal functionality "mostly" + // works and we can use regular functions to remove + // ctrs/pods/etc + runtime.valid = true + + return runtime.reset(context.Background()) + } + // If we're renumbering locks, do it now. // It breaks out of normal runtime init, and will not return a valid // runtime. @@ -818,7 +874,7 @@ func (r *Runtime) DeferredShutdown(force bool) { // still containers running or mounted func (r *Runtime) Shutdown(force bool) error { if !r.valid { - return define.ErrRuntimeStopped + return nil } if r.workerChannel != nil { @@ -1067,7 +1123,7 @@ func (r *Runtime) mergeDBConfig(dbConfig *DBConfig) { if !r.storageSet.GraphDriverNameSet && dbConfig.GraphDriver != "" { if r.storageConfig.GraphDriverName != dbConfig.GraphDriver && r.storageConfig.GraphDriverName != "" { - logrus.Errorf("User-selected graph driver %q overwritten by graph driver %q from database - delete libpod local files to resolve", + logrus.Errorf("User-selected graph driver %q overwritten by graph driver %q from database - delete libpod local files to resolve. May prevent use of images created by other tools", r.storageConfig.GraphDriverName, dbConfig.GraphDriver) } r.storageConfig.GraphDriverName = dbConfig.GraphDriver |