VirtualBox

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

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

Make the backend type a per-image property and get away from the per container property. Required e.g. for snapshotting iSCSI disks (whenever we get there).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 166.1 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_Normal;
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, static_cast <IHardDisk *> (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_Normal &&
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_Normal;
1223 else if (strcmp (type, "immutable") == 0)
1224 mType = HardDiskType_Immutable;
1225 else if (strcmp (type, "writethrough") == 0)
1226 mType = HardDiskType_Writethrough;
1227 else
1228 ComAssertMsgFailedRet (("Invalid hard disk type '%s'\n", type),
1229 E_FAIL);
1230 }
1231 else
1232 mType = HardDiskType_Normal;
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_Normal:
1282 type = "normal";
1283 break;
1284 case HardDiskType_Immutable:
1285 type = "immutable";
1286 break;
1287 case HardDiskType_Writethrough:
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, static_cast <IVirtualDiskImage *> (this),
2649 desc, 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_Writethrough)
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_Writethrough;
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 (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_Writethrough)
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_Writethrough;
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, "VMDK", 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 (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_Writethrough)
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_Writethrough;
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 mFormat = Bstr (pszFormat);
4420 RTStrFree (pszFormat);
4421
4422 /* Create the corresponding container. */
4423 vrc = VDCreate (VDError, this, &mContainer);
4424
4425 /* the format has been already checked for presence at this point */
4426 ComAssertRCBreak (vrc, rc = E_FAIL);
4427
4428 /* Call queryInformation() anyway (even if it will block), because
4429 * it is the only way to get the UUID of the existing VDI and
4430 * initialize the vital mId property. */
4431 Bstr errMsg;
4432 rc = queryInformation (&errMsg);
4433 if (SUCCEEDED (rc))
4434 {
4435 /* We are constructing a new HVirtualDiskImage object. If there
4436 * is a fatal accessibility error (we cannot read image UUID),
4437 * we have to fail. We do so even on non-fatal errors as well,
4438 * because it's not worth to keep going with the inaccessible
4439 * image from the very beginning (when nothing else depends on
4440 * it yet). */
4441 if (!errMsg.isNull())
4442 rc = setErrorBstr (E_FAIL, errMsg);
4443 }
4444 }
4445 else
4446 {
4447 mRegistered = FALSE;
4448 mState = NotCreated;
4449 mId.create();
4450 }
4451 }
4452 while (0);
4453
4454 if (FAILED (rc))
4455 uninit();
4456
4457 return rc;
4458}
4459
4460/**
4461 * Uninitializes the instance and sets the ready flag to FALSE.
4462 * Called either from FinalRelease(), by the parent when it gets destroyed,
4463 * or by a third party when it decides this object is no more valid.
4464 */
4465void HCustomHardDisk::uninit()
4466{
4467 LogFlowThisFunc (("\n"));
4468
4469 AutoLock alock (this);
4470 if (!isReady())
4471 return;
4472
4473 HardDisk::protectedUninit (alock);
4474}
4475
4476// IHardDisk properties
4477////////////////////////////////////////////////////////////////////////////////
4478
4479STDMETHODIMP HCustomHardDisk::COMGETTER(Description) (BSTR *aDescription)
4480{
4481 if (!aDescription)
4482 return E_POINTER;
4483
4484 AutoLock alock (this);
4485 CHECK_READY();
4486
4487 mDescription.cloneTo (aDescription);
4488 return S_OK;
4489}
4490
4491STDMETHODIMP HCustomHardDisk::COMSETTER(Description) (INPTR BSTR aDescription)
4492{
4493 AutoLock alock (this);
4494 CHECK_READY();
4495
4496 CHECK_BUSY_AND_READERS();
4497
4498 return E_NOTIMPL;
4499}
4500
4501STDMETHODIMP HCustomHardDisk::COMGETTER(Size) (ULONG64 *aSize)
4502{
4503 if (!aSize)
4504 return E_POINTER;
4505
4506 AutoLock alock (this);
4507 CHECK_READY();
4508
4509 *aSize = mSize;
4510 return S_OK;
4511}
4512
4513STDMETHODIMP HCustomHardDisk::COMGETTER(ActualSize) (ULONG64 *aActualSize)
4514{
4515 if (!aActualSize)
4516 return E_POINTER;
4517
4518 AutoLock alock (this);
4519 CHECK_READY();
4520
4521 *aActualSize = mActualSize;
4522 return S_OK;
4523}
4524
4525// ICustomHardDisk properties
4526////////////////////////////////////////////////////////////////////////////////
4527
4528STDMETHODIMP HCustomHardDisk::COMGETTER(Location) (BSTR *aLocation)
4529{
4530 if (!aLocation)
4531 return E_POINTER;
4532
4533 AutoLock alock (this);
4534 CHECK_READY();
4535
4536 mLocationFull.cloneTo (aLocation);
4537 return S_OK;
4538}
4539
4540STDMETHODIMP HCustomHardDisk::COMSETTER(Location) (INPTR BSTR aLocation)
4541{
4542 AutoLock alock (this);
4543 CHECK_READY();
4544
4545 if (mState != NotCreated)
4546 return setError (E_ACCESSDENIED,
4547 tr ("Cannot change the file path of the existing hard disk '%ls'"),
4548 toString().raw());
4549
4550 CHECK_BUSY_AND_READERS();
4551
4552 /// @todo currently, we assume that location is always a file path for
4553 /// all custom hard disks. This is not generally correct, and needs to be
4554 /// parametrized in the VD plugin interface.
4555
4556 /* append the default path if only a name is given */
4557 Bstr path = aLocation;
4558 if (aLocation && *aLocation)
4559 {
4560 Utf8Str fp = aLocation;
4561 if (!RTPathHavePath (fp))
4562 {
4563 AutoReaderLock propsLock (mVirtualBox->systemProperties());
4564 path = Utf8StrFmt ("%ls%c%s",
4565 mVirtualBox->systemProperties()->defaultVDIFolder().raw(),
4566 RTPATH_DELIMITER,
4567 fp.raw());
4568 }
4569 }
4570
4571 return setLocation (path);
4572}
4573
4574STDMETHODIMP HCustomHardDisk::COMGETTER(Created) (BOOL *aCreated)
4575{
4576 if (!aCreated)
4577 return E_POINTER;
4578
4579 AutoLock alock (this);
4580 CHECK_READY();
4581
4582 *aCreated = mState >= Created;
4583 return S_OK;
4584}
4585
4586STDMETHODIMP HCustomHardDisk::COMGETTER(Format) (BSTR *aFormat)
4587{
4588 if (!aFormat)
4589 return E_POINTER;
4590
4591 AutoLock alock (this);
4592 CHECK_READY();
4593
4594 mFormat.cloneTo (aFormat);
4595 return S_OK;
4596}
4597
4598// ICustomHardDisk methods
4599/////////////////////////////////////////////////////////////////////////////
4600
4601STDMETHODIMP HCustomHardDisk::CreateDynamicImage (ULONG64 aSize, IProgress **aProgress)
4602{
4603 if (!aProgress)
4604 return E_POINTER;
4605
4606 AutoLock alock (this);
4607 CHECK_READY();
4608
4609 return createImage (aSize, TRUE /* aDynamic */, aProgress);
4610}
4611
4612STDMETHODIMP HCustomHardDisk::CreateFixedImage (ULONG64 aSize, IProgress **aProgress)
4613{
4614 if (!aProgress)
4615 return E_POINTER;
4616
4617 AutoLock alock (this);
4618 CHECK_READY();
4619
4620 return createImage (aSize, FALSE /* aDynamic */, aProgress);
4621}
4622
4623STDMETHODIMP HCustomHardDisk::DeleteImage()
4624{
4625 AutoLock alock (this);
4626 CHECK_READY();
4627 CHECK_BUSY_AND_READERS();
4628
4629 return E_NOTIMPL;
4630
4631/// @todo later
4632}
4633
4634// public/protected methods for internal purposes only
4635/////////////////////////////////////////////////////////////////////////////
4636
4637/**
4638 * Attempts to mark the hard disk as registered.
4639 * Only VirtualBox can call this method.
4640 */
4641HRESULT HCustomHardDisk::trySetRegistered (BOOL aRegistered)
4642{
4643 AutoLock alock (this);
4644 CHECK_READY();
4645
4646 if (aRegistered)
4647 {
4648 if (mState == NotCreated)
4649 return setError (E_FAIL,
4650 tr ("Storage location '%ls' is not yet created for this hard disk"),
4651 mLocationFull.raw());
4652 }
4653 else
4654 {
4655 ComAssertRet (mState >= Created, E_FAIL);
4656 }
4657
4658 return HardDisk::trySetRegistered (aRegistered);
4659}
4660
4661/**
4662 * Checks accessibility of this hard disk image only (w/o parents).
4663 *
4664 * @param aAccessError on output, a null string indicates the hard disk is
4665 * accessible, otherwise contains a message describing
4666 * the reason of inaccessibility.
4667 */
4668HRESULT HCustomHardDisk::getAccessible (Bstr &aAccessError)
4669{
4670 AutoLock alock (this);
4671 CHECK_READY();
4672
4673 if (mStateCheckSem != NIL_RTSEMEVENTMULTI)
4674 {
4675 /* An accessibility check in progress on some other thread,
4676 * wait for it to finish. */
4677
4678 ComAssertRet (mStateCheckWaiters != (ULONG) ~0, E_FAIL);
4679 ++ mStateCheckWaiters;
4680 alock.leave();
4681
4682 int vrc = RTSemEventMultiWait (mStateCheckSem, RT_INDEFINITE_WAIT);
4683
4684 alock.enter();
4685 AssertReturn (mStateCheckWaiters != 0, E_FAIL);
4686 -- mStateCheckWaiters;
4687 if (mStateCheckWaiters == 0)
4688 {
4689 RTSemEventMultiDestroy (mStateCheckSem);
4690 mStateCheckSem = NIL_RTSEMEVENTMULTI;
4691 }
4692
4693 AssertRCReturn (vrc, E_FAIL);
4694
4695 /* don't touch aAccessError, it has been already set */
4696 return S_OK;
4697 }
4698
4699 /* check the basic accessibility */
4700 HRESULT rc = getBaseAccessible (aAccessError, true /* aCheckBusy */);
4701 if (FAILED (rc) || !aAccessError.isNull())
4702 return rc;
4703
4704 if (mState >= Created)
4705 {
4706 return queryInformation (&aAccessError);
4707 }
4708
4709 aAccessError = Utf8StrFmt ("Hard disk '%ls' is not yet created",
4710 mLocationFull.raw());
4711 return S_OK;
4712}
4713
4714/**
4715 * Saves hard disk settings to the specified storage node and saves
4716 * all children to the specified hard disk node
4717 *
4718 * @param aHDNode <HardDisk> or <DiffHardDisk> node.
4719 * @param aStorageNode <VirtualDiskImage> node.
4720 */
4721HRESULT HCustomHardDisk::saveSettings (settings::Key &aHDNode,
4722 settings::Key &aStorageNode)
4723{
4724 AssertReturn (!aHDNode.isNull() && !aStorageNode.isNull(), E_FAIL);
4725
4726 AutoLock alock (this);
4727 CHECK_READY();
4728
4729 /* location (required) */
4730 aStorageNode.setValue <Bstr> ("location", mLocationFull);
4731
4732 /* format (required) */
4733 aStorageNode.setValue <Bstr> ("format", mFormat);
4734
4735 /* save basic settings and children */
4736 return HardDisk::saveSettings (aHDNode);
4737}
4738
4739/**
4740 * Returns the string representation of this hard disk.
4741 * When \a aShort is false, returns the full image file path.
4742 * Otherwise, returns the image file name only.
4743 *
4744 * @param aShort if true, a short representation is returned
4745 */
4746Bstr HCustomHardDisk::toString (bool aShort /* = false */)
4747{
4748 AutoLock alock (this);
4749
4750 /// @todo currently, we assume that location is always a file path for
4751 /// all custom hard disks. This is not generally correct, and needs to be
4752 /// parametrized in the VD plugin interface.
4753
4754 if (!aShort)
4755 return mLocationFull;
4756 else
4757 {
4758 Utf8Str fname = mLocationFull;
4759 return RTPathFilename (fname.mutableRaw());
4760 }
4761}
4762
4763/**
4764 * Creates a clone of this hard disk by storing hard disk data in the given
4765 * VDI file.
4766 *
4767 * If the operation fails, @a aDeleteTarget will be set to @c true unless the
4768 * failure happened because the target file already existed.
4769 *
4770 * @param aId UUID to assign to the created image.
4771 * @param aTargetPath VDI file where the cloned image is to be to stored.
4772 * @param aProgress progress object to run during operation.
4773 * @param aDeleteTarget Whether it is recommended to delete target on
4774 * failure or not.
4775 */
4776HRESULT
4777HCustomHardDisk::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath,
4778 Progress *aProgress, bool &aDeleteTarget)
4779{
4780 ComAssertMsgFailed (("Not implemented"));
4781 return E_NOTIMPL;
4782}
4783
4784/**
4785 * Creates a new differencing image for this hard disk with the given
4786 * VDI file name.
4787 *
4788 * @param aId UUID to assign to the created image
4789 * @param aTargetPath VDI file where to store the created differencing image
4790 * @param aProgress progress object to run during operation
4791 * (can be NULL)
4792 */
4793HRESULT
4794HCustomHardDisk::createDiffImage (const Guid &aId, const Utf8Str &aTargetPath,
4795 Progress *aProgress)
4796{
4797 ComAssertMsgFailed (("Not implemented"));
4798 return E_NOTIMPL;
4799}
4800
4801// private methods
4802/////////////////////////////////////////////////////////////////////////////
4803
4804/**
4805 * Helper to set a new location.
4806 *
4807 * @note
4808 * Must be called from under the object's lock!
4809 */
4810HRESULT HCustomHardDisk::setLocation (const BSTR aLocation)
4811{
4812 /// @todo currently, we assume that location is always a file path for
4813 /// all custom hard disks. This is not generally correct, and needs to be
4814 /// parametrized in the VD plugin interface.
4815
4816 if (aLocation && *aLocation)
4817 {
4818 /* get the full file name */
4819 char locationFull [RTPATH_MAX];
4820 int vrc = RTPathAbsEx (mVirtualBox->homeDir(), Utf8Str (aLocation),
4821 locationFull, sizeof (locationFull));
4822 if (VBOX_FAILURE (vrc))
4823 return setError (E_FAIL,
4824 tr ("Invalid hard disk location '%ls' (%Vrc)"), aLocation, vrc);
4825
4826 mLocation = aLocation;
4827 mLocationFull = locationFull;
4828 }
4829 else
4830 {
4831 mLocation.setNull();
4832 mLocationFull.setNull();
4833 }
4834
4835 return S_OK;
4836}
4837
4838/**
4839 * Helper to query information about the custom hard disk.
4840 *
4841 * @param aAccessError not used when NULL, otherwise see #getAccessible()
4842 *
4843 * @note Must be called from under the object's lock, only after
4844 * CHECK_BUSY_AND_READERS() succeeds.
4845 */
4846HRESULT HCustomHardDisk::queryInformation (Bstr *aAccessError)
4847{
4848 AssertReturn (isLockedOnCurrentThread(), E_FAIL);
4849
4850 /* create a lock object to completely release it later */
4851 AutoLock alock (this);
4852
4853 AssertReturn (mStateCheckWaiters == 0, E_FAIL);
4854
4855 ComAssertRet (mState >= Created, E_FAIL);
4856
4857 HRESULT rc = S_OK;
4858 int vrc = VINF_SUCCESS;
4859
4860 /* lazily create a semaphore */
4861 vrc = RTSemEventMultiCreate (&mStateCheckSem);
4862 ComAssertRCRet (vrc, E_FAIL);
4863
4864 /* Reference the disk to prevent any concurrent modifications
4865 * after releasing the lock below (to unblock getters before
4866 * a lengthy operation). */
4867 addReader();
4868
4869 alock.leave();
4870
4871 /* VBoxVHDD management interface needs to be optimized: we're opening a
4872 * file three times in a raw to get three bits of information. */
4873
4874 Utf8Str location = mLocationFull;
4875 Bstr errMsg;
4876
4877 /* reset any previous error report from VDError() */
4878 mLastVDError.setNull();
4879
4880 do
4881 {
4882 Guid id, parentId;
4883
4884 vrc = VDOpen (mContainer, Utf8Str (mFormat), location, VD_OPEN_FLAGS_INFO);
4885 if (VBOX_FAILURE (vrc))
4886 break;
4887
4888 vrc = VDGetUuid (mContainer, 0, id.ptr());
4889 if (VBOX_FAILURE (vrc))
4890 break;
4891 vrc = VDGetParentUuid (mContainer, 0, parentId.ptr());
4892 if (VBOX_FAILURE (vrc))
4893 break;
4894
4895 if (!mId.isEmpty())
4896 {
4897 /* check that the actual UUID of the image matches the stored UUID */
4898 if (mId != id)
4899 {
4900 errMsg = Utf8StrFmt (
4901 tr ("Actual UUID {%Vuuid} of the hard disk image '%s' doesn't "
4902 "match UUID {%Vuuid} stored in the registry"),
4903 id.ptr(), location.raw(), mId.ptr());
4904 break;
4905 }
4906 }
4907 else
4908 {
4909 /* assgn an UUID read from the image file */
4910 mId = id;
4911 }
4912
4913 if (mParent)
4914 {
4915 /* check parent UUID */
4916 AutoLock parentLock (mParent);
4917 if (mParent->id() != parentId)
4918 {
4919 errMsg = Utf8StrFmt (
4920 tr ("UUID {%Vuuid} of the parent image '%ls' stored in "
4921 "the hard disk image file '%s' doesn't match "
4922 "UUID {%Vuuid} stored in the registry"),
4923 parentId.raw(), mParent->toString().raw(),
4924 location.raw(), mParent->id().raw());
4925 break;
4926 }
4927 }
4928 else if (!parentId.isEmpty())
4929 {
4930 errMsg = Utf8StrFmt (
4931 tr ("Hard disk image '%s' is a differencing image that is linked "
4932 "to a hard disk with UUID {%Vuuid} and cannot be used "
4933 "directly as a base hard disk"),
4934 location.raw(), parentId.raw());
4935 break;
4936 }
4937
4938 /* get actual file size */
4939 /// @todo is there a direct method in RT?
4940 {
4941 RTFILE file = NIL_RTFILE;
4942 vrc = RTFileOpen (&file, location, RTFILE_O_READ);
4943 if (VBOX_SUCCESS (vrc))
4944 {
4945 uint64_t size = 0;
4946 vrc = RTFileGetSize (file, &size);
4947 if (VBOX_SUCCESS (vrc))
4948 mActualSize = size;
4949 RTFileClose (file);
4950 }
4951 if (VBOX_FAILURE (vrc))
4952 break;
4953 }
4954
4955 /* query logical size only for non-differencing images */
4956 if (!mParent)
4957 {
4958 uint64_t size = VDGetSize (mContainer, 0);
4959 /* convert to MBytes */
4960 mSize = size / 1024 / 1024;
4961 }
4962 }
4963 while (0);
4964
4965 VDCloseAll (mContainer);
4966
4967 /* enter the lock again */
4968 alock.enter();
4969
4970 /* remove the reference */
4971 releaseReader();
4972
4973 if (FAILED (rc) || VBOX_FAILURE (vrc) || !errMsg.isNull())
4974 {
4975 LogWarningFunc (("'%ls' is not accessible "
4976 "(rc=%08X, vrc=%Vrc, errMsg='%ls')\n",
4977 mLocationFull.raw(), rc, vrc, errMsg.raw()));
4978
4979 if (aAccessError)
4980 {
4981 if (!errMsg.isNull())
4982 *aAccessError = errMsg;
4983 else if (!mLastVDError.isNull())
4984 *aAccessError = mLastVDError;
4985 else if (VBOX_FAILURE (vrc))
4986 *aAccessError = Utf8StrFmt (
4987 tr ("Could not access hard disk '%ls' (%Vrc)"),
4988 mLocationFull.raw(), vrc);
4989 }
4990
4991 /* downgrade to not accessible */
4992 mState = Created;
4993 }
4994 else
4995 {
4996 if (aAccessError)
4997 aAccessError->setNull();
4998
4999 mState = Accessible;
5000 }
5001
5002 /* inform waiters if there are any */
5003 if (mStateCheckWaiters > 0)
5004 {
5005 RTSemEventMultiSignal (mStateCheckSem);
5006 }
5007 else
5008 {
5009 /* delete the semaphore ourselves */
5010 RTSemEventMultiDestroy (mStateCheckSem);
5011 mStateCheckSem = NIL_RTSEMEVENTMULTI;
5012 }
5013
5014 /* cleanup the last error report from VDError() */
5015 mLastVDError.setNull();
5016
5017 return rc;
5018}
5019
5020/**
5021 * Helper to create hard disk images.
5022 *
5023 * @param aSize size in MB
5024 * @param aDynamic dynamic or fixed image
5025 * @param aProgress address of IProgress pointer to return
5026 */
5027HRESULT HCustomHardDisk::createImage (ULONG64 aSize, BOOL aDynamic,
5028 IProgress **aProgress)
5029{
5030 ComAssertMsgFailed (("Not implemented"));
5031 return E_NOTIMPL;
5032}
5033
5034/* static */
5035DECLCALLBACK(int) HCustomHardDisk::VDITaskThread (RTTHREAD thread, void *pvUser)
5036{
5037 AssertMsgFailed (("Not implemented"));
5038 return VERR_GENERAL_FAILURE;
5039}
5040
5041/* static */
5042DECLCALLBACK(void) HCustomHardDisk::VDError (void *pvUser, int rc, RT_SRC_POS_DECL,
5043 const char *pszFormat, va_list va)
5044{
5045 HCustomHardDisk *that = static_cast <HCustomHardDisk *> (pvUser);
5046 AssertReturnVoid (that != NULL);
5047
5048 /// @todo pass the error message to the operation initiator
5049 Utf8Str err = Utf8StrFmtVA (pszFormat, va);
5050 if (VBOX_FAILURE (rc))
5051 err = Utf8StrFmt ("%s (%Vrc)", err.raw(), rc);
5052
5053 if (that->mLastVDError.isNull())
5054 that->mLastVDError = err;
5055 else
5056 that->mLastVDError = Utf8StrFmt
5057 ("%s.\n%s", that->mLastVDError.raw(), err.raw());
5058}
5059
5060////////////////////////////////////////////////////////////////////////////////
5061// HVHDImage class
5062////////////////////////////////////////////////////////////////////////////////
5063
5064// constructor / destructor
5065////////////////////////////////////////////////////////////////////////////////
5066
5067HRESULT HVHDImage::FinalConstruct()
5068{
5069 HRESULT rc = HardDisk::FinalConstruct();
5070 if (FAILED (rc))
5071 return rc;
5072
5073 mState = NotCreated;
5074
5075 mStateCheckSem = NIL_RTSEMEVENTMULTI;
5076 mStateCheckWaiters = 0;
5077
5078 mSize = 0;
5079 mActualSize = 0;
5080
5081 /* initialize the container */
5082 int vrc = VDCreate (VDError, this, &mContainer);
5083 ComAssertRCRet (vrc, E_FAIL);
5084
5085 return S_OK;
5086}
5087
5088void HVHDImage::FinalRelease()
5089{
5090 if (mContainer != NULL)
5091 VDDestroy (mContainer);
5092
5093 HardDisk::FinalRelease();
5094}
5095
5096// public initializer/uninitializer for internal purposes only
5097////////////////////////////////////////////////////////////////////////////////
5098
5099// public methods for internal purposes only
5100/////////////////////////////////////////////////////////////////////////////
5101
5102/**
5103 * Initializes the VHD hard disk object by reading its properties from
5104 * the given configuration node. The created hard disk will be marked as
5105 * registered on success.
5106 *
5107 * @param aHDNode <HardDisk> node
5108 * @param aVHDNode <VirtualDiskImage> node
5109 */
5110HRESULT HVHDImage::init (VirtualBox *aVirtualBox, HardDisk *aParent,
5111 const settings::Key &aHDNode,
5112 const settings::Key &aVHDNode)
5113{
5114 LogFlowThisFunc (("\n"));
5115
5116 AssertReturn (!aHDNode.isNull() && !aVHDNode.isNull(), E_FAIL);
5117
5118 AutoLock alock (this);
5119 ComAssertRet (!isReady(), E_UNEXPECTED);
5120
5121 mStorageType = HardDiskStorageType_VHDImage;
5122
5123 HRESULT rc = S_OK;
5124
5125 do
5126 {
5127 rc = protectedInit (aVirtualBox, aParent);
5128 CheckComRCBreakRC (rc);
5129
5130 /* set ready to let protectedUninit() be called on failure */
5131 setReady (true);
5132
5133 /* filePath (required) */
5134 Bstr filePath = aVHDNode.stringValue("filePath");
5135
5136 rc = setFilePath (filePath);
5137 CheckComRCBreakRC (rc);
5138
5139 LogFlowThisFunc (("'%ls'\n", mFilePathFull.raw()));
5140
5141 /* load basic settings and children */
5142 rc = loadSettings (aHDNode);
5143 CheckComRCBreakRC (rc);
5144
5145 if (mType != HardDiskType_Writethrough)
5146 {
5147 rc = setError (E_FAIL,
5148 tr ("Currently, non-Writethrough VHD images are not "
5149 "allowed ('%ls')"),
5150 toString().raw());
5151 break;
5152 }
5153
5154 mState = Created;
5155 mRegistered = TRUE;
5156
5157 /* Don't call queryInformation() for registered hard disks to
5158 * prevent the calling thread (i.e. the VirtualBox server startup
5159 * thread) from an unexpected freeze. The vital mId property (UUID)
5160 * is read from the registry file in loadSettings(). To get the rest,
5161 * the user will have to call COMGETTER(Accessible) manually. */
5162 }
5163 while (0);
5164
5165 if (FAILED (rc))
5166 uninit();
5167
5168 return rc;
5169}
5170
5171/**
5172 * Initializes the VHD hard disk object using the given image file name.
5173 *
5174 * @param aVirtualBox VirtualBox parent.
5175 * @param aParent Currently, must always be @c NULL.
5176 * @param aFilePath Path to the image file, or @c NULL to create an
5177 * image-less object.
5178 * @param aRegistered Whether to mark this disk as registered or not
5179 * (ignored when @a aFilePath is @c NULL, assuming @c FALSE)
5180 */
5181HRESULT HVHDImage::init (VirtualBox *aVirtualBox, HardDisk *aParent,
5182 const BSTR aFilePath, BOOL aRegistered /* = FALSE */)
5183{
5184 LogFlowThisFunc (("aFilePath='%ls', aRegistered=%d\n", aFilePath, aRegistered));
5185
5186 AssertReturn (aParent == NULL, E_FAIL);
5187
5188 AutoLock alock (this);
5189 ComAssertRet (!isReady(), E_UNEXPECTED);
5190
5191 mStorageType = HardDiskStorageType_VHDImage;
5192
5193 HRESULT rc = S_OK;
5194
5195 do
5196 {
5197 rc = protectedInit (aVirtualBox, aParent);
5198 CheckComRCBreakRC (rc);
5199
5200 /* set ready to let protectedUninit() be called on failure */
5201 setReady (true);
5202
5203 rc = setFilePath (aFilePath);
5204 CheckComRCBreakRC (rc);
5205
5206 /* currently, all VHD hard disks are writethrough */
5207 mType = HardDiskType_Writethrough;
5208
5209 Assert (mId.isEmpty());
5210
5211 if (aFilePath && *aFilePath)
5212 {
5213 mRegistered = aRegistered;
5214 mState = Created;
5215
5216 /* Call queryInformation() anyway (even if it will block), because
5217 * it is the only way to get the UUID of the existing VDI and
5218 * initialize the vital mId property. */
5219 Bstr errMsg;
5220 rc = queryInformation (&errMsg);
5221 if (SUCCEEDED (rc))
5222 {
5223 /* We are constructing a new HVirtualDiskImage object. If there
5224 * is a fatal accessibility error (we cannot read image UUID),
5225 * we have to fail. We do so even on non-fatal errors as well,
5226 * because it's not worth to keep going with the inaccessible
5227 * image from the very beginning (when nothing else depends on
5228 * it yet). */
5229 if (!errMsg.isNull())
5230 rc = setErrorBstr (E_FAIL, errMsg);
5231 }
5232 }
5233 else
5234 {
5235 mRegistered = FALSE;
5236 mState = NotCreated;
5237 mId.create();
5238 }
5239 }
5240 while (0);
5241
5242 if (FAILED (rc))
5243 uninit();
5244
5245 return rc;
5246}
5247
5248/**
5249 * Uninitializes the instance and sets the ready flag to FALSE.
5250 * Called either from FinalRelease(), by the parent when it gets destroyed,
5251 * or by a third party when it decides this object is no more valid.
5252 */
5253void HVHDImage::uninit()
5254{
5255 LogFlowThisFunc (("\n"));
5256
5257 AutoLock alock (this);
5258 if (!isReady())
5259 return;
5260
5261 HardDisk::protectedUninit (alock);
5262}
5263
5264// IHardDisk properties
5265////////////////////////////////////////////////////////////////////////////////
5266
5267STDMETHODIMP HVHDImage::COMGETTER(Description) (BSTR *aDescription)
5268{
5269 if (!aDescription)
5270 return E_POINTER;
5271
5272 AutoLock alock (this);
5273 CHECK_READY();
5274
5275 mDescription.cloneTo (aDescription);
5276 return S_OK;
5277}
5278
5279STDMETHODIMP HVHDImage::COMSETTER(Description) (INPTR BSTR aDescription)
5280{
5281 AutoLock alock (this);
5282 CHECK_READY();
5283
5284 CHECK_BUSY_AND_READERS();
5285
5286 return E_NOTIMPL;
5287
5288/// @todo implement
5289//
5290// if (mState >= Created)
5291// {
5292// int vrc = VDISetImageComment (Utf8Str (mFilePathFull), Utf8Str (aDescription));
5293// if (VBOX_FAILURE (vrc))
5294// return setError (E_FAIL,
5295// tr ("Could not change the description of the VDI hard disk '%ls' "
5296// "(%Vrc)"),
5297// toString().raw(), vrc);
5298// }
5299//
5300// mDescription = aDescription;
5301// return S_OK;
5302}
5303
5304STDMETHODIMP HVHDImage::COMGETTER(Size) (ULONG64 *aSize)
5305{
5306 if (!aSize)
5307 return E_POINTER;
5308
5309 AutoLock alock (this);
5310 CHECK_READY();
5311
5312/// @todo will need this if we add suppord for differencing VMDKs
5313//
5314// /* only a non-differencing image knows the logical size */
5315// if (isDifferencing())
5316// return root()->COMGETTER(Size) (aSize);
5317
5318 *aSize = mSize;
5319 return S_OK;
5320}
5321
5322STDMETHODIMP HVHDImage::COMGETTER(ActualSize) (ULONG64 *aActualSize)
5323{
5324 if (!aActualSize)
5325 return E_POINTER;
5326
5327 AutoLock alock (this);
5328 CHECK_READY();
5329
5330 *aActualSize = mActualSize;
5331 return S_OK;
5332}
5333
5334// IVirtualDiskImage properties
5335////////////////////////////////////////////////////////////////////////////////
5336
5337STDMETHODIMP HVHDImage::COMGETTER(FilePath) (BSTR *aFilePath)
5338{
5339 if (!aFilePath)
5340 return E_POINTER;
5341
5342 AutoLock alock (this);
5343 CHECK_READY();
5344
5345 mFilePathFull.cloneTo (aFilePath);
5346 return S_OK;
5347}
5348
5349STDMETHODIMP HVHDImage::COMSETTER(FilePath) (INPTR BSTR aFilePath)
5350{
5351 AutoLock alock (this);
5352 CHECK_READY();
5353
5354 if (mState != NotCreated)
5355 return setError (E_ACCESSDENIED,
5356 tr ("Cannot change the file path of the existing hard disk '%ls'"),
5357 toString().raw());
5358
5359 CHECK_BUSY_AND_READERS();
5360
5361 /* append the default path if only a name is given */
5362 Bstr path = aFilePath;
5363 if (aFilePath && *aFilePath)
5364 {
5365 Utf8Str fp = aFilePath;
5366 if (!RTPathHavePath (fp))
5367 {
5368 AutoReaderLock propsLock (mVirtualBox->systemProperties());
5369 path = Utf8StrFmt ("%ls%c%s",
5370 mVirtualBox->systemProperties()->defaultVDIFolder().raw(),
5371 RTPATH_DELIMITER,
5372 fp.raw());
5373 }
5374 }
5375
5376 return setFilePath (path);
5377}
5378
5379STDMETHODIMP HVHDImage::COMGETTER(Created) (BOOL *aCreated)
5380{
5381 if (!aCreated)
5382 return E_POINTER;
5383
5384 AutoLock alock (this);
5385 CHECK_READY();
5386
5387 *aCreated = mState >= Created;
5388 return S_OK;
5389}
5390
5391// IVHDImage methods
5392/////////////////////////////////////////////////////////////////////////////
5393
5394STDMETHODIMP HVHDImage::CreateDynamicImage (ULONG64 aSize, IProgress **aProgress)
5395{
5396 if (!aProgress)
5397 return E_POINTER;
5398
5399 AutoLock alock (this);
5400 CHECK_READY();
5401
5402 return createImage (aSize, TRUE /* aDynamic */, aProgress);
5403}
5404
5405STDMETHODIMP HVHDImage::CreateFixedImage (ULONG64 aSize, IProgress **aProgress)
5406{
5407 if (!aProgress)
5408 return E_POINTER;
5409
5410 AutoLock alock (this);
5411 CHECK_READY();
5412
5413 return createImage (aSize, FALSE /* aDynamic */, aProgress);
5414}
5415
5416STDMETHODIMP HVHDImage::DeleteImage()
5417{
5418 AutoLock alock (this);
5419 CHECK_READY();
5420 CHECK_BUSY_AND_READERS();
5421
5422 return E_NOTIMPL;
5423
5424/// @todo later
5425// We will need to parse the file in order to delete all related delta and
5426// sparse images etc. We may also want to obey the .vmdk.lck file
5427// which is (as far as I understood) created when the VMware VM is
5428// running or saved etc.
5429//
5430// if (mRegistered)
5431// return setError (E_ACCESSDENIED,
5432// tr ("Cannot delete an image of the registered hard disk image '%ls"),
5433// mFilePathFull.raw());
5434// if (mState == NotCreated)
5435// return setError (E_FAIL,
5436// tr ("Hard disk image has been already deleted or never created"));
5437//
5438// int vrc = RTFileDelete (Utf8Str (mFilePathFull));
5439// if (VBOX_FAILURE (vrc))
5440// return setError (E_FAIL, tr ("Could not delete the image file '%ls' (%Vrc)"),
5441// mFilePathFull.raw(), vrc);
5442//
5443// mState = NotCreated;
5444//
5445// /* reset the fields */
5446// mSize = 0;
5447// mActualSize = 0;
5448//
5449// return S_OK;
5450}
5451
5452// public/protected methods for internal purposes only
5453/////////////////////////////////////////////////////////////////////////////
5454
5455/**
5456 * Attempts to mark the hard disk as registered.
5457 * Only VirtualBox can call this method.
5458 */
5459HRESULT HVHDImage::trySetRegistered (BOOL aRegistered)
5460{
5461 AutoLock alock (this);
5462 CHECK_READY();
5463
5464 if (aRegistered)
5465 {
5466 if (mState == NotCreated)
5467 return setError (E_FAIL,
5468 tr ("Image file '%ls' is not yet created for this hard disk"),
5469 mFilePathFull.raw());
5470
5471/// @todo will need this if we add suppord for differencing VHDs
5472// if (isDifferencing())
5473// return setError (E_FAIL,
5474// tr ("Hard disk '%ls' is differencing and cannot be unregistered "
5475// "explicitly"),
5476// mFilePathFull.raw());
5477 }
5478 else
5479 {
5480 ComAssertRet (mState >= Created, E_FAIL);
5481 }
5482
5483 return HardDisk::trySetRegistered (aRegistered);
5484}
5485
5486/**
5487 * Checks accessibility of this hard disk image only (w/o parents).
5488 *
5489 * @param aAccessError on output, a null string indicates the hard disk is
5490 * accessible, otherwise contains a message describing
5491 * the reason of inaccessibility.
5492 */
5493HRESULT HVHDImage::getAccessible (Bstr &aAccessError)
5494{
5495 AutoLock alock (this);
5496 CHECK_READY();
5497
5498 if (mStateCheckSem != NIL_RTSEMEVENTMULTI)
5499 {
5500 /* An accessibility check in progress on some other thread,
5501 * wait for it to finish. */
5502
5503 ComAssertRet (mStateCheckWaiters != (ULONG) ~0, E_FAIL);
5504 ++ mStateCheckWaiters;
5505 alock.leave();
5506
5507 int vrc = RTSemEventMultiWait (mStateCheckSem, RT_INDEFINITE_WAIT);
5508
5509 alock.enter();
5510 AssertReturn (mStateCheckWaiters != 0, E_FAIL);
5511 -- mStateCheckWaiters;
5512 if (mStateCheckWaiters == 0)
5513 {
5514 RTSemEventMultiDestroy (mStateCheckSem);
5515 mStateCheckSem = NIL_RTSEMEVENTMULTI;
5516 }
5517
5518 AssertRCReturn (vrc, E_FAIL);
5519
5520 /* don't touch aAccessError, it has been already set */
5521 return S_OK;
5522 }
5523
5524 /* check the basic accessibility */
5525 HRESULT rc = getBaseAccessible (aAccessError, true /* aCheckBusy */);
5526 if (FAILED (rc) || !aAccessError.isNull())
5527 return rc;
5528
5529 if (mState >= Created)
5530 {
5531 return queryInformation (&aAccessError);
5532 }
5533
5534 aAccessError = Utf8StrFmt ("Hard disk image '%ls' is not yet created",
5535 mFilePathFull.raw());
5536 return S_OK;
5537}
5538
5539/**
5540 * Saves hard disk settings to the specified storage node and saves
5541 * all children to the specified hard disk node
5542 *
5543 * @param aHDNode <HardDisk> or <DiffHardDisk> node
5544 * @param aStorageNode <VirtualDiskImage> node
5545 */
5546HRESULT HVHDImage::saveSettings (settings::Key &aHDNode, settings::Key &aStorageNode)
5547{
5548 AssertReturn (!aHDNode.isNull() && !aStorageNode.isNull(), E_FAIL);
5549
5550 AutoLock alock (this);
5551 CHECK_READY();
5552
5553 /* filePath (required) */
5554 aStorageNode.setValue <Bstr> ("filePath", mFilePath);
5555
5556 /* save basic settings and children */
5557 return HardDisk::saveSettings (aHDNode);
5558}
5559
5560/**
5561 * Checks if the given change of \a aOldPath to \a aNewPath affects the path
5562 * of this hard disk and updates it if necessary to reflect the new location.
5563 * Intended to be from HardDisk::updatePaths().
5564 *
5565 * @param aOldPath old path (full)
5566 * @param aNewPath new path (full)
5567 *
5568 * @note Locks this object for writing.
5569 */
5570void HVHDImage::updatePath (const char *aOldPath, const char *aNewPath)
5571{
5572 AssertReturnVoid (aOldPath);
5573 AssertReturnVoid (aNewPath);
5574
5575 AutoLock alock (this);
5576 AssertReturnVoid (isReady());
5577
5578 size_t oldPathLen = strlen (aOldPath);
5579
5580 Utf8Str path = mFilePathFull;
5581 LogFlowThisFunc (("VHD.fullPath={%s}\n", path.raw()));
5582
5583 if (RTPathStartsWith (path, aOldPath))
5584 {
5585 Utf8Str newPath = Utf8StrFmt ("%s%s", aNewPath,
5586 path.raw() + oldPathLen);
5587 path = newPath;
5588
5589 mVirtualBox->calculateRelativePath (path, path);
5590
5591 unconst (mFilePathFull) = newPath;
5592 unconst (mFilePath) = path;
5593
5594 LogFlowThisFunc (("-> updated: full={%s} short={%s}\n",
5595 newPath.raw(), path.raw()));
5596 }
5597}
5598
5599/**
5600 * Returns the string representation of this hard disk.
5601 * When \a aShort is false, returns the full image file path.
5602 * Otherwise, returns the image file name only.
5603 *
5604 * @param aShort if true, a short representation is returned
5605 */
5606Bstr HVHDImage::toString (bool aShort /* = false */)
5607{
5608 AutoLock alock (this);
5609
5610 if (!aShort)
5611 return mFilePathFull;
5612 else
5613 {
5614 Utf8Str fname = mFilePathFull;
5615 return RTPathFilename (fname.mutableRaw());
5616 }
5617}
5618
5619/**
5620 * Creates a clone of this hard disk by storing hard disk data in the given
5621 * VDI file.
5622 *
5623 * If the operation fails, @a aDeleteTarget will be set to @c true unless the
5624 * failure happened because the target file already existed.
5625 *
5626 * @param aId UUID to assign to the created image.
5627 * @param aTargetPath VDI file where the cloned image is to be to stored.
5628 * @param aProgress progress object to run during operation.
5629 * @param aDeleteTarget Whether it is recommended to delete target on
5630 * failure or not.
5631 */
5632HRESULT
5633HVHDImage::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath,
5634 Progress *aProgress, bool &aDeleteTarget)
5635{
5636 ComAssertMsgFailed (("Not implemented"));
5637 return E_NOTIMPL;
5638
5639/// @todo will need this if we add suppord for differencing VHDs
5640// Use code from HVirtualDiskImage::cloneToImage as an example.
5641}
5642
5643/**
5644 * Creates a new differencing image for this hard disk with the given
5645 * VDI file name.
5646 *
5647 * @param aId UUID to assign to the created image
5648 * @param aTargetPath VDI file where to store the created differencing image
5649 * @param aProgress progress object to run during operation
5650 * (can be NULL)
5651 */
5652HRESULT
5653HVHDImage::createDiffImage (const Guid &aId, const Utf8Str &aTargetPath,
5654 Progress *aProgress)
5655{
5656 ComAssertMsgFailed (("Not implemented"));
5657 return E_NOTIMPL;
5658
5659/// @todo will need this if we add suppord for differencing VHDs
5660// Use code from HVirtualDiskImage::createDiffImage as an example.
5661}
5662
5663// private methods
5664/////////////////////////////////////////////////////////////////////////////
5665
5666/**
5667 * Helper to set a new file path.
5668 * Resolves a path relatively to the Virtual Box home directory.
5669 *
5670 * @note
5671 * Must be called from under the object's lock!
5672 */
5673HRESULT HVHDImage::setFilePath (const BSTR aFilePath)
5674{
5675 if (aFilePath && *aFilePath)
5676 {
5677 /* get the full file name */
5678 char filePathFull [RTPATH_MAX];
5679 int vrc = RTPathAbsEx (mVirtualBox->homeDir(), Utf8Str (aFilePath),
5680 filePathFull, sizeof (filePathFull));
5681 if (VBOX_FAILURE (vrc))
5682 return setError (E_FAIL,
5683 tr ("Invalid image file path '%ls' (%Vrc)"), aFilePath, vrc);
5684
5685 mFilePath = aFilePath;
5686 mFilePathFull = filePathFull;
5687 }
5688 else
5689 {
5690 mFilePath.setNull();
5691 mFilePathFull.setNull();
5692 }
5693
5694 return S_OK;
5695}
5696
5697/**
5698 * Helper to query information about the VDI hard disk.
5699 *
5700 * @param aAccessError not used when NULL, otherwise see #getAccessible()
5701 *
5702 * @note Must be called from under the object's lock, only after
5703 * CHECK_BUSY_AND_READERS() succeeds.
5704 */
5705HRESULT HVHDImage::queryInformation (Bstr *aAccessError)
5706{
5707 AssertReturn (isLockedOnCurrentThread(), E_FAIL);
5708
5709 /* create a lock object to completely release it later */
5710 AutoLock alock (this);
5711
5712 AssertReturn (mStateCheckWaiters == 0, E_FAIL);
5713
5714 ComAssertRet (mState >= Created, E_FAIL);
5715
5716 HRESULT rc = S_OK;
5717 int vrc = VINF_SUCCESS;
5718
5719 /* lazily create a semaphore */
5720 vrc = RTSemEventMultiCreate (&mStateCheckSem);
5721 ComAssertRCRet (vrc, E_FAIL);
5722
5723 /* Reference the disk to prevent any concurrent modifications
5724 * after releasing the lock below (to unblock getters before
5725 * a lengthy operation). */
5726 addReader();
5727
5728 alock.leave();
5729
5730 /* VBoxVHDD management interface needs to be optimized: we're opening a
5731 * file three times in a raw to get three bits of information. */
5732
5733 Utf8Str filePath = mFilePathFull;
5734 Bstr errMsg;
5735
5736 /* reset any previous error report from VDError() */
5737 mLastVDError.setNull();
5738
5739 do
5740 {
5741 Guid id, parentId;
5742
5743 /// @todo changed from VD_OPEN_FLAGS_READONLY to VD_OPEN_FLAGS_NORMAL,
5744 /// because otherwise registering a VHD which so far has no UUID will
5745 /// yield a null UUID. It cannot be added to a VHD opened readonly,
5746 /// obviously. This of course changes locking behavior, but for now
5747 /// this is acceptable. A better solution needs to be found later.
5748 vrc = VDOpen (mContainer, "VHD", filePath, VD_OPEN_FLAGS_NORMAL);
5749 if (VBOX_FAILURE (vrc))
5750 break;
5751
5752 vrc = VDGetUuid (mContainer, 0, id.ptr());
5753 if (VBOX_FAILURE (vrc))
5754 break;
5755 vrc = VDGetParentUuid (mContainer, 0, parentId.ptr());
5756 if (VBOX_FAILURE (vrc))
5757 break;
5758
5759 if (!mId.isEmpty())
5760 {
5761 /* check that the actual UUID of the image matches the stored UUID */
5762 if (mId != id)
5763 {
5764 errMsg = Utf8StrFmt (
5765 tr ("Actual UUID {%Vuuid} of the hard disk image '%s' doesn't "
5766 "match UUID {%Vuuid} stored in the registry"),
5767 id.ptr(), filePath.raw(), mId.ptr());
5768 break;
5769 }
5770 }
5771 else
5772 {
5773 /* assgn an UUID read from the image file */
5774 mId = id;
5775 }
5776
5777 if (mParent)
5778 {
5779 /* check parent UUID */
5780 AutoLock parentLock (mParent);
5781 if (mParent->id() != parentId)
5782 {
5783 errMsg = Utf8StrFmt (
5784 tr ("UUID {%Vuuid} of the parent image '%ls' stored in "
5785 "the hard disk image file '%s' doesn't match "
5786 "UUID {%Vuuid} stored in the registry"),
5787 parentId.raw(), mParent->toString().raw(),
5788 filePath.raw(), mParent->id().raw());
5789 break;
5790 }
5791 }
5792 else if (!parentId.isEmpty())
5793 {
5794 errMsg = Utf8StrFmt (
5795 tr ("Hard disk image '%s' is a differencing image that is linked "
5796 "to a hard disk with UUID {%Vuuid} and cannot be used "
5797 "directly as a base hard disk"),
5798 filePath.raw(), parentId.raw());
5799 break;
5800 }
5801
5802 /* get actual file size */
5803 /// @todo is there a direct method in RT?
5804 {
5805 RTFILE file = NIL_RTFILE;
5806 vrc = RTFileOpen (&file, filePath, RTFILE_O_READ);
5807 if (VBOX_SUCCESS (vrc))
5808 {
5809 uint64_t size = 0;
5810 vrc = RTFileGetSize (file, &size);
5811 if (VBOX_SUCCESS (vrc))
5812 mActualSize = size;
5813 RTFileClose (file);
5814 }
5815 if (VBOX_FAILURE (vrc))
5816 break;
5817 }
5818
5819 /* query logical size only for non-differencing images */
5820 if (!mParent)
5821 {
5822 uint64_t size = VDGetSize (mContainer, 0);
5823 /* convert to MBytes */
5824 mSize = size / 1024 / 1024;
5825 }
5826 }
5827 while (0);
5828
5829 VDCloseAll (mContainer);
5830
5831 /* enter the lock again */
5832 alock.enter();
5833
5834 /* remove the reference */
5835 releaseReader();
5836
5837 if (FAILED (rc) || VBOX_FAILURE (vrc) || !errMsg.isNull())
5838 {
5839 LogWarningFunc (("'%ls' is not accessible "
5840 "(rc=%08X, vrc=%Vrc, errMsg='%ls')\n",
5841 mFilePathFull.raw(), rc, vrc, errMsg.raw()));
5842
5843 if (aAccessError)
5844 {
5845 if (!errMsg.isNull())
5846 *aAccessError = errMsg;
5847 else if (!mLastVDError.isNull())
5848 *aAccessError = mLastVDError;
5849 else if (VBOX_FAILURE (vrc))
5850 *aAccessError = Utf8StrFmt (
5851 tr ("Could not access hard disk image '%ls' (%Vrc)"),
5852 mFilePathFull.raw(), vrc);
5853 }
5854
5855 /* downgrade to not accessible */
5856 mState = Created;
5857 }
5858 else
5859 {
5860 if (aAccessError)
5861 aAccessError->setNull();
5862
5863 mState = Accessible;
5864 }
5865
5866 /* inform waiters if there are any */
5867 if (mStateCheckWaiters > 0)
5868 {
5869 RTSemEventMultiSignal (mStateCheckSem);
5870 }
5871 else
5872 {
5873 /* delete the semaphore ourselves */
5874 RTSemEventMultiDestroy (mStateCheckSem);
5875 mStateCheckSem = NIL_RTSEMEVENTMULTI;
5876 }
5877
5878 /* cleanup the last error report from VDError() */
5879 mLastVDError.setNull();
5880
5881 return rc;
5882}
5883
5884/**
5885 * Helper to create hard disk images.
5886 *
5887 * @param aSize size in MB
5888 * @param aDynamic dynamic or fixed image
5889 * @param aProgress address of IProgress pointer to return
5890 */
5891HRESULT HVHDImage::createImage (ULONG64 aSize, BOOL aDynamic,
5892 IProgress **aProgress)
5893{
5894 ComAssertMsgFailed (("Not implemented"));
5895 return E_NOTIMPL;
5896
5897/// @todo later
5898// Use code from HVirtualDiskImage::createImage as an example.
5899}
5900
5901/* static */
5902DECLCALLBACK(int) HVHDImage::VDITaskThread (RTTHREAD thread, void *pvUser)
5903{
5904 AssertMsgFailed (("Not implemented"));
5905 return VERR_GENERAL_FAILURE;
5906
5907/// @todo later
5908// Use code from HVirtualDiskImage::VDITaskThread as an example.
5909}
5910
5911/* static */
5912DECLCALLBACK(void) HVHDImage::VDError (void *pvUser, int rc, RT_SRC_POS_DECL,
5913 const char *pszFormat, va_list va)
5914{
5915 HVHDImage *that = static_cast <HVHDImage *> (pvUser);
5916 AssertReturnVoid (that != NULL);
5917
5918 /// @todo pass the error message to the operation initiator
5919 Utf8Str err = Utf8StrFmtVA (pszFormat, va);
5920 if (VBOX_FAILURE (rc))
5921 err = Utf8StrFmt ("%s (%Vrc)", err.raw(), rc);
5922
5923 if (that->mLastVDError.isNull())
5924 that->mLastVDError = err;
5925 else
5926 that->mLastVDError = Utf8StrFmt
5927 ("%s.\n%s", that->mLastVDError.raw(), err.raw());
5928}
5929
Note: See TracBrowser for help on using the repository browser.

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