exec_windows.go (4944B)
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 windows 8 9 import ( 10 errorspkg "errors" 11 "unsafe" 12 ) 13 14 // EscapeArg rewrites command line argument s as prescribed 15 // in http://msdn.microsoft.com/en-us/library/ms880421. 16 // This function returns "" (2 double quotes) if s is empty. 17 // Alternatively, these transformations are done: 18 // - every back slash (\) is doubled, but only if immediately 19 // followed by double quote ("); 20 // - every double quote (") is escaped by back slash (\); 21 // - finally, s is wrapped with double quotes (arg -> "arg"), 22 // but only if there is space or tab inside s. 23 func EscapeArg(s string) string { 24 if len(s) == 0 { 25 return "\"\"" 26 } 27 n := len(s) 28 hasSpace := false 29 for i := 0; i < len(s); i++ { 30 switch s[i] { 31 case '"', '\\': 32 n++ 33 case ' ', '\t': 34 hasSpace = true 35 } 36 } 37 if hasSpace { 38 n += 2 39 } 40 if n == len(s) { 41 return s 42 } 43 44 qs := make([]byte, n) 45 j := 0 46 if hasSpace { 47 qs[j] = '"' 48 j++ 49 } 50 slashes := 0 51 for i := 0; i < len(s); i++ { 52 switch s[i] { 53 default: 54 slashes = 0 55 qs[j] = s[i] 56 case '\\': 57 slashes++ 58 qs[j] = s[i] 59 case '"': 60 for ; slashes > 0; slashes-- { 61 qs[j] = '\\' 62 j++ 63 } 64 qs[j] = '\\' 65 j++ 66 qs[j] = s[i] 67 } 68 j++ 69 } 70 if hasSpace { 71 for ; slashes > 0; slashes-- { 72 qs[j] = '\\' 73 j++ 74 } 75 qs[j] = '"' 76 j++ 77 } 78 return string(qs[:j]) 79 } 80 81 // ComposeCommandLine escapes and joins the given arguments suitable for use as a Windows command line, 82 // in CreateProcess's CommandLine argument, CreateService/ChangeServiceConfig's BinaryPathName argument, 83 // or any program that uses CommandLineToArgv. 84 func ComposeCommandLine(args []string) string { 85 var commandLine string 86 for i := range args { 87 if i > 0 { 88 commandLine += " " 89 } 90 commandLine += EscapeArg(args[i]) 91 } 92 return commandLine 93 } 94 95 // DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv, 96 // as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that 97 // command lines are passed around. 98 // DecomposeCommandLine returns error if commandLine contains NUL. 99 func DecomposeCommandLine(commandLine string) ([]string, error) { 100 if len(commandLine) == 0 { 101 return []string{}, nil 102 } 103 utf16CommandLine, err := UTF16FromString(commandLine) 104 if err != nil { 105 return nil, errorspkg.New("string with NUL passed to DecomposeCommandLine") 106 } 107 var argc int32 108 argv, err := CommandLineToArgv(&utf16CommandLine[0], &argc) 109 if err != nil { 110 return nil, err 111 } 112 defer LocalFree(Handle(unsafe.Pointer(argv))) 113 var args []string 114 for _, v := range (*argv)[:argc] { 115 args = append(args, UTF16ToString((*v)[:])) 116 } 117 return args, nil 118 } 119 120 func CloseOnExec(fd Handle) { 121 SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0) 122 } 123 124 // FullPath retrieves the full path of the specified file. 125 func FullPath(name string) (path string, err error) { 126 p, err := UTF16PtrFromString(name) 127 if err != nil { 128 return "", err 129 } 130 n := uint32(100) 131 for { 132 buf := make([]uint16, n) 133 n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil) 134 if err != nil { 135 return "", err 136 } 137 if n <= uint32(len(buf)) { 138 return UTF16ToString(buf[:n]), nil 139 } 140 } 141 } 142 143 // NewProcThreadAttributeList allocates a new ProcThreadAttributeListContainer, with the requested maximum number of attributes. 144 func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeListContainer, error) { 145 var size uintptr 146 err := initializeProcThreadAttributeList(nil, maxAttrCount, 0, &size) 147 if err != ERROR_INSUFFICIENT_BUFFER { 148 if err == nil { 149 return nil, errorspkg.New("unable to query buffer size from InitializeProcThreadAttributeList") 150 } 151 return nil, err 152 } 153 alloc, err := LocalAlloc(LMEM_FIXED, uint32(size)) 154 if err != nil { 155 return nil, err 156 } 157 // size is guaranteed to be ≥1 by InitializeProcThreadAttributeList. 158 al := &ProcThreadAttributeListContainer{data: (*ProcThreadAttributeList)(unsafe.Pointer(alloc))} 159 err = initializeProcThreadAttributeList(al.data, maxAttrCount, 0, &size) 160 if err != nil { 161 return nil, err 162 } 163 return al, err 164 } 165 166 // Update modifies the ProcThreadAttributeList using UpdateProcThreadAttribute. 167 func (al *ProcThreadAttributeListContainer) Update(attribute uintptr, value unsafe.Pointer, size uintptr) error { 168 al.pointers = append(al.pointers, value) 169 return updateProcThreadAttribute(al.data, 0, attribute, value, size, nil, nil) 170 } 171 172 // Delete frees ProcThreadAttributeList's resources. 173 func (al *ProcThreadAttributeListContainer) Delete() { 174 deleteProcThreadAttributeList(al.data) 175 LocalFree(Handle(unsafe.Pointer(al.data))) 176 al.data = nil 177 al.pointers = nil 178 } 179 180 // List returns the actual ProcThreadAttributeList to be passed to StartupInfoEx. 181 func (al *ProcThreadAttributeListContainer) List() *ProcThreadAttributeList { 182 return al.data 183 }