Linux系统调用六、stat函数与 struct stat 文件信息结构体深度刨析


在这里插入图片描述


🎈1. inode节点与硬链接

在这里插入图片描述
通过上图可以看到,硬链接和源文件引用的是同一个inode节点,并且在inode节点中有一条硬链接计数信息,每当inode被引用一次,这个硬链接计数就会加1,我们可以通过ls命令来查看inode节点信息。我们先建立一个文件以及该文件的硬链接,通过ll命令可以查看文件信息(实际上这些信息就是存在inode节点中的信息)。
在这里插入图片描述
可以看到,建立一个硬链接之后,硬链接计数增加了1个。通过ls命令的-i选项可以查看文件的inode节点编号。
在这里插入图片描述
硬链接文件和源文件的inode节点编号一样,说明它们引用的是同一个inode节点。

在上图中的目录项中,有一条信息是类型,如果当前是目录的话,可以继续进入下一级目录。简单举个例子,比如说我们使用vi打开当前目录可以得到下面的内容。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AXC0o1lo-1653311503984)(./Typora_picture_reference/1652516397217.png)]
这里面的三个条目是当前文件夹下的文件,我们可以通过tree命令查看一下当前文件夹./的目录结构
在这里插入图片描述
当我们把光标停在某一文件所在行按回车键就可以查看该文件内容,如果这个文件是目录,就会进入该目录并显示目录下的条目。

比如,进入file.txt文件
在这里插入图片描述
进入目录111
在这里插入图片描述

🎈2. stat函数与 struct stat 结构体

  • 包含头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
  • 函数原型
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
  • 函数功能

    Get file status - These functions return information about a file.

  • 函数参数

    • path:指定文件

    • buf:buf是一个传出参数,也就是一级指针做输出,我们应该先定义一个结构体变量,并把该变量取地址&传给形参。

      struct stat 
      {
      	dev_t     st_dev;     /* ID of device containing file */
      	ino_t     st_ino;     /* inode number */
          mode_t    st_mode;    /* protection 实际上就是权限位 */
          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; /* blocksize for file system 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 最后状态改变时间 */
      };
      
      • 对于结构体struct stat中的 mode_t st_mode 进行简要介绍(下面并没有全部列出,只列出了部分),首先是判断文件类型的两种方法。第一种是掩码的方式。

        S_IFMT     0170000   bit mask for the file type bit fields
        S_IFSOCK   0140000   socket
        S_IFLNK    0120000   symbolic link 符号链接
        S_IFREG    0100000   regular file 普通文件
        S_IFBLK    0060000   block device 块设备
        S_IFDIR    0040000   directory 目录
        S_IFCHR    0020000   character device 字符设备
        S_IFIFO    0010000   FIFO 管道
        

        这个实际上就是我们ll命令查看文件时,第一列所显示的文件类型,在上面列出的内容中,中间一列数字用来标识文件类型,从S_IFSOCK到S_IFIFO就是我们所熟知的7种文件类型,S_IFMT是文件类型的掩码。通过和S_IFMT的与运算,就可以把标识文件类型的位保留下来(也就是前两个数017,第一个0表示8进制,后面17共四位 1 111 用来标识文件类型,那么S_IFMT用二进制表示就是1111后面加12个0,通过与与运算就把后面12位置为0而保留代表文件类型的四个bit,再把相与的结果与下面的S_IFSOCK到S_IFIFO进行对比来判断文件类型),进而判断文件类型。比如,现在有一个0041100,把它和S_IFMT做与运算结果为

        0041100 & 0170000 =  0040000		
        		  (S_IFMT)  (S_IFDIR)
        

        通过结果对比可以得出这是一个目录文件。这是通过掩码的方式来判断文件类型。

        另外一种判断文件类型的方法是使用它为我们提供的宏来判断,7种文件类型判断相关的宏如下所示,这里的m是指stat结构体中的st_mode。

        S_ISREG(m)  is it a regular file?
        S_ISDIR(m)  directory?
        S_ISCHR(m)  character device?
        S_ISBLK(m)  block device?
        S_ISFIFO(m) FIFO (named pipe)?
        S_ISLNK(m)  symbolic link? (Not in POSIX.1-1996.)
        S_ISSOCK(m) socket? (Not in POSIX.1-1996.)
        

        最后就是用户、组、其他用户的权限位(4位掩码,第一个0表示8进制)

        S_IRWXU    00700     mask for file owner permissions
        S_IRUSR    00400     owner has read permission
        S_IWUSR    00200     owner has write permission
        S_IXUSR    00100     owner has execute permission
        S_IRWXG    00070     mask for group permissions
        S_IRGRP    00040     group has read permission
        S_IWGRP    00020     group has write permission
        S_IXGRP    00010     group has execute permission
        S_IRWXO    00007     mask for permissions for others (not in group)
        S_IROTH    00004     others have read permission
        S_IWOTH    00002     others have write permission
        S_IXOTH    00001     others have execute permission
        
      • st_mode 各位含义示意图

      在这里插入图片描述

      • 结构体struct stat中的时间time_t也是一个结构体,它的原型如下

        struct timespec
        {
            _kernel_time_t 	tv_sec; /*seconds 当前时间到1970.1.1 0:0:0的秒数*/
            long 			tv_nsec; /*nanoseconds 纳秒*/
        }
        
  • 函数返回值

    成功返回0,失败返回-1并设置errno 。On success, zero is returned. On error, -1 is returned, and errno is set appropriately.

🎈3. stat函数实例分析及stat命令

下面通过一个实例来演示一下stat函数的使用方法。测试函数如下

/************************************************************
  >File Name  : getstat.c
  >Author     : QQ
  >Company    : QQ
  >Create Time: 2022年05月14日 星期六 18时37分17秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(int argc, char* argv[])
{
	if(argc < 2)
	{
		printf("not found filename\n");
		return -1;	
	}
	struct stat m_status;
	stat(argv[1], &m_status);
	return 0;
}

我们可以借助gdb调试器来查看结构体内容,并且借助gdb强大的功能可以很优美的打印出结构体内容。
在这里插入图片描述
我们使用set来设置一下,优美的打印结构体
在这里插入图片描述
该文件的硬链接数为st_nlink=2,我们确实对该文件创建了硬链接
在这里插入图片描述
我们可以看到这里面有一个st_mode=33188,这个就是我们在介绍stat结构体时重点介绍的权限参数,但是这里是十进制显示的,我们可以把它的八进制形式在gdb中打印出来
在这里插入图片描述
我们通过这个8进制数和文件类型掩码、权限掩码分别相与就可以得到这个文件的文件类型以及各用户的权限。

S_IFMT & 0100644 = 0100000 ===>  S_IFREG    0100000

可以看到相与之后的值对应于S_IFREG,也就是说这是个普通文件。

继续往下看 st_uid=0,st_gid=0,这个对应的是用户及组ID,这里要说明的是,我当前使用的是root用户,相应用户的ID可以在 /etc/passwd 中查看,我们可以使用vim编辑器打开文件查看

vim /etc/passwd

在这里插入图片描述
在第一行就可以看到root,后面两个0就对应结构体中的 st_uid=0和st_gid=0 。

继续往下看st_size = 11表示文件大小,可以通过ll命令来验证一下
在这里插入图片描述
再后面是块大小st_blksize = 4096和块的个数st_blocks = 8,那么每个块的大小是512,实际上这两个内容就是记录有多少个512大小的块。

再后面就是三个时间结构体的信息,最近访问时间、最近更改时间、最近状态改动时间。

  • Access 最近访问时间:是指最近的一次访问(读/写等),比如使用cat、touch等命令访问了该文件(访问但是没有修改),那么最近访问时间就会更新;
  • Modify 最近更改时间:是指最近一次文件内容的更改时间;
  • Change 最近状态改动时间:是指最近一次文件属性的更改时间,文件属性的更改包括文件大小、硬链接计数、文件权限等等的修改,并且一般Modify发生改变的时候,Change也会随之变化,因为文件内容的修改一般都会涉及到文件大小等的变化;

如果我们只是用cat查看一下文件,那么只有Access时间会更新;如果我们只是修改文件的权限,比如增加可执行权限,那么只有Change时间更新;如果我们使用重定向往文件中写入内容,那么Modify和Change时间会更新,而Access时间不会更新,因为在重定向的过程中,并没有访问文件。

实际上,上面介绍的这些内容,直接通过stat命令就可以查看
在这里插入图片描述

🎈4. 实现 ls -l filename命令

我们可以通过stat函数来实现 ls -l 命令的功能,下面我们实现查看指定文件的 ls -l 命令,即

ls -l filename

实现代码如下

/************************************************************
  >File Name  : mls.c
  >Author     : QQ
  >Company    : QQ
  >Create Time: 2022年05月15日 星期日 16时29分01秒
  >实现目标:-rw-r--r--. 2 root root 11 5月  14 15:02 file.txt
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>

int main(int argc, char* argv[])
{
	if(argc < 2)
	{
		printf("not found filename\n");
		return -1;
	}
	/*通过stat函数获取文件信息*/
	struct stat fstatus;
	stat(argv[1], &fstatus); /*穿透*/
    /*lstat(argv[1], &fstatus); 非穿透 */
	/*解析文件信息,st_mode st_uid st_gid time*/
	/*-rw-r--r--.*/
	char stmode[11];
	memset(stmode, '-', sizeof(stmode));
	/*使用宏来判断文件属性*/
	if(S_ISREG(fstatus.st_mode)) /* regular file */
	{
		stmode[0] = '-';
	}
	if(S_ISDIR(fstatus.st_mode))
    {
		stmode[0] = 'd';
	}
	if(S_ISCHR(fstatus.st_mode))
	{
		stmode[0] = 'c';
	}
	if(S_ISBLK(fstatus.st_mode))
	{
		stmode[0] = 'b';
	}
    if(S_ISFIFO(fstatus.st_mode))
	{
		stmode[0] = 'p';
	}
	if(S_ISLNK(fstatus.st_mode))
	{
		stmode[0] = 'l';
	}      
	if(S_ISSOCK(fstatus.st_mode))
	{
		stmode[0] = 's';
	}
	/*解析权限位*/
	if(fstatus.st_mode & S_IRUSR) 
	{/*为真表示拥有该权限,否则无权限,因为整块内存已初始化为 - 所以不需要else分支*/
		stmode[1] = 'r';
	}
	if(fstatus.st_mode & S_IWUSR) 
	{
		stmode[2] = 'w';
	}
	if(fstatus.st_mode & S_IXUSR) 
	{
		stmode[3] = 'x';
	}
	if(fstatus.st_mode & S_IRGRP) 
	{
		stmode[4] = 'r';
	}
	if(fstatus.st_mode & S_IWGRP) 
	{
		stmode[5] = 'w';
	}
	if(fstatus.st_mode & S_IXGRP) 
	{
		stmode[6] = 'x';
	}
	if(fstatus.st_mode & S_IROTH) 
	{
		stmode[7] = 'r';
	}
	if(fstatus.st_mode & S_IWOTH) 
	{
		stmode[8] = 'w';
	}
	if(fstatus.st_mode & S_IXOTH) 
	{
		stmode[9] = 'x';
	}
	stmode[10] = '\0';
	
	/*获取时间  localtime() 函数(非系统调用)
	*原型:struct tm *localtime(const time_t *timep);
	*参数:time_t类型,struct stat中time_t st_atime,这里应该是文件访问时间
	*返回:struct tm {
               int tm_sec;          seconds (0-60 )
               int tm_min;          minutes (0-59) 
               int tm_hour;         hours (0-23) 
               int tm_mday;         day of the month (1-31) 
               int tm_mon;          month (0-11) 
               int tm_year;         year (-1900)  如果要求实际年份,应加上1990
               int tm_wday;         day of the week sunday=0
               int tm_yday;         day in the year 
               int tm_isdst;        daylight saving time 
           };
	*/
	struct tm* filetime = localtime(&fstatus.st_atim.tv_sec);
	char timebuf[20] = {0};
	sprintf(timebuf, "%d月  %d %02d:%02d", \
					filetime->tm_mon + 1, \
					filetime->tm_mday, \
					filetime->tm_hour, \
					filetime->tm_min);
					
	/*打印格式 -rw-r--r--. 2 root root 11 5月  14 15:02 file.txt*/
	printf("%s %ld %s %s %ld %s %s\n", \
			stmode, \
			fstatus.st_nlink, \
			getpwuid(fstatus.st_uid)->pw_name, \
			getgrgid(fstatus.st_gid)->gr_name, \
			fstatus.st_size, \
			timebuf, \
			argv[1]);
			
	/* 两个函数(非系统调用)
	struct passwd *getpwuid(uid_t uid); 根据uid获取用户信息
	struct passwd {
               char   *pw_name;        username 
               char   *pw_passwd;      user password 
               uid_t   pw_uid;         user ID 
               gid_t   pw_gid;         group ID 
               char   *pw_gecos;       real name 
               char   *pw_dir;         home directory 
               char   *pw_shell;       shell program 
           };
   struct group *getgrgid(gid_t gid); 根据gid获取组信息
   struct group {
               char   *gr_name;        group name 
               char   *gr_passwd;      group password 
               gid_t   gr_gid;         group ID 
               char  **gr_mem;         group members 
           };
	*/
	
	return 0;
}

测试一下效果
在这里插入图片描述

🎈5. 穿透与非穿透

上面介绍了stat函数并通过stat函数实现了 ls -l 命令的功能。我们上面演示了使用自己实现的 ./mls 查看文件信息,假如说使用 ./mls 查看一个链接文件是什么效果呢,下面演示一下。
在这里插入图片描述
通过对比我们可以看到,符号链接(软链接)file.txt.soft的实际大小是8,但是我们自己实现的 ./mls 命令显示的大小是11。实际上,原因是这样的,我们在实现 ./mls 命令的时候是基于stat函数来获取文件信息的,stat函数有一个特性就是在获取链接文件信息的时候会进行穿透,去追溯符号链接的源文件,也就是说我们通过上面的命令 ./mls file.txt.soft 获取到的大小实际上是源文件file.txt的大小,我们可以验证一下。
在这里插入图片描述
那么我们自己如何实现获取符号链接的实际大小呢,这就用到了非穿透函数lstat,只要把上面代码实现中的函数调用stat替换为lstat就可以了,下面测试一下。

lstat(argv[1], &fstatus);

在这里插入图片描述


在这里插入图片描述

在这里插入图片描述


posted @ 2022-05-23 21:32  Mindtechnist  阅读(417)  评论(0编辑  收藏  举报  来源