VirtualBox

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

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

GuestCtrl: Added support for explicitly waiting on stdout/stderr, bugfixes, logging adjustments.

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