VirtualBox

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

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

MachineImplMoveVM: improve directory cleanup - now there is a chance (for older VMs not very high since often there are VM config files when the settings version was bumped) that the old directory will be truly gone after the operation

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