diff options
author | Valentin Rothberg <vrothberg@suse.com> | 2018-07-26 15:26:52 +0200 |
---|---|---|
committer | Atomic Bot <atomic-devel@projectatomic.io> | 2018-07-26 17:01:40 +0000 |
commit | 159f7f179bf63dee2710680a12164d7487e7e696 (patch) | |
tree | d3defe9fbb39411ba8dd6dd252815de62ddc824a /vendor | |
parent | d9ae17400d40403134f7cb484a7cb5e14fa02635 (diff) | |
download | podman-159f7f179bf63dee2710680a12164d7487e7e696.tar.gz podman-159f7f179bf63dee2710680a12164d7487e7e696.tar.bz2 podman-159f7f179bf63dee2710680a12164d7487e7e696.zip |
vendor latest containers/psgo
Signed-off-by: Valentin Rothberg <vrothberg@suse.com>
Closes: #1162
Approved by: rhatdan
Diffstat (limited to 'vendor')
13 files changed, 934 insertions, 618 deletions
diff --git a/vendor/github.com/containers/psgo/README.md b/vendor/github.com/containers/psgo/README.md index 5c4f6e150..7b779b539 100644 --- a/vendor/github.com/containers/psgo/README.md +++ b/vendor/github.com/containers/psgo/README.md @@ -1,23 +1,23 @@ -[![GoDoc](https://godoc.org/github.com/containers/psgo?status.svg)](https://godoc.org/github.com/containers/psgo/ps) [![Build Status](https://travis-ci.org/containers/psgo.svg?branch=master)](https://travis-ci.org/containers/psgo) +[![GoDoc](https://godoc.org/github.com/containers/psgo?status.svg)](https://godoc.org/github.com/containers/psgo) [![Build Status](https://travis-ci.org/containers/psgo.svg?branch=master)](https://travis-ci.org/containers/psgo) # psgo -A ps (1) AIX-format compatible golang library extended with various descriptors useful for displaying container-related data. +A ps(1) AIX-format compatible golang library extended with various descriptors useful for displaying container-related data. -The idea behind the library is to provide an easy to use way of extracting process-related data, just as ps (1) does. The problem when using ps (1) is that the ps format strings split columns with whitespaces, making the output nearly impossible to parse. It also adds some jitter as we have to fork and execute ps either in the container or filter the output afterwards, further limiting applicability. +The idea behind the library is to provide an easy to use way of extracting process-related data, just as ps(1) does. The problem when using ps(1) is that the ps format strings split columns with whitespaces, making the output nearly impossible to parse. It also adds some jitter as we have to fork and execute ps either in the container or filter the output afterwards, further limiting applicability. This library aims to make things a bit more comfortable, especially for container runtimes, as the API allows to join the mount namespace of a given process and will parse `/proc` and `/dev/` from there. The API consists of the following functions: - - `ps.ProcessInfo(format string) ([]string, error)` - - ProcessInfo returns the process information of all processes in the current mount namespace. The input format must be a comma-separated list of supported AIX format descriptors. If the input string is empty, the DefaultFormat is used. The return value is a slice of tab-separated strings, to easily use the output for column-based formatting (e.g., with the `text/tabwriter` package). + - `psgo.ProcessInfo(descriptors []string) ([][]string, error)` + - ProcessInfo returns the process information of all processes in the current mount namespace. The input descriptors must be a slice of supported AIX format descriptors in the normal form or in the code form, if supported. If the input descriptor slice is empty, the `psgo.DefaultDescriptors` are used. The return value contains the string slice of process data, one per process. - - `ps.JoinNamespaceAndProcessInfo(pid, format string) ([]string, error)` + - `psgo.JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string, error)` - JoinNamespaceAndProcessInfo has the same semantics as ProcessInfo but joins the mount namespace of the specified pid before extracting data from /proc. This way, we can extract the `/proc` data from a container without executing any command inside the container. - - `ps.ListDescriptors() []string` + - `psgo.ListDescriptors() []string` - ListDescriptors returns a sorted string slice of all supported AIX format descriptors in the normal form (e.g., "args,comm,user"). It can be useful in the context of bash-completion, help messages, etc. ### Listing processes -We can use the [psgo](https://github.com/containers/psgo/blob/master/psgo.go) tool from this project to test the core components of this library. First, let's build `psgo` via `make build`. The binary is now located under `./bin/psgo`. By default `psgo` displays data about all running processes in the current mount namespace, similar to the output of `ps -ef`. +We can use the [psgo](https://github.com/containers/psgo/blob/master/sample/sample.go) sample tool from this project to test the core components of this library. First, let's build `psgo` via `make build`. The binary is now located under `./bin/psgo`. By default `psgo` displays data about all running processes in the current mount namespace, similar to the output of `ps -ef`. ``` $ ./bin/psgo | head -n5 @@ -46,18 +46,26 @@ root 1 0 0.000 17.249905587s ? 0s sleep ### 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. +- **capbnd** + - Set of bounding capabilities. See capabilities(7) for more information. +- **capeff** + - Set of effective capabilities. See capabilities(7) for more information. - **capinh** - - Set of inheritable capabilities. See capabilities (7) for more information. + - Set of inheritable capabilities. See capabilities(7) for more information. - **capprm** - - Set of permitted capabilities. See capabilities (7) for more information. -- **capeff** - - Set of effective capabilities. See capabilities (7) for more information. -- **capbnd** - - Set of bounding capabilities. See capabilities (7) for more information. -- **seccomp** - - Seccomp mode of the process (i.e., disabled, strict or filter). See seccomp (2) for more information. + - Set of permitted capabilities. See capabilities(7) for more information. +- **hgroup** + - The corresponding effective group of a container process on the host. +- **hpid** + - The corresponding host PID of a container process. +- **huser** + - The corresponding effective user of a container process on the host. - **label** - Current security attributes of the process. +- **seccomp** + - Seccomp mode of the process (i.e., disabled, strict or filter). See seccomp(2) for more information. +- **state** + - Process state codes (e.g, **R** for *running*, **S** for *sleeping*). See proc(5) for more information. We can try out different format descriptors with the psgo binary: diff --git a/vendor/github.com/containers/psgo/ps/capabilities.go b/vendor/github.com/containers/psgo/internal/capabilities/capabilities.go index 4301792a5..8aba94f39 100644 --- a/vendor/github.com/containers/psgo/ps/capabilities.go +++ b/vendor/github.com/containers/psgo/internal/capabilities/capabilities.go @@ -1,4 +1,7 @@ -package ps +// Package capabilities provides a mapping from common kernel bit masks to the +// alphanumerical represenation of kernel capabilities. See capabilities(7) +// for additional information. +package capabilities var ( // capabilities are a mapping from a numerical value to the textual @@ -47,16 +50,17 @@ var ( 36: "BLOCK_SUSPEND", 37: "AUDIT_READ", } - // fullCAPs represents the value of a bitmask with a full capability + + // FullCAPs represents the value of a bitmask with a full capability // set. - fullCAPs = uint64(0x3FFFFFFFFF) + FullCAPs = uint64(0x3FFFFFFFFF) ) -// maskToCaps iterates over mask and returns a slice of corresponding +// TranslateMask iterates over mask and returns a slice of corresponding // capabilities. If a bit is out of range of known capabilities, it is set as // "unknown" to catch potential regressions when new capabilities are added to // the kernel. -func maskToCaps(mask uint64) []string { +func TranslateMask(mask uint64) []string { caps := []string{} for i := uint(0); i < 64; i++ { if (mask>>i)&0x1 == 1 { diff --git a/vendor/github.com/containers/psgo/ps/tty.go b/vendor/github.com/containers/psgo/internal/dev/tty.go index 7c5f3a53b..37be9972d 100644 --- a/vendor/github.com/containers/psgo/ps/tty.go +++ b/vendor/github.com/containers/psgo/internal/dev/tty.go @@ -1,4 +1,4 @@ -package ps +package dev import ( "os" @@ -6,58 +6,58 @@ import ( "syscall" ) -// tty represents a tty including its minor and major device number and the +// TTY represents a tty including its minor and major device number and the // path to the tty. -type tty struct { - // minor device number - minor uint64 - // major device number - major uint64 - // path to the tty - device string +type TTY struct { + // Minor device number. + Minor uint64 + // Major device number. + Major uint64 + // Path to the tty device. + Path string } -// majDevNum returns the major device number of rdev (see stat_t.Rdev). -func majDevNum(rdev uint64) uint64 { - return (rdev >> 8) & 0xfff -} +// cache TTYs to avoid redundant lookups +var devices *[]TTY -// minDevNum returns the minor device number of rdev (see stat_t.Rdev). -func minDevNum(rdev uint64) uint64 { - return (rdev & 0xff) | ((rdev >> 12) & 0xfff00) -} - -// ttyNrToDev returns the major and minor number of the tty device from -// /proc/$pid/stat.tty_nr as described in man 5 proc. -func ttyNrToDev(ttyNr uint64) (uint64, uint64) { +// FindTTY return the corresponding TTY to the ttyNr or nil of non could be +// found. +func FindTTY(ttyNr uint64) (*TTY, error) { // (man 5 proc) The minor device number is contained in the combination // of bits 31 to 20 and 7 to 0; the major device number is in bits 15 // to 8. maj := (ttyNr >> 8) & 0xFF min := (ttyNr & 0xFF) | ((ttyNr >> 20) & 0xFFF) - return maj, min -} -// findTTY returns a tty with the corresponding major and minor device number -// or nil if no matching tty is found. -func findTTY(maj, min uint64) (*tty, error) { - if len(ttyDevices) == 0 { - var err error - ttyDevices, err = getTTYs() + if devices == nil { + devs, err := getTTYs() if err != nil { return nil, err } + devices = devs } - for _, t := range ttyDevices { - if t.minor == min && t.major == maj { - return t, nil + + for _, t := range *devices { + if t.Minor == min && t.Major == maj { + return &t, nil } } + return nil, nil } +// majDevNum returns the major device number of rdev (see stat_t.Rdev). +func majDevNum(rdev uint64) uint64 { + return (rdev >> 8) & 0xfff +} + +// minDevNum returns the minor device number of rdev (see stat_t.Rdev). +func minDevNum(rdev uint64) uint64 { + return (rdev & 0xff) | ((rdev >> 12) & 0xfff00) +} + // getTTYs parses /dev for tty and pts devices. -func getTTYs() ([]*tty, error) { +func getTTYs() (*[]TTY, error) { devDir, err := os.Open("/dev/") if err != nil { return nil, err @@ -90,7 +90,7 @@ func getTTYs() ([]*tty, error) { devices = append(devices, "/dev/pts/"+d) } - ttys := []*tty{} + ttys := []TTY{} for _, dev := range devices { fi, err := os.Stat(dev) if err != nil { @@ -101,13 +101,13 @@ func getTTYs() ([]*tty, error) { return nil, err } s := fi.Sys().(*syscall.Stat_t) - t := tty{ - minor: minDevNum(s.Rdev), - major: majDevNum(s.Rdev), - device: dev, + t := TTY{ + Minor: minDevNum(s.Rdev), + Major: majDevNum(s.Rdev), + Path: dev, } - ttys = append(ttys, &t) + ttys = append(ttys, t) } - return ttys, nil + return &ttys, nil } diff --git a/vendor/github.com/containers/psgo/internal/host/host.go b/vendor/github.com/containers/psgo/internal/host/host.go new file mode 100644 index 000000000..7c2a48cb2 --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/host/host.go @@ -0,0 +1,67 @@ +// Package host extracts data from the host, such as the system's boot time or +// the tick rate of the system clock. +package host + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +/* +#include <unistd.h> +*/ +import "C" + +var ( + // cache host queries to redundant calculations + clockTicks *int64 + bootTime *int64 +) + +// ClockTicks returns sysconf(SC_CLK_TCK). +func ClockTicks() int64 { + if clockTicks == nil { + ticks := int64(C.sysconf(C._SC_CLK_TCK)) + clockTicks = &ticks + } + return *clockTicks +} + +// BootTime parses /proc/uptime returns the boot time in seconds since the +// Epoch, 1970-01-01 00:00:00 +0000 (UTC). +func BootTime() (int64, error) { + if bootTime != nil { + return *bootTime, nil + } + + f, err := os.Open("/proc/stat") + if err != nil { + return 0, err + } + + btimeStr := "" + scanner := bufio.NewScanner(f) + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) < 2 { + continue + } + if fields[0] == "btime" { + btimeStr = fields[1] + } + } + + if len(btimeStr) == 0 { + return 0, fmt.Errorf("couldn't extract boot time from /proc/stat") + } + + btimeSec, err := strconv.ParseInt(btimeStr, 10, 64) + if err != nil { + return 0, fmt.Errorf("error parsing boot time from /proc/stat: %s", err) + } + bootTime = &btimeSec + return btimeSec, nil +} diff --git a/vendor/github.com/containers/psgo/internal/proc/attr.go b/vendor/github.com/containers/psgo/internal/proc/attr.go new file mode 100644 index 000000000..0385ef9e6 --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/proc/attr.go @@ -0,0 +1,24 @@ +package proc + +import ( + "fmt" + "io/ioutil" + "os" + "strings" +) + +// ParseAttrCurrent returns the contents of /proc/$pid/attr/current of "?" if +// labeling is not supported on the host. +func ParseAttrCurrent(pid string) (string, error) { + data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%s/attr/current", pid)) + if err != nil { + _, err = os.Stat(fmt.Sprintf("/proc/%s", pid)) + if os.IsNotExist(err) { + // PID doesn't exist + return "", err + } + // PID exists but labeling seems to be unsupported + return "?", nil + } + return strings.Trim(string(data), "\n"), nil +} diff --git a/vendor/github.com/containers/psgo/internal/proc/cmdline.go b/vendor/github.com/containers/psgo/internal/proc/cmdline.go new file mode 100644 index 000000000..1899dd2dc --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/proc/cmdline.go @@ -0,0 +1,22 @@ +package proc + +import ( + "bytes" + "fmt" + "io/ioutil" +) + +// ParseCmdLine parses a /proc/$pid/cmdline file and returns a string slice. +func ParseCmdLine(pid string) ([]string, error) { + data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%s/cmdline", pid)) + if err != nil { + return nil, err + } + + cmdLine := []string{} + for _, rawCmd := range bytes.Split(data, []byte{0}) { + cmdLine = append(cmdLine, string(rawCmd)) + } + + return cmdLine, nil +} diff --git a/vendor/github.com/containers/psgo/internal/proc/ns.go b/vendor/github.com/containers/psgo/internal/proc/ns.go new file mode 100644 index 000000000..61b4b2b58 --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/proc/ns.go @@ -0,0 +1,15 @@ +package proc + +import ( + "fmt" + "os" +) + +// ParsePIDNamespace returns the content of /proc/$pid/ns/pid. +func ParsePIDNamespace(pid string) (string, error) { + pidNS, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/pid", pid)) + if err != nil { + return "", err + } + return pidNS, nil +} diff --git a/vendor/github.com/containers/psgo/internal/proc/pids.go b/vendor/github.com/containers/psgo/internal/proc/pids.go new file mode 100644 index 000000000..ff1565cf1 --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/proc/pids.go @@ -0,0 +1,79 @@ +package proc + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +// GetPIDs extracts and returns all PIDs from /proc. +func GetPIDs() ([]string, error) { + procDir, err := os.Open("/proc/") + if err != nil { + return nil, err + } + defer procDir.Close() + + // extract string slice of all directories in procDir + pidDirs, err := procDir.Readdirnames(0) + if err != nil { + return nil, err + } + + pids := []string{} + for _, pidDir := range pidDirs { + _, err := strconv.Atoi(pidDir) + if err != nil { + // skip non-numerical entries (e.g., `/proc/softirqs`) + continue + } + pids = append(pids, pidDir) + } + + return pids, nil +} + +// pidCgroupPath returns the path to the pid's pids cgroup. +func pidCgroupPath(pid string) (string, error) { + f, err := os.Open(fmt.Sprintf("/proc/%s/cgroup", pid)) + if err != nil { + return "", err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + fields := strings.Split(scanner.Text(), ":") + if len(fields) != 3 { + continue + } + if fields[1] == "pids" { + return fmt.Sprintf("/sys/fs/cgroup/pids/%s/cgroup.procs", fields[2]), nil + } + } + return "", fmt.Errorf("couldn't find pids group for PID %s", pid) +} + +// GetPIDsFromCgroup returns a strings slice of all pids listesd in pid's pids +// cgroup. +func GetPIDsFromCgroup(pid string) ([]string, error) { + cgroupPath, err := pidCgroupPath(pid) + if err != nil { + return nil, err + } + + f, err := os.Open(cgroupPath) + if err != nil { + return nil, err + } + defer f.Close() + + pids := []string{} + scanner := bufio.NewScanner(f) + for scanner.Scan() { + pids = append(pids, scanner.Text()) + } + return pids, nil +} diff --git a/vendor/github.com/containers/psgo/ps/stat.go b/vendor/github.com/containers/psgo/internal/proc/stat.go index 0488d4616..b238b4fcf 100644 --- a/vendor/github.com/containers/psgo/ps/stat.go +++ b/vendor/github.com/containers/psgo/internal/proc/stat.go @@ -1,116 +1,72 @@ -package ps +package proc import ( - "bufio" "fmt" "io/ioutil" - "os" - "strconv" "strings" ) -/* -#include <unistd.h> -*/ -import "C" - -// getClockTicks returns sysconf(SC_CLK_TCK). -func getClockTicks() int64 { - return int64(C.sysconf(C._SC_CLK_TCK)) -} - -// bootTime parses /proc/uptime returns the time.Time of system boot. -func getBootTime() (int64, error) { - f, err := os.Open("/proc/stat") - if err != nil { - return 0, err - } - - btimeStr := "" - scanner := bufio.NewScanner(f) - for scanner.Scan() { - fields := strings.Fields(scanner.Text()) - if len(fields) < 2 { - continue - } - if fields[0] == "btime" { - btimeStr = fields[1] - } - } - - if len(btimeStr) == 0 { - return 0, fmt.Errorf("couldn't extract boot time from /proc/stat") - } - - btimeSec, err := strconv.ParseInt(btimeStr, 10, 64) - if err != nil { - return 0, fmt.Errorf("error parsing boot time from /proc/stat: %s", err) - } - - return btimeSec, nil -} - -// stat is a direct translation of a `/proc/[pid]/stat` file as described in +// Stat is a direct translation of a `/proc/[pid]/stat` file as described in // the proc(5) manpage. Please note that it is not a full translation as not // all fields are in the scope of this library and higher indices are // Kernel-version dependent. -type stat struct { +type Stat struct { // (1) The process ID - pid string + Pid string // (2) The filename of the executable, in parentheses. This is visible // whether or not the executable is swapped out. - comm string + Comm string // (3) The process state (e.g., running, sleeping, zombie, dead). // Refer to proc(5) for further deatils. - state string + State string // (4) The PID of the parent of this process. - ppid string + Ppid string // (5) The process group ID of the process. - pgrp string + Pgrp string // (6) The session ID of the process. - session string + Session string // (7) The controlling terminal of the process. (The minor device // number is contained in the combination of bits 31 to 20 and 7 to 0; // the major device number is in bits 15 to 8.) - ttyNr string + TtyNr string // (8) The ID of the foreground process group of the controlling // terminal of the process. - tpgid string + Tpgid string // (9) The kernel flags word of the process. For bit meanings, see the // PF_* defines in the Linux kernel source file // include/linux/sched.h. Details depend on the kernel version. - flags string + Flags string // (10) The number of minor faults the process has made which have not // required loading a memory page from disk. - minflt string + Minflt string // (11) The number of minor faults that the process's waited-for // children have made. - cminflt string + Cminflt string // (12) The number of major faults the process has made which have // required loading a memory page from disk. - majflt string + Majflt string // (13) The number of major faults that the process's waited-for // children have made. - cmajflt string + Cmajflt string // (14) Amount of time that this process has been scheduled in user // mode, measured in clock ticks (divide by // sysconf(_SC_CLK_TCK)). This includes guest time, guest_time // (time spent running a virtual CPU, see below), so that applications // that are not aware of the guest time field do not lose that time // from their calculations. - utime string + Utime string // (15) Amount of time that this process has been scheduled in kernel // mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). - stime string + Stime string // (16) Amount of time that this process's waited-for children have // been scheduled in user mode, measured in clock ticks (divide by // sysconf(_SC_CLK_TCK)). (See also times(2).) This includes guest // time, cguest_time (time spent running a virtual CPU, see below). - cutime string + Cutime string // (17) Amount of time that this process's waited-for children have // been scheduled in kernel mode, measured in clock ticks (divide by // sysconf(_SC_CLK_TCK)). - cstime string + Cstime string // (18) (Explanation for Linux 2.6+) For processes running a real-time // scheduling policy (policy below; see sched_setscheduler(2)), this is // the negated scheduling pri- ority, minus one; that is, a number @@ -120,43 +76,39 @@ type stat struct { // in the kernel. The kernel stores nice values as numbers in the // range 0 (high) to 39 (low), corresponding to the user-visible nice // range of -20 to 19. - priority string + Priority string // (19) The nice value (see setpriority(2)), a value in the range 19 // (low priority) to -20 (high priority). - nice string + Nice string // (20) Number of threads in this process (since Linux 2.6). Before // kernel 2.6, this field was hard coded to 0 as a placeholder for an // earlier removed field. - numThreads string + NumThreads string // (21) The time in jiffies before the next SIGALRM is sent to the // process due to an interval timer. Since kernel 2.6.17, this // field is no longer maintained, and is hard coded as 0. - itrealvalue string + Itrealvalue string // (22) The time the process started after system boot. In kernels // before Linux 2.6, this value was expressed in jiffies. Since // Linux 2.6, the value is expressed in clock ticks (divide by // sysconf(_SC_CLK_TCK)). - starttime string + Starttime string // (23) Virtual memory size in bytes. - vsize string + Vsize string } // readStat is used for mocking in unit tests. var readStat = func(path string) ([]string, error) { data, err := ioutil.ReadFile(path) if err != nil { - if os.IsNotExist(err) { - err = errNoSuchPID - } return nil, err } - return strings.Fields(string(data)), nil } -// parseStat parses the /proc/$pid/stat file and returns a stat. -func parseStat(path string) (*stat, error) { - fields, err := readStat(path) +// ParseStat parses the /proc/$pid/stat file and returns a Stat. +func ParseStat(pid string) (*Stat, error) { + fields, err := readStat(fmt.Sprintf("/proc/%s/stat", pid)) if err != nil { return nil, err } @@ -165,29 +117,29 @@ func parseStat(path string) (*stat, error) { return fields[i-1] } - return &stat{ - pid: fieldAt(1), - comm: fieldAt(2), - state: fieldAt(3), - ppid: fieldAt(4), - pgrp: fieldAt(5), - session: fieldAt(6), - ttyNr: fieldAt(7), - tpgid: fieldAt(8), - flags: fieldAt(9), - minflt: fieldAt(10), - cminflt: fieldAt(11), - majflt: fieldAt(12), - cmajflt: fieldAt(13), - utime: fieldAt(14), - stime: fieldAt(15), - cutime: fieldAt(16), - cstime: fieldAt(17), - priority: fieldAt(18), - nice: fieldAt(19), - numThreads: fieldAt(20), - itrealvalue: fieldAt(21), - starttime: fieldAt(22), - vsize: fieldAt(23), + return &Stat{ + Pid: fieldAt(1), + Comm: fieldAt(2), + State: fieldAt(3), + Ppid: fieldAt(4), + Pgrp: fieldAt(5), + Session: fieldAt(6), + TtyNr: fieldAt(7), + Tpgid: fieldAt(8), + Flags: fieldAt(9), + Minflt: fieldAt(10), + Cminflt: fieldAt(11), + Majflt: fieldAt(12), + Cmajflt: fieldAt(13), + Utime: fieldAt(14), + Stime: fieldAt(15), + Cutime: fieldAt(16), + Cstime: fieldAt(17), + Priority: fieldAt(18), + Nice: fieldAt(19), + NumThreads: fieldAt(20), + Itrealvalue: fieldAt(21), + Starttime: fieldAt(22), + Vsize: fieldAt(23), }, nil } diff --git a/vendor/github.com/containers/psgo/ps/status.go b/vendor/github.com/containers/psgo/internal/proc/status.go index 7f496bc48..b8e06dd6b 100644 --- a/vendor/github.com/containers/psgo/ps/status.go +++ b/vendor/github.com/containers/psgo/internal/proc/status.go @@ -1,4 +1,4 @@ -package ps +package proc import ( "bufio" @@ -9,164 +9,161 @@ import ( "github.com/pkg/errors" ) -// status is a direct translation of a `/proc/[pid]/status`, wich provides much +// Status is a direct translation of a `/proc/[pid]/status`, wich provides much // of the information in /proc/[pid]/stat and /proc/[pid]/statm in a format // that's easier for humans to parse. -type status struct { +type Status struct { // Name: Command run by this process. - name string + Name string // Umask: Process umask, expressed in octal with a leading zero; see // umask(2). (Since Linux 4.7.) - umask string + Umask string // State: Current state of the process. One of "R (running)", "S // (sleeping)", "D (disk sleep)", "T (stopped)", "T (tracing stop)", "Z // (zombie)", or "X (dead)". - state string + State string // Tgid: Thread group ID (i.e., Process ID). - tgid string + Tgid string // Ngid: NUMA group ID (0 if none; since Linux 3.13). - ngid string + Ngid string // Pid: Thread ID (see gettid(2)). - pid string + Pid string // PPid: PID of parent process. - pPid string + PPid string // TracerPid: PID of process tracing this process (0 if not being traced). - tracerPid string + TracerPid string // Uids: Real, effective, saved set, and filesystem. - uids []string + Uids []string // Gids: Real, effective, saved set, and filesystem. - gids []string + Gids []string // FDSize: Number of file descriptor slots currently allocated. - fdSize string + FdSize string // Groups: Supplementary group list. - groups []string + Groups []string // NStgid : Thread group ID (i.e., PID) in each of the PID namespaces // of which [pid] is a member. The leftmost entry shows the value // with respect to the PID namespace of the reading process, followed // by the value in successively nested inner namespaces. (Since Linux // 4.1.) - nStgid string + NStgid string // NSpid: Thread ID in each of the PID namespaces of which [pid] is a // member. The fields are ordered as for NStgid. (Since Linux 4.1.) - nSpid string + NSpid []string // NSpgid: Process group ID in each of the PID namespaces of which // [pid] is a member. The fields are ordered as for NStgid. (Since // Linux 4.1.) - nSpgid string + NSpgid string // NSsid: descendant namespace session ID hierarchy Session ID in // each of the PID names- paces of which [pid] is a member. The fields // are ordered as for NStgid. (Since Linux 4.1.) - nSsid string - // VmPeak: Peak virtual memory size. - vmPeak string - // VmSize: Virtual memory size. - vmSize string - // VmLck: Locked memory size (see mlock(3)). - vmLCK string - // VmPin: Pinned memory size (since Linux 3.2). These are pages + NSsid string + // VMPeak: Peak virtual memory size. + VMPeak string + // VMSize: Virtual memory size. + VMSize string + // VMLck: Locked memory size (see mlock(3)). + VMLCK string + // VMPin: Pinned memory size (since Linux 3.2). These are pages // that can't be moved because something needs to directly access // physical memory. - vmPin string - // VmHWM: Peak resident set size ("high water mark"). - vmHWM string - // VmRSS: Resident set size. Note that the value here is the sum of + VMPin string + // VMHWM: Peak resident set size ("high water mark"). + VMHWM string + // VMRSS: Resident set size. Note that the value here is the sum of // RssAnon, RssFile, and RssShmem. - vmRSS string + VMRSS string // RssAnon: Size of resident anonymous memory. (since Linux 4.5). - rssAnon string + RssAnon string // RssFile: Size of resident file mappings. (since Linux 4.5). - rssFile string + RssFile string // RssShmem: Size of resident shared memory (includes System V // shared memory, mappings from tmpfs(5), and shared anonymous // mappings). (since Linux 4.5). - rssShmem string - // VmData: Size of data segment. - vmData string - // VmStk: Size of stack segment. - vmStk string - // VmExe: Size of text segment. - vmExe string - // VmLib: Shared library code size. - vmLib string - // VmPTE: Page table entries size (since Linux 2.6.10). - vmPTE string - // VmPMD: Size of second-level page tables (since Linux 4.0). - vmPMD string - // VmSwap: Swapped-out virtual memory size by anonymous private pages; + RssShmem string + // VMData: Size of data segment. + VMData string + // VMStk: Size of stack segment. + VMStk string + // VMExe: Size of text segment. + VMExe string + // VMLib: Shared library code size. + VMLib string + // VMPTE: Page table entries size (since Linux 2.6.10). + VMPTE string + // VMPMD: Size of second-level page tables (since Linux 4.0). + VMPMD string + // VMSwap: Swapped-out virtual memory size by anonymous private pages; // shmem swap usage is not included (since Linux 2.6.34). - vmSwap string + VMSwap string // HugetlbPages: Size of hugetlb memory portions. (since Linux 4.4). - hugetlbPages string + HugetlbPages string // Threads: Number of threads in process containing this thread. - threads string + Threads string // SigQ: This field contains two slash-separated numbers that relate to // queued signals for the real user ID of this process. The first of // these is the number of currently queued signals for this real // user ID, and the second is the resource limit on the number of // queued signals for this process (see the description of // RLIMIT_SIGPENDING in getr- limit(2)). - sigQ string + SigQ string // SigPnd: Number of signals pending for thread and for (see pthreads(7)). - sigPnd string + SigPnd string // ShdPnd: Number of signals pending for process as a whole (see // signal(7)). - shdPnd string + ShdPnd string // SigBlk: Mask indicating signals being blocked (see signal(7)). - sigBlk string + SigBlk string // SigIgn: Mask indicating signals being ignored (see signal(7)). - sigIgn string + SigIgn string // SigCgt: Mask indicating signals being blocked caught (see signal(7)). - sigCgt string + SigCgt string // CapInh: Mask of capabilities enabled in inheritable sets (see // capabilities(7)). - capInh string + CapInh string // CapPrm: Mask of capabilities enabled in permitted sets (see // capabilities(7)). - capPrm string + CapPrm string // CapEff: Mask of capabilities enabled in effective sets (see // capabilities(7)). - capEff string + CapEff string // CapBnd: Capability Bounding set (since Linux 2.6.26, see // capabilities(7)). - capBnd string + CapBnd string // CapAmb: Ambient capability set (since Linux 4.3, see capabilities(7)). - capAmb string + CapAmb string // NoNewPrivs: Value of the no_new_privs bit (since Linux 4.10, see // prctl(2)). - noNewPrivs string + NoNewPrivs string // Seccomp: Seccomp mode of the process (since Linux 3.8, see // seccomp(2)). 0 means SEC- COMP_MODE_DISABLED; 1 means // SECCOMP_MODE_STRICT; 2 means SECCOMP_MODE_FILTER. This field is // provided only if the kernel was built with the CONFIG_SECCOMP kernel // configu- ration option enabled. - seccomp string + Seccomp string // Cpus_allowed: Mask of CPUs on which this process may run // (since Linux 2.6.24, see cpuset(7)). - cpusAllowed string + CpusAllowed string // Cpus_allowed_list: Same as previous, but in "list format" (since // Linux 2.6.26, see cpuset(7)). - cpusAllowedList string + CpusAllowedList string // Mems_allowed: Mask of memory nodes allowed to this process // (since Linux 2.6.24, see cpuset(7)). - memsAllowed string + MemsAllowed string // Mems_allowed_list: Same as previous, but in "list format" (since // Linux 2.6.26, see cpuset(7)). - memsAllowedList string + MemsAllowedList string // voluntaryCtxtSwitches: Number of voluntary context switches // (since Linux 2.6.23). - voluntaryCtxtSwitches string + VoluntaryCtxtSwitches string // nonvoluntaryCtxtSwitches: Number of involuntary context switches // (since Linux 2.6.23). - nonvoluntaryCtxtSwitches string + NonvoluntaryCtxtSwitches string } // readStatus is used for mocking in unit tests. var readStatus = func(path string) ([]string, error) { f, err := os.Open(path) if err != nil { - if os.IsNotExist(err) { - err = errNoSuchPID - } return nil, err } lines := []string{} @@ -177,14 +174,15 @@ var readStatus = func(path string) ([]string, error) { return lines, nil } -// parseStatus parses the /proc/$pid/status file and returns a *status. -func parseStatus(path string) (*status, error) { +// ParseStatus parses the /proc/$pid/status file and returns a *Status. +func ParseStatus(pid string) (*Status, error) { + path := fmt.Sprintf("/proc/%s/status", pid) lines, err := readStatus(path) if err != nil { return nil, err } - s := status{} + s := Status{} errUnexpectedInput := errors.New(fmt.Sprintf("unexpected input from %s", path)) for _, line := range lines { fields := strings.Fields(line) @@ -194,119 +192,117 @@ func parseStatus(path string) (*status, error) { switch fields[0] { case "Name:": - s.name = fields[1] + s.Name = fields[1] case "Umask:": - s.umask = fields[1] + s.Umask = fields[1] case "State:": - s.state = fields[1] + s.State = fields[1] case "Tgid:": - s.tgid = fields[1] + s.Tgid = fields[1] case "Ngid:": - s.ngid = fields[1] + s.Ngid = fields[1] case "Pid:": - s.pid = fields[1] + s.Pid = fields[1] case "PPid:": - s.pPid = fields[1] + s.PPid = fields[1] case "TracerPid:": - s.tracerPid = fields[1] + s.TracerPid = fields[1] case "Uid:": if len(fields) != 5 { return nil, errors.Wrap(errUnexpectedInput, line) } - s.uids = []string{fields[1], fields[2], fields[3], fields[4]} + s.Uids = []string{fields[1], fields[2], fields[3], fields[4]} case "Gid:": if len(fields) != 5 { return nil, errors.Wrap(errUnexpectedInput, line) } - s.gids = []string{fields[1], fields[2], fields[3], fields[4]} + s.Gids = []string{fields[1], fields[2], fields[3], fields[4]} case "FDSize:": - s.fdSize = fields[1] + s.FdSize = fields[1] case "Groups:": - for _, g := range fields[1:] { - s.groups = append(s.groups, g) - } + s.Groups = fields[1:] case "NStgid:": - s.nStgid = fields[1] + s.NStgid = fields[1] case "NSpid:": - s.nSpid = fields[1] + s.NSpid = fields[1:] case "NSpgid:": - s.nSpgid = fields[1] + s.NSpgid = fields[1] case "NSsid:": - s.nSsid = fields[1] + s.NSsid = fields[1] case "VmPeak:": - s.vmPeak = fields[1] + s.VMPeak = fields[1] case "VmSize:": - s.vmSize = fields[1] + s.VMSize = fields[1] case "VmLck:": - s.vmLCK = fields[1] + s.VMLCK = fields[1] case "VmPin:": - s.vmPin = fields[1] + s.VMPin = fields[1] case "VmHWM:": - s.vmHWM = fields[1] + s.VMHWM = fields[1] case "VmRSS:": - s.vmRSS = fields[1] + s.VMRSS = fields[1] case "RssAnon:": - s.rssAnon = fields[1] + s.RssAnon = fields[1] case "RssFile:": - s.rssFile = fields[1] + s.RssFile = fields[1] case "RssShmem:": - s.rssShmem = fields[1] + s.RssShmem = fields[1] case "VmData:": - s.vmData = fields[1] + s.VMData = fields[1] case "VmStk:": - s.vmStk = fields[1] + s.VMStk = fields[1] case "VmExe:": - s.vmExe = fields[1] + s.VMExe = fields[1] case "VmLib:": - s.vmLib = fields[1] + s.VMLib = fields[1] case "VmPTE:": - s.vmPTE = fields[1] + s.VMPTE = fields[1] case "VmPMD:": - s.vmPMD = fields[1] + s.VMPMD = fields[1] case "VmSwap:": - s.vmSwap = fields[1] + s.VMSwap = fields[1] case "HugetlbPages:": - s.hugetlbPages = fields[1] + s.HugetlbPages = fields[1] case "Threads:": - s.threads = fields[1] + s.Threads = fields[1] case "SigQ:": - s.sigQ = fields[1] + s.SigQ = fields[1] case "SigPnd:": - s.sigPnd = fields[1] + s.SigPnd = fields[1] case "ShdPnd:": - s.shdPnd = fields[1] + s.ShdPnd = fields[1] case "SigBlk:": - s.sigBlk = fields[1] + s.SigBlk = fields[1] case "SigIgn:": - s.sigIgn = fields[1] + s.SigIgn = fields[1] case "SigCgt:": - s.sigCgt = fields[1] + s.SigCgt = fields[1] case "CapInh:": - s.capInh = fields[1] + s.CapInh = fields[1] case "CapPrm:": - s.capPrm = fields[1] + s.CapPrm = fields[1] case "CapEff:": - s.capEff = fields[1] + s.CapEff = fields[1] case "CapBnd:": - s.capBnd = fields[1] + s.CapBnd = fields[1] case "CapAmb:": - s.capAmb = fields[1] + s.CapAmb = fields[1] case "NoNewPrivs:": - s.noNewPrivs = fields[1] + s.NoNewPrivs = fields[1] case "Seccomp:": - s.seccomp = fields[1] + s.Seccomp = fields[1] case "Cpus_allowed:": - s.cpusAllowed = fields[1] + s.CpusAllowed = fields[1] case "Cpus_allowed_list:": - s.cpusAllowedList = fields[1] + s.CpusAllowedList = fields[1] case "Mems_allowed:": - s.memsAllowed = fields[1] + s.MemsAllowed = fields[1] case "Mems_allowed_list:": - s.memsAllowedList = fields[1] + s.MemsAllowedList = fields[1] case "voluntary_ctxt_switches:": - s.voluntaryCtxtSwitches = fields[1] + s.VoluntaryCtxtSwitches = fields[1] case "nonvoluntary_ctxt_switches:": - s.nonvoluntaryCtxtSwitches = fields[1] + s.NonvoluntaryCtxtSwitches = fields[1] } } diff --git a/vendor/github.com/containers/psgo/internal/process/process.go b/vendor/github.com/containers/psgo/internal/process/process.go new file mode 100644 index 000000000..b1ea076b5 --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/process/process.go @@ -0,0 +1,205 @@ +package process + +import ( + "os" + "strconv" + "time" + + "github.com/containers/psgo/internal/host" + "github.com/containers/psgo/internal/proc" + "github.com/opencontainers/runc/libcontainer/user" + "github.com/pkg/errors" +) + +// Process includes process-related from the /proc FS. +type Process struct { + // PID is the process ID. + Pid string + // Stat contains data from /proc/$pid/stat. + Stat proc.Stat + // Status containes data from /proc/$pid/status. + Status proc.Status + // CmdLine containes data from /proc/$pid/cmdline. + CmdLine []string + // Label containers data from /proc/$pid/attr/current. + Label string + // PidNS contains data from /proc/$pid/ns/pid. + PidNS string + // Huser is the effective host user of a container process. + Huser string + // Hgroup is the effective host group of a container process. + Hgroup string +} + +// LookupGID returns the textual group ID, if it can be optained, or the +// decimal representation otherwise. +func LookupGID(gid string) (string, error) { + gidNum, err := strconv.Atoi(gid) + if err != nil { + return "", errors.Wrap(err, "error parsing group ID") + } + g, err := user.LookupGid(gidNum) + if err != nil { + return gid, nil + } + return g.Name, nil +} + +// LookupUID return the textual user ID, if it can be optained, or the decimal +// representation otherwise. +func LookupUID(uid string) (string, error) { + uidNum, err := strconv.Atoi(uid) + if err != nil { + return "", errors.Wrap(err, "error parsing user ID") + } + u, err := user.LookupUid(uidNum) + if err != nil { + return uid, nil + } + return u.Name, nil +} + +// New returns a new Process with the specified pid and parses the relevant +// data from /proc and /dev. +func New(pid string) (*Process, error) { + p := Process{Pid: pid} + + if err := p.parseStat(); err != nil { + return nil, err + } + if err := p.parseStatus(); err != nil { + return nil, err + } + if err := p.parseCmdLine(); err != nil { + return nil, err + } + if err := p.parsePIDNamespace(); err != nil { + // Ignore permission errors as those occur for some pids when + // the caller has limited permissions. + if !os.IsPermission(err) { + return nil, err + } + } + if err := p.parseLabel(); err != nil { + return nil, err + } + + return &p, nil +} + +// FromPIDs creates a new Process for each pid. +func FromPIDs(pids []string) ([]*Process, error) { + processes := []*Process{} + for _, pid := range pids { + p, err := New(pid) + if err != nil { + if os.IsNotExist(err) { + // proc parsing is racy + // Let's ignore "does not exist" errors + continue + } + return nil, err + } + processes = append(processes, p) + } + return processes, nil +} + +// parseStat parses /proc/$pid/stat. +func (p *Process) parseStat() error { + s, err := proc.ParseStat(p.Pid) + if err != nil { + return err + } + p.Stat = *s + return nil +} + +// parseStatus parses /proc/$pid/status. +func (p *Process) parseStatus() error { + s, err := proc.ParseStatus(p.Pid) + if err != nil { + return err + } + p.Status = *s + return nil +} + +// parseCmdLine parses /proc/$pid/cmdline. +func (p *Process) parseCmdLine() error { + s, err := proc.ParseCmdLine(p.Pid) + if err != nil { + return err + } + p.CmdLine = s + return nil +} + +// parsePIDNamespace parses all host-related data fields. +func (p *Process) parsePIDNamespace() error { + pidNS, err := proc.ParsePIDNamespace(p.Pid) + if err != nil { + return err + } + p.PidNS = pidNS + return nil +} + +// parseLabel parses the security label. +func (p *Process) parseLabel() error { + label, err := proc.ParseAttrCurrent(p.Pid) + if err != nil { + return err + } + p.Label = label + return nil +} + +// SetHostData sets all host-related data fields. +func (p *Process) SetHostData() error { + var err error + + p.Huser, err = LookupUID(p.Status.Uids[1]) + if err != nil { + return err + } + + p.Hgroup, err = LookupGID(p.Status.Gids[1]) + if err != nil { + return err + } + + return nil +} + +// ElapsedTime returns the time.Duration since process p was created. +func (p *Process) ElapsedTime() (time.Duration, error) { + sinceBoot, err := strconv.ParseInt(p.Stat.Starttime, 10, 64) + if err != nil { + return 0, err + } + + sinceBoot = sinceBoot / host.ClockTicks() + + bootTime, err := host.BootTime() + if err != nil { + return 0, err + } + created := time.Unix(sinceBoot+bootTime, 0) + return (time.Now()).Sub(created), nil +} + +// CPUTime returns the cumlative CPU time of process p as a time.Duration. +func (p *Process) CPUTime() (time.Duration, error) { + user, err := strconv.ParseInt(p.Stat.Utime, 10, 64) + if err != nil { + return 0, err + } + system, err := strconv.ParseInt(p.Stat.Stime, 10, 64) + if err != nil { + return 0, err + } + secs := (user + system) / host.ClockTicks() + cpu := time.Unix(secs, 0) + return cpu.Sub(time.Unix(0, 0)), nil +} diff --git a/vendor/github.com/containers/psgo/ps/cmdline.go b/vendor/github.com/containers/psgo/ps/cmdline.go deleted file mode 100644 index d9991a9a3..000000000 --- a/vendor/github.com/containers/psgo/ps/cmdline.go +++ /dev/null @@ -1,34 +0,0 @@ -package ps - -import ( - "bytes" - "io/ioutil" - "os" -) - -// readCmdline can be used for mocking in unit tests. -func readCmdline(path string) (string, error) { - data, err := ioutil.ReadFile(path) - if err != nil { - if os.IsNotExist(err) { - err = errNoSuchPID - } - return "", err - } - - return string(data), nil -} - -// parseCmdline parses a /proc/$pid/cmdline file and returns a string slice. -func parseCmdline(path string) ([]string, error) { - raw, err := readCmdline(path) - if err != nil { - return nil, err - } - - cmdLine := []string{} - for _, rawCmd := range bytes.Split([]byte(raw), []byte{0}) { - cmdLine = append(cmdLine, string(rawCmd)) - } - return cmdLine, nil -} diff --git a/vendor/github.com/containers/psgo/ps/ps.go b/vendor/github.com/containers/psgo/psgo.go index f8d8d03b5..e52089b3c 100644 --- a/vendor/github.com/containers/psgo/ps/ps.go +++ b/vendor/github.com/containers/psgo/psgo.go @@ -1,4 +1,4 @@ -// Package ps is a ps (1) AIX-format compatible golang library extended with +// Package psgo is a ps (1) AIX-format compatible golang library extended with // various descriptors useful for displaying container-related data. // // The idea behind the library is to provide an easy to use way of extracting @@ -10,46 +10,88 @@ // // Please visit https://github.com/containers/psgo for further details about // supported format descriptors and to see some usage examples. -package ps +package psgo import ( "fmt" - "io/ioutil" "os" "runtime" "sort" "strconv" "strings" "sync" - "time" - "github.com/opencontainers/runc/libcontainer/user" + "github.com/containers/psgo/internal/capabilities" + "github.com/containers/psgo/internal/dev" + "github.com/containers/psgo/internal/proc" + "github.com/containers/psgo/internal/process" "github.com/pkg/errors" "golang.org/x/sys/unix" ) -// DefaultFormat is the `ps -ef` compatible default format. -const DefaultFormat = "user,pid,ppid,pcpu,etime,tty,time,comm" +// processFunc is used to map a given aixFormatDescriptor to a corresponding +// function extracting the desired data from a process. +type processFunc func(*process.Process) (string, error) -var ( - // ErrUnkownDescriptor is returned when an unknown descriptor is parsed. - ErrUnkownDescriptor = errors.New("unknown descriptor") +// aixFormatDescriptor as mentioned in the ps(1) manpage. A given descriptor +// can either be specified via its code (e.g., "%C") or its normal representation +// (e.g., "pcpu") and will be printed under its corresponding header (e.g, "%CPU"). +type aixFormatDescriptor struct { + // code descriptor in the short form (e.g., "%C"). + code string + // normal descriptor in the long form (e.g., "pcpu"). + normal string + // header of the descriptor (e.g., "%CPU"). + header string + // onHost controls if data of the corresponding host processes will be + // extracted as well. + onHost bool + // procFN points to the corresponding method to etract the desired data. + procFn processFunc +} - // errNoSuchPID is returned when `/proc/PID` does not exist (anymore). - errNoSuchPID = errors.New("PID does not exist in /proc") +// translateDescriptors parses the descriptors and returns a correspodning slice of +// aixFormatDescriptors. Descriptors can be specified in the normal and in the +// code form (if supported). If the descriptors slice is empty, the +// `DefaultDescriptors` is used. +func translateDescriptors(descriptors []string) ([]aixFormatDescriptor, error) { + if len(descriptors) == 0 { + descriptors = DefaultDescriptors + } - // bootTime holds the host's boot time. Singleton to safe some time and - // energy. - bootTime int64 + formatDescriptors := []aixFormatDescriptor{} + for _, d := range descriptors { + d = strings.TrimSpace(d) + found := false + for _, aix := range aixFormatDescriptors { + if d == aix.code || d == aix.normal { + formatDescriptors = append(formatDescriptors, aix) + found = true + } + } + if !found { + return nil, errors.Wrapf(ErrUnkownDescriptor, "'%s'", d) + } + } - // clockTicks is the value of sysconf(SC_CLK_TCK) - clockTicks = getClockTicks() + return formatDescriptors, nil +} - // ttyDevices is a slice of ttys. Singledton to safe some time and - // energy. - ttyDevices []*tty +var ( + // DefaultDescriptors is the `ps -ef` compatible default format. + DefaultDescriptors = []string{"user", "pid", "ppid", "pcpu", "etime", "tty", "time", "comm"} - descriptors = []aixFormatDescriptor{ + // ErrUnkownDescriptor is returned when an unknown descriptor is parsed. + ErrUnkownDescriptor = errors.New("unknown descriptor") + + // hostProcesses are the processes on the host. It should only be used + // in the context of containers and is meant to display data of the + // container processes from the host's (i.e., calling process) view. + // Currently, all host processes contain only the required data from + // /proc/$pid/status. + hostProcesses []*process.Process + + aixFormatDescriptors = []aixFormatDescriptor{ { code: "%C", normal: "pcpu", @@ -170,165 +212,36 @@ var ( header: "LABEL", procFn: processLABEL, }, + { + normal: "hpid", + header: "HPID", + onHost: true, + procFn: processHPID, + }, + { + normal: "huser", + header: "HUSER", + onHost: true, + procFn: processHUSER, + }, + { + normal: "hgroup", + header: "HGROUP", + onHost: true, + procFn: processHGROUP, + }, + { + normal: "state", + header: "STATE", + procFn: processState, + }, } ) -// process includes a process ID and the corresponding data from /proc/pid/stat, -// /proc/pid/status and from /prod/pid/cmdline. -type process struct { - pid int - pstat *stat - pstatus *status - cmdline []string -} - -// elapsedTime returns the time.Duration since process p was created. -func (p *process) elapsedTime() (time.Duration, error) { - sinceBoot, err := strconv.ParseInt(p.pstat.starttime, 10, 64) - if err != nil { - return 0, err - } - sinceBoot = sinceBoot / clockTicks - - if bootTime == 0 { - bootTime, err = getBootTime() - if err != nil { - return 0, err - } - } - created := time.Unix(sinceBoot+bootTime, 0) - return (time.Now()).Sub(created), nil -} - -// cpuTime returns the cumlative CPU time of process p as a time.Duration. -func (p *process) cpuTime() (time.Duration, error) { - user, err := strconv.ParseInt(p.pstat.utime, 10, 64) - if err != nil { - return 0, err - } - system, err := strconv.ParseInt(p.pstat.stime, 10, 64) - if err != nil { - return 0, err - } - secs := (user + system) / clockTicks - cpu := time.Unix(secs, 0) - return cpu.Sub(time.Unix(0, 0)), nil -} - -// processes returns a process slice of processes mentioned in /proc. -func processes() ([]*process, error) { - pids, err := getPIDs() - if err != nil { - panic(err) - } - - processes := []*process{} - for _, pid := range pids { - var ( - err error - p process - ) - p.pid = pid - p.pstat, err = parseStat(fmt.Sprintf("/proc/%d/stat", pid)) - if err != nil { - if err == errNoSuchPID { - continue - } - return nil, err - } - p.pstatus, err = parseStatus(fmt.Sprintf("/proc/%d/status", pid)) - if err != nil { - if err == errNoSuchPID { - continue - } - return nil, err - } - p.cmdline, err = parseCmdline(fmt.Sprintf("/proc/%d/cmdline", pid)) - if err != nil { - if err == errNoSuchPID { - continue - } - return nil, err - } - processes = append(processes, &p) - } - - return processes, nil -} - -// getPIDs extracts and returns all PIDs from /proc. -func getPIDs() ([]int, error) { - procDir, err := os.Open("/proc/") - if err != nil { - return nil, err - } - defer procDir.Close() - - // extract string slice of all directories in procDir - pidDirs, err := procDir.Readdirnames(0) - if err != nil { - return nil, err - } - - // convert pidDirs to int - pids := []int{} - for _, pidDir := range pidDirs { - pid, err := strconv.Atoi(pidDir) - if err != nil { - // skip non-numerical entries (e.g., `/proc/softirqs`) - continue - } - pids = append(pids, pid) - } - - return pids, nil -} - -// processFunc is used to map a given aixFormatDescriptor to a corresponding -// function extracting the desired data from a process. -type processFunc func(*process) (string, error) - -// aixFormatDescriptor as mentioned in the ps(1) manpage. A given descriptor -// can either be specified via its code (e.g., "%C") or its normal representation -// (e.g., "pcpu") and will be printed under its corresponding header (e.g, "%CPU"). -type aixFormatDescriptor struct { - code string - normal string - header string - procFn processFunc -} - -// processDescriptors calls each `procFn` of all formatDescriptors on each -// process and returns an array of tab-separated strings. -func processDescriptors(formatDescriptors []aixFormatDescriptor, processes []*process) ([]string, error) { - data := []string{} - // create header - headerArr := []string{} - for _, desc := range formatDescriptors { - headerArr = append(headerArr, desc.header) - } - data = append(data, strings.Join(headerArr, "\t")) - - // dispatch all descriptor functions on each process - for _, proc := range processes { - pData := []string{} - for _, desc := range formatDescriptors { - dataStr, err := desc.procFn(proc) - if err != nil { - return nil, err - } - pData = append(pData, dataStr) - } - data = append(data, strings.Join(pData, "\t")) - } - - return data, nil -} - // ListDescriptors returns a string slice of all supported AIX format // descriptors in the normal form. func ListDescriptors() (list []string) { - for _, d := range descriptors { + for _, d := range aixFormatDescriptors { list = append(list, d.normal) } sort.Strings(list) @@ -337,13 +250,27 @@ func ListDescriptors() (list []string) { // JoinNamespaceAndProcessInfo has the same semantics as ProcessInfo but joins // the mount namespace of the specified pid before extracting data from `/proc`. -func JoinNamespaceAndProcessInfo(pid, format string) ([]string, error) { +func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string, error) { var ( - data []string + data [][]string dataErr error wg sync.WaitGroup ) + aixDescriptors, err := translateDescriptors(descriptors) + if err != nil { + return nil, err + } + + // extract data from host processes only on-demand / when at least one + // of the specified descriptors requires host data + for _, d := range aixDescriptors { + if d.onHost { + setHostProcesses(pid) + break + } + } + wg.Add(1) go func() { defer wg.Done() @@ -362,7 +289,19 @@ func JoinNamespaceAndProcessInfo(pid, format string) ([]string, error) { return } unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS) - data, dataErr = ProcessInfo(format) + + pids, err := proc.GetPIDs() + if err != nil { + dataErr = err + return + } + processes, err := process.FromPIDs(pids) + if err != nil { + dataErr = err + return + } + + data, dataErr = processDescriptors(aixDescriptors, processes) }() wg.Wait() @@ -372,120 +311,137 @@ func JoinNamespaceAndProcessInfo(pid, format string) ([]string, error) { // ProcessInfo returns the process information of all processes in the current // mount namespace. The input format must be a comma-separated list of // supported AIX format descriptors. If the input string is empty, the -// DefaultFormat is used. +// `DefaultDescriptors` is used. // The return value is an array of tab-separated strings, to easily use the // output for column-based formatting (e.g., with the `text/tabwriter` package). -func ProcessInfo(format string) ([]string, error) { - if len(format) == 0 { - format = DefaultFormat +func ProcessInfo(descriptors []string) ([][]string, error) { + aixDescriptors, err := translateDescriptors(descriptors) + if err != nil { + return nil, err } - formatDescriptors, err := parseDescriptors(format) + pids, err := proc.GetPIDs() if err != nil { return nil, err } - - processes, err := processes() + processes, err := process.FromPIDs(pids) if err != nil { return nil, err } - return processDescriptors(formatDescriptors, processes) + return processDescriptors(aixDescriptors, processes) } -// parseDescriptors parses the input string and returns a correspodning array -// of aixFormatDescriptors, which are expected to be separated by commas. -// The input format is "desc1, desc2, ..., desN" where a given descriptor can be -// specified both, in the code and in the normal form. A concrete example is -// "pid, %C, nice, %a". -func parseDescriptors(input string) ([]aixFormatDescriptor, error) { - formatDescriptors := []aixFormatDescriptor{} - for _, s := range strings.Split(input, ",") { - s = strings.TrimSpace(s) - found := false - for _, d := range descriptors { - if s == d.code || s == d.normal { - formatDescriptors = append(formatDescriptors, d) - found = true - } - } - if !found { - return nil, errors.Wrapf(ErrUnkownDescriptor, "'%s'", s) +// setHostProcesses sets `hostProcesses`. +func setHostProcesses(pid string) error { + // get processes + pids, err := proc.GetPIDsFromCgroup(pid) + if err != nil { + return err + } + + processes, err := process.FromPIDs(pids) + if err != nil { + return err + } + + // set the additional host data + for _, p := range processes { + if err := p.SetHostData(); err != nil { + return err } } - return formatDescriptors, nil + + hostProcesses = processes + return nil } -// lookupGID returns the textual group ID, if it can be optained, or the -// decimal input representation otherwise. -func lookupGID(gid string) (string, error) { - gidNum, err := strconv.Atoi(gid) - if err != nil { - return "", errors.Wrap(err, "error parsing group ID") +// processDescriptors calls each `procFn` of all formatDescriptors on each +// process and returns an array of tab-separated strings. +func processDescriptors(formatDescriptors []aixFormatDescriptor, processes []*process.Process) ([][]string, error) { + data := [][]string{} + // create header + header := []string{} + for _, desc := range formatDescriptors { + header = append(header, desc.header) } - g, err := user.LookupGid(gidNum) - if err != nil { - return gid, nil + data = append(data, header) + + // dispatch all descriptor functions on each process + for _, proc := range processes { + pData := []string{} + for _, desc := range formatDescriptors { + dataStr, err := desc.procFn(proc) + if err != nil { + return nil, err + } + pData = append(pData, dataStr) + } + data = append(data, pData) + } + + return data, nil +} + +// findHostProcess returns the corresponding process from `hostProcesses` or +// nil if non is found. +func findHostProcess(p *process.Process) *process.Process { + for _, hp := range hostProcesses { + // We expect the host process to be in another namespace, so + // /proc/$pid/status.NSpid must have at least two entries. + if len(hp.Status.NSpid) < 2 { + continue + } + // The process' PID must match the one in the NS of the host + // process and both must share the same pid NS. + if p.Pid == hp.Status.NSpid[1] && p.PidNS == hp.PidNS { + return hp + } } - return g.Name, nil + return nil } // processGROUP returns the effective group ID of the process. This will be // the textual group ID, if it can be optained, or a decimal representation // otherwise. -func processGROUP(p *process) (string, error) { - return lookupGID(p.pstatus.gids[1]) +func processGROUP(p *process.Process) (string, error) { + return process.LookupGID(p.Status.Gids[1]) } // processRGROUP returns the real group ID of the process. This will be // the textual group ID, if it can be optained, or a decimal representation // otherwise. -func processRGROUP(p *process) (string, error) { - return lookupGID(p.pstatus.gids[0]) +func processRGROUP(p *process.Process) (string, error) { + return process.LookupGID(p.Status.Gids[0]) } // processPPID returns the parent process ID of process p. -func processPPID(p *process) (string, error) { - return p.pstatus.pPid, nil -} - -// lookupUID return the textual user ID, if it can be optained, or the decimal -// input representation otherwise. -func lookupUID(uid string) (string, error) { - uidNum, err := strconv.Atoi(uid) - if err != nil { - return "", errors.Wrap(err, "error parsing user ID") - } - u, err := user.LookupUid(uidNum) - if err != nil { - return uid, nil - } - return u.Name, nil - +func processPPID(p *process.Process) (string, error) { + return p.Status.PPid, nil } // 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 // otherwise. -func processUSER(p *process) (string, error) { - return lookupUID(p.pstatus.uids[1]) +func processUSER(p *process.Process) (string, error) { + return process.LookupUID(p.Status.Uids[1]) } // 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 // otherwise. -func processRUSER(p *process) (string, error) { - return lookupUID(p.pstatus.uids[0]) +func processRUSER(p *process.Process) (string, error) { + return process.LookupUID(p.Status.Uids[0]) } // processName returns the name of process p in the format "[$name]". -func processName(p *process) (string, error) { - return fmt.Sprintf("[%s]", p.pstatus.name), nil +func processName(p *process.Process) (string, error) { + return fmt.Sprintf("[%s]", p.Status.Name), nil } // processARGS returns the command of p with all its arguments. -func processARGS(p *process) (string, error) { - args := p.cmdline +func processARGS(p *process.Process) (string, error) { + args := p.CmdLine // ps (1) returns "[$name]" if command/args are empty if len(args) == 0 { return processName(p) @@ -494,8 +450,8 @@ func processARGS(p *process) (string, error) { } // processCOMM returns the command name (i.e., executable name) of process p. -func processCOMM(p *process) (string, error) { - args := p.cmdline +func processCOMM(p *process.Process) (string, error) { + args := p.CmdLine // ps (1) returns "[$name]" if command/args are empty if len(args) == 0 { return processName(p) @@ -505,28 +461,28 @@ func processCOMM(p *process) (string, error) { } // processNICE returns the nice value of process p. -func processNICE(p *process) (string, error) { - return p.pstat.nice, nil +func processNICE(p *process.Process) (string, error) { + return p.Stat.Nice, nil } // processPID returns the process ID of process p. -func processPID(p *process) (string, error) { - return p.pstatus.pid, nil +func processPID(p *process.Process) (string, error) { + return p.Pid, nil } // processPGID returns the process group ID of process p. -func processPGID(p *process) (string, error) { - return p.pstat.pgrp, nil +func processPGID(p *process.Process) (string, error) { + return p.Stat.Pgrp, nil } // processPCPU returns how many percent of the CPU time process p uses as // a three digit float as string. -func processPCPU(p *process) (string, error) { - elapsed, err := p.elapsedTime() +func processPCPU(p *process.Process) (string, error) { + elapsed, err := p.ElapsedTime() if err != nil { return "", err } - cpu, err := p.cpuTime() + cpu, err := p.CPUTime() if err != nil { return "", err } @@ -536,8 +492,8 @@ func processPCPU(p *process) (string, error) { } // processETIME returns the elapsed time since the process was started. -func processETIME(p *process) (string, error) { - elapsed, err := p.elapsedTime() +func processETIME(p *process.Process) (string, error) { + elapsed, err := p.ElapsedTime() if err != nil { return "", nil } @@ -545,8 +501,8 @@ func processETIME(p *process) (string, error) { } // processTIME returns the cumulative CPU time of process p. -func processTIME(p *process) (string, error) { - cpu, err := p.cpuTime() +func processTIME(p *process.Process) (string, error) { + cpu, err := p.CPUTime() if err != nil { return "", err } @@ -554,29 +510,28 @@ func processTIME(p *process) (string, error) { } // processTTY returns the controlling tty (terminal) of process p. -func processTTY(p *process) (string, error) { - ttyNr, err := strconv.ParseUint(p.pstat.ttyNr, 10, 64) +func processTTY(p *process.Process) (string, error) { + ttyNr, err := strconv.ParseUint(p.Stat.TtyNr, 10, 64) if err != nil { return "", nil } - maj, min := ttyNrToDev(ttyNr) - t, err := findTTY(maj, min) + tty, err := dev.FindTTY(ttyNr) if err != nil { - return "", err + return "", nil } ttyS := "?" - if t != nil { - ttyS = strings.TrimPrefix(t.device, "/dev/") + if tty != nil { + ttyS = strings.TrimPrefix(tty.Path, "/dev/") } return ttyS, nil } // processVSZ returns the virtual memory size of process p in KiB (1024-byte // units). -func processVSZ(p *process) (string, error) { - vmsize, err := strconv.Atoi(p.pstat.vsize) +func processVSZ(p *process.Process) (string, error) { + vmsize, err := strconv.Atoi(p.Stat.Vsize) if err != nil { return "", err } @@ -591,10 +546,10 @@ func parseCAP(cap string) (string, error) { if err != nil { return "", err } - if mask == fullCAPs { + if mask == capabilities.FullCAPs { return "full", nil } - caps := maskToCaps(mask) + caps := capabilities.TranslateMask(mask) if len(caps) == 0 { return "none", nil } @@ -605,35 +560,35 @@ func parseCAP(cap string) (string, error) { // processCAPINH returns the set of inheritable capabilties associated with // process p. If all capabilties are set, "full" is returned. If no // capability is enabled, "none" is returned. -func processCAPINH(p *process) (string, error) { - return parseCAP(p.pstatus.capInh) +func processCAPINH(p *process.Process) (string, error) { + return parseCAP(p.Status.CapInh) } // processCAPPRM returns the set of permitted capabilties associated with // process p. If all capabilties are set, "full" is returned. If no // capability is enabled, "none" is returned. -func processCAPPRM(p *process) (string, error) { - return parseCAP(p.pstatus.capPrm) +func processCAPPRM(p *process.Process) (string, error) { + return parseCAP(p.Status.CapPrm) } // processCAPEFF returns the set of effective capabilties associated with // process p. If all capabilties are set, "full" is returned. If no // capability is enabled, "none" is returned. -func processCAPEFF(p *process) (string, error) { - return parseCAP(p.pstatus.capEff) +func processCAPEFF(p *process.Process) (string, error) { + return parseCAP(p.Status.CapEff) } // processCAPBND returns the set of bounding capabilties associated with // process p. If all capabilties are set, "full" is returned. If no // capability is enabled, "none" is returned. -func processCAPBND(p *process) (string, error) { - return parseCAP(p.pstatus.capBnd) +func processCAPBND(p *process.Process) (string, error) { + return parseCAP(p.Status.CapBnd) } // processSECCOMP returns the seccomp mode of the process (i.e., disabled, // strict or filter) or "?" if /proc/$pid/status.seccomp has a unknown value. -func processSECCOMP(p *process) (string, error) { - switch p.pstatus.seccomp { +func processSECCOMP(p *process.Process) (string, error) { + switch p.Status.Seccomp { case "0": return "disabled", nil case "1": @@ -645,18 +600,41 @@ func processSECCOMP(p *process) (string, error) { } } -// processLABEL returns the process label of process p. -func processLABEL(p *process) (string, error) { - data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/attr/current", p.pid)) - if err != nil { - if os.IsNotExist(err) { - // make sure the pid does not exist, - // could be system does not support labeling. - if _, err2 := os.Stat(fmt.Sprintf("/proc/%d", p.pid)); err2 != nil { - return "", errNoSuchPID - } - } - return "", err +// processLABEL returns the process label of process p or "?" if the system +// doesn't support labeling. +func processLABEL(p *process.Process) (string, error) { + return p.Label, nil +} + +// processHPID returns the PID of the corresponding host process of the +// (container) or "?" if no corresponding process could be found. +func processHPID(p *process.Process) (string, error) { + if hp := findHostProcess(p); hp != nil { + return hp.Pid, nil + } + return "?", nil +} + +// processHUSER returns the effective user ID of the corresponding host process +// of the (container) or "?" if no corresponding process could be found. +func processHUSER(p *process.Process) (string, error) { + if hp := findHostProcess(p); hp != nil { + return hp.Huser, nil + } + return "?", nil +} + +// processHGROUP returns the effective group ID of the corresponding host +// process of the (container) or "?" if no corresponding process could be +// found. +func processHGROUP(p *process.Process) (string, error) { + if hp := findHostProcess(p); hp != nil { + return hp.Hgroup, nil } - return strings.Trim(string(data), "\x00"), nil + return "?", nil +} + +// processState returns the process state of process p. +func processState(p *process.Process) (string, error) { + return p.Status.State, nil } |