VirtualBox

source: vbox/trunk/src/VBox/Additions/common/VBoxGuest/VBoxGuest-win-pnp.cpp@ 69496

Last change on this file since 69496 was 69496, checked in by vboxsync, 7 years ago

*: scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.6 KB
Line 
1/* $Id: VBoxGuest-win-pnp.cpp 69496 2017-10-28 14:55:58Z vboxsync $ */
2/** @file
3 * VBoxGuest-win-pnp - Windows Plug'n'Play specifics.
4 */
5
6/*
7 * Copyright (C) 2010-2017 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 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include "VBoxGuest-win.h"
32#include "VBoxGuestInternal.h"
33#include <VBox/err.h>
34#include <VBox/log.h>
35#include <VBox/version.h>
36#include <VBox/VBoxGuestLib.h>
37#include <iprt/assert.h>
38
39
40/*********************************************************************************************************************************
41* Defined Constants And Macros *
42*********************************************************************************************************************************/
43RT_C_DECLS_BEGIN
44static NTSTATUS vgdrvNtSendIrpSynchronously(PDEVICE_OBJECT pDevObj, PIRP pIrp, BOOLEAN fStrict);
45static NTSTATUS vgdrvNtPnPIrpComplete(PDEVICE_OBJECT pDevObj, PIRP pIrp, PKEVENT pEvent);
46static VOID vgdrvNtShowDeviceResources(PCM_PARTIAL_RESOURCE_LIST pResourceList);
47RT_C_DECLS_END
48
49#ifdef ALLOC_PRAGMA
50# pragma alloc_text(PAGE, vgdrvNtPnP)
51# pragma alloc_text(PAGE, vgdrvNtPower)
52# pragma alloc_text(PAGE, vgdrvNtSendIrpSynchronously)
53# pragma alloc_text(PAGE, vgdrvNtShowDeviceResources)
54#endif
55
56
57/**
58 * Irp completion routine for PnP Irps we send.
59 *
60 * @returns NT status code.
61 * @param pDevObj Device object.
62 * @param pIrp Request packet.
63 * @param pEvent Semaphore.
64 */
65static NTSTATUS vgdrvNtPnpIrpComplete(PDEVICE_OBJECT pDevObj, PIRP pIrp, PKEVENT pEvent)
66{
67 RT_NOREF2(pDevObj, pIrp);
68 KeSetEvent(pEvent, 0, FALSE);
69 return STATUS_MORE_PROCESSING_REQUIRED;
70}
71
72
73/**
74 * Helper to send a PnP IRP and wait until it's done.
75 *
76 * @returns NT status code.
77 * @param pDevObj Device object.
78 * @param pIrp Request packet.
79 * @param fStrict When set, returns an error if the IRP gives an error.
80 */
81static NTSTATUS vgdrvNtSendIrpSynchronously(PDEVICE_OBJECT pDevObj, PIRP pIrp, BOOLEAN fStrict)
82{
83 KEVENT Event;
84
85 KeInitializeEvent(&Event, SynchronizationEvent, FALSE);
86
87 IoCopyCurrentIrpStackLocationToNext(pIrp);
88 IoSetCompletionRoutine(pIrp, (PIO_COMPLETION_ROUTINE)vgdrvNtPnpIrpComplete, &Event, TRUE, TRUE, TRUE);
89
90 NTSTATUS rc = IoCallDriver(pDevObj, pIrp);
91
92 if (rc == STATUS_PENDING)
93 {
94 KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
95 rc = pIrp->IoStatus.Status;
96 }
97
98 if ( !fStrict
99 && (rc == STATUS_NOT_SUPPORTED || rc == STATUS_INVALID_DEVICE_REQUEST))
100 {
101 rc = STATUS_SUCCESS;
102 }
103
104 Log(("vgdrvNtSendIrpSynchronously: Returning 0x%x\n", rc));
105 return rc;
106}
107
108
109/**
110 * PnP Request handler.
111 *
112 * @param pDevObj Device object.
113 * @param pIrp Request packet.
114 */
115NTSTATUS vgdrvNtPnP(PDEVICE_OBJECT pDevObj, PIRP pIrp)
116{
117 PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension;
118 PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
119
120#ifdef LOG_ENABLED
121 static char *s_apszFnctName[] =
122 {
123 "IRP_MN_START_DEVICE",
124 "IRP_MN_QUERY_REMOVE_DEVICE",
125 "IRP_MN_REMOVE_DEVICE",
126 "IRP_MN_CANCEL_REMOVE_DEVICE",
127 "IRP_MN_STOP_DEVICE",
128 "IRP_MN_QUERY_STOP_DEVICE",
129 "IRP_MN_CANCEL_STOP_DEVICE",
130 "IRP_MN_QUERY_DEVICE_RELATIONS",
131 "IRP_MN_QUERY_INTERFACE",
132 "IRP_MN_QUERY_CAPABILITIES",
133 "IRP_MN_QUERY_RESOURCES",
134 "IRP_MN_QUERY_RESOURCE_REQUIREMENTS",
135 "IRP_MN_QUERY_DEVICE_TEXT",
136 "IRP_MN_FILTER_RESOURCE_REQUIREMENTS",
137 "IRP_MN_0xE",
138 "IRP_MN_READ_CONFIG",
139 "IRP_MN_WRITE_CONFIG",
140 "IRP_MN_EJECT",
141 "IRP_MN_SET_LOCK",
142 "IRP_MN_QUERY_ID",
143 "IRP_MN_QUERY_PNP_DEVICE_STATE",
144 "IRP_MN_QUERY_BUS_INFORMATION",
145 "IRP_MN_DEVICE_USAGE_NOTIFICATION",
146 "IRP_MN_SURPRISE_REMOVAL",
147 };
148 Log(("vgdrvNtPnP: MinorFunction: %s\n",
149 pStack->MinorFunction < RT_ELEMENTS(s_apszFnctName) ? s_apszFnctName[pStack->MinorFunction] : "Unknown"));
150#endif
151
152 NTSTATUS rc = STATUS_SUCCESS;
153 switch (pStack->MinorFunction)
154 {
155 case IRP_MN_START_DEVICE:
156 {
157 Log(("vgdrvNtPnP: START_DEVICE\n"));
158
159 /* This must be handled first by the lower driver. */
160 rc = vgdrvNtSendIrpSynchronously(pDevExt->pNextLowerDriver, pIrp, TRUE);
161
162 if ( NT_SUCCESS(rc)
163 && NT_SUCCESS(pIrp->IoStatus.Status))
164 {
165 Log(("vgdrvNtPnP: START_DEVICE: pStack->Parameters.StartDevice.AllocatedResources = %p\n",
166 pStack->Parameters.StartDevice.AllocatedResources));
167
168 if (pStack->Parameters.StartDevice.AllocatedResources)
169 rc = vgdrvNtInit(pDevObj, pIrp);
170 else
171 {
172 Log(("vgdrvNtPnP: START_DEVICE: No resources, pDevExt = %p, nextLowerDriver = %p!\n",
173 pDevExt, pDevExt ? pDevExt->pNextLowerDriver : NULL));
174 rc = STATUS_UNSUCCESSFUL;
175 }
176 }
177
178 if (NT_ERROR(rc))
179 {
180 Log(("vgdrvNtPnP: START_DEVICE: Error: rc = 0x%x\n", rc));
181
182 /* Need to unmap memory in case of errors ... */
183/** @todo r=bird: vgdrvNtInit maps it and is responsible for cleaning up its own friggin mess...
184 * Fix it instead of kind of working around things there!! */
185 vgdrvNtUnmapVMMDevMemory(pDevExt);
186 }
187 break;
188 }
189
190 case IRP_MN_CANCEL_REMOVE_DEVICE:
191 {
192 Log(("vgdrvNtPnP: CANCEL_REMOVE_DEVICE\n"));
193
194 /* This must be handled first by the lower driver. */
195 rc = vgdrvNtSendIrpSynchronously(pDevExt->pNextLowerDriver, pIrp, TRUE);
196
197 if (NT_SUCCESS(rc) && pDevExt->enmDevState == VGDRVNTDEVSTATE_PENDINGREMOVE)
198 {
199 /* Return to the state prior to receiving the IRP_MN_QUERY_REMOVE_DEVICE request. */
200 pDevExt->enmDevState = pDevExt->enmPrevDevState;
201 }
202
203 /* Complete the IRP. */
204 break;
205 }
206
207 case IRP_MN_SURPRISE_REMOVAL:
208 {
209 Log(("vgdrvNtPnP: IRP_MN_SURPRISE_REMOVAL\n"));
210
211 VBOXGUEST_UPDATE_DEVSTATE(pDevExt, VGDRVNTDEVSTATE_SURPRISEREMOVED);
212
213 /* Do nothing here actually. Cleanup is done in IRP_MN_REMOVE_DEVICE.
214 * This request is not expected for VBoxGuest.
215 */
216 LogRel(("VBoxGuest: unexpected device removal\n"));
217
218 /* Pass to the lower driver. */
219 pIrp->IoStatus.Status = STATUS_SUCCESS;
220
221 IoSkipCurrentIrpStackLocation(pIrp);
222
223 rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp);
224
225 /* Do not complete the IRP. */
226 return rc;
227 }
228
229 case IRP_MN_QUERY_REMOVE_DEVICE:
230 {
231 Log(("vgdrvNtPnP: QUERY_REMOVE_DEVICE\n"));
232
233#ifdef VBOX_REBOOT_ON_UNINSTALL
234 Log(("vgdrvNtPnP: QUERY_REMOVE_DEVICE: Device cannot be removed without a reboot.\n"));
235 rc = STATUS_UNSUCCESSFUL;
236#endif
237
238 if (NT_SUCCESS(rc))
239 {
240 VBOXGUEST_UPDATE_DEVSTATE(pDevExt, VGDRVNTDEVSTATE_PENDINGREMOVE);
241
242 /* This IRP passed down to lower driver. */
243 pIrp->IoStatus.Status = STATUS_SUCCESS;
244
245 IoSkipCurrentIrpStackLocation(pIrp);
246
247 rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp);
248 Log(("vgdrvNtPnP: QUERY_REMOVE_DEVICE: Next lower driver replied rc = 0x%x\n", rc));
249
250 /* we must not do anything the IRP after doing IoSkip & CallDriver
251 * since the driver below us will complete (or already have completed) the IRP.
252 * I.e. just return the status we got from IoCallDriver */
253 return rc;
254 }
255
256 /* Complete the IRP on failure. */
257 break;
258 }
259
260 case IRP_MN_REMOVE_DEVICE:
261 {
262 Log(("vgdrvNtPnP: REMOVE_DEVICE\n"));
263
264 VBOXGUEST_UPDATE_DEVSTATE(pDevExt, VGDRVNTDEVSTATE_REMOVED);
265
266 /* Free hardware resources. */
267 /** @todo this should actually free I/O ports, interrupts, etc.
268 * Update/bird: vgdrvNtCleanup actually does that... So, what's there to do? */
269 rc = vgdrvNtCleanup(pDevObj);
270 Log(("vgdrvNtPnP: REMOVE_DEVICE: vgdrvNtCleanup rc = 0x%08X\n", rc));
271
272 /*
273 * We need to send the remove down the stack before we detach,
274 * but we don't need to wait for the completion of this operation
275 * (and to register a completion routine).
276 */
277 pIrp->IoStatus.Status = STATUS_SUCCESS;
278
279 IoSkipCurrentIrpStackLocation(pIrp);
280
281 rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp);
282 Log(("vgdrvNtPnP: REMOVE_DEVICE: Next lower driver replied rc = 0x%x\n", rc));
283
284 IoDetachDevice(pDevExt->pNextLowerDriver);
285
286 Log(("vgdrvNtPnP: REMOVE_DEVICE: Removing device ...\n"));
287
288 /* Destroy device extension and clean up everything else. */
289 VGDrvCommonDeleteDevExt(&pDevExt->Core);
290
291 /* Remove DOS device + symbolic link. */
292 UNICODE_STRING win32Name;
293 RtlInitUnicodeString(&win32Name, VBOXGUEST_DEVICE_NAME_DOS);
294 IoDeleteSymbolicLink(&win32Name);
295
296 Log(("vgdrvNtPnP: REMOVE_DEVICE: Deleting device ...\n"));
297
298 /* Last action: Delete our device! pDevObj is *not* failed
299 * anymore after this call! */
300 IoDeleteDevice(pDevObj);
301
302 Log(("vgdrvNtPnP: REMOVE_DEVICE: Device removed!\n"));
303
304 /* Propagating rc from IoCallDriver. */
305 return rc; /* Make sure that we don't do anything below here anymore! */
306 }
307
308 case IRP_MN_CANCEL_STOP_DEVICE:
309 {
310 Log(("vgdrvNtPnP: CANCEL_STOP_DEVICE\n"));
311
312 /* This must be handled first by the lower driver. */
313 rc = vgdrvNtSendIrpSynchronously(pDevExt->pNextLowerDriver, pIrp, TRUE);
314
315 if (NT_SUCCESS(rc) && pDevExt->enmDevState == VGDRVNTDEVSTATE_PENDINGSTOP)
316 {
317 /* Return to the state prior to receiving the IRP_MN_QUERY_STOP_DEVICE request. */
318 pDevExt->enmDevState = pDevExt->enmPrevDevState;
319 }
320
321 /* Complete the IRP. */
322 break;
323 }
324
325 case IRP_MN_QUERY_STOP_DEVICE:
326 {
327 Log(("vgdrvNtPnP: QUERY_STOP_DEVICE\n"));
328
329#ifdef VBOX_REBOOT_ON_UNINSTALL
330 Log(("vgdrvNtPnP: QUERY_STOP_DEVICE: Device cannot be stopped without a reboot!\n"));
331 pIrp->IoStatus.Status = STATUS_UNSUCCESSFUL;
332#endif
333
334 if (NT_SUCCESS(rc))
335 {
336 VBOXGUEST_UPDATE_DEVSTATE(pDevExt, VGDRVNTDEVSTATE_PENDINGSTOP);
337
338 /* This IRP passed down to lower driver. */
339 pIrp->IoStatus.Status = STATUS_SUCCESS;
340
341 IoSkipCurrentIrpStackLocation(pIrp);
342
343 rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp);
344 Log(("vgdrvNtPnP: QUERY_STOP_DEVICE: Next lower driver replied rc = 0x%x\n", rc));
345
346 /* we must not do anything with the IRP after doing IoSkip & CallDriver
347 * since the driver below us will complete (or already have completed) the IRP.
348 * I.e. just return the status we got from IoCallDriver */
349 return rc;
350 }
351
352 /* Complete the IRP on failure. */
353 break;
354 }
355
356 case IRP_MN_STOP_DEVICE:
357 {
358 Log(("vgdrvNtPnP: STOP_DEVICE\n"));
359
360 VBOXGUEST_UPDATE_DEVSTATE(pDevExt, VGDRVNTDEVSTATE_STOPPED);
361
362 /* Free hardware resources. */
363 /** @todo this should actually free I/O ports, interrupts, etc.
364 * Update/bird: vgdrvNtCleanup actually does that... So, what's there to do? */
365 rc = vgdrvNtCleanup(pDevObj);
366 Log(("vgdrvNtPnP: STOP_DEVICE: cleaning up, rc = 0x%x\n", rc));
367
368 /* Pass to the lower driver. */
369 pIrp->IoStatus.Status = STATUS_SUCCESS;
370
371 IoSkipCurrentIrpStackLocation(pIrp);
372
373 rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp);
374 Log(("vgdrvNtPnP: STOP_DEVICE: Next lower driver replied rc = 0x%x\n", rc));
375
376 return rc;
377 }
378
379 default:
380 {
381 IoSkipCurrentIrpStackLocation(pIrp);
382 rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp);
383 return rc;
384 }
385 }
386
387 pIrp->IoStatus.Status = rc;
388 IoCompleteRequest(pIrp, IO_NO_INCREMENT);
389
390 Log(("vgdrvNtPnP: Returning with rc = 0x%x\n", rc));
391 return rc;
392}
393
394
395/**
396 * Handle the power completion event.
397 *
398 * @returns NT status code.
399 * @param pDevObj Targetted device object.
400 * @param pIrp IO request packet.
401 * @param pContext Context value passed to IoSetCompletionRoutine in VBoxGuestPower.
402 */
403static NTSTATUS vgdrvNtPowerComplete(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp, IN PVOID pContext)
404{
405#ifdef VBOX_STRICT
406 RT_NOREF1(pDevObj);
407 PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pContext;
408 PIO_STACK_LOCATION pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
409
410 Assert(pDevExt);
411
412 if (pIrpSp)
413 {
414 Assert(pIrpSp->MajorFunction == IRP_MJ_POWER);
415 if (NT_SUCCESS(pIrp->IoStatus.Status))
416 {
417 switch (pIrpSp->MinorFunction)
418 {
419 case IRP_MN_SET_POWER:
420 switch (pIrpSp->Parameters.Power.Type)
421 {
422 case DevicePowerState:
423 switch (pIrpSp->Parameters.Power.State.DeviceState)
424 {
425 case PowerDeviceD0:
426 break;
427 default: /* Shut up MSC */ break;
428 }
429 break;
430 default: /* Shut up MSC */ break;
431 }
432 break;
433 }
434 }
435 }
436#else
437 RT_NOREF3(pDevObj, pIrp, pContext);
438#endif
439
440 return STATUS_SUCCESS;
441}
442
443
444/**
445 * Handle the Power requests.
446 *
447 * @returns NT status code
448 * @param pDevObj device object
449 * @param pIrp IRP
450 */
451NTSTATUS vgdrvNtPower(PDEVICE_OBJECT pDevObj, PIRP pIrp)
452{
453 PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
454 PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension;
455 POWER_STATE_TYPE enmPowerType = pStack->Parameters.Power.Type;
456 POWER_STATE PowerState = pStack->Parameters.Power.State;
457 POWER_ACTION enmPowerAction = pStack->Parameters.Power.ShutdownType;
458
459 Log(("vgdrvNtPower:\n"));
460
461 switch (pStack->MinorFunction)
462 {
463 case IRP_MN_SET_POWER:
464 {
465 Log(("vgdrvNtPower: IRP_MN_SET_POWER, type= %d\n", enmPowerType));
466 switch (enmPowerType)
467 {
468 case SystemPowerState:
469 {
470 Log(("vgdrvNtPower: SystemPowerState, action = %d, state = %d/%d\n",
471 enmPowerAction, PowerState.SystemState, PowerState.DeviceState));
472
473 switch (enmPowerAction)
474 {
475 case PowerActionSleep:
476
477 /* System now is in a working state. */
478 if (PowerState.SystemState == PowerSystemWorking)
479 {
480 if ( pDevExt
481 && pDevExt->LastSystemPowerAction == PowerActionHibernate)
482 {
483 Log(("vgdrvNtPower: Returning from hibernation!\n"));
484 int rc = VGDrvCommonReinitDevExtAfterHibernation(&pDevExt->Core,
485 vgdrvNtVersionToOSType(g_enmVGDrvNtVer));
486 if (RT_FAILURE(rc))
487 Log(("vgdrvNtPower: Cannot re-init VMMDev chain, rc = %d!\n", rc));
488 }
489 }
490 break;
491
492 case PowerActionShutdownReset:
493 {
494 Log(("vgdrvNtPower: Power action reset!\n"));
495
496 /* Tell the VMM that we no longer support mouse pointer integration. */
497 VMMDevReqMouseStatus *pReq = NULL;
498 int vrc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof (VMMDevReqMouseStatus),
499 VMMDevReq_SetMouseStatus);
500 if (RT_SUCCESS(vrc))
501 {
502 pReq->mouseFeatures = 0;
503 pReq->pointerXPos = 0;
504 pReq->pointerYPos = 0;
505
506 vrc = VbglR0GRPerform(&pReq->header);
507 if (RT_FAILURE(vrc))
508 {
509 Log(("vgdrvNtPower: error communicating new power status to VMMDev. vrc = %Rrc\n", vrc));
510 }
511
512 VbglR0GRFree(&pReq->header);
513 }
514
515 /* Don't do any cleanup here; there might be still coming in some IOCtls after we got this
516 * power action and would assert/crash when we already cleaned up all the stuff! */
517 break;
518 }
519
520 case PowerActionShutdown:
521 case PowerActionShutdownOff:
522 {
523 Log(("vgdrvNtPower: Power action shutdown!\n"));
524 if (PowerState.SystemState >= PowerSystemShutdown)
525 {
526 Log(("vgdrvNtPower: Telling the VMMDev to close the VM ...\n"));
527
528 VMMDevPowerStateRequest *pReq = pDevExt->pPowerStateRequest;
529 int vrc = VERR_NOT_IMPLEMENTED;
530 if (pReq)
531 {
532 pReq->header.requestType = VMMDevReq_SetPowerStatus;
533 pReq->powerState = VMMDevPowerState_PowerOff;
534
535 vrc = VbglR0GRPerform(&pReq->header);
536 }
537 if (RT_FAILURE(vrc))
538 Log(("vgdrvNtPower: Error communicating new power status to VMMDev. vrc = %Rrc\n", vrc));
539
540 /* No need to do cleanup here; at this point we should've been
541 * turned off by VMMDev already! */
542 }
543 break;
544 }
545
546 case PowerActionHibernate:
547
548 Log(("vgdrvNtPower: Power action hibernate!\n"));
549 break;
550
551 case PowerActionWarmEject:
552 Log(("vgdrvNtPower: PowerActionWarmEject!\n"));
553 break;
554
555 default:
556 Log(("vgdrvNtPower: %d\n", enmPowerAction));
557 break;
558 }
559
560 /*
561 * Save the current system power action for later use.
562 * This becomes handy when we return from hibernation for example.
563 */
564 if (pDevExt)
565 pDevExt->LastSystemPowerAction = enmPowerAction;
566
567 break;
568 }
569 default:
570 break;
571 }
572 break;
573 }
574 default:
575 break;
576 }
577
578 /*
579 * Whether we are completing or relaying this power IRP,
580 * we must call PoStartNextPowerIrp.
581 */
582 PoStartNextPowerIrp(pIrp);
583
584 /*
585 * Send the IRP down the driver stack, using PoCallDriver
586 * (not IoCallDriver, as for non-power irps).
587 */
588 IoCopyCurrentIrpStackLocationToNext(pIrp);
589 IoSetCompletionRoutine(pIrp,
590 vgdrvNtPowerComplete,
591 (PVOID)pDevExt,
592 TRUE,
593 TRUE,
594 TRUE);
595 return PoCallDriver(pDevExt->pNextLowerDriver, pIrp);
596}
597
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