File: proc.go

package info (click to toggle)
runc 1.3.3%2Bds1-3
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 3,136 kB
  • sloc: sh: 2,298; ansic: 1,125; makefile: 229
file content (137 lines) | stat: -rw-r--r-- 3,678 bytes parent folder | download | duplicates (2)
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package system

import (
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"

	"github.com/opencontainers/runc/internal/pathrs"
)

// State is the status of a process.
type State rune

const ( // Only values for Linux 3.14 and later are listed here
	Dead        State = 'X'
	DiskSleep   State = 'D'
	Running     State = 'R'
	Sleeping    State = 'S'
	Stopped     State = 'T'
	TracingStop State = 't'
	Zombie      State = 'Z'
	Parked      State = 'P'
	Idle        State = 'I'
)

// String forms of the state from proc(5)'s documentation for
// /proc/[pid]/status' "State" field.
func (s State) String() string {
	switch s {
	case Dead:
		return "dead"
	case DiskSleep:
		return "disk sleep"
	case Running:
		return "running"
	case Sleeping:
		return "sleeping"
	case Stopped:
		return "stopped"
	case TracingStop:
		return "tracing stop"
	case Zombie:
		return "zombie"
	case Parked:
		return "parked"
	case Idle:
		return "idle" // kernel thread
	default:
		return fmt.Sprintf("unknown (%c)", s)
	}
}

// Stat_t represents the information from /proc/[pid]/stat, as
// described in proc(5) with names based on the /proc/[pid]/status
// fields.
type Stat_t struct {
	// Name is the command run by the process.
	Name string

	// State is the state of the process.
	State State

	// StartTime is the number of clock ticks after system boot (since
	// Linux 2.6).
	StartTime uint64
}

// Stat returns a Stat_t instance for the specified process.
func Stat(pid int) (Stat_t, error) {
	var stat Stat_t

	statFile, err := pathrs.ProcPidOpen(pid, "stat", os.O_RDONLY)
	if err != nil {
		return stat, err
	}
	defer statFile.Close()

	bytes, err := io.ReadAll(statFile)
	if err != nil {
		return stat, err
	}
	return parseStat(string(bytes))
}

func parseStat(data string) (stat Stat_t, err error) {
	// Example:
	// 89653 (gunicorn: maste) S 89630 89653 89653 0 -1 4194560 29689 28896 0 3 146 32 76 19 20 0 1 0 2971844 52965376 3920 18446744073709551615 1 1 0 0 0 0 0 16781312 137447943 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0
	// The fields are space-separated, see full description in proc(5).
	//
	// We are only interested in:
	//  * field 2: process name. It is the only field enclosed into
	//    parenthesis, as it can contain spaces (and parenthesis) inside.
	//  * field 3: process state, a single character (%c)
	//  * field 22: process start time, a long unsigned integer (%llu).

	// 1. Look for the first '(' and the last ')' first, what's in between is Name.
	//    We expect at least 20 fields and a space after the last one.

	const minAfterName = 20*2 + 1 // the min field is '0 '.

	first := strings.IndexByte(data, '(')
	if first < 0 || first+minAfterName >= len(data) {
		return stat, fmt.Errorf("invalid stat data (no comm or too short): %q", data)
	}

	last := strings.LastIndexByte(data, ')')
	if last <= first || last+minAfterName >= len(data) {
		return stat, fmt.Errorf("invalid stat data (no comm or too short): %q", data)
	}

	stat.Name = data[first+1 : last]

	// 2. Remove fields 1 and 2 and a space after. State is right after.
	data = data[last+2:]
	stat.State = State(data[0])

	// 3. StartTime is field 22, data is at field 3 now, so we need to skip 19 spaces.
	skipSpaces := 22 - 3
	for first = 0; skipSpaces > 0 && first < len(data); first++ {
		if data[first] == ' ' {
			skipSpaces--
		}
	}
	// Now first points to StartTime; look for space right after.
	i := strings.IndexByte(data[first:], ' ')
	if i < 0 {
		return stat, fmt.Errorf("invalid stat data (too short): %q", data)
	}
	stat.StartTime, err = strconv.ParseUint(data[first:first+i], 10, 64)
	if err != nil {
		return stat, fmt.Errorf("invalid stat data (bad start time): %w", err)
	}

	return stat, nil
}