/administrator/components/com_akeeba/akeeba/engines/archiver/jpa.php
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
- <?php
- /**
- * Akeeba Engine
- * The modular PHP5 site backup engine
- * @copyright Copyright (c)2009-2012 Nicholas K. Dionysopoulos
- * @license GNU GPL version 3 or, at your option, any later version
- * @package akeebaengine
- * @version $Id: jpa.php 545 2011-04-09 10:01:17Z nikosdion $
- */
-
- // Protection against direct access
- defined('AKEEBAENGINE') or die('Restricted access');
-
- if(!function_exists('akstringlen')) {
- function akstringlen($string) {
- return function_exists('mb_strlen') ? mb_strlen($string,'8bit') : strlen($string);
- }
- }
-
- /**
- * JoomlaPack Archive creation class
- *
- * JPA Format 1.0 implemented, minus BZip2 compression support
- */
- class AEArchiverJpa extends AEAbstractArchiver
- {
- /** @var integer How many files are contained in the archive */
- private $_fileCount = 0;
-
- /** @var integer The total size of files contained in the archive as they are stored */
- private $_compressedSize = 0;
-
- /** @var integer The total size of files contained in the archive when they are extracted to disk. */
- private $_uncompressedSize = 0;
-
- /** @var string The name of the file holding the ZIP's data, which becomes the final archive */
- private $_dataFileName;
-
- /** @var string Standard Header signature */
- private $_archive_signature = "\x4A\x50\x41";
-
- /** @var string Entity Block signature */
- private $_fileHeader = "\x4A\x50\x46";
-
- /** @var string Marks the split archive's extra header */
- private $_extraHeaderSplit = "\x4A\x50\x01\x01"; //
-
- /** @var bool Should I use Split ZIP? */
- private $_useSplitZIP = false;
-
- /** @var int Maximum fragment size, in bytes */
- private $_fragmentSize = 0;
-
- /** @var int Current fragment number */
- private $_currentFragment = 1;
-
- /** @var int Total number of fragments */
- private $_totalFragments = 1;
-
- /** @var string Archive full path without extension */
- private $_dataFileNameBase = '';
-
- /** @var bool Should I store symlinks as such (no dereferencing?) */
- private $_symlink_store_target = false;
-
- /**
- * Extend the bootstrap code to add some define's used by the JPA format engine
- * @see backend/akeeba/abstract/AEAbstractArchiver#__bootstrap_code()
- */
- protected function __bootstrap_code()
- {
- if(!defined('_AKEEBA_COMPRESSION_THRESHOLD'))
- {
- $config = AEFactory::getConfiguration();
- define("_AKEEBA_COMPRESSION_THRESHOLD", $config->get('engine.archiver.common.big_file_threshold')); // Don't compress files over this size
-
- /**
- * Akeeba Backup and JPA Format version change chart:
- * Akeeba Backup 3.0: JPA Format 1.1 is used
- * Akeeba Backup 3.1: JPA Format 1.2 with file modification timestamp is used
- */
- define( '_JPA_MAJOR', 1 ); // JPA Format major version number
- define( '_JPA_MINOR', 2 ); // JPA Format minor version number
-
- }
- parent::__bootstrap_code();
- }
-
- /**
- * Initialises the archiver class, creating the archive from an existent
- * installer's JPA archive.
- *
- * @param string $sourceJPAPath Absolute path to an installer's JPA archive
- * @param string $targetArchivePath Absolute path to the generated archive
- * @param array $options A named key array of options (optional)
- * @access public
- */
- public function initialize( $targetArchivePath, $options = array() )
- {
- AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "AEArchiverJpa :: new instance - archive $targetArchivePath");
- $this->_dataFileName = $targetArchivePath;
-
- // NEW 2.3: Should we enable Split ZIP feature?
- $registry = AEFactory::getConfiguration();
- $fragmentsize = $registry->get('engine.archiver.common.part_size', 0);
- if($fragmentsize >= 65536)
- {
- // If the fragment size is AT LEAST 64Kb, enable Split ZIP
- $this->_useSplitZIP = true;
- $this->_fragmentSize = $fragmentsize;
-
- // Indicate that we have at least 1 part
- $statistics = AEFactory::getStatistics();
- $statistics->updateMultipart(1);
- $this->_totalFragments = 1;
-
- AEUtilLogger::WriteLog(_AE_LOG_INFO, "AEArchiverJpa :: Spanned JPA creation enabled");
- $this->_dataFileNameBase = dirname($targetArchivePath).'/'.basename($targetArchivePath,'.jpa');
- $this->_dataFileName = $this->_dataFileNameBase.'.j01';
- }
-
- // NEW 2.3: Should I use Symlink Target Storage?
- $dereferencesymlinks = $registry->get('engine.archiver.common.dereference_symlinks', true);
- if(!$dereferencesymlinks)
- {
- // We are told not to dereference symlinks. Are we on Windows?
- if (function_exists('php_uname'))
- {
- $isWindows = stristr(php_uname(), 'windows');
- }
- else
- {
- $isWindows = (DIRECTORY_SEPARATOR == '\\');
- }
- // If we are not on Windows, enable symlink target storage
- $this->_symlink_store_target = !$isWindows;
- }
-
- // Try to kill the archive if it exists
- AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "AEArchiverJpa :: Killing old archive");
- $fp = @fopen( $this->_dataFileName, "wb" );
- if (!($fp === false)) {
- @ftruncate( $fp,0 );
- @fclose( $fp );
- } else {
- if( file_exists($this->_dataFileName) ) @unlink( $this->_dataFileName );
- @touch( $this->_dataFileName );
- if(function_exists('chmod')) {
- chmod($this->_dataFileName, 0666);
- }
- }
-
- // Write the initial instance of the archive header
- $this->_writeArchiveHeader();
- if($this->getError()) return;
- }
-
- /**
- * Updates the Standard Header with current information
- */
- public function finalize()
- {
- // If Spanned JPA and there is no .jpa file, rename the last fragment to .jpa
- if($this->_useSplitZIP)
- {
- $extension = substr($this->_dataFileName, -3);
- if($extension != '.jpa')
- {
- AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Renaming last JPA part to .JPA extension');
- $newName = $this->_dataFileNameBase.'.jpa';
- if(!@rename($this->_dataFileName, $newName))
- {
- $this->setError('Could not rename last JPA part to .JPA extension.');
- return false;
- }
- $this->_dataFileName = $newName;
- }
-
- // Finally, point to the first part so that we can re-write the correct header information
- if($this->_totalFragments > 1)
- {
- $this->_dataFileName = $this->_dataFileNameBase.'.j01';
- }
- }
-
- // Re-write the archive header
- $this->_writeArchiveHeader();
-
- if($this->getError()) return;
- }
-
- /**
- * Returns a string with the extension (including the dot) of the files produced
- * by this class.
- * @return string
- */
- public function getExtension()
- {
- return '.jpa';
- }
-
-
- /**
- * The most basic file transaction: add a single entry (file or directory) to
- * the archive.
- *
- * @param bool $isVirtual If true, the next parameter contains file data instead of a file name
- * @param string $sourceNameOrData Absolute file name to read data from or the file data itself is $isVirtual is true
- * @param string $targetName The (relative) file name under which to store the file in the archive
- * @return True on success, false otherwise
- * @since 1.2.1
- * @access protected
- * @abstract
- */
- protected function _addFile( $isVirtual, &$sourceNameOrData, $targetName )
- {
- static $configuration;
-
- $isDir = false;
- $isSymlink = false;
- if(is_null($isVirtual)) $isVirtual = false;
- $compressionMethod = 0;
-
- if($isVirtual)
- AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "-- Adding $targetName to archive (virtual data)");
- else AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "-- Adding $targetName to archive (source: $sourceNameOrData)");
-
- if(!$configuration) {
- $configuration = AEFactory::getConfiguration();
- }
- $timer = AEFactory::getTimer();
-
- // Initialize archive file pointer
- $fp = null;
-
- // Initialize inode change timestamp
- $filectime = 0;
-
- if(!$configuration->get('volatile.engine.archiver.processingfile',false))
- {
- // Uncache data -- WHY DO THAT?!
- /**
- $configuration->set('volatile.engine.archiver.sourceNameOrData', null);
- $configuration->set('volatile.engine.archiver.unc_len', null);
- $configuration->set('volatile.engine.archiver.resume', null);
- $configuration->set('volatile.engine.archiver.processingfile',false);
- /**/
-
- // See if it's a directory
- $isDir = $isVirtual ? false : is_dir($sourceNameOrData);
- // See if it's a symlink (w/out dereference)
- $isSymlink = false;
- if($this->_symlink_store_target && !$isVirtual)
- {
- $isSymlink = is_link($sourceNameOrData);
- }
-
- // Get real size before compression
- if($isVirtual)
- {
- $fileSize = akstringlen($sourceNameOrData);
- $filectime = time();
- }
- else
- {
- if($isSymlink)
- {
- $fileSize = akstringlen( @readlink($sourceNameOrData) );
- }
- else
- {
- // Is the file readable?
- if(!is_readable($sourceNameOrData) && !$isDir)
- {
- // Unreadable files won't be recorded in the archive file
- $this->setWarning( 'Unreadable file '.$sourceNameOrData.'. Check permissions');
- return false;
- }
-
- // Get the filesize
- $fileSize = $isDir ? 0 : @filesize($sourceNameOrData);
- $filectime = $isDir ? 0 : @filemtime($sourceNameOrData);
- }
- }
-
- // Decide if we will compress
- if ($isDir || $isSymlink) {
- $compressionMethod = 0; // don't compress directories...
- } else {
- // Do we have plenty of memory left?
- $memLimit = ini_get("memory_limit");
-
- if(strstr($memLimit, 'M')) {
- $memLimit = (int)$memLimit * 1048576;
- } elseif(strstr($totalRAM, 'K')) {
- $memLimit = (int)$memLimit * 1024;
- } elseif(strstr($memLimit, 'G')) {
- $memLimit = (int)$memLimit * 1073741824;
- } else {
- $memLimit = (int)$memLimit;
- }
-
- if( is_numeric($memLimit) && ($memLimit < 0) ) $memLimit = ""; // 1.2a3 -- Rare case with memory_limit < 0, e.g. -1Mb!
- if (($memLimit == "") || ($fileSize >= _AKEEBA_COMPRESSION_THRESHOLD)) {
- // No memory limit, or over 1Mb files => always compress up to 1Mb files (otherwise it times out)
- $compressionMethod = ($fileSize <= _AKEEBA_COMPRESSION_THRESHOLD) ? 1 : 0;
- } elseif ( function_exists("memory_get_usage") ) {
- // 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
- $memLimit = $this->_return_bytes( $memLimit );
- $availableRAM = $memLimit - memory_get_usage();
- $compressionMethod = (($availableRAM / 2.5) >= $fileSize) ? 1 : 0;
- } else {
- // PHP can't report memory usage, compress only files up to 512Kb (conservative approach) and hope it doesn't break
- $compressionMethod = ($fileSize <= 524288) ? 1 : 0;
- }
- }
-
- $compressionMethod = function_exists("gzcompress") ? $compressionMethod : 0;
-
- $storedName = $targetName;
-
- /* "Entity Description BLock" segment. */
- $unc_len = &$fileSize; // File size
- $storedName .= ($isDir) ? "/" : "";
-
- if ($compressionMethod == 1) {
- if($isVirtual)
- {
- $udata =& $sourceNameOrData;
- }
- else
- {
- // Get uncompressed data
- $udata = @file_get_contents( $sourceNameOrData ); // PHP > 4.3.0 saves us the trouble
- }
-
- if ($udata === FALSE) {
- // Unreadable file, skip it.
- $this->setWarning( 'Unreadable file '.$sourceNameOrData.'. Check permissions');
- return false;
- } else {
- // Proceed with compression
- $zdata = @gzcompress($udata);
- if ($zdata === false) {
- // If compression fails, let it behave like no compression was available
- $c_len = &$unc_len;
- $compressionMethod = 0;
- } else {
- unset( $udata );
- $zdata = substr(substr($zdata, 0, -4), 2);
- $c_len = akstringlen($zdata);
- }
- }
- } else {
- $c_len = $unc_len;
- // Test for unreadable files
- if(!$isVirtual && !$isSymlink && !$isDir)
- {
- $myfp = @fopen($sourceNameOrData, 'rb');
- if($myfp === false)
- {
- // Unreadable file, skip it.
- $this->setWarning( 'Unreadable file '.$sourceNameOrData.'. Check permissions');
- return false;
- }
- @fclose($myfp);
- }
- }
-
- $this->_compressedSize += $c_len; // Update global data
- $this->_uncompressedSize += $fileSize; // Update global data
- $this->_fileCount++;
-
- // Get file permissions
- $perms = 0755;
- if(!$isVirtual) {
- if(@file_exists($sourceNameOrData)) {
- if(@is_file($sourceNameOrData) || @is_link($sourceNameOrData)) {
- if(@is_readable($sourceNameOrData)) {
- $perms = @fileperms( $sourceNameOrData );
- }
- }
- }
- }
-
- // Calculate Entity Description Block length
- $blockLength = 21 + akstringlen($storedName) ;
- if($filectime > 0) $blockLength += 8; // If we need to store the file mod date
-
- // Get file type
- if( (!$isDir) && (!$isSymlink) ) {
- $fileType = 1;
- } elseif($isSymlink) {
- $fileType = 2;
- } elseif($isDir) {
- $fileType = 0;
- }
-
- // If it's a split ZIP file, we've got to make sure that the header can fit in the part
- if($this->_useSplitZIP)
- {
- // Compare to free part space
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
- if($free_space <= $blockLength)
- {
- // Not enough space on current part, create new part
- if(!$this->_createNewPart())
- {
- $this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
- return false;
- }
- }
- }
-
- // Open data file for output
- $fp = @fopen( $this->_dataFileName, "ab");
- if ($fp === false)
- {
- $this->setError("Could not open archive file '{$this->_dataFileName}' for append!");
- return false;
- }
- $this->_fwrite( $fp, $this->_fileHeader ); // Entity Description Block header
- if($this->getError()) return false;
- $this->_fwrite( $fp, pack('v', $blockLength) ); // Entity Description Block header length
- $this->_fwrite( $fp, pack('v', akstringlen($storedName) ) ); // Length of entity path
- $this->_fwrite( $fp, $storedName ); // Entity path
- $this->_fwrite( $fp, pack('C', $fileType ) ); // Entity type
- $this->_fwrite( $fp, pack('C', $compressionMethod ) ); // Compression method
- $this->_fwrite( $fp, pack('V', $c_len ) ); // Compressed size
- $this->_fwrite( $fp, pack('V', $unc_len ) ); // Uncompressed size
- $this->_fwrite( $fp, pack('V', $perms ) ); // Entity permissions
-
- // Timestamp Extra Field, only for files
- if($filectime > 0) {
- $this->_fwrite( $fp, "\x00\x01" ); // Extra Field Identifier
- $this->_fwrite( $fp, pack('v', 8) ); // Extra Field Length
- $this->_fwrite( $fp, pack('V', $filectime) ); // Timestamp
- }
-
- // Cache useful information about the file
- if(!$isDir && !$isSymlink && !$isVirtual)
- {
- $configuration->set('volatile.engine.archiver.unc_len', $unc_len);
- $configuration->set('volatile.engine.archiver.sourceNameOrData', $sourceNameOrData);
- }
- }
- else
- {
- // If we are continuing file packing we have an uncompressed, non-virtual file.
- // We need to set up these variables so as not to throw any PHP notices.
- $isDir = false;
- $isSymlink = false;
- $isVirtual = false;
- $compressionMethod = 0;
-
- // Create a file pointer to the archive file
- $fp = @fopen( $this->_dataFileName, "ab");
- if ($fp === false)
- {
- $this->setError("Could not open archive file '{$this->_dataFileName}' for append!");
- return false;
- }
- }
-
- /* "File data" segment. */
- if ($compressionMethod == 1) {
- if(!$this->_useSplitZIP)
- {
- // Just dump the compressed data
- $this->_fwrite( $fp, $zdata );
- if($this->getError()) {
- @fclose($fp);
- return false;
- }
- }
- else
- {
- // Split ZIP. Check if we need to split the part in the middle of the data.
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
- if($free_space >= akstringlen($zdata) )
- {
- // Write in one part
- $this->_fwrite( $fp, $zdata );
- if($this->getError()) {
- @fclose($fp);
- return false;
- }
- }
- else
- {
- $bytes_left = akstringlen($zdata);
-
- while($bytes_left > 0)
- {
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
-
- // Split between parts - Write first part
- $this->_fwrite( $fp, $zdata, min(akstringlen($zdata), $free_space) );
- if($this->getError()) {
- @fclose($fp);
- return false;
- }
-
- // Get the rest of the data
- $bytes_left = akstringlen($zdata) - $free_space;
-
- if($bytes_left > 0)
- {
- // Create new part
- @fclose($fp);
- if(!$this->_createNewPart())
- {
- // Die if we couldn't create the new part
- $this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
- return false;
- }
- else
- {
- // Close the old data file
- @fclose($fp);
- // Open data file for output
- $fp = @fopen( $this->_dataFileName, "ab");
- if ($fp === false)
- {
- $this->setError("Could not open archive file {$this->_dataFileName} for append!");
- return false;
- }
- }
- $zdata = substr($zdata, -$bytes_left);
- }
- }
- }
- }
- unset( $zdata );
- } elseif ( (!$isDir) && (!$isSymlink) ) {
- if($isVirtual)
- {
- if(!$this->_useSplitZIP)
- {
- // Just dump the data
- $this->_fwrite( $fp, $sourceNameOrData );
- if($this->getError()) {
- @fclose($fp);
- return false;
- }
- }
- else
- {
- // Split JPA. Check if we need to split the part in the middle of the data.
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
- if($free_space >= akstringlen($sourceNameOrData) )
- {
- // Write in one part
- $this->_fwrite( $fp, $sourceNameOrData );
- if($this->getError()) return false;
- }
- else
- {
- $bytes_left = akstringlen($sourceNameOrData);
-
- while($bytes_left > 0)
- {
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
-
- // Split between parts - Write first part
- $this->_fwrite( $fp, $sourceNameOrData, min(akstringlen($sourceNameOrData), $free_space) );
- if($this->getError()) {
- @fclose($fp);
- return false;
- }
-
- // Get the rest of the data
- $rest_size = akstringlen($sourceNameOrData) - $free_space;
- if($rest_size > 0)
- {
- // Create new part
- if(!$this->_createNewPart())
- {
- // Die if we couldn't create the new part
- $this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
- @fclose($fp);
- return false;
- }
- else
- {
- // Close the old data file
- @fclose($fp);
- // Open data file for output
- $fp = @fopen( $this->_dataFileName, "ab");
- if ($fp === false)
- {
- $this->setError("Could not open archive file {$this->_dataFileName} for append!");
- return false;
- }
- }
- $zdata = substr($sourceNameOrData, -$rest_size);
- }
- $bytes_left = $rest_size;
- } // end while
- }
- }
- }
- else
- {
- // IMPORTANT! Only this case can be spanned across steps: uncompressed, non-virtual data
- // Load cached data if we're resumming file packing
- if( $configuration->get('volatile.engine.archiver.processingfile',false) )
- {
- $sourceNameOrData = $configuration->get('volatile.engine.archiver.sourceNameOrData', '');
- $unc_len = $configuration->get('volatile.engine.archiver.unc_len', 0);
- $resume = $configuration->get('volatile.engine.archiver.resume', 0);
- }
-
- // Copy the file contents, ignore directories
- $zdatafp = @fopen( $sourceNameOrData, "rb" );
- if( $zdatafp === FALSE )
- {
- $this->setWarning('Unreadable file '.$sourceNameOrData.'. Check permissions');
- @fclose($fp);
- return false;
- }
- else
- {
- // Seek to the resume point if required
- if( $configuration->get('volatile.engine.archiver.processingfile',false) )
- {
- // Seek to new offset
- $seek_result = @fseek($zdatafp, $resume);
- if( $seek_result === -1 )
- {
- // What?! We can't resume!
- $this->setError(sprintf('Could not resume packing of file %s. Your archive is damaged!', $sourceNameOrData));
- @fclose($zdatafp);
- @fclose($fp);
- return false;
- }
-
- // Doctor the uncompressed size to match the remainder of the data
- $unc_len = $unc_len - $resume;
- }
-
- if(!$this->_useSplitZIP)
- {
- while( !feof($zdatafp) && ($timer->getTimeLeft() > 0) && ($unc_len > 0) ) {
- $zdata = fread($zdatafp, AKEEBA_CHUNK);
- $this->_fwrite( $fp, $zdata, min(akstringlen($zdata), AKEEBA_CHUNK) );
- $unc_len -= min(akstringlen($zdata), AKEEBA_CHUNK);
- if($this->getError()) {
- @fclose($zdatafp);
- @fclose($fp);
- return false;
- }
- }
- // WARNING!!! The extra $unc_len != 0 check is necessary as PHP won't reach EOF for 0-byte files.
- if(!feof($zdatafp) && ($unc_len != 0))
- {
- // We have to break, or we'll time out!
- $resume = @ftell($zdatafp);
- $configuration->set('volatile.engine.archiver.resume', $resume);
- $configuration->set('volatile.engine.archiver.processingfile',true);
- @fclose($zdatafp);
- @fclose($fp);
- return true;
- }
- }
- else
- {
- // Split JPA - Do we have enough space to host the whole file?
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
- if($free_space >= $unc_len)
- {
- // Yes, it will fit inside this part, do quick copy
- while( !feof($zdatafp) && ($timer->getTimeLeft() > 0) && ($unc_len > 0) ) {
- $zdata = fread($zdatafp, AKEEBA_CHUNK);
- $this->_fwrite( $fp, $zdata, min(akstringlen($zdata), AKEEBA_CHUNK) );
- //$unc_len -= min(akstringlen($zdata), AKEEBA_CHUNK);
- $unc_len -= AKEEBA_CHUNK;
- if($this->getError()) {
- @fclose($zdatafp);
- @fclose($fp);
- return false;
- }
- }
- //if(!feof($zdatafp) && ($unc_len != 0))
- if(!feof($zdatafp) && ($unc_len > 0))
- {
- // We have to break, or we'll time out!
- $resume = @ftell($zdatafp);
- $configuration->set('volatile.engine.archiver.resume', $resume);
- $configuration->set('volatile.engine.archiver.processingfile',true);
- @fclose($zdatafp);
- @fclose($fp);
- return true;
- }
- }
- else
- {
- // No, we'll have to split between parts. We'll loop until we run
- // out of space.
-
- while( !feof($zdatafp) && ($timer->getTimeLeft() > 0) )
- {
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
-
- // Find optimal chunk size
- $chunk_size_primary = min(AKEEBA_CHUNK, $free_space);
- if($chunk_size_primary <= 0) $chunk_size_primary = max(AKEEBA_CHUNK, $free_space);
- // Calculate if we have to read some more data (smaller chunk size)
- // and how many times we must read w/ the primary chunk size
- $chunk_size_secondary = $free_space % $chunk_size_primary;
- $loop_times = ($free_space - $chunk_size_secondary) / $chunk_size_primary;
- // Read and write with the primary chunk size
- for( $i = 1; $i <= $loop_times; $i++ )
- {
- $zdata = fread($zdatafp, $chunk_size_primary);
- $this->_fwrite( $fp, $zdata, min(akstringlen($zdata), $chunk_size_primary) );
- //$unc_len -= min(akstringlen($zdata), $chunk_size_primary);
- $unc_len -= $chunk_size_primary;
- if($this->getError()) {
- @fclose($zdatafp);
- @fclose($fp);
- return false;
- }
-
- // Do we have enough time to proceed?
- //if( (!feof($zdatafp)) && ($unc_len != 0) && ($timer->getTimeLeft() <= 0) ) {
- if( (!feof($zdatafp)) && ($unc_len >= 0) && ($timer->getTimeLeft() <= 0) ) {
- // No, we have to break, or we'll time out!
- $resume = @ftell($zdatafp);
- $configuration->set('volatile.engine.archiver.resume', $resume);
- $configuration->set('volatile.engine.archiver.processingfile',true);
- @fclose($zdatafp);
- @fclose($fp);
- return true;
- }
-
- }
-
- // Read and write w/ secondary chunk size, if non-zero
- if($chunk_size_secondary > 0)
- {
- $zdata = fread($zdatafp, $chunk_size_secondary);
- $this->_fwrite( $fp, $zdata, min(akstringlen($zdata), $chunk_size_secondary) );
- //$unc_len -= min(akstringlen($zdata), $chunk_size_secondary);
- $unc_len -= $chunk_size_secondary;
- if($this->getError()) {
- @fclose($zdatafp);
- @fclose($fp);
- return false;
- }
- }
-
- // Do we have enough time to proceed?
- //if( (!feof($zdatafp)) && ($unc_len != 0) && ($timer->getTimeLeft() <= 0) ) {
- if( (!feof($zdatafp)) && ($unc_len >= 0) && ($timer->getTimeLeft() <= 0) ) {
- // No, we have to break, or we'll time out!
- $resume = @ftell($zdatafp);
- $configuration->set('volatile.engine.archiver.resume', $resume);
- $configuration->set('volatile.engine.archiver.processingfile',true);
-
- // ...and create a new part as well
- if(!$this->_createNewPart())
- {
- // Die if we couldn't create the new part
- $this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
- @fclose($zdatafp);
- @fclose($fp);
- return false;
- }
-
- // ...then, return
- @fclose($zdatafp);
- @fclose($fp);
- return true;
- }
-
- // Create new JPA part, but only if we'll have more data to write
- //if(!feof($zdatafp) && ($unc_len != 0) && ($unc_len > 0) )
- if(!feof($zdatafp) && ($unc_len > 0) )
- {
- if(!$this->_createNewPart())
- {
- // Die if we couldn't create the new part
- $this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
- @fclose($zdatafp);
- @fclose($fp);
- return false;
- }
- else
- {
- // Close the old data file
- fclose($fp);
-
- // We have created the part. If the user asked for immediate post-proc, break step now.
- if($configuration->get('engine.postproc.common.after_part',0))
- {
- $resume = @ftell($zdatafp);
- $configuration->set('volatile.engine.archiver.resume', $resume);
- $configuration->set('volatile.engine.archiver.processingfile',true);
-
- $configuration->set('volatile.breakflag',true);
- @fclose($zdatafp);
- @fclose($fp);
- return true;
- }
-
- // Open data file for output
- $fp = @fopen( $this->_dataFileName, "ab");
- if ($fp === false)
- {
- $this->setError("Could not open archive file {$this->_dataFileName} for append!");
- @fclose($zdatafp);
- return false;
- }
- }
- }
- } // end while
- }
- }
- @fclose( $zdatafp );
- }
- }
- }
- elseif($isSymlink)
- {
- $this->_fwrite($fp, @readlink($sourceNameOrData) );
- }
-
- @fclose( $fp );
-
- //AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "DEBUG -- Added $targetName to archive");
-
- // Uncache data
- $configuration->set('volatile.engine.archiver.sourceNameOrData', null);
- $configuration->set('volatile.engine.archiver.unc_len', null);
- $configuration->set('volatile.engine.archiver.resume', null);
- $configuration->set('volatile.engine.archiver.processingfile',false);
-
- // ... and return TRUE = success
- return TRUE;
- }
-
-
- // ------------------------------------------------------------------------
- // Archiver-specific utility functions
- // ------------------------------------------------------------------------
- /**
- * Outputs a Standard Header at the top of the file
- *
- */
- private function _writeArchiveHeader()
- {
- $fp = @fopen( $this->_dataFileName, 'r+' );
- if($fp === false)
- {
- $this->setError('Could not open '.$this->_dataFileName.' for writing. Check permissions and open_basedir restrictions.');
- return false;
- }
-
- // Calculate total header size
- $headerSize = 19; // Standard Header
- if($this->_useSplitZIP) $headerSize += 8; // Spanned JPA header
-
- $this->_fwrite( $fp, $this->_archive_signature ); // ID string (JPA)
- if($this->getError()) return false;
- $this->_fwrite( $fp, pack('v', $headerSize) ); // Header length; fixed to 19 bytes
- $this->_fwrite( $fp, pack('C', _JPA_MAJOR ) ); // Major version
- $this->_fwrite( $fp, pack('C', _JPA_MINOR ) ); // Minor version
- $this->_fwrite( $fp, pack('V', $this->_fileCount ) ); // File count
- $this->_fwrite( $fp, pack('V', $this->_uncompressedSize ) ); // Size of files when extracted
- $this->_fwrite( $fp, pack('V', $this->_compressedSize ) ); // Size of files when stored
-
- // Do I need to add a split archive's header too?
- if($this->_useSplitZIP)
- {
- $this->_fwrite( $fp, $this->_extraHeaderSplit); // Signature
- $this->_fwrite( $fp, pack('v', 4) ); // Extra field length
- $this->_fwrite( $fp, pack('v', $this->_totalFragments) ); // Number of parts
- }
-
- @fclose( $fp );
- if( function_exists('chmod') )
- {
- @chmod($this->_dataFileName, 0755);
- }
- }
-
- private function _createNewPart($finalPart = false)
- {
- // Push the previous part if we have to post-process it immediately
- $configuration = AEFactory::getConfiguration();
- if($configuration->get('engine.postproc.common.after_part',0))
- {
- // The first part needs its header overwritten during archive
- // finalization. Skip it from immediate processing.
- if($this->_currentFragment != 1)
- {
- $this->finishedPart[] = $this->_dataFileName;
- }
- }
-
- $this->_totalFragments++;
- $this->_currentFragment = $this->_totalFragments;
- if($finalPart)
- {
- $this->_dataFileName = $this->_dataFileNameBase.'.jpa';
- }
- else
- {
- $this->_dataFileName = $this->_dataFileNameBase.'.j'.sprintf('%02d', $this->_currentFragment);
- }
- AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Creating new JPA part #'.$this->_currentFragment.', file '.$this->_dataFileName);
- $statistics = AEFactory::getStatistics();
- $statistics->updateMultipart($this->_totalFragments);
- // Try to remove any existing file
- @unlink($this->_dataFileName);
- // Touch the new file
- $result = @touch($this->_dataFileName);
- if(function_exists('chmod')) {
- chmod($this->_dataFileName, 0666);
- }
- // Try to write 6 bytes to it
- if($result) {
- $result = @file_put_contents($this->_dataFileName, 'AKEEBA') == 6;
- }
- if($result) {
- @unlink($this->_dataFileName);
- $result = @touch($this->_dataFileName);
- if(function_exists('chmod')) {
- chmod($this->_dataFileName, 0666);
- }
- }
- return $result;
- }
-
- }