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:
Fabricio Voznika
2020-01-28 18:31:28 -08:00
committed by gVisor bot
parent 3d046fef06
commit 396c574db2
5 changed files with 252 additions and 56 deletions
@@ -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.
+1 -1
View File
@@ -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),
+75 -9
View File
@@ -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)
}
+61 -15
View File
@@ -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 {
+113 -29
View File
@@ -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)
}
}