utimes系统调用及示例
好的,我们来深入学习 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,因为它们提供了更好的精度和灵活性。