基于libfuse的C语言只读文件系统实现问题咨询
基于libfuse实现只读ext2文件系统的read函数解决方案
要实现读取ext2镜像中的实际文件,核心是直接解析ext2文件系统结构,通过e2fslibs库操作ext2镜像的inode、数据块等关键组件。以下是完整的实现代码及说明:
完整实现代码
#define FUSE_USE_VERSION 30 #include <fuse.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <time.h> #include <string.h> #include <stdlib.h> #include <ext2fs/ext2fs.h> static ext2_filsys fs; // 全局ext2文件系统句柄 // 转换FUSE路径为ext2兼容路径(去掉开头的/) static char* get_ext2_path(const char* fuse_path) { if (strcmp(fuse_path, "/") == 0) { return strdup("."); } return strdup(fuse_path + 1); } static int do_getattr(const char *path, struct stat *st) { char* ext2_path = get_ext2_path(path); ext2_ino_t ino; struct ext2_inode inode; int ret; // 查找路径对应的inode编号 ret = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, ext2_path, &ino); if (ret) { free(ext2_path); return -ret; } // 读取inode信息 ret = ext2fs_read_inode(fs, ino, &inode); if (ret) { free(ext2_path); return -ret; } // 填充stat结构 st->st_uid = inode.i_uid; st->st_gid = inode.i_gid; st->st_atime = inode.i_atime; st->st_mtime = inode.i_mtime; st->st_ctime = inode.i_ctime; st->st_nlink = inode.i_links_count; st->st_size = inode.i_size; if (LINUX_S_ISDIR(inode.i_mode)) { st->st_mode = S_IFDIR | (inode.i_mode & 0777); } else if (LINUX_S_ISREG(inode.i_mode)) { st->st_mode = S_IFREG | (inode.i_mode & 0777); } else { st->st_mode = inode.i_mode & 0777; } free(ext2_path); return 0; } static int do_readdir(const char *path, void *buffer, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { char* ext2_path = get_ext2_path(path); ext2_ino_t ino; struct ext2_dir_entry *de; struct ext2_dir_entry_2 *de2; char *buf; int ret, i; int blocksize = fs->blocksize; // 查找目录对应的inode ret = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, ext2_path, &ino); if (ret) { free(ext2_path); return -ret; } buf = malloc(blocksize); if (!buf) { free(ext2_path); return -ENOMEM; } // 遍历目录的所有直接数据块 for (i = 0; i < EXT2_NDIR_BLOCKS; i++) { if (fs->inode_table[ino-1].i_block[i] == 0) break; ret = ext2fs_read_block(fs, fs->inode_table[ino-1].i_block[i], buf); if (ret) { free(buf); free(ext2_path); return -ret; } // 遍历目录条目 de = (struct ext2_dir_entry *)buf; while ((char *)de < buf + blocksize) { if (de->inode == 0) { de = (struct ext2_dir_entry *)((char *)de + de->rec_len); continue; } // 兼容ext2两种目录条目格式 if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) { de2 = (struct ext2_dir_entry_2 *)de; filler(buffer, de2->name, NULL, 0); } else { filler(buffer, de->name, NULL, 0); } de = (struct ext2_dir_entry *)((char *)de + de->rec_len); } } free(buf); free(ext2_path); return 0; } static int do_read(const char *path, char *buffer, size_t size, off_t offset, struct fuse_file_info *fi) { char* ext2_path = get_ext2_path(path); ext2_ino_t ino; struct ext2_inode inode; int ret; int blocksize = fs->blocksize; char *block_buf = malloc(blocksize); size_t bytes_read = 0; size_t remaining = size; off_t current_offset = offset; if (!block_buf) { free(ext2_path); return -ENOMEM; } // 查找文件对应的inode ret = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, ext2_path, &ino); if (ret) { free(block_buf); free(ext2_path); return -ret; } // 读取inode信息 ret = ext2fs_read_inode(fs, ino, &inode); if (ret) { free(block_buf); free(ext2_path); return -ret; } // 偏移超出文件大小,直接返回0 if (offset >= inode.i_size) { free(block_buf); free(ext2_path); return 0; } // 计算实际可读取的字节数 if (offset + size > inode.i_size) { remaining = inode.i_size - offset; } // 读取数据块(仅处理直接块,可扩展支持间接块) while (remaining > 0) { int block_idx = current_offset / blocksize; int offset_in_block = current_offset % blocksize; size_t read_size = (remaining > blocksize - offset_in_block) ? (blocksize - offset_in_block) : remaining; // 超出直接块范围,返回不支持错误 if (block_idx >= EXT2_NDIR_BLOCKS) { free(block_buf); free(ext2_path); return -ENOSYS; } blk64_t block_num = inode.i_block[block_idx]; if (block_num == 0) break; // 读取整个数据块 ret = ext2fs_read_block(fs, block_num, block_buf); if (ret) { free(block_buf); free(ext2_path); return -ret; } // 复制指定内容到FUSE缓冲区 memcpy(buffer + bytes_read, block_buf + offset_in_block, read_size); bytes_read += read_size; remaining -= read_size; current_offset += read_size; } free(block_buf); free(ext2_path); return bytes_read; } static struct fuse_operations operations = { .getattr = do_getattr, .readdir = do_readdir, .read = do_read, }; int main(int argc, char *argv[]) { int ret; // 参数检查:需要传入ext2镜像路径和挂载点 if (argc < 3) { fprintf(stderr, "Usage: %s <ext2-image> <mount-point>\n", argv[0]); return 1; } // 打开ext2镜像文件 ret = ext2fs_open(argv[1], EXT2_FLAG_RW, 0, 0, unix_io_manager, &fs); if (ret) { fprintf(stderr, "Failed to open ext2 image: %s\n", strerror(-ret)); return 1; } // 调整参数,让fuse_main处理挂载点 argv[1] = argv[2]; argc--; // 启动FUSE文件系统 ret = fuse_main(argc, argv, &operations, NULL); // 关闭ext2文件系统句柄 ext2fs_close(fs); return ret; }
关键实现说明
ext2文件系统初始化
- 使用
ext2fs_open打开ext2镜像,获取全局文件系统句柄fs,后续所有操作基于该句柄。 - 编译时需链接e2fslibs库:
gcc -o ext2ro ext2ro.c -lfuse -le2fs
- 使用
路径转换
- FUSE路径以
/开头,而ext2fs的路径解析函数需要相对路径,通过get_ext2_path完成转换。
- FUSE路径以
getattr函数
- 通过
ext2fs_namei查找路径对应的inode编号,读取inode信息后映射到struct stat结构,返回文件/目录的元数据。
- 通过
readdir函数
- 读取目录inode的数据块,遍历其中的目录条目,通过
filler函数返回给FUSE,实现目录列表展示。
- 读取目录inode的数据块,遍历其中的目录条目,通过
read函数
- 根据文件inode的直接块信息,计算读取的块索引和块内偏移,读取对应数据块并复制到FUSE缓冲区,返回实际读取的字节数。
使用步骤
- 创建并格式化ext2镜像:
dd if=/dev/zero of=ext2.img bs=1M count=10 mkfs.ext2 ext2.img - 编译代码并挂载:
gcc -o ext2ro ext2ro.c -lfuse -le2fs sudo ./ext2ro ext2.img /mnt/ext2ro - 访问挂载点即可查看ext2镜像中的实际文件。
内容的提问来源于stack exchange,提问作者Ususi
相关产品推荐
相关产品推荐

