VirtualBox

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

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

API/HardDisk, Storage/VBoxHDD: use image variant, and to implement cloning to different image variant add a parameter to VDCopy.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 141.1 KB
Line 
1/* $Id: HardDiskImpl.cpp 17836 2009-03-13 15:26:27Z 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 aVariant Precise image variant to create.
2242 * @param aProgress Where to find/store a Progress object to track operation
2243 * completion.
2244 * @param aWait @c true if this method should block instead of creating
2245 * an asynchronous thread.
2246 *
2247 * @note Locks this object and @a aTarget for writing.
2248 */
2249HRESULT HardDisk::createDiffStorage(ComObjPtr<HardDisk> &aTarget,
2250 HardDiskVariant_T aVariant,
2251 ComObjPtr<Progress> *aProgress,
2252 bool aWait)
2253{
2254 AssertReturn (!aTarget.isNull(), E_FAIL);
2255 AssertReturn (aProgress != NULL || aWait == true, E_FAIL);
2256
2257 AutoCaller autoCaller (this);
2258 CheckComRCReturnRC (autoCaller.rc());
2259
2260 AutoCaller targetCaller (aTarget);
2261 CheckComRCReturnRC (targetCaller.rc());
2262
2263 AutoMultiWriteLock2 alock (this, aTarget);
2264
2265 AssertReturn (mm.type != HardDiskType_Writethrough, E_FAIL);
2266
2267 /* Note: MediaState_LockedWrite is ok when taking an online snapshot */
2268 AssertReturn (m.state == MediaState_LockedRead ||
2269 m.state == MediaState_LockedWrite, E_FAIL);
2270
2271 if (aTarget->m.state != MediaState_NotCreated)
2272 return aTarget->setStateError();
2273
2274 HRESULT rc = S_OK;
2275
2276 /* check that the hard disk is not attached to any VM in the current state*/
2277 for (BackRefList::const_iterator it = m.backRefs.begin();
2278 it != m.backRefs.end(); ++ it)
2279 {
2280 if (it->inCurState)
2281 {
2282 /* Note: when a VM snapshot is being taken, all normal hard disks
2283 * attached to the VM in the current state will be, as an exception,
2284 * also associated with the snapshot which is about to create (see
2285 * SnapshotMachine::init()) before deassociating them from the
2286 * current state (which takes place only on success in
2287 * Machine::fixupHardDisks()), so that the size of snapshotIds
2288 * will be 1 in this case. The given condition is used to filter out
2289 * this legal situatinon and do not report an error. */
2290
2291 if (it->snapshotIds.size() == 0)
2292 {
2293 return setError (VBOX_E_INVALID_OBJECT_STATE,
2294 tr ("Hard disk '%ls' is attached to a virtual machine "
2295 "with UUID {%RTuuid}. No differencing hard disks "
2296 "based on it may be created until it is detached"),
2297 m.locationFull.raw(), it->machineId.raw());
2298 }
2299
2300 Assert (it->snapshotIds.size() == 1);
2301 }
2302 }
2303
2304 ComObjPtr <Progress> progress;
2305
2306 if (aProgress != NULL)
2307 {
2308 /* use the existing progress object... */
2309 progress = *aProgress;
2310
2311 /* ...but create a new one if it is null */
2312 if (progress.isNull())
2313 {
2314 progress.createObject();
2315 rc = progress->init (mVirtualBox, static_cast<IHardDisk*> (this),
2316 BstrFmt (tr ("Creating differencing hard disk storage unit '%ls'"),
2317 aTarget->m.locationFull.raw()),
2318 FALSE /* aCancelable */);
2319 CheckComRCReturnRC (rc);
2320 }
2321 }
2322
2323 /* setup task object and thread to carry out the operation
2324 * asynchronously */
2325
2326 std::auto_ptr <Task> task (new Task (this, progress, Task::CreateDiff));
2327 AssertComRCReturnRC (task->autoCaller.rc());
2328
2329 task->setData (aTarget);
2330 task->d.variant = aVariant;
2331
2332 /* register a task (it will deregister itself when done) */
2333 ++ mm.numCreateDiffTasks;
2334 Assert (mm.numCreateDiffTasks != 0); /* overflow? */
2335
2336 if (aWait)
2337 {
2338 /* go to Creating state before starting the task */
2339 aTarget->m.state = MediaState_Creating;
2340
2341 rc = task->runNow();
2342 }
2343 else
2344 {
2345 rc = task->startThread();
2346 CheckComRCReturnRC (rc);
2347
2348 /* go to Creating state before leaving the lock */
2349 aTarget->m.state = MediaState_Creating;
2350 }
2351
2352 /* task is now owned (or already deleted) by taskThread() so release it */
2353 task.release();
2354
2355 if (aProgress != NULL)
2356 {
2357 /* return progress to the caller */
2358 *aProgress = progress;
2359 }
2360
2361 return rc;
2362}
2363
2364/**
2365 * Prepares this (source) hard disk, target hard disk and all intermediate hard
2366 * disks for the merge operation.
2367 *
2368 * This method is to be called prior to calling the #mergeTo() to perform
2369 * necessary consistency checks and place involved hard disks to appropriate
2370 * states. If #mergeTo() is not called or fails, the state modifications
2371 * performed by this method must be undone by #cancelMergeTo().
2372 *
2373 * Note that when @a aIgnoreAttachments is @c true then it's the caller's
2374 * responsibility to detach the source and all intermediate hard disks before
2375 * calling #mergeTo() (which will fail otherwise).
2376 *
2377 * See #mergeTo() for more information about merging.
2378 *
2379 * @param aTarget Target hard disk.
2380 * @param aChain Where to store the created merge chain.
2381 * @param aIgnoreAttachments Don't check if the source or any intermediate
2382 * hard disk is attached to any VM.
2383 *
2384 * @note Locks treeLock() for reading. Locks this object, aTarget and all
2385 * intermediate hard disks for writing.
2386 */
2387HRESULT HardDisk::prepareMergeTo(HardDisk *aTarget,
2388 MergeChain * &aChain,
2389 bool aIgnoreAttachments /*= false*/)
2390{
2391 AssertReturn (aTarget != NULL, E_FAIL);
2392
2393 AutoCaller autoCaller (this);
2394 AssertComRCReturnRC (autoCaller.rc());
2395
2396 AutoCaller targetCaller (aTarget);
2397 AssertComRCReturnRC (targetCaller.rc());
2398
2399 aChain = NULL;
2400
2401 /* we walk the tree */
2402 AutoReadLock treeLock (this->treeLock());
2403
2404 HRESULT rc = S_OK;
2405
2406 /* detect the merge direction */
2407 bool forward;
2408 {
2409 HardDisk *parent = mParent;
2410 while (parent != NULL && parent != aTarget)
2411 parent = parent->mParent;
2412 if (parent == aTarget)
2413 forward = false;
2414 else
2415 {
2416 parent = aTarget->mParent;
2417 while (parent != NULL && parent != this)
2418 parent = parent->mParent;
2419 if (parent == this)
2420 forward = true;
2421 else
2422 {
2423 Bstr tgtLoc;
2424 {
2425 AutoReadLock alock (this);
2426 tgtLoc = aTarget->locationFull();
2427 }
2428
2429 AutoReadLock alock (this);
2430 return setError (E_FAIL,
2431 tr ("Hard disks '%ls' and '%ls' are unrelated"),
2432 m.locationFull.raw(), tgtLoc.raw());
2433 }
2434 }
2435 }
2436
2437 /* build the chain (will do necessary checks and state changes) */
2438 std::auto_ptr <MergeChain> chain (new MergeChain (forward,
2439 aIgnoreAttachments));
2440 {
2441 HardDisk *last = forward ? aTarget : this;
2442 HardDisk *first = forward ? this : aTarget;
2443
2444 for (;;)
2445 {
2446 if (last == aTarget)
2447 rc = chain->addTarget (last);
2448 else if (last == this)
2449 rc = chain->addSource (last);
2450 else
2451 rc = chain->addIntermediate (last);
2452 CheckComRCReturnRC (rc);
2453
2454 if (last == first)
2455 break;
2456
2457 last = last->mParent;
2458 }
2459 }
2460
2461 aChain = chain.release();
2462
2463 return S_OK;
2464}
2465
2466/**
2467 * Merges this hard disk to the specified hard disk which must be either its
2468 * direct ancestor or descendant.
2469 *
2470 * Given this hard disk is SOURCE and the specified hard disk is TARGET, we will
2471 * get two varians of the merge operation:
2472 *
2473 * forward merge
2474 * ------------------------->
2475 * [Extra] <- SOURCE <- Intermediate <- TARGET
2476 * Any Del Del LockWr
2477 *
2478 *
2479 * backward merge
2480 * <-------------------------
2481 * TARGET <- Intermediate <- SOURCE <- [Extra]
2482 * LockWr Del Del LockWr
2483 *
2484 * Each scheme shows the involved hard disks on the hard disk chain where
2485 * SOURCE and TARGET belong. Under each hard disk there is a state value which
2486 * the hard disk must have at a time of the mergeTo() call.
2487 *
2488 * The hard disks in the square braces may be absent (e.g. when the forward
2489 * operation takes place and SOURCE is the base hard disk, or when the backward
2490 * merge operation takes place and TARGET is the last child in the chain) but if
2491 * they present they are involved too as shown.
2492 *
2493 * Nor the source hard disk neither intermediate hard disks may be attached to
2494 * any VM directly or in the snapshot, otherwise this method will assert.
2495 *
2496 * The #prepareMergeTo() method must be called prior to this method to place all
2497 * involved to necessary states and perform other consistency checks.
2498 *
2499 * If @a aWait is @c true then this method will perform the operation on the
2500 * calling thread and will not return to the caller until the operation is
2501 * completed. When this method succeeds, all intermediate hard disk objects in
2502 * the chain will be uninitialized, the state of the target hard disk (and all
2503 * involved extra hard disks) will be restored and @a aChain will be deleted.
2504 * Note that this (source) hard disk is not uninitialized because of possible
2505 * AutoCaller instances held by the caller of this method on the current thread.
2506 * It's therefore the responsibility of the caller to call HardDisk::uninit()
2507 * after releasing all callers in this case!
2508 *
2509 * If @a aWait is @c false then this method will crea,te a thread to perform the
2510 * create operation asynchronously and will return immediately. If the operation
2511 * succeeds, the thread will uninitialize the source hard disk object and all
2512 * intermediate hard disk objects in the chain, reset the state of the target
2513 * hard disk (and all involved extra hard disks) and delete @a aChain. If the
2514 * operation fails, the thread will only reset the states of all involved hard
2515 * disks and delete @a aChain.
2516 *
2517 * When this method fails (regardless of the @a aWait mode), it is a caller's
2518 * responsiblity to undo state changes and delete @a aChain using
2519 * #cancelMergeTo().
2520 *
2521 * If @a aProgress is not NULL but the object it points to is @c null then a new
2522 * progress object will be created and assigned to @a *aProgress on success,
2523 * otherwise the existing progress object is used. If Progress is NULL, then no
2524 * progress object is created/used at all. Note that @a aProgress cannot be
2525 * NULL when @a aWait is @c false (this method will assert in this case).
2526 *
2527 * @param aChain Merge chain created by #prepareMergeTo().
2528 * @param aProgress Where to find/store a Progress object to track operation
2529 * completion.
2530 * @param aWait @c true if this method should block instead of creating
2531 * an asynchronous thread.
2532 *
2533 * @note Locks the branch lock for writing. Locks the hard disks from the chain
2534 * for writing.
2535 */
2536HRESULT HardDisk::mergeTo(MergeChain *aChain,
2537 ComObjPtr <Progress> *aProgress,
2538 bool aWait)
2539{
2540 AssertReturn (aChain != NULL, E_FAIL);
2541 AssertReturn (aProgress != NULL || aWait == true, E_FAIL);
2542
2543 AutoCaller autoCaller (this);
2544 CheckComRCReturnRC (autoCaller.rc());
2545
2546 HRESULT rc = S_OK;
2547
2548 ComObjPtr <Progress> progress;
2549
2550 if (aProgress != NULL)
2551 {
2552 /* use the existing progress object... */
2553 progress = *aProgress;
2554
2555 /* ...but create a new one if it is null */
2556 if (progress.isNull())
2557 {
2558 AutoReadLock alock (this);
2559
2560 progress.createObject();
2561 rc = progress->init (mVirtualBox, static_cast<IHardDisk*>(this),
2562 BstrFmt (tr ("Merging hard disk '%s' to '%s'"),
2563 name().raw(), aChain->target()->name().raw()),
2564 FALSE /* aCancelable */);
2565 CheckComRCReturnRC (rc);
2566 }
2567 }
2568
2569 /* setup task object and thread to carry out the operation
2570 * asynchronously */
2571
2572 std::auto_ptr <Task> task (new Task (this, progress, Task::Merge));
2573 AssertComRCReturnRC (task->autoCaller.rc());
2574
2575 task->setData (aChain);
2576
2577 /* Note: task owns aChain (will delete it when not needed) in all cases
2578 * except when @a aWait is @c true and runNow() fails -- in this case
2579 * aChain will be left away because cancelMergeTo() will be applied by the
2580 * caller on it as it is required in the documentation above */
2581
2582 if (aWait)
2583 {
2584 rc = task->runNow();
2585 }
2586 else
2587 {
2588 rc = task->startThread();
2589 CheckComRCReturnRC (rc);
2590 }
2591
2592 /* task is now owned (or already deleted) by taskThread() so release it */
2593 task.release();
2594
2595 if (aProgress != NULL)
2596 {
2597 /* return progress to the caller */
2598 *aProgress = progress;
2599 }
2600
2601 return rc;
2602}
2603
2604/**
2605 * Undoes what #prepareMergeTo() did. Must be called if #mergeTo() is not called
2606 * or fails. Frees memory occupied by @a aChain.
2607 *
2608 * @param aChain Merge chain created by #prepareMergeTo().
2609 *
2610 * @note Locks the hard disks from the chain for writing.
2611 */
2612void HardDisk::cancelMergeTo (MergeChain *aChain)
2613{
2614 AutoCaller autoCaller (this);
2615 AssertComRCReturnVoid (autoCaller.rc());
2616
2617 AssertReturnVoid (aChain != NULL);
2618
2619 /* the destructor will do the thing */
2620 delete aChain;
2621}
2622
2623// private methods
2624////////////////////////////////////////////////////////////////////////////////
2625
2626/**
2627 * Sets the value of m.location and calculates the value of m.locationFull.
2628 *
2629 * Reimplements MediumBase::setLocation() to specially treat non-FS-path
2630 * locations and to prepend the default hard disk folder if the given location
2631 * string does not contain any path information at all.
2632 *
2633 * Also, if the specified location is a file path that ends with '/' then the
2634 * file name part will be generated by this method automatically in the format
2635 * '{<uuid>}.<ext>' where <uuid> is a fresh UUID that this method will generate
2636 * and assign to this medium, and <ext> is the default extension for this
2637 * medium's storage format. Note that this procedure requires the media state to
2638 * be NotCreated and will return a faiulre otherwise.
2639 *
2640 * @param aLocation Location of the storage unit. If the locaiton is a FS-path,
2641 * then it can be relative to the VirtualBox home directory.
2642 *
2643 * @note Must be called from under this object's write lock.
2644 */
2645HRESULT HardDisk::setLocation (CBSTR aLocation)
2646{
2647 /// @todo so far, we assert but later it makes sense to support null
2648 /// locations for hard disks that are not yet created fail to create a
2649 /// storage unit instead
2650 CheckComArgStrNotEmptyOrNull (aLocation);
2651
2652 AutoCaller autoCaller (this);
2653 AssertComRCReturnRC (autoCaller.rc());
2654
2655 /* formatObj may be null only when initializing from an existing path and
2656 * no format is known yet */
2657 AssertReturn ((!mm.format.isNull() && !mm.formatObj.isNull()) ||
2658 (autoCaller.state() == InInit &&
2659 m.state != MediaState_NotCreated && m.id.isEmpty() &&
2660 mm.format.isNull() && mm.formatObj.isNull()),
2661 E_FAIL);
2662
2663 /* are we dealing with a new hard disk constructed using the existing
2664 * location? */
2665 bool isImport = mm.format.isNull();
2666
2667 if (isImport ||
2668 (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File))
2669 {
2670 Guid id;
2671
2672 Utf8Str location (aLocation);
2673
2674 if (m.state == MediaState_NotCreated)
2675 {
2676 /* must be a file (formatObj must be already known) */
2677 Assert (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File);
2678
2679 if (RTPathFilename (location) == NULL)
2680 {
2681 /* no file name is given (either an empty string or ends with a
2682 * slash), generate a new UUID + file name if the state allows
2683 * this */
2684
2685 ComAssertMsgRet (!mm.formatObj->fileExtensions().empty(),
2686 ("Must be at least one extension if it is "
2687 "HardDiskFormatCapabilities_File\n"),
2688 E_FAIL);
2689
2690 Bstr ext = mm.formatObj->fileExtensions().front();
2691 ComAssertMsgRet (!ext.isEmpty(),
2692 ("Default extension must not be empty\n"),
2693 E_FAIL);
2694
2695 id.create();
2696
2697 location = Utf8StrFmt ("%s{%RTuuid}.%ls",
2698 location.raw(), id.raw(), ext.raw());
2699 }
2700 }
2701
2702 /* append the default folder if no path is given */
2703 if (!RTPathHavePath (location))
2704 {
2705 AutoReadLock propsLock (mVirtualBox->systemProperties());
2706 location = Utf8StrFmt ("%ls%c%s",
2707 mVirtualBox->systemProperties()->defaultHardDiskFolder().raw(),
2708 RTPATH_DELIMITER,
2709 location.raw());
2710 }
2711
2712 /* get the full file name */
2713 Utf8Str locationFull;
2714 int vrc = mVirtualBox->calculateFullPath (location, locationFull);
2715 if (RT_FAILURE (vrc))
2716 return setError (VBOX_E_FILE_ERROR,
2717 tr ("Invalid hard disk storage file location '%s' (%Rrc)"),
2718 location.raw(), vrc);
2719
2720 /* detect the backend from the storage unit if importing */
2721 if (isImport)
2722 {
2723 char *backendName = NULL;
2724
2725 /* is it a file? */
2726 {
2727 RTFILE file;
2728 vrc = RTFileOpen (&file, locationFull, RTFILE_O_READ);
2729 if (RT_SUCCESS (vrc))
2730 RTFileClose (file);
2731 }
2732 if (RT_SUCCESS (vrc))
2733 {
2734 vrc = VDGetFormat (locationFull, &backendName);
2735 }
2736 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
2737 {
2738 /* assume it's not a file, restore the original location */
2739 location = locationFull = aLocation;
2740 vrc = VDGetFormat (locationFull, &backendName);
2741 }
2742
2743 if (RT_FAILURE (vrc))
2744 return setError (VBOX_E_IPRT_ERROR,
2745 tr ("Could not get the storage format of the hard disk "
2746 "'%s' (%Rrc)"), locationFull.raw(), vrc);
2747
2748 ComAssertRet (backendName != NULL && *backendName != '\0', E_FAIL);
2749
2750 HRESULT rc = setFormat (Bstr (backendName));
2751 RTStrFree (backendName);
2752
2753 /* setFormat() must not fail since we've just used the backend so
2754 * the format object must be there */
2755 AssertComRCReturnRC (rc);
2756 }
2757
2758 /* is it still a file? */
2759 if (mm.formatObj->capabilities() & HardDiskFormatCapabilities_File)
2760 {
2761 m.location = location;
2762 m.locationFull = locationFull;
2763
2764 if (m.state == MediaState_NotCreated)
2765 {
2766 /* assign a new UUID (this UUID will be used when calling
2767 * VDCreateBase/VDCreateDiff as a wanted UUID). Note that we
2768 * also do that if we didn't generate it to make sure it is
2769 * either generated by us or reset to null */
2770 unconst (m.id) = id;
2771 }
2772 }
2773 else
2774 {
2775 m.location = locationFull;
2776 m.locationFull = locationFull;
2777 }
2778 }
2779 else
2780 {
2781 m.location = aLocation;
2782 m.locationFull = aLocation;
2783 }
2784
2785 return S_OK;
2786}
2787
2788/**
2789 * Checks that the format ID is valid and sets it on success.
2790 *
2791 * Note that this method will caller-reference the format object on success!
2792 * This reference must be released somewhere to let the HardDiskFormat object be
2793 * uninitialized.
2794 *
2795 * @note Must be called from under this object's write lock.
2796 */
2797HRESULT HardDisk::setFormat (CBSTR aFormat)
2798{
2799 /* get the format object first */
2800 {
2801 AutoReadLock propsLock (mVirtualBox->systemProperties());
2802
2803 unconst (mm.formatObj)
2804 = mVirtualBox->systemProperties()->hardDiskFormat (aFormat);
2805 if (mm.formatObj.isNull())
2806 return setError (E_INVALIDARG,
2807 tr ("Invalid hard disk storage format '%ls'"), aFormat);
2808
2809 /* reference the format permanently to prevent its unexpected
2810 * uninitialization */
2811 HRESULT rc = mm.formatObj->addCaller();
2812 AssertComRCReturnRC (rc);
2813
2814 /* get properties (preinsert them as keys in the map). Note that the
2815 * map doesn't grow over the object life time since the set of
2816 * properties is meant to be constant. */
2817
2818 Assert (mm.properties.empty());
2819
2820 for (HardDiskFormat::PropertyList::const_iterator it =
2821 mm.formatObj->properties().begin();
2822 it != mm.formatObj->properties().end();
2823 ++ it)
2824 {
2825 mm.properties.insert (std::make_pair (it->name, Bstr::Null));
2826 }
2827 }
2828
2829 unconst (mm.format) = aFormat;
2830
2831 return S_OK;
2832}
2833
2834/**
2835 * Queries information from the image file.
2836 *
2837 * As a result of this call, the accessibility state and data members such as
2838 * size and description will be updated with the current information.
2839 *
2840 * Reimplements MediumBase::queryInfo() to query hard disk information using the
2841 * VD backend interface.
2842 *
2843 * @note This method may block during a system I/O call that checks storage
2844 * accessibility.
2845 *
2846 * @note Locks treeLock() for reading and writing (for new diff media checked
2847 * for the first time). Locks mParent for reading. Locks this object for
2848 * writing.
2849 */
2850HRESULT HardDisk::queryInfo()
2851{
2852 AutoWriteLock alock (this);
2853
2854 AssertReturn (m.state == MediaState_Created ||
2855 m.state == MediaState_Inaccessible ||
2856 m.state == MediaState_LockedRead ||
2857 m.state == MediaState_LockedWrite,
2858 E_FAIL);
2859
2860 HRESULT rc = S_OK;
2861
2862 int vrc = VINF_SUCCESS;
2863
2864 /* check if a blocking queryInfo() call is in progress on some other thread,
2865 * and wait for it to finish if so instead of querying data ourselves */
2866 if (m.queryInfoSem != NIL_RTSEMEVENTMULTI)
2867 {
2868 Assert (m.state == MediaState_LockedRead);
2869
2870 ++ m.queryInfoCallers;
2871 alock.leave();
2872
2873 vrc = RTSemEventMultiWait (m.queryInfoSem, RT_INDEFINITE_WAIT);
2874
2875 alock.enter();
2876 -- m.queryInfoCallers;
2877
2878 if (m.queryInfoCallers == 0)
2879 {
2880 /* last waiting caller deletes the semaphore */
2881 RTSemEventMultiDestroy (m.queryInfoSem);
2882 m.queryInfoSem = NIL_RTSEMEVENTMULTI;
2883 }
2884
2885 AssertRC (vrc);
2886
2887 return S_OK;
2888 }
2889
2890 /* lazily create a semaphore for possible callers */
2891 vrc = RTSemEventMultiCreate (&m.queryInfoSem);
2892 ComAssertRCRet (vrc, E_FAIL);
2893
2894 bool tempStateSet = false;
2895 if (m.state != MediaState_LockedRead &&
2896 m.state != MediaState_LockedWrite)
2897 {
2898 /* Cause other methods to prevent any modifications before leaving the
2899 * lock. Note that clients will never see this temporary state change
2900 * since any COMGETTER(State) is (or will be) blocked until we finish
2901 * and restore the actual state. */
2902 m.state = MediaState_LockedRead;
2903 tempStateSet = true;
2904 }
2905
2906 /* leave the lock before a blocking operation */
2907 alock.leave();
2908
2909 bool success = false;
2910 Utf8Str lastAccessError;
2911
2912 try
2913 {
2914 Utf8Str location (m.locationFull);
2915
2916 /* are we dealing with a new hard disk constructed using the existing
2917 * location? */
2918 bool isImport = m.id.isEmpty();
2919
2920 PVBOXHDD hdd;
2921 vrc = VDCreate (mm.vdDiskIfaces, &hdd);
2922 ComAssertRCThrow (vrc, E_FAIL);
2923
2924 try
2925 {
2926 unsigned flags = VD_OPEN_FLAGS_INFO;
2927
2928 /* Note that we don't use VD_OPEN_FLAGS_READONLY when opening new
2929 * hard disks because that would prevent necessary modifications
2930 * when opening hard disks of some third-party formats for the first
2931 * time in VirtualBox (such as VMDK for which VDOpen() needs to
2932 * generate an UUID if it is missing) */
2933 if (!isImport)
2934 flags |= VD_OPEN_FLAGS_READONLY;
2935
2936 vrc = VDOpen (hdd, Utf8Str (mm.format), location, flags,
2937 mm.vdDiskIfaces);
2938 if (RT_FAILURE (vrc))
2939 {
2940 lastAccessError = Utf8StrFmt (
2941 tr ("Could not open the hard disk '%ls'%s"),
2942 m.locationFull.raw(), vdError (vrc).raw());
2943 throw S_OK;
2944 }
2945
2946 if (mm.formatObj->capabilities() & HardDiskFormatCapabilities_Uuid)
2947 {
2948 /* check the UUID */
2949 RTUUID uuid;
2950 vrc = VDGetUuid (hdd, 0, &uuid);
2951 ComAssertRCThrow (vrc, E_FAIL);
2952
2953 if (isImport)
2954 {
2955 unconst (m.id) = uuid;
2956 }
2957 else
2958 {
2959 Assert (!m.id.isEmpty());
2960
2961 if (m.id != uuid)
2962 {
2963 lastAccessError = Utf8StrFmt (
2964 tr ("UUID {%RTuuid} of the hard disk '%ls' does "
2965 "not match the value {%RTuuid} stored in the "
2966 "media registry ('%ls')"),
2967 &uuid, m.locationFull.raw(), m.id.raw(),
2968 mVirtualBox->settingsFileName().raw());
2969 throw S_OK;
2970 }
2971 }
2972 }
2973 else
2974 {
2975 /* the backend does not support storing UUIDs within the
2976 * underlying storage so use what we store in XML */
2977
2978 /* generate an UUID for an imported UUID-less hard disk */
2979 if (isImport)
2980 unconst (m.id).create();
2981 }
2982
2983 /* check the type */
2984 VDIMAGETYPE type;
2985 vrc = VDGetImageType (hdd, 0, &type);
2986 ComAssertRCThrow (vrc, E_FAIL);
2987
2988 if (type == VD_IMAGE_TYPE_DIFF)
2989 {
2990 RTUUID parentId;
2991 vrc = VDGetParentUuid (hdd, 0, &parentId);
2992 ComAssertRCThrow (vrc, E_FAIL);
2993
2994 if (isImport)
2995 {
2996 /* the parent must be known to us. Note that we freely
2997 * call locking methods of mVirtualBox and parent from the
2998 * write lock (breaking the {parent,child} lock order)
2999 * because there may be no concurrent access to the just
3000 * opened hard disk on ther threads yet (and init() will
3001 * fail if this method reporst MediaState_Inaccessible) */
3002
3003 Guid id = parentId;
3004 ComObjPtr<HardDisk> parent;
3005 rc = mVirtualBox->findHardDisk(&id, NULL,
3006 false /* aSetError */,
3007 &parent);
3008 if (FAILED (rc))
3009 {
3010 lastAccessError = Utf8StrFmt (
3011 tr ("Parent hard disk with UUID {%RTuuid} of the "
3012 "hard disk '%ls' is not found in the media "
3013 "registry ('%ls')"),
3014 &parentId, m.locationFull.raw(),
3015 mVirtualBox->settingsFileName().raw());
3016 throw S_OK;
3017 }
3018
3019 /* deassociate from VirtualBox, associate with parent */
3020
3021 mVirtualBox->removeDependentChild (this);
3022
3023 /* we set mParent & children() */
3024 AutoWriteLock treeLock (this->treeLock());
3025
3026 Assert (mParent.isNull());
3027 mParent = parent;
3028 mParent->addDependentChild (this);
3029 }
3030 else
3031 {
3032 /* we access mParent */
3033 AutoReadLock treeLock (this->treeLock());
3034
3035 /* check that parent UUIDs match. Note that there's no need
3036 * for the parent's AutoCaller (our lifetime is bound to
3037 * it) */
3038
3039 if (mParent.isNull())
3040 {
3041 lastAccessError = Utf8StrFmt (
3042 tr ("Hard disk '%ls' is differencing but it is not "
3043 "associated with any parent hard disk in the "
3044 "media registry ('%ls')"),
3045 m.locationFull.raw(),
3046 mVirtualBox->settingsFileName().raw());
3047 throw S_OK;
3048 }
3049
3050 AutoReadLock parentLock (mParent);
3051 if (mParent->state() != MediaState_Inaccessible &&
3052 mParent->id() != parentId)
3053 {
3054 lastAccessError = Utf8StrFmt (
3055 tr ("Parent UUID {%RTuuid} of the hard disk '%ls' "
3056 "does not match UUID {%RTuuid} of its parent "
3057 "hard disk stored in the media registry ('%ls')"),
3058 &parentId, m.locationFull.raw(),
3059 mParent->id().raw(),
3060 mVirtualBox->settingsFileName().raw());
3061 throw S_OK;
3062 }
3063
3064 /// @todo NEWMEDIA what to do if the parent is not
3065 /// accessible while the diff is? Probably, nothing. The
3066 /// real code will detect the mismatch anyway.
3067 }
3068 }
3069
3070 m.size = VDGetFileSize (hdd, 0);
3071 mm.logicalSize = VDGetSize (hdd, 0) / _1M;
3072
3073 success = true;
3074 }
3075 catch (HRESULT aRC)
3076 {
3077 rc = aRC;
3078 }
3079
3080 VDDestroy (hdd);
3081
3082 }
3083 catch (HRESULT aRC)
3084 {
3085 rc = aRC;
3086 }
3087
3088 alock.enter();
3089
3090 if (success)
3091 m.lastAccessError.setNull();
3092 else
3093 {
3094 m.lastAccessError = lastAccessError;
3095 LogWarningFunc (("'%ls' is not accessible (error='%ls', "
3096 "rc=%Rhrc, vrc=%Rrc)\n",
3097 m.locationFull.raw(), m.lastAccessError.raw(),
3098 rc, vrc));
3099 }
3100
3101 /* inform other callers if there are any */
3102 if (m.queryInfoCallers > 0)
3103 {
3104 RTSemEventMultiSignal (m.queryInfoSem);
3105 }
3106 else
3107 {
3108 /* delete the semaphore ourselves */
3109 RTSemEventMultiDestroy (m.queryInfoSem);
3110 m.queryInfoSem = NIL_RTSEMEVENTMULTI;
3111 }
3112
3113 if (tempStateSet)
3114 {
3115 /* Set the proper state according to the result of the check */
3116 if (success)
3117 m.state = MediaState_Created;
3118 else
3119 m.state = MediaState_Inaccessible;
3120 }
3121 else
3122 {
3123 /* we're locked, use a special field to store the result */
3124 m.accessibleInLock = success;
3125 }
3126
3127 return rc;
3128}
3129
3130/**
3131 * @note Called from this object's AutoMayUninitSpan and from under mVirtualBox
3132 * write lock.
3133 *
3134 * @note Also reused by HardDisk::Reset().
3135 *
3136 * @note Locks treeLock() for reading.
3137 */
3138HRESULT HardDisk::canClose()
3139{
3140 /* we access children */
3141 AutoReadLock treeLock (this->treeLock());
3142
3143 if (children().size() != 0)
3144 return setError (E_FAIL,
3145 tr ("Hard disk '%ls' has %d child hard disks"),
3146 children().size());
3147
3148 return S_OK;
3149}
3150
3151/**
3152 * @note Called from within this object's AutoWriteLock.
3153 */
3154HRESULT HardDisk::canAttach(const Guid &aMachineId,
3155 const Guid &aSnapshotId)
3156{
3157 if (mm.numCreateDiffTasks > 0)
3158 return setError (E_FAIL,
3159 tr ("One or more differencing child hard disks are "
3160 "being created for the hard disk '%ls' (%u)"),
3161 m.locationFull.raw(), mm.numCreateDiffTasks);
3162
3163 return S_OK;
3164}
3165
3166/**
3167 * @note Called from within this object's AutoMayUninitSpan (or AutoCaller) and
3168 * from under mVirtualBox write lock.
3169 *
3170 * @note Locks treeLock() for writing.
3171 */
3172HRESULT HardDisk::unregisterWithVirtualBox()
3173{
3174 /* Note that we need to de-associate ourselves from the parent to let
3175 * unregisterHardDisk() properly save the registry */
3176
3177 /* we modify mParent and access children */
3178 AutoWriteLock treeLock (this->treeLock());
3179
3180 const ComObjPtr<HardDisk, ComWeakRef> parent = mParent;
3181
3182 AssertReturn (children().size() == 0, E_FAIL);
3183
3184 if (!mParent.isNull())
3185 {
3186 /* deassociate from the parent, associate with VirtualBox */
3187 mVirtualBox->addDependentChild (this);
3188 mParent->removeDependentChild (this);
3189 mParent.setNull();
3190 }
3191
3192 HRESULT rc = mVirtualBox->unregisterHardDisk(this);
3193
3194 if (FAILED (rc))
3195 {
3196 if (!parent.isNull())
3197 {
3198 /* re-associate with the parent as we are still relatives in the
3199 * registry */
3200 mParent = parent;
3201 mParent->addDependentChild (this);
3202 mVirtualBox->removeDependentChild (this);
3203 }
3204 }
3205
3206 return rc;
3207}
3208
3209/**
3210 * Returns the last error message collected by the vdErrorCall callback and
3211 * resets it.
3212 *
3213 * The error message is returned prepended with a dot and a space, like this:
3214 * <code>
3215 * ". <error_text> (%Rrc)"
3216 * </code>
3217 * to make it easily appendable to a more general error message. The @c %Rrc
3218 * format string is given @a aVRC as an argument.
3219 *
3220 * If there is no last error message collected by vdErrorCall or if it is a
3221 * null or empty string, then this function returns the following text:
3222 * <code>
3223 * " (%Rrc)"
3224 * </code>
3225 *
3226 * @note Doesn't do any object locking; it is assumed that the caller makes sure
3227 * the callback isn't called by more than one thread at a time.
3228 *
3229 * @param aVRC VBox error code to use when no error message is provided.
3230 */
3231Utf8Str HardDisk::vdError (int aVRC)
3232{
3233 Utf8Str error;
3234
3235 if (mm.vdError.isEmpty())
3236 error = Utf8StrFmt (" (%Rrc)", aVRC);
3237 else
3238 error = Utf8StrFmt (".\n%s", mm.vdError.raw());
3239
3240 mm.vdError.setNull();
3241
3242 return error;
3243}
3244
3245/**
3246 * Error message callback.
3247 *
3248 * Puts the reported error message to the mm.vdError field.
3249 *
3250 * @note Doesn't do any object locking; it is assumed that the caller makes sure
3251 * the callback isn't called by more than one thread at a time.
3252 *
3253 * @param pvUser The opaque data passed on container creation.
3254 * @param rc The VBox error code.
3255 * @param RT_SRC_POS_DECL Use RT_SRC_POS.
3256 * @param pszFormat Error message format string.
3257 * @param va Error message arguments.
3258 */
3259/*static*/
3260DECLCALLBACK(void) HardDisk::vdErrorCall(void *pvUser, int rc, RT_SRC_POS_DECL,
3261 const char *pszFormat, va_list va)
3262{
3263 HardDisk *that = static_cast<HardDisk*>(pvUser);
3264 AssertReturnVoid (that != NULL);
3265
3266 if (that->mm.vdError.isEmpty())
3267 that->mm.vdError =
3268 Utf8StrFmt ("%s (%Rrc)", Utf8StrFmtVA (pszFormat, va).raw(), rc);
3269 else
3270 that->mm.vdError =
3271 Utf8StrFmt ("%s.\n%s (%Rrc)", that->mm.vdError.raw(),
3272 Utf8StrFmtVA (pszFormat, va).raw(), rc);
3273}
3274
3275/**
3276 * PFNVMPROGRESS callback handler for Task operations.
3277 *
3278 * @param uPercent Completetion precentage (0-100).
3279 * @param pvUser Pointer to the Progress instance.
3280 */
3281/*static*/
3282DECLCALLBACK(int) HardDisk::vdProgressCall(PVM /* pVM */, unsigned uPercent,
3283 void *pvUser)
3284{
3285 HardDisk *that = static_cast<HardDisk*>(pvUser);
3286 AssertReturn (that != NULL, VERR_GENERAL_FAILURE);
3287
3288 if (that->mm.vdProgress != NULL)
3289 {
3290 /* update the progress object, capping it at 99% as the final percent
3291 * is used for additional operations like setting the UUIDs and similar. */
3292 that->mm.vdProgress->notifyProgress (RT_MIN (uPercent, 99));
3293 }
3294
3295 return VINF_SUCCESS;
3296}
3297
3298/* static */
3299DECLCALLBACK(bool) HardDisk::vdConfigAreKeysValid (void *pvUser,
3300 const char *pszzValid)
3301{
3302 HardDisk *that = static_cast<HardDisk*>(pvUser);
3303 AssertReturn (that != NULL, false);
3304
3305 /* we always return true since the only keys we have are those found in
3306 * VDBACKENDINFO */
3307 return true;
3308}
3309
3310/* static */
3311DECLCALLBACK(int) HardDisk::vdConfigQuerySize(void *pvUser, const char *pszName,
3312 size_t *pcbValue)
3313{
3314 AssertReturn (VALID_PTR (pcbValue), VERR_INVALID_POINTER);
3315
3316 HardDisk *that = static_cast<HardDisk*>(pvUser);
3317 AssertReturn (that != NULL, VERR_GENERAL_FAILURE);
3318
3319 Data::PropertyMap::const_iterator it =
3320 that->mm.properties.find (Bstr (pszName));
3321 if (it == that->mm.properties.end())
3322 return VERR_CFGM_VALUE_NOT_FOUND;
3323
3324 /* we interpret null values as "no value" in HardDisk */
3325 if (it->second.isNull())
3326 return VERR_CFGM_VALUE_NOT_FOUND;
3327
3328 *pcbValue = it->second.length() + 1 /* include terminator */;
3329
3330 return VINF_SUCCESS;
3331}
3332
3333/* static */
3334DECLCALLBACK(int) HardDisk::vdConfigQuery (void *pvUser, const char *pszName,
3335 char *pszValue, size_t cchValue)
3336{
3337 AssertReturn (VALID_PTR (pszValue), VERR_INVALID_POINTER);
3338
3339 HardDisk *that = static_cast<HardDisk*>(pvUser);
3340 AssertReturn (that != NULL, VERR_GENERAL_FAILURE);
3341
3342 Data::PropertyMap::const_iterator it =
3343 that->mm.properties.find (Bstr (pszName));
3344 if (it == that->mm.properties.end())
3345 return VERR_CFGM_VALUE_NOT_FOUND;
3346
3347 Utf8Str value = it->second;
3348 if (value.length() >= cchValue)
3349 return VERR_CFGM_NOT_ENOUGH_SPACE;
3350
3351 /* we interpret null values as "no value" in HardDisk */
3352 if (it->second.isNull())
3353 return VERR_CFGM_VALUE_NOT_FOUND;
3354
3355 memcpy (pszValue, value, value.length() + 1);
3356
3357 return VINF_SUCCESS;
3358}
3359
3360/**
3361 * Thread function for time-consuming tasks.
3362 *
3363 * The Task structure passed to @a pvUser must be allocated using new and will
3364 * be freed by this method before it returns.
3365 *
3366 * @param pvUser Pointer to the Task instance.
3367 */
3368/* static */
3369DECLCALLBACK(int) HardDisk::taskThread (RTTHREAD thread, void *pvUser)
3370{
3371 std::auto_ptr <Task> task (static_cast <Task *> (pvUser));
3372 AssertReturn (task.get(), VERR_GENERAL_FAILURE);
3373
3374 bool isAsync = thread != NIL_RTTHREAD;
3375
3376 HardDisk *that = task->that;
3377
3378 /// @todo ugly hack, fix ComAssert... later
3379 #define setError that->setError
3380
3381 /* Note: no need in AutoCaller because Task does that */
3382
3383 LogFlowFuncEnter();
3384 LogFlowFunc (("{%p}: operation=%d\n", that, task->operation));
3385
3386 HRESULT rc = S_OK;
3387
3388 switch (task->operation)
3389 {
3390 ////////////////////////////////////////////////////////////////////////
3391
3392 case Task::CreateDynamic:
3393 case Task::CreateFixed:
3394 {
3395 /* The lock is also used as a signal from the task initiator (which
3396 * releases it only after RTThreadCreate()) that we can start the job */
3397 AutoWriteLock thatLock (that);
3398
3399 /* these parameters we need after creation */
3400 uint64_t size = 0, logicalSize = 0;
3401
3402 /* The object may request a specific UUID (through a special form of
3403 * the setLocation() argument). Otherwise we have to generate it */
3404 Guid id = that->m.id;
3405 bool generateUuid = id.isEmpty();
3406 if (generateUuid)
3407 {
3408 id.create();
3409 /* VirtualBox::registerHardDisk() will need UUID */
3410 unconst (that->m.id) = id;
3411 }
3412
3413 try
3414 {
3415 PVBOXHDD hdd;
3416 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3417 ComAssertRCThrow (vrc, E_FAIL);
3418
3419 Utf8Str format (that->mm.format);
3420 Utf8Str location (that->m.locationFull);
3421 uint64_t capabilities = that->mm.formatObj->capabilities();
3422
3423 /* unlock before the potentially lengthy operation */
3424 Assert (that->m.state == MediaState_Creating);
3425 thatLock.leave();
3426
3427 try
3428 {
3429 /* ensure the directory exists */
3430 rc = VirtualBox::ensureFilePathExists (location);
3431 CheckComRCThrowRC (rc);
3432
3433 PDMMEDIAGEOMETRY geo = { 0 }; /* auto-detect */
3434
3435 /* needed for vdProgressCallback */
3436 that->mm.vdProgress = task->progress;
3437
3438 vrc = VDCreateBase (hdd, format, location,
3439 task->operation == Task::CreateDynamic ?
3440 VD_IMAGE_TYPE_NORMAL :
3441 VD_IMAGE_TYPE_FIXED,
3442 task->d.size * _1M,
3443 task->d.variant,
3444 NULL, &geo, &geo, id.raw(),
3445 VD_OPEN_FLAGS_NORMAL,
3446 NULL, that->mm.vdDiskIfaces);
3447
3448 if (RT_FAILURE (vrc))
3449 {
3450 throw setError (E_FAIL,
3451 tr ("Could not create the hard disk storage "
3452 "unit '%s'%s"),
3453 location.raw(), that->vdError (vrc).raw());
3454 }
3455
3456 size = VDGetFileSize (hdd, 0);
3457 logicalSize = VDGetSize (hdd, 0) / _1M;
3458 }
3459 catch (HRESULT aRC) { rc = aRC; }
3460
3461 VDDestroy (hdd);
3462 }
3463 catch (HRESULT aRC) { rc = aRC; }
3464
3465 if (SUCCEEDED (rc))
3466 {
3467 /* register with mVirtualBox as the last step and move to
3468 * Created state only on success (leaving an orphan file is
3469 * better than breaking media registry consistency) */
3470 rc = that->mVirtualBox->registerHardDisk(that);
3471 }
3472
3473 thatLock.maybeEnter();
3474
3475 if (SUCCEEDED (rc))
3476 {
3477 that->m.state = MediaState_Created;
3478
3479 that->m.size = size;
3480 that->mm.logicalSize = logicalSize;
3481 }
3482 else
3483 {
3484 /* back to NotCreated on failiure */
3485 that->m.state = MediaState_NotCreated;
3486
3487 /* reset UUID to prevent it from being reused next time */
3488 if (generateUuid)
3489 unconst (that->m.id).clear();
3490 }
3491
3492 break;
3493 }
3494
3495 ////////////////////////////////////////////////////////////////////////
3496
3497 case Task::CreateDiff:
3498 {
3499 ComObjPtr<HardDisk> &target = task->d.target;
3500
3501 /* Lock both in {parent,child} order. The lock is also used as a
3502 * signal from the task initiator (which releases it only after
3503 * RTThreadCreate()) that we can start the job*/
3504 AutoMultiWriteLock2 thatLock (that, target);
3505
3506 uint64_t size = 0, logicalSize = 0;
3507
3508 /* The object may request a specific UUID (through a special form of
3509 * the setLocation() argument). Otherwise we have to generate it */
3510 Guid targetId = target->m.id;
3511 bool generateUuid = targetId.isEmpty();
3512 if (generateUuid)
3513 {
3514 targetId.create();
3515 /* VirtualBox::registerHardDisk() will need UUID */
3516 unconst (target->m.id) = targetId;
3517 }
3518
3519 try
3520 {
3521 PVBOXHDD hdd;
3522 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3523 ComAssertRCThrow (vrc, E_FAIL);
3524
3525 Guid id = that->m.id;
3526 Utf8Str format (that->mm.format);
3527 Utf8Str location (that->m.locationFull);
3528
3529 Utf8Str targetFormat (target->mm.format);
3530 Utf8Str targetLocation (target->m.locationFull);
3531
3532 Assert (target->m.state == MediaState_Creating);
3533
3534 /* Note: MediaState_LockedWrite is ok when taking an online
3535 * snapshot */
3536 Assert (that->m.state == MediaState_LockedRead ||
3537 that->m.state == MediaState_LockedWrite);
3538
3539 /* unlock before the potentially lengthy operation */
3540 thatLock.leave();
3541
3542 try
3543 {
3544 vrc = VDOpen (hdd, format, location,
3545 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
3546 that->mm.vdDiskIfaces);
3547 if (RT_FAILURE (vrc))
3548 {
3549 throw setError (E_FAIL,
3550 tr ("Could not open the hard disk storage "
3551 "unit '%s'%s"),
3552 location.raw(), that->vdError (vrc).raw());
3553 }
3554
3555 /* ensure the target directory exists */
3556 rc = VirtualBox::ensureFilePathExists (targetLocation);
3557 CheckComRCThrowRC (rc);
3558
3559 /* needed for vdProgressCallback */
3560 that->mm.vdProgress = task->progress;
3561
3562 vrc = VDCreateDiff (hdd, targetFormat, targetLocation,
3563 task->d.variant,
3564 NULL, targetId.raw(),
3565 id.raw(),
3566 VD_OPEN_FLAGS_NORMAL,
3567 target->mm.vdDiskIfaces,
3568 that->mm.vdDiskIfaces);
3569
3570 that->mm.vdProgress = NULL;
3571
3572 if (RT_FAILURE (vrc))
3573 {
3574 throw setError (E_FAIL,
3575 tr ("Could not create the differencing hard disk "
3576 "storage unit '%s'%s"),
3577 targetLocation.raw(), that->vdError (vrc).raw());
3578 }
3579
3580 size = VDGetFileSize (hdd, 1);
3581 logicalSize = VDGetSize (hdd, 1) / _1M;
3582 }
3583 catch (HRESULT aRC) { rc = aRC; }
3584
3585 VDDestroy (hdd);
3586 }
3587 catch (HRESULT aRC) { rc = aRC; }
3588
3589 if (SUCCEEDED (rc))
3590 {
3591 /* we set mParent & children() (note that thatLock is released
3592 * here), but lock VirtualBox first to follow the rule */
3593 AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
3594 that->treeLock());
3595
3596 Assert (target->mParent.isNull());
3597
3598 /* associate the child with the parent and deassociate from
3599 * VirtualBox */
3600 target->mParent = that;
3601 that->addDependentChild (target);
3602 target->mVirtualBox->removeDependentChild (target);
3603
3604 /* diffs for immutable hard disks are auto-reset by default */
3605 target->mm.autoReset =
3606 that->root()->mm.type == HardDiskType_Immutable ?
3607 TRUE : FALSE;
3608
3609 /* register with mVirtualBox as the last step and move to
3610 * Created state only on success (leaving an orphan file is
3611 * better than breaking media registry consistency) */
3612 rc = that->mVirtualBox->registerHardDisk (target);
3613
3614 if (FAILED (rc))
3615 {
3616 /* break the parent association on failure to register */
3617 target->mVirtualBox->addDependentChild (target);
3618 that->removeDependentChild (target);
3619 target->mParent.setNull();
3620 }
3621 }
3622
3623 thatLock.maybeEnter();
3624
3625 if (SUCCEEDED (rc))
3626 {
3627 target->m.state = MediaState_Created;
3628
3629 target->m.size = size;
3630 target->mm.logicalSize = logicalSize;
3631 }
3632 else
3633 {
3634 /* back to NotCreated on failiure */
3635 target->m.state = MediaState_NotCreated;
3636
3637 target->mm.autoReset = FALSE;
3638
3639 /* reset UUID to prevent it from being reused next time */
3640 if (generateUuid)
3641 unconst (target->m.id).clear();
3642 }
3643
3644 if (isAsync)
3645 {
3646 /* unlock ourselves when done (unless in MediaState_LockedWrite
3647 * state because of taking the online snapshot*/
3648 if (that->m.state != MediaState_LockedWrite)
3649 {
3650 HRESULT rc2 = that->UnlockRead (NULL);
3651 AssertComRC (rc2);
3652 }
3653 }
3654
3655 /* deregister the task registered in createDiffStorage() */
3656 Assert (that->mm.numCreateDiffTasks != 0);
3657 -- that->mm.numCreateDiffTasks;
3658
3659 /* Note that in sync mode, it's the caller's responsibility to
3660 * unlock the hard disk */
3661
3662 break;
3663 }
3664
3665 ////////////////////////////////////////////////////////////////////////
3666
3667 case Task::Merge:
3668 {
3669 /* The lock is also used as a signal from the task initiator (which
3670 * releases it only after RTThreadCreate()) that we can start the
3671 * job. We don't actually need the lock for anything else since the
3672 * object is protected by MediaState_Deleting and we don't modify
3673 * its sensitive fields below */
3674 {
3675 AutoWriteLock thatLock (that);
3676 }
3677
3678 MergeChain *chain = task->d.chain.get();
3679
3680#if 0
3681 LogFlow (("*** MERGE forward = %RTbool\n", chain->isForward()));
3682#endif
3683
3684 try
3685 {
3686 PVBOXHDD hdd;
3687 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3688 ComAssertRCThrow (vrc, E_FAIL);
3689
3690 try
3691 {
3692 /* open all hard disks in the chain (they are in the
3693 * {parent,child} order in there. Note that we don't lock
3694 * objects in this chain since they must be in states
3695 * (Deleting and LockedWrite) that prevent from chaning
3696 * their format and location fields from outside. */
3697
3698 for (MergeChain::const_iterator it = chain->begin();
3699 it != chain->end(); ++ it)
3700 {
3701 /* complex sanity (sane complexity) */
3702 Assert ((chain->isForward() &&
3703 ((*it != chain->back() &&
3704 (*it)->m.state == MediaState_Deleting) ||
3705 (*it == chain->back() &&
3706 (*it)->m.state == MediaState_LockedWrite))) ||
3707 (!chain->isForward() &&
3708 ((*it != chain->front() &&
3709 (*it)->m.state == MediaState_Deleting) ||
3710 (*it == chain->front() &&
3711 (*it)->m.state == MediaState_LockedWrite))));
3712
3713 Assert (*it == chain->target() ||
3714 (*it)->m.backRefs.size() == 0);
3715
3716 /* open the first image with VDOPEN_FLAGS_INFO because
3717 * it's not necessarily the base one */
3718 vrc = VDOpen (hdd, Utf8Str ((*it)->mm.format),
3719 Utf8Str ((*it)->m.locationFull),
3720 it == chain->begin() ?
3721 VD_OPEN_FLAGS_INFO : 0,
3722 (*it)->mm.vdDiskIfaces);
3723 if (RT_FAILURE (vrc))
3724 throw vrc;
3725#if 0
3726 LogFlow (("*** MERGE disk = %ls\n",
3727 (*it)->m.locationFull.raw()));
3728#endif
3729 }
3730
3731 /* needed for vdProgressCallback */
3732 that->mm.vdProgress = task->progress;
3733
3734 unsigned start = chain->isForward() ?
3735 0 : chain->size() - 1;
3736 unsigned end = chain->isForward() ?
3737 chain->size() - 1 : 0;
3738#if 0
3739 LogFlow (("*** MERGE from %d to %d\n", start, end));
3740#endif
3741 vrc = VDMerge (hdd, start, end, that->mm.vdDiskIfaces);
3742
3743 that->mm.vdProgress = NULL;
3744
3745 if (RT_FAILURE (vrc))
3746 throw vrc;
3747
3748 /* update parent UUIDs */
3749 /// @todo VDMerge should be taught to do so, including the
3750 /// multiple children case
3751 if (chain->isForward())
3752 {
3753 /* target's UUID needs to be updated (note that target
3754 * is the only image in the container on success) */
3755 vrc = VDSetParentUuid (hdd, 0, chain->parent()->m.id);
3756 if (RT_FAILURE (vrc))
3757 throw vrc;
3758 }
3759 else
3760 {
3761 /* we need to update UUIDs of all source's children
3762 * which cannot be part of the container at once so
3763 * add each one in there individually */
3764 if (chain->children().size() > 0)
3765 {
3766 for (List::const_iterator it = chain->children().begin();
3767 it != chain->children().end(); ++ it)
3768 {
3769 /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
3770 vrc = VDOpen (hdd, Utf8Str ((*it)->mm.format),
3771 Utf8Str ((*it)->m.locationFull),
3772 VD_OPEN_FLAGS_INFO,
3773 (*it)->mm.vdDiskIfaces);
3774 if (RT_FAILURE (vrc))
3775 throw vrc;
3776
3777 vrc = VDSetParentUuid (hdd, 1,
3778 chain->target()->m.id);
3779 if (RT_FAILURE (vrc))
3780 throw vrc;
3781
3782 vrc = VDClose (hdd, false /* fDelete */);
3783 if (RT_FAILURE (vrc))
3784 throw vrc;
3785 }
3786 }
3787 }
3788 }
3789 catch (HRESULT aRC) { rc = aRC; }
3790 catch (int aVRC)
3791 {
3792 throw setError (E_FAIL,
3793 tr ("Could not merge the hard disk '%ls' to '%ls'%s"),
3794 chain->source()->m.locationFull.raw(),
3795 chain->target()->m.locationFull.raw(),
3796 that->vdError (aVRC).raw());
3797 }
3798
3799 VDDestroy (hdd);
3800 }
3801 catch (HRESULT aRC) { rc = aRC; }
3802
3803 HRESULT rc2;
3804
3805 bool saveSettingsFailed = false;
3806
3807 if (SUCCEEDED (rc))
3808 {
3809 /* all hard disks but the target were successfully deleted by
3810 * VDMerge; reparent the last one and uninitialize deleted */
3811
3812 /* we set mParent & children() (note that thatLock is released
3813 * here), but lock VirtualBox first to follow the rule */
3814 AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
3815 that->treeLock());
3816
3817 HardDisk *source = chain->source();
3818 HardDisk *target = chain->target();
3819
3820 if (chain->isForward())
3821 {
3822 /* first, unregister the target since it may become a base
3823 * hard disk which needs re-registration */
3824 rc2 = target->mVirtualBox->
3825 unregisterHardDisk (target, false /* aSaveSettings */);
3826 AssertComRC (rc2);
3827
3828 /* then, reparent it and disconnect the deleted branch at
3829 * both ends (chain->parent() is source's parent) */
3830 target->mParent->removeDependentChild (target);
3831 target->mParent = chain->parent();
3832 if (!target->mParent.isNull())
3833 {
3834 target->mParent->addDependentChild (target);
3835 target->mParent->removeDependentChild (source);
3836 source->mParent.setNull();
3837 }
3838 else
3839 {
3840 target->mVirtualBox->addDependentChild (target);
3841 target->mVirtualBox->removeDependentChild (source);
3842 }
3843
3844 /* then, register again */
3845 rc2 = target->mVirtualBox->
3846 registerHardDisk (target, false /* aSaveSettings */);
3847 AssertComRC (rc2);
3848 }
3849 else
3850 {
3851 Assert (target->children().size() == 1);
3852 HardDisk *targetChild = target->children().front();
3853
3854 /* disconnect the deleted branch at the elder end */
3855 target->removeDependentChild (targetChild);
3856 targetChild->mParent.setNull();
3857
3858 const List &children = chain->children();
3859
3860 /* reparent source's chidren and disconnect the deleted
3861 * branch at the younger end m*/
3862 if (children.size() > 0)
3863 {
3864 /* obey {parent,child} lock order */
3865 AutoWriteLock sourceLock (source);
3866
3867 for (List::const_iterator it = children.begin();
3868 it != children.end(); ++ it)
3869 {
3870 AutoWriteLock childLock (*it);
3871
3872 (*it)->mParent = target;
3873 (*it)->mParent->addDependentChild (*it);
3874 source->removeDependentChild (*it);
3875 }
3876 }
3877 }
3878
3879 /* try to save the hard disk registry */
3880 rc = that->mVirtualBox->saveSettings();
3881
3882 if (SUCCEEDED (rc))
3883 {
3884 /* unregister and uninitialize all hard disks in the chain
3885 * but the target */
3886
3887 for (MergeChain::iterator it = chain->begin();
3888 it != chain->end();)
3889 {
3890 if (*it == chain->target())
3891 {
3892 ++ it;
3893 continue;
3894 }
3895
3896 rc2 = (*it)->mVirtualBox->
3897 unregisterHardDisk(*it, false /* aSaveSettings */);
3898 AssertComRC (rc2);
3899
3900 /* now, uninitialize the deleted hard disk (note that
3901 * due to the Deleting state, uninit() will not touch
3902 * the parent-child relationship so we need to
3903 * uninitialize each disk individually) */
3904
3905 /* note that the operation initiator hard disk (which is
3906 * normally also the source hard disk) is a special case
3907 * -- there is one more caller added by Task to it which
3908 * we must release. Also, if we are in sync mode, the
3909 * caller may still hold an AutoCaller instance for it
3910 * and therefore we cannot uninit() it (it's therefore
3911 * the caller's responsibility) */
3912 if (*it == that)
3913 task->autoCaller.release();
3914
3915 /* release the caller added by MergeChain before
3916 * uninit() */
3917 (*it)->releaseCaller();
3918
3919 if (isAsync || *it != that)
3920 (*it)->uninit();
3921
3922 /* delete (to prevent uninitialization in MergeChain
3923 * dtor) and advance to the next item */
3924 it = chain->erase (it);
3925 }
3926
3927 /* Note that states of all other hard disks (target, parent,
3928 * children) will be restored by the MergeChain dtor */
3929 }
3930 else
3931 {
3932 /* too bad if we fail, but we'll need to rollback everything
3933 * we did above to at least keep the HD tree in sync with
3934 * the current registry on disk */
3935
3936 saveSettingsFailed = true;
3937
3938 /// @todo NEWMEDIA implement a proper undo
3939
3940 AssertFailed();
3941 }
3942 }
3943
3944 if (FAILED (rc))
3945 {
3946 /* Here we come if either VDMerge() failed (in which case we
3947 * assume that it tried to do everything to make a further
3948 * retry possible -- e.g. not deleted intermediate hard disks
3949 * and so on) or VirtualBox::saveSettings() failed (where we
3950 * should have the original tree but with intermediate storage
3951 * units deleted by VDMerge()). We have to only restore states
3952 * (through the MergeChain dtor) unless we are run synchronously
3953 * in which case it's the responsibility of the caller as stated
3954 * in the mergeTo() docs. The latter also implies that we
3955 * don't own the merge chain, so release it in this case. */
3956
3957 if (!isAsync)
3958 task->d.chain.release();
3959
3960 NOREF (saveSettingsFailed);
3961 }
3962
3963 break;
3964 }
3965
3966 ////////////////////////////////////////////////////////////////////////
3967
3968 case Task::Clone:
3969 {
3970 ComObjPtr<HardDisk> &target = task->d.target;
3971
3972 /* Lock both in {parent,child} order. The lock is also used as a
3973 * signal from the task initiator (which releases it only after
3974 * RTThreadCreate()) that we can start the job*/
3975 AutoMultiWriteLock2 thatLock (that, target);
3976
3977 uint64_t size = 0, logicalSize = 0;
3978
3979 /* The object may request a specific UUID (through a special form of
3980 * the setLocation() argument). Otherwise we have to generate it */
3981 Guid targetId = target->m.id;
3982 bool generateUuid = targetId.isEmpty();
3983 if (generateUuid)
3984 {
3985 targetId.create();
3986 /* VirtualBox::registerHardDisk() will need UUID */
3987 unconst (target->m.id) = targetId;
3988 }
3989
3990 try
3991 {
3992 PVBOXHDD hdd;
3993 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
3994 ComAssertRCThrow (vrc, E_FAIL);
3995
3996 Utf8Str format (that->mm.format);
3997 Utf8Str location (that->m.locationFull);
3998
3999 Utf8Str targetFormat (target->mm.format);
4000 Utf8Str targetLocation (target->m.locationFull);
4001
4002 Assert (target->m.state == MediaState_Creating);
4003
4004 Assert (that->m.state == MediaState_LockedRead);
4005
4006 /* unlock before the potentially lengthy operation */
4007 thatLock.leave();
4008
4009 try
4010 {
4011 vrc = VDOpen (hdd, format, location,
4012 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
4013 that->mm.vdDiskIfaces);
4014 if (RT_FAILURE (vrc))
4015 {
4016 throw setError (E_FAIL,
4017 tr ("Could not open the hard disk storage "
4018 "unit '%s'%s"),
4019 location.raw(), that->vdError (vrc).raw());
4020 }
4021
4022 /* ensure the target directory exists */
4023 rc = VirtualBox::ensureFilePathExists (targetLocation);
4024 CheckComRCThrowRC (rc);
4025
4026 /* needed for vdProgressCallback */
4027 that->mm.vdProgress = task->progress;
4028
4029 PVBOXHDD targetHdd;
4030 int vrc = VDCreate (that->mm.vdDiskIfaces, &targetHdd);
4031 ComAssertRCThrow (vrc, E_FAIL);
4032
4033 vrc = VDCopy (hdd, 0, targetHdd, targetFormat,
4034 targetLocation, false, 0, task->d.variant,
4035 targetId.raw(), NULL,
4036 target->mm.vdDiskIfaces,
4037 that->mm.vdDiskIfaces);
4038
4039 that->mm.vdProgress = NULL;
4040
4041 if (RT_FAILURE (vrc))
4042 {
4043 VDDestroy (targetHdd);
4044
4045 throw setError (E_FAIL,
4046 tr ("Could not create the clone hard disk "
4047 "'%s'%s"),
4048 targetLocation.raw(), that->vdError (vrc).raw());
4049 }
4050
4051 size = VDGetFileSize (targetHdd, 0);
4052 logicalSize = VDGetSize (targetHdd, 0) / _1M;
4053
4054 VDDestroy (targetHdd);
4055 }
4056 catch (HRESULT aRC) { rc = aRC; }
4057
4058 VDDestroy (hdd);
4059 }
4060 catch (HRESULT aRC) { rc = aRC; }
4061
4062 if (SUCCEEDED (rc))
4063 {
4064 /* we set mParent & children() (note that thatLock is released
4065 * here), but lock VirtualBox first to follow the rule */
4066 AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
4067 that->treeLock());
4068
4069 Assert (target->mParent.isNull());
4070
4071 if (!that->mParent.isNull())
4072 {
4073 /* associate the clone with the original's parent and
4074 * deassociate from VirtualBox */
4075 target->mParent = that->mParent;
4076 that->mParent->addDependentChild (target);
4077 target->mVirtualBox->removeDependentChild (target);
4078
4079 /* register with mVirtualBox as the last step and move to
4080 * Created state only on success (leaving an orphan file is
4081 * better than breaking media registry consistency) */
4082 rc = that->mVirtualBox->registerHardDisk(target);
4083
4084 if (FAILED (rc))
4085 {
4086 /* break the parent association on failure to register */
4087 target->mVirtualBox->addDependentChild (target);
4088 that->mParent->removeDependentChild (target);
4089 target->mParent.setNull();
4090 }
4091 }
4092 else
4093 {
4094 /* just register */
4095 rc = that->mVirtualBox->registerHardDisk(target);
4096 }
4097 }
4098
4099 thatLock.maybeEnter();
4100
4101 if (SUCCEEDED (rc))
4102 {
4103 target->m.state = MediaState_Created;
4104
4105 target->m.size = size;
4106 target->mm.logicalSize = logicalSize;
4107 }
4108 else
4109 {
4110 /* back to NotCreated on failiure */
4111 target->m.state = MediaState_NotCreated;
4112
4113 /* reset UUID to prevent it from being reused next time */
4114 if (generateUuid)
4115 unconst (target->m.id).clear();
4116 }
4117
4118 if (isAsync)
4119 {
4120 /* unlock ourselves when done (unless in MediaState_LockedWrite
4121 * state because of taking the online snapshot*/
4122 if (that->m.state != MediaState_LockedWrite)
4123 {
4124 HRESULT rc2 = that->UnlockRead (NULL);
4125 AssertComRC (rc2);
4126 }
4127 }
4128
4129 /* Note that in sync mode, it's the caller's responsibility to
4130 * unlock the hard disk */
4131
4132 break;
4133 }
4134
4135 ////////////////////////////////////////////////////////////////////////
4136
4137 case Task::Delete:
4138 {
4139 /* The lock is also used as a signal from the task initiator (which
4140 * releases it only after RTThreadCreate()) that we can start the job */
4141 AutoWriteLock thatLock (that);
4142
4143 try
4144 {
4145 PVBOXHDD hdd;
4146 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
4147 ComAssertRCThrow (vrc, E_FAIL);
4148
4149 Utf8Str format (that->mm.format);
4150 Utf8Str location (that->m.locationFull);
4151
4152 /* unlock before the potentially lengthy operation */
4153 Assert (that->m.state == MediaState_Deleting);
4154 thatLock.leave();
4155
4156 try
4157 {
4158 vrc = VDOpen (hdd, format, location,
4159 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
4160 that->mm.vdDiskIfaces);
4161 if (RT_SUCCESS (vrc))
4162 vrc = VDClose (hdd, true /* fDelete */);
4163
4164 if (RT_FAILURE (vrc))
4165 {
4166 throw setError (E_FAIL,
4167 tr ("Could not delete the hard disk storage "
4168 "unit '%s'%s"),
4169 location.raw(), that->vdError (vrc).raw());
4170 }
4171
4172 }
4173 catch (HRESULT aRC) { rc = aRC; }
4174
4175 VDDestroy (hdd);
4176 }
4177 catch (HRESULT aRC) { rc = aRC; }
4178
4179 thatLock.maybeEnter();
4180
4181 /* go to the NotCreated state even on failure since the storage
4182 * may have been already partially deleted and cannot be used any
4183 * more. One will be able to manually re-open the storage if really
4184 * needed to re-register it. */
4185 that->m.state = MediaState_NotCreated;
4186
4187 /* Reset UUID to prevent Create* from reusing it again */
4188 unconst (that->m.id).clear();
4189
4190 break;
4191 }
4192
4193 case Task::Reset:
4194 {
4195 /* The lock is also used as a signal from the task initiator (which
4196 * releases it only after RTThreadCreate()) that we can start the job */
4197 AutoWriteLock thatLock (that);
4198
4199 /// @todo Below we use a pair of delete/create operations to reset
4200 /// the diff contents but the most efficient way will of course be
4201 /// to add a VDResetDiff() API call
4202
4203 uint64_t size = 0, logicalSize = 0;
4204
4205 try
4206 {
4207 PVBOXHDD hdd;
4208 int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
4209 ComAssertRCThrow (vrc, E_FAIL);
4210
4211 Guid id = that->m.id;
4212 Utf8Str format (that->mm.format);
4213 Utf8Str location (that->m.locationFull);
4214
4215 Guid parentId = that->mParent->m.id;
4216 Utf8Str parentFormat (that->mParent->mm.format);
4217 Utf8Str parentLocation (that->mParent->m.locationFull);
4218
4219 Assert (that->m.state == MediaState_LockedWrite);
4220
4221 /* unlock before the potentially lengthy operation */
4222 thatLock.leave();
4223
4224 try
4225 {
4226 /* first, delete the storage unit */
4227 vrc = VDOpen (hdd, format, location,
4228 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
4229 that->mm.vdDiskIfaces);
4230 if (RT_SUCCESS (vrc))
4231 vrc = VDClose (hdd, true /* fDelete */);
4232
4233 if (RT_FAILURE (vrc))
4234 {
4235 throw setError (E_FAIL,
4236 tr ("Could not delete the hard disk storage "
4237 "unit '%s'%s"),
4238 location.raw(), that->vdError (vrc).raw());
4239 }
4240
4241 /* next, create it again */
4242 vrc = VDOpen (hdd, parentFormat, parentLocation,
4243 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
4244 that->mm.vdDiskIfaces);
4245 if (RT_FAILURE (vrc))
4246 {
4247 throw setError (E_FAIL,
4248 tr ("Could not open the hard disk storage "
4249 "unit '%s'%s"),
4250 parentLocation.raw(), that->vdError (vrc).raw());
4251 }
4252
4253 /* needed for vdProgressCallback */
4254 that->mm.vdProgress = task->progress;
4255
4256 vrc = VDCreateDiff (hdd, format, location,
4257 /// @todo use the same image variant as before
4258 VD_IMAGE_FLAGS_NONE,
4259 NULL, id.raw(),
4260 parentId.raw(),
4261 VD_OPEN_FLAGS_NORMAL,
4262 that->mm.vdDiskIfaces,
4263 that->mm.vdDiskIfaces);
4264
4265 that->mm.vdProgress = NULL;
4266
4267 if (RT_FAILURE (vrc))
4268 {
4269 throw setError (E_FAIL,
4270 tr ("Could not create the differencing hard disk "
4271 "storage unit '%s'%s"),
4272 location.raw(), that->vdError (vrc).raw());
4273 }
4274
4275 size = VDGetFileSize (hdd, 1);
4276 logicalSize = VDGetSize (hdd, 1) / _1M;
4277 }
4278 catch (HRESULT aRC) { rc = aRC; }
4279
4280 VDDestroy (hdd);
4281 }
4282 catch (HRESULT aRC) { rc = aRC; }
4283
4284 thatLock.enter();
4285
4286 that->m.size = size;
4287 that->mm.logicalSize = logicalSize;
4288
4289 if (isAsync)
4290 {
4291 /* unlock ourselves when done */
4292 HRESULT rc2 = that->UnlockWrite (NULL);
4293 AssertComRC (rc2);
4294 }
4295
4296 /* Note that in sync mode, it's the caller's responsibility to
4297 * unlock the hard disk */
4298
4299 break;
4300 }
4301
4302 default:
4303 AssertFailedReturn (VERR_GENERAL_FAILURE);
4304 }
4305
4306 /* complete the progress if run asynchronously */
4307 if (isAsync)
4308 {
4309 if (!task->progress.isNull())
4310 task->progress->notifyComplete (rc);
4311 }
4312 else
4313 {
4314 task->rc = rc;
4315 }
4316
4317 LogFlowFunc (("rc=%Rhrc\n", rc));
4318 LogFlowFuncLeave();
4319
4320 return VINF_SUCCESS;
4321
4322 /// @todo ugly hack, fix ComAssert... later
4323 #undef setError
4324}
4325/* 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