Файловый менеджер - Редактировать - /var/www/html/administrator/components/com_akeebabackup/vendor/akeeba/engine/engine/Archiver/BaseArchiver.php
Ðазад
<?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2026 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Archiver; defined('AKEEBAENGINE') || die(); use Akeeba\Engine\Base\Exceptions\ErrorException; use Akeeba\Engine\Base\Exceptions\WarningException; use Akeeba\Engine\Factory; if (!defined('AKEEBA_CHUNK')) { $configuration = Factory::getConfiguration(); $chunksize = $configuration->get('engine.archiver.common.chunk_size', 1048576); define('AKEEBA_CHUNK', $chunksize); } if (!function_exists('aksubstr')) { /** * Attempt to use mbstring for getting parts of strings * * @param string $string * @param int $start * @param int|null $length * * @return string */ function aksubstr($string, $start, $length = null) { return function_exists('mb_substr') ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length); } } /** * Abstract class for custom archiver implementations */ abstract class BaseArchiver extends BaseFileManagement { /** @var array The last part which has been finalized and waits to be post-processed */ public $finishedPart = []; /** @var resource File pointer to the archive being currently written to */ protected $fp = null; /** @var resource File pointer to the archive's central directory file (for ZIP) */ protected $cdfp = null; /** @var string The name of the file holding the archive's data, which becomes the final archive */ protected $_dataFileName; /** @var string Archive full path without extension */ protected $dataFileNameWithoutExtension = ''; /** @var bool Should I store symlinks as such (no dereferencing?) */ protected $storeSymlinkTarget = false; /** @var int Part size for split archives, in bytes */ protected $partSize = 0; /** @var bool Should I use Split ZIP? */ protected $useSplitArchive = false; /** @var int Permissions for the backup archive part files */ protected $permissions = null; /** * Release file pointers when the object is being serialized * * @codeCoverageIgnore * * @return void */ public function _onSerialize() { $this->_closeAllFiles(); $this->fp = null; $this->cdfp = null; } /** * Release file pointers when the object is being destroyed * * @codeCoverageIgnore * * @return void */ public function __destruct() { $this->_closeAllFiles(); $this->fp = null; $this->cdfp = null; } /** * Create a new archive part file (but does NOT open it for writing) * * @param bool $finalPart True if this is the final part * * @return bool False if creating a new part fails */ abstract protected function createNewPartFile($finalPart = false); /** * Create a new part file and open it for writing * * @param bool $finalPart Is this the final part? * * @return void */ protected function createAndOpenNewPart($finalPart = false) { @$this->fclose($this->fp); $this->fp = null; // Not enough space on current part, create new part if (!$this->createNewPartFile($finalPart)) { $extension = $this->getExtension(); $extension = ltrim(strtoupper($extension), '.'); throw new ErrorException("Could not create new $extension part file " . basename($this->_dataFileName)); } $this->openArchiveForOutput(true); } /** * Create a new backup archive * * @return void * * @throws ErrorException */ protected function createNewBackupArchive() { Factory::getLog()->debug(__CLASS__ . " :: Killing old archive"); $this->fp = $this->fopen($this->_dataFileName, "w"); if ($this->fp === false) { if (file_exists($this->_dataFileName)) { @unlink($this->_dataFileName); } @touch($this->_dataFileName); @chmod($this->_dataFileName, 0666); $this->fp = $this->fopen($this->_dataFileName, "w"); if ($this->fp !== false) { throw new ErrorException("Could not open archive file '{$this->_dataFileName}' for append!"); } } @ftruncate($this->fp, 0); } /** * Opens the backup archive file for output. Returns false if the archive file cannot be opened in binary append * mode. * * @param bool $force Should I forcibly reopen the file? If false, I'll only open the file if the current * file pointer is null. * * @return void */ protected function openArchiveForOutput($force = false) { if (is_null($this->fp) || $force) { $this->fp = $this->fopen($this->_dataFileName, "a"); } if ($this->fp === false) { $this->fp = null; throw new ErrorException("Could not open archive file '{$this->_dataFileName}' for append!"); } } /** * Converts a human formatted size to integer representation of bytes, * e.g. 1M to 1024768 * * @param string $setting The value in human readable format, e.g. "1M" * * @return integer The value in bytes */ protected function humanToIntegerBytes($setting) { $val = trim($setting); $last = strtolower($val[strlen($val) - 1]); if (is_numeric($last)) { return $setting; } switch ($last) { case 't': $val *= 1024; case 'g': $val *= 1024; case 'm': $val *= 1024; case 'k': $val *= 1024; } return (int) $val; } /** * Get the PHP memory limit in bytes * * @return int|null Memory limit in bytes or null if we can't figure it out. */ protected function getMemoryLimit() { if (!function_exists('ini_get')) { return null; } $memLimit = ini_get("memory_limit"); if ((is_numeric($memLimit) && ($memLimit < 0)) || !is_numeric($memLimit)) { $memLimit = 0; // 1.2a3 -- Rare case with memory_limit < 0, e.g. -1Mb! } $memLimit = $this->humanToIntegerBytes($memLimit); return $memLimit; } /** * Enable storing of symlink target if we are not on Windows * * @return void */ protected function enableSymlinkTargetStorage() { $configuration = Factory::getConfiguration(); $dereferenceSymlinks = $configuration->get('engine.archiver.common.dereference_symlinks', true); if ($dereferenceSymlinks) { return; } // We are told not to dereference symlinks. Are we on Windows? $isWindows = (DIRECTORY_SEPARATOR == '\\'); if (function_exists('php_uname')) { $isWindows = stristr(php_uname(), 'windows'); } // If we are not on Windows, enable symlink target storage $this->storeSymlinkTarget = !$isWindows; } /** * Gets the file size and last modification time (also works on virtual files and symlinks) * * @param string $sourceNameOrData File path to the source file or source data (if $isVirtual is true) * @param bool $isVirtual Is this a virtual file? * @param bool $isSymlink Is this a symlink? * @param bool $isDir Is this a directory? * * @return array */ protected function getFileSizeAndModificationTime(&$sourceNameOrData, $isVirtual, $isSymlink, $isDir) { // Get real size before compression if ($isVirtual) { $fileSize = akstrlen($sourceNameOrData); $fileModTime = time(); return [$fileSize, $fileModTime]; } if ($isSymlink) { $fileSize = akstrlen(@readlink($sourceNameOrData)); $fileModTime = 0; return [$fileSize, $fileModTime]; } // Is the file readable? if (!is_readable($sourceNameOrData) && !$isDir) { // Really, REALLY check if it is readable (PHP sometimes lies, dammit!) $myFP = @$this->fopen($sourceNameOrData, 'r'); if ($myFP === false) { // Unreadable file, skip it. throw new WarningException('Unreadable file ' . $sourceNameOrData . '. Check permissions'); } @$this->fclose($myFP); } // Get the file size $fileSize = $isDir ? 0 : @filesize($sourceNameOrData); $fileModTime = $isDir ? 0 : @filemtime($sourceNameOrData); return [$fileSize, $fileModTime]; } /** * Get the preferred compression method for a file * * @param int $fileSize File size in bytes * @param int $memLimit Memory limit in bytes * @param bool $isDir Is it a directory? * @param bool $isSymlink Is it a symlink? * * @return int Compression method to use: 0 (uncompressed) or 1 (gzip deflate) */ protected function getCompressionMethod($fileSize, $memLimit, $isDir, $isSymlink) { // If we don't have gzip installed we can't compress anything if (!function_exists("gzcompress")) { return 0; } // Don't compress directories or symlinks if ($isDir || $isSymlink) { return 0; } // Do not compress files over the compression threshold if ($fileSize >= _AKEEBA_COMPRESSION_THRESHOLD) { return 0; } // No memory limit, file smaller than the compression threshold: always compress. if (is_numeric($memLimit) && ($memLimit == 0)) { return 1; } // Non-zero memory limit, PHP can report memory usage, see if there's enough memory. if (is_numeric($memLimit) && function_exists("memory_get_usage")) { $availableRAM = $memLimit - memory_get_usage(); // Conservative approach: if the file size is over 40% of the available memory we won't compress. $compressionMethod = (($availableRAM / 2.5) >= $fileSize) ? 1 : 0; return $compressionMethod; } // Non-zero memory limit, PHP can't report memory usage, compress only files up to 512Kb (very conservative) return ($fileSize <= 524288) ? 1 : 0; } /** * Checks if the file exists and is readable * * @param string $sourceNameOrData The path to the file being compressed, or the raw file data for virtual files * @param bool $isVirtual Is this a virtual file? * @param bool $isSymlink Is this a symlink? * @param bool $isDir Is this a directory? * * @return void * * @throws WarningException */ protected function testIfFileExists(&$sourceNameOrData, &$isVirtual, &$isDir, &$isSymlink) { if ($isVirtual || $isDir) { return; } if (!@file_exists($sourceNameOrData)) { if ($isSymlink) { throw new WarningException('The symlink ' . $sourceNameOrData . ' points to a file or folder that no longer exists and will NOT be backed up.'); } throw new WarningException('The file ' . $sourceNameOrData . ' no longer exists and will NOT be backed up. Are you backing up temporary or cache data?'); } if (!@is_readable($sourceNameOrData)) { throw new WarningException('Unreadable file ' . $sourceNameOrData . '. Check permissions.'); } } /** * Try to get the compressed data for a file * * @param string $sourceNameOrData * @param bool $isVirtual * @param int $compressionMethod * @param string $zdata * @param int $unc_len * @param int $c_len * * @return void */ protected function getZData(&$sourceNameOrData, &$isVirtual, &$compressionMethod, &$zdata, &$unc_len, &$c_len) { // Get uncompressed data $udata =& $sourceNameOrData; if (!$isVirtual) { $udata = @file_get_contents($sourceNameOrData); if ($udata === false) { @clearstatcache($sourceNameOrData); $fileExists = @file_exists($sourceNameOrData); if (!$fileExists) { throw new ErrorException( sprintf( 'The file %s went away before we could start putting it in the backup archive. We cannot continue the backup. Your archive is damaged! If this is a temporary, cache, or log file we advise you to exclude it from the backup. If its containing directory is meant to include temporary, cache, or log file we recommend that you exclude the contents of the file\'s containing folder.', $sourceNameOrData ) ); } throw new ErrorException('Unreadable file ' . $sourceNameOrData . '. Check permissions. Your archive is corrupt!'); } } // If the compression fails, we will let it behave like no compression was available $c_len = $unc_len; $compressionMethod = 0; // Proceed with compression $zdata = @gzcompress($udata); if ($zdata !== false) { // The compression succeeded unset($udata); $compressionMethod = 1; $zdata = aksubstr($zdata, 2, -4); $c_len = akstrlen($zdata); } } /** * Returns the bytes available for writing data to the current part file (i.e. part size minus current offset) * * @return int */ protected function getPartFreeSize() { clearstatcache(); $current_part_size = @filesize($this->_dataFileName); return (int) $this->partSize - ($current_part_size === false ? 0 : $current_part_size); } /** * Enable split archive creation where possible * * @return void */ protected function enableSplitArchives() { $configuration = Factory::getConfiguration(); $partSize = $configuration->get('engine.archiver.common.part_size', 0); // If the part size is less than 64Kb we won't enable split archives if ($partSize < 65536) { return; } $extension = $this->getExtension(); $altExtension = substr($extension, 0, 2) . '01'; $archiveTypeUppercase = strtoupper(substr($extension, 1)); Factory::getLog()->info(__CLASS__ . " :: Split $archiveTypeUppercase creation enabled"); $this->useSplitArchive = true; $this->partSize = $partSize; $this->dataFileNameWithoutExtension = dirname($this->_dataFileName) . '/' . basename($this->_dataFileName, $extension); $this->_dataFileName = $this->dataFileNameWithoutExtension . $altExtension; // Indicate that we have at least 1 part $statistics = Factory::getStatistics(); $statistics->updateMultipart(1); } /** * Write a file's GZip compressed data to the archive, taking into account archive splitting * * @param string $zdata The compressed data to write to the archive * * @return void */ protected function putRawDataIntoArchive(&$zdata) { // Single part archive. Just dump the compressed data. if (!$this->useSplitArchive) { $this->fwrite($this->fp, $zdata); return; } // Split JPA. Check if we need to split the part in the middle of the data. $freeSpaceInPart = $this->getPartFreeSize(); // Nope. We have enough space to write all of the data in this part. if ($freeSpaceInPart >= akstrlen($zdata)) { $this->fwrite($this->fp, $zdata); return; } $bytesLeftInData = akstrlen($zdata); while ($bytesLeftInData > 0) { // Try to write to the archive. We can only write as much bytes as the free space in the backup archive OR // the total data bytes left, whichever is lower. $bytesWritten = $this->fwrite($this->fp, $zdata, min($bytesLeftInData, $freeSpaceInPart)); // Since we may have written fewer bytes than anticipated we use the real bytes written for calculations $freeSpaceInPart -= $bytesWritten; $bytesLeftInData -= $bytesWritten; // If we still have data to write, remove the part already written and keep the rest if ($bytesLeftInData > 0) { $zdata = aksubstr($zdata, -$bytesLeftInData); } // If the part file is full create a new one if ($freeSpaceInPart <= 0) { // Create new part $this->createAndOpenNewPart(); // Get its free space $freeSpaceInPart = $this->getPartFreeSize(); } } // Tell PHP to free up some memory $zdata = null; } /** * Begin or resume adding an uncompressed file into the archive. * * IMPORTANT! Only this case can be spanned across steps: uncompressed, non-virtual data * * @param string $sourceNameOrData The path to the file we are reading from. * @param int $fileLength The file size we are supposed to read, in bytes. * @param int $resumeOffset Offset in the file to resume reading from * * @return bool True to indicate more processing is required in the next step */ protected function putUncompressedFileIntoArchive(&$sourceNameOrData, $fileLength = 0, $resumeOffset = null) { $expectedTotalLength = $fileLength; // Copy the file contents, ignore directories $sourceFilePointer = @fopen($sourceNameOrData, "r"); if ($sourceFilePointer === false) { clearstatcache($sourceNameOrData); $fileExists = @file_exists($sourceNameOrData); if (!$fileExists && $resumeOffset === null) { throw new ErrorException( sprintf( 'The file %s went away before we could start putting it in the backup archive. We cannot continue the backup. Your archive is damaged! If this is a temporary, cache, or log file we advise you to exclude it from the backup. If its containing directory is meant to include temporary, cache, or log file we recommend that you exclude the contents of the file\'s containing folder.', $sourceNameOrData ) ); } if (!$fileExists) { throw new ErrorException( sprintf( 'The file %s went away while putting it in the backup archive. We cannot continue the backup. Your archive is damaged! If this is a temporary, cache, or log file we advise you to exclude it from the backup. If its containing directory is meant to include temporary, cache, or log file we recommend that you exclude the contents of the file\'s containing folder.', $sourceNameOrData ) ); } // If we have already written the file header and can't read the data your archive is busted. throw new ErrorException('Unreadable file ' . $sourceNameOrData . '. Check permissions. Your archive is corrupt!'); } // Seek to the resume point if required if (!is_null($resumeOffset)) { // Seek to new offset $seek_result = @fseek($sourceFilePointer, $resumeOffset); if ($seek_result === -1) { // What?! We can't resume! $this->conditionalFileClose($sourceFilePointer); Factory::getLog()->warning( sprintf('The file %s went away!', $sourceNameOrData) ); throw new ErrorException(sprintf('Could not resume packing of file %s. Your archive is damaged!', $sourceNameOrData)); } // Change the uncompressed size to reflect the remaining data $fileLength -= $resumeOffset; } $mustBreak = $this->putDataFromFileIntoArchive($sourceFilePointer, $fileLength, $expectedTotalLength); $this->conditionalFileClose($sourceFilePointer); return $mustBreak; } /** * Return the requested permissions for the backup archive file. * * @return int * @since 8.0.0 */ protected function getPermissions(): int { if (!is_null($this->permissions)) { return $this->permissions; } $configuration = Factory::getConfiguration(); $permissions = $configuration->get('engine.archiver.common.permissions', '0666') ?: '0666'; $this->permissions = octdec($permissions); return $this->permissions; } /** * Put up to $fileLength bytes of the file pointer $sourceFilePointer into the backup archive. Returns true if we * ran out of time and need to perform a step break. Returns false when the whole quantity of data has been copied. * Throws an ErrorException if something terrible happens. * * @param resource $sourceFilePointer The pointer to the input file * @param int $fileLength How many bytes to copy * * @return bool True to indicate we need to resume packing the file in the next step */ private function putDataFromFileIntoArchive(&$sourceFilePointer, &$fileLength, $expectedTotalLength) { // Get references to engine objects we're going to be using $configuration = Factory::getConfiguration(); $timer = Factory::getTimer(); $isEOF = false; // Quick copy data into the archive, AKEEBA_CHUNK bytes at a time while (!$isEOF && ($timer->getTimeLeft() > 0) && ($fileLength > 0)) { // DEBUG - Artificial delay for development purposes if (defined('AKEEBA_DEBUG_BIG_FILE_MULTIPART_DELAY')) { usleep(AKEEBA_DEBUG_BIG_FILE_MULTIPART_DELAY); } // Normally I read up to AKEEBA_CHUNK bytes at a time, unless the remaining $fileLength is smaller. $chunkSize = min(AKEEBA_CHUNK, $fileLength); // Do I have a split ZIP? if ($this->useSplitArchive) { // I must only read up to the free space in the part file if it's less than AKEEBA_CHUNK. $free_space = $this->getPartFreeSize(); $chunkSize = min($free_space, AKEEBA_CHUNK, $fileLength); // If I ran out of free space I have to create a new part file. if ($free_space <= 0) { $this->createAndOpenNewPart(); // 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)) { $resumeOffset = @ftell($sourceFilePointer); $this->conditionalFileClose($sourceFilePointer); $configuration->set('volatile.engine.archiver.resume', $resumeOffset); $configuration->set('volatile.engine.archiver.processingfile', true); $configuration->set('volatile.breakflag', true); // Always close the open part when immediate post-processing is requested @$this->fclose($this->fp); $this->fp = null; return true; } // No immediate post-proc. Recalculate the optimal chunk size. $free_space = $this->getPartFreeSize(); $chunkSize = min($free_space, AKEEBA_CHUNK, $fileLength); } } // Read some data and write it to the backup archive part file $data = fread($sourceFilePointer, $chunkSize); $bytesWritten = $this->fwrite($this->fp, $data, akstrlen($data)); // Subtract the written bytes from the bytes left to write $fileLength -= $bytesWritten; // Have we reached the End of File? $isEOF = feof($sourceFilePointer); /** * CATCH FILE SIZE CHANGE: THE FILE GREW * * PHP claims we have not reached EOF, but $fileLength is 0 which means that we have read as much data as * there was to read from the file. This is suspicious. * * First, we check if this is really true by trying to read one more byte. If this fails, it means that we * actually have reached EOF, but either PHP or the underlying Operating System did something funky. In this * case we just shrug and move on. * * If, however, we COULD read another byte it means that the file grew between starting to back it up and * reaching this point. In this case we will warn the user. Most likely we have a temporary, or log file * (usually a PHP error log) which should be **excluded** from the backup. */ if (!$isEOF && $fileLength === 0) { $junk = fread($sourceFilePointer, 1); if ($junk === false || is_string($junk) && strlen($junk) === 0) { break; } $metaData = stream_get_meta_data($sourceFilePointer); @clearstatcache($metaData['uri']); $actualFileSize = @filesize($metaData['uri']); Factory::getLog()->warning( sprintf( 'The file %s grew while putting it in the backup archive. Expected size: %u bytes. Actual size: %u. If this is a temporary, cache, or log file we advise you to exclude it from the backup. If its containing directory is meant to include temporary, cache, or log file we recommend that you exclude the contents of the file\'s containing folder.', $metaData['uri'], $expectedTotalLength, $actualFileSize ) ); break; } /** * CATCH FILE SIZE CHANGE: THE FILE SHRUNK * * PHP claims that we have reached EOF, but $fileLength is greater than zero, i.e. we have not read as much * data from the file as its size was at the start of the backup. This means that the file shrunk between * start of backup and now. * * Since we cannot create data out of nothing we have to warn the user about this problem and immediately * fail the backup. Most likely this was a temporary file, therefore it (or its containing directory) needs * to be excluded from the backup. */ if ($isEOF && ($fileLength > 0)) { $metaData = stream_get_meta_data($sourceFilePointer); @clearstatcache($metaData['uri']); $fileExists = @file_exists($metaData['uri']); $actualFileSize = $fileExists ? @filesize($metaData['uri']) : null; if ($fileExists) { Factory::getLog()->warning( sprintf('The file %s shrunk during backup.', $metaData['uri']) ); throw new ErrorException( sprintf( 'The file %s shrunk while putting it in the backup archive. Expected size: %u bytes. Actual size: %u. We cannot continue the backup. Your archive is damaged! If this is a temporary, cache, or log file we advise you to exclude it from the backup. If its containing directory is meant to include temporary, cache, or log file we recommend that you exclude the contents of the file\'s containing folder.', $metaData['uri'], $expectedTotalLength, $actualFileSize ) ); } else { Factory::getLog()->warning( sprintf('The file %s went away during backup.', $metaData['uri']) ); throw new ErrorException( sprintf( 'The file %s went away while putting it in the backup archive. We cannot continue the backup. Your archive is damaged! If this is a temporary, cache, or log file we advise you to exclude it from the backup. If its containing directory is meant to include temporary, cache, or log file we recommend that you exclude the contents of the file\'s containing folder.', $metaData['uri'] ) ); } } } // WARNING!!! The extra $unc_len != 0 check is necessary as PHP won't reach EOF for 0-byte files. if (!feof($sourceFilePointer) && ($fileLength != 0)) { // We have to break, or we'll time out! $resumeOffset = @ftell($sourceFilePointer); $this->conditionalFileClose($sourceFilePointer); $configuration->set('volatile.engine.archiver.resume', $resumeOffset); $configuration->set('volatile.engine.archiver.processingfile', true); return true; } return false; } }
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка