github.com/sagernet/sing@v0.4.0-beta.19.0.20240518125136-f67a0988a636/common/bufio/splice_linux.go (about)

     1  package bufio
     2  
     3  import (
     4  	"syscall"
     5  
     6  	E "github.com/sagernet/sing/common/exceptions"
     7  	N "github.com/sagernet/sing/common/network"
     8  
     9  	"golang.org/x/sys/unix"
    10  )
    11  
    12  const maxSpliceSize = 1 << 20
    13  
    14  func splice(source syscall.RawConn, destination syscall.RawConn, readCounters []N.CountFunc, writeCounters []N.CountFunc) (handed bool, n int64, err error) {
    15  	handed = true
    16  	var pipeFDs [2]int
    17  	err = unix.Pipe2(pipeFDs[:], syscall.O_CLOEXEC|syscall.O_NONBLOCK)
    18  	if err != nil {
    19  		return
    20  	}
    21  	defer unix.Close(pipeFDs[0])
    22  	defer unix.Close(pipeFDs[1])
    23  
    24  	_, _ = unix.FcntlInt(uintptr(pipeFDs[0]), unix.F_SETPIPE_SZ, maxSpliceSize)
    25  	var readN int
    26  	var readErr error
    27  	var writeSize int
    28  	var writeErr error
    29  	readFunc := func(fd uintptr) (done bool) {
    30  		p0, p1 := unix.Splice(int(fd), nil, pipeFDs[1], nil, maxSpliceSize, unix.SPLICE_F_NONBLOCK)
    31  		readN = int(p0)
    32  		readErr = p1
    33  		return readErr != unix.EAGAIN
    34  	}
    35  	writeFunc := func(fd uintptr) (done bool) {
    36  		for writeSize > 0 {
    37  			p0, p1 := unix.Splice(pipeFDs[0], nil, int(fd), nil, writeSize, unix.SPLICE_F_NONBLOCK|unix.SPLICE_F_MOVE)
    38  			writeN := int(p0)
    39  			writeErr = p1
    40  			if writeErr != nil {
    41  				return writeErr != unix.EAGAIN
    42  			}
    43  			writeSize -= writeN
    44  		}
    45  		return true
    46  	}
    47  	for {
    48  		err = source.Read(readFunc)
    49  		if err != nil {
    50  			readErr = err
    51  		}
    52  		if readErr != nil {
    53  			if readErr == unix.EINVAL || readErr == unix.ENOSYS {
    54  				handed = false
    55  				return
    56  			}
    57  			err = E.Cause(readErr, "splice read")
    58  			return
    59  		}
    60  		if readN == 0 {
    61  			return
    62  		}
    63  		writeSize = readN
    64  		err = destination.Write(writeFunc)
    65  		if err != nil {
    66  			writeErr = err
    67  		}
    68  		if writeErr != nil {
    69  			err = E.Cause(writeErr, "splice write")
    70  			return
    71  		}
    72  		for _, readCounter := range readCounters {
    73  			readCounter(int64(readN))
    74  		}
    75  		for _, writeCounter := range writeCounters {
    76  			writeCounter(int64(readN))
    77  		}
    78  	}
    79  }