false, 'error' => 'Invalid request']); exit; } $action = $input['action']; switch ($action) { case 'save_config': saveConfig($input['config']); break; case 'load_config': loadConfig(); break; case 'restart_service': restartService(); break; case 'list_tapes': listTapes(); break; case 'delete_tape': deleteTape($input['tape_name']); break; case 'bulk_delete_tapes': bulkDeleteTapes($input['pattern']); break; case 'create_tapes': createTapes($input); break; case 'list_targets': listTargets(); break; case 'create_target': createTarget($input); break; case 'delete_target': deleteTarget($input['tid']); break; case 'add_lun': addLun($input); break; case 'bind_initiator': bindInitiator($input); break; case 'unbind_initiator': unbindInitiator($input); break; default: echo json_encode(['success' => false, 'error' => 'Unknown action']); } // ============================================ // iSCSI Target Management Functions // ============================================ function listTargets() { $output = []; $returnCode = 0; exec('sudo tgtadm --lld iscsi --mode target --op show 2>&1', $output, $returnCode); if ($returnCode !== 0) { echo json_encode([ 'success' => false, 'error' => 'Failed to list targets: ' . implode(' ', $output) ]); return; } $targets = []; $currentTarget = null; $inACLSection = false; foreach ($output as $line) { if (preg_match('/^Target (\d+): (.+)$/', $line, $matches)) { if ($currentTarget) { $targets[] = $currentTarget; } $currentTarget = [ 'tid' => intval($matches[1]), 'name' => trim($matches[2]), 'luns' => 0, 'acls' => 0 ]; $inACLSection = false; } elseif ($currentTarget && preg_match('/^\s+LUN: (\d+)/', $line)) { $currentTarget['luns']++; $inACLSection = false; } elseif ($currentTarget && preg_match('/^\s+ACL information:/', $line)) { $inACLSection = true; } elseif ($currentTarget && $inACLSection && preg_match('/^\s+(.+)$/', $line, $matches)) { $acl = trim($matches[1]); if (!empty($acl) && !preg_match('/^(Account|I_T nexus|LUN|System)/', $acl)) { $currentTarget['acls']++; } } } if ($currentTarget) { $targets[] = $currentTarget; } echo json_encode([ 'success' => true, 'targets' => $targets ]); } function createTarget($params) { $tid = isset($params['tid']) ? intval($params['tid']) : 0; $name = isset($params['name']) ? trim($params['name']) : ''; if ($tid <= 0) { echo json_encode(['success' => false, 'error' => 'Invalid TID']); return; } if (empty($name)) { echo json_encode(['success' => false, 'error' => 'Target name is required']); return; } if (!preg_match('/^[a-zA-Z0-9._-]+$/', $name)) { echo json_encode(['success' => false, 'error' => 'Invalid target name format']); return; } $iqn = "iqn.2024-01.com.vtl-linux:$name"; $command = sprintf( 'sudo tgtadm --lld iscsi --mode target --op new --tid %d --targetname %s 2>&1', $tid, escapeshellarg($iqn) ); $output = []; $returnCode = 0; exec($command, $output, $returnCode); if ($returnCode === 0) { echo json_encode([ 'success' => true, 'message' => 'Target created successfully', 'iqn' => $iqn ]); } else { echo json_encode([ 'success' => false, 'error' => 'Failed to create target: ' . implode(' ', $output) ]); } } function deleteTarget($tid) { $tid = intval($tid); if ($tid <= 0) { echo json_encode(['success' => false, 'error' => 'Invalid TID']); return; } $command = sprintf( 'sudo tgtadm --lld iscsi --mode target --op delete --force --tid %d 2>&1', $tid ); $output = []; $returnCode = 0; exec($command, $output, $returnCode); if ($returnCode === 0) { echo json_encode([ 'success' => true, 'message' => 'Target deleted successfully' ]); } else { echo json_encode([ 'success' => false, 'error' => 'Failed to delete target: ' . implode(' ', $output) ]); } } function addLun($params) { $tid = isset($params['tid']) ? intval($params['tid']) : 0; $lun = isset($params['lun']) ? intval($params['lun']) : 0; $device = isset($params['device']) ? trim($params['device']) : ''; if ($tid <= 0) { echo json_encode(['success' => false, 'error' => 'Invalid TID']); return; } if ($lun < 0) { echo json_encode(['success' => false, 'error' => 'Invalid LUN number']); return; } if (empty($device)) { echo json_encode(['success' => false, 'error' => 'Device path is required']); return; } if (!preg_match('#^/dev/(sg\d+|sd[a-z]+)$#', $device)) { echo json_encode(['success' => false, 'error' => 'Invalid device path']); return; } if (!file_exists($device)) { echo json_encode(['success' => false, 'error' => 'Device does not exist']); return; } $command = sprintf( 'sudo tgtadm --lld iscsi --mode logicalunit --op new --tid %d --lun %d --backing-store %s 2>&1', $tid, $lun, escapeshellarg($device) ); $output = []; $returnCode = 0; exec($command, $output, $returnCode); if ($returnCode === 0) { echo json_encode([ 'success' => true, 'message' => 'LUN added successfully' ]); } else { echo json_encode([ 'success' => false, 'error' => 'Failed to add LUN: ' . implode(' ', $output) ]); } } function bindInitiator($params) { $tid = isset($params['tid']) ? intval($params['tid']) : 0; $address = isset($params['address']) ? trim($params['address']) : ''; if ($tid <= 0) { echo json_encode(['success' => false, 'error' => 'Invalid TID']); return; } if (empty($address)) { echo json_encode(['success' => false, 'error' => 'Initiator address is required']); return; } if ($address !== 'ALL' && !filter_var($address, FILTER_VALIDATE_IP)) { echo json_encode(['success' => false, 'error' => 'Invalid IP address']); return; } $command = sprintf( 'sudo tgtadm --lld iscsi --mode target --op bind --tid %d --initiator-address %s 2>&1', $tid, escapeshellarg($address) ); $output = []; $returnCode = 0; exec($command, $output, $returnCode); if ($returnCode === 0) { echo json_encode([ 'success' => true, 'message' => 'Initiator allowed successfully' ]); } else { echo json_encode([ 'success' => false, 'error' => 'Failed to bind initiator: ' . implode(' ', $output) ]); } } function unbindInitiator($params) { $tid = isset($params['tid']) ? intval($params['tid']) : 0; $address = isset($params['address']) ? trim($params['address']) : ''; if ($tid <= 0) { echo json_encode(['success' => false, 'error' => 'Invalid TID']); return; } if (empty($address)) { echo json_encode(['success' => false, 'error' => 'Initiator address is required']); return; } if ($address !== 'ALL' && !filter_var($address, FILTER_VALIDATE_IP)) { echo json_encode(['success' => false, 'error' => 'Invalid IP address']); return; } $command = sprintf( 'sudo tgtadm --lld iscsi --mode target --op unbind --tid %d --initiator-address %s 2>&1', $tid, escapeshellarg($address) ); $output = []; $returnCode = 0; exec($command, $output, $returnCode); if ($returnCode === 0) { echo json_encode([ 'success' => true, 'message' => 'Initiator blocked successfully' ]); } else { echo json_encode([ 'success' => false, 'error' => 'Failed to unbind initiator: ' . implode(' ', $output) ]); } } function createTapes($params) { $library = isset($params['library']) ? intval($params['library']) : 10; $barcodePrefix = isset($params['barcode_prefix']) ? trim($params['barcode_prefix']) : ''; $startNum = isset($params['start_num']) ? intval($params['start_num']) : 0; $count = isset($params['count']) ? intval($params['count']) : 1; $size = isset($params['size']) ? intval($params['size']) : 2500000; $mediaType = isset($params['media_type']) ? $params['media_type'] : 'data'; $density = isset($params['density']) ? $params['density'] : 'LTO6'; if (empty($barcodePrefix)) { echo json_encode(['success' => false, 'error' => 'Barcode prefix is required']); return; } if ($count < 1 || $count > 100) { echo json_encode(['success' => false, 'error' => 'Count must be between 1 and 100']); return; } if (strlen($barcodePrefix) > 6) { echo json_encode(['success' => false, 'error' => 'Barcode prefix too long (max 6 chars)']); return; } $validMediaTypes = ['data', 'clean', 'WORM']; if (!in_array($mediaType, $validMediaTypes)) { echo json_encode(['success' => false, 'error' => 'Invalid media type']); return; } $validDensities = ['LTO5', 'LTO6', 'LTO7', 'LTO8', 'LTO9']; if (!in_array($density, $validDensities)) { echo json_encode(['success' => false, 'error' => 'Invalid density']); return; } $createdCount = 0; $errors = []; for ($i = 0; $i < $count; $i++) { $barcodeNum = str_pad($startNum + $i, 6, '0', STR_PAD_LEFT); $barcode = $barcodePrefix . $barcodeNum; $command = sprintf( 'mktape -l %d -m %s -s %d -t %s -d %s 2>&1', $library, escapeshellarg($barcode), $size, escapeshellarg($mediaType), escapeshellarg($density) ); $output = []; $returnCode = 0; exec($command, $output, $returnCode); if ($returnCode === 0) { $createdCount++; } else { $errors[] = $barcode . ': ' . implode(' ', $output); } } if ($createdCount > 0) { $response = [ 'success' => true, 'created_count' => $createdCount, 'message' => "Created $createdCount tape(s)" ]; if (!empty($errors)) { $response['errors'] = $errors; } echo json_encode($response); } else { echo json_encode([ 'success' => false, 'error' => 'Failed to create tapes: ' . implode('; ', $errors) ]); } } function saveConfig($config) { global $DEVICE_CONF, $BACKUP_DIR; if (empty($config)) { echo json_encode(['success' => false, 'error' => 'Empty configuration']); return; } // Create backup of existing config if (file_exists($DEVICE_CONF)) { $backupFile = $BACKUP_DIR . '/device.conf.' . date('Y-m-d_H-i-s'); if (!copy($DEVICE_CONF, $backupFile)) { echo json_encode(['success' => false, 'error' => 'Failed to create backup']); return; } } // Write new config if (file_put_contents($DEVICE_CONF, $config) === false) { echo json_encode(['success' => false, 'error' => 'Failed to write configuration file. Check permissions.']); return; } // Set proper permissions chmod($DEVICE_CONF, 0644); echo json_encode([ 'success' => true, 'file' => $DEVICE_CONF, 'backup' => isset($backupFile) ? $backupFile : null, 'message' => 'Configuration saved successfully' ]); } function loadConfig() { global $DEVICE_CONF; if (!file_exists($DEVICE_CONF)) { echo json_encode(['success' => false, 'error' => 'Configuration file not found']); return; } $config = file_get_contents($DEVICE_CONF); echo json_encode([ 'success' => true, 'config' => $config ]); } function restartService() { // Check if user has sudo privileges $output = []; $returnCode = 0; exec('sudo systemctl restart mhvtl 2>&1', $output, $returnCode); if ($returnCode === 0) { echo json_encode([ 'success' => true, 'message' => 'Service restarted successfully' ]); } else { echo json_encode([ 'success' => false, 'error' => 'Failed to restart service: ' . implode("\n", $output) ]); } } function listTapes() { $tapeDir = '/opt/mhvtl'; if (!is_dir($tapeDir)) { echo json_encode(['success' => false, 'error' => 'Tape directory not found']); return; } $tapes = []; $items = scandir($tapeDir); foreach ($items as $item) { if ($item === '.' || $item === '..') { continue; } $path = $tapeDir . '/' . $item; if (is_dir($path)) { $stat = stat($path); $size = getDirSize($path); $tapes[] = [ 'name' => $item, 'size' => formatBytes($size), 'modified' => date('Y-m-d H:i:s', $stat['mtime']) ]; } } usort($tapes, function($a, $b) { return strcmp($a['name'], $b['name']); }); echo json_encode([ 'success' => true, 'tapes' => $tapes ]); } function getDirSize($dir) { $size = 0; foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)) as $file) { $size += $file->getSize(); } return $size; } function formatBytes($bytes) { $units = ['B', 'KB', 'MB', 'GB', 'TB']; $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); $pow = min($pow, count($units) - 1); $bytes /= pow(1024, $pow); return round($bytes, 2) . ' ' . $units[$pow]; } function deleteTape($tapeName) { $tapeDir = '/opt/mhvtl'; $tapePath = $tapeDir . '/' . basename($tapeName); if (!file_exists($tapePath)) { echo json_encode(['success' => false, 'error' => 'Tape not found']); return; } if (!is_dir($tapePath)) { echo json_encode(['success' => false, 'error' => 'Invalid tape path']); return; } if (strpos(realpath($tapePath), realpath($tapeDir)) !== 0) { echo json_encode(['success' => false, 'error' => 'Security violation: Path traversal detected']); return; } $output = []; $returnCode = 0; exec('sudo rm -rf ' . escapeshellarg($tapePath) . ' 2>&1', $output, $returnCode); if ($returnCode === 0) { echo json_encode([ 'success' => true, 'message' => 'Tape deleted successfully' ]); } else { echo json_encode([ 'success' => false, 'error' => 'Failed to delete tape: ' . implode("\n", $output) ]); } } function bulkDeleteTapes($pattern) { $tapeDir = '/opt/mhvtl'; if (empty($pattern)) { echo json_encode(['success' => false, 'error' => 'Pattern is required']); return; } if (strpos($pattern, '/') !== false || strpos($pattern, '..') !== false) { echo json_encode(['success' => false, 'error' => 'Invalid pattern']); return; } $items = glob($tapeDir . '/' . $pattern, GLOB_ONLYDIR); if (empty($items)) { echo json_encode([ 'success' => true, 'deleted_count' => 0, 'message' => 'No tapes found matching pattern' ]); return; } $deletedCount = 0; $errors = []; foreach ($items as $item) { if (strpos(realpath($item), realpath($tapeDir)) !== 0) { continue; } $output = []; $returnCode = 0; exec('sudo rm -rf ' . escapeshellarg($item) . ' 2>&1', $output, $returnCode); if ($returnCode === 0) { $deletedCount++; } else { $errors[] = basename($item) . ': ' . implode(' ', $output); } } if ($deletedCount > 0) { $message = "Deleted $deletedCount tape(s)"; if (!empty($errors)) { $message .= '. Errors: ' . implode('; ', $errors); } echo json_encode([ 'success' => true, 'deleted_count' => $deletedCount, 'message' => $message ]); } else { echo json_encode([ 'success' => false, 'error' => 'Failed to delete tapes: ' . implode('; ', $errors) ]); } } ?>