mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement TTY field in control.Processes().
Threadgroups already know their TTY (if they have one), which now contains the TTY Index, and is returned in the Processes() call. PiperOrigin-RevId: 284263850
This commit is contained in:
committed by
gVisor bot
parent
ea7a100202
commit
663fe840f7
@@ -268,7 +268,6 @@ func (proc *Proc) Ps(args *PsArgs, out *string) error {
|
||||
}
|
||||
|
||||
// Process contains information about a single process in a Sandbox.
|
||||
// TODO(b/117881927): Implement TTY field.
|
||||
type Process struct {
|
||||
UID auth.KUID `json:"uid"`
|
||||
PID kernel.ThreadID `json:"pid"`
|
||||
@@ -276,6 +275,9 @@ type Process struct {
|
||||
PPID kernel.ThreadID `json:"ppid"`
|
||||
// Processor utilization
|
||||
C int32 `json:"c"`
|
||||
// TTY name of the process. Will be of the form "pts/N" if there is a
|
||||
// TTY, or "?" if there is not.
|
||||
TTY string `json:"tty"`
|
||||
// Start time
|
||||
STime string `json:"stime"`
|
||||
// CPU time
|
||||
@@ -285,18 +287,19 @@ type Process struct {
|
||||
}
|
||||
|
||||
// ProcessListToTable prints a table with the following format:
|
||||
// UID PID PPID C STIME TIME CMD
|
||||
// 0 1 0 0 14:04 505262ns tail
|
||||
// UID PID PPID C TTY STIME TIME CMD
|
||||
// 0 1 0 0 pty/4 14:04 505262ns tail
|
||||
func ProcessListToTable(pl []*Process) string {
|
||||
var buf bytes.Buffer
|
||||
tw := tabwriter.NewWriter(&buf, 10, 1, 3, ' ', 0)
|
||||
fmt.Fprint(tw, "UID\tPID\tPPID\tC\tSTIME\tTIME\tCMD")
|
||||
fmt.Fprint(tw, "UID\tPID\tPPID\tC\tTTY\tSTIME\tTIME\tCMD")
|
||||
for _, d := range pl {
|
||||
fmt.Fprintf(tw, "\n%d\t%d\t%d\t%d\t%s\t%s\t%s",
|
||||
fmt.Fprintf(tw, "\n%d\t%d\t%d\t%d\t%s\t%s\t%s\t%s",
|
||||
d.UID,
|
||||
d.PID,
|
||||
d.PPID,
|
||||
d.C,
|
||||
d.TTY,
|
||||
d.STime,
|
||||
d.Time,
|
||||
d.Cmd)
|
||||
@@ -347,7 +350,7 @@ func Processes(k *kernel.Kernel, containerID string, out *[]*Process) error {
|
||||
if p := tg.Leader().Parent(); p != nil {
|
||||
ppid = p.PIDNamespace().IDOfThreadGroup(p.ThreadGroup())
|
||||
}
|
||||
*out = append(*out, &Process{
|
||||
p := Process{
|
||||
UID: tg.Leader().Credentials().EffectiveKUID,
|
||||
PID: pid,
|
||||
PPID: ppid,
|
||||
@@ -355,7 +358,9 @@ func Processes(k *kernel.Kernel, containerID string, out *[]*Process) error {
|
||||
C: percentCPU(tg.CPUStats(), tg.Leader().StartTime(), now),
|
||||
Time: tg.CPUStats().SysTime.String(),
|
||||
Cmd: tg.Leader().Name(),
|
||||
})
|
||||
TTY: ttyName(tg.TTY()),
|
||||
}
|
||||
*out = append(*out, &p)
|
||||
}
|
||||
sort.Slice(*out, func(i, j int) bool { return (*out)[i].PID < (*out)[j].PID })
|
||||
return nil
|
||||
@@ -395,3 +400,10 @@ func percentCPU(stats usage.CPUStats, startTime, now ktime.Time) int32 {
|
||||
}
|
||||
return int32(percentCPU)
|
||||
}
|
||||
|
||||
func ttyName(tty *kernel.TTY) string {
|
||||
if tty == nil {
|
||||
return "?"
|
||||
}
|
||||
return fmt.Sprintf("pts/%d", tty.Index)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestProcessListTable(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
pl: []*Process{},
|
||||
expected: "UID PID PPID C STIME TIME CMD",
|
||||
expected: "UID PID PPID C TTY STIME TIME CMD",
|
||||
},
|
||||
{
|
||||
pl: []*Process{
|
||||
@@ -43,6 +43,7 @@ func TestProcessListTable(t *testing.T) {
|
||||
PID: 0,
|
||||
PPID: 0,
|
||||
C: 0,
|
||||
TTY: "?",
|
||||
STime: "0",
|
||||
Time: "0",
|
||||
Cmd: "zero",
|
||||
@@ -52,14 +53,15 @@ func TestProcessListTable(t *testing.T) {
|
||||
PID: 1,
|
||||
PPID: 1,
|
||||
C: 1,
|
||||
TTY: "pts/4",
|
||||
STime: "1",
|
||||
Time: "1",
|
||||
Cmd: "one",
|
||||
},
|
||||
},
|
||||
expected: `UID PID PPID C STIME TIME CMD
|
||||
0 0 0 0 0 0 zero
|
||||
1 1 1 1 1 1 one`,
|
||||
expected: `UID PID PPID C TTY STIME TIME CMD
|
||||
0 0 0 0 ? 0 0 zero
|
||||
1 1 1 1 pts/4 1 1 one`,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ func newTerminal(ctx context.Context, d *dirInodeOperations, n uint32) *Terminal
|
||||
d: d,
|
||||
n: n,
|
||||
ld: newLineDiscipline(termios),
|
||||
masterKTTY: &kernel.TTY{},
|
||||
slaveKTTY: &kernel.TTY{},
|
||||
masterKTTY: &kernel.TTY{Index: n},
|
||||
slaveKTTY: &kernel.TTY{Index: n},
|
||||
}
|
||||
t.EnableLeakCheck("tty.Terminal")
|
||||
return &t
|
||||
|
||||
@@ -21,8 +21,19 @@ import "sync"
|
||||
//
|
||||
// +stateify savable
|
||||
type TTY struct {
|
||||
// Index is the terminal index. It is immutable.
|
||||
Index uint32
|
||||
|
||||
mu sync.Mutex `state:"nosave"`
|
||||
|
||||
// tg is protected by mu.
|
||||
tg *ThreadGroup
|
||||
}
|
||||
|
||||
// TTY returns the thread group's controlling terminal. If nil, there is no
|
||||
// controlling terminal.
|
||||
func (tg *ThreadGroup) TTY() *TTY {
|
||||
tg.signalHandlers.mu.Lock()
|
||||
defer tg.signalHandlers.mu.Unlock()
|
||||
return tg.tty
|
||||
}
|
||||
|
||||
@@ -98,10 +98,14 @@ func procListsEqual(got, want []*control.Process) bool {
|
||||
for i := range got {
|
||||
pd1 := got[i]
|
||||
pd2 := want[i]
|
||||
// Zero out unimplemented and timing dependant fields.
|
||||
// Zero out timing dependant fields.
|
||||
pd1.Time = ""
|
||||
pd1.STime = ""
|
||||
pd1.C = 0
|
||||
// Ignore TTY field too, since it's not relevant in the cases
|
||||
// where we use this method. Tests that care about the TTY
|
||||
// field should check for it themselves.
|
||||
pd1.TTY = ""
|
||||
if *pd1 != *pd2 {
|
||||
return false
|
||||
}
|
||||
@@ -2112,6 +2116,95 @@ func TestOverlayfsStaleRead(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestTTYField checks TTY field returned by container.Processes().
|
||||
func TestTTYField(t *testing.T) {
|
||||
stop := testutil.StartReaper()
|
||||
defer stop()
|
||||
|
||||
testApp, err := testutil.FindFile("runsc/container/test_app/test_app")
|
||||
if err != nil {
|
||||
t.Fatal("error finding test_app:", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
useTTY bool
|
||||
wantTTYField string
|
||||
}{
|
||||
{
|
||||
name: "no tty",
|
||||
useTTY: false,
|
||||
wantTTYField: "?",
|
||||
},
|
||||
{
|
||||
name: "tty used",
|
||||
useTTY: true,
|
||||
wantTTYField: "pts/0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
conf := testutil.TestConfig()
|
||||
|
||||
// We will run /bin/sleep, possibly with an open TTY.
|
||||
cmd := []string{"/bin/sleep", "10000"}
|
||||
if test.useTTY {
|
||||
// Run inside the "pty-runner".
|
||||
cmd = append([]string{testApp, "pty-runner"}, cmd...)
|
||||
}
|
||||
|
||||
spec := testutil.NewSpecWithArgs(cmd...)
|
||||
rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
|
||||
if err != nil {
|
||||
t.Fatalf("error setting up container: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(rootDir)
|
||||
defer os.RemoveAll(bundleDir)
|
||||
|
||||
// Create and start the container.
|
||||
args := Args{
|
||||
ID: testutil.UniqueContainerID(),
|
||||
Spec: spec,
|
||||
BundleDir: bundleDir,
|
||||
}
|
||||
c, err := New(conf, args)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating container: %v", err)
|
||||
}
|
||||
defer c.Destroy()
|
||||
if err := c.Start(conf); err != nil {
|
||||
t.Fatalf("error starting container: %v", err)
|
||||
}
|
||||
|
||||
// Wait for sleep to be running, and check the TTY
|
||||
// field.
|
||||
var gotTTYField string
|
||||
cb := func() error {
|
||||
ps, err := c.Processes()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error getting process data from container: %v", err)
|
||||
return &backoff.PermanentError{Err: err}
|
||||
}
|
||||
for _, p := range ps {
|
||||
if strings.Contains(p.Cmd, "sleep") {
|
||||
gotTTYField = p.TTY
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("sleep not running")
|
||||
}
|
||||
if err := testutil.Poll(cb, 30*time.Second); err != nil {
|
||||
t.Fatalf("error waiting for sleep process: %v", err)
|
||||
}
|
||||
|
||||
if gotTTYField != test.wantTTYField {
|
||||
t.Errorf("tty field got %q, want %q", gotTTYField, test.wantTTYField)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// executeSync synchronously executes a new process.
|
||||
func (cont *Container) executeSync(args *control.ExecArgs) (syscall.WaitStatus, error) {
|
||||
pid, err := cont.Execute(args)
|
||||
|
||||
@@ -15,5 +15,6 @@ go_binary(
|
||||
"//pkg/unet",
|
||||
"//runsc/testutil",
|
||||
"@com_github_google_subcommands//:go_default_library",
|
||||
"@com_github_kr_pty//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
|
||||
"flag"
|
||||
"github.com/google/subcommands"
|
||||
"github.com/kr/pty"
|
||||
"gvisor.dev/gvisor/runsc/testutil"
|
||||
)
|
||||
|
||||
@@ -41,6 +43,7 @@ func main() {
|
||||
subcommands.Register(new(fdReceiver), "")
|
||||
subcommands.Register(new(fdSender), "")
|
||||
subcommands.Register(new(forkBomb), "")
|
||||
subcommands.Register(new(ptyRunner), "")
|
||||
subcommands.Register(new(reaper), "")
|
||||
subcommands.Register(new(syscall), "")
|
||||
subcommands.Register(new(taskTree), "")
|
||||
@@ -352,3 +355,40 @@ func (c *capability) Execute(ctx context.Context, f *flag.FlagSet, args ...inter
|
||||
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
type ptyRunner struct{}
|
||||
|
||||
// Name implements subcommands.Command.
|
||||
func (*ptyRunner) Name() string {
|
||||
return "pty-runner"
|
||||
}
|
||||
|
||||
// Synopsis implements subcommands.Command.
|
||||
func (*ptyRunner) Synopsis() string {
|
||||
return "runs the given command with an open pty terminal"
|
||||
}
|
||||
|
||||
// Usage implements subcommands.Command.
|
||||
func (*ptyRunner) Usage() string {
|
||||
return "pty-runner [command]"
|
||||
}
|
||||
|
||||
// SetFlags implements subcommands.Command.SetFlags.
|
||||
func (*ptyRunner) SetFlags(f *flag.FlagSet) {}
|
||||
|
||||
// Execute implements subcommands.Command.
|
||||
func (*ptyRunner) Execute(_ context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
c := exec.Command(fs.Args()[0], fs.Args()[1:]...)
|
||||
f, err := pty.Start(c)
|
||||
if err != nil {
|
||||
fmt.Printf("pty.Start failed: %v", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Copy stdout from the command to keep this process alive until the
|
||||
// subprocess exits.
|
||||
io.Copy(os.Stdout, f)
|
||||
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user