podman-top: use containers/psgo

Use github.com/containers/psgo instead of execing `ps (1)`.  The psgo
library enables a much more flexible interface with respect to which
data to be printed (e.g., capabilities, seccomp mode, PID, PCPU, etc.)
while the output can be parsed reliably.  The library does not use
ps (1) but parses /proc and /dev instead.  To list the processes of a
given container, psgo will join the mount namespace of the given
container and extract all data from there.

Notice that this commit breaks compatibility with docker-top.

Signed-off-by: Valentin Rothberg <vrothberg@suse.com>

Closes: #1113
Approved by: rhatdan
This commit is contained in:
Valentin Rothberg
2018-07-19 14:41:58 +02:00
committed by Atomic Bot
parent 98703eb204
commit ba1871dac0
17 changed files with 1788 additions and 164 deletions

View File

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"text/tabwriter" "text/tabwriter"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -14,11 +15,15 @@ import (
var ( var (
topFlags = []cli.Flag{ topFlags = []cli.Flag{
LatestFlag, LatestFlag,
cli.BoolFlag{
Name: "list-descriptors",
Hidden: true,
},
} }
topDescription = ` topDescription = `Display the running processes of the container. Specify format descriptors
podman top to alter the output. You may run "podman top -l pid pcpu seccomp" to print
the process ID, the CPU percentage and the seccomp mode of each process of
Display the running processes of the container. the latest container.
` `
topCommand = cli.Command{ topCommand = cli.Command{
@ -27,7 +32,7 @@ var (
Description: topDescription, Description: topDescription,
Flags: topFlags, Flags: topFlags,
Action: topCmd, Action: topCmd,
ArgsUsage: "CONTAINER-NAME", ArgsUsage: "CONTAINER-NAME [format descriptors]",
SkipArgReorder: true, SkipArgReorder: true,
} }
) )
@ -37,6 +42,15 @@ func topCmd(c *cli.Context) error {
var err error var err error
args := c.Args() args := c.Args()
if c.Bool("list-descriptors") {
descriptors, err := libpod.GetContainerPidInformationDescriptors()
if err != nil {
return err
}
fmt.Println(strings.Join(descriptors, "\n"))
return nil
}
if len(args) < 1 && !c.Bool("latest") { if len(args) < 1 && !c.Bool("latest") {
return errors.Errorf("you must provide the name or id of a running container") return errors.Errorf("you must provide the name or id of a running container")
} }
@ -50,9 +64,12 @@ func topCmd(c *cli.Context) error {
} }
defer runtime.Shutdown(false) defer runtime.Shutdown(false)
var descriptors []string
if c.Bool("latest") { if c.Bool("latest") {
descriptors = args
container, err = runtime.GetLatestContainer() container, err = runtime.GetLatestContainer()
} else { } else {
descriptors = args[1:]
container, err = runtime.LookupContainer(args[0]) container, err = runtime.LookupContainer(args[0])
} }
@ -67,12 +84,12 @@ func topCmd(c *cli.Context) error {
return errors.Errorf("top can only be used on running containers") return errors.Errorf("top can only be used on running containers")
} }
psOutput, err := container.GetContainerPidInformation([]string{}) psOutput, err := container.GetContainerPidInformation(descriptors)
if err != nil { if err != nil {
return err return err
} }
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0)
for _, proc := range psOutput { for _, proc := range psOutput {
fmt.Fprintln(w, proc) fmt.Fprintln(w, proc)
} }

View File

@ -1809,6 +1809,14 @@ _podman_tag() {
esac esac
} }
__podman_top_descriptors() {
podman top --list-descriptors
}
__podman_complete_top_descriptors() {
COMPREPLY=($(compgen -W "$(__podman_top_descriptors)" -- "$cur"))
}
_podman_top() { _podman_top() {
local options_with_args=" local options_with_args="
" "
@ -1818,6 +1826,14 @@ _podman_top() {
--latest --latest
-l -l
" "
# podman-top works on only *one* container, which means that when we have
# three or more arguments, we can complete with top descriptors.
if [[ "${COMP_CWORD}" -ge 3 ]]; then
__podman_complete_top_descriptors
return
fi
case "$cur" in case "$cur" in
-*) -*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))

View File

@ -4,10 +4,10 @@
podman\-top - Display the running processes of a container podman\-top - Display the running processes of a container
## SYNOPSIS ## SYNOPSIS
**podman top** [*options*] *container* [*ps-options*] **podman top** [*options*] *container* [*format-descriptors*]
## DESCRIPTION ## DESCRIPTION
Display the running process of the container. *ps-options* can be any of the options you would pass to `ps(1)`. Display the running process of the container. The *format-descriptors* are ps (1) compatible AIX format descriptors but extended to print additional information, such as the seccomp mode or the effective capabilities of a given process.
## OPTIONS ## OPTIONS
@ -20,25 +20,59 @@ Display the running process of the container. *ps-options* can be any of the opt
Instead of providing the container name or ID, use the last created container. If you use methods other than Podman Instead of providing the container name or ID, use the last created container. If you use methods other than Podman
to run containers such as CRI-O, the last started container could be from either of those methods. to run containers such as CRI-O, the last started container could be from either of those methods.
## FORMAT DESCRIPTORS
The following descriptors are supported in addition to the AIX format descriptors mentioned in ps (1):
**capinh**
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 effective 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.
**label**
Current security attributes of the process.
## EXAMPLES ## EXAMPLES
By default, `podman-top` prints data similar to `ps -ef`:
``` ```
# podman top f5a62a71b07 # podman top f5a62a71b07
UID PID PPID %CPU STIME TT TIME CMD USER PID PPID %CPU ELAPSED TTY TIME COMMAND
0 18715 18705 0.0 10:35 pts/0 00:00:00 /bin/bash root 1 0 0.000 20.386825206s pts/0 0s sh
0 18741 18715 0.0 10:35 pts/0 00:00:00 vi root 7 1 0.000 16.386882887s pts/0 0s sleep
# root 8 1 0.000 11.386886562s pts/0 0s vi
``` ```
The output can be controlled by specifying format descriptors as arguments after the container:
``` ```
#podman --log-level=debug top f5a62a71b07 -o pid,fuser,f,comm,label # sudo ./bin/podman top -l pid seccomp args %C
PID FUSER F COMMAND LABEL PID SECCOMP COMMAND %CPU
18715 root 4 bash system_u:system_r:container_t:s0:c429,c1016 1 filter sh 0.000
18741 root 0 vi system_u:system_r:container_t:s0:c429,c1016 8 filter vi /etc/ 0.000
#
``` ```
## SEE ALSO ## SEE ALSO
podman(1), ps(1) podman(1), ps(1), seccomp(2), capabilities(7)
## HISTORY ## HISTORY
December 2017, Originally compiled by Brent Baude<bbaude@redhat.com> December 2017, Originally compiled by Brent Baude<bbaude@redhat.com>
July 2018, Introduce format descriptors by Valentin Rothberg <vrothberg@suse.com>

View File

@ -1,135 +0,0 @@
package libpod
import (
"io/ioutil"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/pkg/util"
"github.com/projectatomic/libpod/utils"
"github.com/sirupsen/logrus"
)
// GetContainerPids reads sysfs to obtain the pids associated with the container's cgroup
// and uses locking
func (c *Container) GetContainerPids() ([]string, error) {
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
if err := c.syncContainer(); err != nil {
return []string{}, errors.Wrapf(err, "error updating container %s state", c.ID())
}
}
return c.getContainerPids()
}
// Gets the pids for a container without locking. should only be called from a func where
// locking has already been established.
func (c *Container) getContainerPids() ([]string, error) {
cgroupPath, err := c.CGroupPath()
if err != nil {
return nil, err
}
taskFile := filepath.Join("/sys/fs/cgroup/pids", cgroupPath, "tasks")
logrus.Debug("reading pids from ", taskFile)
content, err := ioutil.ReadFile(taskFile)
if err != nil {
return []string{}, errors.Wrapf(err, "unable to read pids from %s", taskFile)
}
return strings.Fields(string(content)), nil
}
// GetContainerPidInformation calls ps with the appropriate options and returns
// the results as a string and the container's PIDs as a []string. Note that
// the ps output columns of each string are separated by a '\t\'. Currently,
// the args argument is overwriten with []string{"-ef"} until there is a
// portable library for ps-1 or to parse the procFS to extract all data.
func (c *Container) GetContainerPidInformation(args []string) ([]string, error) {
// XXX(ps-issue): args is overwriten with []{"-ef"} as the ps-1 tool
// doesn't support a generic way of splitting columns, rendering its
// output hard to parse. Docker first deterimes the number of columns
// and merges all exceeding ones into the last one. We believe that
// writing a go library which parses procFS in a ps-compatible way may
// be more beneficial in the long run. Until then, args will be
// ignored.
args = []string{"-ef"}
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
if err := c.syncContainer(); err != nil {
return []string{}, errors.Wrapf(err, "error updating container %s state", c.ID())
}
}
pids, err := c.getContainerPids()
if err != nil {
return []string{}, errors.Wrapf(err, "unable to obtain pids for ", c.ID())
}
args = append(args, "-p", strings.Join(pids, ","))
logrus.Debug("Executing: ", strings.Join(args, " "))
results, err := utils.ExecCmd("ps", args...)
if err != nil {
return []string{}, errors.Wrapf(err, "unable to obtain information about pids")
}
filteredOutput, err := filterPids(results, pids)
if err != nil {
return []string{}, err
}
return filteredOutput, nil
}
func filterPids(psOutput string, pids []string) ([]string, error) {
var output []string
results := strings.Split(psOutput, "\n")
// The headers are in the first line of the results
headers := fieldsASCII(results[0])
// We need to make sure PID in headers, so that we can filter
// Pids that don't belong.
// append the headers back in
output = append(output, strings.Join(headers, "\t"))
pidIndex := -1
for i, header := range headers {
if header == "PID" {
pidIndex = i
}
}
if pidIndex == -1 {
return []string{}, errors.Errorf("unable to find PID field in ps output. try a different set of ps arguments")
}
for _, l := range results[1:] {
if l == "" {
continue
}
cols := fieldsASCII(l)
pid := cols[pidIndex]
if util.StringInSlice(pid, pids) {
// XXX(ps-issue): Strip cols down to the header's size
// and merge exceeding fields. This is required to
// "merge" the overhanging CMD entries which can
// contain white spaces.
out := cols[:len(headers)-1]
out = append(out, strings.Join(cols[len(headers)-1:], " "))
output = append(output, strings.Join(out, "\t"))
}
}
return output, nil
}
// Detects ascii whitespaces
func fieldsASCII(s string) []string {
fn := func(r rune) bool {
switch r {
case '\t', '\n', '\f', '\r', ' ':
return true
}
return false
}
return strings.FieldsFunc(s, fn)
}

View File

@ -0,0 +1,30 @@
// +build linux
package libpod
import (
"strconv"
"strings"
"github.com/containers/psgo/ps"
)
// GetContainerPidInformation returns process-related data of all processes in
// the container. The output data can be controlled via the `descriptors`
// argument which expects format descriptors and supports all AIXformat
// descriptors of ps (1) plus some additional ones to for instance inspect the
// set of effective capabilities. Eeach element in the returned string slice
// is a tab-separated string.
//
// For more details, please refer to github.com/containers/psgo.
func (c *Container) GetContainerPidInformation(descriptors []string) ([]string, error) {
pid := strconv.Itoa(c.state.PID)
format := strings.Join(descriptors, ",")
return ps.JoinNamespaceAndProcessInfo(pid, format)
}
// GetContainerPidInformationDescriptors returns a string slice of all supported
// format descriptors of GetContainerPidInformation.
func GetContainerPidInformationDescriptors() ([]string, error) {
return ps.ListDescriptors(), nil
}

View File

@ -0,0 +1,21 @@
// +build !linux
package libpod
// GetContainerPidInformation returns process-related data of all processes in
// the container. The output data can be controlled via the `descriptors`
// argument which expects format descriptors and supports all AIXformat
// descriptors of ps (1) plus some additional ones to for instance inspect the
// set of effective capabilities. Eeach element in the returned string slice
// is a tab-separated string.
//
// For more details, please refer to github.com/containers/psgo.
func (c *Container) GetContainerPidInformation(descriptors []string) ([]string, error) {
return nil, ErrNotImplemented
}
// GetContainerPidInformationDescriptors returns a string slice of all supported
// format descriptors of GetContainerPidInformation.
func GetContainerPidInformationDescriptors() ([]string, error) {
return nil, ErrNotImplemented
}

View File

@ -59,32 +59,24 @@ var _ = Describe("Podman top", func() {
Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1)) Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1))
}) })
// XXX(ps-issue): for the time being, podman-top and the libpod API
// GetContainerPidInformation(...) will ignore any arguments passed to ps,
// so we have to disable the tests below. Please refer to
// https://github.com/projectatomic/libpod/pull/939 for more background
// information.
It("podman top with options", func() { It("podman top with options", func() {
Skip("podman-top with options: options are temporarily ignored")
session := podmanTest.Podman([]string{"run", "-d", ALPINE, "top", "-d", "2"}) session := podmanTest.Podman([]string{"run", "-d", ALPINE, "top", "-d", "2"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0)) Expect(session.ExitCode()).To(Equal(0))
result := podmanTest.Podman([]string{"top", session.OutputToString(), "-o", "pid,fuser,f,comm,label"}) result := podmanTest.Podman([]string{"top", session.OutputToString(), "pid", "%C", "args"})
result.WaitWithDefaultTimeout() result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(0)) Expect(result.ExitCode()).To(Equal(0))
Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1)) Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1))
}) })
It("podman top on container invalid options", func() { It("podman top on container invalid options", func() {
Skip("podman-top with invalid options: options are temporarily ignored")
top := podmanTest.RunTopContainer("") top := podmanTest.RunTopContainer("")
top.WaitWithDefaultTimeout() top.WaitWithDefaultTimeout()
Expect(top.ExitCode()).To(Equal(0)) Expect(top.ExitCode()).To(Equal(0))
cid := top.OutputToString() cid := top.OutputToString()
result := podmanTest.Podman([]string{"top", cid, "-o time"}) result := podmanTest.Podman([]string{"top", cid, "invalid"})
result.WaitWithDefaultTimeout() result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(125)) Expect(result.ExitCode()).To(Equal(125))
}) })

View File

@ -12,6 +12,7 @@ github.com/containernetworking/cni v0.7.0-alpha1
github.com/containernetworking/plugins 1fb94a4222eafc6f948eacdca9c9f2158b427e53 github.com/containernetworking/plugins 1fb94a4222eafc6f948eacdca9c9f2158b427e53
github.com/containers/image c6e0eee0f8eb38e78ae2e44a9aeea0576f451617 github.com/containers/image c6e0eee0f8eb38e78ae2e44a9aeea0576f451617
github.com/containers/storage 8b1a0f8d6863cf05709af333b8997a437652ec4c github.com/containers/storage 8b1a0f8d6863cf05709af333b8997a437652ec4c
github.com/containers/psgo 59a9dad536216e91da1861c9fbba75b85da84dcd
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

201
vendor/github.com/containers/psgo/LICENSE generated vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

60
vendor/github.com/containers/psgo/README.md generated vendored Normal file
View File

@ -0,0 +1,60 @@
[![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)
# psgo
A ps (1) AIX-format compatible golang library. Please note, that the library is still under development.
The idea behind the library is to implement 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.
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` from there. Currently, the API consists of two functions:
- `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 an array of tab-separated strings, to easily use the output for column-based formatting (e.g., with the `text/tabwriter` package).
- `JoinNamespaceAndProcessInfo(pid, format 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.
A sample implementation using this API can be found [here](https://github.com/containers/psgo/blob/master/psgo.go). You can compile the sample `psgo` tool via `make build`.
### Listing processes
```
./bin/psgo | head -n5
USER PID PPID %CPU ELAPSED TTY TIME COMMAND
root 1 0 0.064 6h3m27.677997443s ? 13.98s systemd
root 2 0 0.000 6h3m27.678380128s ? 20ms [kthreadd]
root 4 2 0.000 6h3m27.678701852s ? 0s [kworker/0:0H]
root 6 2 0.000 6h3m27.678999508s ? 0s [mm_percpu_wq]
```
### Changing the output format
The format strings are ps (1) AIX format strings, and must be separated by commas:
```
CODE NORMAL HEADER
%C pcpu %CPU
%G group GROUP
%P ppid PPID
%U user USER
%a args COMMAND
%c comm COMMAND
%g rgroup RGROUP
%n nice NI
%p pid PID
%r pgid PGID
%t etime ELAPSED
%u ruser RUSER
%x time TIME
%y tty TTY
%z vsz VSZ
```
To extract the effective user ID, the PID and and the command (i.e., name of the binary), we can run `./bin/psgo -format "user, %p, comm"`. Notice, that both, the *code* and *normal* notation of the descriptors can be used.
### List processes inside a container / Joining another mount namespace
To demonstrate the usecase for containers, let's run a container and display the running processes inside this container:
```
$ docker run -d --name foo alpine sleep 100
$ docker inspect --format '{{.State.Pid}}' foo
$ sudo ./bin/psgo -pid1377
USER PID PPID %CPU ELAPSED TTY TIME COMMAND
root 1 0 0.193 25.959923679s ? 50ms sleep
```

71
vendor/github.com/containers/psgo/ps/capabilities.go generated vendored Normal file
View File

@ -0,0 +1,71 @@
package ps
var (
// capabilities are a mapping from a numerical value to the textual
// representation of a given capability. A map allows to easily check
// if a given value is included or not.
//
// NOTE: this map must be maintained and kept in sync with the
// ./include/uapi/linux/capability.h kernel header.
capabilities = map[uint]string{
0: "CHOWN",
1: "DAC_OVERRIDE",
2: "DAC_READ_SEARCH",
3: "FOWNER",
4: "FSETID",
5: "KILL",
6: "SETGID",
7: "SETUID",
8: "SETPCAP",
9: "LINUX_IMMUTABLE",
10: "NET_BIND_SERVICE",
11: "NET_BROADCAST",
12: "NET_ADMIN",
13: "NET_RAW",
14: "IPC_LOCK",
15: "IPC_OWNER",
16: "SYS_MODULE",
17: "SYS_RAWIO",
18: "SYS_CHROOT",
19: "SYS_PTRACE",
20: "SYS_PACCT",
21: "SYS_ADMIN",
22: "SYS_BOOT",
23: "SYS_NICE",
24: "SYS_RESOURCE",
25: "SYS_TIME",
26: "SYS_TTY_CONFIG",
27: "MKNOD",
28: "LEASE",
29: "AUDIT_WRITE",
30: "AUDIT_CONTROL",
31: "SETFCAP",
32: "MAC_OVERRIDE",
33: "MAC_ADMIN",
34: "SYSLOG",
35: "WAKE_ALARM",
36: "BLOCK_SUSPEND",
37: "AUDIT_READ",
}
// fullCAPs represents the value of a bitmask with a full capability
// set.
fullCAPs = uint64(0x3FFFFFFFFF)
)
// maskToCaps 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 {
caps := []string{}
for i := uint(0); i < 64; i++ {
if (mask>>i)&0x1 == 1 {
c, known := capabilities[i]
if !known {
c = "unknown"
}
caps = append(caps, c)
}
}
return caps
}

34
vendor/github.com/containers/psgo/ps/cmdline.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
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
}

655
vendor/github.com/containers/psgo/ps/ps.go generated vendored Normal file
View File

@ -0,0 +1,655 @@
package ps
import (
"fmt"
"io/ioutil"
"os"
"os/user"
"runtime"
"strconv"
"strings"
"sync"
"time"
"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"
var (
// ErrUnkownDescriptor is returned when an unknown descriptor is parsed.
ErrUnkownDescriptor = errors.New("unknown descriptor")
// errNoSuchPID is returned when `/proc/PID` does not exist (anymore).
errNoSuchPID = errors.New("PID does not exist in /proc")
// bootTime holds the host's boot time. Singleton to safe some time and
// energy.
bootTime int64
// clockTicks is the value of sysconf(SC_CLK_TCK)
clockTicks = getClockTicks()
// ttyDevices is a slice of ttys. Singledton to safe some time and
// energy.
ttyDevices []*tty
descriptors = []aixFormatDescriptor{
{
code: "%C",
normal: "pcpu",
header: "%CPU",
procFn: processPCPU,
},
{
code: "%G",
normal: "group",
header: "GROUP",
procFn: processGROUP,
},
{
code: "%P",
normal: "ppid",
header: "PPID",
procFn: processPPID,
},
{
code: "%U",
normal: "user",
header: "USER",
procFn: processUSER,
},
{
code: "%a",
normal: "args",
header: "COMMAND",
procFn: processARGS,
},
{
code: "%c",
normal: "comm",
header: "COMMAND",
procFn: processCOMM,
},
{
code: "%g",
normal: "rgroup",
header: "RGROUP",
procFn: processRGROUP,
},
{
code: "%n",
normal: "nice",
header: "NI",
procFn: processNICE,
},
{
code: "%p",
normal: "pid",
header: "PID",
procFn: processPID,
},
{
code: "%r",
normal: "pgid",
header: "PGID",
procFn: processPGID,
},
{
code: "%t",
normal: "etime",
header: "ELAPSED",
procFn: processETIME,
},
{
code: "%u",
normal: "ruser",
header: "RUSER",
procFn: processRUSER,
},
{
code: "%x",
normal: "time",
header: "TIME",
procFn: processTIME,
},
{
code: "%y",
normal: "tty",
header: "TTY",
procFn: processTTY,
},
{
code: "%z",
normal: "vsz",
header: "VSZ",
procFn: processVSZ,
},
{
normal: "capinh",
header: "CAPABILITIES",
procFn: processCAPINH,
},
{
normal: "capprm",
header: "CAPABILITIES",
procFn: processCAPPRM,
},
{
normal: "capeff",
header: "CAPABILITIES",
procFn: processCAPEFF,
},
{
normal: "capbnd",
header: "CAPABILITIES",
procFn: processCAPBND,
},
{
normal: "seccomp",
header: "SECCOMP",
procFn: processSECCOMP,
},
{
normal: "label",
header: "LABEL",
procFn: processLABEL,
},
}
)
// 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 {
list = append(list, d.normal)
}
return
}
// 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) {
var (
data []string
dataErr error
wg sync.WaitGroup
)
wg.Add(1)
go func() {
defer wg.Done()
runtime.LockOSThread()
fd, err := os.Open(fmt.Sprintf("/proc/%s/ns/mnt", pid))
if err != nil {
dataErr = err
return
}
defer fd.Close()
// create a new mountns on the current thread
if err = unix.Unshare(unix.CLONE_NEWNS); err != nil {
dataErr = err
return
}
unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS)
data, dataErr = ProcessInfo(format)
}()
wg.Wait()
return data, dataErr
}
// 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 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
}
formatDescriptors, err := parseDescriptors(format)
if err != nil {
return nil, err
}
processes, err := processes()
if err != nil {
return nil, err
}
return processDescriptors(formatDescriptors, 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)
}
}
return formatDescriptors, nil
}
// lookupGID returns the textual group ID, if it can be optained, or the
// decimal input representation otherwise.
func lookupGID(gid string) (string, error) {
if gid == "0" {
return "root", nil
}
g, err := user.LookupGroupId(gid)
if err != nil {
switch err.(type) {
case user.UnknownGroupIdError:
return gid, nil
default:
return "", err
}
}
return g.Name, 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])
}
// 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])
}
// 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) {
if uid == "0" {
return "root", nil
}
u, err := user.LookupId(uid)
if err != nil {
switch err.(type) {
case user.UnknownUserError:
return uid, nil
default:
return "", err
}
}
return u.Username, 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])
}
// 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])
}
// 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
}
// processARGS returns the command of p with all its arguments.
func processARGS(p *process) (string, error) {
args := p.cmdline
// ps (1) returns "[$name]" if command/args are empty
if len(args) == 0 {
return processName(p)
}
return strings.Join(args, " "), nil
}
// processCOMM returns the command name (i.e., executable name) of process p.
func processCOMM(p *process) (string, error) {
args := p.cmdline
// ps (1) returns "[$name]" if command/args are empty
if len(args) == 0 {
return processName(p)
}
spl := strings.Split(args[0], "/")
return spl[len(spl)-1], nil
}
// processNICE returns the nice value of process p.
func processNICE(p *process) (string, error) {
return p.pstat.nice, nil
}
// processPID returns the process ID of process p.
func processPID(p *process) (string, error) {
return p.pstatus.pid, nil
}
// processPGID returns the process group ID of process p.
func processPGID(p *process) (string, error) {
return p.pstat.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()
if err != nil {
return "", err
}
cpu, err := p.cpuTime()
if err != nil {
return "", err
}
pcpu := 100 * cpu.Seconds() / elapsed.Seconds()
return strconv.FormatFloat(pcpu, 'f', 3, 64), nil
}
// processETIME returns the elapsed time since the process was started.
func processETIME(p *process) (string, error) {
elapsed, err := p.elapsedTime()
if err != nil {
return "", nil
}
return fmt.Sprintf("%v", elapsed), nil
}
// processTIME returns the cumulative CPU time of process p.
func processTIME(p *process) (string, error) {
cpu, err := p.cpuTime()
if err != nil {
return "", err
}
return fmt.Sprintf("%v", cpu), nil
}
// processTTY returns the controlling tty (terminal) of process p.
func processTTY(p *process) (string, error) {
ttyNr, err := strconv.ParseUint(p.pstat.ttyNr, 10, 64)
if err != nil {
return "", nil
}
maj, min := ttyNrToDev(ttyNr)
t, err := findTTY(maj, min)
if err != nil {
return "", err
}
ttyS := "?"
if t != nil {
ttyS = strings.TrimPrefix(t.device, "/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)
if err != nil {
return "", err
}
return fmt.Sprintf("%d", vmsize/1024), nil
}
// parseCAP parses cap (a string bit mask) and returns the associated set of
// capabilities. If all capabilties are set, "full" is returned. If no
// capability is enabled, "none" is returned.
func parseCAP(cap string) (string, error) {
mask, err := strconv.ParseUint(cap, 16, 64)
if err != nil {
return "", err
}
if mask == fullCAPs {
return "full", nil
}
caps := maskToCaps(mask)
if len(caps) == 0 {
return "none", nil
}
return strings.Join(caps, ", "), nil
}
// 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)
}
// 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)
}
// 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)
}
// 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)
}
// 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 {
case "0":
return "disabled", nil
case "1":
return "strict", nil
case "2":
return "filter", nil
default:
return "?", nil
}
}
// 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
}
return strings.Trim(string(data), "\x00"), nil
}

193
vendor/github.com/containers/psgo/ps/stat.go generated vendored Normal file
View File

@ -0,0 +1,193 @@
package ps
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
// 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 {
// (1) The process ID
pid string
// (2) The filename of the executable, in parentheses. This is visible
// whether or not the executable is swapped out.
comm string
// (3) The process state (e.g., running, sleeping, zombie, dead).
// Refer to proc(5) for further deatils.
state string
// (4) The PID of the parent of this process.
ppid string
// (5) The process group ID of the process.
pgrp string
// (6) The session ID of the process.
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
// (8) The ID of the foreground process group of the controlling
// terminal of the process.
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
// (10) The number of minor faults the process has made which have not
// required loading a memory page from disk.
minflt string
// (11) The number of minor faults that the process's waited-for
// children have made.
cminflt string
// (12) The number of major faults the process has made which have
// required loading a memory page from disk.
majflt string
// (13) The number of major faults that the process's waited-for
// children have made.
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
// (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
// (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
// (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
// (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
// in the range -2 to -100, corresponding to real-time priorities 1 to
// 99. For processes running under a non-real-time scheduling
// policy, this is the raw nice value (setpriority(2)) as represented
// 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
// (19) The nice value (see setpriority(2)), a value in the range 19
// (low priority) to -20 (high priority).
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
// (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
// (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
// (23) Virtual memory size in bytes.
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)
if err != nil {
return nil, err
}
fieldAt := func(i int) string {
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),
}, nil
}

314
vendor/github.com/containers/psgo/ps/status.go generated vendored Normal file
View File

@ -0,0 +1,314 @@
package ps
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/pkg/errors"
)
// 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 {
// Name: Command run by this process.
name string
// Umask: Process umask, expressed in octal with a leading zero; see
// umask(2). (Since Linux 4.7.)
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
// Tgid: Thread group ID (i.e., Process ID).
tgid string
// Ngid: NUMA group ID (0 if none; since Linux 3.13).
ngid string
// Pid: Thread ID (see gettid(2)).
pid string
// PPid: PID of parent process.
pPid string
// TracerPid: PID of process tracing this process (0 if not being traced).
tracerPid string
// Uids: Real, effective, saved set, and filesystem.
uids []string
// Gids: Real, effective, saved set, and filesystem.
gids []string
// FDSize: Number of file descriptor slots currently allocated.
fdSize string
// Groups: Supplementary group list.
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
// 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
// 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
// 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
// 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
// RssAnon, RssFile, and RssShmem.
vmRSS string
// RssAnon: Size of resident anonymous memory. (since Linux 4.5).
rssAnon string
// RssFile: Size of resident file mappings. (since Linux 4.5).
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;
// shmem swap usage is not included (since Linux 2.6.34).
vmSwap string
// HugetlbPages: Size of hugetlb memory portions. (since Linux 4.4).
hugetlbPages string
// Threads: Number of threads in process containing this thread.
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
// SigPnd: Number of signals pending for thread and for (see pthreads(7)).
sigPnd string
// ShdPnd: Number of signals pending for process as a whole (see
// signal(7)).
shdPnd string
// SigBlk: Mask indicating signals being blocked (see signal(7)).
sigBlk string
// SigIgn: Mask indicating signals being ignored (see signal(7)).
sigIgn string
// SigCgt: Mask indicating signals being blocked caught (see signal(7)).
sigCgt string
// CapInh: Mask of capabilities enabled in inheritable sets (see
// capabilities(7)).
capInh string
// CapPrm: Mask of capabilities enabled in permitted sets (see
// capabilities(7)).
capPrm string
// CapEff: Mask of capabilities enabled in effective sets (see
// capabilities(7)).
capEff string
// CapBnd: Capability Bounding set (since Linux 2.6.26, see
// capabilities(7)).
capBnd string
// CapAmb: Ambient capability set (since Linux 4.3, see capabilities(7)).
capAmb string
// NoNewPrivs: Value of the no_new_privs bit (since Linux 4.10, see
// prctl(2)).
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
// Cpus_allowed: Mask of CPUs on which this process may run
// (since Linux 2.6.24, see cpuset(7)).
cpusAllowed string
// Cpus_allowed_list: Same as previous, but in "list format" (since
// Linux 2.6.26, see cpuset(7)).
cpusAllowedList string
// Mems_allowed: Mask of memory nodes allowed to this process
// (since Linux 2.6.24, see cpuset(7)).
memsAllowed string
// Mems_allowed_list: Same as previous, but in "list format" (since
// Linux 2.6.26, see cpuset(7)).
memsAllowedList string
// voluntaryCtxtSwitches: Number of voluntary context switches
// (since Linux 2.6.23).
voluntaryCtxtSwitches string
// nonvoluntaryCtxtSwitches: Number of involuntary context switches
// (since Linux 2.6.23).
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{}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
return lines, nil
}
// parseStatus parses the /proc/$pid/status file and returns a *status.
func parseStatus(path string) (*status, error) {
lines, err := readStatus(path)
if err != nil {
return nil, err
}
s := status{}
errUnexpectedInput := errors.New(fmt.Sprintf("unexpected input from %s", path))
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
switch fields[0] {
case "Name:":
s.name = fields[1]
case "Umask:":
s.umask = fields[1]
case "State:":
s.state = fields[1]
case "Tgid:":
s.tgid = fields[1]
case "Ngid:":
s.ngid = fields[1]
case "Pid:":
s.pid = fields[1]
case "PPid:":
s.pPid = fields[1]
case "TracerPid:":
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]}
case "Gid:":
if len(fields) != 5 {
return nil, errors.Wrap(errUnexpectedInput, line)
}
s.gids = []string{fields[1], fields[2], fields[3], fields[4]}
case "FDSize:":
s.fdSize = fields[1]
case "Groups:":
for _, g := range fields[1:] {
s.groups = append(s.groups, g)
}
case "NStgid:":
s.nStgid = fields[1]
case "NSpid:":
s.nSpid = fields[1]
case "NSpgid:":
s.nSpgid = fields[1]
case "NSsid:":
s.nSsid = fields[1]
case "VmPeak:":
s.vmPeak = fields[1]
case "VmSize:":
s.vmSize = fields[1]
case "VmLck:":
s.vmLCK = fields[1]
case "VmPin:":
s.vmPin = fields[1]
case "VmHWM:":
s.vmHWM = fields[1]
case "VmRSS:":
s.vmRSS = fields[1]
case "RssAnon:":
s.rssAnon = fields[1]
case "RssFile:":
s.rssFile = fields[1]
case "RssShmem:":
s.rssShmem = fields[1]
case "VmData:":
s.vmData = fields[1]
case "VmStk:":
s.vmStk = fields[1]
case "VmExe:":
s.vmExe = fields[1]
case "VmLib:":
s.vmLib = fields[1]
case "VmPTE:":
s.vmPTE = fields[1]
case "VmPMD:":
s.vmPMD = fields[1]
case "VmSwap:":
s.vmSwap = fields[1]
case "HugetlbPages:":
s.hugetlbPages = fields[1]
case "Threads:":
s.threads = fields[1]
case "SigQ:":
s.sigQ = fields[1]
case "SigPnd:":
s.sigPnd = fields[1]
case "ShdPnd:":
s.shdPnd = fields[1]
case "SigBlk:":
s.sigBlk = fields[1]
case "SigIgn:":
s.sigIgn = fields[1]
case "SigCgt:":
s.sigCgt = fields[1]
case "CapInh:":
s.capInh = fields[1]
case "CapPrm:":
s.capPrm = fields[1]
case "CapEff:":
s.capEff = fields[1]
case "CapBnd:":
s.capBnd = fields[1]
case "CapAmb:":
s.capAmb = fields[1]
case "NoNewPrivs:":
s.noNewPrivs = fields[1]
case "Seccomp:":
s.seccomp = fields[1]
case "Cpus_allowed:":
s.cpusAllowed = fields[1]
case "Cpus_allowed_list:":
s.cpusAllowedList = fields[1]
case "Mems_allowed:":
s.memsAllowed = fields[1]
case "Mems_allowed_list:":
s.memsAllowedList = fields[1]
case "voluntary_ctxt_switches:":
s.voluntaryCtxtSwitches = fields[1]
case "nonvoluntary_ctxt_switches:":
s.nonvoluntaryCtxtSwitches = fields[1]
}
}
return &s, nil
}

113
vendor/github.com/containers/psgo/ps/tty.go generated vendored Normal file
View File

@ -0,0 +1,113 @@
package ps
import (
"os"
"strings"
"syscall"
)
// 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
}
// 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)
}
// 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) {
// (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 err != nil {
return nil, err
}
}
for _, t := range ttyDevices {
if t.minor == min && t.major == maj {
return t, nil
}
}
return nil, nil
}
// getTTYs parses /dev for tty and pts devices.
func getTTYs() ([]*tty, error) {
devDir, err := os.Open("/dev/")
if err != nil {
return nil, err
}
defer devDir.Close()
devices := []string{}
devTTYs, err := devDir.Readdirnames(0)
if err != nil {
return nil, err
}
for _, d := range devTTYs {
if !strings.HasPrefix(d, "tty") {
continue
}
devices = append(devices, "/dev/"+d)
}
devPTSDir, err := os.Open("/dev/pts/")
if err != nil {
return nil, err
}
defer devPTSDir.Close()
devPTSs, err := devPTSDir.Readdirnames(0)
if err != nil {
return nil, err
}
for _, d := range devPTSs {
devices = append(devices, "/dev/pts/"+d)
}
ttys := []*tty{}
for _, dev := range devices {
fi, err := os.Stat(dev)
if err != nil {
if os.IsNotExist(err) {
// catch race conditions
continue
}
return nil, err
}
s := fi.Sys().(*syscall.Stat_t)
t := tty{
minor: minDevNum(s.Rdev),
major: majDevNum(s.Rdev),
device: dev,
}
ttys = append(ttys, &t)
}
return ttys, nil
}

7
vendor/github.com/containers/psgo/vendor.conf generated vendored Normal file
View File

@ -0,0 +1,7 @@
github.com/davecgh/go-spew master
github.com/pkg/errors master
github.com/pmezard/go-difflib master
github.com/sirupsen/logrus master
github.com/stretchr/testify master
golang.org/x/crypto master
golang.org/x/sys master