[USETUP] Improve the FileSup module.
[reactos.git] / base / setup / usetup / filesup.c
index 2fe323a..49defff 100644 (file)
@@ -18,7 +18,7 @@
  */
 /* COPYRIGHT:       See COPYING in the top level directory
  * PROJECT:         ReactOS text-mode setup
- * FILE:            subsys/system/usetup/filesup.c
+ * FILE:            base/setup/usetup/filesup.c
  * PURPOSE:         File support functions
  * PROGRAMMER:      Eric Kohl
  *                  Casper S. Hornstrup (chorns@users.sourceforge.net)
@@ -48,8 +48,9 @@ SetupCreateSingleDirectory(
     HANDLE DirectoryHandle;
     NTSTATUS Status;
 
-    RtlCreateUnicodeString(&PathName,
-                           DirectoryName);
+    if(!RtlCreateUnicodeString(&PathName, DirectoryName))
+        return STATUS_NO_MEMORY;
+
     if (PathName.Length > sizeof(WCHAR) &&
         PathName.Buffer[PathName.Length / sizeof(WCHAR) - 2] == L'\\' &&
         PathName.Buffer[PathName.Length / sizeof(WCHAR) - 1] == L'.')
@@ -92,44 +93,6 @@ SetupCreateSingleDirectory(
     return Status;
 }
 
-
-static
-BOOLEAN
-DoesPathExist(
-    PWSTR PathName)
-{
-    OBJECT_ATTRIBUTES ObjectAttributes;
-    IO_STATUS_BLOCK IoStatusBlock;
-    UNICODE_STRING Name;
-    HANDLE FileHandle;
-    NTSTATUS Status;
-
-    RtlInitUnicodeString(&Name,
-                         PathName);
-
-    InitializeObjectAttributes(&ObjectAttributes,
-                               &Name,
-                               OBJ_CASE_INSENSITIVE,
-                               NULL,
-                               NULL);
-
-    Status = NtOpenFile(&FileHandle,
-                        GENERIC_READ | SYNCHRONIZE,
-                        &ObjectAttributes,
-                        &IoStatusBlock,
-                        0,
-                        FILE_SYNCHRONOUS_IO_NONALERT);
-    if (!NT_SUCCESS(Status))
-    {
-        return FALSE;
-    }
-
-    NtClose(FileHandle);
-
-    return TRUE;
-}
-
-
 NTSTATUS
 SetupCreateDirectory(
     PWCHAR PathName)
@@ -167,7 +130,7 @@ SetupCreateDirectory(
             *Ptr = 0;
 
             DPRINT("PathBuffer: %S\n", PathBuffer);
-            if (!DoesPathExist(PathBuffer))
+            if (!DoesPathExist(NULL, PathBuffer))
             {
                 DPRINT("Create: %S\n", PathBuffer);
                 Status = SetupCreateSingleDirectory(PathBuffer);
@@ -181,7 +144,7 @@ SetupCreateDirectory(
         Ptr++;
     }
 
-    if (!DoesPathExist(PathBuffer))
+    if (!DoesPathExist(NULL, PathBuffer))
     {
         DPRINT("Create: %S\n", PathBuffer);
         Status = SetupCreateSingleDirectory(PathBuffer);
@@ -197,7 +160,6 @@ done:
     return Status;
 }
 
-
 NTSTATUS
 SetupCopyFile(
     PWCHAR SourceFileName,
@@ -217,7 +179,6 @@ SetupCopyFile(
     SIZE_T SourceSectionSize = 0;
     LARGE_INTEGER ByteOffset;
 
-#ifdef __REACTOS__
     RtlInitUnicodeString(&FileName,
                          SourceFileName);
 
@@ -238,20 +199,6 @@ SetupCopyFile(
         DPRINT1("NtOpenFile failed: %x, %wZ\n", Status, &FileName);
         goto done;
     }
-#else
-    FileHandleSource = CreateFileW(SourceFileName,
-                                   GENERIC_READ,
-                                   FILE_SHARE_READ,
-                                   NULL,
-                                   OPEN_EXISTING,
-                                   0,
-                                   NULL);
-    if (FileHandleSource == INVALID_HANDLE_VALUE)
-    {
-        Status = STATUS_UNSUCCESSFUL;
-        goto done;
-    }
-#endif
 
     Status = NtQueryInformationFile(FileHandleSource,
                                     &IoStatusBlock,
@@ -327,13 +274,74 @@ SetupCopyFile(
                           0);
     if (!NT_SUCCESS(Status))
     {
-        DPRINT1("NtCreateFile failed: %x\n", Status);
-        goto unmapsrcsec;
+        /* Open may have failed because the file to overwrite
+         * is in readonly mode
+         */
+        if (Status == STATUS_ACCESS_DENIED)
+        {
+            FILE_BASIC_INFORMATION FileBasicInfo;
+
+            /* Reattempt to open it with limited access */
+            Status = NtCreateFile(&FileHandleDest,
+                                  FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
+                                  &ObjectAttributes,
+                                  &IoStatusBlock,
+                                  NULL,
+                                  FILE_ATTRIBUTE_NORMAL,
+                                  0,
+                                  FILE_OPEN,
+                                  FILE_NO_INTERMEDIATE_BUFFERING |
+                                  FILE_SEQUENTIAL_ONLY |
+                                  FILE_SYNCHRONOUS_IO_NONALERT,
+                                  NULL,
+                                  0);
+            /* Fail for real if we cannot open it that way */
+            if (!NT_SUCCESS(Status))
+            {
+                DPRINT1("NtCreateFile failed: %x, %wZ\n", Status, &FileName);
+                goto unmapsrcsec;
+            }
+
+            /* Zero our basic info, just to set attributes */
+            RtlZeroMemory(&FileBasicInfo, sizeof(FileBasicInfo));
+            /* Reset attributes to normal, no read-only */
+            FileBasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
+            /* We basically don't care about whether it succeed:
+             * if it didn't, later open will fail
+             */
+            NtSetInformationFile(FileHandleDest, &IoStatusBlock, &FileBasicInfo,
+                                 sizeof(FileBasicInfo), FileBasicInformation);
+
+            /* Close file */
+            NtClose(FileHandleDest);
+
+            /* And re-attempt overwrite */
+            Status = NtCreateFile(&FileHandleDest,
+                                  GENERIC_WRITE | SYNCHRONIZE,
+                                  &ObjectAttributes,
+                                  &IoStatusBlock,
+                                  NULL,
+                                  FILE_ATTRIBUTE_NORMAL,
+                                  0,
+                                  FILE_OVERWRITE_IF,
+                                  FILE_NO_INTERMEDIATE_BUFFERING |
+                                  FILE_SEQUENTIAL_ONLY |
+                                  FILE_SYNCHRONOUS_IO_NONALERT,
+                                  NULL,
+                                  0);
+        }
+
+        /* We failed */
+        if (!NT_SUCCESS(Status))
+        {
+            DPRINT1("NtCreateFile failed: %x, %wZ\n", Status, &FileName);
+            goto unmapsrcsec;
+        }
     }
 
     RegionSize = (ULONG)PAGE_ROUND_UP(FileStandard.EndOfFile.u.LowPart);
     IoStatusBlock.Status = 0;
-    ByteOffset.QuadPart = 0;
+    ByteOffset.QuadPart = 0ULL;
     Status = NtWriteFile(FileHandleDest,
                          NULL,
                          NULL,
@@ -388,7 +396,7 @@ done:
     return Status;
 }
 
-#ifdef __REACTOS__
+
 NTSTATUS
 SetupExtractFile(
     PWCHAR CabinetFileName,
@@ -466,51 +474,418 @@ SetupExtractFile(
 
     return STATUS_SUCCESS;
 }
-#endif
+
+
+BOOLEAN
+IsValidPath(
+    IN PCWSTR InstallDir)
+{
+    UINT i, Length;
+
+    Length = wcslen(InstallDir);
+
+    // TODO: Add check for 8.3 too.
+
+    /* Path must be at least 2 characters long */
+//    if (Length < 2)
+//        return FALSE;
+
+    /* Path must start with a backslash */
+//    if (InstallDir[0] != L'\\')
+//        return FALSE;
+
+    /* Path must not end with a backslash */
+    if (InstallDir[Length - 1] == L'\\')
+        return FALSE;
+
+    /* Path must not contain whitespace characters */
+    for (i = 0; i < Length; i++)
+    {
+        if (iswspace(InstallDir[i]))
+            return FALSE;
+    }
+
+    /* Path component must not end with a dot */
+    for (i = 0; i < Length; i++)
+    {
+        if (InstallDir[i] == L'\\' && i > 0)
+        {
+            if (InstallDir[i - 1] == L'.')
+                return FALSE;
+        }
+    }
+
+    if (InstallDir[Length - 1] == L'.')
+        return FALSE;
+
+    return TRUE;
+}
+
+NTSTATUS
+ConcatPaths(
+    IN OUT PWSTR PathElem1,
+    IN SIZE_T cchPathSize,
+    IN PCWSTR PathElem2 OPTIONAL)
+{
+    NTSTATUS Status;
+    SIZE_T cchPathLen;
+
+    if (!PathElem2)
+        return STATUS_SUCCESS;
+    if (cchPathSize <= 1)
+        return STATUS_SUCCESS;
+
+    cchPathLen = min(cchPathSize, wcslen(PathElem1));
+
+    if (PathElem2[0] != L'\\' && cchPathLen > 0 && PathElem1[cchPathLen-1] != L'\\')
+    {
+        /* PathElem2 does not start with '\' and PathElem1 does not end with '\' */
+        Status = RtlStringCchCatW(PathElem1, cchPathSize, L"\\");
+        if (!NT_SUCCESS(Status))
+            return Status;
+    }
+    else if (PathElem2[0] == L'\\' && cchPathLen > 0 && PathElem1[cchPathLen-1] == L'\\')
+    {
+        /* PathElem2 starts with '\' and PathElem1 ends with '\' */
+        while (*PathElem2 == L'\\')
+            ++PathElem2; // Skip any backslash
+    }
+    Status = RtlStringCchCatW(PathElem1, cchPathSize, PathElem2);
+    return Status;
+}
+
+//
+// NOTE: It may be possible to merge both DoesPathExist and DoesFileExist...
+//
+BOOLEAN
+DoesPathExist(
+    IN HANDLE RootDirectory OPTIONAL,
+    IN PCWSTR PathName)
+{
+    NTSTATUS Status;
+    HANDLE FileHandle;
+    OBJECT_ATTRIBUTES ObjectAttributes;
+    IO_STATUS_BLOCK IoStatusBlock;
+    UNICODE_STRING Name;
+
+    RtlInitUnicodeString(&Name, PathName);
+
+    InitializeObjectAttributes(&ObjectAttributes,
+                               &Name,
+                               OBJ_CASE_INSENSITIVE,
+                               RootDirectory,
+                               NULL);
+
+    Status = NtOpenFile(&FileHandle,
+                        FILE_LIST_DIRECTORY | SYNCHRONIZE,
+                        &ObjectAttributes,
+                        &IoStatusBlock,
+                        FILE_SHARE_READ | FILE_SHARE_WRITE,
+                        FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE);
+    if (NT_SUCCESS(Status))
+        NtClose(FileHandle);
+    else
+        DPRINT1("Failed to open directory %wZ, Status 0x%08lx\n", &Name, Status);
+
+    return NT_SUCCESS(Status);
+}
 
 BOOLEAN
 DoesFileExist(
-    PWSTR PathName,
-    PWSTR FileName)
+    IN HANDLE RootDirectory OPTIONAL,
+    IN PCWSTR PathName OPTIONAL,
+    IN PCWSTR FileName)
 {
+    NTSTATUS Status;
+    HANDLE FileHandle;
     OBJECT_ATTRIBUTES ObjectAttributes;
     IO_STATUS_BLOCK IoStatusBlock;
     UNICODE_STRING Name;
     WCHAR FullName[MAX_PATH];
-    HANDLE FileHandle;
-    NTSTATUS Status;
 
-    wcscpy(FullName, PathName);
-    if (FileName != NULL)
+    if (PathName)
+        RtlStringCchCopyW(FullName, ARRAYSIZE(FullName), PathName);
+    else
+        FullName[0] = UNICODE_NULL;
+
+    if (FileName)
+        ConcatPaths(FullName, ARRAYSIZE(FullName), FileName);
+
+    RtlInitUnicodeString(&Name, FullName);
+
+    InitializeObjectAttributes(&ObjectAttributes,
+                               &Name,
+                               OBJ_CASE_INSENSITIVE,
+                               RootDirectory,
+                               NULL);
+
+    Status = NtOpenFile(&FileHandle,
+                        GENERIC_READ | SYNCHRONIZE,
+                        &ObjectAttributes,
+                        &IoStatusBlock,
+                        FILE_SHARE_READ | FILE_SHARE_WRITE,
+                        FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
+    if (NT_SUCCESS(Status))
+        NtClose(FileHandle);
+    else
+        DPRINT1("Failed to open file %wZ, Status 0x%08lx\n", &Name, Status);
+
+    return NT_SUCCESS(Status);
+}
+
+/*
+ * The format of NtPath should be:
+ *    \Device\HarddiskXXX\PartitionYYY[\path] ,
+ * where XXX and YYY respectively represent the hard disk and partition numbers,
+ * and [\path] represent an optional path (separated by '\\').
+ *
+ * If a NT path of such a form is correctly parsed, the function returns respectively:
+ * - in pDiskNumber: the hard disk number XXX,
+ * - in pPartNumber: the partition number YYY,
+ * - in PathComponent: pointer value (inside NtPath) to the beginning of \path.
+ *
+ * NOTE: The function does not accept leading whitespace.
+ */
+BOOLEAN
+NtPathToDiskPartComponents(
+    IN PCWSTR NtPath,
+    OUT PULONG pDiskNumber,
+    OUT PULONG pPartNumber,
+    OUT PCWSTR* PathComponent OPTIONAL)
+{
+    ULONG DiskNumber, PartNumber;
+    PCWSTR Path;
+
+    *pDiskNumber = 0;
+    *pPartNumber = 0;
+    if (PathComponent) *PathComponent = NULL;
+
+    Path = NtPath;
+
+    if (_wcsnicmp(Path, L"\\Device\\Harddisk", 16) != 0)
+    {
+        /* The NT path doesn't start with the prefix string, thus it cannot be a hard disk device path */
+        DPRINT1("'%S' : Not a possible hard disk device.\n", NtPath);
+        return FALSE;
+    }
+
+    Path += 16;
+
+    /* A number must be present now */
+    if (!iswdigit(*Path))
     {
-        if (FileName[0] != L'\\')
-            wcscat(FullName, L"\\");
-        wcscat(FullName, FileName);
+        DPRINT1("'%S' : expected a number! Not a regular hard disk device.\n", Path);
+        return FALSE;
     }
+    DiskNumber = wcstoul(Path, (PWSTR*)&Path, 10);
 
-    RtlInitUnicodeString(&Name,
-                         FullName);
+    /* Either NULL termination, or a path separator must be present now */
+    if (!Path)
+    {
+        DPRINT1("An error happened!\n");
+        return FALSE;
+    }
+    else if (*Path && *Path != OBJ_NAME_PATH_SEPARATOR)
+    {
+        DPRINT1("'%S' : expected a path separator!\n", Path);
+        return FALSE;
+    }
+
+    if (!*Path)
+    {
+        DPRINT1("The path only specified a hard disk (and nothing else, like a partition...), so we stop there.\n");
+        goto Quit;
+    }
+
+    /* Here, *Path == L'\\' */
+
+    if (_wcsnicmp(Path, L"\\Partition", 10) != 0)
+    {
+        /* Actually, \Partition is optional so, if we don't have it, we still return success. Or should we? */
+        DPRINT1("'%S' : unexpected format!\n", NtPath);
+        goto Quit;
+    }
+
+    Path += 10;
+
+    /* A number must be present now */
+    if (!iswdigit(*Path))
+    {
+        /* 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? */
+        DPRINT1("'%S' : expected a number!\n", Path);
+        goto Quit;
+    }
+    PartNumber = wcstoul(Path, (PWSTR*)&Path, 10);
+
+    /* Either NULL termination, or a path separator must be present now */
+    if (!Path)
+    {
+        /* We fail here because wcstoul failed for whatever reason */
+        DPRINT1("An error happened!\n");
+        return FALSE;
+    }
+    else if (*Path && *Path != OBJ_NAME_PATH_SEPARATOR)
+    {
+        /* We shouldn't fail here because it just means this part of path is actually not a partition specifier. Or should we? */
+        DPRINT1("'%S' : expected a path separator!\n", Path);
+        goto Quit;
+    }
+
+    /* OK, here we really have a partition specifier: return its number */
+    *pPartNumber = PartNumber;
+
+Quit:
+    /* Return the disk number */
+    *pDiskNumber = DiskNumber;
+
+    /* Return the path component also, if the user wants it */
+    if (PathComponent) *PathComponent = Path;
+
+    return TRUE;
+}
+
+NTSTATUS
+OpenAndMapFile(
+    IN HANDLE RootDirectory OPTIONAL,
+    IN PCWSTR PathName OPTIONAL,
+    IN PCWSTR FileName,             // OPTIONAL
+    OUT PHANDLE FileHandle,         // IN OUT PHANDLE OPTIONAL
+    OUT PHANDLE SectionHandle,
+    OUT PVOID* BaseAddress,
+    OUT PULONG FileSize OPTIONAL)
+{
+    NTSTATUS Status;
+    OBJECT_ATTRIBUTES ObjectAttributes;
+    IO_STATUS_BLOCK IoStatusBlock;
+    SIZE_T ViewSize;
+    PVOID ViewBase;
+    UNICODE_STRING Name;
+    WCHAR FullName[MAX_PATH];
+
+    if (PathName)
+        RtlStringCchCopyW(FullName, ARRAYSIZE(FullName), PathName);
+    else
+        FullName[0] = UNICODE_NULL;
+
+    if (FileName)
+        ConcatPaths(FullName, ARRAYSIZE(FullName), FileName);
+
+    RtlInitUnicodeString(&Name, FullName);
 
     InitializeObjectAttributes(&ObjectAttributes,
                                &Name,
                                OBJ_CASE_INSENSITIVE,
-                               NULL,
+                               RootDirectory,
                                NULL);
 
-    Status = NtOpenFile(&FileHandle,
+    *FileHandle = NULL;
+    *SectionHandle = NULL;
+
+    Status = NtOpenFile(FileHandle,
                         GENERIC_READ | SYNCHRONIZE,
                         &ObjectAttributes,
                         &IoStatusBlock,
-                        0,
-                        FILE_SYNCHRONOUS_IO_NONALERT);
+                        FILE_SHARE_READ,
+                        FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
     if (!NT_SUCCESS(Status))
     {
-      return FALSE;
+        DPRINT1("Failed to open file %wZ, Status 0x%08lx\n", &Name, Status);
+        return Status;
+    }
+
+    if (FileSize)
+    {
+        /* Query the file size */
+        FILE_STANDARD_INFORMATION FileInfo;
+        Status = NtQueryInformationFile(*FileHandle,
+                                        &IoStatusBlock,
+                                        &FileInfo,
+                                        sizeof(FileInfo),
+                                        FileStandardInformation);
+        if (!NT_SUCCESS(Status))
+        {
+            DPRINT("NtQueryInformationFile() failed (Status %lx)\n", Status);
+            NtClose(*FileHandle);
+            *FileHandle = NULL;
+            return Status;
+        }
+
+        if (FileInfo.EndOfFile.HighPart != 0)
+            DPRINT1("WARNING!! The file %wZ is too large!\n", Name);
+
+        *FileSize = FileInfo.EndOfFile.LowPart;
+
+        DPRINT("File size: %lu\n", *FileSize);
     }
 
-    NtClose(FileHandle);
+    /* Map the file in memory */
 
-    return TRUE;
+    /* Create the section */
+    Status = NtCreateSection(SectionHandle,
+                             SECTION_MAP_READ,
+                             NULL,
+                             NULL,
+                             PAGE_READONLY,
+                             SEC_COMMIT /* | SEC_IMAGE (_NO_EXECUTE) */,
+                             *FileHandle);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("Failed to create a memory section for file %wZ, Status 0x%08lx\n", &Name, Status);
+        NtClose(*FileHandle);
+        *FileHandle = NULL;
+        return Status;
+    }
+
+    /* Map the section */
+    ViewSize = 0;
+    ViewBase = NULL;
+    Status = NtMapViewOfSection(*SectionHandle,
+                                NtCurrentProcess(),
+                                &ViewBase,
+                                0, 0,
+                                NULL,
+                                &ViewSize,
+                                ViewShare,
+                                0,
+                                PAGE_READONLY);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("Failed to map a view for file %wZ, Status 0x%08lx\n", &Name, Status);
+        NtClose(*SectionHandle);
+        *SectionHandle = NULL;
+        NtClose(*FileHandle);
+        *FileHandle = NULL;
+        return Status;
+    }
+
+    *BaseAddress = ViewBase;
+    return STATUS_SUCCESS;
+}
+
+BOOLEAN
+UnMapFile(
+    IN HANDLE SectionHandle,
+    IN PVOID BaseAddress)
+{
+    NTSTATUS Status;
+    BOOLEAN Success = TRUE;
+
+    Status = NtUnmapViewOfSection(NtCurrentProcess(), BaseAddress);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("UnMapFile: NtUnmapViewOfSection(0x%p) failed with Status 0x%08lx\n",
+                BaseAddress, Status);
+        Success = FALSE;
+    }
+    Status = NtClose(SectionHandle);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("UnMapFile: NtClose(0x%p) failed with Status 0x%08lx\n",
+                SectionHandle, Status);
+        Success = FALSE;
+    }
+
+    return Success;
 }
 
 /* EOF */