Skip to content

Commit c2f43cd

Browse files
tanyifenggvisor-bot
authored andcommitted
proc: Add pos and mnt_id fields to /proc/[pid]/fdinfo/[fd]
Currently gVisor only outputs the flags field in fdinfo, which causes the Python library psutil to fail with IndexError when parsing fdinfo, as they expect pos to be the first line. Add the typical base fields pos and mnt_id. - pos: via Seek(ctx, 0, SEEK_CUR), defaults to 0 for non-seekable fds - mnt_id: from file.Mount().ID FUTURE_COPYBARA_INTEGRATE_REVIEW=#12819 from tanyifeng:proc-fdinfo-pos-mntid d238786 PiperOrigin-RevId: 893667695
1 parent 9572729 commit c2f43cd

2 files changed

Lines changed: 94 additions & 3 deletions

File tree

pkg/sentry/fsimpl/proc/task_fds.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,11 +349,16 @@ func (d *fdInfoData) Generate(ctx context.Context, buf *bytes.Buffer) error {
349349
return linuxerr.ENOENT
350350
}
351351
defer d.fs.SafeDecRefFD(ctx, file)
352-
// TODO(b/121266871): Include pos, locks, and other data. For now we only
353-
// have flags.
352+
// Currently we output the typical base fields: pos, flags, mnt_id.
353+
// TODO(b/121266871): Add ino, lock, and type-specific fields.
354354
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
355+
var pos int64
356+
if p, err := file.Seek(ctx, 0, linux.SEEK_CUR); err == nil {
357+
pos = p
358+
}
355359
flags := uint(file.StatusFlags()) | descriptorFlags.ToLinuxFileFlags()
356-
fmt.Fprintf(buf, "flags:\t0%o\n", flags)
360+
mntID := file.Mount().ID
361+
fmt.Fprintf(buf, "pos:\t%d\nflags:\t0%o\nmnt_id:\t%d\n", pos, flags, mntID)
357362
return nil
358363
}
359364

pkg/sentry/fsimpl/proc/tasks_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"math"
2020
"path"
2121
"strconv"
22+
"strings"
2223
"testing"
2324

2425
"gvisor.dev/gvisor/pkg/abi/linux"
@@ -510,3 +511,88 @@ func TestTree(t *testing.T) {
510511
iterateDir(ctx, t, s, fd)
511512
fd.DecRef(ctx)
512513
}
514+
515+
func TestFdInfoContent(t *testing.T) {
516+
s := setup(t)
517+
defer s.Destroy()
518+
519+
k := kernel.KernelFromContext(s.Ctx)
520+
tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
521+
task, err := testutil.CreateTask(s.Ctx, "name", tc, s.MntNs, s.Root, s.Root)
522+
if err != nil {
523+
t.Fatalf("CreateTask(): %v", err)
524+
}
525+
526+
// Create a test file and add it to the task's FD table.
527+
pop := &vfs.PathOperation{
528+
Root: s.Root,
529+
Start: s.Root,
530+
Path: fspath.Parse("test-file"),
531+
}
532+
opts := &vfs.OpenOptions{
533+
Flags: linux.O_RDWR | linux.O_CREAT,
534+
Mode: 0644,
535+
}
536+
file, err := s.VFS.OpenAt(s.Ctx, s.Creds, pop, opts)
537+
if err != nil {
538+
t.Fatalf("failed to create test file: %v", err)
539+
}
540+
defer file.DecRef(s.Ctx)
541+
542+
fdno, err := task.FDTable().NewFD(task.AsyncContext(), 0, file, kernel.FDFlags{})
543+
if err != nil {
544+
t.Fatalf("NewFD(): %v", err)
545+
}
546+
547+
// Read /proc/1/fdinfo/<fd>.
548+
ctx := task.AsyncContext()
549+
fdInfoPath := fmt.Sprintf("/proc/1/fdinfo/%d", fdno)
550+
fdInfoFD, err := s.VFS.OpenAt(
551+
ctx,
552+
auth.CredentialsFromContext(s.Ctx),
553+
&vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse(fdInfoPath)},
554+
&vfs.OpenOptions{},
555+
)
556+
if err != nil {
557+
t.Fatalf("OpenAt(%q) failed: %v", fdInfoPath, err)
558+
}
559+
defer fdInfoFD.DecRef(ctx)
560+
561+
buf := make([]byte, 256)
562+
n, err := fdInfoFD.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{})
563+
if err != nil {
564+
t.Fatalf("Read(%q) failed: %v", fdInfoPath, err)
565+
}
566+
content := string(buf[:n])
567+
568+
// Verify that pos, flags, and mnt_id fields are present.
569+
if !strings.HasPrefix(content, "pos:\t") {
570+
t.Errorf("fdinfo content should start with 'pos:', got: %q", content)
571+
}
572+
if !strings.Contains(content, "flags:\t") {
573+
t.Errorf("fdinfo content should contain 'flags:', got: %q", content)
574+
}
575+
if !strings.Contains(content, "mnt_id:\t") {
576+
t.Errorf("fdinfo content should contain 'mnt_id:', got: %q", content)
577+
}
578+
579+
// Verify the order: pos, flags, mnt_id (matching Linux's seq_show).
580+
lines := strings.Split(strings.TrimSpace(content), "\n")
581+
if len(lines) < 3 {
582+
t.Fatalf("expected at least 3 lines in fdinfo, got %d: %q", len(lines), content)
583+
}
584+
if !strings.HasPrefix(lines[0], "pos:\t") {
585+
t.Errorf("first line should be 'pos:', got: %q", lines[0])
586+
}
587+
if !strings.HasPrefix(lines[1], "flags:\t") {
588+
t.Errorf("second line should be 'flags:', got: %q", lines[1])
589+
}
590+
if !strings.HasPrefix(lines[2], "mnt_id:\t") {
591+
t.Errorf("third line should be 'mnt_id:', got: %q", lines[2])
592+
}
593+
594+
// Verify pos is 0 for a freshly opened file.
595+
if !strings.HasPrefix(lines[0], "pos:\t0") {
596+
t.Errorf("pos should be 0 for freshly opened file, got: %q", lines[0])
597+
}
598+
}

0 commit comments

Comments
 (0)