access 不能可靠判断文件只读,因其仅检查真实UID/GID的写权限,易受NFS、目录权限、Windows属性等干扰;应改用stat结合UID/GID比对或直接open/CreateFile验证。

用 access 检查文件是否只读,为什么常返回错误结果
access 本身不直接判断“只读”,它只能检查当前进程是否有某类权限(如写权限)。所谓“只读”,其实是通过 access(path, W_OK) 返回 -1 来**间接推断**:如果写权限被拒绝,且文件存在、可读,那它大概率是只读的。但要注意:access 检查的是**真实用户 ID 和组 ID 的权限**,不是有效 ID,所以在 setuid 程序中可能和实际 open 行为不一致。
常见误判场景包括:
- 文件在 NFS 或某些虚拟文件系统上,
access可能始终返回 -1(即使实际可写) - 目录有写权限,但文件本身被 chmod 444,
access("file", W_OK)仍可能返回 0(因为目录可写,允许 unlink)——这不代表你能修改文件内容 - Windows 下
access对只读属性的判断依赖文件系统属性位,而非 POSIX 权限,和 Linux 行为不一致
stat + st_mode 才能可靠判断只读属性(跨平台需注意)
真正判断一个普通文件是否“只读”,应结合 stat 获取 st_mode,再检查用户/组/其他三类权限位中是否有写位(S_IWUSR、S_IWGRP、S_IWOTH),并确认它不是目录(否则写权限意义不同)。
示例逻辑:
立即学习“C++免费学习笔记(深入)”;
#includebool is_file_readonly(const char* path) { struct stat sb; if (stat(path, &sb) != 0) return false; // 文件不存在或无访问权 if ((sb.st_mode & S_IFMT) != S_IFREG) return false; // 非普通文件 uid_t uid = getuid(); gid_t gid = getgid(); bool user_can_write = (sb.st_mode & S_IWUSR) && (sb.st_uid == uid); bool group_can_write = (sb.st_mode & S_IWGRP) && (sb.st_gid == gid); bool other_can_write = (sb.st_mode & S_IWOTH); return !(user_can_write || group_can_write || other_can_write); }
注意:getuid() 和 getgid() 是必须的,仅看 mode 位而不比对 UID/GID 会误判(比如你不是文件属主,但其他用户有写权,你依然不能写)。
Windows 下要用 GetFileAttributes 替代 access
Windows 没有 POSIX 权限模型,文件“只读”由单独的只读属性位控制(FILE_ATTRIBUTE_READONLY),和 ACL 是两套机制。此时 access(path, W_OK) 在 MinGW 或 MSVC 中可能模拟得不准确,尤其对 NTFS ACL 复杂的路径。
更稳妥的做法:
- 用
GetFileAttributesA或GetFileAttributesW - 检查返回值是否包含
FILE_ATTRIBUTE_DIRECTORY(跳过目录) - 再检查是否含
FILE_ATTRIBUTE_READONLY且不含FILE_ATTRIBUTE_HIDDEN或FILE_ATTRIBUTE_SYSTEM(这些属性不影响写,但常被误认为“只读”)
注意:该属性可被用户随时修改(右键 → 属性 → 勾选只读),但它不阻止管理员或拥有写 ACL 的用户修改文件 —— 所以它只是轻量提示,不是权限强制。
真正想“尝试写”,就别绕开 open 或 fopen
所有静态检查(access、stat、GetFileAttributes)都只是快照。文件权限可能在检查后瞬间被其他进程修改,或者受 mount 选项(如 ro)、SELinux/AppArmor 等策略拦截。
如果你的逻辑本质是“我要写这个文件,先确保能写”,最可靠的方式永远是直接尝试:
- Linux/macOS:
int fd = open(path, O_WRONLY | O_NONBLOCK);,检查fd == -1 && errno == EACCES - Windows:
HANDLE h = CreateFile(path, GENERIC_WRITE, ..., OPEN_EXISTING, ...);,检查h == INVALID_HANDLE_VALUE且GetLastError() == ERROR_ACCESS_DENIED
这种做法看似“暴力”,实则避免了竞态条件(TOCTOU),也是 glibc 和 CRT 内部的真实行为。很多开发者执着于提前判断,反而引入了本可避免的 bug。
权限判断的复杂点从来不在函数调用本身,而在于你究竟要解决什么问题:是给用户显示一个灰色按钮?还是防止程序崩溃?前者可以宽松估算,后者必须用实际打开来验证。










