summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/adapter/runtime.go93
-rw-r--r--pkg/adapter/runtime_remote.go75
-rw-r--r--pkg/rootless/rootless.go9
-rw-r--r--pkg/rootless/rootless_linux.go96
-rw-r--r--pkg/rootless/rootless_unsupported.go33
-rw-r--r--pkg/spec/spec.go9
-rw-r--r--pkg/util/utils.go23
-rw-r--r--pkg/varlinkapi/events.go56
-rw-r--r--pkg/varlinkapi/system.go1
9 files changed, 328 insertions, 67 deletions
diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go
index 732b89530..a0951f677 100644
--- a/pkg/adapter/runtime.go
+++ b/pkg/adapter/runtime.go
@@ -3,11 +3,13 @@
package adapter
import (
+ "bufio"
"context"
"io"
"io/ioutil"
"os"
"strconv"
+ "text/template"
"github.com/containers/buildah"
"github.com/containers/buildah/imagebuildah"
@@ -16,7 +18,9 @@ import (
"github.com/containers/image/types"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
@@ -337,3 +341,92 @@ func IsImageNotFound(err error) bool {
func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.HealthCheckStatus, error) {
return r.Runtime.HealthCheck(c.InputArgs[0])
}
+
+// JoinOrCreateRootlessPod joins the specified pod if it is running or it creates a new user namespace
+// if the pod is stopped
+func (r *LocalRuntime) JoinOrCreateRootlessPod(pod *Pod) (bool, int, error) {
+ if os.Geteuid() == 0 {
+ return false, 0, nil
+ }
+ opts := rootless.Opts{
+ Argument: pod.ID(),
+ }
+
+ inspect, err := pod.Inspect()
+ if err != nil {
+ return false, 0, err
+ }
+ for _, ctr := range inspect.Containers {
+ prevCtr, err := r.LookupContainer(ctr.ID)
+ if err != nil {
+ return false, -1, err
+ }
+ s, err := prevCtr.State()
+ if err != nil {
+ return false, -1, err
+ }
+ if s != libpod.ContainerStateRunning && s != libpod.ContainerStatePaused {
+ continue
+ }
+ data, err := ioutil.ReadFile(prevCtr.Config().ConmonPidFile)
+ if err != nil {
+ return false, -1, errors.Wrapf(err, "cannot read conmon PID file %q", prevCtr.Config().ConmonPidFile)
+ }
+ conmonPid, err := strconv.Atoi(string(data))
+ if err != nil {
+ return false, -1, errors.Wrapf(err, "cannot parse PID %q", data)
+ }
+ return rootless.JoinDirectUserAndMountNSWithOpts(uint(conmonPid), &opts)
+ }
+
+ return rootless.BecomeRootInUserNSWithOpts(&opts)
+}
+
+// Events is a wrapper to libpod to obtain libpod/podman events
+func (r *LocalRuntime) Events(c *cliconfig.EventValues) error {
+ var (
+ fromStart bool
+ eventsError error
+ )
+ options, err := shared.GenerateEventOptions(c.Filter, c.Since, c.Until)
+ if err != nil {
+ return errors.Wrapf(err, "unable to generate event options")
+ }
+ tmpl, err := template.New("events").Parse(c.Format)
+ if err != nil {
+ return err
+ }
+ if len(c.Since) > 0 || len(c.Until) > 0 {
+ fromStart = true
+ }
+ eventChannel := make(chan *events.Event)
+ go func() {
+ eventsError = r.Runtime.Events(fromStart, c.Stream, options, eventChannel)
+ }()
+
+ if eventsError != nil {
+ return eventsError
+ }
+ if err != nil {
+ return errors.Wrapf(err, "unable to tail the events log")
+ }
+ w := bufio.NewWriter(os.Stdout)
+ for event := range eventChannel {
+ if len(c.Format) > 0 {
+ if err := tmpl.Execute(w, event); err != nil {
+ return err
+ }
+ } else {
+ if _, err := w.Write([]byte(event.ToHumanReadable())); err != nil {
+ return err
+ }
+ }
+ if _, err := w.Write([]byte("\n")); err != nil {
+ return err
+ }
+ if err := w.Flush(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go
index 10c25c3f3..01f774dbd 100644
--- a/pkg/adapter/runtime_remote.go
+++ b/pkg/adapter/runtime_remote.go
@@ -10,6 +10,7 @@ import (
"io/ioutil"
"os"
"strings"
+ "text/template"
"time"
"github.com/containers/buildah/imagebuildah"
@@ -18,6 +19,7 @@ import (
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/utils"
"github.com/containers/storage/pkg/archive"
@@ -751,3 +753,76 @@ func IsImageNotFound(err error) bool {
func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.HealthCheckStatus, error) {
return -1, libpod.ErrNotImplemented
}
+
+// JoinOrCreateRootlessPod joins the specified pod if it is running or it creates a new user namespace
+// if the pod is stopped
+func (r *LocalRuntime) JoinOrCreateRootlessPod(pod *Pod) (bool, int, error) {
+ // Nothing to do in the remote case
+ return true, 0, nil
+}
+
+// Events monitors libpod/podman events over a varlink connection
+func (r *LocalRuntime) Events(c *cliconfig.EventValues) error {
+ reply, err := iopodman.GetEvents().Send(r.Conn, uint64(varlink.More), c.Filter, c.Since, c.Stream, c.Until)
+ if err != nil {
+ return errors.Wrapf(err, "unable to obtain events")
+ }
+
+ w := bufio.NewWriter(os.Stdout)
+ tmpl, err := template.New("events").Parse(c.Format)
+ if err != nil {
+ return err
+ }
+
+ for {
+ returnedEvent, flags, err := reply()
+ if err != nil {
+ // When the error handling is back into podman, we can flip this to a better way to check
+ // for problems. For now, this works.
+ return err
+ }
+ if returnedEvent.Time == "" && returnedEvent.Status == "" && returnedEvent.Type == "" {
+ // We got a blank event return, signals end of stream in certain cases
+ break
+ }
+ eTime, err := time.Parse(time.RFC3339Nano, returnedEvent.Time)
+ if err != nil {
+ return errors.Wrapf(err, "unable to parse time of event %s", returnedEvent.Time)
+ }
+ eType, err := events.StringToType(returnedEvent.Type)
+ if err != nil {
+ return err
+ }
+ eStatus, err := events.StringToStatus(returnedEvent.Status)
+ if err != nil {
+ return err
+ }
+ event := events.Event{
+ ID: returnedEvent.Id,
+ Image: returnedEvent.Image,
+ Name: returnedEvent.Name,
+ Status: eStatus,
+ Time: eTime,
+ Type: eType,
+ }
+ if len(c.Format) > 0 {
+ if err := tmpl.Execute(w, event); err != nil {
+ return err
+ }
+ } else {
+ if _, err := w.Write([]byte(event.ToHumanReadable())); err != nil {
+ return err
+ }
+ }
+ if _, err := w.Write([]byte("\n")); err != nil {
+ return err
+ }
+ if err := w.Flush(); err != nil {
+ return err
+ }
+ if flags&varlink.Continues == 0 {
+ break
+ }
+ }
+ return nil
+}
diff --git a/pkg/rootless/rootless.go b/pkg/rootless/rootless.go
new file mode 100644
index 000000000..a531e43ce
--- /dev/null
+++ b/pkg/rootless/rootless.go
@@ -0,0 +1,9 @@
+package rootless
+
+// Opts allows to customize how re-execing to a rootless process is done
+type Opts struct {
+ // Argument overrides the arguments on the command line
+ // for the re-execed process. The process in the namespace
+ // must use rootless.Argument() to read its value.
+ Argument string
+}
diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go
index 933cfa2c2..b2677f7d9 100644
--- a/pkg/rootless/rootless_linux.go
+++ b/pkg/rootless/rootless_linux.go
@@ -11,7 +11,6 @@ import (
"os/user"
"runtime"
"strconv"
- "strings"
"sync"
"syscall"
"unsafe"
@@ -61,6 +60,11 @@ func SkipStorageSetup() bool {
return skipStorageSetup
}
+// Argument returns the argument that was set for the rootless session.
+func Argument() string {
+ return os.Getenv("_LIBPOD_ROOTLESS_ARG")
+}
+
// GetRootlessUID returns the UID of the user in the parent userNS
func GetRootlessUID() int {
uidEnv := os.Getenv("_LIBPOD_ROOTLESS_UID")
@@ -135,8 +139,16 @@ func JoinNS(pid uint, preserveFDs int) (bool, int, error) {
// JoinDirectUserAndMountNS re-exec podman in a new userNS and join the user and mount
// namespace of the specified PID without looking up its parent. Useful to join directly
-// the conmon process.
+// the conmon process. It is a convenience function for JoinDirectUserAndMountNSWithOpts
+// with a default configuration.
func JoinDirectUserAndMountNS(pid uint) (bool, int, error) {
+ return JoinDirectUserAndMountNSWithOpts(pid, nil)
+}
+
+// JoinDirectUserAndMountNSWithOpts re-exec podman in a new userNS and join the user and
+// mount namespace of the specified PID without looking up its parent. Useful to join
+// directly the conmon process.
+func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) {
if os.Geteuid() == 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" {
return false, -1, nil
}
@@ -153,6 +165,12 @@ func JoinDirectUserAndMountNS(pid uint) (bool, int, error) {
}
defer userNS.Close()
+ if opts != nil && opts.Argument != "" {
+ if err := os.Setenv("_LIBPOD_ROOTLESS_ARG", opts.Argument); err != nil {
+ return false, -1, err
+ }
+ }
+
pidC := C.reexec_userns_join(C.int(userNS.Fd()), C.int(mountNS.Fd()))
if int(pidC) < 0 {
return false, -1, errors.Errorf("cannot re-exec process")
@@ -192,27 +210,19 @@ func JoinNSPath(path string) (bool, int, error) {
return true, int(ret), nil
}
-const defaultMinimumMappings = 65536
-
-func getMinimumIDs(p string) int {
- content, err := ioutil.ReadFile(p)
- if err != nil {
- logrus.Debugf("error reading data from %q, use a default value of %d", p, defaultMinimumMappings)
- return defaultMinimumMappings
- }
- ret, err := strconv.Atoi(strings.TrimSuffix(string(content), "\n"))
- if err != nil {
- logrus.Debugf("error reading data from %q, use a default value of %d", p, defaultMinimumMappings)
- return defaultMinimumMappings
- }
- return ret + 1
-}
-
// BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed
// into a new user namespace and the return code from the re-executed podman process.
// If podman was re-executed the caller needs to propagate the error code returned by the child
-// process.
+// process. It is a convenience function for BecomeRootInUserNSWithOpts with a default configuration.
func BecomeRootInUserNS() (bool, int, error) {
+ return BecomeRootInUserNSWithOpts(nil)
+}
+
+// BecomeRootInUserNSWithOpts re-exec podman in a new userNS. It returns whether podman was
+// re-execute into a new user namespace and the return code from the re-executed podman process.
+// If podman was re-executed the caller needs to propagate the error code returned by the child
+// process.
+func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) {
if os.Geteuid() == 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" {
if os.Getenv("_LIBPOD_USERNS_CONFIGURED") == "init" {
return false, 0, runInUser()
@@ -231,53 +241,30 @@ func BecomeRootInUserNS() (bool, int, error) {
defer w.Close()
defer w.Write([]byte("0"))
+ if opts != nil && opts.Argument != "" {
+ if err := os.Setenv("_LIBPOD_ROOTLESS_ARG", opts.Argument); err != nil {
+ return false, -1, err
+ }
+ }
+
pidC := C.reexec_in_user_namespace(C.int(r.Fd()))
pid := int(pidC)
if pid < 0 {
return false, -1, errors.Errorf("cannot re-exec process")
}
- allowSingleIDMapping := os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") != ""
-
var uids, gids []idtools.IDMap
username := os.Getenv("USER")
if username == "" {
user, err := user.LookupId(fmt.Sprintf("%d", os.Getuid()))
- if err != nil && !allowSingleIDMapping {
- if os.IsNotExist(err) {
- return false, 0, errors.Wrapf(err, "/etc/subuid or /etc/subgid does not exist, see subuid/subgid man pages for information on these files")
- }
- return false, 0, errors.Wrapf(err, "could not find user by UID nor USER env was set")
- }
if err == nil {
username = user.Username
}
}
mappings, err := idtools.NewIDMappings(username, username)
- if !allowSingleIDMapping {
- if err != nil {
- return false, -1, err
- }
-
- availableGIDs, availableUIDs := 0, 0
- for _, i := range mappings.UIDs() {
- availableUIDs += i.Size
- }
-
- minUIDs := getMinimumIDs("/proc/sys/kernel/overflowuid")
- if availableUIDs < minUIDs {
- return false, 0, fmt.Errorf("not enough UIDs available for the user, at least %d are needed", minUIDs)
- }
-
- for _, i := range mappings.GIDs() {
- availableGIDs += i.Size
- }
- minGIDs := getMinimumIDs("/proc/sys/kernel/overflowgid")
- if availableGIDs < minGIDs {
- return false, 0, fmt.Errorf("not enough GIDs available for the user, at least %d are needed", minGIDs)
- }
- }
- if err == nil {
+ if err != nil {
+ logrus.Warnf("cannot find mappings for user %s: %v", username, err)
+ } else {
uids = mappings.UIDs()
gids = mappings.GIDs()
}
@@ -285,12 +272,10 @@ func BecomeRootInUserNS() (bool, int, error) {
uidsMapped := false
if mappings != nil && uids != nil {
err := tryMappingTool("newuidmap", pid, os.Getuid(), uids)
- if !allowSingleIDMapping && err != nil {
- return false, 0, err
- }
uidsMapped = err == nil
}
if !uidsMapped {
+ logrus.Warnf("using rootless single mapping into the namespace. This might break some images. Check /etc/subuid and /etc/subgid for adding subids")
setgroups := fmt.Sprintf("/proc/%d/setgroups", pid)
err = ioutil.WriteFile(setgroups, []byte("deny\n"), 0666)
if err != nil {
@@ -307,9 +292,6 @@ func BecomeRootInUserNS() (bool, int, error) {
gidsMapped := false
if mappings != nil && gids != nil {
err := tryMappingTool("newgidmap", pid, os.Getgid(), gids)
- if !allowSingleIDMapping && err != nil {
- return false, 0, err
- }
gidsMapped = err == nil
}
if !gidsMapped {
diff --git a/pkg/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go
index 1823c023e..54e70961b 100644
--- a/pkg/rootless/rootless_unsupported.go
+++ b/pkg/rootless/rootless_unsupported.go
@@ -11,10 +11,18 @@ func IsRootless() bool {
return false
}
+// BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed
+// into a new user namespace and the return code from the re-executed podman process.
+// If podman was re-executed the caller needs to propagate the error code returned by the child
+// process. It is a convenience function for BecomeRootInUserNSWithOpts with a default configuration.
+func BecomeRootInUserNS() (bool, int, error) {
+ return false, -1, errors.New("this function is not supported on this os")
+}
+
// BecomeRootInUserNS is a stub function that always returns false and an
// error on unsupported OS's
-func BecomeRootInUserNS() (bool, int, error) {
- return false, -1, errors.New("this function is not supported on this os1")
+func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) {
+ return false, -1, errors.New("this function is not supported on this os")
}
// GetRootlessUID returns the UID of the user in the parent userNS
@@ -34,18 +42,31 @@ func SkipStorageSetup() bool {
// JoinNS re-exec podman in a new userNS and join the user namespace of the specified
// PID.
func JoinNS(pid uint) (bool, int, error) {
- return false, -1, errors.New("this function is not supported on this os2")
+ return false, -1, errors.New("this function is not supported on this os")
}
// JoinNSPath re-exec podman in a new userNS and join the owner user namespace of the
// specified path.
func JoinNSPath(path string) (bool, int, error) {
- return false, -1, errors.New("this function is not supported on this os3")
+ return false, -1, errors.New("this function is not supported on this os")
+}
+
+// JoinDirectUserAndMountNSWithOpts re-exec podman in a new userNS and join the user and
+// mount namespace of the specified PID without looking up its parent. Useful to join
+// directly the conmon process.
+func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) {
+ return false, -1, errors.New("this function is not supported on this os")
}
// JoinDirectUserAndMountNS re-exec podman in a new userNS and join the user and mount
// namespace of the specified PID without looking up its parent. Useful to join directly
-// the conmon process.
+// the conmon process. It is a convenience function for JoinDirectUserAndMountNSWithOpts
+// with a default configuration.
func JoinDirectUserAndMountNS(pid uint) (bool, int, error) {
- return false, -1, errors.New("this function is not supported on this os4")
+ return false, -1, errors.New("this function is not supported on this os")
+}
+
+// Argument returns the argument that was set for the rootless session.
+func Argument() string {
+ return ""
}
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index 28a636fa6..32d47732b 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -454,10 +454,6 @@ func findMount(target string, mounts []*pmount.Info) (*pmount.Info, error) {
}
func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) {
- if config.PidMode.IsHost() && rootless.IsRootless() {
- return
- }
-
if !config.Privileged {
for _, mp := range []string{
"/proc/acpi",
@@ -469,10 +465,15 @@ func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator)
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware",
+ "/sys/fs/selinux",
} {
g.AddLinuxMaskedPaths(mp)
}
+ if config.PidMode.IsHost() && rootless.IsRootless() {
+ return
+ }
+
for _, rp := range []string{
"/proc/asound",
"/proc/bus",
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index a4576191b..d7e1ddd38 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -7,6 +7,7 @@ import (
"path/filepath"
"strings"
"syscall"
+ "time"
"github.com/BurntSushi/toml"
"github.com/containers/image/types"
@@ -347,3 +348,25 @@ func StorageConfigFile() string {
}
return storage.DefaultConfigFile
}
+
+// ParseInputTime takes the users input and to determine if it is valid and
+// returns a time format and error. The input is compared to known time formats
+// or a duration which implies no-duration
+func ParseInputTime(inputTime string) (time.Time, error) {
+ timeFormats := []string{time.RFC3339Nano, time.RFC3339, "2006-01-02T15:04:05", "2006-01-02T15:04:05.999999999",
+ "2006-01-02Z07:00", "2006-01-02"}
+ // iterate the supported time formats
+ for _, tf := range timeFormats {
+ t, err := time.Parse(tf, inputTime)
+ if err == nil {
+ return t, nil
+ }
+ }
+
+ // input might be a duration
+ duration, err := time.ParseDuration(inputTime)
+ if err != nil {
+ return time.Time{}, errors.Errorf("unable to interpret time value")
+ }
+ return time.Now().Add(-duration), nil
+}
diff --git a/pkg/varlinkapi/events.go b/pkg/varlinkapi/events.go
new file mode 100644
index 000000000..d3fe3d65f
--- /dev/null
+++ b/pkg/varlinkapi/events.go
@@ -0,0 +1,56 @@
+package varlinkapi
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/cmd/podman/varlink"
+ "github.com/containers/libpod/libpod/events"
+)
+
+// GetEvents is a remote endpoint to get events from the event log
+func (i *LibpodAPI) GetEvents(call iopodman.VarlinkCall, filter []string, since string, stream bool, until string) error {
+ var (
+ fromStart bool
+ eventsError error
+ event *events.Event
+ )
+ if call.WantsMore() {
+ call.Continues = true
+ }
+ filters, err := shared.GenerateEventOptions(filter, since, until)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ if len(since) > 0 || len(until) > 0 {
+ fromStart = true
+ }
+ eventChannel := make(chan *events.Event)
+ go func() {
+ eventsError = i.Runtime.Events(fromStart, stream, filters, eventChannel)
+ }()
+ if eventsError != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ for {
+ event = <-eventChannel
+ if event == nil {
+ call.Continues = false
+ break
+ }
+ call.ReplyGetEvents(iopodman.Event{
+ Id: event.ID,
+ Image: event.Image,
+ Name: event.Name,
+ Status: fmt.Sprintf("%s", event.Status),
+ Time: event.Time.Format(time.RFC3339Nano),
+ Type: fmt.Sprintf("%s", event.Type),
+ })
+ if !call.Continues {
+ // For a one-shot on events, we break out here
+ break
+ }
+ }
+ return call.ReplyGetEvents(iopodman.Event{})
+}
diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go
index 3f32615ec..816143e9f 100644
--- a/pkg/varlinkapi/system.go
+++ b/pkg/varlinkapi/system.go
@@ -52,6 +52,7 @@ func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error {
Mem_free: host["MemFree"].(int64),
Mem_total: host["MemTotal"].(int64),
Swap_free: host["SwapFree"].(int64),
+ Swap_total: host["SwapTotal"].(int64),
Arch: host["arch"].(string),
Cpus: int64(host["cpus"].(int)),
Hostname: host["hostname"].(string),