VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/GuestCtrlImplTasks.cpp@ 40685

Last change on this file since 40685 was 40685, checked in by vboxsync, 13 years ago

Main/GuestCtrl: Introduced host<->guest PID mapping for immediate returns of executeProcess(); bugfixes.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 49.3 KB
Line 
1/* $Id: */
2/** @file
3 * VirtualBox Guest Control - Threaded operations (tasks).
4 */
5
6/*
7 * Copyright (C) 2011 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#include <memory>
19
20#include "GuestImpl.h"
21#include "GuestCtrlImplPrivate.h"
22
23#include "Global.h"
24#include "ConsoleImpl.h"
25#include "ProgressImpl.h"
26#include "VMMDev.h"
27
28#include "AutoCaller.h"
29#include "Logging.h"
30
31#include <VBox/VMMDev.h>
32#ifdef VBOX_WITH_GUEST_CONTROL
33# include <VBox/com/array.h>
34# include <VBox/com/ErrorInfo.h>
35#endif
36
37#include <iprt/file.h>
38#include <iprt/isofs.h>
39#include <iprt/list.h>
40#include <iprt/path.h>
41
42GuestTask::GuestTask(TaskType aTaskType, Guest *aThat, Progress *aProgress)
43 : taskType(aTaskType),
44 pGuest(aThat),
45 pProgress(aProgress),
46 rc(S_OK)
47{
48
49}
50
51GuestTask::~GuestTask()
52{
53
54}
55
56int GuestTask::startThread()
57{
58 return RTThreadCreate(NULL, GuestTask::taskThread, this,
59 0, RTTHREADTYPE_MAIN_HEAVY_WORKER, 0,
60 "GuestTask");
61}
62
63/* static */
64DECLCALLBACK(int) GuestTask::taskThread(RTTHREAD /* aThread */, void *pvUser)
65{
66 std::auto_ptr<GuestTask> task(static_cast<GuestTask*>(pvUser));
67 AssertReturn(task.get(), VERR_GENERAL_FAILURE);
68
69 ComObjPtr<Guest> pGuest = task->pGuest;
70
71 LogFlowFuncEnter();
72
73 HRESULT rc = S_OK;
74
75 switch (task->taskType)
76 {
77#ifdef VBOX_WITH_GUEST_CONTROL
78 case TaskType_CopyFileToGuest:
79 {
80 rc = pGuest->taskCopyFileToGuest(task.get());
81 break;
82 }
83 case TaskType_CopyFileFromGuest:
84 {
85 rc = pGuest->taskCopyFileFromGuest(task.get());
86 break;
87 }
88 case TaskType_UpdateGuestAdditions:
89 {
90 rc = pGuest->taskUpdateGuestAdditions(task.get());
91 break;
92 }
93#endif
94 default:
95 AssertMsgFailed(("Invalid task type %u specified!\n", task->taskType));
96 break;
97 }
98
99 LogFlowFunc(("rc=%Rhrc\n", rc));
100 LogFlowFuncLeave();
101
102 return VINF_SUCCESS;
103}
104
105/* static */
106int GuestTask::uploadProgress(unsigned uPercent, void *pvUser)
107{
108 GuestTask *pTask = *(GuestTask**)pvUser;
109
110 if ( pTask
111 && !pTask->pProgress.isNull())
112 {
113 BOOL fCanceled;
114 pTask->pProgress->COMGETTER(Canceled)(&fCanceled);
115 if (fCanceled)
116 return -1;
117 pTask->pProgress->SetCurrentOperationProgress(uPercent);
118 }
119 return VINF_SUCCESS;
120}
121
122/* static */
123HRESULT GuestTask::setProgressSuccess(ComObjPtr<Progress> pProgress)
124{
125 BOOL fCanceled;
126 BOOL fCompleted;
127 if ( SUCCEEDED(pProgress->COMGETTER(Canceled(&fCanceled)))
128 && !fCanceled
129 && SUCCEEDED(pProgress->COMGETTER(Completed(&fCompleted)))
130 && !fCompleted)
131 {
132 return pProgress->notifyComplete(S_OK);
133 }
134
135 return S_OK;
136}
137
138/* static */
139HRESULT GuestTask::setProgressErrorMsg(HRESULT hr, ComObjPtr<Progress> pProgress,
140 const char *pszText, ...)
141{
142 BOOL fCanceled;
143 BOOL fCompleted;
144 if ( SUCCEEDED(pProgress->COMGETTER(Canceled(&fCanceled)))
145 && !fCanceled
146 && SUCCEEDED(pProgress->COMGETTER(Completed(&fCompleted)))
147 && !fCompleted)
148 {
149 va_list va;
150 va_start(va, pszText);
151 HRESULT hr2 = pProgress->notifyCompleteV(hr,
152 COM_IIDOF(IGuest),
153 Guest::getStaticComponentName(),
154 pszText,
155 va);
156 va_end(va);
157 if (hr2 == S_OK) /* If unable to retrieve error, return input error. */
158 hr2 = hr;
159 return hr2;
160 }
161 return S_OK;
162}
163
164/* static */
165HRESULT GuestTask::setProgressErrorParent(HRESULT hr,
166 ComObjPtr<Progress> pProgress, ComObjPtr<Guest> pGuest)
167{
168 return setProgressErrorMsg(hr, pProgress,
169 Utf8Str(com::ErrorInfo((IGuest*)pGuest, COM_IIDOF(IGuest)).getText()).c_str());
170}
171
172#ifdef VBOX_WITH_GUEST_CONTROL
173HRESULT Guest::taskCopyFileToGuest(GuestTask *aTask)
174{
175 LogFlowFuncEnter();
176
177 AutoCaller autoCaller(this);
178 if (FAILED(autoCaller.rc())) return autoCaller.rc();
179
180 /*
181 * Do *not* take a write lock here since we don't (and won't)
182 * touch any class-specific data (of IGuest) here - only the member functions
183 * which get called here can do that.
184 */
185
186 HRESULT rc = S_OK;
187
188 try
189 {
190 ComObjPtr<Guest> pGuest = aTask->pGuest;
191
192 /* Does our source file exist? */
193 if (!RTFileExists(aTask->strSource.c_str()))
194 {
195 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
196 Guest::tr("Source file \"%s\" does not exist or is not a file"),
197 aTask->strSource.c_str());
198 }
199 else
200 {
201 RTFILE fileSource;
202 int vrc = RTFileOpen(&fileSource, aTask->strSource.c_str(),
203 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
204 if (RT_FAILURE(vrc))
205 {
206 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
207 Guest::tr("Could not open source file \"%s\" for reading (%Rrc)"),
208 aTask->strSource.c_str(), vrc);
209 }
210 else
211 {
212 uint64_t cbSize;
213 vrc = RTFileGetSize(fileSource, &cbSize);
214 if (RT_FAILURE(vrc))
215 {
216 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
217 Guest::tr("Could not query file size of \"%s\" (%Rrc)"),
218 aTask->strSource.c_str(), vrc);
219 }
220 else
221 {
222 com::SafeArray<IN_BSTR> args;
223 com::SafeArray<IN_BSTR> env;
224
225 /*
226 * Prepare tool command line.
227 */
228 char szOutput[RTPATH_MAX];
229 size_t cchOutput = RTStrPrintf(szOutput, sizeof(szOutput), "--output=%s", aTask->strDest.c_str());
230 if (cchOutput && cchOutput <= sizeof(szOutput) - 1)
231 {
232 /*
233 * Normalize path slashes, based on the detected guest.
234 */
235 Utf8Str osType = mData.mOSTypeId;
236 if ( osType.contains("Microsoft", Utf8Str::CaseInsensitive)
237 || osType.contains("Windows", Utf8Str::CaseInsensitive))
238 {
239 /* We have a Windows guest. */
240 RTPathChangeToDosSlashes(szOutput, true /* Force conversion. */);
241 }
242 else /* ... or something which isn't from Redmond ... */
243 {
244 RTPathChangeToUnixSlashes(szOutput, true /* Force conversion. */);
245 }
246
247 args.push_back(Bstr(szOutput).raw()); /* We want to write a file ... */
248 }
249 else
250 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
251 Guest::tr("Destination file for source \"%s\" invalid (%ubytes)"),
252 aTask->strSource.c_str(), cchOutput);
253
254 ComPtr<IProgress> execProgress;
255 ULONG uPID;
256 if (SUCCEEDED(rc))
257 {
258 LogRel(("Copying file \"%s\" to guest \"%s\" (%u bytes) ...\n",
259 aTask->strSource.c_str(), aTask->strDest.c_str(), cbSize));
260 /*
261 * Okay, since we gathered all stuff we need until now to start the
262 * actual copying, start the guest part now.
263 */
264 rc = pGuest->executeAndWaitForTool(Bstr(VBOXSERVICE_TOOL_CAT).raw(),
265 Bstr("Copying file to guest").raw(),
266 ComSafeArrayAsInParam(args),
267 ComSafeArrayAsInParam(env),
268 Bstr(aTask->strUserName).raw(),
269 Bstr(aTask->strPassword).raw(),
270 ExecuteProcessFlag_WaitForProcessStartOnly,
271 NULL, NULL,
272 execProgress.asOutParam(), &uPID);
273 if (FAILED(rc))
274 rc = GuestTask::setProgressErrorParent(rc, aTask->pProgress, pGuest);
275 }
276
277 if (SUCCEEDED(rc))
278 {
279 BOOL fCompleted = FALSE;
280 BOOL fCanceled = FALSE;
281
282 uint64_t cbTransferedTotal = 0;
283 uint64_t cbToRead = cbSize;
284
285 SafeArray<BYTE> aInputData(_64K);
286 while ( SUCCEEDED(execProgress->COMGETTER(Completed(&fCompleted)))
287 && !fCompleted)
288 {
289 size_t cbRead = 0;
290 if (cbSize) /* If we have nothing to read, take a shortcut. */
291 {
292 /** @todo Not very efficient, but works for now. */
293 vrc = RTFileSeek(fileSource, cbTransferedTotal,
294 RTFILE_SEEK_BEGIN, NULL /* poffActual */);
295 if (RT_SUCCESS(vrc))
296 {
297 vrc = RTFileRead(fileSource, (uint8_t*)aInputData.raw(),
298 RT_MIN(cbToRead, _64K), &cbRead);
299 /*
300 * Some other error occured? There might be a chance that RTFileRead
301 * could not resolve/map the native error code to an IPRT code, so just
302 * print a generic error.
303 */
304 if (RT_FAILURE(vrc))
305 {
306 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
307 Guest::tr("Could not read from file \"%s\" (%Rrc)"),
308 aTask->strSource.c_str(), vrc);
309 break;
310 }
311 }
312 else
313 {
314 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
315 Guest::tr("Seeking file \"%s\" failed; offset = %RU64 (%Rrc)"),
316 aTask->strSource.c_str(), cbTransferedTotal, vrc);
317 break;
318 }
319 }
320 /* Resize buffer to reflect amount we just have read.
321 * Size 0 is allowed! */
322 aInputData.resize(cbRead);
323
324 ULONG uFlags = ProcessInputFlag_None;
325 /* Did we reach the end of the content we want to transfer (last chunk)? */
326 if ( (cbRead < _64K)
327 /* Did we reach the last block which is exactly _64K? */
328 || (cbToRead - cbRead == 0)
329 /* ... or does the user want to cancel? */
330 || ( SUCCEEDED(aTask->pProgress->COMGETTER(Canceled(&fCanceled)))
331 && fCanceled)
332 )
333 {
334 uFlags |= ProcessInputFlag_EndOfFile;
335 }
336
337 ULONG cbBytesWritten = 0;
338 rc = pGuest->SetProcessInput(uPID, uFlags,
339 0 /* Infinite timeout */,
340 ComSafeArrayAsInParam(aInputData), &cbBytesWritten);
341 if (FAILED(rc))
342 {
343 rc = GuestTask::setProgressErrorParent(rc, aTask->pProgress, pGuest);
344 break;
345 }
346
347 Assert(cbRead <= cbToRead);
348 Assert(cbToRead >= cbRead);
349 /* Only subtract bytes reported written by the guest. */
350 cbToRead -= cbBytesWritten;
351
352 cbTransferedTotal += cbBytesWritten;
353 Assert(cbTransferedTotal <= cbSize);
354 aTask->pProgress->SetCurrentOperationProgress((ULONG)(cbTransferedTotal * 100 / cbSize));
355
356 /* End of file reached? */
357 if (cbToRead == 0)
358 break;
359
360 /* Did the user cancel the operation above? */
361 if (fCanceled)
362 break;
363
364 /* Progress canceled by Main API? */
365 if ( SUCCEEDED(execProgress->COMGETTER(Canceled(&fCanceled)))
366 && fCanceled)
367 {
368 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
369 Guest::tr("Copy operation of file \"%s\" was canceled on guest side"),
370 aTask->strSource.c_str());
371 break;
372 }
373 }
374
375 if (SUCCEEDED(rc))
376 {
377 /*
378 * If we got here this means the started process either was completed,
379 * canceled or we simply got all stuff transferred.
380 */
381 rc = executeWaitForExit(uPID, execProgress, 0 /* No timeout */);
382 if (FAILED(rc))
383 {
384 rc = GuestTask::setProgressErrorParent(rc, aTask->pProgress, pGuest);
385 }
386 else
387 {
388 VBOXGUESTCTRL_PROCESS proc;
389 vrc = processGetStatus(uPID, &proc, true /* Remove from PID list. */);
390 if (RT_SUCCESS(vrc))
391 {
392
393 if ( proc.mExitCode != 0
394 || proc.mStatus != ExecuteProcessStatus_TerminatedNormally)
395 {
396 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
397 Guest::tr("Guest process reported error %u (status: %u) while copying file \"%s\" to \"%s\""),
398 proc.mExitCode, proc.mStatus, aTask->strSource.c_str(), aTask->strDest.c_str());
399 }
400 }
401 }
402 }
403
404 if (SUCCEEDED(rc))
405 {
406 if (fCanceled)
407 {
408 /*
409 * In order to make the progress object to behave nicely, we also have to
410 * notify the object with a complete event when it's canceled.
411 */
412 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
413 Guest::tr("Copying file \"%s\" to guest canceled"),
414 aTask->strSource.c_str());
415 }
416 else
417 {
418 /*
419 * Even if we succeeded until here make sure to check whether we really transfered
420 * everything.
421 */
422 if ( cbSize > 0
423 && cbTransferedTotal == 0)
424 {
425 /* If nothing was transfered but the file size was > 0 then "vbox_cat" wasn't able to write
426 * to the destination -> access denied. */
427 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
428 Guest::tr("Access denied when copying file \"%s\" to \"%s\""),
429 aTask->strSource.c_str(), aTask->strDest.c_str());
430 }
431 else if (cbTransferedTotal < cbSize)
432 {
433 /* If we did not copy all let the user know. */
434 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
435 Guest::tr("Copying file \"%s\" failed (%u/%u bytes transfered)"),
436 aTask->strSource.c_str(), cbTransferedTotal, cbSize);
437 }
438 else /* Yay, all went fine! */
439 rc = GuestTask::setProgressSuccess(aTask->pProgress);
440 }
441 }
442 }
443 }
444 RTFileClose(fileSource);
445 }
446 }
447 }
448 catch (HRESULT aRC)
449 {
450 rc = aRC;
451 }
452
453 /* Clean up */
454 aTask->rc = rc;
455
456 LogFlowFunc(("rc=%Rhrc\n", rc));
457 LogFlowFuncLeave();
458
459 return VINF_SUCCESS;
460}
461
462HRESULT Guest::taskCopyFileFromGuest(GuestTask *aTask)
463{
464 LogFlowFuncEnter();
465
466 AutoCaller autoCaller(this);
467 if (FAILED(autoCaller.rc())) return autoCaller.rc();
468
469 /*
470 * Do *not* take a write lock here since we don't (and won't)
471 * touch any class-specific data (of IGuest) here - only the member functions
472 * which get called here can do that.
473 */
474
475 HRESULT rc = S_OK;
476
477 try
478 {
479 ComObjPtr<Guest> pGuest = aTask->pGuest;
480
481 /* Does our source file exist? */
482 BOOL fFileExists;
483 rc = pGuest->FileExists(Bstr(aTask->strSource).raw(),
484 Bstr(aTask->strUserName).raw(), Bstr(aTask->strPassword).raw(),
485 &fFileExists);
486 if (SUCCEEDED(rc))
487 {
488 if (!fFileExists)
489 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
490 Guest::tr("Source file \"%s\" does not exist, or is not a file"),
491 aTask->strSource.c_str());
492 }
493 else
494 rc = GuestTask::setProgressErrorParent(rc, aTask->pProgress, pGuest);
495
496 /* Query file size to make an estimate for our progress object. */
497 if (SUCCEEDED(rc))
498 {
499 LONG64 lFileSize;
500 rc = pGuest->FileQuerySize(Bstr(aTask->strSource).raw(),
501 Bstr(aTask->strUserName).raw(), Bstr(aTask->strPassword).raw(),
502 &lFileSize);
503 if (FAILED(rc))
504 rc = GuestTask::setProgressErrorParent(rc, aTask->pProgress, pGuest);
505
506 com::SafeArray<IN_BSTR> args;
507 com::SafeArray<IN_BSTR> env;
508
509 if (SUCCEEDED(rc))
510 {
511 /*
512 * Prepare tool command line.
513 */
514 char szSource[RTPATH_MAX];
515 size_t cchSource = RTStrPrintf(szSource, sizeof(szSource), "%s", aTask->strSource.c_str());
516 if (cchSource && cchSource <= sizeof(szSource) - 1)
517 {
518 /*
519 * Normalize path slashes, based on the detected guest.
520 */
521 Utf8Str osType = mData.mOSTypeId;
522 if ( osType.contains("Microsoft", Utf8Str::CaseInsensitive)
523 || osType.contains("Windows", Utf8Str::CaseInsensitive))
524 {
525 /* We have a Windows guest. */
526 RTPathChangeToDosSlashes(szSource, true /* Force conversion. */);
527 }
528 else /* ... or something which isn't from Redmond ... */
529 {
530 RTPathChangeToUnixSlashes(szSource, true /* Force conversion. */);
531 }
532
533 args.push_back(Bstr(szSource).raw()); /* Tell our cat tool which file to output. */
534 }
535 else
536 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
537 Guest::tr("Source file \"%s\" too long (%ubytes)"),
538 aTask->strSource.c_str(), cchSource);
539 }
540
541 ComPtr<IProgress> execProgress;
542 ULONG uPID;
543 if (SUCCEEDED(rc))
544 {
545 LogRel(("Copying file \"%s\" to host \"%s\" (%u bytes) ...\n",
546 aTask->strSource.c_str(), aTask->strDest.c_str(), lFileSize));
547
548 /*
549 * Okay, since we gathered all stuff we need until now to start the
550 * actual copying, start the guest part now.
551 */
552 rc = pGuest->executeAndWaitForTool(Bstr(VBOXSERVICE_TOOL_CAT).raw(),
553 Bstr("Copying file to host").raw(),
554 ComSafeArrayAsInParam(args),
555 ComSafeArrayAsInParam(env),
556 Bstr(aTask->strUserName).raw(),
557 Bstr(aTask->strPassword).raw(),
558 ExecuteProcessFlag_WaitForProcessStartOnly
559 | ExecuteProcessFlag_WaitForStdOut,
560 NULL, NULL,
561 execProgress.asOutParam(), &uPID);
562 if (FAILED(rc))
563 rc = GuestTask::setProgressErrorParent(rc, aTask->pProgress, pGuest);
564 }
565
566 if (SUCCEEDED(rc))
567 {
568 BOOL fCompleted = FALSE;
569 BOOL fCanceled = FALSE;
570
571 RTFILE hFileDest;
572 int vrc = RTFileOpen(&hFileDest, aTask->strDest.c_str(),
573 RTFILE_O_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_DENY_WRITE);
574 if (RT_FAILURE(vrc))
575 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
576 Guest::tr("Unable to create/open destination file \"%s\", rc=%Rrc"),
577 aTask->strDest.c_str(), vrc);
578 else
579 {
580 /* Note: Using size_t here is possible because the file size is
581 * stored as 32-bit value in the ISO 9660 file system. */
582 size_t cbToRead = lFileSize;
583 size_t cbTransfered = 0;
584 while ( SUCCEEDED(execProgress->COMGETTER(Completed(&fCompleted)))
585 && !fCompleted)
586 {
587 SafeArray<BYTE> aOutputData;
588 rc = pGuest->GetProcessOutput(uPID, ProcessOutputFlag_None /* StdOut */,
589 0 /* No timeout. */,
590 _64K, ComSafeArrayAsOutParam(aOutputData));
591 if (SUCCEEDED(rc))
592 {
593 if (aOutputData.size())
594 {
595 vrc = RTFileWrite(hFileDest, aOutputData.raw(), aOutputData.size(), NULL /* No partial writes */);
596 if (RT_FAILURE(vrc))
597 {
598 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
599 Guest::tr("Error writing to file \"%s\" (%u bytes left), rc=%Rrc"),
600 aTask->strSource.c_str(), cbToRead, vrc);
601 break;
602 }
603
604 Assert(cbToRead >= aOutputData.size());
605 cbToRead -= aOutputData.size();
606 cbTransfered += aOutputData.size();
607
608 aTask->pProgress->SetCurrentOperationProgress(cbTransfered / (lFileSize / 100.0));
609 }
610
611 /* Nothing read this time; try next round. */
612 }
613 else
614 {
615 rc = GuestTask::setProgressErrorParent(rc, aTask->pProgress, pGuest);
616 break;
617 }
618 }
619
620 RTFileClose(hFileDest);
621
622 if (SUCCEEDED(rc))
623 {
624 if ( cbTransfered
625 && (cbTransfered != (size_t)lFileSize))
626 {
627 /*
628 * Only bitch about an unexpected end of a file when there already
629 * was data read from that file. If this was the very first read we can
630 * be (almost) sure that this file is not meant to be read by the specified user.
631 */
632 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
633 Guest::tr("Unexpected end of file \"%s\" (%u bytes total, %u bytes transferred)"),
634 aTask->strSource.c_str(), lFileSize, cbTransfered);
635 }
636
637 if (SUCCEEDED(rc))
638 rc = GuestTask::setProgressSuccess(aTask->pProgress);
639 }
640 }
641 }
642 }
643 }
644 catch (HRESULT aRC)
645 {
646 rc = aRC;
647 }
648
649 /* Clean up */
650 aTask->rc = rc;
651
652 LogFlowFunc(("rc=%Rhrc\n", rc));
653 LogFlowFuncLeave();
654
655 return VINF_SUCCESS;
656}
657
658HRESULT Guest::taskUpdateGuestAdditions(GuestTask *aTask)
659{
660 LogFlowFuncEnter();
661
662 AutoCaller autoCaller(this);
663 if (FAILED(autoCaller.rc())) return autoCaller.rc();
664
665 /*
666 * Do *not* take a write lock here since we don't (and won't)
667 * touch any class-specific data (of IGuest) here - only the member functions
668 * which get called here can do that.
669 */
670
671 HRESULT rc = S_OK;
672 BOOL fCompleted;
673 BOOL fCanceled;
674
675 try
676 {
677 ComObjPtr<Guest> pGuest = aTask->pGuest;
678
679 aTask->pProgress->SetCurrentOperationProgress(10);
680
681 /*
682 * Determine guest OS type and the required installer image.
683 * At the moment only Windows guests are supported.
684 */
685 Utf8Str installerImage;
686 Bstr osTypeId;
687 if ( SUCCEEDED(pGuest->COMGETTER(OSTypeId(osTypeId.asOutParam())))
688 && !osTypeId.isEmpty())
689 {
690 Utf8Str osTypeIdUtf8(osTypeId); /* Needed for .contains(). */
691 if ( osTypeIdUtf8.contains("Microsoft", Utf8Str::CaseInsensitive)
692 || osTypeIdUtf8.contains("Windows", Utf8Str::CaseInsensitive))
693 {
694 if (osTypeIdUtf8.contains("64", Utf8Str::CaseInsensitive))
695 installerImage = "VBOXWINDOWSADDITIONS_AMD64.EXE";
696 else
697 installerImage = "VBOXWINDOWSADDITIONS_X86.EXE";
698 /* Since the installers are located in the root directory,
699 * no further path processing needs to be done (yet). */
700 }
701 else /* Everything else is not supported (yet). */
702 throw GuestTask::setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, aTask->pProgress,
703 Guest::tr("Detected guest OS (%s) does not support automatic Guest Additions updating, please update manually"),
704 osTypeIdUtf8.c_str());
705 }
706 else
707 throw GuestTask::setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, aTask->pProgress,
708 Guest::tr("Could not detected guest OS type/version, please update manually"));
709 Assert(!installerImage.isEmpty());
710
711 /*
712 * Try to open the .ISO file and locate the specified installer.
713 */
714 RTISOFSFILE iso;
715 int vrc = RTIsoFsOpen(&iso, aTask->strSource.c_str());
716 if (RT_FAILURE(vrc))
717 {
718 rc = GuestTask::setProgressErrorMsg(VBOX_E_FILE_ERROR, aTask->pProgress,
719 Guest::tr("Invalid installation medium detected: \"%s\""),
720 aTask->strSource.c_str());
721 }
722 else
723 {
724 uint32_t cbOffset;
725 size_t cbLength;
726 vrc = RTIsoFsGetFileInfo(&iso, installerImage.c_str(), &cbOffset, &cbLength);
727 if ( RT_SUCCESS(vrc)
728 && cbOffset
729 && cbLength)
730 {
731 vrc = RTFileSeek(iso.file, cbOffset, RTFILE_SEEK_BEGIN, NULL);
732 if (RT_FAILURE(vrc))
733 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
734 Guest::tr("Could not seek to setup file on installation medium \"%s\" (%Rrc)"),
735 aTask->strSource.c_str(), vrc);
736 }
737 else
738 {
739 switch (vrc)
740 {
741 case VERR_FILE_NOT_FOUND:
742 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
743 Guest::tr("Setup file was not found on installation medium \"%s\""),
744 aTask->strSource.c_str());
745 break;
746
747 default:
748 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
749 Guest::tr("An unknown error (%Rrc) occured while retrieving information of setup file on installation medium \"%s\""),
750 vrc, aTask->strSource.c_str());
751 break;
752 }
753 }
754
755 /* Specify the ouput path on the guest side. */
756 Utf8Str strInstallerPath = "%TEMP%\\VBoxWindowsAdditions.exe";
757
758 if (RT_SUCCESS(vrc))
759 {
760 /* Okay, we're ready to start our copy routine on the guest! */
761 aTask->pProgress->SetCurrentOperationProgress(15);
762
763 /* Prepare command line args. */
764 com::SafeArray<IN_BSTR> args;
765 com::SafeArray<IN_BSTR> env;
766
767 args.push_back(Bstr("--output").raw()); /* We want to write a file ... */
768 args.push_back(Bstr(strInstallerPath.c_str()).raw()); /* ... with this path. */
769
770 if (SUCCEEDED(rc))
771 {
772 ComPtr<IProgress> progressCat;
773 ULONG uPID;
774
775 /*
776 * Start built-in "vbox_cat" tool (inside VBoxService) to
777 * copy over/pipe the data into a file on the guest (with
778 * system rights, no username/password specified).
779 */
780 rc = pGuest->executeProcessInternal(Bstr(VBOXSERVICE_TOOL_CAT).raw(),
781 ExecuteProcessFlag_Hidden
782 | ExecuteProcessFlag_WaitForProcessStartOnly,
783 ComSafeArrayAsInParam(args),
784 ComSafeArrayAsInParam(env),
785 Bstr("").raw() /* Username. */,
786 Bstr("").raw() /* Password */,
787 5 * 1000 /* Wait 5s for getting the process started. */,
788 &uPID, progressCat.asOutParam(), &vrc);
789 if (FAILED(rc))
790 {
791 /* Errors which return VBOX_E_NOT_SUPPORTED can be safely skipped by the caller
792 * to silently fall back to "normal" (old) .ISO mounting. */
793
794 /* Due to a very limited COM error range we use vrc for a more detailed error
795 * lookup to figure out what went wrong. */
796 switch (vrc)
797 {
798 /* Guest execution service is not (yet) ready. This basically means that either VBoxService
799 * is not running (yet) or that the Guest Additions are too old (because VBoxService does not
800 * support the guest execution feature in this version). */
801 case VERR_NOT_FOUND:
802 LogRel(("Guest Additions seem not to be installed yet\n"));
803 rc = GuestTask::setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, aTask->pProgress,
804 Guest::tr("Guest Additions seem not to be installed or are not ready to update yet"));
805 break;
806
807 /* Getting back a VERR_INVALID_PARAMETER indicates that the installed Guest Additions are supporting the guest
808 * execution but not the built-in "vbox_cat" tool of VBoxService (< 4.0). */
809 case VERR_INVALID_PARAMETER:
810 LogRel(("Guest Additions are installed but don't supported automatic updating\n"));
811 rc = GuestTask::setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, aTask->pProgress,
812 Guest::tr("Installed Guest Additions do not support automatic updating"));
813 break;
814
815 case VERR_TIMEOUT:
816 LogRel(("Guest was unable to start copying the Guest Additions setup within time\n"));
817 rc = GuestTask::setProgressErrorMsg(E_FAIL, aTask->pProgress,
818 Guest::tr("Guest was unable to start copying the Guest Additions setup within time"));
819 break;
820
821 default:
822 rc = GuestTask::setProgressErrorMsg(E_FAIL, aTask->pProgress,
823 Guest::tr("Error copying Guest Additions setup file to guest path \"%s\" (%Rrc)"),
824 strInstallerPath.c_str(), vrc);
825 break;
826 }
827 }
828 else
829 {
830 LogRel(("Automatic update of Guest Additions started, using \"%s\"\n", aTask->strSource.c_str()));
831 LogRel(("Copying Guest Additions installer \"%s\" to \"%s\" on guest ...\n",
832 installerImage.c_str(), strInstallerPath.c_str()));
833 aTask->pProgress->SetCurrentOperationProgress(20);
834
835 /* Wait for process to exit ... */
836 SafeArray<BYTE> aInputData(_64K);
837 while ( SUCCEEDED(progressCat->COMGETTER(Completed(&fCompleted)))
838 && !fCompleted)
839 {
840 size_t cbRead;
841 /* cbLength contains remaining bytes of our installer file
842 * opened above to read. */
843 size_t cbToRead = RT_MIN(cbLength, _64K);
844 if (cbToRead)
845 {
846 vrc = RTFileRead(iso.file, (uint8_t*)aInputData.raw(), cbToRead, &cbRead);
847 if ( cbRead
848 && RT_SUCCESS(vrc))
849 {
850 /* Resize buffer to reflect amount we just have read. */
851 if (cbRead > 0)
852 aInputData.resize(cbRead);
853
854 /* Did we reach the end of the content we want to transfer (last chunk)? */
855 ULONG uFlags = ProcessInputFlag_None;
856 if ( (cbRead < _64K)
857 /* Did we reach the last block which is exactly _64K? */
858 || (cbToRead - cbRead == 0)
859 /* ... or does the user want to cancel? */
860 || ( SUCCEEDED(aTask->pProgress->COMGETTER(Canceled(&fCanceled)))
861 && fCanceled)
862 )
863 {
864 uFlags |= ProcessInputFlag_EndOfFile;
865 }
866
867 /* Transfer the current chunk ... */
868 #ifdef DEBUG_andy
869 LogRel(("Copying Guest Additions (%u bytes left) ...\n", cbLength));
870 #endif
871 ULONG uBytesWritten;
872 rc = pGuest->SetProcessInput(uPID, uFlags,
873 10 * 1000 /* Wait 10s for getting the input data transfered. */,
874 ComSafeArrayAsInParam(aInputData), &uBytesWritten);
875 if (FAILED(rc))
876 {
877 rc = GuestTask::setProgressErrorParent(rc, aTask->pProgress, pGuest);
878 break;
879 }
880
881 /* If task was canceled above also cancel the process execution. */
882 if (fCanceled)
883 progressCat->Cancel();
884
885 #ifdef DEBUG_andy
886 LogRel(("Copying Guest Additions (%u bytes written) ...\n", uBytesWritten));
887 #endif
888 Assert(cbLength >= uBytesWritten);
889 cbLength -= uBytesWritten;
890 }
891 else if (RT_FAILURE(vrc))
892 {
893 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
894 Guest::tr("Error while reading setup file \"%s\" (To read: %u, Size: %u) from installation medium (%Rrc)"),
895 installerImage.c_str(), cbToRead, cbLength, vrc);
896 }
897 }
898
899 /* Internal progress canceled? */
900 if ( SUCCEEDED(progressCat->COMGETTER(Canceled(&fCanceled)))
901 && fCanceled)
902 {
903 aTask->pProgress->Cancel();
904 break;
905 }
906 }
907 }
908 }
909 }
910 RTIsoFsClose(&iso);
911
912 if ( SUCCEEDED(rc)
913 && ( SUCCEEDED(aTask->pProgress->COMGETTER(Canceled(&fCanceled)))
914 && !fCanceled
915 )
916 )
917 {
918 /*
919 * Installer was transferred successfully, so let's start it
920 * (with system rights).
921 */
922 LogRel(("Preparing to execute Guest Additions update ...\n"));
923 aTask->pProgress->SetCurrentOperationProgress(66);
924
925 /* Prepare command line args for installer. */
926 com::SafeArray<IN_BSTR> installerArgs;
927 com::SafeArray<IN_BSTR> installerEnv;
928
929 /** @todo Only Windows! */
930 installerArgs.push_back(Bstr(strInstallerPath).raw()); /* The actual (internal) installer image (as argv[0]). */
931 /* Note that starting at Windows Vista the lovely session 0 separation applies:
932 * This means that if we run an application with the profile/security context
933 * of VBoxService (system rights!) we're not able to show any UI. */
934 installerArgs.push_back(Bstr("/S").raw()); /* We want to install in silent mode. */
935 installerArgs.push_back(Bstr("/l").raw()); /* ... and logging enabled. */
936 /* Don't quit VBoxService during upgrade because it still is used for this
937 * piece of code we're in right now (that is, here!) ... */
938 installerArgs.push_back(Bstr("/no_vboxservice_exit").raw());
939 /* Tell the installer to report its current installation status
940 * using a running VBoxTray instance via balloon messages in the
941 * Windows taskbar. */
942 installerArgs.push_back(Bstr("/post_installstatus").raw());
943
944 /*
945 * Start the just copied over installer with system rights
946 * in silent mode on the guest. Don't use the hidden flag since there
947 * may be pop ups the user has to process.
948 */
949 ComPtr<IProgress> progressInstaller;
950 ULONG uPID;
951 rc = pGuest->executeProcessInternal(Bstr(strInstallerPath).raw(),
952 ExecuteProcessFlag_WaitForProcessStartOnly,
953 ComSafeArrayAsInParam(installerArgs),
954 ComSafeArrayAsInParam(installerEnv),
955 Bstr("").raw() /* Username */,
956 Bstr("").raw() /* Password */,
957 10 * 1000 /* Wait 10s for getting the process started */,
958 &uPID, progressInstaller.asOutParam(), &vrc);
959 if (SUCCEEDED(rc))
960 {
961 LogRel(("Guest Additions update is running ...\n"));
962
963 /* If the caller does not want to wait for out guest update process to end,
964 * complete the progress object now so that the caller can do other work. */
965 if (aTask->uFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly)
966 {
967 rc = GuestTask::setProgressSuccess(aTask->pProgress);
968 AssertComRC(rc);
969 }
970 else
971 aTask->pProgress->SetCurrentOperationProgress(70);
972
973 /* Wait until the Guest Additions installer finishes ... */
974 while ( SUCCEEDED(progressInstaller->COMGETTER(Completed(&fCompleted)))
975 && !fCompleted)
976 {
977 if ( SUCCEEDED(aTask->pProgress->COMGETTER(Canceled(&fCanceled)))
978 && fCanceled)
979 {
980 progressInstaller->Cancel();
981 break;
982 }
983 /* Progress canceled by Main API? */
984 if ( SUCCEEDED(progressInstaller->COMGETTER(Canceled(&fCanceled)))
985 && fCanceled)
986 {
987 break;
988 }
989 RTThreadSleep(100);
990 }
991
992 ExecuteProcessStatus_T retStatus;
993 ULONG uRetExitCode, uRetFlags;
994 rc = pGuest->GetProcessStatus(uPID, &uRetExitCode, &uRetFlags, &retStatus);
995 if (SUCCEEDED(rc))
996 {
997 if (fCompleted)
998 {
999 if (uRetExitCode == 0)
1000 {
1001 LogRel(("Guest Additions update successful!\n"));
1002 if ( SUCCEEDED(aTask->pProgress->COMGETTER(Completed(&fCompleted)))
1003 && !fCompleted)
1004 {
1005 rc = GuestTask::setProgressSuccess(aTask->pProgress);
1006 }
1007 }
1008 else
1009 {
1010 LogRel(("Guest Additions update failed (Exit code=%u, Status=%u, Flags=%u)\n",
1011 uRetExitCode, retStatus, uRetFlags));
1012 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
1013 Guest::tr("Guest Additions update failed with exit code=%u (status=%u, flags=%u)"),
1014 uRetExitCode, retStatus, uRetFlags);
1015 }
1016 }
1017 else if ( SUCCEEDED(progressInstaller->COMGETTER(Canceled(&fCanceled)))
1018 && fCanceled)
1019 {
1020 LogRel(("Guest Additions update was canceled\n"));
1021 rc = GuestTask::setProgressErrorMsg(VBOX_E_IPRT_ERROR, aTask->pProgress,
1022 Guest::tr("Guest Additions update was canceled by the guest with exit code=%u (status=%u, flags=%u)"),
1023 uRetExitCode, retStatus, uRetFlags);
1024 }
1025 else
1026 {
1027 LogRel(("Guest Additions update was canceled by the user\n"));
1028 }
1029 }
1030 else
1031 rc = GuestTask::setProgressErrorParent(rc, aTask->pProgress, pGuest);
1032 }
1033 else
1034 rc = GuestTask::setProgressErrorParent(rc, aTask->pProgress, pGuest);
1035 }
1036 }
1037 }
1038 catch (HRESULT aRC)
1039 {
1040 rc = aRC;
1041 }
1042
1043 /* Clean up */
1044 aTask->rc = rc;
1045
1046 LogFlowFunc(("rc=%Rhrc\n", rc));
1047 LogFlowFuncLeave();
1048
1049 return VINF_SUCCESS;
1050}
1051#endif
1052
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette