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