diff --git a/src/c#/GeneralUpdate.Drivelution/Linux/Implementation/LinuxGeneralDrivelution.cs b/src/c#/GeneralUpdate.Drivelution/Linux/Implementation/LinuxGeneralDrivelution.cs index 7c9a3188..9005e8d6 100644 --- a/src/c#/GeneralUpdate.Drivelution/Linux/Implementation/LinuxGeneralDrivelution.cs +++ b/src/c#/GeneralUpdate.Drivelution/Linux/Implementation/LinuxGeneralDrivelution.cs @@ -1,142 +1,98 @@ using System.Runtime.Versioning; using GeneralUpdate.Common.Shared; using GeneralUpdate.Drivelution.Abstractions; +using GeneralUpdate.Drivelution.Abstractions.Configuration; using GeneralUpdate.Drivelution.Abstractions.Exceptions; using GeneralUpdate.Drivelution.Abstractions.Models; +using GeneralUpdate.Drivelution.Core.Pipeline; using GeneralUpdate.Drivelution.Core.Utilities; using GeneralUpdate.Drivelution.Linux.Helpers; namespace GeneralUpdate.Drivelution.Linux.Implementation; /// -/// Linux驱动更新器实现 -/// Linux driver updater implementation +/// Linux driver updater implementation. +/// Inherits the unified pipeline from and adds Linux-specific +/// sudo permission check, kernel module / .deb / .rpm installation, and module parsing. /// [SupportedOSPlatform("linux")] -public class LinuxGeneralDrivelution : IGeneralDrivelution +public class LinuxGeneralDrivelution : BaseDriverUpdater { - private readonly IDriverValidator _validator; - private readonly IDriverBackup _backup; - - public LinuxGeneralDrivelution(IDriverValidator validator, IDriverBackup backup) + /// + /// Initializes a new instance of the class. + /// + /// Driver validator. + /// Driver backup manager. + /// Configuration options (optional). + public LinuxGeneralDrivelution( + IDriverValidator validator, + IDriverBackup backup, + DrivelutionOptions? options = null) + : base(validator, backup, options) { - _validator = validator ?? throw new ArgumentNullException(nameof(validator)); - _backup = backup ?? throw new ArgumentNullException(nameof(backup)); } + // ─── Pipeline overrides ──────────────────────────────────────────── + /// - public async Task UpdateAsync( - DriverInfo driverInfo, - UpdateStrategy strategy, - CancellationToken cancellationToken = default) + protected override IEnumerable GetPipelineSteps(UpdateStrategy strategy) { - var result = new UpdateResult - { - StartTime = DateTime.UtcNow, - Status = UpdateStatus.NotStarted - }; + yield return CreateSudoCheckStep(); - try - { - GeneralTracer.Info($"Starting driver update for: {driverInfo.Name} v{driverInfo.Version}"); - - // Permission check - await LinuxPermissionHelper.EnsureSudoAsync(); - - // Validation - result.Status = UpdateStatus.Validating; - if (!await ValidateAsync(driverInfo, cancellationToken)) - { - throw new DriverValidationException("Driver validation failed", "General"); - } - - // Backup if required - if (strategy.RequireBackup) - { - result.Status = UpdateStatus.BackingUp; - var backupPath = GenerateBackupPath(driverInfo, strategy.BackupPath); - if (await BackupAsync(driverInfo, backupPath, cancellationToken)) - { - result.BackupPath = backupPath; - } - } - - // Execute update - result.Status = UpdateStatus.Updating; - await ExecuteDriverInstallationAsync(driverInfo, cancellationToken); - - result.Success = true; - result.Status = UpdateStatus.Succeeded; - result.Message = "Driver update completed successfully"; - } - catch (Exception ex) - { - GeneralTracer.Error("Driver update failed", ex); - result.Success = false; - result.Status = UpdateStatus.Failed; - result.Error = new ErrorInfo - { - Type = ErrorType.InstallationFailed, - Message = ex.Message, - Details = ex.ToString() - }; - } - finally - { - result.EndTime = DateTime.UtcNow; - } - - return result; + foreach (var step in base.GetPipelineSteps(strategy)) + yield return step; } /// - public async Task ValidateAsync(DriverInfo driverInfo, CancellationToken cancellationToken = default) + protected override async Task InstallCoreAsync( + DriverInfo driverInfo, + UpdateStrategy strategy, + CancellationToken cancellationToken) { - if (!File.Exists(driverInfo.FilePath)) - { - return false; - } + GeneralTracer.Info($"Installing Linux driver: {driverInfo.FilePath}"); + + var extension = Path.GetExtension(driverInfo.FilePath).ToLowerInvariant(); - // Validate hash if provided - if (!string.IsNullOrEmpty(driverInfo.Hash)) + switch (extension) { - if (!await _validator.ValidateIntegrityAsync( - driverInfo.FilePath, - driverInfo.Hash, - driverInfo.HashAlgorithm, - cancellationToken)) - { - return false; - } + case ".ko": + await InstallKernelModuleAsync(driverInfo.FilePath, cancellationToken); + break; + case ".deb": + await InstallDebPackageAsync(driverInfo.FilePath, cancellationToken); + break; + case ".rpm": + await InstallRpmPackageAsync(driverInfo.FilePath, cancellationToken); + break; + default: + GeneralTracer.Warn($"Unknown driver format: {extension}. Attempting generic installation."); + await InstallKernelModuleAsync(driverInfo.FilePath, cancellationToken); + break; } - // Validate compatibility - return await _validator.ValidateCompatibilityAsync(driverInfo, cancellationToken); + GeneralTracer.Info("Linux driver installation completed"); } - /// - public Task BackupAsync(DriverInfo driverInfo, string backupPath, CancellationToken cancellationToken = default) - { - return _backup.BackupAsync(driverInfo.FilePath, backupPath, cancellationToken); - } + // ─── Rollback override ───────────────────────────────────────────── /// - public async Task RollbackAsync(string backupPath, CancellationToken cancellationToken = default) + public override async Task RollbackAsync( + string backupPath, + CancellationToken cancellationToken = default) { try { - GeneralTracer.Info($"Rolling back driver from backup: {backupPath}"); - + GeneralTracer.Info($"Rolling back Linux driver from backup: {backupPath}"); + if (!Directory.Exists(backupPath)) { GeneralTracer.Error($"Backup directory not found: {backupPath}"); return false; } - // Find backed up kernel modules (.ko files) var koFiles = Directory.GetFiles(backupPath, "*.ko", SearchOption.AllDirectories); - - if (!koFiles.Any()) + + if (koFiles.Length == 0) { GeneralTracer.Warn($"No kernel module backups found in: {backupPath}"); return false; @@ -146,15 +102,21 @@ public async Task RollbackAsync(string backupPath, CancellationToken cance { try { - GeneralTracer.Info($"Attempting to restore kernel module: {koFile}"); - - // Copy back to /lib/modules or appropriate location var moduleName = Path.GetFileNameWithoutExtension(koFile); - - // Try to unload current module first - await ExecuteCommandAsync("modprobe", $"-r {moduleName}", cancellationToken); - - // Try to reload the backed-up module (if system supports it) + GeneralTracer.Info($"Restoring kernel module: {moduleName}"); + + // Unload current module first + try + { + await ExecuteCommandAsync("modprobe", $"-r {moduleName}", cancellationToken); + } + catch + { + // Module might not be loaded — ignore + } + + // Reload the backed-up module + await ExecuteCommandAsync("insmod", koFile, cancellationToken); GeneralTracer.Info($"Restored module: {moduleName}"); } catch (Exception ex) @@ -167,223 +129,48 @@ public async Task RollbackAsync(string backupPath, CancellationToken cance } catch (Exception ex) { - GeneralTracer.Error("Failed to rollback driver", ex); + GeneralTracer.Error("Linux driver rollback failed", ex); return false; } } - private async Task ExecuteDriverInstallationAsync(DriverInfo driverInfo, CancellationToken cancellationToken) - { - GeneralTracer.Info($"Installing Linux driver: {driverInfo.FilePath}"); - - var filePath = driverInfo.FilePath; - var extension = Path.GetExtension(filePath).ToLowerInvariant(); - - try - { - // Handle different Linux driver formats - if (extension == ".ko") - { - // Kernel module installation - await InstallKernelModuleAsync(filePath, cancellationToken); - } - else if (extension == ".deb") - { - // Debian package installation - await InstallDebPackageAsync(filePath, cancellationToken); - } - else if (extension == ".rpm") - { - // RPM package installation - await InstallRpmPackageAsync(filePath, cancellationToken); - } - else - { - GeneralTracer.Warn($"Unknown driver format: {extension}. Attempting generic installation."); - // Try to detect and install generically - await InstallKernelModuleAsync(filePath, cancellationToken); - } - - GeneralTracer.Info("Driver installation completed successfully"); - } - catch (Exception ex) - { - GeneralTracer.Error("Failed to install Linux driver", ex); - throw new DriverInstallationException( - $"Failed to install Linux driver: {ex.Message}", ex); - } - } - - private async Task InstallKernelModuleAsync(string modulePath, CancellationToken cancellationToken) - { - GeneralTracer.Info($"Installing kernel module: {modulePath}"); - - var moduleName = Path.GetFileNameWithoutExtension(modulePath); - - try - { - // Try to use insmod (direct installation) - GeneralTracer.Info("Attempting to load module using insmod"); - await ExecuteCommandAsync("insmod", modulePath, cancellationToken); - GeneralTracer.Info("Module loaded successfully using insmod"); - } - catch - { - try - { - // Fallback to modprobe if insmod fails - GeneralTracer.Info("Attempting to load module using modprobe"); - - // Copy to modules directory first (may require permissions) - var kernelVersion = await GetKernelVersionAsync(cancellationToken); - var targetDir = $"/lib/modules/{kernelVersion}/extra"; - - GeneralTracer.Info($"Target module directory: {targetDir}"); - - // Note: This would typically require root permissions - // In a real scenario, you'd use sudo or elevated permissions - - await ExecuteCommandAsync("modprobe", moduleName, cancellationToken); - GeneralTracer.Info("Module loaded successfully using modprobe"); - } - catch (Exception ex) - { - GeneralTracer.Error("Failed to load kernel module", ex); - throw; - } - } - } + // ─── Driver discovery overrides ──────────────────────────────────── - private async Task InstallDebPackageAsync(string packagePath, CancellationToken cancellationToken) - { - GeneralTracer.Info($"Installing Debian package: {packagePath}"); - - try - { - // Use dpkg to install the package - await ExecuteCommandAsync("dpkg", $"-i {packagePath}", cancellationToken); - GeneralTracer.Info("Debian package installed successfully"); - } - catch (Exception ex) - { - GeneralTracer.Error("Failed to install Debian package", ex); - throw; - } - } - - private async Task InstallRpmPackageAsync(string packagePath, CancellationToken cancellationToken) - { - GeneralTracer.Info($"Installing RPM package: {packagePath}"); - - try - { - // Try rpm command first - try - { - await ExecuteCommandAsync("rpm", $"-ivh {packagePath}", cancellationToken); - } - catch - { - // Fallback to dnf/yum - await ExecuteCommandAsync("dnf", $"install -y {packagePath}", cancellationToken); - } - - GeneralTracer.Info("RPM package installed successfully"); - } - catch (Exception ex) - { - GeneralTracer.Error("Failed to install RPM package", ex); - throw; - } - } - - private async Task GetKernelVersionAsync(CancellationToken cancellationToken) - { - try - { - var output = await ExecuteCommandAsync("uname", "-r", cancellationToken); - return output.Trim(); - } - catch - { - return "current"; - } - } - - private async Task ExecuteCommandAsync(string command, string arguments, CancellationToken cancellationToken) - { - var startInfo = new System.Diagnostics.ProcessStartInfo - { - FileName = command, - Arguments = arguments, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = System.Diagnostics.Process.Start(startInfo); - if (process == null) - { - throw new InvalidOperationException($"Failed to start process: {command}"); - } - - var output = await process.StandardOutput.ReadToEndAsync(cancellationToken); - var error = await process.StandardError.ReadToEndAsync(cancellationToken); - - await process.WaitForExitAsync(cancellationToken); - - if (process.ExitCode != 0) - { - GeneralTracer.Warn($"Command {command} {arguments} exited with code {process.ExitCode}. Error: {error}"); - throw new InvalidOperationException($"Command failed with exit code {process.ExitCode}: {error}"); - } - - return output; - } - - private string GenerateBackupPath(DriverInfo driverInfo, string baseBackupPath) - { - if (string.IsNullOrEmpty(baseBackupPath)) - { - baseBackupPath = "/var/backup/drivers"; - } - - var fileName = Path.GetFileName(driverInfo.FilePath); - return Path.Combine(baseBackupPath, fileName); - } + /// + protected override string GetDefaultSearchPattern() => "*.ko"; /// - public async Task> GetDriversFromDirectoryAsync( + public override async Task> GetDriversFromDirectoryAsync( string directoryPath, string? searchPattern = null, CancellationToken cancellationToken = default) { - var driverInfoList = new List(); + var drivers = new List(); try { - GeneralTracer.Info($"Reading driver information from directory: {directoryPath}"); + GeneralTracer.Info($"Reading Linux drivers from directory: {directoryPath}"); if (!Directory.Exists(directoryPath)) { GeneralTracer.Warn($"Directory not found: {directoryPath}"); - return driverInfoList; + return drivers; } - // Default to kernel modules for Linux - var pattern = searchPattern ?? "*.ko"; - var driverFiles = Directory.GetFiles(directoryPath, pattern, SearchOption.AllDirectories); + // Search for kernel modules, .deb, and .rpm packages + var driverFiles = new List(); + driverFiles.AddRange(Directory.GetFiles(directoryPath, searchPattern ?? "*.ko", + SearchOption.AllDirectories)); - // Also look for .deb and .rpm packages if no specific pattern was provided - if (searchPattern == null) + if (searchPattern is null) { - var debFiles = Directory.GetFiles(directoryPath, "*.deb", SearchOption.AllDirectories); - var rpmFiles = Directory.GetFiles(directoryPath, "*.rpm", SearchOption.AllDirectories); - driverFiles = driverFiles.Concat(debFiles).Concat(rpmFiles).ToArray(); + driverFiles.AddRange(Directory.GetFiles(directoryPath, "*.deb", + SearchOption.AllDirectories)); + driverFiles.AddRange(Directory.GetFiles(directoryPath, "*.rpm", + SearchOption.AllDirectories)); } - GeneralTracer.Info($"Found {driverFiles.Length} driver files matching pattern: {pattern}"); + GeneralTracer.Info($"Found {driverFiles.Count} Linux driver file(s)"); foreach (var filePath in driverFiles) { @@ -392,12 +179,9 @@ public async Task> GetDriversFromDirectoryAsync( try { - var driverInfo = await ParseDriverFileAsync(filePath, cancellationToken); - if (driverInfo != null) - { - driverInfoList.Add(driverInfo); - GeneralTracer.Info($"Parsed driver: {driverInfo.Name} v{driverInfo.Version}"); - } + var driverInfo = await ParseLinuxDriverFileAsync(filePath, cancellationToken); + if (driverInfo is not null) + drivers.Add(driverInfo); } catch (Exception ex) { @@ -405,51 +189,126 @@ public async Task> GetDriversFromDirectoryAsync( } } - GeneralTracer.Info($"Successfully loaded {driverInfoList.Count} driver(s) from directory"); + GeneralTracer.Info($"Loaded {drivers.Count} Linux driver(s) from directory"); } catch (Exception ex) { GeneralTracer.Error($"Error reading drivers from directory: {directoryPath}", ex); } - return driverInfoList; + return drivers; } + // ─── Private: Pipeline steps ──────────────────────────────────────── + /// - /// Parses driver file information + /// Creates a sudo privilege check as the first pipeline step. /// - private async Task ParseDriverFileAsync(string filePath, CancellationToken cancellationToken) + private static IPipelineStep CreateSudoCheckStep() { + return new DelegateStep("CheckSudo", + execute: async (context, ct) => + { + context.Result.StepLogs.Add($"[{DateTime.Now:HH:mm:ss}] Checking Linux root privileges"); + + try + { + await LinuxPermissionHelper.EnsureSudoAsync(); + return PipelineResult.Ok(); + } + catch (Exception ex) + { + return PipelineResult.Fail( + $"Root privileges are required for driver updates. {ex.Message}"); + } + }); + } + + // ─── Private: Format-specific installers ─────────────────────────── + + private static async Task InstallKernelModuleAsync( + string modulePath, + CancellationToken cancellationToken) + { + GeneralTracer.Info($"Installing kernel module: {modulePath}"); + try { - var fileInfo = new FileInfo(filePath); - var fileName = Path.GetFileNameWithoutExtension(filePath); - var extension = Path.GetExtension(filePath).ToLowerInvariant(); + await ExecuteCommandAsync("insmod", modulePath, cancellationToken); + GeneralTracer.Info("Module loaded via insmod"); + } + catch + { + // Fallback: modprobe with the module name + GeneralTracer.Info("insmod failed, trying modprobe..."); + var moduleName = Path.GetFileNameWithoutExtension(modulePath); + await ExecuteCommandAsync("modprobe", moduleName, cancellationToken); + GeneralTracer.Info("Module loaded via modprobe"); + } + } + + private static async Task InstallDebPackageAsync( + string packagePath, + CancellationToken cancellationToken) + { + GeneralTracer.Info($"Installing Debian package: {packagePath}"); + await ExecuteCommandAsync("dpkg", $"-i {packagePath}", cancellationToken); + GeneralTracer.Info("Debian package installed"); + } + + private static async Task InstallRpmPackageAsync( + string packagePath, + CancellationToken cancellationToken) + { + GeneralTracer.Info($"Installing RPM package: {packagePath}"); + + try + { + await ExecuteCommandAsync("rpm", $"-ivh {packagePath}", cancellationToken); + } + catch + { + // Fallback to dnf/yum + await ExecuteCommandAsync("dnf", $"install -y {packagePath}", cancellationToken); + } + + GeneralTracer.Info("RPM package installed"); + } + // ─── Private: File parsing ───────────────────────────────────────── + + private static async Task ParseLinuxDriverFileAsync( + string filePath, + CancellationToken cancellationToken) + { + try + { var driverInfo = new DriverInfo { - Name = fileName, + Name = Path.GetFileNameWithoutExtension(filePath), FilePath = filePath, TargetOS = "Linux", Architecture = Environment.Is64BitOperatingSystem ? "x64" : "x86" }; - // Parse based on file type - if (extension == ".ko") - { - await ParseKernelModuleAsync(filePath, driverInfo, cancellationToken); - } - else if (extension == ".deb") - { - await ParseDebPackageAsync(filePath, driverInfo, cancellationToken); - } - else if (extension == ".rpm") + var extension = Path.GetExtension(filePath).ToLowerInvariant(); + + switch (extension) { - await ParseRpmPackageAsync(filePath, driverInfo, cancellationToken); + case ".ko": + await ParseKernelModuleAsync(filePath, driverInfo, cancellationToken); + break; + case ".deb": + await ParseDebPackageAsync(filePath, driverInfo, cancellationToken); + break; + case ".rpm": + await ParseRpmPackageAsync(filePath, driverInfo, cancellationToken); + break; } - // Get file hash for integrity validation - driverInfo.Hash = await HashValidator.ComputeHashAsync(filePath, "SHA256", cancellationToken); + // Compute file hash for integrity validation + driverInfo.Hash = await HashValidator.ComputeHashAsync( + filePath, "SHA256", cancellationToken); driverInfo.HashAlgorithm = "SHA256"; return driverInfo; @@ -461,134 +320,167 @@ public async Task> GetDriversFromDirectoryAsync( } } - /// - /// Parses kernel module - /// - private async Task ParseKernelModuleAsync(string koPath, DriverInfo driverInfo, CancellationToken cancellationToken) + private static async Task ParseKernelModuleAsync( + string koPath, + DriverInfo driverInfo, + CancellationToken cancellationToken) { try { - // Try to get module info using modinfo command var output = await ExecuteCommandAsync("modinfo", koPath, cancellationToken); var lines = output.Split('\n'); foreach (var line in lines) { - var trimmedLine = line.Trim(); + var trimmed = line.Trim(); - if (trimmedLine.StartsWith("version:", StringComparison.OrdinalIgnoreCase)) + if (trimmed.StartsWith("version:", StringComparison.OrdinalIgnoreCase)) + driverInfo.Version = trimmed[8..].Trim(); + else if (trimmed.StartsWith("description:", StringComparison.OrdinalIgnoreCase)) + driverInfo.Description = trimmed[12..].Trim(); + else if (trimmed.StartsWith("alias:", StringComparison.OrdinalIgnoreCase)) { - driverInfo.Version = trimmedLine.Substring(8).Trim(); - } - else if (trimmedLine.StartsWith("description:", StringComparison.OrdinalIgnoreCase)) - { - driverInfo.Description = trimmedLine.Substring(12).Trim(); - } - else if (trimmedLine.StartsWith("alias:", StringComparison.OrdinalIgnoreCase)) - { - var alias = trimmedLine.Substring(6).Trim(); if (string.IsNullOrEmpty(driverInfo.HardwareId)) - { - driverInfo.HardwareId = alias; - } + driverInfo.HardwareId = trimmed[6..].Trim(); } } if (string.IsNullOrEmpty(driverInfo.Version)) - { driverInfo.Version = "1.0.0"; - } } catch (Exception ex) { - GeneralTracer.Debug($"Could not get module info for: {koPath} - {ex.Message}"); + GeneralTracer.Debug($"Could not get module info for {koPath}: {ex.Message}"); driverInfo.Version = "1.0.0"; } } - /// - /// Parses Debian package - /// - private async Task ParseDebPackageAsync(string debPath, DriverInfo driverInfo, CancellationToken cancellationToken) + private static async Task ParseDebPackageAsync( + string debPath, + DriverInfo driverInfo, + CancellationToken cancellationToken) { try { - // Try to get package info using dpkg-deb command - // Use proper argument passing to avoid injection issues var escapedPath = debPath.Replace("'", "'\\''"); var output = await ExecuteCommandAsync("dpkg-deb", $"-I '{escapedPath}'", cancellationToken); - var lines = output.Split('\n'); - foreach (var line in lines) + foreach (var line in output.Split('\n')) { - var trimmedLine = line.Trim(); + var trimmed = line.Trim(); - if (trimmedLine.StartsWith("Version:", StringComparison.OrdinalIgnoreCase)) - { - driverInfo.Version = trimmedLine.Substring(8).Trim(); - } - else if (trimmedLine.StartsWith("Description:", StringComparison.OrdinalIgnoreCase)) - { - driverInfo.Description = trimmedLine.Substring(12).Trim(); - } + if (trimmed.StartsWith("Version:", StringComparison.OrdinalIgnoreCase)) + driverInfo.Version = trimmed[8..].Trim(); + else if (trimmed.StartsWith("Description:", StringComparison.OrdinalIgnoreCase)) + driverInfo.Description = trimmed[12..].Trim(); } if (string.IsNullOrEmpty(driverInfo.Version)) - { driverInfo.Version = "1.0.0"; - } } catch (Exception ex) { - GeneralTracer.Debug($"Could not get package info for: {debPath} - {ex.Message}"); + GeneralTracer.Debug($"Could not get package info for {debPath}: {ex.Message}"); driverInfo.Version = "1.0.0"; } } - /// - /// Parses RPM package - /// - private async Task ParseRpmPackageAsync(string rpmPath, DriverInfo driverInfo, CancellationToken cancellationToken) + private static async Task ParseRpmPackageAsync( + string rpmPath, + DriverInfo driverInfo, + CancellationToken cancellationToken) { try { - // Try to get package info using rpm command - // Use proper argument passing to avoid injection issues var escapedPath = rpmPath.Replace("'", "'\\''"); var output = await ExecuteCommandAsync("rpm", $"-qip '{escapedPath}'", cancellationToken); - var lines = output.Split('\n'); - foreach (var line in lines) + foreach (var line in output.Split('\n')) { - var trimmedLine = line.Trim(); + var trimmed = line.Trim(); - if (trimmedLine.StartsWith("Version", StringComparison.OrdinalIgnoreCase)) + if (trimmed.StartsWith("Version", StringComparison.OrdinalIgnoreCase)) { - var parts = trimmedLine.Split(':'); + var parts = trimmed.Split(':'); if (parts.Length > 1) - { driverInfo.Version = parts[1].Trim(); - } } - else if (trimmedLine.StartsWith("Summary", StringComparison.OrdinalIgnoreCase)) + else if (trimmed.StartsWith("Summary", StringComparison.OrdinalIgnoreCase)) { - var parts = trimmedLine.Split(':'); + var parts = trimmed.Split(':'); if (parts.Length > 1) - { driverInfo.Description = parts[1].Trim(); - } } } if (string.IsNullOrEmpty(driverInfo.Version)) - { driverInfo.Version = "1.0.0"; - } } catch (Exception ex) { - GeneralTracer.Debug($"Could not get package info for: {rpmPath} - {ex.Message}"); + GeneralTracer.Debug($"Could not get package info for {rpmPath}: {ex.Message}"); driverInfo.Version = "1.0.0"; } } + + // ─── Private: Command execution ──────────────────────────────────── + + private static async Task ExecuteCommandAsync( + string command, + string arguments, + CancellationToken cancellationToken) + { + var psi = new System.Diagnostics.ProcessStartInfo + { + FileName = command, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = System.Diagnostics.Process.Start(psi) + ?? throw new InvalidOperationException($"Failed to start process: {command}"); + + var output = await process.StandardOutput.ReadToEndAsync(cancellationToken); + var error = await process.StandardError.ReadToEndAsync(cancellationToken); + await process.WaitForExitAsync(cancellationToken); + + if (process.ExitCode != 0) + { + GeneralTracer.Warn($"Command {command} {arguments} exited with code {process.ExitCode}. Error: {error}"); + throw new InvalidOperationException( + $"Command '{command}' failed with exit code {process.ExitCode}: {error}"); + } + + return output; + } + + // ─── Nested type ────────────────────────────────────────────────── + + /// + /// A lightweight pipeline step backed by delegates. + /// + private sealed class DelegateStep : IPipelineStep + { + private readonly Func> _execute; + + public string StepName { get; } + + public DelegateStep( + string stepName, + Func> execute) + { + StepName = stepName; + _execute = execute; + } + + public bool ShouldExecute(PipelineContext context) => true; + + public Task ExecuteAsync( + PipelineContext context, + CancellationToken cancellationToken) + => _execute(context, cancellationToken); + } }