2 * PROJECT: ReactOS Setup Library
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: File support functions.
5 * COPYRIGHT: Copyright 2017-2018 Hermes Belusca-Maito
8 /* INCLUDES *****************************************************************/
17 // ACHTUNG! HAXX FIXME!!
19 #define _SEH2_LEAVE goto __SEH2_FINALLY__label;
20 #define _SEH2_FINALLY __SEH2_FINALLY__label:
24 /* FUNCTIONS ****************************************************************/
26 // TODO: Move SetupCreateDirectory later...
31 IN BOOLEAN ForceDelete
) // ForceDelete can be used to delete read-only files
34 UNICODE_STRING NtPathU
;
35 OBJECT_ATTRIBUTES ObjectAttributes
;
36 IO_STATUS_BLOCK IoStatusBlock
;
38 FILE_DISPOSITION_INFORMATION FileDispInfo
;
39 BOOLEAN RetryOnce
= FALSE
;
41 /* Open the directory name that was passed in */
42 RtlInitUnicodeString(&NtPathU
, FileName
);
43 InitializeObjectAttributes(&ObjectAttributes
,
49 Retry
: // We go back there once if RetryOnce == TRUE
50 Status
= NtOpenFile(&FileHandle
,
51 DELETE
| FILE_READ_ATTRIBUTES
|
52 (RetryOnce
? FILE_WRITE_ATTRIBUTES
: 0),
55 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
56 FILE_NON_DIRECTORY_FILE
| FILE_OPEN_FOR_BACKUP_INTENT
);
57 if (!NT_SUCCESS(Status
))
59 DPRINT1("NtOpenFile failed with Status 0x%08lx\n", Status
);
65 FILE_BASIC_INFORMATION FileInformation
;
67 Status
= NtQueryInformationFile(FileHandle
,
70 sizeof(FILE_BASIC_INFORMATION
),
71 FileBasicInformation
);
72 if (!NT_SUCCESS(Status
))
74 DPRINT1("NtQueryInformationFile failed with Status 0x%08lx\n", Status
);
79 FileInformation
.FileAttributes
= FILE_ATTRIBUTE_NORMAL
;
80 Status
= NtSetInformationFile(FileHandle
,
83 sizeof(FILE_BASIC_INFORMATION
),
84 FileBasicInformation
);
86 if (!NT_SUCCESS(Status
))
88 DPRINT1("NtSetInformationFile failed with Status 0x%08lx\n", Status
);
93 /* Ask for the file to be deleted */
94 FileDispInfo
.DeleteFile
= TRUE
;
95 Status
= NtSetInformationFile(FileHandle
,
98 sizeof(FILE_DISPOSITION_INFORMATION
),
99 FileDispositionInformation
);
102 if (!NT_SUCCESS(Status
))
103 DPRINT1("Deletion of file '%S' failed, Status 0x%08lx\n", FileName
, Status
);
105 // FIXME: Check the precise value of Status!
106 if (!NT_SUCCESS(Status
) && ForceDelete
&& !RetryOnce
)
113 /* Return result to the caller */
119 IN PCWSTR SourceFileName
,
120 IN PCWSTR DestinationFileName
,
121 IN BOOLEAN FailIfExists
)
124 UNICODE_STRING FileName
;
125 OBJECT_ATTRIBUTES ObjectAttributes
;
126 HANDLE FileHandleSource
;
127 HANDLE FileHandleDest
;
128 IO_STATUS_BLOCK IoStatusBlock
;
129 FILE_STANDARD_INFORMATION FileStandard
;
130 FILE_BASIC_INFORMATION FileBasic
;
132 HANDLE SourceFileSection
;
133 PVOID SourceFileMap
= NULL
;
134 SIZE_T SourceSectionSize
= 0;
135 LARGE_INTEGER ByteOffset
;
137 RtlInitUnicodeString(&FileName
, SourceFileName
);
138 InitializeObjectAttributes(&ObjectAttributes
,
140 OBJ_CASE_INSENSITIVE
,
144 Status
= NtOpenFile(&FileHandleSource
,
149 FILE_SEQUENTIAL_ONLY
);
150 if (!NT_SUCCESS(Status
))
152 DPRINT1("NtOpenFile failed: %x, %wZ\n", Status
, &FileName
);
156 Status
= NtQueryInformationFile(FileHandleSource
,
159 sizeof(FILE_STANDARD_INFORMATION
),
160 FileStandardInformation
);
161 if (!NT_SUCCESS(Status
))
163 DPRINT1("NtQueryInformationFile failed: %x\n", Status
);
167 Status
= NtQueryInformationFile(FileHandleSource
,
170 sizeof(FILE_BASIC_INFORMATION
),
171 FileBasicInformation
);
172 if (!NT_SUCCESS(Status
))
174 DPRINT1("NtQueryInformationFile failed: %x\n", Status
);
178 Status
= NtCreateSection(&SourceFileSection
,
185 if (!NT_SUCCESS(Status
))
187 DPRINT1("NtCreateSection failed: %x, %S\n", Status
, SourceFileName
);
191 Status
= NtMapViewOfSection(SourceFileSection
,
201 if (!NT_SUCCESS(Status
))
203 DPRINT1("NtMapViewOfSection failed: %x, %S\n", Status
, SourceFileName
);
207 RtlInitUnicodeString(&FileName
, DestinationFileName
);
208 InitializeObjectAttributes(&ObjectAttributes
,
210 OBJ_CASE_INSENSITIVE
,
214 Status
= NtCreateFile(&FileHandleDest
,
215 GENERIC_WRITE
| SYNCHRONIZE
,
219 FileBasic
.FileAttributes
, // FILE_ATTRIBUTE_NORMAL,
221 FailIfExists
? FILE_CREATE
: FILE_OVERWRITE_IF
,
222 FILE_NO_INTERMEDIATE_BUFFERING
|
223 FILE_SEQUENTIAL_ONLY
|
224 FILE_SYNCHRONOUS_IO_NONALERT
,
227 if (!NT_SUCCESS(Status
))
230 * Open may have failed because the file to overwrite
231 * is in readonly mode.
233 if (Status
== STATUS_ACCESS_DENIED
)
235 FILE_BASIC_INFORMATION FileBasicInfo
;
237 /* Reattempt to open it with limited access */
238 Status
= NtCreateFile(&FileHandleDest
,
239 FILE_WRITE_ATTRIBUTES
| SYNCHRONIZE
,
243 FILE_ATTRIBUTE_NORMAL
,
246 FILE_NO_INTERMEDIATE_BUFFERING
|
247 FILE_SEQUENTIAL_ONLY
|
248 FILE_SYNCHRONOUS_IO_NONALERT
,
251 /* Fail for real if we cannot open it that way */
252 if (!NT_SUCCESS(Status
))
254 DPRINT1("NtCreateFile failed: %x, %wZ\n", Status
, &FileName
);
258 /* Zero our basic info, just to set attributes */
259 RtlZeroMemory(&FileBasicInfo
, sizeof(FileBasicInfo
));
260 /* Reset attributes to normal, no read-only */
261 FileBasicInfo
.FileAttributes
= FILE_ATTRIBUTE_NORMAL
;
263 * We basically don't care about whether it succeed:
264 * if it didn't, later open will fail.
266 NtSetInformationFile(FileHandleDest
, &IoStatusBlock
, &FileBasicInfo
,
267 sizeof(FileBasicInfo
), FileBasicInformation
);
270 NtClose(FileHandleDest
);
272 /* And re-attempt overwrite */
273 Status
= NtCreateFile(&FileHandleDest
,
274 GENERIC_WRITE
| SYNCHRONIZE
,
278 FILE_ATTRIBUTE_NORMAL
,
281 FILE_NO_INTERMEDIATE_BUFFERING
|
282 FILE_SEQUENTIAL_ONLY
|
283 FILE_SYNCHRONOUS_IO_NONALERT
,
289 if (!NT_SUCCESS(Status
))
291 DPRINT1("NtCreateFile failed: %x, %wZ\n", Status
, &FileName
);
296 RegionSize
= (ULONG
)PAGE_ROUND_UP(FileStandard
.EndOfFile
.u
.LowPart
);
297 IoStatusBlock
.Status
= 0;
298 ByteOffset
.QuadPart
= 0ULL;
299 Status
= NtWriteFile(FileHandleDest
,
308 if (!NT_SUCCESS(Status
))
310 DPRINT1("NtWriteFile failed: %x:%x, iosb: %p src: %p, size: %x\n",
311 Status
, IoStatusBlock
.Status
, &IoStatusBlock
, SourceFileMap
, RegionSize
);
315 /* Copy file date/time from source file */
316 Status
= NtSetInformationFile(FileHandleDest
,
319 sizeof(FILE_BASIC_INFORMATION
),
320 FileBasicInformation
);
321 if (!NT_SUCCESS(Status
))
323 DPRINT1("NtSetInformationFile failed: %x\n", Status
);
327 /* Shorten the file back to its real size after completing the write */
328 Status
= NtSetInformationFile(FileHandleDest
,
330 &FileStandard
.EndOfFile
,
331 sizeof(FILE_END_OF_FILE_INFORMATION
),
332 FileEndOfFileInformation
);
333 if (!NT_SUCCESS(Status
))
335 DPRINT1("NtSetInformationFile failed: %x\n", Status
);
339 NtClose(FileHandleDest
);
342 NtUnmapViewOfSection(NtCurrentProcess(), SourceFileMap
);
345 NtClose(SourceFileSection
);
348 NtClose(FileHandleSource
);
355 * Synchronized with its kernel32 counterpart, but we don't manage reparse points here.
359 IN PCWSTR ExistingFileName
,
360 IN PCWSTR NewFileName
,
364 IO_STATUS_BLOCK IoStatusBlock
;
365 OBJECT_ATTRIBUTES ObjectAttributes
;
366 PFILE_RENAME_INFORMATION RenameInfo
;
367 UNICODE_STRING NewPathU
, ExistingPathU
;
368 HANDLE SourceHandle
= NULL
;
369 BOOLEAN ReplaceIfExists
;
371 RtlInitUnicodeString(&ExistingPathU
, ExistingFileName
);
372 RtlInitUnicodeString(&NewPathU
, NewFileName
);
376 ReplaceIfExists
= !!(Flags
& MOVEFILE_REPLACE_EXISTING
);
378 /* Unless we manage a proper opening, we'll attempt to reopen without reparse support */
379 InitializeObjectAttributes(&ObjectAttributes
,
381 OBJ_CASE_INSENSITIVE
,
384 /* Attempt to open source file */
385 Status
= NtOpenFile(&SourceHandle
,
386 FILE_READ_ATTRIBUTES
| DELETE
| SYNCHRONIZE
,
389 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
390 FILE_OPEN_FOR_BACKUP_INTENT
| ((Flags
& MOVEFILE_WRITE_THROUGH
) ? FILE_WRITE_THROUGH
: 0));
391 if (!NT_SUCCESS(Status
))
393 if (Status
!= STATUS_INVALID_PARAMETER
)
399 /* At that point, we MUST have a source handle */
400 ASSERT(SourceHandle
);
402 /* Allocate renaming buffer and fill it */
403 RenameInfo
= RtlAllocateHeap(RtlGetProcessHeap(), 0, NewPathU
.Length
+ sizeof(FILE_RENAME_INFORMATION
));
404 if (RenameInfo
== NULL
)
406 Status
= STATUS_NO_MEMORY
;
410 RtlCopyMemory(&RenameInfo
->FileName
, NewPathU
.Buffer
, NewPathU
.Length
);
411 RenameInfo
->ReplaceIfExists
= ReplaceIfExists
;
412 RenameInfo
->RootDirectory
= NULL
;
413 RenameInfo
->FileNameLength
= NewPathU
.Length
;
415 /* Attempt to rename the file */
416 Status
= NtSetInformationFile(SourceHandle
,
419 NewPathU
.Length
+ sizeof(FILE_RENAME_INFORMATION
),
420 FileRenameInformation
);
421 RtlFreeHeap(RtlGetProcessHeap(), 0, RenameInfo
);
422 if (NT_SUCCESS(Status
))
424 /* If it succeeded, all fine, quit */
428 * If we failed for any other reason than not the same device, fail.
429 * If we failed because of different devices, only allow renaming
430 * if user allowed copy.
432 if (Status
!= STATUS_NOT_SAME_DEVICE
|| !(Flags
& MOVEFILE_COPY_ALLOWED
))
434 /* ReactOS hack! To be removed once all FSD have proper renaming support
435 * Just leave status to error and leave
437 if (Status
== STATUS_NOT_IMPLEMENTED
)
439 DPRINT1("Forcing copy, renaming not supported by FSD\n");
447 /* Close the source file */
448 NtClose(SourceHandle
);
451 /* Perform the file copy */
452 Status
= SetupCopyFile(ExistingFileName
,
456 /* If it succeeded, delete the source file */
457 if (NT_SUCCESS(Status
))
459 /* Force-delete files even if read-only */
460 SetupDeleteFile(ExistingFileName
, TRUE
);
466 NtClose(SourceHandle
);
475 IN OUT PWSTR PathBuffer
,
476 IN SIZE_T cchPathSize
,
477 IN ULONG NumberOfPathComponents
,
478 IN
va_list PathComponentsList
)
480 NTSTATUS Status
= STATUS_SUCCESS
;
482 PCWSTR PathComponent
;
485 return STATUS_SUCCESS
;
487 while (NumberOfPathComponents
--)
489 PathComponent
= va_arg(PathComponentsList
, PCWSTR
);
493 cchPathLen
= min(cchPathSize
, wcslen(PathBuffer
));
494 if (cchPathLen
>= cchPathSize
)
495 return STATUS_BUFFER_OVERFLOW
;
497 if (PathComponent
[0] != OBJ_NAME_PATH_SEPARATOR
&&
498 cchPathLen
> 0 && PathBuffer
[cchPathLen
-1] != OBJ_NAME_PATH_SEPARATOR
)
500 /* PathComponent does not start with '\' and PathBuffer does not end with '\' */
501 Status
= RtlStringCchCatW(PathBuffer
, cchPathSize
, L
"\\");
502 if (!NT_SUCCESS(Status
))
505 else if (PathComponent
[0] == OBJ_NAME_PATH_SEPARATOR
&&
506 cchPathLen
> 0 && PathBuffer
[cchPathLen
-1] == OBJ_NAME_PATH_SEPARATOR
)
508 /* PathComponent starts with '\' and PathBuffer ends with '\' */
509 while (*PathComponent
== OBJ_NAME_PATH_SEPARATOR
)
510 ++PathComponent
; // Skip any backslash
512 Status
= RtlStringCchCatW(PathBuffer
, cchPathSize
, PathComponent
);
513 if (!NT_SUCCESS(Status
))
522 OUT PWSTR PathBuffer
,
523 IN SIZE_T cchPathSize
,
524 IN ULONG NumberOfPathComponents
,
525 IN
va_list PathComponentsList
)
528 return STATUS_SUCCESS
;
530 *PathBuffer
= UNICODE_NULL
;
531 return ConcatPathsV(PathBuffer
, cchPathSize
,
532 NumberOfPathComponents
,
538 IN OUT PWSTR PathBuffer
,
539 IN SIZE_T cchPathSize
,
540 IN ULONG NumberOfPathComponents
,
544 va_list PathComponentsList
;
547 return STATUS_SUCCESS
;
549 va_start(PathComponentsList
, NumberOfPathComponents
);
550 Status
= ConcatPathsV(PathBuffer
, cchPathSize
,
551 NumberOfPathComponents
,
553 va_end(PathComponentsList
);
560 OUT PWSTR PathBuffer
,
561 IN SIZE_T cchPathSize
,
562 IN ULONG NumberOfPathComponents
,
566 va_list PathComponentsList
;
569 return STATUS_SUCCESS
;
571 *PathBuffer
= UNICODE_NULL
;
573 va_start(PathComponentsList
, NumberOfPathComponents
);
574 Status
= CombinePathsV(PathBuffer
, cchPathSize
,
575 NumberOfPathComponents
,
577 va_end(PathComponentsList
);
584 IN HANDLE RootDirectory OPTIONAL
,
586 IN BOOLEAN IsDirectory
)
591 OBJECT_ATTRIBUTES ObjectAttributes
;
592 IO_STATUS_BLOCK IoStatusBlock
;
594 RtlInitUnicodeString(&Name
, PathName
);
595 InitializeObjectAttributes(&ObjectAttributes
,
597 OBJ_CASE_INSENSITIVE
,
601 Status
= NtOpenFile(&FileHandle
,
602 IsDirectory
? (FILE_LIST_DIRECTORY
| SYNCHRONIZE
)
603 : FILE_GENERIC_READ
, // Contains SYNCHRONIZE
606 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
607 FILE_SYNCHRONOUS_IO_NONALERT
|
608 (IsDirectory
? FILE_DIRECTORY_FILE
609 : FILE_NON_DIRECTORY_FILE
));
610 if (NT_SUCCESS(Status
))
616 DPRINT("Failed to open %s '%wZ', Status 0x%08lx\n",
617 IsDirectory
? "directory" : "file",
621 return NT_SUCCESS(Status
);
624 // FIXME: DEPRECATED! HACKish function that needs to be deprecated!
627 IN PCWSTR PathName OPTIONAL
,
630 WCHAR FullName
[MAX_PATH
];
631 CombinePaths(FullName
, ARRAYSIZE(FullName
), 2, PathName
, FileName
);
632 return DoesFileExist(NULL
, FullName
);
636 * The format of NtPath should be:
637 * \Device\HarddiskXXX\PartitionYYY[\path] ,
638 * where XXX and YYY respectively represent the hard disk and partition numbers,
639 * and [\path] represent an optional path (separated by '\\').
641 * If a NT path of such a form is correctly parsed, the function returns respectively:
642 * - in pDiskNumber: the hard disk number XXX,
643 * - in pPartNumber: the partition number YYY,
644 * - in PathComponent: pointer value (inside NtPath) to the beginning of \path.
646 * NOTE: The function does not accept leading whitespace.
649 NtPathToDiskPartComponents(
651 OUT PULONG pDiskNumber
,
652 OUT PULONG pPartNumber
,
653 OUT PCWSTR
* PathComponent OPTIONAL
)
655 ULONG DiskNumber
, PartNumber
;
660 if (PathComponent
) *PathComponent
= NULL
;
664 if (_wcsnicmp(Path
, L
"\\Device\\Harddisk", 16) != 0)
666 /* The NT path doesn't start with the prefix string, thus it cannot be a hard disk device path */
667 DPRINT1("'%S' : Not a possible hard disk device.\n", NtPath
);
673 /* A number must be present now */
674 if (!iswdigit(*Path
))
676 DPRINT1("'%S' : expected a number! Not a regular hard disk device.\n", Path
);
679 DiskNumber
= wcstoul(Path
, (PWSTR
*)&Path
, 10);
681 /* Either NULL termination, or a path separator must be present now */
682 if (*Path
&& *Path
!= OBJ_NAME_PATH_SEPARATOR
)
684 DPRINT1("'%S' : expected a path separator!\n", Path
);
690 DPRINT1("The path only specified a hard disk (and nothing else, like a partition...), so we stop there.\n");
694 /* Here, *Path == L'\\' */
696 if (_wcsnicmp(Path
, L
"\\Partition", 10) != 0)
698 /* Actually, \Partition is optional so, if we don't have it, we still return success. Or should we? */
699 DPRINT1("'%S' : unexpected format!\n", NtPath
);
705 /* A number must be present now */
706 if (!iswdigit(*Path
))
708 /* 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? */
709 DPRINT1("'%S' : expected a number!\n", Path
);
712 PartNumber
= wcstoul(Path
, (PWSTR
*)&Path
, 10);
714 /* Either NULL termination, or a path separator must be present now */
715 if (*Path
&& *Path
!= OBJ_NAME_PATH_SEPARATOR
)
717 /* We shouldn't fail here because it just means this part of path is actually not a partition specifier. Or should we? */
718 DPRINT1("'%S' : expected a path separator!\n", Path
);
722 /* OK, here we really have a partition specifier: return its number */
723 *pPartNumber
= PartNumber
;
726 /* Return the disk number */
727 *pDiskNumber
= DiskNumber
;
729 /* Return the path component also, if the user wants it */
730 if (PathComponent
) *PathComponent
= Path
;
737 IN HANDLE RootDirectory OPTIONAL
,
738 IN PCWSTR PathNameToFile
,
739 OUT PHANDLE FileHandle
, // IN OUT PHANDLE OPTIONAL
740 OUT PHANDLE SectionHandle
,
741 OUT PVOID
* BaseAddress
,
742 OUT PULONG FileSize OPTIONAL
,
743 IN BOOLEAN ReadWriteAccess
)
746 UNICODE_STRING FileName
;
747 OBJECT_ATTRIBUTES ObjectAttributes
;
748 IO_STATUS_BLOCK IoStatusBlock
;
749 ULONG SectionPageProtection
;
755 RtlInitUnicodeString(&FileName
, PathNameToFile
);
756 InitializeObjectAttributes(&ObjectAttributes
,
758 OBJ_CASE_INSENSITIVE
,
763 *SectionHandle
= NULL
;
765 Status
= NtOpenFile(FileHandle
,
766 FILE_GENERIC_READ
| // Contains SYNCHRONIZE
767 (ReadWriteAccess
? FILE_GENERIC_WRITE
: 0),
771 FILE_SYNCHRONOUS_IO_NONALERT
| FILE_NON_DIRECTORY_FILE
);
772 if (!NT_SUCCESS(Status
))
774 DPRINT1("Failed to open file '%wZ', Status 0x%08lx\n", &FileName
, Status
);
780 /* Query the file size */
781 FILE_STANDARD_INFORMATION FileInfo
;
782 Status
= NtQueryInformationFile(*FileHandle
,
786 FileStandardInformation
);
787 if (!NT_SUCCESS(Status
))
789 DPRINT("NtQueryInformationFile() failed (Status %lx)\n", Status
);
790 NtClose(*FileHandle
);
795 if (FileInfo
.EndOfFile
.HighPart
!= 0)
796 DPRINT1("WARNING!! The file '%wZ' is too large!\n", &FileName
);
798 *FileSize
= FileInfo
.EndOfFile
.LowPart
;
800 DPRINT("File size: %lu\n", *FileSize
);
803 /* Map the file in memory */
805 SectionPageProtection
= (ReadWriteAccess
? PAGE_READWRITE
: PAGE_READONLY
);
807 /* Create the section */
808 Status
= NtCreateSection(SectionHandle
,
809 STANDARD_RIGHTS_REQUIRED
| SECTION_QUERY
|
811 (ReadWriteAccess
? SECTION_MAP_WRITE
: 0),
814 SectionPageProtection
,
815 SEC_COMMIT
/* | SEC_IMAGE (_NO_EXECUTE) */,
817 if (!NT_SUCCESS(Status
))
819 DPRINT1("Failed to create a memory section for file '%wZ', Status 0x%08lx\n", &FileName
, Status
);
820 NtClose(*FileHandle
);
825 /* Map the section */
828 Status
= NtMapViewOfSection(*SectionHandle
,
836 SectionPageProtection
);
837 if (!NT_SUCCESS(Status
))
839 DPRINT1("Failed to map a view for file '%wZ', Status 0x%08lx\n", &FileName
, Status
);
840 NtClose(*SectionHandle
);
841 *SectionHandle
= NULL
;
842 NtClose(*FileHandle
);
847 *BaseAddress
= ViewBase
;
848 return STATUS_SUCCESS
;
853 IN HANDLE SectionHandle
,
854 IN PVOID BaseAddress
)
857 BOOLEAN Success
= TRUE
;
859 Status
= NtUnmapViewOfSection(NtCurrentProcess(), BaseAddress
);
860 if (!NT_SUCCESS(Status
))
862 DPRINT1("UnMapFile: NtUnmapViewOfSection(0x%p) failed with Status 0x%08lx\n",
863 BaseAddress
, Status
);
866 Status
= NtClose(SectionHandle
);
867 if (!NT_SUCCESS(Status
))
869 DPRINT1("UnMapFile: NtClose(0x%p) failed with Status 0x%08lx\n",
870 SectionHandle
, Status
);