Files
2024-07-15 11:28:08 +02:00

236 lines
5.8 KiB
PHP

<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Archiver;
defined('AKEEBAENGINE') || die();
use Akeeba\Engine\Base\Exceptions\ErrorException;
use Akeeba\Engine\Factory;
if (!function_exists('akstrlen'))
{
/**
* Attempt to use mbstring for calculating the binary string length.
*
* @param $string
*
* @return int
*/
function akstrlen($string)
{
return function_exists('mb_strlen') ? mb_strlen($string, '8bit') : strlen($string);
}
}
/**
* Abstract class for an archiver using managed file pointers
*/
abstract class BaseFileManagement extends Base
{
/** @var resource File pointer to the archive's central directory file (for ZIP) */
protected $cdfp = null;
/** @var resource File pointer to the archive being currently written to */
protected $fp = null;
/** @var array An array of the last open files for writing and their last written to offsets */
private $fileOffsets = [];
/** @var array An array of open file pointers */
private $filePointers = [];
/** @var null|string The last filename fwrite() wrote to */
private $lastFileName = null;
/** @var null|resource The last file pointer fwrite() wrote to */
private $lastFilePointer = null;
/**
* Release file pointers when the object is being destroyed
*
* @codeCoverageIgnore
*
* @return void
*/
public function __destruct()
{
$this->_closeAllFiles();
$this->fp = null;
$this->cdfp = null;
}
/**
* Release file pointers when the object is being serialized
*
* @codeCoverageIgnore
*
* @return void
*/
public function _onSerialize()
{
$this->_closeAllFiles();
$this->fp = null;
$this->cdfp = null;
}
/**
* Closes all open files known to this archiver object
*
* @return void
*/
protected function _closeAllFiles()
{
if (!empty($this->filePointers))
{
foreach ($this->filePointers as $file => $fp)
{
$this->conditionalFileClose($fp);
unset($this->filePointers[$file]);
}
}
}
/**
* Closes an already open file
*
* @param resource $fp The file pointer to close
*
* @return boolean
*/
protected function fclose(&$fp)
{
$result = true;
$offset = array_search($fp, $this->filePointers, true);
if (!is_null($fp) && is_resource($fp))
{
$result = $this->conditionalFileClose($fp);
}
if ($offset !== false)
{
unset($this->filePointers[$offset]);
}
$fp = null;
return $result;
}
protected function fcloseByName($file)
{
if (!array_key_exists($file, $this->filePointers))
{
return true;
}
$ret = $this->fclose($this->filePointers[$file]);
if (array_key_exists($file, $this->filePointers))
{
unset($this->filePointers[$file]);
}
return $ret;
}
/**
* Opens a file, if it's not already open, or returns its cached file pointer if it's already open
*
* @param string $file The filename to open
* @param string $mode File open mode, defaults to binary write
*
* @return resource
*/
protected function fopen($file, $mode = 'w')
{
if (!array_key_exists($file, $this->filePointers))
{
//Factory::getLog()->debug("Opening backup archive $file with mode $mode");
$this->filePointers[$file] = @fopen($file, $mode);
// If we open a file for append we have to seek to the correct offset
if (substr($mode, 0, 1) == 'a')
{
if (isset($this->fileOffsets[$file]))
{
Factory::getLog()->debug("Truncating backup archive file $file to " . $this->fileOffsets[$file] . " bytes");
@ftruncate($this->filePointers[$file], $this->fileOffsets[$file]);
}
fseek($this->filePointers[$file], 0, SEEK_END);
}
}
return $this->filePointers[$file];
}
/**
* Write to file, defeating magic_quotes_runtime settings (pure binary write)
*
* @param resource $fp Handle to a file
* @param string $data The data to write to the file
* @param integer $p_len Maximum length of data to write
*
* @return int The number of bytes written
*
* @throws ErrorException When writing to the file is not possible
*/
protected function fwrite($fp, $data, $p_len = null)
{
if ($fp !== $this->lastFilePointer)
{
$this->lastFilePointer = $fp;
$this->lastFileName = array_search($fp, $this->filePointers, true);
}
$len = is_null($p_len) ? (akstrlen($data)) : $p_len;
$ret = fwrite($fp, $data, $len);
if (($ret === false) || (abs(($ret - $len)) >= 1))
{
// Log debug information about the archive file's existence and current size. This helps us figure out if
// there is a server-imposed maximum file size limit.
clearstatcache();
$fileExists = @file_exists($this->lastFileName) ? 'exists' : 'does NOT exist';
$currentSize = @filesize($this->lastFileName);
Factory::getLog()->debug(sprintf("%s::_fwrite() ERROR!! Cannot write to archive file %s. The file %s. File size %s bytes after writing %s of %d bytes. Please check the output directory permissions and make sure you have enough disk space available. If this does not help, please set up a Part Size for Split Archives LOWER than this size and retry backing up.", __CLASS__, $this->lastFileName, $fileExists, $currentSize, $ret, $len));
throw new ErrorException(sprintf("Couldn\'t write to the archive file; check the output directory permissions and make sure you have enough disk space available. [len=%s / %s]", $ret, $len));
}
if ($this->lastFileName !== false)
{
$this->fileOffsets[$this->lastFileName] = @ftell($fp);
}
return $ret;
}
/**
* Removes a file path from the list of resumable offsets
*
* @param $filename
*/
protected function removeFromOffsetsList($filename)
{
if (isset($this->fileOffsets[$filename]))
{
unset($this->fileOffsets[$filename]);
}
}
}