VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/MachineImplMoveVM.cpp@ 78824

Last change on this file since 78824 was 78765, checked in by vboxsync, 6 years ago

Main/MachineImplMoveVM.cpp: Another todo. bugref:8345

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 64.4 KB
Line 
1/* $Id: MachineImplMoveVM.cpp 78765 2019-05-26 04:56:41Z vboxsync $ */
2/** @file
3 * Implementation of MachineMoveVM
4 */
5
6/*
7 * Copyright (C) 2011-2019 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#define LOG_GROUP LOG_GROUP_MAIN_MACHINE
19#include <iprt/fs.h>
20#include <iprt/dir.h>
21#include <iprt/file.h>
22#include <iprt/path.h>
23#include <iprt/cpp/utils.h>
24#include <iprt/stream.h>
25#include <VBox/com/ErrorInfo.h>
26
27#include "MachineImplMoveVM.h"
28#include "MediumFormatImpl.h"
29#include "VirtualBoxImpl.h"
30#include "LoggingNew.h"
31
32/** @todo r=klaus this file is abusing the release log by putting pretty much
33 * everything there. Should be adjusted to use debug logging where appropriate
34 * and if really necessary some LogRel2 and the like. */
35
36
37/* This variable is global and used in the different places so it must be cleared each time before usage to avoid failure */
38std::vector< ComObjPtr<Machine> > machineList;
39
40typedef std::multimap<Utf8Str, Utf8Str> list_t;
41typedef std::multimap<Utf8Str, Utf8Str>::const_iterator cit_t;
42typedef std::multimap<Utf8Str, Utf8Str>::iterator it_t;
43typedef std::pair <std::multimap<Utf8Str, Utf8Str>::iterator, std::multimap<Utf8Str, Utf8Str>::iterator> rangeRes_t;
44
45struct fileList_t
46{
47 HRESULT add(const Utf8Str& folder, const Utf8Str& file)
48 {
49 HRESULT rc = S_OK;
50 m_list.insert(std::make_pair(folder, file));
51 return rc;
52 }
53
54 HRESULT add(const Utf8Str& fullPath)
55 {
56 HRESULT rc = S_OK;
57 Utf8Str folder = fullPath;
58 folder.stripFilename();
59 Utf8Str filename = fullPath;
60 filename.stripPath();
61 m_list.insert(std::make_pair(folder, filename));
62 return rc;
63 }
64
65 HRESULT removeFileFromList(const Utf8Str& fullPath)
66 {
67 HRESULT rc = S_OK;
68 Utf8Str folder = fullPath;
69 folder.stripFilename();
70 Utf8Str filename = fullPath;
71 filename.stripPath();
72 rangeRes_t res = m_list.equal_range(folder);
73 for (it_t it=res.first; it!=res.second;)
74 {
75 if (it->second.equals(filename))
76 {
77 it_t it2 = it;
78 ++it;
79 m_list.erase(it2);
80 }
81 else
82 ++it;
83 }
84
85 return rc;
86 }
87
88 HRESULT removeFileFromList(const Utf8Str& path, const Utf8Str& fileName)
89 {
90 HRESULT rc = S_OK;
91 rangeRes_t res = m_list.equal_range(path);
92 for (it_t it=res.first; it!=res.second;)
93 {
94 if (it->second.equals(fileName))
95 {
96 it_t it2 = it;
97 ++it;
98 m_list.erase(it2);
99 }
100 else
101 ++it;
102 }
103 return rc;
104 }
105
106 HRESULT removeFolderFromList(const Utf8Str& path)
107 {
108 HRESULT rc = S_OK;
109 m_list.erase(path);
110 return rc;
111 }
112
113 rangeRes_t getFilesInRange(const Utf8Str& path)
114 {
115 rangeRes_t res;
116 res = m_list.equal_range(path);
117 return res;
118 }
119
120 std::list<Utf8Str> getFilesInList(const Utf8Str& path)
121 {
122 std::list<Utf8Str> list_;
123 rangeRes_t res = m_list.equal_range(path);
124 for (it_t it=res.first; it!=res.second; ++it)
125 list_.push_back(it->second);
126 return list_;
127 }
128
129
130 list_t m_list;
131
132};
133
134
135void MachineMoveVM::ErrorInfoItem::printItem(bool fLog) const
136{
137 if (fLog)
138 {
139 LogRelFunc(("(The error code is %Rrc): %s\n",m_code, m_description.c_str()));
140 }
141 else
142 RTPrintf("(The error code is %Rrc): %s\n",m_code, m_description.c_str());
143}
144
145HRESULT MachineMoveVM::init()
146{
147 HRESULT rc = S_OK;
148
149 errorsList.clear();
150
151 Utf8Str strTargetFolder;
152 /* adding a trailing slash if it's needed */
153 {
154 size_t len = m_targetPath.length() + 2;
155 if (len >= RTPATH_MAX)
156 {
157 /** @todo r=bird: Why the leading space in errorDesc? */
158 /** @todo "The destination path exceeds the maximum length" should
159 * suffice here, I think. */
160 /** @todo r=bird: missing tr(). */
161 /** @todo r=bird: Actually, why bother with these error strings here? Nobody
162 * will ever see them, given that the caller deletes the task when
163 * init() fails. */
164 Utf8Str errorDesc(" The destination path isn't correct. The length of path exceeded the maximum value.");
165 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
166 /** @todo r=bird: What on earth is this? It will cause the caller to leak the
167 * task structure, unless it's catching exception (which it wasn't, but
168 * I made it is now). I sure hope this wasn't intentional... */
169 throw m_pMachine->setError(VBOX_E_IPRT_ERROR, m_pMachine->tr(errorDesc.c_str()));
170 }
171
172 /** @todo r=bird: I need to add a Utf8Str method or iprt/cxx/path.h thingy
173 * for doing this. We need this often and code like this doesn't
174 * need to be repeated and re-optimized in each instance... */
175 char* path = new char [len];
176 RTStrCopy(path, len, m_targetPath.c_str());
177 RTPathEnsureTrailingSeparator(path, len);
178 strTargetFolder = m_targetPath = path;
179 delete[] path;
180 }
181
182 /*
183 * We have a mode which user is able to request
184 * basic mode:
185 * - The images which are solely attached to the VM
186 * and located in the original VM folder will be moved.
187 *
188 * Comment: in the future some other modes can be added.
189 */
190
191 try
192 {
193 RTFOFF cbTotal = 0;
194 RTFOFF cbFree = 0;
195 uint32_t cbBlock = 0;
196 uint32_t cbSector = 0;
197
198 int vrc = RTFsQuerySizes(strTargetFolder.c_str(), &cbTotal, &cbFree, &cbBlock, &cbSector);
199 if (FAILED(vrc))
200 {
201 /** @todo r=bird: What's this RTPrint doing here?!? */
202 RTPrintf("strTargetFolder is %s\n", strTargetFolder.c_str());
203 Utf8StrFmt errorDesc("Unable to move machine. Can't get the destination storage size (%s)",
204 strTargetFolder.c_str());
205 errorsList.push_back(ErrorInfoItem(E_FAIL, errorDesc.c_str()));
206 rc = m_pMachine->setErrorBoth(E_FAIL, vrc, m_pMachine->tr(errorDesc.c_str()));
207 throw rc;
208 }
209
210 RTDIR hDir;
211 Utf8Str strTempFile = strTargetFolder + "test.txt";
212 vrc = RTDirOpen(&hDir, strTargetFolder.c_str());
213 if (FAILED(vrc))
214 throw rc = vrc; /** @todo r=bird: RTDirOpen returns a VBox status code. That is _not_ the same as a COM HRESULT! */
215 else
216 {
217 RTFILE hFile;
218 vrc = RTFileOpen(&hFile, strTempFile.c_str(), RTFILE_O_OPEN_CREATE | RTFILE_O_READWRITE | RTFILE_O_DENY_NONE);
219 if (FAILED(vrc))
220 {
221 LogRelFunc(("Can't create a test file %s (The error is %Rrc)\n", strTempFile.c_str(), vrc));
222 Utf8StrFmt errorDesc("Can't create a test file test.txt in the %s. Check the access rights of "
223 "the destination folder.", strTargetFolder.c_str());
224 errorsList.push_back(ErrorInfoItem(HRESULT(vrc), errorDesc.c_str()));
225 rc = m_pMachine->setErrorBoth(VBOX_E_FILE_ERROR, vrc, m_pMachine->tr(errorDesc.c_str()));
226 /** @todo r=bird: What happens to this error and 'rc'? Should it be ignored? */
227 }
228
229 RTFileClose(hFile); /** @todo r=bird: Whey close if opening fail? */
230 RTFileDelete(strTempFile.c_str());
231 RTDirClose(hDir);
232 }
233
234 /** @todo r=bird: Why the need for 'info' here? LogRelFunc can do the exact
235 * same thing without touching the heap. I don't get what this is
236 * about... There is also excessive log duplication here as well as
237 * seemingly unnecessary trailing whitespace. */
238 Utf8Str info = Utf8StrFmt("blocks: total %RTfoff, free %RTfoff ", cbTotal, cbFree);
239 LogRelFunc(("%s \n", info.c_str()));
240 LogRelFunc(("total space (Kb) %RTfoff (Mb) %RTfoff (Gb) %RTfoff\n", cbTotal/_1K, cbTotal/_1M, cbTotal/_1G));
241 LogRelFunc(("total free space (Kb) %RTfoff (Mb) %RTfoff (Gb) %RTfoff\n", cbFree/_1K, cbFree/_1M, cbFree/_1G));
242
243 RTFSPROPERTIES properties;
244 vrc = RTFsQueryProperties(strTargetFolder.c_str(), &properties);
245 if (FAILED(vrc)) throw vrc;
246 info = Utf8StrFmt("disk properties:\n"
247 "remote: %s \n"
248 "read only: %s \n"
249 "compressed: %s \n",
250 properties.fRemote == true ? "true":"false",
251 properties.fReadOnly == true ? "true":"false",
252 properties.fCompressed == true ? "true":"false");
253
254 LogRelFunc(("%s \n", info.c_str()));
255
256 /* Get the original VM path */
257 Utf8Str strSettingsFilePath;
258 Bstr bstr_settingsFilePath;
259 m_pMachine->COMGETTER(SettingsFilePath)(bstr_settingsFilePath.asOutParam()); /** @todo r=bird: check the status code. */
260 strSettingsFilePath = bstr_settingsFilePath;
261 strSettingsFilePath.stripFilename();
262
263 vmFolders.insert(std::make_pair(VBox_SettingFolder, strSettingsFilePath));
264
265 /* Collect all files from the VM's folder */
266 fileList_t fullFileList;
267 rc = getFilesList(strSettingsFilePath, fullFileList);
268 if (FAILED(rc)) throw rc;
269
270 /*
271 * Collect all known folders used by the VM:
272 * - log folder;
273 * - state folder;
274 * - snapshot folder.
275 */
276 Utf8Str strLogFolder;
277 Bstr bstr_logFolder;
278 m_pMachine->COMGETTER(LogFolder)(bstr_logFolder.asOutParam());
279 strLogFolder = bstr_logFolder;
280 if ( m_type.equals("basic")
281 && strLogFolder.contains(strSettingsFilePath))
282 {
283 vmFolders.insert(std::make_pair(VBox_LogFolder, strLogFolder));
284 }
285
286 Utf8Str strStateFilePath;
287 Bstr bstr_stateFilePath;
288 MachineState_T machineState;
289 rc = m_pMachine->COMGETTER(State)(&machineState);
290 if (FAILED(rc)) throw rc;
291 if (machineState == MachineState_Saved)
292 {
293 m_pMachine->COMGETTER(StateFilePath)(bstr_stateFilePath.asOutParam());
294 strStateFilePath = bstr_stateFilePath;
295 strStateFilePath.stripFilename();
296 if ( m_type.equals("basic")
297 && strStateFilePath.contains(strSettingsFilePath))
298 vmFolders.insert(std::make_pair(VBox_StateFolder, strStateFilePath));//Utf8Str(bstr_stateFilePath)));
299 }
300
301 Utf8Str strSnapshotFolder;
302 Bstr bstr_snapshotFolder;
303 m_pMachine->COMGETTER(SnapshotFolder)(bstr_snapshotFolder.asOutParam());
304 strSnapshotFolder = bstr_snapshotFolder;
305 if ( m_type.equals("basic")
306 && strSnapshotFolder.contains(strSettingsFilePath))
307 vmFolders.insert(std::make_pair(VBox_SnapshotFolder, strSnapshotFolder));
308
309 if (m_pMachine->i_isSnapshotMachine())
310 {
311 Bstr bstrSrcMachineId;
312 rc = m_pMachine->COMGETTER(Id)(bstrSrcMachineId.asOutParam());
313 if (FAILED(rc)) throw rc;
314 ComPtr<IMachine> newSrcMachine;
315 rc = m_pMachine->i_getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), newSrcMachine.asOutParam());
316 if (FAILED(rc)) throw rc;
317 }
318
319 /* Add the current machine and all snapshot machines below this machine
320 * in a list for further processing.
321 */
322
323 long long neededFreeSpace = 0;
324
325 /* Actual file list */
326 fileList_t actualFileList;
327 Utf8Str strTargetImageName;
328
329 /* Global variable (defined at the beginning of file), so clear it before usage */
330 machineList.clear();
331 machineList.push_back(m_pMachine);
332
333 {
334 ULONG cSnapshots = 0;
335 rc = m_pMachine->COMGETTER(SnapshotCount)(&cSnapshots);
336 if (FAILED(rc)) throw rc;
337 if (cSnapshots > 0)
338 {
339 Utf8Str id;
340 if (m_pMachine->i_isSnapshotMachine())
341 id = m_pMachine->i_getSnapshotId().toString();
342 ComPtr<ISnapshot> pSnapshot;
343 rc = m_pMachine->FindSnapshot(Bstr(id).raw(), pSnapshot.asOutParam());
344 if (FAILED(rc)) throw rc;
345 rc = createMachineList(pSnapshot, machineList);
346 if (FAILED(rc)) throw rc;
347 }
348 }
349
350 ULONG uCount = 1;//looks like it should be initialized by 1. See assertion in the Progress::setNextOperation()
351 ULONG uTotalWeight = 1;
352
353 /* The lists llMedias and llSaveStateFiles are filled in the queryMediasForAllStates() */
354 queryMediasForAllStates(machineList); /** @todo r=bird: As a rule status codes needs checking every time. */
355
356 {
357 uint64_t totalMediumsSize = 0;
358
359 for (size_t i = 0; i < llMedias.size(); ++i)
360 {
361 LONG64 cbSize = 0;
362 MEDIUMTASKCHAINMOVE &mtc = llMedias.at(i);
363 for (size_t a = mtc.chain.size(); a > 0; --a)
364 {
365 Bstr bstrLocation;
366 Utf8Str strLocation;
367 Utf8Str name = mtc.chain[a - 1].strBaseName;
368 ComPtr<IMedium> plMedium = mtc.chain[a - 1].pMedium;
369 rc = plMedium->COMGETTER(Location)(bstrLocation.asOutParam());
370 if (FAILED(rc)) throw rc;
371 strLocation = bstrLocation;
372
373 /*if an image is located in the actual VM folder it will be added to the actual list */
374 if (strLocation.contains(strSettingsFilePath))
375 {
376 rc = plMedium->COMGETTER(Size)(&cbSize);
377 if (FAILED(rc)) throw rc;
378
379 std::pair<std::map<Utf8Str, MEDIUMTASKMOVE>::iterator,bool> ret;
380 ret = finalMediumsMap.insert(std::make_pair(name, mtc.chain[a - 1]));
381 if (ret.second == true)
382 {
383 /* Calculate progress data */
384 ++uCount;
385 uTotalWeight += mtc.chain[a - 1].uWeight;
386 totalMediumsSize += cbSize;
387 LogRelFunc(("Image %s was added into the moved list\n", name.c_str()));
388 }
389 }
390 }
391 }
392
393 LogRelFunc(("Total Size of images is %lld bytes\n", totalMediumsSize));
394 neededFreeSpace += totalMediumsSize;
395 }
396
397 /* Prepare data for moving ".sav" files */
398 {
399 uint64_t totalStateSize = 0;
400
401 for (size_t i = 0; i < llSaveStateFiles.size(); ++i)
402 {
403 uint64_t cbFile = 0;
404 SAVESTATETASKMOVE &sst = llSaveStateFiles.at(i);
405
406 Utf8Str name = sst.strSaveStateFile;
407 /*if a state file is located in the actual VM folder it will be added to the actual list */
408 if (name.contains(strSettingsFilePath))
409 {
410 vrc = RTFileQuerySize(name.c_str(), &cbFile);
411 if (RT_SUCCESS(vrc))
412 {
413 std::pair<std::map<Utf8Str, SAVESTATETASKMOVE>::iterator,bool> ret;
414 ret = finalSaveStateFilesMap.insert(std::make_pair(name, sst));
415 if (ret.second == true)
416 {
417 totalStateSize += cbFile;
418 ++uCount;
419 uTotalWeight += sst.uWeight;
420 LogRelFunc(("The state file %s was added into the moved list\n", name.c_str()));
421 }
422 }
423 else
424 LogRelFunc(("The state file %s wasn't added into the moved list. Couldn't get the file size.\n",
425 name.c_str()));
426 }
427 }
428
429 neededFreeSpace += totalStateSize;
430 }
431
432 /* Prepare data for moving the log files */
433 {
434 Utf8Str strFolder = vmFolders[VBox_LogFolder];
435
436 if (RTPathExists(strFolder.c_str()))
437 {
438 uint64_t totalLogSize = 0;
439 rc = getFolderSize(strFolder, totalLogSize);
440 if (SUCCEEDED(rc))
441 {
442 neededFreeSpace += totalLogSize;
443 if (cbFree - neededFreeSpace <= _1M)
444 {
445 throw VERR_OUT_OF_RESOURCES;//less than 1Mb free space on the target location
446 }
447
448 fileList_t filesList;
449 getFilesList(strFolder, filesList); /** @todo r=bird: return code check */
450 cit_t it = filesList.m_list.begin();
451 while (it != filesList.m_list.end())
452 {
453 Utf8Str strFile = it->first.c_str();
454 strFile.append(RTPATH_DELIMITER).append(it->second.c_str());
455
456 uint64_t cbFile = 0;
457 vrc = RTFileQuerySize(strFile.c_str(), &cbFile);
458 if (RT_SUCCESS(vrc))
459 {
460 uCount += 1;
461 uTotalWeight += (ULONG)((cbFile + _1M - 1) / _1M);
462 actualFileList.add(strFile);
463 LogRelFunc(("The log file %s added into the moved list\n", strFile.c_str()));
464 }
465 else
466 LogRelFunc(("The log file %s wasn't added into the moved list. Couldn't get the file size."
467 "\n", strFile.c_str()));
468 ++it;
469 }
470 }
471 }
472 else
473 {
474 LogRelFunc(("Information: The original log folder %s doesn't exist\n", strFolder.c_str()));
475 rc = S_OK;//it's not error in this case if there isn't an original log folder
476 }
477 }
478
479 LogRelFunc(("Total space needed is %lld bytes\n", neededFreeSpace));
480 /* Check a target location on enough room */
481 if (cbFree - neededFreeSpace <= _1M)
482 {
483 LogRelFunc(("but free space on destination is %RTfoff\n", cbFree));
484 /** @todo r=bird: You're throwing a VBox status code here, but only catching
485 * HRESULT. So, this will go to the caller or someone else up the
486 * stack, which is unacceptable throw behaviour. */
487 throw VERR_OUT_OF_RESOURCES;//less than 1Mb free space on the target location
488 }
489
490 /* Add step for .vbox machine setting file */
491 { /** @todo what's the scoping for? */
492 ++uCount;
493 uTotalWeight += 1;
494 }
495
496 /* Reserve additional steps in case of failure and rollback all changes */
497 {
498 uTotalWeight += uCount;//just add 1 for each possible rollback operation
499 uCount += uCount;//and increase the steps twice
500 }
501
502 /* Init Progress instance */
503 {
504 rc = m_pProgress->init(m_pMachine->i_getVirtualBox(),
505 static_cast<IMachine*>(m_pMachine) /* aInitiator */,
506 Utf8Str(m_pMachine->tr("Moving Machine")),
507 true /* fCancellable */,
508 uCount,
509 uTotalWeight,
510 Utf8Str(m_pMachine->tr("Initialize Moving")),
511 1);
512 if (FAILED(rc))
513 {
514 Utf8StrFmt errorDesc("Couldn't correctly setup the progress object for moving VM operation (%Rrc)", rc);
515 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
516
517 throw m_pMachine->setError(VBOX_E_IPRT_ERROR, m_pMachine->tr(errorDesc.c_str()));
518 }
519 }
520
521 /* save all VM data */
522 m_pMachine->i_setModified(Machine::IsModified_MachineData);
523 rc = m_pMachine->SaveSettings();
524 }
525 catch(HRESULT hrc)
526 {
527/** @todo r=bird: Seriously, why throw any error when you can just return
528 * them. This is just copying the thrown message and passing it onto
529 * the return statemnt a few lines later. (The log statemnt doesn't
530 * really count as it doesn't log the status.) */
531 rc = hrc;
532 }
533
534 LogFlowFuncLeave();
535
536 return rc;
537}
538
539void MachineMoveVM::printStateFile(settings::SnapshotsList &snl)
540{
541 settings::SnapshotsList::iterator it;
542 for (it = snl.begin(); it != snl.end(); ++it)
543 {
544 if (!it->strStateFile.isEmpty())
545 {
546 settings::Snapshot snap = (settings::Snapshot)(*it);
547 LogRelFunc(("snap.uuid = %s\n", snap.uuid.toStringCurly().c_str()));
548 LogRelFunc(("snap.strStateFile = %s\n", snap.strStateFile.c_str()));
549 }
550
551 if (!it->llChildSnapshots.empty())
552 printStateFile(it->llChildSnapshots);
553 }
554}
555
556/* static */
557DECLCALLBACK(int) MachineMoveVM::updateProgress(unsigned uPercent, void *pvUser)
558{
559 MachineMoveVM* pTask = *(MachineMoveVM**)pvUser;
560
561 if ( pTask
562 && !pTask->m_pProgress.isNull())
563 {
564 BOOL fCanceled;
565 pTask->m_pProgress->COMGETTER(Canceled)(&fCanceled);
566 if (fCanceled)
567 return -1;
568 pTask->m_pProgress->SetCurrentOperationProgress(uPercent);
569 }
570 return VINF_SUCCESS;
571}
572
573/* static */
574DECLCALLBACK(int) MachineMoveVM::copyFileProgress(unsigned uPercentage, void *pvUser)
575{
576 ComObjPtr<Progress> pProgress = *static_cast< ComObjPtr<Progress>* >(pvUser);
577
578 BOOL fCanceled = false;
579 HRESULT rc = pProgress->COMGETTER(Canceled)(&fCanceled);
580 if (FAILED(rc)) return VERR_GENERAL_FAILURE;
581 /* If canceled by the user tell it to the copy operation. */
582 if (fCanceled) return VERR_CANCELLED;
583 /* Set the new process. */
584 rc = pProgress->SetCurrentOperationProgress(uPercentage);
585 if (FAILED(rc)) return VERR_GENERAL_FAILURE;
586
587 return VINF_SUCCESS;
588}
589
590
591/* static */
592void MachineMoveVM::i_MoveVMThreadTask(MachineMoveVM* task)
593{
594 LogFlowFuncEnter();
595 HRESULT rc = S_OK;
596
597 MachineMoveVM* taskMoveVM = task;
598 ComObjPtr<Machine> &machine = taskMoveVM->m_pMachine;
599
600 AutoCaller autoCaller(machine);
601// if (FAILED(autoCaller.rc())) return;//Should we return something here?
602
603 Utf8Str strTargetFolder = taskMoveVM->m_targetPath;
604 {
605 Bstr bstrMachineName;
606 machine->COMGETTER(Name)(bstrMachineName.asOutParam());
607 strTargetFolder.append(Utf8Str(bstrMachineName));
608 }
609
610 RTCList<Utf8Str> newFiles; /* All extra created files (save states, ...) */
611 RTCList<Utf8Str> originalFiles; /* All original files except images */
612 typedef std::map<Utf8Str, ComObjPtr<Medium> > MediumMap;
613 MediumMap mapOriginalMedium;
614
615 /*
616 * We have the couple modes which user is able to request
617 * basic mode:
618 * - The images which are solely attached to the VM
619 * and located in the original VM folder will be moved.
620 * All subfolders related to the original VM are also moved from the original location
621 * (Standard - snapshots and logs folders).
622 *
623 * canonical mode:
624 * - All disks tied with the VM will be moved into a new location if it's possible.
625 * All folders related to the original VM are also moved.
626 * This mode is intended to collect all files/images/snapshots related to the VM in the one place.
627 *
628 */
629
630 /*
631 * A way to handle shareable disk:
632 * Collect the shareable disks attched to the VM.
633 * Get the machines whom the shareable disks attach to.
634 * Return an error if the state of any VM doesn't allow to move a shareable disk and
635 * this disk is located in the VM's folder (it means the disk is intended for "moving").
636 */
637
638
639 /*
640 * Check new destination whether enough room for the VM or not. if "not" return an error.
641 * Make a copy of VM settings and a list with all files which are moved. Save the list on the disk.
642 * Start "move" operation.
643 * Check the result of operation.
644 * if the operation was successful:
645 * - delete all files in the original VM folder;
646 * - update VM disks info with new location;
647 * - update all other VM if it's needed;
648 * - update global settings
649 */
650
651 try
652 {
653 /* Move all disks */
654 rc = taskMoveVM->moveAllDisks(taskMoveVM->finalMediumsMap, &strTargetFolder);
655 if (FAILED(rc))
656 throw rc;
657
658 /* Get Machine::Data here because moveAllDisks() change it */
659 Machine::Data *machineData = machine->mData.data();
660 settings::MachineConfigFile *machineConfFile = machineData->pMachineConfigFile;
661
662 /* Copy all save state files. */
663 Utf8Str strTrgSnapshotFolder;
664 {
665 /* When the current snapshot folder is absolute we reset it to the
666 * default relative folder. */
667 if (RTPathStartsWithRoot((*machineConfFile).machineUserData.strSnapshotFolder.c_str()))
668 (*machineConfFile).machineUserData.strSnapshotFolder = "Snapshots";
669 (*machineConfFile).strStateFile = "";
670
671 /* The absolute name of the snapshot folder. */
672 strTrgSnapshotFolder = Utf8StrFmt("%s%c%s", strTargetFolder.c_str(), RTPATH_DELIMITER,
673 (*machineConfFile).machineUserData.strSnapshotFolder.c_str());
674
675 /* Check if a snapshot folder is necessary and if so doesn't already
676 * exists. */
677 if ( taskMoveVM->finalSaveStateFilesMap.size() != 0
678 && !RTDirExists(strTrgSnapshotFolder.c_str()))
679 {
680 int vrc = RTDirCreateFullPath(strTrgSnapshotFolder.c_str(), 0700);
681 if (RT_FAILURE(vrc))
682 {
683 Utf8StrFmt errorDesc("Could not create snapshots folder '%s' (%Rrc)", strTrgSnapshotFolder.c_str(), vrc);
684 taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
685
686 throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, machine->tr(errorDesc.c_str()));
687 }
688 }
689
690 std::map<Utf8Str, SAVESTATETASKMOVE>::iterator itState = taskMoveVM->finalSaveStateFilesMap.begin();
691 while (itState != taskMoveVM->finalSaveStateFilesMap.end())
692 {
693 const SAVESTATETASKMOVE &sst = itState->second;
694 const Utf8Str &strTrgSaveState = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER,
695 RTPathFilename(sst.strSaveStateFile.c_str()));
696
697 /* Move to next sub-operation. */
698 rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(machine->tr("Copy the save state file '%s' ..."),
699 RTPathFilename(sst.strSaveStateFile.c_str())).raw(), sst.uWeight);
700 if (FAILED(rc)) throw rc;
701
702 int vrc = RTFileCopyEx(sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), 0,
703 MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
704 if (RT_FAILURE(vrc))
705 {
706 Utf8StrFmt errorDesc("Could not copy state file '%s' to '%s' (%Rrc)",
707 sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), vrc);
708 taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
709
710 throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, machine->tr(errorDesc.c_str()));
711 }
712
713 /* save new file in case of restoring */
714 newFiles.append(strTrgSaveState);
715 /* save original file for deletion in the end */
716 originalFiles.append(sst.strSaveStateFile);
717 ++itState;
718 }
719 }
720
721 /*
722 * Update state file path
723 * very important step!
724 * Not obvious how to do it correctly.
725 */
726 {
727 LogRelFunc(("Update state file path\n"));
728 rc = taskMoveVM->updatePathsToStateFiles(taskMoveVM->finalSaveStateFilesMap,
729 taskMoveVM->vmFolders[VBox_SettingFolder],
730 strTargetFolder);
731 if (FAILED(rc))
732 throw rc;
733 }
734
735 /*
736 * Moving Machine settings file
737 * The settings file are moved after all disks and snapshots because this file should be updated
738 * with actual information and only then should be moved.
739 */
740 {
741 LogRelFunc(("Copy Machine settings file \n"));
742
743 rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(machine->tr("Copy Machine settings file '%s' ..."),
744 (*machineConfFile).machineUserData.strName.c_str()).raw(), 1);
745 if (FAILED(rc)) throw rc;
746
747 Utf8Str strTargetSettingsFilePath = strTargetFolder;
748
749 /* Check a folder existing and create one if it's not */
750 if (!RTDirExists(strTargetSettingsFilePath.c_str()))
751 {
752 int vrc = RTDirCreateFullPath(strTargetSettingsFilePath.c_str(), 0700);
753 if (RT_FAILURE(vrc))
754 {
755 Utf8StrFmt errorDesc("Could not create a home machine folder '%s' (%Rrc)",
756 strTargetSettingsFilePath.c_str(), vrc);
757 taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
758
759 throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, machine->tr(errorDesc.c_str()));
760 }
761 LogRelFunc(("Created a home machine folder %s\n", strTargetSettingsFilePath.c_str()));
762 }
763
764 /* Create a full path */
765 Bstr bstrMachineName;
766 machine->COMGETTER(Name)(bstrMachineName.asOutParam());
767 strTargetSettingsFilePath.append(RTPATH_DELIMITER).append(Utf8Str(bstrMachineName));
768 strTargetSettingsFilePath.append(".vbox");
769
770 Utf8Str strSettingsFilePath;
771 Bstr bstr_settingsFilePath;
772 machine->COMGETTER(SettingsFilePath)(bstr_settingsFilePath.asOutParam());
773 strSettingsFilePath = bstr_settingsFilePath;
774
775 int vrc = RTFileCopyEx(strSettingsFilePath.c_str(), strTargetSettingsFilePath.c_str(), 0,
776 MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
777 if (RT_FAILURE(vrc))
778 {
779 Utf8StrFmt errorDesc("Could not copy the setting file '%s' to '%s' (%Rrc)",
780 strSettingsFilePath.c_str(), strTargetSettingsFilePath.stripFilename().c_str(), vrc);
781 taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
782
783 throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, machine->tr(errorDesc.c_str()));
784 }
785
786 LogRelFunc(("The setting file %s has been copied into the folder %s\n", strSettingsFilePath.c_str(),
787 strTargetSettingsFilePath.stripFilename().c_str()));
788
789 /* save new file in case of restoring */
790 newFiles.append(strTargetSettingsFilePath);
791 /* save original file for deletion in the end */
792 originalFiles.append(strSettingsFilePath);
793 }
794
795 /* Moving Machine log files */
796 {
797 LogRelFunc(("Copy machine log files \n"));
798
799 if (taskMoveVM->vmFolders[VBox_LogFolder].isNotEmpty())
800 {
801 /* Check an original log folder existence */
802 if (RTDirExists(taskMoveVM->vmFolders[VBox_LogFolder].c_str()))
803 {
804 Utf8Str strTargetLogFolderPath = strTargetFolder;
805 strTargetLogFolderPath.append(RTPATH_DELIMITER).append("Logs");
806
807 /* Check a destination log folder existence and create one if it's not */
808 if (!RTDirExists(strTargetLogFolderPath.c_str()))
809 {
810 int vrc = RTDirCreateFullPath(strTargetLogFolderPath.c_str(), 0700);
811 if (RT_FAILURE(vrc))
812 {
813 Utf8StrFmt errorDesc("Could not create log folder '%s' (%Rrc)",
814 strTargetLogFolderPath.c_str(), vrc);
815 taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
816
817 throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, machine->tr(errorDesc.c_str()));
818 }
819 LogRelFunc(("Created a log machine folder %s\n", strTargetLogFolderPath.c_str()));
820 }
821
822 fileList_t filesList;
823 taskMoveVM->getFilesList(taskMoveVM->vmFolders[VBox_LogFolder], filesList);
824 cit_t it = filesList.m_list.begin();
825 while(it != filesList.m_list.end())
826 {
827 Utf8Str strFullSourceFilePath = it->first.c_str();
828 strFullSourceFilePath.append(RTPATH_DELIMITER).append(it->second.c_str());
829
830 Utf8Str strFullTargetFilePath = strTargetLogFolderPath;
831 strFullTargetFilePath.append(RTPATH_DELIMITER).append(it->second.c_str());
832
833 /* Move to next sub-operation. */
834 rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(machine->tr("Copying the log file '%s' ..."),
835 RTPathFilename(strFullSourceFilePath.c_str())).raw(),
836 1);
837 if (FAILED(rc)) throw rc;
838
839 int vrc = RTFileCopyEx(strFullSourceFilePath.c_str(), strFullTargetFilePath.c_str(), 0,
840 MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
841 if (RT_FAILURE(vrc))
842 {
843 Utf8StrFmt errorDesc("Could not copy the log file '%s' to '%s' (%Rrc)",
844 strFullSourceFilePath.c_str(),
845 strFullTargetFilePath.stripFilename().c_str(),
846 vrc);
847 taskMoveVM->errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
848
849 throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, machine->tr(errorDesc.c_str()));
850 }
851
852 LogRelFunc(("The log file %s has been copied into the folder %s\n", strFullSourceFilePath.c_str(),
853 strFullTargetFilePath.stripFilename().c_str()));
854
855 /* save new file in case of restoring */
856 newFiles.append(strFullTargetFilePath);
857 /* save original file for deletion in the end */
858 originalFiles.append(strFullSourceFilePath);
859
860 ++it;
861 }
862 }
863 }
864 }
865
866 /* save all VM data */
867 {
868 rc = machine->SaveSettings();
869 }
870
871 {
872 LogRelFunc(("Update path to XML setting file\n"));
873 Utf8Str strTargetSettingsFilePath = strTargetFolder;
874 Bstr bstrMachineName;
875 machine->COMGETTER(Name)(bstrMachineName.asOutParam());
876 strTargetSettingsFilePath.append(RTPATH_DELIMITER).append(Utf8Str(bstrMachineName)).append(".vbox");
877 machineData->m_strConfigFileFull = strTargetSettingsFilePath;
878 machine->mParent->i_copyPathRelativeToConfig(strTargetSettingsFilePath, machineData->m_strConfigFile);
879 }
880
881 /* Save global settings in the VirtualBox.xml */
882 {
883 /* Marks the global registry for uuid as modified */
884 Guid uuid = machine->mData->mUuid;
885 machine->mParent->i_markRegistryModified(uuid);
886
887 /* for saving the global settings we should hold only the VirtualBox lock */
888 AutoWriteLock vboxLock(machine->mParent COMMA_LOCKVAL_SRC_POS);
889
890 rc = machine->mParent->i_saveSettings();
891 }
892 }
893 catch(HRESULT hrc)
894 {
895 LogRelFunc(("Moving machine to a new destination was failed. Check original and destination places.\n"));
896 rc = hrc;
897 taskMoveVM->result = rc;
898 }
899 catch (...)
900 {
901 LogRelFunc(("Moving machine to a new destination was failed. Check original and destination places.\n"));
902 rc = VirtualBoxBase::handleUnexpectedExceptions(machine, RT_SRC_POS);
903 taskMoveVM->result = rc;
904 }
905
906 /* Cleanup on failure */
907 if (FAILED(rc))
908 {
909 Machine::Data *machineData = machine->mData.data();
910
911 /* ! Apparently we should update the Progress object !*/
912 ULONG operationCount = 0;
913 rc = taskMoveVM->m_pProgress->COMGETTER(OperationCount)(&operationCount);
914 ULONG operation = 0;
915 rc = taskMoveVM->m_pProgress->COMGETTER(Operation)(&operation);
916 Bstr bstrOperationDescription;
917 rc = taskMoveVM->m_pProgress->COMGETTER(OperationDescription)(bstrOperationDescription.asOutParam());
918 Utf8Str strOperationDescription = bstrOperationDescription;
919 ULONG operationPercent = 0;
920 rc = taskMoveVM->m_pProgress->COMGETTER(OperationPercent)(&operationPercent);
921
922 Bstr bstrMachineName;
923 machine->COMGETTER(Name)(bstrMachineName.asOutParam());
924 LogRelFunc(("Moving machine %s was failed on operation %s\n",
925 Utf8Str(bstrMachineName.raw()).c_str(), Utf8Str(bstrOperationDescription.raw()).c_str()));
926
927 /* Restoring the original mediums */
928 try
929 {
930 /*
931 * Fix the progress counter
932 * In instance, the whole "move vm" operation is failed on 5th step. But total count is 20.
933 * Where 20 = 2 * 10 operations, where 10 is the real number of operations. And this value was doubled
934 * earlier in the init() exactly for one reason - rollback operation. Because in this case we must do
935 * the same operations but in backward direction.
936 * Thus now we want to correct the progress counter from 5 to 15. Why?
937 * Because we should have evaluated the counter as "20/2 + (20/2 - 5)" = 15 or just "20 - 5" = 15
938 * And because the 5th step failed it shouldn't be counted.
939 * As result, we need to rollback 4 operations.
940 * Thus we start from "operation + 1" and finish when "i < operationCount - operation".
941 */
942 for (ULONG i = operation + 1; i < operationCount - operation; ++i)
943 {
944 rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt("Skip the empty operation %d...", i + 1).raw(), 1);
945 if (FAILED(rc)) throw rc;
946 }
947
948 rc = taskMoveVM->moveAllDisks(taskMoveVM->finalMediumsMap);
949 if (FAILED(rc))
950 throw rc;
951 }
952 catch(HRESULT hrc)
953 {
954 LogRelFunc(("Rollback scenario: restoration the original mediums were failed. Machine can be corrupted.\n"));
955 taskMoveVM->result = hrc;
956 }
957 catch (...)
958 {
959 LogRelFunc(("Rollback scenario: restoration the original mediums were failed. Machine can be corrupted.\n"));
960 rc = VirtualBoxBase::handleUnexpectedExceptions(machine, RT_SRC_POS);
961 taskMoveVM->result = rc;
962 }
963
964 /* Revert original paths to the state files */
965 rc = taskMoveVM->updatePathsToStateFiles(taskMoveVM->finalSaveStateFilesMap,
966 strTargetFolder,
967 taskMoveVM->vmFolders[VBox_SettingFolder]);
968 if (FAILED(rc))
969 {
970 LogRelFunc(("Rollback scenario: can't restore the original paths to the state files. "
971 "Machine settings %s can be corrupted.\n", machineData->m_strConfigFileFull.c_str()));
972 }
973
974 /* Delete all created files. Here we update progress object */
975 rc = taskMoveVM->deleteFiles(newFiles);
976 if (FAILED(rc))
977 LogRelFunc(("Rollback scenario: can't delete new created files. Check the destination folder."));
978
979 /* Delete destination folder */
980 RTDirRemove(strTargetFolder.c_str());
981
982 /* save all VM data */
983 {
984 AutoWriteLock srcLock(machine COMMA_LOCKVAL_SRC_POS);
985 srcLock.release();
986 rc = machine->SaveSettings();
987 srcLock.acquire();
988 }
989
990 /* Restore an original path to XML setting file */
991 {
992 LogRelFunc(("Rollback scenario: restoration of the original path to XML setting file\n"));
993 Utf8Str strOriginalSettingsFilePath = taskMoveVM->vmFolders[VBox_SettingFolder];
994 strOriginalSettingsFilePath.append(RTPATH_DELIMITER).append(Utf8Str(bstrMachineName)).append(".vbox");
995 machineData->m_strConfigFileFull = strOriginalSettingsFilePath;
996 machine->mParent->i_copyPathRelativeToConfig(strOriginalSettingsFilePath, machineData->m_strConfigFile);
997 }
998
999 /* Marks the global registry for uuid as modified */
1000 {
1001 AutoWriteLock srcLock(machine COMMA_LOCKVAL_SRC_POS);
1002 srcLock.release();
1003 Guid uuid = machine->mData->mUuid;
1004 machine->mParent->i_markRegistryModified(uuid);
1005 srcLock.acquire();
1006 }
1007
1008 /* save the global settings; for that we should hold only the VirtualBox lock */
1009 {
1010 AutoWriteLock vboxLock(machine->mParent COMMA_LOCKVAL_SRC_POS);
1011 rc = machine->mParent->i_saveSettings();
1012 }
1013
1014 /* In case of failure the progress object on the other side (user side) get notification about operation
1015 completion but the operation percentage may not be set to 100% */
1016 }
1017 else /*Operation was successful and now we can delete the original files like the state files, XML setting, log files */
1018 {
1019 /*
1020 * In case of success it's not urgent to update the progress object because we call i_notifyComplete() with
1021 * the success result. As result, the last number of progress operation can be not equal the number of operations
1022 * because we doubled the number of operations for rollback case.
1023 * But if we want to update the progress object corectly it's needed to add all medium moved by standard
1024 * "move medium" logic (for us it's taskMoveVM->finalMediumsMap) to the current number of operation.
1025 */
1026
1027 ULONG operationCount = 0;
1028 rc = taskMoveVM->m_pProgress->COMGETTER(OperationCount)(&operationCount);
1029 ULONG operation = 0;
1030 rc = taskMoveVM->m_pProgress->COMGETTER(Operation)(&operation);
1031
1032 for (ULONG i = operation; i < operation + taskMoveVM->finalMediumsMap.size() - 1; ++i)
1033 {
1034 rc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt("Skip the empty operation %d...", i).raw(), 1);
1035 if (FAILED(rc)) throw rc; /** @todo r=bird: Who exactly should be catching this? Our caller is
1036 * ThreadTask::taskHandlerThreadProc and it doesn't catch anything, nor does the
1037 * IPRT. */
1038 }
1039
1040 rc = taskMoveVM->deleteFiles(originalFiles);
1041 if (FAILED(rc))
1042 LogRelFunc(("Forward scenario: can't delete all original files.\n"));
1043 }
1044
1045 if (!taskMoveVM->m_pProgress.isNull())
1046 {
1047 /* Set the first error happened */
1048 if (!taskMoveVM->errorsList.empty())
1049 {
1050 ErrorInfoItem ei = taskMoveVM->errorsList.front();
1051 machine->setError(ei.m_code, machine->tr(ei.m_description.c_str()));
1052 }
1053
1054 taskMoveVM->m_pProgress->i_notifyComplete(taskMoveVM->result);
1055 }
1056
1057 LogFlowFuncLeave();
1058}
1059
1060HRESULT MachineMoveVM::moveAllDisks(const std::map<Utf8Str, MEDIUMTASKMOVE>& listOfDisks,
1061 const Utf8Str* strTargetFolder)
1062{
1063 HRESULT rc = S_OK;
1064 ComObjPtr<Machine> &machine = m_pMachine;
1065 Utf8Str strLocation;
1066
1067 AutoWriteLock machineLock(machine COMMA_LOCKVAL_SRC_POS);
1068
1069 try{
1070 std::map<Utf8Str, MEDIUMTASKMOVE>::const_iterator itMedium = listOfDisks.begin();
1071 while(itMedium != listOfDisks.end())
1072 {
1073 const MEDIUMTASKMOVE &mt = itMedium->second;
1074 ComPtr<IMedium> pMedium = mt.pMedium;
1075 Utf8Str strTargetImageName;
1076 Bstr bstrLocation;
1077 Bstr bstrSrcName;
1078
1079 rc = pMedium->COMGETTER(Name)(bstrSrcName.asOutParam());
1080 if (FAILED(rc)) throw rc;
1081
1082 if (strTargetFolder != NULL && !strTargetFolder->isEmpty())
1083 {
1084 strTargetImageName = *strTargetFolder;
1085 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
1086 if (FAILED(rc)) throw rc;
1087 strLocation = bstrLocation;
1088
1089 if (mt.fSnapshot == true)
1090 {
1091 strLocation.stripFilename().stripPath().append(RTPATH_DELIMITER).append(Utf8Str(bstrSrcName));
1092 }
1093 else
1094 {
1095 strLocation.stripPath();
1096 }
1097
1098 strTargetImageName.append(RTPATH_DELIMITER).append(strLocation);
1099 rc = m_pProgress->SetNextOperation(BstrFmt(machine->tr("Moving medium '%ls' ..."),
1100 bstrSrcName.raw()).raw(),
1101 mt.uWeight);
1102 if (FAILED(rc)) throw rc;
1103 }
1104 else
1105 {
1106 strTargetImageName = mt.strBaseName;//Should contain full path to the image
1107 rc = m_pProgress->SetNextOperation(BstrFmt(machine->tr("Moving medium '%ls' back..."),
1108 bstrSrcName.raw()).raw(),
1109 mt.uWeight);
1110 if (FAILED(rc)) throw rc;
1111 }
1112
1113
1114
1115 /* consistency: use \ if appropriate on the platform */
1116 RTPathChangeToDosSlashes(strTargetImageName.mutableRaw(), false);
1117
1118 bstrLocation = strTargetImageName.c_str();
1119
1120 MediumType_T mediumType;//immutable, shared, passthrough
1121 rc = pMedium->COMGETTER(Type)(&mediumType);
1122 if (FAILED(rc)) throw rc;
1123
1124 DeviceType_T deviceType;//floppy, hard, DVD
1125 rc = pMedium->COMGETTER(DeviceType)(&deviceType);
1126 if (FAILED(rc)) throw rc;
1127
1128 /* Drop lock early because IMedium::MoveTo needs to get the VirtualBox one. */
1129 machineLock.release();
1130
1131 ComPtr<IProgress> moveDiskProgress;
1132 rc = pMedium->MoveTo(bstrLocation.raw(), moveDiskProgress.asOutParam());
1133 if (SUCCEEDED(rc))
1134 {
1135 /* In case of failure moveDiskProgress would be in the invalid state or not initialized at all
1136 * Call i_waitForOtherProgressCompletion only in success
1137 */
1138 /* Wait until the other process has finished. */
1139 rc = m_pProgress->WaitForOtherProgressCompletion(moveDiskProgress, 0 /* indefinite wait */);
1140 }
1141
1142 /*acquire the lock back*/
1143 machineLock.acquire();
1144
1145 /** @todo r=klaus get rid of custom error handling logic everywhere in this file */
1146 if (FAILED(rc))
1147 {
1148 com::ErrorInfoKeeper eik;
1149 Utf8Str errorDesc(eik.getText().raw());
1150 errorsList.push_back(ErrorInfoItem(eik.getResultCode(), errorDesc.c_str()));
1151 }
1152
1153 if (FAILED(rc)) throw rc;
1154
1155 LogRelFunc(("Moving %s has been finished\n", strTargetImageName.c_str()));
1156
1157 ++itMedium;
1158 }
1159
1160 machineLock.release();
1161 }
1162 catch(HRESULT hrc)
1163 {
1164 LogRelFunc(("\nException during moving the disk %s\n", strLocation.c_str()));
1165 rc = hrc;
1166 machineLock.release();
1167 }
1168 catch (...)
1169 {
1170 LogRelFunc(("\nException during moving the disk %s\n", strLocation.c_str()));
1171 rc = VirtualBoxBase::handleUnexpectedExceptions(m_pMachine, RT_SRC_POS);
1172 machineLock.release();
1173 }
1174
1175 if (FAILED(rc))
1176 {
1177 Utf8StrFmt errorDesc("Exception during moving the disk %s\n", strLocation.c_str());
1178 errorsList.push_back(ErrorInfoItem(rc, errorDesc.c_str()));
1179 }
1180
1181 return rc;
1182}
1183
1184HRESULT MachineMoveVM::updatePathsToStateFiles(const std::map<Utf8Str, SAVESTATETASKMOVE>& listOfFiles,
1185 const Utf8Str& sourcePath, const Utf8Str& targetPath)
1186{
1187 HRESULT rc = S_OK;
1188
1189 std::map<Utf8Str, SAVESTATETASKMOVE>::const_iterator itState = listOfFiles.begin();
1190 while (itState != listOfFiles.end())
1191 {
1192 const SAVESTATETASKMOVE &sst = itState->second;
1193
1194 if (sst.snapshotUuid != Guid::Empty)
1195 {
1196 Utf8Str strGuidMachine = sst.snapshotUuid.toString();
1197 ComObjPtr<Snapshot> snapshotMachineObj;
1198
1199 rc = m_pMachine->i_findSnapshotById(sst.snapshotUuid, snapshotMachineObj, true);
1200 if (SUCCEEDED(rc) && !snapshotMachineObj.isNull())
1201 {
1202 snapshotMachineObj->i_updateSavedStatePaths(sourcePath.c_str(),
1203 targetPath.c_str());
1204 }
1205 }
1206 else
1207 {
1208 const Utf8Str &path = m_pMachine->mSSData->strStateFilePath;
1209 /*
1210 * This check for the case when a new value is equal to the old one.
1211 * Maybe the more clever check is needed in the some corner cases.
1212 */
1213 if (!path.contains(targetPath))
1214 {
1215 m_pMachine->mSSData->strStateFilePath = Utf8StrFmt("%s%s",
1216 targetPath.c_str(),
1217 path.c_str() + sourcePath.length());
1218 }
1219 }
1220
1221 ++itState;
1222 }
1223
1224 return rc;
1225}
1226
1227HRESULT MachineMoveVM::getFilesList(const Utf8Str& strRootFolder, fileList_t &filesList)
1228{
1229 RTDIR hDir;
1230 HRESULT rc = S_OK;
1231 int vrc = RTDirOpen(&hDir, strRootFolder.c_str());
1232 if (RT_SUCCESS(vrc))
1233 {
1234 RTDIRENTRY DirEntry;
1235 while (RT_SUCCESS(RTDirRead(hDir, &DirEntry, NULL)))
1236 {
1237 if (RTDirEntryIsStdDotLink(&DirEntry))
1238 continue;
1239
1240 if (DirEntry.enmType == RTDIRENTRYTYPE_FILE)
1241 {
1242 Utf8Str fullPath(strRootFolder);
1243 filesList.add(strRootFolder, DirEntry.szName);
1244 }
1245 else if (DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY)
1246 {
1247 Utf8Str strNextFolder(strRootFolder);
1248 strNextFolder.append(RTPATH_DELIMITER).append(DirEntry.szName);
1249 rc = getFilesList(strNextFolder, filesList);
1250 if (FAILED(rc))
1251 break;
1252 }
1253 }
1254
1255 vrc = RTDirClose(hDir);
1256 AssertRC(vrc);
1257 }
1258 else if (vrc == VERR_FILE_NOT_FOUND)
1259 {
1260 Utf8StrFmt errorDesc("Folder '%s' doesn't exist (%Rrc)", strRootFolder.c_str(), vrc);
1261 errorsList.push_back(ErrorInfoItem(VERR_FILE_NOT_FOUND, errorDesc.c_str()));
1262
1263 rc = m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, m_pMachine->tr(errorDesc.c_str()));
1264 }
1265 else
1266 {
1267 Utf8StrFmt errorDesc("Could not open folder '%s' (%Rrc)", strRootFolder.c_str(), vrc);
1268 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
1269
1270/** @todo r=bird: document this throwing business, please! Error handling is
1271 * a bit fishy here. See also */
1272 throw m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, m_pMachine->tr(errorDesc.c_str()));
1273 }
1274
1275 return rc;
1276}
1277
1278HRESULT MachineMoveVM::deleteFiles(const RTCList<Utf8Str>& listOfFiles)
1279{
1280 HRESULT rc = S_OK;
1281 /* Delete all created files. */
1282 try
1283 {
1284 for (size_t i = 0; i < listOfFiles.size(); ++i)
1285 {
1286 LogRelFunc(("Deleting file %s ...\n", listOfFiles.at(i).c_str()));
1287 rc = m_pProgress->SetNextOperation(BstrFmt("Deleting file %s...", listOfFiles.at(i).c_str()).raw(), 1);
1288 if (FAILED(rc)) throw rc;
1289
1290 int vrc = RTFileDelete(listOfFiles.at(i).c_str());
1291 if (RT_FAILURE(vrc))
1292 {
1293 Utf8StrFmt errorDesc("Could not delete file '%s' (%Rrc)", listOfFiles.at(i).c_str(), rc);
1294 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
1295
1296 rc = m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, m_pMachine->tr(errorDesc.c_str()));
1297 }
1298 else
1299 LogRelFunc(("File %s has been deleted\n", listOfFiles.at(i).c_str()));
1300 }
1301 }
1302 catch(HRESULT hrc)
1303 {
1304 rc = hrc;
1305 }
1306 catch (...)
1307 {
1308 rc = VirtualBoxBase::handleUnexpectedExceptions(m_pMachine, RT_SRC_POS);
1309 }
1310
1311 return rc;
1312}
1313
1314HRESULT MachineMoveVM::getFolderSize(const Utf8Str& strRootFolder, uint64_t& size)
1315{
1316 HRESULT rc = S_OK;
1317 int vrc = 0;
1318 uint64_t totalFolderSize = 0;
1319 fileList_t filesList;
1320
1321 bool ex = RTPathExists(strRootFolder.c_str());
1322 if (ex == true)
1323 {
1324 rc = getFilesList(strRootFolder, filesList);
1325 if (SUCCEEDED(rc))
1326 {
1327 cit_t it = filesList.m_list.begin();
1328 while(it != filesList.m_list.end())
1329 {
1330 uint64_t cbFile = 0;
1331 Utf8Str fullPath = it->first;
1332 fullPath.append(RTPATH_DELIMITER).append(it->second);
1333 vrc = RTFileQuerySize(fullPath.c_str(), &cbFile);
1334 if (RT_SUCCESS(vrc))
1335 {
1336 totalFolderSize += cbFile;
1337 }
1338 else
1339 {
1340 Utf8StrFmt errorDesc("Could not get the size of file '%s' (%Rrc)", fullPath.c_str(), vrc);
1341 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
1342
1343 throw m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, m_pMachine->tr(errorDesc.c_str()));
1344 }
1345 ++it;
1346 }
1347
1348 size = totalFolderSize;
1349 }
1350 else
1351 {
1352/** @todo r=bird: This only happens if RTDirOpen fails. So, I'm not sure
1353 * what you're going for here... See not about throwing in getFilesList(). */
1354 Utf8StrFmt errorDesc("Could not calculate the size of folder '%s'", strRootFolder.c_str());
1355 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
1356
1357 m_pMachine->setError(VBOX_E_IPRT_ERROR, m_pMachine->tr(errorDesc.c_str()));
1358 }
1359 }
1360 else
1361 size = 0;
1362
1363 return rc;
1364}
1365
1366HRESULT MachineMoveVM::queryBaseName(const ComPtr<IMedium> &pMedium, Utf8Str &strBaseName) const
1367{
1368 ComPtr<IMedium> pBaseMedium;
1369 HRESULT rc = pMedium->COMGETTER(Base)(pBaseMedium.asOutParam());
1370 if (FAILED(rc)) return rc;
1371 Bstr bstrBaseName;
1372 rc = pBaseMedium->COMGETTER(Name)(bstrBaseName.asOutParam());
1373 if (FAILED(rc)) return rc;
1374 strBaseName = bstrBaseName;
1375 return rc;
1376}
1377
1378HRESULT MachineMoveVM::createMachineList(const ComPtr<ISnapshot> &pSnapshot,
1379 std::vector< ComObjPtr<Machine> > &aMachineList) const
1380{
1381 HRESULT rc = S_OK;
1382 Bstr name;
1383 rc = pSnapshot->COMGETTER(Name)(name.asOutParam());
1384 if (FAILED(rc)) return rc;
1385
1386 ComPtr<IMachine> l_pMachine;
1387 rc = pSnapshot->COMGETTER(Machine)(l_pMachine.asOutParam());
1388 if (FAILED(rc)) return rc;
1389 aMachineList.push_back((Machine*)(IMachine*)l_pMachine);
1390
1391 SafeIfaceArray<ISnapshot> sfaChilds;
1392 rc = pSnapshot->COMGETTER(Children)(ComSafeArrayAsOutParam(sfaChilds));
1393 if (FAILED(rc)) return rc;
1394 for (size_t i = 0; i < sfaChilds.size(); ++i)
1395 {
1396 rc = createMachineList(sfaChilds[i], aMachineList);
1397 if (FAILED(rc)) return rc;
1398 }
1399
1400 return rc;
1401}
1402
1403HRESULT MachineMoveVM::queryMediasForAllStates(const std::vector<ComObjPtr<Machine> > &aMachineList)
1404{
1405 /* In this case we create a exact copy of the original VM. This means just
1406 * adding all directly and indirectly attached disk images to the worker
1407 * list. */
1408 HRESULT rc = S_OK;
1409 for (size_t i = 0; i < aMachineList.size(); ++i)
1410 {
1411 const ComObjPtr<Machine> &machine = aMachineList.at(i);
1412
1413 /* Add all attachments (and their parents) of the different
1414 * machines to a worker list. */
1415 SafeIfaceArray<IMediumAttachment> sfaAttachments;
1416 rc = machine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
1417 if (FAILED(rc)) return rc;
1418 for (size_t a = 0; a < sfaAttachments.size(); ++a)
1419 {
1420 const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[a];
1421 DeviceType_T deviceType;//floppy, hard, DVD
1422 rc = pAtt->COMGETTER(Type)(&deviceType);
1423 if (FAILED(rc)) return rc;
1424
1425 /* Valid medium attached? */
1426 ComPtr<IMedium> pMedium;
1427 rc = pAtt->COMGETTER(Medium)(pMedium.asOutParam());
1428 if (FAILED(rc)) return rc;
1429
1430 if (pMedium.isNull())
1431 continue;
1432
1433 Bstr bstrLocation;
1434 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
1435 if (FAILED(rc)) throw rc;
1436
1437 /* Cast to ComObjPtr<Medium> */
1438 ComObjPtr<Medium> pObjMedium = (Medium *)(IMedium *)pMedium;
1439
1440 /*Check for "read-only" medium in terms that VBox can't create this one */
1441 bool fPass = isMediumTypeSupportedForMoving(pMedium);
1442 if(!fPass)
1443 {
1444 LogRelFunc(("Skipping file %s because of this medium type hasn't been supported for moving.\n",
1445 Utf8Str(bstrLocation.raw()).c_str()));
1446 continue;
1447 }
1448
1449 MEDIUMTASKCHAINMOVE mtc;
1450 mtc.devType = deviceType;
1451 mtc.fAttachLinked = false;
1452 mtc.fCreateDiffs = false;
1453
1454 while (!pMedium.isNull())
1455 {
1456 /* Refresh the state so that the file size get read. */
1457 MediumState_T e;
1458 rc = pMedium->RefreshState(&e);
1459 if (FAILED(rc)) return rc;
1460
1461 LONG64 lSize;
1462 rc = pMedium->COMGETTER(Size)(&lSize);
1463 if (FAILED(rc)) return rc;
1464
1465 MediumType_T mediumType;//immutable, shared, passthrough
1466 rc = pMedium->COMGETTER(Type)(&mediumType);
1467 if (FAILED(rc)) throw rc;
1468
1469 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
1470 if (FAILED(rc)) throw rc;
1471
1472 MEDIUMTASKMOVE mt;// = {false, "basename", NULL, 0, 0};
1473 mt.strBaseName = bstrLocation;
1474 Utf8Str strFolder = vmFolders[VBox_SnapshotFolder];
1475 if (strFolder.isNotEmpty() && mt.strBaseName.contains(strFolder))
1476 {
1477 mt.fSnapshot = true;
1478 }
1479 else
1480 mt.fSnapshot = false;
1481
1482 mt.uIdx = UINT32_MAX;
1483 mt.pMedium = pMedium;
1484 mt.uWeight = (ULONG)((lSize + _1M - 1) / _1M);
1485 mtc.chain.append(mt);
1486
1487 /* Query next parent. */
1488 rc = pMedium->COMGETTER(Parent)(pMedium.asOutParam());
1489 if (FAILED(rc)) return rc;
1490 }
1491
1492 llMedias.append(mtc);
1493 }
1494 /* Add the save state files of this machine if there is one. */
1495 rc = addSaveState(machine);
1496 if (FAILED(rc)) return rc;
1497
1498 }
1499 /* Build up the index list of the image chain. Unfortunately we can't do
1500 * that in the previous loop, cause there we go from child -> parent and
1501 * didn't know how many are between. */
1502 for (size_t i = 0; i < llMedias.size(); ++i)
1503 {
1504 uint32_t uIdx = 0;
1505 MEDIUMTASKCHAINMOVE &mtc = llMedias.at(i);
1506 for (size_t a = mtc.chain.size(); a > 0; --a)
1507 mtc.chain[a - 1].uIdx = uIdx++;
1508 }
1509
1510 return rc;
1511}
1512
1513HRESULT MachineMoveVM::addSaveState(const ComObjPtr<Machine> &machine)
1514{
1515 Bstr bstrSrcSaveStatePath;
1516 HRESULT rc = machine->COMGETTER(StateFilePath)(bstrSrcSaveStatePath.asOutParam());
1517 if (FAILED(rc)) return rc;
1518 if (!bstrSrcSaveStatePath.isEmpty())
1519 {
1520 SAVESTATETASKMOVE sst;
1521
1522 sst.snapshotUuid = machine->i_getSnapshotId();
1523 sst.strSaveStateFile = bstrSrcSaveStatePath;
1524 uint64_t cbSize;
1525
1526 int vrc = RTFileQuerySize(sst.strSaveStateFile.c_str(), &cbSize);
1527 if (RT_FAILURE(vrc))
1528 {
1529 Utf8StrFmt errorDesc("Could not get file size of '%s' (%Rrc)", sst.strSaveStateFile.c_str(), vrc);
1530 errorsList.push_back(ErrorInfoItem(VBOX_E_IPRT_ERROR, errorDesc.c_str()));
1531
1532 return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, m_pMachine->tr(errorDesc.c_str()));
1533 }
1534 /* same rule as above: count both the data which needs to
1535 * be read and written */
1536 sst.uWeight = (ULONG)(2 * (cbSize + _1M - 1) / _1M);
1537 llSaveStateFiles.append(sst);
1538 }
1539 return S_OK;
1540}
1541
1542void MachineMoveVM::updateProgressStats(MEDIUMTASKCHAINMOVE &mtc, ULONG &uCount, ULONG &uTotalWeight) const
1543{
1544
1545 /* Currently the copying of diff images involves reading at least
1546 * the biggest parent in the previous chain. So even if the new
1547 * diff image is small in size, it could need some time to create
1548 * it. Adding the biggest size in the chain should balance this a
1549 * little bit more, i.e. the weight is the sum of the data which
1550 * needs to be read and written. */
1551 ULONG uMaxWeight = 0;
1552 for (size_t e = mtc.chain.size(); e > 0; --e)
1553 {
1554 MEDIUMTASKMOVE &mt = mtc.chain.at(e - 1);
1555 mt.uWeight += uMaxWeight;
1556
1557 /* Calculate progress data */
1558 ++uCount;
1559 uTotalWeight += mt.uWeight;
1560
1561 /* Save the max size for better weighting of diff image
1562 * creation. */
1563 uMaxWeight = RT_MAX(uMaxWeight, mt.uWeight);
1564 }
1565}
1566
1567bool MachineMoveVM::isMediumTypeSupportedForMoving(const ComPtr<IMedium> &pMedium)
1568{
1569 HRESULT rc = S_OK;
1570 bool fSupported = true;
1571 Bstr bstrLocation;
1572 rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
1573 if (FAILED(rc))
1574 {
1575 fSupported = false;
1576 throw rc;
1577 }
1578
1579 DeviceType_T deviceType;
1580 rc = pMedium->COMGETTER(DeviceType)(&deviceType);
1581 if (FAILED(rc))
1582 {
1583 fSupported = false;
1584 throw rc;
1585 }
1586
1587 ComPtr<IMediumFormat> mediumFormat;
1588 rc = pMedium->COMGETTER(MediumFormat)(mediumFormat.asOutParam());
1589 if (FAILED(rc))
1590 {
1591 fSupported = false;
1592 throw rc;
1593 }
1594
1595 /*Check whether VBox is able to create this medium format or not, i.e. medium can be "read-only" */
1596 Bstr bstrFormatName;
1597 rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
1598 if (FAILED(rc))
1599 {
1600 fSupported = false;
1601 throw rc;
1602 }
1603
1604 Utf8Str formatName = Utf8Str(bstrFormatName);
1605 if (formatName.compare("VHDX", Utf8Str::CaseInsensitive) == 0)
1606 {
1607 LogRelFunc(("Skipping medium %s. VHDX format is supported in \"read-only\" mode only. \n",
1608 Utf8Str(bstrLocation.raw()).c_str()));
1609 fSupported = false;
1610 }
1611
1612 /* Check whether medium is represented by file on the disk or not */
1613 if (fSupported)
1614 {
1615 ComObjPtr<Medium> pObjMedium = (Medium *)(IMedium *)pMedium;
1616 fSupported = pObjMedium->i_isMediumFormatFile();
1617 if (!fSupported)
1618 {
1619 LogRelFunc(("Skipping medium %s because it's not a real file on the disk.\n",
1620 Utf8Str(bstrLocation.raw()).c_str()));
1621 }
1622 }
1623
1624 /* some special checks for DVD */
1625 if (fSupported && deviceType == DeviceType_DVD)
1626 {
1627 Utf8Str ext = bstrLocation;
1628
1629 //only ISO image is moved. Otherwise adding some information into log file
1630 bool fIso = ext.endsWith(".iso", Utf8Str::CaseInsensitive);
1631 if (fIso == false)
1632 {
1633 LogRelFunc(("Skipping file %s. Only ISO images are supported for now.\n",
1634 Utf8Str(bstrLocation.raw()).c_str()));
1635 fSupported = false;
1636 }
1637 }
1638
1639 return fSupported;
1640}
Note: See TracBrowser for help on using the repository browser.

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