exfat文件系统-基于fuse

简介

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.ccluster以及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
};
  • 14
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值