Clean up pipe internals and add fcntl support

Pipe internals are made more efficient by avoiding garbage collection.
A pool is now used that can be shared by all pipes, and buffers are
chained via an intrusive list. The documentation for pipe structures
and methods is also simplified and clarified.

The pipe tests are now parameterized, so that they are run on all
different variants (named pipes, small buffers, default buffers).

The pipe buffer sizes are exposed by fcntl, which is now supported
by this change. A size change test has been added to the suite.

These new tests uncovered a bug regarding the semantics of open
named pipes with O_NONBLOCK, which is also fixed by this CL. This
fix also addresses the lack of the O_LARGEFILE flag for named pipes.

PiperOrigin-RevId: 249375888
Change-Id: I48e61e9c868aedb0cadda2dff33f09a560dee773
This commit is contained in:
Adin Scannell
2019-05-21 20:12:27 -07:00
committed by Shentubot
parent c8857f7269
commit ae1bb08871
15 changed files with 844 additions and 494 deletions
+3 -1
View File
@@ -17,7 +17,6 @@ package linux
// Comands from linux/fcntl.h.
const (
F_DUPFD = 0
F_DUPFD_CLOEXEC = 1030
F_GETFD = 1
F_GETFL = 3
F_GETOWN = 9
@@ -26,6 +25,9 @@ const (
F_SETLK = 6
F_SETLKW = 7
F_SETOWN = 8
F_DUPFD_CLOEXEC = 1024 + 6
F_SETPIPE_SZ = 1024 + 7
F_GETPIPE_SZ = 1024 + 8
)
// Flags for fcntl.
+5 -3
View File
@@ -10,16 +10,16 @@ go_template_instance(
prefix = "buffer",
template = "//pkg/ilist:generic_list",
types = {
"Element": "*Buffer",
"Linker": "*Buffer",
"Element": "*buffer",
"Linker": "*buffer",
},
)
go_library(
name = "pipe",
srcs = [
"buffer.go",
"buffer_list.go",
"buffers.go",
"device.go",
"node.go",
"pipe.go",
@@ -37,6 +37,7 @@ go_library(
"//pkg/sentry/device",
"//pkg/sentry/fs",
"//pkg/sentry/fs/fsutil",
"//pkg/sentry/safemem",
"//pkg/sentry/usermem",
"//pkg/syserror",
"//pkg/waiter",
@@ -47,6 +48,7 @@ go_test(
name = "pipe_test",
size = "small",
srcs = [
"buffer_test.go",
"node_test.go",
"pipe_test.go",
],
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2018 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 pipe
import (
"sync"
"gvisor.googlesource.com/gvisor/pkg/sentry/safemem"
)
// buffer encapsulates a queueable byte buffer.
//
// Note that the total size is slightly less than two pages. This
// is done intentionally to ensure that the buffer object aligns
// with runtime internals. We have no hard size or alignment
// requirements. This two page size will effectively minimize
// internal fragmentation, but still have a large enough chunk
// to limit excessive segmentation.
//
// +stateify savable
type buffer struct {
data [8144]byte
read int
write int
bufferEntry
}
// Reset resets internal data.
//
// This must be called before use.
func (b *buffer) Reset() {
b.read = 0
b.write = 0
}
// Empty indicates the buffer is empty.
//
// This indicates there is no data left to read.
func (b *buffer) Empty() bool {
return b.read == b.write
}
// Full indicates the buffer is full.
//
// This indicates there is no capacity left to write.
func (b *buffer) Full() bool {
return b.write == len(b.data)
}
// WriteFromBlocks implements safemem.Writer.WriteFromBlocks.
func (b *buffer) WriteFromBlocks(srcs safemem.BlockSeq) (uint64, error) {
dst := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(b.data[b.write:]))
n, err := safemem.CopySeq(dst, srcs)
b.write += int(n)
return n, err
}
// ReadToBlocks implements safemem.Reader.ReadToBlocks.
func (b *buffer) ReadToBlocks(dsts safemem.BlockSeq) (uint64, error) {
src := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(b.data[b.read:b.write]))
n, err := safemem.CopySeq(dsts, src)
b.read += int(n)
return n, err
}
// bufferPool is a pool for buffers.
var bufferPool = sync.Pool{
New: func() interface{} {
return new(buffer)
},
}
// newBuffer grabs a new buffer from the pool.
func newBuffer() *buffer {
b := bufferPool.Get().(*buffer)
b.Reset()
return b
}
+32
View File
@@ -0,0 +1,32 @@
// 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 pipe
import (
"testing"
"unsafe"
"gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
)
func TestBufferSize(t *testing.T) {
bufferSize := unsafe.Sizeof(buffer{})
if bufferSize < usermem.PageSize {
t.Errorf("buffer is less than a page")
}
if bufferSize > (2 * usermem.PageSize) {
t.Errorf("buffer is greater than two pages")
}
}
-48
View File
@@ -1,48 +0,0 @@
// Copyright 2018 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 pipe
// Buffer encapsulates a queueable byte buffer that can
// easily be truncated. It is designed only for use with pipes.
//
// +stateify savable
type Buffer struct {
bufferEntry
data []byte
}
// newBuffer initializes a Buffer.
func newBuffer(buf []byte) *Buffer {
return &Buffer{data: buf}
}
// bytes returns the bytes contained in the buffer.
func (b *Buffer) bytes() []byte {
return b.data
}
// size returns the number of bytes contained in the buffer.
func (b *Buffer) size() int {
return len(b.data)
}
// truncate removes the first n bytes from the buffer.
func (b *Buffer) truncate(n int) int {
if n > len(b.data) {
panic("Trying to truncate past end of array.")
}
b.data = b.data[n:]
return len(b.data)
}
+3 -4
View File
@@ -67,7 +67,6 @@ func NewInodeOperations(ctx context.Context, perms fs.FilePermissions, p *Pipe)
InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, fs.FileOwnerFromContext(ctx), perms, linux.PIPEFS_MAGIC),
p: p,
}
}
// GetFile implements fs.InodeOperations.GetFile. Named pipes have special blocking
@@ -87,7 +86,7 @@ func (i *inodeOperations) GetFile(ctx context.Context, d *fs.Dirent, flags fs.Fi
switch {
case flags.Read && !flags.Write: // O_RDONLY.
r := i.p.ROpen(ctx)
r := i.p.Open(ctx, flags)
i.newHandleLocked(&i.rWakeup)
if i.p.isNamed && !flags.NonBlocking && !i.p.HasWriters() {
@@ -103,7 +102,7 @@ func (i *inodeOperations) GetFile(ctx context.Context, d *fs.Dirent, flags fs.Fi
return r, nil
case flags.Write && !flags.Read: // O_WRONLY.
w := i.p.WOpen(ctx)
w := i.p.Open(ctx, flags)
i.newHandleLocked(&i.wWakeup)
if i.p.isNamed && !i.p.HasReaders() {
@@ -123,7 +122,7 @@ func (i *inodeOperations) GetFile(ctx context.Context, d *fs.Dirent, flags fs.Fi
case flags.Read && flags.Write: // O_RDWR.
// Pipes opened for read-write always succeeds without blocking.
rw := i.p.RWOpen(ctx)
rw := i.p.Open(ctx, flags)
i.newHandleLocked(&i.rWakeup)
i.newHandleLocked(&i.wWakeup)
return rw, nil
+213 -108
View File
@@ -12,11 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package pipe provides an in-memory implementation of a unidirectional
// pipe.
//
// The goal of this pipe is to emulate the pipe syscall in all of its
// edge cases and guarantees of atomic IO.
// Package pipe provides a pipe implementation.
package pipe
import (
@@ -32,8 +28,29 @@ import (
"gvisor.googlesource.com/gvisor/pkg/waiter"
)
// DefaultPipeSize is the system-wide default size of a pipe in bytes.
const DefaultPipeSize = 65536
const (
// MinimumPipeSize is a hard limit of the minimum size of a pipe.
MinimumPipeSize = 64 << 10
// DefaultPipeSize is the system-wide default size of a pipe in bytes.
DefaultPipeSize = MinimumPipeSize
// MaximumPipeSize is a hard limit on the maximum size of a pipe.
MaximumPipeSize = 8 << 20
)
// Sizer is an interface for setting and getting the size of a pipe.
//
// It is implemented by Pipe and, through embedding, all other types.
type Sizer interface {
// PipeSize returns the pipe capacity in bytes.
PipeSize() int64
// SetPipeSize sets the new pipe capacity in bytes.
//
// The new size is returned (which may be capped).
SetPipeSize(int64) (int64, error)
}
// Pipe is an encapsulation of a platform-independent pipe.
// It manages a buffered byte queue shared between a reader/writer
@@ -43,49 +60,76 @@ const DefaultPipeSize = 65536
type Pipe struct {
waiter.Queue `state:"nosave"`
// Whether this is a named or anonymous pipe.
// isNamed indicates whether this is a named pipe.
//
// This value is immutable.
isNamed bool
// atomicIOBytes is the maximum number of bytes that the pipe will
// guarantee atomic reads or writes atomically.
//
// This value is immutable.
atomicIOBytes int64
// The dirent backing this pipe. Shared by all readers and writers.
//
// This value is immutable.
Dirent *fs.Dirent
// The buffered byte queue.
data bufferList
// Max size of the pipe in bytes. When this max has been reached,
// writers will get EWOULDBLOCK.
max int
// Current size of the pipe in bytes.
size int
// Max number of bytes the pipe can guarantee to read or write
// atomically.
atomicIOBytes int
// The number of active readers for this pipe. Load/store atomically.
// The number of active readers for this pipe.
//
// Access atomically.
readers int32
// The number of active writes for this pipe. Load/store atomically.
// The number of active writes for this pipe.
//
// Access atomically.
writers int32
// This flag indicates if this pipe ever had a writer. Note that this does
// not necessarily indicate there is *currently* a writer, just that there
// has been a writer at some point since the pipe was created.
//
// Protected by mu.
hadWriter bool
// Lock protecting all pipe internal state.
// mu protects all pipe internal state below.
mu sync.Mutex `state:"nosave"`
// data is the buffer queue of pipe contents.
//
// This is protected by mu.
data bufferList
// max is the maximum size of the pipe in bytes. When this max has been
// reached, writers will get EWOULDBLOCK.
//
// This is protected by mu.
max int64
// size is the current size of the pipe in bytes.
//
// This is protected by mu.
size int64
// hadWriter indicates if this pipe ever had a writer. Note that this
// does not necessarily indicate there is *currently* a writer, just
// that there has been a writer at some point since the pipe was
// created.
//
// This is protected by mu.
hadWriter bool
}
// NewPipe initializes and returns a pipe. A pipe created by this function is
// persistent, and will remain valid even without any open fds to it. Named
// pipes for mknod(2) are created via this function. Note that the
// implementation of blocking semantics for opening the read and write ends of a
// named pipe are left to filesystems.
func NewPipe(ctx context.Context, isNamed bool, sizeBytes, atomicIOBytes int) *Pipe {
// NewPipe initializes and returns a pipe.
//
// N.B. The size and atomicIOBytes will be bounded.
func NewPipe(ctx context.Context, isNamed bool, sizeBytes, atomicIOBytes int64) *Pipe {
if sizeBytes < MinimumPipeSize {
sizeBytes = MinimumPipeSize
}
if sizeBytes > MaximumPipeSize {
sizeBytes = MaximumPipeSize
}
if atomicIOBytes <= 0 {
atomicIOBytes = 1
}
if atomicIOBytes > sizeBytes {
atomicIOBytes = sizeBytes
}
p := &Pipe{
isNamed: isNamed,
max: sizeBytes,
@@ -110,48 +154,45 @@ func NewPipe(ctx context.Context, isNamed bool, sizeBytes, atomicIOBytes int) *P
return p
}
// NewConnectedPipe initializes a pipe and returns a pair of objects (which
// implement kio.File) representing the read and write ends of the pipe. A pipe
// created by this function becomes invalid as soon as either the read or write
// end is closed, and errors on subsequent operations on either end. Pipes
// for pipe(2) and pipe2(2) are generally created this way.
func NewConnectedPipe(ctx context.Context, sizeBytes int, atomicIOBytes int) (*fs.File, *fs.File) {
// NewConnectedPipe initializes a pipe and returns a pair of objects
// representing the read and write ends of the pipe.
func NewConnectedPipe(ctx context.Context, sizeBytes, atomicIOBytes int64) (*fs.File, *fs.File) {
p := NewPipe(ctx, false /* isNamed */, sizeBytes, atomicIOBytes)
return p.ROpen(ctx), p.WOpen(ctx)
return p.Open(ctx, fs.FileFlags{Read: true}), p.Open(ctx, fs.FileFlags{Write: true})
}
// ROpen opens the pipe for reading.
func (p *Pipe) ROpen(ctx context.Context) *fs.File {
p.rOpen()
return fs.NewFile(ctx, p.Dirent, fs.FileFlags{Read: true}, &Reader{
ReaderWriter: ReaderWriter{Pipe: p},
})
}
// WOpen opens the pipe for writing.
func (p *Pipe) WOpen(ctx context.Context) *fs.File {
p.wOpen()
return fs.NewFile(ctx, p.Dirent, fs.FileFlags{Write: true}, &Writer{
ReaderWriter: ReaderWriter{Pipe: p},
})
}
// RWOpen opens the pipe for both reading and writing.
func (p *Pipe) RWOpen(ctx context.Context) *fs.File {
p.rOpen()
p.wOpen()
return fs.NewFile(ctx, p.Dirent, fs.FileFlags{Read: true, Write: true}, &ReaderWriter{
Pipe: p,
})
// Open opens the pipe and returns a new file.
//
// Precondition: at least one of flags.Read or flags.Write must be set.
func (p *Pipe) Open(ctx context.Context, flags fs.FileFlags) *fs.File {
switch {
case flags.Read && flags.Write:
p.rOpen()
p.wOpen()
return fs.NewFile(ctx, p.Dirent, flags, &ReaderWriter{
Pipe: p,
})
case flags.Read:
p.rOpen()
return fs.NewFile(ctx, p.Dirent, flags, &Reader{
ReaderWriter: ReaderWriter{Pipe: p},
})
case flags.Write:
p.wOpen()
return fs.NewFile(ctx, p.Dirent, flags, &Writer{
ReaderWriter: ReaderWriter{Pipe: p},
})
default:
// Precondition violated.
panic("invalid pipe flags")
}
}
// read reads data from the pipe into dst and returns the number of bytes
// read, or returns ErrWouldBlock if the pipe is empty.
//
// Precondition: this pipe must have readers.
func (p *Pipe) read(ctx context.Context, dst usermem.IOSequence) (int64, error) {
if !p.HasReaders() {
return 0, syscall.EBADF
}
// Don't block for a zero-length read even if the pipe is empty.
if dst.NumBytes() == 0 {
return 0, nil
@@ -159,8 +200,8 @@ func (p *Pipe) read(ctx context.Context, dst usermem.IOSequence) (int64, error)
p.mu.Lock()
defer p.mu.Unlock()
// If there is nothing to read at the moment but there is a writer, tell the
// caller to block.
// Is the pipe empty?
if p.size == 0 {
if !p.HasWriters() {
// There are no writers, return EOF.
@@ -168,64 +209,94 @@ func (p *Pipe) read(ctx context.Context, dst usermem.IOSequence) (int64, error)
}
return 0, syserror.ErrWouldBlock
}
var n int64
for buffer := p.data.Front(); buffer != nil; buffer = p.data.Front() {
n0, err := dst.CopyOut(ctx, buffer.bytes())
n += int64(n0)
p.size -= n0
if buffer.truncate(n0) == 0 {
p.data.Remove(buffer)
// Limit how much we consume.
if dst.NumBytes() > p.size {
dst = dst.TakeFirst64(p.size)
}
done := int64(0)
for dst.NumBytes() > 0 {
// Pop the first buffer.
first := p.data.Front()
if first == nil {
break
}
dst = dst.DropFirst(n0)
if dst.NumBytes() == 0 || err != nil {
return n, err
// Copy user data.
n, err := dst.CopyOutFrom(ctx, first)
done += int64(n)
p.size -= n
dst = dst.DropFirst64(n)
// Empty buffer?
if first.Empty() {
// Push to the free list.
p.data.Remove(first)
bufferPool.Put(first)
}
// Handle errors.
if err != nil {
return done, err
}
}
return n, nil
return done, nil
}
// write writes data from sv into the pipe and returns the number of bytes
// written. If no bytes are written because the pipe is full (or has less than
// atomicIOBytes free capacity), write returns ErrWouldBlock.
//
// Precondition: this pipe must have writers.
func (p *Pipe) write(ctx context.Context, src usermem.IOSequence) (int64, error) {
p.mu.Lock()
defer p.mu.Unlock()
if !p.HasWriters() {
return 0, syscall.EBADF
}
// Can't write to a pipe with no readers.
if !p.HasReaders() {
return 0, syscall.EPIPE
}
// POSIX requires that a write smaller than atomicIOBytes (PIPE_BUF) be
// atomic, but requires no atomicity for writes larger than this. However,
// Linux appears to provide stronger semantics than this in practice:
// unmerged writes are done one PAGE_SIZE buffer at a time, so for larger
// writes, the writing of each PIPE_BUF-sized chunk is atomic. We implement
// this by writing at most atomicIOBytes at a time if we can't service the
// write in its entirety.
canWrite := src.NumBytes()
if canWrite > int64(p.max-p.size) {
if p.max-p.size >= p.atomicIOBytes {
canWrite = int64(p.atomicIOBytes)
} else {
// atomic, but requires no atomicity for writes larger than this.
wanted := src.NumBytes()
if avail := p.max - p.size; wanted > avail {
if wanted <= p.atomicIOBytes {
return 0, syserror.ErrWouldBlock
}
// Limit to the available capacity.
src = src.TakeFirst64(avail)
}
// Copy data from user memory into a pipe-owned buffer.
buf := make([]byte, canWrite)
n, err := src.CopyIn(ctx, buf)
if n > 0 {
p.data.PushBack(newBuffer(buf[:n]))
done := int64(0)
for src.NumBytes() > 0 {
// Need a new buffer?
last := p.data.Back()
if last == nil || last.Full() {
// Add a new buffer to the data list.
last = newBuffer()
p.data.PushBack(last)
}
// Copy user data.
n, err := src.CopyInTo(ctx, last)
done += int64(n)
p.size += n
src = src.DropFirst64(n)
// Handle errors.
if err != nil {
return done, err
}
}
if int64(n) < src.NumBytes() && err == nil {
if wanted > done {
// Partial write due to full pipe.
err = syserror.ErrWouldBlock
return done, syserror.ErrWouldBlock
}
return int64(n), err
return done, nil
}
// rOpen signals a new reader of the pipe.
@@ -267,6 +338,9 @@ func (p *Pipe) HasWriters() bool {
return atomic.LoadInt32(&p.writers) > 0
}
// rReadinessLocked calculates the read readiness.
//
// Precondition: mu must be held.
func (p *Pipe) rReadinessLocked() waiter.EventMask {
ready := waiter.EventMask(0)
if p.HasReaders() && p.data.Front() != nil {
@@ -290,6 +364,9 @@ func (p *Pipe) rReadiness() waiter.EventMask {
return p.rReadinessLocked()
}
// wReadinessLocked calculates the write readiness.
//
// Precondition: mu must be held.
func (p *Pipe) wReadinessLocked() waiter.EventMask {
ready := waiter.EventMask(0)
if p.HasWriters() && p.size < p.max {
@@ -317,8 +394,36 @@ func (p *Pipe) rwReadiness() waiter.EventMask {
return p.rReadinessLocked() | p.wReadinessLocked()
}
func (p *Pipe) queuedSize() int {
// queued returns the amount of queued data.
func (p *Pipe) queued() int64 {
p.mu.Lock()
defer p.mu.Unlock()
return p.size
}
// PipeSize implements PipeSizer.PipeSize.
func (p *Pipe) PipeSize() int64 {
p.mu.Lock()
defer p.mu.Unlock()
return p.max
}
// SetPipeSize implements PipeSize.SetPipeSize.
func (p *Pipe) SetPipeSize(size int64) (int64, error) {
if size < 0 {
return 0, syserror.EINVAL
}
if size < MinimumPipeSize {
size = MinimumPipeSize // Per spec.
}
if size > MaximumPipeSize {
return 0, syserror.EPERM
}
p.mu.Lock()
defer p.mu.Unlock()
if size < p.size {
return 0, syserror.EBUSY
}
p.max = size
return size, nil
}
+4 -3
View File
@@ -58,15 +58,16 @@ func TestPipeReadBlock(t *testing.T) {
func TestPipeWriteBlock(t *testing.T) {
const atomicIOBytes = 2
const capacity = MinimumPipeSize
ctx := contexttest.Context(t)
r, w := NewConnectedPipe(ctx, 10, atomicIOBytes)
r, w := NewConnectedPipe(ctx, capacity, atomicIOBytes)
defer r.DecRef()
defer w.DecRef()
msg := []byte("here's some bytes")
msg := make([]byte, capacity+1)
n, err := w.Writev(ctx, usermem.BytesIOSequence(msg))
if wantN, wantErr := int64(atomicIOBytes), syserror.ErrWouldBlock; n != wantN || err != wantErr {
if wantN, wantErr := int64(capacity), syserror.ErrWouldBlock; n != wantN || err != wantErr {
t.Fatalf("Writev: got (%d, %v), wanted (%d, %v)", n, err, wantN, wantErr)
}
}
+3
View File
@@ -27,8 +27,11 @@ type Reader struct {
}
// Release implements fs.FileOperations.Release.
//
// This overrides ReaderWriter.Release.
func (r *Reader) Release() {
r.Pipe.rClose()
// Wake up writers.
r.Pipe.Notify(waiter.EventOut)
}
+3 -3
View File
@@ -15,7 +15,6 @@
package pipe
import (
"fmt"
"math"
"syscall"
@@ -49,6 +48,7 @@ type ReaderWriter struct {
func (rw *ReaderWriter) Release() {
rw.Pipe.rClose()
rw.Pipe.wClose()
// Wake up readers and writers.
rw.Pipe.Notify(waiter.EventIn | waiter.EventOut)
}
@@ -81,9 +81,9 @@ func (rw *ReaderWriter) Ioctl(ctx context.Context, io usermem.IO, args arch.Sysc
// Switch on ioctl request.
switch int(args[1].Int()) {
case linux.FIONREAD:
v := rw.queuedSize()
v := rw.queued()
if v > math.MaxInt32 {
panic(fmt.Sprintf("Impossibly large pipe queued size: %d", v))
v = math.MaxInt32 // Silently truncate.
}
// Copy result to user-space.
_, err := usermem.CopyObjectOut(ctx, io, args[2].Pointer(), int32(v), usermem.IOOpts{
+3
View File
@@ -27,8 +27,11 @@ type Writer struct {
}
// Release implements fs.FileOperations.Release.
//
// This overrides ReaderWriter.Release.
func (w *Writer) Release() {
w.Pipe.wClose()
// Wake up readers.
w.Pipe.Notify(waiter.EventHUp)
}
+14
View File
@@ -27,6 +27,7 @@ import (
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/auth"
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/fasync"
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/kdefs"
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/pipe"
ktime "gvisor.googlesource.com/gvisor/pkg/sentry/kernel/time"
"gvisor.googlesource.com/gvisor/pkg/sentry/limits"
"gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
@@ -943,6 +944,19 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
}
err := tmpfs.AddSeals(file.Dirent.Inode, args[2].Uint())
return 0, nil, err
case linux.F_GETPIPE_SZ:
sz, ok := file.FileOperations.(pipe.Sizer)
if !ok {
return 0, nil, syserror.EINVAL
}
return uintptr(sz.PipeSize()), nil, nil
case linux.F_SETPIPE_SZ:
sz, ok := file.FileOperations.(pipe.Sizer)
if !ok {
return 0, nil, syserror.EINVAL
}
n, err := sz.SetPipeSize(int64(args[2].Int()))
return uintptr(n), nil, err
default:
// Everything else is not yet supported.
return 0, nil, syserror.EINVAL
+5 -1
View File
@@ -191,7 +191,11 @@ syscall_test(test = "//test/syscalls/linux:partial_bad_buffer_test")
syscall_test(test = "//test/syscalls/linux:pause_test")
syscall_test(test = "//test/syscalls/linux:pipe_test")
syscall_test(
size = "large",
shard_count = 5,
test = "//test/syscalls/linux:pipe_test",
)
syscall_test(test = "//test/syscalls/linux:poll_test")
+2
View File
@@ -1237,6 +1237,8 @@ cc_binary(
linkstatic = 1,
deps = [
"//test/util:file_descriptor",
"//test/util:posix_error",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
File diff suppressed because it is too large Load Diff