文章目录
简介
exfat文件系统规范:https://learn.microsoft.com/zh-CN/windows/win32/fileio/exfat-specification#6341-allocationpossible-field
exfat的文件系统在github上:https://github.com/relan/exfat.git
现在githug上的exfat文件系统只支持fuse v3.0.0以下版本。
本文主要关注基于fuse的exfat文件系统代码实现。
exfat的文件格式
Exfat文件头
DBR
DBR开始于EXFAT文件系统的第0号扇区。它由6部分组成,分别为跳转指令、OEM代号、保留区、BPB参数、引导程序和结束标志。DBR有一个备份位于12扇区,即0x1800地址
- 00H~02H:跳转指令0xEB 76 90,代表汇编语言的JMP 76。
- 03H~0AH:OEM代号,即明文EXFAT。
- 0BH~3FH:保留区,暂时不用,目前均为00H。
- 40H~77H:BPB参数区。(BIOS Parameter Block,BIOS参数块),其中一些重要的参数描述如下:
- 40H~47H:分区的起始扇区号,也称为隐藏扇区数,是指本分区前的物理扇区数。 图中为0x20,即分区是从物理扇区32开始
- 48H~4FH:分区的总扇区数。 总扇区数为:0x75F7FE0=123699168 对应到字节数*512约等于61个G
- 50H~53H:FAT表的起始扇区号,即从DBR到FAT的扇区数 0x800=2048:即FAT表位于2048逻辑扇区
- 54H~57H:FAT表占用的扇区数,它与分区大小、簇大小有关 0xF00=3840 大小为1.9M
- 58H~5BH:数据区的起始扇区号。一般是指簇位图的起始位置 0x1800=6144 5CH~5FH:分区内的总簇数
- 0x075f67=483175 60H~63H:根目录的起始簇号 0x04 根目录位于4簇 0x64 - 0x67 4 卷序列号 0x68- 0x69 2 卷版本号(固定为0001) 0x6A - 0x6B 2 卷状态 挂载时将会设置为2,卸载时写为0,如果挂载时发现内容已经为2,则表明上一次卸载是一次异常的行为
- 6CH~6CH:每扇区字节数(EXFAT中每扇区字节数用2的N次方的形式表示,一般总是09H,表示512)
- 6DH~6DH:每簇扇区数(2的N次方),EXFAT支持512B到32MB的簇大小。扇区数为8,即每簇有256个扇区,131072字节,128K 6E~6E:FAT表的个数。 exfat默认为1个FAT表根据微软的规范,TexFAT(Transaction-Safe Extended FAT)可以支持两个FAT以确保文件系统不会因为突然插拔导致错误
- 0x6F 1 驱动标记
- 0x70 1 分区使用百分比
- 78H~1FDH:共390字节的引导程序。
- 1FE~1FF:55 AA结束标志。
综上所述,由以上参数可以得到当前的U盘如下地址:
0x0 DBR
0x1800 DBR Backup
0x100000 FAT表
0x300000 簇位图
0x320000 大小写转换表
0x340000 根目录的目录项
FAT
exfat文件系统一般只要一个fat表。
每4个字节表示一个蔟的信息。
不同于fat32文件系统,如果文件连续存储,则蔟对应fat表的项中存储值为0,如果不连续存储,则蔟对应fat表的项中存储值为下一个存储蔟的蔟号。
①0、1号FAT项:为“F8FFFFFF FFFFFFFF”,表示FAT起始位置。
②2号FAT项:分配给元文件“簇位图”使用,“FFFFFFFF”是文件结束标志,表示“簇位图”这个文件占用了一个簇。
③3号FAT项:分配给元文件“大小写转换表”使用,“FFFFFFFF”是文件结束标志,表示“大小写转换表”这个文件占用了一个簇。
④4号FAT项:分配给根目录使用,“FFFFFFFF”是文件结束标志,表示目前根目录占用了一个簇。
簇位图
位于2号蔟。如果蔟使用了,则对应位被置1,否则为0。
每一位对应一个。第一个字节bit0对应2号蔟。bit7对应9号蔟。第二个字节bit0对应10号蔟。
目录项
目录项描述
目录项的属性固定长度为32,一般文件用三个目录项描述,分别为属性1+属性2+文件名,读取目录项的时候check_entries会检查目录项是否按照上述顺序进行排列。
属性1:
0x01附属目录项数表明后续还有几个目录项,一般都是长文件名下目录项会随之增多
0x04的文件属性如下:
属性2:
属性3:
struct exfat_entry_meta1 /* file or directory info (part 1) */
{
uint8_t type; /* EXFAT_ENTRY_FILE */
uint8_t continuations;
le16_t checksum;
le16_t attrib; /* combination of EXFAT_ATTRIB_xxx */
le16_t __unknown1;
le16_t crtime, crdate; /* creation date and time */
le16_t mtime, mdate; /* latest modification date and time */
le16_t atime, adate; /* latest access date and time */
uint8_t crtime_cs; /* creation time in cs (centiseconds) */
uint8_t mtime_cs; /* latest modification time in cs */
uint8_t crtime_tzo, mtime_tzo, atime_tzo; /* timezone offset encoded */
uint8_t __unknown2[7];
}
PACKED;
struct exfat_entry_meta2 /* file or directory info (part 2) */
{
uint8_t type; /* EXFAT_ENTRY_FILE_INFO */
uint8_t flags; /* combination of EXFAT_FLAG_xxx */
uint8_t __unknown1;
uint8_t name_length;
le16_t name_hash;
le16_t __unknown2;
le64_t valid_size; /* in bytes, less or equal to size */
uint8_t __unknown3[4];
le32_t start_cluster;
le64_t size; /* in bytes */
}
struct exfat_entry_name /* file or directory name */
{
uint8_t type; /* EXFAT_ENTRY_FILE_NAME */
uint8_t __unknown;
le16_t name[EXFAT_ENAME_MAX]; /* in UTF-16LE */
}
代码结构
主要涉及文件
├── fuse
│ ├── main.c
├── libexfat
│ ├── byteorder.h
│ ├── cluster.c
│ ├── compiler.h
│ ├── exfatfs.h
│ ├── exfat.h
│ ├── io.c
│ ├── log.c
│ ├── lookup.c
│ ├── mount.c
│ ├── node.c
│ ├── platform.h
│ ├── repair.c
│ ├── time.c
│ ├── utf.c
│ └── utils.c
文件 | 描述 |
---|---|
cluster.c | cluster以及bitmap的申请释放,来支持对文件的操作。 |
io.c | 各种IO相关操作的支持。 |
lookup.c | 主要有exfat_lookup函数,以及exfat_readdir函数。 |
main.c | 主要是对ecfat文件格式的block设备的mount; 解析options;注册fuse_exfat_ops,并启动fuse守护进程。 |
mount.c | 主要实现exfat_mount和exfat_unmount函数。 |
node.c | 对文件各种操作的支持。 |
主要结构体
struct exfat
{
struct exfat_dev* dev; //指向exfat_dev
struct exfat_super_block* sb; //指向super block
uint16_t* upcase; //指向 EXFAT_ENTRY_UPCASE 的entry解析出的 大写表格目录条目
struct exfat_node* root; //指向根目录node。
struct
{
cluster_t start_cluster; //bitmap的起始cluster
uint32_t size;//cluster count /* in bits */
bitmap_t* chunk;//指向malloc的bitmap_t
uint32_t chunk_size;//cluster count /* in bits */
bool dirty;
}
cmap; //指向 EXFAT_ENTRY_BITMAP 的entry解析出的 cluster位图
char label[EXFAT_UTF8_ENAME_BUFFER_MAX]; //存储 EXFAT_ENTRY_LABEL中解析的label
void* zero_cluster;//一个空的cluster用来,erase cluster。
int dmask, fmask;//mount option的opt_umask和fmask
uid_t uid;//mount option的uid
gid_t gid;//mount option的gid
int ro;
bool noatime;//mount option的noatime
enum { EXFAT_REPAIR_NO, EXFAT_REPAIR_ASK, EXFAT_REPAIR_YES } repair;
};
struct exfat_dev
{
int fd;//dev block的fd
enum exfat_mode mode;//mount option解析可以设置为EXFAT_MODE_RO,EXFAT_MODE_RW,EXFAT_MODE_ANY,决定exfat打开fd的方式是ro,rw。
off_t size; //fd的size/* in bytes */
#ifdef USE_UBLIO
off_t pos;
ublio_filehandle_t ufh;
#endif
};
enum exfat_mode
{
EXFAT_MODE_RO,
EXFAT_MODE_RW,
EXFAT_MODE_ANY,
};
struct exfat_super_block
{
uint8_t jump[3]; /* 0x00 jmp and nop instructions */
uint8_t oem_name[8]; /* 0x03 "EXFAT " */
uint8_t __unused1[53]; /* 0x0B always 0 */
le64_t sector_start; /* 0x40 partition first sector */
le64_t sector_count; /* 0x48 partition sectors count */
le32_t fat_sector_start; /* 0x50 FAT first sector */
le32_t fat_sector_count; /* 0x54 FAT sectors count */
le32_t cluster_sector_start; /* 0x58 first cluster sector */
le32_t cluster_count; /* 0x5C total clusters count */
le32_t rootdir_cluster; /* 0x60 first cluster of the root dir */
le32_t volume_serial; /* 0x64 volume serial number */
struct /* 0x68 FS version */
{
uint8_t minor;
uint8_t major;
}
version;
le16_t volume_state; /* 0x6A volume state flags */
uint8_t sector_bits; /* 0x6C sector size as (1 << n) */
uint8_t spc_bits; /* 0x6D sectors per cluster as (1 << n) */
uint8_t fat_count; /* 0x6E always 1 */
uint8_t drive_no; /* 0x6F always 0x80 */
uint8_t allocated_percent; /* 0x70 percentage of allocated space */
uint8_t __unused2[397]; /* 0x71 always 0 */
le16_t boot_signature; /* the value of 0xAA55 */
}
PACKED;
STATIC_ASSERT(sizeof(struct exfat_super_block) == 512);
struct exfat_node
{
struct exfat_node* parent;
struct exfat_node* child;
struct exfat_node* next;
struct exfat_node* prev;
int references; //文件被访问++
uint32_t fptr_index;//指向文件中能够当前正在访问的cluster index
cluster_t fptr_cluster; //从start_cluster开始,指向文件中能够当前正在访问的cluster
off_t entry_offset; //node 在parent node的clutser中的entry的offset
cluster_t start_cluster; //node对应文件 存在cluster的位置
uint16_t attrib;//node的attr EXFAT_ATTRIB_DIR表示文件,
uint8_t continuations; //表示node对应文件在FAT表上,cluster是连续的,或者说用连续的cluster描述文件内容
bool is_contiguous : 1;
bool is_cached : 1;
bool is_dirty : 1;
bool is_unlinked : 1;
uint64_t size;//文件或者文件夹的大小,
time_t mtime, atime;//这两个时间 最后mtime修改时间 最后atime访问时间
le16_t name[EXFAT_NAME_MAX + 1];//文件名
};
对于node中的parent,child,next,prev的说明如下图:
root中有a,b,c三个文件夹,其中a是root文件夹所在的cluster中的第一个node,a中有a1和a2两个文件,c中有c1文件。
下图是winhex打开的,U盘根目录的文件描述
1.根目录下面有a,b ,c三个文件。
2.已经删除的文件以及文件夹,依旧可以看到。(不懂数据恢复的香港男明星)
3.根目录下同时能看到bitmap和upcase文件。
4.每个文件夹大小都为128K(本人U盘格式化为128K一个cluster),但是如果一个文件夹中的子文件夹或者文件太多,可以申请更多的cluster。
下图是winhex打开的,root node的cluster
1.在本文描述的exfat文件系统中,读取disk中的文件夹以及文件的时候,会找到文件夹对应的cluster,读取32字节的exfat_entry信息来解析。
exfat_entry描述如下,type的种类主要有bitmap(0x81),upcase(0x82)以及file(0x85)。
#define EXFAT_ENTRY_VALID 0x80
#define EXFAT_ENTRY_BITMAP (0x01 | EXFAT_ENTRY_VALID) //bitmap
#define EXFAT_ENTRY_UPCASE (0x02 | EXFAT_ENTRY_VALID) //大写表格目录条目
#define EXFAT_ENTRY_FILE (0x05 | EXFAT_ENTRY_VALID) //文件以及文件夹的描述
struct exfat_entry /* common container for all entries */
{
uint8_t type; /* any of EXFAT_ENTRY_xxx */
uint8_t data[31];
}
PACKED;
STATIC_ASSERT(sizeof(struct exfat_entry) == 32);
2.关于上图中bitmap和upcase的描述如下
struct exfat_entry_bitmap /* allocated clusters bitmap */
{
uint8_t type; /* EXFAT_ENTRY_BITMAP */
uint8_t __unknown1[19];
le32_t start_cluster; //bitmap的存储的cluster
le64_t size; /* in bytes */
}
PACKED;
STATIC_ASSERT(sizeof(struct exfat_entry_bitmap) == 32);
struct exfat_entry_upcase /* upper case translation table */
{
uint8_t type; /* EXFAT_ENTRY_UPCASE */
uint8_t __unknown1[3];
le32_t checksum;
uint8_t __unknown2[12];
le32_t start_cluster; //upcase存储的cluster
le64_t size; /* in bytes */
}
PACKED;
STATIC_ASSERT(sizeof(struct exfat_entry_upcase) == 32);
3.参考上图中的根目录的cluster。对于文件夹a来说第一个entry是meta1,第二个entry是meta2,第三个entry是name。关于meta1和meta2 的描述如下:
a文件描述的type 0x85(代表文件类型) attrib 0x10(代表文件夹) start_cluster 为0x05(代表a文件的内容在cluster 5) valid_size 0x20000 (文件夹的大小为0x20000-128K) size 0x20000 (文件夹的大小为0x20000-128K)
关于目录项相关的信息参考
#define EXFAT_ATTRIB_DIR 0x10
#define EXFAT_ATTRIB_ARCH 0x20
struct exfat_entry_meta1 /* file or directory info (part 1) */
{
uint8_t type; /* EXFAT_ENTRY_FILE */
uint8_t continuations;
le16_t checksum;
le16_t attrib; /* combination of EXFAT_ATTRIB_xxx */
le16_t __unknown1;
le16_t crtime, crdate; /* creation date and time */
le16_t mtime, mdate; /* latest modification date and time */
le16_t atime, adate; /* latest access date and time */
uint8_t crtime_cs; /* creation time in cs (centiseconds) */
uint8_t mtime_cs; /* latest modification time in cs */
uint8_t crtime_tzo, mtime_tzo, atime_tzo; /* timezone offset encoded */
uint8_t __unknown2[7];
}
PACKED;
STATIC_ASSERT(sizeof(struct exfat_entry_meta1) == 32);
struct exfat_entry_meta2 /* file or directory info (part 2) */
{
uint8_t type; /* EXFAT_ENTRY_FILE_INFO */
uint8_t flags; /* combination of EXFAT_FLAG_xxx */
uint8_t __unknown1;
uint8_t name_length;
le16_t name_hash;
le16_t __unknown2;
le64_t valid_size; /* in bytes, less or equal to size */
uint8_t __unknown3[4];
le32_t start_cluster;
le64_t size; /* in bytes */
}
PACKED;
STATIC_ASSERT(sizeof(struct exfat_entry_meta2) == 32);
a文件夹的cluster内容如下
1.对于a文件夹的cluster为前面描述的cluster 05。
2.对于a1空文件 type 0x85(代表文件类型) , attrib 0x20(代表文件), start_cluster 为0x0 ,valid_size为0x0, size 为0x0。
3.对于logfs文件 mv成为a2文件,可以看到除了各个exfat_entry的type有变化,其他的
4.关于a2文件type 0x85(代表文件类型) , attrib 0x20(代表文件), start_cluster 为0x08 ,valid_size为 0x20f(527字节), size为 0x20f。
关于FAT表
对于一个大文件的情况,以c文件夹中的c1文件为例子。c1文件大小为1MB,start cluster是 9,每个cluster是128KB,c1文件占用cluster 9到clsuter 16, 8个cluster。
我们看一下c1文件的FAT表。可以看到c1文件(cluster 9->16)的FAT表都是0,c1应该是contiguous。
如何让文件对应的FAT表中有数据呢,就是让文件的cluster number不连续。
1.创建了一个文件c1,大小为1M,占用cluster9->16。
2.在创建一个文件c2,大小为1M,占用cluster17->24。
3.增大文件c1到1.5M,如下图。
在打开FAT表发现c1文件的FAT表中有值。这个是后c1文件占用的cluster为cluster 0x9->0x10(1M) 和0x19->0x1c(512k),其间为c2文件的FAT表,因为c2文件的cluster是连续的所以,c2文件的FAT表为0。
文件,文件的cluster,以及FAT表的关系,看下面函数实现exfat_advance_cluster。
cluster_t exfat_advance_cluster(const struct exfat* ef,
struct exfat_node* node, uint32_t count)//count为在文件中以cluster为单位的偏移,该值由文件的offset/cluster size来
{
uint32_t i;
if (node->fptr_index > count)
{
node->fptr_index = 0;
node->fptr_cluster = node->start_cluster;
}
for (i = node->fptr_index; i < count; i++)//根据cluster number在FAT表中逐个遍历文件的cluser。
{
node->fptr_cluster = exfat_next_cluster(ef, node, node->fptr_cluster);//找到当前文件的cluster对应FAT表,根据FAT表找到该文件下一个cluster号。
if (CLUSTER_INVALID(*ef->sb, node->fptr_cluster))
break; /* the caller should handle this and print appropriate
error message */
}
node->fptr_index = count;
return node->fptr_cluster;
}
调用的exfat_next_cluster函数。
cluster_t exfat_next_cluster(const struct exfat* ef,
const struct exfat_node* node, cluster_t cluster)
{
le32_t next;
off_t fat_offset;
if (cluster < EXFAT_FIRST_DATA_CLUSTER)
exfat_bug("bad cluster 0x%x", cluster);
if (node->is_contiguous)//如果node是is_contiguous状态,返回 cluster+1。
return cluster + 1;
fat_offset = s2o(ef, le32_to_cpu(ef->sb->fat_sector_start))
+ cluster * sizeof(cluster_t);//找到cluster对应的FAT表的offset
if (exfat_pread(ef->dev, &next, sizeof(next), fat_offset) < 0)//读取对应的FAT表中的值,找到下一个cluster号。
return EXFAT_CLUSTER_BAD; /* the caller should handle this and print
appropriate error message */
return le32_to_cpu(next);
}
关于BITMAP
格式化之后的U盘。对于bitmap如下图:
对于upcase,如下图:
对于根目录,如下图:
在根目录下创建一个a文件夹占用cluster4,如下图:
在根目录下创建一个a文件夹占用cluster4时,bitmap的使用情况。可见bitmap从0x07变为0x0f,cluster 4 标记为占用。
代码实现
exfat_mount
1.调用exfat_open函数填充全局变量exfat结构体的exfat_dev。
2.读出fd的DBR,用来填充exfat结构体的exfat_super_block。
3.检查DBR中的exfat_super_block,是否符合要求。
4.调用exfat_cache_directory函数,解析root的cluster,依次解析bitmap,upcase,以及根目录的file,并且建立根目录下面文件的层级关系。
exfat_generic_pwrite
对文件的写,通过该函数实现。解释如下
ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
const void* buffer, size_t size, off_t offset)//node是要写入的文件,buffer是写入内容,size是写入内容的大小,offset是对于文件写入地址的偏移
{
uint64_t newsize = offset;
int rc;
cluster_t cluster;
const char* bufp = buffer;
off_t lsize, loffset, remainder;
if (offset < 0)
return -EINVAL;
if (newsize > node->size)
{
rc = exfat_truncate(ef, node, newsize, true);
if (rc != 0)
return rc;
}
if (newsize + size > node->size)//如果当前node的size覆盖不了offset(文件写入地址的偏移)或者offset+size(是写入内容的大小),就要exfat_truncate
{
rc = exfat_truncate(ef, node, newsize + size, false);//参数newsize + size是写入之后文件的size,参数false是指不执行erase
if (rc != 0)
return rc;
}
if (size == 0)
return 0;
cluster = exfat_advance_cluster(ef, node, newsize / CLUSTER_SIZE(*ef->sb));//根据当前文件在FAT表中的描述,找到offset所在的cluster,从offset所在的cluster开始写。
if (CLUSTER_INVALID(*ef->sb, cluster))
{
exfat_error("invalid cluster 0x%x while writing", cluster);
return -EIO;
}
loffset = newsize % CLUSTER_SIZE(*ef->sb);
remainder = size;
while (remainder > 0)
{
if (CLUSTER_INVALID(*ef->sb, cluster))
{
exfat_error("invalid cluster 0x%x while writing", cluster);
return -EIO;
}
lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
if (exfat_pwrite(ef->dev, bufp, lsize,//将buffer中的内容写到相应的offset
exfat_c2o(ef, cluster) + loffset) < 0)//exfat_c2o将cluster的编号转换成offset
{
exfat_error("failed to write cluster %#x", cluster);
return -EIO;
}
bufp += lsize;
loffset = 0;
remainder -= lsize;
cluster = exfat_next_cluster(ef, node, cluster);//根据当前cluster对应的FAT表中的描述,找到下一个cluster。
}
if (!(node->attrib & EXFAT_ATTRIB_DIR))
/* directory's mtime should be updated by the caller only when it
creates or removes something in this directory */
exfat_update_mtime(node);//如果不是文件夹,更新修改日期。
return size - remainder;
}
再看exfat_truncate函数实现,函数根据传入的size大小跟node的size大小作比较,如果文件增大有需要就申请cluster,文件减小有需要就释放cluster,这两个操作同时会设置相应的bitmap和FAT表。并设置是否是contiguous。
int exfat_truncate(struct exfat* ef, struct exfat_node* node, uint64_t size,
bool erase)
{
uint32_t c1 = bytes2clusters(ef, node->size);//写入之前的文件占用的cluster
uint32_t c2 = bytes2clusters(ef, size);//写入之后文件占用的cluster
int rc = 0;
if (node->references == 0 && node->parent)
exfat_bug("no references, node changes can be lost");
if (node->size == size)
return 0;
if (c1 < c2)//写入之前的文件占用的cluster 小于 写入之后文件占用的cluster。 就要调用grow_file申请新的cluster。
rc = grow_file(ef, node, c1, c2 - c1);
else if (c1 > c2)//写入之前的文件占用的cluster 大于 写入之后文件占用的cluster。 就要调用shrink_file删除不用的cluster。
rc = shrink_file(ef, node, c1, c1 - c2);
if (rc != 0)
return rc;
if (erase)
{
rc = erase_range(ef, node, node->size, size);//清空文件相应区间
if (rc != 0)
return rc;
}
exfat_update_mtime(node);//更新文件修改时间
node->size = size;//更新size
node->is_dirty = true;//node变为is_dirty。
return 0;
}
grow_file函数实现如下:
static int grow_file(struct exfat* ef, struct exfat_node* node,
uint32_t current, uint32_t difference)
{
cluster_t previous;
cluster_t next;
uint32_t allocated = 0;
if (difference == 0)
exfat_bug("zero clusters count passed");
if (node->start_cluster != EXFAT_CLUSTER_FREE)//对于非空文件或者文件夹的处理方法,空文件的start_cluster为EXFAT_CLUSTER_FREE(0)
{
/* get the last cluster of the file */
previous = exfat_advance_cluster(ef, node, current - 1);//找到上一个cluster
if (CLUSTER_INVALID(*ef->sb, previous))
{
exfat_error("invalid cluster 0x%x while growing", previous);
return -EIO;
}
}
else
{
if (node->fptr_index != 0)
exfat_bug("non-zero pointer index (%u)", node->fptr_index);
/* file does not have clusters (i.e. is empty), allocate
the first one for it */
previous = allocate_cluster(ef, 0);//空文件直接申请一个cluster。allocate_cluster的方式是从第二个参数,逐个遍历bitmap找到没有占用的,返回cluster number,并标记相应的bitmap为1。
if (CLUSTER_INVALID(*ef->sb, previous))
return -ENOSPC;
node->fptr_cluster = node->start_cluster = previous;//fptr_cluster 和 start_cluster
allocated = 1;
/* file consists of only one cluster, so it's contiguous */
node->is_contiguous = true;//node的cluster为连续
}
while (allocated < difference)//allocated是新生申请的cluster个数,difference是需要的cluster个数。
{
next = allocate_cluster(ef, previous + 1);//从previous + 1对应的bitmap开始申请cluster。
if (CLUSTER_INVALID(*ef->sb, next))
{
if (allocated != 0)
shrink_file(ef, node, current + allocated, allocated);
return -ENOSPC;
}
if (next != previous + 1 && node->is_contiguous)//判断申请的cluster是不是连续。
{
/* it's a pity, but we are not able to keep the file contiguous
anymore */
if (!make_noncontiguous(ef, node->start_cluster, previous))//如果不连续,需要填充FAT表!!
return -EIO;
node->is_contiguous = false;//设置为不连续
node->is_dirty = true;//标记为drity
}
if (!set_next_cluster(ef, node->is_contiguous, previous, next))
return -EIO;
previous = next;
allocated++;
}
if (!set_next_cluster(ef, node->is_contiguous, previous,
EXFAT_CLUSTER_END))
return -EIO;
return 0;
}
fuse_exfat_mkdir
主要调用了exfat_mkdir函数实现
int exfat_mkdir(struct exfat* ef, const char* path)//根据参数path创建文件夹
{
int rc;
struct exfat_node* node;
rc = create(ef, path, EXFAT_ATTRIB_DIR);//创建文件夹的实现
if (rc != 0)
return rc;
rc = exfat_lookup(ef, &node, path);//找到创建的文件夹,返回node。
if (rc != 0)
return 0;
/* directories always have at least one cluster */
rc = exfat_truncate(ef, node, CLUSTER_SIZE(*ef->sb), true);//给创建的文件夹申请一个cluster
if (rc != 0)
{
delete(ef, node);
exfat_put_node(ef, node);
return rc;
}
rc = exfat_flush_node(ef, node);
if (rc != 0)
{
delete(ef, node);
exfat_put_node(ef, node);
return rc;
}
exfat_put_node(ef, node);
return 0;
}
create函数实现如下:
static int create(struct exfat* ef, const char* path, uint16_t attrib)
{
struct exfat_node* dir;
struct exfat_node* existing;
off_t offset = -1;
le16_t name[EXFAT_NAME_MAX + 1];
int rc;
rc = exfat_split(ef, &dir, &existing, name, path);//根据传入的path,逐级找到创建文件夹的parent。 若能找到,建文件夹的parent的node赋值给参数dir,existing标记为1。
if (rc != 0)
return rc;
if (existing != NULL) //path不存在,退出
{
exfat_put_node(ef, existing);
exfat_put_node(ef, dir);
return -EEXIST;
}
rc = find_slot(ef, dir, &offset,
2 + DIV_ROUND_UP(exfat_utf16_length(name), EXFAT_ENAME_MAX));//dir为要创建的文件夹的parent文件夹的node。offset为创建的文件entry在parent文件夹的偏移。
if (rc != 0)
{
exfat_put_node(ef, dir);
return rc;
}
rc = commit_entry(ef, dir, name, offset, attrib);//将创建文件夹的信息写入对应的offset,并创建文件夹的node。
if (rc != 0)
{
exfat_put_node(ef, dir);
return rc;
}
exfat_update_mtime(dir);//更新parent文件夹的修改时间
rc = exfat_flush_node(ef, dir);
exfat_put_node(ef, dir);
return rc;
}
find_slot函数实现如下:
static int find_slot(struct exfat* ef, struct exfat_node* dir,
off_t* offset, int n)//参数n代表需要占用多少个连续的exfat_entry,对于文件来说一般是3个exfat_entry。
{
bitmap_t* dmap;
struct exfat_node* p;
size_t i;
int contiguous = 0;
if (!dir->is_cached)
exfat_bug("directory is not cached");
/* build a bitmap of valid entries in the directory */
dmap = calloc(BMAP_SIZE(dir->size / sizeof(struct exfat_entry)),//对于文件夹的dir->size,一般是以cluster的size为单位的。
sizeof(bitmap_t));//对parent文件夹的cluster中的每个exfat_entry做一个bitmap(注意这个bitmap跟上文提到的cluster使用bitmap无关)
if (dmap == NULL)
{
exfat_error("failed to allocate directory bitmap (%"PRIu64")",
dir->size / sizeof(struct exfat_entry));
return -ENOMEM;
}
for (p = dir->child; p != NULL; p = p->next)//遍历parent文件夹下所有的文件node。
for (i = 0; i < 1u + p->continuations; i++)
BMAP_SET(dmap, p->entry_offset / sizeof(struct exfat_entry) + i);//根据node的entry_offset标记bitmap,代表该exfat_entry已经被占用。
/* find a slot in the directory entries bitmap */
for (i = 0; i < dir->size / sizeof(struct exfat_entry); i++)//遍历bitmap中的exfat_entry,找到连续n个空闲的exfat_entry。
{
if (BMAP_GET(dmap, i) == 0)
{
if (contiguous++ == 0)
*offset = (off_t) i * sizeof(struct exfat_entry);
if (contiguous == n)//找到连续n个空闲的exfat_entry。
{
int rc;
/* suitable slot is found, check that it's not occupied */
rc = check_slot(ef, dir, *offset, n);//check对应的exfat_entry
if (rc == -EINVAL)
{
/* slot at (i-n) is occupied, go back and check (i-n+1) */
i -= contiguous - 1;
contiguous = 0;
}
else
{
/* slot is free or an error occurred */
free(dmap);
return rc;
}
}
}
else
contiguous = 0;
}
free(dmap);
/* no suitable slots found, extend the directory */
if (contiguous == 0)//如果找不到合适的exfat_entry存储文件的node
*offset = dir->size;//offset设置到parent文件夹的末端。
return exfat_truncate(ef, dir,
ROUND_UP(dir->size + sizeof(struct exfat_entry[n - contiguous]),
CLUSTER_SIZE(*ef->sb)), true);//parent文件夹申请更多的cluster来存储新文件夹的exfat_entry。
}
exfat_lookup主要是根据参数path,遍历对应个层级的node,最终找到path对应的node之后返回。
int exfat_lookup(struct exfat* ef, struct exfat_node** node,
const char* path)
{
struct exfat_node* parent;
const char* p;
size_t n;
int rc;
/* start from the root directory */
parent = *node = exfat_get_node(ef->root);
for (p = path; (n = get_comp(p, &p)); p += n)//根据path做文件夹的步进查找
{
if (n == 1 && *p == '.') /* skip "." component */
continue;
rc = lookup_name(ef, parent, node, p, n);//exfat_readdir参数exfat_iterator找到parent的node,依次读取对应的parent下的node。
if (rc != 0)
{
exfat_put_node(ef, parent);
return rc;
}
exfat_put_node(ef, parent);
parent = *node;//查找下一级文件
}
return 0;
}
fuse_exfat_ops
最后介绍基于fuse的exfat的ops,在exfat的main函数中注册了fuse的exfat ops如下,关于fuse暂不介绍。
rc = fuse_exfat_main(fuse_options, mount_point);
fuse_exfat_ops的各个函数的描述如下:
static struct fuse_operations fuse_exfat_ops =
{
.getattr = fuse_exfat_getattr, //从root node开始便利查找,对应path下面的文件,找到对应的node并解析,获取status。
.truncate = fuse_exfat_truncate,//首先查找文件,文件存在调用exfat_truncate 通过申请cluster调整文件大小。
.readdir = fuse_exfat_readdir,//找到path对应的node,找到描述node的cluster,将cluster中的entry读出来解析。
.open = fuse_exfat_open,//找到path对应的node,返回fuse_file_info
.create = fuse_exfat_create,//返回fuse_file_info
.release = fuse_exfat_release,//释放fuse_file_info对应node的references
.flush = fuse_exfat_flush,//flash fuse_file_info的node
.fsync = fuse_exfat_fsync,//先给dirty的node都flush,再flush bitmap。最后fsync exfat_dev的fd。
.fsyncdir = fuse_exfat_fsync,//同上
.read = fuse_exfat_read,//调用exfat_generic_pread函数读文件
.write = fuse_exfat_write,//调用exfat_generic_pwrite函数写文件
.unlink = fuse_exfat_unlink,//删除文件(不包括文件夹),先调用exfat_lookup查找文件,找到之后调用delete删除。
.rmdir = fuse_exfat_rmdir,//删除文件夹,先调用exfat_lookup查找文件,在调用exfat_cache_directory看看当前文件夹中是不是有子文件,若果没有调用delete删除。
.mknod = fuse_exfat_mknod,//创建文件
.mkdir = fuse_exfat_mkdir,//创建文件夹,主要是调用create函数创建文件夹。
.rename = fuse_exfat_rename,//调用exfat_rename函数,重命名,或者移动文件。找到源文件的node,将源文件的node,通过rename_entry函数写到新文件的node。
.utimens = fuse_exfat_utimens,//修改文件的mtime和atime
.chmod = fuse_exfat_chmod,//检查设置的mode是否合理
.chown = fuse_exfat_chown,//设置UID和PID
.statfs = fuse_exfat_statfs,//??
.init = fuse_exfat_init,//设置sb的volume_state 为EXFAT_STATE_MOUNTED
.destroy = fuse_exfat_destroy,//调用 exfat_unmount
};