xref: /reactos/drivers/storage/mountmgr/symlink.c (revision 53221834)
1 /*
2  *  ReactOS kernel
3  *  Copyright (C) 2011-2012 ReactOS Team
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  * COPYRIGHT:        See COPYING in the top level directory
20  * PROJECT:          ReactOS kernel
21  * FILE:             drivers/filesystem/mountmgr/symlink.c
22  * PURPOSE:          Mount Manager - Symbolic links functions
23  * PROGRAMMER:       Pierre Schweitzer (pierre.schweitzer@reactos.org)
24  */
25 
26 #include "mntmgr.h"
27 
28 #define NDEBUG
29 #include <debug.h>
30 
31 UNICODE_STRING DeviceMount = RTL_CONSTANT_STRING(MOUNTMGR_DEVICE_NAME);
32 UNICODE_STRING DosDevicesMount = RTL_CONSTANT_STRING(L"\\DosDevices\\MountPointManager");
33 UNICODE_STRING DosDevices = RTL_CONSTANT_STRING(L"\\DosDevices\\");
34 UNICODE_STRING DeviceFloppy = RTL_CONSTANT_STRING(L"\\Device\\Floppy");
35 UNICODE_STRING DeviceCdRom = RTL_CONSTANT_STRING(L"\\Device\\CdRom");
36 UNICODE_STRING DosGlobal = RTL_CONSTANT_STRING(L"\\GLOBAL??\\");
37 UNICODE_STRING Global = RTL_CONSTANT_STRING(L"\\??\\");
38 UNICODE_STRING SafeVolumes = RTL_CONSTANT_STRING(L"\\Device\\VolumesSafeForWriteAccess");
39 UNICODE_STRING Volume = RTL_CONSTANT_STRING(L"\\??\\Volume");
40 UNICODE_STRING ReparseIndex = RTL_CONSTANT_STRING(L"\\$Extend\\$Reparse:$R:$INDEX_ALLOCATION");
41 
42 /*
43  * @implemented
44  */
45 NTSTATUS
46 CreateStringWithGlobal(IN PUNICODE_STRING DosName,
47                        OUT PUNICODE_STRING GlobalString)
48 {
49     UNICODE_STRING IntGlobal;
50 
51     if (RtlPrefixUnicodeString(&DosDevices, DosName, TRUE))
52     {
53         /* DOS device - use DOS global */
54         IntGlobal.Length = DosName->Length - DosDevices.Length + DosGlobal.Length;
55         IntGlobal.MaximumLength = IntGlobal.Length + sizeof(WCHAR);
56         IntGlobal.Buffer = AllocatePool(IntGlobal.MaximumLength);
57         if (!IntGlobal.Buffer)
58         {
59             return STATUS_INSUFFICIENT_RESOURCES;
60         }
61 
62         RtlCopyMemory(IntGlobal.Buffer, DosGlobal.Buffer, DosGlobal.Length);
63         RtlCopyMemory(IntGlobal.Buffer + (DosGlobal.Length / sizeof(WCHAR)),
64                       DosName->Buffer + (DosDevices.Length / sizeof(WCHAR)),
65                       DosName->Length - DosDevices.Length);
66         IntGlobal.Buffer[IntGlobal.Length / sizeof(WCHAR)] = UNICODE_NULL;
67     }
68     else if (RtlPrefixUnicodeString(&Global, DosName, TRUE))
69     {
70         /* Switch to DOS global */
71         IntGlobal.Length = DosName->Length - Global.Length + DosGlobal.Length;
72         IntGlobal.MaximumLength = IntGlobal.Length + sizeof(WCHAR);
73         IntGlobal.Buffer = AllocatePool(IntGlobal.MaximumLength);
74         if (!IntGlobal.Buffer)
75         {
76             return STATUS_INSUFFICIENT_RESOURCES;
77         }
78 
79         RtlCopyMemory(IntGlobal.Buffer, DosGlobal.Buffer, DosGlobal.Length);
80         RtlCopyMemory(IntGlobal.Buffer + (DosGlobal.Length / sizeof(WCHAR)),
81                       DosName->Buffer + (Global.Length / sizeof(WCHAR)),
82                       DosName->Length - Global.Length);
83         IntGlobal.Buffer[IntGlobal.Length / sizeof(WCHAR)] = UNICODE_NULL;
84     }
85     else
86     {
87         /* Simply duplicate string */
88         IntGlobal.Length = DosName->Length;
89         IntGlobal.MaximumLength = DosName->MaximumLength;
90         IntGlobal.Buffer = AllocatePool(IntGlobal.MaximumLength);
91         if (!IntGlobal.Buffer)
92         {
93             return STATUS_INSUFFICIENT_RESOURCES;
94         }
95 
96         RtlCopyMemory(IntGlobal.Buffer, DosName->Buffer, IntGlobal.MaximumLength);
97     }
98 
99     /* Return string */
100     GlobalString->Length = IntGlobal.Length;
101     GlobalString->MaximumLength = IntGlobal.MaximumLength;
102     GlobalString->Buffer = IntGlobal.Buffer;
103 
104     return STATUS_SUCCESS;
105 }
106 
107 /*
108  * @implemented
109  */
110 NTSTATUS
111 GlobalCreateSymbolicLink(IN PUNICODE_STRING DosName,
112                          IN PUNICODE_STRING DeviceName)
113 {
114     NTSTATUS Status;
115     UNICODE_STRING GlobalName;
116 
117     /* First create the global string */
118     Status = CreateStringWithGlobal(DosName, &GlobalName);
119     if (!NT_SUCCESS(Status))
120     {
121         return Status;
122     }
123 
124     /* Then, create the symlink */
125     Status = IoCreateSymbolicLink(&GlobalName, DeviceName);
126 
127     FreePool(GlobalName.Buffer);
128 
129     return Status;
130 }
131 
132 /*
133  * @implemented
134  */
135 NTSTATUS
136 GlobalDeleteSymbolicLink(IN PUNICODE_STRING DosName)
137 {
138     NTSTATUS Status;
139     UNICODE_STRING GlobalName;
140 
141     /* Recreate the string (to find the link) */
142     Status = CreateStringWithGlobal(DosName, &GlobalName);
143     if (!NT_SUCCESS(Status))
144     {
145         return Status;
146     }
147 
148     /* And delete the link */
149     Status = IoDeleteSymbolicLink(&GlobalName);
150 
151     FreePool(GlobalName.Buffer);
152 
153     return Status;
154 }
155 
156 /*
157  * @implemented
158  */
159 VOID
160 SendLinkCreated(IN PUNICODE_STRING SymbolicName)
161 {
162     PIRP Irp;
163     KEVENT Event;
164     ULONG NameSize;
165     NTSTATUS Status;
166     PFILE_OBJECT FileObject;
167     PIO_STACK_LOCATION Stack;
168     PMOUNTDEV_NAME Name = NULL;
169     PDEVICE_OBJECT DeviceObject;
170     IO_STATUS_BLOCK IoStatusBlock;
171 
172     /* Get the device associated with the name */
173     Status = IoGetDeviceObjectPointer(SymbolicName,
174                                       FILE_READ_ATTRIBUTES,
175                                       &FileObject,
176                                       &DeviceObject);
177     if (!NT_SUCCESS(Status))
178     {
179         return;
180     }
181 
182     /* Get attached device (will notify it) */
183     DeviceObject = IoGetAttachedDeviceReference(FileObject->DeviceObject);
184 
185     /* NameSize is the size of the whole MOUNTDEV_NAME struct */
186     NameSize = sizeof(USHORT) + SymbolicName->Length;
187     Name = AllocatePool(NameSize);
188     if (!Name)
189     {
190         goto Cleanup;
191     }
192 
193     /* Initialize struct */
194     Name->NameLength = SymbolicName->Length;
195     RtlCopyMemory(Name->Name, SymbolicName->Buffer, SymbolicName->Length);
196 
197     KeInitializeEvent(&Event, NotificationEvent, FALSE);
198     /* Microsoft does it twice... Once with limited access, second with any
199      * So, first one here
200      */
201     Irp = IoBuildDeviceIoControlRequest(CTL_CODE(MOUNTDEVCONTROLTYPE, 4, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
202                                         DeviceObject,
203                                         Name,
204                                         NameSize,
205                                         NULL,
206                                         0,
207                                         FALSE,
208                                         &Event,
209                                         &IoStatusBlock);
210     /* This one can fail, no one matters */
211     if (Irp)
212     {
213         Stack = IoGetNextIrpStackLocation(Irp);
214         Stack->FileObject = FileObject;
215 
216         Status = IoCallDriver(DeviceObject, Irp);
217         if (Status == STATUS_PENDING)
218         {
219             KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
220         }
221     }
222 
223     /* Then, second one */
224     KeInitializeEvent(&Event, NotificationEvent, FALSE);
225     Irp = IoBuildDeviceIoControlRequest(IOCTL_MOUNTDEV_LINK_CREATED,
226                                         DeviceObject,
227                                         Name,
228                                         NameSize,
229                                         NULL,
230                                         0,
231                                         FALSE,
232                                         &Event,
233                                         &IoStatusBlock);
234     if (!Irp)
235     {
236         goto Cleanup;
237     }
238 
239     Stack = IoGetNextIrpStackLocation(Irp);
240     Stack->FileObject = FileObject;
241 
242     /* Really notify */
243     Status = IoCallDriver(DeviceObject, Irp);
244     if (Status == STATUS_PENDING)
245     {
246         KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
247     }
248 
249 Cleanup:
250     if (Name)
251     {
252         FreePool(Name);
253     }
254 
255     ObDereferenceObject(DeviceObject);
256     ObDereferenceObject(FileObject);
257 
258     return;
259 }
260 
261 /*
262  * @implemented
263  */
264 VOID
265 SendLinkDeleted(IN PUNICODE_STRING DeviceName,
266                 IN PUNICODE_STRING SymbolicName)
267 {
268     PIRP Irp;
269     KEVENT Event;
270     ULONG NameSize;
271     NTSTATUS Status;
272     PFILE_OBJECT FileObject;
273     PIO_STACK_LOCATION Stack;
274     PMOUNTDEV_NAME Name = NULL;
275     PDEVICE_OBJECT DeviceObject;
276     IO_STATUS_BLOCK IoStatusBlock;
277 
278     /* Get the device associated with the name */
279     Status = IoGetDeviceObjectPointer(DeviceName,
280                                       FILE_READ_ATTRIBUTES,
281                                       &FileObject,
282                                       &DeviceObject);
283     if (!NT_SUCCESS(Status))
284     {
285         return;
286     }
287 
288     /* Get attached device (will notify it) */
289     DeviceObject = IoGetAttachedDeviceReference(FileObject->DeviceObject);
290 
291     /* NameSize is the size of the whole MOUNTDEV_NAME struct */
292     NameSize = sizeof(USHORT) + SymbolicName->Length;
293     Name = AllocatePool(NameSize);
294     if (!Name)
295     {
296         goto Cleanup;
297     }
298 
299     /* Initialize struct */
300     Name->NameLength = SymbolicName->Length;
301     RtlCopyMemory(Name->Name, SymbolicName->Buffer, SymbolicName->Length);
302 
303     KeInitializeEvent(&Event, NotificationEvent, FALSE);
304     /* Cf: SendLinkCreated comment */
305     Irp = IoBuildDeviceIoControlRequest(CTL_CODE(MOUNTDEVCONTROLTYPE, 5, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
306                                         DeviceObject,
307                                         Name,
308                                         NameSize,
309                                         NULL,
310                                         0,
311                                         FALSE,
312                                         &Event,
313                                         &IoStatusBlock);
314     /* This one can fail, no one matters */
315     if (Irp)
316     {
317         Stack = IoGetNextIrpStackLocation(Irp);
318         Stack->FileObject = FileObject;
319 
320         Status = IoCallDriver(DeviceObject, Irp);
321         if (Status == STATUS_PENDING)
322         {
323             KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
324         }
325     }
326 
327     /* Then, second one */
328     KeInitializeEvent(&Event, NotificationEvent, FALSE);
329     Irp = IoBuildDeviceIoControlRequest(IOCTL_MOUNTDEV_LINK_DELETED,
330                                         DeviceObject,
331                                         Name,
332                                         NameSize,
333                                         NULL,
334                                         0,
335                                         FALSE,
336                                         &Event,
337                                         &IoStatusBlock);
338     if (!Irp)
339     {
340         goto Cleanup;
341     }
342 
343     Stack = IoGetNextIrpStackLocation(Irp);
344     Stack->FileObject = FileObject;
345 
346     /* Really notify */
347     Status = IoCallDriver(DeviceObject, Irp);
348     if (Status == STATUS_PENDING)
349     {
350         KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
351     }
352 
353 Cleanup:
354     if (Name)
355     {
356         FreePool(Name);
357     }
358 
359     ObDereferenceObject(DeviceObject);
360     ObDereferenceObject(FileObject);
361 
362     return;
363 }
364 
365 /*
366  * @implemented
367  */
368 NTSTATUS
369 NTAPI
370 SymbolicLinkNamesFromUniqueIdCount(IN PWSTR ValueName,
371                                    IN ULONG ValueType,
372                                    IN PVOID ValueData,
373                                    IN ULONG ValueLength,
374                                    IN PVOID Context,
375                                    IN PVOID EntryContext)
376 {
377     UNICODE_STRING ValueNameString;
378     PMOUNTDEV_UNIQUE_ID UniqueId = Context;
379 
380     if (ValueName[0] != L'#' || ValueType != REG_BINARY ||
381         (UniqueId->UniqueIdLength != ValueLength))
382     {
383         return STATUS_SUCCESS;
384     }
385 
386     if (RtlCompareMemory(UniqueId->UniqueId, ValueData, ValueLength) != ValueLength)
387     {
388         return STATUS_SUCCESS;
389     }
390 
391     /* That one matched, increase count */
392     RtlInitUnicodeString(&ValueNameString, ValueName);
393     if (ValueNameString.Length)
394     {
395         (*((PULONG)EntryContext))++;
396     }
397 
398     return STATUS_SUCCESS;
399 }
400 
401 /*
402  * @implemented
403  */
404 NTSTATUS
405 NTAPI
406 SymbolicLinkNamesFromUniqueIdQuery(IN PWSTR ValueName,
407                                    IN ULONG ValueType,
408                                    IN PVOID ValueData,
409                                    IN ULONG ValueLength,
410                                    IN PVOID Context,
411                                    IN PVOID EntryContext)
412 {
413     UNICODE_STRING ValueNameString;
414     PMOUNTDEV_UNIQUE_ID UniqueId = Context;
415     /* Unicode strings table */
416     PUNICODE_STRING ReturnString = EntryContext;
417 
418     if (ValueName[0] != L'#' || ValueType != REG_BINARY ||
419         (UniqueId->UniqueIdLength != ValueLength))
420     {
421         return STATUS_SUCCESS;
422     }
423 
424     if (RtlCompareMemory(UniqueId->UniqueId, ValueData, ValueLength) != ValueLength)
425     {
426         return STATUS_SUCCESS;
427     }
428 
429     /* Unique ID matches, let's put the symlink */
430     RtlInitUnicodeString(&ValueNameString, ValueName);
431     if (!ValueNameString.Length)
432     {
433         return STATUS_SUCCESS;
434     }
435 
436     /* Allocate string to copy */
437     ValueNameString.Buffer = AllocatePool(ValueNameString.MaximumLength);
438     if (!ValueNameString.Buffer)
439     {
440         return STATUS_SUCCESS;
441     }
442 
443     /* Copy */
444     RtlCopyMemory(ValueNameString.Buffer, ValueName, ValueNameString.Length);
445     ValueNameString.Buffer[ValueNameString.Length / sizeof(WCHAR)] = UNICODE_NULL;
446 
447     while (ReturnString->Length)
448     {
449         ReturnString++;
450     }
451 
452     /* And return that string */
453     *ReturnString = ValueNameString;
454 
455     return STATUS_SUCCESS;
456 }
457 
458 /*
459  * @implemented
460  */
461 NTSTATUS
462 CreateNewVolumeName(OUT PUNICODE_STRING VolumeName,
463                     IN PGUID VolumeGuid OPTIONAL)
464 {
465     GUID Guid;
466     NTSTATUS Status;
467     UNICODE_STRING GuidString;
468 
469     /* If no GUID was provided, then create one */
470     if (!VolumeGuid)
471     {
472         Status = ExUuidCreate(&Guid);
473         if (!NT_SUCCESS(Status))
474         {
475             return Status;
476         }
477     }
478     else
479     {
480         RtlCopyMemory(&Guid, VolumeGuid, sizeof(GUID));
481     }
482 
483     /* Convert GUID to string */
484     Status = RtlStringFromGUID(&Guid, &GuidString);
485     if (!NT_SUCCESS(Status))
486     {
487         return Status;
488     }
489 
490     /* Size for volume namespace, literal GUID, and null char */
491     VolumeName->MaximumLength = 0x14 + 0x4C + sizeof(UNICODE_NULL);
492     VolumeName->Buffer = AllocatePool(0x14 + 0x4C + sizeof(UNICODE_NULL));
493     if (!VolumeName->Buffer)
494     {
495         Status = STATUS_INSUFFICIENT_RESOURCES;
496     }
497     else
498     {
499         RtlCopyUnicodeString(VolumeName, &Volume);
500         RtlAppendUnicodeStringToString(VolumeName, &GuidString);
501         VolumeName->Buffer[VolumeName->Length / sizeof(WCHAR)] = UNICODE_NULL;
502         Status = STATUS_SUCCESS;
503     }
504 
505     ExFreePoolWithTag(GuidString.Buffer, 0);
506 
507     return Status;
508 }
509 
510 /*
511  * @implemented
512  */
513 NTSTATUS
514 QuerySymbolicLinkNamesFromStorage(IN PDEVICE_EXTENSION DeviceExtension,
515                                   IN PDEVICE_INFORMATION DeviceInformation,
516                                   IN PUNICODE_STRING SuggestedLinkName,
517                                   IN BOOLEAN UseOnlyIfThereAreNoOtherLinks,
518                                   OUT PUNICODE_STRING * SymLinks,
519                                   OUT PULONG SymLinkCount,
520                                   IN BOOLEAN HasGuid,
521                                   IN LPGUID Guid)
522 {
523     NTSTATUS Status;
524     BOOLEAN WriteNew;
525     RTL_QUERY_REGISTRY_TABLE QueryTable[2];
526 
527     UNREFERENCED_PARAMETER(DeviceExtension);
528 
529     /* First of all, count links */
530     RtlZeroMemory(QueryTable, sizeof(QueryTable));
531     QueryTable[0].QueryRoutine = SymbolicLinkNamesFromUniqueIdCount;
532     QueryTable[0].EntryContext = SymLinkCount;
533     *SymLinkCount = 0;
534 
535     Status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
536                                     DatabasePath,
537                                     QueryTable,
538                                     DeviceInformation->UniqueId,
539                                     NULL);
540     if (!NT_SUCCESS(Status))
541     {
542         *SymLinkCount = 0;
543     }
544 
545     /* Check if we have to write a new one first */
546     if (SuggestedLinkName && !IsDriveLetter(SuggestedLinkName) &&
547         UseOnlyIfThereAreNoOtherLinks && *SymLinkCount == 0)
548     {
549         WriteNew = TRUE;
550     }
551     else
552     {
553         WriteNew = FALSE;
554     }
555 
556     /* If has GUID, it makes one more link */
557     if (HasGuid)
558     {
559         (*SymLinkCount)++;
560     }
561 
562     if (WriteNew)
563     {
564         /* Write link */
565         RtlWriteRegistryValue(RTL_REGISTRY_ABSOLUTE,
566                               DatabasePath,
567                               SuggestedLinkName->Buffer,
568                               REG_BINARY,
569                               DeviceInformation->UniqueId->UniqueId,
570                               DeviceInformation->UniqueId->UniqueIdLength);
571 
572         /* And recount all the needed links */
573         RtlZeroMemory(QueryTable, sizeof(QueryTable));
574         QueryTable[0].QueryRoutine = SymbolicLinkNamesFromUniqueIdCount;
575         QueryTable[0].EntryContext = SymLinkCount;
576         *SymLinkCount = 0;
577 
578         Status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
579                                         DatabasePath,
580                                         QueryTable,
581                                         DeviceInformation->UniqueId,
582                                         NULL);
583         if (!NT_SUCCESS(Status))
584         {
585             return STATUS_NOT_FOUND;
586         }
587     }
588 
589     /* Not links found? */
590     if (!*SymLinkCount)
591     {
592         return STATUS_NOT_FOUND;
593     }
594 
595     /* Allocate a buffer big enough to hold symlinks (table of unicode strings) */
596     *SymLinks = AllocatePool(*SymLinkCount * sizeof(UNICODE_STRING));
597     if (!*SymLinks)
598     {
599         return STATUS_INSUFFICIENT_RESOURCES;
600     }
601 
602     /* Prepare to query links */
603     RtlZeroMemory(*SymLinks, *SymLinkCount * sizeof(UNICODE_STRING));
604     RtlZeroMemory(QueryTable, sizeof(QueryTable));
605     QueryTable[0].QueryRoutine = SymbolicLinkNamesFromUniqueIdQuery;
606 
607     /* No GUID? Keep it that way */
608     if (!HasGuid)
609     {
610         QueryTable[0].EntryContext = *SymLinks;
611     }
612     /* Otherwise, first create volume name */
613     else
614     {
615         Status = CreateNewVolumeName(SymLinks[0], Guid);
616         if (!NT_SUCCESS(Status))
617         {
618             FreePool(*SymLinks);
619             return Status;
620         }
621 
622         /* Skip first link (ours) */
623         QueryTable[0].EntryContext = *SymLinks + 1;
624     }
625 
626     /* Now, query */
627     RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
628                            DatabasePath,
629                            QueryTable,
630                            DeviceInformation->UniqueId,
631                            NULL);
632 
633     return STATUS_SUCCESS;
634 }
635 
636 /*
637  * @implemented
638  */
639 PSAVED_LINK_INFORMATION
640 RemoveSavedLinks(IN PDEVICE_EXTENSION DeviceExtension,
641                  IN PMOUNTDEV_UNIQUE_ID UniqueId)
642 {
643     PLIST_ENTRY NextEntry;
644     PSAVED_LINK_INFORMATION SavedLinkInformation;
645 
646     /* No saved links? Easy! */
647     if (IsListEmpty(&(DeviceExtension->SavedLinksListHead)))
648     {
649         return NULL;
650     }
651 
652     /* Now, browse saved links */
653     for (NextEntry = DeviceExtension->SavedLinksListHead.Flink;
654          NextEntry != &(DeviceExtension->SavedLinksListHead);
655          NextEntry = NextEntry->Flink)
656     {
657         SavedLinkInformation = CONTAINING_RECORD(NextEntry,
658                                                  SAVED_LINK_INFORMATION,
659                                                  SavedLinksListEntry);
660 
661         /* Find the one that matches */
662         if (SavedLinkInformation->UniqueId->UniqueIdLength == UniqueId->UniqueIdLength)
663         {
664             if (RtlCompareMemory(SavedLinkInformation->UniqueId->UniqueId,
665                                  UniqueId->UniqueId,
666                                  UniqueId->UniqueIdLength) ==
667                 UniqueId->UniqueIdLength)
668             {
669                 /* Remove it and return it */
670                 RemoveEntryList(&(SavedLinkInformation->SavedLinksListEntry));
671                 return SavedLinkInformation;
672             }
673         }
674     }
675 
676     /* None found (none removed) */
677     return NULL;
678 }
679 
680 /*
681  * @implemented
682  */
683 NTSTATUS
684 QuerySuggestedLinkName(IN PUNICODE_STRING SymbolicName,
685                        OUT PUNICODE_STRING SuggestedLinkName,
686                        OUT PBOOLEAN UseOnlyIfThereAreNoOtherLinks)
687 {
688     PIRP Irp;
689     KEVENT Event;
690     NTSTATUS Status;
691     USHORT NameLength;
692     PFILE_OBJECT FileObject;
693     PDEVICE_OBJECT DeviceObject;
694     IO_STATUS_BLOCK IoStatusBlock;
695     PIO_STACK_LOCATION IoStackLocation;
696     PMOUNTDEV_SUGGESTED_LINK_NAME IoCtlSuggested;
697 
698     /* First, get device */
699     Status = IoGetDeviceObjectPointer(SymbolicName,
700                                       FILE_READ_ATTRIBUTES,
701                                       &FileObject,
702                                       &DeviceObject);
703     if (!NT_SUCCESS(Status))
704     {
705         return Status;
706     }
707 
708     /* Then, get attached device */
709     DeviceObject = IoGetAttachedDeviceReference(FileObject->DeviceObject);
710 
711     /* Then, prepare buffer to query suggested name */
712     IoCtlSuggested = AllocatePool(sizeof(MOUNTDEV_SUGGESTED_LINK_NAME));
713     if (!IoCtlSuggested)
714     {
715         Status = STATUS_INSUFFICIENT_RESOURCES;
716         goto Dereference;
717     }
718 
719     /* Prepare request */
720     KeInitializeEvent(&Event, NotificationEvent, FALSE);
721     Irp = IoBuildDeviceIoControlRequest(IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME,
722                                         DeviceObject,
723                                         NULL,
724                                         0,
725                                         IoCtlSuggested,
726                                         sizeof(MOUNTDEV_SUGGESTED_LINK_NAME),
727                                         FALSE,
728                                         &Event,
729                                         &IoStatusBlock);
730     if (!Irp)
731     {
732         Status = STATUS_INSUFFICIENT_RESOURCES;
733         goto Release;
734     }
735 
736     IoStackLocation = IoGetNextIrpStackLocation(Irp);
737     IoStackLocation->FileObject = FileObject;
738 
739     /* And ask */
740     Status = IoCallDriver(DeviceObject, Irp);
741     if (Status == STATUS_PENDING)
742     {
743         KeWaitForSingleObject(&Event, Executive, KernelMode,
744                               FALSE, NULL);
745         Status = IoStatusBlock.Status;
746     }
747 
748     /* Overflow? Normal */
749     if (Status == STATUS_BUFFER_OVERFLOW)
750     {
751         /* Reallocate big enough buffer */
752         NameLength = IoCtlSuggested->NameLength + sizeof(MOUNTDEV_SUGGESTED_LINK_NAME);
753         FreePool(IoCtlSuggested);
754 
755         IoCtlSuggested = AllocatePool(NameLength);
756         if (!IoCtlSuggested)
757         {
758             Status = STATUS_INSUFFICIENT_RESOURCES;
759             goto Dereference;
760         }
761 
762         /* And reask */
763         KeInitializeEvent(&Event, NotificationEvent, FALSE);
764         Irp = IoBuildDeviceIoControlRequest(IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME,
765                                             DeviceObject,
766                                             NULL,
767                                             0,
768                                             IoCtlSuggested,
769                                             NameLength,
770                                             FALSE,
771                                             &Event,
772                                             &IoStatusBlock);
773         if (!Irp)
774         {
775             Status = STATUS_INSUFFICIENT_RESOURCES;
776             goto Release;
777         }
778 
779         IoStackLocation = IoGetNextIrpStackLocation(Irp);
780         IoStackLocation->FileObject = FileObject;
781 
782         Status = IoCallDriver(DeviceObject, Irp);
783         if (Status == STATUS_PENDING)
784         {
785             KeWaitForSingleObject(&Event, Executive, KernelMode,
786                                   FALSE, NULL);
787             Status = IoStatusBlock.Status;
788         }
789     }
790 
791     if (!NT_SUCCESS(Status))
792     {
793         goto Release;
794     }
795 
796     /* Now we have suggested name, copy it */
797     SuggestedLinkName->Length = IoCtlSuggested->NameLength;
798     SuggestedLinkName->MaximumLength = IoCtlSuggested->NameLength + sizeof(UNICODE_NULL);
799     SuggestedLinkName->Buffer = AllocatePool(IoCtlSuggested->NameLength + sizeof(UNICODE_NULL));
800     if (!SuggestedLinkName->Buffer)
801     {
802         Status = STATUS_INSUFFICIENT_RESOURCES;
803     }
804     else
805     {
806         RtlCopyMemory(SuggestedLinkName->Buffer, IoCtlSuggested->Name, IoCtlSuggested->NameLength);
807         SuggestedLinkName->Buffer[SuggestedLinkName->Length / sizeof(WCHAR)] = UNICODE_NULL;
808     }
809 
810     /* Also return its priority */
811     *UseOnlyIfThereAreNoOtherLinks = IoCtlSuggested->UseOnlyIfThereAreNoOtherLinks;
812 
813 Release:
814     FreePool(IoCtlSuggested);
815 
816 Dereference:
817     ObDereferenceObject(DeviceObject);
818     ObDereferenceObject(FileObject);
819 
820     return Status;
821 }
822 
823 /*
824  * @implemented
825  */
826 BOOLEAN
827 RedirectSavedLink(IN PSAVED_LINK_INFORMATION SavedLinkInformation,
828                   IN PUNICODE_STRING DosName,
829                   IN PUNICODE_STRING NewLink)
830 {
831     PLIST_ENTRY NextEntry;
832     PSYMLINK_INFORMATION SymlinkInformation;
833 
834     /* Find the link */
835     for (NextEntry = SavedLinkInformation->SymbolicLinksListHead.Flink;
836          NextEntry != &(SavedLinkInformation->SymbolicLinksListHead);
837          NextEntry = NextEntry->Flink)
838     {
839         SymlinkInformation = CONTAINING_RECORD(NextEntry, SYMLINK_INFORMATION, SymbolicLinksListEntry);
840 
841         if (!RtlEqualUnicodeString(DosName, &(SymlinkInformation->Name), TRUE))
842         {
843             /* Delete old link */
844             GlobalDeleteSymbolicLink(DosName);
845             /* Set its new location */
846             GlobalCreateSymbolicLink(DosName, NewLink);
847 
848             /* And remove it from the list (not valid any more) */
849             RemoveEntryList(&(SymlinkInformation->SymbolicLinksListEntry));
850             FreePool(SymlinkInformation->Name.Buffer);
851             FreePool(SymlinkInformation);
852 
853             return TRUE;
854         }
855     }
856 
857     return FALSE;
858 }
859 
860 /*
861  * @implemented
862  */
863 VOID
864 DeleteSymbolicLinkNameFromMemory(IN PDEVICE_EXTENSION DeviceExtension,
865                                  IN PUNICODE_STRING SymbolicLink,
866                                  IN BOOLEAN MarkOffline)
867 {
868     PLIST_ENTRY DeviceEntry, SymbolEntry;
869     PDEVICE_INFORMATION DeviceInformation;
870     PSYMLINK_INFORMATION SymlinkInformation;
871 
872     /* First of all, ensure we have devices */
873     if (IsListEmpty(&(DeviceExtension->DeviceListHead)))
874     {
875         return;
876     }
877 
878     /* Then, look for the symbolic name */
879     for (DeviceEntry = DeviceExtension->DeviceListHead.Flink;
880          DeviceEntry != &(DeviceExtension->DeviceListHead);
881          DeviceEntry = DeviceEntry->Flink)
882     {
883         DeviceInformation = CONTAINING_RECORD(DeviceEntry, DEVICE_INFORMATION, DeviceListEntry);
884 
885         for (SymbolEntry = DeviceInformation->SymbolicLinksListHead.Flink;
886              SymbolEntry != &(DeviceInformation->SymbolicLinksListHead);
887              SymbolEntry = SymbolEntry->Flink)
888         {
889             SymlinkInformation = CONTAINING_RECORD(SymbolEntry, SYMLINK_INFORMATION, SymbolicLinksListEntry);
890 
891             /* One we have found it */
892             if (RtlCompareUnicodeString(SymbolicLink, &(SymlinkInformation->Name), TRUE) == 0)
893             {
894                 /* Check if caller just want it to be offline */
895                 if (MarkOffline)
896                 {
897                     SymlinkInformation->Online = FALSE;
898                 }
899                 else
900                 {
901                     /* If not, delete it & notify */
902                     SendLinkDeleted(&(DeviceInformation->SymbolicName), SymbolicLink);
903                     RemoveEntryList(&(SymlinkInformation->SymbolicLinksListEntry));
904 
905                     FreePool(SymlinkInformation->Name.Buffer);
906                     FreePool(SymlinkInformation);
907                 }
908 
909                 /* No need to go farther */
910                 return;
911             }
912         }
913     }
914 
915     return;
916 }
917 
918 /*
919  * @implemented
920  */
921 BOOLEAN
922 IsDriveLetter(PUNICODE_STRING SymbolicName)
923 {
924     WCHAR Letter, Colon;
925 
926     /* We must have a precise length */
927     if (SymbolicName->Length != DosDevices.Length + 2 * sizeof(WCHAR))
928     {
929         return FALSE;
930     }
931 
932     /* Must start with the DosDevices prefix */
933     if (!RtlPrefixUnicodeString(&DosDevices, SymbolicName, TRUE))
934     {
935         return FALSE;
936     }
937 
938     /* Check if letter is correct */
939     Letter = SymbolicName->Buffer[DosDevices.Length / sizeof(WCHAR)];
940     if ((Letter < L'A' || Letter > L'Z') && Letter != (WCHAR)-1)
941     {
942         return FALSE;
943     }
944 
945     /* And finally it must end with a colon */
946     Colon = SymbolicName->Buffer[DosDevices.Length / sizeof(WCHAR) + 1];
947     if (Colon != L':')
948     {
949         return FALSE;
950     }
951 
952     return TRUE;
953 }
954 
955 /*
956  * @implemented
957  */
958 NTSTATUS
959 MountMgrQuerySymbolicLink(IN PUNICODE_STRING SymbolicName,
960                           IN OUT PUNICODE_STRING LinkTarget)
961 {
962     NTSTATUS Status;
963     HANDLE LinkHandle;
964     OBJECT_ATTRIBUTES ObjectAttributes;
965 
966     /* Open the symbolic link */
967     InitializeObjectAttributes(&ObjectAttributes,
968                                SymbolicName,
969                                OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
970                                NULL,
971                                NULL);
972 
973     Status = ZwOpenSymbolicLinkObject(&LinkHandle,
974                                       GENERIC_READ,
975                                       &ObjectAttributes);
976     if (!NT_SUCCESS(Status))
977     {
978         return Status;
979     }
980 
981     /* Query its target */
982     Status = ZwQuerySymbolicLinkObject(LinkHandle,
983                                        LinkTarget,
984                                        NULL);
985 
986     ZwClose(LinkHandle);
987 
988     if (!NT_SUCCESS(Status))
989     {
990         return Status;
991     }
992 
993     if (LinkTarget->Length <= sizeof(WCHAR))
994     {
995         return Status;
996     }
997 
998     /* If it's not finished by \, just return */
999     if (LinkTarget->Buffer[LinkTarget->Length / sizeof(WCHAR) - 1] != L'\\')
1000     {
1001         return Status;
1002     }
1003 
1004     /* Otherwise, ensure to drop the tailing \ */
1005     LinkTarget->Length -= sizeof(WCHAR);
1006     LinkTarget->Buffer[LinkTarget->Length / sizeof(WCHAR)] = UNICODE_NULL;
1007 
1008     return Status;
1009 }
1010