基于C++实现的模拟文件系统

Gentleman

发布日期: 2021-04-04 14:44:56 浏览量: 177
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

1.实验要求

  • 设计和实现一个模拟文件系统,要求包括目录、普通文件和文件的存储

  • 文件系统的目录结构采用类似Linux的树状结构

  • 要求模拟的操作包括:

    • 目录的添加、删除、重命名
    • 目录的显示(列表)
    • 文件的添加、删除、重命名
    • 文件和目录的拷贝
    • 文件的读写操作
  • 用户进入时显示可用命令列表;用户输入help时显示所有命令的帮助文档

    • 输入某个命令+?时显示该条命令的使用说明
  • 用户输入exit时退出该系统

  • 实验实现基于LINUX平台。

  • 实验开发语言必须选用C/C++,不能选用JAVA

2.实验环境

调试环境

  • 操作系统:Ubuntu 16.04 TLS

  • 内存:3.5GiB

  • 处理器:AMD E-350 Processor×2

  • 图形:AMD PALM(DRM 2.50.0/4.15.0-45-generic,LLVM 6.6.6)

  • 操作系统类型:64位

  • 磁盘:30.4GB

开发环境

  • 开发工具:Visual Studio 2017

  • 操作系统:Windows 10 家庭中文版

  • 处理器:Inter(R) Core(TM) i7-8565u CPU \@ 1.8GHz 1.99 GHz

  • 内存:8.00 GB

  • 系统类型:64位操作系统,基于x64的处理器

3.实验设计

3.1 系统流程

整体系统操作模拟Ubuntu命令行,具体流程如下:

3.2 文件结构

整体系统采用属性结构组织文件,具体图示如下:

3.3 实现的命令

命令 用法 说明 选项
cd cd [dir] 显示当前目录名或改变当前目录
ls ls [dir] 显示当前或指定路径下所有文件和目录
mkdir mkdir dir 在当前目录下建立一个新目录
touch touch file 在当前目录下新建一个新文件
gedit gedit file 读写指定的文件
rm rm -d\ -f file\ dir 删除指定的目录或文件 -d:删除目录 -f:删除文件
cp cp -d\ -f\ -cd\ -cf SOURSE DEST 从原路径复制一个文件或目录到目的路径下 -d:复制目录 -f:复制文件 -cd:复制目录,但不在原路径下保留原目录 -cf:复制文件,但不在原路径下保留原文件
rename rename -d\ -f oldname newname 更改指定文件或目录的名字 -d:重命名目录 -f:重命名文件
su su 更改当前用户
cls cls 清屏
exit exit 退出文件系统
help help 显示帮助文档

4.数据结构

为简化代码结构,系统未采用面向对象编程的思想。将用户、文件、目录分别封装成一个struct结构体。

4.1 用户数据结构

  1. struct user
  2. {
  3. string name;//用户名
  4. string password;//密码
  5. };

用户结构体中包含用户的用户名以及密码。

4.2 文件数据结构

  1. struct file
  2. {
  3. string name;//文件名
  4. vector<string> content;//文件内容
  5. user owner;//文件所有者
  6. };

文件结构体包含文件名、文件内容以及文件所有者。需要特别指出的是,目前系统实现的多用户权限可以概括为:文件创建者为文件所有者,非文件所有者可以知道该文件的存在,但不能对该文件执行读写、复制、删除等操作。

4.3 目录数据结构

  1. struct dir {
  2. string name;//目录名
  3. dir* pre;//父目录
  4. map<string, file*> files;//所包含的文件
  5. map<string, dir*> next;//子目录
  6. };

目录结构体包含目录名、父目录、当前目录下的文件以及直接子目录。其中,为方便代码编写,后两个成员使用map容器包装,其中map的first为文件名(或目录名),second为对应的文件指针(或目录指针)。

5.模块详解

5.1 用户指令

以下按3.3节展开。

5.1.1 cd

显示当前目录的绝对路径或改变当前目录。

流程图

关键代码

  1. //cd
  2. void cd(string name) {
  3. if (name == "") {
  4. //显示当前目录的绝对路径
  5. }
  6. else {
  7. dir* tmp = pathTrans(name);//解析路径
  8. if (tmp == NULL) {
  9. cout << "No Such Directory.\n";
  10. }
  11. else {
  12. curdir = tmp;//进入用户输入的路径
  13. }
  14. }
  15. }

5.1.2 ls

显示当前目录下或指定路径下所有文件和目录。

流程图

关键代码

  1. //ls
  2. void ls(string path) {
  3. dir *tmp = curdir;
  4. if (path != "") {
  5. curdir = pathTrans(path);//解析路径
  6. if (curdir == NULL) {
  7. //输出错误提示
  8. }
  9. }
  10. //遍历输出文件和目录信息
  11. for (auto it = curdir->files.begin(); it != curdir->files.end(); it++) {
  12. }
  13. for (auto it = curdir->next.begin(); it != curdir->next.end(); it++) {
  14. }
  15. curdir = tmp;
  16. }

5.1.3 mkdir

在当前目录下建立一个新目录。mkdir dir,其中dir表示新建目录的目录名。

流程图

关键代码

  1. //创建目录
  2. void mkdir(string name) {
  3. if (name == "") {
  4. cout << "Require Parameters" << endl;
  5. }
  6. else if (curdir->next.find(name) != curdir->next.end()) {
  7. cout << "There is a directory having same name.\n";
  8. }
  9. else if (!judgeName(name)) {
  10. cout << "Name has at least a illegal character.\n";
  11. }
  12. else {
  13. dir *tmp = new dir();//一定要这样创建,否则字符串后面就不能读取
  14. tmp->name = name;
  15. tmp->pre = curdir;
  16. curdir->next[name] = tmp;
  17. }
  18. }

5.1.4 touch

在当前目录下新建一个新文件。touch file,其中file表示新建文件的文件名

流程图

关键代码

  1. //建立文件
  2. void touch(string name) {
  3. if (name == "") {
  4. cout << "Require Parameters" << endl;
  5. }
  6. else if (curdir->files.find(name) != curdir->files.end()) {
  7. cout << "There is a same file.\n";
  8. }
  9. else if (!judgeName(name)) {
  10. cout << "Name has at least a illegal character.\n";
  11. }
  12. else
  13. {
  14. //建立对应的新文件
  15. }
  16. }

5.1.5 gedit

读写指定的文件。gedit file,其中file表示需要读写的文件,可以用绝对路径或相对路径指定。

流程图

关键代码

  1. //编辑文件
  2. void gedit(string name) {
  3. dir *t = curdir;
  4. if (name.find_last_of('/') != name.npos) {
  5. //解析路径
  6. }
  7. //是否存在目标文件
  8. if (curdir->files.find(name) == curdir->files.end()) {}
  9. //目标文件是否为当前用户所拥有
  10. else if (curdir->files[name]->owner.name != curuser.name) {}
  11. else {
  12. ofstream out("tmp.dat");
  13. //将文件当前内容输入临时文件
  14. for (int i = 0; i < curdir->files[name]->content.size(); i++) {}
  15. out.close();
  16. //用gedit打开临时文件
  17. system("gedit tmp.dat");
  18. //读取临时文件中的内容,存入文件
  19. ifstream in("tmp.dat");
  20. while (getline(in, t)) {
  21. //读取临时文件内容
  22. }
  23. }
  24. curdir = t;
  25. }

因本文件系统完全运行在内存中,创立的文件与目录并未存在实际磁盘中。因此,为提高用户读写文件的体验,在此引入了一个临时文件机制:将存储在内存中的文件暂时存储到临时文件中,然后用gedit打开这个临时文件。在用户编辑完成后,再将临时文件中的内容转存到内存中。

Gedit是一个GNOME桌面环境下兼容UTF-8的文本编辑器。它使用GTK+编写而成,因此它十分的简单易用,有良好的语法高亮,对中文支持很好,支持包括gb2312、gbk在内的多种字符编码。

虽然这样的操作效率不高,但是借助gedit强大的功能,能给用户带来一种编辑真实文件的体验。

5.1.6 rm

删除指定的目录或文件。

用法:rm -d|-f

  • file|dir,其中file与dir代表需要删除的文件或目录,目录(或文件)均可用绝对路径或者相对路径表示
  • 选项:
    • -d:删除目录
    • -f:删除文件

流程图

关键代码

  1. //删除
  2. void rm(string tmp) {
  3. //选项解析,路径解析
  4. //删除目录
  5. if (option == "-d") {
  6. //是否存在目标目录
  7. if (curdir->next.find(name) == curdir->next.end()) {}
  8. else {
  9. deletedir(curdir->next[name]);//递归删除
  10. }
  11. }
  12. else if (option == "-f") {
  13. //是否存在目标目录
  14. if (curdir->files.find(name) == curdir->files.end()) {}
  15. //是否为文件所有者
  16. else if (curdir->files[name]->owner.name != curuser.name) {}
  17. else {
  18. delete(curdir->files[name]);
  19. }
  20. }
  21. }

因本系统采用树形的目录结构,因此在删除目录时需要用到递归,如deletedir函数所示。

  1. //递归删除目录
  2. void deletedir(dir *cur) {
  3. //先删文件
  4. for (auto it = cur->files.begin(); it != cur->files.end(); it++) {
  5. delete(it->second);
  6. }
  7. cur->files.clear();
  8. //再删目录,要嵌套删除
  9. for (auto it = cur->next.begin(); it != cur->next.end(); it++) {
  10. deletedir(it->second);
  11. }
  12. cur->next.clear();
  13. delete(cur);
  14. }

5.1.7 cp

复制一个文件或目录到指定路径下。

  • 用法:cp -d|-f|-cd|-cf SOURCE DEST
    • 其中SOURCE为需要复制的文件或目录,DEST为需要复制到的路径。SOURCE与DEST均可用绝对路径或者相对路径表示
    • 选项:
      • -d:复制目录
      • -f:复制文件
      • -cd:复制目录,但不在原路径下保留原目录
      • -cf:复制文件,但不在原路径下保留原文件

流程图

关键代码

  1. //复制
  2. void cp(string tmp) {
  3. //解析选项与路径
  4. if (option == "-f") {
  5. //是否有同名文件
  6. if (den->files.find(name) != den->files.end()) {}
  7. //是否存在目标文件
  8. else if (sou->files.find(name) == sou->files.end()) {}
  9. //是否为当前用户所拥有
  10. else if (curdir->files[name]->owner.name != curuser.name) {}
  11. else {
  12. file *tmp = new file(*(sou->files[name]));//复制文件
  13. den->files[name] = tmp;//放入目的目录下
  14. }
  15. }
  16. else if (option == "-d") {
  17. //是否有同名目录
  18. if (den->next.find(name) != den->next.end()) {}
  19. //是否存在目标目录
  20. else if (sou->next.find(name) == sou->next.end()) {}
  21. else {
  22. dir *tmp = cpDir(sou->next[name]);//递归复制目录
  23. tmp->pre = den;
  24. den->next[name] = tmp;//放入到目的目录下
  25. }
  26. }
  27. else if (option == "-cf") {
  28. //是否有同名文件
  29. if (den->files.find(name) != den->files.end()){}
  30. //是否存在目标文件
  31. else if (sou->files.find(name) == sou->files.end()){}
  32. //是否为当前用户所拥有
  33. else if (curdir->files[name]->owner.name != curuser.name){}
  34. else
  35. {
  36. den->files[name] = sou->files[name];//放入到目的目录下
  37. sou->files.erase(name);
  38. }
  39. }
  40. else if (option == "-cd") {
  41. //是否有同名目录
  42. if (den->next.find(name) != den->next.end()){}
  43. //是否存在目标目录
  44. else if (sou->next.find(name) == sou->next.end()){}
  45. else
  46. {
  47. den->next[name] = sou->next[name];//放入到目的目录下
  48. sou->next.erase(name);
  49. }
  50. }
  51. }

跟删除类似,在复制目录时需要用到递归复制,如cpDir所示。

  1. //递归复制目录
  2. dir* cpDir(dir *tmp) {
  3. dir *goal = new dir(*tmp);
  4. //清除原来的内容
  5. goal->next.clear();
  6. goal->files.clear();
  7. //把文件重建
  8. for (auto it = tmp->files.begin(); it != tmp->files.end(); it++) {
  9. file *f = new file(*(it->second));
  10. goal->files[it->first] = f;
  11. }
  12. //重建目录
  13. for (auto it = tmp->next.begin(); it != tmp->next.end(); it++) {
  14. dir *d = cpDir(it->second);
  15. d->pre = goal;
  16. goal->next[it->first] = d;
  17. }
  18. return goal;
  19. }

5.1.8 rename

更改指定文件或目录的名字。

  • 用法:rename -d|-f oldname newname
    • oldname代表需要重命名的目录或文件,newname代表重命名后的名字。oldname可以使用绝对路径或相对路径
    • 选项:
      • -d:重命名目录
      • -f:重命名文件

流程图

关键代码

  1. void rename(string tmp) {
  2. //解析路径与选项
  3. if (!judgeName(newname)) {}//新名字中是否有非法字符
  4. if (option == "-d") {
  5. if (curdir->next.find(old) == curdir->next.end()) {}//是否存在目标目录
  6. else if (curdir->next.find(newname) != curdir->next.end()) {}//是否存在与新名字重名的目录
  7. else {
  8. //重命名
  9. }
  10. }
  11. else if (option == "-f") {
  12. if (curdir->files.find(old) == curdir->files.end()) {}//是否存在对应文件
  13. else if (curdir->files.find(newname) != curdir->files.end()) {}//是否存在与新名字重名的文件
  14. else if (curdir->files[old]->owner.name != curuser.name) {}//需重命名文件是否为当前用户拥有
  15. else {
  16. //重命名
  17. }
  18. }
  19. }

5.1.9 su

更改当前用户(调用login函数,详见下文)。

5.1.10 cls

清屏。

5.1.11 exit

退出文件系统(主要调用save函数,详见下文)。

5.1.12 help

显示帮助文档。

5.2 其他系统操作

5.2.1 登录

流程图

关键代码

  1. void login() {
  2. bool flag = 1;
  3. map<string, string> users;//所有注册用户
  4. //打印欢迎语
  5. ifstream in("user.dat");
  6. string tname, tpass;
  7. while (in >> tname >> tpass) {}//读入所有注册用户的信息
  8. //输入用户名
  9. while (flag) {
  10. if (users.find(tname) == users.end()) {}//注册用户中是否有输入用户
  11. else {
  12. if (users[tname] == tpass) {
  13. //核对用户名与密码是否匹配
  14. }
  15. else {
  16. printf("password is incorrect!\n");
  17. }
  18. }
  19. }
  20. if (!flag) {
  21. printf("This user is not exist.\nDo you want to creat a new user?(y/n):");
  22. char choice;
  23. cin >> choice;
  24. if (choice == 'Y' || choice == 'y') {
  25. //注册
  26. }
  27. else {
  28. login();
  29. }
  30. }
  31. //重新存回
  32. ofstream out("user.dat");
  33. for (auto it = users.begin(); it != users.end(); it++) {}
  34. return;
  35. }

5.2.2 保存系统状态

为提高用户体验,每次用户使用exit命令退出系统时,系统会保存退出前的系统状态。

保存系统状态的主要实现思想是:通过递归遍历系统的目录结构,以字符串向量的形式存储包括目录名、文件名、文件内容在内的所有信息。最后将字符串向量存入record.dat文件。字符串向量内部的结构类似xml文件。具体流程图与代码如下:

exit函数

save函数

具体代码

  1. //退出系统
  2. void exit() {
  3. records.clear();
  4. save(root);
  5. ofstream outr("record.dat");
  6. for (int i = 0; i < records.size(); i++) {
  7. outr << records[i] << endl;
  8. }
  9. }
  1. //存储目前情况
  2. void save(dir *tmp) {
  3. records.push_back(tmp->name);//目录名
  4. records.push_back(to_string(tmp->files.size()));//文件数
  5. for (auto it = tmp->files.begin(); it != tmp->files.end(); it++) {
  6. records.push_back(it->second->name);//文件名
  7. for (int i = 0; i < it->second->content.size(); i++) {
  8. records.push_back( it->second->content[i]);//文件内容
  9. }
  10. records.push_back("content");//文件内容结束符
  11. records.push_back(it->second->owner.name);//所有者用户名
  12. records.push_back(it->second->owner.password);//所有者密码
  13. }
  14. records.push_back(to_string(tmp->next.size()));//子目录数
  15. for (auto it = tmp->next.begin(); it != tmp->next.end(); it++) {
  16. records.push_back(it->second->name);//子目录名
  17. save(it->second);//递归子目录
  18. }
  19. }

为方便调试,record.dat文件以明文存储,文件内容如下:

5.2.3 恢复系统状态

为提高用户体验,每次打开系统时,系统会根据record.dat文件存储的系统状态恢复上一次退出前的系统状态。

恢复系统状态的实现思想与保存系统状态(5.2.2)类似,根据record.dat的数据重建目录结构。
具体流程图与代码如下:

init函数

creat函数

具体代码

  1. void init() {
  2. ifstream inr("record.dat");
  3. string tmp;
  4. if (!inr) {
  5. initDir();
  6. }
  7. while (inr >> tmp)
  8. records.push_back(tmp);
  9. if (records.size() >= 1) {
  10. root = curdir = creat(NULL);
  11. }
  12. else {
  13. initDir();
  14. }
  15. }
  1. //还原上一次系统关闭状态
  2. dir* creat(dir *last) {
  3. dir *tmp = new dir();//新建目录
  4. tmp->name=records[reco++];//读取目录名
  5. tmp->pre = last;//设置父指针
  6. string t;
  7. t= records[reco++];//读取文件数
  8. for (int i = 0; i < stoi(t); i++) {
  9. file *tfile = new file();//新建文件
  10. tfile->name= records[reco++];//读取文件名
  11. while (1) {
  12. string ts;
  13. ts= records[reco++];//读取文件内容
  14. if (ts != "content") {//不是关键字就持续读入内容
  15. tfile->content.push_back(ts);
  16. }
  17. else {
  18. break;
  19. }
  20. }
  21. user a;
  22. a.name = records[reco++];//读取用户名
  23. a.password= records[reco++];//密码
  24. tfile->owner = a;
  25. tmp->files[tfile->name] = tfile;//将新建的文件加入目录
  26. }
  27. t= records[reco++];//子目录数
  28. for (int i = 0; i < stoi(t); i++) {
  29. string name;
  30. name= records[reco++];//子目录名
  31. tmp->next[name] = creat(tmp);//递归新建子目录
  32. }
  33. return tmp;
  34. }

5.2.4 路径解析

为提高用户体验,系统引入了路径解析的机制,可以让包括cd、ls、gedit在内的多种操作同时支持相对路径与绝对路径。绝对路径与相对路径的表示方式与Ubuntu系统相同。

路径解析输入的参数为绝对路径或相对路径,返回的结果是目标目录的指针。具体流程图与代码如下:

流程图

关键代码

  1. dir* pathTrans(string path) {
  2. string tmp = path;
  3. //绝对路径
  4. if (path[0] == '~' || path[0] == '/') {
  5. dir *cur = root;
  6. if (path[0] == '/')path = "~" + path;
  7. vector<string> tmp = split(path);//按照/分割路径
  8. for (int i = 1; i < tmp.size(); i++) {
  9. if (cur->next.find(tmp[i]) == cur->next.end()) {
  10. return NULL;
  11. }
  12. cur = cur->next[tmp[i]];
  13. }
  14. return cur;
  15. }
  16. //相对路径
  17. else {
  18. dir *cur = curdir;
  19. vector<string> tmp = split(path);//按照/分割路径
  20. for (int i = 0; i < tmp.size(); i++) {
  21. if (tmp[i] == ".") {
  22. }
  23. else if (tmp[i] == "..") {
  24. if (cur == root) {//不能再往上走了
  25. return NULL;
  26. }
  27. else {
  28. cur = cur->pre;
  29. }
  30. }
  31. else if (cur->next.find(tmp[i]) == cur->next.end()) {
  32. return NULL;
  33. }
  34. else if (cur->next.find(tmp[i]) != cur->next.end()) {
  35. cur = cur->next[tmp[i]];
  36. }
  37. }
  38. return cur;
  39. }
  40. return NULL;
  41. }

6.实验演示

6.1 登录

欢迎界面

当用户名与密码不匹配,系统会让用户反复输入密码,直到正确为止:

当输入用户名与不在已注册用户中,若用户同意创建用户,系统会根据先前输入的用户名与密码创建用户:

6.2帮助文档与使用说明

用户输入help时显示所有命令的帮助文档:

输入某个命令+?时显示该条命令的使用说明:

6.3 cd命令

红框为目前根节点下拥有的目录;黄框展示使用相对路径、绝对路径更改目录,以及回到上一级目录的情况;蓝框展示遇到错误路径的情况。

6.4 ls命令

黄框展示有参及无参ls命令的使用情况;蓝框展示遇到错误路径的情况。

6.5 mkdir命令

红框展示目前根节点下拥有的目录;黄框展示新建一个paly目录的情况;蓝框展示遇到重名目录的情况;第三张图展示新建目录名中存在非法字符的情况。

6.6 touch命令

红框展示~/study/college目录下拥有的文件;黄框展示新建一个test2.txt文件的情况;蓝框展示遇到重名文件的情况;展示新建文件名中存在非法字符的情况。

6.7 gedit命令

红框展示~/study/college目录下拥有的文件;黄框展示读写test2.txt文件的情况,截图下半部为gedit打开临时文件;蓝框展示遇到缺少文件名及文件非当前用户拥有的情况。

6.8 rm命令

红框展示根目录下拥有的文件与目录;黄框展示删除work目录的情况;绿框展示删除test.txt文件的情况;蓝框展示遇到不存在目录或文件的情况。

6.9 cp命令

红框展示~/study目录与~/work目录下拥有的文件与目录;黄框展示复制college目录到~/work目录的情况。

红框展示~/work与~/study/college目录下拥有的文件与目录;黄框展示复制test2.txt文件到~/work目录的情况。

红框展示~/work与~/study/college目录下拥有的文件与目录;黄框展示剪贴test2.txt文件到~/work目录的情况。

红框展示~/work与~/study目录下拥有的文件与目录;黄框展示剪贴college目录到~/work目录的情况。

6.10 用户权限

系统实现的多用户权限可以概括为:文件创建者为文件所有者,非文件所有者可以知道该文件的存在,但不能对该文件执行读写、复制、删除等操作。下图展示了非文件所有者不能对文件执行gedit、rm、rename、cp命令的情况:

上传的附件 cloud_download 基于C++实现的模拟文件系统.7z ( 3.09mb, 2次下载 )
error_outline 下载需要8点积分

发送私信

一个人幸运的前提,其实是ta有能力改变自己

10
文章数
24
评论数
最近文章
eject