71f11ba42b6227526966371f9218eafe28192fc6
[reactos.git] / reactos / ntoskrnl / io / plugplay.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/io/plugplay.c
5 * PURPOSE: Plug-and-play interface routines
6 *
7 * PROGRAMMERS: Eric Kohl <eric.kohl@t-online.de>
8 */
9
10 /* INCLUDES *****************************************************************/
11
12 #include <ntoskrnl.h>
13
14 #define NDEBUG
15 #include <internal/debug.h>
16
17
18 typedef struct _PNP_EVENT_ENTRY
19 {
20 LIST_ENTRY ListEntry;
21 PLUGPLAY_EVENT_BLOCK Event;
22 } PNP_EVENT_ENTRY, *PPNP_EVENT_ENTRY;
23
24
25 /* GLOBALS *******************************************************************/
26
27 static LIST_ENTRY IopPnpEventQueueHead;
28 static KEVENT IopPnpNotifyEvent;
29
30 /* FUNCTIONS *****************************************************************/
31
32 NTSTATUS INIT_FUNCTION
33 IopInitPlugPlayEvents(VOID)
34 {
35 InitializeListHead(&IopPnpEventQueueHead);
36
37 KeInitializeEvent(&IopPnpNotifyEvent,
38 SynchronizationEvent,
39 FALSE);
40
41 return STATUS_SUCCESS;
42 }
43
44
45 NTSTATUS
46 IopQueueTargetDeviceEvent(const GUID *Guid,
47 PUNICODE_STRING DeviceIds)
48 {
49 PPNP_EVENT_ENTRY EventEntry;
50 DWORD TotalSize;
51
52 TotalSize =
53 FIELD_OFFSET(PLUGPLAY_EVENT_BLOCK, TargetDevice.DeviceIds) +
54 DeviceIds->MaximumLength;
55
56 EventEntry = ExAllocatePool(NonPagedPool,
57 TotalSize + FIELD_OFFSET(PNP_EVENT_ENTRY, Event));
58 if (EventEntry == NULL)
59 return STATUS_INSUFFICIENT_RESOURCES;
60
61 memcpy(&EventEntry->Event.EventGuid,
62 Guid,
63 sizeof(GUID));
64 EventEntry->Event.EventCategory = TargetDeviceChangeEvent;
65 EventEntry->Event.TotalSize = TotalSize;
66
67 memcpy(&EventEntry->Event.TargetDevice.DeviceIds,
68 DeviceIds->Buffer,
69 DeviceIds->MaximumLength);
70
71 InsertHeadList(&IopPnpEventQueueHead,
72 &EventEntry->ListEntry);
73 KeSetEvent(&IopPnpNotifyEvent,
74 0,
75 FALSE);
76
77 return STATUS_SUCCESS;
78 }
79
80
81 /*
82 * Remove the current PnP event from the tail of the event queue
83 * and signal IopPnpNotifyEvent if there is yet another event in the queue.
84 */
85 static NTSTATUS
86 IopRemovePlugPlayEvent(VOID)
87 {
88 /* Remove a pnp event entry from the tail of the queue */
89 if (!IsListEmpty(&IopPnpEventQueueHead))
90 {
91 ExFreePool(RemoveTailList(&IopPnpEventQueueHead));
92 }
93
94 /* Signal the next pnp event in the queue */
95 if (!IsListEmpty(&IopPnpEventQueueHead))
96 {
97 KeSetEvent(&IopPnpNotifyEvent,
98 0,
99 FALSE);
100 }
101
102 return STATUS_SUCCESS;
103 }
104
105
106 /*
107 * Plug and Play event structure used by NtGetPlugPlayEvent.
108 *
109 * EventGuid
110 * Can be one of the following values:
111 * GUID_HWPROFILE_QUERY_CHANGE
112 * GUID_HWPROFILE_CHANGE_CANCELLED
113 * GUID_HWPROFILE_CHANGE_COMPLETE
114 * GUID_TARGET_DEVICE_QUERY_REMOVE
115 * GUID_TARGET_DEVICE_REMOVE_CANCELLED
116 * GUID_TARGET_DEVICE_REMOVE_COMPLETE
117 * GUID_PNP_CUSTOM_NOTIFICATION
118 * GUID_PNP_POWER_NOTIFICATION
119 * GUID_DEVICE_* (see above)
120 *
121 * EventCategory
122 * Type of the event that happened.
123 *
124 * Result
125 * ?
126 *
127 * Flags
128 * ?
129 *
130 * TotalSize
131 * Size of the event block including the device IDs and other
132 * per category specific fields.
133 */
134 /*
135 * NtGetPlugPlayEvent
136 *
137 * Returns one Plug & Play event from a global queue.
138 *
139 * Parameters
140 * Reserved1
141 * Reserved2
142 * Always set to zero.
143 *
144 * Buffer
145 * The buffer that will be filled with the event information on
146 * successful return from the function.
147 *
148 * BufferSize
149 * Size of the buffer pointed by the Buffer parameter. If the
150 * buffer size is not large enough to hold the whole event
151 * information, error STATUS_BUFFER_TOO_SMALL is returned and
152 * the buffer remains untouched.
153 *
154 * Return Values
155 * STATUS_PRIVILEGE_NOT_HELD
156 * STATUS_BUFFER_TOO_SMALL
157 * STATUS_SUCCESS
158 *
159 * Remarks
160 * This function isn't multi-thread safe!
161 *
162 * @implemented
163 */
164 NTSTATUS STDCALL
165 NtGetPlugPlayEvent(IN ULONG Reserved1,
166 IN ULONG Reserved2,
167 OUT PPLUGPLAY_EVENT_BLOCK Buffer,
168 IN ULONG BufferSize)
169 {
170 PPNP_EVENT_ENTRY Entry;
171 NTSTATUS Status;
172
173 DPRINT("NtGetPlugPlayEvent() called\n");
174
175 /* Function can only be called from user-mode */
176 if (KeGetPreviousMode() != UserMode)
177 {
178 DPRINT1("NtGetPlugPlayEvent cannot be called from kernel mode!\n");
179 return STATUS_ACCESS_DENIED;
180 }
181
182 /* Check for Tcb privilege */
183 if (!SeSinglePrivilegeCheck(SeTcbPrivilege,
184 UserMode))
185 {
186 DPRINT1("NtGetPlugPlayEvent: Caller does not hold the SeTcbPrivilege privilege!\n");
187 return STATUS_PRIVILEGE_NOT_HELD;
188 }
189
190 /* Wait for a PnP event */
191 DPRINT("Waiting for pnp notification event\n");
192 Status = KeWaitForSingleObject(&IopPnpNotifyEvent,
193 UserRequest,
194 KernelMode,
195 FALSE,
196 NULL);
197 if (!NT_SUCCESS(Status))
198 {
199 DPRINT1("KeWaitForSingleObject() failed (Status %lx)\n", Status);
200 return Status;
201 }
202
203 /* Get entry from the tail of the queue */
204 Entry = CONTAINING_RECORD(IopPnpEventQueueHead.Blink,
205 PNP_EVENT_ENTRY,
206 ListEntry);
207
208 /* Check the buffer size */
209 if (BufferSize < Entry->Event.TotalSize)
210 {
211 DPRINT1("Buffer is too small for the pnp-event\n");
212 return STATUS_BUFFER_TOO_SMALL;
213 }
214
215 /* Copy event data to the user buffer */
216 memcpy(Buffer,
217 &Entry->Event,
218 Entry->Event.TotalSize);
219
220 DPRINT("NtGetPlugPlayEvent() done\n");
221
222 return STATUS_SUCCESS;
223 }
224
225
226 static PDEVICE_OBJECT
227 IopTraverseDeviceNode(PDEVICE_NODE Node, PUNICODE_STRING DeviceInstance)
228 {
229 PDEVICE_OBJECT DeviceObject;
230 PDEVICE_NODE ChildNode;
231
232 if (RtlEqualUnicodeString(&Node->InstancePath,
233 DeviceInstance, TRUE))
234 return Node->PhysicalDeviceObject;
235
236 /* Traversal of all children nodes */
237 for (ChildNode = Node->Child;
238 ChildNode != NULL;
239 ChildNode = ChildNode->NextSibling)
240 {
241 DeviceObject = IopTraverseDeviceNode(ChildNode, DeviceInstance);
242 if (DeviceObject != NULL)
243 {
244 return DeviceObject;
245 }
246 }
247
248 return NULL;
249 }
250
251
252 static PDEVICE_OBJECT
253 IopGetDeviceObjectFromDeviceInstance(PUNICODE_STRING DeviceInstance)
254 {
255 #if 0
256 OBJECT_ATTRIBUTES ObjectAttributes;
257 UNICODE_STRING KeyName, ValueName;
258 LPWSTR KeyNameBuffer;
259 HANDLE InstanceKeyHandle;
260 HANDLE ControlKeyHandle;
261 NTSTATUS Status;
262 PKEY_VALUE_PARTIAL_INFORMATION ValueInformation;
263 ULONG ValueInformationLength;
264 PDEVICE_OBJECT DeviceObject = NULL;
265
266 DPRINT("IopGetDeviceObjectFromDeviceInstance(%wZ) called\n", DeviceInstance);
267
268 KeyNameBuffer = ExAllocatePool(PagedPool,
269 (49 * sizeof(WCHAR)) + DeviceInstance->Length);
270 if (KeyNameBuffer == NULL)
271 {
272 DPRINT1("Failed to allocate key name buffer!\n");
273 return NULL;
274 }
275
276 wcscpy(KeyNameBuffer, L"\\Registry\\Machine\\System\\CurrentControlSet\\Enum\\");
277 wcscat(KeyNameBuffer, DeviceInstance->Buffer);
278
279 RtlInitUnicodeString(&KeyName,
280 KeyNameBuffer);
281 InitializeObjectAttributes(&ObjectAttributes,
282 &KeyName,
283 OBJ_CASE_INSENSITIVE,
284 NULL,
285 NULL);
286
287 Status = ZwOpenKey(&InstanceKeyHandle,
288 KEY_READ,
289 &ObjectAttributes);
290 ExFreePool(KeyNameBuffer);
291 if (!NT_SUCCESS(Status))
292 {
293 DPRINT1("Failed to open the instance key (Status %lx)\n", Status);
294 return NULL;
295 }
296
297 /* Open the 'Control' subkey */
298 RtlInitUnicodeString(&KeyName,
299 L"Control");
300 InitializeObjectAttributes(&ObjectAttributes,
301 &KeyName,
302 OBJ_CASE_INSENSITIVE,
303 InstanceKeyHandle,
304 NULL);
305
306 Status = ZwOpenKey(&ControlKeyHandle,
307 KEY_READ,
308 &ObjectAttributes);
309 ZwClose(InstanceKeyHandle);
310 if (!NT_SUCCESS(Status))
311 {
312 DPRINT1("Failed to open the 'Control' key (Status %lx)\n", Status);
313 return NULL;
314 }
315
316 /* Query the 'DeviceReference' value */
317 RtlInitUnicodeString(&ValueName,
318 L"DeviceReference");
319 ValueInformationLength = FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,
320 Data[0]) + sizeof(ULONG);
321 ValueInformation = ExAllocatePool(PagedPool, ValueInformationLength);
322 if (ValueInformation == NULL)
323 {
324 DPRINT1("Failed to allocate the name information buffer!\n");
325 ZwClose(ControlKeyHandle);
326 return NULL;
327 }
328
329 Status = ZwQueryValueKey(ControlKeyHandle,
330 &ValueName,
331 KeyValuePartialInformation,
332 ValueInformation,
333 ValueInformationLength,
334 &ValueInformationLength);
335 ZwClose(ControlKeyHandle);
336 if (!NT_SUCCESS(Status))
337 {
338 DPRINT1("Failed to query the 'DeviceReference' value (Status %lx)\n", Status);
339 return NULL;
340 }
341
342 /* Check the device object */
343 RtlCopyMemory(&DeviceObject,
344 ValueInformation->Data,
345 sizeof(PDEVICE_OBJECT));
346
347 DPRINT("DeviceObject: %p\n", DeviceObject);
348
349 if (DeviceObject->Type != IO_TYPE_DEVICE ||
350 DeviceObject->DeviceObjectExtension == NULL ||
351 DeviceObject->DeviceObjectExtension->DeviceNode == NULL ||
352 !RtlEqualUnicodeString(&DeviceObject->DeviceObjectExtension->DeviceNode->InstancePath,
353 DeviceInstance, TRUE))
354 {
355 DPRINT1("Invalid object type!\n");
356 return NULL;
357 }
358
359 DPRINT("Instance path: %wZ\n", &DeviceObject->DeviceObjectExtension->DeviceNode->InstancePath);
360
361 ObReferenceObject(DeviceObject);
362
363 DPRINT("IopGetDeviceObjectFromDeviceInstance() done\n");
364
365 return DeviceObject;
366 #endif
367
368 if (IopRootDeviceNode == NULL)
369 return NULL;
370
371 if (DeviceInstance == NULL ||
372 DeviceInstance->Length == 0
373 )
374 {
375 if (IopRootDeviceNode->PhysicalDeviceObject)
376 {
377 ObReferenceObject(IopRootDeviceNode->PhysicalDeviceObject);
378 return IopRootDeviceNode->PhysicalDeviceObject;
379 }
380 else
381 return NULL;
382 }
383
384 return IopTraverseDeviceNode(IopRootDeviceNode, DeviceInstance);
385
386 }
387
388
389
390 static NTSTATUS
391 IopGetDeviceProperty(PPLUGPLAY_CONTROL_PROPERTY_DATA PropertyData)
392 {
393 PDEVICE_OBJECT DeviceObject = NULL;
394 NTSTATUS Status;
395
396 DPRINT("IopGetDeviceProperty() called\n");
397 DPRINT("Device name: %wZ\n", &PropertyData->DeviceInstance);
398
399 /* Get the device object */
400 DeviceObject = IopGetDeviceObjectFromDeviceInstance(&PropertyData->DeviceInstance);
401 if (DeviceObject == NULL)
402 return STATUS_NO_SUCH_DEVICE;
403
404 Status = IoGetDeviceProperty(DeviceObject,
405 PropertyData->Property,
406 PropertyData->BufferSize,
407 PropertyData->Buffer,
408 &PropertyData->BufferSize);
409
410 ObDereferenceObject(DeviceObject);
411
412 return Status;
413 }
414
415
416 static NTSTATUS
417 IopGetRelatedDevice(PPLUGPLAY_CONTROL_RELATED_DEVICE_DATA RelatedDeviceData)
418 {
419 UNICODE_STRING RootDeviceName;
420 PDEVICE_OBJECT DeviceObject = NULL;
421 PDEVICE_NODE DeviceNode = NULL;
422 PDEVICE_NODE RelatedDeviceNode;
423
424 DPRINT("IopGetRelatedDevice() called\n");
425 DPRINT("Device name: %wZ\n", &RelatedDeviceData->TargetDeviceInstance);
426
427 RtlInitUnicodeString(&RootDeviceName,
428 L"HTREE\\ROOT\\0");
429 if (RtlEqualUnicodeString(&RelatedDeviceData->TargetDeviceInstance,
430 &RootDeviceName,
431 TRUE))
432 {
433 DeviceNode = IopRootDeviceNode;
434 }
435 else
436 {
437 /* Get the device object */
438 DeviceObject = IopGetDeviceObjectFromDeviceInstance(&RelatedDeviceData->TargetDeviceInstance);
439 if (DeviceObject == NULL)
440 return STATUS_NO_SUCH_DEVICE;
441
442 DeviceNode = DeviceObject->DeviceObjectExtension->DeviceNode;
443 }
444
445 switch (RelatedDeviceData->Relation)
446 {
447 case PNP_GET_PARENT_DEVICE:
448 RelatedDeviceNode = DeviceNode->Parent;
449 break;
450
451 case PNP_GET_CHILD_DEVICE:
452 RelatedDeviceNode = DeviceNode->Child;
453 break;
454
455 case PNP_GET_SIBLING_DEVICE:
456 RelatedDeviceNode = DeviceNode->NextSibling;
457 break;
458
459 default:
460 if (DeviceObject != NULL)
461 {
462 ObDereferenceObject(DeviceObject);
463 }
464
465 return STATUS_INVALID_PARAMETER;
466 }
467
468 if (RelatedDeviceNode == NULL)
469 {
470 if (DeviceObject)
471 {
472 ObDereferenceObject(DeviceObject);
473 }
474
475 return STATUS_NO_SUCH_DEVICE;
476 }
477
478 if (RelatedDeviceNode->InstancePath.Length >
479 RelatedDeviceData->RelatedDeviceInstance.MaximumLength)
480 {
481 if (DeviceObject)
482 {
483 ObDereferenceObject(DeviceObject);
484 }
485
486 return STATUS_BUFFER_TOO_SMALL;
487 }
488
489 /* Copy related device instance name */
490 RtlCopyMemory(RelatedDeviceData->RelatedDeviceInstance.Buffer,
491 RelatedDeviceNode->InstancePath.Buffer,
492 RelatedDeviceNode->InstancePath.Length);
493 RelatedDeviceData->RelatedDeviceInstance.Length =
494 RelatedDeviceNode->InstancePath.Length;
495
496 if (DeviceObject != NULL)
497 {
498 ObDereferenceObject(DeviceObject);
499 }
500
501 DPRINT("IopGetRelatedDevice() done\n");
502
503 return STATUS_SUCCESS;
504 }
505
506
507 static NTSTATUS
508 IopDeviceStatus(PPLUGPLAY_CONTROL_STATUS_DATA StatusData)
509 {
510 PDEVICE_OBJECT DeviceObject;
511 PDEVICE_NODE DeviceNode;
512
513 DPRINT("IopDeviceStatus() called\n");
514 DPRINT("Device name: %wZ\n", &StatusData->DeviceInstance);
515
516 /* Get the device object */
517 DeviceObject = IopGetDeviceObjectFromDeviceInstance(&StatusData->DeviceInstance);
518 if (DeviceObject == NULL)
519 return STATUS_NO_SUCH_DEVICE;
520
521 DeviceNode = DeviceObject->DeviceObjectExtension->DeviceNode;
522
523 switch (StatusData->Operation)
524 {
525 case PNP_GET_DEVICE_STATUS:
526 DPRINT("Get status data\n");
527 StatusData->DeviceStatus = DeviceNode->Flags;
528 StatusData->DeviceProblem = DeviceNode->Problem;
529 break;
530
531 case PNP_SET_DEVICE_STATUS:
532 DPRINT("Set status data\n");
533 DeviceNode->Flags = StatusData->DeviceStatus;
534 DeviceNode->Problem = StatusData->DeviceProblem;
535 break;
536
537 case PNP_CLEAR_DEVICE_STATUS:
538 DPRINT1("FIXME: Clear status data!\n");
539 break;
540 }
541
542 ObDereferenceObject(DeviceObject);
543
544 return STATUS_SUCCESS;
545 }
546
547
548 static NTSTATUS
549 IopGetDeviceDepth(PPLUGPLAY_CONTROL_DEPTH_DATA DepthData)
550 {
551 PDEVICE_OBJECT DeviceObject;
552 PDEVICE_NODE DeviceNode;
553
554 DPRINT("IopGetDeviceDepth() called\n");
555 DPRINT("Device name: %wZ\n", &DepthData->DeviceInstance);
556
557 /* Get the device object */
558 DeviceObject = IopGetDeviceObjectFromDeviceInstance(&DepthData->DeviceInstance);
559 if (DeviceObject == NULL)
560 return STATUS_NO_SUCH_DEVICE;
561
562 DeviceNode = DeviceObject->DeviceObjectExtension->DeviceNode;
563
564 DepthData->Depth = DeviceNode->Level;
565
566 ObDereferenceObject(DeviceObject);
567
568 return STATUS_SUCCESS;
569 }
570
571
572 /*
573 * NtPlugPlayControl
574 *
575 * A function for doing various Plug & Play operations from user mode.
576 *
577 * Parameters
578 * PlugPlayControlClass
579 * 0x00 Reenumerate device tree
580 *
581 * Buffer points to UNICODE_STRING decribing the instance
582 * path (like "HTREE\ROOT\0" or "Root\ACPI_HAL\0000"). For
583 * more information about instance paths see !devnode command
584 * in kernel debugger or look at "Inside Windows 2000" book,
585 * chapter "Driver Loading, Initialization, and Installation".
586 *
587 * 0x01 Register new device
588 * 0x02 Deregister device
589 * 0x03 Initialize device
590 * 0x04 Start device
591 * 0x06 Query and remove device
592 * 0x07 User response
593 *
594 * Called after processing the message from NtGetPlugPlayEvent.
595 *
596 * 0x08 Generate legacy device
597 * 0x09 Get interface device list
598 * 0x0A Get property data
599 * 0x0B Device class association (Registration)
600 * 0x0C Get related device
601 * 0x0D Get device interface alias
602 * 0x0E Get/set/clear device status
603 * 0x0F Get device depth
604 * 0x10 Query device relations
605 * 0x11 Query target device relation
606 * 0x12 Query conflict list
607 * 0x13 Retrieve dock data
608 * 0x14 Reset device
609 * 0x15 Halt device
610 * 0x16 Get blocked driver data
611 *
612 * Buffer
613 * The buffer contains information that is specific to each control
614 * code. The buffer is read-only.
615 *
616 * BufferSize
617 * Size of the buffer pointed by the Buffer parameter. If the
618 * buffer size specifies incorrect value for specified control
619 * code, error ??? is returned.
620 *
621 * Return Values
622 * STATUS_PRIVILEGE_NOT_HELD
623 * STATUS_SUCCESS
624 * ...
625 *
626 * @unimplemented
627 */
628 NTSTATUS STDCALL
629 NtPlugPlayControl(IN PLUGPLAY_CONTROL_CLASS PlugPlayControlClass,
630 IN OUT PVOID Buffer,
631 IN ULONG BufferLength)
632 {
633 NTSTATUS Status = STATUS_SUCCESS;
634
635 DPRINT("NtPlugPlayControl(%lu %p %lu) called\n",
636 PlugPlayControlClass, Buffer, BufferLength);
637
638 /* Function can only be called from user-mode */
639 if (KeGetPreviousMode() != UserMode)
640 {
641 DPRINT1("NtGetPlugPlayEvent cannot be called from kernel mode!\n");
642 return STATUS_ACCESS_DENIED;
643 }
644
645 /* Check for Tcb privilege */
646 if (!SeSinglePrivilegeCheck(SeTcbPrivilege,
647 UserMode))
648 {
649 DPRINT1("NtGetPlugPlayEvent: Caller does not hold the SeTcbPrivilege privilege!\n");
650 return STATUS_PRIVILEGE_NOT_HELD;
651 }
652
653 /* Probe the buffer */
654 _SEH_TRY
655 {
656 ProbeForWrite(Buffer,
657 BufferLength,
658 sizeof(ULONG));
659 }
660 _SEH_HANDLE
661 {
662 Status = _SEH_GetExceptionCode();
663 }
664 _SEH_END;
665
666 if (!NT_SUCCESS(Status))
667 {
668 return Status;
669 }
670
671 switch (PlugPlayControlClass)
672 {
673 case PlugPlayControlUserResponse:
674 if (Buffer || BufferLength != 0)
675 return STATUS_INVALID_PARAMETER;
676 return IopRemovePlugPlayEvent();
677
678 case PlugPlayControlProperty:
679 if (!Buffer || BufferLength < sizeof(PLUGPLAY_CONTROL_PROPERTY_DATA))
680 return STATUS_INVALID_PARAMETER;
681 return IopGetDeviceProperty((PPLUGPLAY_CONTROL_PROPERTY_DATA)Buffer);
682
683 case PlugPlayControlGetRelatedDevice:
684 if (!Buffer || BufferLength < sizeof(PLUGPLAY_CONTROL_RELATED_DEVICE_DATA))
685 return STATUS_INVALID_PARAMETER;
686 return IopGetRelatedDevice((PPLUGPLAY_CONTROL_RELATED_DEVICE_DATA)Buffer);
687
688 case PlugPlayControlDeviceStatus:
689 if (!Buffer || BufferLength < sizeof(PLUGPLAY_CONTROL_STATUS_DATA))
690 return STATUS_INVALID_PARAMETER;
691 return IopDeviceStatus((PPLUGPLAY_CONTROL_STATUS_DATA)Buffer);
692
693 case PlugPlayControlGetDeviceDepth:
694 if (!Buffer || BufferLength < sizeof(PLUGPLAY_CONTROL_DEPTH_DATA))
695 return STATUS_INVALID_PARAMETER;
696 return IopGetDeviceDepth((PPLUGPLAY_CONTROL_DEPTH_DATA)Buffer);
697
698 default:
699 return STATUS_NOT_IMPLEMENTED;
700 }
701
702 return STATUS_NOT_IMPLEMENTED;
703 }
704
705 /* EOF */