VirtualBox

source: vbox/trunk/src/VBox/Main/MediumImpl.cpp@ 25149

Last change on this file since 25149 was 25149, checked in by vboxsync, 15 years ago

Main: cleanup: remove all CheckComRC* macros (no functional change)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 189.8 KB
Line 
1/* $Id: MediumImpl.cpp 25149 2009-12-02 14:34:47Z vboxsync $ */
2
3/** @file
4 *
5 * VirtualBox COM class implementation
6 */
7
8/*
9 * Copyright (C) 2008-2009 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 "MediumImpl.h"
25#include "ProgressImpl.h"
26#include "SystemPropertiesImpl.h"
27#include "VirtualBoxImpl.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 <VBox/VBoxHDD.h>
43
44#include <algorithm>
45
46////////////////////////////////////////////////////////////////////////////////
47//
48// Medium data definition
49//
50////////////////////////////////////////////////////////////////////////////////
51
52/** Describes how a machine refers to this image. */
53struct BackRef
54{
55 /** Equality predicate for stdc++. */
56 struct EqualsTo : public std::unary_function <BackRef, bool>
57 {
58 explicit EqualsTo(const Guid &aMachineId) : machineId(aMachineId) {}
59
60 bool operator()(const argument_type &aThat) const
61 {
62 return aThat.machineId == machineId;
63 }
64
65 const Guid machineId;
66 };
67
68 typedef std::list<Guid> GuidList;
69
70 BackRef(const Guid &aMachineId,
71 const Guid &aSnapshotId = Guid::Empty)
72 : machineId(aMachineId),
73 fInCurState(aSnapshotId.isEmpty())
74 {
75 if (!aSnapshotId.isEmpty())
76 llSnapshotIds.push_back(aSnapshotId);
77 }
78
79 Guid machineId;
80 bool fInCurState : 1;
81 GuidList llSnapshotIds;
82};
83
84typedef std::list<BackRef> BackRefList;
85
86struct Medium::Data
87{
88 Data()
89 : state(MediumState_NotCreated),
90 size(0),
91 readers(0),
92 preLockState(MediumState_NotCreated),
93 queryInfoSem(NIL_RTSEMEVENTMULTI),
94 queryInfoRunning(false),
95 type(MediumType_Normal),
96 devType(DeviceType_HardDisk),
97 logicalSize(0),
98 hddOpenMode(OpenReadWrite),
99 autoReset(false),
100 setImageId(false),
101 setParentId(false),
102 hostDrive(FALSE),
103 implicit(false),
104 numCreateDiffTasks(0),
105 vdDiskIfaces(NULL)
106 {}
107
108 const Guid id;
109 Utf8Str strDescription;
110 MediumState_T state;
111 Utf8Str strLocation;
112 Utf8Str strLocationFull;
113 uint64_t size;
114 Utf8Str strLastAccessError;
115
116 BackRefList backRefs;
117
118 size_t readers;
119 MediumState_T preLockState;
120
121 RTSEMEVENTMULTI queryInfoSem;
122 bool queryInfoRunning : 1;
123
124 const Utf8Str strFormat;
125 ComObjPtr<MediumFormat> formatObj;
126
127 MediumType_T type;
128 DeviceType_T devType;
129 uint64_t logicalSize; /*< In MBytes. */
130
131 HDDOpenMode hddOpenMode;
132
133 BOOL autoReset : 1;
134
135 /** the following members are invalid after changing UUID on open */
136 BOOL setImageId : 1;
137 BOOL setParentId : 1;
138 const Guid imageId;
139 const Guid parentId;
140
141 BOOL hostDrive : 1;
142
143 typedef std::map <Bstr, Bstr> PropertyMap;
144 PropertyMap properties;
145
146 bool implicit : 1;
147
148 uint32_t numCreateDiffTasks;
149
150 Utf8Str vdError; /*< Error remembered by the VD error callback. */
151
152 VDINTERFACE vdIfError;
153 VDINTERFACEERROR vdIfCallsError;
154
155 VDINTERFACE vdIfConfig;
156 VDINTERFACECONFIG vdIfCallsConfig;
157
158 VDINTERFACE vdIfTcpNet;
159 VDINTERFACETCPNET vdIfCallsTcpNet;
160
161 PVDINTERFACE vdDiskIfaces;
162};
163
164////////////////////////////////////////////////////////////////////////////////
165//
166// Globals
167//
168////////////////////////////////////////////////////////////////////////////////
169
170/**
171 * Asynchronous task thread parameter bucket.
172 *
173 * Note that instances of this class must be created using new() because the
174 * task thread function will delete them when the task is complete!
175 *
176 * @note The constructor of this class adds a caller on the managed Medium
177 * object which is automatically released upon destruction.
178 */
179struct Medium::Task : public com::SupportErrorInfoBase
180{
181 enum Operation { CreateBase,
182 CreateDiff,
183 Merge,
184 Clone,
185 Delete,
186 Reset,
187 Compact
188 };
189
190 Medium *that;
191 VirtualBoxBaseProto::AutoCaller m_autoCaller;
192
193 ComObjPtr<Progress> m_pProgress;
194 Operation m_operation;
195
196 /** Where to save the result when executed using #runNow(). */
197 HRESULT m_rc;
198
199 Task(Medium *aThat,
200 Progress *aProgress,
201 Operation aOperation)
202 : that(aThat),
203 m_autoCaller(aThat),
204 m_pProgress(aProgress),
205 m_operation(aOperation),
206 m_rc(S_OK)
207 {}
208
209 ~Task();
210
211 void setData(Medium *aTarget)
212 {
213 d.target = aTarget;
214 HRESULT rc = d.target->addCaller();
215 AssertComRC(rc);
216 }
217
218 void setData(Medium *aTarget, Medium *aParent)
219 {
220 d.target = aTarget;
221 HRESULT rc = d.target->addCaller();
222 AssertComRC(rc);
223 d.parentDisk = aParent;
224 if (aParent)
225 {
226 rc = d.parentDisk->addCaller();
227 AssertComRC(rc);
228 }
229 }
230
231 void setData(MergeChain *aChain)
232 {
233 AssertReturnVoid(aChain != NULL);
234 d.chain.reset(aChain);
235 }
236
237 void setData(ImageChain *aSrcChain, ImageChain *aParentChain)
238 {
239 AssertReturnVoid(aSrcChain != NULL);
240 AssertReturnVoid(aParentChain != NULL);
241 d.source.reset(aSrcChain);
242 d.parent.reset(aParentChain);
243 }
244
245 void setData(ImageChain *aImgChain)
246 {
247 AssertReturnVoid(aImgChain != NULL);
248 d.images.reset(aImgChain);
249 }
250
251 HRESULT startThread();
252 HRESULT runNow();
253
254 struct Data
255 {
256 Data() : size(0) {}
257
258 /* CreateBase */
259
260 uint64_t size;
261
262 /* CreateBase, CreateDiff, Clone */
263
264 MediumVariant_T variant;
265
266 /* CreateDiff, Clone */
267
268 ComObjPtr<Medium> target;
269
270 /* Clone */
271
272 /** Media to open, in {parent,child} order */
273 std::auto_ptr <ImageChain> source;
274 /** Media which are parent of target, in {parent,child} order */
275 std::auto_ptr <ImageChain> parent;
276 /** The to-be parent medium object */
277 ComObjPtr<Medium> parentDisk;
278
279 /* Merge */
280
281 /** Media to merge, in {parent,child} order */
282 std::auto_ptr <MergeChain> chain;
283
284 /* Compact */
285
286 /** Media to open, in {parent,child} order */
287 std::auto_ptr <ImageChain> images;
288 }
289 d;
290
291protected:
292
293 // SupportErrorInfoBase interface
294 const GUID &mainInterfaceID() const { return COM_IIDOF(IMedium); }
295 const char *componentName() const { return Medium::ComponentName(); }
296};
297
298Medium::Task::~Task()
299{
300 /* remove callers added by setData() */
301 if (!d.target.isNull())
302 d.target->releaseCaller();
303}
304
305/**
306 * Starts a new thread driven by the Medium::taskThread() function and passes
307 * this Task instance as an argument.
308 *
309 * Note that if this method returns success, this Task object becomes an ownee
310 * of the started thread and will be automatically deleted when the thread
311 * terminates.
312 *
313 * @note When the task is executed by this method, IProgress::notifyComplete()
314 * is automatically called for the progress object associated with this
315 * task when the task is finished to signal the operation completion for
316 * other threads asynchronously waiting for it.
317 */
318HRESULT Medium::Task::startThread()
319{
320 int vrc = RTThreadCreate(NULL, Medium::taskThread, this,
321 0, RTTHREADTYPE_MAIN_HEAVY_WORKER, 0,
322 "Medium::Task");
323 ComAssertMsgRCRet(vrc,
324 ("Could not create Medium::Task thread (%Rrc)\n", vrc),
325 E_FAIL);
326
327 return S_OK;
328}
329
330/**
331 * Runs Medium::taskThread() by passing it this Task instance as an argument
332 * on the current thread instead of creating a new one.
333 *
334 * This call implies that it is made on another temporary thread created for
335 * some asynchronous task. Avoid calling it from a normal thread since the task
336 * operatinos are potentially lengthy and will block the calling thread in this
337 * case.
338 *
339 * Note that this Task object will be deleted by taskThread() when this method
340 * returns!
341 *
342 * @note When the task is executed by this method, IProgress::notifyComplete()
343 * is not called for the progress object associated with this task when
344 * the task is finished. Instead, the result of the operation is returned
345 * by this method directly and it's the caller's responsibility to
346 * complete the progress object in this case.
347 */
348HRESULT Medium::Task::runNow()
349{
350 /* NIL_RTTHREAD indicates synchronous call. */
351 Medium::taskThread(NIL_RTTHREAD, this);
352
353 return m_rc;
354}
355
356////////////////////////////////////////////////////////////////////////////////
357//
358// Merge chain class
359//
360////////////////////////////////////////////////////////////////////////////////
361
362/**
363 * Helper class for merge operations.
364 *
365 * @note It is assumed that when modifying methods of this class are called,
366 * Medium::getTreeLock() is held in read mode.
367 */
368class Medium::MergeChain : public Medium::List,
369 public com::SupportErrorInfoBase
370{
371public:
372
373 MergeChain(bool aForward, bool aIgnoreAttachments)
374 : mForward(aForward)
375 , mIgnoreAttachments(aIgnoreAttachments) {}
376
377 ~MergeChain()
378 {
379 for (iterator it = mChildren.begin(); it != mChildren.end(); ++ it)
380 {
381 HRESULT rc = (*it)->UnlockWrite(NULL);
382 AssertComRC(rc);
383
384 (*it)->releaseCaller();
385 }
386
387 for (iterator it = begin(); it != end(); ++ it)
388 {
389 AutoWriteLock alock(*it);
390 Assert((*it)->m->state == MediumState_LockedWrite ||
391 (*it)->m->state == MediumState_Deleting);
392 if ((*it)->m->state == MediumState_LockedWrite)
393 (*it)->UnlockWrite(NULL);
394 else
395 (*it)->m->state = MediumState_Created;
396
397 (*it)->releaseCaller();
398 }
399
400 if (!mParent.isNull())
401 mParent->releaseCaller();
402 }
403
404 HRESULT addSource(Medium *aMedium)
405 {
406 HRESULT rc = aMedium->addCaller();
407 if (FAILED(rc)) return rc;
408
409 AutoWriteLock alock(aMedium);
410
411 if (mForward)
412 {
413 rc = checkChildrenAndAttachmentsAndImmutable(aMedium);
414 if (FAILED(rc))
415 {
416 aMedium->releaseCaller();
417 return rc;
418 }
419 }
420
421 /* We have to fetch the state with the COM method, cause it's possible
422 that the medium isn't fully initialized yet. */
423 MediumState_T m;
424 rc = aMedium->RefreshState(&m);
425 if (FAILED(rc)) return rc;
426 /* go to Deleting */
427 switch (m)
428 {
429 case MediumState_Created:
430 aMedium->m->state = MediumState_Deleting;
431 break;
432 default:
433 aMedium->releaseCaller();
434 return aMedium->setStateError();
435 }
436
437 push_front(aMedium);
438
439 if (mForward)
440 {
441 /* we will need parent to reparent target */
442 if (!aMedium->mParent.isNull())
443 {
444 rc = aMedium->mParent->addCaller();
445 if (FAILED(rc)) return rc;
446
447 mParent = aMedium->mParent;
448 }
449 }
450 else
451 {
452 /* we will need to reparent children */
453 for (List::const_iterator it = aMedium->getChildren().begin();
454 it != aMedium->getChildren().end();
455 ++it)
456 {
457 ComObjPtr<Medium> pMedium = *it;
458 rc = pMedium->addCaller();
459 if (FAILED(rc)) return rc;
460
461 rc = pMedium->LockWrite(NULL);
462 if (FAILED(rc))
463 {
464 pMedium->releaseCaller();
465 return rc;
466 }
467
468 mChildren.push_back(pMedium);
469 }
470 }
471
472 return S_OK;
473 }
474
475 HRESULT addTarget(Medium *aMedium)
476 {
477 HRESULT rc = aMedium->addCaller();
478 if (FAILED(rc)) return rc;
479
480 AutoWriteLock alock(aMedium);
481
482 if (!mForward)
483 {
484 rc = checkChildrenAndImmutable(aMedium);
485 if (FAILED(rc))
486 {
487 aMedium->releaseCaller();
488 return rc;
489 }
490 }
491
492 /* go to LockedWrite */
493 rc = aMedium->LockWrite(NULL);
494 if (FAILED(rc))
495 {
496 aMedium->releaseCaller();
497 return rc;
498 }
499
500 push_front(aMedium);
501
502 return S_OK;
503 }
504
505 HRESULT addIntermediate(Medium *aMedium)
506 {
507 HRESULT rc = aMedium->addCaller();
508 if (FAILED(rc)) return rc;
509
510 AutoWriteLock alock(aMedium);
511
512 rc = checkChildrenAndAttachments(aMedium);
513 if (FAILED(rc))
514 {
515 aMedium->releaseCaller();
516 return rc;
517 }
518
519 /* go to Deleting */
520 switch (aMedium->m->state)
521 {
522 case MediumState_Created:
523 aMedium->m->state = MediumState_Deleting;
524 break;
525 default:
526 aMedium->releaseCaller();
527 return aMedium->setStateError();
528 }
529
530 push_front(aMedium);
531
532 return S_OK;
533 }
534
535 bool isForward() const { return mForward; }
536 Medium *parent() const { return mParent; }
537 const List &children() const { return mChildren; }
538
539 Medium *source() const
540 { AssertReturn(size() > 0, NULL); return mForward ? front() : back(); }
541
542 Medium *target() const
543 { AssertReturn(size() > 0, NULL); return mForward ? back() : front(); }
544
545protected:
546
547 // SupportErrorInfoBase interface
548 const GUID &mainInterfaceID() const { return COM_IIDOF(IMedium); }
549 const char *componentName() const { return Medium::ComponentName(); }
550
551private:
552
553 HRESULT check(Medium *aMedium, bool aChildren, bool aAttachments,
554 bool aImmutable)
555 {
556 if (aChildren)
557 {
558 /* not going to multi-merge as it's too expensive */
559 if (aMedium->getChildren().size() > 1)
560 {
561 return setError(E_FAIL,
562 tr("Medium '%s' involved in the merge operation has more than one child medium (%d)"),
563 aMedium->m->strLocationFull.raw(),
564 aMedium->getChildren().size());
565 }
566 }
567
568 if (aAttachments && !mIgnoreAttachments)
569 {
570 if (aMedium->m->backRefs.size() != 0)
571 return setError(E_FAIL,
572 tr("Medium '%s' is attached to %d virtual machines"),
573 aMedium->m->strLocationFull.raw(),
574 aMedium->m->backRefs.size());
575 }
576
577 if (aImmutable)
578 {
579 if (aMedium->m->type == MediumType_Immutable)
580 return setError(E_FAIL,
581 tr("Medium '%s' is immutable"),
582 aMedium->m->strLocationFull.raw());
583 }
584
585 return S_OK;
586 }
587
588 HRESULT checkChildren(Medium *aMedium)
589 { return check(aMedium, true, false, false); }
590
591 HRESULT checkChildrenAndImmutable(Medium *aMedium)
592 { return check(aMedium, true, false, true); }
593
594 HRESULT checkChildrenAndAttachments(Medium *aMedium)
595 { return check(aMedium, true, true, false); }
596
597 HRESULT checkChildrenAndAttachmentsAndImmutable(Medium *aMedium)
598 { return check(aMedium, true, true, true); }
599
600 /** true if forward merge, false if backward */
601 bool mForward : 1;
602 /** true to not perform attachment checks */
603 bool mIgnoreAttachments : 1;
604
605 /** Parent of the source when forward merge (if any) */
606 ComObjPtr <Medium> mParent;
607 /** Children of the source when backward merge (if any) */
608 List mChildren;
609};
610
611////////////////////////////////////////////////////////////////////////////////
612//
613// ImageChain class
614//
615////////////////////////////////////////////////////////////////////////////////
616
617/**
618 * Helper class for image operations involving the entire parent chain.
619 *
620 * @note It is assumed that when modifying methods of this class are called,
621 * Medium::getTreeLock() is held in read mode.
622 */
623class Medium::ImageChain : public Medium::List,
624 public com::SupportErrorInfoBase
625{
626public:
627
628 ImageChain() {}
629
630 ~ImageChain()
631 {
632 /* empty? */
633 if (begin() != end())
634 {
635 List::const_iterator last = end();
636 last--;
637 for (List::const_iterator it = begin(); it != end(); ++ it)
638 {
639 AutoWriteLock alock(*it);
640 if (it == last)
641 {
642 Assert( (*it)->m->state == MediumState_LockedRead
643 || (*it)->m->state == MediumState_LockedWrite);
644 if ((*it)->m->state == MediumState_LockedRead)
645 (*it)->UnlockRead(NULL);
646 else if ((*it)->m->state == MediumState_LockedWrite)
647 (*it)->UnlockWrite(NULL);
648 }
649 else
650 {
651 Assert((*it)->m->state == MediumState_LockedRead);
652 if ((*it)->m->state == MediumState_LockedRead)
653 (*it)->UnlockRead(NULL);
654 }
655
656 (*it)->releaseCaller();
657 }
658 }
659 }
660
661 HRESULT addImage(Medium *aMedium)
662 {
663 HRESULT rc = aMedium->addCaller();
664 if (FAILED(rc)) return rc;
665
666 push_front(aMedium);
667
668 return S_OK;
669 }
670
671 HRESULT lockImagesRead()
672 {
673 /* Lock all disks in the chain in {parent, child} order,
674 * and make sure they are accessible. */
675 /// @todo code duplication with SessionMachine::lockMedia, see below
676 ErrorInfoKeeper eik(true /* aIsNull */);
677 MultiResult mrc(S_OK);
678 for (List::const_iterator it = begin(); it != end(); ++ it)
679 {
680 HRESULT rc = S_OK;
681 MediumState_T mediumState = (*it)->getState();
682
683 /* accessibility check must be first, otherwise locking
684 * interferes with getting the medium state. */
685 if (mediumState == MediumState_Inaccessible)
686 {
687 rc = (*it)->RefreshState(&mediumState);
688 if (FAILED(rc)) return rc;
689
690 if (mediumState == MediumState_Inaccessible)
691 {
692 Bstr error;
693 rc = (*it)->COMGETTER(LastAccessError)(error.asOutParam());
694 if (FAILED(rc)) return rc;
695
696 Bstr loc;
697 rc = (*it)->COMGETTER(Location)(loc.asOutParam());
698 if (FAILED(rc)) return rc;
699
700 /* collect multiple errors */
701 eik.restore();
702
703 /* be in sync with Medium::setStateError() */
704 Assert(!error.isEmpty());
705 mrc = setError(E_FAIL,
706 tr("Medium '%ls' is not accessible. %ls"),
707 loc.raw(), error.raw());
708
709 eik.fetch();
710 }
711 }
712
713 rc = (*it)->LockRead(&mediumState);
714 if (FAILED(rc)) return rc;
715 }
716
717 eik.restore();
718 HRESULT rc2 = (HRESULT)mrc;
719 if (FAILED(rc2)) return rc2;
720
721 return S_OK;
722 }
723
724 HRESULT lockImagesReadAndLastWrite()
725 {
726 /* Lock all disks in the chain in {parent, child} order,
727 * and make sure they are accessible. */
728 /// @todo code duplication with SessionMachine::lockMedia, see below
729 ErrorInfoKeeper eik(true /* aIsNull */);
730 MultiResult mrc(S_OK);
731 List::const_iterator last = end();
732 last--;
733 for (List::const_iterator it = begin(); it != end(); ++ it)
734 {
735 HRESULT rc = S_OK;
736 MediumState_T mediumState = (*it)->getState();
737
738 /* accessibility check must be first, otherwise locking
739 * interferes with getting the medium state. */
740 if (mediumState == MediumState_Inaccessible)
741 {
742 rc = (*it)->RefreshState(&mediumState);
743 if (FAILED(rc)) return rc;
744
745 if (mediumState == MediumState_Inaccessible)
746 {
747 Bstr error;
748 rc = (*it)->COMGETTER(LastAccessError)(error.asOutParam());
749 if (FAILED(rc)) return rc;
750
751 Bstr loc;
752 rc = (*it)->COMGETTER(Location)(loc.asOutParam());
753 if (FAILED(rc)) return rc;
754
755 /* collect multiple errors */
756 eik.restore();
757
758 /* be in sync with Medium::setStateError() */
759 Assert(!error.isEmpty());
760 mrc = setError(E_FAIL,
761 tr("Medium '%ls' is not accessible. %ls"),
762 loc.raw(), error.raw());
763
764 eik.fetch();
765 }
766 }
767
768 if (it == last)
769 rc = (*it)->LockWrite(&mediumState);
770 else
771 rc = (*it)->LockRead(&mediumState);
772 }
773
774 eik.restore();
775 HRESULT rc2 = (HRESULT)mrc;
776 if (FAILED(rc2)) return rc2;
777
778 return S_OK;
779 }
780
781protected:
782
783 // SupportErrorInfoBase interface
784 const GUID &mainInterfaceID() const { return COM_IIDOF(IMedium); }
785 const char *componentName() const { return Medium::ComponentName(); }
786
787private:
788
789};
790
791
792////////////////////////////////////////////////////////////////////////////////
793//
794// Medium constructor / destructor
795//
796////////////////////////////////////////////////////////////////////////////////
797
798DEFINE_EMPTY_CTOR_DTOR(Medium)
799
800HRESULT Medium::FinalConstruct()
801{
802 m = new Data;
803
804 /* Initialize the callbacks of the VD error interface */
805 m->vdIfCallsError.cbSize = sizeof(VDINTERFACEERROR);
806 m->vdIfCallsError.enmInterface = VDINTERFACETYPE_ERROR;
807 m->vdIfCallsError.pfnError = vdErrorCall;
808 m->vdIfCallsError.pfnMessage = NULL;
809
810 /* Initialize the callbacks of the VD config interface */
811 m->vdIfCallsConfig.cbSize = sizeof(VDINTERFACECONFIG);
812 m->vdIfCallsConfig.enmInterface = VDINTERFACETYPE_CONFIG;
813 m->vdIfCallsConfig.pfnAreKeysValid = vdConfigAreKeysValid;
814 m->vdIfCallsConfig.pfnQuerySize = vdConfigQuerySize;
815 m->vdIfCallsConfig.pfnQuery = vdConfigQuery;
816
817 /* Initialize the callbacks of the VD TCP interface (we always use the host
818 * IP stack for now) */
819 m->vdIfCallsTcpNet.cbSize = sizeof(VDINTERFACETCPNET);
820 m->vdIfCallsTcpNet.enmInterface = VDINTERFACETYPE_TCPNET;
821 m->vdIfCallsTcpNet.pfnClientConnect = RTTcpClientConnect;
822 m->vdIfCallsTcpNet.pfnClientClose = RTTcpClientClose;
823 m->vdIfCallsTcpNet.pfnSelectOne = RTTcpSelectOne;
824 m->vdIfCallsTcpNet.pfnRead = RTTcpRead;
825 m->vdIfCallsTcpNet.pfnWrite = RTTcpWrite;
826 m->vdIfCallsTcpNet.pfnFlush = RTTcpFlush;
827
828 /* Initialize the per-disk interface chain */
829 int vrc;
830 vrc = VDInterfaceAdd(&m->vdIfError,
831 "Medium::vdInterfaceError",
832 VDINTERFACETYPE_ERROR,
833 &m->vdIfCallsError, this, &m->vdDiskIfaces);
834 AssertRCReturn(vrc, E_FAIL);
835
836 vrc = VDInterfaceAdd(&m->vdIfConfig,
837 "Medium::vdInterfaceConfig",
838 VDINTERFACETYPE_CONFIG,
839 &m->vdIfCallsConfig, this, &m->vdDiskIfaces);
840 AssertRCReturn(vrc, E_FAIL);
841
842 vrc = VDInterfaceAdd(&m->vdIfTcpNet,
843 "Medium::vdInterfaceTcpNet",
844 VDINTERFACETYPE_TCPNET,
845 &m->vdIfCallsTcpNet, this, &m->vdDiskIfaces);
846 AssertRCReturn(vrc, E_FAIL);
847
848 vrc = RTSemEventMultiCreate(&m->queryInfoSem);
849 AssertRCReturn(vrc, E_FAIL);
850 vrc = RTSemEventMultiSignal(m->queryInfoSem);
851 AssertRCReturn(vrc, E_FAIL);
852
853 return S_OK;
854}
855
856void Medium::FinalRelease()
857{
858 uninit();
859
860 delete m;
861}
862
863/**
864 * Initializes the hard disk object without creating or opening an associated
865 * storage unit.
866 *
867 * For hard disks that don't have the VD_CAP_CREATE_FIXED or
868 * VD_CAP_CREATE_DYNAMIC capability (and therefore cannot be created or deleted
869 * with the means of VirtualBox) the associated storage unit is assumed to be
870 * ready for use so the state of the hard disk object will be set to Created.
871 *
872 * @param aVirtualBox VirtualBox object.
873 * @param aLocation Storage unit location.
874 */
875HRESULT Medium::init(VirtualBox *aVirtualBox,
876 CBSTR aFormat,
877 CBSTR aLocation)
878{
879 AssertReturn(aVirtualBox != NULL, E_FAIL);
880 AssertReturn(aFormat != NULL && *aFormat != '\0', E_FAIL);
881
882 /* Enclose the state transition NotReady->InInit->Ready */
883 AutoInitSpan autoInitSpan(this);
884 AssertReturn(autoInitSpan.isOk(), E_FAIL);
885
886 HRESULT rc = S_OK;
887
888 /* share VirtualBox weakly (parent remains NULL so far) */
889 unconst(mVirtualBox) = aVirtualBox;
890
891 /* register with VirtualBox early, since uninit() will
892 * unconditionally unregister on failure */
893 aVirtualBox->addDependentChild(this);
894
895 /* no storage yet */
896 m->state = MediumState_NotCreated;
897
898 /* cannot be a host drive */
899 m->hostDrive = FALSE;
900
901 /* No storage unit is created yet, no need to queryInfo() */
902
903 rc = setFormat(aFormat);
904 if (FAILED(rc)) return rc;
905
906 if (m->formatObj->capabilities() & MediumFormatCapabilities_File)
907 {
908 rc = setLocation(aLocation);
909 if (FAILED(rc)) return rc;
910 }
911 else
912 {
913 rc = setLocation(aLocation);
914 if (FAILED(rc)) return rc;
915
916 /// @todo later we may want to use a pfnComposeLocation backend info
917 /// callback to generate a well-formed location value (based on the hard
918 /// disk properties we have) rather than allowing each caller to invent
919 /// its own (pseudo-)location.
920 }
921
922 if (!(m->formatObj->capabilities() &
923 (MediumFormatCapabilities_CreateFixed |
924 MediumFormatCapabilities_CreateDynamic)))
925 {
926 /* storage for hard disks of this format can neither be explicitly
927 * created by VirtualBox nor deleted, so we place the hard disk to
928 * Created state here and also add it to the registry */
929 m->state = MediumState_Created;
930 unconst(m->id).create();
931 rc = mVirtualBox->registerHardDisk(this);
932
933 /// @todo later we may want to use a pfnIsConfigSufficient backend info
934 /// callback that would tell us when we have enough properties to work
935 /// with the hard disk and this information could be used to actually
936 /// move such hard disks from NotCreated to Created state. Instead of
937 /// pfnIsConfigSufficient we can use MediumFormat property
938 /// descriptions to see which properties are mandatory
939 }
940
941 /* Confirm a successful initialization when it's the case */
942 if (SUCCEEDED(rc))
943 autoInitSpan.setSucceeded();
944
945 return rc;
946}
947
948/**
949 * Initializes the medium object by opening the storage unit at the specified
950 * location. The enOpenMode parameter defines whether the image will be opened
951 * read/write or read-only.
952 *
953 * Note that the UUID, format and the parent of this medium will be
954 * determined when reading the medium storage unit, unless new values are
955 * specified by the parameters. If the detected or set parent is
956 * not known to VirtualBox, then this method will fail.
957 *
958 * @param aVirtualBox VirtualBox object.
959 * @param aLocation Storage unit location.
960 * @param enOpenMode Whether to open the image read/write or read-only.
961 * @param aDeviceType Device type of medium.
962 * @param aSetImageId Whether to set the image UUID or not.
963 * @param aImageId New image UUID if @aSetId is true. Empty string means
964 * create a new UUID, and a zero UUID is invalid.
965 * @param aSetParentId Whether to set the parent UUID or not.
966 * @param aParentId New parent UUID if @aSetParentId is true. Empty string
967 * means create a new UUID, and a zero UUID is valid.
968 */
969HRESULT Medium::init(VirtualBox *aVirtualBox,
970 CBSTR aLocation,
971 HDDOpenMode enOpenMode,
972 DeviceType_T aDeviceType,
973 BOOL aSetImageId,
974 const Guid &aImageId,
975 BOOL aSetParentId,
976 const Guid &aParentId)
977{
978 AssertReturn(aVirtualBox, E_INVALIDARG);
979 AssertReturn(aLocation, E_INVALIDARG);
980
981 /* Enclose the state transition NotReady->InInit->Ready */
982 AutoInitSpan autoInitSpan(this);
983 AssertReturn(autoInitSpan.isOk(), E_FAIL);
984
985 HRESULT rc = S_OK;
986
987 /* share VirtualBox weakly (parent remains NULL so far) */
988 unconst(mVirtualBox) = aVirtualBox;
989
990 /* register with VirtualBox early, since uninit() will
991 * unconditionally unregister on failure */
992 aVirtualBox->addDependentChild(this);
993
994 /* there must be a storage unit */
995 m->state = MediumState_Created;
996
997 /* remember device type for correct unregistering later */
998 m->devType = aDeviceType;
999
1000 /* cannot be a host drive */
1001 m->hostDrive = FALSE;
1002
1003 /* remember the open mode (defaults to ReadWrite) */
1004 m->hddOpenMode = enOpenMode;
1005
1006 if (aDeviceType == DeviceType_HardDisk)
1007 rc = setLocation(aLocation);
1008 else
1009 rc = setLocation(aLocation, "RAW");
1010 if (FAILED(rc)) return rc;
1011
1012 /* save the new uuid values, will be used by queryInfo() */
1013 m->setImageId = aSetImageId;
1014 unconst(m->imageId) = aImageId;
1015 m->setParentId = aSetParentId;
1016 unconst(m->parentId) = aParentId;
1017
1018 /* get all the information about the medium from the storage unit */
1019 rc = queryInfo();
1020
1021 if (SUCCEEDED(rc))
1022 {
1023 /* if the storage unit is not accessible, it's not acceptable for the
1024 * newly opened media so convert this into an error */
1025 if (m->state == MediumState_Inaccessible)
1026 {
1027 Assert(!m->strLastAccessError.isEmpty());
1028 rc = setError(E_FAIL, m->strLastAccessError);
1029 }
1030 else
1031 {
1032 AssertReturn(!m->id.isEmpty(), E_FAIL);
1033
1034 /* storage format must be detected by queryInfo() if the medium is accessible */
1035 AssertReturn(!m->strFormat.isEmpty(), E_FAIL);
1036 }
1037 }
1038
1039 /* Confirm a successful initialization when it's the case */
1040 if (SUCCEEDED(rc))
1041 autoInitSpan.setSucceeded();
1042
1043 return rc;
1044}
1045
1046/**
1047 * Initializes the medium object by loading its data from the given settings
1048 * node. In this mode, the image will always be opened read/write.
1049 *
1050 * @param aVirtualBox VirtualBox object.
1051 * @param aParent Parent medium disk or NULL for a root (base) medium.
1052 * @param aDeviceType Device type of the medium.
1053 * @param aNode Configuration settings.
1054 *
1055 * @note Locks VirtualBox lock for writing, getTreeLock() for writing.
1056 */
1057HRESULT Medium::init(VirtualBox *aVirtualBox,
1058 Medium *aParent,
1059 DeviceType_T aDeviceType,
1060 const settings::Medium &data)
1061{
1062 using namespace settings;
1063
1064 AssertReturn(aVirtualBox, E_INVALIDARG);
1065
1066 /* Enclose the state transition NotReady->InInit->Ready */
1067 AutoInitSpan autoInitSpan(this);
1068 AssertReturn(autoInitSpan.isOk(), E_FAIL);
1069
1070 HRESULT rc = S_OK;
1071
1072 /* share VirtualBox and parent weakly */
1073 unconst(mVirtualBox) = aVirtualBox;
1074
1075 /* register with VirtualBox/parent early, since uninit() will
1076 * unconditionally unregister on failure */
1077 if (aParent == NULL)
1078 aVirtualBox->addDependentChild(this);
1079 else
1080 {
1081 /* we set mParent */
1082 AutoWriteLock treeLock(this->getTreeLock());
1083
1084 mParent = aParent;
1085 aParent->addDependentChild(this);
1086 }
1087
1088 /* see below why we don't call queryInfo() (and therefore treat the medium
1089 * as inaccessible for now */
1090 m->state = MediumState_Inaccessible;
1091 m->strLastAccessError = tr("Accessibility check was not yet performed");
1092
1093 /* required */
1094 unconst(m->id) = data.uuid;
1095
1096 /* assume not a host drive */
1097 m->hostDrive = FALSE;
1098
1099 /* optional */
1100 m->strDescription = data.strDescription;
1101
1102 /* required */
1103 if (aDeviceType == DeviceType_HardDisk)
1104 {
1105 AssertReturn(!data.strFormat.isEmpty(), E_FAIL);
1106 rc = setFormat(Bstr(data.strFormat));
1107 if (FAILED(rc)) return rc;
1108 }
1109 else
1110 {
1111 /// @todo handle host drive settings here as well?
1112 if (!data.strFormat.isEmpty())
1113 rc = setFormat(Bstr(data.strFormat));
1114 else
1115 rc = setFormat(Bstr("RAW"));
1116 if (FAILED(rc)) return rc;
1117 }
1118
1119 /* optional, only for diffs, default is false;
1120 * we can only auto-reset diff images, so they
1121 * must not have a parent */
1122 if (aParent != NULL)
1123 m->autoReset = data.fAutoReset;
1124 else
1125 m->autoReset = false;
1126
1127 /* properties (after setting the format as it populates the map). Note that
1128 * if some properties are not supported but preseint in the settings file,
1129 * they will still be read and accessible (for possible backward
1130 * compatibility; we can also clean them up from the XML upon next
1131 * XML format version change if we wish) */
1132 for (settings::PropertiesMap::const_iterator it = data.properties.begin();
1133 it != data.properties.end(); ++ it)
1134 {
1135 const Utf8Str &name = it->first;
1136 const Utf8Str &value = it->second;
1137 m->properties[Bstr(name)] = Bstr(value);
1138 }
1139
1140 /* required */
1141 rc = setLocation(data.strLocation);
1142 if (FAILED(rc)) return rc;
1143
1144 if (aDeviceType == DeviceType_HardDisk)
1145 {
1146 /* type is only for base hard disks */
1147 if (mParent.isNull())
1148 m->type = data.hdType;
1149 }
1150 else
1151 m->type = MediumType_Writethrough;
1152
1153 /* remember device type for correct unregistering later */
1154 m->devType = aDeviceType;
1155
1156 LogFlowThisFunc(("m->locationFull='%s', m->format=%s, m->id={%RTuuid}\n",
1157 m->strLocationFull.raw(), m->strFormat.raw(), m->id.raw()));
1158
1159 /* Don't call queryInfo() for registered media to prevent the calling
1160 * thread (i.e. the VirtualBox server startup thread) from an unexpected
1161 * freeze but mark it as initially inaccessible instead. The vital UUID,
1162 * location and format properties are read from the registry file above; to
1163 * get the actual state and the rest of the data, the user will have to call
1164 * COMGETTER(State). */
1165
1166 /* load all children */
1167 for (settings::MediaList::const_iterator it = data.llChildren.begin();
1168 it != data.llChildren.end();
1169 ++it)
1170 {
1171 const settings::Medium &med = *it;
1172
1173 ComObjPtr<Medium> pHD;
1174 pHD.createObject();
1175 rc = pHD->init(aVirtualBox,
1176 this, // parent
1177 aDeviceType,
1178 med); // child data
1179 if (FAILED(rc)) break;
1180
1181 rc = mVirtualBox->registerHardDisk(pHD, false /* aSaveRegistry */);
1182 if (FAILED(rc)) break;
1183 }
1184
1185 /* Confirm a successful initialization when it's the case */
1186 if (SUCCEEDED(rc))
1187 autoInitSpan.setSucceeded();
1188
1189 return rc;
1190}
1191
1192/**
1193 * Initializes the medium object by providing the host drive information.
1194 * Not used for anything but the host floppy/host DVD case.
1195 *
1196 * @todo optimize all callers to avoid reconstructing objects with the same
1197 * information over and over again - in the typical case each VM referring to
1198 * a particular host drive has its own instance.
1199 *
1200 * @param aVirtualBox VirtualBox object.
1201 * @param aDeviceType Device type of the medium.
1202 * @param aLocation Location of the host drive.
1203 * @param aDescription Comment for this host drive.
1204 *
1205 * @note Locks VirtualBox lock for writing, getTreeLock() for writing.
1206 */
1207HRESULT Medium::init(VirtualBox *aVirtualBox,
1208 DeviceType_T aDeviceType,
1209 CBSTR aLocation,
1210 CBSTR aDescription)
1211{
1212 ComAssertRet(aDeviceType == DeviceType_DVD || aDeviceType == DeviceType_Floppy, E_INVALIDARG);
1213 ComAssertRet(aLocation, E_INVALIDARG);
1214
1215 /* Enclose the state transition NotReady->InInit->Ready */
1216 AutoInitSpan autoInitSpan(this);
1217 AssertReturn(autoInitSpan.isOk(), E_FAIL);
1218
1219 /* share VirtualBox weakly (parent remains NULL so far) */
1220 unconst(mVirtualBox) = aVirtualBox;
1221 aVirtualBox->addDependentChild(this);
1222
1223 /* fake up a UUID which is unique, but also reproducible */
1224 RTUUID uuid;
1225 RTUuidClear(&uuid);
1226 if (aDeviceType == DeviceType_DVD)
1227 memcpy(&uuid.au8[0], "DVD", 3);
1228 else
1229 memcpy(&uuid.au8[0], "FD", 2);
1230 /* use device name, adjusted to the end of uuid, shortened if necessary */
1231 Utf8Str loc(aLocation);
1232 size_t cbLocation = strlen(loc.raw());
1233 if (cbLocation > 12)
1234 memcpy(&uuid.au8[4], loc.raw() + (cbLocation - 12), 12);
1235 else
1236 memcpy(&uuid.au8[4 + 12 - cbLocation], loc.raw(), cbLocation);
1237 unconst(m->id) = uuid;
1238
1239 m->type = MediumType_Writethrough;
1240 m->devType = aDeviceType;
1241 m->state = MediumState_Created;
1242 m->hostDrive = true;
1243 HRESULT rc = setFormat(Bstr("RAW"));
1244 if (FAILED(rc)) return rc;
1245 rc = setLocation(aLocation);
1246 if (FAILED(rc)) return rc;
1247 m->strDescription = aDescription;
1248
1249/// @todo generate uuid (similarly to host network interface uuid) from location and device type
1250
1251 autoInitSpan.setSucceeded();
1252 return S_OK;
1253}
1254
1255/**
1256 * Uninitializes the instance.
1257 *
1258 * Called either from FinalRelease() or by the parent when it gets destroyed.
1259 *
1260 * @note All children of this hard disk get uninitialized by calling their
1261 * uninit() methods.
1262 *
1263 * @note Locks getTreeLock() for writing, VirtualBox for writing.
1264 */
1265void Medium::uninit()
1266{
1267 /* Enclose the state transition Ready->InUninit->NotReady */
1268 AutoUninitSpan autoUninitSpan(this);
1269 if (autoUninitSpan.uninitDone())
1270 return;
1271
1272 if (!m->formatObj.isNull())
1273 {
1274 /* remove the caller reference we added in setFormat() */
1275 m->formatObj->releaseCaller();
1276 m->formatObj.setNull();
1277 }
1278
1279 if (m->state == MediumState_Deleting)
1280 {
1281 /* we are being uninitialized after've been deleted by merge.
1282 * Reparenting has already been done so don't touch it here (we are
1283 * now orphans and remoeDependentChild() will assert) */
1284
1285 Assert(mParent.isNull());
1286 }
1287 else
1288 {
1289 /* we uninit children and reset mParent
1290 * and VirtualBox::removeDependentChild() needs a write lock */
1291 AutoMultiWriteLock2 alock(mVirtualBox->lockHandle(), this->getTreeLock());
1292
1293 uninitDependentChildren();
1294
1295 if (!mParent.isNull())
1296 {
1297 mParent->removeDependentChild(this);
1298 mParent.setNull();
1299 }
1300 else
1301 mVirtualBox->removeDependentChild(this);
1302 }
1303
1304 RTSemEventMultiSignal(m->queryInfoSem);
1305 RTSemEventMultiDestroy(m->queryInfoSem);
1306 m->queryInfoSem = NIL_RTSEMEVENTMULTI;
1307
1308 unconst(mVirtualBox).setNull();
1309}
1310
1311////////////////////////////////////////////////////////////////////////////////
1312//
1313// IMedium public methods
1314//
1315////////////////////////////////////////////////////////////////////////////////
1316
1317STDMETHODIMP Medium::COMGETTER(Id)(BSTR *aId)
1318{
1319 CheckComArgOutPointerValid(aId);
1320
1321 AutoCaller autoCaller(this);
1322 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1323
1324 AutoReadLock alock(this);
1325
1326 m->id.toUtf16().cloneTo(aId);
1327
1328 return S_OK;
1329}
1330
1331STDMETHODIMP Medium::COMGETTER(Description)(BSTR *aDescription)
1332{
1333 CheckComArgOutPointerValid(aDescription);
1334
1335 AutoCaller autoCaller(this);
1336 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1337
1338 AutoReadLock alock(this);
1339
1340 if (m->strDescription.isEmpty())
1341 Bstr("").cloneTo(aDescription);
1342 else
1343 m->strDescription.cloneTo(aDescription);
1344
1345 return S_OK;
1346}
1347
1348STDMETHODIMP Medium::COMSETTER(Description)(IN_BSTR aDescription)
1349{
1350 CheckComArgNotNull(aDescription);
1351
1352 AutoCaller autoCaller(this);
1353 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1354
1355 AutoWriteLock alock(this);
1356
1357 /// @todo update m->description and save the global registry (and local
1358 /// registries of portable VMs referring to this medium), this will also
1359 /// require to add the mRegistered flag to data
1360
1361 ReturnComNotImplemented();
1362}
1363
1364STDMETHODIMP Medium::COMGETTER(State)(MediumState_T *aState)
1365{
1366 CheckComArgOutPointerValid(aState);
1367
1368 AutoCaller autoCaller(this);
1369 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1370
1371 AutoReadLock alock(this);
1372 *aState = m->state;
1373
1374 return S_OK;
1375}
1376
1377
1378STDMETHODIMP Medium::COMGETTER(Location)(BSTR *aLocation)
1379{
1380 CheckComArgOutPointerValid(aLocation);
1381
1382 AutoCaller autoCaller(this);
1383 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1384
1385 AutoReadLock alock(this);
1386
1387 m->strLocationFull.cloneTo(aLocation);
1388
1389 return S_OK;
1390}
1391
1392STDMETHODIMP Medium::COMSETTER(Location)(IN_BSTR aLocation)
1393{
1394 CheckComArgNotNull(aLocation);
1395
1396 AutoCaller autoCaller(this);
1397 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1398
1399 AutoWriteLock alock(this);
1400
1401 /// @todo NEWMEDIA for file names, add the default extension if no extension
1402 /// is present (using the information from the VD backend which also implies
1403 /// that one more parameter should be passed to setLocation() requesting
1404 /// that functionality since it is only allwed when called from this method
1405
1406 /// @todo NEWMEDIA rename the file and set m->location on success, then save
1407 /// the global registry (and local registries of portable VMs referring to
1408 /// this medium), this will also require to add the mRegistered flag to data
1409
1410 ReturnComNotImplemented();
1411}
1412
1413STDMETHODIMP Medium::COMGETTER(Name)(BSTR *aName)
1414{
1415 CheckComArgOutPointerValid(aName);
1416
1417 AutoCaller autoCaller(this);
1418 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1419
1420 AutoReadLock alock(this);
1421
1422 getName().cloneTo(aName);
1423
1424 return S_OK;
1425}
1426
1427STDMETHODIMP Medium::COMGETTER(DeviceType)(DeviceType_T *aDeviceType)
1428{
1429 CheckComArgOutPointerValid(aDeviceType);
1430
1431 AutoCaller autoCaller(this);
1432 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1433
1434 AutoReadLock alock(this);
1435
1436 *aDeviceType = m->devType;
1437
1438 return S_OK;
1439}
1440
1441STDMETHODIMP Medium::COMGETTER(HostDrive)(BOOL *aHostDrive)
1442{
1443 CheckComArgOutPointerValid(aHostDrive);
1444
1445 AutoCaller autoCaller(this);
1446 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1447
1448 AutoReadLock alock(this);
1449
1450 *aHostDrive = m->hostDrive;
1451
1452 return S_OK;
1453}
1454
1455STDMETHODIMP Medium::COMGETTER(Size)(ULONG64 *aSize)
1456{
1457 CheckComArgOutPointerValid(aSize);
1458
1459 AutoCaller autoCaller(this);
1460 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1461
1462 AutoReadLock alock(this);
1463
1464 *aSize = m->size;
1465
1466 return S_OK;
1467}
1468
1469STDMETHODIMP Medium::COMGETTER(Format)(BSTR *aFormat)
1470{
1471 if (aFormat == NULL)
1472 return E_POINTER;
1473
1474 AutoCaller autoCaller(this);
1475 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1476
1477 /* no need to lock, m->format is const */
1478 m->strFormat.cloneTo(aFormat);
1479
1480 return S_OK;
1481}
1482
1483STDMETHODIMP Medium::COMGETTER(Type)(MediumType_T *aType)
1484{
1485 if (aType == NULL)
1486 return E_POINTER;
1487
1488 AutoCaller autoCaller(this);
1489 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1490
1491 AutoReadLock alock(this);
1492
1493 *aType = m->type;
1494
1495 return S_OK;
1496}
1497
1498STDMETHODIMP Medium::COMSETTER(Type)(MediumType_T aType)
1499{
1500 AutoCaller autoCaller(this);
1501 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1502
1503 /* VirtualBox::saveSettings() needs a write lock */
1504 AutoMultiWriteLock2 alock(mVirtualBox, this);
1505
1506 switch (m->state)
1507 {
1508 case MediumState_Created:
1509 case MediumState_Inaccessible:
1510 break;
1511 default:
1512 return setStateError();
1513 }
1514
1515 if (m->type == aType)
1516 {
1517 /* Nothing to do */
1518 return S_OK;
1519 }
1520
1521 /* we access mParent & children() */
1522 AutoReadLock treeLock(this->getTreeLock());
1523
1524 /* cannot change the type of a differencing hard disk */
1525 if (!mParent.isNull())
1526 return setError(E_FAIL,
1527 tr("Hard disk '%s' is a differencing hard disk"),
1528 m->strLocationFull.raw());
1529
1530 /* cannot change the type of a hard disk being in use */
1531 if (m->backRefs.size() != 0)
1532 return setError(E_FAIL,
1533 tr("Hard disk '%s' is attached to %d virtual machines"),
1534 m->strLocationFull.raw(), m->backRefs.size());
1535
1536 switch (aType)
1537 {
1538 case MediumType_Normal:
1539 case MediumType_Immutable:
1540 {
1541 /* normal can be easily converted to immutable and vice versa even
1542 * if they have children as long as they are not attached to any
1543 * machine themselves */
1544 break;
1545 }
1546 case MediumType_Writethrough:
1547 {
1548 /* cannot change to writethrough if there are children */
1549 if (getChildren().size() != 0)
1550 return setError(E_FAIL,
1551 tr("Cannot change type for hard disk '%s' since it has %d child hard disk(s)"),
1552 m->strLocationFull.raw(), getChildren().size());
1553 break;
1554 }
1555 default:
1556 AssertFailedReturn(E_FAIL);
1557 }
1558
1559 m->type = aType;
1560
1561 HRESULT rc = mVirtualBox->saveSettings();
1562
1563 return rc;
1564}
1565
1566STDMETHODIMP Medium::COMGETTER(Parent)(IMedium **aParent)
1567{
1568 if (aParent == NULL)
1569 return E_POINTER;
1570
1571 AutoCaller autoCaller(this);
1572 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1573
1574 /* we access mParent */
1575 AutoReadLock treeLock(this->getTreeLock());
1576
1577 mParent.queryInterfaceTo(aParent);
1578
1579 return S_OK;
1580}
1581
1582STDMETHODIMP Medium::COMGETTER(Children)(ComSafeArrayOut(IMedium *, aChildren))
1583{
1584 if (ComSafeArrayOutIsNull(aChildren))
1585 return E_POINTER;
1586
1587 AutoCaller autoCaller(this);
1588 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1589
1590 /* we access children */
1591 AutoReadLock treeLock(this->getTreeLock());
1592
1593 SafeIfaceArray<IMedium> children(this->getChildren());
1594 children.detachTo(ComSafeArrayOutArg(aChildren));
1595
1596 return S_OK;
1597}
1598
1599STDMETHODIMP Medium::COMGETTER(Base)(IMedium **aBase)
1600{
1601 if (aBase == NULL)
1602 return E_POINTER;
1603
1604 /* base() will do callers/locking */
1605
1606 getBase().queryInterfaceTo(aBase);
1607
1608 return S_OK;
1609}
1610
1611STDMETHODIMP Medium::COMGETTER(ReadOnly)(BOOL *aReadOnly)
1612{
1613 if (aReadOnly == NULL)
1614 return E_POINTER;
1615
1616 AutoCaller autoCaller(this);
1617 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1618
1619 /* isRadOnly() will do locking */
1620
1621 *aReadOnly = isReadOnly();
1622
1623 return S_OK;
1624}
1625
1626STDMETHODIMP Medium::COMGETTER(LogicalSize)(ULONG64 *aLogicalSize)
1627{
1628 CheckComArgOutPointerValid(aLogicalSize);
1629
1630 {
1631 AutoCaller autoCaller(this);
1632 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1633
1634 AutoReadLock alock(this);
1635
1636 /* we access mParent */
1637 AutoReadLock treeLock(this->getTreeLock());
1638
1639 if (mParent.isNull())
1640 {
1641 *aLogicalSize = m->logicalSize;
1642
1643 return S_OK;
1644 }
1645 }
1646
1647 /* We assume that some backend may decide to return a meaningless value in
1648 * response to VDGetSize() for differencing hard disks and therefore
1649 * always ask the base hard disk ourselves. */
1650
1651 /* base() will do callers/locking */
1652
1653 return getBase()->COMGETTER(LogicalSize)(aLogicalSize);
1654}
1655
1656STDMETHODIMP Medium::COMGETTER(AutoReset)(BOOL *aAutoReset)
1657{
1658 CheckComArgOutPointerValid(aAutoReset);
1659
1660 AutoCaller autoCaller(this);
1661 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1662
1663 AutoReadLock alock(this);
1664
1665 if (mParent.isNull())
1666 *aAutoReset = FALSE;
1667
1668 *aAutoReset = m->autoReset;
1669
1670 return S_OK;
1671}
1672
1673STDMETHODIMP Medium::COMSETTER(AutoReset)(BOOL aAutoReset)
1674{
1675 AutoCaller autoCaller(this);
1676 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1677
1678 /* VirtualBox::saveSettings() needs a write lock */
1679 AutoMultiWriteLock2 alock(mVirtualBox, this);
1680
1681 if (mParent.isNull())
1682 return setError(VBOX_E_NOT_SUPPORTED,
1683 tr("Hard disk '%s' is not differencing"),
1684 m->strLocationFull.raw());
1685
1686 if (m->autoReset != aAutoReset)
1687 {
1688 m->autoReset = aAutoReset;
1689
1690 return mVirtualBox->saveSettings();
1691 }
1692
1693 return S_OK;
1694}
1695STDMETHODIMP Medium::COMGETTER(LastAccessError)(BSTR *aLastAccessError)
1696{
1697 CheckComArgOutPointerValid(aLastAccessError);
1698
1699 AutoCaller autoCaller(this);
1700 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1701
1702 AutoReadLock alock(this);
1703
1704 if (m->strLastAccessError.isEmpty())
1705 Bstr("").cloneTo(aLastAccessError);
1706 else
1707 m->strLastAccessError.cloneTo(aLastAccessError);
1708
1709 return S_OK;
1710}
1711
1712STDMETHODIMP Medium::COMGETTER(MachineIds)(ComSafeArrayOut(BSTR,aMachineIds))
1713{
1714 if (ComSafeGUIDArrayOutIsNull(aMachineIds))
1715 return E_POINTER;
1716
1717 AutoCaller autoCaller(this);
1718 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1719
1720 AutoReadLock alock(this);
1721
1722 com::SafeArray<BSTR> machineIds;
1723
1724 if (m->backRefs.size() != 0)
1725 {
1726 machineIds.reset(m->backRefs.size());
1727
1728 size_t i = 0;
1729 for (BackRefList::const_iterator it = m->backRefs.begin();
1730 it != m->backRefs.end(); ++ it, ++ i)
1731 {
1732 it->machineId.toUtf16().detachTo(&machineIds [i]);
1733 }
1734 }
1735
1736 machineIds.detachTo(ComSafeArrayOutArg(aMachineIds));
1737
1738 return S_OK;
1739}
1740
1741STDMETHODIMP Medium::RefreshState(MediumState_T *aState)
1742{
1743 CheckComArgOutPointerValid(aState);
1744
1745 AutoCaller autoCaller(this);
1746 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1747
1748 /* queryInfo() locks this for writing. */
1749 AutoWriteLock alock(this);
1750
1751 HRESULT rc = S_OK;
1752
1753 switch (m->state)
1754 {
1755 case MediumState_Created:
1756 case MediumState_Inaccessible:
1757 case MediumState_LockedRead:
1758 {
1759 rc = queryInfo();
1760 break;
1761 }
1762 default:
1763 break;
1764 }
1765
1766 *aState = m->state;
1767
1768 return rc;
1769}
1770
1771STDMETHODIMP Medium::GetSnapshotIds(IN_BSTR aMachineId,
1772 ComSafeArrayOut(BSTR, aSnapshotIds))
1773{
1774 CheckComArgExpr(aMachineId, Guid(aMachineId).isEmpty() == false);
1775 CheckComArgOutSafeArrayPointerValid(aSnapshotIds);
1776
1777 AutoCaller autoCaller(this);
1778 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1779
1780 AutoReadLock alock(this);
1781
1782 com::SafeArray<BSTR> snapshotIds;
1783
1784 Guid id(aMachineId);
1785 for (BackRefList::const_iterator it = m->backRefs.begin();
1786 it != m->backRefs.end(); ++ it)
1787 {
1788 if (it->machineId == id)
1789 {
1790 size_t size = it->llSnapshotIds.size();
1791
1792 /* if the medium is attached to the machine in the current state, we
1793 * return its ID as the first element of the array */
1794 if (it->fInCurState)
1795 ++size;
1796
1797 if (size > 0)
1798 {
1799 snapshotIds.reset(size);
1800
1801 size_t j = 0;
1802 if (it->fInCurState)
1803 it->machineId.toUtf16().detachTo(&snapshotIds[j++]);
1804
1805 for (BackRef::GuidList::const_iterator jt = it->llSnapshotIds.begin();
1806 jt != it->llSnapshotIds.end();
1807 ++jt, ++j)
1808 {
1809 (*jt).toUtf16().detachTo(&snapshotIds[j]);
1810 }
1811 }
1812
1813 break;
1814 }
1815 }
1816
1817 snapshotIds.detachTo(ComSafeArrayOutArg(aSnapshotIds));
1818
1819 return S_OK;
1820}
1821
1822/**
1823 * @note @a aState may be NULL if the state value is not needed (only for
1824 * in-process calls).
1825 */
1826STDMETHODIMP Medium::LockRead(MediumState_T *aState)
1827{
1828 AutoCaller autoCaller(this);
1829 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1830
1831 AutoWriteLock alock(this);
1832
1833 /* Wait for a concurrently running queryInfo() to complete */
1834 while (m->queryInfoRunning)
1835 {
1836 alock.leave();
1837 RTSemEventMultiWait(m->queryInfoSem, RT_INDEFINITE_WAIT);
1838 alock.enter();
1839 }
1840
1841 /* return the current state before */
1842 if (aState)
1843 *aState = m->state;
1844
1845 HRESULT rc = S_OK;
1846
1847 switch (m->state)
1848 {
1849 case MediumState_Created:
1850 case MediumState_Inaccessible:
1851 case MediumState_LockedRead:
1852 {
1853 ++m->readers;
1854
1855 ComAssertMsgBreak(m->readers != 0, ("Counter overflow"), rc = E_FAIL);
1856
1857 /* Remember pre-lock state */
1858 if (m->state != MediumState_LockedRead)
1859 m->preLockState = m->state;
1860
1861 LogFlowThisFunc(("Okay - prev state=%d readers=%d\n", m->state, m->readers));
1862 m->state = MediumState_LockedRead;
1863
1864 break;
1865 }
1866 default:
1867 {
1868 LogFlowThisFunc(("Failing - state=%d\n", m->state));
1869 rc = setStateError();
1870 break;
1871 }
1872 }
1873
1874 return rc;
1875}
1876
1877/**
1878 * @note @a aState may be NULL if the state value is not needed (only for
1879 * in-process calls).
1880 */
1881STDMETHODIMP Medium::UnlockRead(MediumState_T *aState)
1882{
1883 AutoCaller autoCaller(this);
1884 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1885
1886 AutoWriteLock alock(this);
1887
1888 HRESULT rc = S_OK;
1889
1890 switch (m->state)
1891 {
1892 case MediumState_LockedRead:
1893 {
1894 Assert(m->readers != 0);
1895 --m->readers;
1896
1897 /* Reset the state after the last reader */
1898 if (m->readers == 0)
1899 m->state = m->preLockState;
1900
1901 LogFlowThisFunc(("new state=%d\n", m->state));
1902 break;
1903 }
1904 default:
1905 {
1906 LogFlowThisFunc(("Failing - state=%d\n", m->state));
1907 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
1908 tr ("Medium '%s' is not locked for reading"),
1909 m->strLocationFull.raw());
1910 break;
1911 }
1912 }
1913
1914 /* return the current state after */
1915 if (aState)
1916 *aState = m->state;
1917
1918 return rc;
1919}
1920
1921/**
1922 * @note @a aState may be NULL if the state value is not needed (only for
1923 * in-process calls).
1924 */
1925STDMETHODIMP Medium::LockWrite(MediumState_T *aState)
1926{
1927 AutoCaller autoCaller(this);
1928 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1929
1930 AutoWriteLock alock(this);
1931
1932 /* Wait for a concurrently running queryInfo() to complete */
1933 while (m->queryInfoRunning)
1934 {
1935 alock.leave();
1936 RTSemEventMultiWait(m->queryInfoSem, RT_INDEFINITE_WAIT);
1937 alock.enter();
1938 }
1939
1940 /* return the current state before */
1941 if (aState)
1942 *aState = m->state;
1943
1944 HRESULT rc = S_OK;
1945
1946 switch (m->state)
1947 {
1948 case MediumState_Created:
1949 case MediumState_Inaccessible:
1950 {
1951 m->preLockState = m->state;
1952
1953 LogFlowThisFunc(("Okay - prev state=%d locationFull=%s\n", m->state, getLocationFull().c_str()));
1954 m->state = MediumState_LockedWrite;
1955 break;
1956 }
1957 default:
1958 {
1959 LogFlowThisFunc(("Failing - state=%d locationFull=%s\n", m->state, getLocationFull().c_str()));
1960 rc = setStateError();
1961 break;
1962 }
1963 }
1964
1965 return rc;
1966}
1967
1968/**
1969 * @note @a aState may be NULL if the state value is not needed (only for
1970 * in-process calls).
1971 */
1972STDMETHODIMP Medium::UnlockWrite(MediumState_T *aState)
1973{
1974 AutoCaller autoCaller(this);
1975 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1976
1977 AutoWriteLock alock(this);
1978
1979 HRESULT rc = S_OK;
1980
1981 switch (m->state)
1982 {
1983 case MediumState_LockedWrite:
1984 {
1985 m->state = m->preLockState;
1986 LogFlowThisFunc(("new state=%d locationFull=%s\n", m->state, getLocationFull().c_str()));
1987 break;
1988 }
1989 default:
1990 {
1991 LogFlowThisFunc(("Failing - state=%d locationFull=%s\n", m->state, getLocationFull().c_str()));
1992 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
1993 tr ("Medium '%s' is not locked for writing"),
1994 m->strLocationFull.raw());
1995 break;
1996 }
1997 }
1998
1999 /* return the current state after */
2000 if (aState)
2001 *aState = m->state;
2002
2003 return rc;
2004}
2005
2006STDMETHODIMP Medium::Close()
2007{
2008 AutoMayUninitSpan mayUninitSpan(this);
2009 if (FAILED(mayUninitSpan.rc())) return mayUninitSpan.rc();
2010
2011 if (mayUninitSpan.alreadyInProgress())
2012 return S_OK;
2013
2014 /* unregisterWithVirtualBox() is assumed to always need a write mVirtualBox
2015 * lock as it is intenede to modify its internal structires. Also, we want
2016 * to unregister ourselves atomically after detecting that closure is
2017 * possible to make sure that we don't do that after another thread has done
2018 * VirtualBox::find*() but before it starts using us (provided that it holds
2019 * a mVirtualBox lock of course). */
2020
2021 AutoWriteLock vboxLock(mVirtualBox);
2022
2023 bool wasCreated = true;
2024
2025 switch (m->state)
2026 {
2027 case MediumState_NotCreated:
2028 wasCreated = false;
2029 break;
2030 case MediumState_Created:
2031 case MediumState_Inaccessible:
2032 break;
2033 default:
2034 return setStateError();
2035 }
2036
2037 if (m->backRefs.size() != 0)
2038 return setError(VBOX_E_OBJECT_IN_USE,
2039 tr("Medium '%s' is attached to %d virtual machines"),
2040 m->strLocationFull.raw(), m->backRefs.size());
2041
2042 /* perform extra media-dependent close checks */
2043 HRESULT rc = canClose();
2044 if (FAILED(rc)) return rc;
2045
2046 if (wasCreated)
2047 {
2048 /* remove from the list of known media before performing actual
2049 * uninitialization (to keep the media registry consistent on
2050 * failure to do so) */
2051 rc = unregisterWithVirtualBox();
2052 if (FAILED(rc)) return rc;
2053 }
2054
2055 /* cause uninit() to happen on success */
2056 mayUninitSpan.acceptUninit();
2057
2058 return S_OK;
2059}
2060
2061STDMETHODIMP Medium::GetProperty(IN_BSTR aName, BSTR *aValue)
2062{
2063 CheckComArgStrNotEmptyOrNull(aName);
2064 CheckComArgOutPointerValid(aValue);
2065
2066 AutoCaller autoCaller(this);
2067 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2068
2069 AutoReadLock alock(this);
2070
2071 Data::PropertyMap::const_iterator it = m->properties.find(Bstr(aName));
2072 if (it == m->properties.end())
2073 return setError(VBOX_E_OBJECT_NOT_FOUND,
2074 tr("Property '%ls' does not exist"), aName);
2075
2076 if (it->second.isEmpty())
2077 Bstr("").cloneTo(aValue);
2078 else
2079 it->second.cloneTo(aValue);
2080
2081 return S_OK;
2082}
2083
2084STDMETHODIMP Medium::SetProperty(IN_BSTR aName, IN_BSTR aValue)
2085{
2086 CheckComArgStrNotEmptyOrNull(aName);
2087
2088 AutoCaller autoCaller(this);
2089 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2090
2091 /* VirtualBox::saveSettings() needs a write lock */
2092 AutoMultiWriteLock2 alock(mVirtualBox, this);
2093
2094 switch (m->state)
2095 {
2096 case MediumState_Created:
2097 case MediumState_Inaccessible:
2098 break;
2099 default:
2100 return setStateError();
2101 }
2102
2103 Data::PropertyMap::iterator it = m->properties.find(Bstr(aName));
2104 if (it == m->properties.end())
2105 return setError(VBOX_E_OBJECT_NOT_FOUND,
2106 tr("Property '%ls' does not exist"),
2107 aName);
2108
2109 if (aValue && !*aValue)
2110 it->second = (const char *)NULL;
2111 else
2112 it->second = aValue;
2113
2114 HRESULT rc = mVirtualBox->saveSettings();
2115
2116 return rc;
2117}
2118
2119STDMETHODIMP Medium::GetProperties(IN_BSTR aNames,
2120 ComSafeArrayOut(BSTR, aReturnNames),
2121 ComSafeArrayOut(BSTR, aReturnValues))
2122{
2123 CheckComArgOutSafeArrayPointerValid(aReturnNames);
2124 CheckComArgOutSafeArrayPointerValid(aReturnValues);
2125
2126 AutoCaller autoCaller(this);
2127 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2128
2129 AutoReadLock alock(this);
2130
2131 /// @todo make use of aNames according to the documentation
2132 NOREF(aNames);
2133
2134 com::SafeArray<BSTR> names(m->properties.size());
2135 com::SafeArray<BSTR> values(m->properties.size());
2136 size_t i = 0;
2137
2138 for (Data::PropertyMap::const_iterator it = m->properties.begin();
2139 it != m->properties.end();
2140 ++it)
2141 {
2142 it->first.cloneTo(&names[i]);
2143 if (it->second.isEmpty())
2144 Bstr("").cloneTo(&values [i]);
2145 else
2146 it->second.cloneTo(&values [i]);
2147 ++ i;
2148 }
2149
2150 names.detachTo(ComSafeArrayOutArg(aReturnNames));
2151 values.detachTo(ComSafeArrayOutArg(aReturnValues));
2152
2153 return S_OK;
2154}
2155
2156STDMETHODIMP Medium::SetProperties(ComSafeArrayIn(IN_BSTR, aNames),
2157 ComSafeArrayIn(IN_BSTR, aValues))
2158{
2159 CheckComArgSafeArrayNotNull(aNames);
2160 CheckComArgSafeArrayNotNull(aValues);
2161
2162 AutoCaller autoCaller(this);
2163 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2164
2165 /* VirtualBox::saveSettings() needs a write lock */
2166 AutoMultiWriteLock2 alock(mVirtualBox, this);
2167
2168 com::SafeArray<IN_BSTR> names(ComSafeArrayInArg(aNames));
2169 com::SafeArray<IN_BSTR> values(ComSafeArrayInArg(aValues));
2170
2171 /* first pass: validate names */
2172 for (size_t i = 0;
2173 i < names.size();
2174 ++i)
2175 {
2176 if (m->properties.find(Bstr(names[i])) == m->properties.end())
2177 return setError(VBOX_E_OBJECT_NOT_FOUND,
2178 tr("Property '%ls' does not exist"), names[i]);
2179 }
2180
2181 /* second pass: assign */
2182 for (size_t i = 0;
2183 i < names.size();
2184 ++i)
2185 {
2186 Data::PropertyMap::iterator it = m->properties.find(Bstr(names[i]));
2187 AssertReturn(it != m->properties.end(), E_FAIL);
2188
2189 if (values[i] && !*values[i])
2190 it->second = (const char *)NULL;
2191 else
2192 it->second = values [i];
2193 }
2194
2195 HRESULT rc = mVirtualBox->saveSettings();
2196
2197 return rc;
2198}
2199
2200STDMETHODIMP Medium::CreateBaseStorage(ULONG64 aLogicalSize,
2201 MediumVariant_T aVariant,
2202 IProgress **aProgress)
2203{
2204 CheckComArgOutPointerValid(aProgress);
2205
2206 AutoCaller autoCaller(this);
2207 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2208
2209 AutoWriteLock alock(this);
2210
2211 aVariant = (MediumVariant_T)((unsigned)aVariant & (unsigned)~MediumVariant_Diff);
2212 if ( !(aVariant & MediumVariant_Fixed)
2213 && !(m->formatObj->capabilities() & MediumFormatCapabilities_CreateDynamic))
2214 return setError(VBOX_E_NOT_SUPPORTED,
2215 tr("Hard disk format '%s' does not support dynamic storage creation"),
2216 m->strFormat.raw());
2217 if ( (aVariant & MediumVariant_Fixed)
2218 && !(m->formatObj->capabilities() & MediumFormatCapabilities_CreateDynamic))
2219 return setError(VBOX_E_NOT_SUPPORTED,
2220 tr("Hard disk format '%s' does not support fixed storage creation"),
2221 m->strFormat.raw());
2222
2223 switch (m->state)
2224 {
2225 case MediumState_NotCreated:
2226 break;
2227 default:
2228 return setStateError();
2229 }
2230
2231 ComObjPtr <Progress> progress;
2232 progress.createObject();
2233 /// @todo include fixed/dynamic
2234 HRESULT rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
2235 (aVariant & MediumVariant_Fixed)
2236 ? BstrFmt(tr("Creating fixed hard disk storage unit '%s'"), m->strLocationFull.raw())
2237 : BstrFmt(tr("Creating dynamic hard disk storage unit '%s'"), m->strLocationFull.raw()),
2238 TRUE /* aCancelable */);
2239 if (FAILED(rc)) return rc;
2240
2241 /* setup task object and thread to carry out the operation
2242 * asynchronously */
2243
2244 std::auto_ptr <Task> task(new Task(this, progress, Task::CreateBase));
2245 AssertComRCReturnRC(task->m_autoCaller.rc());
2246
2247 task->d.size = aLogicalSize;
2248 task->d.variant = aVariant;
2249
2250 rc = task->startThread();
2251 if (FAILED(rc)) return rc;
2252
2253 /* go to Creating state on success */
2254 m->state = MediumState_Creating;
2255
2256 /* task is now owned by taskThread() so release it */
2257 task.release();
2258
2259 /* return progress to the caller */
2260 progress.queryInterfaceTo(aProgress);
2261
2262 return S_OK;
2263}
2264
2265STDMETHODIMP Medium::DeleteStorage(IProgress **aProgress)
2266{
2267 CheckComArgOutPointerValid(aProgress);
2268
2269 AutoCaller autoCaller(this);
2270 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2271
2272 ComObjPtr <Progress> progress;
2273
2274 HRESULT rc = deleteStorageNoWait(progress);
2275 if (SUCCEEDED(rc))
2276 {
2277 /* return progress to the caller */
2278 progress.queryInterfaceTo(aProgress);
2279 }
2280
2281 return rc;
2282}
2283
2284STDMETHODIMP Medium::CreateDiffStorage(IMedium *aTarget,
2285 MediumVariant_T aVariant,
2286 IProgress **aProgress)
2287{
2288 CheckComArgNotNull(aTarget);
2289 CheckComArgOutPointerValid(aProgress);
2290
2291 AutoCaller autoCaller(this);
2292 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2293
2294 ComObjPtr<Medium> diff;
2295 HRESULT rc = mVirtualBox->cast(aTarget, diff);
2296 if (FAILED(rc)) return rc;
2297
2298 AutoWriteLock alock(this);
2299
2300 if (m->type == MediumType_Writethrough)
2301 return setError(E_FAIL,
2302 tr("Hard disk '%s' is Writethrough"),
2303 m->strLocationFull.raw());
2304
2305 /* We want to be locked for reading as long as our diff child is being
2306 * created */
2307 rc = LockRead(NULL);
2308 if (FAILED(rc)) return rc;
2309
2310 ComObjPtr <Progress> progress;
2311
2312 rc = createDiffStorageNoWait(diff, aVariant, progress);
2313 if (FAILED(rc))
2314 {
2315 HRESULT rc2 = UnlockRead(NULL);
2316 AssertComRC(rc2);
2317 /* Note: on success, taskThread() will unlock this */
2318 }
2319 else
2320 {
2321 /* return progress to the caller */
2322 progress.queryInterfaceTo(aProgress);
2323 }
2324
2325 return rc;
2326}
2327
2328STDMETHODIMP Medium::MergeTo(IN_BSTR /* aTargetId */, IProgress ** /* aProgress */)
2329{
2330 AutoCaller autoCaller(this);
2331 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2332
2333 ReturnComNotImplemented();
2334}
2335
2336STDMETHODIMP Medium::CloneTo(IMedium *aTarget,
2337 MediumVariant_T aVariant,
2338 IMedium *aParent,
2339 IProgress **aProgress)
2340{
2341 CheckComArgNotNull(aTarget);
2342 CheckComArgOutPointerValid(aProgress);
2343
2344 AutoCaller autoCaller(this);
2345 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2346
2347 ComObjPtr <Medium> target;
2348 HRESULT rc = mVirtualBox->cast(aTarget, target);
2349 if (FAILED(rc)) return rc;
2350 ComObjPtr <Medium> parent;
2351 if (aParent)
2352 {
2353 rc = mVirtualBox->cast(aParent, parent);
2354 if (FAILED(rc)) return rc;
2355 }
2356
2357 AutoMultiWriteLock3 alock(this, target, parent);
2358
2359 ComObjPtr <Progress> progress;
2360
2361 try
2362 {
2363 if ( target->m->state != MediumState_NotCreated
2364 && target->m->state != MediumState_Created)
2365 throw target->setStateError();
2366
2367 /** @todo separate out creating/locking an image chain from
2368 * SessionMachine::lockMedia and use it from here too.
2369 * logically this belongs into Medium functionality. */
2370
2371 /* Build the source chain and lock images in the proper order. */
2372 std::auto_ptr <ImageChain> srcChain(new ImageChain());
2373
2374 /* we walk the source tree */
2375 AutoReadLock srcTreeLock(this->getTreeLock());
2376 for (Medium *hd = this; hd; hd = hd->mParent)
2377 {
2378 rc = srcChain->addImage(hd);
2379 if (FAILED(rc)) throw rc;
2380 }
2381 rc = srcChain->lockImagesRead();
2382 if (FAILED(rc)) throw rc;
2383
2384 /* Build the parent chain and lock images in the proper order. */
2385 std::auto_ptr <ImageChain> parentChain(new ImageChain());
2386
2387 /* we walk the future parent tree */
2388 AutoReadLock parentTreeLock;
2389 if (parent)
2390 parentTreeLock.attach(parent->getTreeLock());
2391 for (Medium *hd = parent; hd; hd = hd->mParent)
2392 {
2393 rc = parentChain->addImage(hd);
2394 if (FAILED(rc)) throw rc;
2395 }
2396 if (target->m->state == MediumState_Created)
2397 {
2398 /* If we're cloning to an existing image the parent chain also
2399 * contains the target image, and it gets locked for writing. */
2400 rc = parentChain->addImage(target);
2401 if (FAILED(rc)) throw rc;
2402 rc = parentChain->lockImagesReadAndLastWrite();
2403 if (FAILED(rc)) throw rc;
2404 }
2405 else
2406 {
2407 rc = parentChain->lockImagesRead();
2408 if (FAILED(rc)) throw rc;
2409 }
2410
2411 progress.createObject();
2412 rc = progress->init(mVirtualBox, static_cast <IMedium *>(this),
2413 BstrFmt(tr("Creating clone hard disk '%s'"),
2414 target->m->strLocationFull.raw()),
2415 TRUE /* aCancelable */);
2416 if (FAILED(rc)) throw rc;
2417
2418 /* setup task object and thread to carry out the operation
2419 * asynchronously */
2420
2421 std::auto_ptr <Task> task(new Task(this, progress, Task::Clone));
2422 AssertComRCThrowRC(task->m_autoCaller.rc());
2423
2424 task->setData(target, parent);
2425 task->d.variant = aVariant;
2426 task->setData(srcChain.release(), parentChain.release());
2427
2428 rc = task->startThread();
2429 if (FAILED(rc)) throw rc;
2430
2431 if (target->m->state == MediumState_NotCreated)
2432 {
2433 /* go to Creating state before leaving the lock */
2434 target->m->state = MediumState_Creating;
2435 }
2436
2437 /* task is now owned (or already deleted) by taskThread() so release it */
2438 task.release();
2439 }
2440 catch (HRESULT aRC)
2441 {
2442 rc = aRC;
2443 }
2444
2445 if (SUCCEEDED(rc))
2446 {
2447 /* return progress to the caller */
2448 progress.queryInterfaceTo(aProgress);
2449 }
2450
2451 return rc;
2452}
2453
2454STDMETHODIMP Medium::Compact(IProgress **aProgress)
2455{
2456 CheckComArgOutPointerValid(aProgress);
2457
2458 AutoCaller autoCaller(this);
2459 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2460
2461 AutoWriteLock alock(this);
2462
2463 ComObjPtr <Progress> progress;
2464
2465 HRESULT rc = S_OK;
2466
2467 try
2468 {
2469 /** @todo separate out creating/locking an image chain from
2470 * SessionMachine::lockMedia and use it from here too.
2471 * logically this belongs into Medium functionality. */
2472
2473 /* Build the image chain and lock images in the proper order. */
2474 std::auto_ptr <ImageChain> imgChain(new ImageChain());
2475
2476 /* we walk the image tree */
2477 AutoReadLock srcTreeLock(this->getTreeLock());
2478 for (Medium *hd = this; hd; hd = hd->mParent)
2479 {
2480 rc = imgChain->addImage(hd);
2481 if (FAILED(rc)) throw rc;
2482 }
2483 rc = imgChain->lockImagesReadAndLastWrite();
2484 if (FAILED(rc)) throw rc;
2485
2486 progress.createObject();
2487 rc = progress->init(mVirtualBox, static_cast <IMedium *>(this),
2488 BstrFmt(tr("Compacting hard disk '%s'"), m->strLocationFull.raw()),
2489 TRUE /* aCancelable */);
2490 if (FAILED(rc)) throw rc;
2491
2492 /* setup task object and thread to carry out the operation
2493 * asynchronously */
2494
2495 std::auto_ptr <Task> task(new Task(this, progress, Task::Compact));
2496 AssertComRCThrowRC(task->m_autoCaller.rc());
2497
2498 task->setData(imgChain.release());
2499
2500 rc = task->startThread();
2501 if (FAILED(rc)) throw rc;
2502
2503 /* task is now owned (or already deleted) by taskThread() so release it */
2504 task.release();
2505 }
2506 catch (HRESULT aRC)
2507 {
2508 rc = aRC;
2509 }
2510
2511 if (SUCCEEDED(rc))
2512 {
2513 /* return progress to the caller */
2514 progress.queryInterfaceTo(aProgress);
2515 }
2516
2517 return rc;
2518}
2519
2520STDMETHODIMP Medium::Resize(ULONG64 aLogicalSize, IProgress **aProgress)
2521{
2522 CheckComArgOutPointerValid(aProgress);
2523
2524 AutoCaller autoCaller(this);
2525 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2526
2527 NOREF(aLogicalSize);
2528 NOREF(aProgress);
2529 ReturnComNotImplemented();
2530}
2531
2532STDMETHODIMP Medium::Reset(IProgress **aProgress)
2533{
2534 CheckComArgOutPointerValid(aProgress);
2535
2536 AutoCaller autoCaller(this);
2537 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2538
2539 AutoWriteLock alock(this);
2540
2541 LogFlowThisFunc(("ENTER for medium %s\n", m->strLocationFull.c_str()));
2542
2543 if (mParent.isNull())
2544 return setError(VBOX_E_NOT_SUPPORTED,
2545 tr ("Hard disk '%s' is not differencing"),
2546 m->strLocationFull.raw());
2547
2548 HRESULT rc = canClose();
2549 if (FAILED(rc)) return rc;
2550
2551 rc = LockWrite(NULL);
2552 if (FAILED(rc)) return rc;
2553
2554 ComObjPtr <Progress> progress;
2555
2556 try
2557 {
2558 progress.createObject();
2559 rc = progress->init(mVirtualBox, static_cast <IMedium *>(this),
2560 BstrFmt(tr("Resetting differencing hard disk '%s'"),
2561 m->strLocationFull.raw()),
2562 FALSE /* aCancelable */);
2563 if (FAILED(rc)) throw rc;
2564
2565 /* setup task object and thread to carry out the operation
2566 * asynchronously */
2567
2568 std::auto_ptr<Task> task(new Task(this, progress, Task::Reset));
2569 AssertComRCThrowRC(task->m_autoCaller.rc());
2570
2571 rc = task->startThread();
2572 if (FAILED(rc)) throw rc;
2573
2574 /* task is now owned (or already deleted) by taskThread() so release it */
2575 task.release();
2576 }
2577 catch (HRESULT aRC)
2578 {
2579 rc = aRC;
2580 }
2581
2582 if (FAILED (rc))
2583 {
2584 HRESULT rc2 = UnlockWrite(NULL);
2585 AssertComRC(rc2);
2586 /* Note: on success, taskThread() will unlock this */
2587 }
2588 else
2589 {
2590 /* return progress to the caller */
2591 progress.queryInterfaceTo(aProgress);
2592 }
2593
2594 LogFlowThisFunc(("LEAVE, rc=%Rhrc\n", rc));
2595
2596 return rc;
2597}
2598
2599////////////////////////////////////////////////////////////////////////////////
2600//
2601// Medium internal methods
2602//
2603////////////////////////////////////////////////////////////////////////////////
2604
2605/**
2606 * Checks if the given change of \a aOldPath to \a aNewPath affects the location
2607 * of this media and updates it if necessary to reflect the new location.
2608 *
2609 * @param aOldPath Old path (full).
2610 * @param aNewPath New path (full).
2611 *
2612 * @note Locks this object for writing.
2613 */
2614HRESULT Medium::updatePath(const char *aOldPath, const char *aNewPath)
2615{
2616 AssertReturn(aOldPath, E_FAIL);
2617 AssertReturn(aNewPath, E_FAIL);
2618
2619 AutoCaller autoCaller(this);
2620 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2621
2622 AutoWriteLock alock(this);
2623
2624 LogFlowThisFunc(("locationFull.before='%s'\n", m->strLocationFull.raw()));
2625
2626 Utf8Str path = m->strLocationFull;
2627
2628 if (RTPathStartsWith(path.c_str(), aOldPath))
2629 {
2630 Utf8Str newPath = Utf8StrFmt("%s%s", aNewPath,
2631 path.raw() + strlen(aOldPath));
2632 path = newPath;
2633
2634 mVirtualBox->calculateRelativePath(path, path);
2635
2636 unconst(m->strLocationFull) = newPath;
2637 unconst(m->strLocation) = path;
2638
2639 LogFlowThisFunc(("locationFull.after='%s'\n", m->strLocationFull.raw()));
2640 }
2641
2642 return S_OK;
2643}
2644
2645/**
2646 * Adds the given machine and optionally the snapshot to the list of the objects
2647 * this image is attached to.
2648 *
2649 * @param aMachineId Machine ID.
2650 * @param aSnapshotId Snapshot ID; when non-empty, adds a snapshot attachment.
2651 */
2652HRESULT Medium::attachTo(const Guid &aMachineId,
2653 const Guid &aSnapshotId /*= Guid::Empty*/)
2654{
2655 AssertReturn(!aMachineId.isEmpty(), E_FAIL);
2656
2657 LogFlowThisFunc(("ENTER, aMachineId: {%RTuuid}, aSnapshotId: {%RTuuid}\n", aMachineId.raw(), aSnapshotId.raw()));
2658
2659 AutoCaller autoCaller(this);
2660 AssertComRCReturnRC(autoCaller.rc());
2661
2662 AutoWriteLock alock(this);
2663
2664 switch (m->state)
2665 {
2666 case MediumState_Created:
2667 case MediumState_Inaccessible:
2668 case MediumState_LockedRead:
2669 case MediumState_LockedWrite:
2670 break;
2671
2672 default:
2673 return setStateError();
2674 }
2675
2676 if (m->numCreateDiffTasks > 0)
2677 return setError(E_FAIL,
2678 tr("Cannot attach hard disk '%s' {%RTuuid}: %u differencing child hard disk(s) are being created"),
2679 m->strLocationFull.raw(),
2680 m->id.raw(),
2681 m->numCreateDiffTasks);
2682
2683 BackRefList::iterator it =
2684 std::find_if(m->backRefs.begin(), m->backRefs.end(),
2685 BackRef::EqualsTo(aMachineId));
2686 if (it == m->backRefs.end())
2687 {
2688 BackRef ref(aMachineId, aSnapshotId);
2689 m->backRefs.push_back(ref);
2690
2691 return S_OK;
2692 }
2693
2694 // if the caller has not supplied a snapshot ID, then we're attaching
2695 // to a machine a medium which represents the machine's current state,
2696 // so set the flag
2697 if (aSnapshotId.isEmpty())
2698 {
2699 /* sanity: no duplicate attachments */
2700 AssertReturn(!it->fInCurState, E_FAIL);
2701 it->fInCurState = true;
2702
2703 return S_OK;
2704 }
2705
2706 // otherwise: a snapshot medium is being attached
2707
2708 /* sanity: no duplicate attachments */
2709 for (BackRef::GuidList::const_iterator jt = it->llSnapshotIds.begin();
2710 jt != it->llSnapshotIds.end();
2711 ++jt)
2712 {
2713 const Guid &idOldSnapshot = *jt;
2714
2715 if (idOldSnapshot == aSnapshotId)
2716 return setError(E_FAIL,
2717 tr("Cannot attach medium '%s' {%RTuuid} from snapshot '%RTuuid': medium is already in use by snapshot '%RTuuid'"),
2718 m->strLocationFull.raw(),
2719 m->id.raw(),
2720 aSnapshotId.raw(),
2721 idOldSnapshot.raw());
2722 }
2723
2724 it->llSnapshotIds.push_back(aSnapshotId);
2725 it->fInCurState = false;
2726
2727 LogFlowThisFuncLeave();
2728
2729 return S_OK;
2730}
2731
2732/**
2733 * Removes the given machine and optionally the snapshot from the list of the
2734 * objects this image is attached to.
2735 *
2736 * @param aMachineId Machine ID.
2737 * @param aSnapshotId Snapshot ID; when non-empty, removes the snapshot
2738 * attachment.
2739 */
2740HRESULT Medium::detachFrom(const Guid &aMachineId,
2741 const Guid &aSnapshotId /*= Guid::Empty*/)
2742{
2743 AssertReturn(!aMachineId.isEmpty(), E_FAIL);
2744
2745 AutoCaller autoCaller(this);
2746 AssertComRCReturnRC(autoCaller.rc());
2747
2748 AutoWriteLock alock(this);
2749
2750 BackRefList::iterator it =
2751 std::find_if(m->backRefs.begin(), m->backRefs.end(),
2752 BackRef::EqualsTo(aMachineId));
2753 AssertReturn(it != m->backRefs.end(), E_FAIL);
2754
2755 if (aSnapshotId.isEmpty())
2756 {
2757 /* remove the current state attachment */
2758 it->fInCurState = false;
2759 }
2760 else
2761 {
2762 /* remove the snapshot attachment */
2763 BackRef::GuidList::iterator jt =
2764 std::find(it->llSnapshotIds.begin(), it->llSnapshotIds.end(), aSnapshotId);
2765
2766 AssertReturn(jt != it->llSnapshotIds.end(), E_FAIL);
2767 it->llSnapshotIds.erase(jt);
2768 }
2769
2770 /* if the backref becomes empty, remove it */
2771 if (it->fInCurState == false && it->llSnapshotIds.size() == 0)
2772 m->backRefs.erase(it);
2773
2774 return S_OK;
2775}
2776
2777#ifdef DEBUG
2778/**
2779 * Debugging helper that gets called after VirtualBox initialization that writes all
2780 * machine backreferences to the debug log.
2781 */
2782void Medium::dumpBackRefs()
2783{
2784 AutoCaller autoCaller(this);
2785 AutoReadLock(this);
2786
2787 LogFlowThisFunc(("Dumping backrefs for medium '%s':\n", m->strLocationFull.raw()));
2788
2789 for (BackRefList::iterator it2 = m->backRefs.begin();
2790 it2 != m->backRefs.end();
2791 ++it2)
2792 {
2793 const BackRef &ref = *it2;
2794 LogFlowThisFunc((" Backref from machine {%RTuuid} (fInCurState: %d)\n", ref.machineId.raw(), ref.fInCurState));
2795
2796 for (BackRef::GuidList::const_iterator jt2 = it2->llSnapshotIds.begin();
2797 jt2 != it2->llSnapshotIds.end();
2798 ++jt2)
2799 {
2800 const Guid &id = *jt2;
2801 LogFlowThisFunc((" Backref from snapshot {%RTuuid}\n", id.raw()));
2802 }
2803 }
2804}
2805#endif
2806
2807/**
2808 * Internal method to return the medium's GUID. Must have caller + locking!
2809 * @return
2810 */
2811const Guid& Medium::getId() const
2812{
2813 return m->id;
2814}
2815
2816/**
2817 * Internal method to return the medium's GUID. Must have caller + locking!
2818 * @return
2819 */
2820MediumState_T Medium::getState() const
2821{
2822 return m->state;
2823}
2824
2825/**
2826 * Internal method to return the medium's location. Must have caller + locking!
2827 * @return
2828 */
2829const Utf8Str& Medium::getLocation() const
2830{
2831 return m->strLocation;
2832}
2833
2834/**
2835 * Internal method to return the medium's full location. Must have caller + locking!
2836 * @return
2837 */
2838const Utf8Str& Medium::getLocationFull() const
2839{
2840 return m->strLocationFull;
2841}
2842
2843/**
2844 * Internal method to return the medium's size. Must have caller + locking!
2845 * @return
2846 */
2847uint64_t Medium::getSize() const
2848{
2849 return m->size;
2850}
2851
2852/**
2853 * Internal method to return the medium's list of backrefs. Must have caller + locking!
2854 * @return
2855 */
2856const Guid* Medium::getFirstMachineBackrefId() const
2857{
2858 if (!m->backRefs.size())
2859 return NULL;
2860
2861 return &m->backRefs.front().machineId;
2862}
2863
2864const Guid* Medium::getFirstMachineBackrefSnapshotId() const
2865{
2866 if (!m->backRefs.size())
2867 return NULL;
2868
2869 const BackRef &ref = m->backRefs.front();
2870 if (!ref.llSnapshotIds.size())
2871 return NULL;
2872
2873 return &ref.llSnapshotIds.front();
2874}
2875
2876/**
2877 * Checks if the given change of \a aOldPath to \a aNewPath affects the location
2878 * of this hard disk or any its child and updates the paths if necessary to
2879 * reflect the new location.
2880 *
2881 * @param aOldPath Old path (full).
2882 * @param aNewPath New path (full).
2883 *
2884 * @note Locks getTreeLock() for reading, this object and all children for writing.
2885 */
2886void Medium::updatePaths(const char *aOldPath, const char *aNewPath)
2887{
2888 AssertReturnVoid(aOldPath);
2889 AssertReturnVoid(aNewPath);
2890
2891 AutoCaller autoCaller(this);
2892 AssertComRCReturnVoid(autoCaller.rc());
2893
2894 AutoWriteLock alock(this);
2895
2896 /* we access children() */
2897 AutoReadLock treeLock(this->getTreeLock());
2898
2899 updatePath(aOldPath, aNewPath);
2900
2901 /* update paths of all children */
2902 for (List::const_iterator it = getChildren().begin();
2903 it != getChildren().end();
2904 ++ it)
2905 {
2906 (*it)->updatePaths(aOldPath, aNewPath);
2907 }
2908}
2909
2910/**
2911 * Returns the base hard disk of the hard disk chain this hard disk is part of.
2912 *
2913 * The base hard disk is found by walking up the parent-child relationship axis.
2914 * If the hard disk doesn't have a parent (i.e. it's a base hard disk), it
2915 * returns itself in response to this method.
2916 *
2917 * @param aLevel Where to store the number of ancestors of this hard disk
2918 * (zero for the base), may be @c NULL.
2919 *
2920 * @note Locks getTreeLock() for reading.
2921 */
2922ComObjPtr<Medium> Medium::getBase(uint32_t *aLevel /*= NULL*/)
2923{
2924 ComObjPtr<Medium> pBase;
2925 uint32_t level;
2926
2927 AutoCaller autoCaller(this);
2928 AssertReturn(autoCaller.isOk(), pBase);
2929
2930 /* we access mParent */
2931 AutoReadLock treeLock(this->getTreeLock());
2932
2933 pBase = this;
2934 level = 0;
2935
2936 if (!mParent.isNull())
2937 {
2938 for (;;)
2939 {
2940 AutoCaller baseCaller(pBase);
2941 AssertReturn(baseCaller.isOk(), pBase);
2942
2943 if (pBase->mParent.isNull())
2944 break;
2945
2946 pBase = pBase->mParent;
2947 ++level;
2948 }
2949 }
2950
2951 if (aLevel != NULL)
2952 *aLevel = level;
2953
2954 return pBase;
2955}
2956
2957/**
2958 * Returns @c true if this hard disk cannot be modified because it has
2959 * dependants (children) or is part of the snapshot. Related to the hard disk
2960 * type and posterity, not to the current media state.
2961 *
2962 * @note Locks this object and getTreeLock() for reading.
2963 */
2964bool Medium::isReadOnly()
2965{
2966 AutoCaller autoCaller(this);
2967 AssertComRCReturn(autoCaller.rc(), false);
2968
2969 AutoReadLock alock(this);
2970
2971 /* we access children */
2972 AutoReadLock treeLock(this->getTreeLock());
2973
2974 switch (m->type)
2975 {
2976 case MediumType_Normal:
2977 {
2978 if (getChildren().size() != 0)
2979 return true;
2980
2981 for (BackRefList::const_iterator it = m->backRefs.begin();
2982 it != m->backRefs.end(); ++ it)
2983 if (it->llSnapshotIds.size() != 0)
2984 return true;
2985
2986 return false;
2987 }
2988 case MediumType_Immutable:
2989 {
2990 return true;
2991 }
2992 case MediumType_Writethrough:
2993 {
2994 return false;
2995 }
2996 default:
2997 break;
2998 }
2999
3000 AssertFailedReturn(false);
3001}
3002
3003/**
3004 * Saves hard disk data by appending a new <HardDisk> child node to the given
3005 * parent node which can be either <HardDisks> or <HardDisk>.
3006 *
3007 * @param data Settings struct to be updated.
3008 *
3009 * @note Locks this object, getTreeLock() and children for reading.
3010 */
3011HRESULT Medium::saveSettings(settings::Medium &data)
3012{
3013 AutoCaller autoCaller(this);
3014 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3015
3016 AutoReadLock alock(this);
3017
3018 /* we access mParent */
3019 AutoReadLock treeLock(this->getTreeLock());
3020
3021 data.uuid = m->id;
3022 data.strLocation = m->strLocation;
3023 data.strFormat = m->strFormat;
3024
3025 /* optional, only for diffs, default is false */
3026 if (!mParent.isNull())
3027 data.fAutoReset = !!m->autoReset;
3028 else
3029 data.fAutoReset = false;
3030
3031 /* optional */
3032 data.strDescription = m->strDescription;
3033
3034 /* optional properties */
3035 data.properties.clear();
3036 for (Data::PropertyMap::const_iterator it = m->properties.begin();
3037 it != m->properties.end();
3038 ++it)
3039 {
3040 /* only save properties that have non-default values */
3041 if (!it->second.isNull())
3042 {
3043 Utf8Str name = it->first;
3044 Utf8Str value = it->second;
3045 data.properties[name] = value;
3046 }
3047 }
3048
3049 /* only for base hard disks */
3050 if (mParent.isNull())
3051 data.hdType = m->type;
3052
3053 /* save all children */
3054 for (List::const_iterator it = getChildren().begin();
3055 it != getChildren().end();
3056 ++it)
3057 {
3058 settings::Medium med;
3059 HRESULT rc = (*it)->saveSettings(med);
3060 AssertComRCReturnRC(rc);
3061 data.llChildren.push_back(med);
3062 }
3063
3064 return S_OK;
3065}
3066
3067/**
3068 * Compares the location of this hard disk to the given location.
3069 *
3070 * The comparison takes the location details into account. For example, if the
3071 * location is a file in the host's filesystem, a case insensitive comparison
3072 * will be performed for case insensitive filesystems.
3073 *
3074 * @param aLocation Location to compare to (as is).
3075 * @param aResult Where to store the result of comparison: 0 if locations
3076 * are equal, 1 if this object's location is greater than
3077 * the specified location, and -1 otherwise.
3078 */
3079HRESULT Medium::compareLocationTo(const char *aLocation, int &aResult)
3080{
3081 AutoCaller autoCaller(this);
3082 AssertComRCReturnRC(autoCaller.rc());
3083
3084 AutoReadLock alock(this);
3085
3086 Utf8Str locationFull(m->strLocationFull);
3087
3088 /// @todo NEWMEDIA delegate the comparison to the backend?
3089
3090 if (m->formatObj->capabilities() & MediumFormatCapabilities_File)
3091 {
3092 Utf8Str location(aLocation);
3093
3094 /* For locations represented by files, append the default path if
3095 * only the name is given, and then get the full path. */
3096 if (!RTPathHavePath(aLocation))
3097 {
3098 location = Utf8StrFmt("%s%c%s",
3099 mVirtualBox->getDefaultHardDiskFolder().raw(),
3100 RTPATH_DELIMITER,
3101 aLocation);
3102 }
3103
3104 int vrc = mVirtualBox->calculateFullPath(location, location);
3105 if (RT_FAILURE(vrc))
3106 return setError(E_FAIL,
3107 tr("Invalid hard disk storage file location '%s' (%Rrc)"),
3108 location.raw(),
3109 vrc);
3110
3111 aResult = RTPathCompare(locationFull.c_str(), location.c_str());
3112 }
3113 else
3114 aResult = locationFull.compare(aLocation);
3115
3116 return S_OK;
3117}
3118
3119/**
3120 * Checks that this hard disk may be discarded and performs necessary state
3121 * changes.
3122 *
3123 * This method is to be called prior to calling the #discard() to perform
3124 * necessary consistency checks and place involved hard disks to appropriate
3125 * states. If #discard() is not called or fails, the state modifications
3126 * performed by this method must be undone by #cancelDiscard().
3127 *
3128 * See #discard() for more info about discarding hard disks.
3129 *
3130 * @param aChain Where to store the created merge chain (may return NULL
3131 * if no real merge is necessary).
3132 *
3133 * @note Locks getTreeLock() for reading. Locks this object, aTarget and all
3134 * intermediate hard disks for writing.
3135 */
3136HRESULT Medium::prepareDiscard(MergeChain * &aChain)
3137{
3138 AutoCaller autoCaller(this);
3139 AssertComRCReturnRC(autoCaller.rc());
3140
3141 aChain = NULL;
3142
3143 AutoWriteLock alock(this);
3144
3145 /* we access mParent & children() */
3146 AutoReadLock treeLock(this->getTreeLock());
3147
3148 AssertReturn(m->type == MediumType_Normal, E_FAIL);
3149
3150 if (getChildren().size() == 0)
3151 {
3152 /* special treatment of the last hard disk in the chain: */
3153
3154 if (mParent.isNull())
3155 {
3156 /* lock only, to prevent any usage; discard() will unlock */
3157 return LockWrite(NULL);
3158 }
3159
3160 /* the differencing hard disk w/o children will be deleted, protect it
3161 * from attaching to other VMs (this is why Deleting) */
3162
3163 switch (m->state)
3164 {
3165 case MediumState_Created:
3166 m->state = MediumState_Deleting;
3167 break;
3168 default:
3169 return setStateError();
3170 }
3171
3172 /* aChain is intentionally NULL here */
3173
3174 return S_OK;
3175 }
3176
3177 /* not going multi-merge as it's too expensive */
3178 if (getChildren().size() > 1)
3179 return setError(E_FAIL,
3180 tr ("Hard disk '%s' has more than one child hard disk (%d)"),
3181 m->strLocationFull.raw(), getChildren().size());
3182
3183 /* this is a read-only hard disk with children; it must be associated with
3184 * exactly one snapshot (when the snapshot is being taken, none of the
3185 * current VM's hard disks may be attached to other VMs). Note that by the
3186 * time when discard() is called, there must be no any attachments at all
3187 * (the code calling prepareDiscard() should detach). */
3188 AssertReturn(m->backRefs.size() == 1, E_FAIL);
3189 AssertReturn(!m->backRefs.front().fInCurState, E_FAIL);
3190 AssertReturn(m->backRefs.front().llSnapshotIds.size() == 1, E_FAIL);
3191
3192 ComObjPtr<Medium> child = getChildren().front();
3193
3194 /* we keep this locked, so lock the affected child to make sure the lock
3195 * order is correct when calling prepareMergeTo() */
3196 AutoWriteLock childLock(child);
3197
3198 /* delegate the rest to the profi */
3199 if (mParent.isNull())
3200 {
3201 /* base hard disk, backward merge */
3202
3203 Assert(child->m->backRefs.size() == 1);
3204 if (child->m->backRefs.front().machineId != m->backRefs.front().machineId)
3205 {
3206 /* backward merge is too tricky, we'll just detach on discard, so
3207 * lock only, to prevent any usage; discard() will only unlock
3208 * (since we return NULL in aChain) */
3209 return LockWrite(NULL);
3210 }
3211
3212 return child->prepareMergeTo(this, aChain, true /* aIgnoreAttachments */);
3213 }
3214 else
3215 {
3216 /* forward merge */
3217 return prepareMergeTo(child, aChain, true /* aIgnoreAttachments */);
3218 }
3219}
3220
3221/**
3222 * Discards this hard disk.
3223 *
3224 * Discarding the hard disk is merging its contents to its differencing child
3225 * hard disk (forward merge) or contents of its child hard disk to itself
3226 * (backward merge) if this hard disk is a base hard disk. If this hard disk is
3227 * a differencing hard disk w/o children, then it will be simply deleted.
3228 * Calling this method on a base hard disk w/o children will do nothing and
3229 * silently succeed. If this hard disk has more than one child, the method will
3230 * currently return an error (since merging in this case would be too expensive
3231 * and result in data duplication).
3232 *
3233 * When the backward merge takes place (i.e. this hard disk is a target) then,
3234 * on success, this hard disk will automatically replace the differencing child
3235 * hard disk used as a source (which will then be deleted) in the attachment
3236 * this child hard disk is associated with. This will happen only if both hard
3237 * disks belong to the same machine because otherwise such a replace would be
3238 * too tricky and could be not expected by the other machine. Same relates to a
3239 * case when the child hard disk is not associated with any machine at all. When
3240 * the backward merge is not applied, the method behaves as if the base hard
3241 * disk were not attached at all -- i.e. simply detaches it from the machine but
3242 * leaves the hard disk chain intact.
3243 *
3244 * This method is basically a wrapper around #mergeTo() that selects the correct
3245 * merge direction and performs additional actions as described above and.
3246 *
3247 * Note that this method will not return until the merge operation is complete
3248 * (which may be quite time consuming depending on the size of the merged hard
3249 * disks).
3250 *
3251 * Note that #prepareDiscard() must be called before calling this method. If
3252 * this method returns a failure, the caller must call #cancelDiscard(). On
3253 * success, #cancelDiscard() must not be called (this method will perform all
3254 * necessary steps such as resetting states of all involved hard disks and
3255 * deleting @a aChain).
3256 *
3257 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
3258 * no real merge takes place).
3259 *
3260 * @note Locks the hard disks from the chain for writing. Locks the machine
3261 * object when the backward merge takes place. Locks getTreeLock() lock for
3262 * reading or writing.
3263 */
3264HRESULT Medium::discard(ComObjPtr<Progress> &aProgress, ULONG ulWeight, MergeChain *aChain)
3265{
3266 AssertReturn(!aProgress.isNull(), E_FAIL);
3267
3268 ComObjPtr <Medium> hdFrom;
3269
3270 HRESULT rc = S_OK;
3271
3272 {
3273 AutoCaller autoCaller(this);
3274 AssertComRCReturnRC(autoCaller.rc());
3275
3276 aProgress->SetNextOperation(BstrFmt(tr("Discarding hard disk '%s'"), getName().raw()),
3277 ulWeight); // weight
3278
3279 if (aChain == NULL)
3280 {
3281 AutoWriteLock alock(this);
3282
3283 /* we access mParent & children() */
3284 AutoReadLock treeLock(this->getTreeLock());
3285
3286 Assert(getChildren().size() == 0);
3287
3288 /* special treatment of the last hard disk in the chain: */
3289
3290 if (mParent.isNull())
3291 {
3292 rc = UnlockWrite(NULL);
3293 AssertComRC(rc);
3294 return rc;
3295 }
3296
3297 /* delete the differencing hard disk w/o children */
3298
3299 Assert(m->state == MediumState_Deleting);
3300
3301 /* go back to Created since deleteStorage() expects this state */
3302 m->state = MediumState_Created;
3303
3304 hdFrom = this;
3305
3306 // deleteStorageAndWait calls unregisterWithVirtualBox which gets
3307 // a write tree lock, so don't deadlock
3308 treeLock.unlock();
3309 alock.unlock();
3310
3311 rc = deleteStorageAndWait(&aProgress);
3312 }
3313 else
3314 {
3315 hdFrom = aChain->source();
3316
3317 rc = hdFrom->mergeToAndWait(aChain, &aProgress);
3318 }
3319 }
3320
3321 if (SUCCEEDED(rc))
3322 {
3323 /* mergeToAndWait() cannot uninitialize the initiator because of
3324 * possible AutoCallers on the current thread, deleteStorageAndWait()
3325 * doesn't do it either; do it ourselves */
3326 hdFrom->uninit();
3327 }
3328
3329 return rc;
3330}
3331
3332/**
3333 * Undoes what #prepareDiscard() did. Must be called if #discard() is not called
3334 * or fails. Frees memory occupied by @a aChain.
3335 *
3336 * @param aChain Merge chain created by #prepareDiscard() (may be NULL if
3337 * no real merge takes place).
3338 *
3339 * @note Locks the hard disks from the chain for writing. Locks getTreeLock() for
3340 * reading.
3341 */
3342void Medium::cancelDiscard(MergeChain *aChain)
3343{
3344 AutoCaller autoCaller(this);
3345 AssertComRCReturnVoid(autoCaller.rc());
3346
3347 if (aChain == NULL)
3348 {
3349 AutoWriteLock alock(this);
3350
3351 /* we access mParent & children() */
3352 AutoReadLock treeLock(this->getTreeLock());
3353
3354 Assert(getChildren().size() == 0);
3355
3356 /* special treatment of the last hard disk in the chain: */
3357
3358 if (mParent.isNull())
3359 {
3360 HRESULT rc = UnlockWrite(NULL);
3361 AssertComRC(rc);
3362 return;
3363 }
3364
3365 /* the differencing hard disk w/o children will be deleted, protect it
3366 * from attaching to other VMs (this is why Deleting) */
3367
3368 Assert(m->state == MediumState_Deleting);
3369 m->state = MediumState_Created;
3370
3371 return;
3372 }
3373
3374 /* delegate the rest to the profi */
3375 cancelMergeTo(aChain);
3376}
3377
3378/**
3379 * Returns a preferred format for differencing hard disks.
3380 */
3381Bstr Medium::preferredDiffFormat()
3382{
3383 Utf8Str strFormat;
3384
3385 AutoCaller autoCaller(this);
3386 AssertComRCReturn(autoCaller.rc(), strFormat);
3387
3388 /* m->format is const, no need to lock */
3389 strFormat = m->strFormat;
3390
3391 /* check that our own format supports diffs */
3392 if (!(m->formatObj->capabilities() & MediumFormatCapabilities_Differencing))
3393 {
3394 /* use the default format if not */
3395 AutoReadLock propsLock(mVirtualBox->systemProperties());
3396 strFormat = mVirtualBox->getDefaultHardDiskFormat();
3397 }
3398
3399 return strFormat;
3400}
3401
3402/**
3403 * Returns the medium type. Must have caller + locking!
3404 * @return
3405 */
3406MediumType_T Medium::getType() const
3407{
3408 return m->type;
3409}
3410
3411/**
3412 * Returns VirtualBox::hardDiskTreeHandle(), for convenience. Don't forget
3413 * to follow these locking rules:
3414 *
3415 * 1. The write lock on this handle must be either held alone on the thread
3416 * or requested *after* the VirtualBox object lock. Mixing with other
3417 * locks is prohibited.
3418 *
3419 * 2. The read lock on this handle may be intermixed with any other lock
3420 * with the exception that it must be requested *after* the VirtualBox
3421 * object lock.
3422 */
3423RWLockHandle* Medium::getTreeLock()
3424{
3425 return &mVirtualBox->hardDiskTreeLockHandle();
3426}
3427
3428// private methods
3429////////////////////////////////////////////////////////////////////////////////
3430
3431/**
3432 * Returns a short version of the location attribute.
3433 *
3434 * @note Must be called from under this object's read or write lock.
3435 */
3436Utf8Str Medium::getName()
3437{
3438 Utf8Str name = RTPathFilename(m->strLocationFull.c_str());
3439 return name;
3440}
3441
3442/**
3443 * Sets the value of m->strLocation and calculates the value of m->strLocationFull.
3444 *
3445 * Treats non-FS-path locations specially, and prepends the default hard disk
3446 * folder if the given location string does not contain any path information
3447 * at all.
3448 *
3449 * Also, if the specified location is a file path that ends with '/' then the
3450 * file name part will be generated by this method automatically in the format
3451 * '{<uuid>}.<ext>' where <uuid> is a fresh UUID that this method will generate
3452 * and assign to this medium, and <ext> is the default extension for this
3453 * medium's storage format. Note that this procedure requires the media state to
3454 * be NotCreated and will return a failure otherwise.
3455 *
3456 * @param aLocation Location of the storage unit. If the location is a FS-path,
3457 * then it can be relative to the VirtualBox home directory.
3458 * @param aFormat Optional fallback format if it is an import and the format
3459 * cannot be determined.
3460 *
3461 * @note Must be called from under this object's write lock.
3462 */
3463HRESULT Medium::setLocation(const Utf8Str &aLocation, const Utf8Str &aFormat)
3464{
3465 AssertReturn(!aLocation.isEmpty(), E_FAIL);
3466
3467 AutoCaller autoCaller(this);
3468 AssertComRCReturnRC(autoCaller.rc());
3469
3470 /* formatObj may be null only when initializing from an existing path and
3471 * no format is known yet */
3472 AssertReturn( (!m->strFormat.isEmpty() && !m->formatObj.isNull())
3473 || ( autoCaller.state() == InInit
3474 && m->state != MediumState_NotCreated
3475 && m->id.isEmpty()
3476 && m->strFormat.isEmpty()
3477 && m->formatObj.isNull()),
3478 E_FAIL);
3479
3480 /* are we dealing with a new medium constructed using the existing
3481 * location? */
3482 bool isImport = m->strFormat.isEmpty();
3483
3484 if ( isImport
3485 || ( (m->formatObj->capabilities() & MediumFormatCapabilities_File)
3486 && !m->hostDrive))
3487 {
3488 Guid id;
3489
3490 Utf8Str location(aLocation);
3491
3492 if (m->state == MediumState_NotCreated)
3493 {
3494 /* must be a file (formatObj must be already known) */
3495 Assert(m->formatObj->capabilities() & MediumFormatCapabilities_File);
3496
3497 if (RTPathFilename(location.c_str()) == NULL)
3498 {
3499 /* no file name is given (either an empty string or ends with a
3500 * slash), generate a new UUID + file name if the state allows
3501 * this */
3502
3503 ComAssertMsgRet(!m->formatObj->fileExtensions().empty(),
3504 ("Must be at least one extension if it is MediumFormatCapabilities_File\n"),
3505 E_FAIL);
3506
3507 Bstr ext = m->formatObj->fileExtensions().front();
3508 ComAssertMsgRet(!ext.isEmpty(),
3509 ("Default extension must not be empty\n"),
3510 E_FAIL);
3511
3512 id.create();
3513
3514 location = Utf8StrFmt("%s{%RTuuid}.%ls",
3515 location.raw(), id.raw(), ext.raw());
3516 }
3517 }
3518
3519 /* append the default folder if no path is given */
3520 if (!RTPathHavePath(location.c_str()))
3521 location = Utf8StrFmt("%s%c%s",
3522 mVirtualBox->getDefaultHardDiskFolder().raw(),
3523 RTPATH_DELIMITER,
3524 location.raw());
3525
3526 /* get the full file name */
3527 Utf8Str locationFull;
3528 int vrc = mVirtualBox->calculateFullPath(location, locationFull);
3529 if (RT_FAILURE(vrc))
3530 return setError(VBOX_E_FILE_ERROR,
3531 tr("Invalid medium storage file location '%s' (%Rrc)"),
3532 location.raw(), vrc);
3533
3534 /* detect the backend from the storage unit if importing */
3535 if (isImport)
3536 {
3537 char *backendName = NULL;
3538
3539 /* is it a file? */
3540 {
3541 RTFILE file;
3542 vrc = RTFileOpen(&file, locationFull.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
3543 if (RT_SUCCESS(vrc))
3544 RTFileClose(file);
3545 }
3546 if (RT_SUCCESS(vrc))
3547 {
3548 vrc = VDGetFormat(NULL, locationFull.c_str(), &backendName);
3549 }
3550 else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
3551 {
3552 /* assume it's not a file, restore the original location */
3553 location = locationFull = aLocation;
3554 vrc = VDGetFormat(NULL, locationFull.c_str(), &backendName);
3555 }
3556
3557 if (RT_FAILURE(vrc))
3558 {
3559 if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
3560 return setError(VBOX_E_FILE_ERROR,
3561 tr("Could not find file for the medium '%s' (%Rrc)"),
3562 locationFull.raw(), vrc);
3563 else if (aFormat.isEmpty())
3564 return setError(VBOX_E_IPRT_ERROR,
3565 tr("Could not get the storage format of the medium '%s' (%Rrc)"),
3566 locationFull.raw(), vrc);
3567 else
3568 {
3569 HRESULT rc = setFormat(Bstr(aFormat));
3570 /* setFormat() must not fail since we've just used the backend so
3571 * the format object must be there */
3572 AssertComRCReturnRC(rc);
3573 }
3574 }
3575 else
3576 {
3577 ComAssertRet(backendName != NULL && *backendName != '\0', E_FAIL);
3578
3579 HRESULT rc = setFormat(Bstr(backendName));
3580 RTStrFree(backendName);
3581
3582 /* setFormat() must not fail since we've just used the backend so
3583 * the format object must be there */
3584 AssertComRCReturnRC(rc);
3585 }
3586 }
3587
3588 /* is it still a file? */
3589 if (m->formatObj->capabilities() & MediumFormatCapabilities_File)
3590 {
3591 m->strLocation = location;
3592 m->strLocationFull = locationFull;
3593
3594 if (m->state == MediumState_NotCreated)
3595 {
3596 /* assign a new UUID (this UUID will be used when calling
3597 * VDCreateBase/VDCreateDiff as a wanted UUID). Note that we
3598 * also do that if we didn't generate it to make sure it is
3599 * either generated by us or reset to null */
3600 unconst(m->id) = id;
3601 }
3602 }
3603 else
3604 {
3605 m->strLocation = locationFull;
3606 m->strLocationFull = locationFull;
3607 }
3608 }
3609 else
3610 {
3611 m->strLocation = aLocation;
3612 m->strLocationFull = aLocation;
3613 }
3614
3615 return S_OK;
3616}
3617
3618/**
3619 * Queries information from the image file.
3620 *
3621 * As a result of this call, the accessibility state and data members such as
3622 * size and description will be updated with the current information.
3623 *
3624 * @note This method may block during a system I/O call that checks storage
3625 * accessibility.
3626 *
3627 * @note Locks getTreeLock() for reading and writing (for new diff media checked
3628 * for the first time). Locks mParent for reading. Locks this object for
3629 * writing.
3630 */
3631HRESULT Medium::queryInfo()
3632{
3633 AutoWriteLock alock(this);
3634
3635 if ( m->state != MediumState_Created
3636 && m->state != MediumState_Inaccessible
3637 && m->state != MediumState_LockedRead)
3638 return E_FAIL;
3639
3640 HRESULT rc = S_OK;
3641
3642 int vrc = VINF_SUCCESS;
3643
3644 /* check if a blocking queryInfo() call is in progress on some other thread,
3645 * and wait for it to finish if so instead of querying data ourselves */
3646 if (m->queryInfoRunning)
3647 {
3648 Assert( m->state == MediumState_LockedRead
3649 || m->state == MediumState_LockedWrite);
3650
3651 alock.leave();
3652
3653 vrc = RTSemEventMultiWait(m->queryInfoSem, RT_INDEFINITE_WAIT);
3654
3655 alock.enter();
3656
3657 AssertRC(vrc);
3658
3659 return S_OK;
3660 }
3661
3662 bool success = false;
3663 Utf8Str lastAccessError;
3664
3665 /* are we dealing with a new medium constructed using the existing
3666 * location? */
3667 bool isImport = m->id.isEmpty();
3668 unsigned flags = VD_OPEN_FLAGS_INFO;
3669
3670 /* Note that we don't use VD_OPEN_FLAGS_READONLY when opening new
3671 * media because that would prevent necessary modifications
3672 * when opening media of some third-party formats for the first
3673 * time in VirtualBox (such as VMDK for which VDOpen() needs to
3674 * generate an UUID if it is missing) */
3675 if ( (m->hddOpenMode == OpenReadOnly)
3676 || !isImport
3677 )
3678 flags |= VD_OPEN_FLAGS_READONLY;
3679
3680 /* Lock the medium, which makes the behavior much more consistent */
3681 if (flags & VD_OPEN_FLAGS_READONLY)
3682 rc = LockRead(NULL);
3683 else
3684 rc = LockWrite(NULL);
3685 if (FAILED(rc)) return rc;
3686
3687 /* Copies of the input state fields which are not read-only,
3688 * as we're dropping the lock. CAUTION: be extremely careful what
3689 * you do with the contents of this medium object, as you will
3690 * create races if there are concurrent changes. */
3691 Utf8Str format(m->strFormat);
3692 Utf8Str location(m->strLocationFull);
3693 ComObjPtr<MediumFormat> formatObj = m->formatObj;
3694
3695 /* "Output" values which can't be set because the lock isn't held
3696 * at the time the values are determined. */
3697 Guid mediumId = m->id;
3698 uint64_t mediumSize = 0;
3699 uint64_t mediumLogicalSize = 0;
3700
3701 /* leave the lock before a lengthy operation */
3702 vrc = RTSemEventMultiReset(m->queryInfoSem);
3703 ComAssertRCThrow(vrc, E_FAIL);
3704 m->queryInfoRunning = true;
3705 alock.leave();
3706
3707 try
3708 {
3709 /* skip accessibility checks for host drives */
3710 if (m->hostDrive)
3711 {
3712 success = true;
3713 throw S_OK;
3714 }
3715
3716 PVBOXHDD hdd;
3717 vrc = VDCreate(m->vdDiskIfaces, &hdd);
3718 ComAssertRCThrow(vrc, E_FAIL);
3719
3720 try
3721 {
3722 /** @todo This kind of opening of images is assuming that diff
3723 * images can be opened as base images. Should be fixed ASAP. */
3724 vrc = VDOpen(hdd,
3725 format.c_str(),
3726 location.c_str(),
3727 flags,
3728 m->vdDiskIfaces);
3729 if (RT_FAILURE(vrc))
3730 {
3731 lastAccessError = Utf8StrFmt(tr("Could not open the medium '%s'%s"),
3732 location.c_str(), vdError(vrc).c_str());
3733 throw S_OK;
3734 }
3735
3736 if (formatObj->capabilities() & MediumFormatCapabilities_Uuid)
3737 {
3738 /* Modify the UUIDs if necessary. The associated fields are
3739 * not modified by other code, so no need to copy. */
3740 if (m->setImageId)
3741 {
3742 vrc = VDSetUuid(hdd, 0, m->imageId);
3743 ComAssertRCThrow(vrc, E_FAIL);
3744 }
3745 if (m->setParentId)
3746 {
3747 vrc = VDSetParentUuid(hdd, 0, m->parentId);
3748 ComAssertRCThrow(vrc, E_FAIL);
3749 }
3750 /* zap the information, these are no long-term members */
3751 m->setImageId = false;
3752 unconst(m->imageId).clear();
3753 m->setParentId = false;
3754 unconst(m->parentId).clear();
3755
3756 /* check the UUID */
3757 RTUUID uuid;
3758 vrc = VDGetUuid(hdd, 0, &uuid);
3759 ComAssertRCThrow(vrc, E_FAIL);
3760
3761 if (isImport)
3762 {
3763 mediumId = uuid;
3764
3765 if (mediumId.isEmpty() && (m->hddOpenMode == OpenReadOnly))
3766 // only when importing a VDMK that has no UUID, create one in memory
3767 mediumId.create();
3768 }
3769 else
3770 {
3771 Assert(!mediumId.isEmpty());
3772
3773 if (mediumId != uuid)
3774 {
3775 lastAccessError = Utf8StrFmt(
3776 tr("UUID {%RTuuid} of the medium '%s' does not match the value {%RTuuid} stored in the media registry ('%s')"),
3777 &uuid,
3778 location.c_str(),
3779 mediumId.raw(),
3780 mVirtualBox->settingsFilePath().c_str());
3781 throw S_OK;
3782 }
3783 }
3784 }
3785 else
3786 {
3787 /* the backend does not support storing UUIDs within the
3788 * underlying storage so use what we store in XML */
3789
3790 /* generate an UUID for an imported UUID-less medium */
3791 if (isImport)
3792 {
3793 if (m->setImageId)
3794 mediumId = m->imageId;
3795 else
3796 mediumId.create();
3797 }
3798 }
3799
3800 /* check the type */
3801 unsigned uImageFlags;
3802 vrc = VDGetImageFlags(hdd, 0, &uImageFlags);
3803 ComAssertRCThrow(vrc, E_FAIL);
3804
3805 if (uImageFlags & VD_IMAGE_FLAGS_DIFF)
3806 {
3807 RTUUID parentId;
3808 vrc = VDGetParentUuid(hdd, 0, &parentId);
3809 ComAssertRCThrow(vrc, E_FAIL);
3810
3811 if (isImport)
3812 {
3813 /* the parent must be known to us. Note that we freely
3814 * call locking methods of mVirtualBox and parent from the
3815 * write lock (breaking the {parent,child} lock order)
3816 * because there may be no concurrent access to the just
3817 * opened hard disk on ther threads yet (and init() will
3818 * fail if this method reporst MediumState_Inaccessible) */
3819
3820 Guid id = parentId;
3821 ComObjPtr<Medium> parent;
3822 rc = mVirtualBox->findHardDisk(&id, NULL,
3823 false /* aSetError */,
3824 &parent);
3825 if (FAILED(rc))
3826 {
3827 lastAccessError = Utf8StrFmt(
3828 tr("Parent hard disk with UUID {%RTuuid} of the hard disk '%s' is not found in the media registry ('%s')"),
3829 &parentId, location.c_str(),
3830 mVirtualBox->settingsFilePath().c_str());
3831 throw S_OK;
3832 }
3833
3834 /* deassociate from VirtualBox, associate with parent */
3835
3836 mVirtualBox->removeDependentChild(this);
3837
3838 /* we set mParent & children() */
3839 AutoWriteLock treeLock(this->getTreeLock());
3840
3841 Assert(mParent.isNull());
3842 mParent = parent;
3843 mParent->addDependentChild(this);
3844 }
3845 else
3846 {
3847 /* we access mParent */
3848 AutoReadLock treeLock(this->getTreeLock());
3849
3850 /* check that parent UUIDs match. Note that there's no need
3851 * for the parent's AutoCaller (our lifetime is bound to
3852 * it) */
3853
3854 if (mParent.isNull())
3855 {
3856 lastAccessError = Utf8StrFmt(
3857 tr("Hard disk '%s' is differencing but it is not associated with any parent hard disk in the media registry ('%s')"),
3858 location.c_str(),
3859 mVirtualBox->settingsFilePath().c_str());
3860 throw S_OK;
3861 }
3862
3863 AutoReadLock parentLock(mParent);
3864 if ( mParent->getState() != MediumState_Inaccessible
3865 && mParent->getId() != parentId)
3866 {
3867 lastAccessError = Utf8StrFmt(
3868 tr ("Parent UUID {%RTuuid} of the hard disk '%s' does not match UUID {%RTuuid} of its parent hard disk stored in the media registry ('%s')"),
3869 &parentId, location.c_str(),
3870 mParent->getId().raw(),
3871 mVirtualBox->settingsFilePath().c_str());
3872 throw S_OK;
3873 }
3874
3875 /// @todo NEWMEDIA what to do if the parent is not
3876 /// accessible while the diff is? Probably, nothing. The
3877 /// real code will detect the mismatch anyway.
3878 }
3879 }
3880
3881 mediumSize = VDGetFileSize(hdd, 0);
3882 mediumLogicalSize = VDGetSize(hdd, 0) / _1M;
3883
3884 success = true;
3885 }
3886 catch (HRESULT aRC)
3887 {
3888 rc = aRC;
3889 }
3890
3891 VDDestroy(hdd);
3892
3893 }
3894 catch (HRESULT aRC)
3895 {
3896 rc = aRC;
3897 }
3898
3899 alock.enter();
3900
3901 if (isImport)
3902 unconst(m->id) = mediumId;
3903
3904 if (success)
3905 {
3906 m->size = mediumSize;
3907 m->logicalSize = mediumLogicalSize;
3908 m->strLastAccessError.setNull();
3909 }
3910 else
3911 {
3912 m->strLastAccessError = lastAccessError;
3913 LogWarningFunc(("'%s' is not accessible (error='%s', rc=%Rhrc, vrc=%Rrc)\n",
3914 location.c_str(), m->strLastAccessError.c_str(),
3915 rc, vrc));
3916 }
3917
3918 /* inform other callers if there are any */
3919 RTSemEventMultiSignal(m->queryInfoSem);
3920 m->queryInfoRunning = false;
3921
3922 /* Set the proper state according to the result of the check */
3923 if (success)
3924 m->preLockState = MediumState_Created;
3925 else
3926 m->preLockState = MediumState_Inaccessible;
3927
3928 if (flags & VD_OPEN_FLAGS_READONLY)
3929 rc = UnlockRead(NULL);
3930 else
3931 rc = UnlockWrite(NULL);
3932 if (FAILED(rc)) return rc;
3933
3934 return rc;
3935}
3936
3937/**
3938 * Sets the extended error info according to the current media state.
3939 *
3940 * @note Must be called from under this object's write or read lock.
3941 */
3942HRESULT Medium::setStateError()
3943{
3944 HRESULT rc = E_FAIL;
3945
3946 switch (m->state)
3947 {
3948 case MediumState_NotCreated:
3949 {
3950 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3951 tr("Storage for the medium '%s' is not created"),
3952 m->strLocationFull.raw());
3953 break;
3954 }
3955 case MediumState_Created:
3956 {
3957 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3958 tr("Storage for the medium '%s' is already created"),
3959 m->strLocationFull.raw());
3960 break;
3961 }
3962 case MediumState_LockedRead:
3963 {
3964 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3965 tr("Medium '%s' is locked for reading by another task"),
3966 m->strLocationFull.raw());
3967 break;
3968 }
3969 case MediumState_LockedWrite:
3970 {
3971 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3972 tr("Medium '%s' is locked for writing by another task"),
3973 m->strLocationFull.raw());
3974 break;
3975 }
3976 case MediumState_Inaccessible:
3977 {
3978 /* be in sync with Console::powerUpThread() */
3979 if (!m->strLastAccessError.isEmpty())
3980 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3981 tr("Medium '%s' is not accessible. %s"),
3982 m->strLocationFull.raw(), m->strLastAccessError.c_str());
3983 else
3984 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3985 tr("Medium '%s' is not accessible"),
3986 m->strLocationFull.raw());
3987 break;
3988 }
3989 case MediumState_Creating:
3990 {
3991 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3992 tr("Storage for the medium '%s' is being created"),
3993 m->strLocationFull.raw());
3994 break;
3995 }
3996 case MediumState_Deleting:
3997 {
3998 rc = setError(VBOX_E_INVALID_OBJECT_STATE,
3999 tr("Storage for the medium '%s' is being deleted"),
4000 m->strLocationFull.raw());
4001 break;
4002 }
4003 default:
4004 {
4005 AssertFailed();
4006 break;
4007 }
4008 }
4009
4010 return rc;
4011}
4012
4013/**
4014 * Deletes the hard disk storage unit.
4015 *
4016 * If @a aProgress is not NULL but the object it points to is @c null then a new
4017 * progress object will be created and assigned to @a *aProgress on success,
4018 * otherwise the existing progress object is used. If Progress is NULL, then no
4019 * progress object is created/used at all.
4020 *
4021 * When @a aWait is @c false, this method will create a thread to perform the
4022 * delete operation asynchronously and will return immediately. Otherwise, it
4023 * will perform the operation on the calling thread and will not return to the
4024 * caller until the operation is completed. Note that @a aProgress cannot be
4025 * NULL when @a aWait is @c false (this method will assert in this case).
4026 *
4027 * @param aProgress Where to find/store a Progress object to track operation
4028 * completion.
4029 * @param aWait @c true if this method should block instead of creating
4030 * an asynchronous thread.
4031 *
4032 * @note Locks mVirtualBox and this object for writing. Locks getTreeLock() for
4033 * writing.
4034 */
4035HRESULT Medium::deleteStorage(ComObjPtr <Progress> *aProgress, bool aWait)
4036{
4037 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4038
4039 /* unregisterWithVirtualBox() needs a write lock. We want to unregister
4040 * ourselves atomically after detecting that deletion is possible to make
4041 * sure that we don't do that after another thread has done
4042 * VirtualBox::findHardDisk() but before it starts using us (provided that
4043 * it holds a mVirtualBox lock too of course). */
4044
4045 AutoMultiWriteLock2 alock(mVirtualBox->lockHandle(), this->lockHandle());
4046 LogFlowThisFunc(("aWait=%RTbool locationFull=%s\n", aWait, getLocationFull().c_str() ));
4047
4048 if ( !(m->formatObj->capabilities() & ( MediumFormatCapabilities_CreateDynamic
4049 | MediumFormatCapabilities_CreateFixed)))
4050 return setError(VBOX_E_NOT_SUPPORTED,
4051 tr("Hard disk format '%s' does not support storage deletion"),
4052 m->strFormat.raw());
4053
4054 /* Note that we are fine with Inaccessible state too: a) for symmetry with
4055 * create calls and b) because it doesn't really harm to try, if it is
4056 * really inaccessible, the delete operation will fail anyway. Accepting
4057 * Inaccessible state is especially important because all registered hard
4058 * disks are initially Inaccessible upon VBoxSVC startup until
4059 * COMGETTER(State) is called. */
4060
4061 switch (m->state)
4062 {
4063 case MediumState_Created:
4064 case MediumState_Inaccessible:
4065 break;
4066 default:
4067 return setStateError();
4068 }
4069
4070 if (m->backRefs.size() != 0)
4071 {
4072 Utf8Str strMachines;
4073 for (BackRefList::const_iterator it = m->backRefs.begin();
4074 it != m->backRefs.end();
4075 ++it)
4076 {
4077 const BackRef &b = *it;
4078 if (strMachines.length())
4079 strMachines.append(", ");
4080 strMachines.append(b.machineId.toString().c_str());
4081 }
4082#ifdef DEBUG
4083 dumpBackRefs();
4084#endif
4085 return setError(VBOX_E_OBJECT_IN_USE,
4086 tr("Cannot delete storage: hard disk '%s' is still attached to the following %d virtual machine(s): %s"),
4087 m->strLocationFull.c_str(),
4088 m->backRefs.size(),
4089 strMachines.c_str());
4090 }
4091
4092 HRESULT rc = canClose();
4093 if (FAILED(rc)) return rc;
4094
4095 /* go to Deleting state before leaving the lock */
4096 m->state = MediumState_Deleting;
4097
4098 /* we need to leave this object's write lock now because of
4099 * unregisterWithVirtualBox() that locks getTreeLock() for writing */
4100 alock.leave();
4101
4102 /* try to remove from the list of known hard disks before performing actual
4103 * deletion (we favor the consistency of the media registry in the first
4104 * place which would have been broken if unregisterWithVirtualBox() failed
4105 * after we successfully deleted the storage) */
4106
4107 rc = unregisterWithVirtualBox();
4108
4109 alock.enter();
4110
4111 /* restore the state because we may fail below; we will set it later again*/
4112 m->state = MediumState_Created;
4113
4114 if (FAILED(rc)) return rc;
4115
4116 ComObjPtr <Progress> progress;
4117
4118 if (aProgress != NULL)
4119 {
4120 /* use the existing progress object... */
4121 progress = *aProgress;
4122
4123 /* ...but create a new one if it is null */
4124 if (progress.isNull())
4125 {
4126 progress.createObject();
4127 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
4128 BstrFmt(tr("Deleting hard disk storage unit '%s'"),
4129 m->strLocationFull.raw()),
4130 FALSE /* aCancelable */);
4131 if (FAILED(rc)) return rc;
4132 }
4133 }
4134
4135 std::auto_ptr <Task> task(new Task(this, progress, Task::Delete));
4136 AssertComRCReturnRC(task->m_autoCaller.rc());
4137
4138 if (aWait)
4139 {
4140 /* go to Deleting state before starting the task */
4141 m->state = MediumState_Deleting;
4142
4143 rc = task->runNow();
4144 }
4145 else
4146 {
4147 rc = task->startThread();
4148 if (FAILED(rc)) return rc;
4149
4150 /* go to Deleting state before leaving the lock */
4151 m->state = MediumState_Deleting;
4152 }
4153
4154 /* task is now owned (or already deleted) by taskThread() so release it */
4155 task.release();
4156
4157 if (aProgress != NULL)
4158 {
4159 /* return progress to the caller */
4160 *aProgress = progress;
4161 }
4162
4163 return rc;
4164}
4165
4166/**
4167 * Creates a new differencing storage unit using the given target hard disk's
4168 * format and the location. Note that @c aTarget must be NotCreated.
4169 *
4170 * As opposed to the CreateDiffStorage() method, this method doesn't try to lock
4171 * this hard disk for reading assuming that the caller has already done so. This
4172 * is used when taking an online snaopshot (where all original hard disks are
4173 * locked for writing and must remain such). Note however that if @a aWait is
4174 * @c false and this method returns a success then the thread started by
4175 * this method will unlock the hard disk (unless it is in
4176 * MediumState_LockedWrite state) so make sure the hard disk is either in
4177 * MediumState_LockedWrite or call #LockRead() before calling this method! If @a
4178 * aWait is @c true then this method neither locks nor unlocks the hard disk, so
4179 * make sure you do it yourself as needed.
4180 *
4181 * If @a aProgress is not NULL but the object it points to is @c null then a new
4182 * progress object will be created and assigned to @a *aProgress on success,
4183 * otherwise the existing progress object is used. If @a aProgress is NULL, then no
4184 * progress object is created/used at all.
4185 *
4186 * When @a aWait is @c false, this method will create a thread to perform the
4187 * create operation asynchronously and will return immediately. Otherwise, it
4188 * will perform the operation on the calling thread and will not return to the
4189 * caller until the operation is completed. Note that @a aProgress cannot be
4190 * NULL when @a aWait is @c false (this method will assert in this case).
4191 *
4192 * @param aTarget Target hard disk.
4193 * @param aVariant Precise image variant to create.
4194 * @param aProgress Where to find/store a Progress object to track operation
4195 * completion.
4196 * @param aWait @c true if this method should block instead of creating
4197 * an asynchronous thread.
4198 *
4199 * @note Locks this object and @a aTarget for writing.
4200 */
4201HRESULT Medium::createDiffStorage(ComObjPtr<Medium> &aTarget,
4202 MediumVariant_T aVariant,
4203 ComObjPtr<Progress> *aProgress,
4204 bool aWait)
4205{
4206 AssertReturn(!aTarget.isNull(), E_FAIL);
4207 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4208
4209 AutoCaller autoCaller(this);
4210 if (FAILED(autoCaller.rc())) return autoCaller.rc();
4211
4212 AutoCaller targetCaller(aTarget);
4213 if (FAILED(targetCaller.rc())) return targetCaller.rc();
4214
4215 AutoMultiWriteLock2 alock(this, aTarget);
4216
4217 AssertReturn(m->type != MediumType_Writethrough, E_FAIL);
4218
4219 /* Note: MediumState_LockedWrite is ok when taking an online snapshot */
4220 AssertReturn(m->state == MediumState_LockedRead ||
4221 m->state == MediumState_LockedWrite, E_FAIL);
4222
4223 if (aTarget->m->state != MediumState_NotCreated)
4224 return aTarget->setStateError();
4225
4226 HRESULT rc = S_OK;
4227
4228 /* check that the hard disk is not attached to any VM in the current state*/
4229 for (BackRefList::const_iterator it = m->backRefs.begin();
4230 it != m->backRefs.end(); ++ it)
4231 {
4232 if (it->fInCurState)
4233 {
4234 /* Note: when a VM snapshot is being taken, all normal hard disks
4235 * attached to the VM in the current state will be, as an exception,
4236 * also associated with the snapshot which is about to create (see
4237 * SnapshotMachine::init()) before deassociating them from the
4238 * current state (which takes place only on success in
4239 * Machine::fixupHardDisks()), so that the size of snapshotIds
4240 * will be 1 in this case. The given condition is used to filter out
4241 * this legal situatinon and do not report an error. */
4242
4243 if (it->llSnapshotIds.size() == 0)
4244 {
4245 return setError(VBOX_E_INVALID_OBJECT_STATE,
4246 tr("Hard disk '%s' is attached to a virtual machine with UUID {%RTuuid}. No differencing hard disks based on it may be created until it is detached"),
4247 m->strLocationFull.raw(), it->machineId.raw());
4248 }
4249
4250 Assert(it->llSnapshotIds.size() == 1);
4251 }
4252 }
4253
4254 ComObjPtr <Progress> progress;
4255
4256 if (aProgress != NULL)
4257 {
4258 /* use the existing progress object... */
4259 progress = *aProgress;
4260
4261 /* ...but create a new one if it is null */
4262 if (progress.isNull())
4263 {
4264 progress.createObject();
4265 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
4266 BstrFmt(tr("Creating differencing hard disk storage unit '%s'"),
4267 aTarget->m->strLocationFull.raw()),
4268 TRUE /* aCancelable */);
4269 if (FAILED(rc)) return rc;
4270 }
4271 }
4272
4273 /* setup task object and thread to carry out the operation
4274 * asynchronously */
4275
4276 std::auto_ptr <Task> task(new Task(this, progress, Task::CreateDiff));
4277 AssertComRCReturnRC(task->m_autoCaller.rc());
4278
4279 task->setData(aTarget);
4280 task->d.variant = aVariant;
4281
4282 /* register a task (it will deregister itself when done) */
4283 ++m->numCreateDiffTasks;
4284 Assert(m->numCreateDiffTasks != 0); /* overflow? */
4285
4286 if (aWait)
4287 {
4288 /* go to Creating state before starting the task */
4289 aTarget->m->state = MediumState_Creating;
4290
4291 rc = task->runNow();
4292 }
4293 else
4294 {
4295 rc = task->startThread();
4296 if (FAILED(rc)) return rc;
4297
4298 /* go to Creating state before leaving the lock */
4299 aTarget->m->state = MediumState_Creating;
4300 }
4301
4302 /* task is now owned (or already deleted) by taskThread() so release it */
4303 task.release();
4304
4305 if (aProgress != NULL)
4306 {
4307 /* return progress to the caller */
4308 *aProgress = progress;
4309 }
4310
4311 return rc;
4312}
4313
4314/**
4315 * Prepares this (source) hard disk, target hard disk and all intermediate hard
4316 * disks for the merge operation.
4317 *
4318 * This method is to be called prior to calling the #mergeTo() to perform
4319 * necessary consistency checks and place involved hard disks to appropriate
4320 * states. If #mergeTo() is not called or fails, the state modifications
4321 * performed by this method must be undone by #cancelMergeTo().
4322 *
4323 * Note that when @a aIgnoreAttachments is @c true then it's the caller's
4324 * responsibility to detach the source and all intermediate hard disks before
4325 * calling #mergeTo() (which will fail otherwise).
4326 *
4327 * See #mergeTo() for more information about merging.
4328 *
4329 * @param aTarget Target hard disk.
4330 * @param aChain Where to store the created merge chain.
4331 * @param aIgnoreAttachments Don't check if the source or any intermediate
4332 * hard disk is attached to any VM.
4333 *
4334 * @note Locks getTreeLock() for reading. Locks this object, aTarget and all
4335 * intermediate hard disks for writing.
4336 */
4337HRESULT Medium::prepareMergeTo(Medium *aTarget,
4338 MergeChain * &aChain,
4339 bool aIgnoreAttachments /*= false*/)
4340{
4341 AssertReturn(aTarget != NULL, E_FAIL);
4342
4343 AutoCaller autoCaller(this);
4344 AssertComRCReturnRC(autoCaller.rc());
4345
4346 AutoCaller targetCaller(aTarget);
4347 AssertComRCReturnRC(targetCaller.rc());
4348
4349 aChain = NULL;
4350
4351 /* we walk the tree */
4352 AutoReadLock treeLock(this->getTreeLock());
4353
4354 HRESULT rc = S_OK;
4355
4356 /* detect the merge direction */
4357 bool forward;
4358 {
4359 Medium *parent = mParent;
4360 while (parent != NULL && parent != aTarget)
4361 parent = parent->mParent;
4362 if (parent == aTarget)
4363 forward = false;
4364 else
4365 {
4366 parent = aTarget->mParent;
4367 while (parent != NULL && parent != this)
4368 parent = parent->mParent;
4369 if (parent == this)
4370 forward = true;
4371 else
4372 {
4373 Utf8Str tgtLoc;
4374 {
4375 AutoReadLock alock(this);
4376 tgtLoc = aTarget->getLocationFull();
4377 }
4378
4379 AutoReadLock alock(this);
4380 return setError(E_FAIL,
4381 tr("Hard disks '%s' and '%s' are unrelated"),
4382 m->strLocationFull.raw(), tgtLoc.raw());
4383 }
4384 }
4385 }
4386
4387 /* build the chain (will do necessary checks and state changes) */
4388 std::auto_ptr <MergeChain> chain(new MergeChain(forward,
4389 aIgnoreAttachments));
4390 {
4391 Medium *last = forward ? aTarget : this;
4392 Medium *first = forward ? this : aTarget;
4393
4394 for (;;)
4395 {
4396 if (last == aTarget)
4397 rc = chain->addTarget(last);
4398 else if (last == this)
4399 rc = chain->addSource(last);
4400 else
4401 rc = chain->addIntermediate(last);
4402 if (FAILED(rc)) return rc;
4403
4404 if (last == first)
4405 break;
4406
4407 last = last->mParent;
4408 }
4409 }
4410
4411 aChain = chain.release();
4412
4413 return S_OK;
4414}
4415
4416/**
4417 * Merges this hard disk to the specified hard disk which must be either its
4418 * direct ancestor or descendant.
4419 *
4420 * Given this hard disk is SOURCE and the specified hard disk is TARGET, we will
4421 * get two varians of the merge operation:
4422 *
4423 * forward merge
4424 * ------------------------->
4425 * [Extra] <- SOURCE <- Intermediate <- TARGET
4426 * Any Del Del LockWr
4427 *
4428 *
4429 * backward merge
4430 * <-------------------------
4431 * TARGET <- Intermediate <- SOURCE <- [Extra]
4432 * LockWr Del Del LockWr
4433 *
4434 * Each scheme shows the involved hard disks on the hard disk chain where
4435 * SOURCE and TARGET belong. Under each hard disk there is a state value which
4436 * the hard disk must have at a time of the mergeTo() call.
4437 *
4438 * The hard disks in the square braces may be absent (e.g. when the forward
4439 * operation takes place and SOURCE is the base hard disk, or when the backward
4440 * merge operation takes place and TARGET is the last child in the chain) but if
4441 * they present they are involved too as shown.
4442 *
4443 * Nor the source hard disk neither intermediate hard disks may be attached to
4444 * any VM directly or in the snapshot, otherwise this method will assert.
4445 *
4446 * The #prepareMergeTo() method must be called prior to this method to place all
4447 * involved to necessary states and perform other consistency checks.
4448 *
4449 * If @a aWait is @c true then this method will perform the operation on the
4450 * calling thread and will not return to the caller until the operation is
4451 * completed. When this method succeeds, all intermediate hard disk objects in
4452 * the chain will be uninitialized, the state of the target hard disk (and all
4453 * involved extra hard disks) will be restored and @a aChain will be deleted.
4454 * Note that this (source) hard disk is not uninitialized because of possible
4455 * AutoCaller instances held by the caller of this method on the current thread.
4456 * It's therefore the responsibility of the caller to call Medium::uninit()
4457 * after releasing all callers in this case!
4458 *
4459 * If @a aWait is @c false then this method will crea,te a thread to perform the
4460 * create operation asynchronously and will return immediately. If the operation
4461 * succeeds, the thread will uninitialize the source hard disk object and all
4462 * intermediate hard disk objects in the chain, reset the state of the target
4463 * hard disk (and all involved extra hard disks) and delete @a aChain. If the
4464 * operation fails, the thread will only reset the states of all involved hard
4465 * disks and delete @a aChain.
4466 *
4467 * When this method fails (regardless of the @a aWait mode), it is a caller's
4468 * responsiblity to undo state changes and delete @a aChain using
4469 * #cancelMergeTo().
4470 *
4471 * If @a aProgress is not NULL but the object it points to is @c null then a new
4472 * progress object will be created and assigned to @a *aProgress on success,
4473 * otherwise the existing progress object is used. If Progress is NULL, then no
4474 * progress object is created/used at all. Note that @a aProgress cannot be
4475 * NULL when @a aWait is @c false (this method will assert in this case).
4476 *
4477 * @param aChain Merge chain created by #prepareMergeTo().
4478 * @param aProgress Where to find/store a Progress object to track operation
4479 * completion.
4480 * @param aWait @c true if this method should block instead of creating
4481 * an asynchronous thread.
4482 *
4483 * @note Locks the branch lock for writing. Locks the hard disks from the chain
4484 * for writing.
4485 */
4486HRESULT Medium::mergeTo(MergeChain *aChain,
4487 ComObjPtr <Progress> *aProgress,
4488 bool aWait)
4489{
4490 AssertReturn(aChain != NULL, E_FAIL);
4491 AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
4492
4493 AutoCaller autoCaller(this);
4494 if (FAILED(autoCaller.rc())) return autoCaller.rc();
4495
4496 HRESULT rc = S_OK;
4497
4498 ComObjPtr <Progress> progress;
4499
4500 if (aProgress != NULL)
4501 {
4502 /* use the existing progress object... */
4503 progress = *aProgress;
4504
4505 /* ...but create a new one if it is null */
4506 if (progress.isNull())
4507 {
4508 AutoReadLock alock(this);
4509
4510 progress.createObject();
4511 rc = progress->init(mVirtualBox, static_cast<IMedium*>(this),
4512 BstrFmt(tr("Merging hard disk '%s' to '%s'"),
4513 getName().raw(),
4514 aChain->target()->getName().raw()),
4515 TRUE /* aCancelable */);
4516 if (FAILED(rc)) return rc;
4517 }
4518 }
4519
4520 /* setup task object and thread to carry out the operation
4521 * asynchronously */
4522
4523 std::auto_ptr <Task> task(new Task(this, progress, Task::Merge));
4524 AssertComRCReturnRC(task->m_autoCaller.rc());
4525
4526 task->setData(aChain);
4527
4528 /* Note: task owns aChain (will delete it when not needed) in all cases
4529 * except when @a aWait is @c true and runNow() fails -- in this case
4530 * aChain will be left away because cancelMergeTo() will be applied by the
4531 * caller on it as it is required in the documentation above */
4532
4533 if (aWait)
4534 {
4535 rc = task->runNow();
4536 }
4537 else
4538 {
4539 rc = task->startThread();
4540 if (FAILED(rc)) return rc;
4541 }
4542
4543 /* task is now owned (or already deleted) by taskThread() so release it */
4544 task.release();
4545
4546 if (aProgress != NULL)
4547 {
4548 /* return progress to the caller */
4549 *aProgress = progress;
4550 }
4551
4552 return rc;
4553}
4554
4555/**
4556 * Undoes what #prepareMergeTo() did. Must be called if #mergeTo() is not called
4557 * or fails. Frees memory occupied by @a aChain.
4558 *
4559 * @param aChain Merge chain created by #prepareMergeTo().
4560 *
4561 * @note Locks the hard disks from the chain for writing.
4562 */
4563void Medium::cancelMergeTo(MergeChain *aChain)
4564{
4565 AutoCaller autoCaller(this);
4566 AssertComRCReturnVoid(autoCaller.rc());
4567
4568 AssertReturnVoid(aChain != NULL);
4569
4570 /* the destructor will do the thing */
4571 delete aChain;
4572}
4573
4574/**
4575 * Checks that the format ID is valid and sets it on success.
4576 *
4577 * Note that this method will caller-reference the format object on success!
4578 * This reference must be released somewhere to let the MediumFormat object be
4579 * uninitialized.
4580 *
4581 * @note Must be called from under this object's write lock.
4582 */
4583HRESULT Medium::setFormat(CBSTR aFormat)
4584{
4585 /* get the format object first */
4586 {
4587 AutoReadLock propsLock(mVirtualBox->systemProperties());
4588
4589 unconst(m->formatObj)
4590 = mVirtualBox->systemProperties()->mediumFormat(aFormat);
4591 if (m->formatObj.isNull())
4592 return setError(E_INVALIDARG,
4593 tr("Invalid hard disk storage format '%ls'"), aFormat);
4594
4595 /* reference the format permanently to prevent its unexpected
4596 * uninitialization */
4597 HRESULT rc = m->formatObj->addCaller();
4598 AssertComRCReturnRC(rc);
4599
4600 /* get properties (preinsert them as keys in the map). Note that the
4601 * map doesn't grow over the object life time since the set of
4602 * properties is meant to be constant. */
4603
4604 Assert(m->properties.empty());
4605
4606 for (MediumFormat::PropertyList::const_iterator it =
4607 m->formatObj->properties().begin();
4608 it != m->formatObj->properties().end();
4609 ++ it)
4610 {
4611 m->properties.insert(std::make_pair(it->name, Bstr::Null));
4612 }
4613 }
4614
4615 unconst(m->strFormat) = aFormat;
4616
4617 return S_OK;
4618}
4619
4620/**
4621 * @note Called from this object's AutoMayUninitSpan and from under mVirtualBox
4622 * write lock.
4623 *
4624 * @note Also reused by Medium::Reset().
4625 *
4626 * @note Locks getTreeLock() for reading.
4627 */
4628HRESULT Medium::canClose()
4629{
4630 /* we access children */
4631 AutoReadLock treeLock(this->getTreeLock());
4632
4633 if (getChildren().size() != 0)
4634 return setError(E_FAIL,
4635 tr("Cannot close medium '%s' because it has %d child hard disk(s)"),
4636 m->strLocationFull.raw(), getChildren().size());
4637
4638 return S_OK;
4639}
4640
4641/**
4642 * @note Called from within this object's AutoMayUninitSpan (or AutoCaller) and
4643 * from under mVirtualBox write lock.
4644 *
4645 * @note Locks getTreeLock() for writing.
4646 */
4647HRESULT Medium::unregisterWithVirtualBox()
4648{
4649 /* Note that we need to de-associate ourselves from the parent to let
4650 * unregisterHardDisk() properly save the registry */
4651
4652 /* we modify mParent and access children */
4653 AutoWriteLock treeLock(this->getTreeLock());
4654
4655 const ComObjPtr<Medium, ComWeakRef> parent = mParent;
4656
4657 AssertReturn(getChildren().size() == 0, E_FAIL);
4658
4659 if (!mParent.isNull())
4660 {
4661 /* deassociate from the parent, associate with VirtualBox */
4662 mVirtualBox->addDependentChild(this);
4663 mParent->removeDependentChild(this);
4664 mParent.setNull();
4665 }
4666
4667 HRESULT rc = E_FAIL;
4668 switch (m->devType)
4669 {
4670 case DeviceType_DVD:
4671 rc = mVirtualBox->unregisterDVDImage(this);
4672 break;
4673 case DeviceType_Floppy:
4674 rc = mVirtualBox->unregisterFloppyImage(this);
4675 break;
4676 case DeviceType_HardDisk:
4677 rc = mVirtualBox->unregisterHardDisk(this);
4678 break;
4679 default:
4680 break;
4681 }
4682
4683 if (FAILED(rc))
4684 {
4685 if (!parent.isNull())
4686 {
4687 /* re-associate with the parent as we are still relatives in the
4688 * registry */
4689 mParent = parent;
4690 mParent->addDependentChild(this);
4691 mVirtualBox->removeDependentChild(this);
4692 }
4693 }
4694
4695 return rc;
4696}
4697
4698/**
4699 * Returns the last error message collected by the vdErrorCall callback and
4700 * resets it.
4701 *
4702 * The error message is returned prepended with a dot and a space, like this:
4703 * <code>
4704 * ". <error_text> (%Rrc)"
4705 * </code>
4706 * to make it easily appendable to a more general error message. The @c %Rrc
4707 * format string is given @a aVRC as an argument.
4708 *
4709 * If there is no last error message collected by vdErrorCall or if it is a
4710 * null or empty string, then this function returns the following text:
4711 * <code>
4712 * " (%Rrc)"
4713 * </code>
4714 *
4715 * @note Doesn't do any object locking; it is assumed that the caller makes sure
4716 * the callback isn't called by more than one thread at a time.
4717 *
4718 * @param aVRC VBox error code to use when no error message is provided.
4719 */
4720Utf8Str Medium::vdError(int aVRC)
4721{
4722 Utf8Str error;
4723
4724 if (m->vdError.isEmpty())
4725 error = Utf8StrFmt(" (%Rrc)", aVRC);
4726 else
4727 error = Utf8StrFmt(".\n%s", m->vdError.raw());
4728
4729 m->vdError.setNull();
4730
4731 return error;
4732}
4733
4734/**
4735 * Error message callback.
4736 *
4737 * Puts the reported error message to the m->vdError field.
4738 *
4739 * @note Doesn't do any object locking; it is assumed that the caller makes sure
4740 * the callback isn't called by more than one thread at a time.
4741 *
4742 * @param pvUser The opaque data passed on container creation.
4743 * @param rc The VBox error code.
4744 * @param RT_SRC_POS_DECL Use RT_SRC_POS.
4745 * @param pszFormat Error message format string.
4746 * @param va Error message arguments.
4747 */
4748/*static*/
4749DECLCALLBACK(void) Medium::vdErrorCall(void *pvUser, int rc, RT_SRC_POS_DECL,
4750 const char *pszFormat, va_list va)
4751{
4752 NOREF(pszFile); NOREF(iLine); NOREF(pszFunction); /* RT_SRC_POS_DECL */
4753
4754 Medium *that = static_cast<Medium*>(pvUser);
4755 AssertReturnVoid(that != NULL);
4756
4757 if (that->m->vdError.isEmpty())
4758 that->m->vdError =
4759 Utf8StrFmt("%s (%Rrc)", Utf8StrFmtVA(pszFormat, va).raw(), rc);
4760 else
4761 that->m->vdError =
4762 Utf8StrFmt("%s.\n%s (%Rrc)", that->m->vdError.raw(),
4763 Utf8StrFmtVA(pszFormat, va).raw(), rc);
4764}
4765
4766/**
4767 * PFNVMPROGRESS callback handler for Task operations.
4768 *
4769 * @param uPercent Completetion precentage (0-100).
4770 * @param pvUser Pointer to the Progress instance.
4771 */
4772/*static*/
4773DECLCALLBACK(int) Medium::vdProgressCall(PVM /* pVM */, unsigned uPercent,
4774 void *pvUser)
4775{
4776 Progress *that = static_cast<Progress *>(pvUser);
4777
4778 if (that != NULL)
4779 {
4780 /* update the progress object, capping it at 99% as the final percent
4781 * is used for additional operations like setting the UUIDs and similar. */
4782 HRESULT rc = that->SetCurrentOperationProgress(uPercent * 99 / 100);
4783 if (FAILED(rc))
4784 {
4785 if (rc == E_FAIL)
4786 return VERR_CANCELLED;
4787 else
4788 return VERR_INVALID_STATE;
4789 }
4790 }
4791
4792 return VINF_SUCCESS;
4793}
4794
4795/* static */
4796DECLCALLBACK(bool) Medium::vdConfigAreKeysValid(void *pvUser,
4797 const char * /* pszzValid */)
4798{
4799 Medium *that = static_cast<Medium*>(pvUser);
4800 AssertReturn(that != NULL, false);
4801
4802 /* we always return true since the only keys we have are those found in
4803 * VDBACKENDINFO */
4804 return true;
4805}
4806
4807/* static */
4808DECLCALLBACK(int) Medium::vdConfigQuerySize(void *pvUser, const char *pszName,
4809 size_t *pcbValue)
4810{
4811 AssertReturn(VALID_PTR(pcbValue), VERR_INVALID_POINTER);
4812
4813 Medium *that = static_cast<Medium*>(pvUser);
4814 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4815
4816 Data::PropertyMap::const_iterator it =
4817 that->m->properties.find(Bstr(pszName));
4818 if (it == that->m->properties.end())
4819 return VERR_CFGM_VALUE_NOT_FOUND;
4820
4821 /* we interpret null values as "no value" in Medium */
4822 if (it->second.isNull())
4823 return VERR_CFGM_VALUE_NOT_FOUND;
4824
4825 *pcbValue = it->second.length() + 1 /* include terminator */;
4826
4827 return VINF_SUCCESS;
4828}
4829
4830/* static */
4831DECLCALLBACK(int) Medium::vdConfigQuery(void *pvUser, const char *pszName,
4832 char *pszValue, size_t cchValue)
4833{
4834 AssertReturn(VALID_PTR(pszValue), VERR_INVALID_POINTER);
4835
4836 Medium *that = static_cast<Medium*>(pvUser);
4837 AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
4838
4839 Data::PropertyMap::const_iterator it =
4840 that->m->properties.find(Bstr(pszName));
4841 if (it == that->m->properties.end())
4842 return VERR_CFGM_VALUE_NOT_FOUND;
4843
4844 Utf8Str value = it->second;
4845 if (value.length() >= cchValue)
4846 return VERR_CFGM_NOT_ENOUGH_SPACE;
4847
4848 /* we interpret null values as "no value" in Medium */
4849 if (it->second.isNull())
4850 return VERR_CFGM_VALUE_NOT_FOUND;
4851
4852 memcpy(pszValue, value.c_str(), value.length() + 1);
4853
4854 return VINF_SUCCESS;
4855}
4856
4857/**
4858 * Thread function for time-consuming tasks.
4859 *
4860 * The Task structure passed to @a pvUser must be allocated using new and will
4861 * be freed by this method before it returns.
4862 *
4863 * @param pvUser Pointer to the Task instance.
4864 */
4865/* static */
4866DECLCALLBACK(int) Medium::taskThread(RTTHREAD thread, void *pvUser)
4867{
4868 std::auto_ptr <Task> task(static_cast <Task *>(pvUser));
4869 AssertReturn(task.get(), VERR_GENERAL_FAILURE);
4870
4871 bool isAsync = thread != NIL_RTTHREAD;
4872
4873 Medium *that = task->that;
4874
4875 /* Set up a per-operation progress interface, can be used freely (for
4876 * binary operations you can use it either on the source or target). */
4877 VDINTERFACEPROGRESS vdIfCallsProgress;
4878 vdIfCallsProgress.cbSize = sizeof(VDINTERFACEPROGRESS);
4879 vdIfCallsProgress.enmInterface = VDINTERFACETYPE_PROGRESS;
4880 vdIfCallsProgress.pfnProgress = Medium::vdProgressCall;
4881 VDINTERFACE vdIfProgress;
4882 PVDINTERFACE vdOperationIfaces = NULL;
4883 int vrc1 = VDInterfaceAdd(&vdIfProgress,
4884 "Medium::vdInterfaceProgress",
4885 VDINTERFACETYPE_PROGRESS,
4886 &vdIfCallsProgress,
4887 task->m_pProgress,
4888 &vdOperationIfaces);
4889 AssertRCReturn(vrc1, E_FAIL);
4890
4891 /// @todo ugly hack, fix ComAssert... later
4892 #define setError that->setError
4893
4894 /* Note: no need in AutoCaller because Task does that */
4895
4896 LogFlowFuncEnter();
4897 LogFlowFunc(("{%p}: operation=%d\n", that, task->m_operation));
4898
4899 HRESULT rc = S_OK;
4900
4901 switch (task->m_operation)
4902 {
4903 ////////////////////////////////////////////////////////////////////////
4904
4905 case Task::CreateBase:
4906 {
4907 /* The lock is also used as a signal from the task initiator (which
4908 * releases it only after RTThreadCreate()) that we can start the job */
4909 AutoWriteLock thatLock(that);
4910
4911 /* these parameters we need after creation */
4912 uint64_t size = 0, logicalSize = 0;
4913
4914 /* The object may request a specific UUID (through a special form of
4915 * the setLocation() argument). Otherwise we have to generate it */
4916 Guid id = that->m->id;
4917 bool generateUuid = id.isEmpty();
4918 if (generateUuid)
4919 {
4920 id.create();
4921 /* VirtualBox::registerHardDisk() will need UUID */
4922 unconst(that->m->id) = id;
4923 }
4924
4925 try
4926 {
4927 PVBOXHDD hdd;
4928 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
4929 ComAssertRCThrow(vrc, E_FAIL);
4930
4931 Utf8Str format(that->m->strFormat);
4932 Utf8Str location(that->m->strLocationFull);
4933 /* uint64_t capabilities = */ that->m->formatObj->capabilities();
4934
4935 /* unlock before the potentially lengthy operation */
4936 Assert(that->m->state == MediumState_Creating);
4937 thatLock.leave();
4938
4939 try
4940 {
4941 /* ensure the directory exists */
4942 rc = VirtualBox::ensureFilePathExists(location);
4943 if (FAILED(rc)) throw rc;
4944
4945 PDMMEDIAGEOMETRY geo = { 0 }; /* auto-detect */
4946
4947 vrc = VDCreateBase(hdd, format.c_str(), location.c_str(),
4948 task->d.size * _1M,
4949 task->d.variant,
4950 NULL, &geo, &geo, id.raw(),
4951 VD_OPEN_FLAGS_NORMAL,
4952 NULL, vdOperationIfaces);
4953 if (RT_FAILURE(vrc))
4954 {
4955 throw setError(E_FAIL,
4956 tr("Could not create the hard disk storage unit '%s'%s"),
4957 location.raw(), that->vdError(vrc).raw());
4958 }
4959
4960 size = VDGetFileSize(hdd, 0);
4961 logicalSize = VDGetSize(hdd, 0) / _1M;
4962 }
4963 catch (HRESULT aRC) { rc = aRC; }
4964
4965 VDDestroy(hdd);
4966 }
4967 catch (HRESULT aRC) { rc = aRC; }
4968
4969 if (SUCCEEDED(rc))
4970 {
4971 /* register with mVirtualBox as the last step and move to
4972 * Created state only on success (leaving an orphan file is
4973 * better than breaking media registry consistency) */
4974 rc = that->mVirtualBox->registerHardDisk(that);
4975 }
4976
4977 thatLock.maybeEnter();
4978
4979 if (SUCCEEDED(rc))
4980 {
4981 that->m->state = MediumState_Created;
4982
4983 that->m->size = size;
4984 that->m->logicalSize = logicalSize;
4985 }
4986 else
4987 {
4988 /* back to NotCreated on failure */
4989 that->m->state = MediumState_NotCreated;
4990
4991 /* reset UUID to prevent it from being reused next time */
4992 if (generateUuid)
4993 unconst(that->m->id).clear();
4994 }
4995
4996 break;
4997 }
4998
4999 ////////////////////////////////////////////////////////////////////////
5000
5001 case Task::CreateDiff:
5002 {
5003 ComObjPtr<Medium> &target = task->d.target;
5004
5005 /* Lock both in {parent,child} order. The lock is also used as a
5006 * signal from the task initiator (which releases it only after
5007 * RTThreadCreate()) that we can start the job*/
5008 AutoMultiWriteLock2 thatLock(that, target);
5009
5010 uint64_t size = 0, logicalSize = 0;
5011
5012 /* The object may request a specific UUID (through a special form of
5013 * the setLocation() argument). Otherwise we have to generate it */
5014 Guid targetId = target->m->id;
5015 bool generateUuid = targetId.isEmpty();
5016 if (generateUuid)
5017 {
5018 targetId.create();
5019 /* VirtualBox::registerHardDisk() will need UUID */
5020 unconst(target->m->id) = targetId;
5021 }
5022
5023 try
5024 {
5025 PVBOXHDD hdd;
5026 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5027 ComAssertRCThrow(vrc, E_FAIL);
5028
5029 Guid id = that->m->id;
5030 Utf8Str format(that->m->strFormat);
5031 Utf8Str location(that->m->strLocationFull);
5032
5033 Utf8Str targetFormat(target->m->strFormat);
5034 Utf8Str targetLocation(target->m->strLocationFull);
5035
5036 Assert(target->m->state == MediumState_Creating);
5037
5038 /* Note: MediumState_LockedWrite is ok when taking an online
5039 * snapshot */
5040 Assert(that->m->state == MediumState_LockedRead ||
5041 that->m->state == MediumState_LockedWrite);
5042
5043 /* unlock before the potentially lengthy operation */
5044 thatLock.leave();
5045
5046 try
5047 {
5048 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5049 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5050 that->m->vdDiskIfaces);
5051 if (RT_FAILURE(vrc))
5052 {
5053 throw setError(E_FAIL,
5054 tr("Could not open the hard disk storage unit '%s'%s"),
5055 location.raw(), that->vdError(vrc).raw());
5056 }
5057
5058 /* ensure the target directory exists */
5059 rc = VirtualBox::ensureFilePathExists(targetLocation);
5060 if (FAILED(rc)) throw rc;
5061
5062 /** @todo add VD_IMAGE_FLAGS_DIFF to the image flags, to
5063 * be on the safe side. */
5064 vrc = VDCreateDiff(hdd, targetFormat.c_str(),
5065 targetLocation.c_str(),
5066 task->d.variant,
5067 NULL, targetId.raw(),
5068 id.raw(),
5069 VD_OPEN_FLAGS_NORMAL,
5070 target->m->vdDiskIfaces,
5071 vdOperationIfaces);
5072 if (RT_FAILURE(vrc))
5073 {
5074 throw setError(E_FAIL,
5075 tr("Could not create the differencing hard disk storage unit '%s'%s"),
5076 targetLocation.raw(), that->vdError(vrc).raw());
5077 }
5078
5079 size = VDGetFileSize(hdd, 1);
5080 logicalSize = VDGetSize(hdd, 1) / _1M;
5081 }
5082 catch (HRESULT aRC) { rc = aRC; }
5083
5084 VDDestroy(hdd);
5085 }
5086 catch (HRESULT aRC) { rc = aRC; }
5087
5088 if (SUCCEEDED(rc))
5089 {
5090 /* we set mParent & children() (note that thatLock is released
5091 * here), but lock VirtualBox first to follow the rule */
5092 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5093 that->getTreeLock());
5094
5095 Assert(target->mParent.isNull());
5096
5097 /* associate the child with the parent and deassociate from
5098 * VirtualBox */
5099 target->mParent = that;
5100 that->addDependentChild(target);
5101 target->mVirtualBox->removeDependentChild(target);
5102
5103 /** @todo r=klaus neither target nor that->base() are locked,
5104 * potential race! */
5105 /* diffs for immutable hard disks are auto-reset by default */
5106 target->m->autoReset =
5107 that->getBase()->m->type == MediumType_Immutable ?
5108 TRUE : FALSE;
5109
5110 /* register with mVirtualBox as the last step and move to
5111 * Created state only on success (leaving an orphan file is
5112 * better than breaking media registry consistency) */
5113 rc = that->mVirtualBox->registerHardDisk(target);
5114
5115 if (FAILED(rc))
5116 {
5117 /* break the parent association on failure to register */
5118 target->mVirtualBox->addDependentChild(target);
5119 that->removeDependentChild(target);
5120 target->mParent.setNull();
5121 }
5122 }
5123
5124 thatLock.maybeEnter();
5125
5126 if (SUCCEEDED(rc))
5127 {
5128 target->m->state = MediumState_Created;
5129
5130 target->m->size = size;
5131 target->m->logicalSize = logicalSize;
5132 }
5133 else
5134 {
5135 /* back to NotCreated on failure */
5136 target->m->state = MediumState_NotCreated;
5137
5138 target->m->autoReset = FALSE;
5139
5140 /* reset UUID to prevent it from being reused next time */
5141 if (generateUuid)
5142 unconst(target->m->id).clear();
5143 }
5144
5145 if (isAsync)
5146 {
5147 /* unlock ourselves when done (unless in MediumState_LockedWrite
5148 * state because of taking the online snapshot*/
5149 if (that->m->state != MediumState_LockedWrite)
5150 {
5151 HRESULT rc2 = that->UnlockRead(NULL);
5152 AssertComRC(rc2);
5153 }
5154 }
5155
5156 /* deregister the task registered in createDiffStorage() */
5157 Assert(that->m->numCreateDiffTasks != 0);
5158 --that->m->numCreateDiffTasks;
5159
5160 /* Note that in sync mode, it's the caller's responsibility to
5161 * unlock the hard disk */
5162
5163 break;
5164 }
5165
5166 ////////////////////////////////////////////////////////////////////////
5167
5168 case Task::Merge:
5169 {
5170 /* The lock is also used as a signal from the task initiator (which
5171 * releases it only after RTThreadCreate()) that we can start the
5172 * job. We don't actually need the lock for anything else since the
5173 * object is protected by MediumState_Deleting and we don't modify
5174 * its sensitive fields below */
5175 {
5176 AutoWriteLock thatLock(that);
5177 }
5178
5179 MergeChain *chain = task->d.chain.get();
5180
5181#if 0
5182 LogFlow(("*** MERGE forward = %RTbool\n", chain->isForward()));
5183#endif
5184
5185 try
5186 {
5187 PVBOXHDD hdd;
5188 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5189 ComAssertRCThrow(vrc, E_FAIL);
5190
5191 try
5192 {
5193 /* Open all hard disks in the chain (they are in the
5194 * {parent,child} order in there. Note that we don't lock
5195 * objects in this chain since they must be in states
5196 * (Deleting and LockedWrite) that prevent from changing
5197 * their format and location fields from outside. */
5198
5199 for (MergeChain::const_iterator it = chain->begin();
5200 it != chain->end(); ++ it)
5201 {
5202 /* complex sanity (sane complexity) */
5203 Assert((chain->isForward() &&
5204 ((*it != chain->back() &&
5205 (*it)->m->state == MediumState_Deleting) ||
5206 (*it == chain->back() &&
5207 (*it)->m->state == MediumState_LockedWrite))) ||
5208 (!chain->isForward() &&
5209 ((*it != chain->front() &&
5210 (*it)->m->state == MediumState_Deleting) ||
5211 (*it == chain->front() &&
5212 (*it)->m->state == MediumState_LockedWrite))));
5213
5214 Assert(*it == chain->target() ||
5215 (*it)->m->backRefs.size() == 0);
5216
5217 /* open the first image with VDOPEN_FLAGS_INFO because
5218 * it's not necessarily the base one */
5219 vrc = VDOpen(hdd, (*it)->m->strFormat.c_str(),
5220 (*it)->m->strLocationFull.c_str(),
5221 it == chain->begin() ?
5222 VD_OPEN_FLAGS_INFO : 0,
5223 (*it)->m->vdDiskIfaces);
5224 if (RT_FAILURE(vrc))
5225 throw vrc;
5226#if 0
5227 LogFlow(("*** MERGE disk = %s\n", (*it)->m->strLocationFull.raw()));
5228#endif
5229 }
5230
5231 unsigned start = chain->isForward() ?
5232 0 : (unsigned)chain->size() - 1;
5233 unsigned end = chain->isForward() ?
5234 (unsigned)chain->size() - 1 : 0;
5235#if 0
5236 LogFlow(("*** MERGE from %d to %d\n", start, end));
5237#endif
5238 vrc = VDMerge(hdd, start, end, vdOperationIfaces);
5239 if (RT_FAILURE(vrc))
5240 throw vrc;
5241
5242 /* update parent UUIDs */
5243 /// @todo VDMerge should be taught to do so, including the
5244 /// multiple children case
5245 if (chain->isForward())
5246 {
5247 /* target's UUID needs to be updated (note that target
5248 * is the only image in the container on success) */
5249 vrc = VDSetParentUuid(hdd, 0, chain->parent()->m->id);
5250 if (RT_FAILURE(vrc))
5251 throw vrc;
5252 }
5253 else
5254 {
5255 /* we need to update UUIDs of all source's children
5256 * which cannot be part of the container at once so
5257 * add each one in there individually */
5258 if (chain->children().size() > 0)
5259 {
5260 for (List::const_iterator it = chain->children().begin();
5261 it != chain->children().end(); ++ it)
5262 {
5263 /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
5264 vrc = VDOpen(hdd, (*it)->m->strFormat.c_str(),
5265 (*it)->m->strLocationFull.c_str(),
5266 VD_OPEN_FLAGS_INFO,
5267 (*it)->m->vdDiskIfaces);
5268 if (RT_FAILURE(vrc))
5269 throw vrc;
5270
5271 vrc = VDSetParentUuid(hdd, 1,
5272 chain->target()->m->id);
5273 if (RT_FAILURE(vrc))
5274 throw vrc;
5275
5276 vrc = VDClose(hdd, false /* fDelete */);
5277 if (RT_FAILURE(vrc))
5278 throw vrc;
5279 }
5280 }
5281 }
5282 }
5283 catch (HRESULT aRC) { rc = aRC; }
5284 catch (int aVRC)
5285 {
5286 throw setError(E_FAIL,
5287 tr("Could not merge the hard disk '%s' to '%s'%s"),
5288 chain->source()->m->strLocationFull.raw(),
5289 chain->target()->m->strLocationFull.raw(),
5290 that->vdError(aVRC).raw());
5291 }
5292
5293 VDDestroy(hdd);
5294 }
5295 catch (HRESULT aRC) { rc = aRC; }
5296
5297 HRESULT rc2;
5298
5299 bool saveSettingsFailed = false;
5300
5301 if (SUCCEEDED(rc))
5302 {
5303 /* all hard disks but the target were successfully deleted by
5304 * VDMerge; reparent the last one and uninitialize deleted */
5305
5306 /* we set mParent & children() (note that thatLock is released
5307 * here), but lock VirtualBox first to follow the rule */
5308 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5309 that->getTreeLock());
5310
5311 Medium *source = chain->source();
5312 Medium *target = chain->target();
5313
5314 if (chain->isForward())
5315 {
5316 /* first, unregister the target since it may become a base
5317 * hard disk which needs re-registration */
5318 rc2 = target->mVirtualBox->
5319 unregisterHardDisk(target, false /* aSaveSettings */);
5320 AssertComRC(rc2);
5321
5322 /* then, reparent it and disconnect the deleted branch at
5323 * both ends (chain->parent() is source's parent) */
5324 target->mParent->removeDependentChild(target);
5325 target->mParent = chain->parent();
5326 if (!target->mParent.isNull())
5327 {
5328 target->mParent->addDependentChild(target);
5329 target->mParent->removeDependentChild(source);
5330 source->mParent.setNull();
5331 }
5332 else
5333 {
5334 target->mVirtualBox->addDependentChild(target);
5335 target->mVirtualBox->removeDependentChild(source);
5336 }
5337
5338 /* then, register again */
5339 rc2 = target->mVirtualBox->
5340 registerHardDisk(target, false /* aSaveSettings */);
5341 AssertComRC(rc2);
5342 }
5343 else
5344 {
5345 Assert(target->getChildren().size() == 1);
5346 Medium *targetChild = target->getChildren().front();
5347
5348 /* disconnect the deleted branch at the elder end */
5349 target->removeDependentChild(targetChild);
5350 targetChild->mParent.setNull();
5351
5352 const List &children = chain->children();
5353
5354 /* reparent source's chidren and disconnect the deleted
5355 * branch at the younger end m*/
5356 if (children.size() > 0)
5357 {
5358 /* obey {parent,child} lock order */
5359 AutoWriteLock sourceLock(source);
5360
5361 for (List::const_iterator it = children.begin();
5362 it != children.end(); ++ it)
5363 {
5364 AutoWriteLock childLock(*it);
5365
5366 (*it)->mParent = target;
5367 (*it)->mParent->addDependentChild(*it);
5368 source->removeDependentChild(*it);
5369 }
5370 }
5371 }
5372
5373 /* try to save the hard disk registry */
5374 rc = that->mVirtualBox->saveSettings();
5375
5376 if (SUCCEEDED(rc))
5377 {
5378 /* unregister and uninitialize all hard disks in the chain
5379 * but the target */
5380
5381 for (MergeChain::iterator it = chain->begin();
5382 it != chain->end();)
5383 {
5384 if (*it == chain->target())
5385 {
5386 ++ it;
5387 continue;
5388 }
5389
5390 rc2 = (*it)->mVirtualBox->
5391 unregisterHardDisk(*it, false /* aSaveSettings */);
5392 AssertComRC(rc2);
5393
5394 /* now, uninitialize the deleted hard disk (note that
5395 * due to the Deleting state, uninit() will not touch
5396 * the parent-child relationship so we need to
5397 * uninitialize each disk individually) */
5398
5399 /* note that the operation initiator hard disk (which is
5400 * normally also the source hard disk) is a special case
5401 * -- there is one more caller added by Task to it which
5402 * we must release. Also, if we are in sync mode, the
5403 * caller may still hold an AutoCaller instance for it
5404 * and therefore we cannot uninit() it (it's therefore
5405 * the caller's responsibility) */
5406 if (*it == that)
5407 task->m_autoCaller.release();
5408
5409 /* release the caller added by MergeChain before
5410 * uninit() */
5411 (*it)->releaseCaller();
5412
5413 if (isAsync || *it != that)
5414 (*it)->uninit();
5415
5416 /* delete (to prevent uninitialization in MergeChain
5417 * dtor) and advance to the next item */
5418 it = chain->erase(it);
5419 }
5420
5421 /* Note that states of all other hard disks (target, parent,
5422 * children) will be restored by the MergeChain dtor */
5423 }
5424 else
5425 {
5426 /* too bad if we fail, but we'll need to rollback everything
5427 * we did above to at least keep the HD tree in sync with
5428 * the current registry on disk */
5429
5430 saveSettingsFailed = true;
5431
5432 /// @todo NEWMEDIA implement a proper undo
5433
5434 AssertFailed();
5435 }
5436 }
5437
5438 if (FAILED(rc))
5439 {
5440 /* Here we come if either VDMerge() failed (in which case we
5441 * assume that it tried to do everything to make a further
5442 * retry possible -- e.g. not deleted intermediate hard disks
5443 * and so on) or VirtualBox::saveSettings() failed (where we
5444 * should have the original tree but with intermediate storage
5445 * units deleted by VDMerge()). We have to only restore states
5446 * (through the MergeChain dtor) unless we are run synchronously
5447 * in which case it's the responsibility of the caller as stated
5448 * in the mergeTo() docs. The latter also implies that we
5449 * don't own the merge chain, so release it in this case. */
5450
5451 if (!isAsync)
5452 task->d.chain.release();
5453
5454 NOREF(saveSettingsFailed);
5455 }
5456
5457 break;
5458 }
5459
5460 ////////////////////////////////////////////////////////////////////////
5461
5462 case Task::Clone:
5463 {
5464 ComObjPtr<Medium> &target = task->d.target;
5465 ComObjPtr<Medium> &parent = task->d.parentDisk;
5466
5467 /* Lock all in {parent,child} order. The lock is also used as a
5468 * signal from the task initiator (which releases it only after
5469 * RTThreadCreate()) that we can start the job. */
5470 AutoMultiWriteLock3 thatLock(that, target, parent);
5471
5472 ImageChain *srcChain = task->d.source.get();
5473 ImageChain *parentChain = task->d.parent.get();
5474
5475 uint64_t size = 0, logicalSize = 0;
5476
5477 /* The object may request a specific UUID (through a special form of
5478 * the setLocation() argument). Otherwise we have to generate it */
5479 Guid targetId = target->m->id;
5480 bool generateUuid = targetId.isEmpty();
5481 if (generateUuid)
5482 {
5483 targetId.create();
5484 /* VirtualBox::registerHardDisk() will need UUID */
5485 unconst(target->m->id) = targetId;
5486 }
5487
5488 try
5489 {
5490 PVBOXHDD hdd;
5491 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5492 ComAssertRCThrow(vrc, E_FAIL);
5493
5494 try
5495 {
5496 /* Open all hard disk images in the source chain. */
5497 for (List::const_iterator it = srcChain->begin();
5498 it != srcChain->end(); ++ it)
5499 {
5500 /* sanity check */
5501 Assert((*it)->m->state == MediumState_LockedRead);
5502
5503 /** Open all images in read-only mode. */
5504 vrc = VDOpen(hdd, (*it)->m->strFormat.c_str(),
5505 (*it)->m->strLocationFull.c_str(),
5506 VD_OPEN_FLAGS_READONLY,
5507 (*it)->m->vdDiskIfaces);
5508 if (RT_FAILURE(vrc))
5509 {
5510 throw setError(E_FAIL,
5511 tr("Could not open the hard disk storage unit '%s'%s"),
5512 (*it)->m->strLocationFull.raw(),
5513 that->vdError(vrc).raw());
5514 }
5515 }
5516
5517 Utf8Str targetFormat(target->m->strFormat);
5518 Utf8Str targetLocation(target->m->strLocationFull);
5519
5520 Assert( target->m->state == MediumState_Creating
5521 || target->m->state == MediumState_LockedWrite);
5522 Assert(that->m->state == MediumState_LockedRead);
5523 Assert(parent.isNull() || parent->m->state == MediumState_LockedRead);
5524
5525 /* unlock before the potentially lengthy operation */
5526 thatLock.leave();
5527
5528 /* ensure the target directory exists */
5529 rc = VirtualBox::ensureFilePathExists(targetLocation);
5530 if (FAILED(rc)) throw rc;
5531
5532 PVBOXHDD targetHdd;
5533 vrc = VDCreate(that->m->vdDiskIfaces, &targetHdd);
5534 ComAssertRCThrow(vrc, E_FAIL);
5535
5536 try
5537 {
5538 /* Open all hard disk images in the parent chain. */
5539 for (List::const_iterator it = parentChain->begin();
5540 it != parentChain->end(); ++ it)
5541 {
5542 /** @todo r=klaus (*it) is not locked, lots of
5543 * race opportunities below */
5544 /* sanity check */
5545 Assert( (*it)->m->state == MediumState_LockedRead
5546 || (*it)->m->state == MediumState_LockedWrite);
5547
5548 /* Open all images in appropriate mode. */
5549 vrc = VDOpen(targetHdd, (*it)->m->strFormat.c_str(),
5550 (*it)->m->strLocationFull.c_str(),
5551 ((*it)->m->state == MediumState_LockedWrite) ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY,
5552 (*it)->m->vdDiskIfaces);
5553 if (RT_FAILURE(vrc))
5554 {
5555 throw setError(E_FAIL,
5556 tr("Could not open the hard disk storage unit '%s'%s"),
5557 (*it)->m->strLocationFull.raw(),
5558 that->vdError(vrc).raw());
5559 }
5560 }
5561
5562 /** @todo r=klaus target isn't locked, race getting the state */
5563 vrc = VDCopy(hdd, VD_LAST_IMAGE, targetHdd,
5564 targetFormat.c_str(),
5565 target->m->state == MediumState_Creating ? targetLocation.raw() : (char *)NULL,
5566 false, 0,
5567 task->d.variant, targetId.raw(), NULL,
5568 target->m->vdDiskIfaces,
5569 vdOperationIfaces);
5570 if (RT_FAILURE(vrc))
5571 {
5572 throw setError(E_FAIL,
5573 tr("Could not create the clone hard disk '%s'%s"),
5574 targetLocation.raw(), that->vdError(vrc).raw());
5575 }
5576 size = VDGetFileSize(targetHdd, 0);
5577 logicalSize = VDGetSize(targetHdd, 0) / _1M;
5578 }
5579 catch (HRESULT aRC) { rc = aRC; }
5580
5581 VDDestroy(targetHdd);
5582 }
5583 catch (HRESULT aRC) { rc = aRC; }
5584
5585 VDDestroy(hdd);
5586 }
5587 catch (HRESULT aRC) { rc = aRC; }
5588
5589 /* Only do the parent changes for newly created images. */
5590 if (target->m->state == MediumState_Creating)
5591 {
5592 if (SUCCEEDED(rc))
5593 {
5594 /* we set mParent & children() (note that thatLock is released
5595 * here), but lock VirtualBox first to follow the rule */
5596 AutoMultiWriteLock2 alock(that->mVirtualBox->lockHandle(),
5597 that->getTreeLock());
5598
5599 Assert(target->mParent.isNull());
5600
5601 if (parent)
5602 {
5603 /* associate the clone with the parent and deassociate
5604 * from VirtualBox */
5605 target->mParent = parent;
5606 parent->addDependentChild(target);
5607 target->mVirtualBox->removeDependentChild(target);
5608
5609 /* register with mVirtualBox as the last step and move to
5610 * Created state only on success (leaving an orphan file is
5611 * better than breaking media registry consistency) */
5612 rc = parent->mVirtualBox->registerHardDisk(target);
5613
5614 if (FAILED(rc))
5615 {
5616 /* break parent association on failure to register */
5617 target->mVirtualBox->addDependentChild(target);
5618 parent->removeDependentChild(target);
5619 target->mParent.setNull();
5620 }
5621 }
5622 else
5623 {
5624 /* just register */
5625 rc = that->mVirtualBox->registerHardDisk(target);
5626 }
5627 }
5628 }
5629
5630 thatLock.maybeEnter();
5631
5632 if (target->m->state == MediumState_Creating)
5633 {
5634 if (SUCCEEDED(rc))
5635 {
5636 target->m->state = MediumState_Created;
5637
5638 target->m->size = size;
5639 target->m->logicalSize = logicalSize;
5640 }
5641 else
5642 {
5643 /* back to NotCreated on failure */
5644 target->m->state = MediumState_NotCreated;
5645
5646 /* reset UUID to prevent it from being reused next time */
5647 if (generateUuid)
5648 unconst(target->m->id).clear();
5649 }
5650 }
5651
5652 /* Everything is explicitly unlocked when the task exits,
5653 * as the task destruction also destroys the source chain. */
5654
5655 /* Make sure the source chain is released early. It could happen
5656 * that we get a deadlock in Appliance::Import when Medium::Close
5657 * is called & the source chain is released at the same time. */
5658 task->d.source.reset();
5659 break;
5660 }
5661
5662 ////////////////////////////////////////////////////////////////////////
5663
5664 case Task::Delete:
5665 {
5666 /* The lock is also used as a signal from the task initiator (which
5667 * releases it only after RTThreadCreate()) that we can start the job */
5668 AutoWriteLock thatLock(that);
5669
5670 try
5671 {
5672 PVBOXHDD hdd;
5673 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5674 ComAssertRCThrow(vrc, E_FAIL);
5675
5676 Utf8Str format(that->m->strFormat);
5677 Utf8Str location(that->m->strLocationFull);
5678
5679 /* unlock before the potentially lengthy operation */
5680 Assert(that->m->state == MediumState_Deleting);
5681 thatLock.leave();
5682
5683 try
5684 {
5685 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5686 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5687 that->m->vdDiskIfaces);
5688 if (RT_SUCCESS(vrc))
5689 vrc = VDClose(hdd, true /* fDelete */);
5690
5691 if (RT_FAILURE(vrc))
5692 {
5693 throw setError(E_FAIL,
5694 tr("Could not delete the hard disk storage unit '%s'%s"),
5695 location.raw(), that->vdError(vrc).raw());
5696 }
5697
5698 }
5699 catch (HRESULT aRC) { rc = aRC; }
5700
5701 VDDestroy(hdd);
5702 }
5703 catch (HRESULT aRC) { rc = aRC; }
5704
5705 thatLock.maybeEnter();
5706
5707 /* go to the NotCreated state even on failure since the storage
5708 * may have been already partially deleted and cannot be used any
5709 * more. One will be able to manually re-open the storage if really
5710 * needed to re-register it. */
5711 that->m->state = MediumState_NotCreated;
5712
5713 /* Reset UUID to prevent Create* from reusing it again */
5714 unconst(that->m->id).clear();
5715
5716 break;
5717 }
5718
5719 case Task::Reset:
5720 {
5721 /* The lock is also used as a signal from the task initiator (which
5722 * releases it only after RTThreadCreate()) that we can start the job */
5723 AutoWriteLock thatLock(that);
5724
5725 /// @todo Below we use a pair of delete/create operations to reset
5726 /// the diff contents but the most efficient way will of course be
5727 /// to add a VDResetDiff() API call
5728
5729 uint64_t size = 0, logicalSize = 0;
5730
5731 try
5732 {
5733 PVBOXHDD hdd;
5734 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5735 ComAssertRCThrow(vrc, E_FAIL);
5736
5737 Guid id = that->m->id;
5738 Utf8Str format(that->m->strFormat);
5739 Utf8Str location(that->m->strLocationFull);
5740
5741 Guid parentId = that->mParent->m->id;
5742 Utf8Str parentFormat(that->mParent->m->strFormat);
5743 Utf8Str parentLocation(that->mParent->m->strLocationFull);
5744
5745 Assert(that->m->state == MediumState_LockedWrite);
5746
5747 /* unlock before the potentially lengthy operation */
5748 thatLock.leave();
5749
5750 try
5751 {
5752 /* first, delete the storage unit */
5753 vrc = VDOpen(hdd, format.c_str(), location.c_str(),
5754 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5755 that->m->vdDiskIfaces);
5756 if (RT_SUCCESS(vrc))
5757 vrc = VDClose(hdd, true /* fDelete */);
5758
5759 if (RT_FAILURE(vrc))
5760 {
5761 throw setError(E_FAIL,
5762 tr("Could not delete the hard disk storage unit '%s'%s"),
5763 location.raw(), that->vdError(vrc).raw());
5764 }
5765
5766 /* next, create it again */
5767 vrc = VDOpen(hdd, parentFormat.c_str(), parentLocation.c_str(),
5768 VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
5769 that->m->vdDiskIfaces);
5770 if (RT_FAILURE(vrc))
5771 {
5772 throw setError(E_FAIL,
5773 tr("Could not open the hard disk storage unit '%s'%s"),
5774 parentLocation.raw(), that->vdError(vrc).raw());
5775 }
5776
5777 vrc = VDCreateDiff(hdd, format.c_str(), location.c_str(),
5778 /// @todo use the same image variant as before
5779 VD_IMAGE_FLAGS_NONE,
5780 NULL, id.raw(),
5781 parentId.raw(),
5782 VD_OPEN_FLAGS_NORMAL,
5783 that->m->vdDiskIfaces,
5784 vdOperationIfaces);
5785 if (RT_FAILURE(vrc))
5786 {
5787 throw setError(E_FAIL,
5788 tr("Could not create the differencing hard disk storage unit '%s'%s"),
5789 location.raw(), that->vdError(vrc).raw());
5790 }
5791
5792 size = VDGetFileSize(hdd, 1);
5793 logicalSize = VDGetSize(hdd, 1) / _1M;
5794 }
5795 catch (HRESULT aRC) { rc = aRC; }
5796
5797 VDDestroy(hdd);
5798 }
5799 catch (HRESULT aRC) { rc = aRC; }
5800
5801 thatLock.enter();
5802
5803 that->m->size = size;
5804 that->m->logicalSize = logicalSize;
5805
5806 if (isAsync)
5807 {
5808 /* unlock ourselves when done */
5809 HRESULT rc2 = that->UnlockWrite(NULL);
5810 AssertComRC(rc2);
5811 }
5812
5813 /* Note that in sync mode, it's the caller's responsibility to
5814 * unlock the hard disk */
5815
5816 break;
5817 }
5818
5819 ////////////////////////////////////////////////////////////////////////
5820
5821 case Task::Compact:
5822 {
5823 /* Lock all in {parent,child} order. The lock is also used as a
5824 * signal from the task initiator (which releases it only after
5825 * RTThreadCreate()) that we can start the job. */
5826 AutoWriteLock thatLock(that);
5827
5828 ImageChain *imgChain = task->d.images.get();
5829
5830 try
5831 {
5832 PVBOXHDD hdd;
5833 int vrc = VDCreate(that->m->vdDiskIfaces, &hdd);
5834 ComAssertRCThrow(vrc, E_FAIL);
5835
5836 try
5837 {
5838 /* Open all hard disk images in the chain. */
5839 List::const_iterator last = imgChain->end();
5840 last--;
5841 for (List::const_iterator it = imgChain->begin();
5842 it != imgChain->end(); ++ it)
5843 {
5844 /* sanity check */
5845 if (it == last)
5846 Assert((*it)->m->state == MediumState_LockedWrite);
5847 else
5848 Assert((*it)->m->state == MediumState_LockedRead);
5849
5850 /** Open all images but last in read-only mode. */
5851 vrc = VDOpen(hdd, (*it)->m->strFormat.c_str(),
5852 (*it)->m->strLocationFull.c_str(),
5853 (it == last) ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY,
5854 (*it)->m->vdDiskIfaces);
5855 if (RT_FAILURE(vrc))
5856 {
5857 throw setError(E_FAIL,
5858 tr("Could not open the hard disk storage unit '%s'%s"),
5859 (*it)->m->strLocationFull.raw(),
5860 that->vdError(vrc).raw());
5861 }
5862 }
5863
5864 Assert(that->m->state == MediumState_LockedWrite);
5865
5866 Utf8Str location(that->m->strLocationFull);
5867
5868 /* unlock before the potentially lengthy operation */
5869 thatLock.leave();
5870
5871 vrc = VDCompact(hdd, VD_LAST_IMAGE, vdOperationIfaces);
5872 if (RT_FAILURE(vrc))
5873 {
5874 if (vrc == VERR_NOT_SUPPORTED)
5875 throw setError(VBOX_E_NOT_SUPPORTED,
5876 tr("Compacting is not yet supported for hard disk '%s'"),
5877 location.raw());
5878 else if (vrc == VERR_NOT_IMPLEMENTED)
5879 throw setError(E_NOTIMPL,
5880 tr("Compacting is not implemented, hard disk '%s'"),
5881 location.raw());
5882 else
5883 throw setError(E_FAIL,
5884 tr("Could not compact hard disk '%s'%s"),
5885 location.raw(),
5886 that->vdError(vrc).raw());
5887 }
5888 }
5889 catch (HRESULT aRC) { rc = aRC; }
5890
5891 VDDestroy(hdd);
5892 }
5893 catch (HRESULT aRC) { rc = aRC; }
5894
5895 /* Everything is explicitly unlocked when the task exits,
5896 * as the task destruction also destroys the image chain. */
5897
5898 break;
5899 }
5900
5901 default:
5902 AssertFailedReturn(VERR_GENERAL_FAILURE);
5903 }
5904
5905 /* complete the progress if run asynchronously */
5906 if (isAsync)
5907 {
5908 if (!task->m_pProgress.isNull())
5909 task->m_pProgress->notifyComplete(rc);
5910 }
5911 else
5912 {
5913 task->m_rc = rc;
5914 }
5915
5916 LogFlowFunc(("rc=%Rhrc\n", rc));
5917 LogFlowFuncLeave();
5918
5919 return VINF_SUCCESS;
5920
5921 /// @todo ugly hack, fix ComAssert... later
5922 #undef setError
5923}
5924
5925/* vi: set tabstop=4 shiftwidth=4 expandtab: */
Note: See TracBrowser for help on using the repository browser.

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