Passed
Push — develop ( 3bb550...c283a9 )
by Nikolay
06:01 queued 11s
created

SysLogsManagementProcessor::scanDirRecursively()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 20
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 20
rs 9.2222
c 0
b 0
f 0
cc 6
nc 5
nop 1
1
<?php
2
/*
3
 * Copyright © MIKO LLC - All Rights Reserved
4
 * Unauthorized copying of this file, via any medium is strictly prohibited
5
 * Proprietary and confidential
6
 * Written by Alexey Portnov, 9 2020
7
 */
8
9
namespace MikoPBX\PBXCoreREST\Lib;
10
11
use MikoPBX\Core\System\Network;
12
use MikoPBX\Core\System\System;
13
use MikoPBX\Core\System\Util;
14
use MikoPBX\PBXCoreREST\Workers\WorkerMakeLogFilesArchive;
15
use Phalcon\Di;
16
use Phalcon\Di\Injectable;
17
use phpDocumentor\Reflection\Types\Self_;
18
19
class SysLogsManagementProcessor extends Injectable
20
{
21
    public const DEFAULT_FILENAME = 'asterisk/messages';
22
23
    /**
24
     * Processes System requests
25
     *
26
     * @param array $request
27
     *
28
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
29
     * @throws \Exception
30
     */
31
    public static function callBack(array $request): PBXApiResult
32
    {
33
        $action         = $request['action'];
34
        $data           = $request['data'];
35
        $res            = new PBXApiResult();
36
        $res->processor = __METHOD__;
37
        switch ($action) {
38
            case 'getLogFromFile':
39
                $res = self::getLogFromFile($data['filename'], $data['filter'], $data['lines'], $data['offset']);
40
                break;
41
            case 'startLog':
42
                $res = self::startLog();
43
                break;
44
            case 'stopLog':
45
                $res = self::stopLog();
46
                break;
47
            case 'downloadLogsArchive':
48
                $res = self::downloadLogsArchive($data['filename']);
49
                break;
50
            case 'downloadLogFile':
51
                $res = self::downloadLogFile($data['filename']);
52
                break;
53
            case 'getLogsList':
54
                $res = self::getLogsList();
55
                break;
56
            default:
57
                $res->messages[] = "Unknown action - {$action} in syslogCallBack";
58
        }
59
60
        $res->function = $action;
61
62
        return $res;
63
    }
64
65
    /**
66
     * Gets log file content partially
67
     *
68
     * @param string $filename
69
     * @param string $filter
70
     * @param int    $lines
71
     * @param int    $offset
72
     *
73
     * @return PBXApiResult
74
     */
75
    private static function getLogFromFile(string $filename, string $filter = '', $lines = 500, $offset = 0): PBXApiResult
76
    {
77
        $res            = new PBXApiResult();
78
        $res->processor = __METHOD__;
79
        $filename       = System::getLogDir() . '/' . $filename;
80
        if ( ! file_exists($filename)) {
81
            $res->success    = false;
82
            $res->messages[] = 'No access to the file ' . $filename;
83
        } else {
84
            $res->success = true;
85
            $head         = Util::which('head');
86
            $grep         = Util::which('grep');
87
            $tail         = Util::which('tail');
88
            $filter       = escapeshellarg($filter);
89
            $offset       = (int)$offset;
90
            $lines        = (int)$lines;
91
            $linesPlusOffset = $lines+$offset;
92
93
            $di          = Di::getDefault();
94
            $dirsConfig  = $di->getShared('config');
95
            $filenameTmp = $dirsConfig->path('www.downloadCacheDir') . '/' . __FUNCTION__ . '_' . time() . '.log';
96
            if (empty($filter)){
97
                $cmd         = "{$tail} -n {$linesPlusOffset} {$filename}";
98
            } else {
99
                $cmd         = "{$grep} -F {$filter} {$filename} | $tail -n {$linesPlusOffset}";
100
            }
101
            if ($offset>0){
102
                $cmd .= " | {$head} -n {$lines}";
103
            }
104
            $cmd .= " > $filenameTmp";
105
106
            Util::mwExec("$cmd; chown www:www $filenameTmp");
107
            $res->data['cmd']=$cmd;
108
            $res->data['filename'] = $filenameTmp;
109
        }
110
111
        return $res;
112
    }
113
114
    /**
115
     * Стартует запись логов.
116
     *
117
     * @param int $timeout
118
     *
119
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
120
     * @throws \Exception
121
     */
122
    private static function startLog($timeout = 300): PBXApiResult
123
    {
124
        $res            = new PBXApiResult();
125
        $res->processor = __METHOD__;
126
        $logDir         = System::getLogDir();
127
128
        // TCP dump
129
        $tcpDumpDir  = "{$logDir}/tcpDump";
130
        Util::mwMkdir($tcpDumpDir);
131
        $network     = new Network();
132
        $arr_eth     = $network->getInterfacesNames();
133
        $tcpdumpPath = Util::which('tcpdump');
134
        foreach ($arr_eth as $eth) {
135
            Util::mwExecBgWithTimeout(
136
                "{$tcpdumpPath} -i {$eth} -n -s 0 -vvv -w {$tcpDumpDir}/{$eth}.pcap",
137
                $timeout,
138
                "{$tcpDumpDir}/{$eth}_out.log"
139
            );
140
        }
141
        $res->success = true;
142
143
        return $res;
144
    }
145
146
    /**
147
     * Prepare log archive file
148
     *
149
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
150
     * @throws \Exception
151
     */
152
    private static function stopLog(): PBXApiResult
153
    {
154
        $res            = new PBXApiResult();
155
        $res->processor = __METHOD__;
156
        $di             = Di::getDefault();
157
        $dirsConfig     = $di->getShared('config');
158
        $temp_dir       = $dirsConfig->path('core.tempDir');
159
160
        // Collect system info
161
        $logDir         = System::getLogDir();
162
        $systemInfoFile = "{$logDir}/system-information.log";
163
        file_put_contents($systemInfoFile, SysinfoManagementProcessor::prepareSysyinfoContent());
164
165
        $futureFileName        = $temp_dir . '/temp-all-log-' . time() . '.zip';
166
        $res->data['filename'] = $futureFileName;
167
        $res->success          = true;
168
169
        Util::killByName('timeout');
170
        Util::killByName('tcpdump');
171
172
        // Create background task
173
        $merge_settings                = [];
174
        $merge_settings['result_file'] = $futureFileName;
175
        $settings_file                 = "{$temp_dir}/logs_archive_settings.json";
176
        file_put_contents(
177
            $settings_file,
178
            json_encode($merge_settings, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
179
        );
180
        $phpPath               = Util::which('php');
181
        $workerFilesMergerPath = Util::getFilePathByClassName(WorkerMakeLogFilesArchive::class);
182
        Util::mwExecBg("{$phpPath} -f {$workerFilesMergerPath} '{$settings_file}'");
183
184
        return $res;
185
    }
186
187
    /**
188
     * Check if archive ready then create download link
189
     *
190
     * @param string $resultFile
191
     *
192
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
193
     * @throws \Exception
194
     */
195
    private static function downloadLogsArchive(string $resultFile): PBXApiResult
196
    {
197
        $res            = new PBXApiResult();
198
        $res->processor = __METHOD__;
199
200
        $progress_file = "{$resultFile}.progress";
201
        if ( ! file_exists($progress_file)) {
202
            $res->messages[] = 'Archive does not exist. Try again!';
203
        } elseif (file_exists($progress_file) && file_get_contents($progress_file) === '100') {
204
            $uid          = Util::generateRandomString(36);
205
            $di           = Di::getDefault();
206
            $downloadLink = $di->getShared('config')->path('www.downloadCacheDir');
207
            $result_dir   = "{$downloadLink}/{$uid}";
208
            Util::mwMkdir($result_dir);
209
            $link_name = 'MikoPBXLogs_' . basename($resultFile);
210
            Util::createUpdateSymlink($resultFile, "{$result_dir}/{$link_name}");
211
            Util::addRegularWWWRights("{$result_dir}/{$link_name}");
212
            $res->success          = true;
213
            $res->data['status']   = "READY";
214
            $res->data['filename'] = "{$uid}/{$link_name}";
215
        } else {
216
            $res->success        = true;
217
            $res->data['status'] = "preparing";
218
        }
219
220
        return $res;
221
    }
222
223
    /**
224
     * Prepares downloadable log file
225
     *
226
     * @param string $filename
227
     *
228
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
229
     * @throws \Exception
230
     */
231
    private static function downloadLogFile(string $filename): PBXApiResult
232
    {
233
        $res            = new PBXApiResult();
234
        $res->processor = __METHOD__;
235
        $filename       = System::getLogDir() . '/' . $filename;
236
        if ( ! file_exists($filename)) {
237
            $res->success    = false;
238
            $res->messages[] = 'File does not exist ' . $filename;
239
        } else {
240
            $uid          = Util::generateRandomString(36);
241
            $di           = Di::getDefault();
242
            $downloadLink = $di->getShared('config')->path('www.downloadCacheDir');
243
            $result_dir   = "{$downloadLink}/{$uid}";
244
            Util::mwMkdir($result_dir);
245
            $link_name = basename($filename);
246
            $lnPath    = Util::which('ln');
247
            $chownPath = Util::which('chown');
248
            Util::mwExec("{$lnPath} -s {$filename} {$result_dir}/{$link_name}");
249
            Util::mwExec("{$chownPath} www:www {$result_dir}/{$link_name}");
250
            $res->success          = true;
251
            $res->data['filename'] = "{$uid}/{$link_name}";
252
        }
253
254
        return $res;
255
    }
256
257
    /**
258
     * Returns list of log files to show them on web interface
259
     *
260
     * @return \MikoPBX\PBXCoreREST\Lib\PBXApiResult
261
     */
262
    private static function getLogsList(): PBXApiResult
263
    {
264
        $res            = new PBXApiResult();
265
        $res->processor = __METHOD__;
266
        $logDir         = System::getLogDir();
267
        $filesList      = [];
268
        $entries        = self::scanDirRecursively($logDir);
269
        $entries        = Util::flattenArray($entries);
270
        foreach ($entries as $entry) {
271
            $fileSize = filesize($entry);
272
            $now      = time();
273
            if ($fileSize === 0
274
                || $now - filemtime($entry) > 604800 // Older than 10 days
275
            ) {
276
                continue;
277
            }
278
279
            $relativePath             = str_ireplace($logDir . '/', '', $entry);
280
            $fileSizeKB               = ceil($fileSize / 1024);
281
            $filesList[$relativePath] =
282
                [
283
                    'path'    => $relativePath,
284
                    'size'    => "{$fileSizeKB} kb",
285
                    'default' => ($relativePath === self::DEFAULT_FILENAME),
286
                ];
287
        }
288
289
        ksort($filesList);
290
        $res->success       = true;
291
        $res->data['files'] = $filesList;
292
293
        return $res;
294
    }
295
296
    /**
297
     *
298
     * Scans a directory just like scandir(), only recursively
299
     * returns a hierarchical array representing the directory structure
300
     *
301
     * @param string $dir directory to scan
302
     *
303
     * @return array
304
     */
305
    private static function scanDirRecursively(string $dir): array
306
    {
307
        $list = [];
308
309
        //get directory contents
310
        foreach (scandir($dir) as $d) {
311
            //ignore any of the files in the array
312
            if (in_array($d, ['.', '..'])) {
313
                continue;
314
            }
315
            //if current file ($d) is a directory, call scanDirRecursively
316
            if (is_dir($dir . '/' . $d)) {
317
                $list[] = self::scanDirRecursively($dir . '/' . $d);
318
                //otherwise, add the file to the list
319
            } elseif (is_file($dir . '/' . $d) || is_link($dir . '/' . $d)) {
320
                $list[] = $dir . '/' . $d;
321
            }
322
        }
323
324
        return $list;
325
    }
326
}