This commit is contained in:
Zeke Lu 2025-06-20 15:37:00 -04:00 committed by GitHub
commit fcc5ae134b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 162 additions and 2 deletions

View File

@ -7,6 +7,7 @@
package exec_test
import (
"bufio"
"fmt"
"internal/testenv"
"io"
@ -14,6 +15,7 @@ import (
"os/exec"
"strconv"
"strings"
"sync"
"syscall"
"testing"
)
@ -25,6 +27,7 @@ var (
func init() {
registerHelperCommand("pipehandle", cmdPipeHandle)
registerHelperCommand("crtpipehandle", cmdCRTPipeHandle)
}
func cmdPipeHandle(args ...string) {
@ -67,6 +70,104 @@ func TestPipePassing(t *testing.T) {
}
}
func cmdCRTPipeHandle(args ...string) {
get_osfhandle := syscall.NewLazyDLL("msvcrt.dll").NewProc("_get_osfhandle")
h3, _, _ := get_osfhandle.Call(3)
if h3 == uintptr(syscall.InvalidHandle) {
fmt.Fprintf(os.Stderr, "_get_osfhandle: pipe 3 is invalid\n")
os.Exit(1)
}
pipe3 := os.NewFile(h3, "in")
defer pipe3.Close()
h4, _, _ := get_osfhandle.Call(4)
if h4 == uintptr(syscall.InvalidHandle) {
fmt.Fprintf(os.Stderr, "_get_osfhandle: pipe 4 is invalid\n")
os.Exit(1)
}
pipe4 := os.NewFile(h4, "out")
defer pipe4.Close()
br := bufio.NewReader(pipe3)
line, _, err := br.ReadLine()
if err != nil {
fmt.Fprintf(os.Stderr, "reading pipe failed: %v\n", err)
os.Exit(1)
}
if string(line) == "ping" {
_, err := fmt.Fprintf(pipe4, "%s\n", args[0])
if err != nil {
fmt.Fprintf(os.Stderr, "writing to pipe failed: %v\n", err)
os.Exit(1)
}
} else {
fmt.Fprintf(os.Stderr, "unexpected content from pipe: %q\n", line)
os.Exit(1)
}
}
func TestCRTPipePassing(t *testing.T) {
crt := syscall.NewLazyDLL("msvcrt.dll")
if err := crt.Load(); err != nil {
t.Skipf("can't run test due to missing msvcrt.dll: %v", err)
}
r3, w3, err := os.Pipe()
if err != nil {
t.Errorf("failed to create pipe 3: %v", err)
}
defer func() {
r3.Close()
w3.Close()
}()
r4, w4, err := os.Pipe()
if err != nil {
t.Errorf("failed to create pipe 4: %v", err)
}
defer func() {
r4.Close()
w4.Close()
}()
const marker = "pong"
childProc := helperCommand(t, "crtpipehandle", marker)
childProc.SysProcAttr = &syscall.SysProcAttr{
AdditionalInheritedHandles: []syscall.Handle{
syscall.Handle(r3.Fd()),
syscall.Handle(w4.Fd()),
},
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
output, err := childProc.CombinedOutput()
if err != nil {
t.Errorf("child proc exited: %v. output:\n%s", err, output)
r3.Close()
w4.Close()
}
wg.Done()
}()
_, err = fmt.Fprint(w3, "ping\n")
if err != nil {
t.Errorf("writing pipe failed: %v", err)
}
br := bufio.NewReader(r4)
response, _, err := br.ReadLine()
if err != nil {
t.Error(err)
}
if string(response) != marker {
t.Errorf("got %q; want %q", string(response), marker)
}
wg.Wait()
}
func TestNoInheritHandles(t *testing.T) {
t.Parallel()

View File

@ -375,6 +375,9 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
if err != nil {
return 0, 0, err
}
if len(fd) > 3 {
si.CbReserved2, si.Reserved2 = createChildStdioBuffer(fd)
}
}
envBlock, err := createEnvBlock(attr.Env)
@ -403,3 +406,59 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
func Exec(argv0 string, argv []string, envv []string) (err error) {
return EWINDOWS
}
// The C Run-time (CRT) file descriptor mode flags. See
// https://github.com/nodejs/node/blob/71691e53/deps/uv/src/win/process-stdio.c#L57-L65
const (
crt_FD_OPEN uint8 = 1 << iota
crt_FD_EOFLAG
crt_FD_CRLF
crt_FD_PIPE
crt_FD_NOINHERIT
crt_FD_APPEND
crt_FD_DEV
crt_FD_TEXT
)
// createChildStdioBuffer creates a buffer that describes the handles that will
// be inherited by the child process so that the child process can retrieve the
// inherited handles by calling the CRT function _get_osfhandle.
//
// The buffer has the following layout:
// int number_of_fds
// unsigned char crt_flags[number_of_fds]
// HANDLE os_handle[number_of_fds]
//
// See:
// https://github.com/nodejs/node/blob/71691e53/deps/uv/src/win/process-stdio.c#L32-L37
// https://github.com/reactos/reactos/blob/57c84dd6/sdk/lib/crt/stdio/file.c#L405-L532
// https://github.com/reactos/reactos/blob/57c84dd6/sdk/lib/crt/stdio/file.c#L1588-L1599
func createChildStdioBuffer(fd []Handle) (uint16, *byte) {
count := int32(len(fd))
var crtFlags uint8
int32Size := unsafe.Sizeof(count)
flagsSize := unsafe.Sizeof(crtFlags)
handleSize := unsafe.Sizeof(fd[0])
size := uint16(int32Size + flagsSize*uintptr(count) + handleSize*uintptr(count))
buf := make([]byte, size)
*(*int32)(unsafe.Pointer(&buf[0])) = count
for i := uintptr(0); i < uintptr(count); i++ {
// (ZekeLu) should it be restrict to report the error for invalid or
// unknown handles?
ft, _ := GetFileType(fd[i])
switch ft {
case FILE_TYPE_DISK:
crtFlags = crt_FD_OPEN
case FILE_TYPE_PIPE:
crtFlags = crt_FD_OPEN | crt_FD_PIPE
default:
crtFlags = crt_FD_OPEN | crt_FD_DEV
}
*(*uint8)(unsafe.Pointer(&buf[int32Size+flagsSize*i])) = crtFlags
*(*Handle)(unsafe.Pointer(&buf[int32Size+flagsSize*uintptr(count)+handleSize*i])) = fd[i]
}
return size, &buf[0]
}

View File

@ -491,8 +491,8 @@ type StartupInfo struct {
FillAttribute uint32
Flags uint32
ShowWindow uint16
_ uint16
_ *byte
CbReserved2 uint16
Reserved2 *byte
StdInput Handle
StdOutput Handle
StdErr Handle