xref: /reactos/ntoskrnl/ob/oblink.c (revision f4d29a74)
1 /*
2 * PROJECT:         ReactOS Kernel
3 * LICENSE:         GPL - See COPYING in the top level directory
4 * FILE:            ntoskrnl/ob/oblink.c
5 * PURPOSE:         Implements symbolic links
6 * PROGRAMMERS:     Alex Ionescu (alex@relsoft.net)
7 *                  David Welch (welch@mcmail.com)
8 */
9 
10 /* INCLUDES *****************************************************************/
11 
12 #include <ntoskrnl.h>
13 #define NDEBUG
14 #include <debug.h>
15 
16 /* GLOBALS ******************************************************************/
17 
18 POBJECT_TYPE ObpSymbolicLinkObjectType = NULL;
19 
20 /* PRIVATE FUNCTIONS *********************************************************/
21 
22 VOID
23 NTAPI
24 ObpDeleteSymbolicLinkName(IN POBJECT_SYMBOLIC_LINK SymbolicLink)
25 {
26     POBJECT_HEADER ObjectHeader;
27     POBJECT_HEADER_NAME_INFO ObjectNameInfo;
28 
29     /* FIXME: Need to support Device maps */
30 
31     /* Get header data */
32     ObjectHeader = OBJECT_TO_OBJECT_HEADER(SymbolicLink);
33     ObjectNameInfo = ObpReferenceNameInfo(ObjectHeader);
34 
35     /* Check if we are not actually in a directory with a device map */
36     if (!(ObjectNameInfo) ||
37         !(ObjectNameInfo->Directory) /*||
38         !(ObjectNameInfo->Directory->DeviceMap)*/)
39     {
40         ObpDereferenceNameInfo(ObjectNameInfo);
41         return;
42     }
43 
44     /* Check if it's a DOS drive letter, and remove the entry from drive map if needed */
45     if (SymbolicLink->DosDeviceDriveIndex != 0 &&
46         ObjectNameInfo->Name.Length == 2 * sizeof(WCHAR) &&
47         ObjectNameInfo->Name.Buffer[1] == L':' &&
48         ( (ObjectNameInfo->Name.Buffer[0] >= L'A' &&
49             ObjectNameInfo->Name.Buffer[0] <= L'Z') ||
50           (ObjectNameInfo->Name.Buffer[0] >= L'a' &&
51             ObjectNameInfo->Name.Buffer[0] <= L'z') ))
52     {
53         /* Remove the drive entry */
54         KeAcquireGuardedMutex(&ObpDeviceMapLock);
55         ObSystemDeviceMap->DriveType[SymbolicLink->DosDeviceDriveIndex-1] =
56             DOSDEVICE_DRIVE_UNKNOWN;
57         ObSystemDeviceMap->DriveMap &=
58             ~(1 << (SymbolicLink->DosDeviceDriveIndex-1));
59         KeReleaseGuardedMutex(&ObpDeviceMapLock);
60 
61         /* Reset the drive index, valid drive index starts from 1 */
62         SymbolicLink->DosDeviceDriveIndex = 0;
63     }
64 
65     ObpDereferenceNameInfo(ObjectNameInfo);
66 }
67 
68 NTSTATUS
69 NTAPI
70 ObpParseSymbolicLinkToIoDeviceObject(IN POBJECT_DIRECTORY SymbolicLinkDirectory,
71                                      IN OUT POBJECT_DIRECTORY *Directory,
72                                      IN OUT PUNICODE_STRING TargetPath,
73                                      IN OUT POBP_LOOKUP_CONTEXT Context,
74                                      OUT PVOID *Object)
75 {
76     UNICODE_STRING Name;
77     BOOLEAN ManualUnlock;
78 
79     if (! TargetPath || ! Object || ! Context || ! Directory ||
80         ! SymbolicLinkDirectory)
81     {
82         return STATUS_INVALID_PARAMETER;
83     }
84 
85     /* FIXME: Need to support Device maps */
86 
87     /* Try walking the target path and open each part of the path */
88     while (TargetPath->Length)
89     {
90         /* Strip '\' if present at the beginning of the target path */
91         if (TargetPath->Length >= sizeof(OBJ_NAME_PATH_SEPARATOR)&&
92             TargetPath->Buffer[0] == OBJ_NAME_PATH_SEPARATOR)
93         {
94             TargetPath->Buffer++;
95             TargetPath->Length -= sizeof(OBJ_NAME_PATH_SEPARATOR);
96         }
97 
98         /* Remember the current component of the target path */
99         Name = *TargetPath;
100 
101         /* Move forward to the next component of the target path */
102         while (TargetPath->Length >= sizeof(OBJ_NAME_PATH_SEPARATOR))
103         {
104             if (TargetPath->Buffer[0] != OBJ_NAME_PATH_SEPARATOR)
105             {
106                 TargetPath->Buffer++;
107                 TargetPath->Length -= sizeof(OBJ_NAME_PATH_SEPARATOR);
108             }
109             else
110                 break;
111         }
112 
113         Name.Length -= TargetPath->Length;
114 
115         /* Finished processing the entire path, stop */
116         if (! Name.Length)
117             break;
118 
119         /*
120          * Make sure a deadlock does not occur as an exclusive lock on a pushlock
121          * would have already taken one in ObpLookupObjectName() on the parent
122          * directory where the symlink is being created [ObInsertObject()].
123          * Prevent recursive locking by faking lock state in the lookup context
124          * when the current directory is same as the parent directory where
125          * the symlink is being created. If the lock state is not faked,
126          * ObpLookupEntryDirectory() will try to get a recursive lock on the
127          * pushlock and hang. For e.g. this happens when a substed drive is pointed to
128          * another substed drive.
129          */
130         if (*Directory == SymbolicLinkDirectory && ! Context->DirectoryLocked)
131         {
132             /* Fake lock state so that ObpLookupEntryDirectory() doesn't attempt a lock */
133             ManualUnlock = TRUE;
134             Context->DirectoryLocked = TRUE;
135         }
136         else
137             ManualUnlock = FALSE;
138 
139         *Object = ObpLookupEntryDirectory(*Directory,
140                                           &Name,
141                                           0,
142                                           FALSE,
143                                           Context);
144 
145         /* Locking was faked, undo it now */
146         if (*Directory == SymbolicLinkDirectory && ManualUnlock)
147             Context->DirectoryLocked = FALSE;
148 
149         /* Lookup failed, stop */
150         if (! *Object)
151             break;
152 
153         if (OBJECT_TO_OBJECT_HEADER(*Object)->Type == ObpDirectoryObjectType)
154         {
155             /* Make this current directory, and continue search */
156             *Directory = (POBJECT_DIRECTORY)*Object;
157         }
158         else if (OBJECT_TO_OBJECT_HEADER(*Object)->Type == ObpSymbolicLinkObjectType &&
159             (((POBJECT_SYMBOLIC_LINK)*Object)->DosDeviceDriveIndex == 0))
160         {
161             /* Symlink points to another initialized symlink, ask caller to reparse */
162             *Directory = ObpRootDirectoryObject;
163             TargetPath = &((POBJECT_SYMBOLIC_LINK)*Object)->LinkTarget;
164             return STATUS_REPARSE_OBJECT;
165         }
166         else
167         {
168             /* Neither directory or symlink, stop */
169             break;
170         }
171     }
172 
173     /* Return a valid object, only if object type is IoDeviceObject */
174     if (*Object &&
175         OBJECT_TO_OBJECT_HEADER(*Object)->Type != IoDeviceObjectType)
176     {
177         *Object = NULL;
178     }
179     return STATUS_SUCCESS;
180 }
181 
182 VOID
183 NTAPI
184 ObpCreateSymbolicLinkName(IN POBJECT_SYMBOLIC_LINK SymbolicLink)
185 {
186     POBJECT_HEADER ObjectHeader;
187     POBJECT_HEADER_NAME_INFO ObjectNameInfo;
188     PVOID Object = NULL;
189     POBJECT_DIRECTORY Directory;
190     UNICODE_STRING TargetPath;
191     NTSTATUS Status;
192     ULONG DriveType = DOSDEVICE_DRIVE_CALCULATE;
193     ULONG ReparseCnt;
194     const ULONG MaxReparseAttempts = 20;
195     OBP_LOOKUP_CONTEXT Context;
196 
197     /* FIXME: Need to support Device maps */
198 
199     /* Get header data */
200     ObjectHeader = OBJECT_TO_OBJECT_HEADER(SymbolicLink);
201     ObjectNameInfo = ObpReferenceNameInfo(ObjectHeader);
202 
203     /* Check if we are not actually in a directory with a device map */
204     if (!(ObjectNameInfo) ||
205         !(ObjectNameInfo->Directory) /*||
206         !(ObjectNameInfo->Directory->DeviceMap)*/)
207     {
208         ObpDereferenceNameInfo(ObjectNameInfo);
209         return;
210     }
211 
212     /* Check if it's a DOS drive letter, and set the drive index accordingly */
213     if (ObjectNameInfo->Name.Length == 2 * sizeof(WCHAR) &&
214         ObjectNameInfo->Name.Buffer[1] == L':' &&
215         ( (ObjectNameInfo->Name.Buffer[0] >= L'A' &&
216             ObjectNameInfo->Name.Buffer[0] <= L'Z') ||
217           (ObjectNameInfo->Name.Buffer[0] >= L'a' &&
218             ObjectNameInfo->Name.Buffer[0] <= L'z') ))
219     {
220         SymbolicLink->DosDeviceDriveIndex =
221             RtlUpcaseUnicodeChar(ObjectNameInfo->Name.Buffer[0]) - L'A';
222         /* The Drive index start from 1 */
223         SymbolicLink->DosDeviceDriveIndex++;
224 
225         /* Initialize lookup context */
226         ObpInitializeLookupContext(&Context);
227 
228         /* Start the search from the root */
229         Directory = ObpRootDirectoryObject;
230         TargetPath = SymbolicLink->LinkTarget;
231 
232         /*
233          * Locate the IoDeviceObject if any this symbolic link points to.
234          * To prevent endless reparsing, setting an upper limit on the
235          * number of reparses.
236          */
237         Status = STATUS_REPARSE_OBJECT;
238         ReparseCnt = 0;
239         while (Status == STATUS_REPARSE_OBJECT &&
240                ReparseCnt < MaxReparseAttempts)
241         {
242             Status =
243                 ObpParseSymbolicLinkToIoDeviceObject(ObjectNameInfo->Directory,
244                                                      &Directory,
245                                                      &TargetPath,
246                                                      &Context,
247                                                      &Object);
248             if (Status == STATUS_REPARSE_OBJECT)
249                 ReparseCnt++;
250         }
251 
252         /* Cleanup lookup context */
253         ObpReleaseLookupContext(&Context);
254 
255         /* Error, or max resparse attemtps exceeded */
256         if (! NT_SUCCESS(Status) || ReparseCnt >= MaxReparseAttempts)
257         {
258             /* Cleanup */
259             ObpDereferenceNameInfo(ObjectNameInfo);
260             return;
261         }
262 
263         if (Object)
264         {
265             /* Calculate the drive type */
266             switch(((PDEVICE_OBJECT)Object)->DeviceType)
267             {
268             case FILE_DEVICE_VIRTUAL_DISK:
269                 DriveType = DOSDEVICE_DRIVE_RAMDISK;
270                 break;
271             case FILE_DEVICE_CD_ROM:
272             case FILE_DEVICE_CD_ROM_FILE_SYSTEM:
273                 DriveType = DOSDEVICE_DRIVE_CDROM;
274                 break;
275             case FILE_DEVICE_DISK:
276             case FILE_DEVICE_DISK_FILE_SYSTEM:
277             case FILE_DEVICE_FILE_SYSTEM:
278                 if (((PDEVICE_OBJECT)Object)->Characteristics & FILE_REMOVABLE_MEDIA)
279                     DriveType = DOSDEVICE_DRIVE_REMOVABLE;
280                 else
281                     DriveType = DOSDEVICE_DRIVE_FIXED;
282                 break;
283             case FILE_DEVICE_NETWORK:
284             case FILE_DEVICE_NETWORK_FILE_SYSTEM:
285                 DriveType = DOSDEVICE_DRIVE_REMOTE;
286                 break;
287             default:
288                 DPRINT1("Device Type %lu for %wZ is not known or unhandled\n",
289                         ((PDEVICE_OBJECT)Object)->DeviceType,
290                         &SymbolicLink->LinkTarget);
291                 DriveType = DOSDEVICE_DRIVE_UNKNOWN;
292             }
293         }
294 
295         /* Add a new drive entry */
296         KeAcquireGuardedMutex(&ObpDeviceMapLock);
297         ObSystemDeviceMap->DriveType[SymbolicLink->DosDeviceDriveIndex-1] =
298             (UCHAR)DriveType;
299         ObSystemDeviceMap->DriveMap |=
300             1 << (SymbolicLink->DosDeviceDriveIndex-1);
301         KeReleaseGuardedMutex(&ObpDeviceMapLock);
302     }
303 
304     /* Cleanup */
305     ObpDereferenceNameInfo(ObjectNameInfo);
306 }
307 
308 /*++
309 * @name ObpDeleteSymbolicLink
310 *
311 *     The ObpDeleteSymbolicLink routine <FILLMEIN>
312 *
313 * @param ObjectBody
314 *        <FILLMEIN>
315 *
316 * @return None.
317 *
318 * @remarks None.
319 *
320 *--*/
321 VOID
322 NTAPI
323 ObpDeleteSymbolicLink(PVOID ObjectBody)
324 {
325     POBJECT_SYMBOLIC_LINK SymlinkObject = (POBJECT_SYMBOLIC_LINK)ObjectBody;
326 
327     /* Make sure that the symbolic link has a name */
328     if (SymlinkObject->LinkTarget.Buffer)
329     {
330         /* Free the name */
331         ExFreePool(SymlinkObject->LinkTarget.Buffer);
332         SymlinkObject->LinkTarget.Buffer = NULL;
333     }
334 }
335 
336 /*++
337 * @name ObpParseSymbolicLink
338 *
339 *     The ObpParseSymbolicLink routine <FILLMEIN>
340 *
341 * @param Object
342 *        <FILLMEIN>
343 *
344 * @param NextObject
345 *        <FILLMEIN>
346 *
347 * @param FullPath
348 *        <FILLMEIN>
349 *
350 * @param RemainingPath
351 *        <FILLMEIN>
352 *
353 * @param Attributes
354 *        <FILLMEIN>
355 *
356 * @return STATUS_SUCCESS or appropriate error value.
357 *
358 * @remarks None.
359 *
360 *--*/
361 NTSTATUS
362 NTAPI
363 ObpParseSymbolicLink(IN PVOID ParsedObject,
364                      IN PVOID ObjectType,
365                      IN OUT PACCESS_STATE AccessState,
366                      IN KPROCESSOR_MODE AccessMode,
367                      IN ULONG Attributes,
368                      IN OUT PUNICODE_STRING FullPath,
369                      IN OUT PUNICODE_STRING RemainingName,
370                      IN OUT PVOID Context OPTIONAL,
371                      IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
372                      OUT PVOID *NextObject)
373 {
374     POBJECT_SYMBOLIC_LINK SymlinkObject = (POBJECT_SYMBOLIC_LINK)ParsedObject;
375     PUNICODE_STRING TargetPath;
376     PWSTR NewTargetPath;
377     ULONG LengthUsed, MaximumLength, TempLength;
378     NTSTATUS Status;
379     PAGED_CODE();
380 
381     /* Assume failure */
382     *NextObject = NULL;
383 
384     /* Check if we're out of name to parse */
385     if (!RemainingName->Length)
386     {
387         /* Check if we got an object type */
388         if (ObjectType)
389         {
390             /* Reference the object only */
391             Status = ObReferenceObjectByPointer(ParsedObject,
392                                                 0,
393                                                 ObjectType,
394                                                 AccessMode);
395             if (NT_SUCCESS(Status))
396             {
397                 /* Return it */
398                 *NextObject = ParsedObject;
399             }
400 
401             if ((NT_SUCCESS(Status)) || (Status != STATUS_OBJECT_TYPE_MISMATCH))
402             {
403                 /* Fail */
404                 return Status;
405             }
406         }
407     }
408     else if (RemainingName->Buffer[0] != OBJ_NAME_PATH_SEPARATOR)
409     {
410         /* Symbolic links must start with a backslash */
411         return STATUS_OBJECT_TYPE_MISMATCH;
412     }
413 
414     /* Check if this symlink is bound to a specific object */
415     if (SymlinkObject->LinkTargetObject)
416     {
417         UNIMPLEMENTED;
418     }
419 
420     /* Set the target path and length */
421     TargetPath = &SymlinkObject->LinkTarget;
422     TempLength = TargetPath->Length;
423 
424     /*
425      * Strip off the extra trailing '\', if we don't do this we will end up
426      * adding a extra '\' between TargetPath and RemainingName
427      * causing caller's like ObpLookupObjectName() to fail.
428      */
429     if (TempLength && RemainingName->Length)
430     {
431         /* The target and remaining names aren't empty, so check for slashes */
432         if ((TargetPath->Buffer[TempLength / sizeof(WCHAR) - 1] ==
433             OBJ_NAME_PATH_SEPARATOR) &&
434             (RemainingName->Buffer[0] == OBJ_NAME_PATH_SEPARATOR))
435         {
436             /* Reduce the length by one to cut off the extra '\' */
437             TempLength -= sizeof(OBJ_NAME_PATH_SEPARATOR);
438         }
439     }
440 
441     /* Calculate the new length */
442     LengthUsed = TempLength + RemainingName->Length;
443 
444     /* Check if it's not too much */
445     if (LengthUsed > 0xFFF0)
446         return STATUS_NAME_TOO_LONG;
447 
448     /* Optimization: check if the new name is shorter */
449     if (FullPath->MaximumLength <= LengthUsed)
450     {
451         /* It's not, allocate a new one */
452         MaximumLength = LengthUsed + sizeof(WCHAR);
453         NewTargetPath = ExAllocatePoolWithTag(NonPagedPool,
454                                               MaximumLength,
455                                               OB_NAME_TAG);
456         if (!NewTargetPath) return STATUS_INSUFFICIENT_RESOURCES;
457     }
458     else
459     {
460         /* It is! Reuse the name... */
461         MaximumLength = FullPath->MaximumLength;
462         NewTargetPath = FullPath->Buffer;
463     }
464 
465     /* Make sure we have a length */
466     if (RemainingName->Length)
467     {
468         /* Copy the new path */
469         RtlMoveMemory((PVOID)((ULONG_PTR)NewTargetPath + TempLength),
470                       RemainingName->Buffer,
471                       RemainingName->Length);
472     }
473 
474     /* Copy the target path and null-terminate it */
475     RtlCopyMemory(NewTargetPath, TargetPath->Buffer, TempLength);
476     NewTargetPath[LengthUsed / sizeof(WCHAR)] = UNICODE_NULL;
477 
478     /* If the optimization didn't work, free the old buffer */
479     if (NewTargetPath != FullPath->Buffer) ExFreePool(FullPath->Buffer);
480 
481     /* Update the path values */
482     FullPath->Length = (USHORT)LengthUsed;
483     FullPath->MaximumLength = (USHORT)MaximumLength;
484     FullPath->Buffer = NewTargetPath;
485 
486     /* Tell the parse routine to start reparsing */
487     return STATUS_REPARSE;
488 }
489 
490 /* PUBLIC FUNCTIONS **********************************************************/
491 
492 /*++
493 * @name NtCreateSymbolicLinkObject
494 * @implemented NT4
495 *
496 *     The NtCreateSymbolicLinkObject opens or creates a symbolic link object.
497 *
498 * @param LinkHandle
499 *        Variable which receives the symlink handle.
500 *
501 * @param DesiredAccess
502 *        Desired access to the symlink.
503 *
504 * @param ObjectAttributes
505 *        Structure describing the symlink.
506 *
507 * @param LinkTarget
508 *        Unicode string defining the symlink's target
509 *
510 * @return STATUS_SUCCESS or appropriate error value.
511 *
512 * @remarks None.
513 *
514 *--*/
515 NTSTATUS
516 NTAPI
517 NtCreateSymbolicLinkObject(OUT PHANDLE LinkHandle,
518                            IN ACCESS_MASK DesiredAccess,
519                            IN POBJECT_ATTRIBUTES ObjectAttributes,
520                            IN PUNICODE_STRING LinkTarget)
521 {
522     HANDLE hLink;
523     POBJECT_SYMBOLIC_LINK SymbolicLink;
524     UNICODE_STRING CapturedLinkTarget;
525     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
526     NTSTATUS Status;
527     PAGED_CODE();
528 
529     /* Check if we need to probe parameters */
530     if (PreviousMode != KernelMode)
531     {
532         _SEH2_TRY
533         {
534             /* Probe the target */
535             CapturedLinkTarget = ProbeForReadUnicodeString(LinkTarget);
536             ProbeForRead(CapturedLinkTarget.Buffer,
537                          CapturedLinkTarget.MaximumLength,
538                          sizeof(WCHAR));
539 
540             /* Probe the return handle */
541             ProbeForWriteHandle(LinkHandle);
542         }
543         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
544         {
545             /* Return the exception code */
546             _SEH2_YIELD(return _SEH2_GetExceptionCode());
547         }
548         _SEH2_END;
549     }
550     else
551     {
552         /* No need to capture */
553         CapturedLinkTarget = *LinkTarget;
554     }
555 
556     /* Check if the maximum length is odd */
557     if (CapturedLinkTarget.MaximumLength % sizeof(WCHAR))
558     {
559         /* Round it down */
560         CapturedLinkTarget.MaximumLength =
561             (USHORT)ALIGN_DOWN(CapturedLinkTarget.MaximumLength, WCHAR);
562     }
563 
564     /* Fail if the length is odd, or if the maximum is smaller or 0 */
565     if ((CapturedLinkTarget.Length % sizeof(WCHAR)) ||
566         (CapturedLinkTarget.MaximumLength < CapturedLinkTarget.Length) ||
567         !(CapturedLinkTarget.MaximumLength))
568     {
569         /* This message is displayed on the debugger in Windows */
570         DbgPrint("OB: Invalid symbolic link target - %wZ\n",
571                  &CapturedLinkTarget);
572         return STATUS_INVALID_PARAMETER;
573     }
574 
575     /* Create the object */
576     Status = ObCreateObject(PreviousMode,
577                             ObpSymbolicLinkObjectType,
578                             ObjectAttributes,
579                             PreviousMode,
580                             NULL,
581                             sizeof(OBJECT_SYMBOLIC_LINK),
582                             0,
583                             0,
584                             (PVOID*)&SymbolicLink);
585     if (NT_SUCCESS(Status))
586     {
587         /* Success! Fill in the creation time immediately */
588         KeQuerySystemTime(&SymbolicLink->CreationTime);
589 
590         /* Setup the target name */
591         SymbolicLink->LinkTarget.Length = CapturedLinkTarget.Length;
592         SymbolicLink->LinkTarget.MaximumLength = CapturedLinkTarget.MaximumLength;
593         SymbolicLink->LinkTarget.Buffer =
594             ExAllocatePoolWithTag(PagedPool,
595                                   CapturedLinkTarget.MaximumLength,
596                                   TAG_SYMLINK_TARGET);
597         if (!SymbolicLink->LinkTarget.Buffer)
598         {
599             /* Dereference the symbolic link object and fail */
600             ObDereferenceObject(SymbolicLink);
601             return STATUS_NO_MEMORY;
602         }
603 
604         /* Copy it */
605         _SEH2_TRY
606         {
607             RtlCopyMemory(SymbolicLink->LinkTarget.Buffer,
608                           CapturedLinkTarget.Buffer,
609                           CapturedLinkTarget.MaximumLength);
610         }
611         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
612         {
613             ObDereferenceObject(SymbolicLink);
614             _SEH2_YIELD(return _SEH2_GetExceptionCode());
615         }
616         _SEH2_END;
617 
618         /* Initialize the remaining name, dos drive index and target object */
619         SymbolicLink->LinkTargetObject = NULL;
620         SymbolicLink->DosDeviceDriveIndex = 0;
621         RtlInitUnicodeString(&SymbolicLink->LinkTargetRemaining, NULL);
622 
623         /* Insert it into the object tree */
624         Status = ObInsertObject(SymbolicLink,
625                                 NULL,
626                                 DesiredAccess,
627                                 0,
628                                 NULL,
629                                 &hLink);
630         if (NT_SUCCESS(Status))
631         {
632             _SEH2_TRY
633             {
634                 /* Return the handle to caller */
635                 *LinkHandle = hLink;
636             }
637             _SEH2_EXCEPT(ExSystemExceptionFilter())
638             {
639                 /* Get exception code */
640                 Status = _SEH2_GetExceptionCode();
641             }
642             _SEH2_END;
643         }
644     }
645 
646     /* Return status to caller */
647     return Status;
648 }
649 
650 /*++
651 * @name NtOpenSymbolicLinkObject
652 * @implemented NT4
653 *
654 *     The NtOpenSymbolicLinkObject opens a symbolic link object.
655 *
656 * @param LinkHandle
657 *        Variable which receives the symlink handle.
658 *
659 * @param DesiredAccess
660 *        Desired access to the symlink.
661 *
662 * @param ObjectAttributes
663 *        Structure describing the symlink.
664 *
665 * @return STATUS_SUCCESS or appropriate error value.
666 *
667 * @remarks None.
668 *
669 *--*/
670 NTSTATUS
671 NTAPI
672 NtOpenSymbolicLinkObject(OUT PHANDLE LinkHandle,
673                          IN ACCESS_MASK DesiredAccess,
674                          IN POBJECT_ATTRIBUTES ObjectAttributes)
675 {
676     HANDLE hLink;
677     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
678     NTSTATUS Status;
679     PAGED_CODE();
680 
681     /* Check if we need to probe parameters */
682     if (PreviousMode != KernelMode)
683     {
684         _SEH2_TRY
685         {
686             /* Probe the return handle */
687             ProbeForWriteHandle(LinkHandle);
688         }
689         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
690         {
691             /* Return the exception code */
692             _SEH2_YIELD(return _SEH2_GetExceptionCode());
693         }
694         _SEH2_END;
695     }
696 
697     /* Open the object */
698     Status = ObOpenObjectByName(ObjectAttributes,
699                                 ObpSymbolicLinkObjectType,
700                                 PreviousMode,
701                                 NULL,
702                                 DesiredAccess,
703                                 NULL,
704                                 &hLink);
705 
706     _SEH2_TRY
707     {
708         /* Return the handle to caller */
709         *LinkHandle = hLink;
710     }
711     _SEH2_EXCEPT(ExSystemExceptionFilter())
712     {
713         /* Get exception code */
714         Status = _SEH2_GetExceptionCode();
715     }
716     _SEH2_END;
717 
718     /* Return status to caller */
719     return Status;
720 }
721 
722 /*++
723 * @name NtQuerySymbolicLinkObject
724 * @implemented NT4
725 *
726 *     The NtQuerySymbolicLinkObject queries a symbolic link object.
727 *
728 * @param LinkHandle
729 *        Symlink handle to query
730 *
731 * @param LinkTarget
732 *        Unicode string defining the symlink's target
733 *
734 * @param ResultLength
735 *        Caller supplied storage for the number of bytes written (or NULL).
736 *
737 * @return STATUS_SUCCESS or appropriate error value.
738 *
739 * @remarks None.
740 *
741 *--*/
742 NTSTATUS
743 NTAPI
744 NtQuerySymbolicLinkObject(IN HANDLE LinkHandle,
745                           OUT PUNICODE_STRING LinkTarget,
746                           OUT PULONG ResultLength OPTIONAL)
747 {
748     UNICODE_STRING SafeLinkTarget = { 0, 0, NULL };
749     POBJECT_SYMBOLIC_LINK SymlinkObject;
750     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
751     NTSTATUS Status;
752     ULONG LengthUsed;
753     PAGED_CODE();
754 
755     if (PreviousMode != KernelMode)
756     {
757         _SEH2_TRY
758         {
759             /* Probe the unicode string for read and write */
760             ProbeForWriteUnicodeString(LinkTarget);
761 
762             /* Probe the unicode string's buffer for write */
763             SafeLinkTarget = *LinkTarget;
764             ProbeForWrite(SafeLinkTarget.Buffer,
765                           SafeLinkTarget.MaximumLength,
766                           sizeof(WCHAR));
767 
768             /* Probe the return length */
769             if (ResultLength) ProbeForWriteUlong(ResultLength);
770         }
771         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
772         {
773             /* Return the exception code */
774             _SEH2_YIELD(return _SEH2_GetExceptionCode());
775         }
776         _SEH2_END;
777     }
778     else
779     {
780         /* No need to probe */
781         SafeLinkTarget = *LinkTarget;
782     }
783 
784     /* Reference the object */
785     Status = ObReferenceObjectByHandle(LinkHandle,
786                                        SYMBOLIC_LINK_QUERY,
787                                        ObpSymbolicLinkObjectType,
788                                        PreviousMode,
789                                        (PVOID *)&SymlinkObject,
790                                        NULL);
791     if (NT_SUCCESS(Status))
792     {
793         /* Lock the object */
794         ObpAcquireObjectLock(OBJECT_TO_OBJECT_HEADER(SymlinkObject));
795 
796         /*
797          * So here's the thing: If you specify a return length, then the
798          * implementation will use the maximum length. If you don't, then
799          * it will use the length.
800          */
801         LengthUsed = ResultLength ? SymlinkObject->LinkTarget.MaximumLength :
802                                     SymlinkObject->LinkTarget.Length;
803 
804         /* Enter SEH so we can safely copy */
805         _SEH2_TRY
806         {
807             /* Make sure our buffer will fit */
808             if (LengthUsed <= SafeLinkTarget.MaximumLength)
809             {
810                 /* Copy the buffer */
811                 RtlCopyMemory(SafeLinkTarget.Buffer,
812                               SymlinkObject->LinkTarget.Buffer,
813                               LengthUsed);
814 
815                 /* Copy the new length */
816                 LinkTarget->Length = SymlinkObject->LinkTarget.Length;
817             }
818             else
819             {
820                 /* Otherwise set the failure status */
821                 Status = STATUS_BUFFER_TOO_SMALL;
822             }
823 
824             /* In both cases, check if the required length was requested */
825             if (ResultLength)
826             {
827                 /* Then return it */
828                 *ResultLength = SymlinkObject->LinkTarget.MaximumLength;
829             }
830         }
831         _SEH2_EXCEPT(ExSystemExceptionFilter())
832         {
833             /* Get the error code */
834             Status = _SEH2_GetExceptionCode();
835         }
836         _SEH2_END;
837 
838         /* Unlock and dereference the object */
839         ObpReleaseObjectLock(OBJECT_TO_OBJECT_HEADER(SymlinkObject));
840         ObDereferenceObject(SymlinkObject);
841     }
842 
843     /* Return query status */
844     return Status;
845 }
846 
847 /* EOF */
848