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