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

Ubuntu 20.04下FUSE挂载Zip时无法创建目录的问题

问题:Ubuntu 20.04下FUSE挂载含目录的Zip文件时,目录无法显示

在Ubuntu 20.04系统中,使用FUSE挂载Zip文件时,仅包含文件的Zip能正常处理,但含目录的Zip里的目录无法在挂载点显示。当前实现试图通过getattr回调标记S_IFDIR来让目录可见,但未生效。

实现流程:

  • main函数读取Zip条目名称,将目录和文件分别存入directoryNames和fileNames数组
  • 将Zip文件与挂载点参数传递给FUSE
  • 期望通过readdir回调传递路径至getattr,标记S_IFDIR使目录可见,但未生效

编译命令:

gcc -o programName filename -lzip -lfuse -D_FILE_OFFSET_BITS=64

运行方式:

./programName zipFile.zip mountPoint

完整原始代码

#define FUSE_USE_VERSION 30
#include <fuse.h>
#include <zip.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#define MAX_SIZE 100
zip_t *archive;

char directoryNames[255][255];
char fileNames[255][255];

int dirIndex = -1;
int fileIndex = -1;
static int do_getattr(const char *path, struct stat *st)
{
    if (strcmp(path, "/") == 0)
    {
        st->st_mode = S_IFDIR | 0777;
        st->st_nlink = 2;
    }
    else if (path[strlen(path) - 1] == '/')
    {
        st->st_mode = S_IFDIR | 0777;
        st->st_nlink = 1;
        st->st_size = 1024;
    }
    else
    {
        st->st_mode = S_IFREG | 0777;
        st->st_nlink = 1;
        st->st_size = 1024;
    }
    return 0;
}
static int readdir_callback(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
    (void)offset;
    (void)fi;
    filler(buf, ".", NULL, 0);
    filler(buf, "..", NULL, 0);
    if (strcmp(path, "/") == 0)
    {
        int num_directories = sizeof(directoryNames) / sizeof(directoryNames[0]);
        for (int i = 0; i < num_directories; i++)
        {
            if (directoryNames[i][0] != '\0')
            {
                printf("FILLED Directories %s\n", directoryNames[i]);
                filler(buf, directoryNames[i], NULL, 0);
            }
            else
            {
                break;
            }
        }
        int num_files = sizeof(fileNames) / sizeof(fileNames[0]);
        for (int k = 0; k < num_files; k++)
        {
            if (fileNames[k][0] != '\0')
            {
                filler(buf, fileNames[k], NULL, 0);
                //   printf("FILLED Files %s\n", fileNames[k]);
            }
            else
            {
                break;
            }
        }
    }
    return 0;
}
static int do_read(const char *path, char *buffer, size_t size, off_t offset, struct fuse_file_info *fi)
{
    int num_entries = zip_get_num_entries(archive, 0);
    for (int i = 0; i < num_entries; i++)
    {
        const char *entry_name = zip_get_name(archive, i, 0);
        char name[MAX_SIZE];
        snprintf(name, MAX_SIZE, "/%s", entry_name);
        if (strcmp(path, name) == 0)
        {
            struct zip_file *entry_file = zip_fopen_index(archive, i, 0);
            if (entry_file == NULL)
            {
                printf("Failed to open entry file '%s'\n", entry_name);
                continue;
            }
            long fileSize = 1024;
            char *content = (char *)malloc(fileSize + 1);
            zip_int64_t bytes_read = zip_fread(entry_file, content, fileSize);
            memcpy(buffer, content + offset, size);
            return strlen(content) - offset;
        }
    }
    return 0;
}
static struct fuse_operations operations = {
    .getattr = do_getattr,
    .readdir = readdir_callback,
    .read = do_read,
};
int main(int argc, char **argv)
{
    if (argc < 3)
    {
        printf("not enought input arguments");
        return 0;
    }
    int error;
    archive = zip_open(argv[1], ZIP_RDONLY, &error);
    if (archive == NULL)
    {
        printf("Failed to open: %s\n", argv[1]);
        return 0;
    }

    memset(directoryNames, 0, sizeof(directoryNames));
    memset(fileNames, 0, sizeof(fileNames));
    int num_entries = zip_get_num_entries(archive, 0);
    for (int i = 0; i < num_entries; i++)
    {
        const char *entry_name = zip_get_name(archive, i, 0);
        if (entry_name[strlen(entry_name) - 1] == '/')
        {
            char dirName[255];

            dirIndex++;
            strcpy(dirName, entry_name);
            strcpy(directoryNames[dirIndex], dirName);
        }
        else
        {
            char fileName[255];
            fileIndex++;
            strcpy(fileName, entry_name);
            strcpy(fileNames[fileIndex], fileName);
        }
    }
    return fuse_main(argc - 1, argv + 1, &operations, NULL);
}

问题根源

  1. getattr路径匹配逻辑错误:FUSE传入的目录路径(如/testdir)不带末尾斜杠,当前代码用path[strlen(path)-1] == '/'判断目录的逻辑完全失效,导致目录被误判为普通文件。
  2. 目录名称存储格式不匹配:从Zip中读取的目录名称带末尾斜杠(如testdir/),但FUSE传递给getattr的路径是/testdir,两者无法匹配。
  3. readdir仅支持根目录:没有实现子目录的内容读取逻辑,即使目录能显示,进入子目录也会为空。
  4. do_read存在内存泄漏和大小硬编码问题:未关闭打开的Zip文件,且固定读取1024字节,无法处理大文件。

修复方案

1. 修正目录名称存储逻辑

在main函数中,存储目录名称时去掉末尾斜杠,确保和FUSE路径格式一致:

if (entry_name[strlen(entry_name) - 1] == '/')
{
    dirIndex++;
    // 去掉末尾斜杠后存储
    strncpy(directoryNames[dirIndex], entry_name, strlen(entry_name)-1);
    directoryNames[dirIndex][strlen(entry_name)-1] = '\0';
}

2. 重构getattr路径匹配逻辑

遍历存储的目录和文件列表,精确匹配路径并返回正确的stat信息:

static int do_getattr(const char *path, struct stat *st)
{
    memset(st, 0, sizeof(struct stat));
    if (strcmp(path, "/") == 0)
    {
        st->st_mode = S_IFDIR | 0777;
        st->st_nlink = 2;
        st->st_size = 1024;
        st->st_mtime = time(NULL);
        return 0;
    }

    // 检查是否为目录
    const char *dir_path = path + 1;
    for (int i = 0; i <= dirIndex; i++)
    {
        if (strcmp(directoryNames[i], dir_path) == 0)
        {
            st->st_mode = S_IFDIR | 0777;
            st->st_nlink = 2;
            st->st_size = 1024;
            st->st_mtime = time(NULL);
            return 0;
        }
    }

    // 检查是否为文件
    const char *file_path = path + 1;
    zip_int64_t idx = zip_name_locate(archive, file_path, 0);
    if (idx != -1)
    {
        struct zip_stat stat;
        zip_stat_init(&stat);
        zip_stat_index(archive, idx, 0, &stat);
        st->st_mode = S_IFREG | 0777;
        st->st_nlink = 1;
        st->st_size = stat.size;
        st->st_mtime = stat.mtime;
        return 0;
    }

    return -ENOENT; // 路径不存在时返回标准错误码
}

3. 实现子目录readdir逻辑

修改readdir_callback,支持读取子目录下的文件和子目录:

static int readdir_callback(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
    (void)offset;
    (void)fi;
    filler(buf, ".", NULL, 0);
    filler(buf, "..", NULL, 0);

    if (strcmp(path, "/") == 0)
    {
        // 添加顶层目录
        for (int i = 0; i <= dirIndex; i++)
        {
            if (directoryNames[i][0] != '\0')
            {
                filler(buf, directoryNames[i], NULL, 0);
            }
        }
        // 添加顶层文件
        for (int i = 0; i <= fileIndex; i++)
        {
            if (fileNames[i][0] != '\0' && strchr(fileNames[i], '/') == NULL)
            {
                filler(buf, fileNames[i], NULL, 0);
            }
        }
    }
    else
    {
        // 处理子目录,比如/testdir
        const char *dir_prefix = path + 1;
        size_t prefix_len = strlen(dir_prefix);
        char full_prefix[256];
        snprintf(full_prefix, sizeof(full_prefix), "%s/", dir_prefix);

        int num_entries = zip_get_num_entries(archive, 0);
        for (int i = 0; i < num_entries; i++)
        {
            const char *entry_name = zip_get_name(archive, i, 0);
            if (strncmp(entry_name, full_prefix, prefix_len + 1) == 0)
            {
                const char *relative_path = entry_name + prefix_len + 1;
                // 处理子目录
                if (relative_path[strlen(relative_path)-1] == '/')
                {
                    char *slash_pos = strchr(relative_path, '/');
                    if (slash_pos == relative_path) continue;
                    char subdir[256];
                    if (slash_pos != NULL)
                    {
                        strncpy(subdir, relative_path, slash_pos - relative_path);
                        subdir[slash_pos - relative_path] = '\0';
                    }
                    else
                    {
                        strcpy(subdir, relative_path);
                    }
                    filler(buf, subdir, NULL, 0);
                }
                // 处理当前目录下的文件
                else if (strchr(relative_path, '/') == NULL)
                {
                    filler(buf, relative_path, NULL, 0);
                }
            }
        }
    }
    return 0;
}

4. 修复do_read的内存泄漏和大小问题

static int do_read(const char *path, char *buffer, size_t size, off_t offset, struct fuse_file_info *fi)
{
    const char *entry_name = path + 1;
    zip_int64_t idx = zip_name_locate(archive, entry_name, 0);
    if (idx == -1)
    {
        return -ENOENT;
    }

    struct zip_stat stat;
    zip_stat_init(&stat);
    if (zip_stat_index(archive, idx, 0, &stat) != 0)
    {
        return -EIO;
    }

    if (offset >= stat.size)
    {
        return 0;
    }

    size_t read_size = size;
    if (offset + read_size > stat.size)
    {
        read_size = stat.size - offset;
    }

    struct zip_file *entry_file = zip_fopen_index(archive, idx, 0);
    if (entry_file == NULL)
    {
        return -EIO;
    }

    if (zip_fseek(entry_file, offset, SEEK_SET) != 0)
    {
        zip_fclose(entry_file);
        return -EIO;
    }

    zip_int64_t bytes_read = zip_fread(entry_file, buffer, read_size);
    zip_fclose(entry_file);

    if (bytes_read < 0)
    {
        return -EIO;
    }

    return bytes_read;
}

内容的提问来源于stack exchange,提问作者Michał Szczepaniak

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 08:27:06