utimes系统调用及示例

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

1. 函数介绍

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

  1. 访问时间 (atime): 文件上一次被读取的时间。
  2. 修改时间 (mtime): 文件内容上一次被修改的时间。
  3. 状态改变时间 (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 的路径组件时权限不足,或者没有写权限(因为修改时间戳通常需要写权限)。
  • EFAULTfilename 或 times 指向了调用进程无法访问的内存地址。
  • EINVALtimes 数组中的时间值无效(例如,微秒数超出范围)。
  • EIO: I/O 错误。
  • ELOOP: 解析 filename 时遇到符号链接循环。
  • ENAMETOOLONGfilename 太长。
  • ENOENTfilename 指定的文件或目录不存在。
  • ENOMEM: 内核内存不足。
  • ENOTDIRfilename 的某个前缀不是目录。
  • EPERM: 系统调用被阻止(例如,由 seccomp 或安全模块)。
  • EROFSfilename 所在的文件系统是只读的。

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_OMITUTIME_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,因为它们提供了更好的精度和灵活性。
此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注