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

使用memcpy与mmap多线程复制目录文件时输出仅1Byte问题求助

问题产生原因
  • 文件名被意外篡改:代码中strcat(filename, "copy")直接修改了指向源文件名的指针,后续打开源文件时实际尝试打开的是拼接后的目标文件名,此时目标文件还未创建,源文件打开失败,fstat读取到的文件大小为无效值。
  • 目录项缓冲区复用问题:readdir返回的struct dirent内存是C库内部复用的,直接将d_name指针传给子线程时,主循环后续的readdir调用会覆盖原有内容,子线程拿到的文件名大概率是错误的。
  • 无错误检查逻辑:open、fstat等系统调用失败后没有做拦截,后续逻辑基于无效的文件描述符执行,获取到的文件大小异常,最终写入后只有你主动填充的1字节内容,和你遇到的现象完全吻合。
  • 未过滤特殊目录项:readdir会返回.和..两个特殊目录,尝试打开目录作为普通文件读取必然失败。
  • 缓冲区溢出风险:直接向d_name指向的固定大小缓冲区拼接字符串,会触发缓冲区溢出,属于未定义行为,可能引发各种奇怪问题。
  • 路径拼接缺失:opendir打开的是你传入的指定路径的目录,直接打开d_name只会在当前工作目录查找文件,若运行程序的目录和你要处理的目录不一致,会直接找不到文件。
  • 资源泄漏:打开的源文件描述符未关闭,mmap映射的内存未释放,会逐渐耗尽系统资源,多线程运行时尤其明显。
修复方案
  • 单独分配内存存储源文件完整路径和目标文件名,不修改原始d_name内容。
  • 主循环中过滤.和..特殊目录项,仅处理普通文件。
  • 拼接目录路径和文件名,保证能正确找到目标目录下的文件。
  • 为每个线程独立拷贝文件名,避免readdir缓冲区复用导致的内容覆盖。
  • 增加所有系统调用的错误检查,异常时直接打印错误并返回。
  • 补充资源释放逻辑,关闭所有打开的文件描述符,释放mmap映射和动态分配的内存。
  • 调整目标文件名生成逻辑,单独拼接不修改源文件名变量。
修复后完整代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>

void* copyFile(void* arg) {
    char *srcpath = (char*)arg;
    // 单独分配目标文件名内存,不修改源路径
    int path_len = strlen(srcpath);
    char *destpath = malloc(path_len + 10); // 留足够空间存.copy后缀
    if (!destpath) {
        perror("malloc destpath failed");
        free(srcpath);
        return NULL;
    }
    snprintf(destpath, path_len + 10, "%s.copy", srcpath);

    // 打开源文件,增加错误检查
    int in = open(srcpath, O_RDONLY);
    if (in < 0) {
        perror("open src file failed");
        free(srcpath);
        free(destpath);
        return NULL;
    }

    // 打开目标文件
    int out = open(destpath, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    if (out < 0) {
        perror("open dest file failed");
        close(in);
        free(srcpath);
        free(destpath);
        return NULL;
    }

    // 获取源文件大小
    struct stat statbuf;
    if (fstat(in, &statbuf) < 0) {
        perror("fstat failed");
        close(in);
        close(out);
        free(srcpath);
        free(destpath);
        return NULL;
    }
    off_t insize = statbuf.st_size;
    // 空文件不需要后续处理
    if (insize == 0) {
        close(in);
        close(out);
        free(srcpath);
        free(destpath);
        return NULL;
    }

    // 扩展目标文件大小
    if (lseek(out, insize - 1, SEEK_SET) < 0) {
        perror("lseek failed");
        close(in);
        close(out);
        free(srcpath);
        free(destpath);
        return NULL;
    }
    if (write(out, "", 1) < 0) {
        perror("write failed");
        close(in);
        close(out);
        free(srcpath);
        free(destpath);
        return NULL;
    }

    // 映射源文件
    char* inmap = mmap(0, insize, PROT_READ, MAP_FILE | MAP_PRIVATE, in, 0);
    if (inmap == MAP_FAILED) {
        perror("mmap infile failed");
        close(in);
        close(out);
        free(srcpath);
        free(destpath);
        return NULL;
    }

    // 映射目标文件
    char* outmap = mmap(0, insize, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, out, 0);
    if (outmap == MAP_FAILED) {
        perror("mmap outfile failed");
        munmap(inmap, insize);
        close(in);
        close(out);
        free(srcpath);
        free(destpath);
        return NULL;
    }

    // 拷贝内容
    memcpy(outmap, inmap, insize);

    // 释放资源
    munmap(inmap, insize);
    munmap(outmap, insize);
    close(in);
    close(out);
    free(srcpath);
    free(destpath);
    return NULL;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("usage: %s <target_dir>\n", argv[0]);
        return 1;
    }

    DIR* d = opendir(argv[1]);
    if (!d) {
        perror("opendir failed");
        return 1;
    }

    int i = 0;
    pthread_t threads[50];
    struct dirent* e;

    while((e = readdir(d)) != NULL) {
        // 过滤.和..,只处理普通文件
        if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0 || e->d_type != DT_REG) {
            continue;
        }
        // 拼接完整路径,避免当前工作目录不对找不到文件
        int dir_len = strlen(argv[1]);
        int name_len = strlen(e->d_name);
        char *srcpath = malloc(dir_len + name_len + 2); // 留位置给/和结束符
        if (!srcpath) {
            perror("malloc srcpath failed");
            continue;
        }
        snprintf(srcpath, dir_len + name_len + 2, "%s/%s", argv[1], e->d_name);

        printf("processing %s\n", srcpath);
        // 传入单独分配的路径,避免缓冲区复用问题
        if (pthread_create(&threads[i], NULL, copyFile, srcpath) != 0) {
            perror("pthread_create failed");
            free(srcpath);
            continue;
        }
        i++;
        // 避免超过线程数组上限
        if (i >= 50) break;
    }

    for(int j = 0; j < i; j++) {
        pthread_join(threads[j], NULL);
    }

    closedir(d);
    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 16:15:03