#include "stdafx.h" #include #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 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 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& queFiles, FILE* fTmp) { for (std::vector::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; }