xpcom/threads/nsProcessCommon.cpp

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:c87c18ddc138
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 /*****************************************************************************
7 *
8 * nsProcess is used to execute new processes and specify if you want to
9 * wait (blocking) or continue (non-blocking).
10 *
11 *****************************************************************************
12 */
13
14 #include "mozilla/ArrayUtils.h"
15
16 #include "nsCOMPtr.h"
17 #include "nsAutoPtr.h"
18 #include "nsMemory.h"
19 #include "nsProcess.h"
20 #include "prio.h"
21 #include "prenv.h"
22 #include "nsCRT.h"
23 #include "nsThreadUtils.h"
24 #include "nsIObserverService.h"
25 #include "mozilla/Services.h"
26
27 #include <stdlib.h>
28
29 #if defined(PROCESSMODEL_WINAPI)
30 #include "prmem.h"
31 #include "nsString.h"
32 #include "nsLiteralString.h"
33 #include "nsReadableUtils.h"
34 #else
35 #ifdef XP_MACOSX
36 #include <crt_externs.h>
37 #include <spawn.h>
38 #include <sys/wait.h>
39 #endif
40 #include <sys/types.h>
41 #include <signal.h>
42 #endif
43
44 using namespace mozilla;
45
46 #ifdef XP_MACOSX
47 cpu_type_t pref_cpu_types[2] = {
48 #if defined(__i386__)
49 CPU_TYPE_X86,
50 #elif defined(__x86_64__)
51 CPU_TYPE_X86_64,
52 #elif defined(__ppc__)
53 CPU_TYPE_POWERPC,
54 #endif
55 CPU_TYPE_ANY };
56 #endif
57
58 //-------------------------------------------------------------------//
59 // nsIProcess implementation
60 //-------------------------------------------------------------------//
61 NS_IMPL_ISUPPORTS(nsProcess, nsIProcess,
62 nsIObserver)
63
64 //Constructor
65 nsProcess::nsProcess()
66 : mThread(nullptr)
67 , mLock("nsProcess.mLock")
68 , mShutdown(false)
69 , mBlocking(false)
70 , mPid(-1)
71 , mObserver(nullptr)
72 , mWeakObserver(nullptr)
73 , mExitValue(-1)
74 #if !defined(XP_MACOSX)
75 , mProcess(nullptr)
76 #endif
77 {
78 }
79
80 //Destructor
81 nsProcess::~nsProcess()
82 {
83 }
84
85 NS_IMETHODIMP
86 nsProcess::Init(nsIFile* executable)
87 {
88 if (mExecutable)
89 return NS_ERROR_ALREADY_INITIALIZED;
90
91 if (NS_WARN_IF(!executable))
92 return NS_ERROR_INVALID_ARG;
93 bool isFile;
94
95 //First make sure the file exists
96 nsresult rv = executable->IsFile(&isFile);
97 if (NS_FAILED(rv)) return rv;
98 if (!isFile)
99 return NS_ERROR_FAILURE;
100
101 //Store the nsIFile in mExecutable
102 mExecutable = executable;
103 //Get the path because it is needed by the NSPR process creation
104 #ifdef XP_WIN
105 rv = mExecutable->GetTarget(mTargetPath);
106 if (NS_FAILED(rv) || mTargetPath.IsEmpty() )
107 #endif
108 rv = mExecutable->GetPath(mTargetPath);
109
110 return rv;
111 }
112
113
114 #if defined(XP_WIN)
115 // Out param `wideCmdLine` must be PR_Freed by the caller.
116 static int assembleCmdLine(char *const *argv, wchar_t **wideCmdLine,
117 UINT codePage)
118 {
119 char *const *arg;
120 char *p, *q, *cmdLine;
121 int cmdLineSize;
122 int numBackslashes;
123 int i;
124 int argNeedQuotes;
125
126 /*
127 * Find out how large the command line buffer should be.
128 */
129 cmdLineSize = 0;
130 for (arg = argv; *arg; arg++) {
131 /*
132 * \ and " need to be escaped by a \. In the worst case,
133 * every character is a \ or ", so the string of length
134 * may double. If we quote an argument, that needs two ".
135 * Finally, we need a space between arguments, and
136 * a null byte at the end of command line.
137 */
138 cmdLineSize += 2 * strlen(*arg) /* \ and " need to be escaped */
139 + 2 /* we quote every argument */
140 + 1; /* space in between, or final null */
141 }
142 p = cmdLine = (char *) PR_MALLOC(cmdLineSize*sizeof(char));
143 if (p == nullptr) {
144 return -1;
145 }
146
147 for (arg = argv; *arg; arg++) {
148 /* Add a space to separates the arguments */
149 if (arg != argv) {
150 *p++ = ' ';
151 }
152 q = *arg;
153 numBackslashes = 0;
154 argNeedQuotes = 0;
155
156 /* If the argument contains white space, it needs to be quoted. */
157 if (strpbrk(*arg, " \f\n\r\t\v")) {
158 argNeedQuotes = 1;
159 }
160
161 if (argNeedQuotes) {
162 *p++ = '"';
163 }
164 while (*q) {
165 if (*q == '\\') {
166 numBackslashes++;
167 q++;
168 } else if (*q == '"') {
169 if (numBackslashes) {
170 /*
171 * Double the backslashes since they are followed
172 * by a quote
173 */
174 for (i = 0; i < 2 * numBackslashes; i++) {
175 *p++ = '\\';
176 }
177 numBackslashes = 0;
178 }
179 /* To escape the quote */
180 *p++ = '\\';
181 *p++ = *q++;
182 } else {
183 if (numBackslashes) {
184 /*
185 * Backslashes are not followed by a quote, so
186 * don't need to double the backslashes.
187 */
188 for (i = 0; i < numBackslashes; i++) {
189 *p++ = '\\';
190 }
191 numBackslashes = 0;
192 }
193 *p++ = *q++;
194 }
195 }
196
197 /* Now we are at the end of this argument */
198 if (numBackslashes) {
199 /*
200 * Double the backslashes if we have a quote string
201 * delimiter at the end.
202 */
203 if (argNeedQuotes) {
204 numBackslashes *= 2;
205 }
206 for (i = 0; i < numBackslashes; i++) {
207 *p++ = '\\';
208 }
209 }
210 if (argNeedQuotes) {
211 *p++ = '"';
212 }
213 }
214
215 *p = '\0';
216 int32_t numChars = MultiByteToWideChar(codePage, 0, cmdLine, -1, nullptr, 0);
217 *wideCmdLine = (wchar_t *) PR_MALLOC(numChars*sizeof(wchar_t));
218 MultiByteToWideChar(codePage, 0, cmdLine, -1, *wideCmdLine, numChars);
219 PR_Free(cmdLine);
220 return 0;
221 }
222 #endif
223
224 void nsProcess::Monitor(void *arg)
225 {
226 nsRefPtr<nsProcess> process = dont_AddRef(static_cast<nsProcess*>(arg));
227
228 if (!process->mBlocking)
229 PR_SetCurrentThreadName("RunProcess");
230
231 #if defined(PROCESSMODEL_WINAPI)
232 DWORD dwRetVal;
233 unsigned long exitCode = -1;
234
235 dwRetVal = WaitForSingleObject(process->mProcess, INFINITE);
236 if (dwRetVal != WAIT_FAILED) {
237 if (GetExitCodeProcess(process->mProcess, &exitCode) == FALSE)
238 exitCode = -1;
239 }
240
241 // Lock in case Kill or GetExitCode are called during this
242 {
243 MutexAutoLock lock(process->mLock);
244 CloseHandle(process->mProcess);
245 process->mProcess = nullptr;
246 process->mExitValue = exitCode;
247 if (process->mShutdown)
248 return;
249 }
250 #else
251 #ifdef XP_MACOSX
252 int exitCode = -1;
253 int status = 0;
254 if (waitpid(process->mPid, &status, 0) == process->mPid) {
255 if (WIFEXITED(status)) {
256 exitCode = WEXITSTATUS(status);
257 }
258 else if(WIFSIGNALED(status)) {
259 exitCode = 256; // match NSPR's signal exit status
260 }
261 }
262 #else
263 int32_t exitCode = -1;
264 if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS)
265 exitCode = -1;
266 #endif
267
268 // Lock in case Kill or GetExitCode are called during this
269 {
270 MutexAutoLock lock(process->mLock);
271 #if !defined(XP_MACOSX)
272 process->mProcess = nullptr;
273 #endif
274 process->mExitValue = exitCode;
275 if (process->mShutdown)
276 return;
277 }
278 #endif
279
280 // If we ran a background thread for the monitor then notify on the main
281 // thread
282 if (NS_IsMainThread()) {
283 process->ProcessComplete();
284 }
285 else {
286 nsCOMPtr<nsIRunnable> event =
287 NS_NewRunnableMethod(process, &nsProcess::ProcessComplete);
288 NS_DispatchToMainThread(event);
289 }
290 }
291
292 void nsProcess::ProcessComplete()
293 {
294 if (mThread) {
295 nsCOMPtr<nsIObserverService> os =
296 mozilla::services::GetObserverService();
297 if (os)
298 os->RemoveObserver(this, "xpcom-shutdown");
299 PR_JoinThread(mThread);
300 mThread = nullptr;
301 }
302
303 const char* topic;
304 if (mExitValue < 0)
305 topic = "process-failed";
306 else
307 topic = "process-finished";
308
309 mPid = -1;
310 nsCOMPtr<nsIObserver> observer;
311 if (mWeakObserver)
312 observer = do_QueryReferent(mWeakObserver);
313 else if (mObserver)
314 observer = mObserver;
315 mObserver = nullptr;
316 mWeakObserver = nullptr;
317
318 if (observer)
319 observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr);
320 }
321
322 // XXXldb |args| has the wrong const-ness
323 NS_IMETHODIMP
324 nsProcess::Run(bool blocking, const char **args, uint32_t count)
325 {
326 return CopyArgsAndRunProcess(blocking, args, count, nullptr, false);
327 }
328
329 // XXXldb |args| has the wrong const-ness
330 NS_IMETHODIMP
331 nsProcess::RunAsync(const char **args, uint32_t count,
332 nsIObserver* observer, bool holdWeak)
333 {
334 return CopyArgsAndRunProcess(false, args, count, observer, holdWeak);
335 }
336
337 nsresult
338 nsProcess::CopyArgsAndRunProcess(bool blocking, const char** args,
339 uint32_t count, nsIObserver* observer,
340 bool holdWeak)
341 {
342 // Add one to the count for the program name and one for null termination.
343 char **my_argv = nullptr;
344 my_argv = (char**)NS_Alloc(sizeof(char*) * (count + 2));
345 if (!my_argv) {
346 return NS_ERROR_OUT_OF_MEMORY;
347 }
348
349 my_argv[0] = ToNewUTF8String(mTargetPath);
350
351 for (uint32_t i = 0; i < count; i++) {
352 my_argv[i + 1] = const_cast<char*>(args[i]);
353 }
354
355 my_argv[count + 1] = nullptr;
356
357 nsresult rv = RunProcess(blocking, my_argv, observer, holdWeak, false);
358
359 NS_Free(my_argv[0]);
360 NS_Free(my_argv);
361 return rv;
362 }
363
364 // XXXldb |args| has the wrong const-ness
365 NS_IMETHODIMP
366 nsProcess::Runw(bool blocking, const char16_t **args, uint32_t count)
367 {
368 return CopyArgsAndRunProcessw(blocking, args, count, nullptr, false);
369 }
370
371 // XXXldb |args| has the wrong const-ness
372 NS_IMETHODIMP
373 nsProcess::RunwAsync(const char16_t **args, uint32_t count,
374 nsIObserver* observer, bool holdWeak)
375 {
376 return CopyArgsAndRunProcessw(false, args, count, observer, holdWeak);
377 }
378
379 nsresult
380 nsProcess::CopyArgsAndRunProcessw(bool blocking, const char16_t** args,
381 uint32_t count, nsIObserver* observer,
382 bool holdWeak)
383 {
384 // Add one to the count for the program name and one for null termination.
385 char **my_argv = nullptr;
386 my_argv = (char**)NS_Alloc(sizeof(char*) * (count + 2));
387 if (!my_argv) {
388 return NS_ERROR_OUT_OF_MEMORY;
389 }
390
391 my_argv[0] = ToNewUTF8String(mTargetPath);
392
393 for (uint32_t i = 0; i < count; i++) {
394 my_argv[i + 1] = ToNewUTF8String(nsDependentString(args[i]));
395 }
396
397 my_argv[count + 1] = nullptr;
398
399 nsresult rv = RunProcess(blocking, my_argv, observer, holdWeak, true);
400
401 for (uint32_t i = 0; i <= count; i++) {
402 NS_Free(my_argv[i]);
403 }
404 NS_Free(my_argv);
405 return rv;
406 }
407
408 nsresult
409 nsProcess::RunProcess(bool blocking, char **my_argv, nsIObserver* observer,
410 bool holdWeak, bool argsUTF8)
411 {
412 if (NS_WARN_IF(!mExecutable))
413 return NS_ERROR_NOT_INITIALIZED;
414 if (NS_WARN_IF(mThread))
415 return NS_ERROR_ALREADY_INITIALIZED;
416
417 if (observer) {
418 if (holdWeak) {
419 mWeakObserver = do_GetWeakReference(observer);
420 if (!mWeakObserver)
421 return NS_NOINTERFACE;
422 }
423 else {
424 mObserver = observer;
425 }
426 }
427
428 mExitValue = -1;
429 mPid = -1;
430
431 #if defined(PROCESSMODEL_WINAPI)
432 BOOL retVal;
433 wchar_t *cmdLine = nullptr;
434
435 // The 'argv' array is null-terminated and always starts with the program path.
436 // If the second slot is non-null then arguments are being passed.
437 if (my_argv[1] != nullptr &&
438 assembleCmdLine(my_argv + 1, &cmdLine, argsUTF8 ? CP_UTF8 : CP_ACP) == -1) {
439 return NS_ERROR_FILE_EXECUTION_FAILED;
440 }
441
442 /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows
443 * from appearing. This makes behavior the same on all platforms. The flag
444 * will not have any effect on non-console applications.
445 */
446
447 // The program name in my_argv[0] is always UTF-8
448 NS_ConvertUTF8toUTF16 wideFile(my_argv[0]);
449
450 SHELLEXECUTEINFOW sinfo;
451 memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW));
452 sinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
453 sinfo.hwnd = nullptr;
454 sinfo.lpFile = wideFile.get();
455 sinfo.nShow = SW_SHOWNORMAL;
456 sinfo.fMask = SEE_MASK_FLAG_DDEWAIT |
457 SEE_MASK_NO_CONSOLE |
458 SEE_MASK_NOCLOSEPROCESS;
459
460 if (cmdLine)
461 sinfo.lpParameters = cmdLine;
462
463 retVal = ShellExecuteExW(&sinfo);
464 if (!retVal) {
465 return NS_ERROR_FILE_EXECUTION_FAILED;
466 }
467
468 mProcess = sinfo.hProcess;
469
470 if (cmdLine)
471 PR_Free(cmdLine);
472
473 mPid = GetProcessId(mProcess);
474 #elif defined(XP_MACOSX)
475 // Initialize spawn attributes.
476 posix_spawnattr_t spawnattr;
477 if (posix_spawnattr_init(&spawnattr) != 0) {
478 return NS_ERROR_FAILURE;
479 }
480
481 // Set spawn attributes.
482 size_t attr_count = ArrayLength(pref_cpu_types);
483 size_t attr_ocount = 0;
484 if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types, &attr_ocount) != 0 ||
485 attr_ocount != attr_count) {
486 posix_spawnattr_destroy(&spawnattr);
487 return NS_ERROR_FAILURE;
488 }
489
490 // Note that the 'argv' array is already null-terminated, which 'posix_spawnp' requires.
491 pid_t newPid = 0;
492 int result = posix_spawnp(&newPid, my_argv[0], nullptr, &spawnattr, my_argv, *_NSGetEnviron());
493 mPid = static_cast<int32_t>(newPid);
494
495 posix_spawnattr_destroy(&spawnattr);
496
497 if (result != 0) {
498 return NS_ERROR_FAILURE;
499 }
500 #else
501 mProcess = PR_CreateProcess(my_argv[0], my_argv, nullptr, nullptr);
502 if (!mProcess)
503 return NS_ERROR_FAILURE;
504 struct MYProcess {
505 uint32_t pid;
506 };
507 MYProcess* ptrProc = (MYProcess *) mProcess;
508 mPid = ptrProc->pid;
509 #endif
510
511 NS_ADDREF_THIS();
512 mBlocking = blocking;
513 if (blocking) {
514 Monitor(this);
515 if (mExitValue < 0)
516 return NS_ERROR_FILE_EXECUTION_FAILED;
517 }
518 else {
519 mThread = PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this,
520 PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
521 PR_JOINABLE_THREAD, 0);
522 if (!mThread) {
523 NS_RELEASE_THIS();
524 return NS_ERROR_FAILURE;
525 }
526
527 // It isn't a failure if we just can't watch for shutdown
528 nsCOMPtr<nsIObserverService> os =
529 mozilla::services::GetObserverService();
530 if (os)
531 os->AddObserver(this, "xpcom-shutdown", false);
532 }
533
534 return NS_OK;
535 }
536
537 NS_IMETHODIMP nsProcess::GetIsRunning(bool *aIsRunning)
538 {
539 if (mThread)
540 *aIsRunning = true;
541 else
542 *aIsRunning = false;
543
544 return NS_OK;
545 }
546
547 NS_IMETHODIMP
548 nsProcess::GetPid(uint32_t *aPid)
549 {
550 if (!mThread)
551 return NS_ERROR_FAILURE;
552 if (mPid < 0)
553 return NS_ERROR_NOT_IMPLEMENTED;
554 *aPid = mPid;
555 return NS_OK;
556 }
557
558 NS_IMETHODIMP
559 nsProcess::Kill()
560 {
561 if (!mThread)
562 return NS_ERROR_FAILURE;
563
564 {
565 MutexAutoLock lock(mLock);
566 #if defined(PROCESSMODEL_WINAPI)
567 if (TerminateProcess(mProcess, 0) == 0)
568 return NS_ERROR_FAILURE;
569 #elif defined(XP_MACOSX)
570 if (kill(mPid, SIGKILL) != 0)
571 return NS_ERROR_FAILURE;
572 #else
573 if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS))
574 return NS_ERROR_FAILURE;
575 #endif
576 }
577
578 // We must null out mThread if we want IsRunning to return false immediately
579 // after this call.
580 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
581 if (os)
582 os->RemoveObserver(this, "xpcom-shutdown");
583 PR_JoinThread(mThread);
584 mThread = nullptr;
585
586 return NS_OK;
587 }
588
589 NS_IMETHODIMP
590 nsProcess::GetExitValue(int32_t *aExitValue)
591 {
592 MutexAutoLock lock(mLock);
593
594 *aExitValue = mExitValue;
595
596 return NS_OK;
597 }
598
599 NS_IMETHODIMP
600 nsProcess::Observe(nsISupports* subject, const char* topic, const char16_t* data)
601 {
602 // Shutting down, drop all references
603 if (mThread) {
604 nsCOMPtr<nsIObserverService> os =
605 mozilla::services::GetObserverService();
606 if (os)
607 os->RemoveObserver(this, "xpcom-shutdown");
608 mThread = nullptr;
609 }
610
611 mObserver = nullptr;
612 mWeakObserver = nullptr;
613
614 MutexAutoLock lock(mLock);
615 mShutdown = true;
616
617 return NS_OK;
618 }

mercurial