15.1 获取文件信息:stat()
利用系统调用 stat()、lstat()以及 fstat(),可获取与文件有关的信息,其中大部分提取自文件 i 节点。
#include <sys/stat.h> int stat(const char *pathname, struct stat *statbuf); /* 成功返回0,失败返回-1 */ int lstat(const char *pathname, struct stat *statbuf); /* 成功返回0,失败返回-1 */ int fstat(int fd, struct stat *statbuf); /* 成功返回0,失败返回-1 */
以上 3 个系统调用之间仅有的区别在于对文件的描述方式不同。
- stat()会返回所命名文件的相关信息。
- lstat()与 stat()类似,区别在于如果文件属于符号链接,那么所返回的信息针对的是符号链接自身(而非符号链接所指向的文件)。
- fstat()则会返回由某个打开文件描述符所指代文件的相关信息。
系统调用 stat()和 lstat()无需对其所操作的文件本身拥有任何权限,但针对指定pathname 的父目录要有执行(搜索)权限。而只要供之以有效的文件描述符,fstat()系统调用总是成功。
stat类型结构体如下:
struct stat { dev_t st_dev; /* 文件驻留的设备的ID,包括设备的主、辅ID */ ino_t st_ino; /* 文件的i节点号 */ mode_t st_mode; /* 文件类型和文件权限,内含位掩码,低12位定义了文件权限, 最低9位分别用来表示文件属主属组以及其他用户的读、写、执行权限 */ nlink_t st_nlink; /* 指向文件的(硬)链接数 */ uid_t st_uid; /* 文件的属主 */ gid_t st_gid; /* 文件的属组 */ dev_t st_rdev; /* 设备专用文件的ID,包括设备的主、辅ID */ off_t st_size; /* 文件从大小(字节数) */ blksize_t st_blksize; /* 针对文件系统上文件继续I/O操作时的最优块大小 */ blkcnt_t st_blocks; /* 分配给文件的总块数,块大小为512 字节 */ time_t st_atime; /* 上次访问时间 */ time_t st_mtime; /* 上次修改时间 */ time_t st_ctime; /* 上次文件状态发生改变的时间 */ }
下表为st_mode检查文件类型的宏:
常量 | 测试宏 | 文件类型 |
---|---|---|
S_IFREG | S_ISREG() | 常规文件 |
S_IFDIR | S_ISDIR() | 目录 |
S_IFCHR | S_ISCHR() | 字符设备 |
S_IFBLK | S_ISBLK() | 块设备 |
S_IFIFO | S_ISFIFO() | FIFO 或管道 |
S_IFSOCK | S_ISSOCK() | 套接字 |
S_IFLNK | S_ISLNK() | 符号链接 |
程序示例:获取并解释文件的stat信息
#include <sys/sysmacros.h> #include <sys/stat.h> #include <time.h> #include "file_perms.h" #include "tlpi_hdr.h" static void displayStatInfo(const struct stat *sb) { printf("File type: "); /* 判断文件类型 */ switch (sb->st_mode & S_IFMT) { case S_IFREG: printf("regular file\n"); break; case S_IFDIR: printf("directory\n"); break; case S_IFCHR: printf("character device\n"); break; case S_IFBLK: printf("block device\n"); break; case S_IFLNK: printf("symbolic (soft) link\n"); break; case S_IFIFO: printf("FIFO or pipe\n"); break; case S_IFSOCK: printf("socket\n"); break; default: printf("unknown file type?\n"); break; } printf("Device containing i-node: major=%ld minor=%ld\n", (long) major(sb->st_dev), (long) minor(sb->st_dev)); printf("I-node number: %ld\n", (long) sb->st_ino); printf("Mode: %lo (%s)\n", (unsigned long) sb->st_mode, filePermStr(sb->st_mode, 0)); if (sb->st_mode & (S_ISUID | S_ISGID | S_ISVTX)) printf(" special bits set: %s%s%s\n", (sb->st_mode & S_ISUID) ? "set-UID " : "", (sb->st_mode & S_ISGID) ? "set-GID " : "", (sb->st_mode & S_ISVTX) ? "sticky " : ""); printf("Number of (hard) links: %ld\n", (long) sb->st_nlink); printf("Ownership: UID=%ld GID=%ld\n", (long) sb->st_uid, (long) sb->st_gid); if (S_ISCHR(sb->st_mode) || S_ISBLK(sb->st_mode)) printf("Device number (st_rdev): major=%ld; minor=%ld\n", (long) major(sb->st_rdev), (long) minor(sb->st_rdev)); printf("File size: %lld bytes\n", (long long) sb->st_size); printf("Optimal I/O block size: %ld bytes\n", (long) sb->st_blksize); printf("512B blocks allocated: %lld\n", (long long) sb->st_blocks); printf("Last file access: %s", ctime(&sb->st_atime)); printf("Last file modification: %s", ctime(&sb->st_mtime)); printf("Last status change: %s", ctime(&sb->st_ctime)); } int main(int argc, char *argv[]) { struct stat sb; Boolean statLink; /* True if "-l" specified (i.e., use lstat) */ int fname; /* Location of filename argument in argv[] */ statLink = (argc > 1) && strcmp(argv[1], "-l") == 0; /* Simple parsing for "-l" */ fname = statLink ? 2 : 1; if (fname >= argc || (argc > 1 && strcmp(argv[1], "--help") == 0)) usageErr("%s [-l] file\n" " -l = use lstat() instead of stat()\n", argv[0]); if (statLink) { if (lstat(argv[fname], &sb) == -1) errExit("lstat"); } else { if (stat(argv[fname], &sb) == -1) errExit("stat"); } displayStatInfo(&sb); exit(EXIT_SUCCESS); }
echo 'All operating systems provide services for programs they run'> apue chmod g+s apueTurn on set-group-ID bit; afects last status change time cat apue Afects last file access timeAll operating systems provide services for programs they run ./t_stat apue File type: regular file Device containing i-node: major=3 minor=11 I-node number: 234363 Mode: 102644(rw-r--r--) special bits set: set-GID Number of (hard) links: 7 Ownership: UID=1000GID=100 File size: 61 bytes Optimal I/0 block size: 4096 bytes 512B blocks allocated: 8 Last file access: Mon Jun 809:40:07 2011 Last file modification: Mon Jun 809:39:25 2011 Last status change: Mon Jun 8 09:39:51 2011
15.2 文件时间戳
15.2.1 使用 utime()和 utimes()来改变文件时间戳
使用 utime()或与之相关的系统调用集之一,可显式改变存储于文件 i 节点中的文件上次访问时间戳和上次修改时间戳。utime()与 utimes()之间最显著的差别在于后者可以以微秒级精度来指定时间值。解压文件时,tar(1)和 unzip(1)之类的程序会使用这些系统调用去重置文件的时间戳。
#include <utime.h> int utime(const char *pathname, const struct utimbuf *buf); /* 成功返回0,失败返回-1 */ int utimes(const char *pathname, const struct timeval tv[2]); /* 成功返回0,失败返回-1 */
参数 pathname 用来标识欲修改时间的文件。若该参数为符号链接,则会进一步解除引用。参数 buf 既可为 NULL,也可为指向 utimbuf 结构的指针。
struct utimbuf { time_t actime; /* 访问时间 */ time_t modtime; /* 修改时间 */ }
utime()的运作方式则视以下两种不同情况而定。
- 如果 buf 为 NULL,那么会将文件的上次访问和修改时间同时置为当前时间。这时,进程要么具有特权级别(CAP_FOWNER 或 CAP_DAC_OVERRIDE),要么其有效用户 ID 与该文件的用户 ID(属主)相匹配,且对文件有写权限(逻辑上,对文件拥有写权限的进程在调用其他系统调用时,可能会于无意间改变这些时间戳)。(准确地说,在 Linux 系统中,用来与文件用户 ID 做比对的是进程的文件系统用户ID,而非其有效用户 ID。)
- 若将 buf 指定为指向 utimbuf 结构的指针,则会使用该结构的相应字段去更新文件的上次访问和修改时间。此时,要么调用程序具有特权级别(CAP_FOWNER),要么进程的有效用户 ID 必需匹配文件的用户 ID(仅对文件拥有写权限是不够的)。
为更改文件时间戳中的一项,可以先利用 stat()来获取两个时间,并使用其中之一来初始化 utimbuf 结构,然后再将另一时间置为期望值。下列代码演示了这一操作,将文件的上次修改时间改为与上次访问时间相同。
struct stat sb; struct utimbuf utb; if(stat(pathname, &sb) == -1) errExit("stat"); utb.actime = sb.st_atime; utb.modtime = sb.st_atime; if(utime(pathname, &utb) == -1) errExit("utime");
只要调用 utime()成功,总会将文件的上次状态更改时间置为当前时间。
futimes()和 lutimes()库函数的功能与 utimes()大同小异。前两者与后者之间的差异在于,用来指定要更改时间戳文件的参数不同。调用 lutimes()时,使用路径名来指定文件,有别于调用 utimes()的是:对于 lutimes(),若路径名指向一符号链接,则调用不会对该链接进行解引用,而是更改链接自身的时间戳。
#include <sys/time.h> int futimes(int fd, const struct timeval tv[2]); /* 成功返回0,失败返回-1 */ int lutimes(const char *pathname, const struct timeval tv[2]); /* 成功返回0,失败返回-1 */
15.2.2 使用 utimensat()和 futimens()改变文件时间戳
utimensat()系统调用会把由 pathname 指定文件的时间戳更新为由数组 times 指定的值。使用 futimens()库函数可更新打开文件描述符 fd 所指代文件的各个文件时间戳。
#include <sys/stat.h> /* 成功返回0,失败返回-1 */ int utimensat(int dirfd, const char *pathname, const struct timespec time[2], int flags); int futimes(int fd, const struct timespec time[2]);
若将 times 指定为 NULL,则会将以上两个文件时间戳都更新为当前时间。若 times 值为非 NULL,则会针对指定文件在 times[0]中放置新的上次访问时间,在 times[1]中放置新的上次修改时间。数组 times 所含的每一元素都是如下格式的一个结构:
struct timespec { time_t tv_sec; long tv_nsec; }
若有意将时间戳之一置为当前时间,则可将相应的 tv_nsec 字段指定为特殊值UTIME_NOW。若希望某一时间戳保持不变,则需把相应的 tv_nsec 字段指定为特殊值UTIME_OMIT。无论是上述哪一种情况,都将忽略相应 tv_sec 字段中的值。
flags 参数可以为 0,或者 AT_SYMLINK_NOFOLLOW,意即当 pathname 为符号链接时,不会对其解引用(也就是说,改变的是符号链接自身的时间戳)。相形之下,utimes()总是对符号链接进行解引用。
以下代码片段在将对文件的上次访问时间置为当前时间的同时,上次修改时间则保持不变。
struct timespec times[2]; time[0].tv_sec = 0; time[0].tv_nsec = UTIME_NOW; time[1].tv_sec = 0; time[1].tv_nsec = UTIME_OMIT; if(utimesat(AT_FDCWD, "myfile", times, 0) == -1) errExit("utimesat");
15.3 文件属主
15.3.1 新建文件的属主
文件创建时,其用户 ID“取自”进程的有效用户 ID。而新建文件的组 ID 则“取自”进程的有效组 ID(等同于 System V 系统的默认行为),或父目录的组 ID(BSD 系统的行为)。当为项目创建目录时,需要该目录下的所有文件隶属于某一特定组,并且可为该组所有成员所访问。这时,采用后一种行为就非常实用。新建文件的组 ID 在这两者间如何取舍是由多种因素决定的,新文件所在文件系统的类型就是其中之一
15.3.2 改变文件属主:chown()、fchown()和 lchown()
系统调用 chown()、lchown()和 fchown()可用来改变文件的属主(用户 ID)和属组(组ID)。
#include <unsistd.h> int chown(const char *pathname, uid_t owner, gid_t group); int lchown(const char *pathname, uid_t owner, gid_t group); int fchown(int fd, uid_t owner, gid_t group);
以上 3 个系统调用之间的区别类似于 stat()系统调用一族。
- chown()改变由 pathname 参数命名文件的所有权。
- lchown()用途与 chown()相同,不同之处在于若参数 pathname 为一符号链接,则将会改变链接文件本身的所有权,而与该链接所指代的文件无干。
- fchown()也会改变文件的所有权,只是文件由打开文件描述符 fd 所引用。参数 owner 和 group 分别为文件指定新的用户 ID 和组 ID。若只打算改变其中之一,只需将另一参数置为−1,即可令与之相关的 ID 保持不变。
只有特权级进程(CAP_CHOWN)才能使用 chown()改变文件的用户 ID。非特权级进程可使用 chown()将自己所拥有文件的组 ID 改为其所从属的任一属组的 ID,前提是进程的有效用户ID 与文件的用户 ID 相匹配。特权级进程则可将文件的组 ID 修改为任意值。
【注】如果文件组的属主或属组发生了改变,那么 set-user-ID 和 set-group-ID 权限位也会随之关闭。这一安全举措是为了防止如下行为:普通用户若能打开某一可执行文件的 set-user-ID(或 set-group-ID)位,然后再设法令其为某些特权级用户(或组)所拥有,就能在执行该文件时获得特权用户身份。
改变文件的属主和属组时,如果已然屏蔽了属组的可执行权限位,或者要改变的是目录的所有权时,那么将不会屏蔽 set-group-ID 权限位。在上述两种情况下,set-group-ID 位的用途并非是去创建一个启用了 set-group-ID 的程序,因此将该位屏蔽并不可取。set-group-ID 的其他用途如下所示。
- 若屏蔽了属组的可执行权限位,则可利用 set-group-ID 权限位来启用强制文件锁定
- 当作用于目录时,可利用 set-group-ID 位来控制在该目录下创建文件的所有权
程序示例:改变文件的属主和属组
#include <pwd.h> #include <grp.h> #include "ugid_functions.h" /* Declarations of userIdFromName() and groupIdFromName() */ #include "tlpi_hdr.h" int main(int argc, char *argv[]) { uid_t uid; gid_t gid; int j; Boolean errFnd; if (argc < 3 || strcmp(argv[1], "--help") == 0) usageErr("%s owner group [file...]\n" " owner or group can be '-', " "meaning leave unchanged\n", argv[0]); if (strcmp(argv[1], "-") == 0) { /* "-" ==> don't change owner */ uid = -1; } else { /* Turn user name into UID */ uid = userIdFromName(argv[1]); if (uid == -1) fatal("No such user (%s)", argv[1]); } if (strcmp(argv[2], "-") == 0) { /* "-" ==> don't change group */ gid = -1; } else { /* Turn group name into GID */ gid = groupIdFromName(argv[2]); if (gid == -1) fatal("No such group (%s)", argv[2]); } /* Change ownership of all files named in remaining arguments */ errFnd = FALSE; for (j = 3; j < argc; j++) { if (chown(argv[j], uid, gid) == -1) { errMsg("chown: %s", argv[j]); errFnd = TRUE; } } exit(errFnd ? EXIT_FAILURE : EXIT_SUCCESS); }
15.4 文件权限
15.4.1 普通文件的权限
stat 结构中 st_mod 字段的低 12 位定义了文件权限。其中的前 3 位为专用位,分别是 set-user-ID 位、set-group-ID 位和 sticky 位(在图 15-1 中分别被标注为 U、G、T位),将在 15.4.5 节中详细介绍。其余 9 位则构成了定义权限的掩码,分别授予访问文件的各类用户。文件权限掩码分为 3 类。
- Owner(亦称为 user):授予文件属主的权限。
- Group:授予文件属组成员用户的权限。
- Other:授予其他用户的权限。
可为每一类用户授予的权限如下所示。
- Read:可阅读文件的内容。
- Write:可更改文件的内容。
- Execute:可以执行文件(亦即,文件是程序或脚本)。要执行脚本文件(比如,一个bash 脚本),需同时具备读权限和执行权限。
执行 ls–l 命令,可查看文件的权限和所有权,如下所示:
vainx@DESKTOP-0DN0PNJ:~/wsl-code/tlpi-book/fileio$ ls -l seek_io -rwxr-xr-x 1 vainx vainx 21872 Jul 19 19:35 seek_io
在以上输出中,将文件权限显示为“rwxr-xr-x”(该字符串起始处的连接号“-”表明该文件属于普通文件)。在解释该字符串时,需将其一剖为三,以 3 个字符为一组,分别表示读、写、可执行权限具备与否。第一组字符用来表示文件属主的权限,在本例中,则是读、写、执行权限俱全。第二组字符用来表示属组权限,对于本例,组内用户具有读和可执行权限,但不具有写权限。最后一组字符用来表示其他用户的权限,本例中的其他用户具有读和可执行权限,但不具有写权限。
头文件<sys/stat.h>定义了可与 stat 结构中 st_mode 相与(&)的常量,用于检查特定权限位置位与否。(<fcntl.h>为 open()系统调用提供了原型,在程序中包含该头文件也可定义这些常量。)下列出了这些常量。
常量 | 其他值 | 权限位 |
---|---|---|
S_ISUID | 04000 | Set-user-ID |
S_ISGID | 02000 | Set-group-ID |
S_ISVTX | 01000 | Sticky |
S_IRUSR | 0400 | User-read |
S_IWUSR | 0200 | User-write |
S_IXUSR | 0100 | User-execute |
S_IRGRP | 040 | Group-read |
S_IWGRP | 020 | Group-write |
S_IXGRP | 010 | Group-execute |
S_IROTH | 04 | Other-read |
S_IWOTH | 02 | Other-write |
S_IXOTH | 01 | Other-execute |
函数 char *filePermStr(mode_t perm, int flags)
,会针对给定的文件权限掩码返回一个静态分配的字符串,以 ls(1)所采用的风格来表示该掩码。如果在 filePermStr()的 flag 参数中设置了 FP_SPECIAL 标志,那么返回的字符串将包括set-user-ID、set-group-ID,以及 sticky 位的设置信息,其表现形式同样会沿袭 ls(1)的风格。
15.4.2 目录权限
目录与文件拥有相同的权限方案,只是对 3 种权限的含义另有所指。
- 读权限:可列出(比如,通过 ls 命令)目录之下的内容(即目录下的文件名)。
- 写权限:可在目录内创建、删除文件。注意,要删除文件,对文件本身无需有任何权限。
- 可执行权限:可访问目录中的文件。因此,有时也将对目录的执行权限称为 search(搜索)权限。
访问文件时,需要拥有对路径名所列所有目录的执行权限。例如,想读取文件/home/mtk/x,则需拥有对目录/、/home 以及/home/mtk 的执行权限(还要有对文件 x 自身的读权限)。若当前的工作目录为/home/mtk/sub1,访问相对路径名…/sub2/x 时,需握有/home/mtk 和/home/mtk/sub2 这两个目录的可执行权限(不必有对/或/home 的执行权限)。
拥有对目录的读权限,用户只是能查看目录中的文件列表。要想访问目录内文件的内容或是这些文件的 i 节点信息,还需握有对目录的执行权限。反之,若拥有对目录的可执行权限,而无读权限,只要知道目录内文件的名称,仍可对
其进行访问,但不能列出目录下的内容(即目录所含的其他文件名)。在控制对公共目录内容的访问时,这是一种常用技术,简单而且实用。要想在目录中添加或删除文件,需要同时拥有对该目录的执行和写权限。
15.4.3 权限检查算法
只要在访问文件或目录的系统调用中指定了路径名称,内核就会检查相应文件的权限。如果赋予系统调用的路径名还包含目录前缀时,那么内核除去会检查对文件本身所需的权限以外,还会检查前缀所含每个目录的可执行权限。内核会使用进程的有效用户 ID、有效组 ID以及辅助组 ID,来执行权限检查。
检查文件权限时,内核所遵循的规则如下。
- 对于特权级进程,授予其所有访问权限。
- 若进程的有效用户 ID 与文件的用户 ID(属主)相同,内核会根据文件的属主权限,授予进程相应的访问权限。比方说,若文件权限掩码中的属主读权限(owner-read permission)位被置位,则授予进程读权限。否则,则拒绝进程对文件的读取操作。
- 若进程的有效组 ID 或任一附属组 ID 与文件的组 ID(属组)相匹配,内核会根据文件的属组权限,授予进程对文件的相应访问权限。
- 若以上三点皆不满足,内核会根据文件的 other(其他)权限,授予进程相应权限。
内核会依次执行针对属主、属组以及其他用户的权限检查,只要匹配上述检查规则之一,便会停止检查。这样得出的结果可能会在意料之外,比方说,若组权限超过了属主权限,那么文件属主所拥有的权限要低于组成员的权限;若为文件的其他用户分配的权限大于文件属主或属组,上述论述也同样适用。
由于文件的权限及所有权信息都维护于文件的 i 节点之内,故而也为指向同一 i 节点的所有文件名(链接)所共享。
若进程为特权级进程,则内核在检查权限时将授予进程所有的访问权限。这一论述成立,其实还要加个限制条件。对于非目录文件,仅当该文件的 3 种权限类型(至少)之一具有可执行权限时,Linux 才会将该权限赋予一特权级进程。
15.4.4 检查对文件的访问权限:access()
如上节所述,当进程访问文件时,系统会以其 effective(有效)用户 ID、effective(有效)组ID 以及附属组 ID 来确定权限。当然,对于程序(比如,set-user-ID 或 set-group-ID 程序)来说,根据进程的 real(真实)用户 ID 和组 ID 来检查对文件的访问权限,也并非没有可能。
系统调用 access()就是根据进程的真实用户 ID 和组 ID(以及附属组 ID),去检查对pathname 参数所指定文件的访问权限。
#incldue <unistd.h> /* 由 pathname 所指定的文件具备mode 参数包含的所有权限,access()将返回 0; 只要有一项权限未得到满足(或者有错误发生),access()则返回−1。 */ int access(const char *pathname, int mode);
若 pathname 为符号链接,access()将对其解引用。参数 mode 是下表 中常量相或(|)而成的位掩码。
常量 | 描述 |
---|---|
F_OK | 有这个文件吗 |
R_OK | 对该文件有读权限吗 |
W_OK | 对该文件有写权限吗 |
X_OK | 对该文件有执行权限吗 |
由于对某一文件调用 access()与对同一文件的后续操作之间存在时间差,因此(不论间隔多么短暂)执行后续操作时,也无法保证在对文件的后续操作时由 access()所返回的信息依然正确。在某些应用程序设计中,上述情形可能会导致安全漏洞。比方说,假设有一 set-user-ID-root 程序,使用 access()来检查程序的真实用户 id 是否可以访问某文件,如果可以访问,就对其执行(open()或 exec()之类的)操作。问题是,若输入 access()的路径名为符号链接,而恶意用户可抢在第二步检查之前设法更改该链接,使其指向另一文件,则最终会导致 set-user-ID-root 去操作真实用户 ID 并无权限的文件。因如此,建议杜绝使用 access()
15.4.5 Set-User-ID、Set-Group-ID 和 Sticky 位
除了 9 位用来表明属主、属组和其他用户的权限之外,文件权限掩码还另设有 3 个附加位,分别为 set-user-ID (bit 04000)、set-group-ID (bit 02000)和 sticky (bit 01000)位。et-group-ID 位还有两种其他用途:对于在以 nogrpid 选项装配的目录下所新建的文件,控制其群组从属关系;可用于强制锁定文件。
作用于目录时,sticky 权限位起限制删除位的作用。为目录设置该位,则表明仅当非特权进程具有对目录的写权限,且为文件或目录的属主时,才能对目录下的文件进行删除(unlink()、rmdir())和重命名(rename())操作。(具有 CAP_FOWNER 能力的进程可省去对属主的检查。)可藉此机制来创建为多个用户共享的一个目录,各个用户可在其下创建或删除属于自己的文件,但不能删除隶属于其他用户的文件。为/tmp 目录设置 sticky 权限位,原因正在于此。
和set-user-ID位类似,可通过 chmod 命令(chmod +t file)或 chmod()系统调用来设置文件的 sticky 权限位。若对
某文件设置了 sticky 权限位,则当执行 ls–l 命令显示该文件时,会在其他用户执行权限字段上看到字母 T,其大小写则要取决于是否对文件开启了其他用户执行权限位,如下所示:
vainx@DESKTOP-0DN0PNJ:~/wsl-code$ ls -l tfile -rw-r--r-- 1 vainx vainx 100003 Jul 20 07:08 tfile vainx@DESKTOP-0DN0PNJ:~/wsl-code$ chmod +t tfile vainx@DESKTOP-0DN0PNJ:~/wsl-code$ ls -l tfile -rw-r--r-T 1 vainx vainx 100003 Jul 20 07:08 tfile vainx@DESKTOP-0DN0PNJ:~/wsl-code$ chmod o+x tfile vainx@DESKTOP-0DN0PNJ:~/wsl-code$ ls -l tfile -rw-r--r-t 1 vainx vainx 100003 Jul 20 07:08 tfile
15.4.6 进程的文件模式创建掩码:umask()
对于新建文件,内核会使用 open()或 creat()中 mode 参数所指定的权限。对于新建目录,则会根据 mkdir()的 mode 参数来设置权限。然而,文件模式创建掩码(简称为 umask)会对这些设置进行修改。umask 是一种进程属性,当进程新建文件或目录时,该属性用于指明应屏蔽哪些权限位。
进程的 umask 通常继承自其父 shell,其结果往往正如人们所期望的那样:用户可以使用shell 的内置命令 umask 来改变 shell 进程的 umask,从而控制在 shell 下运行程序的 umask。
大多数 shell 的初始化文件会将 umask 默认置为八进制值 022 (----w–w-)。其含义为对于同组或其他用户,应总是屏蔽写权限。因此,假定 open()调用中的 mode 参数为 0666(即令所有用户享有读、写权限,通常如此),那么对新建文件来说,其属主拥有读、写权限,所有其他用户只具有读权限(针对文件执行 ls–l 命令,会显示“rw-r–r—”)。同理,假定将 mkdir()的 mode 参数指定为 0777(即所有用户享有所有权限),那么对于新建目录来说,其属主享有所有权限,同组和其他用户则只拥有读取和执行权限(即 rwxr-xr-x)。
系统调用 umask()将进程的 umask 改变为 mask 参数所指定的值。可以以八进制数或是用来表示文件权限位的常量相或(|)来指定 mask 参数。
#inlcude <sys/stat.h> mode_t umask(mode_t mask); /* 调用总会成功,并返回进程的前一umask */
程序示例:使用 umask()
#include <sys/stat.h> #include <fcntl.h> #include "file_perms.h" #include "tlpi_hdr.h" #define MYFILE "myfile" #define MYDIR "mydir" #define FILE_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) #define DIR_PERMS (S_IRWXU | S_IRWXG | S_IRWXO) #define UMASK_SETTING (S_IWGRP | S_IXGRP | S_IWOTH | S_IXOTH) int main(int argc, char *argv[]) { int fd; struct stat sb; mode_t u; umask(UMASK_SETTING); fd = open(MYFILE, O_RDWR | O_CREAT | O_EXCL, FILE_PERMS); if (fd == -1) errExit("open-%s", MYFILE); if (mkdir(MYDIR, DIR_PERMS) == -1) errExit("mkdir-%s", MYDIR); u = umask(0); /* Retrieves (and clears) umask value */ if (stat(MYFILE, &sb) == -1) errExit("stat-%s", MYFILE); printf("Requested file perms: %s\n", filePermStr(FILE_PERMS, 0)); printf("Process umask: %s\n", filePermStr(u, 0)); printf("Actual file perms: %s\n\n", filePermStr(sb.st_mode, 0)); if (stat(MYDIR, &sb) == -1) errExit("stat-%s", MYDIR); printf("Requested dir. perms: %s\n", filePermStr(DIR_PERMS, 0)); printf("Process umask: %s\n", filePermStr(u, 0)); printf("Actual dir. perms: %s\n", filePermStr(sb.st_mode, 0)); if (unlink(MYFILE) == -1) errMsg("unlink-%s", MYFILE); if (rmdir(MYDIR) == -1) errMsg("rmdir-%s", MYDIR); exit(EXIT_SUCCESS); }
vainx@DESKTOP-0DN0PNJ:~/wsl-code/tlpi-book/files$ ./t_umask Requested file perms: rw-rw---- Process umask: ----wx-wx Actual file perms: rw-r----- Requested dir. perms: rwxrwxrwx Process umask: ----wx-wx Actual dir. perms: rwxr--r--
15.4.7 更改文件权限:chmod()和 fchmod()
可利用系统调用 chmod()和 fchmod()去修改文件权限。
#include <sys/stat.h> int chmod(const char *pathname, mode_t mode); /* 成功返回0,失败返回-1 */ int fchmod(int fd, mode_t mode); /* 成功返回,失败返回- */
系统调用 chmod()更改由 pathname 参数所指定文件的权限。若该参数所指为符号链接,调用 chmod()会改变符号链接所指代文件的访问权限,而非对符号链接自身的访问权限。(符号链接自创建起,其所有权限便为所有用户共享,且这些权限也不得更改。对符号链接解引用时,将忽略所有这些权限。)
系统调用 fchmod()更改由打开文件描述符 fd 所指代文件的权限。
参数 mode 用于描述文件的新权限,可以采用八进制数字形式,亦或是用来表示文件权限位的常量相或(|)而成的掩码。要想更改文件权限,进程要么具有特权级别(CAP_FOWNER),要么其有效用户 ID 于文件的用户 ID(属主)相匹配(准确说来,对于 Linux 系统上的非特权级进程,需与文件用户 ID 相匹配的是进程的文件系统用户 ID,而非其有效用户 ID)
要修改文件的特定权限位,需先调用 stat()来获取文件的现有权限,调整想修改的权限位,然后使用 chmod()去更新权限。
struct stat sb; mode_t mode; if(stat("myfile", &sb) == -) errExit("stat"); mode = (sb.st_mode | S_IWUSR) & ~S_IROTH; if(chmod("myfile", mode) == -1) errExit("chmod");
执行以上代码,等价于执行如下 shell 命令:
chmod u+w,o-r myfile