diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index 23b187de..b9ca259b 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -6698,21 +6698,27 @@ def api_smart_status(disk_name): def api_smart_run_test(disk_name): """Start a SMART self-test on a disk.""" try: + logging.info(f"[SMART Test] Starting test for disk: {disk_name}") + # Validate disk name (security) if not re.match(r'^[a-zA-Z0-9]+$', disk_name): + logging.warning(f"[SMART Test] Invalid disk name rejected: {disk_name}") return jsonify({'error': 'Invalid disk name'}), 400 device = f'/dev/{disk_name}' if not os.path.exists(device): + logging.warning(f"[SMART Test] Device not found: {device}") return jsonify({'error': 'Device not found'}), 404 data = request.get_json() or {} test_type = data.get('test_type', 'short') + logging.info(f"[SMART Test] Test type: {test_type}, Device: {device}") if test_type not in ('short', 'long'): return jsonify({'error': 'Invalid test type. Use "short" or "long"'}), 400 is_nvme = _is_nvme(disk_name) + logging.info(f"[SMART Test] Is NVMe: {is_nvme}") # Check tools and auto-install if missing tools = _ensure_smart_tools(install_if_missing=True) @@ -6728,7 +6734,7 @@ def api_smart_run_test(disk_name): # NVMe: self-test-code 1=short, 2=long code = 1 if test_type == 'short' else 2 - # First check if device supports self-test + # First check if device is accessible check_proc = subprocess.run( ['nvme', 'id-ctrl', device], capture_output=True, text=True, timeout=10 @@ -6736,24 +6742,48 @@ def api_smart_run_test(disk_name): if check_proc.returncode != 0: return jsonify({'error': f'Cannot access NVMe device: {check_proc.stderr.strip() or "Device not responding"}'}), 500 + # Check if device supports self-test by looking at OACS field + # OACS bit 4 (0x10) indicates Device Self-test support + oacs_output = check_proc.stdout + supports_selftest = True # Assume supported by default + for line in oacs_output.split('\n'): + if 'oacs' in line.lower(): + try: + # Parse OACS value (usually in hex) + oacs_val = int(line.split(':')[-1].strip(), 0) + if not (oacs_val & 0x10): + supports_selftest = False + except (ValueError, IndexError): + pass + break + + if not supports_selftest: + return jsonify({'error': 'This NVMe device does not support self-test (OACS bit 4 not set)'}), 400 + + logging.info(f"[SMART Test] Running: nvme device-self-test {device} --self-test-code={code}") proc = subprocess.run( ['nvme', 'device-self-test', device, f'--self-test-code={code}'], capture_output=True, text=True, timeout=30 ) + logging.info(f"[SMART Test] Result: returncode={proc.returncode}, stdout={proc.stdout[:200] if proc.stdout else ''}, stderr={proc.stderr[:200] if proc.stderr else ''}") if proc.returncode != 0: error_msg = proc.stderr.strip() or proc.stdout.strip() or 'Unknown error' # Some NVMe devices don't support self-test - if 'not support' in error_msg.lower() or 'invalid' in error_msg.lower(): + if 'not support' in error_msg.lower() or 'invalid' in error_msg.lower() or 'operation' in error_msg.lower(): return jsonify({'error': f'This NVMe device does not support self-test: {error_msg}'}), 400 + # Check for permission errors + if 'permission' in error_msg.lower() or 'operation not permitted' in error_msg.lower(): + return jsonify({'error': f'Permission denied. Run as root: {error_msg}'}), 403 return jsonify({'error': f'Failed to start test: {error_msg}'}), 500 # Start background monitor to save JSON when test completes + # Use 'nvme self-test-log' to check test status (not device-self-test) sleep_interval = 10 if test_type == 'short' else 60 subprocess.Popen( f''' sleep 5 - while nvme device-self-test {device} --self-test-code=0 2>/dev/null | grep -qi 'in progress'; do + while nvme self-test-log {device} 2>/dev/null | grep -qi 'in progress\\|operation in progress'; do sleep {sleep_interval} done nvme smart-log -o json {device} > {json_path} 2>/dev/null