0a9ec218 |
//+build !windows
|
aa49632f |
package daemon
import ( |
ddae20c0 |
"context" |
a793564b |
"fmt" |
aa49632f |
"os/exec" |
25393322 |
"regexp" |
aa49632f |
"strconv"
"strings"
|
16bdbaaa |
"github.com/docker/docker/api/types/container" |
aa49632f |
)
|
25393322 |
func validatePSArgs(psArgs string) error {
// NOTE: \\s does not detect unicode whitespaces.
// So we use fieldsASCII instead of strings.Fields in parsePSOutput.
// See https://github.com/docker/docker/pull/24358 |
f7f101d5 |
// nolint: gosimple |
25393322 |
re := regexp.MustCompile("\\s+([^\\s]*)=\\s*(PID[^\\s]*)")
for _, group := range re.FindAllStringSubmatch(psArgs, -1) {
if len(group) >= 3 {
k := group[1]
v := group[2]
if k != "pid" {
return fmt.Errorf("specifying \"%s=%s\" is not allowed", k, v)
}
} |
d25a6537 |
} |
25393322 |
return nil
} |
3e096cb9 |
|
25393322 |
// fieldsASCII is similar to strings.Fields but only allows ASCII whitespaces
func fieldsASCII(s string) []string {
fn := func(r rune) bool {
switch r {
case '\t', '\n', '\f', '\r', ' ':
return true
}
return false |
d25a6537 |
} |
25393322 |
return strings.FieldsFunc(s, fn)
} |
aa49632f |
|
a7497c39 |
func appendProcess2ProcList(procList *container.ContainerTopOKBody, fields []string) {
// Make sure number of fields equals number of header titles
// merging "overhanging" fields
process := fields[:len(procList.Titles)-1]
process = append(process, strings.Join(fields[len(procList.Titles)-1:], " "))
procList.Processes = append(procList.Processes, process)
}
|
ddae20c0 |
func hasPid(procs []uint32, pid int) bool {
for _, p := range procs {
if int(p) == pid { |
a7497c39 |
return true
}
}
return false
}
|
ddae20c0 |
func parsePSOutput(output []byte, procs []uint32) (*container.ContainerTopOKBody, error) { |
16bdbaaa |
procList := &container.ContainerTopOKBody{} |
3e096cb9 |
|
d25a6537 |
lines := strings.Split(string(output), "\n") |
25393322 |
procList.Titles = fieldsASCII(lines[0]) |
aa49632f |
|
d25a6537 |
pidIndex := -1 |
3e096cb9 |
for i, name := range procList.Titles { |
d25a6537 |
if name == "PID" {
pidIndex = i |
aa49632f |
} |
d25a6537 |
}
if pidIndex == -1 { |
a793564b |
return nil, fmt.Errorf("Couldn't find PID field in ps output") |
d25a6537 |
} |
aa49632f |
|
abd72d40 |
// loop through the output and extract the PID from each line |
a7497c39 |
// fixing #30580, be able to display thread line also when "m" option used
// in "docker top" client command
preContainedPidFlag := false |
d25a6537 |
for _, line := range lines[1:] {
if len(line) == 0 {
continue
} |
25393322 |
fields := fieldsASCII(line) |
a7497c39 |
var (
p int
err error
)
if fields[pidIndex] == "-" {
if preContainedPidFlag {
appendProcess2ProcList(procList, fields)
}
continue
}
p, err = strconv.Atoi(fields[pidIndex]) |
d25a6537 |
if err != nil { |
a793564b |
return nil, fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) |
d25a6537 |
} |
aa49632f |
|
ddae20c0 |
if hasPid(procs, p) { |
a7497c39 |
preContainedPidFlag = true
appendProcess2ProcList(procList, fields)
continue |
aa49632f |
} |
a7497c39 |
preContainedPidFlag = false |
aa49632f |
} |
25393322 |
return procList, nil
}
// ContainerTop lists the processes running inside of the given
// container by calling ps with the given args, or with the flags
// "-ef" if no args are given. An error is returned if the container
// is not found, or is not running, or if there are any problems
// running ps, or parsing the output. |
16bdbaaa |
func (daemon *Daemon) ContainerTop(name string, psArgs string) (*container.ContainerTopOKBody, error) { |
25393322 |
if psArgs == "" {
psArgs = "-ef"
}
if err := validatePSArgs(psArgs); err != nil {
return nil, err
}
container, err := daemon.GetContainer(name)
if err != nil {
return nil, err
}
if !container.IsRunning() { |
ebcb7d6b |
return nil, errNotRunning(container.ID) |
25393322 |
}
if container.IsRestarting() {
return nil, errContainerIsRestarting(container.ID)
}
|
ddae20c0 |
procs, err := daemon.containerd.ListPids(context.Background(), container.ID) |
25393322 |
if err != nil {
return nil, err
}
output, err := exec.Command("ps", strings.Split(psArgs, " ")...).Output()
if err != nil {
return nil, fmt.Errorf("Error running ps: %v", err)
} |
ddae20c0 |
procList, err := parsePSOutput(output, procs) |
25393322 |
if err != nil {
return nil, err
} |
ca5ede2d |
daemon.LogContainerEvent(container, "top") |
3e096cb9 |
return procList, nil |
aa49632f |
} |