Skip to content

fix(updater): macOS 更新报错 Cross-device link (os error 18) #156

@jorben

Description

@jorben

问题描述

用户在菜单中点击"检查更新"并下载安装时,macOS 上报错:

Cross-device link (os error 18)

根因分析

tauri-plugin-updater 2.10.1 的 macOS 安装逻辑 (updater.rs install_inner) 使用 tempfile::Builder::new().tempdir() 在系统临时目录(/var/folders/...)创建备份和解压目录,然后用 std::fs::rename 移动 .app bundle。

当 App 不在系统卷时(外置硬盘、非系统分区、DMG 等),rename 无法跨文件系统操作,触发 POSIX EXDEV 错误。

插件只对 PermissionDenied 做了 admin fallback(AppleScript mv -f),EXDEV 没有 fallback,错误直接暴露给前端。

出错位置

tauri-plugin-updater/src/updater.rs:1255
std::fs::rename(&self.extract_path, tmp_backup_dir.path().join("current_app"))

上游状态

解决方案

tauri-plugin-updater 的 macOS install_inner 做两处补丁,通过 [patch.crates-io] 集成。

补丁 1:临时目录优先同卷创建

tempdir() 改为 tempdir_in(app_parent),优先在 .app 同目录创建临时文件,确保 rename 在同一文件系统内操作。父目录不可写时回退到系统 temp。

// 原始
let tmp_backup_dir = tempfile::Builder::new()
    .prefix("tauri_current_app")
    .tempdir()?;
let tmp_extract_dir = tempfile::Builder::new()
    .prefix("tauri_updated_app")
    .tempdir()?;

// 修改后
let app_parent = self.extract_path.parent();

let tmp_backup_dir = app_parent
    .and_then(|p| {
        tempfile::Builder::new()
            .prefix(".tauri_current_app")
            .tempdir_in(p)
            .ok()
    })
    .map_or_else(
        || tempfile::Builder::new().prefix("tauri_current_app").tempdir(),
        Ok,
    )?;

let tmp_extract_dir = app_parent
    .and_then(|p| {
        tempfile::Builder::new()
            .prefix(".tauri_updated_app")
            .tempdir_in(p)
            .ok()
    })
    .map_or_else(
        || tempfile::Builder::new().prefix("tauri_updated_app").tempdir(),
        Ok,
    )?;

补丁 2:EXDEV 走管理员 fallback

将 EXDEV 错误归入 admin authorization 路径,利用 AppleScript mv -f 天然支持跨设备移动。

// 原始
let need_authorization = if let Err(err) = move_result {
    if err.kind() == std::io::ErrorKind::PermissionDenied {
        true
    } else {
        std::fs::remove_dir_all(tmp_extract_dir.path()).ok();
        return Err(err.into());
    }
} else {
    false
};

// 修改后
let need_authorization = if let Err(err) = move_result {
    if err.kind() == std::io::ErrorKind::PermissionDenied
        || err.raw_os_error() == Some(18) // libc::EXDEV
    {
        true
    } else {
        std::fs::remove_dir_all(tmp_extract_dir.path()).ok();
        return Err(err.into());
    }
} else {
    false
};

场景覆盖

场景 补丁 1 效果 补丁 2 效果
App 在外置盘、用户有写权限 tempdir_in 同卷成功,rename 正常 不触发
App 在 /Applications、无写权限 tempdir_in 失败,回退系统 temp EXDEV → admin mv -f
App 在系统盘(正常情况) tempdir_in 同卷成功 不触发

集成方式

src-tauri/Cargo.toml 中添加:

[patch.crates-io]
tauri-plugin-updater = { git = "https://github.com/TiyAgents/plugins-workspace", branch = "fix/macos-exdev-updater" }

上游 #1983 合并发布后移除 patch。

影响范围

  • 仅影响 macOS 更新安装流程
  • 不影响 Windows / Linux 更新路径
  • 不影响检查更新、下载流程

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions