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