VirtualBox

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

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

Main: Differencing hard disks belonging to a snapshot could be sometimes left in the snapshot directory after discarding the snapshot instead of deleting them (in particular, when powering off and reverting to the current snapshot at once).

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

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