mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add vfs.DynamicBytesFileDescriptionImpl.
This replaces fs/proc/seqfile for vfs2-based filesystems. PiperOrigin-RevId: 263254647
This commit is contained in:
@@ -18,6 +18,7 @@ go_library(
|
||||
"permissions.go",
|
||||
"resolving_path.go",
|
||||
"syscalls.go",
|
||||
"testutil.go",
|
||||
"vfs.go",
|
||||
],
|
||||
importpath = "gvisor.dev/gvisor/pkg/sentry/vfs",
|
||||
@@ -40,7 +41,16 @@ go_test(
|
||||
name = "vfs_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"file_description_impl_util_test.go",
|
||||
"mount_test.go",
|
||||
],
|
||||
embed = [":vfs"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/sentry/context",
|
||||
"//pkg/sentry/context/contexttest",
|
||||
"//pkg/sentry/kernel/auth",
|
||||
"//pkg/sentry/usermem",
|
||||
"//pkg/syserror",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
@@ -140,3 +144,106 @@ func (DirectoryFileDescriptionDefaultImpl) PWrite(ctx context.Context, src userm
|
||||
func (DirectoryFileDescriptionDefaultImpl) Write(ctx context.Context, src usermem.IOSequence, opts WriteOptions) (int64, error) {
|
||||
return 0, syserror.EISDIR
|
||||
}
|
||||
|
||||
// DynamicBytesFileDescriptionImpl may be embedded by implementations of
|
||||
// FileDescriptionImpl that represent read-only regular files whose contents
|
||||
// are backed by a bytes.Buffer that is regenerated when necessary, consistent
|
||||
// with Linux's fs/seq_file.c:single_open().
|
||||
//
|
||||
// DynamicBytesFileDescriptionImpl.SetDataSource() must be called before first
|
||||
// use.
|
||||
type DynamicBytesFileDescriptionImpl struct {
|
||||
FileDescriptionDefaultImpl
|
||||
|
||||
data DynamicBytesSource // immutable
|
||||
mu sync.Mutex // protects the following fields
|
||||
buf bytes.Buffer
|
||||
off int64
|
||||
lastRead int64 // offset at which the last Read, PRead, or Seek ended
|
||||
}
|
||||
|
||||
// DynamicBytesSource represents a data source for a
|
||||
// DynamicBytesFileDescriptionImpl.
|
||||
type DynamicBytesSource interface {
|
||||
// Generate writes the file's contents to buf.
|
||||
Generate(ctx context.Context, buf *bytes.Buffer) error
|
||||
}
|
||||
|
||||
// SetDataSource must be called exactly once on fd before first use.
|
||||
func (fd *DynamicBytesFileDescriptionImpl) SetDataSource(data DynamicBytesSource) {
|
||||
fd.data = data
|
||||
}
|
||||
|
||||
// Preconditions: fd.mu must be locked.
|
||||
func (fd *DynamicBytesFileDescriptionImpl) preadLocked(ctx context.Context, dst usermem.IOSequence, offset int64, opts *ReadOptions) (int64, error) {
|
||||
// Regenerate the buffer if it's empty, or before pread() at a new offset.
|
||||
// Compare fs/seq_file.c:seq_read() => traverse().
|
||||
switch {
|
||||
case offset != fd.lastRead:
|
||||
fd.buf.Reset()
|
||||
fallthrough
|
||||
case fd.buf.Len() == 0:
|
||||
if err := fd.data.Generate(ctx, &fd.buf); err != nil {
|
||||
fd.buf.Reset()
|
||||
// fd.off is not updated in this case.
|
||||
fd.lastRead = 0
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
bs := fd.buf.Bytes()
|
||||
if offset >= int64(len(bs)) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n, err := dst.CopyOut(ctx, bs[offset:])
|
||||
fd.lastRead = offset + int64(n)
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// PRead implements FileDescriptionImpl.PRead.
|
||||
func (fd *DynamicBytesFileDescriptionImpl) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts ReadOptions) (int64, error) {
|
||||
fd.mu.Lock()
|
||||
n, err := fd.preadLocked(ctx, dst, offset, &opts)
|
||||
fd.mu.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Read implements FileDescriptionImpl.Read.
|
||||
func (fd *DynamicBytesFileDescriptionImpl) Read(ctx context.Context, dst usermem.IOSequence, opts ReadOptions) (int64, error) {
|
||||
fd.mu.Lock()
|
||||
n, err := fd.preadLocked(ctx, dst, fd.off, &opts)
|
||||
fd.off += n
|
||||
fd.mu.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Seek implements FileDescriptionImpl.Seek.
|
||||
func (fd *DynamicBytesFileDescriptionImpl) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
||||
fd.mu.Lock()
|
||||
defer fd.mu.Unlock()
|
||||
switch whence {
|
||||
case linux.SEEK_SET:
|
||||
// Use offset as given.
|
||||
case linux.SEEK_CUR:
|
||||
offset += fd.off
|
||||
default:
|
||||
// fs/seq_file:seq_lseek() rejects SEEK_END etc.
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
if offset < 0 {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
if offset != fd.lastRead {
|
||||
// Regenerate the file's contents immediately. Compare
|
||||
// fs/seq_file.c:seq_lseek() => traverse().
|
||||
fd.buf.Reset()
|
||||
if err := fd.data.Generate(ctx, &fd.buf); err != nil {
|
||||
fd.buf.Reset()
|
||||
fd.off = 0
|
||||
fd.lastRead = 0
|
||||
return 0, err
|
||||
}
|
||||
fd.lastRead = offset
|
||||
}
|
||||
fd.off = offset
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context/contexttest"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// genCountFD is a read-only FileDescriptionImpl representing a regular file
|
||||
// that contains the number of times its DynamicBytesSource.Generate()
|
||||
// implementation has been called.
|
||||
type genCountFD struct {
|
||||
vfsfd FileDescription
|
||||
DynamicBytesFileDescriptionImpl
|
||||
|
||||
count uint64 // accessed using atomic memory ops
|
||||
}
|
||||
|
||||
func newGenCountFD(mnt *Mount, vfsd *Dentry) *FileDescription {
|
||||
var fd genCountFD
|
||||
fd.vfsfd.Init(&fd, mnt, vfsd)
|
||||
fd.DynamicBytesFileDescriptionImpl.SetDataSource(&fd)
|
||||
return &fd.vfsfd
|
||||
}
|
||||
|
||||
// Release implements FileDescriptionImpl.Release.
|
||||
func (fd *genCountFD) Release() {
|
||||
}
|
||||
|
||||
// StatusFlags implements FileDescriptionImpl.StatusFlags.
|
||||
func (fd *genCountFD) StatusFlags(ctx context.Context) (uint32, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// SetStatusFlags implements FileDescriptionImpl.SetStatusFlags.
|
||||
func (fd *genCountFD) SetStatusFlags(ctx context.Context, flags uint32) error {
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
// Stat implements FileDescriptionImpl.Stat.
|
||||
func (fd *genCountFD) Stat(ctx context.Context, opts StatOptions) (linux.Statx, error) {
|
||||
// Note that Statx.Mask == 0 in the return value.
|
||||
return linux.Statx{}, nil
|
||||
}
|
||||
|
||||
// SetStat implements FileDescriptionImpl.SetStat.
|
||||
func (fd *genCountFD) SetStat(ctx context.Context, opts SetStatOptions) error {
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
// Generate implements DynamicBytesSource.Generate.
|
||||
func (fd *genCountFD) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
fmt.Fprintf(buf, "%d", atomic.AddUint64(&fd.count, 1))
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestGenCountFD(t *testing.T) {
|
||||
ctx := contexttest.Context(t)
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
|
||||
vfsObj := New() // vfs.New()
|
||||
vfsObj.MustRegisterFilesystemType("testfs", FDTestFilesystemType{})
|
||||
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "testfs", &NewFilesystemOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create testfs root mount: %v", err)
|
||||
}
|
||||
vd := mntns.Root()
|
||||
defer vd.DecRef()
|
||||
|
||||
fd := newGenCountFD(vd.Mount(), vd.Dentry())
|
||||
defer fd.DecRef()
|
||||
|
||||
// The first read causes Generate to be called to fill the FD's buffer.
|
||||
buf := make([]byte, 2)
|
||||
ioseq := usermem.BytesIOSequence(buf)
|
||||
n, err := fd.Impl().Read(ctx, ioseq, ReadOptions{})
|
||||
if n != 1 || (err != nil && err != io.EOF) {
|
||||
t.Fatalf("first Read: got (%d, %v), wanted (1, nil or EOF)", n, err)
|
||||
}
|
||||
if want := byte('1'); buf[0] != want {
|
||||
t.Errorf("first Read: got byte %c, wanted %c", buf[0], want)
|
||||
}
|
||||
|
||||
// A second read without seeking is still at EOF.
|
||||
n, err = fd.Impl().Read(ctx, ioseq, ReadOptions{})
|
||||
if n != 0 || err != io.EOF {
|
||||
t.Fatalf("second Read: got (%d, %v), wanted (0, EOF)", n, err)
|
||||
}
|
||||
|
||||
// Seeking to the beginning of the file causes it to be regenerated.
|
||||
n, err = fd.Impl().Seek(ctx, 0, linux.SEEK_SET)
|
||||
if n != 0 || err != nil {
|
||||
t.Fatalf("Seek: got (%d, %v), wanted (0, nil)", n, err)
|
||||
}
|
||||
n, err = fd.Impl().Read(ctx, ioseq, ReadOptions{})
|
||||
if n != 1 || (err != nil && err != io.EOF) {
|
||||
t.Fatalf("Read after Seek: got (%d, %v), wanted (1, nil or EOF)", n, err)
|
||||
}
|
||||
if want := byte('2'); buf[0] != want {
|
||||
t.Errorf("Read after Seek: got byte %c, wanted %c", buf[0], want)
|
||||
}
|
||||
|
||||
// PRead at the beginning of the file also causes it to be regenerated.
|
||||
n, err = fd.Impl().PRead(ctx, ioseq, 0, ReadOptions{})
|
||||
if n != 1 || (err != nil && err != io.EOF) {
|
||||
t.Fatalf("PRead: got (%d, %v), wanted (1, nil or EOF)", n, err)
|
||||
}
|
||||
if want := byte('3'); buf[0] != want {
|
||||
t.Errorf("PRead: got byte %c, wanted %c", buf[0], want)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// 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 (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// FDTestFilesystemType is a test-only FilesystemType that produces Filesystems
|
||||
// for which all FilesystemImpl methods taking a path return EPERM. It is used
|
||||
// to produce Mounts and Dentries for testing of FileDescriptionImpls that do
|
||||
// not depend on their originating Filesystem.
|
||||
type FDTestFilesystemType struct{}
|
||||
|
||||
// FDTestFilesystem is a test-only FilesystemImpl produced by
|
||||
// FDTestFilesystemType.
|
||||
type FDTestFilesystem struct {
|
||||
vfsfs Filesystem
|
||||
}
|
||||
|
||||
// NewFilesystem implements FilesystemType.NewFilesystem.
|
||||
func (fstype FDTestFilesystemType) NewFilesystem(ctx context.Context, creds *auth.Credentials, source string, opts NewFilesystemOptions) (*Filesystem, *Dentry, error) {
|
||||
var fs FDTestFilesystem
|
||||
fs.vfsfs.Init(&fs)
|
||||
return &fs.vfsfs, fs.NewDentry(), nil
|
||||
}
|
||||
|
||||
// Release implements FilesystemImpl.Release.
|
||||
func (fs *FDTestFilesystem) Release() {
|
||||
}
|
||||
|
||||
// Sync implements FilesystemImpl.Sync.
|
||||
func (fs *FDTestFilesystem) Sync(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDentryAt implements FilesystemImpl.GetDentryAt.
|
||||
func (fs *FDTestFilesystem) GetDentryAt(ctx context.Context, rp *ResolvingPath, opts GetDentryOptions) (*Dentry, error) {
|
||||
return nil, syserror.EPERM
|
||||
}
|
||||
|
||||
// LinkAt implements FilesystemImpl.LinkAt.
|
||||
func (fs *FDTestFilesystem) LinkAt(ctx context.Context, rp *ResolvingPath, vd VirtualDentry) error {
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
// MkdirAt implements FilesystemImpl.MkdirAt.
|
||||
func (fs *FDTestFilesystem) MkdirAt(ctx context.Context, rp *ResolvingPath, opts MkdirOptions) error {
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
// MknodAt implements FilesystemImpl.MknodAt.
|
||||
func (fs *FDTestFilesystem) MknodAt(ctx context.Context, rp *ResolvingPath, opts MknodOptions) error {
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
// OpenAt implements FilesystemImpl.OpenAt.
|
||||
func (fs *FDTestFilesystem) OpenAt(ctx context.Context, rp *ResolvingPath, opts OpenOptions) (*FileDescription, error) {
|
||||
return nil, syserror.EPERM
|
||||
}
|
||||
|
||||
// ReadlinkAt implements FilesystemImpl.ReadlinkAt.
|
||||
func (fs *FDTestFilesystem) ReadlinkAt(ctx context.Context, rp *ResolvingPath) (string, error) {
|
||||
return "", syserror.EPERM
|
||||
}
|
||||
|
||||
// RenameAt implements FilesystemImpl.RenameAt.
|
||||
func (fs *FDTestFilesystem) RenameAt(ctx context.Context, rp *ResolvingPath, vd VirtualDentry, opts RenameOptions) error {
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
// RmdirAt implements FilesystemImpl.RmdirAt.
|
||||
func (fs *FDTestFilesystem) RmdirAt(ctx context.Context, rp *ResolvingPath) error {
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
// SetStatAt implements FilesystemImpl.SetStatAt.
|
||||
func (fs *FDTestFilesystem) SetStatAt(ctx context.Context, rp *ResolvingPath, opts SetStatOptions) error {
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
// StatAt implements FilesystemImpl.StatAt.
|
||||
func (fs *FDTestFilesystem) StatAt(ctx context.Context, rp *ResolvingPath, opts StatOptions) (linux.Statx, error) {
|
||||
return linux.Statx{}, syserror.EPERM
|
||||
}
|
||||
|
||||
// StatFSAt implements FilesystemImpl.StatFSAt.
|
||||
func (fs *FDTestFilesystem) StatFSAt(ctx context.Context, rp *ResolvingPath) (linux.Statfs, error) {
|
||||
return linux.Statfs{}, syserror.EPERM
|
||||
}
|
||||
|
||||
// SymlinkAt implements FilesystemImpl.SymlinkAt.
|
||||
func (fs *FDTestFilesystem) SymlinkAt(ctx context.Context, rp *ResolvingPath, target string) error {
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
// UnlinkAt implements FilesystemImpl.UnlinkAt.
|
||||
func (fs *FDTestFilesystem) UnlinkAt(ctx context.Context, rp *ResolvingPath) error {
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
type fdTestDentry struct {
|
||||
vfsd Dentry
|
||||
}
|
||||
|
||||
// NewDentry returns a new Dentry.
|
||||
func (fs *FDTestFilesystem) NewDentry() *Dentry {
|
||||
var d fdTestDentry
|
||||
d.vfsd.Init(&d)
|
||||
return &d.vfsd
|
||||
}
|
||||
|
||||
// IncRef implements DentryImpl.IncRef.
|
||||
func (d *fdTestDentry) IncRef(vfsfs *Filesystem) {
|
||||
}
|
||||
|
||||
// TryIncRef implements DentryImpl.TryIncRef.
|
||||
func (d *fdTestDentry) TryIncRef(vfsfs *Filesystem) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// DecRef implements DentryImpl.DecRef.
|
||||
func (d *fdTestDentry) DecRef(vfsfs *Filesystem) {
|
||||
}
|
||||
Reference in New Issue
Block a user