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

Linux/C下拷贝文件时裁剪末尾零填充空洞的实现方案

解决方案:裁剪tmpfs空洞文件至实际数据大小

优先方案:系统调用直接定位实际数据末尾

方法1:利用lseek的SEEK_DATA/SEEK_HOLE快速定位

Linux的lseek支持特殊定位标志,可直接跳过空洞或数据块,从文件末尾往前找到最后一个有数据的位置,这是最轻量化的实现:

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

off_t get_actual_data_size(int fd) {
    off_t total_size = lseek(fd, 0, SEEK_END);
    if (total_size == -1) return -1;

    off_t current_pos = total_size;
    while (current_pos > 0) {
        // 从当前位置往前找第一个数据块
        current_pos = lseek(fd, current_pos - 1, SEEK_DATA);
        if (current_pos != -1) {
            // 实际数据大小为最后一个数据字节的下一位
            return current_pos + 1;
        }
        if (errno != ENXIO) {
            // 非空洞导致的错误,直接返回失败
            return -1;
        }
        // 当前位置是空洞,跳转到空洞的起始位置继续往前找
        current_pos = lseek(fd, current_pos, SEEK_HOLE);
        if (current_pos == -1) return -1;
    }
    // 文件全为空洞,返回0
    return 0;
}

使用流程:

  1. 以O_RDONLY模式打开目标tmpfs文件
  2. 调用该函数获取实际数据大小
  3. 对下载副本执行ftruncate裁剪,或拷贝时仅读取到该偏移量

方法2:结合FICLONE快速拷贝+裁剪

如果需要生成离线副本,可先利用ioctl(FICLONE)快速克隆文件(tmpfs下克隆几乎无开销),再用上述方法获取实际大小后裁剪:

#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>

int copy_and_trim(const char* src_path, const char* dst_path) {
    int src_fd = open(src_path, O_RDONLY);
    if (src_fd == -1) return -1;

    int dst_fd = open(dst_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (dst_fd == -1) {
        close(src_fd);
        return -1;
    }

    // 获取源文件总大小并初始化目标文件
    off_t total_size = lseek(src_fd, 0, SEEK_END);
    if (total_size == -1) goto cleanup;
    if (ftruncate(dst_fd, total_size) == -1) goto cleanup;

    // 尝试快速克隆,失败则回退到普通拷贝
    if (ioctl(dst_fd, FICLONE, src_fd) == -1) {
        lseek(src_fd, 0, SEEK_SET);
        char buf[4096];
        ssize_t n;
        while ((n = read(src_fd, buf, sizeof(buf))) > 0) {
            if (write(dst_fd, buf, n) != n) goto cleanup;
        }
        if (n == -1) goto cleanup;
    }

    // 获取实际数据大小并裁剪目标文件
    off_t actual_size = get_actual_data_size(src_fd);
    if (actual_size == -1) goto cleanup;
    if (ftruncate(dst_fd, actual_size) == -1) goto cleanup;

    close(src_fd);
    close(dst_fd);
    return 0;

cleanup:
    close(src_fd);
    close(dst_fd);
    unlink(dst_path);
    return -1;
}

备选方案:通过fiemap解析文件区间

利用ioctl(FS_IOC_FIEMAP)获取文件的所有已分配区间,遍历找到最大的已使用偏移量:

#include <linux/fs.h>
#include <linux/fiemap.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>

off_t get_actual_size_via_fiemap(int fd) {
    struct fiemap fm = {0};
    fm.fm_length = ~0ULL; // 覆盖整个文件范围

    if (ioctl(fd, FS_IOC_FIEMAP, &fm) == -1) {
        return -1;
    }

    off_t max_end = 0;
    for (int i = 0; i < fm.fm_mapped_extents; i++) {
        struct fiemap_extent* fe = &fm.fm_extents[i];
        // 跳过空洞区间
        if (!(fe->fe_flags & FIEMAP_EXTENT_HOLE)) {
            off_t extent_end = fe->fe_logical + fe->fe_length;
            if (extent_end > max_end) {
                max_end = extent_end;
            }
        }
    }
    return max_end;
}

注意:tmpfs完全支持fiemap,但需确保调用进程有足够权限。

最不推荐方案:调用系统工具(无system())

若上述系统调用不可用,可通过execvp调用fallocate工具裁剪空洞,避免使用system():

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

int trim_file_via_fallocate(const char* path) {
    int fd = open(path, O_RDONLY);
    if (fd == -1) return -1;
    off_t actual_size = get_actual_data_size(fd);
    off_t total_size = lseek(fd, 0, SEEK_END);
    close(fd);
    if (actual_size == -1 || total_size == -1) return -1;

    char offset_str[32], length_str[32];
    snprintf(offset_str, sizeof(offset_str), "%lld", (long long)actual_size);
    snprintf(length_str, sizeof(length_str), "%lld", (long long)(total_size - actual_size));

    char* args[] = {"fallocate", "--collapse-range", "--offset", offset_str, "--length", length_str, path, NULL};
    execvp(args[0], args);
    // execvp仅在失败时返回
    return -1;
}

该方法依赖系统预装fallocate,可靠性不如系统调用。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 04:50:14