diff --git a/application/utils.inc.php b/application/utils.inc.php
index a470a0c72d..479ed3b4d0 100644
--- a/application/utils.inc.php
+++ b/application/utils.inc.php
@@ -384,24 +384,8 @@ class utils
$sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex];
$doc_content = file_get_contents($sTmpName);
- if (function_exists('finfo_file'))
- {
- // as of PHP 5.3 the fileinfo extension is bundled within PHP
- // in which case we don't trust the mime type provided by the browser
- $rInfo = @finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
- if ($rInfo !== false)
- {
- $sType = @finfo_file($rInfo, $sTmpName);
- if ( ($sType !== false)
- && is_string($sType)
- && (strlen($sType)>0))
- {
- $sMimeType = $sType;
- }
- }
- @finfo_close($rInfo);
- }
- $oDocument = new ormDocument($doc_content, $sMimeType, $sName);
+ $sMimeType = self::GetFileMimeType($sTmpName);
+ $oDocument = new ormDocument($doc_content, $sMimeType, $sName);
break;
case UPLOAD_ERR_NO_FILE:
@@ -2045,4 +2029,38 @@ class utils
{
return ITOP_REVISION === 'svn';
}
+
+ /**
+ * @see https://php.net/manual/en/function.finfo-file.php
+ *
+ * @param string $sFilePath file full path
+ * @param string $sDefaultMimeType
+ *
+ * @return string mime type, defaults to application/octet-stream
+ * @uses finfo_file in FileInfo extension (bundled in PHP since version 5.3)
+ * @since 2.7.0 N°2366
+ */
+ public static function GetFileMimeType($sFilePath, $sDefaultMimeType = 'application/octet-stream')
+ {
+ if (!function_exists('finfo_file'))
+ {
+ return $sDefaultMimeType;
+ }
+
+ $sMimeType = $sDefaultMimeType;
+ $rInfo = @finfo_open(FILEINFO_MIME_TYPE);
+ if ($rInfo !== false)
+ {
+ $sType = @finfo_file($rInfo, $sFilePath);
+ if (($sType !== false)
+ && is_string($sType)
+ && ($sType !== ''))
+ {
+ $sMimeType = $sType;
+ }
+ }
+ @finfo_close($rInfo);
+
+ return $sMimeType;
+ }
}
diff --git a/setup/backup.class.inc.php b/setup/backup.class.inc.php
index de4ddd9522..57080f2a81 100644
--- a/setup/backup.class.inc.php
+++ b/setup/backup.class.inc.php
@@ -56,672 +56,484 @@ interface BackupArchive
/**
* Returns the entry contents using its name
*
+ * Note: both params $length and $flags are unused in the current archive format (TarGzArchive)... But this is a
+ * public interface and there might be some other implementations out there so: DON'T REMOVE THEM!
+ *
* @param string $name Name of the entry
* @param int $length [optional] The length to be read from the entry. If 0, then the entire entry is read.
- * @param int $flags [optional] The flags to use to open the archive. the following values may be ORed to it.
- * ZipArchive::FL_UNCHANGED
+ * @param int $flags [optional] The flags to use to open the archive.
+ * Was used with Zip archives, example : ZipArchive::FL_UNCHANGED
*
* @return string the contents of the entry on success or FALSE on failure.
*/
public function getFromName($name, $length = 0, $flags = null);
}
-if (class_exists('ZipArchive')) // The setup must be able to start even if the "zip" extension is not loaded
+
+class BackupException extends Exception
+{
+}
+
+
+class DBBackup
{
/**
- * Handles adding directories into a Zip archive, and a unified API for archive read
- * suggested enhancement: refactor the API for writing as well
+ * utf8mb4 was added in MySQL 5.5.3 but works with programs like mysqldump only since MySQL 5.5.33
+ *
+ * @since 2.5 see N°1001
*/
- class ZipArchiveEx extends ZipArchive implements BackupArchive
- {
- public function addDir($sDir, $sZipDir = '')
- {
- if (is_dir($sDir))
- {
- if ($dh = opendir($sDir))
- {
- // Add the directory
- if (!empty($sZipDir))
- {
- $this->addEmptyDir($sZipDir);
- }
+ const MYSQL_VERSION_WITH_UTF8MB4_IN_PROGRAMS = '5.5.33';
- // Loop through all the files
- while (($sFile = readdir($dh)) !== false)
- {
- // If it's a folder, run the function again!
- if (!is_file($sDir.$sFile))
- {
- // Skip parent and root directories
- if (($sFile !== ".") && ($sFile !== ".."))
- {
- $this->addDir($sDir.$sFile."/", $sZipDir.$sFile."/");
- }
- }
- else
- {
- // Add the files
- $this->addFile($sDir.$sFile, $sZipDir.$sFile);
- }
- }
- }
- }
- }
-
- /**
- * @param string $sFile
- *
- * @return bool TRUE if the file is present, FALSE otherwise.
- */
- public function hasFile($sFile)
- {
- return ($this->locateName($sFile) !== false);
- }
-
- /**
- * @param string $sDirectory
- *
- * @return bool TRUE if the directory is present, FALSE otherwise.
- */
- public function hasDir($sDirectory)
- {
- return ($this->locateName($sDirectory) !== false);
- }
-
- /**
- * @param string $sDestinationDir
- * @param string $sArchiveFile
- *
- * @return bool TRUE on success or FALSE on failure.
- */
- public function extractFileTo($sDestinationDir, $sArchiveFile)
- {
- return $this->extractTo($sDestinationDir, $sArchiveFile);
- }
-
- /**
- * Extract a whole directory from the archive.
- * Usage: $oZip->extractDirTo('/var/www/html/itop/data', '/production-modules/')
- *
- * @param string $sDestinationDir
- * @param string $sZipDir Must start and end with a slash !!
- *
- * @return boolean
- */
- public function extractDirTo($sDestinationDir, $sZipDir)
- {
- $aFiles = array();
- for ($i = 0; $i < $this->numFiles; $i++)
- {
- $sEntry = $this->getNameIndex($i);
- //Use strpos() to check if the entry name contains the directory we want to extract
- if (strpos($sEntry, $sZipDir) === 0)
- {
- //Add the entry to our array if it in in our desired directory
- $aFiles[] = $sEntry;
- }
- }
- // Extract only the selected files
- if ((count($aFiles) > 0) && ($this->extractTo($sDestinationDir, $aFiles) === true))
- {
- return true;
- }
-
- return false;
- }
- } // class ZipArchiveEx
-
- class BackupException extends Exception
+ // To be overriden depending on the expected usages
+ protected function LogInfo($sMsg)
{
}
- class DBBackup
+ protected function LogError($sMsg)
{
- /**
- * utf8mb4 was added in MySQL 5.5.3 but works with programs like mysqldump only since MySQL 5.5.33
- *
- * @since 2.5 see N°1001
- */
- const MYSQL_VERSION_WITH_UTF8MB4_IN_PROGRAMS = '5.5.33';
+ }
- // To be overriden depending on the expected usages
- protected function LogInfo($sMsg)
+ /** @var Config */
+ protected $oConfig;
+
+ // shortcuts used for log purposes
+ /** @var string */
+ protected $sDBHost;
+ /** @var int */
+ protected $iDBPort;
+ /** @var string */
+ protected $sDBName;
+ /** @var string */
+ protected $sDBSubName;
+
+ /**
+ * Connects to the database to backup
+ *
+ * @param Config $oConfig object containing the database configuration.
+ * If null then uses the default configuration ({@see MetaModel::GetConfig})
+ *
+ * @since 2.5 uses a Config object instead of passing each attribute (there were far too many with the addition of MySQL TLS parameters
+ * !)
+ */
+ public function __construct($oConfig = null)
+ {
+ if (is_null($oConfig))
{
+ // Defaulting to the current config
+ $oConfig = MetaModel::GetConfig();
}
- protected function LogError($sMsg)
+ $this->oConfig = $oConfig;
+
+ // init log variables
+ CMDBSource::InitServerAndPort($oConfig->Get('db_host'), $this->sDBHost, $this->iDBPort);
+ $this->sDBName = $oConfig->get('db_name');
+ $this->sDBSubName = $oConfig->get('db_subname');
+ }
+
+ protected $sMySQLBinDir = '';
+
+ /**
+ * Create a normalized backup name, depending on the current date/time and Database
+ *
+ * @param string sMySQLBinDir Name and path, eventually containing itop placeholders + time formatting specs
+ */
+ public function SetMySQLBinDir($sMySQLBinDir)
+ {
+ $this->sMySQLBinDir = $sMySQLBinDir;
+ }
+
+ /**
+ * Create a normalized backup name, depending on the current date/time and Database
+ *
+ * @param string sNameSpec Name and path, eventually containing itop placeholders + time formatting specs
+ *
+ * @return string
+ */
+ public function MakeName($sNameSpec = "__DB__-%Y-%m-%d")
+ {
+ $sFileName = $sNameSpec;
+ $sFileName = str_replace('__HOST__', $this->sDBHost, $sFileName);
+ $sFileName = str_replace('__DB__', $this->sDBName, $sFileName);
+ $sFileName = str_replace('__SUBNAME__', $this->sDBSubName, $sFileName);
+ // Transform %Y, etc.
+ $sFileName = strftime($sFileName);
+
+ return $sFileName;
+ }
+
+ /**
+ * @param string $sTargetFile Path and name, without the extension
+ * @param string|null $sSourceConfigFile Configuration file to embed into the backup, if not the current one
+ *
+ * @throws \CoreException if CMDBSource not initialized
+ * @throws \BackupException if archive cannot be created
+ * @throws \Exception
+ */
+ public function CreateCompressedBackup($sTargetFile, $sSourceConfigFile = null)
+ {
+ $bIsCmdbSourceInitialized = CMDBSource::GetMysqli() instanceof mysqli;
+ if (!$bIsCmdbSourceInitialized)
{
+ $sErrorMsg = 'Cannot backup : CMDBSource not initialized !';
+ $this->LogError($sErrorMsg);
+ throw new CoreException($sErrorMsg);
}
- /** @var Config */
- protected $oConfig;
+ $this->LogInfo("Creating backup: '$sTargetFile.tar.gz'");
- // shortcuts used for log purposes
- /** @var string */
- protected $sDBHost;
- /** @var int */
- protected $iDBPort;
- /** @var string */
- protected $sDBName;
- /** @var string */
- protected $sDBSubName;
+ $oArchive = new ITopArchiveTar($sTargetFile.'.tar.gz');
- /**
- * Connects to the database to backup
- *
- * @param Config $oConfig object containing the database configuration.
- * If null then uses the default configuration ({@see MetaModel::GetConfig})
- *
- * @since 2.5 uses a Config object instead of passing each attribute (there were far too many with the addition of MySQL TLS parameters !)
- */
- public function __construct($oConfig = null)
+ $sTmpFolder = APPROOT.'data/tmp-backup-'.rand(10000, getrandmax());
+ $aFiles = $this->PrepareFilesToBackup($sSourceConfigFile, $sTmpFolder);
+
+ $sFilesList = var_export($aFiles, true);
+ $this->LogInfo("backup: adding to archive files '$sFilesList'");
+ $bArchiveCreationResult = $oArchive->createModify($aFiles, '', $sTmpFolder);
+ if (!$bArchiveCreationResult)
{
- if (is_null($oConfig))
- {
- // Defaulting to the current config
- $oConfig = MetaModel::GetConfig();
- }
-
- $this->oConfig = $oConfig;
-
- // init log variables
- CMDBSource::InitServerAndPort($oConfig->Get('db_host'), $this->sDBHost, $this->iDBPort);
- $this->sDBName = $oConfig->get('db_name');
- $this->sDBSubName = $oConfig->get('db_subname');
+ $sErrorMsg = 'Cannot backup : unable to create archive';
+ $this->LogError($sErrorMsg);
+ throw new BackupException($sErrorMsg);
}
- protected $sMySQLBinDir = '';
+ $this->LogInfo("backup: removing tmp folder '$sTmpFolder'");
+ SetupUtils::rrmdir($sTmpFolder);
+ }
- /**
- * Create a normalized backup name, depending on the current date/time and Database
- *
- * @param string sMySQLBinDir Name and path, eventually containing itop placeholders + time formatting specs
- */
- public function SetMySQLBinDir($sMySQLBinDir)
+ /**
+ * Copy files to store into the temporary folder, in addition to the SQL dump
+ *
+ * @param string $sSourceConfigFile
+ * @param string $sTmpFolder
+ *
+ * @return array list of files to archive
+ * @throws \Exception
+ */
+ protected function PrepareFilesToBackup($sSourceConfigFile, $sTmpFolder)
+ {
+ $aRet = array();
+ if (is_dir($sTmpFolder))
{
- $this->sMySQLBinDir = $sMySQLBinDir;
- }
-
- /**
- * Create a normalized backup name, depending on the current date/time and Database
- *
- * @param string sNameSpec Name and path, eventually containing itop placeholders + time formatting specs
- *
- * @return string
- */
- public function MakeName($sNameSpec = "__DB__-%Y-%m-%d")
- {
- $sFileName = $sNameSpec;
- $sFileName = str_replace('__HOST__', $this->sDBHost, $sFileName);
- $sFileName = str_replace('__DB__', $this->sDBName, $sFileName);
- $sFileName = str_replace('__SUBNAME__', $this->sDBSubName, $sFileName);
- // Transform %Y, etc.
- $sFileName = strftime($sFileName);
-
- return $sFileName;
- }
-
- /**
- * @deprecated 2.4.0 Zip files are limited to 4 Gb, use CreateCompressedBackup to create tar.gz files
- *
- * @param string $sZipFile
- * @param string|null $sSourceConfigFile
- *
- * @throws \BackupException
- */
- public function CreateZip($sZipFile, $sSourceConfigFile = null)
- {
- $aContents = array();
-
- // Note: the file is created by tempnam and might not be writeable by another process (Windows/IIS)
- // (delete it before spawning a process)
- $sDataFile = tempnam(SetupUtils::GetTmpDir(), 'itop-');
- $this->LogInfo("Data file: '$sDataFile'");
- $this->DoBackup($sDataFile);
- $aContents[] = array(
- 'source' => $sDataFile,
- 'dest' => 'itop-dump.sql',
- );
-
- foreach ($this->GetAdditionalFiles($sSourceConfigFile) as $sArchiveFile => $sSourceFile)
- {
- $aContents[] = array(
- 'source' => $sSourceFile,
- 'dest' => $sArchiveFile,
- );
- }
-
- $this->DoZip($aContents, $sZipFile);
-
- // Windows/IIS: the data file has been created by the spawned process...
- // trying to delete it will issue a warning, itself stopping the setup abruptely
- @unlink($sDataFile);
- }
-
- /**
- * @param string $sTargetFile Path and name, without the extension
- * @param string|null $sSourceConfigFile Configuration file to embed into the backup, if not the current one
- *
- * @throws \CoreException if CMDBSource not initialized
- * @throws \BackupException if archive cannot be created
- * @throws \Exception
- */
- public function CreateCompressedBackup($sTargetFile, $sSourceConfigFile = null)
- {
- $bIsCmdbSourceInitialized = CMDBSource::GetMysqli() instanceof mysqli;
- if (!$bIsCmdbSourceInitialized)
- {
- $sErrorMsg = 'Cannot backup : CMDBSource not initialized !';
- $this->LogError($sErrorMsg);
- throw new CoreException($sErrorMsg);
- }
-
- $this->LogInfo("Creating backup: '$sTargetFile.tar.gz'");
-
- $oArchive = new ITopArchiveTar($sTargetFile.'.tar.gz');
-
- $sTmpFolder = APPROOT.'data/tmp-backup-'.rand(10000, getrandmax());
- $aFiles = $this->PrepareFilesToBackup($sSourceConfigFile, $sTmpFolder);
-
- $sFilesList = var_export($aFiles, true);
- $this->LogInfo("backup: adding to archive files '$sFilesList'");
- $bArchiveCreationResult = $oArchive->createModify($aFiles, '', $sTmpFolder);
- if (!$bArchiveCreationResult)
- {
- $sErrorMsg = 'Cannot backup : unable to create archive';
- $this->LogError($sErrorMsg);
- throw new BackupException($sErrorMsg);
- }
-
- $this->LogInfo("backup: removing tmp folder '$sTmpFolder'");
SetupUtils::rrmdir($sTmpFolder);
}
-
- /**
- * Copy files to store into the temporary folder, in addition to the SQL dump
- *
- * @param string $sSourceConfigFile
- * @param string $sTmpFolder
- *
- * @return array list of files to archive
- * @throws \Exception
- */
- protected function PrepareFilesToBackup($sSourceConfigFile, $sTmpFolder)
+ $this->LogInfo("backup: creating tmp dir '$sTmpFolder'");
+ @mkdir($sTmpFolder, 0777, true);
+ if (is_null($sSourceConfigFile))
{
- $aRet = array();
- if (is_dir($sTmpFolder))
- {
- SetupUtils::rrmdir($sTmpFolder);
- }
- $this->LogInfo("backup: creating tmp dir '$sTmpFolder'");
- @mkdir($sTmpFolder, 0777, true);
- if (is_null($sSourceConfigFile))
- {
- $sSourceConfigFile = MetaModel::GetConfig()->GetLoadedFile();
- }
- if (!empty($sSourceConfigFile))
- {
- $sFile = $sTmpFolder.'/config-itop.php';
- $this->LogInfo("backup: adding resource '$sSourceConfigFile'");
- copy($sSourceConfigFile, $sFile);
- $aRet[] = $sFile;
- }
-
- $sDeltaFile = APPROOT.'data/'.utils::GetCurrentEnvironment().'.delta.xml';
- if (file_exists($sDeltaFile))
- {
- $sFile = $sTmpFolder.'/delta.xml';
- $this->LogInfo("backup: adding resource '$sDeltaFile'");
- copy($sDeltaFile, $sFile);
- $aRet[] = $sFile;
- }
- $sExtraDir = APPROOT.'data/'.utils::GetCurrentEnvironment().'-modules/';
- if (is_dir($sExtraDir))
- {
- $sModules = utils::GetCurrentEnvironment().'-modules';
- $sFile = $sTmpFolder.'/'.$sModules;
- $this->LogInfo("backup: adding resource '$sExtraDir'");
- SetupUtils::copydir($sExtraDir, $sFile);
- $aRet[] = $sFile;
- }
- $sDataFile = $sTmpFolder.'/itop-dump.sql';
- $this->DoBackup($sDataFile);
- $aRet[] = $sDataFile;
-
- return $aRet;
+ $sSourceConfigFile = MetaModel::GetConfig()->GetLoadedFile();
+ }
+ if (!empty($sSourceConfigFile))
+ {
+ $sFile = $sTmpFolder.'/config-itop.php';
+ $this->LogInfo("backup: adding resource '$sSourceConfigFile'");
+ copy($sSourceConfigFile, $sFile);
+ $aRet[] = $sFile;
}
- protected static function EscapeShellArg($sValue)
+ $sDeltaFile = APPROOT.'data/'.utils::GetCurrentEnvironment().'.delta.xml';
+ if (file_exists($sDeltaFile))
{
- // Note: See comment from the 23-Apr-2004 03:30 in the PHP documentation
- // It suggests to rely on pctnl_* function instead of using escapeshellargs
- return escapeshellarg($sValue);
+ $sFile = $sTmpFolder.'/delta.xml';
+ $this->LogInfo("backup: adding resource '$sDeltaFile'");
+ copy($sDeltaFile, $sFile);
+ $aRet[] = $sFile;
+ }
+ $sExtraDir = APPROOT.'data/'.utils::GetCurrentEnvironment().'-modules/';
+ if (is_dir($sExtraDir))
+ {
+ $sModules = utils::GetCurrentEnvironment().'-modules';
+ $sFile = $sTmpFolder.'/'.$sModules;
+ $this->LogInfo("backup: adding resource '$sExtraDir'");
+ SetupUtils::copydir($sExtraDir, $sFile);
+ $aRet[] = $sFile;
+ }
+ $sDataFile = $sTmpFolder.'/itop-dump.sql';
+ $this->DoBackup($sDataFile);
+ $aRet[] = $sDataFile;
+
+ return $aRet;
+ }
+
+ protected static function EscapeShellArg($sValue)
+ {
+ // Note: See comment from the 23-Apr-2004 03:30 in the PHP documentation
+ // It suggests to rely on pctnl_* function instead of using escapeshellargs
+ return escapeshellarg($sValue);
+ }
+
+ /**
+ * Create a backup file
+ *
+ * @param string $sBackupFileName
+ *
+ * @throws \BackupException
+ */
+ public function DoBackup($sBackupFileName)
+ {
+ $sHost = self::EscapeShellArg($this->sDBHost);
+ $sUser = self::EscapeShellArg($this->oConfig->Get('db_user'));
+ $sPwd = self::EscapeShellArg($this->oConfig->Get('db_pwd'));
+ $sDBName = self::EscapeShellArg($this->sDBName);
+
+ // Just to check the connection to the DB (better than getting the retcode of mysqldump = 1)
+ $this->DBConnect();
+
+ $sTables = '';
+ if ($this->sDBSubName != '')
+ {
+ // This instance of iTop uses a prefix for the tables, so there may be other tables in the database
+ // Let's explicitely list all the tables and views to dump
+ $aTables = $this->EnumerateTables();
+ if (count($aTables) == 0)
+ {
+ // No table has been found with the given prefix
+ throw new BackupException("No table has been found with the given prefix");
+ }
+ $aEscapedTables = array();
+ foreach ($aTables as $sTable)
+ {
+ $aEscapedTables[] = self::EscapeShellArg($sTable);
+ }
+ $sTables = implode(' ', $aEscapedTables);
}
- /**
- * Create a backup file
- *
- * @param string $sBackupFileName
- *
- * @throws \BackupException
- */
- public function DoBackup($sBackupFileName)
- {
- $sHost = self::EscapeShellArg($this->sDBHost);
- $sUser = self::EscapeShellArg($this->oConfig->Get('db_user'));
- $sPwd = self::EscapeShellArg($this->oConfig->Get('db_pwd'));
- $sDBName = self::EscapeShellArg($this->sDBName);
+ $this->LogInfo("Starting backup of $this->sDBHost/$this->sDBName(suffix:'$this->sDBSubName')");
- // Just to check the connection to the DB (better than getting the retcode of mysqldump = 1)
- $this->DBConnect();
+ $sMySQLDump = $this->GetMysqldumpCommand();
- $sTables = '';
- if ($this->sDBSubName != '')
- {
- // This instance of iTop uses a prefix for the tables, so there may be other tables in the database
- // Let's explicitely list all the tables and views to dump
- $aTables = $this->EnumerateTables();
- if (count($aTables) == 0)
- {
- // No table has been found with the given prefix
- throw new BackupException("No table has been found with the given prefix");
- }
- $aEscapedTables = array();
- foreach ($aTables as $sTable)
- {
- $aEscapedTables[] = self::EscapeShellArg($sTable);
- }
- $sTables = implode(' ', $aEscapedTables);
- }
+ // Store the results in a temporary file
+ $sTmpFileName = self::EscapeShellArg($sBackupFileName);
- $this->LogInfo("Starting backup of $this->sDBHost/$this->sDBName(suffix:'$this->sDBSubName')");
+ $sPortOption = self::GetMysqliCliSingleOption('port', $this->iDBPort);
+ $sTlsOptions = self::GetMysqlCliTlsOptions($this->oConfig);
- $sMySQLDump = $this->GetMysqldumpCommand();
+ $sMysqlVersion = CMDBSource::GetDBVersion();
+ $bIsMysqlSupportUtf8mb4 = (version_compare($sMysqlVersion, self::MYSQL_VERSION_WITH_UTF8MB4_IN_PROGRAMS) === -1);
+ $sMysqldumpCharset = $bIsMysqlSupportUtf8mb4 ? 'utf8' : DEFAULT_CHARACTER_SET;
- // Store the results in a temporary file
- $sTmpFileName = self::EscapeShellArg($sBackupFileName);
+ // Delete the file created by tempnam() so that the spawned process can write into it (Windows/IIS)
+ @unlink($sBackupFileName);
- $sPortOption = self::GetMysqliCliSingleOption('port', $this->iDBPort);
- $sTlsOptions = self::GetMysqlCliTlsOptions($this->oConfig);
-
- $sMysqlVersion = CMDBSource::GetDBVersion();
- $bIsMysqlSupportUtf8mb4 = (version_compare($sMysqlVersion, self::MYSQL_VERSION_WITH_UTF8MB4_IN_PROGRAMS) === -1);
- $sMysqldumpCharset = $bIsMysqlSupportUtf8mb4 ? 'utf8' : DEFAULT_CHARACTER_SET;
-
- // Delete the file created by tempnam() so that the spawned process can write into it (Windows/IIS)
- @unlink($sBackupFileName);
-
- // Store the password into a temporary file to avoid mysql complaint
- $sMySQLDumpCnfFile = tempnam(SetupUtils::GetTmpDir(), 'itop-mysqldump-');
- $sMySQLDumpCnf = <<&1";
- $sCommandDisplay = "$sMySQLDump --defaults-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortOption --user=xxxxx $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables";
+ // Note: opt implicitely sets lock-tables... which cancels the benefit of single-transaction!
+ // skip-lock-tables compensates and allows for writes during a backup
+ $sCommand = "$sMySQLDump --defaults-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortOption --user=$sUser $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables 2>&1";
+ $sCommandDisplay = "$sMySQLDump --defaults-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortOption --user=xxxxx $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables";
- // Now run the command for real
- $this->LogInfo("backup: generate data file with command: $sCommandDisplay");
- $aOutput = array();
- $iRetCode = 0;
- exec($sCommand, $aOutput, $iRetCode);
- @unlink($sMySQLDumpCnfFile);
+ // Now run the command for real
+ $this->LogInfo("backup: generate data file with command: $sCommandDisplay");
+ $aOutput = array();
+ $iRetCode = 0;
+ exec($sCommand, $aOutput, $iRetCode);
+ @unlink($sMySQLDumpCnfFile);
+ foreach ($aOutput as $sLine)
+ {
+ $this->LogInfo("mysqldump said: $sLine");
+ }
+ if ($iRetCode != 0)
+ {
+ // Cleanup residual output (Happens with Error 2020: Got packet bigger than 'maxallowedpacket' bytes...)
+ if (file_exists($sBackupFileName))
+ {
+ unlink($sBackupFileName);
+ }
+
+ $this->LogError("Failed to execute: $sCommandDisplay. The command returned:$iRetCode");
foreach ($aOutput as $sLine)
{
- $this->LogInfo("mysqldump said: $sLine");
+ $this->LogError("mysqldump said: $sLine");
}
- if ($iRetCode != 0)
+ if (count($aOutput) == 1)
{
- // Cleanup residual output (Happens with Error 2020: Got packet bigger than 'maxallowedpacket' bytes...)
- if (file_exists($sBackupFileName))
- {
- unlink($sBackupFileName);
- }
-
- $this->LogError("Failed to execute: $sCommandDisplay. The command returned:$iRetCode");
- foreach ($aOutput as $sLine)
- {
- $this->LogError("mysqldump said: $sLine");
- }
- if (count($aOutput) == 1)
- {
- $sMoreInfo = trim($aOutput[0]);
- }
- else
- {
- $sMoreInfo = "Check the log files 'log/setup.log' or 'log/error.log' for more information.";
- }
- throw new BackupException("Failed to execute mysqldump: ".$sMoreInfo);
- }
- }
-
- /**
- * Helper to create a ZIP out of several files
- *
- * @param array $aFiles
- * @param string $sZipArchiveFile
- *
- * @throws \BackupException
- */
- protected function DoZip($aFiles, $sZipArchiveFile)
- {
- foreach ($aFiles as $aFile)
- {
- $sFile = $aFile['source'];
- if (!is_file($sFile) && !is_dir($sFile))
- {
- throw new BackupException("File '$sFile' does not exist or could not be read");
- }
- }
- // Make sure the target path exists
- $sZipDir = dirname($sZipArchiveFile);
- SetupUtils::builddir($sZipDir);
-
- $oZip = new ZipArchiveEx();
- $res = $oZip->open($sZipArchiveFile, ZipArchive::CREATE | ZipArchive::OVERWRITE);
- if ($res === true)
- {
- foreach ($aFiles as $aFile)
- {
- if (is_dir($aFile['source']))
- {
- $oZip->addDir($aFile['source'], $aFile['dest']);
- }
- else
- {
- $oZip->addFile($aFile['source'], $aFile['dest']);
- }
- }
- if ($oZip->close())
- {
- $this->LogInfo("Archive: $sZipArchiveFile created");
- }
- else
- {
- $this->LogError("Failed to save zip archive: $sZipArchiveFile");
- throw new BackupException("Failed to save zip archive: $sZipArchiveFile");
- }
+ $sMoreInfo = trim($aOutput[0]);
}
else
{
- $this->LogError("Failed to create zip archive: $sZipArchiveFile.");
- throw new BackupException("Failed to create zip archive: $sZipArchiveFile.");
+ $sMoreInfo = "Check the log files 'log/setup.log' or 'log/error.log' for more information.";
}
+ throw new BackupException("Failed to execute mysqldump: ".$sMoreInfo);
}
+ }
- /**
- * Helper to download the file directly from the browser
- *
- * @param string $sFile
- */
- public function DownloadBackup($sFile)
+ /**
+ * Helper to download the file directly from the browser
+ *
+ * @param string $sFile full file path
+ *
+ * @throws \InvalidParameterException if the file doesn't exists
+ */
+ public function DownloadBackup($sFile)
+ {
+ if (!file_exists($sFile))
{
- if (file_exists($sFile))
- {
- header('Content-Description: File Transfer');
- header('Content-Type: multipart/x-zip');
- header('Content-Disposition: inline; filename="'.basename($sFile).'"');
- header('Expires: 0');
- header('Cache-Control: must-revalidate');
- header('Pragma: public');
- header('Content-Length: '.filesize($sFile));
- readfile($sFile) ;
- }
- else
- {
- throw new InvalidParameterException('Invalid file path');
- }
+ throw new InvalidParameterException('Invalid file path');
}
- /**
- * Helper to open a Database connection
- *
- * @return \mysqli
- * @throws \BackupException
- * @uses CMDBSource
- */
- protected function DBConnect()
+ $sMimeType = utils::GetFileMimeType($sFile);
+
+ header('Content-Description: File Transfer');
+ header('Content-Type: '.$sMimeType);
+ header('Content-Disposition: inline; filename="'.basename($sFile).'"');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate');
+ header('Pragma: public');
+ header('Content-Length: '.filesize($sFile));
+ readfile($sFile);
+ }
+
+ /**
+ * Helper to open a Database connection
+ *
+ * @return \mysqli
+ * @throws \BackupException
+ * @uses CMDBSource
+ */
+ protected function DBConnect()
+ {
+ $oConfig = $this->oConfig;
+ $sServer = $oConfig->Get('db_host');
+ $sUser = $oConfig->Get('db_user');
+ $sPwd = $oConfig->Get('db_pwd');
+ $sSource = $oConfig->Get('db_name');
+ $sTlsEnabled = $oConfig->Get('db_tls.enabled');
+ $sTlsCA = $oConfig->Get('db_tls.ca');
+
+ try
{
- $oConfig = $this->oConfig;
- $sServer = $oConfig->Get('db_host');
- $sUser = $oConfig->Get('db_user');
- $sPwd = $oConfig->Get('db_pwd');
- $sSource = $oConfig->Get('db_name');
- $sTlsEnabled = $oConfig->Get('db_tls.enabled');
- $sTlsCA = $oConfig->Get('db_tls.ca');
+ $oMysqli = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $sTlsEnabled, $sTlsCA,
+ false);
- try
+ if ($oMysqli->connect_errno)
{
- $oMysqli = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $sTlsEnabled, $sTlsCA,
- false);
-
- if ($oMysqli->connect_errno)
- {
- $sHost = is_null($this->iDBPort) ? $this->sDBHost : $this->sDBHost.' on port '.$this->iDBPort;
- throw new BackupException("Cannot connect to the MySQL server '$sHost' (".$oMysqli->connect_errno.") ".$oMysqli->connect_error);
- }
- if (!$oMysqli->select_db($this->sDBName))
- {
- throw new BackupException("The database '$this->sDBName' does not seem to exist");
- }
-
- return $oMysqli;
+ $sHost = is_null($this->iDBPort) ? $this->sDBHost : $this->sDBHost.' on port '.$this->iDBPort;
+ throw new BackupException("Cannot connect to the MySQL server '$sHost' (".$oMysqli->connect_errno.") ".$oMysqli->connect_error);
}
- catch (MySQLException $e)
+ if (!$oMysqli->select_db($this->sDBName))
{
- throw new BackupException($e->getMessage());
+ throw new BackupException("The database '$this->sDBName' does not seem to exist");
}
+
+ return $oMysqli;
}
-
- /**
- * Helper to enumerate the tables of the database
- *
- * @throws \BackupException
- */
- protected function EnumerateTables()
+ catch (MySQLException $e)
{
- $oMysqli = $this->DBConnect();
- if ($this->sDBSubName != '')
- {
- $oResult = $oMysqli->query("SHOW TABLES LIKE '{$this->sDBSubName}%'");
- }
- else
- {
- $oResult = $oMysqli->query("SHOW TABLES");
- }
- if (!$oResult)
- {
- throw new BackupException("Failed to execute the SHOW TABLES query: ".$oMysqli->error);
- }
- $aTables = array();
- while ($aRow = $oResult->fetch_row())
- {
- $aTables[] = $aRow[0];
- }
-
- return $aTables;
+ throw new BackupException($e->getMessage());
}
+ }
-
- /**
- * @param Config $oConfig
- *
- * @return string TLS arguments for CLI programs such as mysqldump. Empty string if the config does not use TLS.
- *
- * @see https://dev.mysql.com/doc/refman/5.6/en/encrypted-connection-options.html
- * @since 2.5
- */
- public static function GetMysqlCliTlsOptions($oConfig)
+ /**
+ * Helper to enumerate the tables of the database
+ *
+ * @throws \BackupException
+ */
+ protected function EnumerateTables()
+ {
+ $oMysqli = $this->DBConnect();
+ if ($this->sDBSubName != '')
{
- $bDbTlsEnabled = $oConfig->Get('db_tls.enabled');
- if (!$bDbTlsEnabled)
- {
- return '';
- }
- $sTlsOptions = '';
-
- $sDBVendor= CMDBSource::GetDBVendor();
- $sDBVersion = CMDBSource::GetDBVersion();
- $sMysqlSSLModeVersion = '5.7.0'; //Mysql 5.7.0 and upper deprecated --ssl and uses --ssl-mode instead
- if ($sDBVendor === CMDBSource::ENUM_DB_VENDOR_MYSQL && version_compare($sDBVersion, $sMysqlSSLModeVersion, '>='))
- {
- $sTlsOptions .= ' --ssl-mode=VERIFY_CA';
- }
- else
- {
- $sTlsOptions .= ' --ssl';
- }
-
- // ssl-key parameter : not implemented
- // ssl-cert parameter : not implemented
-
- $sTlsOptions .= self::GetMysqliCliSingleOption('ssl-ca', $oConfig->Get('db_tls.ca'));
-
- // ssl-cipher parameter : not implemented
- // ssl-capath parameter : not implemented
-
- return $sTlsOptions;
+ $oResult = $oMysqli->query("SHOW TABLES LIKE '{$this->sDBSubName}%'");
}
-
- /**
- * @param string $sCliArgName
- * @param string $sData
- *
- * @return string empty if data is empty, else argument in form of ' --cliargname=data'
- */
- private static function GetMysqliCliSingleOption($sCliArgName, $sData)
+ else
{
- if (empty($sData))
- {
- return '';
- }
-
- return ' --'.$sCliArgName.'='.self::EscapeShellArg($sData);
+ $oResult = $oMysqli->query("SHOW TABLES");
}
-
- /**
- * @return string the command to launch mysqldump (without its params)
- */
- private function GetMysqldumpCommand()
+ if (!$oResult)
{
- $sMySQLBinDir = utils::ReadParam('mysql_bindir', $this->sMySQLBinDir, true);
- if (empty($sMySQLBinDir))
- {
- $sMysqldumpCommand = 'mysqldump';
- }
- else
- {
- $sMysqldumpCommand = '"'.$sMySQLBinDir.'/mysqldump"';
- }
-
- return $sMysqldumpCommand;
+ throw new BackupException("Failed to execute the SHOW TABLES query: ".$oMysqli->error);
}
+ $aTables = array();
+ while ($aRow = $oResult->fetch_row())
+ {
+ $aTables[] = $aRow[0];
+ }
+
+ return $aTables;
+ }
+
+
+ /**
+ * @see https://dev.mysql.com/doc/refman/5.6/en/encrypted-connection-options.html
+ *
+ * @param Config $oConfig
+ *
+ * @return string TLS arguments for CLI programs such as mysqldump. Empty string if the config does not use TLS.
+ *
+ * @since 2.5
+ */
+ public static function GetMysqlCliTlsOptions($oConfig)
+ {
+ $bDbTlsEnabled = $oConfig->Get('db_tls.enabled');
+ if (!$bDbTlsEnabled)
+ {
+ return '';
+ }
+ $sTlsOptions = '';
+
+ $sDBVendor = CMDBSource::GetDBVendor();
+ $sDBVersion = CMDBSource::GetDBVersion();
+ $sMysqlSSLModeVersion = '5.7.0'; //Mysql 5.7.0 and upper deprecated --ssl and uses --ssl-mode instead
+ if ($sDBVendor === CMDBSource::ENUM_DB_VENDOR_MYSQL && version_compare($sDBVersion, $sMysqlSSLModeVersion, '>='))
+ {
+ $sTlsOptions .= ' --ssl-mode=VERIFY_CA';
+ }
+ else
+ {
+ $sTlsOptions .= ' --ssl';
+ }
+
+ // ssl-key parameter : not implemented
+ // ssl-cert parameter : not implemented
+
+ $sTlsOptions .= self::GetMysqliCliSingleOption('ssl-ca', $oConfig->Get('db_tls.ca'));
+
+ // ssl-cipher parameter : not implemented
+ // ssl-capath parameter : not implemented
+
+ return $sTlsOptions;
+ }
+
+ /**
+ * @param string $sCliArgName
+ * @param string $sData
+ *
+ * @return string empty if data is empty, else argument in form of ' --cliargname=data'
+ */
+ private static function GetMysqliCliSingleOption($sCliArgName, $sData)
+ {
+ if (empty($sData))
+ {
+ return '';
+ }
+
+ return ' --'.$sCliArgName.'='.self::EscapeShellArg($sData);
+ }
+
+ /**
+ * @return string the command to launch mysqldump (without its params)
+ */
+ private function GetMysqldumpCommand()
+ {
+ $sMySQLBinDir = utils::ReadParam('mysql_bindir', $this->sMySQLBinDir, true);
+ if (empty($sMySQLBinDir))
+ {
+ $sMysqldumpCommand = 'mysqldump';
+ }
+ else
+ {
+ $sMysqldumpCommand = '"'.$sMySQLBinDir.'/mysqldump"';
+ }
+
+ return $sMysqldumpCommand;
}
}