mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
Regular file reads copy data out of the image's shared mapping. When the image is backed by slow storage such as a FUSE mount, walking the mapping faults it in one page at a time, turning a large read into many small, largely serialized requests to the FUSE server. This makes erofs much slower than fsimpl/gofer for large reads on such backends. On those filesystems, read the data directly from the image file descriptor with preadv2(2) instead, so the host serves a large read with a single syscall and its own readahead, matching how fsimpl/gofer reads from host FDs. The mapping copy is kept everywhere else, since it avoids a syscall and is faster when the page cache is warm. The backing filesystem is detected at mount time via fstatfs(2); currently only FUSE opts into FD reads. Inode.Data, which returned the data as a mapping-backed BlockSeq, is replaced by Inode.DataRanges, which returns the image byte ranges that hold the inode's data.
260 lines
7.2 KiB
Go
260 lines
7.2 KiB
Go
// Copyright 2023 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 erofs
|
|
|
|
import (
|
|
"io"
|
|
"sync"
|
|
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
"gvisor.dev/gvisor/pkg/context"
|
|
"gvisor.dev/gvisor/pkg/erofs"
|
|
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
|
"gvisor.dev/gvisor/pkg/hostarch"
|
|
"gvisor.dev/gvisor/pkg/log"
|
|
"gvisor.dev/gvisor/pkg/safemem"
|
|
"gvisor.dev/gvisor/pkg/sentry/hostfd"
|
|
"gvisor.dev/gvisor/pkg/sentry/memmap"
|
|
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
|
"gvisor.dev/gvisor/pkg/usermem"
|
|
)
|
|
|
|
// +stateify savable
|
|
type regularFileFD struct {
|
|
fileDescription
|
|
|
|
// offMu protects off.
|
|
offMu sync.Mutex `state:"nosave"`
|
|
|
|
// off is the file offset.
|
|
// +checklocks:offMu
|
|
off int64
|
|
}
|
|
|
|
// PRead implements vfs.FileDescriptionImpl.PRead.
|
|
func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
|
if 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 dst.NumBytes() == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
inode := fd.inode()
|
|
ranges, err := inode.DataRanges()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
r := ®ularFileReader{
|
|
image: inode.fs.image,
|
|
ranges: ranges,
|
|
off: uint64(offset),
|
|
useRead: inode.fs.useReadForIO,
|
|
}
|
|
return dst.CopyOutFrom(ctx, r)
|
|
}
|
|
|
|
type regularFileReader struct {
|
|
image *erofs.Image
|
|
ranges [2]erofs.FileRange
|
|
off uint64
|
|
useRead bool
|
|
}
|
|
|
|
// ReadToBlocks implements safemem.Reader.ReadToBlocks.
|
|
func (r *regularFileReader) ReadToBlocks(dsts safemem.BlockSeq) (uint64, error) {
|
|
// Find the range containing the current offset. A read must not span the
|
|
// discontinuity between ranges in the image, so it is clamped to the end
|
|
// of the containing range.
|
|
off := r.off
|
|
for _, rng := range r.ranges {
|
|
if off < rng.Size {
|
|
dsts = dsts.TakeFirst64(rng.Size - off)
|
|
imageOff := rng.Off + off
|
|
var (
|
|
n uint64
|
|
err error
|
|
)
|
|
if r.useRead {
|
|
n, err = hostfd.Preadv2(int32(r.image.FD()), dsts, int64(imageOff), 0 /* flags */)
|
|
} else {
|
|
src := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(rng.Bytes)).DropFirst64(off)
|
|
n, err = safemem.CopySeq(dsts, src)
|
|
}
|
|
r.off += n
|
|
return n, err
|
|
}
|
|
off -= rng.Size
|
|
}
|
|
return 0, io.EOF
|
|
}
|
|
|
|
// Read implements vfs.FileDescriptionImpl.Read.
|
|
func (fd *regularFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
|
fd.offMu.Lock()
|
|
n, err := fd.PRead(ctx, dst, fd.off, opts)
|
|
fd.off += n
|
|
fd.offMu.Unlock()
|
|
return n, err
|
|
}
|
|
|
|
// PWrite implements vfs.FileDescriptionImpl.PWrite.
|
|
func (fd *regularFileFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
|
return 0, linuxerr.EROFS
|
|
}
|
|
|
|
// Write implements vfs.FileDescriptionImpl.Write.
|
|
func (fd *regularFileFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
|
return 0, linuxerr.EROFS
|
|
}
|
|
|
|
// Seek implements vfs.FileDescriptionImpl.Seek.
|
|
func (fd *regularFileFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
|
fd.offMu.Lock()
|
|
defer fd.offMu.Unlock()
|
|
switch whence {
|
|
case linux.SEEK_SET:
|
|
// use offset as specified
|
|
case linux.SEEK_CUR:
|
|
offset += fd.off
|
|
case linux.SEEK_END:
|
|
offset += int64(fd.inode().Size())
|
|
default:
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
if offset < 0 {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
fd.off = offset
|
|
return offset, nil
|
|
}
|
|
|
|
// ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap.
|
|
func (fd *regularFileFD) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error {
|
|
if opts.MaxPerms.Write && !opts.Private {
|
|
return linuxerr.EINVAL
|
|
}
|
|
return vfs.GenericConfigureMMap(&fd.vfsfd, fd.inode(), opts)
|
|
}
|
|
|
|
// AddMapping implements memmap.Mappable.AddMapping.
|
|
func (i *inode) AddMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) error {
|
|
i.mapsMu.Lock()
|
|
i.mappings.AddMapping(ms, ar, offset, writable)
|
|
i.mapsMu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// RemoveMapping implements memmap.Mappable.RemoveMapping.
|
|
func (i *inode) RemoveMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) {
|
|
i.mapsMu.Lock()
|
|
i.mappings.RemoveMapping(ms, ar, offset, writable)
|
|
i.mapsMu.Unlock()
|
|
}
|
|
|
|
// CopyMapping implements memmap.Mappable.CopyMapping.
|
|
func (i *inode) CopyMapping(ctx context.Context, ms memmap.MappingSpace, srcAR, dstAR hostarch.AddrRange, offset uint64, writable bool) error {
|
|
i.AddMapping(ctx, ms, dstAR, offset, writable)
|
|
return nil
|
|
}
|
|
|
|
// Translate implements memmap.Mappable.Translate.
|
|
func (i *inode) Translate(ctx context.Context, required, optional memmap.MappableRange, at hostarch.AccessType) ([]memmap.Translation, error) {
|
|
pgend, _ := hostarch.PageRoundUp(i.Size())
|
|
if required.End > pgend {
|
|
if required.Start >= pgend {
|
|
return nil, &memmap.BusError{io.EOF}
|
|
}
|
|
required.End = pgend
|
|
}
|
|
if optional.End > pgend {
|
|
optional.End = pgend
|
|
}
|
|
if at.Write {
|
|
// This shouldn't be possible due to the check in ConfigureMMap().
|
|
inodeTranslateWriteWarnOnce.Do(func() {
|
|
log.Traceback("erofs.inode.Translate: unexpected access type %v", at)
|
|
})
|
|
return nil, &memmap.BusError{linuxerr.EROFS}
|
|
}
|
|
offset, err := i.DataOffset()
|
|
if err != nil {
|
|
return nil, &memmap.BusError{err}
|
|
}
|
|
mr := optional
|
|
return []memmap.Translation{
|
|
{
|
|
Source: mr,
|
|
File: &i.fs.mf,
|
|
Offset: mr.Start + offset,
|
|
Perms: hostarch.ReadExecute,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
var inodeTranslateWriteWarnOnce sync.Once
|
|
|
|
// InvalidateUnsavable implements memmap.Mappable.InvalidateUnsavable.
|
|
func (i *inode) InvalidateUnsavable(ctx context.Context) error {
|
|
i.mapsMu.Lock()
|
|
i.mappings.InvalidateAll(memmap.InvalidateOpts{})
|
|
i.mapsMu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// +stateify savable
|
|
type imageMemmapFile struct {
|
|
memmap.DefaultMemoryType
|
|
memmap.NoBufferedIOFallback
|
|
|
|
image *erofs.Image
|
|
}
|
|
|
|
// IncRef implements memmap.File.IncRef.
|
|
func (mf *imageMemmapFile) IncRef(fr memmap.FileRange, memCgID uint32) {}
|
|
|
|
// DecRef implements memmap.File.DecRef.
|
|
func (mf *imageMemmapFile) DecRef(fr memmap.FileRange) {}
|
|
|
|
// MapInternal implements memmap.File.MapInternal.
|
|
func (mf *imageMemmapFile) MapInternal(fr memmap.FileRange, at hostarch.AccessType) (safemem.BlockSeq, error) {
|
|
if at.Write {
|
|
return safemem.BlockSeq{}, &memmap.BusError{linuxerr.EROFS}
|
|
}
|
|
bytes, err := mf.image.BytesAt(fr.Start, fr.Length())
|
|
if err != nil {
|
|
return safemem.BlockSeq{}, &memmap.BusError{err}
|
|
}
|
|
return safemem.BlockSeqOf(safemem.BlockFromSafeSlice(bytes)), nil
|
|
}
|
|
|
|
// DataFD implements memmap.File.DataFD.
|
|
func (mf *imageMemmapFile) DataFD(fr memmap.FileRange) (int, error) {
|
|
return mf.FD(), nil
|
|
}
|
|
|
|
// FD implements memmap.File.FD.
|
|
func (mf *imageMemmapFile) FD() int {
|
|
return mf.image.FD()
|
|
}
|