mirror of https://github.com/containers/podman.git
Vendor in latest containers/psgo code
This fixes a couple of issues with podman top. podman top --latest USER HUSER Now shows you the User inside of the containers usernamespace as well as the user on the host. podman top --latest capeff capbnd Now has headings that differentiatiate between the Capabiltiies. We also have support for ambient capabilities. Signed-off-by: Daniel J Walsh <dwalsh@redhat.com> Closes: #1286 Approved by: vrothberg
This commit is contained in:
parent
d20f3a5146
commit
37e3f47ef3
|
@ -12,7 +12,7 @@ github.com/containernetworking/cni v0.7.0-alpha1
|
||||||
github.com/containernetworking/plugins 1562a1e60ed101aacc5e08ed9dbeba8e9f3d4ec1
|
github.com/containernetworking/plugins 1562a1e60ed101aacc5e08ed9dbeba8e9f3d4ec1
|
||||||
github.com/containers/image 134f99bed228d6297dc01d152804f6f09f185418
|
github.com/containers/image 134f99bed228d6297dc01d152804f6f09f185418
|
||||||
github.com/containers/storage 17c7d1fee5603ccf6dd97edc14162fc1510e7e23
|
github.com/containers/storage 17c7d1fee5603ccf6dd97edc14162fc1510e7e23
|
||||||
github.com/containers/psgo 382fc951fe0a8aba62043862ce1a56f77524db87
|
github.com/containers/psgo master
|
||||||
github.com/coreos/go-systemd v14
|
github.com/coreos/go-systemd v14
|
||||||
github.com/cri-o/ocicni master
|
github.com/cri-o/ocicni master
|
||||||
github.com/cyphar/filepath-securejoin v0.2.1
|
github.com/cyphar/filepath-securejoin v0.2.1
|
||||||
|
|
|
@ -46,6 +46,8 @@ root 1 0 0.000 17.249905587s ? 0s sleep
|
||||||
### Format descriptors
|
### Format descriptors
|
||||||
The ps library is compatible with all AIX format descriptors of the ps command-line utility (see `man 1 ps` for details) but it also supports some additional descriptors that can be useful when seeking specific process-related information.
|
The ps library is compatible with all AIX format descriptors of the ps command-line utility (see `man 1 ps` for details) but it also supports some additional descriptors that can be useful when seeking specific process-related information.
|
||||||
|
|
||||||
|
- **capamb**
|
||||||
|
- Set of ambient capabilities. See capabilities(7) for more information.
|
||||||
- **capbnd**
|
- **capbnd**
|
||||||
- Set of bounding capabilities. See capabilities(7) for more information.
|
- Set of bounding capabilities. See capabilities(7) for more information.
|
||||||
- **capeff**
|
- **capeff**
|
||||||
|
|
|
@ -13,3 +13,12 @@ func ParsePIDNamespace(pid string) (string, error) {
|
||||||
}
|
}
|
||||||
return pidNS, nil
|
return pidNS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseUserNamespace returns the content of /proc/$pid/ns/user.
|
||||||
|
func ParseUserNamespace(pid string) (string, error) {
|
||||||
|
userNS, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/user", pid))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return userNS, nil
|
||||||
|
}
|
||||||
|
|
|
@ -4,8 +4,10 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containers/psgo/internal/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -160,8 +162,24 @@ type Status struct {
|
||||||
NonvoluntaryCtxtSwitches string
|
NonvoluntaryCtxtSwitches string
|
||||||
}
|
}
|
||||||
|
|
||||||
// readStatus is used for mocking in unit tests.
|
// readStatusUserNS joins the user namespace of pid and returns the content of
|
||||||
var readStatus = func(path string) ([]string, error) {
|
// /proc/pid/status as a string slice.
|
||||||
|
func readStatusUserNS(pid string) ([]string, error) {
|
||||||
|
path := fmt.Sprintf("/proc/%s/status", pid)
|
||||||
|
args := []string{"nsenter", "-U", "-t", pid, "cat", path}
|
||||||
|
|
||||||
|
c := exec.Command(args[0], args[1:]...)
|
||||||
|
output, err := c.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error executing %q: %v", strings.Join(args, " "), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Split(string(output), "\n"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readStatusDefault returns the content of /proc/pid/status as a string slice.
|
||||||
|
func readStatusDefault(pid string) ([]string, error) {
|
||||||
|
path := fmt.Sprintf("/proc/%s/status", pid)
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -175,15 +193,26 @@ var readStatus = func(path string) ([]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseStatus parses the /proc/$pid/status file and returns a *Status.
|
// ParseStatus parses the /proc/$pid/status file and returns a *Status.
|
||||||
func ParseStatus(pid string) (*Status, error) {
|
func ParseStatus(ctx *types.PsContext, pid string) (*Status, error) {
|
||||||
path := fmt.Sprintf("/proc/%s/status", pid)
|
var lines []string
|
||||||
lines, err := readStatus(path)
|
var err error
|
||||||
|
|
||||||
|
if ctx.JoinUserNS {
|
||||||
|
lines, err = readStatusUserNS(pid)
|
||||||
|
} else {
|
||||||
|
lines, err = readStatusDefault(pid)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return parseStatus(pid, lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseStatus extracts data from lines and returns a *Status.
|
||||||
|
func parseStatus(pid string, lines []string) (*Status, error) {
|
||||||
s := Status{}
|
s := Status{}
|
||||||
errUnexpectedInput := errors.New(fmt.Sprintf("unexpected input from %s", path))
|
errUnexpectedInput := fmt.Errorf("unexpected input from /proc/%s/status", pid)
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
fields := strings.Fields(line)
|
fields := strings.Fields(line)
|
||||||
if len(fields) < 2 {
|
if len(fields) < 2 {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/containers/psgo/internal/host"
|
"github.com/containers/psgo/internal/host"
|
||||||
"github.com/containers/psgo/internal/proc"
|
"github.com/containers/psgo/internal/proc"
|
||||||
|
"github.com/containers/psgo/internal/types"
|
||||||
"github.com/opencontainers/runc/libcontainer/user"
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -61,13 +62,13 @@ func LookupUID(uid string) (string, error) {
|
||||||
|
|
||||||
// New returns a new Process with the specified pid and parses the relevant
|
// New returns a new Process with the specified pid and parses the relevant
|
||||||
// data from /proc and /dev.
|
// data from /proc and /dev.
|
||||||
func New(pid string) (*Process, error) {
|
func New(ctx *types.PsContext, pid string) (*Process, error) {
|
||||||
p := Process{Pid: pid}
|
p := Process{Pid: pid}
|
||||||
|
|
||||||
if err := p.parseStat(); err != nil {
|
if err := p.parseStat(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := p.parseStatus(); err != nil {
|
if err := p.parseStatus(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := p.parseCmdLine(); err != nil {
|
if err := p.parseCmdLine(); err != nil {
|
||||||
|
@ -88,10 +89,10 @@ func New(pid string) (*Process, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromPIDs creates a new Process for each pid.
|
// FromPIDs creates a new Process for each pid.
|
||||||
func FromPIDs(pids []string) ([]*Process, error) {
|
func FromPIDs(ctx *types.PsContext, pids []string) ([]*Process, error) {
|
||||||
processes := []*Process{}
|
processes := []*Process{}
|
||||||
for _, pid := range pids {
|
for _, pid := range pids {
|
||||||
p, err := New(pid)
|
p, err := New(ctx, pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
// proc parsing is racy
|
// proc parsing is racy
|
||||||
|
@ -116,8 +117,8 @@ func (p *Process) parseStat() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseStatus parses /proc/$pid/status.
|
// parseStatus parses /proc/$pid/status.
|
||||||
func (p *Process) parseStatus() error {
|
func (p *Process) parseStatus(ctx *types.PsContext) error {
|
||||||
s, err := proc.ParseStatus(p.Pid)
|
s, err := proc.ParseStatus(ctx, p.Pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -135,7 +136,7 @@ func (p *Process) parseCmdLine() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsePIDNamespace parses all host-related data fields.
|
// parsePIDNamespace sets the PID namespace.
|
||||||
func (p *Process) parsePIDNamespace() error {
|
func (p *Process) parsePIDNamespace() error {
|
||||||
pidNS, err := proc.ParsePIDNamespace(p.Pid)
|
pidNS, err := proc.ParsePIDNamespace(p.Pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
// PsContext controls some internals of the psgo library.
|
||||||
|
type PsContext struct {
|
||||||
|
// JoinUserNS will force /proc and /dev parsing from within each PIDs
|
||||||
|
// user namespace.
|
||||||
|
JoinUserNS bool
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/containers/psgo/internal/dev"
|
"github.com/containers/psgo/internal/dev"
|
||||||
"github.com/containers/psgo/internal/proc"
|
"github.com/containers/psgo/internal/proc"
|
||||||
"github.com/containers/psgo/internal/process"
|
"github.com/containers/psgo/internal/process"
|
||||||
|
"github.com/containers/psgo/internal/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
@ -182,24 +183,29 @@ var (
|
||||||
header: "VSZ",
|
header: "VSZ",
|
||||||
procFn: processVSZ,
|
procFn: processVSZ,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
normal: "capamb",
|
||||||
|
header: "AMBIENT CAPS",
|
||||||
|
procFn: processCAPAMB,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
normal: "capinh",
|
normal: "capinh",
|
||||||
header: "CAPABILITIES",
|
header: "INHERITED CAPS",
|
||||||
procFn: processCAPINH,
|
procFn: processCAPINH,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
normal: "capprm",
|
normal: "capprm",
|
||||||
header: "CAPABILITIES",
|
header: "PERMITTED CAPS",
|
||||||
procFn: processCAPPRM,
|
procFn: processCAPPRM,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
normal: "capeff",
|
normal: "capeff",
|
||||||
header: "CAPABILITIES",
|
header: "EFFECTIVE CAPS",
|
||||||
procFn: processCAPEFF,
|
procFn: processCAPEFF,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
normal: "capbnd",
|
normal: "capbnd",
|
||||||
header: "CAPABILITIES",
|
header: "BOUNDING CAPS",
|
||||||
procFn: processCAPBND,
|
procFn: processCAPBND,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -276,6 +282,19 @@ func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string,
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
// extract user namespaces prior to joining the mount namespace
|
||||||
|
currentUserNs, err := proc.ParseUserNamespace("self")
|
||||||
|
if err != nil {
|
||||||
|
dataErr = errors.Wrapf(err, "error determining user namespace")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pidUserNs, err := proc.ParseUserNamespace(pid)
|
||||||
|
if err != nil {
|
||||||
|
dataErr = errors.Wrapf(err, "error determining user namespace of PID %s", pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// join the mount namespace of pid
|
||||||
fd, err := os.Open(fmt.Sprintf("/proc/%s/ns/mnt", pid))
|
fd, err := os.Open(fmt.Sprintf("/proc/%s/ns/mnt", pid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dataErr = err
|
dataErr = err
|
||||||
|
@ -290,12 +309,19 @@ func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string,
|
||||||
}
|
}
|
||||||
unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS)
|
unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS)
|
||||||
|
|
||||||
|
// extract all pids mentioned in pid's mount namespace
|
||||||
pids, err := proc.GetPIDs()
|
pids, err := proc.GetPIDs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dataErr = err
|
dataErr = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
processes, err := process.FromPIDs(pids)
|
|
||||||
|
ctx := types.PsContext{
|
||||||
|
// join the user NS if the pid's user NS is different
|
||||||
|
// to the caller's user NS.
|
||||||
|
JoinUserNS: currentUserNs != pidUserNs,
|
||||||
|
}
|
||||||
|
processes, err := process.FromPIDs(&ctx, pids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dataErr = err
|
dataErr = err
|
||||||
return
|
return
|
||||||
|
@ -324,7 +350,9 @@ func ProcessInfo(descriptors []string) ([][]string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
processes, err := process.FromPIDs(pids)
|
|
||||||
|
ctx := types.PsContext{JoinUserNS: false}
|
||||||
|
processes, err := process.FromPIDs(&ctx, pids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -340,7 +368,8 @@ func setHostProcesses(pid string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
processes, err := process.FromPIDs(pids)
|
ctx := types.PsContext{JoinUserNS: false}
|
||||||
|
processes, err := process.FromPIDs(&ctx, pids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -421,14 +450,14 @@ func processPPID(p *process.Process) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// processUSER returns the effective user name of the process. This will be
|
// processUSER returns the effective user name of the process. This will be
|
||||||
// the textual group ID, if it can be optained, or a decimal representation
|
// the textual user ID, if it can be optained, or a decimal representation
|
||||||
// otherwise.
|
// otherwise.
|
||||||
func processUSER(p *process.Process) (string, error) {
|
func processUSER(p *process.Process) (string, error) {
|
||||||
return process.LookupUID(p.Status.Uids[1])
|
return process.LookupUID(p.Status.Uids[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
// processRUSER returns the effective user name of the process. This will be
|
// processRUSER returns the effective user name of the process. This will be
|
||||||
// the textual group ID, if it can be optained, or a decimal representation
|
// the textual user ID, if it can be optained, or a decimal representation
|
||||||
// otherwise.
|
// otherwise.
|
||||||
func processRUSER(p *process.Process) (string, error) {
|
func processRUSER(p *process.Process) (string, error) {
|
||||||
return process.LookupUID(p.Status.Uids[0])
|
return process.LookupUID(p.Status.Uids[0])
|
||||||
|
@ -557,6 +586,13 @@ func parseCAP(cap string) (string, error) {
|
||||||
return strings.Join(caps, ","), nil
|
return strings.Join(caps, ","), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processCAPAMB returns the set of ambient capabilties associated with
|
||||||
|
// process p. If all capabilties are set, "full" is returned. If no
|
||||||
|
// capability is enabled, "none" is returned.
|
||||||
|
func processCAPAMB(p *process.Process) (string, error) {
|
||||||
|
return parseCAP(p.Status.CapAmb)
|
||||||
|
}
|
||||||
|
|
||||||
// processCAPINH returns the set of inheritable capabilties associated with
|
// processCAPINH returns the set of inheritable capabilties associated with
|
||||||
// process p. If all capabilties are set, "full" is returned. If no
|
// process p. If all capabilties are set, "full" is returned. If no
|
||||||
// capability is enabled, "none" is returned.
|
// capability is enabled, "none" is returned.
|
||||||
|
|
Loading…
Reference in New Issue