You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
827 lines
32 KiB
827 lines
32 KiB
<?php
|
|
|
|
/**
|
|
*
|
|
* @package Duplicator
|
|
* @copyright (c) 2021, Snapcreek LLC
|
|
*/
|
|
|
|
namespace Duplicator\Libs\DupArchive;
|
|
|
|
use Duplicator\Libs\DupArchive\Headers\DupArchiveDirectoryHeader;
|
|
use Duplicator\Libs\DupArchive\Headers\DupArchiveFileHeader;
|
|
use Duplicator\Libs\DupArchive\Headers\DupArchiveHeader;
|
|
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderFileHeader;
|
|
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderGlobHeader;
|
|
use Duplicator\Libs\DupArchive\Info\DupArchiveInfo;
|
|
use Duplicator\Libs\DupArchive\Processors\DupArchiveDirectoryProcessor;
|
|
use Duplicator\Libs\DupArchive\Processors\DupArchiveFileProcessor;
|
|
use Duplicator\Libs\DupArchive\Processors\DupArchiveProcessingFailure;
|
|
use Duplicator\Libs\DupArchive\States\DupArchiveCreateState;
|
|
use Duplicator\Libs\DupArchive\States\DupArchiveExpandState;
|
|
use Duplicator\Libs\DupArchive\States\DupArchiveSimpleCreateState;
|
|
use Duplicator\Libs\DupArchive\States\DupArchiveSimpleExpandState;
|
|
use Duplicator\Libs\DupArchive\Utils\DupArchiveScanUtil;
|
|
use Duplicator\Libs\DupArchive\Utils\DupArchiveUtil;
|
|
use Duplicator\Libs\Snap\Snap32BitSizeLimitException;
|
|
use Duplicator\Libs\Snap\SnapIO;
|
|
use Exception;
|
|
use stdClass;
|
|
|
|
/**
|
|
* $re = '/\/\/.* /';
|
|
* $subst = '';
|
|
*
|
|
* $re = '/(.*^\s*)(namespace.*?)(;)(.*)/sm';
|
|
* $subst = '$2 { $4}';
|
|
*
|
|
* $re = '/\/\*.*?\*\//s'; ''
|
|
* $re = '/\n\s*\n/s'; "\n"
|
|
*/
|
|
class DupArchiveEngine extends DupArchive
|
|
{
|
|
const EXCEPTION_NON_FATAL = 0;
|
|
const EXCEPTION_FATAL = 1;
|
|
|
|
/** @var string|null */
|
|
public static $targetRootPath = null;
|
|
|
|
/**
|
|
* Dup archive init
|
|
*
|
|
* @param DupArchiveLoggerBase $logger logger object
|
|
* @param string|null $targetRootPath archive target root path or null not root path
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function init(DupArchiveLoggerBase $logger, $targetRootPath = null)
|
|
{
|
|
DupArchiveUtil::$logger = $logger;
|
|
self::$targetRootPath = $targetRootPath;
|
|
}
|
|
|
|
/**
|
|
* Get local path
|
|
*
|
|
* @param string $path item path
|
|
* @param DupArchiveCreateState $createState base path
|
|
*
|
|
* @return string
|
|
*/
|
|
protected static function getLocalPath($path, DupArchiveCreateState $createState)
|
|
{
|
|
$result = '';
|
|
if (self::$targetRootPath === null) {
|
|
$result = substr($path, $createState->basepathLength);
|
|
$result = ltrim($result, '/');
|
|
if ($createState->newBasePath !== null) {
|
|
$result = $createState->newBasePath . $result;
|
|
}
|
|
} else {
|
|
$safePath = SnapIO::safePathUntrailingslashit($path);
|
|
$result = ltrim(
|
|
$createState->newBasePath . preg_replace('/^' . preg_quote(self::$targetRootPath, '/') . '(.*)/m', '$1', $safePath),
|
|
'/'
|
|
);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Get archvie info from path
|
|
*
|
|
* @param string $filepath archvie path
|
|
*
|
|
* @return DupArchiveInfo
|
|
*/
|
|
public static function getArchiveInfo($filepath)
|
|
{
|
|
$archiveInfo = new DupArchiveInfo();
|
|
|
|
DupArchiveUtil::log("archive size=" . filesize($filepath));
|
|
$archiveHandle = SnapIO::fopen($filepath, 'rb');
|
|
$archiveInfo->archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle);
|
|
$moreToRead = true;
|
|
|
|
while ($moreToRead) {
|
|
$headerType = self::getNextHeaderType($archiveHandle);
|
|
|
|
// DupArchiveUtil::log("next header type=$headerType: " . ftell($archiveHandle));
|
|
|
|
switch ($headerType) {
|
|
case self::HEADER_TYPE_FILE:
|
|
$fileHeader = DupArchiveFileHeader::readFromArchive($archiveHandle, true, true);
|
|
$archiveInfo->fileHeaders[] = $fileHeader;
|
|
DupArchiveUtil::log("file" . $fileHeader->relativePath);
|
|
break;
|
|
|
|
case self::HEADER_TYPE_DIR:
|
|
$directoryHeader = DupArchiveDirectoryHeader::readFromArchive($archiveHandle, true);
|
|
|
|
$archiveInfo->directoryHeaders[] = $directoryHeader;
|
|
break;
|
|
|
|
case self::HEADER_TYPE_NONE:
|
|
$moreToRead = false;
|
|
}
|
|
}
|
|
return $archiveInfo;
|
|
}
|
|
|
|
/**
|
|
* Add folder to archive
|
|
*
|
|
* can't span requests since create state can't store list of files
|
|
*
|
|
* @param string $archiveFilepath archive file
|
|
* @param string $directory folder to add
|
|
* @param string $basepath base path to consider (?)
|
|
* @param boolean $includeFiles if true include files
|
|
* @param string $newBasepath new base path
|
|
* @param int $globSize global size
|
|
*
|
|
* @return stdClass
|
|
*/
|
|
public static function addDirectoryToArchiveST(
|
|
$archiveFilepath,
|
|
$directory,
|
|
$basepath,
|
|
$includeFiles = false,
|
|
$newBasepath = null,
|
|
$globSize = DupArchiveCreateState::DEFAULT_GLOB_SIZE
|
|
) {
|
|
if ($includeFiles) {
|
|
$scan = DupArchiveScanUtil::createScanObject($directory);
|
|
} else {
|
|
$scan = new stdClass();
|
|
$scan->Files = array();
|
|
$scan->Dirs = array();
|
|
}
|
|
|
|
$createState = new DupArchiveSimpleCreateState();
|
|
|
|
$createState->archiveOffset = filesize($archiveFilepath);
|
|
$createState->archivePath = $archiveFilepath;
|
|
$createState->basePath = $basepath;
|
|
$createState->basepathLength = strlen($basepath);
|
|
$createState->timerEnabled = false;
|
|
$createState->globSize = $globSize;
|
|
$createState->newBasePath = $newBasepath;
|
|
|
|
self::addItemsToArchive($createState, $scan);
|
|
|
|
$retVal = new stdClass();
|
|
$retVal->numDirsAdded = $createState->currentDirectoryIndex;
|
|
$retVal->numFilesAdded = $createState->currentFileIndex;
|
|
|
|
if ($createState->skippedFileCount > 0) {
|
|
throw new Exception("One or more files were were not able to be added when adding {$directory} to {$archiveFilepath}");
|
|
} elseif ($createState->skippedDirectoryCount > 0) {
|
|
throw new Exception("One or more directories were not able to be added when adding {$directory} to {$archiveFilepath}");
|
|
}
|
|
|
|
return $retVal;
|
|
}
|
|
|
|
/**
|
|
* Add relative file to archive
|
|
*
|
|
* @param string $archiveFilepath archive file
|
|
* @param string $filepath file to add
|
|
* @param string $relativePath relative path in archive
|
|
* @param int $globSize global size
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function addRelativeFileToArchiveST(
|
|
$archiveFilepath,
|
|
$filepath,
|
|
$relativePath,
|
|
$globSize = DupArchiveCreateState::DEFAULT_GLOB_SIZE
|
|
) {
|
|
$createState = new DupArchiveSimpleCreateState();
|
|
|
|
$createState->archiveOffset = filesize($archiveFilepath);
|
|
$createState->archivePath = $archiveFilepath;
|
|
$createState->basePath = null;
|
|
$createState->basepathLength = 0;
|
|
$createState->timerEnabled = false;
|
|
$createState->globSize = $globSize;
|
|
|
|
$scan = new stdClass();
|
|
|
|
$scan->Files = array();
|
|
$scan->Dirs = array();
|
|
|
|
$scan->Files[] = $filepath;
|
|
|
|
if ($relativePath != null) {
|
|
$scan->FileAliases = array();
|
|
$scan->FileAliases[$filepath] = $relativePath;
|
|
}
|
|
|
|
self::addItemsToArchive($createState, $scan);
|
|
}
|
|
|
|
/**
|
|
* Add file in archive from src
|
|
*
|
|
* @param string|resource $archive Archive path or archive handle
|
|
* @param string $src source string
|
|
* @param string $relativeFilePath relative path
|
|
* @param int $forceSize if 0 size is auto of content is filled of \0 char to size
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function addFileFromSrc(
|
|
$archive,
|
|
$src,
|
|
$relativeFilePath,
|
|
$forceSize = 0
|
|
) {
|
|
if (is_resource($archive)) {
|
|
$archiveHandle = $archive;
|
|
SnapIO::fseek($archiveHandle, 0, SEEK_SET);
|
|
} else {
|
|
if (($archiveHandle = SnapIO::fopen($archive, 'r+b')) == false) {
|
|
throw new Exception('Can\'t open archive');
|
|
}
|
|
}
|
|
|
|
$createState = new DupArchiveSimpleCreateState();
|
|
$createState->archiveOffset = SnapIO::ftell($archiveHandle);
|
|
$createState->basePath = dirname($relativeFilePath);
|
|
$createState->basepathLength = strlen($createState->basePath);
|
|
$createState->timerEnabled = false;
|
|
|
|
if ($forceSize == 0) {
|
|
$archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle);
|
|
$createState->isCompressed = $archiveHeader->isCompressed;
|
|
} else {
|
|
// ff force size is enables the src isn't compress
|
|
$createState->isCompressed = false;
|
|
}
|
|
|
|
SnapIO::fseek($archiveHandle, 0, SEEK_END);
|
|
|
|
$result = DupArchiveFileProcessor::writeFileSrcToArchive($createState, $archiveHandle, $src, $relativeFilePath, $forceSize);
|
|
|
|
if (!is_resource($archive)) {
|
|
SnapIO::fclose($archiveHandle);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Add file in archive from src
|
|
*
|
|
* @param string $archiveFilepath archive path
|
|
* @param string $src source string
|
|
* @param string $relativeFilePath relative path
|
|
* @param int $offset start search location
|
|
* @param int $sizeToSearch max size where search
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function replaceFileContent(
|
|
$archiveFilepath,
|
|
$src,
|
|
$relativeFilePath,
|
|
$offset = 0,
|
|
$sizeToSearch = 0
|
|
) {
|
|
if (($archiveHandle = SnapIO::fopen($archiveFilepath, 'r+b')) == false) {
|
|
throw new Exception('Can\'t open archive');
|
|
}
|
|
|
|
if (($filePos = self::searchPath($archiveHandle, $relativeFilePath, $offset, $sizeToSearch)) == false) {
|
|
return false;
|
|
}
|
|
$fileHeader = DupArchiveReaderFileHeader::readFromArchive($archiveHandle);
|
|
$globHeader = DupArchiveReaderGlobHeader::readFromArchive($archiveHandle);
|
|
SnapIO::fseek($archiveHandle, $filePos);
|
|
|
|
$createState = new DupArchiveSimpleCreateState();
|
|
$createState->archivePath = $archiveFilepath;
|
|
$createState->archiveOffset = $filePos;
|
|
$createState->basePath = dirname($relativeFilePath);
|
|
$createState->basepathLength = strlen($createState->basePath);
|
|
$createState->timerEnabled = false;
|
|
$createState->isCompressed = false; // replaced content can't be compressed
|
|
|
|
$forceSize = $globHeader->storedSize;
|
|
|
|
$result = DupArchiveFileProcessor::writeFileSrcToArchive($createState, $archiveHandle, $src, $relativeFilePath, $forceSize);
|
|
SnapIO::fclose($archiveHandle);
|
|
|
|
return $result;
|
|
}
|
|
|
|
|
|
/**
|
|
* Add file in archive using base dir
|
|
*
|
|
* @param string $archiveFilepath archive file
|
|
* @param string $basePath base path
|
|
* @param string $filepath file to add
|
|
* @param int $globSize global size
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function addFileToArchiveUsingBaseDirST(
|
|
$archiveFilepath,
|
|
$basePath,
|
|
$filepath,
|
|
$globSize = DupArchiveCreateState::DEFAULT_GLOB_SIZE
|
|
) {
|
|
$createState = new DupArchiveSimpleCreateState();
|
|
|
|
$createState->archiveOffset = filesize($archiveFilepath);
|
|
$createState->archivePath = $archiveFilepath;
|
|
$createState->basePath = $basePath;
|
|
$createState->basepathLength = strlen($basePath);
|
|
$createState->timerEnabled = false;
|
|
$createState->globSize = $globSize;
|
|
|
|
$scan = new stdClass();
|
|
|
|
$scan->Files = array();
|
|
$scan->Dirs = array();
|
|
|
|
$scan->Files[] = $filepath;
|
|
|
|
self::addItemsToArchive($createState, $scan);
|
|
}
|
|
|
|
/**
|
|
* Create archive
|
|
*
|
|
* @param string $archivePath archive file path
|
|
* @param bool $isCompressed is compressed
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function createArchive($archivePath, $isCompressed)
|
|
{
|
|
if (($archiveHandle = SnapIO::fopen($archivePath, 'w+b')) === false) {
|
|
throw new Exception('Can\t create dup archvie file ' . $archivePath);
|
|
}
|
|
|
|
$archiveHeader = DupArchiveHeader::create($isCompressed);
|
|
$archiveHeader->writeToArchive($archiveHandle);
|
|
|
|
//reserver space for index
|
|
$src = json_encode(array('test'));
|
|
$src .= str_repeat("\0", self::INDEX_FILE_SIZE - strlen($src));
|
|
self::addFileFromSrc($archiveHandle, $src, self::INDEX_FILE_NAME, self::INDEX_FILE_SIZE);
|
|
|
|
// Intentionally do not write build state since if something goes wrong we went it to start over on the archive
|
|
SnapIO::fclose($archiveHandle);
|
|
}
|
|
|
|
/**
|
|
* Add items to archive
|
|
*
|
|
* @param DupArchiveCreateState $createState create state info
|
|
* @param stdClass $scanFSInfo scan if
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function addItemsToArchive(DupArchiveCreateState $createState, stdClass $scanFSInfo)
|
|
{
|
|
if ($createState->globSize == -1) {
|
|
$createState->globSize = DupArchiveCreateState::DEFAULT_GLOB_SIZE;
|
|
}
|
|
|
|
DupArchiveUtil::tlogObject("addItemsToArchive start", $createState);
|
|
|
|
$directoryCount = count($scanFSInfo->Dirs);
|
|
$fileCount = count($scanFSInfo->Files);
|
|
$createState->startTimer();
|
|
$archiveHandle = SnapIO::fopen($createState->archivePath, 'r+b');
|
|
|
|
DupArchiveUtil::tlog("Archive size=", filesize($createState->archivePath));
|
|
DupArchiveUtil::tlog("Archive location is now " . SnapIO::ftell($archiveHandle));
|
|
|
|
$archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle);
|
|
|
|
$createState->isCompressed = $archiveHeader->isCompressed;
|
|
|
|
if ($createState->archiveOffset == filesize($createState->archivePath)) {
|
|
DupArchiveUtil::tlog(
|
|
"Seeking to end of archive location because of offset {$createState->archiveOffset} " .
|
|
"for file size " . filesize($createState->archivePath)
|
|
);
|
|
SnapIO::fseek($archiveHandle, 0, SEEK_END);
|
|
} else {
|
|
DupArchiveUtil::tlog("Seeking archive offset {$createState->archiveOffset} for file size " . filesize($createState->archivePath));
|
|
SnapIO::fseek($archiveHandle, $createState->archiveOffset);
|
|
}
|
|
|
|
while (($createState->currentDirectoryIndex < $directoryCount) && (!$createState->timedOut())) {
|
|
if ($createState->throttleDelayInUs !== 0) {
|
|
usleep($createState->throttleDelayInUs);
|
|
}
|
|
|
|
$directory = $scanFSInfo->Dirs[$createState->currentDirectoryIndex];
|
|
|
|
try {
|
|
$relativeDirectoryPath = '';
|
|
|
|
if (isset($scanFSInfo->DirectoryAliases) && array_key_exists($directory, $scanFSInfo->DirectoryAliases)) {
|
|
$relativeDirectoryPath = $scanFSInfo->DirectoryAliases[$directory];
|
|
} else {
|
|
$relativeDirectoryPath = self::getLocalPath($directory, $createState);
|
|
}
|
|
|
|
if ($relativeDirectoryPath !== '') {
|
|
DupArchiveDirectoryProcessor::writeDirectoryToArchive($createState, $archiveHandle, $directory, $relativeDirectoryPath);
|
|
} else {
|
|
$createState->skippedDirectoryCount++;
|
|
$createState->currentDirectoryIndex++;
|
|
}
|
|
} catch (Exception $ex) {
|
|
DupArchiveUtil::log("Failed to add {$directory} to archive. Error: " . $ex->getMessage(), true);
|
|
|
|
$createState->addFailure(DupArchiveProcessingFailure::TYPE_DIRECTORY, $directory, $ex->getMessage(), false);
|
|
$createState->currentDirectoryIndex++;
|
|
$createState->skippedDirectoryCount++;
|
|
$createState->save();
|
|
}
|
|
}
|
|
|
|
$createState->archiveOffset = SnapIO::ftell($archiveHandle);
|
|
|
|
$workTimestamp = time();
|
|
while (($createState->currentFileIndex < $fileCount) && (!$createState->timedOut())) {
|
|
$filepath = $scanFSInfo->Files[$createState->currentFileIndex];
|
|
|
|
try {
|
|
$relativeFilePath = '';
|
|
|
|
if (isset($scanFSInfo->FileAliases) && array_key_exists($filepath, $scanFSInfo->FileAliases)) {
|
|
$relativeFilePath = $scanFSInfo->FileAliases[$filepath];
|
|
} else {
|
|
$relativeFilePath = self::getLocalPath($filepath, $createState);
|
|
}
|
|
|
|
// Uncomment when testing error handling
|
|
// if((strpos($relativeFilePath, 'dup-installer') !== false) || (strpos($relativeFilePath, 'lib') !== false)) {
|
|
// Dup_Log::Trace("Was going to do intentional error to {$relativeFilePath} but skipping");
|
|
// } else {
|
|
// throw new Exception("#### intentional file error when writing " . $relativeFilePath);
|
|
// }
|
|
// }
|
|
|
|
DupArchiveFileProcessor::writeFilePortionToArchive($createState, $archiveHandle, $filepath, $relativeFilePath);
|
|
|
|
if (($createState->isRobust) && (time() - $workTimestamp >= 1)) {
|
|
DupArchiveUtil::log("Robust mode create state save");
|
|
|
|
// When in robustness mode save the state every second
|
|
$workTimestamp = time();
|
|
$createState->working = ($createState->currentDirectoryIndex < $directoryCount) || ($createState->currentFileIndex < $fileCount);
|
|
$createState->save();
|
|
}
|
|
} catch (Snap32BitSizeLimitException $ex) {
|
|
throw $ex;
|
|
} catch (Exception $ex) {
|
|
DupArchiveUtil::log("Failed to add {$filepath} to archive. Error: " . $ex->getMessage() . $ex->getTraceAsString(), true);
|
|
$createState->currentFileIndex++;
|
|
$createState->skippedFileCount++;
|
|
$createState->addFailure(DupArchiveProcessingFailure::TYPE_FILE, $filepath, $ex->getMessage(), ($ex->getCode() === self::EXCEPTION_FATAL));
|
|
$createState->save();
|
|
}
|
|
}
|
|
|
|
$createState->working = ($createState->currentDirectoryIndex < $directoryCount) || ($createState->currentFileIndex < $fileCount);
|
|
$createState->save();
|
|
|
|
SnapIO::fclose($archiveHandle);
|
|
|
|
if (!$createState->working) {
|
|
DupArchiveUtil::log("compress done");
|
|
} else {
|
|
DupArchiveUtil::tlog("compress not done so continuing later");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Expand archive
|
|
*
|
|
* @param DupArchiveExpandState $expandState expand state
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function expandArchive(DupArchiveExpandState $expandState)
|
|
{
|
|
$expandState->startTimer();
|
|
$archiveHandle = SnapIO::fopen($expandState->archivePath, 'rb');
|
|
|
|
SnapIO::fseek($archiveHandle, $expandState->archiveOffset);
|
|
|
|
if ($expandState->archiveOffset == 0) {
|
|
$expandState->archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle);
|
|
$expandState->isCompressed = $expandState->archiveHeader->isCompressed;
|
|
$expandState->archiveOffset = SnapIO::ftell($archiveHandle);
|
|
|
|
$expandState->save();
|
|
} else {
|
|
DupArchiveUtil::log("#### seeking archive offset {$expandState->archiveOffset}");
|
|
}
|
|
|
|
DupArchiveUtil::log('DUP EXPAND OFFSET ' . $expandState->archiveOffset);
|
|
|
|
if ((!$expandState->validateOnly) || ($expandState->validationType == DupArchiveExpandState::VALIDATION_FULL)) {
|
|
$moreItems = self::expandItems($expandState, $archiveHandle);
|
|
} else {
|
|
$moreItems = self::standardValidateItems($expandState, $archiveHandle);
|
|
}
|
|
|
|
$expandState->working = $moreItems;
|
|
$expandState->save();
|
|
|
|
SnapIO::fclose($archiveHandle, false);
|
|
|
|
if (!$expandState->working) {
|
|
DupArchiveUtil::log("DUP EXPAND DONE");
|
|
|
|
if (($expandState->expectedFileCount != -1) && ($expandState->expectedFileCount != $expandState->fileWriteCount)) {
|
|
$expandState->addFailure(
|
|
DupArchiveProcessingFailure::TYPE_FILE,
|
|
'Archive',
|
|
"Number of files expected ({$expandState->expectedFileCount}) doesn't equal number written ({$expandState->fileWriteCount})."
|
|
);
|
|
}
|
|
|
|
if (($expandState->expectedDirectoryCount != -1) && ($expandState->expectedDirectoryCount != $expandState->directoryWriteCount)) {
|
|
$expandState->addFailure(
|
|
DupArchiveProcessingFailure::TYPE_DIRECTORY,
|
|
'Archive',
|
|
"Number of directories expected ({$expandState->expectedDirectoryCount}) " .
|
|
"doesn't equal number written ({$expandState->directoryWriteCount})."
|
|
);
|
|
}
|
|
} else {
|
|
DupArchiveUtil::tlogObject("expand not done so continuing later", $expandState);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Single-threaded file expansion
|
|
*
|
|
* @param string $archiveFilePath archive path
|
|
* @param string $relativeFilePaths relative file path in archive
|
|
* @param string $destPath destination path
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function expandFiles($archiveFilePath, $relativeFilePaths, $destPath)
|
|
{
|
|
// Not setting timeout timestamp so it will never timeout
|
|
DupArchiveUtil::tlog("opening archive {$archiveFilePath}");
|
|
|
|
$archiveHandle = SnapIO::fopen($archiveFilePath, 'r');
|
|
|
|
/* @var $expandState DupArchiveSimpleExpandState */
|
|
$expandState = new DupArchiveSimpleExpandState();
|
|
|
|
$expandState->archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle);
|
|
$expandState->isCompressed = $expandState->archiveHeader->isCompressed;
|
|
$expandState->archiveOffset = SnapIO::ftell($archiveHandle);
|
|
$expandState->includedFiles = $relativeFilePaths;
|
|
$expandState->filteredDirectories = array('*');
|
|
$expandState->filteredFiles = array('*');
|
|
// $expandState->basePath = $destPath . '/tempExtract'; // RSR remove once extract works
|
|
$expandState->basePath = $destPath; // RSR remove once extract works
|
|
// TODO: Filter out all directories/files except those in the list
|
|
self::expandItems($expandState, $archiveHandle);
|
|
}
|
|
|
|
/**
|
|
* Expand dup archive items
|
|
*
|
|
* @param DupArchiveExpandState $expandState dup archive expand state
|
|
* @param resource $archiveHandle dup archvie resource
|
|
*
|
|
* @return bool true if more to read
|
|
*/
|
|
private static function expandItems(DupArchiveExpandState $expandState, $archiveHandle)
|
|
{
|
|
$moreToRead = true;
|
|
$workTimestamp = time();
|
|
|
|
while ($moreToRead && (!$expandState->timedOut())) {
|
|
if ($expandState->throttleDelayInUs !== 0) {
|
|
usleep($expandState->throttleDelayInUs);
|
|
}
|
|
|
|
if ($expandState->currentFileHeader != null) {
|
|
DupArchiveUtil::tlog("Writing file {$expandState->currentFileHeader->relativePath}");
|
|
|
|
if (self::filePassesFilters($expandState)) {
|
|
try {
|
|
$fileCompleted = DupArchiveFileProcessor::writeToFile($expandState, $archiveHandle);
|
|
} catch (Exception $ex) {
|
|
DupArchiveUtil::log("Failed to write to {$expandState->currentFileHeader->relativePath}. Error: " . $ex->getMessage(), true);
|
|
|
|
// Reset things - skip over this file within the archive.
|
|
SnapIO::fseek($archiveHandle, $expandState->lastHeaderOffset);
|
|
self::skipToNextHeader($archiveHandle, $expandState->currentFileHeader);
|
|
|
|
$expandState->archiveOffset = ftell($archiveHandle);
|
|
$expandState->addFailure(
|
|
DupArchiveProcessingFailure::TYPE_FILE,
|
|
$expandState->currentFileHeader->relativePath,
|
|
$ex->getMessage(),
|
|
false
|
|
);
|
|
$expandState->resetForFile();
|
|
$expandState->lastHeaderOffset = -1;
|
|
$expandState->save();
|
|
}
|
|
} else {
|
|
self::skipFileInArchive($archiveHandle, $expandState->currentFileHeader);
|
|
$expandState->resetForFile();
|
|
}
|
|
} else {
|
|
// Header is null so read in the next one
|
|
$expandState->lastHeaderOffset = @ftell($archiveHandle);
|
|
$headerType = self::getNextHeaderType($archiveHandle);
|
|
|
|
DupArchiveUtil::tlog('header type ' . $headerType);
|
|
switch ($headerType) {
|
|
case self::HEADER_TYPE_FILE:
|
|
DupArchiveUtil::tlog('File header');
|
|
$expandState->currentFileHeader = DupArchiveFileHeader::readFromArchive($archiveHandle, false, true);
|
|
$expandState->archiveOffset = @ftell($archiveHandle);
|
|
DupArchiveUtil::tlog('Just read file header from archive');
|
|
break;
|
|
case self::HEADER_TYPE_DIR:
|
|
DupArchiveUtil::tlog('Directory Header');
|
|
$directoryHeader = DupArchiveDirectoryHeader::readFromArchive($archiveHandle, true);
|
|
|
|
if (self::passesDirectoryExclusion($expandState, $directoryHeader->relativePath)) {
|
|
$createdDirectory = true;
|
|
|
|
if (!$expandState->validateOnly) {
|
|
$createdDirectory = DupArchiveFileProcessor::createDirectory($expandState, $directoryHeader);
|
|
}
|
|
|
|
if ($createdDirectory) {
|
|
$expandState->directoryWriteCount++;
|
|
}
|
|
}
|
|
$expandState->archiveOffset = ftell($archiveHandle);
|
|
DupArchiveUtil::tlog('Just read directory header ' . $directoryHeader->relativePath . ' from archive');
|
|
break;
|
|
case self::HEADER_TYPE_NONE:
|
|
$moreToRead = false;
|
|
}
|
|
}
|
|
|
|
if (($expandState->isRobust) && (time() - $workTimestamp >= 1)) {
|
|
DupArchiveUtil::log("Robust mode extract state save for standard validate");
|
|
|
|
// When in robustness mode save the state every second
|
|
$workTimestamp = time();
|
|
$expandState->save();
|
|
}
|
|
}
|
|
|
|
$expandState->save();
|
|
|
|
return $moreToRead;
|
|
}
|
|
|
|
/**
|
|
* check exclude dir
|
|
*
|
|
* @param DupArchiveExpandState $expandState dup archive expand state
|
|
* @param string $candidate check exclude dir
|
|
*
|
|
* @return bool
|
|
*/
|
|
private static function passesDirectoryExclusion(DupArchiveExpandState $expandState, $candidate)
|
|
{
|
|
foreach ($expandState->filteredDirectories as $directoryFilter) {
|
|
if ($directoryFilter === '*') {
|
|
return false;
|
|
}
|
|
|
|
if (SnapIO::getRelativePath($candidate, $directoryFilter) !== false) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (in_array($candidate, $expandState->excludedDirWithoutChilds)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check flils filters
|
|
*
|
|
* @param DupArchiveExpandState $expandState dup archive expand state
|
|
*
|
|
* @return boolean
|
|
*/
|
|
private static function filePassesFilters(DupArchiveExpandState $expandState)
|
|
{
|
|
$candidate = $expandState->currentFileHeader->relativePath;
|
|
|
|
// Included files trumps all exclusion filters
|
|
foreach ($expandState->includedFiles as $includedFile) {
|
|
if ($includedFile === $candidate) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (self::passesDirectoryExclusion($expandState, $candidate)) {
|
|
foreach ($expandState->filteredFiles as $fileFilter) {
|
|
if ($fileFilter === '*' || $fileFilter === $candidate) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Validate items
|
|
*
|
|
* @param DupArchiveExpandState $expandState dup archive expan state
|
|
* @param resource $archiveHandle dup archive resource
|
|
*
|
|
* @return bool true if more to read
|
|
*/
|
|
private static function standardValidateItems(DupArchiveExpandState $expandState, $archiveHandle)
|
|
{
|
|
$moreToRead = true;
|
|
|
|
$to = $expandState->timedOut();
|
|
$workTimestamp = time();
|
|
|
|
while ($moreToRead && (!$to)) {
|
|
if ($expandState->throttleDelayInUs !== 0) {
|
|
usleep($expandState->throttleDelayInUs);
|
|
}
|
|
|
|
if ($expandState->currentFileHeader != null) {
|
|
try {
|
|
$fileCompleted = DupArchiveFileProcessor::standardValidateFileEntry($expandState, $archiveHandle);
|
|
|
|
if ($fileCompleted) {
|
|
$expandState->resetForFile();
|
|
}
|
|
|
|
// Expand state taken care of within the write to file to ensure consistency
|
|
} catch (Exception $ex) {
|
|
DupArchiveUtil::log("Failed validate file in archive. Error: " . $ex->getMessage(), true);
|
|
DupArchiveUtil::logObject("expand state", $expandState, true);
|
|
// $expandState->currentFileIndex++;
|
|
// RSR TODO: Need way to skip past that file
|
|
|
|
$expandState->addFailure(DupArchiveProcessingFailure::TYPE_FILE, $expandState->currentFileHeader->relativePath, $ex->getMessage());
|
|
$expandState->save();
|
|
|
|
$moreToRead = false;
|
|
}
|
|
} else {
|
|
$headerType = self::getNextHeaderType($archiveHandle);
|
|
|
|
switch ($headerType) {
|
|
case self::HEADER_TYPE_FILE:
|
|
$expandState->currentFileHeader = DupArchiveFileHeader::readFromArchive($archiveHandle, false, true);
|
|
$expandState->archiveOffset = ftell($archiveHandle);
|
|
break;
|
|
case self::HEADER_TYPE_DIR:
|
|
$directoryHeader = DupArchiveDirectoryHeader::readFromArchive($archiveHandle, true);
|
|
$expandState->directoryWriteCount++;
|
|
$expandState->archiveOffset = ftell($archiveHandle);
|
|
break;
|
|
case self::HEADER_TYPE_NONE:
|
|
$moreToRead = false;
|
|
}
|
|
}
|
|
|
|
if (($expandState->isRobust) && (time() - $workTimestamp >= 1)) {
|
|
DupArchiveUtil::log("Robust mdoe extract state save for standard validate");
|
|
|
|
// When in robustness mode save the state every second
|
|
$workTimestamp = time();
|
|
$expandState->save();
|
|
}
|
|
$to = $expandState->timedOut();
|
|
}
|
|
|
|
$expandState->save();
|
|
|
|
return $moreToRead;
|
|
}
|
|
}
|
|
|