utimes系统调用及示例

好的,我们来深入学习 utimes 系统调用

1. 函数介绍

在 Linux 系统中,每个文件都关联着一些重要的时间属性:

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

访问时间 (atime): 文件上一次被读取的时间。

修改时间 (mtime): 文件内容上一次被修改的时间。

状态改变时间 (ctime): 文件的元数据(如权限、所有者、链接数等)上一次被改变的时间。

这些时间戳对于系统管理、备份策略、审计日志等非常重要。

utimes (Update Times) 系统调用的作用就是手动设置一个文件的访问时间 (atime) 和 修改时间 (mtime)。

简单来说,utimes 就是让你用程序来“篡改”一个文件的“上次访问时间”和“上次修改时间”。

你可能会问,为什么要手动修改这些时间呢?常见的场景有:

  • 文件同步工具:在同步文件时,可能需要确保目标文件的时间戳与源文件完全一致。

  • 备份和归档:某些备份工具可能需要调整文件时间戳以匹配备份时的状态。

  • 测试:编写测试程序时,可能需要模拟文件在特定时间点被访问或修改。

  • 修复:如果因为某些原因文件的时间戳不正确,可以手动修正。

2. 函数原型

1
2
3
4
#include <sys/time.h> // 包含 utimes 函数声明和 timeval 结构体

int utimes(const char *filename, const struct timeval times&#91;2]);

3. 功能

设置由 filename 指定的文件的访问时间 (atime) 和修改时间 (mtime)。

4. 参数

filename:

  • const char * 类型。

  • 指向一个以 null 结尾的字符串,表示要修改时间戳的文件的路径名。

times:

  • const struct timeval times[2] 类型。

  • 一个包含两个 struct timeval 元素的数组。

  • times[0] 指定了新的访问时间 (atime)。

  • times[1] 指定了新的修改时间 (mtime)。

  • 如果 times 指针为 NULL,则 utimes 会将 atime 和 mtime 都设置为当前时间。

struct timeval 结构体:

1
2
3
4
5
struct timeval {
time_t tv_sec; /* 秒数 (自 Unix 纪元以来) */
suseconds_t tv_usec; /* 微秒数 (0-999999) */
};

5. 返回值

  • 成功: 返回 0。

  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

  • EACCES: 搜索 filename 的路径组件时权限不足,或者没有写权限(因为修改时间戳通常需要写权限)。

  • EFAULT: filename 或 times 指向了调用进程无法访问的内存地址。

  • EINVAL: times 数组中的时间值无效(例如,微秒数超出范围)。

  • EIO: I/O 错误。

  • ELOOP: 解析 filename 时遇到符号链接循环。

  • ENAMETOOLONG: filename 太长。

  • ENOENT: filename 指定的文件或目录不存在。

  • ENOMEM: 内核内存不足。

  • ENOTDIR: filename 的某个前缀不是目录。

  • EPERM: 系统调用被阻止(例如,由 seccomp 或安全模块)。

  • EROFS: filename 所在的文件系统是只读的。

7. 相似函数或关联函数

  • utime: 一个更老的、功能类似的函数。它使用 struct utimbuf,精度只到秒。utimes 是 utime 的微秒精度版本。#include <utime.h> struct utimbuf { time_t actime; /* 访问时间 / time_t modtime; / 修改时间 */ }; int utime(const char *filename, const struct utimbuf *times);

  • lutimes: 与 utimes 类似,但如果 filename 是一个符号链接,它会修改符号链接本身的 atime 和 mtime,而不是它指向的目标文件。

  • futimes: 与 utimes 类似,但它通过已打开的文件描述符 (fd) 来指定文件,而不是文件路径。int futimes(int fd, const struct timeval tv[2]);

  • futimens / utimensat: 更现代的系统调用,使用 struct timespec,提供纳秒级精度,并且有更多选项(如 UTIME_OMIT, UTIME_NOW)。这些是推荐在新代码中使用的。#include <fcntl.h> // 包含 AT_FDCWD 等 #include <sys/stat.h> // 包含 timespec, UTIME_* 常量 int futimens(int fd, const struct timespec times[2]); int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);

8. 示例代码

下面的示例演示了如何使用 utimes 来修改文件的时间戳,并与其他相关函数进行比较。

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
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h> // 包含 utimes, timeval
#include <sys/stat.h> // 包含 stat, timespec
#include <utime.h> // 包含 utime, utimbuf
#include <fcntl.h> // 包含 open, O_* flags
#include <string.h>
#include <errno.h>
#include <time.h> // 包含 time, localtime, strftime

// 辅助函数:打印文件的时间信息
void print_file_times(const char *filename) {
struct stat sb;
if (stat(filename, &sb) == -1) {
perror("stat");
return;
}

printf("File: %s\n", filename);
// 使用 ctime 将 time_t 转换为可读字符串
printf(" Last Status Change (ctime): %s", ctime(&sb.st_ctime)); // ctime 包含换行符
printf(" Last Modification (mtime): %s", ctime(&sb.st_mtime));
printf(" Last Access (atime): %s", ctime(&sb.st_atime));
printf("\n");
}

// 辅助函数:创建一个测试文件
void create_test_file(const char *filename) {
FILE *f = fopen(filename, "w");
if (!f) {
perror("fopen");
exit(EXIT_FAILURE);
}
fprintf(f, "This is a test file for utimes example.\n");
fclose(f);
printf("Created test file: %s\n\n", filename);
}

int main() {
const char *test_file = "utimes_test_file.txt";
struct timeval new_times&#91;2];
time_t fixed_time_sec;
struct tm tm_tmp;

printf("--- Demonstrating utimes ---\n");

// 1. 创建一个测试文件
create_test_file(test_file);

// 2. 显示初始时间
printf("1. Initial timestamps:\n");
print_file_times(test_file);

// 3. 使用 utimes 将 atime 和 mtime 设置为固定时间
printf("2. Setting timestamps to a fixed time using utimes()...\n");
// 设置一个固定的日期时间:例如 2023-10-27 10:00:00 UTC
memset(&tm_tmp, 0, sizeof(tm_tmp));
tm_tmp.tm_year = 2023 - 1900; // tm_year is years since 1900
tm_tmp.tm_mon = 10 - 1; // tm_mon is 0-11
tm_tmp.tm_mday = 27;
tm_tmp.tm_hour = 10;
tm_tmp.tm_min = 0;
tm_tmp.tm_sec = 0;
tm_tmp.tm_isdst = 0; // Not daylight saving time
fixed_time_sec = timegm(&tm_tmp); // Convert to time_t (UTC)
if (fixed_time_sec == -1) {
perror("timegm");
unlink(test_file);
exit(EXIT_FAILURE);
}

new_times&#91;0].tv_sec = fixed_time_sec; // atime
new_times&#91;0].tv_usec = 123456; // atime 微秒
new_times&#91;1].tv_sec = fixed_time_sec; // mtime
new_times&#91;1].tv_usec = 654321; // mtime 微秒

if (utimes(test_file, new_times) == -1) {
perror("utimes");
unlink(test_file);
exit(EXIT_FAILURE);
}
printf("Set atime to %s", ctime(&fixed_time_sec)); // ctime adds newline
printf("Set mtime to %s", ctime(&fixed_time_sec));
printf("Note: Microseconds are set but not displayed by ctime.\n\n");

printf("3. Timestamps after utimes (fixed time):\n");
print_file_times(test_file);

// 4. 使用 utimes(NULL) 将时间设置为当前时间
printf("4. Setting timestamps to CURRENT time using utimes(NULL)...\n");
sleep(3); // 等待几秒,确保当前时间不同
if (utimes(test_file, NULL) == -1) {
perror("utimes(NULL)");
unlink(test_file);
exit(EXIT_FAILURE);
}
printf("Timestamps updated to current time.\n\n");

printf("5. Timestamps after utimes(NULL):\n");
print_file_times(test_file);

// 6. 比较 utime (旧版,秒级精度)
printf("6. --- Comparing with older utime() ---\n");
struct utimbuf old_times;
old_times.actime = fixed_time_sec; // 访问时间
old_times.modtime = fixed_time_sec; // 修改时间
printf("Setting timestamps back to fixed time using utime() (second precision)...\n");
if (utime(test_file, &old_times) == -1) {
perror("utime");
} else {
printf("utime() succeeded.\n");
print_file_times(test_file);
printf("Note: atime and mtime are now at second precision.\n\n");
}

// 7. 比较 futimes (通过文件描述符)
printf("7. --- Comparing with futimes() ---\n");
int fd = open(test_file, O_RDONLY);
if (fd == -1) {
perror("open");
} else {
new_times&#91;0].tv_sec = fixed_time_sec + 3600; // atime + 1 小时
new_times&#91;0].tv_usec = 0;
new_times&#91;1].tv_sec = fixed_time_sec + 7200; // mtime + 2 小时
new_times&#91;1].tv_usec = 0;
printf("Setting timestamps using futimes() via file descriptor...\n");
if (futimes(fd, new_times) == -1) {
perror("futimes");
} else {
printf("futimes() succeeded.\n");
print_file_times(test_file);
}
close(fd);
}

// 8. 清理
printf("8. --- Cleaning up ---\n");
if (unlink(test_file) == 0) {
printf("Deleted test file '%s'.\n", test_file);
} else {
perror("unlink");
}

printf("\n--- Summary ---\n");
printf("1. utimes(filename, times&#91;2]): Sets atime and mtime for a file via its path.\n");
printf("2. Precision is up to microseconds (tv_usec).\n");
printf("3. If times is NULL, both atime and mtime are set to the current time.\n");
printf("4. Related functions:\n");
printf(" - utime(): Older, second-precision version.\n");
printf(" - lutimes(): Modifies symlink itself, not target.\n");
printf(" - futimes(): Modifies file via file descriptor.\n");
printf(" - futimens() / utimensat(): Modern, nanosecond-precision, more flexible (recommended).\n");

return 0;
}

9. 编译和运行

1
2
3
4
5
6
# 假设代码保存在 utimes_example.c 中
gcc -o utimes_example utimes_example.c

# 运行程序
./utimes_example

10. 预期输出

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
--- Demonstrating utimes ---
Created test file: utimes_test_file.txt

1. Initial timestamps:
File: utimes_test_file.txt
Last Status Change (ctime): Fri Oct 27 11:00:00 2023
Last Modification (mtime): Fri Oct 27 11:00:00 2023
Last Access (atime): Fri Oct 27 11:00:00 2023

2. Setting timestamps to a fixed time using utimes()...
Set atime to Fri Oct 27 10:00:00 2023
Set mtime to Fri Oct 27 10:00:00 2023
Note: Microseconds are set but not displayed by ctime.

3. Timestamps after utimes (fixed time):
File: utimes_test_file.txt
Last Status Change (ctime): Fri Oct 27 11:00:00 2023
Last Modification (mtime): Fri Oct 27 10:00:00 2023
Last Access (atime): Fri Oct 27 10:00:00 2023

4. Setting timestamps to CURRENT time using utimes(NULL)...
Timestamps updated to current time.

5. Timestamps after utimes(NULL):
File: utimes_test_file.txt
Last Status Change (ctime): Fri Oct 27 11:00:03 2023
Last Modification (mtime): Fri Oct 27 11:00:03 2023
Last Access (atime): Fri Oct 27 11:00:03 2023

6. --- Comparing with older utime() ---
Setting timestamps back to fixed time using utime() (second precision)...
utime() succeeded.
File: utimes_test_file.txt
Last Status Change (ctime): Fri Oct 27 11:00:03 2023
Last Modification (mtime): Fri Oct 27 10:00:00 2023
Last Access (atime): Fri Oct 27 10:00:00 2023
Note: atime and mtime are now at second precision.

7. --- Comparing with futimes() ---
Setting timestamps using futimes() via file descriptor...
futimes() succeeded.
File: utimes_test_file.txt
Last Status Change (ctime): Fri Oct 27 11:00:03 2023
Last Modification (mtime): Fri Oct 27 12:00:00 2023
Last Access (atime): Fri Oct 27 11:00:00 2023

8. --- Cleaning up ---
Deleted test file 'utimes_test_file.txt'.

--- Summary ---
1. utimes(filename, times&#91;2]): Sets atime and mtime for a file via its path.
2. Precision is up to microseconds (tv_usec).
3. If times is NULL, both atime and mtime are set to the current time.
4. Related functions:
- utime(): Older, second-precision version.
- lutimes(): Modifies symlink itself, not target.
- futimes(): Modifies file via file descriptor.
- futimens() / utimensat(): Modern, nanosecond-precision, more flexible (recommended).

11. 总结

utimes 是一个用于修改文件访问时间和修改时间的系统调用。

  • 核心功能:精确设置文件的 atime 和 mtime(微秒级)。

  • 参数:文件路径和包含两个 timeval 结构的数组。

  • 特殊用法:传入 NULL 可将时间设置为当前时间。

相关函数:

  • utime: 更老的秒级精度版本。

  • lutimes: 修改符号链接本身的时间。

  • futimes: 通过文件描述符修改时间。

  • futimens / utimensat: 现代推荐的函数,提供纳秒精度和更多控制选项。

使用场景:文件同步、备份、测试、时间戳修复。

给 Linux 编程小白的建议:虽然 utimes 很有用,但在编写新代码时,考虑使用更新、更强大的 utimensat 或 futimens,因为它们提供了更好的精度和灵活性。

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