/lib/util/support.inc.php
PHP | 836 lines | 519 code | 108 blank | 209 comment | 73 complexity | 745f5aec25a5c5070c890dbefd0a240b MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, GPL-3.0
- <?php
- /**
- * $Id:$
- *
- * KnowledgeTree Community Edition
- * Document Management Made Simple
- * Copyright (C) 2008, 2009 KnowledgeTree Inc.
- * Portions copyright The Jam Warehouse Software (Pty) Limited
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License version 3 as published by the
- * Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * You can contact KnowledgeTree Inc., PO Box 7775 #87847, San Francisco,
- * California 94120-7775, or email info@knowledgetree.com.
- *
- * The interactive user interfaces in modified source and object code versions
- * of this program must display Appropriate Legal Notices, as required under
- * Section 5 of the GNU General Public License version 3.
- *
- * In accordance with Section 7(b) of the GNU General Public License version 3,
- * these Appropriate Legal Notices must retain the display of the "Powered by
- * KnowledgeTree" logo and retain the original copyright notice. If the display of the
- * logo is not reasonably feasible for technical reasons, the Appropriate Legal Notices
- * must display the words "Powered by KnowledgeTree" and retain the original
- * copyright notice.
- * Contributor( s): ______________________________________
- *
- */
- /**
- * TODO: refactor into seperate comparison object
- *
- */
- class MD5SourceTree
- {
- private $rootDir;
- private $logFilename;
- private $logFile;
- private $numDirectories;
- private $numFiles;
- private $comparisonFailure;
- private $exclusions;
- public function __construct($exclusions = array())
- {
- $this->numDirectories = 0;
- $this->numFiles = 0;
- $this->exclusions = $exclusions;
- }
- /**
- * Helper function to traverse the directories. Called initially by scan()
- *
- * @param string $dir
- */
- private function _scan($dir)
- {
- if (in_array($dir, $this->exclusions))
- {
- return;
- }
- if (is_dir($dir))
- {
- if ($dh = opendir($dir))
- {
- while (($filename = readdir($dh)) !== false)
- {
- if (substr($filename,0,1) == '.')
- {
- continue;
- }
- $path = $dir . '/' . $filename;
- if (is_dir($path))
- {
- $this->numDirectories++;
- $this->_scan($path);
- }
- else
- {
- $this->numFiles++;
- if (is_readable($path))
- {
- $md5 = md5_file($path);
- $path = substr($path, strlen($this->rootDir) + 1);
- fwrite($this->logFile, "$md5:$path\n");
- }
- }
- }
- closedir($dh);
- }
- }
- }
- /**
- * This does the scan of the directory.
- *
- * @param string $rootDir
- * @param string $reportFile
- */
- public function scan($rootDir, $reportFile)
- {
- $this->rootDir = $rootDir;
- $this->logFilename = $reportFile;
- $this->logFile = fopen($reportFile,'wt');
- $this->_scan($rootDir);
- fclose($this->logFile);
- }
- /**
- * Used by the compare function, to load a md5 file
- *
- * @param string $path
- * @return array
- */
- private function _loadDirectory($path)
- {
- $dirs = array();
- $numFiles = 0;
- $numDirectories = 0;
- $fp = fopen($path, 'rt');
- while (!feof($fp))
- {
- $line = fgets($fp, 10240);
- list($md5, $path) = explode(':',$line);
- $dirname = dirname($path);
- $filename = basename($path);
- $numFiles++;
- $dirs[$dirname][$filename] = $md5;
- }
- fclose($fp);
- return array('numFiles'=>$numFiles, 'numDirectories'=>$numDirectories, 'dirs'=>$dirs);
- }
- /**
- * Internal function used to compare two md5 directory structures.
- *
- * @param array $prev
- * @param array $cur
- * @param string $msg
- */
- private function _compare($prev, $cur, $msg)
- {
- foreach($prev['dirs'] as $prevDir=>$prevDirFiles)
- {
- if (!array_key_exists($prevDir, $cur['dirs']))
- {
- print "$msg: $prevDir does not exist in target.\n";
- }
- else
- {
- foreach($prevDirFiles as $prevFilename=>$prevMD5)
- {
- if (!array_key_exists($prevFilename, $cur['dirs'][$prevDir]))
- {
- $prevFilename = substr($prevFilename,0,-1);
- print "$msg: $prevFilename does not exist in $prevDir.\n";
- }
- else
- {
- if (in_array($prevDir . '/' . $prevFilename, $this->comparisonFailure))
- {
- continue;
- }
- $newMD5 = $cur['dirs'][$prevDir][$prevFilename];
- if ($prevMD5 != $newMD5)
- {
- $this->comparisonFailure[] = $prevDir . '/' . $prevFilename;
- $prevFilename = substr($prevFilename,0,-1);
- print "$msg: $prevFilename does not match md5; $prevMD5 != $newMD5.\n";
- }
- }
- }
- }
- }
- }
- /**
- * Compare to md5 report files
- *
- * @param string $reportA
- * @param string $reportB
- */
- public function compare($reportA, $reportB)
- {
- if (is_null($reportB))
- {
- $reportB = $this->logFilename;
- }
- $this->comparisonFailure = array();
- $prev = $this->_loadDirectory($reportA);
- $cur = $this->_loadDirectory($reportB);
- if ($prev['numDirectories'] != $cur['numDirectories'])
- {
- print "Folder count mismatch!\n";
- }
- if ($prev['numFiles'] != $cur['numFiles'])
- {
- print "File count mismatch!\n";
- }
- $this->_compare($prev, $cur,'>');
- $this->_compare($cur,$prev,'<');
- }
- }
- class SupportUtil
- {
- private $path;
- private $innodb;
- private $noninnodb;
- /**
- * Constructor for SupportUtil. Creates a folder with format support-YYYY-MM-DD_HH-mm-ss
- *
- */
- function __construct()
- {
- $config = KTConfig::getSingleton();
- $tempdir = $config->get('urls/tmpDirectory');
- $this->path = $tempdir . "/support-" . date('Y-m-d_H-i-s');
- mkdir($this->path);
- }
- /**
- * Main function to capture as much info that is reasonable.
- *
- */
- public function capture()
- {
- // get php info
- $this->capture_phpinfo($this->path . '/phpinfo.htm');
- // get db schema
- $tables = $this->capture_db_schema($this->path);
- // get zseq counters from taables
- $this->capture_zseqs($tables, $this->path . '/zseqreport.htm');
- // get md5 on table
- $exclusions = array(
- KT_DIR . '/var',
- realpath(KT_DIR . '/../var')
- );
- $tree = new MD5SourceTree($exclusions);
- $config = KTConfig::getSingleton();
- $sourcePath = $config->get('KnowledgeTree/fileSystemRoot');
- $tree->scan($sourcePath, $this->path . '/md5report.txt');
- // get plugins
- $this->capture_plugins($this->path . '/plugins.htm');
- // get logs
- $this->capture_logs($this->path);
- // get sys info
- $this->get_sysinfo($this->path);
- // get storage engine list
- $this->create_storage_engine($this->path);
- // get disk space listing
- $this->capture_df($this->path);
- // get process listing
- $this->capture_ps($this->path);
- // get version files
- $this->capture_version_files($this->path);
- // get system settings
- $this->capture_system_settings($this->path);
- // create out index file
- $this->create_index($this->path);
- }
- /**
- * Main helper function to cleanup after creating zip file
- *
- * @param stirng $path
- */
- private function _cleanup($path)
- {
- $dh = opendir($path);
- while (($filename = readdir($dh)) !== false)
- {
- if (substr($filename,0,1) == '.') continue;
- $fullname = $path . '/' . $filename;
- if (is_dir($fullname))
- {
- $this->_cleanup($fullname);
- }
- else
- {
- unlink($fullname);
- }
- }
- closedir($dh);
- rmdir($path);
- }
- /**
- * Main cleanup function
- *
- */
- public function cleanup()
- {
- $this->_cleanup($this->path);
- }
- /**
- * Creates an archive file
- *
- * @return string
- */
- public function archive()
- {
- $zip = KTUtil::findCommand('export/zip', 'zip');
- chdir(dirname($this->path));
- $subdir = basename($this->path);
- $archivename = $this->path . '.zip';
- $cmd = "\"$zip\" -r \"$archivename\" \"$subdir\"";
- KTUtil::pexec($cmd);
- return $archivename;
- }
- /**
- * Tries to get list of running processes
- *
- * @param string $path
- */
- private function capture_ps($path)
- {
- $ps = KTUtil::findCommand('externalBinary/ps', 'ps');
- if (!file_exists($ps) || !is_executable($ps))
- {
- return;
- }
- $cmd = "'$ps' waux";
- // TODO: refactor to use KTUtil::pexec
- $ps = popen($cmd, 'r');
- $content = fread($ps , 10240);
- pclose($ps);
- file_put_contents($path . '/ps.txt', $content);
- }
- /**
- * Get list of KnowledgeTree version files
- *
- * @param string $path
- */
- private function capture_version_files($path)
- {
- $path = $path . '/versions';
- mkdir($path);
- $ver_path = KT_DIR . '/docs';
- $dh = opendir($ver_path);
- while (($filename = readdir($dh)) !== false)
- {
- if (substr($filename, 0, 7) == 'VERSION')
- {
- copy($ver_path . '/' . $filename, $path . '/' . $filename);
- }
- }
- closedir($dh);
- }
- /**
- * Dump the system_settings table, except for dashboard-state entries.
- *
- * @param string $path
- */
- private function capture_system_settings($path)
- {
- $sql = "SELECT id, name, value FROM system_settings";
- $rs = DBUtil::getResultArray($sql);
- $html = "<h1>System Settings</h1>";
- $html .= '<br><table border=1 cellpadding=0 cellspacing=0>';
- foreach($rs as $rec)
- {
- $id = $rec['id'];
- $name = $rec['name'];
- $value = $rec['value'];
- if (substr($name, 0, 15) == 'dashboard-state') continue;
- $html .= "<tr><td>$id<td>$name<td>$value\r\n";
- }
- $html .= '</table>';
- file_put_contents($path . '/systemsettings.htm', $html);
- }
- /**
- * Get disk usage
- *
- * @param string $path
- */
- private function capture_df($path)
- {
- $df = KTUtil::findCommand('externalBinary/df', 'df');
- if (!file_exists($df) || !is_executable($df))
- {
- return;
- }
- $df = popen($df, 'r');
- $content = fread($df, 10240);
- pclose($df);
- file_put_contents($path . '/df.txt', $content);
- }
- /**
- * Get php info
- *
- * @param string $filename
- */
- private function capture_phpinfo($filename)
- {
- ob_start();
- phpinfo();
- $phpinfo = ob_get_clean();
- file_put_contents($filename, $phpinfo);
- }
- /**
- * Helper table to get schema
- *
- * @param string $folder
- * @return string
- */
- private function capture_table_schema($folder)
- {
- $tables = array();
- $sql = 'show tables';
- $results = DBUtil::getResultArray($sql);
- foreach($results as $rec)
- {
- $rec = array_values($rec);
- $tablename = $rec[0];
- $sql = "show create table $tablename";
- $sql = DBUtil::getOneResultKey($sql,'Create Table');
- file_put_contents($folder . '/' . $tablename . '.sql.txt', $sql);
- $sql = strtolower($sql);
- if (strpos($sql, 'innodb') === false)
- $this->noninnodb[] = $tablename;
- else
- $this->innodb[] = $tablename;
- $tables[] = $tablename;
- }
- return $tables;
- }
- /**
- * Get database schema
- *
- * @param string $folder
- * @param string $suffix
- * @return array
- */
- private function capture_db_schema($folder, $suffix='')
- {
- $schema_folder = $folder . '/' . $suffix . 'schema';
- mkdir($schema_folder);
- return $this->capture_table_schema($schema_folder);
- }
- /**
- * Get list of plugins
- *
- * @param string $filename
- */
- private function capture_plugins($filename)
- {
- $sql = 'select namespace,path, disabled, unavailable,friendly_name from plugins';
- $result = DBUtil::getResultArray($sql);
- $plugins = "<h1>Plugin Status Report</h1>";
- $plugins .= '<table border=1 cellpadding=0 cellspacing=0u >';
- $plugins .= '<tr><th>Display Name<th>Availability<th>Namespace<th>Path';
- foreach($result as $rec)
- {
- $fileexists = file_exists(KT_DIR . '/' . $rec['path'])?'':'<font color="red">';
- $status = ($rec['disabled'] == 0)?'<font color="green">':'<font color="orange">';
- $unavailable = ($rec['unavailable'] == 0)?'available':'<font color="orange">unavailable';
- $plugins .= '<tr>';
- $plugins .= '<td>' . $status . $rec['friendly_name'];
- $plugins .= '<td>' . $unavailable;
- $plugins .= '<td>' . $rec['namespace'];
- $plugins .= '<td>' . $fileexists . $rec['path'] . "\r\n";
- }
- $plugins .= '</table>';
- $plugins .= '<br>Plugin name is <font color=green>green</font> if enabled and <font color=orange>orange</font> if disabled .';
- $plugins .= '<br>Availability indicates that KnowledgeTree has detected the plugin not to be available.';
- $plugins .= '<br>Path is coloured <font color=red>red</font> if the plugin file cannot be resolved. If the path is not resolved, it should be flagged unavailable.';
- file_put_contents($filename, $plugins);
- }
- /**
- * Make a zseq report
- *
- * @param string $tables
- * @param string $filename
- */
- private function capture_zseqs($tables, $filename)
- {
- $zseqs = '<h1>Table Counter Report</h1>';
- $zseqs .= '<table border=1 cellpadding=0 cellspacing=0>';
- $zseqs .= '<tr><td>Table<td>Max ID<td>ZSEQ<td>Status';
- foreach($tables as $ztablename)
- {
- if (substr($ztablename, 0, 5) != 'zseq_')
- {
- continue;
- }
- $tablename = substr($ztablename, 5);
- $sql = "SELECT max(id) as maxid FROM $tablename";
- $maxid = DBUtil::getOneResultKey($sql, 'maxid');
- $sql = "SELECT id FROM $ztablename";
- $zseqid = DBUtil::getOneResultKey($sql, 'id');
- $note = (is_null($maxid) || $maxid <= $zseqid)?'OK':'FAIL';
- if ($note == 'FAIL' && $maxid > $zseqid)
- {
- $note = 'COUNTER PROBLEM! maxid should be less than or equal to zseq';
- }
- if (PEAR::isError($maxid))
- {
- $maxid = '??';
- $note = "STRANGE - DB ERROR ON $tablename";
- }
- if (PEAR::isError($zseqid))
- {
- $zseqid = '??';
- $note = "STRANGE - DB ERROR ON $ztablename";
- }
- if (is_null($maxid))
- {
- $maxid='empty';
- }
- if (is_null($zseqid))
- {
- $zseqid='empty';
- $note = "STRANGE - ZSEQ SHOULD NOT BE EMPTY ON $ztablename";
- }
- $zseqs .= "<tr><td>$tablename<td>$maxid<td>$zseqid<td>$note\r\n";
- }
- $zseqs .= "</table>";
- file_put_contents($filename, $zseqs);
- }
- /**
- * Get log files
- *
- * @param string $path
- */
- private function capture_logs($path)
- {
- $path = $path . '/logs';
- mkdir($path);
- $this->capture_kt_log($path);
- $this->capture_apache_log($path);
- $this->capture_php_log($path);
- $this->capture_mysql_log($path);
- }
- /**
- * Get Php log file. KT makes a php_error_log when tweak setting is enabled.
- *
- * @param string $path
- */
- private function capture_php_log($path)
- {
- $config = KTConfig::getSingleton();
- $logdir = $config->get('urls/logDirectory');
- $logfile = $logdir . '/php_error_log';
- if (file_exists($logfile))
- {
- copy($logfile, $path . '/php-error_log.txt');
- }
- }
- /**
- * Get mysql log from stack. It is difficult to resolve otherwise.
- *
- * @param string $path
- */
- private function capture_mysql_log($path)
- {
- $stack_path = realpath(KT_DIR . '/../mysql/data');
- if ($stack_path === false || !is_dir($stack_path))
- {
- return;
- }
- $dh = opendir($stack_path);
- while (($filename = readdir($dh)) !== false)
- {
- if (substr($filename, -4) == '.log' && strpos($filename, 'err') !== false)
- {
- copy($stack_path . '/' . $filename, $path . '/mysql-' . $filename);
- }
- }
- closedir($dh);
- }
- /**
- * Get Apache log file from stack. It is difficult to resolve otherwise.
- *
- * @param string $path
- */
- private function capture_apache_log($path)
- {
- $stack_path = realpath(KT_DIR . '/../apache2/logs');
- if ($stack_path === false || !is_dir($stack_path))
- {
- return;
- }
- $dh = opendir($stack_path);
- while (($filename = readdir($dh)) !== false)
- {
- if (substr($filename, -4) == '.log' && strpos($filename, 'err') !== false)
- {
- copy($stack_path . '/' . $filename, $path . '/apache-' . $filename);
- }
- }
- closedir($dh);
- }
- /**
- * Get KT log file.
- *
- * @param string $path
- */
- private function capture_kt_log($path)
- {
- $date = date('Y-m-d');
- $config = KTConfig::getSingleton();
- $logdir = $config->get('urls/logDirectory');
- $dh = opendir($logdir);
- while (($filename = readdir($dh)) !== false)
- {
- if (substr($filename,0,14) != 'log-' . $date)
- {
- continue;
- }
- copy($logdir . '/' . $filename, $path . '/kt-' . $filename);
- }
- closedir($dh);
- }
- /**
- * Get some basic info on Linux if possible. Get cpuinfo, loadavg, meminfo
- *
- * @param string $path
- */
- private function get_sysinfo($path)
- {
- if (!OS_UNIX && !is_dir('/proc'))
- {
- return;
- }
- $path .= '/sysinfo';
- mkdir($path);
- $this->get_sysinfo_file('cpuinfo', $path);
- $this->get_sysinfo_file('loadavg', $path);
- $this->get_sysinfo_file('meminfo', $path);
- }
- /**
- * Helper to get linux sysinfo
- *
- * @param string $filename
- * @param string $path
- */
- private function get_sysinfo_file($filename, $path)
- {
- if (!is_readable('/proc/' . $filename))
- {
- return;
- }
- $content = file_get_contents('/proc/' . $filename);
- file_put_contents($path . '/' . $filename . '.txt', $content);
- }
- /**
- * Helper to create the index file for the support archive.
- *
- * @param string $title
- * @param string $path
- * @param boolean $relative
- * @return string
- */
- private function get_index_contents($title, $path, $relative = true)
- {
- if (!is_dir($path))
- {
- return '';
- }
- $contents = array();
- $dh = opendir($path);
- while (($filename = readdir($dh)) !== false)
- {
- if (substr($filename,0,1) == '.') continue;
- $fullname = $path . '/' . $filename;
- if (!file_exists($fullname) || is_dir($fullname))
- {
- continue;
- }
- $contents[] = $fullname;
- }
- closedir($dh);
- sort($contents);
- $html = $title;
- if (empty($contents))
- {
- $html .= 'There is no content for this section.';
- return $html;
- }
- $dir = '';
- if ($relative) $dir = basename($path) . '/';
- foreach($contents as $filename)
- {
- $corename = basename($filename);
- $ext = pathinfo($corename, PATHINFO_EXTENSION);
- $basename = substr($corename, 0, -strlen($ext)-1);
- $html .= "<a href=\"$dir$corename\">$basename</a><br>";
- }
- return $html;
- }
- /**
- * Create the support archvie index.htm
- *
- * @param string $path
- */
- private function create_index($path)
- {
- $contents = $this->get_index_contents('<h1>Support Info</h1><br>', $path, false);
- $contents .= $this->get_index_contents('<h2>System Info</h2>', $path . '/sysinfo');
- $contents .= $this->get_index_contents('<h2>Logs</h2>', $path . '/logs');
- $contents .= $this->get_index_contents('<h2>Schema</h2>', $path . '/schema');
- file_put_contents($path . '/index.htm', $contents);
- }
- /**
- * Get list of tables based on InnoDB
- *
- * @param string $path
- */
- private function create_storage_engine($path)
- {
- $html = '<h1>Table Storage Engines<h1>';
- $html .= '<table>';
- $html .= '<tr><td valign=top>';
- $html .= '<h2>InnoDB</h2>';
- foreach($this->innodb as $tablename)
- {
- $html .= "$tablename<br>";
- }
- $html .= '<td valign=top>';
- $html .= '<h2>Non-InnoDB</h2>';
- foreach($this->noninnodb as $tablename)
- {
- $html .= "$tablename<br>";
- }
- $html .= '</table>';
- file_put_contents($path . '/tablestorage.htm', $html);
- }
- }
- ?>