|
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 func DecomposeCommandLine(commandLine string) ([]string, error) { |
|
99 if len(commandLine) == 0 { |
|
100 return []string{}, nil |
|
101 } |
|
102 var argc int32 |
|
103 argv, err := CommandLineToArgv(StringToUTF16Ptr(commandLine), &argc) |
|
104 if err != nil { |
|
105 return nil, err |
|
106 } |
|
107 defer LocalFree(Handle(unsafe.Pointer(argv))) |
|
108 var args []string |
|
109 for _, v := range (*argv)[:argc] { |
|
110 args = append(args, UTF16ToString((*v)[:])) |
|
111 } |
|
112 return args, nil |
|
113 } |
|
114 |
|
115 func CloseOnExec(fd Handle) { |
|
116 SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0) |
|
117 } |
|
118 |
|
119 // FullPath retrieves the full path of the specified file. |
|
120 func FullPath(name string) (path string, err error) { |
|
121 p, err := UTF16PtrFromString(name) |
|
122 if err != nil { |
|
123 return "", err |
|
124 } |
|
125 n := uint32(100) |
|
126 for { |
|
127 buf := make([]uint16, n) |
|
128 n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil) |
|
129 if err != nil { |
|
130 return "", err |
|
131 } |
|
132 if n <= uint32(len(buf)) { |
|
133 return UTF16ToString(buf[:n]), nil |
|
134 } |
|
135 } |
|
136 } |
|
137 |
|
138 // NewProcThreadAttributeList allocates a new ProcThreadAttributeListContainer, with the requested maximum number of attributes. |
|
139 func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeListContainer, error) { |
|
140 var size uintptr |
|
141 err := initializeProcThreadAttributeList(nil, maxAttrCount, 0, &size) |
|
142 if err != ERROR_INSUFFICIENT_BUFFER { |
|
143 if err == nil { |
|
144 return nil, errorspkg.New("unable to query buffer size from InitializeProcThreadAttributeList") |
|
145 } |
|
146 return nil, err |
|
147 } |
|
148 alloc, err := LocalAlloc(LMEM_FIXED, uint32(size)) |
|
149 if err != nil { |
|
150 return nil, err |
|
151 } |
|
152 // size is guaranteed to be ≥1 by InitializeProcThreadAttributeList. |
|
153 al := &ProcThreadAttributeListContainer{data: (*ProcThreadAttributeList)(unsafe.Pointer(alloc))} |
|
154 err = initializeProcThreadAttributeList(al.data, maxAttrCount, 0, &size) |
|
155 if err != nil { |
|
156 return nil, err |
|
157 } |
|
158 return al, err |
|
159 } |
|
160 |
|
161 // Update modifies the ProcThreadAttributeList using UpdateProcThreadAttribute. |
|
162 func (al *ProcThreadAttributeListContainer) Update(attribute uintptr, value unsafe.Pointer, size uintptr) error { |
|
163 al.pointers = append(al.pointers, value) |
|
164 return updateProcThreadAttribute(al.data, 0, attribute, value, size, nil, nil) |
|
165 } |
|
166 |
|
167 // Delete frees ProcThreadAttributeList's resources. |
|
168 func (al *ProcThreadAttributeListContainer) Delete() { |
|
169 deleteProcThreadAttributeList(al.data) |
|
170 LocalFree(Handle(unsafe.Pointer(al.data))) |
|
171 al.data = nil |
|
172 al.pointers = nil |
|
173 } |
|
174 |
|
175 // List returns the actual ProcThreadAttributeList to be passed to StartupInfoEx. |
|
176 func (al *ProcThreadAttributeListContainer) List() *ProcThreadAttributeList { |
|
177 return al.data |
|
178 } |