├── FileManageSystem ├── README.md ├── commands.py ├── config.py ├── file_pointer.py ├── file_system.py ├── file_ui.py ├── initialize_disk.py ├── models.py ├── running_pfs.py ├── user.py └── utils.py ├── LICENSE ├── README.md ├── docs ├── bug解决.md ├── 命令实现.md ├── 基本知识点.md ├── 文件系统中的文件结构.md └── 空闲块分配.md ├── terminal.png └── test ├── __init__.py ├── test_models.py ├── test_running_pfs.py └── test_utils.py /FileManageSystem/README.md: -------------------------------------------------------------------------------- 1 | # 设计实现 2 | 3 | ## 三层结构 4 | 5 | [models.py](models.py)数据结构实现存放基本的数据结构和最底层的方法 6 | 7 | [file_system.py](file_system.py)文件系统实现存放文件系统封装底层数据结构和方法 8 | 9 | [commands.py](commands.py)命令实现基于文件系统提供的接口组合使用实现各种命令 -------------------------------------------------------------------------------- /FileManageSystem/commands.py: -------------------------------------------------------------------------------- 1 | """ 2 | author:Wenquan Yang 3 | time:2020/6/14 20:18 4 | intro:命令模块 5 | """ 6 | import pickle 7 | from threading import Thread 8 | from config import * 9 | from utils import check_auth 10 | from utils import color 11 | from utils import line 12 | from file_system import FileSystem 13 | from file_ui import TextEdit 14 | 15 | 16 | def useradd(fs: FileSystem, *args): 17 | """ 18 | 添加用户,并将用户目录挂载到base/home/下 19 | :param fs: 20 | :return: 21 | """ 22 | if args and args[0] == '-h': 23 | print(""" 24 | 添加用户 25 | 使用方法直接输入useradd不需要参数 26 | 只有root用户可以使用此命令 27 | 添加完用户后会在base/home/下新建对应的用户文件目录 28 | 命名为用户名 29 | """) 30 | return 31 | 32 | if fs.current_user_id != ROOT_ID: 33 | print("非root用户无权添加账户") 34 | return 35 | user_name, user_id = fs.add_user(fs.current_user_id) 36 | 37 | # 通过base目录切换到home目录 38 | base_cat = fs.load_base_obj() 39 | home_id = base_cat.get_dir('home') 40 | home_inode = fs.get_inode(home_id) 41 | home_cat = home_inode.get_target_obj(fs.fp) 42 | 43 | if not home_cat: 44 | return 45 | 46 | # 获取新的目录的inode,并添加到home目录中 47 | new_inode = fs.get_new_inode(user_id=user_id) 48 | home_cat.add_new_cat(name=user_name, inode_id=new_inode.i_no) 49 | 50 | # 新建新的用户目录 51 | new_cat = fs.get_new_cat(name=user_name, parent_inode_id=home_id) 52 | 53 | # 写回新建的目录,和home目录 54 | fs.write_back(new_inode, bytes(new_cat)) 55 | fs.write_back(home_inode, bytes(home_cat)) 56 | 57 | # 写回对应的被修改的节点 58 | new_inode.write_back(fs.fp) 59 | home_inode.write_back(fs.fp) 60 | 61 | 62 | @line 63 | def pwd(fs: FileSystem, *args): 64 | """ 65 | 打印当前目录的完整路径 66 | :param fs: 67 | :return: 68 | """ 69 | print(fs.pwd()) 70 | 71 | 72 | def clear(fs: FileSystem, *args): 73 | """ 74 | 清空终端输出 75 | :param fs: 76 | :return: 77 | """ 78 | fs.clear() 79 | 80 | 81 | def cls(fs: FileSystem, *args): 82 | fs.clear() 83 | 84 | 85 | def su(fs: FileSystem, *args): 86 | """ 87 | 切换当前用户指令 88 | :param fs: 89 | :param username: 90 | :return: 91 | """ 92 | username = args[0] 93 | if username == '-h': 94 | print(""" 95 | 切换用户 96 | su username 97 | root用户切换其他账户不需要密码 98 | 普通账户切换需要输入密码 99 | """) 100 | return 101 | 102 | if fs.login(username=username): 103 | if username != 'root': 104 | cd(fs, f'~/home/{username}') 105 | else: 106 | cd(fs, f'~/{ROOT}') 107 | 108 | 109 | def mkdir(fs: FileSystem, name: str): 110 | """ 111 | 新建目录 112 | 新建索引对象-->新建目录对象-->将前两者作为键值对加入当前索引对应的目录-->申请空间存放新建的目录 113 | :param fs: 114 | :param name: 文件夹名称 115 | :return: 116 | """ 117 | if name == '-h': 118 | print(""" 119 | 新建文件夹 120 | mkdir dirname 121 | 只可以在当前目录的新建不可跳级新建 122 | 获得当前目录对象pwd_obj 123 | 检查命名冲突,pwd_obj.check_name(name) 124 | 获取新的inode对象 125 | 将新建文件夹的名字和inode号作为键值对写回pwd_obj 126 | 写回新建的目录对象new_obj,并且将其开辟的新的地址块号添加到对应的inode对象中 127 | 写回新的inode对象 128 | """) 129 | return 130 | 131 | pwd_cat = fs.load_pwd_obj() # 当前目录 132 | flag, info = pwd_cat.check_name(name) 133 | if not flag: 134 | print(info) 135 | return 136 | 137 | new_inode = fs.get_new_inode(user_id=fs.current_user_id) 138 | pwd_cat.add_new_cat(name=name, inode_id=new_inode.i_no) 139 | new_cat = fs.get_new_cat(name=name, parent_inode_id=fs.pwd_inode.i_no) 140 | fs.write_back(new_inode, bytes(new_cat)) # 写回新建目录 141 | fs.write_back(fs.pwd_inode, bytes(pwd_cat)) # 写回当前目录,因为新建的目录挂载当前目录也被修改了 142 | new_inode.write_back(fs.fp) 143 | 144 | 145 | def cd(fs: FileSystem, *args: str): 146 | """ 147 | 切换目录,可以多级目录切换 148 | :param fs: 149 | :param args: 切换到的目录名 150 | :return: True/False 表示切换是否完全成功 151 | """ 152 | if args[0] == '-h': 153 | print(""" 154 | 切换目录 155 | dirName:要切换到的目标目录名 156 | cd hello 切换一级目录 157 | cd hello\hello 切换多级目录 158 | cd ..\.. 切换上层目录 159 | cd ~ 切换到根目录 160 | """) 161 | else: 162 | fs.chdir(args[0]) 163 | 164 | 165 | def cp(fs: FileSystem, *args): 166 | """ 167 | 复制文件/目录参数-r 168 | :param fs: 169 | :param args: 170 | :return: 171 | """ 172 | if args[0] == '-h': 173 | print(""" 174 | 复制文件 175 | cp xx/xx/src_filename xx/xx/tgt_dir 176 | 复制文件到其他目录 177 | 支持跨目录层级调用 178 | 仅支持复制文件 179 | """) 180 | return 181 | 182 | if args[0] == '-r': 183 | path_src = args[1] 184 | path_tgt = args[2] 185 | else: 186 | path_src = args[0] 187 | path_tgt = args[1] 188 | name = path_src.split('/')[-1] # 取出文件名 189 | cnt1 = len(path_src.split('/')) - 1 # 第一个目录的深度 190 | cnt2 = len(path_tgt.split('/')) # 第二个目录的深度 191 | text_copy = "" # 文件内容 192 | cd(fs, '/'.join(path_src.split('/')[:-1])) 193 | pwd_cat = fs.load_pwd_obj() 194 | flag = pwd_cat.is_exist_son_files(name) 195 | if flag == -1: 196 | print("{} 文件不存在".format(name)) 197 | cd(fs, '/'.join(['..'] * cnt1)) 198 | return 199 | else: 200 | if flag == FILE_TYPE: 201 | inode_io = pwd_cat.son_files[name] 202 | inode = fs.get_inode(inode_id=inode_io) 203 | flag2, text = fs.load_files_block(inode) 204 | if flag2: 205 | text_copy = text # 传递内容 206 | if flag == DIR_TYPE: 207 | print("不能复制文件夹") 208 | cd(fs, '/'.join(['..'] * cnt1)) 209 | return 210 | 211 | cd(fs, '/'.join(['..'] * cnt1)) 212 | # 增加到现在的目录下 213 | cd(fs, '/'.join(path_tgt.split('/'))) 214 | touch(fs, name) 215 | pwd_cat_new = fs.load_pwd_obj() 216 | new_inode_io = pwd_cat_new.son_files[name] 217 | new_inode = fs.get_inode(inode_id=new_inode_io) 218 | fs.write_back(new_inode, pickle.dumps(text_copy)) 219 | new_inode.write_back(fs.fp) 220 | cd(fs, '/'.join(['..'] * cnt2)) 221 | 222 | 223 | def mv(fs: FileSystem, *args): 224 | """ 225 | 剪切文件或目录 mv home/ywq/demo home/caohang 226 | :param fs: 227 | :param args: 228 | :return: 229 | """ 230 | if args[0] == '-h': 231 | print(""" 232 | 剪切文件或目录: 233 | mv xx/xx/src xx/xx/tgt 234 | 可以跨目录层级剪切目录或文件到其他目录 235 | """) 236 | return 237 | 238 | path_src = args[0] # home/ywq/demo 239 | path_tgt = args[1] # home/caohang 240 | name = path_src.split('/')[-1] # 取出文件名 241 | cnt1 = len(path_src.split('/')) - 1 # 第一个目录的深度 242 | cnt2 = len(path_tgt.split('/')) # 第二个目录的深度 243 | inode_io = 0 244 | # 删掉原来的目录 245 | cd(fs, '/'.join(path_src.split('/')[:-1])) 246 | pwd_cat = fs.load_pwd_obj() # 当前目录块 247 | flag = pwd_cat.is_exist_son_files(name) 248 | if flag == -1: 249 | print("{} 文件不存在".format(name)) 250 | cd(fs, '/'.join(['..'] * cnt1)) 251 | return 252 | else: 253 | if flag == FILE_TYPE: 254 | inode_io = pwd_cat.son_files[name] 255 | if flag == DIR_TYPE: 256 | inode_io = pwd_cat.son_dirs[name] 257 | pwd_cat.remove(name, flag) 258 | fs.write_back(fs.pwd_inode, bytes(pwd_cat)) 259 | cd(fs, '/'.join(['..'] * cnt1)) 260 | 261 | # 增加到现在的目录下 262 | cd(fs, '/'.join(path_tgt.split('/'))) 263 | pwd_cat_new = fs.load_pwd_obj() # 要增加的目录块 264 | if flag == FILE_TYPE: 265 | pwd_cat_new.add_new_file(name, inode_io) 266 | if flag == DIR_TYPE: 267 | inode = fs.get_inode(inode_io) 268 | new_cat = inode.get_target_obj(fs.fp) 269 | new_cat.parent_inode_id = fs.pwd_inode.i_no 270 | pwd_cat_new.add_new_cat(name, inode_io) 271 | fs.write_back(inode, bytes(new_cat)) 272 | fs.write_back(fs.pwd_inode, bytes(pwd_cat_new)) 273 | cd(fs, '/'.join(['..'] * cnt2)) 274 | 275 | 276 | def rename(fs: FileSystem, *args): 277 | """ 278 | 修改名字 rename name1 name2 279 | :param fs: 280 | :param args: 281 | :return: 282 | """ 283 | if args[0] == '-h': 284 | print(""" 285 | 修改名字 286 | rename src_name tgt_name 287 | 不可跨目录层级使用 288 | 可以修改文件/目录的名字 289 | """) 290 | return 291 | 292 | name1 = args[0] 293 | name2 = args[1] 294 | 295 | pwd_cat = fs.load_pwd_obj() # 当前目录 296 | flag = pwd_cat.is_exist_son_files(name1) 297 | if flag == -1: 298 | print("{} 文件不存在".format(name1)) 299 | else: 300 | if name2 in pwd_cat.son_files or name2 in pwd_cat.son_dirs: 301 | print("{} 文件重名".format(name2)) 302 | else: 303 | if flag == FILE_TYPE: 304 | pwd_cat.add_new_file(name2, pwd_cat.son_files[name1]) 305 | if flag == DIR_TYPE: 306 | pwd_cat.add_new_cat(name2, pwd_cat.son_dirs[name1]) 307 | pwd_cat.remove(name1, flag) 308 | fs.write_back(fs.pwd_inode, bytes(pwd_cat)) 309 | 310 | 311 | def touch(fs: FileSystem, name: str): 312 | """ 313 | 新建文件 314 | :param fs: 315 | :param name: 316 | :return: 317 | """ 318 | if name == '-h': 319 | print(""" 320 | 新建文件 321 | touch file_name 322 | 不可跨目录层级调用 323 | """) 324 | return 325 | 326 | pwd_cat = fs.load_pwd_obj() # 当前目录 327 | flag, info = pwd_cat.check_name(name) 328 | if not flag: 329 | print(info) 330 | return 331 | 332 | new_inode = fs.get_new_inode(user_id=fs.current_user_id) 333 | new_inode.target_type = 0 # 文件 334 | pwd_cat.add_new_file(name, new_inode.i_no) 335 | fs.write_back(fs.pwd_inode, bytes(pwd_cat)) 336 | new_inode.write_back(fs.fp) 337 | 338 | 339 | def vim(fs: FileSystem, name: str): 340 | """ 341 | 向文件中输入内容,或者是修改文件 342 | :param fs: 343 | :param name: 344 | :return: 345 | """ 346 | if name == '-h': 347 | print(""" 348 | 编辑文件中的内容 349 | vim file_name 350 | 不可跨层级调用 351 | 命令使用后会打开一个线程单独运行文本编辑器 352 | """) 353 | return 354 | 355 | pwd_cat = fs.load_pwd_obj() # 当前目录 356 | flag = pwd_cat.is_exist_son_files(name) 357 | if flag == -1: 358 | touch(fs, name) 359 | vim(fs, name) 360 | return 361 | if flag == DIR_TYPE: 362 | print("{} 是文件夹".format(name)) 363 | if flag == FILE_TYPE: 364 | inode_io = pwd_cat.get_file(name) 365 | inode = fs.get_inode(inode_id=inode_io) 366 | if check_auth(inode.user_id, fs.current_user_id): 367 | def func(): 368 | flag_x, s = fs.load_files_block(inode) 369 | if not s: 370 | s = '' 371 | te = TextEdit(s) 372 | te.run() 373 | s = te.s 374 | if len(pickle.dumps(s)) <= (13 * BLOCK_SIZE - 100): 375 | fs.write_back(inode, pickle.dumps(s)) 376 | inode.write_back(fs.fp) 377 | else: 378 | print("out of size") 379 | 380 | vim_thread = Thread(target=func) 381 | vim_thread.start() 382 | else: 383 | print("cannot edit file .: Permission denied") 384 | 385 | 386 | @line 387 | def more(fs: FileSystem, name: str): 388 | """ 389 | 展示文件 390 | :param fs: 391 | :param name: 392 | :return: 393 | """ 394 | 395 | if name and name == '-h': 396 | print(""" 397 | 显示文本内容 398 | more file_name 399 | 不可跨目录层级调用 400 | 完全显示文本内容 401 | """) 402 | return 403 | 404 | pwd_cat = fs.load_pwd_obj() # 当前目录 405 | flag = pwd_cat.is_exist_son_files(name) 406 | if flag == -1: 407 | print("{} 文件不存在".format(name)) 408 | if flag == DIR_TYPE: 409 | print("{} 是文件夹".format(name)) 410 | if flag == FILE_TYPE: 411 | inode_io = pwd_cat.get_file(name) 412 | inode = fs.get_inode(inode_id=inode_io) 413 | flag, text = fs.load_files_block(inode) 414 | if flag: 415 | print(text) 416 | else: 417 | print("cannot open file .: Permission denied") 418 | 419 | 420 | def tree_x(fs: FileSystem, depth: int, level=0): 421 | """ 422 | 生成文件目录结构,具体样式百度 423 | . 424 | └── test 425 | ├── css 426 | ├── img 427 | │   └── head 428 | └── js 429 | :param fs: 430 | :param depth: 打印的深度 431 | :param level: 已经到达的文件深度 432 | :return: 433 | """ 434 | if depth == 0: 435 | return 436 | pwd_cat = fs.load_pwd_obj() # 获取当前目录 437 | file_list = pwd_cat.file_name_and_types() 438 | for name, flag in file_list: 439 | if flag == DIR_TYPE: # 文件夹 440 | print("│ " * level, end="") 441 | print("├──", color(name, DIR_COLOR_F, DIR_COLOR_B)) 442 | flag_x = fs.ch_sig_dir(name, info=False) 443 | if flag_x: 444 | tree_x(fs, depth - 1, level + 1) 445 | fs.ch_sig_dir("..") 446 | if flag == FILE_TYPE: # 文件 447 | print("│ " * level, end="") 448 | print("├──", color(name, FILE_COLOR_F, FILE_COLOR_B)) 449 | 450 | 451 | @line 452 | def tree(fs: FileSystem, *args): 453 | if args and args[0] == '-h': 454 | print(""" 455 | 打印目录结构 456 | tree 单独使用打印一层 457 | tree -d n 指定打印层数 458 | """) 459 | return 460 | 461 | depth = 1 462 | if args: 463 | if args[0] == '-d': 464 | depth = int(args[1]) 465 | if depth < 1: 466 | depth = 1 467 | tree_x(fs, depth) 468 | 469 | 470 | @line 471 | def ls(fs: FileSystem, *args): 472 | """ 473 | 打印当前目录下的全部文件 474 | :param fs: 475 | :return: 476 | """ 477 | if args and args[0] == '-h': 478 | print(""" 479 | 打印当前目录下的全部文件 480 | ls 481 | 绿色表示目录 482 | 白色表示普通文件 483 | """) 484 | return 485 | 486 | pwd_cat = fs.load_pwd_obj() 487 | file_list = pwd_cat.file_name_and_types() 488 | for name, flag in file_list: 489 | if flag == DIR_TYPE: 490 | print(color(name, DIR_COLOR_F, DIR_COLOR_B), end=' ') 491 | elif flag == FILE_TYPE: 492 | print(color(name, FILE_COLOR_F, FILE_COLOR_B), end=' ') 493 | print() 494 | 495 | 496 | @line 497 | def ll(fs: FileSystem, *args): 498 | """ 499 | 打印当前目录下的具体信息 500 | :param fs: 501 | :return: 502 | """ 503 | if args and args[0] == '-h': 504 | print(""" 505 | 打印当前目录下的全部文件及详细信息 506 | ll 507 | 绿色表示目录 508 | 白色表示普通文件 509 | 打印内容分别是: 510 | 1.文件数(普通文件是1,目录是其下一级所包含的文件数目) 511 | 2.上次修改日期(2020-06-19 00:22:42) 512 | 3.文件大小 (512B 整数倍) 513 | 4.拥有者的ID 514 | 5.文件/目录 名 515 | """) 516 | return 517 | fs.show_lls_info() 518 | 519 | 520 | @line 521 | def stat(fs: FileSystem, name): 522 | if name == '-h': 523 | print(""" 524 | 显示文件详情 525 | stat filename/dirname 526 | 不可跨目录层级调用 527 | """) 528 | return 529 | pwd_cat = fs.load_pwd_obj() 530 | type_x = pwd_cat.is_exist_son_files(name) 531 | if type_x == -1: 532 | return 533 | inode_id = pwd_cat.get_inode_id(name, type_x) 534 | inode = fs.get_inode(inode_id) 535 | inode.show_detail_info(fs.fp) 536 | 537 | 538 | @line 539 | def detail(fs: FileSystem, *args): 540 | if args and args[0] == '-h': 541 | print(""" 542 | 显示系统信息: 543 | 系统名 544 | 索引块使用情况 545 | 数据块使用情况 546 | """) 547 | return 548 | fs.show() 549 | 550 | 551 | def rm(fs: FileSystem, *args): 552 | """ 553 | 删除文件 554 | :param fs: 555 | :param args: 参数 556 | :return: 557 | """ 558 | if args[0] == '-h': 559 | print(""" 560 | 删除文件或目录 561 | rm filename 删除当前文件 562 | rm -r dirname 递归删除目录及其下所有内容 563 | 不可跨目录层级调用 564 | """) 565 | return 566 | 567 | pwd_cat = fs.load_pwd_obj() 568 | power = False # 删除力度 569 | if args[0] == '-r': 570 | power = True 571 | name = args[1] 572 | else: 573 | name = args[0] 574 | 575 | flag = pwd_cat.is_exist_son_files(name) 576 | if flag == -1: 577 | print("文件不存在") 578 | else: 579 | inode_id = -1 580 | if flag == DIR_TYPE: 581 | if not power: 582 | print("无法直接删除目录请使用 rm -r dir_name") 583 | return 584 | inode_id = pwd_cat.get_dir(name) 585 | elif flag == FILE_TYPE: 586 | 587 | if not power: 588 | dose = input(f"rm:是否删除一般文件“{name}”[Y/N]:") 589 | if dose.lower() != 'y': 590 | return 591 | inode_id = pwd_cat.get_file(name) 592 | 593 | if inode_id != -1: 594 | flag_x = fs.free_up_inode(inode_id) 595 | if flag_x: 596 | # 从当前目录中删除并将当前目录写回 597 | pwd_cat.remove(name, flag) 598 | fs.write_back(fs.pwd_inode, bytes(pwd_cat)) 599 | else: 600 | print("cannot delete directory/file .: Permission denied") 601 | 602 | 603 | def main(*args): 604 | """ 605 | 打印支持的命令 606 | :return: 607 | """ 608 | print(""" 609 | 这是一个模拟的文件系统 610 | fms.pfs用于模拟磁盘,会在系统运行的时候加载系统关闭时关闭 611 | 系统中的信息和用户文件都存放于fms.pfs中,系统运行时进行加载 612 | 基于最基本linux中的inode---dir_block---inode---data_block结构 613 | 包含基本的block有superblock,inode,datablock(dirblock,fileblock) 614 | 不同于linux中使用bitmap进行空间分配,本系统使用成组链接法进行分配 615 | 616 | 支持多用户多级目录,以及用户访问权限划分 617 | 618 | 支持的命令有 (通过cmd -h 查看使用 例如 (useradd -h,su -h,tree -h,)) 619 | 添加用户 useradd 620 | 切换用户 su username 621 | 当前路径 pwd 622 | 清空屏幕 clear(cls) 623 | 新建目录 mkdir dirname 624 | 新建文件 touch filename 625 | 编辑文件 vim filename 626 | 显示文件 more filename 627 | 切换目录 cd targetdir 628 | 复制功能 cp srcname tgtpath 629 | 剪切功能 mv srcname tgtpath 630 | 重命名 rename srcname tgtname 631 | 目录结构 tree -d n 632 | 目录内容 ls 633 | 目录详情 ll 634 | 文件信息 stat filename/dirname 635 | 系统信息 detail 636 | 删除文件 rm [-r] filename/dirname 637 | """) 638 | -------------------------------------------------------------------------------- /FileManageSystem/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | author:Wenquan Yang 3 | time:2020/6/9 1:36 4 | content:配置文件 5 | """ 6 | 7 | BLOCK_SIZE = 512 # 磁盘块大小Bytes 8 | BLOCK_NUM = 2560 # 磁盘块总数量 9 | 10 | SUPER_BLOCK_NUM = 2 # 超级块占用的块数 11 | INODE_BLOCK_NUM = 256 # 索引占用的块数 12 | DATA_BLOCK_NUM = BLOCK_NUM - SUPER_BLOCK_NUM - INODE_BLOCK_NUM 13 | 14 | INODE_BLOCK_START_ID = SUPER_BLOCK_NUM 15 | 16 | DATA_BLOCK_START_ID = SUPER_BLOCK_NUM + INODE_BLOCK_NUM + 1 # 数据块的起始地址 17 | 18 | INODE_SIZE = 512 # INODE占用的块区大小 19 | 20 | DISK_SIZE = BLOCK_SIZE * BLOCK_NUM # 磁盘大小 21 | DISK_NAME = "../fms.pfs" 22 | DIR_NUM = 128 # 每个目录锁包含的最大文件数 23 | FREE_NODE_CNT = 50 # 超级块中空闲节点的最大块数 24 | FREE_BLOCK_CNT = 100 # 超级块中空闲数据块的最大块数 25 | 26 | BASE_NAME = "base" # 根目录名 27 | 28 | FILE_TYPE = 0 # 文件类型 29 | DIR_TYPE = 1 # 目录类型 30 | 31 | ROOT_ID = 0 32 | ROOT = 'root' 33 | 34 | INIT_DIRS = ['root', 'home', 'etc'] 35 | 36 | VERSION = "V 1.2" 37 | 38 | LOGO = """ 39 | ____ ______ _____ 40 | / __ \ / ____// ___/ 41 | / /_/ // /_ \__ \\ 42 | / ____// __/ ___/ / 43 | /_/ /_/ /____/ 44 | """ 45 | 46 | # color 47 | FILE_COLOR_F = "37" # 文件名前景色 48 | FILE_COLOR_B = "40" # 文件名背景色 49 | 50 | DIR_COLOR_F = "32" 51 | DIR_COLOR_B = "40" 52 | -------------------------------------------------------------------------------- /FileManageSystem/file_pointer.py: -------------------------------------------------------------------------------- 1 | """ 2 | author:Wenquan Yang 3 | time:2020/6/11 0:09 4 | """ 5 | from config import * 6 | 7 | 8 | class FilePointer: 9 | """ 10 | 对基本的文件模块做一个扩充, 11 | 将一些基本的配置放出初始化部分使其更加适应我们自定义的文件系统 12 | 目前这这部分是直接硬封装没加扩展,后续看情况加(咕咕 13 | """ 14 | 15 | def __init__(self, mode): 16 | self._fp = open(DISK_NAME, mode) 17 | self._fp.seek(DISK_SIZE + 1, 0) 18 | self._fp.write(b'\x00') 19 | self._fp.seek(0) 20 | 21 | def seek(self, size): 22 | self._fp.seek(size) 23 | 24 | def write(self, obj): 25 | assert isinstance(obj, bytes) 26 | self._fp.write(obj) 27 | 28 | def read(self, size=512): 29 | return self._fp.read(size) 30 | 31 | def close(self): 32 | self._fp.close() 33 | 34 | def __enter__(self): 35 | return self 36 | 37 | def __exit__(self, exc_type, exc_val, exc_tb): 38 | self._fp.close() 39 | 40 | 41 | def file_func(mode): 42 | """ 43 | 文件装饰器,添加此装饰器就不需要关注文件的打开关闭 44 | :param mode: 读写方式,主要针对初始化磁盘'wb+'和加载磁盘'ab+' 45 | :return: 46 | """ 47 | 48 | def func_wrapper(func): 49 | def return_wrapper(): 50 | with FilePointer(mode) as fp: 51 | res = func(fp) 52 | return res 53 | 54 | return return_wrapper 55 | 56 | return func_wrapper 57 | -------------------------------------------------------------------------------- /FileManageSystem/file_system.py: -------------------------------------------------------------------------------- 1 | """ 2 | author:Wenquan Yang 3 | time:2020/6/12 22:30 4 | intro: 文件系统 5 | """ 6 | import os 7 | import getpass 8 | import pickle 9 | from config import * 10 | from utils import form_serializer 11 | from utils import split_serializer 12 | from utils import check_auth 13 | from utils import logo 14 | from utils import color 15 | from models import SuperBlock 16 | from models import INode 17 | from models import CatalogBlock 18 | from file_pointer import FilePointer 19 | from user import User 20 | 21 | 22 | class FileSystem: 23 | """ 24 | 文件系统,负责磁盘中数据的加载与写入 25 | 对整个文件系统所有数据结构以及操作的封装,后续所有功能扩展的调用都从这里调用 26 | """ 27 | 28 | def __init__(self, fp): 29 | self.fp = fp 30 | self.load_disk() 31 | self.sp = self.load_disk() 32 | self.base_inode = self.get_base_inode() 33 | self.pwd_inode = self.base_inode 34 | self.path = ['base'] # 用于存储当前路径,每个文件名是一个item 35 | self.current_user_id = ROOT_ID 36 | self.current_user_name = 'root' 37 | self.user_counts = 0 38 | 39 | def __enter__(self): 40 | """ 41 | 文件系统的启动部分 42 | :return: 43 | """ 44 | self.clear() 45 | self.login() 46 | self.show_info() 47 | if self.current_user_name != ROOT: 48 | self.chdir(f'home/{self.current_user_name}') 49 | else: 50 | self.chdir(f'{ROOT}') 51 | return self 52 | 53 | def __exit__(self, exc_type, exc_val, exc_tb): 54 | """ 55 | 上下文管理,退出的时候写回超级块,根节点inode,当前节点inode 56 | :param exc_type: 57 | :param exc_val: 58 | :param exc_tb: 59 | :return: 60 | """ 61 | self.sp.write_back(self.fp) 62 | self.base_inode.write_back(self.fp) 63 | self.pwd_inode.write_back(self.fp) 64 | 65 | def ch_sig_dir(self, name, info=True): 66 | """ 67 | 单级目录切换 68 | :param name: 69 | :return: 70 | """ 71 | pwd_cat = self.load_pwd_obj() 72 | if name == "..": 73 | target_id = pwd_cat.parent_inode_id 74 | if target_id == -1: 75 | return False 76 | elif name == "~": 77 | target_id = self.get_base_dir_inode_id() 78 | else: 79 | target_id = pwd_cat.get_dir(name) 80 | 81 | if target_id: 82 | inode = self.get_inode(target_id) 83 | 84 | if not check_auth(inode.user_id, self.current_user_id): 85 | if self.current_user_id != ROOT_ID: 86 | if name not in [*INIT_DIRS[1:], "~"]: 87 | if name == '..' and self.path[-2] in [*INIT_DIRS[1:], BASE_NAME]: 88 | pass 89 | else: 90 | if info: 91 | print("cannot open directory .: Permission denied") 92 | return False 93 | self.write_back_pwd_inode() 94 | self.pwd_inode = inode 95 | if name == "..": 96 | self.path_pop() 97 | elif name == "~": 98 | self.path_clear() 99 | else: 100 | self.path_add(self.get_pwd_cat_name()) 101 | return True 102 | return False 103 | 104 | def chdir(self, args): 105 | """ 106 | 切换目录 107 | :param args: 108 | :return: 109 | """ 110 | name_list = args.split('/') 111 | for name in name_list: 112 | if not self.ch_sig_dir(name): 113 | break 114 | 115 | def _get_password_file_inode_id(self): 116 | base_cat = self.load_base_obj() 117 | target_id = base_cat.get_dir('etc') 118 | target_inode = self.get_inode(target_id) 119 | target_dir = target_inode.get_target_obj(self.fp) 120 | password_file_inode_id = target_dir.get_file('password') 121 | return password_file_inode_id 122 | 123 | def _create_password_file(self): 124 | pwd_cat = self.load_pwd_obj() 125 | target_id = pwd_cat.get_dir('etc') 126 | target_inode = self.get_inode(target_id) 127 | target_dir = target_inode.get_target_obj(self.fp) 128 | new_inode = self.get_new_inode(user_id=ROOT_ID, file_type=FILE_TYPE) 129 | target_dir.add_new_file('password', new_inode.i_no) 130 | self.write_back(target_inode, bytes(target_dir)) 131 | target_inode.write_back(self.fp) 132 | new_inode.write_back(self.fp) 133 | return new_inode.i_no 134 | 135 | def _init_root_user(self): 136 | print("系统初始状态,创建root用户请设置密码:") 137 | flag = 3 138 | password1 = 'admin' 139 | while flag > 0: 140 | password1 = getpass.getpass("输入密码:") 141 | password2 = getpass.getpass("确认密码:") 142 | if password1 == password2: 143 | break 144 | else: 145 | print("两次密码不一致重新输入") 146 | flag -= 1 147 | root = User(name='root', password=password1, user_id=ROOT_ID) 148 | password_file_inode_id = self._create_password_file() 149 | password_file_inode = self.get_inode(inode_id=password_file_inode_id) 150 | self.write_back(password_file_inode, pickle.dumps([root])) 151 | password_file_inode.write_back(self.fp) 152 | return password_file_inode_id 153 | 154 | def login(self, username=None): 155 | """ 156 | 登录模块 157 | :return: 158 | """ 159 | self.clear() 160 | print("=用户登录=") 161 | password_file_inode_id = self._get_password_file_inode_id() 162 | if not password_file_inode_id: 163 | password_file_inode_id = self._init_root_user() 164 | password_inode = self.get_inode(password_file_inode_id) 165 | password_list = password_inode.get_target_obj(self.fp) 166 | self.user_counts = len(password_list) 167 | if not username: 168 | flag = 3 169 | while flag > 0: 170 | username = input("用户名:") 171 | password = getpass.getpass("密码:") 172 | 173 | for item in password_list: 174 | assert isinstance(item, User) 175 | if item.login(username, password): 176 | self.current_user_id = item.user_id 177 | self.current_user_name = item.name 178 | return True 179 | flag -= 1 180 | print("用户名或密码错误") 181 | exit(0) 182 | else: 183 | password = None 184 | if self.current_user_id != ROOT_ID: 185 | password = getpass.getpass("密码:") 186 | for item in password_list: 187 | assert isinstance(item, User) 188 | if item.login(username, password, root_user=True): 189 | self.current_user_id = item.user_id 190 | self.current_user_name = item.name 191 | return True 192 | print("用户名或密码错误") 193 | return False 194 | 195 | def show_lls_info(self): 196 | """ 197 | 打印当前目录下的详细文件信息 198 | :return: 199 | """ 200 | pwd_cat = self.load_pwd_obj() 201 | for name, inode_id in pwd_cat.son_list(): 202 | inode = self.get_inode(inode_id) 203 | res = inode.show_ll_info(self.fp) 204 | if inode.target_type == DIR_TYPE: 205 | name = color(name, DIR_COLOR_F, DIR_COLOR_B) 206 | else: 207 | name = color(name, FILE_COLOR_F, FILE_COLOR_B) 208 | print(' '.join(res) + ' ' + name) 209 | 210 | def add_user(self, user_id): 211 | if not check_auth(ROOT_ID, user_id): 212 | print("非root账户无权新建账户") 213 | return 214 | password_file_inode_id = self._get_password_file_inode_id() 215 | password_inode = self.get_inode(password_file_inode_id) 216 | password_list = password_inode.get_target_obj(self.fp) 217 | flag = 3 218 | username = 'user' + str(self.user_counts) 219 | password = 'admin' 220 | while flag > 0: 221 | username = input("输入用户名:") 222 | for item in password_list: 223 | if username == item.name: 224 | print("用户名重复") 225 | flag -= 1 226 | continue 227 | password1 = getpass.getpass("输入密码:") 228 | password2 = getpass.getpass("确认密码:") 229 | if password1 != password2: 230 | print("密码不一致") 231 | flag -= 1 232 | continue 233 | else: 234 | password = password1 235 | break 236 | password_list.append(User(username, password, self.user_counts)) 237 | self.user_counts += 1 238 | self.write_back(password_inode, pickle.dumps(password_list)) 239 | return username, self.user_counts - 1 240 | 241 | def get_base_dir_inode_id(self): 242 | return self.sp.base_dir_inode_id 243 | 244 | def get_current_path_name(self): 245 | return self.path[-1] 246 | 247 | def pwd(self): 248 | return '/'.join(self.path) 249 | 250 | def load_disk(self): 251 | """ 252 | 加载磁盘,获取超级块 253 | :return: 254 | """ 255 | self.fp.seek(0) 256 | return SuperBlock.form_bytes((form_serializer(self.fp, SUPER_BLOCK_NUM))) # 加载超级块 257 | 258 | def path_clear(self): 259 | while len(self.path) > 1: 260 | self.path.pop(-1) 261 | 262 | def path_add(self, name): 263 | self.path.append(name) 264 | 265 | def path_pop(self): 266 | self.path.pop(-1) 267 | 268 | def get_base_inode(self): 269 | """ 270 | 获取根目录的inode 271 | :return: INode 272 | """ 273 | base_inode_id = self.sp.base_dir_inode_id # 读取超级块中保存的base节点的块号 274 | self.fp.seek((INODE_BLOCK_START_ID + base_inode_id) * BLOCK_SIZE) 275 | return INode.form_bytes(self.fp.read()) # 根据块号加载根目录的inode 276 | 277 | def load_pwd_obj(self): 278 | """ 279 | 获取当前inode对应的对象 280 | :return: CatalogBlock 281 | """ 282 | return self.pwd_inode.get_target_obj(self.fp) 283 | 284 | def load_base_obj(self): 285 | """ 286 | 获取base_inode对应的目录对象 287 | :return: 288 | """ 289 | return self.base_inode.get_target_obj(self.fp) 290 | 291 | def load_files_block(self, inode: INode): 292 | """ 293 | 获取对应inode文件的内容 294 | :return:反序列化的内容 295 | """ 296 | if check_auth(inode.user_id, self.current_user_id): 297 | return True, inode.get_target_obj(self.fp) 298 | else: 299 | return False, None 300 | 301 | def get_pwd_cat_name(self): 302 | """ 303 | 获取当前inode对应的目录的名称 304 | :return: 305 | """ 306 | return self.load_pwd_obj().name 307 | 308 | def get_new_inode(self, user_id=10, file_type=DIR_TYPE): 309 | """ 310 | 获取新的inode 311 | :return:inode对象 312 | """ 313 | inode_id = self.sp.get_free_inode_id(self.fp) 314 | return INode(i_no=inode_id, user_id=user_id, target_type=file_type) 315 | 316 | def get_inode(self, inode_id): 317 | """ 318 | 获取inode对象 319 | :param inode_id: 320 | :return: 321 | """ 322 | self.fp.seek((INODE_BLOCK_START_ID + inode_id) * BLOCK_SIZE) 323 | inode = INode.form_bytes(self.fp.read()) 324 | return inode 325 | 326 | @staticmethod 327 | def get_new_cat(name, parent_inode_id): 328 | return CatalogBlock(name, parent_inode_id) 329 | 330 | def write_back(self, inode: INode, serializer: bytes): 331 | """ 332 | 申请空闲的数据块并将id添加到inode的栈中 333 | 写回新建的目录或者是文本 334 | :param inode: 335 | :param serializer: 336 | :return: 337 | """ 338 | assert isinstance(serializer, bytes) 339 | i_sectors = inode.i_sectors 340 | k = 0 341 | inode.clear() 342 | for item in split_serializer(serializer): 343 | if i_sectors[k] != -1: 344 | data_block_id = i_sectors[k] 345 | else: 346 | data_block_id = self.sp.get_data_block_id(self.fp) 347 | inode.add_block_id(data_block_id) 348 | self.fp.seek((data_block_id + DATA_BLOCK_START_ID) * BLOCK_SIZE) 349 | self.fp.write(item) 350 | self.fp.seek((data_block_id + DATA_BLOCK_START_ID) * BLOCK_SIZE) 351 | k += 1 352 | 353 | # 如果有多余的则释放空间 354 | for block_id in i_sectors[k:]: 355 | if block_id == -1: 356 | break 357 | self.sp.free_up_data_block(self.fp, block_id) 358 | 359 | def write_back_pwd_inode(self): 360 | """ 361 | 写回当前的pwd_inode 362 | :return: 363 | """ 364 | self.pwd_inode.write_back(self.fp) 365 | 366 | def free_up_inode(self, inode_id: int): 367 | """ 368 | 释放文件对应的inode,同时级联释放inode指向的空间 369 | 如果指向的是inode指向的是目录则递归删除 370 | :return: 371 | """ 372 | inode = self.get_inode(inode_id) 373 | if not check_auth(inode.user_id, self.current_user_id): 374 | return False 375 | if inode.target_type == DIR_TYPE: 376 | inode_target = inode.get_target_obj(self.fp) 377 | for son_inode_id in inode_target.get_all_son_inode(): 378 | self.free_up_inode(son_inode_id) 379 | for i in range(inode.i_sectors_state): 380 | self.sp.free_up_data_block(self.fp, inode.get_sector(i)) 381 | self.sp.free_up_inode_block(self.fp, inode_id) 382 | return True 383 | 384 | def show_info(self): 385 | self.clear() 386 | self.show() 387 | 388 | def show(self): 389 | print("Welcome to the PFS") 390 | logo() 391 | self.sp.show_sp_info() 392 | 393 | def clear(self): 394 | os.system('cls') 395 | 396 | 397 | def file_system_func(func): 398 | """ 399 | 文件系统的装饰器包装上下文管理器,简化实际编写的时候的代码不需要使用with直接在函数上加个装饰器即可 400 | :param func: 401 | :return: 402 | """ 403 | 404 | def func_wrapper(): 405 | with FilePointer('rb+') as fp: 406 | with FileSystem(fp) as fs: 407 | res = func(fs) 408 | return res 409 | 410 | return func_wrapper 411 | -------------------------------------------------------------------------------- /FileManageSystem/file_ui.py: -------------------------------------------------------------------------------- 1 | """ 2 | author:Wenquan Yang 3 | time:2020/6/12 22:30 4 | intro: 文本编辑器可视化界面 5 | """ 6 | import tkinter as tk 7 | from tkinter import scrolledtext 8 | 9 | 10 | class TextEdit: 11 | def __init__(self, text: str): 12 | self.win = TextWindow('文本编辑器') 13 | self.s = text 14 | self.flag = False 15 | self.layout(text) 16 | 17 | def layout(self, s): 18 | frame = tk.Frame(self.win, width=300, height=80) 19 | frame.pack(fill=tk.X, ipady=2, expand=False) 20 | btn_save = tk.Button(frame, text='保存', command=self.save) # 创建按钮用于保存文件 21 | 22 | # 放置按钮 23 | btn_save.pack(side=tk.TOP, anchor=tk.E, ipadx=10) 24 | 25 | btn_save.configure(font=("Consolas", 16)) 26 | 27 | # 创建滚动多行文本框,用于编辑文件 28 | self.text = scrolledtext.ScrolledText(self.win, wrap=tk.WORD) 29 | self.text.pack(side=tk.BOTTOM) 30 | self.text.configure(font=("Consolas", 16)) 31 | self.text.insert(tk.INSERT, s) # s为传入的字符串,显示内容 32 | 33 | def run(self): 34 | self.win.mainloop() # 进入消息循环 35 | 36 | def save(self): 37 | """ 38 | 保存修改内容 39 | """ 40 | self.flag = True 41 | self.s = self.text.get(0.0, tk.END) # 获取文本内容 42 | self.s = self.s.strip() 43 | 44 | 45 | class TextWindow(tk.Tk): 46 | def __init__(self, title): 47 | super().__init__() 48 | self.title(title) 49 | -------------------------------------------------------------------------------- /FileManageSystem/initialize_disk.py: -------------------------------------------------------------------------------- 1 | """ 2 | author:Wenquan Yang 3 | time:2020/6/11 1:24 4 | intro: 磁盘格式化部分 5 | """ 6 | from models import * 7 | from file_pointer import file_func 8 | from utils import * 9 | 10 | 11 | @file_func('wb') 12 | def initialization(fp): 13 | # 超级块写入 14 | sp = SuperBlock() 15 | 16 | # 索引链接写入 17 | tmp = INODE_BLOCK_NUM 18 | start = 0 19 | while tmp > 0: 20 | sp.inode_unused_cnt -= 1 21 | if tmp < FREE_NODE_CNT: 22 | inode_group_link = INodeGroupLink(start, tmp) 23 | else: 24 | inode_group_link = INodeGroupLink(start) 25 | inode_group_link.write_back(fp) 26 | start += FREE_NODE_CNT 27 | tmp -= FREE_NODE_CNT 28 | 29 | # 数据块链接写入 30 | tmp = DATA_BLOCK_NUM 31 | start = 0 32 | while tmp > 0: 33 | sp.block_unused_cnt -= 1 34 | if tmp < FREE_BLOCK_CNT: 35 | block_group_link = BlockGroupLink(start, tmp) 36 | else: 37 | block_group_link = BlockGroupLink(start) 38 | block_group_link.write_back(fp) 39 | start += FREE_BLOCK_CNT 40 | tmp -= FREE_BLOCK_CNT 41 | 42 | # 初始化一个根目录 base_inode and base_dir 43 | inode_id = sp.get_free_inode_id(fp) 44 | inode = INode(inode_id, ROOT_ID) 45 | base_dir = CatalogBlock(BASE_NAME) 46 | 47 | for file_name in INIT_DIRS: 48 | new_dir(sp, fp, base_dir, file_name, inode_id) 49 | 50 | # 写回根目录 51 | dir_write_back(sp, inode, bytes(base_dir), fp) 52 | inode.write_back(fp) 53 | 54 | # 写入超级块 55 | sp.base_dir_inode_id = inode_id 56 | sp.write_back(fp) 57 | 58 | 59 | def new_dir(sp, fp, base_dir, name, parent_inode_id): 60 | inode_id = sp.get_free_inode_id(fp) 61 | inode = INode(inode_id, ROOT_ID) 62 | base_dir.add_new_cat(name=name, inode_id=inode_id) 63 | dir_write_back(sp, inode, bytes(CatalogBlock(name, parent_inode_id)), fp) 64 | inode.write_back(fp) 65 | 66 | 67 | def dir_write_back(sp: SuperBlock, inode: INode, dir_b: bytes, fp): 68 | for block in split_serializer(dir_b): 69 | block_id = sp.get_data_block_id(fp) 70 | inode.add_block_id(block_id) 71 | fp.seek((block_id + DATA_BLOCK_START_ID) * BLOCK_SIZE) 72 | fp.write(block) 73 | 74 | 75 | if __name__ == '__main__': 76 | initialization() 77 | -------------------------------------------------------------------------------- /FileManageSystem/models.py: -------------------------------------------------------------------------------- 1 | """ 2 | author:Wenquan Yang 3 | time:2020/6/9 1:35 4 | intro:数据结构定义 5 | """ 6 | 7 | import pickle 8 | import time 9 | from config import * 10 | from utils import split_serializer 11 | 12 | 13 | class Block: 14 | 15 | def __bytes__(self): 16 | """ 17 | 序列化当前对象 18 | :return:返回bytes 19 | """ 20 | return pickle.dumps(self) 21 | 22 | @staticmethod 23 | def form_bytes(s: bytes): 24 | """ 25 | 反序列化 26 | :param s:从文件读取的序列 27 | :return: 根据序列构建的对象 28 | """ 29 | try: 30 | obj = pickle.loads(s) 31 | except BaseException: 32 | raise TypeError("字节序列反序列化成Block对象失败") 33 | return obj 34 | 35 | def write_back(self, fp): 36 | """ 37 | 模块写回 38 | 要求第一步先定位当前块所在的位置使用fp.seek(xxx) 39 | :param fp: 40 | :return: 41 | """ 42 | raise NotImplemented("当前对象未实现write_back方法") 43 | 44 | 45 | class SuperBlock(Block): 46 | """ 47 | 超级块 48 | """ 49 | 50 | def __init__(self): 51 | self.inode_cnt = INODE_BLOCK_NUM # inode 总数量 52 | self.inode_unused_cnt = INODE_BLOCK_NUM # inode 空闲数量 53 | self.block_cnt = DATA_BLOCK_NUM # 数据块总数量 54 | self.block_unused_cnt = DATA_BLOCK_NUM # 数据块空闲数量 55 | self.valid_bit = False # True被挂载,False未被挂载 56 | self.block_size = BLOCK_SIZE 57 | self.inode_size = INODE_SIZE 58 | self.block_group_link = BlockGroupLink(0) 59 | self.node_group_link = INodeGroupLink(0) 60 | self.base_dir_inode_id = -1 # 根目录的inode_id 61 | 62 | def show_sp_info(self): 63 | print("INODE使用情况:", self.inode_unused_cnt, '/', self.inode_cnt) 64 | print("DATABLOCK使用情况:", self.block_unused_cnt, '/', self.block_cnt) 65 | 66 | def write_back(self, fp): 67 | fp.seek(0) 68 | start = 0 69 | for item in split_serializer(bytes(self)): 70 | if start == SUPER_BLOCK_NUM: 71 | raise ValueError("超级块大小超出限制") 72 | fp.seek(start * BLOCK_SIZE) 73 | fp.write(item) 74 | start += 1 75 | 76 | def get_data_block_id(self, fp): 77 | """ 78 | 申请空闲块 79 | :return:空闲块id 80 | """ 81 | if self.block_unused_cnt == 0: 82 | raise Exception("没有空闲空间了") 83 | flag, tmp_id = self.block_group_link.get_free_block() 84 | 85 | if flag: # 有空闲空间 86 | self.block_unused_cnt -= 1 87 | return tmp_id 88 | 89 | # 切换空闲栈 90 | # 写回 91 | self.block_group_link.write_back(fp) 92 | del self.block_group_link # 在内存中删除当前块,这个不必须,gc会自动收了 93 | 94 | # 读取 95 | db_id = DATA_BLOCK_START_ID + tmp_id 96 | fp.seek(db_id * BLOCK_SIZE) 97 | self.block_group_link = BlockGroupLink.form_bytes(fp.read()) # 根据block_id从磁盘读取写回 98 | 99 | return self.get_data_block_id(fp) 100 | 101 | def free_up_data_block(self, fp, block_id): 102 | """ 103 | 释放磁盘块,即将空出的块写回空闲块号栈 104 | :param fp: 105 | :param block_id: 106 | :return: 107 | """ 108 | 109 | if self.block_group_link.has_free_space(): 110 | self.block_unused_cnt += 1 111 | self.block_group_link.add_to_stack(block_id) 112 | return 113 | 114 | next_id = self.block_group_link.get_next_stack() 115 | 116 | # 写回 117 | self.block_group_link.write_back(fp) 118 | del self.block_group_link # 在内存中删除当前块,这个不必须,gc会自动收了 119 | 120 | # 读取 121 | db_id = DATA_BLOCK_START_ID + next_id 122 | fp.seek(db_id * BLOCK_SIZE) 123 | self.block_group_link = BlockGroupLink.form_bytes(fp.read()) # 根据block_id从磁盘读取写回 124 | 125 | self.free_up_data_block(fp, block_id) 126 | 127 | def get_free_inode_id(self, fp): 128 | """ 129 | 获得空闲的inode 130 | :return: 索引id 131 | """ 132 | if self.inode_unused_cnt == 0: 133 | raise Exception("没有空闲inode") 134 | flag, tmp_id = self.node_group_link.get_free_block() 135 | 136 | if flag: # 有空闲空间 137 | self.inode_unused_cnt -= 1 138 | return tmp_id 139 | 140 | # 切换空闲栈 141 | # 写回 142 | self.node_group_link.write_back(fp) 143 | del self.node_group_link # 在内存中删除当前块,这个不必须,gc会自动收了 144 | 145 | # 读取 146 | db_id = INODE_BLOCK_START_ID + tmp_id 147 | fp.seek(db_id * BLOCK_SIZE) 148 | self.node_group_link = Block.form_bytes(fp.read()) # 根据block_id从磁盘读取写回 149 | 150 | return self.get_free_inode_id(fp) 151 | 152 | def free_up_inode_block(self, fp, block_id): 153 | if self.node_group_link.has_free_space(): 154 | self.inode_unused_cnt += 1 155 | self.node_group_link.add_to_stack(block_id) 156 | return 157 | 158 | next_id = self.node_group_link.get_next_stack() 159 | 160 | # 写回 161 | self.node_group_link.write_back(fp) 162 | del self.node_group_link # 在内存中删除当前块,这个不必须,gc会自动收了 163 | 164 | # 读取 165 | db_id = INODE_BLOCK_START_ID + next_id 166 | fp.seek(db_id * BLOCK_SIZE) 167 | self.node_group_link = Block.form_bytes(fp.read()) # 根据block_id从磁盘读取写回 168 | 169 | self.free_up_data_block(fp, block_id) 170 | 171 | 172 | class INode(Block): 173 | """ 174 | 存储文件的相关信息,用于定位到文件所在的数据块 175 | """ 176 | 177 | def __init__(self, i_no: int, user_id: int, target_type=DIR_TYPE): 178 | """ 179 | 180 | :param i_no: 节点块号 181 | :param user_id: 用户号 182 | :param target_type: 目标文件的类型 183 | """ 184 | self._i_no = i_no # inode编号 185 | self._write_deny = False # 写文件,防止多个进程同时对一个文件写 186 | self._user_id = user_id 187 | self._group_id = -1 188 | self._size = 0 # 指向的文件/目录的大小字节数 189 | self._ctime = time.time() # inode上一次变动时间,创建时间 190 | self._mtime = time.time() # 文件内容上一次变动的时间 191 | self._atime = time.time() # 文件上一次打开的时间 192 | self._i_sectors = [-1] * 13 # 指向的文件/目录所在的数据块 193 | self._i_sectors_state = 0 # 13块存放数据的栈用了几块 194 | self._target_type = target_type # 0指代文件,1指代目录 195 | self.user_group = {ROOT_ID, user_id} # 可访问用户 196 | 197 | def get_sector(self, idx): 198 | return self._i_sectors[idx] 199 | 200 | def add_block_id(self, block_id): 201 | if self._i_sectors_state == 12: 202 | raise Exception("文件超出大小INode无法存放") 203 | self._i_sectors[self._i_sectors_state] = block_id 204 | self._i_sectors_state += 1 205 | 206 | def clear(self): 207 | self._i_sectors = [-1] * 13 208 | self._i_sectors_state = 0 209 | 210 | def get_target_obj(self, fp): 211 | if self._i_sectors_state == 0: 212 | return None 213 | s = self.get_target_bytes(fp) 214 | self.update_atime() 215 | self.write_back(fp) 216 | if self.target_type == 1: 217 | return CatalogBlock.form_bytes(s) 218 | else: 219 | return pickle.loads(s) 220 | 221 | def get_target_bytes(self, fp): 222 | s = b'' 223 | for block_id in self._i_sectors[:self._i_sectors_state]: 224 | fp.seek((block_id + DATA_BLOCK_START_ID) * BLOCK_SIZE) 225 | s += fp.read() 226 | return s 227 | 228 | def show_ll_info(self, fp): 229 | """ 230 | 使用ll指令时的单条信息 231 | :param fp 232 | :return: str 233 | """ 234 | count = 1 235 | if self.target_type == DIR_TYPE: 236 | target_dir = self.get_target_obj(fp) 237 | count = target_dir.counts 238 | time_x = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self._mtime)) 239 | size_x = BLOCK_SIZE * self.i_sectors_state 240 | return str(count), time_x, str(size_x), str(self.user_id) 241 | 242 | def show_detail_info(self, fp): 243 | """ 244 | 使用info name指令时显示的信息 245 | :return: 246 | """ 247 | print("INODE_ID:", self.i_no) 248 | if self.target_type == FILE_TYPE: 249 | print("类型:文件") 250 | else: 251 | print("类型:目录") 252 | print("拥有者ID:", self.user_id) 253 | print("创建时间:", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self._ctime))) 254 | print("上一次打开时间:", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self._atime))) 255 | print("上一次修改时间:", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self._mtime))) 256 | print("size:", len(self.get_target_bytes(fp))) 257 | print("占用数据块:", self._i_sectors) 258 | 259 | @property 260 | def i_sectors(self): 261 | return self._i_sectors 262 | 263 | @property 264 | def i_sectors_state(self): 265 | return self._i_sectors_state 266 | 267 | @i_sectors_state.setter 268 | def i_sectors_state(self, val): 269 | self._i_sectors_state = val 270 | 271 | @property 272 | def target_type(self): 273 | return self._target_type 274 | 275 | @target_type.setter 276 | def target_type(self, value): 277 | if value not in (0, 1): 278 | raise ValueError("输入的文件类型错误") 279 | self._target_type = value 280 | 281 | @property 282 | def i_no(self): 283 | return self._i_no 284 | 285 | @property 286 | def user_id(self): 287 | return self._user_id 288 | 289 | @user_id.setter 290 | def user_id(self, user_id: int): 291 | if not isinstance(user_id, int): 292 | raise TypeError("user_id需要int类型") 293 | self._user_id = user_id 294 | 295 | def update_ctime(self): 296 | self._ctime = time.time() 297 | 298 | def update_mtime(self): 299 | self._mtime = time.time() 300 | 301 | def update_atime(self): 302 | self._atime = time.time() 303 | 304 | @property 305 | def write_deny(self): 306 | return self._write_deny 307 | 308 | def lock(self): 309 | """ 310 | 加锁 311 | 某个进程修改指向的文件的时候禁止其他进程修改 312 | :return: 313 | """ 314 | self._write_deny = True 315 | 316 | def unlock(self): 317 | """ 318 | 释放锁 319 | 进程使用完指向的文件后释放 320 | :return: 321 | """ 322 | self._write_deny = False 323 | 324 | def write_back(self, fp): 325 | self._mtime = time.time() # 修改时间 326 | db_id = INODE_BLOCK_START_ID + self.i_no 327 | fp.seek(db_id * BLOCK_SIZE) 328 | fp.write(bytes(self)) 329 | 330 | 331 | class CatalogBlock(Block): 332 | """ 333 | 目录块 334 | """ 335 | 336 | def __init__(self, name, parent_inode_id=-1): 337 | """ 338 | 初始化目录块 339 | :param name: 目录名 340 | """ 341 | self.name = name 342 | self.parent_inode_id = parent_inode_id # 上级目录的inode索引的id 343 | self.son_files = dict() # key:filename,value:inode_id 344 | self.son_dirs = dict() 345 | self.counts = 0 346 | 347 | def son_list(self): 348 | yield from self.son_files.items() 349 | yield from self.son_dirs.items() 350 | 351 | def add_new_cat(self, name, inode_id): 352 | self.son_dirs[name] = inode_id 353 | self.counts += 1 354 | 355 | def add_new_file(self, name, inode_id): 356 | self.son_files[name] = inode_id 357 | self.counts += 1 358 | 359 | def get_dir(self, dir_name): 360 | """ 361 | 获取对应目录的inode_id 362 | :param dir_name: 363 | :return: 364 | """ 365 | return self.son_dirs.get(dir_name) 366 | 367 | def get_file(self, file_name): 368 | """ 369 | 获取对应文件的inode_id 370 | :param file_name: 371 | :return: 372 | """ 373 | return self.son_files.get(file_name) 374 | 375 | def get_inode_id(self, name, type_x): 376 | if type_x == FILE_TYPE: 377 | return self.get_file(name) 378 | if type_x == DIR_TYPE: 379 | return self.get_dir(name) 380 | 381 | def check_name(self, name): 382 | """ 383 | 检查文件名称是否存在 384 | :param name: 385 | :return: 是否存在,message 386 | """ 387 | if name in self.son_dirs or name in self.son_files: 388 | return False, f"新建的名字{name}已经存在" 389 | else: 390 | return True, None 391 | 392 | def get_all_son_inode(self) -> list: 393 | """ 394 | 返回所有子目录文件的节点 395 | :return: list 396 | """ 397 | return [v for _, v in self.son_files.items()] + [v for _, v in self.son_dirs.items()] 398 | 399 | def remove(self, name, flag): 400 | if flag == FILE_TYPE: 401 | self.son_files.pop(name) 402 | self.counts -= 1 403 | elif flag == DIR_TYPE: 404 | self.son_dirs.pop(name) 405 | self.counts -= 1 406 | 407 | def file_name_and_types(self): 408 | return [(key, DIR_TYPE) for key in self.son_dirs.keys()] \ 409 | + [(key, FILE_TYPE) for key in self.son_files.keys()] 410 | 411 | def is_exist_son_files(self, name): 412 | """ 413 | :return:1 存在son_files 414 | :return:0 存在son_dirs 415 | :return:-1 不存在 416 | """ 417 | if name in self.son_files: 418 | return FILE_TYPE 419 | if name in self.son_dirs: 420 | return DIR_TYPE 421 | if name not in self.son_dirs and name not in self.son_files: 422 | return -1 423 | 424 | 425 | class GroupLink(Block): 426 | def __init__(self, start_block_id, cnt): 427 | self.block_id = start_block_id 428 | self._count = cnt 429 | self.stack = [i for i in range(start_block_id + self.count, start_block_id, -1)] 430 | if self.count < FREE_BLOCK_CNT: # 不足成组数目则是最后一组,在栈顶放入0 431 | self.stack.insert(0, 0) 432 | 433 | @property 434 | def count(self): 435 | return self._count 436 | 437 | def get_free_block(self): 438 | """ 439 | 获得一个空闲的id 440 | :return: 大于0的值表示返回一个正确的 441 | """ 442 | if self.count > 1: 443 | self._count -= 1 444 | return True, self.stack.pop(-1) 445 | else: 446 | return False, self.stack[0] 447 | 448 | def has_free_space(self): 449 | return self.count < FREE_BLOCK_CNT 450 | 451 | def add_to_stack(self, block_id): 452 | self.stack.append(block_id) 453 | self._count += 1 454 | 455 | def get_next_stack(self): 456 | """ 457 | 返回指向的下的下一个栈的id 458 | :return: 459 | """ 460 | return self.stack[0] 461 | 462 | 463 | class BlockGroupLink(GroupLink): 464 | """ 465 | 成组分配中的基本块管理一组空闲块 466 | """ 467 | 468 | def __init__(self, start_block_id, cnt=FREE_BLOCK_CNT): 469 | super().__init__(start_block_id, cnt) 470 | 471 | def write_back(self, fp): 472 | db_id = DATA_BLOCK_START_ID + self.block_id 473 | fp.seek(db_id * BLOCK_SIZE) 474 | fp.write(bytes(self)) 475 | 476 | 477 | class INodeGroupLink(GroupLink): 478 | 479 | def __init__(self, start_block_id, cnt=FREE_NODE_CNT): 480 | super().__init__(start_block_id, cnt) 481 | 482 | def write_back(self, fp): 483 | db_id = INODE_BLOCK_START_ID + self.block_id 484 | fp.seek(db_id * BLOCK_SIZE) 485 | fp.write(bytes(self)) 486 | -------------------------------------------------------------------------------- /FileManageSystem/running_pfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | author:Wenquan Yang 3 | time:2020/6/12 22:50 4 | intro:文件系统实际运行部分 5 | """ 6 | import commands 7 | from utils import bar 8 | from file_system import FileSystem 9 | from file_system import file_system_func 10 | 11 | 12 | @file_system_func 13 | def running_pfs(fs: FileSystem): 14 | while True: 15 | bar(fs.current_user_name, fs.get_current_path_name()) 16 | cmd = input().split() 17 | if cmd[0] == 'exit': 18 | break 19 | try: 20 | func = getattr(commands, cmd[0]) 21 | func(fs, *cmd[1:]) 22 | except AttributeError: 23 | print("\n命令不支持\n") 24 | 25 | 26 | def main(): 27 | running_pfs() 28 | 29 | 30 | if __name__ == '__main__': 31 | main() 32 | -------------------------------------------------------------------------------- /FileManageSystem/user.py: -------------------------------------------------------------------------------- 1 | """ 2 | author:Wenquan Yang 3 | time:2020/6/16 0:35 4 | """ 5 | import hashlib 6 | 7 | 8 | def md5(text: str): 9 | m = hashlib.md5() 10 | m.update(text.encode("utf-8")) 11 | return m.hexdigest() 12 | 13 | 14 | class User: 15 | def __init__(self, name, password, user_id): 16 | self._name = name 17 | self._password = md5(password) 18 | self._user_id = user_id 19 | 20 | @property 21 | def user_id(self): 22 | return self._user_id 23 | 24 | @property 25 | def name(self): 26 | return self._name 27 | 28 | def login(self, name, password, root_user=False): 29 | return name == self._name and (root_user or md5(password) == self._password) 30 | 31 | def __str__(self): 32 | print(self.name, self.user_id, self._password) 33 | -------------------------------------------------------------------------------- /FileManageSystem/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | author:Wenquan Yang 3 | time:2020/6/10 2:35 4 | content:各种工具脚本 5 | """ 6 | import time 7 | import pickle 8 | from math import ceil 9 | from config import BLOCK_SIZE, ROOT_ID, VERSION 10 | 11 | 12 | def serializer(text: str) -> list: 13 | """ 14 | 输入文本,将其转换成按照block大小切分的块 15 | :param text: 待序列化的文本 16 | :return: list[b'',b''] 17 | """ 18 | b_text = pickle.dumps(text) 19 | block_num = int(ceil(len(b_text) / BLOCK_SIZE)) # 计算块数向上取整 20 | yield from [b_text[BLOCK_SIZE * i:BLOCK_SIZE * (i + 1)] for i in range(block_num)] 21 | 22 | 23 | def split_serializer(b_obj: bytes) -> list: 24 | """ 25 | 输入字节流按照block大小切分 26 | :param b_obj: 27 | :return: 28 | """ 29 | block_num = int(ceil(len(b_obj) / BLOCK_SIZE)) # 计算块数向上取整 30 | yield from [b_obj[BLOCK_SIZE * i:BLOCK_SIZE * (i + 1)] for i in range(block_num)] 31 | 32 | 33 | def form_serializer(fp, block_num): 34 | s = b'' 35 | for _ in range(block_num): 36 | s += fp.read() 37 | return s 38 | 39 | 40 | def check_auth(auth_id, user_id): 41 | return auth_id == user_id or user_id == ROOT_ID 42 | 43 | 44 | def color(filename: str, front_color: str, back_color: str): 45 | filenames = '\33[0;' + front_color + ';' + back_color + 'm' + filename + '\33[0m' 46 | return filenames 47 | 48 | 49 | def bar(user_name, current_path): 50 | time_now = time.strftime(" %H:%M:%S ", time.localtime()) 51 | time_now = color(time_now, "30", "47") 52 | user_name = color(' ' + user_name + '@PFS ', "37", "40") 53 | current_path = color(' >' + current_path + ' ', "37", "44") 54 | version = color(' ' + VERSION + ' ', "31", "42") 55 | cmd_in = color('> ', "44", "40") 56 | print(time_now + user_name + version + current_path) 57 | print(cmd_in, end="") 58 | 59 | 60 | def logo(): 61 | LOGO1 = '\33[1;34;40m' + " ____ ______ _____\n / __ \ / ____// ___/\n" 62 | LOGO2 = '\33[1;36;40m' + " / /_/ // /_ \__ \\" + "\n" 63 | LOGO3 = '\33[1;32;40m' + "/ ____// __/ ___/ /\n/_/ /_/ /____/\n\n\33[0m" 64 | print(LOGO1, LOGO2, LOGO3) 65 | 66 | 67 | def line(func): 68 | def return_func(*args): 69 | print() 70 | func(*args) 71 | print() 72 | 73 | return return_func 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Arik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 操作系统课程设计 2 | 3 | 多用户、多级目录结构文件系统的设计与实现。 4 | 5 | 模拟实现类Linux的文件系统 6 | 7 | 在Windows环境下开发使用,目前测试MacOS下无法使用完整功能,Linux环境未测试。 8 | 9 | ## 使用方法 10 | 11 | 进入[FileManageSystem](FileManageSystem)目录下 12 | 1. 运行[initialize_disk.py](FileManageSystem/initialize_disk.py)生成一个固定大小的文件模拟磁盘 13 | 2. 在终端中运行[running_pfs.py](FileManageSystem/running_pfs.py) 模拟加载磁盘新建命令行终端 14 | 3. 输入`main`指令查看支持的命令 15 | 16 | ![terminal](terminal.png) 17 | 18 | 19 | ## 文件目录 20 | 21 | ```txt 22 | C:. 23 | │ fms.pfs # 磁盘 24 | │ README.md 25 | │ 26 | ├─docs 27 | │ bug解决.md 28 | │ 命令实现.md 29 | │ 基本知识点.md 30 | │ 文件系统中的文件结构.md 31 | │ 空闲块分配.md 32 | │ 33 | ├─FileManageSystem 34 | │ commands.py # 命令的实现类似cd,mv,ll 35 | │ config.py # 基本配置 36 | │ file_pointer.py # 磁盘指针 37 | │ file_system.py # 文件系统,命令的实现通过调用这里面的接口 38 | │ file_ui.py # 文本编辑的ui界面 39 | │ initialize_disk.py # 磁盘初始化 40 | │ main.py 41 | │ models.py # 基本底层数据结构 42 | │ README.md 43 | │ running_pfs.py # 运行文件系统 44 | │ user.py # 用户 45 | │ utils.py # 一些的脚本 46 | │ 47 | └─test # 测试文件待完善 48 | test_models.py 49 | test_running_pfs.py 50 | test_utils.py 51 | __init__.py 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/bug解决.md: -------------------------------------------------------------------------------- 1 | # 遇到的一些有价值的bug 2 | 3 | ## 文件读写问题 4 | 发现是在新建文件的时候发现刚新建的也无法读出,期间也在指定的位置即使用seek 5 | 移动指针发现即使刚使用write然后再读出也是没有内容的。后来查阅文档发现使用a+模式可以用seek()方法读但是写操作 6 | 永远是在文件的末尾。[stack overflow](https://stackoverflow.com/questions/1466000/difference-between-modes-a-a-w-w-and-r-in-built-in-open-function) 7 | -------------------------------------------------------------------------------- /docs/命令实现.md: -------------------------------------------------------------------------------- 1 | # 命令实现 2 | 3 | 4 | 5 | ## mkdir -name 6 | 新建文件夹 7 | 1. 获得当前目录对象pwd_obj 8 | 2. 检查命名冲突,pwd_obj.check_name(name) 9 | 3. 获取新的inode对象 10 | 4. 将新建文件夹的名字和inode号作为键值对写回pwd_obj 11 | 5. 写回新建的目录对象new_obj,并且将其开辟的新的地址块号添加到对应的inode对象中 12 | 6. 写回新的inode对象 13 | 14 | 15 | 16 | ## cd [dirName] 17 | 切换目录 18 | 1. dirName:要切换到的目标目录名 19 | 2. `cd hello` 切换一级目录 20 | 3. `cd hello\hello` 切换多级目录 21 | 4. `cd ..\..` 切换上层目录 22 | 5. `cd ~` 切换到根目录 23 | 24 | 25 | ## rm [options] name 26 | 删除文件/目录 27 | 1. 目前提供的选择只有-r 28 | 2. `rm demo.txt` 输入后则会询问是否删除,且无法删除目录 29 | 3. `rm -r demo` 直接删除文件或者递归删除目录 30 | -------------------------------------------------------------------------------- /docs/基本知识点.md: -------------------------------------------------------------------------------- 1 | ## iNode介绍 2 | 1. 用于存放文件的元数据信息,不存储文件内容 3 | 1. 类型 4 | 2. 权限 5 | 3. 拥有者,owner,group owner 6 | 4. 创建时间,ctime,mtime,atime 7 | 5. 连接数 8 | 6. 文件内容所在的位置(磁盘块) 9 | 2. 以数组的形式存储,每个元素是一个iNode(256byte) 10 | 3. 一个Map映射表Filename => inode-index 11 | 4. inode的总数在格式化文件系统的时候就已经确定 12 | 13 | -------------------------------------------------------------------------------- /docs/文件系统中的文件结构.md: -------------------------------------------------------------------------------- 1 | # 文件系统中的文件结构 2 | 3 | 模仿linux中的文件结构 4 | 5 | ``` 6 | \(base) | 7 | |root 8 | |etc |password 9 | 10 | |home |user01 11 | |user02 12 | ... 13 | ``` 14 | 15 | 16 | 17 | 进入系统的时候系统从base出发找到etc中的账户密码,检查用户登录,接着根据用户信息进入到用户相应的用户根目录 18 | 19 | 几篇文件系统相关的博客 20 | 21 | + [十六. 文件系统二(创建文件系统)](https://zhuanlan.zhihu.com/p/36754495) 22 | + [Linux文件系统详解](https://juejin.im/post/5b8ba9e26fb9a019c372e100#heading-13) 23 | + [Linux文件系统详解](https://www.cnblogs.com/bellkosmos/p/detail_of_linux_file_system.html) 24 | + [鸟叔linux私房菜中文件系统部分](http://cn.linux.vbird.org/linux_basic/linux_basic.php) 25 | + [linux下对inode和块的理解](https://www.cnblogs.com/whych/p/9315723.html) 26 | -------------------------------------------------------------------------------- /docs/空闲块分配.md: -------------------------------------------------------------------------------- 1 | # 空闲块分配 2 | 3 | 使用成组链接法 4 | 5 | 文档博客 6 | 7 | + [实例讲解成组链接法](https://blog.csdn.net/Ajay666/article/details/73569654?tdsourcetag=s_pctim_aiomsg) 8 | 9 | 以数据块分配为例,每组有100个数据块, 10 | 11 | 假设总计有500个数据块,则第0个数据块为“组长”记录其后的1~100个块的空闲状态,第100个数据块为组长记录其后101~200个块的空闲状态..... 12 | 13 | inode的分配也是使用成组链接法,实现形式与数据块相同 -------------------------------------------------------------------------------- /terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BangBOOM/File-System/810e1593ad4f2e5d3032896b259543dca0e8ee6a/terminal.png -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BangBOOM/File-System/810e1593ad4f2e5d3032896b259543dca0e8ee6a/test/__init__.py -------------------------------------------------------------------------------- /test/test_models.py: -------------------------------------------------------------------------------- 1 | """ 2 | author:Wenquan Yang 3 | time:2020/6/10 23:16 4 | """ 5 | from unittest import TestCase 6 | 7 | 8 | class TestSuperBlock(TestCase): 9 | def test_get_free_block(self): 10 | self.fail() 11 | -------------------------------------------------------------------------------- /test/test_running_pfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | author:Wenquan Yang 3 | time:2020/6/12 23:20 4 | """ 5 | from unittest import TestCase 6 | from running_pfs import running_pfs 7 | 8 | 9 | class Test(TestCase): 10 | def test_running_pfs(self): 11 | running_pfs() 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/test_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | author:Wenquan Yang 3 | time:2020/6/10 2:53 4 | """ 5 | 6 | import pickle 7 | from unittest import TestCase 8 | from utils import serializer 9 | 10 | 11 | class Test(TestCase): 12 | def test_serializer(self): 13 | """ 14 | 测试序列化切分的正确性,通过序列化切分,在组合反序列化与最开始的原始字符串比对 15 | :return: 16 | """ 17 | s = "hello world" * (2 ** 8) 18 | s_b = b'' 19 | for item in serializer(s): 20 | s_b += item 21 | self.assertEqual(s, pickle.loads(s_b)) 22 | --------------------------------------------------------------------------------