Wine 运行 PyInstaller onefile 程序报错:INTERNAL ERROR: cannot create temporary directory! 的排查与修复

背景

在 Linux 上用 Wine 运行某些 Windows 程序(尤其是 PyInstaller 的 onefile 打包产物,例如 labelImg.exe)时,可能会直接退出并提示:

[PID] INTERNAL ERROR: cannot create temporary directory!

同时常见伴随信息包括:

  • ... starting ... in experimental wow64 mode(运行 32 位程序时的 wow64 提示)
  • 程序目录或 Wine 前缀本身看起来没有明显权限问题

这篇记录一个可复用的定位思路,以及对“已发布 exe、无法重打包”的情况下的可行修复方法。


现象复现与关键线索

运行程序:

wine labelImg.exe

报错:

[PID] INTERNAL ERROR: cannot create temporary directory!

进一步检查 Wine 的临时目录(默认是 C:\users\<name>\Temp,映射到 ~/.wine/drive_c/users/<name>/Temp)后,发现大量 _MEIxxxx 目录(PyInstaller onefile 运行时解包目录)权限异常:

ls -ld ~/.wine/drive_c/users/*/Temp/_MEI* | head

典型异常权限类似:

d--------- 2 user user 4096 ... _MEI1234

这意味着目录虽然“存在”,但当前用户在 Linux 侧 没有任何读写执行权限,在 Wine 内部访问就会变成 Access denied,从而触发 PyInstaller 的 “cannot create temporary directory”。


根因:PyInstaller 的安全修复 + Wine 对特定 SID 的兼容问题

PyInstaller 曾做过一个 Windows 侧的安全修复(CVE-2019-16784),核心是:

  • onefile 解包目录默认会被创建在 %TEMP% 下(例如 _MEI1234
  • 为避免在某些场景(比如服务账户的 C:\Windows\Temp)发生本地提权,PyInstaller 的 bootloader 会给 _MEIxxxx 目录设置非常严格的 ACL
  • 早期实现里会用一个特殊的 SID:S-1-3-4(在 Windows 上可代表“当前目录所有者”一类语义)

问题在于:Wine 对 S-1-3-4 的 ACL 处理不完整/不兼容,导致创建出来的 _MEIxxxx 目录在宿主 Linux 文件系统上落成 000 权限(d---------),于是程序自己也进不去,最终报:

INTERNAL ERROR: cannot create temporary directory!

如何快速验证

1) 在 exe 内搜索 S-1-3-4

很多 PyInstaller onefile 程序会把相关 ACL 描述串直接编进 exe(UTF-16LE 文本)。可以直接查:

strings -el labelImg.exe | rg "S-1-3-4"

若能搜到类似:

D:(A;;FA;;;S-1-3-4)

基本就坐实了:这是 PyInstaller bootloader 的 ACL 逻辑触发了 Wine 兼容问题。

2) 验证 _MEIxxxx 目录确实不可访问

在 Wine 的 cmd 里尝试进入目录(会 Access denied):

wine cmd /c "dir %TEMP% | findstr _MEI"
wine cmd /c "cd %TEMP%\\_MEI1234 && echo OK"

解决方案(无需重打包):二进制补丁替换 SID

如果你拿到的只有一个 labelImg.exe,无法从源码重新用新版 PyInstaller 打包,一个“务实可用”的办法是:

把 exe 里的 S-1-3-4 替换为 S-1-1-0(Everyone),让 _MEIxxxx 目录不再被 Wine 弄成 000 权限,从而能正常解包启动。

步骤

labelImg.exe 所在目录执行:

cp -n labelImg.exe labelImg.exe.bak

python - <<'PY'
from pathlib import Path

path = Path("labelImg.exe")
data = path.read_bytes()

old = "S-1-3-4".encode("utf-16le")
new = "S-1-1-0".encode("utf-16le")  # Everyone

assert len(old) == len(new)
count = data.count(old)
assert count >= 1, "pattern not found"

path.write_bytes(data.replace(old, new))
print("patched occurrences:", count)
PY

然后再运行:

wine labelImg.exe

回滚

如果要恢复原版:

mv -f labelImg.exe.bak labelImg.exe

可选:清理残留的坏 _MEIxxxx 目录

旧的 _MEIxxxx 目录可能已经是 000 权限,会越积越多。可以在确保没有 Wine 程序运行后清理:

# 先尽量关闭 Wine 后台服务(如果 wineserver 本身已退出,这条可能返回非 0,可以忽略)
wineserver -k || true

find ~/.wine/drive_c/users/*/Temp -maxdepth 1 -type d -name '_MEI*' \
  -exec chmod 700 {} + \
  -exec rm -rf {} +

说明:wineserver -k 返回非 0 不一定是错误,常见原因是当前没有 wineserver 在跑。

更“正统”的长期方案(推荐)

上面的二进制补丁属于“救火”方式。更推荐的长期方案是:

  1. 重新打包

    • 用较新的 PyInstaller 版本重新构建(新版本已避免使用 Wine 不支持的 S-1-3-4 方案)
    • 或改用 onedir(不走 onefile 解包逻辑,直接避免 _MEIxxxx
  2. 不建议sudo wine ...

    • Wine 官方 FAQ 明确不建议用 root 跑 Wine;不仅有安全风险,还容易把 ~/.wine 权限弄坏

注意事项(安全/副作用)

  • 该补丁把 onefile 临时目录权限从“只允许所有者”放宽为“Everyone 可访问”,会降低 Windows 侧的隔离强度。
  • 但在 Wine 环境里,这通常是为了绕开兼容性问题的权衡;实际风险取决于你的使用场景(是否多人共享、是否运行不可信 exe 等)。

总结

当 Wine 运行 PyInstaller onefile 程序报 INTERNAL ERROR: cannot create temporary directory! 且出现 d---------_MEIxxxx 目录时,优先怀疑 PyInstaller bootloader 的 ACL(S-1-3-4)与 Wine 的兼容问题;对无法重打包的 exe,可用“等长替换 SID”的二进制补丁快速恢复可用性。


创作:framsit
编译:GPT 5.2

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐