archive.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. /*##############################################################################
  2. HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems®.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ############################################################################## */
  13. #include "platform.h"
  14. #ifdef _WIN32
  15. #define S_ISDIR(m) (((m)&_S_IFDIR)!=0)
  16. #endif
  17. #include "jlib.hpp"
  18. #include "jio.hpp"
  19. #include "jmutex.hpp"
  20. #include "jfile.hpp"
  21. #include "jlog.hpp"
  22. #include "jregexp.hpp"
  23. #include "archive.hpp"
  24. #include <sys/stat.h>
  25. #include <archive.h>
  26. #include <archive_entry.h>
  27. /*
  28. * Direct access to files in zip archives (and other libarchive-supported formats), without needing to extract them first
  29. * Installs hooks into createIFile, spotting filenames of the form /my/directory/myfile.zip/{password}/path/within/archive
  30. */
  31. //Require a trailing / on zip files - use /. for the root - otherwise zip files always get expanded when they are read.
  32. #ifdef _WIN32
  33. #define ARCHIVE_SIGNATURE "[.]{zip|tar|tar[.]gz|tgz}{/|\\\\}"
  34. #else
  35. #define ARCHIVE_SIGNATURE "[.]{zip|tar|tar[.]gz|tgz}/"
  36. #endif
  37. static RegExpr *signature;
  38. static CriticalSection *lock;
  39. static const char *splitName(const char *fileName)
  40. {
  41. if (!fileName)
  42. return NULL;
  43. CriticalBlock b(*lock);
  44. const char *sig = signature->find(fileName);
  45. if (sig)
  46. return sig+signature->findlen();
  47. else
  48. return NULL;
  49. }
  50. static void splitArchivedFileName(const char *fullName, StringAttr &container, StringAttr &option, StringAttr &relPath)
  51. {
  52. const char *tail = splitName(fullName);
  53. assertex(tail);
  54. size_t containerLen = tail-fullName;
  55. if (fullName[containerLen-1]==PATHSEPCHAR)
  56. containerLen--;
  57. container.set(fullName, containerLen);
  58. if (*tail=='{')
  59. {
  60. tail++;
  61. const char *end = strchr(tail, '}');
  62. if (!end)
  63. throw MakeStringException(0, "Invalid archive-embedded filename - no matching } found");
  64. option.set(tail, end - tail);
  65. tail = end+1;
  66. if (*tail==PATHSEPCHAR)
  67. tail++;
  68. else if (*tail != 0)
  69. throw MakeStringException(0, "Invalid archive-embedded filename - " PATHSEPSTR " expected after }");
  70. }
  71. else
  72. option.clear();
  73. if (tail && *tail)
  74. {
  75. StringBuffer s(tail);
  76. s.replace(PATHSEPCHAR, '/');
  77. relPath.set(s);
  78. }
  79. else
  80. relPath.clear();
  81. }
  82. static StringBuffer & buildArchivedFileName(StringBuffer &fullname, const char *archiveFile, const char *option, const char *relPath)
  83. {
  84. fullname.append(archiveFile);
  85. if (option && *option)
  86. fullname.append(PATHSEPCHAR).append('{').append(option).append('}');
  87. if (relPath && *relPath)
  88. fullname.append(PATHSEPCHAR).append(relPath);
  89. return fullname;
  90. }
  91. IDirectoryIterator *createArchiveDirectoryIterator(const char *gitFileName, const char *mask, bool sub, bool includeDirs);
  92. // Wrapper around libarchive's archive_entry struct to ensure we free them at right time
  93. // Because not clear whether safe to use a struct archive_entry object after the archive has been closed,
  94. // we copy the info we need out of them into something we CAN be sure of the lifespan of
  95. class ArchiveEntry : public CInterface, implements IInterface
  96. {
  97. public:
  98. IMPLEMENT_IINTERFACE;
  99. ArchiveEntry(struct archive_entry *entry)
  100. {
  101. mode = archive_entry_filetype(entry);
  102. filesize = archive_entry_size(entry);
  103. path.set(archive_entry_pathname(entry));
  104. accessTime = archive_entry_atime(entry);
  105. createTime = archive_entry_ctime(entry);
  106. modifiedTime = archive_entry_mtime(entry);
  107. }
  108. bool isDir() const
  109. {
  110. return S_ISDIR(mode);
  111. }
  112. inline offset_t size()
  113. {
  114. return filesize;
  115. }
  116. const char *pathname()
  117. {
  118. return path.get();
  119. }
  120. CDateTime &getAccessTime(CDateTime &t)
  121. {
  122. t.set(accessTime);
  123. return t;
  124. }
  125. CDateTime &getCreateTime(CDateTime &t)
  126. {
  127. t.set(createTime);
  128. return t;
  129. }
  130. CDateTime &getModifiedTime(CDateTime &t)
  131. {
  132. t.set(modifiedTime);
  133. return t;
  134. }
  135. private:
  136. unsigned mode;
  137. offset_t filesize;
  138. StringAttr path;
  139. time_t accessTime;
  140. time_t createTime;
  141. time_t modifiedTime;
  142. };
  143. // IFileIO implementation for reading out of libarchive-supported archives
  144. // Because of the nature of the libarchive this may not be efficient for some archive formats
  145. // Have to read through the entire archive directory to find the bit you want, it seems
  146. // It's possible that we could add some seek support to at least avoid having to do so twice?
  147. class ArchiveFileIO : implements IFileIO, public CInterface
  148. {
  149. public:
  150. IMPLEMENT_IINTERFACE;
  151. ArchiveFileIO(const char *_fullName) : fullName(_fullName)
  152. {
  153. // Sadly it seems we can't use a saved entry to read data from an archive. We have to open a new archive
  154. // object and scan through until we find the matching file, in order to extract it.
  155. StringAttr container, option, relpath;
  156. splitArchivedFileName(_fullName, container, option, relpath);
  157. if (!relpath)
  158. relpath.set("");
  159. curPos = 0;
  160. lastPos = 0;
  161. curBuffSize = 0;
  162. curBuff = NULL;
  163. archive = archive_read_new();
  164. #ifdef _WIN32
  165. archive_read_support_format_zip(archive);
  166. archive_read_support_format_tar(archive);
  167. archive_read_support_compression_bzip2(archive);
  168. #else
  169. archive_read_support_format_all(archive);
  170. #if (ARCHIVE_VERSION_NUMBER >= 3000000)
  171. archive_read_support_filter_all(archive);
  172. #else
  173. archive_read_support_compression_all(archive);
  174. #endif
  175. #endif
  176. int retcode = archive_read_open_filename(archive, container, 10240);
  177. if (retcode == ARCHIVE_OK)
  178. {
  179. struct archive_entry *entry = archive_entry_new();
  180. while (archive_read_next_header2(archive, entry) == ARCHIVE_OK)
  181. {
  182. const char *filename = archive_entry_pathname(entry);
  183. if (strcmp(filename, relpath.get())==0)
  184. {
  185. fileSize = archive_entry_size(entry);
  186. break;
  187. }
  188. }
  189. archive_entry_free(entry);
  190. }
  191. }
  192. ~ArchiveFileIO()
  193. {
  194. #if (ARCHIVE_VERSION_NUMBER >= 3000000)
  195. archive_read_free(archive);
  196. #else
  197. archive_read_finish(archive);
  198. #endif
  199. }
  200. virtual size32_t read(offset_t pos, size32_t len, void * _data)
  201. {
  202. // NOTE - we don't support multithreaded access (the sequential-only restriction would make that tricky anyway)
  203. if (pos < lastPos)
  204. throw MakeStringException(0, "Only sequential access to contained file %s supported", fullName.get());
  205. byte *data = (byte *) _data;
  206. size32_t lenRequested = len;
  207. while (len > 0 && pos < fileSize)
  208. {
  209. if (pos >= curPos+curBuffSize)
  210. {
  211. int ret = archive_read_data_block(archive, &curBuff, &curBuffSize, &curPos);
  212. if (ret != ARCHIVE_OK)
  213. {
  214. if (ret == ARCHIVE_EOF)
  215. break; // This shouldn't happen if the quoted fileSize was accurate...
  216. else
  217. throw MakeStringException(0, "Read error reading contained file %s", fullName.get());
  218. }
  219. }
  220. else
  221. {
  222. // Copy as much of the current request as we can fulfil from this block
  223. offset_t buffOffset = (pos - curPos);
  224. size_t copyLen = (curBuffSize - buffOffset) > len ? len : curBuffSize - buffOffset; // careful for overflows, we are mixing 64/32bit values
  225. if (curBuff)
  226. memcpy(data, ((const byte *) curBuff) + buffOffset, copyLen);
  227. else
  228. memset(data, 0, copyLen); // Sparse areas of compressed files may be represented with NULL buffers
  229. data += copyLen;
  230. len -= copyLen;
  231. pos += copyLen;
  232. }
  233. }
  234. lastPos = pos;
  235. return lenRequested - len;
  236. }
  237. virtual offset_t size()
  238. {
  239. return fileSize;
  240. }
  241. virtual void close()
  242. {
  243. }
  244. // Write methods not implemented - this is a read-only file
  245. virtual size32_t write(offset_t pos, size32_t len, const void * data)
  246. {
  247. throwUnexpected();
  248. }
  249. virtual offset_t appendFile(IFile *file,offset_t pos=0,offset_t len=(offset_t)-1)
  250. {
  251. throwUnexpected();
  252. }
  253. virtual void setSize(offset_t size)
  254. {
  255. throwUnexpected();
  256. }
  257. virtual void flush()
  258. {
  259. throwUnexpected();
  260. }
  261. unsigned __int64 getStatistic(StatisticKind kind)
  262. {
  263. return 0;
  264. }
  265. protected:
  266. struct archive *archive = nullptr;
  267. offset_t fileSize = 0;
  268. #if ARCHIVE_VERSION_NUMBER < 3000000
  269. off_t curPos = 0;
  270. #else
  271. #if defined(_WIN32)
  272. #define int64_t __int64
  273. #endif
  274. int64_t curPos = 0;
  275. #endif
  276. offset_t lastPos = 0;
  277. size_t curBuffSize = 0;
  278. const void *curBuff = nullptr;
  279. StringAttr fullName;
  280. };
  281. // IFile implementation for reading out of libarchive-supported archives
  282. // These use the struct_archive_entry objects allocated in the directory iterator
  283. // in the hope they might be useful for directly seeking to the file to be extracted
  284. // at some point.
  285. class ArchiveFile : implements IFile, public CInterface
  286. {
  287. public:
  288. IMPLEMENT_IINTERFACE;
  289. ArchiveFile(const char *_fileName, ArchiveEntry *_entry)
  290. : fullName(_fileName),entry(_entry)
  291. {
  292. }
  293. virtual bool exists()
  294. {
  295. return entry != NULL;
  296. }
  297. virtual bool getTime(CDateTime * createTime, CDateTime * modifiedTime, CDateTime * accessedTime)
  298. {
  299. if (entry)
  300. {
  301. if (accessedTime)
  302. entry->getAccessTime(*accessedTime);
  303. if (createTime)
  304. entry->getCreateTime(*createTime);
  305. if (modifiedTime)
  306. entry->getModifiedTime(*modifiedTime);
  307. return true;
  308. }
  309. else
  310. return false;
  311. }
  312. virtual fileBool isDirectory()
  313. {
  314. if (!entry)
  315. return fileBool::notFound;
  316. return entry->isDir() ? fileBool::foundYes : fileBool::foundNo;
  317. }
  318. virtual fileBool isFile()
  319. {
  320. if (!entry)
  321. return fileBool::notFound;
  322. return entry->isDir() ? fileBool::foundNo : fileBool::foundYes;
  323. }
  324. virtual fileBool isReadOnly()
  325. {
  326. if (!entry)
  327. return fileBool::notFound;
  328. return fileBool::foundYes;
  329. }
  330. virtual IFileIO * open(IFOmode mode, IFEflags extraFlags=IFEnone)
  331. {
  332. assertex(mode==IFOread && entry != NULL);
  333. return new ArchiveFileIO(fullName.str());
  334. }
  335. virtual IFileAsyncIO * openAsync(IFOmode mode)
  336. {
  337. UNIMPLEMENTED;
  338. }
  339. virtual IFileIO * openShared(IFOmode mode, IFSHmode shmode, IFEflags extraFlags=IFEnone)
  340. {
  341. assertex(mode==IFOread && entry != NULL);
  342. return new ArchiveFileIO(fullName.str());
  343. }
  344. virtual const char * queryFilename()
  345. {
  346. return fullName.str();
  347. }
  348. virtual offset_t size()
  349. {
  350. if (!entry)
  351. return (offset_t) -1;
  352. return entry->size();
  353. }
  354. // Directory functions
  355. virtual IDirectoryIterator *directoryFiles(const char *mask, bool sub, bool includeDirs)
  356. {
  357. if (isDirectory() != fileBool::foundYes || (mask && !*mask)) // Empty mask string means matches nothing - NULL means matches everything
  358. return createNullDirectoryIterator();
  359. else
  360. {
  361. StringBuffer dirName(fullName);
  362. dirName.append(PATHSEPCHAR);
  363. return createArchiveDirectoryIterator(dirName, mask, sub, includeDirs);
  364. }
  365. }
  366. virtual bool getInfo(bool &_isdir,offset_t &_size,CDateTime &_modtime)
  367. {
  368. _isdir = isDirectory()==fileBool::foundYes;
  369. _size = size();
  370. _modtime.clear(); // MORE could probably do better
  371. return true; // MORE should this be false if not existing?
  372. }
  373. // Not going to be implemented - this IFile interface is too big..
  374. virtual bool setTime(const CDateTime * createTime, const CDateTime * modifiedTime, const CDateTime * accessedTime) { UNIMPLEMENTED; }
  375. virtual bool remove() { UNIMPLEMENTED; }
  376. virtual void rename(const char *newTail) { UNIMPLEMENTED; }
  377. virtual void move(const char *newName) { UNIMPLEMENTED; }
  378. virtual void setReadOnly(bool ro) { UNIMPLEMENTED; }
  379. virtual void setFilePermissions(unsigned fPerms) { UNIMPLEMENTED; }
  380. virtual bool setCompression(bool set) { UNIMPLEMENTED; }
  381. virtual offset_t compressedSize() { UNIMPLEMENTED; }
  382. virtual unsigned getCRC() { UNIMPLEMENTED; }
  383. virtual void setCreateFlags(unsigned short cflags) { UNIMPLEMENTED; }
  384. virtual void setShareMode(IFSHmode shmode) { UNIMPLEMENTED; }
  385. virtual bool createDirectory() { UNIMPLEMENTED; }
  386. virtual IDirectoryDifferenceIterator *monitorDirectory(
  387. IDirectoryIterator *prev=NULL, // in (NULL means use current as baseline)
  388. const char *mask=NULL,
  389. bool sub=false,
  390. bool includedirs=false,
  391. unsigned checkinterval=60*1000,
  392. unsigned timeout=(unsigned)-1,
  393. Semaphore *abortsem=NULL) { UNIMPLEMENTED; }
  394. virtual void copySection(const RemoteFilename &dest, offset_t toOfs=(offset_t)-1, offset_t fromOfs=0, offset_t size=(offset_t)-1, ICopyFileProgress *progress=NULL, CFflags copyFlags=CFnone) { UNIMPLEMENTED; }
  395. virtual void copyTo(IFile *dest, size32_t buffersize=DEFAULT_COPY_BLKSIZE, ICopyFileProgress *progress=NULL, bool usetmp=false, CFflags copyFlags=CFnone) { UNIMPLEMENTED; }
  396. virtual IMemoryMappedFile *openMemoryMapped(offset_t ofs=0, memsize_t len=(memsize_t)-1, bool write=false) { UNIMPLEMENTED; }
  397. protected:
  398. StringBuffer fullName;
  399. Linked<ArchiveEntry> entry;
  400. };
  401. static IFile *createIFileInArchive(const char *containedFileName)
  402. {
  403. StringBuffer fname(containedFileName);
  404. assertex(fname.length());
  405. removeTrailingPathSepChar(fname);
  406. StringAttr container, option, relpath;
  407. splitArchivedFileName(fname.str(), container, option, relpath);
  408. if (relpath.length())
  409. {
  410. StringBuffer dirPath, dirTail;
  411. dirPath.append(container).append(option);
  412. splitFilename(relpath, &dirPath, &dirPath, &dirTail, &dirTail);
  413. Owned<IDirectoryIterator> dir = createArchiveDirectoryIterator(dirPath.str(), dirTail.str(), false, true);
  414. if (dir->first())
  415. {
  416. Linked<IFile> file = &dir->query();
  417. assertex(!dir->next());
  418. return file.getClear();
  419. }
  420. else
  421. return new ArchiveFile(containedFileName, NULL);
  422. }
  423. else
  424. {
  425. // Create an IFile representing the root of the archive as a directory
  426. struct archive_entry *rootEntry = archive_entry_new();
  427. archive_entry_set_pathname(rootEntry, ".");
  428. archive_entry_set_mode(rootEntry, S_IFDIR);
  429. archive_entry_set_size(rootEntry, 0);
  430. return new ArchiveFile(containedFileName, new ArchiveEntry(rootEntry));
  431. }
  432. }
  433. class ArchiveDirectoryIterator : implements IDirectoryIterator, public CInterface
  434. {
  435. public:
  436. IMPLEMENT_IINTERFACE;
  437. ArchiveDirectoryIterator(const char *_containedFileName, const char *_mask, bool _sub, bool _includeDirs)
  438. : mask(_mask), sub(_sub), includeDirs(_includeDirs)
  439. {
  440. splitArchivedFileName(_containedFileName, container, option, relDir);
  441. curIndex = 0;
  442. }
  443. virtual StringBuffer &getName(StringBuffer &buf)
  444. {
  445. assertex(curFile);
  446. return buf.append(curFile->queryFilename());
  447. }
  448. virtual bool isDir()
  449. {
  450. assertex(curFile);
  451. return curFile->isDirectory()==fileBool::foundYes;
  452. }
  453. virtual __int64 getFileSize()
  454. {
  455. assertex(curFile);
  456. return curFile->size();
  457. }
  458. virtual bool getModifiedTime(CDateTime &ret)
  459. {
  460. UNIMPLEMENTED;
  461. }
  462. virtual bool first()
  463. {
  464. curFile.clear();
  465. entries.kill();
  466. curIndex = 0;
  467. struct archive *archive = archive_read_new();
  468. #ifdef _WIN32
  469. archive_read_support_format_zip(archive);
  470. archive_read_support_format_tar(archive);
  471. archive_read_support_compression_bzip2(archive);
  472. #else
  473. archive_read_support_format_all(archive);
  474. #if (ARCHIVE_VERSION_NUMBER >= 3000000)
  475. archive_read_support_filter_all(archive);
  476. #else
  477. archive_read_support_compression_all(archive);
  478. #endif
  479. #endif
  480. int retcode = archive_read_open_filename(archive, container, 10240);
  481. if (retcode == ARCHIVE_OK)
  482. {
  483. struct archive_entry *entry = archive_entry_new();
  484. while (archive_read_next_header2(archive, entry) == ARCHIVE_OK)
  485. {
  486. unsigned mode = archive_entry_filetype(entry);
  487. bool isDir = S_ISDIR(mode);
  488. if (includeDirs || !isDir)
  489. {
  490. const char *filename = archive_entry_pathname(entry);
  491. if (memcmp(filename, relDir.get(), relDir.length())==0)
  492. {
  493. StringBuffer tail(filename + relDir.length());
  494. if (tail.length())
  495. {
  496. if (tail.charAt(tail.length()-1)=='/' || tail.charAt(tail.length()-1)==PATHSEPCHAR)
  497. tail.remove(tail.length()-1, 1);
  498. }
  499. else
  500. {
  501. assert(isDir);
  502. tail.append(".");
  503. }
  504. // Strip off a trailing /, then check that there is no / in the tail
  505. if (strchr(tail, PATHSEPCHAR) == NULL && (!mask.length() || WildMatch(tail, mask, false)))
  506. {
  507. entries.append(*new ArchiveEntry(entry));
  508. }
  509. }
  510. }
  511. }
  512. archive_entry_free(entry);
  513. }
  514. #if (ARCHIVE_VERSION_NUMBER >= 3000000)
  515. archive_read_free(archive);
  516. #else
  517. archive_read_finish(archive);
  518. #endif
  519. return next();
  520. }
  521. virtual bool next()
  522. {
  523. if (entries.isItem(curIndex))
  524. {
  525. ArchiveEntry &entry = entries.item(curIndex);
  526. curIndex++;
  527. const char *filename = entry.pathname();
  528. StringBuffer containedFileName;
  529. buildArchivedFileName(containedFileName, container, option, filename);
  530. removeTrailingPathSepChar(containedFileName);
  531. curFile.setown(new ArchiveFile(containedFileName, &entry));
  532. return true;
  533. }
  534. else
  535. {
  536. curFile.clear();
  537. return false;
  538. }
  539. }
  540. virtual bool isValid() { return curFile != NULL; }
  541. virtual IFile & query() { return *curFile; }
  542. protected:
  543. StringAttr container;
  544. StringAttr option;
  545. StringAttr relDir;
  546. StringAttr mask;
  547. Owned<IFile> curFile;
  548. unsigned curIndex;
  549. IArrayOf<ArchiveEntry> entries; // The entries that matched
  550. bool includeDirs;
  551. bool sub;
  552. };
  553. IDirectoryIterator *createArchiveDirectoryIterator(const char *gitFileName, const char *mask, bool sub, bool includeDirs)
  554. {
  555. assertex(sub==false); // I don't know what it means!
  556. return new ArchiveDirectoryIterator(gitFileName, mask, sub, includeDirs);
  557. }
  558. class CArchiveFileHook : public CInterface, implements IContainedFileHook
  559. {
  560. public:
  561. IMPLEMENT_IINTERFACE;
  562. virtual IFile * createIFile(const char *fileName)
  563. {
  564. if (isArchiveFileName(fileName))
  565. return createIFileInArchive(fileName);
  566. else
  567. return NULL;
  568. }
  569. protected:
  570. static bool isArchiveFileName(const char *fileName)
  571. {
  572. if (fileName)
  573. return splitName(fileName) != NULL;
  574. return false;
  575. }
  576. } *archiveFileHook;
  577. extern ARCHIVEFILE_API void installFileHook()
  578. {
  579. CriticalBlock b(*lock); // Probably overkill!
  580. if (!archiveFileHook)
  581. {
  582. archiveFileHook = new CArchiveFileHook;
  583. addContainedFileHook(archiveFileHook);
  584. }
  585. }
  586. extern ARCHIVEFILE_API void removeFileHook()
  587. {
  588. if (lock)
  589. {
  590. CriticalBlock b(*lock); // Probably overkill!
  591. if (archiveFileHook)
  592. {
  593. removeContainedFileHook(archiveFileHook);
  594. delete archiveFileHook;
  595. archiveFileHook = NULL;
  596. }
  597. }
  598. }
  599. MODULE_INIT(INIT_PRIORITY_STANDARD)
  600. {
  601. lock = new CriticalSection;
  602. signature = new RegExpr(ARCHIVE_SIGNATURE);
  603. archiveFileHook = NULL;
  604. return true;
  605. }
  606. MODULE_EXIT()
  607. {
  608. if (archiveFileHook)
  609. {
  610. removeContainedFileHook(archiveFileHook);
  611. ::Release(archiveFileHook);
  612. archiveFileHook = NULL;
  613. }
  614. delete signature;
  615. delete lock;
  616. lock = NULL;
  617. signature = NULL;
  618. }