stat 是 Unix/Linux 系统中用于获取文件状态信息的核心系统调用。它能够提供关于文件的元数据,而无需打开文件本身。

1. 函数的概念与用途

stat 函数用于获取指定路径名对应文件的信息,包括文件大小、权限、所有者、时间戳等元数据。这些信息存储在 struct stat 结构中。

主要用途:

  • 检查文件是否存在及其类型(普通文件、目录、符号链接等)
  • 获取文件大小、权限和所有权信息
  • 查看文件的访问、修改和状态变更时间
  • 实现类似 ls -l 命令的功能
  • 在文件操作前验证文件属性

2. 函数的声明与出处

stat 函数及其相关变体定义在 <sys/stat.h> 头文件中,是 POSIX 标准的一部分。

#include <sys/stat.h>

int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);

三种变体的区别:

  • stat(): 获取路径名指向的文件信息(会跟随符号链接)
  • lstat(): 类似 stat(),但不跟随符号链接(获取链接本身的信息)
  • fstat(): 通过文件描述符获取已打开文件的信息

3. 返回值的含义与取值范围

  • 成功时:返回 0
  • 失败时:返回 -1,并设置 errno 来指示错误原因

常见错误码 (errno):

  • ENOENT: 路径名的一部分不存在
  • EACCES: 对路径名的某部分无搜索权限
  • ENOTDIR: 路径名的某部分不是目录
  • ELOOP: 解析路径名时遇到太多符号链接
  • EFAULT: 错误的地址
  • ENAMETOOLONG: 路径名太长

4. 参数的含义与取值范围

  1. const char *pathname (对于 statlstat)

    • 含义:要查询的文件路径
    • 取值范围:任何有效的文件系统路径(绝对或相对路径)
  2. int fd (对于 fstat)

    • 含义:已打开文件的文件描述符
    • 取值范围:有效的文件描述符(通常由 open() 返回)
  3. struct stat *statbuf (所有变体)

    • 含义:指向 struct stat 的指针,用于存储获取到的文件信息
    • 取值范围:必须指向有效的内存位置,大小至少为 sizeof(struct stat)

5. struct stat 结构体详解

struct stat 包含丰富的文件元数据,其具体字段可能因系统而异,但通常包括:

struct stat {
    dev_t     st_dev;         /* 文件所在设备的 ID */
    ino_t     st_ino;         /* Inode 号 */
    mode_t    st_mode;        /* 文件类型和模式 */
    nlink_t   st_nlink;       /* 硬链接数 */
    uid_t     st_uid;         /* 所有者的用户 ID */
    gid_t     st_gid;         /* 所有者的组 ID */
    dev_t     st_rdev;        /* 设备 ID(如果是特殊文件) */
    off_t     st_size;        /* 总大小,字节为单位 */
    blksize_t st_blksize;     /* 文件系统 I/O 的块大小 */
    blkcnt_t  st_blocks;      /* 分配的 512B 块数 */
    
    /* 时间戳(具体精度和字段可能因系统而异) */
    time_t    st_atime;       /* 最后访问时间 */
    time_t    st_mtime;       /* 最后修改时间 */
    time_t    st_ctime;       /* 最后状态变更时间 */
};

6. 函数使用案例

下面是一个完整的示例,展示如何使用 stat 获取文件信息:

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <string.h>

void print_file_type(mode_t mode) {
    if (S_ISREG(mode)) printf("普通文件");
    else if (S_ISDIR(mode)) printf("目录");
    else if (S_ISCHR(mode)) printf("字符设备");
    else if (S_ISBLK(mode)) printf("块设备");
    else if (S_ISFIFO(mode)) printf("FIFO/命名管道");
    else if (S_ISLNK(mode)) printf("符号链接");
    else if (S_ISSOCK(mode)) printf("套接字");
    else printf("未知类型");
}

void print_permissions(mode_t mode) {
    printf("%c%c%c%c%c%c%c%c%c",
           (mode & S_IRUSR) ? 'r' : '-',
           (mode & S_IWUSR) ? 'w' : '-',
           (mode & S_IXUSR) ? 'x' : '-',
           (mode & S_IRGRP) ? 'r' : '-',
           (mode & S_IWGRP) ? 'w' : '-',
           (mode & S_IXGRP) ? 'x' : '-',
           (mode & S_IROTH) ? 'r' : '-',
           (mode & S_IWOTH) ? 'w' : '-',
           (mode & S_IXOTH) ? 'x' : '-');
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "用法: %s <文件名>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    struct stat sb;
    
    if (stat(argv[1], &sb) == -1) {
        fprintf(stderr, "stat() 失败: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    
    printf("文件信息: %s\n", argv[1]);
    printf("========================\n");
    printf("文件类型: ");
    print_file_type(sb.st_mode);
    printf("\n");
    
    printf("权限: ");
    print_permissions(sb.st_mode);
    printf("\n");
    
    printf("硬链接数: %ld\n", (long) sb.st_nlink);
    printf("所有者UID: %d\n", sb.st_uid);
    printf("组GID: %d\n", sb.st_gid);
    printf("文件大小: %lld 字节\n", (long long) sb.st_size);
    printf("块大小: %ld 字节\n", (long) sb.st_blksize);
    printf("分配块数: %lld\n", (long long) sb.st_blocks);
    
    printf("最后访问: %s", ctime(&sb.st_atime));
    printf("最后修改: %s", ctime(&sb.st_mtime));
    printf("最后状态变更: %s", ctime(&sb.st_ctime));
    
    return 0;
}

7. 编译方式与注意事项

编译命令:

gcc -o stat_demo stat_demo.c

注意事项:

  1. 权限问题:需要对目标文件路径有执行权限才能成功调用 stat()
  2. 符号链接:使用 stat() 会跟随符号链接,如需获取链接本身信息,使用 lstat()
  3. 时间精度:不同系统可能支持不同精度的时间戳(秒、纳秒等)
  4. 可移植性struct stat 的具体字段可能因系统和架构而异
  5. 文件描述符:使用 fstat() 时,确保文件描述符有效且已打开
  6. 错误处理:始终检查返回值并处理可能的错误情况

8. 执行结果说明

假设有一个名为 test.txt 的文件,运行程序:

$ ./stat_demo test.txt

可能的输出:

文件信息: test.txt
========================
文件类型: 普通文件
权限: rw-r--r--
硬链接数: 1
所有者UID: 1000
组GID: 1000
文件大小: 1024 字节
块大小: 4096 字节
分配块数: 8
最后访问: Wed Jun 15 10:30:45 2023
最后修改: Wed Jun 14 16:20:30 2023
最后状态变更: Wed Jun 14 16:20:30 2023

结果解释:

  • 这是一个普通文件,权限为所有者可读写,组和其他用户只读
  • 文件大小为1024字节,但实际占用8个512字节的块(即4096字节,由于文件系统块大小)
  • 时间戳显示文件的最后访问、修改和状态变更时间

9. 图文总结

以下是 stat 系统调用的工作流程:

调用 statpath, &sb
系统处理请求
路径解析
是符号链接?
跟随链接
直接访问目标
读取文件元数据
填充struct stat
返回成功0
解析失败
读取失败
设置errno
返回-1

关键点总结:

  1. stat 提供了一种无需打开文件即可获取文件元数据的方法
  2. 三种变体 (stat, fstat, lstat) 适用于不同场景
  3. 返回的 struct stat 包含丰富的文件信息,如类型、权限、大小和时间戳
  4. 正确使用 stat 需要了解文件系统权限和路径解析规则
  5. 始终检查返回值并处理错误情况是良好编程实践

stat 系统调用是文件操作和系统编程的基础,深入理解其工作原理和用法对于开发可靠的系统软件至关重要。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐