// Copyright 2020 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tmpfs import ( "fmt" "testing" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/sentry/contexttest" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/vfs" ) func TestStatAfterCreate(t *testing.T) { ctx := contexttest.Context(t) mode := linux.FileMode(0644) // Run with different file types. for _, typ := range []string{"file", "dir", "pipe"} { t.Run(fmt.Sprintf("type=%q", typ), func(t *testing.T) { var ( fd *vfs.FileDescription cleanup func() err error ) switch typ { case "file": fd, cleanup, err = newFileFD(ctx, mode) case "dir": fd, cleanup, err = newDirFD(ctx, mode) case "pipe": fd, cleanup, err = newPipeFD(ctx, mode) default: panic(fmt.Sprintf("unknown typ %q", typ)) } if err != nil { t.Fatal(err) } defer cleanup() got, err := fd.Stat(ctx, vfs.StatOptions{}) if err != nil { t.Fatalf("Stat failed: %v", err) } // Atime, Ctime, Mtime should all be current time (non-zero). atime, ctime, mtime := got.Atime.ToNsec(), got.Ctime.ToNsec(), got.Mtime.ToNsec() if atime != ctime || ctime != mtime { t.Errorf("got atime=%d ctime=%d mtime=%d, wanted equal values", atime, ctime, mtime) } if atime == 0 { t.Errorf("got atime=%d, want non-zero", atime) } // Btime should be 0, as it is not set by tmpfs. if btime := got.Btime.ToNsec(); btime != 0 { t.Errorf("got btime %d, want 0", got.Btime.ToNsec()) } // Size should be 0 (except for directories, which make up a size // of 20 per entry, including the "." and ".." entries present in // otherwise-empty directories). wantSize := uint64(0) if typ == "dir" { wantSize = 40 } if got.Size != wantSize { t.Errorf("got size %d, want %d", got.Size, wantSize) } // Nlink should be 1 for files, 2 for dirs. wantNlink := uint32(1) if typ == "dir" { wantNlink = 2 } if got.Nlink != wantNlink { t.Errorf("got nlink %d, want %d", got.Nlink, wantNlink) } // UID and GID are set from context creds. creds := auth.CredentialsFromContext(ctx) if got.UID != uint32(creds.EffectiveKUID) { t.Errorf("got uid %d, want %d", got.UID, uint32(creds.EffectiveKUID)) } if got.GID != uint32(creds.EffectiveKGID) { t.Errorf("got gid %d, want %d", got.GID, uint32(creds.EffectiveKGID)) } // Mode. wantMode := uint16(mode) switch typ { case "file": wantMode |= linux.S_IFREG case "dir": wantMode |= linux.S_IFDIR case "pipe": wantMode |= linux.S_IFIFO default: panic(fmt.Sprintf("unknown typ %q", typ)) } if got.Mode != wantMode { t.Errorf("got mode %x, want %x", got.Mode, wantMode) } // Ino. if got.Ino == 0 { t.Errorf("got ino %d, want not 0", got.Ino) } }) } } func TestSetStatAtime(t *testing.T) { ctx := contexttest.Context(t) fd, cleanup, err := newFileFD(ctx, 0644) if err != nil { t.Fatal(err) } defer cleanup() allStatOptions := vfs.StatOptions{Mask: linux.STATX_ALL} // Get initial stat. initialStat, err := fd.Stat(ctx, allStatOptions) if err != nil { t.Fatalf("Stat failed: %v", err) } // Set atime, but without the mask. if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: linux.Statx{ Mask: 0, Atime: linux.NsecToStatxTimestamp(100), }}); err != nil { t.Errorf("SetStat atime without mask failed: %v", err) } // Atime should be unchanged. if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil { t.Errorf("Stat got error: %v", err) } else if gotStat.Atime != initialStat.Atime { t.Errorf("Stat got atime %d, want %d", gotStat.Atime, initialStat.Atime) } // Set atime, this time included in the mask. setStat := linux.Statx{ Mask: linux.STATX_ATIME, Atime: linux.NsecToStatxTimestamp(100), } if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: setStat}); err != nil { t.Errorf("SetStat atime with mask failed: %v", err) } if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil { t.Errorf("Stat got error: %v", err) } else if gotStat.Atime != setStat.Atime { t.Errorf("Stat got atime %d, want %d", gotStat.Atime, setStat.Atime) } } func TestSetStat(t *testing.T) { ctx := contexttest.Context(t) mode := linux.FileMode(0644) // Run with different file types. for _, typ := range []string{"file", "dir", "pipe"} { t.Run(fmt.Sprintf("type=%q", typ), func(t *testing.T) { var ( fd *vfs.FileDescription cleanup func() err error ) switch typ { case "file": fd, cleanup, err = newFileFD(ctx, mode) case "dir": fd, cleanup, err = newDirFD(ctx, mode) case "pipe": fd, cleanup, err = newPipeFD(ctx, mode) default: panic(fmt.Sprintf("unknown typ %q", typ)) } if err != nil { t.Fatal(err) } defer cleanup() allStatOptions := vfs.StatOptions{Mask: linux.STATX_ALL} // Get initial stat. initialStat, err := fd.Stat(ctx, allStatOptions) if err != nil { t.Fatalf("Stat failed: %v", err) } // Set atime, but without the mask. if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: linux.Statx{ Mask: 0, Atime: linux.NsecToStatxTimestamp(100), }}); err != nil { t.Errorf("SetStat atime without mask failed: %v", err) } // Atime should be unchanged. if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil { t.Errorf("Stat got error: %v", err) } else if gotStat.Atime != initialStat.Atime { t.Errorf("Stat got atime %d, want %d", gotStat.Atime, initialStat.Atime) } // Set atime, this time included in the mask. setStat := linux.Statx{ Mask: linux.STATX_ATIME, Atime: linux.NsecToStatxTimestamp(100), } if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: setStat}); err != nil { t.Errorf("SetStat atime with mask failed: %v", err) } if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil { t.Errorf("Stat got error: %v", err) } else if gotStat.Atime != setStat.Atime { t.Errorf("Stat got atime %d, want %d", gotStat.Atime, setStat.Atime) } }) } }