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