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 }