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