Skip to content

Commit 9eead07

Browse files
**runsc ps: Add process group ID (PGID) to output**
Add the process group ID (PGID) field to the Process struct and populate it using the existing kernel ProcessGroup API. The PGID is now displayed in both table and JSON output of `runsc ps`. The JSON output format is changed from a bare PID array to the full Process struct serialization, making fields like PGID, PPID, UID, and command name available to callers. This enables container runtimes to discover process group IDs from outside the sandbox without needing to exec into the container, which is useful for sending signals to entire process groups via `runsc kill` when the container may be under memory pressure.
1 parent 9355540 commit 9eead07

3 files changed

Lines changed: 63 additions & 9 deletions

File tree

pkg/sentry/control/proc.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,9 @@ type Process struct {
354354
UID auth.KUID `json:"uid"`
355355
PID kernel.ThreadID `json:"pid"`
356356
// Parent PID
357-
PPID kernel.ThreadID `json:"ppid"`
357+
PPID kernel.ThreadID `json:"ppid"`
358+
// Process Group ID
359+
PGID kernel.ThreadID `json:"pgid"`
358360
Threads []kernel.ThreadID `json:"threads"`
359361
// Processor utilization
360362
C int32 `json:"c"`
@@ -370,17 +372,18 @@ type Process struct {
370372
}
371373

372374
// ProcessListToTable prints a table with the following format:
373-
// UID PID PPID C TTY STIME TIME CMD
374-
// 0 1 0 0 pty/4 14:04 505262ns tail
375+
// UID PID PPID PGID C TTY STIME TIME CMD
376+
// 0 1 0 1 0 pty/4 14:04 505262ns tail
375377
func ProcessListToTable(pl []*Process) string {
376378
var buf bytes.Buffer
377379
tw := tabwriter.NewWriter(&buf, 10, 1, 3, ' ', 0)
378-
fmt.Fprint(tw, "UID\tPID\tPPID\tC\tTTY\tSTIME\tTIME\tCMD")
380+
fmt.Fprint(tw, "UID\tPID\tPPID\tPGID\tC\tTTY\tSTIME\tTIME\tCMD")
379381
for _, d := range pl {
380-
fmt.Fprintf(tw, "\n%d\t%d\t%d\t%d\t%s\t%s\t%s\t%s",
382+
fmt.Fprintf(tw, "\n%d\t%d\t%d\t%d\t%d\t%s\t%s\t%s\t%s",
381383
d.UID,
382384
d.PID,
383385
d.PPID,
386+
d.PGID,
384387
d.C,
385388
d.TTY,
386389
d.STime,
@@ -435,11 +438,16 @@ func Processes(k *kernel.Kernel, containerID string, out *[]*Process) error {
435438
if p := tg.Leader().Parent(); p != nil {
436439
ppid = pidns.IDOfThreadGroup(p.ThreadGroup())
437440
}
441+
pgid := kernel.ThreadID(0)
442+
if pg := tg.ProcessGroup(); pg != nil {
443+
pgid = kernel.ThreadID(pidns.IDOfProcessGroup(pg))
444+
}
438445
threads := tg.MemberIDs(pidns)
439446
*out = append(*out, &Process{
440447
UID: tg.Leader().Credentials().EffectiveKUID,
441448
PID: pid,
442449
PPID: ppid,
450+
PGID: pgid,
443451
Threads: threads,
444452
STime: formatStartTime(now, tg.Leader().StartTime()),
445453
C: percentCPU(tg.CPUStats(), tg.Leader().StartTime(), now),

pkg/sentry/control/proc_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@ func TestProcessListTable(t *testing.T) {
3434
}{
3535
{
3636
pl: []*Process{},
37-
expected: "UID PID PPID C TTY STIME TIME CMD",
37+
expected: "UID PID PPID PGID C TTY STIME TIME CMD",
3838
},
3939
{
4040
pl: []*Process{
4141
{
4242
UID: 0,
4343
PID: 0,
4444
PPID: 0,
45+
PGID: 0,
4546
C: 0,
4647
TTY: "?",
4748
STime: "0",
@@ -52,16 +53,17 @@ func TestProcessListTable(t *testing.T) {
5253
UID: 1,
5354
PID: 1,
5455
PPID: 1,
56+
PGID: 1,
5557
C: 1,
5658
TTY: "pts/4",
5759
STime: "1",
5860
Time: "1",
5961
Cmd: "one",
6062
},
6163
},
62-
expected: `UID PID PPID C TTY STIME TIME CMD
63-
0 0 0 0 ? 0 0 zero
64-
1 1 1 1 pts/4 1 1 one`,
64+
expected: `UID PID PPID PGID C TTY STIME TIME CMD
65+
0 0 0 0 0 ? 0 0 zero
66+
1 1 1 1 1 pts/4 1 1 one`,
6567
},
6668
}
6769

runsc/container/container_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ func procEqual(got, want *control.Process) bool {
250250
if want.PPID != -1 && want.PPID != got.PPID {
251251
return false
252252
}
253+
if want.PGID != -1 && want.PGID != got.PGID {
254+
return false
255+
}
253256
if len(want.TTY) != 0 && want.TTY != got.TTY {
254257
return false
255258
}
@@ -269,6 +272,7 @@ func newProcessBuilder() *processBuilder {
269272
UID: math.MaxUint32,
270273
PID: -1,
271274
PPID: -1,
275+
PGID: -1,
272276
},
273277
}
274278
}
@@ -288,6 +292,11 @@ func (p *processBuilder) PPID(ppid kernel.ThreadID) *processBuilder {
288292
return p
289293
}
290294

295+
func (p *processBuilder) PGID(pgid kernel.ThreadID) *processBuilder {
296+
p.process.PGID = pgid
297+
return p
298+
}
299+
291300
func (p *processBuilder) UID(uid auth.KUID) *processBuilder {
292301
p.process.UID = uid
293302
return p
@@ -2593,6 +2602,41 @@ func TestTTYField(t *testing.T) {
25932602
}
25942603
}
25952604

2605+
// TestPGIDField checks PGID returned by container.Processes().
2606+
func TestPGIDField(t *testing.T) {
2607+
stop := testutil.StartReaper()
2608+
defer stop()
2609+
2610+
conf := testutil.TestConfig(t)
2611+
spec := testutil.NewSpecWithArgs("/bin/sleep", "10000")
2612+
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
2613+
if err != nil {
2614+
t.Fatalf("error setting up container: %v", err)
2615+
}
2616+
defer cleanup()
2617+
2618+
args := Args{
2619+
ID: testutil.RandomContainerID(),
2620+
Spec: spec,
2621+
BundleDir: bundleDir,
2622+
}
2623+
c, err := New(conf, args)
2624+
if err != nil {
2625+
t.Fatalf("error creating container: %v", err)
2626+
}
2627+
defer c.Destroy()
2628+
if err := c.Start(conf); err != nil {
2629+
t.Fatalf("error starting container: %v", err)
2630+
}
2631+
2632+
expectedPL := []*control.Process{
2633+
newProcessBuilder().PID(1).PPID(0).PGID(1).Cmd("sleep").Process(),
2634+
}
2635+
if err := waitForProcessList(c, expectedPL); err != nil {
2636+
t.Fatalf("error waiting for process list with pgid: %v", err)
2637+
}
2638+
}
2639+
25962640
// Test that container can run even when there are corrupt state files in the
25972641
// root directiry.
25982642
func TestCreateWithCorruptedStateFile(t *testing.T) {

0 commit comments

Comments
 (0)