LinuxUNIX系统编程手册——(十五)文件属性

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_IFREGS_ISREG()常规文件
S_IFDIRS_ISDIR()目录
S_IFCHRS_ISCHR()字符设备
S_IFBLKS_ISBLK()块设备
S_IFIFOS_ISFIFO()FIFO 或管道
S_IFSOCKS_ISSOCK()套接字
S_IFLNKS_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_ISUID04000Set-user-ID
S_ISGID02000Set-group-ID
S_ISVTX01000Sticky
S_IRUSR0400User-read
S_IWUSR0200User-write
S_IXUSR0100User-execute
S_IRGRP040Group-read
S_IWGRP020Group-write
S_IXGRP010Group-execute
S_IROTH04Other-read
S_IWOTH02Other-write
S_IXOTH01Other-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,来执行权限检查。

检查文件权限时,内核所遵循的规则如下。

  1. 对于特权级进程,授予其所有访问权限。
  2. 若进程的有效用户 ID 与文件的用户 ID(属主)相同,内核会根据文件的属主权限,授予进程相应的访问权限。比方说,若文件权限掩码中的属主读权限(owner-read permission)位被置位,则授予进程读权限。否则,则拒绝进程对文件的读取操作。
  3. 若进程的有效组 ID 或任一附属组 ID 与文件的组 ID(属组)相匹配,内核会根据文件的属组权限,授予进程对文件的相应访问权限。
  4. 若以上三点皆不满足,内核会根据文件的 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
  • 29
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值