Passed
Branch master (f497d2)
by Mike
03:18
created

grommunio-sync-top.php (15 issues)

1
#!/usr/bin/env php
2
<?php
3
/*
4
 * SPDX-License-Identifier: AGPL-3.0-only
5
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
6
 * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH
7
 *
8
 * Shows realtime information about connected devices and active connections
9
 * in a top-style format.
10
 */
11
12
require_once 'vendor/autoload.php';
13
if (!defined('GSYNC_CONFIG')) {
14
	define('GSYNC_CONFIG', 'config.php');
15
}
16
17
include_once GSYNC_CONFIG;
18
19
/*
20
 * MAIN
21
 */
22
	declare(ticks=1);
23
	define('BASE_PATH_CLI', dirname(__FILE__) . "/");
24
	set_include_path(get_include_path() . PATH_SEPARATOR . BASE_PATH_CLI);
25
26
	if (!defined('GSYNC_CONFIG')) {
27
		define('GSYNC_CONFIG', BASE_PATH_CLI . 'config.php');
28
	}
29
30
	include_once GSYNC_CONFIG;
31
32
	try {
33
		GSync::CheckConfig();
34
		if (!function_exists("pcntl_signal")) {
35
			throw new FatalException("Function pcntl_signal() is not available. Please install package 'php5-pcntl' (or similar) on your system.");
36
		}
37
38
		if (php_sapi_name() != "cli") {
39
			throw new FatalException("This script can only be called from the CLI.");
40
		}
41
42
		$zpt = new GSyncTop();
43
44
		// check if help was requested from CLI
45
		if (in_array('-h', $argv) || in_array('--help', $argv)) {
46
			echo $zpt->UsageInstructions();
47
48
			exit(0);
49
		}
50
51
		if ($zpt->IsAvailable()) {
52
			pcntl_signal(SIGINT, [$zpt, "SignalHandler"]);
53
			$zpt->run();
54
			$zpt->scrClear();
55
			system("stty sane");
56
		}
57
		else {
58
			echo "grommunio-sync interprocess communication (IPC) is not available or TopCollector is disabled.\n";
59
		}
60
	}
61
	catch (GSyncException $zpe) {
62
		fwrite(STDERR, get_class($zpe) . ": " . $zpe->getMessage() . "\n");
63
64
		exit(1);
65
	}
66
67
	echo "terminated\n";
68
69
/*
70
 * grommunio-sync-top
71
 */
72
class GSyncTop {
73
	// show options
74
	public const SHOW_DEFAULT = 0;
75
	public const SHOW_ACTIVE_ONLY = 1;
76
	public const SHOW_UNKNOWN_ONLY = 2;
77
	public const SHOW_TERM_DEFAULT_TIME = 5; // 5 secs
78
79
	private $topCollector;
80
	private $starttime;
81
	private $action;
82
	private $filter;
83
	private $status;
84
	private $statusexpire;
85
	private $wide;
86
	private $wasEnabled;
87
	private $terminate;
88
	private $scrSize;
89
	private $pingInterval;
90
	private $showPush;
91
	private $showTermSec;
92
93
	private $linesActive = [];
94
	private $linesOpen = [];
95
	private $linesUnknown = [];
96
	private $linesTerm = [];
97
	private $pushConn = 0;
98
	private $activeConn = [];
99
	private $activeHosts = [];
100
	private $activeUsers = [];
101
	private $activeDevices = [];
102
103
	/**
104
	 * Constructor.
105
	 */
106
	public function __construct() {
107
		$this->starttime = time();
108
		$this->currenttime = time();
0 ignored issues
show
Bug Best Practice introduced by
The property currenttime does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
109
		$this->action = "";
110
		$this->filter = false;
111
		$this->status = false;
112
		$this->statusexpire = 0;
113
		$this->helpexpire = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property helpexpire does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
114
		$this->doingTail = false;
0 ignored issues
show
Bug Best Practice introduced by
The property doingTail does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
115
		$this->wide = false;
116
		$this->terminate = false;
117
		$this->showPush = true;
118
		$this->showOption = self::SHOW_DEFAULT;
0 ignored issues
show
Bug Best Practice introduced by
The property showOption does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
119
		$this->showTermSec = self::SHOW_TERM_DEFAULT_TIME;
120
		$this->scrSize = ['width' => 80, 'height' => 24];
121
		$this->pingInterval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 12;
122
123
		// get a TopCollector
124
		$this->topCollector = new TopCollector();
125
	}
126
127
	/**
128
	 * Requests data from the running grommunio-sync processes.
129
	 *
130
	 * @return
131
	 */
132
	private function initialize() {
133
		// request feedback from active processes
134
		$this->wasEnabled = $this->topCollector->CollectData();
135
136
		// remove obsolete data
137
		$this->topCollector->ClearLatest(true);
138
139
		// start with default colours
140
		$this->scrDefaultColors();
141
	}
142
143
	/**
144
	 * Main loop of grommunio-sync-top
145
	 * Runs until termination is requested.
146
	 *
147
	 * @return
148
	 */
149
	public function run() {
150
		$this->initialize();
151
152
		do {
153
			$this->currenttime = time();
0 ignored issues
show
Bug Best Practice introduced by
The property currenttime does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
154
155
			// see if shared memory is active
156
			if (!$this->IsAvailable()) {
157
				$this->terminate = true;
158
			}
159
160
			// active processes should continue sending data
161
			$this->topCollector->CollectData();
162
163
			// get and process data from processes
164
			$this->topCollector->ClearLatest();
165
			$topdata = $this->topCollector->ReadLatest();
166
			$this->processData($topdata);
167
168
			// clear screen
169
			$this->scrClear();
170
171
			// check if screen size changed
172
			$s = $this->scrGetSize();
173
			if ($this->scrSize['width'] != $s['width']) {
174
				if ($s['width'] > 180) {
175
					$this->wide = true;
176
				}
177
				else {
178
					$this->wide = false;
179
				}
180
			}
181
			$this->scrSize = $s;
182
183
			// print overview
184
			$this->scrOverview();
185
186
			// wait for user input
187
			$this->readLineProcess();
188
		}
189
		while ($this->terminate != true);
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
190
	}
191
192
	/**
193
	 * Indicates if TopCollector is available collecting data.
194
	 *
195
	 * @return bool
196
	 */
197
	public function IsAvailable() {
198
		if (defined('TOPCOLLECTOR_DISABLED') && constant('TOPCOLLECTOR_DISABLED') === true) {
199
			return false;
200
		}
201
202
		return $this->topCollector->IsActive();
203
	}
204
205
	/**
206
	 * Processes data written by the running processes.
207
	 *
208
	 * @param array $data
209
	 *
210
	 * @return
211
	 */
212
	private function processData($data) {
213
		$this->linesActive = [];
214
		$this->linesOpen = [];
215
		$this->linesUnknown = [];
216
		$this->linesTerm = [];
217
		$this->pushConn = 0;
218
		$this->activeConn = [];
219
		$this->activeHosts = [];
220
		$this->activeUsers = [];
221
		$this->activeDevices = [];
222
223
		if (!is_array($data)) {
0 ignored issues
show
The condition is_array($data) is always true.
Loading history...
224
			return;
225
		}
226
227
		foreach ($data as $devid => $users) {
228
			foreach ($users as $user => $pids) {
229
				foreach ($pids as $pid => $line) {
230
					if (!is_array($line)) {
231
						continue;
232
					}
233
234
					$line['command'] = Utils::GetCommandFromCode($line['command']);
235
236
					if ($line["ended"] == 0) {
237
						$this->activeDevices[$devid] = 1;
238
						$this->activeUsers[$user] = 1;
239
						$this->activeConn[$pid] = 1;
240
						$this->activeHosts[$line['ip']] = 1;
241
242
						$line["time"] = $this->currenttime - $line['start'];
243
						if ($line['push'] === true) {
244
							++$this->pushConn;
245
						}
246
247
						// ignore push connections
248
						if ($line['push'] === true && !$this->showPush) {
249
							continue;
250
						}
251
252
						if ($this->filter !== false) {
253
							$f = $this->filter;
254
							if (!($line["pid"] == $f || $line["ip"] == $f || strtolower($line['command']) == strtolower($f) || preg_match("/.*?{$f}.*?/i", $line['user']) ||
0 ignored issues
show
$f of type true is incompatible with the type string expected by parameter $string of strtolower(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

254
							if (!($line["pid"] == $f || $line["ip"] == $f || strtolower($line['command']) == strtolower(/** @scrutinizer ignore-type */ $f) || preg_match("/.*?{$f}.*?/i", $line['user']) ||
Loading history...
255
								preg_match("/.*?{$f}.*?/i", $line['devagent']) || preg_match("/.*?{$f}.*?/i", $line['devid']) || preg_match("/.*?{$f}.*?/i", $line['addinfo']))) {
256
								continue;
257
							}
258
						}
259
260
						$lastUpdate = $this->currenttime - $line["update"];
261
						if ($this->currenttime - $line["update"] < 2) {
262
							$this->linesActive[$line["update"] . $line["pid"]] = $line;
263
						}
264
						elseif (($line['push'] === true && $lastUpdate > ($this->pingInterval + 2)) || ($line['push'] !== true && $lastUpdate > 4)) {
265
							$this->linesUnknown[$line["update"] . $line["pid"]] = $line;
266
						}
267
						else {
268
							$this->linesOpen[$line["update"] . $line["pid"]] = $line;
269
						}
270
					}
271
					else {
272
						// do not show terminated + expired connections
273
						if ($this->currenttime > $line['ended'] + $this->showTermSec) {
274
							continue;
275
						}
276
277
						if ($this->filter !== false) {
278
							$f = $this->filter;
279
							if (
280
									!(
281
										$line['pid'] == $f ||
282
										$line['ip'] == $f ||
283
										strtolower($line['command']) == strtolower($f) ||
284
										preg_match("/.*?{$f}.*?/i", $line['user']) ||
285
										preg_match("/.*?{$f}.*?/i", $line['devagent']) ||
286
										preg_match("/.*?{$f}.*?/i", $line['devid']) ||
287
										preg_match("/.*?{$f}.*?/i", $line['addinfo'])
288
									)) {
289
								continue;
290
							}
291
						}
292
293
						$line['time'] = $line['ended'] - $line['start'];
294
						$this->linesTerm[$line['update'] . $line['pid']] = $line;
295
					}
296
				}
297
			}
298
		}
299
300
		// sort by execution time
301
		krsort($this->linesActive);
302
		krsort($this->linesOpen);
303
		krsort($this->linesUnknown);
304
		krsort($this->linesTerm);
305
	}
306
307
	/**
308
	 * Prints data to the terminal.
309
	 *
310
	 * @return
311
	 */
312
	private function scrOverview() {
313
		$linesAvail = $this->scrSize['height'] - 8;
314
		$lc = 1;
315
		$this->scrPrintAt($lc, 0, "\033[1mgrommunio-sync-top live statistics\033[0m\t\t\t\t\t" . @strftime("%d/%m/%Y %T") . "\n");
0 ignored issues
show
Are you sure @strftime('%d/%m/%Y %T') of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

315
		$this->scrPrintAt($lc, 0, "\033[1mgrommunio-sync-top live statistics\033[0m\t\t\t\t\t" . /** @scrutinizer ignore-type */ @strftime("%d/%m/%Y %T") . "\n");
Loading history...
316
		++$lc;
317
318
		$this->scrPrintAt($lc, 0, sprintf("Open connections: %d\t\t\t\tUsers:\t %d\tgrommunio-sync:   %s ", count($this->activeConn), count($this->activeUsers), $this->getVersion()));
319
		++$lc;
320
		$this->scrPrintAt($lc, 0, sprintf("Push connections: %d\t\t\t\tDevices: %d\tPHP-MAPI: %s", $this->pushConn, count($this->activeDevices), phpversion("mapi")));
321
		++$lc;
322
		$this->scrPrintAt($lc, 0, sprintf("                                                Hosts:\t %d", count($this->activeHosts)));
323
		++$lc;
324
		++$lc;
325
326
		$this->scrPrintAt($lc, 0, "\033[4m" . $this->getLine(['pid' => 'PID', 'ip' => 'IP', 'user' => 'USER', 'command' => 'COMMAND', 'time' => 'TIME', 'devagent' => 'AGENT', 'devid' => 'DEVID', 'addinfo' => 'Additional Information']) . str_repeat(" ", 20) . "\033[0m");
327
		++$lc;
328
329
		// print help text if requested
330
		$hl = 0;
331
		if ($this->helpexpire > $this->currenttime) {
332
			$help = $this->scrHelp();
333
			$linesAvail -= count($help);
334
			$hl = $this->scrSize['height'] - count($help) - 1;
335
			foreach ($help as $h) {
336
				$this->scrPrintAt($hl, 0, $h);
337
				++$hl;
338
			}
339
		}
340
341
		$toPrintActive = $linesAvail;
342
		$toPrintOpen = $linesAvail;
343
		$toPrintUnknown = $linesAvail;
344
		$toPrintTerm = $linesAvail;
345
346
		// default view: show all unknown, no terminated and half active+open
347
		if (count($this->linesActive) + count($this->linesOpen) + count($this->linesUnknown) > $linesAvail) {
348
			$toPrintUnknown = count($this->linesUnknown);
349
			$toPrintActive = count($this->linesActive);
350
			$toPrintOpen = $linesAvail - $toPrintUnknown - $toPrintActive;
351
			$toPrintTerm = 0;
352
		}
353
354
		if ($this->showOption == self::SHOW_ACTIVE_ONLY) {
355
			$toPrintActive = $linesAvail;
356
			$toPrintOpen = 0;
357
			$toPrintUnknown = 0;
358
			$toPrintTerm = 0;
359
		}
360
361
		if ($this->showOption == self::SHOW_UNKNOWN_ONLY) {
362
			$toPrintActive = 0;
363
			$toPrintOpen = 0;
364
			$toPrintUnknown = $linesAvail;
365
			$toPrintTerm = 0;
366
		}
367
368
		$linesprinted = 0;
369
		foreach ($this->linesActive as $time => $l) {
370
			if ($linesprinted >= $toPrintActive) {
371
				break;
372
			}
373
374
			$this->scrPrintAt($lc, 0, "\033[01m" . $this->getLine($l) . "\033[0m");
375
			++$lc;
376
			++$linesprinted;
377
		}
378
379
		$linesprinted = 0;
380
		foreach ($this->linesOpen as $time => $l) {
381
			if ($linesprinted >= $toPrintOpen) {
382
				break;
383
			}
384
385
			$this->scrPrintAt($lc, 0, $this->getLine($l));
386
			++$lc;
387
			++$linesprinted;
388
		}
389
390
		$linesprinted = 0;
391
		foreach ($this->linesUnknown as $time => $l) {
392
			if ($linesprinted >= $toPrintUnknown) {
393
				break;
394
			}
395
396
			$color = "0;31m";
397
			if ($l['push'] == false && $time - $l["start"] > 30) {
398
				$color = "1;31m";
399
			}
400
			$this->scrPrintAt($lc, 0, "\033[0" . $color . $this->getLine($l) . "\033[0m");
401
			++$lc;
402
			++$linesprinted;
403
		}
404
405
		if ($toPrintTerm > 0) {
406
			$toPrintTerm = $linesAvail - $lc + 6;
407
		}
408
409
		$linesprinted = 0;
410
		foreach ($this->linesTerm as $time => $l) {
411
			if ($linesprinted >= $toPrintTerm) {
412
				break;
413
			}
414
415
			$this->scrPrintAt($lc, 0, "\033[01;30m" . $this->getLine($l) . "\033[0m");
416
			++$lc;
417
			++$linesprinted;
418
		}
419
420
		// add the lines used when displaying the help text
421
		$lc += $hl;
422
		$this->scrPrintAt($lc, 0, "\033[K");
423
		++$lc;
424
		$this->scrPrintAt($lc, 0, "Colorscheme: \033[01mActive  \033[0mOpen  \033[01;31mUnknown  \033[01;30mTerminated\033[0m");
425
426
		// remove old status
427
		if ($this->statusexpire < $this->currenttime) {
428
			$this->status = false;
429
		}
430
431
		// show request information and help command
432
		if ($this->starttime + 6 > $this->currenttime) {
433
			$this->status = sprintf("Requesting information (takes up to %dsecs)", $this->pingInterval) . str_repeat(".", ($this->currenttime - $this->starttime)) . "  type \033[01;31mh\033[00;31m or \033[01;31mhelp\033[00;31m for usage instructions";
434
			$this->statusexpire = $this->currenttime + 1;
435
		}
436
437
		$str = "";
438
		if (!$this->showPush) {
439
			$str .= "\033[00;32mPush: \033[01;32mNo\033[0m   ";
440
		}
441
442
		if ($this->showOption == self::SHOW_ACTIVE_ONLY) {
443
			$str .= "\033[01;32mActive only\033[0m   ";
444
		}
445
446
		if ($this->showOption == self::SHOW_UNKNOWN_ONLY) {
447
			$str .= "\033[01;32mUnknown only\033[0m   ";
448
		}
449
450
		if ($this->showTermSec != self::SHOW_TERM_DEFAULT_TIME) {
451
			$str .= "\033[01;32mTerminated: " . $this->showTermSec . "s\033[0m   ";
452
		}
453
454
		if ($this->filter !== false || ($this->status !== false && $this->statusexpire > $this->currenttime)) {
455
			// print filter in green
456
			if ($this->filter !== false) {
457
				$str .= "\033[00;32mFilter: \033[01;32m{$this->filter}\033[0m   ";
458
			}
459
			// print status in red
460
			if ($this->status !== false) {
461
				$str .= "\033[00;31m{$this->status}\033[0m";
462
			}
463
		}
464
		$this->scrPrintAt(5, 0, $str);
465
466
		$this->scrPrintAt(4, 0, "Action: \033[01m" . $this->action . "\033[0m");
467
	}
468
469
	/**
470
	 * Waits for a keystroke and processes the requested command.
471
	 *
472
	 * @return
473
	 */
474
	private function readLineProcess() {
475
		$ans = explode("^^", `bash -c "read -n 1 -t 1 ANS ; echo \\\$?^^\\\$ANS;"`);
476
477
		if ($ans[0] < 128) {
478
			if (isset($ans[1]) && bin2hex(trim($ans[1])) == "7f") {
479
				$this->action = substr($this->action, 0, -1);
480
			}
481
482
			if (isset($ans[1]) && $ans[1] != "") {
483
				$this->action .= trim(preg_replace("/[^A-Za-z0-9:]/", "", $ans[1]));
484
			}
485
486
			if (bin2hex($ans[0]) == "30" && bin2hex($ans[1]) == "0a") {
487
				$cmds = explode(':', $this->action);
488
				if ($cmds[0] == "quit" || $cmds[0] == "q" || (isset($cmds[1]) && $cmds[0] == "" && $cmds[1] == "q")) {
489
					$this->topCollector->CollectData(true);
490
					$this->topCollector->ClearLatest(true);
491
492
					$this->terminate = true;
493
				}
494
				elseif ($cmds[0] == "clear") {
495
					$this->topCollector->ClearLatest(true);
496
					$this->topCollector->CollectData(true);
497
					$this->topCollector->ReInitIPC();
498
				}
499
				elseif ($cmds[0] == "filter" || $cmds[0] == "f") {
500
					if (!isset($cmds[1]) || $cmds[1] == "") {
501
						$this->filter = false;
502
						$this->status = "No filter";
503
						$this->statusexpire = $this->currenttime + 5;
504
					}
505
					else {
506
						$this->filter = $cmds[1];
507
						$this->status = false;
508
					}
509
				}
510
				elseif ($cmds[0] == "option" || $cmds[0] == "o") {
511
					if (!isset($cmds[1]) || $cmds[1] == "") {
512
						$this->status = "Option value needs to be specified. See 'help' or 'h' for instructions";
513
						$this->statusexpire = $this->currenttime + 5;
514
					}
515
					elseif ($cmds[1] == "p" || $cmds[1] == "push" || $cmds[1] == "ping") {
516
						$this->showPush = !$this->showPush;
517
					}
518
					elseif ($cmds[1] == "a" || $cmds[1] == "active") {
519
						$this->showOption = self::SHOW_ACTIVE_ONLY;
0 ignored issues
show
Bug Best Practice introduced by
The property showOption does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
520
					}
521
					elseif ($cmds[1] == "u" || $cmds[1] == "unknown") {
522
						$this->showOption = self::SHOW_UNKNOWN_ONLY;
523
					}
524
					elseif ($cmds[1] == "d" || $cmds[1] == "default") {
525
						$this->showOption = self::SHOW_DEFAULT;
526
						$this->showTermSec = self::SHOW_TERM_DEFAULT_TIME;
527
						$this->showPush = true;
528
					}
529
					elseif (is_numeric($cmds[1])) {
530
						$this->showTermSec = $cmds[1];
531
					}
532
					else {
533
						$this->status = sprintf("Option '%s' unknown", $cmds[1]);
534
						$this->statusexpire = $this->currenttime + 5;
535
					}
536
				}
537
				elseif ($cmds[0] == "reset" || $cmds[0] == "r") {
538
					$this->filter = false;
539
					$this->wide = false;
540
					$this->helpexpire = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property helpexpire does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
541
					$this->status = "reset";
542
					$this->statusexpire = $this->currenttime + 2;
543
				}
544
				// enable/disable wide view
545
				elseif ($cmds[0] == "wide" || $cmds[0] == "w") {
546
					$this->wide = !$this->wide;
547
					$this->status = ($this->wide) ? "w i d e  view" : "normal view";
548
					$this->statusexpire = $this->currenttime + 2;
549
				}
550
				elseif ($cmds[0] == "help" || $cmds[0] == "h") {
551
					$this->helpexpire = $this->currenttime + 20;
552
				}
553
				// grep the log file
554
				elseif (($cmds[0] == "log" || $cmds[0] == "l") && isset($cmds[1])) {
555
					if (!file_exists(LOGFILE)) {
556
						$this->status = "Logfile can not be found: " . LOGFILE;
557
					}
558
					else {
559
						system('bash -c "fgrep -a ' . escapeshellarg($cmds[1]) . ' ' . LOGFILE . ' | less +G" > `tty`');
560
						$this->status = "Returning from log, updating data";
561
					}
562
					$this->statusexpire = time() + 5; // it might be much "later" now
563
				}
564
				// tail the log file
565
				elseif (($cmds[0] == "tail" || $cmds[0] == "t")) {
566
					if (!file_exists(LOGFILE)) {
567
						$this->status = "Logfile can not be found: " . LOGFILE;
568
					}
569
					else {
570
						$this->doingTail = true;
0 ignored issues
show
Bug Best Practice introduced by
The property doingTail does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
571
						$this->scrClear();
572
						$this->scrPrintAt(1, 0, $this->scrAsBold("Press CTRL+C to return to grommunio-sync-top\n\n"));
573
						$secondary = "";
574
						if (isset($cmds[1])) {
575
							$secondary = " -n 200 | grep " . escapeshellarg($cmds[1]);
576
						}
577
						system('bash -c "tail -f ' . LOGFILE . $secondary . '" > `tty`');
578
						$this->doingTail = false;
579
						$this->status = "Returning from tail, updating data";
580
					}
581
					$this->statusexpire = time() + 5; // it might be much "later" now
582
				}
583
				// tail the error log file
584
				elseif (($cmds[0] == "error" || $cmds[0] == "e")) {
585
					if (!file_exists(LOGERRORFILE)) {
586
						$this->status = "Error logfile can not be found: " . LOGERRORFILE;
587
					}
588
					else {
589
						$this->doingTail = true;
590
						$this->scrClear();
591
						$this->scrPrintAt(1, 0, $this->scrAsBold("Press CTRL+C to return to grommunio-sync-top\n\n"));
592
						$secondary = "";
593
						if (isset($cmds[1])) {
594
							$secondary = " -n 200 | grep " . escapeshellarg($cmds[1]);
595
						}
596
						system('bash -c "tail -f ' . LOGERRORFILE . $secondary . '" > `tty`');
597
						$this->doingTail = false;
598
						$this->status = "Returning from tail, updating data";
599
					}
600
					$this->statusexpire = time() + 5; // it might be much "later" now
601
				}
602
				elseif ($cmds[0] != "") {
603
					$this->status = sprintf("Command '%s' unknown", $cmds[0]);
604
					$this->statusexpire = $this->currenttime + 8;
605
				}
606
				$this->action = "";
607
			}
608
		}
609
	}
610
611
	/**
612
	 * Signal handler function.
613
	 *
614
	 * @param int $signo signal number
615
	 *
616
	 * @return
617
	 */
618
	public function SignalHandler($signo) {
0 ignored issues
show
The parameter $signo is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

618
	public function SignalHandler(/** @scrutinizer ignore-unused */ $signo) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
619
		// don't terminate if the signal was sent by terminating tail
620
		if (!$this->doingTail) {
621
			$this->topCollector->CollectData(true);
622
			$this->topCollector->ClearLatest(true);
623
			$this->terminate = true;
624
		}
625
	}
626
627
	/**
628
	 * Returns usage instructions.
629
	 *
630
	 * @return string
631
	 */
632
	public function UsageInstructions() {
633
		$help = "Usage:\n\tgrommunio-sync-top.php\n\n" .
634
				"  grommunio-sync-top is a live top-like overview of what grommunio-sync is doing. It does not have specific command line options.\n\n" .
635
				"  When grommunio-sync-top is running you can specify certain actions and options which can be executed (listed below).\n" .
636
				"  This help information can also be shown inside grommunio-sync-top by hitting 'help' or 'h'.\n\n";
637
		$scrhelp = $this->scrHelp();
638
		unset($scrhelp[0]);
639
640
		$help .= implode("\n", $scrhelp);
641
		$help .= "\n\n";
642
643
		return $help;
644
	}
645
646
	/**
647
	 * Prints a 'help' text at the end of the page.
648
	 *
649
	 * @return array with help lines
650
	 */
651
	private function scrHelp() {
652
		$h = [];
653
		$secs = $this->helpexpire - $this->currenttime;
654
		$h[] = "Actions supported by grommunio-sync-top (help page still displayed for " . $secs . "secs)";
655
		$h[] = "  " . $this->scrAsBold("Action") . "\t\t" . $this->scrAsBold("Comment");
656
		$h[] = "  " . $this->scrAsBold("h") . " or " . $this->scrAsBold("help") . "\t\tDisplays this information.";
657
		$h[] = "  " . $this->scrAsBold("q") . ", " . $this->scrAsBold("quit") . " or " . $this->scrAsBold(":q") . "\t\tExits grommunio-sync-top.";
658
		$h[] = "  " . $this->scrAsBold("w") . " or " . $this->scrAsBold("wide") . "\t\tTries not to truncate data. Automatically done if more than 180 columns available.";
659
		$h[] = "  " . $this->scrAsBold("f:VAL") . " or " . $this->scrAsBold("filter:VAL") . "\tOnly display connections which contain VAL. This value is case-insensitive.";
660
		$h[] = "  " . $this->scrAsBold("f:") . " or " . $this->scrAsBold("filter:") . "\t\tWithout a search word: resets the filter.";
661
		$h[] = "  " . $this->scrAsBold("l:STR") . " or " . $this->scrAsBold("log:STR") . "\tIssues 'less +G' on the logfile, after grepping on the optional STR.";
662
		$h[] = "  " . $this->scrAsBold("t:STR") . " or " . $this->scrAsBold("tail:STR") . "\tIssues 'tail -f' on the logfile, grepping for optional STR.";
663
		$h[] = "  " . $this->scrAsBold("e:STR") . " or " . $this->scrAsBold("error:STR") . "\tIssues 'tail -f' on the error logfile, grepping for optional STR.";
664
		$h[] = "  " . $this->scrAsBold("r") . " or " . $this->scrAsBold("reset") . "\t\tResets 'wide' or 'filter'.";
665
		$h[] = "  " . $this->scrAsBold("o:") . " or " . $this->scrAsBold("option:") . "\t\tSets display options. Valid options specified below";
666
		$h[] = "  " . $this->scrAsBold("  p") . " or " . $this->scrAsBold("push") . "\t\tLists/not lists active and open push connections.";
667
		$h[] = "  " . $this->scrAsBold("  a") . " or " . $this->scrAsBold("action") . "\t\tLists only active connections.";
668
		$h[] = "  " . $this->scrAsBold("  u") . " or " . $this->scrAsBold("unknown") . "\tLists only unknown connections.";
669
		$h[] = "  " . $this->scrAsBold("  10") . " or " . $this->scrAsBold("20") . "\t\tLists terminated connections for 10 or 20 seconds. Any other number can be used.";
670
		$h[] = "  " . $this->scrAsBold("  d") . " or " . $this->scrAsBold("default") . "\tUses default options";
671
672
		return $h;
673
	}
674
675
	/**
676
	 * Encapsulates string with different color escape characters.
677
	 *
678
	 * @param string $text
679
	 *
680
	 * @return string same text as bold
681
	 */
682
	private function scrAsBold($text) {
683
		return "\033[01m" . $text . "\033[0m";
684
	}
685
686
	/**
687
	 * Prints one line of precessed data.
688
	 *
689
	 * @param array $l line information
690
	 *
691
	 * @return string
692
	 */
693
	private function getLine($l) {
694
		if ($this->wide === true) {
695
			return sprintf("%s%s%s%s%s%s%s%s", $this->ptStr($l['pid'], 6), $this->ptStr($l['ip'], 16), $this->ptStr($l['user'], 24), $this->ptStr($l['command'], 16), $this->ptStr($this->sec2min($l['time']), 8), $this->ptStr($l['devagent'], 28), $this->ptStr($l['devid'], 33, true), $l['addinfo']);
696
		}
697
698
		return sprintf("%s%s%s%s%s%s%s%s", $this->ptStr($l['pid'], 6), $this->ptStr($l['ip'], 16), $this->ptStr($l['user'], 8), $this->ptStr($l['command'], 8), $this->ptStr($this->sec2min($l['time']), 6), $this->ptStr($l['devagent'], 20), $this->ptStr($l['devid'], 12, true), $l['addinfo']);
699
	}
700
701
	/**
702
	 * Pads and trims string.
703
	 *
704
	 * @param string $str       to be trimmed/padded
705
	 * @param int    $size      characters to be considered
706
	 * @param bool   $cutmiddle (optional) indicates where to long information should
707
	 *                          be trimmed of, false means at the end
708
	 *
709
	 * @return string
710
	 */
711
	private function ptStr($str, $size, $cutmiddle = false) {
712
		if (strlen($str) < $size) {
713
			return str_pad($str, $size);
714
		}
715
		if ($cutmiddle == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
716
			$cut = ($size - 2) / 2;
717
718
			return $this->ptStr(substr($str, 0, $cut) . ".." . substr($str, (-1) * ($cut - 1)), $size);
719
		}
720
721
		return substr($str, 0, $size - 3) . ".. ";
722
	}
723
724
	/**
725
	 * Tries to discover the size of the current terminal.
726
	 *
727
	 * @return array 'width' and 'height' as keys
728
	 */
729
	private function scrGetSize() {
730
		$tty = strtolower(exec('stty -a | fgrep columns'));
731
		if (preg_match_all("/rows.([0-9]+);.columns.([0-9]+);/", $tty, $output) ||
732
			preg_match_all("/([0-9]+).rows;.([0-9]+).columns;/", $tty, $output)) {
733
			return ['width' => $output[2][0], 'height' => $output[1][0]];
734
		}
735
736
		return ['width' => 80, 'height' => 24];
737
	}
738
739
	/**
740
	 * Returns the version of the current grommunio-sync installation.
741
	 *
742
	 * @return string
743
	 */
744
	private function getVersion() {
745
		return GROMMUNIOSYNC_VERSION;
746
	}
747
748
	/**
749
	 * Converts seconds in MM:SS.
750
	 *
751
	 * @param int $s seconds
752
	 *
753
	 * @return string
754
	 */
755
	private function sec2min($s) {
756
		if (!is_int($s)) {
0 ignored issues
show
The condition is_int($s) is always true.
Loading history...
757
			return $s;
758
		}
759
760
		return sprintf("%02.2d:%02.2d", floor($s / 60), $s % 60);
761
	}
762
763
	/**
764
	 * Resets the default colors of the terminal.
765
	 *
766
	 * @return
767
	 */
768
	private function scrDefaultColors() {
769
		echo "\033[0m";
770
	}
771
772
	/**
773
	 * Clears screen of the terminal.
774
	 *
775
	 * @param array $data
776
	 *
777
	 * @return
778
	 */
779
	public function scrClear() {
780
		echo "\033[2J";
781
	}
782
783
	/**
784
	 * Prints a text at a specific screen/terminal coordinates.
785
	 *
786
	 * @param int    $row  row number
787
	 * @param int    $col  column number
788
	 * @param string $text to be printed
789
	 *
790
	 * @return
791
	 */
792
	private function scrPrintAt($row, $col, $text = "") {
793
		echo "\033[" . $row . ";" . $col . "H" . $text;
794
	}
795
}
796