临时文件的难点不只是“用完后删除”。同一台机器上的程序可能争用文件名,长时间运行的任务可能撞上自动清理,内存文件系统也可能耗尽空间。systemd 的《Using /tmp/ and /var/tmp/ Safely》把这些问题放在一起:先选对存放位置,再用安全的创建方式,最后明确谁负责资源的整个生命周期。
本文根据 systemd 官方原文翻译整理。原页未标个人作者,归属 systemd 项目;源文标注 LGPL-2.1-or-later。本文保留这一声明,注明中文翻译及版本校订。本文仅做静态核对,没有更改服务、文件系统或清理规则。

先分清目录承诺了什么
/tmp/ 与 /var/tmp/ 都是 Linux 提供的公共可写临时目录。常见系统把前者放在 tmpfs 上,数据占用内存或交换空间,并在重启时清空;后者通常位于持久文件系统上。因而,原文建议只把体积小且有明确大小上限的文件放进 /tmp/,其他临时数据考虑 /var/tmp/;需要跨重启保留的数据不能依赖 /tmp/。
“通常”不是保证。实际挂载方式、发行版规则和管理员配置才决定行为。/var/tmp/ 也不是长期存档目录,它仍可能接受定期清理。如果应用收到可信环境中的 TMPDIR,应优先使用该路径,不要硬编码这两个目录;涉及提权或不可信调用者时,还应由程序自己的安全策略判断是否接受环境变量。目录层级的具体用途可查 file-hierarchy(7)。
公共名称空间里,固定名字就是竞争入口
假设程序准备创建 /tmp/foo。另一名本地用户或另一个实例可以提前占用这个名字,令程序失败;更危险的实现还可能接受对方预先放好的文件或符号链接。先检查不存在、随后再创建,仍然留下检查与使用之间的竞争窗口。可预测文件名至少会造成拒绝服务风险,错误的打开方式还可能把读写导向不可信对象。
应把随机选择名字和安全创建交给合适的 API:POSIX 的 mkstemp()、mkdtemp(),glibc 的 mkostemp(),以及标准 C 的 tmpfile()。Linux 还可以使用带 O_TMPFILE 的 open(),或者 memfd_create()。最后一种直接创建由内存/交换空间支持的匿名文件,不经过 /tmp/ 或 /var/tmp/ 的公共路径。
原文提到一种例外:在启动早期由 tmpfiles.d 预建专用子目录,再在里面使用固定名字,X11 的 /tmp/.X11-unix/ 属于类似做法。不过如果软件包能够在运行期间安装,创建时机和目录占用就会变得复杂。因此它不是一般应用的首选方案。安全的目录权限和随机创建仍然更容易解释和维护。
PrivateTmp 是附加隔离,也会改变共享和清理语义
systemd 服务可以设置以下单元选项:
[Service]
PrivateTmp=yes
这是配置片段,不是完整服务文件。启用后,systemd 通过文件系统名称空间和绑定挂载,为该服务呈现独立的 /tmp/ 和 /var/tmp/。服务看到的路径不变,实际使用的是宿主机临时目录里的私有区域,减少了跨服务名称冲突和本地攻击面。
应用仍应使用安全的创建 API。某些容器环境不允许文件系统名称空间,不能假定这层隔离始终可用。另一方面,依赖两个服务通过 /tmp/ 传文件的设计会失效,因为两个服务看到的临时空间不同。启用该设置还会让这些私有临时目录随服务停止而清理;即使路径叫 /var/tmp/,也不应再据此推断内容跨服务重启存在。准备启用前,要核对应用是否使用临时路径进行进程间协作。
自动清理解决残留,也可能打断长任务
systemd-tmpfiles 会根据时间戳和配置实施老化清理,使异常退出、没有自行收尾的程序不至于无限堆积临时文件。原文列出的 /tmp/ 10 天、/var/tmp/ 30 天是其描述的默认策略,不能照搬为所有主机的承诺。管理员应检查本机合并后的 tmpfiles 配置、清理定时器及对应版本的手册。
这对长期任务提出一个问题:程序还认为文件有用,但路径已经被清理器移除。原文给出了七种策略,它们的适用对象和代价不同。
- 始终持有文件描述符,并通过它访问文件。文件从目录树中解除链接后,只要描述符还打开,进程仍能读写它。若不需要名称,可以创建后立即
unlink()。最后一个引用关闭后,内核负责回收资源;程序异常退出也会关闭其描述符。这保护的是已打开文件,不能让以后按原路径重新打开的操作继续成功。 - 直接创建匿名文件。
memfd_create()和O_TMPFILE省去了“先存在一个名称,再解除链接”的窗口。没有目录项,就没有按路径进行老化删除的对象。这尤其适合只需文件描述符的临时普通文件;不能拿它替代整个临时目录树。 - 对文件或目录持有 BSD 文件锁。
flock()的共享锁或排他锁可使 systemd-tmpfiles 的老化算法跳过被锁文件,或被锁目录及其子树。它适合需要多个文件、特殊文件节点或解包目录的任务。进程退出后锁随描述符关闭释放,清理器又能回收遗留内容。解压保留旧时间戳的 tar 包尤其需要留意:刚解出来的文件可能已符合老化条件。shell 可使用 util-linux 的flock工具表达这种生命周期。 - 定期更新时间戳。用
utimensat()等接口维持文件访问时间,可以避免满足原文所述的老化条件。但这要求覆盖所有要保护的对象,正确设置更新频率,并理解实际清理器的判定。它比“资源存活等于描述符或锁存活”更容易遗漏。 - 普通文件使用 sticky bit。原文所述的 systemd-tmpfiles 逻辑跳过带 sticky bit 的普通文件。这条规则不适用于目录:目录上的 sticky bit 有不同的权限语义。不要把它误认为所有清理程序通用的“保留”标志。
- 选用应用自己的运行时或状态目录。特权服务可考虑
/run/下的专用目录,非特权应用可考虑XDG_RUNTIME_DIR;需要持久数据则应按用途使用专用状态目录,例如/var/lib/。原文也举到~/.config/,但不应因此把大块临时数据随意塞进用户配置目录。这类方案适合系统范围单实例、较长期状态的服务;程序要自行处理正常和异常退出的残留。/run/是运行时目录,不提供跨重启持久性。 - 通过 tmpfiles.d 排除。
x、X类型可按各自规则排除匹配路径的老化清理,也可使用/run/tmpfiles.d/中的运行时配置。应查本机手册区分两种类型对目录和内容的处理。排除意味着放弃这层兜底,应用必须有可靠的回收办法,不能只添加一条永不清理的规则就结束。
原文优先推荐第二和第三种:只处理普通文件时,优先匿名文件;需要目录及多文件时,优先使用安全创建的目录并持有锁。这里关于锁和 sticky bit 的说法针对 systemd-tmpfiles 的相应清理语义,其他清理器可能不遵守。部署时应核对实际执行清理的软件。
空间不足要成为正常错误路径
公共临时目录共享容量,/tmp/ 常比持久磁盘空间紧张。匿名文件也会占内存、交换空间或文件系统容量,并不会消除资源耗尽风险。程序应限定临时数据规模,处理 ENOSPC,在错误消息中说明哪个位置没有空间,以及用户能采取什么措施。关键程序可以设计另一存放位置的回退,但回退时仍须维持权限、安全创建和容量限制。
版本校订:原文称 tmpfs 在内核中仍无原生配额支持,这一表述已经过时。当前 Linux 内核 tmpfs 文档列出 quota、usrquota、grpquota 及用户/组块数和 inode 硬限制等挂载选项,并说明相关选项不能在重新挂载时任意修改。实际是否可用仍取决于运行内核和挂载配置。不能从“tmpfs 支持配额”推断本机已开启配额,也不能从旧文推断它绝对不支持。
启动早期不要依赖常规临时目录
在 basic.target,更具体地说 local-fs.target 就绪前,/tmp/ 与 /var/tmp/ 可能尚未可用,或者不可写。启动早期组件更适合使用 memfd_create(),或 /run/ 下属于本程序的目录。
/dev/shm/ 也不是通用临时目录的替代品。它用于 POSIX 共享内存,通常同样由 tmpfs 支持,却是公共可写区域,也不提供本文讨论的通用自动清理保证。对程序而言,临时资源的位置、名称、打开的描述符、锁以及退出收尾应当组成同一个设计,而不是彼此独立的补丁。
核对日期:2026-10-05。本文的示意配置和 API 说明未执行;静态阅读不等于漏洞审计,也不证明目标主机的具体配置符合这些条件。配图由未完纪自绘。












暂无评论内容