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