ntoskrnl header cleanups
[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 NTAPI
243 ObpAddEntryDirectory(PDIRECTORY_OBJECT Parent,
244 POBJECT_HEADER Header,
245 PWSTR Name)
246 /*
247 * FUNCTION: Add an entry to a namespace directory
248 * ARGUMENTS:
249 * Parent = directory to add in
250 * Header = Header of the object to add the entry for
251 * Name = Name to give the entry
252 */
253 {
254 KIRQL oldlvl;
255
256 ASSERT(HEADER_TO_OBJECT_NAME(Header));
257 HEADER_TO_OBJECT_NAME(Header)->Directory = Parent;
258
259 KeAcquireSpinLock(&Parent->Lock, &oldlvl);
260 InsertTailList(&Parent->head, &Header->Entry);
261 KeReleaseSpinLock(&Parent->Lock, oldlvl);
262 }
263
264
265 VOID
266 NTAPI
267 ObpRemoveEntryDirectory(POBJECT_HEADER Header)
268 /*
269 * FUNCTION: Remove an entry from a namespace directory
270 * ARGUMENTS:
271 * Header = Header of the object to remove
272 */
273 {
274 KIRQL oldlvl;
275
276 DPRINT("ObpRemoveEntryDirectory(Header %x)\n",Header);
277
278 KeAcquireSpinLock(&(HEADER_TO_OBJECT_NAME(Header)->Directory->Lock),&oldlvl);
279 if (Header->Entry.Flink && Header->Entry.Blink)
280 {
281 RemoveEntryList(&(Header->Entry));
282 Header->Entry.Flink = Header->Entry.Blink = NULL;
283 }
284 KeReleaseSpinLock(&(HEADER_TO_OBJECT_NAME(Header)->Directory->Lock),oldlvl);
285 }
286
287 NTSTATUS
288 STDCALL
289 ObpCreateDirectory(OB_OPEN_REASON Reason,
290 PVOID ObjectBody,
291 PEPROCESS Process,
292 ULONG HandleCount,
293 ACCESS_MASK GrantedAccess)
294 {
295 PDIRECTORY_OBJECT Directory = ObjectBody;
296
297 if (Reason == ObCreateHandle)
298 {
299 InitializeListHead(&Directory->head);
300 KeInitializeSpinLock(&Directory->Lock);
301 }
302
303 return STATUS_SUCCESS;
304 }
305
306 PVOID
307 ObpFindEntryDirectory(PDIRECTORY_OBJECT DirectoryObject,
308 PWSTR Name,
309 ULONG Attributes)
310 {
311 PLIST_ENTRY current = DirectoryObject->head.Flink;
312 POBJECT_HEADER current_obj;
313
314 DPRINT("ObFindEntryDirectory(dir %x, name %S)\n",DirectoryObject, Name);
315
316 if (Name[0]==0)
317 {
318 return(DirectoryObject);
319 }
320 if (Name[0]=='.' && Name[1]==0)
321 {
322 return(DirectoryObject);
323 }
324 if (Name[0]=='.' && Name[1]=='.' && Name[2]==0)
325 {
326 return(HEADER_TO_OBJECT_NAME(BODY_TO_HEADER(DirectoryObject))->Directory);
327 }
328 while (current!=(&(DirectoryObject->head)))
329 {
330 current_obj = CONTAINING_RECORD(current,OBJECT_HEADER,Entry);
331 DPRINT(" Scanning: %S for: %S\n",HEADER_TO_OBJECT_NAME(current_obj)->Name.Buffer, Name);
332 if (Attributes & OBJ_CASE_INSENSITIVE)
333 {
334 if (_wcsicmp(HEADER_TO_OBJECT_NAME(current_obj)->Name.Buffer, Name)==0)
335 {
336 DPRINT("Found it %x\n",&current_obj->Body);
337 return(&current_obj->Body);
338 }
339 }
340 else
341 {
342 if ( wcscmp(HEADER_TO_OBJECT_NAME(current_obj)->Name.Buffer, Name)==0)
343 {
344 DPRINT("Found it %x\n",&current_obj->Body);
345 return(&current_obj->Body);
346 }
347 }
348 current = current->Flink;
349 }
350 DPRINT(" Not Found: %s() = NULL\n",__FUNCTION__);
351 return(NULL);
352 }
353
354
355 NTSTATUS STDCALL
356 ObpParseDirectory(PVOID Object,
357 PVOID * NextObject,
358 PUNICODE_STRING FullPath,
359 PWSTR * Path,
360 ULONG Attributes)
361 {
362 PWSTR Start;
363 PWSTR End;
364 PVOID FoundObject;
365 KIRQL oldlvl;
366
367 DPRINT("ObpParseDirectory(Object %x, Path %x, *Path %S)\n",
368 Object,Path,*Path);
369
370 *NextObject = NULL;
371
372 if ((*Path) == NULL)
373 {
374 return STATUS_UNSUCCESSFUL;
375 }
376
377 Start = *Path;
378 if (*Start == L'\\')
379 Start++;
380
381 End = wcschr(Start, L'\\');
382 if (End != NULL)
383 {
384 *End = 0;
385 }
386
387 KeAcquireSpinLock(&(((PDIRECTORY_OBJECT)Object)->Lock), &oldlvl);
388 FoundObject = ObpFindEntryDirectory(Object, Start, Attributes);
389 if (FoundObject == NULL)
390 {
391 KeReleaseSpinLock(&(((PDIRECTORY_OBJECT)Object)->Lock), oldlvl);
392 if (End != NULL)
393 {
394 *End = L'\\';
395 }
396 return STATUS_UNSUCCESSFUL;
397 }
398
399 ObReferenceObjectByPointer(FoundObject,
400 STANDARD_RIGHTS_REQUIRED,
401 NULL,
402 UserMode);
403 KeReleaseSpinLock(&(((PDIRECTORY_OBJECT)Object)->Lock), oldlvl);
404 if (End != NULL)
405 {
406 *End = L'\\';
407 *Path = End;
408 }
409 else
410 {
411 *Path = NULL;
412 }
413
414 *NextObject = FoundObject;
415
416 return STATUS_SUCCESS;
417 }
418
419 VOID
420 INIT_FUNCTION
421 ObInit(VOID)
422 {
423 OBJECT_ATTRIBUTES ObjectAttributes;
424 UNICODE_STRING Name;
425 SECURITY_DESCRIPTOR SecurityDescriptor;
426 OBJECT_TYPE_INITIALIZER ObjectTypeInitializer;
427
428 /* Initialize the security descriptor cache */
429 ObpInitSdCache();
430
431 /* Initialize the Default Event */
432 KeInitializeEvent(&ObpDefaultObject, NotificationEvent, TRUE );
433
434 /* Create the Type Type */
435 DPRINT("Creating Type Type\n");
436 RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
437 RtlInitUnicodeString(&Name, L"Type");
438 ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
439 ObjectTypeInitializer.ValidAccessMask = OBJECT_TYPE_ALL_ACCESS;
440 ObjectTypeInitializer.UseDefaultObject = TRUE;
441 ObjectTypeInitializer.MaintainTypeList = TRUE;
442 ObjectTypeInitializer.PoolType = NonPagedPool;
443 ObjectTypeInitializer.GenericMapping = ObpTypeMapping;
444 ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(OBJECT_TYPE);
445 ObpCreateTypeObject(&ObjectTypeInitializer, &Name, &ObTypeObjectType);
446
447 /* Create the Directory Type */
448 DPRINT("Creating Directory Type\n");
449 RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
450 RtlInitUnicodeString(&Name, L"Directory");
451 ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
452 ObjectTypeInitializer.ValidAccessMask = DIRECTORY_ALL_ACCESS;
453 ObjectTypeInitializer.UseDefaultObject = FALSE;
454 ObjectTypeInitializer.OpenProcedure = ObpCreateDirectory;
455 ObjectTypeInitializer.ParseProcedure = ObpParseDirectory;
456 ObjectTypeInitializer.MaintainTypeList = FALSE;
457 ObjectTypeInitializer.GenericMapping = ObpDirectoryMapping;
458 ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(DIRECTORY_OBJECT);
459 ObpCreateTypeObject(&ObjectTypeInitializer, &Name, &ObDirectoryType);
460
461 /* Create security descriptor */
462 RtlCreateSecurityDescriptor(&SecurityDescriptor,
463 SECURITY_DESCRIPTOR_REVISION1);
464 RtlSetOwnerSecurityDescriptor(&SecurityDescriptor,
465 SeAliasAdminsSid,
466 FALSE);
467 RtlSetGroupSecurityDescriptor(&SecurityDescriptor,
468 SeLocalSystemSid,
469 FALSE);
470 RtlSetDaclSecurityDescriptor(&SecurityDescriptor,
471 TRUE,
472 SePublicDefaultDacl,
473 FALSE);
474
475 /* Create root directory */
476 DPRINT("Creating Root Directory\n");
477 InitializeObjectAttributes(&ObjectAttributes,
478 NULL,
479 OBJ_PERMANENT,
480 NULL,
481 &SecurityDescriptor);
482 ObCreateObject(KernelMode,
483 ObDirectoryType,
484 &ObjectAttributes,
485 KernelMode,
486 NULL,
487 sizeof(DIRECTORY_OBJECT),
488 0,
489 0,
490 (PVOID*)&NameSpaceRoot);
491 ObInsertObject((PVOID)NameSpaceRoot,
492 NULL,
493 DIRECTORY_ALL_ACCESS,
494 0,
495 NULL,
496 NULL);
497
498 /* Create '\ObjectTypes' directory */
499 RtlInitUnicodeString(&Name, L"\\ObjectTypes");
500 InitializeObjectAttributes(&ObjectAttributes,
501 &Name,
502 OBJ_PERMANENT,
503 NULL,
504 &SecurityDescriptor);
505 ObCreateObject(KernelMode,
506 ObDirectoryType,
507 &ObjectAttributes,
508 KernelMode,
509 NULL,
510 sizeof(DIRECTORY_OBJECT),
511 0,
512 0,
513 (PVOID*)&ObpTypeDirectoryObject);
514 ObInsertObject((PVOID)ObpTypeDirectoryObject,
515 NULL,
516 DIRECTORY_ALL_ACCESS,
517 0,
518 NULL,
519 NULL);
520
521 /* Insert the two objects we already created but couldn't add */
522 /* NOTE: Uses TypeList & Creator Info in OB 2.0 */
523 ObpAddEntryDirectory(ObpTypeDirectoryObject, BODY_TO_HEADER(ObTypeObjectType), NULL);
524 ObpAddEntryDirectory(ObpTypeDirectoryObject, BODY_TO_HEADER(ObDirectoryType), NULL);
525
526 /* Create 'symbolic link' object type */
527 ObInitSymbolicLinkImplementation();
528
529 /* FIXME: Hack Hack! */
530 ObSystemDeviceMap = ExAllocatePoolWithTag(NonPagedPool, sizeof(*ObSystemDeviceMap), TAG('O', 'b', 'D', 'm'));
531 RtlZeroMemory(ObSystemDeviceMap, sizeof(*ObSystemDeviceMap));
532 }
533
534 NTSTATUS
535 STDCALL
536 ObpCreateTypeObject(POBJECT_TYPE_INITIALIZER ObjectTypeInitializer,
537 PUNICODE_STRING TypeName,
538 POBJECT_TYPE *ObjectType)
539 {
540 POBJECT_HEADER Header;
541 POBJECT_TYPE LocalObjectType;
542 ULONG HeaderSize;
543 NTSTATUS Status;
544
545 DPRINT("ObpCreateTypeObject(ObjectType: %wZ)\n", TypeName);
546
547 /* Allocate the Object */
548 Status = ObpAllocateObject(NULL,
549 TypeName,
550 ObTypeObjectType,
551 OBJECT_ALLOC_SIZE(sizeof(OBJECT_TYPE)),
552 &Header);
553 if (!NT_SUCCESS(Status))
554 {
555 DPRINT1("ObpAllocateObject failed!\n");
556 return Status;
557 }
558
559 LocalObjectType = (POBJECT_TYPE)&Header->Body;
560 DPRINT("Local ObjectType: %p Header: %p \n", LocalObjectType, Header);
561
562 /* Check if this is the first Object Type */
563 if (!ObTypeObjectType)
564 {
565 ObTypeObjectType = LocalObjectType;
566 Header->Type = ObTypeObjectType;
567 LocalObjectType->Key = TAG('O', 'b', 'j', 'T');
568 }
569 else
570 {
571 CHAR Tag[4];
572 Tag[0] = TypeName->Buffer[0];
573 Tag[1] = TypeName->Buffer[1];
574 Tag[2] = TypeName->Buffer[2];
575 Tag[3] = TypeName->Buffer[3];
576
577 /* Set Tag */
578 DPRINT("Convert: %s \n", Tag);
579 LocalObjectType->Key = *(PULONG)Tag;
580 }
581
582 /* Set it up */
583 LocalObjectType->TypeInfo = *ObjectTypeInitializer;
584 LocalObjectType->Name = *TypeName;
585 LocalObjectType->TypeInfo.PoolType = ObjectTypeInitializer->PoolType;
586
587 /* These two flags need to be manually set up */
588 Header->Flags |= OB_FLAG_KERNEL_MODE | OB_FLAG_PERMANENT;
589
590 /* Check if we have to maintain a type list */
591 if (NtGlobalFlag & FLG_MAINTAIN_OBJECT_TYPELIST)
592 {
593 /* Enable support */
594 LocalObjectType->TypeInfo.MaintainTypeList = TRUE;
595 }
596
597 /* Calculate how much space our header'll take up */
598 HeaderSize = sizeof(OBJECT_HEADER) + sizeof(OBJECT_HEADER_NAME_INFO) +
599 (ObjectTypeInitializer->MaintainHandleCount ?
600 sizeof(OBJECT_HEADER_HANDLE_INFO) : 0);
601
602 /* Update the Pool Charges */
603 if (ObjectTypeInitializer->PoolType == NonPagedPool)
604 {
605 LocalObjectType->TypeInfo.DefaultNonPagedPoolCharge += HeaderSize;
606 }
607 else
608 {
609 LocalObjectType->TypeInfo.DefaultPagedPoolCharge += HeaderSize;
610 }
611
612 /* All objects types need a security procedure */
613 if (!ObjectTypeInitializer->SecurityProcedure)
614 {
615 LocalObjectType->TypeInfo.SecurityProcedure = SeDefaultObjectMethod;
616 }
617
618 /* Select the Wait Object */
619 if (LocalObjectType->TypeInfo.UseDefaultObject)
620 {
621 /* Add the SYNCHRONIZE access mask since it's waitable */
622 LocalObjectType->TypeInfo.ValidAccessMask |= SYNCHRONIZE;
623
624 /* Use the "Default Object", a simple event */
625 LocalObjectType->DefaultObject = &ObpDefaultObject;
626 }
627 /* Special system objects get an optimized hack so they can be waited on */
628 else if (TypeName->Length == 8 && !wcscmp(TypeName->Buffer, L"File"))
629 {
630 LocalObjectType->DefaultObject = (PVOID)FIELD_OFFSET(FILE_OBJECT, Event);
631 }
632 /* FIXME: When LPC stops sucking, add a hack for Waitable Ports */
633 else
634 {
635 /* No default Object */
636 LocalObjectType->DefaultObject = NULL;
637 }
638
639 /* Initialize Object Type components */
640 ExInitializeResourceLite(&LocalObjectType->Mutex);
641 InitializeListHead(&LocalObjectType->TypeList);
642
643 /* Insert it into the Object Directory */
644 if (ObpTypeDirectoryObject)
645 {
646 ObpAddEntryDirectory(ObpTypeDirectoryObject, Header, TypeName->Buffer);
647 ObReferenceObject(ObpTypeDirectoryObject);
648 }
649
650 *ObjectType = LocalObjectType;
651 return Status;
652 }
653
654 /* EOF */