VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/SerialPortImpl.cpp@ 97698

Last change on this file since 97698 was 96407, checked in by vboxsync, 2 years ago

scm copyright and license note update

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.2 KB
Line 
1/* $Id: SerialPortImpl.cpp 96407 2022-08-22 17:43:14Z vboxsync $ */
2/** @file
3 * VirtualBox COM class implementation
4 */
5
6/*
7 * Copyright (C) 2006-2022 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28#define LOG_GROUP LOG_GROUP_MAIN_SERIALPORT
29#include "SerialPortImpl.h"
30#include "MachineImpl.h"
31#include "VirtualBoxImpl.h"
32#include "GuestOSTypeImpl.h"
33
34#include <iprt/assert.h>
35#include <iprt/string.h>
36#include <iprt/cpp/utils.h>
37
38#include <VBox/settings.h>
39
40#include "AutoStateDep.h"
41#include "AutoCaller.h"
42#include "LoggingNew.h"
43
44//////////////////////////////////////////////////////////////////////////////////
45//
46// SerialPort private data definition
47//
48//////////////////////////////////////////////////////////////////////////////////
49
50struct SerialPort::Data
51{
52 Data()
53 : fModified(false),
54 pMachine(NULL)
55 { }
56
57 bool fModified;
58 Machine * const pMachine;
59 const ComObjPtr<SerialPort> pPeer;
60 Backupable<settings::SerialPort> bd;
61};
62
63// constructor / destructor
64/////////////////////////////////////////////////////////////////////////////
65
66DEFINE_EMPTY_CTOR_DTOR(SerialPort)
67
68HRESULT SerialPort::FinalConstruct()
69{
70 return BaseFinalConstruct();
71}
72
73void SerialPort::FinalRelease()
74{
75 uninit();
76 BaseFinalRelease();
77}
78
79// public initializer/uninitializer for internal purposes only
80/////////////////////////////////////////////////////////////////////////////
81
82/**
83 * Initializes the Serial Port object.
84 *
85 * @param aParent Handle of the parent object.
86 * @param aSlot Slot number the serial port is plugged into.
87 */
88HRESULT SerialPort::init(Machine *aParent, ULONG aSlot)
89{
90 LogFlowThisFunc(("aParent=%p, aSlot=%d\n", aParent, aSlot));
91
92 ComAssertRet(aParent, E_INVALIDARG);
93
94 /* Enclose the state transition NotReady->InInit->Ready */
95 AutoInitSpan autoInitSpan(this);
96 AssertReturn(autoInitSpan.isOk(), E_FAIL);
97
98 m = new Data();
99
100 unconst(m->pMachine) = aParent;
101 /* m->pPeer is left null */
102
103 m->bd.allocate();
104
105 /* initialize data */
106 m->bd->ulSlot = aSlot;
107
108 /* Confirm a successful initialization */
109 autoInitSpan.setSucceeded();
110
111 return S_OK;
112}
113
114/**
115 * Initializes the Serial Port object given another serial port object
116 * (a kind of copy constructor). This object shares data with
117 * the object passed as an argument.
118 *
119 * @note This object must be destroyed before the original object
120 * it shares data with is destroyed.
121 *
122 * @note Locks @a aThat object for reading.
123 */
124HRESULT SerialPort::init(Machine *aParent, SerialPort *aThat)
125{
126 LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
127
128 ComAssertRet(aParent && aThat, E_INVALIDARG);
129
130 /* Enclose the state transition NotReady->InInit->Ready */
131 AutoInitSpan autoInitSpan(this);
132 AssertReturn(autoInitSpan.isOk(), E_FAIL);
133
134 m = new Data();
135
136 unconst(m->pMachine) = aParent;
137 unconst(m->pPeer) = aThat;
138
139 AutoCaller thatCaller(aThat);
140 AssertComRCReturnRC(thatCaller.rc());
141
142 AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
143 m->bd.share(aThat->m->bd);
144
145 /* Confirm a successful initialization */
146 autoInitSpan.setSucceeded();
147
148 return S_OK;
149}
150
151/**
152 * Initializes the guest object given another guest object
153 * (a kind of copy constructor). This object makes a private copy of data
154 * of the original object passed as an argument.
155 *
156 * @note Locks @a aThat object for reading.
157 */
158HRESULT SerialPort::initCopy(Machine *aParent, SerialPort *aThat)
159{
160 LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
161
162 ComAssertRet(aParent && aThat, E_INVALIDARG);
163
164 /* Enclose the state transition NotReady->InInit->Ready */
165 AutoInitSpan autoInitSpan(this);
166 AssertReturn(autoInitSpan.isOk(), E_FAIL);
167
168 m = new Data();
169
170 unconst(m->pMachine) = aParent;
171 /* pPeer is left null */
172
173 AutoCaller thatCaller(aThat);
174 AssertComRCReturnRC(thatCaller.rc());
175
176 AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
177 m->bd.attachCopy(aThat->m->bd);
178
179 /* Confirm a successful initialization */
180 autoInitSpan.setSucceeded();
181
182 return S_OK;
183}
184
185/**
186 * Uninitializes the instance and sets the ready flag to FALSE.
187 * Called either from FinalRelease() or by the parent when it gets destroyed.
188 */
189void SerialPort::uninit()
190{
191 LogFlowThisFunc(("\n"));
192
193 /* Enclose the state transition Ready->InUninit->NotReady */
194 AutoUninitSpan autoUninitSpan(this);
195 if (autoUninitSpan.uninitDone())
196 return;
197
198 m->bd.free();
199
200 unconst(m->pPeer) = NULL;
201 unconst(m->pMachine) = NULL;
202
203 delete m;
204 m = NULL;
205}
206
207// ISerialPort properties
208/////////////////////////////////////////////////////////////////////////////
209
210HRESULT SerialPort::getEnabled(BOOL *aEnabled)
211{
212 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
213
214 *aEnabled = m->bd->fEnabled;
215
216 return S_OK;
217}
218
219
220HRESULT SerialPort::setEnabled(BOOL aEnabled)
221{
222 LogFlowThisFunc(("aEnabled=%RTbool\n", aEnabled));
223
224 /* the machine needs to be mutable */
225 AutoMutableStateDependency adep(m->pMachine);
226 if (FAILED(adep.rc())) return adep.rc();
227
228 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
229
230 if (m->bd->fEnabled != RT_BOOL(aEnabled))
231 {
232 m->bd.backup();
233 m->bd->fEnabled = RT_BOOL(aEnabled);
234
235 m->fModified = true;
236 // leave the lock before informing callbacks
237 alock.release();
238
239 AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
240 m->pMachine->i_setModified(Machine::IsModified_SerialPorts);
241 mlock.release();
242
243 m->pMachine->i_onSerialPortChange(this);
244 }
245
246 return S_OK;
247}
248
249
250HRESULT SerialPort::getHostMode(PortMode_T *aHostMode)
251{
252 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
253
254 *aHostMode = m->bd->portMode;
255
256 return S_OK;
257}
258
259HRESULT SerialPort::setHostMode(PortMode_T aHostMode)
260{
261 /* the machine needs to be mutable */
262 AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine);
263 if (FAILED(adep.rc())) return adep.rc();
264
265 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
266
267 if (m->bd->portMode != aHostMode)
268 {
269 switch (aHostMode)
270 {
271 case PortMode_RawFile:
272 if (m->bd->strPath.isEmpty())
273 return setError(E_INVALIDARG,
274 tr("Cannot set the raw file mode of the serial port %d "
275 "because the file path is empty or null"),
276 m->bd->ulSlot);
277 break;
278 case PortMode_HostPipe:
279 if (m->bd->strPath.isEmpty())
280 return setError(E_INVALIDARG,
281 tr("Cannot set the host pipe mode of the serial port %d "
282 "because the pipe path is empty or null"),
283 m->bd->ulSlot);
284 break;
285 case PortMode_HostDevice:
286 if (m->bd->strPath.isEmpty())
287 return setError(E_INVALIDARG,
288 tr("Cannot set the host device mode of the serial port %d "
289 "because the device path is empty or null"),
290 m->bd->ulSlot);
291 break;
292 case PortMode_TCP:
293 if (m->bd->strPath.isEmpty())
294 return setError(E_INVALIDARG,
295 tr("Cannot set the host device mode of the serial port %d "
296 "because the server address or TCP port is invalid"),
297 m->bd->ulSlot);
298 break;
299 case PortMode_Disconnected:
300 break;
301#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
302 case PortMode_32BitHack: /* (compiler warnings) */
303 AssertFailedBreak();
304#endif
305 }
306
307 m->bd.backup();
308 m->bd->portMode = aHostMode;
309
310 m->fModified = true;
311 // leave the lock before informing callbacks
312 alock.release();
313
314 AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
315 m->pMachine->i_setModified(Machine::IsModified_SerialPorts);
316 mlock.release();
317
318 m->pMachine->i_onSerialPortChange(this);
319 }
320
321 return S_OK;
322}
323
324HRESULT SerialPort::getSlot(ULONG *aSlot)
325{
326 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
327
328 *aSlot = m->bd->ulSlot;
329
330 return S_OK;
331}
332
333
334HRESULT SerialPort::getIRQ(ULONG *aIRQ)
335{
336 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
337
338 *aIRQ = m->bd->ulIRQ;
339
340 return S_OK;
341}
342
343
344HRESULT SerialPort::setIRQ(ULONG aIRQ)
345{
346 /* check IRQ limits
347 * (when changing this, make sure it corresponds to XML schema */
348 if (aIRQ > 255)
349 return setError(E_INVALIDARG,
350 tr("Invalid IRQ number of the serial port %d: %lu (must be in range [0, %lu])"),
351 m->bd->ulSlot, aIRQ, 255);
352
353 /* the machine needs to be mutable */
354 AutoMutableStateDependency adep(m->pMachine);
355 if (FAILED(adep.rc())) return adep.rc();
356
357 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
358
359 if (m->bd->ulIRQ != aIRQ)
360 {
361 m->bd.backup();
362 m->bd->ulIRQ = aIRQ;
363
364 m->fModified = true;
365 // leave the lock before informing callbacks
366 alock.release();
367
368 AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
369 m->pMachine->i_setModified(Machine::IsModified_SerialPorts);
370 mlock.release();
371
372 m->pMachine->i_onSerialPortChange(this);
373 }
374
375 return S_OK;
376}
377
378
379HRESULT SerialPort::getIOBase(ULONG *aIOBase)
380{
381 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
382
383 *aIOBase = m->bd->ulIOBase;
384
385 return S_OK;
386}
387
388HRESULT SerialPort::setIOBase(ULONG aIOBase)
389{
390 /* check IOBase limits
391 * (when changing this, make sure it corresponds to XML schema */
392 if (aIOBase > 0xFFFF)
393 return setError(E_INVALIDARG,
394 tr("Invalid I/O port base address of the serial port %d: %lu (must be in range [0, 0x%X])"),
395 m->bd->ulSlot, aIOBase, 0, 0xFFFF);
396
397 AutoCaller autoCaller(this);
398 if (FAILED(autoCaller.rc())) return autoCaller.rc();
399
400 /* the machine needs to be mutable */
401 AutoMutableStateDependency adep(m->pMachine);
402 if (FAILED(adep.rc())) return adep.rc();
403
404 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
405
406 HRESULT rc = S_OK;
407
408 if (m->bd->ulIOBase != aIOBase)
409 {
410 m->bd.backup();
411 m->bd->ulIOBase = aIOBase;
412
413 m->fModified = true;
414 // leave the lock before informing callbacks
415 alock.release();
416
417 AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
418 m->pMachine->i_setModified(Machine::IsModified_SerialPorts);
419 mlock.release();
420
421 m->pMachine->i_onSerialPortChange(this);
422 }
423
424 return rc;
425}
426
427HRESULT SerialPort::getPath(com::Utf8Str &aPath)
428{
429 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
430
431 aPath = m->bd->strPath;
432
433 return S_OK;
434}
435
436
437HRESULT SerialPort::setPath(const com::Utf8Str &aPath)
438{
439 /* the machine needs to be mutable */
440 AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine);
441 if (FAILED(adep.rc())) return adep.rc();
442
443 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
444
445 if (aPath != m->bd->strPath)
446 {
447 HRESULT rc = i_checkSetPath(aPath);
448 if (FAILED(rc)) return rc;
449
450 m->bd.backup();
451 m->bd->strPath = aPath;
452
453 m->fModified = true;
454 // leave the lock before informing callbacks
455 alock.release();
456
457 AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
458 m->pMachine->i_setModified(Machine::IsModified_SerialPorts);
459 mlock.release();
460
461 m->pMachine->i_onSerialPortChange(this);
462 }
463
464 return S_OK;
465}
466
467HRESULT SerialPort::getServer(BOOL *aServer)
468{
469 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
470
471 *aServer = m->bd->fServer;
472
473 return S_OK;
474}
475
476HRESULT SerialPort::setServer(BOOL aServer)
477{
478 /* the machine needs to be mutable */
479 AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine);
480 if (FAILED(adep.rc())) return adep.rc();
481
482 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
483
484 if (m->bd->fServer != RT_BOOL(aServer))
485 {
486 m->bd.backup();
487 m->bd->fServer = RT_BOOL(aServer);
488
489 m->fModified = true;
490 // leave the lock before informing callbacks
491 alock.release();
492
493 AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
494 m->pMachine->i_setModified(Machine::IsModified_SerialPorts);
495 mlock.release();
496
497 m->pMachine->i_onSerialPortChange(this);
498 }
499
500 return S_OK;
501}
502
503HRESULT SerialPort::getUartType(UartType_T *aUartType)
504{
505 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
506
507 *aUartType = m->bd->uartType;
508
509 return S_OK;
510}
511
512HRESULT SerialPort::setUartType(UartType_T aUartType)
513{
514 /* the machine needs to be mutable */
515 AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine);
516 if (FAILED(adep.rc())) return adep.rc();
517
518 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
519
520 if (m->bd->uartType != aUartType)
521 {
522 m->bd.backup();
523 m->bd->uartType = aUartType;
524
525 m->fModified = true;
526 // leave the lock before informing callbacks
527 alock.release();
528
529 AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
530 m->pMachine->i_setModified(Machine::IsModified_SerialPorts);
531 mlock.release();
532
533 m->pMachine->i_onSerialPortChange(this);
534 }
535
536 return S_OK;
537}
538
539// public methods only for internal purposes
540////////////////////////////////////////////////////////////////////////////////
541
542/**
543 * Loads settings from the given port node.
544 * May be called once right after this object creation.
545 *
546 * @param data Configuration settings.
547 *
548 * @note Locks this object for writing.
549 */
550HRESULT SerialPort::i_loadSettings(const settings::SerialPort &data)
551{
552
553 AutoCaller autoCaller(this);
554 AssertComRCReturnRC(autoCaller.rc());
555
556 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
557
558 // simply copy
559 *m->bd.data() = data;
560
561 return S_OK;
562}
563
564/**
565 * Saves the port settings to the given port node.
566 *
567 * Note that the given Port node is completely empty on input.
568 *
569 * @param data Configuration settings.
570 *
571 * @note Locks this object for reading.
572 */
573HRESULT SerialPort::i_saveSettings(settings::SerialPort &data)
574{
575 AutoCaller autoCaller(this);
576 AssertComRCReturnRC(autoCaller.rc());
577
578 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
579
580 // simply copy
581 data = *m->bd.data();
582
583 return S_OK;
584}
585
586/**
587 * Returns true if any setter method has modified settings of this instance.
588 * @return
589 */
590bool SerialPort::i_isModified()
591{
592 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
593 return m->fModified;
594}
595
596/**
597 * @note Locks this object for writing.
598 */
599void SerialPort::i_rollback()
600{
601 /* sanity */
602 AutoCaller autoCaller(this);
603 AssertComRCReturnVoid(autoCaller.rc());
604
605 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
606
607 m->bd.rollback();
608}
609
610/**
611 * @note Locks this object for writing, together with the peer object (also
612 * for writing) if there is one.
613 */
614void SerialPort::i_commit()
615{
616 /* sanity */
617 AutoCaller autoCaller(this);
618 AssertComRCReturnVoid(autoCaller.rc());
619
620 /* sanity too */
621 AutoCaller peerCaller(m->pPeer);
622 AssertComRCReturnVoid(peerCaller.rc());
623
624 /* lock both for writing since we modify both (pPeer is "master" so locked
625 * first) */
626 AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
627
628 if (m->bd.isBackedUp())
629 {
630 m->bd.commit();
631 if (m->pPeer)
632 {
633 /* attach new data to the peer and reshare it */
634 m->pPeer->m->bd.attach(m->bd);
635 }
636 }
637}
638
639/**
640 * @note Locks this object for writing, together with the peer object
641 * represented by @a aThat (locked for reading).
642 */
643void SerialPort::i_copyFrom(SerialPort *aThat)
644{
645 AssertReturnVoid(aThat != NULL);
646
647 /* sanity */
648 AutoCaller autoCaller(this);
649 AssertComRCReturnVoid(autoCaller.rc());
650
651 /* sanity too */
652 AutoCaller thatCaller(aThat);
653 AssertComRCReturnVoid(thatCaller.rc());
654
655 /* peer is not modified, lock it for reading (aThat is "master" so locked
656 * first) */
657 AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
658 AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
659
660 /* this will back up current data */
661 m->bd.assignCopy(aThat->m->bd);
662}
663
664/**
665 * Applies the defaults for this serial port.
666 *
667 * @note This method currently assumes that the object is in the state after
668 * calling init(), it does not set defaults from an arbitrary state.
669 */
670void SerialPort::i_applyDefaults(GuestOSType *aOsType)
671{
672 /* sanity */
673 AutoCaller autoCaller(this);
674 AssertComRCReturnVoid(autoCaller.rc());
675
676 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
677
678 /* Set some more defaults. */
679 switch (m->bd->ulSlot)
680 {
681 case 0:
682 {
683 m->bd->ulIOBase = 0x3f8;
684 m->bd->ulIRQ = 4;
685 break;
686 }
687 case 1:
688 {
689 m->bd->ulIOBase = 0x2f8;
690 m->bd->ulIRQ = 3;
691 break;
692 }
693 case 2:
694 {
695 m->bd->ulIOBase = 0x3e8;
696 m->bd->ulIRQ = 4;
697 break;
698 }
699 case 3:
700 {
701 m->bd->ulIOBase = 0x2e8;
702 m->bd->ulIRQ = 3;
703 break;
704 }
705 default:
706 AssertMsgFailed(("Serial port slot %u exceeds limit\n", m->bd->ulSlot));
707 break;
708 }
709
710 uint32_t numSerialEnabled = 0;
711 if (aOsType)
712 numSerialEnabled = aOsType->i_numSerialEnabled();
713
714 /* Enable port if requested */
715 if (m->bd->ulSlot < numSerialEnabled)
716 {
717 m->bd->fEnabled = true;
718 }
719}
720
721bool SerialPort::i_hasDefaults()
722{
723 /* sanity */
724 AutoCaller autoCaller(this);
725 AssertComRCReturn(autoCaller.rc(), true);
726
727 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
728
729 if ( !m->bd->fEnabled
730 && m->bd->portMode == PortMode_Disconnected
731 && !m->bd->fServer)
732 {
733 /* Could be default, check the IO base and IRQ. */
734 switch (m->bd->ulSlot)
735 {
736 case 0:
737 if (m->bd->ulIOBase == 0x3f8 && m->bd->ulIRQ == 4)
738 return true;
739 break;
740 case 1:
741 if (m->bd->ulIOBase == 0x2f8 && m->bd->ulIRQ == 3)
742 return true;
743 break;
744 case 2:
745 if (m->bd->ulIOBase == 0x3e8 && m->bd->ulIRQ == 4)
746 return true;
747 break;
748 case 3:
749 if (m->bd->ulIOBase == 0x2e8 && m->bd->ulIRQ == 3)
750 return true;
751 break;
752 default:
753 AssertMsgFailed(("Serial port slot %u exceeds limit\n", m->bd->ulSlot));
754 break;
755 }
756
757 /* Detect old-style defaults (0x3f8, irq 4) in any slot, they are still
758 * in place for many VMs created by old VirtualBox versions. */
759 if (m->bd->ulIOBase == 0x3f8 && m->bd->ulIRQ == 4)
760 return true;
761 }
762
763 return false;
764}
765
766/**
767 * Validates COMSETTER(Path) arguments.
768 */
769HRESULT SerialPort::i_checkSetPath(const Utf8Str &str)
770{
771 AssertReturn(isWriteLockOnCurrentThread(), E_FAIL);
772
773 if ( ( m->bd->portMode == PortMode_HostDevice
774 || m->bd->portMode == PortMode_HostPipe
775 || m->bd->portMode == PortMode_TCP
776 || m->bd->portMode == PortMode_RawFile
777 ) && str.isEmpty()
778 )
779 return setError(E_INVALIDARG,
780 tr("Path of the serial port %d may not be empty or null in "
781 "host pipe, host device or TCP mode"),
782 m->bd->ulSlot);
783
784 return S_OK;
785}
786
787/* vi: set tabstop=4 shiftwidth=4 expandtab: */
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