<div dir="ltr"><div>Hi there,</div><div><br></div><div>Re: <a href="https://www.virtualbox.org/ticket/19320" target="_blank">https://www.virtualbox.org/ticket/19320</a> - Allow exporting & importing of VMs with NVMe storage devices</div><div><a href="https://www.virtualbox.org/ticket/19320" target="_blank"></a></div><div><br></div><div></div><div>VirtualBox errors with VBOX_E_NOT_SUPPORTED when exporting a VM with an NVMe controller. I am not a C++ programmer but have had a crack at the patch below by taking cues from the import/export code for the other controller types.</div><div><br></div><div>As far as I can tell there is no OVF specified ResourceSubType for NVMe so I chose “nvme” like VirtualBox’s existing internal representation. VMWare Fusion uses vmware.nvme.controller in its exports so I supported that as well for importing. Let me know if you would rather use that for compatibility on export, or a vendor prefixed version like org.virtualbox.nvme instead.</div><div><br></div><div><br></div><div>Following code licensed under MIT.</div><div><br></div><div><br></div><div>Index: src/VBox/Frontends/VirtualBox/src/widgets/UIApplianceEditorWidget.cpp<br>===================================================================<br>--- src/VBox/Frontends/VirtualBox/src/widgets/UIApplianceEditorWidget.cpp        (revision 82781)<br>+++ src/VBox/Frontends/VirtualBox/src/widgets/UIApplianceEditorWidget.cpp     (working copy)<br>@@ -482,6 +482,7 @@<br>                     case KVirtualSystemDescriptionType_HardDiskControllerSATA: value = UIApplianceEditorWidget::tr("Storage Controller (SATA)"); break;<br>                     case KVirtualSystemDescriptionType_HardDiskControllerSCSI: value = UIApplianceEditorWidget::tr("Storage Controller (SCSI)"); break;<br>                     case KVirtualSystemDescriptionType_HardDiskControllerSAS:  value = UIApplianceEditorWidget::tr("Storage Controller (SAS)"); break;<br>+                    case KVirtualSystemDescriptionType_HardDiskControllerNVMe: value = UIApplianceEditorWidget::tr("Storage Controller (NVMe)"); break;<br>                     case KVirtualSystemDescriptionType_CDROM:                  value = UIApplianceEditorWidget::tr("DVD"); break;<br>                     case KVirtualSystemDescriptionType_Floppy:                 value = UIApplianceEditorWidget::tr("Floppy"); break;<br>                     case KVirtualSystemDescriptionType_NetworkAdapter:         value = UIApplianceEditorWidget::tr("Network Adapter"); break;<br>@@ -656,6 +657,7 @@<br>                     case KVirtualSystemDescriptionType_HardDiskControllerSATA: value = UIIconPool::iconSet(":/sata_16px.png"); break;<br>                     case KVirtualSystemDescriptionType_HardDiskControllerSCSI: value = UIIconPool::iconSet(":/scsi_16px.png"); break;<br>                     case KVirtualSystemDescriptionType_HardDiskControllerSAS:  value = UIIconPool::iconSet(":/sas_16px.png"); break;<br>+                    case KVirtualSystemDescriptionType_HardDiskControllerNVMe: value = UIIconPool::iconSet(":/pcie_16px.png"); break;<br>                     case KVirtualSystemDescriptionType_HardDiskImage:          value = UIIconPool::iconSet(":/hd_16px.png"); break;<br>                     case KVirtualSystemDescriptionType_CDROM:                  value = UIIconPool::iconSet(":/cd_16px.png"); break;<br>                     case KVirtualSystemDescriptionType_Floppy:                 value = UIIconPool::iconSet(":/fd_16px.png"); break;<br>@@ -1340,7 +1342,8 @@<br>                 if (types[i] == KVirtualSystemDescriptionType_HardDiskControllerIDE ||<br>                     types[i] == KVirtualSystemDescriptionType_HardDiskControllerSATA ||<br>                     types[i] == KVirtualSystemDescriptionType_HardDiskControllerSCSI ||<br>-                    types[i] == KVirtualSystemDescriptionType_HardDiskControllerSAS)<br>+                    types[i] == KVirtualSystemDescriptionType_HardDiskControllerSAS ||<br>+                    types[i] == KVirtualSystemDescriptionType_HardDiskControllerNVMe)<br>                     controllerMap[i] = pHardwareItem;<br>             }<br>         }<br>@@ -1681,6 +1684,7 @@<br>     KVirtualSystemDescriptionType_HardDiskControllerSATA,<br>     KVirtualSystemDescriptionType_HardDiskControllerSCSI,<br>     KVirtualSystemDescriptionType_HardDiskControllerSAS,<br>+    KVirtualSystemDescriptionType_HardDiskControllerNVMe,<br>     /* OCI */<br>     KVirtualSystemDescriptionType_CloudProfileName,<br>     KVirtualSystemDescriptionType_CloudBucket,<br>Index: src/VBox/Frontends/VirtualBox/src/widgets/UIApplianceExportEditorWidget.cpp<br>===================================================================<br>--- src/VBox/Frontends/VirtualBox/src/widgets/UIApplianceExportEditorWidget.cpp     (revision 82781)<br>+++ src/VBox/Frontends/VirtualBox/src/widgets/UIApplianceExportEditorWidget.cpp       (working copy)<br>@@ -50,6 +50,7 @@<br>             << KVirtualSystemDescriptionType_HardDiskControllerSATA<br>             << KVirtualSystemDescriptionType_HardDiskControllerSCSI<br>             << KVirtualSystemDescriptionType_HardDiskControllerSAS<br>+            << KVirtualSystemDescriptionType_HardDiskControllerNVMe<br>             << KVirtualSystemDescriptionType_CloudProfileName;<br>     }<br> };<br>Index: src/VBox/Main/idl/VirtualBox.xidl<br>===================================================================<br>--- src/VBox/Main/idl/VirtualBox.xidl        (revision 82781)<br>+++ src/VBox/Main/idl/VirtualBox.xidl (working copy)<br>@@ -4024,6 +4024,7 @@<br>     <const name="CloudOCISubnetCompartment" value="47" /><br>     <const name="CloudPublicSSHKey" value="48" /><br>     <const name="BootingFirmware" value="49" /><br>+    <const name="HardDiskControllerNVMe" value="50" /><br>   </enum><br> <br>   <enum<br>Index: src/VBox/Main/include/ovfreader.h<br>===================================================================<br>--- src/VBox/Main/include/ovfreader.h        (revision 82781)<br>+++ src/VBox/Main/include/ovfreader.h (working copy)<br>@@ -534,8 +534,8 @@<br> {<br>     uint32_t                idController;       // instance ID (Item/InstanceId); this gets referenced from VirtualDisk<br> <br>-    enum ControllerSystemType { IDE, SATA, SCSI };<br>-    ControllerSystemType    system;             // one of IDE, SATA, SCSI<br>+    enum ControllerSystemType { IDE, SATA, SCSI, NVMe };<br>+    ControllerSystemType    system;             // one of IDE, SATA, SCSI, NVMe<br> <br>     RTCString        strControllerType;<br>             // controller subtype (Item/ResourceSubType); e.g. "LsiLogic"; can be empty (esp. for IDE)<br>Index: src/VBox/Main/src-server/ApplianceImpl.cpp<br>===================================================================<br>--- src/VBox/Main/src-server/ApplianceImpl.cpp       (revision 82781)<br>+++ src/VBox/Main/src-server/ApplianceImpl.cpp        (working copy)<br>@@ -1770,6 +1770,7 @@<br>             case VirtualSystemDescriptionType_HardDiskControllerSATA:<br>             case VirtualSystemDescriptionType_HardDiskControllerSCSI:<br>             case VirtualSystemDescriptionType_HardDiskControllerSAS:<br>+            case VirtualSystemDescriptionType_HardDiskControllerNVMe:<br>                 if (d.strRef == strRef)<br>                     return &d;<br>                 break;<br>Index: src/VBox/Main/src-server/ApplianceImplExport.cpp<br>===================================================================<br>--- src/VBox/Main/src-server/ApplianceImplExport.cpp        (revision 82781)<br>+++ src/VBox/Main/src-server/ApplianceImplExport.cpp  (working copy)<br>@@ -185,6 +185,7 @@<br>         int32_t lIDEControllerSecondaryIndex = 0;<br>         int32_t lSATAControllerIndex = 0;<br>         int32_t lSCSIControllerIndex = 0;<br>+        int32_t lNVMeControllerIndex = 0;<br> <br>         /* Fetch all available storage controllers */<br>         com::SafeIfaceArray<IStorageController> nwControllers;<br>@@ -195,6 +196,7 @@<br>         ComPtr<IStorageController> pSATAController;<br>         ComPtr<IStorageController> pSCSIController;<br>         ComPtr<IStorageController> pSASController;<br>+        ComPtr<IStorageController> pNVMeController;<br>         for (size_t j = 0; j < nwControllers.size(); ++j)<br>         {<br>             StorageBus_T eType;<br>@@ -212,6 +214,8 @@<br>             else if (   eType == StorageBus_SAS<br>                      && pSASController.isNull())<br>                 pSASController = nwControllers[j];<br>+            else if (   eType == StorageBus_PCIe)<br>+                pNVMeController = nwControllers[j];<br>         }<br> <br> //     <const name="HardDiskControllerIDE" value="6" /><br>@@ -292,6 +296,16 @@<br>                                  strVBox);<br>         }<br> <br>+        if (!pNVMeController.isNull())<br>+        {<br>+            Utf8Str strVBox = "NVMe";<br>+            lNVMeControllerIndex = (int32_t)pNewDesc->m->maDescriptions.size();<br>+            pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerNVMe,<br>+                                 Utf8StrFmt("%d", lNVMeControllerIndex),<br>+                                 strVBox,<br>+                                 strVBox);<br>+        }<br>+<br> //     <const name="HardDiskImage" value="9" /><br> //     <const name="Floppy" value="18" /><br> //     <const name="CDROM" value="19" /><br>@@ -344,6 +358,8 @@<br>                 strStBus = "SCSI";<br>                 else if ( storageBus == StorageBus_SAS)<br>                 strStBus = "SAS";<br>+                else if ( storageBus == StorageBus_PCIe)<br>+                strStBus = "PCIe";<br>                 LogRel(("Warning: skip the medium (bus: %s, slot: %d, port: %d). No storage device attached.\n",<br>                 strStBus.c_str(), lDevice, lChannel));<br>                 continue;<br>@@ -520,6 +536,11 @@<br>                     lControllerVsys = lSCSIControllerIndex;<br>                     break;<br> <br>+                case StorageBus_PCIe:<br>+                    lChannelVsys = lChannel;        // should be between 0 and 255<br>+                    lControllerVsys = lNVMeControllerIndex;<br>+                    break;<br>+<br>                 case StorageBus_Floppy:<br>                     lChannelVsys = 0;<br>                     lControllerVsys = 0;<br>@@ -1477,6 +1498,8 @@<br>     int32_t lSATAControllerIndex = 0;<br>     uint32_t idSCSIController = 0;<br>     int32_t lSCSIControllerIndex = 0;<br>+    uint32_t idNVMeController = 0;<br>+    int32_t lNVMeControllerIndex = 0;<br> <br>     uint32_t ulInstanceID = 1;<br> <br>@@ -1499,6 +1522,7 @@<br>                           : desc.type == VirtualSystemDescriptionType_HardDiskControllerSATA ? "HardDiskControllerSATA"<br>                           : desc.type == VirtualSystemDescriptionType_HardDiskControllerSCSI ? "HardDiskControllerSCSI"<br>                           : desc.type == VirtualSystemDescriptionType_HardDiskControllerSAS ? "HardDiskControllerSAS"<br>+                          : desc.type == VirtualSystemDescriptionType_HardDiskControllerNVMe ? "HardDiskControllerNVMe"<br>                           : desc.type == VirtualSystemDescriptionType_HardDiskImage ? "HardDiskImage"<br>                           : Utf8StrFmt("%d", desc.type).c_str()),<br>                          desc.strRef.c_str(),<br>@@ -1689,6 +1713,42 @@<br>                     }<br>                     break;<br> <br>+                case VirtualSystemDescriptionType_HardDiskControllerNVMe:<br>+                    /*  <Item><br>+                            <rasd:Caption>nvmeController0</rasd:Caption><br>+                            <rasd:Description>NVMe Controller</rasd:Description><br>+                            <rasd:InstanceId>4</rasd:InstanceId><br>+                            <rasd:ResourceType>20</rasd:ResourceType><br>+                            <rasd:ResourceSubType>nvme</rasd:ResourceSubType><br>+                            <rasd:Address>0</rasd:Address><br>+                            <rasd:BusNumber>0</rasd:BusNumber><br>+                        </Item><br>+                    */<br>+                    if (uLoop == 1)<br>+                    {<br>+                        strDescription = "NVMe Controller";<br>+                        strCaption = "nvmeController0";<br>+                        type = ovf::ResourceType_OtherStorageDevice; // 20<br>+                        // it seems that OVFTool always writes these two, and since we can only<br>+                        // have one NVMe controller, we'll use this as well<br>+                        lAddress = 0;<br>+                        lBusNumber = 0;<br>+<br>+                        if (    desc.strVBoxCurrent.isEmpty()      // NVMe is the default in VirtualBox<br>+                             || (!desc.strVBoxCurrent.compare("nvme", Utf8Str::CaseInsensitive))<br>+                            )<br>+                            strResourceSubType = "nvme";<br>+                        else<br>+                            throw setError(VBOX_E_NOT_SUPPORTED,<br>+                                           tr("Invalid config string \"%s\" in NVMe controller"),<br>+                                           desc.strVBoxCurrent.c_str());<br>+<br>+                        // remember this ID<br>+                        idNVMeController = ulInstanceID;<br>+                        lNVMeControllerIndex = lIndexThis;<br>+                    }<br>+                    break;<br>+<br>                 case VirtualSystemDescriptionType_HardDiskImage:<br>                     /*  <Item><br>                             <rasd:Caption>disk1</rasd:Caption><br>@@ -1725,6 +1785,8 @@<br>                                 ulParent = idSCSIController;<br>                             else if (lControllerIndex == lSATAControllerIndex)<br>                                 ulParent = idSATAController;<br>+                            else if (lControllerIndex == lNVMeControllerIndex)<br>+                                ulParent = idNVMeController;<br>                         }<br>                         if (pos2 != Utf8Str::npos)<br>                             RTStrToInt32Ex(desc.strExtraConfigCurrent.c_str() + pos2 + 8, NULL, 0, &lAddressOnParent);<br>@@ -1802,6 +1864,8 @@<br>                                 ulParent = idSCSIController;<br>                             else if (lControllerIndex == lSATAControllerIndex)<br>                                 ulParent = idSATAController;<br>+                            else if (lControllerIndex == lNVMeControllerIndex)<br>+                                ulParent = idNVMeController;<br>                         }<br>                         if (pos2 != Utf8Str::npos)<br>                             RTStrToInt32Ex(desc.strExtraConfigCurrent.c_str() + pos2 + 8, NULL, 0, &lAddressOnParent);<br>Index: src/VBox/Main/src-server/ApplianceImplImport.cpp<br>===================================================================<br>--- src/VBox/Main/src-server/ApplianceImplImport.cpp        (revision 82781)<br>+++ src/VBox/Main/src-server/ApplianceImplImport.cpp  (working copy)<br>@@ -561,6 +561,7 @@<br>             uint16_t cIDEused = 0;<br>             uint16_t cSATAused = 0; NOREF(cSATAused);<br>             uint16_t cSCSIused = 0; NOREF(cSCSIused);<br>+            uint16_t cNVMeused = 0; NOREF(cNVMeused);<br>             ovf::ControllersMap::const_iterator hdcIt;<br>             /* Iterate through all storage controllers */<br>             for (hdcIt = vsysThis.mapControllers.begin();<br>@@ -649,6 +650,28 @@<br>                                             strControllerID.c_str());<br>                         ++cSCSIused;<br>                     break;<br>+<br>+                    case ovf::HardDiskController::NVMe:<br>+                        /* Check for the constrains */<br>+                        if (cNVMeused < 1)<br>+                        {<br>+                            /* We only support a plain NVMe controller, so use them always */<br>+                            pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerNVMe,<br>+                                                 strControllerID,<br>+                                                 hdc.strControllerType,<br>+                                                 "NVMe");<br>+                        }<br>+                        else<br>+                        {<br>+                            /* Warn only once */<br>+                            if (cNVMeused == 1)<br>+                                i_addWarning(tr("The virtual system \"%s\" requests support for more than one "<br>+                                                "NVMe controller, but VirtualBox has support for only one"),<br>+                                                vsysThis.strName.c_str());<br>+<br>+                        }<br>+                        ++cNVMeused;<br>+                    break;<br>                 }<br>             }<br> <br>@@ -3516,6 +3539,14 @@<br>             break;<br>         }<br> <br>+        case ovf::HardDiskController::NVMe:<br>+        {<br>+            controllerName = "NVMe";<br>+            lControllerPort = (long)ulAddressOnParent;<br>+            lDevice = (long)0;<br>+            break;<br>+        }<br>+<br>         default: break;<br>     }<br> <br>@@ -4229,6 +4260,29 @@<br>         if (FAILED(rc)) throw rc;<br>     }<br> <br>+    /* Storage controller NVMe */<br>+    std::list<VirtualSystemDescriptionEntry*> vsdeHDCNVMe =<br>+        vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerNVMe);<br>+    if (vsdeHDCNVMe.size() > 1)<br>+        throw setError(VBOX_E_FILE_ERROR,<br>+                       tr("Too many NVMe controllers in OVF; import facility only supports one"));<br>+    if (!vsdeHDCNVMe.empty())<br>+    {<br>+        ComPtr<IStorageController> pController;<br>+        const Utf8Str &hdcVBox = vsdeHDCNVMe.front()->strVBoxCurrent;<br>+        if (hdcVBox == "NVMe")<br>+        {<br>+            rc = pNewMachine->AddStorageController(Bstr("NVMe").raw(),<br>+                                                   StorageBus_PCIe,<br>+                                                   pController.asOutParam());<br>+            if (FAILED(rc)) throw rc;<br>+        }<br>+        else<br>+            throw setError(VBOX_E_FILE_ERROR,<br>+                           tr("Invalid NVMe controller type \"%s\""),<br>+                           hdcVBox.c_str());<br>+    }<br>+<br>     /* Now its time to register the machine before we add any storage devices */<br>     rc = mVirtualBox->RegisterMachine(pNewMachine);<br>     if (FAILED(rc)) throw rc;<br>Index: src/VBox/Main/testcase/tstOVF.cpp<br>===================================================================<br>--- src/VBox/Main/testcase/tstOVF.cpp   (revision 82781)<br>+++ src/VBox/Main/testcase/tstOVF.cpp (working copy)<br>@@ -194,6 +194,10 @@<br>                     pcszType = "scsi";<br>                 break;<br> <br>+                case VirtualSystemDescriptionType_HardDiskControllerNVMe:<br>+                    pcszType = "nvme";<br>+                break;<br>+<br>                 case VirtualSystemDescriptionType_HardDiskImage:<br>                     pcszType = "hd";<br>                 break;<br>Index: src/VBox/Main/xml/ovfreader.cpp<br>===================================================================<br>--- src/VBox/Main/xml/ovfreader.cpp       (revision 82781)<br>+++ src/VBox/Main/xml/ovfreader.cpp   (working copy)<br>@@ -640,7 +640,7 @@<br>                         // handled separately in second loop below<br>                         break;<br> <br>-                    case ResourceType_OtherStorageDevice:        // 20       SATA controller<br>+                    case ResourceType_OtherStorageDevice:        // 20       SATA/NVMe controller<br>                     {<br>                         /* <Item><br>                             <rasd:Description>SATA Controller</rasd:Description><br>@@ -661,8 +661,18 @@<br> <br>                             vsys.mapControllers[i.ulInstanceID] = hdc;<br>                         }<br>+                        else if (   i.strResourceSubType.compare("nvme", RTCString::CaseInsensitive) == 0<br>+                            || i.strResourceSubType.compare("vmware.nvme.controller", RTCString::CaseInsensitive) == 0)<br>+                        {<br>+                            HardDiskController hdc;<br>+                            hdc.system = HardDiskController::NVMe;<br>+                            hdc.idController = i.ulInstanceID;<br>+                            hdc.strControllerType = i.strResourceSubType;<br>+<br>+                            vsys.mapControllers[i.ulInstanceID] = hdc;<br>+                        }<br>                         else<br>-                            throw OVFLogicError(N_("Error reading \"%s\": Host resource of type \"Other Storage Device (%d)\" is supported with SATA AHCI controllers only, line %d (subtype:%s)"),<br>+                            throw OVFLogicError(N_("Error reading \"%s\": Host resource of type \"Other Storage Device (%d)\" is supported with SATA AHCI and NVMe controllers only, line %d (subtype:%s)"),<br>                                                 m_strPath.c_str(),<br>                                                 ResourceType_OtherStorageDevice,<br>                                                 i.ulLineNumber, i.strResourceSubType.c_str() );<br></div><div><br></div><div><br></div><div>-- </div><div>Best wishes,</div><div>Dave Cardwell.</div></div>