diff options
-rw-r--r-- | cmd/podman/libpodruntime/runtime.go | 5 | ||||
-rw-r--r-- | cmd/podman/top.go | 31 | ||||
-rw-r--r-- | completions/bash/podman | 16 | ||||
-rw-r--r-- | docs/podman-top.1.md | 58 | ||||
-rw-r--r-- | docs/podman.1.md | 8 | ||||
-rw-r--r-- | libpod/container_top.go | 135 | ||||
-rw-r--r-- | libpod/container_top_linux.go | 30 | ||||
-rw-r--r-- | libpod/container_top_unsupported.go | 21 | ||||
-rw-r--r-- | libpod/image/pull.go | 8 | ||||
-rw-r--r-- | pkg/registries/registries.go | 17 | ||||
-rw-r--r-- | pkg/secrets/secrets.go | 18 | ||||
-rw-r--r-- | test/e2e/top_test.go | 12 | ||||
-rw-r--r-- | vendor.conf | 1 | ||||
-rw-r--r-- | vendor/github.com/containers/psgo/LICENSE | 201 | ||||
-rw-r--r-- | vendor/github.com/containers/psgo/README.md | 60 | ||||
-rw-r--r-- | vendor/github.com/containers/psgo/ps/capabilities.go | 71 | ||||
-rw-r--r-- | vendor/github.com/containers/psgo/ps/cmdline.go | 34 | ||||
-rw-r--r-- | vendor/github.com/containers/psgo/ps/ps.go | 655 | ||||
-rw-r--r-- | vendor/github.com/containers/psgo/ps/stat.go | 193 | ||||
-rw-r--r-- | vendor/github.com/containers/psgo/ps/status.go | 314 | ||||
-rw-r--r-- | vendor/github.com/containers/psgo/ps/tty.go | 113 | ||||
-rw-r--r-- | vendor/github.com/containers/psgo/vendor.conf | 7 |
22 files changed, 1832 insertions, 176 deletions
diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index 098864810..3216d288b 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -57,6 +57,11 @@ func GetDefaultStoreOptions() (storage.StoreOptions, error) { if err != nil { return storageOpts, err } + + storageConf := filepath.Join(os.Getenv("HOME"), ".config/containers/storage.conf") + if _, err := os.Stat(storageConf); err == nil { + storage.ReloadConfigurationFile(storageConf, &storageOpts) + } } return storageOpts, nil } diff --git a/cmd/podman/top.go b/cmd/podman/top.go index d848723e2..749d97505 100644 --- a/cmd/podman/top.go +++ b/cmd/podman/top.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "strings" "text/tabwriter" "github.com/pkg/errors" @@ -14,11 +15,15 @@ import ( var ( topFlags = []cli.Flag{ LatestFlag, + cli.BoolFlag{ + Name: "list-descriptors", + Hidden: true, + }, } - topDescription = ` - podman top - - Display the running processes of the container. + topDescription = `Display the running processes of the container. Specify format descriptors +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 +the latest container. ` topCommand = cli.Command{ @@ -27,7 +32,7 @@ var ( Description: topDescription, Flags: topFlags, Action: topCmd, - ArgsUsage: "CONTAINER-NAME", + ArgsUsage: "CONTAINER-NAME [format descriptors]", SkipArgReorder: true, } ) @@ -37,6 +42,15 @@ func topCmd(c *cli.Context) error { var err error 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") { 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) + var descriptors []string if c.Bool("latest") { + descriptors = args container, err = runtime.GetLatestContainer() } else { + descriptors = args[1:] 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") } - psOutput, err := container.GetContainerPidInformation([]string{}) + psOutput, err := container.GetContainerPidInformation(descriptors) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) + w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) for _, proc := range psOutput { fmt.Fprintln(w, proc) } diff --git a/completions/bash/podman b/completions/bash/podman index b01a30aaa..424370904 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1809,6 +1809,14 @@ _podman_tag() { esac } +__podman_top_descriptors() { + podman top --list-descriptors +} + +__podman_complete_top_descriptors() { + COMPREPLY=($(compgen -W "$(__podman_top_descriptors)" -- "$cur")) +} + _podman_top() { local options_with_args=" " @@ -1818,6 +1826,14 @@ _podman_top() { --latest -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 -*) COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) diff --git a/docs/podman-top.1.md b/docs/podman-top.1.md index a9abd8baf..d95d4df7f 100644 --- a/docs/podman-top.1.md +++ b/docs/podman-top.1.md @@ -4,10 +4,10 @@ podman\-top - Display the running processes of a container ## SYNOPSIS -**podman top** [*options*] *container* [*ps-options*] +**podman top** [*options*] *container* [*format-descriptors*] ## 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 @@ -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 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 +By default, `podman-top` prints data similar to `ps -ef`: + ``` # podman top f5a62a71b07 - UID PID PPID %CPU STIME TT TIME CMD - 0 18715 18705 0.0 10:35 pts/0 00:00:00 /bin/bash - 0 18741 18715 0.0 10:35 pts/0 00:00:00 vi -# +USER PID PPID %CPU ELAPSED TTY TIME COMMAND +root 1 0 0.000 20.386825206s pts/0 0s sh +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 - PID FUSER F COMMAND LABEL -18715 root 4 bash system_u:system_r:container_t:s0:c429,c1016 -18741 root 0 vi system_u:system_r:container_t:s0:c429,c1016 -# +# sudo ./bin/podman top -l pid seccomp args %C +PID SECCOMP COMMAND %CPU +1 filter sh 0.000 +8 filter vi /etc/ 0.000 ``` + ## SEE ALSO -podman(1), ps(1) +podman(1), ps(1), seccomp(2), capabilities(7) ## HISTORY December 2017, Originally compiled by Brent Baude<bbaude@redhat.com> + +July 2018, Introduce format descriptors by Valentin Rothberg <vrothberg@suse.com> diff --git a/docs/podman.1.md b/docs/podman.1.md index ea7f93afa..5581e0569 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -117,7 +117,7 @@ Print the version **libpod.conf** (`/etc/containers/libpod.conf`) -libpod.conf is the configuration file for all tools using libpod to manage containers. This file is ignored when running in rootless mode. +libpod.conf is the configuration file for all tools using libpod to manage containers. When Podman runs in rootless mode, then the file `$HOME/.config/containers/libpod.conf` is used. **storage.conf** (`/etc/containers/storage.conf`) @@ -125,6 +125,8 @@ storage.conf is the storage configuration file for all tools using containers/st The storage configuration file specifies all of the available container storage options for tools using shared container storage. +When Podman runs in rootless mode, the file `$HOME/.config/containers/storage.conf` is also loaded. + **mounts.conf** (`/usr/share/containers/mounts.conf` and optionally `/etc/containers/mounts.conf`) The mounts.conf files specify volume mount directories that are automatically mounted inside containers when executing the `podman run` or `podman start` commands. Container processes can then use this content. The volume mount content does not get committed to the final image if you do a `podman commit`. @@ -137,6 +139,8 @@ The format of the mounts.conf is the volume format /SRC:/DEST, one mount per lin Note this is not a volume mount. The content of the volumes is copied into container storage, not bind mounted directly from the host. +When Podman runs in rootless mode, the file `$HOME/.config/containers/mounts.conf` is also used. + **hook JSON** (`/usr/share/containers/oci/hooks.d/*.json`) Each `*.json` file in `/usr/share/containers/oci/hooks.d` configures a hook for Podman containers. For more details on the syntax of the JSON files and the semantics of hook injection, see `oci-hooks(5)`. @@ -153,6 +157,8 @@ Hooks are not used when running in rootless mode. registries.conf is the configuration file which specifies which container registries should be consulted when completing image names which do not include a registry or domain portion. +When Podman runs in rootless mode, the file `$HOME/.config/containers/registries.conf` is used. + ## Rootless mode Podman can also be used as non-root user. When podman runs in rootless mode, an user namespace is automatically created. diff --git a/libpod/container_top.go b/libpod/container_top.go deleted file mode 100644 index f5d314d49..000000000 --- a/libpod/container_top.go +++ /dev/null @@ -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) -} diff --git a/libpod/container_top_linux.go b/libpod/container_top_linux.go new file mode 100644 index 000000000..450130add --- /dev/null +++ b/libpod/container_top_linux.go @@ -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 +} diff --git a/libpod/container_top_unsupported.go b/libpod/container_top_unsupported.go new file mode 100644 index 000000000..1e6fb836d --- /dev/null +++ b/libpod/container_top_unsupported.go @@ -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 +} diff --git a/libpod/image/pull.go b/libpod/image/pull.go index a5a398eb1..f12c1ae5f 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io" - "os" "strings" cp "github.com/containers/image/copy" @@ -277,12 +276,7 @@ func (i *Image) createNamesToPull() ([]*pullStruct, error) { pullNames = append(pullNames, &ps) } else { - registryConfigPath := "" - envOverride := os.Getenv("REGISTRIES_CONFIG_PATH") - if len(envOverride) > 0 { - registryConfigPath = envOverride - } - searchRegistries, err := sysregistries.GetRegistries(&types.SystemContext{SystemRegistriesConfPath: registryConfigPath}) + searchRegistries, err := registries.GetRegistries() if err != nil { return nil, err } diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 844d2c415..c84bb21f6 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -2,15 +2,27 @@ package registries import ( "os" + "path/filepath" "github.com/containers/image/pkg/sysregistries" "github.com/containers/image/types" "github.com/pkg/errors" + "github.com/projectatomic/libpod/pkg/rootless" ) +// userRegistriesFile is the path to the per user registry configuration file. +var userRegistriesFile = filepath.Join(os.Getenv("HOME"), ".config/containers/registries.conf") + // GetRegistries obtains the list of registries defined in the global registries file. func GetRegistries() ([]string, error) { registryConfigPath := "" + + if rootless.IsRootless() { + if _, err := os.Stat(userRegistriesFile); err == nil { + registryConfigPath = userRegistriesFile + } + } + envOverride := os.Getenv("REGISTRIES_CONFIG_PATH") if len(envOverride) > 0 { registryConfigPath = envOverride @@ -25,6 +37,11 @@ func GetRegistries() ([]string, error) { // GetInsecureRegistries obtains the list of insecure registries from the global registration file. func GetInsecureRegistries() ([]string, error) { registryConfigPath := "" + + if _, err := os.Stat(userRegistriesFile); err == nil { + registryConfigPath = userRegistriesFile + } + envOverride := os.Getenv("REGISTRIES_CONFIG_PATH") if len(envOverride) > 0 { registryConfigPath = envOverride diff --git a/pkg/secrets/secrets.go b/pkg/secrets/secrets.go index ba0f3b925..bc63ece00 100644 --- a/pkg/secrets/secrets.go +++ b/pkg/secrets/secrets.go @@ -10,6 +10,7 @@ import ( rspec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" + "github.com/projectatomic/libpod/pkg/rootless" "github.com/sirupsen/logrus" ) @@ -20,6 +21,9 @@ var ( // OverrideMountsFile holds the default mount paths in the form // "host_path:container_path" overridden by the user OverrideMountsFile = "/etc/containers/mounts.conf" + // UserOverrideMountsFile holds the default mount paths in the form + // "host_path:container_path" overridden by the rootless user + UserOverrideMountsFile = filepath.Join(os.Getenv("HOME"), ".config/containers/mounts.conf") ) // secretData stores the name of the file and the content read from it @@ -143,15 +147,21 @@ func SecretMountsWithUIDGID(mountLabel, containerWorkingDir, mountFile, mountPre // Note for testing purposes only if mountFile == "" { mountFiles = append(mountFiles, []string{OverrideMountsFile, DefaultMountsFile}...) + if rootless.IsRootless() { + mountFiles = append([]string{UserOverrideMountsFile}, mountFiles...) + } } else { mountFiles = append(mountFiles, mountFile) } for _, file := range mountFiles { - mounts, err := addSecretsFromMountsFile(file, mountLabel, containerWorkingDir, mountPrefix, uid, gid) - if err != nil { - logrus.Warnf("error mounting secrets, skipping: %v", err) + if _, err := os.Stat(file); err == nil { + mounts, err := addSecretsFromMountsFile(file, mountLabel, containerWorkingDir, mountPrefix, uid, gid) + if err != nil { + logrus.Warnf("error mounting secrets, skipping: %v", err) + } + secretMounts = mounts + break } - secretMounts = append(secretMounts, mounts...) } // Add FIPS mode secret if /etc/system-fips exists on the host diff --git a/test/e2e/top_test.go b/test/e2e/top_test.go index e7ac90219..d26a19ba4 100644 --- a/test/e2e/top_test.go +++ b/test/e2e/top_test.go @@ -59,32 +59,24 @@ var _ = Describe("Podman top", func() { 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() { - Skip("podman-top with options: options are temporarily ignored") session := podmanTest.Podman([]string{"run", "-d", ALPINE, "top", "-d", "2"}) session.WaitWithDefaultTimeout() 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() Expect(result.ExitCode()).To(Equal(0)) Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1)) }) It("podman top on container invalid options", func() { - Skip("podman-top with invalid options: options are temporarily ignored") top := podmanTest.RunTopContainer("") top.WaitWithDefaultTimeout() Expect(top.ExitCode()).To(Equal(0)) cid := top.OutputToString() - result := podmanTest.Podman([]string{"top", cid, "-o time"}) + result := podmanTest.Podman([]string{"top", cid, "invalid"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(125)) }) diff --git a/vendor.conf b/vendor.conf index 3b42027ae..e36d2f082 100644 --- a/vendor.conf +++ b/vendor.conf @@ -12,6 +12,7 @@ github.com/containernetworking/cni v0.7.0-alpha1 github.com/containernetworking/plugins 1fb94a4222eafc6f948eacdca9c9f2158b427e53 github.com/containers/image c6e0eee0f8eb38e78ae2e44a9aeea0576f451617 github.com/containers/storage 8b1a0f8d6863cf05709af333b8997a437652ec4c +github.com/containers/psgo 59a9dad536216e91da1861c9fbba75b85da84dcd github.com/coreos/go-systemd v14 github.com/cri-o/ocicni master github.com/cyphar/filepath-securejoin v0.2.1 diff --git a/vendor/github.com/containers/psgo/LICENSE b/vendor/github.com/containers/psgo/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/containers/psgo/LICENSE @@ -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. diff --git a/vendor/github.com/containers/psgo/README.md b/vendor/github.com/containers/psgo/README.md new file mode 100644 index 000000000..f97008c02 --- /dev/null +++ b/vendor/github.com/containers/psgo/README.md @@ -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 +``` diff --git a/vendor/github.com/containers/psgo/ps/capabilities.go b/vendor/github.com/containers/psgo/ps/capabilities.go new file mode 100644 index 000000000..4301792a5 --- /dev/null +++ b/vendor/github.com/containers/psgo/ps/capabilities.go @@ -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 +} diff --git a/vendor/github.com/containers/psgo/ps/cmdline.go b/vendor/github.com/containers/psgo/ps/cmdline.go new file mode 100644 index 000000000..d9991a9a3 --- /dev/null +++ b/vendor/github.com/containers/psgo/ps/cmdline.go @@ -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 +} diff --git a/vendor/github.com/containers/psgo/ps/ps.go b/vendor/github.com/containers/psgo/ps/ps.go new file mode 100644 index 000000000..a7e7cafad --- /dev/null +++ b/vendor/github.com/containers/psgo/ps/ps.go @@ -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 +} diff --git a/vendor/github.com/containers/psgo/ps/stat.go b/vendor/github.com/containers/psgo/ps/stat.go new file mode 100644 index 000000000..0488d4616 --- /dev/null +++ b/vendor/github.com/containers/psgo/ps/stat.go @@ -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 +} diff --git a/vendor/github.com/containers/psgo/ps/status.go b/vendor/github.com/containers/psgo/ps/status.go new file mode 100644 index 000000000..7f496bc48 --- /dev/null +++ b/vendor/github.com/containers/psgo/ps/status.go @@ -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 +} diff --git a/vendor/github.com/containers/psgo/ps/tty.go b/vendor/github.com/containers/psgo/ps/tty.go new file mode 100644 index 000000000..7c5f3a53b --- /dev/null +++ b/vendor/github.com/containers/psgo/ps/tty.go @@ -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 +} diff --git a/vendor/github.com/containers/psgo/vendor.conf b/vendor/github.com/containers/psgo/vendor.conf new file mode 100644 index 000000000..ebdb065ef --- /dev/null +++ b/vendor/github.com/containers/psgo/vendor.conf @@ -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 |