基于 FUSE 的简单文件系统实现
源码
#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 <sys/stat.h>
#include <errno.h>
/*
* 编译和挂载文件系统说明
*
* 1. 编译文件系统:
* 使用以下命令编译文件系统代码:
* gcc FS.c -o FS `pkg-config fuse --cflags --libs`
*
* 参数说明:
* - gcc FS.c: 编译 FS.c 文件。
* - -o FS: 指定输出文件名为 FS。
* - `pkg-config fuse --cflags --libs`: 自动获取 FUSE 库的编译和链接参数。
*
* 2. 挂载文件系统:
* 使用以下命令挂载文件系统:
* ./FS -f Desktop/OS/mountpoint4
*
* 参数说明:
* - ./FS: 运行编译生成的文件系统程序。
* - -f: 在前台运行文件系统(调试模式),程序会输出日志信息。
* - Desktop/OS/mountpoint4: 挂载点路径,文件系统将挂载到该目录。
*
* 3. 挂载后的操作:
* 挂载成功后,可以通过挂载点路径(如 Desktop/OS/mountpoint4)访问文件系统:
* - 创建文件: touch Desktop/OS/mountpoint4/test.txt
* - 读取文件: cat Desktop/OS/mountpoint4/test.txt
* - 删除文件: rm Desktop/OS/mountpoint4/test.txt
*
* 4. 卸载文件系统:
* 使用以下命令卸载文件系统:
* fusermount -u Desktop/OS/mountpoint4
*
* 参数说明:
* - fusermount -u: 卸载文件系统。
* - Desktop/OS/mountpoint4: 挂载点路径。
*
* 5. 调试模式:
* 如果使用 -f 参数在前台运行文件系统,程序会输出日志信息,方便调试。例如:
* OPEN /test.txt
* READ /test.txt
* CLOSE /test.txt
*
* 注意事项:
* 1. 确保挂载点路径存在且为空目录。
* 2. 如果挂载失败,检查是否有权限问题或路径错误。
* 3. 调试完成后,可以去掉 -f 参数,让文件系统在后台运行。
*/
#define block_size 1024
/*
* superblock - 文件系统超级块结构
*
* 功能:
* 1. 存储文件系统的全局元数据信息。
* 2. 管理数据块和 inode 的分配状态。
*
* 字段说明:
* - datablocks: 数据块数组,存储文件系统的所有数据块。
* - data_bitmap: 数据块位图,标识哪些数据块已被占用('1')或空闲('0')。
* - inode_bitmap: inode 位图,标识哪些 inode 已被占用('1')或空闲('0')。
*
* 示例:
* 假设文件系统刚初始化,调用 initialize_superblock() 后:
* - spblock.data_bitmap 的所有位均为 '0',表示所有数据块空闲。
* - spblock.inode_bitmap 的所有位均为 '0',表示所有 inode 空闲。
*
* 注意:
* - 超级块是文件系统的核心数据结构,必须在文件系统初始化时调用 initialize_superblock() 方法。
* - 数据块位图和 inode 位图的大小固定为 100 个字节。
*/
typedef struct superblock
{
char datablocks[block_size * 100]; // 数据块数组,存储文件系统的所有数据块
char data_bitmap[105]; // 数据块位图,标识数据块的占用状态
char inode_bitmap[105]; // inode 位图,标识 inode 的占用状态
} superblock;
/*
* inode - 文件系统索引节点结构
*
* 功能:
* 1. 存储文件或目录的元数据信息。
* 2. 管理文件数据块的分配和引用。
*
* 字段说明:
* - datablocks: 数据块编号数组,存储文件数据所在的数据块。
* - number: inode 的唯一标识符。
* - blocks: 文件占用的数据块数量。
* - size: 文件或目录的大小(以字节为单位)。
*
* 示例:
* 假设文件系统结构如下:
* /
* ├── home
* │ └── user
* └── test.txt
*
* inode 示例:
* inode (test.txt):
* - datablocks: [5, 6, 7] // 文件数据存储在数据块 5、6、7 中
* - number: 3 // inode 编号为 3
* - blocks: 3 // 文件占用 3 个数据块
* - size: 3072 // 文件大小为 3072 字节
*
* 注意:
* - inode 是文件系统的核心数据结构,用于管理文件的元数据和数据块。
* - 每个文件或目录都有一个唯一的 inode。
*/
typedef struct inode
{
int datablocks[16]; // 数据块编号数组,存储文件数据所在的数据块
int number; // inode 的唯一标识符
int blocks; // 文件占用的数据块数量
// int link; //==number of links
int size; // 文件或目录的大小(以字节为单位)
} inode;
/*
* filetype - 文件/目录元数据结构
*
* 功能:
* 1. 存储文件或目录的元数据信息。
* 2. 用于管理文件系统的树形结构。
*
* 字段说明:
* - valid: 标识节点是否有效(1 表示有效,0 表示无效)。
* - test: 保留字段,未使用。
* - path: 文件或目录的完整路径。
* - name: 文件或目录的名称。
* - inum: 指向关联的 inode 结构。
* - children: 子节点指针数组,用于存储目录的子文件或子目录。
* - num_children: 子节点数量。
* - num_links: 硬链接数。
* - parent: 指向父目录的指针。
* - type: 文件类型(如 "file" 或 "directory")。
* - permissions: 文件或目录的权限模式(如 0777)。
* - user_id: 文件或目录的用户 ID。
* - group_id: 文件或目录的组 ID。
* - a_time: 最后访问时间。
* - m_time: 最后修改时间。
* - c_time: 最后状态更改时间。
* - b_time: 创建时间。
* - size: 文件或目录的大小(以字节为单位)。
* - datablocks: 数据块编号数组,存储文件数据所在的数据块。
* - number: 文件或目录的编号(唯一标识)。
* - blocks: 文件占用的数据块数量。
*
* 示例:
* 假设文件系统结构如下:
* /
* ├── home
* │ └── user
* └── test.txt
*
* 文件类型示例:
* Filetype (Root):
* - valid: 1
* - path: "/"
* - name: "/"
* - type: "directory"
* - permissions: 0777
* - user_id: 1000
* - group_id: 1000
* - a_time: 1698765432
* - m_time: 1698765432
* - c_time: 1698765432
* - b_time: 1698765432
* - size: 4096
* - number: 1
* - blocks: 1
* - children: [Filetype (home), Filetype (test.txt)]
* - num_children: 2
* - parent: NULL
* - num_links: 2
*
* 注意:
* - 文件类型和目录类型使用相同的结构体。
* - 目录的 size 字段通常表示目录元数据的大小。
*/
typedef struct filetype
{
int valid; // 标识节点是否有效
char test[10]; // 保留字段,未使用
char path[100]; // 文件或目录的完整路径
char name[100]; // 文件或目录的名称
inode *inum; // 指向关联的 inode 结构
struct filetype **children; // 子节点指针数组
int num_children; // 子节点数量
int num_links; // 硬链接数
struct filetype *parent; // 指向父目录的指针
char type[20]; // 文件类型(如 "file" 或 "directory")
mode_t permissions; // 文件或目录的权限模式
uid_t user_id; // 用户 ID
gid_t group_id; // 组 ID
time_t a_time; // 最后访问时间
time_t m_time; // 最后修改时间
time_t c_time; // 最后状态更改时间
time_t b_time; // 创建时间
off_t size; // 文件或目录的大小
int datablocks[16]; // 数据块编号数组
int number; // 文件或目录的编号
int blocks; // 文件占用的数据块数量
} filetype;
superblock spblock;
/*
* initialize_superblock - 初始化超级块
*
* 功能:
* 1. 初始化文件系统的超级块结构。
* 2. 将数据块位图和 inode 位图初始化为全 0,表示所有数据块和 inode 均为空闲状态。
*
* 参数:
* - 无。
*
* 返回值:
* - 无。
*
* 实现逻辑:
* 1. 使用 memset 将数据块位图(spblock.data_bitmap)初始化为全 0。
* 2. 使用 memset 将 inode 位图(spblock.inode_bitmap)初始化为全 0。
*
* 示例:
* 假设文件系统刚创建,调用 initialize_superblock() 后:
* - spblock.data_bitmap 的所有位均为 0,表示所有数据块空闲。
* - spblock.inode_bitmap 的所有位均为 0,表示所有 inode 空闲。
*
* 注意:
* - 超级块是文件系统的核心数据结构,必须在文件系统初始化时调用此方法。
* - 数据块位图和 inode 位图的大小固定为 100 个字节。
*/
void initialize_superblock()
{
memset(spblock.data_bitmap, '0', 100 * sizeof(char));
memset(spblock.inode_bitmap, '0', 100 * sizeof(char));
}
filetype *root;
filetype file_array[50];
/*
* tree_to_array - 将文件树结构序列化为数组
*
* 功能:
* 1. 通过广度优先遍历(BFS)将文件树结构扁平化为数组。
* 2. 将树中的每个节点按遍历顺序存储到数组中。
* 3. 使用无效节点(valid = 0)填充空位,确保数组长度固定。
*
* 参数:
* - queue: 用于广度优先遍历的队列,存储待处理的节点。
* - front: 队列的起始索引,指向当前处理的节点。
* - rear: 队列的结束索引,指向下一个可插入的位置。
* - index: 当前数组的索引,指向下一个可存储的位置。
*
* 实现逻辑:
* 1. 从队列中取出当前节点(queue[*front]),并将其存储到数组(file_array[*index])中。
* 2. 如果当前节点有效(valid = 1),将其子节点加入队列。
* 3. 如果当前节点无效或子节点不足 5 个,用无效节点填充队列。
* 4. 递归处理队列中的下一个节点,直到队列为空或数组已满。
*
* 示例:
* 假设文件树结构如下:
* /
* ├── home
* │ └── user
* └── test.txt
*
* 序列化后的数组布局如下:
* Filetype 0 (Root):
* - valid: 1
* - path: "/"
* - name: "/"
* - type: "directory"
* - children: [Filetype 1, Filetype 2]
*
* Filetype 1 (home):
* - valid: 1
* - path: "/home"
* - name: "home"
* - type: "directory"
* - children: [Filetype 3]
*
* Filetype 2 (test.txt):
* - valid: 1
* - path: "/test.txt"
* - name: "test.txt"
* - type: "file"
* - children: []
*
* Filetype 3 (user):
* - valid: 1
* - path: "/home/user"
* - name: "user"
* - type: "directory"
* - children: []
*
* Filetype 4-30:
* - valid: 0 (无效节点,用于占位)
*
* 注意:
* - 数组长度固定为 31,包括无效节点。
* - 每个节点最多有 5 个子节点。
*/
void tree_to_array(filetype *queue, int *front, int *rear, int *index)
{
if (rear < front)
return;
if (*index > 30)
return;
filetype curr_node = queue[*front];
*front += 1;
file_array[*index] = curr_node;
*index += 1;
if (*index < 6)
{
if (curr_node.valid)
{
int n = 0;
int i;
for (i = 0; i < curr_node.num_children; i++)
{
if (*rear < *front)
*rear = *front;
queue[*rear] = *(curr_node.children[i]);
*rear += 1;
}
while (i < 5)
{
filetype waste_node;
waste_node.valid = 0;
queue[*rear] = waste_node;
*rear += 1;
i++;
}
}
else
{
int i = 0;
while (i < 5)
{
filetype waste_node;
waste_node.valid = 0;
queue[*rear] = waste_node;
*rear += 1;
i++;
}
}
}
tree_to_array(queue, front, rear, index);
}
/*
* save_contents - 保存文件系统内容到磁盘
*
* 功能:
* 1. 将内存中的文件树结构通过广度优先遍历序列化为数组。
* 2. 将序列化后的数组和超级块分别保存到 `file_structure.bin` 和 `super.bin` 文件中。
*
* 文件布局:
* - `file_structure.bin` 存储文件树的所有节点信息,按广度优先遍历顺序扁平化为数组。
* - 每个节点包含文件/目录的元数据,如路径、名称、类型、权限、时间戳等。
* - 无效节点(valid = 0)用于占位,确保数组长度固定。
* - 在内存中,通过 children 指针重建树结构。
* - `super.bin` 存储超级块信息,包括数据块和 inode 的位图。
*
* 示例:
* 假设文件系统结构如下:
* /
* ├── home
* │ └── user
* └── test.txt
*
* `file_structure.bin` 的布局可能如下:
* Filetype 0 (Root):
* - valid: 1
* - path: "/"
* - name: "/"
* - type: "directory"
* - children: [Filetype 1, Filetype 2]
*
* Filetype 1 (home):
* - valid: 1
* - path: "/home"
* - name: "home"
* - type: "directory"
* - children: [Filetype 3]
*
* Filetype 2 (test.txt):
* - valid: 1
* - path: "/test.txt"
* - name: "test.txt"
* - type: "file"
* - children: []
*
* Filetype 3 (user):
* - valid: 1
* - path: "/home/user"
* - name: "user"
* - type: "directory"
* - children: []
*
* Filetype 4-30:
* - valid: 0 (无效节点,用于占位)
*
* 流程:
* 1. 初始化队列,将根节点加入队列。
* 2. 通过广度优先遍历将文件树序列化为数组。
* 3. 将序列化后的数组写入 `file_structure.bin`。
* 4. 将超级块写入 `super.bin`。
*
* 注意:
* - 文件树最多支持 31 个节点(包括无效节点)。
* - 每个节点最多有 5 个子节点。
*/
int save_contents()
{
printf("SAVING\n");
filetype *queue = malloc(sizeof(filetype) * 60);
int front = 0;
int rear = 0;
queue[0] = *root;
int index = 0;
tree_to_array(queue, &front, &rear, &index);
for (int i = 0; i < 31; i++)
{
printf("%d", file_array[i].valid);
}
FILE *fd = fopen("file_structure.bin", "wb");
FILE *fd1 = fopen("super.bin", "wb");
fwrite(file_array, sizeof(filetype) * 31, 1, fd);
fwrite(&spblock, sizeof(superblock), 1, fd1);
fclose(fd);
fclose(fd1);
printf("\n");
}
/*
* initialize_root_directory - 初始化根目录
*
* 功能:
* 1. 创建并初始化文件系统的根目录。
* 2. 设置根目录的元数据,包括路径、名称、类型、权限、时间戳等。
* 3. 标记根目录的 inode 为已使用。
* 4. 调用 save_contents 方法将初始化后的文件系统保存到磁盘。
*
* 实现逻辑:
* 1. 在 inode 位图中标记根目录的 inode 为已使用(spblock.inode_bitmap[1] = 1)。
* 2. 分配内存并初始化根目录结构(filetype)。
* 3. 设置根目录的路径为 "/",名称为 "/"。
* 4. 设置根目录的类型为 "directory",权限为 0777。
* 5. 初始化时间戳(创建时间、访问时间、修改时间等)。
* 6. 设置根目录的 inode 编号为 2。
* 7. 调用 save_contents 方法保存文件系统。
*
* 初始化后的根目录结构示例:
* Filetype (Root):
* - valid: 1
* - path: "/"
* - name: "/"
* - type: "directory"
* - permissions: 0777
* - user_id: 当前用户ID
* - group_id: 当前组ID
* - a_time: 当前时间
* - m_time: 当前时间
* - c_time: 当前时间
* - b_time: 当前时间
* - size: 0
* - number: 2 (inode 编号)
* - blocks: 0
* - children: NULL
* - num_children: 0
* - parent: NULL
* - num_links: 2
*
* 注意:
* - 根目录的 inode 编号固定为 2。
* - 根目录是文件系统的起点,所有其他文件和目录都是其子节点。
*/
void initialize_root_directory()
{
spblock.inode_bitmap[1] = 1; // marking it with 0
root = (filetype *)malloc(sizeof(filetype));
strcpy(root->path, "/");
strcpy(root->name, "/");
root->children = NULL;
root->num_children = 0;
root->parent = NULL;
root->num_links = 2;
root->valid = 1;
strcpy(root->test, "test");
// root -> type = malloc(10);
strcpy(root->type, "directory");
root->c_time = time(NULL);
root->a_time = time(NULL);
root->m_time = time(NULL);
root->b_time = time(NULL);
root->permissions = S_IFDIR | 0777;
root->size = 0;
root->group_id = getgid();
root->user_id = getuid();
root->number = 2;
// root -> size = 0;
root->blocks = 0;
save_contents();
}
/*
* filetype_from_path - 根据路径查找对应的文件节点
*
* 功能:
* 1. 根据给定的路径,在文件树中查找对应的文件或目录节点。
* 2. 支持绝对路径(以 "/" 开头)的解析。
* 3. 如果路径不存在,返回 NULL。
*
* 参数:
* - path: 要查找的路径字符串(必须以 "/" 开头)。
*
* 返回值:
* - 成功时返回对应的文件节点指针(filetype *)。
* - 如果路径不存在或路径格式错误,返回 NULL。
*
* 实现逻辑:
* 1. 检查路径是否以 "/" 开头,如果不是则报错并退出。
* 2. 从根节点开始,逐级解析路径中的目录名。
* 3. 在当前节点的子节点中查找匹配的目录或文件。
* 4. 如果找到匹配的节点,返回该节点;否则返回 NULL。
*
* 示例:
* 假设文件树结构如下:
* /
* ├── home
* │ └── user
* └── test.txt
*
* 调用示例:
* 1. filetype_from_path("/") -> 返回根节点
* 2. filetype_from_path("/home") -> 返回 home 目录节点
* 3. filetype_from_path("/home/user") -> 返回 user 目录节点
* 4. filetype_from_path("/test.txt") -> 返回 test.txt 文件节点
* 5. filetype_from_path("/invalid") -> 返回 NULL
*
* 注意:
* - 路径必须以 "/" 开头。
* - 路径末尾的 "/" 会被自动去除。
* - 如果路径不存在,返回 NULL。
*/
filetype *filetype_from_path(char *path)
{
char curr_folder[100];
char *path_name = malloc(strlen(path) + 2);
strcpy(path_name, path);
filetype *curr_node = root;
fflush(stdin);
if (strcmp(path_name, "/") == 0)
return curr_node;
if (path_name[0] != '/')
{
printf("INCORRECT PATH\n");
exit(1);
}
else
{
path_name++;
}
if (path_name[strlen(path_name) - 1] == '/')
{
path_name[strlen(path_name) - 1] = '\0';
}
char *index;
int flag = 0;
while (strlen(path_name) != 0)
{
index = strchr(path_name, '/');
if (index != NULL)
{
strncpy(curr_folder, path_name, index - path_name);
curr_folder[index - path_name] = '\0';
flag = 0;
for (int i = 0; i < curr_node->num_children; i++)
{
if (strcmp((curr_node->children)[i]->name, curr_folder) == 0)
{
curr_node = (curr_node->children)[i];
flag = 1;
break;
}
}
if (flag == 0)
return NULL;
}
else
{
strcpy(curr_folder, path_name);
flag = 0;
for (int i = 0; i < curr_node->num_children; i++)
{
if (strcmp((curr_node->children)[i]->name, curr_folder) == 0)
{
curr_node = (curr_node->children)[i];
return curr_node;
}
}
return NULL;
}
path_name = index + 1;
}
}
/*
* find_free_inode - 查找空闲的 inode
*
* 功能:
* 1. 查找当前文件系统中空闲的 inode 编号。
* 2. 用于创建新文件或目录时分配 inode。
*
* 参数:
* - 无。
*
* 返回值:
* - 成功时返回空闲的 inode 编号。
* - 如果没有空闲 inode,返回 -1。
*
* 实现逻辑:
* 1. 遍历 inode 表,查找第一个空闲的 inode。
* 2. 返回找到的 inode 编号。
*
* 注意:
* - 如果 inode 表已满,需扩展文件系统。
*/
int find_free_inode()
{
for (int i = 2; i < 100; i++)
{
if (spblock.inode_bitmap[i] == '0')
{
spblock.inode_bitmap[i] = '1';
}
return i;
}
}
/*
* find_free_db - 查找空闲的数据块
*
* 功能:
* 1. 查找当前文件系统中空闲的数据块编号。
* 2. 用于存储文件数据时分配数据块。
*
* 参数:
* - 无。
*
* 返回值:
* - 成功时返回空闲的数据块编号。
* - 如果没有空闲数据块,返回 -1。
*
* 实现逻辑:
* 1. 遍历数据块位图,查找第一个空闲的数据块。
* 2. 返回找到的数据块编号。
*
* 注意:
* - 如果数据块已满,需扩展文件系统。
*/
int find_free_db()
{
for (int i = 1; i < 100; i++)
{
if (spblock.inode_bitmap[i] == '0')
{
spblock.inode_bitmap[i] = '1';
}
return i;
}
}
/*
* add_child - 添加子节点
*
* 功能:
* 1. 将指定节点添加到父目录的子节点列表中。
* 2. 更新父目录的子节点数量和列表。
*
* 参数:
* - parent: 父目录节点。
* - child: 要添加的子节点。
*
* 返回值:
* - 无。
*
* 实现逻辑:
* 1. 扩展父目录的子节点列表(如果需要)。
* 2. 将子节点添加到列表中。
* 3. 更新父目录的子节点数量。
*
* 注意:
* - 确保父目录是目录类型。
*/
void add_child(filetype *parent, filetype *child)
{
(parent->num_children)++;
parent->children = realloc(parent->children, (parent->num_children) * sizeof(filetype *));
(parent->children)[parent->num_children - 1] = child;
}
/*
* mymkdir - 创建新目录
*
* 功能:
* 1. 在指定路径下创建一个新目录。
* 2. 分配并初始化新目录的元数据,包括路径、名称、类型、权限、时间戳等。
* 3. 将新目录添加到父目录的子节点列表中。
* 4. 调用 save_contents 方法将更新后的文件系统保存到磁盘。
*
* 参数:
* - path: 新目录的完整路径(必须以 "/" 开头)。
* - mode: 目录的权限模式(未直接使用,固定为 0777)。
*
* 返回值:
* - 成功时返回 0。
* - 如果父目录不存在,返回 -ENOENT。
*
* 实现逻辑:
* 1. 查找一个空闲的 inode 编号。
* 2. 解析路径,获取新目录的名称和父目录路径。
* 3. 分配内存并初始化新目录结构(filetype)。
* 4. 设置新目录的元数据,包括路径、名称、类型、权限、时间戳等。
* 5. 将新目录添加到父目录的子节点列表中。
* 6. 调用 save_contents 方法保存文件系统。
*
* 示例:
* 假设文件系统结构如下:
* /
* └── home
*
* 调用示例:
* 1. mymkdir("/home/user", 0777) -> 在 /home 下创建 user 目录
* - 新目录路径: "/home/user"
* - 新目录名称: "user"
* - 父目录: "/home"
* 2. mymkdir("/invalid/path", 0777) -> 返回 -ENOENT(父目录不存在)
*
* 注意:
* - 路径必须以 "/" 开头。
* - 新目录的权限固定为 0777。
* - 如果父目录不存在,操作失败。
*/
static int mymkdir(const char *path, mode_t mode)
{
printf("MKDIR\n");
int index = find_free_inode();
filetype *new_folder = malloc(sizeof(filetype));
char *pathname = malloc(strlen(path) + 2);
strcpy(pathname, path);
char *rindex = strrchr(pathname, '/');
// new_folder -> name = malloc(strlen(pathname)+2);
strcpy(new_folder->name, rindex + 1);
// new_folder -> path = malloc(strlen(pathname)+2);
strcpy(new_folder->path, pathname);
*rindex = '\0';
if (strlen(pathname) == 0)
strcpy(pathname, "/");
new_folder->children = NULL;
new_folder->num_children = 0;
new_folder->parent = filetype_from_path(pathname);
new_folder->num_links = 2;
new_folder->valid = 1;
strcpy(new_folder->test, "test");
if (new_folder->parent == NULL)
return -ENOENT;
// printf(";;;;%p;;;;\n", new_folder);
add_child(new_folder->parent, new_folder);
// new_folder -> type = malloc(10);
strcpy(new_folder->type, "directory");
new_folder->c_time = time(NULL);
new_folder->a_time = time(NULL);
new_folder->m_time = time(NULL);
new_folder->b_time = time(NULL);
new_folder->permissions = S_IFDIR | 0777;
new_folder->size = 0;
new_folder->group_id = getgid();
new_folder->user_id = getuid();
new_folder->number = index;
new_folder->blocks = 0;
save_contents();
return 0;
}
/*
* myreaddir - 读取目录内容
*
* 功能:
* 1. 读取指定目录下的所有文件和子目录。
* 2. 使用 FUSE 提供的 filler 函数将目录项添加到缓冲区。
* 3. 更新目录的访问时间。
*
* 参数:
* - path: 要读取的目录路径。
* - buffer: 用于存储目录项的缓冲区。
* - filler: FUSE 提供的回调函数,用于将目录项添加到缓冲区。
* - offset: 读取偏移量(未使用)。
* - fi: 文件信息结构(未使用)。
*
* 返回值:
* - 成功时返回 0。
* - 如果目录不存在,返回 -ENOENT。
*
* 实现逻辑:
* 1. 使用 filler 函数添加 "." 和 ".." 目录项。
* 2. 根据路径查找对应的目录节点。
* 3. 如果目录存在,遍历其子节点,使用 filler 函数将每个子节点添加到缓冲区。
* 4. 更新目录的访问时间。
*
* 示例:
* 假设文件系统结构如下:
* /
* ├── home
* │ └── user
* └── test.txt
*
* 调用示例:
* 1. myreaddir("/", buffer, filler, 0, fi) -> 缓冲区包含 ["home", "test.txt"]
* 2. myreaddir("/home", buffer, filler, 0, fi) -> 缓冲区包含 ["user"]
* 3. myreaddir("/invalid", buffer, filler, 0, fi) -> 返回 -ENOENT
*
* 注意:
* - 每次读取目录时,都会更新目录的访问时间。
* - 如果目录不存在,返回 -ENOENT。
*/
int myreaddir(const char *path, void *buffer, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
printf("READDIR\n");
filler(buffer, ".", NULL, 0);
filler(buffer, "..", NULL, 0);
char *pathname = malloc(strlen(path) + 2);
strcpy(pathname, path);
filetype *dir_node = filetype_from_path(pathname);
if (dir_node == NULL)
{
return -ENOENT;
}
else
{
dir_node->a_time = time(NULL);
for (int i = 0; i < dir_node->num_children; i++)
{
printf(":%s:\n", dir_node->children[i]->name);
filler(buffer, dir_node->children[i]->name, NULL, 0);
}
}
return 0;
}
/*
* mygetattr - 获取文件或目录属性
*
* 功能:
* 1. 获取指定路径的文件或目录的属性信息。
* 2. 将属性信息填充到 stat 结构中。
* 3. 支持文件和目录的属性查询。
*
* 参数:
* - path: 要查询的文件或目录的完整路径。
* - statit: 用于存储属性信息的 stat 结构指针。
*
* 返回值:
* - 成功时返回 0。
* - 如果文件或目录不存在,返回 -ENOENT。
*
* 实现逻辑:
* 1. 根据路径查找对应的文件或目录节点。
* 2. 如果节点存在,将属性信息填充到 stat 结构中,包括:
* - 用户 ID (st_uid)
* - 组 ID (st_gid)
* - 访问时间 (st_atime)
* - 修改时间 (st_mtime)
* - 创建时间 (st_ctime)
* - 权限模式 (st_mode)
* - 硬链接数 (st_nlink)
* - 文件大小 (st_size)
* - 数据块数 (st_blocks)
* 3. 如果节点不存在,返回错误码。
*
* 示例:
* 假设文件系统结构如下:
* /
* ├── home
* │ └── user
* └── test.txt
*
* 调用示例:
* 1. mygetattr("/test.txt", statit) -> 获取 test.txt 文件的属性
* 2. mygetattr("/home", statit) -> 获取 home 目录的属性
* 3. mygetattr("/invalid", statit) -> 返回 -ENOENT(文件或目录不存在)
*
* 注意:
* - 必须正确处理文件和目录的属性查询。
*/
static int mygetattr(const char *path, struct stat *statit)
{
char *pathname;
pathname = (char *)malloc(strlen(path) + 2);
strcpy(pathname, path);
printf("GETATTR %s\n", pathname);
filetype *file_node = filetype_from_path(pathname);
if (file_node == NULL)
return -ENOENT;
statit->st_uid = file_node->user_id; // The owner of the file/directory is the user who mounted the filesystem
statit->st_gid = file_node->group_id; // The group of the file/directory is the same as the group of the user who mounted the filesystem
statit->st_atime = file_node->a_time; // The last "a"ccess of the file/directory is right now
statit->st_mtime = file_node->m_time; // The last "m"odification of the file/directory is right now
statit->st_ctime = file_node->c_time;
statit->st_mode = file_node->permissions;
statit->st_nlink = file_node->num_links + file_node->num_children;
statit->st_size = file_node->size;
statit->st_blocks = file_node->blocks;
return 0;
}
/*
* myrmdir - 删除目录
*
* 功能:
* 1. 删除指定路径的目录。
* 2. 从父目录的子节点列表中移除该目录。
* 3. 调用 save_contents 方法将更新后的文件系统保存到磁盘。
*
* 参数:
* - path: 要删除的目录的完整路径。
*
* 返回值:
* - 成功时返回 0。
* - 如果目录不存在,返回 -ENOENT。
* - 如果目录非空,返回 -ENOTEMPTY。
*
* 实现逻辑:
* 1. 解析路径,获取目录名称和父目录路径。
* 2. 在父目录的子节点列表中查找匹配的目录。
* 3. 如果找到匹配的目录且为空,将其从子节点列表中移除。
* 4. 调用 save_contents 方法保存文件系统。
*
* 示例:
* 假设文件系统结构如下:
* /
* ├── home
* │ └── user
* └── test
*
* 调用示例:
* 1. myrmdir("/test") -> 删除 test 目录
* 2. myrmdir("/home/user") -> 返回 -ENOTEMPTY(user 目录非空)
* 3. myrmdir("/invalid") -> 返回 -ENOENT(目录不存在)
*
* 注意:
* - 只能删除空目录。
* - 如果目录非空,操作失败。
*/
int myrmdir(const char *path)
{
char *pathname = malloc(strlen(path) + 2);
strcpy(pathname, path);
char *rindex = strrchr(pathname, '/');
char *folder_delete = malloc(strlen(rindex + 1) + 2);
strcpy(folder_delete, rindex + 1);
*rindex = '\0';
if (strlen(pathname) == 0)
strcpy(pathname, "/");
filetype *parent = filetype_from_path(pathname);
if (parent == NULL)
return -ENOENT;
if (parent->num_children == 0)
return -ENOENT;
filetype *curr_child = (parent->children)[0];
int index = 0;
while (index < (parent->num_children))
{
if (strcmp(curr_child->name, folder_delete) == 0)
{
break;
}
index++;
curr_child = (parent->children)[index];
}
if (index < (parent->num_children))
{
if (((parent->children)[index]->num_children) != 0)
return -ENOTEMPTY;
for (int i = index + 1; i < (parent->num_children); i++)
{
(parent->children)[i - 1] = (parent->children)[i];
}
(parent->num_children) -= 1;
}
else
{
return -ENOENT;
}
save_contents();
return 0;
}
/*
* myrm - 删除文件
*
* 功能:
* 1. 删除指定路径的文件。
* 2. 从父目录的子节点列表中移除该文件。
* 3. 调用 save_contents 方法将更新后的文件系统保存到磁盘。
*
* 参数:
* - path: 要删除的文件的完整路径。
*
* 返回值:
* - 成功时返回 0。
* - 如果文件不存在,返回 -ENOENT。
* - 如果文件是目录且非空,返回 -ENOTEMPTY。
*
* 实现逻辑:
* 1. 解析路径,获取文件名和父目录路径。
* 2. 在父目录的子节点列表中查找匹配的文件。
* 3. 如果找到匹配的文件,将其从子节点列表中移除。
* 4. 调用 save_contents 方法保存文件系统。
*
* 示例:
* 假设文件系统结构如下:
* /
* ├── home
* │ └── user
* └── test.txt
*
* 调用示例:
* 1. myrm("/test.txt") -> 删除 test.txt 文件
* 2. myrm("/home/user") -> 返回 -ENOTEMPTY(user 是目录且非空)
* 3. myrm("/invalid") -> 返回 -ENOENT(文件不存在)
*
* 注意:
* - 只能删除文件,不能删除目录。
* - 如果文件是目录且非空,操作失败。
*/
int myrm(const char *path)
{
char *pathname = malloc(strlen(path) + 2);
strcpy(pathname, path);
char *rindex = strrchr(pathname, '/');
char *folder_delete = malloc(strlen(rindex + 1) + 2);
strcpy(folder_delete, rindex + 1);
*rindex = '\0';
if (strlen(pathname) == 0)
strcpy(pathname, "/");
filetype *parent = filetype_from_path(pathname);
if (parent == NULL)
return -ENOENT;
if (parent->num_children == 0)
return -ENOENT;
filetype *curr_child = (parent->children)[0];
int index = 0;
while (index < (parent->num_children))
{
if (strcmp(curr_child->name, folder_delete) == 0)
{
break;
}
index++;
curr_child = (parent->children)[index];
}
if (index < (parent->num_children))
{
if (((parent->children)[index]->num_children) != 0)
return -ENOTEMPTY;
for (int i = index + 1; i < (parent->num_children); i++)
{
(parent->children)[i - 1] = (parent->children)[i];
}
(parent->num_children) -= 1;
}
else
{
return -ENOENT;
}
save_contents();
return 0;
}
/*
* mycreate - 创建新文件
*
* 功能:
* 1. 在指定路径下创建一个新文件。
* 2. 分配并初始化新文件的元数据,包括路径、名称、类型、权限、时间戳等。
* 3. 将新文件添加到父目录的子节点列表中。
* 4. 调用 save_contents 方法将更新后的文件系统保存到磁盘。
*
* 参数:
* - path: 新文件的完整路径(必须以 "/" 开头)。
* - mode: 文件的权限模式(未直接使用,固定为 0777)。
* - fi: 文件信息结构(未使用)。
*
* 返回值:
* - 成功时返回 0。
* - 如果父目录不存在,返回 -ENOENT。
*
* 实现逻辑:
* 1. 查找一个空闲的 inode 编号。
* 2. 解析路径,获取新文件的名称和父目录路径。
* 3. 分配内存并初始化新文件结构(filetype)。
* 4. 设置新文件的元数据,包括路径、名称、类型、权限、时间戳等。
* 5. 将新文件添加到父目录的子节点列表中。
* 6. 调用 save_contents 方法保存文件系统。
*
* 示例:
* 假设文件系统结构如下:
* /
* └── home
*
* 调用示例:
* 1. mycreate("/home/test.txt", 0777, fi) -> 在 /home 下创建 test.txt 文件
* - 新文件路径: "/home/test.txt"
* - 新文件名称: "test.txt"
* - 父目录: "/home"
* 2. mycreate("/invalid/path/file.txt", 0777, fi) -> 返回 -ENOENT(父目录不存在)
*
* 注意:
* - 路径必须以 "/" 开头。
* - 新文件的权限固定为 0777。
* - 如果父目录不存在,操作失败。
*/
int mycreate(const char *path, mode_t mode, struct fuse_file_info *fi)
{
printf("CREATEFILE\n");
int index = find_free_inode();
filetype *new_file = malloc(sizeof(filetype));
char *pathname = malloc(strlen(path) + 2);
strcpy(pathname, path);
char *rindex = strrchr(pathname, '/');
strcpy(new_file->name, rindex + 1);
strcpy(new_file->path, pathname);
*rindex = '\0';
if (strlen(pathname) == 0)
strcpy(pathname, "/");
new_file->children = NULL;
new_file->num_children = 0;
new_file->parent = filetype_from_path(pathname);
new_file->num_links = 0;
new_file->valid = 1;
if (new_file->parent == NULL)
return -ENOENT;
add_child(new_file->parent, new_file);
// new_file -> type = malloc(10);
strcpy(new_file->type, "file");
new_file->c_time = time(NULL);
new_file->a_time = time(NULL);
new_file->m_time = time(NULL);
new_file->b_time = time(NULL);
new_file->permissions = S_IFREG | 0777;
new_file->size = 0;
new_file->group_id = getgid();
new_file->user_id = getuid();
new_file->number = index;
for (int i = 0; i < 16; i++)
{
(new_file->datablocks)[i] = find_free_db();
}
// new_file -> size = 0;
new_file->blocks = 0;
save_contents();
return 0;
}
/*
* myopen - 打开文件
*
* 功能:
* 1. 打开指定路径的文件。
* 2. 检查文件是否存在。
* 3. 更新文件的访问时间。
*
* 参数:
* - path: 要打开的文件的完整路径。
* - fi: 文件信息结构(未使用)。
*
* 返回值:
* - 成功时返回 0。
* - 如果文件不存在,返回 -ENOENT。
*
* 实现逻辑:
* 1. 根据路径查找对应的文件节点。
* 2. 如果文件存在,更新文件的访问时间。
* 3. 如果文件不存在,返回错误码。
*
* 示例:
* 假设文件系统结构如下:
* /
* └── test.txt
*
* 调用示例:
* 1. myopen("/test.txt", fi) -> 成功打开 test.txt 文件
* 2. myopen("/invalid.txt", fi) -> 返回 -ENOENT(文件不存在)
*
* 注意:
* - 打开文件时会更新文件的访问时间。
* - 如果文件不存在,返回 -ENOENT。
*/
int myopen(const char *path, struct fuse_file_info *fi)
{
printf("OPEN\n");
char *pathname = malloc(sizeof(path) + 1);
strcpy(pathname, path);
filetype *file = filetype_from_path(pathname);
return 0;
}
/*
* myread - 读取文件内容
*
* 功能:
* 1. 读取指定文件的内容。
* 2. 根据文件的大小和数据块分布,从磁盘中读取数据。
* 3. 将读取的数据存储到提供的缓冲区中。
* 4. 更新文件的访问时间。
*
* 参数:
* - path: 要读取的文件的完整路径。
* - buf: 用于存储读取数据的缓冲区。
* - size: 要读取的数据大小。
* - offset: 读取的偏移量(未使用)。
* - fi: 文件信息结构(未使用)。
*
* 返回值:
* - 成功时返回实际读取的字节数。
* - 如果文件不存在,返回 -ENOENT。
*
* 实现逻辑:
* 1. 根据路径查找对应的文件节点。
* 2. 如果文件存在,根据文件的大小和数据块分布,从磁盘中读取数据。
* 3. 将读取的数据存储到提供的缓冲区中。
* 4. 更新文件的访问时间。
*
* 示例:
* 假设文件系统结构如下:
* /
* └── test.txt
*
* 调用示例:
* 1. myread("/test.txt", buf, 1024, 0, fi) -> 读取 test.txt 文件的前 1024 字节
* 2. myread("/invalid.txt", buf, 1024, 0, fi) -> 返回 -ENOENT(文件不存在)
*
* 注意:
* - 读取数据时会更新文件的访问时间。
* - 如果文件不存在,返回 -ENOENT。
*/
int myread(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
printf("READ\n");
char *pathname = malloc(sizeof(path) + 1);
strcpy(pathname, path);
filetype *file = filetype_from_path(pathname);
if (file == NULL)
return -ENOENT;
else
{
char *str = malloc(sizeof(char) * 1024 * (file->blocks));
printf(":%ld:\n", file->size);
strcpy(str, "");
int i;
for (i = 0; i < (file->blocks) - 1; i++)
{
strncat(str, &spblock.datablocks[block_size * (file->datablocks[i])], 1024);
printf("--> %s", str);
}
strncat(str, &spblock.datablocks[block_size * (file->datablocks[i])], (file->size) % 1024);
printf("--> %s", str);
// strncpy(str, &spblock.datablocks[block_size*(file -> datablocks[0])], file->size);
strcpy(buf, str);
}
return file->size;
}
/*
* myaccess - 检查文件访问权限
*
* 功能:
* 1. 检查指定路径的文件或目录是否可访问。
* 2. 目前仅作为占位符实现,始终返回成功。
*
* 参数:
* - path: 要检查的文件或目录的完整路径。
* - mask: 访问权限掩码(未使用)。
*
* 返回值:
* - 始终返回 0,表示成功。
*
* 注意:
* - 当前实现未实际检查权限,需根据需求完善。
*/
int myaccess(const char *path, int mask)
{
return 0;
}
/*
* myrename - 重命名文件或目录
*
* 功能:
* 1. 将文件或目录从旧路径重命名为新路径。
* 2. 更新文件或目录的名称和路径。
* 3. 调用 save_contents 方法将更新后的文件系统保存到磁盘。
*
* 参数:
* - from: 文件或目录的原始路径。
* - to: 文件或目录的新路径。
*
* 返回值:
* - 成功时返回 0。
* - 如果原始路径对应的文件或目录不存在,返回 -ENOENT。
*
* 实现逻辑:
* 1. 解析原始路径,获取文件或目录的节点。
* 2. 解析新路径,获取新名称和父目录路径。
* 3. 更新文件或目录的名称和路径。
* 4. 调用 save_contents 方法保存文件系统。
*
* 示例:
* 假设文件系统结构如下:
* /
* ├── home
* │ └── user
* └── test.txt
*
* 调用示例:
* 1. myrename("/test.txt", "/home/test.txt") -> 将 test.txt 移动到 /home 目录下
* 2. myrename("/home/user", "/home/new_user") -> 将 user 目录重命名为 new_user
* 3. myrename("/invalid", "/new_path") -> 返回 -ENOENT(文件或目录不存在)
*
* 注意:
* - 重命名操作会同时更新文件或目录的名称和路径。
* - 如果原始路径对应的文件或目录不存在,返回 -ENOENT。
*/
int myrename(const char *from, const char *to)
{
printf("RENAME: %s\n", from);
printf("RENAME: %s\n", to);
char *pathname = malloc(strlen(from) + 2);
strcpy(pathname, from);
char *rindex1 = strrchr(pathname, '/');
filetype *file = filetype_from_path(pathname);
*rindex1 = '\0';
char *pathname2 = malloc(strlen(to) + 2);
strcpy(pathname2, to);
char *rindex2 = strrchr(pathname2, '/');
if (file == NULL)
return -ENOENT;
// file -> name = realloc(file -> name, strlen(rindex2+1)+2);
strcpy(file->name, rindex2 + 1);
// file -> path = realloc(file -> path, strlen(to)+2);
strcpy(file->path, to);
printf(":%s:\n", file->name);
printf(":%s:\n", file->path);
save_contents();
return 0;
}
/*
* mytruncate - 截断文件
*
* 功能:
* 1. 将指定文件截断到指定大小。
* 2. 目前仅作为占位符实现,未实际实现功能。
*
* 参数:
* - path: 要截断的文件的完整路径。
* - size: 目标文件大小。
*
* 返回值:
* - 始终返回 0,表示成功。
*
* 注意:
* - 当前实现未实际截断文件,需根据需求完善。
*/
int mytruncate(const char *path, off_t size)
{
return 0;
}
/*
* mywrite - 向文件写入数据
*
* 功能:
* 1. 将数据写入指定文件。
* 2. 根据文件当前大小和数据块使用情况,将数据写入合适的块。
* 3. 更新文件的大小和块使用情况。
* 4. 调用 save_contents 方法将更新后的文件系统保存到磁盘。
*
* 参数:
* - path: 要写入的文件的完整路径。
* - buf: 要写入的数据缓冲区。
* - size: 要写入的数据大小。
* - offset: 写入的偏移量(未使用)。
* - fi: 文件信息结构(未使用)。
*
* 返回值:
* - 成功时返回实际写入的字节数。
* - 如果文件不存在,返回 -ENOENT。
*
* 实现逻辑:
* 1. 根据路径查找对应的文件节点。
* 2. 如果文件当前大小为 0,直接将数据写入第一个数据块。
* 3. 如果文件已有数据,检查最后一个数据块是否有剩余空间:
* - 如果有剩余空间,将数据写入剩余空间。
* - 如果没有剩余空间,分配新的数据块并写入数据。
* 4. 更新文件的大小和块使用情况。
* 5. 调用 save_contents 方法保存文件系统。
*
* 示例:
* 假设文件系统结构如下:
* /
* └── test.txt
*
* 调用示例:
* 1. mywrite("/test.txt", "Hello", 5, 0, fi) -> 将 "Hello" 写入 test.txt 文件
* - 文件大小更新为 5
* - 数据块 0 包含 "Hello"
* 2. mywrite("/test.txt", " World", 6, 0, fi) -> 将 " World" 追加到 test.txt 文件
* - 文件大小更新为 11
* - 数据块 0 包含 "Hello World"
* 3. mywrite("/invalid.txt", "Data", 4, 0, fi) -> 返回 -ENOENT(文件不存在)
*
* 注意:
* - 写入数据时会自动追加到文件末尾。
* - 如果文件不存在,返回 -ENOENT。
*/
int mywrite(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
printf("WRITING\n");
char *pathname = malloc(sizeof(path) + 1);
strcpy(pathname, path);
filetype *file = filetype_from_path(pathname);
if (file == NULL)
return -ENOENT;
int indexno = (file->blocks) - 1;
if (file->size == 0)
{
strcpy(&spblock.datablocks[block_size * ((file->datablocks)[0])], buf);
file->size = strlen(buf);
(file->blocks)++;
}
else
{
int currblk = (file->blocks) - 1;
int len1 = 1024 - (file->size % 1024);
if (len1 >= strlen(buf))
{
strcat(&spblock.datablocks[block_size * ((file->datablocks)[currblk])], buf);
file->size += strlen(buf);
printf("---> %s\n", &spblock.datablocks[block_size * ((file->datablocks)[currblk])]);
}
else
{
char *cpystr = malloc(1024 * sizeof(char));
strncpy(cpystr, buf, len1 - 1);
strcat(&spblock.datablocks[block_size * ((file->datablocks)[currblk])], cpystr);
strcpy(cpystr, buf);
strcpy(&spblock.datablocks[block_size * ((file->datablocks)[currblk + 1])], (cpystr + len1 - 1));
file->size += strlen(buf);
printf("---> %s\n", &spblock.datablocks[block_size * ((file->datablocks)[currblk])]);
(file->blocks)++;
}
}
save_contents();
return strlen(buf);
}
static struct fuse_operations operations =
{
.mkdir = mymkdir, // 创建目录
.getattr = mygetattr, // 获取文件/目录属性
.readdir = myreaddir, // 读取目录内容
.rmdir = myrmdir, // 删除目录
.open = myopen, // 打开文件
.read = myread, // 读取文件内容
.write = mywrite, // 写入文件内容
.create = mycreate, // 创建文件
.rename = myrename, // 重命名文件/目录
.unlink = myrm, // 删除文件
};
int main(int argc, char *argv[])
{
// 二进制文件代表了基于磁盘的文件系统(file layout)
FILE *fd = fopen("file_structure.bin", "rb");
if (fd)
{
printf("LOADING\n");
// 如果文件存在,读取文件结构数据到 file_array
fread(&file_array, sizeof(filetype) * 31, 1, fd);
// 初始化子节点索引
int child_startindex = 1;
// 设置根节点的父节点为 NULL
file_array[0].parent = NULL;
// 遍历前 6 个节点,在内存中重建文件树结构
for (int i = 0; i < 6; i++)
{
file_array[i].num_children = 0;
file_array[i].children = NULL;
// 为每个节点添加子节点
for (int j = child_startindex; j < child_startindex + 5; j++)
{
if (file_array[j].valid)
{
add_child(&file_array[i], &file_array[j]);
}
}
child_startindex += 5;
}
// 设置根节点
root = &file_array[0];
// 打开并读取超级块数据
FILE *fd1 = fopen("super.bin", "rb");
fread(&spblock, sizeof(superblock), 1, fd1);
}
else
{
// 如果文件不存在,初始化超级块和根目录
initialize_superblock();
initialize_root_directory();
}
// FUSE 库的主入口函数,用于启动文件系统, 指向 fuse_operations 结构体的指针
return fuse_main(argc, argv, &operations, NULL);
}