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