Bearsampp 2026.3.26
API documentation
Loading...
Searching...
No Matches
class.batch.php
Go to the documentation of this file.
1<?php
2/*
3 * Copyright (c) 2021-2024 Bearsampp
4 * License: GNU General Public License version 3 or later; see LICENSE.txt
5 * Author: Bear
6 * Website: https://bearsampp.com
7 * Github: https://github.com/Bearsampp
8 */
9
30class Batch
31{
32 const END_PROCESS_STR = 'FINISHED!';
33 const CATCH_OUTPUT_FALSE = 'bearsamppCatchOutputFalse';
34
38 public function __construct()
39 {
40 }
41
47 private static function writeLog($log)
48 {
49 global $bearsamppRoot;
50 Util::logDebug($log, $bearsamppRoot->getBatchLogFilePath());
51 }
52
59 public static function findExeByPid($pid)
60 {
61 // Sanitize PID to prevent command injection
62 $sanitizedPid = Util::sanitizePID($pid);
63 if ($sanitizedPid === false) {
64 self::writeLog('Invalid PID provided to findExeByPid: ' . var_export($pid, true));
65 return false;
66 }
67
68 $result = self::exec('findExeByPid', 'TASKLIST /FO CSV /NH /FI "PID eq ' . $sanitizedPid . '"', 5);
69 if ($result !== false) {
70 $expResult = explode('","', $result[0]);
71 if (is_array($expResult) && count($expResult) > 2 && isset($expResult[0]) && !empty($expResult[0])) {
72 return substr($expResult[0], 1);
73 }
74 }
75
76 return false;
77 }
78
85 public static function getProcessUsingPort($port)
86 {
87 // Sanitize port to prevent command injection
88 $sanitizedPort = Util::sanitizePort($port);
89 if ($sanitizedPort === false) {
90 self::writeLog('Invalid port provided to getProcessUsingPort: ' . var_export($port, true));
91 return null;
92 }
93
94 $result = self::exec('getProcessUsingPort', 'NETSTAT -aon', 4);
95 if ($result !== false) {
96 foreach ($result as $row) {
97 if (!Util::startWith($row, 'TCP')) {
98 continue;
99 }
100 $rowExp = explode(' ', preg_replace('/\s+/', ' ', $row));
101 if (count($rowExp) == 5 && Util::endWith($rowExp[1], ':' . $sanitizedPort) && $rowExp[3] == 'LISTENING') {
102 $pid = intval($rowExp[4]);
103 $exe = self::findExeByPid($pid);
104 if ($exe !== false) {
105 return $exe . ' (' . $pid . ')';
106 }
107 return $pid;
108 }
109 }
110 }
111
112 return null;
113 }
114
120 public static function exitApp($restart = false)
121 {
123
124 $content = 'PING 1.1.1.1 -n 1 -w 2000 > nul' . PHP_EOL;
125 $content .= '"' . $bearsamppRoot->getExeFilePath() . '" -quit -id={bearsampp}' . PHP_EOL;
126 if ($restart) {
127 $basename = 'restartApp';
128 Util::logInfo('Restart App');
129 $content .= '"' . $bearsamppCore->getPhpExe() . '" "' . Core::isRoot_FILE . '" "' . Action::RESTART . '"' . PHP_EOL;
130 } else {
131 $basename = 'exitApp';
132 Util::logInfo('Exit App');
133 }
134
136 self::execStandalone($basename, $content);
137 }
138
142 public static function restartApp()
143 {
144 self::exitApp(true);
145 }
146
152 public static function getPearVersion()
153 {
154 global $bearsamppBins;
155
156 $result = self::exec('getPearVersion', 'CMD /C "' . $bearsamppBins->getPhp()->getPearExe() . '" -V', 5);
157 if (is_array($result)) {
158 foreach ($result as $row) {
159 if (Util::startWith($row, 'PEAR Version:')) {
160 $expResult = explode(' ', $row);
161 if (count($expResult) == 3) {
162 return trim($expResult[2]);
163 }
164 }
165 }
166 }
167
168 return null;
169 }
170
174 public static function refreshEnvVars()
175 {
177 self::execStandalone('refreshEnvVars', '"' . $bearsamppCore->getSetEnvExe() . '" -a ' . Registry::APP_PATH_REG_ENTRY . ' "' . Util::formatWindowsPath($bearsamppRoot->getRootPath()) . '"');
178 }
179
185 public static function initializeMysql($path)
186 {
187 if (!file_exists($path . '/init.bat')) {
188 Util::logWarning($path . '/init.bat does not exist');
189 return;
190 }
191 self::exec('initializeMysql', 'CMD /C "' . $path . '/init.bat"', 60);
192 }
193
199 public static function installPostgresqlService()
200 {
201 global $bearsamppBins;
202
203 $cmd = '"' . Util::formatWindowsPath($bearsamppBins->getPostgresql()->getCtlExe()) . '" register -N "' . BinPostgresql::SERVICE_NAME . '"';
204 $cmd .= ' -U "LocalSystem" -D "' . Util::formatWindowsPath($bearsamppBins->getPostgresql()->getSymlinkPath()) . '\\data"';
205 $cmd .= ' -l "' . Util::formatWindowsPath($bearsamppBins->getPostgresql()->getErrorLog()) . '" -w';
206 self::exec('installPostgresqlService', $cmd, true, false);
207
208 if (!$bearsamppBins->getPostgresql()->getService()->isInstalled()) {
209 return false;
210 }
211
212 self::setServiceDisplayName(BinPostgresql::SERVICE_NAME, $bearsamppBins->getPostgresql()->getService()->getDisplayName());
213 self::setServiceDescription(BinPostgresql::SERVICE_NAME, $bearsamppBins->getPostgresql()->getService()->getDisplayName());
215
216 return true;
217 }
218
224 public static function uninstallPostgresqlService()
225 {
226 global $bearsamppBins;
227
228 $cmd = '"' . Util::formatWindowsPath($bearsamppBins->getPostgresql()->getCtlExe()) . '" unregister -N "' . BinPostgresql::SERVICE_NAME . '"';
229 $cmd .= ' -l "' . Util::formatWindowsPath($bearsamppBins->getPostgresql()->getErrorLog()) . '" -w';
230 self::exec('uninstallPostgresqlService', $cmd, true, false);
231 return !$bearsamppBins->getPostgresql()->getService()->isInstalled();
232 }
233
239 public static function initializePostgresql($path)
240 {
241 if (!file_exists($path . '/init.bat')) {
242 Util::logWarning($path . '/init.bat does not exist');
243 return;
244 }
245 self::exec('initializePostgresql', 'CMD /C "' . $path . '/init.bat"', 15);
246 }
247
253 public static function initializeMariadb($path)
254 {
255 if (!file_exists($path . '/init.bat')) {
256 Util::logWarning($path . '/init.bat does not exist');
257 return;
258 }
259 self::exec('initializeMariadb', 'CMD /C "' . $path . '/init.bat"', 60);
260 }
261
268 public static function createSymlink($src, $dest)
269 {
270 global $bearsamppCore;
271 $src = Util::formatWindowsPath($src);
272 $dest = Util::formatWindowsPath($dest);
273 self::exec('createSymlink', '"' . $bearsamppCore->getLnExe() . '" --absolute --symbolic --traditional --1023safe "' . $src . '" ' . '"' . $dest . '"', true, false);
274 }
275
282 public static function removeSymlink($link)
283 {
284 if (!file_exists($link)) {
285 self::writeLog('-> removeSymlink: Link does not exist: ' . $link);
286 return true; // If the link doesn't exist, nothing to do
287 }
288
289 // Check if it's a directory symlink
290 $isDirectory = is_dir($link);
291 $formattedLink = Util::formatWindowsPath($link);
292
293 try {
294 // Use different commands based on whether it's a directory or file symlink
295 if ($isDirectory) {
296 // For directory symlinks
297 self::exec('removeSymlink', 'rmdir /Q "' . $formattedLink . '"', true, false);
298 } else {
299 // For file symlinks
300 self::exec('removeSymlink', 'del /F /Q "' . $formattedLink . '"', true, false);
301 }
302
303 // Check if removal was successful
304 if (file_exists($link)) {
305 self::writeLog('-> removeSymlink: Failed to remove symlink: ' . $link);
306 return false;
307 }
308
309 self::writeLog('-> removeSymlink: Successfully removed symlink: ' . $link);
310 return true;
311 } catch (Exception $e) {
312 self::writeLog('-> removeSymlink: Exception: ' . $e->getMessage());
313 return false;
314 }
315 }
316
322 public static function getOsInfo()
323 {
324 $result = self::exec('getOsInfo', 'ver', 5);
325 if (is_array($result)) {
326 foreach ($result as $row) {
327 if (Util::startWith($row, 'Microsoft')) {
328 return trim($row);
329 }
330 }
331 }
332 return '';
333 }
334
341 public static function setServiceDisplayName($serviceName, $displayName)
342 {
343 // Sanitize service name to prevent command injection
344 $sanitizedName = Util::sanitizeServiceName($serviceName);
345 if ($sanitizedName === false) {
346 self::writeLog('Invalid service name provided to setServiceDisplayName: ' . $serviceName);
347 return;
348 }
349
350 // Remove quotes and dangerous characters from display name
351 $sanitizedDisplayName = str_replace('"', '', $displayName);
352 $sanitizedDisplayName = preg_replace('/[<>|&^]/', '', $sanitizedDisplayName);
353
354 $cmd = 'sc config ' . $sanitizedName . ' DisplayName= "' . $sanitizedDisplayName . '"';
355 self::exec('setServiceDisplayName', $cmd, true, false);
356 }
357
364 public static function setServiceDescription($serviceName, $desc)
365 {
366 // Sanitize service name to prevent command injection
367 $sanitizedName = Util::sanitizeServiceName($serviceName);
368 if ($sanitizedName === false) {
369 self::writeLog('Invalid service name provided to setServiceDescription: ' . $serviceName);
370 return;
371 }
372
373 // Remove quotes and dangerous characters from description
374 $sanitizedDesc = str_replace('"', '', $desc);
375 $sanitizedDesc = preg_replace('/[<>|&^]/', '', $sanitizedDesc);
376
377 $cmd = 'sc description ' . $sanitizedName . ' "' . $sanitizedDesc . '"';
378 self::exec('setServiceDescription', $cmd, true, false);
379 }
380
387 public static function setServiceStartType($serviceName, $startType)
388 {
389 // Sanitize service name to prevent command injection
390 $sanitizedName = Util::sanitizeServiceName($serviceName);
391 if ($sanitizedName === false) {
392 self::writeLog('Invalid service name provided to setServiceStartType: ' . $serviceName);
393 return;
394 }
395
396 // Validate start type (only allow known values)
397 $allowedStartTypes = ['auto', 'demand', 'disabled', 'delayed-auto'];
398 if (!in_array(strtolower($startType), $allowedStartTypes, true)) {
399 self::writeLog('Invalid start type provided: ' . $startType);
400 return;
401 }
402
403 $cmd = 'sc config ' . $sanitizedName . ' start= ' . strtolower($startType);
404 self::exec('setServiceStartType', $cmd, true, false);
405 }
406
415 public static function execStandalone($basename, $content, $silent = true)
416 {
417 return self::exec($basename, $content, false, false, true, $silent);
418 }
419
432 public static function exec($basename, $content, $timeout = true, $catchOutput = true, $standalone = false, $silent = true, $rebuild = true)
433 {
434 global $bearsamppConfig, $bearsamppWinbinder;
435 $result = false;
436
437 $resultFile = self::getTmpFile('.tmp', $basename);
438 $scriptPath = self::getTmpFile('.bat', $basename);
439 $checkFile = self::getTmpFile('.tmp', $basename);
440
441 // Redirect output
442 if ($catchOutput) {
443 $content .= '> "' . $resultFile . '"' . (!Util::endWith($content, '2') ? ' 2>&1' : '');
444 }
445
446 // Header
447 $header = '@ECHO OFF' . PHP_EOL . PHP_EOL;
448
449 // Footer
450 $footer = PHP_EOL . (!$standalone ? PHP_EOL . 'ECHO ' . self::END_PROCESS_STR . ' > "' . $checkFile . '"' : '');
451
452 // Process
453 file_put_contents($scriptPath, $header . $content . $footer);
454 $bearsamppWinbinder->exec($scriptPath, null, $silent);
455
456 if (!$standalone) {
457 $timeout = is_numeric($timeout) ? $timeout : ($timeout === true ? $bearsamppConfig->getScriptsTimeout() : false);
458 $maxtime = time() + $timeout;
459 $noTimeout = $timeout === false;
460 while ($result === false || empty($result)) {
461 if (file_exists($checkFile)) {
462 $check = file($checkFile);
463 if (!empty($check) && trim($check[0]) == self::END_PROCESS_STR) {
464 if ($catchOutput && file_exists($resultFile)) {
465 $result = file($resultFile);
466 } else {
467 $result = self::CATCH_OUTPUT_FALSE;
468 }
469 }
470 }
471 if ($maxtime < time() && !$noTimeout) {
472 break;
473 }
474 }
475 }
476
477 self::writeLog('Exec:');
478 self::writeLog('-> basename: ' . $basename);
479 self::writeLog('-> content: ' . str_replace(PHP_EOL, ' \\\\ ', $content));
480 self::writeLog('-> checkFile: ' . $checkFile);
481 self::writeLog('-> resultFile: ' . $resultFile);
482 self::writeLog('-> scriptPath: ' . $scriptPath);
483
484 if ($result !== false && !empty($result) && is_array($result)) {
485 if ($rebuild) {
486 $rebuildResult = array();
487 foreach ($result as $row) {
488 $row = trim($row);
489 if (!empty($row)) {
490 $rebuildResult[] = $row;
491 }
492 }
493 $result = $rebuildResult;
494 }
495 self::writeLog('-> result: ' . substr(implode(' \\\\ ', $result), 0, 2048));
496 } else {
497 self::writeLog('-> result: N/A');
498 }
499
500 return $result;
501 }
502
510 private static function getTmpFile($ext, $customName = null)
511 {
512 global $bearsamppCore;
513 return Util::formatWindowsPath($bearsamppCore->getTmpPath() . '/' . (!empty($customName) ? $customName . '-' : '') . Util::random() . $ext);
514 }
515}
$result
global $bearsamppBins
global $bearsamppRoot
$port
global $bearsamppCore
const RESTART
__construct()
static writeLog($log)
static removeSymlink($link)
static installPostgresqlService()
const CATCH_OUTPUT_FALSE
static initializeMariadb($path)
static initializePostgresql($path)
static getProcessUsingPort($port)
static execStandalone($basename, $content, $silent=true)
static setServiceDisplayName($serviceName, $displayName)
static setServiceStartType($serviceName, $startType)
static uninstallPostgresqlService()
static refreshEnvVars()
static initializeMysql($path)
static createSymlink($src, $dest)
static getTmpFile($ext, $customName=null)
static exitApp($restart=false)
static getOsInfo()
static setServiceDescription($serviceName, $desc)
static getPearVersion()
static restartApp()
static exec($basename, $content, $timeout=true, $catchOutput=true, $standalone=false, $silent=true, $rebuild=true)
const END_PROCESS_STR
static findExeByPid($pid)
const isRoot_FILE
const APP_PATH_REG_ENTRY
static sanitizeServiceName($serviceName)
static logInfo($data, $file=null)
static logDebug($data, $file=null)
static startWith($string, $search)
static random($length=32, $withNumeric=true)
static endWith($string, $search)
static formatWindowsPath($path)
static sanitizePID($pid)
static sanitizePort($port)
static logWarning($data, $file=null)
static killBins($refreshProcs=false)
global $bearsamppConfig
Definition homepage.php:41