mirror of
https://github.com/golang/sys.git
synced 2026-02-08 11:46:04 +03:00
It turns out that if you write Go pointers to Go memory, the Go compiler must be involved so that it generates various calls to the GC in the process. Letting Windows write Go pointers to Go memory violated this. We fix this by having all the Windows-managed memory be just a boring []byte blob. Then, in order to prevent the GC from prematurely cleaning up the pointers referenced by that []byte blob, or in the future moving memory and attempting to fix up pointers, we copy the data to the Windows heap and then maintain a little array of pointers that have been used. Every time the Update function is called with a new pointer, we make a copy and append it to the list. Then, on Delete, we free the pointers from the Windows heap. Updates golang/go#44900. Change-Id: I42340a93fd9f6b8d10340634cf833fd4559a5f4f Reviewed-on: https://go-review.googlesource.com/c/sys/+/300369 Trust: Jason A. Donenfeld <Jason@zx2c4.com> Run-TryBot: Jason A. Donenfeld <Jason@zx2c4.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
196 lines
5.2 KiB
Go
196 lines
5.2 KiB
Go
// Copyright 2009 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Fork, exec, wait, etc.
|
|
|
|
package windows
|
|
|
|
import (
|
|
errorspkg "errors"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/internal/unsafeheader"
|
|
)
|
|
|
|
// EscapeArg rewrites command line argument s as prescribed
|
|
// in http://msdn.microsoft.com/en-us/library/ms880421.
|
|
// This function returns "" (2 double quotes) if s is empty.
|
|
// Alternatively, these transformations are done:
|
|
// - every back slash (\) is doubled, but only if immediately
|
|
// followed by double quote (");
|
|
// - every double quote (") is escaped by back slash (\);
|
|
// - finally, s is wrapped with double quotes (arg -> "arg"),
|
|
// but only if there is space or tab inside s.
|
|
func EscapeArg(s string) string {
|
|
if len(s) == 0 {
|
|
return "\"\""
|
|
}
|
|
n := len(s)
|
|
hasSpace := false
|
|
for i := 0; i < len(s); i++ {
|
|
switch s[i] {
|
|
case '"', '\\':
|
|
n++
|
|
case ' ', '\t':
|
|
hasSpace = true
|
|
}
|
|
}
|
|
if hasSpace {
|
|
n += 2
|
|
}
|
|
if n == len(s) {
|
|
return s
|
|
}
|
|
|
|
qs := make([]byte, n)
|
|
j := 0
|
|
if hasSpace {
|
|
qs[j] = '"'
|
|
j++
|
|
}
|
|
slashes := 0
|
|
for i := 0; i < len(s); i++ {
|
|
switch s[i] {
|
|
default:
|
|
slashes = 0
|
|
qs[j] = s[i]
|
|
case '\\':
|
|
slashes++
|
|
qs[j] = s[i]
|
|
case '"':
|
|
for ; slashes > 0; slashes-- {
|
|
qs[j] = '\\'
|
|
j++
|
|
}
|
|
qs[j] = '\\'
|
|
j++
|
|
qs[j] = s[i]
|
|
}
|
|
j++
|
|
}
|
|
if hasSpace {
|
|
for ; slashes > 0; slashes-- {
|
|
qs[j] = '\\'
|
|
j++
|
|
}
|
|
qs[j] = '"'
|
|
j++
|
|
}
|
|
return string(qs[:j])
|
|
}
|
|
|
|
// ComposeCommandLine escapes and joins the given arguments suitable for use as a Windows command line,
|
|
// in CreateProcess's CommandLine argument, CreateService/ChangeServiceConfig's BinaryPathName argument,
|
|
// or any program that uses CommandLineToArgv.
|
|
func ComposeCommandLine(args []string) string {
|
|
var commandLine string
|
|
for i := range args {
|
|
if i > 0 {
|
|
commandLine += " "
|
|
}
|
|
commandLine += EscapeArg(args[i])
|
|
}
|
|
return commandLine
|
|
}
|
|
|
|
// DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv,
|
|
// as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that
|
|
// command lines are passed around.
|
|
func DecomposeCommandLine(commandLine string) ([]string, error) {
|
|
if len(commandLine) == 0 {
|
|
return []string{}, nil
|
|
}
|
|
var argc int32
|
|
argv, err := CommandLineToArgv(StringToUTF16Ptr(commandLine), &argc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer LocalFree(Handle(unsafe.Pointer(argv)))
|
|
var args []string
|
|
for _, v := range (*argv)[:argc] {
|
|
args = append(args, UTF16ToString((*v)[:]))
|
|
}
|
|
return args, nil
|
|
}
|
|
|
|
func CloseOnExec(fd Handle) {
|
|
SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
|
|
}
|
|
|
|
// FullPath retrieves the full path of the specified file.
|
|
func FullPath(name string) (path string, err error) {
|
|
p, err := UTF16PtrFromString(name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
n := uint32(100)
|
|
for {
|
|
buf := make([]uint16, n)
|
|
n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if n <= uint32(len(buf)) {
|
|
return UTF16ToString(buf[:n]), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// NewProcThreadAttributeList allocates a new ProcThreadAttributeListContainer, with the requested maximum number of attributes.
|
|
func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeListContainer, error) {
|
|
var size uintptr
|
|
err := initializeProcThreadAttributeList(nil, maxAttrCount, 0, &size)
|
|
if err != ERROR_INSUFFICIENT_BUFFER {
|
|
if err == nil {
|
|
return nil, errorspkg.New("unable to query buffer size from InitializeProcThreadAttributeList")
|
|
}
|
|
return nil, err
|
|
}
|
|
// size is guaranteed to be ≥1 by InitializeProcThreadAttributeList.
|
|
al := &ProcThreadAttributeListContainer{data: (*ProcThreadAttributeList)(unsafe.Pointer(&make([]byte, size)[0]))}
|
|
err = initializeProcThreadAttributeList(al.data, maxAttrCount, 0, &size)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return al, err
|
|
}
|
|
|
|
// Update modifies the ProcThreadAttributeList using UpdateProcThreadAttribute.
|
|
// Note that the value passed to this function will be copied into memory
|
|
// allocated by LocalAlloc, the contents of which should not contain any
|
|
// Go-managed pointers, even if the passed value itself is a Go-managed
|
|
// pointer.
|
|
func (al *ProcThreadAttributeListContainer) Update(attribute uintptr, value unsafe.Pointer, size uintptr) error {
|
|
alloc, err := LocalAlloc(LMEM_FIXED, uint32(size))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var src, dst []byte
|
|
hdr := (*unsafeheader.Slice)(unsafe.Pointer(&src))
|
|
hdr.Data = value
|
|
hdr.Cap = int(size)
|
|
hdr.Len = int(size)
|
|
hdr = (*unsafeheader.Slice)(unsafe.Pointer(&dst))
|
|
hdr.Data = unsafe.Pointer(alloc)
|
|
hdr.Cap = int(size)
|
|
hdr.Len = int(size)
|
|
copy(dst, src)
|
|
al.heapAllocations = append(al.heapAllocations, alloc)
|
|
return updateProcThreadAttribute(al.data, 0, attribute, unsafe.Pointer(alloc), size, nil, nil)
|
|
}
|
|
|
|
// Delete frees ProcThreadAttributeList's resources.
|
|
func (al *ProcThreadAttributeListContainer) Delete() {
|
|
deleteProcThreadAttributeList(al.data)
|
|
for i := range al.heapAllocations {
|
|
LocalFree(Handle(al.heapAllocations[i]))
|
|
}
|
|
al.heapAllocations = nil
|
|
}
|
|
|
|
// List returns the actual ProcThreadAttributeList to be passed to StartupInfoEx.
|
|
func (al *ProcThreadAttributeListContainer) List() *ProcThreadAttributeList {
|
|
return al.data
|
|
}
|