github.com/dorkamotorka/go/src@v0.0.0-20230614113921-187095f0e316/syscall/exec_windows.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Fork, exec, wait, etc. 6 7 package syscall 8 9 import ( 10 "internal/bytealg" 11 "runtime" 12 "sync" 13 "unicode/utf16" 14 "unsafe" 15 ) 16 17 var ForkLock sync.RWMutex 18 19 // EscapeArg rewrites command line argument s as prescribed 20 // in https://msdn.microsoft.com/en-us/library/ms880421. 21 // This function returns "" (2 double quotes) if s is empty. 22 // Alternatively, these transformations are done: 23 // - every back slash (\) is doubled, but only if immediately 24 // followed by double quote ("); 25 // - every double quote (") is escaped by back slash (\); 26 // - finally, s is wrapped with double quotes (arg -> "arg"), 27 // but only if there is space or tab inside s. 28 func EscapeArg(s string) string { 29 if len(s) == 0 { 30 return `""` 31 } 32 for i := 0; i < len(s); i++ { 33 switch s[i] { 34 case '"', '\\', ' ', '\t': 35 // Some escaping required. 36 b := make([]byte, 0, len(s)+2) 37 b = appendEscapeArg(b, s) 38 return string(b) 39 } 40 } 41 return s 42 } 43 44 // appendEscapeArg escapes the string s, as per escapeArg, 45 // appends the result to b, and returns the updated slice. 46 func appendEscapeArg(b []byte, s string) []byte { 47 if len(s) == 0 { 48 return append(b, `""`...) 49 } 50 51 needsBackslash := false 52 hasSpace := false 53 for i := 0; i < len(s); i++ { 54 switch s[i] { 55 case '"', '\\': 56 needsBackslash = true 57 case ' ', '\t': 58 hasSpace = true 59 } 60 } 61 62 if !needsBackslash && !hasSpace { 63 // No special handling required; normal case. 64 return append(b, s...) 65 } 66 if !needsBackslash { 67 // hasSpace is true, so we need to quote the string. 68 b = append(b, '"') 69 b = append(b, s...) 70 return append(b, '"') 71 } 72 73 if hasSpace { 74 b = append(b, '"') 75 } 76 slashes := 0 77 for i := 0; i < len(s); i++ { 78 c := s[i] 79 switch c { 80 default: 81 slashes = 0 82 case '\\': 83 slashes++ 84 case '"': 85 for ; slashes > 0; slashes-- { 86 b = append(b, '\\') 87 } 88 b = append(b, '\\') 89 } 90 b = append(b, c) 91 } 92 if hasSpace { 93 for ; slashes > 0; slashes-- { 94 b = append(b, '\\') 95 } 96 b = append(b, '"') 97 } 98 99 return b 100 } 101 102 // makeCmdLine builds a command line out of args by escaping "special" 103 // characters and joining the arguments with spaces. 104 func makeCmdLine(args []string) string { 105 var b []byte 106 for _, v := range args { 107 if len(b) > 0 { 108 b = append(b, ' ') 109 } 110 b = appendEscapeArg(b, v) 111 } 112 return string(b) 113 } 114 115 // createEnvBlock converts an array of environment strings into 116 // the representation required by CreateProcess: a sequence of NUL 117 // terminated strings followed by a nil. 118 // Last bytes are two UCS-2 NULs, or four NUL bytes. 119 // If any string contains a NUL, it returns (nil, EINVAL). 120 func createEnvBlock(envv []string) (*uint16, error) { 121 if len(envv) == 0 { 122 return &utf16.Encode([]rune("\x00\x00"))[0], nil 123 } 124 length := 0 125 for _, s := range envv { 126 if bytealg.IndexByteString(s, 0) != -1 { 127 return nil, EINVAL 128 } 129 length += len(s) + 1 130 } 131 length += 1 132 133 b := make([]byte, length) 134 i := 0 135 for _, s := range envv { 136 l := len(s) 137 copy(b[i:i+l], []byte(s)) 138 copy(b[i+l:i+l+1], []byte{0}) 139 i = i + l + 1 140 } 141 copy(b[i:i+1], []byte{0}) 142 143 return &utf16.Encode([]rune(string(b)))[0], nil 144 } 145 146 func CloseOnExec(fd Handle) { 147 SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0) 148 } 149 150 func SetNonblock(fd Handle, nonblocking bool) (err error) { 151 return nil 152 } 153 154 // FullPath retrieves the full path of the specified file. 155 func FullPath(name string) (path string, err error) { 156 p, err := UTF16PtrFromString(name) 157 if err != nil { 158 return "", err 159 } 160 n := uint32(100) 161 for { 162 buf := make([]uint16, n) 163 n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil) 164 if err != nil { 165 return "", err 166 } 167 if n <= uint32(len(buf)) { 168 return UTF16ToString(buf[:n]), nil 169 } 170 } 171 } 172 173 func isSlash(c uint8) bool { 174 return c == '\\' || c == '/' 175 } 176 177 func normalizeDir(dir string) (name string, err error) { 178 ndir, err := FullPath(dir) 179 if err != nil { 180 return "", err 181 } 182 if len(ndir) > 2 && isSlash(ndir[0]) && isSlash(ndir[1]) { 183 // dir cannot have \\server\share\path form 184 return "", EINVAL 185 } 186 return ndir, nil 187 } 188 189 func volToUpper(ch int) int { 190 if 'a' <= ch && ch <= 'z' { 191 ch += 'A' - 'a' 192 } 193 return ch 194 } 195 196 func joinExeDirAndFName(dir, p string) (name string, err error) { 197 if len(p) == 0 { 198 return "", EINVAL 199 } 200 if len(p) > 2 && isSlash(p[0]) && isSlash(p[1]) { 201 // \\server\share\path form 202 return p, nil 203 } 204 if len(p) > 1 && p[1] == ':' { 205 // has drive letter 206 if len(p) == 2 { 207 return "", EINVAL 208 } 209 if isSlash(p[2]) { 210 return p, nil 211 } else { 212 d, err := normalizeDir(dir) 213 if err != nil { 214 return "", err 215 } 216 if volToUpper(int(p[0])) == volToUpper(int(d[0])) { 217 return FullPath(d + "\\" + p[2:]) 218 } else { 219 return FullPath(p) 220 } 221 } 222 } else { 223 // no drive letter 224 d, err := normalizeDir(dir) 225 if err != nil { 226 return "", err 227 } 228 if isSlash(p[0]) { 229 return FullPath(d[:2] + p) 230 } else { 231 return FullPath(d + "\\" + p) 232 } 233 } 234 } 235 236 type ProcAttr struct { 237 Dir string 238 Env []string 239 Files []uintptr 240 Sys *SysProcAttr 241 } 242 243 type SysProcAttr struct { 244 HideWindow bool 245 CmdLine string // used if non-empty, else the windows command line is built by escaping the arguments passed to StartProcess 246 CreationFlags uint32 247 Token Token // if set, runs new process in the security context represented by the token 248 ProcessAttributes *SecurityAttributes // if set, applies these security attributes as the descriptor for the new process 249 ThreadAttributes *SecurityAttributes // if set, applies these security attributes as the descriptor for the main thread of the new process 250 NoInheritHandles bool // if set, each inheritable handle in the calling process is not inherited by the new process 251 AdditionalInheritedHandles []Handle // a list of additional handles, already marked as inheritable, that will be inherited by the new process 252 ParentProcess Handle // if non-zero, the new process regards the process given by this handle as its parent process, and AdditionalInheritedHandles, if set, should exist in this parent process 253 } 254 255 var zeroProcAttr ProcAttr 256 var zeroSysProcAttr SysProcAttr 257 258 func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) { 259 if len(argv0) == 0 { 260 return 0, 0, EWINDOWS 261 } 262 if attr == nil { 263 attr = &zeroProcAttr 264 } 265 sys := attr.Sys 266 if sys == nil { 267 sys = &zeroSysProcAttr 268 } 269 270 if len(attr.Files) > 3 { 271 return 0, 0, EWINDOWS 272 } 273 if len(attr.Files) < 3 { 274 return 0, 0, EINVAL 275 } 276 277 if len(attr.Dir) != 0 { 278 // StartProcess assumes that argv0 is relative to attr.Dir, 279 // because it implies Chdir(attr.Dir) before executing argv0. 280 // Windows CreateProcess assumes the opposite: it looks for 281 // argv0 relative to the current directory, and, only once the new 282 // process is started, it does Chdir(attr.Dir). We are adjusting 283 // for that difference here by making argv0 absolute. 284 var err error 285 argv0, err = joinExeDirAndFName(attr.Dir, argv0) 286 if err != nil { 287 return 0, 0, err 288 } 289 } 290 argv0p, err := UTF16PtrFromString(argv0) 291 if err != nil { 292 return 0, 0, err 293 } 294 295 var cmdline string 296 // Windows CreateProcess takes the command line as a single string: 297 // use attr.CmdLine if set, else build the command line by escaping 298 // and joining each argument with spaces 299 if sys.CmdLine != "" { 300 cmdline = sys.CmdLine 301 } else { 302 cmdline = makeCmdLine(argv) 303 } 304 305 var argvp *uint16 306 if len(cmdline) != 0 { 307 argvp, err = UTF16PtrFromString(cmdline) 308 if err != nil { 309 return 0, 0, err 310 } 311 } 312 313 var dirp *uint16 314 if len(attr.Dir) != 0 { 315 dirp, err = UTF16PtrFromString(attr.Dir) 316 if err != nil { 317 return 0, 0, err 318 } 319 } 320 321 var maj, min, build uint32 322 rtlGetNtVersionNumbers(&maj, &min, &build) 323 isWin7 := maj < 6 || (maj == 6 && min <= 1) 324 // NT kernel handles are divisible by 4, with the bottom 3 bits left as 325 // a tag. The fully set tag correlates with the types of handles we're 326 // concerned about here. Except, the kernel will interpret some 327 // special handle values, like -1, -2, and so forth, so kernelbase.dll 328 // checks to see that those bottom three bits are checked, but that top 329 // bit is not checked. 330 isLegacyWin7ConsoleHandle := func(handle Handle) bool { return isWin7 && handle&0x10000003 == 3 } 331 332 p, _ := GetCurrentProcess() 333 parentProcess := p 334 if sys.ParentProcess != 0 { 335 parentProcess = sys.ParentProcess 336 } 337 fd := make([]Handle, len(attr.Files)) 338 for i := range attr.Files { 339 if attr.Files[i] > 0 { 340 destinationProcessHandle := parentProcess 341 342 // On Windows 7, console handles aren't real handles, and can only be duplicated 343 // into the current process, not a parent one, which amounts to the same thing. 344 if parentProcess != p && isLegacyWin7ConsoleHandle(Handle(attr.Files[i])) { 345 destinationProcessHandle = p 346 } 347 348 err := DuplicateHandle(p, Handle(attr.Files[i]), destinationProcessHandle, &fd[i], 0, true, DUPLICATE_SAME_ACCESS) 349 if err != nil { 350 return 0, 0, err 351 } 352 defer DuplicateHandle(parentProcess, fd[i], 0, nil, 0, false, DUPLICATE_CLOSE_SOURCE) 353 } 354 } 355 si := new(_STARTUPINFOEXW) 356 si.ProcThreadAttributeList, err = newProcThreadAttributeList(2) 357 if err != nil { 358 return 0, 0, err 359 } 360 defer deleteProcThreadAttributeList(si.ProcThreadAttributeList) 361 si.Cb = uint32(unsafe.Sizeof(*si)) 362 si.Flags = STARTF_USESTDHANDLES 363 if sys.HideWindow { 364 si.Flags |= STARTF_USESHOWWINDOW 365 si.ShowWindow = SW_HIDE 366 } 367 if sys.ParentProcess != 0 { 368 err = updateProcThreadAttribute(si.ProcThreadAttributeList, 0, _PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, unsafe.Pointer(&sys.ParentProcess), unsafe.Sizeof(sys.ParentProcess), nil, nil) 369 if err != nil { 370 return 0, 0, err 371 } 372 } 373 si.StdInput = fd[0] 374 si.StdOutput = fd[1] 375 si.StdErr = fd[2] 376 377 fd = append(fd, sys.AdditionalInheritedHandles...) 378 379 // On Windows 7, console handles aren't real handles, so don't pass them 380 // through to PROC_THREAD_ATTRIBUTE_HANDLE_LIST. 381 for i := range fd { 382 if isLegacyWin7ConsoleHandle(fd[i]) { 383 fd[i] = 0 384 } 385 } 386 387 // The presence of a NULL handle in the list is enough to cause PROC_THREAD_ATTRIBUTE_HANDLE_LIST 388 // to treat the entire list as empty, so remove NULL handles. 389 j := 0 390 for i := range fd { 391 if fd[i] != 0 { 392 fd[j] = fd[i] 393 j++ 394 } 395 } 396 fd = fd[:j] 397 398 willInheritHandles := len(fd) > 0 && !sys.NoInheritHandles 399 400 // Do not accidentally inherit more than these handles. 401 if willInheritHandles { 402 err = updateProcThreadAttribute(si.ProcThreadAttributeList, 0, _PROC_THREAD_ATTRIBUTE_HANDLE_LIST, unsafe.Pointer(&fd[0]), uintptr(len(fd))*unsafe.Sizeof(fd[0]), nil, nil) 403 if err != nil { 404 return 0, 0, err 405 } 406 } 407 408 envBlock, err := createEnvBlock(attr.Env) 409 if err != nil { 410 return 0, 0, err 411 } 412 413 pi := new(ProcessInformation) 414 flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT | _EXTENDED_STARTUPINFO_PRESENT 415 if sys.Token != 0 { 416 err = CreateProcessAsUser(sys.Token, argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, envBlock, dirp, &si.StartupInfo, pi) 417 } else { 418 err = CreateProcess(argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, envBlock, dirp, &si.StartupInfo, pi) 419 } 420 if err != nil { 421 return 0, 0, err 422 } 423 defer CloseHandle(Handle(pi.Thread)) 424 runtime.KeepAlive(fd) 425 runtime.KeepAlive(sys) 426 427 return int(pi.ProcessId), uintptr(pi.Process), nil 428 } 429 430 func Exec(argv0 string, argv []string, envv []string) (err error) { 431 return EWINDOWS 432 }