setfacl 命令详解与参数实测(Ubuntu 22.04)

Ubuntu 22.04 实测逐参数验证 setfacl:授权与删除、mask 重算、默认 ACL 继承、备份恢复排障。

Linux命令Linux系统UbuntuUbuntu 22.04命令行用户权限2026/10/17

setfacl 命令详解与参数实测(Ubuntu 22.04)

实测目标:在 Ubuntu 22.04 服务器上逐个实测 setfacl 的全部参数——四类条目(u:/g:/o:/m:)的授权与删除、大写 X 语义、mask 重算时机(-n/--mask/-m m::)、默认 ACL(-d)与继承、递归与符号链接(-R/-L/-P)、批量文件(-M/-X)、整体替换(--set/--set-file)、干跑(--test)与恢复(--restore),并给出 cp/tar/mv 对 ACL 的保留差异与「授权了却不生效」的真实排障全过程。全部命令真实执行取证,回显逐字照抄。

一、环境信息

  • 操作系统:Ubuntu 22.04,x86_64
  • 命令所属软件包:acl 2.3.1-1(dpkg -l acl 实测;setfacl -v 输出 setfacl 2.3.1)
  • 文件系统:演示目录所在根文件系统已启用 POSIX ACL(本文全部 setfacl 授权与 getfacl 读回成功即为实证)
  • 演示用户与组(id/getent group 实测回显):
    • aclu1:uid=1017(aclu1) gid=3106(aclu1) groups=3106(aclu1)
    • aclu2:uid=1018(aclu2) gid=3107(aclu2) groups=3107(aclu2),3108(aclteam)
    • 演示组 aclteam(gid 3108):aclteam:x:3108:aclu2
  • 演示目录:/root/demo/acl/,内含 acl-f1.txt、acl-f2.txt、plain.txt 等普通文本文件与 sdir、proj、share 等演示子目录(数据准备见 2.4 节)
  • 执行身份:root(修改 ACL 要求操作者是文件属主或 root)

⚠️ 注意:演示路径位于 /root 下,非 root 用户访问需要逐级目录的 x 权限(/root 与中间目录已为演示用户授予 x)。这是本文实战章节真实踩过的坑,完整排障过程见 4.1 节。

二、命令概览

本章先给出 setfacl 的语法骨架与条目格式(2.1)、--help 全量回显(2.2)、版本信息(2.3)与测试数据准备(2.4),为第三章逐参数实测建立坐标系。

2.1 语法与条目格式

Usage: setfacl [-bkndRLP] { -m|-M|-x|-X ... } file ...

用法骨架包含两层含义:必须从 -m/-M/-x/-X 四个修改动作里选一个(少了动作直接报 Usage 错误,3.3 节有实测),-b/-k/-n/-d/-R/-L/-P 以及 --set/--set-file/--restore/--test/--mask 都是修饰开关。每条 ACL 记录(条目)的统一格式是 tag:qualifier:perms,七种形态如下:

条目形态含义getfacl 回显示例
user::权限(简写 u::)属主条目,等价 chmod 的 u 位user::rw-
user:用户名:权限(u:名字:)命名用户条目——ACL 的核心价值user:aclu1:rw-
group::权限(g::)属组条目,等价 chmod 的 g 位group::r--
group:组名:权限(g:名字:)命名组条目group:aclteam:r-x
mask::权限(m::)命名用户/命名组/属组条目的生效权限上限mask::rwx
other::权限(o::)其他人条目,等价 o 位other::r--
default:...(配 -d)目录的默认 ACL:以后新建对象的继承模板default:user::rwx

权限字段(perms)可写 r/w/x 的组合(如 rw)、条件执行位大写 X,或一个八进制数字(0–7,如 7=rwx)。

💡 技术拓展:Linux 上「细粒度权限控制」不止 setfacl 一种方案,主要选项对比——

方案粒度工具适用场景
传统三段位属主/属组/其他 三档chmod/chown权限模型简单、对象少
POSIX ACL(本文)任意多命名用户/组 + 默认 ACL 继承setfacl/getfacl精确到个人/小组的放行与回收
文件属性位追加/不可变等行为开关chattr/lsattr防篡改、只追加日志文件
强制访问控制 MAC进程级策略SELinux、AppArmor高安全等级系统、服务降权

本文实际采用 POSIX ACL(setfacl):无需重构既有权限模型即可对单个用户/组放行,Ubuntu 22.04 的主流文件系统开箱支持。

2.2 --help 全量输出

setfacl --help
setfacl 2.3.1 -- set file access control lists
Usage: setfacl [-bkndRLP] { -m|-M|-x|-X ... } file ...
  -m, --modify=acl        modify the current ACL(s) of file(s)
  -M, --modify-file=file  read ACL entries to modify from file
  -x, --remove=acl        remove entries from the ACL(s) of file(s)
  -X, --remove-file=file  read ACL entries to remove from file
  -b, --remove-all        remove all extended ACL entries
  -k, --remove-default    remove the default ACL
      --set=acl           set the ACL of file(s), replacing the current ACL
      --set-file=file     read ACL entries to set from file
      --mask              do recalculate the effective rights mask
  -n, --no-mask           don't recalculate the effective rights mask
  -d, --default           operations apply to the default ACL
  -R, --recursive         recurse into subdirectories
  -L, --logical           logical walk, follow symbolic links
  -P, --physical          physical walk, do not follow symbolic links
      --restore=file      restore ACLs (inverse of `getfacl -R')
      --test              test mode (ACLs are not modified)
  -v, --version           print version and exit
  -h, --help              this help text

对照这张帮助表读第三章会更顺:-m/-M 是修改(可新增),-x/-X 是移除,-b/-k 是两类「清空」,-n/--mask 一对控制 mask 是否重算,-d 把操作目标切到默认 ACL,-R/-L/-P 控制递归与符号链接语义,--set/--set-file 是替换语义,--test/--restore 一对负责「预演与恢复」。

💡 说明:--help 里 --restore 的注释是 inverse of getfacl -R——这一句把 setfacl 与 getfacl 的分工点得很透:getfacl 负责「读出清单」,setfacl 负责「写回去」,本文 3.8/3.11 节的批量与恢复能力全部建立在这对组合上。

2.3 版本信息

setfacl -v
dpkg -l acl | tail -1
setfacl 2.3.1

ii  acl            2.3.1-1      amd64        access control list - utilities

命令版本 acl 2.3.1 与软件包 2.3.1-1 一致,Ubuntu 22.04 仓库默认版本,无需额外安装。

💡 说明:命令版本(setfacl -v)与软件包版本(dpkg -l)是两个口径——命令只报上游版本号 2.3.1,Debian 打包后缀 -1 只出现在软件包名里。排查跨机行为差异时以命令版本为准。

2.4 测试数据准备

本文全部实测基于以下演示数据,读者可逐条执行复现:

mkdir -p /root/demo/acl && cd /root/demo/acl
printf 'hello acl\n' > acl-f1.txt
echo demo2 > acl-f2.txt
echo plain > plain.txt
setfacl -b acl-f1.txt acl-f2.txt plain.txt
ls -l acl-f1.txt acl-f2.txt plain.txt
-rw-r--r-- 1 root root 10 Oct 16 15:47 acl-f1.txt
-rw-r----- 1 root root  6 Oct 16 15:47 acl-f2.txt
-rw-r--r-- 1 root root  6 Oct 16 15:47 plain.txt

三个要点:> 重写已有文件的内容但不改变其权限位(acl-f2.txt 沿用了磁盘上此前遗留的 640 基线,属组受限;想统一为 644 可先删除重建);setfacl -b 把三个文件复位到只剩基础条目,是每次实验前的标准动作;演示用户 aclu1/aclu2 与演示组 aclteam 的成员关系见「环境信息」。

💡 说明:后文各节命令独立可执行,但共享同一批演示文件——按顺序做时 ACL 状态会层层叠加,每节开头的命令已包含复位或依赖说明,照抄即可对上回显。

三、参数逐项实测

本章按 --help 顺序逐参数实测:每节都是「命令 + 完整回显 + 说明 + 要点」。除特别注明外,命令都在 /root/demo/acl 目录语义下执行,回显全部来自真实运行。

3.1 -m:修改/新增 ACL 条目(u: g: o: m: 四类与数字形式)

-m 是 setfacl 最高频的动作:在现有 ACL 上修改或新增条目,其他条目不动。先给 aclu1 授权并读回验证:

setfacl -b /root/demo/acl/acl-f1.txt
setfacl -m u:aclu1:rw /root/demo/acl/acl-f1.txt; echo "退出码=$?"
ls -l /root/demo/acl/acl-f1.txt
getfacl -c /root/demo/acl/acl-f1.txt
退出码=0
-rw-rw-r--+ 1 root root 10 Oct 16 14:29 /root/demo/acl/acl-f1.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rw-
group::r--
mask::rw-
other::r--

三个观察点:ls -l 权限位末尾多了一个 +,提示该文件带扩展 ACL;getfacl 多出 user:aclu1:rw- 命名条目与 mask::rw- 掩码行;授权真实生效——aclu1 能实际追加写入(前提是路径逐级可穿越,见 4.1 节):

su -s /bin/bash aclu1 -c 'echo aclu1-writes >> /root/demo/acl/acl-f1.txt'; echo "退出码=$?"
cat /root/demo/acl/acl-f1.txt
退出码=0
hello acl
aclu1-writes

-m 一条命令可写多个条目(逗号分隔),命名组用 g:,基础条目 others 用 o:: 修改——把文件收成「属主可读写、aclteam 组可读可进、其他人无任何权限」:

setfacl -m g:aclteam:rx,o::--- /root/demo/acl/acl-f2.txt
getfacl -c /root/demo/acl/acl-f2.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
group::r--
group:aclteam:r-x
mask::r-x
other::---

权限字段还接受单个八进制数字,7 即 rwx:

setfacl -m u:aclu1:7 /root/demo/acl/plain.txt
getfacl -c /root/demo/acl/plain.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rwx
group::r--
mask::rwx
other::---

(plain.txt 此刻的基础条目是 rw-/r--/---,即磁盘上遗留的 640 基线,不影响本节结论。)

💡 说明:-m 的语义是「叠加修改」——只动命令里点名的条目,其余条目原样保留;与之相对的「整体替换」语义是 --set(3.9 节实测)。授权后 mask 自动重算为受约束条目的并集,所以 user:aclu1:rw- 一落地 mask 就变成了 rw-(重算规则详见 3.3 节)。

3.2 大写 X:条件执行位

权限字段里的 x 会无条件加执行位,而大写 X 只在「对象是目录,或已有任意执行位」时才落地为 x,否则落地为 -。对普通文件和目录各测一次:

echo plain > /root/demo/acl/plain.txt
setfacl -m u:aclu1:X /root/demo/acl/plain.txt
getfacl -c /root/demo/acl/plain.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:---
group::r--
mask::r--
other::---

普通文件没有任何既有执行位,X 落地为 ---——aclu1 一个权限位都没拿到。换成目录:

mkdir -p /root/demo/acl/xdemo
setfacl -m u:aclu1:X /root/demo/acl/xdemo
getfacl -c /root/demo/acl/xdemo
getfacl: Removing leading '/' from absolute path names
user::rwx
user:aclu1:--x
group::r-x
mask::r-x
other::r-x

目录拿到 --x(可进入、可穿越),文件拿到 ---。

💡 说明:批量给「目录树」授权时的惯用法是 rX/rx——rX 让目录获得执行位(能 cd 进去)而文件保持只读,避免把普通文件误标成可执行。大写 X 与八进制数字形式互斥:数字 7 永远等于 rwx,没有条件语义。

3.3 mask:生效权限上限与重算时机

mask 是「授权了却不生效」问题的核心:命名用户、命名组、属组三类条目的实际生效权限 = 条目权限 ∩ mask。先看默认行为——每次 -m 修改后 mask 自动重算:

setfacl -m u:aclu1:rwx /root/demo/acl/acl-f1.txt
getfacl -c /root/demo/acl/acl-f1.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rwx
group::r--
mask::rwx
other::r--

aclu1 拿到 rwx,mask 自动重算为 rwx。手动收紧 mask 到 r--,观察 #effective: 注释与真实写入:

setfacl -m m::r-- /root/demo/acl/acl-f1.txt
getfacl /root/demo/acl/acl-f1.txt
getfacl: Removing leading '/' from absolute path names
# file: root/demo/acl/acl-f1.txt
# owner: root
# group: root
user::rw-
user:aclu1:rwx	#effective:r--
group::r--
mask::r--
other::r--
su -s /bin/bash aclu1 -c 'echo blocked >> /root/demo/acl/acl-f1.txt'; echo "退出码=$?"
bash: line 1: /root/demo/acl/acl-f1.txt: Permission denied
退出码=1

aclu1 名义上 rwx,生效只剩 r--,追加写入被内核拒绝——这就是 mask 裁剪的现场。再次 -m 命名条目会触发 mask 重算,裁剪自动解除:

setfacl -m u:aclu1:rw /root/demo/acl/acl-f1.txt
getfacl -c /root/demo/acl/acl-f1.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rw-
group::r--
mask::rw-
other::r--

-n 冻结重算:条目改成 rwx 但 mask 维持原值 rw-,出现 #effective: 注释:

setfacl -n -m u:aclu1:rwx /root/demo/acl/acl-f1.txt
getfacl -c /root/demo/acl/acl-f1.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rwx	#effective:rw-
group::r--
mask::rw-
other::r--

--mask 显式要求重算(与 -m 联用):

setfacl -m u:aclu1:rw --mask /root/demo/acl/acl-f1.txt
getfacl -c /root/demo/acl/acl-f1.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rw-
group::r--
mask::rw-
other::r--

单独使用 --mask(不带修改动作)会被拒绝——Usage 骨架里 { -m|-M|-x|-X ... } 是必选项:

setfacl --mask /root/demo/acl/acl-f2.txt
Usage: setfacl [-bkndRLP] { -m|-M|-x|-X ... } file ...
Try `setfacl --help' for more information.

只重算 mask 不动其他条目,用 -m m::权限 形式——直接把 mask 放宽到 rwx:

setfacl -m m::rwx /root/demo/acl/acl-f1.txt
getfacl -c /root/demo/acl/acl-f1.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rw-
group::r--
mask::rwx
other::r--

还可以在同一条命令里授条目的同时收紧 mask——命令里显式给了 m:: 条目,setfacl 就不再重算,收紧立即生效:

echo n > /root/demo/acl/m1.txt
setfacl -m u:aclu1:rw,m::r-- /root/demo/acl/m1.txt
getfacl -c /root/demo/acl/m1.txt
user::rw-
user:aclu1:rw-	#effective:r--
group::r--
mask::r--
other::r--

💡 说明:mask 重算规则总结——① -m/-M/-x 修改命名条目后默认自动重算为「命名用户 ∪ 命名组 ∪ 属组」的并集;② 命令里显式写了 m:: 条目则以命令值为准、不重算;③ -n 显式冻结本次不重算;④ --mask 与修改动作联用时强制重算。排查裁剪问题时 getfacl -e(带 #effective: 注释)最快定位。

3.4 -x:删除指定条目

-x 按条目形态删除:只写 tag:qualifier,不写权限字段。删除 aclu1 的条目(mask 随之重算回 r--):

setfacl -x u:aclu1 /root/demo/acl/acl-f1.txt
getfacl -c /root/demo/acl/acl-f1.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
group::r--
mask::r--
other::r--

两种典型报错——带了权限字段、删除不存在的条目,都是退出码 2:

setfacl -x u:aclu1:rw /root/demo/acl/acl-f1.txt; echo "退出码=$?"
setfacl -x u:aclu9 /root/demo/acl/acl-f1.txt; echo "退出码=$?"
setfacl: Option -x: Invalid argument near character 9
退出码=2
setfacl: Option -x: Invalid argument near character 3
退出码=2

qualifier 也可以写数字 UID:存在用户的 UID 会解析回名字,不存在的 UID 按数字落盘、按数字删除:

echo n > /root/demo/acl/num.txt
setfacl -m u:1018:r /root/demo/acl/num.txt
getfacl -c /root/demo/acl/num.txt
setfacl -m u:1599:r /root/demo/acl/num.txt
getfacl -c /root/demo/acl/num.txt
setfacl -x u:1599 /root/demo/acl/num.txt
getfacl -c /root/demo/acl/num.txt
user::rw-
user:aclu2:r--
group::r--
mask::r--
other::r--

user::rw-
user:aclu2:r--
user:1599:r--
group::r--
mask::r--
other::r--

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

💡 说明:-x 的记忆点是「删条目不写权限」——u:aclu1 合法,u:aclu1:rw 直接报 Invalid argument。删不存在的条目也报错(不是静默成功),脚本里判断删除结果可以直接看退出码。数字 UID 形态在用户被删除后仍有用:残留的 ACL 条目可以用 -x u:旧UID 清掉。

3.5 -b 与 -k:全清与只清默认

-b(remove-all)清除全部扩展条目。对文件——+ 号消失、只剩基础条目:

setfacl -b /root/demo/acl/acl-f1.txt
ls -l /root/demo/acl/acl-f1.txt
getfacl -c /root/demo/acl/acl-f1.txt
-rw-r--r-- 1 root root 23 Oct 16 14:29 /root/demo/acl/acl-f1.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
group::r--
other::r--

对带默认 ACL 的目录,-b 把访问 ACL 与默认 ACL 一起清掉:

mkdir -p /root/demo/acl/sdir2
setfacl -m u:aclu1:rx /root/demo/acl/sdir2
setfacl -d -m u:aclu1:rwx /root/demo/acl/sdir2
getfacl /root/demo/acl/sdir2
getfacl: Removing leading '/' from absolute path names
# file: root/demo/acl/sdir2
# owner: root
# group: root
user::rwx
user:aclu1:r-x
group::r-x
mask::r-x
other::r-x
default:user::rwx
default:user:aclu1:rwx
default:group::r-x
default:mask::rwx
default:other::r-x
setfacl -b /root/demo/acl/sdir2
getfacl /root/demo/acl/sdir2
getfacl: Removing leading '/' from absolute path names
# file: root/demo/acl/sdir2
# owner: root
# group: root
user::rwx
group::r-x
other::r-x

-k(remove-default)只清默认 ACL,访问 ACL 上的扩展条目原样保留:

mkdir -p /root/demo/acl/sdir3
setfacl -m u:aclu1:rx /root/demo/acl/sdir3
setfacl -d -m u:aclu1:rwx /root/demo/acl/sdir3
setfacl -k /root/demo/acl/sdir3
getfacl /root/demo/acl/sdir3
getfacl: Removing leading '/' from absolute path names
# file: root/demo/acl/sdir3
# owner: root
# group: root
user::rwx
user:aclu1:r-x
group::r-x
mask::r-x
other::r-x

💡 说明:-b 与 -k 的分工——-b = 「这个对象的 ACL 全部不要了」(含默认 ACL),-k = 「继承模板不要了,但已生效的授权保留」。清理离职员工权限时用 -x u:某人 精确回收;整体重置时才用 -b。

3.6 -d/--default:默认 ACL 与继承

-d 把操作目标切到默认 ACL:它只存在于目录,规定「以后在这里新建的对象继承什么」。给 sdir3 设置默认 ACL 并验证继承——root 新建的文件自动带出命名条目与 mask:

setfacl -b /root/demo/acl/sdir3
rm -rf /root/demo/acl/sdir3/subdir /root/demo/acl/sdir3/*.txt
setfacl -d -m u:aclu1:rwx,g::r-x,o::r-x /root/demo/acl/sdir3
touch /root/demo/acl/sdir3/newfile.txt
getfacl -c /root/demo/acl/sdir3/newfile.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rwx	#effective:rw-
group::r-x	#effective:r--
mask::rw-
other::r--

新建子目录不仅继承访问 ACL,还把默认 ACL 一并带下去(继承链不断):

mkdir /root/demo/acl/sdir3/subdir
getfacl -c /root/demo/acl/sdir3/subdir
getfacl: Removing leading '/' from absolute path names
user::rwx
user:aclu1:rwx
group::r-x
mask::rwx
other::r-x
default:user::rwx
default:user:aclu1:rwx
default:group::r-x
default:mask::rwx
default:other::r-x

接下来让 aclu1 在该目录里建文件。第一轮真实失败了——报 Permission denied:

su -s /bin/bash aclu1 -c 'echo byaclu1 > /root/demo/acl/sdir3/byaclu1.txt'
getfacl -c /root/demo/acl/sdir3/byaclu1.txt
bash: line 1: /root/demo/acl/sdir3/byaclu1.txt: Permission denied
getfacl: /root/demo/acl/sdir3/byaclu1.txt: No such file or directory

原因不在默认 ACL,而在目录自身的访问 ACL:上面只设了默认 ACL,sdir3 的访问 ACL 并没有给 aclu1 放行,aclu1 连「在这个目录里创建文件」的资格都没有。给目录访问 ACL 同步授权后,继承立即生效:

setfacl -m u:aclu1:rwx /root/demo/acl/sdir3
getfacl -c /root/demo/acl/sdir3
getfacl: Removing leading '/' from absolute path names
user::rwx
user:aclu1:rwx
group::r-x
mask::rwx
other::r-x
default:user::rwx
default:user:aclu1:rwx
default:group::r-x
default:mask::rwx
default:other::r-x
su -s /bin/bash aclu1 -c 'echo byaclu1 > /root/demo/acl/sdir3/byaclu1.txt'; echo "退出码=$?"
getfacl -c /root/demo/acl/sdir3/byaclu1.txt
退出码=0
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rwx	#effective:rw-
group::r-x	#effective:r--
mask::rw-
other::r--
su -s /bin/bash aclu1 -c 'echo append-ok >> /root/demo/acl/sdir3/byaclu1.txt && cat /root/demo/acl/sdir3/byaclu1.txt'; echo "退出码=$?"
byaclu1
append-ok
退出码=0

aclu1 自己创建的文件同样继承了 user:aclu1:rwx,读写验证全部通过。再看 umask 的角色——aclu1 的 umask 是 0002,但新文件的组条目生效权限是 r--(按默认 ACL 的 r-x 收敛),权限完全由默认 ACL 与创建模式取交集决定:

su -s /bin/bash aclu1 -c 'umask'
0002

-d 也能与 -x 联用,精确删除单条默认条目——注意删除后默认 ACL 的 mask 自动重算(rwx → r-x):

mkdir -p /root/demo/acl/dx
setfacl -d -m u:aclu1:rwx,g::r-x,o::r-x /root/demo/acl/dx
getfacl -c /root/demo/acl/dx
setfacl -d -x u:aclu1 /root/demo/acl/dx; echo "退出码=$?"
getfacl -c /root/demo/acl/dx
user::rwx
group::r-x
other::r-x
default:user::rwx
default:user:aclu1:rwx
default:group::r-x
default:mask::rwx
default:other::r-x

退出码=0
user::rwx
group::r-x
other::r-x
default:user::rwx
default:group::r-x
default:mask::r-x
default:other::r-x

💡 说明:默认 ACL 的两条铁律——① 它不改变目录自身的访问权限(本节真实失败案例就是只设了默认、没设访问);② 一旦目录有默认 ACL,新建对象的权限 = 默认 ACL 条目 ∩ 创建模式,umask 不再参与计算(文件创建模式 0666 交出 w 位后,user:aclu1:rwx 收敛为生效 rw-)。共享目录场景必须「访问 ACL + 默认 ACL」成对设置(4.3 节实战)。

3.7 -R / -L / -P:递归与符号链接

-R 让授权穿越整棵目录树,按树序逐个处理目录、子目录与文件:

mkdir -p /root/demo/acl/sdir/sub
echo n > /root/demo/acl/sdir/sub/n.txt
setfacl -R -m u:aclu2:rx /root/demo/acl/sdir
getfacl -R -c /root/demo/acl/sdir
getfacl: Removing leading '/' from absolute path names
user::rwx
user:aclu2:r-x
group::r-x
mask::r-x
other::r-x

user::rwx
user:aclu2:r-x
group::r-x
mask::r-x
other::r-x

user::rw-
user:aclu2:r-x
group::r--
mask::r-x
other::r--

符号链接的语义由 -L/-P 控制。先看默认(跟随):对链接本身 -m,实际改到的是目标文件:

echo plain > /root/demo/acl/plain.txt
setfacl -b /root/demo/acl/plain.txt
ln -sfn /root/demo/acl/plain.txt /root/demo/acl/slink1
setfacl -m u:aclu1:rw /root/demo/acl/slink1
getfacl -p /root/demo/acl/plain.txt
# file: /root/demo/acl/plain.txt
# owner: root
# group: root
user::rw-
user:aclu1:rw-
group::r--
mask::rw-
other::r--

-P(physical,严格不跟随):对命令行上的链接参数静默跳过——退出码 0、不报错、目标文件纹丝不动(符号链接自身不存储 ACL):

setfacl -b /root/demo/acl/plain.txt
ln -sfn /root/demo/acl/plain.txt /root/demo/acl/slink2
setfacl -P -m u:aclu2:rw /root/demo/acl/slink2; echo "退出码=$?"
getfacl -p /root/demo/acl/plain.txt
退出码=0
# file: /root/demo/acl/plain.txt
# owner: root
# group: root
user::rw-
group::r--
other::r--

去掉 -P(默认跟随)对照组——同名命令立即作用到目标:

setfacl -m u:aclu2:rw /root/demo/acl/slink2
getfacl -p /root/demo/acl/plain.txt
# file: /root/demo/acl/plain.txt
# owner: root
# group: root
user::rw-
user:aclu2:rw-
group::r--
mask::rw-
other::r--

递归场景的差异更关键。建一棵含「指向树外的链接」的树,-R -P 不跟随链接:树内 3 个对象各拿到 ACL,树外目标 plain.txt 未被波及:

rm -rf /root/demo/acl/rdir
mkdir -p /root/demo/acl/rdir/sub
echo n > /root/demo/acl/rdir/f
ln -s /root/demo/acl/plain.txt /root/demo/acl/rdir/lnk
setfacl -R -P -m u:aclu1:rx /root/demo/acl/rdir
getfacl -p /root/demo/acl/plain.txt
getfacl -R -c /root/demo/acl/rdir
# file: /root/demo/acl/plain.txt
# owner: root
# group: root
user::rw-
group::r--
other::r--

user::rwx
user:aclu1:r-x
group::r-x
mask::r-x
other::r-x

user::rw-
user:aclu1:r-x
group::r--
mask::r-x
other::r--

user::rwx
user:aclu1:r-x
group::r-x
mask::r-x
other::r-x

-R -L 显式跟随:树内链接被跟踪到树外,plain.txt 被改到(aclu2 条目落地):

setfacl -R -L -m u:aclu2:rx /root/demo/acl/rdir
getfacl -p /root/demo/acl/plain.txt
# file: /root/demo/acl/plain.txt
# owner: root
# group: root
user::rw-
user:aclu2:r-x
group::r--
mask::r-x
other::r--

💡 说明:-L/-P 只影响「要不要跟进链接」,默认行为等价 -L(跟随)。运维上建议批量授权时显式带 -P:既防止树内的恶意/悬空链接把授权「引」到树外,也避免踩到链接环。-P 跳过时不报错、退出码 0,脚本无法靠退出码感知跳过——需要确认时用 getfacl 复核。

3.8 -M / -X:从文件批量修改与移除

-M/-X 与 -m/-x 同义,只是条目清单改从文件读——清单格式与 getfacl -c 的输出完全同构,一行一条。批量修改三条:

printf 'u:aclu1:rw-\nu:aclu2:r--\ng:aclteam:r--\n' > /root/demo/acl/batch2.acl
setfacl -M /root/demo/acl/batch2.acl /root/demo/acl/plain.txt
getfacl -c /root/demo/acl/plain.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rw-
user:aclu2:r--
group::r--
group:aclteam:r--
mask::rw-
other::---

批量移除——清单里同样只写 tag:qualifier 不写权限:

printf 'u:aclu1\nu:aclu2\ng:aclteam\n' > /root/demo/acl/xbatch2.acl
setfacl -X /root/demo/acl/xbatch2.acl /root/demo/acl/plain.txt
getfacl -c /root/demo/acl/plain.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
group::r--
mask::r--
other::---

清单文件位置写 - 表示标准输入,可以直接管道生成、省去临时文件:

echo n > /root/demo/acl/mi.txt
printf 'u:aclu1:rw-\ng:aclteam:r--\n' | setfacl -M - /root/demo/acl/mi.txt; echo "退出码=$?"
getfacl -c /root/demo/acl/mi.txt
退出码=0
user::rw-
user:aclu1:rw-
group::r--
group:aclteam:r--
mask::rw-
other::r--

💡 说明:-M/-X 的最大价值是「清单即资产」——把部门授权清单纳入版本管理,换机器、批量重放都有据可依;配合 getfacl -c 对象 > 清单 还能实现 ACL 在对象间的复制(3.9 节 --set-file 同理)。

3.9 --set / --set-file:整体替换

-m 是叠加,--set 是整体替换:命令里没写的条目全部消失。plain.txt 此前带有 aclu1/aclu2/aclteam 等条目,--set 只保留命令列出的内容:

setfacl --set u::rw-,u:aclu2:r--,g::r--,o::--- /root/demo/acl/plain.txt
getfacl -c /root/demo/acl/plain.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu2:r--
group::r--
mask::r--
other::---

--set-file 从文件读整份 ACL。最实用的玩法:直接把另一个文件的 getfacl 产物灌进来,实现「ACL 复制」:

getfacl -c /root/demo/acl/acl-f1.txt > /root/demo/acl/setfile.acl
setfacl --set-file=/root/demo/acl/setfile.acl /root/demo/acl/plain.txt
getfacl -c /root/demo/acl/plain.txt
getfacl: Removing leading '/' from absolute path names
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rw-
group::r--
mask::rw-
other::r--

💡 说明:--set 适合「以我列的为准」的收敛动作(如把临时放行全部收掉、只留白名单);--set-file + getfacl -c 两行就是最小成本的 ACL 迁移方案。注意替换语义下属主/属组/其他三条基础条目也要写全,漏写的按替换语义处理。

3.10 --test:干跑预演

--test 完整执行解析与计算,但不落盘,把「将要生效的 ACL」以单行格式打到标准输出——单行内条目以逗号分隔,末尾固定带一个 ,* 收尾标记:

setfacl --test -m u:aclu2:rwx /root/demo/acl/plain.txt
echo '--- 复核未生效 ---'
getfacl -c /root/demo/acl/plain.txt
/root/demo/acl/proj/../acl/plain.txt: u::rw-,u:aclu1:rwx,u:aclu2:rwx,g::r--,m::rwx,o::---,*
--- 复核未生效 ---
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rwx
group::r--
mask::rwx
other::---

复核确认 plain.txt 的 ACL 与干跑前一致(没有 u:aclu2 条目)——--test 确实什么都没改。单行格式里能提前看到:新增的 u:aclu2:rwx、mask 将重算为 rwx。

💡 说明:--test 的正确位置是「所有批量/递归授权的前一步」——setfacl --test -R -m ... 目录 先看整树结果清单,确认无误后去掉 --test 重放。这也是 4.1 节实战的标准开场。

3.11 --restore:按 getfacl -R 清单恢复

--restore 是 getfacl -R 的逆操作:按备份清单里的 # file: 路径逐个还原 ACL。完整闭环——备份、模拟误清、恢复、复核:

cd /root/demo/acl
getfacl -R sdir > sdir-backup.acl
setfacl -R -b /root/demo/acl/sdir
echo '--- 破坏后 ---'
getfacl -R -c /root/demo/acl/sdir
setfacl --restore=sdir-backup.acl
echo '--- 恢复后 ---'
getfacl -R -c /root/demo/acl/sdir
--- 破坏后 ---
getfacl: Removing leading '/' from absolute path names
user::rwx
group::r-x
other::r-x

user::rwx
group::r-x
other::r-x

user::rw-
group::r--
other::r--

--- 恢复后 ---
getfacl: Removing leading '/' from absolute path names
user::rwx
user:aclu2:r-x
group::r-x
mask::r-x
other::r-x

user::rwx
user:aclu2:r-x
group::r-x
mask::r-x
other::r-x

user::rw-
user:aclu2:r-x
group::r--
mask::r-x
other::r--

三个对象(sdir、sdir/sub、sdir/sub/n.txt)的扩展条目与 mask 全部还原。但 --restore 有一个真实的坑——备份与恢复两端的路径形态必须对齐。下面是一次真实失败:备份时用绝对路径(getfacl 默认把清单里的前导 / 剥掉),恢复时却 cd /root/demo/acl 执行:

getfacl -R /root/demo/acl/proj > /root/demo/acl/proj-backup.acl
setfacl -R -b /root/demo/acl/proj
cd /root/demo/acl && setfacl --restore=proj-backup.acl
setfacl: root/demo/acl/proj: No such file or directory
setfacl: root/demo/acl/proj/src: No such file or directory
setfacl: root/demo/acl/proj/Makefile: No such file or directory

清单里是 root/demo/acl/proj(无前导 /),在 /root/demo/acl 下执行被解析成 /root/demo/acl/root/demo/acl/proj——自然不存在。正确姿势:回到 /(清单相对路径的基准点)执行恢复:

cd / && setfacl --restore=root/demo/acl/proj-backup.acl; echo "恢复退出码=$?"
getfacl -R -c /root/demo/acl/proj > /tmp/proj-after.txt
diff /tmp/proj-after.txt /tmp/proj-after2.txt; echo "diff退出码=$?"
恢复退出码=0
getfacl: Removing leading '/' from absolute path names
2a3,4
> group:aclteam:r-x
> mask::r-x
6a9,10
> group:aclteam:r-x
> mask::r-x
diff退出码=1

恢复退出码 0,diff 显示 proj 目录与 proj/src 子目录的 group:aclteam:r-x 与 mask::r-x 两行重新出现(Makefile 段的授权此前已被重新执行过——见 4.1 节,故无差异行)。

💡 说明:对齐规则一句话——清单里的 # file: 写的什么形态,就在哪一级目录执行 --restore。默认 getfacl 剥掉前导 /,所以绝对路径备份的标准恢复姿势是 cd / && setfacl --restore=...;也可以备份时就在目标目录里用相对路径(如上文 sdir 案例),恢复时回同一目录。批量误操作后先 --test 不适用于恢复,但恢复后用 getfacl -R + diff 复核(如上)是必须动作。

3.12 错误场景与退出码

把会踩到的报错集中实测。非法用户名、非法 tag、目标文件不存在三类:

setfacl -m u:nosuchuser:rw /root/demo/acl/plain.txt; echo "退出码=$?"
setfacl -m z::rwx /root/demo/acl/plain.txt; echo "退出码=$?"
setfacl -m u:aclu1:rw /no/such/file; echo "退出码=$?"
setfacl: Option -m: Invalid argument near character 3
退出码=2
setfacl: Option -m: Invalid argument near character 1
退出码=2
setfacl: /no/such/file: No such file or directory
退出码=1

还有一类「结构性」报错:ACL 里有命名条目时 mask 是必需的,直接 -x m:: 删 mask 会被内核判定为畸形 ACL:

echo n > /root/demo/acl/m1.txt
setfacl -m u:aclu1:rw /root/demo/acl/m1.txt
setfacl -x m:: /root/demo/acl/m1.txt; echo "退出码=$?"
getfacl -c /root/demo/acl/m1.txt
setfacl: m1.txt: Malformed access ACL `user::rw-,user:aclu1:rw-,group::r--,other::r--': Missing or wrong entry at entry 4
退出码=1
user::rw-
user:aclu1:rw-	#effective:r--
group::r--
mask::r--
other::r--

退出码规律:0 成功;1 文件级错误(对象不存在、内核拒绝该 ACL 结构);2 参数级错误(条目语法、选项用法,如 3.3 节单独使用 --mask 的 Usage 报错)。

💡 说明:Invalid argument near character N 的 N 指向条目串里出问题的字段位置——z::rwx 的非法 tag 在第 1 字符、u:nosuchuser:rw 的非法限定名从第 3 字符起。批量脚本里建议逐条 try:一条非法条目(退出码 2)不会影响其他合法文件的修改,但会让整个命令返回非 0。

3.13 chmod 与 ACL 的联动(含 ls -l 的 + 号)

ACL 与传统权限位是同一份数据的两个视图,chmod 会直接改写 ACL 的对应条目——存在命名条目时,chmod 的 g 位改写的其实是 mask。实测链条:

echo data > /root/demo/acl/cm.txt
setfacl -m u:aclu1:rwx /root/demo/acl/cm.txt
getfacl -c /root/demo/acl/cm.txt
chmod g-w /root/demo/acl/cm.txt
getfacl -c /root/demo/acl/cm.txt
ls -l /root/demo/acl/cm.txt
chmod g+w /root/demo/acl/cm.txt
getfacl -c /root/demo/acl/cm.txt
user::rw-
user:aclu1:rwx
group::r--
mask::rwx
other::r--

user::rw-
user:aclu1:rwx	#effective:r-x
group::r--
mask::r-x
other::r--

-rw-r-xr--+ 1 root root 5 Oct 16 15:35 cm.txt
user::rw-
user:aclu1:rwx
group::r--
mask::rwx
other::r--

逐步解读:chmod g-w 后 mask 从 rwx 收到 r-x,aclu1 出现 #effective:r-x 注释(名义 rwx 被裁剪);此刻 ls -l 显示 -rw-r-xr--+——属组位显示的正是 mask 值;chmod g+w 放回后 mask 恢复 rwx,注释消失。+ 号只提示「有扩展 ACL」,细节必须 getfacl 看。目录仅设默认 ACL(访问 ACL 只有基础条目)时同样显示 +:

mkdir -p /root/demo/acl/donly
setfacl -d -m u::rwx,g::r-x,o::r-x /root/demo/acl/donly
ls -ld /root/demo/acl/donly
getfacl -c /root/demo/acl/donly
getfacl -d -c /root/demo/acl/donly
drwxr-xr-x+ 2 root root 4096 Oct 16 15:35 donly
user::rwx
group::r-x
other::r-x
default:user::rwx
default:group::r-x
default:other::r-x

user::rwx
group::r-x
other::r-x

第一段是完整 ACL(访问 ACL 三条基础条目 + 默认 ACL 三条 default: 条目),显示 drwxr-xr-x+;第二段 getfacl -d 单看默认 ACL,条目以不带 default: 前缀的裸形态显示。

💡 说明:看到 + 就该用 getfacl 看细节,仅凭 ls 的三段位判断权限会漏掉命名条目与默认 ACL;反之看到属组位「莫名变化」时,先想想是不是有人跑了 chmod、改的其实是 mask(本节实测)。

3.14 相关命令对照拓展表

setfacl 不是孤岛,它与权限族其他命令的关系一张表说清:

命令职责与 ACL 的关系 / 关键差异典型用法
setfacl增删改 ACL 条目本文主角:命名用户/组、mask、默认 ACL 都由它维护setfacl -m u:aclu1:rw f
getfacl读 ACL输出即 setfacl 的输入格式,是备份/迁移载体(3.8/3.11 节实测)getfacl -R dir > backup.acl
chmod改三段位改写 ACL 基础条目;有命名条目时 g 位改写的是 mask(3.13 节实测)chmod 750 f
chown改属主(可连带属组)只换归属、不增删条目;user:: 条目自动指向新属主chown aclu1 f
chgrp改属组同上;group:: 条目自动指向新属组chgrp aclteam f
umask进程侧收敛新建对象默认权限目录设了默认 ACL 后 umask 不参与计算(3.6 节实测)umask 0022

关于 ls -l 的 + 号:它表示该对象带扩展 ACL——存在命名条目,或目录存在默认 ACL(3.13 节 donly 目录仅设默认 ACL 也显示 drwxr-xr-x+);有命名条目时属组位显示的是 mask 值而非 group:: 基础条目。+ 只是「提醒」,真正的授权细节以 getfacl 为准(读类详解见本专栏《getfacl 命令详解与参数实测(Ubuntu 22.04)》篇)。

💡 说明:选型口诀——「归属找 chown/chgrp,三段位找 chmod,点名到人/组与继承模板找 setfacl」。三者操作的是同一份权限数据,互相都会改写对方视图里的值,排障时按这个表对号入座即可。

四、实战示例

本章用四个真实运维场景把第三章的参数串起来:单文件放行(含一次真实排障)、整树授权与误清恢复、共享目录、搬家时的 ACL 保留。所有回显来自真实执行。

4.1 实战A:给运维组放行单个文件——预演、落地、排障、闭环

场景:/root/demo/acl/proj/Makefile 要让运维组(aclteam)可读可执行、其他人无权限。第一步永远先干跑:

setfacl --test -m g:aclteam:rx,o::--- /root/demo/acl/proj/Makefile
/root/demo/acl/proj/Makefile: u::rw-,g::r--,g:aclteam:r-x,m::r-x,o::---,*

单行清单符合预期:aclteam 得 r-x、mask r-x、other 清零。正式落地并复核:

setfacl -m g:aclteam:rx,o::--- /root/demo/acl/proj/Makefile
getfacl -c /root/demo/acl/proj/Makefile
getfacl: Removing leading '/' from absolute path names
user::rw-
group::r--
group:aclteam:r-x
mask::r-x
other::---

ACL 落地正确。用组员 aclu2 验证——真实结果是被拒:

su -s /bin/bash aclu2 -c 'cat /root/demo/acl/proj/Makefile'
cat: /root/demo/acl/proj/Makefile: Permission denied

开始排障。第一查组成员(组条目只对真实组员生效):

id aclu2
getent group aclteam
uid=1018(aclu2) gid=3107(aclu2) groups=3107(aclu2),3108(aclteam)
aclteam:x:3108:aclu2

组员身份没问题。第二查逐级路径权限——目标在 /root 下,每一级目录都需要 x 穿越权:

ls -ld /root /root/demo /root/demo/acl
getfacl -p /root
drwx--x---+  1 root root 20480 Oct 16 01:12 /root
drwxr-xr-x+ 89 root root  4096 Oct 16 14:20 /root/demo
drwxr-xr-x+ 11 root root  4096 Oct 16 15:38 /root/demo/acl
# file: /root
# owner: root
# group: root
user::rwx
user:aclu1:--x
group::---
mask::--x
other::---

根因找到:/root 的扩展 ACL 只授了 user:aclu1:--x,没有 aclu2——aclu2 在 /root 这一级就被拦下,根本没走到目标文件。逐级补 x 后闭环验证:

setfacl -m u:aclu2:x /root /root/demo /root/demo/acl
getfacl -p /root
su -s /bin/bash aclu2 -c 'cat /root/demo/acl/proj/Makefile'; echo "读退出码=$?"
su -s /bin/bash aclu2 -c 'echo x >> /root/demo/acl/proj/Makefile'; echo "写退出码=$?"
# file: /root
# owner: root
# group: root
user::rwx
user:aclu1:--x
user:aclu2:--x
group::---
mask::--x
other::---

all:
读退出码=0
bash: line 1: /root/demo/acl/proj/Makefile: Permission denied
写退出码=1

aclu2 读取成功(输出文件内容 all:)、追加写入仍被拒——r-x 没有 w 位,最小放行精确达成。

💡 说明:这次排障沉淀出一条固定顺序——「组条目不生效」先 id 查组成员,再 ls -ld/getfacl 逐级查路径穿越权限(目录的 x 是进入权),最后才看目标对象的 ACL。ACL 条目挂在目标上,但到达目标的每一级目录都要放行。

4.2 实战B:整树授权、备份与误清恢复

场景:整个 proj 树要给运维组放行,之后要能整体回滚。整树授权并复核三个对象(proj、proj/src、proj/Makefile):

setfacl -R -m g:aclteam:rx /root/demo/acl/proj
getfacl -R -c /root/demo/acl/proj
getfacl: Removing leading '/' from absolute path names
user::rwx
group::r-x
group:aclteam:r-x
mask::r-x
other::r-x

user::rwx
group::r-x
group:aclteam:r-x
mask::r-x
other::r-x

user::rw-
group::r--
group:aclteam:r-x
mask::r-x
other::---

关键动作:授权之后立即用 getfacl -R 落一份带 # file: 头的备份清单:

getfacl -R /root/demo/acl/proj > /root/demo/acl/proj-backup.acl
getfacl: Removing leading '/' from absolute path names

模拟一次误清(比如手滑对整树跑了 -b):

setfacl -R -b /root/demo/acl/proj
getfacl -R -c /root/demo/acl/proj
getfacl: Removing leading '/' from absolute path names
user::rwx
group::r-x
other::r-x

user::rwx
group::r-x
other::r-x

user::rw-
group::r--
other::---

恢复时踩到 3.11 节的路径陷阱(在 /root/demo/acl 下执行,清单路径对不上):

cd /root/demo/acl && setfacl --restore=proj-backup.acl
setfacl: root/demo/acl/proj: No such file or directory
setfacl: root/demo/acl/proj/src: No such file or directory
setfacl: root/demo/acl/proj/Makefile: No such file or directory

逐行 diff 实锤——三个对象的 group:aclteam 与 mask 全部缺失,恢复确实没有发生:

getfacl -R -c /root/demo/acl/proj > /tmp/proj-after.txt && diff /tmp/proj-before.txt /tmp/proj-after.txt
getfacl: Removing leading '/' from absolute path names
3,4d2
< group:aclteam:r-x
< mask::r-x
9,10d6
< group:aclteam:r-x
< mask::r-x
15,16d10
< group:aclteam:r-x
< mask::r-x

按对齐规则回到 / 执行,恢复成功并 diff 复核:

cd / && setfacl --restore=root/demo/acl/proj-backup.acl; echo "恢复退出码=$?"
getfacl -R -c /root/demo/acl/proj > /tmp/proj-after.txt
diff /tmp/proj-after.txt /tmp/proj-after2.txt; echo "diff退出码=$?"
恢复退出码=0
getfacl: Removing leading '/' from absolute path names
2a3,4
> group:aclteam:r-x
> mask::r-x
6a9,10
> group:aclteam:r-x
> mask::r-x
diff退出码=1

(diff 的退出码 1 表示「有差异」——此处差异正是恢复回来的 group:aclteam:r-x 与 mask::r-x 两行,是好消息。)

💡 说明:整树 ACL 运维的黄金三件套——setfacl -R -m 授权、getfacl -R > 清单 备份、setfacl --restore 恢复。备份必须在授权后立即做;恢复时记住路径对齐(3.11 节);恢复后用 getfacl -R -c + diff 复核到行。

4.3 实战C:团队共享目录——访问 ACL 与默认 ACL 成对设置

场景:给运维组建一个共享目录,组员可进可建、新建文件自动带继承权限、组外人员完全隔离。目录级两刀——访问 ACL 放行组员、默认 ACL 定继承模板:

mkdir -p /root/demo/acl/share
setfacl -m g:aclteam:rwx,o::--- /root/demo/acl/share
setfacl -d -m u::rwx,g::rwx,o::--- /root/demo/acl/share
getfacl /root/demo/acl/share
getfacl: Removing leading '/' from absolute path names
# file: root/demo/acl/share
# owner: root
# group: root
user::rwx
group::r-x
group:aclteam:rwx
mask::rwx
other::---
default:user::rwx
default:group::rwx
default:other::---

上半段是访问 ACL(组员 rwx、other 清零),下半段是默认 ACL(新建对象继承模板)。组员 aclu2 建文件——第一轮在 /root 路径穿越未打通时真实失败过(回显同 4.1 节),打通后验证:

su -s /bin/bash aclu2 -c 'echo hi > /root/demo/acl/share/a2.txt'; echo "创建退出码=$?"
getfacl -c /root/demo/acl/share/a2.txt
su -s /bin/bash aclu2 -c 'cat /root/demo/acl/share/a2.txt'; echo "回读退出码=$?"
创建退出码=0
getfacl: Removing leading '/' from absolute path names
user::rw-
group::rw-
other::---

hi
回读退出码=0

新文件自动继承默认 ACL:属主 rw-(rwx 与文件创建模式 0666 取交集交出 x)、属组 rw-、other 清零。非组员 aclu1 被 other::--- 挡住,隔离同样成立:

su -s /bin/bash aclu1 -c 'cat /root/demo/acl/share/a2.txt'
cat: /root/demo/acl/share/a2.txt: Permission denied

💡 说明:共享目录的完整公式 = 访问 ACL(现在谁能用这个目录)+ 默认 ACL(以后新建的归谁),缺一不可——只设默认 ACL 会出现 3.6 节那种「目录都进不去」的失败。新建文件的最终形态是「默认条目 ∩ 创建模式」,所以模板里的 rwx 落到普通文件会自动收敛为 rw-。

4.4 实战D:搬家时 ACL 的保留差异(cp / cp -p / tar / mv)

场景:带 ACL 的文件要复制、归档、改名,哪些操作保得住 ACL?先给 Makefile 授权作为源对象:

setfacl -m u:aclu1:rw /root/demo/acl/proj/Makefile
getfacl -c /root/demo/acl/proj/Makefile
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rw-
group::r--
mask::rw-
other::---

cp 默认:新建目标文件,ACL 丢失:

cp /root/demo/acl/proj/Makefile /root/demo/acl/proj/mk-cp.txt
getfacl -c /root/demo/acl/proj/mk-cp.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
group::r--
other::---

cp -p:连同命名条目与 mask 完整保留:

cp -p /root/demo/acl/proj/Makefile /root/demo/acl/proj/mk-cpp.txt
getfacl -c /root/demo/acl/proj/mk-cpp.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rw-
group::r--
mask::rw-
other::---

tar --acls:归档时显式带 --acls 才保存 ACL,解包后完整还原:

tar --acls -cf /tmp/mk.tar -C /root/demo/acl/proj Makefile
rm -f /tmp/Makefile && tar --acls -xf /tmp/mk.tar -C /tmp
getfacl -c /tmp/Makefile
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rw-
group::r--
mask::rw-
other::---

mv:同一文件系统内改名/移动不动 inode,ACL 原样保留:

mv /root/demo/acl/proj/mk-cpp.txt /root/demo/acl/proj/mk-mv.txt
getfacl -c /root/demo/acl/proj/mk-mv.txt
getfacl: Removing leading '/' from absolute path names
user::rw-
user:aclu1:rw-
group::r--
mask::rw-
other::---

四种操作结论汇总:

操作ACL 是否保留实测结论
cp✗新文件只剩基础条目,按目标侧规则重建
cp -p✓命名条目、mask 完整保留
tar --acls✓归档与解包都须显式 --acls(GNU tar 默认不处理 ACL)
mv✓同一文件系统内不动 inode;跨文件系统移动退化为复制,行为同 cp

💡 说明:备份/迁移清单里务必写明 ACL 策略——普通 cp/tar 静默丢 ACL 且不报任何错,是最容易被忽略的权限回退。跨机器迁移更稳的姿势是 getfacl -R 清单 + 目标机 --restore(4.2 节),与打包方式解耦。

五、注意事项与常见问题

Q1:给用户授了权,为什么还是写不进去? 十有八九是 mask 裁剪:命名条目的实际生效权限 = 条目权限 ∩ mask(3.3 节实测,裁剪时 getfacl 显示 #effective: 注释、写入被拒)。修复:setfacl -m m::权限 放宽、或重新 -m 该条目触发自动重算。另一个隐蔽来源是 chmod:有命名条目时 chmod 的 g 位改写的就是 mask(3.13 节实测 chmod g-w 把 mask 收到 r-x)。

Q2:组条目设了,某组员却访问不了,怎么排查? 固定三步:① id 用户 确认真的在组里(组成员关系在会话建立时生效,usermod 追加组后旧会话不自动获得,需重新登录或新开 su);② 逐级检查路径权限——目标文件的 ACL 再正确,路径上任何一级目录缺 x 都会被拦(4.1 节真实案例:/root 只授了 aclu1 的穿越权,aclu2 在组、ACL 正确仍被拒);③ getfacl 复核目标 ACL 与 mask。

Q3:setfacl --restore 报 No such file or directory 是怎么回事? 备份清单里 # file: 的路径形态与恢复时所在目录没对齐(4.2 节真实案例)。getfacl 默认剥掉清单路径的前导 /,所以绝对路径备份的标准姿势是 cd / && setfacl --restore=...;相对路径备份则回备份时的同一目录执行。恢复后用 getfacl -R -c + diff 逐行复核。

Q4:Invalid argument near character N 和 Malformed access ACL 分别是什么错? 前者是参数级错误(退出码 2):tag 非法(如 z::rwx 在第 1 字符)、用户/组名不存在、-x 带了权限字段(-x u:aclu1:rw 在第 9 字符)——按 N 定位字段即可(3.4/3.12 节实测)。后者是结构级错误(退出码 1):ACL 里存在命名条目时 mask 是必需的,-x m:: 直接删 mask 会被内核拒绝,应改为放宽 -m m:: 或先删命名条目(3.12 节实测)。

Q5:-b 和 -k 都是清空,差在哪? -b 清掉全部扩展条目:命名用户/组、mask,以及目录的默认 ACL 一起清(3.5 节 sdir2 实测);-k 只清默认 ACL,访问 ACL 的扩展条目原样保留(3.5 节 sdir3 实测)。只想去掉「继承模板」用 -k;对象要彻底回到传统三段位用 -b。

Q6:目录设了默认 ACL,umask 还起作用吗?还要注意什么? umask 不再参与:新建对象的权限 = 默认 ACL 条目 ∩ 创建模式(3.6 节实测:aclu1 umask 0002,新文件组条目生效权限仍按模板 r-x 收敛为 r--)。更要紧的两个坑:默认 ACL 不改变目录自身的访问权限,访问 ACL 要单独设(3.6 节真实失败案例);继承只影响「新建」,对设置默认 ACL 之前就存在的子对象无效,需要 setfacl -R 补齐。

Q7:cp、mv、tar 谁会保留 ACL? 实测结论(4.4 节):cp 默认丢、cp -p 保留;tar 必须归档/解包两侧都显式 --acls,且 GNU tar 默认不处理 ACL;mv 同一文件系统内保留(不动 inode),跨文件系统移动退化为复制、行为同 cp。批量迁移建议走 getfacl -R 清单 + --restore,与打包工具解耦。

Q8:ls -l 末尾的 + 号什么时候出现?属组位怎么「变值」了? 出现 + 表示带扩展 ACL:存在命名条目,或目录存在默认 ACL(3.13 节 donly 目录仅设默认 ACL 也显示 +)。有命名条目时,ls 属组位显示的其实是 mask 值而非 group:: 基础条目——3.13 节 cm.txt 上 chmod g-w 后 ls 显示 -rw-r-xr--+,那个 r-x 正是收紧后的 mask。+ 只是提醒,细节以 getfacl 为准。

六、参考链接