xref: /reactos/base/setup/lib/utils/filesup.c (revision fc7bc3bb)
1 /*
2  * PROJECT:     ReactOS Setup Library
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     File support functions.
5  * COPYRIGHT:   Casper S. Hornstrup (chorns@users.sourceforge.net)
6  *              Copyright 2017-2018 Hermes Belusca-Maito
7  */
8 
9 /* INCLUDES *****************************************************************/
10 
11 #include "precomp.h"
12 #include "filesup.h"
13 
14 #define NDEBUG
15 #include <debug.h>
16 
17 
18 // ACHTUNG! HAXX FIXME!!
19 #define _SEH2_TRY
20 #define _SEH2_LEAVE     goto __SEH2_FINALLY__label;
21 #define _SEH2_FINALLY   __SEH2_FINALLY__label:
22 #define _SEH2_END
23 
24 
25 /* FUNCTIONS ****************************************************************/
26 
27 static
28 NTSTATUS
29 SetupCreateSingleDirectory(
30     IN PCWSTR DirectoryName)
31 {
32     OBJECT_ATTRIBUTES ObjectAttributes;
33     IO_STATUS_BLOCK IoStatusBlock;
34     UNICODE_STRING PathName;
35     HANDLE DirectoryHandle;
36     NTSTATUS Status;
37 
38     if (!RtlCreateUnicodeString(&PathName, DirectoryName))
39         return STATUS_NO_MEMORY;
40 
41     if (PathName.Length > sizeof(WCHAR) &&
42         PathName.Buffer[PathName.Length / sizeof(WCHAR) - 2] == L'\\' &&
43         PathName.Buffer[PathName.Length / sizeof(WCHAR) - 1] == L'.')
44     {
45         PathName.Length -= sizeof(WCHAR);
46         PathName.Buffer[PathName.Length / sizeof(WCHAR)] = UNICODE_NULL;
47     }
48 
49     if (PathName.Length > sizeof(WCHAR) &&
50         PathName.Buffer[PathName.Length / sizeof(WCHAR) - 1] == L'\\')
51     {
52         PathName.Length -= sizeof(WCHAR);
53         PathName.Buffer[PathName.Length / sizeof(WCHAR)] = UNICODE_NULL;
54     }
55 
56     InitializeObjectAttributes(&ObjectAttributes,
57                                &PathName,
58                                OBJ_CASE_INSENSITIVE | OBJ_INHERIT,
59                                NULL,
60                                NULL);
61 
62     Status = NtCreateFile(&DirectoryHandle,
63                           FILE_LIST_DIRECTORY | SYNCHRONIZE,
64                           &ObjectAttributes,
65                           &IoStatusBlock,
66                           NULL,
67                           FILE_ATTRIBUTE_DIRECTORY,
68                           FILE_SHARE_READ | FILE_SHARE_WRITE,
69                           FILE_OPEN_IF,
70                           FILE_OPEN_FOR_BACKUP_INTENT | FILE_DIRECTORY_FILE,
71                           NULL,
72                           0);
73     if (NT_SUCCESS(Status))
74     {
75         NtClose(DirectoryHandle);
76     }
77 
78     RtlFreeUnicodeString(&PathName);
79 
80     return Status;
81 }
82 
83 NTSTATUS
84 SetupCreateDirectory(
85     IN PCWSTR PathName)
86 {
87     NTSTATUS Status = STATUS_SUCCESS;
88     PWCHAR PathBuffer = NULL;
89     PWCHAR Ptr, EndPtr;
90     ULONG BackslashCount;
91     ULONG Size;
92 
93     Size = (wcslen(PathName) + 1) * sizeof(WCHAR);
94     PathBuffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, Size);
95     if (PathBuffer == NULL)
96         return STATUS_INSUFFICIENT_RESOURCES;
97 
98     wcscpy(PathBuffer, PathName);
99     EndPtr = PathBuffer + wcslen(PathName);
100 
101     Ptr = PathBuffer;
102 
103     /* Skip the '\Device\HarddiskX\PartitionY\ part */
104     BackslashCount = 0;
105     while (Ptr < EndPtr && BackslashCount < 4)
106     {
107         if (*Ptr == L'\\')
108             BackslashCount++;
109 
110         Ptr++;
111     }
112 
113     while (Ptr < EndPtr)
114     {
115         if (*Ptr == L'\\')
116         {
117             *Ptr = 0;
118 
119             DPRINT("PathBuffer: %S\n", PathBuffer);
120             if (!DoesDirExist(NULL, PathBuffer))
121             {
122                 DPRINT("Create: %S\n", PathBuffer);
123                 Status = SetupCreateSingleDirectory(PathBuffer);
124                 if (!NT_SUCCESS(Status))
125                     goto done;
126             }
127 
128             *Ptr = L'\\';
129         }
130 
131         Ptr++;
132     }
133 
134     if (!DoesDirExist(NULL, PathBuffer))
135     {
136         DPRINT("Create: %S\n", PathBuffer);
137         Status = SetupCreateSingleDirectory(PathBuffer);
138         if (!NT_SUCCESS(Status))
139             goto done;
140     }
141 
142 done:
143     DPRINT("Done.\n");
144     if (PathBuffer != NULL)
145         RtlFreeHeap(RtlGetProcessHeap(), 0, PathBuffer);
146 
147     return Status;
148 }
149 
150 NTSTATUS
151 SetupDeleteFile(
152     IN PCWSTR FileName,
153     IN BOOLEAN ForceDelete) // ForceDelete can be used to delete read-only files
154 {
155     NTSTATUS Status;
156     UNICODE_STRING NtPathU;
157     OBJECT_ATTRIBUTES ObjectAttributes;
158     IO_STATUS_BLOCK IoStatusBlock;
159     HANDLE FileHandle;
160     FILE_DISPOSITION_INFORMATION FileDispInfo;
161     BOOLEAN RetryOnce = FALSE;
162 
163     /* Open the directory name that was passed in */
164     RtlInitUnicodeString(&NtPathU, FileName);
165     InitializeObjectAttributes(&ObjectAttributes,
166                                &NtPathU,
167                                OBJ_CASE_INSENSITIVE,
168                                NULL,
169                                NULL);
170 
171 Retry: // We go back there once if RetryOnce == TRUE
172     Status = NtOpenFile(&FileHandle,
173                         DELETE | FILE_READ_ATTRIBUTES |
174                         (RetryOnce ? FILE_WRITE_ATTRIBUTES : 0),
175                         &ObjectAttributes,
176                         &IoStatusBlock,
177                         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
178                         FILE_NON_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT);
179     if (!NT_SUCCESS(Status))
180     {
181         DPRINT1("NtOpenFile failed with Status 0x%08lx\n", Status);
182         return Status;
183     }
184 
185     if (RetryOnce)
186     {
187         FILE_BASIC_INFORMATION FileInformation;
188 
189         Status = NtQueryInformationFile(FileHandle,
190                                         &IoStatusBlock,
191                                         &FileInformation,
192                                         sizeof(FILE_BASIC_INFORMATION),
193                                         FileBasicInformation);
194         if (!NT_SUCCESS(Status))
195         {
196             DPRINT1("NtQueryInformationFile failed with Status 0x%08lx\n", Status);
197             NtClose(FileHandle);
198             return Status;
199         }
200 
201         FileInformation.FileAttributes = FILE_ATTRIBUTE_NORMAL;
202         Status = NtSetInformationFile(FileHandle,
203                                       &IoStatusBlock,
204                                       &FileInformation,
205                                       sizeof(FILE_BASIC_INFORMATION),
206                                       FileBasicInformation);
207         NtClose(FileHandle);
208         if (!NT_SUCCESS(Status))
209         {
210             DPRINT1("NtSetInformationFile failed with Status 0x%08lx\n", Status);
211             return Status;
212         }
213     }
214 
215     /* Ask for the file to be deleted */
216     FileDispInfo.DeleteFile = TRUE;
217     Status = NtSetInformationFile(FileHandle,
218                                   &IoStatusBlock,
219                                   &FileDispInfo,
220                                   sizeof(FILE_DISPOSITION_INFORMATION),
221                                   FileDispositionInformation);
222     NtClose(FileHandle);
223 
224     if (!NT_SUCCESS(Status))
225         DPRINT1("Deletion of file '%S' failed, Status 0x%08lx\n", FileName, Status);
226 
227     // FIXME: Check the precise value of Status!
228     if (!NT_SUCCESS(Status) && ForceDelete && !RetryOnce)
229     {
230         /* Retry once */
231         RetryOnce = TRUE;
232         goto Retry;
233     }
234 
235     /* Return result to the caller */
236     return Status;
237 }
238 
239 NTSTATUS
240 SetupCopyFile(
241     IN PCWSTR SourceFileName,
242     IN PCWSTR DestinationFileName,
243     IN BOOLEAN FailIfExists)
244 {
245     NTSTATUS Status;
246     UNICODE_STRING FileName;
247     OBJECT_ATTRIBUTES ObjectAttributes;
248     HANDLE FileHandleSource;
249     HANDLE FileHandleDest;
250     IO_STATUS_BLOCK IoStatusBlock;
251     FILE_STANDARD_INFORMATION FileStandard;
252     FILE_BASIC_INFORMATION FileBasic;
253     ULONG RegionSize;
254     HANDLE SourceFileSection;
255     PVOID SourceFileMap = NULL;
256     SIZE_T SourceSectionSize = 0;
257     LARGE_INTEGER ByteOffset;
258 
259     RtlInitUnicodeString(&FileName, SourceFileName);
260     InitializeObjectAttributes(&ObjectAttributes,
261                                &FileName,
262                                OBJ_CASE_INSENSITIVE,
263                                NULL,
264                                NULL);
265 
266     Status = NtOpenFile(&FileHandleSource,
267                         GENERIC_READ,
268                         &ObjectAttributes,
269                         &IoStatusBlock,
270                         FILE_SHARE_READ,
271                         FILE_SEQUENTIAL_ONLY);
272     if (!NT_SUCCESS(Status))
273     {
274         DPRINT1("NtOpenFile failed: %x, %wZ\n", Status, &FileName);
275         goto done;
276     }
277 
278     Status = NtQueryInformationFile(FileHandleSource,
279                                     &IoStatusBlock,
280                                     &FileStandard,
281                                     sizeof(FILE_STANDARD_INFORMATION),
282                                     FileStandardInformation);
283     if (!NT_SUCCESS(Status))
284     {
285         DPRINT1("NtQueryInformationFile failed: %x\n", Status);
286         goto closesrc;
287     }
288 
289     Status = NtQueryInformationFile(FileHandleSource,
290                                     &IoStatusBlock,
291                                     &FileBasic,
292                                     sizeof(FILE_BASIC_INFORMATION),
293                                     FileBasicInformation);
294     if (!NT_SUCCESS(Status))
295     {
296         DPRINT1("NtQueryInformationFile failed: %x\n", Status);
297         goto closesrc;
298     }
299 
300     Status = NtCreateSection(&SourceFileSection,
301                              SECTION_MAP_READ,
302                              NULL,
303                              NULL,
304                              PAGE_READONLY,
305                              SEC_COMMIT,
306                              FileHandleSource);
307     if (!NT_SUCCESS(Status))
308     {
309         DPRINT1("NtCreateSection failed: %x, %S\n", Status, SourceFileName);
310         goto closesrc;
311     }
312 
313     Status = NtMapViewOfSection(SourceFileSection,
314                                 NtCurrentProcess(),
315                                 &SourceFileMap,
316                                 0,
317                                 0,
318                                 NULL,
319                                 &SourceSectionSize,
320                                 ViewUnmap,
321                                 0,
322                                 PAGE_READONLY);
323     if (!NT_SUCCESS(Status))
324     {
325         DPRINT1("NtMapViewOfSection failed: %x, %S\n", Status, SourceFileName);
326         goto closesrcsec;
327     }
328 
329     RtlInitUnicodeString(&FileName, DestinationFileName);
330     InitializeObjectAttributes(&ObjectAttributes,
331                                &FileName,
332                                OBJ_CASE_INSENSITIVE,
333                                NULL,
334                                NULL);
335 
336     Status = NtCreateFile(&FileHandleDest,
337                           GENERIC_WRITE | SYNCHRONIZE,
338                           &ObjectAttributes,
339                           &IoStatusBlock,
340                           NULL,
341                           FileBasic.FileAttributes, // FILE_ATTRIBUTE_NORMAL,
342                           0,
343                           FailIfExists ? FILE_CREATE : FILE_OVERWRITE_IF,
344                           FILE_NO_INTERMEDIATE_BUFFERING |
345                           FILE_SEQUENTIAL_ONLY |
346                           FILE_SYNCHRONOUS_IO_NONALERT,
347                           NULL,
348                           0);
349     if (!NT_SUCCESS(Status))
350     {
351         /*
352          * Open may have failed because the file to overwrite
353          * is in readonly mode.
354          */
355         if (Status == STATUS_ACCESS_DENIED)
356         {
357             FILE_BASIC_INFORMATION FileBasicInfo;
358 
359             /* Reattempt to open it with limited access */
360             Status = NtCreateFile(&FileHandleDest,
361                                   FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
362                                   &ObjectAttributes,
363                                   &IoStatusBlock,
364                                   NULL,
365                                   FILE_ATTRIBUTE_NORMAL,
366                                   0,
367                                   FILE_OPEN,
368                                   FILE_NO_INTERMEDIATE_BUFFERING |
369                                   FILE_SEQUENTIAL_ONLY |
370                                   FILE_SYNCHRONOUS_IO_NONALERT,
371                                   NULL,
372                                   0);
373             /* Fail for real if we cannot open it that way */
374             if (!NT_SUCCESS(Status))
375             {
376                 DPRINT1("NtCreateFile failed: %x, %wZ\n", Status, &FileName);
377                 goto unmapsrcsec;
378             }
379 
380             /* Zero our basic info, just to set attributes */
381             RtlZeroMemory(&FileBasicInfo, sizeof(FileBasicInfo));
382             /* Reset attributes to normal, no read-only */
383             FileBasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
384             /*
385              * We basically don't care about whether it succeed:
386              * if it didn't, later open will fail.
387              */
388             NtSetInformationFile(FileHandleDest, &IoStatusBlock, &FileBasicInfo,
389                                  sizeof(FileBasicInfo), FileBasicInformation);
390 
391             /* Close file */
392             NtClose(FileHandleDest);
393 
394             /* And re-attempt overwrite */
395             Status = NtCreateFile(&FileHandleDest,
396                                   GENERIC_WRITE | SYNCHRONIZE,
397                                   &ObjectAttributes,
398                                   &IoStatusBlock,
399                                   NULL,
400                                   FILE_ATTRIBUTE_NORMAL,
401                                   0,
402                                   FILE_OVERWRITE_IF,
403                                   FILE_NO_INTERMEDIATE_BUFFERING |
404                                   FILE_SEQUENTIAL_ONLY |
405                                   FILE_SYNCHRONOUS_IO_NONALERT,
406                                   NULL,
407                                   0);
408         }
409 
410         /* We failed */
411         if (!NT_SUCCESS(Status))
412         {
413             DPRINT1("NtCreateFile failed: %x, %wZ\n", Status, &FileName);
414             goto unmapsrcsec;
415         }
416     }
417 
418     RegionSize = (ULONG)PAGE_ROUND_UP(FileStandard.EndOfFile.u.LowPart);
419     IoStatusBlock.Status = 0;
420     ByteOffset.QuadPart = 0ULL;
421     Status = NtWriteFile(FileHandleDest,
422                          NULL,
423                          NULL,
424                          NULL,
425                          &IoStatusBlock,
426                          SourceFileMap,
427                          RegionSize,
428                          &ByteOffset,
429                          NULL);
430     if (!NT_SUCCESS(Status))
431     {
432         DPRINT1("NtWriteFile failed: %x:%x, iosb: %p src: %p, size: %x\n",
433                 Status, IoStatusBlock.Status, &IoStatusBlock, SourceFileMap, RegionSize);
434         goto closedest;
435     }
436 
437     /* Copy file date/time from source file */
438     Status = NtSetInformationFile(FileHandleDest,
439                                   &IoStatusBlock,
440                                   &FileBasic,
441                                   sizeof(FILE_BASIC_INFORMATION),
442                                   FileBasicInformation);
443     if (!NT_SUCCESS(Status))
444     {
445         DPRINT1("NtSetInformationFile failed: %x\n", Status);
446         goto closedest;
447     }
448 
449     /* Shorten the file back to its real size after completing the write */
450     Status = NtSetInformationFile(FileHandleDest,
451                                   &IoStatusBlock,
452                                   &FileStandard.EndOfFile,
453                                   sizeof(FILE_END_OF_FILE_INFORMATION),
454                                   FileEndOfFileInformation);
455     if (!NT_SUCCESS(Status))
456     {
457         DPRINT1("NtSetInformationFile failed: %x\n", Status);
458     }
459 
460 closedest:
461     NtClose(FileHandleDest);
462 
463 unmapsrcsec:
464     NtUnmapViewOfSection(NtCurrentProcess(), SourceFileMap);
465 
466 closesrcsec:
467     NtClose(SourceFileSection);
468 
469 closesrc:
470     NtClose(FileHandleSource);
471 
472 done:
473     return Status;
474 }
475 
476 /*
477  * Synchronized with its kernel32 counterpart, but we don't manage reparse points here.
478  */
479 NTSTATUS
480 SetupMoveFile(
481     IN PCWSTR ExistingFileName,
482     IN PCWSTR NewFileName,
483     IN ULONG Flags)
484 {
485     NTSTATUS Status;
486     IO_STATUS_BLOCK IoStatusBlock;
487     OBJECT_ATTRIBUTES ObjectAttributes;
488     PFILE_RENAME_INFORMATION RenameInfo;
489     UNICODE_STRING NewPathU, ExistingPathU;
490     HANDLE SourceHandle = NULL;
491     BOOLEAN ReplaceIfExists;
492 
493     RtlInitUnicodeString(&ExistingPathU, ExistingFileName);
494     RtlInitUnicodeString(&NewPathU, NewFileName);
495 
496     _SEH2_TRY
497     {
498         ReplaceIfExists = !!(Flags & MOVEFILE_REPLACE_EXISTING);
499 
500         /* Unless we manage a proper opening, we'll attempt to reopen without reparse support */
501         InitializeObjectAttributes(&ObjectAttributes,
502                                    &ExistingPathU,
503                                    OBJ_CASE_INSENSITIVE,
504                                    NULL,
505                                    NULL);
506         /* Attempt to open source file */
507         Status = NtOpenFile(&SourceHandle,
508                             FILE_READ_ATTRIBUTES | DELETE | SYNCHRONIZE,
509                             &ObjectAttributes,
510                             &IoStatusBlock,
511                             FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
512                             FILE_OPEN_FOR_BACKUP_INTENT | ((Flags & MOVEFILE_WRITE_THROUGH) ? FILE_WRITE_THROUGH : 0));
513         if (!NT_SUCCESS(Status))
514         {
515             if (Status != STATUS_INVALID_PARAMETER)
516             {
517                 _SEH2_LEAVE;
518             }
519         }
520 
521         /* At that point, we MUST have a source handle */
522         ASSERT(SourceHandle);
523 
524         /* Allocate renaming buffer and fill it */
525         RenameInfo = RtlAllocateHeap(RtlGetProcessHeap(), 0, NewPathU.Length + sizeof(FILE_RENAME_INFORMATION));
526         if (RenameInfo == NULL)
527         {
528             Status = STATUS_NO_MEMORY;
529             _SEH2_LEAVE;
530         }
531 
532         RtlCopyMemory(&RenameInfo->FileName, NewPathU.Buffer, NewPathU.Length);
533         RenameInfo->ReplaceIfExists = ReplaceIfExists;
534         RenameInfo->RootDirectory = NULL;
535         RenameInfo->FileNameLength = NewPathU.Length;
536 
537         /* Attempt to rename the file */
538         Status = NtSetInformationFile(SourceHandle,
539                                       &IoStatusBlock,
540                                       RenameInfo,
541                                       NewPathU.Length + sizeof(FILE_RENAME_INFORMATION),
542                                       FileRenameInformation);
543         RtlFreeHeap(RtlGetProcessHeap(), 0, RenameInfo);
544         if (NT_SUCCESS(Status))
545         {
546             /* If it succeeded, all fine, quit */
547             _SEH2_LEAVE;
548         }
549         /*
550          * If we failed for any other reason than not the same device, fail.
551          * If we failed because of different devices, only allow renaming
552          * if user allowed copy.
553          */
554         if (Status != STATUS_NOT_SAME_DEVICE || !(Flags & MOVEFILE_COPY_ALLOWED))
555         {
556             /* ReactOS hack! To be removed once all FSD have proper renaming support
557              * Just leave status to error and leave
558              */
559             if (Status == STATUS_NOT_IMPLEMENTED)
560             {
561                 DPRINT1("Forcing copy, renaming not supported by FSD\n");
562             }
563             else
564             {
565                 _SEH2_LEAVE;
566             }
567         }
568 
569         /* Close the source file */
570         NtClose(SourceHandle);
571         SourceHandle = NULL;
572 
573         /* Perform the file copy */
574         Status = SetupCopyFile(ExistingFileName,
575                                NewFileName,
576                                !ReplaceIfExists);
577 
578         /* If it succeeded, delete the source file */
579         if (NT_SUCCESS(Status))
580         {
581             /* Force-delete files even if read-only */
582             SetupDeleteFile(ExistingFileName, TRUE);
583         }
584     }
585     _SEH2_FINALLY
586     {
587         if (SourceHandle)
588             NtClose(SourceHandle);
589     }
590     _SEH2_END;
591 
592     return Status;
593 }
594 
595 NTSTATUS
596 ConcatPathsV(
597     IN OUT PWSTR PathBuffer,
598     IN SIZE_T cchPathSize,
599     IN ULONG NumberOfPathComponents,
600     IN va_list PathComponentsList)
601 {
602     NTSTATUS Status = STATUS_SUCCESS;
603     SIZE_T cchPathLen;
604     PCWSTR PathComponent;
605 
606     if (cchPathSize < 1)
607         return STATUS_SUCCESS;
608 
609     while (NumberOfPathComponents--)
610     {
611         PathComponent = va_arg(PathComponentsList, PCWSTR);
612         if (!PathComponent)
613             continue;
614 
615         cchPathLen = min(cchPathSize, wcslen(PathBuffer));
616         if (cchPathLen >= cchPathSize)
617             return STATUS_BUFFER_OVERFLOW;
618 
619         if (PathComponent[0] != OBJ_NAME_PATH_SEPARATOR &&
620             cchPathLen > 0 && PathBuffer[cchPathLen-1] != OBJ_NAME_PATH_SEPARATOR)
621         {
622             /* PathComponent does not start with '\' and PathBuffer does not end with '\' */
623             Status = RtlStringCchCatW(PathBuffer, cchPathSize, L"\\");
624             if (!NT_SUCCESS(Status))
625                 return Status;
626         }
627         else if (PathComponent[0] == OBJ_NAME_PATH_SEPARATOR &&
628                  cchPathLen > 0 && PathBuffer[cchPathLen-1] == OBJ_NAME_PATH_SEPARATOR)
629         {
630             /* PathComponent starts with '\' and PathBuffer ends with '\' */
631             while (*PathComponent == OBJ_NAME_PATH_SEPARATOR)
632                 ++PathComponent; // Skip any backslash
633         }
634         Status = RtlStringCchCatW(PathBuffer, cchPathSize, PathComponent);
635         if (!NT_SUCCESS(Status))
636             return Status;
637     }
638 
639     return Status;
640 }
641 
642 NTSTATUS
643 CombinePathsV(
644     OUT PWSTR PathBuffer,
645     IN SIZE_T cchPathSize,
646     IN ULONG NumberOfPathComponents,
647     IN va_list PathComponentsList)
648 {
649     if (cchPathSize < 1)
650         return STATUS_SUCCESS;
651 
652     *PathBuffer = UNICODE_NULL;
653     return ConcatPathsV(PathBuffer, cchPathSize,
654                         NumberOfPathComponents,
655                         PathComponentsList);
656 }
657 
658 NTSTATUS
659 ConcatPaths(
660     IN OUT PWSTR PathBuffer,
661     IN SIZE_T cchPathSize,
662     IN ULONG NumberOfPathComponents,
663     IN /* PCWSTR */ ...)
664 {
665     NTSTATUS Status;
666     va_list PathComponentsList;
667 
668     if (cchPathSize < 1)
669         return STATUS_SUCCESS;
670 
671     va_start(PathComponentsList, NumberOfPathComponents);
672     Status = ConcatPathsV(PathBuffer, cchPathSize,
673                           NumberOfPathComponents,
674                           PathComponentsList);
675     va_end(PathComponentsList);
676 
677     return Status;
678 }
679 
680 NTSTATUS
681 CombinePaths(
682     OUT PWSTR PathBuffer,
683     IN SIZE_T cchPathSize,
684     IN ULONG NumberOfPathComponents,
685     IN /* PCWSTR */ ...)
686 {
687     NTSTATUS Status;
688     va_list PathComponentsList;
689 
690     if (cchPathSize < 1)
691         return STATUS_SUCCESS;
692 
693     *PathBuffer = UNICODE_NULL;
694 
695     va_start(PathComponentsList, NumberOfPathComponents);
696     Status = CombinePathsV(PathBuffer, cchPathSize,
697                            NumberOfPathComponents,
698                            PathComponentsList);
699     va_end(PathComponentsList);
700 
701     return Status;
702 }
703 
704 BOOLEAN
705 DoesPathExist(
706     IN HANDLE RootDirectory OPTIONAL,
707     IN PCWSTR PathName,
708     IN BOOLEAN IsDirectory)
709 {
710     NTSTATUS Status;
711     UNICODE_STRING Name;
712     HANDLE FileHandle;
713     OBJECT_ATTRIBUTES ObjectAttributes;
714     IO_STATUS_BLOCK IoStatusBlock;
715 
716     RtlInitUnicodeString(&Name, PathName);
717     InitializeObjectAttributes(&ObjectAttributes,
718                                &Name,
719                                OBJ_CASE_INSENSITIVE,
720                                RootDirectory,
721                                NULL);
722 
723     Status = NtOpenFile(&FileHandle,
724                         IsDirectory ? (FILE_LIST_DIRECTORY | SYNCHRONIZE)
725                                     :  FILE_GENERIC_READ, // Contains SYNCHRONIZE
726                         &ObjectAttributes,
727                         &IoStatusBlock,
728                         FILE_SHARE_READ | FILE_SHARE_WRITE,
729                         FILE_SYNCHRONOUS_IO_NONALERT |
730                             (IsDirectory ? FILE_DIRECTORY_FILE
731                                          : FILE_NON_DIRECTORY_FILE));
732     if (NT_SUCCESS(Status))
733     {
734         NtClose(FileHandle);
735     }
736     else
737     {
738         DPRINT("Failed to open %s '%wZ', Status 0x%08lx\n",
739                IsDirectory ? "directory" : "file",
740                &Name, Status);
741     }
742 
743     return NT_SUCCESS(Status);
744 }
745 
746 // FIXME: DEPRECATED! HACKish function that needs to be deprecated!
747 BOOLEAN
748 DoesFileExist_2(
749     IN PCWSTR PathName OPTIONAL,
750     IN PCWSTR FileName)
751 {
752     WCHAR FullName[MAX_PATH];
753     CombinePaths(FullName, ARRAYSIZE(FullName), 2, PathName, FileName);
754     return DoesFileExist(NULL, FullName);
755 }
756 
757 /*
758  * The format of NtPath should be:
759  *    \Device\HarddiskXXX\PartitionYYY[\path] ,
760  * where XXX and YYY respectively represent the hard disk and partition numbers,
761  * and [\path] represent an optional path (separated by '\\').
762  *
763  * If a NT path of such a form is correctly parsed, the function returns respectively:
764  * - in pDiskNumber: the hard disk number XXX,
765  * - in pPartNumber: the partition number YYY,
766  * - in PathComponent: pointer value (inside NtPath) to the beginning of \path.
767  *
768  * NOTE: The function does not accept leading whitespace.
769  */
770 BOOLEAN
771 NtPathToDiskPartComponents(
772     IN PCWSTR NtPath,
773     OUT PULONG pDiskNumber,
774     OUT PULONG pPartNumber,
775     OUT PCWSTR* PathComponent OPTIONAL)
776 {
777     ULONG DiskNumber, PartNumber;
778     PCWSTR Path;
779 
780     *pDiskNumber = 0;
781     *pPartNumber = 0;
782     if (PathComponent) *PathComponent = NULL;
783 
784     Path = NtPath;
785 
786     if (_wcsnicmp(Path, L"\\Device\\Harddisk", 16) != 0)
787     {
788         /* The NT path doesn't start with the prefix string, thus it cannot be a hard disk device path */
789         DPRINT1("'%S' : Not a possible hard disk device.\n", NtPath);
790         return FALSE;
791     }
792 
793     Path += 16;
794 
795     /* A number must be present now */
796     if (!iswdigit(*Path))
797     {
798         DPRINT1("'%S' : expected a number! Not a regular hard disk device.\n", Path);
799         return FALSE;
800     }
801     DiskNumber = wcstoul(Path, (PWSTR*)&Path, 10);
802 
803     /* Either NULL termination, or a path separator must be present now */
804     if (*Path && *Path != OBJ_NAME_PATH_SEPARATOR)
805     {
806         DPRINT1("'%S' : expected a path separator!\n", Path);
807         return FALSE;
808     }
809 
810     if (!*Path)
811     {
812         DPRINT1("The path only specified a hard disk (and nothing else, like a partition...), so we stop there.\n");
813         goto Quit;
814     }
815 
816     /* Here, *Path == L'\\' */
817 
818     if (_wcsnicmp(Path, L"\\Partition", 10) != 0)
819     {
820         /* Actually, \Partition is optional so, if we don't have it, we still return success. Or should we? */
821         DPRINT1("'%S' : unexpected format!\n", NtPath);
822         goto Quit;
823     }
824 
825     Path += 10;
826 
827     /* A number must be present now */
828     if (!iswdigit(*Path))
829     {
830         /* If we don't have a number it means this part of path is actually not a partition specifier, so we shouldn't fail either. Or should we? */
831         DPRINT1("'%S' : expected a number!\n", Path);
832         goto Quit;
833     }
834     PartNumber = wcstoul(Path, (PWSTR*)&Path, 10);
835 
836     /* Either NULL termination, or a path separator must be present now */
837     if (*Path && *Path != OBJ_NAME_PATH_SEPARATOR)
838     {
839         /* We shouldn't fail here because it just means this part of path is actually not a partition specifier. Or should we? */
840         DPRINT1("'%S' : expected a path separator!\n", Path);
841         goto Quit;
842     }
843 
844     /* OK, here we really have a partition specifier: return its number */
845     *pPartNumber = PartNumber;
846 
847 Quit:
848     /* Return the disk number */
849     *pDiskNumber = DiskNumber;
850 
851     /* Return the path component also, if the user wants it */
852     if (PathComponent) *PathComponent = Path;
853 
854     return TRUE;
855 }
856 
857 /**
858  * @brief
859  * Opens and maps a file in memory.
860  *
861  * @param[in]   RootDirectory
862  * @param[in]   PathNameToFile
863  * Path to the file, either in absolute form, or relative to the opened
864  * root directory given by the RootDirectory handle.
865  *
866  * @param[out]  FileHandle
867  * An optional pointer to a variable receiving a handle to the opened file.
868  * If NULL, the underlying file handle is closed.
869  *
870  * @param[out]  FileSize
871  * An optional pointer to a variable receiving the size of the opened file.
872  *
873  * @param[out]  SectionHandle
874  * A pointer to a variable receiving a handle to a section mapping the file.
875  *
876  * @param[out]  BaseAddress
877  * A pointer to a variable receiving the address where the file is mapped.
878  *
879  * @param[in]   ReadWriteAccess
880  * A boolean variable specifying whether to map the file for read and write
881  * access (TRUE), or read-only access (FALSE).
882  *
883  * @return
884  * STATUS_SUCCESS in case of success, or a status code in case of error.
885  **/
886 NTSTATUS
887 OpenAndMapFile(
888     _In_opt_ HANDLE RootDirectory,
889     _In_ PCWSTR PathNameToFile,
890     _Out_opt_ PHANDLE FileHandle,
891     _Out_opt_ PULONG FileSize,
892     _Out_ PHANDLE SectionHandle,
893     _Out_ PVOID* BaseAddress,
894     _In_ BOOLEAN ReadWriteAccess)
895 {
896     NTSTATUS Status;
897     UNICODE_STRING FileName;
898     OBJECT_ATTRIBUTES ObjectAttributes;
899     IO_STATUS_BLOCK IoStatusBlock;
900     HANDLE LocalFileHandle;
901 
902     /* Open the file */
903     RtlInitUnicodeString(&FileName, PathNameToFile);
904     InitializeObjectAttributes(&ObjectAttributes,
905                                &FileName,
906                                OBJ_CASE_INSENSITIVE,
907                                RootDirectory,
908                                NULL);
909 
910     if (FileHandle) *FileHandle = NULL;
911     Status = NtOpenFile(&LocalFileHandle,
912                         FILE_GENERIC_READ | // Contains SYNCHRONIZE
913                             (ReadWriteAccess ? FILE_GENERIC_WRITE : 0),
914                         &ObjectAttributes,
915                         &IoStatusBlock,
916                         FILE_SHARE_READ,
917                         FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
918     if (!NT_SUCCESS(Status))
919     {
920         DPRINT1("Failed to open file '%wZ' (Status 0x%08lx)\n", &FileName, Status);
921         return Status;
922     }
923 
924     if (FileSize)
925     {
926         /* Query the file size */
927         FILE_STANDARD_INFORMATION FileInfo;
928         Status = NtQueryInformationFile(LocalFileHandle,
929                                         &IoStatusBlock,
930                                         &FileInfo,
931                                         sizeof(FileInfo),
932                                         FileStandardInformation);
933         if (!NT_SUCCESS(Status))
934         {
935             DPRINT("NtQueryInformationFile() failed (Status 0x%08lx)\n", Status);
936             goto Quit;
937         }
938 
939         if (FileInfo.EndOfFile.HighPart != 0)
940             DPRINT1("WARNING!! The file '%wZ' is too large!\n", &FileName);
941 
942         *FileSize = FileInfo.EndOfFile.LowPart;
943         DPRINT("File size: %lu\n", *FileSize);
944     }
945 
946     /* Map the whole file into memory */
947     Status = MapFile(LocalFileHandle,
948                      SectionHandle,
949                      BaseAddress,
950                      ReadWriteAccess);
951     if (!NT_SUCCESS(Status))
952     {
953         DPRINT1("Failed to map file '%wZ' (Status 0x%08lx)\n", &FileName, Status);
954         goto Quit;
955     }
956 
957 Quit:
958     /* If we succeeded, return the opened file handle if needed.
959      * If we failed or the caller does not need the handle, close it now. */
960     if (NT_SUCCESS(Status) && FileHandle)
961         *FileHandle = LocalFileHandle;
962     else
963         NtClose(LocalFileHandle);
964 
965     return Status;
966 }
967 
968 /**
969  * @brief
970  * Maps an opened file in memory.
971  *
972  * @param[in]   FileHandle
973  * A handle to an opened file to map.
974  *
975  * @param[out]  SectionHandle
976  * A pointer to a variable receiving a handle to a section mapping the file.
977  *
978  * @param[out]  BaseAddress
979  * A pointer to a variable receiving the address where the file is mapped.
980  *
981  * @param[in]   ReadWriteAccess
982  * A boolean variable specifying whether to map the file for read and write
983  * access (TRUE), or read-only access (FALSE).
984  *
985  * @return
986  * STATUS_SUCCESS in case of success, or a status code in case of error.
987  **/
988 NTSTATUS
989 MapFile(
990     _In_ HANDLE FileHandle,
991     _Out_ PHANDLE SectionHandle,
992     _Out_ PVOID* BaseAddress,
993     _In_ BOOLEAN ReadWriteAccess)
994 {
995     NTSTATUS Status;
996     ULONG SectionPageProtection;
997     SIZE_T ViewSize;
998     PVOID ViewBase;
999 
1000     SectionPageProtection = (ReadWriteAccess ? PAGE_READWRITE : PAGE_READONLY);
1001 
1002     /* Create the section */
1003     *SectionHandle = NULL;
1004     Status = NtCreateSection(SectionHandle,
1005                              STANDARD_RIGHTS_REQUIRED | SECTION_QUERY |
1006                              SECTION_MAP_READ |
1007                                 (ReadWriteAccess ? SECTION_MAP_WRITE : 0),
1008                              NULL,
1009                              NULL,
1010                              SectionPageProtection,
1011                              SEC_COMMIT /* | SEC_IMAGE (_NO_EXECUTE) */,
1012                              FileHandle);
1013     if (!NT_SUCCESS(Status))
1014     {
1015         DPRINT1("Failed to create a memory section for file 0x%p (Status 0x%08lx)\n",
1016                 FileHandle, Status);
1017         return Status;
1018     }
1019 
1020     /* Map the section */
1021     ViewSize = 0;
1022     ViewBase = NULL;
1023     Status = NtMapViewOfSection(*SectionHandle,
1024                                 NtCurrentProcess(),
1025                                 &ViewBase,
1026                                 0, 0,
1027                                 NULL,
1028                                 &ViewSize,
1029                                 ViewShare,
1030                                 0,
1031                                 SectionPageProtection);
1032     if (!NT_SUCCESS(Status))
1033     {
1034         DPRINT1("Failed to map a view for file 0x%p (Status 0x%08lx)\n",
1035                 FileHandle, Status);
1036         NtClose(*SectionHandle);
1037         *SectionHandle = NULL;
1038         return Status;
1039     }
1040 
1041     *BaseAddress = ViewBase;
1042     return STATUS_SUCCESS;
1043 }
1044 
1045 /**
1046  * @brief
1047  * Unmaps a mapped file by section.
1048  *
1049  * @param[in]   SectionHandle
1050  * The handle to the section mapping the file.
1051  *
1052  * @param[in]   BaseAddress
1053  * The base address where the file is mapped.
1054  *
1055  * @return
1056  * TRUE if the file was successfully unmapped; FALSE if an error occurred.
1057  **/
1058 BOOLEAN
1059 UnMapFile(
1060     _In_ HANDLE SectionHandle,
1061     _In_ PVOID BaseAddress)
1062 {
1063     NTSTATUS Status;
1064     BOOLEAN Success = TRUE;
1065 
1066     Status = NtUnmapViewOfSection(NtCurrentProcess(), BaseAddress);
1067     if (!NT_SUCCESS(Status))
1068     {
1069         DPRINT1("NtUnmapViewOfSection(0x%p) failed (Status 0x%08lx)\n",
1070                 BaseAddress, Status);
1071         Success = FALSE;
1072     }
1073     Status = NtClose(SectionHandle);
1074     if (!NT_SUCCESS(Status))
1075     {
1076         DPRINT1("NtClose(0x%p) failed (Status 0x%08lx)\n",
1077                 SectionHandle, Status);
1078         Success = FALSE;
1079     }
1080 
1081     return Success;
1082 }
1083 
1084 /* EOF */
1085