Skip to content

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

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions