@@ -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:\t 0" ) {
596+ t .Errorf ("pos should be 0 for freshly opened file, got: %q" , lines [0 ])
597+ }
598+ }
0 commit comments