Passed
Push — develop ( bbba6b...68db6a )
by Nikolay
05:49 queued 11s
created

Storage::diskIsMounted()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 14
rs 9.9332
c 0
b 0
f 0
cc 3
nc 4
nop 2
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, 7 2020
7
 */
8
9
namespace MikoPBX\Core\System;
10
11
use MikoPBX\Core\Workers\WorkerRemoveOldRecords;
12
use MikoPBX\Common\Models\Storage as StorageModel;
13
use MikoPBX\Common\Providers\ConfigProvider;
14
use Phalcon\Di;
15
use function MikoPBX\Common\Config\appPath;
16
17
18
/**
19
 * Вспомогательные методы.
20
 */
21
class Storage
22
{
23
24
    /**
25
     * @var \Phalcon\Di\DiInterface|null
26
     */
27
    private $di;
28
29
    /**
30
     * @var \Phalcon\Config
31
     */
32
    private $config;
33
34
    /**
35
     * System constructor.
36
     */
37
    public function __construct()
38
    {
39
        $this->di     = Di::getDefault();
40
        $this->config = $this->di->getShared('config');
0 ignored issues
show
Bug introduced by
The method getShared() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

40
        /** @scrutinizer ignore-call */ 
41
        $this->config = $this->di->getShared('config');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
41
    }
42
43
    /**
44
     * Возвращает директорию для хранения файлов записей разговоров.
45
     *
46
     * @return string
47
     */
48
    public static function getMonitorDir(): string
49
    {
50
        $di     = Di::getDefault();
51
        if ($di !== null){
52
            return $di->getConfig()->path('asterisk.monitordir');
53
        }
54
        return '/tmp';
55
    }
56
57
    /**
58
     * Возвращает директорию для хранения media файлов.
59
     *
60
     * @return string
61
     */
62
    public static function getMediaDir(): string
63
    {
64
        $di     = Di::getDefault();
65
        if ($di !== null){
66
            return $di->getConfig()->path('core.mediaMountPoint');
67
        }
68
        return '/tmp';
69
    }
70
71
72
    /**
73
     * Прверяем является ли диск хранилищем.
74
     *
75
     * @param $device
76
     *
77
     * @return bool
78
     */
79
    public static function isStorageDisk($device): bool
80
    {
81
        $result = false;
82
        if ( ! file_exists("{$device}")) {
83
            return $result;
84
        }
85
86
        $tmp_dir = '/tmp/mnt_' . time();
87
        Util::mwMkdir($tmp_dir);
88
        $out = [];
89
90
        $storage  = new Storage();
91
        $uid_part = 'UUID=' . $storage->getUuid($device) . '';
92
        $format   = $storage->getFsType($device);
93
        if($format === ''){
94
            return false;
95
        }
96
        $mountPath = Util::which('mount');
97
        $umountPath = Util::which('umount');
98
        $rmPath = Util::which('rm');
99
100
        Util::mwExec("{$mountPath} -t {$format} {$uid_part} {$tmp_dir}", $out);
101
        if (is_dir("{$tmp_dir}/mikopbx") && trim(implode('', $out)) === '') {
102
            // $out - пустая строка, ошибок нет
103
            // присутствует каталог mikopbx.
104
            $result = true;
105
        }
106
        if (self::isStorageDiskMounted($device)) {
107
            Util::mwExec("{$umountPath} {$device}");
108
        }
109
110
        if ( ! self::isStorageDiskMounted($device)) {
111
            Util::mwExec("{$rmPath} -rf '{$tmp_dir}'");
112
        }
113
114
        return $result;
115
    }
116
117
    /**
118
     * Получение идентификатора устройства.
119
     *
120
     * @param $device
121
     *
122
     * @return string
123
     */
124
    public function getUuid($device): string
125
    {
126
        if (empty($device)) {
127
            return '';
128
        }
129
        $blkidPath = Util::which('blkid');
130
        $busyboxPath = Util::which('busybox');
131
        $sedPath = Util::which('sed');
132
        $grepPath = Util::which('grep');
133
        $awkPath = Util::which('awk');
134
        $headPath = Util::which('head');
135
136
        $res = Util::mwExec(
137
            "{$blkidPath} -ofull {$device} | {$busyboxPath} {$sedPath} -r 's/[[:alnum:]]+=/\\n&/g' | {$busyboxPath} {$grepPath} \"^UUID\" | {$busyboxPath} {$awkPath} -F \"\\\"\" '{print $2}' | {$headPath} -n 1",
138
            $output
139
        );
140
        if ($res == 0 && count($output) > 0) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $res of type integer|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
141
            $result = $output[0];
142
        } else {
143
            $result = '';
144
        }
145
146
        return $result;
147
    }
148
149
    /**
150
     * Возвращает тип файловой системы блочного устройства.
151
     *
152
     * @param $device
153
     *
154
     * @return string
155
     */
156
    public function getFsType($device): string
157
    {
158
        $blkidPath = Util::which('blkid');
159
        $busyboxPath = Util::which('busybox');
160
        $sedPath = Util::which('sed');
161
        $grepPath = Util::which('grep');
162
        $awkPath = Util::which('awk');
163
164
        $device = str_replace('/dev/', '', $device);
165
        $out    = [];
166
        Util::mwExec(
167
            "{$blkidPath} -ofull /dev/{$device} | {$busyboxPath} {$sedPath} -r 's/[[:alnum:]]+=/\\n&/g' | {$busyboxPath} {$grepPath} \"^TYPE=\" | {$busyboxPath} {$awkPath} -F \"\\\"\" '{print $2}'",
168
            $out
169
        );
170
        $format = implode('', $out);
171
        if ($format == 'msdosvfat') {
172
            $format = 'msdos';
173
        }
174
175
        return $format;
176
    }
177
178
    /**
179
     * Проверка, смонтирован ли диск - хранилище.
180
     *
181
     * @param string $filter
182
     * @param string $mount_dir
183
     *
184
     * @return bool
185
     */
186
    public static function isStorageDiskMounted($filter = '', &$mount_dir = ''): bool
187
    {
188
        if (Util::isSystemctl() && file_exists('/storage/usbdisk1/')) {
189
            $mount_dir = '/storage/usbdisk1/';
190
191
            return true;
192
        }
193
        if ('' === $filter) {
194
            $di     = Di::getDefault();
195
            if ($di !== null){
196
                $varEtcPath = $di->getConfig()->path('core.varEtcPath');
197
            } else {
198
                $varEtcPath = '/var/etc';
199
            }
200
201
            $filename   = "{$varEtcPath}/storage_device";
202
            if (file_exists($filename)) {
203
                $filter = file_get_contents($filename);
204
            } else {
205
                $filter = 'usbdisk1';
206
            }
207
        }
208
        $filter = escapeshellarg($filter);
209
210
        $out = [];
211
        $grepPath = Util::which('grep');
212
        $mountPath = Util::which('mount');
213
        $awkPath = Util::which('awk');
214
        Util::mwExec("{$mountPath} | {$grepPath} {$filter} | {$awkPath} '{print $3}'", $out);
215
        $mount_dir = trim(implode('', $out));
216
217
        return ($mount_dir !== '');
218
    }
219
220
    /**
221
     * Монитирование каталога с удаленного сервера SFTP.
222
     *
223
     * @param        $host
224
     * @param int    $port
225
     * @param string $user
226
     * @param string $pass
227
     * @param string $remout_dir
228
     * @param string $local_dir
229
     *
230
     * @return bool
231
     */
232
    public static function mountSftpDisk($host, $port, $user, $pass, $remout_dir, $local_dir): bool
233
    {
234
        Util::mwMkdir($local_dir);
235
236
        $out     = [];
237
        $timeoutPath = Util::which('timeout');
238
        $sshfsPath = Util::which('sshfs');
239
240
        $command = "{$timeoutPath} -t 3 {$sshfsPath} -p {$port} -o nonempty -o password_stdin -o 'StrictHostKeyChecking=no' " .
241
            "{$user}@{$host}:{$remout_dir} {$local_dir} << EOF\n" .
242
            "{$pass}\n" .
243
            "EOF\n";
244
        // file_put_contents('/tmp/sshfs_'.$host, $command);
245
        Util::mwExec($command, $out);
246
        $response = trim(implode('', $out));
247
        if ('Terminated' == $response) {
248
            // Удаленный сервер не ответил / или не корректно указан пароль.
249
            unset($response);
250
        }
251
252
        return self::isStorageDiskMounted("$local_dir ");
253
    }
254
255
    /**
256
     * Монитирование каталога с удаленного сервера FTP.
257
     *
258
     * @param        $host
259
     * @param        $port
260
     * @param        $user
261
     * @param        $pass
262
     * @param string $remout_dir
263
     * @param        $local_dir
264
     *
265
     * @return bool
266
     */
267
    public static function mountFtp($host, $port, $user, $pass, $remout_dir, $local_dir): bool
268
    {
269
        Util::mwMkdir($local_dir);
270
        $out = [];
271
272
        // Собираем строку подключения к ftp.
273
        $auth_line = '';
274
        if ( ! empty($user)) {
275
            $auth_line .= 'user="' . $user;
276
            if ( ! empty($pass)) {
277
                $auth_line .= ":{$pass}";
278
            }
279
            $auth_line .= '",';
280
        }
281
282
        $connect_line = 'ftp://' . $host;
283
        if ( ! empty($port)) {
284
            $connect_line .= ":{$port}";
285
        }
286
        if ( ! empty($remout_dir)) {
287
            $connect_line .= "$remout_dir";
288
        }
289
290
        $timeoutPath = Util::which('timeout');
291
        $curlftpfsPath = Util::which('curlftpfs');
292
        $command = "{$timeoutPath} -t 3 {$curlftpfsPath}  -o allow_other -o {$auth_line}fsname={$host} {$connect_line} {$local_dir}";
293
        Util::mwExec($command, $out);
294
        $response = trim(implode('', $out));
295
        if ('Terminated' === $response) {
296
            // Удаленный сервер не ответил / или не корректно указан пароль.
297
            unset($response);
298
        }
299
300
        return self::isStorageDiskMounted("$local_dir ");
301
    }
302
303
    /**
304
     * Запускает процесс форматирования диска.
305
     *
306
     * @param $dev
307
     *
308
     * @return array|bool
309
     */
310
    public static function mkfs_disk($dev)
311
    {
312
        if ( ! file_exists($dev)) {
313
            $dev = "/dev/{$dev}";
314
        }
315
        if ( ! file_exists($dev)) {
316
            return false;
317
        }
318
        $dir = '';
319
        self::isStorageDiskMounted("$dev", $dir);
320
321
        if (empty($dir) || self::umountDisk($dir)) {
322
            // Диск размонтирован.
323
            $st = new Storage();
324
            // Будет запущен процесс:
325
            $st->formatDiskLocal($dev, true);
326
            sleep(1);
327
328
            return (self::statusMkfs($dev) == 'inprogress');
329
        } else {
330
            // Ошибка размонтирования диска.
331
            return false;
332
        }
333
    }
334
335
    /**
336
     * Размонтирует диск. Удаляет каталог в случае успеха.
337
     *
338
     * @param $dir
339
     *
340
     * @return bool
341
     */
342
    public static function umountDisk($dir): bool
343
    {
344
        $umountPath = Util::which('umount');
345
        $rmPath = Util::which('rm');
346
        if (self::isStorageDiskMounted($dir)) {
347
            Util::mwExec("/sbin/shell_functions.sh 'killprocesses' '$dir' -TERM 0");
348
            Util::mwExec("{$umountPath} {$dir}");
349
        }
350
        $result = ! self::isStorageDiskMounted($dir);
351
        if ($result && file_exists($dir)) {
352
            // Если диск не смонтирован, то удаляем каталог.
353
            Util::mwExec("{$rmPath} -rf '{$dir}'");
354
        }
355
356
        return $result;
357
    }
358
359
    /**
360
     * Разметка диска.
361
     *
362
     * @param string $device
363
     * @param bool   $bg
364
     *
365
     * @return mixed
366
     */
367
    public function formatDiskLocal($device, $bg = false)
368
    {
369
        $partedPath = Util::which('parted');
370
        $retVal = Util::mwExec(
371
            "{$partedPath} --script --align optimal '{$device}' 'mklabel msdos mkpart primary ext4 0% 100%'"
372
        );
373
        Util::sysLogMsg(__CLASS__, "{$partedPath} returned {$retVal}");
374
        if (false === $bg) {
375
            sleep(1);
376
        }
377
        return $this->formatDiskLocalPart2($device, $bg);
378
    }
379
380
    /**
381
     * Форматирование диска.
382
     *
383
     * @param string $device
384
     * @param bool   $bg
385
     *
386
     * @return mixed
387
     */
388
    private function formatDiskLocalPart2($device, $bg = false)
389
    {
390
        if (is_numeric(substr($device, -1))) {
391
            $device_id = "";
392
        } else {
393
            $device_id = "1";
394
        }
395
        $format = 'ext4';
396
        $mkfsPath = Util::which("mkfs.{$format}");
397
        $cmd    = "{$mkfsPath} {$device}{$device_id}";
398
        if ($bg === false) {
399
            $retVal = Util::mwExec("{$cmd} 2>&1");
400
            Util::sysLogMsg(__CLASS__, "{$mkfsPath} returned {$retVal}");
401
        } else {
402
            usleep(200000);
403
            Util::mwExecBg($cmd);
404
            $retVal = true;
405
        }
406
        return $retVal;
407
    }
408
409
    /**
410
     * Возвращает текущий статус форматирования диска.
411
     *
412
     * @param $dev
413
     *
414
     * @return string
415
     */
416
    public static function statusMkfs($dev): string
417
    {
418
        if ( ! file_exists($dev)) {
419
            $dev = "/dev/{$dev}";
420
        }
421
        $out = [];
422
        $psPath = Util::which('ps');
423
        $grepPath = Util::which('grep');
424
        Util::mwExec("{$psPath} -A -f | {$grepPath} {$dev} | {$grepPath} mkfs | {$grepPath} -v grep", $out);
425
        $mount_dir = trim(implode('', $out));
426
427
        return empty($mount_dir) ? 'ended' : 'inprogress';
428
    }
429
430
    /**
431
     * Проверка свободного места на дисках. Уведомление в случае проблем.
432
     */
433
    public function checkFreeSpace(): void
434
    {
435
        $util    = new Util();
436
        $hdd     = $this->getAllHdd(true);
437
        // Создание больщого файла для тестов.
438
        // head -c 1500MB /dev/urandom > /storage/usbdisk1/big_file.mp3
439
        foreach ($hdd as $disk) {
440
            if ($disk['sys_disk'] === true && ! self::isStorageDiskMounted("{$disk['id']}4")) {
441
                // Это системный диск (4ый раздел). Он не смонтирован.
442
                continue;
443
            }
444
445
            $free       = ($disk['free_space'] / $disk['size'] * 100);
446
            $need_alert = false;
447
            $test_alert = '';
448
            if ($free < 5) {
449
                $need_alert = true;
450
                $test_alert = "The {$disk['id']} has less than 5% of free space available.";
451
            }
452
453
            if ($disk['free_space'] < 500) {
454
                $need_alert = true;
455
                $test_alert = "The {$disk['id']} has less than 500MB of free space available.";
456
            }
457
458
            if ($disk['free_space'] < 100) {
459
                $need_alert  = true;
460
                $test_alert  = "The {$disk['id']} has less than 100MB of free space available. Old call records will be deleted.";
461
                Util::restartPHPWorker(WorkerRemoveOldRecords::class);
462
            }
463
464
            if ( ! $need_alert) {
465
                continue;
466
            }
467
468
            Util::sysLogMsg("STORAGE", $test_alert);
469
            $data = [
470
                'Device     - ' => "/dev/{$disk['id']}",
471
                'Directoire - ' => "{$disk['mounted']}",
472
                'Desciption - ' => $test_alert,
473
            ];
474
            // Добавляем задачу на уведомление.
475
            $util->addJobToBeanstalk('WorkerNotifyError_storage', $data);
476
        }
477
    }
478
479
    /**
480
     * Возвращает все подключенные HDD.
481
     *
482
     * @param bool $mounted_only
483
     *
484
     * @return array
485
     */
486
    public function getAllHdd($mounted_only = false): array
487
    {
488
        $res_disks = [];
489
490
        if (Util::isSystemctl()) {
491
            $out = [];
492
            $grepPath = Util::which('grep');
493
            $dfPath = Util::which('df');
494
            $awkPath = Util::which('awk');
495
            Util::mwExec(
496
                "{$dfPath} -k /storage/usbdisk1 | {$awkPath}  '{ print $1 \"|\" $3 \"|\" $4} ' | {$grepPath} -v 'Available'",
497
                $out
498
            );
499
            $disk_data = explode('|', implode(" ", $out));
500
            if (count($disk_data) === 3) {
501
                $m_size      = round(($disk_data[1] + $disk_data[2]) / 1024, 1);
502
                $res_disks[] = [
503
                    'id'         => $disk_data[0],
504
                    'size'       => "" . $m_size,
505
                    'size_text'  => "" . $m_size . " Mb",
506
                    'vendor'     => 'Debian',
507
                    'mounted'    => '/storage/usbdisk1',
508
                    'free_space' => round($disk_data[2] / 1024, 1),
509
                    'partitions' => [],
510
                    'sys_disk'   => true,
511
                ];
512
            }
513
514
            return $res_disks;
515
        }
516
517
        $cd_disks = $this->cdromGetDevices();
518
        $cd_disks = array_unique($cd_disks);
519
520
        // TODO Получение данных о дисках в формате JSON:
521
        // lsblk -J -b -o VENDOR,MODEL,SERIAL,LABEL,TYPE,FSTYPE,MOUNTPOINT,SUBSYSTEMS,NAME,UUID
522
        $disks = $this->diskGetDevices();
523
        $disks = array_unique($disks);
524
525
        $cf_disk    = '';
526
        $varEtcPath = $this->config->path('core.varEtcPath');
527
        if (file_exists($varEtcPath . '/cfdevice')) {
528
            $cf_disk = trim(file_get_contents($varEtcPath . '/cfdevice'));
529
        }
530
531
        foreach ($disks as $disk) {
532
            if (in_array($disk, $cd_disks)) {
533
                // Это CD-ROM.
534
                continue;
535
            }
536
            unset($temp_vendor, $temp_size, $original_size);
537
            $mounted = self::diskIsMounted("{$disk}");
538
            if ($mounted_only === true && $mounted === false) {
539
                continue;
540
            }
541
            $sys_disk = ($cf_disk == $disk);
542
543
            $mb_size = 0;
544
            if (is_file("/sys/block/" . $disk . "/size")) {
545
                $original_size = trim(file_get_contents("/sys/block/" . $disk . "/size"));
546
                $original_size = ($original_size * 512 / 1024 / 1024);
547
                $mb_size       = $original_size;
548
            }
549
            if ($mb_size > 100) {
550
                $temp_size   = sprintf("%.0f MB", $mb_size);
551
                $temp_vendor = $this->getVendorDisk($disk);
552
                $free_space  = $this->getFreeSpace($disk);
553
554
                $arr_disk_info = $this->determineFormatFs($disk);
555
                if (count($arr_disk_info) > 0) {
556
                    $used = 0;
557
                    foreach ($arr_disk_info as $disk_info) {
558
                        $used += $disk_info['used_space'];
559
                    }
560
                    if ($used > 0) {
561
                        $free_space = $mb_size - $used;
562
                    }
563
                }
564
565
                $res_disks[] = [
566
                    'id'         => $disk,
567
                    'size'       => $mb_size,
568
                    'size_text'  => $temp_size,
569
                    'vendor'     => $temp_vendor,
570
                    'mounted'    => $mounted,
571
                    'free_space' => $free_space,
572
                    'partitions' => $arr_disk_info,
573
                    'sys_disk'   => $sys_disk,
574
                ];
575
            }
576
        }
577
578
        return $res_disks;
579
    }
580
581
    /**
582
     * Получение массива подключенныйх cdrom.
583
     *
584
     * @return array
585
     */
586
    private function cdromGetDevices(): array
587
    {
588
        $grepPath = Util::which('grep');
589
        $sysctlPath = Util::which('sysctl');
590
        $busyboxPath = Util::which('busybox');
591
        $cutPath = Util::which('cut');
592
593
        return explode(
594
            " ",
595
            trim(exec("{$sysctlPath} -n dev.cdrom.info | {$busyboxPath} {$grepPath} 'drive name' | {$busyboxPath} {$cutPath} -f 3"))
596
        );
597
    }
598
599
    /**
600
     * Получение массива подключенныйх HDD.
601
     *
602
     * @return array
603
     */
604
    private function diskGetDevices(): array
605
    {
606
        //  TODO // Переписать через использование lsblk.
607
        $grepPath = Util::which('grep');
608
        $lsPath = Util::which('ls');
609
        $trPath = Util::which('tr');
610
        return explode(" ", trim(exec("{$lsPath} /dev | {$grepPath} '^[a-z]d[a-z]' | {$trPath} \"\n\" \" \"")));
611
    }
612
613
    /**
614
     * Проверка, смонтирован ли диск.
615
     *
616
     * @param $disk
617
     * @param $filter
618
     *
619
     * @return string|bool
620
     */
621
    public static function diskIsMounted($disk, $filter = '/dev/')
622
    {
623
        $out = [];
624
        $grepPath = Util::which('grep');
625
        $mountPath = Util::which('mount');
626
        Util::mwExec("{$mountPath} | {$grepPath} '{$filter}{$disk}'", $out);
627
        if (count($out) > 0) {
628
            $res_out = end($out);
629
        } else {
630
            $res_out = implode('', $out);
631
        }
632
        $data = explode(' ', trim($res_out));
633
634
        return (count($data) > 2) ? $data[2] : false;
635
    }
636
637
    /**
638
     * Получение сведений по диску.
639
     *
640
     * @param $disk
641
     *
642
     * @return string
643
     */
644
    private function getVendorDisk($disk): string
645
    {
646
        $temp_vendor = [];
647
        if (is_file("/sys/block/" . $disk . "/device/vendor")) {
648
            $data = trim(file_get_contents("/sys/block/" . $disk . "/device/vendor"));
649
            if ($data != '') {
650
                $temp_vendor[] = trim(str_replace(',', ' ', $data));
651
            }
652
        }
653
        if (is_file("/sys/block/" . $disk . "/device/model")) {
654
            $data = trim(file_get_contents("/sys/block/" . $disk . "/device/model"));
655
            if ($data != '') {
656
                $temp_vendor[] = trim(str_replace(',', ' ', $data));
657
            }
658
        }
659
        if (count($temp_vendor) == 0) {
660
            $temp_vendor[] = $disk;
661
        }
662
        if (is_file("/sys/block/" . $disk . "/device/type")) {
663
            $data = trim(file_get_contents("/sys/block/" . $disk . "/device/type"));
664
            if ($data != '') {
665
                $temp_vendor[] = trim(str_replace(',', ' ', $data));
666
            }
667
        }
668
669
        return implode(', ', $temp_vendor);
670
    }
671
672
    /**
673
     * Получаем свободное место на диске в Mb.
674
     *
675
     * @param $hdd
676
     *
677
     * @return mixed
678
     */
679
    public function getFreeSpace($hdd)
680
    {
681
        $out = [];
682
        $hdd = escapeshellarg($hdd);
683
        $grepPath = Util::which('grep');
684
        $awkPath = Util::which('awk');
685
        $dfPath = Util::which('df');
686
        Util::mwExec("{$dfPath} -m | {$grepPath} {$hdd} | {$awkPath} '{print $4}'", $out);
687
        $result = 0;
688
        foreach ($out as $res) {
689
            if ( ! is_numeric($res)) {
690
                continue;
691
            }
692
            $result += (1 * $res);
693
        }
694
695
        return $result;
696
    }
697
698
    /**
699
     * Определить формат файловой системы и размер дисков.
700
     *
701
     * @param $device
702
     *
703
     * @return array|bool
704
     */
705
    public function determineFormatFs($device)
706
    {
707
        $grepPath = Util::which('grep');
708
        $lsPath = Util::which('ls');
709
        $trPath = Util::which('tr');
710
        $allow_formats = ['ext2', 'ext4', 'fat', 'ntfs', 'msdos'];
711
        $device        = str_replace('/dev/', '', $device);
712
        $devices       = explode(" ", trim(exec("{$lsPath} /dev | {$grepPath} '{$device}' | {$trPath} \"\n\" \" \"")));
713
714
        $result_data = [];
715
        foreach ($devices as $dev) {
716
            if (empty($dev) || (count($devices) > 1 && $device == $dev) || is_dir("/sys/block/{$dev}")) {
717
                continue;
718
            }
719
            $mb_size        = 0;
720
            $path_size_info = '';
721
            $tmp_path       = "/sys/block/{$device}/{$dev}/size";
722
            if (file_exists($tmp_path)) {
723
                $path_size_info = $tmp_path;
724
            }
725
            if (empty($path_size_info)) {
726
                $tmp_path = "/sys/block/" . substr($dev, 0, 3) . "/{$dev}/size";
727
                if (file_exists($tmp_path)) {
728
                    $path_size_info = $tmp_path;
729
                }
730
            }
731
732
            if ( ! empty($path_size_info)) {
733
                $original_size = trim(file_get_contents($path_size_info));
734
                $original_size = ($original_size * 512 / 1024 / 1024);
735
                $mb_size       = $original_size;
736
            }
737
738
            $tmp_dir = "/tmp/{$dev}_" . time();
739
            $out     = [];
740
741
            $fs           = null;
742
            $need_unmount = false;
743
            $mount_dir    = '';
744
            if (self::isStorageDiskMounted("/dev/{$dev} ", $mount_dir)) {
745
                $grepPath = Util::which('grep');
746
                $awkPath = Util::which('awk');
747
                $mountPath = Util::which('mount');
748
                Util::mwExec("{$mountPath} | {$grepPath} '/dev/{$dev}' | {$awkPath} '{print $5}'", $out);
749
                $fs         = trim(implode("", $out));
750
                $fs         = ($fs == 'fuseblk') ? 'ntfs' : $fs;
751
                $free_space = $this->getFreeSpace("/dev/{$dev} ");
752
                $used_space = $mb_size - $free_space;
753
            } else {
754
                $format = $this->getFsType($device);
755
                if (in_array($format, $allow_formats)) {
756
                    $fs = $format;
757
                }
758
                self::mountDisk($dev, $format, $tmp_dir);
759
760
                $need_unmount = true;
761
                $used_space   = Util::getSizeOfFile("$tmp_dir");
762
            }
763
            $result_data[] = [
764
                "dev"        => $dev,
765
                'size'       => round($mb_size, 2),
766
                "used_space" => round($used_space, 2),
767
                "free_space" => round($mb_size - $used_space, 2),
768
                "uuid"       => $this->getUuid("/dev/{$dev} "),
769
                "fs"         => $fs,
770
            ];
771
            if ($need_unmount) {
772
                self::umountDisk($tmp_dir);
773
            }
774
        }
775
776
        return $result_data;
777
    }
778
779
    /**
780
     * Монтирует диск в указанный каталог.
781
     *
782
     * @param $dev
783
     * @param $format
784
     * @param $dir
785
     *
786
     * @return bool
787
     */
788
    public static function mountDisk($dev, $format, $dir): bool
789
    {
790
        if (self::isStorageDiskMounted("/dev/{$dev} ")) {
791
            return true;
792
        }
793
        Util::mwMkdir($dir);
794
795
        if ( ! file_exists($dir)) {
796
            Util::sysLogMsg('Storage', "Unable mount $dev $format to $dir. Unable create dir.");
797
798
            return false;
799
        }
800
        $dev = str_replace('/dev/', '', $dev);
801
        if ('ntfs' == $format) {
802
            $mountNtfs3gPath = Util::which('mount.ntfs-3g');
803
            Util::mwExec("{$mountNtfs3gPath} /dev/{$dev} {$dir}", $out);
804
        } else {
805
            $storage  = new Storage();
806
            $uid_part = 'UUID=' . $storage->getUuid("/dev/{$dev}") . '';
807
            $mountPath = Util::which('mount');
808
            Util::mwExec("{$mountPath} -t {$format} {$uid_part} {$dir}", $out);
809
        }
810
811
        return self::isStorageDiskMounted("/dev/{$dev} ");
812
    }
813
814
    /**
815
     * Монтирование разделов диска с базой данных настроек.
816
     */
817
    public function configure(): void
818
    {
819
        $cf_disk          = '';
820
        $varEtcPath       = $this->config->path('core.varEtcPath');
821
        $storage_dev_file = "{$varEtcPath}/storage_device";
822
        if (file_exists($storage_dev_file)) {
823
            unlink($storage_dev_file);
824
        }
825
826
        if (file_exists($varEtcPath . '/cfdevice')) {
827
            $cf_disk = trim(file_get_contents($varEtcPath . '/cfdevice'));
828
        }
829
830
        $disks = $this->getDiskSettings();
831
        $conf  = '';
832
        foreach ($disks as $disk) {
833
            clearstatcache();
834
            if ($disk['device'] !== "/dev/{$cf_disk}") {
835
                // Если это обычный диск, то раздел 1.
836
                $dev = "{$disk['device']}1";
837
            } else {
838
                // Если это системный диск, то пытаемся подключить раздел 4.
839
                $dev = "{$disk['device']}4";
840
            }
841
            if ( ! $this->hddExists($dev)) {
842
                // Диск не существует.
843
                continue;
844
            }
845
            if ($disk['media'] === '1' || ! file_exists($storage_dev_file)) {
846
                file_put_contents($storage_dev_file, "/storage/usbdisk{$disk['id']}");
847
                $this->updateConfigWithNewMountPoint("/storage/usbdisk{$disk['id']}");
848
            }
849
850
            $str_uid     = 'UUID=' . $this->getUuid($dev) . '';
851
            $format_p4   = $this->getFsType($dev);
852
            $conf        .= "{$str_uid} /storage/usbdisk{$disk['id']} {$format_p4} async,rw 0 0\n";
853
            $mount_point = "/storage/usbdisk{$disk['id']}";
854
            Util::mwMkdir($mount_point);
855
        }
856
        $this->saveFstab($conf);
857
        $this->createWorkDirs();
858
        System::setupPhpLog();
859
    }
860
861
    /**
862
     * Получаем настройки диска из базы данных.
863
     *
864
     * @param string $id
865
     *
866
     * @return array
867
     */
868
    public function getDiskSettings($id = ''): array
869
    {
870
        $data = [];
871
        if ('' === $id) {
872
            $pbxSettings = StorageModel::find();
873
            if ($pbxSettings) {
0 ignored issues
show
introduced by
$pbxSettings is of type Phalcon\Mvc\Model\ResultsetInterface, thus it always evaluated to true.
Loading history...
874
                // Возвращаем данные до модификации.
875
                $data = $pbxSettings->toArray();
876
            }
877
        } else {
878
            $pbxSettings = StorageModel::findFirst("id='$id'");
879
            if ($pbxSettings !== null) {
880
                $data = $pbxSettings->toArray();
881
            }
882
        }
883
884
        return $data;
885
    }
886
887
    /**
888
     * Проверяет, существует ли диск в массиве.
889
     *
890
     * @param $disk
891
     *
892
     * @return bool
893
     */
894
    private function hddExists($disk): bool
895
    {
896
        $result = false;
897
        $uid    = $this->getUuid("{$disk}");
898
        if (file_exists("{$disk}") && $uid !== false) {
899
            $result = true;
900
        }
901
902
        return $result;
903
    }
904
905
    /**
906
     * After mount storage we will change /mountpoint/ to new $mount_point value
907
     *
908
     * @param string $mount_point
909
     *
910
     * @throws \JsonException
911
     */
912
    private function updateConfigWithNewMountPoint(string $mount_point): void
913
    {
914
        $staticSettingsFile     = '/etc/inc/mikopbx-settings.json';
915
        $staticSettingsFileOrig =  appPath('config/mikopbx-settings.json');
916
917
        $jsonString = file_get_contents($staticSettingsFileOrig);
918
        $data       = json_decode($jsonString, true, 512, JSON_THROW_ON_ERROR);
919
        foreach ($data as $rootKey => $rootEntry) {
920
            foreach ($rootEntry as $nestedKey => $entry) {
921
                if (stripos($entry, '/mountpoint') !== false) {
922
                    $data[$rootKey][$nestedKey] = str_ireplace('/mountpoint', $mount_point, $entry);
923
                }
924
            }
925
        }
926
        $newJsonString = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
927
        file_put_contents($staticSettingsFile, $newJsonString);
928
929
        // Update config variable
930
        $this->di->remove('config');
931
        $this->di->register(new ConfigProvider());
932
        $this->config = $this->di->getShared('config');
933
934
    }
935
936
    /**
937
     * Генерация файла fstab. Монтирование разделов.
938
     *
939
     * @param string $conf
940
     */
941
    public function saveFstab($conf = ''): void
942
    {
943
        $varEtcPath = $this->config->path('core.varEtcPath');
944
        // Точка монтирования доп. дисков.
945
        Util::mwMkdir('/storage');
946
        $chmodPath = Util::which('chmod');
947
        Util::mwExec("{$chmodPath} 755 /storage");
948
        if ( ! file_exists($varEtcPath . '/cfdevice')) {
949
            return;
950
        }
951
        $fstab     = '';
952
        $file_data = file_get_contents($varEtcPath . '/cfdevice');
953
        $cf_disk   = trim($file_data);
954
        if ('' == $cf_disk) {
955
            return;
956
        }
957
        // $part1 	 = (strpos($cf_disk, "mmcblk") !== false)?"{$cf_disk}p1":"{$cf_disk}1"; // Boot
958
        $part2 = (strpos($cf_disk, 'mmcblk') !== false) ? "{$cf_disk}p2" : "{$cf_disk}2"; // Offload
959
        $part3 = (strpos($cf_disk, 'mmcblk') !== false) ? "{$cf_disk}p3" : "{$cf_disk}3"; // Conf
960
961
962
        $uid_part2 = 'UUID=' . $this->getUuid("/dev/{$part2}") . '';
963
        $format_p2 = $this->getFsType($part2);
964
        $uid_part3 = 'UUID=' . $this->getUuid("/dev/{$part3}") . '';
965
        $format_p3 = $this->getFsType($part3);
966
967
        // $fstab .= "/dev/{$part1} /cf msdos ro 1 1\n"; // НЕ МОНТИРУЕМ!
968
        $fstab .= "{$uid_part2} /offload {$format_p2} ro 0 0\n";
969
        $fstab .= "{$uid_part3} /cf {$format_p3} rw 1 1\n";
970
        $fstab .= $conf;
971
972
        file_put_contents("/etc/fstab", $fstab);
973
        // Дублируем для работы vmtoolsd.
974
        file_put_contents("/etc/mtab", $fstab);
975
        $mountPath = Util::which('mount');
976
        Util::mwExec("{$mountPath} -a 2> /dev/null");
977
        Util::addRegularWWWRights('/cf');
978
    }
979
980
    /**
981
     * Create system folders
982
     *
983
     * @return void
984
     */
985
    private function createWorkDirs(): void{
986
        $path = '';
987
        $mountPath = Util::which('mount');
988
        Util::mwExec("{$mountPath} -o remount,rw /offload 2> /dev/null");
989
990
        $isLiveCd = file_exists('/offload/livecd');
991
        // Create dirs
992
        $arrConfig = $this->config->toArray();
993
        foreach ($arrConfig as $rootEntry) {
994
            foreach ($rootEntry as $key => $entry) {
995
                if (stripos($key, 'path') === false && stripos($key, 'dir') === false) {
996
                    continue;
997
                }
998
                if (file_exists($entry)) {
999
                    continue;
1000
                }
1001
                if($isLiveCd && strpos($entry, '/offload/') === 0){
1002
                    continue;
1003
                }
1004
                $path .= " $entry";
1005
            }
1006
        }
1007
1008
        if (!empty($path)) {
1009
            Util::mwMkdir($path);
1010
        }
1011
1012
        $downloadCacheDir = appPath('sites/pbxcore/files/cache');
1013
        Util::mwMkdir($downloadCacheDir);
1014
        Util::createUpdateSymlink($this->config->path('core.downloadCachePath'), $downloadCacheDir);
1015
1016
        $jsCacheDir = appPath('sites/admin-cabinet/assets/js/cache');
1017
        Util::createUpdateSymlink($this->config->path('adminApplication.cacheDir') . '/js', $jsCacheDir);
1018
1019
        $cssCacheDir = appPath('sites/admin-cabinet/assets/css/cache');
1020
        Util::createUpdateSymlink($this->config->path('adminApplication.cacheDir') . '/css', $cssCacheDir);
1021
1022
        $imgCacheDir = appPath('sites/admin-cabinet/assets/img/cache');
1023
        Util::createUpdateSymlink($this->config->path('adminApplication.cacheDir') . '/img', $imgCacheDir);
1024
1025
        Util::createUpdateSymlink($this->config->path('core.phpSessionPath'), '/var/lib/php/session');
1026
        Util::createUpdateSymlink($this->config->path('core.uploadPath'), '/ultmp');
1027
1028
        $filePath = appPath('src/Core/Asterisk/Configs/lua/extensions.lua');
1029
        Util::createUpdateSymlink($filePath, '/etc/asterisk/extensions.lua');
1030
1031
        // Create symlinks to AGI-BIN
1032
        $agiBinDir = $this->config->path('asterisk.astagidir');
1033
        Util::mwMkdir($agiBinDir);
1034
1035
        $roAgiBinFolder = appPath('src/Core/Asterisk/agi-bin');
1036
        $files = glob("$roAgiBinFolder/*.{php}", GLOB_BRACE);
1037
        foreach ($files as $file) {
1038
            $fileInfo = pathinfo($file);
1039
            $newFilename = "{$agiBinDir}/{$fileInfo['filename']}.{$fileInfo['extension']}";
1040
            Util::createUpdateSymlink($file, $newFilename);
1041
        }
1042
        $this->clearCacheFiles();
1043
        $this->applyFolderRights();
1044
    }
1045
1046
    /**
1047
     * Fix permissions for Folder and Files
1048
     */
1049
    private function applyFolderRights(): void
1050
    {
1051
        // Add Rights to the WWW dirs plus some core dirs
1052
        $www_dirs  = [];
1053
        $arrConfig = $this->config->adminApplication->toArray();
1054
        foreach ($arrConfig as $key => $entry) {
1055
            if (stripos($key, 'path') === false
1056
                && stripos($key, 'dir') === false
1057
            ) {
1058
                continue;
1059
            }
1060
            $www_dirs[] = $entry;
1061
        }
1062
1063
        $www_dirs[] = $this->config->path('database.logsPath');
1064
        $www_dirs[] = $this->config->path('core.phpSessionPath');
1065
        $www_dirs[] = $this->config->path('core.tempPath');
1066
        $www_dirs[] = $this->config->path('core.uploadPath');
1067
        $www_dirs[] = '/etc/version';
1068
        $www_dirs[] = appPath('/');
1069
1070
        // Add read rights
1071
        Util::addRegularWWWRights(implode(' ', $www_dirs));
1072
1073
        // Add executable rights
1074
        $exec_dirs[] = appPath('src/Core/Asterisk/agi-bin');
0 ignored issues
show
Comprehensibility Best Practice introduced by
$exec_dirs was never initialized. Although not strictly required by PHP, it is generally a good practice to add $exec_dirs = array(); before regardless.
Loading history...
1075
        $exec_dirs[] = appPath('src/Core/Rc');
1076
        Util::addExecutableRights(implode(' ', $exec_dirs));
1077
1078
        $mountPath = Util::which('mount');
1079
        Util::mwExec("{$mountPath} -o remount,ro /offload 2> /dev/null");
1080
    }
1081
1082
    /**
1083
     * Сохраняем новые данные диска.
1084
     *
1085
     * @param        $data
1086
     * @param string $id
1087
     */
1088
    public function saveDiskSettings($data, $id = '1'): void
1089
    {
1090
        if ( ! is_array($data)) {
1091
            return;
1092
        }
1093
        $disk_data = $this->getDiskSettings($id);
1094
        if (count($disk_data) === 0) {
1095
            $uniqid           = strtoupper('STORAGE-DISK-' . md5(time()));
1096
            $storage_settings = new StorageModel();
1097
            foreach ($data as $key => $val) {
1098
                $storage_settings->writeAttribute($key, $val);
1099
            }
1100
            $storage_settings->writeAttribute('uniqid', $uniqid);
1101
            $storage_settings->save();
1102
        } else {
1103
            $storage_settings = StorageModel::findFirst("id = '$id'");
1104
            if ($storage_settings === null){
1105
                return;
1106
            }
1107
            foreach ($data as $key => $value) {
1108
                $storage_settings->writeAttribute($key, $value);
1109
            }
1110
            $storage_settings->save();
1111
        }
1112
    }
1113
1114
    /**
1115
     * Clear cache folders from old and orphaned files
1116
     */
1117
    public function clearCacheFiles()
1118
    {
1119
        $cacheDirs = [];
1120
        $cacheDirs[] = $this->config->path('core.uploadPath');
1121
        $cacheDirs[] = $this->config->path('core.downloadCachePath');
1122
        $cacheDirs[] = $this->config->path('adminApplication.cacheDir').'/js';
1123
        $cacheDirs[] = $this->config->path('adminApplication.cacheDir').'/css';
1124
        $cacheDirs[] = $this->config->path('adminApplication.cacheDir').'/img';
1125
        $cacheDirs[] = $this->config->path('adminApplication.cacheDir').'/volt';
1126
        $rmPath = Util::which('rm');
1127
        foreach ($cacheDirs as $cacheDir){
1128
            if (!empty($cacheDir)){
1129
                Util::mwExec("{$rmPath} -rf {$cacheDir}/*");
1130
            }
1131
        }
1132
    }
1133
1134
    /**
1135
     * Clear cache folders from PHP sessions files
1136
     */
1137
    public static function clearSessionsFiles()
1138
    {
1139
        $di     = Di::getDefault();
1140
        if ($di===null){
1141
            return;
1142
        }
1143
        $config = $di->getShared('config');
1144
        $phpSessionPath = $config->path('core.phpSessionPath');
1145
        if (!empty($phpSessionPath)){
1146
            $rmPath = Util::which('rm');
1147
            Util::mwExec("{$rmPath} -rf {$phpSessionPath}/*");
1148
        }
1149
    }
1150
1151
}