目录
1 文件访问权限
1.1 文件权限基本概念
1.2 普通权限
1.3 特殊权限
2 目录权限
3 access函数检查文件权限
3.1 access函数简介
3.2 示例程序
3.3 chmod修改文件权限
3.4 fchmod函数
4 umask 函数
4.1 umask简介
4.2 示例程序
1 文件访问权限
1.1 文件权限基本概念
struct stat 结构体中的 st_mode 字段记录了文件的访问权限位。此时的文件并不仅仅指的是普通文件;所有文件类型文件(目录、设备文件)都有访问权限。
文件的权限可以分为两个大类,分别是普通权限和特殊权限(也可称为附加权限)。普通权限包括对文件的读、写以及执行,而特殊权限则包括一些对文件的附加权限,譬如Set-User-ID、Set-Group-ID以及Sticky。
1.2 普通权限
每个文件都有 9 个普通的访问权限位,可将它们分为 3 类,如下表:
st_mode 权限表示宏 | 含义 |
S_IRUSR S_IWUSR S_IXUSR | 文件所有者读权限 文件所有者写权限 文件所有者执行权限 |
S_IRGRP S_IWGRP S_IXGRP | 同组用户读权限 同组用户写权限 同组用户执行权限 |
S_IROTH S_IWOTH S_IXOTH | 其它用户读权限 其它用户写权限 其它用户执行权限 |
可以 ls 命令或 stat 命令可以查看到文件的这 9 个访问权限,如下所示:
上面的红线框中, 描述了该文件的 9 个访问权限以及文件类型,例如"-rwxrwxrx":
最前面的一个字符表示该文件的类型,这个前面给大家介绍过, " - "表示该文件是一个普通文件。
- r 表示具有读权限;
- w 表示具有写权限;
- x 表示具有执行权限;
- -表示无此权限。
当进程每次对文件进行读、写、执行等操作时,内核就会对文件进行访问权限检查,以确定该进程对文件是否拥有相应的权限。而文件的权限检查就涉及到了文件的所有者(st_uid)、文件所属组(st_gid)以及其它用户,当然这里指的是从文件的角度来看;而对于进程来说,参与文件权限检查的是进程的有效用户、有效用户组以及进程的附属组用户。
如何判断权限,首先判断需要进行操作的文件的所有者:
- 如果进程的有效用户 ID 等于文件所有者 ID(st_uid),意味着该进程以文件所有者的角色存在;
- 如果进程的有效用户 ID 并不等于文件所有者 ID,意味着该进程并不是文件所有者身份;但是进程的有效用户组 ID 或进程的附属组 ID 之一等于文件的组 ID(st_gid),那么意味着该进程以文件所属组成员的角色存在,也就是文件所属组的同组用户成员。
- 如果进程的有效用户 ID 不等于文件所有者 ID、并且进程的有效用户组 ID 或进程的所有附属组 ID均不等于文件的组 ID(st_gid) ,那么意味着该进程以其它用户的角色存在。
- 如果进程的有效用户 ID 等于 0(root 用户),则无需进行权限检查,直接对该文件拥有最高权限。
1.3 特殊权限
st_mode 字段中除了记录文件的 9 个普通权限之外,还记录了文件的 3 个特殊权限,也就是图中所表示的 S 字段权限位。
S 字段三个 bit 位中,从高位到低位依次表示文件的 set-user-ID 位权限、 set-groupID 位权限以及 sticky 位权限,如下所示:
特殊权限 | 含义 |
S_ISUID | set-user-ID 位权限 |
S_ISGID | set-group-ID 位权限 |
S_ISVTX | Sticky 位权限 |
这三种权限分别使用 S_ISUID、 S_ISGID 和 S_ISVTX 三个宏来表示:
S_ISUID 04000 set-user-ID bit
S_ISGID 02000 set-group-ID bit (see below)
S_ISVTX 01000 sticky bit (see below)
同样,以上数字使用的是八进制方式表示。 对应的 bit 位数字为 1,则表示设置了该权限、为 0 则表示并未设置该权限。三个权限位的作用如下:
- 当进程对文件进行操作的时候、将进行权限检查,如果文件的 set-user-ID 位权限被设置,内核会将进程的有效 ID 设置为该文件的用户 ID(文件所有者 ID) ,意味着该进程直接获取了文件所有者的权限、以文件所有者的身份操作该文件。
- 当进程对文件进行操作的时候、将进行权限检查,如果文件的 set-group-ID 位权限被设置,内核会将进程的有效用户组 ID 设置为该文件的用户组 ID(文件所属组 ID) ,意味着该进程直接获取了文件所属组成员的权限、以文件所属组成员的身份操作该文件。
Linux 系统下绝大部分的文件都没有设置 set-user-ID 位权限和 set-group-ID 位权限,所以通常情况下,进程的有效用户等于实际用户(有效用户 ID 等于实际用户 ID),有效组等于实际组(有效组 ID 等于实际组 ID)。
可以通过 st_mode 变量判断文件是否设置了 set-user-ID 位权限,代码如下:
if (st.st_mode & S_ISUID) {
//设置了 set-user-ID 位权限
} else {
//没有设置 set-user-ID 位权限
}
2 目录权限
删除文件、创建文件这些操作也是需要相应权限的,这些权限通过目录获取。目录(文件夹)在 Linux 系统下也是一种文件,拥有与普通文件相同的权限方案(S/U/G/O) ,只是这些权限的含义与文件权限不同。
- 目录的读权限: 可列出(譬如:通过 ls 命令) 目录之下的内容(即目录下有哪些文件)。
- 目录的写权限: 可以在目录下创建文件、删除文件。
- 目录的执行权限: 可访问目录下的文件,譬如对目录下的文件进行读、写、执行等操作。
可以使用 ls 命令查看目录中的文件权限:
要想访问目录下的文件,譬如查看文件的 inode 节点、大小、权限等信息,还需要对目录拥有执行权限。反之,若拥有对目录的执行权限、而无读权限,只要知道目录内文件的名称,仍可对其进行访问,但不能列出目录下的内容(即目录下包含的其它文件的名称)。
要想在目录下创建文件或删除原有文件,需要同时拥有对该目录的执行和写权限。
由此可知,如果需要对文件进行读、写或执行等操作,不光是需要拥有该文件本身的读、写或执行权限,还需要拥有文件所在目录的执行权限。
3 access函数检查文件权限
3.1 access函数简介
文件的权限检查不仅讨论文件本身的权限,还需要涉及到文件所在目录的权限, 只有同时都满足了,才能通过操作系统的权限检查,进而才可以对文件进行相关操作;所以,程序当中对文件进行相关操作之前,需要先检查执行进程的用户是否对该文件拥有相应的权限。可以使用 access 系统调用检查文件权限,函数原型如下:
#include <unistd.h>
int access(const char *pathname, int mode);
pathname: 需要进行权限检查的文件路径。
mode: 该参数除了可以单独使用之外,还可以通过按位或运算符" | "组合在一起,可以取以下值:
- F_OK:检查文件是否存在
- R_OK:检查是否拥有读权限
- W_OK:检查是否拥有写权限
- X_OK:检查是否拥有执行权限
返回值: 检查项通过则返回 0,表示拥有相应的权限并且文件存在;否则返回-1,如果多个检查项组合在一起,只要其中任何一项不通过都会返回-1。
3.2 示例程序
下面的程序是一个简单的命令行工具,其功能是检查用户指定的文件是否存在以及是否具备读、写和执行的权限。
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return 1;
}
const char *filename = argv[1];
// 检查文件是否存在
if (access(filename, F_OK) == -1) {
perror("File does not exist");
return 1;
}
// 检查文件的读权限
if (access(filename, R_OK) == -1) {
fprintf(stderr, "File is not readable.\n");
}
// 检查文件的写权限
if (access(filename, W_OK) == -1) {
fprintf(stderr, "File is not writable.\n");
}
// 检查文件的执行权限
if (access(filename, X_OK) == -1) {
fprintf(stderr, "File is not executable.\n");
}
// 如果没有错误发生,说明文件具有所有检查的权限
if (errno == 0) {
printf("File has all the required permissions.\n");
}
return 0;
}
程序首先检查命令行参数的数量是否正确,即程序名称后是否跟有一个文件名。如果参数数量不正确,程序将打印使用说明并退出。如果参数数量正确,程序将使用 access
函数来检查文件的存在性(F_OK
)、读权限(R_OK
)、写权限(W_OK
)和执行权限(X_OK
)。如果文件不存在或缺少相应的权限,程序将打印相应的错误信息。如果所有检查都通过,即没有触发任何错误,程序将打印一条消息表明文件具有所有检查的权限,然后正常退出。程序运行结果如下:
3.3 chmod修改文件权限
在 Linux 系统下,可以使用 chmod
命令修改文件权限,该命令内部实现方法其实是调用了 chmod
函数。chmod
函数是一个用于改变文件或目录权限的系统调用。它允许用户设置文件的读(r)、写(w)和执行(x)权限,以及特殊权限,如设置用户ID(setuid,简称suid)和粘滞位(sticky bit)。函数原型如下:
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
path
:指定要改变权限的文件或目录的路径。
mode
:指定新的权限模式,与 open 函数的第三个参数一样。权限模式可以通过运算符" | "组合:
S_IRWXU
:为文件所有者设置读、写和执行权限。S_IRUSR
:为文件所有者设置读权限。S_IWUSR
:为文件所有者设置写权限。S_IXUSR
:为文件所有者设置执行权限。S_IRWXG
:为文件所属组设置读、写和执行权限。S_IRGRP
:为文件所属组设置读权限。S_IWGRP
:为文件所属组设置写权限。S_IXGRP
:为文件所属组设置执行权限。S_IRWXO
:为其他用户设置读、写和执行权限。S_IROTH
:为其他用户设置读权限。S_IWOTH
:为其他用户设置写权限。S_IXOTH
:为其他用户设置执行权限。S_ISUID
:设置文件的setuid位。S_ISGID
:设置文件的setgid位。S_ISVTX
:设置文件的粘滞位。
下面是使用 chmod
函数的一个示例,其中 chmod
函数传入一个文件名参数,并尝试为该文件设置特定的权限模式。在这个例子中,我们将为文件设置文件所有者的读、写和执行权限。
#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>
int main(int argc, char *argv[]) {
// 检查是否传入了文件名作为参数
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return 1;
}
const char *filename = argv[1]; // 从参数中获取文件名
mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR; // 设置文件所有者的读、写和执行权限
// 使用chmod函数改变文件权限
if (chmod(filename, mode) == -1) {
// 如果chmod函数调用失败,打印错误消息
perror("Failed to change file mode");
return 1;
}
// 如果权限改变成功,打印成功消息
printf("File mode for '%s' changed successfully.\n", filename);
return 0;
}
程序同样先检查是否传入了正确的参数数量,即程序名称后跟一个文件名。如果参数数量正确,程序将尝试使用 chmod
函数改变 argv[1]
指定的文件的权限。在这个例子中,设置的权限模式是 S_IRUSR | S_IWUSR | S_IXUSR
,这意味着我们将为文件所有者设置读、写和执行权限。如果 chmod
调用失败,perror
将打印错误消息,程序返回状态码1。如果权限改变成功,程序将打印一条成功消息。程序运行结果如下:
3.4 fchmod函数
该函数功能与 chmod 一样,参数略有不同。 fchmod()与 chmod()的区别在于使用了文件描述符来代替文件路径,就像是 fstat 与 stat 的区别。函数原型如下所示:
#include <sys/stat.h>
int fchmod(int fd, mode_t mode);
使用了文件描述符 fd 代替了文件路径 pathname,其它功能都是一样的。
4 umask 函数
4.1 umask简介
umask
(用户文件创建掩码,User File Creation Mask)用于决定新创建的文件和目录的默认权限,umask
定义了文件系统创建文件或目录时默认应该屏蔽掉的权限位。
前面提到,文件和目录的权限可以用三位八进制数来表示,分别对应所有者(user)、所属组(group)和其他用户(others)的读(r)、写(w)和执行(x)权限。例如,一个文件的权限 755
表示:
- 所有者有读、写和执行权限(7)
- 所属组有读和执行权限(5)
- 其他用户有读和执行权限(5)
umask
值定义了这些权限位中哪些应该被设置为“关闭”(即不允许)。umask
的值也是用八进制数表示的,但它的含义是相反的:umask
中的每个位表示应该从默认权限中减去相应的权限。例如,如果 umask
设置为 022
:
- 对于文件,这将从默认权限
666
(可读可写)中减去022
,结果为644
(可读可写,但不可执行)。 - 对于目录,这将从默认权限
777
(可读可写可执行)中减去022
,结果为755
(可读可写可执行,但组和其他用户不可写入)。
umask
的默认值通常是 022
,这意味着新创建的文件默认没有执行权限,新创建的目录默认不允许组和其他用户写入。
在Linux系统中,umask
(用户文件创建掩码)函数用于设置或获取当前进程的文件模式创建掩码。这个掩码决定了新创建的文件和目录的默认权限。函数原型:
#include <sys/stat.h>
mode_t umask(mode_t mask);
mask
:一个 mode_t
类型的值,用来设置新的umask值。这个值通常由 S_IRWXU
(用户读、写、执行)、S_IRWXG
(组读、写、执行)和 S_IRWXO
(其他用户读、写、执行)的组合来表示,但是用它们的补码来表示。例如,如果你想设置文件默认没有写权限,目录默认没有写和执行权限,你可以使用 022
作为掩码值。
4.2 示例程序
代码演示了如何使用 umask
函数来获取和修改进程的文件创建掩码。
#include <stdio.h>
#include <sys/stat.h>
int main() {
// 获取当前umask值
mode_t current_umask = umask(0); // 设置umask为0,获取当前值
// 打印当前umask值
printf("Current umask: %04o\n", current_umask);
// 设置新的umask值,这里设置为022,即组和其他用户默认没有写权限
mode_t new_umask = umask(S_IRWXG | S_IRWXO);
// 创建一个文件,将应用新的umask值
FILE *file = fopen("testfile.txt", "w");
if (file == NULL) {
perror("Failed to open file");
return 1;
}
fputs("Hello, World!\n", file);
fclose(file);
// 打印新的umask值
printf("New umask: %04o\n", new_umask);
// 可以恢复原来的umask值
umask(current_umask);
return 0;
}
程序首先通过调用 umask(0)
获取当前的umask值,并打印出来。然后,它将umask设置为 022
,这意味着新创建的文件将默认不授予组和其他用户写权限。在这个umask值下,程序创建并写入一个名为 testfile.txt
的文件。之后,程序打印出新的umask值,并最终通过再次调用 umask
用之前的值恢复原始的umask设置。程序运行结果如下: