VirtualBox

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

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

iprt/isofs: gcc warning and 32-bit file size limit explanation

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

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