PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/administrator/components/com_akeeba/akeeba/engines/archiver/jpa.php

https://bitbucket.org/organicdevelopment/joomla-2.5
PHP | 950 lines | 718 code | 78 blank | 154 comment | 127 complexity | 6b96e74abc84560d027c8700a1f7dbbd MD5 | raw file
Possible License(s): LGPL-3.0, GPL-2.0, MIT, BSD-3-Clause, LGPL-2.1
  1. <?php
  2. /**
  3. * Akeeba Engine
  4. * The modular PHP5 site backup engine
  5. * @copyright Copyright (c)2009-2012 Nicholas K. Dionysopoulos
  6. * @license GNU GPL version 3 or, at your option, any later version
  7. * @package akeebaengine
  8. * @version $Id: jpa.php 545 2011-04-09 10:01:17Z nikosdion $
  9. */
  10. // Protection against direct access
  11. defined('AKEEBAENGINE') or die('Restricted access');
  12. if(!function_exists('akstringlen')) {
  13. function akstringlen($string) {
  14. return function_exists('mb_strlen') ? mb_strlen($string,'8bit') : strlen($string);
  15. }
  16. }
  17. /**
  18. * JoomlaPack Archive creation class
  19. *
  20. * JPA Format 1.0 implemented, minus BZip2 compression support
  21. */
  22. class AEArchiverJpa extends AEAbstractArchiver
  23. {
  24. /** @var integer How many files are contained in the archive */
  25. private $_fileCount = 0;
  26. /** @var integer The total size of files contained in the archive as they are stored */
  27. private $_compressedSize = 0;
  28. /** @var integer The total size of files contained in the archive when they are extracted to disk. */
  29. private $_uncompressedSize = 0;
  30. /** @var string The name of the file holding the ZIP's data, which becomes the final archive */
  31. private $_dataFileName;
  32. /** @var string Standard Header signature */
  33. private $_archive_signature = "\x4A\x50\x41";
  34. /** @var string Entity Block signature */
  35. private $_fileHeader = "\x4A\x50\x46";
  36. /** @var string Marks the split archive's extra header */
  37. private $_extraHeaderSplit = "\x4A\x50\x01\x01"; //
  38. /** @var bool Should I use Split ZIP? */
  39. private $_useSplitZIP = false;
  40. /** @var int Maximum fragment size, in bytes */
  41. private $_fragmentSize = 0;
  42. /** @var int Current fragment number */
  43. private $_currentFragment = 1;
  44. /** @var int Total number of fragments */
  45. private $_totalFragments = 1;
  46. /** @var string Archive full path without extension */
  47. private $_dataFileNameBase = '';
  48. /** @var bool Should I store symlinks as such (no dereferencing?) */
  49. private $_symlink_store_target = false;
  50. /**
  51. * Extend the bootstrap code to add some define's used by the JPA format engine
  52. * @see backend/akeeba/abstract/AEAbstractArchiver#__bootstrap_code()
  53. */
  54. protected function __bootstrap_code()
  55. {
  56. if(!defined('_AKEEBA_COMPRESSION_THRESHOLD'))
  57. {
  58. $config = AEFactory::getConfiguration();
  59. define("_AKEEBA_COMPRESSION_THRESHOLD", $config->get('engine.archiver.common.big_file_threshold')); // Don't compress files over this size
  60. /**
  61. * Akeeba Backup and JPA Format version change chart:
  62. * Akeeba Backup 3.0: JPA Format 1.1 is used
  63. * Akeeba Backup 3.1: JPA Format 1.2 with file modification timestamp is used
  64. */
  65. define( '_JPA_MAJOR', 1 ); // JPA Format major version number
  66. define( '_JPA_MINOR', 2 ); // JPA Format minor version number
  67. }
  68. parent::__bootstrap_code();
  69. }
  70. /**
  71. * Initialises the archiver class, creating the archive from an existent
  72. * installer's JPA archive.
  73. *
  74. * @param string $sourceJPAPath Absolute path to an installer's JPA archive
  75. * @param string $targetArchivePath Absolute path to the generated archive
  76. * @param array $options A named key array of options (optional)
  77. * @access public
  78. */
  79. public function initialize( $targetArchivePath, $options = array() )
  80. {
  81. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "AEArchiverJpa :: new instance - archive $targetArchivePath");
  82. $this->_dataFileName = $targetArchivePath;
  83. // NEW 2.3: Should we enable Split ZIP feature?
  84. $registry = AEFactory::getConfiguration();
  85. $fragmentsize = $registry->get('engine.archiver.common.part_size', 0);
  86. if($fragmentsize >= 65536)
  87. {
  88. // If the fragment size is AT LEAST 64Kb, enable Split ZIP
  89. $this->_useSplitZIP = true;
  90. $this->_fragmentSize = $fragmentsize;
  91. // Indicate that we have at least 1 part
  92. $statistics = AEFactory::getStatistics();
  93. $statistics->updateMultipart(1);
  94. $this->_totalFragments = 1;
  95. AEUtilLogger::WriteLog(_AE_LOG_INFO, "AEArchiverJpa :: Spanned JPA creation enabled");
  96. $this->_dataFileNameBase = dirname($targetArchivePath).'/'.basename($targetArchivePath,'.jpa');
  97. $this->_dataFileName = $this->_dataFileNameBase.'.j01';
  98. }
  99. // NEW 2.3: Should I use Symlink Target Storage?
  100. $dereferencesymlinks = $registry->get('engine.archiver.common.dereference_symlinks', true);
  101. if(!$dereferencesymlinks)
  102. {
  103. // We are told not to dereference symlinks. Are we on Windows?
  104. if (function_exists('php_uname'))
  105. {
  106. $isWindows = stristr(php_uname(), 'windows');
  107. }
  108. else
  109. {
  110. $isWindows = (DIRECTORY_SEPARATOR == '\\');
  111. }
  112. // If we are not on Windows, enable symlink target storage
  113. $this->_symlink_store_target = !$isWindows;
  114. }
  115. // Try to kill the archive if it exists
  116. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "AEArchiverJpa :: Killing old archive");
  117. $fp = @fopen( $this->_dataFileName, "wb" );
  118. if (!($fp === false)) {
  119. @ftruncate( $fp,0 );
  120. @fclose( $fp );
  121. } else {
  122. if( file_exists($this->_dataFileName) ) @unlink( $this->_dataFileName );
  123. @touch( $this->_dataFileName );
  124. if(function_exists('chmod')) {
  125. chmod($this->_dataFileName, 0666);
  126. }
  127. }
  128. // Write the initial instance of the archive header
  129. $this->_writeArchiveHeader();
  130. if($this->getError()) return;
  131. }
  132. /**
  133. * Updates the Standard Header with current information
  134. */
  135. public function finalize()
  136. {
  137. // If Spanned JPA and there is no .jpa file, rename the last fragment to .jpa
  138. if($this->_useSplitZIP)
  139. {
  140. $extension = substr($this->_dataFileName, -3);
  141. if($extension != '.jpa')
  142. {
  143. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Renaming last JPA part to .JPA extension');
  144. $newName = $this->_dataFileNameBase.'.jpa';
  145. if(!@rename($this->_dataFileName, $newName))
  146. {
  147. $this->setError('Could not rename last JPA part to .JPA extension.');
  148. return false;
  149. }
  150. $this->_dataFileName = $newName;
  151. }
  152. // Finally, point to the first part so that we can re-write the correct header information
  153. if($this->_totalFragments > 1)
  154. {
  155. $this->_dataFileName = $this->_dataFileNameBase.'.j01';
  156. }
  157. }
  158. // Re-write the archive header
  159. $this->_writeArchiveHeader();
  160. if($this->getError()) return;
  161. }
  162. /**
  163. * Returns a string with the extension (including the dot) of the files produced
  164. * by this class.
  165. * @return string
  166. */
  167. public function getExtension()
  168. {
  169. return '.jpa';
  170. }
  171. /**
  172. * The most basic file transaction: add a single entry (file or directory) to
  173. * the archive.
  174. *
  175. * @param bool $isVirtual If true, the next parameter contains file data instead of a file name
  176. * @param string $sourceNameOrData Absolute file name to read data from or the file data itself is $isVirtual is true
  177. * @param string $targetName The (relative) file name under which to store the file in the archive
  178. * @return True on success, false otherwise
  179. * @since 1.2.1
  180. * @access protected
  181. * @abstract
  182. */
  183. protected function _addFile( $isVirtual, &$sourceNameOrData, $targetName )
  184. {
  185. static $configuration;
  186. $isDir = false;
  187. $isSymlink = false;
  188. if(is_null($isVirtual)) $isVirtual = false;
  189. $compressionMethod = 0;
  190. if($isVirtual)
  191. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "-- Adding $targetName to archive (virtual data)");
  192. else AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "-- Adding $targetName to archive (source: $sourceNameOrData)");
  193. if(!$configuration) {
  194. $configuration = AEFactory::getConfiguration();
  195. }
  196. $timer = AEFactory::getTimer();
  197. // Initialize archive file pointer
  198. $fp = null;
  199. // Initialize inode change timestamp
  200. $filectime = 0;
  201. if(!$configuration->get('volatile.engine.archiver.processingfile',false))
  202. {
  203. // Uncache data -- WHY DO THAT?!
  204. /**
  205. $configuration->set('volatile.engine.archiver.sourceNameOrData', null);
  206. $configuration->set('volatile.engine.archiver.unc_len', null);
  207. $configuration->set('volatile.engine.archiver.resume', null);
  208. $configuration->set('volatile.engine.archiver.processingfile',false);
  209. /**/
  210. // See if it's a directory
  211. $isDir = $isVirtual ? false : is_dir($sourceNameOrData);
  212. // See if it's a symlink (w/out dereference)
  213. $isSymlink = false;
  214. if($this->_symlink_store_target && !$isVirtual)
  215. {
  216. $isSymlink = is_link($sourceNameOrData);
  217. }
  218. // Get real size before compression
  219. if($isVirtual)
  220. {
  221. $fileSize = akstringlen($sourceNameOrData);
  222. $filectime = time();
  223. }
  224. else
  225. {
  226. if($isSymlink)
  227. {
  228. $fileSize = akstringlen( @readlink($sourceNameOrData) );
  229. }
  230. else
  231. {
  232. // Is the file readable?
  233. if(!is_readable($sourceNameOrData) && !$isDir)
  234. {
  235. // Unreadable files won't be recorded in the archive file
  236. $this->setWarning( 'Unreadable file '.$sourceNameOrData.'. Check permissions');
  237. return false;
  238. }
  239. // Get the filesize
  240. $fileSize = $isDir ? 0 : @filesize($sourceNameOrData);
  241. $filectime = $isDir ? 0 : @filemtime($sourceNameOrData);
  242. }
  243. }
  244. // Decide if we will compress
  245. if ($isDir || $isSymlink) {
  246. $compressionMethod = 0; // don't compress directories...
  247. } else {
  248. // Do we have plenty of memory left?
  249. $memLimit = ini_get("memory_limit");
  250. if(strstr($memLimit, 'M')) {
  251. $memLimit = (int)$memLimit * 1048576;
  252. } elseif(strstr($totalRAM, 'K')) {
  253. $memLimit = (int)$memLimit * 1024;
  254. } elseif(strstr($memLimit, 'G')) {
  255. $memLimit = (int)$memLimit * 1073741824;
  256. } else {
  257. $memLimit = (int)$memLimit;
  258. }
  259. if( is_numeric($memLimit) && ($memLimit < 0) ) $memLimit = ""; // 1.2a3 -- Rare case with memory_limit < 0, e.g. -1Mb!
  260. if (($memLimit == "") || ($fileSize >= _AKEEBA_COMPRESSION_THRESHOLD)) {
  261. // No memory limit, or over 1Mb files => always compress up to 1Mb files (otherwise it times out)
  262. $compressionMethod = ($fileSize <= _AKEEBA_COMPRESSION_THRESHOLD) ? 1 : 0;
  263. } elseif ( function_exists("memory_get_usage") ) {
  264. // PHP can report memory usage, see if there's enough available memory; Joomla! alone eats about 5-6Mb! This code is called on files <= 1Mb
  265. $memLimit = $this->_return_bytes( $memLimit );
  266. $availableRAM = $memLimit - memory_get_usage();
  267. $compressionMethod = (($availableRAM / 2.5) >= $fileSize) ? 1 : 0;
  268. } else {
  269. // PHP can't report memory usage, compress only files up to 512Kb (conservative approach) and hope it doesn't break
  270. $compressionMethod = ($fileSize <= 524288) ? 1 : 0;
  271. }
  272. }
  273. $compressionMethod = function_exists("gzcompress") ? $compressionMethod : 0;
  274. $storedName = $targetName;
  275. /* "Entity Description BLock" segment. */
  276. $unc_len = &$fileSize; // File size
  277. $storedName .= ($isDir) ? "/" : "";
  278. if ($compressionMethod == 1) {
  279. if($isVirtual)
  280. {
  281. $udata =& $sourceNameOrData;
  282. }
  283. else
  284. {
  285. // Get uncompressed data
  286. $udata = @file_get_contents( $sourceNameOrData ); // PHP > 4.3.0 saves us the trouble
  287. }
  288. if ($udata === FALSE) {
  289. // Unreadable file, skip it.
  290. $this->setWarning( 'Unreadable file '.$sourceNameOrData.'. Check permissions');
  291. return false;
  292. } else {
  293. // Proceed with compression
  294. $zdata = @gzcompress($udata);
  295. if ($zdata === false) {
  296. // If compression fails, let it behave like no compression was available
  297. $c_len = &$unc_len;
  298. $compressionMethod = 0;
  299. } else {
  300. unset( $udata );
  301. $zdata = substr(substr($zdata, 0, -4), 2);
  302. $c_len = akstringlen($zdata);
  303. }
  304. }
  305. } else {
  306. $c_len = $unc_len;
  307. // Test for unreadable files
  308. if(!$isVirtual && !$isSymlink && !$isDir)
  309. {
  310. $myfp = @fopen($sourceNameOrData, 'rb');
  311. if($myfp === false)
  312. {
  313. // Unreadable file, skip it.
  314. $this->setWarning( 'Unreadable file '.$sourceNameOrData.'. Check permissions');
  315. return false;
  316. }
  317. @fclose($myfp);
  318. }
  319. }
  320. $this->_compressedSize += $c_len; // Update global data
  321. $this->_uncompressedSize += $fileSize; // Update global data
  322. $this->_fileCount++;
  323. // Get file permissions
  324. $perms = 0755;
  325. if(!$isVirtual) {
  326. if(@file_exists($sourceNameOrData)) {
  327. if(@is_file($sourceNameOrData) || @is_link($sourceNameOrData)) {
  328. if(@is_readable($sourceNameOrData)) {
  329. $perms = @fileperms( $sourceNameOrData );
  330. }
  331. }
  332. }
  333. }
  334. // Calculate Entity Description Block length
  335. $blockLength = 21 + akstringlen($storedName) ;
  336. if($filectime > 0) $blockLength += 8; // If we need to store the file mod date
  337. // Get file type
  338. if( (!$isDir) && (!$isSymlink) ) {
  339. $fileType = 1;
  340. } elseif($isSymlink) {
  341. $fileType = 2;
  342. } elseif($isDir) {
  343. $fileType = 0;
  344. }
  345. // If it's a split ZIP file, we've got to make sure that the header can fit in the part
  346. if($this->_useSplitZIP)
  347. {
  348. // Compare to free part space
  349. clearstatcache();
  350. $current_part_size = @filesize($this->_dataFileName);
  351. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  352. if($free_space <= $blockLength)
  353. {
  354. // Not enough space on current part, create new part
  355. if(!$this->_createNewPart())
  356. {
  357. $this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
  358. return false;
  359. }
  360. }
  361. }
  362. // Open data file for output
  363. $fp = @fopen( $this->_dataFileName, "ab");
  364. if ($fp === false)
  365. {
  366. $this->setError("Could not open archive file '{$this->_dataFileName}' for append!");
  367. return false;
  368. }
  369. $this->_fwrite( $fp, $this->_fileHeader ); // Entity Description Block header
  370. if($this->getError()) return false;
  371. $this->_fwrite( $fp, pack('v', $blockLength) ); // Entity Description Block header length
  372. $this->_fwrite( $fp, pack('v', akstringlen($storedName) ) ); // Length of entity path
  373. $this->_fwrite( $fp, $storedName ); // Entity path
  374. $this->_fwrite( $fp, pack('C', $fileType ) ); // Entity type
  375. $this->_fwrite( $fp, pack('C', $compressionMethod ) ); // Compression method
  376. $this->_fwrite( $fp, pack('V', $c_len ) ); // Compressed size
  377. $this->_fwrite( $fp, pack('V', $unc_len ) ); // Uncompressed size
  378. $this->_fwrite( $fp, pack('V', $perms ) ); // Entity permissions
  379. // Timestamp Extra Field, only for files
  380. if($filectime > 0) {
  381. $this->_fwrite( $fp, "\x00\x01" ); // Extra Field Identifier
  382. $this->_fwrite( $fp, pack('v', 8) ); // Extra Field Length
  383. $this->_fwrite( $fp, pack('V', $filectime) ); // Timestamp
  384. }
  385. // Cache useful information about the file
  386. if(!$isDir && !$isSymlink && !$isVirtual)
  387. {
  388. $configuration->set('volatile.engine.archiver.unc_len', $unc_len);
  389. $configuration->set('volatile.engine.archiver.sourceNameOrData', $sourceNameOrData);
  390. }
  391. }
  392. else
  393. {
  394. // If we are continuing file packing we have an uncompressed, non-virtual file.
  395. // We need to set up these variables so as not to throw any PHP notices.
  396. $isDir = false;
  397. $isSymlink = false;
  398. $isVirtual = false;
  399. $compressionMethod = 0;
  400. // Create a file pointer to the archive file
  401. $fp = @fopen( $this->_dataFileName, "ab");
  402. if ($fp === false)
  403. {
  404. $this->setError("Could not open archive file '{$this->_dataFileName}' for append!");
  405. return false;
  406. }
  407. }
  408. /* "File data" segment. */
  409. if ($compressionMethod == 1) {
  410. if(!$this->_useSplitZIP)
  411. {
  412. // Just dump the compressed data
  413. $this->_fwrite( $fp, $zdata );
  414. if($this->getError()) {
  415. @fclose($fp);
  416. return false;
  417. }
  418. }
  419. else
  420. {
  421. // Split ZIP. Check if we need to split the part in the middle of the data.
  422. clearstatcache();
  423. $current_part_size = @filesize($this->_dataFileName);
  424. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  425. if($free_space >= akstringlen($zdata) )
  426. {
  427. // Write in one part
  428. $this->_fwrite( $fp, $zdata );
  429. if($this->getError()) {
  430. @fclose($fp);
  431. return false;
  432. }
  433. }
  434. else
  435. {
  436. $bytes_left = akstringlen($zdata);
  437. while($bytes_left > 0)
  438. {
  439. clearstatcache();
  440. $current_part_size = @filesize($this->_dataFileName);
  441. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  442. // Split between parts - Write first part
  443. $this->_fwrite( $fp, $zdata, min(akstringlen($zdata), $free_space) );
  444. if($this->getError()) {
  445. @fclose($fp);
  446. return false;
  447. }
  448. // Get the rest of the data
  449. $bytes_left = akstringlen($zdata) - $free_space;
  450. if($bytes_left > 0)
  451. {
  452. // Create new part
  453. @fclose($fp);
  454. if(!$this->_createNewPart())
  455. {
  456. // Die if we couldn't create the new part
  457. $this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
  458. return false;
  459. }
  460. else
  461. {
  462. // Close the old data file
  463. @fclose($fp);
  464. // Open data file for output
  465. $fp = @fopen( $this->_dataFileName, "ab");
  466. if ($fp === false)
  467. {
  468. $this->setError("Could not open archive file {$this->_dataFileName} for append!");
  469. return false;
  470. }
  471. }
  472. $zdata = substr($zdata, -$bytes_left);
  473. }
  474. }
  475. }
  476. }
  477. unset( $zdata );
  478. } elseif ( (!$isDir) && (!$isSymlink) ) {
  479. if($isVirtual)
  480. {
  481. if(!$this->_useSplitZIP)
  482. {
  483. // Just dump the data
  484. $this->_fwrite( $fp, $sourceNameOrData );
  485. if($this->getError()) {
  486. @fclose($fp);
  487. return false;
  488. }
  489. }
  490. else
  491. {
  492. // Split JPA. Check if we need to split the part in the middle of the data.
  493. clearstatcache();
  494. $current_part_size = @filesize($this->_dataFileName);
  495. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  496. if($free_space >= akstringlen($sourceNameOrData) )
  497. {
  498. // Write in one part
  499. $this->_fwrite( $fp, $sourceNameOrData );
  500. if($this->getError()) return false;
  501. }
  502. else
  503. {
  504. $bytes_left = akstringlen($sourceNameOrData);
  505. while($bytes_left > 0)
  506. {
  507. clearstatcache();
  508. $current_part_size = @filesize($this->_dataFileName);
  509. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  510. // Split between parts - Write first part
  511. $this->_fwrite( $fp, $sourceNameOrData, min(akstringlen($sourceNameOrData), $free_space) );
  512. if($this->getError()) {
  513. @fclose($fp);
  514. return false;
  515. }
  516. // Get the rest of the data
  517. $rest_size = akstringlen($sourceNameOrData) - $free_space;
  518. if($rest_size > 0)
  519. {
  520. // Create new part
  521. if(!$this->_createNewPart())
  522. {
  523. // Die if we couldn't create the new part
  524. $this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
  525. @fclose($fp);
  526. return false;
  527. }
  528. else
  529. {
  530. // Close the old data file
  531. @fclose($fp);
  532. // Open data file for output
  533. $fp = @fopen( $this->_dataFileName, "ab");
  534. if ($fp === false)
  535. {
  536. $this->setError("Could not open archive file {$this->_dataFileName} for append!");
  537. return false;
  538. }
  539. }
  540. $zdata = substr($sourceNameOrData, -$rest_size);
  541. }
  542. $bytes_left = $rest_size;
  543. } // end while
  544. }
  545. }
  546. }
  547. else
  548. {
  549. // IMPORTANT! Only this case can be spanned across steps: uncompressed, non-virtual data
  550. // Load cached data if we're resumming file packing
  551. if( $configuration->get('volatile.engine.archiver.processingfile',false) )
  552. {
  553. $sourceNameOrData = $configuration->get('volatile.engine.archiver.sourceNameOrData', '');
  554. $unc_len = $configuration->get('volatile.engine.archiver.unc_len', 0);
  555. $resume = $configuration->get('volatile.engine.archiver.resume', 0);
  556. }
  557. // Copy the file contents, ignore directories
  558. $zdatafp = @fopen( $sourceNameOrData, "rb" );
  559. if( $zdatafp === FALSE )
  560. {
  561. $this->setWarning('Unreadable file '.$sourceNameOrData.'. Check permissions');
  562. @fclose($fp);
  563. return false;
  564. }
  565. else
  566. {
  567. // Seek to the resume point if required
  568. if( $configuration->get('volatile.engine.archiver.processingfile',false) )
  569. {
  570. // Seek to new offset
  571. $seek_result = @fseek($zdatafp, $resume);
  572. if( $seek_result === -1 )
  573. {
  574. // What?! We can't resume!
  575. $this->setError(sprintf('Could not resume packing of file %s. Your archive is damaged!', $sourceNameOrData));
  576. @fclose($zdatafp);
  577. @fclose($fp);
  578. return false;
  579. }
  580. // Doctor the uncompressed size to match the remainder of the data
  581. $unc_len = $unc_len - $resume;
  582. }
  583. if(!$this->_useSplitZIP)
  584. {
  585. while( !feof($zdatafp) && ($timer->getTimeLeft() > 0) && ($unc_len > 0) ) {
  586. $zdata = fread($zdatafp, AKEEBA_CHUNK);
  587. $this->_fwrite( $fp, $zdata, min(akstringlen($zdata), AKEEBA_CHUNK) );
  588. $unc_len -= min(akstringlen($zdata), AKEEBA_CHUNK);
  589. if($this->getError()) {
  590. @fclose($zdatafp);
  591. @fclose($fp);
  592. return false;
  593. }
  594. }
  595. // WARNING!!! The extra $unc_len != 0 check is necessary as PHP won't reach EOF for 0-byte files.
  596. if(!feof($zdatafp) && ($unc_len != 0))
  597. {
  598. // We have to break, or we'll time out!
  599. $resume = @ftell($zdatafp);
  600. $configuration->set('volatile.engine.archiver.resume', $resume);
  601. $configuration->set('volatile.engine.archiver.processingfile',true);
  602. @fclose($zdatafp);
  603. @fclose($fp);
  604. return true;
  605. }
  606. }
  607. else
  608. {
  609. // Split JPA - Do we have enough space to host the whole file?
  610. clearstatcache();
  611. $current_part_size = @filesize($this->_dataFileName);
  612. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  613. if($free_space >= $unc_len)
  614. {
  615. // Yes, it will fit inside this part, do quick copy
  616. while( !feof($zdatafp) && ($timer->getTimeLeft() > 0) && ($unc_len > 0) ) {
  617. $zdata = fread($zdatafp, AKEEBA_CHUNK);
  618. $this->_fwrite( $fp, $zdata, min(akstringlen($zdata), AKEEBA_CHUNK) );
  619. //$unc_len -= min(akstringlen($zdata), AKEEBA_CHUNK);
  620. $unc_len -= AKEEBA_CHUNK;
  621. if($this->getError()) {
  622. @fclose($zdatafp);
  623. @fclose($fp);
  624. return false;
  625. }
  626. }
  627. //if(!feof($zdatafp) && ($unc_len != 0))
  628. if(!feof($zdatafp) && ($unc_len > 0))
  629. {
  630. // We have to break, or we'll time out!
  631. $resume = @ftell($zdatafp);
  632. $configuration->set('volatile.engine.archiver.resume', $resume);
  633. $configuration->set('volatile.engine.archiver.processingfile',true);
  634. @fclose($zdatafp);
  635. @fclose($fp);
  636. return true;
  637. }
  638. }
  639. else
  640. {
  641. // No, we'll have to split between parts. We'll loop until we run
  642. // out of space.
  643. while( !feof($zdatafp) && ($timer->getTimeLeft() > 0) )
  644. {
  645. clearstatcache();
  646. $current_part_size = @filesize($this->_dataFileName);
  647. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  648. // Find optimal chunk size
  649. $chunk_size_primary = min(AKEEBA_CHUNK, $free_space);
  650. if($chunk_size_primary <= 0) $chunk_size_primary = max(AKEEBA_CHUNK, $free_space);
  651. // Calculate if we have to read some more data (smaller chunk size)
  652. // and how many times we must read w/ the primary chunk size
  653. $chunk_size_secondary = $free_space % $chunk_size_primary;
  654. $loop_times = ($free_space - $chunk_size_secondary) / $chunk_size_primary;
  655. // Read and write with the primary chunk size
  656. for( $i = 1; $i <= $loop_times; $i++ )
  657. {
  658. $zdata = fread($zdatafp, $chunk_size_primary);
  659. $this->_fwrite( $fp, $zdata, min(akstringlen($zdata), $chunk_size_primary) );
  660. //$unc_len -= min(akstringlen($zdata), $chunk_size_primary);
  661. $unc_len -= $chunk_size_primary;
  662. if($this->getError()) {
  663. @fclose($zdatafp);
  664. @fclose($fp);
  665. return false;
  666. }
  667. // Do we have enough time to proceed?
  668. //if( (!feof($zdatafp)) && ($unc_len != 0) && ($timer->getTimeLeft() <= 0) ) {
  669. if( (!feof($zdatafp)) && ($unc_len >= 0) && ($timer->getTimeLeft() <= 0) ) {
  670. // No, we have to break, or we'll time out!
  671. $resume = @ftell($zdatafp);
  672. $configuration->set('volatile.engine.archiver.resume', $resume);
  673. $configuration->set('volatile.engine.archiver.processingfile',true);
  674. @fclose($zdatafp);
  675. @fclose($fp);
  676. return true;
  677. }
  678. }
  679. // Read and write w/ secondary chunk size, if non-zero
  680. if($chunk_size_secondary > 0)
  681. {
  682. $zdata = fread($zdatafp, $chunk_size_secondary);
  683. $this->_fwrite( $fp, $zdata, min(akstringlen($zdata), $chunk_size_secondary) );
  684. //$unc_len -= min(akstringlen($zdata), $chunk_size_secondary);
  685. $unc_len -= $chunk_size_secondary;
  686. if($this->getError()) {
  687. @fclose($zdatafp);
  688. @fclose($fp);
  689. return false;
  690. }
  691. }
  692. // Do we have enough time to proceed?
  693. //if( (!feof($zdatafp)) && ($unc_len != 0) && ($timer->getTimeLeft() <= 0) ) {
  694. if( (!feof($zdatafp)) && ($unc_len >= 0) && ($timer->getTimeLeft() <= 0) ) {
  695. // No, we have to break, or we'll time out!
  696. $resume = @ftell($zdatafp);
  697. $configuration->set('volatile.engine.archiver.resume', $resume);
  698. $configuration->set('volatile.engine.archiver.processingfile',true);
  699. // ...and create a new part as well
  700. if(!$this->_createNewPart())
  701. {
  702. // Die if we couldn't create the new part
  703. $this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
  704. @fclose($zdatafp);
  705. @fclose($fp);
  706. return false;
  707. }
  708. // ...then, return
  709. @fclose($zdatafp);
  710. @fclose($fp);
  711. return true;
  712. }
  713. // Create new JPA part, but only if we'll have more data to write
  714. //if(!feof($zdatafp) && ($unc_len != 0) && ($unc_len > 0) )
  715. if(!feof($zdatafp) && ($unc_len > 0) )
  716. {
  717. if(!$this->_createNewPart())
  718. {
  719. // Die if we couldn't create the new part
  720. $this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
  721. @fclose($zdatafp);
  722. @fclose($fp);
  723. return false;
  724. }
  725. else
  726. {
  727. // Close the old data file
  728. fclose($fp);
  729. // We have created the part. If the user asked for immediate post-proc, break step now.
  730. if($configuration->get('engine.postproc.common.after_part',0))
  731. {
  732. $resume = @ftell($zdatafp);
  733. $configuration->set('volatile.engine.archiver.resume', $resume);
  734. $configuration->set('volatile.engine.archiver.processingfile',true);
  735. $configuration->set('volatile.breakflag',true);
  736. @fclose($zdatafp);
  737. @fclose($fp);
  738. return true;
  739. }
  740. // Open data file for output
  741. $fp = @fopen( $this->_dataFileName, "ab");
  742. if ($fp === false)
  743. {
  744. $this->setError("Could not open archive file {$this->_dataFileName} for append!");
  745. @fclose($zdatafp);
  746. return false;
  747. }
  748. }
  749. }
  750. } // end while
  751. }
  752. }
  753. @fclose( $zdatafp );
  754. }
  755. }
  756. }
  757. elseif($isSymlink)
  758. {
  759. $this->_fwrite($fp, @readlink($sourceNameOrData) );
  760. }
  761. @fclose( $fp );
  762. //AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "DEBUG -- Added $targetName to archive");
  763. // Uncache data
  764. $configuration->set('volatile.engine.archiver.sourceNameOrData', null);
  765. $configuration->set('volatile.engine.archiver.unc_len', null);
  766. $configuration->set('volatile.engine.archiver.resume', null);
  767. $configuration->set('volatile.engine.archiver.processingfile',false);
  768. // ... and return TRUE = success
  769. return TRUE;
  770. }
  771. // ------------------------------------------------------------------------
  772. // Archiver-specific utility functions
  773. // ------------------------------------------------------------------------
  774. /**
  775. * Outputs a Standard Header at the top of the file
  776. *
  777. */
  778. private function _writeArchiveHeader()
  779. {
  780. $fp = @fopen( $this->_dataFileName, 'r+' );
  781. if($fp === false)
  782. {
  783. $this->setError('Could not open '.$this->_dataFileName.' for writing. Check permissions and open_basedir restrictions.');
  784. return false;
  785. }
  786. // Calculate total header size
  787. $headerSize = 19; // Standard Header
  788. if($this->_useSplitZIP) $headerSize += 8; // Spanned JPA header
  789. $this->_fwrite( $fp, $this->_archive_signature ); // ID string (JPA)
  790. if($this->getError()) return false;
  791. $this->_fwrite( $fp, pack('v', $headerSize) ); // Header length; fixed to 19 bytes
  792. $this->_fwrite( $fp, pack('C', _JPA_MAJOR ) ); // Major version
  793. $this->_fwrite( $fp, pack('C', _JPA_MINOR ) ); // Minor version
  794. $this->_fwrite( $fp, pack('V', $this->_fileCount ) ); // File count
  795. $this->_fwrite( $fp, pack('V', $this->_uncompressedSize ) ); // Size of files when extracted
  796. $this->_fwrite( $fp, pack('V', $this->_compressedSize ) ); // Size of files when stored
  797. // Do I need to add a split archive's header too?
  798. if($this->_useSplitZIP)
  799. {
  800. $this->_fwrite( $fp, $this->_extraHeaderSplit); // Signature
  801. $this->_fwrite( $fp, pack('v', 4) ); // Extra field length
  802. $this->_fwrite( $fp, pack('v', $this->_totalFragments) ); // Number of parts
  803. }
  804. @fclose( $fp );
  805. if( function_exists('chmod') )
  806. {
  807. @chmod($this->_dataFileName, 0755);
  808. }
  809. }
  810. private function _createNewPart($finalPart = false)
  811. {
  812. // Push the previous part if we have to post-process it immediately
  813. $configuration = AEFactory::getConfiguration();
  814. if($configuration->get('engine.postproc.common.after_part',0))
  815. {
  816. // The first part needs its header overwritten during archive
  817. // finalization. Skip it from immediate processing.
  818. if($this->_currentFragment != 1)
  819. {
  820. $this->finishedPart[] = $this->_dataFileName;
  821. }
  822. }
  823. $this->_totalFragments++;
  824. $this->_currentFragment = $this->_totalFragments;
  825. if($finalPart)
  826. {
  827. $this->_dataFileName = $this->_dataFileNameBase.'.jpa';
  828. }
  829. else
  830. {
  831. $this->_dataFileName = $this->_dataFileNameBase.'.j'.sprintf('%02d', $this->_currentFragment);
  832. }
  833. AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Creating new JPA part #'.$this->_currentFragment.', file '.$this->_dataFileName);
  834. $statistics = AEFactory::getStatistics();
  835. $statistics->updateMultipart($this->_totalFragments);
  836. // Try to remove any existing file
  837. @unlink($this->_dataFileName);
  838. // Touch the new file
  839. $result = @touch($this->_dataFileName);
  840. if(function_exists('chmod')) {
  841. chmod($this->_dataFileName, 0666);
  842. }
  843. // Try to write 6 bytes to it
  844. if($result) {
  845. $result = @file_put_contents($this->_dataFileName, 'AKEEBA') == 6;
  846. }
  847. if($result) {
  848. @unlink($this->_dataFileName);
  849. $result = @touch($this->_dataFileName);
  850. if(function_exists('chmod')) {
  851. chmod($this->_dataFileName, 0666);
  852. }
  853. }
  854. return $result;
  855. }
  856. }