好的,我们来深入学习 utimes
系统调用
1. 函数介绍
在 Linux 系统中,每个文件都关联着一些重要的时间属性:
- 访问时间 (atime): 文件上一次被读取的时间。
- 修改时间 (mtime): 文件内容上一次被修改的时间。
- 状态改变时间 (ctime): 文件的元数据(如权限、所有者、链接数等)上一次被改变的时间。
这些时间戳对于系统管理、备份策略、审计日志等非常重要。
utimes
(Update Times) 系统调用的作用就是手动设置一个文件的访问时间 (atime) 和 修改时间 (mtime)。
简单来说,utimes
就是让你用程序来“篡改”一个文件的“上次访问时间”和“上次修改时间”。
你可能会问,为什么要手动修改这些时间呢?常见的场景有:
- 文件同步工具:在同步文件时,可能需要确保目标文件的时间戳与源文件完全一致。
- 备份和归档:某些备份工具可能需要调整文件时间戳以匹配备份时的状态。
- 测试:编写测试程序时,可能需要模拟文件在特定时间点被访问或修改。
- 修复:如果因为某些原因文件的时间戳不正确,可以手动修正。
2. 函数原型
#include <sys/time.h> // 包含 utimes 函数声明和 timeval 结构体
int utimes(const char *filename, const struct timeval times[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
结构体:
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
来修改文件的时间戳,并与其他相关函数进行比较。
#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[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[0].tv_sec = fixed_time_sec; // atime
new_times[0].tv_usec = 123456; // atime 微秒
new_times[1].tv_sec = fixed_time_sec; // mtime
new_times[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[0].tv_sec = fixed_time_sec + 3600; // atime + 1 小时
new_times[0].tv_usec = 0;
new_times[1].tv_sec = fixed_time_sec + 7200; // mtime + 2 小时
new_times[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[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. 编译和运行
# 假设代码保存在 utimes_example.c 中
gcc -o utimes_example utimes_example.c
# 运行程序
./utimes_example
10. 预期输出
--- 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[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
,因为它们提供了更好的精度和灵活性。