VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxManage/VBoxManageUSB.cpp@ 35273

Last change on this file since 35273 was 33294, checked in by vboxsync, 14 years ago

Main: API change, merge IVirtualBox::getMachine() with findMachine()

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.7 KB
Line 
1/* $Id: VBoxManageUSB.cpp 33294 2010-10-21 10:45:26Z vboxsync $ */
2/** @file
3 * VBoxManage - VirtualBox's command-line interface.
4 */
5
6/*
7 * Copyright (C) 2006-2010 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#include <VBox/com/com.h>
19#include <VBox/com/string.h>
20#include <VBox/com/Guid.h>
21#include <VBox/com/array.h>
22#include <VBox/com/ErrorInfo.h>
23#include <VBox/com/errorprint.h>
24#include <VBox/com/EventQueue.h>
25
26#include <VBox/com/VirtualBox.h>
27
28#include "VBoxManage.h"
29
30#include <iprt/asm.h>
31
32/* missing XPCOM <-> COM wrappers */
33#ifndef STDMETHOD_
34# define STDMETHOD_(ret, meth) NS_IMETHOD_(ret) meth
35#endif
36#ifndef NS_GET_IID
37# define NS_GET_IID(I) IID_##I
38#endif
39#ifndef RT_OS_WINDOWS
40#define IUnknown nsISupports
41#endif
42
43using namespace com;
44
45/**
46 * Quick IUSBDevice implementation for detaching / attaching
47 * devices to the USB Controller.
48 */
49class MyUSBDevice : public IUSBDevice
50{
51public:
52 // public initializer/uninitializer for internal purposes only
53 MyUSBDevice(uint16_t a_u16VendorId, uint16_t a_u16ProductId, uint16_t a_bcdRevision, uint64_t a_u64SerialHash, const char *a_pszComment)
54 : m_usVendorId(a_u16VendorId), m_usProductId(a_u16ProductId),
55 m_bcdRevision(a_bcdRevision), m_u64SerialHash(a_u64SerialHash),
56 m_bstrComment(a_pszComment),
57 m_cRefs(0)
58 {
59 }
60
61 STDMETHOD_(ULONG, AddRef)(void)
62 {
63 return ASMAtomicIncU32(&m_cRefs);
64 }
65 STDMETHOD_(ULONG, Release)(void)
66 {
67 ULONG cRefs = ASMAtomicDecU32(&m_cRefs);
68 if (!cRefs)
69 delete this;
70 return cRefs;
71 }
72 STDMETHOD(QueryInterface)(const IID &iid, void **ppvObject)
73 {
74 Guid guid(iid);
75 if (guid == Guid(NS_GET_IID(IUnknown)))
76 *ppvObject = (IUnknown *)this;
77#ifdef RT_OS_WINDOWS
78 else if (guid == Guid(NS_GET_IID(IDispatch)))
79 *ppvObject = (IDispatch *)this;
80#endif
81 else if (guid == Guid(NS_GET_IID(IUSBDevice)))
82 *ppvObject = (IUSBDevice *)this;
83 else
84 return E_NOINTERFACE;
85 AddRef();
86 return S_OK;
87 }
88
89 STDMETHOD(COMGETTER(Id))(OUT_GUID a_pId) { return E_NOTIMPL; }
90 STDMETHOD(COMGETTER(VendorId))(USHORT *a_pusVendorId) { *a_pusVendorId = m_usVendorId; return S_OK; }
91 STDMETHOD(COMGETTER(ProductId))(USHORT *a_pusProductId) { *a_pusProductId = m_usProductId; return S_OK; }
92 STDMETHOD(COMGETTER(Revision))(USHORT *a_pusRevision) { *a_pusRevision = m_bcdRevision; return S_OK; }
93 STDMETHOD(COMGETTER(SerialHash))(ULONG64 *a_pullSerialHash) { *a_pullSerialHash = m_u64SerialHash; return S_OK; }
94 STDMETHOD(COMGETTER(Manufacturer))(BSTR *a_pManufacturer) { return E_NOTIMPL; }
95 STDMETHOD(COMGETTER(Product))(BSTR *a_pProduct) { return E_NOTIMPL; }
96 STDMETHOD(COMGETTER(SerialNumber))(BSTR *a_pSerialNumber) { return E_NOTIMPL; }
97 STDMETHOD(COMGETTER(Address))(BSTR *a_pAddress) { return E_NOTIMPL; }
98
99private:
100 /** The vendor id of this USB device. */
101 USHORT m_usVendorId;
102 /** The product id of this USB device. */
103 USHORT m_usProductId;
104 /** The product revision number of this USB device.
105 * (high byte = integer; low byte = decimal) */
106 USHORT m_bcdRevision;
107 /** The USB serial hash of the device. */
108 uint64_t m_u64SerialHash;
109 /** The user comment string. */
110 Bstr m_bstrComment;
111 /** Reference counter. */
112 uint32_t volatile m_cRefs;
113};
114
115
116// types
117///////////////////////////////////////////////////////////////////////////////
118
119template <typename T>
120class Nullable
121{
122public:
123
124 Nullable() : mIsNull(true) {}
125 Nullable(const T &aValue, bool aIsNull = false)
126 : mIsNull(aIsNull), mValue(aValue) {}
127
128 bool isNull() const { return mIsNull; };
129 void setNull(bool aIsNull = true) { mIsNull = aIsNull; }
130
131 operator const T&() const { return mValue; }
132
133 Nullable &operator= (const T &aValue)
134 {
135 mValue = aValue;
136 mIsNull = false;
137 return *this;
138 }
139
140private:
141
142 bool mIsNull;
143 T mValue;
144};
145
146/** helper structure to encapsulate USB filter manipulation commands */
147struct USBFilterCmd
148{
149 struct USBFilter
150 {
151 USBFilter()
152 : mAction(USBDeviceFilterAction_Null)
153 {}
154
155 Bstr mName;
156 Nullable <bool> mActive;
157 Bstr mVendorId;
158 Bstr mProductId;
159 Bstr mRevision;
160 Bstr mManufacturer;
161 Bstr mProduct;
162 Bstr mRemote;
163 Bstr mSerialNumber;
164 Nullable <ULONG> mMaskedInterfaces;
165 USBDeviceFilterAction_T mAction;
166 };
167
168 enum Action { Invalid, Add, Modify, Remove };
169
170 USBFilterCmd() : mAction(Invalid), mIndex(0), mGlobal(false) {}
171
172 Action mAction;
173 uint32_t mIndex;
174 /** flag whether the command target is a global filter */
175 bool mGlobal;
176 /** machine this command is targeted at (null for global filters) */
177 ComPtr<IMachine> mMachine;
178 USBFilter mFilter;
179};
180
181int handleUSBFilter(HandlerArg *a)
182{
183 HRESULT rc = S_OK;
184 USBFilterCmd cmd;
185
186 /* at least: 0: command, 1: index, 2: --target, 3: <target value> */
187 if (a->argc < 4)
188 return errorSyntax(USAGE_USBFILTER, "Not enough parameters");
189
190 /* which command? */
191 cmd.mAction = USBFilterCmd::Invalid;
192 if (!strcmp(a->argv[0], "add")) cmd.mAction = USBFilterCmd::Add;
193 else if (!strcmp(a->argv[0], "modify")) cmd.mAction = USBFilterCmd::Modify;
194 else if (!strcmp(a->argv[0], "remove")) cmd.mAction = USBFilterCmd::Remove;
195
196 if (cmd.mAction == USBFilterCmd::Invalid)
197 return errorSyntax(USAGE_USBFILTER, "Invalid parameter '%s'", a->argv[0]);
198
199 /* which index? */
200 if (VINF_SUCCESS != RTStrToUInt32Full(a->argv[1], 10, &cmd.mIndex))
201 return errorSyntax(USAGE_USBFILTER, "Invalid index '%s'", a->argv[1]);
202
203 switch (cmd.mAction)
204 {
205 case USBFilterCmd::Add:
206 case USBFilterCmd::Modify:
207 {
208 /* at least: 0: command, 1: index, 2: --target, 3: <target value>, 4: --name, 5: <name value> */
209 if (a->argc < 6)
210 {
211 if (cmd.mAction == USBFilterCmd::Add)
212 return errorSyntax(USAGE_USBFILTER_ADD, "Not enough parameters");
213
214 return errorSyntax(USAGE_USBFILTER_MODIFY, "Not enough parameters");
215 }
216
217 // set Active to true by default
218 // (assuming that the user sets up all necessary attributes
219 // at once and wants the filter to be active immediately)
220 if (cmd.mAction == USBFilterCmd::Add)
221 cmd.mFilter.mActive = true;
222
223 for (int i = 2; i < a->argc; i++)
224 {
225 if ( !strcmp(a->argv[i], "--target")
226 || !strcmp(a->argv[i], "-target"))
227 {
228 if (a->argc <= i + 1 || !*a->argv[i+1])
229 return errorArgument("Missing argument to '%s'", a->argv[i]);
230 i++;
231 if (!strcmp(a->argv[i], "global"))
232 cmd.mGlobal = true;
233 else
234 {
235 /* assume it's a UUID of a machine */
236 CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(a->argv[i]).raw(),
237 cmd.mMachine.asOutParam()), 1);
238 }
239 }
240 else if ( !strcmp(a->argv[i], "--name")
241 || !strcmp(a->argv[i], "-name"))
242 {
243 if (a->argc <= i + 1 || !*a->argv[i+1])
244 return errorArgument("Missing argument to '%s'", a->argv[i]);
245 i++;
246 cmd.mFilter.mName = a->argv[i];
247 }
248 else if ( !strcmp(a->argv[i], "--active")
249 || !strcmp(a->argv[i], "-active"))
250 {
251 if (a->argc <= i + 1)
252 return errorArgument("Missing argument to '%s'", a->argv[i]);
253 i++;
254 if (!strcmp(a->argv[i], "yes"))
255 cmd.mFilter.mActive = true;
256 else if (!strcmp(a->argv[i], "no"))
257 cmd.mFilter.mActive = false;
258 else
259 return errorArgument("Invalid --active argument '%s'", a->argv[i]);
260 }
261 else if ( !strcmp(a->argv[i], "--vendorid")
262 || !strcmp(a->argv[i], "-vendorid"))
263 {
264 if (a->argc <= i + 1)
265 return errorArgument("Missing argument to '%s'", a->argv[i]);
266 i++;
267 cmd.mFilter.mVendorId = a->argv[i];
268 }
269 else if ( !strcmp(a->argv[i], "--productid")
270 || !strcmp(a->argv[i], "-productid"))
271 {
272 if (a->argc <= i + 1)
273 return errorArgument("Missing argument to '%s'", a->argv[i]);
274 i++;
275 cmd.mFilter.mProductId = a->argv[i];
276 }
277 else if ( !strcmp(a->argv[i], "--revision")
278 || !strcmp(a->argv[i], "-revision"))
279 {
280 if (a->argc <= i + 1)
281 return errorArgument("Missing argument to '%s'", a->argv[i]);
282 i++;
283 cmd.mFilter.mRevision = a->argv[i];
284 }
285 else if ( !strcmp(a->argv[i], "--manufacturer")
286 || !strcmp(a->argv[i], "-manufacturer"))
287 {
288 if (a->argc <= i + 1)
289 return errorArgument("Missing argument to '%s'", a->argv[i]);
290 i++;
291 cmd.mFilter.mManufacturer = a->argv[i];
292 }
293 else if ( !strcmp(a->argv[i], "--product")
294 || !strcmp(a->argv[i], "-product"))
295 {
296 if (a->argc <= i + 1)
297 return errorArgument("Missing argument to '%s'", a->argv[i]);
298 i++;
299 cmd.mFilter.mProduct = a->argv[i];
300 }
301 else if ( !strcmp(a->argv[i], "--remote")
302 || !strcmp(a->argv[i], "-remote"))
303 {
304 if (a->argc <= i + 1)
305 return errorArgument("Missing argument to '%s'", a->argv[i]);
306 i++;
307 cmd.mFilter.mRemote = a->argv[i];
308 }
309 else if ( !strcmp(a->argv[i], "--serialnumber")
310 || !strcmp(a->argv[i], "-serialnumber"))
311 {
312 if (a->argc <= i + 1)
313 return errorArgument("Missing argument to '%s'", a->argv[i]);
314 i++;
315 cmd.mFilter.mSerialNumber = a->argv[i];
316 }
317 else if ( !strcmp(a->argv[i], "--maskedinterfaces")
318 || !strcmp(a->argv[i], "-maskedinterfaces"))
319 {
320 if (a->argc <= i + 1)
321 return errorArgument("Missing argument to '%s'", a->argv[i]);
322 i++;
323 uint32_t u32;
324 int vrc = RTStrToUInt32Full(a->argv[i], 0, &u32);
325 if (RT_FAILURE(vrc))
326 return errorArgument("Failed to convert the --maskedinterfaces value '%s' to a number, vrc=%Rrc", a->argv[i], vrc);
327 cmd.mFilter.mMaskedInterfaces = u32;
328 }
329 else if ( !strcmp(a->argv[i], "--action")
330 || !strcmp(a->argv[i], "-action"))
331 {
332 if (a->argc <= i + 1)
333 return errorArgument("Missing argument to '%s'", a->argv[i]);
334 i++;
335 if (!strcmp(a->argv[i], "ignore"))
336 cmd.mFilter.mAction = USBDeviceFilterAction_Ignore;
337 else if (!strcmp(a->argv[i], "hold"))
338 cmd.mFilter.mAction = USBDeviceFilterAction_Hold;
339 else
340 return errorArgument("Invalid USB filter action '%s'", a->argv[i]);
341 }
342 else
343 return errorSyntax(cmd.mAction == USBFilterCmd::Add ? USAGE_USBFILTER_ADD : USAGE_USBFILTER_MODIFY,
344 "Unknown option '%s'", a->argv[i]);
345 }
346
347 if (cmd.mAction == USBFilterCmd::Add)
348 {
349 // mandatory/forbidden options
350 if ( cmd.mFilter.mName.isEmpty()
351 ||
352 ( cmd.mGlobal
353 && cmd.mFilter.mAction == USBDeviceFilterAction_Null
354 )
355 || ( !cmd.mGlobal
356 && !cmd.mMachine)
357 || ( cmd.mGlobal
358 && !cmd.mFilter.mRemote.isEmpty())
359 )
360 {
361 return errorSyntax(USAGE_USBFILTER_ADD, "Mandatory options not supplied");
362 }
363 }
364 break;
365 }
366
367 case USBFilterCmd::Remove:
368 {
369 /* at least: 0: command, 1: index, 2: --target, 3: <target value> */
370 if (a->argc < 4)
371 return errorSyntax(USAGE_USBFILTER_REMOVE, "Not enough parameters");
372
373 for (int i = 2; i < a->argc; i++)
374 {
375 if ( !strcmp(a->argv[i], "--target")
376 || !strcmp(a->argv[i], "-target"))
377 {
378 if (a->argc <= i + 1 || !*a->argv[i+1])
379 return errorArgument("Missing argument to '%s'", a->argv[i]);
380 i++;
381 if (!strcmp(a->argv[i], "global"))
382 cmd.mGlobal = true;
383 else
384 {
385 CHECK_ERROR_RET(a->virtualBox, FindMachine(Bstr(a->argv[i]).raw(),
386 cmd.mMachine.asOutParam()), 1);
387 }
388 }
389 }
390
391 // mandatory options
392 if (!cmd.mGlobal && !cmd.mMachine)
393 return errorSyntax(USAGE_USBFILTER_REMOVE, "Mandatory options not supplied");
394
395 break;
396 }
397
398 default: break;
399 }
400
401 USBFilterCmd::USBFilter &f = cmd.mFilter;
402
403 ComPtr <IHost> host;
404 ComPtr <IUSBController> ctl;
405 if (cmd.mGlobal)
406 CHECK_ERROR_RET(a->virtualBox, COMGETTER(Host)(host.asOutParam()), 1);
407 else
408 {
409 /* open a session for the VM */
410 CHECK_ERROR_RET(cmd.mMachine, LockMachine(a->session, LockType_Write), 1);
411 /* get the mutable session machine */
412 a->session->COMGETTER(Machine)(cmd.mMachine.asOutParam());
413 /* and get the USB controller */
414 CHECK_ERROR_RET(cmd.mMachine, COMGETTER(USBController)(ctl.asOutParam()), 1);
415 }
416
417 switch (cmd.mAction)
418 {
419 case USBFilterCmd::Add:
420 {
421 if (cmd.mGlobal)
422 {
423 ComPtr <IHostUSBDeviceFilter> flt;
424 CHECK_ERROR_BREAK(host, CreateUSBDeviceFilter(f.mName.raw(),
425 flt.asOutParam()));
426
427 if (!f.mActive.isNull())
428 CHECK_ERROR_BREAK(flt, COMSETTER(Active)(f.mActive));
429 if (!f.mVendorId.isEmpty())
430 CHECK_ERROR_BREAK(flt, COMSETTER(VendorId)(f.mVendorId.raw()));
431 if (!f.mProductId.isEmpty())
432 CHECK_ERROR_BREAK(flt, COMSETTER(ProductId)(f.mProductId.raw()));
433 if (!f.mRevision.isEmpty())
434 CHECK_ERROR_BREAK(flt, COMSETTER(Revision)(f.mRevision.raw()));
435 if (!f.mManufacturer.isEmpty())
436 CHECK_ERROR_BREAK(flt, COMSETTER(Manufacturer)(f.mManufacturer.raw()));
437 if (!f.mSerialNumber.isEmpty())
438 CHECK_ERROR_BREAK(flt, COMSETTER(SerialNumber)(f.mSerialNumber.raw()));
439 if (!f.mMaskedInterfaces.isNull())
440 CHECK_ERROR_BREAK(flt, COMSETTER(MaskedInterfaces)(f.mMaskedInterfaces));
441
442 if (f.mAction != USBDeviceFilterAction_Null)
443 CHECK_ERROR_BREAK(flt, COMSETTER(Action)(f.mAction));
444
445 CHECK_ERROR_BREAK(host, InsertUSBDeviceFilter(cmd.mIndex, flt));
446 }
447 else
448 {
449 ComPtr <IUSBDeviceFilter> flt;
450 CHECK_ERROR_BREAK(ctl, CreateDeviceFilter(f.mName.raw(),
451 flt.asOutParam()));
452
453 if (!f.mActive.isNull())
454 CHECK_ERROR_BREAK(flt, COMSETTER(Active)(f.mActive));
455 if (!f.mVendorId.isEmpty())
456 CHECK_ERROR_BREAK(flt, COMSETTER(VendorId)(f.mVendorId.raw()));
457 if (!f.mProductId.isEmpty())
458 CHECK_ERROR_BREAK(flt, COMSETTER(ProductId)(f.mProductId.raw()));
459 if (!f.mRevision.isEmpty())
460 CHECK_ERROR_BREAK(flt, COMSETTER(Revision)(f.mRevision.raw()));
461 if (!f.mManufacturer.isEmpty())
462 CHECK_ERROR_BREAK(flt, COMSETTER(Manufacturer)(f.mManufacturer.raw()));
463 if (!f.mRemote.isEmpty())
464 CHECK_ERROR_BREAK(flt, COMSETTER(Remote)(f.mRemote.raw()));
465 if (!f.mSerialNumber.isEmpty())
466 CHECK_ERROR_BREAK(flt, COMSETTER(SerialNumber)(f.mSerialNumber.raw()));
467 if (!f.mMaskedInterfaces.isNull())
468 CHECK_ERROR_BREAK(flt, COMSETTER(MaskedInterfaces)(f.mMaskedInterfaces));
469
470 CHECK_ERROR_BREAK(ctl, InsertDeviceFilter(cmd.mIndex, flt));
471 }
472 break;
473 }
474 case USBFilterCmd::Modify:
475 {
476 if (cmd.mGlobal)
477 {
478 SafeIfaceArray <IHostUSBDeviceFilter> coll;
479 CHECK_ERROR_BREAK(host, COMGETTER(USBDeviceFilters)(ComSafeArrayAsOutParam(coll)));
480
481 ComPtr <IHostUSBDeviceFilter> flt = coll[cmd.mIndex];
482
483 if (!f.mName.isEmpty())
484 CHECK_ERROR_BREAK(flt, COMSETTER(Name)(f.mName.raw()));
485 if (!f.mActive.isNull())
486 CHECK_ERROR_BREAK(flt, COMSETTER(Active)(f.mActive));
487 if (!f.mVendorId.isEmpty())
488 CHECK_ERROR_BREAK(flt, COMSETTER(VendorId)(f.mVendorId.raw()));
489 if (!f.mProductId.isEmpty())
490 CHECK_ERROR_BREAK(flt, COMSETTER(ProductId)(f.mProductId.raw()));
491 if (!f.mRevision.isEmpty())
492 CHECK_ERROR_BREAK(flt, COMSETTER(Revision)(f.mRevision.raw()));
493 if (!f.mManufacturer.isEmpty())
494 CHECK_ERROR_BREAK(flt, COMSETTER(Manufacturer)(f.mManufacturer.raw()));
495 if (!f.mSerialNumber.isEmpty())
496 CHECK_ERROR_BREAK(flt, COMSETTER(SerialNumber)(f.mSerialNumber.raw()));
497 if (!f.mMaskedInterfaces.isNull())
498 CHECK_ERROR_BREAK(flt, COMSETTER(MaskedInterfaces)(f.mMaskedInterfaces));
499
500 if (f.mAction != USBDeviceFilterAction_Null)
501 CHECK_ERROR_BREAK(flt, COMSETTER(Action)(f.mAction));
502 }
503 else
504 {
505 SafeIfaceArray <IUSBDeviceFilter> coll;
506 CHECK_ERROR_BREAK(ctl, COMGETTER(DeviceFilters)(ComSafeArrayAsOutParam(coll)));
507
508 ComPtr <IUSBDeviceFilter> flt = coll[cmd.mIndex];
509
510 if (!f.mName.isEmpty())
511 CHECK_ERROR_BREAK(flt, COMSETTER(Name)(f.mName.raw()));
512 if (!f.mActive.isNull())
513 CHECK_ERROR_BREAK(flt, COMSETTER(Active)(f.mActive));
514 if (!f.mVendorId.isEmpty())
515 CHECK_ERROR_BREAK(flt, COMSETTER(VendorId)(f.mVendorId.raw()));
516 if (!f.mProductId.isEmpty())
517 CHECK_ERROR_BREAK(flt, COMSETTER(ProductId)(f.mProductId.raw()));
518 if (!f.mRevision.isEmpty())
519 CHECK_ERROR_BREAK(flt, COMSETTER(Revision)(f.mRevision.raw()));
520 if (!f.mManufacturer.isEmpty())
521 CHECK_ERROR_BREAK(flt, COMSETTER(Manufacturer)(f.mManufacturer.raw()));
522 if (!f.mRemote.isEmpty())
523 CHECK_ERROR_BREAK(flt, COMSETTER(Remote)(f.mRemote.raw()));
524 if (!f.mSerialNumber.isEmpty())
525 CHECK_ERROR_BREAK(flt, COMSETTER(SerialNumber)(f.mSerialNumber.raw()));
526 if (!f.mMaskedInterfaces.isNull())
527 CHECK_ERROR_BREAK(flt, COMSETTER(MaskedInterfaces)(f.mMaskedInterfaces));
528 }
529 break;
530 }
531 case USBFilterCmd::Remove:
532 {
533 if (cmd.mGlobal)
534 {
535 ComPtr <IHostUSBDeviceFilter> flt;
536 CHECK_ERROR_BREAK(host, RemoveUSBDeviceFilter(cmd.mIndex));
537 }
538 else
539 {
540 ComPtr <IUSBDeviceFilter> flt;
541 CHECK_ERROR_BREAK(ctl, RemoveDeviceFilter(cmd.mIndex, flt.asOutParam()));
542 }
543 break;
544 }
545 default:
546 break;
547 }
548
549 if (cmd.mMachine)
550 {
551 if (SUCCEEDED(rc))
552 {
553 /* commit the session */
554 CHECK_ERROR(cmd.mMachine, SaveSettings());
555 }
556 /* close the session */
557 a->session->UnlockMachine();
558 }
559
560 return SUCCEEDED(rc) ? 0 : 1;
561}
562/* vi: set tabstop=4 shiftwidth=4 expandtab: */
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use