...
Run Format

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 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	// 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	}
   226	
   227	var zeroProcAttr ProcAttr
   228	var zeroSysProcAttr SysProcAttr
   229	
   230	func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
   231		if len(argv0) == 0 {
   232			return 0, 0, EWINDOWS
   233		}
   234		if attr == nil {
   235			attr = &zeroProcAttr
   236		}
   237		sys := attr.Sys
   238		if sys == nil {
   239			sys = &zeroSysProcAttr
   240		}
   241	
   242		if len(attr.Files) > 3 {
   243			return 0, 0, EWINDOWS
   244		}
   245		if len(attr.Files) < 3 {
   246			return 0, 0, EINVAL
   247		}
   248	
   249		if len(attr.Dir) != 0 {
   250			// StartProcess assumes that argv0 is relative to attr.Dir,
   251			// because it implies Chdir(attr.Dir) before executing argv0.
   252			// Windows CreateProcess assumes the opposite: it looks for
   253			// argv0 relative to the current directory, and, only once the new
   254			// process is started, it does Chdir(attr.Dir). We are adjusting
   255			// for that difference here by making argv0 absolute.
   256			var err error
   257			argv0, err = joinExeDirAndFName(attr.Dir, argv0)
   258			if err != nil {
   259				return 0, 0, err
   260			}
   261		}
   262		argv0p, err := UTF16PtrFromString(argv0)
   263		if err != nil {
   264			return 0, 0, err
   265		}
   266	
   267		var cmdline string
   268		// Windows CreateProcess takes the command line as a single string:
   269		// use attr.CmdLine if set, else build the command line by escaping
   270		// and joining each argument with spaces
   271		if sys.CmdLine != "" {
   272			cmdline = sys.CmdLine
   273		} else {
   274			cmdline = makeCmdLine(argv)
   275		}
   276	
   277		var argvp *uint16
   278		if len(cmdline) != 0 {
   279			argvp, err = UTF16PtrFromString(cmdline)
   280			if err != nil {
   281				return 0, 0, err
   282			}
   283		}
   284	
   285		var dirp *uint16
   286		if len(attr.Dir) != 0 {
   287			dirp, err = UTF16PtrFromString(attr.Dir)
   288			if err != nil {
   289				return 0, 0, err
   290			}
   291		}
   292	
   293		// Acquire the fork lock so that no other threads
   294		// create new fds that are not yet close-on-exec
   295		// before we fork.
   296		ForkLock.Lock()
   297		defer ForkLock.Unlock()
   298	
   299		p, _ := GetCurrentProcess()
   300		fd := make([]Handle, len(attr.Files))
   301		for i := range attr.Files {
   302			if attr.Files[i] > 0 {
   303				err := DuplicateHandle(p, Handle(attr.Files[i]), p, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
   304				if err != nil {
   305					return 0, 0, err
   306				}
   307				defer CloseHandle(Handle(fd[i]))
   308			}
   309		}
   310		si := new(StartupInfo)
   311		si.Cb = uint32(unsafe.Sizeof(*si))
   312		si.Flags = STARTF_USESTDHANDLES
   313		if sys.HideWindow {
   314			si.Flags |= STARTF_USESHOWWINDOW
   315			si.ShowWindow = SW_HIDE
   316		}
   317		si.StdInput = fd[0]
   318		si.StdOutput = fd[1]
   319		si.StdErr = fd[2]
   320	
   321		pi := new(ProcessInformation)
   322	
   323		flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT
   324		err = CreateProcess(argv0p, argvp, nil, nil, true, flags, createEnvBlock(attr.Env), dirp, si, pi)
   325		if err != nil {
   326			return 0, 0, err
   327		}
   328		defer CloseHandle(Handle(pi.Thread))
   329	
   330		return int(pi.ProcessId), uintptr(pi.Process), nil
   331	}
   332	
   333	func Exec(argv0 string, argv []string, envv []string) (err error) {
   334		return EWINDOWS
   335	}
   336	

View as plain text