VirtualBox

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

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

Main: Implemented IHardDisk::reset().

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