mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
The goal is to differentiate O_DSYNC and O_SYNC, O_DSYNC behaves same as O_SYNC today in gVisor. assisted-by: agy Fix #1897 PiperOrigin-RevId: 958610974
1121 lines
38 KiB
Go
1121 lines
38 KiB
Go
// Copyright 2019 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 vfs
|
|
|
|
import (
|
|
"io"
|
|
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
"gvisor.dev/gvisor/pkg/atomicbitops"
|
|
"gvisor.dev/gvisor/pkg/context"
|
|
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
|
"gvisor.dev/gvisor/pkg/sentry/arch"
|
|
"gvisor.dev/gvisor/pkg/sentry/fsimpl/lock"
|
|
"gvisor.dev/gvisor/pkg/sentry/fsmetric"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
|
"gvisor.dev/gvisor/pkg/sentry/memmap"
|
|
"gvisor.dev/gvisor/pkg/sync"
|
|
"gvisor.dev/gvisor/pkg/usermem"
|
|
"gvisor.dev/gvisor/pkg/waiter"
|
|
)
|
|
|
|
// A FileDescription represents an open file description, which is the entity
|
|
// referred to by a file descriptor (POSIX.1-2017 3.258 "Open File
|
|
// Description").
|
|
//
|
|
// FileDescriptions are reference-counted. Unless otherwise specified, all
|
|
// FileDescription methods require that a reference is held.
|
|
//
|
|
// FileDescription is analogous to Linux's struct file.
|
|
//
|
|
// +stateify savable
|
|
type FileDescription struct {
|
|
FileDescriptionRefs
|
|
|
|
// creds is the credentials under which the FileDescription was opened. It is immutable.
|
|
creds *auth.Credentials
|
|
|
|
// flagsMu protects `statusFlags` and `asyncHandler` below.
|
|
flagsMu sync.Mutex `state:"nosave"`
|
|
|
|
// statusFlags contains status flags, "initialized by open(2) and possibly
|
|
// modified by fcntl()" - fcntl(2). statusFlags can be read using atomic
|
|
// memory operations when it does not need to be synchronized with an
|
|
// access to asyncHandler.
|
|
statusFlags atomicbitops.Uint32
|
|
|
|
// fmodeFlags contains FMODE_* flags that describe the file's open mode
|
|
// properties, analogous to Linux's struct file::f_mode. fmodeFlags is
|
|
// immutable after initialization and is used on hot paths, so it is
|
|
// accessed without locking.
|
|
fmodeFlags uint32
|
|
|
|
// asyncHandler handles O_ASYNC signal generation. It is set with the
|
|
// F_SETOWN or F_SETOWN_EX fcntls. For asyncHandler to be used, O_ASYNC must
|
|
// also be set by fcntl(2).
|
|
asyncHandler FileAsync
|
|
|
|
// epolls is the set of epollInterests registered for this FileDescription.
|
|
// epolls is protected by epollMu.
|
|
epollMu epollMutex `state:"nosave"`
|
|
epolls map[*epollInterest]struct{}
|
|
|
|
// vd is the filesystem location at which this FileDescription was opened.
|
|
// A reference is held on vd. vd is immutable.
|
|
vd VirtualDentry
|
|
|
|
// opts contains options passed to FileDescription.Init(). opts is
|
|
// immutable.
|
|
opts FileDescriptionOptions
|
|
|
|
usedLockBSD atomicbitops.Uint32
|
|
|
|
// impl is the FileDescriptionImpl associated with this Filesystem. impl is
|
|
// immutable. This should be the last field in FileDescription.
|
|
impl FileDescriptionImpl
|
|
}
|
|
|
|
// SetCreated marks this FileDescription as having been created by the
|
|
// current open operation.
|
|
//
|
|
// Preconditions: Must only be called during FD creation and initialization
|
|
// phase, before the FD is visible to other goroutines.
|
|
func (fd *FileDescription) SetCreated() {
|
|
fd.fmodeFlags |= linux.FMODE_CREATED
|
|
}
|
|
|
|
// IsCreated returns true if the file was newly created by the current open
|
|
// operation.
|
|
func (fd *FileDescription) IsCreated() bool {
|
|
return fd.fmodeFlags&linux.FMODE_CREATED != 0
|
|
}
|
|
|
|
// FileDescriptionOptions contains options to FileDescription.Init().
|
|
//
|
|
// +stateify savable
|
|
type FileDescriptionOptions struct {
|
|
// If AllowDirectIO is true, allow O_DIRECT to be set on the file.
|
|
AllowDirectIO bool
|
|
|
|
// If DenyPRead is true, calls to FileDescription.PRead() return ESPIPE.
|
|
DenyPRead bool
|
|
|
|
// If DenyPWrite is true, calls to FileDescription.PWrite() return
|
|
// ESPIPE.
|
|
DenyPWrite bool
|
|
|
|
// If UseDentryMetadata is true, calls to FileDescription methods that
|
|
// interact with file and filesystem metadata (Stat, SetStat, StatFS,
|
|
// ListXattr, GetXattr, SetXattr, RemoveXattr) are implemented by calling
|
|
// the corresponding FilesystemImpl methods instead of the corresponding
|
|
// FileDescriptionImpl methods.
|
|
//
|
|
// UseDentryMetadata is intended for file descriptions that are implemented
|
|
// outside of individual filesystems, such as pipes, sockets, and device
|
|
// special files. FileDescriptions for which UseDentryMetadata is true may
|
|
// embed DentryMetadataFileDescriptionImpl to obtain appropriate
|
|
// implementations of FileDescriptionImpl methods that should not be
|
|
// called.
|
|
UseDentryMetadata bool
|
|
|
|
// If DenySpliceIn is true, splice into descriptor isn't allowed.
|
|
DenySpliceIn bool
|
|
|
|
// If SpecialFile is true, this file description is for a special file.
|
|
// Currently, the only difference for special files is that they can be
|
|
// opened for writing through read-only mounts.
|
|
SpecialFile bool
|
|
}
|
|
|
|
// FileCreationFlags are the set of flags passed to FileDescription.Init() but
|
|
// omitted from FileDescription.StatusFlags().
|
|
const FileCreationFlags = linux.O_CREAT | linux.O_EXCL | linux.O_NOCTTY | linux.O_TRUNC
|
|
|
|
// Init must be called before first use of fd. If it succeeds, it takes
|
|
// references on mnt and d. flags is the initial file description flags, which
|
|
// is usually the full set of flags passed to open(2).
|
|
func (fd *FileDescription) Init(impl FileDescriptionImpl, flags uint32, creds *auth.Credentials, mnt *Mount, d *Dentry, opts *FileDescriptionOptions) error {
|
|
if MayWriteFileWithOpenFlags(flags) {
|
|
// Skip the mount writability check for special files.
|
|
if !opts.SpecialFile {
|
|
if err := mnt.CheckBeginWrite(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
fd.fmodeFlags |= linux.FMODE_WRITE
|
|
}
|
|
if MayReadFileWithOpenFlags(flags) {
|
|
fd.fmodeFlags |= linux.FMODE_READ
|
|
}
|
|
|
|
fd.InitRefs()
|
|
fd.creds = creds
|
|
|
|
// Remove "file creation flags" to mirror the behavior from file.f_flags in
|
|
// fs/open.c:do_dentry_open.
|
|
fd.statusFlags = atomicbitops.FromUint32(flags &^ FileCreationFlags)
|
|
fd.vd = VirtualDentry{
|
|
mount: mnt,
|
|
dentry: d,
|
|
}
|
|
mnt.IncRef()
|
|
d.IncRef()
|
|
fd.opts = *opts
|
|
fd.impl = impl
|
|
return nil
|
|
}
|
|
|
|
// DecRef decrements fd's reference count.
|
|
func (fd *FileDescription) DecRef(ctx context.Context) {
|
|
fd.FileDescriptionRefs.DecRef(func() {
|
|
// Generate inotify events.
|
|
ev := uint32(linux.IN_CLOSE_NOWRITE)
|
|
if fd.IsWritable() {
|
|
ev = linux.IN_CLOSE_WRITE
|
|
}
|
|
fd.Dentry().InotifyWithParent(ctx, ev, 0, PathEvent)
|
|
|
|
// Unregister fd from all epoll instances.
|
|
fd.epollMu.Lock()
|
|
epolls := fd.epolls
|
|
fd.epolls = nil
|
|
fd.epollMu.Unlock()
|
|
for epi := range epolls {
|
|
ep := epi.epoll
|
|
ep.interestMu.Lock()
|
|
// Check that epi has not been concurrently unregistered by
|
|
// EpollInstance.DeleteInterest() or EpollInstance.Release().
|
|
if _, ok := ep.interest[epi.key]; ok {
|
|
fd.EventUnregister(&epi.waiter)
|
|
ep.removeLocked(epi)
|
|
}
|
|
ep.interestMu.Unlock()
|
|
}
|
|
|
|
// If BSD locks were used, release any lock that it may have acquired.
|
|
if fd.usedLockBSD.Load() != 0 {
|
|
fd.impl.UnlockBSD(context.Background(), fd)
|
|
}
|
|
|
|
// Unlock any OFD locks.
|
|
if fd.impl.SupportsLocks() {
|
|
fd.impl.UnlockPOSIX(ctx, fd, lock.LockRange{0, lock.LockEOF})
|
|
}
|
|
|
|
// Clean up O_ASYNC state.
|
|
fd.flagsMu.Lock()
|
|
if fd.statusFlags.RacyLoad()&linux.O_ASYNC != 0 && fd.asyncHandler != nil {
|
|
fd.impl.UnregisterFileAsyncHandler(fd)
|
|
}
|
|
fd.asyncHandler = nil
|
|
fd.flagsMu.Unlock()
|
|
|
|
// Release implementation resources.
|
|
fd.impl.Release(ctx)
|
|
// Only release a mount write reference if Init() acquired one.
|
|
if fd.IsWritable() && !fd.opts.SpecialFile {
|
|
fd.vd.mount.EndWrite()
|
|
}
|
|
fd.vd.DecRef(ctx)
|
|
})
|
|
}
|
|
|
|
// Mount returns the mount on which fd was opened. It does not take a reference
|
|
// on the returned Mount.
|
|
func (fd *FileDescription) Mount() *Mount {
|
|
return fd.vd.mount
|
|
}
|
|
|
|
// Dentry returns the dentry at which fd was opened. It does not take a
|
|
// reference on the returned Dentry.
|
|
func (fd *FileDescription) Dentry() *Dentry {
|
|
return fd.vd.dentry
|
|
}
|
|
|
|
// VirtualDentry returns the location at which fd was opened. It does not take
|
|
// a reference on the returned VirtualDentry.
|
|
func (fd *FileDescription) VirtualDentry() VirtualDentry {
|
|
return fd.vd
|
|
}
|
|
|
|
// Options returns the options passed to fd.Init().
|
|
func (fd *FileDescription) Options() FileDescriptionOptions {
|
|
return fd.opts
|
|
}
|
|
|
|
// StatusFlags returns file description status flags, as for fcntl(F_GETFL).
|
|
func (fd *FileDescription) StatusFlags() uint32 {
|
|
return fd.statusFlags.Load()
|
|
}
|
|
|
|
// SetStatusFlags sets file description status flags, as for fcntl(F_SETFL).
|
|
func (fd *FileDescription) SetStatusFlags(ctx context.Context, creds *auth.Credentials, flags uint32) error {
|
|
// Compare Linux's fs/fcntl.c:setfl().
|
|
oldFlags := fd.StatusFlags()
|
|
// Linux documents this check as "O_APPEND cannot be cleared if the file is
|
|
// marked as append-only and the file is open for write", which would make
|
|
// sense. However, the check as actually implemented seems to be "O_APPEND
|
|
// cannot be changed if the file is marked as append-only".
|
|
if (flags^oldFlags)&linux.O_APPEND != 0 {
|
|
stat, err := fd.Stat(ctx, StatOptions{
|
|
// There is no mask bit for stx_attributes.
|
|
Mask: 0,
|
|
// Linux just reads inode::i_flags directly.
|
|
Sync: linux.AT_STATX_DONT_SYNC,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if (stat.AttributesMask&linux.STATX_ATTR_APPEND != 0) && (stat.Attributes&linux.STATX_ATTR_APPEND != 0) {
|
|
return linuxerr.EPERM
|
|
}
|
|
}
|
|
if (flags&linux.O_NOATIME != 0) && (oldFlags&linux.O_NOATIME == 0) {
|
|
stat, err := fd.Stat(ctx, StatOptions{
|
|
Mask: linux.STATX_UID,
|
|
// Linux's inode_owner_or_capable() just reads inode::i_uid
|
|
// directly.
|
|
Sync: linux.AT_STATX_DONT_SYNC,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if stat.Mask&linux.STATX_UID == 0 {
|
|
return linuxerr.EPERM
|
|
}
|
|
if !CanActAsOwner(creds, auth.KUID(stat.UID)) {
|
|
return linuxerr.EPERM
|
|
}
|
|
}
|
|
if flags&linux.O_DIRECT != 0 && !fd.opts.AllowDirectIO {
|
|
return linuxerr.EINVAL
|
|
}
|
|
// TODO(gvisor.dev/issue/1035): FileDescriptionImpl.SetOAsync()?
|
|
const settableFlags = linux.O_APPEND | linux.O_ASYNC | linux.O_DIRECT | linux.O_NOATIME | linux.O_NONBLOCK
|
|
fd.flagsMu.Lock()
|
|
defer fd.flagsMu.Unlock()
|
|
if fd.asyncHandler != nil {
|
|
// Use fd.statusFlags instead of oldFlags, which may have become outdated,
|
|
// to avoid double registering/unregistering.
|
|
if fd.statusFlags.RacyLoad()&linux.O_ASYNC == 0 && flags&linux.O_ASYNC != 0 {
|
|
if err := fd.impl.RegisterFileAsyncHandler(fd); err != nil {
|
|
return err
|
|
}
|
|
} else if fd.statusFlags.RacyLoad()&linux.O_ASYNC != 0 && flags&linux.O_ASYNC == 0 {
|
|
fd.impl.UnregisterFileAsyncHandler(fd)
|
|
}
|
|
}
|
|
fd.statusFlags.Store((oldFlags &^ settableFlags) | (flags & settableFlags))
|
|
return nil
|
|
}
|
|
|
|
// IsReadable returns true if fd was opened for reading.
|
|
func (fd *FileDescription) IsReadable() bool {
|
|
return fd.fmodeFlags&linux.FMODE_READ != 0
|
|
}
|
|
|
|
// IsWritable returns true if fd was opened for writing.
|
|
func (fd *FileDescription) IsWritable() bool {
|
|
return fd.fmodeFlags&linux.FMODE_WRITE != 0
|
|
}
|
|
|
|
// Impl returns the FileDescriptionImpl associated with fd.
|
|
func (fd *FileDescription) Impl() FileDescriptionImpl {
|
|
return fd.impl
|
|
}
|
|
|
|
// SyncOptions contains options for FileDescriptionImpl.Sync.
|
|
type SyncOptions struct {
|
|
// DataOnly indicates that only file data (and necessary metadata to read data)
|
|
// needs to be synced, equivalent to fdatasync(2).
|
|
DataOnly bool
|
|
}
|
|
|
|
// FileDescriptionImpl contains implementation details for an FileDescription.
|
|
// Implementations of FileDescriptionImpl should contain their associated
|
|
// FileDescription by value as their first field.
|
|
//
|
|
// For all functions that return linux.Statx, Statx.Uid and Statx.Gid will
|
|
// be interpreted as IDs in the root UserNamespace (i.e. as auth.KUID and
|
|
// auth.KGID respectively).
|
|
//
|
|
// All methods may return errors not specified.
|
|
//
|
|
// FileDescriptionImpl is analogous to Linux's struct file_operations.
|
|
type FileDescriptionImpl interface {
|
|
// Release is called when the associated FileDescription reaches zero
|
|
// references.
|
|
Release(ctx context.Context)
|
|
|
|
// OnClose is called when a file descriptor representing the
|
|
// FileDescription is closed. Note that returning a non-nil error does not
|
|
// prevent the file descriptor from being closed.
|
|
OnClose(ctx context.Context) error
|
|
|
|
// Stat returns metadata for the file represented by the FileDescription.
|
|
//
|
|
// If opts.Sync == linux.AT_STATX_SYNC_DONT_SYNC, Stat cannot take locks
|
|
// preceding memmap.MappingIdentity locks.
|
|
Stat(ctx context.Context, opts StatOptions) (linux.Statx, error)
|
|
|
|
// SetStat updates metadata for the file represented by the
|
|
// FileDescription. Implementations are responsible for checking if the
|
|
// operation can be performed (see vfs.CheckSetStat() for common checks).
|
|
SetStat(ctx context.Context, opts SetStatOptions) error
|
|
|
|
// StatFS returns metadata for the filesystem containing the file
|
|
// represented by the FileDescription.
|
|
StatFS(ctx context.Context) (linux.Statfs, error)
|
|
|
|
// Allocate grows the file to offset + length bytes.
|
|
// Only mode == 0 is supported currently.
|
|
//
|
|
// Allocate should return EISDIR on directories, ESPIPE on pipes, and ENODEV on
|
|
// other files where it is not supported.
|
|
//
|
|
// Preconditions: The FileDescription was opened for writing.
|
|
Allocate(ctx context.Context, mode, offset, length uint64) error
|
|
|
|
// waiter.Waitable methods may be used to poll for I/O events.
|
|
waiter.Waitable
|
|
|
|
// Epollable indicates whether this file can be used with epoll_ctl(2).
|
|
Epollable() bool
|
|
|
|
// PRead reads from the file into dst, starting at the given offset, and
|
|
// returns the number of bytes read. PRead is permitted to return partial
|
|
// reads with a nil error.
|
|
//
|
|
// Errors:
|
|
//
|
|
// - If opts.Flags specifies unsupported options, PRead returns EOPNOTSUPP.
|
|
//
|
|
// Preconditions:
|
|
// * The FileDescription was opened for reading.
|
|
// * FileDescriptionOptions.DenyPRead == false.
|
|
PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts ReadOptions) (int64, error)
|
|
|
|
// Read is similar to PRead, but does not specify an offset.
|
|
//
|
|
// For files with an implicit FileDescription offset (e.g. regular files),
|
|
// Read begins at the FileDescription offset, and advances the offset by
|
|
// the number of bytes read; note that POSIX 2.9.7 "Thread Interactions
|
|
// with Regular File Operations" requires that all operations that may
|
|
// mutate the FileDescription offset are serialized.
|
|
//
|
|
// Errors:
|
|
//
|
|
// - If opts.Flags specifies unsupported options, Read returns EOPNOTSUPP.
|
|
//
|
|
// Preconditions: The FileDescription was opened for reading.
|
|
Read(ctx context.Context, dst usermem.IOSequence, opts ReadOptions) (int64, error)
|
|
|
|
// PWrite writes src to the file, starting at the given offset, and returns
|
|
// the number of bytes written. PWrite is permitted to return partial
|
|
// writes with a nil error.
|
|
//
|
|
// As in Linux (but not POSIX), if O_APPEND is in effect for the
|
|
// FileDescription, PWrite should ignore the offset and append data to the
|
|
// end of the file.
|
|
//
|
|
// Errors:
|
|
//
|
|
// - If opts.Flags specifies unsupported options, PWrite returns
|
|
// EOPNOTSUPP.
|
|
//
|
|
// Preconditions:
|
|
// * The FileDescription was opened for writing.
|
|
// * FileDescriptionOptions.DenyPWrite == false.
|
|
PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts WriteOptions) (int64, error)
|
|
|
|
// Write is similar to PWrite, but does not specify an offset, which is
|
|
// implied as for Read.
|
|
//
|
|
// Write is a FileDescriptionImpl method, instead of a wrapper around
|
|
// PWrite that uses a FileDescription offset, to make it possible for
|
|
// remote filesystems to implement O_APPEND correctly (i.e. atomically with
|
|
// respect to writers outside the scope of VFS).
|
|
//
|
|
// Errors:
|
|
//
|
|
// - If opts.Flags specifies unsupported options, Write returns EOPNOTSUPP.
|
|
//
|
|
// Preconditions: The FileDescription was opened for writing.
|
|
Write(ctx context.Context, src usermem.IOSequence, opts WriteOptions) (int64, error)
|
|
|
|
// IterDirents invokes cb on each entry in the directory represented by the
|
|
// FileDescription. If IterDirents has been called since the last call to
|
|
// Seek, it continues iteration from the end of the last call.
|
|
IterDirents(ctx context.Context, cb IterDirentsCallback) error
|
|
|
|
// Seek changes the FileDescription offset (assuming one exists) and
|
|
// returns its new value.
|
|
//
|
|
// For directories, if whence == SEEK_SET and offset == 0, the caller is
|
|
// rewinddir(), such that Seek "shall also cause the directory stream to
|
|
// refer to the current state of the corresponding directory" -
|
|
// POSIX.1-2017.
|
|
Seek(ctx context.Context, offset int64, whence int32) (int64, error)
|
|
|
|
// Sync requests that cached state associated with the file represented by
|
|
// the FileDescription is synchronized with persistent storage, and blocks
|
|
// until this is complete.
|
|
Sync(ctx context.Context, opts SyncOptions) error
|
|
|
|
// ConfigureMMap mutates opts to implement mmap(2) for the file. Most
|
|
// implementations that support memory mapping can call
|
|
// GenericConfigureMMap with the appropriate memmap.Mappable.
|
|
ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error
|
|
|
|
// Ioctl implements the ioctl(2) syscall.
|
|
Ioctl(ctx context.Context, uio usermem.IO, sysno uintptr, args arch.SyscallArguments) (uintptr, error)
|
|
|
|
// ListXattr returns all extended attribute names for the file.
|
|
ListXattr(ctx context.Context, size uint64) ([]string, error)
|
|
|
|
// GetXattr returns the value associated with the given extended attribute
|
|
// for the file.
|
|
GetXattr(ctx context.Context, opts GetXattrOptions) (string, error)
|
|
|
|
// SetXattr changes the value associated with the given extended attribute
|
|
// for the file.
|
|
SetXattr(ctx context.Context, opts SetXattrOptions) error
|
|
|
|
// RemoveXattr removes the given extended attribute from the file.
|
|
RemoveXattr(ctx context.Context, name string) error
|
|
|
|
// GetPosixACL fetches the POSIX ACL from the file.
|
|
GetPosixACL(ctx context.Context, t ACLType) (*PosixACL, error)
|
|
|
|
// SetPosixACL sets the POSIX ACL for the file, returning the resulting ACL and mode.
|
|
SetPosixACL(ctx context.Context, t ACLType, acl *PosixACL, clearSGID bool) (*PosixACL, linux.FileMode, error)
|
|
|
|
// SupportsLocks indicates whether file locks are supported.
|
|
SupportsLocks() bool
|
|
|
|
// LockBSD tries to acquire a BSD-style advisory file lock.
|
|
LockBSD(ctx context.Context, uid lock.UniqueID, ownerPID int32, t lock.LockType, block bool) error
|
|
|
|
// UnlockBSD releases a BSD-style advisory file lock.
|
|
UnlockBSD(ctx context.Context, uid lock.UniqueID) error
|
|
|
|
// LockPOSIX tries to acquire a POSIX-style advisory file lock.
|
|
LockPOSIX(ctx context.Context, uid lock.UniqueID, ownerPID int32, t lock.LockType, r lock.LockRange, block bool) error
|
|
|
|
// UnlockPOSIX releases a POSIX-style advisory file lock.
|
|
UnlockPOSIX(ctx context.Context, uid lock.UniqueID, ComputeLockRange lock.LockRange) error
|
|
|
|
// TestPOSIX returns information about whether the specified lock can be held, in the style of the F_GETLK fcntl.
|
|
TestPOSIX(ctx context.Context, uid lock.UniqueID, t lock.LockType, r lock.LockRange) (linux.Flock, error)
|
|
|
|
RegisterFileAsyncHandler(fd *FileDescription) error
|
|
UnregisterFileAsyncHandler(fd *FileDescription)
|
|
}
|
|
|
|
// Dirent holds the information contained in struct linux_dirent64.
|
|
//
|
|
// +stateify savable
|
|
type Dirent struct {
|
|
// Name is the filename.
|
|
Name string
|
|
|
|
// Type is the file type, a linux.DT_* constant.
|
|
Type uint8
|
|
|
|
// Ino is the inode number.
|
|
Ino uint64
|
|
|
|
// NextOff is the offset of the *next* Dirent in the directory; that is,
|
|
// FileDescription.Seek(NextOff, SEEK_SET) (as called by seekdir(3)) will
|
|
// cause the next call to FileDescription.IterDirents() to yield the next
|
|
// Dirent. (The offset of the first Dirent in a directory is always 0.)
|
|
NextOff int64
|
|
}
|
|
|
|
// IterDirentsCallback receives Dirents from FileDescriptionImpl.IterDirents.
|
|
type IterDirentsCallback interface {
|
|
// Handle handles the given iterated Dirent. If Handle returns a non-nil
|
|
// error, FileDescriptionImpl.IterDirents must stop iteration and return
|
|
// the error; the next call to FileDescriptionImpl.IterDirents should
|
|
// restart with the same Dirent.
|
|
Handle(dirent Dirent) error
|
|
}
|
|
|
|
// IterDirentsCallbackFunc implements IterDirentsCallback for a function with
|
|
// the semantics of IterDirentsCallback.Handle.
|
|
type IterDirentsCallbackFunc func(dirent Dirent) error
|
|
|
|
// Handle implements IterDirentsCallback.Handle.
|
|
func (f IterDirentsCallbackFunc) Handle(dirent Dirent) error {
|
|
return f(dirent)
|
|
}
|
|
|
|
// OnClose is called when a file descriptor representing the FileDescription is
|
|
// closed. Returning a non-nil error should not prevent the file descriptor
|
|
// from being closed.
|
|
func (fd *FileDescription) OnClose(ctx context.Context) error {
|
|
return fd.impl.OnClose(ctx)
|
|
}
|
|
|
|
// Stat returns metadata for the file represented by fd.
|
|
func (fd *FileDescription) Stat(ctx context.Context, opts StatOptions) (linux.Statx, error) {
|
|
var (
|
|
stat linux.Statx
|
|
err error
|
|
)
|
|
if fd.opts.UseDentryMetadata {
|
|
vfsObj := fd.vd.mount.vfs
|
|
rp := vfsObj.getResolvingPath(auth.CredentialsFromContext(ctx), &PathOperation{
|
|
Root: fd.vd,
|
|
Start: fd.vd,
|
|
})
|
|
stat, err = fd.vd.mount.fs.impl.StatAt(ctx, rp, opts)
|
|
rp.Release(ctx)
|
|
} else {
|
|
stat, err = fd.impl.Stat(ctx, opts)
|
|
}
|
|
if err == nil && opts.Mask&linux.STATX_MNT_ID != 0 {
|
|
stat.MntID = fd.vd.mount.ID
|
|
stat.Mask |= linux.STATX_MNT_ID
|
|
}
|
|
return stat, err
|
|
}
|
|
|
|
// SetStat updates metadata for the file represented by fd.
|
|
func (fd *FileDescription) SetStat(ctx context.Context, opts SetStatOptions) error {
|
|
if fd.opts.UseDentryMetadata {
|
|
vfsObj := fd.vd.mount.vfs
|
|
rp := vfsObj.getResolvingPath(auth.CredentialsFromContext(ctx), &PathOperation{
|
|
Root: fd.vd,
|
|
Start: fd.vd,
|
|
})
|
|
err := fd.vd.mount.fs.impl.SetStatAt(ctx, rp, opts)
|
|
rp.Release(ctx)
|
|
return err
|
|
}
|
|
if err := fd.impl.SetStat(ctx, opts); err != nil {
|
|
return err
|
|
}
|
|
if ev := InotifyEventFromStatMask(opts.Stat.Mask); ev != 0 {
|
|
fd.Dentry().InotifyWithParent(ctx, ev, 0, InodeEvent)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StatFS returns metadata for the filesystem containing the file represented
|
|
// by fd.
|
|
func (fd *FileDescription) StatFS(ctx context.Context) (linux.Statfs, error) {
|
|
if fd.opts.UseDentryMetadata {
|
|
vfsObj := fd.vd.mount.vfs
|
|
rp := vfsObj.getResolvingPath(auth.CredentialsFromContext(ctx), &PathOperation{
|
|
Root: fd.vd,
|
|
Start: fd.vd,
|
|
})
|
|
statfs, err := fd.vd.mount.fs.impl.StatFSAt(ctx, rp)
|
|
rp.Release(ctx)
|
|
return statfs, err
|
|
}
|
|
return fd.impl.StatFS(ctx)
|
|
}
|
|
|
|
// Allocate grows file represented by FileDescription to offset + length bytes.
|
|
func (fd *FileDescription) Allocate(ctx context.Context, mode, offset, length uint64) error {
|
|
if !fd.IsWritable() {
|
|
return linuxerr.EBADF
|
|
}
|
|
if err := fd.impl.Allocate(ctx, mode, offset, length); err != nil {
|
|
return err
|
|
}
|
|
fd.Dentry().InotifyWithParent(ctx, linux.IN_MODIFY, 0, PathEvent)
|
|
return nil
|
|
}
|
|
|
|
// Readiness implements waiter.Waitable.Readiness.
|
|
//
|
|
// It returns fd's I/O readiness.
|
|
func (fd *FileDescription) Readiness(mask waiter.EventMask) waiter.EventMask {
|
|
return fd.impl.Readiness(mask)
|
|
}
|
|
|
|
// EventRegister implements waiter.Waitable.EventRegister.
|
|
//
|
|
// It registers e for I/O readiness events in mask.
|
|
func (fd *FileDescription) EventRegister(e *waiter.Entry) error {
|
|
return fd.impl.EventRegister(e)
|
|
}
|
|
|
|
// EventUnregister implements waiter.Waitable.EventUnregister.
|
|
//
|
|
// It unregisters e for I/O readiness events.
|
|
func (fd *FileDescription) EventUnregister(e *waiter.Entry) {
|
|
fd.impl.EventUnregister(e)
|
|
}
|
|
|
|
// Epollable returns whether this file can be used with epoll_ctl(2).
|
|
func (fd *FileDescription) Epollable() bool {
|
|
return fd.impl.Epollable()
|
|
}
|
|
|
|
// PRead reads from the file represented by fd into dst, starting at the given
|
|
// offset, and returns the number of bytes read. PRead is permitted to return
|
|
// partial reads with a nil error.
|
|
func (fd *FileDescription) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts ReadOptions) (int64, error) {
|
|
if fd.opts.DenyPRead {
|
|
return 0, linuxerr.ESPIPE
|
|
}
|
|
if !fd.IsReadable() {
|
|
return 0, linuxerr.EBADF
|
|
}
|
|
start := fsmetric.StartReadWait()
|
|
n, err := fd.impl.PRead(ctx, dst, offset, opts)
|
|
if n > 0 {
|
|
fd.Dentry().InotifyWithParent(ctx, linux.IN_ACCESS, 0, PathEvent)
|
|
}
|
|
fsmetric.Reads.Increment()
|
|
fsmetric.FinishReadWait(fsmetric.ReadWait, start)
|
|
return n, err
|
|
}
|
|
|
|
// Read is similar to PRead, but does not specify an offset.
|
|
func (fd *FileDescription) Read(ctx context.Context, dst usermem.IOSequence, opts ReadOptions) (int64, error) {
|
|
if !fd.IsReadable() {
|
|
return 0, linuxerr.EBADF
|
|
}
|
|
start := fsmetric.StartReadWait()
|
|
n, err := fd.impl.Read(ctx, dst, opts)
|
|
if n > 0 {
|
|
fd.Dentry().InotifyWithParent(ctx, linux.IN_ACCESS, 0, PathEvent)
|
|
}
|
|
fsmetric.Reads.Increment()
|
|
fsmetric.FinishReadWait(fsmetric.ReadWait, start)
|
|
return n, err
|
|
}
|
|
|
|
// PWrite writes src to the file represented by fd, starting at the given
|
|
// offset, and returns the number of bytes written. PWrite is permitted to
|
|
// return partial writes with a nil error.
|
|
func (fd *FileDescription) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts WriteOptions) (int64, error) {
|
|
if fd.opts.DenyPWrite {
|
|
return 0, linuxerr.ESPIPE
|
|
}
|
|
if !fd.IsWritable() {
|
|
return 0, linuxerr.EBADF
|
|
}
|
|
n, err := fd.impl.PWrite(ctx, src, offset, opts)
|
|
if n > 0 {
|
|
fd.Dentry().InotifyWithParent(ctx, linux.IN_MODIFY, 0, PathEvent)
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
// Write is similar to PWrite, but does not specify an offset.
|
|
func (fd *FileDescription) Write(ctx context.Context, src usermem.IOSequence, opts WriteOptions) (int64, error) {
|
|
if !fd.IsWritable() {
|
|
return 0, linuxerr.EBADF
|
|
}
|
|
n, err := fd.impl.Write(ctx, src, opts)
|
|
if n > 0 {
|
|
fd.Dentry().InotifyWithParent(ctx, linux.IN_MODIFY, 0, PathEvent)
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
// IterDirents invokes cb on each entry in the directory represented by fd. If
|
|
// IterDirents has been called since the last call to Seek, it continues
|
|
// iteration from the end of the last call.
|
|
func (fd *FileDescription) IterDirents(ctx context.Context, cb IterDirentsCallback) error {
|
|
defer fd.Dentry().InotifyWithParent(ctx, linux.IN_ACCESS, 0, PathEvent)
|
|
return fd.impl.IterDirents(ctx, cb)
|
|
}
|
|
|
|
// Seek changes fd's offset (assuming one exists) and returns its new value.
|
|
func (fd *FileDescription) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
|
return fd.impl.Seek(ctx, offset, whence)
|
|
}
|
|
|
|
// Sync has the semantics of fsync(2).
|
|
func (fd *FileDescription) Sync(ctx context.Context) error {
|
|
return fd.impl.Sync(ctx, SyncOptions{DataOnly: false})
|
|
}
|
|
|
|
// SyncData has the semantics of fdatasync(2).
|
|
func (fd *FileDescription) SyncData(ctx context.Context) error {
|
|
return fd.impl.Sync(ctx, SyncOptions{DataOnly: true})
|
|
}
|
|
|
|
// ConfigureMMap mutates opts to implement mmap(2) for the file represented by
|
|
// fd.
|
|
func (fd *FileDescription) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error {
|
|
return fd.impl.ConfigureMMap(ctx, opts)
|
|
}
|
|
|
|
// Ioctl implements the ioctl(2) syscall.
|
|
func (fd *FileDescription) Ioctl(ctx context.Context, uio usermem.IO, sysno uintptr, args arch.SyscallArguments) (uintptr, error) {
|
|
return fd.impl.Ioctl(ctx, uio, sysno, args)
|
|
}
|
|
|
|
// ListXattr returns all extended attribute names for the file represented by
|
|
// fd.
|
|
//
|
|
// If the size of the list (including a NUL terminating byte after every entry)
|
|
// would exceed size, ERANGE may be returned. Note that implementations
|
|
// are free to ignore size entirely and return without error). In all cases,
|
|
// if size is 0, the list should be returned without error, regardless of size.
|
|
func (fd *FileDescription) ListXattr(ctx context.Context, size uint64) ([]string, error) {
|
|
if fd.opts.UseDentryMetadata {
|
|
vfsObj := fd.vd.mount.vfs
|
|
rp := vfsObj.getResolvingPath(auth.CredentialsFromContext(ctx), &PathOperation{
|
|
Root: fd.vd,
|
|
Start: fd.vd,
|
|
})
|
|
names, err := fd.vd.mount.fs.impl.ListXattrAt(ctx, rp, size)
|
|
rp.Release(ctx)
|
|
return names, err
|
|
}
|
|
names, err := fd.impl.ListXattr(ctx, size)
|
|
if linuxerr.Equals(linuxerr.EOPNOTSUPP, err) {
|
|
// Linux doesn't actually return EOPNOTSUPP in this case; instead,
|
|
// fs/xattr.c:vfs_listxattr() falls back to allowing the security
|
|
// subsystem to return security extended attributes, which by default
|
|
// don't exist.
|
|
return nil, nil
|
|
}
|
|
return names, err
|
|
}
|
|
|
|
// GetXattr returns the value associated with the given extended attribute for
|
|
// the file represented by fd.
|
|
//
|
|
// If the size of the return value exceeds opts.Size, ERANGE may be returned
|
|
// (note that implementations are free to ignore opts.Size entirely and return
|
|
// without error). In all cases, if opts.Size is 0, the value should be
|
|
// returned without error, regardless of size.
|
|
func (fd *FileDescription) GetXattr(ctx context.Context, opts *GetXattrOptions) (string, error) {
|
|
if fd.opts.UseDentryMetadata {
|
|
vfsObj := fd.vd.mount.vfs
|
|
rp := vfsObj.getResolvingPath(auth.CredentialsFromContext(ctx), &PathOperation{
|
|
Root: fd.vd,
|
|
Start: fd.vd,
|
|
})
|
|
val, err := fd.vd.mount.fs.impl.GetXattrAt(ctx, rp, *opts)
|
|
rp.Release(ctx)
|
|
return val, err
|
|
}
|
|
return fd.impl.GetXattr(ctx, *opts)
|
|
}
|
|
|
|
// SetXattr changes the value associated with the given extended attribute for
|
|
// the file represented by fd.
|
|
func (fd *FileDescription) SetXattr(ctx context.Context, opts *SetXattrOptions) error {
|
|
if fd.opts.UseDentryMetadata {
|
|
vfsObj := fd.vd.mount.vfs
|
|
rp := vfsObj.getResolvingPath(auth.CredentialsFromContext(ctx), &PathOperation{
|
|
Root: fd.vd,
|
|
Start: fd.vd,
|
|
})
|
|
err := fd.vd.mount.fs.impl.SetXattrAt(ctx, rp, *opts)
|
|
rp.Release(ctx)
|
|
return err
|
|
}
|
|
if err := fd.impl.SetXattr(ctx, *opts); err != nil {
|
|
return err
|
|
}
|
|
fd.Dentry().InotifyWithParent(ctx, linux.IN_ATTRIB, 0, InodeEvent)
|
|
return nil
|
|
}
|
|
|
|
// RemoveXattr removes the given extended attribute from the file represented
|
|
// by fd.
|
|
func (fd *FileDescription) RemoveXattr(ctx context.Context, name string) error {
|
|
if fd.opts.UseDentryMetadata {
|
|
vfsObj := fd.vd.mount.vfs
|
|
rp := vfsObj.getResolvingPath(auth.CredentialsFromContext(ctx), &PathOperation{
|
|
Root: fd.vd,
|
|
Start: fd.vd,
|
|
})
|
|
err := fd.vd.mount.fs.impl.RemoveXattrAt(ctx, rp, name)
|
|
rp.Release(ctx)
|
|
return err
|
|
}
|
|
if err := fd.impl.RemoveXattr(ctx, name); err != nil {
|
|
return err
|
|
}
|
|
fd.Dentry().InotifyWithParent(ctx, linux.IN_ATTRIB, 0, InodeEvent)
|
|
return nil
|
|
}
|
|
|
|
// GetPosixACL gets the POSIX ACL from the file represented by fd.
|
|
func (fd *FileDescription) GetPosixACL(ctx context.Context, t ACLType) (*PosixACL, error) {
|
|
if fd.opts.UseDentryMetadata {
|
|
vfsObj := fd.vd.mount.vfs
|
|
rp := vfsObj.getResolvingPath(auth.CredentialsFromContext(ctx), &PathOperation{
|
|
Root: fd.vd,
|
|
Start: fd.vd,
|
|
})
|
|
acl, err := fd.vd.mount.fs.impl.GetPosixACLAt(ctx, rp, t)
|
|
rp.Release(ctx)
|
|
return acl, err
|
|
}
|
|
return fd.impl.GetPosixACL(ctx, t)
|
|
}
|
|
|
|
// SetPosixACL sets the POSIX ACL for the file represented by fd.
|
|
// The resulting ACL and mode are returned.
|
|
func (fd *FileDescription) SetPosixACL(ctx context.Context, t ACLType, acl *PosixACL, clearSGID bool) (*PosixACL, linux.FileMode, error) {
|
|
if fd.opts.UseDentryMetadata {
|
|
vfsObj := fd.vd.mount.vfs
|
|
rp := vfsObj.getResolvingPath(auth.CredentialsFromContext(ctx), &PathOperation{
|
|
Root: fd.vd,
|
|
Start: fd.vd,
|
|
})
|
|
acl, mode, err := fd.vd.mount.fs.impl.SetPosixACLAt(ctx, rp, t, acl, clearSGID)
|
|
rp.Release(ctx)
|
|
return acl, mode, err
|
|
}
|
|
return fd.impl.SetPosixACL(ctx, t, acl, clearSGID)
|
|
}
|
|
|
|
// SyncFS instructs the filesystem containing fd to execute the semantics of
|
|
// syncfs(2).
|
|
func (fd *FileDescription) SyncFS(ctx context.Context) error {
|
|
return fd.vd.mount.fs.impl.Sync(ctx)
|
|
}
|
|
|
|
// MappedName implements memmap.MappingIdentity.MappedName.
|
|
func (fd *FileDescription) MappedName(ctx context.Context) string {
|
|
vfsroot := RootFromContext(ctx)
|
|
s, _ := fd.vd.mount.vfs.PathnameWithDeleted(ctx, vfsroot, fd.vd)
|
|
if vfsroot.Ok() {
|
|
vfsroot.DecRef(ctx)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// DeviceID implements memmap.MappingIdentity.DeviceID.
|
|
func (fd *FileDescription) DeviceID() uint64 {
|
|
stat, err := fd.Stat(context.Background(), StatOptions{
|
|
// There is no STATX_DEV; we assume that Stat will return it if it's
|
|
// available regardless of mask.
|
|
Mask: 0,
|
|
// fs/proc/task_mmu.c:show_map_vma() just reads inode::i_sb->s_dev
|
|
// directly.
|
|
Sync: linux.AT_STATX_DONT_SYNC,
|
|
})
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return uint64(linux.MakeDeviceID(uint16(stat.DevMajor), stat.DevMinor))
|
|
}
|
|
|
|
// InodeID implements memmap.MappingIdentity.InodeID.
|
|
func (fd *FileDescription) InodeID() uint64 {
|
|
stat, err := fd.Stat(context.Background(), StatOptions{
|
|
Mask: linux.STATX_INO,
|
|
// fs/proc/task_mmu.c:show_map_vma() just reads inode::i_ino directly.
|
|
Sync: linux.AT_STATX_DONT_SYNC,
|
|
})
|
|
if err != nil || stat.Mask&linux.STATX_INO == 0 {
|
|
return 0
|
|
}
|
|
return stat.Ino
|
|
}
|
|
|
|
// Msync implements memmap.MappingIdentity.Msync.
|
|
func (fd *FileDescription) Msync(ctx context.Context, mr memmap.MappableRange) error {
|
|
return fd.Sync(ctx)
|
|
}
|
|
|
|
// SupportsLocks indicates whether file locks are supported.
|
|
func (fd *FileDescription) SupportsLocks() bool {
|
|
return fd.impl.SupportsLocks()
|
|
}
|
|
|
|
// LockBSD tries to acquire a BSD-style advisory file lock.
|
|
func (fd *FileDescription) LockBSD(ctx context.Context, ownerPID int32, lockType lock.LockType, block bool) error {
|
|
fd.usedLockBSD.Store(1)
|
|
return fd.impl.LockBSD(ctx, fd, ownerPID, lockType, block)
|
|
}
|
|
|
|
// UnlockBSD releases a BSD-style advisory file lock.
|
|
func (fd *FileDescription) UnlockBSD(ctx context.Context) error {
|
|
return fd.impl.UnlockBSD(ctx, fd)
|
|
}
|
|
|
|
// LockPOSIX locks a POSIX-style file range lock.
|
|
func (fd *FileDescription) LockPOSIX(ctx context.Context, uid lock.UniqueID, ownerPID int32, t lock.LockType, r lock.LockRange, block bool) error {
|
|
return fd.impl.LockPOSIX(ctx, uid, ownerPID, t, r, block)
|
|
}
|
|
|
|
// UnlockPOSIX unlocks a POSIX-style file range lock.
|
|
func (fd *FileDescription) UnlockPOSIX(ctx context.Context, uid lock.UniqueID, r lock.LockRange) error {
|
|
return fd.impl.UnlockPOSIX(ctx, uid, r)
|
|
}
|
|
|
|
// TestPOSIX returns information about whether the specified lock can be held.
|
|
func (fd *FileDescription) TestPOSIX(ctx context.Context, uid lock.UniqueID, t lock.LockType, r lock.LockRange) (linux.Flock, error) {
|
|
return fd.impl.TestPOSIX(ctx, uid, t, r)
|
|
}
|
|
|
|
// ComputeLockRange computes the range of a file lock based on the given values.
|
|
func (fd *FileDescription) ComputeLockRange(ctx context.Context, start uint64, length uint64, whence int16) (lock.LockRange, error) {
|
|
var off int64
|
|
switch whence {
|
|
case linux.SEEK_SET:
|
|
off = 0
|
|
case linux.SEEK_CUR:
|
|
// Note that Linux does not hold any mutexes while retrieving the file
|
|
// offset, see fs/locks.c:flock_to_posix_lock and fs/locks.c:fcntl_setlk.
|
|
curOff, err := fd.Seek(ctx, 0, linux.SEEK_CUR)
|
|
if err != nil {
|
|
return lock.LockRange{}, err
|
|
}
|
|
off = curOff
|
|
case linux.SEEK_END:
|
|
stat, err := fd.Stat(ctx, StatOptions{Mask: linux.STATX_SIZE})
|
|
if err != nil {
|
|
return lock.LockRange{}, err
|
|
}
|
|
off = int64(stat.Size)
|
|
default:
|
|
return lock.LockRange{}, linuxerr.EINVAL
|
|
}
|
|
|
|
return lock.ComputeRange(int64(start), int64(length), off)
|
|
}
|
|
|
|
// ReadFull read all contents from the file.
|
|
func (fd *FileDescription) ReadFull(ctx context.Context, dst usermem.IOSequence, offset int64) (int64, error) {
|
|
var total int64
|
|
for dst.NumBytes() > 0 {
|
|
n, err := fd.PRead(ctx, dst, offset+total, ReadOptions{})
|
|
total += n
|
|
if err == io.EOF && total != 0 {
|
|
return total, io.ErrUnexpectedEOF
|
|
} else if err != nil {
|
|
return total, err
|
|
}
|
|
dst = dst.DropFirst64(n)
|
|
}
|
|
return total, nil
|
|
}
|
|
|
|
// A FileAsync sends signals to its owner when w is ready for IO. This is only
|
|
// implemented by pkg/sentry/fasync:FileAsync, but we unfortunately need this
|
|
// interface to avoid circular dependencies.
|
|
type FileAsync interface {
|
|
Register(w waiter.Waitable) error
|
|
Unregister(w waiter.Waitable)
|
|
}
|
|
|
|
// AsyncHandler returns the FileAsync for fd.
|
|
func (fd *FileDescription) AsyncHandler() FileAsync {
|
|
fd.flagsMu.Lock()
|
|
defer fd.flagsMu.Unlock()
|
|
return fd.asyncHandler
|
|
}
|
|
|
|
// SetAsyncHandler sets fd.asyncHandler if it has not been set before and
|
|
// returns it.
|
|
func (fd *FileDescription) SetAsyncHandler(newHandler func() FileAsync) (FileAsync, error) {
|
|
fd.flagsMu.Lock()
|
|
defer fd.flagsMu.Unlock()
|
|
if fd.asyncHandler == nil {
|
|
fd.asyncHandler = newHandler()
|
|
if fd.statusFlags.RacyLoad()&linux.O_ASYNC != 0 {
|
|
if err := fd.impl.RegisterFileAsyncHandler(fd); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
return fd.asyncHandler, nil
|
|
}
|
|
|
|
// CopyRegularFileData copies data from srcFD to dstFD until reading from srcFD
|
|
// returns EOF or an error. It returns the number of bytes copied.
|
|
func CopyRegularFileData(ctx context.Context, dstFD, srcFD *FileDescription) (int64, error) {
|
|
done := int64(0)
|
|
buf := usermem.BytesIOSequence(make([]byte, 32*1024)) // arbitrary buffer size
|
|
for {
|
|
readN, readErr := srcFD.Read(ctx, buf, ReadOptions{})
|
|
if readErr != nil && readErr != io.EOF {
|
|
return done, readErr
|
|
}
|
|
src := buf.TakeFirst64(readN)
|
|
for src.NumBytes() != 0 {
|
|
writeN, writeErr := dstFD.Write(ctx, src, WriteOptions{})
|
|
done += writeN
|
|
src = src.DropFirst64(writeN)
|
|
if writeErr != nil {
|
|
return done, writeErr
|
|
}
|
|
}
|
|
if readErr == io.EOF {
|
|
return done, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetFilePrivileges returns the file privileges for the file represented by fd.
|
|
func (fd *FileDescription) GetFilePrivileges(ctx context.Context) (auth.FilePrivileges, error) {
|
|
stat, err := fd.Stat(ctx, StatOptions{
|
|
Mask: linux.STATX_UID | linux.STATX_GID | linux.STATX_MODE,
|
|
})
|
|
if err != nil {
|
|
return auth.FilePrivileges{}, err
|
|
}
|
|
|
|
filePrivs := auth.FilePrivileges{SetUserID: auth.NoID, SetGroupID: auth.NoID, HasCaps: false}
|
|
if fd.Mount().Options().Flags.NoSUID {
|
|
return filePrivs, nil
|
|
}
|
|
|
|
if stat.Mask&linux.STATX_MODE != 0 {
|
|
if stat.Mode&linux.ModeSetUID != 0 && stat.Mask&linux.STATX_UID != 0 {
|
|
filePrivs.SetUserID = auth.KUID(stat.UID)
|
|
}
|
|
if stat.Mode&linux.ModeSetGID != 0 && stat.Mask&linux.STATX_GID != 0 {
|
|
filePrivs.SetGroupID = auth.KGID(stat.GID)
|
|
}
|
|
}
|
|
|
|
fileCaps, err := fd.GetXattr(ctx, &GetXattrOptions{Name: linux.XATTR_SECURITY_CAPABILITY, Size: linux.XATTR_CAPS_SZ_3})
|
|
switch {
|
|
case linuxerr.Equals(linuxerr.ENODATA, err), linuxerr.Equals(linuxerr.EOPNOTSUPP, err):
|
|
return filePrivs, nil
|
|
case err != nil:
|
|
return auth.FilePrivileges{}, err
|
|
}
|
|
vfsCaps, err := auth.VfsCapDataOf([]byte(fileCaps))
|
|
if err != nil {
|
|
return auth.FilePrivileges{}, err
|
|
}
|
|
|
|
filePrivs.HasCaps = true
|
|
filePrivs.PermittedCaps = auth.CapabilitySet(vfsCaps.Permitted())
|
|
filePrivs.InheritableCaps = auth.CapabilitySet(vfsCaps.Inheritable())
|
|
filePrivs.Effective = (vfsCaps.MagicEtc & linux.VFS_CAP_FLAGS_EFFECTIVE) > 0
|
|
|
|
// gVisor does not support ID-mapped mounts and all filesystems are owned by
|
|
// the initial user namespace. So we can directly cast the root ID to KUID.
|
|
filePrivs.CapRootID = auth.KUID(vfsCaps.RootID)
|
|
|
|
return filePrivs, nil
|
|
}
|
|
|
|
// Credentials returns the credentials under which fd was opened.
|
|
func (fd *FileDescription) Credentials() *auth.Credentials {
|
|
return fd.creds
|
|
}
|