Passed
Push — develop ( dadf5b...439163 )
by Nikolay
04:25
created

SysLogsManagementProcessor::startLog()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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