-
Notifications
You must be signed in to change notification settings - Fork 0
/
Base.php
756 lines (699 loc) · 22.3 KB
/
Base.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
<?php
namespace Aza\Components\CliBase;
use Aza\Components\CliBase\Exceptions\Exception;
/**
* Basic functionality and helper methods
* for CLI and Daemon applications
*
* @project Anizoptera CMF
* @package system.cli.base
* @author Amal Samally <amal.samally at gmail.com>
* @license MIT
*/
abstract class Base
{
// region Exit codes
// @link http://www.freebsd.org/cgi/man.cgi?query=sysexits&sektion=3
// @link http://www.hiteksoftware.com/knowledge/articles/049.htm
// @link http://tldp.org/LDP/abs/html/exitcodes.html
/**
* The successful exit
*/
const EX_OK = 0;
/**
* Error occured. Catchall for general errors.
* Miscellaneous errors, such as "divide by zero" and other impermissible operations.
*/
const EX_ERROR = 1;
/**
* Action locked. Misuse of shell builtins (according to Bash documentation).
* Missing keyword or command.
*/
const EX_LOCKED = 2;
/**
* The command was used incorrectly, e.g., with the
* wrong number of arguments, a bad flag, a bad syntax
* in a parameter, or whatever.
*/
const EX_USAGE = 64;
/**
* The input data was incorrect in some way.
* This should only be used for user's data and not system
* files.
*/
const EX_DATAERR = 65;
/**
* An input file (not a system file) did not exist or
* was not readable. This could also include errors
* like ``No message'' to a mailer (if it cared to
* catch it).
*/
const EX_NOINPUT = 66;
/**
* The user specified did not exist.
* This might be used for mail addresses or remote logins.
*/
const EX_NOUSER = 67;
/**
* The host specified did not exist. This is used in
* mail addresses or network requests.
*/
const EX_NOHOST = 68;
/**
* A service is unavailable. This can occur if a sup-
* port program or file does not exist. This can also
* be used as a catchall message when something you
* wanted to do does not work, but you do not know
* why.
*/
const EX_UNAVAILABLE = 69;
/**
* An internal software error has been detected. This
* should be limited to non-operating system related
* errors as possible.
*/
const EX_SOFTWARE = 70;
/**
* An operating system error has been detected. This
* is intended to be used for such things as ``cannot
* fork'', ``cannot create pipe'', or the like. It
* includes things like getuid returning a user that
* does not exist in the passwd file.
*/
const EX_OSERR = 71;
/**
* Some system file (e.g., /etc/passwd, /var/run/utmp,
* etc.) does not exist, cannot be opened, or has some
* sort of error (e.g., syntax error).
*/
const EX_OSFILE = 72;
/**
* A (user specified) output file cannot be created.
*/
const EX_CANTCREAT = 73;
/**
* An error occurred while doing I/O on some file.
*/
const EX_IOERR = 74;
/**
* Temporary failure, indicating something that is not
* really an error. In sendmail, this means that a
* mailer (e.g.) could not create a connection, and
* the request should be reattempted later.
*/
const EX_TEMPFAIL = 75;
/**
* The remote system returned something that was 'not
* possible' during a protocol exchange.
*/
const EX_PROTOCOL = 76;
/**
* You did not have sufficient permission to perform
* the operation. This is not intended for file sys-
* tem problems, which should use EX_NOINPUT or
* EX_CANTCREAT, but rather for higher level permis-
* sions.
*/
const EX_NOPERM = 77;
/**
* Something was found in an unconfigured or miscon-
* figured state.
*/
const EX_CONFIG = 78;
/**
* Process terminated (interrupted)
*/
const EX_TERM = 143;
/**
* Incorrect environment (data or command not found etc..)
*/
const EX_ENVIRONMENT = 127;
//endregion
/**
* Whether forks supported
*
* @var bool
*/
public static $hasForkSupport;
/**
* Whether current process has parent process (so this is child)
*
* @var bool
*/
public static $hasParent = false;
/**
* POSIX (Unix) signals reference
*
* SIG_DFL
* specifies the default action for the particular signal.
*
* SIG_IGN
* specifies that the signal should be ignored.
* Your program generally should not ignore signals that represent serious events
* or that are normally used to request termination. You cannot ignore the SIGKILL
* or SIGSTOP signals at all. You can ignore program error signals like SIGSEGV,
* but ignoring the error won't enable the program to continue executing meaningfully.
* Ignoring user requests such as SIGINT, SIGQUIT, and SIGTSTP is unfriendly.
*
* When you do not wish signals to be delivered during a certain part of the program,
* the thing to do is to block them, not ignore them.
*/
public static $signals = array(
/*
* The SIGHUP ("hang-up") signal is used to report that the user's
* terminal is disconnected, perhaps because a network or telephone
* connection was broken. For more information about this.
* This signal is also used to report the termination of the controlling process
* on a terminal to jobs associated with that session; this termination effectively
* disconnects all processes in the session from the controlling terminal.
*/
SIGHUP => 'SIGHUP',
/*
* The SIGINT ("program interrupt") signal is sent when the user
* types the INTR character (normally ^C).
*/
SIGINT => 'SIGINT',
/*
* The SIGQUIT signal is similar to SIGINT, except that it's
* controlled by a different key--the QUIT character, usually
* C-\---and produces a core dump when it terminates the process,
* just like a program error signal. You can think of this as a
* program error condition "detected" by the user.
*/
SIGQUIT => 'SIGQUIT',
/*
* The name of this signal is derived from "illegal instruction";
* it usually means your program is trying to execute garbage or
* a privileged instruction. Since the C compiler generates only
* valid instructions, SIGILL typically indicates that the executable
* file is corrupted, or that you are trying to execute data. Some
* common ways of getting into the latter situation are by passing an
* invalid object where a pointer to a function was expected, or by
* writing past the end of an automatic array (or similar problems
* with pointers to automatic variables) and corrupting other data on
* the stack such as the return address of a stack frame.
* SIGILL can also be generated when the stack overflows, or when the
* system has trouble running the handler for a signal.
*/
SIGILL => 'SIGILL',
/*
* Generated by the machine's breakpoint instruction, and possibly other trap
* instructions. This signal is used by debuggers. Your program will probably
* only see SIGTRAP if it is somehow executing bad instructions.
*/
SIGTRAP => 'SIGTRAP',
/*
* This signal indicates an error detected by the program itself
*/
SIGABRT => 'SIGABRT',
/*
* Emulator trap; this results from certain unimplemented instructions
* which might be emulated in software, or the operating system's failure
* to properly emulate them.
*/
7 => 'SIGEMT',
/*
* The SIGFPE signal reports a fatal arithmetic error.
* Although the name is derived from "floating-point exception",
* this signal actually covers all arithmetic errors, including division
* by zero and overflow. If a program stores integer data in a location
* which is then used in a floating-point operation, this often causes an
* "invalid operation" exception, because the processor cannot recognize the
* data as a floating-point number.
*/
SIGFPE => 'SIGFPE',
/*
* The SIGKILL signal is used to cause immediate program termination.
* It cannot be handled or ignored, and is therefore always fatal.
* It is also not possible to block this signal.
* This signal is usually generated only by explicit request.
* Since it cannot be handled, you should generate it only as a last resort,
* after first trying a less drastic method such as SIGINT or SIGTERM.
* If a process does not respond to any other termination signals,
* sending it a SIGKILL signal will almost always cause it to go away.
*/
SIGKILL => 'SIGKILL',
/*
* This signal is generated when an invalid pointer is dereferenced.
* Like SIGSEGV, this signal is typically the result of dereferencing
* an uninitialized pointer. The difference between the two is that SIGSEGV
* indicates an invalid access to valid memory, while SIGBUS indicates an
* access to an invalid address. In particular, SIGBUS signals often result
* from dereferencing a misaligned pointer, such as referring to a four-word integer
* at an address not divisible by four. (Each kind of computer has its own requirements
* for address alignment.)
* The name of this signal is an abbreviation for "bus error".
*/
SIGBUS => 'SIGBUS',
/*
* This signal is generated when a program tries to read or write outside
* the memory that is allocated for it, or to write memory that can only
* be read. (Actually, the signals only occur when the program goes far
* enough outside to be detected by the system's memory protection mechanism.)
* The name is an abbreviation for "segmentation violation".
* Common ways of getting a SIGSEGV condition include dereferencing a null
* or uninitialized pointer, or when you use a pointer to step through an array,
* but fail to check for the end of the array. It varies among systems whether
* dereferencing a null pointer generates SIGSEGV or SIGBUS.
*/
SIGSEGV => 'SIGSEGV',
/*
* Bad system call; that is to say, the instruction to trap to the operating system
* was executed, but the code number for the system call to perform was invalid.
*/
SIGSYS => 'SIGSYS',
/*
* Broken pipe. If you use pipes or FIFOs, you have to design your application
* so that one process opens the pipe for reading before another starts writing.
* If the reading process never starts, or terminates unexpectedly, writing to
* the pipe or FIFO raises a SIGPIPE signal. If SIGPIPE is blocked, handled or
* ignored, the offending call fails with EPIPE instead.
* Another cause of SIGPIPE is when you try to output to a socket that isn't
* connected.
*/
SIGPIPE => 'SIGPIPE',
/*
* This signal typically indicates expiration of a timer
* that measures real or clock time.
*/
SIGALRM => 'SIGALRM',
/*
* The SIGTERM signal is a generic signal used to cause program termination.
* Unlike SIGKILL, this signal can be blocked, handled, and ignored. It is the
* normal way to politely ask a program to terminate.
* The shell command kill generates SIGTERM by default.
*/
SIGTERM => 'SIGTERM',
/*
* This signal is sent when "urgent" or out-of-band data arrives on a socket
*/
SIGURG => 'SIGURG',
/*
* The SIGSTOP signal stops the process. It cannot be handled, ignored, or blocked.
*/
SIGSTOP => 'SIGSTOP',
/*
* The SIGTSTP signal is an interactive stop signal. Unlike SIGSTOP, this signal
* can be handled and ignored.
* Your program should handle this signal if you have a special need to leave files
* or system tables in a secure state when a process is stopped. For example, programs
* that turn off echoing should handle SIGTSTP so they can turn echoing back on before stopping.
* This signal is generated when the user types the SUSP character (normally ^Z).
*/
SIGTSTP => 'SIGTSTP',
/*
* You can send a SIGCONT signal to a process to make it continue.
* This signal is special--it always makes the process continue if it is stopped,
* before the signal is delivered. The default behavior is to do nothing else.
* You cannot block this signal. You can set a handler, but SIGCONT always makes
* the process continue regardless.
* Most programs have no reason to handle SIGCONT; they simply resume execution
* without realizing they were ever stopped. You can use a handler for SIGCONT
* to make a program do something special when it is stopped and continued--for example,
* to reprint a prompt when it is suspended while waiting for input.
*/
SIGCONT => 'SIGCONT',
/*
* This signal is sent to a parent process whenever one of its child processes
* terminates or stops.
* The default action for this signal is to ignore it. If you establish a handler
* for this signal while there are child processes that have terminated but not
* reported their status via wait or waitpid, whether your new handler applies
* to those processes or not depends on the particular operating system.
*/
SIGCHLD => 'SIGCHLD',
/*
* A process cannot read from the user's terminal while it is running as a
* background job. When any process in a background job tries to read from
* the terminal, all of the processes in the job are sent a SIGTTIN signal.
* The default action for this signal is to stop the process.
*/
SIGTTIN => 'SIGTTIN',
/*
* This is similar to SIGTTIN, but is generated when a process in a background
* job attempts to write to the terminal or set its modes. Again, the default
* action is to stop the process. SIGTTOU is only generated for an attempt
* to write to the terminal if the TOSTOP output mode is set;
*/
SIGTTOU => 'SIGTTOU',
/*
* This signal is sent when a file descriptor is ready to perform input or output.
* On most operating systems, terminals and sockets are the only kinds of files
* that can generate SIGIO; other kinds, including ordinary files, never generate
* SIGIO even if you ask them to.
* In the GNU system SIGIO will always be generated properly if you successfully
* set asynchronous mode with fcntl.
*/
SIGIO => 'SIGIO',
/*
* CPU time limit exceeded. This signal is generated when the process exceeds
* its soft resource limit on CPU time.
*/
SIGXCPU => 'SIGXCPU',
/*
* File size limit exceeded. This signal is generated when the process attempts
* to extend a file so it exceeds the process's soft resource limit on file size.
*/
SIGXFSZ => 'SIGXFSZ',
/*
* This signal typically indicates expiration of a timer that measures
* CPU time used by the current process. The name is an abbreviation
* for "virtual time alarm".
*/
SIGVTALRM => 'SIGVTALRM',
/*
* This signal typically indicates expiration of a timer that measures
* both CPU time used by the current process, and CPU time expended on
* behalf of the process by the system. Such a timer is used to
* implement code profiling facilities, hence the name of this signal.
*/
SIGPROF => 'SIGPROF',
/*
* Information request. In 4.4 BSD and the GNU system, this signal is sent to all
* the processes in the foreground process group of the controlling terminal when
* the user types the STATUS character in canonical mode.;
* If the process is the leader of the process group, the default action is to
* print some status information about the system and what the process is doing.
* Otherwise the default is to do nothing.
*/
28 => 'SIGINFO',
/*
* Window size change. This is generated on some systems (including GNU)
* when the terminal driver's record of the number of rows and columns on
* the screen is changed. The default action is to ignore it.
* If a program does full-screen display, it should handle SIGWINCH.
* When the signal arrives, it should fetch the new screen size and
* reformat its display accordingly.
*/
SIGWINCH => 'SIGWINCH',
/*
* The SIGUSR1 and SIGUSR2 signals are set aside for you to use any way you want.
* They're useful for simple interprocess communication, if you write a signal
* handler for them in the program that receives the signal.
* The default action is to terminate the process.
*/
SIGUSR1 => 'SIGUSR1',
SIGUSR2 => 'SIGUSR2',
);
/**
* Returns name of the signal
*
* @param int $signo <p>
* Signal code. (See SIG* constants)
* </p>
* @param bool &$found [optional] <p>
* If signal name found. FALSE for unknown signals.
* </p>
*
* @return string
*/
public static function getSignalName($signo, &$found = null)
{
return ($found = isset(self::$signals[$signo]))
? self::$signals[$signo]
: 'UNKNOWN';
}
/**
* Initializes signal handler
*
* @throws Exception
*
* @see pcntl_signal
*
* @param callback $handler
* @param int $signo
* @param bool $ignore
* @param bool $default
*/
public static function signalHandle($handler, $signo = null,
$ignore = false, $default = false)
{
$handler = $ignore ? SIG_IGN : ($default ? SIG_DFL : $handler);
if ($signo !== null) {
if (isset(self::$signals[$signo])
&& SIGKILL !== $signo
&& SIGSTOP !== $signo
) {
if (!pcntl_signal($signo, $handler)) {
$name = self::$signals[$signo];
throw new Exception(
"Can't initialize signal handler for $name ($signo)"
);
}
}
} else {
foreach (self::$signals as $signo => $name) {
if ($signo === SIGKILL || $signo === SIGSTOP) {
continue;
}
if (!pcntl_signal($signo, $handler)) {
throw new Exception(
"Can't initialize signal handler for $name ($signo)"
);
}
}
}
}
/**
* Waits for the signal, with a timeout
*
* @see pcntl_sigtimedwait
* @see pcntl_sigprocmask
*
* @param int $signo <p>
* Signal's number
* </p>
* @param int $seconds [optional] <p>
* Timeout in seconds.
* </p>
* @param int $nanoseconds [optional] <p>
* Timeout in nanoseconds.
* </p>
* @param array $siginfo [optional] <p>
* The siginfo is set to an array containing
* informations about the signal. See
* {@link pcntl_sigwaitinfo}.
* </p>
*
* @return bool
*/
public static function signalWait($signo, $seconds = 1,
$nanoseconds = null, &$siginfo = null)
{
if (isset(self::$signals[$signo])) {
pcntl_sigprocmask(SIG_BLOCK, array($signo));
$res = pcntl_sigtimedwait(
array($signo), $siginfo,
$seconds, $nanoseconds
);
pcntl_sigprocmask(SIG_UNBLOCK, array($signo));
if ($res > 0) {
return true;
}
}
return false;
}
/**
* Forks the currently running process
*
* @param \Closure $parentCb [optional] <p>
* Callback for the parent process. Called after fork.
* </p>
*
* @return int the PID of the child process is returned
* in the parent's thread of execution, and a 0 is
* returned in the child's thread of execution.
*
* @throws Exception if could not fork
*/
public static function fork($parentCb = null)
{
/*
* pcntl_fork triggers E_WARNING errors.
* For Ubuntu error codes indicate the following:
*
* Error 11: Resource temporarily unavailable
* Error 12: Cannot allocate memory
*/
if (-1 === $pid = pcntl_fork()) {
$parentCb && $parentCb($pid);
throw new Exception('Could not fork');
}
// Child
else if (0 === $pid) {
self::$hasParent = true;
}
// Parent
else {
$parentCb && $parentCb($pid);
}
return $pid;
}
/**
* Detaches process from the controlling terminal
*
* @throws Exception if could not detach
*/
public static function detach()
{
// Fork and exit in parent process
if (self::fork()) {
exit;
}
// Make the current process a session leader
if (-1 === posix_setsid()) {
throw new Exception(
'Could not detach from terminal'
);
}
self::$hasParent = false;
}
/**
* Returns current tty width in columns
*
* @param resource $stream
*
* @return int
*/
public static function getTtyColumns($stream = null)
{
if (IS_WIN || !self::getIsTty($stream ?: STDOUT)) {
return 95;
}
$cols = (int)shell_exec(
'stty -a 2>/dev/null | grep -oPm 1 "(?<=columns )(\d+)(?=;)" 2>/dev/null'
);
return max(40, $cols);
}
/**
* Determine if a file descriptor is an interactive terminal
*
* @see posix_isatty
*
* @param resource|int $stream File descriptor resource
*
* @return bool
*/
public static function getIsTty($stream)
{
return function_exists('posix_isatty')
&& @posix_isatty($stream);
}
/**
* Returns command by PID
*
* @param int $pid Process ID
*
* @return string
*/
public static function getCommandByPid($pid)
{
if ($pid < 1 || IS_WIN) {
return '';
}
exec("ps -p {$pid} -o%c 2>/dev/null", $data);
return $data && count($data) === 2
? array_pop($data)
: '';
}
/**
* Kills process with it's childs recursively
*
* @throws Exception
*
* @uses posix
*
* @param int $pid Process ID
* @param int $signal Kill signal (SIGTERM, SIGKILL ...)
*/
public static function killProcessTree($pid, $signal = SIGTERM)
{
if ($pid < 1 || IS_WIN) {
return;
}
// Kill childs
exec(
"ps -ef 2>/dev/null | awk '\$3 == '$pid' { print \$2 }' 2>/dev/null",
$output, $ret
);
if ($ret) {
throw new Exception(
'You need ps, grep, and awk'
);
}
foreach ($output as $t) {
if ($t != $pid) {
self::killProcessTree($t, $signal);
}
}
// Kill self
posix_kill($pid, $signal);
}
/**
* Checks if process is running.
* Supports processes of another user
* if you have no root rights.
*
* @uses posix
*
* @param int $pid Process ID
*
* @return bool
*/
public static function getProcessIsAlive($pid)
{
// EPERM === 1
return posix_kill($pid, 0)
|| posix_get_last_error() === 1;
}
/**
* Sets current process title (if possible)
*
* @uses proctitle
*
* @see cli_set_process_title
* @see setproctitle
*
* @link https://wiki.php.net/rfc/cli_process_title#specification
* @link http://php.net/setproctitle
*
* @param string $title Process title
*/
public static function setProcessTitle($title)
{
// PHP 5.5 cli_set_process_title
if (function_exists('cli_set_process_title')) {
cli_set_process_title($title);
}
// PECL Proctitle (not recommended, considered buggy)
else if (function_exists('setproctitle')) {
setproctitle($title);
}
}
/**
* Returns prepared for logging time string
*
* @return string
*/
public static function getTimeForLog()
{
list($usec, $sec) = explode(' ', microtime());
return date(sprintf(
'[Y.m.d H:i:s.%0-6.6s O]',
(int)($usec * 1.0E+6)
), $sec);
}
}
/**
* Windows flag
*
* It only needed if Anizoptera CMF is not bootstrapped
*
* We can use PHP_OS, but practically,
* testing for DIRECTORY_SEPARATOR is more straightforward.
*/
defined('IS_WIN') || define('IS_WIN', DIRECTORY_SEPARATOR !== '/');
// CLI environment, posix and pcntl extensions
Base::$hasForkSupport = PHP_SAPI === 'cli'
&& function_exists('pcntl_fork')
&& function_exists('posix_getpid');