1<?php 2// Copyright (C) 2010-2018 Combodo SARL 3// 4// This file is part of iTop. 5// 6// iTop is free software; you can redistribute it and/or modify 7// it under the terms of the GNU Affero General Public License as published by 8// the Free Software Foundation, either version 3 of the License, or 9// (at your option) any later version. 10// 11// iTop is distributed in the hope that it will be useful, 12// but WITHOUT ANY WARRANTY; without even the implied warranty of 13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14// GNU Affero General Public License for more details. 15// 16// You should have received a copy of the GNU Affero General Public License 17// along with iTop. If not, see <http://www.gnu.org/licenses/> 18 19require_once('tar.php'); 20 21interface BackupArchive 22{ 23 /** 24 * @param string $sFile 25 * 26 * @return bool <b>TRUE</b> if the file is present, <b>FALSE</b> otherwise. 27 */ 28 public function hasFile($sFile); 29 30 /** 31 * @param string $sDirectory 32 * 33 * @return bool <b>TRUE</b> if the directory is present, <b>FALSE</b> otherwise. 34 */ 35 public function hasDir($sDirectory); 36 37 /** 38 * @param string $sDestinationDir 39 * @param string $sArchiveFile 40 * 41 * @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure. 42 */ 43 public function extractFileTo($sDestinationDir, $sArchiveFile); 44 45 /** 46 * Extract a whole directory from the archive. 47 * Usage: $oArchive->extractDirTo('/var/www/html/itop/data', '/production-modules/') 48 * 49 * @param string $sDestinationDir 50 * @param string $sArchiveDir Note: must start and end with a slash !! 51 * 52 * @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure. 53 */ 54 public function extractDirTo($sDestinationDir, $sArchiveDir); 55 56 /** 57 * Returns the entry contents using its name 58 * 59 * @param string $name Name of the entry 60 * @param int $length [optional] The length to be read from the entry. If 0, then the entire entry is read. 61 * @param int $flags [optional] The flags to use to open the archive. the following values may be ORed to it. 62 * <b>ZipArchive::FL_UNCHANGED</b> 63 * 64 * @return string the contents of the entry on success or <b>FALSE</b> on failure. 65 */ 66 public function getFromName($name, $length = 0, $flags = null); 67} 68 69if (class_exists('ZipArchive')) // The setup must be able to start even if the "zip" extension is not loaded 70{ 71 /** 72 * Handles adding directories into a Zip archive, and a unified API for archive read 73 * suggested enhancement: refactor the API for writing as well 74 */ 75 class ZipArchiveEx extends ZipArchive implements BackupArchive 76 { 77 public function addDir($sDir, $sZipDir = '') 78 { 79 if (is_dir($sDir)) 80 { 81 if ($dh = opendir($sDir)) 82 { 83 // Add the directory 84 if (!empty($sZipDir)) 85 { 86 $this->addEmptyDir($sZipDir); 87 } 88 89 // Loop through all the files 90 while (($sFile = readdir($dh)) !== false) 91 { 92 // If it's a folder, run the function again! 93 if (!is_file($sDir.$sFile)) 94 { 95 // Skip parent and root directories 96 if (($sFile !== ".") && ($sFile !== "..")) 97 { 98 $this->addDir($sDir.$sFile."/", $sZipDir.$sFile."/"); 99 } 100 } 101 else 102 { 103 // Add the files 104 $this->addFile($sDir.$sFile, $sZipDir.$sFile); 105 } 106 } 107 } 108 } 109 } 110 111 /** 112 * @param string $sFile 113 * 114 * @return bool <b>TRUE</b> if the file is present, <b>FALSE</b> otherwise. 115 */ 116 public function hasFile($sFile) 117 { 118 return ($this->locateName($sFile) !== false); 119 } 120 121 /** 122 * @param string $sDirectory 123 * 124 * @return bool <b>TRUE</b> if the directory is present, <b>FALSE</b> otherwise. 125 */ 126 public function hasDir($sDirectory) 127 { 128 return ($this->locateName($sDirectory) !== false); 129 } 130 131 /** 132 * @param string $sDestinationDir 133 * @param string $sArchiveFile 134 * 135 * @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure. 136 */ 137 public function extractFileTo($sDestinationDir, $sArchiveFile) 138 { 139 return $this->extractTo($sDestinationDir, $sArchiveFile); 140 } 141 142 /** 143 * Extract a whole directory from the archive. 144 * Usage: $oZip->extractDirTo('/var/www/html/itop/data', '/production-modules/') 145 * 146 * @param string $sDestinationDir 147 * @param string $sZipDir Must start and end with a slash !! 148 * 149 * @return boolean 150 */ 151 public function extractDirTo($sDestinationDir, $sZipDir) 152 { 153 $aFiles = array(); 154 for ($i = 0; $i < $this->numFiles; $i++) 155 { 156 $sEntry = $this->getNameIndex($i); 157 //Use strpos() to check if the entry name contains the directory we want to extract 158 if (strpos($sEntry, $sZipDir) === 0) 159 { 160 //Add the entry to our array if it in in our desired directory 161 $aFiles[] = $sEntry; 162 } 163 } 164 // Extract only the selected files 165 if ((count($aFiles) > 0) && ($this->extractTo($sDestinationDir, $aFiles) === true)) 166 { 167 return true; 168 } 169 170 return false; 171 } 172 } // class ZipArchiveEx 173 174 class BackupException extends Exception 175 { 176 } 177 178 class DBBackup 179 { 180 /** 181 * utf8mb4 was added in MySQL 5.5.3 but works with programs like mysqldump only since MySQL 5.5.33 182 * 183 * @since 2.5 see N°1001 184 */ 185 const MYSQL_VERSION_WITH_UTF8MB4_IN_PROGRAMS = '5.5.33'; 186 187 // To be overriden depending on the expected usages 188 protected function LogInfo($sMsg) 189 { 190 } 191 192 protected function LogError($sMsg) 193 { 194 } 195 196 /** @var Config */ 197 protected $oConfig; 198 199 // shortcuts used for log purposes 200 /** @var string */ 201 protected $sDBHost; 202 /** @var int */ 203 protected $iDBPort; 204 /** @var string */ 205 protected $sDBName; 206 /** @var string */ 207 protected $sDBSubName; 208 209 /** 210 * Connects to the database to backup 211 * 212 * @param Config $oConfig object containing the database configuration.<br> 213 * If null then uses the default configuration ({@see MetaModel::GetConfig}) 214 * 215 * @since 2.5 uses a Config object instead of passing each attribute (there were far too many with the addition of MySQL TLS parameters !) 216 */ 217 public function __construct($oConfig = null) 218 { 219 if (is_null($oConfig)) 220 { 221 // Defaulting to the current config 222 $oConfig = MetaModel::GetConfig(); 223 } 224 225 $this->oConfig = $oConfig; 226 227 // init log variables 228 CMDBSource::InitServerAndPort($oConfig->Get('db_host'), $this->sDBHost, $this->iDBPort); 229 $this->sDBName = $oConfig->get('db_name'); 230 $this->sDBSubName = $oConfig->get('db_subname'); 231 } 232 233 protected $sMySQLBinDir = ''; 234 235 /** 236 * Create a normalized backup name, depending on the current date/time and Database 237 * 238 * @param string sMySQLBinDir Name and path, eventually containing itop placeholders + time formatting specs 239 */ 240 public function SetMySQLBinDir($sMySQLBinDir) 241 { 242 $this->sMySQLBinDir = $sMySQLBinDir; 243 } 244 245 /** 246 * Create a normalized backup name, depending on the current date/time and Database 247 * 248 * @param string sNameSpec Name and path, eventually containing itop placeholders + time formatting specs 249 * 250 * @return string 251 */ 252 public function MakeName($sNameSpec = "__DB__-%Y-%m-%d") 253 { 254 $sFileName = $sNameSpec; 255 $sFileName = str_replace('__HOST__', $this->sDBHost, $sFileName); 256 $sFileName = str_replace('__DB__', $this->sDBName, $sFileName); 257 $sFileName = str_replace('__SUBNAME__', $this->sDBSubName, $sFileName); 258 // Transform %Y, etc. 259 $sFileName = strftime($sFileName); 260 261 return $sFileName; 262 } 263 264 /** 265 * @deprecated 2.4.0 Zip files are limited to 4 Gb, use CreateCompressedBackup to create tar.gz files 266 * 267 * @param string $sZipFile 268 * @param string|null $sSourceConfigFile 269 * 270 * @throws \BackupException 271 */ 272 public function CreateZip($sZipFile, $sSourceConfigFile = null) 273 { 274 $aContents = array(); 275 276 // Note: the file is created by tempnam and might not be writeable by another process (Windows/IIS) 277 // (delete it before spawning a process) 278 $sDataFile = tempnam(SetupUtils::GetTmpDir(), 'itop-'); 279 $this->LogInfo("Data file: '$sDataFile'"); 280 $this->DoBackup($sDataFile); 281 $aContents[] = array( 282 'source' => $sDataFile, 283 'dest' => 'itop-dump.sql', 284 ); 285 286 foreach ($this->GetAdditionalFiles($sSourceConfigFile) as $sArchiveFile => $sSourceFile) 287 { 288 $aContents[] = array( 289 'source' => $sSourceFile, 290 'dest' => $sArchiveFile, 291 ); 292 } 293 294 $this->DoZip($aContents, $sZipFile); 295 296 // Windows/IIS: the data file has been created by the spawned process... 297 // trying to delete it will issue a warning, itself stopping the setup abruptely 298 @unlink($sDataFile); 299 } 300 301 /** 302 * @param string $sTargetFile Path and name, without the extension 303 * @param string|null $sSourceConfigFile Configuration file to embed into the backup, if not the current one 304 * 305 * @throws \Exception 306 */ 307 public function CreateCompressedBackup($sTargetFile, $sSourceConfigFile = null) 308 { 309 $this->LogInfo("Creating backup: '$sTargetFile.tar.gz'"); 310 311 $oArchive = new ArchiveTar($sTargetFile.'.tar.gz'); 312 313 $sTmpFolder = APPROOT.'data/tmp-backup-'.rand(10000, getrandmax()); 314 $aFiles = $this->PrepareFilesToBackup($sSourceConfigFile, $sTmpFolder); 315 316 $sFilesList = var_export($aFiles, true); 317 $this->LogInfo("backup: adding to archive files '$sFilesList'"); 318 $oArchive->createModify($aFiles, '', $sTmpFolder); 319 320 $this->LogInfo("backup: removing tmp folder '$sTmpFolder'"); 321 SetupUtils::rrmdir($sTmpFolder); 322 } 323 324 /** 325 * Copy files to store into the temporary folder, in addition to the SQL dump 326 * 327 * @param string $sSourceConfigFile 328 * @param string $sTmpFolder 329 * 330 * @return array list of files to archive 331 * @throws \Exception 332 */ 333 protected function PrepareFilesToBackup($sSourceConfigFile, $sTmpFolder) 334 { 335 $aRet = array(); 336 if (is_dir($sTmpFolder)) 337 { 338 SetupUtils::rrmdir($sTmpFolder); 339 } 340 $this->LogInfo("backup: creating tmp dir '$sTmpFolder'"); 341 @mkdir($sTmpFolder, 0777, true); 342 if (is_null($sSourceConfigFile)) 343 { 344 $sSourceConfigFile = MetaModel::GetConfig()->GetLoadedFile(); 345 } 346 if (!empty($sSourceConfigFile)) 347 { 348 $sFile = $sTmpFolder.'/config-itop.php'; 349 $this->LogInfo("backup: adding resource '$sSourceConfigFile'"); 350 copy($sSourceConfigFile, $sFile); 351 $aRet[] = $sFile; 352 } 353 354 $sDeltaFile = APPROOT.'data/'.utils::GetCurrentEnvironment().'.delta.xml'; 355 if (file_exists($sDeltaFile)) 356 { 357 $sFile = $sTmpFolder.'/delta.xml'; 358 $this->LogInfo("backup: adding resource '$sDeltaFile'"); 359 copy($sDeltaFile, $sFile); 360 $aRet[] = $sFile; 361 } 362 $sExtraDir = APPROOT.'data/'.utils::GetCurrentEnvironment().'-modules/'; 363 if (is_dir($sExtraDir)) 364 { 365 $sModules = utils::GetCurrentEnvironment().'-modules'; 366 $sFile = $sTmpFolder.'/'.$sModules; 367 $this->LogInfo("backup: adding resource '$sExtraDir'"); 368 SetupUtils::copydir($sExtraDir, $sFile); 369 $aRet[] = $sFile; 370 } 371 $sDataFile = $sTmpFolder.'/itop-dump.sql'; 372 $this->DoBackup($sDataFile); 373 $aRet[] = $sDataFile; 374 375 return $aRet; 376 } 377 378 protected static function EscapeShellArg($sValue) 379 { 380 // Note: See comment from the 23-Apr-2004 03:30 in the PHP documentation 381 // It suggests to rely on pctnl_* function instead of using escapeshellargs 382 return escapeshellarg($sValue); 383 } 384 385 /** 386 * Create a backup file 387 * 388 * @param string $sBackupFileName 389 * 390 * @throws \BackupException 391 */ 392 public function DoBackup($sBackupFileName) 393 { 394 $sHost = self::EscapeShellArg($this->sDBHost); 395 $sUser = self::EscapeShellArg($this->oConfig->Get('db_user')); 396 $sPwd = self::EscapeShellArg($this->oConfig->Get('db_pwd')); 397 $sDBName = self::EscapeShellArg($this->sDBName); 398 399 // Just to check the connection to the DB (better than getting the retcode of mysqldump = 1) 400 $this->DBConnect(); 401 402 $sTables = ''; 403 if ($this->sDBSubName != '') 404 { 405 // This instance of iTop uses a prefix for the tables, so there may be other tables in the database 406 // Let's explicitely list all the tables and views to dump 407 $aTables = $this->EnumerateTables(); 408 if (count($aTables) == 0) 409 { 410 // No table has been found with the given prefix 411 throw new BackupException("No table has been found with the given prefix"); 412 } 413 $aEscapedTables = array(); 414 foreach ($aTables as $sTable) 415 { 416 $aEscapedTables[] = self::EscapeShellArg($sTable); 417 } 418 $sTables = implode(' ', $aEscapedTables); 419 } 420 421 $this->LogInfo("Starting backup of $this->sDBHost/$this->sDBName(suffix:'$this->sDBSubName')"); 422 423 $sMySQLDump = $this->GetMysqldumpCommand(); 424 425 // Store the results in a temporary file 426 $sTmpFileName = self::EscapeShellArg($sBackupFileName); 427 428 $sPortOption = self::GetMysqliCliSingleOption('port', $this->iDBPort); 429 $sTlsOptions = self::GetMysqlCliTlsOptions($this->oConfig); 430 431 $sMysqldumpVersion = self::GetMysqldumpVersion($sMySQLDump); 432 $bIsMysqldumpSupportUtf8mb4 = (version_compare($sMysqldumpVersion, 433 self::MYSQL_VERSION_WITH_UTF8MB4_IN_PROGRAMS) == -1); 434 $sMysqldumpCharset = $bIsMysqldumpSupportUtf8mb4 ? 'utf8' : DEFAULT_CHARACTER_SET; 435 436 // Delete the file created by tempnam() so that the spawned process can write into it (Windows/IIS) 437 @unlink($sBackupFileName); 438 // Note: opt implicitely sets lock-tables... which cancels the benefit of single-transaction! 439 // skip-lock-tables compensates and allows for writes during a backup 440 $sCommand = "$sMySQLDump --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortOption --user=$sUser --password=$sPwd $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables 2>&1"; 441 $sCommandDisplay = "$sMySQLDump --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortOption --user=xxxxx --password=xxxxx $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables"; 442 443 // Now run the command for real 444 $this->LogInfo("backup: generate data file with command: $sCommandDisplay"); 445 $aOutput = array(); 446 $iRetCode = 0; 447 exec($sCommand, $aOutput, $iRetCode); 448 foreach ($aOutput as $sLine) 449 { 450 $this->LogInfo("mysqldump said: $sLine"); 451 } 452 if ($iRetCode != 0) 453 { 454 // Cleanup residual output (Happens with Error 2020: Got packet bigger than 'maxallowedpacket' bytes...) 455 if (file_exists($sBackupFileName)) 456 { 457 unlink($sBackupFileName); 458 } 459 460 $this->LogError("Failed to execute: $sCommandDisplay. The command returned:$iRetCode"); 461 foreach ($aOutput as $sLine) 462 { 463 $this->LogError("mysqldump said: $sLine"); 464 } 465 if (count($aOutput) == 1) 466 { 467 $sMoreInfo = trim($aOutput[0]); 468 } 469 else 470 { 471 $sMoreInfo = "Check the log files '".realpath(APPROOT.'/log/setup.log or error.log')."' for more information."; 472 } 473 throw new BackupException("Failed to execute mysqldump: ".$sMoreInfo); 474 } 475 } 476 477 /** 478 * Helper to create a ZIP out of several files 479 * 480 * @param array $aFiles 481 * @param string $sZipArchiveFile 482 * 483 * @throws \BackupException 484 */ 485 protected function DoZip($aFiles, $sZipArchiveFile) 486 { 487 foreach ($aFiles as $aFile) 488 { 489 $sFile = $aFile['source']; 490 if (!is_file($sFile) && !is_dir($sFile)) 491 { 492 throw new BackupException("File '$sFile' does not exist or could not be read"); 493 } 494 } 495 // Make sure the target path exists 496 $sZipDir = dirname($sZipArchiveFile); 497 SetupUtils::builddir($sZipDir); 498 499 $oZip = new ZipArchiveEx(); 500 $res = $oZip->open($sZipArchiveFile, ZipArchive::CREATE | ZipArchive::OVERWRITE); 501 if ($res === true) 502 { 503 foreach ($aFiles as $aFile) 504 { 505 if (is_dir($aFile['source'])) 506 { 507 $oZip->addDir($aFile['source'], $aFile['dest']); 508 } 509 else 510 { 511 $oZip->addFile($aFile['source'], $aFile['dest']); 512 } 513 } 514 if ($oZip->close()) 515 { 516 $this->LogInfo("Archive: $sZipArchiveFile created"); 517 } 518 else 519 { 520 $this->LogError("Failed to save zip archive: $sZipArchiveFile"); 521 throw new BackupException("Failed to save zip archive: $sZipArchiveFile"); 522 } 523 } 524 else 525 { 526 $this->LogError("Failed to create zip archive: $sZipArchiveFile."); 527 throw new BackupException("Failed to create zip archive: $sZipArchiveFile."); 528 } 529 } 530 531 /** 532 * Helper to download the file directly from the browser 533 * 534 * @param string $sFile 535 */ 536 public function DownloadBackup($sFile) 537 { 538 if (file_exists($sFile)) 539 { 540 header('Content-Description: File Transfer'); 541 header('Content-Type: multipart/x-zip'); 542 header('Content-Disposition: inline; filename="'.basename($sFile).'"'); 543 header('Expires: 0'); 544 header('Cache-Control: must-revalidate'); 545 header('Pragma: public'); 546 header('Content-Length: '.filesize($sFile)); 547 readfile($sFile) ; 548 } 549 else 550 { 551 throw new InvalidParameterException('Invalid file path'); 552 } 553 } 554 555 /** 556 * Helper to open a Database connection 557 * 558 * @return \mysqli 559 * @throws \BackupException 560 * @uses CMDBSource 561 */ 562 protected function DBConnect() 563 { 564 $oConfig = $this->oConfig; 565 $sServer = $oConfig->Get('db_host'); 566 $sUser = $oConfig->Get('db_user'); 567 $sPwd = $oConfig->Get('db_pwd'); 568 $sSource = $oConfig->Get('db_name'); 569 $sTlsEnabled = $oConfig->Get('db_tls.enabled'); 570 $sTlsCA = $oConfig->Get('db_tls.ca'); 571 572 try 573 { 574 $oMysqli = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $sTlsEnabled, $sTlsCA, 575 false); 576 577 if ($oMysqli->connect_errno) 578 { 579 $sHost = is_null($this->iDBPort) ? $this->sDBHost : $this->sDBHost.' on port '.$this->iDBPort; 580 throw new BackupException("Cannot connect to the MySQL server '$sHost' (".$oMysqli->connect_errno.") ".$oMysqli->connect_error); 581 } 582 if (!$oMysqli->select_db($this->sDBName)) 583 { 584 throw new BackupException("The database '$this->sDBName' does not seem to exist"); 585 } 586 587 return $oMysqli; 588 } 589 catch (MySQLException $e) 590 { 591 throw new BackupException($e->getMessage()); 592 } 593 } 594 595 /** 596 * Helper to enumerate the tables of the database 597 * 598 * @throws \BackupException 599 */ 600 protected function EnumerateTables() 601 { 602 $oMysqli = $this->DBConnect(); 603 if ($this->sDBSubName != '') 604 { 605 $oResult = $oMysqli->query("SHOW TABLES LIKE '{$this->sDBSubName}%'"); 606 } 607 else 608 { 609 $oResult = $oMysqli->query("SHOW TABLES"); 610 } 611 if (!$oResult) 612 { 613 throw new BackupException("Failed to execute the SHOW TABLES query: ".$oMysqli->error); 614 } 615 $aTables = array(); 616 while ($aRow = $oResult->fetch_row()) 617 { 618 $aTables[] = $aRow[0]; 619 } 620 621 return $aTables; 622 } 623 624 625 /** 626 * @param Config $oConfig 627 * 628 * @return string TLS arguments for CLI programs such as mysqldump. Empty string if the config does not use TLS. 629 * 630 * @see https://dev.mysql.com/doc/refman/5.6/en/encrypted-connection-options.html 631 * @since 2.5 632 */ 633 public static function GetMysqlCliTlsOptions($oConfig) 634 { 635 $bDbTlsEnabled = $oConfig->Get('db_tls.enabled'); 636 if (!$bDbTlsEnabled) 637 { 638 return ''; 639 } 640 641 $sTlsOptions = ''; 642 $sTlsOptions .= ' --ssl'; 643 644 // ssl-key parameter : not implemented 645 // ssl-cert parameter : not implemented 646 647 $sTlsOptions .= self::GetMysqliCliSingleOption('ssl-ca', $oConfig->Get('db_tls.ca')); 648 649 // ssl-cipher parameter : not implemented 650 // ssl-capath parameter : not implemented 651 652 return $sTlsOptions; 653 } 654 655 /** 656 * @param string $sCliArgName 657 * @param string $sData 658 * 659 * @return string empty if data is empty, else argument in form of ' --cliargname=data' 660 */ 661 private static function GetMysqliCliSingleOption($sCliArgName, $sData) 662 { 663 if (empty($sData)) 664 { 665 return ''; 666 } 667 668 return ' --'.$sCliArgName.'='.self::EscapeShellArg($sData); 669 } 670 671 /** 672 * @return string the command to launch mysqldump (without its params) 673 */ 674 private function GetMysqldumpCommand() 675 { 676 $sMySQLBinDir = utils::ReadParam('mysql_bindir', $this->sMySQLBinDir, true); 677 if (empty($sMySQLBinDir)) 678 { 679 $sMysqldumpCommand = 'mysqldump'; 680 } 681 else 682 { 683 $sMysqldumpCommand = '"'.$sMySQLBinDir.'/mysqldump"'; 684 } 685 686 return $sMysqldumpCommand; 687 } 688 689 /** 690 * @param string $sMysqldumpCommand 691 * 692 * @return string version of the mysqldump program, as parsed from program return 693 * 694 * @uses mysqldump -V Sample return value : mysqldump Ver 10.13 Distrib 5.7.19, for Win64 (x86_64) 695 * @since 2.5 needed to check compatibility with utf8mb4 (N°1001) 696 * @throws \BackupException 697 */ 698 private static function GetMysqldumpVersion($sMysqldumpCommand) 699 { 700 $sCommand = $sMysqldumpCommand.' -V'; 701 $aOutput = array(); 702 exec($sCommand, $aOutput, $iRetCode); 703 704 if ($iRetCode != 0) 705 { 706 throw new BackupException("mysqldump could not be executed (retcode=$iRetCode): Please make sure it is installed and located at : $sMysqldumpCommand"); 707 } 708 709 $sMysqldumpOutput = $aOutput[0]; 710 $aDumpVersionMatchResults = array(); 711 preg_match('/Distrib (\d+\.\d+\.\d+)/', $sMysqldumpOutput, $aDumpVersionMatchResults); 712 713 return $aDumpVersionMatchResults[1]; 714 } 715 } 716} 717 718class TarGzArchive implements BackupArchive 719{ 720 /* 721 * @var ArchiveTar 722 */ 723 protected $oArchive; 724 /* 725 * string[] 726 */ 727 protected $aFiles = null; 728 729 public function __construct($sFile) 730 { 731 $this->oArchive = new ArchiveTar($sFile); 732 } 733 734 /** 735 * @param string $sFile 736 * 737 * @return bool <b>TRUE</b> if the file is present, <b>FALSE</b> otherwise. 738 */ 739 public function hasFile($sFile) 740 { 741 // remove leading and tailing / 742 $sFile = trim($sFile, "/ \t\n\r\0\x0B"); 743 if ($this->aFiles === null) 744 { 745 // Initial load 746 $this->buildFileList(); 747 } 748 foreach ($this->aFiles as $aArchFile) 749 { 750 if ($aArchFile['filename'] == $sFile) 751 { 752 return true; 753 } 754 } 755 756 return false; 757 } 758 759 /** 760 * @param string $sDirectory 761 * 762 * @return bool <b>TRUE</b> if the directory is present, <b>FALSE</b> otherwise. 763 */ 764 public function hasDir($sDirectory) 765 { 766 // remove leading and tailing / 767 $sDirectory = trim($sDirectory, "/ \t\n\r\0\x0B"); 768 if ($this->aFiles === null) 769 { 770 // Initial load 771 $this->buildFileList(); 772 } 773 foreach ($this->aFiles as $aArchFile) 774 { 775 if (($aArchFile['typeflag'] == 5) && ($aArchFile['filename'] == $sDirectory)) 776 { 777 return true; 778 } 779 } 780 781 return false; 782 } 783 784 /** 785 * @param string $p_path 786 * @param null $aEntries 787 * 788 * @return bool 789 */ 790 public function extractTo($p_path = '', $aEntries = null) 791 { 792 return $this->oArchive->extract($p_path); 793 } 794 795 /** 796 * @param string $sDestinationDir 797 * @param string $sArchiveFile 798 * 799 * @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure. 800 */ 801 public function extractFileTo($sDestinationDir, $sArchiveFile) 802 { 803 return $this->oArchive->extractList($sArchiveFile, $sDestinationDir); 804 } 805 806 /** 807 * Extract a whole directory from the archive. 808 * Usage: $oArchive->extractDirTo('/var/www/html/itop/data', '/production-modules/') 809 * 810 * @param string $sDestinationDir 811 * @param string $sArchiveDir 812 * 813 * @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure. 814 */ 815 public function extractDirTo($sDestinationDir, $sArchiveDir) 816 { 817 return $this->oArchive->extractList($sArchiveDir, $sDestinationDir); 818 } 819 820 /** 821 * Returns the entry contents using its name 822 * 823 * @param string $name Name of the entry 824 * @param int $length unused. 825 * @param int $flags unused. 826 * 827 * @return string the contents of the entry on success or <b>FALSE</b> on failure. 828 */ 829 public function getFromName($name, $length = 0, $flags = null) 830 { 831 return $this->oArchive->extractInString($name); 832 } 833 834 /** 835 */ 836 protected function buildFileList() 837 { 838 $this->aFiles = $this->oArchive->listContent(); 839 } 840} 841 842