31be0d2da7085405a035984c16c658fea878a015
[reactos.git] / reactos / ntoskrnl / ob / namespc.c
1 /* $Id$
2 *
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS kernel
5 * FILE: ntoskrnl/ob/namespc.c
6 * PURPOSE: Manages the system namespace
7 *
8 * PROGRAMMERS: David Welch (welch@mcmail.com)
9 */
10
11 /* INCLUDES ***************************************************************/
12
13 #include <ntoskrnl.h>
14 #define NDEBUG
15 #include <internal/debug.h>
16
17 extern ULONG NtGlobalFlag;
18
19 /* GLOBALS ****************************************************************/
20
21 POBJECT_TYPE ObDirectoryType = NULL;
22 POBJECT_TYPE ObTypeObjectType = NULL;
23
24 PDIRECTORY_OBJECT NameSpaceRoot = NULL;
25 PDIRECTORY_OBJECT ObpTypeDirectoryObject = NULL;
26 /* FIXME: Move this somewhere else once devicemap support is in */
27 PDEVICE_MAP ObSystemDeviceMap = NULL;
28 KEVENT ObpDefaultObject;
29
30 static GENERIC_MAPPING ObpDirectoryMapping = {
31 STANDARD_RIGHTS_READ|DIRECTORY_QUERY|DIRECTORY_TRAVERSE,
32 STANDARD_RIGHTS_WRITE|DIRECTORY_CREATE_OBJECT|DIRECTORY_CREATE_SUBDIRECTORY,
33 STANDARD_RIGHTS_EXECUTE|DIRECTORY_QUERY|DIRECTORY_TRAVERSE,
34 DIRECTORY_ALL_ACCESS};
35
36 static GENERIC_MAPPING ObpTypeMapping = {
37 STANDARD_RIGHTS_READ,
38 STANDARD_RIGHTS_WRITE,
39 STANDARD_RIGHTS_EXECUTE,
40 0x000F0001};
41
42 NTSTATUS
43 STDCALL
44 ObpAllocateObject(POBJECT_CREATE_INFORMATION ObjectCreateInfo,
45 PUNICODE_STRING ObjectName,
46 POBJECT_TYPE ObjectType,
47 ULONG ObjectSize,
48 POBJECT_HEADER *ObjectHeader);
49
50 /* FUNCTIONS **************************************************************/
51
52 /*
53 * @implemented
54 */
55 NTSTATUS STDCALL
56 ObReferenceObjectByName(PUNICODE_STRING ObjectPath,
57 ULONG Attributes,
58 PACCESS_STATE PassedAccessState,
59 ACCESS_MASK DesiredAccess,
60 POBJECT_TYPE ObjectType,
61 KPROCESSOR_MODE AccessMode,
62 PVOID ParseContext,
63 PVOID* ObjectPtr)
64 {
65 PVOID Object = NULL;
66 UNICODE_STRING RemainingPath;
67 UNICODE_STRING ObjectName;
68 OBJECT_CREATE_INFORMATION ObjectCreateInfo;
69 NTSTATUS Status;
70
71 PAGED_CODE();
72
73 /* Capture the name */
74 DPRINT("Capturing Name\n");
75 Status = ObpCaptureObjectName(&ObjectName, ObjectPath, AccessMode);
76 if (!NT_SUCCESS(Status))
77 {
78 DPRINT("ObpCaptureObjectName() failed (Status %lx)\n", Status);
79 return Status;
80 }
81
82 /*
83 * Create a fake ObjectCreateInfo structure. Note that my upcoming
84 * ObFindObject refactoring will remove the need for this hack.
85 */
86 ObjectCreateInfo.RootDirectory = NULL;
87 ObjectCreateInfo.Attributes = Attributes;
88
89 Status = ObFindObject(&ObjectCreateInfo,
90 &ObjectName,
91 &Object,
92 &RemainingPath,
93 ObjectType);
94
95 if (ObjectName.Buffer) ExFreePool(ObjectName.Buffer);
96
97 if (!NT_SUCCESS(Status))
98 {
99 return(Status);
100 }
101 DPRINT("RemainingPath.Buffer '%S' Object %p\n", RemainingPath.Buffer, Object);
102
103 if (RemainingPath.Buffer != NULL || Object == NULL)
104 {
105 DPRINT("Object %p\n", Object);
106 *ObjectPtr = NULL;
107 RtlFreeUnicodeString (&RemainingPath);
108 return(STATUS_OBJECT_NAME_NOT_FOUND);
109 }
110 *ObjectPtr = Object;
111 RtlFreeUnicodeString (&RemainingPath);
112 return(STATUS_SUCCESS);
113 }
114
115
116 /**********************************************************************
117 * NAME EXPORTED
118 * ObOpenObjectByName
119 *
120 * DESCRIPTION
121 * Obtain a handle to an existing object.
122 *
123 * ARGUMENTS
124 * ObjectAttributes
125 * ...
126 * ObjectType
127 * ...
128 * ParseContext
129 * ...
130 * AccessMode
131 * ...
132 * DesiredAccess
133 * ...
134 * PassedAccessState
135 * ...
136 * Handle
137 * Handle to close.
138 *
139 * RETURN VALUE
140 * Status.
141 *
142 * @implemented
143 */
144 NTSTATUS STDCALL
145 ObOpenObjectByName(IN POBJECT_ATTRIBUTES ObjectAttributes,
146 IN POBJECT_TYPE ObjectType,
147 IN OUT PVOID ParseContext,
148 IN KPROCESSOR_MODE AccessMode,
149 IN ACCESS_MASK DesiredAccess,
150 IN PACCESS_STATE PassedAccessState,
151 OUT PHANDLE Handle)
152 {
153 UNICODE_STRING RemainingPath;
154 PVOID Object = NULL;
155 UNICODE_STRING ObjectName;
156 OBJECT_CREATE_INFORMATION ObjectCreateInfo;
157 NTSTATUS Status;
158
159 PAGED_CODE();
160
161 DPRINT("ObOpenObjectByName(...)\n");
162
163 /* Capture all the info */
164 DPRINT("Capturing Create Info\n");
165 Status = ObpCaptureObjectAttributes(ObjectAttributes,
166 AccessMode,
167 ObjectType,
168 &ObjectCreateInfo,
169 &ObjectName);
170 if (!NT_SUCCESS(Status))
171 {
172 DPRINT("ObpCaptureObjectAttributes() failed (Status %lx)\n", Status);
173 return Status;
174 }
175
176 Status = ObFindObject(&ObjectCreateInfo,
177 &ObjectName,
178 &Object,
179 &RemainingPath,
180 ObjectType);
181 ObpReleaseCapturedAttributes(&ObjectCreateInfo);
182 if (ObjectName.Buffer) ExFreePool(ObjectName.Buffer);
183 if (!NT_SUCCESS(Status))
184 {
185 DPRINT("ObFindObject() failed (Status %lx)\n", Status);
186 return Status;
187 }
188
189 DPRINT("OBject: %x, Remaining Path: %wZ\n", Object, &RemainingPath);
190 if (Object == NULL)
191 {
192 RtlFreeUnicodeString(&RemainingPath);
193 return STATUS_UNSUCCESSFUL;
194 }
195 if (RemainingPath.Buffer != NULL)
196 {
197 if (wcschr(RemainingPath.Buffer + 1, L'\\') == NULL)
198 Status = STATUS_OBJECT_NAME_NOT_FOUND;
199 else
200 Status =STATUS_OBJECT_PATH_NOT_FOUND;
201 RtlFreeUnicodeString(&RemainingPath);
202 ObDereferenceObject(Object);
203 return Status;
204 }
205
206 Status = ObpCreateHandle(PsGetCurrentProcess(),
207 Object,
208 DesiredAccess,
209 FALSE,
210 Handle);
211
212 ObDereferenceObject(Object);
213 RtlFreeUnicodeString(&RemainingPath);
214
215 return Status;
216 }
217
218 VOID
219 STDCALL
220 ObQueryDeviceMapInformation(PEPROCESS Process,
221 PPROCESS_DEVICEMAP_INFORMATION DeviceMapInfo)
222 {
223 //KIRQL OldIrql ;
224
225 /*
226 * FIXME: This is an ugly hack for now, to always return the System Device Map
227 * instead of returning the Process Device Map. Not important yet since we don't use it
228 */
229
230 /* FIXME: Acquire the DeviceMap Spinlock */
231 // KeAcquireSpinLock(DeviceMap->Lock, &OldIrql);
232
233 /* Make a copy */
234 DeviceMapInfo->Query.DriveMap = ObSystemDeviceMap->DriveMap;
235 RtlMoveMemory(DeviceMapInfo->Query.DriveType, ObSystemDeviceMap->DriveType, sizeof(ObSystemDeviceMap->DriveType));
236
237 /* FIXME: Release the DeviceMap Spinlock */
238 // KeReleasepinLock(DeviceMap->Lock, OldIrql);
239 }
240
241 VOID
242 ObpAddEntryDirectory(PDIRECTORY_OBJECT Parent,
243 POBJECT_HEADER Header,
244 PWSTR Name)
245 /*
246 * FUNCTION: Add an entry to a namespace directory
247 * ARGUMENTS:
248 * Parent = directory to add in
249 * Header = Header of the object to add the entry for
250 * Name = Name to give the entry
251 */
252 {
253 KIRQL oldlvl;
254
255 ASSERT(HEADER_TO_OBJECT_NAME(Header));
256 HEADER_TO_OBJECT_NAME(Header)->Directory = Parent;
257
258 KeAcquireSpinLock(&Parent->Lock, &oldlvl);
259 InsertTailList(&Parent->head, &Header->Entry);
260 KeReleaseSpinLock(&Parent->Lock, oldlvl);
261 }
262
263
264 VOID
265 ObpRemoveEntryDirectory(POBJECT_HEADER Header)
266 /*
267 * FUNCTION: Remove an entry from a namespace directory
268 * ARGUMENTS:
269 * Header = Header of the object to remove
270 */
271 {
272 KIRQL oldlvl;
273
274 DPRINT("ObpRemoveEntryDirectory(Header %x)\n",Header);
275
276 KeAcquireSpinLock(&(HEADER_TO_OBJECT_NAME(Header)->Directory->Lock),&oldlvl);
277 if (Header->Entry.Flink && Header->Entry.Blink)
278 {
279 RemoveEntryList(&(Header->Entry));
280 Header->Entry.Flink = Header->Entry.Blink = NULL;
281 }
282 KeReleaseSpinLock(&(HEADER_TO_OBJECT_NAME(Header)->Directory->Lock),oldlvl);
283 }
284
285 NTSTATUS
286 STDCALL
287 ObpCreateDirectory(OB_OPEN_REASON Reason,
288 PVOID ObjectBody,
289 PEPROCESS Process,
290 ULONG HandleCount,
291 ACCESS_MASK GrantedAccess)
292 {
293 PDIRECTORY_OBJECT Directory = ObjectBody;
294
295 if (Reason == ObCreateHandle)
296 {
297 InitializeListHead(&Directory->head);
298 KeInitializeSpinLock(&Directory->Lock);
299 }
300
301 return STATUS_SUCCESS;
302 }
303
304 PVOID
305 ObpFindEntryDirectory(PDIRECTORY_OBJECT DirectoryObject,
306 PWSTR Name,
307 ULONG Attributes)
308 {
309 PLIST_ENTRY current = DirectoryObject->head.Flink;
310 POBJECT_HEADER current_obj;
311
312 DPRINT("ObFindEntryDirectory(dir %x, name %S)\n",DirectoryObject, Name);
313
314 if (Name[0]==0)
315 {
316 return(DirectoryObject);
317 }
318 if (Name[0]=='.' && Name[1]==0)
319 {
320 return(DirectoryObject);
321 }
322 if (Name[0]=='.' && Name[1]=='.' && Name[2]==0)
323 {
324 return(HEADER_TO_OBJECT_NAME(BODY_TO_HEADER(DirectoryObject))->Directory);
325 }
326 while (current!=(&(DirectoryObject->head)))
327 {
328 current_obj = CONTAINING_RECORD(current,OBJECT_HEADER,Entry);
329 DPRINT(" Scanning: %S for: %S\n",HEADER_TO_OBJECT_NAME(current_obj)->Name.Buffer, Name);
330 if (Attributes & OBJ_CASE_INSENSITIVE)
331 {
332 if (_wcsicmp(HEADER_TO_OBJECT_NAME(current_obj)->Name.Buffer, Name)==0)
333 {
334 DPRINT("Found it %x\n",&current_obj->Body);
335 return(&current_obj->Body);
336 }
337 }
338 else
339 {
340 if ( wcscmp(HEADER_TO_OBJECT_NAME(current_obj)->Name.Buffer, Name)==0)
341 {
342 DPRINT("Found it %x\n",&current_obj->Body);
343 return(&current_obj->Body);
344 }
345 }
346 current = current->Flink;
347 }
348 DPRINT(" Not Found: %s() = NULL\n",__FUNCTION__);
349 return(NULL);
350 }
351
352
353 NTSTATUS STDCALL
354 ObpParseDirectory(PVOID Object,
355 PVOID * NextObject,
356 PUNICODE_STRING FullPath,
357 PWSTR * Path,
358 ULONG Attributes)
359 {
360 PWSTR Start;
361 PWSTR End;
362 PVOID FoundObject;
363 KIRQL oldlvl;
364
365 DPRINT("ObpParseDirectory(Object %x, Path %x, *Path %S)\n",
366 Object,Path,*Path);
367
368 *NextObject = NULL;
369
370 if ((*Path) == NULL)
371 {
372 return STATUS_UNSUCCESSFUL;
373 }
374
375 Start = *Path;
376 if (*Start == L'\\')
377 Start++;
378
379 End = wcschr(Start, L'\\');
380 if (End != NULL)
381 {
382 *End = 0;
383 }
384
385 KeAcquireSpinLock(&(((PDIRECTORY_OBJECT)Object)->Lock), &oldlvl);
386 FoundObject = ObpFindEntryDirectory(Object, Start, Attributes);
387 if (FoundObject == NULL)
388 {
389 KeReleaseSpinLock(&(((PDIRECTORY_OBJECT)Object)->Lock), oldlvl);
390 if (End != NULL)
391 {
392 *End = L'\\';
393 }
394 return STATUS_UNSUCCESSFUL;
395 }
396
397 ObReferenceObjectByPointer(FoundObject,
398 STANDARD_RIGHTS_REQUIRED,
399 NULL,
400 UserMode);
401 KeReleaseSpinLock(&(((PDIRECTORY_OBJECT)Object)->Lock), oldlvl);
402 if (End != NULL)
403 {
404 *End = L'\\';
405 *Path = End;
406 }
407 else
408 {
409 *Path = NULL;
410 }
411
412 *NextObject = FoundObject;
413
414 return STATUS_SUCCESS;
415 }
416
417 VOID
418 INIT_FUNCTION
419 ObInit(VOID)
420 {
421 OBJECT_ATTRIBUTES ObjectAttributes;
422 UNICODE_STRING Name;
423 SECURITY_DESCRIPTOR SecurityDescriptor;
424 OBJECT_TYPE_INITIALIZER ObjectTypeInitializer;
425
426 /* Initialize the security descriptor cache */
427 ObpInitSdCache();
428
429 /* Initialize the Default Event */
430 KeInitializeEvent(&ObpDefaultObject, NotificationEvent, TRUE );
431
432 /* Create the Type Type */
433 DPRINT("Creating Type Type\n");
434 RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
435 RtlInitUnicodeString(&Name, L"Type");
436 ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
437 ObjectTypeInitializer.ValidAccessMask = OBJECT_TYPE_ALL_ACCESS;
438 ObjectTypeInitializer.UseDefaultObject = TRUE;
439 ObjectTypeInitializer.MaintainTypeList = TRUE;
440 ObjectTypeInitializer.PoolType = NonPagedPool;
441 ObjectTypeInitializer.GenericMapping = ObpTypeMapping;
442 ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(OBJECT_TYPE);
443 ObpCreateTypeObject(&ObjectTypeInitializer, &Name, &ObTypeObjectType);
444
445 /* Create the Directory Type */
446 DPRINT("Creating Directory Type\n");
447 RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
448 RtlInitUnicodeString(&Name, L"Directory");
449 ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
450 ObjectTypeInitializer.ValidAccessMask = DIRECTORY_ALL_ACCESS;
451 ObjectTypeInitializer.UseDefaultObject = FALSE;
452 ObjectTypeInitializer.OpenProcedure = ObpCreateDirectory;
453 ObjectTypeInitializer.ParseProcedure = ObpParseDirectory;
454 ObjectTypeInitializer.MaintainTypeList = FALSE;
455 ObjectTypeInitializer.GenericMapping = ObpDirectoryMapping;
456 ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(DIRECTORY_OBJECT);
457 ObpCreateTypeObject(&ObjectTypeInitializer, &Name, &ObDirectoryType);
458
459 /* Create security descriptor */
460 RtlCreateSecurityDescriptor(&SecurityDescriptor,
461 SECURITY_DESCRIPTOR_REVISION1);
462 RtlSetOwnerSecurityDescriptor(&SecurityDescriptor,
463 SeAliasAdminsSid,
464 FALSE);
465 RtlSetGroupSecurityDescriptor(&SecurityDescriptor,
466 SeLocalSystemSid,
467 FALSE);
468 RtlSetDaclSecurityDescriptor(&SecurityDescriptor,
469 TRUE,
470 SePublicDefaultDacl,
471 FALSE);
472
473 /* Create root directory */
474 DPRINT("Creating Root Directory\n");
475 InitializeObjectAttributes(&ObjectAttributes,
476 NULL,
477 OBJ_PERMANENT,
478 NULL,
479 &SecurityDescriptor);
480 ObCreateObject(KernelMode,
481 ObDirectoryType,
482 &ObjectAttributes,
483 KernelMode,
484 NULL,
485 sizeof(DIRECTORY_OBJECT),
486 0,
487 0,
488 (PVOID*)&NameSpaceRoot);
489 ObInsertObject((PVOID)NameSpaceRoot,
490 NULL,
491 DIRECTORY_ALL_ACCESS,
492 0,
493 NULL,
494 NULL);
495
496 /* Create '\ObjectTypes' directory */
497 RtlInitUnicodeString(&Name, L"\\ObjectTypes");
498 InitializeObjectAttributes(&ObjectAttributes,
499 &Name,
500 OBJ_PERMANENT,
501 NULL,
502 &SecurityDescriptor);
503 ObCreateObject(KernelMode,
504 ObDirectoryType,
505 &ObjectAttributes,
506 KernelMode,
507 NULL,
508 sizeof(DIRECTORY_OBJECT),
509 0,
510 0,
511 (PVOID*)&ObpTypeDirectoryObject);
512 ObInsertObject((PVOID)ObpTypeDirectoryObject,
513 NULL,
514 DIRECTORY_ALL_ACCESS,
515 0,
516 NULL,
517 NULL);
518
519 /* Insert the two objects we already created but couldn't add */
520 /* NOTE: Uses TypeList & Creator Info in OB 2.0 */
521 ObpAddEntryDirectory(ObpTypeDirectoryObject, BODY_TO_HEADER(ObTypeObjectType), NULL);
522 ObpAddEntryDirectory(ObpTypeDirectoryObject, BODY_TO_HEADER(ObDirectoryType), NULL);
523
524 /* Create 'symbolic link' object type */
525 ObInitSymbolicLinkImplementation();
526
527 /* FIXME: Hack Hack! */
528 ObSystemDeviceMap = ExAllocatePoolWithTag(NonPagedPool, sizeof(*ObSystemDeviceMap), TAG('O', 'b', 'D', 'm'));
529 RtlZeroMemory(ObSystemDeviceMap, sizeof(*ObSystemDeviceMap));
530 }
531
532 NTSTATUS
533 STDCALL
534 ObpCreateTypeObject(POBJECT_TYPE_INITIALIZER ObjectTypeInitializer,
535 PUNICODE_STRING TypeName,
536 POBJECT_TYPE *ObjectType)
537 {
538 POBJECT_HEADER Header;
539 POBJECT_TYPE LocalObjectType;
540 ULONG HeaderSize;
541 NTSTATUS Status;
542
543 DPRINT("ObpCreateTypeObject(ObjectType: %wZ)\n", TypeName);
544
545 /* Allocate the Object */
546 Status = ObpAllocateObject(NULL,
547 TypeName,
548 ObTypeObjectType,
549 OBJECT_ALLOC_SIZE(sizeof(OBJECT_TYPE)),
550 &Header);
551 if (!NT_SUCCESS(Status))
552 {
553 DPRINT1("ObpAllocateObject failed!\n");
554 return Status;
555 }
556
557 LocalObjectType = (POBJECT_TYPE)&Header->Body;
558 DPRINT("Local ObjectType: %p Header: %p \n", LocalObjectType, Header);
559
560 /* Check if this is the first Object Type */
561 if (!ObTypeObjectType)
562 {
563 ObTypeObjectType = LocalObjectType;
564 Header->Type = ObTypeObjectType;
565 LocalObjectType->Key = TAG('O', 'b', 'j', 'T');
566 }
567 else
568 {
569 CHAR Tag[4];
570 Tag[0] = TypeName->Buffer[0];
571 Tag[1] = TypeName->Buffer[1];
572 Tag[2] = TypeName->Buffer[2];
573 Tag[3] = TypeName->Buffer[3];
574
575 /* Set Tag */
576 DPRINT("Convert: %s \n", Tag);
577 LocalObjectType->Key = *(PULONG)Tag;
578 }
579
580 /* Set it up */
581 LocalObjectType->TypeInfo = *ObjectTypeInitializer;
582 LocalObjectType->Name = *TypeName;
583 LocalObjectType->TypeInfo.PoolType = ObjectTypeInitializer->PoolType;
584
585 /* These two flags need to be manually set up */
586 Header->Flags |= OB_FLAG_KERNEL_MODE | OB_FLAG_PERMANENT;
587
588 /* Check if we have to maintain a type list */
589 if (NtGlobalFlag & FLG_MAINTAIN_OBJECT_TYPELIST)
590 {
591 /* Enable support */
592 LocalObjectType->TypeInfo.MaintainTypeList = TRUE;
593 }
594
595 /* Calculate how much space our header'll take up */
596 HeaderSize = sizeof(OBJECT_HEADER) + sizeof(OBJECT_HEADER_NAME_INFO) +
597 (ObjectTypeInitializer->MaintainHandleCount ?
598 sizeof(OBJECT_HEADER_HANDLE_INFO) : 0);
599
600 /* Update the Pool Charges */
601 if (ObjectTypeInitializer->PoolType == NonPagedPool)
602 {
603 LocalObjectType->TypeInfo.DefaultNonPagedPoolCharge += HeaderSize;
604 }
605 else
606 {
607 LocalObjectType->TypeInfo.DefaultPagedPoolCharge += HeaderSize;
608 }
609
610 /* All objects types need a security procedure */
611 if (!ObjectTypeInitializer->SecurityProcedure)
612 {
613 LocalObjectType->TypeInfo.SecurityProcedure = SeDefaultObjectMethod;
614 }
615
616 /* Select the Wait Object */
617 if (LocalObjectType->TypeInfo.UseDefaultObject)
618 {
619 /* Add the SYNCHRONIZE access mask since it's waitable */
620 LocalObjectType->TypeInfo.ValidAccessMask |= SYNCHRONIZE;
621
622 /* Use the "Default Object", a simple event */
623 LocalObjectType->DefaultObject = &ObpDefaultObject;
624 }
625 /* Special system objects get an optimized hack so they can be waited on */
626 else if (TypeName->Length == 8 && !wcscmp(TypeName->Buffer, L"File"))
627 {
628 LocalObjectType->DefaultObject = (PVOID)FIELD_OFFSET(FILE_OBJECT, Event);
629 }
630 /* FIXME: When LPC stops sucking, add a hack for Waitable Ports */
631 else
632 {
633 /* No default Object */
634 LocalObjectType->DefaultObject = NULL;
635 }
636
637 /* Initialize Object Type components */
638 ExInitializeResourceLite(&LocalObjectType->Mutex);
639 InitializeListHead(&LocalObjectType->TypeList);
640
641 /* Insert it into the Object Directory */
642 if (ObpTypeDirectoryObject)
643 {
644 ObpAddEntryDirectory(ObpTypeDirectoryObject, Header, TypeName->Buffer);
645 ObReferenceObject(ObpTypeDirectoryObject);
646 }
647
648 *ObjectType = LocalObjectType;
649 return Status;
650 }
651
652 /* EOF */