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  }