VirtualBox

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

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

no need for floating point here

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