fstat系统调用及示例

fstat – 获取文件状态信息(通过文件描述符)

1. 函数介绍

fstat 是一个 Linux 系统调用,用于获取通过文件描述符指定的文件的详细状态信息。与 stat 不同,fstat 通过文件描述符而不是文件路径来操作文件,这样可以避免在多线程环境中因文件重命名或删除而导致的竞态条件。

fstat 返回的信息包括文件类型、大小、权限、所有者、时间戳等元数据,这些信息对于文件操作、权限检查和系统管理非常有用。

探索fstat系统调用,学习如何通过文件描述符获取文件状态信息。示例代码助您快速上手,深入理解Linux编程精髓。

2. 函数原型

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

int fstat(int fd, struct stat *buf);

3. 功能

获取通过文件描述符指定的文件的状态信息,并将结果存储在 struct stat 结构体中。

4. 参数

  • int fd: 文件描述符,通过 open()creat()dup() 等函数获得
  • struct stat *buf: 指向 stat 结构体的指针,用于存储文件状态信息

5. 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. 返回值

  • 成功时返回 0
  • 失败时返回 -1,并设置 errno

7. 常见 errno 错误码

  • EBADF: 无效的文件描述符
  • EFAULTbuf 指针指向无效地址
  • EOVERFLOW: 文件大小、inode 号或链接数超出范围
  • EIO: I/O 错误
  • EACCES: 权限不足(某些特殊情况下)
  • ENOMEM: 内存不足

8. 相似函数,或关联函数

  • stat(): 通过文件路径获取文件状态
  • lstat(): 获取符号链接本身的状态(不跟随链接)
  • fstatat(): 相对于目录文件描述符获取文件状态
  • statx(): 更现代的文件状态获取函数(Linux 4.11+)
  • access(): 检查文件访问权限
  • chmod()chown(): 修改文件权限和所有者

9. 示例代码

示例1:基本使用 – 获取文件基本信息

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

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

void print_file_permissions(mode_t mode) {
    printf("权限: ");
    printf((S_ISDIR(mode)) ? "d" : "-");
    printf((mode & S_IRUSR) ? "r" : "-");
    printf((mode & S_IWUSR) ? "w" : "-");
    printf((mode & S_IXUSR) ? "x" : "-");
    printf((mode & S_IRGRP) ? "r" : "-");
    printf((mode & S_IWGRP) ? "w" : "-");
    printf((mode & S_IXGRP) ? "x" : "-");
    printf((mode & S_IROTH) ? "r" : "-");
    printf((mode & S_IWOTH) ? "w" : "-");
    printf((mode & S_IXOTH) ? "x" : "-");
    printf("\n");
}

int main() {
    int fd;
    struct stat file_stat;
    int ret;
    
    // 创建测试文件
    fd = open("test_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        exit(EXIT_FAILURE);
    }
    
    printf("成功创建文件,文件描述符: %d\n", fd);
    
    // 写入一些内容
    const char *content = "这是一个测试文件内容。\n包含多行文本。\n用于演示 fstat 功能。";
    write(fd, content, strlen(content));
    
    // 获取文件状态信息
    ret = fstat(fd, &file_stat);
    if (ret == -1) {
        perror("获取文件状态失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
    
    printf("\n=== 文件状态信息 ===\n");
    printf("设备 ID: %ld\n", (long)file_stat.st_dev);
    printf("inode 号: %ld\n", (long)file_stat.st_ino);
    
    print_file_type(file_stat.st_mode);
    print_file_permissions(file_stat.st_mode);
    
    printf("硬链接数: %ld\n", (long)file_stat.st_nlink);
    printf("所有者 UID: %d\n", file_stat.st_uid);
    printf("所属组 GID: %d\n", file_stat.st_gid);
    printf("文件大小: %ld 字节\n", (long)file_stat.st_size);
    printf("I/O 块大小: %ld\n", (long)file_stat.st_blksize);
    printf("分配的块数: %ld (512字节块)\n", (long)file_stat.st_blocks);
    
    // 打印时间信息
    printf("\n时间信息:\n");
    printf("最后访问时间: %s", ctime(&file_stat.st_atime));
    printf("最后修改时间: %s", ctime(&file_stat.st_mtime));
    printf("状态改变时间: %s", ctime(&file_stat.st_ctime));
    
    close(fd);
    return 0;
}

示例2:错误处理和特殊文件类型

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

void check_file_status(int fd, const char *description) {
    struct stat file_stat;
    int ret;
    
    printf("\n检查 %s:\n", description);
    printf("文件描述符: %d\n", fd);
    
    ret = fstat(fd, &file_stat);
    if (ret == -1) {
        printf("  fstat 调用失败: %s\n", strerror(errno));
        return;
    }
    
    printf("  获取状态成功\n");
    printf("  文件大小: %ld 字节\n", (long)file_stat.st_size);
    
    // 检查文件类型
    if (S_ISREG(file_stat.st_mode)) {
        printf("  类型: 普通文件\n");
    } else if (S_ISDIR(file_stat.st_mode)) {
        printf("  类型: 目录\n");
    } else if (S_ISCHR(file_stat.st_mode)) {
        printf("  类型: 字符设备\n");
        printf("  设备号: %ld\n", (long)file_stat.st_rdev);
    } else if (S_ISBLK(file_stat.st_mode)) {
        printf("  类型: 块设备\n");
        printf("  设备号: %ld\n", (long)file_stat.st_rdev);
    } else if (S_ISFIFO(file_stat.st_mode)) {
        printf("  类型: FIFO/管道\n");
    } else if (S_ISSOCK(file_stat.st_mode)) {
        printf("  类型: 套接字\n");
    }
}

int main() {
    int fd_regular, fd_dir, fd_stdin;
    
    // 测试普通文件
    fd_regular = open("test_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd_regular != -1) {
        write(fd_regular, "test content", 12);
        check_file_status(fd_regular, "普通文件");
        close(fd_regular);
    }
    
    // 测试目录
    fd_dir = open(".", O_RDONLY);  // 打开当前目录
    if (fd_dir != -1) {
        check_file_status(fd_dir, "当前目录");
        close(fd_dir);
    }
    
    // 测试标准输入(管道)
    check_file_status(STDIN_FILENO, "标准输入");
    
    // 测试无效文件描述符
    printf("\n测试无效文件描述符:\n");
    struct stat invalid_stat;
    if (fstat(-1, &invalid_stat) == -1) {
        printf("  无效文件描述符测试: %s\n", strerror(errno));
    }
    
    return 0;
}

示例3:文件监控和变化检测

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

typedef struct {
    time_t mtime;    // 最后修改时间
    off_t size;      // 文件大小
} file_monitor_t;

int monitor_file_changes(int fd, file_monitor_t *last_state) {
    struct stat current_stat;
    
    if (fstat(fd, &current_stat) == -1) {
        perror("获取文件状态失败");
        return -1;
    }
    
    int changed = 0;
    
    // 检查文件大小变化
    if (current_stat.st_size != last_state->size) {
        printf("文件大小变化: %ld -> %ld 字节\n", 
               (long)last_state->size, (long)current_stat.st_size);
        changed = 1;
    }
    
    // 检查修改时间变化
    if (current_stat.st_mtime != last_state->mtime) {
        printf("文件修改时间变化\n");
        printf("  旧时间: %s", ctime(&last_state->mtime));
        printf("  新时间: %s", ctime(&current_stat.st_mtime));
        changed = 1;
    }
    
    // 更新状态
    last_state->mtime = current_stat.st_mtime;
    last_state->size = current_stat.st_size;
    
    return changed;
}

int main() {
    int fd;
    file_monitor_t monitor_state = {0, 0};
    int i;
    
    // 创建监控文件
    fd = open("monitor_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建监控文件失败");
        exit(EXIT_FAILURE);
    }
    
    printf("开始监控文件变化...\n");
    printf("文件描述符: %d\n", fd);
    
    // 初始化监控状态
    if (monitor_file_changes(fd, &monitor_state) == -1) {
        close(fd);
        exit(EXIT_FAILURE);
    }
    
    printf("初始状态记录完成\n");
    
    // 模拟文件变化
    for (i = 0; i < 3; i++) {
        sleep(2);
        
        printf("\n--- 第 %d 次检查 ---\n", i + 1);
        
        // 向文件添加内容
        char buffer[50];
        snprintf(buffer, sizeof(buffer), "更新内容 %d\n", i + 1);
        write(fd, buffer, strlen(buffer));
        
        // 检查变化
        if (monitor_file_changes(fd, &monitor_state)) {
            printf("检测到文件变化\n");
        } else {
            printf("文件无变化\n");
        }
    }
    
    close(fd);
    
    // 清理测试文件
    unlink("monitor_file.txt");
    
    return 0;
}

10. 实用宏和函数

// 检查文件类型的宏
S_ISREG(m)   // 是否为普通文件
S_ISDIR(m)   // 是否为目录
S_ISCHR(m)   // 是否为字符设备
S_ISBLK(m)   // 是否为块设备
S_ISFIFO(m)  // 是否为 FIFO
S_ISLNK(m)   // 是否为符号链接
S_ISSOCK(m)  // 是否为套接字

// 权限检查宏
S_IRUSR  // 用户读权限
S_IWUSR  // 用户写权限
S_IXUSR  // 用户执行权限
S_IRGRP  // 组读权限
S_IWGRP  // 组写权限
S_IXGRP  // 组执行权限
S_IROTH  // 其他读权限
S_IWOTH  // 其他写权限
S_IXOTH  // 其他执行权限

11. 实际应用场景

fstat 在以下场景中非常有用:

  • 文件管理器显示文件详细信息
  • 备份工具检查文件是否需要备份
  • 编译系统检查源文件是否比目标文件新
  • 日志系统监控文件大小和修改时间
  • 安全工具验证文件属性
  • 系统监控工具跟踪文件系统变化

总结

fstat 是获取文件状态信息的核心系统调用,通过文件描述符提供了安全可靠的接口。关键要点:

  1. 使用文件描述符避免路径相关的竞态条件
  2. 返回丰富的文件元数据信息
  3. 支持各种文件类型的状态查询
  4. 正确处理各种错误情况
  5. 在文件监控和管理应用中广泛使用

fstat 与相关的 statlstat 函数一起,构成了 Linux 系统中文件状态查询的基础工具集。

fstat是Linux系统调用,用于通过文件描述符获取文件状态信息,避免了路径操作可能引发的竞态问题。该函数返回包括文件类型、大小、权限、时间戳等元数据,存储在stat结构体中。其函数原型为int fstat(int fd, struct stat *buf),成功返回0,失败返回-1并设置errno。常见错误包括无效描述符(EBADF)和内存不足(ENOMEM)。与stat通过路径获取信息不同,fstat更适用于多线程环境。示例代码展示了如何获取文件基本信息、类型判断和错误处理,适用于普通文件、目录等各类文件系统对象的状态检查。

此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注