You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用O_TMPFILE文件实现原子覆盖?

问题描述

现有一段可正常运行的代码,但希望优化。手册里给出两种linkat用法示例:第一种从未成功运行;第二种仅在文件名唯一时有效,但我的场景无法保证文件名唯一,因此用了中间文件名。原本想直接用rename跳过中间文件,却遇到跨设备问题。想请教是否有方法改进这段代码——先调用linkat再立即rename的操作看起来不太合理。

原代码

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    int fd = open("./", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    
    int len = write(fd, "data\n", 5);
    printf("Written %d bytes to %d\n", len, fd);
    const char myfile[] = "myfile";
    if (linkat(fd, "", AT_FDCWD, myfile, AT_EMPTY_PATH)) {
        perror("linkat");
        char buf[1024];
        snprintf(buf, 1024,  "/proc/self/fd/%d", fd);
        //if (linkat(AT_FDCWD, buf, AT_FDCWD, myfile, AT_SYMLINK_FOLLOW) < 0) {
        if (linkat(AT_FDCWD, buf, AT_FDCWD, "intermediaryName", AT_SYMLINK_FOLLOW) < 0) {
            perror("linkat backup failed");
            return 1;
        }
        if (rename("intermediaryName", myfile) < 0) {
            perror("rename");
            return 1;
        }
    }
    printf("Success\n");
    return 0;
}
优化方案

1. 修复第一种linkat用法失败问题

第一种linkat(fd, "", AT_FDCWD, myfile, AT_EMPTY_PATH)失败,核心原因通常是文件系统不支持O_TMPFILE配合AT_EMPTY_PATH的链接操作,或者内核版本低于3.11(该组合需要Linux 3.11+支持)。可以先确认目标文件系统类型(ext4、XFS等主流文件系统支持,老旧文件系统可能不支持),同时保证open调用的目录与myfile在同一设备上。

2. 避免中间文件的原子化方案

如果环境允许,可直接通过扩展linkat的标志位实现无中间文件操作:

  • 原子覆盖(Linux 5.12+):使用AT_REPLACE_FD标志,直接用临时文件原子替换已存在的myfile,无需中间文件:
    if (!linkat(fd, "", AT_FDCWD, myfile, AT_EMPTY_PATH | AT_REPLACE_FD)) {
        // 操作成功
    }
    
  • 兼容旧内核覆盖(Linux 3.15+):通过/proc/self/fd路径链接时,加上AT_REPLACE标志直接覆盖目标文件:
    linkat(AT_FDCWD, buf, AT_FDCWD, myfile, AT_SYMLINK_FOLLOW | AT_REPLACE)
    

3. 跨设备场景的终极解决方案

如果临时文件与myfile不在同一设备,link和rename都会失败(rename跨设备本质是复制+删除)。此时必须采用复制+原子重命名的方案:

  • 用copy_file_range或sendfile高效复制临时文件数据到目标目录的临时文件
  • 再通过rename原子替换目标文件,保证操作的原子性

优化后代码示例(兼容多场景)

以下代码优先尝试原子链接,失败则尝试直接覆盖,最后 fallback 到跨设备复制方案:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define TMP_SUFFIX ".tmpXXXXXX"

// 跨设备时的文件复制+原子重命名
static int copy_and_rename(int src_fd, const char *dest_path) {
    // 生成目标目录下的唯一临时文件
    char tmp_path[1024];
    snprintf(tmp_path, sizeof(tmp_path), "%s%s", dest_path, TMP_SUFFIX);
    int dest_fd = mkstemp(tmp_path);
    if (dest_fd < 0) return -1;

    // 高效复制文件内容
    off_t offset = 0;
    ssize_t ret;
    while ((ret = copy_file_range(src_fd, &offset, dest_fd, NULL, 1024*1024, 0)) > 0) {}
    if (ret < 0) {
        close(dest_fd);
        unlink(tmp_path);
        return -1;
    }

    // 原子重命名替换目标文件
    int rename_ret = rename(tmp_path, dest_path);
    close(dest_fd);
    return rename_ret;
}

int main(int argc, char *argv[])
{
    int fd = open("./", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    
    int len = write(fd, "data\n", 5);
    printf("Written %d bytes to %d\n", len, fd);
    const char myfile[] = "myfile";
    int ret = 0;

    // 优先尝试原子链接(无中间文件)
    if (!linkat(fd, "", AT_FDCWD, myfile, AT_EMPTY_PATH | AT_REPLACE_FD)) {
        printf("Success via atomic linkat\n");
        goto cleanup;
    }

    // 尝试直接覆盖目标文件
    char buf[1024];
    snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd);
    if (!linkat(AT_FDCWD, buf, AT_FDCWD, myfile, AT_SYMLINK_FOLLOW | AT_REPLACE)) {
        printf("Success via linkat replace\n");
        goto cleanup;
    }

    // 跨设备场景:复制+原子重命名
    if (copy_and_rename(fd, myfile) == 0) {
        printf("Success via copy and rename\n");
        goto cleanup;
    }

    perror("All methods failed");
    ret = 1;

cleanup:
    close(fd);
    return ret;
}

内容的提问来源于stack exchange,提问作者Stan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 03:23:17