Passed
Pull Request — master (#1)
by
unknown
03:05
created

grommunio-sync-top.php (5 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
// retrieve userinfo
13
$USERINFO = posix_getpwnam("grosync");
14
define('UID', $USERINFO['uid']);
15
define('GID', $USERINFO['gid']);
16
17
require_once 'vendor/autoload.php';
18
if (!defined('GSYNC_CONFIG')) {
19
	define('GSYNC_CONFIG', 'config.php');
20
}
21
22
chown(GSYNC_CONFIG, UID);
23
24
include_once GSYNC_CONFIG;
25
26
// apply ownwership
27
chown(LOGFILE, UID);
28
chgrp(LOGFILE, GID);
29
chown(LOGERRORFILE, UID);
30
chgrp(LOGERRORFILE, GID);
31
32
/*
33
 * MAIN
34
 */
35
	declare(ticks=1);
36
	define('BASE_PATH_CLI', dirname(__FILE__) . "/");
37
	set_include_path(get_include_path() . PATH_SEPARATOR . BASE_PATH_CLI);
38
39
	if (!defined('GSYNC_CONFIG')) {
40
		define('GSYNC_CONFIG', BASE_PATH_CLI . 'config.php');
41
	}
42
43
44
	include_once GSYNC_CONFIG;
45
46
	try {
47
		// change user of running process
48
		posix_seteuid(UID);
49
		posix_setegid(GID);
50
51
		GSync::CheckConfig();
52
		if (!function_exists("pcntl_signal")) {
53
			throw new FatalException("Function pcntl_signal() is not available. Please install package 'php5-pcntl' (or similar) on your system.");
54
		}
55
56
		if (php_sapi_name() != "cli") {
57
			throw new FatalException("This script can only be called from the CLI.");
58
		}
59
60
		$zpt = new GSyncTop();
61
62
		// check if help was requested from CLI
63
		if (in_array('-h', $argv) || in_array('--help', $argv)) {
64
			echo $zpt->UsageInstructions();
65
66
			exit(0);
67
		}
68
69
		if ($zpt->IsAvailable()) {
70
			pcntl_signal(SIGINT, [$zpt, "SignalHandler"]);
71
			$zpt->run();
72
			$zpt->scrClear();
73
			system("stty sane");
74
		}
75
		else {
76
			echo "grommunio-sync interprocess communication (IPC) is not available or TopCollector is disabled.\n";
77
		}
78
	}
79
	catch (GSyncException $zpe) {
80
		fwrite(STDERR, get_class($zpe) . ": " . $zpe->getMessage() . "\n");
81
82
		exit(1);
83
	}
84
85
	echo "terminated\n";
86
87
/*
88
 * grommunio-sync-top
89
 */
90
class GSyncTop {
91
	// show options
92
	public const SHOW_DEFAULT = 0;
93
	public const SHOW_ACTIVE_ONLY = 1;
94
	public const SHOW_UNKNOWN_ONLY = 2;
95
	public const SHOW_TERM_DEFAULT_TIME = 5; // 5 secs
96
97
	private $topCollector;
98
	private $starttime;
99
	private $currenttime;
100
	private $action;
101
	private $filter;
102
	private $status;
103
	private $statusexpire;
104
	private $helpexpire;
105
	private $doingTail;
106
	private $wide;
107
	private $wasEnabled;
108
	private $terminate;
109
	private $scrSize;
110
	private $pingInterval;
111
	private $showPush;
112
	private $showOption;
113
	private $showTermSec;
114
115
	private $linesActive = [];
116
	private $linesOpen = [];
117
	private $linesUnknown = [];
118
	private $linesTerm = [];
119
	private $pushConn = 0;
120
	private $activeConn = [];
121
	private $activeHosts = [];
122
	private $activeUsers = [];
123
	private $activeDevices = [];
124
125
	/**
126
	 * Constructor.
127
	 */
128
	public function __construct() {
129
		$this->starttime = time();
130
		$this->currenttime = time();
131
		$this->action = "";
132
		$this->filter = false;
133
		$this->status = false;
134
		$this->statusexpire = 0;
135
		$this->helpexpire = 0;
136
		$this->doingTail = false;
137
		$this->wide = false;
138
		$this->terminate = false;
139
		$this->showPush = true;
140
		$this->showOption = self::SHOW_DEFAULT;
141
		$this->showTermSec = self::SHOW_TERM_DEFAULT_TIME;
142
		$this->scrSize = ['width' => 80, 'height' => 24];
143
		$this->pingInterval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 12;
144
145
		// get a TopCollector
146
		$this->topCollector = new TopCollector();
147
	}
148
149
	/**
150
	 * Requests data from the running grommunio-sync processes.
151
	 *
152
	 * @return
153
	 */
154
	private function initialize() {
155
		// request feedback from active processes
156
		$this->wasEnabled = $this->topCollector->CollectData();
157
158
		// remove obsolete data
159
		$this->topCollector->ClearLatest(true);
160
161
		// start with default colours
162
		$this->scrDefaultColors();
163
	}
164
165
	/**
166
	 * Main loop of grommunio-sync-top
167
	 * Runs until termination is requested.
168
	 *
169
	 * @return
170
	 */
171
	public function run() {
172
		$this->initialize();
173
174
		do {
175
			$this->currenttime = time();
176
177
			// see if shared memory is active
178
			if (!$this->IsAvailable()) {
179
				$this->terminate = true;
180
			}
181
182
			// active processes should continue sending data
183
			$this->topCollector->CollectData();
184
185
			// get and process data from processes
186
			$this->topCollector->ClearLatest();
187
			$topdata = $this->topCollector->ReadLatest();
188
			$this->processData($topdata);
189
190
			// clear screen
191
			$this->scrClear();
192
193
			// check if screen size changed
194
			$s = $this->scrGetSize();
195
			if ($this->scrSize['width'] != $s['width']) {
196
				if ($s['width'] > 180) {
197
					$this->wide = true;
198
				}
199
				else {
200
					$this->wide = false;
201
				}
202
			}
203
			$this->scrSize = $s;
204
205
			// print overview
206
			$this->scrOverview();
207
208
			// wait for user input
209
			$this->readLineProcess();
210
		}
211
		while ($this->terminate !== true);
212
	}
213
214
	/**
215
	 * Indicates if TopCollector is available collecting data.
216
	 *
217
	 * @return bool
218
	 */
219
	public function IsAvailable() {
220
		if (defined('TOPCOLLECTOR_DISABLED') && constant('TOPCOLLECTOR_DISABLED') === true) {
221
			return false;
222
		}
223
224
		return $this->topCollector->IsActive();
225
	}
226
227
	/**
228
	 * Processes data written by the running processes.
229
	 *
230
	 * @param array $data
231
	 *
232
	 * @return
233
	 */
234
	private function processData($data) {
235
		$this->linesActive = [];
236
		$this->linesOpen = [];
237
		$this->linesUnknown = [];
238
		$this->linesTerm = [];
239
		$this->pushConn = 0;
240
		$this->activeConn = [];
241
		$this->activeHosts = [];
242
		$this->activeUsers = [];
243
		$this->activeDevices = [];
244
245
		if (!is_array($data)) {
0 ignored issues
show
The condition is_array($data) is always true.
Loading history...
246
			return;
247
		}
248
249
		foreach ($data as $devid => $users) {
250
			foreach ($users as $user => $pids) {
251
				foreach ($pids as $pid => $line) {
252
					if (!is_array($line)) {
253
						continue;
254
					}
255
256
					$line['command'] = Utils::GetCommandFromCode($line['command']);
257
258
					if ($line["ended"] == 0) {
259
						$this->activeDevices[$devid] = 1;
260
						$this->activeUsers[$user] = 1;
261
						$this->activeConn[$pid] = 1;
262
						$this->activeHosts[$line['ip']] = 1;
263
264
						$line["time"] = $this->currenttime - $line['start'];
265
						if ($line['push'] === true) {
266
							++$this->pushConn;
267
						}
268
269
						// ignore push connections
270
						if ($line['push'] === true && !$this->showPush) {
271
							continue;
272
						}
273
274
						if ($this->filter !== false) {
275
							$f = $this->filter;
276
							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

276
							if (!($line["pid"] == $f || $line["ip"] == $f || strtolower($line['command']) == strtolower(/** @scrutinizer ignore-type */ $f) || preg_match("/.*?{$f}.*?/i", $line['user']) ||
Loading history...
277
								preg_match("/.*?{$f}.*?/i", $line['devagent']) || preg_match("/.*?{$f}.*?/i", $line['devid']) || preg_match("/.*?{$f}.*?/i", $line['addinfo']))) {
278
								continue;
279
							}
280
						}
281
282
						$lastUpdate = $this->currenttime - $line["update"];
283
						if ($this->currenttime - $line["update"] < 2) {
284
							$this->linesActive[$line["update"] . $line["pid"]] = $line;
285
						}
286
						elseif (($line['push'] === true && $lastUpdate > ($this->pingInterval + 2)) || ($line['push'] !== true && $lastUpdate > 4)) {
287
							$this->linesUnknown[$line["update"] . $line["pid"]] = $line;
288
						}
289
						else {
290
							$this->linesOpen[$line["update"] . $line["pid"]] = $line;
291
						}
292
					}
293
					else {
294
						// do not show terminated + expired connections
295
						if ($this->currenttime > $line['ended'] + $this->showTermSec) {
296
							continue;
297
						}
298
299
						if ($this->filter !== false) {
300
							$f = $this->filter;
301
							if (
302
									!(
303
										$line['pid'] == $f ||
304
										$line['ip'] == $f ||
305
										strtolower($line['command']) == strtolower($f) ||
306
										preg_match("/.*?{$f}.*?/i", $line['user']) ||
307
										preg_match("/.*?{$f}.*?/i", $line['devagent']) ||
308
										preg_match("/.*?{$f}.*?/i", $line['devid']) ||
309
										preg_match("/.*?{$f}.*?/i", $line['addinfo'])
310
									)) {
311
								continue;
312
							}
313
						}
314
315
						$line['time'] = $line['ended'] - $line['start'];
316
						$this->linesTerm[$line['update'] . $line['pid']] = $line;
317
					}
318
				}
319
			}
320
		}
321
322
		// sort by execution time
323
		krsort($this->linesActive);
324
		krsort($this->linesOpen);
325
		krsort($this->linesUnknown);
326
		krsort($this->linesTerm);
327
	}
328
329
	/**
330
	 * Prints data to the terminal.
331
	 *
332
	 * @return
333
	 */
334
	private function scrOverview() {
335
		$linesAvail = $this->scrSize['height'] - 8;
336
		$lc = 1;
337
		$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

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

640
	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...
641
		// don't terminate if the signal was sent by terminating tail
642
		if (!$this->doingTail) {
643
			$this->topCollector->CollectData(true);
644
			$this->topCollector->ClearLatest(true);
645
			$this->terminate = true;
646
		}
647
	}
648
649
	/**
650
	 * Returns usage instructions.
651
	 *
652
	 * @return string
653
	 */
654
	public function UsageInstructions() {
655
		$help = "Usage:\n\tgrommunio-sync-top.php\n\n" .
656
				"  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" .
657
				"  When grommunio-sync-top is running you can specify certain actions and options which can be executed (listed below).\n" .
658
				"  This help information can also be shown inside grommunio-sync-top by hitting 'help' or 'h'.\n\n";
659
		$scrhelp = $this->scrHelp();
660
		unset($scrhelp[0]);
661
662
		$help .= implode("\n", $scrhelp);
663
		$help .= "\n\n";
664
665
		return $help;
666
	}
667
668
	/**
669
	 * Prints a 'help' text at the end of the page.
670
	 *
671
	 * @return array with help lines
672
	 */
673
	private function scrHelp() {
674
		$h = [];
675
		$secs = $this->helpexpire - $this->currenttime;
676
		$h[] = "Actions supported by grommunio-sync-top (help page still displayed for " . $secs . "secs)";
677
		$h[] = "  " . $this->scrAsBold("Action") . "\t\t" . $this->scrAsBold("Comment");
678
		$h[] = "  " . $this->scrAsBold("h") . " or " . $this->scrAsBold("help") . "\t\tDisplays this information.";
679
		$h[] = "  " . $this->scrAsBold("q") . ", " . $this->scrAsBold("quit") . " or " . $this->scrAsBold(":q") . "\t\tExits grommunio-sync-top.";
680
		$h[] = "  " . $this->scrAsBold("w") . " or " . $this->scrAsBold("wide") . "\t\tTries not to truncate data. Automatically done if more than 180 columns available.";
681
		$h[] = "  " . $this->scrAsBold("f:VAL") . " or " . $this->scrAsBold("filter:VAL") . "\tOnly display connections which contain VAL. This value is case-insensitive.";
682
		$h[] = "  " . $this->scrAsBold("f:") . " or " . $this->scrAsBold("filter:") . "\t\tWithout a search word: resets the filter.";
683
		$h[] = "  " . $this->scrAsBold("l:STR") . " or " . $this->scrAsBold("log:STR") . "\tIssues 'less +G' on the logfile, after grepping on the optional STR.";
684
		$h[] = "  " . $this->scrAsBold("t:STR") . " or " . $this->scrAsBold("tail:STR") . "\tIssues 'tail -f' on the logfile, grepping for optional STR.";
685
		$h[] = "  " . $this->scrAsBold("e:STR") . " or " . $this->scrAsBold("error:STR") . "\tIssues 'tail -f' on the error logfile, grepping for optional STR.";
686
		$h[] = "  " . $this->scrAsBold("r") . " or " . $this->scrAsBold("reset") . "\t\tResets 'wide' or 'filter'.";
687
		$h[] = "  " . $this->scrAsBold("o:") . " or " . $this->scrAsBold("option:") . "\t\tSets display options. Valid options specified below";
688
		$h[] = "  " . $this->scrAsBold("  p") . " or " . $this->scrAsBold("push") . "\t\tLists/not lists active and open push connections.";
689
		$h[] = "  " . $this->scrAsBold("  a") . " or " . $this->scrAsBold("action") . "\t\tLists only active connections.";
690
		$h[] = "  " . $this->scrAsBold("  u") . " or " . $this->scrAsBold("unknown") . "\tLists only unknown connections.";
691
		$h[] = "  " . $this->scrAsBold("  10") . " or " . $this->scrAsBold("20") . "\t\tLists terminated connections for 10 or 20 seconds. Any other number can be used.";
692
		$h[] = "  " . $this->scrAsBold("  d") . " or " . $this->scrAsBold("default") . "\tUses default options";
693
694
		return $h;
695
	}
696
697
	/**
698
	 * Encapsulates string with different color escape characters.
699
	 *
700
	 * @param string $text
701
	 *
702
	 * @return string same text as bold
703
	 */
704
	private function scrAsBold($text) {
705
		return "\033[01m" . $text . "\033[0m";
706
	}
707
708
	/**
709
	 * Prints one line of precessed data.
710
	 *
711
	 * @param array $l line information
712
	 *
713
	 * @return string
714
	 */
715
	private function getLine($l) {
716
		if ($this->wide === true) {
717
			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']);
718
		}
719
720
		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']);
721
	}
722
723
	/**
724
	 * Pads and trims string.
725
	 *
726
	 * @param string $str       to be trimmed/padded
727
	 * @param int    $size      characters to be considered
728
	 * @param bool   $cutmiddle (optional) indicates where to long information should
729
	 *                          be trimmed of, false means at the end
730
	 *
731
	 * @return string
732
	 */
733
	private function ptStr($str, $size, $cutmiddle = false) {
734
		if (strlen($str) < $size) {
735
			return str_pad($str, $size);
736
		}
737
		if ($cutmiddle === true) {
738
			$cut = ($size - 2) / 2;
739
740
			return $this->ptStr(substr($str, 0, $cut) . ".." . substr($str, (-1) * ($cut - 1)), $size);
741
		}
742
743
		return substr($str, 0, $size - 3) . ".. ";
744
	}
745
746
	/**
747
	 * Tries to discover the size of the current terminal.
748
	 *
749
	 * @return array 'width' and 'height' as keys
750
	 */
751
	private function scrGetSize() {
752
		$tty = strtolower(exec('stty -a | fgrep columns'));
753
		if (preg_match_all("/rows.([0-9]+);.columns.([0-9]+);/", $tty, $output) ||
754
			preg_match_all("/([0-9]+).rows;.([0-9]+).columns;/", $tty, $output)) {
755
			return ['width' => $output[2][0], 'height' => $output[1][0]];
756
		}
757
758
		return ['width' => 80, 'height' => 24];
759
	}
760
761
	/**
762
	 * Returns the version of the current grommunio-sync installation.
763
	 *
764
	 * @return string
765
	 */
766
	private function getVersion() {
767
		return GROMMUNIOSYNC_VERSION;
768
	}
769
770
	/**
771
	 * Converts seconds in MM:SS.
772
	 *
773
	 * @param int $s seconds
774
	 *
775
	 * @return string
776
	 */
777
	private function sec2min($s) {
778
		if (!is_int($s)) {
0 ignored issues
show
The condition is_int($s) is always true.
Loading history...
779
			return $s;
780
		}
781
782
		return sprintf("%02.2d:%02.2d", floor($s / 60), $s % 60);
783
	}
784
785
	/**
786
	 * Resets the default colors of the terminal.
787
	 *
788
	 * @return
789
	 */
790
	private function scrDefaultColors() {
791
		echo "\033[0m";
792
	}
793
794
	/**
795
	 * Clears screen of the terminal.
796
	 *
797
	 * @param array $data
798
	 *
799
	 * @return
800
	 */
801
	public function scrClear() {
802
		echo "\033[2J";
803
	}
804
805
	/**
806
	 * Prints a text at a specific screen/terminal coordinates.
807
	 *
808
	 * @param int    $row  row number
809
	 * @param int    $col  column number
810
	 * @param string $text to be printed
811
	 *
812
	 * @return
813
	 */
814
	private function scrPrintAt($row, $col, $text = "") {
815
		echo "\033[" . $row . ";" . $col . "H" . $text;
816
	}
817
}
818