github.com/spotify/syslog-redirector-golang@v0.0.0-20140320174030-4859f03d829a/src/pkg/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 http://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 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. 86 func 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. 101 func 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 124 func CloseOnExec(fd Handle) { 125 SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0) 126 } 127 128 func SetNonblock(fd Handle, nonblocking bool) (err error) { 129 return nil 130 } 131 132 // getFullPath retrieves the full path of the specified file. 133 // Just a wrapper for Windows GetFullPathName api. 134 func getFullPath(name string) (path string, err error) { 135 p, err := UTF16PtrFromString(name) 136 if err != nil { 137 return "", err 138 } 139 buf := make([]uint16, 100) 140 n, err := GetFullPathName(p, uint32(len(buf)), &buf[0], nil) 141 if err != nil { 142 return "", err 143 } 144 if n > uint32(len(buf)) { 145 // Windows is asking for bigger buffer. 146 buf = make([]uint16, n) 147 n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil) 148 if err != nil { 149 return "", err 150 } 151 if n > uint32(len(buf)) { 152 return "", EINVAL 153 } 154 } 155 return UTF16ToString(buf[:n]), nil 156 } 157 158 func isSlash(c uint8) bool { 159 return c == '\\' || c == '/' 160 } 161 162 func normalizeDir(dir string) (name string, err error) { 163 ndir, err := getFullPath(dir) 164 if err != nil { 165 return "", err 166 } 167 if len(ndir) > 2 && isSlash(ndir[0]) && isSlash(ndir[1]) { 168 // dir cannot have \\server\share\path form 169 return "", EINVAL 170 } 171 return ndir, nil 172 } 173 174 func volToUpper(ch int) int { 175 if 'a' <= ch && ch <= 'z' { 176 ch += 'A' - 'a' 177 } 178 return ch 179 } 180 181 func joinExeDirAndFName(dir, p string) (name string, err error) { 182 if len(p) == 0 { 183 return "", EINVAL 184 } 185 if len(p) > 2 && isSlash(p[0]) && isSlash(p[1]) { 186 // \\server\share\path form 187 return p, nil 188 } 189 if len(p) > 1 && p[1] == ':' { 190 // has drive letter 191 if len(p) == 2 { 192 return "", EINVAL 193 } 194 if isSlash(p[2]) { 195 return p, nil 196 } else { 197 d, err := normalizeDir(dir) 198 if err != nil { 199 return "", err 200 } 201 if volToUpper(int(p[0])) == volToUpper(int(d[0])) { 202 return getFullPath(d + "\\" + p[2:]) 203 } else { 204 return getFullPath(p) 205 } 206 } 207 } else { 208 // no drive letter 209 d, err := normalizeDir(dir) 210 if err != nil { 211 return "", err 212 } 213 if isSlash(p[0]) { 214 return getFullPath(d[:2] + p) 215 } else { 216 return getFullPath(d + "\\" + p) 217 } 218 } 219 // we shouldn't be here 220 return "", EINVAL 221 } 222 223 type ProcAttr struct { 224 Dir string 225 Env []string 226 Files []uintptr 227 Sys *SysProcAttr 228 } 229 230 type SysProcAttr struct { 231 HideWindow bool 232 CmdLine string // used if non-empty, else the windows command line is built by escaping the arguments passed to StartProcess 233 CreationFlags uint32 234 } 235 236 var zeroProcAttr ProcAttr 237 var zeroSysProcAttr SysProcAttr 238 239 func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) { 240 if len(argv0) == 0 { 241 return 0, 0, EWINDOWS 242 } 243 if attr == nil { 244 attr = &zeroProcAttr 245 } 246 sys := attr.Sys 247 if sys == nil { 248 sys = &zeroSysProcAttr 249 } 250 251 if len(attr.Files) > 3 { 252 return 0, 0, EWINDOWS 253 } 254 255 if len(attr.Dir) != 0 { 256 // StartProcess assumes that argv0 is relative to attr.Dir, 257 // because it implies Chdir(attr.Dir) before executing argv0. 258 // Windows CreateProcess assumes the opposite: it looks for 259 // argv0 relative to the current directory, and, only once the new 260 // process is started, it does Chdir(attr.Dir). We are adjusting 261 // for that difference here by making argv0 absolute. 262 var err error 263 argv0, err = joinExeDirAndFName(attr.Dir, argv0) 264 if err != nil { 265 return 0, 0, err 266 } 267 } 268 argv0p, err := UTF16PtrFromString(argv0) 269 if err != nil { 270 return 0, 0, err 271 } 272 273 var cmdline string 274 // Windows CreateProcess takes the command line as a single string: 275 // use attr.CmdLine if set, else build the command line by escaping 276 // and joining each argument with spaces 277 if sys.CmdLine != "" { 278 cmdline = sys.CmdLine 279 } else { 280 cmdline = makeCmdLine(argv) 281 } 282 283 var argvp *uint16 284 if len(cmdline) != 0 { 285 argvp, err = UTF16PtrFromString(cmdline) 286 if err != nil { 287 return 0, 0, err 288 } 289 } 290 291 var dirp *uint16 292 if len(attr.Dir) != 0 { 293 dirp, err = UTF16PtrFromString(attr.Dir) 294 if err != nil { 295 return 0, 0, err 296 } 297 } 298 299 // Acquire the fork lock so that no other threads 300 // create new fds that are not yet close-on-exec 301 // before we fork. 302 ForkLock.Lock() 303 defer ForkLock.Unlock() 304 305 p, _ := GetCurrentProcess() 306 fd := make([]Handle, len(attr.Files)) 307 for i := range attr.Files { 308 if attr.Files[i] > 0 { 309 err := DuplicateHandle(p, Handle(attr.Files[i]), p, &fd[i], 0, true, DUPLICATE_SAME_ACCESS) 310 if err != nil { 311 return 0, 0, err 312 } 313 defer CloseHandle(Handle(fd[i])) 314 } 315 } 316 si := new(StartupInfo) 317 si.Cb = uint32(unsafe.Sizeof(*si)) 318 si.Flags = STARTF_USESTDHANDLES 319 if sys.HideWindow { 320 si.Flags |= STARTF_USESHOWWINDOW 321 si.ShowWindow = SW_HIDE 322 } 323 si.StdInput = fd[0] 324 si.StdOutput = fd[1] 325 si.StdErr = fd[2] 326 327 pi := new(ProcessInformation) 328 329 flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT 330 err = CreateProcess(argv0p, argvp, nil, nil, true, flags, createEnvBlock(attr.Env), dirp, si, pi) 331 if err != nil { 332 return 0, 0, err 333 } 334 defer CloseHandle(Handle(pi.Thread)) 335 336 return int(pi.ProcessId), uintptr(pi.Process), nil 337 } 338 339 func Exec(argv0 string, argv []string, envv []string) (err error) { 340 return EWINDOWS 341 }