Passed
Push — develop ( 3c5942...636dd4 )
by Nikolay
04:57
created

Storage::mountDisk()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 22
rs 9.7998
c 0
b 0
f 0
cc 4
nc 4
nop 3
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, 4 2020
7
 */
8
9
namespace MikoPBX\Core\System;
10
11
use MikoPBX\Core\Workers\WorkerRemoveOldRecords;
12
use MikoPBX\Common\Models\{PbxExtensionModules, 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
94
        $mountPath = Util::which('mount');
95
        $umountPath = Util::which('umount');
96
        $rmPath = Util::which('rm');
97
98
        Util::mwExec("{$mountPath} -t {$format} {$uid_part} {$tmp_dir}", $out);
99
        if (is_dir("{$tmp_dir}/mikopbx") && trim(implode('', $out)) === '') {
100
            // $out - пустая строка, ошибок нет
101
            // присутствует каталог mikopbx.
102
            $result = true;
103
        }
104
        if (self::isStorageDiskMounted($device)) {
105
            Util::mwExec("{$umountPath} {$device}");
106
        }
107
108
        if ( ! self::isStorageDiskMounted($device)) {
109
            Util::mwExec("{$rmPath} -rf '{$tmp_dir}'");
110
        }
111
112
        return $result;
113
    }
114
115
    /**
116
     * Получение идентификатора устройства.
117
     *
118
     * @param $device
119
     *
120
     * @return string
121
     */
122
    public function getUuid($device): string
123
    {
124
        if (strlen($device) == 0) {
125
            return '';
126
        }
127
        $blkidPath = Util::which('blkid');
128
        $busyboxPath = Util::which('busybox');
129
        $sedPath = Util::which('sed');
130
        $grepPath = Util::which('grep');
131
        $awkPath = Util::which('awk');
132
        $headPath = Util::which('head');
133
134
        $res = Util::mwExec(
135
            "{$blkidPath} -ofull {$device} | {$busyboxPath} {$sedPath} -r 's/[[:alnum:]]+=/\\n&/g' | {$busyboxPath} {$grepPath} \"^UUID\" | {$busyboxPath} {$awkPath} -F \"\\\"\" '{print $2}' | {$headPath} -n 1",
136
            $output
137
        );
138
        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...
139
            $result = $output[0];
140
        } else {
141
            $result = '';
142
        }
143
144
        return $result;
145
    }
146
147
    /**
148
     * Возвращает тип файловой системы блочного устройства.
149
     *
150
     * @param $device
151
     *
152
     * @return string
153
     */
154
    public function getFsType($device): string
155
    {
156
        $blkidPath = Util::which('blkid');
157
        $busyboxPath = Util::which('busybox');
158
        $sedPath = Util::which('sed');
159
        $grepPath = Util::which('grep');
160
        $awkPath = Util::which('awk');
161
162
        $device = str_replace('/dev/', '', $device);
163
        $out    = [];
164
        Util::mwExec(
165
            "{$blkidPath} -ofull /dev/{$device} | {$busyboxPath} {$sedPath} -r 's/[[:alnum:]]+=/\\n&/g' | {$busyboxPath} {$grepPath} \"^TYPE=\" | {$busyboxPath} {$awkPath} -F \"\\\"\" '{print $2}'",
166
            $out
167
        );
168
        $format = implode('', $out);
169
        if ($format == 'msdosvfat') {
170
            $format = 'msdos';
171
        }
172
173
        return $format;
174
    }
175
176
    /**
177
     * Проверка, смонтирован ли диск - хранилище.
178
     *
179
     * @param string $filter
180
     * @param string $mount_dir
181
     *
182
     * @return bool
183
     */
184
    public static function isStorageDiskMounted($filter = '', &$mount_dir = ''): bool
185
    {
186
        if (Util::isSystemctl() && file_exists('/storage/usbdisk1/')) {
187
            $mount_dir = '/storage/usbdisk1/';
188
189
            return true;
190
        }
191
        if ('' === $filter) {
192
            $di     = Di::getDefault();
193
            if ($di !== null){
194
                $varEtcPath = $di->getConfig()->path('core.varEtcPath');
195
            } else {
196
                $varEtcPath = '/var/etc';
197
            }
198
199
            $filename   = "{$varEtcPath}/storage_device";
200
            if (file_exists($filename)) {
201
                $filter = file_get_contents($filename);
202
            } else {
203
                $filter = 'usbdisk1';
204
            }
205
        }
206
        $filter = escapeshellarg($filter);
207
208
        $out = [];
209
        $grepPath = Util::which('grep');
210
        $mountPath = Util::which('mount');
211
        $awkPath = Util::which('awk');
212
        Util::mwExec("{$mountPath} | {$grepPath} {$filter} | {$awkPath} '{print $3}'", $out);
213
        $mount_dir = trim(implode('', $out));
214
215
        return ($mount_dir !== '');
216
    }
217
218
    /**
219
     * Монитирование каталога с удаленного сервера SFTP.
220
     *
221
     * @param        $host
222
     * @param int    $port
223
     * @param string $user
224
     * @param string $pass
225
     * @param string $remout_dir
226
     * @param string $local_dir
227
     *
228
     * @return bool
229
     */
230
    public static function mountSftpDisk($host, $port, $user, $pass, $remout_dir, $local_dir): bool
231
    {
232
        Util::mwMkdir($local_dir);
233
234
        $out     = [];
235
        $timeoutPath = Util::which('timeout');
236
        $sshfsPath = Util::which('sshfs');
237
238
        $command = "{$timeoutPath} -t 3 {$sshfsPath} -p {$port} -o nonempty -o password_stdin -o 'StrictHostKeyChecking=no' " .
239
            "{$user}@{$host}:{$remout_dir} {$local_dir} << EOF\n" .
240
            "{$pass}\n" .
241
            "EOF\n";
242
        // file_put_contents('/tmp/sshfs_'.$host, $command);
243
        Util::mwExec($command, $out);
244
        $response = trim(implode('', $out));
245
        if ('Terminated' == $response) {
246
            // Удаленный сервер не ответил / или не корректно указан пароль.
247
            unset($response);
248
        }
249
250
        return self::isStorageDiskMounted("$local_dir ");
251
    }
252
253
    /**
254
     * Монитирование каталога с удаленного сервера FTP.
255
     *
256
     * @param        $host
257
     * @param        $port
258
     * @param        $user
259
     * @param        $pass
260
     * @param string $remout_dir
261
     * @param        $local_dir
262
     *
263
     * @return bool
264
     */
265
    public static function mountFtp($host, $port, $user, $pass, $remout_dir, $local_dir): bool
266
    {
267
        Util::mwMkdir($local_dir);
268
        $out = [];
269
270
        // Собираем строку подключения к ftp.
271
        $auth_line = '';
272
        if ( ! empty($user)) {
273
            $auth_line .= 'user="' . $user;
274
            if ( ! empty($pass)) {
275
                $auth_line .= ":{$pass}";
276
            }
277
            $auth_line .= '",';
278
        }
279
280
        $connect_line = 'ftp://' . $host;
281
        if ( ! empty($port)) {
282
            $connect_line .= ":{$port}";
283
        }
284
        if ( ! empty($remout_dir)) {
285
            $connect_line .= "$remout_dir";
286
        }
287
288
        $timeoutPath = Util::which('timeout');
289
        $curlftpfsPath = Util::which('curlftpfs');
290
        $command = "{$timeoutPath} -t 3 {$curlftpfsPath}  -o allow_other -o {$auth_line}fsname={$host} {$connect_line} {$local_dir}";
291
        Util::mwExec($command, $out);
292
        $response = trim(implode('', $out));
293
        if ('Terminated' === $response) {
294
            // Удаленный сервер не ответил / или не корректно указан пароль.
295
            unset($response);
296
        }
297
298
        return self::isStorageDiskMounted("$local_dir ");
299
    }
300
301
    /**
302
     * Запускает процесс форматирования диска.
303
     *
304
     * @param $dev
305
     *
306
     * @return array|bool
307
     */
308
    public static function mkfs_disk($dev)
309
    {
310
        if ( ! file_exists($dev)) {
311
            $dev = "/dev/{$dev}";
312
        }
313
        if ( ! file_exists($dev)) {
314
            return false;
315
        }
316
        $dir = '';
317
        self::isStorageDiskMounted("$dev", $dir);
318
319
        if (empty($dir) || self::umountDisk($dir)) {
320
            // Диск размонтирован.
321
            $st = new Storage();
322
            // Будет запущен процесс:
323
            $st->formatDiskLocal($dev, true);
324
            sleep(1);
325
326
            return (self::statusMkfs($dev) == 'inprogress');
327
        } else {
328
            // Ошибка размонтирования диска.
329
            return false;
330
        }
331
    }
332
333
    /**
334
     * Размонтирует диск. Удаляет каталог в случае успеха.
335
     *
336
     * @param $dir
337
     *
338
     * @return bool
339
     */
340
    public static function umountDisk($dir): bool
341
    {
342
        $umountPath = Util::which('umount');
343
        $rmPath = Util::which('rm');
344
        if (self::isStorageDiskMounted($dir)) {
345
            Util::mwExec("/etc/rc/shell_functions.sh 'killprocesses' '$dir' -TERM 0");
346
            Util::mwExec("{$umountPath} {$dir}");
347
        }
348
        $result = ! self::isStorageDiskMounted($dir);
349
        if ($result && file_exists($dir)) {
350
            // Если диск не смонтирован, то удаляем каталог.
351
            Util::mwExec("{$rmPath} -rf '{$dir}'");
352
        }
353
354
        return $result;
355
    }
356
357
    /**
358
     * Разметка диска.
359
     *
360
     * @param string $device
361
     * @param bool   $bg
362
     *
363
     * @return mixed
364
     */
365
    public function formatDiskLocal($device, $bg = false)
366
    {
367
        openlog("storage", LOG_NDELAY, LOG_DAEMON);
368
        $echoPath = Util::which('echo');
369
        $fdiskPath = Util::which('fdisk');
370
        // overwrite with fresh DOS partition table
371
        Util::mwExec(
372
            "{$echoPath} \"o\n" .
373
            // create new
374
            "n\n" .
375
            // primary partition
376
            "p\n" .
377
            // number 1
378
            "1\n" .
379
            // from the beginning
380
            "\n" .
381
            // to the end
382
            "\n" .
383
            // change type
384
            /*
385
            "t\n" .
386
            // to FAT32
387
            "b\n" .
388
            // set active
389
            "a\n" .
390
            // partition 1
391
            "1\n" .
392
            */
393
            // and write changes
394
            "w\n" .
395
            "\" | {$fdiskPath} " . $device,
396
            $out,
397
            $retval
398
        );
399
        syslog(LOG_NOTICE, "fdisk returned " . $retval);
400
        closelog();
401
402
        if (false == $bg) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
403
            sleep(1);
404
        }
405
406
        return $this->formatDiskLocalPart2($device, $bg);
407
    }
408
409
    /**
410
     * Форматирование диска.
411
     *
412
     * @param string $device
413
     * @param bool   $bg
414
     *
415
     * @return mixed
416
     */
417
    private function formatDiskLocalPart2($device, $bg = false)
418
    {
419
        if (is_numeric(substr($device, -1))) {
420
            $device_id = "";
421
        } else {
422
            $device_id = "1";
423
        }
424
        $format = 'ext4';
425
        $mkfsPath = Util::which("mkfs.{$format}");
426
        $cmd    = "{$mkfsPath} {$device}{$device_id}";
427
        if ($bg == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
428
            openlog("storage_format_disk", LOG_NDELAY, LOG_DAEMON);
429
            Util::mwExec("{$cmd} 2>&1", $out, $retval);
430
            syslog(LOG_NOTICE, "{$mkfsPath} returned {$retval}");
431
            closelog();
432
        } else {
433
            usleep(200000);
434
            Util::mwExecBg("$cmd");
435
            $retval = true;
436
        }
437
438
        return $retval;
439
    }
440
441
    /**
442
     * Возвращает текущий статус форматирования диска.
443
     *
444
     * @param $dev
445
     *
446
     * @return string
447
     */
448
    public static function statusMkfs($dev): string
449
    {
450
        if ( ! file_exists($dev)) {
451
            $dev = "/dev/{$dev}";
452
        }
453
        $out = [];
454
        Util::mwExec("ps -A -f | grep {$dev} | grep mkfs | grep -v grep", $out);
455
        $mount_dir = trim(implode('', $out));
456
457
        return ($mount_dir == '') ? 'ended' : 'inprogress';
458
    }
459
460
    /**
461
     * Проверка свободного места на дисках. Уведомление в случае проблем.
462
     */
463
    public function checkFreeSpace(): void
464
    {
465
        $util    = new Util();
466
        $hdd     = $this->getAllHdd(true);
467
        // Создание больщого файла для тестов.
468
        // head -c 1500MB /dev/urandom > /storage/usbdisk1/big_file.mp3
469
        foreach ($hdd as $disk) {
470
            if ($disk['sys_disk'] === true && ! self::isStorageDiskMounted("{$disk['id']}4")) {
471
                // Это системный диск (4ый раздел). Он не смонтирован.
472
                continue;
473
            }
474
475
            $free       = ($disk['free_space'] / $disk['size'] * 100);
476
            $need_alert = false;
477
            $test_alert = '';
478
            if ($free < 5) {
479
                $need_alert = true;
480
                $test_alert = "The {$disk['id']} has less than 5% of free space available.";
481
            }
482
483
            if ($disk['free_space'] < 500) {
484
                $need_alert = true;
485
                $test_alert = "The {$disk['id']} has less than 500MB of free space available.";
486
            }
487
488
            if ($disk['free_space'] < 100) {
489
                $need_alert  = true;
490
                $test_alert  = "The {$disk['id']} has less than 100MB of free space available. Old call records will be deleted.";
491
                Util::restartPHPWorker(WorkerRemoveOldRecords::class);
492
            }
493
494
            if ( ! $need_alert) {
495
                continue;
496
            }
497
498
            Util::sysLogMsg("STORAGE", $test_alert);
499
            $data = [
500
                'Device     - ' => "/dev/{$disk['id']}",
501
                'Directoire - ' => "{$disk['mounted']}",
502
                'Desciption - ' => $test_alert,
503
            ];
504
            // Добавляем задачу на уведомление.
505
            $util->addJobToBeanstalk('WorkerNotifyError_storage', $data);
506
        }
507
    }
508
509
    /**
510
     * Возвращает все подключенные HDD.
511
     *
512
     * @param bool $mounted_only
513
     *
514
     * @return array
515
     */
516
    public function getAllHdd($mounted_only = false): array
517
    {
518
        $res_disks = [];
519
520
        if (Util::isSystemctl()) {
521
            $out = [];
522
            $grepPath = Util::which('grep');
523
            $dfPath = Util::which('df');
524
            $awkPath = Util::which('awk');
525
            Util::mwExec(
526
                "{$dfPath} -k /storage/usbdisk1 | {$awkPath}  '{ print $1 \"|\" $3 \"|\" $4} ' | {$grepPath} -v 'Available'",
527
                $out
528
            );
529
            $disk_data = explode('|', implode(" ", $out));
530
            if (count($disk_data) === 3) {
531
                $m_size      = round(($disk_data[1] + $disk_data[2]) / 1024, 1);
532
                $res_disks[] = [
533
                    'id'         => $disk_data[0],
534
                    'size'       => "" . $m_size,
535
                    'size_text'  => "" . $m_size . " Mb",
536
                    'vendor'     => 'Debian',
537
                    'mounted'    => '/storage/usbdisk1',
538
                    'free_space' => round($disk_data[2] / 1024, 1),
539
                    'partitions' => [],
540
                    'sys_disk'   => true,
541
                ];
542
            }
543
544
            return $res_disks;
545
        }
546
547
        $cd_disks = $this->cdromGetDevices();
548
        $cd_disks = array_unique($cd_disks);
549
550
        // TODO Получение данных о дисках в формате JSON:
551
        // lsblk -J -b -o VENDOR,MODEL,SERIAL,LABEL,TYPE,FSTYPE,MOUNTPOINT,SUBSYSTEMS,NAME,UUID
552
        $disks = $this->diskGetDevices();
553
        $disks = array_unique($disks);
554
555
        $cf_disk    = '';
556
        $varEtcPath = $this->config->path('core.varEtcPath');
557
        if (file_exists($varEtcPath . '/cfdevice')) {
558
            $cf_disk = trim(file_get_contents($varEtcPath . '/cfdevice'));
559
        }
560
561
        foreach ($disks as $disk) {
562
            if (in_array($disk, $cd_disks)) {
563
                // Это CD-ROM.
564
                continue;
565
            }
566
            unset($temp_vendor, $temp_size, $original_size);
567
            $mounted = self::diskIsMounted("{$disk}");
568
            if ($mounted_only === true && $mounted === false) {
569
                continue;
570
            }
571
            $sys_disk = ($cf_disk == $disk);
572
573
            $mb_size = 0;
574
            if (is_file("/sys/block/" . $disk . "/size")) {
575
                $original_size = trim(file_get_contents("/sys/block/" . $disk . "/size"));
576
                $original_size = ($original_size * 512 / 1024 / 1024);
577
                $mb_size       = $original_size;
578
            }
579
            if ($mb_size > 100) {
580
                $temp_size   = sprintf("%.0f MB", $mb_size);
581
                $temp_vendor = $this->getVendorDisk($disk);
582
                $free_space  = $this->getFreeSpace($disk);
583
584
                $arr_disk_info = $this->determineFormatFs($disk);
585
                if (count($arr_disk_info) > 0) {
586
                    $used = 0;
587
                    foreach ($arr_disk_info as $disk_info) {
588
                        $used += $disk_info['used_space'];
589
                    }
590
                    if ($used > 0) {
591
                        $free_space = $mb_size - $used;
592
                    }
593
                }
594
595
                $res_disks[] = [
596
                    'id'         => $disk,
597
                    'size'       => $mb_size,
598
                    'size_text'  => $temp_size,
599
                    'vendor'     => $temp_vendor,
600
                    'mounted'    => $mounted,
601
                    'free_space' => $free_space,
602
                    'partitions' => $arr_disk_info,
603
                    'sys_disk'   => $sys_disk,
604
                ];
605
            }
606
        }
607
608
        return $res_disks;
609
    }
610
611
    /**
612
     * Получение массива подключенныйх cdrom.
613
     *
614
     * @return array
615
     */
616
    private function cdromGetDevices(): array
617
    {
618
        $grepPath = Util::which('grep');
619
        $sysctlPath = Util::which('sysctl');
620
        $busyboxPath = Util::which('busybox');
621
        $cutPath = Util::which('cut');
622
623
        return explode(
624
            " ",
625
            trim(exec("{$sysctlPath} -n dev.cdrom.info | {$busyboxPath} {$grepPath} 'drive name' | {$busyboxPath} {$cutPath} -f 3"))
626
        );
627
    }
628
629
    /**
630
     * Получение массива подключенныйх HDD.
631
     *
632
     * @return array
633
     */
634
    private function diskGetDevices(): array
635
    {
636
        //  TODO // Переписать через использование lsblk.
637
        $grepPath = Util::which('grep');
638
        $lsPath = Util::which('ls');
639
        $trPath = Util::which('tr');
640
        return explode(" ", trim(exec("{$lsPath} /dev | {$grepPath} '^[a-z]d[a-z]' | {$trPath} \"\n\" \" \"")));
641
    }
642
643
    /**
644
     * Проверка, смонтирован ли диск.
645
     *
646
     * @param $disk
647
     * @param $filter
648
     *
649
     * @return string|bool
650
     */
651
    public static function diskIsMounted($disk, $filter = '/dev/')
652
    {
653
        $out = [];
654
        Util::mwExec("mount | grep '{$filter}{$disk}'", $out);
655
        if (count($out) > 0) {
656
            $res_out = end($out);
657
        } else {
658
            $res_out = implode('', $out);
659
        }
660
        $data = explode(' ', trim($res_out));
661
662
        return (count($data) > 2) ? $data[2] : false;
663
    }
664
665
    /**
666
     * Получение сведений по диску.
667
     *
668
     * @param $disk
669
     *
670
     * @return string
671
     */
672
    private function getVendorDisk($disk): string
673
    {
674
        $temp_vendor = [];
675
        if (is_file("/sys/block/" . $disk . "/device/vendor")) {
676
            $data = trim(file_get_contents("/sys/block/" . $disk . "/device/vendor"));
677
            if ($data != '') {
678
                $temp_vendor[] = trim(str_replace(',', ' ', $data));
679
            }
680
        }
681
        if (is_file("/sys/block/" . $disk . "/device/model")) {
682
            $data = trim(file_get_contents("/sys/block/" . $disk . "/device/model"));
683
            if ($data != '') {
684
                $temp_vendor[] = trim(str_replace(',', ' ', $data));
685
            }
686
        }
687
        if (count($temp_vendor) == 0) {
688
            $temp_vendor[] = $disk;
689
        }
690
        if (is_file("/sys/block/" . $disk . "/device/type")) {
691
            $data = trim(file_get_contents("/sys/block/" . $disk . "/device/type"));
692
            if ($data != '') {
693
                $temp_vendor[] = trim(str_replace(',', ' ', $data));
694
            }
695
        }
696
697
        return implode(', ', $temp_vendor);
698
    }
699
700
    /**
701
     * Получаем свободное место на диске в Mb.
702
     *
703
     * @param $hdd
704
     *
705
     * @return mixed
706
     */
707
    public function getFreeSpace($hdd)
708
    {
709
        $out = [];
710
        $hdd = escapeshellarg($hdd);
711
        Util::mwExec("df -m | grep {$hdd} | awk '{print $4}'", $out);
712
        $result = 0;
713
        foreach ($out as $res) {
714
            if ( ! is_numeric($res)) {
715
                continue;
716
            }
717
            $result += (1 * $res);
718
        }
719
720
        return $result;
721
    }
722
723
    /**
724
     * Определить формат файловой системы и размер дисков.
725
     *
726
     * @param $device
727
     *
728
     * @return array|bool
729
     */
730
    public function determineFormatFs($device)
731
    {
732
        $grepPath = Util::which('grep');
733
        $lsPath = Util::which('ls');
734
        $trPath = Util::which('tr');
735
        $allow_formats = ['ext2', 'ext4', 'fat', 'ntfs', 'msdos'];
736
        $device        = str_replace('/dev/', '', $device);
737
        $devices       = explode(" ", trim(exec("{$lsPath} /dev | {$grepPath} '{$device}' | {$trPath} \"\n\" \" \"")));
738
739
        $result_data = [];
740
        foreach ($devices as $dev) {
741
            if (empty($dev) || (count($devices) > 1 && $device == $dev) || is_dir("/sys/block/{$dev}")) {
742
                continue;
743
            }
744
            $mb_size        = 0;
745
            $path_size_info = '';
746
            $tmp_path       = "/sys/block/{$device}/{$dev}/size";
747
            if (file_exists($tmp_path)) {
748
                $path_size_info = $tmp_path;
749
            }
750
            if (empty($path_size_info)) {
751
                $tmp_path = "/sys/block/" . substr($dev, 0, 3) . "/{$dev}/size";
752
                if (file_exists($tmp_path)) {
753
                    $path_size_info = $tmp_path;
754
                }
755
            }
756
757
            if ( ! empty($path_size_info)) {
758
                $original_size = trim(file_get_contents($path_size_info));
759
                $original_size = ($original_size * 512 / 1024 / 1024);
760
                $mb_size       = $original_size;
761
            }
762
763
            $tmp_dir = "/tmp/{$dev}_" . time();
764
            $out     = [];
765
766
            $fs           = null;
767
            $need_unmount = false;
768
            $mount_dir    = '';
769
            if (self::isStorageDiskMounted("/dev/{$dev} ", $mount_dir)) {
770
                Util::mwExec("mount | grep '/dev/{$dev}' | awk '{print $5}'", $out);
771
                $fs         = trim(implode("", $out));
772
                $fs         = ($fs == 'fuseblk') ? 'ntfs' : $fs;
773
                $free_space = $this->getFreeSpace("/dev/{$dev} ");
774
                $used_space = $mb_size - $free_space;
775
            } else {
776
                $format = $this->getFsType($device);
777
                if (in_array($format, $allow_formats)) {
778
                    $fs = $format;
779
                }
780
                self::mountDisk($dev, $format, $tmp_dir);
781
782
                $need_unmount = true;
783
                $used_space   = Util::getSizeOfFile("$tmp_dir");
784
            }
785
            $result_data[] = [
786
                "dev"        => $dev,
787
                'size'       => round($mb_size, 2),
788
                "used_space" => round($used_space, 2),
789
                "free_space" => round($mb_size - $used_space, 2),
790
                "uuid"       => $this->getUuid("/dev/{$dev} "),
791
                "fs"         => $fs,
792
            ];
793
            if ($need_unmount) {
794
                self::umountDisk($tmp_dir);
795
            }
796
        }
797
798
        return $result_data;
799
    }
800
801
    /**
802
     * Монтирует диск в указанный каталог.
803
     *
804
     * @param $dev
805
     * @param $format
806
     * @param $dir
807
     *
808
     * @return bool
809
     */
810
    public static function mountDisk($dev, $format, $dir): bool
811
    {
812
        if (self::isStorageDiskMounted("/dev/{$dev} ")) {
813
            return true;
814
        }
815
        Util::mwMkdir($dir);
816
817
        if ( ! file_exists($dir)) {
818
            Util::sysLogMsg('Storage', "Unable mount $dev $format to $dir. Unable create dir.");
819
820
            return false;
821
        }
822
        $dev = str_replace('/dev/', '', $dev);
823
        if ('ntfs' == $format) {
824
            Util::mwExec("mount.ntfs-3g /dev/{$dev} {$dir}", $out);
825
        } else {
826
            $storage  = new Storage();
827
            $uid_part = 'UUID=' . $storage->getUuid("/dev/{$dev}") . '';
828
            Util::mwExec("mount -t {$format} {$uid_part} {$dir}", $out);
829
        }
830
831
        return self::isStorageDiskMounted("/dev/{$dev} ");
832
    }
833
834
    /**
835
     * Монтирование разделов диска с базой данных настроек.
836
     */
837
    public function configure(): void
838
    {
839
        $is_mounted       = false;
840
        $cf_disk          = '';
841
        $varEtcPath       = $this->config->path('core.varEtcPath');
842
        $storage_dev_file = "{$varEtcPath}/storage_device";
843
        if (file_exists($storage_dev_file)) {
844
            unlink($storage_dev_file);
845
        }
846
847
        if (file_exists($varEtcPath . '/cfdevice')) {
848
            $cf_disk = trim(file_get_contents($varEtcPath . '/cfdevice'));
849
        }
850
851
        $disks = $this->getDiskSettings();
852
        $conf  = '';
853
        foreach ($disks as $disk) {
854
            clearstatcache();
855
            if ($disk['device'] !== "/dev/{$cf_disk}") {
856
                // Если это обычный диск, то раздел 1.
857
                $dev = "{$disk['device']}1";
858
            } else {
859
                // Если это системный диск, то пытаемся подключить раздел 4.
860
                $dev = "{$disk['device']}4";
861
            }
862
            if ( ! $this->hddExists($dev)) {
863
                // Диск не существует.
864
                continue;
865
            }
866
            if ($disk['media'] === '1' || ! file_exists($storage_dev_file)) {
867
                file_put_contents($storage_dev_file, "/storage/usbdisk{$disk['id']}");
868
                $this->updateConfigWithNewMountPoint("/storage/usbdisk{$disk['id']}");
869
            }
870
871
            $str_uid     = 'UUID=' . $this->getUuid($dev) . '';
872
            $format_p4   = $this->getFsType($dev);
873
            $conf        .= "{$str_uid} /storage/usbdisk{$disk['id']} {$format_p4} async,rw 0 0\n";
874
            $is_mounted  = self::isStorageDiskMounted("/storage/usbdisk{$disk['id']}");
875
            $mount_point = "/storage/usbdisk{$disk['id']}";
876
            Util::mwMkdir($mount_point);
877
        }
878
        $this->saveFstab($conf);
879
        $this->createWorkDirs();
880
        System::setupPhpLog();
881
882
        if ($is_mounted) {
883
            $this->deleteOldModules();
884
        }
885
    }
886
887
    /**
888
     * Получаем настройки диска из базы данных.
889
     *
890
     * @param string $id
891
     *
892
     * @return array
893
     */
894
    public function getDiskSettings($id = ''): array
895
    {
896
        $data = [];
897
        if ('' === $id) {
898
            $pbxSettings = StorageModel::find();
899
            if ($pbxSettings) {
0 ignored issues
show
introduced by
$pbxSettings is of type Phalcon\Mvc\Model\ResultsetInterface, thus it always evaluated to true.
Loading history...
900
                // Возвращаем данные до модификации.
901
                $data = $pbxSettings->toArray();
902
            }
903
        } else {
904
            $pbxSettings = StorageModel::findFirst("id='$id'");
905
            if ($pbxSettings !== null) {
906
                $data = $pbxSettings->toArray();
907
            }
908
        }
909
910
        return $data;
911
    }
912
913
    /**
914
     * Проверяет, существует ли диск в массиве.
915
     *
916
     * @param $disk
917
     *
918
     * @return bool
919
     */
920
    private function hddExists($disk): bool
921
    {
922
        $result = false;
923
        $uid    = $this->getUuid("{$disk}");
924
        if (file_exists("{$disk}") && $uid !== false) {
925
            $result = true;
926
        }
927
928
        return $result;
929
    }
930
931
    /**
932
     * After mount storage we will change /mountpoint/ to new $mount_point value
933
     *
934
     * @param string $mount_point
935
     *
936
     * @throws \JsonException
937
     */
938
    private function updateConfigWithNewMountPoint(string $mount_point): void
939
    {
940
        $staticSettingsFile     = '/etc/inc/mikopbx-settings.json';
941
        $staticSettingsFileOrig =  appPath('config/mikopbx-settings.json');
942
943
        $jsonString = file_get_contents($staticSettingsFileOrig);
944
        $data       = json_decode($jsonString, true, 512, JSON_THROW_ON_ERROR);
945
        foreach ($data as $rootKey => $rootEntry) {
946
            foreach ($rootEntry as $nestedKey => $entry) {
947
                if (stripos($entry, '/mountpoint') !== false) {
948
                    $data[$rootKey][$nestedKey] = str_ireplace('/mountpoint', $mount_point, $entry);
949
                }
950
            }
951
        }
952
        $newJsonString = json_encode($data, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
953
        file_put_contents($staticSettingsFile, $newJsonString);
954
955
        // Update config variable
956
        $this->di->remove('config');
957
        $this->di->register(new ConfigProvider());
958
        $this->config = $this->di->getShared('config');
959
    }
960
961
    /**
962
     * Генерация файла fstab. Монтирование разделов.
963
     *
964
     * @param string $conf
965
     */
966
    public function saveFstab($conf = ''): void
967
    {
968
        $varEtcPath = $this->config->path('core.varEtcPath');
969
        // Точка монтирования доп. дисков.
970
        Util::mwMkdir('/storage/');
971
        if ( ! file_exists($varEtcPath . '/cfdevice')) {
972
            return;
973
        }
974
        $fstab     = '';
975
        $file_data = file_get_contents($varEtcPath . '/cfdevice');
976
        $cf_disk   = trim($file_data);
977
        if ('' == $cf_disk) {
978
            return;
979
        }
980
        // $part1 	 = (strpos($cf_disk, "mmcblk") !== false)?"{$cf_disk}p1":"{$cf_disk}1"; // Boot
981
        $part2 = (strpos($cf_disk, 'mmcblk') !== false) ? "{$cf_disk}p2" : "{$cf_disk}2"; // Offload
982
        $part3 = (strpos($cf_disk, 'mmcblk') !== false) ? "{$cf_disk}p3" : "{$cf_disk}3"; // Conf
983
984
985
        $uid_part2 = 'UUID=' . $this->getUuid("/dev/{$part2}") . '';
986
        $format_p2 = $this->getFsType($part2);
987
        $uid_part3 = 'UUID=' . $this->getUuid("/dev/{$part3}") . '';
988
        $format_p3 = $this->getFsType($part3);
989
990
        // $fstab .= "/dev/{$part1} /cf msdos ro 1 1\n"; // НЕ МОНТИРУЕМ!
991
        $fstab .= "{$uid_part2} /offload {$format_p2} ro 0 0\n";
992
        $fstab .= "{$uid_part3} /cf {$format_p3} rw 1 1\n";
993
        $fstab .= $conf;
994
995
        file_put_contents("/etc/fstab", $fstab);
996
        // Дублируем для работы vmtoolsd.
997
        file_put_contents("/etc/mtab", $fstab);
998
        Util::mwExec('mount -a 2> /dev/null');
999
        Util::addRegularWWWRights('/cf');
1000
    }
1001
1002
    /**
1003
     * Create system folders
1004
     *
1005
     * @return void
1006
     */
1007
    private function createWorkDirs(): void
1008
    {
1009
        $path = '';
1010
        Util::mwExec('mount -o remount,rw /offload 2> /dev/null');
1011
1012
        // Create dirs
1013
        $arrConfig = $this->config->toArray();
1014
        foreach ($arrConfig as $rootEntry) {
1015
            foreach ($rootEntry as $key => $entry) {
1016
                if (stripos($key, 'path') === false
1017
                    && stripos($key, 'dir') === false
1018
                ) {
1019
                    continue;
1020
                }
1021
1022
                if (file_exists($entry)) {
1023
                    continue;
1024
                }
1025
                $path .= " $entry";
1026
            }
1027
        }
1028
1029
        if ( ! empty($path)) {
1030
            Util::mwMkdir($path);
1031
        }
1032
1033
        $jsCacheDir = appPath('sites/admin-cabinet/assets/js/cache');
1034
        Util::createUpdateSymlink(
1035
            $this->config->path('adminApplication.cacheDir') . '/js',
1036
            $jsCacheDir
1037
        );
1038
1039
        $cssCacheDir = appPath('sites/admin-cabinet/assets/css/cache');
1040
        Util::createUpdateSymlink(
1041
            $this->config->path('adminApplication.cacheDir') . '/css',
1042
            $cssCacheDir
1043
        );
1044
1045
        $imgCacheDir = appPath('sites/admin-cabinet/assets/img/cache');
1046
        Util::createUpdateSymlink(
1047
            $this->config->path('adminApplication.cacheDir') . '/img',
1048
            $imgCacheDir
1049
        );
1050
        Util::createUpdateSymlink($this->config->path('core.phpSessionPath'), '/var/lib/php/session');
1051
        Util::createUpdateSymlink($this->config->path('core.tempPath'), '/ultmp');
1052
1053
        $filePath = appPath('src/Core/Asterisk/Configs/lua/extensions.lua');
1054
        Util::createUpdateSymlink($filePath, '/etc/asterisk/extensions.lua');
1055
1056
        // Create symlinks to AGI-BIN
1057
        $agiBinDir = $this->config->path('asterisk.astagidir');
1058
        Util::mwMkdir($agiBinDir);
1059
1060
        $roAgiBinFolder = appPath('src/Core/Asterisk/agi-bin');
1061
        $files = glob("$roAgiBinFolder/*.{php}", GLOB_BRACE);
1062
        foreach($files as $file) {
1063
            $newFilename = $agiBinDir.'/'. pathinfo($file)['filename'];
1064
            Util::createUpdateSymlink($file, $newFilename);
1065
        }
1066
1067
        $this->applyFolderRights();
1068
    }
1069
1070
    /**
1071
     * Fix permissions for Folder and Files
1072
     */
1073
    private function applyFolderRights(): void
1074
    {
1075
        // Add Rights to the WWW dirs plus some core dirs
1076
        $www_dirs  = [];
1077
        $arrConfig = $this->config->adminApplication->toArray();
1078
        foreach ($arrConfig as $key => $entry) {
1079
            if (stripos($key, 'path') === false
1080
                && stripos($key, 'dir') === false
1081
            ) {
1082
                continue;
1083
            }
1084
            $www_dirs[] = $entry;
1085
        }
1086
1087
        $www_dirs[] = $this->config->path('database.logsPath');
1088
        $www_dirs[] = $this->config->path('core.phpSessionPath');
1089
        $www_dirs[] = $this->config->path('core.tempPath');
1090
        $www_dirs[] = '/etc/version';
1091
        $www_dirs[] = appPath('/');
1092
1093
        // Add read rights
1094
        Util::addRegularWWWRights(implode(' ', $www_dirs));
1095
1096
        // Add executable rights
1097
        $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...
1098
        $exec_dirs[] = appPath('src/Core/Workers');
1099
        $exec_dirs[] = appPath('src/Core/Rc');
1100
        Util::mwExec('find ' . implode(' ', $exec_dirs) . ' -type f -exec chmod +x {} \;');
1101
        Util::mwExec('mount -o remount,ro /offload 2> /dev/null');
1102
    }
1103
1104
1105
    /**
1106
     * Delete old modules, not installed on the system
1107
     */
1108
    private function deleteOldModules(): void
1109
    {
1110
        // Проверим подключены ли модули.
1111
        /** @var \MikoPBX\Common\Models\PbxExtensionModules $modules */
1112
        $modules = PbxExtensionModules::find();
1113
        foreach ($modules as $module) {
1114
            if ( ! is_dir("{$this->config->path('core.modulesDir')}/{$module->uniqid}")) {
1115
                // Модуль не установлен... Нужно дать возможность переустановить модуль.
1116
                // Чистим запись об установленном модуле:
1117
                $modules->delete();
1118
            }
1119
        }
1120
    }
1121
1122
    /**
1123
     * Сохраняем новые данные диска.
1124
     *
1125
     * @param        $data
1126
     * @param string $id
1127
     */
1128
    public function saveDiskSettings($data, $id = '1'): void
1129
    {
1130
        if ( ! is_array($data)) {
1131
            return;
1132
        }
1133
        $disk_data = $this->getDiskSettings($id);
1134
        if (count($disk_data) === 0) {
1135
            $uniqid           = strtoupper('STORAGE-DISK-' . md5(time()));
1136
            $storage_settings = new StorageModel();
1137
            foreach ($data as $key => $val) {
1138
                $storage_settings->writeAttribute($key, $val);
1139
            }
1140
            $storage_settings->writeAttribute('uniqid', $uniqid);
1141
            $storage_settings->save();
1142
        } else {
1143
            $storage_settings = StorageModel::findFirst("id = '$id'");
1144
            if ($storage_settings === null){
1145
                return;
1146
            }
1147
            foreach ($data as $key => $value) {
1148
                $storage_settings->writeAttribute($key, $value);
1149
            }
1150
            $storage_settings->save();
1151
        }
1152
    }
1153
}