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
This commit is contained in:
Ayush Ranjan
2022-05-24 15:59:52 -07:00
committed by gVisor bot
parent 38a3c98a0d
commit b274736ac7
2 changed files with 66 additions and 0 deletions
+52
View File
@@ -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
}
+14
View File
@@ -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)
}
}
}
}