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