| 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 |