mirror of
				https://github.com/containers/podman.git
				synced 2025-10-31 18:08:51 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			907 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			907 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2018 psgo authors
 | |
| //
 | |
| // 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.
 | |
| 
 | |
| // Package psgo is a ps (1) AIX-format compatible golang library extended with
 | |
| // various descriptors useful for displaying container-related data.
 | |
| //
 | |
| // The idea behind the library is to provide an easy to use way of extracting
 | |
| // process-related data, just as ps (1) does. The problem when using ps (1) is
 | |
| // that the ps format strings split columns with whitespaces, making the output
 | |
| // nearly impossible to parse. It also adds some jitter as we have to fork and
 | |
| // execute ps either in the container or filter the output afterwards, further
 | |
| // limiting applicability.
 | |
| //
 | |
| // Please visit https://github.com/containers/psgo for further details about
 | |
| // supported format descriptors and to see some usage examples.
 | |
| package psgo
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"runtime"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 
 | |
| 	"github.com/containers/psgo/internal/capabilities"
 | |
| 	"github.com/containers/psgo/internal/dev"
 | |
| 	"github.com/containers/psgo/internal/proc"
 | |
| 	"github.com/containers/psgo/internal/process"
 | |
| 	"github.com/containers/storage/pkg/idtools"
 | |
| 	"golang.org/x/sys/unix"
 | |
| )
 | |
| 
 | |
| // JoinNamespaceOpts specifies different options for joining the specified namespaces.
 | |
| type JoinNamespaceOpts struct {
 | |
| 	// UIDMap specifies a mapping for UIDs in the container.  If specified
 | |
| 	// huser will perform the reverse mapping.
 | |
| 	UIDMap []idtools.IDMap
 | |
| 	// GIDMap specifies a mapping for GIDs in the container.  If specified
 | |
| 	// hgroup will perform the reverse mapping.
 | |
| 	GIDMap []idtools.IDMap
 | |
| 
 | |
| 	// FillMappings specified whether UIDMap and GIDMap must be initialized
 | |
| 	// with the current user namespace.
 | |
| 	FillMappings bool
 | |
| }
 | |
| 
 | |
| type psContext struct {
 | |
| 	// Processes in the container.
 | |
| 	containersProcesses []*process.Process
 | |
| 	// Processes on the host.  Used to map those to the ones running in the container.
 | |
| 	hostProcesses []*process.Process
 | |
| 	// tty and pty devices.
 | |
| 	ttys *[]dev.TTY
 | |
| 	// Various options
 | |
| 	opts *JoinNamespaceOpts
 | |
| }
 | |
| 
 | |
| // processFunc is used to map a given aixFormatDescriptor to a corresponding
 | |
| // function extracting the desired data from a process.
 | |
| type processFunc func(*process.Process, *psContext) (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 descriptor in the short form (e.g., "%C").
 | |
| 	code string
 | |
| 	// normal descriptor in the long form (e.g., "pcpu").
 | |
| 	normal string
 | |
| 	// header of the descriptor (e.g., "%CPU").
 | |
| 	header string
 | |
| 	// onHost controls if data of the corresponding host processes will be
 | |
| 	// extracted as well.
 | |
| 	onHost bool
 | |
| 	// procFN points to the corresponding method to extract the desired data.
 | |
| 	procFn processFunc
 | |
| }
 | |
| 
 | |
| // findID converts the specified id to the host mapping
 | |
| func findID(idStr string, mapping []idtools.IDMap, lookupFunc func(uid string) (string, error), overflowFile string) (string, error) {
 | |
| 	if len(mapping) == 0 {
 | |
| 		return idStr, nil
 | |
| 	}
 | |
| 
 | |
| 	id, err := strconv.ParseInt(idStr, 10, 0)
 | |
| 	if err != nil {
 | |
| 		return "", fmt.Errorf("cannot parse ID: %w", err)
 | |
| 	}
 | |
| 	for _, m := range mapping {
 | |
| 		if int(id) >= m.ContainerID && int(id) < m.ContainerID+m.Size {
 | |
| 			user := fmt.Sprintf("%d", m.HostID+(int(id)-m.ContainerID))
 | |
| 
 | |
| 			return lookupFunc(user)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// User not found, read the overflow
 | |
| 	overflow, err := ioutil.ReadFile(overflowFile)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	return string(overflow), nil
 | |
| }
 | |
| 
 | |
| // translateDescriptors parses the descriptors and returns a correspodning slice of
 | |
| // aixFormatDescriptors.  Descriptors can be specified in the normal and in the
 | |
| // code form (if supported).  If the descriptors slice is empty, the
 | |
| // `DefaultDescriptors` is used.
 | |
| func translateDescriptors(descriptors []string) ([]aixFormatDescriptor, error) {
 | |
| 	if len(descriptors) == 0 {
 | |
| 		descriptors = DefaultDescriptors
 | |
| 	}
 | |
| 
 | |
| 	formatDescriptors := []aixFormatDescriptor{}
 | |
| 	for _, d := range descriptors {
 | |
| 		d = strings.TrimSpace(d)
 | |
| 		found := false
 | |
| 		for _, aix := range aixFormatDescriptors {
 | |
| 			if d == aix.code || d == aix.normal {
 | |
| 				formatDescriptors = append(formatDescriptors, aix)
 | |
| 				found = true
 | |
| 			}
 | |
| 		}
 | |
| 		if !found {
 | |
| 			return nil, fmt.Errorf("'%s': %w", d, ErrUnknownDescriptor)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return formatDescriptors, nil
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	// DefaultDescriptors is the `ps -ef` compatible default format.
 | |
| 	DefaultDescriptors = []string{"user", "pid", "ppid", "pcpu", "etime", "tty", "time", "args"}
 | |
| 
 | |
| 	// ErrUnknownDescriptor is returned when an unknown descriptor is parsed.
 | |
| 	ErrUnknownDescriptor = errors.New("unknown descriptor")
 | |
| 
 | |
| 	aixFormatDescriptors = []aixFormatDescriptor{
 | |
| 		{
 | |
| 			code:   "%C",
 | |
| 			normal: "pcpu",
 | |
| 			header: "%CPU",
 | |
| 			procFn: processPCPU,
 | |
| 		},
 | |
| 		{
 | |
| 			code:   "%G",
 | |
| 			normal: "group",
 | |
| 			header: "GROUP",
 | |
| 			procFn: processGROUP,
 | |
| 		},
 | |
| 		{
 | |
| 			normal: "groups",
 | |
| 			header: "GROUPS",
 | |
| 			procFn: processGROUPS,
 | |
| 		},
 | |
| 		{
 | |
| 			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: "capamb",
 | |
| 			header: "AMBIENT CAPS",
 | |
| 			procFn: processCAPAMB,
 | |
| 		},
 | |
| 		{
 | |
| 			normal: "capinh",
 | |
| 			header: "INHERITED CAPS",
 | |
| 			procFn: processCAPINH,
 | |
| 		},
 | |
| 		{
 | |
| 			normal: "capprm",
 | |
| 			header: "PERMITTED CAPS",
 | |
| 			procFn: processCAPPRM,
 | |
| 		},
 | |
| 		{
 | |
| 			normal: "capeff",
 | |
| 			header: "EFFECTIVE CAPS",
 | |
| 			procFn: processCAPEFF,
 | |
| 		},
 | |
| 		{
 | |
| 			normal: "capbnd",
 | |
| 			header: "BOUNDING CAPS",
 | |
| 			procFn: processCAPBND,
 | |
| 		},
 | |
| 		{
 | |
| 			normal: "seccomp",
 | |
| 			header: "SECCOMP",
 | |
| 			procFn: processSECCOMP,
 | |
| 		},
 | |
| 		{
 | |
| 			normal: "label",
 | |
| 			header: "LABEL",
 | |
| 			procFn: processLABEL,
 | |
| 		},
 | |
| 		{
 | |
| 			normal: "hpid",
 | |
| 			header: "HPID",
 | |
| 			onHost: true,
 | |
| 			procFn: processHPID,
 | |
| 		},
 | |
| 		{
 | |
| 			normal: "huser",
 | |
| 			header: "HUSER",
 | |
| 			onHost: true,
 | |
| 			procFn: processHUSER,
 | |
| 		},
 | |
| 		{
 | |
| 			normal: "hgroup",
 | |
| 			header: "HGROUP",
 | |
| 			onHost: true,
 | |
| 			procFn: processHGROUP,
 | |
| 		},
 | |
| 		{
 | |
| 			normal: "hgroups",
 | |
| 			header: "HGROUPS",
 | |
| 			onHost: true,
 | |
| 			procFn: processHGROUPS,
 | |
| 		},
 | |
| 		{
 | |
| 			normal: "rss",
 | |
| 			header: "RSS",
 | |
| 			procFn: processRSS,
 | |
| 		},
 | |
| 		{
 | |
| 			normal: "state",
 | |
| 			header: "STATE",
 | |
| 			procFn: processState,
 | |
| 		},
 | |
| 		{
 | |
| 			normal: "stime",
 | |
| 			header: "STIME",
 | |
| 			procFn: processStartTime,
 | |
| 		},
 | |
| 	}
 | |
| )
 | |
| 
 | |
| // ListDescriptors returns a string slice of all supported AIX format
 | |
| // descriptors in the normal form.
 | |
| func ListDescriptors() (list []string) {
 | |
| 	for _, d := range aixFormatDescriptors {
 | |
| 		list = append(list, d.normal)
 | |
| 	}
 | |
| 	sort.Strings(list)
 | |
| 	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 string, descriptors []string) ([][]string, error) {
 | |
| 	return JoinNamespaceAndProcessInfoWithOptions(pid, descriptors, &JoinNamespaceOpts{})
 | |
| }
 | |
| 
 | |
| func contextFromOptions(options *JoinNamespaceOpts) (*psContext, error) {
 | |
| 	ctx := new(psContext)
 | |
| 	ctx.opts = options
 | |
| 	if ctx.opts != nil && ctx.opts.FillMappings {
 | |
| 		uidMappings, err := proc.ReadMappings("/proc/self/uid_map")
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		gidMappings, err := proc.ReadMappings("/proc/self/gid_map")
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		ctx.opts.UIDMap = uidMappings
 | |
| 		ctx.opts.GIDMap = gidMappings
 | |
| 
 | |
| 		ctx.opts.FillMappings = false
 | |
| 	}
 | |
| 	return ctx, nil
 | |
| }
 | |
| 
 | |
| // JoinNamespaceAndProcessInfoWithOptions has the same semantics as ProcessInfo but joins
 | |
| // the mount namespace of the specified pid before extracting data from `/proc`.
 | |
| func JoinNamespaceAndProcessInfoWithOptions(pid string, descriptors []string, options *JoinNamespaceOpts) ([][]string, error) {
 | |
| 	var (
 | |
| 		data    [][]string
 | |
| 		dataErr error
 | |
| 		wg      sync.WaitGroup
 | |
| 	)
 | |
| 
 | |
| 	aixDescriptors, err := translateDescriptors(descriptors)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	ctx, err := contextFromOptions(options)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// extract data from host processes only on-demand / when at least one
 | |
| 	// of the specified descriptors requires host data
 | |
| 	for _, d := range aixDescriptors {
 | |
| 		if d.onHost {
 | |
| 			ctx.hostProcesses, err = hostProcesses(pid)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	wg.Add(1)
 | |
| 	go func() {
 | |
| 		defer wg.Done()
 | |
| 		runtime.LockOSThread()
 | |
| 
 | |
| 		// extract user namespaces prior to joining the mount namespace
 | |
| 		currentUserNs, err := proc.ParseUserNamespace("self")
 | |
| 		if err != nil {
 | |
| 			dataErr = fmt.Errorf("error determining user namespace: %w", err)
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		pidUserNs, err := proc.ParseUserNamespace(pid)
 | |
| 		if err != nil {
 | |
| 			dataErr = fmt.Errorf("error determining user namespace of PID %s: %w", pid, err)
 | |
| 		}
 | |
| 
 | |
| 		// join the mount namespace of pid
 | |
| 		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
 | |
| 		}
 | |
| 		if err := unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS); err != nil {
 | |
| 			dataErr = err
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		// extract all pids mentioned in pid's mount namespace
 | |
| 		pids, err := proc.GetPIDs()
 | |
| 		if err != nil {
 | |
| 			dataErr = err
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		// join the user NS if the pid's user NS is different
 | |
| 		// to the caller's user NS.
 | |
| 		joinUserNS := currentUserNs != pidUserNs
 | |
| 
 | |
| 		ctx.containersProcesses, err = process.FromPIDs(pids, joinUserNS)
 | |
| 		if err != nil {
 | |
| 			dataErr = err
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		data, dataErr = processDescriptors(aixDescriptors, ctx)
 | |
| 	}()
 | |
| 	wg.Wait()
 | |
| 
 | |
| 	return data, dataErr
 | |
| }
 | |
| 
 | |
| // JoinNamespaceAndProcessInfoByPidsWithOptions has similar semantics to
 | |
| // JoinNamespaceAndProcessInfo and avoids duplicate entries by joining a giving
 | |
| // PID namespace only once.
 | |
| func JoinNamespaceAndProcessInfoByPidsWithOptions(pids []string, descriptors []string, options *JoinNamespaceOpts) ([][]string, error) {
 | |
| 	// Extracting data from processes that share the same PID namespace
 | |
| 	// would yield duplicate results.  Avoid that by extracting data only
 | |
| 	// from the first process in `pids` from a given PID namespace.
 | |
| 	// `nsMap` is used for quick lookups if a given PID namespace is
 | |
| 	// already covered, `pidList` is used to preserve the order which is
 | |
| 	// not guaranteed by nondeterministic maps in golang.
 | |
| 	nsMap := make(map[string]bool)
 | |
| 	pidList := []string{}
 | |
| 	for _, pid := range pids {
 | |
| 		ns, err := proc.ParsePIDNamespace(pid)
 | |
| 		if err != nil {
 | |
| 			if errors.Is(err, os.ErrNotExist) {
 | |
| 				// catch race conditions
 | |
| 				continue
 | |
| 			}
 | |
| 			return nil, fmt.Errorf("error extracting PID namespace: %w", err)
 | |
| 		}
 | |
| 		if _, exists := nsMap[ns]; !exists {
 | |
| 			nsMap[ns] = true
 | |
| 			pidList = append(pidList, pid)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	data := [][]string{}
 | |
| 	for i, pid := range pidList {
 | |
| 		pidData, err := JoinNamespaceAndProcessInfoWithOptions(pid, descriptors, options)
 | |
| 		if errors.Is(err, os.ErrNotExist) {
 | |
| 			// catch race conditions
 | |
| 			continue
 | |
| 		}
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		if i == 0 {
 | |
| 			data = append(data, pidData[0])
 | |
| 		}
 | |
| 		data = append(data, pidData[1:]...)
 | |
| 	}
 | |
| 
 | |
| 	return data, nil
 | |
| }
 | |
| 
 | |
| // JoinNamespaceAndProcessInfoByPids has similar semantics to
 | |
| // JoinNamespaceAndProcessInfo and avoids duplicate entries by joining a giving
 | |
| // PID namespace only once.
 | |
| func JoinNamespaceAndProcessInfoByPids(pids []string, descriptors []string) ([][]string, error) {
 | |
| 	return JoinNamespaceAndProcessInfoByPidsWithOptions(pids, descriptors, &JoinNamespaceOpts{})
 | |
| }
 | |
| 
 | |
| // 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
 | |
| // `DefaultDescriptors` is used.
 | |
| // The return value is an array of tab-separated strings, to easily use the
 | |
| // output for column-based formatting (e.g., with the `text/tabwriter` package).
 | |
| func ProcessInfo(descriptors []string) ([][]string, error) {
 | |
| 	pids, err := proc.GetPIDs()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return ProcessInfoByPids(pids, descriptors)
 | |
| }
 | |
| 
 | |
| // ProcessInfoByPids is like ProcessInfo, but the process information returned
 | |
| // is limited to a list of user specified PIDs.
 | |
| func ProcessInfoByPids(pids []string, descriptors []string) ([][]string, error) {
 | |
| 	aixDescriptors, err := translateDescriptors(descriptors)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	ctx, err := contextFromOptions(nil)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	ctx.containersProcesses, err = process.FromPIDs(pids, false)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return processDescriptors(aixDescriptors, ctx)
 | |
| }
 | |
| 
 | |
| // hostProcesses returns all processes running in the current namespace.
 | |
| func hostProcesses(pid string) ([]*process.Process, error) {
 | |
| 	// get processes
 | |
| 	pids, err := proc.GetPIDsFromCgroup(pid)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	processes, err := process.FromPIDs(pids, false)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// set the additional host data
 | |
| 	for _, p := range processes {
 | |
| 		if err := p.SetHostData(); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return processes, nil
 | |
| }
 | |
| 
 | |
| // processDescriptors calls each `procFn` of all formatDescriptors on each
 | |
| // process and returns an array of tab-separated strings.
 | |
| func processDescriptors(formatDescriptors []aixFormatDescriptor, ctx *psContext) ([][]string, error) {
 | |
| 	data := [][]string{}
 | |
| 	// create header
 | |
| 	header := []string{}
 | |
| 	for _, desc := range formatDescriptors {
 | |
| 		header = append(header, desc.header)
 | |
| 	}
 | |
| 	data = append(data, header)
 | |
| 
 | |
| 	// dispatch all descriptor functions on each process
 | |
| 	for _, proc := range ctx.containersProcesses {
 | |
| 		pData := []string{}
 | |
| 		for _, desc := range formatDescriptors {
 | |
| 			dataStr, err := desc.procFn(proc, ctx)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			pData = append(pData, dataStr)
 | |
| 		}
 | |
| 		data = append(data, pData)
 | |
| 	}
 | |
| 
 | |
| 	return data, nil
 | |
| }
 | |
| 
 | |
| // findHostProcess returns the corresponding process from `hostProcesses` or
 | |
| // nil if non is found.
 | |
| func findHostProcess(p *process.Process, ctx *psContext) *process.Process {
 | |
| 	for _, hp := range ctx.hostProcesses {
 | |
| 		// We expect the host process to be in another namespace, so
 | |
| 		// /proc/$pid/status.NSpid must have at least two entries.
 | |
| 		if len(hp.Status.NSpid) < 2 {
 | |
| 			continue
 | |
| 		}
 | |
| 		// The process' PID must match the one in the NS of the host
 | |
| 		// process and both must share the same pid NS.
 | |
| 		if p.Pid == hp.Status.NSpid[1] && p.PidNS == hp.PidNS {
 | |
| 			return hp
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // processGROUP returns the effective group ID of the process.  This will be
 | |
| // the textual group ID, if it can be obtained, or a decimal representation
 | |
| // otherwise.
 | |
| func processGROUP(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return process.LookupGID(p.Status.Gids[1])
 | |
| }
 | |
| 
 | |
| // processGROUPS returns the supplementary groups of the process separated by
 | |
| // comma. This will be the textual group ID, if it can be obtained, or a
 | |
| // decimal representation otherwise.
 | |
| func processGROUPS(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	var err error
 | |
| 	groups := make([]string, len(p.Status.Groups))
 | |
| 	for i, g := range p.Status.Groups {
 | |
| 		groups[i], err = process.LookupGID(g)
 | |
| 		if err != nil {
 | |
| 			return "", err
 | |
| 		}
 | |
| 	}
 | |
| 	return strings.Join(groups, ","), nil
 | |
| }
 | |
| 
 | |
| // processRGROUP returns the real group ID of the process.  This will be
 | |
| // the textual group ID, if it can be obtained, or a decimal representation
 | |
| // otherwise.
 | |
| func processRGROUP(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return process.LookupGID(p.Status.Gids[0])
 | |
| }
 | |
| 
 | |
| // processPPID returns the parent process ID of process p.
 | |
| func processPPID(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return p.Status.PPid, nil
 | |
| }
 | |
| 
 | |
| // processUSER returns the effective user name of the process.  This will be
 | |
| // the textual user ID, if it can be obtained, or a decimal representation
 | |
| // otherwise.
 | |
| func processUSER(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return process.LookupUID(p.Status.Uids[1])
 | |
| }
 | |
| 
 | |
| // processRUSER returns the effective user name of the process.  This will be
 | |
| // the textual user ID, if it can be obtained, or a decimal representation
 | |
| // otherwise.
 | |
| func processRUSER(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return process.LookupUID(p.Status.Uids[0])
 | |
| }
 | |
| 
 | |
| // processName returns the name of process p in the format "[$name]".
 | |
| func processName(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return fmt.Sprintf("[%s]", p.Status.Name), nil
 | |
| }
 | |
| 
 | |
| // processARGS returns the command of p with all its arguments.
 | |
| func processARGS(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	// ps (1) returns "[$name]" if command/args are empty
 | |
| 	if p.CmdLine[0] == "" {
 | |
| 		return processName(p, ctx)
 | |
| 	}
 | |
| 	return strings.Join(p.CmdLine, " "), nil
 | |
| }
 | |
| 
 | |
| // processCOMM returns the command name (i.e., executable name) of process p.
 | |
| func processCOMM(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return p.Stat.Comm, nil
 | |
| }
 | |
| 
 | |
| // processNICE returns the nice value of process p.
 | |
| func processNICE(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return p.Stat.Nice, nil
 | |
| }
 | |
| 
 | |
| // processPID returns the process ID of process p.
 | |
| func processPID(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return p.Pid, nil
 | |
| }
 | |
| 
 | |
| // processPGID returns the process group ID of process p.
 | |
| func processPGID(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return p.Stat.Pgrp, nil
 | |
| }
 | |
| 
 | |
| // processPCPU returns how many percent of the CPU time process p uses as
 | |
| // a three digit float as string.
 | |
| func processPCPU(p *process.Process, ctx *psContext) (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.Process, ctx *psContext) (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.Process, ctx *psContext) (string, error) {
 | |
| 	cpu, err := p.CPUTime()
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	return fmt.Sprintf("%v", cpu), nil
 | |
| }
 | |
| 
 | |
| // processStartTime returns the start time of process p.
 | |
| func processStartTime(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	sTime, err := p.StartTime()
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	return fmt.Sprintf("%v", sTime), nil
 | |
| }
 | |
| 
 | |
| // processTTY returns the controlling tty (terminal) of process p.
 | |
| func processTTY(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	ttyNr, err := strconv.ParseUint(p.Stat.TtyNr, 10, 64)
 | |
| 	if err != nil {
 | |
| 		return "", nil
 | |
| 	}
 | |
| 
 | |
| 	tty, err := dev.FindTTY(ttyNr, ctx.ttys)
 | |
| 	if err != nil {
 | |
| 		return "", nil
 | |
| 	}
 | |
| 
 | |
| 	ttyS := "?"
 | |
| 	if tty != nil {
 | |
| 		ttyS = strings.TrimPrefix(tty.Path, "/dev/")
 | |
| 	}
 | |
| 	return ttyS, nil
 | |
| }
 | |
| 
 | |
| // processVSZ returns the virtual memory size of process p in KiB (1024-byte
 | |
| // units).
 | |
| func processVSZ(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	vmsize, err := strconv.Atoi(p.Stat.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 capabilities 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 == capabilities.FullCAPs {
 | |
| 		return "full", nil
 | |
| 	}
 | |
| 	caps := capabilities.TranslateMask(mask)
 | |
| 	if len(caps) == 0 {
 | |
| 		return "none", nil
 | |
| 	}
 | |
| 	sort.Strings(caps)
 | |
| 	return strings.Join(caps, ","), nil
 | |
| }
 | |
| 
 | |
| // processCAPAMB returns the set of ambient capabilities associated with
 | |
| // process p.  If all capabilities are set, "full" is returned.  If no
 | |
| // capability is enabled, "none" is returned.
 | |
| func processCAPAMB(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return parseCAP(p.Status.CapAmb)
 | |
| }
 | |
| 
 | |
| // processCAPINH returns the set of inheritable capabilities associated with
 | |
| // process p.  If all capabilities are set, "full" is returned.  If no
 | |
| // capability is enabled, "none" is returned.
 | |
| func processCAPINH(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return parseCAP(p.Status.CapInh)
 | |
| }
 | |
| 
 | |
| // processCAPPRM returns the set of permitted capabilities associated with
 | |
| // process p.  If all capabilities are set, "full" is returned.  If no
 | |
| // capability is enabled, "none" is returned.
 | |
| func processCAPPRM(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return parseCAP(p.Status.CapPrm)
 | |
| }
 | |
| 
 | |
| // processCAPEFF returns the set of effective capabilities associated with
 | |
| // process p.  If all capabilities are set, "full" is returned.  If no
 | |
| // capability is enabled, "none" is returned.
 | |
| func processCAPEFF(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return parseCAP(p.Status.CapEff)
 | |
| }
 | |
| 
 | |
| // processCAPBND returns the set of bounding capabilities associated with
 | |
| // process p.  If all capabilities are set, "full" is returned.  If no
 | |
| // capability is enabled, "none" is returned.
 | |
| func processCAPBND(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return parseCAP(p.Status.CapBnd)
 | |
| }
 | |
| 
 | |
| // processSECCOMP returns the seccomp mode of the process (i.e., disabled,
 | |
| // strict or filter) or "?" if /proc/$pid/status.seccomp has a unknown value.
 | |
| func processSECCOMP(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	switch p.Status.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 or "?" if the system
 | |
| // doesn't support labeling.
 | |
| func processLABEL(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return p.Label, nil
 | |
| }
 | |
| 
 | |
| // processHPID returns the PID of the corresponding host process of the
 | |
| // (container) or "?" if no corresponding process could be found.
 | |
| func processHPID(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	if hp := findHostProcess(p, ctx); hp != nil {
 | |
| 		return hp.Pid, nil
 | |
| 	}
 | |
| 	return "?", nil
 | |
| }
 | |
| 
 | |
| // processHUSER returns the effective user ID of the corresponding host process
 | |
| // of the (container) or "?" if no corresponding process could be found.
 | |
| func processHUSER(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	if hp := findHostProcess(p, ctx); hp != nil {
 | |
| 		if ctx.opts != nil && len(ctx.opts.UIDMap) > 0 {
 | |
| 			return findID(hp.Status.Uids[1], ctx.opts.UIDMap, process.LookupUID, "/proc/sys/fs/overflowuid")
 | |
| 		}
 | |
| 		return hp.Huser, nil
 | |
| 	}
 | |
| 	return "?", nil
 | |
| }
 | |
| 
 | |
| // processHGROUP returns the effective group ID of the corresponding host
 | |
| // process of the (container) or "?" if no corresponding process could be
 | |
| // found.
 | |
| func processHGROUP(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	if hp := findHostProcess(p, ctx); hp != nil {
 | |
| 		if ctx.opts != nil && len(ctx.opts.GIDMap) > 0 {
 | |
| 			return findID(hp.Status.Gids[1], ctx.opts.GIDMap, process.LookupGID, "/proc/sys/fs/overflowgid")
 | |
| 		}
 | |
| 		return hp.Hgroup, nil
 | |
| 	}
 | |
| 	return "?", nil
 | |
| }
 | |
| 
 | |
| // processHGROUPS returns the supplementary groups of the corresponding host
 | |
| // process of the (container) or "?" if no corresponding process could be
 | |
| // found.
 | |
| func processHGROUPS(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	if hp := findHostProcess(p, ctx); hp != nil {
 | |
| 		groups := hp.Status.Groups
 | |
| 		if ctx.opts != nil && len(ctx.opts.GIDMap) > 0 {
 | |
| 			var err error
 | |
| 			for i, g := range groups {
 | |
| 				groups[i], err = findID(g, ctx.opts.GIDMap, process.LookupGID, "/proc/sys/fs/overflowgid")
 | |
| 				if err != nil {
 | |
| 					return "", err
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		return strings.Join(groups, ","), nil
 | |
| 	}
 | |
| 	return "?", nil
 | |
| }
 | |
| 
 | |
| // processRSS returns the resident set size of process p in KiB (1024-byte
 | |
| // units).
 | |
| func processRSS(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	if p.Status.VMRSS == "" {
 | |
| 		// probably a kernel thread
 | |
| 		return "0", nil
 | |
| 	}
 | |
| 	return p.Status.VMRSS, nil
 | |
| }
 | |
| 
 | |
| // processState returns the process state of process p.
 | |
| func processState(p *process.Process, ctx *psContext) (string, error) {
 | |
| 	return p.Status.State, nil
 | |
| }
 | 
