VirtualBox

source: vbox/trunk/src/VBox/Main/HardDiskImpl.cpp@ 3411

Last change on this file since 3411 was 3411, checked in by vboxsync, 17 years ago

Main: Do not delete target hard disk images when the "clone/create VDI" operation fails because the target file already exists.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 117.6 KB
Line 
1/** @file
2 *
3 * VirtualBox COM class implementation
4 */
5
6/*
7 * Copyright (C) 2006-2007 innotek GmbH
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 as published by the Free Software Foundation,
13 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
14 * distribution. VirtualBox OSE is distributed in the hope that it will
15 * be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * If you received this file as part of a commercial VirtualBox
18 * distribution, then only the terms of your commercial VirtualBox
19 * license agreement apply instead of the previous paragraph.
20 */
21
22#include "HardDiskImpl.h"
23#include "ProgressImpl.h"
24#include "VirtualBoxImpl.h"
25#include "SystemPropertiesImpl.h"
26#include "Logging.h"
27
28#include <iprt/string.h>
29#include <iprt/thread.h>
30#include <iprt/file.h>
31#include <iprt/path.h>
32#include <iprt/dir.h>
33#include <iprt/cpputils.h>
34#include <VBox/VBoxHDD.h>
35#include <VBox/err.h>
36
37#include <algorithm>
38
39#define CHECK_BUSY() \
40 do { \
41 if (isBusy()) \
42 return setError (E_UNEXPECTED, \
43 tr ("Hard disk '%ls' is being used by another task"), \
44 toString().raw()); \
45 } while (0)
46
47#define CHECK_BUSY_AND_READERS() \
48do { \
49 if (readers() > 0 || isBusy()) \
50 return setError (E_UNEXPECTED, \
51 tr ("Hard disk '%ls' is being used by another task"), \
52 toString().raw()); \
53} while (0)
54
55/** Task structure for asynchronous VDI operations */
56struct VDITask
57{
58 enum Op { CreateDynamic, CreateStatic, CloneToImage };
59
60 VDITask (Op op, HVirtualDiskImage *i, Progress *p)
61 : operation (op)
62 , vdi (i)
63 , progress (p)
64 {}
65
66 Op operation;
67 ComObjPtr <HVirtualDiskImage> vdi;
68 ComObjPtr <Progress> progress;
69
70 /* for CreateDynamic, CreateStatic */
71 uint64_t size;
72
73 /* for CloneToImage */
74 ComObjPtr <HardDisk> source;
75};
76
77/**
78 * Progress callback handler for VDI operations.
79 *
80 * @param uPercent Completetion precentage (0-100).
81 * @param pvUser Pointer to the Progress instance.
82 */
83static DECLCALLBACK(int) progressCallback (PVM /* pVM */, unsigned uPercent, void *pvUser)
84{
85 Progress *progress = static_cast <Progress *> (pvUser);
86
87 /* update the progress object */
88 if (progress)
89 progress->notifyProgress (uPercent);
90
91 return VINF_SUCCESS;
92}
93
94////////////////////////////////////////////////////////////////////////////////
95// HardDisk class
96////////////////////////////////////////////////////////////////////////////////
97
98// constructor / destructor
99////////////////////////////////////////////////////////////////////////////////
100
101/** Shold be called by subclasses from #FinalConstruct() */
102HRESULT HardDisk::FinalConstruct()
103{
104 mRegistered = FALSE;
105
106 mStorageType = HardDiskStorageType_VirtualDiskImage;
107 mType = HardDiskType_NormalHardDisk;
108
109 mBusy = false;
110 mReaders = 0;
111
112 return S_OK;
113}
114
115/**
116 * Shold be called by subclasses from #FinalRelease().
117 * Uninitializes this object by calling #uninit() if it's not yet done.
118 */
119void HardDisk::FinalRelease()
120{
121 uninit();
122}
123
124// protected initializer/uninitializer for internal purposes only
125////////////////////////////////////////////////////////////////////////////////
126
127/**
128 * Initializes the hard disk object.
129 *
130 * Subclasses should call this or any other #init() method from their
131 * init() implementations.
132 *
133 * @note
134 * This method doesn't do |isReady()| check and doesn't call
135 * |setReady (true)| on success!
136 * @note
137 * This method must be called from under the object's lock!
138 */
139HRESULT HardDisk::protectedInit (VirtualBox *aVirtualBox, HardDisk *aParent)
140{
141 LogFlowThisFunc (("aParent=%p\n", aParent));
142
143 ComAssertRet (aVirtualBox, E_INVALIDARG);
144
145 mVirtualBox = aVirtualBox;
146 mParent = aParent;
147
148 if (!aParent)
149 aVirtualBox->addDependentChild (this);
150 else
151 aParent->addDependentChild (this);
152
153 return S_OK;
154}
155
156/**
157 * Uninitializes the instance.
158 * Subclasses should call this from their uninit() implementations.
159 * The readiness flag must be true on input and will be set to false
160 * on output.
161 *
162 * @param alock this object's autolock
163 *
164 * @note
165 * Using mParent and mVirtualBox members after this method returns
166 * is forbidden.
167 */
168void HardDisk::protectedUninit (AutoLock &alock)
169{
170 LogFlowThisFunc (("\n"));
171
172 Assert (alock.belongsTo (this));
173 Assert (isReady());
174
175 /* uninit all children */
176 uninitDependentChildren();
177
178 setReady (false);
179
180 if (mParent)
181 mParent->removeDependentChild (this);
182 else
183 {
184 alock.leave();
185 mVirtualBox->removeDependentChild (this);
186 alock.enter();
187 }
188
189 mParent.setNull();
190 mVirtualBox.setNull();
191}
192
193// IHardDisk properties
194/////////////////////////////////////////////////////////////////////////////
195
196STDMETHODIMP HardDisk::COMGETTER(Id) (GUIDPARAMOUT aId)
197{
198 if (!aId)
199 return E_POINTER;
200
201 AutoLock alock (this);
202 CHECK_READY();
203
204 mId.cloneTo (aId);
205 return S_OK;
206}
207
208STDMETHODIMP HardDisk::COMGETTER(StorageType) (HardDiskStorageType_T *aStorageType)
209{
210 if (!aStorageType)
211 return E_POINTER;
212
213 AutoLock alock (this);
214 CHECK_READY();
215
216 *aStorageType = mStorageType;
217 return S_OK;
218}
219
220STDMETHODIMP HardDisk::COMGETTER(Location) (BSTR *aLocation)
221{
222 if (!aLocation)
223 return E_POINTER;
224
225 AutoLock alock (this);
226 CHECK_READY();
227
228 toString (false /* aShort */).cloneTo (aLocation);
229 return S_OK;
230}
231
232STDMETHODIMP HardDisk::COMGETTER(Type) (HardDiskType_T *aType)
233{
234 if (!aType)
235 return E_POINTER;
236
237 AutoLock alock (this);
238 CHECK_READY();
239
240 *aType = mType;
241 return S_OK;
242}
243
244STDMETHODIMP HardDisk::COMSETTER(Type) (HardDiskType_T aType)
245{
246 AutoLock alock (this);
247 CHECK_READY();
248
249 if (mRegistered)
250 return setError (E_FAIL,
251 tr ("You cannot change the type of the registered hard disk '%ls'"),
252 toString().raw());
253
254 /* return silently if nothing to do */
255 if (mType == aType)
256 return S_OK;
257
258 if (mStorageType == HardDiskStorageType_VMDKImage)
259 return setError (E_FAIL,
260 tr ("Currently, changing the type of VMDK hard disks is not allowed"));
261
262 if (mStorageType == HardDiskStorageType_ISCSIHardDisk)
263 return setError (E_FAIL,
264 tr ("Currently, changing the type of iSCSI hard disks is not allowed"));
265
266 /// @todo (dmik) later: allow to change the type on any registered hard disk
267 // depending on whether it is attached or not, has children etc.
268 // Don't forget to save hdd configuration afterwards.
269
270 mType = aType;
271 return S_OK;
272}
273
274STDMETHODIMP HardDisk::COMGETTER(Parent) (IHardDisk **aParent)
275{
276 if (!aParent)
277 return E_POINTER;
278
279 AutoLock alock (this);
280 CHECK_READY();
281
282 mParent.queryInterfaceTo (aParent);
283 return S_OK;
284}
285
286STDMETHODIMP HardDisk::COMGETTER(Children) (IHardDiskCollection **aChildren)
287{
288 if (!aChildren)
289 return E_POINTER;
290
291 AutoLock lock(this);
292 CHECK_READY();
293
294 AutoLock chLock (childrenLock());
295
296 ComObjPtr <HardDiskCollection> collection;
297 collection.createObject();
298 collection->init (children());
299 collection.queryInterfaceTo (aChildren);
300 return S_OK;
301}
302
303STDMETHODIMP HardDisk::COMGETTER(Root) (IHardDisk **aRoot)
304{
305 if (!aRoot)
306 return E_POINTER;
307
308 AutoLock lock(this);
309 CHECK_READY();
310
311 root().queryInterfaceTo (aRoot);
312 return S_OK;
313}
314
315STDMETHODIMP HardDisk::COMGETTER(Accessible) (BOOL *aAccessible)
316{
317 if (!aAccessible)
318 return E_POINTER;
319
320 AutoLock alock (this);
321 CHECK_READY();
322
323 HRESULT rc = getAccessible (mLastAccessError);
324 if (FAILED (rc))
325 return rc;
326
327 *aAccessible = mLastAccessError.isNull();
328 return S_OK;
329}
330
331STDMETHODIMP HardDisk::COMGETTER(AllAccessible) (BOOL *aAllAccessible)
332{
333 if (!aAllAccessible)
334 return E_POINTER;
335
336 AutoLock alock (this);
337 CHECK_READY();
338
339 if (mParent)
340 {
341 HRESULT rc = S_OK;
342
343 /* check the accessibility state of all ancestors */
344 ComObjPtr <HardDisk> parent = (HardDisk *) mParent;
345 while (parent)
346 {
347 AutoLock parentLock (parent);
348 HRESULT rc = parent->getAccessible (mLastAccessError);
349 if (FAILED (rc))
350 break;
351 *aAllAccessible = mLastAccessError.isNull();
352 if (!*aAllAccessible)
353 break;
354 parent = parent->mParent;
355 }
356
357 return rc;
358 }
359
360 HRESULT rc = getAccessible (mLastAccessError);
361 if (FAILED (rc))
362 return rc;
363
364 *aAllAccessible = mLastAccessError.isNull();
365 return S_OK;
366}
367
368STDMETHODIMP HardDisk::COMGETTER(LastAccessError) (BSTR *aLastAccessError)
369{
370 if (!aLastAccessError)
371 return E_POINTER;
372
373 AutoLock alock (this);
374 CHECK_READY();
375
376 mLastAccessError.cloneTo (aLastAccessError);
377 return S_OK;
378}
379
380STDMETHODIMP HardDisk::COMGETTER(MachineId) (GUIDPARAMOUT aMachineId)
381{
382 if (!aMachineId)
383 return E_POINTER;
384
385 AutoLock alock (this);
386 CHECK_READY();
387
388 mMachineId.cloneTo (aMachineId);
389 return S_OK;
390}
391
392STDMETHODIMP HardDisk::COMGETTER(SnapshotId) (GUIDPARAMOUT aSnapshotId)
393{
394 if (!aSnapshotId)
395 return E_POINTER;
396
397 AutoLock alock (this);
398 CHECK_READY();
399
400 mSnapshotId.cloneTo (aSnapshotId);
401 return S_OK;
402}
403
404STDMETHODIMP HardDisk::CloneToImage (INPTR BSTR aFilePath,
405 IVirtualDiskImage **aImage,
406 IProgress **aProgress)
407{
408 if (!aFilePath || !(*aFilePath))
409 return E_INVALIDARG;
410 if (!aImage || !aProgress)
411 return E_POINTER;
412
413 AutoLock alock (this);
414 CHECK_READY();
415 CHECK_BUSY();
416
417 if (!mParent.isNull())
418 return setError (E_FAIL,
419 tr ("Cloning differencing VDI images is not yet supported ('%ls')"),
420 toString().raw());
421
422 HRESULT rc = S_OK;
423
424 /* create a project object */
425 ComObjPtr <Progress> progress;
426 progress.createObject();
427 rc = progress->init (mVirtualBox, (IVirtualDiskImage *) this,
428 Bstr (tr ("Creating a hard disk clone")),
429 FALSE /* aCancelable */);
430 CheckComRCReturnRC (rc);
431
432 /* create an imageless resulting object */
433 ComObjPtr <HVirtualDiskImage> image;
434 image.createObject();
435 rc = image->init (mVirtualBox, NULL, NULL);
436 CheckComRCReturnRC (rc);
437
438 /* append the default path if only a name is given */
439 Bstr path = aFilePath;
440 {
441 Utf8Str fp = aFilePath;
442 if (!RTPathHavePath (fp))
443 {
444 AutoReaderLock propsLock (mVirtualBox->systemProperties());
445 path = Utf8StrFmt ("%ls%c%s",
446 mVirtualBox->systemProperties()->defaultVDIFolder().raw(),
447 RTPATH_DELIMITER,
448 fp.raw());
449 }
450 }
451
452 /* set the desired path */
453 rc = image->setFilePath (path);
454 CheckComRCReturnRC (rc);
455
456 /* ensure the directory exists */
457 {
458 Utf8Str imageDir = image->filePath();
459 RTPathStripFilename (imageDir.mutableRaw());
460 if (!RTDirExists (imageDir))
461 {
462 int vrc = RTDirCreateFullPath (imageDir, 0777);
463 if (VBOX_FAILURE (vrc))
464 {
465 return setError (E_FAIL,
466 tr ("Could not create a directory '%s' "
467 "to store the image file (%Vrc)"),
468 imageDir.raw(), vrc);
469 }
470 }
471 }
472
473 /* mark as busy (being created)
474 * (VDI task thread will unmark it) */
475 image->setBusy();
476
477 /* fill in a VDI task data */
478 VDITask *task = new VDITask (VDITask::CloneToImage, image, progress);
479 task->source = this;
480
481 /* increase readers until finished
482 * (VDI task thread will decrease them) */
483 addReader();
484
485 /* create the hard disk creation thread, pass operation data */
486 int vrc = RTThreadCreate (NULL, HVirtualDiskImage::VDITaskThread,
487 (void *) task, 0, RTTHREADTYPE_MAIN_HEAVY_WORKER,
488 0, "VDITask");
489 ComAssertMsgRC (vrc, ("Could not create a thread (%Vrc)", vrc));
490 if (VBOX_FAILURE (vrc))
491 {
492 releaseReader();
493 image->clearBusy();
494 delete task;
495 return E_FAIL;
496 }
497
498 /* return interfaces to the caller */
499 image.queryInterfaceTo (aImage);
500 progress.queryInterfaceTo (aProgress);
501
502 return S_OK;
503}
504
505// public methods for internal purposes only
506/////////////////////////////////////////////////////////////////////////////
507
508/**
509 * Returns the very first (grand-) parent of this hard disk or the hard
510 * disk itself, if it doesn't have a parent.
511 *
512 * @note
513 * Must be called from under the object's lock
514 */
515ComObjPtr <HardDisk> HardDisk::root() const
516{
517 ComObjPtr <HardDisk> root = const_cast <HardDisk *> (this);
518 ComObjPtr <HardDisk> parent;
519 while ((parent = root->parent()))
520 root = parent;
521
522 return root;
523}
524
525/**
526 * Attempts to mark the hard disk as registered.
527 * Must be always called by every reimplementation.
528 * Only VirtualBox can call this method.
529 *
530 * @param aRegistered true to set registered and false to set unregistered
531 */
532HRESULT HardDisk::trySetRegistered (BOOL aRegistered)
533{
534 AutoLock alock (this);
535 CHECK_READY();
536
537 if (aRegistered)
538 {
539 ComAssertRet (mMachineId.isEmpty(), E_FAIL);
540 ComAssertRet (mId && children().size() == 0, E_FAIL);
541
542 if (mRegistered)
543 return setError (E_FAIL,
544 tr ("Hard disk '%ls' is already registered"),
545 toString().raw());
546
547 CHECK_BUSY();
548 }
549 else
550 {
551 if (!mRegistered)
552 return setError (E_FAIL,
553 tr ("Hard disk '%ls' is already unregistered"),
554 toString().raw());
555
556 if (!mMachineId.isEmpty())
557 return setError (E_FAIL,
558 tr ("Hard disk '%ls' is attached to a virtual machine with UUID {%s}"),
559 toString().raw(), mMachineId.toString().raw());
560
561 if (children().size() > 0)
562 return setError (E_FAIL,
563 tr ("Hard disk '%ls' has %d differencing hard disks based on it"),
564 toString().raw(), children().size());
565
566 CHECK_BUSY_AND_READERS();
567 }
568
569 mRegistered = aRegistered;
570 return S_OK;
571}
572
573/**
574 * Checks basic accessibility of this hard disk only (w/o parents).
575 * Must be always called by every HardDisk::getAccessible() reimplementation
576 * in the first place.
577 *
578 * When @a aCheckBusy is true, this method checks that mBusy = false (and
579 * returns an appropriate error if not). This lets reimplementations
580 * successfully call addReader() after getBaseAccessible() succeeds to
581 * reference the disk and protect it from being modified or deleted before
582 * the remaining check steps are done. Note that in this case, the
583 * reimplementation must enter the object lock before calling this method and
584 * must not leave it before calling addReader() to avoid race condition.
585 *
586 * When @a aCheckReaders is true, this method checks that mReaders = 0 (and
587 * returns an appropriate error if not). When set to true together with
588 * @a aCheckBusy, this lets reimplementations successfully call setBusy() after
589 * getBaseAccessible() succeeds to lock the disk and make sure nobody is
590 * referencing it until the remaining check steps are done. Note that in this
591 * case, the reimplementation must enter the object lock before calling this
592 * method and must not leave it before calling setBusy() to avoid race
593 * condition.
594 *
595 * @param aAccessError On output, a null string indicates the hard disk is
596 * accessible, otherwise contains a message describing
597 * the reason of inaccessibility.
598 * @param aCheckBusy Whether to do the busy check or not.
599 * @param aCheckReaders Whether to do readers check or not.
600 */
601HRESULT HardDisk::getBaseAccessible (Bstr &aAccessError,
602 bool aCheckBusy /* = false */,
603 bool aCheckReaders /* = false */)
604{
605 AutoLock alock (this);
606 CHECK_READY();
607
608 aAccessError.setNull();
609
610 if (aCheckBusy)
611 {
612 if (mBusy)
613 {
614 aAccessError = Utf8StrFmt (
615 tr ("Hard disk '%ls' is being exclusively used by another task"),
616 toString().raw());
617 return S_OK;
618 }
619 }
620
621 if (aCheckReaders)
622 {
623 if (mReaders > 0)
624 {
625 aAccessError = Utf8StrFmt (
626 tr ("Hard disk '%ls' is being used by another task (%d readers)"),
627 toString().raw(), mReaders);
628 return S_OK;
629 }
630 }
631
632 return S_OK;
633}
634
635/**
636 * Returns true if the set of properties that makes this object unique
637 * is equal to the same set of properties in the given object.
638 */
639bool HardDisk::sameAs (HardDisk *that)
640{
641 AutoLock alock (this);
642 if (!isReady())
643 return false;
644
645 /// @todo (dmik) besides UUID, we temporarily use toString() to uniquely
646 // identify objects. This is ok for VDIs but may be not good for iSCSI,
647 // so it will need a reimp of this method.
648
649 return that->mId == mId ||
650 toString (false /* aShort */) == that->toString (false /* aShort */);
651}
652
653/**
654 * Marks this hard disk as busy.
655 * A busy hard disk cannot have readers and its properties (UUID, description)
656 * cannot be externally modified.
657 */
658void HardDisk::setBusy()
659{
660 AutoLock alock (this);
661 AssertReturnVoid (isReady());
662
663 AssertMsgReturnVoid (mBusy == false, ("%ls", toString().raw()));
664 AssertMsgReturnVoid (mReaders == 0, ("%ls", toString().raw()));
665
666 mBusy = true;
667}
668
669/**
670 * Clears the busy flag previously set by #setBusy().
671 */
672void HardDisk::clearBusy()
673{
674 AutoLock alock (this);
675 AssertReturnVoid (isReady());
676
677 AssertMsgReturnVoid (mBusy == true, ("%ls", toString().raw()));
678
679 mBusy = false;
680}
681
682/**
683 * Increases the number of readers of this hard disk.
684 * A hard disk that have readers cannot be marked as busy (and vice versa)
685 * and its properties (UUID, description) cannot be externally modified.
686 */
687void HardDisk::addReader()
688{
689 AutoLock alock (this);
690 AssertReturnVoid (isReady());
691
692 AssertMsgReturnVoid (mBusy == false, ("%ls", toString().raw()));
693
694 ++ mReaders;
695}
696
697/**
698 * Decreases the number of readers of this hard disk.
699 */
700void HardDisk::releaseReader()
701{
702 AutoLock alock (this);
703 AssertReturnVoid (isReady());
704
705 AssertMsgReturnVoid (mBusy == false, ("%ls", toString().raw()));
706 AssertMsgReturnVoid (mReaders > 0, ("%ls", toString().raw()));
707
708 -- mReaders;
709}
710
711/**
712 * Increases the number of readers on all ancestors of this hard disk.
713 */
714void HardDisk::addReaderOnAncestors()
715{
716 AutoLock alock (this);
717 AssertReturnVoid (isReady());
718
719 if (mParent)
720 {
721 AutoLock alock (mParent);
722 mParent->addReader();
723 mParent->addReaderOnAncestors();
724 }
725}
726
727/**
728 * Decreases the number of readers on all ancestors of this hard disk.
729 */
730void HardDisk::releaseReaderOnAncestors()
731{
732 AutoLock alock (this);
733 AssertReturnVoid (isReady());
734
735 if (mParent)
736 {
737 AutoLock alock (mParent);
738 mParent->releaseReaderOnAncestors();
739 mParent->releaseReader();
740 }
741}
742
743/**
744 * Returns true if this hard disk has children not belonging to the same
745 * machine.
746 */
747bool HardDisk::hasForeignChildren()
748{
749 AutoLock alock (this);
750 AssertReturn (isReady(), false);
751
752 AssertReturn (!mMachineId.isEmpty(), false);
753
754 /* check all children */
755 AutoLock chLock (childrenLock());
756 for (HardDiskList::const_iterator it = children().begin();
757 it != children().end();
758 ++ it)
759 {
760 ComObjPtr <HardDisk> child = *it;
761 AutoLock childLock (child);
762 if (child->mMachineId != mMachineId)
763 return true;
764 }
765
766 return false;
767}
768
769/**
770 * Marks this hard disk and all its children as busy.
771 * Used for merge operations.
772 * Returns a meaningful error info on failure.
773 */
774HRESULT HardDisk::setBusyWithChildren()
775{
776 AutoLock alock (this);
777 AssertReturn (isReady(), E_FAIL);
778
779 const char *errMsg = tr ("Hard disk '%ls' is being used by another task");
780
781 if (mReaders > 0 || mBusy)
782 return setError (E_FAIL, errMsg, toString().raw());
783
784 AutoLock chLock (childrenLock());
785
786 for (HardDiskList::const_iterator it = children().begin();
787 it != children().end();
788 ++ it)
789 {
790 ComObjPtr <HardDisk> child = *it;
791 AutoLock childLock (child);
792 if (child->mReaders > 0 || child->mBusy)
793 {
794 /* reset the busy flag of all previous children */
795 while (it != children().begin())
796 (*(-- it))->clearBusy();
797 return setError (E_FAIL, errMsg, child->toString().raw());
798 }
799 else
800 child->mBusy = true;
801 }
802
803 mBusy = true;
804
805 return S_OK;
806}
807
808/**
809 * Clears the busy flag of this hard disk and all its children.
810 * An opposite to #setBusyWithChildren.
811 */
812void HardDisk::clearBusyWithChildren()
813{
814 AutoLock alock (this);
815 AssertReturn (isReady(), (void) 0);
816
817 AssertReturn (mBusy == true, (void) 0);
818
819 AutoLock chLock (childrenLock());
820
821 for (HardDiskList::const_iterator it = children().begin();
822 it != children().end();
823 ++ it)
824 {
825 ComObjPtr <HardDisk> child = *it;
826 AutoLock childLock (child);
827 Assert (child->mBusy == true);
828 child->mBusy = false;
829 }
830
831 mBusy = false;
832}
833
834/**
835 * Checks that this hard disk and all its direct children are accessible.
836 */
837HRESULT HardDisk::getAccessibleWithChildren (Bstr &aAccessError)
838{
839 AutoLock alock (this);
840 AssertReturn (isReady(), E_FAIL);
841
842 HRESULT rc = getAccessible (aAccessError);
843 if (FAILED (rc) || !aAccessError.isNull())
844 return rc;
845
846 AutoLock chLock (childrenLock());
847
848 for (HardDiskList::const_iterator it = children().begin();
849 it != children().end();
850 ++ it)
851 {
852 ComObjPtr <HardDisk> child = *it;
853 rc = child->getAccessible (aAccessError);
854 if (FAILED (rc) || !aAccessError.isNull())
855 return rc;
856 }
857
858 return rc;
859}
860
861/**
862 * Checks that this hard disk and all its descendants are consistent.
863 * For now, the consistency means that:
864 *
865 * 1) every differencing image is associated with a registered machine
866 * 2) every root image that has differencing children is associated with
867 * a registered machine.
868 *
869 * This method is used by the VirtualBox constructor after loading all hard
870 * disks and all machines.
871 */
872HRESULT HardDisk::checkConsistency()
873{
874 AutoLock alock (this);
875 AssertReturn (isReady(), E_FAIL);
876
877 if (isDifferencing())
878 {
879 Assert (mVirtualBox->isMachineIdValid (mMachineId) ||
880 mMachineId.isEmpty());
881
882 if (mMachineId.isEmpty())
883 return setError (E_FAIL,
884 tr ("Differencing hard disk '%ls' is not associated with "
885 "any registered virtual machine or snapshot"),
886 toString().raw());
887 }
888
889 HRESULT rc = S_OK;
890
891 AutoLock chLock (childrenLock());
892
893 if (mParent.isNull() && mType == HardDiskType_NormalHardDisk &&
894 children().size() != 0)
895 {
896 if (mMachineId.isEmpty())
897 return setError (E_FAIL,
898 tr ("Hard disk '%ls' is not associated with any registered "
899 "virtual machine or snapshot, but has differencing child "
900 "hard disks based on it"),
901 toString().raw());
902 }
903
904 for (HardDiskList::const_iterator it = children().begin();
905 it != children().end() && SUCCEEDED (rc);
906 ++ it)
907 {
908 rc = (*it)->checkConsistency();
909 }
910
911 return rc;
912}
913
914/**
915 * Creates a differencing hard disk for this hard disk and returns the
916 * created hard disk object to the caller.
917 *
918 * The created differencing hard disk is automatically added to the list of
919 * children of this hard disk object and registered within VirtualBox.
920
921 * The specified progress object (if not NULL) receives the percentage
922 * of the operation completion. However, it is responsibility of the caller to
923 * call Progress::notifyComplete() after this method returns.
924 *
925 * @param aFolder folder where to create the differencing disk
926 * (must be a full path)
927 * @param aMachineId machine ID the new hard disk will belong to
928 * @param aHardDisk resulting hard disk object
929 * @param aProgress progress object to run during copy operation
930 * (may be NULL)
931 *
932 * @note
933 * Must be NOT called from under locks of other objects that need external
934 * access dirung this method execurion!
935 */
936HRESULT HardDisk::createDiffHardDisk (const Bstr &aFolder, const Guid &aMachineId,
937 ComObjPtr <HVirtualDiskImage> &aHardDisk,
938 Progress *aProgress)
939{
940 AssertReturn (!aFolder.isEmpty() && !aMachineId.isEmpty(),
941 E_FAIL);
942
943 AutoLock alock (this);
944 CHECK_READY();
945
946 ComAssertRet (isBusy() == false, E_FAIL);
947
948 Guid id;
949 id.create();
950
951 Utf8Str filePathTo = Utf8StrFmt ("%ls%c{%Vuuid}.vdi",
952 aFolder.raw(), RTPATH_DELIMITER, id.ptr());
953
954 /* try to make the path relative to the vbox home dir */
955 const char *filePathToRel = filePathTo;
956 {
957 const Utf8Str &homeDir = mVirtualBox->homeDir();
958 if (!strncmp (filePathTo, homeDir, homeDir.length()))
959 filePathToRel = (filePathToRel + homeDir.length() + 1);
960 }
961
962 /* first ensure the directory exists */
963 {
964 Utf8Str dir = aFolder;
965 if (!RTDirExists (dir))
966 {
967 int vrc = RTDirCreateFullPath (dir, 0777);
968 if (VBOX_FAILURE (vrc))
969 {
970 return setError (E_FAIL,
971 tr ("Could not create a directory '%s' "
972 "to store the image file (%Vrc)"),
973 dir.raw(), vrc);
974 }
975 }
976 }
977
978 alock.leave();
979
980 /* call storage type specific diff creation method */
981 HRESULT rc = createDiffImage (id, filePathTo, aProgress);
982
983 alock.enter();
984
985 CheckComRCReturnRC (rc);
986
987 ComObjPtr <HVirtualDiskImage> vdi;
988 vdi.createObject();
989 rc = vdi->init (mVirtualBox, this, Bstr (filePathToRel),
990 TRUE /* aRegistered */);
991 CheckComRCReturnRC (rc);
992
993 /* associate the created hard disk with the given machine */
994 vdi->setMachineId (aMachineId);
995
996 rc = mVirtualBox->registerHardDisk (vdi, VirtualBox::RHD_Internal);
997 CheckComRCReturnRC (rc);
998
999 aHardDisk = vdi;
1000
1001 return S_OK;
1002}
1003
1004/**
1005 * Checks if the given change of \a aOldPath to \a aNewPath affects the path
1006 * of this hard disk or any of its children and updates it if necessary (by
1007 * calling #updatePath()). Intended to be called only by
1008 * VirtualBox::updateSettings() if a machine's name change causes directory
1009 * renaming that affects this image.
1010 *
1011 * @param aOldPath old path (full)
1012 * @param aNewPath new path (full)
1013 *
1014 * @note Locks this object and all children for writing.
1015 */
1016void HardDisk::updatePaths (const char *aOldPath, const char *aNewPath)
1017{
1018 AssertReturnVoid (aOldPath);
1019 AssertReturnVoid (aNewPath);
1020
1021 AutoLock alock (this);
1022 AssertReturnVoid (isReady());
1023
1024 updatePath (aOldPath, aNewPath);
1025
1026 /* update paths of all children */
1027 AutoLock chLock (childrenLock());
1028 for (HardDiskList::const_iterator it = children().begin();
1029 it != children().end();
1030 ++ it)
1031 {
1032 (*it)->updatePaths (aOldPath, aNewPath);
1033 }
1034}
1035
1036/**
1037 * Helper method that deduces a hard disk object type to create from
1038 * the location string format and from the contents of the resource
1039 * pointed to by the location string.
1040 *
1041 * Currently, the location string must be a file path which is
1042 * passed to the HVirtualDiskImage or HVMDKImage initializer in
1043 * attempt to create a hard disk object.
1044 *
1045 * @param aVirtualBox
1046 * @param aLocation
1047 * @param hardDisk
1048 *
1049 * @return
1050 */
1051/* static */
1052HRESULT HardDisk::openHardDisk (VirtualBox *aVirtualBox, INPTR BSTR aLocation,
1053 ComObjPtr <HardDisk> &hardDisk)
1054{
1055 LogFlowFunc (("aLocation=\"%ls\"\n", aLocation));
1056
1057 AssertReturn (aVirtualBox, E_POINTER);
1058
1059 /* null and empty strings are not allowed locations */
1060 AssertReturn (aLocation, E_INVALIDARG);
1061 AssertReturn (*aLocation, E_INVALIDARG);
1062
1063 static const struct
1064 {
1065 HardDiskStorageType_T type;
1066 const char *ext;
1067 }
1068 storageTypes[] =
1069 {
1070 { HardDiskStorageType_VMDKImage, ".vmdk" },
1071 { HardDiskStorageType_VirtualDiskImage, ".vdi" },
1072 };
1073
1074 /* try to guess the probe order by extension */
1075 size_t first = 0;
1076 bool haveFirst = false;
1077 Utf8Str loc = aLocation;
1078 char *ext = RTPathExt (loc);
1079
1080 for (size_t i = 0; i < ELEMENTS (storageTypes); ++ i)
1081 {
1082 if (RTPathCompare (ext, storageTypes [i].ext) == 0)
1083 {
1084 first = i;
1085 haveFirst = true;
1086 break;
1087 }
1088 }
1089
1090 HRESULT rc = S_OK;
1091
1092 HRESULT firstRC = S_OK;
1093 com::ErrorInfoKeeper firstErr (true /* aIsNull */);
1094
1095 for (size_t i = 0; i < ELEMENTS (storageTypes); ++ i)
1096 {
1097 size_t j = !haveFirst ? i : i == 0 ? first : i == first ? 0 : i;
1098 switch (storageTypes [j].type)
1099 {
1100 case HardDiskStorageType_VirtualDiskImage:
1101 {
1102 ComObjPtr <HVirtualDiskImage> obj;
1103 obj.createObject();
1104 rc = obj->init (aVirtualBox, NULL, aLocation,
1105 FALSE /* aRegistered */);
1106 if (SUCCEEDED (rc))
1107 {
1108 hardDisk = obj;
1109 return rc;
1110 }
1111 break;
1112 }
1113 case HardDiskStorageType_VMDKImage:
1114 {
1115 ComObjPtr <HVMDKImage> obj;
1116 obj.createObject();
1117 rc = obj->init (aVirtualBox, NULL, aLocation,
1118 FALSE /* aRegistered */);
1119 if (SUCCEEDED (rc))
1120 {
1121 hardDisk = obj;
1122 return rc;
1123 }
1124 break;
1125 }
1126 default:
1127 {
1128 AssertComRCReturnRC (E_FAIL);
1129 }
1130 }
1131
1132 Assert (FAILED (rc));
1133
1134 /* remember the error of the matching class */
1135 if (haveFirst && j == first)
1136 {
1137 firstRC = rc;
1138 firstErr.fetch();
1139 }
1140 }
1141
1142 if (haveFirst)
1143 {
1144 Assert (FAILED (firstRC));
1145 /* firstErr will restore the error info upon destruction */
1146 return firstRC;
1147 }
1148
1149 /* There was no exact extension match; chances are high that an error we
1150 * got after probing is useless. Use a generic error message instead. */
1151
1152 firstErr.forget();
1153
1154 return setError (E_FAIL,
1155 tr ("Could not recognize the format of the hard disk '%ls'. "
1156 "Either the given format is not supported or hard disk data "
1157 "is corrupt"),
1158 aLocation);
1159}
1160
1161// protected methods
1162/////////////////////////////////////////////////////////////////////////////
1163
1164/**
1165 * Loads the base settings of the hard disk from the given node, registers
1166 * it and loads and registers all child hard disks as HVirtualDiskImage
1167 * instances.
1168 *
1169 * Subclasses must call this method in their init() or loadSettings() methods
1170 * *after* they load specific parts of data (at least, necessary to let
1171 * toString() function correctly), in order to be properly loaded from the
1172 * settings file and registered.
1173 *
1174 * @param aHDNode <HardDisk> node when #isDifferencing() = false, or
1175 * <DiffHardDisk> node otherwise
1176 *
1177 * @note
1178 * Must be called from under the object's lock
1179 */
1180HRESULT HardDisk::loadSettings (CFGNODE aHDNode)
1181{
1182 AssertReturn (aHDNode, E_FAIL);
1183
1184 Guid uuid; /* uuid (required) */
1185 CFGLDRQueryUUID (aHDNode, "uuid", uuid.ptr());
1186 mId = uuid;
1187
1188 if (!isDifferencing())
1189 {
1190 Bstr type; /* type (required for <HardDisk> nodes only) */
1191 CFGLDRQueryBSTR (aHDNode, "type", type.asOutParam());
1192 if (type == L"normal")
1193 mType = HardDiskType_NormalHardDisk;
1194 else if (type == L"immutable")
1195 mType = HardDiskType_ImmutableHardDisk;
1196 else if (type == L"writethrough")
1197 mType = HardDiskType_WritethroughHardDisk;
1198 else
1199 ComAssertMsgFailedRet (("Invalid hard disk type '%ls'\n", type.raw()),
1200 E_FAIL);
1201 }
1202 else
1203 mType = HardDiskType_NormalHardDisk;
1204
1205 HRESULT rc = mVirtualBox->registerHardDisk (this, VirtualBox::RHD_OnStartUp);
1206 if (FAILED (rc))
1207 return rc;
1208
1209 /* load all children */
1210 unsigned count = 0;
1211 CFGLDRCountChildren (aHDNode, "DiffHardDisk", &count);
1212 for (unsigned i = 0; i < count && SUCCEEDED (rc); ++ i)
1213 {
1214 CFGNODE hdNode = 0;
1215
1216 CFGLDRGetChildNode (aHDNode, "DiffHardDisk", i, &hdNode);
1217 ComAssertBreak (hdNode, rc = E_FAIL);
1218
1219 do
1220 {
1221 CFGNODE vdiNode = 0;
1222 CFGLDRGetChildNode (hdNode, "VirtualDiskImage", 0, &vdiNode);
1223 ComAssertBreak (vdiNode, rc = E_FAIL);
1224
1225 ComObjPtr <HVirtualDiskImage> vdi;
1226 vdi.createObject();
1227 rc = vdi->init (mVirtualBox, this, hdNode, vdiNode);
1228
1229 CFGLDRReleaseNode (vdiNode);
1230 }
1231 while (0);
1232
1233 CFGLDRReleaseNode (hdNode);
1234 }
1235
1236 return rc;
1237}
1238
1239/**
1240 * Saves the base settings of the hard disk to the given node
1241 * and saves all child hard disks as <DiffHardDisk> nodes.
1242 *
1243 * Subclasses must call this method in their saveSettings() methods
1244 * in order to be properly saved to the settings file.
1245 *
1246 * @param aHDNode <HardDisk> node when #isDifferencing() = false, or
1247 * <DiffHardDisk> node otherwise
1248 *
1249 * @note
1250 * Must be called from under the object's lock
1251 */
1252HRESULT HardDisk::saveSettings (CFGNODE aHDNode)
1253{
1254 AssertReturn (aHDNode, E_FAIL);
1255
1256 /* uuid (required) */
1257 CFGLDRSetUUID (aHDNode, "uuid", mId.ptr());
1258
1259 if (!isDifferencing())
1260 {
1261 /* type (required) */
1262 const char *type = NULL;
1263 switch (mType)
1264 {
1265 case HardDiskType_NormalHardDisk:
1266 type = "normal";
1267 break;
1268 case HardDiskType_ImmutableHardDisk:
1269 type = "immutable";
1270 break;
1271 case HardDiskType_WritethroughHardDisk:
1272 type = "writethrough";
1273 break;
1274 }
1275 CFGLDRSetString (aHDNode, "type", type);
1276 }
1277
1278 HRESULT rc = S_OK;
1279
1280 /* save all children */
1281 AutoLock chLock (childrenLock());
1282 for (HardDiskList::const_iterator it = children().begin();
1283 it != children().end() && SUCCEEDED (rc);
1284 ++ it)
1285 {
1286 ComObjPtr <HardDisk> child = *it;
1287 AutoLock childLock (child);
1288
1289 CFGNODE hdNode = 0;
1290 CFGLDRAppendChildNode (aHDNode, "DiffHardDisk", &hdNode);
1291 ComAssertBreak (hdNode, rc = E_FAIL);
1292
1293 do
1294 {
1295 CFGNODE vdiNode = 0;
1296 CFGLDRAppendChildNode (hdNode, "VirtualDiskImage", &vdiNode);
1297 ComAssertBreak (vdiNode, rc = E_FAIL);
1298
1299 rc = child->saveSettings (hdNode, vdiNode);
1300
1301 CFGLDRReleaseNode (vdiNode);
1302 }
1303 while (0);
1304
1305 CFGLDRReleaseNode (hdNode);
1306 }
1307
1308 return rc;
1309}
1310
1311////////////////////////////////////////////////////////////////////////////////
1312// HVirtualDiskImage class
1313////////////////////////////////////////////////////////////////////////////////
1314
1315// constructor / destructor
1316////////////////////////////////////////////////////////////////////////////////
1317
1318HRESULT HVirtualDiskImage::FinalConstruct()
1319{
1320 HRESULT rc = HardDisk::FinalConstruct();
1321 if (FAILED (rc))
1322 return rc;
1323
1324 mState = NotCreated;
1325
1326 mStateCheckSem = NIL_RTSEMEVENTMULTI;
1327 mStateCheckWaiters = 0;
1328
1329 mSize = 0;
1330 mActualSize = 0;
1331
1332 return S_OK;
1333}
1334
1335void HVirtualDiskImage::FinalRelease()
1336{
1337 HardDisk::FinalRelease();
1338}
1339
1340// public initializer/uninitializer for internal purposes only
1341////////////////////////////////////////////////////////////////////////////////
1342
1343// public methods for internal purposes only
1344/////////////////////////////////////////////////////////////////////////////
1345
1346/**
1347 * Initializes the VDI hard disk object by reading its properties from
1348 * the given configuration node. The created hard disk will be marked as
1349 * registered on success.
1350 *
1351 * @param aHDNode <HardDisk> node
1352 * @param aVDINode <VirtualDiskImage> node
1353 */
1354HRESULT HVirtualDiskImage::init (VirtualBox *aVirtualBox, HardDisk *aParent,
1355 CFGNODE aHDNode, CFGNODE aVDINode)
1356{
1357 LogFlowThisFunc (("aHDNode=%p, aVDINode=%p\n", aHDNode, aVDINode));
1358
1359 AssertReturn (aHDNode && aVDINode, E_FAIL);
1360
1361 AutoLock alock (this);
1362 ComAssertRet (!isReady(), E_UNEXPECTED);
1363
1364 mStorageType = HardDiskStorageType_VirtualDiskImage;
1365
1366 HRESULT rc = S_OK;
1367
1368 do
1369 {
1370 rc = protectedInit (aVirtualBox, aParent);
1371 CheckComRCBreakRC (rc);
1372
1373 /* set ready to let protectedUninit() be called on failure */
1374 setReady (true);
1375
1376 /* filePath (required) */
1377 Bstr filePath;
1378 CFGLDRQueryBSTR (aVDINode, "filePath", filePath.asOutParam());
1379
1380 rc = setFilePath (filePath);
1381 CheckComRCBreakRC (rc);
1382
1383 LogFlowThisFunc (("'%ls'\n", mFilePathFull.raw()));
1384
1385 /* load basic settings and children */
1386 rc = loadSettings (aHDNode);
1387 CheckComRCBreakRC (rc);
1388
1389 mState = Created;
1390 mRegistered = TRUE;
1391
1392 /* Don't call queryInformation() for registered hard disks to
1393 * prevent the calling thread (i.e. the VirtualBox server startup
1394 * thread) from an unexpected freeze. The vital mId property (UUID)
1395 * is read from the registry file in loadSettings(). To get the rest,
1396 * the user will have to call COMGETTER(Accessible) manually. */
1397 }
1398 while (0);
1399
1400 if (FAILED (rc))
1401 uninit();
1402
1403 return rc;
1404}
1405
1406/**
1407 * Initializes the VDI hard disk object using the given image file name.
1408 *
1409 * @param aVirtualBox VirtualBox parent.
1410 * @param aParent Parent hard disk.
1411 * @param aFilePath Path to the image file, or @c NULL to create an
1412 * image-less object.
1413 * @param aRegistered Whether to mark this disk as registered or not
1414 * (ignored when @a aFilePath is @c NULL, assuming @c FALSE)
1415 */
1416HRESULT HVirtualDiskImage::init (VirtualBox *aVirtualBox, HardDisk *aParent,
1417 const BSTR aFilePath, BOOL aRegistered /* = FALSE */)
1418{
1419 LogFlowThisFunc (("aFilePath='%ls', aRegistered=%d\n",
1420 aFilePath, aRegistered));
1421
1422 AutoLock alock (this);
1423 ComAssertRet (!isReady(), E_UNEXPECTED);
1424
1425 mStorageType = HardDiskStorageType_VirtualDiskImage;
1426
1427 HRESULT rc = S_OK;
1428
1429 do
1430 {
1431 rc = protectedInit (aVirtualBox, aParent);
1432 CheckComRCBreakRC (rc);
1433
1434 /* set ready to let protectedUninit() be called on failure */
1435 setReady (true);
1436
1437 rc = setFilePath (aFilePath);
1438 CheckComRCBreakRC (rc);
1439
1440 Assert (mId.isEmpty());
1441
1442 if (aFilePath && *aFilePath)
1443 {
1444 mRegistered = aRegistered;
1445 mState = Created;
1446
1447 /* Call queryInformation() anyway (even if it will block), because
1448 * it is the only way to get the UUID of the existing VDI and
1449 * initialize the vital mId property. */
1450 Bstr errMsg;
1451 rc = queryInformation (&errMsg);
1452 if (SUCCEEDED (rc))
1453 {
1454 /* We are constructing a new HVirtualDiskImage object. If there
1455 * is a fatal accessibility error (we cannot read image UUID),
1456 * we have to fail. We do so even on non-fatal errors as well,
1457 * because it's not worth to keep going with the inaccessible
1458 * image from the very beginning (when nothing else depends on
1459 * it yet). */
1460 if (!errMsg.isNull())
1461 rc = setErrorBstr (E_FAIL, errMsg);
1462 }
1463 }
1464 else
1465 {
1466 mRegistered = FALSE;
1467 mState = NotCreated;
1468 mId.create();
1469 }
1470 }
1471 while (0);
1472
1473 if (FAILED (rc))
1474 uninit();
1475
1476 return rc;
1477}
1478
1479/**
1480 * Uninitializes the instance and sets the ready flag to FALSE.
1481 * Called either from FinalRelease(), by the parent when it gets destroyed,
1482 * or by a third party when it decides this object is no more valid.
1483 */
1484void HVirtualDiskImage::uninit()
1485{
1486 LogFlowThisFunc (("\n"));
1487
1488 AutoLock alock (this);
1489 if (!isReady())
1490 return;
1491
1492 HardDisk::protectedUninit (alock);
1493}
1494
1495// IHardDisk properties
1496////////////////////////////////////////////////////////////////////////////////
1497
1498STDMETHODIMP HVirtualDiskImage::COMGETTER(Description) (BSTR *aDescription)
1499{
1500 if (!aDescription)
1501 return E_POINTER;
1502
1503 AutoLock alock (this);
1504 CHECK_READY();
1505
1506 mDescription.cloneTo (aDescription);
1507 return S_OK;
1508}
1509
1510STDMETHODIMP HVirtualDiskImage::COMSETTER(Description) (INPTR BSTR aDescription)
1511{
1512 AutoLock alock (this);
1513 CHECK_READY();
1514
1515 CHECK_BUSY_AND_READERS();
1516
1517 if (mState >= Created)
1518 {
1519 int vrc = VDISetImageComment (Utf8Str (mFilePathFull), Utf8Str (aDescription));
1520 if (VBOX_FAILURE (vrc))
1521 return setError (E_FAIL,
1522 tr ("Could not change the description of the VDI hard disk '%ls' "
1523 "(%Vrc)"),
1524 toString().raw(), vrc);
1525 }
1526
1527 mDescription = aDescription;
1528 return S_OK;
1529}
1530
1531STDMETHODIMP HVirtualDiskImage::COMGETTER(Size) (ULONG64 *aSize)
1532{
1533 if (!aSize)
1534 return E_POINTER;
1535
1536 AutoLock alock (this);
1537 CHECK_READY();
1538
1539 /* only a non-differencing image knows the logical size */
1540 if (isDifferencing())
1541 return root()->COMGETTER(Size) (aSize);
1542
1543 *aSize = mSize;
1544 return S_OK;
1545}
1546
1547STDMETHODIMP HVirtualDiskImage::COMGETTER(ActualSize) (ULONG64 *aActualSize)
1548{
1549 if (!aActualSize)
1550 return E_POINTER;
1551
1552 AutoLock alock (this);
1553 CHECK_READY();
1554
1555 *aActualSize = mActualSize;
1556 return S_OK;
1557}
1558
1559// IVirtualDiskImage properties
1560////////////////////////////////////////////////////////////////////////////////
1561
1562STDMETHODIMP HVirtualDiskImage::COMGETTER(FilePath) (BSTR *aFilePath)
1563{
1564 if (!aFilePath)
1565 return E_POINTER;
1566
1567 AutoLock alock (this);
1568 CHECK_READY();
1569
1570 mFilePathFull.cloneTo (aFilePath);
1571 return S_OK;
1572}
1573
1574STDMETHODIMP HVirtualDiskImage::COMSETTER(FilePath) (INPTR BSTR aFilePath)
1575{
1576 AutoLock alock (this);
1577 CHECK_READY();
1578
1579 if (mState != NotCreated)
1580 return setError (E_ACCESSDENIED,
1581 tr ("Cannot change the file path of the existing hard disk '%ls'"),
1582 toString().raw());
1583
1584 CHECK_BUSY_AND_READERS();
1585
1586 // append the default path if only a name is given
1587 Bstr path = aFilePath;
1588 if (aFilePath && *aFilePath)
1589 {
1590 Utf8Str fp = aFilePath;
1591 if (!RTPathHavePath (fp))
1592 {
1593 AutoReaderLock propsLock (mVirtualBox->systemProperties());
1594 path = Utf8StrFmt ("%ls%c%s",
1595 mVirtualBox->systemProperties()->defaultVDIFolder().raw(),
1596 RTPATH_DELIMITER,
1597 fp.raw());
1598 }
1599 }
1600
1601 return setFilePath (path);
1602}
1603
1604STDMETHODIMP HVirtualDiskImage::COMGETTER(Created) (BOOL *aCreated)
1605{
1606 if (!aCreated)
1607 return E_POINTER;
1608
1609 AutoLock alock (this);
1610 CHECK_READY();
1611
1612 *aCreated = mState >= Created;
1613 return S_OK;
1614}
1615
1616// IVirtualDiskImage methods
1617/////////////////////////////////////////////////////////////////////////////
1618
1619STDMETHODIMP HVirtualDiskImage::CreateDynamicImage (ULONG64 aSize, IProgress **aProgress)
1620{
1621 if (!aProgress)
1622 return E_POINTER;
1623
1624 AutoLock alock (this);
1625 CHECK_READY();
1626
1627 return createImage (aSize, TRUE /* aDynamic */, aProgress);
1628}
1629
1630STDMETHODIMP HVirtualDiskImage::CreateFixedImage (ULONG64 aSize, IProgress **aProgress)
1631{
1632 if (!aProgress)
1633 return E_POINTER;
1634
1635 AutoLock alock (this);
1636 CHECK_READY();
1637
1638 return createImage (aSize, FALSE /* aDynamic */, aProgress);
1639}
1640
1641STDMETHODIMP HVirtualDiskImage::DeleteImage()
1642{
1643 AutoLock alock (this);
1644 CHECK_READY();
1645 CHECK_BUSY_AND_READERS();
1646
1647 if (mRegistered)
1648 return setError (E_ACCESSDENIED,
1649 tr ("Cannot delete an image of the registered hard disk image '%ls"),
1650 mFilePathFull.raw());
1651 if (mState == NotCreated)
1652 return setError (E_FAIL,
1653 tr ("Hard disk image has been already deleted or never created"));
1654
1655 int vrc = RTFileDelete (Utf8Str (mFilePathFull));
1656 if (VBOX_FAILURE (vrc))
1657 return setError (E_FAIL, tr ("Could not delete the image file '%ls' (%Vrc)"),
1658 mFilePathFull.raw(), vrc);
1659
1660 mState = NotCreated;
1661
1662 // reset the fields
1663 mSize = 0;
1664 mActualSize = 0;
1665
1666 return S_OK;
1667}
1668
1669// public/protected methods for internal purposes only
1670/////////////////////////////////////////////////////////////////////////////
1671
1672/**
1673 * Attempts to mark the hard disk as registered.
1674 * Only VirtualBox can call this method.
1675 */
1676HRESULT HVirtualDiskImage::trySetRegistered (BOOL aRegistered)
1677{
1678 AutoLock alock (this);
1679 CHECK_READY();
1680
1681 if (aRegistered)
1682 {
1683 if (mState == NotCreated)
1684 return setError (E_FAIL,
1685 tr ("Image file '%ls' is not yet created for this hard disk"),
1686 mFilePathFull.raw());
1687 if (isDifferencing())
1688 return setError (E_FAIL,
1689 tr ("Hard disk '%ls' is differencing and cannot be unregistered "
1690 "explicitly"),
1691 mFilePathFull.raw());
1692 }
1693 else
1694 {
1695 ComAssertRet (mState >= Created, E_FAIL);
1696 }
1697
1698 return HardDisk::trySetRegistered (aRegistered);
1699}
1700
1701/**
1702 * Checks accessibility of this hard disk image only (w/o parents).
1703 *
1704 * @param aAccessError on output, a null string indicates the hard disk is
1705 * accessible, otherwise contains a message describing
1706 * the reason of inaccessibility.
1707 */
1708HRESULT HVirtualDiskImage::getAccessible (Bstr &aAccessError)
1709{
1710 AutoLock alock (this);
1711 CHECK_READY();
1712
1713 if (mStateCheckSem != NIL_RTSEMEVENTMULTI)
1714 {
1715 /* An accessibility check in progress on some other thread,
1716 * wait for it to finish. */
1717
1718 ComAssertRet (mStateCheckWaiters != (ULONG) ~0, E_FAIL);
1719 ++ mStateCheckWaiters;
1720 alock.leave();
1721
1722 int vrc = RTSemEventMultiWait (mStateCheckSem, RT_INDEFINITE_WAIT);
1723
1724 alock.enter();
1725 AssertReturn (mStateCheckWaiters != 0, E_FAIL);
1726 -- mStateCheckWaiters;
1727 if (mStateCheckWaiters == 0)
1728 {
1729 RTSemEventMultiDestroy (mStateCheckSem);
1730 mStateCheckSem = NIL_RTSEMEVENTMULTI;
1731 }
1732
1733 AssertRCReturn (vrc, E_FAIL);
1734
1735 /* don't touch aAccessError, it has been already set */
1736 return S_OK;
1737 }
1738
1739 /* check the basic accessibility */
1740 HRESULT rc = getBaseAccessible (aAccessError, true /* aCheckBusy */);
1741 if (FAILED (rc) || !aAccessError.isNull())
1742 return rc;
1743
1744 if (mState >= Created)
1745 {
1746 return queryInformation (&aAccessError);
1747 }
1748
1749 aAccessError = Utf8StrFmt ("Hard disk image '%ls' is not yet created",
1750 mFilePathFull.raw());
1751 return S_OK;
1752}
1753
1754/**
1755 * Saves hard disk settings to the specified storage node and saves
1756 * all children to the specified hard disk node
1757 *
1758 * @param aHDNode <HardDisk> or <DiffHardDisk> node
1759 * @param aStorageNode <VirtualDiskImage> node
1760 */
1761HRESULT HVirtualDiskImage::saveSettings (CFGNODE aHDNode, CFGNODE aStorageNode)
1762{
1763 AssertReturn (aHDNode && aStorageNode, E_FAIL);
1764
1765 AutoLock alock (this);
1766 CHECK_READY();
1767
1768 // filePath (required)
1769 CFGLDRSetBSTR (aStorageNode, "filePath", mFilePath);
1770
1771 // save basic settings and children
1772 return HardDisk::saveSettings (aHDNode);
1773}
1774
1775/**
1776 * Checks if the given change of \a aOldPath to \a aNewPath affects the path
1777 * of this hard disk and updates it if necessary to reflect the new location.
1778 * Intended to be from HardDisk::updatePaths().
1779 *
1780 * @param aOldPath old path (full)
1781 * @param aNewPath new path (full)
1782 *
1783 * @note Locks this object for writing.
1784 */
1785void HVirtualDiskImage::updatePath (const char *aOldPath, const char *aNewPath)
1786{
1787 AssertReturnVoid (aOldPath);
1788 AssertReturnVoid (aNewPath);
1789
1790 AutoLock alock (this);
1791 AssertReturnVoid (isReady());
1792
1793 size_t oldPathLen = strlen (aOldPath);
1794
1795 Utf8Str path = mFilePathFull;
1796 LogFlowThisFunc (("VDI.fullPath={%s}\n", path.raw()));
1797
1798 if (RTPathStartsWith (path, aOldPath))
1799 {
1800 Utf8Str newPath = Utf8StrFmt ("%s%s", aNewPath,
1801 path.raw() + oldPathLen);
1802 path = newPath;
1803
1804 mVirtualBox->calculateRelativePath (path, path);
1805
1806 unconst (mFilePathFull) = newPath;
1807 unconst (mFilePath) = path;
1808
1809 LogFlowThisFunc (("-> updated: full={%s} short={%s}\n",
1810 newPath.raw(), path.raw()));
1811 }
1812}
1813
1814/**
1815 * Returns the string representation of this hard disk.
1816 * When \a aShort is false, returns the full image file path.
1817 * Otherwise, returns the image file name only.
1818 *
1819 * @param aShort if true, a short representation is returned
1820 */
1821Bstr HVirtualDiskImage::toString (bool aShort /* = false */)
1822{
1823 AutoLock alock (this);
1824
1825 if (!aShort)
1826 return mFilePathFull;
1827 else
1828 {
1829 Utf8Str fname = mFilePathFull;
1830 return RTPathFilename (fname.mutableRaw());
1831 }
1832}
1833
1834/**
1835 * Creates a clone of this hard disk by storing hard disk data in the given
1836 * VDI file.
1837 *
1838 * If the operation fails, @a aDeleteTarget will be set to @c true unless the
1839 * failure happened because the target file already existed.
1840 *
1841 * @param aId UUID to assign to the created image.
1842 * @param aTargetPath VDI file where the cloned image is to be to stored.
1843 * @param aProgress progress object to run during operation.
1844 * @param aDeleteTarget Whether it is recommended to delete target on
1845 * failure or not.
1846 */
1847HRESULT
1848HVirtualDiskImage::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath,
1849 Progress *aProgress, bool &aDeleteTarget)
1850{
1851 /* normally, the target file should be deleted on error */
1852 aDeleteTarget = true;
1853
1854 AssertReturn (!aId.isEmpty(), E_FAIL);
1855 AssertReturn (!aTargetPath.isNull(), E_FAIL);
1856 AssertReturn (aProgress, E_FAIL);
1857
1858 AutoLock alock (this);
1859 AssertReturn (isReady(), E_FAIL);
1860
1861 AssertReturn (isBusy() == false, E_FAIL);
1862
1863 /// @todo (dmik) cloning of differencing images is not yet supported
1864 AssertReturn (mParent.isNull(), E_FAIL);
1865
1866 Utf8Str filePathFull = mFilePathFull;
1867
1868 if (mState == NotCreated)
1869 return setError (E_FAIL,
1870 tr ("Source hard disk image '%s' is not yet created"),
1871 filePathFull.raw());
1872
1873 addReader();
1874 alock.leave();
1875
1876 int vrc = VDICopyImage (aTargetPath, filePathFull, NULL,
1877 progressCallback,
1878 static_cast <Progress *> (aProgress));
1879
1880 alock.enter();
1881 releaseReader();
1882
1883 /* We don't want to delete existing user files */
1884 if (vrc == VERR_ALREADY_EXISTS)
1885 aDeleteTarget = false;
1886
1887 if (VBOX_SUCCESS (vrc))
1888 vrc = VDISetImageUUIDs (aTargetPath, aId, NULL, NULL, NULL);
1889
1890 if (VBOX_FAILURE (vrc))
1891 return setError (E_FAIL,
1892 tr ("Could not copy the hard disk image '%s' to '%s' (%Vrc)"),
1893 filePathFull.raw(), aTargetPath.raw(), vrc);
1894
1895 return S_OK;
1896}
1897
1898/**
1899 * Creates a new differencing image for this hard disk with the given
1900 * VDI file name.
1901 *
1902 * @param aId UUID to assign to the created image
1903 * @param aTargetPath VDI file where to store the created differencing image
1904 * @param aProgress progress object to run during operation
1905 * (can be NULL)
1906 */
1907HRESULT
1908HVirtualDiskImage::createDiffImage (const Guid &aId, const Utf8Str &aTargetPath,
1909 Progress *aProgress)
1910{
1911 AssertReturn (!aId.isEmpty(), E_FAIL);
1912 AssertReturn (!aTargetPath.isNull(), E_FAIL);
1913
1914 AutoLock alock (this);
1915 AssertReturn (isReady(), E_FAIL);
1916
1917 AssertReturn (isBusy() == false, E_FAIL);
1918 AssertReturn (mState >= Created, E_FAIL);
1919
1920 addReader();
1921 alock.leave();
1922
1923 int vrc = VDICreateDifferenceImage (aTargetPath, Utf8Str (mFilePathFull),
1924 NULL, aProgress ? progressCallback : NULL,
1925 static_cast <Progress *> (aProgress));
1926 alock.enter();
1927 releaseReader();
1928
1929 /* update the UUID to correspond to the file name */
1930 if (VBOX_SUCCESS (vrc))
1931 vrc = VDISetImageUUIDs (aTargetPath, aId, NULL, NULL, NULL);
1932
1933 if (VBOX_FAILURE (vrc))
1934 return setError (E_FAIL,
1935 tr ("Could not create a differencing hard disk '%s' (%Vrc)"),
1936 aTargetPath.raw(), vrc);
1937
1938 return S_OK;
1939}
1940
1941/**
1942 * Copies the image file of this hard disk to a separate VDI file (with an
1943 * unique creation UUID) and creates a new hard disk object for the copied
1944 * image. The copy will be created as a child of this hard disk's parent
1945 * (so that this hard disk must be a differencing one).
1946 *
1947 * The specified progress object (if not NULL) receives the percentage
1948 * of the operation completion. However, it is responsibility of the caller to
1949 * call Progress::notifyComplete() after this method returns.
1950 *
1951 * @param aFolder folder where to create a copy (must be a full path)
1952 * @param aMachineId machine ID the new hard disk will belong to
1953 * @param aHardDisk resulting hard disk object
1954 * @param aProgress progress object to run during copy operation
1955 * (may be NULL)
1956 *
1957 * @note
1958 * Must be NOT called from under locks of other objects that need external
1959 * access dirung this method execurion!
1960 */
1961HRESULT
1962HVirtualDiskImage::cloneDiffImage (const Bstr &aFolder, const Guid &aMachineId,
1963 ComObjPtr <HVirtualDiskImage> &aHardDisk,
1964 Progress *aProgress)
1965{
1966 AssertReturn (!aFolder.isEmpty() && !aMachineId.isEmpty(),
1967 E_FAIL);
1968
1969 AutoLock alock (this);
1970 CHECK_READY();
1971
1972 AssertReturn (!mParent.isNull(), E_FAIL);
1973
1974 ComAssertRet (isBusy() == false, E_FAIL);
1975 ComAssertRet (mState >= Created, E_FAIL);
1976
1977 Guid id;
1978 id.create();
1979
1980 Utf8Str filePathTo = Utf8StrFmt ("%ls%c{%Vuuid}.vdi",
1981 aFolder.raw(), RTPATH_DELIMITER, id.ptr());
1982
1983 /* try to make the path relative to the vbox home dir */
1984 const char *filePathToRel = filePathTo;
1985 {
1986 const Utf8Str &homeDir = mVirtualBox->homeDir();
1987 if (!strncmp (filePathTo, homeDir, homeDir.length()))
1988 filePathToRel = (filePathToRel + homeDir.length() + 1);
1989 }
1990
1991 /* first ensure the directory exists */
1992 {
1993 Utf8Str dir = aFolder;
1994 if (!RTDirExists (dir))
1995 {
1996 int vrc = RTDirCreateFullPath (dir, 0777);
1997 if (VBOX_FAILURE (vrc))
1998 {
1999 return setError (E_FAIL,
2000 tr ("Could not create a directory '%s' "
2001 "to store the image file (%Vrc)"),
2002 dir.raw(), vrc);
2003 }
2004 }
2005 }
2006
2007 Utf8Str filePathFull = mFilePathFull;
2008
2009 alock.leave();
2010
2011 int vrc = VDICopyImage (filePathTo, filePathFull, NULL,
2012 progressCallback,
2013 static_cast <Progress *> (aProgress));
2014
2015 alock.enter();
2016
2017 /* get modification and parent UUIDs of this image */
2018 RTUUID modUuid, parentUuid, parentModUuid;
2019 if (VBOX_SUCCESS (vrc))
2020 vrc = VDIGetImageUUIDs (filePathFull, NULL, &modUuid,
2021 &parentUuid, &parentModUuid);
2022
2023 // update the UUID of the copy to correspond to the file name
2024 // and copy all other UUIDs from this image
2025 if (VBOX_SUCCESS (vrc))
2026 vrc = VDISetImageUUIDs (filePathTo, id, &modUuid,
2027 &parentUuid, &parentModUuid);
2028
2029 if (VBOX_FAILURE (vrc))
2030 return setError (E_FAIL,
2031 tr ("Could not copy the hard disk image '%s' to '%s' (%Vrc)"),
2032 filePathFull.raw(), filePathTo.raw(), vrc);
2033
2034 ComObjPtr <HVirtualDiskImage> vdi;
2035 vdi.createObject();
2036 HRESULT rc = vdi->init (mVirtualBox, mParent, Bstr (filePathToRel),
2037 TRUE /* aRegistered */);
2038 if (FAILED (rc))
2039 return rc;
2040
2041 /* associate the created hard disk with the given machine */
2042 vdi->setMachineId (aMachineId);
2043
2044 rc = mVirtualBox->registerHardDisk (vdi, VirtualBox::RHD_Internal);
2045 if (FAILED (rc))
2046 return rc;
2047
2048 aHardDisk = vdi;
2049
2050 return S_OK;
2051}
2052
2053/**
2054 * Merges this child image to its parent image and updates the parent UUID
2055 * of all children of this image (to point to this image's parent).
2056 * It's a responsibility of the caller to unregister and uninitialize
2057 * the merged image on success.
2058 *
2059 * This method is intended to be called on a worker thread (the operation
2060 * can be time consuming).
2061 *
2062 * The specified progress object (if not NULL) receives the percentage
2063 * of the operation completion. However, it is responsibility of the caller to
2064 * call Progress::notifyComplete() after this method returns.
2065 *
2066 * @param aProgress progress object to run during copy operation
2067 * (may be NULL)
2068 *
2069 * @note
2070 * This method expects that both this hard disk and the paret hard disk
2071 * are marked as busy using #setBusyWithChildren() prior to calling it!
2072 * Busy flags of both hard disks will be cleared by this method
2073 * on a successful return. In case of failure, #clearBusyWithChildren()
2074 * must be called on a parent.
2075 *
2076 * @note
2077 * Must be NOT called from under locks of other objects that need external
2078 * access dirung this method execurion!
2079 */
2080HRESULT HVirtualDiskImage::mergeImageToParent (Progress *aProgress)
2081{
2082 LogFlowThisFunc (("mFilePathFull='%ls'\n", mFilePathFull.raw()));
2083
2084 AutoLock alock (this);
2085 CHECK_READY();
2086
2087 AssertReturn (!mParent.isNull(), E_FAIL);
2088 AutoLock parentLock (mParent);
2089
2090 ComAssertRet (isBusy() == true, E_FAIL);
2091 ComAssertRet (mParent->isBusy() == true, E_FAIL);
2092
2093 ComAssertRet (mState >= Created && mParent->asVDI()->mState >= Created, E_FAIL);
2094
2095 ComAssertMsgRet (mParent->storageType() == HardDiskStorageType_VirtualDiskImage,
2096 ("non VDI storage types are not yet supported!"), E_FAIL);
2097
2098 parentLock.leave();
2099 alock.leave();
2100
2101 int vrc = VDIMergeImage (Utf8Str (mFilePathFull),
2102 Utf8Str (mParent->asVDI()->mFilePathFull),
2103 progressCallback,
2104 static_cast <Progress *> (aProgress));
2105 alock.enter();
2106 parentLock.enter();
2107
2108 if (VBOX_FAILURE (vrc))
2109 return setError (E_FAIL,
2110 tr ("Could not merge the hard disk image '%ls' to "
2111 "its parent image '%ls' (%Vrc)"),
2112 mFilePathFull.raw(), mParent->asVDI()->mFilePathFull.raw(), vrc);
2113
2114 {
2115 HRESULT rc = S_OK;
2116
2117 AutoLock chLock (childrenLock());
2118
2119 for (HardDiskList::const_iterator it = children().begin();
2120 it != children().end(); ++ it)
2121 {
2122 ComObjPtr <HVirtualDiskImage> child = (*it)->asVDI();
2123 AutoLock childLock (child);
2124
2125 /* reparent the child */
2126 child->mParent = mParent;
2127 if (mParent)
2128 mParent->addDependentChild (child);
2129
2130 /* change the parent UUID in the image as well */
2131 RTUUID parentUuid, parentModUuid;
2132 vrc = VDIGetImageUUIDs (Utf8Str (mParent->asVDI()->mFilePathFull),
2133 &parentUuid, &parentModUuid, NULL, NULL);
2134 if (VBOX_FAILURE (vrc))
2135 {
2136 rc = setError (E_FAIL,
2137 tr ("Could not access the hard disk image '%ls' (%Vrc)"),
2138 mParent->asVDI()->mFilePathFull.raw(), vrc);
2139 break;
2140 }
2141 ComAssertBreak (mParent->id() == Guid (parentUuid), rc = E_FAIL);
2142 vrc = VDISetImageUUIDs (Utf8Str (child->mFilePathFull),
2143 NULL, NULL, &parentUuid, &parentModUuid);
2144 if (VBOX_FAILURE (vrc))
2145 {
2146 rc = setError (E_FAIL,
2147 tr ("Could not update parent UUID of the hard disk image "
2148 "'%ls' (%Vrc)"),
2149 child->mFilePathFull.raw(), vrc);
2150 break;
2151 }
2152 }
2153
2154 if (FAILED (rc))
2155 return rc;
2156 }
2157
2158 /* detach all our children to avoid their uninit in #uninit() */
2159 removeDependentChildren();
2160
2161 mParent->clearBusy();
2162 clearBusy();
2163
2164 return S_OK;
2165}
2166
2167/**
2168 * Merges this image to all its child images, updates the parent UUID
2169 * of all children of this image (to point to this image's parent).
2170 * It's a responsibility of the caller to unregister and uninitialize
2171 * the merged image on success.
2172 *
2173 * This method is intended to be called on a worker thread (the operation
2174 * can be time consuming).
2175 *
2176 * The specified progress object (if not NULL) receives the percentage
2177 * of the operation completion. However, it is responsibility of the caller to
2178 * call Progress::notifyComplete() after this method returns.
2179 *
2180 * @param aProgress progress object to run during copy operation
2181 * (may be NULL)
2182 *
2183 * @note
2184 * This method expects that both this hard disk and all children
2185 * are marked as busy using setBusyWithChildren() prior to calling it!
2186 * Busy flags of all affected hard disks will be cleared by this method
2187 * on a successful return. In case of failure, #clearBusyWithChildren()
2188 * must be called for this hard disk.
2189 *
2190 * @note
2191 * Must be NOT called from under locks of other objects that need external
2192 * access dirung this method execurion!
2193 */
2194HRESULT HVirtualDiskImage::mergeImageToChildren (Progress *aProgress)
2195{
2196 LogFlowThisFunc (("mFilePathFull='%ls'\n", mFilePathFull.raw()));
2197
2198 AutoLock alock (this);
2199 CHECK_READY();
2200
2201 /* this must be a diff image */
2202 AssertReturn (isDifferencing(), E_FAIL);
2203
2204 ComAssertRet (isBusy() == true, E_FAIL);
2205 ComAssertRet (mState >= Created, E_FAIL);
2206
2207 ComAssertMsgRet (mParent->storageType() == HardDiskStorageType_VirtualDiskImage,
2208 ("non VDI storage types are not yet supported!"), E_FAIL);
2209
2210 {
2211 HRESULT rc = S_OK;
2212
2213 AutoLock chLock (childrenLock());
2214
2215 /* iterate over a copy since we will modify the list */
2216 HardDiskList list = children();
2217
2218 for (HardDiskList::const_iterator it = list.begin();
2219 it != list.end(); ++ it)
2220 {
2221 ComObjPtr <HardDisk> hd = *it;
2222 ComObjPtr <HVirtualDiskImage> child = hd->asVDI();
2223 AutoLock childLock (child);
2224
2225 ComAssertRet (child->isBusy() == true, E_FAIL);
2226 ComAssertBreak (child->mState >= Created, rc = E_FAIL);
2227
2228 childLock.leave();
2229 alock.leave();
2230
2231 int vrc = VDIMergeImage (Utf8Str (mFilePathFull),
2232 Utf8Str (child->mFilePathFull),
2233 progressCallback,
2234 static_cast <Progress *> (aProgress));
2235 alock.enter();
2236 childLock.enter();
2237
2238 if (VBOX_FAILURE (vrc))
2239 {
2240 rc = setError (E_FAIL,
2241 tr ("Could not merge the hard disk image '%ls' to "
2242 "its parent image '%ls' (%Vrc)"),
2243 mFilePathFull.raw(), child->mFilePathFull.raw(), vrc);
2244 break;
2245 }
2246
2247 /* reparent the child */
2248 child->mParent = mParent;
2249 if (mParent)
2250 mParent->addDependentChild (child);
2251
2252 /* change the parent UUID in the image as well */
2253 RTUUID parentUuid, parentModUuid;
2254 vrc = VDIGetImageUUIDs (Utf8Str (mParent->asVDI()->mFilePathFull),
2255 &parentUuid, &parentModUuid, NULL, NULL);
2256 if (VBOX_FAILURE (vrc))
2257 {
2258 rc = setError (E_FAIL,
2259 tr ("Could not access the hard disk image '%ls' (%Vrc)"),
2260 mParent->asVDI()->mFilePathFull.raw(), vrc);
2261 break;
2262 }
2263 ComAssertBreak (mParent->id() == Guid (parentUuid), rc = E_FAIL);
2264 vrc = VDISetImageUUIDs (Utf8Str (child->mFilePathFull),
2265 NULL, NULL, &parentUuid, &parentModUuid);
2266 if (VBOX_FAILURE (vrc))
2267 {
2268 rc = setError (E_FAIL,
2269 tr ("Could not update parent UUID of the hard disk image "
2270 "'%ls' (%Vrc)"),
2271 child->mFilePathFull.raw(), vrc);
2272 break;
2273 }
2274
2275 /* detach child to avoid its uninit in #uninit() */
2276 removeDependentChild (child);
2277
2278 /* remove the busy flag */
2279 child->clearBusy();
2280 }
2281
2282 if (FAILED (rc))
2283 return rc;
2284 }
2285
2286 clearBusy();
2287
2288 return S_OK;
2289}
2290
2291/**
2292 * Deletes and recreates the differencing hard disk image from scratch.
2293 * The file name and UUID remain the same.
2294 */
2295HRESULT HVirtualDiskImage::wipeOutImage()
2296{
2297 AutoLock alock (this);
2298 CHECK_READY();
2299
2300 AssertReturn (isDifferencing(), E_FAIL);
2301 AssertReturn (mRegistered, E_FAIL);
2302 AssertReturn (mState >= Created, E_FAIL);
2303
2304 ComAssertMsgRet (mParent->storageType() == HardDiskStorageType_VirtualDiskImage,
2305 ("non-VDI storage types are not yet supported!"), E_FAIL);
2306
2307 Utf8Str filePathFull = mFilePathFull;
2308
2309 int vrc = RTFileDelete (filePathFull);
2310 if (VBOX_FAILURE (vrc))
2311 return setError (E_FAIL,
2312 tr ("Could not delete the image file '%s' (%Vrc)"),
2313 filePathFull.raw(), vrc);
2314
2315 vrc = VDICreateDifferenceImage (filePathFull,
2316 Utf8Str (mParent->asVDI()->mFilePathFull),
2317 NULL, NULL, NULL);
2318 /* update the UUID to correspond to the file name */
2319 if (VBOX_SUCCESS (vrc))
2320 vrc = VDISetImageUUIDs (filePathFull, mId, NULL, NULL, NULL);
2321
2322 if (VBOX_FAILURE (vrc))
2323 return setError (E_FAIL,
2324 tr ("Could not create a differencing hard disk '%s' (%Vrc)"),
2325 filePathFull.raw(), vrc);
2326
2327 return S_OK;
2328}
2329
2330// private methods
2331/////////////////////////////////////////////////////////////////////////////
2332
2333/**
2334 * Helper to set a new file path.
2335 * Resolves a path relatively to the Virtual Box home directory.
2336 *
2337 * @note
2338 * Must be called from under the object's lock!
2339 */
2340HRESULT HVirtualDiskImage::setFilePath (const BSTR aFilePath)
2341{
2342 if (aFilePath && *aFilePath)
2343 {
2344 /* get the full file name */
2345 char filePathFull [RTPATH_MAX];
2346 int vrc = RTPathAbsEx (mVirtualBox->homeDir(), Utf8Str (aFilePath),
2347 filePathFull, sizeof (filePathFull));
2348 if (VBOX_FAILURE (vrc))
2349 return setError (E_FAIL,
2350 tr ("Invalid image file path '%ls' (%Vrc)"), aFilePath, vrc);
2351
2352 mFilePath = aFilePath;
2353 mFilePathFull = filePathFull;
2354 }
2355 else
2356 {
2357 mFilePath.setNull();
2358 mFilePathFull.setNull();
2359 }
2360
2361 return S_OK;
2362}
2363
2364/**
2365 * Helper to query information about the VDI hard disk.
2366 *
2367 * @param aAccessError not used when NULL, otherwise see #getAccessible()
2368 *
2369 * @note Must be called from under the object's lock, only after
2370 * CHECK_BUSY_AND_READERS() succeeds.
2371 */
2372HRESULT HVirtualDiskImage::queryInformation (Bstr *aAccessError)
2373{
2374 AssertReturn (isLockedOnCurrentThread(), E_FAIL);
2375
2376 /* create a lock object to completely release it later */
2377 AutoLock alock (this);
2378
2379 AssertReturn (mStateCheckWaiters == 0, E_FAIL);
2380
2381 ComAssertRet (mState >= Created, E_FAIL);
2382
2383 HRESULT rc = S_OK;
2384 int vrc = VINF_SUCCESS;
2385
2386 /* lazily create a semaphore */
2387 vrc = RTSemEventMultiCreate (&mStateCheckSem);
2388 ComAssertRCRet (vrc, E_FAIL);
2389
2390 /* Reference the disk to prevent any concurrent modifications
2391 * after releasing the lock below (to unblock getters before
2392 * a lengthy operation). */
2393 addReader();
2394
2395 alock.leave();
2396
2397 /* VBoxVHDD management interface needs to be optimized: we're opening a
2398 * file three times in a raw to get three bits of information. */
2399
2400 Utf8Str filePath = mFilePathFull;
2401 Bstr errMsg;
2402
2403 do
2404 {
2405 /* check the image file */
2406 Guid id, parentId;
2407 vrc = VDICheckImage (filePath, NULL, NULL, NULL,
2408 id.ptr(), parentId.ptr(), NULL, 0);
2409
2410 if (VBOX_FAILURE (vrc))
2411 break;
2412
2413 if (!mId.isEmpty())
2414 {
2415 /* check that the actual UUID of the image matches the stored UUID */
2416 if (mId != id)
2417 {
2418 errMsg = Utf8StrFmt (
2419 tr ("Actual UUID {%Vuuid} of the hard disk image '%s' doesn't "
2420 "match UUID {%Vuuid} stored in the registry"),
2421 id.ptr(), filePath.raw(), mId.ptr());
2422 break;
2423 }
2424 }
2425 else
2426 {
2427 /* assgn an UUID read from the image file */
2428 mId = id;
2429 }
2430
2431 if (mParent)
2432 {
2433 /* check parent UUID */
2434 AutoLock parentLock (mParent);
2435 if (mParent->id() != parentId)
2436 {
2437 errMsg = Utf8StrFmt (
2438 tr ("UUID {%Vuuid} of the parent image '%ls' stored in "
2439 "the hard disk image file '%s' doesn't match "
2440 "UUID {%Vuuid} stored in the registry"),
2441 parentId.raw(), mParent->toString().raw(),
2442 filePath.raw(), mParent->id().raw());
2443 break;
2444 }
2445 }
2446 else if (!parentId.isEmpty())
2447 {
2448 errMsg = Utf8StrFmt (
2449 tr ("Hard disk image '%s' is a differencing image that is linked "
2450 "to a hard disk with UUID {%Vuuid} and cannot be used "
2451 "directly as a base hard disk"),
2452 filePath.raw(), parentId.raw());
2453 break;
2454 }
2455
2456 {
2457 RTFILE file = NIL_RTFILE;
2458 vrc = RTFileOpen (&file, filePath, RTFILE_O_READ);
2459 if (VBOX_SUCCESS (vrc))
2460 {
2461 uint64_t size = 0;
2462 vrc = RTFileGetSize (file, &size);
2463 if (VBOX_SUCCESS (vrc))
2464 mActualSize = size;
2465 RTFileClose (file);
2466 }
2467 if (VBOX_FAILURE (vrc))
2468 break;
2469 }
2470
2471 if (!mParent)
2472 {
2473 /* query logical size only for non-differencing images */
2474
2475 PVDIDISK disk = VDIDiskCreate();
2476 vrc = VDIDiskOpenImage (disk, Utf8Str (mFilePathFull),
2477 VDI_OPEN_FLAGS_READONLY);
2478 if (VBOX_SUCCESS (vrc))
2479 {
2480 uint64_t size = VDIDiskGetSize (disk);
2481 /* convert to MBytes */
2482 mSize = size / 1024 / 1024;
2483 }
2484
2485 VDIDiskDestroy (disk);
2486 if (VBOX_FAILURE (vrc))
2487 break;
2488 }
2489 }
2490 while (0);
2491
2492 /* enter the lock again */
2493 alock.enter();
2494
2495 /* remove the reference */
2496 releaseReader();
2497
2498 if (FAILED (rc) || VBOX_FAILURE (vrc) || !errMsg.isNull())
2499 {
2500 LogWarningFunc (("'%ls' is not accessible "
2501 "(rc=%08X, vrc=%Vrc, errMsg='%ls')\n",
2502 mFilePathFull.raw(), rc, vrc, errMsg.raw()));
2503
2504 if (aAccessError)
2505 {
2506 if (!errMsg.isNull())
2507 *aAccessError = errMsg;
2508 else if (VBOX_FAILURE (vrc))
2509 *aAccessError = Utf8StrFmt (
2510 tr ("Could not access hard disk image '%ls' (%Vrc)"),
2511 mFilePathFull.raw(), vrc);
2512 }
2513
2514 /* downgrade to not accessible */
2515 mState = Created;
2516 }
2517 else
2518 {
2519 if (aAccessError)
2520 aAccessError->setNull();
2521
2522 mState = Accessible;
2523 }
2524
2525 /* inform waiters if there are any */
2526 if (mStateCheckWaiters > 0)
2527 {
2528 RTSemEventMultiSignal (mStateCheckSem);
2529 }
2530 else
2531 {
2532 /* delete the semaphore ourselves */
2533 RTSemEventMultiDestroy (mStateCheckSem);
2534 mStateCheckSem = NIL_RTSEMEVENTMULTI;
2535 }
2536
2537 return rc;
2538}
2539
2540/**
2541 * Helper to create hard disk images.
2542 *
2543 * @param aSize size in MB
2544 * @param aDynamic dynamic or fixed image
2545 * @param aProgress address of IProgress pointer to return
2546 */
2547HRESULT HVirtualDiskImage::createImage (ULONG64 aSize, BOOL aDynamic,
2548 IProgress **aProgress)
2549{
2550 AutoLock alock (this);
2551
2552 CHECK_BUSY_AND_READERS();
2553
2554 if (mState != NotCreated)
2555 return setError (E_ACCESSDENIED,
2556 tr ("Hard disk image '%ls' is already created"),
2557 mFilePathFull.raw());
2558
2559 if (!mFilePathFull)
2560 return setError (E_ACCESSDENIED,
2561 tr ("Cannot create a hard disk image using an empty (null) file path"),
2562 mFilePathFull.raw());
2563
2564 /* first ensure the directory exists */
2565 {
2566 Utf8Str imageDir = mFilePathFull;
2567 RTPathStripFilename (imageDir.mutableRaw());
2568 if (!RTDirExists (imageDir))
2569 {
2570 int vrc = RTDirCreateFullPath (imageDir, 0777);
2571 if (VBOX_FAILURE (vrc))
2572 {
2573 return setError (E_FAIL,
2574 tr ("Could not create a directory '%s' "
2575 "to store the image file (%Vrc)"),
2576 imageDir.raw(), vrc);
2577 }
2578 }
2579 }
2580
2581 /* check whether the given file exists or not */
2582 RTFILE file;
2583 int vrc = RTFileOpen (&file, Utf8Str (mFilePathFull),
2584 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
2585 if (vrc != VERR_FILE_NOT_FOUND)
2586 {
2587 if (VBOX_SUCCESS (vrc))
2588 RTFileClose (file);
2589 switch(vrc)
2590 {
2591 case VINF_SUCCESS:
2592 return setError (E_FAIL,
2593 tr ("Image file '%ls' already exists"),
2594 mFilePathFull.raw());
2595
2596 default:
2597 return setError(E_FAIL,
2598 tr ("Invalid image file path '%ls' (%Vrc)"),
2599 mFilePathFull.raw(), vrc);
2600 }
2601 }
2602
2603 /* check VDI size limits */
2604 {
2605 HRESULT rc;
2606 ULONG64 maxVDISize;
2607 ComPtr <ISystemProperties> props;
2608 rc = mVirtualBox->COMGETTER(SystemProperties) (props.asOutParam());
2609 ComAssertComRCRet (rc, E_FAIL);
2610 rc = props->COMGETTER(MaxVDISize) (&maxVDISize);
2611 ComAssertComRCRet (rc, E_FAIL);
2612
2613 if (aSize < 1 || aSize > maxVDISize)
2614 return setError (E_INVALIDARG,
2615 tr ("Invalid VDI size: %llu MB (must be in range [1, %llu] MB)"),
2616 aSize, maxVDISize);
2617 }
2618
2619 HRESULT rc;
2620
2621 /* create a project object */
2622 ComObjPtr <Progress> progress;
2623 progress.createObject();
2624 {
2625 Bstr desc = aDynamic ? tr ("Creating a dynamically expanding hard disk")
2626 : tr ("Creating a fixed-size hard disk");
2627 rc = progress->init (mVirtualBox, (IVirtualDiskImage *) this, desc,
2628 FALSE /* aCancelable */);
2629 CheckComRCReturnRC (rc);
2630 }
2631
2632 /* mark as busy (being created)
2633 * (VDI task thread will unmark it) */
2634 setBusy();
2635
2636 /* fill in VDI task data */
2637 VDITask *task = new VDITask (aDynamic ? VDITask::CreateDynamic
2638 : VDITask::CreateStatic,
2639 this, progress);
2640 task->size = aSize;
2641 task->size *= 1024 * 1024; /* convert to bytes */
2642
2643 /* create the hard disk creation thread, pass operation data */
2644 vrc = RTThreadCreate (NULL, VDITaskThread, (void *) task, 0,
2645 RTTHREADTYPE_MAIN_HEAVY_WORKER, 0, "VDITask");
2646 ComAssertMsgRC (vrc, ("Could not create a thread (%Vrc)", vrc));
2647 if (VBOX_FAILURE (vrc))
2648 {
2649 clearBusy();
2650 delete task;
2651 rc = E_FAIL;
2652 }
2653 else
2654 {
2655 /* get one interface for the caller */
2656 progress.queryInterfaceTo (aProgress);
2657 }
2658
2659 return rc;
2660}
2661
2662/* static */
2663DECLCALLBACK(int) HVirtualDiskImage::VDITaskThread (RTTHREAD thread, void *pvUser)
2664{
2665 VDITask *task = static_cast <VDITask *> (pvUser);
2666 AssertReturn (task, VERR_GENERAL_FAILURE);
2667
2668 LogFlowFunc (("operation=%d, size=%llu\n", task->operation, task->size));
2669
2670 VDIIMAGETYPE type = (VDIIMAGETYPE) 0;
2671
2672 switch (task->operation)
2673 {
2674 case VDITask::CreateDynamic: type = VDI_IMAGE_TYPE_NORMAL; break;
2675 case VDITask::CreateStatic: type = VDI_IMAGE_TYPE_FIXED; break;
2676 case VDITask::CloneToImage: break;
2677 default: AssertFailedReturn (VERR_GENERAL_FAILURE); break;
2678 }
2679
2680 HRESULT rc = S_OK;
2681 Utf8Str errorMsg;
2682
2683 bool deleteTarget = true;
2684
2685 if (task->operation == VDITask::CloneToImage)
2686 {
2687 Assert (!task->vdi->id().isEmpty());
2688 /// @todo (dmik) check locks
2689 AutoLock sourceLock (task->source);
2690 rc = task->source->cloneToImage (task->vdi->id(),
2691 Utf8Str (task->vdi->filePathFull()),
2692 task->progress, deleteTarget);
2693
2694 /* release reader added in HardDisk::CloneToImage() */
2695 task->source->releaseReader();
2696 }
2697 else
2698 {
2699 int vrc = VDICreateBaseImage (Utf8Str (task->vdi->filePathFull()),
2700 type, task->size,
2701 Utf8Str (task->vdi->mDescription),
2702 progressCallback,
2703 static_cast <Progress *> (task->progress));
2704
2705 /* We don't want to delete existing user files */
2706 if (vrc == VERR_ALREADY_EXISTS)
2707 deleteTarget = false;
2708
2709 if (VBOX_SUCCESS (vrc) && task->vdi->id())
2710 {
2711 /* we have a non-null UUID, update the created image */
2712 vrc = VDISetImageUUIDs (Utf8Str (task->vdi->filePathFull()),
2713 task->vdi->id().raw(), NULL, NULL, NULL);
2714 }
2715
2716 if (VBOX_FAILURE (vrc))
2717 {
2718 errorMsg = Utf8StrFmt (
2719 tr ("Falied to create a hard disk image '%ls' (%Vrc)"),
2720 task->vdi->filePathFull().raw(), vrc);
2721 rc = E_FAIL;
2722 }
2723 }
2724
2725 LogFlowFunc (("rc=%08X\n", rc));
2726
2727 AutoLock alock (task->vdi);
2728
2729 /* clear busy set in in HardDisk::CloneToImage() or
2730 * in HVirtualDiskImage::createImage() */
2731 task->vdi->clearBusy();
2732
2733 if (SUCCEEDED (rc))
2734 {
2735 task->vdi->mState = HVirtualDiskImage::Created;
2736 /* update VDI data fields */
2737 Bstr errMsg;
2738 rc = task->vdi->queryInformation (&errMsg);
2739 /* we want to deliver the access check result to the caller
2740 * immediately, before he calls HardDisk::GetAccssible() himself. */
2741 if (SUCCEEDED (rc) && !errMsg.isNull())
2742 task->progress->notifyCompleteBstr (
2743 E_FAIL, COM_IIDOF (IVirtualDiskImage), getComponentName(),
2744 errMsg);
2745 else
2746 task->progress->notifyComplete (rc);
2747 }
2748 else
2749 {
2750 /* delete the target file so we don't have orphaned files */
2751 if (deleteTarget)
2752 RTFileDelete(Utf8Str (task->vdi->filePathFull()));
2753
2754 task->vdi->mState = HVirtualDiskImage::NotCreated;
2755 /* complete the progress object */
2756 if (errorMsg)
2757 task->progress->notifyComplete (
2758 E_FAIL, COM_IIDOF (IVirtualDiskImage), getComponentName(),
2759 errorMsg);
2760 else
2761 task->progress->notifyComplete (rc);
2762 }
2763
2764 delete task;
2765
2766 return VINF_SUCCESS;
2767}
2768
2769////////////////////////////////////////////////////////////////////////////////
2770// HISCSIHardDisk class
2771////////////////////////////////////////////////////////////////////////////////
2772
2773// constructor / destructor
2774////////////////////////////////////////////////////////////////////////////////
2775
2776HRESULT HISCSIHardDisk::FinalConstruct()
2777{
2778 HRESULT rc = HardDisk::FinalConstruct();
2779 if (FAILED (rc))
2780 return rc;
2781
2782 mSize = 0;
2783 mActualSize = 0;
2784
2785 mPort = 0;
2786 mLun = 0;
2787
2788 return S_OK;
2789}
2790
2791void HISCSIHardDisk::FinalRelease()
2792{
2793 HardDisk::FinalRelease();
2794}
2795
2796// public initializer/uninitializer for internal purposes only
2797////////////////////////////////////////////////////////////////////////////////
2798
2799// public methods for internal purposes only
2800/////////////////////////////////////////////////////////////////////////////
2801
2802/**
2803 * Initializes the iSCSI hard disk object by reading its properties from
2804 * the given configuration node. The created hard disk will be marked as
2805 * registered on success.
2806 *
2807 * @param aHDNode <HardDisk> node
2808 * @param aVDINod <ISCSIHardDisk> node
2809 */
2810HRESULT HISCSIHardDisk::init (VirtualBox *aVirtualBox,
2811 CFGNODE aHDNode, CFGNODE aISCSINode)
2812{
2813 LogFlowThisFunc (("aHDNode=%p, aISCSINode=%p\n", aHDNode, aISCSINode));
2814
2815 AssertReturn (aHDNode && aISCSINode, E_FAIL);
2816
2817 AutoLock alock (this);
2818 ComAssertRet (!isReady(), E_UNEXPECTED);
2819
2820 mStorageType = HardDiskStorageType_ISCSIHardDisk;
2821
2822 HRESULT rc = S_OK;
2823
2824 do
2825 {
2826 rc = protectedInit (aVirtualBox, NULL);
2827 CheckComRCBreakRC (rc);
2828
2829 /* set ready to let protectedUninit() be called on failure */
2830 setReady (true);
2831
2832 /* server (required) */
2833 CFGLDRQueryBSTR (aISCSINode, "server", mServer.asOutParam());
2834 /* target (required) */
2835 CFGLDRQueryBSTR (aISCSINode, "target", mTarget.asOutParam());
2836
2837 /* port (optional) */
2838 CFGLDRQueryUInt16 (aISCSINode, "port", &mPort);
2839 /* lun (optional) */
2840 CFGLDRQueryUInt64 (aISCSINode, "lun", &mLun);
2841 /* userName (optional) */
2842 CFGLDRQueryBSTR (aISCSINode, "userName", mUserName.asOutParam());
2843 /* password (optional) */
2844 CFGLDRQueryBSTR (aISCSINode, "password", mPassword.asOutParam());
2845
2846 LogFlowThisFunc (("'iscsi:%ls:%hu@%ls/%ls:%llu'\n",
2847 mServer.raw(), mPort, mUserName.raw(), mTarget.raw(),
2848 mLun));
2849
2850 /* load basic settings and children */
2851 rc = loadSettings (aHDNode);
2852 CheckComRCBreakRC (rc);
2853
2854 if (mType != HardDiskType_WritethroughHardDisk)
2855 {
2856 rc = setError (E_FAIL,
2857 tr ("Currently, non-Writethrough iSCSI hard disks are not "
2858 "allowed ('%ls')"),
2859 toString().raw());
2860 break;
2861 }
2862
2863 mRegistered = TRUE;
2864 }
2865 while (0);
2866
2867 if (FAILED (rc))
2868 uninit();
2869
2870 return rc;
2871}
2872
2873/**
2874 * Initializes the iSCSI hard disk object using default values for all
2875 * properties. The created hard disk will NOT be marked as registered.
2876 */
2877HRESULT HISCSIHardDisk::init (VirtualBox *aVirtualBox)
2878{
2879 LogFlowThisFunc (("\n"));
2880
2881 AutoLock alock (this);
2882 ComAssertRet (!isReady(), E_UNEXPECTED);
2883
2884 mStorageType = HardDiskStorageType_ISCSIHardDisk;
2885
2886 HRESULT rc = S_OK;
2887
2888 do
2889 {
2890 rc = protectedInit (aVirtualBox, NULL);
2891 CheckComRCBreakRC (rc);
2892
2893 /* set ready to let protectedUninit() be called on failure */
2894 setReady (true);
2895
2896 /* we have to generate a new UUID */
2897 mId.create();
2898 /* currently, all iSCSI hard disks are writethrough */
2899 mType = HardDiskType_WritethroughHardDisk;
2900 mRegistered = FALSE;
2901 }
2902 while (0);
2903
2904 if (FAILED (rc))
2905 uninit();
2906
2907 return rc;
2908}
2909
2910/**
2911 * Uninitializes the instance and sets the ready flag to FALSE.
2912 * Called either from FinalRelease(), by the parent when it gets destroyed,
2913 * or by a third party when it decides this object is no more valid.
2914 */
2915void HISCSIHardDisk::uninit()
2916{
2917 LogFlowThisFunc (("\n"));
2918
2919 AutoLock alock (this);
2920 if (!isReady())
2921 return;
2922
2923 HardDisk::protectedUninit (alock);
2924}
2925
2926// IHardDisk properties
2927////////////////////////////////////////////////////////////////////////////////
2928
2929STDMETHODIMP HISCSIHardDisk::COMGETTER(Description) (BSTR *aDescription)
2930{
2931 if (!aDescription)
2932 return E_POINTER;
2933
2934 AutoLock alock (this);
2935 CHECK_READY();
2936
2937 mDescription.cloneTo (aDescription);
2938 return S_OK;
2939}
2940
2941STDMETHODIMP HISCSIHardDisk::COMSETTER(Description) (INPTR BSTR aDescription)
2942{
2943 AutoLock alock (this);
2944 CHECK_READY();
2945
2946 CHECK_BUSY_AND_READERS();
2947
2948 if (mDescription != aDescription)
2949 {
2950 mDescription = aDescription;
2951 if (mRegistered)
2952 return mVirtualBox->saveSettings();
2953 }
2954
2955 return S_OK;
2956}
2957
2958STDMETHODIMP HISCSIHardDisk::COMGETTER(Size) (ULONG64 *aSize)
2959{
2960 if (!aSize)
2961 return E_POINTER;
2962
2963 AutoLock alock (this);
2964 CHECK_READY();
2965
2966 *aSize = mSize;
2967 return S_OK;
2968}
2969
2970STDMETHODIMP HISCSIHardDisk::COMGETTER(ActualSize) (ULONG64 *aActualSize)
2971{
2972 if (!aActualSize)
2973 return E_POINTER;
2974
2975 AutoLock alock (this);
2976 CHECK_READY();
2977
2978 *aActualSize = mActualSize;
2979 return S_OK;
2980}
2981
2982// IISCSIHardDisk properties
2983////////////////////////////////////////////////////////////////////////////////
2984
2985STDMETHODIMP HISCSIHardDisk::COMGETTER(Server) (BSTR *aServer)
2986{
2987 if (!aServer)
2988 return E_POINTER;
2989
2990 AutoLock alock (this);
2991 CHECK_READY();
2992
2993 mServer.cloneTo (aServer);
2994 return S_OK;
2995}
2996
2997STDMETHODIMP HISCSIHardDisk::COMSETTER(Server) (INPTR BSTR aServer)
2998{
2999 if (!aServer || !*aServer)
3000 return E_INVALIDARG;
3001
3002 AutoLock alock (this);
3003 CHECK_READY();
3004
3005 CHECK_BUSY_AND_READERS();
3006
3007 if (mServer != aServer)
3008 {
3009 mServer = aServer;
3010 if (mRegistered)
3011 return mVirtualBox->saveSettings();
3012 }
3013
3014 return S_OK;
3015}
3016
3017STDMETHODIMP HISCSIHardDisk::COMGETTER(Port) (USHORT *aPort)
3018{
3019 if (!aPort)
3020 return E_POINTER;
3021
3022 AutoLock alock (this);
3023 CHECK_READY();
3024
3025 *aPort = mPort;
3026 return S_OK;
3027}
3028
3029STDMETHODIMP HISCSIHardDisk::COMSETTER(Port) (USHORT aPort)
3030{
3031 AutoLock alock (this);
3032 CHECK_READY();
3033
3034 CHECK_BUSY_AND_READERS();
3035
3036 if (mPort != aPort)
3037 {
3038 mPort = aPort;
3039 if (mRegistered)
3040 return mVirtualBox->saveSettings();
3041 }
3042
3043 return S_OK;
3044}
3045
3046STDMETHODIMP HISCSIHardDisk::COMGETTER(Target) (BSTR *aTarget)
3047{
3048 if (!aTarget)
3049 return E_POINTER;
3050
3051 AutoLock alock (this);
3052 CHECK_READY();
3053
3054 mTarget.cloneTo (aTarget);
3055 return S_OK;
3056}
3057
3058STDMETHODIMP HISCSIHardDisk::COMSETTER(Target) (INPTR BSTR aTarget)
3059{
3060 if (!aTarget || !*aTarget)
3061 return E_INVALIDARG;
3062
3063 AutoLock alock (this);
3064 CHECK_READY();
3065
3066 CHECK_BUSY_AND_READERS();
3067
3068 if (mTarget != aTarget)
3069 {
3070 mTarget = aTarget;
3071 if (mRegistered)
3072 return mVirtualBox->saveSettings();
3073 }
3074
3075 return S_OK;
3076}
3077
3078STDMETHODIMP HISCSIHardDisk::COMGETTER(Lun) (ULONG64 *aLun)
3079{
3080 if (!aLun)
3081 return E_POINTER;
3082
3083 AutoLock alock (this);
3084 CHECK_READY();
3085
3086 *aLun = mLun;
3087 return S_OK;
3088}
3089
3090STDMETHODIMP HISCSIHardDisk::COMSETTER(Lun) (ULONG64 aLun)
3091{
3092 AutoLock alock (this);
3093 CHECK_READY();
3094
3095 CHECK_BUSY_AND_READERS();
3096
3097 if (mLun != aLun)
3098 {
3099 mLun = aLun;
3100 if (mRegistered)
3101 return mVirtualBox->saveSettings();
3102 }
3103
3104 return S_OK;
3105}
3106
3107STDMETHODIMP HISCSIHardDisk::COMGETTER(UserName) (BSTR *aUserName)
3108{
3109 if (!aUserName)
3110 return E_POINTER;
3111
3112 AutoLock alock (this);
3113 CHECK_READY();
3114
3115 mUserName.cloneTo (aUserName);
3116 return S_OK;
3117}
3118
3119STDMETHODIMP HISCSIHardDisk::COMSETTER(UserName) (INPTR BSTR aUserName)
3120{
3121 AutoLock alock (this);
3122 CHECK_READY();
3123
3124 CHECK_BUSY_AND_READERS();
3125
3126 if (mUserName != aUserName)
3127 {
3128 mUserName = aUserName;
3129 if (mRegistered)
3130 return mVirtualBox->saveSettings();
3131 }
3132
3133 return S_OK;
3134}
3135
3136STDMETHODIMP HISCSIHardDisk::COMGETTER(Password) (BSTR *aPassword)
3137{
3138 if (!aPassword)
3139 return E_POINTER;
3140
3141 AutoLock alock (this);
3142 CHECK_READY();
3143
3144 mPassword.cloneTo (aPassword);
3145 return S_OK;
3146}
3147
3148STDMETHODIMP HISCSIHardDisk::COMSETTER(Password) (INPTR BSTR aPassword)
3149{
3150 AutoLock alock (this);
3151 CHECK_READY();
3152
3153 CHECK_BUSY_AND_READERS();
3154
3155 if (mPassword != aPassword)
3156 {
3157 mPassword = aPassword;
3158 if (mRegistered)
3159 return mVirtualBox->saveSettings();
3160 }
3161
3162 return S_OK;
3163}
3164
3165// public/protected methods for internal purposes only
3166/////////////////////////////////////////////////////////////////////////////
3167
3168/**
3169 * Attempts to mark the hard disk as registered.
3170 * Only VirtualBox can call this method.
3171 */
3172HRESULT HISCSIHardDisk::trySetRegistered (BOOL aRegistered)
3173{
3174 AutoLock alock (this);
3175 CHECK_READY();
3176
3177 if (aRegistered)
3178 {
3179 if (mServer.isEmpty() || mTarget.isEmpty())
3180 return setError (E_FAIL,
3181 tr ("iSCSI Hard disk has no server or target defined"));
3182 }
3183 else
3184 {
3185 }
3186
3187 return HardDisk::trySetRegistered (aRegistered);
3188}
3189
3190/**
3191 * Checks accessibility of this iSCSI hard disk.
3192 */
3193HRESULT HISCSIHardDisk::getAccessible (Bstr &aAccessError)
3194{
3195 AutoLock alock (this);
3196 CHECK_READY();
3197
3198 /* check the basic accessibility */
3199 HRESULT rc = getBaseAccessible (aAccessError);
3200 if (FAILED (rc) || !aAccessError.isNull())
3201 return rc;
3202
3203 return queryInformation (aAccessError);
3204}
3205
3206/**
3207 * Saves hard disk settings to the specified storage node and saves
3208 * all children to the specified hard disk node
3209 *
3210 * @param aHDNode <HardDisk>
3211 * @param aStorageNode <ISCSIHardDisk> node
3212 */
3213HRESULT HISCSIHardDisk::saveSettings (CFGNODE aHDNode, CFGNODE aStorageNode)
3214{
3215 AssertReturn (aHDNode && aStorageNode, E_FAIL);
3216
3217 AutoLock alock (this);
3218 CHECK_READY();
3219
3220 /* server (required) */
3221 CFGLDRSetBSTR (aStorageNode, "server", mServer);
3222 /* target (required) */
3223 CFGLDRSetBSTR (aStorageNode, "target", mTarget);
3224
3225 /* port (optional) */
3226 if (mPort != 0)
3227 CFGLDRSetUInt16 (aStorageNode, "port", mPort);
3228 else
3229 CFGLDRDeleteAttribute (aStorageNode, "port");
3230 /* lun (optional) */
3231 if (mLun != 0)
3232 CFGLDRSetUInt64 (aStorageNode, "lun", mLun);
3233 else
3234 CFGLDRDeleteAttribute (aStorageNode, "lun");
3235 /* userName (optional) */
3236 if (!mUserName.isNull())
3237 CFGLDRSetBSTR (aStorageNode, "userName", mUserName);
3238 else
3239 CFGLDRDeleteAttribute (aStorageNode, "userName");
3240 /* password (optional) */
3241 if (!mPassword.isNull())
3242 CFGLDRSetBSTR (aStorageNode, "password", mPassword);
3243 else
3244 CFGLDRDeleteAttribute (aStorageNode, "password");
3245
3246 /* save basic settings and children */
3247 return HardDisk::saveSettings (aHDNode);
3248}
3249
3250/**
3251 * Returns the string representation of this hard disk.
3252 * When \a aShort is false, returns the full image file path.
3253 * Otherwise, returns the image file name only.
3254 *
3255 * @param aShort if true, a short representation is returned
3256 */
3257Bstr HISCSIHardDisk::toString (bool aShort /* = false */)
3258{
3259 AutoLock alock (this);
3260
3261 Bstr str;
3262 if (!aShort)
3263 {
3264 /* the format is iscsi:[<user@>]<server>[:<port>]/<target>[:<lun>] */
3265 str = Utf8StrFmt ("iscsi:%s%ls%s/%ls%s",
3266 !mUserName.isNull() ? Utf8StrFmt ("%ls@", mUserName.raw()).raw() : "",
3267 mServer.raw(),
3268 mPort != 0 ? Utf8StrFmt (":%hu", mPort).raw() : "",
3269 mTarget.raw(),
3270 mLun != 0 ? Utf8StrFmt (":%llu", mLun).raw() : "");
3271 }
3272 else
3273 {
3274 str = Utf8StrFmt ("%ls%s",
3275 mTarget.raw(),
3276 mLun != 0 ? Utf8StrFmt (":%llu", mLun).raw() : "");
3277 }
3278
3279 return str;
3280}
3281
3282/**
3283 * Creates a clone of this hard disk by storing hard disk data in the given
3284 * VDI file.
3285 *
3286 * If the operation fails, @a aDeleteTarget will be set to @c true unless the
3287 * failure happened because the target file already existed.
3288 *
3289 * @param aId UUID to assign to the created image.
3290 * @param aTargetPath VDI file where the cloned image is to be to stored.
3291 * @param aProgress progress object to run during operation.
3292 * @param aDeleteTarget Whether it is recommended to delete target on
3293 * failure or not.
3294 */
3295HRESULT
3296HISCSIHardDisk::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath,
3297 Progress *aProgress, bool &aDeleteTarget)
3298{
3299 ComAssertMsgFailed (("Not implemented"));
3300 return E_NOTIMPL;
3301
3302// AssertReturn (isBusy() == false, E_FAIL);
3303// addReader();
3304// releaseReader();
3305}
3306
3307/**
3308 * Creates a new differencing image for this hard disk with the given
3309 * VDI file name.
3310 *
3311 * @param aId UUID to assign to the created image
3312 * @param aTargetPath VDI file where to store the created differencing image
3313 * @param aProgress progress object to run during operation
3314 * (can be NULL)
3315 */
3316HRESULT
3317HISCSIHardDisk::createDiffImage (const Guid &aId, const Utf8Str &aTargetPath,
3318 Progress *aProgress)
3319{
3320 ComAssertMsgFailed (("Not implemented"));
3321 return E_NOTIMPL;
3322
3323// AssertReturn (isBusy() == false, E_FAIL);
3324// addReader();
3325// releaseReader();
3326}
3327
3328// private methods
3329/////////////////////////////////////////////////////////////////////////////
3330
3331/**
3332 * Helper to query information about the iSCSI hard disk.
3333 *
3334 * @param aAccessError see #getAccessible()
3335 * @note
3336 * Must be called from under the object's lock!
3337 */
3338HRESULT HISCSIHardDisk::queryInformation (Bstr &aAccessError)
3339{
3340 /// @todo (dmik) query info about this iSCSI disk,
3341 // set mSize and mActualSize,
3342 // or set aAccessError in case of failure
3343
3344 aAccessError.setNull();
3345 return S_OK;
3346}
3347
3348////////////////////////////////////////////////////////////////////////////////
3349// HVMDKImage class
3350////////////////////////////////////////////////////////////////////////////////
3351
3352// constructor / destructor
3353////////////////////////////////////////////////////////////////////////////////
3354
3355HRESULT HVMDKImage::FinalConstruct()
3356{
3357 HRESULT rc = HardDisk::FinalConstruct();
3358 if (FAILED (rc))
3359 return rc;
3360
3361 mState = NotCreated;
3362
3363 mStateCheckSem = NIL_RTSEMEVENTMULTI;
3364 mStateCheckWaiters = 0;
3365
3366 mSize = 0;
3367 mActualSize = 0;
3368
3369 /* initialize the container */
3370 int vrc = VDCreate ("VMDK", VDError, this, &mContainer);
3371 ComAssertRCRet (vrc, E_FAIL);
3372
3373 return S_OK;
3374}
3375
3376void HVMDKImage::FinalRelease()
3377{
3378 if (mContainer != NULL)
3379 VDDestroy (mContainer);
3380
3381 HardDisk::FinalRelease();
3382}
3383
3384// public initializer/uninitializer for internal purposes only
3385////////////////////////////////////////////////////////////////////////////////
3386
3387// public methods for internal purposes only
3388/////////////////////////////////////////////////////////////////////////////
3389
3390/**
3391 * Initializes the VMDK hard disk object by reading its properties from
3392 * the given configuration node. The created hard disk will be marked as
3393 * registered on success.
3394 *
3395 * @param aHDNode <HardDisk> node
3396 * @param aVMDKNode <VirtualDiskImage> node
3397 */
3398HRESULT HVMDKImage::init (VirtualBox *aVirtualBox, HardDisk *aParent,
3399 CFGNODE aHDNode, CFGNODE aVMDKNode)
3400{
3401 LogFlowThisFunc (("aHDNode=%p, aVMDKNode=%p\n", aHDNode, aVMDKNode));
3402
3403 AssertReturn (aHDNode && aVMDKNode, E_FAIL);
3404
3405 AutoLock alock (this);
3406 ComAssertRet (!isReady(), E_UNEXPECTED);
3407
3408 mStorageType = HardDiskStorageType_VMDKImage;
3409
3410 HRESULT rc = S_OK;
3411
3412 do
3413 {
3414 rc = protectedInit (aVirtualBox, aParent);
3415 CheckComRCBreakRC (rc);
3416
3417 /* set ready to let protectedUninit() be called on failure */
3418 setReady (true);
3419
3420 /* filePath (required) */
3421 Bstr filePath;
3422 CFGLDRQueryBSTR (aVMDKNode, "filePath", filePath.asOutParam());
3423
3424 rc = setFilePath (filePath);
3425 CheckComRCBreakRC (rc);
3426
3427 LogFlowThisFunc (("'%ls'\n", mFilePathFull.raw()));
3428
3429 /* load basic settings and children */
3430 rc = loadSettings (aHDNode);
3431 CheckComRCBreakRC (rc);
3432
3433 if (mType != HardDiskType_WritethroughHardDisk)
3434 {
3435 rc = setError (E_FAIL,
3436 tr ("Currently, non-Writethrough VMDK images are not "
3437 "allowed ('%ls')"),
3438 toString().raw());
3439 break;
3440 }
3441
3442 mState = Created;
3443 mRegistered = TRUE;
3444
3445 /* Don't call queryInformation() for registered hard disks to
3446 * prevent the calling thread (i.e. the VirtualBox server startup
3447 * thread) from an unexpected freeze. The vital mId property (UUID)
3448 * is read from the registry file in loadSettings(). To get the rest,
3449 * the user will have to call COMGETTER(Accessible) manually. */
3450 }
3451 while (0);
3452
3453 if (FAILED (rc))
3454 uninit();
3455
3456 return rc;
3457}
3458
3459/**
3460 * Initializes the VMDK hard disk object using the given image file name.
3461 *
3462 * @param aVirtualBox VirtualBox parent.
3463 * @param aParent Currently, must always be @c NULL.
3464 * @param aFilePath Path to the image file, or @c NULL to create an
3465 * image-less object.
3466 * @param aRegistered Whether to mark this disk as registered or not
3467 * (ignored when @a aFilePath is @c NULL, assuming @c FALSE)
3468 */
3469HRESULT HVMDKImage::init (VirtualBox *aVirtualBox, HardDisk *aParent,
3470 const BSTR aFilePath, BOOL aRegistered /* = FALSE */)
3471{
3472 LogFlowThisFunc (("aFilePath='%ls', aRegistered=%d\n", aFilePath, aRegistered));
3473
3474 AssertReturn (aParent == NULL, E_FAIL);
3475
3476 AutoLock alock (this);
3477 ComAssertRet (!isReady(), E_UNEXPECTED);
3478
3479 mStorageType = HardDiskStorageType_VMDKImage;
3480
3481 HRESULT rc = S_OK;
3482
3483 do
3484 {
3485 rc = protectedInit (aVirtualBox, aParent);
3486 CheckComRCBreakRC (rc);
3487
3488 /* set ready to let protectedUninit() be called on failure */
3489 setReady (true);
3490
3491 rc = setFilePath (aFilePath);
3492 CheckComRCBreakRC (rc);
3493
3494 /* currently, all VMDK hard disks are writethrough */
3495 mType = HardDiskType_WritethroughHardDisk;
3496
3497 Assert (mId.isEmpty());
3498
3499 if (aFilePath && *aFilePath)
3500 {
3501 mRegistered = aRegistered;
3502 mState = Created;
3503
3504 /* Call queryInformation() anyway (even if it will block), because
3505 * it is the only way to get the UUID of the existing VDI and
3506 * initialize the vital mId property. */
3507 Bstr errMsg;
3508 rc = queryInformation (&errMsg);
3509 if (SUCCEEDED (rc))
3510 {
3511 /* We are constructing a new HVirtualDiskImage object. If there
3512 * is a fatal accessibility error (we cannot read image UUID),
3513 * we have to fail. We do so even on non-fatal errors as well,
3514 * because it's not worth to keep going with the inaccessible
3515 * image from the very beginning (when nothing else depends on
3516 * it yet). */
3517 if (!errMsg.isNull())
3518 rc = setErrorBstr (E_FAIL, errMsg);
3519 }
3520 }
3521 else
3522 {
3523 mRegistered = FALSE;
3524 mState = NotCreated;
3525 mId.create();
3526 }
3527 }
3528 while (0);
3529
3530 if (FAILED (rc))
3531 uninit();
3532
3533 return rc;
3534}
3535
3536/**
3537 * Uninitializes the instance and sets the ready flag to FALSE.
3538 * Called either from FinalRelease(), by the parent when it gets destroyed,
3539 * or by a third party when it decides this object is no more valid.
3540 */
3541void HVMDKImage::uninit()
3542{
3543 LogFlowThisFunc (("\n"));
3544
3545 AutoLock alock (this);
3546 if (!isReady())
3547 return;
3548
3549 HardDisk::protectedUninit (alock);
3550}
3551
3552// IHardDisk properties
3553////////////////////////////////////////////////////////////////////////////////
3554
3555STDMETHODIMP HVMDKImage::COMGETTER(Description) (BSTR *aDescription)
3556{
3557 if (!aDescription)
3558 return E_POINTER;
3559
3560 AutoLock alock (this);
3561 CHECK_READY();
3562
3563 mDescription.cloneTo (aDescription);
3564 return S_OK;
3565}
3566
3567STDMETHODIMP HVMDKImage::COMSETTER(Description) (INPTR BSTR aDescription)
3568{
3569 AutoLock alock (this);
3570 CHECK_READY();
3571
3572 CHECK_BUSY_AND_READERS();
3573
3574 return E_NOTIMPL;
3575
3576/// @todo (r=dmik) implement
3577//
3578// if (mState >= Created)
3579// {
3580// int vrc = VDISetImageComment (Utf8Str (mFilePathFull), Utf8Str (aDescription));
3581// if (VBOX_FAILURE (vrc))
3582// return setError (E_FAIL,
3583// tr ("Could not change the description of the VDI hard disk '%ls' "
3584// "(%Vrc)"),
3585// toString().raw(), vrc);
3586// }
3587//
3588// mDescription = aDescription;
3589// return S_OK;
3590}
3591
3592STDMETHODIMP HVMDKImage::COMGETTER(Size) (ULONG64 *aSize)
3593{
3594 if (!aSize)
3595 return E_POINTER;
3596
3597 AutoLock alock (this);
3598 CHECK_READY();
3599
3600/// @todo (r=dmik) will need this if we add suppord for differencing VMDKs
3601//
3602// /* only a non-differencing image knows the logical size */
3603// if (isDifferencing())
3604// return root()->COMGETTER(Size) (aSize);
3605
3606 *aSize = mSize;
3607 return S_OK;
3608}
3609
3610STDMETHODIMP HVMDKImage::COMGETTER(ActualSize) (ULONG64 *aActualSize)
3611{
3612 if (!aActualSize)
3613 return E_POINTER;
3614
3615 AutoLock alock (this);
3616 CHECK_READY();
3617
3618 *aActualSize = mActualSize;
3619 return S_OK;
3620}
3621
3622// IVirtualDiskImage properties
3623////////////////////////////////////////////////////////////////////////////////
3624
3625STDMETHODIMP HVMDKImage::COMGETTER(FilePath) (BSTR *aFilePath)
3626{
3627 if (!aFilePath)
3628 return E_POINTER;
3629
3630 AutoLock alock (this);
3631 CHECK_READY();
3632
3633 mFilePathFull.cloneTo (aFilePath);
3634 return S_OK;
3635}
3636
3637STDMETHODIMP HVMDKImage::COMSETTER(FilePath) (INPTR BSTR aFilePath)
3638{
3639 AutoLock alock (this);
3640 CHECK_READY();
3641
3642 if (mState != NotCreated)
3643 return setError (E_ACCESSDENIED,
3644 tr ("Cannot change the file path of the existing hard disk '%ls'"),
3645 toString().raw());
3646
3647 CHECK_BUSY_AND_READERS();
3648
3649 /* append the default path if only a name is given */
3650 Bstr path = aFilePath;
3651 if (aFilePath && *aFilePath)
3652 {
3653 Utf8Str fp = aFilePath;
3654 if (!RTPathHavePath (fp))
3655 {
3656 AutoReaderLock propsLock (mVirtualBox->systemProperties());
3657 path = Utf8StrFmt ("%ls%c%s",
3658 mVirtualBox->systemProperties()->defaultVDIFolder().raw(),
3659 RTPATH_DELIMITER,
3660 fp.raw());
3661 }
3662 }
3663
3664 return setFilePath (path);
3665}
3666
3667STDMETHODIMP HVMDKImage::COMGETTER(Created) (BOOL *aCreated)
3668{
3669 if (!aCreated)
3670 return E_POINTER;
3671
3672 AutoLock alock (this);
3673 CHECK_READY();
3674
3675 *aCreated = mState >= Created;
3676 return S_OK;
3677}
3678
3679// IVMDKImage methods
3680/////////////////////////////////////////////////////////////////////////////
3681
3682STDMETHODIMP HVMDKImage::CreateDynamicImage (ULONG64 aSize, IProgress **aProgress)
3683{
3684 if (!aProgress)
3685 return E_POINTER;
3686
3687 AutoLock alock (this);
3688 CHECK_READY();
3689
3690 return createImage (aSize, TRUE /* aDynamic */, aProgress);
3691}
3692
3693STDMETHODIMP HVMDKImage::CreateFixedImage (ULONG64 aSize, IProgress **aProgress)
3694{
3695 if (!aProgress)
3696 return E_POINTER;
3697
3698 AutoLock alock (this);
3699 CHECK_READY();
3700
3701 return createImage (aSize, FALSE /* aDynamic */, aProgress);
3702}
3703
3704STDMETHODIMP HVMDKImage::DeleteImage()
3705{
3706 AutoLock alock (this);
3707 CHECK_READY();
3708 CHECK_BUSY_AND_READERS();
3709
3710 return E_NOTIMPL;
3711
3712/// @todo (r=dmik) later
3713// We will need to parse the file in order to delete all related delta and
3714// sparse images etc. We may also want to obey the .vmdk.lck file
3715// which is (as far as I understood) created when the VMware VM is
3716// running or saved etc.
3717//
3718// if (mRegistered)
3719// return setError (E_ACCESSDENIED,
3720// tr ("Cannot delete an image of the registered hard disk image '%ls"),
3721// mFilePathFull.raw());
3722// if (mState == NotCreated)
3723// return setError (E_FAIL,
3724// tr ("Hard disk image has been already deleted or never created"));
3725//
3726// int vrc = RTFileDelete (Utf8Str (mFilePathFull));
3727// if (VBOX_FAILURE (vrc))
3728// return setError (E_FAIL, tr ("Could not delete the image file '%ls' (%Vrc)"),
3729// mFilePathFull.raw(), vrc);
3730//
3731// mState = NotCreated;
3732//
3733// /* reset the fields */
3734// mSize = 0;
3735// mActualSize = 0;
3736//
3737// return S_OK;
3738}
3739
3740// public/protected methods for internal purposes only
3741/////////////////////////////////////////////////////////////////////////////
3742
3743/**
3744 * Attempts to mark the hard disk as registered.
3745 * Only VirtualBox can call this method.
3746 */
3747HRESULT HVMDKImage::trySetRegistered (BOOL aRegistered)
3748{
3749 AutoLock alock (this);
3750 CHECK_READY();
3751
3752 if (aRegistered)
3753 {
3754 if (mState == NotCreated)
3755 return setError (E_FAIL,
3756 tr ("Image file '%ls' is not yet created for this hard disk"),
3757 mFilePathFull.raw());
3758
3759/// @todo (r=dmik) will need this if we add suppord for differencing VMDKs
3760// if (isDifferencing())
3761// return setError (E_FAIL,
3762// tr ("Hard disk '%ls' is differencing and cannot be unregistered "
3763// "explicitly"),
3764// mFilePathFull.raw());
3765 }
3766 else
3767 {
3768 ComAssertRet (mState >= Created, E_FAIL);
3769 }
3770
3771 return HardDisk::trySetRegistered (aRegistered);
3772}
3773
3774/**
3775 * Checks accessibility of this hard disk image only (w/o parents).
3776 *
3777 * @param aAccessError on output, a null string indicates the hard disk is
3778 * accessible, otherwise contains a message describing
3779 * the reason of inaccessibility.
3780 */
3781HRESULT HVMDKImage::getAccessible (Bstr &aAccessError)
3782{
3783 AutoLock alock (this);
3784 CHECK_READY();
3785
3786 if (mStateCheckSem != NIL_RTSEMEVENTMULTI)
3787 {
3788 /* An accessibility check in progress on some other thread,
3789 * wait for it to finish. */
3790
3791 ComAssertRet (mStateCheckWaiters != (ULONG) ~0, E_FAIL);
3792 ++ mStateCheckWaiters;
3793 alock.leave();
3794
3795 int vrc = RTSemEventMultiWait (mStateCheckSem, RT_INDEFINITE_WAIT);
3796
3797 alock.enter();
3798 AssertReturn (mStateCheckWaiters != 0, E_FAIL);
3799 -- mStateCheckWaiters;
3800 if (mStateCheckWaiters == 0)
3801 {
3802 RTSemEventMultiDestroy (mStateCheckSem);
3803 mStateCheckSem = NIL_RTSEMEVENTMULTI;
3804 }
3805
3806 AssertRCReturn (vrc, E_FAIL);
3807
3808 /* don't touch aAccessError, it has been already set */
3809 return S_OK;
3810 }
3811
3812 /* check the basic accessibility */
3813 HRESULT rc = getBaseAccessible (aAccessError, true /* aCheckBusy */);
3814 if (FAILED (rc) || !aAccessError.isNull())
3815 return rc;
3816
3817 if (mState >= Created)
3818 {
3819 return queryInformation (&aAccessError);
3820 }
3821
3822 aAccessError = Utf8StrFmt ("Hard disk image '%ls' is not yet created",
3823 mFilePathFull.raw());
3824 return S_OK;
3825}
3826
3827/**
3828 * Saves hard disk settings to the specified storage node and saves
3829 * all children to the specified hard disk node
3830 *
3831 * @param aHDNode <HardDisk> or <DiffHardDisk> node
3832 * @param aStorageNode <VirtualDiskImage> node
3833 */
3834HRESULT HVMDKImage::saveSettings (CFGNODE aHDNode, CFGNODE aStorageNode)
3835{
3836 AssertReturn (aHDNode && aStorageNode, E_FAIL);
3837
3838 AutoLock alock (this);
3839 CHECK_READY();
3840
3841 /* filePath (required) */
3842 CFGLDRSetBSTR (aStorageNode, "filePath", mFilePath);
3843
3844 /* save basic settings and children */
3845 return HardDisk::saveSettings (aHDNode);
3846}
3847
3848/**
3849 * Checks if the given change of \a aOldPath to \a aNewPath affects the path
3850 * of this hard disk and updates it if necessary to reflect the new location.
3851 * Intended to be from HardDisk::updatePaths().
3852 *
3853 * @param aOldPath old path (full)
3854 * @param aNewPath new path (full)
3855 *
3856 * @note Locks this object for writing.
3857 */
3858void HVMDKImage::updatePath (const char *aOldPath, const char *aNewPath)
3859{
3860 AssertReturnVoid (aOldPath);
3861 AssertReturnVoid (aNewPath);
3862
3863 AutoLock alock (this);
3864 AssertReturnVoid (isReady());
3865
3866 size_t oldPathLen = strlen (aOldPath);
3867
3868 Utf8Str path = mFilePathFull;
3869 LogFlowThisFunc (("VMDK.fullPath={%s}\n", path.raw()));
3870
3871 if (RTPathStartsWith (path, aOldPath))
3872 {
3873 Utf8Str newPath = Utf8StrFmt ("%s%s", aNewPath,
3874 path.raw() + oldPathLen);
3875 path = newPath;
3876
3877 mVirtualBox->calculateRelativePath (path, path);
3878
3879 unconst (mFilePathFull) = newPath;
3880 unconst (mFilePath) = path;
3881
3882 LogFlowThisFunc (("-> updated: full={%s} short={%s}\n",
3883 newPath.raw(), path.raw()));
3884 }
3885}
3886
3887/**
3888 * Returns the string representation of this hard disk.
3889 * When \a aShort is false, returns the full image file path.
3890 * Otherwise, returns the image file name only.
3891 *
3892 * @param aShort if true, a short representation is returned
3893 */
3894Bstr HVMDKImage::toString (bool aShort /* = false */)
3895{
3896 AutoLock alock (this);
3897
3898 if (!aShort)
3899 return mFilePathFull;
3900 else
3901 {
3902 Utf8Str fname = mFilePathFull;
3903 return RTPathFilename (fname.mutableRaw());
3904 }
3905}
3906
3907/**
3908 * Creates a clone of this hard disk by storing hard disk data in the given
3909 * VDI file.
3910 *
3911 * If the operation fails, @a aDeleteTarget will be set to @c true unless the
3912 * failure happened because the target file already existed.
3913 *
3914 * @param aId UUID to assign to the created image.
3915 * @param aTargetPath VDI file where the cloned image is to be to stored.
3916 * @param aProgress progress object to run during operation.
3917 * @param aDeleteTarget Whether it is recommended to delete target on
3918 * failure or not.
3919 */
3920HRESULT
3921HVMDKImage::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath,
3922 Progress *aProgress, bool &aDeleteTarget)
3923{
3924 ComAssertMsgFailed (("Not implemented"));
3925 return E_NOTIMPL;
3926
3927/// @todo (r=dmik) will need this if we add suppord for differencing VMDKs
3928// Use code from HVirtualDiskImage::cloneToImage as an example.
3929}
3930
3931/**
3932 * Creates a new differencing image for this hard disk with the given
3933 * VDI file name.
3934 *
3935 * @param aId UUID to assign to the created image
3936 * @param aTargetPath VDI file where to store the created differencing image
3937 * @param aProgress progress object to run during operation
3938 * (can be NULL)
3939 */
3940HRESULT
3941HVMDKImage::createDiffImage (const Guid &aId, const Utf8Str &aTargetPath,
3942 Progress *aProgress)
3943{
3944 ComAssertMsgFailed (("Not implemented"));
3945 return E_NOTIMPL;
3946
3947/// @todo (r=dmik) will need this if we add suppord for differencing VMDKs
3948// Use code from HVirtualDiskImage::createDiffImage as an example.
3949}
3950
3951// private methods
3952/////////////////////////////////////////////////////////////////////////////
3953
3954/**
3955 * Helper to set a new file path.
3956 * Resolves a path relatively to the Virtual Box home directory.
3957 *
3958 * @note
3959 * Must be called from under the object's lock!
3960 */
3961HRESULT HVMDKImage::setFilePath (const BSTR aFilePath)
3962{
3963 if (aFilePath && *aFilePath)
3964 {
3965 /* get the full file name */
3966 char filePathFull [RTPATH_MAX];
3967 int vrc = RTPathAbsEx (mVirtualBox->homeDir(), Utf8Str (aFilePath),
3968 filePathFull, sizeof (filePathFull));
3969 if (VBOX_FAILURE (vrc))
3970 return setError (E_FAIL,
3971 tr ("Invalid image file path '%ls' (%Vrc)"), aFilePath, vrc);
3972
3973 mFilePath = aFilePath;
3974 mFilePathFull = filePathFull;
3975 }
3976 else
3977 {
3978 mFilePath.setNull();
3979 mFilePathFull.setNull();
3980 }
3981
3982 return S_OK;
3983}
3984
3985/**
3986 * Helper to query information about the VDI hard disk.
3987 *
3988 * @param aAccessError not used when NULL, otherwise see #getAccessible()
3989 *
3990 * @note Must be called from under the object's lock, only after
3991 * CHECK_BUSY_AND_READERS() succeeds.
3992 */
3993HRESULT HVMDKImage::queryInformation (Bstr *aAccessError)
3994{
3995 AssertReturn (isLockedOnCurrentThread(), E_FAIL);
3996
3997 /* create a lock object to completely release it later */
3998 AutoLock alock (this);
3999
4000 AssertReturn (mStateCheckWaiters == 0, E_FAIL);
4001
4002 ComAssertRet (mState >= Created, E_FAIL);
4003
4004 HRESULT rc = S_OK;
4005 int vrc = VINF_SUCCESS;
4006
4007 /* lazily create a semaphore */
4008 vrc = RTSemEventMultiCreate (&mStateCheckSem);
4009 ComAssertRCRet (vrc, E_FAIL);
4010
4011 /* Reference the disk to prevent any concurrent modifications
4012 * after releasing the lock below (to unblock getters before
4013 * a lengthy operation). */
4014 addReader();
4015
4016 alock.leave();
4017
4018 /* VBoxVHDD management interface needs to be optimized: we're opening a
4019 * file three times in a raw to get three bits of information. */
4020
4021 Utf8Str filePath = mFilePathFull;
4022 Bstr errMsg;
4023
4024 /* reset any previous error report from VDError() */
4025 mLastVDError.setNull();
4026
4027 do
4028 {
4029 Guid id, parentId;
4030
4031 /// @todo changed from VD_OPEN_FLAGS_READONLY to VD_OPEN_FLAGS_NORMAL,
4032 /// because otherwise registering a VMDK which so far has no UUID will
4033 /// yield a null UUID. It cannot be added to a VMDK opened readonly,
4034 /// obviously. This of course changes locking behavior, but for now
4035 /// this is acceptable. A better solution needs to be found later.
4036 vrc = VDOpen (mContainer, filePath, VD_OPEN_FLAGS_NORMAL);
4037 if (VBOX_FAILURE (vrc))
4038 break;
4039
4040 vrc = VDGetUuid (mContainer, 0, id.ptr());
4041 if (VBOX_FAILURE (vrc))
4042 break;
4043 vrc = VDGetParentUuid (mContainer, 0, parentId.ptr());
4044 if (VBOX_FAILURE (vrc))
4045 break;
4046
4047 if (!mId.isEmpty())
4048 {
4049 /* check that the actual UUID of the image matches the stored UUID */
4050 if (mId != id)
4051 {
4052 errMsg = Utf8StrFmt (
4053 tr ("Actual UUID {%Vuuid} of the hard disk image '%s' doesn't "
4054 "match UUID {%Vuuid} stored in the registry"),
4055 id.ptr(), filePath.raw(), mId.ptr());
4056 break;
4057 }
4058 }
4059 else
4060 {
4061 /* assgn an UUID read from the image file */
4062 mId = id;
4063 }
4064
4065 if (mParent)
4066 {
4067 /* check parent UUID */
4068 AutoLock parentLock (mParent);
4069 if (mParent->id() != parentId)
4070 {
4071 errMsg = Utf8StrFmt (
4072 tr ("UUID {%Vuuid} of the parent image '%ls' stored in "
4073 "the hard disk image file '%s' doesn't match "
4074 "UUID {%Vuuid} stored in the registry"),
4075 parentId.raw(), mParent->toString().raw(),
4076 filePath.raw(), mParent->id().raw());
4077 break;
4078 }
4079 }
4080 else if (!parentId.isEmpty())
4081 {
4082 errMsg = Utf8StrFmt (
4083 tr ("Hard disk image '%s' is a differencing image that is linked "
4084 "to a hard disk with UUID {%Vuuid} and cannot be used "
4085 "directly as a base hard disk"),
4086 filePath.raw(), parentId.raw());
4087 break;
4088 }
4089
4090 /* get actual file size */
4091 /// @todo is there a direct method in RT?
4092 {
4093 RTFILE file = NIL_RTFILE;
4094 vrc = RTFileOpen (&file, filePath, RTFILE_O_READ);
4095 if (VBOX_SUCCESS (vrc))
4096 {
4097 uint64_t size = 0;
4098 vrc = RTFileGetSize (file, &size);
4099 if (VBOX_SUCCESS (vrc))
4100 mActualSize = size;
4101 RTFileClose (file);
4102 }
4103 if (VBOX_FAILURE (vrc))
4104 break;
4105 }
4106
4107 /* query logical size only for non-differencing images */
4108 if (!mParent)
4109 {
4110 uint64_t size = VDGetSize (mContainer);
4111 /* convert to MBytes */
4112 mSize = size / 1024 / 1024;
4113 }
4114 }
4115 while (0);
4116
4117 VDCloseAll (mContainer);
4118
4119 /* enter the lock again */
4120 alock.enter();
4121
4122 /* remove the reference */
4123 releaseReader();
4124
4125 if (FAILED (rc) || VBOX_FAILURE (vrc) || !errMsg.isNull())
4126 {
4127 LogWarningFunc (("'%ls' is not accessible "
4128 "(rc=%08X, vrc=%Vrc, errMsg='%ls')\n",
4129 mFilePathFull.raw(), rc, vrc, errMsg.raw()));
4130
4131 if (aAccessError)
4132 {
4133 if (!errMsg.isNull())
4134 *aAccessError = errMsg;
4135 else if (!mLastVDError.isNull())
4136 *aAccessError = mLastVDError;
4137 else if (VBOX_FAILURE (vrc))
4138 *aAccessError = Utf8StrFmt (
4139 tr ("Could not access hard disk image '%ls' (%Vrc)"),
4140 mFilePathFull.raw(), vrc);
4141 }
4142
4143 /* downgrade to not accessible */
4144 mState = Created;
4145 }
4146 else
4147 {
4148 if (aAccessError)
4149 aAccessError->setNull();
4150
4151 mState = Accessible;
4152 }
4153
4154 /* inform waiters if there are any */
4155 if (mStateCheckWaiters > 0)
4156 {
4157 RTSemEventMultiSignal (mStateCheckSem);
4158 }
4159 else
4160 {
4161 /* delete the semaphore ourselves */
4162 RTSemEventMultiDestroy (mStateCheckSem);
4163 mStateCheckSem = NIL_RTSEMEVENTMULTI;
4164 }
4165
4166 /* cleanup the last error report from VDError() */
4167 mLastVDError.setNull();
4168
4169 return rc;
4170}
4171
4172/**
4173 * Helper to create hard disk images.
4174 *
4175 * @param aSize size in MB
4176 * @param aDynamic dynamic or fixed image
4177 * @param aProgress address of IProgress pointer to return
4178 */
4179HRESULT HVMDKImage::createImage (ULONG64 aSize, BOOL aDynamic,
4180 IProgress **aProgress)
4181{
4182 ComAssertMsgFailed (("Not implemented"));
4183 return E_NOTIMPL;
4184
4185/// @todo (r=dmik) later
4186// Use code from HVirtualDiskImage::createImage as an example.
4187}
4188
4189/* static */
4190DECLCALLBACK(int) HVMDKImage::VDITaskThread (RTTHREAD thread, void *pvUser)
4191{
4192 AssertMsgFailed (("Not implemented"));
4193 return VERR_GENERAL_FAILURE;
4194
4195/// @todo (r=dmik) later
4196// Use code from HVirtualDiskImage::VDITaskThread as an example.
4197}
4198
4199/* static */
4200DECLCALLBACK(void) HVMDKImage::VDError (void *pvUser, int rc, RT_SRC_POS_DECL,
4201 const char *pszFormat, va_list va)
4202{
4203 HVMDKImage *that = static_cast <HVMDKImage *> (pvUser);
4204 AssertReturnVoid (that != NULL);
4205
4206 /// @todo pass the error message to the operation initiator
4207 Utf8Str err = Utf8StrFmt (pszFormat, va);
4208 if (VBOX_FAILURE (rc))
4209 err = Utf8StrFmt ("%s (%Vrc)", err.raw(), rc);
4210
4211 if (that->mLastVDError.isNull())
4212 that->mLastVDError = err;
4213 else
4214 that->mLastVDError = Utf8StrFmt
4215 ("%s.\n%s", that->mLastVDError.raw(), err.raw());
4216}
4217
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use