From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- .../VBoxManage/VBoxManageStorageController.cpp | 1306 ++++++++++++++++++++ 1 file changed, 1306 insertions(+) create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageStorageController.cpp (limited to 'src/VBox/Frontends/VBoxManage/VBoxManageStorageController.cpp') diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageStorageController.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageStorageController.cpp new file mode 100644 index 00000000..395196ad --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageStorageController.cpp @@ -0,0 +1,1306 @@ +/* $Id: VBoxManageStorageController.cpp $ */ +/** @file + * VBoxManage - The storage controller related commands. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "VBoxManage.h" +using namespace com; + +DECLARE_TRANSLATION_CONTEXT(Storage); + +// funcs +/////////////////////////////////////////////////////////////////////////////// + + +static const RTGETOPTDEF g_aStorageAttachOptions[] = +{ + { "--storagectl", 's', RTGETOPT_REQ_STRING }, + { "--port", 'p', RTGETOPT_REQ_UINT32 }, + { "--device", 'd', RTGETOPT_REQ_UINT32 }, + { "--type", 't', RTGETOPT_REQ_STRING }, + { "--medium", 'm', RTGETOPT_REQ_STRING }, + { "--mtype", 'M', RTGETOPT_REQ_STRING }, + { "--passthrough", 'h', RTGETOPT_REQ_STRING }, + { "--tempeject", 'e', RTGETOPT_REQ_STRING }, + { "--nonrotational", 'n', RTGETOPT_REQ_STRING }, + { "--discard", 'u', RTGETOPT_REQ_STRING }, + { "--hotpluggable", 'o', RTGETOPT_REQ_STRING }, + { "--bandwidthgroup", 'b', RTGETOPT_REQ_STRING }, + { "--forceunmount", 'f', RTGETOPT_REQ_NOTHING }, + { "--comment", 'C', RTGETOPT_REQ_STRING }, + { "--setuuid", 'q', RTGETOPT_REQ_STRING }, + { "--setparentuuid", 'Q', RTGETOPT_REQ_STRING }, + // iSCSI options + { "--server", 'S', RTGETOPT_REQ_STRING }, + { "--target", 'T', RTGETOPT_REQ_STRING }, + { "--tport", 'P', RTGETOPT_REQ_STRING }, + { "--lun", 'L', RTGETOPT_REQ_STRING }, + { "--encodedlun", 'E', RTGETOPT_REQ_STRING }, + { "--username", 'U', RTGETOPT_REQ_STRING }, + { "--password", 'W', RTGETOPT_REQ_STRING }, + { "--passwordfile", 'w', RTGETOPT_REQ_STRING }, + { "--initiator", 'N', RTGETOPT_REQ_STRING }, + { "--intnet", 'I', RTGETOPT_REQ_NOTHING }, +}; + +RTEXITCODE handleStorageAttach(HandlerArg *a) +{ + int c = VERR_INTERNAL_ERROR; /* initialized to shut up gcc */ + HRESULT hrc = S_OK; + ULONG port = ~0U; + ULONG device = ~0U; + bool fForceUnmount = false; + bool fSetMediumType = false; + bool fSetNewUuid = false; + bool fSetNewParentUuid = false; + MediumType_T enmMediumType = MediumType_Normal; + Bstr bstrComment; + const char *pszCtl = NULL; + DeviceType_T devTypeRequested = DeviceType_Null; + const char *pszMedium = NULL; + const char *pszPassThrough = NULL; + const char *pszTempEject = NULL; + const char *pszNonRotational = NULL; + const char *pszDiscard = NULL; + const char *pszHotPluggable = NULL; + const char *pszBandwidthGroup = NULL; + Bstr bstrNewUuid; + Bstr bstrNewParentUuid; + // iSCSI options + Bstr bstrServer; + Bstr bstrTarget; + Bstr bstrPort; + Bstr bstrLun; + Bstr bstrUsername; + Bstr bstrPassword; + Bstr bstrInitiator; + Bstr bstrIso; + Utf8Str strIso; + bool fIntNet = false; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + ComPtr machine; + ComPtr storageCtl; + ComPtr systemProperties; + + RTGetOptInit(&GetState, a->argc, a->argv, g_aStorageAttachOptions, + RT_ELEMENTS(g_aStorageAttachOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + + while ( SUCCEEDED(hrc) + && (c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 's': // storage controller name + { + if (ValueUnion.psz) + pszCtl = ValueUnion.psz; + else + hrc = E_FAIL; + break; + } + + case 'p': // port + { + port = ValueUnion.u32; + break; + } + + case 'd': // device + { + device = ValueUnion.u32; + break; + } + + case 'm': // medium |iSCSI> + { + if (ValueUnion.psz) + pszMedium = ValueUnion.psz; + else + hrc = E_FAIL; + break; + } + + case 't': // type + { + if (ValueUnion.psz) + { + if (!RTStrICmp(ValueUnion.psz, "hdd")) + devTypeRequested = DeviceType_HardDisk; + else if (!RTStrICmp(ValueUnion.psz, "fdd")) + devTypeRequested = DeviceType_Floppy; + else if (!RTStrICmp(ValueUnion.psz, "dvddrive")) + devTypeRequested = DeviceType_DVD; + else + return errorArgument(Storage::tr("Invalid --type argument '%s'"), ValueUnion.psz); + } + else + hrc = E_FAIL; + break; + } + + case 'h': // passthrough + { + if (ValueUnion.psz) + pszPassThrough = ValueUnion.psz; + else + hrc = E_FAIL; + break; + } + + case 'e': // tempeject + { + if (ValueUnion.psz) + pszTempEject = ValueUnion.psz; + else + hrc = E_FAIL; + break; + } + + case 'n': // nonrotational + { + if (ValueUnion.psz) + pszNonRotational = ValueUnion.psz; + else + hrc = E_FAIL; + break; + } + + case 'u': // discard + { + if (ValueUnion.psz) + pszDiscard = ValueUnion.psz; + else + hrc = E_FAIL; + break; + } + + case 'o': // hotpluggable + { + if (ValueUnion.psz) + pszHotPluggable = ValueUnion.psz; + else + hrc = E_FAIL; + break; + } + + case 'b': // bandwidthgroup + { + if (ValueUnion.psz) + pszBandwidthGroup = ValueUnion.psz; + else + hrc = E_FAIL; + break; + } + + case 'f': // force unmount medium during runtime + { + fForceUnmount = true; + break; + } + + case 'C': + if (ValueUnion.psz) + bstrComment = ValueUnion.psz; + else + hrc = E_FAIL; + break; + + case 'q': + if (ValueUnion.psz) + { + bstrNewUuid = ValueUnion.psz; + fSetNewUuid = true; + } + else + hrc = E_FAIL; + break; + + case 'Q': + if (ValueUnion.psz) + { + bstrNewParentUuid = ValueUnion.psz; + fSetNewParentUuid = true; + } + else + hrc = E_FAIL; + break; + + case 'S': // --server + bstrServer = ValueUnion.psz; + break; + + case 'T': // --target + bstrTarget = ValueUnion.psz; + break; + + case 'P': // --tport + bstrPort = ValueUnion.psz; + break; + + case 'L': // --lun + bstrLun = ValueUnion.psz; + break; + + case 'E': // --encodedlun + bstrLun = BstrFmt("enc%s", ValueUnion.psz); + break; + + case 'U': // --username + bstrUsername = ValueUnion.psz; + break; + + case 'W': // --password + bstrPassword = ValueUnion.psz; + break; + + case 'w': // --passwordFile + { + Utf8Str utf8Password; + RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &utf8Password); + if (rcExit != RTEXITCODE_SUCCESS) + hrc = E_FAIL; + bstrPassword = utf8Password; + break; + } + case 'N': // --initiator + bstrInitiator = ValueUnion.psz; + break; + + case 'M': // --type + { + int vrc = parseMediumType(ValueUnion.psz, &enmMediumType); + if (RT_FAILURE(vrc)) + return errorArgument(Storage::tr("Invalid medium type '%s'"), ValueUnion.psz); + fSetMediumType = true; + break; + } + + case 'I': // --intnet + fIntNet = true; + break; + + default: + { + errorGetOpt(c, &ValueUnion); + hrc = E_FAIL; + break; + } + } + } + + if (FAILED(hrc)) + return RTEXITCODE_FAILURE; + + if (!pszCtl) + return errorSyntax(Storage::tr("Storage controller name not specified")); + + /* get the virtualbox system properties */ + CHECK_ERROR_RET(a->virtualBox, COMGETTER(SystemProperties)(systemProperties.asOutParam()), RTEXITCODE_FAILURE); + + // find the machine, lock it, get the mutable session machine + CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam()), RTEXITCODE_FAILURE); + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + SessionType_T st; + CHECK_ERROR_RET(a->session, COMGETTER(Type)(&st), RTEXITCODE_FAILURE); + a->session->COMGETTER(Machine)(machine.asOutParam()); + + try + { + bool fRunTime = (st == SessionType_Shared); + + if (fRunTime) + { + if (pszPassThrough) + throw Utf8Str(Storage::tr("Drive passthrough state cannot be changed while the VM is running\n")); + else if (pszBandwidthGroup) + throw Utf8Str(Storage::tr("Bandwidth group cannot be changed while the VM is running\n")); + } + + /* check if the storage controller is present */ + hrc = machine->GetStorageControllerByName(Bstr(pszCtl).raw(), + storageCtl.asOutParam()); + if (FAILED(hrc)) + throw Utf8StrFmt(Storage::tr("Could not find a controller named '%s'\n"), pszCtl); + + StorageBus_T storageBus = StorageBus_Null; + CHECK_ERROR_RET(storageCtl, COMGETTER(Bus)(&storageBus), RTEXITCODE_FAILURE); + ULONG maxPorts = 0; + CHECK_ERROR_RET(systemProperties, GetMaxPortCountForStorageBus(storageBus, &maxPorts), RTEXITCODE_FAILURE); + ULONG maxDevices = 0; + CHECK_ERROR_RET(systemProperties, GetMaxDevicesPerPortForStorageBus(storageBus, &maxDevices), RTEXITCODE_FAILURE); + + if (port == ~0U) + { + if (maxPorts == 1) + port = 0; + else + return errorSyntax(Storage::tr("Port not specified")); + } + if (device == ~0U) + { + if (maxDevices == 1) + device = 0; + else + return errorSyntax(Storage::tr("Device not specified")); + } + + /* for sata controller check if the port count is big enough + * to accommodate the current port which is being assigned + * else just increase the port count + */ + { + ULONG ulPortCount = 0; + ULONG ulMaxPortCount = 0; + + CHECK_ERROR(storageCtl, COMGETTER(MaxPortCount)(&ulMaxPortCount)); + CHECK_ERROR(storageCtl, COMGETTER(PortCount)(&ulPortCount)); + + if ( (ulPortCount != ulMaxPortCount) + && (port >= ulPortCount) + && (port < ulMaxPortCount)) + CHECK_ERROR(storageCtl, COMSETTER(PortCount)(port + 1)); + } + + StorageControllerType_T ctlType = StorageControllerType_Null; + CHECK_ERROR(storageCtl, COMGETTER(ControllerType)(&ctlType)); + + if (!RTStrICmp(pszMedium, "none")) + { + CHECK_ERROR(machine, DetachDevice(Bstr(pszCtl).raw(), port, device)); + } + else if (!RTStrICmp(pszMedium, "emptydrive")) + { + if (fRunTime) + { + ComPtr mediumAttachment; + DeviceType_T deviceType = DeviceType_Null; + hrc = machine->GetMediumAttachment(Bstr(pszCtl).raw(), port, device, + mediumAttachment.asOutParam()); + if (SUCCEEDED(hrc)) + { + mediumAttachment->COMGETTER(Type)(&deviceType); + + if ( (deviceType == DeviceType_DVD) + || (deviceType == DeviceType_Floppy)) + { + /* just unmount the floppy/dvd */ + CHECK_ERROR(machine, UnmountMedium(Bstr(pszCtl).raw(), + port, + device, + fForceUnmount)); + } + } + else if (devTypeRequested == DeviceType_DVD) + { + /* + * Try to attach an empty DVD drive as a hotplug operation. + * Main will complain if the controller doesn't support hotplugging. + */ + CHECK_ERROR(machine, AttachDeviceWithoutMedium(Bstr(pszCtl).raw(), port, device, + devTypeRequested)); + deviceType = DeviceType_DVD; /* To avoid the error message below. */ + } + + if ( FAILED(hrc) + || !( deviceType == DeviceType_DVD + || deviceType == DeviceType_Floppy) + ) + throw Utf8StrFmt(Storage::tr("No DVD/Floppy Drive attached to the controller '%s'" + "at the port: %u, device: %u"), pszCtl, port, device); + + } + else + { + DeviceType_T deviceType = DeviceType_Null; + com::SafeArray saDeviceTypes; + ULONG driveCheck = 0; + + /* check if the device type is supported by the controller */ + CHECK_ERROR(systemProperties, GetDeviceTypesForStorageBus(storageBus, ComSafeArrayAsOutParam(saDeviceTypes))); + for (size_t i = 0; i < saDeviceTypes.size(); ++ i) + { + if ( (saDeviceTypes[i] == DeviceType_DVD) + || (saDeviceTypes[i] == DeviceType_Floppy)) + driveCheck++; + } + + if (!driveCheck) + throw Utf8StrFmt(Storage::tr("The attachment is not supported by the storage controller '%s'"), pszCtl); + + if (storageBus == StorageBus_Floppy) + deviceType = DeviceType_Floppy; + else + deviceType = DeviceType_DVD; + + /* attach a empty floppy/dvd drive after removing previous attachment */ + machine->DetachDevice(Bstr(pszCtl).raw(), port, device); + CHECK_ERROR(machine, AttachDeviceWithoutMedium(Bstr(pszCtl).raw(), port, device, + deviceType)); + } + } // end if (!RTStrICmp(pszMedium, "emptydrive")) + else + { + ComPtr pMedium2Mount; + + // not "none", not "emptydrive": then it must be a UUID or filename or hostdrive or iSCSI; + // for all these we first need to know the type of drive we're attaching to + { + /* + * try to determine the type of the drive from the + * storage controller chipset, the attachment and + * the medium being attached + */ + if (ctlType == StorageControllerType_I82078) // floppy controller + devTypeRequested = DeviceType_Floppy; + else + { + /* + * for SATA/SCSI/IDE it is hard to tell if it is a harddisk or + * a dvd being attached so lets check if the medium attachment + * and the medium, both are of same type. if yes then we are + * sure of its type and don't need the user to enter it manually + * else ask the user for the type. + */ + ComPtr mediumAttachment; + hrc = machine->GetMediumAttachment(Bstr(pszCtl).raw(), port, + device, + mediumAttachment.asOutParam()); + if (SUCCEEDED(hrc)) + { + DeviceType_T deviceType; + mediumAttachment->COMGETTER(Type)(&deviceType); + + if (pszMedium) + { + if (!RTStrICmp(pszMedium, "additions")) + { + ComPtr pProperties; + CHECK_ERROR(a->virtualBox, + COMGETTER(SystemProperties)(pProperties.asOutParam())); + CHECK_ERROR(pProperties, COMGETTER(DefaultAdditionsISO)(bstrIso.asOutParam())); + strIso = Utf8Str(bstrIso); + if (strIso.isEmpty()) + throw Utf8Str(Storage::tr("Cannot find the Guest Additions ISO image\n")); + pszMedium = strIso.c_str(); + if (devTypeRequested == DeviceType_Null) + devTypeRequested = DeviceType_DVD; + } + ComPtr pExistingMedium; + hrc = openMedium(a, pszMedium, deviceType, + AccessMode_ReadWrite, + pExistingMedium, + false /* fForceNewUuidOnOpen */, + true /* fSilent */); + if (SUCCEEDED(hrc) && pExistingMedium) + { + if ( (deviceType == DeviceType_DVD) + || (deviceType == DeviceType_HardDisk) + ) + devTypeRequested = deviceType; + } + } + else + devTypeRequested = deviceType; + } + } + } + + if (devTypeRequested == DeviceType_Null) // still the initializer value? + throw Utf8Str(Storage::tr("Argument --type must be specified\n")); + + /* check if the device type is supported by the controller */ + { + com::SafeArray saDeviceTypes; + + CHECK_ERROR(systemProperties, GetDeviceTypesForStorageBus(storageBus, ComSafeArrayAsOutParam(saDeviceTypes))); + if (SUCCEEDED(hrc)) + { + ULONG driveCheck = 0; + for (size_t i = 0; i < saDeviceTypes.size(); ++ i) + if (saDeviceTypes[i] == devTypeRequested) + driveCheck++; + if (!driveCheck) + throw Utf8StrFmt(Storage::tr("The given attachment is not supported by the storage controller '%s'"), pszCtl); + } + else + goto leave; + } + + // find the medium given + /* host drive? */ + if (!RTStrNICmp(pszMedium, RT_STR_TUPLE("host:"))) + { + ComPtr host; + CHECK_ERROR(a->virtualBox, COMGETTER(Host)(host.asOutParam())); + + if (devTypeRequested == DeviceType_DVD) + { + hrc = host->FindHostDVDDrive(Bstr(pszMedium + 5).raw(), + pMedium2Mount.asOutParam()); + if (!pMedium2Mount) + { + /* 2nd try: try with the real name, important on Linux+libhal */ + char szPathReal[RTPATH_MAX]; + if (RT_FAILURE(RTPathReal(pszMedium + 5, szPathReal, sizeof(szPathReal)))) + throw Utf8StrFmt(Storage::tr("Invalid host DVD drive name \"%s\""), pszMedium + 5); + hrc = host->FindHostDVDDrive(Bstr(szPathReal).raw(), + pMedium2Mount.asOutParam()); + if (!pMedium2Mount) + throw Utf8StrFmt(Storage::tr("Invalid host DVD drive name \"%s\""), pszMedium + 5); + } + } + else + { + // floppy + hrc = host->FindHostFloppyDrive(Bstr(pszMedium + 5).raw(), + pMedium2Mount.asOutParam()); + if (!pMedium2Mount) + throw Utf8StrFmt(Storage::tr("Invalid host floppy drive name \"%s\""), pszMedium + 5); + } + } + else if (!RTStrICmp(pszMedium, "iSCSI")) + { + /* check for required options */ + if (bstrServer.isEmpty() || bstrTarget.isEmpty()) + throw Utf8StrFmt(Storage::tr("Parameters --server and --target are required for iSCSI media")); + + /** @todo move the location stuff to Main, which can use pfnComposeName + * from the disk backends to construct the location properly. Also do + * not use slashes to separate the parts, as otherwise only the last + * element containing information will be shown. */ + Bstr bstrISCSIMedium; + if ( bstrLun.isEmpty() + || (bstrLun == "0") + || (bstrLun == "enc0") + ) + bstrISCSIMedium = BstrFmt("%ls|%ls", bstrServer.raw(), bstrTarget.raw()); + else + bstrISCSIMedium = BstrFmt("%ls|%ls|%ls", bstrServer.raw(), bstrTarget.raw(), bstrLun.raw()); + + CHECK_ERROR(a->virtualBox, CreateMedium(Bstr("iSCSI").raw(), + bstrISCSIMedium.raw(), + AccessMode_ReadWrite, + DeviceType_HardDisk, + pMedium2Mount.asOutParam())); + if (FAILED(hrc)) goto leave; /** @todo r=andy Argh!! Why not using exceptions here? */ + if (!bstrPort.isEmpty()) + bstrServer = BstrFmt("%ls:%ls", bstrServer.raw(), bstrPort.raw()); + + // set the other iSCSI parameters as properties + com::SafeArray names; + com::SafeArray values; + Bstr("TargetAddress").detachTo(names.appendedRaw()); + bstrServer.detachTo(values.appendedRaw()); + Bstr("TargetName").detachTo(names.appendedRaw()); + bstrTarget.detachTo(values.appendedRaw()); + + if (!bstrLun.isEmpty()) + { + Bstr("LUN").detachTo(names.appendedRaw()); + bstrLun.detachTo(values.appendedRaw()); + } + if (!bstrUsername.isEmpty()) + { + Bstr("InitiatorUsername").detachTo(names.appendedRaw()); + bstrUsername.detachTo(values.appendedRaw()); + } + if (!bstrPassword.isEmpty()) + { + Bstr("InitiatorSecret").detachTo(names.appendedRaw()); + bstrPassword.detachTo(values.appendedRaw()); + } + if (!bstrInitiator.isEmpty()) + { + Bstr("InitiatorName").detachTo(names.appendedRaw()); + bstrInitiator.detachTo(values.appendedRaw()); + } + + /// @todo add --targetName and --targetPassword options + + if (fIntNet) + { + Bstr("HostIPStack").detachTo(names.appendedRaw()); + Bstr("0").detachTo(values.appendedRaw()); + } + + CHECK_ERROR(pMedium2Mount, SetProperties(ComSafeArrayAsInParam(names), + ComSafeArrayAsInParam(values))); + if (FAILED(hrc)) goto leave; + Bstr guid; + CHECK_ERROR(pMedium2Mount, COMGETTER(Id)(guid.asOutParam())); + if (FAILED(hrc)) goto leave; + RTPrintf(Storage::tr("iSCSI disk created. UUID: %s\n"), Utf8Str(guid).c_str()); + } + else + { + if (!pszMedium) + { + ComPtr mediumAttachment; + hrc = machine->GetMediumAttachment(Bstr(pszCtl).raw(), port, + device, + mediumAttachment.asOutParam()); + if (FAILED(hrc)) + throw Utf8Str(Storage::tr("Missing --medium argument")); + } + else + { + Bstr bstrMedium(pszMedium); + hrc = openMedium(a, pszMedium, devTypeRequested, + AccessMode_ReadWrite, pMedium2Mount, + fSetNewUuid, false /* fSilent */); + if (FAILED(hrc) || !pMedium2Mount) + throw Utf8StrFmt(Storage::tr("Invalid UUID or filename \"%s\""), pszMedium); + } + } + + // set medium/parent medium UUID, if so desired + if (pMedium2Mount && (fSetNewUuid || fSetNewParentUuid)) + { + CHECK_ERROR(pMedium2Mount, SetIds(fSetNewUuid, bstrNewUuid.raw(), + fSetNewParentUuid, bstrNewParentUuid.raw())); + if (FAILED(hrc)) + throw Utf8Str(Storage::tr("Failed to set the medium/parent medium UUID")); + } + + // set medium type, if so desired + if (pMedium2Mount && fSetMediumType) + { + MediumType_T enmMediumTypeOld; + CHECK_ERROR(pMedium2Mount, COMGETTER(Type)(&enmMediumTypeOld)); + if (SUCCEEDED(hrc)) + { + if (enmMediumTypeOld != enmMediumType) + { + CHECK_ERROR(pMedium2Mount, COMSETTER(Type)(enmMediumType)); + if (FAILED(hrc)) + throw Utf8Str(Storage::tr("Failed to set the medium type")); + } + } + } + + if (pMedium2Mount && !bstrComment.isEmpty()) + { + CHECK_ERROR(pMedium2Mount, COMSETTER(Description)(bstrComment.raw())); + } + + if (pszMedium) + { + switch (devTypeRequested) + { + case DeviceType_DVD: + case DeviceType_Floppy: + { + if (!fRunTime) + { + ComPtr mediumAttachment; + // check if there is a dvd/floppy drive at the given location, if not attach one first + hrc = machine->GetMediumAttachment(Bstr(pszCtl).raw(), + port, + device, + mediumAttachment.asOutParam()); + if (SUCCEEDED(hrc)) + { + DeviceType_T deviceType; + mediumAttachment->COMGETTER(Type)(&deviceType); + if (deviceType != devTypeRequested) + { + machine->DetachDevice(Bstr(pszCtl).raw(), port, device); + hrc = machine->AttachDeviceWithoutMedium(Bstr(pszCtl).raw(), + port, + device, + devTypeRequested); // DeviceType_DVD or DeviceType_Floppy + } + } + else + { + hrc = machine->AttachDeviceWithoutMedium(Bstr(pszCtl).raw(), + port, + device, + devTypeRequested); // DeviceType_DVD or DeviceType_Floppy + } + } + + if (pMedium2Mount) + { + CHECK_ERROR(machine, MountMedium(Bstr(pszCtl).raw(), + port, + device, + pMedium2Mount, + fForceUnmount)); + } + break; + } // end DeviceType_DVD or DeviceType_Floppy: + + case DeviceType_HardDisk: + { + // if there is anything attached at the given location, remove it + machine->DetachDevice(Bstr(pszCtl).raw(), port, device); + CHECK_ERROR(machine, AttachDevice(Bstr(pszCtl).raw(), + port, + device, + DeviceType_HardDisk, + pMedium2Mount)); + break; + } + + default: break; /* Shut up MSC */ + } + } + } + + if ( pszPassThrough + && (SUCCEEDED(hrc))) + { + ComPtr mattach; + CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port, + device, mattach.asOutParam())); + + if (SUCCEEDED(hrc)) + { + if (!RTStrICmp(pszPassThrough, "on")) + { + CHECK_ERROR(machine, PassthroughDevice(Bstr(pszCtl).raw(), + port, device, TRUE)); + } + else if (!RTStrICmp(pszPassThrough, "off")) + { + CHECK_ERROR(machine, PassthroughDevice(Bstr(pszCtl).raw(), + port, device, FALSE)); + } + else + throw Utf8StrFmt(Storage::tr("Invalid --passthrough argument '%s'"), pszPassThrough); + } + else + throw Utf8StrFmt(Storage::tr("Couldn't find the controller attachment for the controller '%s'\n"), pszCtl); + } + + if ( pszTempEject + && (SUCCEEDED(hrc))) + { + ComPtr mattach; + CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port, + device, mattach.asOutParam())); + + if (SUCCEEDED(hrc)) + { + if (!RTStrICmp(pszTempEject, "on")) + { + CHECK_ERROR(machine, TemporaryEjectDevice(Bstr(pszCtl).raw(), + port, device, TRUE)); + } + else if (!RTStrICmp(pszTempEject, "off")) + { + CHECK_ERROR(machine, TemporaryEjectDevice(Bstr(pszCtl).raw(), + port, device, FALSE)); + } + else + throw Utf8StrFmt(Storage::tr("Invalid --tempeject argument '%s'"), pszTempEject); + } + else + throw Utf8StrFmt(Storage::tr("Couldn't find the controller attachment for the controller '%s'\n"), pszCtl); + } + + if ( pszNonRotational + && (SUCCEEDED(hrc))) + { + ComPtr mattach; + CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port, + device, mattach.asOutParam())); + + if (SUCCEEDED(hrc)) + { + if (!RTStrICmp(pszNonRotational, "on")) + { + CHECK_ERROR(machine, NonRotationalDevice(Bstr(pszCtl).raw(), + port, device, TRUE)); + } + else if (!RTStrICmp(pszNonRotational, "off")) + { + CHECK_ERROR(machine, NonRotationalDevice(Bstr(pszCtl).raw(), + port, device, FALSE)); + } + else + throw Utf8StrFmt(Storage::tr("Invalid --nonrotational argument '%s'"), pszNonRotational); + } + else + throw Utf8StrFmt(Storage::tr("Couldn't find the controller attachment for the controller '%s'\n"), pszCtl); + } + + if ( pszDiscard + && (SUCCEEDED(hrc))) + { + ComPtr mattach; + CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port, + device, mattach.asOutParam())); + + if (SUCCEEDED(hrc)) + { + if (!RTStrICmp(pszDiscard, "on")) + { + CHECK_ERROR(machine, SetAutoDiscardForDevice(Bstr(pszCtl).raw(), + port, device, TRUE)); + } + else if (!RTStrICmp(pszDiscard, "off")) + { + CHECK_ERROR(machine, SetAutoDiscardForDevice(Bstr(pszCtl).raw(), + port, device, FALSE)); + } + else + throw Utf8StrFmt(Storage::tr("Invalid --discard argument '%s'"), pszDiscard); + } + else + throw Utf8StrFmt(Storage::tr("Couldn't find the controller attachment for the controller '%s'\n"), pszCtl); + } + + if ( pszHotPluggable + && (SUCCEEDED(hrc))) + { + ComPtr mattach; + CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port, + device, mattach.asOutParam())); + + if (SUCCEEDED(hrc)) + { + if (!RTStrICmp(pszHotPluggable, "on")) + { + CHECK_ERROR(machine, SetHotPluggableForDevice(Bstr(pszCtl).raw(), + port, device, TRUE)); + } + else if (!RTStrICmp(pszHotPluggable, "off")) + { + CHECK_ERROR(machine, SetHotPluggableForDevice(Bstr(pszCtl).raw(), + port, device, FALSE)); + } + else + throw Utf8StrFmt(Storage::tr("Invalid --hotpluggable argument '%s'"), pszHotPluggable); + } + else + throw Utf8StrFmt(Storage::tr("Couldn't find the controller attachment for the controller '%s'\n"), pszCtl); + } + + if ( pszBandwidthGroup + && !fRunTime + && SUCCEEDED(hrc)) + { + + if (!RTStrICmp(pszBandwidthGroup, "none")) + { + /* Just remove the bandwidth gorup. */ + CHECK_ERROR(machine, SetNoBandwidthGroupForDevice(Bstr(pszCtl).raw(), + port, device)); + } + else + { + ComPtr bwCtrl; + ComPtr bwGroup; + + CHECK_ERROR(machine, COMGETTER(BandwidthControl)(bwCtrl.asOutParam())); + + if (SUCCEEDED(hrc)) + { + CHECK_ERROR(bwCtrl, GetBandwidthGroup(Bstr(pszBandwidthGroup).raw(), bwGroup.asOutParam())); + if (SUCCEEDED(hrc)) + { + CHECK_ERROR(machine, SetBandwidthGroupForDevice(Bstr(pszCtl).raw(), + port, device, bwGroup)); + } + } + } + } + + /* commit changes */ + if (SUCCEEDED(hrc)) + CHECK_ERROR(machine, SaveSettings()); + } + catch (const Utf8Str &strError) + { + errorArgument("%s", strError.c_str()); + hrc = E_FAIL; + } + + // machine must always be unlocked, even on errors +leave: + a->session->UnlockMachine(); + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +static const RTGETOPTDEF g_aStorageControllerOptions[] = +{ + { "--name", 'n', RTGETOPT_REQ_STRING }, + { "--add", 'a', RTGETOPT_REQ_STRING }, + { "--controller", 'c', RTGETOPT_REQ_STRING }, + { "--portcount", 'p', RTGETOPT_REQ_UINT32 }, + { "--remove", 'r', RTGETOPT_REQ_NOTHING }, + { "--rename", 'R', RTGETOPT_REQ_STRING }, + { "--hostiocache", 'i', RTGETOPT_REQ_STRING }, + { "--bootable", 'b', RTGETOPT_REQ_STRING }, +}; + +RTEXITCODE handleStorageController(HandlerArg *a) +{ + int c; + const char *pszCtl = NULL; + const char *pszBusType = NULL; + const char *pszCtlType = NULL; + const char *pszHostIOCache = NULL; + const char *pszBootable = NULL; + const char *pszCtlNewName = NULL; + ULONG portcount = ~0U; + bool fRemoveCtl = false; + ComPtr machine; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + + if (a->argc < 4) + return errorSyntax(Storage::tr("Too few parameters")); + + RTGetOptInit (&GetState, a->argc, a->argv, g_aStorageControllerOptions, + RT_ELEMENTS(g_aStorageControllerOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + + while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (c) + { + case 'n': // controller name + Assert(ValueUnion.psz); + pszCtl = ValueUnion.psz; + break; + + case 'a': // controller bus type + Assert(ValueUnion.psz); + pszBusType = ValueUnion.psz; + break; + + case 'c': // controller + Assert(ValueUnion.psz); + pszCtlType = ValueUnion.psz; + break; + + case 'p': // portcount + portcount = ValueUnion.u32; + break; + + case 'r': // remove controller + fRemoveCtl = true; + break; + + case 'R': // rename controller + Assert(ValueUnion.psz); + pszCtlNewName = ValueUnion.psz; + break; + + case 'i': + pszHostIOCache = ValueUnion.psz; + break; + + case 'b': + pszBootable = ValueUnion.psz; + break; + + default: + return errorGetOpt(c, &ValueUnion); + } + } + + HRESULT hrc; + + /* try to find the given machine */ + CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(), + machine.asOutParam()), RTEXITCODE_FAILURE); + + /* open a session for the VM */ + CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE); + + /* get the mutable session machine */ + a->session->COMGETTER(Machine)(machine.asOutParam()); + + if (!pszCtl) + { + /* it's important to always close sessions */ + a->session->UnlockMachine(); + return errorSyntax(Storage::tr("Storage controller name not specified\n")); + } + + if (fRemoveCtl) + { + CHECK_ERROR(machine, RemoveStorageController(Bstr(pszCtl).raw())); + } + else + { + if (pszBusType) + { + ComPtr ctl; + + if (!RTStrICmp(pszBusType, "ide")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_IDE, + ctl.asOutParam())); + } + else if (!RTStrICmp(pszBusType, "sata")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_SATA, + ctl.asOutParam())); + } + else if (!RTStrICmp(pszBusType, "scsi")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_SCSI, + ctl.asOutParam())); + } + else if (!RTStrICmp(pszBusType, "floppy")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_Floppy, + ctl.asOutParam())); + } + else if (!RTStrICmp(pszBusType, "sas")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_SAS, + ctl.asOutParam())); + } + else if (!RTStrICmp(pszBusType, "usb")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_USB, + ctl.asOutParam())); + } + else if (!RTStrICmp(pszBusType, "pcie")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_PCIe, + ctl.asOutParam())); + } + else if (!RTStrICmp(pszBusType, "virtio-scsi") || !RTStrICmp(pszBusType, "virtio")) + { + CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(), + StorageBus_VirtioSCSI, + ctl.asOutParam())); + } + else + { + errorArgument(Storage::tr("Invalid --add argument '%s'"), pszBusType); + hrc = E_FAIL; + } + } + + if ( pszCtlType + && SUCCEEDED(hrc)) + { + ComPtr ctl; + + CHECK_ERROR(machine, GetStorageControllerByName(Bstr(pszCtl).raw(), + ctl.asOutParam())); + + if (SUCCEEDED(hrc)) + { + if (!RTStrICmp(pszCtlType, "lsilogic")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_LsiLogic)); + } + else if (!RTStrICmp(pszCtlType, "buslogic")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_BusLogic)); + } + else if (!RTStrICmp(pszCtlType, "intelahci")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_IntelAhci)); + } + else if (!RTStrICmp(pszCtlType, "piix3")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_PIIX3)); + } + else if (!RTStrICmp(pszCtlType, "piix4")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_PIIX4)); + } + else if (!RTStrICmp(pszCtlType, "ich6")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_ICH6)); + } + else if (!RTStrICmp(pszCtlType, "i82078")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_I82078)); + } + else if (!RTStrICmp(pszCtlType, "lsilogicsas")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_LsiLogicSas)); + } + else if (!RTStrICmp(pszCtlType, "usb")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_USB)); + } + else if (!RTStrICmp(pszCtlType, "nvme")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_NVMe)); + } + else if (!RTStrICmp(pszCtlType, "virtio-scsi") || !RTStrICmp(pszCtlType, "virtio")) + { + CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_VirtioSCSI)); + } + else + { + errorArgument(Storage::tr("Invalid --type argument '%s'"), pszCtlType); + hrc = E_FAIL; + } + } + else + { + errorArgument(Storage::tr("Couldn't find the controller with the name: '%s'\n"), pszCtl); + hrc = E_FAIL; + } + } + + if ( (portcount != ~0U) + && SUCCEEDED(hrc)) + { + ComPtr ctl; + + CHECK_ERROR(machine, GetStorageControllerByName(Bstr(pszCtl).raw(), + ctl.asOutParam())); + + if (SUCCEEDED(hrc)) + { + CHECK_ERROR(ctl, COMSETTER(PortCount)(portcount)); + } + else + { + errorArgument(Storage::tr("Couldn't find the controller with the name: '%s'\n"), pszCtl); + hrc = E_FAIL; /** @todo r=andy Overwrites original hrc. */ + } + } + + if ( pszHostIOCache + && SUCCEEDED(hrc)) + { + ComPtr ctl; + + CHECK_ERROR(machine, GetStorageControllerByName(Bstr(pszCtl).raw(), + ctl.asOutParam())); + + if (SUCCEEDED(hrc)) + { + if (!RTStrICmp(pszHostIOCache, "on")) + { + CHECK_ERROR(ctl, COMSETTER(UseHostIOCache)(TRUE)); + } + else if (!RTStrICmp(pszHostIOCache, "off")) + { + CHECK_ERROR(ctl, COMSETTER(UseHostIOCache)(FALSE)); + } + else + { + errorArgument(Storage::tr("Invalid --hostiocache argument '%s'"), pszHostIOCache); + hrc = E_FAIL; + } + } + else + { + errorArgument(Storage::tr("Couldn't find the controller with the name: '%s'\n"), pszCtl); + hrc = E_FAIL; /** @todo r=andy Ditto. */ + } + } + + if ( pszBootable + && SUCCEEDED(hrc)) + { + if (SUCCEEDED(hrc)) + { + if (!RTStrICmp(pszBootable, "on")) + { + CHECK_ERROR(machine, SetStorageControllerBootable(Bstr(pszCtl).raw(), TRUE)); + } + else if (!RTStrICmp(pszBootable, "off")) + { + CHECK_ERROR(machine, SetStorageControllerBootable(Bstr(pszCtl).raw(), FALSE)); + } + else + { + errorArgument(Storage::tr("Invalid --bootable argument '%s'"), pszBootable); + hrc = E_FAIL; + } + } + else + { + errorArgument(Storage::tr("Couldn't find the controller with the name: '%s'\n"), pszCtl); + hrc = E_FAIL; + } + } + + if ( pszCtlNewName + && SUCCEEDED(hrc)) + { + ComPtr ctl; + + CHECK_ERROR(machine, GetStorageControllerByName(Bstr(pszCtl).raw(), + ctl.asOutParam())); + + if (SUCCEEDED(hrc)) + { + CHECK_ERROR(ctl, COMSETTER(Name)(Bstr(pszCtlNewName).raw())); + } + else + { + errorArgument(Storage::tr("Couldn't find the controller with the name: '%s'\n"), pszCtl); + hrc = E_FAIL; + } + } + + } + + /* commit changes */ + if (SUCCEEDED(hrc)) + CHECK_ERROR(machine, SaveSettings()); + + /* it's important to always close sessions */ + a->session->UnlockMachine(); + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} -- cgit v1.2.3