文件权限与创建:umask()、chmod()、access() 函数的使用,以及 Linux 文件权限模型
聊到文件操作,有个话题绕不开——权限。你想想看,在 Linux 下跑程序,动不动就 "Permission denied",是不是很头疼?
我刚开始做嵌入式开发那会儿,就吃过这个亏。程序写得好好的,一部署到目标板上,读写文件直接崩了。查了半天,原来是文件权限没设对。从那以后,我对这套权限模型就格外上心。
今天咱们就把 umask()、chmod()、access() 这三个函数彻底讲明白。顺便把 Linux 文件权限模型也捋一遍。
Linux 文件权限模型:先搞懂底层逻辑
Linux 的权限模型,说白了就三组权限:所有者(User)、所属组(Group)、其他人(Others)。每组又分三种操作:读(r=4)、写(w=2)、执行(x=1)。
嗯,这里要注意:数字权限是用八进制表示的。比如 755,就是所有者有读写执行(7),组用户有读和执行(5),其他人也有读和执行(5)。
我习惯用一张图来理解这个模型,你看下面这个:
umask():创建文件时的默认权限遮罩
每次你用 open() 或 creat() 创建新文件时,系统都会拿你指定的权限减去 umask 值。说白了,umask 就是个「权限过滤器」。
举个例子:你调用 open("test.txt", O_CREAT, 0666),如果当前 umask 是 0022,那实际创建出来的文件权限是 0666 & ~0022 = 0644,也就是 rw-r--r--。
我个人习惯把 umask 设成 0027,这样组用户只有读权限,其他人啥权限都没有。安全嘛。
核心公式:实际权限 = 请求权限 & (~umask)
注意是按位取反后做与运算,不是简单的减法。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
// 设置新的 umask 值
mode_t old_mask = umask(0027);
printf("旧的 umask: %04o\n", old_mask);
// 创建文件,请求 0666 权限
int fd = open("test.txt", O_CREAT | O_WRONLY, 0666);
if (fd == -1) {
perror("open");
return 1;
}
close(fd);
// 实际权限会是 0640 (rw-r-----)
// 因为 0666 & ~0027 = 0666 & 0750 = 0640
// 恢复原来的 umask
umask(old_mask);
return 0;
}
注意:umask() 是进程级别的设置,会影响当前进程及其子进程创建的所有文件。多线程环境下要小心,因为 umask 是进程共享的。
chmod():修改已有文件的权限
文件创建完了,想改权限怎么办?用 chmod()。这个函数直接修改文件的权限位,不受 umask 影响。
我在项目中遇到过一个问题:程序需要写一个日志文件,但日志文件是之前用 root 权限创建的,普通用户写不了。后来我在程序初始化时调用了 chmod(),把权限改成 0666,问题就解决了。
#include <sys/stat.h>
#include <stdio.h>
int main() {
// 将文件权限改为 rwxr-xr-x (755)
if (chmod("test.txt", 0755) == -1) {
perror("chmod");
return 1;
}
// 也可以使用符号模式
// S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
// 等价于 0755
printf("权限修改成功\n");
return 0;
}
| 符号常量 | 含义 | 八进制值 |
|---|---|---|
| S_IRUSR | 所有者读 | 0400 |
| S_IWUSR | 所有者写 | 0200 |
| S_IXUSR | 所有者执行 | 0100 |
| S_IRGRP | 组用户读 | 0040 |
| S_IWGRP | 组用户写 | 0020 |
| S_IXGRP | 组用户执行 | 0010 |
| S_IROTH | 其他人读 | 0004 |
| S_IWOTH | 其他人写 | 0002 |
| S_IXOTH | 其他人执行 | 0001 |
小技巧:用 fchmod() 可以通过文件描述符修改权限,适合已经打开的文件。原型是 int fchmod(int fd, mode_t mode);
access():检查文件权限,别等出错了再后悔
有时候你需要在操作文件之前,先确认一下自己有没有权限。这就是 access() 的用武之地。
我曾经写过一个备份工具,每次备份前都要检查源文件是否可读、目标目录是否可写。用 access() 提前检查,比直接操作然后捕获错误要优雅得多。
#include <unistd.h>
#include <stdio.h>
int main() {
const char *filename = "test.txt";
// 检查文件是否存在
if (access(filename, F_OK) == -1) {
printf("文件不存在\n");
return 1;
}
// 检查是否可读
if (access(filename, R_OK) == -1) {
printf("没有读权限\n");
return 1;
}
// 检查是否可写
if (access(filename, W_OK) == -1) {
printf("没有写权限\n");
return 1;
}
// 检查是否可执行
if (access(filename, X_OK) == -1) {
printf("没有执行权限\n");
} else {
printf("文件可执行\n");
}
printf("一切正常,可以操作\n");
return 0;
}
| 模式 | 含义 |
|---|---|
| F_OK | 检查文件是否存在 |
| R_OK | 检查是否可读 |
| W_OK | 检查是否可写 |
| X_OK | 检查是否可执行 |
注意:access() 检查的是进程的实际用户ID和实际组ID,而不是有效用户ID。如果你的程序是 setuid 程序,access() 的结果可能和实际操作时的权限不一致。这种情况我建议用 euidaccess() 或自己手动检查。
避坑指南:我踩过的那些坑
- umask 的线程安全问题:我曾经在一个多线程程序里动态修改 umask,结果其他线程创建的文件权限全乱了。后来我改成在进程启动时统一设置,再也不动了。
- chmod 的符号链接问题:chmod() 会直接修改目标文件的权限,不会影响符号链接本身。如果你想修改符号链接的权限,得用
lchmod()(但很多系统不支持)。 - access() 的竞态条件:检查完权限和实际操作之间,权限可能被其他进程改了。所以 access() 只能作为辅助检查,不能完全依赖它来保证安全。
总结一下
这三个函数,说白了就是一套组合拳:
- umask():控制新文件的默认权限,适合在程序初始化时设置。
- chmod():修改已有文件的权限,适合在运行时动态调整。
- access():提前检查权限,避免操作失败,适合做前置校验。
嗯,掌握了这些,你在 Linux 下操作文件权限应该就游刃有余了。记住,权限问题往往是嵌入式开发中最容易被忽略的坑,提前处理好,能省不少调试时间。