| Total Complexity | 252 | 
| Total Lines | 1799 | 
| Duplicated Lines | 0 % | 
| Changes | 26 | ||
| Bugs | 0 | Features | 0 | 
Complex classes like Storage often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Storage, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 52 | class Storage extends Injectable  | 
            ||
| 53 | { | 
            ||
| 54 | /**  | 
            ||
| 55 | * Move read-only sounds to the storage.  | 
            ||
| 56 | * This function assumes a storage disk is mounted.  | 
            ||
| 57 | *  | 
            ||
| 58 | * @return void  | 
            ||
| 59 | */  | 
            ||
| 60 | public function moveReadOnlySoundsToStorage(): void  | 
            ||
| 95 | }  | 
            ||
| 96 | |||
| 97 | /**  | 
            ||
| 98 | * Check if a storage disk is mounted.  | 
            ||
| 99 | *  | 
            ||
| 100 | * @param string $filter Optional filter for the storage disk.  | 
            ||
| 101 | * @param string $mount_dir If the disk is mounted, the mount directory will be stored in this variable.  | 
            ||
| 102 | * @return bool Returns true if the storage disk is mounted, false otherwise.  | 
            ||
| 103 | */  | 
            ||
| 104 | public static function isStorageDiskMounted(string $filter = '', string &$mount_dir = ''): bool  | 
            ||
| 137 | }  | 
            ||
| 138 | |||
| 139 | /**  | 
            ||
| 140 | * Copy MOH (Music on Hold) files to the storage.  | 
            ||
| 141 | * This function assumes a storage disk is mounted.  | 
            ||
| 142 | *  | 
            ||
| 143 | * @return void  | 
            ||
| 144 | */  | 
            ||
| 145 | public function copyMohFilesToStorage(): void  | 
            ||
| 146 |     { | 
            ||
| 147 | // Check if a storage disk is mounted  | 
            ||
| 148 |         if (!self::isStorageDiskMounted()) { | 
            ||
| 149 | return;  | 
            ||
| 150 | }  | 
            ||
| 151 | |||
| 152 |         $oldMohDir =  $this->config->path('asterisk.astvarlibdir') . '/sounds/moh'; | 
            ||
| 153 |         $currentMohDir = $this->config->path('asterisk.mohdir'); | 
            ||
| 154 | |||
| 155 | // If the old MOH directory doesn't exist or unable to create the current MOH directory, return  | 
            ||
| 156 |         if (!file_exists($oldMohDir) || Util::mwMkdir($currentMohDir)) { | 
            ||
| 157 | return;  | 
            ||
| 158 | }  | 
            ||
| 159 | |||
| 160 | $files = scandir($oldMohDir);  | 
            ||
| 161 | |||
| 162 | // Iterate through each file in the old MOH directory  | 
            ||
| 163 |         foreach ($files as $file) { | 
            ||
| 164 |             if (in_array($file, ['.', '..'])) { | 
            ||
| 165 | continue;  | 
            ||
| 166 | }  | 
            ||
| 167 | |||
| 168 | // Copy the file from the old MOH directory to the current MOH directory  | 
            ||
| 169 |             if (copy($oldMohDir . '/' . $file, $currentMohDir . '/' . $file)) { | 
            ||
| 170 | $sound_file = new SoundFiles();  | 
            ||
| 171 | $sound_file->path = $currentMohDir . '/' . $file;  | 
            ||
| 172 | $sound_file->category = SoundFiles::CATEGORY_MOH;  | 
            ||
| 173 | $sound_file->name = $file;  | 
            ||
| 174 | $sound_file->save();  | 
            ||
| 175 | }  | 
            ||
| 176 | }  | 
            ||
| 177 | }  | 
            ||
| 178 | |||
| 179 | /**  | 
            ||
| 180 | * Create a file system on a disk.  | 
            ||
| 181 | *  | 
            ||
| 182 | * @param string $dev The device path of the disk.  | 
            ||
| 183 | * @return bool Returns true if the file system creation process is initiated, false otherwise.  | 
            ||
| 184 | */  | 
            ||
| 185 | public static function mkfsDisk(string $dev): bool  | 
            ||
| 186 |     { | 
            ||
| 187 |         if (!file_exists($dev)) { | 
            ||
| 188 | $dev = "/dev/$dev";  | 
            ||
| 189 | }  | 
            ||
| 190 |         if (!file_exists($dev)) { | 
            ||
| 191 | return false;  | 
            ||
| 192 | }  | 
            ||
| 193 | $dir = '';  | 
            ||
| 194 | self::isStorageDiskMounted($dev, $dir);  | 
            ||
| 195 | |||
| 196 | // If the disk is not mounted or successfully unmounted, proceed with the file system creation  | 
            ||
| 197 |         if (empty($dir) || self::umountDisk($dir)) { | 
            ||
| 198 | $st = new self();  | 
            ||
| 199 | // Initiate the file system creation process  | 
            ||
| 200 | $st->formatEntireDisk($dev, true);  | 
            ||
| 201 | sleep(1);  | 
            ||
| 202 | |||
| 203 | return (self::statusMkfs($dev) === 'inprogress');  | 
            ||
| 204 | }  | 
            ||
| 205 | |||
| 206 | // Error occurred during disk unmounting  | 
            ||
| 207 | return false;  | 
            ||
| 208 | }  | 
            ||
| 209 | |||
| 210 | /**  | 
            ||
| 211 | * Unmount a disk.  | 
            ||
| 212 | *  | 
            ||
| 213 | * @param string $dir The mount directory of the disk.  | 
            ||
| 214 | * @return bool Returns true if the disk is successfully unmounted, false otherwise.  | 
            ||
| 215 | */  | 
            ||
| 216 | public static function umountDisk(string $dir): bool  | 
            ||
| 217 |     { | 
            ||
| 218 |         $umount = Util::which('umount'); | 
            ||
| 219 |         $rm = Util::which('rm'); | 
            ||
| 220 | |||
| 221 | // If the disk is mounted, terminate processes using the disk and unmount it  | 
            ||
| 222 |         if (self::isStorageDiskMounted($dir)) { | 
            ||
| 223 |             Processes::mwExec("/sbin/shell_functions.sh 'killprocesses' '$dir' -TERM 0"); | 
            ||
| 224 |             Processes::mwExec("$umount $dir"); | 
            ||
| 225 | }  | 
            ||
| 226 | $result = !self::isStorageDiskMounted($dir);  | 
            ||
| 227 | |||
| 228 | // If the disk is successfully unmounted and the directory exists, remove the directory  | 
            ||
| 229 |         if ($result && file_exists($dir)) { | 
            ||
| 230 |             Processes::mwExec("$rm -rf '$dir'"); | 
            ||
| 231 | }  | 
            ||
| 232 | |||
| 233 | return $result;  | 
            ||
| 234 | }  | 
            ||
| 235 | |||
| 236 | /**  | 
            ||
| 237 | * Format a disk locally using parted command and create one partition  | 
            ||
| 238 | *  | 
            ||
| 239 | * @param string $device The device path of the disk.  | 
            ||
| 240 | * @param bool $bg Whether to run the command in the background.  | 
            ||
| 241 | * @return bool Returns true if the disk formatting process is initiated, false otherwise.  | 
            ||
| 242 | */  | 
            ||
| 243 | public function formatEntireDisk(string $device, bool $bg = false): bool  | 
            ||
| 244 |     { | 
            ||
| 245 |         $parted = Util::which('parted'); | 
            ||
| 246 | |||
| 247 | // First, remove existing partitions and then create a new msdos partition table and ext4 partition  | 
            ||
| 248 | // This command deletes all existing partitions and creates a new primary partition using the full disk  | 
            ||
| 249 | $command = "$parted --script --align optimal '$device' 'mklabel msdos'";  | 
            ||
| 250 | Processes::mwExec($command); // Apply the command to clear the partition table  | 
            ||
| 251 | |||
| 252 | // Now create a new partition that spans the entire disk  | 
            ||
| 253 | $createPartCommand = "$parted --script --align optimal '$device' 'mkpart primary ext4 0% 100%'";  | 
            ||
| 254 | $retVal = Processes::mwExec($createPartCommand);  | 
            ||
| 255 | |||
| 256 | // Log the result of the create partition command  | 
            ||
| 257 | SystemMessages::sysLogMsg(__CLASS__, "$createPartCommand returned $retVal", LOG_INFO);  | 
            ||
| 258 | |||
| 259 | // Get the newly created partition name, assuming it's always the first partition after a fresh format  | 
            ||
| 260 | $partition = self::getDevPartName($device, '1');  | 
            ||
| 261 | |||
| 262 | return $this->formatPartition($partition, $bg);  | 
            ||
| 263 | }  | 
            ||
| 264 | |||
| 265 | /**  | 
            ||
| 266 | * Format a disk locally (part 2) using mkfs command.  | 
            ||
| 267 | *  | 
            ||
| 268 | * @param string $partition The partition for format, "/dev/sdb1" or "/dev/nvme0n1p1".  | 
            ||
| 269 | * @param bool $bg Whether to run the command in the background.  | 
            ||
| 270 | * @return bool Returns true if the disk formatting process is successfully completed, false otherwise.  | 
            ||
| 271 | */  | 
            ||
| 272 | public function formatPartition(string $partition, bool $bg = false): bool  | 
            ||
| 273 |     { | 
            ||
| 274 |         $mkfs = Util::which("mkfs.ext4"); | 
            ||
| 275 | $cmd = "$mkfs $partition";  | 
            ||
| 276 |         if ($bg === false) { | 
            ||
| 277 | // Execute the mkfs command and check the return value  | 
            ||
| 278 |             $retVal = Processes::mwExec("$cmd 2>&1"); | 
            ||
| 279 | SystemMessages::sysLogMsg(__CLASS__, "$cmd returned $retVal");  | 
            ||
| 280 | $result = ($retVal === 0);  | 
            ||
| 281 |         } else { | 
            ||
| 282 | usleep(200000);  | 
            ||
| 283 | // Execute the mkfs command in the background  | 
            ||
| 284 | Processes::mwExecBg($cmd);  | 
            ||
| 285 | $result = true;  | 
            ||
| 286 | }  | 
            ||
| 287 | |||
| 288 | return $result;  | 
            ||
| 289 | }  | 
            ||
| 290 | |||
| 291 | /**  | 
            ||
| 292 | * Get the status of mkfs process on a disk.  | 
            ||
| 293 | *  | 
            ||
| 294 | * @param string $dev The device path of the disk.  | 
            ||
| 295 |      * @return string Returns the status of mkfs process ('inprogress' or 'ended'). | 
            ||
| 296 | */  | 
            ||
| 297 | public static function statusMkfs(string $dev): string  | 
            ||
| 298 |     { | 
            ||
| 299 |         if (!file_exists($dev)) { | 
            ||
| 300 | $dev = "/dev/$dev";  | 
            ||
| 301 | }  | 
            ||
| 302 | $out = [];  | 
            ||
| 303 |         $psPath = Util::which('ps'); | 
            ||
| 304 |         $grepPath = Util::which('grep'); | 
            ||
| 305 | |||
| 306 | // Execute the command to check the status of mkfs process  | 
            ||
| 307 |         Processes::mwExec("$psPath -A -f | $grepPath $dev | $grepPath mkfs | $grepPath -v grep", $out); | 
            ||
| 308 |         $mount_dir = trim(implode('', $out)); | 
            ||
| 309 | |||
| 310 | return empty($mount_dir) ? 'ended' : 'inprogress';  | 
            ||
| 311 | }  | 
            ||
| 312 | |||
| 313 | /**  | 
            ||
| 314 | * Selects the storage disk and performs the necessary configuration.  | 
            ||
| 315 | *  | 
            ||
| 316 | * @param bool $automatic Flag to determine if the disk should be selected automatically  | 
            ||
| 317 | * @param bool $forceFormatStorage Flag to determine if the disk should be formatted  | 
            ||
| 318 | * @return bool Returns true on success, false otherwise  | 
            ||
| 319 | */  | 
            ||
| 320 | public static function selectAndConfigureStorageDisk(  | 
            ||
| 321 | bool $automatic = false,  | 
            ||
| 322 | bool $forceFormatStorage = false  | 
            ||
| 323 |     ): bool { | 
            ||
| 324 | $storage = new self();  | 
            ||
| 325 | |||
| 326 | // Check if the storage disk is already mounted  | 
            ||
| 327 |         if (self::isStorageDiskMounted()) { | 
            ||
| 328 |             SystemMessages::echoWithSyslog(PHP_EOL . " " . Util::translate('Storage disk is already mounted...') . " "); | 
            ||
| 329 | sleep(2);  | 
            ||
| 330 | return true;  | 
            ||
| 331 | }  | 
            ||
| 332 | |||
| 333 | $validDisks = [];  | 
            ||
| 334 | // Get all available hard drives  | 
            ||
| 335 | $all_hdd = $storage->getAllHdd();  | 
            ||
| 336 | $system_disk = '';  | 
            ||
| 337 | $selected_disk = ['size' => 0, 'id' => ''];  | 
            ||
| 338 | // Iterate through all available hard drives  | 
            ||
| 339 |         foreach ($all_hdd as $disk) { | 
            ||
| 340 | $additional = '';  | 
            ||
| 341 | $fourthPartitionName = self::getDevPartName($disk['id'], '4');  | 
            ||
| 342 |             $isLiveCd = ($disk['sys_disk'] && file_exists('/offload/livecd')); | 
            ||
| 343 | $isMountedSysDisk = (!empty($disk['mounted']) && $disk['sys_disk'] && file_exists($fourthPartitionName));  | 
            ||
| 344 | |||
| 345 | // Check if the disk is a system disk and is mounted  | 
            ||
| 346 |             if ($isMountedSysDisk || $isLiveCd) { | 
            ||
| 347 | $system_disk = $disk['id'];  | 
            ||
| 348 | $additional .= "\033[31;1m [SYSTEM]\033[0m";  | 
            ||
| 349 |             } elseif ($disk['mounted']) { | 
            ||
| 350 | // If disk is mounted but not a system disk, continue to the next iteration  | 
            ||
| 351 | continue;  | 
            ||
| 352 | }  | 
            ||
| 353 | |||
| 354 | // Check if the current disk is larger than the previously selected disk  | 
            ||
| 355 |             if ($selected_disk['size'] === 0 || $disk['size'] > $selected_disk['size']) { | 
            ||
| 356 | $selected_disk = $disk;  | 
            ||
| 357 | }  | 
            ||
| 358 | |||
| 359 | $part = $disk['sys_disk'] ? '4' : '1';  | 
            ||
| 360 | $partitionName = self::getDevPartName($disk['id'], $part);  | 
            ||
| 361 |             if (self::isStorageDisk($partitionName)) { | 
            ||
| 362 | $additional .= "\033[33;1m [STORAGE] \033[0m";  | 
            ||
| 363 | }  | 
            ||
| 364 | |||
| 365 | // Check if the disk is a system disk and has a valid partition  | 
            ||
| 366 |             if ($disk['size'] < 2 * 1024) { | 
            ||
| 367 | // If the disk size is less than 2 gb, continue to the next iteration  | 
            ||
| 368 | continue;  | 
            ||
| 369 | }  | 
            ||
| 370 | |||
| 371 | // Add the valid disk to the validDisks array  | 
            ||
| 372 |             $validDisks[$disk['id']] = "      |- {$disk['id']}, {$disk['size_text']}, {$disk['vendor']}$additional\n"; | 
            ||
| 373 | }  | 
            ||
| 374 | |||
| 375 |         if (empty($validDisks)) { | 
            ||
| 376 | // If no valid disks were found, log a message and return 0  | 
            ||
| 377 |             $message = '   |- ' . Util::translate('Valid disks not found...'); | 
            ||
| 378 | SystemMessages::echoWithSyslog($message);  | 
            ||
| 379 | SystemMessages::echoToTeletype(PHP_EOL . $message);  | 
            ||
| 380 | sleep(3);  | 
            ||
| 381 | return false;  | 
            ||
| 382 | }  | 
            ||
| 383 | |||
| 384 | // Check if the disk selection should be automatic  | 
            ||
| 385 |         if ($automatic) { | 
            ||
| 386 | $target_disk_storage = $selected_disk['id'];  | 
            ||
| 387 | SystemMessages::echoToTeletype(  | 
            ||
| 388 | PHP_EOL . ' - ' . "Automatically selected storage disk is $target_disk_storage"  | 
            ||
| 389 | );  | 
            ||
| 390 |         } else { | 
            ||
| 391 |             echo PHP_EOL . " " . Util::translate('Select the drive to store the data.'); | 
            ||
| 392 |             echo PHP_EOL . " " . Util::translate('Selected disk:') . "\033[33;1m [{$selected_disk['id']}] \033[0m " . PHP_EOL . PHP_EOL; | 
            ||
| 393 |             echo(PHP_EOL . " " . Util::translate('Valid disks are:') . " " . PHP_EOL . PHP_EOL); | 
            ||
| 394 |             foreach ($validDisks as $disk) { | 
            ||
| 395 | echo($disk);  | 
            ||
| 396 | }  | 
            ||
| 397 | echo PHP_EOL;  | 
            ||
| 398 | // Open standard input in binary mode for interactive reading  | 
            ||
| 399 |             $fp = fopen('php://stdin', 'rb'); | 
            ||
| 400 |             if ($forceFormatStorage) { | 
            ||
| 401 | echo '*******************************************************************************  | 
            ||
| 402 | * ' . Util::translate('WARNING') . ' | 
            ||
| 403 | * - ' . Util::translate('everything on this device will be erased!') . ' | 
            ||
| 404 | * - ' . Util::translate('this cannot be undone!') . ' | 
            ||
| 405 | *******************************************************************************';  | 
            ||
| 406 | }  | 
            ||
| 407 | // Otherwise, prompt the user to enter a disk  | 
            ||
| 408 |             do { | 
            ||
| 409 |                 echo PHP_EOL . Util::translate('Enter the device name:') . Util::translate('(default value = ') . $selected_disk['id'] . ') :'; | 
            ||
| 410 | $target_disk_storage = trim(fgets($fp));  | 
            ||
| 411 |                 if ($target_disk_storage === '') { | 
            ||
| 412 | $target_disk_storage = $selected_disk['id'];  | 
            ||
| 413 | }  | 
            ||
| 414 | } while (!array_key_exists($target_disk_storage, $validDisks));  | 
            ||
| 415 | }  | 
            ||
| 416 | |||
| 417 | // Determine the disk partition and format if necessary  | 
            ||
| 418 | $dev_disk = "/dev/$target_disk_storage";  | 
            ||
| 419 |         if (!empty($system_disk) && $system_disk === $target_disk_storage) { | 
            ||
| 420 | $part = "4";  | 
            ||
| 421 |         } else { | 
            ||
| 422 | $part = "1";  | 
            ||
| 423 | }  | 
            ||
| 424 | $partitionName = self::getDevPartName($target_disk_storage, $part);  | 
            ||
| 425 |         if ($part === '1' && (!self::isStorageDisk($partitionName) || $forceFormatStorage)) { | 
            ||
| 426 |             echo PHP_EOL . Util::translate('Partitioning and formatting storage disk') . ': ' . $dev_disk . '...' . PHP_EOL; | 
            ||
| 427 | $storage->formatEntireDisk($dev_disk);  | 
            ||
| 428 |         } elseif ($part === '4' && $forceFormatStorage) { | 
            ||
| 429 |             echo PHP_EOL . Util::translate('Formatting storage partition 4 on disk') . ': ' . $dev_disk . '...' . PHP_EOL; | 
            ||
| 430 |             passthru("exec </dev/console >/dev/console 2>/dev/console; /sbin/initial_storage_part_four create $dev_disk"); | 
            ||
| 431 |         } elseif ($part === '4') { | 
            ||
| 432 |             echo PHP_EOL . Util::translate('Update storage partition 4 on disk') . ': ' . $dev_disk . '...' . PHP_EOL; | 
            ||
| 433 |             passthru("exec </dev/console >/dev/console 2>/dev/console; /sbin/initial_storage_part_four update $dev_disk"); | 
            ||
| 434 | }  | 
            ||
| 435 | $partitionName = self::getDevPartName($target_disk_storage, $part);  | 
            ||
| 436 | $uuid = self::getUuid($partitionName);  | 
            ||
| 437 | // Create an array of disk data  | 
            ||
| 438 | $data = [  | 
            ||
| 439 | 'device' => $dev_disk,  | 
            ||
| 440 | 'uniqid' => $uuid,  | 
            ||
| 441 | 'filesystemtype' => 'ext4',  | 
            ||
| 442 | 'name' => 'Storage №1'  | 
            ||
| 443 | ];  | 
            ||
| 444 | echo PHP_EOL . "Disk part: $dev_disk, uid: $uuid" . PHP_EOL;  | 
            ||
| 445 | // Save the disk settings  | 
            ||
| 446 | $storage->saveDiskSettings($data);  | 
            ||
| 447 |         if (file_exists('/offload/livecd')) { | 
            ||
| 448 | // Do not need to start the PBX, it's the station installation in LiveCD mode.  | 
            ||
| 449 | return true;  | 
            ||
| 450 | }  | 
            ||
| 451 | MainDatabaseProvider::recreateDBConnections();  | 
            ||
| 452 | |||
| 453 | // Configure the storage  | 
            ||
| 454 | $storage->configure();  | 
            ||
| 455 | MainDatabaseProvider::recreateDBConnections();  | 
            ||
| 456 | $success = self::isStorageDiskMounted();  | 
            ||
| 457 |         if ($success === true && $automatic) { | 
            ||
| 458 | SystemMessages::echoToTeletype(PHP_EOL . ' |- The data storage disk has been successfully mounted ... ');  | 
            ||
| 459 | sleep(2);  | 
            ||
| 460 | System::reboot();  | 
            ||
| 461 | return true;  | 
            ||
| 462 | }  | 
            ||
| 463 | |||
| 464 |         if ($automatic) { | 
            ||
| 465 | SystemMessages::echoToTeletype(PHP_EOL . ' |- Storage disk was not mounted automatically ... ');  | 
            ||
| 466 | }  | 
            ||
| 467 | |||
| 468 | fclose(STDERR);  | 
            ||
| 469 |         echo('   |- Update database ... ' . PHP_EOL); | 
            ||
| 470 | |||
| 471 | // Update the database  | 
            ||
| 472 | $dbUpdater = new UpdateDatabase();  | 
            ||
| 473 | $dbUpdater->updateDatabaseStructure();  | 
            ||
| 474 | |||
| 475 |         $STDERR = fopen('php://stderr', 'wb'); | 
            ||
| 476 | CdrDb::checkDb();  | 
            ||
| 477 | |||
| 478 | // Restart syslog  | 
            ||
| 479 | $sysLog = new SyslogConf();  | 
            ||
| 480 | $sysLog->reStart();  | 
            ||
| 481 | |||
| 482 | // Configure PBX  | 
            ||
| 483 | $pbx = new PBX();  | 
            ||
| 484 | $pbx->configure();  | 
            ||
| 485 | |||
| 486 | // Restart processes related to storage  | 
            ||
| 487 | Processes::processPHPWorker(WorkerApiCommands::class);  | 
            ||
| 488 | |||
| 489 | // Check if the disk was mounted successfully  | 
            ||
| 490 |         if ($success === true) { | 
            ||
| 491 |             SystemMessages::echoWithSyslog("\n   |- " . Util::translate('Storage disk was mounted successfully...') . " \n\n"); | 
            ||
| 492 |         } else { | 
            ||
| 493 |             SystemMessages::echoWithSyslog("\n   |- " . Util::translate('Failed to mount the disc...') . " \n\n"); | 
            ||
| 494 | }  | 
            ||
| 495 | |||
| 496 | sleep(3);  | 
            ||
| 497 |         if ($STDERR !== false) { | 
            ||
| 498 | fclose($STDERR);  | 
            ||
| 499 | }  | 
            ||
| 500 | |||
| 501 | return $success;  | 
            ||
| 502 | }  | 
            ||
| 503 | |||
| 504 | /**  | 
            ||
| 505 | * Retrieves the partition name of a device.  | 
            ||
| 506 | *  | 
            ||
| 507 | * @param string $dev The device name  | 
            ||
| 508 | * @param string $part The partition number  | 
            ||
| 509 | * @param bool $verbose print verbose messages  | 
            ||
| 510 | * @return string The partition name  | 
            ||
| 511 | */  | 
            ||
| 512 | public static function getDevPartName(string $dev, string $part, bool $verbose = false): string  | 
            ||
| 513 |     { | 
            ||
| 514 |         $lsBlkPath = Util::which('lsblk'); | 
            ||
| 515 |         $cutPath   = Util::which('cut'); | 
            ||
| 516 |         $grepPath  = Util::which('grep'); | 
            ||
| 517 |         $sortPath  = Util::which('sort'); | 
            ||
| 518 | |||
| 519 | $basenameDisk = basename($dev);  | 
            ||
| 520 |         $pathToDisk = trim(shell_exec("$lsBlkPath -n -p -a -r -o NAME,TYPE | $grepPath disk | $grepPath '$basenameDisk' | $cutPath -d ' ' -f 1")); | 
            ||
| 521 |         if ($verbose) { | 
            ||
| 522 | echo "Get dev full path..." . PHP_EOL;  | 
            ||
| 523 | echo "Source dev: $dev, result full path: $pathToDisk" . PHP_EOL;  | 
            ||
| 524 | }  | 
            ||
| 525 | // Touch the disk to update disk tables  | 
            ||
| 526 |         $partProbePath = Util::which('partprobe'); | 
            ||
| 527 | shell_exec($partProbePath . " '$pathToDisk'");  | 
            ||
| 528 | |||
| 529 | // Touch the disk to update disk tables  | 
            ||
| 530 | $command = "$lsBlkPath -r -p | $grepPath ' part' | $sortPath -u | $cutPath -d ' ' -f 1 | $grepPath '" . $pathToDisk . "' | $grepPath \"$part\$\"";  | 
            ||
| 531 | $devName = trim(shell_exec($command));  | 
            ||
| 532 |         if (empty($devName) && $verbose) { | 
            ||
| 533 |             $verboseMsg = trim(shell_exec("$lsBlkPath -r -p")); | 
            ||
| 534 | echo "--- filtered command ---" . PHP_EOL;  | 
            ||
| 535 | echo $command . PHP_EOL;  | 
            ||
| 536 | echo "--- result 'lsblk -r -p' ---" . PHP_EOL;  | 
            ||
| 537 | echo $verboseMsg . PHP_EOL;  | 
            ||
| 538 | echo "--- --- ---" . PHP_EOL;  | 
            ||
| 539 | }  | 
            ||
| 540 | return $devName;  | 
            ||
| 541 | }  | 
            ||
| 542 | |||
| 543 | /**  | 
            ||
| 544 | * Check if a storage disk is valid.  | 
            ||
| 545 | *  | 
            ||
| 546 | * @param string $device The device path of the storage disk.  | 
            ||
| 547 | * @return bool Returns true if the storage disk is valid, false otherwise.  | 
            ||
| 548 | */  | 
            ||
| 549 | public static function isStorageDisk(string $device): bool  | 
            ||
| 550 |     { | 
            ||
| 551 | $result = false;  | 
            ||
| 552 | // Check if the device path exists  | 
            ||
| 553 |         if (!file_exists($device)) { | 
            ||
| 554 | return $result;  | 
            ||
| 555 | }  | 
            ||
| 556 | |||
| 557 | $tmp_dir = '/tmp/mnt_' . time();  | 
            ||
| 558 | Util::mwMkdir($tmp_dir);  | 
            ||
| 559 | $out = [];  | 
            ||
| 560 | |||
| 561 | $uid_part = 'UUID=' . self::getUuid($device);  | 
            ||
| 562 | $storage = new self();  | 
            ||
| 563 | $format = $storage->getFsType($device);  | 
            ||
| 564 | // If the file system type is not available, return false  | 
            ||
| 565 |         if ($format === '') { | 
            ||
| 566 | return false;  | 
            ||
| 567 | }  | 
            ||
| 568 |         $mount = Util::which('mount'); | 
            ||
| 569 |         $umount = Util::which('umount'); | 
            ||
| 570 |         $rm = Util::which('rm'); | 
            ||
| 571 | |||
| 572 |         Processes::mwExec("$mount -t $format $uid_part $tmp_dir", $out); | 
            ||
| 573 |         if (is_dir("$tmp_dir/mikopbx") && trim(implode('', $out)) === '') { | 
            ||
| 574 | // $out - empty string, no errors  | 
            ||
| 575 | // mikopbx directory exists  | 
            ||
| 576 | $result = true;  | 
            ||
| 577 | }  | 
            ||
| 578 | |||
| 579 | // Check if the storage disk is mounted, and unmount if necessary  | 
            ||
| 580 |         if (self::isStorageDiskMounted($device)) { | 
            ||
| 581 |             Processes::mwExec("$umount $device"); | 
            ||
| 582 | }  | 
            ||
| 583 | |||
| 584 | // Check if the storage disk is unmounted, and remove the temporary directory  | 
            ||
| 585 |         if (!self::isStorageDiskMounted($device)) { | 
            ||
| 586 |             Processes::mwExec("$rm -rf '$tmp_dir'"); | 
            ||
| 587 | }  | 
            ||
| 588 | |||
| 589 | return $result;  | 
            ||
| 590 | }  | 
            ||
| 591 | |||
| 592 | /**  | 
            ||
| 593 | * Saves the disk settings to the database.  | 
            ||
| 594 | *  | 
            ||
| 595 | * @param array $data The disk settings data to be saved.  | 
            ||
| 596 | * @param string $id The ID of the disk settings to be updated (default: '1').  | 
            ||
| 597 | * @return void  | 
            ||
| 598 | */  | 
            ||
| 599 | public function saveDiskSettings(array $data, string $id = '1'): void  | 
            ||
| 600 |     { | 
            ||
| 601 | $disk_data = $this->getDiskSettings($id);  | 
            ||
| 602 |         if (count($disk_data) === 0) { | 
            ||
| 603 | $storage_settings = new StorageModel();  | 
            ||
| 604 |         } else { | 
            ||
| 605 |             $storage_settings = StorageModel::findFirst("id = '$id'"); | 
            ||
| 606 | }  | 
            ||
| 607 |         foreach ($data as $key => $value) { | 
            ||
| 608 | $storage_settings->writeAttribute($key, $value);  | 
            ||
| 609 | }  | 
            ||
| 610 |         if (!$storage_settings->save()) { | 
            ||
| 611 | echo PHP_EOL . "Fail save new storage ID in database..." . PHP_EOL;  | 
            ||
| 612 | }  | 
            ||
| 613 | }  | 
            ||
| 614 | |||
| 615 | /**  | 
            ||
| 616 | * Retrieves the disk settings from the database.  | 
            ||
| 617 | *  | 
            ||
| 618 | * @param string $id The ID of the disk (optional).  | 
            ||
| 619 | * @return array The disk settings.  | 
            ||
| 620 | */  | 
            ||
| 621 | public function getDiskSettings(string $id = ''): array  | 
            ||
| 622 |     { | 
            ||
| 623 | $data = [];  | 
            ||
| 624 |         if ('' === $id) { | 
            ||
| 625 | // Return all disk settings.  | 
            ||
| 626 | $data = StorageModel::find()->toArray();  | 
            ||
| 627 |         } else { | 
            ||
| 628 | // Return disk settings for the specified ID.  | 
            ||
| 629 |             $pbxSettings = StorageModel::findFirst("id='$id'"); | 
            ||
| 630 |             if ($pbxSettings !== null) { | 
            ||
| 631 | $data = $pbxSettings->toArray();  | 
            ||
| 632 | }  | 
            ||
| 633 | }  | 
            ||
| 634 | |||
| 635 | return $data;  | 
            ||
| 636 | }  | 
            ||
| 637 | |||
| 638 | /**  | 
            ||
| 639 | * Configures the storage settings.  | 
            ||
| 640 | */  | 
            ||
| 641 | public function configure(): void  | 
            ||
| 642 |     { | 
            ||
| 643 | $varEtcDir = $this->config->path(Directories::CORE_VAR_ETC_DIR);  | 
            ||
| 644 | $storage_dev_file = "$varEtcDir/storage_device";  | 
            ||
| 645 |         if (!Util::isT2SdeLinux()) { | 
            ||
| 646 | // Configure for non-T2Sde Linux  | 
            ||
| 647 | file_put_contents($storage_dev_file, "/storage/usbdisk1");  | 
            ||
| 648 |             $this->updateConfigWithNewMountPoint("/storage/usbdisk1"); | 
            ||
| 649 | $this->createWorkDirs();  | 
            ||
| 650 | PHPConf::setupLog();  | 
            ||
| 651 | return;  | 
            ||
| 652 | }  | 
            ||
| 653 | |||
| 654 | $cf_disk = '';  | 
            ||
| 655 | |||
| 656 | // Remove the storage_dev_file if it exists  | 
            ||
| 657 |         if (file_exists($storage_dev_file)) { | 
            ||
| 658 | unlink($storage_dev_file);  | 
            ||
| 659 | }  | 
            ||
| 660 | |||
| 661 | // Check if cfdevice file exists and get its content  | 
            ||
| 662 |         if (file_exists($varEtcDir . '/cfdevice')) { | 
            ||
| 663 | $cf_disk = trim(file_get_contents($varEtcDir . '/cfdevice'));  | 
            ||
| 664 | }  | 
            ||
| 665 | |||
| 666 | $disks = $this->getDiskSettings();  | 
            ||
| 667 | $conf = '';  | 
            ||
| 668 | |||
| 669 | // Loop through each disk  | 
            ||
| 670 |         foreach ($disks as $disk) { | 
            ||
| 671 | clearstatcache();  | 
            ||
| 672 | $dev = $this->getStorageDev($disk, $cf_disk);  | 
            ||
| 673 | // Check if the disk exists  | 
            ||
| 674 |             if (!$this->hddExists($dev)) { | 
            ||
| 675 | SystemMessages::sysLogMsg(__METHOD__, "HDD - $dev doesn't exist");  | 
            ||
| 676 | continue;  | 
            ||
| 677 | }  | 
            ||
| 678 | |||
| 679 | // Check if the disk is marked as media or storage_dev_file doesn't exist  | 
            ||
| 680 |             if ($disk['media'] === '1' || !file_exists($storage_dev_file)) { | 
            ||
| 681 | SystemMessages::sysLogMsg(__METHOD__, "Update the storage_dev_file and the mount point configuration");  | 
            ||
| 682 |                 file_put_contents($storage_dev_file, "/storage/usbdisk{$disk['id']}"); | 
            ||
| 683 |                 $this->updateConfigWithNewMountPoint("/storage/usbdisk{$disk['id']}"); | 
            ||
| 684 | }  | 
            ||
| 685 | |||
| 686 | $formatFs = $this->getFsType($dev);  | 
            ||
| 687 | |||
| 688 | // Check if the file system type matches the expected type  | 
            ||
| 689 |             if ($formatFs !== $disk['filesystemtype'] && !($formatFs === 'ext4' && $disk['filesystemtype'] === 'ext2')) { | 
            ||
| 690 |                 SystemMessages::sysLogMsg(__METHOD__, "The file system type has changed {$disk['filesystemtype']} -> $formatFs. The disk will not be connected."); | 
            ||
| 691 | continue;  | 
            ||
| 692 | }  | 
            ||
| 693 | $str_uid = 'UUID=' . self::getUuid($dev);  | 
            ||
| 694 |             $conf .= "$str_uid /storage/usbdisk{$disk['id']} $formatFs async,rw 0 0\n"; | 
            ||
| 695 |             $mount_point = "/storage/usbdisk{$disk['id']}"; | 
            ||
| 696 | Util::mwMkdir($mount_point);  | 
            ||
| 697 | SystemMessages::sysLogMsg(__METHOD__, "Create mount point: $conf");  | 
            ||
| 698 | }  | 
            ||
| 699 | |||
| 700 | // Save the configuration to the fstab file  | 
            ||
| 701 | $this->saveFstab($conf);  | 
            ||
| 702 | |||
| 703 | // Create necessary working directories  | 
            ||
| 704 | $this->createWorkDirs();  | 
            ||
| 705 | |||
| 706 | // Set up the PHP log configuration  | 
            ||
| 707 | PHPConf::setupLog();  | 
            ||
| 708 | }  | 
            ||
| 709 | |||
| 710 | /**  | 
            ||
| 711 | * Updates the configuration file with the new mount point.  | 
            ||
| 712 | *  | 
            ||
| 713 | * After mount storage we will change /mountpoint/ to new $mount_point value  | 
            ||
| 714 | *  | 
            ||
| 715 | * @param string $mount_point The new mount point.  | 
            ||
| 716 | * @throws Error If the original configuration file has a broken format.  | 
            ||
| 717 | */  | 
            ||
| 718 | private function updateConfigWithNewMountPoint(string $mount_point): void  | 
            ||
| 719 |     { | 
            ||
| 720 | $settingsFile = '/etc/inc/mikopbx-settings.json';  | 
            ||
| 721 | $staticSettingsFileOrig = '/etc/inc/mikopbx-settings-orig.json';  | 
            ||
| 722 |         if (!file_exists($staticSettingsFileOrig)) { | 
            ||
| 723 | copy($settingsFile, $staticSettingsFileOrig);  | 
            ||
| 724 | }  | 
            ||
| 725 | |||
| 726 | $jsonString = file_get_contents($staticSettingsFileOrig);  | 
            ||
| 727 |         try { | 
            ||
| 728 | $data = json_decode($jsonString, true, 512, JSON_THROW_ON_ERROR);  | 
            ||
| 729 |         } catch (JsonException $exception) { | 
            ||
| 730 |             throw new Error("$staticSettingsFileOrig has broken format"); | 
            ||
| 731 | }  | 
            ||
| 732 |         foreach ($data as $rootKey => $rootEntry) { | 
            ||
| 733 |             foreach ($rootEntry as $nestedKey => $entry) { | 
            ||
| 734 |                 if (stripos($entry, '/mountpoint') !== false) { | 
            ||
| 735 |                     $data[$rootKey][$nestedKey] = str_ireplace('/mountpoint', $mount_point, $entry); | 
            ||
| 736 | }  | 
            ||
| 737 | }  | 
            ||
| 738 | }  | 
            ||
| 739 | |||
| 740 | $newJsonString = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);  | 
            ||
| 741 | file_put_contents($settingsFile, $newJsonString);  | 
            ||
| 742 | $this->updateEnvironmentAfterChangeMountPoint();  | 
            ||
| 743 | }  | 
            ||
| 744 | |||
| 745 | /**  | 
            ||
| 746 | * Updates the environment after changing the mount point.  | 
            ||
| 747 | * - Recreates the config provider and updates the config variable.  | 
            ||
| 748 | * - Reloads classes from system and storage disks.  | 
            ||
| 749 | * - Reloads all providers.  | 
            ||
| 750 | */  | 
            ||
| 751 | private function updateEnvironmentAfterChangeMountPoint(): void  | 
            ||
| 752 |     { | 
            ||
| 753 | // Update config variable  | 
            ||
| 754 | ConfigProvider::recreateConfigProvider();  | 
            ||
| 755 | $this->config = $this->di->getShared(ConfigProvider::SERVICE_NAME);  | 
            ||
| 756 | |||
| 757 | // Reload cached values  | 
            ||
| 758 | Directories::reset();  | 
            ||
| 759 | |||
| 760 | // Reload classes from system and storage disks  | 
            ||
| 761 | ClassLoader::init();  | 
            ||
| 762 | |||
| 763 | // Reload all providers  | 
            ||
| 764 | RegisterDIServices::init();  | 
            ||
| 765 | }  | 
            ||
| 766 | |||
| 767 | /**  | 
            ||
| 768 | * Creates the necessary working directories and symlinks.  | 
            ||
| 769 | *  | 
            ||
| 770 | * @return void  | 
            ||
| 771 | */  | 
            ||
| 772 | private function createWorkDirs(): void  | 
            ||
| 773 |     { | 
            ||
| 774 | $path = '';  | 
            ||
| 775 |         $mountPath = Util::which('mount'); | 
            ||
| 776 |         Processes::mwExec("$mountPath -o remount,rw /offload 2> /dev/null"); | 
            ||
| 777 | |||
| 778 |         $isLiveCd = file_exists('/offload/livecd'); | 
            ||
| 779 | |||
| 780 | // Create directories  | 
            ||
| 781 | $arrConfig = $this->config->toArray();  | 
            ||
| 782 |         foreach ($arrConfig as $rootEntry) { | 
            ||
| 783 |             foreach ($rootEntry as $key => $entry) { | 
            ||
| 784 |                 if (stripos($key, 'path') === false && stripos($key, 'dir') === false) { | 
            ||
| 785 | continue;  | 
            ||
| 786 | }  | 
            ||
| 787 |                 if (file_exists($entry)) { | 
            ||
| 788 | continue;  | 
            ||
| 789 | }  | 
            ||
| 790 |                 if ($isLiveCd && str_starts_with($entry, '/offload/')) { | 
            ||
| 791 | continue;  | 
            ||
| 792 | }  | 
            ||
| 793 | $path .= " $entry";  | 
            ||
| 794 | }  | 
            ||
| 795 | }  | 
            ||
| 796 | |||
| 797 |         if (!empty($path)) { | 
            ||
| 798 | Util::mwMkdir($path);  | 
            ||
| 799 | }  | 
            ||
| 800 | |||
| 801 |         $downloadCacheDir = appPath('sites/pbxcore/files/cache'); | 
            ||
| 802 |         if (!$isLiveCd) { | 
            ||
| 803 | Util::mwMkdir($downloadCacheDir);  | 
            ||
| 804 |             Util::createUpdateSymlink($this->config->path('www.downloadCacheDir'), $downloadCacheDir); | 
            ||
| 805 | }  | 
            ||
| 806 | |||
| 807 | $this->createAssetsSymlinks();  | 
            ||
| 808 | $this->createViewSymlinks();  | 
            ||
| 809 | $this->createAGIBINSymlinks($isLiveCd);  | 
            ||
| 810 | |||
| 811 |         Util::createUpdateSymlink($this->config->path('www.uploadDir'), '/ultmp'); | 
            ||
| 812 | |||
| 813 |         $filePath = appPath('src/Core/Asterisk/Configs/lua/extensions.lua'); | 
            ||
| 814 | Util::createUpdateSymlink($filePath, '/etc/asterisk/extensions.lua');  | 
            ||
| 815 | |||
| 816 | $this->clearCacheFiles();  | 
            ||
| 817 | $this->clearTmpFiles();  | 
            ||
| 818 | $this->applyFolderRights();  | 
            ||
| 819 |         Processes::mwExec("$mountPath -o remount,ro /offload 2> /dev/null"); | 
            ||
| 820 | }  | 
            ||
| 821 | |||
| 822 | /**  | 
            ||
| 823 | * Clears the cache files for various directories.  | 
            ||
| 824 | *  | 
            ||
| 825 | * @return void  | 
            ||
| 826 | */  | 
            ||
| 827 | public function clearCacheFiles(): void  | 
            ||
| 828 |     { | 
            ||
| 829 | $cacheDirs = [];  | 
            ||
| 830 | $cacheDirs[] = $this->config->path(Directories::WWW_UPLOAD_DIR);  | 
            ||
| 831 | $cacheDirs[] = $this->config->path(Directories::WWW_DOWNLOAD_CACHE_DIR);  | 
            ||
| 832 | $cacheDirs[] = $this->config->path(Directories::APP_ASSETS_CACHE_DIR) . '/js';  | 
            ||
| 833 | $cacheDirs[] = $this->config->path(Directories::APP_ASSETS_CACHE_DIR) . '/css';  | 
            ||
| 834 | $cacheDirs[] = $this->config->path(Directories::APP_ASSETS_CACHE_DIR) . '/img';  | 
            ||
| 835 | $cacheDirs[] = $this->config->path(Directories::APP_VIEW_CACHE_DIR);  | 
            ||
| 836 | $cacheDirs[] = $this->config->path(Directories::APP_VOLT_CACHE_DIR);  | 
            ||
| 837 |         $rmPath = Util::which('rm'); | 
            ||
| 838 | |||
| 839 | // Clear cache files for each directory  | 
            ||
| 840 |         foreach ($cacheDirs as $cacheDir) { | 
            ||
| 841 |             if (!empty($cacheDir)) { | 
            ||
| 842 |                 Processes::mwExec("$rmPath -rf $cacheDir/*"); | 
            ||
| 843 | }  | 
            ||
| 844 | }  | 
            ||
| 845 | |||
| 846 | // Delete boot cache folders if storage disk is mounted  | 
            ||
| 847 |         if (is_dir('/mountpoint') && self::isStorageDiskMounted()) { | 
            ||
| 848 |             Processes::mwExec("$rmPath -rf /mountpoint"); | 
            ||
| 849 | }  | 
            ||
| 850 | }  | 
            ||
| 851 | |||
| 852 | /**  | 
            ||
| 853 | * Clear files in temp directories  | 
            ||
| 854 | * @return void  | 
            ||
| 855 | */  | 
            ||
| 856 | private function clearTmpFiles(): void  | 
            ||
| 857 |     { | 
            ||
| 858 |         $timeout = Util::which('timeout'); | 
            ||
| 859 |         $find = Util::which('find'); | 
            ||
| 860 |         $mv = Util::which('mv'); | 
            ||
| 861 |         $rm = Util::which('rm'); | 
            ||
| 862 |         $nice = Util::which('nice'); | 
            ||
| 863 | $tmpDir = $this->config->path(Directories::CORE_TEMP_DIR);  | 
            ||
| 864 |         if (!file_exists($tmpDir)) { | 
            ||
| 865 | return;  | 
            ||
| 866 | }  | 
            ||
| 867 | // Trying to get a list of files  | 
            ||
| 868 |         Processes::mwExec("$timeout 10 $find $tmpDir -type f", $out, $ret); | 
            ||
| 869 |         if ($ret !== 0) { | 
            ||
| 870 | // there are too many files in the temporary directory, we will clear them  | 
            ||
| 871 | // it may cause a failure when setting access rights (chown)  | 
            ||
| 872 | $resDirForRm = "$tmpDir-" . time();  | 
            ||
| 873 |             shell_exec("$mv '$tmpDir' '$resDirForRm'"); | 
            ||
| 874 |             if (file_exists("$resDirForRm/swapfile")) { | 
            ||
| 875 | // Saving only the swap file  | 
            ||
| 876 |                 shell_exec("$mv '$resDirForRm/swapfile' '$tmpDir/swapfile'"); | 
            ||
| 877 | }  | 
            ||
| 878 | // Let's start deleting temporary files  | 
            ||
| 879 |             Processes::mwExecBg("$nice -n 19 $rm -rf $resDirForRm"); | 
            ||
| 880 | }  | 
            ||
| 881 | Util::mwMkdir($tmpDir, true);  | 
            ||
| 882 | }  | 
            ||
| 883 | |||
| 884 | /**  | 
            ||
| 885 | * Retrieves the storage device for the given disk.  | 
            ||
| 886 | *  | 
            ||
| 887 | * @param array $disk The disk information.  | 
            ||
| 888 | * @param string $cf_disk The cf_disk information.  | 
            ||
| 889 | * @return string The storage device path.  | 
            ||
| 890 | */  | 
            ||
| 891 | private function getStorageDev(array $disk, string $cf_disk): string  | 
            ||
| 892 |     { | 
            ||
| 893 |         if (!empty($disk['uniqid']) && !str_contains($disk['uniqid'], 'STORAGE-DISK')) { | 
            ||
| 894 | // Find the partition name by UID.  | 
            ||
| 895 |             $lsblk = Util::which('lsblk'); | 
            ||
| 896 |             $grep = Util::which('grep'); | 
            ||
| 897 |             $cut = Util::which('cut'); | 
            ||
| 898 |             $cmd = "$lsblk -r -o NAME,UUID | $grep {$disk['uniqid']} | $cut -d ' ' -f 1"; | 
            ||
| 899 | $dev = '/dev/' . trim(shell_exec($cmd) ?? '');  | 
            ||
| 900 |             if ($this->hddExists($dev)) { | 
            ||
| 901 | // Disk exists.  | 
            ||
| 902 | return $dev;  | 
            ||
| 903 | }  | 
            ||
| 904 | }  | 
            ||
| 905 | // Determine the disk by its name.  | 
            ||
| 906 |         if ($disk['device'] !== "/dev/$cf_disk") { | 
            ||
| 907 | // If it's a regular disk, use partition 1.  | 
            ||
| 908 | $part = "1";  | 
            ||
| 909 |         } else { | 
            ||
| 910 | // If it's a system disk, attempt to connect partition 4.  | 
            ||
| 911 | $part = "4";  | 
            ||
| 912 | }  | 
            ||
| 913 | return self::getDevPartName($disk['device'], $part);  | 
            ||
| 914 | }  | 
            ||
| 915 | |||
| 916 | /**  | 
            ||
| 917 | * Checks if a hard drive exists based on the provided disk identifier.  | 
            ||
| 918 | *  | 
            ||
| 919 | * @param string $disk The disk identifier, such as a device path.  | 
            ||
| 920 | * @return bool Returns true if the disk exists and has a non-empty UUID, false otherwise.  | 
            ||
| 921 | */  | 
            ||
| 922 | private function hddExists(string $disk): bool  | 
            ||
| 923 |     { | 
            ||
| 924 | // Check if the given disk identifier points to a directory.  | 
            ||
| 925 |         if (is_dir($disk)) { | 
            ||
| 926 | SystemMessages::sysLogMsg(__METHOD__, $disk . ' is a dir, not disk', LOG_DEBUG);  | 
            ||
| 927 | return false;  | 
            ||
| 928 | }  | 
            ||
| 929 | |||
| 930 | // Check if the file corresponding to the disk exists.  | 
            ||
| 931 |         if (!file_exists($disk)) { | 
            ||
| 932 | SystemMessages::sysLogMsg(__METHOD__, "Check if the file with name $disk exists failed", LOG_DEBUG);  | 
            ||
| 933 | return false;  | 
            ||
| 934 | }  | 
            ||
| 935 | |||
| 936 | // Record the start time for timeout purposes.  | 
            ||
| 937 | $startTime = time();  | 
            ||
| 938 | |||
| 939 | // Loop for up to 10 seconds or until a non-empty UUID is found.  | 
            ||
| 940 |         while (true) { | 
            ||
| 941 | // Retrieve the UUID for the disk.  | 
            ||
| 942 | $uid = self::getUuid($disk);  | 
            ||
| 943 | SystemMessages::sysLogMsg(__METHOD__, "Disk with name $disk has GUID: $uid", LOG_DEBUG);  | 
            ||
| 944 | |||
| 945 | // If the UUID is not empty, the disk exists.  | 
            ||
| 946 |             if (!empty($uid)) { | 
            ||
| 947 | return true;  | 
            ||
| 948 | }  | 
            ||
| 949 | |||
| 950 | // Exit the loop if 10 seconds have passed.  | 
            ||
| 951 |             if ((time() - $startTime) >= 10) { | 
            ||
| 952 | break;  | 
            ||
| 953 | }  | 
            ||
| 954 | |||
| 955 | // Wait for 1 second before the next iteration to avoid high CPU usage.  | 
            ||
| 956 | sleep(1);  | 
            ||
| 957 | }  | 
            ||
| 958 | |||
| 959 | // If the UUID remains empty after 10 seconds, the disk does not exist.  | 
            ||
| 960 | return false;  | 
            ||
| 961 | }  | 
            ||
| 962 | |||
| 963 | /**  | 
            ||
| 964 | * Saves the fstab configuration.  | 
            ||
| 965 | *  | 
            ||
| 966 | * @param string $conf Additional configuration to append to fstab  | 
            ||
| 967 | * @return void  | 
            ||
| 968 | */  | 
            ||
| 969 | public function saveFstab(string $conf = ''): void  | 
            ||
| 970 |     { | 
            ||
| 971 | $varEtcDir = $this->config->path(Directories::CORE_VAR_ETC_DIR);  | 
            ||
| 972 | |||
| 973 | // Create the mount point directory for additional disks  | 
            ||
| 974 |         Util::mwMkdir('/storage'); | 
            ||
| 975 |         $chmodPath = Util::which('chmod'); | 
            ||
| 976 |         Processes::mwExec("$chmodPath 755 /storage"); | 
            ||
| 977 | |||
| 978 | // Check if cf device file exists  | 
            ||
| 979 |         if (!file_exists($varEtcDir . '/cfdevice')) { | 
            ||
| 980 | return;  | 
            ||
| 981 | }  | 
            ||
| 982 | $fstab = '';  | 
            ||
| 983 | |||
| 984 | // Read cf device file  | 
            ||
| 985 | $file_data = file_get_contents($varEtcDir . '/cfdevice');  | 
            ||
| 986 | $cf_disk = trim($file_data);  | 
            ||
| 987 |         if ('' === $cf_disk) { | 
            ||
| 988 | return;  | 
            ||
| 989 | }  | 
            ||
| 990 | $part2 = self::getDevPartName($cf_disk, '2');  | 
            ||
| 991 | $part3 = self::getDevPartName($cf_disk, '3');  | 
            ||
| 992 | |||
| 993 | $uid_part2 = 'UUID=' . self::getUuid($part2);  | 
            ||
| 994 | $format_p2 = $this->getFsType($part2);  | 
            ||
| 995 | $uid_part3 = 'UUID=' . self::getUuid($part3);  | 
            ||
| 996 | $format_p3 = $this->getFsType($part3);  | 
            ||
| 997 | |||
| 998 | $fstab .= "$uid_part2 /offload $format_p2 ro 0 0\n";  | 
            ||
| 999 | $fstab .= "$uid_part3 /cf $format_p3 rw 1 1\n";  | 
            ||
| 1000 | $fstab .= $conf;  | 
            ||
| 1001 | |||
| 1002 | // Write fstab file  | 
            ||
| 1003 |         file_put_contents("/etc/fstab", $fstab); | 
            ||
| 1004 | |||
| 1005 | // Duplicate for vm tools d  | 
            ||
| 1006 |         file_put_contents("/etc/mtab", $fstab); | 
            ||
| 1007 | |||
| 1008 | // Mount the file systems  | 
            ||
| 1009 |         $mountPath     = Util::which('mount'); | 
            ||
| 1010 |         $resultOfMount = Processes::mwExec("$mountPath -a", $out); | 
            ||
| 1011 |         if ($resultOfMount !== 0) { | 
            ||
| 1012 |             SystemMessages::echoToTeletype(" - Error mount " . implode(' ', $out)); | 
            ||
| 1013 | }  | 
            ||
| 1014 | // Add regular www rights to /cf directory  | 
            ||
| 1015 |         Util::addRegularWWWRights('/cf'); | 
            ||
| 1016 | }  | 
            ||
| 1017 | |||
| 1018 | /**  | 
            ||
| 1019 | * Returns candidates to storage  | 
            ||
| 1020 | * @return array  | 
            ||
| 1021 | */  | 
            ||
| 1022 | public function getStorageCandidate(): array  | 
            ||
| 1023 |     { | 
            ||
| 1024 | $disks = $this->getLsBlkDiskInfo();  | 
            ||
| 1025 | $storages = [];  | 
            ||
| 1026 |         foreach ($disks as $disk) { | 
            ||
| 1027 |             if ($disk['type'] !== 'disk') { | 
            ||
| 1028 | continue;  | 
            ||
| 1029 | }  | 
            ||
| 1030 | $children = $disk['children'] ?? [];  | 
            ||
| 1031 |             if (count($children) === 0) { | 
            ||
| 1032 | continue;  | 
            ||
| 1033 | }  | 
            ||
| 1034 |             if (count($children) === 1) { | 
            ||
| 1035 | $part = '1';  | 
            ||
| 1036 |             } else { | 
            ||
| 1037 | $part = '4';  | 
            ||
| 1038 | }  | 
            ||
| 1039 | |||
| 1040 | $dev = '';  | 
            ||
| 1041 | $fs = '';  | 
            ||
| 1042 |             foreach ($children as $child) { | 
            ||
| 1043 |                 if ($child['fstype'] !== 'ext4' && $child['fstype'] !== 'ext2') { | 
            ||
| 1044 | continue;  | 
            ||
| 1045 | }  | 
            ||
| 1046 |                 if ($disk['name'] . $part === $child['name']) { | 
            ||
| 1047 | $dev = '/dev/' . $child['name'];  | 
            ||
| 1048 | $fs = $child['fstype'];  | 
            ||
| 1049 | break;  | 
            ||
| 1050 | }  | 
            ||
| 1051 | }  | 
            ||
| 1052 |             if (!empty($dev) && !empty($fs)) { | 
            ||
| 1053 | $storages[$dev] = $fs;  | 
            ||
| 1054 | }  | 
            ||
| 1055 | }  | 
            ||
| 1056 | |||
| 1057 | return $storages;  | 
            ||
| 1058 | }  | 
            ||
| 1059 | |||
| 1060 | /**  | 
            ||
| 1061 | * Get disk information using lsblk command.  | 
            ||
| 1062 | *  | 
            ||
| 1063 | * @return array An array containing disk information.  | 
            ||
| 1064 | */  | 
            ||
| 1065 | private function getLsBlkDiskInfo(): array  | 
            ||
| 1066 |     { | 
            ||
| 1067 |         $lsBlkPath = Util::which('lsblk'); | 
            ||
| 1068 | |||
| 1069 | // Execute lsblk command to get disk information in JSON format  | 
            ||
| 1070 | Processes::mwExec(  | 
            ||
| 1071 | "$lsBlkPath -J -b -o VENDOR,MODEL,SERIAL,LABEL,TYPE,FSTYPE,MOUNTPOINT,SUBSYSTEMS,NAME,UUID",  | 
            ||
| 1072 | $out  | 
            ||
| 1073 | );  | 
            ||
| 1074 |         try { | 
            ||
| 1075 | $data = json_decode(implode(PHP_EOL, $out), true, 512, JSON_THROW_ON_ERROR);  | 
            ||
| 1076 | $data = $data['blockdevices'] ?? [];  | 
            ||
| 1077 |         } catch (JsonException $e) { | 
            ||
| 1078 | $data = [];  | 
            ||
| 1079 | }  | 
            ||
| 1080 | return $data;  | 
            ||
| 1081 | }  | 
            ||
| 1082 | |||
| 1083 | /**  | 
            ||
| 1084 | * Create system folders and links after upgrade and connect config DB  | 
            ||
| 1085 | *  | 
            ||
| 1086 | * @return void  | 
            ||
| 1087 | */  | 
            ||
| 1088 | public function createWorkDirsAfterDBUpgrade(): void  | 
            ||
| 1102 | }  | 
            ||
| 1103 | |||
| 1104 | /**  | 
            ||
| 1105 | * Creates symlinks for module caches.  | 
            ||
| 1106 | *  | 
            ||
| 1107 | * @return void  | 
            ||
| 1108 | */  | 
            ||
| 1109 | public function createModulesCacheSymlinks(): void  | 
            ||
| 1110 |     { | 
            ||
| 1111 | $modules = PbxExtensionModules::getModulesArray();  | 
            ||
| 1112 |         foreach ($modules as $module) { | 
            ||
| 1113 | // Create cache links for JS, CSS, IMG folders  | 
            ||
| 1114 | PbxExtensionUtils::createAssetsSymlinks($module['uniqid']);  | 
            ||
| 1115 | |||
| 1116 | // Create links for the module view templates  | 
            ||
| 1117 | PbxExtensionUtils::createViewSymlinks($module['uniqid']);  | 
            ||
| 1118 | |||
| 1119 | // Create AGI bin symlinks for the module  | 
            ||
| 1120 | PbxExtensionUtils::createAgiBinSymlinks($module['uniqid']);  | 
            ||
| 1121 | }  | 
            ||
| 1122 | }  | 
            ||
| 1123 | |||
| 1124 | /**  | 
            ||
| 1125 | * Creates symlinks for asset cache directories.  | 
            ||
| 1126 | *  | 
            ||
| 1127 | * @return void  | 
            ||
| 1128 | */  | 
            ||
| 1129 | public function createAssetsSymlinks(): void  | 
            ||
| 1130 |     { | 
            ||
| 1131 | // Create symlink for JS cache directory  | 
            ||
| 1132 |         $jsCacheDir = appPath('sites/admin-cabinet/assets/js/cache'); | 
            ||
| 1133 | Util::createUpdateSymlink($this->config->path(Directories::APP_ASSETS_CACHE_DIR) . '/js', $jsCacheDir);  | 
            ||
| 1134 | |||
| 1135 | // Create symlink for CSS cache directory  | 
            ||
| 1136 |         $cssCacheDir = appPath('sites/admin-cabinet/assets/css/cache'); | 
            ||
| 1137 | Util::createUpdateSymlink($this->config->path(Directories::APP_ASSETS_CACHE_DIR) . '/css', $cssCacheDir);  | 
            ||
| 1138 | |||
| 1139 | // Create symlink for image cache directory  | 
            ||
| 1140 |         $imgCacheDir = appPath('sites/admin-cabinet/assets/img/cache'); | 
            ||
| 1141 | Util::createUpdateSymlink($this->config->path(Directories::APP_ASSETS_CACHE_DIR) . '/img', $imgCacheDir);  | 
            ||
| 1142 | }  | 
            ||
| 1143 | |||
| 1144 | /**  | 
            ||
| 1145 | * Creates symlinks for modules view.  | 
            ||
| 1146 | *  | 
            ||
| 1147 | * @return void  | 
            ||
| 1148 | */  | 
            ||
| 1149 | public function createViewSymlinks(): void  | 
            ||
| 1150 |     { | 
            ||
| 1151 |         $viewCacheDir = appPath('src/AdminCabinet/Views/Modules'); | 
            ||
| 1152 | Util::createUpdateSymlink($this->config->path(Directories::APP_VIEW_CACHE_DIR), $viewCacheDir);  | 
            ||
| 1153 | }  | 
            ||
| 1154 | |||
| 1155 | /**  | 
            ||
| 1156 | * Creates AGI bin symlinks for extension modules.  | 
            ||
| 1157 | *  | 
            ||
| 1158 | * @param bool $isLiveCd Whether the system loaded on LiveCD mode.  | 
            ||
| 1159 | * @return void  | 
            ||
| 1160 | */  | 
            ||
| 1161 | public function createAGIBINSymlinks(bool $isLiveCd): void  | 
            ||
| 1162 |     { | 
            ||
| 1163 | $agiBinDir = $this->config->path(Directories::AST_AGI_BIN_DIR);  | 
            ||
| 1164 |         if ($isLiveCd && !str_starts_with($agiBinDir, '/offload/')) { | 
            ||
| 1165 | Util::mwMkdir($agiBinDir);  | 
            ||
| 1166 | }  | 
            ||
| 1167 | |||
| 1168 |         $roAgiBinFolder = appPath('src/Core/Asterisk/agi-bin'); | 
            ||
| 1169 |         $files = glob("$roAgiBinFolder/*.{php}", GLOB_BRACE); | 
            ||
| 1170 |         foreach ($files as $file) { | 
            ||
| 1171 | $fileInfo = pathinfo($file);  | 
            ||
| 1172 |             $newFilename = "$agiBinDir/{$fileInfo['filename']}.{$fileInfo['extension']}"; | 
            ||
| 1173 | Util::createUpdateSymlink($file, $newFilename);  | 
            ||
| 1174 | }  | 
            ||
| 1175 | }  | 
            ||
| 1176 | |||
| 1177 | /**  | 
            ||
| 1178 | * Applies folder rights to the appropriate directories.  | 
            ||
| 1179 | *  | 
            ||
| 1180 | * @return void  | 
            ||
| 1181 | */  | 
            ||
| 1182 | private function applyFolderRights(): void  | 
            ||
| 1183 |     { | 
            ||
| 1184 | |||
| 1185 | $www_dirs = []; // Directories with WWW rights  | 
            ||
| 1186 | $exec_dirs = []; // Directories with executable rights  | 
            ||
| 1187 | |||
| 1188 | $arrConfig = $this->config->toArray();  | 
            ||
| 1189 | |||
| 1190 | // Get the directories for WWW rights from the configuration  | 
            ||
| 1191 |         foreach ($arrConfig as $key => $entry) { | 
            ||
| 1192 |             if (in_array($key, ['www', 'adminApplication'])) { | 
            ||
| 1193 |                 foreach ($entry as $subKey => $subEntry) { | 
            ||
| 1194 |                     if (stripos($subKey, 'path') === false && stripos($subKey, 'dir') === false) { | 
            ||
| 1195 | continue;  | 
            ||
| 1196 | }  | 
            ||
| 1197 | $www_dirs[] = $subEntry;  | 
            ||
| 1198 | }  | 
            ||
| 1199 | }  | 
            ||
| 1200 | }  | 
            ||
| 1201 | |||
| 1202 | // Add additional directories with WWW rights  | 
            ||
| 1203 | $www_dirs[] = $this->config->path(Directories::CORE_TEMP_DIR);  | 
            ||
| 1204 | $www_dirs[] = $this->config->path(Directories::CORE_LOGS_DIR);  | 
            ||
| 1205 | |||
| 1206 | // Create empty log files with WWW rights  | 
            ||
| 1207 | $logFiles = [  | 
            ||
| 1208 |             $this->config->path('database.debugLogFile'), | 
            ||
| 1209 |             $this->config->path('cdrDatabase.debugLogFile'), | 
            ||
| 1210 | ];  | 
            ||
| 1211 | |||
| 1212 |         foreach ($logFiles as $logFile) { | 
            ||
| 1213 | $filename = (string)$logFile;  | 
            ||
| 1214 |             if (!file_exists($filename)) { | 
            ||
| 1215 | file_put_contents($filename, '');  | 
            ||
| 1216 | }  | 
            ||
| 1217 | $www_dirs[] = $filename;  | 
            ||
| 1218 | }  | 
            ||
| 1219 | |||
| 1220 | $www_dirs[] = '/etc/version';  | 
            ||
| 1221 |         $www_dirs[] = appPath('/'); | 
            ||
| 1222 | |||
| 1223 | // Add read rights to the directories  | 
            ||
| 1224 |         Util::addRegularWWWRights(implode(' ', $www_dirs)); | 
            ||
| 1225 | |||
| 1226 | // Add executable rights to the directories  | 
            ||
| 1227 |         $exec_dirs[] = appPath('src/Core/Asterisk/agi-bin'); | 
            ||
| 1228 |         $exec_dirs[] = appPath('src/Core/Rc'); | 
            ||
| 1229 |         Util::addExecutableRights(implode(' ', $exec_dirs)); | 
            ||
| 1230 | |||
| 1231 |         $mountPath = Util::which('mount'); | 
            ||
| 1232 |         Processes::mwExec("$mountPath -o remount,ro /offload 2> /dev/null"); | 
            ||
| 1233 | }  | 
            ||
| 1234 | |||
| 1235 | /**  | 
            ||
| 1236 | * Mounts the swap file.  | 
            ||
| 1237 | */  | 
            ||
| 1238 | public function mountSwap(): void  | 
            ||
| 1239 |     { | 
            ||
| 1240 | $tempDir = $this->config->path(Directories::CORE_TEMP_DIR);  | 
            ||
| 1241 | $swapFile = "$tempDir/swapfile";  | 
            ||
| 1242 | |||
| 1243 |         $swapOffCmd = Util::which('swapoff'); | 
            ||
| 1244 |         Processes::mwExec("$swapOffCmd $swapFile"); | 
            ||
| 1245 | |||
| 1246 | $this->makeSwapFile($swapFile);  | 
            ||
| 1247 |         if (!file_exists($swapFile)) { | 
            ||
| 1248 | return;  | 
            ||
| 1249 | }  | 
            ||
| 1250 |         $swapOnCmd = Util::which('swapon'); | 
            ||
| 1251 |         $result = Processes::mwExec("$swapOnCmd $swapFile"); | 
            ||
| 1252 |         SystemMessages::sysLogMsg('Swap', 'connect swap result: ' . $result, LOG_INFO); | 
            ||
| 1253 | }  | 
            ||
| 1254 | |||
| 1255 | /**  | 
            ||
| 1256 | * Creates a swap file.  | 
            ||
| 1257 | *  | 
            ||
| 1258 | * @param string $swapFile The path to the swap file.  | 
            ||
| 1259 | */  | 
            ||
| 1260 | private function makeSwapFile(string $swapFile): void  | 
            ||
| 1261 |     { | 
            ||
| 1262 |         $swapLabel = Util::which('swaplabel'); | 
            ||
| 1263 | |||
| 1264 | // Check if swap file already exists  | 
            ||
| 1265 |         if (Processes::mwExec("$swapLabel $swapFile") === 0) { | 
            ||
| 1266 | return;  | 
            ||
| 1267 | }  | 
            ||
| 1268 |         if (file_exists($swapFile)) { | 
            ||
| 1269 | unlink($swapFile);  | 
            ||
| 1270 | }  | 
            ||
| 1271 | |||
| 1272 | $size = $this->getStorageFreeSpaceMb();  | 
            ||
| 1273 |         if ($size > 2000) { | 
            ||
| 1274 | $swapSize = 1024;  | 
            ||
| 1275 |         } elseif ($size > 1000) { | 
            ||
| 1276 | $swapSize = 512;  | 
            ||
| 1277 |         } else { | 
            ||
| 1278 | // Not enough free space.  | 
            ||
| 1279 | return;  | 
            ||
| 1280 | }  | 
            ||
| 1281 | $bs = 1024;  | 
            ||
| 1282 | $countBlock = $swapSize * (1024 * 1024) / $bs;  | 
            ||
| 1283 |         $ddCmd = Util::which('dd'); | 
            ||
| 1284 | |||
| 1285 |         SystemMessages::sysLogMsg('Swap', 'make swap ' . $swapFile, LOG_INFO); | 
            ||
| 1286 | |||
| 1287 | // Create swap file using dd command  | 
            ||
| 1288 |         Processes::mwExec("$ddCmd if=/dev/zero of=$swapFile bs=$bs count=$countBlock"); | 
            ||
| 1289 | |||
| 1290 |         $mkSwapCmd = Util::which('mkswap'); | 
            ||
| 1291 | |||
| 1292 | // Set up swap space on the file  | 
            ||
| 1293 |         Processes::mwExec("$mkSwapCmd $swapFile"); | 
            ||
| 1294 | }  | 
            ||
| 1295 | |||
| 1296 | /**  | 
            ||
| 1297 | * Retrieves the amount of free storage space in megabytes.  | 
            ||
| 1298 | *  | 
            ||
| 1299 | * @return int The amount of free storage space in megabytes.  | 
            ||
| 1300 | */  | 
            ||
| 1301 | public function getStorageFreeSpaceMb(): int  | 
            ||
| 1302 |     { | 
            ||
| 1303 | $size = 0;  | 
            ||
| 1304 | $mntDir = '';  | 
            ||
| 1305 |         $mounted = self::isStorageDiskMounted('', $mntDir); | 
            ||
| 1306 |         if (!$mounted) { | 
            ||
| 1307 | return 0;  | 
            ||
| 1308 | }  | 
            ||
| 1309 | $hd = $this->getAllHdd(true);  | 
            ||
| 1310 |         foreach ($hd as $disk) { | 
            ||
| 1311 |             if ($disk['mounted'] === $mntDir) { | 
            ||
| 1312 | $size = $disk['free_space'];  | 
            ||
| 1313 | break;  | 
            ||
| 1314 | }  | 
            ||
| 1315 | }  | 
            ||
| 1316 | return $size;  | 
            ||
| 1317 | }  | 
            ||
| 1318 | |||
| 1319 | /**  | 
            ||
| 1320 | * Get information about all HDD devices.  | 
            ||
| 1321 | *  | 
            ||
| 1322 | * @param bool $mounted_only Whether to include only mounted devices.  | 
            ||
| 1323 | * @return array An array of HDD device information.  | 
            ||
| 1324 | */  | 
            ||
| 1325 | public function getAllHdd(bool $mounted_only = false): array  | 
            ||
| 1326 |     { | 
            ||
| 1327 | $res_disks = [];  | 
            ||
| 1328 | |||
| 1329 |         if (Util::isDocker()) { | 
            ||
| 1330 | // Get disk information for /storage directory  | 
            ||
| 1331 | $out = [];  | 
            ||
| 1332 |             $grepPath = Util::which('grep'); | 
            ||
| 1333 |             $dfPath = Util::which('df'); | 
            ||
| 1334 |             $awkPath = Util::which('awk'); | 
            ||
| 1335 | |||
| 1336 | // Execute the command to get disk information for /storage directory  | 
            ||
| 1337 | Processes::mwExec(  | 
            ||
| 1338 |                 "$dfPath -k /storage | $awkPath  '{ print \$1 \"|\" $3 \"|\" \$4} ' | $grepPath -v 'Available'", | 
            ||
| 1339 | $out  | 
            ||
| 1340 | );  | 
            ||
| 1341 |             $disk_data = explode('|', implode(" ", $out)); | 
            ||
| 1342 |             if (count($disk_data) === 3) { | 
            ||
| 1343 | $m_size = round((intval($disk_data[1]) + intval($disk_data[2])) / 1024, 1);  | 
            ||
| 1344 | |||
| 1345 | // Add Docker disk information to the result  | 
            ||
| 1346 | $res_disks[] = [  | 
            ||
| 1347 | 'id' => $disk_data[0],  | 
            ||
| 1348 | 'size' => "" . $m_size,  | 
            ||
| 1349 | 'size_text' => "" . $m_size . " Mb",  | 
            ||
| 1350 | 'vendor' => 'Debian',  | 
            ||
| 1351 | 'mounted' => '/storage/usbdisk',  | 
            ||
| 1352 | 'free_space' => round($disk_data[2] / 1024, 1),  | 
            ||
| 1353 | 'partitions' => [],  | 
            ||
| 1354 | 'sys_disk' => true,  | 
            ||
| 1355 | ];  | 
            ||
| 1356 | }  | 
            ||
| 1357 | |||
| 1358 | return $res_disks;  | 
            ||
| 1359 | }  | 
            ||
| 1360 | |||
| 1361 | // Get CD-ROM and HDD devices  | 
            ||
| 1362 | $cd_disks = $this->cdromGetDevices();  | 
            ||
| 1363 | $disks = $this->diskGetDevices();  | 
            ||
| 1364 | |||
| 1365 | $cf_disk = '';  | 
            ||
| 1366 | $varEtcDir = $this->config->path(Directories::CORE_VAR_ETC_DIR);  | 
            ||
| 1367 | |||
| 1368 |         if (file_exists($varEtcDir . '/cfdevice')) { | 
            ||
| 1369 | $cf_disk = trim(file_get_contents($varEtcDir . '/cfdevice'));  | 
            ||
| 1370 | }  | 
            ||
| 1371 | |||
| 1372 |         foreach ($disks as $disk => $diskInfo) { | 
            ||
| 1373 | $type = $diskInfo['fstype'] ?? '';  | 
            ||
| 1374 | |||
| 1375 | // Skip Linux RAID member disks  | 
            ||
| 1376 |             if ($type === 'linux_raid_member') { | 
            ||
| 1377 | continue;  | 
            ||
| 1378 | }  | 
            ||
| 1379 | |||
| 1380 | // Skip CD-ROM disks  | 
            ||
| 1381 |             if (in_array($disk, $cd_disks, true)) { | 
            ||
| 1382 | continue;  | 
            ||
| 1383 | }  | 
            ||
| 1384 | unset($temp_vendor, $temp_size, $original_size);  | 
            ||
| 1385 | $mounted = self::diskIsMounted($disk);  | 
            ||
| 1386 |             if ($mounted_only === true && $mounted === false) { | 
            ||
| 1387 | continue;  | 
            ||
| 1388 | }  | 
            ||
| 1389 | $sys_disk = ($cf_disk === $disk);  | 
            ||
| 1390 | |||
| 1391 | $mb_size = 0;  | 
            ||
| 1392 |             if (is_file("/sys/block/" . $disk . "/size")) { | 
            ||
| 1393 |                 $original_size = trim(file_get_contents("/sys/block/" . $disk . "/size")); | 
            ||
| 1394 | $original_size = ($original_size * 512 / 1024 / 1024);  | 
            ||
| 1395 | $mb_size = round($original_size, 1);  | 
            ||
| 1396 | }  | 
            ||
| 1397 |             if ($mb_size > 100) { | 
            ||
| 1398 |                 $temp_size = sprintf("%.0f MB", $mb_size); | 
            ||
| 1399 | $temp_vendor = $this->getVendorDisk($diskInfo);  | 
            ||
| 1400 | $free_space = self::getFreeSpace($disk);  | 
            ||
| 1401 | |||
| 1402 | $arr_disk_info = $this->determineFormatFs($diskInfo);  | 
            ||
| 1403 | |||
| 1404 |                 if (count($arr_disk_info) > 0) { | 
            ||
| 1405 | $used = 0;  | 
            ||
| 1406 |                     foreach ($arr_disk_info as $disk_info) { | 
            ||
| 1407 | $used += $disk_info['used_space'];  | 
            ||
| 1408 | }  | 
            ||
| 1409 |                     if ($used > 0) { | 
            ||
| 1410 | $free_space = $mb_size - $used;  | 
            ||
| 1411 | }  | 
            ||
| 1412 | }  | 
            ||
| 1413 | |||
| 1414 | // Add HDD device information to the result  | 
            ||
| 1415 | $res_disks[] = [  | 
            ||
| 1416 | 'id' => $disk,  | 
            ||
| 1417 | 'size' => $mb_size,  | 
            ||
| 1418 | 'size_text' => $temp_size,  | 
            ||
| 1419 | 'vendor' => $temp_vendor,  | 
            ||
| 1420 | 'mounted' => $mounted,  | 
            ||
| 1421 | 'free_space' => round($free_space, 1),  | 
            ||
| 1422 | 'partitions' => $arr_disk_info,  | 
            ||
| 1423 | 'sys_disk' => $sys_disk,  | 
            ||
| 1424 | ];  | 
            ||
| 1425 | }  | 
            ||
| 1426 | }  | 
            ||
| 1427 | return $res_disks;  | 
            ||
| 1428 | }  | 
            ||
| 1429 | |||
| 1430 | /**  | 
            ||
| 1431 | * Get CD-ROM devices.  | 
            ||
| 1432 | *  | 
            ||
| 1433 | * @return array An array of CD-ROM device names.  | 
            ||
| 1434 | */  | 
            ||
| 1435 | private function cdromGetDevices(): array  | 
            ||
| 1436 |     { | 
            ||
| 1437 | $disks = [];  | 
            ||
| 1438 | $blockDevices = $this->getLsBlkDiskInfo();  | 
            ||
| 1439 |         foreach ($blockDevices as $diskData) { | 
            ||
| 1440 | $type = $diskData['type'] ?? '';  | 
            ||
| 1441 | $name = $diskData['name'] ?? '';  | 
            ||
| 1442 | |||
| 1443 | // Skip devices that are not CD-ROM  | 
            ||
| 1444 |             if ($type !== 'rom' || $name === '') { | 
            ||
| 1445 | continue;  | 
            ||
| 1446 | }  | 
            ||
| 1447 | $disks[] = $name;  | 
            ||
| 1448 | }  | 
            ||
| 1449 | return $disks;  | 
            ||
| 1450 | }  | 
            ||
| 1451 | |||
| 1452 | /**  | 
            ||
| 1453 | * Get disk devices.  | 
            ||
| 1454 | *  | 
            ||
| 1455 | * @param bool $diskOnly Whether to include only disk devices.  | 
            ||
| 1456 | * @return array An array of disk device information.  | 
            ||
| 1457 | */  | 
            ||
| 1458 | public function diskGetDevices(bool $diskOnly = false): array  | 
            ||
| 1459 |     { | 
            ||
| 1460 | $disks = [];  | 
            ||
| 1461 | $blockDevices = $this->getLsBlkDiskInfo();  | 
            ||
| 1462 | |||
| 1463 |         foreach ($blockDevices as $diskData) { | 
            ||
| 1464 | $type = $diskData['type'] ?? '';  | 
            ||
| 1465 | $name = $diskData['name'] ?? '';  | 
            ||
| 1466 |             if ($type !== 'disk' || $name === '') { | 
            ||
| 1467 | continue;  | 
            ||
| 1468 | }  | 
            ||
| 1469 | $disks[$name] = $diskData;  | 
            ||
| 1470 |             if ($diskOnly === true) { | 
            ||
| 1471 | continue;  | 
            ||
| 1472 | }  | 
            ||
| 1473 | $children = $diskData['children'] ?? [];  | 
            ||
| 1474 | |||
| 1475 |             foreach ($children as $child) { | 
            ||
| 1476 | $childName = $child['name'] ?? '';  | 
            ||
| 1477 |                 if ($childName === '') { | 
            ||
| 1478 | continue;  | 
            ||
| 1479 | }  | 
            ||
| 1480 | $disks[$childName] = $child;  | 
            ||
| 1481 | }  | 
            ||
| 1482 | }  | 
            ||
| 1483 | return $disks;  | 
            ||
| 1484 | }  | 
            ||
| 1485 | |||
| 1486 | /**  | 
            ||
| 1487 | * Check if a disk is mounted.  | 
            ||
| 1488 | *  | 
            ||
| 1489 | * @param string $disk The name of the disk.  | 
            ||
| 1490 | * @param string $filter The filter to match the disk name.  | 
            ||
| 1491 | * @return string|bool The mount point if the disk is mounted, or false if not mounted.  | 
            ||
| 1492 | */  | 
            ||
| 1493 | public static function diskIsMounted(string $disk, string $filter = '/dev/'): bool|string  | 
            ||
| 1494 |     { | 
            ||
| 1495 | $out = [];  | 
            ||
| 1496 |         $grep = Util::which('grep'); | 
            ||
| 1497 |         $mount = Util::which('mount'); | 
            ||
| 1498 |         $head = Util::which('head'); | 
            ||
| 1499 | |||
| 1500 | // Execute mount command and grep the output for the disk name  | 
            ||
| 1501 |         Processes::mwExec("$mount | $grep '{$filter}{$disk}' | $head -n 1", $out); | 
            ||
| 1502 |         if (count($out) > 0) { | 
            ||
| 1503 | $res_out = end($out);  | 
            ||
| 1504 |         } else { | 
            ||
| 1505 |             $res_out = implode('', $out); | 
            ||
| 1506 | }  | 
            ||
| 1507 |         $data = explode(' ', trim($res_out)); | 
            ||
| 1508 | |||
| 1509 | return (count($data) > 2) ? $data[2] : false;  | 
            ||
| 1510 | }  | 
            ||
| 1511 | |||
| 1512 | /**  | 
            ||
| 1513 | * Get the vendor name for a disk.  | 
            ||
| 1514 | *  | 
            ||
| 1515 | * @param array $diskInfo The disk information.  | 
            ||
| 1516 | * @return string The vendor name.  | 
            ||
| 1517 | */  | 
            ||
| 1518 | private function getVendorDisk(array $diskInfo): string  | 
            ||
| 1519 |     { | 
            ||
| 1520 | $temp_vendor = [];  | 
            ||
| 1521 | $keys = ['vendor', 'model', 'type'];  | 
            ||
| 1522 | |||
| 1523 | // Iterate through the keys to retrieve vendor-related data  | 
            ||
| 1524 |         foreach ($keys as $key) { | 
            ||
| 1525 | $data = $diskInfo[$key] ?? '';  | 
            ||
| 1526 |             if ($data !== '') { | 
            ||
| 1527 |                 $temp_vendor[] = trim(str_replace(',', '', $data)); | 
            ||
| 1528 | }  | 
            ||
| 1529 | }  | 
            ||
| 1530 | |||
| 1531 | // If no vendor-related data is found, use the disk name  | 
            ||
| 1532 |         if (empty($temp_vendor)) { | 
            ||
| 1533 | $temp_vendor[] = $diskInfo['name'] ?? 'ERROR: NoName';  | 
            ||
| 1534 | }  | 
            ||
| 1535 |         return implode(', ', $temp_vendor); | 
            ||
| 1536 | }  | 
            ||
| 1537 | |||
| 1538 | /**  | 
            ||
| 1539 | * Get the free space in megabytes for a given HDD.  | 
            ||
| 1540 | *  | 
            ||
| 1541 | * @param string $hdd The name of the HDD.  | 
            ||
| 1542 | * @return float|int The free space in megabytes.  | 
            ||
| 1543 | */  | 
            ||
| 1544 | public static function getFreeSpace(string $hdd): float|int  | 
            ||
| 1545 |     { | 
            ||
| 1546 | $out = [];  | 
            ||
| 1547 | $hdd = escapeshellarg($hdd);  | 
            ||
| 1548 |         $grep = Util::which('grep'); | 
            ||
| 1549 |         $awk = Util::which('awk'); | 
            ||
| 1550 |         $df = Util::which('df'); | 
            ||
| 1551 |         $head = Util::which('head'); | 
            ||
| 1552 | |||
| 1553 | // Execute df command to get the free space for the HDD  | 
            ||
| 1554 |         Processes::mwExec("$df -m | $grep $hdd | $grep -v custom_modules | $head -n 1 | $awk '{print $4}'", $out); | 
            ||
| 1555 | $result = 0;  | 
            ||
| 1556 | |||
| 1557 | // Sum up the free space values  | 
            ||
| 1558 |         foreach ($out as $res) { | 
            ||
| 1559 |             if (!is_numeric($res)) { | 
            ||
| 1560 | continue;  | 
            ||
| 1561 | }  | 
            ||
| 1562 | $result += (1 * $res);  | 
            ||
| 1563 | }  | 
            ||
| 1564 | |||
| 1565 | return $result;  | 
            ||
| 1566 | }  | 
            ||
| 1567 | |||
| 1568 | /**  | 
            ||
| 1569 | * Determine the format and file system information for a device.  | 
            ||
| 1570 | *  | 
            ||
| 1571 | * @param array $deviceInfo The device information.  | 
            ||
| 1572 | * @return array An array containing format and file system information for each device partition.  | 
            ||
| 1573 | */  | 
            ||
| 1574 | public function determineFormatFs(array $deviceInfo): array  | 
            ||
| 1575 |     { | 
            ||
| 1576 | $allow_formats = ['ext2', 'ext4', 'fat', 'ntfs', 'msdos'];  | 
            ||
| 1577 | $device = basename($deviceInfo['name'] ?? '');  | 
            ||
| 1578 | |||
| 1579 |         $devices = $this->getDiskParted('/dev/' . $deviceInfo['name'] ?? ''); | 
            ||
| 1580 | $result_data = [];  | 
            ||
| 1581 | |||
| 1582 | // Iterate through each device partition  | 
            ||
| 1583 |         foreach ($devices as $dev) { | 
            ||
| 1584 |             if (empty($dev) || (count($devices) > 1 && $device === $dev) || is_dir("/sys/block/$dev")) { | 
            ||
| 1585 | continue;  | 
            ||
| 1586 | }  | 
            ||
| 1587 | $mb_size = 0;  | 
            ||
| 1588 | $path_size_info = '';  | 
            ||
| 1589 | $tmp_path = "/sys/block/$device/$dev/size";  | 
            ||
| 1590 |             if (file_exists($tmp_path)) { | 
            ||
| 1591 | $path_size_info = $tmp_path;  | 
            ||
| 1592 | }  | 
            ||
| 1593 | |||
| 1594 | // If the size path is not found, try an alternate path  | 
            ||
| 1595 |             if (empty($path_size_info)) { | 
            ||
| 1596 | $tmp_path = "/sys/block/" . substr($dev, 0, 3) . "/$dev/size";  | 
            ||
| 1597 |                 if (file_exists($tmp_path)) { | 
            ||
| 1598 | $path_size_info = $tmp_path;  | 
            ||
| 1599 | }  | 
            ||
| 1600 | }  | 
            ||
| 1601 | |||
| 1602 | // Calculate the size in megabytes  | 
            ||
| 1603 |             if (!empty($path_size_info)) { | 
            ||
| 1604 | $original_size = trim(file_get_contents($path_size_info));  | 
            ||
| 1605 | $original_size = ($original_size * 512 / 1024 / 1024);  | 
            ||
| 1606 | $mb_size = $original_size;  | 
            ||
| 1607 | }  | 
            ||
| 1608 | |||
| 1609 |             $tmp_dir = "/tmp/{$dev}_" . time(); | 
            ||
| 1610 | $out = [];  | 
            ||
| 1611 | |||
| 1612 | $fs = null;  | 
            ||
| 1613 | $need_unmount = false;  | 
            ||
| 1614 | $mount_dir = '';  | 
            ||
| 1615 | |||
| 1616 | // Check if the device is currently mounted  | 
            ||
| 1617 |             if (self::isStorageDiskMounted("/dev/$dev ", $mount_dir)) { | 
            ||
| 1618 |                 $grepPath = Util::which('grep'); | 
            ||
| 1619 |                 $awkPath = Util::which('awk'); | 
            ||
| 1620 |                 $mountPath = Util::which('mount'); | 
            ||
| 1621 | |||
| 1622 | // Get the file system type and free space of the mounted device  | 
            ||
| 1623 |                 Processes::mwExec("$mountPath | $grepPath '/dev/$dev' | $awkPath '{print $5}'", $out); | 
            ||
| 1624 |                 $fs = trim(implode("", $out)); | 
            ||
| 1625 | $fs = ($fs === 'fuseblk') ? 'ntfs' : $fs;  | 
            ||
| 1626 |                 $free_space = self::getFreeSpace("/dev/$dev "); | 
            ||
| 1627 | $used_space = $mb_size - $free_space;  | 
            ||
| 1628 |             } else { | 
            ||
| 1629 | $format = $this->getFsType($device);  | 
            ||
| 1630 | |||
| 1631 | // Check if the detected format is allowed  | 
            ||
| 1632 |                 if (in_array($format, $allow_formats)) { | 
            ||
| 1633 | $fs = $format;  | 
            ||
| 1634 | }  | 
            ||
| 1635 | |||
| 1636 | // Mount the device and determine the used space  | 
            ||
| 1637 | self::mountDisk($dev, $format, $tmp_dir);  | 
            ||
| 1638 | |||
| 1639 | $need_unmount = true;  | 
            ||
| 1640 | $used_space = Util::getSizeOfFile($tmp_dir);  | 
            ||
| 1641 | }  | 
            ||
| 1642 | |||
| 1643 | // Store the partition information in the result array  | 
            ||
| 1644 | $result_data[] = [  | 
            ||
| 1645 | "dev" => $dev,  | 
            ||
| 1646 | 'size' => round($mb_size, 2),  | 
            ||
| 1647 | "used_space" => round($used_space, 2),  | 
            ||
| 1648 | "free_space" => round($mb_size - $used_space, 2),  | 
            ||
| 1649 |                 "uuid" => self::getUuid("/dev/$dev "), | 
            ||
| 1650 | "fs" => $fs,  | 
            ||
| 1651 | ];  | 
            ||
| 1652 | |||
| 1653 | // Unmount the temporary mount point if needed  | 
            ||
| 1654 |             if ($need_unmount) { | 
            ||
| 1655 | self::umountDisk($tmp_dir);  | 
            ||
| 1656 | }  | 
            ||
| 1657 | }  | 
            ||
| 1658 | |||
| 1659 | return $result_data;  | 
            ||
| 1660 | }  | 
            ||
| 1661 | |||
| 1662 | /**  | 
            ||
| 1663 | * Get the disk partitions using the lsblk command.  | 
            ||
| 1664 | *  | 
            ||
| 1665 | * @param string $diskName The name of the disk.  | 
            ||
| 1666 | * @return array An array of disk partition names.  | 
            ||
| 1667 | */  | 
            ||
| 1668 | private function getDiskParted(string $diskName): array  | 
            ||
| 1669 |     { | 
            ||
| 1670 | $result = [];  | 
            ||
| 1671 |         $lsBlkPath = Util::which('lsblk'); | 
            ||
| 1672 | |||
| 1673 | // Execute lsblk command to get disk partition information in JSON format  | 
            ||
| 1674 |         Processes::mwExec("$lsBlkPath -J -b -o NAME,TYPE $diskName", $out); | 
            ||
| 1675 | |||
| 1676 |         try { | 
            ||
| 1677 | $data = json_decode(implode(PHP_EOL, $out), true, 512, JSON_THROW_ON_ERROR);  | 
            ||
| 1678 | $data = $data['blockdevices'][0] ?? [];  | 
            ||
| 1679 |         } catch (\JsonException $e) { | 
            ||
| 1680 | $data = [];  | 
            ||
| 1681 | }  | 
            ||
| 1682 | |||
| 1683 | $type = $data['children'][0]['type'] ?? '';  | 
            ||
| 1684 | |||
| 1685 | // Check if the disk is not a RAID type  | 
            ||
| 1686 |         if (!str_contains($type, 'raid')) { | 
            ||
| 1687 | $children = $data['children'] ?? [];  | 
            ||
| 1688 |             foreach ($children as $child) { | 
            ||
| 1689 | $result[] = $child['name'];  | 
            ||
| 1690 | }  | 
            ||
| 1691 | }  | 
            ||
| 1692 | |||
| 1693 | return $result;  | 
            ||
| 1694 | }  | 
            ||
| 1695 | |||
| 1696 | /**  | 
            ||
| 1697 | * Get the file system type of a device.  | 
            ||
| 1698 | *  | 
            ||
| 1699 | * @param string $device The device path.  | 
            ||
| 1700 | * @return string The file system type of the device.  | 
            ||
| 1701 | */  | 
            ||
| 1702 | public function getFsType(string $device): string  | 
            ||
| 1703 |     { | 
            ||
| 1704 |         $blkid = Util::which('blkid'); | 
            ||
| 1705 |         $sed = Util::which('sed'); | 
            ||
| 1706 |         $grep = Util::which('grep'); | 
            ||
| 1707 |         $awk = Util::which('awk'); | 
            ||
| 1708 | |||
| 1709 | // Remove '/dev/' from the device path  | 
            ||
| 1710 |         $device = str_replace('/dev/', '', $device); | 
            ||
| 1711 | $out = [];  | 
            ||
| 1712 | |||
| 1713 | // Execute the command to retrieve the file system type of the device  | 
            ||
| 1714 | Processes::mwExec(  | 
            ||
| 1715 |             "$blkid -ofull /dev/$device | $sed -r 's/[[:alnum:]]+=/\\n&/g' | $grep \"^TYPE=\" | $awk -F \"\\\"\" '{print $2}'", | 
            ||
| 1716 | $out  | 
            ||
| 1717 | );  | 
            ||
| 1718 |         $format = implode('', $out); | 
            ||
| 1719 | |||
| 1720 | // Check if the format is 'msdosvfat' and replace it with 'msdos'  | 
            ||
| 1721 |         if ($format === 'msdosvfat') { | 
            ||
| 1722 | $format = 'msdos';  | 
            ||
| 1723 | }  | 
            ||
| 1724 | |||
| 1725 | return $format;  | 
            ||
| 1726 | }  | 
            ||
| 1727 | |||
| 1728 | /**  | 
            ||
| 1729 | * Mount a disk to a directory.  | 
            ||
| 1730 | *  | 
            ||
| 1731 | * @param string $dev The device name.  | 
            ||
| 1732 | * @param string $format The file system format.  | 
            ||
| 1733 | * @param string $dir The directory to mount the disk.  | 
            ||
| 1734 | * @return bool True if the disk was successfully mounted, false otherwise.  | 
            ||
| 1735 | */  | 
            ||
| 1736 | public static function mountDisk(string $dev, string $format, string $dir): bool  | 
            ||
| 1737 |     { | 
            ||
| 1738 | // Check if the disk is already mounted  | 
            ||
| 1739 |         if (self::isStorageDiskMounted("/dev/$dev ")) { | 
            ||
| 1740 | return true;  | 
            ||
| 1741 | }  | 
            ||
| 1742 | |||
| 1743 | // Create the directory if it doesn't exist  | 
            ||
| 1744 | Util::mwMkdir($dir);  | 
            ||
| 1745 | |||
| 1746 | // Check if the directory was created successfully  | 
            ||
| 1747 |         if (!file_exists($dir)) { | 
            ||
| 1748 | SystemMessages::sysLogMsg(__Method__, "Unable mount $dev $format to $dir. Unable create dir.");  | 
            ||
| 1749 | |||
| 1750 | return false;  | 
            ||
| 1751 | }  | 
            ||
| 1752 | |||
| 1753 | // Remove the '/dev/' prefix from the device name  | 
            ||
| 1754 |         $dev = str_replace('/dev/', '', $dev); | 
            ||
| 1755 | |||
| 1756 |         if ('ntfs' === $format) { | 
            ||
| 1757 | // Mount NTFS disk using 'mount.ntfs-3g' command  | 
            ||
| 1758 |             $mountNtfs3gPath = Util::which('mount.ntfs-3g'); | 
            ||
| 1759 |             Processes::mwExec("$mountNtfs3gPath /dev/$dev $dir", $out); | 
            ||
| 1760 |         } else { | 
            ||
| 1761 | // Mount disk using specified file system format and UUID  | 
            ||
| 1762 |             $uid_part = 'UUID=' . self::getUuid("/dev/$dev"); | 
            ||
| 1763 |             $mountPath = Util::which('mount'); | 
            ||
| 1764 |             Processes::mwExec("$mountPath -t $format $uid_part $dir", $out); | 
            ||
| 1765 | }  | 
            ||
| 1766 | |||
| 1767 | // Check if the disk is now mounted  | 
            ||
| 1768 |         return self::isStorageDiskMounted("/dev/$dev "); | 
            ||
| 1769 | }  | 
            ||
| 1770 | |||
| 1771 | /**  | 
            ||
| 1772 | * Get the UUID (Universally Unique Identifier) of a device.  | 
            ||
| 1773 | *  | 
            ||
| 1774 | * @param string $device The device path.  | 
            ||
| 1775 | * @return string The UUID of the device.  | 
            ||
| 1776 | */  | 
            ||
| 1777 | public static function getUuid(string $device): string  | 
            ||
| 1778 |     { | 
            ||
| 1779 |         if (empty($device)) { | 
            ||
| 1780 | return '';  | 
            ||
| 1781 | }  | 
            ||
| 1782 |         $lsblk = Util::which('lsblk'); | 
            ||
| 1783 |         $grep = Util::which('grep'); | 
            ||
| 1784 |         $cut = Util::which('cut'); | 
            ||
| 1785 | |||
| 1786 | // Build the command to retrieve the UUID of the device  | 
            ||
| 1787 | $cmd = "$lsblk -r -o NAME,UUID | $grep " . basename($device) . " | $cut -d ' ' -f 2";  | 
            ||
| 1788 | $res = Processes::mwExec($cmd, $output);  | 
            ||
| 1789 |         if ($res === 0 && !empty($output)) { | 
            ||
| 1790 | $result = $output[0];  | 
            ||
| 1791 |         } else { | 
            ||
| 1792 | $result = '';  | 
            ||
| 1793 | }  | 
            ||
| 1794 | return $result;  | 
            ||
| 1795 | }  | 
            ||
| 1796 | |||
| 1797 | /**  | 
            ||
| 1798 | * Retrieves the name of the disk used for recovery. (conf.recover.)  | 
            ||
| 1799 | *  | 
            ||
| 1800 | * @return string The name of the recovery disk (e.g., '/dev/sda').  | 
            ||
| 1801 | */  | 
            ||
| 1802 | public function getRecoverDiskName(): string  | 
            ||
| 1803 |     { | 
            ||
| 1804 | $disks = $this->diskGetDevices(true);  | 
            ||
| 1805 |         foreach ($disks as $disk => $diskInfo) { | 
            ||
| 1806 | // Check if the disk is a RAID or virtual device  | 
            ||
| 1807 |             if (isset($diskInfo['children'][0]['children'])) { | 
            ||
| 1808 | $diskInfo = $diskInfo['children'][0];  | 
            ||
| 1809 | // Adjust the disk name for RAID or other virtual devices  | 
            ||
| 1810 | $disk = $diskInfo['name'];  | 
            ||
| 1811 | }  | 
            ||
| 1812 |             foreach ($diskInfo['children'] as $child) { | 
            ||
| 1813 | $mountpoint = $child['mountpoint'] ?? '';  | 
            ||
| 1814 | $diskPath = "/dev/$disk";  | 
            ||
| 1815 |                 if ($mountpoint === '/conf.recover' && file_exists($diskPath)) { | 
            ||
| 1816 | return "/dev/$disk";  | 
            ||
| 1817 | }  | 
            ||
| 1818 | }  | 
            ||
| 1819 | }  | 
            ||
| 1820 | return '';  | 
            ||
| 1821 | }  | 
            ||
| 1822 | |||
| 1823 | /**  | 
            ||
| 1824 | * Returns the monitor directory path.  | 
            ||
| 1825 | * @deprecated Use Directories class instead  | 
            ||
| 1826 | *  | 
            ||
| 1827 | * @return string The monitor directory path.  | 
            ||
| 1828 | */  | 
            ||
| 1829 | public static function getMonitorDir(): string  | 
            ||
| 1832 | }  | 
            ||
| 1833 | |||
| 1834 | /**  | 
            ||
| 1835 | * Connect storage in a cloud if it was provisioned but not connected.  | 
            ||
| 1836 | *  | 
            ||
| 1837 | * @return string connection result  | 
            ||
| 1838 | */  | 
            ||
| 1839 | public static function connectStorageInCloud(): string  | 
            ||
| 1840 |     { | 
            ||
| 1841 |         if (PbxSettings::findFirst('key="' . PbxSettings::CLOUD_PROVISIONING . '"') === null) { | 
            ||
| 1842 | return SystemMessages::RESULT_SKIPPED;  | 
            ||
| 1843 | }  | 
            ||
| 1844 | |||
| 1845 | // In some Clouds the virtual machine starts immediately before the storage disk was attached  | 
            ||
| 1846 |         if (!self::selectAndConfigureStorageDisk(true)) { | 
            ||
| 1851 | }  | 
            ||
| 1852 | }  | 
            ||
| 1853 |