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