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: | |
74: | $filename = $_SERVER['SCRIPT_FILENAME']; |
75: | |
76: | if ($parentDir === null) { |
77: | return $filename; |
78: | } |
79: | |
80: | $relative = File::getRelativePath($filename, $parentDir); |
81: | if ($relative === null) { |
82: | throw new LogicException(sprintf( |
83: | "'%s' is not in '%s'", |
84: | $filename, |
85: | $parentDir, |
86: | )); |
87: | } |
88: | |
89: | return $relative; |
90: | } |
91: | |
92: | |
93: | |
94: | |
95: | |
96: | |
97: | public static function getProgramBasename(string ...$suffix): string |
98: | { |
99: | |
100: | $filename = $_SERVER['SCRIPT_FILENAME']; |
101: | $basename = basename($filename); |
102: | |
103: | if (!$suffix) { |
104: | return $basename; |
105: | } |
106: | |
107: | foreach ($suffix as $suffix) { |
108: | if ($suffix === $basename) { |
109: | continue; |
110: | } |
111: | $length = strlen($suffix); |
112: | if (substr($basename, -$length) === $suffix) { |
113: | return substr($basename, 0, -$length); |
114: | } |
115: | } |
116: | |
117: | return $basename; |
118: | } |
119: | |
120: | |
121: | |
122: | |
123: | |
124: | |
125: | |
126: | public static function getTempDir(): string |
127: | { |
128: | $tempDir = sys_get_temp_dir(); |
129: | $dir = @realpath($tempDir); |
130: | if ($dir === false || !is_dir($dir) || !is_writable($dir)) { |
131: | |
132: | throw new RuntimeException( |
133: | sprintf('Not a writable directory: %s', $tempDir), |
134: | ); |
135: | |
136: | } |
137: | return $dir; |
138: | } |
139: | |
140: | |
141: | |
142: | |
143: | |
144: | |
145: | public static function getUserId() |
146: | { |
147: | if (function_exists('posix_geteuid')) { |
148: | return posix_geteuid(); |
149: | } |
150: | |
151: | $user = Env::getNullable( |
152: | 'USERNAME', |
153: | fn() => Env::getNullable('USER', null), |
154: | ); |
155: | if ($user === null) { |
156: | |
157: | throw new RuntimeException('Unable to identify user'); |
158: | |
159: | } |
160: | return $user; |
161: | } |
162: | |
163: | |
164: | |
165: | |
166: | public static function isRunningAsRoot(): bool |
167: | { |
168: | return function_exists('posix_geteuid') |
169: | && posix_geteuid() === 0; |
170: | } |
171: | |
172: | |
173: | |
174: | |
175: | public static function isProcessRunning(int $pid): bool |
176: | { |
177: | if (!self::isWindows()) { |
178: | return posix_kill($pid, 0); |
179: | } |
180: | |
181: | $command = sprintf('tasklist /fo csv /nh /fi "PID eq %d"', $pid); |
182: | $stream = File::openPipe($command, 'r'); |
183: | $csv = File::getCsv($stream); |
184: | if (File::closePipe($stream, $command) !== 0) { |
185: | |
186: | throw new RuntimeException( |
187: | sprintf('Command failed: %s', $command) |
188: | ); |
189: | |
190: | } |
191: | |
192: | return count($csv) === 1 |
193: | && isset($csv[0][1]) |
194: | && $csv[0][1] === (string) $pid; |
195: | } |
196: | |
197: | |
198: | |
199: | |
200: | |
201: | |
202: | |
203: | |
204: | |
205: | |
206: | public static function escapeCommand(array $args): string |
207: | { |
208: | $windows = self::isWindows(); |
209: | |
210: | foreach ($args as $arg) { |
211: | $escaped[] = $windows |
212: | ? self::escapeCmdArg($arg) |
213: | : self::escapeShellArg($arg); |
214: | } |
215: | |
216: | return implode(' ', $escaped); |
217: | } |
218: | |
219: | |
220: | |
221: | |
222: | private static function escapeShellArg(string $arg): string |
223: | { |
224: | return $arg === '' |
225: | || Regex::match('/[^a-z0-9+.\/@_-]/i', $arg) |
226: | ? "'" . str_replace("'", "'\''", $arg) . "'" |
227: | : $arg; |
228: | } |
229: | |
230: | |
231: | |
232: | |
233: | |
234: | |
235: | |
236: | private static function escapeCmdArg(string $arg): string |
237: | { |
238: | $arg = Regex::replace('/(\\\\*)"/', '$1$1\"', $arg, -1, $quoteCount); |
239: | $quote = $arg === '' || strpbrk($arg, " \t,") !== false; |
240: | $meta = $quoteCount > 0 || Regex::match('/%[^%]+%|![^!]+!/', $arg); |
241: | |
242: | if (!$meta && !$quote) { |
243: | $quote = strpbrk($arg, '^&|<>()') !== false; |
244: | } |
245: | |
246: | if ($quote) { |
247: | $arg = '"' . Regex::replace('/(\\\\*)$/', '$1$1', $arg) . '"'; |
248: | } |
249: | |
250: | if ($meta) { |
251: | $arg = Regex::replace('/["^&|<>()%!]/', '^$0', $arg); |
252: | } |
253: | |
254: | return $arg; |
255: | } |
256: | |
257: | |
258: | |
259: | |
260: | public static function isWindows(): bool |
261: | { |
262: | return \PHP_OS_FAMILY === 'Windows'; |
263: | } |
264: | |
265: | |
266: | |
267: | |
268: | |
269: | |
270: | |
271: | public static function handleExitSignals(): bool |
272: | { |
273: | if (!function_exists('pcntl_async_signals')) { |
274: | return false; |
275: | } |
276: | |
277: | $handler = static function (int $signal): void { |
278: | |
279: | $status = 128 + $signal; |
280: | if ( |
281: | class_exists(Err::class) |
282: | && Err::isLoaded() |
283: | && Err::isRegistered() |
284: | ) { |
285: | Err::handleExitSignal($status); |
286: | } |
287: | exit($status); |
288: | |
289: | }; |
290: | |
291: | pcntl_async_signals(true); |
292: | |
293: | return pcntl_signal(\SIGTERM, $handler) |
294: | && pcntl_signal(\SIGINT, $handler) |
295: | && pcntl_signal(\SIGHUP, $handler); |
296: | } |
297: | } |
298: | |