mirror of
https://github.com/golang/go.git
synced 2025-10-20 03:23:18 +00:00
syscall: sort Windows env block in StartProcess
Fixes #29530 Change-Id: Ia28c78274b9288bfa5de9ccb142a452d291a5b66 Reviewed-on: https://go-review.googlesource.com/c/go/+/694435 Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Ian Lance Taylor <iant@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Cherry Mui <cherryyz@google.com> Reviewed-by: Joseph Tsai <joetsai@digital-static.net> Auto-Submit: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
bfd130db02
commit
fa18c547cd
3 changed files with 84 additions and 0 deletions
|
@ -9,6 +9,7 @@ package syscall
|
||||||
import (
|
import (
|
||||||
"internal/bytealg"
|
"internal/bytealg"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
@ -113,6 +114,49 @@ func makeCmdLine(args []string) string {
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func envSorted(envv []string) []string {
|
||||||
|
if len(envv) < 2 {
|
||||||
|
return envv
|
||||||
|
}
|
||||||
|
|
||||||
|
lowerKeyCache := map[string][]byte{} // lowercased keys to avoid recomputing them in sort
|
||||||
|
lowerKey := func(kv string) []byte {
|
||||||
|
eq := bytealg.IndexByteString(kv, '=')
|
||||||
|
if eq < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
k := kv[:eq]
|
||||||
|
v, ok := lowerKeyCache[k]
|
||||||
|
if !ok {
|
||||||
|
v = []byte(k)
|
||||||
|
for i, b := range v {
|
||||||
|
// We only normalize ASCII for now.
|
||||||
|
// In practice, all environment variables are ASCII, and the
|
||||||
|
// syscall package can't import "unicode" anyway.
|
||||||
|
// Also, per https://nullprogram.com/blog/2023/08/23/ the
|
||||||
|
// sorting of environment variables doesn't really matter.
|
||||||
|
// TODO(bradfitz): use RtlCompareUnicodeString instead,
|
||||||
|
// per that blog post? For now, ASCII is good enough.
|
||||||
|
if 'a' <= b && b <= 'z' {
|
||||||
|
v[i] -= 'a' - 'A'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lowerKeyCache[k] = v
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
cmpEnv := func(a, b string) int {
|
||||||
|
return bytealg.Compare(lowerKey(a), lowerKey(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !slices.IsSortedFunc(envv, cmpEnv) {
|
||||||
|
envv = slices.Clone(envv)
|
||||||
|
slices.SortFunc(envv, cmpEnv)
|
||||||
|
}
|
||||||
|
return envv
|
||||||
|
}
|
||||||
|
|
||||||
// createEnvBlock converts an array of environment strings into
|
// createEnvBlock converts an array of environment strings into
|
||||||
// the representation required by CreateProcess: a sequence of NUL
|
// the representation required by CreateProcess: a sequence of NUL
|
||||||
// terminated strings followed by a nil.
|
// terminated strings followed by a nil.
|
||||||
|
@ -122,6 +166,12 @@ func createEnvBlock(envv []string) ([]uint16, error) {
|
||||||
if len(envv) == 0 {
|
if len(envv) == 0 {
|
||||||
return utf16.Encode([]rune("\x00\x00")), nil
|
return utf16.Encode([]rune("\x00\x00")), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/procthread/changing-environment-variables
|
||||||
|
// says that: "All strings in the environment block must be sorted
|
||||||
|
// alphabetically by name."
|
||||||
|
envv = envSorted(envv)
|
||||||
|
|
||||||
var length int
|
var length int
|
||||||
for _, s := range envv {
|
for _, s := range envv {
|
||||||
if bytealg.IndexByteString(s, 0) != -1 {
|
if bytealg.IndexByteString(s, 0) != -1 {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -48,6 +49,37 @@ func TestEscapeArg(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEnvBlockSorted(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
env []string
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
env: []string{"A=1"},
|
||||||
|
want: []string{"A=1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
env: []string{"A=1", "B=2", "C=3"},
|
||||||
|
want: []string{"A=1", "B=2", "C=3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
env: []string{"C=3", "B=2", "A=1"},
|
||||||
|
want: []string{"A=1", "B=2", "C=3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
env: []string{"c=3", "B=2", "a=1"},
|
||||||
|
want: []string{"a=1", "B=2", "c=3"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := syscall.EnvSorted(tt.env)
|
||||||
|
if !slices.Equal(got, tt.want) {
|
||||||
|
t.Errorf("EnvSorted(%q) = %q, want %q", tt.env, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestChangingProcessParent(t *testing.T) {
|
func TestChangingProcessParent(t *testing.T) {
|
||||||
if os.Getenv("GO_WANT_HELPER_PROCESS") == "parent" {
|
if os.Getenv("GO_WANT_HELPER_PROCESS") == "parent" {
|
||||||
// in parent process
|
// in parent process
|
||||||
|
|
|
@ -12,3 +12,5 @@ const PROC_THREAD_ATTRIBUTE_HANDLE_LIST = _PROC_THREAD_ATTRIBUTE_HANDLE_LIST
|
||||||
|
|
||||||
var EncodeWTF16 = encodeWTF16
|
var EncodeWTF16 = encodeWTF16
|
||||||
var DecodeWTF16 = decodeWTF16
|
var DecodeWTF16 = decodeWTF16
|
||||||
|
|
||||||
|
var EnvSorted = envSorted
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue