提示:本文章代码存在bug,但已经可以完成期末作业
文章目录
- 前言(设计目的和内容要求)
- 一、运行过程
- 二、库文件及声明
- 二、函数实现
- 1. void startsys()
- 2. void exitsys()
- 3. void my_format()
- 4. void my_ls()
- 5. void my_mkdir(char *dirname)
- 6. void my_rmdir(char *dirname)
- 7. int my_create(char* filename)
- 8. void my_rm(char *filename)
- 9. void my_cd(char* dirname)
- 10. int my_open(char* filename)
- 11. int my_close(int fd)
- 12. int my_read(int fd)
- 13. int do_read(int fd, int len, char* text)
- 14. int my_write(int fd)
- 15. int do_write(int fd, char* text, int len, char wstyle)
- 16. int get_free_openfilelist()
- 17. unsigned short int get_free_block()
- 18. void help()
- 总结
前言(设计目的和内容要求)
这学期有门课,学Unix和Linux,本篇文章为该课的课程作业。
完整代码在github地址
如果用3模式进行文件的写入,会造成许多问题。原因我查了好久,也没查出来,其效果类似于如下结果。具体代码在do_write部分
/root/> my_create a.txt
/root/> my_open a.txt
/root/a.txt> my_write
1:Truncation 2:Addition
1
please input data, line feed + $$ to end file
12345
$$
/root/a.txt> my_read
12345
/root/a.txt> my_write
1:Truncation 2:Addition
3
please input data, line feed + $$ to end file
qwerty
$$
/root/a.txt> my_read
12345
1. 设计目的
通过具体的文件存储空间的管理、文件的物理结构、目录结构和文件操作的实现,加深对文件系统内部数据结构、功能以及实现过程的理解。
2. 内容要求
(1)在内存中开辟一个虚拟磁盘空间作为文件存储分区,在其上实现一个简单的基于多级目录的单用户单任务系统中的文件系统。在退出该文件系统的使用时,应将该虚拟文件系统以一个文件的方式保存到磁盘上,以便下次可以再将它恢复到内存的虚拟磁盘空间中。
(2)文件存储空间的分配可采用显式链接分配或其他的办法。
(3)文件目录结构采用多级目录结构。为了简单起见,可以不使用索引结点,其中的每个目录项应包含文件名、物理地址、长度等信息,还可以通过目录项实现对文件的读和写的保护。
(4)要求提供以下操作命令:
- my_format:对文件存储器进行格式化,即按照文件系统的结构对虚拟磁盘空间进行布局,并在其上创建根目录以及用于管理文件存储空间等的数据结构。
- my_mkdir:用于创建子目录。
- my_rmdir:用于删除子目录。
- my_ls:用于显示目录中的内容。
- my_cd:用于更改当前目录。
- my_create:用于创建文件。
- my_open:用于打开文件。
- my_close:用于关闭文件。
- my_write:用于写文件。
- my_read:用于读文件。
- my_rm:用于删除文件。
- my_exitsys:用于退出文件系统。
一、运行过程
1. 编译及运行
win
编译filesys.c文件后,可以得到一个filesys.exe,运行即可。
linux
sudo gcc -o filesys.c
./a.out
2. 运行结果
(1)目录更改
(2)文件操作
(3)系统启动与关闭
以下是我根据网上找的代码修改后的,能直接运行的太少了。优快云的文章中的代码注释较为详细,便于大家理解。完整可运行代码见github
二、库文件及声明
1. 头文件
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
2. 常量及数据结构
#ifndef INIT_H
#define INIT_H
#define BLOCKSIZE 1024
#define SIZE 1024000
#define END 65535
#define FREE 0
#define ROOTBLOCKNUM 2
#define MAXOPENFILE 10 // 同时打开最大文件数
#define MAX_TEXT_SIZE 10000
typedef struct FCB {
char filename[8];
char exname[3];
unsigned char attribute; // 0: dir file, 1: data file
unsigned short time;
unsigned short date;
unsigned short first;
unsigned short length;
char free; // 0: 空 fcb
} fcb;//file control block
typedef struct FAT {
unsigned short id;
} fat;//File Allocation Table
typedef struct USEROPEN {
char filename[8];
char exname[3];
unsigned char attribute;//文件属性:值为0时表示目录文件,值为1时表示数据文件
unsigned short time;
unsigned short date;
unsigned short first;
unsigned short length;//文件长度(对数据文件是字节数,对目录文件可以是目录项个数)
char free;
int dirno; // 父目录文件起始盘块号
int diroff; // 该文件对应的 fcb 在父目录中的逻辑序号
char dir[MAXOPENFILE][80]; // 全路径信息
int count;//记录文件的当前读写指针位置(即偏移量)
char fcbstate; // 是否修改 1是 0否
char topenfile; // 表示该用户打开表项是否为空,若值为0,表示为空,否则表示已被某打开文件占据
} useropen;
typedef struct BLOCK {
char magic_number[8];
char information[200];//存储一些描述信息,如磁盘块大小、磁盘块数量、最多打开文件数等、
unsigned short root;
unsigned char* startblock;
} block0;
char* FILENAME = "unix_filesys";// 可以自己定义
unsigned char* myvhard;// 是一个指向虚拟硬盘的指针,myvhard = (unsigned char*)malloc(SIZE);
//useropen *ptrcurdir;
useropen openfilelist[MAXOPENFILE];
int currfd;
//char currentdir[80];
unsigned char* startp;
//extern char *FILENAME;
unsigned char buffer[SIZE];//临时存储区域
3. 函数声明
/* sysfile func */
void startsys();
void my_format();
void my_cd(char* dirname);
int do_read(int fd, int len, char* text);
int do_write(int fd, char* text, int len, char wstyle);
int my_read(int fd);
int my_write(int fd);
void exitsys();
void my_cd(char* dirname);
int my_open(char* filename);
int my_close(int fd);
void my_mkdir(char* dirname);
void my_rmdir(char* dirname);
int my_create(char* filename);
void my_rm(char* filename);
void my_ls();
void help();
int get_free_openfilelist();
unsigned short int get_free_block();
#endif
4. 系统入口
#include "init.c"
//debug func
void test()
{
int openfile_num = 0;
int i;
printf("debug area ############\n");
// print open file number
for (i = 0; i < MAXOPENFILE; i++) {
if (openfilelist[i].topenfile == 1) {
openfile_num++;
}
}
printf(" open file number: %d\n", openfile_num);
printf(" curr file name: %s\n", openfilelist[currfd].filename);
printf(" curr file length: %d\n", openfilelist[currfd].length);
printf("debug end ############\n");
}
int main(void)
{
char cmd[14][11] = {
"my_mkdir", "my_rmdir", "my_ls", "my_cd", "my_create",
"my_rm", "my_open", "my_close", "my_write", "my_read",
"my_exitsys", "my_help", "my_test", "my_format"
};
char command[30], *sp;
int cmd_idx, i;
printf("************* file system ***************\n");
startsys();
while (1) {
printf("%s> ", openfilelist[currfd].dir);
gets(command);
cmd_idx = -1;
if (strcmp(command, "") == 0) {
printf("\n");
continue;
}
sp = strtok(command, " ");
for (i = 0; i < 14; i++) {
if (strcmp(sp, cmd[i]) == 0) {
cmd_idx = i;
break;
}
}
switch (cmd_idx) {
case 0: // mkdir
sp = strtok(NULL, " ");
if (sp != NULL)
my_mkdir(sp);
else
printf("mkdir error\n");
break;
case 1: // rmdir
sp = strtok(NULL, " ");
if (sp != NULL)
my_rmdir(sp);
else
printf("rmdir error\n");
break;
case 2: // ls
my_ls();
break;
case 3: // cd
sp = strtok(NULL, " ");
if (sp != NULL)
my_cd(sp);
else
printf("cd error\n");
break;
case 4: // create
sp = strtok(NULL, " ");
if (sp != NULL)
my_create(sp);
else
printf("create error\n");
break;
case 5: // rm
sp = strtok(NULL, " ");
if (sp != NULL)
my_rm(sp);
else
printf("rm error\n");
break;
case 6: // open
sp = strtok(NULL, " ");
if (sp != NULL)
my_open(sp);
else
printf("open error\n");
break;
case 7: // close
if (openfilelist[currfd].attribute == 1)
my_close(currfd);
else
printf("there is not openning file\n");
break;
case 8: // write
if (openfilelist[currfd].attribute == 1)
my_write(currfd);
else
printf("please open file first, then write\n");
break;
case 9: // read
if (openfilelist[currfd].attribute == 1)
my_read(currfd);
else
printf("please open file first, then read\n");
break;
case 10: // exit
exitsys();
printf("**************** exit file system ****************\n");
return 0;
break;
case 11: // help
help();
break;
case 12: // test
test();
break;
case 13: // format
my_format();
break;
default:
printf("wrong command: %s\n", command);
break;
}
}
return 0;
}
二、函数实现
1. void startsys()
主要为识别或初始化文件系统
void startsys()
{
/**
* 如果存在文件系统(存在 FILENAME 这个文件 且 开头为魔数)则
* 将 root 目录载入打开文件表。
* 否则,调用 my_format 创建文件系统,再载入。
*/
myvhard = (unsigned char*)malloc(SIZE);
FILE* file;
// 读取文件系统
if ((file = fopen(FILENAME, "r")) != NULL) {
// 读取文件内容到缓冲区
fread(buffer, SIZE, 1, file);
fclose(file);
// 判断文件内容的魔数是否为有效
if (memcmp(buffer, "10101010", 8) == 0) {
// 复制文件内容到虚拟硬盘
memcpy(myvhard, buffer, SIZE);
printf("myfsys file read successfully\n");
} else {
printf("invalid myfsys magic num, create file system\n");
// 创建文件系统
my_format();
memcpy(buffer, myvhard, SIZE);
}
} else {
printf("myfsys not find, create file system\n");
// 创建文件系统
my_format();
memcpy(buffer, myvhard, SIZE);
}
// 初始化根目录打开文件表项
fcb* root;
root = (fcb*)(myvhard + 5 * BLOCKSIZE);
strcpy(openfilelist[0].filename, root->filename);
strcpy(openfilelist[0].exname, root->exname);
openfilelist[0].attribute = root->attribute;
openfilelist[0].time = root->time;
openfilelist[0].date = root->date;
openfilelist[0].first = root->first;
openfilelist[0].length = root->length;
openfilelist[0].free = root->free;
openfilelist[0].dirno = 5;
openfilelist[0].diroff = 0;
strcpy(openfilelist[0].dir, "/root/");
openfilelist[0].count = 0;
openfilelist[0].fcbstate = 0;
openfilelist[0].topenfile = 1;
startp = ((block0*)myvhard)->startblock;
currfd = 0;
/*代码讲解
root = (fcb*)(myvhard + 5 * BLOCKSIZE);:这里将 root 指针指向虚拟硬盘上根目录的位置。myvhard 是整个虚拟硬盘的起始地址,5 * BLOCKSIZE 表示根目录的起始位置,因为前面已经设定第六个磁盘块为根目录磁盘块。
strcpy(openfilelist[0].filename, root->filename); 到 openfilelist[0].free = root->free;:这一系列操作是将根目录的信息复制到打开文件表项 openfilelist[0] 中。这包括文件名、扩展名、属性、时间、日期、起始盘块号、长度、空闲标志等信息。
openfilelist[0].dirno = 5; 和 openfilelist[0].diroff = 0;:表示根目录所在的父目录的盘块号和该文件在父目录中的逻辑序号。在这里,根目录是位于第 5 个盘块,逻辑序号为 0。
strcpy(openfilelist[0].dir, "/root/");:表示根目录的全路径名,这里为 "/root/"。
startp = ((block0*)myvhard)->startblock;:将虚拟硬盘上的起始盘块地址赋值给 startp,以备后续使用。
currfd = 0;:将当前文件描述符设为 0,表示当前打开的文件为根目录。
*/
return;
}
2. void exitsys()
exitsys 函数用于系统退出时关闭所有打开的文件,并将文件系统内容写入文件。
void exitsys()
{
/**
* 依次关闭 打开文件。 写入 FILENAME 文件
*/
while (currfd) {
my_close(currfd);
}
FILE* fp = fopen(FILENAME, "w");
fwrite(myvhard, SIZE, 1, fp);
fclose(fp);
}
3. void my_format()
初始化文件系统
void my_format()
{
/**
* 初始化前五个磁盘块
* 设定第六个磁盘块为根目录磁盘块
* 初始化 root 目录: 创建 . 和 .. 目录
* 写入 FILENAME 文件 (写入磁盘空间)
*/
block0* boot = (block0*)myvhard;//将 myvhard 强制类型转换为 block0* 类型,即指向文件系统的第一个块的指针,用于设置文件系统的基本信息。
strcpy(boot->magic_number, "10101010");//设置文件系统的魔数
strcpy(boot->information, "fat file system");//设置文件系统的信息。
boot->root = 5;//设置文件系统的根目录块号为 5
boot->startblock = myvhard + BLOCKSIZE * 5;//设置文件系统的起始块地址,即指向第六个块的地址。
fat* fat1 = (fat*)(myvhard + BLOCKSIZE);
fat* fat2 = (fat*)(myvhard + BLOCKSIZE * 3);//将文件分配表(FAT)的两个副本分别指向第二块和第四块。
int i;
for (i = 0; i < 6; i++) {
fat1[i].id = END;
fat2[i].id = END;
} //将前六个块标记为文件结束(END)。
for (i = 6; i < 1000; i++) {
fat1[i].id = FREE;
fat2[i].id = FREE;
} //将从第七个块到第一千个块标记为空闲(FREE)。
// 将 root 指向第六个块,即根目录的起始位置。
fcb* root = (fcb*)(myvhard + BLOCKSIZE * 5);
strcpy(root->filename, ".");
strcpy(root->exname, "di");
root->attribute = 0;
//初始化根目录的信息,设置文件名为.,扩展名为di,属性为目录文件。
time_t rawTime = time(NULL);
struct tm* time = localtime(&rawTime);//加载本地时间
root->time = time->tm_hour * 2048 + time->tm_min * 32 + time->tm_sec / 2;
root->date = (time->tm_year - 100) * 512 + (time->tm_mon + 1) * 32 + (time->tm_mday);
//设置根目录的创建时间和日期。
root->first = 5;
root->free = 1;
root->length = 2 * sizeof(fcb);
//设置根目录的起始盘块号、空闲标志和长度。
fcb* root2 = root + 1;
memcpy(root2, root, sizeof(fcb));
strcpy(root2->filename, "..");
//复制根目录的信息到根目录下的 ".." 目录项。即root2(..)指向第7个块
for (i = 2; i < (int)(BLOCKSIZE / sizeof(fcb)); i++) {
root2++;
strcpy(root2->filename, "");
root2->free = 0;
}
//初始化根目录下的其他文件项,将文件名置为空字符串,标记为空闲。
FILE* fp = fopen(FILENAME, "w");// 再磁盘上创建作为文件系统的文件
fwrite(myvhard, SIZE, 1, fp);//将文件系统内容写入文件。
fclose(fp);
}
4. void my_ls()
用来打印出当前目录的清单,只做出来了时间和字节数
void my_ls()
{
// 判断是否是目录
if (openfilelist[currfd].attribute == 1) {
printf("data file\n");
return;
}//首先检查当前打开文件的属性是否为1,如果是1表示为数据文件而不是目录,那么输出 "data file" 并返回
char buf[MAX_TEXT_SIZE];//定义一个缓冲区 buf,用于存储当前目录文件的信息。
int i;
// 读取当前目录文件信息(一个个fcb), 载入内存
openfilelist[currfd].count = 0;//将当前打开文件的读写指针位置重置为0,以便从头开始读取目录文件的内容。
do_read(currfd, openfilelist[currfd].length, buf);//调用 do_read 函数从当前目录文件中读取全部内容到缓冲区 buf 中。
// 遍历当前目录 fcb
fcb* fcbptr = (fcb*)buf;
for (i = 0; i < (int)(openfilelist[currfd].length / sizeof(fcb)); i++, fcbptr++) {
if (fcbptr->free == 1) {
if (fcbptr->attribute == 0) {
// 目录项处理
printf("<DIR> %-8s\t%d/%d/%d %d:%d\n",
fcbptr->filename,
(fcbptr->date >> 9) + 2000,
(fcbptr->date >> 5) & 0x000f,
(fcbptr->date) & 0x001f,
(fcbptr->time >> 11),
(fcbptr->time >> 5) & 0x003f);
/*
"<DIR> %-8s\t":这是一个格式化字符串,用于输出目录项的类型 ("<DIR>") 和文件名。%-8s 指示输出一个左对齐的字符串,总宽度为8个字符。\t 表示在文件名和日期之间插入一个制表符。
fcbptr->filename:这是一个字符串,表示文件或目录的名字。fcbptr 是一个指向 fcb 结构体的指针,-> 用于访问结构体中的成员。
(fcbptr->date >> 9) + 2000:这是对日期字段的处理,右移9位用于获取年份(从日期字段中分离出来的前7位),然后加上2000,因为这个文件系统使用的是从2000年开始的年份表示。
(fcbptr->date >> 5) & 0x000f:这是对日期字段的处理,右移5位用于获取月份(从日期字段中分离出来的中间4位),然后通过 & 0x000f 操作获取月份的值。
(fcbptr->date) & 0x001f:这是对日期字段的处理,通过 & 0x001f 操作获取日期的值。
(fcbptr->time >> 11):这是对时间字段的处理,右移11位用于获取小时部分(从时间字段中分离出来的前5位)。
(fcbptr->time >> 5) & 0x003f:这是对时间字段的处理,右移5位用于获取分钟部分(从时间字段中分离出来的中间6位),然后通过 & 0x003f 操作获取分钟的值。
*/
} else {
// 文件项处理
printf("<---> %-8s\t%d/%d/%d %d:%d\t%d\n",
fcbptr->filename,
(fcbptr->date >> 9) + 2000,
(fcbptr->date >> 5) & 0x000f,
(fcbptr->date) & 0x001f,
(fcbptr->time >> 11),
(fcbptr->time >> 5) & 0x003f,
fcbptr->length);
}
}
}
}
5. void my_mkdir(char *dirname)
创建目录
void my_mkdir(char *dirname)
{
/**
* 当前目录:当前打开目录项表示的目录
* 该目录:以下指创建的目录
* 父目录:指该目录的父目录
* 如:
* 我现在在 root 目录下, 输入命令 mkdir a/b/bb
* 表示 在 root 目录下的 a 目录下的 b 目录中创建 bb 目录
* 这时,父目录指 b,该目录指 bb,当前目录指 root
* 以下都用这个表达,简单情况下,当前目录和父目录是一个目录
* 来不及了,先讨论简单情况,即 mkdir bb
*/
int i = 0;
char text[MAX_TEXT_SIZE];
char *fname = strtok(dirname, ".");
char *exname = strtok(NULL, ".");
if (exname != NULL)
{
printf("you can not use extension\n");
return;
}
// 读取父目录信息
openfilelist[currfd].count = 0;
int filelen = do_read(currfd, openfilelist[currfd].length, text);
fcb *fcbptr = (fcb *)text;
// 查找是否重名
for (i = 0; i < (int)(filelen / sizeof(fcb)); i++)
{
if (strcmp(dirname, fcbptr[i].filename) == 0 && fcbptr->attribute == 0)
{
printf("dir has existed\n");
return;
}
}
// 申请一个打开目录表项
int fd = get_free_openfilelist();
if (fd == -1)
{
printf("openfilelist is full\n");
return;
}
// 申请一个磁盘块
unsigned short int block_num = get_free_block();
if (block_num == END)
{
printf("blocks are full\n");
openfilelist[fd].topenfile = 0;
return;
}
// 更新 fat 表
fat *fat1 = (fat *)(myvhard + BLOCKSIZE);
fat *fat2 = (fat *)(myvhard + BLOCKSIZE * 3);
fat1[block_num].id = END;
fat2[block_num].id = END;
// 在父目录中找一个空的 fcb,分配给该目录 ??未考虑父目录满的情况??
for (i = 0; i < (int)(filelen / sizeof(fcb)); i++)
{
if (fcbptr[i].free == 0)
{
break;
}
}
openfilelist[currfd].count = i * sizeof(fcb);
openfilelist[currfd].fcbstate = 1;
// 初始化该 fcb
fcb *fcbtmp = (fcb *)malloc(sizeof(fcb));
fcbtmp->attribute = 0;
time_t rawtime = time(NULL);
struct tm *time = localtime(&rawtime);
fcbtmp->date = (time->tm_year - 100) * 512 + (time->tm_mon + 1) * 32 + (time->tm_mday);
fcbtmp->time = (time->tm_hour) * 2048 + (time->tm_min) * 32 + (time->tm_sec) / 2;
strcpy(fcbtmp->filename, dirname);
strcpy(fcbtmp->exname, "di");
fcbtmp->first = block_num;
fcbtmp->length = 2 * sizeof(fcb);
fcbtmp->free = 1;
do_write(currfd, (char *)fcbtmp, sizeof(fcb), 3);
// 设置打开文件表项
openfilelist[fd].attribute = 0;
openfilelist[fd].count = 0;
openfilelist[fd].date = fcbtmp->date;
openfilelist[fd].time = fcbtmp->time;
openfilelist[fd].dirno = openfilelist[currfd].first;
openfilelist[fd].diroff = i;
strcpy(openfilelist[fd].exname, "di");
strcpy(openfilelist[fd].filename, dirname);
openfilelist[fd].fcbstate = 0;
openfilelist[fd].first = fcbtmp->first;
openfilelist[fd].free = fcbtmp->free;
openfilelist[fd].length = fcbtmp->length;
openfilelist[fd].topenfile = 1;
strcat(strcat(strcpy(openfilelist[fd].dir, (char *)(openfilelist[currfd].dir)), dirname), "/");
// 设置 . 和 .. 目录
fcbtmp->attribute = 0;
fcbtmp->date = fcbtmp->date;
fcbtmp->time = fcbtmp->time;
strcpy(fcbtmp->filename, ".");
strcpy(fcbtmp->exname, "di");
fcbtmp->first = block_num;
fcbtmp->length = 2 * sizeof(fcb);
do_write(fd, (char *)fcbtmp, sizeof(fcb), 3);
fcb *fcbtmp2 = (fcb *)malloc(sizeof(fcb));
memcpy(fcbtmp2, fcbtmp, sizeof(fcb));
strcpy(fcbtmp2->filename, "..");
fcbtmp2->first = openfilelist[currfd].first;
fcbtmp2->length = openfilelist[currfd].length;
fcbtmp2->date = openfilelist[currfd].date;
fcbtmp2->time = openfilelist[currfd].time;
do_write(fd, (char *)fcbtmp2, sizeof(fcb), 3);
// 关闭该目录的打开文件表项,close 会修改父目录中对应该目录的 fcb 信息
/**
* 这里注意,一个目录存在 2 个 fcb 信息,一个为该目录下的 . 目录文件,一个为父目录下的 fcb。
* 因此,这俩个fcb均需要修改,前一个 fcb 由各个函数自己完成,后一个 fcb 修改由 close 完成。
* 所以这里,需要打开文件表,再关闭文件表,实际上更新了后一个 fcb 信息。
*/
my_close(fd);
free(fcbtmp);
free(fcbtmp2);
// 修改父目录 fcb
fcbptr = (fcb *)text;
fcbptr->length = openfilelist[currfd].length;
openfilelist[currfd].count = 0;
do_write(currfd, (char *)fcbptr, sizeof(fcb), 3);
openfilelist[currfd].fcbstate = 1;
}
6. void my_rmdir(char *dirname)
删除目录
void my_rmdir(char *dirname)
{
int i, tag = 0;
char buf[MAX_TEXT_SIZE];
// 排除 . 和 .. 目录
if (strcmp(dirname, ".") == 0 || strcmp(dirname, "..") == 0)
{
printf("can not remove . and .. special dir\n");
return;
}
openfilelist[currfd].count = 0;
do_read(currfd, openfilelist[currfd].length, buf);
// 查找要删除的目录
fcb *fcbptr = (fcb *)buf;
for (i = 0; i < (int)(openfilelist[currfd].length / sizeof(fcb)); i++, fcbptr++)
{
if (fcbptr->free == 0)
continue;
if (strcmp(fcbptr->filename, dirname) == 0 && fcbptr->attribute == 0)
{
tag = 1;
break;
}
}
if (tag != 1)
{
printf("no such dir\n");
return;
}
// 无法删除非空目录
if (fcbptr->length > 2 * sizeof(fcb))
{
printf("can not remove a non empty dir\n");
return;
}
// 更新 fat 表
int block_num = fcbptr->first;
fat *fat1 = (fat *)(myvhard + BLOCKSIZE);
int nxt_num = 0;
while (1)
{
nxt_num = fat1[block_num].id;
fat1[block_num].id = FREE;
if (nxt_num != END)
{
block_num = nxt_num;
}
else
{
break;
}
}
fat1 = (fat *)(myvhard + BLOCKSIZE);
fat *fat2 = (fat *)(myvhard + BLOCKSIZE * 3);
memcpy(fat2, fat1, BLOCKSIZE * 2);
// 更新 fcb
fcbptr->date = 0;
fcbptr->time = 0;
fcbptr->exname[0] = '\0';
fcbptr->filename[0] = '\0';
fcbptr->first = 0;
fcbptr->free = 0;
fcbptr->length = 0;
openfilelist[currfd].count = i * sizeof(fcb);
do_write(currfd, (char *)fcbptr, sizeof(fcb), 3);
// 删除目录需要相应考虑可能删除 fcb,也就是修改父目录 length
// 这里需要注意:因为删除中间的 fcb,目录有效长度不变,即 length 不变
// 因此需要考虑特殊情况,即删除最后一个 fcb 时,极有可能之前的 fcb 都是空的,这是需要
// 循环删除 fcb (以下代码完成),可能需要回收 block 修改 fat 表等过程(do_write 完成)
int lognum = i;
if ((lognum + 1) * sizeof(fcb) == openfilelist[currfd].length)
{
openfilelist[currfd].length -= sizeof(fcb);
lognum--;
fcbptr = (fcb *)buf + lognum;
while (fcbptr->free == 0)
{
fcbptr--;
openfilelist[currfd].length -= sizeof(fcb);
}
}
// 更新父目录 fcb
fcbptr = (fcb *)buf;
fcbptr->length = openfilelist[currfd].length;
openfilelist[currfd].count = 0;
do_write(currfd, (char *)fcbptr, sizeof(fcb), 3);
openfilelist[currfd].fcbstate = 1;
}
7. int my_create(char* filename)
创建文件
int my_create(char* filename)
{
// 非法判断
if (strcmp(filename, "") == 0) {
printf("please input filename\n");
return -1;
}
if (openfilelist[currfd].attribute == 1) {
printf("you are in data file now\n");
return -1;
}
openfilelist[currfd].count = 0;
char buf[MAX_TEXT_SIZE];
do_read(currfd, openfilelist[currfd].length, buf);
int i;
fcb* fcbptr = (fcb*)buf;
// 检查重名
for (i = 0; i < (int)(openfilelist[currfd].length / sizeof(fcb)); i++, fcbptr++) {
if (fcbptr->free == 0) {
continue;
}
if (strcmp(fcbptr->filename, filename) == 0 && fcbptr->attribute == 1) {
printf("the same filename error\n");
return -1;
}
}
// 申请磁盘块并更新 FAT 表
int block_num = get_free_block(); // 调用函数获取一个空闲的磁盘块编号
if (block_num == -1)
{
return -1; // 如果未找到空闲磁盘块,返回错误代码
}
fat *fat1 = (fat *)(myvhard + BLOCKSIZE); // 获取 FAT 表的第一块
fat *fat2 = (fat *)(myvhard + BLOCKSIZE * 3); // 获取 FAT 表的第三块
fat1[block_num].id = END; // 将 FAT 表中对应块号的项设置为文件结束标志
memcpy(fat2, fat1, BLOCKSIZE * 2); // 将 FAT 表的修改复制到第三块,保持两块 FAT 表一致
// 修改 FCB 信息
strcpy(fcbptr->filename, filename); // 将文件名复制到 FCB 结构中
time_t rawtime = time(NULL); // 获取当前时间
struct tm *time = localtime(&rawtime); // 转换为本地时间
fcbptr->date = (time->tm_year - 100) * 512 + (time->tm_mon + 1) * 32 + (time->tm_mday); // 计算并设置日期信息
fcbptr->time = (time->tm_hour) * 2048 + (time->tm_min) * 32 + (time->tm_sec) / 2; // 计算并设置时间信息
fcbptr->first = block_num; // 设置文件的起始磁盘块号
fcbptr->free = 1; // 设置文件状态为已分配
fcbptr->attribute = 1; // 数据文件
fcbptr->length = 0; // 初始化文件长度为0,因为是新建文件
openfilelist[currfd].count = i * sizeof(fcb); // 设置当前文件的读写指针位置
do_write(currfd, (char *)fcbptr, sizeof(fcb), 3); // 将修改后的 FCB 写回磁盘
// 修改父目录 FCB
fcbptr = (fcb *)buf; // 恢复 fcbptr 指向当前目录的内存缓冲区
fcbptr->length = openfilelist[currfd].length; // 更新父目录的文件长度信息
openfilelist[currfd].count = 0; // 重置当前目录的读写指针位置
do_write(currfd, (char *)fcbptr, sizeof(fcb), 3); // 将修改后的父目录 FCB 写回磁盘
openfilelist[currfd].fcbstate = 1; // 设置当前目录的 FCB 状态为已修改
}
8. void my_rm(char *filename)
删除文件
void my_rm(char* filename)
{
char buf[MAX_TEXT_SIZE];
openfilelist[currfd].count = 0;
do_read(currfd, openfilelist[currfd].length, buf);
int i, flag = 0;
fcb* fcbptr = (fcb*)buf;
// 查询
for (i = 0; i < (int)(openfilelist[currfd].length / sizeof(fcb)); i++, fcbptr++) {
if (strcmp(fcbptr->filename, filename) == 0 && fcbptr->attribute == 1) {
flag = 1;
break;
}
}
if (flag != 1) {
printf("no such file\n");
return;
}
// 更新 fat 表
int block_num = fcbptr->first;
fat* fat1 = (fat*)(myvhard + BLOCKSIZE);
int nxt_num = 0;
while (1) {
nxt_num = fat1[block_num].id;
fat1[block_num].id = FREE;
if (nxt_num != END)
block_num = nxt_num;
else
break;
}
fat1 = (fat*)(myvhard + BLOCKSIZE);
fat* fat2 = (fat*)(myvhard + BLOCKSIZE * 3);
memcpy(fat2, fat1, BLOCKSIZE * 2);
// 清空 fcb
fcbptr->date = 0;
fcbptr->time = 0;
fcbptr->exname[0] = '\0';
fcbptr->filename[0] = '\0';
fcbptr->first = 0;
fcbptr->free = 0;
fcbptr->length = 0;
openfilelist[currfd].count = i * sizeof(fcb);
do_write(currfd, (char*)fcbptr, sizeof(fcb), 3);
//
int lognum = i;
if ((lognum + 1) * sizeof(fcb) == openfilelist[currfd].length) {
openfilelist[currfd].length -= sizeof(fcb);
lognum--;
fcbptr = (fcb *)buf + lognum;
while (fcbptr->free == 0) {
fcbptr--;
openfilelist[currfd].length -= sizeof(fcb);
}
}
// 修改父目录 . 目录文件的 fcb
fcbptr = (fcb*)buf;
fcbptr->length = openfilelist[currfd].length;
openfilelist[currfd].count = 0;
do_write(currfd, (char*)fcbptr, sizeof(fcb), 3);
openfilelist[currfd].fcbstate = 1;
}
9. void my_cd(char* dirname)
切换目录
void my_cd(char* dirname)
{
int i = 0;
int tag = -1;
int fd;
if (openfilelist[currfd].attribute == 1) {
// 如果当前打开的是数据文件而非目录
printf("you are in a data file, you could use 'close' to exit this file\n");
return;
}
char* buf = (char*)malloc(10000);
openfilelist[currfd].count = 0;
do_read(currfd, openfilelist[currfd].length, buf); // 读取当前目录的内容到内存缓冲区
fcb* fcbptr = (fcb*)buf;
// 查找目标 FCB
for (i = 0; i < (int)(openfilelist[currfd].length / sizeof(fcb)); i++, fcbptr++) {
if (strcmp(fcbptr->filename, dirname) == 0 && fcbptr->attribute == 0) {
tag = 1;
break;
}
}
if (tag != 1) {
printf("my_cd: no such dir\n");
return; // 如果未找到对应的目录项,返回错误代码
} else {
// 检查 . 和 .. 目录
if (strcmp(fcbptr->filename, ".") == 0) {
return; // 如果是当前目录,直接返回,不进行操作
} else if (strcmp(fcbptr->filename, "..") == 0) {
if (currfd == 0) {
// 如果当前目录已经是根目录,直接返回
return;
} else {
currfd = my_close(currfd); // 关闭当前目录
return;
}
} else {
// 进入其他目录
fd = get_free_openfilelist(); // 获取一个空闲的打开文件表项,File Descriptor文件描述符
if (fd == -1) {
return; // 如果打开文件表已满,返回错误代码
}
// 更新打开文件表项的属性
openfilelist[fd].attribute = fcbptr->attribute;
openfilelist[fd].count = 0;
openfilelist[fd].date = fcbptr->date;
openfilelist[fd].time = fcbptr->time;
strcpy(openfilelist[fd].filename, fcbptr->filename);
strcpy(openfilelist[fd].exname, fcbptr->exname);
openfilelist[fd].first = fcbptr->first;
openfilelist[fd].free = fcbptr->free;
openfilelist[fd].fcbstate = 0;
openfilelist[fd].length = fcbptr->length;
strcat(strcat(strcpy(openfilelist[fd].dir, (char*)(openfilelist[currfd].dir)), dirname), "/"); // 更新目录路径信息
openfilelist[fd].topenfile = 1;
openfilelist[fd].dirno = openfilelist[currfd].first;
openfilelist[fd].diroff = i;
currfd = fd; // 更新当前文件描述符为新目录的文件描述符
}
}
free(buf); // 释放内存缓冲区
}
10. int my_open(char* filename)
打开文件
int my_open(char* filename)
{
char buf[MAX_TEXT_SIZE];//buf 被用来缓存读取的当前目录的内容,以便后续的重名检查和文件信息读取操作。
openfilelist[currfd].count = 0;
do_read(currfd, openfilelist[currfd].length, buf); // 读取当前目录的内容到内存缓冲区
int i, flag = 0;
fcb* fcbptr = (fcb*)buf;
// 重名检查
for (i = 0; i < (int)(openfilelist[currfd].length / sizeof(fcb)); i++, fcbptr++) {
if (strcmp(fcbptr->filename, filename) == 0 && fcbptr->attribute == 1) {
flag = 1;
break;
}
}
if (flag != 1) {
printf("no such file\n");
return -1; // 如果未找到对应的数据文件,返回错误代码
}
// 申请新的打开目录项并初始化该目录项
int fd = get_free_openfilelist(); // 获取一个空闲的打开文件表项
if (fd == -1) {
printf("my_open: full openfilelist\n");
return -1; // 如果打开文件表已满,返回错误代码
}
// 更新 fcb 块的属性
openfilelist[fd].attribute = 1; // 设置为数据文件
openfilelist[fd].count = 0; // 重置读写指针位置
openfilelist[fd].date = fcbptr->date; // 设置打开文件的日期属性
openfilelist[fd].time = fcbptr->time; // 设置打开文件的时间属性
openfilelist[fd].length = fcbptr->length; // 设置打开文件的长度属性
openfilelist[fd].first = fcbptr->first; // 设置打开文件的起始磁盘块号
openfilelist[fd].free = 1; // 设置打开文件状态为已打开
strcpy(openfilelist[fd].filename, fcbptr->filename); // 复制文件名到打开文件表
strcat(strcpy(openfilelist[fd].dir, (char*)(openfilelist[currfd].dir)), filename); // 更新打开文件的路径信息
openfilelist[fd].dirno = openfilelist[currfd].first; // 设置打开文件的父目录起始磁盘块号
openfilelist[fd].diroff = i; // 设置打开文件的目录项偏移量
openfilelist[fd].topenfile = 1; // 设置打开文件表项已被使用
openfilelist[fd].fcbstate = 0; // 设置打开文件的 FCB 状态为未修改
currfd = fd; // 更新当前文件描述符
return 1; // 返回成功打开文件的标志
}
11. int my_close(int fd)
关闭文件
int my_close(int fd)
{
// 检查文件描述符的合法性
if (fd >= MAXOPENFILE || fd < 0) {
printf("my_close: fd error\n");
return -1;
}
int i;
char buf[MAX_TEXT_SIZE];
int father_fd = -1;
fcb* fcbptr;
// 查找当前文件的父目录文件描述符
for (i = 0; i < MAXOPENFILE; i++) {
if (openfilelist[i].first == openfilelist[fd].dirno) {
father_fd = i;
break;
}
}
// 如果未找到父目录
if (father_fd == -1) {
printf("my_close: no father dir\n");
return -1;
}
// 如果该文件的 FCB 状态为已修改(fcbstate为1)
if (openfilelist[fd].fcbstate == 1) {
do_read(father_fd, openfilelist[father_fd].length, buf); // 读取父目录的内容到内存缓冲区
// 更新父目录中对应的 FCB 信息
fcbptr = (fcb*)(buf + sizeof(fcb) * openfilelist[fd].diroff);
strcpy(fcbptr->exname, openfilelist[fd].exname);
strcpy(fcbptr->filename, openfilelist[fd].filename);
fcbptr->first = openfilelist[fd].first;
fcbptr->free = openfilelist[fd].free;
fcbptr->length = openfilelist[fd].length;
fcbptr->time = openfilelist[fd].time;
fcbptr->date = openfilelist[fd].date;
fcbptr->attribute = openfilelist[fd].attribute;
openfilelist[father_fd].count = openfilelist[fd].diroff * sizeof(fcb);
do_write(father_fd, (char*)fcbptr, sizeof(fcb), 3); // 将修改后的 FCB 写回父目录
// 将父目录的长度更新为当前文件描述符的位置
openfilelist[father_fd].length = openfilelist[fd].count;
}
// 释放当前文件描述符对应的打开文件表项
memset(&openfilelist[fd], 0, sizeof(useropen));
// 更新当前文件描述符为父目录的文件描述符
currfd = father_fd;
return father_fd; // 返回父目录文件描述符
}
12. int my_read(int fd)
读取文件的入口
// 读取文件内容并输出到控制台
int my_read(int fd)
{
// 检查文件描述符的合法性
if (fd < 0 || fd >= MAXOPENFILE) {
printf("no such file\n");
return -1;
}
// 初始化文件指针位置为文件开头
openfilelist[fd].count = 0;
// 申请空间用于存放读取的文件内容
char text[MAX_TEXT_SIZE] = "\0";
// 调用 do_read 函数实际进行读取操作
do_read(fd, openfilelist[fd].length, text);
// 输出读取到的文件内容到控制台
printf("%s\n", text);
return 1;
}
13. int do_read(int fd, int len, char* text)
实际读取文件函数
// 实际读取文件内容的函数
int do_read(int fd, int len, char* text)
{
// 记录原始需要读取的长度,用于最后返回
int len_tmp = len;
// 设置用于读取的缓冲区
char* textptr = text;
unsigned char* buf = (unsigned char*)malloc(1024);
// 检查内存分配是否成功
if (buf == NULL) {
printf("do_read reg mem error\n");
return -1;
}
// 初始化文件指针在当前打开文件表项中的偏移量
int off = openfilelist[fd].count;
// 获取文件起始磁盘块号
int block_num = openfilelist[fd].first;
// 获取起始磁盘块对应的 FAT 表项
fat* fatptr = (fat*)(myvhard + BLOCKSIZE) + block_num;
// 定位读取目标磁盘块和块内地址
while (off >= BLOCKSIZE) {
off -= BLOCKSIZE;
// 获取下一个磁盘块号
block_num = fatptr->id;
// 检查磁盘块是否存在
if (block_num == END) {
printf("do_read: block not exist\n");
return -1;
}
// 获取下一个磁盘块对应的 FAT 表项
fatptr = (fat*)(myvhard + BLOCKSIZE) + block_num;
}
// 获取当前磁盘块的内容
unsigned char* blockptr = myvhard + BLOCKSIZE * block_num;
memcpy(buf, blockptr, BLOCKSIZE);
// 读取文件内容
while (len > 0) {
if (BLOCKSIZE - off > len) {
// 若当前磁盘块剩余内容足够,则直接拷贝到目标位置
memcpy(textptr, buf + off, len);
textptr += len;
off += len;
openfilelist[fd].count += len;
len = 0;
} else {
// 否则,先拷贝当前磁盘块剩余内容,再切换到下一磁盘块
memcpy(textptr, buf + off, BLOCKSIZE - off);
textptr += BLOCKSIZE - off;
len -= BLOCKSIZE - off;
// 获取下一个磁盘块号
block_num = fatptr->id;
// 检查磁盘块是否存在
if (block_num == END) {
printf("do_read: len is larger than file\n");
break;
}
// 获取下一个磁盘块对应的 FAT 表项
fatptr = (fat*)(myvhard + BLOCKSIZE) + block_num;
// 获取下一个磁盘块的内容
blockptr = myvhard + BLOCKSIZE * block_num;
memcpy(buf, blockptr, BLOCKSIZE);
}
}
// 释放缓冲区内存
free(buf);
// 返回实际读取的长度
return len_tmp - len;
}
14. int my_write(int fd)
文件写入函数入口
// 写文件操作,支持截断写、追加写两种方式
int my_write(int fd)
{
// 检查文件描述符的合法性
if (fd < 0 || fd >= MAXOPENFILE) {
printf("my_write: no such file\n");
return -1;
}
int wstyle;
while (1)
{
// 1: 截断写,清空全部内容,从头开始写
// 2. 追加写,字面意思
printf("1:Truncation 2:Addition\n");
scanf("%d", &wstyle);
if (wstyle > 3 || wstyle < 1)
{
printf("input error\n");
}
else
{
break;
}
}
// 读取用户输入的数据
char text[MAX_TEXT_SIZE] = "\0";
char texttmp[MAX_TEXT_SIZE] = "\0";
printf("please input data, line feed + $$ to end file\n");
getchar();
while (gets(texttmp)) {
if (strcmp(texttmp, "$$") == 0) {
break;
}
texttmp[strlen(texttmp)] = '\n';
strcat(text, texttmp);
}
text[strlen(text)] = '\0';
// 调用 do_write 函数实际进行写入操作
do_write(fd, text, strlen(text) + 1, wstyle);
// 标记文件已被修改
openfilelist[fd].fcbstate = 1;
return 1;
}
15. int do_write(int fd, char* text, int len, char wstyle)
实际文件写入
可以说是本文最重要的函数
你可以以3模式去写入内容,即wstyle,理论上应该是覆盖文件某一部分内容,具体要看写入指针off在哪。
但是实际操作中,并不能完成这种效果,也请大家帮忙看看为什么
// 实际执行写入文件操作的函数
int do_write(int fd, char* text, int len, char wstyle)
{
// 获取文件起始磁盘块号
int block_num = openfilelist[fd].first;//文件在虚拟磁盘上的第一个磁盘块的块号
int i, tmp_num;
int lentmp = 0;
// 初始化写入指针
char* textptr = text;
// 用于缓存磁盘块内容的数组
char buf[BLOCKSIZE];
// 获取文件起始磁盘块对应的 FAT 表项
fat* fatptr = (fat*)(myvhard + BLOCKSIZE) + block_num;
unsigned char* blockptr;
// 截断写,清空全部内容,从头开始写
if (wstyle == 1) {
openfilelist[fd].count = 0;
openfilelist[fd].length = 0;
} else if (wstyle == 2) {
// 追加写,如果是一般文件,则需要先删除末尾 \0,即将指针移到末位减一个字节处
openfilelist[fd].count = openfilelist[fd].length;
if (openfilelist[fd].attribute == 1) {
if (openfilelist[fd].length != 0) {
// 非空文件
openfilelist[fd].count = openfilelist[fd].length - 1;
}
}
}
int off = openfilelist[fd].count;//表示文件当前的读写指针位置,记录文件的当前读写指针位置(即偏移量)
// 定位磁盘块和块内偏移量
while (off >= BLOCKSIZE) {
block_num = fatptr->id;
if (block_num == END) {
printf("do_write: off error\n");
return -1;
}
fatptr = (fat*)(myvhard + BLOCKSIZE) + block_num;
off -= BLOCKSIZE;
}
blockptr = (unsigned char*)(myvhard + BLOCKSIZE * block_num);
// 写入磁盘
while (len > lentmp) {
// 拷贝当前磁盘块内容到缓冲区
memcpy(buf, blockptr, BLOCKSIZE);
for (; off < BLOCKSIZE; off++) {
// 将文本内容写入磁盘块
*(buf + off) = *textptr;
textptr++;
lentmp++;
if (len == lentmp)
break;
}
// 将缓冲区内容写回磁盘块
memcpy(blockptr, buf, BLOCKSIZE);
// 写入的内容太多,需要写到下一个磁盘块,如果没有磁盘块,就申请一个
if (off == BLOCKSIZE && len != lentmp) {
off = 0;
block_num = fatptr->id;
// 若当前磁盘块已满,申请新的磁盘块
if (block_num == END) {
block_num = get_free_block();
if (block_num == END) {
printf("do_write: block full\n");
return -1;
}
// 获取新磁盘块对应的指针
blockptr = (unsigned char*)(myvhard + BLOCKSIZE * block_num);
// 更新当前磁盘块的 FAT 表项
fatptr->id = block_num;
// 获取新磁盘块对应的 FAT 表项
fatptr = (fat*)(myvhard + BLOCKSIZE) + block_num;
fatptr->id = END;
} else {
// 获取当前磁盘块对应的指针
blockptr = (unsigned char*)(myvhard + BLOCKSIZE * block_num);
// 获取当前磁盘块的 FAT 表项
fatptr = (fat*)(myvhard + BLOCKSIZE) + block_num;
}
}
}
// 更新文件指针位置和长度
openfilelist[fd].count += len;
if (openfilelist[fd].count > openfilelist[fd].length)
openfilelist[fd].length = openfilelist[fd].count;
// 删除多余的磁盘块
if (wstyle == 1 || (wstyle == 3 && openfilelist[fd].attribute == 0)) {
off = openfilelist[fd].length;
fatptr = (fat *)(myvhard + BLOCKSIZE) + openfilelist[fd].first;
while (off >= BLOCKSIZE) {
block_num = fatptr->id;
off -= BLOCKSIZE;
fatptr = (fat *)(myvhard + BLOCKSIZE) + block_num;
}
while (1) {
if (fatptr->id != END) {
i = fatptr->id;
// 标记磁盘块为空闲状态
fatptr->id = FREE;
// 获取下一个磁盘块的 FAT 表项
fatptr = (fat *)(myvhard + BLOCKSIZE) + i;
} else {
// 将最后一个磁盘块的 FAT 表项标记为空闲状态
fatptr->id = FREE;
break;
}
}
// 获取最后一个磁盘块的 FAT 表项
fatptr = (fat *)(myvhard + BLOCKSIZE) + block_num;
// 将最后一个磁盘块的 FAT 表项标记为结束标志
fatptr->id = END;
}
// 更新 FAT 表
memcpy((fat*)(myvhard + BLOCKSIZE * 3), (fat*)(myvhard + BLOCKSIZE), BLOCKSIZE * 2);
return len;
}
16. int get_free_openfilelist()
寻找空闲文件的索引
/**
* @brief 获取一个可用的打开文件表项的索引
*
* @return int 如果找到可用的打开文件表项,返回其索引;如果打开文件表已满,返回 -1。
*/
int get_free_openfilelist()//返回的是第一个空闲的文件的索引
{
int i;
// 遍历打开文件表
for (i = 0; i < MAXOPENFILE; i++) {
// 查找第一个未被占用的表项
if (openfilelist[i].topenfile == 0) {
// 将该表项标记为已被占用,并返回其索引
openfilelist[i].topenfile = 1;
return i;
}
}
// 如果打开文件表已满,返回 -1
return -1;
}
17. unsigned short int get_free_block()
寻找空闲块号
// 获取一个空闲磁盘块的块号
unsigned short int get_free_block()
{
int i;
// 从虚拟磁盘中的 FAT(文件分配表)开始遍历
fat* fat1 = (fat*)(myvhard + BLOCKSIZE);
// 遍历 FAT 表中的每个项,SIZE / BLOCKSIZE 表示磁盘块的总数
for (i = 0; i < (int)(SIZE / BLOCKSIZE); i++) {
// 如果 FAT 表中的某个磁盘块的状态为 FREE,表示该磁盘块空闲
if (fat1[i].id == FREE) {
// 返回该空闲磁盘块的块号
return i;
}
}
// 如果没有找到空闲磁盘块,则返回 END,表示磁盘块分配失败
return END;
}
18. void help()
查看帮助,实际未完成
void help()
{
printf("xxxxxxx\n");
}
总结
本系统基本实现了期末作业所要求的功能,但仍然存在不可理解的运行逻辑和bug,期待寒假搭完LFS后能够对文件系统有更多理解后。为此代码增加命令存储栈、文件锁等功能。