OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "base/process/launch.h" | |
6 | |
7 #include <fcntl.h> | |
8 #include <io.h> | |
9 #include <shellapi.h> | |
10 #include <windows.h> | |
11 #include <userenv.h> | |
12 #include <psapi.h> | |
13 | |
14 #include <ios> | |
15 #include <limits> | |
16 | |
17 #include "base/bind.h" | |
18 #include "base/bind_helpers.h" | |
19 #include "base/command_line.h" | |
20 #include "base/debug/stack_trace.h" | |
21 #include "base/logging.h" | |
22 #include "base/memory/scoped_ptr.h" | |
23 #include "base/message_loop/message_loop.h" | |
24 #include "base/metrics/histogram.h" | |
25 #include "base/process/kill.h" | |
26 #include "base/strings/utf_string_conversions.h" | |
27 #include "base/sys_info.h" | |
28 #include "base/win/object_watcher.h" | |
29 #include "base/win/scoped_handle.h" | |
30 #include "base/win/scoped_process_information.h" | |
31 #include "base/win/startup_information.h" | |
32 #include "base/win/windows_version.h" | |
33 | |
34 // userenv.dll is required for CreateEnvironmentBlock(). | |
35 #pragma comment(lib, "userenv.lib") | |
36 | |
37 namespace base { | |
38 | |
39 namespace { | |
40 | |
41 // This exit code is used by the Windows task manager when it kills a | |
42 // process. It's value is obviously not that unique, and it's | |
43 // surprising to me that the task manager uses this value, but it | |
44 // seems to be common practice on Windows to test for it as an | |
45 // indication that the task manager has killed something if the | |
46 // process goes away. | |
47 const DWORD kProcessKilledExitCode = 1; | |
48 | |
49 } // namespace | |
50 | |
51 void RouteStdioToConsole() { | |
52 // Don't change anything if stdout or stderr already point to a | |
53 // valid stream. | |
54 // | |
55 // If we are running under Buildbot or under Cygwin's default | |
56 // terminal (mintty), stderr and stderr will be pipe handles. In | |
57 // that case, we don't want to open CONOUT$, because its output | |
58 // likely does not go anywhere. | |
59 // | |
60 // We don't use GetStdHandle() to check stdout/stderr here because | |
61 // it can return dangling IDs of handles that were never inherited | |
62 // by this process. These IDs could have been reused by the time | |
63 // this function is called. The CRT checks the validity of | |
64 // stdout/stderr on startup (before the handle IDs can be reused). | |
65 // _fileno(stdout) will return -2 (_NO_CONSOLE_FILENO) if stdout was | |
66 // invalid. | |
67 if (_fileno(stdout) >= 0 || _fileno(stderr) >= 0) | |
68 return; | |
69 | |
70 if (!AttachConsole(ATTACH_PARENT_PROCESS)) { | |
71 unsigned int result = GetLastError(); | |
72 // Was probably already attached. | |
73 if (result == ERROR_ACCESS_DENIED) | |
74 return; | |
75 // Don't bother creating a new console for each child process if the | |
76 // parent process is invalid (eg: crashed). | |
77 if (result == ERROR_GEN_FAILURE) | |
78 return; | |
79 // Make a new console if attaching to parent fails with any other error. | |
80 // It should be ERROR_INVALID_HANDLE at this point, which means the browser | |
81 // was likely not started from a console. | |
82 AllocConsole(); | |
83 } | |
84 | |
85 // Arbitrary byte count to use when buffering output lines. More | |
86 // means potential waste, less means more risk of interleaved | |
87 // log-lines in output. | |
88 enum { kOutputBufferSize = 64 * 1024 }; | |
89 | |
90 if (freopen("CONOUT$", "w", stdout)) { | |
91 setvbuf(stdout, NULL, _IOLBF, kOutputBufferSize); | |
92 // Overwrite FD 1 for the benefit of any code that uses this FD | |
93 // directly. This is safe because the CRT allocates FDs 0, 1 and | |
94 // 2 at startup even if they don't have valid underlying Windows | |
95 // handles. This means we won't be overwriting an FD created by | |
96 // _open() after startup. | |
97 _dup2(_fileno(stdout), 1); | |
98 } | |
99 if (freopen("CONOUT$", "w", stderr)) { | |
100 setvbuf(stderr, NULL, _IOLBF, kOutputBufferSize); | |
101 _dup2(_fileno(stderr), 2); | |
102 } | |
103 | |
104 // Fix all cout, wcout, cin, wcin, cerr, wcerr, clog and wclog. | |
105 std::ios::sync_with_stdio(); | |
106 } | |
107 | |
108 Process LaunchProcess(const CommandLine& cmdline, | |
109 const LaunchOptions& options) { | |
110 return LaunchProcess(cmdline.GetCommandLineString(), options); | |
111 } | |
112 | |
113 Process LaunchProcess(const string16& cmdline, | |
114 const LaunchOptions& options) { | |
115 win::StartupInformation startup_info_wrapper; | |
116 STARTUPINFO* startup_info = startup_info_wrapper.startup_info(); | |
117 | |
118 bool inherit_handles = options.inherit_handles; | |
119 DWORD flags = 0; | |
120 if (options.handles_to_inherit) { | |
121 if (options.handles_to_inherit->empty()) { | |
122 inherit_handles = false; | |
123 } else { | |
124 if (base::win::GetVersion() < base::win::VERSION_VISTA) { | |
125 DLOG(ERROR) << "Specifying handles to inherit requires Vista or later."; | |
126 return Process(); | |
127 } | |
128 | |
129 if (options.handles_to_inherit->size() > | |
130 std::numeric_limits<DWORD>::max() / sizeof(HANDLE)) { | |
131 DLOG(ERROR) << "Too many handles to inherit."; | |
132 return Process(); | |
133 } | |
134 | |
135 if (!startup_info_wrapper.InitializeProcThreadAttributeList(1)) { | |
136 DPLOG(ERROR); | |
137 return Process(); | |
138 } | |
139 | |
140 if (!startup_info_wrapper.UpdateProcThreadAttribute( | |
141 PROC_THREAD_ATTRIBUTE_HANDLE_LIST, | |
142 const_cast<HANDLE*>(&options.handles_to_inherit->at(0)), | |
143 static_cast<DWORD>(options.handles_to_inherit->size() * | |
144 sizeof(HANDLE)))) { | |
145 DPLOG(ERROR); | |
146 return Process(); | |
147 } | |
148 | |
149 inherit_handles = true; | |
150 flags |= EXTENDED_STARTUPINFO_PRESENT; | |
151 } | |
152 } | |
153 | |
154 if (options.empty_desktop_name) | |
155 startup_info->lpDesktop = const_cast<wchar_t*>(L""); | |
156 startup_info->dwFlags = STARTF_USESHOWWINDOW; | |
157 startup_info->wShowWindow = options.start_hidden ? SW_HIDE : SW_SHOW; | |
158 | |
159 if (options.stdin_handle || options.stdout_handle || options.stderr_handle) { | |
160 DCHECK(inherit_handles); | |
161 DCHECK(options.stdin_handle); | |
162 DCHECK(options.stdout_handle); | |
163 DCHECK(options.stderr_handle); | |
164 startup_info->dwFlags |= STARTF_USESTDHANDLES; | |
165 startup_info->hStdInput = options.stdin_handle; | |
166 startup_info->hStdOutput = options.stdout_handle; | |
167 startup_info->hStdError = options.stderr_handle; | |
168 } | |
169 | |
170 if (options.job_handle) { | |
171 flags |= CREATE_SUSPENDED; | |
172 | |
173 // If this code is run under a debugger, the launched process is | |
174 // automatically associated with a job object created by the debugger. | |
175 // The CREATE_BREAKAWAY_FROM_JOB flag is used to prevent this. | |
176 flags |= CREATE_BREAKAWAY_FROM_JOB; | |
177 } | |
178 | |
179 if (options.force_breakaway_from_job_) | |
180 flags |= CREATE_BREAKAWAY_FROM_JOB; | |
181 | |
182 PROCESS_INFORMATION temp_process_info = {}; | |
183 | |
184 string16 writable_cmdline(cmdline); | |
185 if (options.as_user) { | |
186 flags |= CREATE_UNICODE_ENVIRONMENT; | |
187 void* enviroment_block = NULL; | |
188 | |
189 if (!CreateEnvironmentBlock(&enviroment_block, options.as_user, FALSE)) { | |
190 DPLOG(ERROR); | |
191 return Process(); | |
192 } | |
193 | |
194 BOOL launched = | |
195 CreateProcessAsUser(options.as_user, NULL, | |
196 &writable_cmdline[0], | |
197 NULL, NULL, inherit_handles, flags, | |
198 enviroment_block, NULL, startup_info, | |
199 &temp_process_info); | |
200 DestroyEnvironmentBlock(enviroment_block); | |
201 if (!launched) { | |
202 DPLOG(ERROR) << "Command line:" << std::endl << UTF16ToUTF8(cmdline) | |
203 << std::endl;; | |
204 return Process(); | |
205 } | |
206 } else { | |
207 if (!CreateProcess(NULL, | |
208 &writable_cmdline[0], NULL, NULL, | |
209 inherit_handles, flags, NULL, NULL, | |
210 startup_info, &temp_process_info)) { | |
211 DPLOG(ERROR) << "Command line:" << std::endl << UTF16ToUTF8(cmdline) | |
212 << std::endl;; | |
213 return Process(); | |
214 } | |
215 } | |
216 base::win::ScopedProcessInformation process_info(temp_process_info); | |
217 | |
218 if (options.job_handle) { | |
219 if (0 == AssignProcessToJobObject(options.job_handle, | |
220 process_info.process_handle())) { | |
221 DLOG(ERROR) << "Could not AssignProcessToObject."; | |
222 Process scoped_process(process_info.TakeProcessHandle()); | |
223 scoped_process.Terminate(kProcessKilledExitCode, true); | |
224 return Process(); | |
225 } | |
226 | |
227 ResumeThread(process_info.thread_handle()); | |
228 } | |
229 | |
230 if (options.wait) | |
231 WaitForSingleObject(process_info.process_handle(), INFINITE); | |
232 | |
233 return Process(process_info.TakeProcessHandle()); | |
234 } | |
235 | |
236 Process LaunchElevatedProcess(const CommandLine& cmdline, | |
237 const LaunchOptions& options) { | |
238 const string16 file = cmdline.GetProgram().value(); | |
239 const string16 arguments = cmdline.GetArgumentsString(); | |
240 | |
241 SHELLEXECUTEINFO shex_info = {0}; | |
242 shex_info.cbSize = sizeof(shex_info); | |
243 shex_info.fMask = SEE_MASK_NOCLOSEPROCESS; | |
244 shex_info.hwnd = GetActiveWindow(); | |
245 shex_info.lpVerb = L"runas"; | |
246 shex_info.lpFile = file.c_str(); | |
247 shex_info.lpParameters = arguments.c_str(); | |
248 shex_info.lpDirectory = NULL; | |
249 shex_info.nShow = options.start_hidden ? SW_HIDE : SW_SHOW; | |
250 shex_info.hInstApp = NULL; | |
251 | |
252 if (!ShellExecuteEx(&shex_info)) { | |
253 DPLOG(ERROR); | |
254 return Process(); | |
255 } | |
256 | |
257 if (options.wait) | |
258 WaitForSingleObject(shex_info.hProcess, INFINITE); | |
259 | |
260 return Process(shex_info.hProcess); | |
261 } | |
262 | |
263 bool SetJobObjectLimitFlags(HANDLE job_object, DWORD limit_flags) { | |
264 JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info = {0}; | |
265 limit_info.BasicLimitInformation.LimitFlags = limit_flags; | |
266 return 0 != SetInformationJobObject( | |
267 job_object, | |
268 JobObjectExtendedLimitInformation, | |
269 &limit_info, | |
270 sizeof(limit_info)); | |
271 } | |
272 | |
273 bool GetAppOutput(const CommandLine& cl, std::string* output) { | |
274 return GetAppOutput(cl.GetCommandLineString(), output); | |
275 } | |
276 | |
277 bool GetAppOutput(const StringPiece16& cl, std::string* output) { | |
278 HANDLE out_read = NULL; | |
279 HANDLE out_write = NULL; | |
280 | |
281 SECURITY_ATTRIBUTES sa_attr; | |
282 // Set the bInheritHandle flag so pipe handles are inherited. | |
283 sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES); | |
284 sa_attr.bInheritHandle = TRUE; | |
285 sa_attr.lpSecurityDescriptor = NULL; | |
286 | |
287 // Create the pipe for the child process's STDOUT. | |
288 if (!CreatePipe(&out_read, &out_write, &sa_attr, 0)) { | |
289 NOTREACHED() << "Failed to create pipe"; | |
290 return false; | |
291 } | |
292 | |
293 // Ensure we don't leak the handles. | |
294 win::ScopedHandle scoped_out_read(out_read); | |
295 win::ScopedHandle scoped_out_write(out_write); | |
296 | |
297 // Ensure the read handle to the pipe for STDOUT is not inherited. | |
298 if (!SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)) { | |
299 NOTREACHED() << "Failed to disabled pipe inheritance"; | |
300 return false; | |
301 } | |
302 | |
303 FilePath::StringType writable_command_line_string; | |
304 writable_command_line_string.assign(cl.data(), cl.size()); | |
305 | |
306 STARTUPINFO start_info = {}; | |
307 | |
308 start_info.cb = sizeof(STARTUPINFO); | |
309 start_info.hStdOutput = out_write; | |
310 // Keep the normal stdin and stderr. | |
311 start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); | |
312 start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); | |
313 start_info.dwFlags |= STARTF_USESTDHANDLES; | |
314 | |
315 // Create the child process. | |
316 PROCESS_INFORMATION temp_process_info = {}; | |
317 if (!CreateProcess(NULL, | |
318 &writable_command_line_string[0], | |
319 NULL, NULL, | |
320 TRUE, // Handles are inherited. | |
321 0, NULL, NULL, &start_info, &temp_process_info)) { | |
322 NOTREACHED() << "Failed to start process"; | |
323 return false; | |
324 } | |
325 base::win::ScopedProcessInformation proc_info(temp_process_info); | |
326 | |
327 // Close our writing end of pipe now. Otherwise later read would not be able | |
328 // to detect end of child's output. | |
329 scoped_out_write.Close(); | |
330 | |
331 // Read output from the child process's pipe for STDOUT | |
332 const int kBufferSize = 1024; | |
333 char buffer[kBufferSize]; | |
334 | |
335 for (;;) { | |
336 DWORD bytes_read = 0; | |
337 BOOL success = ReadFile(out_read, buffer, kBufferSize, &bytes_read, NULL); | |
338 if (!success || bytes_read == 0) | |
339 break; | |
340 output->append(buffer, bytes_read); | |
341 } | |
342 | |
343 // Let's wait for the process to finish. | |
344 WaitForSingleObject(proc_info.process_handle(), INFINITE); | |
345 | |
346 return true; | |
347 } | |
348 | |
349 void RaiseProcessToHighPriority() { | |
350 SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); | |
351 } | |
352 | |
353 } // namespace base | |
OLD | NEW |