xref: /reactos/base/setup/lib/utils/arcname.c (revision 0c2cdcae)
1 /*
2  * PROJECT:     ReactOS Setup Library
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     ARC path to-and-from NT path resolver.
5  * COPYRIGHT:   Copyright 2017-2018 Hermes Belusca-Maito
6  */
7 /*
8  * References:
9  *
10  * - ARC Specification v1.2: http://netbsd.org./docs/Hardware/Machines/ARC/riscspec.pdf
11  * - "Setup and Startup", MSDN article: https://technet.microsoft.com/en-us/library/cc977184.aspx
12  * - Answer for "How do I determine the ARC path for a particular drive letter in Windows?": https://serverfault.com/a/5929
13  * - ARC - LinuxMIPS: https://www.linux-mips.org/wiki/ARC
14  * - ARCLoad - LinuxMIPS: https://www.linux-mips.org/wiki/ARCLoad
15  * - Inside Windows 2000 Server: https://books.google.fr/books?id=kYT7gKnwUQ8C&pg=PA71&lpg=PA71&dq=nt+arc+path&source=bl&ots=K8I1F_KQ_u&sig=EJq5t-v2qQk-QB7gNSREFj7pTVo&hl=en&sa=X&redir_esc=y#v=onepage&q=nt%20arc%20path&f=false
16  * - Inside Windows Server 2003: https://books.google.fr/books?id=zayrcM9ZYdAC&pg=PA61&lpg=PA61&dq=arc+path+to+nt+path&source=bl&ots=x2JSWfp2MA&sig=g9mufN6TCOrPejDov6Rjp0Jrldo&hl=en&sa=X&redir_esc=y#v=onepage&q=arc%20path%20to%20nt%20path&f=false
17  *
18  * Stuff to read: http://www.adminxp.com/windows2000/index.php?aid=46 and http://www.trcb.com/Computers-and-Technology/Windows-XP/Windows-XP-ARC-Naming-Conventions-1432.htm
19  * concerning which values of disk() or rdisk() are valid when either scsi() or multi() adapters are specified.
20  */
21 
22 /* INCLUDES *****************************************************************/
23 
24 #include "precomp.h"
25 
26 #include "filesup.h"
27 #include "partlist.h"
28 #include "arcname.h"
29 
30 #define NDEBUG
31 #include <debug.h>
32 
33 
34 /* TYPEDEFS *****************************************************************/
35 
36 /* Supported adapter types */
37 typedef enum _ADAPTER_TYPE
38 {
39     EisaAdapter,
40     ScsiAdapter,
41     MultiAdapter,
42     NetAdapter,
43     RamdiskAdapter,
44     AdapterTypeMax
45 } ADAPTER_TYPE, *PADAPTER_TYPE;
46 const PCSTR AdapterTypes_A[] =
47 {
48     "eisa",
49     "scsi",
50     "multi",
51     "net",
52     "ramdisk",
53     NULL
54 };
55 const PCWSTR AdapterTypes_U[] =
56 {
57     L"eisa",
58     L"scsi",
59     L"multi",
60     L"net",
61     L"ramdisk",
62     NULL
63 };
64 
65 /* Supported controller types */
66 typedef enum _CONTROLLER_TYPE
67 {
68     DiskController,
69     CdRomController,
70     ControllerTypeMax
71 } CONTROLLER_TYPE, *PCONTROLLER_TYPE;
72 const PCSTR ControllerTypes_A[] =
73 {
74     "disk",
75     "cdrom",
76     NULL
77 };
78 const PCWSTR ControllerTypes_U[] =
79 {
80     L"disk",
81     L"cdrom",
82     NULL
83 };
84 
85 /* Supported peripheral types */
86 typedef enum _PERIPHERAL_TYPE
87 {
88 //  VDiskPeripheral,
89     RDiskPeripheral,
90     FDiskPeripheral,
91     CdRomPeripheral,
92     PeripheralTypeMax
93 } PERIPHERAL_TYPE, *PPERIPHERAL_TYPE;
94 const PCSTR PeripheralTypes_A[] =
95 {
96 //  "vdisk", // Enable this when we'll support boot from virtual disks!
97     "rdisk",
98     "fdisk",
99     "cdrom",
100     NULL
101 };
102 const PCWSTR PeripheralTypes_U[] =
103 {
104 //  L"vdisk", // Enable this when we'll support boot from virtual disks!
105     L"rdisk",
106     L"fdisk",
107     L"cdrom",
108     NULL
109 };
110 
111 
112 /* FUNCTIONS ****************************************************************/
113 
114 /* static */ PCSTR
115 ArcGetNextTokenA(
116     IN  PCSTR ArcPath,
117     OUT PANSI_STRING TokenSpecifier,
118     OUT PULONG Key)
119 {
120     NTSTATUS Status;
121     PCSTR p = ArcPath;
122     SIZE_T SpecifierLength;
123     ULONG KeyValue;
124 
125     /*
126      * We must have a valid "specifier(key)" string, where 'specifier'
127      * cannot be the empty string, and is followed by '('.
128      */
129     p = strchr(p, '(');
130     if (p == NULL)
131         return NULL; /* No '(' found */
132     if (p == ArcPath)
133         return NULL; /* Path starts with '(' and is thus invalid */
134 
135     SpecifierLength = (p - ArcPath) * sizeof(CHAR);
136     if (SpecifierLength > MAXUSHORT)
137     {
138         return NULL;
139     }
140 
141     /*
142      * The strtoul function skips any leading whitespace.
143      *
144      * Note that if the token is "specifier()" then strtoul won't perform
145      * any conversion and return 0, therefore effectively making the token
146      * equivalent to "specifier(0)", as it should be.
147      */
148     // KeyValue = atoi(p);
149     KeyValue = strtoul(p, (PSTR*)&p, 10);
150 
151     /* Skip any trailing whitespace */
152     while (isspace(*p)) ++p;
153 
154     /* The token must terminate with ')' */
155     if (*p != ')')
156         return NULL;
157 #if 0
158     p = strchr(p, ')');
159     if (p == NULL)
160         return NULL;
161 #endif
162 
163     /* We should have succeeded, copy the token specifier in the buffer */
164     Status = RtlStringCbCopyNA(TokenSpecifier->Buffer,
165                                TokenSpecifier->MaximumLength,
166                                ArcPath, SpecifierLength);
167     if (!NT_SUCCESS(Status))
168         return NULL;
169 
170     TokenSpecifier->Length = (USHORT)SpecifierLength;
171 
172     /* We succeeded, return the token key value */
173     *Key = KeyValue;
174 
175     /* Next token starts just after */
176     return ++p;
177 }
178 
179 static PCWSTR
180 ArcGetNextTokenU(
181     IN  PCWSTR ArcPath,
182     OUT PUNICODE_STRING TokenSpecifier,
183     OUT PULONG Key)
184 {
185     NTSTATUS Status;
186     PCWSTR p = ArcPath;
187     SIZE_T SpecifierLength;
188     ULONG KeyValue;
189 
190     /*
191      * We must have a valid "specifier(key)" string, where 'specifier'
192      * cannot be the empty string, and is followed by '('.
193      */
194     p = wcschr(p, L'(');
195     if (p == NULL)
196         return NULL; /* No '(' found */
197     if (p == ArcPath)
198         return NULL; /* Path starts with '(' and is thus invalid */
199 
200     SpecifierLength = (p - ArcPath) * sizeof(WCHAR);
201     if (SpecifierLength > UNICODE_STRING_MAX_BYTES)
202     {
203         return NULL;
204     }
205 
206     ++p;
207 
208     /*
209      * The strtoul function skips any leading whitespace.
210      *
211      * Note that if the token is "specifier()" then strtoul won't perform
212      * any conversion and return 0, therefore effectively making the token
213      * equivalent to "specifier(0)", as it should be.
214      */
215     // KeyValue = _wtoi(p);
216     KeyValue = wcstoul(p, (PWSTR*)&p, 10);
217 
218     /* Skip any trailing whitespace */
219     while (iswspace(*p)) ++p;
220 
221     /* The token must terminate with ')' */
222     if (*p != L')')
223         return NULL;
224 #if 0
225     p = wcschr(p, L')');
226     if (p == NULL)
227         return NULL;
228 #endif
229 
230     /* We should have succeeded, copy the token specifier in the buffer */
231     Status = RtlStringCbCopyNW(TokenSpecifier->Buffer,
232                                TokenSpecifier->MaximumLength,
233                                ArcPath, SpecifierLength);
234     if (!NT_SUCCESS(Status))
235         return NULL;
236 
237     TokenSpecifier->Length = (USHORT)SpecifierLength;
238 
239     /* We succeeded, return the token key value */
240     *Key = KeyValue;
241 
242     /* Next token starts just after */
243     return ++p;
244 }
245 
246 
247 /* static */ ULONG
248 ArcMatchTokenA(
249     IN PCSTR CandidateToken,
250     IN const PCSTR* TokenTable)
251 {
252     ULONG Index = 0;
253 
254     while (TokenTable[Index] && _stricmp(CandidateToken, TokenTable[Index]) != 0)
255     {
256         ++Index;
257     }
258 
259     return Index;
260 }
261 
262 /* static */ ULONG
263 ArcMatchTokenU(
264     IN PCWSTR CandidateToken,
265     IN const PCWSTR* TokenTable)
266 {
267     ULONG Index = 0;
268 
269     while (TokenTable[Index] && _wcsicmp(CandidateToken, TokenTable[Index]) != 0)
270     {
271         ++Index;
272     }
273 
274     return Index;
275 }
276 
277 static ULONG
278 ArcMatchToken_UStr(
279     IN PCUNICODE_STRING CandidateToken,
280     IN const PCWSTR* TokenTable)
281 {
282     ULONG Index = 0;
283 #if 0
284     SIZE_T Length;
285 #else
286     UNICODE_STRING Token;
287 #endif
288 
289     while (TokenTable[Index])
290     {
291 #if 0
292         Length = wcslen(TokenTable[Index]);
293         if ((Length == CandidateToken->Length / sizeof(WCHAR)) &&
294             (_wcsnicmp(CandidateToken->Buffer, TokenTable[Index], Length) == 0))
295         {
296             break;
297         }
298 #else
299         RtlInitUnicodeString(&Token, TokenTable[Index]);
300         if (RtlEqualUnicodeString(CandidateToken, &Token, TRUE))
301             break;
302 #endif
303 
304         ++Index;
305     }
306 
307     return Index;
308 }
309 
310 
311 BOOLEAN
312 ArcPathNormalize(
313     OUT PUNICODE_STRING NormalizedArcPath,
314     IN  PCWSTR ArcPath)
315 {
316     NTSTATUS Status;
317     PCWSTR EndOfArcName;
318     PCWSTR p;
319     SIZE_T PathLength;
320 
321     if (NormalizedArcPath->MaximumLength < sizeof(UNICODE_NULL))
322         return FALSE;
323 
324     *NormalizedArcPath->Buffer = UNICODE_NULL;
325     NormalizedArcPath->Length = 0;
326 
327     EndOfArcName = wcschr(ArcPath, OBJ_NAME_PATH_SEPARATOR);
328     if (!EndOfArcName)
329         EndOfArcName = ArcPath + wcslen(ArcPath);
330 
331     while ((p = wcsstr(ArcPath, L"()")) && (p < EndOfArcName))
332     {
333 #if 0
334         Status = RtlStringCbCopyNW(NormalizedArcPath->Buffer,
335                                    NormalizedArcPath->MaximumLength,
336                                    ArcPath, (p - ArcPath) * sizeof(WCHAR));
337 #else
338         Status = RtlStringCbCatNW(NormalizedArcPath->Buffer,
339                                   NormalizedArcPath->MaximumLength,
340                                   ArcPath, (p - ArcPath) * sizeof(WCHAR));
341 #endif
342         if (!NT_SUCCESS(Status))
343             return FALSE;
344 
345         Status = RtlStringCbCatW(NormalizedArcPath->Buffer,
346                                  NormalizedArcPath->MaximumLength,
347                                  L"(0)");
348         if (!NT_SUCCESS(Status))
349             return FALSE;
350 #if 0
351         NormalizedArcPath->Buffer += wcslen(NormalizedArcPath->Buffer);
352 #endif
353         ArcPath = p + 2;
354     }
355 
356     Status = RtlStringCbCatW(NormalizedArcPath->Buffer,
357                              NormalizedArcPath->MaximumLength,
358                              ArcPath);
359     if (!NT_SUCCESS(Status))
360         return FALSE;
361 
362     PathLength = wcslen(NormalizedArcPath->Buffer);
363     if (PathLength > UNICODE_STRING_MAX_CHARS)
364     {
365         return FALSE;
366     }
367 
368     NormalizedArcPath->Length = (USHORT)PathLength * sizeof(WCHAR);
369     return TRUE;
370 }
371 
372 
373 /*
374  * ArcNamePath:
375  *      In input, pointer to an ARC path (NULL-terminated) starting by an
376  *      ARC name to be parsed into its different components.
377  *      In output, ArcNamePath points to the beginning of the path after
378  *      the ARC name part.
379  */
380 static NTSTATUS
381 ParseArcName(
382     IN OUT PCWSTR* ArcNamePath,
383     OUT PULONG pAdapterKey,
384     OUT PULONG pControllerKey,
385     OUT PULONG pPeripheralKey,
386     OUT PULONG pPartitionNumber,
387     OUT PADAPTER_TYPE pAdapterType,
388     OUT PCONTROLLER_TYPE pControllerType,
389     OUT PPERIPHERAL_TYPE pPeripheralType,
390     OUT PBOOLEAN pUseSignature)
391 {
392     // NTSTATUS Status;
393     WCHAR TokenBuffer[50];
394     UNICODE_STRING Token;
395     PCWSTR p, q;
396     ULONG AdapterKey = 0;
397     ULONG ControllerKey = 0;
398     ULONG PeripheralKey = 0;
399     ULONG PartitionNumber = 0;
400     ADAPTER_TYPE AdapterType = AdapterTypeMax;
401     CONTROLLER_TYPE ControllerType = ControllerTypeMax;
402     PERIPHERAL_TYPE PeripheralType = PeripheralTypeMax;
403     BOOLEAN UseSignature = FALSE;
404 
405     /*
406      * The format of ArcName is:
407      *    adapter(www)[controller(xxx)peripheral(yyy)[partition(zzz)][filepath]] ,
408      * where the [filepath] part is not being parsed.
409      */
410 
411     RtlInitEmptyUnicodeString(&Token, TokenBuffer, sizeof(TokenBuffer));
412 
413     p = *ArcNamePath;
414 
415     /* Retrieve the adapter */
416     p = ArcGetNextTokenU(p, &Token, &AdapterKey);
417     if (!p)
418     {
419         DPRINT1("No adapter specified!\n");
420         return STATUS_OBJECT_PATH_SYNTAX_BAD;
421     }
422 
423     /* Check for the 'signature()' pseudo-adapter, introduced in Windows 2000 */
424     if (_wcsicmp(Token.Buffer, L"signature") == 0)
425     {
426         /*
427          * We've got a signature! Remember this for later, and set the adapter type to SCSI.
428          * We however check that the rest of the ARC path is valid by parsing the other tokens.
429          * AdapterKey stores the disk signature value (that holds in a ULONG).
430          */
431         UseSignature = TRUE;
432         AdapterType = ScsiAdapter;
433     }
434     else
435     {
436         /* Check for regular adapters */
437         // ArcMatchTokenU(Token.Buffer, AdapterTypes_U);
438         AdapterType = (ADAPTER_TYPE)ArcMatchToken_UStr(&Token, AdapterTypes_U);
439         if (AdapterType >= AdapterTypeMax)
440         {
441             DPRINT1("Invalid adapter type %wZ\n", &Token);
442             return STATUS_OBJECT_NAME_INVALID;
443         }
444 
445         /* Check for adapters that don't take any extra controller or peripheral nodes */
446         if (AdapterType == NetAdapter || AdapterType == RamdiskAdapter)
447         {
448             // if (*p)
449             //     return STATUS_OBJECT_PATH_SYNTAX_BAD;
450 
451             if (AdapterType == NetAdapter)
452             {
453                 DPRINT1("%S(%lu) path is not supported!\n", AdapterTypes_U[AdapterType], AdapterKey);
454                 return STATUS_NOT_SUPPORTED;
455             }
456 
457             goto Quit;
458         }
459     }
460 
461     /* Here, we have either an 'eisa', a 'scsi/signature', or a 'multi' adapter */
462 
463     /* Check for a valid controller */
464     p = ArcGetNextTokenU(p, &Token, &ControllerKey);
465     if (!p)
466     {
467         DPRINT1("%S(%lu) adapter doesn't have a controller!\n", AdapterTypes_U[AdapterType], AdapterKey);
468         return STATUS_OBJECT_PATH_SYNTAX_BAD;
469     }
470     // ArcMatchTokenU(Token.Buffer, ControllerTypes_U);
471     ControllerType = (CONTROLLER_TYPE)ArcMatchToken_UStr(&Token, ControllerTypes_U);
472     if (ControllerType >= ControllerTypeMax)
473     {
474         DPRINT1("Invalid controller type %wZ\n", &Token);
475         return STATUS_OBJECT_NAME_INVALID;
476     }
477 
478     /* Here the controller can only be either a disk or a CDROM */
479 
480     /*
481      * Ignore the controller in case we have a 'multi' adapter.
482      * I guess a similar condition holds for the 'eisa' adapter too...
483      *
484      * For SignatureAdapter, as similar for ScsiAdapter, the controller key corresponds
485      * to the disk target ID. Note that actually, the implementation just ignores the
486      * target ID, as well as the LUN, and just loops over all the available disks and
487      * searches for the one having the correct signature.
488      */
489     if ((AdapterType == MultiAdapter /* || AdapterType == EisaAdapter */) && ControllerKey != 0)
490     {
491         DPRINT1("%S(%lu) adapter with %S(%lu non-zero), ignored!\n",
492                AdapterTypes_U[AdapterType], AdapterKey,
493                ControllerTypes_U[ControllerType], ControllerKey);
494         ControllerKey = 0;
495     }
496 
497     /*
498      * Only the 'scsi' adapter supports a direct 'cdrom' controller.
499      * For the others, we need a 'disk' controller to which a 'cdrom' peripheral can talk to.
500      */
501     if ((AdapterType != ScsiAdapter) && (ControllerType == CdRomController))
502     {
503         DPRINT1("%S(%lu) adapter cannot have a CDROM controller!\n", AdapterTypes_U[AdapterType], AdapterKey);
504         return STATUS_OBJECT_PATH_INVALID;
505     }
506 
507     /* Check for a valid peripheral */
508     p = ArcGetNextTokenU(p, &Token, &PeripheralKey);
509     if (!p)
510     {
511         DPRINT1("%S(%lu)%S(%lu) adapter-controller doesn't have a peripheral!\n",
512                AdapterTypes_U[AdapterType], AdapterKey,
513                ControllerTypes_U[ControllerType], ControllerKey);
514         return STATUS_OBJECT_PATH_SYNTAX_BAD;
515     }
516     // ArcMatchTokenU(Token.Buffer, PeripheralTypes_U);
517     PeripheralType = (PERIPHERAL_TYPE)ArcMatchToken_UStr(&Token, PeripheralTypes_U);
518     if (PeripheralType >= PeripheralTypeMax)
519     {
520         DPRINT1("Invalid peripheral type %wZ\n", &Token);
521         return STATUS_OBJECT_NAME_INVALID;
522     }
523 
524     /*
525      * If we had a 'cdrom' controller already, the corresponding peripheral can only be 'fdisk'
526      * (see for example the ARC syntax for SCSI CD-ROMs: scsi(x)cdrom(y)fdisk(z) where z == 0).
527      */
528     if ((ControllerType == CdRomController) && (PeripheralType != FDiskPeripheral))
529     {
530         DPRINT1("%S(%lu) controller cannot have a %S(%lu) peripheral! (note that we haven't check whether the adapter was SCSI or not)\n",
531                ControllerTypes_U[ControllerType], ControllerKey,
532                PeripheralTypes_U[PeripheralType], PeripheralKey);
533         return STATUS_OBJECT_PATH_INVALID;
534     }
535 
536     /* For a 'scsi' adapter, the possible peripherals are only 'rdisk' or 'fdisk' */
537     if (AdapterType == ScsiAdapter && !(PeripheralType == RDiskPeripheral || PeripheralType == FDiskPeripheral))
538     {
539         DPRINT1("%S(%lu)%S(%lu) SCSI adapter-controller has an invalid peripheral %S(%lu) !\n",
540                AdapterTypes_U[AdapterType], AdapterKey,
541                ControllerTypes_U[ControllerType], ControllerKey,
542                PeripheralTypes_U[PeripheralType], PeripheralKey);
543         return STATUS_OBJECT_PATH_INVALID;
544     }
545 
546 #if 0
547     if (AdapterType == SignatureAdapter && PeripheralKey != 0)
548     {
549         DPRINT1("%S(%lu) adapter with %S(%lu non-zero), ignored!\n",
550                AdapterTypes_U[AdapterType], AdapterKey,
551                PeripheralTypes_U[PeripheralType], PeripheralKey);
552         PeripheralKey = 0;
553     }
554 #endif
555 
556     /* Check for the optional 'partition' specifier */
557     q = ArcGetNextTokenU(p, &Token, &PartitionNumber);
558     if (q && _wcsicmp(Token.Buffer, L"partition") == 0)
559     {
560         /* We've got a partition! */
561         p = q;
562     }
563     else
564     {
565         /*
566          * Either no other ARC token was found, or we've got something else
567          * (possibly invalid or not)...
568          */
569         PartitionNumber = 0;
570     }
571 
572     // TODO: Check the partition number in case of fdisks and cdroms??
573 
574 Quit:
575     /* Return the results */
576     *ArcNamePath      = p;
577     *pAdapterKey      = AdapterKey;
578     *pControllerKey   = ControllerKey;
579     *pPeripheralKey   = PeripheralKey;
580     *pPartitionNumber = PartitionNumber;
581     *pAdapterType     = AdapterType;
582     *pControllerType  = ControllerType;
583     *pPeripheralType  = PeripheralType;
584     *pUseSignature    = UseSignature;
585 
586     return STATUS_SUCCESS;
587 }
588 
589 /*
590  * ArcName:
591  *      ARC name (counted string) to be resolved into a NT device name.
592  *      The caller should have already delimited it from within an ARC path
593  *      (usually by finding where the first path separator appears in the path).
594  *
595  * NtName:
596  *      Receives the resolved NT name. The buffer is NULL-terminated.
597  */
598 static NTSTATUS
599 ResolveArcNameNtSymLink(
600     OUT PUNICODE_STRING NtName,
601     IN  PUNICODE_STRING ArcName)
602 {
603     NTSTATUS Status;
604     OBJECT_ATTRIBUTES ObjectAttributes;
605     HANDLE DirectoryHandle, LinkHandle;
606     UNICODE_STRING ArcNameDir;
607 
608     if (NtName->MaximumLength < sizeof(UNICODE_NULL))
609         return STATUS_BUFFER_TOO_SMALL;
610 
611     /* Open the \ArcName object directory */
612     RtlInitUnicodeString(&ArcNameDir, L"\\ArcName");
613     InitializeObjectAttributes(&ObjectAttributes,
614                                &ArcNameDir,
615                                OBJ_CASE_INSENSITIVE,
616                                NULL,
617                                NULL);
618     Status = NtOpenDirectoryObject(&DirectoryHandle,
619                                    DIRECTORY_ALL_ACCESS,
620                                    &ObjectAttributes);
621     if (!NT_SUCCESS(Status))
622     {
623         DPRINT1("NtOpenDirectoryObject(%wZ) failed, Status 0x%08lx\n", &ArcNameDir, Status);
624         return Status;
625     }
626 
627     /* Open the ARC name link */
628     InitializeObjectAttributes(&ObjectAttributes,
629                                ArcName,
630                                OBJ_CASE_INSENSITIVE,
631                                DirectoryHandle,
632                                NULL);
633     Status = NtOpenSymbolicLinkObject(&LinkHandle,
634                                       SYMBOLIC_LINK_QUERY,
635                                       &ObjectAttributes);
636 
637     /* Close the \ArcName object directory handle */
638     NtClose(DirectoryHandle);
639 
640     /* Check for success */
641     if (!NT_SUCCESS(Status))
642     {
643         DPRINT1("NtOpenSymbolicLinkObject(%wZ) failed, Status 0x%08lx\n", ArcName, Status);
644         return Status;
645     }
646 
647     /* Reserve one WCHAR for the NULL-termination */
648     NtName->MaximumLength -= sizeof(UNICODE_NULL);
649 
650     /* Resolve the link and close its handle */
651     Status = NtQuerySymbolicLinkObject(LinkHandle, NtName, NULL);
652     NtClose(LinkHandle);
653 
654     /* Restore the NULL-termination */
655     NtName->MaximumLength += sizeof(UNICODE_NULL);
656 
657     /* Check for success */
658     if (!NT_SUCCESS(Status))
659     {
660         /* We failed, don't touch NtName */
661         DPRINT1("NtQuerySymbolicLinkObject(%wZ) failed, Status 0x%08lx\n", ArcName, Status);
662     }
663     else
664     {
665         /* We succeeded, NULL-terminate NtName */
666         NtName->Buffer[NtName->Length / sizeof(WCHAR)] = UNICODE_NULL;
667     }
668 
669     return Status;
670 }
671 
672 /*
673  * ArcNamePath:
674  *      In input, pointer to an ARC path (NULL-terminated) starting by an
675  *      ARC name to be resolved into a NT device name.
676  *      In opposition to ResolveArcNameNtSymLink(), the caller does not have
677  *      to delimit the ARC name from within an ARC path. The real ARC name is
678  *      deduced after parsing the ARC path, and, in output, ArcNamePath points
679  *      to the beginning of the path after the ARC name part.
680  *
681  * NtName:
682  *      Receives the resolved NT name. The buffer is NULL-terminated.
683  *
684  * PartList:
685  *      (Optional) partition list that helps in resolving the paths pointing
686  *      to hard disks.
687  */
688 static NTSTATUS
689 ResolveArcNameManually(
690     OUT PUNICODE_STRING NtName,
691     IN OUT PCWSTR* ArcNamePath,
692     IN  PPARTLIST PartList)
693 {
694     NTSTATUS Status;
695     ULONG AdapterKey;
696     ULONG ControllerKey;
697     ULONG PeripheralKey;
698     ULONG PartitionNumber;
699     ADAPTER_TYPE AdapterType;
700     CONTROLLER_TYPE ControllerType;
701     PERIPHERAL_TYPE PeripheralType;
702     BOOLEAN UseSignature;
703     SIZE_T NameLength;
704 
705     if (NtName->MaximumLength < sizeof(UNICODE_NULL))
706         return STATUS_BUFFER_TOO_SMALL;
707 
708     /* Parse the ARC path */
709     Status = ParseArcName(ArcNamePath,
710                           &AdapterKey,
711                           &ControllerKey,
712                           &PeripheralKey,
713                           &PartitionNumber,
714                           &AdapterType,
715                           &ControllerType,
716                           &PeripheralType,
717                           &UseSignature);
718     if (!NT_SUCCESS(Status))
719         return Status;
720 
721     // TODO: Check the partition number in case of fdisks and cdroms??
722 
723     /* Check for adapters that don't take any extra controller or peripheral node */
724     if (AdapterType == NetAdapter || AdapterType == RamdiskAdapter)
725     {
726         if (AdapterType == NetAdapter)
727         {
728             DPRINT1("%S(%lu) path is not supported!\n", AdapterTypes_U[AdapterType], AdapterKey);
729             return STATUS_NOT_SUPPORTED;
730         }
731 
732         Status = RtlStringCbPrintfW(NtName->Buffer, NtName->MaximumLength,
733                                     L"\\Device\\Ramdisk%lu", AdapterKey);
734     }
735     else
736     if (ControllerType == CdRomController) // and so, AdapterType == ScsiAdapter and PeripheralType == FDiskPeripheral
737     {
738         Status = RtlStringCbPrintfW(NtName->Buffer, NtName->MaximumLength,
739                                     L"\\Device\\Scsi\\CdRom%lu", ControllerKey);
740     }
741     else
742     /* Now, ControllerType == DiskController */
743     if (PeripheralType == CdRomPeripheral)
744     {
745         Status = RtlStringCbPrintfW(NtName->Buffer, NtName->MaximumLength,
746                                     L"\\Device\\CdRom%lu", PeripheralKey);
747     }
748     else
749     if (PeripheralType == FDiskPeripheral)
750     {
751         Status = RtlStringCbPrintfW(NtName->Buffer, NtName->MaximumLength,
752                                     L"\\Device\\Floppy%lu", PeripheralKey);
753     }
754     else
755     if (PeripheralType == RDiskPeripheral)
756     {
757         PDISKENTRY DiskEntry;
758         PPARTENTRY PartEntry = NULL;
759 
760         if (UseSignature)
761         {
762             /* The disk signature is stored in AdapterKey */
763             DiskEntry = GetDiskBySignature(PartList, AdapterKey);
764         }
765         else
766         {
767             DiskEntry = GetDiskBySCSI(PartList, AdapterKey,
768                                       ControllerKey, PeripheralKey);
769         }
770         if (!DiskEntry)
771             return STATUS_OBJECT_PATH_NOT_FOUND; // STATUS_NOT_FOUND;
772 
773         if (PartitionNumber != 0)
774         {
775             PartEntry = GetPartition(DiskEntry, PartitionNumber);
776             if (!PartEntry)
777                 return STATUS_OBJECT_PATH_NOT_FOUND; // STATUS_DEVICE_NOT_PARTITIONED;
778             ASSERT(PartEntry->DiskEntry == DiskEntry);
779         }
780 
781         Status = RtlStringCbPrintfW(NtName->Buffer, NtName->MaximumLength,
782                                     L"\\Device\\Harddisk%lu\\Partition%lu",
783                                     DiskEntry->DiskNumber, PartitionNumber);
784     }
785 #if 0 // FIXME: Not implemented yet!
786     else
787     if (PeripheralType == VDiskPeripheral)
788     {
789         // TODO: Check how Win 7+ deals with virtual disks.
790         Status = RtlStringCbPrintfW(NtName->Buffer, NtName->MaximumLength,
791                                     L"\\Device\\VirtualHarddisk%lu\\Partition%lu",
792                                     PeripheralKey, PartitionNumber);
793     }
794 #endif
795 
796     if (!NT_SUCCESS(Status))
797     {
798         /* Returned NtName is invalid, so zero it out */
799         *NtName->Buffer = UNICODE_NULL;
800         NtName->Length = 0;
801 
802         return Status;
803     }
804 
805     /* Update NtName length */
806     NameLength = wcslen(NtName->Buffer);
807     if (NameLength > UNICODE_STRING_MAX_CHARS)
808     {
809         return STATUS_NAME_TOO_LONG;
810     }
811 
812     NtName->Length = (USHORT)NameLength * sizeof(WCHAR);
813 
814     return STATUS_SUCCESS;
815 }
816 
817 
818 BOOLEAN
819 ArcPathToNtPath(
820     OUT PUNICODE_STRING NtPath,
821     IN  PCWSTR ArcPath,
822     IN  PPARTLIST PartList OPTIONAL)
823 {
824     NTSTATUS Status;
825     PCWSTR BeginOfPath;
826     UNICODE_STRING ArcName;
827     SIZE_T PathLength;
828 
829     /* TODO: We should "normalize" the path, i.e. expand all the xxx() into xxx(0) */
830 
831     if (NtPath->MaximumLength < sizeof(UNICODE_NULL))
832         return FALSE;
833 
834     *NtPath->Buffer = UNICODE_NULL;
835     NtPath->Length = 0;
836 
837     /*
838      * - First, check whether the ARC path is already inside \\ArcName
839      *   and if so, map it to the corresponding NT path.
840      * - Only then, if we haven't found any ArcName, try to build a
841      *   NT path by deconstructing the ARC path, using its disk and
842      *   partition numbers. We may use here our disk/partition list.
843      *
844      * See also freeldr/arcname.c
845      *
846      * Note that it would be nice to maintain a cache of these mappings.
847      */
848 
849     /*
850      * Initialize the ARC name to resolve, by cutting the ARC path at the first
851      * NT path separator. The ARC name therefore ends where the NT path part starts.
852      */
853     RtlInitUnicodeString(&ArcName, ArcPath);
854     BeginOfPath = wcschr(ArcName.Buffer, OBJ_NAME_PATH_SEPARATOR);
855     if (BeginOfPath)
856         ArcName.Length = (ULONG_PTR)BeginOfPath - (ULONG_PTR)ArcName.Buffer;
857 
858     /* Resolve the ARC name via NT SymLinks. Note that NtPath is returned NULL-terminated. */
859     Status = ResolveArcNameNtSymLink(NtPath, &ArcName);
860     if (!NT_SUCCESS(Status))
861     {
862         /* We failed, attempt a manual resolution */
863         DPRINT1("ResolveArcNameNtSymLink(ArcName = '%wZ') for ArcPath = '%S' failed, Status 0x%08lx\n", &ArcName, ArcPath, Status);
864 
865         /*
866          * We failed at directly resolving the ARC path, and we cannot perform
867          * a manual resolution because we don't have any disk/partition list,
868          * we therefore fail here.
869          */
870         if (!PartList)
871         {
872             DPRINT1("PartList == NULL, cannot perform a manual resolution\n");
873             return FALSE;
874         }
875 
876         *NtPath->Buffer = UNICODE_NULL;
877         NtPath->Length = 0;
878 
879         BeginOfPath = ArcPath;
880         Status = ResolveArcNameManually(NtPath, &BeginOfPath, PartList);
881         if (!NT_SUCCESS(Status))
882         {
883             /* We really failed this time, bail out */
884             DPRINT1("ResolveArcNameManually(ArcPath = '%S') failed, Status 0x%08lx\n", ArcPath, Status);
885             return FALSE;
886         }
887     }
888 
889     /*
890      * We succeeded. Concatenate the rest of the system-specific path. We know the path is going
891      * to be inside the NT namespace, therefore we can use the path string concatenation function
892      * that uses '\\' as the path separator.
893      */
894     if (BeginOfPath && *BeginOfPath)
895     {
896         Status = ConcatPaths(NtPath->Buffer, NtPath->MaximumLength / sizeof(WCHAR), 1, BeginOfPath);
897         if (!NT_SUCCESS(Status))
898         {
899             /* Buffer not large enough, or whatever...: just bail out */
900             return FALSE;
901         }
902     }
903 
904     PathLength = wcslen(NtPath->Buffer);
905     if (PathLength > UNICODE_STRING_MAX_CHARS)
906     {
907         return FALSE;
908     }
909 
910     NtPath->Length = (USHORT)PathLength * sizeof(WCHAR);
911 
912     return TRUE;
913 }
914 
915 #if 0 // FIXME: Not implemented yet!
916 PWSTR
917 NtPathToArcPath(
918     IN PWSTR NtPath)
919 {
920     /*
921      * - First, check whether any of the ARC paths inside \\ArcName
922      *   map to the corresponding NT path. If so, we are OK.
923      * - Only then, if we haven't found any ArcName, try to build an
924      *   ARC path by deconstructing the NT path, using its disk and
925      *   partition numbers. We may use here our disk/partition list.
926      *
927      * See also freeldr/arcname.c
928      *
929      * Note that it would be nice to maintain a cache of these mappings.
930      */
931 }
932 #endif
933 
934 /* EOF */
935