VirtualBox

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

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

Main: do not reset diff images for immutable disks if the machine has snapshots

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

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