跳到主要内容

文件属性

1. 基本概念

Linux 文件属性指的是文件的一些特征,如文件的类型、权限、大小、时间戳、所有者等等。这些信息对于文件的传输、管理等是必不可少的。而这些信息,可以使用 stat()\fstat()\lstat() 函数来获取。

2. 获取文件属性

2.1 API

  1. stat() 函数用于获取文件的属性信息。函数原型为:
int stat(const char *path, struct stat *buf);

该函数接受两个参数:

  • path:指定要获取属性的文件的路径。
  • buf:指向存储文件属性信息的结构体的指针。

该函数返回 0 表示成功,返回非 0 值表示失败。

  1. fstat() 函数与 stat() 类似,都用于获取文件的属性信息,但 fstat() 函数接受一个文件描述符而不是文件路径作为参数。函数原型为:
int fstat(int fd, struct stat *buf);

该函数接受两个参数:

  • fd:指定要获取属性的文件的文件描述符。
  • buf:指向存储文件属性信息的结构体的指针。

该函数返回 0 表示成功,返回非 0 值表示失败。

使用 fstat() 函数时,需要注意文件描述符必须已经打开,否则会出现错误。

  1. lstat() 函数与 stat() 类似,都用于获取文件的属性信息,但 lstat() 函数能够获取符号链接的属性信息,而 stat() 函数会将符号链接解析为实际的文件。函数原型为:
int lstat(const char *path, struct stat *buf);

该函数接受两个参数:

  • path:指定要获取属性的文件的路径。
  • buf:指向存储文件属性信息的结构体的指针。

该函数返回 0 表示成功,返回非 0 值表示失败。

使用 lstat() 函数时,需要注意符号链接可能指向一个不存在的文件,因此可能会导致函数调用失败。

这三个函数的功能完全一样,区别是: stat()参数是一个文件的名字,而fstat()的参数是一个已经被打开了的文件的描述符fd,而Istat( )则可以获取链接文件本身的属性。

2.2 属性结构体

struct stat 是一个系统定义的数据结构,用于存储与文件相关的信息,通常包含文件的大小、权限、所有者、时间戳等信息。

它的完整定义可能会根据系统平台不同而有所差异。例如,在 Linux 系统中,struct stat 的定义如下:

struct stat {
dev_t st_dev; /* ID of device containing file */ /* 文件所在设备的编号 */
ino_t st_ino; /* inode number */ /* inode 编号 */
mode_t st_mode; /* protection */ /* 文件的权限信息 */
nlink_t st_nlink; /* number of hard links */ /* 硬链接数 */
uid_t st_uid; /* user ID of owner */ /* 文件的所有者的用户 ID */
gid_t st_gid; /* group ID of owner */ /* 文件的所有者的组 ID */
dev_t st_rdev; /* device ID (if special file) */ /* 设备 ID(如果是特殊文件) */
off_t st_size; /* total size, in bytes */ /* 文件大小(以字节为单位) */
blksize_t st_blksize; /* blocksize for file system I/O */ /* 文件系统 I/O 的块大小 */
blkcnt_t st_blocks; /* number of 512B blocks allocated */ /* 分配的 512B 块的数量 */
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 */ /* 上一次状态改变的时间 */
};

2.3 示例代码

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
// 需要传入文件名作为参数
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return 1;
}

// 获取文件信息
struct stat file_stat;
if (stat(argv[1], &file_stat) == -1) {
perror("stat");
return 1;
}

// 打印文件信息
printf("File size: %lld bytes\n", (long long)file_stat.st_size);
printf("Permissions: %o\n", file_stat.st_mode & 0777);
printf("Last accessed: %ld\n", file_stat.st_atime);
printf("Last modified: %ld\n", file_stat.st_mtime);

return 0;
}

3. 文件设备号

属性结构体 stat 中有两个成员涉及文件的设备号,他们分别是 st_devst_rdev,前者只对普通文件有效,它包含了普通文件所在的设备的设备号,因此这个成员对于特殊文件而言是无意义的。而 st_rdev 恰好相反,他储存的是特殊设备文件本身的设备号,因此 st_rdev 对于普通文件而言是无效的。

3.1 获取主设备号

major() 函数用于从设备编号中提取主设备号。函数原型为:

int major(dev_t dev);

该函数接受一个参数:

  • dev:要提取主设备号的设备编号。

该函数返回提取到的主设备号。

使用 major() 函数时,需要注意传入的设备编号必须有效。

3.2 获取次设备号

minor() 函数用于从设备编号中提取次设备号。函数原型为:

int minor(dev_t dev);

该函数接受一个参数:

  • dev:要提取次设备号的设备编号。

该函数返回提取到的次设备号。

使用 minor() 函数时,需要注意传入的设备编号必须有效。

3.3 示例代码

下面是使用 major()minor() 获取主次设备号的示例代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/sysmacros.h>

int main(int argv, char *argc[])
{
struct stat st;
if (stat("/dev/sda", &st) == 0) {
// 获取 /dev/sda 的设备编号
dev_t dev = st.st_dev;

// 提取主设备号
printf("/dev/sda 的主设备号为 %d\n", major(dev));

// 提取次设备号
printf("/dev/sda 的次设备号为 %d\n", minor(dev));
} else {
printf("获取文件属性失败\n");
}
return 0;
}

4. 文件类型和权限

属性成员中的 st_mode 里面包含了文件类型和权限,内部结构如图所示:

o5ejI9.png

4.1 结构图包含含义

st_mode实质上是一个无符号16位短整型数:

  • st_mode[0:8]一一对应地代表了文件的各个用户的权限。
  • st_mode[9]存储了所谓的黏住位(只对目录有效),在拥有该目录的写权限的情况下,如果这一位被设置为1,那么某一用户也只能删除在本目录下属于自己的文件,否则可以删除任意文件。
  • st_mode[10]和 st_mode[11]分别用来设置文件的suid(只对普通文件有效)和 sgid(只对目录有效)。如果suid被设置为1,则任何用户在执行该文件的时候均会获得该文件所有者的临时授权,即其有效UID将等于文件所有者的UID。如果sgid被设置为1,则任何在该目录下执行的程序均会获得该目录所属组成员的临时授权,即其有效GID将等于该目录的所属组成员的GID。
  • st_mode[12:15]用以标识Linux下不同的文件类型,由于Linux总共只有7种文件类型,因此4位足以表达。

4.2 st_mode 详细信息

宏定义值(八进制)值(二进制)含义
S_IFMT01700001 111 000 000 000 000文件类型掩码
S_IFSOCK01400001 100 000 000 000 000文件类型为:套接字
S_IFLNK01200001 010 000 000 000 000文件类型为:链接
S_IFREG01000001 000 000 000 000 000文件类型为:普通文件
S_IFBLK00600000 110 000 000 000 000文件类型为:块设备
S_IFDIR00400000 100 000 000 000 000文件类型为:目录
S_IFCHR00200000 010 000 000 000 000文件类型为:字符设备
S_IFIFO00100000 001 000 000 000 000文件类型为:管道
SIDS_ISUID00040000 000 100 000 000 000文件的suid位
S_ISGID00020000 000 010 000 000 000文件的sgid位
黏住位S_ISVTX00010000 000 001 000 000 000文件的黏住位
S_IRWXU00007000 000 000 111 000 000所有者权限掩码
S_IRUSR00004000 000 000 100 000 000所有者读权限
S_IWUSR00002000 000 000 010 000 000所有者写权限
S_IXUSR00001000 000 000 001 000 000所有者执行权限
S_IRWXG00000700 000 000 000 111 000所属组成员权限掩码
S_IRGRP00000400 000 000 000 100 000所属组成员读权限
s_IWGRP00000200 000 000 000 010 000所属组成员写权限
S_IXGRP00000100 000 000 000 010 000所属组成员执行权限
S_IRWXO00000070 000 0o0 000 000 111其他人权限掩码
S_IROTH00000040 000 000 000 000 100其他人读权限
S_IWOTH00000020 000 000 000 000 010其他人写权限
S_IXOTH00000010 000 000 000 000 001其他人执行权限

4.3 判断文件类型宏

判断文件的类型不需要直接读取 st_mode 的高4位,而是使用以下这些宏定义即可:

宏定义
S_ISREG判断文件是否为:普通文件
S_ISDIR判断文件是否为:目录
S_ISCHR判断文件是否为:字符设备文件
S_ISCHR判断文件是否为:块设备文件
S_ISFIFO判断文件是否为:管道文件
S_ISLNK判断文件是否为:链接文件
S_ISSOcK判断文件是否为:套接字文件

4.4 示例代码

结合文件的设备号,下面的示例代码实现一个功能:判断一个文件是否是特殊设备文件(即字符设备文件或者块设备文件),如果是则打印出其主次设备号,否则打印出其所在设备的主次设备号,代码如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/sysmacros.h>

int main(int argc, char *argv[])
{
// 需要传入文件名作为参数
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
exit(EXIT_FAILURE);
}

struct stat st;
if (stat("/dev/sda", &st) == 0) {
// 获取文件类型
mode_t mode = st.st_mode;
if (S_ISCHR(mode) || S_ISBLK(mode)) {
// 文件是特殊设备文件,提取主次设备号
dev_t dev = st.st_rdev;
printf("/dev/sda 是特殊设备文件,主设备号为 %d,次设备号为 %d\n", major(dev), minor(dev));
} else {
// 文件不是特殊设备文件,提取所在设备的主次设备号
dev_t dev = st.st_dev;
printf("/dev/sda 所在设备的主设备号为 %d,次设备号为 %d\n", major(dev), minor(dev));
}
} else {
printf("获取文件属性失败\n");
}

return 0;
}

// 运行结果:
disnox@MSI:/mnt/e/Code/code$ ./a.out a.out
a.out 所在设备的主设备号为 0,次设备号为 47
disnox@MSI:/mnt/e/Code/code$ ./a.out .
. 所在设备的主设备号为 0,次设备号为 47
disnox@MSI:/mnt/e/Code/code$ ./a.out /dev/sda
/dev/sda 是特殊设备文件,主设备号为 8,次设备号为 0