VirtualBox

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

Last change on this file since 73060 was 73003, checked in by vboxsync, 7 years ago

Main: Use setErrorBoth when we've got a VBox status code handy. (The COM status codes aren't too specfic and this may help us decode error messages and provide an alternative to strstr for API clients. setErrorBoth isn't new, btw.)

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