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
);
139 InitializeObjectAttributes(&ObjectAttributes
,
141 OBJ_CASE_INSENSITIVE
,
145 Status
= NtOpenFile(&FileHandleSource
,
150 FILE_SEQUENTIAL_ONLY
);
151 if (!NT_SUCCESS(Status
))
153 DPRINT1("NtOpenFile failed: %x, %wZ\n", Status
, &FileName
);
157 Status
= NtQueryInformationFile(FileHandleSource
,
160 sizeof(FILE_STANDARD_INFORMATION
),
161 FileStandardInformation
);
162 if (!NT_SUCCESS(Status
))
164 DPRINT1("NtQueryInformationFile failed: %x\n", Status
);
168 Status
= NtQueryInformationFile(FileHandleSource
,
171 sizeof(FILE_BASIC_INFORMATION
),
172 FileBasicInformation
);
173 if (!NT_SUCCESS(Status
))
175 DPRINT1("NtQueryInformationFile failed: %x\n", Status
);
179 Status
= NtCreateSection(&SourceFileSection
,
186 if (!NT_SUCCESS(Status
))
188 DPRINT1("NtCreateSection failed: %x, %S\n", Status
, SourceFileName
);
192 Status
= NtMapViewOfSection(SourceFileSection
,
202 if (!NT_SUCCESS(Status
))
204 DPRINT1("NtMapViewOfSection failed: %x, %S\n", Status
, SourceFileName
);
208 RtlInitUnicodeString(&FileName
, DestinationFileName
);
210 InitializeObjectAttributes(&ObjectAttributes
,
212 OBJ_CASE_INSENSITIVE
,
216 Status
= NtCreateFile(&FileHandleDest
,
217 GENERIC_WRITE
| SYNCHRONIZE
,
221 FileBasic
.FileAttributes
, // FILE_ATTRIBUTE_NORMAL,
223 FailIfExists
? FILE_CREATE
: FILE_OVERWRITE_IF
,
224 FILE_NO_INTERMEDIATE_BUFFERING
|
225 FILE_SEQUENTIAL_ONLY
|
226 FILE_SYNCHRONOUS_IO_NONALERT
,
229 if (!NT_SUCCESS(Status
))
232 * Open may have failed because the file to overwrite
233 * is in readonly mode.
235 if (Status
== STATUS_ACCESS_DENIED
)
237 FILE_BASIC_INFORMATION FileBasicInfo
;
239 /* Reattempt to open it with limited access */
240 Status
= NtCreateFile(&FileHandleDest
,
241 FILE_WRITE_ATTRIBUTES
| SYNCHRONIZE
,
245 FILE_ATTRIBUTE_NORMAL
,
248 FILE_NO_INTERMEDIATE_BUFFERING
|
249 FILE_SEQUENTIAL_ONLY
|
250 FILE_SYNCHRONOUS_IO_NONALERT
,
253 /* Fail for real if we cannot open it that way */
254 if (!NT_SUCCESS(Status
))
256 DPRINT1("NtCreateFile failed: %x, %wZ\n", Status
, &FileName
);
260 /* Zero our basic info, just to set attributes */
261 RtlZeroMemory(&FileBasicInfo
, sizeof(FileBasicInfo
));
262 /* Reset attributes to normal, no read-only */
263 FileBasicInfo
.FileAttributes
= FILE_ATTRIBUTE_NORMAL
;
265 * We basically don't care about whether it succeed:
266 * if it didn't, later open will fail.
268 NtSetInformationFile(FileHandleDest
, &IoStatusBlock
, &FileBasicInfo
,
269 sizeof(FileBasicInfo
), FileBasicInformation
);
272 NtClose(FileHandleDest
);
274 /* And re-attempt overwrite */
275 Status
= NtCreateFile(&FileHandleDest
,
276 GENERIC_WRITE
| SYNCHRONIZE
,
280 FILE_ATTRIBUTE_NORMAL
,
283 FILE_NO_INTERMEDIATE_BUFFERING
|
284 FILE_SEQUENTIAL_ONLY
|
285 FILE_SYNCHRONOUS_IO_NONALERT
,
291 if (!NT_SUCCESS(Status
))
293 DPRINT1("NtCreateFile failed: %x, %wZ\n", Status
, &FileName
);
298 RegionSize
= (ULONG
)PAGE_ROUND_UP(FileStandard
.EndOfFile
.u
.LowPart
);
299 IoStatusBlock
.Status
= 0;
300 ByteOffset
.QuadPart
= 0ULL;
301 Status
= NtWriteFile(FileHandleDest
,
310 if (!NT_SUCCESS(Status
))
312 DPRINT1("NtWriteFile failed: %x:%x, iosb: %p src: %p, size: %x\n",
313 Status
, IoStatusBlock
.Status
, &IoStatusBlock
, SourceFileMap
, RegionSize
);
317 /* Copy file date/time from source file */
318 Status
= NtSetInformationFile(FileHandleDest
,
321 sizeof(FILE_BASIC_INFORMATION
),
322 FileBasicInformation
);
323 if (!NT_SUCCESS(Status
))
325 DPRINT1("NtSetInformationFile failed: %x\n", Status
);
329 /* Shorten the file back to its real size after completing the write */
330 Status
= NtSetInformationFile(FileHandleDest
,
332 &FileStandard
.EndOfFile
,
333 sizeof(FILE_END_OF_FILE_INFORMATION
),
334 FileEndOfFileInformation
);
335 if (!NT_SUCCESS(Status
))
337 DPRINT1("NtSetInformationFile failed: %x\n", Status
);
341 NtClose(FileHandleDest
);
344 NtUnmapViewOfSection(NtCurrentProcess(), SourceFileMap
);
347 NtClose(SourceFileSection
);
350 NtClose(FileHandleSource
);
357 * Synchronized with its kernel32 counterpart, but we don't manage reparse points here.
361 IN PCWSTR ExistingFileName
,
362 IN PCWSTR NewFileName
,
366 IO_STATUS_BLOCK IoStatusBlock
;
367 OBJECT_ATTRIBUTES ObjectAttributes
;
368 PFILE_RENAME_INFORMATION RenameInfo
;
369 UNICODE_STRING NewPathU
, ExistingPathU
;
370 HANDLE SourceHandle
= NULL
;
371 BOOLEAN ReplaceIfExists
;
373 RtlInitUnicodeString(&ExistingPathU
, ExistingFileName
);
374 RtlInitUnicodeString(&NewPathU
, NewFileName
);
378 ReplaceIfExists
= !!(Flags
& MOVEFILE_REPLACE_EXISTING
);
380 /* Unless we manage a proper opening, we'll attempt to reopen without reparse support */
381 InitializeObjectAttributes(&ObjectAttributes
,
383 OBJ_CASE_INSENSITIVE
,
386 /* Attempt to open source file */
387 Status
= NtOpenFile(&SourceHandle
,
388 FILE_READ_ATTRIBUTES
| DELETE
| SYNCHRONIZE
,
391 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
392 FILE_OPEN_FOR_BACKUP_INTENT
| ((Flags
& MOVEFILE_WRITE_THROUGH
) ? FILE_WRITE_THROUGH
: 0));
393 if (!NT_SUCCESS(Status
))
395 if (Status
!= STATUS_INVALID_PARAMETER
)
401 /* At that point, we MUST have a source handle */
402 ASSERT(SourceHandle
);
404 /* Allocate renaming buffer and fill it */
405 RenameInfo
= RtlAllocateHeap(RtlGetProcessHeap(), 0, NewPathU
.Length
+ sizeof(FILE_RENAME_INFORMATION
));
406 if (RenameInfo
== NULL
)
408 Status
= STATUS_NO_MEMORY
;
412 RtlCopyMemory(&RenameInfo
->FileName
, NewPathU
.Buffer
, NewPathU
.Length
);
413 RenameInfo
->ReplaceIfExists
= ReplaceIfExists
;
414 RenameInfo
->RootDirectory
= NULL
;
415 RenameInfo
->FileNameLength
= NewPathU
.Length
;
417 /* Attempt to rename the file */
418 Status
= NtSetInformationFile(SourceHandle
,
421 NewPathU
.Length
+ sizeof(FILE_RENAME_INFORMATION
),
422 FileRenameInformation
);
423 RtlFreeHeap(RtlGetProcessHeap(), 0, RenameInfo
);
424 if (NT_SUCCESS(Status
))
426 /* If it succeeded, all fine, quit */
430 * If we failed for any other reason than not the same device, fail.
431 * If we failed because of different devices, only allow renaming
432 * if user allowed copy.
434 if (Status
!= STATUS_NOT_SAME_DEVICE
|| !(Flags
& MOVEFILE_COPY_ALLOWED
))
436 /* ReactOS hack! To be removed once all FSD have proper renaming support
437 * Just leave status to error and leave
439 if (Status
== STATUS_NOT_IMPLEMENTED
)
441 DPRINT1("Forcing copy, renaming not supported by FSD\n");
449 /* Close the source file */
450 NtClose(SourceHandle
);
453 /* Perform the file copy */
454 Status
= SetupCopyFile(ExistingFileName
,
458 /* If it succeeded, delete the source file */
459 if (NT_SUCCESS(Status
))
461 /* Force-delete files even if read-only */
462 SetupDeleteFile(ExistingFileName
, TRUE
);
468 NtClose(SourceHandle
);
477 IN OUT PWSTR PathBuffer
,
478 IN SIZE_T cchPathSize
,
479 IN ULONG NumberOfPathComponents
,
480 IN
va_list PathComponentsList
)
482 NTSTATUS Status
= STATUS_SUCCESS
;
484 PCWSTR PathComponent
;
487 return STATUS_SUCCESS
;
489 while (NumberOfPathComponents
--)
491 PathComponent
= va_arg(PathComponentsList
, PCWSTR
);
495 cchPathLen
= min(cchPathSize
, wcslen(PathBuffer
));
496 if (cchPathLen
>= cchPathSize
)
497 return STATUS_BUFFER_OVERFLOW
;
499 if (PathComponent
[0] != OBJ_NAME_PATH_SEPARATOR
&&
500 cchPathLen
> 0 && PathBuffer
[cchPathLen
-1] != OBJ_NAME_PATH_SEPARATOR
)
502 /* PathComponent does not start with '\' and PathBuffer does not end with '\' */
503 Status
= RtlStringCchCatW(PathBuffer
, cchPathSize
, L
"\\");
504 if (!NT_SUCCESS(Status
))
507 else if (PathComponent
[0] == OBJ_NAME_PATH_SEPARATOR
&&
508 cchPathLen
> 0 && PathBuffer
[cchPathLen
-1] == OBJ_NAME_PATH_SEPARATOR
)
510 /* PathComponent starts with '\' and PathBuffer ends with '\' */
511 while (*PathComponent
== OBJ_NAME_PATH_SEPARATOR
)
512 ++PathComponent
; // Skip any backslash
514 Status
= RtlStringCchCatW(PathBuffer
, cchPathSize
, PathComponent
);
515 if (!NT_SUCCESS(Status
))
524 OUT PWSTR PathBuffer
,
525 IN SIZE_T cchPathSize
,
526 IN ULONG NumberOfPathComponents
,
527 IN
va_list PathComponentsList
)
530 return STATUS_SUCCESS
;
532 *PathBuffer
= UNICODE_NULL
;
533 return ConcatPathsV(PathBuffer
, cchPathSize
,
534 NumberOfPathComponents
,
540 IN OUT PWSTR PathBuffer
,
541 IN SIZE_T cchPathSize
,
542 IN ULONG NumberOfPathComponents
,
546 va_list PathComponentsList
;
549 return STATUS_SUCCESS
;
551 va_start(PathComponentsList
, NumberOfPathComponents
);
552 Status
= ConcatPathsV(PathBuffer
, cchPathSize
,
553 NumberOfPathComponents
,
555 va_end(PathComponentsList
);
562 OUT PWSTR PathBuffer
,
563 IN SIZE_T cchPathSize
,
564 IN ULONG NumberOfPathComponents
,
568 va_list PathComponentsList
;
571 return STATUS_SUCCESS
;
573 *PathBuffer
= UNICODE_NULL
;
575 va_start(PathComponentsList
, NumberOfPathComponents
);
576 Status
= CombinePathsV(PathBuffer
, cchPathSize
,
577 NumberOfPathComponents
,
579 va_end(PathComponentsList
);
586 IN HANDLE RootDirectory OPTIONAL
,
588 IN BOOLEAN IsDirectory
)
593 OBJECT_ATTRIBUTES ObjectAttributes
;
594 IO_STATUS_BLOCK IoStatusBlock
;
596 RtlInitUnicodeString(&Name
, PathName
);
598 InitializeObjectAttributes(&ObjectAttributes
,
600 OBJ_CASE_INSENSITIVE
,
604 Status
= NtOpenFile(&FileHandle
,
605 IsDirectory
? (FILE_LIST_DIRECTORY
| SYNCHRONIZE
)
606 : FILE_GENERIC_READ
, // Contains SYNCHRONIZE
609 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
610 FILE_SYNCHRONOUS_IO_NONALERT
|
611 (IsDirectory
? FILE_DIRECTORY_FILE
612 : FILE_NON_DIRECTORY_FILE
));
613 if (NT_SUCCESS(Status
))
619 DPRINT("Failed to open %s '%wZ', Status 0x%08lx\n",
620 IsDirectory
? "directory" : "file",
624 return NT_SUCCESS(Status
);
627 // FIXME: DEPRECATED! HACKish function that needs to be deprecated!
630 IN PCWSTR PathName OPTIONAL
,
633 WCHAR FullName
[MAX_PATH
];
634 CombinePaths(FullName
, ARRAYSIZE(FullName
), 2, PathName
, FileName
);
635 return DoesFileExist(NULL
, FullName
);
639 * The format of NtPath should be:
640 * \Device\HarddiskXXX\PartitionYYY[\path] ,
641 * where XXX and YYY respectively represent the hard disk and partition numbers,
642 * and [\path] represent an optional path (separated by '\\').
644 * If a NT path of such a form is correctly parsed, the function returns respectively:
645 * - in pDiskNumber: the hard disk number XXX,
646 * - in pPartNumber: the partition number YYY,
647 * - in PathComponent: pointer value (inside NtPath) to the beginning of \path.
649 * NOTE: The function does not accept leading whitespace.
652 NtPathToDiskPartComponents(
654 OUT PULONG pDiskNumber
,
655 OUT PULONG pPartNumber
,
656 OUT PCWSTR
* PathComponent OPTIONAL
)
658 ULONG DiskNumber
, PartNumber
;
663 if (PathComponent
) *PathComponent
= NULL
;
667 if (_wcsnicmp(Path
, L
"\\Device\\Harddisk", 16) != 0)
669 /* The NT path doesn't start with the prefix string, thus it cannot be a hard disk device path */
670 DPRINT1("'%S' : Not a possible hard disk device.\n", NtPath
);
676 /* A number must be present now */
677 if (!iswdigit(*Path
))
679 DPRINT1("'%S' : expected a number! Not a regular hard disk device.\n", Path
);
682 DiskNumber
= wcstoul(Path
, (PWSTR
*)&Path
, 10);
684 /* Either NULL termination, or a path separator must be present now */
685 if (*Path
&& *Path
!= OBJ_NAME_PATH_SEPARATOR
)
687 DPRINT1("'%S' : expected a path separator!\n", Path
);
693 DPRINT1("The path only specified a hard disk (and nothing else, like a partition...), so we stop there.\n");
697 /* Here, *Path == L'\\' */
699 if (_wcsnicmp(Path
, L
"\\Partition", 10) != 0)
701 /* Actually, \Partition is optional so, if we don't have it, we still return success. Or should we? */
702 DPRINT1("'%S' : unexpected format!\n", NtPath
);
708 /* A number must be present now */
709 if (!iswdigit(*Path
))
711 /* 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? */
712 DPRINT1("'%S' : expected a number!\n", Path
);
715 PartNumber
= wcstoul(Path
, (PWSTR
*)&Path
, 10);
717 /* Either NULL termination, or a path separator must be present now */
718 if (*Path
&& *Path
!= OBJ_NAME_PATH_SEPARATOR
)
720 /* We shouldn't fail here because it just means this part of path is actually not a partition specifier. Or should we? */
721 DPRINT1("'%S' : expected a path separator!\n", Path
);
725 /* OK, here we really have a partition specifier: return its number */
726 *pPartNumber
= PartNumber
;
729 /* Return the disk number */
730 *pDiskNumber
= DiskNumber
;
732 /* Return the path component also, if the user wants it */
733 if (PathComponent
) *PathComponent
= Path
;
740 IN HANDLE RootDirectory OPTIONAL
,
741 IN PCWSTR PathNameToFile
,
742 OUT PHANDLE FileHandle
, // IN OUT PHANDLE OPTIONAL
743 OUT PHANDLE SectionHandle
,
744 OUT PVOID
* BaseAddress
,
745 OUT PULONG FileSize OPTIONAL
,
746 IN BOOLEAN ReadWriteAccess
)
749 UNICODE_STRING FileName
;
750 OBJECT_ATTRIBUTES ObjectAttributes
;
751 IO_STATUS_BLOCK IoStatusBlock
;
752 ULONG SectionPageProtection
;
758 RtlInitUnicodeString(&FileName
, PathNameToFile
);
760 InitializeObjectAttributes(&ObjectAttributes
,
762 OBJ_CASE_INSENSITIVE
,
767 *SectionHandle
= NULL
;
769 Status
= NtOpenFile(FileHandle
,
770 FILE_GENERIC_READ
| // Contains SYNCHRONIZE
771 (ReadWriteAccess
? FILE_GENERIC_WRITE
: 0),
775 FILE_SYNCHRONOUS_IO_NONALERT
| FILE_NON_DIRECTORY_FILE
);
776 if (!NT_SUCCESS(Status
))
778 DPRINT1("Failed to open file '%wZ', Status 0x%08lx\n", &FileName
, Status
);
784 /* Query the file size */
785 FILE_STANDARD_INFORMATION FileInfo
;
786 Status
= NtQueryInformationFile(*FileHandle
,
790 FileStandardInformation
);
791 if (!NT_SUCCESS(Status
))
793 DPRINT("NtQueryInformationFile() failed (Status %lx)\n", Status
);
794 NtClose(*FileHandle
);
799 if (FileInfo
.EndOfFile
.HighPart
!= 0)
800 DPRINT1("WARNING!! The file '%wZ' is too large!\n", &FileName
);
802 *FileSize
= FileInfo
.EndOfFile
.LowPart
;
804 DPRINT("File size: %lu\n", *FileSize
);
807 /* Map the file in memory */
809 SectionPageProtection
= (ReadWriteAccess
? PAGE_READWRITE
: PAGE_READONLY
);
811 /* Create the section */
812 Status
= NtCreateSection(SectionHandle
,
813 STANDARD_RIGHTS_REQUIRED
| SECTION_QUERY
|
815 (ReadWriteAccess
? SECTION_MAP_WRITE
: 0),
818 SectionPageProtection
,
819 SEC_COMMIT
/* | SEC_IMAGE (_NO_EXECUTE) */,
821 if (!NT_SUCCESS(Status
))
823 DPRINT1("Failed to create a memory section for file '%wZ', Status 0x%08lx\n", &FileName
, Status
);
824 NtClose(*FileHandle
);
829 /* Map the section */
832 Status
= NtMapViewOfSection(*SectionHandle
,
840 SectionPageProtection
);
841 if (!NT_SUCCESS(Status
))
843 DPRINT1("Failed to map a view for file '%wZ', Status 0x%08lx\n", &FileName
, Status
);
844 NtClose(*SectionHandle
);
845 *SectionHandle
= NULL
;
846 NtClose(*FileHandle
);
851 *BaseAddress
= ViewBase
;
852 return STATUS_SUCCESS
;
857 IN HANDLE SectionHandle
,
858 IN PVOID BaseAddress
)
861 BOOLEAN Success
= TRUE
;
863 Status
= NtUnmapViewOfSection(NtCurrentProcess(), BaseAddress
);
864 if (!NT_SUCCESS(Status
))
866 DPRINT1("UnMapFile: NtUnmapViewOfSection(0x%p) failed with Status 0x%08lx\n",
867 BaseAddress
, Status
);
870 Status
= NtClose(SectionHandle
);
871 if (!NT_SUCCESS(Status
))
873 DPRINT1("UnMapFile: NtClose(0x%p) failed with Status 0x%08lx\n",
874 SectionHandle
, Status
);