1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
|
// Copyright 2020 New Relic Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package sysinfo
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"regexp"
"runtime"
)
var (
// ErrDockerNotFound is returned if a Docker ID is not found in
// /proc/self/cgroup
ErrDockerNotFound = errors.New("Docker ID not found")
)
// DockerID attempts to detect Docker.
func DockerID() (string, error) {
if "linux" != runtime.GOOS {
return "", ErrFeatureUnsupported
}
f, err := os.Open("/proc/self/cgroup")
if err != nil {
return "", err
}
defer f.Close()
return parseDockerID(f)
}
var (
// The DockerID must be a 64-character lowercase hex string
// be greedy and match anything 64-characters or longer to spot invalid IDs
dockerIDLength = 64
dockerIDRegexRaw = fmt.Sprintf("[0-9a-f]{%d,}", dockerIDLength)
dockerIDRegex = regexp.MustCompile(dockerIDRegexRaw)
)
func parseDockerID(r io.Reader) (string, error) {
// Each line in the cgroup file consists of three colon delimited fields.
// 1. hierarchy ID - we don't care about this
// 2. subsystems - comma separated list of cgroup subsystem names
// 3. control group - control group to which the process belongs
//
// Example
// 5:cpuacct,cpu,cpuset:/daemons
var id string
for scanner := bufio.NewScanner(r); scanner.Scan(); {
line := scanner.Bytes()
cols := bytes.SplitN(line, []byte(":"), 3)
if len(cols) < 3 {
continue
}
// We're only interested in the cpu subsystem.
if !isCPUCol(cols[1]) {
continue
}
id = dockerIDRegex.FindString(string(cols[2]))
if err := validateDockerID(id); err != nil {
// We can stop searching at this point, the CPU
// subsystem should only occur once, and its cgroup is
// not docker or not a format we accept.
return "", err
}
return id, nil
}
return "", ErrDockerNotFound
}
func isCPUCol(col []byte) bool {
// Sometimes we have multiple subsystems in one line, as in this example
// from:
// https://source.datanerd.us/newrelic/cross_agent_tests/blob/master/docker_container_id/docker-1.1.2-native-driver-systemd.txt
//
// 3:cpuacct,cpu:/system.slice/docker-67f98c9e6188f9c1818672a15dbe46237b6ee7e77f834d40d41c5fb3c2f84a2f.scope
splitCSV := func(r rune) bool { return r == ',' }
subsysCPU := []byte("cpu")
for _, subsys := range bytes.FieldsFunc(col, splitCSV) {
if bytes.Equal(subsysCPU, subsys) {
return true
}
}
return false
}
func isHex(r rune) bool {
return ('0' <= r && r <= '9') || ('a' <= r && r <= 'f')
}
func validateDockerID(id string) error {
if len(id) != 64 {
return fmt.Errorf("%s is not %d characters long", id, dockerIDLength)
}
for _, c := range id {
if !isHex(c) {
return fmt.Errorf("Character: %c is not hex in string %s", c, id)
}
}
return nil
}
|