mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
4fbd0728ac
commit
b396d3882c
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user