futimesat系统调用及示例

futimens 函数详解

  1. 函数介绍

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

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

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

  • 访问时间(atime): 文件最后一次被读取的时间

  • 修改时间(mtime): 文件内容最后一次被修改的时间

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

https://www.calcguide.tech/2025/09/08/futimesat系统调用及示例

  1. 函数原型
1
2
3
4
5
#include <fcntl.h>     /* 必须包含此头文件 */
#include <sys/stat.h> /* 或者包含此头文件 */

int futimens(int fd, const struct timespec times&#91;2]);

  1. 功能

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

  1. 参数
  • fd: 已打开的文件描述符

  • times: 指向 struct timespec 数组的指针,数组包含两个元素:times[0]: 访问时间(atime)

  • times[1]: 修改时间(mtime)
    struct timespec 结构体定义:struct timespec { time_t tv_sec; /* 秒数 / long tv_nsec; / 纳秒数 */ };

  1. 特殊时间值

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

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

  • UTIME_NOW: 将时间戳设置为当前时间

  1. 返回值
  • 成功: 返回 0

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

常见错误码:

  • EBADF: fd 不是有效的文件描述符

  • EACCES: 权限不足(通常需要写权限)

  • EINVAL: times 参数无效

  • EROFS: 文件系统是只读的

  • EIO: I/O 错误

  1. 相似函数或关联函数
  • utimensat: 通过文件路径设置时间戳(更灵活,可以指定相对路径)

  • utimes: 设置文件时间戳(较老的接口,精度较低)

  • futimes: 通过文件描述符设置时间戳(较老的接口)

  • stat: 获取文件当前的时间戳信息

  • fstat: 通过文件描述符获取文件信息

  1. 示例代码

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#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&#91;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&#91;0].tv_sec = 1672545600; // Unix 时间戳
new_times&#91;0].tv_nsec = 123456789; // 纳秒

// 修改时间:2023年6月1日 15:30:45
new_times&#91;1].tv_sec = 1685626245; // Unix 时间戳
new_times&#91;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:使用特殊时间值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#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&#91;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&#91;0].tv_sec = 0;
times&#91;0].tv_nsec = UTIME_NOW;
times&#91;1].tv_sec = 0;
times&#91;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&#91;0].tv_nsec = UTIME_NOW; // 访问时间设为当前
times&#91;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&#91;0].tv_nsec = UTIME_OMIT; // 访问时间保持不变
times&#91;1].tv_sec = 1609459200; // 2021年1月1日 00:00:00
times&#91;1].tv_nsec = 500000000; // 500毫秒

if (futimens(fd, times) == -1) {
perror("futimens specific mtime");
} else {
show_file_times(filename, " 设置后");
}

close(fd);
return 0;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#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&#91;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&#91;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&#91;2];

fd = open(filepath, O_WRONLY);
if (fd == -1) {
// 如果写打开失败,尝试只读打开(某些文件可能只需要读权限)
fd = open(filepath, O_RDONLY);
if (fd == -1) {
return -1;
}
}

times&#91;0].tv_sec = new_atime;
times&#91;0].tv_nsec = 0;
times&#91;1].tv_sec = new_mtime;
times&#91;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&#91;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&#91;]) {
if (argc < 2) {
printf("用法: %s <文件或目录路径> &#91;目标时间戳]\n", argv&#91;0]);
printf("示例: %s /path/to/file\n", argv&#91;0]);
printf(" %s /path/to/directory 1672531200\n", argv&#91;0]);
return 1;
}

const char *path = argv&#91;1];
time_t target_time;

// 如果提供了目标时间戳,使用它;否则使用当前时间
if (argc > 2) {
target_time = (time_t)atol(argv&#91;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;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 编译示例程序
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 函数的各种使用方法,从基本的时间戳设置到高级的批量处理工具,帮助你全面掌握这个重要的文件操作函数。

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

data-ad-format="auto" data-full-width-responsive="true">