futimens 函数详解
1. 函数介绍
futimens
是 Linux 系统中用于设置文件时间戳的系统调用。可以把这个函数想象成一个”时间修改器”——它允许你修改文件的访问时间和修改时间,就像你可以修改照片的拍摄时间一样。
在 Linux 文件系统中,每个文件都有两个重要的时间戳:
- 访问时间(atime): 文件最后一次被读取的时间
- 修改时间(mtime): 文件内容最后一次被修改的时间
futimens
通过文件描述符来修改这些时间戳,这使得它比 utimes
更安全,因为文件描述符确保了你操作的是已经打开的文件,避免了竞态条件。
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
注意事项
- 权限要求: 通常需要对文件有写权限才能修改时间戳,但某些系统允许文件所有者修改自己的文件时间戳
- 时间精度:
futimens
支持纳秒级精度,但实际精度取决于文件系统 - 文件系统支持: 不是所有文件系统都支持纳秒级时间戳精度
- 特殊值: 正确使用
UTIME_OMIT
和UTIME_NOW
可以实现灵活的时间控制 - 错误处理: 始终检查返回值,因为权限不足或只读文件系统会导致操作失败
常见使用场景
- 备份系统: 设置备份文件的时间戳与源文件一致
- 构建系统: 确保生成文件的时间戳正确,避免不必要的重新编译
- 文件同步: 同步不同系统间的文件时间戳
- 测试工具: 在测试中模拟不同时间点的文件状态
- 数字取证: 修改文件时间戳用于取证分析(需谨慎使用)
这些示例展示了 futimens
函数的各种使用方法,从基本的时间戳设置到高级的批量处理工具,帮助你全面掌握这个重要的文件操作函数。