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