490 lines
13 KiB
C++
490 lines
13 KiB
C++
#include "stdafx.h"
|
|
#include <smartptr.h>
|
|
#include "MTSafeAllocator.h"
|
|
#include "ZipFileFormat.h"
|
|
#include "ZipDirStructures.h"
|
|
#include "ZipDirTree.h"
|
|
#include "ZipDirList.h"
|
|
#include "ZipDirCache.h"
|
|
#include "ZipDirCacheRW.h"
|
|
#include "ZipDirCacheFactory.h"
|
|
#include "ZipDirFindRW.h"
|
|
|
|
|
|
// declaration of Z_OK for ZipRawDecompress
|
|
#include "zlib/zlib.h"
|
|
|
|
using namespace ZipFile;
|
|
|
|
|
|
void ZipDir::CacheRW::Close()
|
|
{
|
|
if (m_pFile)
|
|
{
|
|
if (!(m_nFlags & FLAGS_READ_ONLY))
|
|
{
|
|
if ((m_nFlags & FLAGS_UNCOMPACTED) && !(m_nFlags&FLAGS_DONT_COMPACT))
|
|
{
|
|
if (!RelinkZip())
|
|
WriteCDR();
|
|
}
|
|
else
|
|
if (m_nFlags & FLAGS_CDR_DIRTY)
|
|
WriteCDR();
|
|
}
|
|
|
|
fclose (m_pFile);
|
|
m_pFile = NULL;
|
|
}
|
|
m_pHeap = NULL;
|
|
m_treeDir.Clear();
|
|
}
|
|
|
|
|
|
// Adds a new file to the zip or update an existing one
|
|
// adds a directory (creates several nested directories if needed)
|
|
ZipDir::ErrorEnum ZipDir::CacheRW::UpdateFile (const char* szRelativePath, void* pUncompressed, unsigned nSize, unsigned nCompressionMethod, int nCompressionLevel)
|
|
{
|
|
SmartPtr pBufferDestroyer(m_pHeap);
|
|
|
|
// we'll need the compressed data
|
|
void* pCompressed;
|
|
unsigned long nSizeCompressed;
|
|
int nError;
|
|
|
|
switch (nCompressionMethod)
|
|
{
|
|
case METHOD_DEFLATE:
|
|
// allocate memory for compression. Min is nSize * 1.001 + 12
|
|
nSizeCompressed = nSize + (nSize >> 3) + 32;
|
|
pCompressed = m_pHeap->Alloc(nSizeCompressed, "ZipDir::CacheRW::UpdateFile");
|
|
pBufferDestroyer.Attach(pCompressed);
|
|
nError = ZipRawCompress(m_pHeap, pUncompressed, &nSizeCompressed, pCompressed, nSize, nCompressionLevel);
|
|
if (Z_OK != nError)
|
|
return ZD_ERROR_ZLIB_FAILED;
|
|
break;
|
|
|
|
case METHOD_STORE:
|
|
pCompressed = pUncompressed;
|
|
nSizeCompressed = nSize;
|
|
break;
|
|
|
|
default:
|
|
return ZD_ERROR_UNSUPPORTED;
|
|
}
|
|
|
|
// create or find the file entry.. this object will rollback (delete the object
|
|
// if the operation fails) if needed.
|
|
FileEntryTransactionAdd pFileEntry(this, szRelativePath);
|
|
|
|
if (!pFileEntry)
|
|
return ZD_ERROR_INVALID_PATH;
|
|
|
|
pFileEntry->OnNewFileData (pUncompressed, nSize, nSizeCompressed, nCompressionMethod);
|
|
// since we changed the time, we'll have to update CDR
|
|
m_nFlags |= FLAGS_CDR_DIRTY;
|
|
|
|
// the new CDR position, if the operation completes successfully
|
|
unsigned lNewCDROffset = m_lCDROffset;
|
|
|
|
if (pFileEntry->IsInitialized())
|
|
{
|
|
// this file entry is already allocated in CDR
|
|
|
|
// check if the new compressed data fits into the old place
|
|
unsigned nFreeSpace = pFileEntry->nEOFOffset - pFileEntry->nFileHeaderOffset - (unsigned)sizeof(ZipFile::LocalFileHeader) - (unsigned)strlen(szRelativePath);
|
|
|
|
if (nFreeSpace != nSizeCompressed)
|
|
m_nFlags |= FLAGS_UNCOMPACTED;
|
|
|
|
if (nFreeSpace >= nSizeCompressed)
|
|
{
|
|
// and we can just override the compressed data in the file
|
|
ErrorEnum e = WriteLocalHeader(m_pFile, pFileEntry, szRelativePath);
|
|
if (e != ZD_ERROR_SUCCESS)
|
|
return e;
|
|
}
|
|
else
|
|
{
|
|
// we need to write the file anew - in place of current CDR
|
|
pFileEntry->nFileHeaderOffset = m_lCDROffset;
|
|
ErrorEnum e = WriteLocalHeader(m_pFile, pFileEntry, szRelativePath);
|
|
lNewCDROffset = pFileEntry->nEOFOffset;
|
|
if (e != ZD_ERROR_SUCCESS)
|
|
return e;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pFileEntry->nFileHeaderOffset = m_lCDROffset;
|
|
ErrorEnum e = WriteLocalHeader(m_pFile, pFileEntry, szRelativePath);
|
|
if (e != ZD_ERROR_SUCCESS)
|
|
return e;
|
|
|
|
lNewCDROffset = pFileEntry->nFileDataOffset + nSizeCompressed;
|
|
|
|
m_nFlags |= FLAGS_CDR_DIRTY;
|
|
}
|
|
|
|
// now we have the fresh local header and data offset
|
|
if (fseek (m_pFile, pFileEntry->nFileDataOffset, SEEK_SET)!=0
|
|
||fwrite (pCompressed, nSizeCompressed, 1, m_pFile) != 1)
|
|
{
|
|
// we need to rollback the transaction: file write failed in the middle, the old
|
|
// data is unrecoverably damaged, and we need to destroy this file entry
|
|
// the CDR is already marked dirty
|
|
return ZD_ERROR_IO_FAILED;
|
|
}
|
|
|
|
// since we wrote the file successfully, update the new CDR position
|
|
m_lCDROffset = lNewCDROffset;
|
|
pFileEntry.Commit();
|
|
|
|
return ZD_ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
// deletes the file from the archive
|
|
ZipDir::ErrorEnum ZipDir::CacheRW::RemoveFile (const char* szRelativePath)
|
|
{
|
|
// find the last slash in the path
|
|
const char* pSlash = max(strrchr(szRelativePath, '/'), strrchr(szRelativePath, '\\'));
|
|
|
|
const char* pFileName; // the name of the file to delete
|
|
|
|
FileEntryTree* pDir; // the dir from which the subdir will be deleted
|
|
|
|
if (pSlash)
|
|
{
|
|
FindDirRW fd (GetRoot());
|
|
// the directory to remove
|
|
pDir = fd.FindExact(string (szRelativePath, pSlash-szRelativePath).c_str());
|
|
if (!pDir)
|
|
return ZD_ERROR_DIR_NOT_FOUND;// there is no such directory
|
|
pFileName = pSlash+1;
|
|
}
|
|
else
|
|
{
|
|
pDir = GetRoot();
|
|
pFileName = szRelativePath;
|
|
}
|
|
|
|
ErrorEnum e = pDir->RemoveFile (pFileName);
|
|
if (e == ZD_ERROR_SUCCESS)
|
|
m_nFlags |= FLAGS_UNCOMPACTED|FLAGS_CDR_DIRTY;
|
|
return e;
|
|
}
|
|
|
|
|
|
// deletes the directory, with all its descendants (files and subdirs)
|
|
ZipDir::ErrorEnum ZipDir::CacheRW::RemoveDir (const char* szRelativePath)
|
|
{
|
|
// find the last slash in the path
|
|
const char* pSlash = max(strrchr(szRelativePath, '/'), strrchr(szRelativePath, '\\'));
|
|
|
|
const char* pDirName; // the name of the dir to delete
|
|
|
|
FileEntryTree* pDir; // the dir from which the subdir will be deleted
|
|
|
|
if (pSlash)
|
|
{
|
|
FindDirRW fd (GetRoot());
|
|
// the directory to remove
|
|
pDir = fd.FindExact(string (szRelativePath, pSlash-szRelativePath).c_str());
|
|
if (!pDir)
|
|
return ZD_ERROR_DIR_NOT_FOUND;// there is no such directory
|
|
pDirName = pSlash+1;
|
|
}
|
|
else
|
|
{
|
|
pDir = GetRoot();
|
|
pDirName = szRelativePath;
|
|
}
|
|
|
|
ErrorEnum e = pDir->RemoveDir (pDirName);
|
|
if (e == ZD_ERROR_SUCCESS)
|
|
m_nFlags |= FLAGS_UNCOMPACTED|FLAGS_CDR_DIRTY;
|
|
return e;
|
|
}
|
|
|
|
// deletes all files and directories in this archive
|
|
ZipDir::ErrorEnum ZipDir::CacheRW::RemoveAll()
|
|
{
|
|
ErrorEnum e = m_treeDir.RemoveAll();
|
|
if (e == ZD_ERROR_SUCCESS)
|
|
m_nFlags |= FLAGS_UNCOMPACTED|FLAGS_CDR_DIRTY;
|
|
return e;
|
|
}
|
|
|
|
ZipDir::ErrorEnum ZipDir::CacheRW::ReadFile (FileEntry* pFileEntry, void* pCompressed, void* pUncompressed)
|
|
{
|
|
if (!pFileEntry)
|
|
return ZD_ERROR_INVALID_CALL;
|
|
|
|
if (pFileEntry->desc.lSizeUncompressed == 0)
|
|
{
|
|
assert (pFileEntry->desc.lSizeCompressed == 0);
|
|
return ZD_ERROR_SUCCESS;
|
|
}
|
|
|
|
assert (pFileEntry->desc.lSizeCompressed > 0);
|
|
|
|
ErrorEnum nError = Refresh(pFileEntry);
|
|
if (nError != ZD_ERROR_SUCCESS)
|
|
return nError;
|
|
|
|
if (fseek (m_pFile, pFileEntry->nFileDataOffset, SEEK_SET))
|
|
return ZD_ERROR_IO_FAILED;
|
|
|
|
SmartPtr pBufferDestroyer(m_pHeap);
|
|
|
|
void* pBuffer = pCompressed; // the buffer where the compressed data will go
|
|
|
|
if (pFileEntry->nMethod == 0 && pUncompressed)
|
|
{
|
|
// we can directly read into the uncompress buffer
|
|
pBuffer = pUncompressed;
|
|
}
|
|
|
|
if (!pBuffer)
|
|
{
|
|
if (!pUncompressed)
|
|
// what's the sense of it - no buffers at all?
|
|
return ZD_ERROR_INVALID_CALL;
|
|
|
|
pBuffer = m_pHeap->Alloc(pFileEntry->desc.lSizeCompressed, "ZipDir::Cache::ReadFile");
|
|
pBufferDestroyer.Attach(pBuffer); // we want it auto-freed once we return
|
|
}
|
|
|
|
if (fread (pBuffer, pFileEntry->desc.lSizeCompressed, 1, m_pFile) != 1)
|
|
return ZD_ERROR_IO_FAILED;
|
|
|
|
// if there's a buffer for uncompressed data, uncompress it to that buffer
|
|
if (pUncompressed)
|
|
{
|
|
if (pFileEntry->nMethod == 0)
|
|
{
|
|
assert (pBuffer == pUncompressed);
|
|
//assert (pFileEntry->nSizeCompressed == pFileEntry->nSizeUncompressed);
|
|
//memcpy (pUncompressed, pBuffer, pFileEntry->nSizeCompressed);
|
|
}
|
|
else
|
|
{
|
|
unsigned long nSizeUncompressed = pFileEntry->desc.lSizeUncompressed;
|
|
if (Z_OK != ZipRawUncompress(m_pHeap, pUncompressed, &nSizeUncompressed, pBuffer, pFileEntry->desc.lSizeCompressed))
|
|
return ZD_ERROR_CORRUPTED_DATA;
|
|
}
|
|
}
|
|
|
|
return ZD_ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// finds the file by exact path
|
|
ZipDir::FileEntry* ZipDir::CacheRW::FindFile (const char* szPath, bool bFullInfo)
|
|
{
|
|
if (!this)
|
|
return NULL;
|
|
ZipDir::FindFileRW fd (GetRoot());
|
|
if (!fd.FindExact(szPath))
|
|
{
|
|
assert (!fd.GetFileEntry());
|
|
return NULL;
|
|
}
|
|
assert (fd.GetFileEntry());
|
|
return fd.GetFileEntry();
|
|
}
|
|
|
|
// returns the size of memory occupied by the instance referred to by this cache
|
|
size_t ZipDir::CacheRW::GetSize()const
|
|
{
|
|
return sizeof(*this) + m_strFilePath.capacity() + m_treeDir.GetSize() - sizeof(m_treeDir);
|
|
}
|
|
|
|
// refreshes information about the given file entry into this file entry
|
|
ZipDir::ErrorEnum ZipDir::CacheRW::Refresh (FileEntry* pFileEntry)
|
|
{
|
|
if (!pFileEntry)
|
|
return ZD_ERROR_INVALID_CALL;
|
|
|
|
if (pFileEntry->nFileDataOffset != pFileEntry->INVALID_DATA_OFFSET)
|
|
return ZD_ERROR_SUCCESS; // the data offset has been successfully read..
|
|
|
|
if (!this)
|
|
return ZD_ERROR_INVALID_CALL; // from which cache is this file entry???
|
|
|
|
return ZipDir::Refresh(m_pFile, pFileEntry);
|
|
}
|
|
|
|
|
|
// writes the CDR to the disk
|
|
bool ZipDir::CacheRW::WriteCDR(FILE* fTarget)
|
|
{
|
|
if (!fTarget)
|
|
return false;
|
|
|
|
if (fseek(fTarget, m_lCDROffset, SEEK_SET))
|
|
return false;
|
|
|
|
FileRecordList arrFiles(GetRoot());
|
|
//arrFiles.SortByFileOffset();
|
|
size_t nSizeCDR = arrFiles.GetStats().nSizeCDR;
|
|
void* pCDR = m_pHeap->Alloc(nSizeCDR, "ZipDir::CacheRW::WriteCDR");
|
|
size_t nSizeCDRSerialized = arrFiles.MakeZipCDR(m_lCDROffset,pCDR);
|
|
assert (nSizeCDRSerialized == nSizeCDR);
|
|
size_t nWriteRes = fwrite (pCDR, nSizeCDR, 1, fTarget);
|
|
m_pHeap->Free(pCDR);
|
|
return nWriteRes == 1;
|
|
}
|
|
|
|
// generates random file name
|
|
string ZipDir::CacheRW::GetRandomName(int nAttempt)
|
|
{
|
|
if (nAttempt)
|
|
{
|
|
char szBuf[8];
|
|
int i;
|
|
for (i = 0; i < sizeof(szBuf)-1;++i)
|
|
{
|
|
int r = rand()%(10 + 'z' - 'a' + 1);
|
|
szBuf[i] = r > 9 ? (r-10)+'a' : '0' + r;
|
|
}
|
|
szBuf[i] = '\0';
|
|
return szBuf;
|
|
}
|
|
else
|
|
return string();
|
|
}
|
|
|
|
bool ZipDir::CacheRW::RelinkZip()
|
|
{
|
|
for (int nAttempt = 0; nAttempt < 32; ++nAttempt)
|
|
{
|
|
string strNewFilePath = m_strFilePath + "$" + GetRandomName(nAttempt);
|
|
if (GetFileAttributes (strNewFilePath.c_str()) != -1)
|
|
continue; // we don't want to overwrite the old temp files for safety reasons
|
|
|
|
FILE* f = fopen (strNewFilePath.c_str(), "wb");
|
|
if (f)
|
|
{
|
|
bool bOk = RelinkZip(f);
|
|
fclose (f); // we don't need the temporary file handle anyway
|
|
|
|
if (!bOk)
|
|
{
|
|
// we don't need the temporary file
|
|
unlink (strNewFilePath.c_str());
|
|
return false;
|
|
}
|
|
|
|
// we successfully relinked, now copy the temporary file to the original file
|
|
fclose (m_pFile);
|
|
m_pFile = NULL;
|
|
|
|
remove (m_strFilePath.c_str());
|
|
if (rename (strNewFilePath.c_str(), m_strFilePath.c_str()) == 0)
|
|
{
|
|
// successfully renamed - reopen
|
|
m_pFile = fopen (m_strFilePath.c_str(), "r+b");
|
|
return m_pFile == NULL;
|
|
}
|
|
else
|
|
{
|
|
// could not rename
|
|
|
|
//m_pFile = fopen (strNewFilePath.c_str(), "r+b");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// couldn't open temp file
|
|
return false;
|
|
}
|
|
|
|
bool ZipDir::CacheRW::RelinkZip(FILE* fTmp)
|
|
{
|
|
FileRecordList arrFiles(GetRoot());
|
|
arrFiles.SortByFileOffset();
|
|
FileRecordList::ZipStats Stats = arrFiles.GetStats();
|
|
|
|
// we back up our file entries, because we'll need to restore them
|
|
// in case the operation fails
|
|
std::vector<FileEntry> arrFileEntryBackup;
|
|
arrFiles.Backup (arrFileEntryBackup);
|
|
|
|
// this is the set of files that are to be written out - compressed data and the file record iterator
|
|
std::vector<FileDataRecordPtr> queFiles;
|
|
queFiles.reserve (g_nMaxItemsRelinkBuffer);
|
|
|
|
// the total size of data in the queue
|
|
unsigned nQueueSize = 0;
|
|
|
|
for (FileRecordList::iterator it = arrFiles.begin(); it != arrFiles.end(); ++it)
|
|
{
|
|
// find the file data offset
|
|
if (ZD_ERROR_SUCCESS != Refresh (it->pFileEntry))
|
|
return false;
|
|
|
|
// go to the file data
|
|
if (fseek (m_pFile, it->pFileEntry->nFileDataOffset, SEEK_SET) != 0)
|
|
return false;
|
|
|
|
// allocate memory for the file compressed data
|
|
FileDataRecordPtr pFile = FileDataRecord::New (*it, m_pHeap);
|
|
|
|
// read the compressed data
|
|
if (it->pFileEntry->desc.lSizeCompressed && fread (pFile->GetData(), it->pFileEntry->desc.lSizeCompressed, 1, m_pFile) != 1)
|
|
return false;
|
|
|
|
// put the file into the queue for copying (writing)
|
|
queFiles.push_back(pFile);
|
|
nQueueSize += it->pFileEntry->desc.lSizeCompressed;
|
|
|
|
// if the queue is big enough, write it out
|
|
if(nQueueSize > g_nSizeRelinkBuffer || queFiles.size() >= g_nMaxItemsRelinkBuffer)
|
|
{
|
|
nQueueSize = 0;
|
|
if (!WriteZipFiles(queFiles, fTmp))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!WriteZipFiles(queFiles, fTmp))
|
|
return false;
|
|
|
|
ZipFile::ulong lOldCDROffset = m_lCDROffset;
|
|
// the file data has now been written out. Now write the CDR
|
|
m_lCDROffset = ftell(fTmp);
|
|
if (m_lCDROffset >= 0 && WriteCDR(fTmp) && 0 == fflush (fTmp))
|
|
// the new file positions are already there - just discard the backup and return
|
|
return true;
|
|
// recover from backup
|
|
arrFiles.Restore (arrFileEntryBackup);
|
|
m_lCDROffset = lOldCDROffset;
|
|
return false;
|
|
}
|
|
|
|
// writes out the file data in the queue into the given file. Empties the queue
|
|
bool ZipDir::CacheRW::WriteZipFiles(std::vector<FileDataRecordPtr>& queFiles, FILE* fTmp)
|
|
{
|
|
for (std::vector<FileDataRecordPtr>::iterator it = queFiles.begin(); it != queFiles.end(); ++it)
|
|
{
|
|
// set the new header offset to the file entry - we won't need it
|
|
(*it)->pFileEntry->nFileHeaderOffset = ftell (fTmp);
|
|
|
|
// while writing the local header, the data offset will also be calculated
|
|
if (ZD_ERROR_SUCCESS != WriteLocalHeader(fTmp, (*it)->pFileEntry, (*it)->strPath.c_str()))
|
|
return false;;
|
|
|
|
// write the compressed file data
|
|
if ((*it)->pFileEntry->desc.lSizeCompressed && fwrite ((*it)->GetData(), (*it)->pFileEntry->desc.lSizeCompressed, 1, fTmp) != 1)
|
|
return false;
|
|
|
|
assert ((*it)->pFileEntry->nEOFOffset == ftell (fTmp));
|
|
}
|
|
queFiles.clear();
|
|
queFiles.reserve (g_nMaxItemsRelinkBuffer);
|
|
return true;
|
|
} |