笔记和练习博客总目录见:开始读TLPI

第15.4节描述了传统的 UNIX(和 Linux)文件权限方案。对于许多应用程序来说,这种方案是足够的。然而,一些应用程序需要对特定用户和组授予的权限进行更精细的控制。为了满足这一需求,许多 UNIX 系统实现了对传统 UNIX 文件权限模型的扩展,称为访问控制列表(ACL)。ACL 允许为任意数量的用户和组指定文件权限。Linux 从内核 2.6 开始提供 ACL。

每个文件系统对 ACL 的支持是可选的,并由内核配置选项在文件系统菜单下控制。Reiserfs 对 ACL 的支持从内核 2.6.7 开始提供。

为了能够在 ext2、ext3、ext4 或 Reiserfs 文件系统上创建 ACL,文件系统必须使用 mount –o acl 选项进行挂载。

💡 XFS默认支持ACL。

ACLs 从未在 UNIX 系统中正式标准化。曾经尝试以 POSIX.1e 和 POSIX.2c 草案标准的形式进行标准化,这些标准分别旨在指定 ACLs 的应用程序接口(API)和 shell 命令(以及其他功能,如能力)。最终,这一标准化尝试失败了,这些草案标准被撤回。尽管如此,许多 UNIX 实现(包括 Linux)仍然基于这些草案标准(通常是最终版本,Draft 17)来实现 ACL。然而,由于 ACL 实现之间存在许多差异(部分源于草案标准的不完整性),编写可移植的使用 ACL 的程序存在一定困难。

本章提供了 ACLs 的描述以及使用它们的简短教程。它还介绍了一些用于操作和检索 ACL 的库函数。我们不会详细介绍所有这些函数,因为它们太多了。(有关详细信息,请参阅手册页。)

17.1 Overview

ACL是一系列ACL条目,每个条目定义了单个用户或用户组的文件权限(见图17-1)。

在这里插入图片描述
Figure 17-1: An access control list

ACL entries
每个 ACL 条目由以下部分组成:

  • 一个标签类型,用于指示该条目是应用于用户、组还是其他类别的用户;
  • 一个可选的标签限定符,用于标识特定的用户或组(即用户 ID 或组 ID);以及
  • 一组权限,用于指定该条目授予的权限(读取、写入和执行)。

💡 ACL(访问控制列表)是附着在文件或目录上的。从函数acl_get_file的说明即可看出:get an ACL by filename

标签类型具有以下值之一:

  • ACL_USER_OBJ
    该条目指定授予文件所有者的权限。每个 ACL 恰好包含一个 ACL_USER_OBJ 条目。此条目对应于传统的文件所有者(用户)权限。
  • ACL_USER
    该条目指定授予由标签限定符标识的用户的权限。一个 ACL 可以包含零个或多个 ACL_USER 条目,但对于特定用户最多只能定义一个 ACL_USER 条目。
  • ACL_GROUP_OBJ
    此条目指定授予文件组的权限。每个 ACL 恰好包含一个 ACL_GROUP_OBJ 条目。该条目对应传统的文件组权限,除非 ACL 还包含 ACL_MASK 条目。
  • ACL_GROUP
    此条目指定授予由标签限定符标识的组的权限。ACL 可以包含零个或多个 ACL_GROUP 条目,但对于特定组最多只能定义一个 ACL_GROUP 条目
  • ACL_MASK
    此条目指定 ACL_USER、ACL_GROUP_OBJ 和 ACL_GROUP 条目可以授予的最大权限。ACL 最多包含一个 ACL_MASK 条目。如果 ACL 包含 ACL_USER 或 ACL_GROUP 条目,则 ACL_MASK 条目是必需的。我们稍后会更多地讨论这种标签类型。
  • ACL_OTHER
    此条目指定授予不匹配任何其他 ACL 条目的用户的权限。每个 ACL 精确包含一个 ACL_OTHER 条目。该条目对应于传统文件的其他权限。

标签限定符仅用于 ACL_USER 和 ACL_GROUP 条目。它指定用户 ID 或组 ID。

Minimal and extended ACLs
最小ACL是语义上等同于传统文件权限集的ACL。它恰好包含三个条目:每种类型各一个,分别是ACL_USER_OBJ、ACL_GROUP_OBJ和ACL_OTHER。扩展ACL则额外包含ACL_USER、ACL_GROUP和ACL_MASK条目。

区分最小ACL和扩展ACL的一个原因是后者为传统权限模型提供了语义扩展。另一个原因涉及Linux对ACL的实现。ACL作为系统扩展属性实现(第16章)。用于维护文件访问ACL的扩展属性名为system.posix_acl_access。只有当文件具有扩展ACL时,才需要这个扩展属性。最小ACL的权限信息可以(并且确实是)存储在传统的文件权限位中

17.2 ACL Permission-Checking Algorithm

对具有 ACL 的文件进行权限检查的情况与传统文件权限模型(第 15.4.3 节)下的情况相同。权限检查按以下顺序进行,直到满足某一条件为止:

  1. 如果该进程具有特权,则授予其所有访问权限。但此描述存在一个例外情况,类似于第15.4.3节所述的传统权限模型:在执行一个文件时,只有当该文件对应的访问控制列表(ACL)中至少有一个条目授予了执行权限,特权进程才会被授予该文件的执行权限。
  2. 如果进程的有效用户 ID 与文件的所有者(用户 ID)匹配,则进程被授予 ACL_USER_OBJ 条目中指定的权限。(严格来说,在 Linux 上,用于本节所述检查的是进程的文件系统 ID,而不是其有效 ID,如第 9.5 节所述。)
  3. 如果进程的有效用户 ID 与某个 ACL_USER 条目中的标签限定符匹配,则进程被授予该条目中指定的权限,并与 ACL_MASK 条目的值进行掩码(AND)操作。
  4. 如果进程的某个组 ID(即有效组 ID 或任何附加组 ID)与文件组匹配(这对应于 ACL_GROUP_OBJ 条目)或与任何 ACL_GROUP 条目的标签限定符匹配,则访问权限通过检查以下各项确定,直到找到匹配项为止:
    • a) 如果进程的某个组ID与文件组匹配,并且 ACL_GROUP_OBJ 条目授予所请求的权限,则该条目决定对文件的访问权限。授予的访问权限会受到 ACL_MASK 条目中值的限制(通过与其进行按位与运算),如果存在的话。
    • b) 如果进程的某个组ID与文件的 ACL_GROUP 条目中的标记限定符匹配,并且该条目授予所请求的权限,则该条目决定授予的权限。授予的访问权限会受到 ACL_MASK 条目中值的限制(通过与其进行按位与运算)。
    • c) 否则,拒绝访问。
  5. 否则,进程将被授予 ACL_OTHER 条目中指定的权限。

我们可以通过两个例子来澄清与组ID相关的规则。假设我们有一个组ID为100的文件,并且该文件受到图17-1所示的ACL保护。如果一个组ID为100的进程调用access(file, R_OK),那么该调用将成功(即返回0)。(我们在第15.4.4节中描述access()。)另一方面,即使ACL_GROUP_OBJ条目授予了所有权限,调用access(file, R_OK | W_OK | X_OK)也会失败(即返回–1,并且errno被设置为EACCES),因为ACL_GROUP_OBJ权限会与ACL_MASK条目进行掩码(AND运算),而该条目拒绝执行权限。

作为另一个使用图17-1的例子,假设我们有一个组ID为102的进程,并且其补充组ID中还包含组ID 103。对于该进程,调用access(file, R_OK)和access(file, W_OK)都会成功,因为它们分别与组ID 102和103的ACL_GROUP条目匹配。另一方面,调用access(file, R_OK | W_OK)将失败,因为没有匹配的ACL_GROUP条目可以同时授予读取和写入权限。

17.3 Long and Short Text Forms for ACLs

在使用 setfacl 和 getfacl 命令(稍后将介绍)或某些 ACL 库函数操作 ACL 时,我们指定 ACL 条目的文本表示形式。这些文本表示形式允许两种格式:

  • 长文本格式的 ACL 每行包含一个 ACL 条目,并且可以包含以 # 字符开始并延续到行尾的注释。getfacl 命令以长文本格式显示 ACL。setfacl 的 -M acl-file 选项从文件中获取 ACL 规范时,期望规范为长文本格式。
  • 短文本格式的 ACL 由用逗号分隔的一系列 ACL 条目组成。

在这两种格式中,每个 ACL 条目由三部分组成,用冒号分隔:

tag-type:[tag-qualifier]: permissions

标签类型是表 17-1 第一列中显示的值之一。标签类型后面可以选择性地跟一个标签限定符,该限定符通过名称或数字标识符识别用户或组。标签限定符仅在 ACL_USER 和 ACL_GROUP 条目中存在。以下是所有对应于传统权限掩码 0650 的短文本形式 ACL:

u::rw-,g::r-x,o::---
u::rw,g::rx,o::-
user::rw,group::rx,other::-

以下 ACL 的简短文本形式包括两个命名用户、一个命名组和一个掩码条目:

u::rw,u:paulh:rw,u:annabel:rw,g::r,g:teach:rw,m::rwx,o::-

Table 17-1: Interpretation of ACL entry text forms

Tag text Tag qualifier present? Corresponding tag type Entry for
u, user N ACL_USER_OBJ File owner (user)
u, user Y ACL_USER Specified user
g, group N ACL_GROUP_OBJ File group
g, group Y ACL_GROUP Specified group
m, mask N ACL_MASK Mask for group class
o, other N ACL_OTHER Other users

17.4 The ACL_MASK Entry and the ACL Group Class

如果 ACL 包含 ACL_USER 或 ACL_GROUP 条目,则必须包含 ACL_MASK 条目。如果 ACL 不包含任何 ACL_USER 或 ACL_GROUP 条目,则 ACL_MASK 条目是可选的。

ACL_MASK 条目作为所谓组类中 ACL 条目授予权限的上限。组类是 ACL 中所有 ACL_USER、ACL_GROUP 和 ACL_GROUP_OBJ 条目的集合。

ACL_MASK 条目的目的是在运行不识别 ACL 的应用程序时提供一致的行为。为了说明为什么需要掩码条目,假设某个文件的 ACL 包含以下条目:

user::rwx # ACL_USER_OBJ
user:paulh:r-x # ACL_USER
group::r-x # ACL_GROUP_OBJ
group:teach:--x # ACL_GROUP
other::--x # ACL_OTHER

现在假设一个程序对该文件执行以下 chmod() 调用:

chmod(pathname, 0700); /* Set permissions to rwx------ */

在不识别 ACL 的应用程序看来,这意味着“拒绝除文件所有者之外的所有人的访问”。即使存在 ACL,这些语义也应保持不变。如果没有 ACL_MASK 条目,可以通过多种方式来实现这一行为,但每种方式都存在一些问题:

  • 仅仅修改 ACL_GROUP_OBJ 和 ACL_USER_OBJ 条目以具有掩码 — 是不够的,因为用户 paulh 和组 teach 仍然对文件有一些权限。
  • 另一种可能性是将新的组和其他权限设置(即全部权限禁用)应用于 ACL 中的所有 ACL_USER、ACL_GROUP、ACL_GROUP_OBJ 和 ACL_OTHER 条目:
user::rwx # ACL_USER_OBJ
user:paulh:--- # ACL_USER
group::--- # ACL_GROUP_OBJ
group:teach:--- # ACL_GROUP
other::--- # ACL_OTHER

这种方法的问题在于,不了解 ACL 的应用程序因此会无意中破坏由了解 ACL 的应用程序建立的文件权限语义,因为下面的调用(例如)不会将 ACL 的 ACL_USER 和 ACL_GROUP 条目恢复到它们之前的状态:

chmod(pathname, 751);
  • 为了避免这些问题,我们可以考虑将 ACL_GROUP_OBJ 条目作为所有 ACL_USER 和 ACL_GROUP 条目的限制集合。然而,这将意味着 ACL_GROUP_OBJ 权限始终需要设置为所有 ACL_USER 和 ACL_GROUP 条目中允许的权限的并集。这将与使用 ACL_GROUP_OBJ 条目来确定授予文件组的权限的做法相冲突。

**ACL_MASK 条目是为了解决这些问题而设计的。它提供了一种机制,允许实现 chmod() 操作的传统含义,而不会破坏由支持 ACL 的应用程序建立的文件权限语义。**当一个 ACL 拥有 ACL_MASK 条目时:

  • 通过 chmod() 对传统组权限的所有更改都会更改 ACL_MASK 条目的设置(而不是 ACL_GROUP_OBJ 条目);并且
  • 调用 stat() 会返回 ACL_MASK 权限(而不是 ACL_GROUP_OBJ 权限)作为 st_mode 字段中的组权限位(见第281页的图 15-1)。

尽管 ACL_MASK 条目提供了一种在面对不支持 ACL 的应用程序时保留 ACL 信息的方法,但反之并不保证。ACL 的存在会覆盖传统操作对文件组权限的影响。例如,假设我们已在某个文件上设置了以下 ACL:

user::rw-,group::---,mask::---,other::r--

如果我们随后对该文件执行命令 chmod g rw,则 ACL 变为:

user::rw-,group::---,mask::rw-,other::r--

17.5 The getfacl and setfacl Commands

在终端中,我们可以使用 getfacl 命令查看文件的 ACL。

$ umask 022
$ touch tfile
$ ls -l tfile
-rw-r--r--. 1 vagrant vagrant 0 Mar 13 06:56 tfile
$ getfacl tfile
# file: tfile
# owner: vagrant
# group: vagrant
user::rw-
group::r--
other::r--

从 getfacl 命令的输出中,我们看到新文件是以最小 ACL 创建的。当显示此 ACL 的文本形式时,getfacl 会在 ACL 条目之前显示三行,显示文件的名称和所有权。我们可以通过指定 --omit-header 选项来防止显示这些行。

接下来,我们演示通过传统的 chmod 命令对文件权限所做的更改会传递到 ACL。

$ chmod u=rwx,g=rx,o=x tfile ## 或chmod 751 tfile
$ getfacl --omit-header tfile
user::rwx
group::r-x
other::--x

setfacl 命令用于修改文件 ACL。在这里,我们使用 setfacl –m 命令向 ACL 添加一个 ACL_USER 和一个 ACL_GROUP 条目:

$ setfacl -m u:paulh:rx,g:teach:x tfile
setfacl: Option -m: Invalid argument near character 3
# 报错是因为user和group不存在
$ sudo useradd paulh
$ sudo groupadd teach
$ setfacl -m u:paulh:rx,g:teach:x tfile
$ getfacl --omit-header tfile
user::rwx
user:paulh:r-x
group::r-x
group:teach:--x
mask::r-x
other::--x

💡 对于默认ACL,mask被设置为所有命名用户和命名组的权限并集。

setfacl -m 选项修改现有的 ACL 条目,或者如果不存在具有给定标签类型和限定符的对应条目,则添加新的条目。我们还可以使用 –R 选项将指定的 ACL 递归地应用到目录树中的所有文件。

从 getfacl 命令的输出中,我们可以看到 setfacl 自动为此 ACL 创建了一个 ACL_MASK 条目。

添加 ACL_USER 和 ACL_GROUP 条目将此 ACL 转换为扩展 ACL,而 ls –l 通过在传统文件权限掩码后追加一个加号(+)来指示这一点:

$ ls -l tfile
-rwxr-x--x+ 1 vagrant vagrant 0 Mar 13 06:56 tfile

我们接着使用 setfacl 禁用 ACL_MASK 条目上的所有权限,除了执行权限,然后再次使用 getfacl 查看 ACL:

# 此输出与前面输出比较,只改变了mask
$ setfacl -m m::x tfile
$ getfacl --omit-header tfile
user::rwx
user:paulh:r-x                  #effective:--x
group::r-x                      #effective:--x
group:teach:--x
mask::--x
other::--x

在用户 paulh 和文件组(group::)的条目之后,getfacl 打印的 #effective: 注释告诉我们,在与 ACL_MASK 条目进行掩码(与运算)之后,这些条目授予的权限实际上会少于条目中指定的权限。

然后我们使用 ls –l 再次查看文件的传统权限位。
我们看到显示的组类权限位反映的是 ACL_MASK 条目的权限(–x),而不是 ACL_GROUP 条目的权限(r-x):

$ ls -l tfile
-rwx--x--x+ 1 vagrant vagrant 0 Mar 13 06:56 tfile

💡 如果最后一位是.,则表示启用了SELinux。如果SELinux和ACL都启用,则只会显示+。用getfacl显示ACL,用ls -Z查看SELinux属性。

setfacl –x 命令可用于从 ACL 中移除条目。在这里,我们移除用户 paulh 和组 teach 的条目(移除条目时未指定权限):

$ setfacl -x u:paulh,g:teach tfile
$ getfacl --omit-header tfile
user::rwx
group::r-x
mask::r-x
other::--x

请注意,在上述操作过程中,setfacl 会自动将掩码条目调整为所有组类条目的并集。(这里只有一个这样的条目:ACL_GROUP_OBJ。)如果我们想要防止这种调整,则必须为 setfacl 指定 –n 选项。

最后,我们注意到 setfacl –b 选项可以用来从 ACL 中移除所有扩展条目,只保留最基本的(即用户、组和其他)条目。

17.6 Default ACLs and File Creation

在目前关于 ACL 的讨论中,我们一直在描述访问 ACL(access ACLs)。顾名思义,访问 ACL 用于确定进程在访问与该 ACL 关联的文件时拥有的权限。我们可以在目录上创建第二种类型的 ACL:默认 ACL(default ACL)。

💡 默认 ACL是附着在目录上的。函数acl_get_file的第二个参数可以指定ACL_TYPE_ACCESS或ACL_TYPE_DEFAULT。

默认 ACL 在决定访问该目录时所授予的权限方面不起任何作用。相反,它的存在与否决定了在该目录下创建的文件和子目录所获得的 ACL 以及权限。(默认 ACL 作为一个扩展属性存储,名称为 system.posix_acl_default。)

要查看和设置目录的默认 ACL,我们使用 getfacl 和 setfacl 命令的 –d 选项。

$ mkdir sub
$ setfacl -d -m u::rwx,u:paulh:rx,g::rx,g:teach:rwx,o::- sub
$ getfacl -d --omit-header sub
user::rwx
user:paulh:r-x
group::r-x
group:teach:rwx
mask::rwx					# setfacl 自动生成了 ACL_MASK 条目
other::---

我们可以使用 setfacl –k 选项从目录中移除默认 ACL。

如果一个目录有默认ACL,那么:

  • 在该目录中创建的新子目录会继承该目录的默认ACL作为其默认ACL。换句话说,默认ACL会随着新子目录的创建而沿目录树向下传播。
  • 在该目录中创建的新文件或子目录会继承该目录的默认ACL作为其访问ACL。与传统文件权限位对应的ACL条目会与系统调用(如open()、mkdir()等)中用于创建文件或子目录的模式参数的相应位进行掩码(AND)操作。这里所说的“对应的ACL条目”是指:
    – ACL_USER_OBJ;
    – ACL_MASK,或者如果ACL_MASK缺失,则为ACL_GROUP_OBJ;
    – ACL_OTHER。

当一个目录有默认 ACL 时,进程的 umask(第 15.4.6 节)不会影响新文件在该目录中创建时访问 ACL 条目的权限。

作为新文件如何从父目录的默认 ACL 继承其访问 ACL 的示例,假设我们使用以下 open() 调用在上面创建的目录中创建一个新文件:

open("sub/tfile", O_RDWR | O_CREAT,
	S_IRWXU | S_IXGRP | S_IXOTH); /* rwx--x--x */

新文件将具有以下访问控制列表(ACL):

$ getfacl --omit-header sub/tfile
user::rwx
user:paulh:r-x #effective:--x
group::r-x #effective:--x
group:teach:rwx #effective:--x
mask::--x
other::---

如果一个目录没有默认ACL,那么:

  • 在该目录中创建的新子目录也没有默认ACL。
  • 新文件或目录的权限按照传统规则设置(第15.4.6节):文件权限设置为mode参数的值(传递给open()、mkdir()等的值),减去被进程umask关闭的位。这会在新文件上产生一个最小的ACL。

17.7 ACL Implementation Limits

各种文件系统的实现对 ACL(访问控制列表)中的条目数量有一定限制:

  • 在 ext2、ext3 和 ext4 上,文件上的 ACL 总数受以下要求约束:文件的所有扩展属性的名称和值的字节必须包含在一个逻辑磁盘块中(第16.2节)。每个 ACL 条目需要 8 个字节,因此文件的 ACL 条目最大数量略小于块大小的八分之一(因为 ACL 的扩展属性名称有一些开销)。因此,4096 字节的块大小最多允许大约 500 个 ACL 条目。(在 2.6.11 之前的内核对 ext2 和 ext3 上的 ACL 设置了 32 个条目的任意限制。)
  • 在 XFS 上,ACL 限制为 25 条目。
  • 在 Reiserfs 和 JFS 上,ACL 可以包含最多 8191 条目。此限制是由于 VFS 对扩展属性值(第 16.2 节)施加的大小限制(64 kB)所导致的。

在撰写本文时,Btrfs 将 ACL 限制为大约 500 个条目。然而,由于 Btrfs 仍处于积极开发阶段,这一限制可能会发生变化。

虽然上述大多数文件系统允许在 ACL 中创建大量条目,但应避免这样做,原因如下:

  • 维护冗长的 ACL 变成了一项复杂且可能出错的系统管理任务。
  • 扫描 ACL 以查找匹配条目(或在进行组 ID 检查时的匹配条目)的时间会随着 ACL 条目数量的增加而线性增加。

通常,我们可以通过在系统组文件(第8.3节)中定义合适的组,并在ACL中使用这些组,将文件上的ACL条目数量保持在合理的范围内。

17.8 The ACL API

POSIX.1e 草案标准定义了一大套用于操作 ACL 的函数和数据结构。由于它们数量众多,我们不会尝试描述所有这些函数的详细信息。相反,我们提供它们使用方法的概述,并以一个示例程序作结。

使用 ACL API 的程序应包含 <sys/acl.h>。如果程序使用了 POSIX.1e 草案标准的各种 Linux 扩展,还可能需要包含 <acl/libacl.h>。(Linux 扩展列表可参见 acl(5) 手册页。)使用该 API 的程序必须使用 –lacl 选项进行编译,以便链接到 libacl 库。

如前所述,在 Linux 上,ACL 是通过扩展属性实现的,而 ACL API 则作为一组库函数实现,用于操作用户空间的数据结构,并在必要时调用 getxattr() 和 setxattr() 来检索和修改存储 ACL 表示的磁盘系统扩展属性。应用程序也可以(虽然不推荐)直接使用 getxattr() 和 setxattr() 来操作 ACL。

Overview
构成 ACL API 的函数列在 acl(5) 手册页中。乍一看,这么多的函数和数据结构可能会令人困惑。图 17-2 提供了各个数据结构之间关系的概述,并指出了许多 ACL 函数的使用情况。

图略
Figure 17-2: Relationship between ACL library functions and data structures

从图 17-2 中,我们可以看到 ACL API 将 ACL 视为一个分层对象:

  • 一个 ACL 由一个或多个 ACL 条目组成。
  • 每个 ACL 条目由一个标签类型、一个可选的标签限定符和一组权限组成。

我们现在简要地看一下各种 ACL 函数。在大多数情况下,我们不会描述每个函数的错误返回。返回整数(状态)的函数通常在成功时返回 0,出错时返回 -1。返回句柄(指针)的函数在出错时返回 NULL。错误可以像往常一样使用 errno 进行诊断。

句柄是用于引用对象或数据结构的一种抽象术语。句柄的表示对 API 实现而言是私有的。例如,它可能是一个指针、数组索引或哈希键。

Fetching a file’s ACL into memory
acl_get_file() 函数检索由路径名标识的文件的 ACL 的副本。

acl_t acl;

acl = acl_get_file(pathname, type);

此函数根据类型是指定为 ACL_TYPE_ACCESS 还是 ACL_TYPE_DEFAULT,检索访问 ACL 或默认 ACL。作为其函数结果,acl_get_file() 返回一个句柄(类型为 acl_t),以用于其他 ACL 函数。

Retrieving entries from an in-memory ACL
acl_get_entry() 函数返回一个句柄(类型为 acl_entry_t),该句柄引用内存中 ACL 中的一个 ACL 条目,该 ACL 由其 acl 参数指定。该句柄会返回到最后一个函数参数指向的位置。

acl_entry_t entry;

status = acl_get_entry(acl, entry_id, &entry);

entry_id 参数决定返回哪个条目的句柄。如果 entry_id 指定为 ACL_FIRST_ENTRY,则返回 ACL 中第一个条目的句柄。如果 entry_id 指定为 ACL_NEXT_ENTRY,则返回在上次检索的 ACL 条目之后的条目的句柄。因此,我们可以通过在第一次调用 acl_get_entry() 时将 type 指定为 ACL_FIRST_ENTRY,并在随后的调用中将 type 指定为 ACL_NEXT_ENTRY,来循环遍历 ACL 中的所有条目。

如果 acl_get_entry() 成功获取一个 ACL 条目,则返回 1;如果没有更多条目,则返回 0;如果出错,则返回 -1。

Retrieving and modifying attributes in an ACL entry
acl_get_tag_type() 和 acl_set_tag_type() 函数用于检索和修改其 entry 参数所指的 ACL 条目中的标签类型。

acl_tag_t tag_type;

status = acl_get_tag_type(entry, &tag_type);
status = acl_set_tag_type(entry, tag_type);

tag_type 参数的类型是 acl_type_t(一个整数类型),其值为 ACL_USER_OBJ、ACL_USER、ACL_GROUP_OBJ、ACL_GROUP、ACL_OTHER 或 ACL_MASK 之一。

acl_get_qualifier() 和 acl_set_qualifier() 函数用于检索和修改其 entry 参数所指向的 ACL 条目中的标签限定符。下面是一个示例,我们假设已经通过检查标签类型确定这是一个 ACL_USER 条目:

uid_t *qualp; /* Pointer to UID */

qualp = acl_get_qualifier(entry);
status = acl_set_qualifier(entry, qualp);

标签限定符仅在此条目的标签类型为 ACL_USER 或 ACL_GROUP 时有效。在前一种情况下,qualp 是指向用户 ID(uid_t *)的指针;在后一种情况下,它是指向组 ID(gid_t *)的指针。

acl_get_permset() 和 acl_set_permset() 函数用于检索和修改其 entry 参数所指的 ACL 条目中的权限集。

acl_permset_t permset;

status = acl_get_permset(entry, &permset);
status = acl_set_permset(entry, permset);

acl_permset_t 数据类型是一个引用权限集的句柄。

以下函数用于操作权限集的内容:

int is_set;

is_set = acl_get_perm(permset, perm);

status = acl_add_perm(permset, perm);
status = acl_delete_perm(permset, perm);
status = acl_clear_perms(permset);

在每一次这些调用中,perm 被指定为 ACL_READ、ACL_WRITE 或 ACL_EXECUTE,含义显而易见。这些函数的使用方式如下:

  • acl_get_perm() 函数如果 permset 指向的权限集中启用了 perm 指定的权限,则返回 1(true),否则返回 0。此函数是对 POSIX.1e 草案标准的 Linux 扩展。
  • acl_add_perm() 函数将 perm 指定的权限添加到 permset 指向的权限集中。
  • acl_delete_perm() 函数从 permset 指向的权限集中移除 perm 指定的权限。(如果权限集中不存在该权限,移除也不会报错。)
  • acl_clear_perms() 函数从 permset 指向的权限集中移除所有权限。

Creating and deleting ACL entries
acl_create_entry() 函数在现有 ACL 中创建一个新条目。指向新条目的句柄会返回到第二个函数参数指向的位置。

acl_entry_t entry;

status = acl_create_entry(&acl, &entry);

然后可以使用前面描述的函数填充新条目。

acl_delete_entry() 函数从 ACL 中删除条目。

status = acl_delete_entry(acl, entry);

Updating a file’s ACL
acl_set_file() 函数是 acl_get_file() 的相反操作。它使用由其 acl 参数引用的内存中 ACL 的内容来更新磁盘上的 ACL。

int status;

status = acl_set_file(pathname, type, acl);

类型参数要么是 ACL_TYPE_ACCESS,用于更新访问 ACL,要么是 ACL_TYPE_DEFAULT,用于更新目录的默认 ACL。

Converting an ACL between in-memory and text form
acl_from_text() 函数将包含长文本或短文本形式 ACL 的字符串转换为内存中的 ACL,并返回一个句柄,该句柄可在随后的函数调用中用于引用该 ACL。

acl = acl_from_text(acl_string);

acl_to_text() 函数执行逆向转换,返回一个与其 acl 参数所引用的 ACL 对应的长文本形式字符串。

char *str;
ssize_t len;

str = acl_to_text(acl, &len);

如果 len 参数没有被指定为 NULL,那么它所指向的缓冲区将用来返回作为函数结果返回的字符串的长度。

Other functions in the ACL API
以下段落描述了图 17-2 中未显示的几个常用 ACL 函数。

acl_calc_mask(&acl) 函数计算并设置内存中 ACL 的 ACL_MASK 条目中的权限,该内存中 ACL 的句柄由其参数指向。通常,每当我们创建或修改 ACL 时都会使用此函数。ACL_MASK 权限被计算为所有 ACL_USER、ACL_GROUP 和 ACL_GROUP_OBJ 条目权限的并集。该函数的一个有用特性是,它会在 ACL_MASK 条目不存在时创建它。这意味着,如果我们向先前的最小 ACL 添加 ACL_USER 和 ACL_GROUP 条目,那么可以使用此函数来确保创建 ACL_MASK 条目。

acl_valid(acl) 函数如果其参数所引用的 ACL 有效则返回 0,否则返回 –1。一个 ACL 有效当且仅当以下所有条件都为真:

  • ACL_USER_OBJ、ACL_GROUP_OBJ 和 ACL_OTHER 条目恰好出现一次;
  • 如果存在任何 ACL_USER 或 ACL_GROUP 条目,则必须有 ACL_MASK 条目;
  • ACL_MASK 条目最多只有一个;
  • 每个 ACL_USER 条目都有唯一的用户 ID;
  • 每个 ACL_GROUP 条目都有唯一的组 ID。

acl_check() 和 acl_error() 函数(后者是 Linux 的扩展)是 acl_valid() 的替代方案,它们的移植性较差,但能更精确地描述格式错误的 ACL 中的错误。具体细节请参见手册页。

acl_delete_def_file(pathname) 函数删除由 pathname 指定的目录上的默认 ACL。

acl_init(count) 函数创建一个新的、空的 ACL 结构,初始时包含至少 count 个 ACL 条目的空间。(count 参数是系统关于预期使用的提示,而不是硬性限制。)新 ACL 的句柄作为函数结果返回。

acl_dup(acl) 函数创建所引用的 ACL 的副本,并将副本 ACL 的句柄作为函数结果返回。

acl_free(handle) 函数释放其他 ACL 函数分配的内存。例如,我们必须使用 acl_free() 来释放由调用 acl_from_text()、acl_to_text()、acl_get_file()、acl_init() 和 acl_dup() 分配的内存。

Example program
清单 17-1 演示了一些 ACL 库函数的使用。该程序检索并显示文件上的 ACL(即,它提供了 getfacl 命令功能的一个子集)。如果指定了 –d 命令行选项,则程序显示目录的默认 ACL,而不是访问 ACL。

下面是使用该程序的一个示例:

$ touch tfile
$ setfacl -m 'u:annie:r,u:paulh:rw,g:teach:r' tfile
setfacl: Option -m: Invalid argument near character 3
$ sudo useradd annie
$ setfacl -m 'u:annie:r,u:paulh:rw,g:teach:r' tfile
$ ./acl_view tfile
user_obj             rw-
user        paulh    rw-
user        annie    r--
group_obj            r--
group       teach    r--
mask                 rw-
other                r--

本书的源代码分发版还包括一个程序 acl/acl_update.c,该程序对 ACL 进行更新(即,它提供了 setfacl 命令功能的一个子集)。

Listing 17-1: Display the access or default ACL on a file

// acl/acl_view.c
// 代码略

示例程序编译需要libacl-devel:

$ sudo yum install -y libacl-devel
$ rpm -qa|grep libacl
libacl-2.3.1-4.el9.x86_64
libacl-devel-2.3.1-4.el9.x86_64

笔记和练习博客总目录见:开始读TLPI

17.9 Summary

从版本 2.6 开始,Linux 支持 ACL。ACL 扩展了传统的 UNIX 文件权限模型,允许按用户和群组的基础控制文件权限。

Further information
草案 POSIX.1e 和 POSIX.2c 标准的最终版本(第 17 版草稿)可在线获取,网址为 http://wt.tuxomania.net/publications/posix.1e/。

acl(5) 手册页概述了 ACL 并提供了一些关于在 Linux 上实现的各种 ACL 库函数可移植性的指导。

有关 Linux 实现 ACL 和扩展属性的详细信息,请参见 [Grünbacher, 2003]。Andreas Grünbacher 维护了一个关于 ACL 信息的网站,网址为 http://acl.bestbits.at/。

17.10 Exercise

参见 TLPI 第17章 练习:Access Control Lists

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐