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  }