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