VirtualBox

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

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

FE/VBoxManage: Remove the now unused VBoxManageHelp build target and the VBOX_ONLY_DOCS #ifdef's in the code, ​bugref:9186 [scm fix]

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