2019-04-29 21:25:05 +00:00
|
|
|
// Copyright 2018 The gVisor Authors.
|
2018-04-27 17:37:02 +00:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
2019-06-20 20:33:29 +00:00
|
|
|
// Package seqfile provides dynamic ordered files.
|
2018-04-27 17:37:02 +00:00
|
|
|
package seqfile
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
"sync"
|
|
|
|
|
2019-06-13 23:49:09 +00:00
|
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
|
|
"gvisor.dev/gvisor/pkg/sentry/context"
|
|
|
|
"gvisor.dev/gvisor/pkg/sentry/fs"
|
|
|
|
"gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
|
|
|
|
"gvisor.dev/gvisor/pkg/sentry/fs/proc/device"
|
|
|
|
ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
|
|
|
|
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
|
|
|
"gvisor.dev/gvisor/pkg/syserror"
|
|
|
|
"gvisor.dev/gvisor/pkg/waiter"
|
2018-04-27 17:37:02 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// SeqHandle is a helper handle to seek in the file.
|
|
|
|
type SeqHandle interface{}
|
|
|
|
|
|
|
|
// SeqData holds the data for one unit in the file.
|
2018-08-02 17:41:44 +00:00
|
|
|
//
|
|
|
|
// +stateify savable
|
2018-04-27 17:37:02 +00:00
|
|
|
type SeqData struct {
|
|
|
|
// The data to be returned to the user.
|
|
|
|
Buf []byte
|
|
|
|
|
|
|
|
// A seek handle used to find the next valid unit in ReadSeqFiledata.
|
|
|
|
Handle SeqHandle
|
|
|
|
}
|
|
|
|
|
|
|
|
// SeqSource is a data source for a SeqFile file.
|
|
|
|
type SeqSource interface {
|
|
|
|
// NeedsUpdate returns true if the consumer of SeqData should call
|
|
|
|
// ReadSeqFileData again. Generation is the generation returned by
|
|
|
|
// ReadSeqFile or 0.
|
|
|
|
NeedsUpdate(generation int64) bool
|
|
|
|
|
|
|
|
// Returns a slice of SeqData ordered by unit and the current
|
|
|
|
// generation. The first entry in the slice is greater than the handle.
|
|
|
|
// If handle is nil then all known records are returned. Generation
|
|
|
|
// must always be greater than 0.
|
2018-06-26 18:34:16 +00:00
|
|
|
ReadSeqFileData(ctx context.Context, handle SeqHandle) ([]SeqData, int64)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SeqGenerationCounter is a counter to keep track if the SeqSource should be
|
|
|
|
// updated. SeqGenerationCounter is not thread-safe and should be protected
|
|
|
|
// with a mutex.
|
|
|
|
type SeqGenerationCounter struct {
|
|
|
|
// The generation that the SeqData is at.
|
|
|
|
generation int64
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetGeneration sets the generation to the new value, be careful to not set it
|
|
|
|
// to a value less than current.
|
|
|
|
func (s *SeqGenerationCounter) SetGeneration(generation int64) {
|
|
|
|
s.generation = generation
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update increments the current generation.
|
|
|
|
func (s *SeqGenerationCounter) Update() {
|
|
|
|
s.generation++
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generation returns the current generation counter.
|
|
|
|
func (s *SeqGenerationCounter) Generation() int64 {
|
|
|
|
return s.generation
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsCurrent returns whether the given generation is current or not.
|
|
|
|
func (s *SeqGenerationCounter) IsCurrent(generation int64) bool {
|
|
|
|
return s.Generation() == generation
|
|
|
|
}
|
|
|
|
|
|
|
|
// SeqFile is used to provide dynamic files that can be ordered by record.
|
2018-08-02 17:41:44 +00:00
|
|
|
//
|
|
|
|
// +stateify savable
|
2018-04-27 17:37:02 +00:00
|
|
|
type SeqFile struct {
|
2019-01-15 04:33:29 +00:00
|
|
|
fsutil.InodeGenericChecker `state:"nosave"`
|
|
|
|
fsutil.InodeNoopRelease `state:"nosave"`
|
|
|
|
fsutil.InodeNoopWriteOut `state:"nosave"`
|
2019-05-09 22:34:44 +00:00
|
|
|
fsutil.InodeNotAllocatable `state:"nosave"`
|
2019-01-15 04:33:29 +00:00
|
|
|
fsutil.InodeNotDirectory `state:"nosave"`
|
|
|
|
fsutil.InodeNotMappable `state:"nosave"`
|
|
|
|
fsutil.InodeNotSocket `state:"nosave"`
|
|
|
|
fsutil.InodeNotSymlink `state:"nosave"`
|
|
|
|
fsutil.InodeNotTruncatable `state:"nosave"`
|
|
|
|
fsutil.InodeVirtual `state:"nosave"`
|
|
|
|
|
|
|
|
fsutil.InodeSimpleExtendedAttributes
|
|
|
|
fsutil.InodeSimpleAttributes
|
2018-04-27 17:37:02 +00:00
|
|
|
|
|
|
|
// mu protects the fields below.
|
|
|
|
mu sync.Mutex `state:"nosave"`
|
|
|
|
|
|
|
|
SeqSource
|
|
|
|
|
|
|
|
source []SeqData
|
|
|
|
generation int64
|
|
|
|
lastRead int64
|
|
|
|
}
|
|
|
|
|
2019-01-15 04:33:29 +00:00
|
|
|
var _ fs.InodeOperations = (*SeqFile)(nil)
|
|
|
|
|
2018-04-27 17:37:02 +00:00
|
|
|
// NewSeqFile returns a seqfile suitable for use by external consumers.
|
|
|
|
func NewSeqFile(ctx context.Context, source SeqSource) *SeqFile {
|
2019-01-15 04:33:29 +00:00
|
|
|
return &SeqFile{
|
|
|
|
InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, fs.RootOwner, fs.FilePermsFromMode(0444), linux.PROC_SUPER_MAGIC),
|
|
|
|
SeqSource: source,
|
|
|
|
}
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewSeqFileInode returns an Inode with SeqFile InodeOperations.
|
|
|
|
func NewSeqFileInode(ctx context.Context, source SeqSource, msrc *fs.MountSource) *fs.Inode {
|
|
|
|
iops := NewSeqFile(ctx, source)
|
|
|
|
sattr := fs.StableAttr{
|
|
|
|
DeviceID: device.ProcDevice.DeviceID(),
|
|
|
|
InodeID: device.ProcDevice.NextIno(),
|
|
|
|
BlockSize: usermem.PageSize,
|
|
|
|
Type: fs.SpecialFile,
|
|
|
|
}
|
2019-06-14 01:39:43 +00:00
|
|
|
return fs.NewInode(ctx, iops, msrc, sattr)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// UnstableAttr returns unstable attributes of the SeqFile.
|
|
|
|
func (s *SeqFile) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) {
|
2019-01-15 04:33:29 +00:00
|
|
|
uattr, err := s.InodeSimpleAttributes.UnstableAttr(ctx, inode)
|
|
|
|
if err != nil {
|
|
|
|
return fs.UnstableAttr{}, err
|
|
|
|
}
|
2018-04-27 17:37:02 +00:00
|
|
|
uattr.ModificationTime = ktime.NowFromContext(ctx)
|
|
|
|
return uattr, nil
|
|
|
|
}
|
|
|
|
|
2019-01-15 04:33:29 +00:00
|
|
|
// GetFile implements fs.InodeOperations.GetFile.
|
|
|
|
func (s *SeqFile) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
|
|
|
|
return fs.NewFile(ctx, dirent, flags, &seqFileOperations{seqFile: s}), nil
|
|
|
|
}
|
|
|
|
|
2018-04-27 17:37:02 +00:00
|
|
|
// findIndexAndOffset finds the unit that corresponds to a certain offset.
|
|
|
|
// Returns the unit and the offset within the unit. If there are not enough
|
|
|
|
// units len(data) and leftover offset is returned.
|
|
|
|
func findIndexAndOffset(data []SeqData, offset int64) (int, int64) {
|
|
|
|
for i, buf := range data {
|
|
|
|
l := int64(len(buf.Buf))
|
|
|
|
if offset < l {
|
|
|
|
return i, offset
|
|
|
|
}
|
|
|
|
offset -= l
|
|
|
|
}
|
|
|
|
return len(data), offset
|
|
|
|
}
|
|
|
|
|
2019-01-15 04:33:29 +00:00
|
|
|
// updateSourceLocked requires that s.mu is held.
|
|
|
|
func (s *SeqFile) updateSourceLocked(ctx context.Context, record int) {
|
|
|
|
var h SeqHandle
|
|
|
|
if record == 0 {
|
|
|
|
h = nil
|
|
|
|
} else {
|
|
|
|
h = s.source[record-1].Handle
|
|
|
|
}
|
|
|
|
// Save what we have previously read.
|
|
|
|
s.source = s.source[:record]
|
|
|
|
var newSource []SeqData
|
|
|
|
newSource, s.generation = s.SeqSource.ReadSeqFileData(ctx, h)
|
|
|
|
s.source = append(s.source, newSource...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// seqFileOperations implements fs.FileOperations.
|
|
|
|
//
|
|
|
|
// +stateify savable
|
|
|
|
type seqFileOperations struct {
|
2019-04-11 07:41:42 +00:00
|
|
|
fsutil.FileGenericSeek `state:"nosave"`
|
|
|
|
fsutil.FileNoIoctl `state:"nosave"`
|
|
|
|
fsutil.FileNoMMap `state:"nosave"`
|
2019-05-21 22:17:05 +00:00
|
|
|
fsutil.FileNoSplice `state:"nosave"`
|
2019-04-11 07:41:42 +00:00
|
|
|
fsutil.FileNoopFlush `state:"nosave"`
|
|
|
|
fsutil.FileNoopFsync `state:"nosave"`
|
|
|
|
fsutil.FileNoopRelease `state:"nosave"`
|
|
|
|
fsutil.FileNotDirReaddir `state:"nosave"`
|
|
|
|
fsutil.FileUseInodeUnstableAttr `state:"nosave"`
|
2019-05-09 22:34:44 +00:00
|
|
|
waiter.AlwaysReady `state:"nosave"`
|
2019-01-15 04:33:29 +00:00
|
|
|
|
|
|
|
seqFile *SeqFile
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ fs.FileOperations = (*seqFileOperations)(nil)
|
|
|
|
|
|
|
|
// Write implements fs.FileOperations.Write.
|
|
|
|
func (*seqFileOperations) Write(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) {
|
|
|
|
return 0, syserror.EACCES
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read implements fs.FileOperations.Read.
|
|
|
|
func (sfo *seqFileOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) {
|
|
|
|
sfo.seqFile.mu.Lock()
|
|
|
|
defer sfo.seqFile.mu.Unlock()
|
2018-04-27 17:37:02 +00:00
|
|
|
|
2019-01-15 04:33:29 +00:00
|
|
|
sfo.seqFile.NotifyAccess(ctx)
|
|
|
|
defer func() { sfo.seqFile.lastRead = offset }()
|
2018-04-27 17:37:02 +00:00
|
|
|
|
|
|
|
updated := false
|
|
|
|
|
|
|
|
// Try to find where we should start reading this file.
|
2019-01-15 04:33:29 +00:00
|
|
|
i, recordOffset := findIndexAndOffset(sfo.seqFile.source, offset)
|
|
|
|
if i == len(sfo.seqFile.source) {
|
2018-04-27 17:37:02 +00:00
|
|
|
// Ok, we're at EOF. Let's first check to see if there might be
|
|
|
|
// more data available to us. If there is more data, add it to
|
|
|
|
// the end and try reading again.
|
2019-01-15 04:33:29 +00:00
|
|
|
if !sfo.seqFile.SeqSource.NeedsUpdate(sfo.seqFile.generation) {
|
2018-04-27 17:37:02 +00:00
|
|
|
return 0, io.EOF
|
|
|
|
}
|
2019-01-15 04:33:29 +00:00
|
|
|
oldLen := len(sfo.seqFile.source)
|
|
|
|
sfo.seqFile.updateSourceLocked(ctx, len(sfo.seqFile.source))
|
2018-04-27 17:37:02 +00:00
|
|
|
updated = true
|
|
|
|
// We know that we had consumed everything up until this point
|
|
|
|
// so we search in the new slice instead of starting over.
|
2019-01-15 04:33:29 +00:00
|
|
|
i, recordOffset = findIndexAndOffset(sfo.seqFile.source[oldLen:], recordOffset)
|
2018-04-27 17:37:02 +00:00
|
|
|
i += oldLen
|
|
|
|
// i is at most the length of the slice which is
|
2019-01-15 04:33:29 +00:00
|
|
|
// len(sfo.seqFile.source) - oldLen. So at most i will be equal to
|
|
|
|
// len(sfo.seqFile.source).
|
|
|
|
if i == len(sfo.seqFile.source) {
|
2018-04-27 17:37:02 +00:00
|
|
|
return 0, io.EOF
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var done int64
|
|
|
|
// We're reading parts of a record, finish reading the current object
|
|
|
|
// before continuing on to the next. We don't refresh our data source
|
|
|
|
// before this record is completed.
|
|
|
|
if recordOffset != 0 {
|
2019-01-15 04:33:29 +00:00
|
|
|
n, err := dst.CopyOut(ctx, sfo.seqFile.source[i].Buf[recordOffset:])
|
2018-04-27 17:37:02 +00:00
|
|
|
done += int64(n)
|
|
|
|
dst = dst.DropFirst(n)
|
|
|
|
if dst.NumBytes() == 0 || err != nil {
|
|
|
|
return done, err
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next/New unit, update the source file if necessary. Make an extra
|
|
|
|
// check to see if we've seeked backwards and if so always update our
|
|
|
|
// data source.
|
2019-01-15 04:33:29 +00:00
|
|
|
if !updated && (sfo.seqFile.SeqSource.NeedsUpdate(sfo.seqFile.generation) || sfo.seqFile.lastRead > offset) {
|
|
|
|
sfo.seqFile.updateSourceLocked(ctx, i)
|
2018-04-27 17:37:02 +00:00
|
|
|
// recordOffset is 0 here and we won't update records behind the
|
|
|
|
// current one so recordOffset is still 0 even though source
|
|
|
|
// just got updated. Just read the next record.
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finish by reading all the available data.
|
2019-01-15 04:33:29 +00:00
|
|
|
for _, buf := range sfo.seqFile.source[i:] {
|
2018-04-27 17:37:02 +00:00
|
|
|
n, err := dst.CopyOut(ctx, buf.Buf)
|
|
|
|
done += int64(n)
|
|
|
|
dst = dst.DropFirst(n)
|
|
|
|
if dst.NumBytes() == 0 || err != nil {
|
|
|
|
return done, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the file shrank (entries not yet read were removed above)
|
|
|
|
// while we tried to read we can end up with nothing read.
|
|
|
|
if done == 0 && dst.NumBytes() != 0 {
|
|
|
|
return 0, io.EOF
|
|
|
|
}
|
|
|
|
return done, nil
|
|
|
|
}
|