VirtualBox

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

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

Fix formatting bugs in the XML config file code. The number base was
lost, and also signed values would have been stored as unsigned.

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

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