Index: trunk/src/VBox/Main/HardDisk2Impl.cpp
===================================================================
--- trunk/src/VBox/Main/HardDisk2Impl.cpp	(revision 15548)
+++ trunk/src/VBox/Main/HardDisk2Impl.cpp	(revision 15556)
@@ -57,5 +57,6 @@
 struct HardDisk2::Task : public com::SupportErrorInfoBase
 {
-    enum Operation { CreateDynamic, CreateFixed, CreateDiff, Merge, Delete };
+    enum Operation { CreateDynamic, CreateFixed, CreateDiff,
+                     Merge, Clone, Delete };
 
     HardDisk2 *that;
@@ -1128,6 +1129,5 @@
                                               IProgress **aProgress)
 {
-    if (aProgress == NULL)
-        return E_POINTER;
+    CheckComArgOutPointerValid (aProgress);
 
     AutoCaller autoCaller (this);
@@ -1184,6 +1184,5 @@
                                             IProgress **aProgress)
 {
-    if (aProgress == NULL)
-        return E_POINTER;
+    CheckComArgOutPointerValid (aProgress);
 
     AutoCaller autoCaller (this);
@@ -1239,6 +1238,8 @@
 STDMETHODIMP HardDisk2::DeleteStorage (IProgress **aProgress)
 {
-    if (aProgress == NULL)
-        return E_POINTER;
+    CheckComArgOutPointerValid (aProgress);
+
+    AutoCaller autoCaller (this);
+    CheckComRCReturnRC (autoCaller.rc());
 
     ComObjPtr <Progress> progress;
@@ -1256,8 +1257,6 @@
 STDMETHODIMP HardDisk2::CreateDiffStorage (IHardDisk2 *aTarget, IProgress **aProgress)
 {
-    if (aTarget == NULL)
-        return E_INVALIDARG;
-    if (aProgress == NULL)
-        return E_POINTER;
+    CheckComArgNotNull (aTarget);
+    CheckComArgOutPointerValid (aProgress);
 
     AutoCaller autoCaller (this);
@@ -1306,8 +1305,70 @@
 STDMETHODIMP HardDisk2::CloneTo (IHardDisk2 *aTarget, IProgress **aProgress)
 {
+    CheckComArgNotNull (aTarget);
+    CheckComArgOutPointerValid (aProgress);
+
     AutoCaller autoCaller (this);
     CheckComRCReturnRC (autoCaller.rc());
 
-    ReturnComNotImplemented();
+    ComObjPtr <HardDisk2> target;
+    HRESULT rc = mVirtualBox->cast (aTarget, target);
+    CheckComRCReturnRC (rc);
+
+    AutoMultiWriteLock2 alock (this, target);
+
+    /* We want to be locked for reading as long as the clone hard disk is being
+     * created*/
+    rc = LockRead (NULL);
+    CheckComRCReturnRC (rc);
+
+    ComObjPtr <Progress> progress;
+
+    try
+    {
+        if (target->m.state != MediaState_NotCreated)
+            throw target->setStateError();
+
+        progress.createObject();
+        rc = progress->init (mVirtualBox, static_cast <IHardDisk2 *> (this),
+            BstrFmt (tr ("Creating a clone hard disk '%s'"),
+                     target->name().raw()),
+            FALSE /* aCancelable */);
+        CheckComRCThrowRC (rc);
+
+        /* setup task object and thread to carry out the operation
+         * asynchronously */
+
+        std::auto_ptr <Task> task (new Task (this, progress, Task::Clone));
+        AssertComRCThrowRC (task->autoCaller.rc());
+
+        task->setData (target);
+
+        rc = task->startThread();
+        CheckComRCThrowRC (rc);
+
+        /* go to Creating state before leaving the lock */
+        target->m.state = MediaState_Creating;
+
+        /* task is now owned (or already deleted) by taskThread() so release it */
+        task.release();
+    }
+    catch (HRESULT aRC)
+    {
+        rc = aRC;
+    }
+
+    if (FAILED (rc))
+    {
+        HRESULT rc2 = UnlockRead (NULL);
+        AssertComRC (rc2);
+        /* Note: on success, taskThread() will unlock this */
+    }
+    else
+    {
+        /* return progress to the caller */
+        progress.queryInterfaceTo (aProgress);
+    }
+
+    return rc;
 }
 
@@ -3203,7 +3264,14 @@
             uint64_t size = 0, logicalSize = 0;
 
-            /* the object may request a specific UUID (through a special
-             * form of the setLocation() argumet) */
+            /* The object may request a specific UUID (through a special form of
+             * the setLocation() argumet). Otherwise we have to generate it */
             Guid id = that->m.id;
+            bool generateUuid = id.isEmpty();
+            if (generateUuid)
+            {
+                id.create();
+                /* VirtualBox::registerHardDisk2() will need UUID */
+                unconst (that->m.id) = id;
+            }
 
             try
@@ -3238,6 +3306,5 @@
                                         task->d.size * _1M,
                                         VD_IMAGE_FLAGS_NONE,
-                                        NULL, &geo, &geo,
-                                        id.isEmpty() ? NULL : id.raw(),
+                                        NULL, &geo, &geo, id.raw(),
                                         VD_OPEN_FLAGS_NORMAL,
                                         NULL, that->mm.vdDiskIfaces);
@@ -3251,21 +3318,4 @@
                     }
 
-                    if (capabilities & HardDiskFormatCapabilities_Uuid)
-                    {
-                        RTUUID uuid;
-                        vrc = VDGetUuid (hdd, 0, &uuid);
-                        ComAssertRCThrow (vrc, E_FAIL);
-
-                        if (!id.isEmpty())
-                            Assert (id == uuid);
-                        else
-                            id = uuid;
-                    }
-                    else
-                    {
-                        /* we have to generate an UUID ourselves */
-                        id.create();
-                    }
-
                     size = VDGetFileSize (hdd, 0);
                     logicalSize = VDGetSize (hdd, 0) / _1M;
@@ -3279,28 +3329,27 @@
             if (SUCCEEDED (rc))
             {
-                /* mVirtualBox->registerHardDisk2() needs a write lock */
-                AutoWriteLock vboxLock (that->mVirtualBox);
-                thatLock.enter();
-
-                unconst (that->m.id) = id;
-
-                that->m.size = size;
-                that->mm.logicalSize = logicalSize;
-
                 /* register with mVirtualBox as the last step and move to
                  * Created state only on success (leaving an orphan file is
                  * better than breaking media registry consistency) */
                 rc = that->mVirtualBox->registerHardDisk2 (that);
-
-                if (SUCCEEDED (rc))
-                    that->m.state = MediaState_Created;
             }
 
-            if (FAILED (rc))
+            thatLock.maybeEnter();
+
+            if (SUCCEEDED (rc))
             {
-                thatLock.maybeEnter();
-
+                that->m.state = MediaState_Created;
+
+                that->m.size = size;
+                that->mm.logicalSize = logicalSize;
+            }
+            else
+            {
                 /* back to NotCreated on failiure */
                 that->m.state = MediaState_NotCreated;
+
+                /* reset UUID to prevent it from being reused next time */
+                if (generateUuid)
+                    unconst (that->m.id).clear();
             }
 
@@ -3320,4 +3369,15 @@
 
             uint64_t size = 0, logicalSize = 0;
+
+            /* The object may request a specific UUID (through a special form of
+             * the setLocation() argumet). Otherwise we have to generate it */
+            Guid targetId = target->m.id;
+            bool generateUuid = targetId.isEmpty();
+            if (generateUuid)
+            {
+                targetId.create();
+                /* VirtualBox::registerHardDisk2() will need UUID */
+                unconst (target->m.id) = targetId;
+            }
 
             try
@@ -3332,8 +3392,4 @@
                 Utf8Str targetFormat (target->mm.format);
                 Utf8Str targetLocation (target->m.locationFull);
-                Guid targetId = target->m.id;
-
-                /* UUID must have been set by setLocation() */
-                Assert (!targetId.isEmpty());
 
                 Assert (target->m.state == MediaState_Creating);
@@ -3434,4 +3490,8 @@
                 /* back to NotCreated on failiure */
                 target->m.state = MediaState_NotCreated;
+
+                /* reset UUID to prevent it from being reused next time */
+                if (generateUuid)
+                    unconst (target->m.id).clear();
             }
 
@@ -3759,4 +3819,171 @@
         ////////////////////////////////////////////////////////////////////////
 
+        case Task::Clone:
+        {
+            ComObjPtr <HardDisk2> &target = task->d.target;
+
+            /* Lock both in {parent,child} order. The lock is also used as a
+             * signal from the task initiator (which releases it only after
+             * RTThreadCreate()) that we can start the job*/
+            AutoMultiWriteLock2 thatLock (that, target);
+
+            uint64_t size = 0, logicalSize = 0;
+
+            /* The object may request a specific UUID (through a special form of
+             * the setLocation() argumet). Otherwise we have to generate it */
+            Guid targetId = target->m.id;
+            bool generateUuid = targetId.isEmpty();
+            if (generateUuid)
+            {
+                targetId.create();
+                /* VirtualBox::registerHardDisk2() will need UUID */
+                unconst (target->m.id) = targetId;
+            }
+
+            try
+            {
+                PVBOXHDD hdd;
+                int vrc = VDCreate (that->mm.vdDiskIfaces, &hdd);
+                ComAssertRCThrow (vrc, E_FAIL);
+
+                Utf8Str format (that->mm.format);
+                Utf8Str location (that->m.locationFull);
+
+                Utf8Str targetFormat (target->mm.format);
+                Utf8Str targetLocation (target->m.locationFull);
+
+                Assert (target->m.state == MediaState_Creating);
+
+                Assert (that->m.state == MediaState_LockedRead);
+
+                /* unlock before the potentially lengthy operation */
+                thatLock.leave();
+
+                try
+                {
+                    vrc = VDOpen (hdd, format, location,
+                                  VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO,
+                                  NULL);
+                    if (RT_FAILURE (vrc))
+                    {
+                        throw setError (E_FAIL,
+                            tr ("Could not open the hard disk storage "
+                                "unit '%s'%s"),
+                            location.raw(), that->vdError (vrc).raw());
+                    }
+
+                    /* ensure the target directory exists */
+                    rc = VirtualBox::ensureFilePathExists (targetLocation);
+                    CheckComRCThrowRC (rc);
+
+                    /* needed for vdProgressCallback */
+                    that->mm.vdProgress = task->progress;
+
+                    PVBOXHDD targetHdd;
+                    int vrc = VDCreate (that->mm.vdDiskIfaces, &targetHdd);
+                    ComAssertRCThrow (vrc, E_FAIL);
+
+                    vrc = VDCopy (hdd, 0, targetHdd, targetFormat,
+                                  targetLocation, false, 0, targetId.raw(),
+                                  NULL, NULL, that->mm.vdDiskIfaces);
+
+                    that->mm.vdProgress = NULL;
+
+                    if (RT_FAILURE (vrc))
+                    {
+                        VDDestroy (targetHdd);
+
+                        throw setError (E_FAIL,
+                            tr ("Could not create the clone hard disk "
+                                "'%s'%s"),
+                            targetLocation.raw(), that->vdError (vrc).raw());
+                    }
+
+                    size = VDGetFileSize (hdd, 0);
+                    logicalSize = VDGetSize (hdd, 0) / _1M;
+
+                    VDDestroy (targetHdd);
+                }
+                catch (HRESULT aRC) { rc = aRC; }
+
+                VDDestroy (hdd);
+            }
+            catch (HRESULT aRC) { rc = aRC; }
+
+            if (SUCCEEDED (rc))
+            {
+                /* we set mParent & children() (note that thatLock is released
+                 * here), but lock VirtualBox first to follow the rule */
+                AutoMultiWriteLock2 alock (that->mVirtualBox->lockHandle(),
+                                           that->treeLock());
+
+                Assert (target->mParent.isNull());
+
+                if (!that->mParent.isNull())
+                {
+                    /* associate the clone with the original's parent and
+                     * deassociate from VirtualBox */
+                    target->mParent = that->mParent;
+                    that->mParent->addDependentChild (target);
+                    target->mVirtualBox->removeDependentChild (target);
+
+                    /* register with mVirtualBox as the last step and move to
+                     * Created state only on success (leaving an orphan file is
+                     * better than breaking media registry consistency) */
+                    rc = that->mVirtualBox->registerHardDisk2 (target);
+
+                    if (FAILED (rc))
+                    {
+                        /* break the parent association on failure to register */
+                        target->mVirtualBox->addDependentChild (target);
+                        that->mParent->removeDependentChild (target);
+                        target->mParent.setNull();
+                    }
+                }
+                else
+                {
+                    /* just register  */
+                    rc = that->mVirtualBox->registerHardDisk2 (target);
+                }
+            }
+
+            thatLock.maybeEnter();
+
+            if (SUCCEEDED (rc))
+            {
+                target->m.state = MediaState_Created;
+
+                target->m.size = size;
+                target->mm.logicalSize = logicalSize;
+            }
+            else
+            {
+                /* back to NotCreated on failiure */
+                target->m.state = MediaState_NotCreated;
+
+                /* reset UUID to prevent it from being reused next time */
+                if (generateUuid)
+                    unconst (target->m.id).clear();
+            }
+
+            if (isAsync)
+            {
+                /* unlock ourselves when done (unless in MediaState_LockedWrite
+                 * state because of taking the online snapshot*/
+                if (that->m.state != MediaState_LockedWrite)
+                {
+                    HRESULT rc2 = that->UnlockRead (NULL);
+                    AssertComRC (rc2);
+                }
+            }
+
+            /* Note that in sync mode, it's the caller's responsibility to
+             * unlock the hard disk */
+
+            break;
+        }
+
+        ////////////////////////////////////////////////////////////////////////
+
         case Task::Delete:
         {
