xref: /reactos/dll/win32/kernel32/client/dosdev.c (revision ea7aca8b)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS system libraries
4  * FILE:            dll/win32/kernel32/client/dosdev.c
5  * PURPOSE:         Dos device functions
6  * PROGRAMMER:      Ariadne (ariadne@xs4all.nl)
7  *                  Pierre Schweitzer
8  * UPDATE HISTORY:
9  *                  Created 01/11/98
10  */
11 
12 /* INCLUDES ******************************************************************/
13 
14 #include <k32.h>
15 
16 #define NDEBUG
17 #include <debug.h>
18 #include <dbt.h>
19 DEBUG_CHANNEL(kernel32file);
20 
21 /* FUNCTIONS *****************************************************************/
22 
23 /*
24  * @implemented
25  */
26 NTSTATUS
IsGlobalDeviceMap(HANDLE DirectoryHandle,PBOOLEAN IsGlobal)27 IsGlobalDeviceMap(
28     HANDLE DirectoryHandle,
29     PBOOLEAN IsGlobal)
30 {
31     NTSTATUS Status;
32     DWORD ReturnLength;
33     UNICODE_STRING GlobalString;
34     OBJECT_NAME_INFORMATION NameInfo, *PNameInfo;
35 
36     /* We need both parameters */
37     if (DirectoryHandle == 0 || IsGlobal == NULL)
38     {
39         return STATUS_INVALID_PARAMETER;
40     }
41 
42     PNameInfo = NULL;
43     _SEH2_TRY
44     {
45         /* Query handle information */
46         Status = NtQueryObject(DirectoryHandle,
47                                ObjectNameInformation,
48                                &NameInfo,
49                                0,
50                                &ReturnLength);
51         /* Only failure we tolerate is length mismatch */
52         if (NT_SUCCESS(Status) || Status == STATUS_INFO_LENGTH_MISMATCH)
53         {
54             /* Allocate big enough buffer */
55             PNameInfo = RtlAllocateHeap(RtlGetProcessHeap(), 0, ReturnLength);
56             if (PNameInfo == NULL)
57             {
58                 Status = STATUS_NO_MEMORY;
59                 _SEH2_LEAVE;
60             }
61 
62             /* Query again handle information */
63             Status = NtQueryObject(DirectoryHandle,
64                                    ObjectNameInformation,
65                                    PNameInfo,
66                                    ReturnLength,
67                                    &ReturnLength);
68 
69             /*
70              * If it succeed, check we have Global??
71              * If so, return success
72              */
73             if (NT_SUCCESS(Status))
74             {
75                 RtlInitUnicodeString(&GlobalString, L"\\GLOBAL??");
76                 *IsGlobal = RtlEqualUnicodeString(&GlobalString, &PNameInfo->Name, FALSE);
77                 Status = STATUS_SUCCESS;
78             }
79         }
80     }
81     _SEH2_FINALLY
82     {
83         if (PNameInfo != NULL)
84         {
85             RtlFreeHeap(RtlGetProcessHeap(), 0, PNameInfo);
86         }
87     }
88     _SEH2_END;
89 
90     return Status;
91 }
92 
93 /*
94  * @implemented
95  */
96 DWORD
FindSymbolicLinkEntry(PWSTR NameToFind,PWSTR NamesList,DWORD TotalEntries,PBOOLEAN Found)97 FindSymbolicLinkEntry(
98     PWSTR NameToFind,
99     PWSTR NamesList,
100     DWORD TotalEntries,
101     PBOOLEAN Found)
102 {
103     WCHAR Current;
104     DWORD Entries;
105     PWSTR PartialNamesList;
106 
107     /* We need all parameters to be set */
108     if (NameToFind == NULL || NamesList == NULL || Found == NULL)
109     {
110         return ERROR_INVALID_PARAMETER;
111     }
112 
113     /* Assume failure */
114     *Found = FALSE;
115 
116     /* If no entries, job done, nothing found */
117     if (TotalEntries == 0)
118     {
119         return ERROR_SUCCESS;
120     }
121 
122     /* Start browsing the names list */
123     Entries = 0;
124     PartialNamesList = NamesList;
125     /* As long as we didn't find the name... */
126     while (wcscmp(NameToFind, PartialNamesList) != 0)
127     {
128         /* We chomped an entry! */
129         ++Entries;
130 
131         /* We're out of entries, bail out not to overrun */
132         if (Entries > TotalEntries)
133         {
134             /*
135              * Even though we found nothing,
136              * the function ran fine
137              */
138             return ERROR_SUCCESS;
139         }
140 
141         /* Jump to the next string */
142         do
143         {
144             Current = *PartialNamesList;
145             ++PartialNamesList;
146         } while (Current != UNICODE_NULL);
147     }
148 
149     /*
150      * We're here because the loop stopped:
151      * it means we found the name in the list
152      */
153     *Found = TRUE;
154     return ERROR_SUCCESS;
155 }
156 
157 /*
158  * @implemented
159  */
160 BOOL
161 WINAPI
DefineDosDeviceA(DWORD dwFlags,LPCSTR lpDeviceName,LPCSTR lpTargetPath)162 DefineDosDeviceA(
163     DWORD dwFlags,
164     LPCSTR lpDeviceName,
165     LPCSTR lpTargetPath
166     )
167 {
168     BOOL Result;
169     NTSTATUS Status;
170     ANSI_STRING AnsiString;
171     PWSTR TargetPathBuffer;
172     UNICODE_STRING TargetPathU;
173     PUNICODE_STRING DeviceNameU;
174 
175     /* Convert DeviceName using static unicode string */
176     RtlInitAnsiString(&AnsiString, lpDeviceName);
177     DeviceNameU = &NtCurrentTeb()->StaticUnicodeString;
178     Status = RtlAnsiStringToUnicodeString(DeviceNameU, &AnsiString, FALSE);
179     if (!NT_SUCCESS(Status))
180     {
181         /*
182          * If the static unicode string is too small,
183          * it's because the name is too long...
184          * so, return appropriate status!
185          */
186         if (Status == STATUS_BUFFER_OVERFLOW)
187         {
188             SetLastError(ERROR_FILENAME_EXCED_RANGE);
189             return FALSE;
190         }
191 
192         BaseSetLastNTError(Status);
193         return FALSE;
194     }
195 
196     /* Convert target path if existing */
197     if (lpTargetPath != NULL)
198     {
199         RtlInitAnsiString(&AnsiString, lpTargetPath);
200         Status = RtlAnsiStringToUnicodeString(&TargetPathU, &AnsiString, TRUE);
201         if (!NT_SUCCESS(Status))
202         {
203             BaseSetLastNTError(Status);
204             return FALSE;
205         }
206 
207         TargetPathBuffer = TargetPathU.Buffer;
208     }
209     else
210     {
211         TargetPathBuffer = NULL;
212     }
213 
214     /* Call W */
215     Result = DefineDosDeviceW(dwFlags, DeviceNameU->Buffer, TargetPathBuffer);
216 
217     /* Free target path if allocated */
218     if (TargetPathBuffer != NULL)
219     {
220         RtlFreeUnicodeString(&TargetPathU);
221     }
222 
223     return Result;
224 }
225 
226 
227 /*
228  * @implemented
229  */
230 BOOL
231 WINAPI
DefineDosDeviceW(DWORD dwFlags,LPCWSTR lpDeviceName,LPCWSTR lpTargetPath)232 DefineDosDeviceW(
233     DWORD dwFlags,
234     LPCWSTR lpDeviceName,
235     LPCWSTR lpTargetPath
236     )
237 {
238     ULONG ArgumentCount;
239     ULONG BufferSize;
240     BASE_API_MESSAGE ApiMessage;
241     PBASE_DEFINE_DOS_DEVICE DefineDosDeviceRequest = &ApiMessage.Data.DefineDosDeviceRequest;
242     PCSR_CAPTURE_BUFFER CaptureBuffer;
243     UNICODE_STRING NtTargetPathU;
244     UNICODE_STRING DeviceNameU;
245     HANDLE hUser32;
246     DEV_BROADCAST_VOLUME dbcv;
247     DWORD dwRecipients;
248     typedef long (WINAPI *BSM_type)(DWORD, LPDWORD, UINT, WPARAM, LPARAM);
249     BSM_type BSM_ptr;
250     BOOLEAN LUIDDeviceMapsEnabled;
251     WCHAR Letter;
252     WPARAM wParam;
253 
254     /* Get status about local device mapping */
255     LUIDDeviceMapsEnabled = BaseStaticServerData->LUIDDeviceMapsEnabled;
256 
257     /* Validate input & flags */
258     if ((dwFlags & 0xFFFFFFE0) ||
259         ((dwFlags & DDD_EXACT_MATCH_ON_REMOVE) &&
260         !(dwFlags & DDD_REMOVE_DEFINITION)) ||
261         (lpTargetPath == NULL && !(dwFlags & (DDD_LUID_BROADCAST_DRIVE | DDD_REMOVE_DEFINITION))) ||
262         ((dwFlags & DDD_LUID_BROADCAST_DRIVE) &&
263          (lpDeviceName == NULL || lpTargetPath != NULL || dwFlags & (DDD_NO_BROADCAST_SYSTEM | DDD_EXACT_MATCH_ON_REMOVE | DDD_RAW_TARGET_PATH) || !LUIDDeviceMapsEnabled)))
264     {
265         SetLastError(ERROR_INVALID_PARAMETER);
266         return FALSE;
267     }
268 
269     /* Initialize device unicode string to ease its use */
270     RtlInitUnicodeString(&DeviceNameU, lpDeviceName);
271 
272     /* The buffer for CSR call will contain it */
273     BufferSize = DeviceNameU.MaximumLength;
274     ArgumentCount = 1;
275 
276     /* If we don't have target path, use empty string */
277     if (lpTargetPath == NULL)
278     {
279         RtlInitUnicodeString(&NtTargetPathU, NULL);
280     }
281     else
282     {
283         /* Else, use it raw if asked to */
284         if (dwFlags & DDD_RAW_TARGET_PATH)
285         {
286             RtlInitUnicodeString(&NtTargetPathU, lpTargetPath);
287         }
288         else
289         {
290             /* Otherwise, use it converted */
291             if (!RtlDosPathNameToNtPathName_U(lpTargetPath,
292                                               &NtTargetPathU,
293                                               NULL,
294                                               NULL))
295             {
296                 WARN("RtlDosPathNameToNtPathName_U() failed\n");
297                 BaseSetLastNTError(STATUS_OBJECT_NAME_INVALID);
298                 return FALSE;
299             }
300         }
301 
302         /* This target path will be the second arg */
303         ArgumentCount = 2;
304         BufferSize += NtTargetPathU.MaximumLength;
305     }
306 
307     /* Allocate the capture buffer for our strings */
308     CaptureBuffer = CsrAllocateCaptureBuffer(ArgumentCount,
309                                              BufferSize);
310     if (CaptureBuffer == NULL)
311     {
312         if (!(dwFlags & DDD_RAW_TARGET_PATH))
313         {
314             RtlFreeUnicodeString(&NtTargetPathU);
315         }
316 
317         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
318         return FALSE;
319     }
320 
321     /* Set the flags */
322     DefineDosDeviceRequest->Flags = dwFlags;
323 
324     /* Allocate a buffer for the device name */
325     DefineDosDeviceRequest->DeviceName.MaximumLength = CsrAllocateMessagePointer(CaptureBuffer,
326                                                                                  DeviceNameU.MaximumLength,
327                                                                                  (PVOID*)&DefineDosDeviceRequest->DeviceName.Buffer);
328     /* And copy it while upcasing it */
329     RtlUpcaseUnicodeString(&DefineDosDeviceRequest->DeviceName, &DeviceNameU, FALSE);
330 
331     /* If we have a target path, copy it too, and free it if allocated */
332     if (NtTargetPathU.Length != 0)
333     {
334         DefineDosDeviceRequest->TargetPath.MaximumLength = CsrAllocateMessagePointer(CaptureBuffer,
335                                                                                      NtTargetPathU.MaximumLength,
336                                                                                      (PVOID*)&DefineDosDeviceRequest->TargetPath.Buffer);
337         RtlCopyUnicodeString(&DefineDosDeviceRequest->TargetPath, &NtTargetPathU);
338 
339         if (!(dwFlags & DDD_RAW_TARGET_PATH))
340         {
341             RtlFreeUnicodeString(&NtTargetPathU);
342         }
343     }
344     /* Otherwise, null initialize the string */
345     else
346     {
347         RtlInitUnicodeString(&DefineDosDeviceRequest->TargetPath, NULL);
348     }
349 
350     /* Finally,  call the server */
351     CsrClientCallServer((PCSR_API_MESSAGE)&ApiMessage,
352                         CaptureBuffer,
353                         CSR_CREATE_API_NUMBER(BASESRV_SERVERDLL_INDEX, BasepDefineDosDevice),
354                         sizeof(*DefineDosDeviceRequest));
355     CsrFreeCaptureBuffer(CaptureBuffer);
356 
357     /* Return failure if any */
358     if (!NT_SUCCESS(ApiMessage.Status))
359     {
360         WARN("CsrClientCallServer() failed (Status %lx)\n", ApiMessage.Status);
361         BaseSetLastNTError(ApiMessage.Status);
362         return FALSE;
363     }
364 
365     /* Here is the success path, we will always return true */
366 
367     /* Should broadcast the event? Only do if not denied and if drive letter */
368     if (!(dwFlags & DDD_NO_BROADCAST_SYSTEM) &&
369         DeviceNameU.Length == 2 * sizeof(WCHAR) &&
370         DeviceNameU.Buffer[1] == L':')
371     {
372         /* Make sure letter is valid and there are no local device mappings */
373         Letter = RtlUpcaseUnicodeChar(DeviceNameU.Buffer[0]) - L'A';
374         if (Letter < 26 && !LUIDDeviceMapsEnabled)
375         {
376             /* Rely on user32 for broadcasting */
377             hUser32 = LoadLibraryW(L"user32.dll");
378             if (hUser32 != 0)
379             {
380                 /* Get the function pointer */
381                 BSM_ptr = (BSM_type)GetProcAddress(hUser32, "BroadcastSystemMessageW");
382                 if (BSM_ptr)
383                 {
384                     /* Set our target */
385                     dwRecipients = BSM_APPLICATIONS;
386 
387                     /* And initialize our structure */
388                     dbcv.dbcv_size = sizeof(DEV_BROADCAST_VOLUME);
389                     dbcv.dbcv_devicetype = DBT_DEVTYP_VOLUME;
390                     dbcv.dbcv_reserved = 0;
391 
392                     /* Set the volume which had the event */
393                     dbcv.dbcv_unitmask = 1 << Letter;
394                     dbcv.dbcv_flags = DBTF_NET;
395 
396                     /* And properly set the event (removal or arrival?) */
397                     wParam = (dwFlags & DDD_REMOVE_DEFINITION) ? DBT_DEVICEREMOVECOMPLETE : DBT_DEVICEARRIVAL;
398 
399                     /* And broadcast! */
400                     BSM_ptr(BSF_SENDNOTIFYMESSAGE | BSF_FLUSHDISK,
401                             &dwRecipients,
402                             WM_DEVICECHANGE,
403                             wParam,
404                             (LPARAM)&dbcv);
405                 }
406 
407                 /* We're done! */
408                 FreeLibrary(hUser32);
409             }
410         }
411     }
412 
413     return TRUE;
414 }
415 
416 
417 /*
418  * @implemented
419  */
420 DWORD
421 WINAPI
QueryDosDeviceA(LPCSTR lpDeviceName,LPSTR lpTargetPath,DWORD ucchMax)422 QueryDosDeviceA(
423     LPCSTR lpDeviceName,
424     LPSTR lpTargetPath,
425     DWORD ucchMax
426     )
427 {
428     NTSTATUS Status;
429     USHORT CurrentPosition;
430     ANSI_STRING AnsiString;
431     UNICODE_STRING TargetPathU;
432     PUNICODE_STRING DeviceNameU;
433     DWORD RetLength, CurrentLength, Length;
434     PWSTR DeviceNameBuffer, TargetPathBuffer;
435 
436     /* If we want a specific device name, convert it */
437     if (lpDeviceName != NULL)
438     {
439         /* Convert DeviceName using static unicode string */
440         RtlInitAnsiString(&AnsiString, lpDeviceName);
441         DeviceNameU = &NtCurrentTeb()->StaticUnicodeString;
442         Status = RtlAnsiStringToUnicodeString(DeviceNameU, &AnsiString, FALSE);
443         if (!NT_SUCCESS(Status))
444         {
445             /*
446              * If the static unicode string is too small,
447              * it's because the name is too long...
448              * so, return appropriate status!
449              */
450             if (Status == STATUS_BUFFER_OVERFLOW)
451             {
452                 SetLastError(ERROR_FILENAME_EXCED_RANGE);
453                 return FALSE;
454             }
455 
456             BaseSetLastNTError(Status);
457             return FALSE;
458         }
459 
460         DeviceNameBuffer = DeviceNameU->Buffer;
461     }
462     else
463     {
464         DeviceNameBuffer = NULL;
465     }
466 
467     /* Allocate the output buffer for W call */
468     TargetPathBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, ucchMax * sizeof(WCHAR));
469     if (TargetPathBuffer == NULL)
470     {
471         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
472         return 0;
473     }
474 
475     /* Call W */
476     Length = QueryDosDeviceW(DeviceNameBuffer, TargetPathBuffer, ucchMax);
477     /* We'll return that length in case of a success */
478     RetLength = Length;
479 
480     /* Handle the case where we would fill output buffer completly */
481     if (Length != 0 && Length == ucchMax)
482     {
483         /* This will be our work length (but not the one we return) */
484         --Length;
485         /* Already 0 the last char */
486         lpTargetPath[Length] = ANSI_NULL;
487     }
488 
489     /* If we had an output, start the convert loop */
490     if (Length != 0)
491     {
492         /*
493          * We'll have to loop because TargetPathBuffer may contain
494          * several strings (NULL separated)
495          * We'll start at position 0
496          */
497         CurrentPosition = 0;
498         while (CurrentPosition < Length)
499         {
500             /* Get the maximum length */
501             CurrentLength = min(Length - CurrentPosition, MAXUSHORT / 2);
502 
503             /* Initialize our output string */
504             AnsiString.Length = 0;
505             AnsiString.MaximumLength = CurrentLength + sizeof(ANSI_NULL);
506             AnsiString.Buffer = &lpTargetPath[CurrentPosition];
507 
508             /* Initialize input string that will be converted */
509             TargetPathU.Length = CurrentLength * sizeof(WCHAR);
510             TargetPathU.MaximumLength = CurrentLength * sizeof(WCHAR) + sizeof(UNICODE_NULL);
511             TargetPathU.Buffer = &TargetPathBuffer[CurrentPosition];
512 
513             /* Convert to ANSI */
514             Status = RtlUnicodeStringToAnsiString(&AnsiString, &TargetPathU, FALSE);
515             if (!NT_SUCCESS(Status))
516             {
517                 BaseSetLastNTError(Status);
518                 /* In case of a failure, forget about everything */
519                 RetLength = 0;
520 
521                 goto Leave;
522             }
523 
524             /* Move to the next string */
525             CurrentPosition += CurrentLength;
526         }
527     }
528 
529 Leave:
530     /* Free our intermediate buffer and leave */
531     RtlFreeHeap(RtlGetProcessHeap(), 0, TargetPathBuffer);
532 
533     return RetLength;
534 }
535 
536 
537 /*
538  * @implemented
539  */
540 DWORD
541 WINAPI
QueryDosDeviceW(LPCWSTR lpDeviceName,LPWSTR lpTargetPath,DWORD ucchMax)542 QueryDosDeviceW(
543     LPCWSTR lpDeviceName,
544     LPWSTR lpTargetPath,
545     DWORD ucchMax
546     )
547 {
548     PWSTR Ptr;
549     PVOID Buffer;
550     NTSTATUS Status;
551     USHORT i, TotalEntries;
552     UNICODE_STRING UnicodeString;
553     OBJECT_ATTRIBUTES ObjectAttributes;
554     HANDLE DirectoryHandle, DeviceHandle;
555     BOOLEAN IsGlobal, GlobalNeeded, Found;
556     POBJECT_DIRECTORY_INFORMATION DirInfo;
557     OBJECT_DIRECTORY_INFORMATION NullEntry = {{0}};
558     ULONG ReturnLength, NameLength, Length, Context, BufferLength;
559 
560     /* Open the '\??' directory */
561     RtlInitUnicodeString(&UnicodeString, L"\\??");
562     InitializeObjectAttributes(&ObjectAttributes,
563                                &UnicodeString,
564                                OBJ_CASE_INSENSITIVE,
565                                NULL,
566                                NULL);
567     Status = NtOpenDirectoryObject(&DirectoryHandle,
568                                    DIRECTORY_QUERY,
569                                    &ObjectAttributes);
570     if (!NT_SUCCESS(Status))
571     {
572         WARN("NtOpenDirectoryObject() failed (Status %lx)\n", Status);
573         BaseSetLastNTError(Status);
574         return 0;
575     }
576 
577     Buffer = NULL;
578     _SEH2_TRY
579     {
580         if (lpDeviceName != NULL)
581         {
582             /* Open the lpDeviceName link object */
583             RtlInitUnicodeString(&UnicodeString, lpDeviceName);
584             InitializeObjectAttributes(&ObjectAttributes,
585                                        &UnicodeString,
586                                        OBJ_CASE_INSENSITIVE,
587                                        DirectoryHandle,
588                                        NULL);
589             Status = NtOpenSymbolicLinkObject(&DeviceHandle,
590                                               SYMBOLIC_LINK_QUERY,
591                                               &ObjectAttributes);
592             if (!NT_SUCCESS(Status))
593             {
594                 WARN("NtOpenSymbolicLinkObject() failed (Status %lx)\n", Status);
595                 _SEH2_LEAVE;
596             }
597 
598             /*
599              * Make sure we don't overrun the output buffer, so convert our DWORD
600              * size to USHORT size properly
601              */
602             Length = (ucchMax <= MAXULONG / sizeof(WCHAR)) ? (ucchMax * sizeof(WCHAR)) : MAXULONG;
603 
604             /* Query link target */
605             UnicodeString.Length = 0;
606             UnicodeString.MaximumLength = Length <= MAXUSHORT ? Length : MAXUSHORT;
607             UnicodeString.Buffer = lpTargetPath;
608 
609             ReturnLength = 0;
610             Status = NtQuerySymbolicLinkObject(DeviceHandle,
611                                                &UnicodeString,
612                                                &ReturnLength);
613             NtClose(DeviceHandle);
614             if (!NT_SUCCESS(Status))
615             {
616                 WARN("NtQuerySymbolicLinkObject() failed (Status %lx)\n", Status);
617                 _SEH2_LEAVE;
618             }
619 
620             TRACE("ReturnLength: %lu\n", ReturnLength);
621             TRACE("TargetLength: %hu\n", UnicodeString.Length);
622             TRACE("Target: '%wZ'\n", &UnicodeString);
623 
624             Length = ReturnLength / sizeof(WCHAR);
625             /* Make sure we null terminate output buffer */
626             if (Length == 0 || lpTargetPath[Length - 1] != UNICODE_NULL)
627             {
628                 if (Length >= ucchMax)
629                 {
630                     TRACE("Buffer is too small\n");
631                     Status = STATUS_BUFFER_TOO_SMALL;
632                     _SEH2_LEAVE;
633                 }
634 
635                 /* Append null-character */
636                 lpTargetPath[Length] = UNICODE_NULL;
637                 Length++;
638             }
639 
640             if (Length < ucchMax)
641             {
642                 /* Append null-character */
643                 lpTargetPath[Length] = UNICODE_NULL;
644                 Length++;
645             }
646 
647             _SEH2_LEAVE;
648         }
649 
650         /*
651          * If LUID device maps are enabled,
652          * ?? may not point to BaseNamedObjects
653          * It may only be local DOS namespace.
654          * And thus, it might be required to browse
655          * Global?? for global devices
656          */
657         GlobalNeeded = FALSE;
658         if (BaseStaticServerData->LUIDDeviceMapsEnabled)
659         {
660             /* Assume ?? == Global?? */
661             IsGlobal = TRUE;
662             /* Check if it's the case */
663             Status = IsGlobalDeviceMap(DirectoryHandle, &IsGlobal);
664             if (NT_SUCCESS(Status) && !IsGlobal)
665             {
666                 /* It's not, we'll have to browse Global?? too! */
667                 GlobalNeeded = TRUE;
668             }
669         }
670 
671         /*
672          * Make sure we don't overrun the output buffer, so convert our DWORD
673          * size to USHORT size properly
674          */
675         BufferLength = (ucchMax <= MAXULONG / sizeof(WCHAR)) ? (ucchMax * sizeof(WCHAR)) : MAXULONG;
676         Length = 0;
677         Ptr = lpTargetPath;
678 
679         Context = 0;
680         TotalEntries = 0;
681 
682         /*
683          * We'll query all entries at once, with a rather big buffer
684          * If it's too small, we'll grow it by 2.
685          * Limit the number of attempts to 3.
686          */
687         for (i = 0; i < 3; ++i)
688         {
689             /* Allocate the query buffer */
690             Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, BufferLength);
691             if (Buffer == NULL)
692             {
693                 Status = STATUS_INSUFFICIENT_RESOURCES;
694                 _SEH2_LEAVE;
695             }
696 
697             /* Perform the query */
698             Status = NtQueryDirectoryObject(DirectoryHandle,
699                                             Buffer,
700                                             BufferLength,
701                                             FALSE,
702                                             TRUE,
703                                             &Context,
704                                             &ReturnLength);
705             /* Only failure accepted is: no more entries */
706             if (!NT_SUCCESS(Status))
707             {
708                 if (Status != STATUS_NO_MORE_ENTRIES)
709                 {
710                     _SEH2_LEAVE;
711                 }
712 
713                 /*
714                  * Which is a success! But break out,
715                  * it means our query returned no results
716                  * so, nothing to parse.
717                  */
718                 Status = STATUS_SUCCESS;
719                 break;
720             }
721 
722             /* In case we had them all, start browsing for devices */
723             if (Status != STATUS_MORE_ENTRIES)
724             {
725                 DirInfo = Buffer;
726 
727                 /* Loop until we find the nul entry (terminating entry) */
728                 while (TRUE)
729                 {
730                     /* It's an entry full of zeroes */
731                     if (RtlCompareMemory(&NullEntry, DirInfo, sizeof(NullEntry)) == sizeof(NullEntry))
732                     {
733                         break;
734                     }
735 
736                     /* Only handle symlinks */
737                     if (!wcscmp(DirInfo->TypeName.Buffer, L"SymbolicLink"))
738                     {
739                         TRACE("Name: '%wZ'\n", &DirInfo->Name);
740 
741                         /* Get name length in chars to only comparisons */
742                         NameLength = DirInfo->Name.Length / sizeof(WCHAR);
743 
744                         /* Make sure we don't overrun output buffer */
745                         if (Length > ucchMax ||
746                             NameLength > ucchMax - Length ||
747                             ucchMax - NameLength - Length < sizeof(WCHAR))
748                         {
749                             Status = STATUS_BUFFER_TOO_SMALL;
750                             _SEH2_LEAVE;
751                         }
752 
753                         /* Copy and NULL terminate string */
754                         memcpy(Ptr, DirInfo->Name.Buffer, DirInfo->Name.Length);
755                         Ptr[NameLength] = UNICODE_NULL;
756 
757                         Ptr += (NameLength + 1);
758                         Length += (NameLength + 1);
759 
760                         /*
761                          * Keep the entries count, in case we would have to
762                          * handle GLOBAL?? too
763                          */
764                         ++TotalEntries;
765                     }
766 
767                     /* Move to the next entry */
768                     ++DirInfo;
769                 }
770 
771                 /*
772                  * No need to loop again here, we got all the entries
773                  * Note: we don't free the buffer here, because we may
774                  * need it for GLOBAL??, so we save a few cycles here.
775                  */
776                 break;
777             }
778 
779             /* Failure path here, we'll need bigger buffer */
780             RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
781             Buffer = NULL;
782 
783             /* We can't have bigger than that one, so leave */
784             if (BufferLength == MAXULONG)
785             {
786                 break;
787             }
788 
789             /* Prevent any overflow while computing new size */
790             if (MAXULONG - BufferLength < BufferLength)
791             {
792                 BufferLength = MAXULONG;
793             }
794             else
795             {
796                 BufferLength *= 2;
797             }
798         }
799 
800         /*
801          * Out of the hot loop, but with more entries left?
802          * that's an error case, leave here!
803          */
804         if (Status == STATUS_MORE_ENTRIES)
805         {
806             Status = STATUS_BUFFER_TOO_SMALL;
807             _SEH2_LEAVE;
808         }
809 
810         /* Now, if we had to handle GLOBAL??, go for it! */
811         if (BaseStaticServerData->LUIDDeviceMapsEnabled && NT_SUCCESS(Status) && GlobalNeeded)
812         {
813             NtClose(DirectoryHandle);
814             DirectoryHandle = 0;
815 
816             RtlInitUnicodeString(&UnicodeString, L"\\GLOBAL??");
817             InitializeObjectAttributes(&ObjectAttributes,
818                                        &UnicodeString,
819                                        OBJ_CASE_INSENSITIVE,
820                                        NULL,
821                                        NULL);
822             Status = NtOpenDirectoryObject(&DirectoryHandle,
823                                            DIRECTORY_QUERY,
824                                            &ObjectAttributes);
825             if (!NT_SUCCESS(Status))
826             {
827                 WARN("NtOpenDirectoryObject() failed (Status %lx)\n", Status);
828                 _SEH2_LEAVE;
829             }
830 
831             /*
832              * We'll query all entries at once, with a rather big buffer
833              * If it's too small, we'll grow it by 2.
834              * Limit the number of attempts to 3.
835              */
836             for (i = 0; i < 3; ++i)
837             {
838                 /* If we had no buffer from previous attempt, allocate one */
839                 if (Buffer == NULL)
840                 {
841                     Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, BufferLength);
842                     if (Buffer == NULL)
843                     {
844                         Status = STATUS_INSUFFICIENT_RESOURCES;
845                         _SEH2_LEAVE;
846                     }
847                 }
848 
849                 /* Perform the query */
850                 Status = NtQueryDirectoryObject(DirectoryHandle,
851                                                 Buffer,
852                                                 BufferLength,
853                                                 FALSE,
854                                                 TRUE,
855                                                 &Context,
856                                                 &ReturnLength);
857                 /* Only failure accepted is: no more entries */
858                 if (!NT_SUCCESS(Status))
859                 {
860                     if (Status != STATUS_NO_MORE_ENTRIES)
861                     {
862                         _SEH2_LEAVE;
863                     }
864 
865                     /*
866                      * Which is a success! But break out,
867                      * it means our query returned no results
868                      * so, nothing to parse.
869                      */
870                     Status = STATUS_SUCCESS;
871                     break;
872                 }
873 
874                 /* In case we had them all, start browsing for devices */
875                 if (Status != STATUS_MORE_ENTRIES)
876                 {
877                     DirInfo = Buffer;
878 
879                     /* Loop until we find the nul entry (terminating entry) */
880                     while (TRUE)
881                     {
882                         /* It's an entry full of zeroes */
883                         if (RtlCompareMemory(&NullEntry, DirInfo, sizeof(NullEntry)) == sizeof(NullEntry))
884                         {
885                             break;
886                         }
887 
888                         /* Only handle symlinks */
889                         if (!wcscmp(DirInfo->TypeName.Buffer, L"SymbolicLink"))
890                         {
891                             TRACE("Name: '%wZ'\n", &DirInfo->Name);
892 
893                             /*
894                              * Now, we previously already browsed ??, and we
895                              * don't want to devices twice, so we'll check
896                              * the output buffer for duplicates.
897                              * We'll add our entry only if we don't have already
898                              * returned it.
899                              */
900                             if (FindSymbolicLinkEntry(DirInfo->Name.Buffer,
901                                                       lpTargetPath,
902                                                       TotalEntries,
903                                                       &Found) == ERROR_SUCCESS &&
904                                 !Found)
905                             {
906                                 /* Get name length in chars to only comparisons */
907                                 NameLength = DirInfo->Name.Length / sizeof(WCHAR);
908 
909                                 /* Make sure we don't overrun output buffer */
910                                 if (Length > ucchMax ||
911                                     NameLength > ucchMax - Length ||
912                                     ucchMax - NameLength - Length < sizeof(WCHAR))
913                                 {
914                                     RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
915                                     NtClose(DirectoryHandle);
916                                     BaseSetLastNTError(STATUS_BUFFER_TOO_SMALL);
917                                     return 0;
918                                 }
919 
920                                 /* Copy and NULL terminate string */
921                                 memcpy(Ptr, DirInfo->Name.Buffer, DirInfo->Name.Length);
922                                 Ptr[NameLength] = UNICODE_NULL;
923 
924                                 Ptr += (NameLength + 1);
925                                 Length += (NameLength + 1);
926                             }
927                         }
928 
929                         /* Move to the next entry */
930                         ++DirInfo;
931                     }
932 
933                     /* No need to loop again here, we got all the entries */
934                     break;
935                 }
936 
937                 /* Failure path here, we'll need bigger buffer */
938                 RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
939                 Buffer = NULL;
940 
941                 /* We can't have bigger than that one, so leave */
942                 if (BufferLength == MAXULONG)
943                 {
944                     break;
945                 }
946 
947                 /* Prevent any overflow while computing new size */
948                 if (MAXULONG - BufferLength < BufferLength)
949                 {
950                     BufferLength = MAXULONG;
951                 }
952                 else
953                 {
954                     BufferLength *= 2;
955                 }
956             }
957 
958             /*
959              * Out of the hot loop, but with more entries left?
960              * that's an error case, leave here!
961              */
962             if (Status == STATUS_MORE_ENTRIES)
963             {
964                 Status = STATUS_BUFFER_TOO_SMALL;
965                 _SEH2_LEAVE;
966             }
967         }
968 
969         /* If we failed somewhere, just leave */
970         if (!NT_SUCCESS(Status))
971         {
972             _SEH2_LEAVE;
973         }
974 
975         /* If we returned no entries, time to write the empty string */
976         if (Length == 0)
977         {
978             /* Unless output buffer is too small! */
979             if (ucchMax <= 0)
980             {
981                 Status = STATUS_BUFFER_TOO_SMALL;
982                 _SEH2_LEAVE;
983             }
984 
985             /* Emptry string is one char (terminator!) */
986             *Ptr = UNICODE_NULL;
987             ++Ptr;
988             Length = 1;
989         }
990 
991         /*
992          * If we have enough room, we need to double terminate the buffer:
993          * that's a MULTI_SZ buffer, its end is marked by double NULL.
994          * One was already added during the "copy string" process.
995          * If we don't have enough room: that's a failure case.
996          */
997         if (Length < ucchMax)
998         {
999             *Ptr = UNICODE_NULL;
1000             ++Ptr;
1001         }
1002         else
1003         {
1004             Status = STATUS_BUFFER_TOO_SMALL;
1005         }
1006     }
1007     _SEH2_FINALLY
1008     {
1009         if (DirectoryHandle != 0)
1010         {
1011             NtClose(DirectoryHandle);
1012         }
1013 
1014         if (Buffer != NULL)
1015         {
1016             RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
1017         }
1018 
1019         if (!NT_SUCCESS(Status))
1020         {
1021             Length = 0;
1022             BaseSetLastNTError(Status);
1023         }
1024     }
1025     _SEH2_END;
1026 
1027     return Length;
1028 }
1029 
1030 /* EOF */
1031