diff --git a/internal/app/methods_driver.go b/internal/app/methods_driver.go index 344233e..07a13cc 100644 --- a/internal/app/methods_driver.go +++ b/internal/app/methods_driver.go @@ -2536,6 +2536,9 @@ func installOptionalDriverAgentFromLocalPath(definition driverDefinition, filePa return installedDriverPackage{}, fmt.Errorf("导入本地驱动代理失败:%w", copyErr) } } + if validateErr := db.ValidateOptionalDriverAgentExecutable(driverType, executablePath); validateErr != nil { + return installedDriverPackage{}, validateErr + } hash, hashErr := hashFileSHA256(executablePath) if hashErr != nil { @@ -2793,11 +2796,15 @@ func ensureOptionalDriverAgentBinary(a *App, definition driverDefinition, execut info, err := os.Stat(executablePath) if err == nil && !info.IsDir() { - hash, hashErr := hashFileSHA256(executablePath) - if hashErr != nil { - return "", "", fmt.Errorf("读取已安装 %s 驱动代理摘要失败:%w", displayName, hashErr) + if validateErr := db.ValidateOptionalDriverAgentExecutable(driverType, executablePath); validateErr != nil { + _ = os.Remove(executablePath) + } else { + hash, hashErr := hashFileSHA256(executablePath) + if hashErr != nil { + return "", "", fmt.Errorf("读取已安装 %s 驱动代理摘要失败:%w", displayName, hashErr) + } + return fmt.Sprintf("local://existing/%s-driver-agent", driverType), hash, nil } - return fmt.Sprintf("local://existing/%s-driver-agent", driverType), hash, nil } if err == nil && info.IsDir() { return "", "", fmt.Errorf("%s 驱动代理路径被目录占用:%s", displayName, executablePath) @@ -2814,6 +2821,10 @@ func ensureOptionalDriverAgentBinary(a *App, definition driverDefinition, execut if copyErr := copyAgentBinary(sourcePath, executablePath); copyErr != nil { return "", "", fmt.Errorf("复制预置 %s 驱动代理失败:%w", displayName, copyErr) } + if validateErr := db.ValidateOptionalDriverAgentExecutable(driverType, executablePath); validateErr != nil { + _ = os.Remove(executablePath) + return "", "", validateErr + } hash, hashErr := hashFileSHA256(executablePath) if hashErr != nil { return "", "", fmt.Errorf("计算预置 %s 驱动代理摘要失败:%w", displayName, hashErr) @@ -2901,6 +2912,10 @@ func downloadOptionalDriverAgentBinary(a *App, definition driverDefinition, urlT if chmodErr := os.Chmod(executablePath, 0o755); chmodErr != nil && stdRuntime.GOOS != "windows" { return "", fmt.Errorf("设置代理权限失败:%w", chmodErr) } + if validateErr := db.ValidateOptionalDriverAgentExecutable(driverType, executablePath); validateErr != nil { + _ = os.Remove(executablePath) + return "", validateErr + } return hash, nil } @@ -3009,6 +3024,10 @@ func downloadOptionalDriverAgentFromBundle(a *App, definition driverDefinition, if chmodErr := os.Chmod(executablePath, 0o755); chmodErr != nil && stdRuntime.GOOS != "windows" { return "", "", fmt.Errorf("设置驱动代理权限失败:%w", chmodErr) } + if validateErr := db.ValidateOptionalDriverAgentExecutable(driverType, executablePath); validateErr != nil { + _ = os.Remove(executablePath) + return "", "", validateErr + } hash, err := hashFileSHA256(executablePath) if err != nil { return "", "", fmt.Errorf("计算驱动代理摘要失败:%w", err) @@ -3334,6 +3353,7 @@ func resolveOptionalDriverAgentDownloadURLs(definition driverDefinition, rawURL } func findExistingOptionalDriverAgentCandidate(definition driverDefinition, targetPath string) (string, bool) { + driverType := normalizeDriverType(definition.Type) targetAbs, _ := filepath.Abs(targetPath) candidates := resolveOptionalDriverAgentCandidatePaths(definition) for _, candidate := range candidates { @@ -3349,9 +3369,13 @@ func findExistingOptionalDriverAgentCandidate(definition driverDefinition, targe continue } info, statErr := os.Stat(absPath) - if statErr == nil && !info.IsDir() { - return absPath, true + if statErr != nil || info.IsDir() { + continue } + if validateErr := db.ValidateOptionalDriverAgentExecutable(driverType, absPath); validateErr != nil { + continue + } + return absPath, true } return "", false } diff --git a/internal/db/driver_agent_binary_check.go b/internal/db/driver_agent_binary_check.go new file mode 100644 index 0000000..762c720 --- /dev/null +++ b/internal/db/driver_agent_binary_check.go @@ -0,0 +1,74 @@ +package db + +import ( + "debug/pe" + "fmt" + "runtime" + "strings" +) + +const ( + peMachineI386 uint16 = 0x014c + peMachineAmd64 uint16 = 0x8664 + peMachineArm64 uint16 = 0xaa64 +) + +func windowsMachineLabel(machine uint16) string { + switch machine { + case peMachineI386: + return "windows-386" + case peMachineAmd64: + return "windows-amd64" + case peMachineArm64: + return "windows-arm64" + default: + return fmt.Sprintf("windows-unknown(0x%04x)", machine) + } +} + +func expectedWindowsMachineForGoArch(goarch string) (uint16, string, bool) { + switch strings.ToLower(strings.TrimSpace(goarch)) { + case "386": + return peMachineI386, "windows-386", true + case "amd64": + return peMachineAmd64, "windows-amd64", true + case "arm64": + return peMachineArm64, "windows-arm64", true + default: + return 0, "", false + } +} + +func validateWindowsExecutableMachine(pathText string) error { + file, err := pe.Open(pathText) + if err != nil { + return fmt.Errorf("无法识别为有效的 Windows 可执行文件:%w", err) + } + defer file.Close() + + expectedMachine, expectedLabel, ok := expectedWindowsMachineForGoArch(runtime.GOARCH) + if !ok { + return nil + } + actualMachine := file.FileHeader.Machine + if actualMachine != expectedMachine { + return fmt.Errorf("可执行文件架构不兼容(文件=%s,当前进程=%s)", windowsMachineLabel(actualMachine), expectedLabel) + } + return nil +} + +// ValidateOptionalDriverAgentExecutable 校验可选驱动代理二进制是否可在当前进程中执行。 +// 当前主要用于 Windows 下的 PE 架构兼容性校验,避免升级后复用到错误架构的旧代理。 +func ValidateOptionalDriverAgentExecutable(driverType string, executablePath string) error { + pathText := strings.TrimSpace(executablePath) + if pathText == "" { + return fmt.Errorf("%s 驱动代理路径为空", driverDisplayName(driverType)) + } + if runtime.GOOS != "windows" { + return nil + } + if err := validateWindowsExecutableMachine(pathText); err != nil { + return fmt.Errorf("%s 驱动代理不可用:%w", driverDisplayName(driverType), err) + } + return nil +} diff --git a/internal/db/driver_support.go b/internal/db/driver_support.go index 517a81a..db00717 100644 --- a/internal/db/driver_support.go +++ b/internal/db/driver_support.go @@ -194,6 +194,9 @@ func optionalGoDriverRuntimeReady(driverType string) (bool, string) { if statErr != nil || info.IsDir() { return false, fmt.Sprintf("%s 驱动代理缺失,请在驱动管理中重新安装启用", driverDisplayName(normalized)) } + if validateErr := ValidateOptionalDriverAgentExecutable(normalized, executablePath); validateErr != nil { + return false, fmt.Sprintf("%s;请在驱动管理中重新安装启用", validateErr.Error()) + } return true, "" } diff --git a/internal/db/driver_support_test.go b/internal/db/driver_support_test.go index 8dc5f62..002fba0 100644 --- a/internal/db/driver_support_test.go +++ b/internal/db/driver_support_test.go @@ -65,11 +65,22 @@ func TestManagedDriverRequiresInstallMarker(t *testing.T) { if err != nil { t.Fatalf("解析 mariadb 代理路径失败: %v", err) } - if err := os.WriteFile(executablePath, []byte("placeholder"), 0o755); err != nil { - t.Fatalf("写入 mariadb 代理占位文件失败: %v", err) - } if runtime.GOOS == "windows" { - _ = os.Chmod(executablePath, 0o644) + selfPath, selfErr := os.Executable() + if selfErr != nil { + t.Fatalf("获取测试进程路径失败: %v", selfErr) + } + content, readErr := os.ReadFile(selfPath) + if readErr != nil { + t.Fatalf("读取测试进程失败: %v", readErr) + } + if err := os.WriteFile(executablePath, content, 0o755); err != nil { + t.Fatalf("写入 mariadb 代理占位可执行文件失败: %v", err) + } + } else { + if err := os.WriteFile(executablePath, []byte("placeholder"), 0o755); err != nil { + t.Fatalf("写入 mariadb 代理占位文件失败: %v", err) + } } supported, reason := DriverRuntimeSupportStatus("mariadb") diff --git a/internal/db/optional_driver_agent_impl.go b/internal/db/optional_driver_agent_impl.go index 1b83902..2579b7c 100644 --- a/internal/db/optional_driver_agent_impl.go +++ b/internal/db/optional_driver_agent_impl.go @@ -9,8 +9,10 @@ import ( "io" "os" "os/exec" + "runtime" "strings" "sync" + "syscall" "time" "GoNavi-Wails/internal/connection" @@ -94,6 +96,9 @@ func newOptionalDriverAgentClient(driverType string, executablePath string) (*op return nil, fmt.Errorf("创建 %s 驱动代理 stderr 失败:%w", driverDisplayName(driverType), err) } if err := cmd.Start(); err != nil { + if isWindowsExecutableMachineMismatch(err) { + return nil, fmt.Errorf("启动 %s 驱动代理失败:%w(检测到驱动代理与当前系统架构不兼容,请在驱动管理中重新安装启用)", driverDisplayName(driverType), err) + } return nil, fmt.Errorf("启动 %s 驱动代理失败:%w", driverDisplayName(driverType), err) } @@ -107,6 +112,30 @@ func newOptionalDriverAgentClient(driverType string, executablePath string) (*op return client, nil } +func isWindowsExecutableMachineMismatch(err error) bool { + if err == nil || runtime.GOOS != "windows" { + return false + } + var errno syscall.Errno + if errors.As(err, &errno) && errno == syscall.Errno(216) { + return true + } + text := strings.ToLower(strings.TrimSpace(err.Error())) + if text == "" { + return false + } + if strings.Contains(text, "not compatible with the version of windows") { + return true + } + if strings.Contains(text, "win32") && strings.Contains(text, "compatible") { + return true + } + if strings.Contains(text, "不是有效的win32应用程序") || strings.Contains(text, "无法在win32模式下运行") { + return true + } + return false +} + func (c *optionalDriverAgentClient) captureStderr(stderr io.Reader) { scanner := bufio.NewScanner(stderr) buffer := make([]byte, 0, 8<<10)