VirtualBox

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

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

GuestCtrl: More bugfixing, use ComPtr/ComObjPtr.

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

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