获取文件信息:stat()

利用系统调用stat()lstat()以及fstat,可获取与文件有关的信息,其中大部分提取自文件i节点

#include <sys/stat.h>

int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
                                        All return 0 on success, or -1 on error

以上3个系统调用之间仅有的区别在于对文件的描述方式不同

  • stat()会返回所命名文件的相关信息
  • lstat()stat()类似,区别在于如果文件属于符号链接,那么所返回的信息针对的是符号链接自身
  • fstat()则会返回由某个打开文件描述符所指代文件的相关信息
  • 参数

    • pathname:指定文件路径
    • fd:指定文件描述符
    • statbuf:用来存储文件信息的缓冲区地址,为stat结构:

      struct stat {
         dev_t     st_dev;         /* ID of device containing file */
         ino_t     st_ino;         /* Inode number */
         mode_t    st_mode;        /* File type and mode */
         nlink_t   st_nlink;       /* Number of hard links */
         uid_t     st_uid;         /* User ID of owner */
         gid_t     st_gid;         /* Group ID of owner */
         dev_t     st_rdev;        /* Device ID (if special file) */
         off_t     st_size;        /* Total size, in bytes */
         blksize_t st_blksize;     /* Block size for filesystem I/O */
         blkcnt_t  st_blocks;      /* Number of 512B blocks allocated */
         time_t      st_atime;       /* Time of last access */
         time_t      st_mtime;       /* Time of last modification */
         time_t      st_ctime;       /* Time of last status change */
      };
  • 返回值

    • 成功:返回0
    • 失败:返回-1

下面对stat结构的一些重要字段加以说明:

  1. 设备ID和i节点号
    st_dev字段标识文件所驻留的设备。st_ino字段则包含了文件的i节点号。
  2. 文件所有权
    st_uidst_gid字段分别标识文件的属主和属组。
  3. 链接数
    st_nlink字段包含了指向文件的(硬)链接数。
  4. 文件类型及权限
    st_mode字段内含有位掩码,起标识文件类型和指定文件权限的双重作用。与常量S_IFMT相与(&),可从该字段中解析文件类型。
  5. 文件大小、已分配块以及最优I/O块大小

    • 对于常规文件,st_size字段表示文件的字节数。对于符号链接,则表示链接所指路径名的长度,以字节为单位。对于共享内存对象,该字段则表示对象大小。
    • st_blocks字段表示实际分配给文件的总块数,块大小为512字节,其中包括了为指针块所分配的空间。现代UNIX使用更大尺寸的逻辑块,st_blocks的取值总是2、4、8的倍数。
    • st_blksize字段所指并非底层文件系统的块大小,而是针对文件系统上文件进行I/O操作时的最优块大小(以字节为单位)。一般而言st_blksize的值为4096
  6. 文件时间戳
    st_atimest_mtimest_ctime字段分别记录了对文件的上次访问时间、上次修改时间、以及文件状态发生改变的上次时间。这3个字段的类型均属于time_t,是标准的UNIX时间格式。

文件时间戳

stat结构的st_atimest_mtimest_ctime字段所含为文件时间戳,分别记录了对文件的上次访问时间、上次修改时间以及文件状态上次发生变更的时间。

使用utime()utimes()来改变文件时间戳

使用utime()或与之相关的系统调用集之一,可显式改变存储于文件i节点中的文件上次访问时间戳和上次修改时间戳

#include <utime.h>

int utime(const char *pathname, struct utimbuf *buf);
                                        Return 0 on success, or -1 on error
  • 参数

    • pathname:标识欲修改时间的文件。若为符号链接会进一步解除引用
    • buf:指定需要修改数据的值。为utimbuf结构指针,可为NULL则置为当前时间
  • 返回值

    • 成功:返回0
    • 失败:返回-1

使用utimensat()futimens()改变文件时间戳

utimensat()系统调用和futimens()库函数为设置对文件的上次访问和修改时间戳提供了扩展功能。优点如下:

  • 可按纳秒级精度设置时间戳
  • 可独立设置某一时间戳
  • 可独立将任一时间戳置为当前时间

utimensat()系统调用会把由pathname指定文件的时间戳更新为由数组times指定的值。

#define _XOPEN_SOURCE 700
#include <sys/stat.h>

int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);
                                        Returns 0 on success, or -1 on error
  • 参数

    • dirfd:目录的文件描述符
    • pathname:标识欲修改时间的文件
    • times:指定需要修改数据的值。为timespec结构数组,可为NULL则将以上两文件都置为当前时间
    • flags:指定是否为符号链接解引用,为0或者AT_SYMLINK_NOFOLLOW则不会解引用
  • 返回值

    • 成功:返回0
    • 失败:返回-1

使用futimens()库函数可更新打开文件描述符fd所指代文件的各个文件时间戳

#define _GNU_SOURCE
#include <sys/stat.h>

int futimens(int fd, const struct timespec times[2]);
                                        Returns 0 on success, or -1 on error

参数与utimensat()所对应参数的用法相同

文件属主

每个文件都有一个与之关联的用户ID(UID)和组ID(GID),藉此可以判定文件的属主和属组

新建文件的属主

文件创建时,其用户ID“取自”进程的有效用户ID。而新建文件的组ID则“取自”进程的有效组ID(等同于System V系统默认行为)或父目录的组ID(BSD系统行为)。
此外,其行为还受文件系统装配选项影响。

改变文件属主:chown()fchown()lchown()

系统调用chown()fchown()lchown()可用来改变文件的属主和属组

#include <unistd.h>

int chown(const char *pathname, uid_t owner, gid_t group);
#=====================================================================
#define _XOPEN_SOURCE 500
#include <unistd.h>

int lchown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
                                        All returns 0 on success, or -1 on error

以上3个系统调用的区别类似stat()一族:

  • chown()改变指定的文件所有权
  • lchown()chown()相同,不同在于若参数指定为符号链接不会解引用而操作符号链接本身
  • fchown()同样改变文件所有权,只是指定文件的参数由文件描述符指定
  • 参数

    • pathname:指定文件路径
    • fd:指定文件描述符
    • owner:指定属主(UID)
    • group:指定属组(GID)
  • 返回值

    • 成功:返回0
    • 失败:返回-1

文件权限

普通文件的权限

stat结构中st_mod字段的低12位定义了文件权限。其中的前3位为专用位,分别是set-user-ID位、set-group-ID位和sticky位。其余9位则构成了定义权限的掩码,分别授予访问文件的各类用户。
文件权限掩码分为三类:

  • Owner:属主
  • Group:属组
  • Other:其他人

可为每一类用户授予的权限如下:

  • Read:可读取文件内容
  • Write:可更改文件内容
  • Execute:可执行文件

目录权限

目录与文件拥有相同的权限方案,只是对3种权限的含义另有所指。

  • Read:可列出目录之下的内容(仅仅是文件名列表)
  • Write:可在目录内创建、删除文件(需要同时拥有 Execute 权限,且操作的仅仅是文件,不涉及文件内容)
  • Execute:可访问目录中的文件(可查看文件内容,即使没有 Read 权限)

访问文件时,需要拥有对路径名所列所有目录的执行权限。

权限检查算法

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

  1. 对于特权级进程,授予其所有访问权限
  2. 若进程的有效用户ID与文件的用户ID(属主)相同,内核会根据文件的属主授权,授予进程相应的访问权限
  3. 若进程的有效组ID或任一附属组ID与文件的组ID(属组)相匹配,内核会根据文件的属组权限,授予进程对文件的相应访问权限
  4. 若以上三点皆不满足,内核会根据文件的其他人(other)权限,授予进程相应权限

内核会依次执行上述检查,只要匹配检查规则之一,便会停止检查。

检查对文件的访问权限:access()

系统调用access()就是根据进程的真实用户ID和组ID(以及附属组ID),去检查对pathname参数所指定文件的访问权限

#include <unistd.h>

int access(const char *pathname, int mode);
                        Returns 0 if all permissions are granted, otherwise -1
  • 参数

    • pathname:指定文件
    • mode:指定所要检查的权限位掩码,用指定常量相或(|)而成的值
  • 返回值

    • 成功:返回0(即满足权限)
    • 失败:返回-1(即不满足权限或者发生错误)

由于对某一文件调用access()与对同一文件的后续操作之间存在时间差,因此(不论间隔多么短暂)执行后续操作时,也无法保证在对文件对后续操作时由access()所返回的信息依然正确。所以建议杜绝使用access()

Set-User-ID、Set-Group-ID 和 Sticky 位

除了9位用来表明属主、属组和其他用户的权限之外,文件权限掩码还另设有3个附加位,分别为Set-user-ID(bit 0400)、Set-group-ID(bit 0200) 和Sticky(bit 0100)位。

  • Set-group-ID位还有其他两种用途:对于在nogrpid选项装配的目录下所新建的文件,控制其群组从属关系;还可用于强制锁定文件
  • Sticky位,在老的UNIX实现中,目的是为了常用程序的运行速度更快;但在现代UNIX实现中,作用于目录时,Sticky权限位起限制删除位的作用。

    • 为目录设置该位,则表明仅当非特权进程具有对目录的写权限,且为文件的属主时,才能对目录下的文件进行删除(unlink()、rmdir())和重命名(rename())操作。
    • 可藉此机制来创建为多个用户共享一个目录,各个用户可在其下创建或删除属于自己的文件,但不能删除隶属于其他用户的文件。
    • 可通过chmod命令(chmod +t file)或chmod()系统调用来设置文件的Sticky权限位。

进程的文件模式创建掩码:umask()

对于新建文件(open())或目录(mkdir()),都会有一个mode参数来指定文件的权限。然而文件模式创建掩码(简称umask)会对这个设置进行修改,umask是一种进程属性,当进程新建文件或目录时,该属性用于指明应屏蔽哪些权限位。
系统调用umask()将进程的umask改变为mask参数所指定的值(原值是继承自其父 shell)

#include <sys/stat.h>

mode_t umask(mode_t mask);
                    Always successfullly returns the previous process umask

参数mask可以以八进制或指定常量相或(|)来指定;该系统调用总会成功,并返回进程的前一 umask。

更改文件权限:chmod()fchmod()

可利用系统调用chmod()fchmod()去修改文件权限

#include <sys/stat.h>

int chmod(const char *pathname, mode_t mode);

#define _OPEN_SOURCE 500
#include <sys/stat.h>
int fchmod(int fd, mode_t mode);
                            Both return 0 on success, or -1 on error

两系统调用的区别仅仅是指定文件的方式不同。
上述系统调用修改权限默认是针对整个所有权限位,如果要修改特定权限位,需先调用stat()来获取文件的现有权限,调整想要修改的权限位,然后使用该系统调用去更新权限。

i 节点标志

某些Linux文件系统允许为文件和目录设置各种各样的i-node flags(I 节点标志)。该特性是一种非标准的Linux扩展功能。

  • 在 shell 中,可通过执行chattrlsattr命令来设置和查看 i 节点标志
  • 在程序中,可利用ioctl()系统调用来获取并修改 i 节点标志

总结

本文主要介绍的内容是文件属性,然后由此展开文件权限。

标签: Linux, C/C++, TLPI

添加新评论