VirtualBox

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

Last change on this file since 17835 was 17825, checked in by vboxsync, 16 years ago

API/HardDIsk: introduce parameter for specifying the image variant to be created.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 140.9 KB
Line 
1/* $Id: HardDiskImpl.cpp 17825 2009-03-13 14:03:09Z vboxsync $ */
2
3/** @file
4 *
5 * VirtualBox COM class implementation
6 */
7
8/*
9 * Copyright (C) 2008 Sun Microsystems, Inc.
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 *
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
20 * Clara, CA 95054 USA or visit http://www.sun.com if you need
21 * additional information or have any questions.
22 */
23
24#include "HardDiskImpl.h"
25
26#include "ProgressImpl.h"
27#include "SystemPropertiesImpl.h"
28
29#include "Logging.h"
30
31#include <VBox/com/array.h>
32#include <VBox/com/SupportErrorInfo.h>
33
34#include <VBox/err.h>
35#include <VBox/settings.h>
36
37#include <iprt/param.h>
38#include <iprt/path.h>
39#include <iprt/file.h>
40#include <iprt/tcp.h>
41
42#include <list>
43#include <memory>
44
45////////////////////////////////////////////////////////////////////////////////
46// Globals
47////////////////////////////////////////////////////////////////////////////////
48
49/**
50 * Asynchronous task thread parameter bucket.
51 *
52 * Note that instances of this class must be created using new() because the
53 * task thread function will delete them when the task is complete!
54 *
55 * @note The constructor of this class adds a caller on the managed HardDisk
56 * object which is automatically released upon destruction.
57 */
58struct HardDisk::Task : public com::SupportErrorInfoBase
59{
60 enum Operation { CreateDynamic, CreateFixed, CreateDiff,
61 Merge, Clone, Delete, Reset };
62
63 HardDisk *that;
64 VirtualBoxBaseProto::AutoCaller autoCaller;
65
66 ComObjPtr <Progress> progress;
67 Operation operation;
68
69 /** Where to save the result when executed using #runNow(). */
70 HRESULT rc;
71
72 Task (HardDisk *aThat, Progress *aProgress, Operation aOperation)
73 : that (aThat), autoCaller (aThat)
74 , progress (aProgress)
75 , operation (aOperation)
76 , rc (S_OK) {}
77
78 ~Task();
79
80 void setData (HardDisk *aTarget)
81 {
82 d.target = aTarget;
83 HRESULT rc = d.target->addCaller();
84 AssertComRC (rc);
85 }
86
87 void setData (MergeChain *aChain)
88 {
89 AssertReturnVoid (aChain != NULL);
90 d.chain.reset (aChain);
91 }
92
93 HRESULT startThread();
94 HRESULT runNow();
95
96 struct Data
97 {
98 Data() : size (0) {}
99
100 /* CreateDynamic, CreateStatic */
101
102 uint64_t size;
103
104 /* CreateDynamic, CreateStatic, CreateDiff, Clone */
105
106 HardDiskVariant_T variant;
107
108 /* CreateDiff */
109
110 ComObjPtr<HardDisk> target;
111
112 /* Merge */
113
114 /** Hard disks to merge, in {parent,child} order */
115 std::auto_ptr <MergeChain> chain;
116 }
117 d;
118
119protected:
120
121 // SupportErrorInfoBase interface
122 const GUID &mainInterfaceID() const { return COM_IIDOF (IHardDisk); }
123 const char *componentName() const { return HardDisk::ComponentName(); }
124};
125
126HardDisk::Task::~Task()
127{
128 /* remove callers added by setData() */
129 if (!d.target.isNull())
130 d.target->releaseCaller();
131}
132
133/**
134 * Starts a new thread driven by the HardDisk::taskThread() function and passes
135 * this Task instance as an argument.
136 *
137 * Note that if this method returns success, this Task object becomes an ownee
138 * of the started thread and will be automatically deleted when the thread
139 * terminates.
140 *
141 * @note When the task is executed by this method, IProgress::notifyComplete()
142 * is automatically called for the progress object associated with this
143 * task when the task is finished to signal the operation completion for
144 * other threads asynchronously waiting for it.
145 */
146HRESULT HardDisk::Task::startThread()
147{
148 int vrc = RTThreadCreate (NULL, HardDisk::taskThread, this,
149 0, RTTHREADTYPE_MAIN_HEAVY_WORKER, 0,
150 "HardDisk::Task");
151 ComAssertMsgRCRet (vrc,
152 ("Could not create HardDisk::Task thread (%Rrc)\n", vrc), E_FAIL);
153
154 return S_OK;
155}
156
157/**
158 * Runs HardDisk::taskThread() by passing it this Task instance as an argument
159 * on the current thread instead of creating a new one.
160 *
161 * This call implies that it is made on another temporary thread created for
162 * some asynchronous task. Avoid calling it from a normal thread since the task
163 * operatinos are potentially lengthy and will block the calling thread in this
164 * case.
165 *
166 * Note that this Task object will be deleted by taskThread() when this method
167 * returns!
168 *
169 * @note When the task is executed by this method, IProgress::notifyComplete()
170 * is not called for the progress object associated with this task when
171 * the task is finished. Instead, the result of the operation is returned
172 * by this method directly and it's the caller's responsibility to
173 * complete the progress object in this case.
174 */
175HRESULT HardDisk::Task::runNow()
176{
177 HardDisk::taskThread (NIL_RTTHREAD, this);
178
179 return rc;
180}
181
182////////////////////////////////////////////////////////////////////////////////
183
184/**
185 * Helper class for merge operations.
186 *
187 * @note It is assumed that when modifying methods of this class are called,
188 * HardDisk::treeLock() is held in read mode.
189 */
190class HardDisk::MergeChain : public HardDisk::List,
191 public com::SupportErrorInfoBase
192{
193public:
194
195 MergeChain (bool aForward, bool aIgnoreAttachments)
196 : mForward (aForward)
197 , mIgnoreAttachments (aIgnoreAttachments) {}
198
199 ~MergeChain()
200 {
201 for (iterator it = mChildren.begin(); it != mChildren.end(); ++ it)
202 {
203 HRESULT rc = (*it)->UnlockWrite (NULL);
204 AssertComRC (rc);
205
206 (*it)->releaseCaller();
207 }
208
209 for (iterator it = begin(); it != end(); ++ it)
210 {
211 AutoWriteLock alock (*it);
212 Assert ((*it)->m.state == MediaState_LockedWrite ||
213 (*it)->m.state == MediaState_Deleting);
214 if ((*it)->m.state == MediaState_LockedWrite)
215 (*it)->UnlockWrite (NULL);
216 else
217 (*it)->m.state = MediaState_Created;
218
219 (*it)->releaseCaller();
220 }
221
222 if (!mParent.isNull())
223 mParent->releaseCaller();
224 }
225
226 HRESULT addSource (HardDisk *aHardDisk)
227 {
228 HRESULT rc = aHardDisk->addCaller();
229 CheckComRCReturnRC (rc);
230
231 AutoWriteLock alock (aHardDisk);
232
233 if (mForward)
234 {
235 rc = checkChildrenAndAttachmentsAndImmutable (aHardDisk);
236 if (FAILED (rc))
237 {
238 aHardDisk->releaseCaller();
239 return rc;
240 }
241 }
242
243 /* go to Deleting */
244 switch (aHardDisk->m.state)
245 {
246 case MediaState_Created:
247 aHardDisk->m.state = MediaState_Deleting;
248 break;
249 default:
250 aHardDisk->releaseCaller();
251 return aHardDisk->setStateError();
252 }
253
254 push_front (aHardDisk);
255
256 if (mForward)
257 {
258 /* we will need parent to reparent target */
259 if (!aHardDisk->mParent.isNull())
260 {
261 rc = aHardDisk->mParent->addCaller();
262 CheckComRCReturnRC (rc);
263
264 mParent = aHardDisk->mParent;
265 }
266 }
267 else
268 {
269 /* we will need to reparent children */
270 for (List::const_iterator it = aHardDisk->children().begin();
271 it != aHardDisk->children().end(); ++ it)
272 {
273 rc = (*it)->addCaller();
274 CheckComRCReturnRC (rc);
275
276 rc = (*it)->LockWrite (NULL);
277 if (FAILED (rc))
278 {
279 (*it)->releaseCaller();
280 return rc;
281 }
282
283 mChildren.push_back (*it);
284 }
285 }
286
287 return S_OK;
288 }
289
290 HRESULT addTarget (HardDisk *aHardDisk)
291 {
292 HRESULT rc = aHardDisk->addCaller();
293 CheckComRCReturnRC (rc);
294
295 AutoWriteLock alock (aHardDisk);
296
297 if (!mForward)
298 {
299 rc = checkChildrenAndImmutable (aHardDisk);
300 if (FAILED (rc))
301 {
302 aHardDisk->releaseCaller();
303 return rc;
304 }
305 }
306
307 /* go to LockedWrite */
308 rc = aHardDisk->LockWrite (NULL);
309 if (FAILED (rc))
310 {
311 aHardDisk->releaseCaller();
312 return rc;
313 }
314
315 push_front (aHardDisk);
316
317 return S_OK;
318 }
319
320 HRESULT addIntermediate (HardDisk *aHardDisk)
321 {
322 HRESULT rc = aHardDisk->addCaller();
323 CheckComRCReturnRC (rc);
324
325 AutoWriteLock alock (aHardDisk);
326
327 rc = checkChildrenAndAttachments (aHardDisk);
328 if (FAILED (rc))
329 {
330 aHardDisk->releaseCaller();
331 return rc;
332 }
333
334 /* go to Deleting */
335 switch (aHardDisk->m.state)
336 {
337 case MediaState_Created:
338 aHardDisk->m.state = MediaState_Deleting;
339 break;
340 default:
341 aHardDisk->releaseCaller();
342 return aHardDisk->setStateError();
343 }
344
345 push_front (aHardDisk);
346
347 return S_OK;
348 }
349
350 bool isForward() const { return mForward; }
351 HardDisk *parent() const { return mParent; }
352 const List &children() const { return mChildren; }
353
354 HardDisk *source() const
355 { AssertReturn (size() > 0, NULL); return mForward ? front() : back(); }
356
357 HardDisk *target() const
358 { AssertReturn (size() > 0, NULL); return mForward ? back() : front(); }
359
360protected:
361
362 // SupportErrorInfoBase interface
363 const GUID &mainInterfaceID() const { return COM_IIDOF (IHardDisk); }
364 const char *componentName() const { return HardDisk::ComponentName(); }
365
366private:
367
368 HRESULT check (HardDisk *aHardDisk, bool aChildren, bool aAttachments,
369 bool aImmutable)
370 {
371 if (aChildren)
372 {
373 /* not going to multi-merge as it's too expensive */
374 if (aHardDisk->children().size() > 1)
375 {
376 return setError (E_FAIL,
377 tr ("Hard disk '%ls' involved in the merge operation "
378 "has more than one child hard disk (%d)"),
379 aHardDisk->m.locationFull.raw(),
380 aHardDisk->children().size());
381 }
382 }
383
384 if (aAttachments && !mIgnoreAttachments)
385 {
386 if (aHardDisk->m.backRefs.size() != 0)
387 return setError (E_FAIL,
388 tr ("Hard disk '%ls' is attached to %d virtual machines"),
389 aHardDisk->m.locationFull.raw(),
390 aHardDisk->m.backRefs.size());
391 }
392
393 if (aImmutable)
394 {
395 if (aHardDisk->mm.type == HardDiskType_Immutable)
396 return setError (E_FAIL,
397 tr ("Hard disk '%ls' is immutable"),
398 aHardDisk->m.locationFull.raw());
399 }
400
401 return S_OK;
402 }
403
404 HRESULT checkChildren (HardDisk *aHardDisk)
405 { return check (aHardDisk, true, false, false); }
406
407 HRESULT checkChildrenAndImmutable (HardDisk *aHardDisk)
408 { return check (aHardDisk, true, false, true); }
409
410 HRESULT checkChildrenAndAttachments (HardDisk *aHardDisk)
411 { return check (aHardDisk, true, true, false); }
412
413 HRESULT checkChildrenAndAttachmentsAndImmutable (HardDisk *aHardDisk)
414 { return check (aHardDisk, true, true, true); }
415
416 /** true if forward merge, false if backward */
417 bool mForward : 1;
418 /** true to not perform attachment checks */
419 bool mIgnoreAttachments : 1;
420
421 /** Parent of the source when forward merge (if any) */
422 ComObjPtr <HardDisk> mParent;
423 /** Children of the source when backward merge (if any) */
424 List mChildren;
425};
426
427////////////////////////////////////////////////////////////////////////////////
428// HardDisk class
429////////////////////////////////////////////////////////////////////////////////
430
431// constructor / destructor
432////////////////////////////////////////////////////////////////////////////////
433
434DEFINE_EMPTY_CTOR_DTOR (HardDisk)
435
436HRESULT HardDisk::FinalConstruct()
437{
438 /* Initialize the callbacks of the VD error interface */
439 mm.vdIfCallsError.cbSize = sizeof (VDINTERFACEERROR);
440 mm.vdIfCallsError.enmInterface = VDINTERFACETYPE_ERROR;
441 mm.vdIfCallsError.pfnError = vdErrorCall;
442
443 /* Initialize the callbacks of the VD progress interface */
444 mm.vdIfCallsProgress.cbSize = sizeof (VDINTERFACEPROGRESS);
445 mm.vdIfCallsProgress.enmInterface = VDINTERFACETYPE_PROGRESS;
446 mm.vdIfCallsProgress.pfnProgress = vdProgressCall;
447
448 /* Initialize the callbacks of the VD config interface */
449 mm.vdIfCallsConfig.cbSize = sizeof (VDINTERFACECONFIG);
450 mm.vdIfCallsConfig.enmInterface = VDINTERFACETYPE_CONFIG;
451 mm.vdIfCallsConfig.pfnAreKeysValid = vdConfigAreKeysValid;
452 mm.vdIfCallsConfig.pfnQuerySize = vdConfigQuerySize;
453 mm.vdIfCallsConfig.pfnQuery = vdConfigQuery;
454
455 /* Initialize the callbacks of the VD TCP interface (we always use the host
456 * IP stack for now) */
457 mm.vdIfCallsTcpNet.cbSize = sizeof (VDINTERFACETCPNET);
458 mm.vdIfCallsTcpNet.enmInterface = VDINTERFACETYPE_TCPNET;
459 mm.vdIfCallsTcpNet.pfnClientConnect = RTTcpClientConnect;
460 mm.vdIfCallsTcpNet.pfnClientClose = RTTcpClientClose;
461 mm.vdIfCallsTcpNet.pfnSelectOne = RTTcpSelectOne;
462 mm.vdIfCallsTcpNet.pfnRead = RTTcpRead;
463 mm.vdIfCallsTcpNet.pfnWrite = RTTcpWrite;
464 mm.vdIfCallsTcpNet.pfnFlush = RTTcpFlush;
465
466 /* Initialize the per-disk interface chain */
467 int vrc;
468 vrc = VDInterfaceAdd (&mm.vdIfError,
469 "HardDisk::vdInterfaceError",
470 VDINTERFACETYPE_ERROR,
471 &mm.vdIfCallsError, this, &mm.vdDiskIfaces);
472 AssertRCReturn (vrc, E_FAIL);
473
474 vrc = VDInterfaceAdd (&mm.vdIfProgress,
475 "HardDisk::vdInterfaceProgress",
476 VDINTERFACETYPE_PROGRESS,
477 &mm.vdIfCallsProgress, this, &mm.vdDiskIfaces);
478 AssertRCReturn (vrc, E_FAIL);
479
480 vrc = VDInterfaceAdd (&mm.vdIfConfig,
481 "HardDisk::vdInterfaceConfig",
482 VDINTERFACETYPE_CONFIG,
483 &mm.vdIfCallsConfig, this, &mm.vdDiskIfaces);
484 AssertRCReturn (vrc, E_FAIL);
485
486 vrc = VDInterfaceAdd (&mm.vdIfTcpNet,
487 "HardDisk::vdInterfaceTcpNet",
488 VDINTERFACETYPE_TCPNET,
489 &mm.vdIfCallsTcpNet, this, &mm.vdDiskIfaces);
490 AssertRCReturn (vrc, E_FAIL);
491
492 return S_OK;
493}
494
495void HardDisk::FinalRelease()
496{
497 uninit();
498}
499
500// public initializer/uninitializer for internal purposes only
501////////////////////////////////////////////////////////////////////////////////
502
503/**
504 * Initializes the hard disk object without creating or opening an associated
505 * storage unit.
506 *
507 * For hard disks that don't have the VD_CAP_CREATE_FIXED or
508 * VD_CAP_CREATE_DYNAMIC capability (and therefore cannot be created or deleted
509 * with the means of VirtualBox) the associated storage unit is assumed to be
510 * ready for use so the state of the hard disk object will be set to Created.
511 *
512 * @param aVirtualBox VirtualBox object.
513 * @param aLocaiton Storage unit location.
514 */
515HRESULT HardDisk::init (VirtualBox *aVirtualBox, CBSTR aFormat,
516 CBSTR aLocation)
517{
518 AssertReturn (aVirtualBox != NULL, E_FAIL);
519 AssertReturn (aFormat != NULL && *aFormat != '\0', E_FAIL);
520
521 /* Enclose the state transition NotReady->InInit->Ready */
522 AutoInitSpan autoInitSpan (this);
523 AssertReturn (autoInitSpan.isOk(), E_FAIL);
524
525 HRESULT rc = S_OK;
526
527 /* share VirtualBox weakly (parent remains NULL so far) */
528 unconst (mVirtualBox) = aVirtualBox;
529
530 /* register with VirtualBox early, since uninit() will
531 * unconditionally unregister on failure */
532 aVirtualBox->addDependentChild (this);
533
534 /* no storage yet */
535 m.state = MediaState_NotCreated;
536
537 /* No storage unit is created yet, no need to queryInfo() */
538
539 rc = setFormat (aFormat);
540 CheckComRCReturnRC (rc);
541
542 if (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File)
543 {
544 rc = setLocation (aLocation);
545 CheckComRCReturnRC (rc);
546 }
547 else
548 {
549 rc = setLocation (aLocation);
550 CheckComRCReturnRC (rc);
551
552 /// @todo later we may want to use a pfnComposeLocation backend info
553 /// callback to generate a well-formed location value (based on the hard
554 /// disk properties we have) rather than allowing each caller to invent
555 /// its own (pseudo-)location.
556 }
557
558 if (!(mm.formatObj->capabilities() &
559 (HardDiskFormatCapabilities_CreateFixed |
560 HardDiskFormatCapabilities_CreateDynamic)))
561 {
562 /* storage for hard disks of this format can neither be explicitly
563 * created by VirtualBox nor deleted, so we place the hard disk to
564 * Created state here and also add it to the registry */
565 m.state = MediaState_Created;
566 unconst (m.id).create();
567 rc = mVirtualBox->registerHardDisk (this);
568
569 /// @todo later we may want to use a pfnIsConfigSufficient backend info
570 /// callback that would tell us when we have enough properties to work
571 /// with the hard disk and this information could be used to actually
572 /// move such hard disks from NotCreated to Created state. Instead of
573 /// pfnIsConfigSufficient we can use HardDiskFormat property
574 /// descriptions to see which properties are mandatory
575 }
576
577 /* Confirm a successful initialization when it's the case */
578 if (SUCCEEDED (rc))
579 autoInitSpan.setSucceeded();
580
581 return rc;
582}
583
584/**
585 * Initializes the hard disk object by opening the storage unit at the specified
586 * location.
587 *
588 * Note that the UUID, format and the parent of this hard disk will be
589 * determined when reading the hard disk storage unit. If the detected parent is
590 * not known to VirtualBox, then this method will fail.
591 *
592 * @param aVirtualBox VirtualBox object.
593 * @param aLocaiton Storage unit location.
594 */
595HRESULT HardDisk::init (VirtualBox *aVirtualBox, CBSTR aLocation)
596{
597 AssertReturn (aVirtualBox, E_INVALIDARG);
598 AssertReturn (aLocation, E_INVALIDARG);
599
600 /* Enclose the state transition NotReady->InInit->Ready */
601 AutoInitSpan autoInitSpan (this);
602 AssertReturn (autoInitSpan.isOk(), E_FAIL);
603
604 HRESULT rc = S_OK;
605
606 /* share VirtualBox weakly (parent remains NULL so far) */
607 unconst (mVirtualBox) = aVirtualBox;
608
609 /* register with VirtualBox early, since uninit() will
610 * unconditionally unregister on failure */
611 aVirtualBox->addDependentChild (this);
612
613 /* there must be a storage unit */
614 m.state = MediaState_Created;
615
616 rc = setLocation (aLocation);
617 CheckComRCReturnRC (rc);
618
619 /* get all the information about the medium from the storage unit */
620 rc = queryInfo();
621 if (SUCCEEDED (rc))
622 {
623 /* if the storage unit is not accessible, it's not acceptable for the
624 * newly opened media so convert this into an error */
625 if (m.state == MediaState_Inaccessible)
626 {
627 Assert (!m.lastAccessError.isEmpty());
628 rc = setError (E_FAIL, Utf8Str (m.lastAccessError));
629 }
630 else
631 {
632 /* storage format must be detected by queryInfo() if the medium is
633 * accessible */
634 AssertReturn (!m.id.isEmpty() && !mm.format.isNull(), E_FAIL);
635 }
636 }
637
638 /* Confirm a successful initialization when it's the case */
639 if (SUCCEEDED (rc))
640 autoInitSpan.setSucceeded();
641
642 return rc;
643}
644
645/**
646 * Initializes the hard disk object by loading its data from the given settings
647 * node.
648 *
649 * @param aVirtualBox VirtualBox object.
650 * @param aParent Parent hard disk or NULL for a root (base) hard disk.
651 * @param aNode <HardDisk> settings node.
652 *
653 * @note Locks VirtualBox lock for writing, treeLock() for writing.
654 */
655HRESULT HardDisk::init (VirtualBox *aVirtualBox, HardDisk *aParent,
656 const settings::Key &aNode)
657{
658 using namespace settings;
659
660 AssertReturn (aVirtualBox, E_INVALIDARG);
661
662 /* Enclose the state transition NotReady->InInit->Ready */
663 AutoInitSpan autoInitSpan (this);
664 AssertReturn (autoInitSpan.isOk(), E_FAIL);
665
666 HRESULT rc = S_OK;
667
668 /* share VirtualBox and parent weakly */
669 unconst (mVirtualBox) = aVirtualBox;
670
671 /* register with VirtualBox/parent early, since uninit() will
672 * unconditionally unregister on failure */
673 if (aParent == NULL)
674 aVirtualBox->addDependentChild (this);
675 else
676 {
677 /* we set mParent */
678 AutoWriteLock treeLock (this->treeLock());
679
680 mParent = aParent;
681 aParent->addDependentChild (this);
682 }
683
684 /* see below why we don't call queryInfo() (and therefore treat the medium
685 * as inaccessible for now */
686 m.state = MediaState_Inaccessible;
687 m.lastAccessError = tr ("Accessibility check was not yet performed");
688
689 /* required */
690 unconst (m.id) = aNode.value <Guid> ("uuid");
691
692 /* optional */
693 {
694 settings::Key descNode = aNode.findKey ("Description");
695 if (!descNode.isNull())
696 m.description = descNode.keyStringValue();
697 }
698
699 /* required */
700 Bstr format = aNode.stringValue ("format");
701 AssertReturn (!format.isNull(), E_FAIL);
702 rc = setFormat (format);
703 CheckComRCReturnRC (rc);
704
705 /* optional, only for diffs, default is false */
706 if (aParent != NULL)
707 mm.autoReset = aNode.value <bool> ("autoReset");
708 else
709 mm.autoReset = false;
710
711 /* properties (after setting the format as it populates the map). Note that
712 * if some properties are not supported but preseint in the settings file,
713 * they will still be read and accessible (for possible backward
714 * compatibility; we can also clean them up from the XML upon next
715 * XML format versino change if we wish) */
716 Key::List properties = aNode.keys ("Property");
717 for (Key::List::const_iterator it = properties.begin();
718 it != properties.end(); ++ it)
719 {
720 mm.properties [Bstr (it->stringValue ("name"))] =
721 Bstr (it->stringValue ("value"));
722 }
723
724 /* required */
725 Bstr location = aNode.stringValue ("location");
726 rc = setLocation (location);
727 CheckComRCReturnRC (rc);
728
729 /* type is only for base hard disks */
730 if (mParent.isNull())
731 {
732 const char *type = aNode.stringValue ("type");
733 if (strcmp (type, "Normal") == 0)
734 mm.type = HardDiskType_Normal;
735 else if (strcmp (type, "Immutable") == 0)
736 mm.type = HardDiskType_Immutable;
737 else if (strcmp (type, "Writethrough") == 0)
738 mm.type = HardDiskType_Writethrough;
739 else
740 AssertFailed();
741 }
742
743 LogFlowThisFunc (("m.locationFull='%ls', mm.format=%ls, m.id={%RTuuid}\n",
744 m.locationFull.raw(), mm.format.raw(), m.id.raw()));
745
746 /* Don't call queryInfo() for registered media to prevent the calling
747 * thread (i.e. the VirtualBox server startup thread) from an unexpected
748 * freeze but mark it as initially inaccessible instead. The vital UUID,
749 * location and format properties are read from the registry file above; to
750 * get the actual state and the rest of the data, the user will have to call
751 * COMGETTER(State). */
752
753 /* load all children */
754 Key::List hardDisks = aNode.keys ("HardDisk");
755 for (Key::List::const_iterator it = hardDisks.begin();
756 it != hardDisks.end(); ++ it)
757 {
758 ComObjPtr<HardDisk> hardDisk;
759 hardDisk.createObject();
760 rc = hardDisk->init (aVirtualBox, this, *it);
761 CheckComRCBreakRC (rc);
762
763 rc = mVirtualBox->registerHardDisk(hardDisk, false /* aSaveRegistry */);
764 CheckComRCBreakRC (rc);
765 }
766
767 /* Confirm a successful initialization when it's the case */
768 if (SUCCEEDED (rc))
769 autoInitSpan.setSucceeded();
770
771 return rc;
772}
773
774/**
775 * Uninitializes the instance.
776 *
777 * Called either from FinalRelease() or by the parent when it gets destroyed.
778 *
779 * @note All children of this hard disk get uninitialized by calling their
780 * uninit() methods.
781 *
782 * @note Locks treeLock() for writing, VirtualBox for writing.
783 */
784void HardDisk::uninit()
785{
786 /* Enclose the state transition Ready->InUninit->NotReady */
787 AutoUninitSpan autoUninitSpan (this);
788 if (autoUninitSpan.uninitDone())
789 return;
790
791 if (!mm.formatObj.isNull())
792 {
793 /* remove the caller reference we added in setFormat() */
794 mm.formatObj->releaseCaller();
795 mm.formatObj.setNull();
796 }
797
798 if (m.state == MediaState_Deleting)
799 {
800 /* we are being uninitialized after've been deleted by merge.
801 * Reparenting has already been done so don't touch it here (we are
802 * now orphans and remoeDependentChild() will assert) */
803
804 Assert (mParent.isNull());
805 }
806 else
807 {
808 /* we uninit children and reset mParent
809 * and VirtualBox::removeDependentChild() needs a write lock */
810 AutoMultiWriteLock2 alock (mVirtualBox->lockHandle(), this->treeLock());
811
812 uninitDependentChildren();
813
814 if (!mParent.isNull())
815 {
816 mParent->removeDependentChild (this);
817 mParent.setNull();
818 }
819 else
820 mVirtualBox->removeDependentChild (this);
821 }
822
823 unconst (mVirtualBox).setNull();
824}
825
826// IHardDisk properties
827////////////////////////////////////////////////////////////////////////////////
828
829STDMETHODIMP HardDisk::COMGETTER(Format) (BSTR *aFormat)
830{
831 if (aFormat == NULL)
832 return E_POINTER;
833
834 AutoCaller autoCaller (this);
835 CheckComRCReturnRC (autoCaller.rc());
836
837 /* no need to lock, mm.format is const */
838 mm.format.cloneTo (aFormat);
839
840 return S_OK;
841}
842
843STDMETHODIMP HardDisk::COMGETTER(Type) (HardDiskType_T *aType)
844{
845 if (aType == NULL)
846 return E_POINTER;
847
848 AutoCaller autoCaller (this);
849 CheckComRCReturnRC (autoCaller.rc());
850
851 AutoReadLock alock (this);
852
853 *aType = mm.type;
854
855 return S_OK;
856}
857
858STDMETHODIMP HardDisk::COMSETTER(Type) (HardDiskType_T aType)
859{
860 AutoCaller autoCaller (this);
861 CheckComRCReturnRC (autoCaller.rc());
862
863 /* VirtualBox::saveSettings() needs a write lock */
864 AutoMultiWriteLock2 alock (mVirtualBox, this);
865
866 switch (m.state)
867 {
868 case MediaState_Created:
869 case MediaState_Inaccessible:
870 break;
871 default:
872 return setStateError();
873 }
874
875 if (mm.type == aType)
876 {
877 /* Nothing to do */
878 return S_OK;
879 }
880
881 /* we access mParent & children() */
882 AutoReadLock treeLock (this->treeLock());
883
884 /* cannot change the type of a differencing hard disk */
885 if (!mParent.isNull())
886 return setError (E_FAIL,
887 tr ("Hard disk '%ls' is a differencing hard disk"),
888 m.locationFull.raw());
889
890 /* cannot change the type of a hard disk being in use */
891 if (m.backRefs.size() != 0)
892 return setError (E_FAIL,
893 tr ("Hard disk '%ls' is attached to %d virtual machines"),
894 m.locationFull.raw(), m.backRefs.size());
895
896 switch (aType)
897 {
898 case HardDiskType_Normal:
899 case HardDiskType_Immutable:
900 {
901 /* normal can be easily converted to imutable and vice versa even
902 * if they have children as long as they are not attached to any
903 * machine themselves */
904 break;
905 }
906 case HardDiskType_Writethrough:
907 {
908 /* cannot change to writethrough if there are children */
909 if (children().size() != 0)
910 return setError (E_FAIL,
911 tr ("Hard disk '%ls' has %d child hard disks"),
912 children().size());
913 break;
914 }
915 default:
916 AssertFailedReturn (E_FAIL);
917 }
918
919 mm.type = aType;
920
921 HRESULT rc = mVirtualBox->saveSettings();
922
923 return rc;
924}
925
926STDMETHODIMP HardDisk::COMGETTER(Parent) (IHardDisk **aParent)
927{
928 if (aParent == NULL)
929 return E_POINTER;
930
931 AutoCaller autoCaller (this);
932 CheckComRCReturnRC (autoCaller.rc());
933
934 /* we access mParent */
935 AutoReadLock treeLock (this->treeLock());
936
937 mParent.queryInterfaceTo (aParent);
938
939 return S_OK;
940}
941
942STDMETHODIMP HardDisk::COMGETTER(Children) (ComSafeArrayOut (IHardDisk *, aChildren))
943{
944 if (ComSafeArrayOutIsNull (aChildren))
945 return E_POINTER;
946
947 AutoCaller autoCaller (this);
948 CheckComRCReturnRC (autoCaller.rc());
949
950 /* we access children */
951 AutoReadLock treeLock (this->treeLock());
952
953 SafeIfaceArray<IHardDisk> children (this->children());
954 children.detachTo (ComSafeArrayOutArg (aChildren));
955
956 return S_OK;
957}
958
959STDMETHODIMP HardDisk::COMGETTER(Root)(IHardDisk **aRoot)
960{
961 if (aRoot == NULL)
962 return E_POINTER;
963
964 /* root() will do callers/locking */
965
966 root().queryInterfaceTo (aRoot);
967
968 return S_OK;
969}
970
971STDMETHODIMP HardDisk::COMGETTER(ReadOnly) (BOOL *aReadOnly)
972{
973 if (aReadOnly == NULL)
974 return E_POINTER;
975
976 AutoCaller autoCaller (this);
977 CheckComRCReturnRC (autoCaller.rc());
978
979 /* isRadOnly() will do locking */
980
981 *aReadOnly = isReadOnly();
982
983 return S_OK;
984}
985
986STDMETHODIMP HardDisk::COMGETTER(LogicalSize) (ULONG64 *aLogicalSize)
987{
988 CheckComArgOutPointerValid (aLogicalSize);
989
990 {
991 AutoCaller autoCaller (this);
992 CheckComRCReturnRC (autoCaller.rc());
993
994 AutoReadLock alock (this);
995
996 /* we access mParent */
997 AutoReadLock treeLock (this->treeLock());
998
999 if (mParent.isNull())
1000 {
1001 *aLogicalSize = mm.logicalSize;
1002
1003 return S_OK;
1004 }
1005 }
1006
1007 /* We assume that some backend may decide to return a meaningless value in
1008 * response to VDGetSize() for differencing hard disks and therefore
1009 * always ask the base hard disk ourselves. */
1010
1011 /* root() will do callers/locking */
1012
1013 return root()->COMGETTER (LogicalSize) (aLogicalSize);
1014}
1015
1016STDMETHODIMP HardDisk::COMGETTER(AutoReset) (BOOL *aAutoReset)
1017{
1018 CheckComArgOutPointerValid (aAutoReset);
1019
1020 AutoCaller autoCaller (this);
1021 CheckComRCReturnRC (autoCaller.rc());
1022
1023 AutoReadLock alock (this);
1024
1025 if (mParent.isNull())
1026 *aAutoReset = FALSE;
1027
1028 *aAutoReset = mm.autoReset;
1029
1030 return S_OK;
1031}
1032
1033STDMETHODIMP HardDisk::COMSETTER(AutoReset) (BOOL aAutoReset)
1034{
1035 AutoCaller autoCaller (this);
1036 CheckComRCReturnRC (autoCaller.rc());
1037
1038 /* VirtualBox::saveSettings() needs a write lock */
1039 AutoMultiWriteLock2 alock (mVirtualBox, this);
1040
1041 if (mParent.isNull())
1042 return setError (VBOX_E_NOT_SUPPORTED,
1043 tr ("Hard disk '%ls' is not differencing"),
1044 m.locationFull.raw());
1045
1046 if (mm.autoReset != aAutoReset)
1047 {
1048 mm.autoReset = aAutoReset;
1049
1050 return mVirtualBox->saveSettings();
1051 }
1052
1053 return S_OK;
1054}
1055
1056// IHardDisk methods
1057////////////////////////////////////////////////////////////////////////////////
1058
1059STDMETHODIMP HardDisk::GetProperty (IN_BSTR aName, BSTR *aValue)
1060{
1061 CheckComArgStrNotEmptyOrNull (aName);
1062 CheckComArgOutPointerValid (aValue);
1063
1064 AutoCaller autoCaller (this);
1065 CheckComRCReturnRC (autoCaller.rc());
1066
1067 AutoReadLock alock (this);
1068
1069 Data::PropertyMap::const_iterator it = mm.properties.find (Bstr (aName));
1070 if (it == mm.properties.end())
1071 return setError (VBOX_E_OBJECT_NOT_FOUND,
1072 tr ("Property '%ls' does not exist"), aName);
1073
1074 it->second.cloneTo (aValue);
1075
1076 return S_OK;
1077}
1078
1079STDMETHODIMP HardDisk::SetProperty (IN_BSTR aName, IN_BSTR aValue)
1080{
1081 CheckComArgStrNotEmptyOrNull (aName);
1082
1083 AutoCaller autoCaller (this);
1084 CheckComRCReturnRC (autoCaller.rc());
1085
1086 /* VirtualBox::saveSettings() needs a write lock */
1087 AutoMultiWriteLock2 alock (mVirtualBox, this);
1088
1089 switch (m.state)
1090 {
1091 case MediaState_Created:
1092 case MediaState_Inaccessible:
1093 break;
1094 default:
1095 return setStateError();
1096 }
1097
1098 Data::PropertyMap::iterator it = mm.properties.find (Bstr (aName));
1099 if (it == mm.properties.end())
1100 return setError (VBOX_E_OBJECT_NOT_FOUND,
1101 tr ("Property '%ls' does not exist"), aName);
1102
1103 it->second = aValue;
1104
1105 HRESULT rc = mVirtualBox->saveSettings();
1106
1107 return rc;
1108}
1109
1110STDMETHODIMP HardDisk::GetProperties(IN_BSTR aNames,
1111 ComSafeArrayOut (BSTR, aReturnNames),
1112 ComSafeArrayOut (BSTR, aReturnValues))
1113{
1114 CheckComArgOutSafeArrayPointerValid (aReturnNames);
1115 CheckComArgOutSafeArrayPointerValid (aReturnValues);
1116
1117 AutoCaller autoCaller (this);
1118 CheckComRCReturnRC (autoCaller.rc());
1119
1120 AutoReadLock alock (this);
1121
1122 /// @todo make use of aNames according to the documentation
1123 NOREF (aNames);
1124
1125 com::SafeArray <BSTR> names (mm.properties.size());
1126 com::SafeArray <BSTR> values (mm.properties.size());
1127 size_t i = 0;
1128
1129 for (Data::PropertyMap::const_iterator it = mm.properties.begin();
1130 it != mm.properties.end(); ++ it)
1131 {
1132 it->first.cloneTo (&names [i]);
1133 it->second.cloneTo (&values [i]);
1134 ++ i;
1135 }
1136
1137 names.detachTo (ComSafeArrayOutArg (aReturnNames));
1138 values.detachTo (ComSafeArrayOutArg (aReturnValues));
1139
1140 return S_OK;
1141}
1142
1143STDMETHODIMP HardDisk::SetProperties(ComSafeArrayIn (IN_BSTR, aNames),
1144 ComSafeArrayIn (IN_BSTR, aValues))
1145{
1146 CheckComArgSafeArrayNotNull (aNames);
1147 CheckComArgSafeArrayNotNull (aValues);
1148
1149 AutoCaller autoCaller (this);
1150 CheckComRCReturnRC (autoCaller.rc());
1151
1152 /* VirtualBox::saveSettings() needs a write lock */
1153 AutoMultiWriteLock2 alock (mVirtualBox, this);
1154
1155 com::SafeArray <IN_BSTR> names (ComSafeArrayInArg (aNames));
1156 com::SafeArray <IN_BSTR> values (ComSafeArrayInArg (aValues));
1157
1158 /* first pass: validate names */
1159 for (size_t i = 0; i < names.size(); ++ i)
1160 {
1161 if (mm.properties.find (Bstr (names [i])) == mm.properties.end())
1162 return setError (VBOX_E_OBJECT_NOT_FOUND,
1163 tr ("Property '%ls' does not exist"), names [i]);
1164 }
1165
1166 /* second pass: assign */
1167 for (size_t i = 0; i < names.size(); ++ i)
1168 {
1169 Data::PropertyMap::iterator it = mm.properties.find (Bstr (names [i]));
1170 AssertReturn (it != mm.properties.end(), E_FAIL);
1171
1172 it->second = values [i];
1173 }
1174
1175 HRESULT rc = mVirtualBox->saveSettings();
1176
1177 return rc;
1178}
1179
1180STDMETHODIMP HardDisk::CreateDynamicStorage(ULONG64 aLogicalSize,
1181 HardDiskVariant_T aVariant,
1182 IProgress **aProgress)
1183{
1184 CheckComArgOutPointerValid (aProgress);
1185
1186 AutoCaller autoCaller (this);
1187 CheckComRCReturnRC (autoCaller.rc());
1188
1189 AutoWriteLock alock (this);
1190
1191 if (!(mm.formatObj->capabilities() &
1192 HardDiskFormatCapabilities_CreateDynamic))
1193 return setError (VBOX_E_NOT_SUPPORTED,
1194 tr ("Hard disk format '%ls' does not support dynamic storage "
1195 "creation"), mm.format.raw());
1196
1197 switch (m.state)
1198 {
1199 case MediaState_NotCreated:
1200 break;
1201 default:
1202 return setStateError();
1203 }
1204
1205 ComObjPtr <Progress> progress;
1206 progress.createObject();
1207 HRESULT rc = progress->init (mVirtualBox, static_cast<IHardDisk*>(this),
1208 BstrFmt (tr ("Creating dynamic hard disk storage unit '%ls'"),
1209 m.locationFull.raw()),
1210 FALSE /* aCancelable */);
1211 CheckComRCReturnRC (rc);
1212
1213 /* setup task object and thread to carry out the operation
1214 * asynchronously */
1215
1216 std::auto_ptr <Task> task (new Task (this, progress, Task::CreateDynamic));
1217 AssertComRCReturnRC (task->autoCaller.rc());
1218
1219 task->d.size = aLogicalSize;
1220 task->d.variant = aVariant;
1221
1222 rc = task->startThread();
1223 CheckComRCReturnRC (rc);
1224
1225 /* go to Creating state on success */
1226 m.state = MediaState_Creating;
1227
1228 /* task is now owned by taskThread() so release it */
1229 task.release();
1230
1231 /* return progress to the caller */
1232 progress.queryInterfaceTo (aProgress);
1233
1234 return S_OK;
1235}
1236
1237STDMETHODIMP HardDisk::CreateFixedStorage(ULONG64 aLogicalSize,
1238 HardDiskVariant_T aVariant,
1239 IProgress **aProgress)
1240{
1241 CheckComArgOutPointerValid (aProgress);
1242
1243 AutoCaller autoCaller (this);
1244 CheckComRCReturnRC (autoCaller.rc());
1245
1246 AutoWriteLock alock (this);
1247
1248 if (!(mm.formatObj->capabilities() &
1249 HardDiskFormatCapabilities_CreateFixed))
1250 return setError (VBOX_E_NOT_SUPPORTED,
1251 tr ("Hard disk format '%ls' does not support fixed storage "
1252 "creation"), mm.format.raw());
1253
1254 switch (m.state)
1255 {
1256 case MediaState_NotCreated:
1257 break;
1258 default:
1259 return setStateError();
1260 }
1261
1262 ComObjPtr <Progress> progress;
1263 progress.createObject();
1264 HRESULT rc = progress->init (mVirtualBox, static_cast<IHardDisk*>(this),
1265 BstrFmt (tr ("Creating fixed hard disk storage unit '%ls'"),
1266 m.locationFull.raw()),
1267 FALSE /* aCancelable */);
1268 CheckComRCReturnRC (rc);
1269
1270 /* setup task object and thread to carry out the operation
1271 * asynchronously */
1272
1273 std::auto_ptr <Task> task (new Task (this, progress, Task::CreateFixed));
1274 AssertComRCReturnRC (task->autoCaller.rc());
1275
1276 task->d.size = aLogicalSize;
1277 task->d.variant = aVariant;
1278
1279 rc = task->startThread();
1280 CheckComRCReturnRC (rc);
1281
1282 /* go to Creating state on success */
1283 m.state = MediaState_Creating;
1284
1285 /* task is now owned by taskThread() so release it */
1286 task.release();
1287
1288 /* return progress to the caller */
1289 progress.queryInterfaceTo (aProgress);
1290
1291 return S_OK;
1292}
1293
1294STDMETHODIMP HardDisk::DeleteStorage (IProgress **aProgress)
1295{
1296 CheckComArgOutPointerValid (aProgress);
1297
1298 AutoCaller autoCaller (this);
1299 CheckComRCReturnRC (autoCaller.rc());
1300
1301 ComObjPtr <Progress> progress;
1302
1303 HRESULT rc = deleteStorageNoWait (progress);
1304 if (SUCCEEDED (rc))
1305 {
1306 /* return progress to the caller */
1307 progress.queryInterfaceTo (aProgress);
1308 }
1309
1310 return rc;
1311}
1312
1313STDMETHODIMP HardDisk::CreateDiffStorage (IHardDisk *aTarget,
1314 HardDiskVariant_T aVariant,
1315 IProgress **aProgress)
1316{
1317 CheckComArgNotNull (aTarget);
1318 CheckComArgOutPointerValid (aProgress);
1319
1320 AutoCaller autoCaller (this);
1321 CheckComRCReturnRC (autoCaller.rc());
1322
1323 ComObjPtr<HardDisk> diff;
1324 HRESULT rc = mVirtualBox->cast (aTarget, diff);
1325 CheckComRCReturnRC (rc);
1326
1327 AutoWriteLock alock (this);
1328
1329 if (mm.type == HardDiskType_Writethrough)
1330 return setError (E_FAIL,
1331 tr ("Hard disk '%ls' is Writethrough"),
1332 m.locationFull.raw());
1333
1334 /* We want to be locked for reading as long as our diff child is being
1335 * created */
1336 rc = LockRead (NULL);
1337 CheckComRCReturnRC (rc);
1338
1339 ComObjPtr <Progress> progress;
1340
1341 rc = createDiffStorageNoWait (diff, aVariant, progress);
1342 if (FAILED (rc))
1343 {
1344 HRESULT rc2 = UnlockRead (NULL);
1345 AssertComRC (rc2);
1346 /* Note: on success, taskThread() will unlock this */
1347 }
1348 else
1349 {
1350 /* return progress to the caller */
1351 progress.queryInterfaceTo (aProgress);
1352 }
1353
1354 return rc;
1355}
1356
1357STDMETHODIMP HardDisk::MergeTo (IN_GUID aTargetId, IProgress **aProgress)
1358{
1359 AutoCaller autoCaller (this);
1360 CheckComRCReturnRC (autoCaller.rc());
1361
1362 ReturnComNotImplemented();
1363}
1364
1365STDMETHODIMP HardDisk::CloneTo (IHardDisk *aTarget,
1366 HardDiskVariant_T aVariant,
1367 IProgress **aProgress)
1368{
1369 CheckComArgNotNull (aTarget);
1370 CheckComArgOutPointerValid (aProgress);
1371
1372 AutoCaller autoCaller (this);
1373 CheckComRCReturnRC (autoCaller.rc());
1374
1375 ComObjPtr <HardDisk> target;
1376 HRESULT rc = mVirtualBox->cast (aTarget, target);
1377 CheckComRCReturnRC (rc);
1378
1379 AutoMultiWriteLock2 alock (this, target);
1380
1381 /* We want to be locked for reading as long as the clone hard disk is being
1382 * created*/
1383 rc = LockRead (NULL);
1384 CheckComRCReturnRC (rc);
1385
1386 ComObjPtr <Progress> progress;
1387
1388 try
1389 {
1390 if (target->m.state != MediaState_NotCreated)
1391 throw target->setStateError();
1392
1393 progress.createObject();
1394 rc = progress->init (mVirtualBox, static_cast <IHardDisk *> (this),
1395 BstrFmt (tr ("Creating clone hard disk '%ls'"),
1396 target->m.locationFull.raw()),
1397 FALSE /* aCancelable */);
1398 CheckComRCThrowRC (rc);
1399
1400 /* setup task object and thread to carry out the operation
1401 * asynchronously */
1402
1403 std::auto_ptr <Task> task (new Task (this, progress, Task::Clone));
1404 AssertComRCThrowRC (task->autoCaller.rc());
1405
1406 task->setData (target);
1407 task->d.variant = aVariant;
1408
1409 rc = task->startThread();
1410 CheckComRCThrowRC (rc);
1411
1412 /* go to Creating state before leaving the lock */
1413 target->m.state = MediaState_Creating;
1414
1415 /* task is now owned (or already deleted) by taskThread() so release it */
1416 task.release();
1417 }
1418 catch (HRESULT aRC)
1419 {
1420 rc = aRC;
1421 }
1422
1423 if (FAILED (rc))
1424 {
1425 HRESULT rc2 = UnlockRead (NULL);
1426 AssertComRC (rc2);
1427 /* Note: on success, taskThread() will unlock this */
1428 }
1429 else
1430 {
1431 /* return progress to the caller */
1432 progress.queryInterfaceTo (aProgress);
1433 }
1434
1435 return rc;
1436}
1437
1438STDMETHODIMP HardDisk::FlattenTo (IHardDisk *aTarget,
1439 HardDiskVariant_T aVariant,
1440 IProgress **aProgress)
1441{
1442 AutoCaller autoCaller (this);
1443 CheckComRCReturnRC (autoCaller.rc());
1444
1445 ReturnComNotImplemented();
1446}
1447
1448STDMETHODIMP HardDisk::Compact (IProgress **aProgress)
1449{
1450 AutoCaller autoCaller (this);
1451 CheckComRCReturnRC (autoCaller.rc());
1452
1453 ReturnComNotImplemented();
1454}
1455
1456STDMETHODIMP HardDisk::Reset (IProgress **aProgress)
1457{
1458 CheckComArgOutPointerValid (aProgress);
1459
1460 AutoCaller autoCaller (this);
1461 CheckComRCReturnRC (autoCaller.rc());
1462
1463 AutoWriteLock alock (this);
1464
1465 if (mParent.isNull())
1466 return setError (VBOX_E_NOT_SUPPORTED,
1467 tr ("Hard disk '%ls' is not differencing"),
1468 m.locationFull.raw());
1469
1470 HRESULT rc = canClose();
1471 CheckComRCReturnRC (rc);
1472
1473 rc = LockWrite (NULL);
1474 CheckComRCReturnRC (rc);
1475
1476 ComObjPtr <Progress> progress;
1477
1478 try
1479 {
1480 progress.createObject();
1481 rc = progress->init (mVirtualBox, static_cast <IHardDisk *> (this),
1482 BstrFmt (tr ("Resetting differencing hard disk '%ls'"),
1483 m.locationFull.raw()),
1484 FALSE /* aCancelable */);
1485 CheckComRCThrowRC (rc);
1486
1487 /* setup task object and thread to carry out the operation
1488 * asynchronously */
1489
1490 std::auto_ptr <Task> task (new Task (this, progress, Task::Reset));
1491 AssertComRCThrowRC (task->autoCaller.rc());
1492
1493 rc = task->startThread();
1494 CheckComRCThrowRC (rc);
1495
1496 /* task is now owned (or already deleted) by taskThread() so release it */
1497 task.release();
1498 }
1499 catch (HRESULT aRC)
1500 {
1501 rc = aRC;
1502 }
1503
1504 if (FAILED (rc))
1505 {
1506 HRESULT rc2 = UnlockWrite (NULL);
1507 AssertComRC (rc2);
1508 /* Note: on success, taskThread() will unlock this */
1509 }
1510 else
1511 {
1512 /* return progress to the caller */
1513 progress.queryInterfaceTo (aProgress);
1514 }
1515
1516 return rc;
1517}
1518
1519// public methods for internal purposes only
1520////////////////////////////////////////////////////////////////////////////////
1521
1522/**
1523 * Checks if the given change of \a aOldPath to \a aNewPath affects the location
1524 * of this hard disk or any its child and updates the paths if necessary to
1525 * reflect the new location.
1526 *
1527 * @param aOldPath Old path (full).
1528 * @param aNewPath New path (full).
1529 *
1530 * @note Locks treeLock() for reading, this object and all children for writing.
1531 */
1532void HardDisk::updatePaths (const char *aOldPath, const char *aNewPath)
1533{
1534 AssertReturnVoid (aOldPath);
1535 AssertReturnVoid (aNewPath);
1536
1537 AutoCaller autoCaller (this);
1538 AssertComRCReturnVoid (autoCaller.rc());
1539
1540 AutoWriteLock alock (this);
1541
1542 /* we access children() */
1543 AutoReadLock treeLock (this->treeLock());
1544
1545 updatePath (aOldPath, aNewPath);
1546
1547 /* update paths of all children */
1548 for (List::const_iterator it = children().begin();
1549 it != children().end();
1550 ++ it)
1551 {
1552 (*it)->updatePaths (aOldPath, aNewPath);
1553 }
1554}
1555
1556/**
1557 * Returns the base hard disk of the hard disk chain this hard disk is part of.
1558 *
1559 * The root hard disk is found by walking up the parent-child relationship axis.
1560 * If the hard disk doesn't have a parent (i.e. it's a base hard disk), it
1561 * returns itself in response to this method.
1562 *
1563 * @param aLevel Where to store the number of ancestors of this hard disk
1564 * (zero for the root), may be @c NULL.
1565 *
1566 * @note Locks treeLock() for reading.
1567 */
1568ComObjPtr <HardDisk> HardDisk::root (uint32_t *aLevel /*= NULL*/)
1569{
1570 ComObjPtr <HardDisk> root;
1571 uint32_t level;
1572
1573 AutoCaller autoCaller (this);
1574 AssertReturn (autoCaller.isOk(), root);
1575
1576 /* we access mParent */
1577 AutoReadLock treeLock (this->treeLock());
1578
1579 root = this;
1580 level = 0;
1581
1582 if (!mParent.isNull())
1583 {
1584 for (;;)
1585 {
1586 AutoCaller rootCaller (root);
1587 AssertReturn (rootCaller.isOk(), root);
1588
1589 if (root->mParent.isNull())
1590 break;
1591
1592 root = root->mParent;
1593 ++ level;
1594 }
1595 }
1596
1597 if (aLevel != NULL)
1598 *aLevel = level;
1599
1600 return root;
1601}
1602
1603/**
1604 * Returns @c true if this hard disk cannot be modified because it has
1605 * dependants (children) or is part of the snapshot. Related to the hard disk
1606 * type and posterity, not to the current media state.
1607 *
1608 * @note Locks this object and treeLock() for reading.
1609 */
1610bool HardDisk::isReadOnly()
1611{
1612 AutoCaller autoCaller (this);
1613 AssertComRCReturn (autoCaller.rc(), false);
1614
1615 AutoReadLock alock (this);
1616
1617 /* we access children */
1618 AutoReadLock treeLock (this->treeLock());
1619
1620 switch (mm.type)
1621 {
1622 case HardDiskType_Normal:
1623 {
1624 if (children().size() != 0)
1625 return true;
1626
1627 for (BackRefList::const_iterator it = m.backRefs.begin();
1628 it != m.backRefs.end(); ++ it)
1629 if (it->snapshotIds.size() != 0)
1630 return true;
1631
1632 return false;
1633 }
1634 case HardDiskType_Immutable:
1635 {
1636 return true;
1637 }
1638 case HardDiskType_Writethrough:
1639 {
1640 return false;
1641 }
1642 default:
1643 break;
1644 }
1645
1646 AssertFailedReturn (false);
1647}
1648
1649/**
1650 * Saves hard disk data by appending a new <HardDisk> child node to the given
1651 * parent node which can be either <HardDisks> or <HardDisk>.
1652 *
1653 * @param aaParentNode Parent <HardDisks> or <HardDisk> node.
1654 *
1655 * @note Locks this object, treeLock() and children for reading.
1656 */
1657HRESULT HardDisk::saveSettings (settings::Key &aParentNode)
1658{
1659 using namespace settings;
1660
1661 AssertReturn (!aParentNode.isNull(), E_FAIL);
1662
1663 AutoCaller autoCaller (this);
1664 CheckComRCReturnRC (autoCaller.rc());
1665
1666 AutoReadLock alock (this);
1667
1668 /* we access mParent */
1669 AutoReadLock treeLock (this->treeLock());
1670
1671 Key diskNode = aParentNode.appendKey ("HardDisk");
1672 /* required */
1673 diskNode.setValue <Guid> ("uuid", m.id);
1674 /* required (note: the original locaiton, not full) */
1675 diskNode.setValue <Bstr> ("location", m.location);
1676 /* required */
1677 diskNode.setValue <Bstr> ("format", mm.format);
1678 /* optional, only for diffs, default is false */
1679 if (!mParent.isNull())
1680 diskNode.setValueOr <bool> ("autoReset", !!mm.autoReset, false);
1681 /* optional */
1682 if (!m.description.isNull())
1683 {
1684 Key descNode = diskNode.createKey ("Description");
1685 descNode.setKeyValue <Bstr> (m.description);
1686 }
1687
1688 /* optional properties */
1689 for (Data::PropertyMap::const_iterator it = mm.properties.begin();
1690 it != mm.properties.end(); ++ it)
1691 {
1692 /* only save properties that have non-default values */
1693 if (!it->second.isNull())
1694 {
1695 Key propNode = diskNode.appendKey ("Property");
1696 propNode.setValue <Bstr> ("name", it->first);
1697 propNode.setValue <Bstr> ("value", it->second);
1698 }
1699 }
1700
1701 /* only for base hard disks */
1702 if (mParent.isNull())
1703 {
1704 const char *type =
1705 mm.type == HardDiskType_Normal ? "Normal" :
1706 mm.type == HardDiskType_Immutable ? "Immutable" :
1707 mm.type == HardDiskType_Writethrough ? "Writethrough" : NULL;
1708 Assert (type != NULL);
1709 diskNode.setStringValue ("type", type);
1710 }
1711
1712 /* save all children */
1713 for (List::const_iterator it = children().begin();
1714 it != children().end();
1715 ++ it)
1716 {
1717 HRESULT rc = (*it)->saveSettings (diskNode);
1718 AssertComRCReturnRC (rc);
1719 }
1720
1721 return S_OK;
1722}
1723
1724/**
1725 * Compares the location of this hard disk to the given location.
1726 *
1727 * The comparison takes the location details into account. For example, if the
1728 * location is a file in the host's filesystem, a case insensitive comparison
1729 * will be performed for case insensitive filesystems.
1730 *
1731 * @param aLocation Location to compare to (as is).
1732 * @param aResult Where to store the result of comparison: 0 if locations
1733 * are equal, 1 if this object's location is greater than
1734 * the specified location, and -1 otherwise.
1735 */
1736HRESULT HardDisk::compareLocationTo (const char *aLocation, int &aResult)
1737{
1738 AutoCaller autoCaller (this);
1739 AssertComRCReturnRC (autoCaller.rc());
1740
1741 AutoReadLock alock (this);
1742
1743 Utf8Str locationFull (m.locationFull);
1744
1745 /// @todo NEWMEDIA delegate the comparison to the backend?
1746
1747 if (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File)
1748 {
1749 Utf8Str location (aLocation);
1750
1751 /* For locations represented by files, append the default path if
1752 * only the name is given, and then get the full path. */
1753 if (!RTPathHavePath (aLocation))
1754 {
1755 AutoReadLock propsLock (mVirtualBox->systemProperties());
1756 location = Utf8StrFmt ("%ls%c%s",
1757 mVirtualBox->systemProperties()->defaultHardDiskFolder().raw(),
1758 RTPATH_DELIMITER, aLocation);
1759 }
1760
1761 int vrc = mVirtualBox->calculateFullPath (location, location);
1762 if (RT_FAILURE (vrc))
1763 return setError (E_FAIL,
1764 tr ("Invalid hard disk storage file location '%s' (%Rrc)"),
1765 location.raw(), vrc);
1766
1767 aResult = RTPathCompare (locationFull, location);
1768 }
1769 else
1770 aResult = locationFull.compare (aLocation);
1771
1772 return S_OK;
1773}
1774
1775/**
1776 * Returns a short version of the location attribute.
1777 *
1778 * Reimplements MediumBase::name() to specially treat non-FS-path locations.
1779 *
1780 * @note Must be called from under this object's read or write lock.
1781 */
1782Utf8Str HardDisk::name()
1783{
1784 /// @todo NEWMEDIA treat non-FS-paths specially! (may require to requiest
1785 /// this information from the VD backend)
1786
1787 Utf8Str location (m.locationFull);
1788
1789 Utf8Str name = RTPathFilename (location);
1790 return name;
1791}
1792
1793/**
1794 * Checks that this hard disk may be discarded and performs necessary state
1795 * changes.
1796 *
1797 * This method is to be called prior to calling the #discrad() to perform
1798 * necessary consistency checks and place involved hard disks to appropriate
1799 * states. If #discard() is not called or fails, the state modifications
1800 * performed by this method must be undone by #cancelDiscard().
1801 *
1802 * See #discard() for more info about discarding hard disks.
1803 *
1804 * @param aChain Where to store the created merge chain (may return NULL
1805 * if no real merge is necessary).
1806 *
1807 * @note Locks treeLock() for reading. Locks this object, aTarget and all
1808 * intermediate hard disks for writing.
1809 */
1810HRESULT HardDisk::prepareDiscard (MergeChain * &aChain)
1811{
1812 AutoCaller autoCaller (this);
1813 AssertComRCReturnRC (autoCaller.rc());
1814
1815 aChain = NULL;
1816
1817 AutoWriteLock alock (this);
1818
1819 /* we access mParent & children() */
1820 AutoReadLock treeLock (this->treeLock());
1821
1822 AssertReturn (mm.type == HardDiskType_Normal, E_FAIL);
1823
1824 if (children().size() == 0)
1825 {
1826 /* special treatment of the last hard disk in the chain: */
1827
1828 if (mParent.isNull())
1829 {
1830 /* lock only, to prevent any usage; discard() will unlock */
1831 return LockWrite (NULL);
1832 }
1833
1834 /* the differencing hard disk w/o children will be deleted, protect it
1835 * from attaching to other VMs (this is why Deleting) */
1836
1837 switch (m.state)
1838 {
1839 case MediaState_Created:
1840 m.state = MediaState_Deleting;
1841 break;
1842 default:
1843 return setStateError();
1844 }
1845
1846 /* aChain is intentionally NULL here */
1847
1848 return S_OK;
1849 }
1850
1851 /* not going multi-merge as it's too expensive */
1852 if (children().size() > 1)
1853 return setError (E_FAIL,
1854 tr ("Hard disk '%ls' has more than one child hard disk (%d)"),
1855 m.locationFull.raw(), children().size());
1856
1857 /* this is a read-only hard disk with children; it must be associated with
1858 * exactly one snapshot (when the snapshot is being taken, none of the
1859 * current VM's hard disks may be attached to other VMs). Note that by the
1860 * time when discard() is called, there must be no any attachments at all
1861 * (the code calling prepareDiscard() should detach). */
1862 AssertReturn (m.backRefs.size() == 1 &&
1863 !m.backRefs.front().inCurState &&
1864 m.backRefs.front().snapshotIds.size() == 1, E_FAIL);
1865
1866 ComObjPtr<HardDisk> child = children().front();
1867
1868 /* we keep this locked, so lock the affected child to make sure the lock
1869 * order is correct when calling prepareMergeTo() */
1870 AutoWriteLock childLock (child);
1871
1872 /* delegate the rest to the profi */
1873 if (mParent.isNull())
1874 {
1875 /* base hard disk, backward merge */
1876
1877 Assert (child->m.backRefs.size() == 1);
1878 if (child->m.backRefs.front().machineId != m.backRefs.front().machineId)
1879 {
1880 /* backward merge is too tricky, we'll just detach on discard, so
1881 * lock only, to prevent any usage; discard() will only unlock
1882 * (since we return NULL in aChain) */
1883 return LockWrite (NULL);
1884 }
1885
1886 return child->prepareMergeTo (this, aChain,
1887 true /* aIgnoreAttachments */);
1888 }
1889 else
1890 {
1891 /* forward merge */
1892 return prepareMergeTo (child, aChain,
1893 true /* aIgnoreAttachments */);
1894 }
1895}
1896
1897/**
1898 * Discards this hard disk.
1899 *
1900 * Discarding the hard disk is merging its contents to its differencing child
1901 * hard disk (forward merge) or contents of its child hard disk to itself
1902 * (backward merge) if this hard disk is a base hard disk. If this hard disk is
1903 * a differencing hard disk w/o children, then it will be simply deleted.
1904 * Calling this method on a base hard disk w/o children will do nothing and
1905 * silently succeed. If this hard disk has more than one child, the method will
1906 * currently return an error (since merging in this case would be too expensive
1907 * and result in data duplication).
1908 *
1909 * When the backward merge takes place (i.e. this hard disk is a target) then,
1910 * on success, this hard disk will automatically replace the differencing child
1911 * hard disk used as a source (which will then be deleted) in the attachment
1912 * this child hard disk is associated with. This will happen only if both hard
1913 * disks belong to the same machine because otherwise such a replace would be
1914 * too tricky and could be not expected by the other machine. Same relates to a
1915 * case when the child hard disk is not associated with any machine at all. When
1916 * the backward merge is not applied, the method behaves as if the base hard
1917 * disk were not attached at all -- i.e. simply detaches it from the machine but
1918 * leaves the hard disk chain intact.
1919 *
1920 * This method is basically a wrapper around #mergeTo() that selects the correct
1921 * merge direction and performs additional actions as described above and.
1922 *
1923 * Note that this method will not return until the merge operation is complete
1924 * (which may be quite time consuming depending on the size of the merged hard
1925 * disks).
1926 *
1927 * Note that #prepareDiscard() must be called before calling this method. If
1928 * this method returns a failure, the caller must call #cancelDiscard(). On
1929 * success, #cancelDiscard() must not be called (this method will perform all
1930 * necessary steps such as resetting states of all involved hard disks and
1931 * deleting @a aChain).
1932 *
1933 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
1934 * no real merge takes place).
1935 *
1936 * @note Locks the hard disks from the chain for writing. Locks the machine
1937 * object when the backward merge takes place. Locks treeLock() lock for
1938 * reading or writing.
1939 */
1940HRESULT HardDisk::discard (ComObjPtr <Progress> &aProgress, MergeChain *aChain)
1941{
1942 AssertReturn (!aProgress.isNull(), E_FAIL);
1943
1944 ComObjPtr <HardDisk> hdFrom;
1945
1946 HRESULT rc = S_OK;
1947
1948 {
1949 AutoCaller autoCaller (this);
1950 AssertComRCReturnRC (autoCaller.rc());
1951
1952 aProgress->advanceOperation (BstrFmt (
1953 tr ("Discarding hard disk '%s'"), name().raw()));
1954
1955 if (aChain == NULL)
1956 {
1957 AutoWriteLock alock (this);
1958
1959 /* we access mParent & children() */
1960 AutoReadLock treeLock (this->treeLock());
1961
1962 Assert (children().size() == 0);
1963
1964 /* special treatment of the last hard disk in the chain: */
1965
1966 if (mParent.isNull())
1967 {
1968 rc = UnlockWrite (NULL);
1969 AssertComRC (rc);
1970 return rc;
1971 }
1972
1973 /* delete the differencing hard disk w/o children */
1974
1975 Assert (m.state == MediaState_Deleting);
1976
1977 /* go back to Created since deleteStorage() expects this state */
1978 m.state = MediaState_Created;
1979
1980 hdFrom = this;
1981
1982 rc = deleteStorageAndWait (&aProgress);
1983 }
1984 else
1985 {
1986 hdFrom = aChain->source();
1987
1988 rc = hdFrom->mergeToAndWait (aChain, &aProgress);
1989 }
1990 }
1991
1992 if (SUCCEEDED (rc))
1993 {
1994 /* mergeToAndWait() cannot uninitialize the initiator because of
1995 * possible AutoCallers on the current thread, deleteStorageAndWait()
1996 * doesn't do it either; do it ourselves */
1997 hdFrom->uninit();
1998 }
1999
2000 return rc;
2001}
2002
2003/**
2004 * Undoes what #prepareDiscard() did. Must be called if #discard() is not called
2005 * or fails. Frees memory occupied by @a aChain.
2006 *
2007 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
2008 * no real merge takes place).
2009 *
2010 * @note Locks the hard disks from the chain for writing. Locks treeLock() for
2011 * reading.
2012 */
2013void HardDisk::cancelDiscard (MergeChain *aChain)
2014{
2015 AutoCaller autoCaller (this);
2016 AssertComRCReturnVoid (autoCaller.rc());
2017
2018 if (aChain == NULL)
2019 {
2020 AutoWriteLock alock (this);
2021
2022 /* we access mParent & children() */
2023 AutoReadLock treeLock (this->treeLock());
2024
2025 Assert (children().size() == 0);
2026
2027 /* special treatment of the last hard disk in the chain: */
2028
2029 if (mParent.isNull())
2030 {
2031 HRESULT rc = UnlockWrite (NULL);
2032 AssertComRC (rc);
2033 return;
2034 }
2035
2036 /* the differencing hard disk w/o children will be deleted, protect it
2037 * from attaching to other VMs (this is why Deleting) */
2038
2039 Assert (m.state == MediaState_Deleting);
2040 m.state = MediaState_Created;
2041
2042 return;
2043 }
2044
2045 /* delegate the rest to the profi */
2046 cancelMergeTo (aChain);
2047}
2048
2049/**
2050 * Returns a preferred format for differencing hard disks.
2051 */
2052Bstr HardDisk::preferredDiffFormat()
2053{
2054 Bstr format;
2055
2056 AutoCaller autoCaller (this);
2057 AssertComRCReturn (autoCaller.rc(), format);
2058
2059 /* mm.format is const, no need to lock */
2060 format = mm.format;
2061
2062 /* check that our own format supports diffs */
2063 if (!(mm.formatObj->capabilities() & HardDiskFormatCapabilities_Differencing))
2064 {
2065 /* use the default format if not */
2066 AutoReadLock propsLock (mVirtualBox->systemProperties());
2067 format = mVirtualBox->systemProperties()->defaultHardDiskFormat();
2068 }
2069
2070 return format;
2071}
2072
2073// protected methods
2074////////////////////////////////////////////////////////////////////////////////
2075
2076/**
2077 * Deletes the hard disk storage unit.
2078 *
2079 * If @a aProgress is not NULL but the object it points to is @c null then a new
2080 * progress object will be created and assigned to @a *aProgress on success,
2081 * otherwise the existing progress object is used. If Progress is NULL, then no
2082 * progress object is created/used at all.
2083 *
2084 * When @a aWait is @c false, this method will create a thread to perform the
2085 * delete operation asynchronously and will return immediately. Otherwise, it
2086 * will perform the operation on the calling thread and will not return to the
2087 * caller until the operation is completed. Note that @a aProgress cannot be
2088 * NULL when @a aWait is @c false (this method will assert in this case).
2089 *
2090 * @param aProgress Where to find/store a Progress object to track operation
2091 * completion.
2092 * @param aWait @c true if this method should block instead of creating
2093 * an asynchronous thread.
2094 *
2095 * @note Locks mVirtualBox and this object for writing. Locks treeLock() for
2096 * writing.
2097 */
2098HRESULT HardDisk::deleteStorage (ComObjPtr <Progress> *aProgress, bool aWait)
2099{
2100 AssertReturn (aProgress != NULL || aWait == true, E_FAIL);
2101
2102 /* unregisterWithVirtualBox() needs a write lock. We want to unregister
2103 * ourselves atomically after detecting that deletion is possible to make
2104 * sure that we don't do that after another thread has done
2105 * VirtualBox::findHardDisk() but before it starts using us (provided that
2106 * it holds a mVirtualBox lock too of course). */
2107
2108 AutoWriteLock vboxLock (mVirtualBox);
2109
2110 AutoWriteLock alock (this);
2111
2112 if (!(mm.formatObj->capabilities() &
2113 (HardDiskFormatCapabilities_CreateDynamic |
2114 HardDiskFormatCapabilities_CreateFixed)))
2115 return setError (VBOX_E_NOT_SUPPORTED,
2116 tr ("Hard disk format '%ls' does not support storage deletion"),
2117 mm.format.raw());
2118
2119 /* Note that we are fine with Inaccessible state too: a) for symmetry with
2120 * create calls and b) because it doesn't really harm to try, if it is
2121 * really inaccessibke, the delete operation will fail anyway. Accepting
2122 * Inaccessible state is especially important because all registered hard
2123 * disks are initially Inaccessible upon VBoxSVC startup until
2124 * COMGETTER(State) is called. */
2125
2126 switch (m.state)
2127 {
2128 case MediaState_Created:
2129 case MediaState_Inaccessible:
2130 break;
2131 default:
2132 return setStateError();
2133 }
2134
2135 if (m.backRefs.size() != 0)
2136 return setError (VBOX_E_OBJECT_IN_USE,
2137 tr ("Hard disk '%ls' is attached to %d virtual machines"),
2138 m.locationFull.raw(), m.backRefs.size());
2139
2140 HRESULT rc = canClose();
2141 CheckComRCReturnRC (rc);
2142
2143 /* go to Deleting state before leaving the lock */
2144 m.state = MediaState_Deleting;
2145
2146 /* we need to leave this object's write lock now because of
2147 * unregisterWithVirtualBox() that locks treeLock() for writing */
2148 alock.leave();
2149
2150 /* try to remove from the list of known hard disks before performing actual
2151 * deletion (we favor the consistency of the media registry in the first
2152 * place which would have been broken if unregisterWithVirtualBox() failed
2153 * after we successfully deleted the storage) */
2154
2155 rc = unregisterWithVirtualBox();
2156
2157 alock.enter();
2158
2159 /* restore the state because we may fail below; we will set it later again*/
2160 m.state = MediaState_Created;
2161
2162 CheckComRCReturnRC (rc);
2163
2164 ComObjPtr <Progress> progress;
2165
2166 if (aProgress != NULL)
2167 {
2168 /* use the existing progress object... */
2169 progress = *aProgress;
2170
2171 /* ...but create a new one if it is null */
2172 if (progress.isNull())
2173 {
2174 progress.createObject();
2175 rc = progress->init (mVirtualBox, static_cast<IHardDisk*>(this),
2176 BstrFmt (tr ("Deleting hard disk storage unit '%ls'"),
2177 m.locationFull.raw()),
2178 FALSE /* aCancelable */);
2179 CheckComRCReturnRC (rc);
2180 }
2181 }
2182
2183 std::auto_ptr <Task> task (new Task (this, progress, Task::Delete));
2184 AssertComRCReturnRC (task->autoCaller.rc());
2185
2186 if (aWait)
2187 {
2188 /* go to Deleting state before starting the task */
2189 m.state = MediaState_Deleting;
2190
2191 rc = task->runNow();
2192 }
2193 else
2194 {
2195 rc = task->startThread();
2196 CheckComRCReturnRC (rc);
2197
2198 /* go to Deleting state before leaving the lock */
2199 m.state = MediaState_Deleting;
2200 }
2201
2202 /* task is now owned (or already deleted) by taskThread() so release it */
2203 task.release();
2204
2205 if (aProgress != NULL)
2206 {
2207 /* return progress to the caller */
2208 *aProgress = progress;
2209 }
2210
2211 return rc;
2212}
2213
2214/**
2215 * Creates a new differencing storage unit using the given target hard disk's
2216 * format and the location. Note that @c aTarget must be NotCreated.
2217 *
2218 * As opposed to the CreateDiffStorage() method, this method doesn't try to lock
2219 * this hard disk for reading assuming that the caller has already done so. This
2220 * is used when taking an online snaopshot (where all original hard disks are
2221 * locked for writing and must remain such). Note however that if @a aWait is
2222 * @c false and this method returns a success then the thread started by
2223 * this method will unlock the hard disk (unless it is in
2224 * MediaState_LockedWrite state) so make sure the hard disk is either in
2225 * MediaState_LockedWrite or call #LockRead() before calling this method! If @a
2226 * aWait is @c true then this method neither locks nor unlocks the hard disk, so
2227 * make sure you do it yourself as needed.
2228 *
2229 * If @a aProgress is not NULL but the object it points to is @c null then a new
2230 * progress object will be created and assigned to @a *aProgress on success,
2231 * otherwise the existing progress object is used. If @a aProgress is NULL, then no
2232 * progress object is created/used at all.
2233 *
2234 * When @a aWait is @c false, this method will create a thread to perform the
2235 * create operation asynchronously and will return immediately. Otherwise, it
2236 * will perform the operation on the calling thread and will not return to the
2237 * caller until the operation is completed. Note that @a aProgress cannot be
2238 * NULL when @a aWait is @c false (this method will assert in this case).
2239 *
2240 * @param aTarget Target hard disk.
2241 * @param aProgress Where to find/store a Progress object to track operation
2242 * completion.
2243 * @param aWait @c true if this method should block instead of creating
2244 * an asynchronous thread.
2245 *
2246 * @note Locks this object and @a aTarget for writing.
2247 */
2248HRESULT HardDisk::createDiffStorage(ComObjPtr<HardDisk> &aTarget,
2249 HardDiskVariant_T aVariant,
2250 ComObjPtr<Progress> *aProgress,
2251 bool aWait)
2252{
2253 AssertReturn (!aTarget.isNull(), E_FAIL);
2254 AssertReturn (aProgress != NULL || aWait == true, E_FAIL);
2255
2256 AutoCaller autoCaller (this);
2257 CheckComRCReturnRC (autoCaller.rc());
2258
2259 AutoCaller targetCaller (aTarget);
2260 CheckComRCReturnRC (targetCaller.rc());
2261
2262 AutoMultiWriteLock2 alock (this, aTarget);
2263
2264 AssertReturn (mm.type != HardDiskType_Writethrough, E_FAIL);
2265
2266 /* Note: MediaState_LockedWrite is ok when taking an online snapshot */
2267 AssertReturn (m.state == MediaState_LockedRead ||
2268 m.state == MediaState_LockedWrite, E_FAIL);
2269
2270 if (aTarget->m.state != MediaState_NotCreated)
2271 return aTarget->setStateError();
2272
2273 HRESULT rc = S_OK;
2274
2275 /* check that the hard disk is not attached to any VM in the current state*/
2276 for (BackRefList::const_iterator it = m.backRefs.begin();
2277 it != m.backRefs.end(); ++ it)
2278 {
2279 if (it->inCurState)
2280 {
2281 /* Note: when a VM snapshot is being taken, all normal hard disks
2282 * attached to the VM in the current state will be, as an exception,
2283 * also associated with the snapshot which is about to create (see
2284 * SnapshotMachine::init()) before deassociating them from the
2285 * current state (which takes place only on success in
2286 * Machine::fixupHardDisks()), so that the size of snapshotIds
2287 * will be 1 in this case. The given condition is used to filter out
2288 * this legal situatinon and do not report an error. */
2289
2290 if (it->snapshotIds.size() == 0)
2291 {
2292 return setError (VBOX_E_INVALID_OBJECT_STATE,
2293 tr ("Hard disk '%ls' is attached to a virtual machine "
2294 "with UUID {%RTuuid}. No differencing hard disks "
2295 "based on it may be created until it is detached"),
2296 m.locationFull.raw(), it->machineId.raw());
2297 }
2298
2299 Assert (it->snapshotIds.size() == 1);
2300 }
2301 }
2302
2303 ComObjPtr <Progress> progress;
2304
2305 if (aProgress != NULL)
2306 {
2307 /* use the existing progress object... */
2308 progress = *aProgress;
2309
2310 /* ...but create a new one if it is null */
2311 if (progress.isNull())
2312 {
2313 progress.createObject();
2314 rc = progress->init (mVirtualBox, static_cast<IHardDisk*> (this),
2315 BstrFmt (tr ("Creating differencing hard disk storage unit '%ls'"),
2316 aTarget->m.locationFull.raw()),
2317 FALSE /* aCancelable */);
2318 CheckComRCReturnRC (rc);
2319 }
2320 }
2321
2322 /* setup task object and thread to carry out the operation
2323 * asynchronously */
2324
2325 std::auto_ptr <Task> task (new Task (this, progress, Task::CreateDiff));
2326 AssertComRCReturnRC (task->autoCaller.rc());
2327
2328 task->setData (aTarget);
2329 task->d.variant = aVariant;
2330
2331 /* register a task (it will deregister itself when done) */
2332 ++ mm.numCreateDiffTasks;
2333 Assert (mm.numCreateDiffTasks != 0); /* overflow? */
2334
2335 if (aWait)
2336 {
2337 /* go to Creating state before starting the task */
2338 aTarget->m.state = MediaState_Creating;
2339
2340 rc = task->runNow();
2341 }
2342 else
2343 {
2344 rc = task->startThread();
2345 CheckComRCReturnRC (rc);
2346
2347 /* go to Creating state before leaving the lock */
2348 aTarget->m.state = MediaState_Creating;
2349 }
2350
2351 /* task is now owned (or already deleted) by taskThread() so release it */
2352 task.release();
2353
2354 if (aProgress != NULL)
2355 {
2356 /* return progress to the caller */
2357 *aProgress = progress;
2358 }
2359
2360 return rc;
2361}
2362
2363/**
2364 * Prepares this (source) hard disk, target hard disk and all intermediate hard
2365 * disks for the merge operation.
2366 *
2367 * This method is to be called prior to calling the #mergeTo() to perform
2368 * necessary consistency checks and place involved hard disks to appropriate
2369 * states. If #mergeTo() is not called or fails, the state modifications
2370 * performed by this method must be undone by #cancelMergeTo().
2371 *
2372 * Note that when @a aIgnoreAttachments is @c true then it's the caller's
2373 * responsibility to detach the source and all intermediate hard disks before
2374 * calling #mergeTo() (which will fail otherwise).
2375 *
2376 * See #mergeTo() for more information about merging.
2377 *
2378 * @param aTarget Target hard disk.
2379 * @param aChain Where to store the created merge chain.
2380 * @param aIgnoreAttachments Don't check if the source or any intermediate
2381 * hard disk is attached to any VM.
2382 *
2383 * @note Locks treeLock() for reading. Locks this object, aTarget and all
2384 * intermediate hard disks for writing.
2385 */
2386HRESULT HardDisk::prepareMergeTo(HardDisk *aTarget,
2387 MergeChain * &aChain,
2388 bool aIgnoreAttachments /*= false*/)
2389{
2390 AssertReturn (aTarget != NULL, E_FAIL);
2391
2392 AutoCaller autoCaller (this);
2393 AssertComRCReturnRC (autoCaller.rc());
2394
2395 AutoCaller targetCaller (aTarget);
2396 AssertComRCReturnRC (targetCaller.rc());
2397
2398 aChain = NULL;
2399
2400 /* we walk the tree */
2401 AutoReadLock treeLock (this->treeLock());
2402
2403 HRESULT rc = S_OK;
2404
2405 /* detect the merge direction */
2406 bool forward;
2407 {
2408 HardDisk *parent = mParent;
2409 while (parent != NULL && parent != aTarget)
2410 parent = parent->mParent;
2411 if (parent == aTarget)
2412 forward = false;
2413 else
2414 {
2415 parent = aTarget->mParent;
2416 while (parent != NULL && parent != this)
2417 parent = parent->mParent;
2418 if (parent == this)
2419 forward = true;
2420 else
2421 {
2422 Bstr tgtLoc;
2423 {
2424 AutoReadLock alock (this);
2425 tgtLoc = aTarget->locationFull();
2426 }
2427
2428 AutoReadLock alock (this);
2429 return setError (E_FAIL,
2430 tr ("Hard disks '%ls' and '%ls' are unrelated"),
2431 m.locationFull.raw(), tgtLoc.raw());
2432 }
2433 }
2434 }
2435
2436 /* build the chain (will do necessary checks and state changes) */
2437 std::auto_ptr <MergeChain> chain (new MergeChain (forward,
2438 aIgnoreAttachments));
2439 {
2440 HardDisk *last = forward ? aTarget : this;
2441 HardDisk *first = forward ? this : aTarget;
2442
2443 for (;;)
2444 {
2445 if (last == aTarget)
2446 rc = chain->addTarget (last);
2447 else if (last == this)
2448 rc = chain->addSource (last);
2449 else
2450 rc = chain->addIntermediate (last);
2451 CheckComRCReturnRC (rc);
2452
2453 if (last == first)
2454 break;
2455
2456 last = last->mParent;
2457 }
2458 }
2459
2460 aChain = chain.release();
2461
2462 return S_OK;
2463}
2464
2465/**
2466 * Merges this hard disk to the specified hard disk which must be either its
2467 * direct ancestor or descendant.
2468 *
2469 * Given this hard disk is SOURCE and the specified hard disk is TARGET, we will
2470 * get two varians of the merge operation:
2471 *
2472 * forward merge
2473 * ------------------------->
2474 * [Extra] <- SOURCE <- Intermediate <- TARGET
2475 * Any Del Del LockWr
2476 *
2477 *
2478 * backward merge
2479 * <-------------------------
2480 * TARGET <- Intermediate <- SOURCE <- [Extra]
2481 * LockWr Del Del LockWr
2482 *
2483 * Each scheme shows the involved hard disks on the hard disk chain where
2484 * SOURCE and TARGET belong. Under each hard disk there is a state value which
2485 * the hard disk must have at a time of the mergeTo() call.
2486 *
2487 * The hard disks in the square braces may be absent (e.g. when the forward
2488 * operation takes place and SOURCE is the base hard disk, or when the backward
2489 * merge operation takes place and TARGET is the last child in the chain) but if
2490 * they present they are involved too as shown.
2491 *
2492 * Nor the source hard disk neither intermediate hard disks may be attached to
2493 * any VM directly or in the snapshot, otherwise this method will assert.
2494 *
2495 * The #prepareMergeTo() method must be called prior to this method to place all
2496 * involved to necessary states and perform other consistency checks.
2497 *
2498 * If @a aWait is @c true then this method will perform the operation on the
2499 * calling thread and will not return to the caller until the operation is
2500 * completed. When this method succeeds, all intermediate hard disk objects in
2501 * the chain will be uninitialized, the state of the target hard disk (and all
2502 * involved extra hard disks) will be restored and @a aChain will be deleted.
2503 * Note that this (source) hard disk is not uninitialized because of possible
2504 * AutoCaller instances held by the caller of this method on the current thread.
2505 * It's therefore the responsibility of the caller to call HardDisk::uninit()
2506 * after releasing all callers in this case!
2507 *
2508 * If @a aWait is @c false then this method will crea,te a thread to perform the
2509 * create operation asynchronously and will return immediately. If the operation
2510 * succeeds, the thread will uninitialize the source hard disk object and all
2511 * intermediate hard disk objects in the chain, reset the state of the target
2512 * hard disk (and all involved extra hard disks) and delete @a aChain. If the
2513 * operation fails, the thread will only reset the states of all involved hard
2514 * disks and delete @a aChain.
2515 *
2516 * When this method fails (regardless of the @a aWait mode), it is a caller's
2517 * responsiblity to undo state changes and delete @a aChain using
2518 * #cancelMergeTo().
2519 *
2520 * If @a aProgress is not NULL but the object it points to is @c null then a new
2521 * progress object will be created and assigned to @a *aProgress on success,
2522 * otherwise the existing progress object is used. If Progress is NULL, then no
2523 * progress object is created/used at all. Note that @a aProgress cannot be
2524 * NULL when @a aWait is @c false (this method will assert in this case).
2525 *
2526 * @param aChain Merge chain created by #prepareMergeTo().
2527 * @param aProgress Where to find/store a Progress object to track operation
2528 * completion.
2529 * @param aWait @c true if this method should block instead of creating
2530 * an asynchronous thread.
2531 *
2532 * @note Locks the branch lock for writing. Locks the hard disks from the chain
2533 * for writing.
2534 */
2535HRESULT HardDisk::mergeTo(MergeChain *aChain,
2536 ComObjPtr <Progress> *aProgress,
2537 bool aWait)
2538{
2539 AssertReturn (aChain != NULL, E_FAIL);
2540 AssertReturn (aProgress != NULL || aWait == true, E_FAIL);
2541
2542 AutoCaller autoCaller (this);
2543 CheckComRCReturnRC (autoCaller.rc());
2544
2545 HRESULT rc = S_OK;
2546
2547 ComObjPtr <Progress> progress;
2548
2549 if (aProgress != NULL)
2550 {
2551 /* use the existing progress object... */
2552 progress = *aProgress;
2553
2554 /* ...but create a new one if it is null */
2555 if (progress.isNull())
2556 {
2557 AutoReadLock alock (this);
2558
2559 progress.createObject();
2560 rc = progress->init (mVirtualBox, static_cast<IHardDisk*>(this),
2561 BstrFmt (tr ("Merging hard disk '%s' to '%s'"),
2562 name().raw(), aChain->target()->name().raw()),
2563 FALSE /* aCancelable */);
2564 CheckComRCReturnRC (rc);
2565 }
2566 }
2567
2568 /* setup task object and thread to carry out the operation
2569 * asynchronously */
2570
2571 std::auto_ptr <Task> task (new Task (this, progress, Task::Merge));
2572 AssertComRCReturnRC (task->autoCaller.rc());
2573
2574 task->setData (aChain);
2575
2576 /* Note: task owns aChain (will delete it when not needed) in all cases
2577 * except when @a aWait is @c true and runNow() fails -- in this case
2578 * aChain will be left away because cancelMergeTo() will be applied by the
2579 * caller on it as it is required in the documentation above */
2580
2581 if (aWait)
2582 {
2583 rc = task->runNow();
2584 }
2585 else
2586 {
2587 rc = task->startThread();
2588 CheckComRCReturnRC (rc);
2589 }
2590
2591 /* task is now owned (or already deleted) by taskThread() so release it */
2592 task.release();
2593
2594 if (aProgress != NULL)
2595 {
2596 /* return progress to the caller */
2597 *aProgress = progress;
2598 }
2599
2600 return rc;
2601}
2602
2603/**
2604 * Undoes what #prepareMergeTo() did. Must be called if #mergeTo() is not called
2605 * or fails. Frees memory occupied by @a aChain.
2606 *
2607 * @param aChain Merge chain created by #prepareMergeTo().
2608 *
2609 * @note Locks the hard disks from the chain for writing.
2610 */
2611void HardDisk::cancelMergeTo (MergeChain *aChain)
2612{
2613 AutoCaller autoCaller (this);
2614 AssertComRCReturnVoid (autoCaller.rc());
2615
2616 AssertReturnVoid (aChain != NULL);
2617
2618 /* the destructor will do the thing */
2619 delete aChain;
2620}
2621
2622// private methods
2623////////////////////////////////////////////////////////////////////////////////
2624
2625/**
2626 * Sets the value of m.location and calculates the value of m.locationFull.
2627 *
2628 * Reimplements MediumBase::setLocation() to specially treat non-FS-path
2629 * locations and to prepend the default hard disk folder if the given location
2630 * string does not contain any path information at all.
2631 *
2632 * Also, if the specified location is a file path that ends with '/' then the
2633 * file name part will be generated by this method automatically in the format
2634 * '{<uuid>}.<ext>' where <uuid> is a fresh UUID that this method will generate
2635 * and assign to this medium, and <ext> is the default extension for this
2636 * medium's storage format. Note that this procedure requires the media state to
2637 * be NotCreated and will return a faiulre otherwise.
2638 *
2639 * @param aLocation Location of the storage unit. If the locaiton is a FS-path,
2640 * then it can be relative to the VirtualBox home directory.
2641 *
2642 * @note Must be called from under this object's write lock.
2643 */
2644HRESULT HardDisk::setLocation (CBSTR aLocation)
2645{
2646 /// @todo so far, we assert but later it makes sense to support null
2647 /// locations for hard disks that are not yet created fail to create a
2648 /// storage unit instead
2649 CheckComArgStrNotEmptyOrNull (aLocation);
2650
2651 AutoCaller autoCaller (this);
2652 AssertComRCReturnRC (autoCaller.rc());
2653
2654 /* formatObj may be null only when initializing from an existing path and
2655 * no format is known yet */
2656 AssertReturn ((!mm.format.isNull() && !mm.formatObj.isNull()) ||
2657 (autoCaller.state() == InInit &&
2658 m.state != MediaState_NotCreated && m.id.isEmpty() &&
2659 mm.format.isNull() && mm.formatObj.isNull()),
2660 E_FAIL);
2661
2662 /* are we dealing with a new hard disk constructed using the existing
2663 * location? */
2664 bool isImport = mm.format.isNull();
2665
2666 if (isImport ||
2667 (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File))
2668 {
2669 Guid id;
2670
2671 Utf8Str location (aLocation);
2672
2673 if (m.state == MediaState_NotCreated)
2674 {
2675 /* must be a file (formatObj must be already known) */
2676 Assert (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File);
2677
2678 if (RTPathFilename (location) == NULL)
2679 {
2680 /* no file name is given (either an empty string or ends with a
2681 * slash), generate a new UUID + file name if the state allows
2682 * this */
2683
2684 ComAssertMsgRet (!mm.formatObj->fileExtensions().empty(),
2685 ("Must be at least one extension if it is "
2686 "HardDiskFormatCapabilities_File\n"),
2687 E_FAIL);
2688
2689 Bstr ext = mm.formatObj->fileExtensions().front();
2690 ComAssertMsgRet (!ext.isEmpty(),
2691 ("Default extension must not be empty\n"),
2692 E_FAIL);
2693
2694 id.create();
2695
2696 location = Utf8StrFmt ("%s{%RTuuid}.%ls",
2697 location.raw(), id.raw(), ext.raw());
2698 }
2699 }
2700
2701 /* append the default folder if no path is given */
2702 if (!RTPathHavePath (location))
2703 {
2704 AutoReadLock propsLock (mVirtualBox->systemProperties());
2705 location = Utf8StrFmt ("%ls%c%s",
2706 mVirtualBox->systemProperties()->defaultHardDiskFolder().raw(),
2707 RTPATH_DELIMITER,
2708 location.raw());
2709 }
2710
2711 /* get the full file name */
2712 Utf8Str locationFull;
2713 int vrc = mVirtualBox->calculateFullPath (location, locationFull);
2714 if (RT_FAILURE (vrc))
2715 return setError (VBOX_E_FILE_ERROR,
2716 tr ("Invalid hard disk storage file location '%s' (%Rrc)"),
2717 location.raw(), vrc);
2718
2719 /* detect the backend from the storage unit if importing */
2720 if (isImport)
2721 {
2722 char *backendName = NULL;
2723
2724 /* is it a file? */
2725 {
2726 RTFILE file;
2727 vrc = RTFileOpen (&file, locationFull, RTFILE_O_READ);
2728 if (RT_SUCCESS (vrc))
2729 RTFileClose (file);
2730 }
2731 if (RT_SUCCESS (vrc))
2732 {
2733 vrc = VDGetFormat (locationFull, &backendName);
2734 }
2735 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
2736 {
2737 /* assume it's not a file, restore the original location */
2738 location = locationFull = aLocation;
2739 vrc = VDGetFormat (locationFull, &backendName);
2740 }
2741
2742 if (RT_FAILURE (vrc))
2743 return setError (VBOX_E_IPRT_ERROR,
2744 tr ("Could not get the storage format of the hard disk "
2745 "'%s' (%Rrc)"), locationFull.raw(), vrc);
2746
2747 ComAssertRet (backendName != NULL && *backendName != '\0', E_FAIL);
2748
2749 HRESULT rc = setFormat (Bstr (backendName));
2750 RTStrFree (backendName);
2751
2752 /* setFormat() must not fail since we've just used the backend so
2753 * the format object must be there */
2754 AssertComRCReturnRC (rc);
2755 }
2756
2757 /* is it still a file? */
2758 if (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File)
2759 {
2760 m.location = location;
2761 m.locationFull = locationFull;
2762
2763 if (m.state == MediaState_NotCreated)
2764 {
2765 /* assign a new UUID (this UUID will be used when calling
2766 * VDCreateBase/VDCreateDiff as a wanted UUID). Note that we
2767 * also do that if we didn't generate it to make sure it is
2768 * either generated by us or reset to null */
2769 unconst (m.id) = id;
2770 }
2771 }
2772 else
2773 {
2774 m.location = locationFull;
2775 m.locationFull = locationFull;
2776 }
2777 }
2778 else
2779 {
2780 m.location = aLocation;
2781 m.locationFull = aLocation;
2782 }
2783
2784 return S_OK;
2785}
2786
2787/**
2788 * Checks that the format ID is valid and sets it on success.
2789 *
2790 * Note that this method will caller-reference the format object on success!
2791 * This reference must be released somewhere to let the HardDiskFormat object be
2792 * uninitialized.
2793 *
2794 * @note Must be called from under this object's write lock.
2795 */
2796HRESULT HardDisk::setFormat (CBSTR aFormat)
2797{
2798 /* get the format object first */
2799 {
2800 AutoReadLock propsLock (mVirtualBox->systemProperties());
2801
2802 unconst (mm.formatObj)
2803 = mVirtualBox->systemProperties()->hardDiskFormat (aFormat);
2804 if (mm.formatObj.isNull())
2805 return setError (E_INVALIDARG,
2806 tr ("Invalid hard disk storage format '%ls'"), aFormat);
2807
2808 /* reference the format permanently to prevent its unexpected
2809 * uninitialization */
2810 HRESULT rc = mm.formatObj->addCaller();
2811 AssertComRCReturnRC (rc);
2812
2813 /* get properties (preinsert them as keys in the map). Note that the
2814 * map doesn't grow over the object life time since the set of
2815 * properties is meant to be constant. */
2816
2817 Assert (mm.properties.empty());
2818
2819 for (HardDiskFormat::PropertyList::const_iterator it =
2820 mm.formatObj->properties().begin();
2821 it != mm.formatObj->properties().end();
2822 ++ it)
2823 {
2824 mm.properties.insert (std::make_pair (it->name, Bstr::Null));
2825 }
2826 }
2827
2828 unconst (mm.format) = aFormat;
2829
2830 return S_OK;
2831}
2832
2833/**
2834 * Queries information from the image file.
2835 *
2836 * As a result of this call, the accessibility state and data members such as
2837 * size and description will be updated with the current information.
2838 *
2839 * Reimplements MediumBase::queryInfo() to query hard disk information using the
2840 * VD backend interface.
2841 *
2842 * @note This method may block during a system I/O call that checks storage
2843 * accessibility.
2844 *
2845 * @note Locks treeLock() for reading and writing (for new diff media checked
2846 * for the first time). Locks mParent for reading. Locks this object for
2847 * writing.
2848 */
2849HRESULT HardDisk::queryInfo()
2850{
2851 AutoWriteLock alock (this);
2852
2853 AssertReturn (m.state == MediaState_Created ||
2854 m.state == MediaState_Inaccessible ||
2855 m.state == MediaState_LockedRead ||
2856 m.state == MediaState_LockedWrite,
2857 E_FAIL);
2858
2859 HRESULT rc = S_OK;
2860
2861 int vrc = VINF_SUCCESS;
2862
2863 /* check if a blocking queryInfo() call is in progress on some other thread,
2864 * and wait for it to finish if so instead of querying data ourselves */
2865 if (m.queryInfoSem != NIL_RTSEMEVENTMULTI)
2866 {
2867 Assert (m.state == MediaState_LockedRead);
2868
2869 ++ m.queryInfoCallers;
2870 alock.leave();
2871
2872 vrc = RTSemEventMultiWait (m.queryInfoSem, RT_INDEFINITE_WAIT);
2873
2874 alock.enter();
2875 -- m.queryInfoCallers;
2876
2877 if (m.queryInfoCallers == 0)
2878 {
2879 /* last waiting caller deletes the semaphore */
2880 RTSemEventMultiDestroy (m.queryInfoSem);
2881 m.queryInfoSem = NIL_RTSEMEVENTMULTI;
2882 }
2883
2884 AssertRC (vrc);
2885
2886 return S_OK;
2887 }
2888
2889 /* lazily create a semaphore for possible callers */
2890 vrc = RTSemEventMultiCreate (&m.queryInfoSem);
2891 ComAssertRCRet (vrc, E_FAIL);
2892
2893 bool tempStateSet = false;
2894 if (m.state != MediaState_LockedRead &&
2895 m.state != MediaState_LockedWrite)
2896 {
2897 /* Cause other methods to prevent any modifications before leaving the
2898 * lock. Note that clients will never see this temporary state change
2899 * since any COMGETTER(State) is (or will be) blocked until we finish
2900 * and restore the actual state. */
2901 m.state = MediaState_LockedRead;
2902 tempStateSet = true;
2903 }
2904
2905 /* leave the lock before a blocking operation */
2906 alock.leave();
2907
2908 bool success = false;
2909 Utf8Str lastAccessError;
2910
2911 try
2912 {
2913 Utf8Str location (m.locationFull);
2914
2915 /* are we dealing with a new hard disk constructed using the existing
2916 * location? */
2917 bool isImport = m.id.isEmpty();
2918
2919 PVBOXHDD hdd;
2920 vrc = VDCreate (mm.vdDiskIfaces, &hdd);
2921 ComAssertRCThrow (vrc, E_FAIL);
2922
2923 try
2924 {
2925 unsigned flags = VD_OPEN_FLAGS_INFO;
2926
2927 /* Note that we don't use VD_OPEN_FLAGS_READONLY when opening new
2928 * hard disks because that would prevent necessary modifications
2929 * when opening hard disks of some third-party formats for the first
2930 * time in VirtualBox (such as VMDK for which VDOpen() needs to
2931 * generate an UUID if it is missing) */
2932 if (!isImport)
2933 flags |= VD_OPEN_FLAGS_READONLY;
2934
2935 vrc = VDOpen (hdd, Utf8Str (mm.format), location, flags,
2936 mm.vdDiskIfaces);
2937 if (RT_FAILURE (vrc))
2938 {
2939 lastAccessError = Utf8StrFmt (
2940 tr ("Could not open the hard disk '%ls'%s"),
2941 m.locationFull.raw(), vdError (vrc).raw());
2942 throw S_OK;
2943 }
2944
2945 if (mm.formatObj->capabilities() & HardDiskFormatCapabilities_Uuid)
2946 {
2947 /* check the UUID */
2948 RTUUID uuid;
2949 vrc = VDGetUuid (hdd, 0, &uuid);
2950 ComAssertRCThrow (vrc, E_FAIL);
2951
2952 if (isImport)
2953 {
2954 unconst (m.id) = uuid;
2955 }
2956 else
2957 {
2958 Assert (!m.id.isEmpty());
2959
2960 if (m.id != uuid)
2961 {
2962 lastAccessError = Utf8StrFmt (
2963 tr ("UUID {%RTuuid} of the hard disk '%ls' does "
2964 "not match the value {%RTuuid} stored in the "
2965 "media registry ('%ls')"),
2966 &uuid, m.locationFull.raw(), m.id.raw(),
2967 mVirtualBox->settingsFileName().raw());
2968 throw S_OK;
2969 }
2970 }
2971 }
2972 else
2973 {
2974 /* the backend does not support storing UUIDs within the
2975 * underlying storage so use what we store in XML */
2976
2977 /* generate an UUID for an imported UUID-less hard disk */
2978 if (isImport)
2979 unconst (m.id).create();
2980 }
2981
2982 /* check the type */
2983 VDIMAGETYPE type;
2984 vrc = VDGetImageType (hdd, 0, &type);
2985 ComAssertRCThrow (vrc, E_FAIL);
2986
2987 if (type == VD_IMAGE_TYPE_DIFF)
2988 {
2989 RTUUID parentId;
2990 vrc = VDGetParentUuid (hdd, 0, &parentId);
2991 ComAssertRCThrow (vrc, E_FAIL);
2992
2993 if (isImport)
2994 {
2995 /* the parent must be known to us. Note that we freely
2996 * call locking methods of mVirtualBox and parent from the
2997 * write lock (breaking the {parent,child} lock order)
2998 * because there may be no concurrent access to the just
2999 * opened hard disk on ther threads yet (and init() will
3000 * fail if this method reporst MediaState_Inaccessible) */
3001
3002 Guid id = parentId;
3003 ComObjPtr<HardDisk> parent;
3004 rc = mVirtualBox->findHardDisk(&id, NULL,
3005 false /* aSetError */,
3006 &parent);
3007 if (FAILED (rc))
3008 {
3009 lastAccessError = Utf8StrFmt (
3010 tr ("Parent hard disk with UUID {%RTuuid} of the "
3011 "hard disk '%ls' is not found in the media "
3012 "registry ('%ls')"),
3013 &parentId, m.locationFull.raw(),
3014 mVirtualBox->settingsFileName().raw());
3015 throw S_OK;
3016 }
3017
3018 /* deassociate from VirtualBox, associate with parent */
3019
3020 mVirtualBox->removeDependentChild (this);
3021
3022 /* we set mParent & children() */
3023 AutoWriteLock treeLock (this->treeLock());
3024
3025 Assert (mParent.isNull());
3026 mParent = parent;
3027 mParent->addDependentChild (this);
3028 }
3029 else
3030 {
3031 /* we access mParent */
3032 AutoReadLock treeLock (this->treeLock());
3033
3034 /* check that parent UUIDs match. Note that there's no need
3035 * for the parent's AutoCaller (our lifetime is bound to
3036 * it) */
3037
3038 if (mParent.isNull())
3039 {
3040 lastAccessError = Utf8StrFmt (
3041 tr ("Hard disk '%ls' is differencing but it is not "
3042 "associated with any parent hard disk in the "
3043 "media registry ('%ls')"),
3044 m.locationFull.raw(),
3045 mVirtualBox->settingsFileName().raw());
3046 throw S_OK;
3047 }
3048
3049 AutoReadLock parentLock (mParent);
3050 if (mParent->state() != MediaState_Inaccessible &&
3051 mParent->id() != parentId)
3052 {
3053 lastAccessError = Utf8StrFmt (
3054 tr ("Parent UUID {%RTuuid} of the hard disk '%ls' "
3055 "does not match UUID {%RTuuid} of its parent "
3056 "hard disk stored in the media registry ('%ls')"),
3057 &parentId, m.locationFull.raw(),
3058 mParent->id().raw(),
3059 mVirtualBox->settingsFileName().raw());
3060 throw S_OK;
3061 }
3062
3063 /// @todo NEWMEDIA what to do if the parent is not
3064 /// accessible while the diff is? Probably, nothing. The
3065 /// real code will detect the mismatch anyway.
3066 }
3067 }
3068
3069 m.size = VDGetFileSize (hdd, 0);
3070 mm.logicalSize = VDGetSize (hdd, 0) / _1M;
3071
3072 success = true;
3073 }
3074 catch (HRESULT aRC)
3075 {
3076 rc = aRC;
3077 }
3078
3079 VDDestroy (hdd);
3080
3081 }
3082 catch (HRESULT aRC)
3083 {
3084 rc = aRC;
3085 }
3086
3087 alock.enter();
3088
3089 if (success)
3090 m.lastAccessError.setNull();
3091 else
3092 {
3093 m.lastAccessError = lastAccessError;
3094 LogWarningFunc (("'%ls' is not accessible (error='%ls', "
3095 "rc=%Rhrc, vrc=%Rrc)\n",
3096 m.locationFull.raw(), m.lastAccessError.raw(),
3097 rc, vrc));
3098 }
3099
3100 /* inform other callers if there are any */
3101 if (m.queryInfoCallers > 0)
3102 {
3103 RTSemEventMultiSignal (m.queryInfoSem);
3104 }
3105 else
3106 {
3107 /* delete the semaphore ourselves */
3108 RTSemEventMultiDestroy (m.queryInfoSem);
3109 m.queryInfoSem = NIL_RTSEMEVENTMULTI;
3110 }
3111
3112 if (tempStateSet)
3113 {
3114 /* Set the proper state according to the result of the check */
3115 if (success)
3116 m.state = MediaState_Created;
3117 else
3118 m.state = MediaState_Inaccessible;
3119 }
3120 else
3121 {
3122 /* we're locked, use a special field to store the result */
3123 m.accessibleInLock = success;
3124 }
3125
3126 return rc;
3127}
3128
3129/**
3130 * @note Called from this object's AutoMayUninitSpan and from under mVirtualBox
3131 * write lock.
3132 *
3133 * @note Also reused by HardDisk::Reset().
3134 *
3135 * @note Locks treeLock() for reading.
3136 */
3137HRESULT HardDisk::canClose()
3138{
3139 /* we access children */
3140 AutoReadLock treeLock (this->treeLock());
3141
3142 if (children().size() != 0)
3143 return setError (E_FAIL,
3144 tr ("Hard disk '%ls' has %d child hard disks"),
3145 children().size());
3146
3147 return S_OK;
3148}
3149
3150/**
3151 * @note Called from within this object's AutoWriteLock.
3152 */
3153HRESULT HardDisk::canAttach(const Guid &aMachineId,
3154 const Guid &aSnapshotId)
3155{
3156 if (mm.numCreateDiffTasks > 0)
3157 return setError (E_FAIL,
3158 tr ("One or more differencing child hard disks are "
3159 "being created for the hard disk '%ls' (%u)"),
3160 m.locationFull.raw(), mm.numCreateDiffTasks);
3161
3162 return S_OK;
3163}
3164
3165/**
3166 * @note Called from within this object's AutoMayUninitSpan (or AutoCaller) and
3167 * from under mVirtualBox write lock.
3168 *
3169 * @note Locks treeLock() for writing.
3170 */
3171HRESULT HardDisk::unregisterWithVirtualBox()
3172{
3173 /* Note that we need to de-associate ourselves from the parent to let
3174 * unregisterHardDisk() properly save the registry */
3175
3176 /* we modify mParent and access children */
3177 AutoWriteLock treeLock (this->treeLock());
3178
3179 const ComObjPtr<HardDisk, ComWeakRef> parent = mParent;
3180
3181 AssertReturn (children().size() == 0, E_FAIL);
3182
3183 if (!mParent.isNull())
3184 {
3185 /* deassociate from the parent, associate with VirtualBox */
3186 mVirtualBox->addDependentChild (this);
3187 mParent->removeDependentChild (this);
3188 mParent.setNull();
3189 }
3190
3191 HRESULT rc = mVirtualBox->unregisterHardDisk(this);
3192
3193 if (FAILED (rc))
3194 {
3195 if (!parent.isNull())
3196 {
3197 /* re-associate with the parent as we are still relatives in the
3198 * registry */
3199 mParent = parent;
3200 mParent->addDependentChild (this);
3201 mVirtualBox->removeDependentChild (this);
3202 }
3203 }
3204
3205 return rc;
3206}
3207
3208/**
3209 * Returns the last error message collected by the vdErrorCall callback and
3210 * resets it.
3211 *
3212 * The error message is returned prepended with a dot and a space, like this:
3213 * <code>
3214 * ". <error_text> (%Rrc)"
3215 * </code>
3216 * to make it easily appendable to a more general error message. The @c %Rrc
3217 * format string is given @a aVRC as an argument.
3218 *
3219 * If there is no last error message collected by vdErrorCall or if it is a
3220 * null or empty string, then this function returns the following text:
3221 * <code>
3222 * " (%Rrc)"
3223 * </code>
3224 *
3225 * @note Doesn't do any object locking; it is assumed that the caller makes sure
3226 * the callback isn't called by more than one thread at a time.
3227 *
3228 * @param aVRC VBox error code to use when no error message is provided.
3229 */
3230Utf8Str HardDisk::vdError (int aVRC)
3231{
3232 Utf8Str error;
3233
3234 if (mm.vdError.isEmpty())
3235 error = Utf8StrFmt (" (%Rrc)", aVRC);
3236 else
3237 error = Utf8StrFmt (".\n%s", mm.vdError.raw());
3238
3239 mm.vdError.setNull();
3240
3241 return error;
3242}
3243
3244/**
3245 * Error message callback.
3246 *
3247 * Puts the reported error message to the mm.vdError field.
3248 *
3249 * @note Doesn't do any object locking; it is assumed that the caller makes sure
3250 * the callback isn't called by more than one thread at a time.
3251 *
3252 * @param pvUser The opaque data passed on container creation.
3253 * @param rc The VBox error code.
3254 * @param RT_SRC_POS_DECL Use RT_SRC_POS.
3255 * @param pszFormat Error message format string.
3256 * @param va Error message arguments.
3257 */
3258/*static*/
3259DECLCALLBACK(void) HardDisk::vdErrorCall(void *pvUser, int rc, RT_SRC_POS_DECL,
3260 const char *pszFormat, va_list va)
3261{
3262 HardDisk *that = static_cast<HardDisk*>(pvUser);
3263 AssertReturnVoid (that != NULL);
3264
3265 if (that->mm.vdError.isEmpty())
3266 that->mm.vdError =
3267 Utf8StrFmt ("%s (%Rrc)", Utf8StrFmtVA (pszFormat, va).raw(), rc);
3268 else
3269 that->mm.vdError =
3270 Utf8StrFmt ("%s.\n%s (%Rrc)", that->mm.vdError.raw(),
3271 Utf8StrFmtVA (pszFormat, va).raw(), rc);
3272}
3273
3274/**
3275 * PFNVMPROGRESS callback handler for Task operations.
3276 *
3277 * @param uPercent Completetion precentage (0-100).
3278 * @param pvUser Pointer to the Progress instance.
3279 */
3280/*static*/
3281DECLCALLBACK(int) HardDisk::vdProgressCall(PVM /* pVM */, unsigned uPercent,
3282 void *pvUser)
3283{
3284 HardDisk *that = static_cast<HardDisk*>(pvUser);
3285 AssertReturn (that != NULL, VERR_GENERAL_FAILURE);
3286
3287 if (that->mm.vdProgress != NULL)
3288 {
3289 /* update the progress object, capping it at 99% as the final percent
3290 * is used for additional operations like setting the UUIDs and similar. */
3291 that->mm.vdProgress->notifyProgress (RT_MIN (uPercent, 99));
3292 }
3293
3294 return VINF_SUCCESS;
3295}
3296
3297/* static */
3298DECLCALLBACK(bool) HardDisk::vdConfigAreKeysValid (void *pvUser,
3299 const char *pszzValid)
3300{
3301 HardDisk *that = static_cast<HardDisk*>(pvUser);
3302 AssertReturn (that != NULL, false);
3303
3304 /* we always return true since the only keys we have are those found in
3305 * VDBACKENDINFO */
3306 return true;
3307}
3308
3309/* static */
3310DECLCALLBACK(int) HardDisk::vdConfigQuerySize(void *pvUser, const char *pszName,
3311 size_t *pcbValue)
3312{
3313 AssertReturn (VALID_PTR (pcbValue), VERR_INVALID_POINTER);
3314
3315 HardDisk *that = static_cast<HardDisk*>(pvUser);
3316 AssertReturn (that != NULL, VERR_GENERAL_FAILURE);
3317
3318 Data::PropertyMap::const_iterator it =
3319 that->mm.properties.find (Bstr (pszName));
3320 if (it == that->mm.properties.end())
3321 return VERR_CFGM_VALUE_NOT_FOUND;
3322
3323 /* we interpret null values as "no value" in HardDisk */
3324 if (it->second.isNull())
3325 return VERR_CFGM_VALUE_NOT_FOUND;
3326
3327 *pcbValue = it->second.length() + 1 /* include terminator */;
3328
3329 return VINF_SUCCESS;
3330}
3331
3332/* static */
3333DECLCALLBACK(int) HardDisk::vdConfigQuery (void *pvUser, const char *pszName,
3334 char *pszValue, size_t cchValue)
3335{
3336 AssertReturn (VALID_PTR (pszValue), VERR_INVALID_POINTER);
3337
3338 HardDisk *that = static_cast<HardDisk*>(pvUser);
3339 AssertReturn (that != NULL, VERR_GENERAL_FAILURE);
3340
3341 Data::PropertyMap::const_iterator it =
3342 that->mm.properties.find (Bstr (pszName));
3343 if (it == that->mm.properties.end())
3344 return VERR_CFGM_VALUE_NOT_FOUND;
3345
3346 Utf8Str value = it->second;
3347 if (value.length() >= cchValue)
3348 return VERR_CFGM_NOT_ENOUGH_SPACE;
3349
3350 /* we interpret null values as "no value" in HardDisk */
3351 if (it->second.isNull())
3352 return VERR_CFGM_VALUE_NOT_FOUND;
3353
3354 memcpy (pszValue, value, value.length() + 1);
3355
3356 return VINF_SUCCESS;
3357}
3358
3359/**
3360 * Thread function for time-consuming tasks.
3361 *
3362 * The Task structure passed to @a pvUser must be allocated using new and will
3363 * be freed by this method before it returns.
3364 *
3365 * @param pvUser Pointer to the Task instance.
3366 */
3367/* static */
3368DECLCALLBACK(int) HardDisk::taskThread (RTTHREAD thread, void *pvUser)
3369{
3370 std::auto_ptr <Task> task (static_cast <Task *> (pvUser));
3371 AssertReturn (task.get(), VERR_GENERAL_FAILURE);
3372
3373 bool isAsync = thread != NIL_RTTHREAD;
3374
3375 HardDisk *that = task->that;
3376
3377 /// @todo ugly hack, fix ComAssert... later
3378 #define setError that->setError
3379
3380 /* Note: no need in AutoCaller because Task does that */
3381
3382 LogFlowFuncEnter();
3383 LogFlowFunc (("{%p}: operation=%d\n", that, task->operation));
3384
3385 HRESULT rc = S_OK;
3386
3387 switch (task->operation)
3388 {
3389 ////////////////////////////////////////////////////////////////////////
3390
3391 case Task::CreateDynamic:
3392 case Task::CreateFixed:
3393 {
3394 /* The lock is also used as a signal from the task initiator (which
3395 * releases it only after RTThreadCreate()) that we can start the job */
3396 AutoWriteLock thatLock (that);
3397
3398 /* these parameters we need after creation */
3399 uint64_t size = 0, logicalSize = 0;
3400
3401 /* The object may request a specific UUID (through a special form of
3402 * the setLocation() argumet). Otherwise we have to generate it */
3403 Guid id = that->m.id;
3404 bool generateUuid = id.isEmpty();
3405 if (generateUuid)
3406 {
3407 id.create();
3408 /* VirtualBox::registerHardDisk() will need UUID */
3409 unconst (that->m.id) = id;
3410 }
3411
3412 try
3413 {
3414 PVBOXHDD hdd;
3415 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3416 ComAssertRCThrow (vrc, E_FAIL);
3417
3418 Utf8Str format (that->mm.format);
3419 Utf8Str location (that->m.locationFull);
3420 uint64_t capabilities = that->mm.formatObj->capabilities();
3421
3422 /* unlock before the potentially lengthy operation */
3423 Assert (that->m.state == MediaState_Creating);
3424 thatLock.leave();
3425
3426 try
3427 {
3428 /* ensure the directory exists */
3429 rc = VirtualBox::ensureFilePathExists (location);
3430 CheckComRCThrowRC (rc);
3431
3432 PDMMEDIAGEOMETRY geo = { 0 }; /* auto-detect */
3433
3434 /* needed for vdProgressCallback */
3435 that->mm.vdProgress = task->progress;
3436
3437 vrc = VDCreateBase (hdd, format, location,
3438 task->operation == Task::CreateDynamic ?
3439 VD_IMAGE_TYPE_NORMAL :
3440 VD_IMAGE_TYPE_FIXED,
3441 task->d.size * _1M,
3442 VD_IMAGE_FLAGS_NONE,
3443 NULL, &geo, &geo, id.raw(),
3444 VD_OPEN_FLAGS_NORMAL,
3445 NULL, that->mm.vdDiskIfaces);
3446
3447 if (RT_FAILURE (vrc))
3448 {
3449 throw setError (E_FAIL,
3450 tr ("Could not create the hard disk storage "
3451 "unit '%s'%s"),
3452 location.raw(), that->vdError (vrc).raw());
3453 }
3454
3455 size = VDGetFileSize (hdd, 0);
3456 logicalSize = VDGetSize (hdd, 0) / _1M;
3457 }
3458 catch (HRESULT aRC) { rc = aRC; }
3459
3460 VDDestroy (hdd);
3461 }
3462 catch (HRESULT aRC) { rc = aRC; }
3463
3464 if (SUCCEEDED (rc))
3465 {
3466 /* register with mVirtualBox as the last step and move to
3467 * Created state only on success (leaving an orphan file is
3468 * better than breaking media registry consistency) */
3469 rc = that->mVirtualBox->registerHardDisk(that);
3470 }
3471
3472 thatLock.maybeEnter();
3473
3474 if (SUCCEEDED (rc))
3475 {
3476 that->m.state = MediaState_Created;
3477
3478 that->m.size = size;
3479 that->mm.logicalSize = logicalSize;
3480 }
3481 else
3482 {
3483 /* back to NotCreated on failiure */
3484 that->m.state = MediaState_NotCreated;
3485
3486 /* reset UUID to prevent it from being reused next time */
3487 if (generateUuid)
3488 unconst (that->m.id).clear();
3489 }
3490
3491 break;
3492 }
3493
3494 ////////////////////////////////////////////////////////////////////////
3495
3496 case Task::CreateDiff:
3497 {
3498 ComObjPtr<HardDisk> &target = task->d.target;
3499
3500 /* Lock both in {parent,child} order. The lock is also used as a
3501 * signal from the task initiator (which releases it only after
3502 * RTThreadCreate()) that we can start the job*/
3503 AutoMultiWriteLock2 thatLock (that, target);
3504
3505 uint64_t size = 0, logicalSize = 0;
3506
3507 /* The object may request a specific UUID (through a special form of
3508 * the setLocation() argument). Otherwise we have to generate it */
3509 Guid targetId = target->m.id;
3510 bool generateUuid = targetId.isEmpty();
3511 if (generateUuid)
3512 {
3513 targetId.create();
3514 /* VirtualBox::registerHardDisk() will need UUID */
3515 unconst (target->m.id) = targetId;
3516 }
3517
3518 try
3519 {
3520 PVBOXHDD hdd;
3521 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3522 ComAssertRCThrow (vrc, E_FAIL);
3523
3524 Guid id = that->m.id;
3525 Utf8Str format (that->mm.format);
3526 Utf8Str location (that->m.locationFull);
3527
3528 Utf8Str targetFormat (target->mm.format);
3529 Utf8Str targetLocation (target->m.locationFull);
3530
3531 Assert (target->m.state == MediaState_Creating);
3532
3533 /* Note: MediaState_LockedWrite is ok when taking an online
3534 * snapshot */
3535 Assert (that->m.state == MediaState_LockedRead ||
3536 that->m.state == MediaState_LockedWrite);
3537
3538 /* unlock before the potentially lengthy operation */
3539 thatLock.leave();
3540
3541 try
3542 {
3543 vrc = VDOpen (hdd, format, location,
3544 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
3545 that->mm.vdDiskIfaces);
3546 if (RT_FAILURE (vrc))
3547 {
3548 throw setError (E_FAIL,
3549 tr ("Could not open the hard disk storage "
3550 "unit '%s'%s"),
3551 location.raw(), that->vdError (vrc).raw());
3552 }
3553
3554 /* ensure the target directory exists */
3555 rc = VirtualBox::ensureFilePathExists (targetLocation);
3556 CheckComRCThrowRC (rc);
3557
3558 /* needed for vdProgressCallback */
3559 that->mm.vdProgress = task->progress;
3560
3561 vrc = VDCreateDiff (hdd, targetFormat, targetLocation,
3562 VD_IMAGE_FLAGS_NONE,
3563 NULL, targetId.raw(),
3564 id.raw(),
3565 VD_OPEN_FLAGS_NORMAL,
3566 target->mm.vdDiskIfaces,
3567 that->mm.vdDiskIfaces);
3568
3569 that->mm.vdProgress = NULL;
3570
3571 if (RT_FAILURE (vrc))
3572 {
3573 throw setError (E_FAIL,
3574 tr ("Could not create the differencing hard disk "
3575 "storage unit '%s'%s"),
3576 targetLocation.raw(), that->vdError (vrc).raw());
3577 }
3578
3579 size = VDGetFileSize (hdd, 1);
3580 logicalSize = VDGetSize (hdd, 1) / _1M;
3581 }
3582 catch (HRESULT aRC) { rc = aRC; }
3583
3584 VDDestroy (hdd);
3585 }
3586 catch (HRESULT aRC) { rc = aRC; }
3587
3588 if (SUCCEEDED (rc))
3589 {
3590 /* we set mParent & children() (note that thatLock is released
3591 * here), but lock VirtualBox first to follow the rule */
3592 AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
3593 that->treeLock());
3594
3595 Assert (target->mParent.isNull());
3596
3597 /* associate the child with the parent and deassociate from
3598 * VirtualBox */
3599 target->mParent = that;
3600 that->addDependentChild (target);
3601 target->mVirtualBox->removeDependentChild (target);
3602
3603 /* diffs for immutable hard disks are auto-reset by default */
3604 target->mm.autoReset =
3605 that->root()->mm.type == HardDiskType_Immutable ?
3606 TRUE : FALSE;
3607
3608 /* register with mVirtualBox as the last step and move to
3609 * Created state only on success (leaving an orphan file is
3610 * better than breaking media registry consistency) */
3611 rc = that->mVirtualBox->registerHardDisk (target);
3612
3613 if (FAILED (rc))
3614 {
3615 /* break the parent association on failure to register */
3616 target->mVirtualBox->addDependentChild (target);
3617 that->removeDependentChild (target);
3618 target->mParent.setNull();
3619 }
3620 }
3621
3622 thatLock.maybeEnter();
3623
3624 if (SUCCEEDED (rc))
3625 {
3626 target->m.state = MediaState_Created;
3627
3628 target->m.size = size;
3629 target->mm.logicalSize = logicalSize;
3630 }
3631 else
3632 {
3633 /* back to NotCreated on failiure */
3634 target->m.state = MediaState_NotCreated;
3635
3636 target->mm.autoReset = FALSE;
3637
3638 /* reset UUID to prevent it from being reused next time */
3639 if (generateUuid)
3640 unconst (target->m.id).clear();
3641 }
3642
3643 if (isAsync)
3644 {
3645 /* unlock ourselves when done (unless in MediaState_LockedWrite
3646 * state because of taking the online snapshot*/
3647 if (that->m.state != MediaState_LockedWrite)
3648 {
3649 HRESULT rc2 = that->UnlockRead (NULL);
3650 AssertComRC (rc2);
3651 }
3652 }
3653
3654 /* deregister the task registered in createDiffStorage() */
3655 Assert (that->mm.numCreateDiffTasks != 0);
3656 -- that->mm.numCreateDiffTasks;
3657
3658 /* Note that in sync mode, it's the caller's responsibility to
3659 * unlock the hard disk */
3660
3661 break;
3662 }
3663
3664 ////////////////////////////////////////////////////////////////////////
3665
3666 case Task::Merge:
3667 {
3668 /* The lock is also used as a signal from the task initiator (which
3669 * releases it only after RTThreadCreate()) that we can start the
3670 * job. We don't actually need the lock for anything else since the
3671 * object is protected by MediaState_Deleting and we don't modify
3672 * its sensitive fields below */
3673 {
3674 AutoWriteLock thatLock (that);
3675 }
3676
3677 MergeChain *chain = task->d.chain.get();
3678
3679#if 0
3680 LogFlow (("*** MERGE forward = %RTbool\n", chain->isForward()));
3681#endif
3682
3683 try
3684 {
3685 PVBOXHDD hdd;
3686 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3687 ComAssertRCThrow (vrc, E_FAIL);
3688
3689 try
3690 {
3691 /* open all hard disks in the chain (they are in the
3692 * {parent,child} order in there. Note that we don't lock
3693 * objects in this chain since they must be in states
3694 * (Deleting and LockedWrite) that prevent from chaning
3695 * their format and location fields from outside. */
3696
3697 for (MergeChain::const_iterator it = chain->begin();
3698 it != chain->end(); ++ it)
3699 {
3700 /* complex sanity (sane complexity) */
3701 Assert ((chain->isForward() &&
3702 ((*it != chain->back() &&
3703 (*it)->m.state == MediaState_Deleting) ||
3704 (*it == chain->back() &&
3705 (*it)->m.state == MediaState_LockedWrite))) ||
3706 (!chain->isForward() &&
3707 ((*it != chain->front() &&
3708 (*it)->m.state == MediaState_Deleting) ||
3709 (*it == chain->front() &&
3710 (*it)->m.state == MediaState_LockedWrite))));
3711
3712 Assert (*it == chain->target() ||
3713 (*it)->m.backRefs.size() == 0);
3714
3715 /* open the first image with VDOPEN_FLAGS_INFO because
3716 * it's not necessarily the base one */
3717 vrc = VDOpen (hdd, Utf8Str ((*it)->mm.format),
3718 Utf8Str ((*it)->m.locationFull),
3719 it == chain->begin() ?
3720 VD_OPEN_FLAGS_INFO : 0,
3721 (*it)->mm.vdDiskIfaces);
3722 if (RT_FAILURE (vrc))
3723 throw vrc;
3724#if 0
3725 LogFlow (("*** MERGE disk = %ls\n",
3726 (*it)->m.locationFull.raw()));
3727#endif
3728 }
3729
3730 /* needed for vdProgressCallback */
3731 that->mm.vdProgress = task->progress;
3732
3733 unsigned start = chain->isForward() ?
3734 0 : chain->size() - 1;
3735 unsigned end = chain->isForward() ?
3736 chain->size() - 1 : 0;
3737#if 0
3738 LogFlow (("*** MERGE from %d to %d\n", start, end));
3739#endif
3740 vrc = VDMerge (hdd, start, end, that->mm.vdDiskIfaces);
3741
3742 that->mm.vdProgress = NULL;
3743
3744 if (RT_FAILURE (vrc))
3745 throw vrc;
3746
3747 /* update parent UUIDs */
3748 /// @todo VDMerge should be taught to do so, including the
3749 /// multiple children case
3750 if (chain->isForward())
3751 {
3752 /* target's UUID needs to be updated (note that target
3753 * is the only image in the container on success) */
3754 vrc = VDSetParentUuid (hdd, 0, chain->parent()->m.id);
3755 if (RT_FAILURE (vrc))
3756 throw vrc;
3757 }
3758 else
3759 {
3760 /* we need to update UUIDs of all source's children
3761 * which cannot be part of the container at once so
3762 * add each one in there individually */
3763 if (chain->children().size() > 0)
3764 {
3765 for (List::const_iterator it = chain->children().begin();
3766 it != chain->children().end(); ++ it)
3767 {
3768 /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
3769 vrc = VDOpen (hdd, Utf8Str ((*it)->mm.format),
3770 Utf8Str ((*it)->m.locationFull),
3771 VD_OPEN_FLAGS_INFO,
3772 (*it)->mm.vdDiskIfaces);
3773 if (RT_FAILURE (vrc))
3774 throw vrc;
3775
3776 vrc = VDSetParentUuid (hdd, 1,
3777 chain->target()->m.id);
3778 if (RT_FAILURE (vrc))
3779 throw vrc;
3780
3781 vrc = VDClose (hdd, false /* fDelete */);
3782 if (RT_FAILURE (vrc))
3783 throw vrc;
3784 }
3785 }
3786 }
3787 }
3788 catch (HRESULT aRC) { rc = aRC; }
3789 catch (int aVRC)
3790 {
3791 throw setError (E_FAIL,
3792 tr ("Could not merge the hard disk '%ls' to '%ls'%s"),
3793 chain->source()->m.locationFull.raw(),
3794 chain->target()->m.locationFull.raw(),
3795 that->vdError (aVRC).raw());
3796 }
3797
3798 VDDestroy (hdd);
3799 }
3800 catch (HRESULT aRC) { rc = aRC; }
3801
3802 HRESULT rc2;
3803
3804 bool saveSettingsFailed = false;
3805
3806 if (SUCCEEDED (rc))
3807 {
3808 /* all hard disks but the target were successfully deleted by
3809 * VDMerge; reparent the last one and uninitialize deleted */
3810
3811 /* we set mParent & children() (note that thatLock is released
3812 * here), but lock VirtualBox first to follow the rule */
3813 AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
3814 that->treeLock());
3815
3816 HardDisk *source = chain->source();
3817 HardDisk *target = chain->target();
3818
3819 if (chain->isForward())
3820 {
3821 /* first, unregister the target since it may become a base
3822 * hard disk which needs re-registration */
3823 rc2 = target->mVirtualBox->
3824 unregisterHardDisk (target, false /* aSaveSettings */);
3825 AssertComRC (rc2);
3826
3827 /* then, reparent it and disconnect the deleted branch at
3828 * both ends (chain->parent() is source's parent) */
3829 target->mParent->removeDependentChild (target);
3830 target->mParent = chain->parent();
3831 if (!target->mParent.isNull())
3832 {
3833 target->mParent->addDependentChild (target);
3834 target->mParent->removeDependentChild (source);
3835 source->mParent.setNull();
3836 }
3837 else
3838 {
3839 target->mVirtualBox->addDependentChild (target);
3840 target->mVirtualBox->removeDependentChild (source);
3841 }
3842
3843 /* then, register again */
3844 rc2 = target->mVirtualBox->
3845 registerHardDisk (target, false /* aSaveSettings */);
3846 AssertComRC (rc2);
3847 }
3848 else
3849 {
3850 Assert (target->children().size() == 1);
3851 HardDisk *targetChild = target->children().front();
3852
3853 /* disconnect the deleted branch at the elder end */
3854 target->removeDependentChild (targetChild);
3855 targetChild->mParent.setNull();
3856
3857 const List &children = chain->children();
3858
3859 /* reparent source's chidren and disconnect the deleted
3860 * branch at the younger end m*/
3861 if (children.size() > 0)
3862 {
3863 /* obey {parent,child} lock order */
3864 AutoWriteLock sourceLock (source);
3865
3866 for (List::const_iterator it = children.begin();
3867 it != children.end(); ++ it)
3868 {
3869 AutoWriteLock childLock (*it);
3870
3871 (*it)->mParent = target;
3872 (*it)->mParent->addDependentChild (*it);
3873 source->removeDependentChild (*it);
3874 }
3875 }
3876 }
3877
3878 /* try to save the hard disk registry */
3879 rc = that->mVirtualBox->saveSettings();
3880
3881 if (SUCCEEDED (rc))
3882 {
3883 /* unregister and uninitialize all hard disks in the chain
3884 * but the target */
3885
3886 for (MergeChain::iterator it = chain->begin();
3887 it != chain->end();)
3888 {
3889 if (*it == chain->target())
3890 {
3891 ++ it;
3892 continue;
3893 }
3894
3895 rc2 = (*it)->mVirtualBox->
3896 unregisterHardDisk(*it, false /* aSaveSettings */);
3897 AssertComRC (rc2);
3898
3899 /* now, uninitialize the deleted hard disk (note that
3900 * due to the Deleting state, uninit() will not touch
3901 * the parent-child relationship so we need to
3902 * uninitialize each disk individually) */
3903
3904 /* note that the operation initiator hard disk (which is
3905 * normally also the source hard disk) is a special case
3906 * -- there is one more caller added by Task to it which
3907 * we must release. Also, if we are in sync mode, the
3908 * caller may still hold an AutoCaller instance for it
3909 * and therefore we cannot uninit() it (it's therefore
3910 * the caller's responsibility) */
3911 if (*it == that)
3912 task->autoCaller.release();
3913
3914 /* release the caller added by MergeChain before
3915 * uninit() */
3916 (*it)->releaseCaller();
3917
3918 if (isAsync || *it != that)
3919 (*it)->uninit();
3920
3921 /* delete (to prevent uninitialization in MergeChain
3922 * dtor) and advance to the next item */
3923 it = chain->erase (it);
3924 }
3925
3926 /* Note that states of all other hard disks (target, parent,
3927 * children) will be restored by the MergeChain dtor */
3928 }
3929 else
3930 {
3931 /* too bad if we fail, but we'll need to rollback everything
3932 * we did above to at least keep the HD tree in sync with
3933 * the current registry on disk */
3934
3935 saveSettingsFailed = true;
3936
3937 /// @todo NEWMEDIA implement a proper undo
3938
3939 AssertFailed();
3940 }
3941 }
3942
3943 if (FAILED (rc))
3944 {
3945 /* Here we come if either VDMerge() failed (in which case we
3946 * assume that it tried to do everything to make a further
3947 * retry possible -- e.g. not deleted intermediate hard disks
3948 * and so on) or VirtualBox::saveSettings() failed (where we
3949 * should have the original tree but with intermediate storage
3950 * units deleted by VDMerge()). We have to only restore states
3951 * (through the MergeChain dtor) unless we are run synchronously
3952 * in which case it's the responsibility of the caller as stated
3953 * in the mergeTo() docs. The latter also implies that we
3954 * don't own the merge chain, so release it in this case. */
3955
3956 if (!isAsync)
3957 task->d.chain.release();
3958
3959 NOREF (saveSettingsFailed);
3960 }
3961
3962 break;
3963 }
3964
3965 ////////////////////////////////////////////////////////////////////////
3966
3967 case Task::Clone:
3968 {
3969 ComObjPtr<HardDisk> &target = task->d.target;
3970
3971 /* Lock both in {parent,child} order. The lock is also used as a
3972 * signal from the task initiator (which releases it only after
3973 * RTThreadCreate()) that we can start the job*/
3974 AutoMultiWriteLock2 thatLock (that, target);
3975
3976 uint64_t size = 0, logicalSize = 0;
3977
3978 /* The object may request a specific UUID (through a special form of
3979 * the setLocation() argumet). Otherwise we have to generate it */
3980 Guid targetId = target->m.id;
3981 bool generateUuid = targetId.isEmpty();
3982 if (generateUuid)
3983 {
3984 targetId.create();
3985 /* VirtualBox::registerHardDisk() will need UUID */
3986 unconst (target->m.id) = targetId;
3987 }
3988
3989 try
3990 {
3991 PVBOXHDD hdd;
3992 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3993 ComAssertRCThrow (vrc, E_FAIL);
3994
3995 Utf8Str format (that->mm.format);
3996 Utf8Str location (that->m.locationFull);
3997
3998 Utf8Str targetFormat (target->mm.format);
3999 Utf8Str targetLocation (target->m.locationFull);
4000
4001 Assert (target->m.state == MediaState_Creating);
4002
4003 Assert (that->m.state == MediaState_LockedRead);
4004
4005 /* unlock before the potentially lengthy operation */
4006 thatLock.leave();
4007
4008 try
4009 {
4010 vrc = VDOpen (hdd, format, location,
4011 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
4012 that->mm.vdDiskIfaces);
4013 if (RT_FAILURE (vrc))
4014 {
4015 throw setError (E_FAIL,
4016 tr ("Could not open the hard disk storage "
4017 "unit '%s'%s"),
4018 location.raw(), that->vdError (vrc).raw());
4019 }
4020
4021 /* ensure the target directory exists */
4022 rc = VirtualBox::ensureFilePathExists (targetLocation);
4023 CheckComRCThrowRC (rc);
4024
4025 /* needed for vdProgressCallback */
4026 that->mm.vdProgress = task->progress;
4027
4028 PVBOXHDD targetHdd;
4029 int vrc = VDCreate (that->mm.vdDiskIfaces, &targetHdd);
4030 ComAssertRCThrow (vrc, E_FAIL);
4031
4032 vrc = VDCopy (hdd, 0, targetHdd, targetFormat,
4033 targetLocation, false, 0, targetId.raw(),
4034 NULL, target->mm.vdDiskIfaces,
4035 that->mm.vdDiskIfaces);
4036
4037 that->mm.vdProgress = NULL;
4038
4039 if (RT_FAILURE (vrc))
4040 {
4041 VDDestroy (targetHdd);
4042
4043 throw setError (E_FAIL,
4044 tr ("Could not create the clone hard disk "
4045 "'%s'%s"),
4046 targetLocation.raw(), that->vdError (vrc).raw());
4047 }
4048
4049 size = VDGetFileSize (targetHdd, 0);
4050 logicalSize = VDGetSize (targetHdd, 0) / _1M;
4051
4052 VDDestroy (targetHdd);
4053 }
4054 catch (HRESULT aRC) { rc = aRC; }
4055
4056 VDDestroy (hdd);
4057 }
4058 catch (HRESULT aRC) { rc = aRC; }
4059
4060 if (SUCCEEDED (rc))
4061 {
4062 /* we set mParent & children() (note that thatLock is released
4063 * here), but lock VirtualBox first to follow the rule */
4064 AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
4065 that->treeLock());
4066
4067 Assert (target->mParent.isNull());
4068
4069 if (!that->mParent.isNull())
4070 {
4071 /* associate the clone with the original's parent and
4072 * deassociate from VirtualBox */
4073 target->mParent = that->mParent;
4074 that->mParent->addDependentChild (target);
4075 target->mVirtualBox->removeDependentChild (target);
4076
4077 /* register with mVirtualBox as the last step and move to
4078 * Created state only on success (leaving an orphan file is
4079 * better than breaking media registry consistency) */
4080 rc = that->mVirtualBox->registerHardDisk(target);
4081
4082 if (FAILED (rc))
4083 {
4084 /* break the parent association on failure to register */
4085 target->mVirtualBox->addDependentChild (target);
4086 that->mParent->removeDependentChild (target);
4087 target->mParent.setNull();
4088 }
4089 }
4090 else
4091 {
4092 /* just register */
4093 rc = that->mVirtualBox->registerHardDisk(target);
4094 }
4095 }
4096
4097 thatLock.maybeEnter();
4098
4099 if (SUCCEEDED (rc))
4100 {
4101 target->m.state = MediaState_Created;
4102
4103 target->m.size = size;
4104 target->mm.logicalSize = logicalSize;
4105 }
4106 else
4107 {
4108 /* back to NotCreated on failiure */
4109 target->m.state = MediaState_NotCreated;
4110
4111 /* reset UUID to prevent it from being reused next time */
4112 if (generateUuid)
4113 unconst (target->m.id).clear();
4114 }
4115
4116 if (isAsync)
4117 {
4118 /* unlock ourselves when done (unless in MediaState_LockedWrite
4119 * state because of taking the online snapshot*/
4120 if (that->m.state != MediaState_LockedWrite)
4121 {
4122 HRESULT rc2 = that->UnlockRead (NULL);
4123 AssertComRC (rc2);
4124 }
4125 }
4126
4127 /* Note that in sync mode, it's the caller's responsibility to
4128 * unlock the hard disk */
4129
4130 break;
4131 }
4132
4133 ////////////////////////////////////////////////////////////////////////
4134
4135 case Task::Delete:
4136 {
4137 /* The lock is also used as a signal from the task initiator (which
4138 * releases it only after RTThreadCreate()) that we can start the job */
4139 AutoWriteLock thatLock (that);
4140
4141 try
4142 {
4143 PVBOXHDD hdd;
4144 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
4145 ComAssertRCThrow (vrc, E_FAIL);
4146
4147 Utf8Str format (that->mm.format);
4148 Utf8Str location (that->m.locationFull);
4149
4150 /* unlock before the potentially lengthy operation */
4151 Assert (that->m.state == MediaState_Deleting);
4152 thatLock.leave();
4153
4154 try
4155 {
4156 vrc = VDOpen (hdd, format, location,
4157 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
4158 that->mm.vdDiskIfaces);
4159 if (RT_SUCCESS (vrc))
4160 vrc = VDClose (hdd, true /* fDelete */);
4161
4162 if (RT_FAILURE (vrc))
4163 {
4164 throw setError (E_FAIL,
4165 tr ("Could not delete the hard disk storage "
4166 "unit '%s'%s"),
4167 location.raw(), that->vdError (vrc).raw());
4168 }
4169
4170 }
4171 catch (HRESULT aRC) { rc = aRC; }
4172
4173 VDDestroy (hdd);
4174 }
4175 catch (HRESULT aRC) { rc = aRC; }
4176
4177 thatLock.maybeEnter();
4178
4179 /* go to the NotCreated state even on failure since the storage
4180 * may have been already partially deleted and cannot be used any
4181 * more. One will be able to manually re-open the storage if really
4182 * needed to re-register it. */
4183 that->m.state = MediaState_NotCreated;
4184
4185 /* Reset UUID to prevent Create* from reusing it again */
4186 unconst (that->m.id).clear();
4187
4188 break;
4189 }
4190
4191 case Task::Reset:
4192 {
4193 /* The lock is also used as a signal from the task initiator (which
4194 * releases it only after RTThreadCreate()) that we can start the job */
4195 AutoWriteLock thatLock (that);
4196
4197 /// @todo Below we use a pair of delete/create operations to reset
4198 /// the diff contents but the most efficient way will of course be
4199 /// to add a VDResetDiff() API call
4200
4201 uint64_t size = 0, logicalSize = 0;
4202
4203 try
4204 {
4205 PVBOXHDD hdd;
4206 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
4207 ComAssertRCThrow (vrc, E_FAIL);
4208
4209 Guid id = that->m.id;
4210 Utf8Str format (that->mm.format);
4211 Utf8Str location (that->m.locationFull);
4212
4213 Guid parentId = that->mParent->m.id;
4214 Utf8Str parentFormat (that->mParent->mm.format);
4215 Utf8Str parentLocation (that->mParent->m.locationFull);
4216
4217 Assert (that->m.state == MediaState_LockedWrite);
4218
4219 /* unlock before the potentially lengthy operation */
4220 thatLock.leave();
4221
4222 try
4223 {
4224 /* first, delete the storage unit */
4225 vrc = VDOpen (hdd, format, location,
4226 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
4227 that->mm.vdDiskIfaces);
4228 if (RT_SUCCESS (vrc))
4229 vrc = VDClose (hdd, true /* fDelete */);
4230
4231 if (RT_FAILURE (vrc))
4232 {
4233 throw setError (E_FAIL,
4234 tr ("Could not delete the hard disk storage "
4235 "unit '%s'%s"),
4236 location.raw(), that->vdError (vrc).raw());
4237 }
4238
4239 /* next, create it again */
4240 vrc = VDOpen (hdd, parentFormat, parentLocation,
4241 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
4242 that->mm.vdDiskIfaces);
4243 if (RT_FAILURE (vrc))
4244 {
4245 throw setError (E_FAIL,
4246 tr ("Could not open the hard disk storage "
4247 "unit '%s'%s"),
4248 parentLocation.raw(), that->vdError (vrc).raw());
4249 }
4250
4251 /* needed for vdProgressCallback */
4252 that->mm.vdProgress = task->progress;
4253
4254 vrc = VDCreateDiff (hdd, format, location,
4255 VD_IMAGE_FLAGS_NONE,
4256 NULL, id.raw(),
4257 parentId.raw(),
4258 VD_OPEN_FLAGS_NORMAL,
4259 that->mm.vdDiskIfaces,
4260 that->mm.vdDiskIfaces);
4261
4262 that->mm.vdProgress = NULL;
4263
4264 if (RT_FAILURE (vrc))
4265 {
4266 throw setError (E_FAIL,
4267 tr ("Could not create the differencing hard disk "
4268 "storage unit '%s'%s"),
4269 location.raw(), that->vdError (vrc).raw());
4270 }
4271
4272 size = VDGetFileSize (hdd, 1);
4273 logicalSize = VDGetSize (hdd, 1) / _1M;
4274 }
4275 catch (HRESULT aRC) { rc = aRC; }
4276
4277 VDDestroy (hdd);
4278 }
4279 catch (HRESULT aRC) { rc = aRC; }
4280
4281 thatLock.enter();
4282
4283 that->m.size = size;
4284 that->mm.logicalSize = logicalSize;
4285
4286 if (isAsync)
4287 {
4288 /* unlock ourselves when done */
4289 HRESULT rc2 = that->UnlockWrite (NULL);
4290 AssertComRC (rc2);
4291 }
4292
4293 /* Note that in sync mode, it's the caller's responsibility to
4294 * unlock the hard disk */
4295
4296 break;
4297 }
4298
4299 default:
4300 AssertFailedReturn (VERR_GENERAL_FAILURE);
4301 }
4302
4303 /* complete the progress if run asynchronously */
4304 if (isAsync)
4305 {
4306 if (!task->progress.isNull())
4307 task->progress->notifyComplete (rc);
4308 }
4309 else
4310 {
4311 task->rc = rc;
4312 }
4313
4314 LogFlowFunc (("rc=%Rhrc\n", rc));
4315 LogFlowFuncLeave();
4316
4317 return VINF_SUCCESS;
4318
4319 /// @todo ugly hack, fix ComAssert... later
4320 #undef setError
4321}
4322/* vi: set tabstop=4 shiftwidth=4 expandtab: */
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