mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Plumb VFS2 imported fds into virtual filesystem.
- When setting up the virtual filesystem, mount a host.filesystem to contain all files that need to be imported. - Make read/preadv syscalls to the host in cases where preadv2 may not be supported yet (likewise for writing). - Make save/restore functions in kernel/kernel.go return early if vfs2 is enabled. PiperOrigin-RevId: 300922353
This commit is contained in:
@@ -266,6 +266,9 @@ type Statx struct {
|
||||
DevMinor uint32
|
||||
}
|
||||
|
||||
// SizeOfStatx is the size of a Statx struct.
|
||||
var SizeOfStatx = binary.Size(Statx{})
|
||||
|
||||
// FileMode represents a mode_t.
|
||||
type FileMode uint16
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ func newSCMRights(fds []int) control.SCMRights {
|
||||
}
|
||||
|
||||
// Files implements control.SCMRights.Files.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/2017): Port to VFS2.
|
||||
func (c *scmRights) Files(ctx context.Context, max int) (control.RightsFiles, bool) {
|
||||
n := max
|
||||
var trunc bool
|
||||
|
||||
@@ -9,9 +9,11 @@ go_library(
|
||||
"host.go",
|
||||
"util.go",
|
||||
],
|
||||
visibility = ["//pkg/sentry:internal"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/context",
|
||||
"//pkg/fd",
|
||||
"//pkg/log",
|
||||
"//pkg/refs",
|
||||
"//pkg/safemem",
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/fd"
|
||||
"gvisor.dev/gvisor/pkg/safemem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/memmap"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
@@ -64,9 +65,7 @@ func (f *defaultFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts v
|
||||
panic("files that can return EWOULDBLOCK (sockets, pipes, etc.) cannot be memory mapped")
|
||||
}
|
||||
|
||||
f.mu.Lock()
|
||||
n, err := readFromHostFD(ctx, f.inode.hostFD, dst, -1, int(opts.Flags))
|
||||
f.mu.Unlock()
|
||||
if isBlockError(err) {
|
||||
// If we got any data at all, return it as a "completed" partial read
|
||||
// rather than retrying until complete.
|
||||
@@ -86,16 +85,22 @@ func (f *defaultFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts v
|
||||
return n, err
|
||||
}
|
||||
|
||||
func readFromHostFD(ctx context.Context, fd int, dst usermem.IOSequence, offset int64, flags int) (int64, error) {
|
||||
if flags&^(linux.RWF_VALID) != 0 {
|
||||
func readFromHostFD(ctx context.Context, hostFD int, dst usermem.IOSequence, offset int64, flags int) (int64, error) {
|
||||
// TODO(gvisor.dev/issue/1672): Support select preadv2 flags.
|
||||
if flags != 0 {
|
||||
return 0, syserror.EOPNOTSUPP
|
||||
}
|
||||
|
||||
reader := safemem.FromVecReaderFunc{
|
||||
func(srcs [][]byte) (int64, error) {
|
||||
n, err := unix.Preadv2(fd, srcs, offset, flags)
|
||||
return int64(n), err
|
||||
},
|
||||
var reader safemem.Reader
|
||||
if offset == -1 {
|
||||
reader = safemem.FromIOReader{fd.NewReadWriter(hostFD)}
|
||||
} else {
|
||||
reader = safemem.FromVecReaderFunc{
|
||||
func(srcs [][]byte) (int64, error) {
|
||||
n, err := unix.Preadv(hostFD, srcs, offset)
|
||||
return int64(n), err
|
||||
},
|
||||
}
|
||||
}
|
||||
n, err := dst.CopyOutFrom(ctx, reader)
|
||||
return int64(n), err
|
||||
@@ -120,9 +125,7 @@ func (f *defaultFileFD) Write(ctx context.Context, src usermem.IOSequence, opts
|
||||
panic("files that can return EWOULDBLOCK (sockets, pipes, etc.) cannot be memory mapped")
|
||||
}
|
||||
|
||||
f.mu.Lock()
|
||||
n, err := writeToHostFD(ctx, f.inode.hostFD, src, -1, int(opts.Flags))
|
||||
f.mu.Unlock()
|
||||
if isBlockError(err) {
|
||||
err = syserror.ErrWouldBlock
|
||||
}
|
||||
@@ -137,16 +140,22 @@ func (f *defaultFileFD) Write(ctx context.Context, src usermem.IOSequence, opts
|
||||
return n, err
|
||||
}
|
||||
|
||||
func writeToHostFD(ctx context.Context, fd int, src usermem.IOSequence, offset int64, flags int) (int64, error) {
|
||||
if flags&^(linux.RWF_VALID) != 0 {
|
||||
func writeToHostFD(ctx context.Context, hostFD int, src usermem.IOSequence, offset int64, flags int) (int64, error) {
|
||||
// TODO(gvisor.dev/issue/1672): Support select pwritev2 flags.
|
||||
if flags != 0 {
|
||||
return 0, syserror.EOPNOTSUPP
|
||||
}
|
||||
|
||||
writer := safemem.FromVecWriterFunc{
|
||||
func(srcs [][]byte) (int64, error) {
|
||||
n, err := unix.Pwritev2(fd, srcs, offset, flags)
|
||||
return int64(n), err
|
||||
},
|
||||
var writer safemem.Writer
|
||||
if offset == -1 {
|
||||
writer = safemem.FromIOWriter{fd.NewReadWriter(hostFD)}
|
||||
} else {
|
||||
writer = safemem.FromVecWriterFunc{
|
||||
func(srcs [][]byte) (int64, error) {
|
||||
n, err := unix.Pwritev(hostFD, srcs, offset)
|
||||
return int64(n), err
|
||||
},
|
||||
}
|
||||
}
|
||||
n, err := src.CopyInTo(ctx, writer)
|
||||
return int64(n), err
|
||||
|
||||
+114
-10
@@ -38,10 +38,19 @@ type filesystem struct {
|
||||
kernfs.Filesystem
|
||||
}
|
||||
|
||||
// NewMount returns a new disconnected mount in vfsObj that may be passed to ImportFD.
|
||||
func NewMount(vfsObj *vfs.VirtualFilesystem) (*vfs.Mount, error) {
|
||||
fs := &filesystem{}
|
||||
fs.Init(vfsObj)
|
||||
vfsfs := fs.VFSFilesystem()
|
||||
// NewDisconnectedMount will take an additional reference on vfsfs.
|
||||
defer vfsfs.DecRef()
|
||||
return vfsObj.NewDisconnectedMount(vfsfs, nil, &vfs.MountOptions{})
|
||||
}
|
||||
|
||||
// ImportFD sets up and returns a vfs.FileDescription from a donated fd.
|
||||
func ImportFD(mnt *vfs.Mount, hostFD int, ownerUID auth.KUID, ownerGID auth.KGID, isTTY bool) (*vfs.FileDescription, error) {
|
||||
// Must be importing to a mount of host.filesystem.
|
||||
fs, ok := mnt.Filesystem().Impl().(*filesystem)
|
||||
fs, ok := mnt.Filesystem().Impl().(*kernfs.Filesystem)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can't import host FDs into filesystems of type %T", mnt.Filesystem().Impl())
|
||||
}
|
||||
@@ -54,8 +63,7 @@ func ImportFD(mnt *vfs.Mount, hostFD int, ownerUID auth.KUID, ownerGID auth.KGID
|
||||
|
||||
fileMode := linux.FileMode(s.Mode)
|
||||
fileType := fileMode.FileType()
|
||||
// Pipes, character devices, and sockets can return EWOULDBLOCK for
|
||||
// operations that would block.
|
||||
// Pipes, character devices, and sockets.
|
||||
isStream := fileType == syscall.S_IFIFO || fileType == syscall.S_IFCHR || fileType == syscall.S_IFSOCK
|
||||
|
||||
i := &inode{
|
||||
@@ -143,11 +151,109 @@ func (i *inode) Mode() linux.FileMode {
|
||||
|
||||
// Stat implements kernfs.Inode.
|
||||
func (i *inode) Stat(_ *vfs.Filesystem, opts vfs.StatOptions) (linux.Statx, error) {
|
||||
if opts.Mask&linux.STATX__RESERVED != 0 {
|
||||
return linux.Statx{}, syserror.EINVAL
|
||||
}
|
||||
if opts.Sync&linux.AT_STATX_SYNC_TYPE == linux.AT_STATX_SYNC_TYPE {
|
||||
return linux.Statx{}, syserror.EINVAL
|
||||
}
|
||||
|
||||
// Limit our host call only to known flags.
|
||||
mask := opts.Mask & linux.STATX_ALL
|
||||
var s unix.Statx_t
|
||||
if err := unix.Statx(i.hostFD, "", int(unix.AT_EMPTY_PATH|opts.Sync), int(opts.Mask), &s); err != nil {
|
||||
err := unix.Statx(i.hostFD, "", int(unix.AT_EMPTY_PATH|opts.Sync), int(mask), &s)
|
||||
// Fallback to fstat(2), if statx(2) is not supported on the host.
|
||||
//
|
||||
// TODO(b/151263641): Remove fallback.
|
||||
if err == syserror.ENOSYS {
|
||||
return i.fstat(opts)
|
||||
} else if err != nil {
|
||||
return linux.Statx{}, err
|
||||
}
|
||||
ls := unixToLinuxStatx(s)
|
||||
|
||||
ls := linux.Statx{Mask: mask}
|
||||
// Unconditionally fill blksize, attributes, and device numbers, as indicated
|
||||
// by /include/uapi/linux/stat.h.
|
||||
//
|
||||
// RdevMajor/RdevMinor are left as zero, so as not to expose host device
|
||||
// numbers.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1672): Use kernfs-specific, internally defined
|
||||
// device numbers. If we use the device number from the host, it may collide
|
||||
// with another sentry-internal device number. We handle device/inode
|
||||
// numbers without relying on the host to prevent collisions.
|
||||
ls.Blksize = s.Blksize
|
||||
ls.Attributes = s.Attributes
|
||||
ls.AttributesMask = s.Attributes_mask
|
||||
|
||||
if mask|linux.STATX_TYPE != 0 {
|
||||
ls.Mode |= s.Mode & linux.S_IFMT
|
||||
}
|
||||
if mask|linux.STATX_MODE != 0 {
|
||||
ls.Mode |= s.Mode &^ linux.S_IFMT
|
||||
}
|
||||
if mask|linux.STATX_NLINK != 0 {
|
||||
ls.Nlink = s.Nlink
|
||||
}
|
||||
if mask|linux.STATX_ATIME != 0 {
|
||||
ls.Atime = unixToLinuxStatxTimestamp(s.Atime)
|
||||
}
|
||||
if mask|linux.STATX_BTIME != 0 {
|
||||
ls.Btime = unixToLinuxStatxTimestamp(s.Btime)
|
||||
}
|
||||
if mask|linux.STATX_CTIME != 0 {
|
||||
ls.Ctime = unixToLinuxStatxTimestamp(s.Ctime)
|
||||
}
|
||||
if mask|linux.STATX_MTIME != 0 {
|
||||
ls.Mtime = unixToLinuxStatxTimestamp(s.Mtime)
|
||||
}
|
||||
if mask|linux.STATX_SIZE != 0 {
|
||||
ls.Size = s.Size
|
||||
}
|
||||
if mask|linux.STATX_BLOCKS != 0 {
|
||||
ls.Blocks = s.Blocks
|
||||
}
|
||||
|
||||
// Use our own internal inode number and file owner.
|
||||
if mask|linux.STATX_INO != 0 {
|
||||
ls.Ino = i.ino
|
||||
}
|
||||
if mask|linux.STATX_UID != 0 {
|
||||
ls.UID = uint32(i.uid)
|
||||
}
|
||||
if mask|linux.STATX_GID != 0 {
|
||||
ls.GID = uint32(i.gid)
|
||||
}
|
||||
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
// fstat is a best-effort fallback for inode.Stat() if the host does not
|
||||
// support statx(2).
|
||||
//
|
||||
// We ignore the mask and sync flags in opts and simply supply
|
||||
// STATX_BASIC_STATS, as fstat(2) itself does not allow the specification
|
||||
// of a mask or sync flags. fstat(2) does not provide any metadata
|
||||
// equivalent to Statx.Attributes, Statx.AttributesMask, or Statx.Btime, so
|
||||
// those fields remain empty.
|
||||
func (i *inode) fstat(opts vfs.StatOptions) (linux.Statx, error) {
|
||||
var s unix.Stat_t
|
||||
if err := unix.Fstat(i.hostFD, &s); err != nil {
|
||||
return linux.Statx{}, err
|
||||
}
|
||||
|
||||
// Note that rdev numbers are left as 0; do not expose host device numbers.
|
||||
ls := linux.Statx{
|
||||
Mask: linux.STATX_BASIC_STATS,
|
||||
Blksize: uint32(s.Blksize),
|
||||
Nlink: uint32(s.Nlink),
|
||||
Mode: uint16(s.Mode),
|
||||
Size: uint64(s.Size),
|
||||
Blocks: uint64(s.Blocks),
|
||||
Atime: timespecToStatxTimestamp(s.Atim),
|
||||
Ctime: timespecToStatxTimestamp(s.Ctim),
|
||||
Mtime: timespecToStatxTimestamp(s.Mtim),
|
||||
}
|
||||
|
||||
// Use our own internal inode number and file owner.
|
||||
//
|
||||
@@ -159,9 +265,6 @@ func (i *inode) Stat(_ *vfs.Filesystem, opts vfs.StatOptions) (linux.Statx, erro
|
||||
ls.UID = uint32(i.uid)
|
||||
ls.GID = uint32(i.gid)
|
||||
|
||||
// Update file mode from the host.
|
||||
i.mode = linux.FileMode(ls.Mode)
|
||||
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
@@ -217,7 +320,6 @@ func (i *inode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptio
|
||||
}
|
||||
|
||||
func (i *inode) open(d *vfs.Dentry, mnt *vfs.Mount) (*vfs.FileDescription, error) {
|
||||
|
||||
fileType := i.mode.FileType()
|
||||
if fileType == syscall.S_IFSOCK {
|
||||
if i.isTTY {
|
||||
@@ -227,6 +329,8 @@ func (i *inode) open(d *vfs.Dentry, mnt *vfs.Mount) (*vfs.FileDescription, error
|
||||
return nil, errors.New("importing host sockets not supported")
|
||||
}
|
||||
|
||||
// TODO(gvisor.dev/issue/1672): Whitelist specific file types here, so that
|
||||
// we don't allow importing arbitrary file types without proper support.
|
||||
if i.isTTY {
|
||||
// TODO(gvisor.dev/issue/1672): support importing host fd as TTY.
|
||||
return nil, errors.New("importing host fd as TTY not supported")
|
||||
|
||||
@@ -35,34 +35,14 @@ func toTimespec(ts linux.StatxTimestamp, omit bool) unix.Timespec {
|
||||
}
|
||||
}
|
||||
|
||||
func unixToLinuxStatx(s unix.Statx_t) linux.Statx {
|
||||
return linux.Statx{
|
||||
Mask: s.Mask,
|
||||
Blksize: s.Blksize,
|
||||
Attributes: s.Attributes,
|
||||
Nlink: s.Nlink,
|
||||
UID: s.Uid,
|
||||
GID: s.Gid,
|
||||
Mode: s.Mode,
|
||||
Ino: s.Ino,
|
||||
Size: s.Size,
|
||||
Blocks: s.Blocks,
|
||||
AttributesMask: s.Attributes_mask,
|
||||
Atime: unixToLinuxStatxTimestamp(s.Atime),
|
||||
Btime: unixToLinuxStatxTimestamp(s.Btime),
|
||||
Ctime: unixToLinuxStatxTimestamp(s.Ctime),
|
||||
Mtime: unixToLinuxStatxTimestamp(s.Mtime),
|
||||
RdevMajor: s.Rdev_major,
|
||||
RdevMinor: s.Rdev_minor,
|
||||
DevMajor: s.Dev_major,
|
||||
DevMinor: s.Dev_minor,
|
||||
}
|
||||
}
|
||||
|
||||
func unixToLinuxStatxTimestamp(ts unix.StatxTimestamp) linux.StatxTimestamp {
|
||||
return linux.StatxTimestamp{Sec: ts.Sec, Nsec: ts.Nsec}
|
||||
}
|
||||
|
||||
func timespecToStatxTimestamp(ts unix.Timespec) linux.StatxTimestamp {
|
||||
return linux.StatxTimestamp{Sec: int64(ts.Sec), Nsec: uint32(ts.Nsec)}
|
||||
}
|
||||
|
||||
// wouldBlock returns true for file types that can return EWOULDBLOCK
|
||||
// for blocking operations, e.g. pipes, character devices, and sockets.
|
||||
func wouldBlock(fileType uint32) bool {
|
||||
|
||||
+28
-12
@@ -467,6 +467,11 @@ func (k *Kernel) flushMountSourceRefs() error {
|
||||
//
|
||||
// Precondition: Must be called with the kernel paused.
|
||||
func (ts *TaskSet) forEachFDPaused(f func(*fs.File, *vfs.FileDescription) error) (err error) {
|
||||
// TODO(gvisor.dev/issue/1663): Add save support for VFS2.
|
||||
if VFS2Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
ts.mu.RLock()
|
||||
defer ts.mu.RUnlock()
|
||||
for t := range ts.Root.tids {
|
||||
@@ -484,7 +489,7 @@ func (ts *TaskSet) forEachFDPaused(f func(*fs.File, *vfs.FileDescription) error)
|
||||
}
|
||||
|
||||
func (ts *TaskSet) flushWritesToFiles(ctx context.Context) error {
|
||||
// TODO(gvisor.dev/issues/1663): Add save support for VFS2.
|
||||
// TODO(gvisor.dev/issue/1663): Add save support for VFS2.
|
||||
return ts.forEachFDPaused(func(file *fs.File, _ *vfs.FileDescription) error {
|
||||
if flags := file.Flags(); !flags.Write {
|
||||
return nil
|
||||
@@ -533,6 +538,11 @@ func (k *Kernel) invalidateUnsavableMappings(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (ts *TaskSet) unregisterEpollWaiters() {
|
||||
// TODO(gvisor.dev/issue/1663): Add save support for VFS2.
|
||||
if VFS2Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
ts.mu.RLock()
|
||||
defer ts.mu.RUnlock()
|
||||
for t := range ts.Root.tids {
|
||||
@@ -1005,11 +1015,14 @@ func (k *Kernel) pauseTimeLocked() {
|
||||
// This means we'll iterate FDTables shared by multiple tasks repeatedly,
|
||||
// but ktime.Timer.Pause is idempotent so this is harmless.
|
||||
if t.fdTable != nil {
|
||||
t.fdTable.forEach(func(_ int32, file *fs.File, _ *vfs.FileDescription, _ FDFlags) {
|
||||
if tfd, ok := file.FileOperations.(*timerfd.TimerOperations); ok {
|
||||
tfd.PauseTimer()
|
||||
}
|
||||
})
|
||||
// TODO(gvisor.dev/issue/1663): Add save support for VFS2.
|
||||
if !VFS2Enabled {
|
||||
t.fdTable.forEach(func(_ int32, file *fs.File, _ *vfs.FileDescription, _ FDFlags) {
|
||||
if tfd, ok := file.FileOperations.(*timerfd.TimerOperations); ok {
|
||||
tfd.PauseTimer()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
k.timekeeper.PauseUpdates()
|
||||
@@ -1034,12 +1047,15 @@ func (k *Kernel) resumeTimeLocked() {
|
||||
it.ResumeTimer()
|
||||
}
|
||||
}
|
||||
if t.fdTable != nil {
|
||||
t.fdTable.forEach(func(_ int32, file *fs.File, _ *vfs.FileDescription, _ FDFlags) {
|
||||
if tfd, ok := file.FileOperations.(*timerfd.TimerOperations); ok {
|
||||
tfd.ResumeTimer()
|
||||
}
|
||||
})
|
||||
// TODO(gvisor.dev/issue/1663): Add save support for VFS2.
|
||||
if !VFS2Enabled {
|
||||
if t.fdTable != nil {
|
||||
t.fdTable.forEach(func(_ int32, file *fs.File, _ *vfs.FileDescription, _ FDFlags) {
|
||||
if tfd, ok := file.FileOperations.(*timerfd.TimerOperations); ok {
|
||||
tfd.ResumeTimer()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,10 @@ func Statx(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
|
||||
mask := args[3].Uint()
|
||||
statxAddr := args[4].Pointer()
|
||||
|
||||
if mask&linux.STATX__RESERVED > 0 {
|
||||
if mask&linux.STATX__RESERVED != 0 {
|
||||
return 0, nil, syserror.EINVAL
|
||||
}
|
||||
if flags&^(linux.AT_SYMLINK_NOFOLLOW|linux.AT_EMPTY_PATH|linux.AT_STATX_SYNC_TYPE) != 0 {
|
||||
return 0, nil, syserror.EINVAL
|
||||
}
|
||||
if flags&linux.AT_STATX_SYNC_TYPE == linux.AT_STATX_SYNC_TYPE {
|
||||
|
||||
@@ -150,7 +150,11 @@ func Statx(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
|
||||
mask := args[3].Uint()
|
||||
statxAddr := args[4].Pointer()
|
||||
|
||||
if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW) != 0 {
|
||||
if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW|linux.AT_STATX_SYNC_TYPE) != 0 {
|
||||
return 0, nil, syserror.EINVAL
|
||||
}
|
||||
|
||||
if mask&linux.STATX__RESERVED != 0 {
|
||||
return 0, nil, syserror.EINVAL
|
||||
}
|
||||
|
||||
|
||||
@@ -284,6 +284,7 @@ var allowedSyscalls = seccomp.SyscallRules{
|
||||
{seccomp.AllowAny{}, seccomp.AllowValue(syscall.SHUT_RDWR)},
|
||||
},
|
||||
syscall.SYS_SIGALTSTACK: {},
|
||||
unix.SYS_STATX: {},
|
||||
syscall.SYS_SYNC_FILE_RANGE: {},
|
||||
syscall.SYS_TGKILL: []seccomp.Rule{
|
||||
{
|
||||
|
||||
@@ -607,7 +607,7 @@ int statx(int dirfd, const char* pathname, int flags, unsigned int mask,
|
||||
}
|
||||
|
||||
TEST_F(StatTest, StatxAbsPath) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, 0) < 0 &&
|
||||
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
|
||||
errno == ENOSYS);
|
||||
|
||||
struct kernel_statx stx;
|
||||
@@ -617,7 +617,7 @@ TEST_F(StatTest, StatxAbsPath) {
|
||||
}
|
||||
|
||||
TEST_F(StatTest, StatxRelPathDirFD) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, 0) < 0 &&
|
||||
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
|
||||
errno == ENOSYS);
|
||||
|
||||
struct kernel_statx stx;
|
||||
@@ -631,7 +631,7 @@ TEST_F(StatTest, StatxRelPathDirFD) {
|
||||
}
|
||||
|
||||
TEST_F(StatTest, StatxRelPathCwd) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, 0) < 0 &&
|
||||
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
|
||||
errno == ENOSYS);
|
||||
|
||||
ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds());
|
||||
@@ -643,7 +643,7 @@ TEST_F(StatTest, StatxRelPathCwd) {
|
||||
}
|
||||
|
||||
TEST_F(StatTest, StatxEmptyPath) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, 0) < 0 &&
|
||||
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
|
||||
errno == ENOSYS);
|
||||
|
||||
const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY));
|
||||
@@ -653,6 +653,58 @@ TEST_F(StatTest, StatxEmptyPath) {
|
||||
EXPECT_TRUE(S_ISREG(stx.stx_mode));
|
||||
}
|
||||
|
||||
TEST_F(StatTest, StatxDoesNotRejectExtraneousMaskBits) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
|
||||
errno == ENOSYS);
|
||||
|
||||
struct kernel_statx stx;
|
||||
// Set all mask bits except for STATX__RESERVED.
|
||||
uint mask = 0xffffffff & ~0x80000000;
|
||||
EXPECT_THAT(statx(-1, test_file_name_.c_str(), 0, mask, &stx),
|
||||
SyscallSucceeds());
|
||||
EXPECT_TRUE(S_ISREG(stx.stx_mode));
|
||||
}
|
||||
|
||||
TEST_F(StatTest, StatxRejectsReservedMaskBit) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
|
||||
errno == ENOSYS);
|
||||
|
||||
struct kernel_statx stx;
|
||||
// Set STATX__RESERVED in the mask.
|
||||
EXPECT_THAT(statx(-1, test_file_name_.c_str(), 0, 0x80000000, &stx),
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
TEST_F(StatTest, StatxSymlink) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
|
||||
errno == ENOSYS);
|
||||
|
||||
std::string parent_dir = "/tmp";
|
||||
TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
TempPath::CreateSymlinkTo(parent_dir, test_file_name_));
|
||||
std::string p = link.path();
|
||||
|
||||
struct kernel_statx stx;
|
||||
EXPECT_THAT(statx(AT_FDCWD, p.c_str(), AT_SYMLINK_NOFOLLOW, STATX_ALL, &stx),
|
||||
SyscallSucceeds());
|
||||
EXPECT_TRUE(S_ISLNK(stx.stx_mode));
|
||||
EXPECT_THAT(statx(AT_FDCWD, p.c_str(), 0, STATX_ALL, &stx),
|
||||
SyscallSucceeds());
|
||||
EXPECT_TRUE(S_ISREG(stx.stx_mode));
|
||||
}
|
||||
|
||||
TEST_F(StatTest, StatxInvalidFlags) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
|
||||
errno == ENOSYS);
|
||||
|
||||
struct kernel_statx stx;
|
||||
EXPECT_THAT(statx(AT_FDCWD, test_file_name_.c_str(), 12345, 0, &stx),
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
EXPECT_THAT(statx(AT_FDCWD, test_file_name_.c_str(),
|
||||
0x6000 /* AT_STATX_SYNC_TYPE */, 0, &stx),
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
|
||||
Reference in New Issue
Block a user