VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/GuestSessionImplTasks.cpp@ 72187

Last change on this file since 72187 was 72090, checked in by vboxsync, 7 years ago

Guest Control/Main: Logging nits.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 88.5 KB
Line 
1/* $Id: GuestSessionImplTasks.cpp 72090 2018-05-03 12:50:00Z vboxsync $ */
2/** @file
3 * VirtualBox Main - Guest session tasks.
4 */
5
6/*
7 * Copyright (C) 2012-2018 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
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_MAIN_GUESTSESSION
23#include "LoggingNew.h"
24
25#include "GuestImpl.h"
26#ifndef VBOX_WITH_GUEST_CONTROL
27# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
28#endif
29#include "GuestSessionImpl.h"
30#include "GuestSessionImplTasks.h"
31#include "GuestCtrlImplPrivate.h"
32
33#include "Global.h"
34#include "AutoCaller.h"
35#include "ConsoleImpl.h"
36#include "ProgressImpl.h"
37
38#include <memory> /* For auto_ptr. */
39
40#include <iprt/env.h>
41#include <iprt/file.h> /* For CopyTo/From. */
42#include <iprt/dir.h>
43#include <iprt/path.h>
44
45
46/*********************************************************************************************************************************
47* Defines *
48*********************************************************************************************************************************/
49
50/**
51 * (Guest Additions) ISO file flags.
52 * Needed for handling Guest Additions updates.
53 */
54#define ISOFILE_FLAG_NONE 0
55/** Copy over the file from host to the
56 * guest. */
57#define ISOFILE_FLAG_COPY_FROM_ISO RT_BIT(0)
58/** Execute file on the guest after it has
59 * been successfully transfered. */
60#define ISOFILE_FLAG_EXECUTE RT_BIT(7)
61/** File is optional, does not have to be
62 * existent on the .ISO. */
63#define ISOFILE_FLAG_OPTIONAL RT_BIT(8)
64
65
66// session task classes
67/////////////////////////////////////////////////////////////////////////////
68
69GuestSessionTask::GuestSessionTask(GuestSession *pSession)
70 : ThreadTask("GenericGuestSessionTask")
71{
72 mSession = pSession;
73
74 switch (mSession->i_getPathStyle())
75 {
76 case PathStyle_DOS:
77 mfPathStyle = RTPATH_STR_F_STYLE_DOS;
78 mPathStyle = "\\";
79 break;
80
81 default:
82 mfPathStyle = RTPATH_STR_F_STYLE_UNIX;
83 mPathStyle = "/";
84 break;
85 }
86}
87
88GuestSessionTask::~GuestSessionTask(void)
89{
90}
91
92int GuestSessionTask::createAndSetProgressObject(ULONG cOperations /* = 1 */)
93{
94 LogFlowThisFunc(("cOperations=%ld\n", cOperations));
95
96 /* Create the progress object. */
97 ComObjPtr<Progress> pProgress;
98 HRESULT hr = pProgress.createObject();
99 if (FAILED(hr))
100 return VERR_COM_UNEXPECTED;
101
102 hr = pProgress->init(static_cast<IGuestSession*>(mSession),
103 Bstr(mDesc).raw(),
104 TRUE /* aCancelable */, cOperations, Bstr(mDesc).raw());
105 if (FAILED(hr))
106 return VERR_COM_UNEXPECTED;
107
108 mProgress = pProgress;
109
110 LogFlowFuncLeave();
111 return VINF_SUCCESS;
112}
113
114int GuestSessionTask::RunAsync(const Utf8Str &strDesc, ComObjPtr<Progress> &pProgress)
115{
116 LogFlowThisFunc(("strDesc=%s\n", strDesc.c_str()));
117
118 mDesc = strDesc;
119 mProgress = pProgress;
120 HRESULT hrc = createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER);
121
122 LogFlowThisFunc(("Returning hrc=%Rhrc\n", hrc));
123 return Global::vboxStatusCodeToCOM(hrc);
124}
125
126int GuestSessionTask::getGuestProperty(const ComObjPtr<Guest> &pGuest,
127 const Utf8Str &strPath, Utf8Str &strValue)
128{
129 ComObjPtr<Console> pConsole = pGuest->i_getConsole();
130 const ComPtr<IMachine> pMachine = pConsole->i_machine();
131
132 Assert(!pMachine.isNull());
133 Bstr strTemp, strFlags;
134 LONG64 i64Timestamp;
135 HRESULT hr = pMachine->GetGuestProperty(Bstr(strPath).raw(),
136 strTemp.asOutParam(),
137 &i64Timestamp, strFlags.asOutParam());
138 if (SUCCEEDED(hr))
139 {
140 strValue = strTemp;
141 return VINF_SUCCESS;
142 }
143 return VERR_NOT_FOUND;
144}
145
146int GuestSessionTask::setProgress(ULONG uPercent)
147{
148 if (mProgress.isNull()) /* Progress is optional. */
149 return VINF_SUCCESS;
150
151 BOOL fCanceled;
152 if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
153 && fCanceled)
154 return VERR_CANCELLED;
155 BOOL fCompleted;
156 if ( SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted)))
157 && fCompleted)
158 {
159 AssertMsgFailed(("Setting value of an already completed progress\n"));
160 return VINF_SUCCESS;
161 }
162 HRESULT hr = mProgress->SetCurrentOperationProgress(uPercent);
163 if (FAILED(hr))
164 return VERR_COM_UNEXPECTED;
165
166 return VINF_SUCCESS;
167}
168
169int GuestSessionTask::setProgressSuccess(void)
170{
171 if (mProgress.isNull()) /* Progress is optional. */
172 return VINF_SUCCESS;
173
174 BOOL fCompleted;
175 if ( SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted)))
176 && !fCompleted)
177 {
178 HRESULT hr = mProgress->i_notifyComplete(S_OK);
179 if (FAILED(hr))
180 return VERR_COM_UNEXPECTED; /** @todo Find a better rc. */
181 }
182
183 return VINF_SUCCESS;
184}
185
186HRESULT GuestSessionTask::setProgressErrorMsg(HRESULT hr, const Utf8Str &strMsg)
187{
188 LogFlowFunc(("hr=%Rhrc, strMsg=%s\n",
189 hr, strMsg.c_str()));
190
191 if (mProgress.isNull()) /* Progress is optional. */
192 return hr; /* Return original rc. */
193
194 BOOL fCanceled;
195 BOOL fCompleted;
196 if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
197 && !fCanceled
198 && SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted)))
199 && !fCompleted)
200 {
201 HRESULT hr2 = mProgress->i_notifyComplete(hr,
202 COM_IIDOF(IGuestSession),
203 GuestSession::getStaticComponentName(),
204 strMsg.c_str());
205 if (FAILED(hr2))
206 return hr2;
207 }
208 return hr; /* Return original rc. */
209}
210
211/**
212 * Creates a directory on the guest.
213 *
214 * @return VBox status code. VERR_ALREADY_EXISTS if directory on the guest already exists.
215 * @param strPath Absolute path to directory on the guest (guest style path) to create.
216 * @param enmDirectoryCreateFlags Directory creation flags.
217 * @param uMode Directory mode to use for creation.
218 * @param fFollowSymlinks Whether to follow symlinks on the guest or not.
219 */
220int GuestSessionTask::directoryCreate(const com::Utf8Str &strPath,
221 DirectoryCreateFlag_T enmDirectoryCreateFlags, uint32_t uMode, bool fFollowSymlinks)
222{
223 LogFlowFunc(("strPath=%s, fFlags=0x%x, uMode=%RU32, fFollowSymlinks=%RTbool\n",
224 strPath.c_str(), enmDirectoryCreateFlags, uMode, fFollowSymlinks));
225
226 GuestFsObjData objData; int rcGuest;
227 int rc = mSession->i_directoryQueryInfo(strPath, fFollowSymlinks, objData, &rcGuest);
228 if (RT_SUCCESS(rc))
229 {
230 return VERR_ALREADY_EXISTS;
231 }
232 else
233 {
234 switch (rc)
235 {
236 case VERR_GSTCTL_GUEST_ERROR:
237 {
238 switch (rcGuest)
239 {
240 case VERR_FILE_NOT_FOUND:
241 case VERR_PATH_NOT_FOUND:
242 rc = mSession->i_directoryCreate(strPath.c_str(), uMode, enmDirectoryCreateFlags, &rcGuest);
243 break;
244 default:
245 break;
246 }
247 break;
248 }
249
250 default:
251 break;
252 }
253 }
254
255 if (RT_FAILURE(rc))
256 {
257 if (rc == VERR_GSTCTL_GUEST_ERROR)
258 {
259 setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestProcess::i_guestErrorToString(rcGuest));
260 }
261 else
262 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
263 Utf8StrFmt(GuestSession::tr("Error creating directory on the guest: %Rrc"), strPath.c_str(), rc));
264 }
265
266 LogFlowFuncLeaveRC(rc);
267 return rc;
268}
269
270/**
271 * Main function for copying a file from guest to the host.
272 *
273 * @return VBox status code.
274 * @param srcFile Guest file (source) to copy to the host. Must be in opened and ready state already.
275 * @param phDstFile Pointer to host file handle (destination) to copy to. Must be in opened and ready state already.
276 * @param fFileCopyFlags File copy flags.
277 * @param offCopy Offset (in bytes) where to start copying the source file.
278 * @param cbSize Size (in bytes) to copy from the source file.
279 */
280int GuestSessionTask::fileCopyFromGuestInner(ComObjPtr<GuestFile> &srcFile, PRTFILE phDstFile, FileCopyFlag_T fFileCopyFlags,
281 uint64_t offCopy, uint64_t cbSize)
282{
283 RT_NOREF(fFileCopyFlags);
284
285 BOOL fCanceled = FALSE;
286 uint64_t cbWrittenTotal = 0;
287 uint64_t cbToRead = cbSize;
288
289 uint32_t uTimeoutMs = 30 * 1000; /* 30s timeout. */
290
291 int rc = VINF_SUCCESS;
292
293 if (offCopy)
294 {
295 uint64_t offActual;
296 rc = srcFile->i_seekAt(offCopy, GUEST_FILE_SEEKTYPE_BEGIN, uTimeoutMs, &offActual);
297 if (RT_FAILURE(rc))
298 {
299 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
300 Utf8StrFmt(GuestSession::tr("Seeking to offset %RU64 failed: %Rrc"), offCopy, rc));
301 return rc;
302 }
303 }
304
305 BYTE byBuf[_64K];
306 while (cbToRead)
307 {
308 uint32_t cbRead;
309 const uint32_t cbChunk = RT_MIN(cbToRead, sizeof(byBuf));
310 rc = srcFile->i_readData(cbChunk, uTimeoutMs, byBuf, sizeof(byBuf), &cbRead);
311 if (RT_FAILURE(rc))
312 {
313 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
314 Utf8StrFmt(GuestSession::tr("Reading %RU32 bytes @ %RU64 from guest failed: %Rrc"), cbChunk, cbWrittenTotal, rc));
315 break;
316 }
317
318 rc = RTFileWrite(*phDstFile, byBuf, cbRead, NULL /* No partial writes */);
319 if (RT_FAILURE(rc))
320 {
321 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
322 Utf8StrFmt(GuestSession::tr("Writing %RU32 bytes to file on host failed: %Rrc"), cbRead, rc));
323 break;
324 }
325
326 Assert(cbToRead >= cbRead);
327 cbToRead -= cbRead;
328
329 /* Update total bytes written to the guest. */
330 cbWrittenTotal += cbRead;
331 Assert(cbWrittenTotal <= cbSize);
332
333 /* Did the user cancel the operation above? */
334 if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
335 && fCanceled)
336 break;
337
338 rc = setProgress((ULONG)(cbWrittenTotal / ((uint64_t)cbSize / 100.0)));
339 if (RT_FAILURE(rc))
340 break;
341 }
342
343 if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
344 && fCanceled)
345 return VINF_SUCCESS;
346
347 if (RT_FAILURE(rc))
348 return rc;
349
350 /*
351 * Even if we succeeded until here make sure to check whether we really transfered
352 * everything.
353 */
354 if ( cbSize > 0
355 && cbWrittenTotal == 0)
356 {
357 /* If nothing was transfered but the file size was > 0 then "vbox_cat" wasn't able to write
358 * to the destination -> access denied. */
359 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
360 Utf8StrFmt(GuestSession::tr("Writing guest file to host failed: Access denied")));
361 rc = VERR_ACCESS_DENIED;
362 }
363 else if (cbWrittenTotal < cbSize)
364 {
365 /* If we did not copy all let the user know. */
366 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
367 Utf8StrFmt(GuestSession::tr("Copying guest file to host to failed (%RU64/%RU64 bytes transfered)"),
368 cbWrittenTotal, cbSize));
369 rc = VERR_INTERRUPTED;
370 }
371
372 LogFlowFuncLeaveRC(rc);
373 return rc;
374}
375
376/**
377 * Copies a file from the guest to the host.
378 *
379 * @return VBox status code. VINF_NO_CHANGE if file was skipped.
380 * @param strSource Full path of source file on the guest to copy.
381 * @param strDest Full destination path and file name (host style) to copy file to.
382 * @param fFileCopyFlags File copy flags.
383 */
384int GuestSessionTask::fileCopyFromGuest(const Utf8Str &strSource, const Utf8Str &strDest, FileCopyFlag_T fFileCopyFlags)
385{
386 LogFlowThisFunc(("strSource=%s, strDest=%s, enmFileCopyFlags=0x%x\n", strSource.c_str(), strDest.c_str(), fFileCopyFlags));
387
388 GuestFileOpenInfo srcOpenInfo;
389 RT_ZERO(srcOpenInfo);
390 srcOpenInfo.mFileName = strSource;
391 srcOpenInfo.mOpenAction = FileOpenAction_OpenExisting;
392 srcOpenInfo.mAccessMode = FileAccessMode_ReadOnly;
393 srcOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */
394
395 ComObjPtr<GuestFile> srcFile;
396
397 GuestFsObjData srcObjData;
398 int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
399 int rc = mSession->i_fsQueryInfo(strSource, TRUE /* fFollowSymlinks */, srcObjData, &rcGuest);
400 if (RT_FAILURE(rc))
401 {
402 switch (rc)
403 {
404 case VERR_GSTCTL_GUEST_ERROR:
405 setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(rcGuest));
406 break;
407
408 default:
409 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
410 Utf8StrFmt(GuestSession::tr("Source file lookup for \"%s\" failed: %Rrc"),
411 strSource.c_str(), rc));
412 break;
413 }
414 }
415 else
416 {
417 switch (srcObjData.mType)
418 {
419 case FsObjType_File:
420 break;
421
422 case FsObjType_Symlink:
423 if (!(fFileCopyFlags & FileCopyFlag_FollowLinks))
424 {
425 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
426 Utf8StrFmt(GuestSession::tr("Source file \"%s\" is a symbolic link"),
427 strSource.c_str(), rc));
428 rc = VERR_IS_A_SYMLINK;
429 }
430 break;
431
432 default:
433 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
434 Utf8StrFmt(GuestSession::tr("Source element \"%s\" is not a file"), strSource.c_str()));
435 rc = VERR_NOT_A_FILE;
436 break;
437 }
438 }
439
440 if (RT_FAILURE(rc))
441 return rc;
442
443 rc = mSession->i_fileOpen(srcOpenInfo, srcFile, &rcGuest);
444 if (RT_FAILURE(rc))
445 {
446 switch (rc)
447 {
448 case VERR_GSTCTL_GUEST_ERROR:
449 setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(rcGuest));
450 break;
451
452 default:
453 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
454 Utf8StrFmt(GuestSession::tr("Source file \"%s\" could not be opened: %Rrc"),
455 strSource.c_str(), rc));
456 break;
457 }
458 }
459
460 if (RT_FAILURE(rc))
461 return rc;
462
463 char *pszDstFile = NULL;
464 RTFSOBJINFO dstObjInfo;
465 RT_ZERO(dstObjInfo);
466
467 bool fSkip = false; /* Whether to skip handling the file. */
468
469 if (RT_SUCCESS(rc))
470 {
471 rc = RTPathQueryInfo(strDest.c_str(), &dstObjInfo, RTFSOBJATTRADD_NOTHING);
472 if (RT_SUCCESS(rc))
473 {
474 if (fFileCopyFlags & FileCopyFlag_NoReplace)
475 {
476 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
477 Utf8StrFmt(GuestSession::tr("Destination file \"%s\" already exists"), strDest.c_str()));
478 rc = VERR_ALREADY_EXISTS;
479 }
480
481 if (fFileCopyFlags & FileCopyFlag_Update)
482 {
483 RTTIMESPEC srcModificationTimeTS;
484 RTTimeSpecSetSeconds(&srcModificationTimeTS, srcObjData.mModificationTime);
485 if (RTTimeSpecCompare(&srcModificationTimeTS, &dstObjInfo.ModificationTime) <= 0)
486 {
487 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
488 Utf8StrFmt(GuestSession::tr("Destination file \"%s\" has same or newer modification date"),
489 strDest.c_str()));
490 fSkip = true;
491 }
492 }
493 }
494 else
495 {
496 if (rc != VERR_FILE_NOT_FOUND) /* Destination file does not exist (yet)? */
497 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
498 Utf8StrFmt(GuestSession::tr("Destination file lookup for \"%s\" failed: %Rrc"),
499 strDest.c_str(), rc));
500 }
501 }
502
503 if (fSkip)
504 {
505 int rc2 = srcFile->i_closeFile(&rcGuest);
506 AssertRC(rc2);
507 return VINF_SUCCESS;
508 }
509
510 if (RT_SUCCESS(rc))
511 {
512 if (RTFS_IS_FILE(dstObjInfo.Attr.fMode))
513 {
514 if (fFileCopyFlags & FileCopyFlag_NoReplace)
515 {
516 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
517 Utf8StrFmt(GuestSession::tr("Destination file \"%s\" already exists"),
518 strDest.c_str(), rc));
519 rc = VERR_ALREADY_EXISTS;
520 }
521 else
522 pszDstFile = RTStrDup(strDest.c_str());
523 }
524 else if (RTFS_IS_DIRECTORY(dstObjInfo.Attr.fMode))
525 {
526 /* Build the final file name with destination path (on the host). */
527 char szDstPath[RTPATH_MAX];
528 RTStrPrintf2(szDstPath, sizeof(szDstPath), "%s", strDest.c_str());
529
530 if ( !strDest.endsWith("\\")
531 && !strDest.endsWith("/"))
532 RTPathAppend(szDstPath, sizeof(szDstPath), "/"); /* IPRT can handle / on all hosts. */
533
534 RTPathAppend(szDstPath, sizeof(szDstPath), RTPathFilenameEx(strSource.c_str(), mfPathStyle));
535
536 pszDstFile = RTStrDup(szDstPath);
537 }
538 else if (RTFS_IS_SYMLINK(dstObjInfo.Attr.fMode))
539 {
540 if (!(fFileCopyFlags & FileCopyFlag_FollowLinks))
541 {
542 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
543 Utf8StrFmt(GuestSession::tr("Destination file \"%s\" is a symbolic link"),
544 strDest.c_str(), rc));
545 rc = VERR_IS_A_SYMLINK;
546 }
547 else
548 pszDstFile = RTStrDup(strDest.c_str());
549 }
550 else
551 {
552 LogFlowThisFunc(("Object type %RU32 not implemented yet\n", dstObjInfo.Attr.fMode));
553 rc = VERR_NOT_IMPLEMENTED;
554 }
555 }
556 else if (rc == VERR_FILE_NOT_FOUND)
557 pszDstFile = RTStrDup(strDest.c_str());
558
559 if ( RT_SUCCESS(rc)
560 || rc == VERR_FILE_NOT_FOUND)
561 {
562 if (!pszDstFile)
563 {
564 setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(GuestSession::tr("No memory to allocate destination file path")));
565 rc = VERR_NO_MEMORY;
566 }
567 else
568 {
569 RTFILE hDstFile;
570 rc = RTFileOpen(&hDstFile, pszDstFile,
571 RTFILE_O_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_DENY_WRITE); /** @todo Use the correct open modes! */
572 if (RT_SUCCESS(rc))
573 {
574 LogFlowThisFunc(("Copying '%s' to '%s' (%RI64 bytes) ...\n", strSource.c_str(), pszDstFile, srcObjData.mObjectSize));
575
576 rc = fileCopyFromGuestInner(srcFile, &hDstFile, fFileCopyFlags, 0 /* Offset, unused */, (uint64_t)srcObjData.mObjectSize);
577
578 int rc2 = RTFileClose(hDstFile);
579 AssertRC(rc2);
580 }
581 else
582 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
583 Utf8StrFmt(GuestSession::tr("Opening/creating destination file \"%s\" failed: %Rrc"),
584 pszDstFile, rc));
585 }
586 }
587
588 RTStrFree(pszDstFile);
589
590 int rc2 = srcFile->i_closeFile(&rcGuest);
591 AssertRC(rc2);
592
593 LogFlowFuncLeaveRC(rc);
594 return rc;
595}
596
597/**
598 * Main function for copying a file from host to the guest.
599 *
600 * @return VBox status code.
601 * @param phSrcFile Pointer to host file handle (source) to copy from. Must be in opened and ready state already.
602 * @param dstFile Guest file (destination) to copy to the guest. Must be in opened and ready state already.
603 * @param fFileCopyFlags File copy flags.
604 * @param offCopy Offset (in bytes) where to start copying the source file.
605 * @param cbSize Size (in bytes) to copy from the source file.
606 */
607int GuestSessionTask::fileCopyToGuestInner(PRTFILE phSrcFile, ComObjPtr<GuestFile> &dstFile, FileCopyFlag_T fFileCopyFlags,
608 uint64_t offCopy, uint64_t cbSize)
609{
610 RT_NOREF(fFileCopyFlags);
611
612 BOOL fCanceled = FALSE;
613 uint64_t cbWrittenTotal = 0;
614 uint64_t cbToRead = cbSize;
615
616 uint32_t uTimeoutMs = 30 * 1000; /* 30s timeout. */
617
618 int rc = VINF_SUCCESS;
619
620 if (offCopy)
621 {
622 uint64_t offActual;
623 rc = RTFileSeek(*phSrcFile, offCopy, RTFILE_SEEK_END, &offActual);
624 if (RT_FAILURE(rc))
625 {
626 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
627 Utf8StrFmt(GuestSession::tr("Seeking to offset %RU64 failed: %Rrc"), offCopy, rc));
628 return rc;
629 }
630 }
631
632 BYTE byBuf[_64K];
633 while (cbToRead)
634 {
635 size_t cbRead;
636 const uint32_t cbChunk = RT_MIN(cbToRead, sizeof(byBuf));
637 rc = RTFileRead(*phSrcFile, byBuf, cbChunk, &cbRead);
638 if (RT_FAILURE(rc))
639 {
640 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
641 Utf8StrFmt(GuestSession::tr("Reading %RU32 bytes @ %RU64 from host failed: %Rrc"), cbChunk, cbWrittenTotal, rc));
642 break;
643 }
644
645 rc = dstFile->i_writeData(uTimeoutMs, byBuf, (uint32_t)cbRead, NULL /* No partial writes */);
646 if (RT_FAILURE(rc))
647 {
648 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
649 Utf8StrFmt(GuestSession::tr("Writing %zu bytes to file on guest failed: %Rrc"), cbRead, rc));
650 break;
651 }
652
653 Assert(cbToRead >= cbRead);
654 cbToRead -= cbRead;
655
656 /* Update total bytes written to the guest. */
657 cbWrittenTotal += cbRead;
658 Assert(cbWrittenTotal <= cbSize);
659
660 /* Did the user cancel the operation above? */
661 if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
662 && fCanceled)
663 break;
664
665 rc = setProgress((ULONG)(cbWrittenTotal / ((uint64_t)cbSize / 100.0)));
666 if (RT_FAILURE(rc))
667 break;
668 }
669
670 if (RT_FAILURE(rc))
671 return rc;
672
673 /*
674 * Even if we succeeded until here make sure to check whether we really transfered
675 * everything.
676 */
677 if ( cbSize > 0
678 && cbWrittenTotal == 0)
679 {
680 /* If nothing was transfered but the file size was > 0 then "vbox_cat" wasn't able to write
681 * to the destination -> access denied. */
682 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
683 Utf8StrFmt(GuestSession::tr("Writing to destination file failed: Access denied")));
684 rc = VERR_ACCESS_DENIED;
685 }
686 else if (cbWrittenTotal < cbSize)
687 {
688 /* If we did not copy all let the user know. */
689 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
690 Utf8StrFmt(GuestSession::tr("Copying to destination failed (%RU64/%RU64 bytes transfered)"),
691 cbWrittenTotal, cbSize));
692 rc = VERR_INTERRUPTED;
693 }
694
695 LogFlowFuncLeaveRC(rc);
696 return rc;
697}
698
699/**
700 * Copies a file from the guest to the host.
701 *
702 * @return VBox status code. VINF_NO_CHANGE if file was skipped.
703 * @param strSource Full path of source file on the host to copy.
704 * @param strDest Full destination path and file name (guest style) to copy file to.
705 * @param fFileCopyFlags File copy flags.
706 */
707int GuestSessionTask::fileCopyToGuest(const Utf8Str &strSource, const Utf8Str &strDest, FileCopyFlag_T fFileCopyFlags)
708{
709 LogFlowThisFunc(("strSource=%s, strDest=%s, fFileCopyFlags=0x%x\n", strSource.c_str(), strDest.c_str(), fFileCopyFlags));
710
711 Utf8Str strDestFinal = strDest;
712
713 GuestFsObjData dstObjData;
714 int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
715 int rc = mSession->i_fsQueryInfo(strDest, TRUE /* fFollowSymlinks */, dstObjData, &rcGuest);
716 if (RT_FAILURE(rc))
717 {
718 switch (rc)
719 {
720 case VERR_GSTCTL_GUEST_ERROR:
721 if (rcGuest == VERR_FILE_NOT_FOUND) /* File might not exist on the guest yet. */
722 {
723 rc = VINF_SUCCESS;
724 }
725 else
726 setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(rcGuest));
727 break;
728
729 default:
730 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
731 Utf8StrFmt(GuestSession::tr("Destination file lookup for \"%s\" failed: %Rrc"),
732 strDest.c_str(), rc));
733 break;
734 }
735 }
736 else
737 {
738 switch (dstObjData.mType)
739 {
740 case FsObjType_Directory:
741 {
742 /* Build the final file name with destination path (on the host). */
743 char szDstPath[RTPATH_MAX];
744 RTStrPrintf2(szDstPath, sizeof(szDstPath), "%s", strDest.c_str());
745
746 if ( !strDest.endsWith("\\")
747 && !strDest.endsWith("/"))
748 RTStrCat(szDstPath, sizeof(szDstPath), "/");
749
750 RTStrCat(szDstPath, sizeof(szDstPath), RTPathFilename(strSource.c_str()));
751
752 strDestFinal = szDstPath;
753 break;
754 }
755
756 case FsObjType_File:
757 if (fFileCopyFlags & FileCopyFlag_NoReplace)
758 {
759 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
760 Utf8StrFmt(GuestSession::tr("Destination file \"%s\" already exists"),
761 strDest.c_str(), rc));
762 rc = VERR_ALREADY_EXISTS;
763 }
764 break;
765
766 case FsObjType_Symlink:
767 if (!(fFileCopyFlags & FileCopyFlag_FollowLinks))
768 {
769 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
770 Utf8StrFmt(GuestSession::tr("Destination file \"%s\" is a symbolic link"),
771 strDest.c_str(), rc));
772 rc = VERR_IS_A_SYMLINK;
773 }
774 break;
775
776 default:
777 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
778 Utf8StrFmt(GuestSession::tr("Destination element \"%s\" not supported"), strDest.c_str()));
779 rc = VERR_NOT_SUPPORTED;
780 break;
781 }
782 }
783
784 if (RT_FAILURE(rc))
785 return rc;
786
787 GuestFileOpenInfo dstOpenInfo;
788 RT_ZERO(dstOpenInfo);
789 dstOpenInfo.mFileName = strDestFinal;
790 if (fFileCopyFlags & FileCopyFlag_NoReplace)
791 dstOpenInfo.mOpenAction = FileOpenAction_CreateNew;
792 else
793 dstOpenInfo.mOpenAction = FileOpenAction_CreateOrReplace;
794 dstOpenInfo.mAccessMode = FileAccessMode_WriteOnly;
795 dstOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */
796
797 ComObjPtr<GuestFile> dstFile;
798 rc = mSession->i_fileOpen(dstOpenInfo, dstFile, &rcGuest);
799 if (RT_FAILURE(rc))
800 {
801 switch (rc)
802 {
803 case VERR_GSTCTL_GUEST_ERROR:
804 setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(rcGuest));
805 break;
806
807 default:
808 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
809 Utf8StrFmt(GuestSession::tr("Destination file \"%s\" could not be opened: %Rrc"),
810 strDestFinal.c_str(), rc));
811 break;
812 }
813 }
814
815 if (RT_FAILURE(rc))
816 return rc;
817
818 char szSrcReal[RTPATH_MAX];
819
820 RTFSOBJINFO srcObjInfo;
821 RT_ZERO(srcObjInfo);
822
823 bool fSkip = false; /* Whether to skip handling the file. */
824
825 if (RT_SUCCESS(rc))
826 {
827 rc = RTPathReal(strSource.c_str(), szSrcReal, sizeof(szSrcReal));
828 if (RT_FAILURE(rc))
829 {
830 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
831 Utf8StrFmt(GuestSession::tr("Source path lookup for \"%s\" failed: %Rrc"),
832 strSource.c_str(), rc));
833 }
834 else
835 {
836 rc = RTPathQueryInfo(szSrcReal, &srcObjInfo, RTFSOBJATTRADD_NOTHING);
837 if (RT_SUCCESS(rc))
838 {
839 if (fFileCopyFlags & FileCopyFlag_Update)
840 {
841 RTTIMESPEC dstModificationTimeTS;
842 RTTimeSpecSetSeconds(&dstModificationTimeTS, dstObjData.mModificationTime);
843 if (RTTimeSpecCompare(&dstModificationTimeTS, &srcObjInfo.ModificationTime) <= 0)
844 {
845 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
846 Utf8StrFmt(GuestSession::tr("Destination file \"%s\" has same or newer modification date"),
847 strDestFinal.c_str()));
848 fSkip = true;
849 }
850 }
851 }
852 else
853 {
854 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
855 Utf8StrFmt(GuestSession::tr("Source file lookup for \"%s\" failed: %Rrc"),
856 szSrcReal, rc));
857 }
858 }
859 }
860
861 if (fSkip)
862 {
863 int rc2 = dstFile->i_closeFile(&rcGuest);
864 AssertRC(rc2);
865 return VINF_SUCCESS;
866 }
867
868 if (RT_SUCCESS(rc))
869 {
870 RTFILE hSrcFile;
871 rc = RTFileOpen(&hSrcFile, szSrcReal,
872 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); /** @todo Use the correct open modes! */
873 if (RT_SUCCESS(rc))
874 {
875 LogFlowThisFunc(("Copying '%s' to '%s' (%RI64 bytes) ...\n",
876 szSrcReal, strDestFinal.c_str(), srcObjInfo.cbObject));
877
878 rc = fileCopyToGuestInner(&hSrcFile, dstFile, fFileCopyFlags, 0 /* Offset, unused */, srcObjInfo.cbObject);
879
880 int rc2 = RTFileClose(hSrcFile);
881 AssertRC(rc2);
882 }
883 else
884 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
885 Utf8StrFmt(GuestSession::tr("Opening source file \"%s\" failed: %Rrc"),
886 szSrcReal, rc));
887 }
888
889 int rc2 = dstFile->i_closeFile(&rcGuest);
890 AssertRC(rc2);
891
892 LogFlowFuncLeaveRC(rc);
893 return rc;
894}
895
896/**
897 * Adds a guest file system entry to a given list.
898 *
899 * @return VBox status code.
900 * @param strFile Path to file system entry to add.
901 * @param fsObjData Guest file system information of entry to add.
902 */
903int FsList::AddEntryFromGuest(const Utf8Str &strFile, const GuestFsObjData &fsObjData)
904{
905 LogFlowFunc(("Adding '%s'\n", strFile.c_str()));
906
907 FsEntry *pEntry = NULL;
908 try
909 {
910 pEntry = new FsEntry();
911 pEntry->fMode = fsObjData.GetFileMode();
912 pEntry->strPath = strFile;
913
914 mVecEntries.push_back(pEntry);
915 }
916 catch (...)
917 {
918 if (pEntry)
919 delete pEntry;
920 return VERR_NO_MEMORY;
921 }
922
923 return VINF_SUCCESS;
924}
925
926/**
927 * Adds a host file system entry to a given list.
928 *
929 * @return VBox status code.
930 * @param strFile Path to file system entry to add.
931 * @param pcObjInfo File system information of entry to add.
932 */
933int FsList::AddEntryFromHost(const Utf8Str &strFile, PCRTFSOBJINFO pcObjInfo)
934{
935 LogFlowFunc(("Adding '%s'\n", strFile.c_str()));
936
937 FsEntry *pEntry = NULL;
938 try
939 {
940 pEntry = new FsEntry();
941 pEntry->fMode = pcObjInfo->Attr.fMode & RTFS_TYPE_MASK;
942 pEntry->strPath = strFile;
943
944 mVecEntries.push_back(pEntry);
945 }
946 catch (...)
947 {
948 if (pEntry)
949 delete pEntry;
950 return VERR_NO_MEMORY;
951 }
952
953 return VINF_SUCCESS;
954}
955
956FsList::FsList(const GuestSessionTask &Task)
957 : mTask(Task)
958{
959}
960
961FsList::~FsList()
962{
963 Destroy();
964}
965
966/**
967 * Initializes a file list.
968 *
969 * @return VBox status code.
970 * @param strSrcRootAbs Source root path (absolute) for this file list.
971 * @param strDstRootAbs Destination root path (absolute) for this file list.
972 * @param SourceSpec Source specification to use.
973 */
974int FsList::Init(const Utf8Str &strSrcRootAbs, const Utf8Str &strDstRootAbs,
975 const GuestSessionFsSourceSpec &SourceSpec)
976{
977 mSrcRootAbs = strSrcRootAbs;
978 mDstRootAbs = strDstRootAbs;
979 mSourceSpec = SourceSpec;
980
981 /* If the source is a directory, make sure the path is properly terminated already. */
982 if (mSourceSpec.enmType == FsObjType_Directory)
983 {
984 if ( !mSrcRootAbs.endsWith("/")
985 && !mSrcRootAbs.endsWith("\\"))
986 mSrcRootAbs += "/";
987
988 if ( !mDstRootAbs.endsWith("/")
989 && !mDstRootAbs.endsWith("\\"))
990 mDstRootAbs += "/";
991 }
992
993 return VINF_SUCCESS;
994}
995
996/**
997 * Destroys a file list.
998 */
999void FsList::Destroy(void)
1000{
1001 LogFlowFuncEnter();
1002
1003 FsEntries::iterator itEntry = mVecEntries.begin();
1004 while (itEntry != mVecEntries.end())
1005 {
1006 FsEntry *pEntry = *itEntry;
1007 delete pEntry;
1008 mVecEntries.erase(itEntry);
1009 itEntry = mVecEntries.begin();
1010 }
1011
1012 Assert(mVecEntries.empty());
1013
1014 LogFlowFuncLeave();
1015}
1016
1017/**
1018 * Builds a guest file list from a given path (and optional filter).
1019 *
1020 * @return VBox status code.
1021 * @param strPath Directory on the guest to build list from.
1022 * @param strSubDir Current sub directory path; needed for recursion.
1023 * Set to an empty path.
1024 */
1025int FsList::AddDirFromGuest(const Utf8Str &strPath, const Utf8Str &strSubDir /* = "" */)
1026{
1027 Utf8Str strPathAbs = strPath;
1028 if ( !strPathAbs.endsWith("/")
1029 && !strPathAbs.endsWith("\\"))
1030 strPathAbs += "/";
1031
1032 Utf8Str strPathSub = strSubDir;
1033 if ( strPathSub.isNotEmpty()
1034 && !strPathSub.endsWith("/")
1035 && !strPathSub.endsWith("\\"))
1036 strPathSub += "/";
1037
1038 strPathAbs += strPathSub;
1039
1040 LogFlowFunc(("Entering '%s' (sub '%s')\n", strPathAbs.c_str(), strPathSub.c_str()));
1041
1042 GuestDirectoryOpenInfo dirOpenInfo;
1043 dirOpenInfo.mFilter = "";
1044 dirOpenInfo.mPath = strPathAbs;
1045 dirOpenInfo.mFlags = 0; /** @todo Handle flags? */
1046
1047 const ComObjPtr<GuestSession> &pSession = mTask.GetSession();
1048
1049 ComObjPtr <GuestDirectory> pDir; int rcGuest;
1050 int rc = pSession->i_directoryOpen(dirOpenInfo, pDir, &rcGuest);
1051 if (RT_FAILURE(rc))
1052 {
1053 switch (rc)
1054 {
1055 case VERR_INVALID_PARAMETER:
1056 break;
1057
1058 case VERR_GSTCTL_GUEST_ERROR:
1059 break;
1060
1061 default:
1062 break;
1063 }
1064
1065 return rc;
1066 }
1067
1068 if (strPathSub.isNotEmpty())
1069 {
1070 GuestFsObjData fsObjData;
1071 fsObjData.mType = FsObjType_Directory;
1072
1073 rc = AddEntryFromGuest(strPathSub, fsObjData);
1074 }
1075
1076 if (RT_SUCCESS(rc))
1077 {
1078 ComObjPtr<GuestFsObjInfo> fsObjInfo;
1079 while (RT_SUCCESS(rc = pDir->i_readInternal(fsObjInfo, &rcGuest)))
1080 {
1081 FsObjType_T enmObjType = FsObjType_Unknown; /* Shut up MSC. */
1082 HRESULT hr2 = fsObjInfo->COMGETTER(Type)(&enmObjType);
1083 AssertComRC(hr2);
1084
1085 com::Bstr bstrName;
1086 hr2 = fsObjInfo->COMGETTER(Name)(bstrName.asOutParam());
1087 AssertComRC(hr2);
1088
1089 Utf8Str strEntry = strPathSub + Utf8Str(bstrName);
1090
1091 LogFlowFunc(("Entry '%s'\n", strEntry.c_str()));
1092
1093 switch (enmObjType)
1094 {
1095 case FsObjType_Directory:
1096 {
1097 if ( bstrName.equals(".")
1098 || bstrName.equals(".."))
1099 {
1100 break;
1101 }
1102
1103 if (!(mSourceSpec.Type.Dir.fRecursive))
1104 break;
1105
1106 rc = AddDirFromGuest(strPath, strEntry);
1107 break;
1108 }
1109
1110 case FsObjType_Symlink:
1111 {
1112 if (mSourceSpec.fFollowSymlinks)
1113 {
1114 /** @todo Symlink handling from guest is not imlemented yet.
1115 * See IGuestSession::symlinkRead(). */
1116 }
1117 break;
1118 }
1119
1120 case FsObjType_File:
1121 {
1122 rc = AddEntryFromGuest(strEntry, fsObjInfo->i_getData());
1123 break;
1124 }
1125
1126 default:
1127 break;
1128 }
1129 }
1130
1131 if (rc == VERR_NO_MORE_FILES) /* End of listing reached? */
1132 rc = VINF_SUCCESS;
1133 }
1134
1135 int rc2 = pDir->i_closeInternal(&rcGuest);
1136 if (RT_SUCCESS(rc))
1137 rc = rc2;
1138
1139 return rc;
1140}
1141
1142/**
1143 * Builds a host file list from a given path (and optional filter).
1144 *
1145 * @return VBox status code.
1146 * @param strPath Directory on the host to build list from.
1147 * @param strSubDir Current sub directory path; needed for recursion.
1148 * Set to an empty path.
1149 */
1150int FsList::AddDirFromHost(const Utf8Str &strPath, const Utf8Str &strSubDir)
1151{
1152 Utf8Str strPathAbs = strPath;
1153 if ( !strPathAbs.endsWith("/")
1154 && !strPathAbs.endsWith("\\"))
1155 strPathAbs += "/";
1156
1157 Utf8Str strPathSub = strSubDir;
1158 if ( strPathSub.isNotEmpty()
1159 && !strPathSub.endsWith("/")
1160 && !strPathSub.endsWith("\\"))
1161 strPathSub += "/";
1162
1163 strPathAbs += strPathSub;
1164
1165 LogFlowFunc(("Entering '%s' (sub '%s')\n", strPathAbs.c_str(), strPathSub.c_str()));
1166
1167 RTFSOBJINFO objInfo;
1168 int rc = RTPathQueryInfo(strPathAbs.c_str(), &objInfo, RTFSOBJATTRADD_NOTHING);
1169 if (RT_SUCCESS(rc))
1170 {
1171 if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
1172 {
1173 if (strPathSub.isNotEmpty())
1174 rc = AddEntryFromHost(strPathSub, &objInfo);
1175
1176 if (RT_SUCCESS(rc))
1177 {
1178 RTDIR hDir;
1179 rc = RTDirOpen(&hDir, strPathAbs.c_str());
1180 if (RT_SUCCESS(rc))
1181 {
1182 do
1183 {
1184 /* Retrieve the next directory entry. */
1185 RTDIRENTRYEX Entry;
1186 rc = RTDirReadEx(hDir, &Entry, NULL, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
1187 if (RT_FAILURE(rc))
1188 {
1189 if (rc == VERR_NO_MORE_FILES)
1190 rc = VINF_SUCCESS;
1191 break;
1192 }
1193
1194 Utf8Str strEntry = strPathSub + Utf8Str(Entry.szName);
1195
1196 LogFlowFunc(("Entry '%s'\n", strEntry.c_str()));
1197
1198 switch (Entry.Info.Attr.fMode & RTFS_TYPE_MASK)
1199 {
1200 case RTFS_TYPE_DIRECTORY:
1201 {
1202 /* Skip "." and ".." entries. */
1203 if (RTDirEntryExIsStdDotLink(&Entry))
1204 break;
1205
1206 if (!(mSourceSpec.Type.Dir.fRecursive))
1207 break;
1208
1209 rc = AddDirFromHost(strPath, strEntry);
1210 break;
1211 }
1212
1213 case RTFS_TYPE_FILE:
1214 {
1215 rc = AddEntryFromHost(strEntry, &Entry.Info);
1216 break;
1217 }
1218
1219 case RTFS_TYPE_SYMLINK:
1220 {
1221 if (mSourceSpec.fFollowSymlinks)
1222 {
1223 Utf8Str strEntryAbs = strPathAbs + Utf8Str(Entry.szName);
1224
1225 char szPathReal[RTPATH_MAX];
1226 rc = RTPathReal(strEntryAbs.c_str(), szPathReal, sizeof(szPathReal));
1227 if (RT_SUCCESS(rc))
1228 {
1229 rc = RTPathQueryInfo(szPathReal, &objInfo, RTFSOBJATTRADD_NOTHING);
1230 if (RT_SUCCESS(rc))
1231 {
1232 LogFlowFunc(("Symlink '%s' -> '%s'\n", strEntryAbs.c_str(), szPathReal));
1233
1234 if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
1235 {
1236 LogFlowFunc(("Symlink to directory\n"));
1237 rc = AddDirFromHost(strPath, strEntry);
1238 }
1239 else if (RTFS_IS_FILE(objInfo.Attr.fMode))
1240 {
1241 LogFlowFunc(("Symlink to file\n"));
1242 rc = AddEntryFromHost(strEntry, &objInfo);
1243 }
1244 else
1245 rc = VERR_NOT_SUPPORTED;
1246 }
1247 else
1248 LogFlowFunc(("Unable to query symlink info for '%s', rc=%Rrc\n", szPathReal, rc));
1249 }
1250 else
1251 {
1252 LogFlowFunc(("Unable to resolve symlink for '%s', rc=%Rrc\n", strPathAbs.c_str(), rc));
1253 if (rc == VERR_FILE_NOT_FOUND) /* Broken symlink, skip. */
1254 rc = VINF_SUCCESS;
1255 }
1256 }
1257 break;
1258 }
1259
1260 default:
1261 break;
1262 }
1263
1264 } while (RT_SUCCESS(rc));
1265
1266 RTDirClose(hDir);
1267 }
1268 }
1269 }
1270 else if (RTFS_IS_FILE(objInfo.Attr.fMode))
1271 {
1272 rc = VERR_IS_A_FILE;
1273 }
1274 else if (RTFS_IS_SYMLINK(objInfo.Attr.fMode))
1275 {
1276 rc = VERR_IS_A_SYMLINK;
1277 }
1278 else
1279 rc = VERR_NOT_SUPPORTED;
1280 }
1281 else
1282 LogFlowFunc(("Unable to query '%s', rc=%Rrc\n", strPathAbs.c_str(), rc));
1283
1284 LogFlowFuncLeaveRC(rc);
1285 return rc;
1286}
1287
1288GuestSessionTaskOpen::GuestSessionTaskOpen(GuestSession *pSession, uint32_t uFlags, uint32_t uTimeoutMS)
1289 : GuestSessionTask(pSession)
1290 , mFlags(uFlags)
1291 , mTimeoutMS(uTimeoutMS)
1292{
1293 m_strTaskName = "gctlSesOpen";
1294}
1295
1296GuestSessionTaskOpen::~GuestSessionTaskOpen(void)
1297{
1298
1299}
1300
1301int GuestSessionTaskOpen::Run(void)
1302{
1303 LogFlowThisFuncEnter();
1304
1305 AutoCaller autoCaller(mSession);
1306 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1307
1308 int vrc = mSession->i_startSession(NULL /*pvrcGuest*/);
1309 /* Nothing to do here anymore. */
1310
1311 LogFlowFuncLeaveRC(vrc);
1312 return vrc;
1313}
1314
1315GuestSessionCopyTask::GuestSessionCopyTask(GuestSession *pSession)
1316 : GuestSessionTask(pSession)
1317{
1318}
1319
1320GuestSessionCopyTask::~GuestSessionCopyTask()
1321{
1322 FsLists::iterator itList = mVecLists.begin();
1323 while (itList != mVecLists.end())
1324 {
1325 FsList *pFsList = (*itList);
1326 pFsList->Destroy();
1327 delete pFsList;
1328 mVecLists.erase(itList);
1329 itList = mVecLists.begin();
1330 }
1331
1332 Assert(mVecLists.empty());
1333}
1334
1335GuestSessionTaskCopyFrom::GuestSessionTaskCopyFrom(GuestSession *pSession, GuestSessionFsSourceSet vecSrc, const Utf8Str &strDest)
1336 : GuestSessionCopyTask(pSession)
1337{
1338 m_strTaskName = "gctlCpyFrm";
1339
1340 mSources = vecSrc;
1341 mDest = strDest;
1342}
1343
1344GuestSessionTaskCopyFrom::~GuestSessionTaskCopyFrom(void)
1345{
1346}
1347
1348HRESULT GuestSessionTaskCopyFrom::Init(const Utf8Str &strTaskDesc)
1349{
1350 setTaskDesc(strTaskDesc);
1351
1352 /* Create the progress object. */
1353 ComObjPtr<Progress> pProgress;
1354 HRESULT hr = pProgress.createObject();
1355 if (FAILED(hr))
1356 return hr;
1357
1358 mProgress = pProgress;
1359
1360 int rc = VINF_SUCCESS;
1361
1362 ULONG cOperations = 0;
1363 Utf8Str strErrorInfo;
1364
1365 /**
1366 * Note: We need to build up the file/directory here instead of GuestSessionTaskCopyFrom::Run
1367 * because the caller expects a ready-for-operation progress object on return.
1368 * The progress object will have a variable operation count, based on the elements to
1369 * be processed.
1370 */
1371
1372 GuestSessionFsSourceSet::iterator itSrc = mSources.begin();
1373 while (itSrc != mSources.end())
1374 {
1375 Utf8Str strSrc = itSrc->strSource;
1376 Utf8Str strDst = mDest;
1377
1378 if (itSrc->enmType == FsObjType_Directory)
1379 {
1380 /* If the source does not end with a slash, copy over the entire directory
1381 * (and not just its contents). */
1382 if ( !strSrc.endsWith("/")
1383 && !strSrc.endsWith("\\"))
1384 {
1385 if ( !strDst.endsWith("/")
1386 && !strDst.endsWith("\\"))
1387 strDst += "/";
1388
1389 strDst += Utf8StrFmt("%s", RTPathFilenameEx(strSrc.c_str(), mfPathStyle));
1390 }
1391 }
1392
1393 LogFlowFunc(("strSrc=%s, strDst=%s\n", strSrc.c_str(), strDst.c_str()));
1394
1395 GuestFsObjData srcObjData; int rcGuest;
1396 rc = mSession->i_fsQueryInfo(strSrc, itSrc->fFollowSymlinks ? TRUE : FALSE,
1397 srcObjData, &rcGuest);
1398 if (RT_FAILURE(rc))
1399 {
1400 strErrorInfo = Utf8StrFmt(GuestSession::tr("No such source file/directory: %s"), strSrc.c_str());
1401 break;
1402 }
1403
1404 if (srcObjData.mType == FsObjType_Directory)
1405 {
1406 if (itSrc->enmType != FsObjType_Directory)
1407 {
1408 strErrorInfo = Utf8StrFmt(GuestSession::tr("Source is not a file: %s"), strSrc.c_str());
1409 rc = VERR_NOT_A_FILE;
1410 break;
1411 }
1412 }
1413 else
1414 {
1415 if (itSrc->enmType != FsObjType_File)
1416 {
1417 strErrorInfo = Utf8StrFmt(GuestSession::tr("Source is not a directory: %s"), strSrc.c_str());
1418 rc = VERR_NOT_A_DIRECTORY;
1419 break;
1420 }
1421 }
1422
1423 FsList *pFsList = NULL;
1424 try
1425 {
1426 pFsList = new FsList(*this);
1427 rc = pFsList->Init(strSrc, strDst, *itSrc);
1428 if (RT_SUCCESS(rc))
1429 {
1430 if (itSrc->enmType == FsObjType_Directory)
1431 {
1432 rc = pFsList->AddDirFromGuest(strSrc);
1433 }
1434 else
1435 rc = pFsList->AddEntryFromGuest(RTPathFilename(strSrc.c_str()), srcObjData);
1436 }
1437
1438 if (RT_FAILURE(rc))
1439 {
1440 delete pFsList;
1441 strErrorInfo = Utf8StrFmt(GuestSession::tr("Error adding source '%s' to list: %Rrc"), strSrc.c_str(), rc);
1442 break;
1443 }
1444
1445 mVecLists.push_back(pFsList);
1446 }
1447 catch (...)
1448 {
1449 rc = VERR_NO_MEMORY;
1450 break;
1451 }
1452
1453 AssertPtr(pFsList);
1454 cOperations += (ULONG)pFsList->mVecEntries.size();
1455
1456 itSrc++;
1457 }
1458
1459 if (cOperations) /* Use the first element as description (if available). */
1460 {
1461 Assert(mVecLists.size());
1462 Assert(mVecLists[0]->mVecEntries.size());
1463
1464 Utf8Str strFirstOp = mDest + mVecLists[0]->mVecEntries[0]->strPath;
1465 hr = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(),
1466 TRUE /* aCancelable */, cOperations + 1 /* Number of operations */,
1467 Bstr(strFirstOp).raw());
1468 }
1469 else /* If no operations have been defined, go with an "empty" progress object when will be used for error handling. */
1470 hr = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(),
1471 TRUE /* aCancelable */, 1 /* cOperations */, Bstr(mDesc).raw());
1472
1473 if (RT_FAILURE(rc))
1474 {
1475 Assert(strErrorInfo.isNotEmpty());
1476 setProgressErrorMsg(VBOX_E_IPRT_ERROR, strErrorInfo);
1477 }
1478
1479 LogFlowFunc(("Returning %Rhrc (%Rrc)\n", hr, rc));
1480 return rc;
1481}
1482
1483int GuestSessionTaskCopyFrom::Run(void)
1484{
1485 LogFlowThisFuncEnter();
1486
1487 AutoCaller autoCaller(mSession);
1488 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1489
1490 int rc = VINF_SUCCESS;
1491
1492 FsLists::const_iterator itList = mVecLists.begin();
1493 while (itList != mVecLists.end())
1494 {
1495 FsList *pList = *itList;
1496 AssertPtr(pList);
1497
1498 const bool fCopyIntoExisting = pList->mSourceSpec.Type.Dir.fCopyFlags & DirectoryCopyFlag_CopyIntoExisting;
1499 const uint32_t fDirMode = 0700; /** @todo Play safe by default; implement ACLs. */
1500
1501 LogFlowFunc(("List: srcRootAbs=%s, dstRootAbs=%s\n", pList->mSrcRootAbs.c_str(), pList->mDstRootAbs.c_str()));
1502
1503 /* Create the root directory. */
1504 if (pList->mSourceSpec.enmType == FsObjType_Directory)
1505 {
1506 rc = RTDirCreate(pList->mDstRootAbs.c_str(), fDirMode, 0 /* fCreate */);
1507 if ( rc == VWRN_ALREADY_EXISTS
1508 && !fCopyIntoExisting)
1509 {
1510 break;
1511 }
1512 }
1513
1514 FsEntries::const_iterator itEntry = pList->mVecEntries.begin();
1515 while (itEntry != pList->mVecEntries.end())
1516 {
1517 FsEntry *pEntry = *itEntry;
1518 AssertPtr(pEntry);
1519
1520 Utf8Str strSrcAbs = pList->mSrcRootAbs;
1521 Utf8Str strDstAbs = pList->mDstRootAbs;
1522 if (pList->mSourceSpec.enmType == FsObjType_Directory)
1523 {
1524 strSrcAbs += pEntry->strPath;
1525 strDstAbs += pEntry->strPath;
1526
1527 if (pList->mSourceSpec.enmPathStyle == PathStyle_DOS)
1528 strDstAbs.findReplace('\\', '/');
1529 }
1530
1531 switch (pEntry->fMode & RTFS_TYPE_MASK)
1532 {
1533 case RTFS_TYPE_DIRECTORY:
1534 LogFlowFunc(("Directory '%s': %s -> %s\n", pEntry->strPath.c_str(), strSrcAbs.c_str(), strDstAbs.c_str()));
1535 if (!pList->mSourceSpec.fDryRun)
1536 {
1537 rc = RTDirCreate(strDstAbs.c_str(), fDirMode, 0 /* fCreate */);
1538 if (rc == VERR_ALREADY_EXISTS)
1539 {
1540 if (!fCopyIntoExisting)
1541 {
1542 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
1543 Utf8StrFmt(GuestSession::tr("Destination directory \"%s\" already exists"),
1544 strDstAbs.c_str()));
1545 break;
1546 }
1547
1548 rc = VINF_SUCCESS;
1549 }
1550
1551 if (RT_FAILURE(rc))
1552 break;
1553 }
1554 break;
1555
1556 case RTFS_TYPE_FILE:
1557 LogFlowFunc(("File '%s': %s -> %s\n", pEntry->strPath.c_str(), strSrcAbs.c_str(), strDstAbs.c_str()));
1558 if (!pList->mSourceSpec.fDryRun)
1559 rc = fileCopyFromGuest(strSrcAbs, strDstAbs, FileCopyFlag_None);
1560 break;
1561
1562 default:
1563 LogFlowFunc(("Warning: Type %d for '%s' is not supported\n",
1564 pEntry->fMode & RTFS_TYPE_MASK, strSrcAbs.c_str()));
1565 break;
1566 }
1567
1568 if (RT_FAILURE(rc))
1569 break;
1570
1571 mProgress->SetNextOperation(Bstr(strSrcAbs).raw(), 1);
1572
1573 ++itEntry;
1574 }
1575
1576 if (RT_FAILURE(rc))
1577 break;
1578
1579 ++itList;
1580 }
1581
1582 if (RT_SUCCESS(rc))
1583 rc = setProgressSuccess();
1584
1585 LogFlowFuncLeaveRC(rc);
1586 return rc;
1587}
1588
1589GuestSessionTaskCopyTo::GuestSessionTaskCopyTo(GuestSession *pSession, GuestSessionFsSourceSet vecSrc, const Utf8Str &strDest)
1590 : GuestSessionCopyTask(pSession)
1591{
1592 m_strTaskName = "gctlCpyTo";
1593
1594 mSources = vecSrc;
1595 mDest = strDest;
1596}
1597
1598GuestSessionTaskCopyTo::~GuestSessionTaskCopyTo(void)
1599{
1600}
1601
1602HRESULT GuestSessionTaskCopyTo::Init(const Utf8Str &strTaskDesc)
1603{
1604 LogFlowFuncEnter();
1605
1606 setTaskDesc(strTaskDesc);
1607
1608 /* Create the progress object. */
1609 ComObjPtr<Progress> pProgress;
1610 HRESULT hr = pProgress.createObject();
1611 if (FAILED(hr))
1612 return hr;
1613
1614 mProgress = pProgress;
1615
1616 int rc = VINF_SUCCESS;
1617
1618 ULONG cOperations = 0;
1619 Utf8Str strErrorInfo;
1620
1621 /**
1622 * Note: We need to build up the file/directory here instead of GuestSessionTaskCopyTo::Run
1623 * because the caller expects a ready-for-operation progress object on return.
1624 * The progress object will have a variable operation count, based on the elements to
1625 * be processed.
1626 */
1627
1628 GuestSessionFsSourceSet::iterator itSrc = mSources.begin();
1629 while (itSrc != mSources.end())
1630 {
1631 Utf8Str strSrc = itSrc->strSource;
1632 Utf8Str strDst = mDest;
1633
1634 if (itSrc->enmType == FsObjType_Directory)
1635 {
1636 /* If the source does not end with a slash, copy over the entire directory
1637 * (and not just its contents). */
1638 if ( !strSrc.endsWith("/")
1639 && !strSrc.endsWith("\\"))
1640 {
1641 if ( !strDst.endsWith("/")
1642 && !strDst.endsWith("\\"))
1643 strDst += "/";
1644
1645 strDst += Utf8StrFmt("%s", RTPathFilenameEx(strSrc.c_str(), mfPathStyle));
1646 }
1647 }
1648
1649 LogFlowFunc(("strSrc=%s, strDst=%s\n", strSrc.c_str(), strDst.c_str()));
1650
1651 RTFSOBJINFO srcFsObjInfo;
1652 rc = RTPathQueryInfo(strSrc.c_str(), &srcFsObjInfo, RTFSOBJATTRADD_NOTHING);
1653 if (RT_FAILURE(rc))
1654 {
1655 strErrorInfo = Utf8StrFmt(GuestSession::tr("No such source file/directory: %s"), strSrc.c_str());
1656 break;
1657 }
1658
1659 if (RTFS_IS_DIRECTORY(srcFsObjInfo.Attr.fMode))
1660 {
1661 if (itSrc->enmType != FsObjType_Directory)
1662 {
1663 strErrorInfo = Utf8StrFmt(GuestSession::tr("Source is not a file: %s"), strSrc.c_str());
1664 rc = VERR_NOT_A_FILE;
1665 break;
1666 }
1667 }
1668 else
1669 {
1670 if (itSrc->enmType == FsObjType_Directory)
1671 {
1672 strErrorInfo = Utf8StrFmt(GuestSession::tr("Source is not a directory: %s"), strSrc.c_str());
1673 rc = VERR_NOT_A_DIRECTORY;
1674 break;
1675 }
1676 }
1677
1678 FsList *pFsList = NULL;
1679 try
1680 {
1681 pFsList = new FsList(*this);
1682 rc = pFsList->Init(strSrc, strDst, *itSrc);
1683 if (RT_SUCCESS(rc))
1684 {
1685 if (itSrc->enmType == FsObjType_Directory)
1686 {
1687 rc = pFsList->AddDirFromHost(strSrc);
1688 }
1689 else
1690 rc = pFsList->AddEntryFromHost(RTPathFilename(strSrc.c_str()), &srcFsObjInfo);
1691 }
1692
1693 if (RT_FAILURE(rc))
1694 {
1695 delete pFsList;
1696 strErrorInfo = Utf8StrFmt(GuestSession::tr("Error adding source '%s' to list: %Rrc"), strSrc.c_str(), rc);
1697 break;
1698 }
1699
1700 mVecLists.push_back(pFsList);
1701 }
1702 catch (...)
1703 {
1704 rc = VERR_NO_MEMORY;
1705 break;
1706 }
1707
1708 AssertPtr(pFsList);
1709 cOperations += (ULONG)pFsList->mVecEntries.size();
1710
1711 itSrc++;
1712 }
1713
1714 if (cOperations) /* Use the first element as description (if available). */
1715 {
1716 Assert(mVecLists.size());
1717 Assert(mVecLists[0]->mVecEntries.size());
1718
1719 Utf8Str strFirstOp = mDest + mVecLists[0]->mVecEntries[0]->strPath;
1720
1721 hr = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(),
1722 TRUE /* aCancelable */, cOperations + 1 /* Number of operations */,
1723 Bstr(strFirstOp).raw());
1724 }
1725 else /* If no operations have been defined, go with an "empty" progress object when will be used for error handling. */
1726 hr = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(),
1727 TRUE /* aCancelable */, 1 /* cOperations */, Bstr(mDesc).raw());
1728
1729 if (RT_FAILURE(rc))
1730 {
1731 Assert(strErrorInfo.isNotEmpty());
1732 setProgressErrorMsg(VBOX_E_IPRT_ERROR, strErrorInfo);
1733 }
1734
1735 LogFlowFunc(("Returning %Rhrc (%Rrc)\n", hr, rc));
1736 return hr;
1737}
1738
1739int GuestSessionTaskCopyTo::Run(void)
1740{
1741 LogFlowThisFuncEnter();
1742
1743 AutoCaller autoCaller(mSession);
1744 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1745
1746 int rc = VINF_SUCCESS;
1747
1748 FsLists::const_iterator itList = mVecLists.begin();
1749 while (itList != mVecLists.end())
1750 {
1751 FsList *pList = *itList;
1752 AssertPtr(pList);
1753
1754 const bool fFollowSymlinks = pList->mSourceSpec.fFollowSymlinks;
1755 const bool fCopyIntoExisting = pList->mSourceSpec.Type.Dir.fCopyFlags & DirectoryCopyFlag_CopyIntoExisting;
1756 const uint32_t fDirMode = 0700; /** @todo Play safe by default; implement ACLs. */
1757
1758 LogFlowFunc(("List: srcRootAbs=%s, dstRootAbs=%s\n", pList->mSrcRootAbs.c_str(), pList->mDstRootAbs.c_str()));
1759
1760 /* Create the root directory. */
1761 if (pList->mSourceSpec.enmType == FsObjType_Directory)
1762 {
1763 rc = directoryCreate(pList->mDstRootAbs.c_str(), DirectoryCreateFlag_None, fDirMode, fFollowSymlinks);
1764 if ( rc == VWRN_ALREADY_EXISTS
1765 && !fCopyIntoExisting)
1766 {
1767 break;
1768 }
1769 }
1770
1771 FsEntries::const_iterator itEntry = pList->mVecEntries.begin();
1772 while (itEntry != pList->mVecEntries.end())
1773 {
1774 FsEntry *pEntry = *itEntry;
1775 AssertPtr(pEntry);
1776
1777 Utf8Str strSrcAbs = pList->mSrcRootAbs;
1778 Utf8Str strDstAbs = pList->mDstRootAbs;
1779 if (pList->mSourceSpec.enmType == FsObjType_Directory)
1780 {
1781 strSrcAbs += pEntry->strPath;
1782 strDstAbs += pEntry->strPath;
1783 }
1784
1785 switch (pEntry->fMode & RTFS_TYPE_MASK)
1786 {
1787 case RTFS_TYPE_DIRECTORY:
1788 LogFlowFunc(("Directory '%s': %s -> %s\n", pEntry->strPath.c_str(), strSrcAbs.c_str(), strDstAbs.c_str()));
1789 if (!pList->mSourceSpec.fDryRun)
1790 {
1791 rc = directoryCreate(strDstAbs.c_str(), DirectoryCreateFlag_None, fDirMode, fFollowSymlinks);
1792 if ( rc == VERR_ALREADY_EXISTS
1793 && !fCopyIntoExisting)
1794 {
1795 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
1796 Utf8StrFmt(GuestSession::tr("Destination directory \"%s\" already exists"),
1797 strDstAbs.c_str()));
1798 break;
1799 }
1800 }
1801 break;
1802
1803 case RTFS_TYPE_FILE:
1804 LogFlowFunc(("File '%s': %s -> %s\n", pEntry->strPath.c_str(), strSrcAbs.c_str(), strDstAbs.c_str()));
1805 if (!pList->mSourceSpec.fDryRun)
1806 rc = fileCopyToGuest(strSrcAbs, strDstAbs, FileCopyFlag_None);
1807 break;
1808
1809 default:
1810 LogFlowFunc(("Warning: Type %d for '%s' is not supported\n",
1811 pEntry->fMode & RTFS_TYPE_MASK, strSrcAbs.c_str()));
1812 break;
1813 }
1814
1815 if (RT_FAILURE(rc))
1816 break;
1817
1818 mProgress->SetNextOperation(Bstr(strSrcAbs).raw(), 1);
1819
1820 ++itEntry;
1821 }
1822
1823 if (RT_FAILURE(rc))
1824 break;
1825
1826 ++itList;
1827 }
1828
1829 if (RT_SUCCESS(rc))
1830 rc = setProgressSuccess();
1831
1832 LogFlowFuncLeaveRC(rc);
1833 return rc;
1834}
1835
1836GuestSessionTaskUpdateAdditions::GuestSessionTaskUpdateAdditions(GuestSession *pSession,
1837 const Utf8Str &strSource,
1838 const ProcessArguments &aArguments,
1839 uint32_t fFlags)
1840 : GuestSessionTask(pSession)
1841{
1842 m_strTaskName = "gctlUpGA";
1843
1844 mSource = strSource;
1845 mArguments = aArguments;
1846 mFlags = fFlags;
1847}
1848
1849GuestSessionTaskUpdateAdditions::~GuestSessionTaskUpdateAdditions(void)
1850{
1851
1852}
1853
1854int GuestSessionTaskUpdateAdditions::addProcessArguments(ProcessArguments &aArgumentsDest, const ProcessArguments &aArgumentsSource)
1855{
1856 int rc = VINF_SUCCESS;
1857
1858 try
1859 {
1860 /* Filter out arguments which already are in the destination to
1861 * not end up having them specified twice. Not the fastest method on the
1862 * planet but does the job. */
1863 ProcessArguments::const_iterator itSource = aArgumentsSource.begin();
1864 while (itSource != aArgumentsSource.end())
1865 {
1866 bool fFound = false;
1867 ProcessArguments::iterator itDest = aArgumentsDest.begin();
1868 while (itDest != aArgumentsDest.end())
1869 {
1870 if ((*itDest).equalsIgnoreCase((*itSource)))
1871 {
1872 fFound = true;
1873 break;
1874 }
1875 ++itDest;
1876 }
1877
1878 if (!fFound)
1879 aArgumentsDest.push_back((*itSource));
1880
1881 ++itSource;
1882 }
1883 }
1884 catch(std::bad_alloc &)
1885 {
1886 return VERR_NO_MEMORY;
1887 }
1888
1889 return rc;
1890}
1891
1892int GuestSessionTaskUpdateAdditions::copyFileToGuest(GuestSession *pSession, PRTISOFSFILE pISO,
1893 Utf8Str const &strFileSource, const Utf8Str &strFileDest,
1894 bool fOptional)
1895{
1896 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
1897 AssertPtrReturn(pISO, VERR_INVALID_POINTER);
1898
1899 uint32_t cbSrcOffset;
1900 size_t cbSrcSize;
1901
1902 int rc = RTIsoFsGetFileInfo(pISO, strFileSource.c_str(), &cbSrcOffset, &cbSrcSize);
1903 if (RT_FAILURE(rc))
1904 {
1905 if (fOptional)
1906 return VINF_SUCCESS;
1907
1908 return rc;
1909 }
1910
1911 Assert(cbSrcOffset);
1912 Assert(cbSrcSize);
1913 rc = RTFileSeek(pISO->file, cbSrcOffset, RTFILE_SEEK_BEGIN, NULL);
1914 if (RT_SUCCESS(rc))
1915 {
1916 LogRel(("Copying Guest Additions installer file \"%s\" to \"%s\" on guest ...\n",
1917 strFileSource.c_str(), strFileDest.c_str()));
1918
1919 GuestFileOpenInfo dstOpenInfo;
1920 RT_ZERO(dstOpenInfo);
1921 dstOpenInfo.mFileName = strFileDest;
1922 dstOpenInfo.mOpenAction = FileOpenAction_CreateOrReplace;
1923 dstOpenInfo.mAccessMode = FileAccessMode_WriteOnly;
1924 dstOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */
1925
1926 ComObjPtr<GuestFile> dstFile; int rcGuest;
1927 rc = mSession->i_fileOpen(dstOpenInfo, dstFile, &rcGuest);
1928 if (RT_FAILURE(rc))
1929 {
1930 switch (rc)
1931 {
1932 case VERR_GSTCTL_GUEST_ERROR:
1933 setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(rcGuest));
1934 break;
1935
1936 default:
1937 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
1938 Utf8StrFmt(GuestSession::tr("Destination file \"%s\" could not be opened: %Rrc"),
1939 strFileDest.c_str(), rc));
1940 break;
1941 }
1942 }
1943 else
1944 {
1945 rc = fileCopyToGuestInner(&pISO->file, dstFile, FileCopyFlag_None, cbSrcOffset, cbSrcSize);
1946
1947 int rc2 = dstFile->i_closeFile(&rcGuest);
1948 AssertRC(rc2);
1949 }
1950
1951 if (RT_FAILURE(rc))
1952 return rc;
1953 }
1954
1955 return rc;
1956}
1957
1958int GuestSessionTaskUpdateAdditions::runFileOnGuest(GuestSession *pSession, GuestProcessStartupInfo &procInfo)
1959{
1960 AssertPtrReturn(pSession, VERR_INVALID_POINTER);
1961
1962 LogRel(("Running %s ...\n", procInfo.mName.c_str()));
1963
1964 GuestProcessTool procTool;
1965 int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
1966 int vrc = procTool.init(pSession, procInfo, false /* Async */, &rcGuest);
1967 if (RT_SUCCESS(vrc))
1968 {
1969 if (RT_SUCCESS(rcGuest))
1970 vrc = procTool.wait(GUESTPROCESSTOOL_WAIT_FLAG_NONE, &rcGuest);
1971 if (RT_SUCCESS(vrc))
1972 vrc = procTool.terminatedOk();
1973 }
1974
1975 if (RT_FAILURE(vrc))
1976 {
1977 switch (vrc)
1978 {
1979 case VWRN_GSTCTL_PROCESS_EXIT_CODE:
1980 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
1981 Utf8StrFmt(GuestSession::tr("Running update file \"%s\" on guest failed: %Rrc"),
1982 procInfo.mExecutable.c_str(), procTool.getRc()));
1983 break;
1984
1985 case VERR_GSTCTL_GUEST_ERROR:
1986 setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestProcess::i_guestErrorToString(rcGuest));
1987 break;
1988
1989 case VERR_INVALID_STATE: /** @todo Special guest control rc needed! */
1990 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
1991 Utf8StrFmt(GuestSession::tr("Update file \"%s\" reported invalid running state"),
1992 procInfo.mExecutable.c_str()));
1993 break;
1994
1995 default:
1996 setProgressErrorMsg(VBOX_E_IPRT_ERROR,
1997 Utf8StrFmt(GuestSession::tr("Error while running update file \"%s\" on guest: %Rrc"),
1998 procInfo.mExecutable.c_str(), vrc));
1999 break;
2000 }
2001 }
2002
2003 return vrc;
2004}
2005
2006int GuestSessionTaskUpdateAdditions::Run(void)
2007{
2008 LogFlowThisFuncEnter();
2009
2010 ComObjPtr<GuestSession> pSession = mSession;
2011 Assert(!pSession.isNull());
2012
2013 AutoCaller autoCaller(pSession);
2014 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2015
2016 int rc = setProgress(10);
2017 if (RT_FAILURE(rc))
2018 return rc;
2019
2020 HRESULT hr = S_OK;
2021
2022 LogRel(("Automatic update of Guest Additions started, using \"%s\"\n", mSource.c_str()));
2023
2024 ComObjPtr<Guest> pGuest(mSession->i_getParent());
2025#if 0
2026 /*
2027 * Wait for the guest being ready within 30 seconds.
2028 */
2029 AdditionsRunLevelType_T addsRunLevel;
2030 uint64_t tsStart = RTTimeSystemMilliTS();
2031 while ( SUCCEEDED(hr = pGuest->COMGETTER(AdditionsRunLevel)(&addsRunLevel))
2032 && ( addsRunLevel != AdditionsRunLevelType_Userland
2033 && addsRunLevel != AdditionsRunLevelType_Desktop))
2034 {
2035 if ((RTTimeSystemMilliTS() - tsStart) > 30 * 1000)
2036 {
2037 rc = VERR_TIMEOUT;
2038 break;
2039 }
2040
2041 RTThreadSleep(100); /* Wait a bit. */
2042 }
2043
2044 if (FAILED(hr)) rc = VERR_TIMEOUT;
2045 if (rc == VERR_TIMEOUT)
2046 hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
2047 Utf8StrFmt(GuestSession::tr("Guest Additions were not ready within time, giving up")));
2048#else
2049 /*
2050 * For use with the GUI we don't want to wait, just return so that the manual .ISO mounting
2051 * can continue.
2052 */
2053 AdditionsRunLevelType_T addsRunLevel;
2054 if ( FAILED(hr = pGuest->COMGETTER(AdditionsRunLevel)(&addsRunLevel))
2055 || ( addsRunLevel != AdditionsRunLevelType_Userland
2056 && addsRunLevel != AdditionsRunLevelType_Desktop))
2057 {
2058 if (addsRunLevel == AdditionsRunLevelType_System)
2059 hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
2060 Utf8StrFmt(GuestSession::tr("Guest Additions are installed but not fully loaded yet, aborting automatic update")));
2061 else
2062 hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
2063 Utf8StrFmt(GuestSession::tr("Guest Additions not installed or ready, aborting automatic update")));
2064 rc = VERR_NOT_SUPPORTED;
2065 }
2066#endif
2067
2068 if (RT_SUCCESS(rc))
2069 {
2070 /*
2071 * Determine if we are able to update automatically. This only works
2072 * if there are recent Guest Additions installed already.
2073 */
2074 Utf8Str strAddsVer;
2075 rc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/Version", strAddsVer);
2076 if ( RT_SUCCESS(rc)
2077 && RTStrVersionCompare(strAddsVer.c_str(), "4.1") < 0)
2078 {
2079 hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
2080 Utf8StrFmt(GuestSession::tr("Guest has too old Guest Additions (%s) installed for automatic updating, please update manually"),
2081 strAddsVer.c_str()));
2082 rc = VERR_NOT_SUPPORTED;
2083 }
2084 }
2085
2086 Utf8Str strOSVer;
2087 eOSType osType = eOSType_Unknown;
2088 if (RT_SUCCESS(rc))
2089 {
2090 /*
2091 * Determine guest OS type and the required installer image.
2092 */
2093 Utf8Str strOSType;
2094 rc = getGuestProperty(pGuest, "/VirtualBox/GuestInfo/OS/Product", strOSType);
2095 if (RT_SUCCESS(rc))
2096 {
2097 if ( strOSType.contains("Microsoft", Utf8Str::CaseInsensitive)
2098 || strOSType.contains("Windows", Utf8Str::CaseInsensitive))
2099 {
2100 osType = eOSType_Windows;
2101
2102 /*
2103 * Determine guest OS version.
2104 */
2105 rc = getGuestProperty(pGuest, "/VirtualBox/GuestInfo/OS/Release", strOSVer);
2106 if (RT_FAILURE(rc))
2107 {
2108 hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
2109 Utf8StrFmt(GuestSession::tr("Unable to detected guest OS version, please update manually")));
2110 rc = VERR_NOT_SUPPORTED;
2111 }
2112
2113 /* Because Windows 2000 + XP and is bitching with WHQL popups even if we have signed drivers we
2114 * can't do automated updates here. */
2115 /* Windows XP 64-bit (5.2) is a Windows 2003 Server actually, so skip this here. */
2116 if ( RT_SUCCESS(rc)
2117 && RTStrVersionCompare(strOSVer.c_str(), "5.0") >= 0)
2118 {
2119 if ( strOSVer.startsWith("5.0") /* Exclude the build number. */
2120 || strOSVer.startsWith("5.1") /* Exclude the build number. */)
2121 {
2122 /* If we don't have AdditionsUpdateFlag_WaitForUpdateStartOnly set we can't continue
2123 * because the Windows Guest Additions installer will fail because of WHQL popups. If the
2124 * flag is set this update routine ends successfully as soon as the installer was started
2125 * (and the user has to deal with it in the guest). */
2126 if (!(mFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly))
2127 {
2128 hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
2129 Utf8StrFmt(GuestSession::tr("Windows 2000 and XP are not supported for automatic updating due to WHQL interaction, please update manually")));
2130 rc = VERR_NOT_SUPPORTED;
2131 }
2132 }
2133 }
2134 else
2135 {
2136 hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
2137 Utf8StrFmt(GuestSession::tr("%s (%s) not supported for automatic updating, please update manually"),
2138 strOSType.c_str(), strOSVer.c_str()));
2139 rc = VERR_NOT_SUPPORTED;
2140 }
2141 }
2142 else if (strOSType.contains("Solaris", Utf8Str::CaseInsensitive))
2143 {
2144 osType = eOSType_Solaris;
2145 }
2146 else /* Everything else hopefully means Linux :-). */
2147 osType = eOSType_Linux;
2148
2149#if 1 /* Only Windows is supported (and tested) at the moment. */
2150 if ( RT_SUCCESS(rc)
2151 && osType != eOSType_Windows)
2152 {
2153 hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
2154 Utf8StrFmt(GuestSession::tr("Detected guest OS (%s) does not support automatic Guest Additions updating, please update manually"),
2155 strOSType.c_str()));
2156 rc = VERR_NOT_SUPPORTED;
2157 }
2158#endif
2159 }
2160 }
2161
2162 RTISOFSFILE iso;
2163 if (RT_SUCCESS(rc))
2164 {
2165 /*
2166 * Try to open the .ISO file to extract all needed files.
2167 */
2168 rc = RTIsoFsOpen(&iso, mSource.c_str());
2169 if (RT_FAILURE(rc))
2170 {
2171 hr = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
2172 Utf8StrFmt(GuestSession::tr("Unable to open Guest Additions .ISO file \"%s\": %Rrc"),
2173 mSource.c_str(), rc));
2174 }
2175 else
2176 {
2177 /* Set default installation directories. */
2178 Utf8Str strUpdateDir = "/tmp/";
2179 if (osType == eOSType_Windows)
2180 strUpdateDir = "C:\\Temp\\";
2181
2182 rc = setProgress(5);
2183
2184 /* Try looking up the Guest Additions installation directory. */
2185 if (RT_SUCCESS(rc))
2186 {
2187 /* Try getting the installed Guest Additions version to know whether we
2188 * can install our temporary Guest Addition data into the original installation
2189 * directory.
2190 *
2191 * Because versions prior to 4.2 had bugs wrt spaces in paths we have to choose
2192 * a different location then.
2193 */
2194 bool fUseInstallDir = false;
2195
2196 Utf8Str strAddsVer;
2197 rc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/Version", strAddsVer);
2198 if ( RT_SUCCESS(rc)
2199 && RTStrVersionCompare(strAddsVer.c_str(), "4.2r80329") > 0)
2200 {
2201 fUseInstallDir = true;
2202 }
2203
2204 if (fUseInstallDir)
2205 {
2206 if (RT_SUCCESS(rc))
2207 rc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/InstallDir", strUpdateDir);
2208 if (RT_SUCCESS(rc))
2209 {
2210 if (osType == eOSType_Windows)
2211 {
2212 strUpdateDir.findReplace('/', '\\');
2213 strUpdateDir.append("\\Update\\");
2214 }
2215 else
2216 strUpdateDir.append("/update/");
2217 }
2218 }
2219 }
2220
2221 if (RT_SUCCESS(rc))
2222 LogRel(("Guest Additions update directory is: %s\n",
2223 strUpdateDir.c_str()));
2224
2225 /* Create the installation directory. */
2226 int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
2227 rc = pSession->i_directoryCreate(strUpdateDir, 755 /* Mode */, DirectoryCreateFlag_Parents, &rcGuest);
2228 if (RT_FAILURE(rc))
2229 {
2230 switch (rc)
2231 {
2232 case VERR_GSTCTL_GUEST_ERROR:
2233 hr = setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestProcess::i_guestErrorToString(rcGuest));
2234 break;
2235
2236 default:
2237 hr = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
2238 Utf8StrFmt(GuestSession::tr("Error creating installation directory \"%s\" on the guest: %Rrc"),
2239 strUpdateDir.c_str(), rc));
2240 break;
2241 }
2242 }
2243 if (RT_SUCCESS(rc))
2244 rc = setProgress(10);
2245
2246 if (RT_SUCCESS(rc))
2247 {
2248 /* Prepare the file(s) we want to copy over to the guest and
2249 * (maybe) want to run. */
2250 switch (osType)
2251 {
2252 case eOSType_Windows:
2253 {
2254 /* Do we need to install our certificates? We do this for W2K and up. */
2255 bool fInstallCert = false;
2256
2257 /* Only Windows 2000 and up need certificates to be installed. */
2258 if (RTStrVersionCompare(strOSVer.c_str(), "5.0") >= 0)
2259 {
2260 fInstallCert = true;
2261 LogRel(("Certificates for auto updating WHQL drivers will be installed\n"));
2262 }
2263 else
2264 LogRel(("Skipping installation of certificates for WHQL drivers\n"));
2265
2266 if (fInstallCert)
2267 {
2268 static struct { const char *pszDst, *pszIso; } const s_aCertFiles[] =
2269 {
2270 { "vbox.cer", "CERT/VBOX.CER" },
2271 { "vbox-sha1.cer", "CERT/VBOX_SHA1.CER" },
2272 { "vbox-sha256.cer", "CERT/VBOX_SHA256.CER" },
2273 { "vbox-sha256-r3.cer", "CERT/VBOX_SHA256_R3.CER" },
2274 { "oracle-vbox.cer", "CERT/ORACLE_VBOX.CER" },
2275 };
2276 uint32_t fCopyCertUtil = ISOFILE_FLAG_COPY_FROM_ISO;
2277 for (uint32_t i = 0; i < RT_ELEMENTS(s_aCertFiles); i++)
2278 {
2279 /* Skip if not present on the ISO. */
2280 uint32_t offIgn;
2281 size_t cbIgn;
2282 rc = RTIsoFsGetFileInfo(&iso, s_aCertFiles[i].pszIso, &offIgn, &cbIgn);
2283 if (RT_FAILURE(rc))
2284 continue;
2285
2286 /* Copy the certificate certificate. */
2287 Utf8Str const strDstCert(strUpdateDir + s_aCertFiles[i].pszDst);
2288 mFiles.push_back(ISOFile(s_aCertFiles[i].pszIso,
2289 strDstCert,
2290 ISOFILE_FLAG_COPY_FROM_ISO | ISOFILE_FLAG_OPTIONAL));
2291
2292 /* Out certificate installation utility. */
2293 /* First pass: Copy over the file (first time only) + execute it to remove any
2294 * existing VBox certificates. */
2295 GuestProcessStartupInfo siCertUtilRem;
2296 siCertUtilRem.mName = "VirtualBox Certificate Utility, removing old VirtualBox certificates";
2297 siCertUtilRem.mArguments.push_back(Utf8Str("remove-trusted-publisher"));
2298 siCertUtilRem.mArguments.push_back(Utf8Str("--root")); /* Add root certificate as well. */
2299 siCertUtilRem.mArguments.push_back(strDstCert);
2300 siCertUtilRem.mArguments.push_back(strDstCert);
2301 mFiles.push_back(ISOFile("CERT/VBOXCERTUTIL.EXE",
2302 strUpdateDir + "VBoxCertUtil.exe",
2303 fCopyCertUtil | ISOFILE_FLAG_EXECUTE | ISOFILE_FLAG_OPTIONAL,
2304 siCertUtilRem));
2305 fCopyCertUtil = 0;
2306 /* Second pass: Only execute (but don't copy) again, this time installng the
2307 * recent certificates just copied over. */
2308 GuestProcessStartupInfo siCertUtilAdd;
2309 siCertUtilAdd.mName = "VirtualBox Certificate Utility, installing VirtualBox certificates";
2310 siCertUtilAdd.mArguments.push_back(Utf8Str("add-trusted-publisher"));
2311 siCertUtilAdd.mArguments.push_back(Utf8Str("--root")); /* Add root certificate as well. */
2312 siCertUtilAdd.mArguments.push_back(strDstCert);
2313 siCertUtilAdd.mArguments.push_back(strDstCert);
2314 mFiles.push_back(ISOFile("CERT/VBOXCERTUTIL.EXE",
2315 strUpdateDir + "VBoxCertUtil.exe",
2316 ISOFILE_FLAG_EXECUTE | ISOFILE_FLAG_OPTIONAL,
2317 siCertUtilAdd));
2318 }
2319 }
2320 /* The installers in different flavors, as we don't know (and can't assume)
2321 * the guest's bitness. */
2322 mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS_X86.EXE",
2323 strUpdateDir + "VBoxWindowsAdditions-x86.exe",
2324 ISOFILE_FLAG_COPY_FROM_ISO));
2325 mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS_AMD64.EXE",
2326 strUpdateDir + "VBoxWindowsAdditions-amd64.exe",
2327 ISOFILE_FLAG_COPY_FROM_ISO));
2328 /* The stub loader which decides which flavor to run. */
2329 GuestProcessStartupInfo siInstaller;
2330 siInstaller.mName = "VirtualBox Windows Guest Additions Installer";
2331 /* Set a running timeout of 5 minutes -- the Windows Guest Additions
2332 * setup can take quite a while, so be on the safe side. */
2333 siInstaller.mTimeoutMS = 5 * 60 * 1000;
2334 siInstaller.mArguments.push_back(Utf8Str("/S")); /* We want to install in silent mode. */
2335 siInstaller.mArguments.push_back(Utf8Str("/l")); /* ... and logging enabled. */
2336 /* Don't quit VBoxService during upgrade because it still is used for this
2337 * piece of code we're in right now (that is, here!) ... */
2338 siInstaller.mArguments.push_back(Utf8Str("/no_vboxservice_exit"));
2339 /* Tell the installer to report its current installation status
2340 * using a running VBoxTray instance via balloon messages in the
2341 * Windows taskbar. */
2342 siInstaller.mArguments.push_back(Utf8Str("/post_installstatus"));
2343 /* Add optional installer command line arguments from the API to the
2344 * installer's startup info. */
2345 rc = addProcessArguments(siInstaller.mArguments, mArguments);
2346 AssertRC(rc);
2347 /* If the caller does not want to wait for out guest update process to end,
2348 * complete the progress object now so that the caller can do other work. */
2349 if (mFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly)
2350 siInstaller.mFlags |= ProcessCreateFlag_WaitForProcessStartOnly;
2351 mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS.EXE",
2352 strUpdateDir + "VBoxWindowsAdditions.exe",
2353 ISOFILE_FLAG_COPY_FROM_ISO | ISOFILE_FLAG_EXECUTE, siInstaller));
2354 break;
2355 }
2356 case eOSType_Linux:
2357 /** @todo Add Linux support. */
2358 break;
2359 case eOSType_Solaris:
2360 /** @todo Add Solaris support. */
2361 break;
2362 default:
2363 AssertReleaseMsgFailed(("Unsupported guest type: %d\n", osType));
2364 break;
2365 }
2366 }
2367
2368 if (RT_SUCCESS(rc))
2369 {
2370 /* We want to spend 40% total for all copying operations. So roughly
2371 * calculate the specific percentage step of each copied file. */
2372 uint8_t uOffset = 20; /* Start at 20%. */
2373 uint8_t uStep = 40 / (uint8_t)mFiles.size(); Assert(mFiles.size() <= 10);
2374
2375 LogRel(("Copying over Guest Additions update files to the guest ...\n"));
2376
2377 std::vector<ISOFile>::const_iterator itFiles = mFiles.begin();
2378 while (itFiles != mFiles.end())
2379 {
2380 if (itFiles->fFlags & ISOFILE_FLAG_COPY_FROM_ISO)
2381 {
2382 bool fOptional = false;
2383 if (itFiles->fFlags & ISOFILE_FLAG_OPTIONAL)
2384 fOptional = true;
2385 rc = copyFileToGuest(pSession, &iso, itFiles->strSource, itFiles->strDest, fOptional);
2386 if (RT_FAILURE(rc))
2387 {
2388 hr = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
2389 Utf8StrFmt(GuestSession::tr("Error while copying file \"%s\" to \"%s\" on the guest: %Rrc"),
2390 itFiles->strSource.c_str(), itFiles->strDest.c_str(), rc));
2391 break;
2392 }
2393 }
2394
2395 rc = setProgress(uOffset);
2396 if (RT_FAILURE(rc))
2397 break;
2398 uOffset += uStep;
2399
2400 ++itFiles;
2401 }
2402 }
2403
2404 /* Done copying, close .ISO file. */
2405 RTIsoFsClose(&iso);
2406
2407 if (RT_SUCCESS(rc))
2408 {
2409 /* We want to spend 35% total for all copying operations. So roughly
2410 * calculate the specific percentage step of each copied file. */
2411 uint8_t uOffset = 60; /* Start at 60%. */
2412 uint8_t uStep = 35 / (uint8_t)mFiles.size(); Assert(mFiles.size() <= 10);
2413
2414 LogRel(("Executing Guest Additions update files ...\n"));
2415
2416 std::vector<ISOFile>::iterator itFiles = mFiles.begin();
2417 while (itFiles != mFiles.end())
2418 {
2419 if (itFiles->fFlags & ISOFILE_FLAG_EXECUTE)
2420 {
2421 rc = runFileOnGuest(pSession, itFiles->mProcInfo);
2422 if (RT_FAILURE(rc))
2423 break;
2424 }
2425
2426 rc = setProgress(uOffset);
2427 if (RT_FAILURE(rc))
2428 break;
2429 uOffset += uStep;
2430
2431 ++itFiles;
2432 }
2433 }
2434
2435 if (RT_SUCCESS(rc))
2436 {
2437 LogRel(("Automatic update of Guest Additions succeeded\n"));
2438 rc = setProgressSuccess();
2439 }
2440 }
2441 }
2442
2443 if (RT_FAILURE(rc))
2444 {
2445 if (rc == VERR_CANCELLED)
2446 {
2447 LogRel(("Automatic update of Guest Additions was canceled\n"));
2448
2449 hr = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
2450 Utf8StrFmt(GuestSession::tr("Installation was canceled")));
2451 }
2452 else
2453 {
2454 Utf8Str strError = Utf8StrFmt("No further error information available (%Rrc)", rc);
2455 if (!mProgress.isNull()) /* Progress object is optional. */
2456 {
2457 com::ProgressErrorInfo errorInfo(mProgress);
2458 if ( errorInfo.isFullAvailable()
2459 || errorInfo.isBasicAvailable())
2460 {
2461 strError = errorInfo.getText();
2462 }
2463 }
2464
2465 LogRel(("Automatic update of Guest Additions failed: %s (%Rhrc)\n",
2466 strError.c_str(), hr));
2467 }
2468
2469 LogRel(("Please install Guest Additions manually\n"));
2470 }
2471
2472 /** @todo Clean up copied / left over installation files. */
2473
2474 LogFlowFuncLeaveRC(rc);
2475 return rc;
2476}
2477
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