0%

​ 本站采用 hexo 架构,部署在 github pages 上,采用 github 作为图床。建议在能够流畅访问 github 的网络环境下浏览。

​ 看见这幅美丽的画了吗?看见了说明你能看见这幅画 (bushi)。

​ 关于左边的这个抽象的双 tags 旋转动画,我也在 diss 它,有时间再把它 ban 掉了。

维修计划:

  • 缩进调整
  • 关闭 tags 动画

一、index

1. 软工II
2. Compiler
3. 数据管理基础 / 操作系统
4. ML

二、最近活动

活动 时间/deadline
大创二阶段 03/07 - 03/22

三、任务链

阶段一 进度
近纲书评
毛概社会实践材料 0426
OS Lab2 验收
Compiler Lab3
SEEC Lab4 Lab5

一、前言

初次接触飞腾开发板,我的 ubuntu 系统无法正常使用 USB 串口线连接开发板,问题集中在 CH341 的驱动上面。

二、问题及解决办法

1. 驱动冲突

物理线路正确连接后,查询 ttyUSB 设备信息,连接后立即 disconnected。

1
2
3
lg@ubuntu:~$ sudo dmesg | grep ttyUSB
[12946.029195] usb 1-2.1: ch341-uart converter now attached to ttyUSB0
[12946.576807] ch341-uart ttyUSB0: ch341-uart converter now disconnected from ttyUSB0

多次尝试后又得到如下报错信息:

1
2
3
4
5
6
7
8
lg@ubuntu:~$ sudo dmesg | grep ch341
[ 38.193329] usbcore: registered new interface driver ch341
[ 38.193360] usbserial: USB Serial support registered for ch341-uart
[ 38.193393] ch341 1-2.1:1.0: ch341-uart converter detected
[ 38.206613] usb 1-2.1: ch341-uart converter now attached to ttyUSB0
[ 38.758581] usb 1-2.1: usbfs: interface 0 claimed by ch341 while 'brltty' sets config #1
[ 38.761742] ch341-uart ttyUSB0: ch341-uart converter now disconnected from ttyUSB0
[ 38.761800] ch341 1-2.1:1.0: device disconnected

发现是 brltty 服务占用了设备接口。

卸载 brltty 即可:

1
sudo apt remove brltty
2. 驱动不适配

brltty 的冲突解决后,ls /dev 即可(应该?)看见 ttyCH341USB0 设备,但无法成功连接/连接不稳定,甚至无法识别。

原因是驱动与内核版本不适配。先卸载 ch341 驱动。

1
sudo rmmod ch341

然后按该帖步骤正确安装驱动:

Ubuntu系统上ttyUSB被挂载为ttyCH341USB_ubuntu ch341-CSDN博客

如遇到签名无效则参考下帖:

https://blog.csdn.net/qq_28680277/article/details/129162559

然后重新接入开发板,即可识别。

image-20240702195622618

一、PPT相关内容

1.何谓实模式,何谓保护模式?
  • 实模式:基地址+偏移量可以直接获得物理地址的模式
    • 缺点:非常不安全
  • 保护模式:不能直接拿到物理地址
    • 需要进行地址转换
    • 从80286开始,是现代操作系统的主要模式
2. 什么是选择子?

在计算机体系结构中,特别是在涉及保护模式的操作系统中,选择子是用于指定描述符表(GDT或LDT)中的描述符的索引。选择子包含了几个关键部分:

  1. 请求特权级(RPL):选择子的低2位表示请求的特权级,决定访问权限。
  2. 表指示标志(TI):第3位,表示选择的是全局描述符表(GDT,TI=0)还是局部描述符表(LDT,TI=1)。
  3. 索引:高13位,表示在GDT或LDT中的具体位置,即该表中的偏移量。

选择子是放在段选择寄存器中的16位值,通过它,处理器可以访问描述符表中相应的段描述符,进而获得段的具体信息,如基地址和界限等。

3. 什么是描述符?

image-20240421185319902

描述符是在保护模式下操作系统中使用的一个数据结构,用于定义内存段的属性和访问权限。每个描述符通常存储在全局描述符表(GDT)或局部描述符表(LDT)中。描述符中包含了以下关键信息:

  1. 基地址(Base Address):段的起始物理地址。
  2. 段界限(Segment Limit):与基地址相结合,定义了段的大小。这个界限用于限制段内的偏移,确保访问不会超出段的实际物理内存范围。
  3. 访问权限(Access Rights):描述段的类型(例如,代码段、数据段等)、描述符类型(系统段或代码/数据段)、以及其他访问控制信息(如读/写权限、执行权限)。
  4. 特权级(DPL, Descriptor Privilege Level):定义了访问该段所需的最低CPU特权级(CPL, Current Privilege Level)。

描述符确保了内存段的保护和正确的访问,是实现多任务和内存保护的基础。通过这些信息,操作系统能够管理不同任务的内存访问权限,防止未授权访问,同时允许合法的访问和操作。

4. 什么是 GDT,什么是 LDT?

GDT(Global Descriptor Table)和LDT(Local Descriptor Table)是在保护模式下使用的两种描述符表,用于定义内存段的属性和访问权限。它们的具体作用和特性如下:

  • GDT (Global Descriptor Table):全局描述符表,是全局唯一的。
    • 存放一些公用的描述符,和包含各进程局部描述符表首地址的描述符。
  • LDT (Local Descriptor Table):局部描述符表,每个进程都可以有一个。
    • 存放本进程内使用的描述符。
5. 分别说明 GDTR 和 LDTR
  • GDTR (GDT Register):48位寄存器,高32位放置GDT首地址,低16位放置GDT限长
    • 限长决定了可寻址的大小,注意低16位放的不是选择子
  • LDTR (LDT Register):16位寄存器,放置一个特殊的选择子,用于查找当前进程的LDT首地址。
6. 说明 GDT 直接查找物理地址的具体步骤。
  1. 给出段选择子(放在段选择寄存器里)+ 偏移量
  2. 若选择了 GDT 方式,则从 GDTR 获取 GDT 首地址,用段选择子中的13位做偏移,拿到 GDT 中的描述符
  3. 如果合法且有权限,用描述符中的段首地址加上 1 中的偏移量找到物理地址, 寻址结束
7. 说明通过 LDT 查找物理地址的具体步骤。
  1. 段选择子读取:首先从程序提供的段选择子开始,这个选择子一般存储在某个段寄存器中(如CS, DS等)。选择子包含两部分重要信息:是否选择GDT(全局描述符表)或LDT(局部描述符表),以及在表中的索引。
  2. 访问GDT获取LDT描述符:如果段选择子指明选择的是LDT,处理器首先需要访问GDT以获取当前进程的LDT描述符。这是因为每个进程都可以有自己的LDT,但是LDT的起始地址和界限是存储在GDT中的一个特殊的系统段描述符中。
  3. 获取LDT的首地址:从GDT中获取到的LDT描述符里包含了LDT的首地址。处理器使用这个地址来访问LDT。
  4. 使用LDT的索引访问段描述符:使用段选择子中的索引来从LDT中获取具体的段描述符。每个段描述符包括基地址、段界限和其他管理属性。
  5. 计算物理地址:最后,使用段描述符中的段基址加上段内偏移(通常是从程序指令或其他寄存器中给出)来计算出物理地址。如果合法且有权限,用描述符2中的段首地址加上步骤 1 中的偏移量找到物理地址。寻址结束
8. 根目录区大小一定么?扇区号是多少?为什么?
根目录区大小
  1. 大小设置
    • 在FAT12文件系统中,根目录区的大小是在格式化时预设的。这个大小是由根目录区能够包含的最大条目数决定的,而每个条目通常占用32字节。例如,如果系统允许根目录区包含224个条目,则根目录区的大小将是224 × 32 = 7168字节。
  2. 计算扇区数
    • 由于扇区通常是512字节,根目录区的大小(以字节为单位)将被512除以得到所需的扇区数。继续上面的例子,7168字节的根目录区将占用7168 / 512 = 14个扇区。
扇区号
  1. 起始扇区
    • FAT12 文件系统的根目录区紧跟在两个FAT表之后。文件系统的起始部分包括保留扇区(通常包括引导扇区),其后是两个FAT表。
    • 根目录的起始扇区号可以通过计算保留扇区和FAT表所占的扇区数来确定。
  2. 计算方法
    • 假设有一个保留扇区和两个FAT表,每个FAT表大小为9扇区,那么根目录的起始扇区号将是 1(保留扇区)+ 9×2(两个FAT表) = 19。因此,根目录的起始扇区号为19。
9. 数据区第一个簇号是多少?为什么?

在1.44M软盘上,FAT前三个字节的值是固定的0xF0、0xFF、0xFF,用于表示这是一个应用在1.44M软盘上的FAT12文件系统。本来序号为0和1的FAT表项应该对应于簇0和簇1,但是由于这两个表项被设置成了固定值,簇0和簇1就没有存在的意义了,所以数据区就起始于簇2

10. FAT表的作用?

FAT项的值代表文件的下一个簇号
值大于或等于0xFF8,表示当前簇已经是本文件的最后一个簇
值为0xFF7,表示它是一个坏簇

11. 解释静态链接的过程及动态链接的过程

静态链接

  1. 空间和地址分配
    • 一旦所有的符号引用都被解决,链接器将为程序中的各个段(如代码段、数据段)分配内存地址。这包括为函数和全局变量分配地址,这样它们在程序运行时就可以通过这些地址被访问。
  2. 符号解析和重定位
    • 链接器通过查看所有提供的目标文件和库来解析这些符号引用,确保每个被引用的符号都能找到一个明确的定义。
    • 链接器将根据它们在最终可执行文件中的位置调整目标文件中的代码和数据。因为在编译时,代码是假设从某个基地址开始的,但是多个代码块放在一起时,实际的起始地址可能不同,因此需要对原始代码中的地址引用进行调整。

动态链接

  1. 动态链接器自举

    动态链接器本身也是一个不依赖其他共享对象的共享对象,需要完成自举。

  2. 装载共享对象

    将可执行文件和链接器自身的符号合并成为全局符号表,开始寻找依赖对象。加载对象的过程可以看做图的遍历过程;新的共享对象加载进来后,其符号将合并入全局符号表;加载完毕后,全局符号表将包含进程动态链接所需全部符号。

  3. 重定位和初始化

    链接器遍历可执行文件和共享对象的重定位表,将它们 GOT/PLT 中每个需要重定位的位置进行修正。完成重定位后,链接器执行 .init 段的代码,进行共享对象特有的初始化过程(例如 C++ 里全局对象的构造函数)。

  4. 转交控制权

    完成所有工作,将控制权转交给程序的入口开始执行。

12. 静态链接相关PPT中为什么使用ld链接而不是gcc?

避免 gcc 进行 glibc 链接。

13. linux下可执行文件的虚拟地址空间默认从哪里开始分配。

可执行文件的虚拟地址空间默认从 0x08048000 开始分配。这个地址被选中是因为它足够高,可以让程序员容易地识别出指针和数字之间的差别,同时又低足够避免和系统库及内核空间发生冲突。

二、实验相关内容

1. BPB指定字段的含义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct BPB {
u16 BPB_BytsPerSec; //每扇区字节数
u8 BPB_SecPerClus; //每簇扇区数
u16 BPB_RsvdSecCnt; //Boot记录占用的扇区数
u8 BPB_NumFATs; //FAT表个数
u16 BPB_RootEntCnt; //根目录最大文件数
u16 BPB_TotSec16; //扇区总数
u8 BPB_Media; //介质描述符
u16 BPB_FATSz16; //每个FAT表所占扇区数
u16 BPB_SecPerTrk; //每磁道扇区数(Sector/track)
u16 BPB_NumHeads; //磁头数(面数)
u32 BPB_HiddSec; //隐藏扇区数
u32 BPB_TotSec32; //如果BPB_ToSec16为0,该值为扇区数
} BPB; //25字节

2. 如何进入子目录并输出(说明方法调用)
3. 如何获得指定文件的内容,即如何获得数据区的内容(比如使用指针等)
4. 如何进行C代码和汇编之间的参数传递和返回值传递
5. 汇编代码中对I/O的处理方式,说明指定寄存器所存值的含义

一、前言

起因是由于 512G 的 ssd 一盘双系统有些吃紧,ubuntu 的 home 和根分区逐渐膨胀,希望重新分区。Gparted 扩展分区仅限于相邻右侧空闲空间,构造逻辑卷组又比较担心对后续可能的系统迁移有所阻碍,于是决定提前进行系统迁移实验。

网上主流的方法有 systemback 备份,dd 注入,以及 Clonezilla 再生三种手段。对于 systemback 似乎我的主板不太支持,换了三个 U 盘制作四五个镜像都没办法正常打开。dd 与 Clonezilla 原理差不多,不过 Clonezilla 是通过制作镜像实现文件迁移,因此自带备份效果。

这里主要讲讲 Clonezilla 系统迁移。最近实在没什么时间,就写个大纲吧 \^_\^。

二、流程

请提前备份重要数据!

请提前准备三个 U 盘,两个引导盘 8G 左右即可,一个需要大些,用于存储镜像。

在 linux 系统中,万物皆文件,文件即万物。迁移系统只需将系统中所有分区搬到目标位置,然后修改启动项即可。

1. 镜像制作

使用 Clonezilla 备份之前需要先制作引导盘,可参考我之前的文章(ubuntu 相关配置)。

然后还需要一个数据盘,进入再生龙界面后选择克隆分区到镜像,选中 ubuntu 的分区即可。

2. 新建目标分区

使用 Ubuntu 引导盘进入 try 界面,利用 Gparted 建立与原系统分区格式相同的分区,且分区容量需大于原分区。

3. 数据注入

再次进入 Clonezilla 界面,选择镜像导出到分区,由于一次性选择超过一个分区无法制定目标盘,因此一次选择一个分区注入到目标位置即可。

4. 修改 fstab

查看分区 uuid

1
$ sudo blkid

新分区的 uuid 与原分区不同,修改 /etc/fstab 中的条目,将对于的 uuid 替换为实际 uuid 即可。

5. 修复引导

原系统与新系统的 grub 引导会冲突,导致两个系统都无法进入。我的做法是将原系统删除,然后使用 boot-tools自动修复 grub。

三、其他问题

这里主要是我自身的原因。迁移之前我将系统原 nvidia-535 驱动换为了 nvidia-545,导致一个内核崩溃,其余旧版内核可以正常运行。迁移后问题逐渐显现出来,与 gnome 有所冲突,部分包无法显现出来。

解决办法是在图形界面中打开附加驱动,更换回推荐驱动,并删除有问题的内核,重新进行安装。关于 gnome 相关问题,比如桌面图标消失、设置栏消失等小问题就慢慢修复吧。

一、前言

微信官方对 linux 系统一直都没有很好的支持,虽然可以用 wine 来兼容 windows 微信,但安装和适配都较为复杂。2022 年 1 月,麒麟软件与腾讯公司联手推动了基于 Linux 平台的原生微信适配工作。优麒麟的原生微信也同样支持 Ubuntu 系统,因此是一个较好的选择。

二、安装与配置

1. 安装

阅读完本文所有内容再考虑进行安装。

软件的安装参考如下链接:

2024如何在Ubuntu上安装原生微信wechat weixin - 知乎

2. 有关 lsb

安装教程中的核心部分为:

1
2
3
4
5
sudo apt update
sudo cp /etc/lsb-release /etc/lsb-release.Ubuntu
sudo apt -y install com.tencent.wechat
sudo apt -y install electronic-wechat-icons-atzlinux
sudo cp /etc/lsb-release /etc/lsb-release.wechat

注意到除了安装的命令之外,还出现了比较特别的两行:

1
2
sudo cp /etc/lsb-release /etc/lsb-release.Ubuntu
sudo cp /etc/lsb-release /etc/lsb-release.wechat

其中涉及三个文件:lsb-releaselsb-release.Ubuntulsb-release.wechat,下面为了方便简称为 lsb

  • lsb 记录了系统的 Linux 发行版信息,包括发行版的标识、版本号和一些其他信息。强烈不建议更改。

  • lsb.Ubuntu 是对系统原 lsb 文件的备份。

  • lsb.wechat 是适配微信的优麒麟系统发行版信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
lg@ubuntu:/etc$ cat lsb-release.Ubuntu 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.4 LTS"

lg@ubuntu:/etc$ cat lsb-release.wechat
DISTRIB_ID=Kylin
DISTRIB_RELEASE=V10
DISTRIB_CODENAME=kylin
DISTRIB_DESCRIPTION="Kylin V10 SP1"
DISTRIB_KYLIN_RELEASE=V10
DISTRIB_VERSION_TYPE=enterprise
DISTRIB_VERSION_MODE=normal

这两条 cp 指令会将系统元 lsb 备份到 lsb.Ubuntu,然后用优麒麟系统的 lsb 文件替代。这么做的是因为微信的登陆接口会读取系统的发行版信息,若非优麒麟则无法登陆。操作比较恶心,属于是反向适配了。

3. 解决办法

目前我的解决办法是采用脚本启动,启动前用优麒麟的 lsb 替换系统 lsh,然后开始计时,十五秒后替换回来,这意味着在启动微信后十五秒内必须完成登陆,且十五秒内不可强制断电关机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# /opt/WeChat/files/wechat.sh
# 当前的 /etc/lsb-release 文件路径
CURRENT_LSB_RELEASE="/etc/lsb-release"
# 原始文件
UBUNTU_LSB_RELEASE="/etc/lsb-release.Ubuntu"

# 使用 /etc/lsb-release.wechat 文件替换当前的 /etc/lsb-release
sudo cp /etc/lsb-release.wechat $CURRENT_LSB_RELEASE

# 启动 WeChat 应用程序
/opt/WeChat/files/wechat &

# 等待15秒后恢复lsb
echo "Wait for reset."
sleep 15
sudo cp $UBUNTU_LSB_RELEASE $CURRENT_LSB_RELEASE
echo "Reset successfully!"

脚本里的两条 sudo 需要在系统配置中添加免密设置。

打开终端,并使用以下命令编辑 /etc/sudoers 文件:

1
sudo visudo

在打开的文件中,添加如下规则(假设你的用户名为 lg):

1
2
lg ALL=(root) NOPASSWD: /bin/cp /etc/lsb-release.wechat /etc/lsb-release
lg ALL=(root) NOPASSWD: /bin/cp /etc/lsb-release.Ubuntu /etc/lsb-release

注意:编辑 /etc/sudoers 文件时要格外小心,确保不要修改错误并保存。建议使用 visudo 命令编辑,因为它会在保存前检查语法错误。

因为启动方式发生变化,我们需要修改桌面图标,换为脚本启动。

(由于我的图标是自己创建的,因此文件名可能有所不同,需要注意。)

打开图标配置:

1
sudo vim /usr/share/applications/wechat.desktop

Exec 条目更换为执行脚本。

1
Exec=bash /opt/WeChat/files/wechat.sh

然后重启即可运用配置。

三、卸载铜豌豆源

1
2
sudo apt purge atzlinux-v12-archive-keyring
sudo apt autoremove

四、后记

关于微信原生这个问题我也是今天搜索的时候偶然发现,不知道其中是否存在更多的漏洞/问题,这种安装方式还是有待斟酌的。

一、任务

制作一个 FAT12 镜像查看器。

支持命令:ls, ls -l, cat。

二、知识

学长的文章(写得好好): FAT12镜像查看工具_解析fat32镜像文件-CSDN博客

cat 需要访问数据区。

ls、ls -l 需要访问目录区。

1. MBR 区

从第 11 个字节开始的 25 个字节构成一个较为特殊的结构 BPB(Bios Parameter Block)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct BPB {
u16 BPB_BytsPerSec; //每扇区字节数
u8 BPB_SecPerClus; //每簇扇区数
u16 BPB_RsvdSecCnt; //Boot记录占用的扇区数
u8 BPB_NumFATs; //FAT表个数
u16 BPB_RootEntCnt; //根目录最大文件数
u16 BPB_TotSec16; //扇区总数
u8 BPB_Media; //介质描述符
u16 BPB_FATSz16; //每个FAT表所占扇区数
u16 BPB_SecPerTrk; //每磁道扇区数(Sector/track)
u16 BPB_NumHeads; //磁头数(面数)
u32 BPB_HiddSec; //隐藏扇区数
u32 BPB_TotSec32; //如果BPB_ToSec16为0,该值为扇区数
} BPB; //25字节

2. FAT 表

FAT1FAT2 互为备份,所以理论上两张表是一样的。

FAT 表项的值的含义:

  • 通常情况下代表文件下一簇号。
  • >= 0xFF8,该簇已经是文件最后一个簇。
  • = 0xFF7,表示一个坏簇。

簇的编号与 FAT 表中的索引:每个簇都有一个唯一的编号,FAT 表中的项索引与这些簇的编号对应。例如,第一个簇的编号对应 FAT 表中的第一个项,第二个簇对应第二个项,依此类推。

3. 根目录区

一个目录项占据 32 字节。

1
2
3
4
5
6
7
8
9
10
typedef struct RootEntry {
char DIR_Name[11]; //长度名+扩展名
u8 DIR_Attr; //文件属性
char reserved[10]; //保留位
u16 DIR_WrtTime; //最后一次写入时间
u16 DIR_WrtDate; //最后一次写入日期
u16 DIR_FstClus; //开始簇号
u32 DIR_FileSize; //文件大小
} RootEntry; //32字节

由此,归结起来,FAT12访问文件的基本操作为:

  1. 首先通过根目录文件查找文件名,确定是哪一个条目,接着在条目中访问 DIR_FstClus 对应的开始簇号。
  2. 当一个簇号访问完后,通过FAT表项查询下一簇号,决定是结束还是继续访问下一簇号,重复第二条。
4. 数据区
1
2
3
4
5
6
7
8
9
typedef struct Entry{
char FILE_NAME[11]; //长度名+扩展名
u8 FILE_Attr; //文件属性
char reserved[10]; //保留位
u16 DIR_WrtTime; //最后一次写入时间
u16 DIR_WrtDate; //最后一次写入日期
u16 DIR_FstClus; //开始簇号
u32 DIR_FileSize; //文件大小
}; //跟 RootEntry 一模一样

FAT12.png

5. File Attribution
掩码 描述
0 0x01 只读
1 0x02 隐藏
2 0x04 系统
3 0x08 卷标
4 0x10 子目录
5 0x20 档案
6 0x40 设备(内部使用,磁盘上看不到)
7 0x80 没有使用

0x0F 是 LFN。

三、注意事项

整体是做一个深度优先遍历。从本质上来说,本次实验难度仅存在于定位数据。这里列出一些值得注意的点。

1. 数据区起始

数据区起始于簇 2。事实上这句话我到现在还没读懂,我是通过插入空簇来实现下标与需求匹配的。

2. FAT 表项的计算

FAT 每个表项占 1.5 个字节,因此每次提取三个字节计算两个表项。

若这三个字节十六进制表示为 AB CD EF(按地址从小到大排列)

那么有

1
2
前一个FAT = DAB
后一个FAT = EFC
3. 关于无关的 Entry

由于 FAT12 文件名仅支持至多 8 位大写的文件名与至多 3 位大写的后缀,若文件名出现小写,在镜像中可能会出现一些 Entry 作为补丁。我们可以通过这些项获取原文件名,但在访问的适合需要将这些项剔除。

4. tool

插件 Hex-editor。

5. 其它
1
2
3
4
5
6
7
8
9
10
11
myString getSpecialName(Entry entry);  // 获取特殊文件名(前一个目录的)
struct LongFileNameEntry { // unused temporarily
uint8_t LFNOrd; // 长文件名序号
uint16_t LFNPart1[5]; // 长文件名的第一部分
uint8_t LFNAttributes; // 文件属性标志
uint8_t LFNReserved1; // 保留字段,应为0x00
uint8_t LFNChecksum; // 校验和
uint16_t LFNPart2[6]; // 长文件名的第二部分
uint16_t LFNReserved2; // 保留字段,应为0x0000
uint16_t LFNPart3[2]; // 长文件名的第三部分
};

框架中出现的这些属于扩展功能,不需要实现。(某人没看题QwQ)

三、代码设计

暂时先不考虑 LFN 的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include <cstring>
#include <fstream>
#include <iostream>
#include <regex>
#include <string>
#include <vector>
#include "myString.h"
using namespace std;
#define mp(x, y) make_pair((x), (y))
#define pii pair<int, int>
#define ui unsigned int

const int N = 1e6 + 6;
bool isLower(char ch) {
return ch >= 'a' && ch <= 'z';
}
bool isUpper(char ch) {
return ch >= 'A' && ch <= 'Z';
}
bool isDigit(char ch) {
return ch >= '0' && ch <= '9';
}
bool isDot(char ch) {
return ch == '.';
}

int getChId(char ch) {
if ('a' <= ch && ch <= 'z')
return ch - 'a';
else if ('A' <= ch && ch <= 'Z')
return ch - 'A' + 26;
else
return -1;
}

myString imgData;
struct BPB { // 11-35 25Bytes
uint16_t BPB_BytsPerSec; // 每扇区字节数 512
uint8_t BPB_SecPerClus; // 每簇扇区数 1
uint16_t BPB_RsvdSecCnt; // Boot记录占用的扇区数 1
uint8_t BPB_NumFATs; // FAT表个数 2
uint16_t BPB_RootEntCnt; // 根目录最大文件数 224
uint16_t BPB_TotSec16; // 扇区总数 2880
uint8_t BPB_Media; // 介质描述符 240
uint16_t BPB_FATSz16; // 每个FAT表所占扇区数 9
uint16_t BPB_SecPerTrk; // 每磁道扇区数(Sector/track) 18
uint16_t BPB_NumHeads; // 磁头数(面数) 2
uint32_t BPB_HiddSec; // 隐藏扇区数 0
uint32_t BPB_TotSec32; // 如果BPB_ToSec16为0,该值为扇区数 1610612736
};

struct Entry { // 32 Bytes
char FILE_Name[11]; // 文件名+扩展名
uint8_t FILE_Attr; // 文件属性
char reserved[10]; // 保留位
uint16_t DIR_WrtTime; // 最后一次写入时间
uint16_t DIR_WrtDate; // 最后一次写入日期
uint16_t DIR_FstClus; // 开始簇号
uint32_t DIR_FileSize; // 文件大小
myString fileName; // 文件名

bool isDir() { return this->FILE_Attr & 0x10; }
};

struct LongFileNameEntry { // unused temporarily
uint8_t LFNOrd; // 长文件名序号
uint16_t LFNPart1[5]; // 长文件名的第一部分
uint8_t LFNAttributes; // 文件属性标志
uint8_t LFNReserved1; // 保留字段,应为0x00
uint8_t LFNChecksum; // 校验和
uint16_t LFNPart2[6]; // 长文件名的第二部分
uint16_t LFNReserved2; // 保留字段,应为0x0000
uint16_t LFNPart3[2]; // 长文件名的第三部分
};
BPB getBPB(myString str) {
char* temp = str.toCharArray();
BPB bpb;
bpb.BPB_BytsPerSec = *((uint16_t*)&temp[0]);
bpb.BPB_SecPerClus = *((uint8_t*)&temp[2]);
bpb.BPB_RsvdSecCnt = *((uint16_t*)&temp[3]);
bpb.BPB_NumFATs = *((uint8_t*)&temp[5]);
bpb.BPB_RootEntCnt = *((uint16_t*)&temp[6]);
bpb.BPB_TotSec16 = *((uint16_t*)&temp[8]);
bpb.BPB_Media = *((uint8_t*)&temp[10]);
bpb.BPB_FATSz16 = *((uint16_t*)&temp[11]);
bpb.BPB_SecPerTrk = *((uint16_t*)&temp[13]);
bpb.BPB_NumHeads = *((uint16_t*)&temp[15]);
bpb.BPB_HiddSec = *((uint32_t*)&temp[17]);
bpb.BPB_TotSec32 = *((uint32_t*)&temp[21]);
return bpb;
}
Entry getEntery(myString str) {
char* temp = str.toCharArray();
Entry entry;

strncpy(entry.FILE_Name, temp, 11);
entry.FILE_Name[11] = '\0';
entry.FILE_Attr = temp[11];
strncpy(entry.reserved, &temp[12], 10);
entry.reserved[10] = '\0';
entry.DIR_WrtTime = *((uint16_t*)&temp[22]);
entry.DIR_WrtDate = *((uint16_t*)&temp[24]);
entry.DIR_FstClus = *((uint16_t*)&temp[26]);
entry.DIR_FileSize = *((uint32_t*)&temp[28]);
return entry;
}

BPB bpb;
vector<int> tabFAT;
vector<Entry> rootEntry;
vector<vector<Entry> > clusEntry;
vector<int> beginRootFile;
vector<myString> command_list = {"ls", "cat"};
vector<char> ls_options = {'l'}, cat_options = {};

int beginData;
Entry rt, errorNode;

struct Command {
Command()
: opName(""),
opParam(0),
target(""),
errorFlag(false),
errorMessage("") {}
myString opName; // 操作名
int opParam; // 参数
myString target; // 目标目录
bool errorFlag; // 错误标志
myString errorMessage; // 错误信息
void setError(myString str) {
errorFlag = true;
errorMessage = str;
}
};

void init(myString str); // 初始化
bool optionExist(char ch, myString opName); // 判断选项是否存在
bool lExist(int opt); // 判断是否存在l选项
int getNextClus(int dataEntryId); // 获取下一个簇
ui getSize(Entry entry); // 获取文件大小
vector<Entry> getChildrens(Entry, bool flag = false); // 获取某个目录的所有孩子
pii countChildren(Entry entry); // 子目录计数
pii countChildrenRoot(); // 根目录计数
myString getEntryInfo(Entry entry, bool flag = false); // 获取文件信息
myString getEntryName(Entry entry); // 获取文件名
myString lsRoot(int opt); // 根目录ls
myString ls(Entry entry, myString path, int opt); // ls
myString getClusData(int id); // 获取簇数据
myString cat(Entry entry); // cat
Entry getEntry(myString path); // 根据最简化路径获取Entry
myString getStartPath(myString path); // 根据完整路径获取起始最简化路径
Command get_command(myString input); // 解析命令,获取操作名、参数、目标目录
myString getSpecialName(Entry entry); // 获取特殊文件名(前一个目录的)
myString getNormalName(Entry entry); // 获取普通文件名
bool shouldLowerCase(myString str); // 判断是否需要获取小写名