...

Source file src/syscall/exec_windows.go

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

View as plain text