Port sendfile to vfs2.

And do some refactoring of the wait logic in sendfile/splice/tee.

Updates #1035 #2923

PiperOrigin-RevId: 322815521
This commit is contained in:
Nicolas Lacasse
2020-07-23 10:36:17 -07:00
committed by gVisor bot
parent 4fbd0728ac
commit b396d3882c
3 changed files with 261 additions and 64 deletions
+258 -63
View File
@@ -15,12 +15,15 @@
package vfs2
import (
"io"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/pipe"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/usermem"
"gvisor.dev/gvisor/pkg/waiter"
)
@@ -110,16 +113,20 @@ func Splice(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal
// Move data.
var (
n int64
err error
inCh chan struct{}
outCh chan struct{}
n int64
err error
)
dw := dualWaiter{
inFile: inFile,
outFile: outFile,
}
defer dw.destroy()
for {
// If both input and output are pipes, delegate to the pipe
// implementation. Otherwise, exactly one end is a pipe, which we
// ensure is consistently ordered after the non-pipe FD's locks by
// passing the pipe FD as usermem.IO to the non-pipe end.
// implementation. Otherwise, exactly one end is a pipe, which
// we ensure is consistently ordered after the non-pipe FD's
// locks by passing the pipe FD as usermem.IO to the non-pipe
// end.
switch {
case inIsPipe && outIsPipe:
n, err = pipe.Splice(t, outPipeFD, inPipeFD, count)
@@ -137,38 +144,15 @@ func Splice(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal
} else {
n, err = inFile.Read(t, outPipeFD.IOSequence(count), vfs.ReadOptions{})
}
default:
panic("not possible")
}
if n != 0 || err != syserror.ErrWouldBlock || nonBlock {
break
}
// Note that the blocking behavior here is a bit different than the
// normal pattern. Because we need to have both data to read and data
// to write simultaneously, we actually explicitly block on both of
// these cases in turn before returning to the splice operation.
if inFile.Readiness(eventMaskRead)&eventMaskRead == 0 {
if inCh == nil {
inCh = make(chan struct{}, 1)
inW, _ := waiter.NewChannelEntry(inCh)
inFile.EventRegister(&inW, eventMaskRead)
defer inFile.EventUnregister(&inW)
continue // Need to refresh readiness.
}
if err = t.Block(inCh); err != nil {
break
}
}
if outFile.Readiness(eventMaskWrite)&eventMaskWrite == 0 {
if outCh == nil {
outCh = make(chan struct{}, 1)
outW, _ := waiter.NewChannelEntry(outCh)
outFile.EventRegister(&outW, eventMaskWrite)
defer outFile.EventUnregister(&outW)
continue // Need to refresh readiness.
}
if err = t.Block(outCh); err != nil {
break
}
if err = dw.waitForBoth(t); err != nil {
break
}
}
@@ -247,45 +231,256 @@ func Tee(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallCo
// Copy data.
var (
inCh chan struct{}
outCh chan struct{}
n int64
err error
)
dw := dualWaiter{
inFile: inFile,
outFile: outFile,
}
defer dw.destroy()
for {
n, err := pipe.Tee(t, outPipeFD, inPipeFD, count)
if n != 0 {
return uintptr(n), nil, nil
n, err = pipe.Tee(t, outPipeFD, inPipeFD, count)
if n != 0 || err != syserror.ErrWouldBlock || nonBlock {
break
}
if err != syserror.ErrWouldBlock || nonBlock {
if err = dw.waitForBoth(t); err != nil {
break
}
}
if n == 0 {
return 0, nil, err
}
outFile.Dentry().InotifyWithParent(linux.IN_MODIFY, 0, vfs.PathEvent)
return uintptr(n), nil, nil
}
// Sendfile implements linux system call sendfile(2).
func Sendfile(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
outFD := args[0].Int()
inFD := args[1].Int()
offsetAddr := args[2].Pointer()
count := int64(args[3].SizeT())
inFile := t.GetFileVFS2(inFD)
if inFile == nil {
return 0, nil, syserror.EBADF
}
defer inFile.DecRef()
if !inFile.IsReadable() {
return 0, nil, syserror.EBADF
}
outFile := t.GetFileVFS2(outFD)
if outFile == nil {
return 0, nil, syserror.EBADF
}
defer outFile.DecRef()
if !outFile.IsWritable() {
return 0, nil, syserror.EBADF
}
// Verify that the outFile Append flag is not set.
if outFile.StatusFlags()&linux.O_APPEND != 0 {
return 0, nil, syserror.EINVAL
}
// Verify that inFile is a regular file or block device. This is a
// requirement; the same check appears in Linux
// (fs/splice.c:splice_direct_to_actor).
if stat, err := inFile.Stat(t, vfs.StatOptions{Mask: linux.STATX_TYPE}); err != nil {
return 0, nil, err
} else if stat.Mask&linux.STATX_TYPE == 0 ||
(stat.Mode&linux.S_IFMT != linux.S_IFREG && stat.Mode&linux.S_IFMT != linux.S_IFBLK) {
return 0, nil, syserror.EINVAL
}
// Copy offset if it exists.
offset := int64(-1)
if offsetAddr != 0 {
if inFile.Options().DenyPRead {
return 0, nil, syserror.ESPIPE
}
if _, err := t.CopyIn(offsetAddr, &offset); err != nil {
return 0, nil, err
}
if offset < 0 {
return 0, nil, syserror.EINVAL
}
if offset+count < 0 {
return 0, nil, syserror.EINVAL
}
}
// Note that the blocking behavior here is a bit different than the
// normal pattern. Because we need to have both data to read and data
// to write simultaneously, we actually explicitly block on both of
// these cases in turn before returning to the tee operation.
if inFile.Readiness(eventMaskRead)&eventMaskRead == 0 {
if inCh == nil {
inCh = make(chan struct{}, 1)
inW, _ := waiter.NewChannelEntry(inCh)
inFile.EventRegister(&inW, eventMaskRead)
defer inFile.EventUnregister(&inW)
continue // Need to refresh readiness.
// Validate count. This must come after offset checks.
if count < 0 {
return 0, nil, syserror.EINVAL
}
if count == 0 {
return 0, nil, nil
}
if count > int64(kernel.MAX_RW_COUNT) {
count = int64(kernel.MAX_RW_COUNT)
}
// Copy data.
var (
n int64
err error
)
dw := dualWaiter{
inFile: inFile,
outFile: outFile,
}
defer dw.destroy()
outPipeFD, outIsPipe := outFile.Impl().(*pipe.VFSPipeFD)
// Reading from input file should never block, since it is regular or
// block device. We only need to check if writing to the output file
// can block.
nonBlock := outFile.StatusFlags()&linux.O_NONBLOCK != 0
if outIsPipe {
for n < count {
var spliceN int64
if offset != -1 {
spliceN, err = inFile.PRead(t, outPipeFD.IOSequence(count), offset, vfs.ReadOptions{})
offset += spliceN
} else {
spliceN, err = inFile.Read(t, outPipeFD.IOSequence(count), vfs.ReadOptions{})
}
if err := t.Block(inCh); err != nil {
return 0, nil, err
n += spliceN
if err == syserror.ErrWouldBlock && !nonBlock {
err = dw.waitForBoth(t)
}
if err != nil {
break
}
}
if outFile.Readiness(eventMaskWrite)&eventMaskWrite == 0 {
if outCh == nil {
outCh = make(chan struct{}, 1)
outW, _ := waiter.NewChannelEntry(outCh)
outFile.EventRegister(&outW, eventMaskWrite)
defer outFile.EventUnregister(&outW)
continue // Need to refresh readiness.
} else {
// Read inFile to buffer, then write the contents to outFile.
buf := make([]byte, count)
for n < count {
var readN int64
if offset != -1 {
readN, err = inFile.PRead(t, usermem.BytesIOSequence(buf), offset, vfs.ReadOptions{})
offset += readN
} else {
readN, err = inFile.Read(t, usermem.BytesIOSequence(buf), vfs.ReadOptions{})
}
if err := t.Block(outCh); err != nil {
return 0, nil, err
if readN == 0 && err == io.EOF {
// We reached the end of the file. Eat the
// error and exit the loop.
err = nil
break
}
n += readN
if err != nil {
break
}
// Write all of the bytes that we read. This may need
// multiple write calls to complete.
wbuf := buf[:n]
for len(wbuf) > 0 {
var writeN int64
writeN, err = outFile.Write(t, usermem.BytesIOSequence(wbuf), vfs.WriteOptions{})
wbuf = wbuf[writeN:]
if err == syserror.ErrWouldBlock && !nonBlock {
err = dw.waitForOut(t)
}
if err != nil {
// We didn't complete the write. Only
// report the bytes that were actually
// written, and rewind the offset.
notWritten := int64(len(wbuf))
n -= notWritten
if offset != -1 {
offset -= notWritten
}
break
}
}
if err == syserror.ErrWouldBlock && !nonBlock {
err = dw.waitForBoth(t)
}
if err != nil {
break
}
}
}
if offsetAddr != 0 {
// Copy out the new offset.
if _, err := t.CopyOut(offsetAddr, offset); err != nil {
return 0, nil, err
}
}
if n == 0 {
return 0, nil, err
}
inFile.Dentry().InotifyWithParent(linux.IN_ACCESS, 0, vfs.PathEvent)
outFile.Dentry().InotifyWithParent(linux.IN_MODIFY, 0, vfs.PathEvent)
return uintptr(n), nil, nil
}
// dualWaiter is used to wait on one or both vfs.FileDescriptions. It is not
// thread-safe, and does not take a reference on the vfs.FileDescriptions.
//
// Users must call destroy() when finished.
type dualWaiter struct {
inFile *vfs.FileDescription
outFile *vfs.FileDescription
inW waiter.Entry
inCh chan struct{}
outW waiter.Entry
outCh chan struct{}
}
// waitForBoth waits for both dw.inFile and dw.outFile to be ready.
func (dw *dualWaiter) waitForBoth(t *kernel.Task) error {
if dw.inFile.Readiness(eventMaskRead)&eventMaskRead == 0 {
if dw.inCh == nil {
dw.inW, dw.inCh = waiter.NewChannelEntry(nil)
dw.inFile.EventRegister(&dw.inW, eventMaskRead)
// We might be ready now. Try again before blocking.
return nil
}
if err := t.Block(dw.inCh); err != nil {
return err
}
}
return dw.waitForOut(t)
}
// waitForOut waits for dw.outfile to be read.
func (dw *dualWaiter) waitForOut(t *kernel.Task) error {
if dw.outFile.Readiness(eventMaskWrite)&eventMaskWrite == 0 {
if dw.outCh == nil {
dw.outW, dw.outCh = waiter.NewChannelEntry(nil)
dw.outFile.EventRegister(&dw.outW, eventMaskWrite)
// We might be ready now. Try again before blocking.
return nil
}
if err := t.Block(dw.outCh); err != nil {
return err
}
}
return nil
}
// destroy cleans up resources help by dw. No more calls to wait* can occur
// after destroy is called.
func (dw *dualWaiter) destroy() {
if dw.inCh != nil {
dw.inFile.EventUnregister(&dw.inW)
dw.inCh = nil
}
if dw.outCh != nil {
dw.outFile.EventUnregister(&dw.outW)
dw.outCh = nil
}
dw.inFile = nil
dw.outFile = nil
}
+1 -1
View File
@@ -44,7 +44,7 @@ func Override() {
s.Table[23] = syscalls.Supported("select", Select)
s.Table[32] = syscalls.Supported("dup", Dup)
s.Table[33] = syscalls.Supported("dup2", Dup2)
delete(s.Table, 40) // sendfile
s.Table[40] = syscalls.Supported("sendfile", Sendfile)
s.Table[41] = syscalls.Supported("socket", Socket)
s.Table[42] = syscalls.Supported("connect", Connect)
s.Table[43] = syscalls.Supported("accept", Accept)
+2
View File
@@ -640,11 +640,13 @@ syscall_test(
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:sendfile_socket_test",
vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:sendfile_test",
vfs2 = "True",
)
syscall_test(