github.com/qichengzx/mattermost-server@v4.5.1-0.20180604164826-2c75247c97d0+incompatible/plugin/rpcplugin/process_windows.go (about) 1 package rpcplugin 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "runtime" 12 "strconv" 13 "strings" 14 "syscall" 15 "unicode/utf16" 16 "unsafe" 17 18 pkgerrors "github.com/pkg/errors" 19 ) 20 21 type process struct { 22 command *cmd 23 } 24 25 func newProcess(ctx context.Context, path string) (Process, io.ReadWriteCloser, error) { 26 ipc, childFiles, err := NewIPC() 27 if err != nil { 28 return nil, nil, err 29 } 30 defer childFiles[0].Close() 31 defer childFiles[1].Close() 32 33 cmd := commandContext(ctx, path) 34 cmd.Stdout = os.Stdout 35 cmd.Stderr = os.Stderr 36 cmd.ExtraFiles = childFiles 37 cmd.Env = append(os.Environ(), 38 fmt.Sprintf("MM_IPC_FD0=%v", childFiles[0].Fd()), 39 fmt.Sprintf("MM_IPC_FD1=%v", childFiles[1].Fd()), 40 ) 41 err = cmd.Start() 42 if err != nil { 43 ipc.Close() 44 return nil, nil, err 45 } 46 47 return &process{ 48 command: cmd, 49 }, ipc, nil 50 } 51 52 func (p *process) Wait() error { 53 return p.command.Wait() 54 } 55 56 func inheritedProcessIPC() (io.ReadWriteCloser, error) { 57 fd0, err := strconv.ParseUint(os.Getenv("MM_IPC_FD0"), 0, 64) 58 if err != nil { 59 return nil, pkgerrors.Wrapf(err, "unable to get ipc file descriptor 0") 60 } 61 fd1, err := strconv.ParseUint(os.Getenv("MM_IPC_FD1"), 0, 64) 62 if err != nil { 63 return nil, pkgerrors.Wrapf(err, "unable to get ipc file descriptor 1") 64 } 65 return InheritedIPC(uintptr(fd0), uintptr(fd1)) 66 } 67 68 // XXX: EVERYTHING BELOW THIS IS COPIED / PASTED STANDARD LIBRARY CODE! 69 // IT CAN BE DELETED IF / WHEN THIS ISSUE IS RESOLVED: https://github.com/golang/go/issues/21085 70 71 // Just about all of os/exec/exec.go is copied / pasted below, altered to use our modified startProcess functions even 72 // further below. 73 74 type cmd struct { 75 // Path is the path of the command to run. 76 // 77 // This is the only field that must be set to a non-zero 78 // value. If Path is relative, it is evaluated relative 79 // to Dir. 80 Path string 81 82 // Args holds command line arguments, including the command as Args[0]. 83 // If the Args field is empty or nil, Run uses {Path}. 84 // 85 // In typical use, both Path and Args are set by calling Command. 86 Args []string 87 88 // Env specifies the environment of the process. 89 // If Env is nil, Run uses the current process's environment. 90 Env []string 91 92 // Dir specifies the working directory of the command. 93 // If Dir is the empty string, Run runs the command in the 94 // calling process's current directory. 95 Dir string 96 97 // Stdin specifies the process's standard input. 98 // If Stdin is nil, the process reads from the null device (os.DevNull). 99 // If Stdin is an *os.File, the process's standard input is connected 100 // directly to that file. 101 // Otherwise, during the execution of the command a separate 102 // goroutine reads from Stdin and delivers that data to the command 103 // over a pipe. In this case, Wait does not complete until the goroutine 104 // stops copying, either because it has reached the end of Stdin 105 // (EOF or a read error) or because writing to the pipe returned an error. 106 Stdin io.Reader 107 108 // Stdout and Stderr specify the process's standard output and error. 109 // 110 // If either is nil, Run connects the corresponding file descriptor 111 // to the null device (os.DevNull). 112 // 113 // If Stdout and Stderr are the same writer, at most one 114 // goroutine at a time will call Write. 115 Stdout io.Writer 116 Stderr io.Writer 117 118 // ExtraFiles specifies additional open files to be inherited by the 119 // new process. It does not include standard input, standard output, or 120 // standard error. If non-nil, entry i becomes file descriptor 3+i. 121 // 122 // BUG(rsc): On OS X 10.6, child processes may sometimes inherit unwanted fds. 123 // https://golang.org/issue/2603 124 ExtraFiles []*os.File 125 126 // SysProcAttr holds optional, operating system-specific attributes. 127 // Run passes it to os.StartProcess as the os.ProcAttr's Sys field. 128 SysProcAttr *syscall.SysProcAttr 129 130 // Process is the underlying process, once started. 131 Process *os.Process 132 133 // ProcessState contains information about an exited process, 134 // available after a call to Wait or Run. 135 ProcessState *os.ProcessState 136 137 ctx context.Context // nil means none 138 lookPathErr error // LookPath error, if any. 139 finished bool // when Wait was called 140 childFiles []*os.File 141 closeAfterStart []io.Closer 142 closeAfterWait []io.Closer 143 goroutine []func() error 144 errch chan error // one send per goroutine 145 waitDone chan struct{} 146 } 147 148 func command(name string, arg ...string) *cmd { 149 cmd := &cmd{ 150 Path: name, 151 Args: append([]string{name}, arg...), 152 } 153 if filepath.Base(name) == name { 154 if lp, err := exec.LookPath(name); err != nil { 155 cmd.lookPathErr = err 156 } else { 157 cmd.Path = lp 158 } 159 } 160 return cmd 161 } 162 163 func commandContext(ctx context.Context, name string, arg ...string) *cmd { 164 if ctx == nil { 165 panic("nil Context") 166 } 167 cmd := command(name, arg...) 168 cmd.ctx = ctx 169 return cmd 170 } 171 172 func interfaceEqual(a, b interface{}) bool { 173 defer func() { 174 recover() 175 }() 176 return a == b 177 } 178 179 func (c *cmd) envv() []string { 180 if c.Env != nil { 181 return c.Env 182 } 183 return os.Environ() 184 } 185 186 func (c *cmd) argv() []string { 187 if len(c.Args) > 0 { 188 return c.Args 189 } 190 return []string{c.Path} 191 } 192 193 var skipStdinCopyError func(error) bool 194 195 func (c *cmd) stdin() (f *os.File, err error) { 196 if c.Stdin == nil { 197 f, err = os.Open(os.DevNull) 198 if err != nil { 199 return 200 } 201 c.closeAfterStart = append(c.closeAfterStart, f) 202 return 203 } 204 205 if f, ok := c.Stdin.(*os.File); ok { 206 return f, nil 207 } 208 209 pr, pw, err := os.Pipe() 210 if err != nil { 211 return 212 } 213 214 c.closeAfterStart = append(c.closeAfterStart, pr) 215 c.closeAfterWait = append(c.closeAfterWait, pw) 216 c.goroutine = append(c.goroutine, func() error { 217 _, err := io.Copy(pw, c.Stdin) 218 if skip := skipStdinCopyError; skip != nil && skip(err) { 219 err = nil 220 } 221 if err1 := pw.Close(); err == nil { 222 err = err1 223 } 224 return err 225 }) 226 return pr, nil 227 } 228 229 func (c *cmd) stdout() (f *os.File, err error) { 230 return c.writerDescriptor(c.Stdout) 231 } 232 233 func (c *cmd) stderr() (f *os.File, err error) { 234 if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) { 235 return c.childFiles[1], nil 236 } 237 return c.writerDescriptor(c.Stderr) 238 } 239 240 func (c *cmd) writerDescriptor(w io.Writer) (f *os.File, err error) { 241 if w == nil { 242 f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0) 243 if err != nil { 244 return 245 } 246 c.closeAfterStart = append(c.closeAfterStart, f) 247 return 248 } 249 250 if f, ok := w.(*os.File); ok { 251 return f, nil 252 } 253 254 pr, pw, err := os.Pipe() 255 if err != nil { 256 return 257 } 258 259 c.closeAfterStart = append(c.closeAfterStart, pw) 260 c.closeAfterWait = append(c.closeAfterWait, pr) 261 c.goroutine = append(c.goroutine, func() error { 262 _, err := io.Copy(w, pr) 263 pr.Close() // in case io.Copy stopped due to write error 264 return err 265 }) 266 return pw, nil 267 } 268 269 func (c *cmd) closeDescriptors(closers []io.Closer) { 270 for _, fd := range closers { 271 fd.Close() 272 } 273 } 274 275 func lookExtensions(path, dir string) (string, error) { 276 if filepath.Base(path) == path { 277 path = filepath.Join(".", path) 278 } 279 if dir == "" { 280 return exec.LookPath(path) 281 } 282 if filepath.VolumeName(path) != "" { 283 return exec.LookPath(path) 284 } 285 if len(path) > 1 && os.IsPathSeparator(path[0]) { 286 return exec.LookPath(path) 287 } 288 dirandpath := filepath.Join(dir, path) 289 // We assume that LookPath will only add file extension. 290 lp, err := exec.LookPath(dirandpath) 291 if err != nil { 292 return "", err 293 } 294 ext := strings.TrimPrefix(lp, dirandpath) 295 return path + ext, nil 296 } 297 298 // Copied from os/exec/exec.go, altered to use osStartProcess (defined below). 299 func (c *cmd) Start() error { 300 if c.lookPathErr != nil { 301 c.closeDescriptors(c.closeAfterStart) 302 c.closeDescriptors(c.closeAfterWait) 303 return c.lookPathErr 304 } 305 if runtime.GOOS == "windows" { 306 lp, err := lookExtensions(c.Path, c.Dir) 307 if err != nil { 308 c.closeDescriptors(c.closeAfterStart) 309 c.closeDescriptors(c.closeAfterWait) 310 return err 311 } 312 c.Path = lp 313 } 314 if c.Process != nil { 315 return errors.New("exec: already started") 316 } 317 if c.ctx != nil { 318 select { 319 case <-c.ctx.Done(): 320 c.closeDescriptors(c.closeAfterStart) 321 c.closeDescriptors(c.closeAfterWait) 322 return c.ctx.Err() 323 default: 324 } 325 } 326 327 type F func(*cmd) (*os.File, error) 328 for _, setupFd := range []F{(*cmd).stdin, (*cmd).stdout, (*cmd).stderr} { 329 fd, err := setupFd(c) 330 if err != nil { 331 c.closeDescriptors(c.closeAfterStart) 332 c.closeDescriptors(c.closeAfterWait) 333 return err 334 } 335 c.childFiles = append(c.childFiles, fd) 336 } 337 c.childFiles = append(c.childFiles, c.ExtraFiles...) 338 339 var err error 340 c.Process, err = osStartProcess(c.Path, c.argv(), &os.ProcAttr{ 341 Dir: c.Dir, 342 Files: c.childFiles, 343 Env: c.envv(), 344 Sys: c.SysProcAttr, 345 }) 346 if err != nil { 347 c.closeDescriptors(c.closeAfterStart) 348 c.closeDescriptors(c.closeAfterWait) 349 return err 350 } 351 352 c.closeDescriptors(c.closeAfterStart) 353 354 c.errch = make(chan error, len(c.goroutine)) 355 for _, fn := range c.goroutine { 356 go func(fn func() error) { 357 c.errch <- fn() 358 }(fn) 359 } 360 361 if c.ctx != nil { 362 c.waitDone = make(chan struct{}) 363 go func() { 364 select { 365 case <-c.ctx.Done(): 366 c.Process.Kill() 367 case <-c.waitDone: 368 } 369 }() 370 } 371 372 return nil 373 } 374 375 func (c *cmd) Wait() error { 376 if c.Process == nil { 377 return errors.New("exec: not started") 378 } 379 if c.finished { 380 return errors.New("exec: Wait was already called") 381 } 382 c.finished = true 383 384 state, err := c.Process.Wait() 385 if c.waitDone != nil { 386 close(c.waitDone) 387 } 388 c.ProcessState = state 389 390 var copyError error 391 for range c.goroutine { 392 if err := <-c.errch; err != nil && copyError == nil { 393 copyError = err 394 } 395 } 396 397 c.closeDescriptors(c.closeAfterWait) 398 399 if err != nil { 400 return err 401 } else if !state.Success() { 402 return &exec.ExitError{ProcessState: state} 403 } 404 405 return copyError 406 } 407 408 // Copied from os/exec_posix.go, altered to use syscallStartProcess (defined below). 409 func osStartProcess(name string, argv []string, attr *os.ProcAttr) (p *os.Process, err error) { 410 // If there is no SysProcAttr (ie. no Chroot or changed 411 // UID/GID), double-check existence of the directory we want 412 // to chdir into. We can make the error clearer this way. 413 if attr != nil && attr.Sys == nil && attr.Dir != "" { 414 if _, err := os.Stat(attr.Dir); err != nil { 415 pe := err.(*os.PathError) 416 pe.Op = "chdir" 417 return nil, pe 418 } 419 } 420 421 sysattr := &syscall.ProcAttr{ 422 Dir: attr.Dir, 423 Env: attr.Env, 424 Sys: attr.Sys, 425 } 426 if sysattr.Env == nil { 427 sysattr.Env = os.Environ() 428 } 429 for _, f := range attr.Files { 430 sysattr.Files = append(sysattr.Files, f.Fd()) 431 } 432 433 pid, _, e := syscallStartProcess(name, argv, sysattr) 434 if e != nil { 435 return nil, &os.PathError{Op: "fork/exec", Path: name, Err: e} 436 } 437 return os.FindProcess(pid) 438 } 439 440 // Everything from this point on is copied from syscall/exec_windows.go 441 442 func makeCmdLine(args []string) string { 443 var s string 444 for _, v := range args { 445 if s != "" { 446 s += " " 447 } 448 s += syscall.EscapeArg(v) 449 } 450 return s 451 } 452 453 func createEnvBlock(envv []string) *uint16 { 454 if len(envv) == 0 { 455 return &utf16.Encode([]rune("\x00\x00"))[0] 456 } 457 length := 0 458 for _, s := range envv { 459 length += len(s) + 1 460 } 461 length += 1 462 463 b := make([]byte, length) 464 i := 0 465 for _, s := range envv { 466 l := len(s) 467 copy(b[i:i+l], []byte(s)) 468 copy(b[i+l:i+l+1], []byte{0}) 469 i = i + l + 1 470 } 471 copy(b[i:i+1], []byte{0}) 472 473 return &utf16.Encode([]rune(string(b)))[0] 474 } 475 476 func isSlash(c uint8) bool { 477 return c == '\\' || c == '/' 478 } 479 480 func normalizeDir(dir string) (name string, err error) { 481 ndir, err := syscall.FullPath(dir) 482 if err != nil { 483 return "", err 484 } 485 if len(ndir) > 2 && isSlash(ndir[0]) && isSlash(ndir[1]) { 486 // dir cannot have \\server\share\path form 487 return "", syscall.EINVAL 488 } 489 return ndir, nil 490 } 491 492 func volToUpper(ch int) int { 493 if 'a' <= ch && ch <= 'z' { 494 ch += 'A' - 'a' 495 } 496 return ch 497 } 498 499 func joinExeDirAndFName(dir, p string) (name string, err error) { 500 if len(p) == 0 { 501 return "", syscall.EINVAL 502 } 503 if len(p) > 2 && isSlash(p[0]) && isSlash(p[1]) { 504 // \\server\share\path form 505 return p, nil 506 } 507 if len(p) > 1 && p[1] == ':' { 508 // has drive letter 509 if len(p) == 2 { 510 return "", syscall.EINVAL 511 } 512 if isSlash(p[2]) { 513 return p, nil 514 } else { 515 d, err := normalizeDir(dir) 516 if err != nil { 517 return "", err 518 } 519 if volToUpper(int(p[0])) == volToUpper(int(d[0])) { 520 return syscall.FullPath(d + "\\" + p[2:]) 521 } else { 522 return syscall.FullPath(p) 523 } 524 } 525 } else { 526 // no drive letter 527 d, err := normalizeDir(dir) 528 if err != nil { 529 return "", err 530 } 531 if isSlash(p[0]) { 532 return syscall.FullPath(d[:2] + p) 533 } else { 534 return syscall.FullPath(d + "\\" + p) 535 } 536 } 537 } 538 539 var zeroProcAttr syscall.ProcAttr 540 var zeroSysProcAttr syscall.SysProcAttr 541 542 // Has minor changes to support file inheritance. 543 func syscallStartProcess(argv0 string, argv []string, attr *syscall.ProcAttr) (pid int, handle uintptr, err error) { 544 if len(argv0) == 0 { 545 return 0, 0, syscall.EWINDOWS 546 } 547 if attr == nil { 548 attr = &zeroProcAttr 549 } 550 sys := attr.Sys 551 if sys == nil { 552 sys = &zeroSysProcAttr 553 } 554 555 if len(attr.Files) < 3 { 556 return 0, 0, syscall.EINVAL 557 } 558 559 if len(attr.Dir) != 0 { 560 // StartProcess assumes that argv0 is relative to attr.Dir, 561 // because it implies Chdir(attr.Dir) before executing argv0. 562 // Windows CreateProcess assumes the opposite: it looks for 563 // argv0 relative to the current directory, and, only once the new 564 // process is started, it does Chdir(attr.Dir). We are adjusting 565 // for that difference here by making argv0 absolute. 566 var err error 567 argv0, err = joinExeDirAndFName(attr.Dir, argv0) 568 if err != nil { 569 return 0, 0, err 570 } 571 } 572 argv0p, err := syscall.UTF16PtrFromString(argv0) 573 if err != nil { 574 return 0, 0, err 575 } 576 577 var cmdline string 578 // Windows CreateProcess takes the command line as a single string: 579 // use attr.CmdLine if set, else build the command line by escaping 580 // and joining each argument with spaces 581 if sys.CmdLine != "" { 582 cmdline = sys.CmdLine 583 } else { 584 cmdline = makeCmdLine(argv) 585 } 586 587 var argvp *uint16 588 if len(cmdline) != 0 { 589 argvp, err = syscall.UTF16PtrFromString(cmdline) 590 if err != nil { 591 return 0, 0, err 592 } 593 } 594 595 var dirp *uint16 596 if len(attr.Dir) != 0 { 597 dirp, err = syscall.UTF16PtrFromString(attr.Dir) 598 if err != nil { 599 return 0, 0, err 600 } 601 } 602 603 // Acquire the fork lock so that no other threads 604 // create new fds that are not yet close-on-exec 605 // before we fork. 606 syscall.ForkLock.Lock() 607 defer syscall.ForkLock.Unlock() 608 609 p, _ := syscall.GetCurrentProcess() 610 fd := make([]syscall.Handle, len(attr.Files)) 611 for i := range attr.Files { 612 if attr.Files[i] <= 0 { 613 continue 614 } 615 if i < 3 { 616 err := syscall.DuplicateHandle(p, syscall.Handle(attr.Files[i]), p, &fd[i], 0, true, syscall.DUPLICATE_SAME_ACCESS) 617 if err != nil { 618 return 0, 0, err 619 } 620 defer syscall.CloseHandle(syscall.Handle(fd[i])) 621 } else { 622 // This is the modification that allows files to be inherited. 623 syscall.SetHandleInformation(syscall.Handle(attr.Files[i]), syscall.HANDLE_FLAG_INHERIT, 1) 624 defer syscall.SetHandleInformation(syscall.Handle(attr.Files[i]), syscall.HANDLE_FLAG_INHERIT, 0) 625 } 626 } 627 si := new(syscall.StartupInfo) 628 si.Cb = uint32(unsafe.Sizeof(*si)) 629 si.Flags = syscall.STARTF_USESTDHANDLES 630 if sys.HideWindow { 631 si.Flags |= syscall.STARTF_USESHOWWINDOW 632 si.ShowWindow = syscall.SW_HIDE 633 } 634 si.StdInput = fd[0] 635 si.StdOutput = fd[1] 636 si.StdErr = fd[2] 637 638 pi := new(syscall.ProcessInformation) 639 640 flags := sys.CreationFlags | syscall.CREATE_UNICODE_ENVIRONMENT 641 err = syscall.CreateProcess(argv0p, argvp, nil, nil, true, flags, createEnvBlock(attr.Env), dirp, si, pi) 642 if err != nil { 643 return 0, 0, err 644 } 645 defer syscall.CloseHandle(syscall.Handle(pi.Thread)) 646 647 return int(pi.ProcessId), uintptr(pi.Process), nil 648 }