嵌入式Linux系统编程 — 3.4 access、chmod和 umask函数修改文件访问权限

目录

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设置。程序运行结果如下:

相关推荐

  1. Linux普通权限、特殊权限、扩展权限Umask值介绍

    2024-06-10 14:42:03       23 阅读
  2. 嵌入Linux之Ubuntu学习笔记(文件系统结构)

    2024-06-10 14:42:03       29 阅读
  3. php 修改 文件权限 函数chmod()

    2024-06-10 14:42:03       9 阅读
  4. 嵌入Linux:空洞文件

    2024-06-10 14:42:03       13 阅读

最近更新

  1. 数据库修复实例2(副本出入口修复)

    2024-06-10 14:42:03       0 阅读
  2. Leetcode 415. 字符串相加-大数相加

    2024-06-10 14:42:03       0 阅读
  3. Docker使用心得

    2024-06-10 14:42:03       0 阅读
  4. 富格林:细心发现虚假确保安全

    2024-06-10 14:42:03       0 阅读
  5. 解析文字示例

    2024-06-10 14:42:03       0 阅读
  6. 计算机系统结构期末复习

    2024-06-10 14:42:03       0 阅读
  7. C#中[StructLayout(LayoutKind.Sequential, Pack = 1)]解释

    2024-06-10 14:42:03       0 阅读
  8. MySQL 保姆级教程(八):创建计算字段

    2024-06-10 14:42:03       0 阅读

热门阅读

  1. Linux常见命令

    2024-06-10 14:42:03       3 阅读
  2. C++——时间复杂度

    2024-06-10 14:42:03       5 阅读
  3. 为何数据仓库需要“分层次”?

    2024-06-10 14:42:03       7 阅读
  4. tensorRT 自定义算子plugin的实现

    2024-06-10 14:42:03       5 阅读
  5. 使用git stash暂存改动,并备注改动内容

    2024-06-10 14:42:03       7 阅读
  6. Vue3学习

    2024-06-10 14:42:03       3 阅读
  7. 使用c语言实字符串倒置及逆波兰数(栈)

    2024-06-10 14:42:03       3 阅读
  8. web前端报名点:深入探索与报名流程指南

    2024-06-10 14:42:03       5 阅读
  9. 深拷贝&浅拷贝解析,从原理理解深拷贝

    2024-06-10 14:42:03       5 阅读
  10. 不要使用业务键作为数据库主键

    2024-06-10 14:42:03       6 阅读
  11. 爬山算法的详细介绍

    2024-06-10 14:42:03       5 阅读