/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS Setup Library * FILE: base/setup/lib/install.c * PURPOSE: Installation functions * PROGRAMMERS: Hervé Poussineau (hpoussin@reactos.org) * Hermes Belusca-Maito (hermes.belusca@sfr.fr) */ /* INCLUDES *****************************************************************/ #include "precomp.h" #include "filesup.h" #include "infsupp.h" #include "setuplib.h" // HAXX for USETUP_DATA!! #include "install.h" #define NDEBUG #include /* FUNCTIONS ****************************************************************/ static BOOL LookupDirectoryById( IN HINF InfHandle, IN OUT PINFCONTEXT InfContext, IN PCWSTR DirId, OUT PCWSTR* pDirectory) { BOOL Success; // ReactOS-specific Success = SpInfFindFirstLine(InfHandle, L"Directories", DirId, InfContext); if (!Success) { // Windows-compatible Success = SpInfFindFirstLine(InfHandle, L"WinntDirectories", DirId, InfContext); if (!Success) DPRINT1("SpInfFindFirstLine() failed\n"); } if (Success) { Success = INF_GetData(InfContext, NULL, pDirectory); if (!Success) DPRINT1("INF_GetData() failed\n"); } if (!Success) DPRINT1("LookupDirectoryById(%S) - directory not found!\n", DirId); return Success; } /* * Note: Modeled after SetupGetSourceFileLocation(), SetupGetSourceInfo() * and SetupGetTargetPath() APIs. * Technically the target path is the same for a given file section, * but here we try to remove this constraint. * * TXTSETUP.SIF entries syntax explained at: * http://www.msfn.org/board/topic/125480-txtsetupsif-syntax/ */ static NTSTATUS GetSourceFileAndTargetLocation( IN HINF InfHandle, IN PINFCONTEXT InfContext OPTIONAL, IN PCWSTR SourceFileName OPTIONAL, OUT PCWSTR* pSourceRootPath, OUT PCWSTR* pSourcePath, OUT PCWSTR* pTargetDirectory, OUT PCWSTR* pTargetFileName) { BOOL Success; INFCONTEXT FileContext; INFCONTEXT DirContext; PCWSTR SourceRootDirId; PCWSTR SourceRootDir; PCWSTR SourceRelativePath; PCWSTR TargetDirId; PCWSTR TargetDir; PCWSTR TargetFileName; /* Either InfContext or SourceFileName must be specified */ if (!InfContext && !SourceFileName) return STATUS_INVALID_PARAMETER; /* InfContext to a file was not given, retrieve one corresponding to SourceFileName */ if (!InfContext) { /* Search for the SourceDisksFiles section */ /* Search in the optional platform-specific first (currently hardcoded; make it runtime-dependent?) */ Success = SpInfFindFirstLine(InfHandle, L"SourceDisksFiles." INF_ARCH, SourceFileName, &FileContext); if (!Success) { /* Search in the global section */ Success = SpInfFindFirstLine(InfHandle, L"SourceDisksFiles", SourceFileName, &FileContext); } if (!Success) { // pSetupData->LastErrorNumber = ERROR_TXTSETUP_SECTION; // if (pSetupData->ErrorRoutine) // pSetupData->ErrorRoutine(pSetupData, SectionName); return STATUS_NOT_FOUND; } InfContext = &FileContext; } // else, InfContext != NULL and ignore SourceFileName (that may or may not be == NULL). /* * Getting Source File Location -- SetupGetSourceFileLocation() */ /* Get source root directory id */ if (!INF_GetDataField(InfContext, 1, &SourceRootDirId)) { /* FIXME: Handle error! */ DPRINT1("INF_GetData() failed\n"); return STATUS_NOT_FOUND; } /* Lookup source root directory -- SetupGetSourceInfo() */ /* Search in the optional platform-specific first (currently hardcoded; make it runtime-dependent?) */ Success = SpInfFindFirstLine(InfHandle, L"SourceDisksNames." INF_ARCH, SourceRootDirId, &DirContext); if (!Success) { /* Search in the global section */ Success = SpInfFindFirstLine(InfHandle, L"SourceDisksNames", SourceRootDirId, &DirContext); if (!Success) DPRINT1("SpInfFindFirstLine(\"SourceDisksNames\", \"%S\") failed\n", SourceRootDirId); } INF_FreeData(SourceRootDirId); if (!Success) { /* FIXME: Handle error! */ // pSetupData->LastErrorNumber = ERROR_TXTSETUP_SECTION; // if (pSetupData->ErrorRoutine) // pSetupData->ErrorRoutine(pSetupData, SectionName); return STATUS_NOT_FOUND; } if (!INF_GetDataField(&DirContext, 4, &SourceRootDir)) { /* FIXME: Handle error! */ DPRINT1("INF_GetData() failed\n"); return STATUS_NOT_FOUND; } /* Get optional source relative directory */ if (!INF_GetDataField(InfContext, 2, &SourceRelativePath)) { SourceRelativePath = NULL; } else if (!*SourceRelativePath) { INF_FreeData(SourceRelativePath); SourceRelativePath = NULL; } if (!SourceRelativePath) { /* Use WinPE directory instead */ if (INF_GetDataField(InfContext, 13, &TargetDirId)) { /* Lookup directory */ Success = LookupDirectoryById(InfHandle, &DirContext, TargetDirId, &SourceRelativePath); INF_FreeData(TargetDirId); if (!Success) { SourceRelativePath = NULL; } else if (!*SourceRelativePath) { INF_FreeData(SourceRelativePath); SourceRelativePath = NULL; } } } /* * Getting Target File Location -- SetupGetTargetPath() */ /* Get target directory id */ if (!INF_GetDataField(InfContext, 8, &TargetDirId)) { /* FIXME: Handle error! */ DPRINT1("INF_GetData() failed\n"); INF_FreeData(SourceRelativePath); INF_FreeData(SourceRootDir); return STATUS_NOT_FOUND; } /* Lookup target directory */ Success = LookupDirectoryById(InfHandle, &DirContext, TargetDirId, &TargetDir); INF_FreeData(TargetDirId); if (!Success) { /* FIXME: Handle error! */ INF_FreeData(SourceRelativePath); INF_FreeData(SourceRootDir); return STATUS_NOT_FOUND; } /* Get optional target file name */ if (!INF_GetDataField(InfContext, 11, &TargetFileName)) TargetFileName = NULL; else if (!*TargetFileName) TargetFileName = NULL; DPRINT("GetSourceFileAndTargetLocation(%S) = " "SrcRootDir: '%S', SrcRelPath: '%S' --> TargetDir: '%S', TargetFileName: '%S'\n", SourceFileName, SourceRootDir, SourceRelativePath, TargetDir, TargetFileName); #if 0 INF_FreeData(TargetDir); INF_FreeData(TargetFileName); INF_FreeData(SourceRelativePath); INF_FreeData(SourceRootDir); #endif *pSourceRootPath = SourceRootDir; *pSourcePath = SourceRelativePath; *pTargetDirectory = TargetDir; *pTargetFileName = TargetFileName; return STATUS_SUCCESS; } static NTSTATUS BuildFullDirectoryPath( IN PCWSTR RootPath, IN PCWSTR BasePath, IN PCWSTR RelativePath, OUT PWSTR FullPath, IN SIZE_T cchFullPathSize) { NTSTATUS Status; if ((RelativePath[0] == UNICODE_NULL) || (RelativePath[0] == L'\\' && RelativePath[1] == UNICODE_NULL)) { /* Installation path */ DPRINT("InstallationPath: '%S'\n", RelativePath); Status = CombinePaths(FullPath, cchFullPathSize, 2, RootPath, BasePath); DPRINT("InstallationPath(2): '%S'\n", FullPath); } else if (RelativePath[0] == L'\\') { /* Absolute path */ DPRINT("AbsolutePath: '%S'\n", RelativePath); Status = CombinePaths(FullPath, cchFullPathSize, 2, RootPath, RelativePath); DPRINT("AbsolutePath(2): '%S'\n", FullPath); } else // if (RelativePath[0] != L'\\') { /* Path relative to the installation path */ DPRINT("RelativePath: '%S'\n", RelativePath); Status = CombinePaths(FullPath, cchFullPathSize, 3, RootPath, BasePath, RelativePath); DPRINT("RelativePath(2): '%S'\n", FullPath); } return Status; } /* * This code enumerates the list of files in reactos.dff / reactos.inf * that need to be extracted from reactos.cab and be installed in their * respective directories. */ /* * IMPORTANT NOTE: The INF file specification used for the .CAB in ReactOS * is not compliant with respect to TXTSETUP.SIF syntax or the standard syntax. */ static BOOLEAN AddSectionToCopyQueueCab( IN PUSETUP_DATA pSetupData, IN HINF InfFile, IN PCWSTR SectionName, IN PCWSTR SourceCabinet, IN PCUNICODE_STRING DestinationPath) { BOOLEAN Success; NTSTATUS Status; INFCONTEXT FilesContext; INFCONTEXT DirContext; PCWSTR SourceFileName; PCWSTR TargetDirId; PCWSTR TargetDir; PCWSTR TargetFileName; WCHAR FileDstPath[MAX_PATH]; /* Search for the SectionName section */ if (!SpInfFindFirstLine(InfFile, SectionName, NULL, &FilesContext)) { DPRINT1("AddSectionToCopyQueueCab(): Unable to find section '%S' in cabinet file\n", SectionName); return FALSE; } /* * Enumerate the files in the section and add them to the file queue. */ do { /* Get source file name and target directory id */ if (!INF_GetData(&FilesContext, &SourceFileName, &TargetDirId)) { /* FIXME: Handle error! */ DPRINT1("INF_GetData() failed\n"); break; } /* Get optional target file name */ if (!INF_GetDataField(&FilesContext, 2, &TargetFileName)) { TargetFileName = NULL; } else if (!*TargetFileName) { INF_FreeData(TargetFileName); TargetFileName = NULL; } /* Lookup target directory */ Success = LookupDirectoryById(InfFile, &DirContext, TargetDirId, &TargetDir); INF_FreeData(TargetDirId); if (!Success) { /* FIXME: Handle error! */ INF_FreeData(TargetFileName); INF_FreeData(SourceFileName); break; } DPRINT("GetSourceTargetFromCab(%S) = " "SrcRootDir: '%S', SrcRelPath: '%S' --> TargetDir: '%S', TargetFileName: '%S'\n", SourceFileName, pSetupData->SourcePath.Buffer, pSetupData->SourceRootDir.Buffer, TargetDir, TargetFileName); Status = CombinePaths(FileDstPath, ARRAYSIZE(FileDstPath), 2, pSetupData->DestinationPath.Buffer, TargetDir); UNREFERENCED_PARAMETER(Status); DPRINT(" --> FileDstPath = '%S'\n", FileDstPath); INF_FreeData(TargetDir); if (!SpFileQueueCopy((HSPFILEQ)pSetupData->SetupFileQueue, pSetupData->SourcePath.Buffer, // SourcePath == SourceRootPath ++ SourceRootDir NULL, SourceFileName, NULL, SourceCabinet, NULL, FileDstPath, TargetFileName, 0 /* FIXME */)) { /* FIXME: Handle error! */ DPRINT1("SpFileQueueCopy() failed\n"); } INF_FreeData(TargetFileName); INF_FreeData(SourceFileName); } while (SpInfFindNextLine(&FilesContext, &FilesContext)); return TRUE; } // Note: Modeled after the SetupQueueCopySection() API /* BOOL SetupQueueCopySection( _In_ HSPFILEQ QueueHandle, _In_ PCTSTR SourceRootPath, _In_ HINF InfHandle, _In_ HINF ListInfHandle, _In_ PCTSTR Section, _In_ DWORD CopyStyle ); */ static BOOLEAN AddSectionToCopyQueue( IN PUSETUP_DATA pSetupData, IN HINF InfFile, IN PCWSTR SectionName, IN PCUNICODE_STRING DestinationPath) { NTSTATUS Status; INFCONTEXT FilesContext; PCWSTR SourceFileName; PCWSTR SourceRootPath; PCWSTR SourcePath; PCWSTR TargetDirectory; PCWSTR TargetFileName; WCHAR FileSrcRootPath[MAX_PATH]; WCHAR FileDstPath[MAX_PATH]; /* * This code enumerates the list of files in txtsetup.sif * that need to be installed in their respective directories. */ /* Search for the SectionName section */ if (!SpInfFindFirstLine(InfFile, SectionName, NULL, &FilesContext)) { DPRINT1("AddSectionToCopyQueue(): Unable to find section '%S' in TXTSETUP.SIF\n", SectionName); return FALSE; } /* * Enumerate the files in the section and add them to the file queue. */ do { /* Get source file name */ if (!INF_GetDataField(&FilesContext, 0, &SourceFileName)) { /* FIXME: Handle error! */ DPRINT1("INF_GetData() failed\n"); break; } Status = GetSourceFileAndTargetLocation(InfFile, &FilesContext, SourceFileName, &SourceRootPath, // SourceRootDir &SourcePath, &TargetDirectory, &TargetFileName); if (!NT_SUCCESS(Status)) { DPRINT1("Could not find source and target location for file '%S'\n", SourceFileName); INF_FreeData(SourceFileName); // FIXME: Another error? pSetupData->LastErrorNumber = ERROR_TXTSETUP_SECTION; if (pSetupData->ErrorRoutine) pSetupData->ErrorRoutine(pSetupData, SectionName); return FALSE; // break; } /* * SourcePath: '\Device\CdRom0\I386' * SourceRootPath: '\Device\CdRom0' * SourceRootDir: '\I386' */ Status = CombinePaths(FileSrcRootPath, ARRAYSIZE(FileSrcRootPath), 2, pSetupData->SourceRootPath.Buffer, SourceRootPath); UNREFERENCED_PARAMETER(Status); // DPRINT1("Could not build the full path for '%S', skipping...\n", SourceRootPath); DPRINT(" --> FileSrcRootPath = '%S'\n", FileSrcRootPath); INF_FreeData(SourceRootPath); Status = CombinePaths(FileDstPath, ARRAYSIZE(FileDstPath), 2, pSetupData->DestinationPath.Buffer, TargetDirectory); UNREFERENCED_PARAMETER(Status); // DPRINT1("Could not build the full path for '%S', skipping...\n", TargetDirectory); DPRINT(" --> FileDstPath = '%S'\n", FileDstPath); INF_FreeData(TargetDirectory); if (!SpFileQueueCopy((HSPFILEQ)pSetupData->SetupFileQueue, FileSrcRootPath, SourcePath, SourceFileName, NULL, NULL, // No SourceCabinet NULL, FileDstPath, TargetFileName, 0 /* FIXME */)) { /* FIXME: Handle error! */ DPRINT1("SpFileQueueCopy() failed\n"); } INF_FreeData(TargetFileName); INF_FreeData(SourcePath); INF_FreeData(SourceFileName); } while (SpInfFindNextLine(&FilesContext, &FilesContext)); return TRUE; } BOOLEAN // ERROR_NUMBER PrepareCopyInfFile( IN OUT PUSETUP_DATA pSetupData, IN HINF InfFile, IN PCWSTR SourceCabinet OPTIONAL) { BOOLEAN Success; NTSTATUS Status; INFCONTEXT DirContext; PWCHAR AdditionalSectionName = NULL; PCWSTR DirKeyValue; WCHAR PathBuffer[MAX_PATH]; if (SourceCabinet == NULL) { /* Add common files -- Search for the SourceDisksFiles section */ /* Search in the optional platform-specific first (currently hardcoded; make it runtime-dependent?) */ Success = AddSectionToCopyQueue(pSetupData, InfFile, L"SourceDisksFiles." INF_ARCH, &pSetupData->DestinationPath); if (!Success) { DPRINT1("AddSectionToCopyQueue(%S) failed!\n", L"SourceDisksFiles." INF_ARCH); } /* Search in the global section */ Success = AddSectionToCopyQueue(pSetupData, InfFile, L"SourceDisksFiles", &pSetupData->DestinationPath); if (!Success) { DPRINT1("AddSectionToCopyQueue(%S) failed!\n", L"SourceDisksFiles"); pSetupData->LastErrorNumber = ERROR_TXTSETUP_SECTION; if (pSetupData->ErrorRoutine) pSetupData->ErrorRoutine(pSetupData, L"SourceDisksFiles"); return FALSE; } /* Add specific files depending of computer type */ if (!ProcessComputerFiles(InfFile, pSetupData->ComputerList, &AdditionalSectionName)) return FALSE; if (AdditionalSectionName && !AddSectionToCopyQueue(pSetupData, InfFile, AdditionalSectionName, &pSetupData->DestinationPath)) { pSetupData->LastErrorNumber = ERROR_TXTSETUP_SECTION; if (pSetupData->ErrorRoutine) pSetupData->ErrorRoutine(pSetupData, AdditionalSectionName); return FALSE; } } else { /* Process a cabinet INF */ Success = AddSectionToCopyQueueCab(pSetupData, InfFile, L"SourceFiles", SourceCabinet, &pSetupData->DestinationPath); if (!Success) { DPRINT1("AddSectionToCopyQueueCab(%S) failed!\n", SourceCabinet); pSetupData->LastErrorNumber = ERROR_CABINET_SECTION; if (pSetupData->ErrorRoutine) pSetupData->ErrorRoutine(pSetupData, L"SourceFiles"); return FALSE; } } /* Create directories */ /* * NOTE: This is technically optional since SpFileQueueCommit() * does that. This is however needed if one wants to create * empty directories. */ /* * FIXME: * Copying files to pSetupData->DestinationRootPath should be done from within * the SystemPartitionFiles section. * At the moment we check whether we specify paths like '\foo' or '\\' for that. * For installing to pSetupData->DestinationPath specify just '\' . */ /* Get destination path */ RtlStringCchCopyW(PathBuffer, ARRAYSIZE(PathBuffer), pSetupData->DestinationPath.Buffer); DPRINT("FullPath(1): '%S'\n", PathBuffer); /* Create the install directory */ Status = SetupCreateDirectory(PathBuffer); if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_COLLISION) { DPRINT1("Creating directory '%S' failed: Status = 0x%08lx\n", PathBuffer, Status); pSetupData->LastErrorNumber = ERROR_CREATE_INSTALL_DIR; if (pSetupData->ErrorRoutine) pSetupData->ErrorRoutine(pSetupData, PathBuffer); return FALSE; } /* Search for the 'Directories' section */ // ReactOS-specific if (!SpInfFindFirstLine(InfFile, L"Directories", NULL, &DirContext)) { // Windows-compatible if (!SpInfFindFirstLine(InfFile, L"WinntDirectories", NULL, &DirContext)) { if (SourceCabinet) pSetupData->LastErrorNumber = ERROR_CABINET_SECTION; else pSetupData->LastErrorNumber = ERROR_TXTSETUP_SECTION; if (pSetupData->ErrorRoutine) pSetupData->ErrorRoutine(pSetupData, L"Directories"); return FALSE; } } /* Enumerate the directory values and create the subdirectories */ do { if (!INF_GetData(&DirContext, NULL, &DirKeyValue)) { DPRINT1("break\n"); break; } Status = BuildFullDirectoryPath(pSetupData->DestinationRootPath.Buffer, pSetupData->InstallPath.Buffer, DirKeyValue, PathBuffer, ARRAYSIZE(PathBuffer)); if (!NT_SUCCESS(Status)) { DPRINT1("Could not build the full path for '%S', skipping...\n", DirKeyValue); INF_FreeData(DirKeyValue); continue; } if ((DirKeyValue[0] == UNICODE_NULL) || (DirKeyValue[0] == L'\\' && DirKeyValue[1] == UNICODE_NULL)) { /* * Installation path -- No need to create it * because it has been already created above. */ } else { /* Arbitrary path -- Create it */ Status = SetupCreateDirectory(PathBuffer); if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_COLLISION) { INF_FreeData(DirKeyValue); DPRINT1("Creating directory '%S' failed: Status = 0x%08lx\n", PathBuffer, Status); pSetupData->LastErrorNumber = ERROR_CREATE_DIR; if (pSetupData->ErrorRoutine) pSetupData->ErrorRoutine(pSetupData, PathBuffer); return FALSE; } } INF_FreeData(DirKeyValue); } while (SpInfFindNextLine(&DirContext, &DirContext)); return TRUE; } // #define USE_CABINET_INF BOOLEAN // ERROR_NUMBER PrepareFileCopy( IN OUT PUSETUP_DATA pSetupData, IN PFILE_COPY_STATUS_ROUTINE StatusRoutine OPTIONAL) { HINF InfHandle; INFCONTEXT CabinetsContext; PCWSTR CabinetName; UINT ErrorLine; #if defined(__REACTOS__) && defined(USE_CABINET_INF) ULONG InfFileSize; PVOID InfFileData; CABINET_CONTEXT CabinetContext; #endif WCHAR PathBuffer[MAX_PATH]; /* Create the file queue */ pSetupData->SetupFileQueue = (PVOID)SpFileQueueOpen(); if (pSetupData->SetupFileQueue == NULL) { pSetupData->LastErrorNumber = ERROR_COPY_QUEUE; if (pSetupData->ErrorRoutine) pSetupData->ErrorRoutine(pSetupData); return FALSE; } /* Prepare the copy of the common files that are not in installation cabinets */ if (!PrepareCopyInfFile(pSetupData, pSetupData->SetupInf, NULL)) { /* FIXME: show an error dialog */ return FALSE; } /* Search for the 'Cabinets' section */ if (!SpInfFindFirstLine(pSetupData->SetupInf, L"Cabinets", NULL, &CabinetsContext)) { /* Skip this step and return success if no cabinet file is listed */ return TRUE; } /* * Enumerate the installation cabinets listed in the * 'Cabinets' section and parse their inf files. */ do { if (!INF_GetData(&CabinetsContext, NULL, &CabinetName)) break; CombinePaths(PathBuffer, ARRAYSIZE(PathBuffer), 2, pSetupData->SourcePath.Buffer, CabinetName); #if defined(__REACTOS__) && defined(USE_CABINET_INF) INF_FreeData(CabinetName); CabinetInitialize(&CabinetContext); CabinetSetEventHandlers(&CabinetContext, NULL, NULL, NULL); CabinetSetCabinetName(&CabinetContext, PathBuffer); if (CabinetOpen(&CabinetContext) == CAB_STATUS_SUCCESS) { DPRINT("Cabinet %S\n", PathBuffer); InfFileData = CabinetGetCabinetReservedArea(&CabinetContext, &InfFileSize); if (InfFileData == NULL) { CabinetCleanup(&CabinetContext); pSetupData->LastErrorNumber = ERROR_CABINET_SCRIPT; if (pSetupData->ErrorRoutine) pSetupData->ErrorRoutine(pSetupData, PathBuffer); return FALSE; } } else { DPRINT("Cannot open cabinet: %S.\n", PathBuffer); CabinetCleanup(&CabinetContext); pSetupData->LastErrorNumber = ERROR_CABINET_MISSING; if (pSetupData->ErrorRoutine) pSetupData->ErrorRoutine(pSetupData, PathBuffer); return FALSE; } InfHandle = INF_OpenBufferedFileA((PSTR)InfFileData, InfFileSize, NULL, INF_STYLE_WIN4, pSetupData->LanguageId, &ErrorLine); CabinetCleanup(&CabinetContext); #else { PWCHAR ptr; /* First find the filename */ ptr = wcsrchr(PathBuffer, L'\\'); if (!ptr) ptr = PathBuffer; /* Then find its extension */ ptr = wcsrchr(ptr, L'.'); if (!ptr) ptr = PathBuffer + wcslen(PathBuffer); /* Replace it by '.inf' */ wcscpy(ptr, L".inf"); InfHandle = SpInfOpenInfFile(PathBuffer, NULL, INF_STYLE_WIN4, pSetupData->LanguageId, &ErrorLine); } #endif if (InfHandle == INVALID_HANDLE_VALUE) { pSetupData->LastErrorNumber = ERROR_INVALID_CABINET_INF; if (pSetupData->ErrorRoutine) pSetupData->ErrorRoutine(pSetupData, PathBuffer); return FALSE; } if (!PrepareCopyInfFile(pSetupData, InfHandle, CabinetName)) { #if !(defined(__REACTOS__) && defined(USE_CABINET_INF)) SpInfCloseInfFile(InfHandle); #endif /* FIXME: show an error dialog */ return FALSE; } #if !(defined(__REACTOS__) && defined(USE_CABINET_INF)) SpInfCloseInfFile(InfHandle); #endif } while (SpInfFindNextLine(&CabinetsContext, &CabinetsContext)); return TRUE; } BOOLEAN DoFileCopy( IN OUT PUSETUP_DATA pSetupData, IN PSP_FILE_CALLBACK_W MsgHandler, IN PVOID Context OPTIONAL) { BOOLEAN Success; Success = SpFileQueueCommit(NULL, (HSPFILEQ)pSetupData->SetupFileQueue, MsgHandler, Context); SpFileQueueClose((HSPFILEQ)pSetupData->SetupFileQueue); pSetupData->SetupFileQueue = NULL; return Success; } /* EOF */