VirtualBox

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

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

Main: create copy of the format string before freeing it

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 141.3 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 int vrc = RTFileDelete (Utf8Str (mFilePathFull));
1669 if (VBOX_FAILURE (vrc))
1670 return setError (E_FAIL, tr ("Could not delete the image file '%ls' (%Vrc)"),
1671 mFilePathFull.raw(), vrc);
1672
1673 mState = NotCreated;
1674
1675 // reset the fields
1676 mSize = 0;
1677 mActualSize = 0;
1678
1679 return S_OK;
1680}
1681
1682// public/protected methods for internal purposes only
1683/////////////////////////////////////////////////////////////////////////////
1684
1685/**
1686 * Attempts to mark the hard disk as registered.
1687 * Only VirtualBox can call this method.
1688 */
1689HRESULT HVirtualDiskImage::trySetRegistered (BOOL aRegistered)
1690{
1691 AutoLock alock (this);
1692 CHECK_READY();
1693
1694 if (aRegistered)
1695 {
1696 if (mState == NotCreated)
1697 return setError (E_FAIL,
1698 tr ("Image file '%ls' is not yet created for this hard disk"),
1699 mFilePathFull.raw());
1700 if (isDifferencing())
1701 return setError (E_FAIL,
1702 tr ("Hard disk '%ls' is differencing and cannot be unregistered "
1703 "explicitly"),
1704 mFilePathFull.raw());
1705 }
1706 else
1707 {
1708 ComAssertRet (mState >= Created, E_FAIL);
1709 }
1710
1711 return HardDisk::trySetRegistered (aRegistered);
1712}
1713
1714/**
1715 * Checks accessibility of this hard disk image only (w/o parents).
1716 *
1717 * @param aAccessError on output, a null string indicates the hard disk is
1718 * accessible, otherwise contains a message describing
1719 * the reason of inaccessibility.
1720 */
1721HRESULT HVirtualDiskImage::getAccessible (Bstr &aAccessError)
1722{
1723 AutoLock alock (this);
1724 CHECK_READY();
1725
1726 if (mStateCheckSem != NIL_RTSEMEVENTMULTI)
1727 {
1728 /* An accessibility check in progress on some other thread,
1729 * wait for it to finish. */
1730
1731 ComAssertRet (mStateCheckWaiters != (ULONG) ~0, E_FAIL);
1732 ++ mStateCheckWaiters;
1733 alock.leave();
1734
1735 int vrc = RTSemEventMultiWait (mStateCheckSem, RT_INDEFINITE_WAIT);
1736
1737 alock.enter();
1738 AssertReturn (mStateCheckWaiters != 0, E_FAIL);
1739 -- mStateCheckWaiters;
1740 if (mStateCheckWaiters == 0)
1741 {
1742 RTSemEventMultiDestroy (mStateCheckSem);
1743 mStateCheckSem = NIL_RTSEMEVENTMULTI;
1744 }
1745
1746 AssertRCReturn (vrc, E_FAIL);
1747
1748 /* don't touch aAccessError, it has been already set */
1749 return S_OK;
1750 }
1751
1752 /* check the basic accessibility */
1753 HRESULT rc = getBaseAccessible (aAccessError, true /* aCheckBusy */);
1754 if (FAILED (rc) || !aAccessError.isNull())
1755 return rc;
1756
1757 if (mState >= Created)
1758 {
1759 return queryInformation (&aAccessError);
1760 }
1761
1762 aAccessError = Utf8StrFmt ("Hard disk image '%ls' is not yet created",
1763 mFilePathFull.raw());
1764 return S_OK;
1765}
1766
1767/**
1768 * Saves hard disk settings to the specified storage node and saves
1769 * all children to the specified hard disk node
1770 *
1771 * @param aHDNode <HardDisk> or <DiffHardDisk> node
1772 * @param aStorageNode <VirtualDiskImage> node
1773 */
1774HRESULT HVirtualDiskImage::saveSettings (CFGNODE aHDNode, CFGNODE aStorageNode)
1775{
1776 AssertReturn (aHDNode && aStorageNode, E_FAIL);
1777
1778 AutoLock alock (this);
1779 CHECK_READY();
1780
1781 // filePath (required)
1782 CFGLDRSetBSTR (aStorageNode, "filePath", mFilePath);
1783
1784 // save basic settings and children
1785 return HardDisk::saveSettings (aHDNode);
1786}
1787
1788/**
1789 * Checks if the given change of \a aOldPath to \a aNewPath affects the path
1790 * of this hard disk and updates it if necessary to reflect the new location.
1791 * Intended to be from HardDisk::updatePaths().
1792 *
1793 * @param aOldPath old path (full)
1794 * @param aNewPath new path (full)
1795 *
1796 * @note Locks this object for writing.
1797 */
1798void HVirtualDiskImage::updatePath (const char *aOldPath, const char *aNewPath)
1799{
1800 AssertReturnVoid (aOldPath);
1801 AssertReturnVoid (aNewPath);
1802
1803 AutoLock alock (this);
1804 AssertReturnVoid (isReady());
1805
1806 size_t oldPathLen = strlen (aOldPath);
1807
1808 Utf8Str path = mFilePathFull;
1809 LogFlowThisFunc (("VDI.fullPath={%s}\n", path.raw()));
1810
1811 if (RTPathStartsWith (path, aOldPath))
1812 {
1813 Utf8Str newPath = Utf8StrFmt ("%s%s", aNewPath,
1814 path.raw() + oldPathLen);
1815 path = newPath;
1816
1817 mVirtualBox->calculateRelativePath (path, path);
1818
1819 unconst (mFilePathFull) = newPath;
1820 unconst (mFilePath) = path;
1821
1822 LogFlowThisFunc (("-> updated: full={%s} short={%s}\n",
1823 newPath.raw(), path.raw()));
1824 }
1825}
1826
1827/**
1828 * Returns the string representation of this hard disk.
1829 * When \a aShort is false, returns the full image file path.
1830 * Otherwise, returns the image file name only.
1831 *
1832 * @param aShort if true, a short representation is returned
1833 */
1834Bstr HVirtualDiskImage::toString (bool aShort /* = false */)
1835{
1836 AutoLock alock (this);
1837
1838 if (!aShort)
1839 return mFilePathFull;
1840 else
1841 {
1842 Utf8Str fname = mFilePathFull;
1843 return RTPathFilename (fname.mutableRaw());
1844 }
1845}
1846
1847/**
1848 * Creates a clone of this hard disk by storing hard disk data in the given
1849 * VDI file.
1850 *
1851 * If the operation fails, @a aDeleteTarget will be set to @c true unless the
1852 * failure happened because the target file already existed.
1853 *
1854 * @param aId UUID to assign to the created image.
1855 * @param aTargetPath VDI file where the cloned image is to be to stored.
1856 * @param aProgress progress object to run during operation.
1857 * @param aDeleteTarget Whether it is recommended to delete target on
1858 * failure or not.
1859 */
1860HRESULT
1861HVirtualDiskImage::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath,
1862 Progress *aProgress, bool &aDeleteTarget)
1863{
1864 /* normally, the target file should be deleted on error */
1865 aDeleteTarget = true;
1866
1867 AssertReturn (!aId.isEmpty(), E_FAIL);
1868 AssertReturn (!aTargetPath.isNull(), E_FAIL);
1869 AssertReturn (aProgress, E_FAIL);
1870
1871 AutoLock alock (this);
1872 AssertReturn (isReady(), E_FAIL);
1873
1874 AssertReturn (isBusy() == false, E_FAIL);
1875
1876 /// @todo (dmik) cloning of differencing images is not yet supported
1877 AssertReturn (mParent.isNull(), E_FAIL);
1878
1879 Utf8Str filePathFull = mFilePathFull;
1880
1881 if (mState == NotCreated)
1882 return setError (E_FAIL,
1883 tr ("Source hard disk image '%s' is not yet created"),
1884 filePathFull.raw());
1885
1886 addReader();
1887 alock.leave();
1888
1889 int vrc = VDICopyImage (aTargetPath, filePathFull, NULL,
1890 progressCallback,
1891 static_cast <Progress *> (aProgress));
1892
1893 alock.enter();
1894 releaseReader();
1895
1896 /* We don't want to delete existing user files */
1897 if (vrc == VERR_ALREADY_EXISTS)
1898 aDeleteTarget = false;
1899
1900 if (VBOX_SUCCESS (vrc))
1901 vrc = VDISetImageUUIDs (aTargetPath, aId, NULL, NULL, NULL);
1902
1903 if (VBOX_FAILURE (vrc))
1904 return setError (E_FAIL,
1905 tr ("Could not copy the hard disk image '%s' to '%s' (%Vrc)"),
1906 filePathFull.raw(), aTargetPath.raw(), vrc);
1907
1908 return S_OK;
1909}
1910
1911/**
1912 * Creates a new differencing image for this hard disk with the given
1913 * VDI file name.
1914 *
1915 * @param aId UUID to assign to the created image
1916 * @param aTargetPath VDI file where to store the created differencing image
1917 * @param aProgress progress object to run during operation
1918 * (can be NULL)
1919 */
1920HRESULT
1921HVirtualDiskImage::createDiffImage (const Guid &aId, const Utf8Str &aTargetPath,
1922 Progress *aProgress)
1923{
1924 AssertReturn (!aId.isEmpty(), E_FAIL);
1925 AssertReturn (!aTargetPath.isNull(), E_FAIL);
1926
1927 AutoLock alock (this);
1928 AssertReturn (isReady(), E_FAIL);
1929
1930 AssertReturn (isBusy() == false, E_FAIL);
1931 AssertReturn (mState >= Created, E_FAIL);
1932
1933 addReader();
1934 alock.leave();
1935
1936 int vrc = VDICreateDifferenceImage (aTargetPath, Utf8Str (mFilePathFull),
1937 NULL, aProgress ? progressCallback : NULL,
1938 static_cast <Progress *> (aProgress));
1939 alock.enter();
1940 releaseReader();
1941
1942 /* update the UUID to correspond to the file name */
1943 if (VBOX_SUCCESS (vrc))
1944 vrc = VDISetImageUUIDs (aTargetPath, aId, NULL, NULL, NULL);
1945
1946 if (VBOX_FAILURE (vrc))
1947 return setError (E_FAIL,
1948 tr ("Could not create a differencing hard disk '%s' (%Vrc)"),
1949 aTargetPath.raw(), vrc);
1950
1951 return S_OK;
1952}
1953
1954/**
1955 * Copies the image file of this hard disk to a separate VDI file (with an
1956 * unique creation UUID) and creates a new hard disk object for the copied
1957 * image. The copy will be created as a child of this hard disk's parent
1958 * (so that this hard disk must be a differencing one).
1959 *
1960 * The specified progress object (if not NULL) receives the percentage
1961 * of the operation completion. However, it is responsibility of the caller to
1962 * call Progress::notifyComplete() after this method returns.
1963 *
1964 * @param aFolder folder where to create a copy (must be a full path)
1965 * @param aMachineId machine ID the new hard disk will belong to
1966 * @param aHardDisk resulting hard disk object
1967 * @param aProgress progress object to run during copy operation
1968 * (may be NULL)
1969 *
1970 * @note
1971 * Must be NOT called from under locks of other objects that need external
1972 * access dirung this method execurion!
1973 */
1974HRESULT
1975HVirtualDiskImage::cloneDiffImage (const Bstr &aFolder, const Guid &aMachineId,
1976 ComObjPtr <HVirtualDiskImage> &aHardDisk,
1977 Progress *aProgress)
1978{
1979 AssertReturn (!aFolder.isEmpty() && !aMachineId.isEmpty(),
1980 E_FAIL);
1981
1982 AutoLock alock (this);
1983 CHECK_READY();
1984
1985 AssertReturn (!mParent.isNull(), E_FAIL);
1986
1987 ComAssertRet (isBusy() == false, E_FAIL);
1988 ComAssertRet (mState >= Created, E_FAIL);
1989
1990 Guid id;
1991 id.create();
1992
1993 Utf8Str filePathTo = Utf8StrFmt ("%ls%c{%Vuuid}.vdi",
1994 aFolder.raw(), RTPATH_DELIMITER, id.ptr());
1995
1996 /* try to make the path relative to the vbox home dir */
1997 const char *filePathToRel = filePathTo;
1998 {
1999 const Utf8Str &homeDir = mVirtualBox->homeDir();
2000 if (!strncmp (filePathTo, homeDir, homeDir.length()))
2001 filePathToRel = (filePathToRel + homeDir.length() + 1);
2002 }
2003
2004 /* first ensure the directory exists */
2005 {
2006 Utf8Str dir = aFolder;
2007 if (!RTDirExists (dir))
2008 {
2009 int vrc = RTDirCreateFullPath (dir, 0777);
2010 if (VBOX_FAILURE (vrc))
2011 {
2012 return setError (E_FAIL,
2013 tr ("Could not create a directory '%s' "
2014 "to store the image file (%Vrc)"),
2015 dir.raw(), vrc);
2016 }
2017 }
2018 }
2019
2020 Utf8Str filePathFull = mFilePathFull;
2021
2022 alock.leave();
2023
2024 int vrc = VDICopyImage (filePathTo, filePathFull, NULL,
2025 progressCallback,
2026 static_cast <Progress *> (aProgress));
2027
2028 alock.enter();
2029
2030 /* get modification and parent UUIDs of this image */
2031 RTUUID modUuid, parentUuid, parentModUuid;
2032 if (VBOX_SUCCESS (vrc))
2033 vrc = VDIGetImageUUIDs (filePathFull, NULL, &modUuid,
2034 &parentUuid, &parentModUuid);
2035
2036 // update the UUID of the copy to correspond to the file name
2037 // and copy all other UUIDs from this image
2038 if (VBOX_SUCCESS (vrc))
2039 vrc = VDISetImageUUIDs (filePathTo, id, &modUuid,
2040 &parentUuid, &parentModUuid);
2041
2042 if (VBOX_FAILURE (vrc))
2043 return setError (E_FAIL,
2044 tr ("Could not copy the hard disk image '%s' to '%s' (%Vrc)"),
2045 filePathFull.raw(), filePathTo.raw(), vrc);
2046
2047 ComObjPtr <HVirtualDiskImage> vdi;
2048 vdi.createObject();
2049 HRESULT rc = vdi->init (mVirtualBox, mParent, Bstr (filePathToRel),
2050 TRUE /* aRegistered */);
2051 if (FAILED (rc))
2052 return rc;
2053
2054 /* associate the created hard disk with the given machine */
2055 vdi->setMachineId (aMachineId);
2056
2057 rc = mVirtualBox->registerHardDisk (vdi, VirtualBox::RHD_Internal);
2058 if (FAILED (rc))
2059 return rc;
2060
2061 aHardDisk = vdi;
2062
2063 return S_OK;
2064}
2065
2066/**
2067 * Merges this child image to its parent image and updates the parent UUID
2068 * of all children of this image (to point to this image's parent).
2069 * It's a responsibility of the caller to unregister and uninitialize
2070 * the merged image on success.
2071 *
2072 * This method is intended to be called on a worker thread (the operation
2073 * can be time consuming).
2074 *
2075 * The specified progress object (if not NULL) receives the percentage
2076 * of the operation completion. However, it is responsibility of the caller to
2077 * call Progress::notifyComplete() after this method returns.
2078 *
2079 * @param aProgress progress object to run during copy operation
2080 * (may be NULL)
2081 *
2082 * @note
2083 * This method expects that both this hard disk and the paret hard disk
2084 * are marked as busy using #setBusyWithChildren() prior to calling it!
2085 * Busy flags of both hard disks will be cleared by this method
2086 * on a successful return. In case of failure, #clearBusyWithChildren()
2087 * must be called on a parent.
2088 *
2089 * @note
2090 * Must be NOT called from under locks of other objects that need external
2091 * access dirung this method execurion!
2092 */
2093HRESULT HVirtualDiskImage::mergeImageToParent (Progress *aProgress)
2094{
2095 LogFlowThisFunc (("mFilePathFull='%ls'\n", mFilePathFull.raw()));
2096
2097 AutoLock alock (this);
2098 CHECK_READY();
2099
2100 AssertReturn (!mParent.isNull(), E_FAIL);
2101 AutoLock parentLock (mParent);
2102
2103 ComAssertRet (isBusy() == true, E_FAIL);
2104 ComAssertRet (mParent->isBusy() == true, E_FAIL);
2105
2106 ComAssertRet (mState >= Created && mParent->asVDI()->mState >= Created, E_FAIL);
2107
2108 ComAssertMsgRet (mParent->storageType() == HardDiskStorageType_VirtualDiskImage,
2109 ("non VDI storage types are not yet supported!"), E_FAIL);
2110
2111 parentLock.leave();
2112 alock.leave();
2113
2114 int vrc = VDIMergeImage (Utf8Str (mFilePathFull),
2115 Utf8Str (mParent->asVDI()->mFilePathFull),
2116 progressCallback,
2117 static_cast <Progress *> (aProgress));
2118 alock.enter();
2119 parentLock.enter();
2120
2121 if (VBOX_FAILURE (vrc))
2122 return setError (E_FAIL,
2123 tr ("Could not merge the hard disk image '%ls' to "
2124 "its parent image '%ls' (%Vrc)"),
2125 mFilePathFull.raw(), mParent->asVDI()->mFilePathFull.raw(), vrc);
2126
2127 {
2128 HRESULT rc = S_OK;
2129
2130 AutoLock chLock (childrenLock());
2131
2132 for (HardDiskList::const_iterator it = children().begin();
2133 it != children().end(); ++ it)
2134 {
2135 ComObjPtr <HVirtualDiskImage> child = (*it)->asVDI();
2136 AutoLock childLock (child);
2137
2138 /* reparent the child */
2139 child->mParent = mParent;
2140 if (mParent)
2141 mParent->addDependentChild (child);
2142
2143 /* change the parent UUID in the image as well */
2144 RTUUID parentUuid, parentModUuid;
2145 vrc = VDIGetImageUUIDs (Utf8Str (mParent->asVDI()->mFilePathFull),
2146 &parentUuid, &parentModUuid, NULL, NULL);
2147 if (VBOX_FAILURE (vrc))
2148 {
2149 rc = setError (E_FAIL,
2150 tr ("Could not access the hard disk image '%ls' (%Vrc)"),
2151 mParent->asVDI()->mFilePathFull.raw(), vrc);
2152 break;
2153 }
2154 ComAssertBreak (mParent->id() == Guid (parentUuid), rc = E_FAIL);
2155 vrc = VDISetImageUUIDs (Utf8Str (child->mFilePathFull),
2156 NULL, NULL, &parentUuid, &parentModUuid);
2157 if (VBOX_FAILURE (vrc))
2158 {
2159 rc = setError (E_FAIL,
2160 tr ("Could not update parent UUID of the hard disk image "
2161 "'%ls' (%Vrc)"),
2162 child->mFilePathFull.raw(), vrc);
2163 break;
2164 }
2165 }
2166
2167 if (FAILED (rc))
2168 return rc;
2169 }
2170
2171 /* detach all our children to avoid their uninit in #uninit() */
2172 removeDependentChildren();
2173
2174 mParent->clearBusy();
2175 clearBusy();
2176
2177 return S_OK;
2178}
2179
2180/**
2181 * Merges this image to all its child images, updates the parent UUID
2182 * of all children of this image (to point to this image's parent).
2183 * It's a responsibility of the caller to unregister and uninitialize
2184 * the merged image on success.
2185 *
2186 * This method is intended to be called on a worker thread (the operation
2187 * can be time consuming).
2188 *
2189 * The specified progress object (if not NULL) receives the percentage
2190 * of the operation completion. However, it is responsibility of the caller to
2191 * call Progress::notifyComplete() after this method returns.
2192 *
2193 * @param aProgress progress object to run during copy operation
2194 * (may be NULL)
2195 *
2196 * @note
2197 * This method expects that both this hard disk and all children
2198 * are marked as busy using setBusyWithChildren() prior to calling it!
2199 * Busy flags of all affected hard disks will be cleared by this method
2200 * on a successful return. In case of failure, #clearBusyWithChildren()
2201 * must be called for this hard disk.
2202 *
2203 * @note
2204 * Must be NOT called from under locks of other objects that need external
2205 * access dirung this method execurion!
2206 */
2207HRESULT HVirtualDiskImage::mergeImageToChildren (Progress *aProgress)
2208{
2209 LogFlowThisFunc (("mFilePathFull='%ls'\n", mFilePathFull.raw()));
2210
2211 AutoLock alock (this);
2212 CHECK_READY();
2213
2214 /* this must be a diff image */
2215 AssertReturn (isDifferencing(), E_FAIL);
2216
2217 ComAssertRet (isBusy() == true, E_FAIL);
2218 ComAssertRet (mState >= Created, E_FAIL);
2219
2220 ComAssertMsgRet (mParent->storageType() == HardDiskStorageType_VirtualDiskImage,
2221 ("non VDI storage types are not yet supported!"), E_FAIL);
2222
2223 {
2224 HRESULT rc = S_OK;
2225
2226 AutoLock chLock (childrenLock());
2227
2228 /* iterate over a copy since we will modify the list */
2229 HardDiskList list = children();
2230
2231 for (HardDiskList::const_iterator it = list.begin();
2232 it != list.end(); ++ it)
2233 {
2234 ComObjPtr <HardDisk> hd = *it;
2235 ComObjPtr <HVirtualDiskImage> child = hd->asVDI();
2236 AutoLock childLock (child);
2237
2238 ComAssertRet (child->isBusy() == true, E_FAIL);
2239 ComAssertBreak (child->mState >= Created, rc = E_FAIL);
2240
2241 childLock.leave();
2242 alock.leave();
2243
2244 int vrc = VDIMergeImage (Utf8Str (mFilePathFull),
2245 Utf8Str (child->mFilePathFull),
2246 progressCallback,
2247 static_cast <Progress *> (aProgress));
2248 alock.enter();
2249 childLock.enter();
2250
2251 if (VBOX_FAILURE (vrc))
2252 {
2253 rc = setError (E_FAIL,
2254 tr ("Could not merge the hard disk image '%ls' to "
2255 "its parent image '%ls' (%Vrc)"),
2256 mFilePathFull.raw(), child->mFilePathFull.raw(), vrc);
2257 break;
2258 }
2259
2260 /* reparent the child */
2261 child->mParent = mParent;
2262 if (mParent)
2263 mParent->addDependentChild (child);
2264
2265 /* change the parent UUID in the image as well */
2266 RTUUID parentUuid, parentModUuid;
2267 vrc = VDIGetImageUUIDs (Utf8Str (mParent->asVDI()->mFilePathFull),
2268 &parentUuid, &parentModUuid, NULL, NULL);
2269 if (VBOX_FAILURE (vrc))
2270 {
2271 rc = setError (E_FAIL,
2272 tr ("Could not access the hard disk image '%ls' (%Vrc)"),
2273 mParent->asVDI()->mFilePathFull.raw(), vrc);
2274 break;
2275 }
2276 ComAssertBreak (mParent->id() == Guid (parentUuid), rc = E_FAIL);
2277 vrc = VDISetImageUUIDs (Utf8Str (child->mFilePathFull),
2278 NULL, NULL, &parentUuid, &parentModUuid);
2279 if (VBOX_FAILURE (vrc))
2280 {
2281 rc = setError (E_FAIL,
2282 tr ("Could not update parent UUID of the hard disk image "
2283 "'%ls' (%Vrc)"),
2284 child->mFilePathFull.raw(), vrc);
2285 break;
2286 }
2287
2288 /* detach child to avoid its uninit in #uninit() */
2289 removeDependentChild (child);
2290
2291 /* remove the busy flag */
2292 child->clearBusy();
2293 }
2294
2295 if (FAILED (rc))
2296 return rc;
2297 }
2298
2299 clearBusy();
2300
2301 return S_OK;
2302}
2303
2304/**
2305 * Deletes and recreates the differencing hard disk image from scratch.
2306 * The file name and UUID remain the same.
2307 */
2308HRESULT HVirtualDiskImage::wipeOutImage()
2309{
2310 AutoLock alock (this);
2311 CHECK_READY();
2312
2313 AssertReturn (isDifferencing(), E_FAIL);
2314 AssertReturn (mRegistered, E_FAIL);
2315 AssertReturn (mState >= Created, E_FAIL);
2316
2317 ComAssertMsgRet (mParent->storageType() == HardDiskStorageType_VirtualDiskImage,
2318 ("non-VDI storage types are not yet supported!"), E_FAIL);
2319
2320 Utf8Str filePathFull = mFilePathFull;
2321
2322 int vrc = RTFileDelete (filePathFull);
2323 if (VBOX_FAILURE (vrc))
2324 return setError (E_FAIL,
2325 tr ("Could not delete the image file '%s' (%Vrc)"),
2326 filePathFull.raw(), vrc);
2327
2328 vrc = VDICreateDifferenceImage (filePathFull,
2329 Utf8Str (mParent->asVDI()->mFilePathFull),
2330 NULL, NULL, NULL);
2331 /* update the UUID to correspond to the file name */
2332 if (VBOX_SUCCESS (vrc))
2333 vrc = VDISetImageUUIDs (filePathFull, mId, NULL, NULL, NULL);
2334
2335 if (VBOX_FAILURE (vrc))
2336 return setError (E_FAIL,
2337 tr ("Could not create a differencing hard disk '%s' (%Vrc)"),
2338 filePathFull.raw(), vrc);
2339
2340 return S_OK;
2341}
2342
2343// private methods
2344/////////////////////////////////////////////////////////////////////////////
2345
2346/**
2347 * Helper to set a new file path.
2348 * Resolves a path relatively to the Virtual Box home directory.
2349 *
2350 * @note
2351 * Must be called from under the object's lock!
2352 */
2353HRESULT HVirtualDiskImage::setFilePath (const BSTR aFilePath)
2354{
2355 if (aFilePath && *aFilePath)
2356 {
2357 /* get the full file name */
2358 char filePathFull [RTPATH_MAX];
2359 int vrc = RTPathAbsEx (mVirtualBox->homeDir(), Utf8Str (aFilePath),
2360 filePathFull, sizeof (filePathFull));
2361 if (VBOX_FAILURE (vrc))
2362 return setError (E_FAIL,
2363 tr ("Invalid image file path '%ls' (%Vrc)"), aFilePath, vrc);
2364
2365 mFilePath = aFilePath;
2366 mFilePathFull = filePathFull;
2367 }
2368 else
2369 {
2370 mFilePath.setNull();
2371 mFilePathFull.setNull();
2372 }
2373
2374 return S_OK;
2375}
2376
2377/**
2378 * Helper to query information about the VDI hard disk.
2379 *
2380 * @param aAccessError not used when NULL, otherwise see #getAccessible()
2381 *
2382 * @note Must be called from under the object's lock, only after
2383 * CHECK_BUSY_AND_READERS() succeeds.
2384 */
2385HRESULT HVirtualDiskImage::queryInformation (Bstr *aAccessError)
2386{
2387 AssertReturn (isLockedOnCurrentThread(), E_FAIL);
2388
2389 /* create a lock object to completely release it later */
2390 AutoLock alock (this);
2391
2392 AssertReturn (mStateCheckWaiters == 0, E_FAIL);
2393
2394 ComAssertRet (mState >= Created, E_FAIL);
2395
2396 HRESULT rc = S_OK;
2397 int vrc = VINF_SUCCESS;
2398
2399 /* lazily create a semaphore */
2400 vrc = RTSemEventMultiCreate (&mStateCheckSem);
2401 ComAssertRCRet (vrc, E_FAIL);
2402
2403 /* Reference the disk to prevent any concurrent modifications
2404 * after releasing the lock below (to unblock getters before
2405 * a lengthy operation). */
2406 addReader();
2407
2408 alock.leave();
2409
2410 /* VBoxVHDD management interface needs to be optimized: we're opening a
2411 * file three times in a raw to get three bits of information. */
2412
2413 Utf8Str filePath = mFilePathFull;
2414 Bstr errMsg;
2415
2416 do
2417 {
2418 /* check the image file */
2419 Guid id, parentId;
2420 vrc = VDICheckImage (filePath, NULL, NULL, NULL,
2421 id.ptr(), parentId.ptr(), NULL, 0);
2422
2423 if (VBOX_FAILURE (vrc))
2424 break;
2425
2426 if (!mId.isEmpty())
2427 {
2428 /* check that the actual UUID of the image matches the stored UUID */
2429 if (mId != id)
2430 {
2431 errMsg = Utf8StrFmt (
2432 tr ("Actual UUID {%Vuuid} of the hard disk image '%s' doesn't "
2433 "match UUID {%Vuuid} stored in the registry"),
2434 id.ptr(), filePath.raw(), mId.ptr());
2435 break;
2436 }
2437 }
2438 else
2439 {
2440 /* assgn an UUID read from the image file */
2441 mId = id;
2442 }
2443
2444 if (mParent)
2445 {
2446 /* check parent UUID */
2447 AutoLock parentLock (mParent);
2448 if (mParent->id() != parentId)
2449 {
2450 errMsg = Utf8StrFmt (
2451 tr ("UUID {%Vuuid} of the parent image '%ls' stored in "
2452 "the hard disk image file '%s' doesn't match "
2453 "UUID {%Vuuid} stored in the registry"),
2454 parentId.raw(), mParent->toString().raw(),
2455 filePath.raw(), mParent->id().raw());
2456 break;
2457 }
2458 }
2459 else if (!parentId.isEmpty())
2460 {
2461 errMsg = Utf8StrFmt (
2462 tr ("Hard disk image '%s' is a differencing image that is linked "
2463 "to a hard disk with UUID {%Vuuid} and cannot be used "
2464 "directly as a base hard disk"),
2465 filePath.raw(), parentId.raw());
2466 break;
2467 }
2468
2469 {
2470 RTFILE file = NIL_RTFILE;
2471 vrc = RTFileOpen (&file, filePath, RTFILE_O_READ);
2472 if (VBOX_SUCCESS (vrc))
2473 {
2474 uint64_t size = 0;
2475 vrc = RTFileGetSize (file, &size);
2476 if (VBOX_SUCCESS (vrc))
2477 mActualSize = size;
2478 RTFileClose (file);
2479 }
2480 if (VBOX_FAILURE (vrc))
2481 break;
2482 }
2483
2484 if (!mParent)
2485 {
2486 /* query logical size only for non-differencing images */
2487
2488 PVDIDISK disk = VDIDiskCreate();
2489 vrc = VDIDiskOpenImage (disk, Utf8Str (mFilePathFull),
2490 VDI_OPEN_FLAGS_READONLY);
2491 if (VBOX_SUCCESS (vrc))
2492 {
2493 uint64_t size = VDIDiskGetSize (disk);
2494 /* convert to MBytes */
2495 mSize = size / 1024 / 1024;
2496 }
2497
2498 VDIDiskDestroy (disk);
2499 if (VBOX_FAILURE (vrc))
2500 break;
2501 }
2502 }
2503 while (0);
2504
2505 /* enter the lock again */
2506 alock.enter();
2507
2508 /* remove the reference */
2509 releaseReader();
2510
2511 if (FAILED (rc) || VBOX_FAILURE (vrc) || !errMsg.isNull())
2512 {
2513 LogWarningFunc (("'%ls' is not accessible "
2514 "(rc=%08X, vrc=%Vrc, errMsg='%ls')\n",
2515 mFilePathFull.raw(), rc, vrc, errMsg.raw()));
2516
2517 if (aAccessError)
2518 {
2519 if (!errMsg.isNull())
2520 *aAccessError = errMsg;
2521 else if (VBOX_FAILURE (vrc))
2522 *aAccessError = Utf8StrFmt (
2523 tr ("Could not access hard disk image '%ls' (%Vrc)"),
2524 mFilePathFull.raw(), vrc);
2525 }
2526
2527 /* downgrade to not accessible */
2528 mState = Created;
2529 }
2530 else
2531 {
2532 if (aAccessError)
2533 aAccessError->setNull();
2534
2535 mState = Accessible;
2536 }
2537
2538 /* inform waiters if there are any */
2539 if (mStateCheckWaiters > 0)
2540 {
2541 RTSemEventMultiSignal (mStateCheckSem);
2542 }
2543 else
2544 {
2545 /* delete the semaphore ourselves */
2546 RTSemEventMultiDestroy (mStateCheckSem);
2547 mStateCheckSem = NIL_RTSEMEVENTMULTI;
2548 }
2549
2550 return rc;
2551}
2552
2553/**
2554 * Helper to create hard disk images.
2555 *
2556 * @param aSize size in MB
2557 * @param aDynamic dynamic or fixed image
2558 * @param aProgress address of IProgress pointer to return
2559 */
2560HRESULT HVirtualDiskImage::createImage (ULONG64 aSize, BOOL aDynamic,
2561 IProgress **aProgress)
2562{
2563 AutoLock alock (this);
2564
2565 CHECK_BUSY_AND_READERS();
2566
2567 if (mState != NotCreated)
2568 return setError (E_ACCESSDENIED,
2569 tr ("Hard disk image '%ls' is already created"),
2570 mFilePathFull.raw());
2571
2572 if (!mFilePathFull)
2573 return setError (E_ACCESSDENIED,
2574 tr ("Cannot create a hard disk image using an empty (null) file path"),
2575 mFilePathFull.raw());
2576
2577 /* first ensure the directory exists */
2578 {
2579 Utf8Str imageDir = mFilePathFull;
2580 RTPathStripFilename (imageDir.mutableRaw());
2581 if (!RTDirExists (imageDir))
2582 {
2583 int vrc = RTDirCreateFullPath (imageDir, 0777);
2584 if (VBOX_FAILURE (vrc))
2585 {
2586 return setError (E_FAIL,
2587 tr ("Could not create a directory '%s' "
2588 "to store the image file (%Vrc)"),
2589 imageDir.raw(), vrc);
2590 }
2591 }
2592 }
2593
2594 /* check whether the given file exists or not */
2595 RTFILE file;
2596 int vrc = RTFileOpen (&file, Utf8Str (mFilePathFull),
2597 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
2598 if (vrc != VERR_FILE_NOT_FOUND)
2599 {
2600 if (VBOX_SUCCESS (vrc))
2601 RTFileClose (file);
2602 switch(vrc)
2603 {
2604 case VINF_SUCCESS:
2605 return setError (E_FAIL,
2606 tr ("Image file '%ls' already exists"),
2607 mFilePathFull.raw());
2608
2609 default:
2610 return setError(E_FAIL,
2611 tr ("Invalid image file path '%ls' (%Vrc)"),
2612 mFilePathFull.raw(), vrc);
2613 }
2614 }
2615
2616 /* check VDI size limits */
2617 {
2618 HRESULT rc;
2619 ULONG64 maxVDISize;
2620 ComPtr <ISystemProperties> props;
2621 rc = mVirtualBox->COMGETTER(SystemProperties) (props.asOutParam());
2622 ComAssertComRCRet (rc, E_FAIL);
2623 rc = props->COMGETTER(MaxVDISize) (&maxVDISize);
2624 ComAssertComRCRet (rc, E_FAIL);
2625
2626 if (aSize < 1 || aSize > maxVDISize)
2627 return setError (E_INVALIDARG,
2628 tr ("Invalid VDI size: %llu MB (must be in range [1, %llu] MB)"),
2629 aSize, maxVDISize);
2630 }
2631
2632 HRESULT rc;
2633
2634 /* create a project object */
2635 ComObjPtr <Progress> progress;
2636 progress.createObject();
2637 {
2638 Bstr desc = aDynamic ? tr ("Creating a dynamically expanding hard disk")
2639 : tr ("Creating a fixed-size hard disk");
2640 rc = progress->init (mVirtualBox, (IVirtualDiskImage *) this, desc,
2641 FALSE /* aCancelable */);
2642 CheckComRCReturnRC (rc);
2643 }
2644
2645 /* mark as busy (being created)
2646 * (VDI task thread will unmark it) */
2647 setBusy();
2648
2649 /* fill in VDI task data */
2650 VDITask *task = new VDITask (aDynamic ? VDITask::CreateDynamic
2651 : VDITask::CreateStatic,
2652 this, progress);
2653 task->size = aSize;
2654 task->size *= 1024 * 1024; /* convert to bytes */
2655
2656 /* create the hard disk creation thread, pass operation data */
2657 vrc = RTThreadCreate (NULL, VDITaskThread, (void *) task, 0,
2658 RTTHREADTYPE_MAIN_HEAVY_WORKER, 0, "VDITask");
2659 ComAssertMsgRC (vrc, ("Could not create a thread (%Vrc)", vrc));
2660 if (VBOX_FAILURE (vrc))
2661 {
2662 clearBusy();
2663 delete task;
2664 rc = E_FAIL;
2665 }
2666 else
2667 {
2668 /* get one interface for the caller */
2669 progress.queryInterfaceTo (aProgress);
2670 }
2671
2672 return rc;
2673}
2674
2675/* static */
2676DECLCALLBACK(int) HVirtualDiskImage::VDITaskThread (RTTHREAD thread, void *pvUser)
2677{
2678 VDITask *task = static_cast <VDITask *> (pvUser);
2679 AssertReturn (task, VERR_GENERAL_FAILURE);
2680
2681 LogFlowFunc (("operation=%d, size=%llu\n", task->operation, task->size));
2682
2683 VDIIMAGETYPE type = (VDIIMAGETYPE) 0;
2684
2685 switch (task->operation)
2686 {
2687 case VDITask::CreateDynamic: type = VDI_IMAGE_TYPE_NORMAL; break;
2688 case VDITask::CreateStatic: type = VDI_IMAGE_TYPE_FIXED; break;
2689 case VDITask::CloneToImage: break;
2690 default: AssertFailedReturn (VERR_GENERAL_FAILURE); break;
2691 }
2692
2693 HRESULT rc = S_OK;
2694 Utf8Str errorMsg;
2695
2696 bool deleteTarget = true;
2697
2698 if (task->operation == VDITask::CloneToImage)
2699 {
2700 Assert (!task->vdi->id().isEmpty());
2701 /// @todo (dmik) check locks
2702 AutoLock sourceLock (task->source);
2703 rc = task->source->cloneToImage (task->vdi->id(),
2704 Utf8Str (task->vdi->filePathFull()),
2705 task->progress, deleteTarget);
2706
2707 /* release reader added in HardDisk::CloneToImage() */
2708 task->source->releaseReader();
2709 }
2710 else
2711 {
2712 int vrc = VDICreateBaseImage (Utf8Str (task->vdi->filePathFull()),
2713 type, task->size,
2714 Utf8Str (task->vdi->mDescription),
2715 progressCallback,
2716 static_cast <Progress *> (task->progress));
2717
2718 /* We don't want to delete existing user files */
2719 if (vrc == VERR_ALREADY_EXISTS)
2720 deleteTarget = false;
2721
2722 if (VBOX_SUCCESS (vrc) && task->vdi->id())
2723 {
2724 /* we have a non-null UUID, update the created image */
2725 vrc = VDISetImageUUIDs (Utf8Str (task->vdi->filePathFull()),
2726 task->vdi->id().raw(), NULL, NULL, NULL);
2727 }
2728
2729 if (VBOX_FAILURE (vrc))
2730 {
2731 errorMsg = Utf8StrFmt (
2732 tr ("Falied to create a hard disk image '%ls' (%Vrc)"),
2733 task->vdi->filePathFull().raw(), vrc);
2734 rc = E_FAIL;
2735 }
2736 }
2737
2738 LogFlowFunc (("rc=%08X\n", rc));
2739
2740 AutoLock alock (task->vdi);
2741
2742 /* clear busy set in in HardDisk::CloneToImage() or
2743 * in HVirtualDiskImage::createImage() */
2744 task->vdi->clearBusy();
2745
2746 if (SUCCEEDED (rc))
2747 {
2748 task->vdi->mState = HVirtualDiskImage::Created;
2749 /* update VDI data fields */
2750 Bstr errMsg;
2751 rc = task->vdi->queryInformation (&errMsg);
2752 /* we want to deliver the access check result to the caller
2753 * immediately, before he calls HardDisk::GetAccssible() himself. */
2754 if (SUCCEEDED (rc) && !errMsg.isNull())
2755 task->progress->notifyCompleteBstr (
2756 E_FAIL, COM_IIDOF (IVirtualDiskImage), getComponentName(),
2757 errMsg);
2758 else
2759 task->progress->notifyComplete (rc);
2760 }
2761 else
2762 {
2763 /* delete the target file so we don't have orphaned files */
2764 if (deleteTarget)
2765 RTFileDelete(Utf8Str (task->vdi->filePathFull()));
2766
2767 task->vdi->mState = HVirtualDiskImage::NotCreated;
2768 /* complete the progress object */
2769 if (errorMsg)
2770 task->progress->notifyComplete (
2771 E_FAIL, COM_IIDOF (IVirtualDiskImage), getComponentName(),
2772 errorMsg);
2773 else
2774 task->progress->notifyComplete (rc);
2775 }
2776
2777 delete task;
2778
2779 return VINF_SUCCESS;
2780}
2781
2782////////////////////////////////////////////////////////////////////////////////
2783// HISCSIHardDisk class
2784////////////////////////////////////////////////////////////////////////////////
2785
2786// constructor / destructor
2787////////////////////////////////////////////////////////////////////////////////
2788
2789HRESULT HISCSIHardDisk::FinalConstruct()
2790{
2791 HRESULT rc = HardDisk::FinalConstruct();
2792 if (FAILED (rc))
2793 return rc;
2794
2795 mSize = 0;
2796 mActualSize = 0;
2797
2798 mPort = 0;
2799 mLun = 0;
2800
2801 return S_OK;
2802}
2803
2804void HISCSIHardDisk::FinalRelease()
2805{
2806 HardDisk::FinalRelease();
2807}
2808
2809// public initializer/uninitializer for internal purposes only
2810////////////////////////////////////////////////////////////////////////////////
2811
2812// public methods for internal purposes only
2813/////////////////////////////////////////////////////////////////////////////
2814
2815/**
2816 * Initializes the iSCSI hard disk object by reading its properties from
2817 * the given configuration node. The created hard disk will be marked as
2818 * registered on success.
2819 *
2820 * @param aHDNode <HardDisk> node
2821 * @param aVDINod <ISCSIHardDisk> node
2822 */
2823HRESULT HISCSIHardDisk::init (VirtualBox *aVirtualBox,
2824 CFGNODE aHDNode, CFGNODE aISCSINode)
2825{
2826 LogFlowThisFunc (("aHDNode=%p, aISCSINode=%p\n", aHDNode, aISCSINode));
2827
2828 AssertReturn (aHDNode && aISCSINode, E_FAIL);
2829
2830 AutoLock alock (this);
2831 ComAssertRet (!isReady(), E_UNEXPECTED);
2832
2833 mStorageType = HardDiskStorageType_ISCSIHardDisk;
2834
2835 HRESULT rc = S_OK;
2836
2837 do
2838 {
2839 rc = protectedInit (aVirtualBox, NULL);
2840 CheckComRCBreakRC (rc);
2841
2842 /* set ready to let protectedUninit() be called on failure */
2843 setReady (true);
2844
2845 /* server (required) */
2846 CFGLDRQueryBSTR (aISCSINode, "server", mServer.asOutParam());
2847 /* target (required) */
2848 CFGLDRQueryBSTR (aISCSINode, "target", mTarget.asOutParam());
2849
2850 /* port (optional) */
2851 CFGLDRQueryUInt16 (aISCSINode, "port", &mPort);
2852 /* lun (optional) */
2853 CFGLDRQueryUInt64 (aISCSINode, "lun", &mLun);
2854 /* userName (optional) */
2855 CFGLDRQueryBSTR (aISCSINode, "userName", mUserName.asOutParam());
2856 /* password (optional) */
2857 CFGLDRQueryBSTR (aISCSINode, "password", mPassword.asOutParam());
2858
2859 LogFlowThisFunc (("'iscsi:%ls:%hu@%ls/%ls:%llu'\n",
2860 mServer.raw(), mPort, mUserName.raw(), mTarget.raw(),
2861 mLun));
2862
2863 /* load basic settings and children */
2864 rc = loadSettings (aHDNode);
2865 CheckComRCBreakRC (rc);
2866
2867 if (mType != HardDiskType_WritethroughHardDisk)
2868 {
2869 rc = setError (E_FAIL,
2870 tr ("Currently, non-Writethrough iSCSI hard disks are not "
2871 "allowed ('%ls')"),
2872 toString().raw());
2873 break;
2874 }
2875
2876 mRegistered = TRUE;
2877 }
2878 while (0);
2879
2880 if (FAILED (rc))
2881 uninit();
2882
2883 return rc;
2884}
2885
2886/**
2887 * Initializes the iSCSI hard disk object using default values for all
2888 * properties. The created hard disk will NOT be marked as registered.
2889 */
2890HRESULT HISCSIHardDisk::init (VirtualBox *aVirtualBox)
2891{
2892 LogFlowThisFunc (("\n"));
2893
2894 AutoLock alock (this);
2895 ComAssertRet (!isReady(), E_UNEXPECTED);
2896
2897 mStorageType = HardDiskStorageType_ISCSIHardDisk;
2898
2899 HRESULT rc = S_OK;
2900
2901 do
2902 {
2903 rc = protectedInit (aVirtualBox, NULL);
2904 CheckComRCBreakRC (rc);
2905
2906 /* set ready to let protectedUninit() be called on failure */
2907 setReady (true);
2908
2909 /* we have to generate a new UUID */
2910 mId.create();
2911 /* currently, all iSCSI hard disks are writethrough */
2912 mType = HardDiskType_WritethroughHardDisk;
2913 mRegistered = FALSE;
2914 }
2915 while (0);
2916
2917 if (FAILED (rc))
2918 uninit();
2919
2920 return rc;
2921}
2922
2923/**
2924 * Uninitializes the instance and sets the ready flag to FALSE.
2925 * Called either from FinalRelease(), by the parent when it gets destroyed,
2926 * or by a third party when it decides this object is no more valid.
2927 */
2928void HISCSIHardDisk::uninit()
2929{
2930 LogFlowThisFunc (("\n"));
2931
2932 AutoLock alock (this);
2933 if (!isReady())
2934 return;
2935
2936 HardDisk::protectedUninit (alock);
2937}
2938
2939// IHardDisk properties
2940////////////////////////////////////////////////////////////////////////////////
2941
2942STDMETHODIMP HISCSIHardDisk::COMGETTER(Description) (BSTR *aDescription)
2943{
2944 if (!aDescription)
2945 return E_POINTER;
2946
2947 AutoLock alock (this);
2948 CHECK_READY();
2949
2950 mDescription.cloneTo (aDescription);
2951 return S_OK;
2952}
2953
2954STDMETHODIMP HISCSIHardDisk::COMSETTER(Description) (INPTR BSTR aDescription)
2955{
2956 AutoLock alock (this);
2957 CHECK_READY();
2958
2959 CHECK_BUSY_AND_READERS();
2960
2961 if (mDescription != aDescription)
2962 {
2963 mDescription = aDescription;
2964 if (mRegistered)
2965 return mVirtualBox->saveSettings();
2966 }
2967
2968 return S_OK;
2969}
2970
2971STDMETHODIMP HISCSIHardDisk::COMGETTER(Size) (ULONG64 *aSize)
2972{
2973 if (!aSize)
2974 return E_POINTER;
2975
2976 AutoLock alock (this);
2977 CHECK_READY();
2978
2979 *aSize = mSize;
2980 return S_OK;
2981}
2982
2983STDMETHODIMP HISCSIHardDisk::COMGETTER(ActualSize) (ULONG64 *aActualSize)
2984{
2985 if (!aActualSize)
2986 return E_POINTER;
2987
2988 AutoLock alock (this);
2989 CHECK_READY();
2990
2991 *aActualSize = mActualSize;
2992 return S_OK;
2993}
2994
2995// IISCSIHardDisk properties
2996////////////////////////////////////////////////////////////////////////////////
2997
2998STDMETHODIMP HISCSIHardDisk::COMGETTER(Server) (BSTR *aServer)
2999{
3000 if (!aServer)
3001 return E_POINTER;
3002
3003 AutoLock alock (this);
3004 CHECK_READY();
3005
3006 mServer.cloneTo (aServer);
3007 return S_OK;
3008}
3009
3010STDMETHODIMP HISCSIHardDisk::COMSETTER(Server) (INPTR BSTR aServer)
3011{
3012 if (!aServer || !*aServer)
3013 return E_INVALIDARG;
3014
3015 AutoLock alock (this);
3016 CHECK_READY();
3017
3018 CHECK_BUSY_AND_READERS();
3019
3020 if (mServer != aServer)
3021 {
3022 mServer = aServer;
3023 if (mRegistered)
3024 return mVirtualBox->saveSettings();
3025 }
3026
3027 return S_OK;
3028}
3029
3030STDMETHODIMP HISCSIHardDisk::COMGETTER(Port) (USHORT *aPort)
3031{
3032 if (!aPort)
3033 return E_POINTER;
3034
3035 AutoLock alock (this);
3036 CHECK_READY();
3037
3038 *aPort = mPort;
3039 return S_OK;
3040}
3041
3042STDMETHODIMP HISCSIHardDisk::COMSETTER(Port) (USHORT aPort)
3043{
3044 AutoLock alock (this);
3045 CHECK_READY();
3046
3047 CHECK_BUSY_AND_READERS();
3048
3049 if (mPort != aPort)
3050 {
3051 mPort = aPort;
3052 if (mRegistered)
3053 return mVirtualBox->saveSettings();
3054 }
3055
3056 return S_OK;
3057}
3058
3059STDMETHODIMP HISCSIHardDisk::COMGETTER(Target) (BSTR *aTarget)
3060{
3061 if (!aTarget)
3062 return E_POINTER;
3063
3064 AutoLock alock (this);
3065 CHECK_READY();
3066
3067 mTarget.cloneTo (aTarget);
3068 return S_OK;
3069}
3070
3071STDMETHODIMP HISCSIHardDisk::COMSETTER(Target) (INPTR BSTR aTarget)
3072{
3073 if (!aTarget || !*aTarget)
3074 return E_INVALIDARG;
3075
3076 AutoLock alock (this);
3077 CHECK_READY();
3078
3079 CHECK_BUSY_AND_READERS();
3080
3081 if (mTarget != aTarget)
3082 {
3083 mTarget = aTarget;
3084 if (mRegistered)
3085 return mVirtualBox->saveSettings();
3086 }
3087
3088 return S_OK;
3089}
3090
3091STDMETHODIMP HISCSIHardDisk::COMGETTER(Lun) (ULONG64 *aLun)
3092{
3093 if (!aLun)
3094 return E_POINTER;
3095
3096 AutoLock alock (this);
3097 CHECK_READY();
3098
3099 *aLun = mLun;
3100 return S_OK;
3101}
3102
3103STDMETHODIMP HISCSIHardDisk::COMSETTER(Lun) (ULONG64 aLun)
3104{
3105 AutoLock alock (this);
3106 CHECK_READY();
3107
3108 CHECK_BUSY_AND_READERS();
3109
3110 if (mLun != aLun)
3111 {
3112 mLun = aLun;
3113 if (mRegistered)
3114 return mVirtualBox->saveSettings();
3115 }
3116
3117 return S_OK;
3118}
3119
3120STDMETHODIMP HISCSIHardDisk::COMGETTER(UserName) (BSTR *aUserName)
3121{
3122 if (!aUserName)
3123 return E_POINTER;
3124
3125 AutoLock alock (this);
3126 CHECK_READY();
3127
3128 mUserName.cloneTo (aUserName);
3129 return S_OK;
3130}
3131
3132STDMETHODIMP HISCSIHardDisk::COMSETTER(UserName) (INPTR BSTR aUserName)
3133{
3134 AutoLock alock (this);
3135 CHECK_READY();
3136
3137 CHECK_BUSY_AND_READERS();
3138
3139 if (mUserName != aUserName)
3140 {
3141 mUserName = aUserName;
3142 if (mRegistered)
3143 return mVirtualBox->saveSettings();
3144 }
3145
3146 return S_OK;
3147}
3148
3149STDMETHODIMP HISCSIHardDisk::COMGETTER(Password) (BSTR *aPassword)
3150{
3151 if (!aPassword)
3152 return E_POINTER;
3153
3154 AutoLock alock (this);
3155 CHECK_READY();
3156
3157 mPassword.cloneTo (aPassword);
3158 return S_OK;
3159}
3160
3161STDMETHODIMP HISCSIHardDisk::COMSETTER(Password) (INPTR BSTR aPassword)
3162{
3163 AutoLock alock (this);
3164 CHECK_READY();
3165
3166 CHECK_BUSY_AND_READERS();
3167
3168 if (mPassword != aPassword)
3169 {
3170 mPassword = aPassword;
3171 if (mRegistered)
3172 return mVirtualBox->saveSettings();
3173 }
3174
3175 return S_OK;
3176}
3177
3178// public/protected methods for internal purposes only
3179/////////////////////////////////////////////////////////////////////////////
3180
3181/**
3182 * Attempts to mark the hard disk as registered.
3183 * Only VirtualBox can call this method.
3184 */
3185HRESULT HISCSIHardDisk::trySetRegistered (BOOL aRegistered)
3186{
3187 AutoLock alock (this);
3188 CHECK_READY();
3189
3190 if (aRegistered)
3191 {
3192 if (mServer.isEmpty() || mTarget.isEmpty())
3193 return setError (E_FAIL,
3194 tr ("iSCSI Hard disk has no server or target defined"));
3195 }
3196 else
3197 {
3198 }
3199
3200 return HardDisk::trySetRegistered (aRegistered);
3201}
3202
3203/**
3204 * Checks accessibility of this iSCSI hard disk.
3205 */
3206HRESULT HISCSIHardDisk::getAccessible (Bstr &aAccessError)
3207{
3208 AutoLock alock (this);
3209 CHECK_READY();
3210
3211 /* check the basic accessibility */
3212 HRESULT rc = getBaseAccessible (aAccessError);
3213 if (FAILED (rc) || !aAccessError.isNull())
3214 return rc;
3215
3216 return queryInformation (aAccessError);
3217}
3218
3219/**
3220 * Saves hard disk settings to the specified storage node and saves
3221 * all children to the specified hard disk node
3222 *
3223 * @param aHDNode <HardDisk>
3224 * @param aStorageNode <ISCSIHardDisk> node
3225 */
3226HRESULT HISCSIHardDisk::saveSettings (CFGNODE aHDNode, CFGNODE aStorageNode)
3227{
3228 AssertReturn (aHDNode && aStorageNode, E_FAIL);
3229
3230 AutoLock alock (this);
3231 CHECK_READY();
3232
3233 /* server (required) */
3234 CFGLDRSetBSTR (aStorageNode, "server", mServer);
3235 /* target (required) */
3236 CFGLDRSetBSTR (aStorageNode, "target", mTarget);
3237
3238 /* port (optional) */
3239 if (mPort != 0)
3240 CFGLDRSetUInt16 (aStorageNode, "port", mPort);
3241 else
3242 CFGLDRDeleteAttribute (aStorageNode, "port");
3243 /* lun (optional, force 0x format to coform to XML Schema!) */
3244 if (mLun != 0)
3245 CFGLDRSetUInt64Ex (aStorageNode, "lun", mLun, 16);
3246 else
3247 CFGLDRDeleteAttribute (aStorageNode, "lun");
3248 /* userName (optional) */
3249 if (!mUserName.isNull())
3250 CFGLDRSetBSTR (aStorageNode, "userName", mUserName);
3251 else
3252 CFGLDRDeleteAttribute (aStorageNode, "userName");
3253 /* password (optional) */
3254 if (!mPassword.isNull())
3255 CFGLDRSetBSTR (aStorageNode, "password", mPassword);
3256 else
3257 CFGLDRDeleteAttribute (aStorageNode, "password");
3258
3259 /* save basic settings and children */
3260 return HardDisk::saveSettings (aHDNode);
3261}
3262
3263/**
3264 * Returns the string representation of this hard disk.
3265 * When \a aShort is false, returns the full image file path.
3266 * Otherwise, returns the image file name only.
3267 *
3268 * @param aShort if true, a short representation is returned
3269 */
3270Bstr HISCSIHardDisk::toString (bool aShort /* = false */)
3271{
3272 AutoLock alock (this);
3273
3274 Bstr str;
3275 if (!aShort)
3276 {
3277 /* the format is iscsi:[<user@>]<server>[:<port>]/<target>[:<lun>] */
3278 str = Utf8StrFmt ("iscsi:%s%ls%s/%ls%s",
3279 !mUserName.isNull() ? Utf8StrFmt ("%ls@", mUserName.raw()).raw() : "",
3280 mServer.raw(),
3281 mPort != 0 ? Utf8StrFmt (":%hu", mPort).raw() : "",
3282 mTarget.raw(),
3283 mLun != 0 ? Utf8StrFmt (":%llu", mLun).raw() : "");
3284 }
3285 else
3286 {
3287 str = Utf8StrFmt ("%ls%s",
3288 mTarget.raw(),
3289 mLun != 0 ? Utf8StrFmt (":%llu", mLun).raw() : "");
3290 }
3291
3292 return str;
3293}
3294
3295/**
3296 * Creates a clone of this hard disk by storing hard disk data in the given
3297 * VDI file.
3298 *
3299 * If the operation fails, @a aDeleteTarget will be set to @c true unless the
3300 * failure happened because the target file already existed.
3301 *
3302 * @param aId UUID to assign to the created image.
3303 * @param aTargetPath VDI file where the cloned image is to be to stored.
3304 * @param aProgress progress object to run during operation.
3305 * @param aDeleteTarget Whether it is recommended to delete target on
3306 * failure or not.
3307 */
3308HRESULT
3309HISCSIHardDisk::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath,
3310 Progress *aProgress, bool &aDeleteTarget)
3311{
3312 ComAssertMsgFailed (("Not implemented"));
3313 return E_NOTIMPL;
3314
3315// AssertReturn (isBusy() == false, E_FAIL);
3316// addReader();
3317// releaseReader();
3318}
3319
3320/**
3321 * Creates a new differencing image for this hard disk with the given
3322 * VDI file name.
3323 *
3324 * @param aId UUID to assign to the created image
3325 * @param aTargetPath VDI file where to store the created differencing image
3326 * @param aProgress progress object to run during operation
3327 * (can be NULL)
3328 */
3329HRESULT
3330HISCSIHardDisk::createDiffImage (const Guid &aId, const Utf8Str &aTargetPath,
3331 Progress *aProgress)
3332{
3333 ComAssertMsgFailed (("Not implemented"));
3334 return E_NOTIMPL;
3335
3336// AssertReturn (isBusy() == false, E_FAIL);
3337// addReader();
3338// releaseReader();
3339}
3340
3341// private methods
3342/////////////////////////////////////////////////////////////////////////////
3343
3344/**
3345 * Helper to query information about the iSCSI hard disk.
3346 *
3347 * @param aAccessError see #getAccessible()
3348 * @note
3349 * Must be called from under the object's lock!
3350 */
3351HRESULT HISCSIHardDisk::queryInformation (Bstr &aAccessError)
3352{
3353 /// @todo (dmik) query info about this iSCSI disk,
3354 // set mSize and mActualSize,
3355 // or set aAccessError in case of failure
3356
3357 aAccessError.setNull();
3358 return S_OK;
3359}
3360
3361////////////////////////////////////////////////////////////////////////////////
3362// HVMDKImage class
3363////////////////////////////////////////////////////////////////////////////////
3364
3365// constructor / destructor
3366////////////////////////////////////////////////////////////////////////////////
3367
3368HRESULT HVMDKImage::FinalConstruct()
3369{
3370 HRESULT rc = HardDisk::FinalConstruct();
3371 if (FAILED (rc))
3372 return rc;
3373
3374 mState = NotCreated;
3375
3376 mStateCheckSem = NIL_RTSEMEVENTMULTI;
3377 mStateCheckWaiters = 0;
3378
3379 mSize = 0;
3380 mActualSize = 0;
3381
3382 /* initialize the container */
3383 int vrc = VDCreate ("VMDK", VDError, this, &mContainer);
3384 ComAssertRCRet (vrc, E_FAIL);
3385
3386 return S_OK;
3387}
3388
3389void HVMDKImage::FinalRelease()
3390{
3391 if (mContainer != NULL)
3392 VDDestroy (mContainer);
3393
3394 HardDisk::FinalRelease();
3395}
3396
3397// public initializer/uninitializer for internal purposes only
3398////////////////////////////////////////////////////////////////////////////////
3399
3400// public methods for internal purposes only
3401/////////////////////////////////////////////////////////////////////////////
3402
3403/**
3404 * Initializes the VMDK hard disk object by reading its properties from
3405 * the given configuration node. The created hard disk will be marked as
3406 * registered on success.
3407 *
3408 * @param aHDNode <HardDisk> node
3409 * @param aVMDKNode <VirtualDiskImage> node
3410 */
3411HRESULT HVMDKImage::init (VirtualBox *aVirtualBox, HardDisk *aParent,
3412 CFGNODE aHDNode, CFGNODE aVMDKNode)
3413{
3414 LogFlowThisFunc (("aHDNode=%p, aVMDKNode=%p\n", aHDNode, aVMDKNode));
3415
3416 AssertReturn (aHDNode && aVMDKNode, E_FAIL);
3417
3418 AutoLock alock (this);
3419 ComAssertRet (!isReady(), E_UNEXPECTED);
3420
3421 mStorageType = HardDiskStorageType_VMDKImage;
3422
3423 HRESULT rc = S_OK;
3424
3425 do
3426 {
3427 rc = protectedInit (aVirtualBox, aParent);
3428 CheckComRCBreakRC (rc);
3429
3430 /* set ready to let protectedUninit() be called on failure */
3431 setReady (true);
3432
3433 /* filePath (required) */
3434 Bstr filePath;
3435 CFGLDRQueryBSTR (aVMDKNode, "filePath", filePath.asOutParam());
3436
3437 rc = setFilePath (filePath);
3438 CheckComRCBreakRC (rc);
3439
3440 LogFlowThisFunc (("'%ls'\n", mFilePathFull.raw()));
3441
3442 /* load basic settings and children */
3443 rc = loadSettings (aHDNode);
3444 CheckComRCBreakRC (rc);
3445
3446 if (mType != HardDiskType_WritethroughHardDisk)
3447 {
3448 rc = setError (E_FAIL,
3449 tr ("Currently, non-Writethrough VMDK images are not "
3450 "allowed ('%ls')"),
3451 toString().raw());
3452 break;
3453 }
3454
3455 mState = Created;
3456 mRegistered = TRUE;
3457
3458 /* Don't call queryInformation() for registered hard disks to
3459 * prevent the calling thread (i.e. the VirtualBox server startup
3460 * thread) from an unexpected freeze. The vital mId property (UUID)
3461 * is read from the registry file in loadSettings(). To get the rest,
3462 * the user will have to call COMGETTER(Accessible) manually. */
3463 }
3464 while (0);
3465
3466 if (FAILED (rc))
3467 uninit();
3468
3469 return rc;
3470}
3471
3472/**
3473 * Initializes the VMDK hard disk object using the given image file name.
3474 *
3475 * @param aVirtualBox VirtualBox parent.
3476 * @param aParent Currently, must always be @c NULL.
3477 * @param aFilePath Path to the image file, or @c NULL to create an
3478 * image-less object.
3479 * @param aRegistered Whether to mark this disk as registered or not
3480 * (ignored when @a aFilePath is @c NULL, assuming @c FALSE)
3481 */
3482HRESULT HVMDKImage::init (VirtualBox *aVirtualBox, HardDisk *aParent,
3483 const BSTR aFilePath, BOOL aRegistered /* = FALSE */)
3484{
3485 LogFlowThisFunc (("aFilePath='%ls', aRegistered=%d\n", aFilePath, aRegistered));
3486
3487 AssertReturn (aParent == NULL, E_FAIL);
3488
3489 AutoLock alock (this);
3490 ComAssertRet (!isReady(), E_UNEXPECTED);
3491
3492 mStorageType = HardDiskStorageType_VMDKImage;
3493
3494 HRESULT rc = S_OK;
3495
3496 do
3497 {
3498 rc = protectedInit (aVirtualBox, aParent);
3499 CheckComRCBreakRC (rc);
3500
3501 /* set ready to let protectedUninit() be called on failure */
3502 setReady (true);
3503
3504 rc = setFilePath (aFilePath);
3505 CheckComRCBreakRC (rc);
3506
3507 /* currently, all VMDK hard disks are writethrough */
3508 mType = HardDiskType_WritethroughHardDisk;
3509
3510 Assert (mId.isEmpty());
3511
3512 if (aFilePath && *aFilePath)
3513 {
3514 mRegistered = aRegistered;
3515 mState = Created;
3516
3517 /* Call queryInformation() anyway (even if it will block), because
3518 * it is the only way to get the UUID of the existing VDI and
3519 * initialize the vital mId property. */
3520 Bstr errMsg;
3521 rc = queryInformation (&errMsg);
3522 if (SUCCEEDED (rc))
3523 {
3524 /* We are constructing a new HVirtualDiskImage object. If there
3525 * is a fatal accessibility error (we cannot read image UUID),
3526 * we have to fail. We do so even on non-fatal errors as well,
3527 * because it's not worth to keep going with the inaccessible
3528 * image from the very beginning (when nothing else depends on
3529 * it yet). */
3530 if (!errMsg.isNull())
3531 rc = setErrorBstr (E_FAIL, errMsg);
3532 }
3533 }
3534 else
3535 {
3536 mRegistered = FALSE;
3537 mState = NotCreated;
3538 mId.create();
3539 }
3540 }
3541 while (0);
3542
3543 if (FAILED (rc))
3544 uninit();
3545
3546 return rc;
3547}
3548
3549/**
3550 * Uninitializes the instance and sets the ready flag to FALSE.
3551 * Called either from FinalRelease(), by the parent when it gets destroyed,
3552 * or by a third party when it decides this object is no more valid.
3553 */
3554void HVMDKImage::uninit()
3555{
3556 LogFlowThisFunc (("\n"));
3557
3558 AutoLock alock (this);
3559 if (!isReady())
3560 return;
3561
3562 HardDisk::protectedUninit (alock);
3563}
3564
3565// IHardDisk properties
3566////////////////////////////////////////////////////////////////////////////////
3567
3568STDMETHODIMP HVMDKImage::COMGETTER(Description) (BSTR *aDescription)
3569{
3570 if (!aDescription)
3571 return E_POINTER;
3572
3573 AutoLock alock (this);
3574 CHECK_READY();
3575
3576 mDescription.cloneTo (aDescription);
3577 return S_OK;
3578}
3579
3580STDMETHODIMP HVMDKImage::COMSETTER(Description) (INPTR BSTR aDescription)
3581{
3582 AutoLock alock (this);
3583 CHECK_READY();
3584
3585 CHECK_BUSY_AND_READERS();
3586
3587 return E_NOTIMPL;
3588
3589/// @todo (r=dmik) implement
3590//
3591// if (mState >= Created)
3592// {
3593// int vrc = VDISetImageComment (Utf8Str (mFilePathFull), Utf8Str (aDescription));
3594// if (VBOX_FAILURE (vrc))
3595// return setError (E_FAIL,
3596// tr ("Could not change the description of the VDI hard disk '%ls' "
3597// "(%Vrc)"),
3598// toString().raw(), vrc);
3599// }
3600//
3601// mDescription = aDescription;
3602// return S_OK;
3603}
3604
3605STDMETHODIMP HVMDKImage::COMGETTER(Size) (ULONG64 *aSize)
3606{
3607 if (!aSize)
3608 return E_POINTER;
3609
3610 AutoLock alock (this);
3611 CHECK_READY();
3612
3613/// @todo (r=dmik) will need this if we add suppord for differencing VMDKs
3614//
3615// /* only a non-differencing image knows the logical size */
3616// if (isDifferencing())
3617// return root()->COMGETTER(Size) (aSize);
3618
3619 *aSize = mSize;
3620 return S_OK;
3621}
3622
3623STDMETHODIMP HVMDKImage::COMGETTER(ActualSize) (ULONG64 *aActualSize)
3624{
3625 if (!aActualSize)
3626 return E_POINTER;
3627
3628 AutoLock alock (this);
3629 CHECK_READY();
3630
3631 *aActualSize = mActualSize;
3632 return S_OK;
3633}
3634
3635// IVirtualDiskImage properties
3636////////////////////////////////////////////////////////////////////////////////
3637
3638STDMETHODIMP HVMDKImage::COMGETTER(FilePath) (BSTR *aFilePath)
3639{
3640 if (!aFilePath)
3641 return E_POINTER;
3642
3643 AutoLock alock (this);
3644 CHECK_READY();
3645
3646 mFilePathFull.cloneTo (aFilePath);
3647 return S_OK;
3648}
3649
3650STDMETHODIMP HVMDKImage::COMSETTER(FilePath) (INPTR BSTR aFilePath)
3651{
3652 AutoLock alock (this);
3653 CHECK_READY();
3654
3655 if (mState != NotCreated)
3656 return setError (E_ACCESSDENIED,
3657 tr ("Cannot change the file path of the existing hard disk '%ls'"),
3658 toString().raw());
3659
3660 CHECK_BUSY_AND_READERS();
3661
3662 /* append the default path if only a name is given */
3663 Bstr path = aFilePath;
3664 if (aFilePath && *aFilePath)
3665 {
3666 Utf8Str fp = aFilePath;
3667 if (!RTPathHavePath (fp))
3668 {
3669 AutoReaderLock propsLock (mVirtualBox->systemProperties());
3670 path = Utf8StrFmt ("%ls%c%s",
3671 mVirtualBox->systemProperties()->defaultVDIFolder().raw(),
3672 RTPATH_DELIMITER,
3673 fp.raw());
3674 }
3675 }
3676
3677 return setFilePath (path);
3678}
3679
3680STDMETHODIMP HVMDKImage::COMGETTER(Created) (BOOL *aCreated)
3681{
3682 if (!aCreated)
3683 return E_POINTER;
3684
3685 AutoLock alock (this);
3686 CHECK_READY();
3687
3688 *aCreated = mState >= Created;
3689 return S_OK;
3690}
3691
3692// IVMDKImage methods
3693/////////////////////////////////////////////////////////////////////////////
3694
3695STDMETHODIMP HVMDKImage::CreateDynamicImage (ULONG64 aSize, IProgress **aProgress)
3696{
3697 if (!aProgress)
3698 return E_POINTER;
3699
3700 AutoLock alock (this);
3701 CHECK_READY();
3702
3703 return createImage (aSize, TRUE /* aDynamic */, aProgress);
3704}
3705
3706STDMETHODIMP HVMDKImage::CreateFixedImage (ULONG64 aSize, IProgress **aProgress)
3707{
3708 if (!aProgress)
3709 return E_POINTER;
3710
3711 AutoLock alock (this);
3712 CHECK_READY();
3713
3714 return createImage (aSize, FALSE /* aDynamic */, aProgress);
3715}
3716
3717STDMETHODIMP HVMDKImage::DeleteImage()
3718{
3719 AutoLock alock (this);
3720 CHECK_READY();
3721 CHECK_BUSY_AND_READERS();
3722
3723 return E_NOTIMPL;
3724
3725/// @todo (r=dmik) later
3726// We will need to parse the file in order to delete all related delta and
3727// sparse images etc. We may also want to obey the .vmdk.lck file
3728// which is (as far as I understood) created when the VMware VM is
3729// running or saved etc.
3730//
3731// if (mRegistered)
3732// return setError (E_ACCESSDENIED,
3733// tr ("Cannot delete an image of the registered hard disk image '%ls"),
3734// mFilePathFull.raw());
3735// if (mState == NotCreated)
3736// return setError (E_FAIL,
3737// tr ("Hard disk image has been already deleted or never created"));
3738//
3739// int vrc = RTFileDelete (Utf8Str (mFilePathFull));
3740// if (VBOX_FAILURE (vrc))
3741// return setError (E_FAIL, tr ("Could not delete the image file '%ls' (%Vrc)"),
3742// mFilePathFull.raw(), vrc);
3743//
3744// mState = NotCreated;
3745//
3746// /* reset the fields */
3747// mSize = 0;
3748// mActualSize = 0;
3749//
3750// return S_OK;
3751}
3752
3753// public/protected methods for internal purposes only
3754/////////////////////////////////////////////////////////////////////////////
3755
3756/**
3757 * Attempts to mark the hard disk as registered.
3758 * Only VirtualBox can call this method.
3759 */
3760HRESULT HVMDKImage::trySetRegistered (BOOL aRegistered)
3761{
3762 AutoLock alock (this);
3763 CHECK_READY();
3764
3765 if (aRegistered)
3766 {
3767 if (mState == NotCreated)
3768 return setError (E_FAIL,
3769 tr ("Image file '%ls' is not yet created for this hard disk"),
3770 mFilePathFull.raw());
3771
3772/// @todo (r=dmik) will need this if we add suppord for differencing VMDKs
3773// if (isDifferencing())
3774// return setError (E_FAIL,
3775// tr ("Hard disk '%ls' is differencing and cannot be unregistered "
3776// "explicitly"),
3777// mFilePathFull.raw());
3778 }
3779 else
3780 {
3781 ComAssertRet (mState >= Created, E_FAIL);
3782 }
3783
3784 return HardDisk::trySetRegistered (aRegistered);
3785}
3786
3787/**
3788 * Checks accessibility of this hard disk image only (w/o parents).
3789 *
3790 * @param aAccessError on output, a null string indicates the hard disk is
3791 * accessible, otherwise contains a message describing
3792 * the reason of inaccessibility.
3793 */
3794HRESULT HVMDKImage::getAccessible (Bstr &aAccessError)
3795{
3796 AutoLock alock (this);
3797 CHECK_READY();
3798
3799 if (mStateCheckSem != NIL_RTSEMEVENTMULTI)
3800 {
3801 /* An accessibility check in progress on some other thread,
3802 * wait for it to finish. */
3803
3804 ComAssertRet (mStateCheckWaiters != (ULONG) ~0, E_FAIL);
3805 ++ mStateCheckWaiters;
3806 alock.leave();
3807
3808 int vrc = RTSemEventMultiWait (mStateCheckSem, RT_INDEFINITE_WAIT);
3809
3810 alock.enter();
3811 AssertReturn (mStateCheckWaiters != 0, E_FAIL);
3812 -- mStateCheckWaiters;
3813 if (mStateCheckWaiters == 0)
3814 {
3815 RTSemEventMultiDestroy (mStateCheckSem);
3816 mStateCheckSem = NIL_RTSEMEVENTMULTI;
3817 }
3818
3819 AssertRCReturn (vrc, E_FAIL);
3820
3821 /* don't touch aAccessError, it has been already set */
3822 return S_OK;
3823 }
3824
3825 /* check the basic accessibility */
3826 HRESULT rc = getBaseAccessible (aAccessError, true /* aCheckBusy */);
3827 if (FAILED (rc) || !aAccessError.isNull())
3828 return rc;
3829
3830 if (mState >= Created)
3831 {
3832 return queryInformation (&aAccessError);
3833 }
3834
3835 aAccessError = Utf8StrFmt ("Hard disk image '%ls' is not yet created",
3836 mFilePathFull.raw());
3837 return S_OK;
3838}
3839
3840/**
3841 * Saves hard disk settings to the specified storage node and saves
3842 * all children to the specified hard disk node
3843 *
3844 * @param aHDNode <HardDisk> or <DiffHardDisk> node
3845 * @param aStorageNode <VirtualDiskImage> node
3846 */
3847HRESULT HVMDKImage::saveSettings (CFGNODE aHDNode, CFGNODE aStorageNode)
3848{
3849 AssertReturn (aHDNode && aStorageNode, E_FAIL);
3850
3851 AutoLock alock (this);
3852 CHECK_READY();
3853
3854 /* filePath (required) */
3855 CFGLDRSetBSTR (aStorageNode, "filePath", mFilePath);
3856
3857 /* save basic settings and children */
3858 return HardDisk::saveSettings (aHDNode);
3859}
3860
3861/**
3862 * Checks if the given change of \a aOldPath to \a aNewPath affects the path
3863 * of this hard disk and updates it if necessary to reflect the new location.
3864 * Intended to be from HardDisk::updatePaths().
3865 *
3866 * @param aOldPath old path (full)
3867 * @param aNewPath new path (full)
3868 *
3869 * @note Locks this object for writing.
3870 */
3871void HVMDKImage::updatePath (const char *aOldPath, const char *aNewPath)
3872{
3873 AssertReturnVoid (aOldPath);
3874 AssertReturnVoid (aNewPath);
3875
3876 AutoLock alock (this);
3877 AssertReturnVoid (isReady());
3878
3879 size_t oldPathLen = strlen (aOldPath);
3880
3881 Utf8Str path = mFilePathFull;
3882 LogFlowThisFunc (("VMDK.fullPath={%s}\n", path.raw()));
3883
3884 if (RTPathStartsWith (path, aOldPath))
3885 {
3886 Utf8Str newPath = Utf8StrFmt ("%s%s", aNewPath,
3887 path.raw() + oldPathLen);
3888 path = newPath;
3889
3890 mVirtualBox->calculateRelativePath (path, path);
3891
3892 unconst (mFilePathFull) = newPath;
3893 unconst (mFilePath) = path;
3894
3895 LogFlowThisFunc (("-> updated: full={%s} short={%s}\n",
3896 newPath.raw(), path.raw()));
3897 }
3898}
3899
3900/**
3901 * Returns the string representation of this hard disk.
3902 * When \a aShort is false, returns the full image file path.
3903 * Otherwise, returns the image file name only.
3904 *
3905 * @param aShort if true, a short representation is returned
3906 */
3907Bstr HVMDKImage::toString (bool aShort /* = false */)
3908{
3909 AutoLock alock (this);
3910
3911 if (!aShort)
3912 return mFilePathFull;
3913 else
3914 {
3915 Utf8Str fname = mFilePathFull;
3916 return RTPathFilename (fname.mutableRaw());
3917 }
3918}
3919
3920/**
3921 * Creates a clone of this hard disk by storing hard disk data in the given
3922 * VDI file.
3923 *
3924 * If the operation fails, @a aDeleteTarget will be set to @c true unless the
3925 * failure happened because the target file already existed.
3926 *
3927 * @param aId UUID to assign to the created image.
3928 * @param aTargetPath VDI file where the cloned image is to be to stored.
3929 * @param aProgress progress object to run during operation.
3930 * @param aDeleteTarget Whether it is recommended to delete target on
3931 * failure or not.
3932 */
3933HRESULT
3934HVMDKImage::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath,
3935 Progress *aProgress, bool &aDeleteTarget)
3936{
3937 ComAssertMsgFailed (("Not implemented"));
3938 return E_NOTIMPL;
3939
3940/// @todo (r=dmik) will need this if we add suppord for differencing VMDKs
3941// Use code from HVirtualDiskImage::cloneToImage as an example.
3942}
3943
3944/**
3945 * Creates a new differencing image for this hard disk with the given
3946 * VDI file name.
3947 *
3948 * @param aId UUID to assign to the created image
3949 * @param aTargetPath VDI file where to store the created differencing image
3950 * @param aProgress progress object to run during operation
3951 * (can be NULL)
3952 */
3953HRESULT
3954HVMDKImage::createDiffImage (const Guid &aId, const Utf8Str &aTargetPath,
3955 Progress *aProgress)
3956{
3957 ComAssertMsgFailed (("Not implemented"));
3958 return E_NOTIMPL;
3959
3960/// @todo (r=dmik) will need this if we add suppord for differencing VMDKs
3961// Use code from HVirtualDiskImage::createDiffImage as an example.
3962}
3963
3964// private methods
3965/////////////////////////////////////////////////////////////////////////////
3966
3967/**
3968 * Helper to set a new file path.
3969 * Resolves a path relatively to the Virtual Box home directory.
3970 *
3971 * @note
3972 * Must be called from under the object's lock!
3973 */
3974HRESULT HVMDKImage::setFilePath (const BSTR aFilePath)
3975{
3976 if (aFilePath && *aFilePath)
3977 {
3978 /* get the full file name */
3979 char filePathFull [RTPATH_MAX];
3980 int vrc = RTPathAbsEx (mVirtualBox->homeDir(), Utf8Str (aFilePath),
3981 filePathFull, sizeof (filePathFull));
3982 if (VBOX_FAILURE (vrc))
3983 return setError (E_FAIL,
3984 tr ("Invalid image file path '%ls' (%Vrc)"), aFilePath, vrc);
3985
3986 mFilePath = aFilePath;
3987 mFilePathFull = filePathFull;
3988 }
3989 else
3990 {
3991 mFilePath.setNull();
3992 mFilePathFull.setNull();
3993 }
3994
3995 return S_OK;
3996}
3997
3998/**
3999 * Helper to query information about the VDI hard disk.
4000 *
4001 * @param aAccessError not used when NULL, otherwise see #getAccessible()
4002 *
4003 * @note Must be called from under the object's lock, only after
4004 * CHECK_BUSY_AND_READERS() succeeds.
4005 */
4006HRESULT HVMDKImage::queryInformation (Bstr *aAccessError)
4007{
4008 AssertReturn (isLockedOnCurrentThread(), E_FAIL);
4009
4010 /* create a lock object to completely release it later */
4011 AutoLock alock (this);
4012
4013 AssertReturn (mStateCheckWaiters == 0, E_FAIL);
4014
4015 ComAssertRet (mState >= Created, E_FAIL);
4016
4017 HRESULT rc = S_OK;
4018 int vrc = VINF_SUCCESS;
4019
4020 /* lazily create a semaphore */
4021 vrc = RTSemEventMultiCreate (&mStateCheckSem);
4022 ComAssertRCRet (vrc, E_FAIL);
4023
4024 /* Reference the disk to prevent any concurrent modifications
4025 * after releasing the lock below (to unblock getters before
4026 * a lengthy operation). */
4027 addReader();
4028
4029 alock.leave();
4030
4031 /* VBoxVHDD management interface needs to be optimized: we're opening a
4032 * file three times in a raw to get three bits of information. */
4033
4034 Utf8Str filePath = mFilePathFull;
4035 Bstr errMsg;
4036
4037 /* reset any previous error report from VDError() */
4038 mLastVDError.setNull();
4039
4040 do
4041 {
4042 Guid id, parentId;
4043
4044 /// @todo changed from VD_OPEN_FLAGS_READONLY to VD_OPEN_FLAGS_NORMAL,
4045 /// because otherwise registering a VMDK which so far has no UUID will
4046 /// yield a null UUID. It cannot be added to a VMDK opened readonly,
4047 /// obviously. This of course changes locking behavior, but for now
4048 /// this is acceptable. A better solution needs to be found later.
4049 vrc = VDOpen (mContainer, filePath, VD_OPEN_FLAGS_NORMAL);
4050 if (VBOX_FAILURE (vrc))
4051 break;
4052
4053 vrc = VDGetUuid (mContainer, 0, id.ptr());
4054 if (VBOX_FAILURE (vrc))
4055 break;
4056 vrc = VDGetParentUuid (mContainer, 0, parentId.ptr());
4057 if (VBOX_FAILURE (vrc))
4058 break;
4059
4060 if (!mId.isEmpty())
4061 {
4062 /* check that the actual UUID of the image matches the stored UUID */
4063 if (mId != id)
4064 {
4065 errMsg = Utf8StrFmt (
4066 tr ("Actual UUID {%Vuuid} of the hard disk image '%s' doesn't "
4067 "match UUID {%Vuuid} stored in the registry"),
4068 id.ptr(), filePath.raw(), mId.ptr());
4069 break;
4070 }
4071 }
4072 else
4073 {
4074 /* assgn an UUID read from the image file */
4075 mId = id;
4076 }
4077
4078 if (mParent)
4079 {
4080 /* check parent UUID */
4081 AutoLock parentLock (mParent);
4082 if (mParent->id() != parentId)
4083 {
4084 errMsg = Utf8StrFmt (
4085 tr ("UUID {%Vuuid} of the parent image '%ls' stored in "
4086 "the hard disk image file '%s' doesn't match "
4087 "UUID {%Vuuid} stored in the registry"),
4088 parentId.raw(), mParent->toString().raw(),
4089 filePath.raw(), mParent->id().raw());
4090 break;
4091 }
4092 }
4093 else if (!parentId.isEmpty())
4094 {
4095 errMsg = Utf8StrFmt (
4096 tr ("Hard disk image '%s' is a differencing image that is linked "
4097 "to a hard disk with UUID {%Vuuid} and cannot be used "
4098 "directly as a base hard disk"),
4099 filePath.raw(), parentId.raw());
4100 break;
4101 }
4102
4103 /* get actual file size */
4104 /// @todo is there a direct method in RT?
4105 {
4106 RTFILE file = NIL_RTFILE;
4107 vrc = RTFileOpen (&file, filePath, RTFILE_O_READ);
4108 if (VBOX_SUCCESS (vrc))
4109 {
4110 uint64_t size = 0;
4111 vrc = RTFileGetSize (file, &size);
4112 if (VBOX_SUCCESS (vrc))
4113 mActualSize = size;
4114 RTFileClose (file);
4115 }
4116 if (VBOX_FAILURE (vrc))
4117 break;
4118 }
4119
4120 /* query logical size only for non-differencing images */
4121 if (!mParent)
4122 {
4123 uint64_t size = VDGetSize (mContainer);
4124 /* convert to MBytes */
4125 mSize = size / 1024 / 1024;
4126 }
4127 }
4128 while (0);
4129
4130 VDCloseAll (mContainer);
4131
4132 /* enter the lock again */
4133 alock.enter();
4134
4135 /* remove the reference */
4136 releaseReader();
4137
4138 if (FAILED (rc) || VBOX_FAILURE (vrc) || !errMsg.isNull())
4139 {
4140 LogWarningFunc (("'%ls' is not accessible "
4141 "(rc=%08X, vrc=%Vrc, errMsg='%ls', mLastVDError='%s')\n",
4142 mFilePathFull.raw(), rc, vrc, errMsg.raw(), mLastVDError.raw()));
4143
4144 if (aAccessError)
4145 {
4146 if (!errMsg.isNull())
4147 *aAccessError = errMsg;
4148 else if (!mLastVDError.isNull())
4149 *aAccessError = mLastVDError;
4150 else if (VBOX_FAILURE (vrc))
4151 *aAccessError = Utf8StrFmt (
4152 tr ("Could not access hard disk image '%ls' (%Vrc)"),
4153 mFilePathFull.raw(), vrc);
4154 }
4155
4156 /* downgrade to not accessible */
4157 mState = Created;
4158 }
4159 else
4160 {
4161 if (aAccessError)
4162 aAccessError->setNull();
4163
4164 mState = Accessible;
4165 }
4166
4167 /* inform waiters if there are any */
4168 if (mStateCheckWaiters > 0)
4169 {
4170 RTSemEventMultiSignal (mStateCheckSem);
4171 }
4172 else
4173 {
4174 /* delete the semaphore ourselves */
4175 RTSemEventMultiDestroy (mStateCheckSem);
4176 mStateCheckSem = NIL_RTSEMEVENTMULTI;
4177 }
4178
4179 /* cleanup the last error report from VDError() */
4180 mLastVDError.setNull();
4181
4182 return rc;
4183}
4184
4185/**
4186 * Helper to create hard disk images.
4187 *
4188 * @param aSize size in MB
4189 * @param aDynamic dynamic or fixed image
4190 * @param aProgress address of IProgress pointer to return
4191 */
4192HRESULT HVMDKImage::createImage (ULONG64 aSize, BOOL aDynamic,
4193 IProgress **aProgress)
4194{
4195 ComAssertMsgFailed (("Not implemented"));
4196 return E_NOTIMPL;
4197
4198/// @todo (r=dmik) later
4199// Use code from HVirtualDiskImage::createImage as an example.
4200}
4201
4202/* static */
4203DECLCALLBACK(int) HVMDKImage::VDITaskThread (RTTHREAD thread, void *pvUser)
4204{
4205 AssertMsgFailed (("Not implemented"));
4206 return VERR_GENERAL_FAILURE;
4207
4208/// @todo (r=dmik) later
4209// Use code from HVirtualDiskImage::VDITaskThread as an example.
4210}
4211
4212/* static */
4213DECLCALLBACK(void) HVMDKImage::VDError (void *pvUser, int rc, RT_SRC_POS_DECL,
4214 const char *pszFormat, va_list va)
4215{
4216 HVMDKImage *that = static_cast <HVMDKImage *> (pvUser);
4217 AssertReturnVoid (that != NULL);
4218
4219 /// @todo pass the error message to the operation initiator
4220 Utf8Str err = Utf8StrFmtVA (pszFormat, va);
4221 if (VBOX_FAILURE (rc))
4222 err = Utf8StrFmt ("%s (%Vrc)", err.raw(), rc);
4223
4224 if (that->mLastVDError.isNull())
4225 that->mLastVDError = err;
4226 else
4227 that->mLastVDError = Utf8StrFmt
4228 ("%s.\n%s", that->mLastVDError.raw(), err.raw());
4229}
4230
4231////////////////////////////////////////////////////////////////////////////////
4232// HCustomHardDisk class
4233////////////////////////////////////////////////////////////////////////////////
4234
4235// constructor / destructor
4236////////////////////////////////////////////////////////////////////////////////
4237
4238HRESULT HCustomHardDisk::FinalConstruct()
4239{
4240 HRESULT rc = HardDisk::FinalConstruct();
4241 if (FAILED (rc))
4242 return rc;
4243
4244 mState = NotCreated;
4245
4246 mStateCheckSem = NIL_RTSEMEVENTMULTI;
4247 mStateCheckWaiters = 0;
4248
4249 mSize = 0;
4250 mActualSize = 0;
4251 mContainer = NULL;
4252
4253 ComAssertRCRet (rc, E_FAIL);
4254
4255 return S_OK;
4256}
4257
4258void HCustomHardDisk::FinalRelease()
4259{
4260 if (mContainer != NULL)
4261 VDDestroy (mContainer);
4262
4263 HardDisk::FinalRelease();
4264}
4265
4266// public initializer/uninitializer for internal purposes only
4267////////////////////////////////////////////////////////////////////////////////
4268
4269// public methods for internal purposes only
4270/////////////////////////////////////////////////////////////////////////////
4271
4272/**
4273 * Initializes the custom hard disk object by reading its properties from
4274 * the given configuration node. The created hard disk will be marked as
4275 * registered on success.
4276 *
4277 * @param aHDNode <HardDisk> node
4278 * @param aCustomNode <VirtualDiskImage> node
4279 */
4280HRESULT HCustomHardDisk::init (VirtualBox *aVirtualBox, HardDisk *aParent,
4281 CFGNODE aHDNode, CFGNODE aCustomNode)
4282{
4283 LogFlowThisFunc (("aHDNode=%p, aCustomNode=%p\n", aHDNode, aCustomNode));
4284
4285 AssertReturn (aHDNode && aCustomNode, E_FAIL);
4286
4287 AutoLock alock (this);
4288 ComAssertRet (!isReady(), E_UNEXPECTED);
4289
4290 mStorageType = HardDiskStorageType_CustomHardDisk;
4291
4292 HRESULT rc = S_OK;
4293 int vrc = VINF_SUCCESS;
4294 do
4295 {
4296 rc = protectedInit (aVirtualBox, aParent);
4297 CheckComRCBreakRC (rc);
4298
4299 /* set ready to let protectedUninit() be called on failure */
4300 setReady (true);
4301
4302 /* location (required) */
4303 Bstr location;
4304 CFGLDRQueryBSTR (aCustomNode, "location", location.asOutParam());
4305
4306 rc = setLocation (location);
4307 CheckComRCBreakRC (rc);
4308
4309 LogFlowThisFunc (("'%ls'\n", mLocationFull.raw()));
4310
4311 /* format (required) */
4312 CFGLDRQueryBSTR (aCustomNode, "format", mFormat.asOutParam());
4313
4314 /* initialize the container */
4315 vrc = VDCreate (Utf8Str (mFormat), VDError, this, &mContainer);
4316 if (VBOX_FAILURE (vrc))
4317 {
4318 AssertRC (vrc);
4319 if (mLastVDError.isEmpty())
4320 rc = setError (E_FAIL,
4321 tr ("Unknown format '%ls' of the custom "
4322 "hard disk '%ls' (%Vrc)"),
4323 mFormat.raw(), toString().raw(), vrc);
4324 else
4325 rc = setErrorBstr (E_FAIL, mLastVDError);
4326 break;
4327 }
4328
4329 /* load basic settings and children */
4330 rc = loadSettings (aHDNode);
4331 CheckComRCBreakRC (rc);
4332
4333 if (mType != HardDiskType_WritethroughHardDisk)
4334 {
4335 rc = setError (E_FAIL,
4336 tr ("Currently, non-Writethrough custom hard disks "
4337 "are not allowed ('%ls')"),
4338 toString().raw());
4339 break;
4340 }
4341
4342 mState = Created;
4343 mRegistered = TRUE;
4344
4345 /* Don't call queryInformation() for registered hard disks to
4346 * prevent the calling thread (i.e. the VirtualBox server startup
4347 * thread) from an unexpected freeze. The vital mId property (UUID)
4348 * is read from the registry file in loadSettings(). To get the rest,
4349 * the user will have to call COMGETTER(Accessible) manually. */
4350 }
4351 while (0);
4352
4353 if (FAILED (rc))
4354 uninit();
4355
4356 return rc;
4357}
4358
4359/**
4360 * Initializes the custom hard disk object using the given image file name.
4361 *
4362 * @param aVirtualBox VirtualBox parent.
4363 * @param aParent Currently, must always be @c NULL.
4364 * @param aLocation Location of the virtual disk, or @c NULL to create an
4365 * image-less object.
4366 * @param aRegistered Whether to mark this disk as registered or not
4367 * (ignored when @a aLocation is @c NULL, assuming @c FALSE)
4368 */
4369HRESULT HCustomHardDisk::init (VirtualBox *aVirtualBox, HardDisk *aParent,
4370 const BSTR aLocation, BOOL aRegistered /* = FALSE */)
4371{
4372 LogFlowThisFunc (("aLocation='%ls', aRegistered=%d\n", aLocation, aRegistered));
4373
4374 AssertReturn (aParent == NULL, E_FAIL);
4375
4376 AutoLock alock (this);
4377 ComAssertRet (!isReady(), E_UNEXPECTED);
4378
4379 mStorageType = HardDiskStorageType_CustomHardDisk;
4380
4381 HRESULT rc = S_OK;
4382
4383 do
4384 {
4385 rc = protectedInit (aVirtualBox, aParent);
4386 CheckComRCBreakRC (rc);
4387
4388 /* set ready to let protectedUninit() be called on failure */
4389 setReady (true);
4390
4391 rc = setLocation (aLocation);
4392 CheckComRCBreakRC (rc);
4393
4394 /* currently, all custom hard disks are writethrough */
4395 mType = HardDiskType_WritethroughHardDisk;
4396
4397 Assert (mId.isEmpty());
4398
4399 if (aLocation && *aLocation)
4400 {
4401 mRegistered = aRegistered;
4402 mState = Created;
4403
4404 char *pszFormat = NULL;
4405
4406 int vrc = VDGetFormat (Utf8Str (mLocation), &pszFormat);
4407 if (VBOX_FAILURE(vrc))
4408 {
4409 AssertRC (vrc);
4410 rc = setError (E_FAIL,
4411 tr ("Cannot recognize the format of the custom "
4412 "hard disk '%ls' (%Vrc)"),
4413 toString().raw(), vrc);
4414 break;
4415 }
4416
4417 /* Create the corresponding container. */
4418 vrc = VDCreate (pszFormat, VDError, this, &mContainer);
4419
4420 if (VBOX_SUCCESS(vrc))
4421 mFormat = Bstr (pszFormat);
4422
4423 RTStrFree (pszFormat);
4424
4425 /* the format has been already checked for presence at this point */
4426 ComAssertRCBreak (vrc, rc = E_FAIL);
4427
4428 /* Call queryInformation() anyway (even if it will block), because
4429 * it is the only way to get the UUID of the existing VDI and
4430 * initialize the vital mId property. */
4431 Bstr errMsg;
4432 rc = queryInformation (&errMsg);
4433 if (SUCCEEDED (rc))
4434 {
4435 /* We are constructing a new HVirtualDiskImage object. If there
4436 * is a fatal accessibility error (we cannot read image UUID),
4437 * we have to fail. We do so even on non-fatal errors as well,
4438 * because it's not worth to keep going with the inaccessible
4439 * image from the very beginning (when nothing else depends on
4440 * it yet). */
4441 if (!errMsg.isNull())
4442 rc = setErrorBstr (E_FAIL, errMsg);
4443 }
4444 }
4445 else
4446 {
4447 mRegistered = FALSE;
4448 mState = NotCreated;
4449 mId.create();
4450 }
4451 }
4452 while (0);
4453
4454 if (FAILED (rc))
4455 uninit();
4456
4457 return rc;
4458}
4459
4460/**
4461 * Uninitializes the instance and sets the ready flag to FALSE.
4462 * Called either from FinalRelease(), by the parent when it gets destroyed,
4463 * or by a third party when it decides this object is no more valid.
4464 */
4465void HCustomHardDisk::uninit()
4466{
4467 LogFlowThisFunc (("\n"));
4468
4469 AutoLock alock (this);
4470 if (!isReady())
4471 return;
4472
4473 HardDisk::protectedUninit (alock);
4474}
4475
4476// IHardDisk properties
4477////////////////////////////////////////////////////////////////////////////////
4478
4479STDMETHODIMP HCustomHardDisk::COMGETTER(Description) (BSTR *aDescription)
4480{
4481 if (!aDescription)
4482 return E_POINTER;
4483
4484 AutoLock alock (this);
4485 CHECK_READY();
4486
4487 mDescription.cloneTo (aDescription);
4488 return S_OK;
4489}
4490
4491STDMETHODIMP HCustomHardDisk::COMSETTER(Description) (INPTR BSTR aDescription)
4492{
4493 AutoLock alock (this);
4494 CHECK_READY();
4495
4496 CHECK_BUSY_AND_READERS();
4497
4498 return E_NOTIMPL;
4499}
4500
4501STDMETHODIMP HCustomHardDisk::COMGETTER(Size) (ULONG64 *aSize)
4502{
4503 if (!aSize)
4504 return E_POINTER;
4505
4506 AutoLock alock (this);
4507 CHECK_READY();
4508
4509 *aSize = mSize;
4510 return S_OK;
4511}
4512
4513STDMETHODIMP HCustomHardDisk::COMGETTER(ActualSize) (ULONG64 *aActualSize)
4514{
4515 if (!aActualSize)
4516 return E_POINTER;
4517
4518 AutoLock alock (this);
4519 CHECK_READY();
4520
4521 *aActualSize = mActualSize;
4522 return S_OK;
4523}
4524
4525// ICustomHardDisk properties
4526////////////////////////////////////////////////////////////////////////////////
4527
4528STDMETHODIMP HCustomHardDisk::COMGETTER(Location) (BSTR *aLocation)
4529{
4530 if (!aLocation)
4531 return E_POINTER;
4532
4533 AutoLock alock (this);
4534 CHECK_READY();
4535
4536 mLocationFull.cloneTo (aLocation);
4537 return S_OK;
4538}
4539
4540STDMETHODIMP HCustomHardDisk::COMSETTER(Location) (INPTR BSTR aLocation)
4541{
4542 AutoLock alock (this);
4543 CHECK_READY();
4544
4545 if (mState != NotCreated)
4546 return setError (E_ACCESSDENIED,
4547 tr ("Cannot change the file path of the existing hard disk '%ls'"),
4548 toString().raw());
4549
4550 CHECK_BUSY_AND_READERS();
4551
4552 /// @todo currently, we assume that location is always a file path for
4553 /// all custom hard disks. This is not generally correct, and needs to be
4554 /// parametrized in the VD plugin interface.
4555
4556 /* append the default path if only a name is given */
4557 Bstr path = aLocation;
4558 if (aLocation && *aLocation)
4559 {
4560 Utf8Str fp = aLocation;
4561 if (!RTPathHavePath (fp))
4562 {
4563 AutoReaderLock propsLock (mVirtualBox->systemProperties());
4564 path = Utf8StrFmt ("%ls%c%s",
4565 mVirtualBox->systemProperties()->defaultVDIFolder().raw(),
4566 RTPATH_DELIMITER,
4567 fp.raw());
4568 }
4569 }
4570
4571 return setLocation (path);
4572}
4573
4574STDMETHODIMP HCustomHardDisk::COMGETTER(Created) (BOOL *aCreated)
4575{
4576 if (!aCreated)
4577 return E_POINTER;
4578
4579 AutoLock alock (this);
4580 CHECK_READY();
4581
4582 *aCreated = mState >= Created;
4583 return S_OK;
4584}
4585
4586STDMETHODIMP HCustomHardDisk::COMGETTER(Format) (BSTR *aFormat)
4587{
4588 if (!aFormat)
4589 return E_POINTER;
4590
4591 AutoLock alock (this);
4592 CHECK_READY();
4593
4594 mFormat.cloneTo (aFormat);
4595 return S_OK;
4596}
4597
4598// ICustomHardDisk methods
4599/////////////////////////////////////////////////////////////////////////////
4600
4601STDMETHODIMP HCustomHardDisk::CreateDynamicImage (ULONG64 aSize, IProgress **aProgress)
4602{
4603 if (!aProgress)
4604 return E_POINTER;
4605
4606 AutoLock alock (this);
4607 CHECK_READY();
4608
4609 return createImage (aSize, TRUE /* aDynamic */, aProgress);
4610}
4611
4612STDMETHODIMP HCustomHardDisk::CreateFixedImage (ULONG64 aSize, IProgress **aProgress)
4613{
4614 if (!aProgress)
4615 return E_POINTER;
4616
4617 AutoLock alock (this);
4618 CHECK_READY();
4619
4620 return createImage (aSize, FALSE /* aDynamic */, aProgress);
4621}
4622
4623STDMETHODIMP HCustomHardDisk::DeleteImage()
4624{
4625 AutoLock alock (this);
4626 CHECK_READY();
4627 CHECK_BUSY_AND_READERS();
4628
4629 return E_NOTIMPL;
4630
4631/// @todo later
4632}
4633
4634// public/protected methods for internal purposes only
4635/////////////////////////////////////////////////////////////////////////////
4636
4637/**
4638 * Attempts to mark the hard disk as registered.
4639 * Only VirtualBox can call this method.
4640 */
4641HRESULT HCustomHardDisk::trySetRegistered (BOOL aRegistered)
4642{
4643 AutoLock alock (this);
4644 CHECK_READY();
4645
4646 if (aRegistered)
4647 {
4648 if (mState == NotCreated)
4649 return setError (E_FAIL,
4650 tr ("Storage location '%ls' is not yet created for this hard disk"),
4651 mLocationFull.raw());
4652 }
4653 else
4654 {
4655 ComAssertRet (mState >= Created, E_FAIL);
4656 }
4657
4658 return HardDisk::trySetRegistered (aRegistered);
4659}
4660
4661/**
4662 * Checks accessibility of this hard disk image only (w/o parents).
4663 *
4664 * @param aAccessError on output, a null string indicates the hard disk is
4665 * accessible, otherwise contains a message describing
4666 * the reason of inaccessibility.
4667 */
4668HRESULT HCustomHardDisk::getAccessible (Bstr &aAccessError)
4669{
4670 AutoLock alock (this);
4671 CHECK_READY();
4672
4673 if (mStateCheckSem != NIL_RTSEMEVENTMULTI)
4674 {
4675 /* An accessibility check in progress on some other thread,
4676 * wait for it to finish. */
4677
4678 ComAssertRet (mStateCheckWaiters != (ULONG) ~0, E_FAIL);
4679 ++ mStateCheckWaiters;
4680 alock.leave();
4681
4682 int vrc = RTSemEventMultiWait (mStateCheckSem, RT_INDEFINITE_WAIT);
4683
4684 alock.enter();
4685 AssertReturn (mStateCheckWaiters != 0, E_FAIL);
4686 -- mStateCheckWaiters;
4687 if (mStateCheckWaiters == 0)
4688 {
4689 RTSemEventMultiDestroy (mStateCheckSem);
4690 mStateCheckSem = NIL_RTSEMEVENTMULTI;
4691 }
4692
4693 AssertRCReturn (vrc, E_FAIL);
4694
4695 /* don't touch aAccessError, it has been already set */
4696 return S_OK;
4697 }
4698
4699 /* check the basic accessibility */
4700 HRESULT rc = getBaseAccessible (aAccessError, true /* aCheckBusy */);
4701 if (FAILED (rc) || !aAccessError.isNull())
4702 return rc;
4703
4704 if (mState >= Created)
4705 {
4706 return queryInformation (&aAccessError);
4707 }
4708
4709 aAccessError = Utf8StrFmt ("Hard disk '%ls' is not yet created",
4710 mLocationFull.raw());
4711 return S_OK;
4712}
4713
4714/**
4715 * Saves hard disk settings to the specified storage node and saves
4716 * all children to the specified hard disk node
4717 *
4718 * @param aHDNode <HardDisk> or <DiffHardDisk> node
4719 * @param aStorageNode <VirtualDiskImage> node
4720 */
4721HRESULT HCustomHardDisk::saveSettings (CFGNODE aHDNode, CFGNODE aStorageNode)
4722{
4723 AssertReturn (aHDNode && aStorageNode, E_FAIL);
4724
4725 AutoLock alock (this);
4726 CHECK_READY();
4727
4728 /* location (required) */
4729 CFGLDRSetBSTR (aStorageNode, "location", mLocationFull);
4730
4731 /* format (required) */
4732 CFGLDRSetBSTR (aStorageNode, "format", mFormat);
4733
4734 /* save basic settings and children */
4735 return HardDisk::saveSettings (aHDNode);
4736}
4737
4738/**
4739 * Returns the string representation of this hard disk.
4740 * When \a aShort is false, returns the full image file path.
4741 * Otherwise, returns the image file name only.
4742 *
4743 * @param aShort if true, a short representation is returned
4744 */
4745Bstr HCustomHardDisk::toString (bool aShort /* = false */)
4746{
4747 AutoLock alock (this);
4748
4749 /// @todo currently, we assume that location is always a file path for
4750 /// all custom hard disks. This is not generally correct, and needs to be
4751 /// parametrized in the VD plugin interface.
4752
4753 if (!aShort)
4754 return mLocationFull;
4755 else
4756 {
4757 Utf8Str fname = mLocationFull;
4758 return RTPathFilename (fname.mutableRaw());
4759 }
4760}
4761
4762/**
4763 * Creates a clone of this hard disk by storing hard disk data in the given
4764 * VDI file.
4765 *
4766 * If the operation fails, @a aDeleteTarget will be set to @c true unless the
4767 * failure happened because the target file already existed.
4768 *
4769 * @param aId UUID to assign to the created image.
4770 * @param aTargetPath VDI file where the cloned image is to be to stored.
4771 * @param aProgress progress object to run during operation.
4772 * @param aDeleteTarget Whether it is recommended to delete target on
4773 * failure or not.
4774 */
4775HRESULT
4776HCustomHardDisk::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath,
4777 Progress *aProgress, bool &aDeleteTarget)
4778{
4779 ComAssertMsgFailed (("Not implemented"));
4780 return E_NOTIMPL;
4781}
4782
4783/**
4784 * Creates a new differencing image for this hard disk with the given
4785 * VDI file name.
4786 *
4787 * @param aId UUID to assign to the created image
4788 * @param aTargetPath VDI file where to store the created differencing image
4789 * @param aProgress progress object to run during operation
4790 * (can be NULL)
4791 */
4792HRESULT
4793HCustomHardDisk::createDiffImage (const Guid &aId, const Utf8Str &aTargetPath,
4794 Progress *aProgress)
4795{
4796 ComAssertMsgFailed (("Not implemented"));
4797 return E_NOTIMPL;
4798}
4799
4800// private methods
4801/////////////////////////////////////////////////////////////////////////////
4802
4803/**
4804 * Helper to set a new location.
4805 *
4806 * @note
4807 * Must be called from under the object's lock!
4808 */
4809HRESULT HCustomHardDisk::setLocation (const BSTR aLocation)
4810{
4811 /// @todo currently, we assume that location is always a file path for
4812 /// all custom hard disks. This is not generally correct, and needs to be
4813 /// parametrized in the VD plugin interface.
4814
4815 if (aLocation && *aLocation)
4816 {
4817 /* get the full file name */
4818 char locationFull [RTPATH_MAX];
4819 int vrc = RTPathAbsEx (mVirtualBox->homeDir(), Utf8Str (aLocation),
4820 locationFull, sizeof (locationFull));
4821 if (VBOX_FAILURE (vrc))
4822 return setError (E_FAIL,
4823 tr ("Invalid hard disk location '%ls' (%Vrc)"), aLocation, vrc);
4824
4825 mLocation = aLocation;
4826 mLocationFull = locationFull;
4827 }
4828 else
4829 {
4830 mLocation.setNull();
4831 mLocationFull.setNull();
4832 }
4833
4834 return S_OK;
4835}
4836
4837/**
4838 * Helper to query information about the custom hard disk.
4839 *
4840 * @param aAccessError not used when NULL, otherwise see #getAccessible()
4841 *
4842 * @note Must be called from under the object's lock, only after
4843 * CHECK_BUSY_AND_READERS() succeeds.
4844 */
4845HRESULT HCustomHardDisk::queryInformation (Bstr *aAccessError)
4846{
4847 AssertReturn (isLockedOnCurrentThread(), E_FAIL);
4848
4849 /* create a lock object to completely release it later */
4850 AutoLock alock (this);
4851
4852 AssertReturn (mStateCheckWaiters == 0, E_FAIL);
4853
4854 ComAssertRet (mState >= Created, E_FAIL);
4855
4856 HRESULT rc = S_OK;
4857 int vrc = VINF_SUCCESS;
4858
4859 /* lazily create a semaphore */
4860 vrc = RTSemEventMultiCreate (&mStateCheckSem);
4861 ComAssertRCRet (vrc, E_FAIL);
4862
4863 /* Reference the disk to prevent any concurrent modifications
4864 * after releasing the lock below (to unblock getters before
4865 * a lengthy operation). */
4866 addReader();
4867
4868 alock.leave();
4869
4870 /* VBoxVHDD management interface needs to be optimized: we're opening a
4871 * file three times in a raw to get three bits of information. */
4872
4873 Utf8Str location = mLocationFull;
4874 Bstr errMsg;
4875
4876 /* reset any previous error report from VDError() */
4877 mLastVDError.setNull();
4878
4879 do
4880 {
4881 Guid id, parentId;
4882
4883 vrc = VDOpen (mContainer, location, VD_OPEN_FLAGS_INFO);
4884 if (VBOX_FAILURE (vrc))
4885 break;
4886
4887 vrc = VDGetUuid (mContainer, 0, id.ptr());
4888 if (VBOX_FAILURE (vrc))
4889 break;
4890 vrc = VDGetParentUuid (mContainer, 0, parentId.ptr());
4891 if (VBOX_FAILURE (vrc))
4892 break;
4893
4894 if (!mId.isEmpty())
4895 {
4896 /* check that the actual UUID of the image matches the stored UUID */
4897 if (mId != id)
4898 {
4899 errMsg = Utf8StrFmt (
4900 tr ("Actual UUID {%Vuuid} of the hard disk image '%s' doesn't "
4901 "match UUID {%Vuuid} stored in the registry"),
4902 id.ptr(), location.raw(), mId.ptr());
4903 break;
4904 }
4905 }
4906 else
4907 {
4908 /* assgn an UUID read from the image file */
4909 mId = id;
4910 }
4911
4912 if (mParent)
4913 {
4914 /* check parent UUID */
4915 AutoLock parentLock (mParent);
4916 if (mParent->id() != parentId)
4917 {
4918 errMsg = Utf8StrFmt (
4919 tr ("UUID {%Vuuid} of the parent image '%ls' stored in "
4920 "the hard disk image file '%s' doesn't match "
4921 "UUID {%Vuuid} stored in the registry"),
4922 parentId.raw(), mParent->toString().raw(),
4923 location.raw(), mParent->id().raw());
4924 break;
4925 }
4926 }
4927 else if (!parentId.isEmpty())
4928 {
4929 errMsg = Utf8StrFmt (
4930 tr ("Hard disk image '%s' is a differencing image that is linked "
4931 "to a hard disk with UUID {%Vuuid} and cannot be used "
4932 "directly as a base hard disk"),
4933 location.raw(), parentId.raw());
4934 break;
4935 }
4936
4937 /* get actual file size */
4938 /// @todo is there a direct method in RT?
4939 {
4940 RTFILE file = NIL_RTFILE;
4941 vrc = RTFileOpen (&file, location, RTFILE_O_READ);
4942 if (VBOX_SUCCESS (vrc))
4943 {
4944 uint64_t size = 0;
4945 vrc = RTFileGetSize (file, &size);
4946 if (VBOX_SUCCESS (vrc))
4947 mActualSize = size;
4948 RTFileClose (file);
4949 }
4950 if (VBOX_FAILURE (vrc))
4951 break;
4952 }
4953
4954 /* query logical size only for non-differencing images */
4955 if (!mParent)
4956 {
4957 uint64_t size = VDGetSize (mContainer);
4958 /* convert to MBytes */
4959 mSize = size / 1024 / 1024;
4960 }
4961 }
4962 while (0);
4963
4964 VDCloseAll (mContainer);
4965
4966 /* enter the lock again */
4967 alock.enter();
4968
4969 /* remove the reference */
4970 releaseReader();
4971
4972 if (FAILED (rc) || VBOX_FAILURE (vrc) || !errMsg.isNull())
4973 {
4974 LogWarningFunc (("'%ls' is not accessible "
4975 "(rc=%08X, vrc=%Vrc, errMsg='%ls')\n",
4976 mLocationFull.raw(), rc, vrc, errMsg.raw()));
4977
4978 if (aAccessError)
4979 {
4980 if (!errMsg.isNull())
4981 *aAccessError = errMsg;
4982 else if (!mLastVDError.isNull())
4983 *aAccessError = mLastVDError;
4984 else if (VBOX_FAILURE (vrc))
4985 *aAccessError = Utf8StrFmt (
4986 tr ("Could not access hard disk '%ls' (%Vrc)"),
4987 mLocationFull.raw(), vrc);
4988 }
4989
4990 /* downgrade to not accessible */
4991 mState = Created;
4992 }
4993 else
4994 {
4995 if (aAccessError)
4996 aAccessError->setNull();
4997
4998 mState = Accessible;
4999 }
5000
5001 /* inform waiters if there are any */
5002 if (mStateCheckWaiters > 0)
5003 {
5004 RTSemEventMultiSignal (mStateCheckSem);
5005 }
5006 else
5007 {
5008 /* delete the semaphore ourselves */
5009 RTSemEventMultiDestroy (mStateCheckSem);
5010 mStateCheckSem = NIL_RTSEMEVENTMULTI;
5011 }
5012
5013 /* cleanup the last error report from VDError() */
5014 mLastVDError.setNull();
5015
5016 return rc;
5017}
5018
5019/**
5020 * Helper to create hard disk images.
5021 *
5022 * @param aSize size in MB
5023 * @param aDynamic dynamic or fixed image
5024 * @param aProgress address of IProgress pointer to return
5025 */
5026HRESULT HCustomHardDisk::createImage (ULONG64 aSize, BOOL aDynamic,
5027 IProgress **aProgress)
5028{
5029 ComAssertMsgFailed (("Not implemented"));
5030 return E_NOTIMPL;
5031}
5032
5033/* static */
5034DECLCALLBACK(int) HCustomHardDisk::VDITaskThread (RTTHREAD thread, void *pvUser)
5035{
5036 AssertMsgFailed (("Not implemented"));
5037 return VERR_GENERAL_FAILURE;
5038}
5039
5040/* static */
5041DECLCALLBACK(void) HCustomHardDisk::VDError (void *pvUser, int rc, RT_SRC_POS_DECL,
5042 const char *pszFormat, va_list va)
5043{
5044 HCustomHardDisk *that = static_cast <HCustomHardDisk *> (pvUser);
5045 AssertReturnVoid (that != NULL);
5046
5047 /// @todo pass the error message to the operation initiator
5048 Utf8Str err = Utf8StrFmt (pszFormat, va);
5049 if (VBOX_FAILURE (rc))
5050 err = Utf8StrFmt ("%s (%Vrc)", err.raw(), rc);
5051
5052 if (that->mLastVDError.isNull())
5053 that->mLastVDError = err;
5054 else
5055 that->mLastVDError = Utf8StrFmt
5056 ("%s.\n%s", that->mLastVDError.raw(), err.raw());
5057}
5058
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