From b274736ac7ee604a5ec8b834c6cad0a621bb0e2e Mon Sep 17 00:00:00 2001 From: Ayush Ranjan Date: Tue, 24 May 2022 15:57:00 -0700 Subject: [PATCH] Add support for /prod/[pid]/fd to trace procfs. On Linux, /proc/[pid]/fd/ entries are symlinks with name as FD number and target as the file path. runsc trace procfs will return the FD numbers and their corresponding file paths for each process. Updates #4805 PiperOrigin-RevId: 450788929 --- runsc/boot/procfs/dump.go | 52 +++++++++++++++++++++++++++++++++++ runsc/container/trace_test.go | 14 ++++++++++ 2 files changed, 66 insertions(+) diff --git a/runsc/boot/procfs/dump.go b/runsc/boot/procfs/dump.go index 79faf8561..784c1d86b 100644 --- a/runsc/boot/procfs/dump.go +++ b/runsc/boot/procfs/dump.go @@ -29,6 +29,14 @@ import ( "gvisor.dev/gvisor/pkg/sentry/vfs" ) +// FDInfo contains information about an application file descriptor. +type FDInfo struct { + // Number is the FD number. + Number int32 `json:"number,omitempty"` + // Path is the path of the file that FD represents. + Path string `json:"path,omitempty"` +} + // ProcessProcfsDump contains the procfs dump for one process. type ProcessProcfsDump struct { // PID is the process ID. @@ -41,6 +49,9 @@ type ProcessProcfsDump struct { Env []string `json:"env,omitempty"` // CWD is the symlink target of /proc/[pid]/cwd. CWD string `json:"cwd,omitempty"` + // FDs contains the directory entries of /proc/[pid]/fd and also contains the + // symlink target for each FD. + FDs []FDInfo `json:"fdlist,omitempty"` } // getMM returns t's MemoryManager. On success, the MemoryManager's users count @@ -102,6 +113,46 @@ func getCWD(ctx context.Context, t *kernel.Task, pid kernel.ThreadID) string { return name } +func getFDs(ctx context.Context, t *kernel.Task, pid kernel.ThreadID) []FDInfo { + type fdInfo struct { + fd *vfs.FileDescription + no int32 + } + var fds []fdInfo + defer func() { + for _, fd := range fds { + fd.fd.DecRef(ctx) + } + }() + + t.WithMuLocked(func(t *kernel.Task) { + if fdTable := t.FDTable(); fdTable != nil { + fdNos := fdTable.GetFDs(ctx) + fds = make([]fdInfo, 0, len(fdNos)) + for _, fd := range fdNos { + file, _ := fdTable.GetVFS2(fd) + if file != nil { + fds = append(fds, fdInfo{fd: file, no: fd}) + } + } + } + }) + + root := vfs.RootFromContext(ctx) + defer root.DecRef(ctx) + + res := make([]FDInfo, 0, len(fds)) + for _, fd := range fds { + path, err := t.Kernel().VFS().PathnameWithDeleted(ctx, root, fd.fd.VirtualDentry()) + if err != nil { + log.Warningf("PathnameWithDeleted failed to find path for fd %d in PID %s: %v", fd.no, pid, err) + path = "" + } + res = append(res, FDInfo{Number: fd.no, Path: path}) + } + return res +} + // Dump returns a procfs dump for process pid. t must be a task in process pid. func Dump(t *kernel.Task, pid kernel.ThreadID) (ProcessProcfsDump, error) { ctx := t.AsyncContext() @@ -118,5 +169,6 @@ func Dump(t *kernel.Task, pid kernel.ThreadID) (ProcessProcfsDump, error) { Args: getMetadataArray(ctx, pid, mm, proc.Cmdline), Env: getMetadataArray(ctx, pid, mm, proc.Environ), CWD: getCWD(ctx, t, pid), + FDs: getFDs(ctx, t, pid), }, nil } diff --git a/runsc/container/trace_test.go b/runsc/container/trace_test.go index 3ad45ef52..34e765cb8 100644 --- a/runsc/container/trace_test.go +++ b/runsc/container/trace_test.go @@ -365,4 +365,18 @@ func TestProcfsDump(t *testing.T) { if spec.Process.Cwd != procfsDump[0].CWD { t.Errorf("expected CWD %q, got %q", spec.Process.Cwd, procfsDump[0].CWD) } + + // Expect 3 host FDs for stdout, stdin and stderr. + if len(procfsDump[0].FDs) != 3 { + t.Errorf("expected 3 FDs for the sleep process, got %+v", procfsDump[0].FDs) + } else { + for i, fd := range procfsDump[0].FDs { + if want := int32(i); fd.Number != want { + t.Errorf("expected FD number %d, got %d", want, fd.Number) + } + if wantSubStr := "host"; !strings.Contains(fd.Path, wantSubStr) { + t.Errorf("expected FD path to contain %q, got %q", wantSubStr, fd.Path) + } + } + } }