Go中文件权限位直接映射Unix八进制语义,0644即-rw-r--r--;硬编码用0xxx最安全,字符串解析必须用strconv.ParseInt(s,8,32),因0644是八进制字面量而644是十进制,后者导致权限错乱。

Go 中的文件权限位不是“计算”出来的,而是直接映射 Unix 八进制权限语义——0644 就是 -rw-r--r--,没有中间换算步骤。硬编码用 0xxx 字面量最安全,字符串解析必须用 strconv.ParseInt(s, 8, 32),否则十进制解析会彻底错乱。
为什么 0644 不等于 644?
Go 编译器把以 0 开头的整数字面量(如 0644)自动识别为八进制,其二进制布局与 POSIX 权限位一一对应。而写成 644(无前导零)会被当十进制数处理,等价于八进制 1204,实际权限变成 ----w--r-T(含 setuid 位),完全不可控。
-
0644→ 八进制 → 二进制110 100 100→ 所有者 rw-、组 r--、其他 r-- -
644→ 十进制 → 二进制1010000100→ 低 9 位是00000100,高位触发非权限标志(如ModeSetuid) - 所有标准库函数(
os.WriteFile、os.Mkdir、os.OpenFile)都直接接收os.FileMode类型,底层就是uint32,不做隐式转换
os.FileMode 的底层结构怎么拆解?
os.FileMode 是 uint32 别名,低 9 位是传统 rwxrwxrwx,高 23 位是文件类型和扩展标志(如 ModeDir、ModeSymlink、ModeSticky)。权限部分必须用 .Perm() 提取,否则可能混入目录位导致 chmod 失败。
- 直接传
0755给os.Mkdir没问题,因为它是纯权限位 - 但
os.Stat().Mode()返回值包含类型位,比如目录会带ModeDir(值为0x4000),此时不能直接传给os.Chmod - 正确做法:
err := os.Chmod(path, info.Mode().Perm()),只取权限子集 - 检查是否可执行:
mode&0111 != 0(0111是八进制,匹配任一 x 位)
从字符串解析权限时常见崩溃点
配置文件或 CLI 参数传来的权限通常是字符串(如 "644" 或 "0644"),用 strconv.Atoi 解析会静默出错,且无法区分意图。
立即学习“go语言免费学习笔记(深入)”;
-
strconv.Atoi("644")→ 返回644(十进制)→os.FileMode(644)≠ 预期权限 -
strconv.ParseInt("644", 8, 32)→ 正确返回644(八进制值,即十进制 420) -
strconv.ParseInt("0644", 8, 32)和strconv.ParseInt("00644", 8, 32)都能正确处理前导零 - 务必做类型转换:
os.FileMode(uint32(parsed)),因为ParseInt返回int64
umask 怎么干扰你设的权限?
即使代码写了 0666,系统 umask(如 0022)也会按位清零:实际权限 = 0666 &^ 0022 = 0644。这不是 Go 的 bug,而是 Unix 机制。
-
os.OpenFile(name, os.O_CREATE, 0666)在 umask=0022 的机器上,结果永远是0644 - 生产环境别依赖默认行为,显式写
0600或0644,让意图清晰 - 想绕过 umask?做不到。唯一办法是创建后立刻
os.Chmod,但存在竞态窗口(敏感文件短暂暴露) - 密钥类文件必须一步到位:
os.WriteFile("key.pem", data, 0600),不要分WriteFile+Chmod
真正容易被忽略的是:权限位和文件类型位共存于同一个 uint32 值里。传错值不会编译报错,但 os.Chmod 可能静默失败,或 chmod 后发现文件变“不可遍历”——那大概率是你没调 .Perm(),把 ModeDir 一起喂给了系统。


















