本文译者是一位开源理念的坚定支持者,所以本文虽然不是软件,但是遵照开源的精神发布。
本文译者十分愿意与他人分享劳动成果,如果你对我的其他翻译作品或者技术文章有兴趣,可以在如下位置查看现有的作品集:
由于译者水平有限,因此不能保证译文内容准确无误。如果你发现了译文中的错误(哪怕是错别字也好),请来信指出,任何提高译文质量的建议我都将虚心接纳。
tmpfiles.d — 配置如何创建、删除、清理 易变文件与临时文件
/etc/tmpfiles.d/*.conf
/run/tmpfiles.d/*.conf
/usr/lib/tmpfiles.d/*.conf
~/.config/user-tmpfiles.d/*.conf
$XDG_RUNTIME_DIR/user-tmpfiles.d/*.conf
~/.local/share/user-tmpfiles.d/*.conf
…
/usr/share/user-tmpfiles.d/*.conf
tmpfiles.d
配置文件定义了一套临时文件管理机制:
创建 文件、目录、管道、设备节点,
调整 访问模式、所有者、属性、限额、内容,
删除 过期文件。
主要用于管理易变的临时文件与目录,例如 /run
,
/tmp
, /var/tmp
,
/sys
, /proc
,
以及 /var
下面的某些目录。
systemd-tmpfiles 根据这些配置,
在系统启动过程中创建易变的临时文件与目录,并在系统运行过程中进行周期性的清理。参见
systemd-tmpfiles(5)
手册以了解 systemd-tmpfiles-setup.service
,
systemd-tmpfiles-cleanup.service
等相关服务。
守护进程经常需要在 /run
目录下拥有专属的运行时目录,以存放通信套接字或管道之类的文件。
对于这种需求,如果不需要 tmpfiles.d
提供的强大灵活性,那么最好直接在单元文件中使用
RuntimeDirectory=
选项来实现(详见
systemd.exec(5) 手册)。
这样做的好处是简单明了,所有与此单元相关的配置都集中在同一个地方(单元文件),
并且运行时目录的生命周期与其所属的服务单元的生命周期一致。
类似的,应该分别使用 StateDirectory=
,
CacheDirectory=
, LogsDirectory=
,
ConfigurationDirectory=
选项来创建位于
/var/lib/
, /var/cache/
, /var/log/
,
/etc/
中的专属子目录。应该仅将 tmpfiles.d
用于管理那些生命周期独立于服务单元或需要复杂配置的文件与目录。
配置文件的名称必须符合
或
package
.conf
格式。
当需要明确的将某部分(part)配置提取出来,以方便用户专门针对这部分进行修改的时候,
应该使用第二种命名格式。package
-part
.conf
对于不同目录下的同名配置文件,仅以优先级最高的目录中的那一个为准。具体说来就是:
/etc/tmpfiles.d
的优先级最高、
/run/tmpfiles.d
的优先级居中、
/usr/lib/tmpfiles.d
的优先级最低。
软件包应该将自带的配置文件安装在 /usr/lib/tmpfiles.d
目录中,
而 /etc/tmpfiles.d
目录仅供系统管理员使用。
所有的配置文件,无论其位于哪个目录中,都统一按照文件名的字典顺序处理。
如果在多个配置文件中设置了同一个路径(文件或目录),那么仅以文件名最靠前(字典顺序)的那一个为准,
其他针对同一个路径的配置项将会作为警告信息记录到错误日志中。
如果有两行的路径互为前后缀,那么始终是先创建前缀行、再创建后缀行,
如果还需要删除,那么顺序正好相反,始终是先删除后缀行、再删除前缀行。
所有带有shell风格通配符的行,都在所有不带通配符的行之后处理。如果有多个操作符应用于同一个文件(例如 ACL,
xattr, 文件属性调整),那么将始终按固定的顺序操作。除上述清空之外,对于其他情况,
文件与目录总是按照它们在配置文件中出现的顺序处理。
如果系统管理员想要屏蔽 /usr/lib/
目录中的某个配置文件,
那么最佳做法是在 /etc/
目录中
创建一个指向 /dev/null
的同名符号链接,
即可彻底屏蔽 /usr/lib/
目录中的同名文件。
配置文件的格式是每行对应一个路径,包含如下字段: 类型, 路径, 权限, 属主, 属组, 寿命, 参数
#Type Path Mode User Group Age Argument d /run/user 0755 root root 10d - L /tmp/foobar - - - - /dev/null
字段值可以用引号界定,并可以包含C风格的转义字符。
"类型"字段由一个单独的、表示类型的字母 与一个可选的感叹号(!)和/或减号(-)组成。
可以识别的类型如下:
f
¶若指定的文件不存在,则创建它,否则什么也不做。 若设置了"参数"字段并且并且指定的文件不存在,则将其内容写入指定的文件。不追踪软连接。
F
¶若指定的文件不存在,则创建它,否则清空已有文件。 若设置了"参数"字段,则将其内容写入指定的文件。不追踪软连接。
w
¶若指定的文件存在,则将"参数"字段的内容写入该文件,否则什么也不做。 注意: (1)不会在"参数"字段内容的末尾添加额外的换行符。 (2)可以在"参数"字段中使用C语言风格的转义字符。 (3)可以在"路径"字段中使用shell风格的通配符。 (4)追踪软连接。
d
¶创建指定的目录并赋于指定的User/Group与权限。如果指定的目录已经存在,那么仅调整User/Group与权限。 如果指定了"寿命"字段,那么该目录中的内容将遵守基于时间的清理策略。
D
¶与 d
类似,
但是如果使用了 --remove
选项,那么将会清空目录中的所有内容。
e
¶调整已存在目录的User/Group与权限,
并遵守基于时间的清理策略。
可以在"路径"字段中使用shell风格的通配符。
如果指定了"寿命"字段,那么该目录中的内容将遵守基于时间的清理策略。
如果"寿命"字段的值为 "0
" ,
那么每次运行 systemd-tmpfiles --clean 命令时,都会无条件清空该目录。
要想让此类型有效,必须至少明确设置 权限, 属主, 属组, 寿命 字段之一。
作为一个例外,当与 !
一起使用时,
无效行可能就变得有意义了(参见后面的例子)。
v
¶如果指定的路径不存在,并且该路径位于一个Btrfs子卷中,
那么按照指定的路径创建子卷。
否则,创建一个普通的目录(与 d
一样)。
仅对Btrfs文件系统有意义。
此处创建的子卷不会被分配给任何 higher-level 配额组(quota group),
如果想要创建简单的配额组层次关系,那么应该使用下面的 q
或 Q
。
q
¶与 v
一样,创建一个子卷或目录,
但不同之处在于,将创建的子卷分配给与父卷相同的 higher-level 配额组(quota group)。
这样可以确保作用于父卷的 higher-level 配额组可以包含此处创建的子卷。
在非Btrfs文件系统上与 d
等价。
只要子卷已存在,无论该子卷是否已经分配有配额组(quota group),
都不会修改任何现有的配额层次关系。参见下面的 Q
以及 btrfs-qgroup(8)
手册,以了解更多关于btrfs的配额组(quota group)的概念。
Q
¶与 v
一样,创建一个子卷或目录,但不同之处在于,
将创建的子卷分配给一个新叶子配额组。不同于 q
之处在于,
并不直接复用父卷的 higher-level 配额组(quota group),
而是首先找到父卷的最低级非叶子配额组(也就是倒数第二级配额组),
然后在此配额组与最末端的叶子配额组之间,插入一个level值减一的"中间配额组",
并且与新建的子卷共享同一个ID。
如果父卷不存在 higher-level 配额组,那么插入一个level值为255且ID与新建子卷相同的"中间配额组",
并且这个新建的"中间配额组"将被指定为父卷的 higher-level 配额组(quota group),
同时,新建子卷的叶子配额组也会分配给它(中间配额组)。
单就新建子卷自身而言,q
与 Q
在实际效果上并无不同,
但是通过插入一个新的 higher-level 配额组,
Q
可以实现对该新建子卷以及未来创建在该子卷之下的下级子卷进行统一的配额限制。
这样,在通过 q
与 Q
创建子卷的同时,也一起实现了一颗"配额子树"。
每一个 Q
创建的子卷都将拥有一个自己专属的配额层次结构(配额树),可用于容纳将来创建的下级子卷。
每一个 q
创建的子卷都没有自己专属的配额树,
其自身会被添加到
直属父卷所属的配额树中。
Q
通常用于
/home
或 /var/lib/machines
这样的目录,
其特点是:不但需要包含多个下级子卷,而且需要将所有下级子卷视为一个整体进行配额限制。
q
通常用于
/var
或 /var/tmp
这样的目录,
其特点是:
要么不需要包含下级子卷,要么不需要将所有下级子卷视为一个整体进行配额限制。
与 q
一样,Q
也不会改变任何已存在子卷的配额层次关系,
无论该子卷是否已经分配有配额组(quota group)。
p
, p+
¶若指定的管道(FIFO)不存在,
则创建它,否则什么也不做。
后缀 +
表示:若指定的路径已存在一个非管道文件,
则先删除此文件再创建指定的管道文件。
L
, L+
¶若指定的软连接不存在,则创建它,否则什么也不做。
后缀 +
表示:
若指定的路径已存在一个非软连接文件或目录,
则先删除此文件或目录再创建指定的软连接。
若"参数"字段为空,
那么将创建一个指向
/usr/share/factory/
目录中同名文件的软连接。
注意,软连接的User/Group与权限将被忽略。
c
, c+
¶若指定的字符设备不存在,
则创建它,否则什么也不做。
后缀 +
表示:
若指定的路径已存在一个非字符设备的文件,
则先删除此文件再创建指定的字符设备。
因为udev并不管理运行时创建的静态设备节点,
所以建议在末尾加上"!",以确保仅在系统启动期间才创建此静态设备节点。
b
, b+
¶若指定的块设备不存在,
则创建它,否则什么也不做。
后缀 +
表示:
若指定的路径已存在一个非块设备的文件,
则先删除此文件再创建指定的块设备。
因为udev并不管理在运行时创建的静态设备节点,
所以建议在末尾加上"!",以确保仅在系统启动期间才创建此静态设备节点。
C
¶如果指定的文件或目录不存在或目录为空,
那么递归的从"参数"字段所指定的文件或目录复制,
否则什么也不做。
注意,如果下级子目录存在且非空,那么将会跳过该子目录的复制。
若"参数"字段为空,
那么将从 /usr/share/factory/
目录中
递归的复制同名文件或目录。
不追踪软连接。
x
¶在根据"寿命"字段清理过期文件时,
忽略指定的路径及该路径下的所有内容。
可以在"路径"字段中使用shell风格的通配符。
注意,
这个保护措施对 r
与 R
无效。
X
¶在根据"寿命"字段清理过期文件时,
仅忽略指定的路径自身而不包括该路径下的其他内容。
可以在"路径"字段中使用shell风格的通配符。
注意,
这个保护措施对
r
与
R
无效。
r
¶若指定的文件或目录存在, 则删除它。 不可用于非空目录。 可以在"路径"字段中使用shell风格的通配符。 不追踪软连接。
R
¶若指定的文件或目录存在,则递归的删除它。 可用于非空目录。 可以在"路径"字段中使用shell风格的通配符。 不追踪软连接。
z
¶若指定的文件或目录存在, 则仅设置其自身的访问权限、属主、属组、重置SELinux安全上下文。 可以在"路径"字段中使用shell风格的通配符。不追踪软连接。
Z
¶若指定的文件或目录存在, 则递归的设置其访问权限、属主、属组、重置SELinux安全上下文。 可以在"路径"字段中使用shell风格的通配符。 不追踪软连接。
t
¶若指定的文件或目录存在, 则仅设置其自身的SMACK标签。 可以在"路径"字段中使用shell风格的通配符。 不追踪软连接。
T
¶若指定的文件或目录存在, 则递归的设置其SMACK标签。 可以在"路径"字段中使用shell风格的通配符。 不追踪软连接。
h
¶若指定的文件或目录存在,则仅调整其自身的属性。 可以在"路径"字段中使用shell风格的通配符。
"参数"字段的格式是 [+-=][aAcCdDeijsStTu]
,
具体解释如下:
+
前缀表示添加属性(这是默认值);
-
前缀表示去除属性;
=
前缀表示设置属性(但对 "aAcCdDeijsStTu
" 范围之外的其他属性没有影响);
而后缀字母
"aAcCdDeijsStTu
" 则用于表示各种属性,
其含义与
chattr(1) 手册里的解释一致。
注意,
将"参数"字段设为一个单独的
=
(无后继字母)
表示重置所有 "aAcCdDeijsStTu
"
代表的属性。
不追踪软连接。
H
¶若指定的文件或目录存在,则递归的调整其属性。
可以在"路径"字段中使用shell风格的通配符。
不追踪软连接。"参数"字段的语法与 h
完全相同。
a
, a+
¶若指定的文件或目录存在,
则仅设置其自身的访问控制列表(POSIX ACL)。
可以在"路径"字段中使用shell风格的通配符。
后缀 +
表示将指定的项添加到已有的访问控制列表中。
除非基本权限已经存在或被明确指定,
否则 systemd-tmpfiles
将会自动根据"权限","属主","属组"字段添加所需的基本权限。
如果没有明确指定访问控制列表或者访问控制列表已经存在,
那么将会叠加上掩码。
不追踪软连接。
A
, A+
¶若指定的文件或目录存在,则递归的设置访问控制列表(POSIX ACL)。
其他与 a
/a+
完全相同。
不追踪软连接。
使用了感叹号(!)标记的行,仅可在系统启动过程中执行,
而不能用于运行中的系统(会破坏系统的正常运行)。
未使用感叹号(!)标记的行,
可以在任意时间安全的执行(例如升级软件包的时候)。
systemd-tmpfiles 仅在明确使用了
--boot
选项的时候
才会执行使用了感叹号(!)标记的行。
例如:
# 确保默认创建此目录 d /tmp/.X11-unix 1777 root root 10d # 仅在系统启动时清理X11的锁文件(但在运行时禁止删除这些文件) r! /tmp/.X[0-9]*-lock
本例中的第二行仅在明确使用了 --boot
选项的时候才会执行,
因为它会破坏正在运行中的系统。
但是第一行则无此限制,可以在任何时候执行都不会对系统造成损害。
如果使用了减号(-), 那么当该行创建(仅限于创建)操作失败时, 将不会导致 systemd-tmpfiles 返回错误代码(也就是仍然执行成功)。
例如:
# 修改 sysfs ,但是兼容容器环境(因为在容器中 /proc 是只读的) w- /proc/sys/vm/swappiness - - - - 10
注意,对于所有能够创建文件的行类型(也就是
f
/F
,
d
/D
/v
/q
/Q
,
p
, L
, c
/b
, C
),
前缀目录将会被自动创建(若需要),并且权限为 0755 、属主为 root 。
如果想要使用不同的权限或属主,那么必须添加或使用相应的 d
行。
"权限"字段表示设置文件或目录的权限,
如果省略或设为 "-
"
则表示使用默认权限,
也就是:对于目录使用"0755"、对于文件使用"0644",
但对于 z
, Z
表示不修改现有的权限。
此字段对于
x
, r
,
R
, L
, t
,
a
没有意义。
可选前缀 "~
" 的含义是掩码,
也就是将此字段当作权限掩码来使用。
换句话说,就是将此字段设置的值与现有的权限做"位与"运算。
举例来说,假设原有文件的权限是"0765",此字段的值是"~1550",
那么文件的最终权限将是"0540"。
如果省略了 sticky/SUID/SGID 位,
那么对于文件来说相当于全部清除,
而对于目录来说则相当于保持不变。
例如"~550",对于文件来说相当于"~0550",
而对于目录来说则相当于"~7550"。
此特性在实践中仅对 Z
有实际价值。
"属主"与"属组"字段分别表示文件或目录的所属用户与所属用户组。可以设为数字形式的UID/GID值,也可以设为字符串形式的用户名称/组名称。
如果省略或者设为 "-
" 则表示使用调用
systemd-tmpfiles 命令的用户及其属组。但对于 z
与 Z
来说,省略或者设为 "-
" 则表示不修改现有的"属主"与"属组"。
此字段对于 x
, r
, R
,
L
, t
, a
没有意义。
"寿命"字段用于
判定在清理过期文件或子目录时应该删除哪些,
也就是决定了文件或子目录的"寿命"。
如果文件或子目录的最后使用时间(详见后文)
与当前系统时间之差大于"寿命"字段的值,
那么这些文件或子目录将被删除。
此字段的值是一个时间长度,
可以使用下面的时间单位后缀:
us
(微秒),
ms
(毫秒),
s
(秒),
m
(分),
h
(时),
d
(天),
w
(周)。
可以同时使用多个时间单位, 例如"5m10s"表示5分10秒(也就是310秒)。 如果省略了时间单位,那么表示使用默认单位"秒"。
如果将此字段设为数字"0", 那么表示在每次清理时都无条件的删除所有文件或子目录。
此字段仅对
d
, D
, e
,
v
, q
,
Q
, C
, x
,
X
有意义。如果省略此字段或将其设为
"-
" ,那么表示不进行任何清理。
如果此字段的值以
"~
" 开头,
那么表示在每次清理时都无条件的保留指定目录直属的文件与子目录,
也就是仅清理直属子目录下的内容。
"寿命"是根据对象的最后修改时间(mtime)、 最后访问时间(atime)、 最后状态变化时间(ctime)(目录除外) 计算的。 如果三者(或两者)中最晚的时间与当前系统时间之差大于"寿命"字段的值, 那么该对象就会被删除, 否则该对象将会被保留。
"参数"字段对不同的"类型"有不同的作用:对于 L
来说,用于指定软连接的目标路径。
对于 c
, b
来说,用于以"主设备号:次设备号"的格式设定设备节点的主/次设备号(十进制整数),例如 "1:3
" 。
对于 f
, F
, w
来说,用于设置一个写入文件的短字符串(实际写入时会在末尾附加一个换行符)。
对于 C
来说,用于指定源文件或者源目录。
对于 t
, T
来说,用于指定将要设置的扩展属性。
对于 a
, A
来说,用于指定将要设置的ACL属性。
对于 h
, H
来说,用于指定将要设置的文件属性。
对于其他类型无意义,将被忽略。
可以在该字段中使用替换符(见下文)。
可以在"路径"(Path)与"参数"(Argument)字段中使用替换符。 不可识别的替换符将会被视为配置错误。 可识别的替换符如下:
表 1. 可识别的替换符
替换符 | 含义 |
---|---|
"%b " | 系统的"Boot ID"字符串。参见 random(4) 手册。 |
"%C " | 缓存目录。对于 --user 模式来说是 $XDG_CACHE_HOME ,否则是 /var/cache |
"%h " | 用户的家目录。运行命令的用户的家目录,对于系统实例则是 "/root " |
"%H " | 系统的主机名(hostname) |
"%L " | 日志目录。对于 --user 模式来说是 $XDG_CONFIG_HOME /log ,否则是 /var/log |
"%m " | 系统的"Machine ID"字符串。参见 machine-id(5) 手册。 |
"%S " | 状态目录。对于 --user 模式来说是 $XDG_CONFIG_HOME ,否则是 /var/lib |
"%t " | 运行时目录。对于 --user 模式来说是 $XDG_RUNTIME_DIR ,否则是 /run |
"%T " | 临时文件目录。也就是 /tmp 或 "$TMPDIR ", "$TEMP ", "$TMP " 之一(若已设置) |
"%g " | 运行命令的用户组名称。对于系统实例则是 "/root " |
"%G " | 运行命令的用户组数字GID 。对于系统实例则是 0 |
"%u " | 运行命令的用户名称。对于系统实例则是 "/root " |
"%U " | 运行命令的用户数字UID 。对于系统实例则是 0 |
"%v " | 内核版本(uname -r 的输出) |
"%V " | 存放大体积临时文件以及持久临时文件的目录。也就是 /var/tmp 或 "$TMPDIR ", "$TEMP ", "$TMP " 之一(若已设置) |
"%% " | 百分号自身(%)。使用"%%"表示一个真正的"%"字符。 |
例 1. 按照指定的User/Group与权限创建目录
screen(1) 需要在系统启动时按照指定的属主/属组/权限创建两个目录
# /usr/lib/tmpfiles.d/screen.conf d /run/screens 1777 root screen 10d d /run/uscreens 0755 root screen 10d12h
/run/screens
中超过10天的内容将被清理。
/run/uscreens
中超过10.5天的内容将被清理。
例 2. 创建一个带有 SMACK 属性的目录
D /run/cups - - - - t /run/cups - - - - security.SMACK64=printing user.attr-with-spaces="foo bar"
该目录的属主将是"root"并且会被赋予默认权限。 目录中的内容将不会被定期清理,但可使用 systemd-tmpfiles --remove 命令清除。
例 3. 创建一个不会被定期清理的目录
abrt(1)
需要在系统启动时按照指定的属主/属组/权限创建一个目录,
并且禁止清理
/var/tmp
目录
# /usr/lib/tmpfiles.d/tmp.conf d /var/tmp 1777 root root 30d
# /usr/lib/tmpfiles.d/abrt.conf d /var/tmp/abrt 0755 abrt abrt -
例 4. 启用系统启动过程中的清理,以及基于时间的清理
# /usr/lib/tmpfiles.d/dnf.conf r! /var/cache/dnf/*/*/download_lock.pid r! /var/cache/dnf/*/*/metadata_lock.pid r! /var/lib/dnf/rpmdb_lock.pid e /var/cache/dnf/ - - - 30d
系统启动过程中将会清理 lock 文件。
/var/cache/dnf/
目录中
闲置超过30天的内容将会被删除。
例 5. 在启动时清空缓存目录
# /usr/lib/tmpfiles.d/krb5rcache.conf e! /var/cache/krb5rcache - - - 0
在启动时清空 /var/cache/krb5rcache/
下的所有内容。即使此目录不存在也不会创建它。