N°2249 - Supportability - Updater module (application upgrade)

This commit is contained in:
Eric
2019-12-13 17:28:35 +01:00
parent 2741a446ea
commit dd5ac38dd4
36 changed files with 3158 additions and 1 deletions

View File

@@ -0,0 +1,133 @@
<?php
/**
* @copyright Copyright (C) 2010-2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\FilesInformation\Service;
use Dict;
use MetaModel;
use utils;
class FilesInformation
{
private static $sItopOwner;
/**
* Check iTop files access rights to tell if core update is possible
*
* @param string $sMessage
*
* @return bool true if core update is possible
* @throws \Combodo\iTop\FilesInformation\Service\FileNotExistException
*/
public static function CanUpdateCore(&$sMessage)
{
self::Init();
// Check than iTop can write everywhere
if (!self::CanWriteRecursive('', $sMessage))
{
return false;
}
return true;
}
/**
* @param string $sRootPath
* @param string $sMessage
*
* @return bool
* @throws \Combodo\iTop\FilesInformation\Service\FileNotExistException
*/
private static function CanWriteRecursive($sRootPath = '', &$sMessage = null)
{
$aDirStats = FilesInformationUtils::Scan($sRootPath, false);
foreach ($aDirStats as $sFileName => $aFileStats)
{
if (!self::CanWriteToFile($aFileStats))
{
$sMessage = Dict::Format('FilesInformation:Error:CantWriteToFile', $sRootPath.DIRECTORY_SEPARATOR.$sFileName);
return false;
}
if (($sFileName != '.') && ($aFileStats['type'] == 'dir'))
{
if (!self::CanWriteRecursive($sRootPath.DIRECTORY_SEPARATOR.$sFileName, $sMessage))
{
return false;
}
}
}
return true;
}
/**
* Check if iTop can write
* @param string $sFilename absolute path to chack
*
* @return bool
* @throws \Combodo\iTop\FilesInformation\Service\FileNotExistException
*/
public static function IsWritable($sFilename)
{
$aFileStats = FilesInformationUtils::GetFileStat(utils::LocalPath($sFilename));
return self::CanWriteToFile($aFileStats);
}
private static function CanWriteToFile($aFileStats)
{
if ($aFileStats['writable'])
{
return true;
}
if ($aFileStats['file_owner'] == self::$sItopOwner)
{
// If iTop owns the file, no pb to write
return true;
}
return false;
}
/**
* @throws \Combodo\iTop\FilesInformation\Service\FileNotExistException
*/
private static function Init()
{
clearstatcache();
$sSourceConfigFile = MetaModel::GetConfig()->GetLoadedFile();
$sSourceConfigFile = utils::LocalPath($sSourceConfigFile);
$aConfigFiles = FilesInformationUtils::Scan(dirname($sSourceConfigFile));
if (!isset($aConfigFiles[basename($sSourceConfigFile)]))
{
return;
}
$aConfigStats = $aConfigFiles[basename($sSourceConfigFile)];
self::$sItopOwner = $aConfigStats['file_owner'];
}
public static function GetItopDiskSpace()
{
return FilesInformationUtils::GetDirSize(realpath(APPROOT));
}
/**
* @param $sLocalDirPath
*
* @return array
* @throws \Combodo\iTop\FilesInformation\Service\FileNotExistException
*/
public static function GetDirInfo($sLocalDirPath)
{
if (utils::AbsolutePath($sLocalDirPath) === false)
{
return array();
}
return FilesInformationUtils::Scan($sLocalDirPath);
}
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* @copyright Copyright (C) 2010-2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\FilesInformation\Service;
use Exception;
class FilesInformationException extends Exception
{
}
class FileNotExistException extends FilesInformationException
{
}
class FileIntegrityException extends FilesInformationException
{
}

View File

@@ -0,0 +1,179 @@
<?php
/**
* @copyright Copyright (C) 2010-2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\FilesInformation\Service;
use utils;
class FilesInformationUtils
{
public static function Init()
{
clearstatcache();
}
/**
* @param string $sPath
* @param bool $bGetDirSize
*
* @return array
* @throws \Combodo\iTop\FilesInformation\Service\FileNotExistException
*/
public static function Scan($sPath = '', $bGetDirSize = true)
{
$sRealRootPath = utils::AbsolutePath($sPath);
$aFiles = scandir($sRealRootPath);
$aFileStats = array();
foreach ($aFiles as $sScanFile)
{
if ($sScanFile == '..')
{
continue;
}
$sFile = $sRealRootPath.DIRECTORY_SEPARATOR.$sScanFile;
$sFileName = utils::LocalPath($sFile);
$aFileStat = self::GetFileStat($sFileName, $bGetDirSize);
$aFileStat['basename'] = $sScanFile;
$aFileStats[$sScanFile] = $aFileStat;
}
return $aFileStats;
}
/**
* @param string $sFilename
*
* @param bool $bGetDirSize
*
* @return array
* @throws \Combodo\iTop\FilesInformation\Service\FileNotExistException
*/
public static function GetFileStat($sFilename, $bGetDirSize = true)
{
$sFile = realpath(APPROOT.$sFilename);
$aStats = @stat($sFile);
if (!$aStats)
{
throw new FileNotExistException($sFilename);
}
$aFileStats = array();
$aFileStats['name'] = $sFilename;
$aFileStats['size'] = $aStats['size'];
if (is_dir($sFile))
{
// Special dir case
if ($bGetDirSize)
{
// The size is computed by aggregating the sizes on the children
$aFileStats['size'] = self::GetDirSize($sFile);
}
}
$aFileStats['display_size'] = utils::BytesToFriendlyFormat($aFileStats['size']);
$aFileStats['perms'] = sprintf("0%o", 0777 & $aStats['mode']);
$aFileStats['mode'] = $aStats['mode'];
$aTypes = array(
0140000=>'socket',
0120000=>'link',
0100000=>'file',
0060000=>'block',
0040000=>'dir',
0020000=>'char',
0010000=>'fifo'
);
$iRawMode = $aStats['mode'];
$iMode = decoct($iRawMode & 0170000); // File Encoding Bit
$sDisplayMode =(array_key_exists(octdec($iMode),$aTypes))?$aTypes[octdec($iMode)]{0}:'u';
$sDisplayMode.=(($iRawMode&0x0100)?'r':'-').(($iRawMode&0x0080)?'w':'-');
$sDisplayMode.=(($iRawMode&0x0040)?(($iRawMode&0x0800)?'s':'x'):(($iRawMode&0x0800)?'S':'-'));
$sDisplayMode.=(($iRawMode&0x0020)?'r':'-').(($iRawMode&0x0010)?'w':'-');
$sDisplayMode.=(($iRawMode&0x0008)?(($iRawMode&0x0400)?'s':'x'):(($iRawMode&0x0400)?'S':'-'));
$sDisplayMode.=(($iRawMode&0x0004)?'r':'-').(($iRawMode&0x0002)?'w':'-');
$sDisplayMode.=(($iRawMode&0x0001)?(($iRawMode&0x0200)?'t':'x'):(($iRawMode&0x0200)?'T':'-'));
$aFileStats['display_mode'] = $sDisplayMode;
$aFileStats['type'] = $aTypes[octdec($iMode)];
$aFileStats['readable'] = is_readable($sFile);
$aFileStats['writable'] = is_writable($sFile);
$aFileStats['file_owner'] = $aStats['uid'];
$aFileStats['file_group'] = $aStats['gid'];
if (function_exists('posix_getpwuid'))
{
$aPwUid = @posix_getpwuid($aStats['uid']);
if (isset($aPwUid['name']))
{
$aFileStats['owner_name'] = $aPwUid['name'];
}
}
if (empty($aFileStats['owner_name']))
{
$aFileStats['owner_name'] = '';
}
if (function_exists('posix_getgrgid'))
{
$aGrGid = @posix_getgrgid($aStats['gid']);
if (isset($aGrGid['name']))
{
$aFileStats['group_name'] = $aGrGid['name'];
}
}
if (empty($aFileStats['group_name']))
{
$aFileStats['group_name'] = '';
}
$aFileStats['mtime'] = date('Y-m-d H:i:s', $aStats['mtime']);
$aFileStats['ctime'] = date('Y-m-d H:i:s', $aStats['ctime']);
return $aFileStats;
}
/**
* @param string $sPath relative iTop path
*
* @return string absolute path
* @throws \Combodo\iTop\FilesInformation\Service\FileNotExistException
*/
public static function GetAbsolutePath($sPath)
{
$sRootPath = realpath(APPROOT);
$sFullPath = realpath($sRootPath.DIRECTORY_SEPARATOR.$sPath);
if (($sFullPath === false) || !utils::StartsWith($sFullPath, $sRootPath))
{
throw new FileNotExistException($sPath);
}
return $sFullPath;
}
public static function GetDirSize($sRealRootPath)
{
$aFiles = scandir($sRealRootPath);
$iSize = 0;
foreach ($aFiles as $sScanFile)
{
if (($sScanFile == '.') || ($sScanFile == '..'))
{
continue;
}
$sFile = $sRealRootPath.DIRECTORY_SEPARATOR.$sScanFile;
if (is_dir($sFile))
{
$iSize += self::GetDirSize($sFile);
}
else
{
$aStats = @stat($sFile);
$iSize += $aStats['size'];
}
}
return $iSize;
}
}

View File

@@ -0,0 +1,123 @@
<?php
/**
* @copyright Copyright (C) 2010-2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\FilesInformation\Service;
use Dict;
use DOMDocument;
use DOMElement;
use DOMNode;
use DOMXPath;
class FilesIntegrity
{
/**
* Get the files defined in the manifest.xml
*
* @param string $sManifest full path of the manifest file
*
* @return array|false list of file info (path, size, md5)
* @throws \Exception
*/
private static function GetInstalledFiles($sManifest)
{
$aFiles = array();
$aManifestStats = @stat($sManifest);
if ($aManifestStats === false)
{
// No manifest
return false;
}
$oManifestDocument = new DOMDocument('1.0', 'UTF-8');
@$oManifestDocument->load($sManifest);
$oXPath = new DOMXPath($oManifestDocument);
$oNodeList = $oXPath->query('/files');
if ($oNodeList->length == 0)
{
// no files
return false;
}
foreach ($oNodeList as $oItems)
{
foreach ($oItems->childNodes as $oFileNode)
{
if (($oFileNode instanceof DOMNode))
{
if ($oFileNode->hasChildNodes())
{
$aFileInfo = array();
foreach ($oFileNode->childNodes as $oFileInfo)
{
if ($oFileInfo instanceof DOMElement)
{
$aFileInfo[$oFileInfo->tagName] = $oFileInfo->textContent;
}
}
$aFiles[] = $aFileInfo;
}
}
}
}
return $aFiles;
}
/**
* Check that files present in iTop folder corresponds to the manifest
*
* @param string $sRootPath
*
* @throws \Combodo\iTop\FilesInformation\Service\FileIntegrityException
*/
public static function CheckInstallationIntegrity($sRootPath = APPROOT)
{
$aFilesInfo = FilesIntegrity::GetInstalledFiles($sRootPath.'manifest.xml');
if ($aFilesInfo === false)
{
throw new FileIntegrityException(Dict::Format('FilesInformation:Error:MissingFile', 'manifest.xml'));
}
@clearstatcache();
foreach ($aFilesInfo as $aFileInfo)
{
$sFile = $sRootPath.$aFileInfo['path'];
if (is_file($sFile))
{
$aStats = @stat($sFile);
$iSize = $aStats['size'];
$sContent = file_get_contents($sFile);
$sChecksum = md5($sContent);
if (($iSize != $aFileInfo['size']) || ($sChecksum != $aFileInfo['md5']))
{
throw new FileIntegrityException(Dict::Format('FilesInformation:Error:CorruptedFile', basename($sFile)));
}
}
// Packed with missing files...
}
}
public static function IsInstallationConform($sRootPath, &$sErrorMsg)
{
$sErrorMsg = '';
try
{
self::CheckInstallationIntegrity($sRootPath);
return true;
}
catch (FileIntegrityException $e)
{
$sErrorMsg = $e->getMessage();
}
return false;
}
}