reactos/sdk/tools/cabman/cabinet.cxx
2020-09-20 17:28:02 +02:00

2994 lines
79 KiB
C++

/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS cabinet manager
* FILE: tools/cabman/cabinet.cxx
* PURPOSE: Cabinet routines
* PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
* Colin Finck <mail@colinfinck.de>
* NOTES: Define CAB_READ_ONLY for read only version
* REVISIONS:
* CSH 21/03-2001 Created
* CSH 15/08-2003 Made it portable
* CF 04/05-2007 Made it compatible with 64-bit operating systems
* TODO:
* - Checksum of datablocks should be calculated
* - EXTRACT.EXE complains if a disk is created manually
* - Folders that are created manually and span disks will result in a damaged cabinet
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if !defined(_WIN32)
# include <dirent.h>
# include <sys/stat.h>
# include <sys/types.h>
#endif
#include "cabinet.h"
#include "CCFDATAStorage.h"
#include "raw.h"
#include "mszip.h"
#ifndef CAB_READ_ONLY
#if 0
#if DBG
void DumpBuffer(void* Buffer, ULONG Size)
{
HANDLE FileHandle;
ULONG BytesWritten;
/* Create file, overwrite if it already exists */
FileHandle = CreateFile("dump.bin", // Create this file
GENERIC_WRITE, // Open for writing
0, // No sharing
NULL, // No security
CREATE_ALWAYS, // Create or overwrite
FILE_ATTRIBUTE_NORMAL, // Normal file
NULL); // No attribute template
if (FileHandle == INVALID_HANDLE_VALUE)
{
DPRINT(MID_TRACE, ("ERROR OPENING '%u'.\n", (UINT)GetLastError()));
return;
}
if (!WriteFile(FileHandle, Buffer, Size, &BytesWritten, NULL))
{
DPRINT(MID_TRACE, ("ERROR WRITING '%u'.\n", (UINT)GetLastError()));
}
CloseFile(FileHandle);
}
#endif /* DBG */
#endif
#endif /* CAB_READ_ONLY */
/* CCabinet */
CCabinet::CCabinet()
/*
* FUNCTION: Default constructor
*/
{
*CabinetName = '\0';
*CabinetPrev = '\0';
*DiskPrev = '\0';
*CabinetNext = '\0';
*DiskNext = '\0';
FileOpen = false;
CabinetReservedFileBuffer = NULL;
CabinetReservedFileSize = 0;
Codec = NULL;
CodecId = -1;
CodecSelected = false;
OutputBuffer = NULL;
InputBuffer = NULL;
MaxDiskSize = 0;
BlockIsSplit = false;
ScratchFile = NULL;
FolderUncompSize = 0;
BytesLeftInBlock = 0;
ReuseBlock = false;
CurrentDataNode = NULL;
}
CCabinet::~CCabinet()
/*
* FUNCTION: Default destructor
*/
{
if (CabinetReservedFileBuffer != NULL)
{
free(CabinetReservedFileBuffer);
CabinetReservedFileBuffer = NULL;
CabinetReservedFileSize = 0;
}
if (CodecSelected)
delete Codec;
}
bool CCabinet::IsSeparator(char Char)
/*
* FUNCTION: Determines if a character is a separator
* ARGUMENTS:
* Char = Character to check
* RETURNS:
* Whether it is a separator
*/
{
if ((Char == '\\') || (Char == '/'))
return true;
else
return false;
}
void CCabinet::ConvertPath(std::string& Path)
/*
* FUNCTION: Replaces \ or / with the one used by the host environment
* ARGUMENTS:
* Path = Pointer to string with pathname
* Allocate = Specifies whether to allocate memory for the new
* string or to change the existing buffer
* RETURNS:
* Pointer to new path
*/
{
for (size_t i = 0; i < Path.size(); ++i)
{
#if defined(_WIN32)
if (Path[i] == '/')
Path[i] = '\\';
#else
if (Path[i] == '\\')
Path[i] = '/';
#endif
}
}
std::string CCabinet::GetFileName(const std::string& Path)
/*
* FUNCTION: Returns a pointer to file name
* ARGUMENTS:
* Path = Pointer to string with pathname
* RETURNS:
* Pointer to filename
*/
{
ULONG i, j;
j = i = (Path[0] ? (Path[1] == ':' ? 2 : 0) : 0);
while (Path [i++])
if (IsSeparator(Path [i - 1]))
j = i;
return Path.c_str() + j;
}
void CCabinet::NormalizePath(std::string& Path)
/*
* FUNCTION: Normalizes a path
* ARGUMENTS:
* Path = string with pathname
*/
{
if (Path.length() > 0)
{
if (!IsSeparator(Path[Path.length() - 1]))
Path += DIR_SEPARATOR_CHAR;
}
}
char* CCabinet::GetCabinetName()
/*
* FUNCTION: Returns pointer to cabinet file name
* RETURNS:
* Pointer to string with name of cabinet
*/
{
return CabinetName;
}
void CCabinet::SetCabinetName(const char* FileName)
/*
* FUNCTION: Sets cabinet file name
* ARGUMENTS:
* FileName = Pointer to string with name of cabinet
*/
{
strcpy(CabinetName, FileName);
}
void CCabinet::SetDestinationPath(const char* DestinationPath)
/*
* FUNCTION: Sets destination path
* ARGUMENTS:
* DestinationPath = Pointer to string with name of destination path
*/
{
DestPath = DestinationPath;
ConvertPath(DestPath);
NormalizePath(DestPath);
}
ULONG CCabinet::AddSearchCriteria(const std::string& SearchCriteria, const std::string& TargetFolder)
/*
* FUNCTION: Adds a criteria to the search criteria list
* ARGUMENTS:
* SearchCriteria = String with the search criteria to add
* RETURNS:
* Status of operation
*/
{
PSEARCH_CRITERIA Criteria;
// Add the criteria to the list of search criteria
Criteria = new SEARCH_CRITERIA;
if(!Criteria)
{
DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
return CAB_STATUS_NOMEMORY;
}
CriteriaList.push_back(Criteria);
// Set the actual criteria string
Criteria->Search = SearchCriteria;
Criteria->TargetFolder = TargetFolder;
return CAB_STATUS_SUCCESS;
}
void CCabinet::DestroySearchCriteria()
/*
* FUNCTION: Destroys the list with the search criteria
*/
{
for (PSEARCH_CRITERIA Criteria : CriteriaList)
{
delete Criteria;
}
CriteriaList.clear();
}
bool CCabinet::HasSearchCriteria()
/*
* FUNCTION: Returns whether we have search criteria
* RETURNS:
* Whether we have search criteria or not.
*/
{
return !CriteriaList.empty();
}
std::string CCabinet::CreateCabFilename(PCFFILE_NODE Node)
{
std::string fname = GetFileName(Node->FileName);
if (!Node->TargetFolder.empty())
{
fname = Node->TargetFolder + fname;
}
return fname;
}
bool CCabinet::SetCompressionCodec(const char* CodecName)
/*
* FUNCTION: Selects the codec to use for compression
* ARGUMENTS:
* CodecName = Pointer to a string with the name of the codec
*/
{
if( !strcasecmp(CodecName, "raw") )
SelectCodec(CAB_CODEC_RAW);
else if( !strcasecmp(CodecName, "mszip") )
SelectCodec(CAB_CODEC_MSZIP);
else
{
printf("ERROR: Invalid codec specified!\n");
return false;
}
return true;
}
const char* CCabinet::GetDestinationPath()
/*
* FUNCTION: Returns destination path
* RETURNS:
* Pointer to string with name of destination path
*/
{
return DestPath.c_str();
}
bool CCabinet::SetCabinetReservedFile(const char* FileName)
/*
* FUNCTION: Sets cabinet reserved file
* ARGUMENTS:
* FileName = Pointer to string with name of cabinet reserved file
*/
{
FILE* FileHandle;
ULONG BytesRead;
std::string ConvertedFileName;
ConvertedFileName = FileName;
ConvertPath(ConvertedFileName);
FileHandle = fopen(ConvertedFileName.c_str(), "rb");
if (FileHandle == NULL)
{
DPRINT(MID_TRACE, ("Cannot open cabinet reserved file.\n"));
return false;
}
CabinetReservedFileSize = GetSizeOfFile(FileHandle);
if (CabinetReservedFileSize == (ULONG)-1)
{
DPRINT(MIN_TRACE, ("Cannot read from cabinet reserved file.\n"));
fclose(FileHandle);
return false;
}
if (CabinetReservedFileSize == 0)
{
fclose(FileHandle);
return false;
}
CabinetReservedFileBuffer = malloc(CabinetReservedFileSize);
if (!CabinetReservedFileBuffer)
{
fclose(FileHandle);
return false;
}
BytesRead = fread(CabinetReservedFileBuffer, 1, CabinetReservedFileSize, FileHandle);
if( BytesRead != CabinetReservedFileSize )
{
fclose(FileHandle);
return false;
}
fclose(FileHandle);
CabinetReservedFile = FileName;
return true;
}
ULONG CCabinet::GetCurrentDiskNumber()
/*
* FUNCTION: Returns current disk number
* RETURNS:
* Current disk number
*/
{
return CurrentDiskNumber;
}
ULONG CCabinet::Open()
/*
* FUNCTION: Opens a cabinet file
* RETURNS:
* Status of operation
*/
{
ULONG Status;
ULONG Index;
if (!FileOpen)
{
ULONG BytesRead;
ULONG Size;
OutputBuffer = malloc(CAB_BLOCKSIZE + 12); // This should be enough
if (!OutputBuffer)
return CAB_STATUS_NOMEMORY;
FileHandle = fopen(CabinetName, "rb");
if (FileHandle == NULL)
{
DPRINT(MID_TRACE, ("Cannot open file.\n"));
return CAB_STATUS_CANNOT_OPEN;
}
FileOpen = true;
/* Load CAB header */
if ((Status = ReadBlock(&CABHeader, sizeof(CFHEADER), &BytesRead))
!= CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
return CAB_STATUS_INVALID_CAB;
}
/* Check header */
if ((BytesRead != sizeof(CFHEADER)) ||
(CABHeader.Signature != CAB_SIGNATURE ) ||
(CABHeader.Version != CAB_VERSION ) ||
(CABHeader.FolderCount == 0 ) ||
(CABHeader.FileCount == 0 ) ||
(CABHeader.FileTableOffset < sizeof(CFHEADER)))
{
CloseCabinet();
DPRINT(MID_TRACE, ("File has invalid header.\n"));
return CAB_STATUS_INVALID_CAB;
}
Size = 0;
/* Read/skip any reserved bytes */
if (CABHeader.Flags & CAB_FLAG_RESERVE)
{
if ((Status = ReadBlock(&Size, sizeof(ULONG), &BytesRead))
!= CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
return CAB_STATUS_INVALID_CAB;
}
CabinetReserved = Size & 0xFFFF;
FolderReserved = (Size >> 16) & 0xFF;
DataReserved = (Size >> 24) & 0xFF;
if (fseek(FileHandle, CabinetReserved, SEEK_CUR) != 0)
{
DPRINT(MIN_TRACE, ("fseek() failed.\n"));
return CAB_STATUS_FAILURE;
}
}
if ((CABHeader.Flags & CAB_FLAG_HASPREV) > 0)
{
/* Read name of previous cabinet */
Status = ReadString(CabinetPrev, 256);
if (Status != CAB_STATUS_SUCCESS)
return Status;
/* Read label of previous disk */
Status = ReadString(DiskPrev, 256);
if (Status != CAB_STATUS_SUCCESS)
return Status;
}
else
{
strcpy(CabinetPrev, "");
strcpy(DiskPrev, "");
}
if ((CABHeader.Flags & CAB_FLAG_HASNEXT) > 0)
{
/* Read name of next cabinet */
Status = ReadString(CabinetNext, 256);
if (Status != CAB_STATUS_SUCCESS)
return Status;
/* Read label of next disk */
Status = ReadString(DiskNext, 256);
if (Status != CAB_STATUS_SUCCESS)
return Status;
}
else
{
strcpy(CabinetNext, "");
strcpy(DiskNext, "");
}
/* Read all folders */
for (Index = 0; Index < CABHeader.FolderCount; Index++)
{
PCFFOLDER_NODE FolderNode = NewFolderNode();
if (!FolderNode)
{
DPRINT(MIN_TRACE, ("Insufficient resources.\n"));
return CAB_STATUS_NOMEMORY;
}
if (Index == 0)
FolderNode->UncompOffset = FolderUncompSize;
FolderNode->Index = Index;
if ((Status = ReadBlock(&FolderNode->Folder,
sizeof(CFFOLDER), &BytesRead)) != CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
return CAB_STATUS_INVALID_CAB;
}
}
/* Read file entries */
Status = ReadFileTable();
if (Status != CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("ReadFileTable() failed (%u).\n", (UINT)Status));
return Status;
}
/* Read data blocks for all folders */
for (PCFFOLDER_NODE Node : FolderList)
{
Status = ReadDataBlocks(Node);
if (Status != CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("ReadDataBlocks() failed (%u).\n", (UINT)Status));
return Status;
}
}
}
return CAB_STATUS_SUCCESS;
}
void CCabinet::Close()
/*
* FUNCTION: Closes the cabinet file
*/
{
if (FileOpen)
{
fclose(FileHandle);
FileOpen = false;
}
}
ULONG CCabinet::FindFirst(PCAB_SEARCH Search)
/*
* FUNCTION: Finds the first file in the cabinet that matches a search criteria
* ARGUMENTS:
* Search = Pointer to search structure
* RETURNS:
* Status of operation
*/
{
RestartSearch = false;
Search->Next = FileList.begin();
return FindNext(Search);
}
ULONG CCabinet::FindNext(PCAB_SEARCH Search)
/*
* FUNCTION: Finds next file in the cabinet that matches a search criteria
* ARGUMENTS:
* Search = Pointer to search structure
* RETURNS:
* Status of operation
*/
{
bool bFound = false;
ULONG Status;
if (RestartSearch)
{
Search->Next = FileList.begin();
/* Skip split files already extracted */
while ((Search->Next != FileList.end()) &&
((*Search->Next)->File.FileControlID > CAB_FILE_MAX_FOLDER) &&
((*Search->Next)->File.FileOffset <= LastFileOffset))
{
DPRINT(MAX_TRACE, ("Skipping file (%s) FileOffset (0x%X) LastFileOffset (0x%X).\n",
(*Search->Next)->FileName.c_str(), (UINT)(*Search->Next)->File.FileOffset, (UINT)LastFileOffset));
Search->Next++;
}
RestartSearch = false;
}
/* Check each search criteria against each file */
while(Search->Next != FileList.end())
{
// Some features (like displaying cabinets) don't require search criteria, so we can just break here.
// If a feature requires it, handle this in the ParseCmdline() function in "main.cxx".
if (CriteriaList.empty())
break;
for (PSEARCH_CRITERIA Criteria : CriteriaList)
{
// FIXME: We could handle path\filename here
if (MatchFileNamePattern((*Search->Next)->FileName.c_str(), Criteria->Search.c_str()))
{
bFound = true;
break;
}
}
if(bFound)
break;
Search->Next++;
}
if (Search->Next == FileList.end())
{
if (strlen(DiskNext) > 0)
{
CloseCabinet();
SetCabinetName(CabinetNext);
OnDiskChange(CabinetNext, DiskNext);
Status = Open();
if (Status != CAB_STATUS_SUCCESS)
return Status;
Search->Next = FileList.begin();
if (Search->Next == FileList.end())
return CAB_STATUS_NOFILE;
}
else
return CAB_STATUS_NOFILE;
}
Search->File = &(*Search->Next)->File;
Search->FileName = (*Search->Next)->FileName;
Search->Next++;
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::ExtractFile(const char* FileName)
/*
* FUNCTION: Extracts a file from the cabinet
* ARGUMENTS:
* FileName = Pointer to buffer with name of file
* RETURNS
* Status of operation
*/
{
ULONG Size;
ULONG Offset;
ULONG BytesRead;
ULONG BytesToRead;
ULONG BytesWritten;
ULONG BytesSkipped;
ULONG BytesToWrite;
ULONG TotalBytesRead;
ULONG CurrentOffset;
PUCHAR Buffer;
PUCHAR CurrentBuffer;
FILE* DestFile;
PCFFILE_NODE File;
CFDATA CFData;
ULONG Status;
bool Skip;
#if defined(_WIN32)
FILETIME FileTime;
#endif
CHAR DestName[PATH_MAX];
CHAR TempName[PATH_MAX];
Status = LocateFile(FileName, &File);
if (Status != CAB_STATUS_SUCCESS)
{
DPRINT(MID_TRACE, ("Cannot locate file (%u).\n", (UINT)Status));
return Status;
}
LastFileOffset = File->File.FileOffset;
switch (CurrentFolderNode->Folder.CompressionType & CAB_COMP_MASK)
{
case CAB_COMP_NONE:
SelectCodec(CAB_CODEC_RAW);
break;
case CAB_COMP_MSZIP:
SelectCodec(CAB_CODEC_MSZIP);
break;
default:
return CAB_STATUS_UNSUPPCOMP;
}
DPRINT(MAX_TRACE, ("Extracting file at uncompressed offset (0x%X) Size (%u bytes) AO (0x%X) UO (0x%X).\n",
(UINT)File->File.FileOffset,
(UINT)File->File.FileSize,
(UINT)File->DataBlock->AbsoluteOffset,
(UINT)File->DataBlock->UncompOffset));
strcpy(DestName, DestPath.c_str());
strcat(DestName, FileName);
/* Create destination file, fail if it already exists */
DestFile = fopen(DestName, "rb");
if (DestFile != NULL)
{
fclose(DestFile);
/* If file exists, ask to overwrite file */
if (OnOverwrite(&File->File, FileName))
{
DestFile = fopen(DestName, "w+b");
if (DestFile == NULL)
return CAB_STATUS_CANNOT_CREATE;
}
else
return CAB_STATUS_FILE_EXISTS;
}
else
{
DestFile = fopen(DestName, "w+b");
if (DestFile == NULL)
return CAB_STATUS_CANNOT_CREATE;
}
#if defined(_WIN32)
if (!DosDateTimeToFileTime(File->File.FileDate, File->File.FileTime, &FileTime))
{
fclose(DestFile);
DPRINT(MIN_TRACE, ("DosDateTimeToFileTime() failed (%u).\n", (UINT)GetLastError()));
return CAB_STATUS_CANNOT_WRITE;
}
SetFileTime(DestFile, NULL, &FileTime, NULL);
#else
//DPRINT(MIN_TRACE, ("FIXME: DosDateTimeToFileTime\n"));
#endif
SetAttributesOnFile(DestName, File->File.Attributes);
Buffer = (PUCHAR)malloc(CAB_BLOCKSIZE + 12); // This should be enough
if (!Buffer)
{
fclose(DestFile);
DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
return CAB_STATUS_NOMEMORY;
}
/* Call OnExtract event handler */
OnExtract(&File->File, FileName);
/* Search to start of file */
if (fseek(FileHandle, (off_t)File->DataBlock->AbsoluteOffset, SEEK_SET) != 0)
{
DPRINT(MIN_TRACE, ("fseek() failed.\n"));
fclose(DestFile);
free(Buffer);
return CAB_STATUS_INVALID_CAB;
}
Size = File->File.FileSize;
Offset = File->File.FileOffset;
CurrentOffset = File->DataBlock->UncompOffset;
Skip = true;
ReuseBlock = (CurrentDataNode == File->DataBlock);
if (Size > 0)
{
do
{
DPRINT(MAX_TRACE, ("CO (0x%X) ReuseBlock (%u) Offset (0x%X) Size (%d) BytesLeftInBlock (%d)\n",
(UINT)File->DataBlock->UncompOffset, (UINT)ReuseBlock, (UINT)Offset, (UINT)Size,
(UINT)BytesLeftInBlock));
if (/*(CurrentDataNode != File->DataBlock) &&*/ (!ReuseBlock) || (BytesLeftInBlock <= 0))
{
DPRINT(MAX_TRACE, ("Filling buffer. ReuseBlock (%u)\n", (UINT)ReuseBlock));
CurrentBuffer = Buffer;
TotalBytesRead = 0;
do
{
DPRINT(MAX_TRACE, ("Size (%u bytes).\n", (UINT)Size));
if (((Status = ReadBlock(&CFData, sizeof(CFDATA), &BytesRead)) !=
CAB_STATUS_SUCCESS) || (BytesRead != sizeof(CFDATA)))
{
fclose(DestFile);
free(Buffer);
DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
return CAB_STATUS_INVALID_CAB;
}
DPRINT(MAX_TRACE, ("Data block: Checksum (0x%X) CompSize (%u bytes) UncompSize (%u bytes)\n",
(UINT)CFData.Checksum,
CFData.CompSize,
CFData.UncompSize));
ASSERT(CFData.CompSize <= CAB_BLOCKSIZE + 12);
BytesToRead = CFData.CompSize;
DPRINT(MAX_TRACE, ("Read: (0x%lX,0x%lX).\n",
(unsigned long)CurrentBuffer, (unsigned long)Buffer));
if (((Status = ReadBlock(CurrentBuffer, BytesToRead, &BytesRead)) !=
CAB_STATUS_SUCCESS) || (BytesToRead != BytesRead))
{
fclose(DestFile);
free(Buffer);
DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
return CAB_STATUS_INVALID_CAB;
}
/* FIXME: Does not work with files generated by makecab.exe */
/*
if (CFData.Checksum != 0)
{
ULONG Checksum = ComputeChecksum(CurrentBuffer, BytesRead, 0);
if (Checksum != CFData.Checksum)
{
CloseFile(DestFile);
free(Buffer);
DPRINT(MIN_TRACE, ("Bad checksum (is 0x%X, should be 0x%X).\n",
Checksum, CFData.Checksum));
return CAB_STATUS_INVALID_CAB;
}
}
*/
TotalBytesRead += BytesRead;
CurrentBuffer += BytesRead;
if (CFData.UncompSize == 0)
{
if (strlen(DiskNext) == 0)
{
fclose(DestFile);
free(Buffer);
return CAB_STATUS_NOFILE;
}
/* CloseCabinet() will destroy all file entries so in case
FileName refers to the FileName field of a CFFOLDER_NODE
structure, we have to save a copy of the filename */
strcpy(TempName, FileName);
CloseCabinet();
SetCabinetName(CabinetNext);
OnDiskChange(CabinetNext, DiskNext);
Status = Open();
if (Status != CAB_STATUS_SUCCESS)
{
fclose(DestFile);
free(Buffer);
return Status;
}
/* The first data block of the file will not be
found as it is located in the previous file */
Status = LocateFile(TempName, &File);
if (Status == CAB_STATUS_NOFILE)
{
DPRINT(MID_TRACE, ("Cannot locate file (%u).\n", (UINT)Status));
fclose(DestFile);
free(Buffer);
return Status;
}
/* The file is continued in the first data block in the folder */
File->DataBlock = CurrentFolderNode->DataList.front();
/* Search to start of file */
if (fseek(FileHandle, (off_t)File->DataBlock->AbsoluteOffset, SEEK_SET) != 0)
{
DPRINT(MIN_TRACE, ("fseek() failed.\n"));
fclose(DestFile);
free(Buffer);
return CAB_STATUS_INVALID_CAB;
}
DPRINT(MAX_TRACE, ("Continuing extraction of file at uncompressed offset (0x%X) Size (%u bytes) AO (0x%X) UO (0x%X).\n",
(UINT)File->File.FileOffset,
(UINT)File->File.FileSize,
(UINT)File->DataBlock->AbsoluteOffset,
(UINT)File->DataBlock->UncompOffset));
CurrentDataNode = File->DataBlock;
ReuseBlock = true;
RestartSearch = true;
}
} while (CFData.UncompSize == 0);
DPRINT(MAX_TRACE, ("TotalBytesRead (%u).\n", (UINT)TotalBytesRead));
Status = Codec->Uncompress(OutputBuffer, Buffer, TotalBytesRead, &BytesToWrite);
if (Status != CS_SUCCESS)
{
fclose(DestFile);
free(Buffer);
DPRINT(MID_TRACE, ("Cannot uncompress block.\n"));
if (Status == CS_NOMEMORY)
return CAB_STATUS_NOMEMORY;
return CAB_STATUS_INVALID_CAB;
}
if (BytesToWrite != CFData.UncompSize)
{
DPRINT(MID_TRACE, ("BytesToWrite (%u) != CFData.UncompSize (%d)\n",
(UINT)BytesToWrite, CFData.UncompSize));
fclose(DestFile);
free(Buffer);
return CAB_STATUS_INVALID_CAB;
}
BytesLeftInBlock = BytesToWrite;
}
else
{
DPRINT(MAX_TRACE, ("Using same buffer. ReuseBlock (%u)\n", (UINT)ReuseBlock));
BytesToWrite = BytesLeftInBlock;
DPRINT(MAX_TRACE, ("Seeking to absolute offset 0x%X.\n",
(UINT)(CurrentDataNode->AbsoluteOffset + sizeof(CFDATA) + CurrentDataNode->Data.CompSize)));
if (((Status = ReadBlock(&CFData, sizeof(CFDATA), &BytesRead)) !=
CAB_STATUS_SUCCESS) || (BytesRead != sizeof(CFDATA)))
{
fclose(DestFile);
free(Buffer);
DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
return CAB_STATUS_INVALID_CAB;
}
DPRINT(MAX_TRACE, ("CFData.CompSize 0x%X CFData.UncompSize 0x%X.\n",
CFData.CompSize, CFData.UncompSize));
/* Go to next data block */
if (fseek(FileHandle, (off_t)CurrentDataNode->AbsoluteOffset + sizeof(CFDATA) +
CurrentDataNode->Data.CompSize, SEEK_SET) != 0)
{
DPRINT(MIN_TRACE, ("fseek() failed.\n"));
fclose(DestFile);
free(Buffer);
return CAB_STATUS_INVALID_CAB;
}
ReuseBlock = false;
}
if (Skip)
BytesSkipped = (Offset - CurrentOffset);
else
BytesSkipped = 0;
BytesToWrite -= BytesSkipped;
if (Size < BytesToWrite)
BytesToWrite = Size;
DPRINT(MAX_TRACE, ("Offset (0x%X) CurrentOffset (0x%X) ToWrite (%u) Skipped (%u)(%u) Size (%u).\n",
(UINT)Offset,
(UINT)CurrentOffset,
(UINT)BytesToWrite,
(UINT)BytesSkipped, (UINT)Skip,
(UINT)Size));
BytesWritten = BytesToWrite;
if (fwrite((void*)((PUCHAR)OutputBuffer + BytesSkipped),
BytesToWrite, 1, DestFile) < 1)
{
fclose(DestFile);
free(Buffer);
DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
return CAB_STATUS_CANNOT_WRITE;
}
Size -= BytesToWrite;
CurrentOffset += BytesToWrite;
/* Don't skip any more bytes */
Skip = false;
} while (Size > 0);
}
fclose(DestFile);
free(Buffer);
return CAB_STATUS_SUCCESS;
}
bool CCabinet::IsCodecSelected()
/*
* FUNCTION: Returns the value of CodecSelected
* RETURNS:
* Whether a codec is selected
*/
{
return CodecSelected;
}
void CCabinet::SelectCodec(LONG Id)
/*
* FUNCTION: Selects codec engine to use
* ARGUMENTS:
* Id = Codec identifier
*/
{
if (CodecSelected)
{
if (Id == CodecId)
return;
CodecSelected = false;
delete Codec;
}
switch (Id)
{
case CAB_CODEC_RAW:
Codec = new CRawCodec();
break;
case CAB_CODEC_MSZIP:
Codec = new CMSZipCodec();
break;
default:
return;
}
CodecId = Id;
CodecSelected = true;
}
#ifndef CAB_READ_ONLY
/* CAB write methods */
ULONG CCabinet::NewCabinet()
/*
* FUNCTION: Creates a new cabinet
* RETURNS:
* Status of operation
*/
{
ULONG Status;
CurrentDiskNumber = 0;
OutputBuffer = malloc(CAB_BLOCKSIZE + 12); // This should be enough
InputBuffer = malloc(CAB_BLOCKSIZE + 12); // This should be enough
if ((!OutputBuffer) || (!InputBuffer))
{
DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
return CAB_STATUS_NOMEMORY;
}
CurrentIBuffer = InputBuffer;
CurrentIBufferSize = 0;
CABHeader.Signature = CAB_SIGNATURE;
CABHeader.Reserved1 = 0; // Not used
CABHeader.CabinetSize = 0; // Not yet known
CABHeader.Reserved2 = 0; // Not used
CABHeader.Reserved3 = 0; // Not used
CABHeader.Version = CAB_VERSION;
CABHeader.FolderCount = 0; // Not yet known
CABHeader.FileCount = 0; // Not yet known
CABHeader.Flags = 0; // Not yet known
// FIXME: Should be random
CABHeader.SetID = 0x534F;
CABHeader.CabinetNumber = 0;
TotalFolderSize = 0;
TotalFileSize = 0;
DiskSize = sizeof(CFHEADER);
InitCabinetHeader();
// NextFolderNumber is 0-based
NextFolderNumber = 0;
CurrentFolderNode = NULL;
Status = NewFolder();
if (Status != CAB_STATUS_SUCCESS)
return Status;
CurrentFolderNode->Folder.DataOffset = DiskSize - TotalHeaderSize;
ScratchFile = new CCFDATAStorage;
if (!ScratchFile)
{
DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
return CAB_STATUS_NOMEMORY;
}
Status = ScratchFile->Create();
CreateNewFolder = false;
CreateNewDisk = false;
PrevCabinetNumber = 0;
return Status;
}
ULONG CCabinet::NewDisk()
/*
* FUNCTION: Forces a new disk to be created
* RETURNS:
* Status of operation
*/
{
// NextFolderNumber is 0-based
NextFolderNumber = 1;
CreateNewDisk = false;
DiskSize = sizeof(CFHEADER) + TotalFolderSize + TotalFileSize;
InitCabinetHeader();
CurrentFolderNode->TotalFolderSize = 0;
CurrentFolderNode->Folder.DataBlockCount = 0;
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::NewFolder()
/*
* FUNCTION: Forces a new folder to be created
* RETURNS:
* Status of operation
*/
{
DPRINT(MAX_TRACE, ("Creating new folder.\n"));
CurrentFolderNode = NewFolderNode();
if (!CurrentFolderNode)
{
DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
return CAB_STATUS_NOMEMORY;
}
switch (CodecId) {
case CAB_CODEC_RAW:
CurrentFolderNode->Folder.CompressionType = CAB_COMP_NONE;
break;
case CAB_CODEC_MSZIP:
CurrentFolderNode->Folder.CompressionType = CAB_COMP_MSZIP;
break;
default:
return CAB_STATUS_UNSUPPCOMP;
}
/* FIXME: This won't work if no files are added to the new folder */
DiskSize += sizeof(CFFOLDER);
TotalFolderSize += sizeof(CFFOLDER);
NextFolderNumber++;
CABHeader.FolderCount++;
LastBlockStart = 0;
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::WriteFileToScratchStorage(PCFFILE_NODE FileNode)
/*
* FUNCTION: Writes a file to the scratch file
* ARGUMENTS:
* FileNode = Pointer to file node
* RETURNS:
* Status of operation
*/
{
ULONG BytesToRead;
ULONG BytesRead;
ULONG Status;
ULONG Size;
if (!ContinueFile)
{
/* Try to open file */
SourceFile = fopen(FileNode->FileName.c_str(), "rb");
if (SourceFile == NULL)
{
DPRINT(MID_TRACE, ("File not found (%s).\n", FileNode->FileNameOnDisk.c_str()));
return CAB_STATUS_NOFILE;
}
if (CreateNewFolder)
{
/* There is always a new folder after
a split file is completely stored */
Status = NewFolder();
if (Status != CAB_STATUS_SUCCESS)
return Status;
CreateNewFolder = false;
}
/* Call OnAdd event handler */
OnAdd(&FileNode->File, FileNode->FileName.c_str());
TotalBytesLeft = FileNode->File.FileSize;
FileNode->File.FileOffset = CurrentFolderNode->UncompOffset;
CurrentFolderNode->UncompOffset += TotalBytesLeft;
FileNode->File.FileControlID = (USHORT)(NextFolderNumber - 1);
CurrentFolderNode->Commit = true;
PrevCabinetNumber = CurrentDiskNumber;
Size = sizeof(CFFILE) + (ULONG)CreateCabFilename(FileNode).length() + 1;
CABHeader.FileTableOffset += Size;
TotalFileSize += Size;
DiskSize += Size;
}
FileNode->Commit = true;
if (TotalBytesLeft > 0)
{
do
{
if (TotalBytesLeft > (ULONG)CAB_BLOCKSIZE - CurrentIBufferSize)
BytesToRead = CAB_BLOCKSIZE - CurrentIBufferSize;
else
BytesToRead = TotalBytesLeft;
if ( (BytesRead = fread(CurrentIBuffer, 1, BytesToRead, SourceFile)) != BytesToRead )
{
DPRINT(MIN_TRACE, ("Cannot read from file. BytesToRead (%u) BytesRead (%u) CurrentIBufferSize (%u).\n",
(UINT)BytesToRead, (UINT)BytesRead, (UINT)CurrentIBufferSize));
return CAB_STATUS_INVALID_CAB;
}
CurrentIBuffer = (unsigned char*)CurrentIBuffer + BytesRead;
CurrentIBufferSize += (USHORT)BytesRead;
if (CurrentIBufferSize == CAB_BLOCKSIZE)
{
Status = WriteDataBlock();
if (Status != CAB_STATUS_SUCCESS)
return Status;
}
TotalBytesLeft -= BytesRead;
} while ((TotalBytesLeft > 0) && (!CreateNewDisk));
}
if (TotalBytesLeft == 0)
{
fclose(SourceFile);
FileNode->Delete = true;
if (FileNode->File.FileControlID > CAB_FILE_MAX_FOLDER)
{
FileNode->File.FileControlID = CAB_FILE_CONTINUED;
CurrentFolderNode->Delete = true;
if ((CurrentIBufferSize > 0) || (CurrentOBufferSize > 0))
{
Status = WriteDataBlock();
if (Status != CAB_STATUS_SUCCESS)
return Status;
}
CreateNewFolder = true;
}
}
else
{
if (FileNode->File.FileControlID <= CAB_FILE_MAX_FOLDER)
FileNode->File.FileControlID = CAB_FILE_SPLIT;
else
FileNode->File.FileControlID = CAB_FILE_PREV_NEXT;
}
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::WriteDisk(ULONG MoreDisks)
/*
* FUNCTION: Forces the current disk to be written
* ARGUMENTS:
* MoreDisks = true if there is one or more disks after this disk
* RETURNS:
* Status of operation
*/
{
ULONG Status;
ContinueFile = false;
for (auto it = FileList.begin(); it != FileList.end();)
{
Status = WriteFileToScratchStorage(*it);
if (Status != CAB_STATUS_SUCCESS)
return Status;
if (CreateNewDisk)
{
/* A data block could span more than two
disks if MaxDiskSize is very small */
while (CreateNewDisk)
{
DPRINT(MAX_TRACE, ("Creating new disk.\n"));
CommitDisk(true);
CloseDisk();
NewDisk();
ContinueFile = true;
CreateNewDisk = false;
DPRINT(MAX_TRACE, ("First on new disk. CurrentIBufferSize (%u) CurrentOBufferSize (%u).\n",
(UINT)CurrentIBufferSize, (UINT)CurrentOBufferSize));
if ((CurrentIBufferSize > 0) || (CurrentOBufferSize > 0))
{
Status = WriteDataBlock();
if (Status != CAB_STATUS_SUCCESS)
return Status;
}
}
}
else
{
ContinueFile = false;
it++;
}
}
if ((CurrentIBufferSize > 0) || (CurrentOBufferSize > 0))
{
/* A data block could span more than two
disks if MaxDiskSize is very small */
ASSERT(CreateNewDisk == false);
do
{
if (CreateNewDisk)
{
DPRINT(MID_TRACE, ("Creating new disk 2.\n"));
CommitDisk(true);
CloseDisk();
NewDisk();
CreateNewDisk = false;
}
if ((CurrentIBufferSize > 0) || (CurrentOBufferSize > 0))
{
Status = WriteDataBlock();
if (Status != CAB_STATUS_SUCCESS)
return Status;
}
} while (CreateNewDisk);
}
CommitDisk(MoreDisks);
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::CommitDisk(ULONG MoreDisks)
/*
* FUNCTION: Commits the current disk
* ARGUMENTS:
* MoreDisks = true if there is one or more disks after this disk
* RETURNS:
* Status of operation
*/
{
ULONG Status;
OnCabinetName(CurrentDiskNumber, CabinetName);
/* Create file, fail if it already exists */
FileHandle = fopen(CabinetName, "rb");
if (FileHandle != NULL)
{
fclose(FileHandle);
/* If file exists, ask to overwrite file */
if (OnOverwrite(NULL, CabinetName))
{
FileHandle = fopen(CabinetName, "w+b");
if (FileHandle == NULL)
return CAB_STATUS_CANNOT_CREATE;
}
else
return CAB_STATUS_FILE_EXISTS;
}
else
{
FileHandle = fopen(CabinetName, "w+b");
if (FileHandle == NULL)
return CAB_STATUS_CANNOT_CREATE;
}
WriteCabinetHeader(MoreDisks != 0);
Status = WriteFolderEntries();
if (Status != CAB_STATUS_SUCCESS)
return Status;
/* Write file entries */
WriteFileEntries();
/* Write data blocks */
for (PCFFOLDER_NODE FolderNode : FolderList)
{
if (FolderNode->Commit)
{
Status = CommitDataBlocks(FolderNode);
if (Status != CAB_STATUS_SUCCESS)
return Status;
/* Remove data blocks for folder */
DestroyDataNodes(FolderNode);
}
}
fclose(FileHandle);
ScratchFile->Truncate();
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::CloseDisk()
/*
* FUNCTION: Closes the current disk
* RETURNS:
* Status of operation
*/
{
DestroyDeletedFileNodes();
/* Destroy folder nodes that are completely stored */
DestroyDeletedFolderNodes();
CurrentDiskNumber++;
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::CloseCabinet()
/*
* FUNCTION: Closes the current cabinet
* RETURNS:
* Status of operation
*/
{
ULONG Status;
DestroyFileNodes();
DestroyFolderNodes();
if (InputBuffer)
{
free(InputBuffer);
InputBuffer = NULL;
}
if (OutputBuffer)
{
free(OutputBuffer);
OutputBuffer = NULL;
}
Close();
if (ScratchFile)
{
Status = ScratchFile->Destroy();
delete ScratchFile;
return Status;
}
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::AddFile(const std::string& FileName, const std::string& TargetFolder)
/*
* FUNCTION: Adds a file to the current disk
* ARGUMENTS:
* FileName = Pointer to string with file name (full path)
* RETURNS:
* Status of operation
*/
{
FILE* SrcFile;
PCFFILE_NODE FileNode;
std::string NewFileName;
NewFileName = FileName;
ConvertPath(NewFileName);
/* Try to open file */
SrcFile = fopen(NewFileName.c_str(), "rb");
if (SrcFile == NULL)
{
DPRINT(MID_TRACE, ("File not found (%s).\n", NewFileName.c_str()));
return CAB_STATUS_CANNOT_OPEN;
}
FileNode = NewFileNode();
if (!FileNode)
{
DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
fclose(SrcFile);
return CAB_STATUS_NOMEMORY;
}
FileNode->FolderNode = CurrentFolderNode;
FileNode->FileName = NewFileName;
FileNode->TargetFolder = TargetFolder;
if (FileNode->TargetFolder.length() > 0 && FileNode->TargetFolder[FileNode->TargetFolder.length() - 1] != '\\')
FileNode->TargetFolder += '\\';
/* FIXME: Check for and handle large files (>= 2GB) */
FileNode->File.FileSize = GetSizeOfFile(SrcFile);
if (FileNode->File.FileSize == (ULONG)-1)
{
DPRINT(MIN_TRACE, ("Cannot read from file.\n"));
fclose(SrcFile);
return CAB_STATUS_CANNOT_READ;
}
if (GetFileTimes(SrcFile, FileNode) != CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("Cannot read file times.\n"));
fclose(SrcFile);
return CAB_STATUS_CANNOT_READ;
}
if (GetAttributesOnFile(FileNode) != CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("Cannot read file attributes.\n"));
fclose(SrcFile);
return CAB_STATUS_CANNOT_READ;
}
fclose(SrcFile);
return CAB_STATUS_SUCCESS;
}
bool CCabinet::CreateSimpleCabinet()
/*
* FUNCTION: Create a simple cabinet based on the files in the criteria list
*/
{
bool bRet = false;
ULONG Status;
#if defined(_WIN32)
HANDLE hFind;
WIN32_FIND_DATA FindFileData;
#else
DIR* dirp;
struct dirent* dp;
struct stat stbuf;
#endif
// Initialize a new cabinet
Status = NewCabinet();
if (Status != CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("Cannot create cabinet (%u).\n", (UINT)Status));
goto cleanup2;
}
// Add each file in the criteria list
for (PSEARCH_CRITERIA Criteria : CriteriaList)
{
// Store the file path with a trailing slash in szFilePath
std::string szSearchPath = Criteria->Search;
ConvertPath(szSearchPath);
auto sep = szSearchPath.find_last_of(DIR_SEPARATOR_CHAR);
std::string szFilePath;
std::string pszFile;
if (sep != std::string::npos)
{
pszFile = szSearchPath.substr(sep + 1); // We want the filename, not the dir separator!
szFilePath = szSearchPath.substr(0, sep + 1);
}
else
{
pszFile = Criteria->Search;
#if !defined(_WIN32)
// needed for opendir()
szFilePath = "./";
#endif
}
#if defined(_WIN32)
// Windows: Use the easy FindFirstFile/FindNextFile API for getting all files and checking them against the pattern
hFind = FindFirstFile(Criteria->Search.c_str(), &FindFileData);
// Don't stop if a search criteria is not found
if(hFind == INVALID_HANDLE_VALUE && GetLastError() != ERROR_FILE_NOT_FOUND)
{
DPRINT(MIN_TRACE, ("FindFirstFile failed, Criteria: %s, error code is %u\n", Criteria->Search.c_str(), (UINT)GetLastError()));
goto cleanup;
}
do
{
if(!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
std::string szFile = szFilePath;
szFile += FindFileData.cFileName;
Status = AddFile(szFile, Criteria->TargetFolder);
if(Status != CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("Cannot add file to cabinet (%u).\n", (UINT)Status));
FindClose(hFind);
goto cleanup;
}
}
}
while(FindNextFile(hFind, &FindFileData));
FindClose(hFind);
#else
// Unix: Use opendir/readdir to loop through all entries, stat to check if it's a file and MatchFileNamePattern to match the file against the pattern
dirp = opendir(szFilePath.c_str());
if(dirp)
{
while( (dp = readdir(dirp)) )
{
std::string szFile = szFilePath;
szFile += dp->d_name;
if(stat(szFile.c_str(), &stbuf) == 0)
{
if(stbuf.st_mode != S_IFDIR)
{
if(MatchFileNamePattern(dp->d_name, pszFile.c_str()))
{
Status = AddFile(szFile, Criteria->TargetFolder);
if(Status != CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("Cannot add file to cabinet (%u).\n", (UINT)Status));
goto cleanup;
}
}
}
}
else
{
DPRINT(MIN_TRACE, ("stat failed, error code is %i\n", errno));
goto cleanup;
}
}
closedir(dirp);
}
#endif
}
Status = WriteDisk(false);
if (Status == CAB_STATUS_SUCCESS)
Status = CloseDisk();
if (Status != CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("Cannot write disk (%u).\n", (UINT)Status));
goto cleanup;
}
cleanup:
CloseCabinet();
bRet = true;
cleanup2:
DestroySearchCriteria();
return bRet;
}
void CCabinet::SetMaxDiskSize(ULONG Size)
/*
* FUNCTION: Sets the maximum size of the current disk
* ARGUMENTS:
* Size = Maximum size of current disk (0 means no maximum size)
*/
{
MaxDiskSize = Size;
}
#endif /* CAB_READ_ONLY */
/* Default event handlers */
bool CCabinet::OnOverwrite(PCFFILE File,
const char* FileName)
/*
* FUNCTION: Called when extracting a file and it already exists
* ARGUMENTS:
* File = Pointer to CFFILE for file being extracted
* FileName = Pointer to buffer with name of file (full path)
* RETURNS
* true if the file should be overwritten, false if not
*/
{
return false;
}
void CCabinet::OnExtract(PCFFILE File,
const char* FileName)
/*
* FUNCTION: Called just before extracting a file
* ARGUMENTS:
* File = Pointer to CFFILE for file being extracted
* FileName = Pointer to buffer with name of file (full path)
*/
{
}
void CCabinet::OnDiskChange(const char* CabinetName,
const char* DiskLabel)
/*
* FUNCTION: Called when a new disk is to be processed
* ARGUMENTS:
* CabinetName = Pointer to buffer with name of cabinet
* DiskLabel = Pointer to buffer with label of disk
*/
{
}
void CCabinet::OnVerboseMessage(const char* Message)
{
}
#ifndef CAB_READ_ONLY
void CCabinet::OnAdd(PCFFILE File,
const char* FileName)
/*
* FUNCTION: Called just before adding a file to a cabinet
* ARGUMENTS:
* File = Pointer to CFFILE for file being added
* FileName = Pointer to buffer with name of file (full path)
*/
{
}
bool CCabinet::OnDiskLabel(ULONG Number, char* Label)
/*
* FUNCTION: Called when a disk needs a label
* ARGUMENTS:
* Number = Cabinet number that needs a label
* Label = Pointer to buffer to place label of disk
* RETURNS:
* true if a disk label was returned, false if not
*/
{
return false;
}
bool CCabinet::OnCabinetName(ULONG Number, char* Name)
/*
* FUNCTION: Called when a cabinet needs a name
* ARGUMENTS:
* Number = Disk number that needs a name
* Name = Pointer to buffer to place name of cabinet
* RETURNS:
* true if a cabinet name was returned, false if not
*/
{
return false;
}
#endif /* CAB_READ_ONLY */
PCFFOLDER_NODE CCabinet::LocateFolderNode(ULONG Index)
/*
* FUNCTION: Locates a folder node
* ARGUMENTS:
* Index = Folder index
* RETURNS:
* Pointer to folder node or NULL if the folder node was not found
*/
{
switch (Index)
{
case CAB_FILE_SPLIT:
return FolderList.back();
case CAB_FILE_CONTINUED:
case CAB_FILE_PREV_NEXT:
return FolderList.front();
}
for (PCFFOLDER_NODE Node : FolderList)
{
if (Node->Index == Index)
return Node;
}
return NULL;
}
ULONG CCabinet::GetAbsoluteOffset(PCFFILE_NODE File)
/*
* FUNCTION: Returns the absolute offset of a file
* ARGUMENTS:
* File = Pointer to CFFILE_NODE structure for file
* RETURNS:
* Status of operation
*/
{
DPRINT(MAX_TRACE, ("FileName '%s' FileOffset (0x%X) FileSize (%u).\n",
File->FileName.c_str(),
(UINT)File->File.FileOffset,
(UINT)File->File.FileSize));
for (PCFDATA_NODE Node : CurrentFolderNode->DataList)
{
DPRINT(MAX_TRACE, ("GetAbsoluteOffset(): Comparing (0x%X, 0x%X) (%u).\n",
(UINT)Node->UncompOffset,
(UINT)(Node->UncompOffset + Node->Data.UncompSize),
(UINT)Node->Data.UncompSize));
/* Node->Data.UncompSize will be 0 if the block is split
(ie. it is the last block in this cabinet) */
if ((Node->Data.UncompSize == 0) ||
((File->File.FileOffset >= Node->UncompOffset) &&
(File->File.FileOffset < Node->UncompOffset +
Node->Data.UncompSize)))
{
File->DataBlock = Node;
return CAB_STATUS_SUCCESS;
}
}
return CAB_STATUS_INVALID_CAB;
}
ULONG CCabinet::LocateFile(const char* FileName,
PCFFILE_NODE *File)
/*
* FUNCTION: Locates a file in the cabinet
* ARGUMENTS:
* FileName = Pointer to string with name of file to locate
* File = Address of pointer to CFFILE_NODE structure to fill
* RETURNS:
* Status of operation
* NOTES:
* Current folder is set to the folder of the file
*/
{
ULONG Status;
DPRINT(MAX_TRACE, ("FileName '%s'\n", FileName));
for (PCFFILE_NODE Node : FileList)
{
// FIXME: We could handle path\filename here
if (strcasecmp(FileName, Node->FileName.c_str()) == 0)
{
CurrentFolderNode = LocateFolderNode(Node->File.FileControlID);
if (!CurrentFolderNode)
{
DPRINT(MID_TRACE, ("Folder with index number (%u) not found.\n",
Node->File.FileControlID));
return CAB_STATUS_INVALID_CAB;
}
if (Node->DataBlock == NULL)
Status = GetAbsoluteOffset(Node);
else
Status = CAB_STATUS_SUCCESS;
*File = Node;
return Status;
}
}
return CAB_STATUS_NOFILE;
}
ULONG CCabinet::ReadString(char* String, LONG MaxLength)
/*
* FUNCTION: Reads a NULL-terminated string from the cabinet
* ARGUMENTS:
* String = Pointer to buffer to place string
* MaxLength = Maximum length of string
* RETURNS:
* Status of operation
*/
{
ULONG BytesRead;
ULONG Status;
LONG Size;
bool Found;
Found = false;
Status = ReadBlock(String, MaxLength, &BytesRead);
if (Status != CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
return CAB_STATUS_INVALID_CAB;
}
// Find the terminating NULL character
for (Size = 0; Size < MaxLength; Size++)
{
if (String[Size] == '\0')
{
Found = true;
break;
}
}
if (!Found)
{
DPRINT(MIN_TRACE, ("Filename in the cabinet file is too long.\n"));
return CAB_STATUS_INVALID_CAB;
}
// Compute the offset of the next CFFILE.
// We have to subtract from the current offset here, because we read MaxLength characters above and most-probably the file name isn't MaxLength characters long.
// + 1 to skip the terminating NULL character as well.
Size = -(MaxLength - Size) + 1;
if (fseek(FileHandle, (off_t)Size, SEEK_CUR) != 0)
{
DPRINT(MIN_TRACE, ("fseek() failed.\n"));
return CAB_STATUS_INVALID_CAB;
}
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::ReadFileTable()
/*
* FUNCTION: Reads the file table from the cabinet file
* RETURNS:
* Status of operation
*/
{
ULONG i;
ULONG Status;
ULONG BytesRead;
PCFFILE_NODE File;
DPRINT(MAX_TRACE, ("Reading file table at absolute offset (0x%X).\n",
(UINT)CABHeader.FileTableOffset));
/* Seek to file table */
if (fseek(FileHandle, (off_t)CABHeader.FileTableOffset, SEEK_SET) != 0)
{
DPRINT(MIN_TRACE, ("fseek() failed.\n"));
return CAB_STATUS_INVALID_CAB;
}
for (i = 0; i < CABHeader.FileCount; i++)
{
File = NewFileNode();
if (!File)
{
DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
return CAB_STATUS_NOMEMORY;
}
if ((Status = ReadBlock(&File->File, sizeof(CFFILE),
&BytesRead)) != CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
return CAB_STATUS_INVALID_CAB;
}
/* Read file name */
char Buf[PATH_MAX];
Status = ReadString(Buf, PATH_MAX);
if (Status != CAB_STATUS_SUCCESS)
return Status;
// FIXME: We could split up folder\file.txt here
File->FileName = Buf;
DPRINT(MAX_TRACE, ("Found file '%s' at uncompressed offset (0x%X). Size (%u bytes) ControlId (0x%X).\n",
File->FileName.c_str(),
(UINT)File->File.FileOffset,
(UINT)File->File.FileSize,
File->File.FileControlID));
}
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::ReadDataBlocks(PCFFOLDER_NODE FolderNode)
/*
* FUNCTION: Reads all CFDATA blocks for a folder from the cabinet file
* ARGUMENTS:
* FolderNode = Pointer to CFFOLDER_NODE structure for folder
* RETURNS:
* Status of operation
*/
{
ULONG AbsoluteOffset;
ULONG UncompOffset;
PCFDATA_NODE Node;
ULONG BytesRead;
ULONG Status;
ULONG i;
DPRINT(MAX_TRACE, ("Reading data blocks for folder (%u) at absolute offset (0x%X).\n",
(UINT)FolderNode->Index, (UINT)FolderNode->Folder.DataOffset));
AbsoluteOffset = FolderNode->Folder.DataOffset;
UncompOffset = FolderNode->UncompOffset;
for (i = 0; i < FolderNode->Folder.DataBlockCount; i++)
{
Node = NewDataNode(FolderNode);
if (!Node)
{
DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
return CAB_STATUS_NOMEMORY;
}
/* Seek to data block */
if (fseek(FileHandle, (off_t)AbsoluteOffset, SEEK_SET) != 0)
{
DPRINT(MIN_TRACE, ("fseek() failed.\n"));
return CAB_STATUS_INVALID_CAB;
}
if ((Status = ReadBlock(&Node->Data, sizeof(CFDATA),
&BytesRead)) != CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
return CAB_STATUS_INVALID_CAB;
}
DPRINT(MAX_TRACE, ("AbsOffset (0x%X) UncompOffset (0x%X) Checksum (0x%X) CompSize (%u) UncompSize (%u).\n",
(UINT)AbsoluteOffset,
(UINT)UncompOffset,
(UINT)Node->Data.Checksum,
Node->Data.CompSize,
Node->Data.UncompSize));
Node->AbsoluteOffset = AbsoluteOffset;
Node->UncompOffset = UncompOffset;
AbsoluteOffset += sizeof(CFDATA) + Node->Data.CompSize;
UncompOffset += Node->Data.UncompSize;
}
FolderUncompSize = UncompOffset;
return CAB_STATUS_SUCCESS;
}
PCFFOLDER_NODE CCabinet::NewFolderNode()
/*
* FUNCTION: Creates a new folder node
* RETURNS:
* Pointer to node if there was enough free memory available, otherwise NULL
*/
{
PCFFOLDER_NODE Node;
Node = new CFFOLDER_NODE;
if (!Node)
return NULL;
Node->Folder.CompressionType = CAB_COMP_NONE;
FolderList.push_back(Node);
return Node;
}
PCFFILE_NODE CCabinet::NewFileNode()
/*
* FUNCTION: Creates a new file node
* ARGUMENTS:
* FolderNode = Pointer to folder node to bind file to
* RETURNS:
* Pointer to node if there was enough free memory available, otherwise NULL
*/
{
PCFFILE_NODE Node;
Node = new CFFILE_NODE;
if (!Node)
return NULL;
FileList.push_back(Node);
return Node;
}
PCFDATA_NODE CCabinet::NewDataNode(PCFFOLDER_NODE FolderNode)
/*
* FUNCTION: Creates a new data block node
* ARGUMENTS:
* FolderNode = Pointer to folder node to bind data block to
* RETURNS:
* Pointer to node if there was enough free memory available, otherwise NULL
*/
{
PCFDATA_NODE Node;
Node = new CFDATA_NODE;
if (!Node)
return NULL;
FolderNode->DataList.push_back(Node);
return Node;
}
void CCabinet::DestroyDataNodes(PCFFOLDER_NODE FolderNode)
/*
* FUNCTION: Destroys data block nodes bound to a folder node
* ARGUMENTS:
* FolderNode = Pointer to folder node
*/
{
for (PCFDATA_NODE Node : FolderNode->DataList)
{
delete Node;
}
FolderNode->DataList.clear();
}
void CCabinet::DestroyFileNodes()
/*
* FUNCTION: Destroys file nodes
*/
{
for (PCFFILE_NODE Node : FileList)
{
delete Node;
}
FileList.clear();
}
void CCabinet::DestroyDeletedFileNodes()
/*
* FUNCTION: Destroys file nodes that are marked for deletion
*/
{
for (auto it = FileList.begin(); it != FileList.end(); )
{
PCFFILE_NODE CurNode = *it;
if (CurNode->Delete)
{
it = FileList.erase(it);
DPRINT(MAX_TRACE, ("Deleting file node: '%s'\n", CurNode->FileName.c_str()));
TotalFileSize -= (sizeof(CFFILE) + (ULONG)CreateCabFilename(CurNode).length() + 1);
delete CurNode;
}
else
{
it++;
}
}
}
void CCabinet::DestroyFolderNodes()
/*
* FUNCTION: Destroys folder nodes
*/
{
for (PCFFOLDER_NODE Node : FolderList)
{
DestroyDataNodes(Node);
delete Node;
}
FolderList.clear();
}
void CCabinet::DestroyDeletedFolderNodes()
/*
* FUNCTION: Destroys folder nodes that are marked for deletion
*/
{
for (auto it = FolderList.begin(); it != FolderList.end();)
{
PCFFOLDER_NODE CurNode = *it;
if (CurNode->Delete)
{
it = FolderList.erase(it);
DestroyDataNodes(CurNode);
delete CurNode;
TotalFolderSize -= sizeof(CFFOLDER);
}
else
{
it++;
}
}
}
ULONG CCabinet::ComputeChecksum(void* Buffer,
ULONG Size,
ULONG Seed)
/*
* FUNCTION: Computes checksum for data block
* ARGUMENTS:
* Buffer = Pointer to data buffer
* Size = Length of data buffer
* Seed = Previously computed checksum
* RETURNS:
* Checksum of buffer
*/
{
int UlongCount; // Number of ULONGs in block
ULONG Checksum; // Checksum accumulator
unsigned char* pb;
ULONG ul;
/* FIXME: Doesn't seem to be correct. EXTRACT.EXE
won't accept checksums computed by this routine */
DPRINT(MIN_TRACE, ("Checksumming buffer (0x%p) Size (%u)\n", Buffer, (UINT)Size));
UlongCount = Size / 4; // Number of ULONGs
Checksum = Seed; // Init checksum
pb = (unsigned char*)Buffer; // Start at front of data block
/* Checksum integral multiple of ULONGs */
while (UlongCount-- > 0)
{
/* NOTE: Build ULONG in big/little-endian independent manner */
ul = *pb++; // Get low-order byte
ul |= (((ULONG)(*pb++)) << 8); // Add 2nd byte
ul |= (((ULONG)(*pb++)) << 16); // Add 3nd byte
ul |= (((ULONG)(*pb++)) << 24); // Add 4th byte
Checksum ^= ul; // Update checksum
}
/* Checksum remainder bytes */
ul = 0;
switch (Size % 4)
{
case 3:
ul |= (((ULONG)(*pb++)) << 16); // Add 3rd byte
case 2:
ul |= (((ULONG)(*pb++)) << 8); // Add 2nd byte
case 1:
ul |= *pb++; // Get low-order byte
default:
break;
}
Checksum ^= ul; // Update checksum
/* Return computed checksum */
return Checksum;
}
ULONG CCabinet::ReadBlock(void* Buffer,
ULONG Size,
PULONG BytesRead)
/*
* FUNCTION: Read a block of data from file
* ARGUMENTS:
* Buffer = Pointer to data buffer
* Size = Length of data buffer
* BytesRead = Pointer to ULONG that on return will contain
* number of bytes read
* RETURNS:
* Status of operation
*/
{
*BytesRead = fread(Buffer, 1, Size, FileHandle);
if ( *BytesRead != Size )
return CAB_STATUS_INVALID_CAB;
return CAB_STATUS_SUCCESS;
}
bool CCabinet::MatchFileNamePattern(const char* FileName, const char* Pattern)
/*
* FUNCTION: Matches a wildcard character pattern against a file
* ARGUMENTS:
* FileName = The file name to check
* Pattern = The pattern
* RETURNS:
* Whether the pattern matches the file
*
* COPYRIGHT:
* This function is based on Busybox code, Copyright (C) 1998 by Erik Andersen, released under GPL2 or any later version.
* Adapted from code written by Ingo Wilken.
* Original location: http://www.busybox.net/cgi-bin/viewcvs.cgi/trunk/busybox/utility.c?rev=5&view=markup
*/
{
const char* retryPattern = NULL;
const char* retryFileName = NULL;
char ch;
while (*FileName || *Pattern)
{
ch = *Pattern++;
switch (ch)
{
case '*':
retryPattern = Pattern;
retryFileName = FileName;
break;
case '?':
if (*FileName++ == '\0')
return false;
break;
default:
if (*FileName == ch)
{
if (*FileName)
FileName++;
break;
}
if (*FileName)
{
Pattern = retryPattern;
FileName = ++retryFileName;
break;
}
return false;
}
if (!Pattern)
return false;
}
return true;
}
#ifndef CAB_READ_ONLY
ULONG CCabinet::InitCabinetHeader()
/*
* FUNCTION: Initializes cabinet header and optional fields
* RETURNS:
* Status of operation
*/
{
ULONG TotalSize;
ULONG Size;
CABHeader.FileTableOffset = 0; // Not known yet
CABHeader.FolderCount = 0; // Not known yet
CABHeader.FileCount = 0; // Not known yet
CABHeader.Flags = 0; // Not known yet
CABHeader.CabinetNumber = (USHORT)CurrentDiskNumber;
if ((CurrentDiskNumber > 0) && (OnCabinetName(PrevCabinetNumber, CabinetPrev)))
{
CABHeader.Flags |= CAB_FLAG_HASPREV;
if (!OnDiskLabel(PrevCabinetNumber, DiskPrev))
strcpy(CabinetPrev, "");
}
if (OnCabinetName(CurrentDiskNumber + 1, CabinetNext))
{
CABHeader.Flags |= CAB_FLAG_HASNEXT;
if (!OnDiskLabel(CurrentDiskNumber + 1, DiskNext))
strcpy(DiskNext, "");
}
TotalSize = 0;
if ((CABHeader.Flags & CAB_FLAG_HASPREV) > 0)
{
DPRINT(MAX_TRACE, ("CabinetPrev '%s'.\n", CabinetPrev));
/* Calculate size of name of previous cabinet */
TotalSize += (ULONG)strlen(CabinetPrev) + 1;
/* Calculate size of label of previous disk */
TotalSize += (ULONG)strlen(DiskPrev) + 1;
}
if ((CABHeader.Flags & CAB_FLAG_HASNEXT) > 0)
{
DPRINT(MAX_TRACE, ("CabinetNext '%s'.\n", CabinetNext));
/* Calculate size of name of next cabinet */
Size = (ULONG)strlen(CabinetNext) + 1;
TotalSize += Size;
NextFieldsSize = Size;
/* Calculate size of label of next disk */
Size = (ULONG)strlen(DiskNext) + 1;
TotalSize += Size;
NextFieldsSize += Size;
}
else
NextFieldsSize = 0;
/* Add cabinet reserved area size if present */
if (CabinetReservedFileSize > 0)
{
CABHeader.Flags |= CAB_FLAG_RESERVE;
TotalSize += CabinetReservedFileSize;
TotalSize += sizeof(ULONG); /* For CabinetResSize, FolderResSize, and FileResSize fields */
}
DiskSize += TotalSize;
TotalHeaderSize = sizeof(CFHEADER) + TotalSize;
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::WriteCabinetHeader(bool MoreDisks)
/*
* FUNCTION: Writes the cabinet header and optional fields
* ARGUMENTS:
* MoreDisks = true if next cabinet name should be included
* RETURNS:
* Status of operation
*/
{
ULONG BytesWritten;
ULONG Size;
if (MoreDisks)
{
CABHeader.Flags |= CAB_FLAG_HASNEXT;
Size = TotalHeaderSize;
}
else
{
CABHeader.Flags &= ~CAB_FLAG_HASNEXT;
DiskSize -= NextFieldsSize;
Size = TotalHeaderSize - NextFieldsSize;
}
/* Set absolute folder offsets */
BytesWritten = Size + TotalFolderSize + TotalFileSize;
CABHeader.FolderCount = 0;
for (PCFFOLDER_NODE FolderNode : FolderList)
{
FolderNode->Folder.DataOffset = BytesWritten;
BytesWritten += FolderNode->TotalFolderSize;
CABHeader.FolderCount++;
}
/* Set absolute offset of file table */
CABHeader.FileTableOffset = Size + TotalFolderSize;
/* Count number of files to be committed */
CABHeader.FileCount = 0;
for (PCFFILE_NODE FileNode : FileList)
{
if (FileNode->Commit)
CABHeader.FileCount++;
}
CABHeader.CabinetSize = DiskSize;
/* Write header */
if (fwrite(&CABHeader, sizeof(CFHEADER), 1, FileHandle) < 1)
{
DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
return CAB_STATUS_CANNOT_WRITE;
}
/* Write per-cabinet reserved area if present */
if (CABHeader.Flags & CAB_FLAG_RESERVE)
{
ULONG ReservedSize;
ReservedSize = CabinetReservedFileSize & 0xffff;
ReservedSize |= (0 << 16); /* Folder reserved area size */
ReservedSize |= (0 << 24); /* Folder reserved area size */
BytesWritten = sizeof(ULONG);
if (fwrite(&ReservedSize, sizeof(ULONG), 1, FileHandle) < 1)
{
DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
return CAB_STATUS_CANNOT_WRITE;
}
BytesWritten = CabinetReservedFileSize;
if (fwrite(CabinetReservedFileBuffer, CabinetReservedFileSize, 1, FileHandle) < 1)
{
DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
return CAB_STATUS_CANNOT_WRITE;
}
}
if ((CABHeader.Flags & CAB_FLAG_HASPREV) > 0)
{
DPRINT(MAX_TRACE, ("CabinetPrev '%s'.\n", CabinetPrev));
/* Write name of previous cabinet */
Size = (ULONG)strlen(CabinetPrev) + 1;
BytesWritten = Size;
if (fwrite(CabinetPrev, Size, 1, FileHandle) < 1)
{
DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
return CAB_STATUS_CANNOT_WRITE;
}
DPRINT(MAX_TRACE, ("DiskPrev '%s'.\n", DiskPrev));
/* Write label of previous disk */
Size = (ULONG)strlen(DiskPrev) + 1;
BytesWritten = Size;
if (fwrite(DiskPrev, Size, 1, FileHandle) < 1)
{
DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
return CAB_STATUS_CANNOT_WRITE;
}
}
if ((CABHeader.Flags & CAB_FLAG_HASNEXT) > 0)
{
DPRINT(MAX_TRACE, ("CabinetNext '%s'.\n", CabinetNext));
/* Write name of next cabinet */
Size = (ULONG)strlen(CabinetNext) + 1;
BytesWritten = Size;
if (fwrite(CabinetNext, Size, 1, FileHandle) < 1)
{
DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
return CAB_STATUS_CANNOT_WRITE;
}
DPRINT(MAX_TRACE, ("DiskNext '%s'.\n", DiskNext));
/* Write label of next disk */
Size = (ULONG)strlen(DiskNext) + 1;
BytesWritten = Size;
if (fwrite(DiskNext, Size, 1, FileHandle) < 1)
{
DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
return CAB_STATUS_CANNOT_WRITE;
}
}
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::WriteFolderEntries()
/*
* FUNCTION: Writes folder entries
* RETURNS:
* Status of operation
*/
{
DPRINT(MAX_TRACE, ("Writing folder table.\n"));
for (PCFFOLDER_NODE FolderNode : FolderList)
{
if (FolderNode->Commit)
{
DPRINT(MAX_TRACE, ("Writing folder entry. CompressionType (0x%X) DataBlockCount (%d) DataOffset (0x%X).\n",
FolderNode->Folder.CompressionType, FolderNode->Folder.DataBlockCount, (UINT)FolderNode->Folder.DataOffset));
if (fwrite(&FolderNode->Folder, sizeof(CFFOLDER), 1, FileHandle) < 1)
{
DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
return CAB_STATUS_CANNOT_WRITE;
}
}
}
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::WriteFileEntries()
/*
* FUNCTION: Writes file entries for all files
* RETURNS:
* Status of operation
*/
{
bool SetCont = false;
DPRINT(MAX_TRACE, ("Writing file table.\n"));
for (PCFFILE_NODE File : FileList)
{
if (File->Commit)
{
/* Remove any continued files that ends in this disk */
if (File->File.FileControlID == CAB_FILE_CONTINUED)
File->Delete = true;
/* The file could end in the last (split) block and should therefore
appear in the next disk too */
if ((File->File.FileOffset + File->File.FileSize >= LastBlockStart) &&
(File->File.FileControlID <= CAB_FILE_MAX_FOLDER) && (BlockIsSplit))
{
File->File.FileControlID = CAB_FILE_SPLIT;
File->Delete = false;
SetCont = true;
}
DPRINT(MAX_TRACE, ("Writing file entry. FileControlID (0x%X) FileOffset (0x%X) FileSize (%u) FileName (%s).\n",
File->File.FileControlID, (UINT)File->File.FileOffset, (UINT)File->File.FileSize, File->FileName.c_str()));
if (fwrite(&File->File, sizeof(CFFILE), 1, FileHandle) < 1)
{
DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
return CAB_STATUS_CANNOT_WRITE;
}
std::string fname = CreateCabFilename(File);
if (fwrite(fname.c_str(), fname.length() + 1, 1, FileHandle) < 1)
{
DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
return CAB_STATUS_CANNOT_WRITE;
}
if (SetCont)
{
File->File.FileControlID = CAB_FILE_CONTINUED;
SetCont = false;
}
}
}
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::CommitDataBlocks(PCFFOLDER_NODE FolderNode)
/*
* FUNCTION: Writes data blocks to the cabinet
* ARGUMENTS:
* FolderNode = Pointer to folder node containing the data blocks
* RETURNS:
* Status of operation
*/
{
ULONG BytesRead;
ULONG Status;
if (!FolderNode->DataList.empty())
Status = ScratchFile->Seek(FolderNode->DataList.front()->ScratchFilePosition);
for (PCFDATA_NODE DataNode : FolderNode->DataList)
{
DPRINT(MAX_TRACE, ("Reading block at (0x%X) CompSize (%u) UncompSize (%u).\n",
(UINT)DataNode->ScratchFilePosition,
DataNode->Data.CompSize,
DataNode->Data.UncompSize));
/* InputBuffer is free for us to use here, so we use it and avoid a
memory allocation. OutputBuffer can't be used here because it may
still contain valid data (if a data block spans two or more disks) */
Status = ScratchFile->ReadBlock(&DataNode->Data, InputBuffer, &BytesRead);
if (Status != CAB_STATUS_SUCCESS)
{
DPRINT(MIN_TRACE, ("Cannot read from scratch file (%u).\n", (UINT)Status));
return Status;
}
if (fwrite(&DataNode->Data, sizeof(CFDATA), 1, FileHandle) < 1)
{
DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
return CAB_STATUS_CANNOT_WRITE;
}
if (fwrite(InputBuffer, DataNode->Data.CompSize, 1, FileHandle) < 1)
{
DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
return CAB_STATUS_CANNOT_WRITE;
}
}
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::WriteDataBlock()
/*
* FUNCTION: Writes the current data block to the scratch file
* RETURNS:
* Status of operation
*/
{
ULONG Status;
ULONG BytesWritten;
PCFDATA_NODE DataNode;
if (!BlockIsSplit)
{
Status = Codec->Compress(OutputBuffer,
InputBuffer,
CurrentIBufferSize,
&TotalCompSize);
DPRINT(MAX_TRACE, ("Block compressed. CurrentIBufferSize (%u) TotalCompSize(%u).\n",
(UINT)CurrentIBufferSize, (UINT)TotalCompSize));
CurrentOBuffer = OutputBuffer;
CurrentOBufferSize = TotalCompSize;
}
DataNode = NewDataNode(CurrentFolderNode);
if (!DataNode)
{
DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
return CAB_STATUS_NOMEMORY;
}
DiskSize += sizeof(CFDATA);
if (MaxDiskSize > 0)
/* Disk size is limited */
BlockIsSplit = (DiskSize + CurrentOBufferSize > MaxDiskSize);
else
BlockIsSplit = false;
if (BlockIsSplit)
{
DataNode->Data.CompSize = (USHORT)(MaxDiskSize - DiskSize);
DataNode->Data.UncompSize = 0;
CreateNewDisk = true;
}
else
{
DataNode->Data.CompSize = (USHORT)CurrentOBufferSize;
DataNode->Data.UncompSize = (USHORT)CurrentIBufferSize;
}
DataNode->Data.Checksum = 0;
DataNode->ScratchFilePosition = ScratchFile->Position();
// FIXME: MAKECAB.EXE does not like this checksum algorithm
//DataNode->Data.Checksum = ComputeChecksum(CurrentOBuffer, DataNode->Data.CompSize, 0);
DPRINT(MAX_TRACE, ("Writing block. Checksum (0x%X) CompSize (%u) UncompSize (%u).\n",
(UINT)DataNode->Data.Checksum,
DataNode->Data.CompSize,
DataNode->Data.UncompSize));
Status = ScratchFile->WriteBlock(&DataNode->Data,
CurrentOBuffer, &BytesWritten);
if (Status != CAB_STATUS_SUCCESS)
return Status;
DiskSize += BytesWritten;
CurrentFolderNode->TotalFolderSize += (BytesWritten + sizeof(CFDATA));
CurrentFolderNode->Folder.DataBlockCount++;
CurrentOBuffer = (unsigned char*)CurrentOBuffer + DataNode->Data.CompSize;
CurrentOBufferSize -= DataNode->Data.CompSize;
LastBlockStart += DataNode->Data.UncompSize;
if (!BlockIsSplit)
{
CurrentIBufferSize = 0;
CurrentIBuffer = InputBuffer;
}
return CAB_STATUS_SUCCESS;
}
#if !defined(_WIN32)
void CCabinet::ConvertDateAndTime(time_t* Time,
PUSHORT DosDate,
PUSHORT DosTime)
/*
* FUNCTION: Returns file times of a file
* ARGUMENTS:
* FileHandle = File handle of file to get file times from
* File = Pointer to CFFILE node for file
* RETURNS:
* Status of operation
*/
{
struct tm *timedef;
timedef = localtime(Time);
DPRINT(MAX_TRACE, ("day: %d, mon: %d, year:%d, hour: %d, min: %d, sec: %d\n",
timedef->tm_mday, timedef->tm_mon, timedef->tm_year,
timedef->tm_sec, timedef->tm_min, timedef->tm_hour));
*DosDate = ((timedef->tm_mday + 1) << 0)
| ((timedef->tm_mon + 1) << 5)
| (((timedef->tm_year + 1900) - 1980) << 9);
*DosTime = (timedef->tm_sec << 0)
| (timedef->tm_min << 5)
| (timedef->tm_hour << 11);
}
#endif // !_WIN32
ULONG CCabinet::GetFileTimes(FILE* FileHandle, PCFFILE_NODE File)
/*
* FUNCTION: Returns file times of a file
* ARGUMENTS:
* FileHandle = File handle of file to get file times from
* File = Pointer to CFFILE node for file
* RETURNS:
* Status of operation
*/
{
#if defined(_WIN32)
FILETIME FileTime;
HANDLE FileNo = UlongToHandle(_fileno(FileHandle));
if (GetFileTime(FileNo, NULL, NULL, &FileTime))
FileTimeToDosDateTime(&FileTime,
&File->File.FileDate,
&File->File.FileTime);
#else
struct stat stbuf;
char buf[PATH_MAX];
// Check for an absolute path
if (File->FileName.length() > 0 && IsSeparator(File->FileName[0]))
strcpy(buf, File->FileName.c_str());
else
{
if (!getcwd(buf, sizeof(buf)))
return CAB_STATUS_CANNOT_READ;
strcat(buf, DIR_SEPARATOR_STRING);
strcat(buf, File->FileName.c_str());
}
if (stat(buf, &stbuf) == -1)
return CAB_STATUS_CANNOT_READ;
ConvertDateAndTime(&stbuf.st_mtime, &File->File.FileDate, &File->File.FileTime);
#endif
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::GetAttributesOnFile(PCFFILE_NODE File)
/*
* FUNCTION: Returns attributes on a file
* ARGUMENTS:
* File = Pointer to CFFILE node for file
* RETURNS:
* Status of operation
*/
{
#if defined(_WIN32)
LONG Attributes;
Attributes = GetFileAttributes(File->FileName.c_str());
if (Attributes == -1)
return CAB_STATUS_CANNOT_READ;
// 0x37 = READONLY | HIDDEN | SYSTEM | DIRECTORY | ARCHIVE
// The IDs for these attributes are the same in the CAB file and under Windows
// If the file has any other attributes, strip them off by the logical AND.
File->File.Attributes = (USHORT)(Attributes & 0x37);
#else
struct stat stbuf;
char buf[PATH_MAX];
// Check for an absolute path
if (File->FileName.length() > 0 && IsSeparator(File->FileName[0]))
strcpy(buf, File->FileName.c_str());
else
{
if (!getcwd(buf, sizeof(buf)))
return CAB_STATUS_CANNOT_READ;
strcat(buf, DIR_SEPARATOR_STRING);
strcat(buf, File->FileName.c_str());
}
if (stat(buf, &stbuf) == -1)
return CAB_STATUS_CANNOT_READ;
#if 0
File->File.Attributes |= CAB_ATTRIB_READONLY;
File->File.Attributes |= CAB_ATTRIB_HIDDEN;
File->File.Attributes |= CAB_ATTRIB_SYSTEM;
#endif
if (stbuf.st_mode & S_IFDIR)
File->File.Attributes |= CAB_ATTRIB_DIRECTORY;
File->File.Attributes |= CAB_ATTRIB_ARCHIVE;
#endif
return CAB_STATUS_SUCCESS;
}
ULONG CCabinet::SetAttributesOnFile(char* FileName, USHORT FileAttributes)
/*
* FUNCTION: Sets attributes on a file
* ARGUMENTS:
* FileName = File name with path
* FileAttributes = Attributes of that file
* RETURNS:
* Status of operation
*/
{
#if defined(_WIN32)
// 0x37 = READONLY | HIDDEN | SYSTEM | DIRECTORY | ARCHIVE
// The IDs for these attributes are the same in the CAB file and under Windows
// If the file has any other attributes, strip them off by the logical AND.
SetFileAttributes(FileName, (DWORD)(FileAttributes & 0x37));
return CAB_STATUS_SUCCESS;
#else
//DPRINT(MIN_TRACE, ("FIXME: SetAttributesOnFile() is unimplemented\n"));
return CAB_STATUS_SUCCESS;
#endif
}
#endif /* CAB_READ_ONLY */
/* EOF */