Linux umask 权限计算:为什么文件是 640,目录却是 750

10-01 5阅读

同一个终端里新建文本文件通常得到 644,新建目录却是 755,这并不矛盾。程序请求的初始权限不同,umask 再从中屏蔽指定权限位。把两者分开计算,才能解释为什么调整掩码后结果仍不符合预期。本文用 Linux 和 Python 3 做隔离实验,不改动现有业务文件或终端的掩码。

Linux umask 权限计算:为什么文件是 640,目录却是 750

AI生成概念配图:三组权限方块经过掩码筛选,分别形成文件与目录的权限组合。仅作概念说明,不代表实际界面或实测结果。

先把八进制权限拆成位

普通权限分为所有者、所属组、其他人三组,每组的读、写、执行分别对应四、二、一。数字七代表三位全部打开,六代表读写,五代表读和执行,四代表只读。文件的执行位允许作为程序执行;目录对应的是搜索权限,用于沿路径查找其中的条目。

在父目录没有默认 ACL 的前提下,创建后的普通权限等于“请求权限按位与掩码取反”,即 mode & ~umask。掩码中的一表示清除对应位,它只会扣掉申请过的权限,不会补上程序没有申请的位。计算时使用八进制;Python 的八进制整数以 0o 开头。

常见普通文件请求 0666,目录请求 0777,但这是创建程序的选择。掩码 027 清除所属组的写权限和其他人的全部权限,因此文件得到 0640,目录得到 0750。掩码 000 也不能给申请 0666 的文件凭空增加执行权限。

用五组掩码直接验证

将代码保存为 umask_demo.py 并用 python3 运行。程序在独立临时目录中显式申请文件和目录权限,再用 stat 读取实际结果。若该目录继承了默认 ACL,实验会主动停止;请选择没有默认 ACL 的实验位置,再比较普通权限公式。

import os
from pathlib import Path
import stat
import tempfile


def mode(path):
    return stat.S_IMODE(path.stat().st_mode) & 0o777


def main():
    with tempfile.TemporaryDirectory(prefix="umask-demo-") as work:
        root = Path(work)
        if "system.posix_acl_default" in os.listxattr(root):
            raise RuntimeError("Use a temporary directory without a default ACL")
        previous = os.umask(0o022)
        try:
            print("umask file directory")
            for mask in (0o000, 0o022, 0o027, 0o077, 0o111):
                os.umask(mask)
                file = root / f"file-{mask:03o}"
                folder = root / f"dir-{mask:03o}"
                fd = os.open(file, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o666)
                os.close(fd)
                folder.mkdir(mode=0o777)
                actual = (mode(file), mode(folder))
                assert actual == (0o666 & ~mask, 0o777 & ~mask)
                print(f"{mask:03o}   {actual[0]:03o}  {actual[1]:03o}")
                folder.chmod(0o700)
            os.umask(0o077)
            existing = root / "file-000"
            assert mode(existing) == 0o666
            existing.chmod(0o640)
            assert mode(existing) == 0o640
            os.umask(0)
            fd = os.open(root / "private", os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
            os.close(fd)
            assert mode(root / "private") == 0o600
            print("existing file unchanged; chmod applied; requested 600 stayed 600")
        finally:
            os.umask(previous)


if __name__ == "__main__":
    main()

本次运行得到:000 对应文件 666、目录 777;022 对应 644、755;027 对应 640、750;077 对应 600、700;111 对应 666、666。最后一组尤其适合检查理解:文件本来就没有执行位,屏蔽执行位以后仍然是 666,不能把掩码当作普通算术减数。

目录在 111 下会失去搜索位,因此代码读取模式后立即把实验子目录改为 700,便于后续清理。这个修正只作用于刚创建的实验对象。实验用 0777 只提取普通九位权限,继承的 setgid 等特殊位需要另查。测试没有用当前账户能否读取来代替权限位判断;特权账户可能绕过部分访问限制。

为什么修改掩码没有改变旧文件

umask 参与新对象的创建,不会追溯修改已存在文件。示例切换到 077 后,先前以 000 创建的文件仍是 666;随后显式调用 chmod,才变成 640。程序若只申请 0600,即使掩码是零,结果仍是 0600。排查时先确认对象是否新建,以及程序到底请求了什么模式。

umask 是进程属性,子进程会继承它。这个脚本只改变自己的进程状态,并在 finally 恢复原值,所以不会改变启动它的终端。服务进程的掩码也应检查它的启动上下文,不能只看你当前交互终端中的值。

排查默认 ACL 与并发影响

父目录存在默认 ACL 时,Linux 按继承的 ACL 和创建请求共同确定权限,普通的掩码计算不再直接适用。实际排查可先查看父目录的默认 ACL,再检查创建程序是否随后调用 chmod。不要只看到最终数字不同,就断定 umask 没有生效。

在多线程程序里,临时设置掩码再恢复会影响同一进程内其他线程的创建操作,因此本例应作为独立脚本运行。Linux 4.7 起可从 /proc/self/status 的 Umask 字段读取当前进程掩码,避免为了查询而先修改。需要严密控制文件权限时,优先在启动阶段确定策略,并显式指定创建模式。

参考资料

资料核验日期:2026年9月30日,UTC。

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。