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