1: | <?php declare(strict_types=1); |
2: | |
3: | namespace Salient\Utility; |
4: | |
5: | use Salient\Core\Facade\Err; |
6: | use LogicException; |
7: | use RuntimeException; |
8: | |
9: | |
10: | |
11: | |
12: | |
13: | |
14: | final class Sys extends AbstractUtility |
15: | { |
16: | |
17: | |
18: | |
19: | public static function getMemoryLimit(): int |
20: | { |
21: | return Get::bytes((string) ini_get('memory_limit')); |
22: | } |
23: | |
24: | |
25: | |
26: | |
27: | |
28: | public static function getMemoryUsagePercent(): float |
29: | { |
30: | $limit = self::getMemoryLimit(); |
31: | |
32: | return $limit <= 0 |
33: | ? 0 |
34: | : (memory_get_usage() * 100 / $limit); |
35: | } |
36: | |
37: | |
38: | |
39: | |
40: | |
41: | |
42: | public static function getCpuUsage(): array |
43: | { |
44: | $usage = getrusage(); |
45: | |
46: | if ($usage === false) { |
47: | |
48: | return [0, 0]; |
49: | |
50: | } |
51: | |
52: | $user_s = $usage['ru_utime.tv_sec'] ?? 0; |
53: | $user_us = $usage['ru_utime.tv_usec'] ?? 0; |
54: | $sys_s = $usage['ru_stime.tv_sec'] ?? 0; |
55: | $sys_us = $usage['ru_stime.tv_usec'] ?? 0; |
56: | |
57: | return [ |
58: | $user_s * 1000000 + $user_us, |
59: | $sys_s * 1000000 + $sys_us, |
60: | ]; |
61: | } |
62: | |
63: | |
64: | |
65: | |
66: | |
67: | |
68: | |
69: | |
70: | |
71: | public static function getProgramName(?string $parentDir = null): string |
72: | { |
73: | $filename = $_SERVER['SCRIPT_FILENAME']; |
74: | |
75: | if ($parentDir === null) { |
76: | return $filename; |
77: | } |
78: | |
79: | $filename = File::getRelativePath($filename, $parentDir); |
80: | if ($filename === null) { |
81: | throw new LogicException(sprintf( |
82: | "'%s' is not in '%s'", |
83: | $_SERVER['SCRIPT_FILENAME'], |
84: | $parentDir, |
85: | )); |
86: | } |
87: | |
88: | return $filename; |
89: | } |
90: | |
91: | |
92: | |
93: | |
94: | |
95: | |
96: | public static function getProgramBasename(string ...$suffixes): string |
97: | { |
98: | $basename = basename($_SERVER['SCRIPT_FILENAME']); |
99: | |
100: | if (!$suffixes) { |
101: | return $basename; |
102: | } |
103: | |
104: | foreach ($suffixes as $suffix) { |
105: | $length = strlen($suffix); |
106: | if (substr($basename, -$length) === $suffix) { |
107: | return substr($basename, 0, -$length); |
108: | } |
109: | } |
110: | |
111: | return $basename; |
112: | } |
113: | |
114: | |
115: | |
116: | |
117: | |
118: | |
119: | |
120: | public static function getTempDir(): string |
121: | { |
122: | $tempDir = sys_get_temp_dir(); |
123: | $dir = @realpath($tempDir); |
124: | if ($dir === false || !is_dir($dir) || !is_writable($dir)) { |
125: | |
126: | throw new RuntimeException( |
127: | sprintf('Not a writable directory: %s', $tempDir), |
128: | ); |
129: | |
130: | } |
131: | return $dir; |
132: | } |
133: | |
134: | |
135: | |
136: | |
137: | |
138: | |
139: | public static function getUserId() |
140: | { |
141: | if (function_exists('posix_geteuid')) { |
142: | return posix_geteuid(); |
143: | } |
144: | |
145: | $user = Env::getNullable('USERNAME', null); |
146: | if ($user !== null) { |
147: | return $user; |
148: | } |
149: | |
150: | |
151: | $user = Env::getNullable('USER', null); |
152: | if ($user !== null) { |
153: | return $user; |
154: | } |
155: | |
156: | throw new RuntimeException('Unable to identify user'); |
157: | |
158: | } |
159: | |
160: | |
161: | |
162: | |
163: | public static function isProcessRunning(int $pid): bool |
164: | { |
165: | if (!self::isWindows()) { |
166: | return posix_kill($pid, 0); |
167: | } |
168: | |
169: | $command = sprintf('tasklist /fo csv /nh /fi "PID eq %d"', $pid); |
170: | $stream = File::openPipe($command, 'r'); |
171: | $csv = File::getCsv($stream); |
172: | if (File::closePipe($stream, $command) !== 0) { |
173: | |
174: | throw new RuntimeException( |
175: | sprintf('Command failed: %s', $command) |
176: | ); |
177: | |
178: | } |
179: | |
180: | return count($csv) === 1 |
181: | && isset($csv[0][1]) |
182: | && $csv[0][1] === (string) $pid; |
183: | } |
184: | |
185: | |
186: | |
187: | |
188: | |
189: | |
190: | |
191: | |
192: | |
193: | public static function escapeCommand(array $args): string |
194: | { |
195: | $windows = self::isWindows(); |
196: | |
197: | foreach ($args as &$arg) { |
198: | $arg = $windows |
199: | ? self::escapeCmdArg($arg) |
200: | : self::escapeShellArg($arg); |
201: | } |
202: | |
203: | return implode(' ', $args); |
204: | } |
205: | |
206: | |
207: | |
208: | |
209: | private static function escapeShellArg(string $arg): string |
210: | { |
211: | if ($arg === '' || Regex::match('/[^a-z0-9+.\/@_-]/i', $arg)) { |
212: | return "'" . str_replace("'", "'\''", $arg) . "'"; |
213: | } |
214: | |
215: | return $arg; |
216: | } |
217: | |
218: | |
219: | |
220: | |
221: | |
222: | |
223: | |
224: | private static function escapeCmdArg(string $arg): string |
225: | { |
226: | $arg = Regex::replace('/(\\\\*)"/', '$1$1\"', $arg, -1, $quoteCount); |
227: | $quote = $arg === '' || strpbrk($arg, " \t,") !== false; |
228: | $meta = $quoteCount > 0 || Regex::match('/%[^%]+%|![^!]+!/', $arg); |
229: | |
230: | if (!$meta && !$quote) { |
231: | $quote = strpbrk($arg, '^&|<>()') !== false; |
232: | } |
233: | |
234: | if ($quote) { |
235: | $arg = '"' . Regex::replace('/(\\\\*)$/', '$1$1', $arg) . '"'; |
236: | } |
237: | |
238: | if ($meta) { |
239: | $arg = Regex::replace('/["^&|<>()%!]/', '^$0', $arg); |
240: | } |
241: | |
242: | return $arg; |
243: | } |
244: | |
245: | |
246: | |
247: | |
248: | public static function isWindows(): bool |
249: | { |
250: | return \PHP_OS_FAMILY === 'Windows'; |
251: | } |
252: | |
253: | |
254: | |
255: | |
256: | |
257: | |
258: | |
259: | public static function handleExitSignals(): bool |
260: | { |
261: | if (!function_exists('pcntl_async_signals')) { |
262: | return false; |
263: | } |
264: | |
265: | $handler = static function (int $signal): void { |
266: | |
267: | $status = 128 + $signal; |
268: | if ( |
269: | class_exists(Err::class) |
270: | && Err::isLoaded() |
271: | && Err::isRegistered() |
272: | ) { |
273: | Err::handleExitSignal($status); |
274: | } |
275: | exit($status); |
276: | |
277: | }; |
278: | |
279: | pcntl_async_signals(true); |
280: | |
281: | return pcntl_signal(\SIGTERM, $handler) |
282: | && pcntl_signal(\SIGINT, $handler) |
283: | && pcntl_signal(\SIGHUP, $handler); |
284: | } |
285: | } |
286: | |