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.
302 lines
8.7 KiB
302 lines
8.7 KiB
<?php |
|
/* |
|
* @Author: witersen |
|
* @Date: 2022-04-24 23:37:06 |
|
* @LastEditors: witersen |
|
* @LastEditTime: 2022-05-09 00:00:19 |
|
* @Description: QQ:1801168257 |
|
*/ |
|
|
|
/** |
|
* 将工作模式限制在cli模式 |
|
*/ |
|
if (!preg_match('/cli/i', php_sapi_name())) { |
|
exit('require php-cli mode'); |
|
} |
|
|
|
ini_set('display_errors', '1'); |
|
|
|
error_reporting(E_ALL); |
|
|
|
define('BASE_PATH', __DIR__); |
|
|
|
require_once BASE_PATH . '/../app/util/Config.php'; |
|
|
|
class Daemon |
|
{ |
|
private $pidFile; |
|
private $workMode; |
|
private $scripts = [ |
|
'start', |
|
'stop', |
|
'console' |
|
]; |
|
private $config_daemon; |
|
private $config_svn; |
|
|
|
function __construct() |
|
{ |
|
$this->pidFile = dirname(__FILE__) . '/svnadmind.pid'; |
|
|
|
Config::load(BASE_PATH . '/../config/'); |
|
$this->config_daemon = Config::get('daemon'); |
|
$this->config_svn = Config::get('svn'); |
|
} |
|
|
|
/** |
|
* 将程序变为守护进程 |
|
*/ |
|
private function InitDeamon() |
|
{ |
|
$pid = pcntl_fork(); |
|
if ($pid < 0) { |
|
exit('pcntl_fork 错误'); |
|
} elseif ($pid > 0) { |
|
exit(); |
|
} |
|
$sid = posix_setsid(); |
|
if (!$sid) { |
|
exit('posix_setsid 错误'); |
|
} |
|
$pid = pcntl_fork(); |
|
if ($pid < 0) { |
|
exit('pcntl_fork 错误'); |
|
} elseif ($pid > 0) { |
|
exit(); |
|
} |
|
chdir('/'); |
|
umask(0); |
|
if (defined('STDIN')) { |
|
fclose(STDIN); |
|
} |
|
if (defined('STDOUT')) { |
|
fclose(STDOUT); |
|
} |
|
if (defined('STDERR')) { |
|
fclose(STDERR); |
|
} |
|
file_put_contents($this->pidFile, getmypid()); |
|
$this->InitSocket(); |
|
} |
|
|
|
/** |
|
* 创建TCP套接字并监听指定端口 |
|
*/ |
|
private function InitSocket() |
|
{ |
|
//创建套接字 |
|
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or exit('启动失败:socket_create 错误' . PHP_EOL); |
|
|
|
//绑定地址和端口 |
|
socket_bind($socket, $this->config_daemon['IPC_ADDRESS'], $this->config_daemon['IPC_PORT']) or exit('启动失败:socket_bind 错误,可能是由于频繁启动,端口未释放,请稍后重试或检查端口冲突' . PHP_EOL); |
|
|
|
//设置可重复使用端口号 |
|
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1); |
|
|
|
//监听 设置并发队列的最大长度 |
|
socket_listen($socket, $this->config_daemon['SOCKET_LISTEN_BACKLOG']); |
|
|
|
while (true) { |
|
//非阻塞式回收僵尸进程 |
|
pcntl_wait($status, WNOHANG); |
|
|
|
$client = socket_accept($socket) or exit('启动失败:socket_accept 错误' . PHP_EOL); |
|
|
|
//非阻塞式回收僵尸进程 |
|
pcntl_wait($status, WNOHANG); |
|
|
|
$pid = pcntl_fork(); |
|
if ($pid == -1) { |
|
exit('启动失败:pcntl_fork 错误' . PHP_EOL); |
|
} else if ($pid == 0) { |
|
$this->HandleRequest($client); |
|
} else { |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* 接收TCP连接并处理指令 |
|
*/ |
|
private function HandleRequest($client) |
|
{ |
|
//接收客户端发送的数据 |
|
$cmmand = socket_read($client, $this->config_daemon['SOCKET_READ_LENGTH']); |
|
|
|
//console模式 |
|
if ($this->workMode == 'console') { |
|
echo PHP_EOL . '---------receive---------' . PHP_EOL; |
|
echo $cmmand . PHP_EOL; |
|
} |
|
|
|
if (trim($cmmand) != '') { |
|
//定义错误输出文件路径 |
|
$stderrFile = $this->config_svn['temp_base_path'] . uniqid(); |
|
|
|
//将标准错误重定向到文件 |
|
//使用状态码来标识错误信息 |
|
ob_start(); |
|
passthru($cmmand . " 2>$stderrFile", $resultCode); |
|
$buffer = ob_get_contents(); |
|
ob_end_clean(); |
|
|
|
//将错误信息和正确信息分类收集 |
|
$result = [ |
|
'resultCode' => $resultCode, |
|
'result' => trim($buffer), |
|
'error' => file_get_contents($stderrFile) |
|
]; |
|
|
|
//销毁文件 |
|
unlink($stderrFile); |
|
} else { |
|
//探测程序会发送空信息 |
|
$result = [ |
|
'resultCode' => 0, |
|
'result' => '', |
|
'error' => '' |
|
]; |
|
} |
|
|
|
//console模式 |
|
if ($this->workMode == 'console') { |
|
echo PHP_EOL . '---------result---------' . PHP_EOL; |
|
echo 'resultCode: ' . $result['resultCode'] . PHP_EOL; |
|
echo 'result: ' . $result['result'] . PHP_EOL; |
|
echo 'error: ' . $result['error'] . PHP_EOL; |
|
} |
|
|
|
//将结果序列化并返回 |
|
socket_write($client, serialize($result), strlen(serialize($result))) or die('失败:socket_write 错误' . PHP_EOL); |
|
|
|
//关闭会话 |
|
socket_close($client); |
|
|
|
//退出进程 |
|
exit(); |
|
} |
|
|
|
/** |
|
* 检查操作系统是否符合要求 |
|
*/ |
|
private function CheckSysType() |
|
{ |
|
if (PHP_OS != 'Linux') { |
|
exit('启动失败:当前操作系统不为Linux' . PHP_EOL); |
|
} |
|
if (file_exists('/etc/redhat-release')) { |
|
$info = file_get_contents('/etc/redhat-release'); |
|
if (!strstr($info, 'CentOS') && (strstr($info, '8.') || strstr($info, '7.'))) { |
|
exit('启动失败:当前仅支持 CentOS 7和 CentOS8 操作系统' . PHP_EOL); |
|
} |
|
return; |
|
} |
|
exit('启动失败:当前仅支持 CentOS 7和 CentOS8 操作系统' . PHP_EOL); |
|
} |
|
|
|
/** |
|
* 检查php版本是否符合要求 |
|
*/ |
|
private function CheckPhpVersion() |
|
{ |
|
if (PHP_VERSION < '5.5') { |
|
exit('支持的最低PHP版本为 5.5 而不是 ' . PHP_VERSION . PHP_EOL); |
|
} else if (PHP_VERSION >= '8.0') { |
|
exit('支持的最高PHP版本低于 8.0 而不是 ' . PHP_VERSION . PHP_EOL); |
|
} |
|
} |
|
|
|
/** |
|
* 检查cli模式需要的函数是否被禁用 |
|
*/ |
|
private function CheckDisabledFun() |
|
{ |
|
$require_functions = ['shell_exec', 'passthru', 'pcntl_signal', 'pcntl_fork', 'pcntl_wait']; |
|
$disable_functions = explode(',', ini_get('disable_functions')); |
|
foreach ($disable_functions as $disable) { |
|
if (in_array(trim($disable), $require_functions)) { |
|
exit("启动失败:需要的 $disable 函数被禁用"); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* 更新密钥 |
|
*/ |
|
private function UpdateSign() |
|
{ |
|
$signCon = sprintf("<?php\n\nreturn ['signature' => '%s'];", uniqid()); |
|
file_put_contents(BASE_PATH . '/../config/sign.php', $signCon); |
|
} |
|
|
|
/** |
|
* 以守护进程模式工作 |
|
*/ |
|
private function StartDaemon() |
|
{ |
|
if (file_exists($this->pidFile)) { |
|
$pid = file_get_contents($this->pidFile); |
|
$result = trim(shell_exec("ps -ax | awk '{ print $1 }' | grep -e \"^$pid$\"")); |
|
if (strstr($result, $pid)) { |
|
exit('程序正在运行中' . PHP_EOL); |
|
} |
|
} |
|
$this->InitDeamon(); |
|
} |
|
|
|
/** |
|
* 关闭守护进程 |
|
*/ |
|
private function StopDaemon() |
|
{ |
|
if (file_exists($this->pidFile)) { |
|
$pid = file_get_contents($this->pidFile); |
|
posix_kill((int)$pid, 9); |
|
unlink($this->pidFile); |
|
} |
|
} |
|
|
|
/** |
|
* 以控制台模式工作 用于调试 |
|
*/ |
|
private function StartConsole() |
|
{ |
|
if (file_exists($this->pidFile)) { |
|
$pid = file_get_contents($this->pidFile); |
|
$result = trim(shell_exec("ps -ax | awk '{ print $1 }' | grep -e \"^$pid$\"")); |
|
if (strstr($result, $pid)) { |
|
exit('程序正在运行中,请先停止' . PHP_EOL); |
|
} |
|
} |
|
$this->InitSocket(); |
|
} |
|
|
|
public function Run($argv) |
|
{ |
|
|
|
if (isset($argv[1])) { |
|
$this->workMode = $argv[1]; |
|
if (!in_array($this->workMode, $this->scripts)) { |
|
exit('用法:php svnadmin.php [start | stop | console]' . PHP_EOL); |
|
} |
|
if ($this->workMode == 'stop') { |
|
$this->StopDaemon(); |
|
} else { |
|
$this->CheckSysType(); |
|
$this->CheckPhpVersion(); |
|
$this->CheckDisabledFun(); |
|
if ($this->workMode == 'start') { |
|
$this->UpdateSign(); |
|
$this->StartDaemon(); |
|
} else if ($this->workMode == 'console') { |
|
$this->StartConsole(); |
|
} |
|
} |
|
} else { |
|
exit('用法:php svnadmin.php [start | stop | console]' . PHP_EOL); |
|
} |
|
} |
|
} |
|
|
|
$deamon = new Daemon(); |
|
$deamon->Run($argv);
|
|
|