mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add support for WritableSource in DynamicBytesFileDescriptionImpl
WritableSource is a convenience interface used for files that can be written to, e.g. /proc/net/ipv4/tpc_sack. It reads max of 4KB and only from offset 0 which should cover most cases. It can be extended as neeed. Updates #1195 PiperOrigin-RevId: 292056924
This commit is contained in:
committed by
gVisor bot
parent
3d046fef06
commit
396c574db2
@@ -108,12 +108,12 @@ func (fd *DynamicBytesFD) PRead(ctx context.Context, dst usermem.IOSequence, off
|
||||
|
||||
// Write implements vfs.FileDescriptionImpl.Write.
|
||||
func (fd *DynamicBytesFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
||||
return fd.FileDescriptionDefaultImpl.Write(ctx, src, opts)
|
||||
return fd.DynamicBytesFileDescriptionImpl.Write(ctx, src, opts)
|
||||
}
|
||||
|
||||
// PWrite implements vfs.FileDescriptionImpl.PWrite.
|
||||
func (fd *DynamicBytesFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
||||
return fd.FileDescriptionDefaultImpl.PWrite(ctx, src, offset, opts)
|
||||
return fd.DynamicBytesFileDescriptionImpl.PWrite(ctx, src, offset, opts)
|
||||
}
|
||||
|
||||
// Release implements vfs.FileDescriptionImpl.Release.
|
||||
|
||||
@@ -69,7 +69,7 @@ func newTasksInode(inoGen InoGenerator, k *kernel.Kernel, pidns *kernel.PIDNames
|
||||
"cpuinfo": newDentry(root, inoGen.NextIno(), 0444, newStaticFile(cpuInfoData(k))),
|
||||
//"filesystems": newDentry(root, inoGen.NextIno(), 0444, &filesystemsData{}),
|
||||
"loadavg": newDentry(root, inoGen.NextIno(), 0444, &loadavgData{}),
|
||||
"sys": newSysDir(root, inoGen),
|
||||
"sys": newSysDir(root, inoGen, k),
|
||||
"meminfo": newDentry(root, inoGen.NextIno(), 0444, &meminfoData{}),
|
||||
"mounts": kernfs.NewStaticSymlink(root, inoGen.NextIno(), "self/mounts"),
|
||||
"net": newNetDir(root, inoGen, k),
|
||||
|
||||
@@ -21,12 +21,16 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/inet"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
// newSysDir returns the dentry corresponding to /proc/sys directory.
|
||||
func newSysDir(root *auth.Credentials, inoGen InoGenerator) *kernfs.Dentry {
|
||||
func newSysDir(root *auth.Credentials, inoGen InoGenerator, k *kernel.Kernel) *kernfs.Dentry {
|
||||
return kernfs.NewStaticDir(root, inoGen.NextIno(), 0555, map[string]*kernfs.Dentry{
|
||||
"kernel": kernfs.NewStaticDir(root, inoGen.NextIno(), 0555, map[string]*kernfs.Dentry{
|
||||
"hostname": newDentry(root, inoGen.NextIno(), 0444, &hostnameData{}),
|
||||
@@ -38,18 +42,18 @@ func newSysDir(root *auth.Credentials, inoGen InoGenerator) *kernfs.Dentry {
|
||||
"mmap_min_addr": newDentry(root, inoGen.NextIno(), 0444, &mmapMinAddrData{}),
|
||||
"overcommit_memory": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("0\n")),
|
||||
}),
|
||||
"net": newSysNetDir(root, inoGen),
|
||||
"net": newSysNetDir(root, inoGen, k),
|
||||
})
|
||||
}
|
||||
|
||||
// newSysNetDir returns the dentry corresponding to /proc/sys/net directory.
|
||||
func newSysNetDir(root *auth.Credentials, inoGen InoGenerator) *kernfs.Dentry {
|
||||
return kernfs.NewStaticDir(root, inoGen.NextIno(), 0555, map[string]*kernfs.Dentry{
|
||||
"net": kernfs.NewStaticDir(root, inoGen.NextIno(), 0555, map[string]*kernfs.Dentry{
|
||||
func newSysNetDir(root *auth.Credentials, inoGen InoGenerator, k *kernel.Kernel) *kernfs.Dentry {
|
||||
var contents map[string]*kernfs.Dentry
|
||||
|
||||
if stack := k.NetworkStack(); stack != nil {
|
||||
contents = map[string]*kernfs.Dentry{
|
||||
"ipv4": kernfs.NewStaticDir(root, inoGen.NextIno(), 0555, map[string]*kernfs.Dentry{
|
||||
// Add tcp_sack.
|
||||
// TODO(gvisor.dev/issue/1195): tcp_sack allows write(2)
|
||||
// "tcp_sack": newTCPSackInode(ctx, msrc, s),
|
||||
"tcp_sack": newDentry(root, inoGen.NextIno(), 0644, &tcpSackData{stack: stack}),
|
||||
|
||||
// The following files are simple stubs until they are implemented in
|
||||
// netstack, most of these files are configuration related. We use the
|
||||
@@ -103,7 +107,11 @@ func newSysNetDir(root *auth.Credentials, inoGen InoGenerator) *kernfs.Dentry {
|
||||
"wmem_default": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("212992")),
|
||||
"wmem_max": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("212992")),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
return kernfs.NewStaticDir(root, inoGen.NextIno(), 0555, map[string]*kernfs.Dentry{
|
||||
"net": kernfs.NewStaticDir(root, inoGen.NextIno(), 0555, contents),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -141,3 +149,61 @@ func (*hostnameData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
buf.WriteString("\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// tcpSackData implements vfs.WritableDynamicBytesSource for
|
||||
// /proc/sys/net/tcp_sack.
|
||||
//
|
||||
// +stateify savable
|
||||
type tcpSackData struct {
|
||||
kernfs.DynamicBytesFile
|
||||
|
||||
stack inet.Stack `state:"wait"`
|
||||
enabled *bool
|
||||
}
|
||||
|
||||
var _ vfs.WritableDynamicBytesSource = (*tcpSackData)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.
|
||||
func (d *tcpSackData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
if d.enabled == nil {
|
||||
sack, err := d.stack.TCPSACKEnabled()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.enabled = &sack
|
||||
}
|
||||
|
||||
val := "0\n"
|
||||
if *d.enabled {
|
||||
// Technically, this is not quite compatible with Linux. Linux stores these
|
||||
// as an integer, so if you write "2" into tcp_sack, you should get 2 back.
|
||||
// Tough luck.
|
||||
val = "1\n"
|
||||
}
|
||||
buf.WriteString(val)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *tcpSackData) Write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) {
|
||||
if offset != 0 {
|
||||
// No need to handle partial writes thus far.
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
if src.NumBytes() == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Limit the amount of memory allocated.
|
||||
src = src.TakeFirst(usermem.PageSize - 1)
|
||||
|
||||
var v int32
|
||||
n, err := usermem.CopyInt32StringInVec(ctx, src.IO, src.Addrs, &v, src.Opts)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if d.enabled == nil {
|
||||
d.enabled = new(bool)
|
||||
}
|
||||
*d.enabled = v != 0
|
||||
return n, d.stack.SetTCPSACKEnabled(*d.enabled)
|
||||
}
|
||||
|
||||
@@ -192,21 +192,6 @@ func (DentryMetadataFileDescriptionImpl) SetStat(ctx context.Context, opts SetSt
|
||||
panic("illegal call to DentryMetadataFileDescriptionImpl.SetStat")
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
@@ -225,6 +210,30 @@ func (s *StaticData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WritableDynamicBytesSource extends DynamicBytesSource to allow writes to the
|
||||
// underlying source.
|
||||
type WritableDynamicBytesSource interface {
|
||||
DynamicBytesSource
|
||||
|
||||
// Write sends writes to the source.
|
||||
Write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
|
||||
// SetDataSource must be called exactly once on fd before first use.
|
||||
func (fd *DynamicBytesFileDescriptionImpl) SetDataSource(data DynamicBytesSource) {
|
||||
fd.data = data
|
||||
@@ -304,6 +313,43 @@ func (fd *DynamicBytesFileDescriptionImpl) Seek(ctx context.Context, offset int6
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
// Preconditions: fd.mu must be locked.
|
||||
func (fd *DynamicBytesFileDescriptionImpl) pwriteLocked(ctx context.Context, src usermem.IOSequence, offset int64, opts WriteOptions) (int64, error) {
|
||||
if opts.Flags&^(linux.RWF_HIPRI|linux.RWF_DSYNC|linux.RWF_SYNC) != 0 {
|
||||
return 0, syserror.EOPNOTSUPP
|
||||
}
|
||||
|
||||
writable, ok := fd.data.(WritableDynamicBytesSource)
|
||||
if !ok {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
n, err := writable.Write(ctx, src, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Invalidate cached data that might exist prior to this call.
|
||||
fd.buf.Reset()
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// PWrite implements FileDescriptionImpl.PWrite.
|
||||
func (fd *DynamicBytesFileDescriptionImpl) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts WriteOptions) (int64, error) {
|
||||
fd.mu.Lock()
|
||||
n, err := fd.pwriteLocked(ctx, src, offset, opts)
|
||||
fd.mu.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write implements FileDescriptionImpl.Write.
|
||||
func (fd *DynamicBytesFileDescriptionImpl) Write(ctx context.Context, src usermem.IOSequence, opts WriteOptions) (int64, error) {
|
||||
fd.mu.Lock()
|
||||
n, err := fd.pwriteLocked(ctx, src, fd.off, opts)
|
||||
fd.off += n
|
||||
fd.mu.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// GenericConfigureMMap may be used by most implementations of
|
||||
// FileDescriptionImpl.ConfigureMMap.
|
||||
func GenericConfigureMMap(fd *FileDescription, m memmap.Mappable, opts *memmap.MMapOpts) error {
|
||||
|
||||
@@ -35,61 +35,80 @@ type fileDescription struct {
|
||||
FileDescriptionDefaultImpl
|
||||
}
|
||||
|
||||
// genCountFD is a read-only FileDescriptionImpl representing a regular file
|
||||
// that contains the number of times its DynamicBytesSource.Generate()
|
||||
// genCount contains the number of times its DynamicBytesSource.Generate()
|
||||
// implementation has been called.
|
||||
type genCountFD struct {
|
||||
fileDescription
|
||||
DynamicBytesFileDescriptionImpl
|
||||
|
||||
type genCount struct {
|
||||
count uint64 // accessed using atomic memory ops
|
||||
}
|
||||
|
||||
func newGenCountFD(vfsObj *VirtualFilesystem) *FileDescription {
|
||||
// Generate implements DynamicBytesSource.Generate.
|
||||
func (g *genCount) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
fmt.Fprintf(buf, "%d", atomic.AddUint64(&g.count, 1))
|
||||
return nil
|
||||
}
|
||||
|
||||
type storeData struct {
|
||||
data string
|
||||
}
|
||||
|
||||
var _ WritableDynamicBytesSource = (*storeData)(nil)
|
||||
|
||||
// Generate implements DynamicBytesSource.
|
||||
func (d *storeData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
buf.WriteString(d.data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate implements WritableDynamicBytesSource.
|
||||
func (d *storeData) Write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) {
|
||||
buf := make([]byte, src.NumBytes())
|
||||
n, err := src.CopyIn(ctx, buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
d.data = string(buf[:n])
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// testFD is a read-only FileDescriptionImpl representing a regular file.
|
||||
type testFD struct {
|
||||
fileDescription
|
||||
DynamicBytesFileDescriptionImpl
|
||||
|
||||
data DynamicBytesSource
|
||||
}
|
||||
|
||||
func newTestFD(vfsObj *VirtualFilesystem, statusFlags uint32, data DynamicBytesSource) *FileDescription {
|
||||
vd := vfsObj.NewAnonVirtualDentry("genCountFD")
|
||||
defer vd.DecRef()
|
||||
var fd genCountFD
|
||||
fd.vfsfd.Init(&fd, 0 /* statusFlags */, vd.Mount(), vd.Dentry(), &FileDescriptionOptions{})
|
||||
fd.DynamicBytesFileDescriptionImpl.SetDataSource(&fd)
|
||||
var fd testFD
|
||||
fd.vfsfd.Init(&fd, statusFlags, vd.Mount(), vd.Dentry(), &FileDescriptionOptions{})
|
||||
fd.DynamicBytesFileDescriptionImpl.SetDataSource(data)
|
||||
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
|
||||
func (fd *testFD) Release() {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func (fd *testFD) 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 {
|
||||
func (fd *testFD) 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)
|
||||
|
||||
vfsObj := New() // vfs.New()
|
||||
fd := newGenCountFD(vfsObj)
|
||||
fd := newTestFD(vfsObj, linux.O_RDWR, &genCount{})
|
||||
defer fd.DecRef()
|
||||
|
||||
// The first read causes Generate to be called to fill the FD's buffer.
|
||||
@@ -130,4 +149,69 @@ func TestGenCountFD(t *testing.T) {
|
||||
if want := byte('3'); buf[0] != want {
|
||||
t.Errorf("PRead: got byte %c, wanted %c", buf[0], want)
|
||||
}
|
||||
|
||||
// Write and PWrite fails.
|
||||
if _, err := fd.Write(ctx, ioseq, WriteOptions{}); err != syserror.EINVAL {
|
||||
t.Errorf("Write: got err %v, wanted %v", err, syserror.EINVAL)
|
||||
}
|
||||
if _, err := fd.PWrite(ctx, ioseq, 0, WriteOptions{}); err != syserror.EINVAL {
|
||||
t.Errorf("Write: got err %v, wanted %v", err, syserror.EINVAL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWritable(t *testing.T) {
|
||||
ctx := contexttest.Context(t)
|
||||
|
||||
vfsObj := New() // vfs.New()
|
||||
fd := newTestFD(vfsObj, linux.O_RDWR, &storeData{data: "init"})
|
||||
defer fd.DecRef()
|
||||
|
||||
buf := make([]byte, 10)
|
||||
ioseq := usermem.BytesIOSequence(buf)
|
||||
if n, err := fd.Read(ctx, ioseq, ReadOptions{}); n != 4 && err != io.EOF {
|
||||
t.Fatalf("Read: got (%v, %v), wanted (4, EOF)", n, err)
|
||||
}
|
||||
if want := "init"; want == string(buf) {
|
||||
t.Fatalf("Read: got %v, wanted %v", string(buf), want)
|
||||
}
|
||||
|
||||
// Test PWrite.
|
||||
want := "write"
|
||||
writeIOSeq := usermem.BytesIOSequence([]byte(want))
|
||||
if n, err := fd.PWrite(ctx, writeIOSeq, 0, WriteOptions{}); int(n) != len(want) && err != nil {
|
||||
t.Errorf("PWrite: got err (%v, %v), wanted (%v, nil)", n, err, len(want))
|
||||
}
|
||||
if n, err := fd.PRead(ctx, ioseq, 0, ReadOptions{}); int(n) != len(want) && err != io.EOF {
|
||||
t.Fatalf("PRead: got (%v, %v), wanted (%v, EOF)", n, err, len(want))
|
||||
}
|
||||
if want == string(buf) {
|
||||
t.Fatalf("PRead: got %v, wanted %v", string(buf), want)
|
||||
}
|
||||
|
||||
// Test Seek to 0 followed by Write.
|
||||
want = "write2"
|
||||
writeIOSeq = usermem.BytesIOSequence([]byte(want))
|
||||
if n, err := fd.Seek(ctx, 0, linux.SEEK_SET); n != 0 && err != nil {
|
||||
t.Errorf("Seek: got err (%v, %v), wanted (0, nil)", n, err)
|
||||
}
|
||||
if n, err := fd.Write(ctx, writeIOSeq, WriteOptions{}); int(n) != len(want) && err != nil {
|
||||
t.Errorf("Write: got err (%v, %v), wanted (%v, nil)", n, err, len(want))
|
||||
}
|
||||
if n, err := fd.PRead(ctx, ioseq, 0, ReadOptions{}); int(n) != len(want) && err != io.EOF {
|
||||
t.Fatalf("PRead: got (%v, %v), wanted (%v, EOF)", n, err, len(want))
|
||||
}
|
||||
if want == string(buf) {
|
||||
t.Fatalf("PRead: got %v, wanted %v", string(buf), want)
|
||||
}
|
||||
|
||||
// Test failure if offset != 0.
|
||||
if n, err := fd.Seek(ctx, 1, linux.SEEK_SET); n != 0 && err != nil {
|
||||
t.Errorf("Seek: got err (%v, %v), wanted (0, nil)", n, err)
|
||||
}
|
||||
if n, err := fd.Write(ctx, writeIOSeq, WriteOptions{}); n != 0 && err != syserror.EINVAL {
|
||||
t.Errorf("Write: got err (%v, %v), wanted (0, EINVAL)", n, err)
|
||||
}
|
||||
if n, err := fd.PWrite(ctx, writeIOSeq, 2, WriteOptions{}); n != 0 && err != syserror.EINVAL {
|
||||
t.Errorf("PWrite: got err (%v, %v), wanted (0, EINVAL)", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user