VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxManage/VBoxManageStorageController.cpp@ 92710

Last change on this file since 92710 was 92372, checked in by vboxsync, 3 years ago

Main: bugref:1909: Added translation marks around messages of VBoxManage output

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 51.0 KB
Line 
1/* $Id: VBoxManageStorageController.cpp 92372 2021-11-11 14:45:18Z vboxsync $ */
2/** @file
3 * VBoxManage - The storage controller related commands.
4 */
5
6/*
7 * Copyright (C) 2006-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#ifndef VBOX_ONLY_DOCS
19
20
21/*********************************************************************************************************************************
22* Header Files *
23*********************************************************************************************************************************/
24#include <VBox/com/com.h>
25#include <VBox/com/array.h>
26#include <VBox/com/ErrorInfo.h>
27#include <VBox/com/errorprint.h>
28#include <VBox/com/VirtualBox.h>
29
30#include <iprt/path.h>
31#include <iprt/param.h>
32#include <iprt/string.h>
33#include <iprt/ctype.h>
34#include <iprt/stream.h>
35#include <iprt/getopt.h>
36#include <VBox/log.h>
37
38#include "VBoxManage.h"
39using namespace com;
40
41DECLARE_TRANSLATION_CONTEXT(Storage);
42
43// funcs
44///////////////////////////////////////////////////////////////////////////////
45
46
47static const RTGETOPTDEF g_aStorageAttachOptions[] =
48{
49 { "--storagectl", 's', RTGETOPT_REQ_STRING },
50 { "--port", 'p', RTGETOPT_REQ_UINT32 },
51 { "--device", 'd', RTGETOPT_REQ_UINT32 },
52 { "--type", 't', RTGETOPT_REQ_STRING },
53 { "--medium", 'm', RTGETOPT_REQ_STRING },
54 { "--mtype", 'M', RTGETOPT_REQ_STRING },
55 { "--passthrough", 'h', RTGETOPT_REQ_STRING },
56 { "--tempeject", 'e', RTGETOPT_REQ_STRING },
57 { "--nonrotational", 'n', RTGETOPT_REQ_STRING },
58 { "--discard", 'u', RTGETOPT_REQ_STRING },
59 { "--hotpluggable", 'o', RTGETOPT_REQ_STRING },
60 { "--bandwidthgroup", 'b', RTGETOPT_REQ_STRING },
61 { "--forceunmount", 'f', RTGETOPT_REQ_NOTHING },
62 { "--comment", 'C', RTGETOPT_REQ_STRING },
63 { "--setuuid", 'q', RTGETOPT_REQ_STRING },
64 { "--setparentuuid", 'Q', RTGETOPT_REQ_STRING },
65 // iSCSI options
66 { "--server", 'S', RTGETOPT_REQ_STRING },
67 { "--target", 'T', RTGETOPT_REQ_STRING },
68 { "--tport", 'P', RTGETOPT_REQ_STRING },
69 { "--lun", 'L', RTGETOPT_REQ_STRING },
70 { "--encodedlun", 'E', RTGETOPT_REQ_STRING },
71 { "--username", 'U', RTGETOPT_REQ_STRING },
72 { "--password", 'W', RTGETOPT_REQ_STRING },
73 { "--passwordfile", 'w', RTGETOPT_REQ_STRING },
74 { "--initiator", 'N', RTGETOPT_REQ_STRING },
75 { "--intnet", 'I', RTGETOPT_REQ_NOTHING },
76};
77
78RTEXITCODE handleStorageAttach(HandlerArg *a)
79{
80 int c = VERR_INTERNAL_ERROR; /* initialized to shut up gcc */
81 HRESULT rc = S_OK;
82 ULONG port = ~0U;
83 ULONG device = ~0U;
84 bool fForceUnmount = false;
85 bool fSetMediumType = false;
86 bool fSetNewUuid = false;
87 bool fSetNewParentUuid = false;
88 MediumType_T enmMediumType = MediumType_Normal;
89 Bstr bstrComment;
90 const char *pszCtl = NULL;
91 DeviceType_T devTypeRequested = DeviceType_Null;
92 const char *pszMedium = NULL;
93 const char *pszPassThrough = NULL;
94 const char *pszTempEject = NULL;
95 const char *pszNonRotational = NULL;
96 const char *pszDiscard = NULL;
97 const char *pszHotPluggable = NULL;
98 const char *pszBandwidthGroup = NULL;
99 Bstr bstrNewUuid;
100 Bstr bstrNewParentUuid;
101 // iSCSI options
102 Bstr bstrServer;
103 Bstr bstrTarget;
104 Bstr bstrPort;
105 Bstr bstrLun;
106 Bstr bstrUsername;
107 Bstr bstrPassword;
108 Bstr bstrInitiator;
109 Bstr bstrIso;
110 Utf8Str strIso;
111 bool fIntNet = false;
112
113 RTGETOPTUNION ValueUnion;
114 RTGETOPTSTATE GetState;
115 ComPtr<IMachine> machine;
116 ComPtr<IStorageController> storageCtl;
117 ComPtr<ISystemProperties> systemProperties;
118
119 RTGetOptInit(&GetState, a->argc, a->argv, g_aStorageAttachOptions,
120 RT_ELEMENTS(g_aStorageAttachOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
121
122 while ( SUCCEEDED(rc)
123 && (c = RTGetOpt(&GetState, &ValueUnion)))
124 {
125 switch (c)
126 {
127 case 's': // storage controller name
128 {
129 if (ValueUnion.psz)
130 pszCtl = ValueUnion.psz;
131 else
132 rc = E_FAIL;
133 break;
134 }
135
136 case 'p': // port
137 {
138 port = ValueUnion.u32;
139 break;
140 }
141
142 case 'd': // device
143 {
144 device = ValueUnion.u32;
145 break;
146 }
147
148 case 'm': // medium <none|emptydrive|additions|uuid|filename|host:<drive>|iSCSI>
149 {
150 if (ValueUnion.psz)
151 pszMedium = ValueUnion.psz;
152 else
153 rc = E_FAIL;
154 break;
155 }
156
157 case 't': // type <dvddrive|hdd|fdd>
158 {
159 if (ValueUnion.psz)
160 {
161 if (!RTStrICmp(ValueUnion.psz, "hdd"))
162 devTypeRequested = DeviceType_HardDisk;
163 else if (!RTStrICmp(ValueUnion.psz, "fdd"))
164 devTypeRequested = DeviceType_Floppy;
165 else if (!RTStrICmp(ValueUnion.psz, "dvddrive"))
166 devTypeRequested = DeviceType_DVD;
167 else
168 return errorArgument(Storage::tr("Invalid --type argument '%s'"), ValueUnion.psz);
169 }
170 else
171 rc = E_FAIL;
172 break;
173 }
174
175 case 'h': // passthrough <on|off>
176 {
177 if (ValueUnion.psz)
178 pszPassThrough = ValueUnion.psz;
179 else
180 rc = E_FAIL;
181 break;
182 }
183
184 case 'e': // tempeject <on|off>
185 {
186 if (ValueUnion.psz)
187 pszTempEject = ValueUnion.psz;
188 else
189 rc = E_FAIL;
190 break;
191 }
192
193 case 'n': // nonrotational <on|off>
194 {
195 if (ValueUnion.psz)
196 pszNonRotational = ValueUnion.psz;
197 else
198 rc = E_FAIL;
199 break;
200 }
201
202 case 'u': // discard <on|off>
203 {
204 if (ValueUnion.psz)
205 pszDiscard = ValueUnion.psz;
206 else
207 rc = E_FAIL;
208 break;
209 }
210
211 case 'o': // hotpluggable <on|off>
212 {
213 if (ValueUnion.psz)
214 pszHotPluggable = ValueUnion.psz;
215 else
216 rc = E_FAIL;
217 break;
218 }
219
220 case 'b': // bandwidthgroup <name>
221 {
222 if (ValueUnion.psz)
223 pszBandwidthGroup = ValueUnion.psz;
224 else
225 rc = E_FAIL;
226 break;
227 }
228
229 case 'f': // force unmount medium during runtime
230 {
231 fForceUnmount = true;
232 break;
233 }
234
235 case 'C':
236 if (ValueUnion.psz)
237 bstrComment = ValueUnion.psz;
238 else
239 rc = E_FAIL;
240 break;
241
242 case 'q':
243 if (ValueUnion.psz)
244 {
245 bstrNewUuid = ValueUnion.psz;
246 fSetNewUuid = true;
247 }
248 else
249 rc = E_FAIL;
250 break;
251
252 case 'Q':
253 if (ValueUnion.psz)
254 {
255 bstrNewParentUuid = ValueUnion.psz;
256 fSetNewParentUuid = true;
257 }
258 else
259 rc = E_FAIL;
260 break;
261
262 case 'S': // --server
263 bstrServer = ValueUnion.psz;
264 break;
265
266 case 'T': // --target
267 bstrTarget = ValueUnion.psz;
268 break;
269
270 case 'P': // --tport
271 bstrPort = ValueUnion.psz;
272 break;
273
274 case 'L': // --lun
275 bstrLun = ValueUnion.psz;
276 break;
277
278 case 'E': // --encodedlun
279 bstrLun = BstrFmt("enc%s", ValueUnion.psz);
280 break;
281
282 case 'U': // --username
283 bstrUsername = ValueUnion.psz;
284 break;
285
286 case 'W': // --password
287 bstrPassword = ValueUnion.psz;
288 break;
289
290 case 'w': // --passwordFile
291 {
292 Utf8Str utf8Password;
293 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &utf8Password);
294 if (rcExit != RTEXITCODE_SUCCESS)
295 rc = E_FAIL;
296 bstrPassword = utf8Password;
297 break;
298 }
299 case 'N': // --initiator
300 bstrInitiator = ValueUnion.psz;
301 break;
302
303 case 'M': // --type
304 {
305 int vrc = parseMediumType(ValueUnion.psz, &enmMediumType);
306 if (RT_FAILURE(vrc))
307 return errorArgument(Storage::tr("Invalid medium type '%s'"), ValueUnion.psz);
308 fSetMediumType = true;
309 break;
310 }
311
312 case 'I': // --intnet
313 fIntNet = true;
314 break;
315
316 default:
317 {
318 errorGetOpt(USAGE_STORAGEATTACH, c, &ValueUnion);
319 rc = E_FAIL;
320 break;
321 }
322 }
323 }
324
325 if (FAILED(rc))
326 return RTEXITCODE_FAILURE;
327
328 if (!pszCtl)
329 return errorSyntax(USAGE_STORAGEATTACH, Storage::tr("Storage controller name not specified"));
330
331 /* get the virtualbox system properties */
332 CHECK_ERROR_RET(a->virtualBox, COMGETTER(SystemProperties)(systemProperties.asOutParam()), RTEXITCODE_FAILURE);
333
334 // find the machine, lock it, get the mutable session machine
335 CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
336 machine.asOutParam()), RTEXITCODE_FAILURE);
337 CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
338 SessionType_T st;
339 CHECK_ERROR_RET(a->session, COMGETTER(Type)(&st), RTEXITCODE_FAILURE);
340 a->session->COMGETTER(Machine)(machine.asOutParam());
341
342 try
343 {
344 bool fRunTime = (st == SessionType_Shared);
345
346 if (fRunTime)
347 {
348 if (pszPassThrough)
349 throw Utf8Str(Storage::tr("Drive passthrough state cannot be changed while the VM is running\n"));
350 else if (pszBandwidthGroup)
351 throw Utf8Str(Storage::tr("Bandwidth group cannot be changed while the VM is running\n"));
352 }
353
354 /* check if the storage controller is present */
355 rc = machine->GetStorageControllerByName(Bstr(pszCtl).raw(),
356 storageCtl.asOutParam());
357 if (FAILED(rc))
358 throw Utf8StrFmt(Storage::tr("Could not find a controller named '%s'\n"), pszCtl);
359
360 StorageBus_T storageBus = StorageBus_Null;
361 CHECK_ERROR_RET(storageCtl, COMGETTER(Bus)(&storageBus), RTEXITCODE_FAILURE);
362 ULONG maxPorts = 0;
363 CHECK_ERROR_RET(systemProperties, GetMaxPortCountForStorageBus(storageBus, &maxPorts), RTEXITCODE_FAILURE);
364 ULONG maxDevices = 0;
365 CHECK_ERROR_RET(systemProperties, GetMaxDevicesPerPortForStorageBus(storageBus, &maxDevices), RTEXITCODE_FAILURE);
366
367 if (port == ~0U)
368 {
369 if (maxPorts == 1)
370 port = 0;
371 else
372 return errorSyntax(USAGE_STORAGEATTACH, Storage::tr("Port not specified"));
373 }
374 if (device == ~0U)
375 {
376 if (maxDevices == 1)
377 device = 0;
378 else
379 return errorSyntax(USAGE_STORAGEATTACH, Storage::tr("Device not specified"));
380 }
381
382 /* for sata controller check if the port count is big enough
383 * to accommodate the current port which is being assigned
384 * else just increase the port count
385 */
386 {
387 ULONG ulPortCount = 0;
388 ULONG ulMaxPortCount = 0;
389
390 CHECK_ERROR(storageCtl, COMGETTER(MaxPortCount)(&ulMaxPortCount));
391 CHECK_ERROR(storageCtl, COMGETTER(PortCount)(&ulPortCount));
392
393 if ( (ulPortCount != ulMaxPortCount)
394 && (port >= ulPortCount)
395 && (port < ulMaxPortCount))
396 CHECK_ERROR(storageCtl, COMSETTER(PortCount)(port + 1));
397 }
398
399 StorageControllerType_T ctlType = StorageControllerType_Null;
400 CHECK_ERROR(storageCtl, COMGETTER(ControllerType)(&ctlType));
401
402 if (!RTStrICmp(pszMedium, "none"))
403 {
404 CHECK_ERROR(machine, DetachDevice(Bstr(pszCtl).raw(), port, device));
405 }
406 else if (!RTStrICmp(pszMedium, "emptydrive"))
407 {
408 if (fRunTime)
409 {
410 ComPtr<IMediumAttachment> mediumAttachment;
411 DeviceType_T deviceType = DeviceType_Null;
412 rc = machine->GetMediumAttachment(Bstr(pszCtl).raw(), port, device,
413 mediumAttachment.asOutParam());
414 if (SUCCEEDED(rc))
415 {
416 mediumAttachment->COMGETTER(Type)(&deviceType);
417
418 if ( (deviceType == DeviceType_DVD)
419 || (deviceType == DeviceType_Floppy))
420 {
421 /* just unmount the floppy/dvd */
422 CHECK_ERROR(machine, UnmountMedium(Bstr(pszCtl).raw(),
423 port,
424 device,
425 fForceUnmount));
426 }
427 }
428 else if (devTypeRequested == DeviceType_DVD)
429 {
430 /*
431 * Try to attach an empty DVD drive as a hotplug operation.
432 * Main will complain if the controller doesn't support hotplugging.
433 */
434 CHECK_ERROR(machine, AttachDeviceWithoutMedium(Bstr(pszCtl).raw(), port, device,
435 devTypeRequested));
436 deviceType = DeviceType_DVD; /* To avoid the error message below. */
437 }
438
439 if ( FAILED(rc)
440 || !( deviceType == DeviceType_DVD
441 || deviceType == DeviceType_Floppy)
442 )
443 throw Utf8StrFmt(Storage::tr("No DVD/Floppy Drive attached to the controller '%s'"
444 "at the port: %u, device: %u"), pszCtl, port, device);
445
446 }
447 else
448 {
449 DeviceType_T deviceType = DeviceType_Null;
450 com::SafeArray <DeviceType_T> saDeviceTypes;
451 ULONG driveCheck = 0;
452
453 /* check if the device type is supported by the controller */
454 CHECK_ERROR(systemProperties, GetDeviceTypesForStorageBus(storageBus, ComSafeArrayAsOutParam(saDeviceTypes)));
455 for (size_t i = 0; i < saDeviceTypes.size(); ++ i)
456 {
457 if ( (saDeviceTypes[i] == DeviceType_DVD)
458 || (saDeviceTypes[i] == DeviceType_Floppy))
459 driveCheck++;
460 }
461
462 if (!driveCheck)
463 throw Utf8StrFmt(Storage::tr("The attachment is not supported by the storage controller '%s'"), pszCtl);
464
465 if (storageBus == StorageBus_Floppy)
466 deviceType = DeviceType_Floppy;
467 else
468 deviceType = DeviceType_DVD;
469
470 /* attach a empty floppy/dvd drive after removing previous attachment */
471 machine->DetachDevice(Bstr(pszCtl).raw(), port, device);
472 CHECK_ERROR(machine, AttachDeviceWithoutMedium(Bstr(pszCtl).raw(), port, device,
473 deviceType));
474 }
475 } // end if (!RTStrICmp(pszMedium, "emptydrive"))
476 else
477 {
478 ComPtr<IMedium> pMedium2Mount;
479
480 // not "none", not "emptydrive": then it must be a UUID or filename or hostdrive or iSCSI;
481 // for all these we first need to know the type of drive we're attaching to
482 {
483 /*
484 * try to determine the type of the drive from the
485 * storage controller chipset, the attachment and
486 * the medium being attached
487 */
488 if (ctlType == StorageControllerType_I82078) // floppy controller
489 devTypeRequested = DeviceType_Floppy;
490 else
491 {
492 /*
493 * for SATA/SCSI/IDE it is hard to tell if it is a harddisk or
494 * a dvd being attached so lets check if the medium attachment
495 * and the medium, both are of same type. if yes then we are
496 * sure of its type and don't need the user to enter it manually
497 * else ask the user for the type.
498 */
499 ComPtr<IMediumAttachment> mediumAttachment;
500 rc = machine->GetMediumAttachment(Bstr(pszCtl).raw(), port,
501 device,
502 mediumAttachment.asOutParam());
503 if (SUCCEEDED(rc))
504 {
505 DeviceType_T deviceType;
506 mediumAttachment->COMGETTER(Type)(&deviceType);
507
508 if (pszMedium)
509 {
510 if (!RTStrICmp(pszMedium, "additions"))
511 {
512 ComPtr<ISystemProperties> pProperties;
513 CHECK_ERROR(a->virtualBox,
514 COMGETTER(SystemProperties)(pProperties.asOutParam()));
515 CHECK_ERROR(pProperties, COMGETTER(DefaultAdditionsISO)(bstrIso.asOutParam()));
516 strIso = Utf8Str(bstrIso);
517 if (strIso.isEmpty())
518 throw Utf8Str(Storage::tr("Cannot find the Guest Additions ISO image\n"));
519 pszMedium = strIso.c_str();
520 if (devTypeRequested == DeviceType_Null)
521 devTypeRequested = DeviceType_DVD;
522 }
523 ComPtr<IMedium> pExistingMedium;
524 rc = openMedium(a, pszMedium, deviceType,
525 AccessMode_ReadWrite,
526 pExistingMedium,
527 false /* fForceNewUuidOnOpen */,
528 true /* fSilent */);
529 if (SUCCEEDED(rc) && pExistingMedium)
530 {
531 if ( (deviceType == DeviceType_DVD)
532 || (deviceType == DeviceType_HardDisk)
533 )
534 devTypeRequested = deviceType;
535 }
536 }
537 else
538 devTypeRequested = deviceType;
539 }
540 }
541 }
542
543 if (devTypeRequested == DeviceType_Null) // still the initializer value?
544 throw Utf8Str(Storage::tr("Argument --type must be specified\n"));
545
546 /* check if the device type is supported by the controller */
547 {
548 com::SafeArray <DeviceType_T> saDeviceTypes;
549
550 CHECK_ERROR(systemProperties, GetDeviceTypesForStorageBus(storageBus, ComSafeArrayAsOutParam(saDeviceTypes)));
551 if (SUCCEEDED(rc))
552 {
553 ULONG driveCheck = 0;
554 for (size_t i = 0; i < saDeviceTypes.size(); ++ i)
555 if (saDeviceTypes[i] == devTypeRequested)
556 driveCheck++;
557 if (!driveCheck)
558 throw Utf8StrFmt(Storage::tr("The given attachment is not supported by the storage controller '%s'"), pszCtl);
559 }
560 else
561 goto leave;
562 }
563
564 // find the medium given
565 /* host drive? */
566 if (!RTStrNICmp(pszMedium, RT_STR_TUPLE("host:")))
567 {
568 ComPtr<IHost> host;
569 CHECK_ERROR(a->virtualBox, COMGETTER(Host)(host.asOutParam()));
570
571 if (devTypeRequested == DeviceType_DVD)
572 {
573 rc = host->FindHostDVDDrive(Bstr(pszMedium + 5).raw(),
574 pMedium2Mount.asOutParam());
575 if (!pMedium2Mount)
576 {
577 /* 2nd try: try with the real name, important on Linux+libhal */
578 char szPathReal[RTPATH_MAX];
579 if (RT_FAILURE(RTPathReal(pszMedium + 5, szPathReal, sizeof(szPathReal))))
580 throw Utf8StrFmt(Storage::tr("Invalid host DVD drive name \"%s\""), pszMedium + 5);
581 rc = host->FindHostDVDDrive(Bstr(szPathReal).raw(),
582 pMedium2Mount.asOutParam());
583 if (!pMedium2Mount)
584 throw Utf8StrFmt(Storage::tr("Invalid host DVD drive name \"%s\""), pszMedium + 5);
585 }
586 }
587 else
588 {
589 // floppy
590 rc = host->FindHostFloppyDrive(Bstr(pszMedium + 5).raw(),
591 pMedium2Mount.asOutParam());
592 if (!pMedium2Mount)
593 throw Utf8StrFmt(Storage::tr("Invalid host floppy drive name \"%s\""), pszMedium + 5);
594 }
595 }
596 else if (!RTStrICmp(pszMedium, "iSCSI"))
597 {
598 /* check for required options */
599 if (bstrServer.isEmpty() || bstrTarget.isEmpty())
600 throw Utf8StrFmt(Storage::tr("Parameters --server and --target are required for iSCSI media"));
601
602 /** @todo move the location stuff to Main, which can use pfnComposeName
603 * from the disk backends to construct the location properly. Also do
604 * not use slashes to separate the parts, as otherwise only the last
605 * element containing information will be shown. */
606 Bstr bstrISCSIMedium;
607 if ( bstrLun.isEmpty()
608 || (bstrLun == "0")
609 || (bstrLun == "enc0")
610 )
611 bstrISCSIMedium = BstrFmt("%ls|%ls", bstrServer.raw(), bstrTarget.raw());
612 else
613 bstrISCSIMedium = BstrFmt("%ls|%ls|%ls", bstrServer.raw(), bstrTarget.raw(), bstrLun.raw());
614
615 CHECK_ERROR(a->virtualBox, CreateMedium(Bstr("iSCSI").raw(),
616 bstrISCSIMedium.raw(),
617 AccessMode_ReadWrite,
618 DeviceType_HardDisk,
619 pMedium2Mount.asOutParam()));
620 if (FAILED(rc)) goto leave;
621 if (!bstrPort.isEmpty())
622 bstrServer = BstrFmt("%ls:%ls", bstrServer.raw(), bstrPort.raw());
623
624 // set the other iSCSI parameters as properties
625 com::SafeArray <BSTR> names;
626 com::SafeArray <BSTR> values;
627 Bstr("TargetAddress").detachTo(names.appendedRaw());
628 bstrServer.detachTo(values.appendedRaw());
629 Bstr("TargetName").detachTo(names.appendedRaw());
630 bstrTarget.detachTo(values.appendedRaw());
631
632 if (!bstrLun.isEmpty())
633 {
634 Bstr("LUN").detachTo(names.appendedRaw());
635 bstrLun.detachTo(values.appendedRaw());
636 }
637 if (!bstrUsername.isEmpty())
638 {
639 Bstr("InitiatorUsername").detachTo(names.appendedRaw());
640 bstrUsername.detachTo(values.appendedRaw());
641 }
642 if (!bstrPassword.isEmpty())
643 {
644 Bstr("InitiatorSecret").detachTo(names.appendedRaw());
645 bstrPassword.detachTo(values.appendedRaw());
646 }
647 if (!bstrInitiator.isEmpty())
648 {
649 Bstr("InitiatorName").detachTo(names.appendedRaw());
650 bstrInitiator.detachTo(values.appendedRaw());
651 }
652
653 /// @todo add --targetName and --targetPassword options
654
655 if (fIntNet)
656 {
657 Bstr("HostIPStack").detachTo(names.appendedRaw());
658 Bstr("0").detachTo(values.appendedRaw());
659 }
660
661 CHECK_ERROR(pMedium2Mount, SetProperties(ComSafeArrayAsInParam(names),
662 ComSafeArrayAsInParam(values)));
663 if (FAILED(rc)) goto leave;
664 Bstr guid;
665 CHECK_ERROR(pMedium2Mount, COMGETTER(Id)(guid.asOutParam()));
666 if (FAILED(rc)) goto leave;
667 RTPrintf(Storage::tr("iSCSI disk created. UUID: %s\n"), Utf8Str(guid).c_str());
668 }
669 else
670 {
671 if (!pszMedium)
672 {
673 ComPtr<IMediumAttachment> mediumAttachment;
674 rc = machine->GetMediumAttachment(Bstr(pszCtl).raw(), port,
675 device,
676 mediumAttachment.asOutParam());
677 if (FAILED(rc))
678 throw Utf8Str(Storage::tr("Missing --medium argument"));
679 }
680 else
681 {
682 Bstr bstrMedium(pszMedium);
683 rc = openMedium(a, pszMedium, devTypeRequested,
684 AccessMode_ReadWrite, pMedium2Mount,
685 fSetNewUuid, false /* fSilent */);
686 if (FAILED(rc) || !pMedium2Mount)
687 throw Utf8StrFmt(Storage::tr("Invalid UUID or filename \"%s\""), pszMedium);
688 }
689 }
690
691 // set medium/parent medium UUID, if so desired
692 if (pMedium2Mount && (fSetNewUuid || fSetNewParentUuid))
693 {
694 CHECK_ERROR(pMedium2Mount, SetIds(fSetNewUuid, bstrNewUuid.raw(),
695 fSetNewParentUuid, bstrNewParentUuid.raw()));
696 if (FAILED(rc))
697 throw Utf8Str(Storage::tr("Failed to set the medium/parent medium UUID"));
698 }
699
700 // set medium type, if so desired
701 if (pMedium2Mount && fSetMediumType)
702 {
703 MediumType_T enmMediumTypeOld;
704 CHECK_ERROR(pMedium2Mount, COMGETTER(Type)(&enmMediumTypeOld));
705 if (SUCCEEDED(rc))
706 {
707 if (enmMediumTypeOld != enmMediumType)
708 {
709 CHECK_ERROR(pMedium2Mount, COMSETTER(Type)(enmMediumType));
710 if (FAILED(rc))
711 throw Utf8Str(Storage::tr("Failed to set the medium type"));
712 }
713 }
714 }
715
716 if (pMedium2Mount && !bstrComment.isEmpty())
717 {
718 CHECK_ERROR(pMedium2Mount, COMSETTER(Description)(bstrComment.raw()));
719 }
720
721 if (pszMedium)
722 {
723 switch (devTypeRequested)
724 {
725 case DeviceType_DVD:
726 case DeviceType_Floppy:
727 {
728 if (!fRunTime)
729 {
730 ComPtr<IMediumAttachment> mediumAttachment;
731 // check if there is a dvd/floppy drive at the given location, if not attach one first
732 rc = machine->GetMediumAttachment(Bstr(pszCtl).raw(),
733 port,
734 device,
735 mediumAttachment.asOutParam());
736 if (SUCCEEDED(rc))
737 {
738 DeviceType_T deviceType;
739 mediumAttachment->COMGETTER(Type)(&deviceType);
740 if (deviceType != devTypeRequested)
741 {
742 machine->DetachDevice(Bstr(pszCtl).raw(), port, device);
743 rc = machine->AttachDeviceWithoutMedium(Bstr(pszCtl).raw(),
744 port,
745 device,
746 devTypeRequested); // DeviceType_DVD or DeviceType_Floppy
747 }
748 }
749 else
750 {
751 rc = machine->AttachDeviceWithoutMedium(Bstr(pszCtl).raw(),
752 port,
753 device,
754 devTypeRequested); // DeviceType_DVD or DeviceType_Floppy
755 }
756 }
757
758 if (pMedium2Mount)
759 {
760 CHECK_ERROR(machine, MountMedium(Bstr(pszCtl).raw(),
761 port,
762 device,
763 pMedium2Mount,
764 fForceUnmount));
765 }
766 break;
767 } // end DeviceType_DVD or DeviceType_Floppy:
768
769 case DeviceType_HardDisk:
770 {
771 // if there is anything attached at the given location, remove it
772 machine->DetachDevice(Bstr(pszCtl).raw(), port, device);
773 CHECK_ERROR(machine, AttachDevice(Bstr(pszCtl).raw(),
774 port,
775 device,
776 DeviceType_HardDisk,
777 pMedium2Mount));
778 break;
779 }
780
781 default: break; /* Shut up MSC */
782 }
783 }
784 }
785
786 if ( pszPassThrough
787 && (SUCCEEDED(rc)))
788 {
789 ComPtr<IMediumAttachment> mattach;
790 CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port,
791 device, mattach.asOutParam()));
792
793 if (SUCCEEDED(rc))
794 {
795 if (!RTStrICmp(pszPassThrough, "on"))
796 {
797 CHECK_ERROR(machine, PassthroughDevice(Bstr(pszCtl).raw(),
798 port, device, TRUE));
799 }
800 else if (!RTStrICmp(pszPassThrough, "off"))
801 {
802 CHECK_ERROR(machine, PassthroughDevice(Bstr(pszCtl).raw(),
803 port, device, FALSE));
804 }
805 else
806 throw Utf8StrFmt(Storage::tr("Invalid --passthrough argument '%s'"), pszPassThrough);
807 }
808 else
809 throw Utf8StrFmt(Storage::tr("Couldn't find the controller attachment for the controller '%s'\n"), pszCtl);
810 }
811
812 if ( pszTempEject
813 && (SUCCEEDED(rc)))
814 {
815 ComPtr<IMediumAttachment> mattach;
816 CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port,
817 device, mattach.asOutParam()));
818
819 if (SUCCEEDED(rc))
820 {
821 if (!RTStrICmp(pszTempEject, "on"))
822 {
823 CHECK_ERROR(machine, TemporaryEjectDevice(Bstr(pszCtl).raw(),
824 port, device, TRUE));
825 }
826 else if (!RTStrICmp(pszTempEject, "off"))
827 {
828 CHECK_ERROR(machine, TemporaryEjectDevice(Bstr(pszCtl).raw(),
829 port, device, FALSE));
830 }
831 else
832 throw Utf8StrFmt(Storage::tr("Invalid --tempeject argument '%s'"), pszTempEject);
833 }
834 else
835 throw Utf8StrFmt(Storage::tr("Couldn't find the controller attachment for the controller '%s'\n"), pszCtl);
836 }
837
838 if ( pszNonRotational
839 && (SUCCEEDED(rc)))
840 {
841 ComPtr<IMediumAttachment> mattach;
842 CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port,
843 device, mattach.asOutParam()));
844
845 if (SUCCEEDED(rc))
846 {
847 if (!RTStrICmp(pszNonRotational, "on"))
848 {
849 CHECK_ERROR(machine, NonRotationalDevice(Bstr(pszCtl).raw(),
850 port, device, TRUE));
851 }
852 else if (!RTStrICmp(pszNonRotational, "off"))
853 {
854 CHECK_ERROR(machine, NonRotationalDevice(Bstr(pszCtl).raw(),
855 port, device, FALSE));
856 }
857 else
858 throw Utf8StrFmt(Storage::tr("Invalid --nonrotational argument '%s'"), pszNonRotational);
859 }
860 else
861 throw Utf8StrFmt(Storage::tr("Couldn't find the controller attachment for the controller '%s'\n"), pszCtl);
862 }
863
864 if ( pszDiscard
865 && (SUCCEEDED(rc)))
866 {
867 ComPtr<IMediumAttachment> mattach;
868 CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port,
869 device, mattach.asOutParam()));
870
871 if (SUCCEEDED(rc))
872 {
873 if (!RTStrICmp(pszDiscard, "on"))
874 {
875 CHECK_ERROR(machine, SetAutoDiscardForDevice(Bstr(pszCtl).raw(),
876 port, device, TRUE));
877 }
878 else if (!RTStrICmp(pszDiscard, "off"))
879 {
880 CHECK_ERROR(machine, SetAutoDiscardForDevice(Bstr(pszCtl).raw(),
881 port, device, FALSE));
882 }
883 else
884 throw Utf8StrFmt(Storage::tr("Invalid --discard argument '%s'"), pszDiscard);
885 }
886 else
887 throw Utf8StrFmt(Storage::tr("Couldn't find the controller attachment for the controller '%s'\n"), pszCtl);
888 }
889
890 if ( pszHotPluggable
891 && (SUCCEEDED(rc)))
892 {
893 ComPtr<IMediumAttachment> mattach;
894 CHECK_ERROR(machine, GetMediumAttachment(Bstr(pszCtl).raw(), port,
895 device, mattach.asOutParam()));
896
897 if (SUCCEEDED(rc))
898 {
899 if (!RTStrICmp(pszHotPluggable, "on"))
900 {
901 CHECK_ERROR(machine, SetHotPluggableForDevice(Bstr(pszCtl).raw(),
902 port, device, TRUE));
903 }
904 else if (!RTStrICmp(pszHotPluggable, "off"))
905 {
906 CHECK_ERROR(machine, SetHotPluggableForDevice(Bstr(pszCtl).raw(),
907 port, device, FALSE));
908 }
909 else
910 throw Utf8StrFmt(Storage::tr("Invalid --hotpluggable argument '%s'"), pszHotPluggable);
911 }
912 else
913 throw Utf8StrFmt(Storage::tr("Couldn't find the controller attachment for the controller '%s'\n"), pszCtl);
914 }
915
916 if ( pszBandwidthGroup
917 && !fRunTime
918 && SUCCEEDED(rc))
919 {
920
921 if (!RTStrICmp(pszBandwidthGroup, "none"))
922 {
923 /* Just remove the bandwidth gorup. */
924 CHECK_ERROR(machine, SetNoBandwidthGroupForDevice(Bstr(pszCtl).raw(),
925 port, device));
926 }
927 else
928 {
929 ComPtr<IBandwidthControl> bwCtrl;
930 ComPtr<IBandwidthGroup> bwGroup;
931
932 CHECK_ERROR(machine, COMGETTER(BandwidthControl)(bwCtrl.asOutParam()));
933
934 if (SUCCEEDED(rc))
935 {
936 CHECK_ERROR(bwCtrl, GetBandwidthGroup(Bstr(pszBandwidthGroup).raw(), bwGroup.asOutParam()));
937 if (SUCCEEDED(rc))
938 {
939 CHECK_ERROR(machine, SetBandwidthGroupForDevice(Bstr(pszCtl).raw(),
940 port, device, bwGroup));
941 }
942 }
943 }
944 }
945
946 /* commit changes */
947 if (SUCCEEDED(rc))
948 CHECK_ERROR(machine, SaveSettings());
949 }
950 catch (const Utf8Str &strError)
951 {
952 errorArgument("%s", strError.c_str());
953 rc = E_FAIL;
954 }
955
956 // machine must always be unlocked, even on errors
957leave:
958 a->session->UnlockMachine();
959
960 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
961}
962
963
964static const RTGETOPTDEF g_aStorageControllerOptions[] =
965{
966 { "--name", 'n', RTGETOPT_REQ_STRING },
967 { "--add", 'a', RTGETOPT_REQ_STRING },
968 { "--controller", 'c', RTGETOPT_REQ_STRING },
969 { "--portcount", 'p', RTGETOPT_REQ_UINT32 },
970 { "--remove", 'r', RTGETOPT_REQ_NOTHING },
971 { "--rename", 'R', RTGETOPT_REQ_STRING },
972 { "--hostiocache", 'i', RTGETOPT_REQ_STRING },
973 { "--bootable", 'b', RTGETOPT_REQ_STRING },
974};
975
976RTEXITCODE handleStorageController(HandlerArg *a)
977{
978 int c;
979 const char *pszCtl = NULL;
980 const char *pszBusType = NULL;
981 const char *pszCtlType = NULL;
982 const char *pszHostIOCache = NULL;
983 const char *pszBootable = NULL;
984 const char *pszCtlNewName = NULL;
985 ULONG portcount = ~0U;
986 bool fRemoveCtl = false;
987 ComPtr<IMachine> machine;
988 RTGETOPTUNION ValueUnion;
989 RTGETOPTSTATE GetState;
990
991 if (a->argc < 4)
992 return errorSyntax(USAGE_STORAGECONTROLLER, Storage::tr("Too few parameters"));
993
994 RTGetOptInit (&GetState, a->argc, a->argv, g_aStorageControllerOptions,
995 RT_ELEMENTS(g_aStorageControllerOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
996
997 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
998 {
999 switch (c)
1000 {
1001 case 'n': // controller name
1002 Assert(ValueUnion.psz);
1003 pszCtl = ValueUnion.psz;
1004 break;
1005
1006 case 'a': // controller bus type <ide/sata/scsi/floppy>
1007 Assert(ValueUnion.psz);
1008 pszBusType = ValueUnion.psz;
1009 break;
1010
1011 case 'c': // controller <lsilogic/buslogic/intelahci/piix3/piix4/ich6/i82078>
1012 Assert(ValueUnion.psz);
1013 pszCtlType = ValueUnion.psz;
1014 break;
1015
1016 case 'p': // portcount
1017 portcount = ValueUnion.u32;
1018 break;
1019
1020 case 'r': // remove controller
1021 fRemoveCtl = true;
1022 break;
1023
1024 case 'R': // rename controller
1025 Assert(ValueUnion.psz);
1026 pszCtlNewName = ValueUnion.psz;
1027 break;
1028
1029 case 'i':
1030 pszHostIOCache = ValueUnion.psz;
1031 break;
1032
1033 case 'b':
1034 pszBootable = ValueUnion.psz;
1035 break;
1036
1037 default:
1038 return errorGetOpt(USAGE_STORAGECONTROLLER, c, &ValueUnion);
1039 }
1040 }
1041
1042 HRESULT rc;
1043
1044 /* try to find the given machine */
1045 CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
1046 machine.asOutParam()), RTEXITCODE_FAILURE);
1047
1048 /* open a session for the VM */
1049 CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Write), RTEXITCODE_FAILURE);
1050
1051 /* get the mutable session machine */
1052 a->session->COMGETTER(Machine)(machine.asOutParam());
1053
1054 if (!pszCtl)
1055 {
1056 /* it's important to always close sessions */
1057 a->session->UnlockMachine();
1058 return errorSyntax(USAGE_STORAGECONTROLLER, Storage::tr("Storage controller name not specified\n"));
1059 }
1060
1061 if (fRemoveCtl)
1062 {
1063 CHECK_ERROR(machine, RemoveStorageController(Bstr(pszCtl).raw()));
1064 }
1065 else
1066 {
1067 if (pszBusType)
1068 {
1069 ComPtr<IStorageController> ctl;
1070
1071 if (!RTStrICmp(pszBusType, "ide"))
1072 {
1073 CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(),
1074 StorageBus_IDE,
1075 ctl.asOutParam()));
1076 }
1077 else if (!RTStrICmp(pszBusType, "sata"))
1078 {
1079 CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(),
1080 StorageBus_SATA,
1081 ctl.asOutParam()));
1082 }
1083 else if (!RTStrICmp(pszBusType, "scsi"))
1084 {
1085 CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(),
1086 StorageBus_SCSI,
1087 ctl.asOutParam()));
1088 }
1089 else if (!RTStrICmp(pszBusType, "floppy"))
1090 {
1091 CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(),
1092 StorageBus_Floppy,
1093 ctl.asOutParam()));
1094 }
1095 else if (!RTStrICmp(pszBusType, "sas"))
1096 {
1097 CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(),
1098 StorageBus_SAS,
1099 ctl.asOutParam()));
1100 }
1101 else if (!RTStrICmp(pszBusType, "usb"))
1102 {
1103 CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(),
1104 StorageBus_USB,
1105 ctl.asOutParam()));
1106 }
1107 else if (!RTStrICmp(pszBusType, "pcie"))
1108 {
1109 CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(),
1110 StorageBus_PCIe,
1111 ctl.asOutParam()));
1112 }
1113 else if (!RTStrICmp(pszBusType, "virtio-scsi") || !RTStrICmp(pszBusType, "virtio"))
1114 {
1115 CHECK_ERROR(machine, AddStorageController(Bstr(pszCtl).raw(),
1116 StorageBus_VirtioSCSI,
1117 ctl.asOutParam()));
1118 }
1119 else
1120 {
1121 errorArgument(Storage::tr("Invalid --add argument '%s'"), pszBusType);
1122 rc = E_FAIL;
1123 }
1124 }
1125
1126 if ( pszCtlType
1127 && SUCCEEDED(rc))
1128 {
1129 ComPtr<IStorageController> ctl;
1130
1131 CHECK_ERROR(machine, GetStorageControllerByName(Bstr(pszCtl).raw(),
1132 ctl.asOutParam()));
1133
1134 if (SUCCEEDED(rc))
1135 {
1136 if (!RTStrICmp(pszCtlType, "lsilogic"))
1137 {
1138 CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_LsiLogic));
1139 }
1140 else if (!RTStrICmp(pszCtlType, "buslogic"))
1141 {
1142 CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_BusLogic));
1143 }
1144 else if (!RTStrICmp(pszCtlType, "intelahci"))
1145 {
1146 CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_IntelAhci));
1147 }
1148 else if (!RTStrICmp(pszCtlType, "piix3"))
1149 {
1150 CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_PIIX3));
1151 }
1152 else if (!RTStrICmp(pszCtlType, "piix4"))
1153 {
1154 CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_PIIX4));
1155 }
1156 else if (!RTStrICmp(pszCtlType, "ich6"))
1157 {
1158 CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_ICH6));
1159 }
1160 else if (!RTStrICmp(pszCtlType, "i82078"))
1161 {
1162 CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_I82078));
1163 }
1164 else if (!RTStrICmp(pszCtlType, "lsilogicsas"))
1165 {
1166 CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_LsiLogicSas));
1167 }
1168 else if (!RTStrICmp(pszCtlType, "usb"))
1169 {
1170 CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_USB));
1171 }
1172 else if (!RTStrICmp(pszCtlType, "nvme"))
1173 {
1174 CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_NVMe));
1175 }
1176 else if (!RTStrICmp(pszCtlType, "virtio-scsi") || !RTStrICmp(pszCtlType, "virtio"))
1177 {
1178 CHECK_ERROR(ctl, COMSETTER(ControllerType)(StorageControllerType_VirtioSCSI));
1179 }
1180 else
1181 {
1182 errorArgument(Storage::tr("Invalid --type argument '%s'"), pszCtlType);
1183 rc = E_FAIL;
1184 }
1185 }
1186 else
1187 {
1188 errorArgument(Storage::tr("Couldn't find the controller with the name: '%s'\n"), pszCtl);
1189 rc = E_FAIL;
1190 }
1191 }
1192
1193 if ( (portcount != ~0U)
1194 && SUCCEEDED(rc))
1195 {
1196 ComPtr<IStorageController> ctl;
1197
1198 CHECK_ERROR(machine, GetStorageControllerByName(Bstr(pszCtl).raw(),
1199 ctl.asOutParam()));
1200
1201 if (SUCCEEDED(rc))
1202 {
1203 CHECK_ERROR(ctl, COMSETTER(PortCount)(portcount));
1204 }
1205 else
1206 {
1207 errorArgument(Storage::tr("Couldn't find the controller with the name: '%s'\n"), pszCtl);
1208 rc = E_FAIL;
1209 }
1210 }
1211
1212 if ( pszHostIOCache
1213 && SUCCEEDED(rc))
1214 {
1215 ComPtr<IStorageController> ctl;
1216
1217 CHECK_ERROR(machine, GetStorageControllerByName(Bstr(pszCtl).raw(),
1218 ctl.asOutParam()));
1219
1220 if (SUCCEEDED(rc))
1221 {
1222 if (!RTStrICmp(pszHostIOCache, "on"))
1223 {
1224 CHECK_ERROR(ctl, COMSETTER(UseHostIOCache)(TRUE));
1225 }
1226 else if (!RTStrICmp(pszHostIOCache, "off"))
1227 {
1228 CHECK_ERROR(ctl, COMSETTER(UseHostIOCache)(FALSE));
1229 }
1230 else
1231 {
1232 errorArgument(Storage::tr("Invalid --hostiocache argument '%s'"), pszHostIOCache);
1233 rc = E_FAIL;
1234 }
1235 }
1236 else
1237 {
1238 errorArgument(Storage::tr("Couldn't find the controller with the name: '%s'\n"), pszCtl);
1239 rc = E_FAIL;
1240 }
1241 }
1242
1243 if ( pszBootable
1244 && SUCCEEDED(rc))
1245 {
1246 if (SUCCEEDED(rc))
1247 {
1248 if (!RTStrICmp(pszBootable, "on"))
1249 {
1250 CHECK_ERROR(machine, SetStorageControllerBootable(Bstr(pszCtl).raw(), TRUE));
1251 }
1252 else if (!RTStrICmp(pszBootable, "off"))
1253 {
1254 CHECK_ERROR(machine, SetStorageControllerBootable(Bstr(pszCtl).raw(), FALSE));
1255 }
1256 else
1257 {
1258 errorArgument(Storage::tr("Invalid --bootable argument '%s'"), pszBootable);
1259 rc = E_FAIL;
1260 }
1261 }
1262 else
1263 {
1264 errorArgument(Storage::tr("Couldn't find the controller with the name: '%s'\n"), pszCtl);
1265 rc = E_FAIL;
1266 }
1267 }
1268
1269 if ( pszCtlNewName
1270 && SUCCEEDED(rc))
1271 {
1272 ComPtr<IStorageController> ctl;
1273
1274 CHECK_ERROR(machine, GetStorageControllerByName(Bstr(pszCtl).raw(),
1275 ctl.asOutParam()));
1276
1277 if (SUCCEEDED(rc))
1278 {
1279 CHECK_ERROR(ctl, COMSETTER(Name)(Bstr(pszCtlNewName).raw()));
1280 }
1281 else
1282 {
1283 errorArgument(Storage::tr("Couldn't find the controller with the name: '%s'\n"), pszCtl);
1284 rc = E_FAIL;
1285 }
1286 }
1287
1288 }
1289
1290 /* commit changes */
1291 if (SUCCEEDED(rc))
1292 CHECK_ERROR(machine, SaveSettings());
1293
1294 /* it's important to always close sessions */
1295 a->session->UnlockMachine();
1296
1297 return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1298}
1299
1300#endif /* !VBOX_ONLY_DOCS */
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