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