futimesat系统调用及示例

futimens 函数详解

1. 函数介绍

futimens 是 Linux 系统中用于设置文件时间戳的系统调用。可以把这个函数想象成一个”时间修改器”——它允许你修改文件的访问时间和修改时间,就像你可以修改照片的拍摄时间一样。

在 Linux 文件系统中,每个文件都有两个重要的时间戳:

  • 访问时间(atime): 文件最后一次被读取的时间
  • 修改时间(mtime): 文件内容最后一次被修改的时间

futimens 通过文件描述符来修改这些时间戳,这使得它比 utimes 更安全,因为文件描述符确保了你操作的是已经打开的文件,避免了竞态条件。

https://www.calcguide.tech/2025/09/08/futimesat%e7%b3%bb%e7%bb%9f%e8%b0%83%e7%94%a8%e5%8f%8a%e7%a4%ba%e4%be%8b

2. 函数原型

#include <fcntl.h>     /* 必须包含此头文件 */
#include <sys/stat.h>  /* 或者包含此头文件 */

int futimens(int fd, const struct timespec times[2]);

3. 功能

futimens 函数用于设置指定文件描述符对应的文件的时间戳。它可以精确地设置文件的访问时间和修改时间到纳秒级别。

4. 参数

  • fd: 已打开的文件描述符
  • times: 指向 struct timespec 数组的指针,数组包含两个元素:
    • times[0]: 访问时间(atime)
    • times[1]: 修改时间(mtime)
    struct timespec 结构体定义:struct timespec { time_t tv_sec; /* 秒数 */ long tv_nsec; /* 纳秒数 */ };

5. 特殊时间值

在 times 数组中,可以使用以下特殊值:

  • UTIME_OMIT: 保持当前时间戳不变(跳过此时间的修改)
  • UTIME_NOW: 将时间戳设置为当前时间

6. 返回值

  • 成功: 返回 0
  • 失败: 返回 -1,并设置相应的 errno 错误码

常见错误码:

  • EBADF: fd 不是有效的文件描述符
  • EACCES: 权限不足(通常需要写权限)
  • EINVAL: times 参数无效
  • EROFS: 文件系统是只读的
  • EIO: I/O 错误

7. 相似函数或关联函数

  • utimensat: 通过文件路径设置时间戳(更灵活,可以指定相对路径)
  • utimes: 设置文件时间戳(较老的接口,精度较低)
  • futimes: 通过文件描述符设置时间戳(较老的接口)
  • stat: 获取文件当前的时间戳信息
  • fstat: 通过文件描述符获取文件信息

8. 示例代码

示例1:基础用法 – 设置特定时间戳

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>

int main() {
    int fd;
    const char *filename = "timestamp_test.txt";
    struct timespec new_times[2];
    struct stat file_info;
    
    // 创建并写入测试文件
    fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    const char *content = "这是一个测试文件\n用于演示 futimens 函数\n";
    write(fd, content, strlen(content));
    close(fd);
    
    // 重新打开文件以获取文件描述符
    fd = open(filename, O_WRONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    // 设置特定的时间戳
    // 访问时间:2023年1月1日 12:00:00
    new_times[0].tv_sec = 1672545600;  // Unix 时间戳
    new_times[0].tv_nsec = 123456789;  // 纳秒
    
    // 修改时间:2023年6月1日 15:30:45
    new_times[1].tv_sec = 1685626245;  // Unix 时间戳
    new_times[1].tv_nsec = 987654321;  // 纳秒
    
    printf("=== 修改前的时间戳 ===\n");
    
    // 获取并显示修改前的时间
    if (stat(filename, &file_info) == -1) {
        perror("stat");
        close(fd);
        return 1;
    }
    
    printf("访问时间: %s", ctime(&file_info.st_atime));
    printf("修改时间: %s", ctime(&file_info.st_mtime));
    
    // 使用 futimens 设置新的时间戳
    if (futimens(fd, new_times) == -1) {
        perror("futimens");
        close(fd);
        return 1;
    }
    
    printf("\n=== 修改后的时间戳 ===\n");
    
    // 获取并显示修改后的时间
    if (stat(filename, &file_info) == -1) {
        perror("stat");
        close(fd);
        return 1;
    }
    
    printf("访问时间: %s", ctime(&file_info.st_atime));
    printf("修改时间: %s", ctime(&file_info.st_mtime));
    
    // 显示纳秒级精度
    printf("\n=== 纳秒级精度 ===\n");
    printf("访问时间: %ld.%09ld 秒\n", 
           file_info.st_atim.tv_sec, file_info.st_atim.tv_nsec);
    printf("修改时间: %ld.%09ld 秒\n", 
           file_info.st_mtim.tv_sec, file_info.st_mtim.tv_nsec);
    
    close(fd);
    return 0;
}

示例2:使用特殊时间值

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>

// 显示文件时间信息的辅助函数
void show_file_times(const char *filename, const char *description) {
    struct stat file_info;
    
    if (stat(filename, &file_info) == -1) {
        perror("stat");
        return;
    }
    
    printf("%s:\n", description);
    printf("  访问时间: %s", ctime(&file_info.st_atime));
    printf("  修改时间: %s", ctime(&file_info.st_mtime));
    printf("\n");
}

int main() {
    int fd;
    const char *filename = "special_times_test.txt";
    struct timespec times[2];
    
    // 创建测试文件
    fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    const char *content = "测试特殊时间值功能\n";
    write(fd, content, strlen(content));
    close(fd);
    
    // 显示初始时间
    show_file_times(filename, "初始时间");
    
    // 重新打开文件
    fd = open(filename, O_WRONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    printf("=== 演示各种时间设置方式 ===\n\n");
    
    // 1. 使用 UTIME_NOW:将两个时间都设置为当前时间
    printf("1. 使用 UTIME_NOW (设置为当前时间):\n");
    times[0].tv_sec = 0;
    times[0].tv_nsec = UTIME_NOW;
    times[1].tv_sec = 0;
    times[1].tv_nsec = UTIME_NOW;
    
    if (futimens(fd, times) == -1) {
        perror("futimens UTIME_NOW");
    } else {
        show_file_times(filename, "  设置后");
    }
    
    sleep(2);  // 等待 2 秒以便看到时间变化
    
    // 2. 只修改访问时间为当前时间,保持修改时间不变
    printf("2. 只修改访问时间为当前时间:\n");
    times[0].tv_nsec = UTIME_NOW;     // 访问时间设为当前
    times[1].tv_nsec = UTIME_OMIT;    // 修改时间保持不变
    
    if (futimens(fd, times) == -1) {
        perror("futimens partial update");
    } else {
        show_file_times(filename, "  设置后");
    }
    
    sleep(2);
    
    // 3. 只修改修改时间为特定时间,保持访问时间不变
    printf("3. 只修改修改时间为特定时间:\n");
    times[0].tv_nsec = UTIME_OMIT;    // 访问时间保持不变
    times[1].tv_sec = 1609459200;     // 2021年1月1日 00:00:00
    times[1].tv_nsec = 500000000;     // 500毫秒
    
    if (futimens(fd, times) == -1) {
        perror("futimens specific mtime");
    } else {
        show_file_times(filename, "  设置后");
    }
    
    close(fd);
    return 0;
}

示例3:批量文件时间戳管理工具

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

// 文件时间信息结构体
struct file_time_info {
    char filename[256];
    time_t atime;
    time_t mtime;
    off_t size;
};

// 获取文件时间信息
int get_file_time_info(const char *filepath, struct file_time_info *info) {
    struct stat file_stat;
    
    if (stat(filepath, &file_stat) == -1) {
        return -1;
    }
    
    strncpy(info->filename, filepath, sizeof(info->filename) - 1);
    info->filename[sizeof(info->filename) - 1] = '\0';
    info->atime = file_stat.st_atime;
    info->mtime = file_stat.st_mtime;
    info->size = file_stat.st_size;
    
    return 0;
}

// 设置文件时间戳
int set_file_times(const char *filepath, time_t new_atime, time_t new_mtime) {
    int fd;
    struct timespec times[2];
    
    fd = open(filepath, O_WRONLY);
    if (fd == -1) {
        // 如果写打开失败,尝试只读打开(某些文件可能只需要读权限)
        fd = open(filepath, O_RDONLY);
        if (fd == -1) {
            return -1;
        }
    }
    
    times[0].tv_sec = new_atime;
    times[0].tv_nsec = 0;
    times[1].tv_sec = new_mtime;
    times[1].tv_nsec = 0;
    
    int result = futimens(fd, times);
    close(fd);
    
    return result;
}

// 打印文件时间信息
void print_file_info(const struct file_time_info *info) {
    printf("文件: %s\n", info->filename);
    printf("  大小: %ld 字节\n", (long)info->size);
    printf("  访问时间: %s", ctime(&info->atime));
    printf("  修改时间: %s", ctime(&info->mtime));
}

// 将目录中所有文件的时间戳设置为相同时间
int sync_directory_times(const char *dirpath, time_t target_time) {
    DIR *dir;
    struct dirent *entry;
    char filepath[512];
    int count = 0;
    int errors = 0;
    
    dir = opendir(dirpath);
    if (dir == NULL) {
        perror("opendir");
        return -1;
    }
    
    printf("正在同步目录 '%s' 中文件的时间戳...\n", dirpath);
    printf("目标时间: %s\n", ctime(&target_time));
    
    while ((entry = readdir(dir)) != NULL) {
        // 跳过当前目录和父目录
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }
        
        // 构造完整文件路径
        snprintf(filepath, sizeof(filepath), "%s/%s", dirpath, entry->d_name);
        
        // 设置文件时间戳
        if (set_file_times(filepath, target_time, target_time) == 0) {
            printf("✓ 成功: %s\n", entry->d_name);
            count++;
        } else {
            printf("✗ 失败: %s (%s)\n", entry->d_name, strerror(errno));
            errors++;
        }
    }
    
    closedir(dir);
    
    printf("\n处理完成: %d 个文件成功, %d 个文件失败\n", count, errors);
    return errors == 0 ? 0 : -1;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("用法: %s <文件或目录路径> [目标时间戳]\n", argv[0]);
        printf("示例: %s /path/to/file\n", argv[0]);
        printf("      %s /path/to/directory 1672531200\n", argv[0]);
        return 1;
    }
    
    const char *path = argv[1];
    time_t target_time;
    
    // 如果提供了目标时间戳,使用它;否则使用当前时间
    if (argc > 2) {
        target_time = (time_t)atol(argv[2]);
    } else {
        target_time = time(NULL);
    }
    
    // 检查路径是文件还是目录
    struct stat path_stat;
    if (stat(path, &path_stat) == -1) {
        perror("stat");
        return 1;
    }
    
    if (S_ISDIR(path_stat.st_mode)) {
        // 处理目录
        return sync_directory_times(path, target_time);
    } else {
        // 处理单个文件
        struct file_time_info before_info, after_info;
        
        printf("=== 文件时间戳修改工具 ===\n\n");
        
        // 获取修改前的信息
        if (get_file_time_info(path, &before_info) == -1) {
            perror("获取文件信息失败");
            return 1;
        }
        
        printf("修改前:\n");
        print_file_info(&before_info);
        
        // 设置新的时间戳
        if (set_file_times(path, target_time, target_time) == -1) {
            perror("设置时间戳失败");
            return 1;
        }
        
        // 获取修改后的信息
        if (get_file_time_info(path, &after_info) == -1) {
            perror("获取修改后信息失败");
            return 1;
        }
        
        printf("\n修改后:\n");
        print_file_info(&after_info);
        
        printf("\n时间戳修改完成!\n");
    }
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -o futimens_example1 example1.c
gcc -o futimens_example2 example2.c
gcc -o futimens_example3 example3.c

# 运行示例
./futimens_example1
./futimens_example2

# 运行文件时间管理工具
./futimens_example3 test_file.txt
./futimens_example3 /path/to/directory
./futimens_example3 test_file.txt 1672531200

注意事项

  1. 权限要求: 通常需要对文件有写权限才能修改时间戳,但某些系统允许文件所有者修改自己的文件时间戳
  2. 时间精度futimens 支持纳秒级精度,但实际精度取决于文件系统
  3. 文件系统支持: 不是所有文件系统都支持纳秒级时间戳精度
  4. 特殊值: 正确使用 UTIME_OMIT 和 UTIME_NOW 可以实现灵活的时间控制
  5. 错误处理: 始终检查返回值,因为权限不足或只读文件系统会导致操作失败

常见使用场景

  1. 备份系统: 设置备份文件的时间戳与源文件一致
  2. 构建系统: 确保生成文件的时间戳正确,避免不必要的重新编译
  3. 文件同步: 同步不同系统间的文件时间戳
  4. 测试工具: 在测试中模拟不同时间点的文件状态
  5. 数字取证: 修改文件时间戳用于取证分析(需谨慎使用)

这些示例展示了 futimens 函数的各种使用方法,从基本的时间戳设置到高级的批量处理工具,帮助你全面掌握这个重要的文件操作函数。

https://blog.csdn.net/zidier215/article/details/151332377?sharetype=blogdetail&sharerId=151332377&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118

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

发表回复

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