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
507 lines
16 KiB
Go
507 lines
16 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 gofer
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"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/fdnotifier"
|
|
"gvisor.dev/gvisor/pkg/hostarch"
|
|
"gvisor.dev/gvisor/pkg/metric"
|
|
"gvisor.dev/gvisor/pkg/sentry/fsmetric"
|
|
"gvisor.dev/gvisor/pkg/sentry/fsutil"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
|
"gvisor.dev/gvisor/pkg/sentry/memmap"
|
|
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
|
"gvisor.dev/gvisor/pkg/sync"
|
|
"gvisor.dev/gvisor/pkg/usermem"
|
|
"gvisor.dev/gvisor/pkg/waiter"
|
|
)
|
|
|
|
// specialFileFD implements vfs.FileDescriptionImpl for pipes, sockets, device
|
|
// special files, and (when filesystemOptions.regularFilesUseSpecialFileFD is
|
|
// in effect) regular files. specialFileFD differs from regularFileFD by using
|
|
// per-FD handles instead of shared per-dentry handles, and never buffering I/O.
|
|
//
|
|
// +stateify savable
|
|
type specialFileFD struct {
|
|
fileDescription
|
|
specialFDEntry
|
|
|
|
// releaseMu synchronizes the closing of fd.handle with fd.sync(). It's safe
|
|
// to access fd.handle without locking for operations that require a ref to
|
|
// be held by the caller, e.g. vfs.FileDescriptionImpl implementations.
|
|
releaseMu sync.RWMutex `state:"nosave"`
|
|
|
|
// handle is used for file I/O. handle is immutable.
|
|
handle handle `state:"nosave"`
|
|
|
|
// isRegularFile is true if this FD represents a regular file which is only
|
|
// possible when filesystemOptions.regularFilesUseSpecialFileFD is in
|
|
// effect. isRegularFile is immutable.
|
|
isRegularFile bool
|
|
|
|
// seekable is true if this file description represents a file for which
|
|
// file offset is significant, i.e. a regular file, character device or
|
|
// block device. seekable is immutable.
|
|
seekable bool
|
|
|
|
// haveQueue is true if this file description represents a file for which
|
|
// queue may send I/O readiness events. haveQueue is immutable.
|
|
haveQueue bool `state:"nosave"`
|
|
queue waiter.Queue
|
|
|
|
// If seekable is true, off is the file offset. off is protected by mu.
|
|
mu sync.Mutex `state:"nosave"`
|
|
off int64
|
|
|
|
// If haveBuf is non-zero, this FD represents a pipe, and buf contains data
|
|
// read from the pipe from previous calls to specialFileFD.savePipeData().
|
|
// haveBuf and buf are protected by bufMu.
|
|
bufMu sync.Mutex `state:"nosave"`
|
|
haveBuf atomicbitops.Uint32
|
|
buf []byte
|
|
|
|
// If handle.fd >= 0, mmapFile provides mappings of it.
|
|
//
|
|
// The use of MmapCachedFile is consistent with legacy behavior (and the
|
|
// fact that historically most specialFileFD mmap() is on regular files on
|
|
// mounts with cache=none), but we should consider using MmapPreciseFile
|
|
// for any "really" special file types.
|
|
mmapFile fsutil.MmapCachedFile
|
|
}
|
|
|
|
func newSpecialFileFD(h handle, mnt *vfs.Mount, d *dentry, flags uint32, creds *auth.Credentials) (*specialFileFD, error) {
|
|
ftype := d.inode.fileType()
|
|
seekable := ftype == linux.S_IFREG || ftype == linux.S_IFCHR || ftype == linux.S_IFBLK
|
|
haveQueue := (ftype == linux.S_IFIFO || ftype == linux.S_IFSOCK || ftype == linux.S_IFCHR) && h.fd >= 0
|
|
fd := &specialFileFD{
|
|
handle: h,
|
|
isRegularFile: ftype == linux.S_IFREG,
|
|
seekable: seekable,
|
|
haveQueue: haveQueue,
|
|
}
|
|
fd.LockFD.Init(&d.inode.locks)
|
|
if haveQueue {
|
|
if err := fdnotifier.AddFD(h.fd, &fd.queue); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err := fd.vfsfd.Init(fd, flags, creds, mnt, &d.vfsd, &vfs.FileDescriptionOptions{
|
|
AllowDirectIO: true,
|
|
DenyPRead: !seekable,
|
|
DenyPWrite: !seekable,
|
|
SpecialFile: linux.FileMode(ftype).IsSpecialFile(),
|
|
}); err != nil {
|
|
if haveQueue {
|
|
fdnotifier.RemoveFD(h.fd)
|
|
}
|
|
return nil, err
|
|
}
|
|
fd.mmapFile.SetFD(int(h.fd))
|
|
d.inode.fs.syncMu.Lock()
|
|
d.inode.fs.specialFileFDs.PushBack(fd)
|
|
d.inode.fs.syncMu.Unlock()
|
|
if fd.vfsfd.IsWritable() && (d.inode.mode.Load()&0111 != 0) {
|
|
metric.SuspiciousOperationsMetric.Increment(&metric.SuspiciousOperationsTypeOpenedWriteExecuteFile)
|
|
}
|
|
if h.fd >= 0 {
|
|
fsmetric.GoferOpensHost.Increment()
|
|
} else {
|
|
fsmetric.GoferOpens9P.Increment()
|
|
}
|
|
return fd, nil
|
|
}
|
|
|
|
// Release implements vfs.FileDescriptionImpl.Release.
|
|
func (fd *specialFileFD) Release(ctx context.Context) {
|
|
if fd.haveQueue {
|
|
fdnotifier.RemoveFD(fd.handle.fd)
|
|
}
|
|
fd.releaseMu.Lock()
|
|
// Forget fd.handle.fd, which will be closed by fd.mmapFile.
|
|
fd.handle.fd = -1
|
|
fd.handle.close(ctx)
|
|
fd.releaseMu.Unlock()
|
|
|
|
fs := fd.vfsfd.Mount().Filesystem().Impl().(*filesystem)
|
|
fs.syncMu.Lock()
|
|
fs.specialFileFDs.Remove(fd)
|
|
fs.syncMu.Unlock()
|
|
|
|
fd.mmapFile.MappableRelease()
|
|
}
|
|
|
|
// OnClose implements vfs.FileDescriptionImpl.OnClose.
|
|
func (fd *specialFileFD) OnClose(ctx context.Context) error {
|
|
if !fd.vfsfd.IsWritable() {
|
|
return nil
|
|
}
|
|
return flush(ctx, fd.handle.fdLisa)
|
|
}
|
|
|
|
// Readiness implements waiter.Waitable.Readiness.
|
|
func (fd *specialFileFD) Readiness(mask waiter.EventMask) waiter.EventMask {
|
|
if fd.haveQueue {
|
|
return fdnotifier.NonBlockingPoll(fd.handle.fd, mask)
|
|
}
|
|
return fd.fileDescription.Readiness(mask)
|
|
}
|
|
|
|
// EventRegister implements waiter.Waitable.EventRegister.
|
|
func (fd *specialFileFD) EventRegister(e *waiter.Entry) error {
|
|
if fd.haveQueue {
|
|
fd.queue.EventRegister(e)
|
|
if err := fdnotifier.UpdateFD(fd.handle.fd); err != nil {
|
|
fd.queue.EventUnregister(e)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
return fd.fileDescription.EventRegister(e)
|
|
}
|
|
|
|
// EventUnregister implements waiter.Waitable.EventUnregister.
|
|
func (fd *specialFileFD) EventUnregister(e *waiter.Entry) {
|
|
if fd.haveQueue {
|
|
fd.queue.EventUnregister(e)
|
|
if err := fdnotifier.UpdateFD(fd.handle.fd); err != nil {
|
|
panic(fmt.Sprint("UpdateFD:", err))
|
|
}
|
|
return
|
|
}
|
|
fd.fileDescription.EventUnregister(e)
|
|
}
|
|
|
|
// Epollable implements FileDescriptionImpl.Epollable.
|
|
func (fd *specialFileFD) Epollable() bool {
|
|
if fd.haveQueue {
|
|
return true
|
|
}
|
|
return fd.fileDescription.Epollable()
|
|
}
|
|
|
|
func (fd *specialFileFD) Allocate(ctx context.Context, mode, offset, length uint64) error {
|
|
if fd.isRegularFile {
|
|
d := fd.dentry()
|
|
return d.doAllocate(ctx, offset, length, func() error {
|
|
return fd.handle.allocate(ctx, mode, offset, length)
|
|
})
|
|
}
|
|
return fd.FileDescriptionDefaultImpl.Allocate(ctx, mode, offset, length)
|
|
}
|
|
|
|
// PRead implements vfs.FileDescriptionImpl.PRead.
|
|
func (fd *specialFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
|
start := fsmetric.StartReadWait()
|
|
defer func() {
|
|
if fd.handle.fd >= 0 {
|
|
fsmetric.GoferReadsHost.Increment()
|
|
fsmetric.FinishReadWait(fsmetric.GoferReadWaitHost, start)
|
|
} else {
|
|
fsmetric.GoferReads9P.Increment()
|
|
fsmetric.FinishReadWait(fsmetric.GoferReadWait9P, start)
|
|
}
|
|
}()
|
|
|
|
if fd.seekable && offset < 0 {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
|
|
// Check that flags are supported.
|
|
//
|
|
// TODO(gvisor.dev/issue/2601): Support select preadv2 flags.
|
|
if opts.Flags&^linux.RWF_HIPRI != 0 {
|
|
return 0, linuxerr.EOPNOTSUPP
|
|
}
|
|
|
|
if d := fd.dentry(); d.inode.cachedMetadataAuthoritative() {
|
|
d.touchAtime(fd.vfsfd.Mount())
|
|
}
|
|
|
|
bufN := int64(0)
|
|
if fd.haveBuf.Load() != 0 {
|
|
var err error
|
|
fd.bufMu.Lock()
|
|
if len(fd.buf) != 0 {
|
|
var n int
|
|
n, err = dst.CopyOut(ctx, fd.buf)
|
|
dst = dst.DropFirst(n)
|
|
fd.buf = fd.buf[n:]
|
|
if len(fd.buf) == 0 {
|
|
fd.haveBuf.Store(0)
|
|
fd.buf = nil
|
|
}
|
|
bufN = int64(n)
|
|
if offset >= 0 {
|
|
offset += bufN
|
|
}
|
|
}
|
|
fd.bufMu.Unlock()
|
|
if err != nil {
|
|
return bufN, err
|
|
}
|
|
}
|
|
|
|
rw := getHandleReadWriter(ctx, &fd.handle, offset)
|
|
n, err := dst.CopyOutFrom(ctx, rw)
|
|
putHandleReadWriter(rw)
|
|
if linuxerr.Equals(linuxerr.EAGAIN, err) {
|
|
err = linuxerr.ErrWouldBlock
|
|
}
|
|
return bufN + n, err
|
|
}
|
|
|
|
// Read implements vfs.FileDescriptionImpl.Read.
|
|
func (fd *specialFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
|
if !fd.seekable {
|
|
return fd.PRead(ctx, dst, -1, opts)
|
|
}
|
|
|
|
fd.mu.Lock()
|
|
n, err := fd.PRead(ctx, dst, fd.off, opts)
|
|
fd.off += n
|
|
fd.mu.Unlock()
|
|
return n, err
|
|
}
|
|
|
|
// PWrite implements vfs.FileDescriptionImpl.PWrite.
|
|
func (fd *specialFileFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
|
n, _, err := fd.pwrite(ctx, src, offset, opts)
|
|
return n, err
|
|
}
|
|
|
|
// pwrite returns the number of bytes written, final offset, error. The final
|
|
// offset should be ignored by PWrite.
|
|
func (fd *specialFileFD) pwrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (written, finalOff int64, err error) {
|
|
if fd.seekable && offset < 0 {
|
|
return 0, offset, linuxerr.EINVAL
|
|
}
|
|
|
|
// Check that flags are supported.
|
|
//
|
|
// TODO(gvisor.dev/issue/2601): Support select pwritev2 flags.
|
|
if opts.Flags&^linux.RWF_HIPRI != 0 {
|
|
return 0, offset, linuxerr.EOPNOTSUPP
|
|
}
|
|
|
|
d := fd.dentry()
|
|
if fd.isRegularFile {
|
|
// If the regular file fd was opened with O_APPEND, make sure the file
|
|
// size is updated. There is a possible race here if size is modified
|
|
// externally after metadata cache is updated.
|
|
if fd.vfsfd.StatusFlags()&linux.O_APPEND != 0 && !d.inode.cachedMetadataAuthoritative() {
|
|
if err := d.inode.updateMetadata(ctx); err != nil {
|
|
return 0, offset, err
|
|
}
|
|
}
|
|
|
|
// We need to hold the metadataMu *while* writing to a regular file.
|
|
d.inode.metadataMu.Lock()
|
|
defer d.inode.metadataMu.Unlock()
|
|
|
|
// Set offset to file size if the regular file was opened with O_APPEND.
|
|
if fd.vfsfd.StatusFlags()&linux.O_APPEND != 0 {
|
|
// Holding d.inode.metadataMu is sufficient for reading d.inode.size.
|
|
offset = int64(d.inode.size.RacyLoad())
|
|
}
|
|
limit, err := vfs.CheckLimit(ctx, offset, src.NumBytes())
|
|
if err != nil {
|
|
return 0, offset, err
|
|
}
|
|
src = src.TakeFirst64(limit)
|
|
}
|
|
|
|
if d.inode.cachedMetadataAuthoritative() {
|
|
if fd.isRegularFile {
|
|
d.touchCMtimeLocked()
|
|
} else {
|
|
d.touchCMtime()
|
|
}
|
|
}
|
|
|
|
// handleReadWriter always writes to the remote file. So O_DIRECT is
|
|
// effectively always set. Invalidate pages in d.inode.mappings that have been
|
|
// written to.
|
|
pgstart := hostarch.PageRoundDown(uint64(offset))
|
|
pgend, ok := hostarch.PageRoundUp(uint64(offset + src.NumBytes()))
|
|
if !ok {
|
|
return 0, offset, linuxerr.EINVAL
|
|
}
|
|
mr := memmap.MappableRange{pgstart, pgend}
|
|
d.inode.mapsMu.Lock()
|
|
d.inode.mappings.Invalidate(mr, memmap.InvalidateOpts{})
|
|
d.inode.mapsMu.Unlock()
|
|
|
|
rw := getHandleReadWriter(ctx, &fd.handle, offset)
|
|
n, err := src.CopyInTo(ctx, rw)
|
|
putHandleReadWriter(rw)
|
|
if n > 0 && fd.vfsfd.StatusFlags()&(linux.O_DSYNC|linux.O_SYNC) != 0 {
|
|
// Note that if syncing the remote file fails, then we can't guarantee that
|
|
// any data was actually written with the semantics of O_DSYNC or
|
|
// O_SYNC, so we return zero bytes written. Compare Linux's
|
|
// mm/filemap.c:generic_file_write_iter() =>
|
|
// include/linux/fs.h:generic_write_sync().
|
|
statusFlags := fd.vfsfd.StatusFlags()
|
|
dataOnly := (statusFlags&linux.O_SYNC) == 0 && (statusFlags&linux.O_DSYNC) != 0
|
|
if err := fd.sync(ctx, false /* forFilesystemSync */, dataOnly); err != nil {
|
|
return 0, offset, err
|
|
}
|
|
}
|
|
if linuxerr.Equals(linuxerr.EAGAIN, err) {
|
|
err = linuxerr.ErrWouldBlock
|
|
}
|
|
// Update offset if the offset is valid.
|
|
if offset >= 0 {
|
|
offset += n
|
|
}
|
|
// Update file size for regular files.
|
|
if fd.isRegularFile {
|
|
// d.inode.metadataMu is already locked at this point.
|
|
if uint64(offset) > d.inode.size.RacyLoad() {
|
|
d.inode.dataMu.Lock()
|
|
defer d.inode.dataMu.Unlock()
|
|
d.inode.size.Store(uint64(offset))
|
|
}
|
|
}
|
|
return int64(n), offset, err
|
|
}
|
|
|
|
// Write implements vfs.FileDescriptionImpl.Write.
|
|
func (fd *specialFileFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
|
if !fd.seekable {
|
|
return fd.PWrite(ctx, src, -1, opts)
|
|
}
|
|
|
|
fd.mu.Lock()
|
|
n, off, err := fd.pwrite(ctx, src, fd.off, opts)
|
|
fd.off = off
|
|
fd.mu.Unlock()
|
|
return n, err
|
|
}
|
|
|
|
// Seek implements vfs.FileDescriptionImpl.Seek.
|
|
func (fd *specialFileFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
|
if !fd.seekable {
|
|
return 0, linuxerr.ESPIPE
|
|
}
|
|
fd.mu.Lock()
|
|
defer fd.mu.Unlock()
|
|
newOffset, err := regularFileSeekLocked(ctx, fd.dentry(), fd.off, offset, whence)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
fd.off = newOffset
|
|
return newOffset, nil
|
|
}
|
|
|
|
// Sync implements vfs.FileDescriptionImpl.Sync.
|
|
func (fd *specialFileFD) Sync(ctx context.Context, opts vfs.SyncOptions) error {
|
|
return fd.sync(ctx, false /* forFilesystemSync */, opts.DataOnly)
|
|
}
|
|
|
|
func (fd *specialFileFD) sync(ctx context.Context, forFilesystemSync bool, dataOnly bool) error {
|
|
// Locks to ensure it didn't race with fd.Release().
|
|
fd.releaseMu.RLock()
|
|
defer fd.releaseMu.RUnlock()
|
|
|
|
var err error
|
|
if dataOnly {
|
|
err = fd.handle.syncData(ctx)
|
|
} else {
|
|
err = fd.handle.sync(ctx)
|
|
}
|
|
if err != nil {
|
|
if !forFilesystemSync {
|
|
return err
|
|
}
|
|
// Only return err if we can reasonably have expected sync to succeed
|
|
// (fd represents a regular file that was opened for writing).
|
|
if fd.isRegularFile && fd.vfsfd.IsWritable() {
|
|
return err
|
|
}
|
|
ctx.Debugf("gofer.specialFileFD.sync: syncing non-writable or non-regular-file FD failed: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap.
|
|
func (fd *specialFileFD) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error {
|
|
if fd.handle.fd < 0 || fd.filesystem().opts.forcePageCache {
|
|
return linuxerr.ENODEV
|
|
}
|
|
return vfs.GenericConfigureMMap(&fd.vfsfd, fd, opts)
|
|
}
|
|
|
|
// AddMapping implements memmap.Mappable.AddMapping.
|
|
func (fd *specialFileFD) AddMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) error {
|
|
fd.mmapFile.AddMapping(ar, offset)
|
|
d := fd.dentry()
|
|
d.inode.mapsMu.Lock()
|
|
defer d.inode.mapsMu.Unlock()
|
|
d.inode.mappings.AddMapping(ms, ar, offset, writable)
|
|
return nil
|
|
}
|
|
|
|
// RemoveMapping implements memmap.Mappable.RemoveMapping.
|
|
func (fd *specialFileFD) RemoveMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) {
|
|
fd.mmapFile.RemoveMapping(ar, offset)
|
|
d := fd.dentry()
|
|
d.inode.mapsMu.Lock()
|
|
defer d.inode.mapsMu.Unlock()
|
|
d.inode.mappings.RemoveMapping(ms, ar, offset, writable)
|
|
}
|
|
|
|
// CopyMapping implements memmap.Mappable.CopyMapping.
|
|
func (fd *specialFileFD) CopyMapping(ctx context.Context, ms memmap.MappingSpace, srcAR, dstAR hostarch.AddrRange, offset uint64, writable bool) error {
|
|
return fd.AddMapping(ctx, ms, dstAR, offset, writable)
|
|
}
|
|
|
|
// Translate implements memmap.Mappable.Translate.
|
|
func (fd *specialFileFD) Translate(ctx context.Context, required, optional memmap.MappableRange, at hostarch.AccessType) ([]memmap.Translation, error) {
|
|
mr := optional
|
|
if fd.filesystem().opts.limitHostFDTranslation {
|
|
mr = maxFillRange(required, optional)
|
|
}
|
|
return []memmap.Translation{
|
|
{
|
|
Source: mr,
|
|
File: &fd.mmapFile,
|
|
Offset: mr.Start,
|
|
Perms: hostarch.AnyAccess,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// InvalidateUnsavable implements memmap.Mappable.InvalidateUnsavable.
|
|
func (fd *specialFileFD) InvalidateUnsavable(ctx context.Context) error {
|
|
d := fd.dentry()
|
|
d.inode.mapsMu.Lock()
|
|
defer d.inode.mapsMu.Unlock()
|
|
d.inode.mappings.InvalidateAll(memmap.InvalidateOpts{})
|
|
return nil
|
|
}
|
|
|
|
func (fd *specialFileFD) updateMetadata(ctx context.Context) error {
|
|
d := fd.dentry()
|
|
d.inode.metadataMu.Lock()
|
|
defer d.inode.metadataMu.Unlock()
|
|
return d.inode.updateMetadataLocked(ctx, fd.handle)
|
|
}
|