FAT32分析(十一)—文件写操作

FAT32分析(十一)—文件写操作

回顾

在FAT32(十)中实现了文件读取的操作,本文将继续分析FAT32如何实现文件的写操作,就是实现存在的文件或者不存在的文件进行数据的写入.文件的写操作有:文件名字修改,文件时间修改,目录创建,文件创建,文件删除,目录删除。

修改文件名称

文件的信息主要存储在目录项中,修改文件名字就是修改目录项中的文件名字.这里需要注意的是文件名字是由"文件名"+"后缀名"组成的。由于在FAT32文件中,文件的名字存储都是已大写的字母存储在磁盘中,我们看到的小写起始由于目录项中有一个标记的属性【DIR_NTRes】记载着文件名字的大小写。所以,修改文件的名字时候,需要将DIR_NTRes也修改掉。

int main (void) 
{
    xfat_err_t err;
    int i;

    for (i = 0; i < sizeof(write_buffer) / sizeof(u32_t); i++) 
    {
        write_buffer[i] = i;
    }

    err = xdisk_open(&disk, "vidsk", &vdisk_driver, (void *)disk_path);
    if (err) 
    {
        printf("open disk failed!\n");
        return -1;
    }

    err = disk_part_test();
    if (err) return err;

    err = xdisk_get_part(&disk, &disk_part, 1);
    if (err < 0) 
    {
        printf("read partition info failed!\n");
        return -1;
    }

    err = xfat_open(&xfat, &disk_part);
    if (err < 0) 
    {
        return err;
    }

    err = fs_modify_file_test();
    if (err) return err;

    err = xdisk_close(&disk);
    if (err) 
    {
        printf("disk close failed!\n");
        return -1;
    }

    printf("Test End!\n");
    return 0;
}

代码实现

主函数

int main (void) 
{
    xfat_err_t err;
    int i;

    for (i = 0; i < sizeof(write_buffer) / sizeof(u32_t); i++) 
    {
        write_buffer[i] = i;
    }

    err = xdisk_open(&disk, "vidsk", &vdisk_driver, (void *)disk_path);
    if (err) 
    {
        printf("open disk failed!\n");
        return -1;
    }

    err = disk_part_test();
    if (err) return err;

    err = xdisk_get_part(&disk, &disk_part, 1);
    if (err < 0) 
    {
        printf("read partition info failed!\n");
        return -1;
    }

    err = xfat_open(&xfat, &disk_part);
    if (err < 0) 
    {
        return err;
    }

    err = fs_modify_file_test(); // 文件名字修改测试
    if (err) return err;

    err = xdisk_close(&disk);
    if (err) 
    {
        printf("disk close failed!\n");
        return -1;
    }

    printf("Test End!\n");
    return 0;
}

测试代码

xfat_err_t fs_modify_file_test(void) 
{
    xfat_err_t err;
    xfile_t file;
    const char * dir_path = "/modify/a0/a1/a2/";
    const char file_name1[] = "ABC.efg";
    const char file_name2[] = "efg.ABC";
    const char * new_name;
    char curr_path[64];

    printf("modify file attr test...\n");

    printf("\n Before rename:\n");

    // 显示原目录下的文件
    err = xfile_open(&xfat, &file, dir_path);
    if (err < 0) 
    {
        printf("Open dir failed!\n");
        return err;
    }

    err = list_sub_files(&file, 0);
    if (err < 0) 
    {
        return err;
    }
    xfile_close(&file);

    // 尝试打开其中一个路径,判断如何命名
    sprintf(curr_path, "%s%s", dir_path, file_name1);
    err = xfile_open(&xfat, &file, curr_path);
    if (err < 0) 
    {
        // 打开文件1失败,则当前文件2存在,新名称为文件1名称
        sprintf(curr_path, "%s%s", dir_path, file_name2);
        new_name = file_name1;
    }
    else 
    {
        sprintf(curr_path, "%s%s", dir_path, file_name1);
        new_name = file_name2;
    }

    // 文件重命名
    err = xfile_rename(&xfat, curr_path, new_name);
    if (err < 0) 
    {
        printf("rename failed: %s -- to -- %s\n", curr_path, new_name);
        return err;
    }
    xfile_close(&file);

    printf("\n After rename:\n");

    // 重命名后,列表显示所有文件,显示命名状态
    err = xfile_open(&xfat, &file, dir_path);
    if (err < 0) 
    {
        printf("Open dir failed!\n");
        return err;
    }

    err = list_sub_files(&file, 0);
    if (err < 0) 
    {
        return err;
    }
    xfile_close(&file);

    return FS_ERR_OK;
}

文件重命名函数

/**
 * 文件重命名
 * @param xfat
 * @param path 需要命名的文件完整路径/modify/a0/a1/a2/
 * @param new_name 文件新的名称 ABC.efg 或者efg.ABC 就是这两个文件名字互换
 * @return
 */
xfat_err_t xfile_rename(xfat_t* xfat, const char* path, const char* new_name) 
{
    diritem_t* diritem = (diritem_t*)0; // 目录项
    u32_t curr_cluster, curr_offset;    // 当前簇号,当前偏移
    u32_t next_cluster, next_offset;    // 下一个簇,下一个簇偏移
    u32_t found_cluster, found_offset;  // 目标所在的簇,目标在当前簇的偏移
    const char * curr_path;             // 当前传入的路径

    curr_cluster = xfat->root_cluster; // 从根目录开始寻找
    curr_offset = 0;                   // 当前偏移为0
    //这个for里面的判断:"/modify/a0/a1/a2/"为当前传入的路径,第一次循环是找到/modify
    // 第二次是找到/modify/a0 直到找到/modify/a0/a1/a2/
    for (curr_path = path; curr_path != '\0'; curr_path = get_child_path(curr_path)) 
    {
        do {
            // 找到下一个目录项
            // 参数:DIRITEM_GET_USED 目录是被使用的
            //       curr_cluster     当前簇  curr_offset 当前簇偏移
            //       found_cluster    目标簇  found_offset 目标簇偏移
            //       next_cluster     下一簇  next_offset  下一簇偏移
            //       temp_buffer      缓冲区  diritem      目录项
            xfat_err_t err = get_next_diritem(xfat, DIRITEM_GET_USED, curr_cluster, curr_offset,
                    &found_cluster, &found_offset , &next_cluster, &next_offset, temp_buffer, &diritem);
            if (err < 0) {
                return err;
            }

            if (diritem == (diritem_t*)0) {    // 已经搜索到目录结束
                return FS_ERR_NONE;
            }
            // 判断目标的目录项中记载的名字是否是匹配
            if (is_filename_match((const char*)diritem->DIR_Name, curr_path)) 
            {
                // 找到,比较下一级子目录
                if (get_file_type(diritem) == FAT_DIR) {
                    curr_cluster = get_diritem_cluster(diritem);
                    curr_offset = 0;
                }
                break;
            }
            // 如果当前簇没有找到那么就继续找下一簇
            curr_cluster = next_cluster;
            curr_offset = next_offset;
        } while (1);
    }

    if (diritem && !curr_path) 
    {
        // 这种方式只能用于SFN文件项重命名
        u32_t dir_sector = to_phy_sector(xfat, found_cluster, found_offset);
        to_sfn((char *)diritem->DIR_Name, new_name);

        // 根据文件名的实际情况,重新配置大小写
        diritem->DIR_NTRes &= ~DIRITEM_NTRES_CASE_MASK;
        diritem->DIR_NTRes |= get_sfn_case_cfg(new_name);
        // 写入新名字
        return xdisk_write_sector(xfat_get_disk(xfat), temp_buffer, dir_sector, 1);
    }

    return FS_ERR_OK;
}

相关API

/**
 * 获取子路径
 * @param dir_path 上一级路径
 * @return
 */
const char * get_child_path(const char *dir_path) 
{
    const char * c = skip_first_path_sep(dir_path);

    // 跳过父目录
    while ((*c != '\0') && !is_path_sep(*c)) 
    {
        c++;
    }

    return (*c == '\0') ? (const char *)0 : c + 1;
}
/**
 * 获取下一个有效的目录项
 * @param xfat
 * @param curr_cluster 当前目录项对应的簇号
 * @param curr_offset  当前目录项对应的偏移
 * @param next_cluster 下一目录项对应的簇号
 * @param next_offset  当前目录项对应的簇偏移
 * @param temp_buffer 簇存储的缓冲区
 * @param diritem 下一个有效的目录项
 * @return
 */
 // 找到下一个目录项
            // 参数:DIRITEM_GET_USED 目录是被使用的
            //       curr_cluster     当前簇  curr_offset 当前簇偏移
            //       found_cluster    目标簇  found_offset 目标簇偏移
            //       next_cluster     下一簇  next_offset  下一簇偏移
            //       temp_buffer      缓冲区  diritem      目录项
xfat_err_t get_next_diritem(xfat_t* xfat, u8_t type, u32_t start_cluster, u32_t start_offset,
    u32_t* found_cluster, u32_t* found_offset, u32_t* next_cluster, u32_t* next_offset,
    u8_t* temp_buffer, diritem_t** diritem) 
    {
    xfat_err_t err;
    diritem_t* r_diritem;

     // 判断当前簇是否是有效的簇
    while (is_cluster_valid(start_cluster)) 
    {
        u32_t sector_offset;

        // 预先取下一位置,方便后续处理
        // 参数 开始簇,开始簇的偏移,下一个簇,下一簇偏移
        // 作用是判断当前位置+一个目录项是否会超越一个簇
        err = move_cluster_pos(xfat, start_cluster, start_offset, sizeof(diritem_t), next_cluster, next_offset);
        if (err < 0) 
        {
            return err;
        }
        // 获取当前位置的扇区偏移
        sector_offset = to_sector_offset(xfat_get_disk(xfat), start_offset);
        // 如果为0就是扇区对齐读取
        if (sector_offset == 0) 
        {
            // 将簇号和偏移转换为扇区号
            u32_t curr_sector = to_phy_sector(xfat, start_cluster, start_offset);
            // 读取一个扇区
            err = xdisk_read_sector(xfat_get_disk(xfat), temp_buffer, curr_sector, 1);
            if (err < 0) return err;
        }
        // 读取扇区偏移处的目录项,如果偏移值为0,就是一个一个目录项读取
        r_diritem = (diritem_t*)(temp_buffer + sector_offset);
        // 判断目录项名字的第零个位置
        switch (r_diritem->DIR_Name[0]) 
        {
        case DIRITEM_NAME_END:  // 目录项的结尾
            if (type & DIRITEM_GET_END) 
            {
                *diritem = r_diritem;
                *found_cluster = start_cluster;
                *found_offset = start_offset;
                return FS_ERR_OK;
            }
            break;
        case DIRITEM_NAME_FREE: // 空闲目录项
            if (type & DIRITEM_GET_FREE) 
            {
                *diritem = r_diritem;
                *found_cluster = start_cluster;
                *found_offset = start_offset;
                return FS_ERR_OK;
            }
            break;
        default: // 目录项已经被使用的如果我们要修改文件的名字,那么目标文件的目录项肯定是被标记使用的了
            if (type & DIRITEM_GET_USED) 
            {
                *diritem = r_diritem; 
                *found_cluster = start_cluster;
                *found_offset = start_offset;
                return FS_ERR_OK;
            }
            break;
        }
       
        start_cluster = *next_cluster;
        start_offset = *next_offset;
    }

    *diritem = (diritem_t*)0;
    return FS_ERR_EOF;
}
/**
 * 移动簇的位置
 * @param xfat
 * @param curr_cluster 当前簇号
 * @param curr_offset 当前簇偏移
 * @param move_bytes 移动的字节量(当前只支持本簇及相邻簇内的移动) ,这里是move_bytes 目录项大小
 * @param next_cluster 移动后的簇号
 * @param next_offset 移动后的簇偏移
 * @return
 * 
 */
// 所以这个函数作用是判断当前位置前移一个目录项是否是超越一个簇,如果超越当前簇就切换簇
xfat_err_t move_cluster_pos(xfat_t* xfat, u32_t curr_cluster, u32_t curr_offset, u32_t move_bytes,
    u32_t* next_cluster, u32_t* next_offset) 
{
    if ((curr_offset + move_bytes) >= xfat->cluster_byte_size) 
    {
        xfat_err_t err = get_next_cluster(xfat, curr_cluster, next_cluster);
        if (err < 0) 
        {
            return err;
        }

        *next_offset = 0; //切换簇后那么下一簇的偏移就是从0开始
    }
    else  // 如果没有超越一个簇的位置
    {
        *next_cluster = curr_cluster;
        *next_offset = curr_offset + move_bytes; // 这里实现了当前簇的目录项的移动
    }

    return FS_ERR_OK;
}

/**
 * 将簇号和簇偏移转换为扇区号
 * @param xfat
 * @param cluster
 * @param cluster_offset
 * @return
 */
u32_t to_phy_sector(xfat_t* xfat, u32_t cluster, u32_t cluster_offset) 
{
    xdisk_t* disk = xfat_get_disk(xfat);
    return cluster_fist_sector((xfat), (cluster)) + to_sector((disk), (cluster_offset));
}
/**
 * 检查sfn字符串中是否是大写。如果中间有任意小写,都认为是小写
 * @param name
 * @return
 */
static u8_t get_sfn_case_cfg(const char * sfn_name) 
{
    u8_t case_cfg = 0;

    int name_len;
    const char * src_name = sfn_name;
    const char * ext_dot;
    const char * p;
    int ext_existed;

    // 跳过开头的分隔符
    while (is_path_sep(*src_name)) {
        src_name++;
    }

    // 找到第一个斜杠之前的字符串,将ext_dot定位到那里,且记录有效长度
    ext_dot = src_name;
    p = src_name;
    name_len = 0;
    while ((*p != '\0') && !is_path_sep(*p)) {
        if (*p == '.') {
            ext_dot = p;
        }
        p++;
        name_len++;
    }

    // 如果文件名以.结尾,意思就是没有扩展名?
    // todo: 长文件名处理?
    ext_existed = (ext_dot > src_name) && (ext_dot < (src_name + name_len - 1));
    for (p = src_name; p < src_name + name_len; p++) {
        if (ext_existed) {
            if (p < ext_dot) { // 文件名主体部分大小写判断
                case_cfg |= islower(*p) ? DIRITEM_NTRES_BODY_LOWER : 0;
            } else if (p > ext_dot) {
                case_cfg |= islower(*p) ? DIRITEM_NTRES_EXT_LOWER : 0;
            }
        } else {
            case_cfg |= islower(*p) ? DIRITEM_NTRES_BODY_LOWER : 0;
        }
    }

    return case_cfg;
}
/**
 * 检查sfn字符串中是否是大写。如果中间有任意小写,都认为是小写
 * @param name
 * @return
 */
static u8_t get_sfn_case_cfg(const char * sfn_name) 
{
    u8_t case_cfg = 0;

    int name_len;
    const char * src_name = sfn_name;
    const char * ext_dot;
    const char * p;
    int ext_existed;

    // 跳过开头的分隔符
    while (is_path_sep(*src_name)) {
        src_name++;
    }

    // 找到第一个斜杠之前的字符串,将ext_dot定位到那里,且记录有效长度
    ext_dot = src_name;
    p = src_name;
    name_len = 0;
    while ((*p != '\0') && !is_path_sep(*p)) {
        if (*p == '.') {
            ext_dot = p;
        }
        p++;
        name_len++;
    }

    // 如果文件名以.结尾,意思就是没有扩展名?
    // todo: 长文件名处理?
    ext_existed = (ext_dot > src_name) && (ext_dot < (src_name + name_len - 1));
    for (p = src_name; p < src_name + name_len; p++) {
        if (ext_existed) {
            if (p < ext_dot) { // 文件名主体部分大小写判断
                case_cfg |= islower(*p) ? DIRITEM_NTRES_BODY_LOWER : 0;
            } else if (p > ext_dot) {
                case_cfg |= islower(*p) ? DIRITEM_NTRES_EXT_LOWER : 0;
            }
        } else {
            case_cfg |= islower(*p) ? DIRITEM_NTRES_BODY_LOWER : 0;
        }
    }

    return case_cfg;
}

修改文件时间

同时目录项中也记载着文件的时间,在Windows中进行文件操作的时候,在文件属性中会记载着文件的创建时间、文件的修改时间、文件的最后一次修改时间。所以,进行文件的操作中也要考虑文件时间的修改。由于文件的时间也是存储在目录项中的。所以,对文件时间的修改的API接口几乎与文件名字的修改一样。

主函数

int main (void) 
{
    xfat_err_t err;
    int i;

    for (i = 0; i < sizeof(write_buffer) / sizeof(u32_t); i++) {
        write_buffer[i] = i;
    };

    err = xdisk_open(&disk, "vidsk", &vdisk_driver, (void *)disk_path);
    if (err) {
        printf("open disk failed!\n");
        return -1;
    }

    err = disk_part_test();
    if (err) return err;

    err = xdisk_get_part(&disk, &disk_part, 1);
    if (err < 0) {
        printf("read partition info failed!\n");
        return -1;
    }

    err = xfat_open(&xfat, &disk_part);
    if (err < 0) {
        return err;
    };
    err = fs_modify_file_test();
    if (err) return err;

    err = xdisk_close(&disk);
    if (err) {
        printf("disk close failed!\n");
        return -1;
    }

    printf("Test End!\n");
    return 0;
}


测试函数

xfat_err_t fs_modify_file_test(void) 
{
    xfat_err_t err;
    xfile_t file;
    const char * dir_path = "/modify/a0/a1/a2/";
    const char file_name1[] = "ABC.efg";
    const char file_name2[] = "efg.ABC";
    const char * new_name;
    char curr_path[64];
    xfile_time_t timeinfo;

    printf("modify file attr test...\n");

    printf("\n Before rename:\n");

    // 显示原目录下的文件
    err = xfile_open(&xfat, &file, dir_path);
    if (err < 0) 
    {
        printf("Open dir failed!\n");
        return err;
    }

    err = list_sub_files(&file, 0);
    if (err < 0) 
    {
        return err;
    }
    xfile_close(&file);

    // 尝试打开其中一个路径,判断如何命名
    sprintf(curr_path, "%s%s", dir_path, file_name1);
    err = xfile_open(&xfat, &file, curr_path);
    if (err < 0) 
    {
        // 打开文件1失败,则当前文件2存在,新名称为文件1名称
        sprintf(curr_path, "%s%s", dir_path, file_name2);
        new_name = file_name1;
    }
     else
      {
        sprintf(curr_path, "%s%s", dir_path, file_name1);
        new_name = file_name2;
    }

    // 文件重命名
    err = xfile_rename(&xfat, curr_path, new_name);
    if (err < 0) 
    {
        printf("rename failed: %s -- to -- %s\n", curr_path, new_name);
        return err;
    }
    xfile_close(&file);

    printf("\n After rename:\n");

    sprintf(curr_path, "%s%s", dir_path, new_name);

    // 修改文件时间
    timeinfo.year = 2030;
    timeinfo.month = 10;
    timeinfo.day = 12;
    timeinfo.hour = 13;
    timeinfo.minute = 32;
    timeinfo.second = 12;
    err = xfile_set_atime(&xfat, curr_path, &timeinfo);
    if (err < 0) 
    {
        printf("set acc time failed!\n");
        return err;
    }

    timeinfo.year = 2031;
    timeinfo.month = 11;
    timeinfo.day = 13;
    timeinfo.hour = 14;
    timeinfo.minute = 33;
    timeinfo.second = 13;
    err = xfile_set_mtime(&xfat, curr_path, &timeinfo);
    if (err < 0) 
    {
        printf("set modify time failed!\n");
        return err;
    }

    timeinfo.year = 2032;
    timeinfo.month = 12;
    timeinfo.day = 14;
    timeinfo.hour = 15;
    timeinfo.minute = 35;
    timeinfo.second = 14;
    err = xfile_set_ctime(&xfat, curr_path, &timeinfo);
    if (err < 0) 
    {
        printf("set create time failed!\n");
        return err;
    }

    // 重命名后,列表显示所有文件,显示命名状态
    err = xfile_open(&xfat, &file, dir_path);
    if (err < 0) 
    {
        printf("Open dir failed!\n");
        return err;
    }

    err = list_sub_files(&file, 0);
    if (err < 0) 
    {
        return err;
    }
    xfile_close(&file);

    return FS_ERR_OK;
}

相关API

无论是文件的访问时间、修改时间、最后一次访问时间都是调用set_file_time接口的

/**
 * 设置文件的访问时间
 * @param xfat xfat结构
 * @param path 文件的完整路径
 * @param time 文件的新访问时间
 * @return
 */
xfat_err_t xfile_set_atime (xfat_t * xfat, const char * path, xfile_time_t * time) {
    xfat_err_t err = set_file_time(xfat, path, XFAT_TIME_ATIME, time);
    return err;
}

/**
 * 设置文件的修改时间
 * @param xfat xfat结构
 * @param path 文件的完整路径
 * @param time 新的文件修改时间
 * @return
 */
xfat_err_t xfile_set_mtime (xfat_t * xfat, const char * path, xfile_time_t * time) {
    xfat_err_t err = set_file_time(xfat, path, XFAT_TIME_MTIME, time);
    return err;
}

/**
 * 设置文件的创建时间
 * @param xfat fsfa结构
 * @param path 文件的完整路径
 * @param time 新的文件创建时间
 * @return
 */
xfat_err_t xfile_set_ctime (xfat_t * xfat, const char * path, xfile_time_t * time) {
    xfat_err_t err = set_file_time(xfat, path, XFAT_TIME_CTIME, time);
    return err;}

set_file_time

前面寻找目录项的过程和修改文件名称的过程是一样的,只不过后面判断类型是修改时间的类型。

/**
 * 设置diritem中相应的时间,用作文件时间修改的回调函数
 * @param xfat xfat结构
 * @param dir_item 目录结构项
 * @param arg1 修改的时间类型
 * @param arg2 新的时间
 * @return
 */
static xfat_err_t set_file_time (xfat_t *xfat, const char * path, stime_type_t time_type, xfile_time_t * time) 
{
    diritem_t* diritem = (diritem_t*)0;
    u32_t curr_cluster, curr_offset;
    u32_t next_cluster, next_offset;
    u32_t found_cluster, found_offset;
    const char * curr_path;

    curr_cluster = xfat->root_cluster;
    curr_offset = 0;
    for (curr_path = path; curr_path != '\0'; curr_path = get_child_path(curr_path)) 
    {
        do {
            xfat_err_t err = get_next_diritem(xfat, DIRITEM_GET_USED, curr_cluster, curr_offset,
                &found_cluster, &found_offset, &next_cluster, &next_offset, temp_buffer, &diritem);
            if (err < 0) {
                return err;
            }

            if (diritem == (diritem_t*)0) {    // 已经搜索到目录结束
                return FS_ERR_NONE;
            }

            if (is_filename_match((const char*)diritem->DIR_Name, curr_path)) {
                // 找到,比较下一级子目录
                if (get_child_path(curr_path)) {
                    curr_cluster = get_diritem_cluster(diritem);
                    curr_offset = 0;
                }
                break;
            }

            curr_cluster = next_cluster;
            curr_offset = next_offset;
        } while (1);
    }

    if (diritem && !curr_path) 
    {
        // 这种方式只能用于SFN文件项重命名
        u32_t dir_sector = to_phy_sector(xfat, curr_cluster, curr_offset);

        // 根据文件名的实际情况,重新配置大小写
        switch (time_type) 
        {
            case XFAT_TIME_CTIME:
                diritem->DIR_CrtDate.year_from_1980 = (u16_t) (time->year - 1980);
                diritem->DIR_CrtDate.month = time->month;
                diritem->DIR_CrtDate.day = time->day;
                diritem->DIR_CrtTime.hour = time->hour;
                diritem->DIR_CrtTime.minute = time->minute;
                diritem->DIR_CrtTime.second_2 = (u16_t) (time->second / 2);
                diritem->DIR_CrtTimeTeenth = (u8_t) (time->second % 2 * 1000 / 100);
                break;
            case XFAT_TIME_ATIME:
                diritem->DIR_LastAccDate.year_from_1980 = (u16_t) (time->year - 1980);
                diritem->DIR_LastAccDate.month = time->month;
                diritem->DIR_LastAccDate.day = time->day;
                break;
            case XFAT_TIME_MTIME:
                diritem->DIR_WrtDate.year_from_1980 = (u16_t) (time->year - 1980);
                diritem->DIR_WrtDate.month = time->month;
                diritem->DIR_WrtDate.day = time->day;
                diritem->DIR_WrtTime.hour = time->hour;
                diritem->DIR_WrtTime.minute = time->minute;
                diritem->DIR_WrtTime.second_2 = (u16_t) (time->second / 2);
                break;
        }

        return xdisk_write_sector(xfat_get_disk(xfat), temp_buffer, dir_sector, 1);
    }

    return FS_ERR_OK;

}

向文件写数据

在FAT32(十)中我们实现了文件的读取,那里写道:如果文件是非扇区对其的那么会将数据先读取到临时缓冲区中,然后将临时缓冲区中的数据读区道用户缓冲区。如果是扇区对其读取的那么就直接将当前数据直接读取到用户缓冲区中。

以上面描述的临簇写为例

首先需要写入的其实地址将可以整扇区的内容从磁盘中读取到临时缓冲区中(这里就是边界对齐处理)这里读取了三个扇区

接着将用户缓冲区中需要写的数据从用户缓存写到临时缓存区中去

然后,将临时缓冲区中的数据写回到磁盘中。

当然上面的操作过程中需要记录当前的读取的标志[pos]

不满一簇的数据从磁盘中的扇区中取出,用户缓冲区中的数据写入到临时缓冲区中,最后写回磁盘中。这个操作过程中需要特别注意pos这个移动的位置。

主函数

int main (void) 
{
    xfat_err_t err;
    int i;

    for (i = 0; i < sizeof(write_buffer) / sizeof(u32_t); i++) 
    {
        write_buffer[i] = i;
    }
    err = xdisk_open(&disk, "vidsk", &vdisk_driver, (void *)disk_path);
    if (err) 
    {
        printf("open disk failed!\n");
        return -1;
    }

    err = disk_part_test();
    if (err) return err;

    err = xdisk_get_part(&disk, &disk_part, 1);
    if (err < 0) 
    {
        printf("read partition info failed!\n");
        return -1;
    }

    err = xfat_open(&xfat, &disk_part);
    if (err < 0) 
    {
        return err;
    }
    err = fs_read_test();
    if (err < 0)
     {
        printf("read tesst failed");
        return -1;
    }
    err = fs_write_test(); // 写测试
    if (err) return err;

    err = xdisk_close(&disk);
    if (err) 
    {
        printf("disk close failed!\n");
        return -1;
    }

    printf("Test End!\n");
    return 0;
}

写测试

int fs_write_test (void) 
{
    const char * dir_path = "/write/";
    char file_path[64];
    xfat_err_t err;

    printf("Write file test!\n");

    sprintf(file_path, "%s%s", dir_path, "1MB.bin");
    err = file_write_test(file_path, 32, 64, 5);     // 不到一个扇区,且非扇区边界对齐的写
    if (err < 0) 
    {
        printf("write file failed!\n");
        return err;
    }

    err = file_write_test(file_path, disk.sector_size, 12, 5);     // 扇区边界写,且非扇区边界对齐的写
    if (err < 0) 
    {
        printf("write file failed!\n");
        return err;
    }

    err = file_write_test(file_path, disk.sector_size + 32, 12, 5);     // 超过1个扇区,且非扇区边界对齐的写
    if (err < 0) 
    {
        printf("write file failed!\n");
        return err;
    }

    err = file_write_test(file_path, xfat.cluster_byte_size, 12, 5);     // 簇边界写,且非扇区边界对齐的写
    if (err < 0) 
    {
        printf("write file failed!\n");
        return err;
    }

    err = file_write_test(file_path, xfat.cluster_byte_size + 32, 12, 5);     // 超过1个簇,且非扇区边界对齐的写
    if (err < 0) 
    {
        printf("write file failed!\n");
        return err;
    }

    err = file_write_test(file_path, 3 * xfat.cluster_byte_size + 32, 12, 5);     // 超过多个簇,且非扇区边界对齐的写
    if (err < 0) 
    {
        printf("write file failed!\n");
        return err;
    }

    printf("Write file test end!\n");
    return 0;
}

非扇区写入,且不超过一个扇区

/**
 * 往指定文件中写入数据
 * @param buffer 数据的缓冲
 * @param elem_size 写入的元素字节大小
 * @param count 写入多少个elem_size
 * @param file 待写入的文件
 * @return
 */
xfile_size_t xfile_write(void * buffer, xfile_size_t elem_size, xfile_size_t count, xfile_t * file) 
{
    xdisk_t * disk = file_get_disk(file);
    u32_t r_count_write = 0;                          // 当前写入了多少个字节
    xfile_size_t bytes_to_write = count * elem_size;  // 总共要写入的字节大小
    xfat_err_t err;
    u8_t * write_buffer = (u8_t *)buffer;             // write_buff 用户写缓冲区

     // 只允许直接写普通文件
    if (file->type != FAT_FILE) 
    {
        file->err = FS_ERR_FSTYPE;
        return 0;
    }

    // 只读性检查
    if (file->attr & XFILE_ATTR_READONLY) 
    {
        file->err = FS_ERR_READONLY;
        return 0;
    }

    // 字节为0,无需写,直接退出
    if (bytes_to_write == 0) 
    {
        file->err = FS_ERR_OK;
        return 0;
    }

    while (bytes_to_write > 0) 
    {
        u32_t curr_write_bytes = 0;
        u32_t sector_count = 0;

		u32_t cluster_sector = to_sector(disk, to_cluster_offset(file->xfat, file->pos));  // 簇中的扇区偏移
		u32_t sector_offset = to_sector_offset(disk, file->pos);  // 扇区偏移位置
		u32_t start_sector = cluster_fist_sector(file->xfat, file->curr_cluster) + cluster_sector; // 当前簇的开始扇区

        // 起始非扇区边界对齐, 只写取当前扇区
        // 或者起始为0,但写量不超过当前扇区,也只写当前扇区
        // 无论哪种情况,都需要暂存到缓冲区中,然后拷贝到回写到扇区中
        if ((sector_offset != 0) || (!sector_offset && (bytes_to_write < disk->sector_size)))  
        {
            sector_count = 1;
            curr_write_bytes = bytes_to_write; // 当前要写入数据字节大小

            // 起始偏移非0,如果跨扇区,只写当前扇区
            if (sector_offset != 0) 
            {
                if (sector_offset + bytes_to_write > disk->sector_size)  //数据写入后超过当前扇区
                {
                    curr_write_bytes = disk->sector_size - sector_offset; // 那么需要重新计算当前要写入的文件大小
                }
            }

            // 写整扇区,写入部分到缓冲,最后再回写
            // todo: 连续多次小批量读时,可能会重新加载同一扇区,这里的tem_buffer 是临时缓冲区
            // 这里是将start_sector 整片扇区都读取到临时缓冲区
            err = xdisk_read_sector(disk, temp_buffer, start_sector, 1);
            if (err < 0) 
            {
                file->err = err;
                return 0;
            }

            // 这里是用户缓冲区【write_bufer】中数据拷贝到临时缓冲区中中【从地址temp_buffer + sector_offset开始拷贝】 
            memcpy(temp_buffer + sector_offset, write_buffer, curr_write_bytes);
            err = xdisk_write_sector(disk, temp_buffer, start_sector, 1);
            if (err < 0) 
            {
                file->err = err;
                return 0;
            }

            write_buffer += curr_write_bytes; // 当前写了curr_write_bytes 字节那么用户数据缓冲区的指针就需要从开始地方移动当前写了curr_write_bytes 大小
            bytes_to_write -= curr_write_bytes; // 剩下要读取的地址等于= 刚开始的数据大小 - 已经写入的数据大小
        } 
        else //这里的判断满足就是跨扇区,或者是跨簇的
        {
            // 起始为0,且写量超过1个扇区,连续写多扇区
            sector_count = to_sector(disk, bytes_to_write);

            // 如果超过一簇,则只写当前簇
            // todo: 这里可以再优化一下,如果簇连写的话,实际是可以连写多簇的
            // 这里是跨簇的,如果满足
            if ((cluster_sector + sector_count) > file->xfat->sec_per_cluster) 
            {
                sector_count = file->xfat->sec_per_cluster - cluster_sector; // 从cluster_sector到当前的簇的结尾的总扇区大小被用户缓冲区直接拷贝
            }

            err = xdisk_write_sector(disk, write_buffer, start_sector, sector_count); // 写入
            if (err != FS_ERR_OK) 
            {
                file->err = err;
                return 0;
            }

            curr_write_bytes = sector_count * disk->sector_size; // 已经写入的字节大小
            write_buffer += curr_write_bytes; // 用户环缓冲区地址移动
            bytes_to_write -= curr_write_bytes; //调整还需写入的字节大小
        }

        r_count_write += curr_write_bytes; // 统计当前已经读取的数据大小

		err = move_file_pos(file, curr_write_bytes); // 调整当前定位
		if (err) return 0;
    }

    file->err = file->pos == file->size;
    return r_count_write / elem_size;
}

重新确定写入位置

static xfat_err_t move_file_pos(xfile_t* file, u32_t move_bytes) {
	u32_t to_move = move_bytes;
	u32_t cluster_offset;

	// 不要超过文件的大小
	if (file->pos + move_bytes >= file->size)
     {
		to_move = file->size - file->pos;
	}

	// 簇间移动调整,需要调整簇
	cluster_offset = to_cluster_offset(file->xfat, file->pos);
	if (cluster_offset + to_move >= file->xfat->cluster_byte_size)
     {
		xfat_err_t err = get_next_cluster(file->xfat, file->curr_cluster, &file->curr_cluster);
		if (err != FS_ERR_OK) {
			file->err = err;
			return err;
		}
	}

	file->pos += to_move;
	return FS_ERR_OK;
}

文件扩容

在前面实现了往文件中写入数据,上面的API都基于一个基本原则是写入的数据少于文件的大小[文件的大小存储在文件对应的目录项中]。如果写入的数据比文件的大小还大的时候,就应该对文件进行扩容了。

在写文件的时候如果文件的数据不满一簇就会分配整个簇。在这个基础上如上图,如果文件扩容的容量小于一簇,那么文件直接写入即可

如果文件扩容的大小比原来的文件内容大于一个或多个簇的时候,那就需要从磁盘中寻找空闲的簇然后分配给当前要扩容的文件

新的簇从哪里获取?

在前面的章中知道,每一簇都有一个簇号其中簇号0代表着当前的簇号是空闲的,可以分配的。而簇号是存在于FAT表中的,所以只要遍历FAT中的簇号为0的簇就可以把它插入到要扩容的文件当中。

如何实现文件的扩容?(扩容的实现步骤)

(1)判断是否需要新的簇来实现扩容。如果需要那么从FAT表中寻找空闲的簇插入到需要扩容的文件簇链中

(2)其次,是将找到的空闲簇挂在到目标文件的簇链中。最后,因为文件的大小改变了。所以,文件的目录项中的文件大小需要更新。

主函数

int main (void) 
{
    xfat_err_t err;
    int i;

    for (i = 0; i < sizeof(write_buffer) / sizeof(u32_t); i++) 
    {
        write_buffer[i] = i;
    }
    err = xdisk_open(&disk, "vidsk", &vdisk_driver, (void *)disk_path);
    if (err) 
    {
        printf("open disk failed!\n");
        return -1;
    }

    err = disk_part_test();
    if (err) return err;

    err = xdisk_get_part(&disk, &disk_part, 1);
    if (err < 0) 
    {
        printf("read partition info failed!\n");
        return -1;
    }

    err = xfat_open(&xfat, &disk_part);
    if (err < 0) 
    {
        return err;
    }
    err = fs_write_test();
    if (err) return err;

    err = xdisk_close(&disk);
    if (err) 
    {
        printf("disk close failed!\n");
        return -1;
    }

    printf("Test End!\n");
    return 0;
}

测试函数

int fs_write_test (void) {
    const char * dir_path = "/write/";
    char file_path[64];
    xfat_err_t err;

    printf("Write file test!\n");

    sprintf(file_path, "%s%s", dir_path, "1MB.bin");
    err = file_write_test(file_path, 32, 64, 5);     // 不到一个扇区,且非扇区边界对齐的写
    if (err < 0) {
        printf("write file failed!\n");
        return err;
    }

    err = file_write_test(file_path, disk.sector_size, 12, 5);     // 扇区边界写,且非扇区边界对齐的写
    if (err < 0) {
        printf("write file failed!\n");
        return err;
    }

    err = file_write_test(file_path, disk.sector_size + 32, 12, 5);     // 超过1个扇区,且非扇区边界对齐的写
    if (err < 0) {
        printf("write file failed!\n");
        return err;
    }

    err = file_write_test(file_path, xfat.cluster_byte_size, 12, 5);     // 簇边界写,且非扇区边界对齐的写
    if (err < 0) {
        printf("write file failed!\n");
        return err;
    }

    err = file_write_test(file_path, xfat.cluster_byte_size + 32, 12, 5);     // 超过1个簇,且非扇区边界对齐的写
    if (err < 0) {
        printf("write file failed!\n");
        return err;
    }

    err = file_write_test(file_path, 3 * xfat.cluster_byte_size + 32, 12, 5);     // 超过多个簇,且非扇区边界对齐的写
    if (err < 0) {
        printf("write file failed!\n");
        return err;
    }

    // 扩容写测试
    do {
        xfile_t file;
        xfile_size_t size = sizeof(write_buffer);
        xfile_size_t file_size;
        u32_t i;

        printf("\n expand write file!\n");

        sprintf(file_path, "%s%s", dir_path, "1KB.bin");

        // 检查文件写入后大小
        err = xfile_open(&xfat, &file, file_path);
        if (err < 0) 
        {
            printf("Open failed:%s\n", file_path);
            return err;
        }
        // write_buff 用户缓冲区 里面保存着将要写入文件的数据
        // 要写入文件的数据大小字节
        // file :数据写入的文件
        err = xfile_write(write_buffer, size, 1, &file);
        if (err < 0) 
        {
            printf("Write failed:%s\n", file_path);
            return err;
        }

        xfile_size(&file, &file_size);
        if (file_size != size) 
        {
            printf("Write failed:%s\n", file_path);
            return err;
        }

		err = xfile_seek(&file, 0, XFAT_SEEK_SET);
		if (err < 0) 
        {
			return err;
		}

        memset(read_buffer, 0, sizeof(read_buffer));
        err = xfile_read(read_buffer, size, 1, &file);
        if (err < 0) 
        {
            printf("read failed\n");
            return err;
        }

        // 检查文件内容
        for (i = 0; i < size / sizeof(read_buffer[0]); i++) 
        {
            if (read_buffer[i] != write_buffer[i]) 
            {
                printf("content different!\n");
                return -1;
            }
        }

        xfile_close(&file);

    } while (0);

    printf("Write file test end!\n");
    return 0;
}

在原有的数据写入中添加扩容的代码

/**
 * 往指定文件中写入数据
 * @param buffer 数据的缓冲
 * @param elem_size 写入的元素字节大小
 * @param count 写入多少个elem_size
 * @param file 待写入的文件
 * @return
 */
xfile_size_t xfile_write(void * buffer, xfile_size_t elem_size, xfile_size_t count, xfile_t * file) {
    xdisk_t * disk = file_get_disk(file);
    u32_t r_count_write = 0;
    xfile_size_t bytes_to_write = count * elem_size;
    xfat_err_t err;
    u8_t * write_buffer = (u8_t *)buffer;

     // 只允许直接写普通文件
    if (file->type != FAT_FILE) {
        file->err = FS_ERR_FSTYPE;
        return 0;
    }

    // 只读性检查
    if (file->attr & XFILE_ATTR_READONLY) {
        file->err = FS_ERR_READONLY;
        return 0;
    }

    // 字节为0,无需写,直接退出
    if (bytes_to_write == 0) {
        file->err = FS_ERR_OK;
        return 0;
    }

    // 当写入量将超过文件大小时,预先分配所有簇,然后再写
    // 后面再写入时,就不必考虑写时文件大小不够的问题了
    if (file->size < file->pos + bytes_to_write) {

        // 参数: file :要扩容的文件
        //        file->pos + bytes_to_write扩容的大小
        err = expand_file(file, file->pos + bytes_to_write); // 文件扩容
        if (err < 0) {
            file->err = err;
            return 0;
        }
    }

	while ((bytes_to_write > 0) && is_cluster_valid(file->curr_cluster)) {
		u32_t curr_write_bytes = 0;
        u32_t sector_count = 0;

		u32_t cluster_sector = to_sector(disk, to_cluster_offset(file->xfat, file->pos));  // 簇中的扇区偏移
		u32_t sector_offset = to_sector_offset(disk, file->pos);  // 扇区偏移位置
		u32_t start_sector = cluster_fist_sector(file->xfat, file->curr_cluster) + cluster_sector;

        // 起始非扇区边界对齐, 只写取当前扇区
        // 或者起始为0,但写量不超过当前扇区,也只写当前扇区
        // 无论哪种情况,都需要暂存到缓冲区中,然后拷贝到回写到扇区中
        if ((sector_offset != 0) || (!sector_offset && (bytes_to_write < disk->sector_size))) {
            sector_count = 1;
            curr_write_bytes = bytes_to_write;

            // 起始偏移非0,如果跨扇区,只写当前扇区
            if (sector_offset != 0) {
                if (sector_offset + bytes_to_write > disk->sector_size) {
                    curr_write_bytes = disk->sector_size - sector_offset;
                }
            }

            // 写整扇区,写入部分到缓冲,最后再回写
            // todo: 连续多次小批量读时,可能会重新加载同一扇区
            err = xdisk_read_sector(disk, temp_buffer, start_sector, 1);
            if (err < 0) {
                file->err = err;
                return 0;
            }

            memcpy(temp_buffer + sector_offset, write_buffer, curr_write_bytes);
            err = xdisk_write_sector(disk, temp_buffer, start_sector, 1);
            if (err < 0) {
                file->err = err;
                return 0;
            }

            write_buffer += curr_write_bytes;
            bytes_to_write -= curr_write_bytes;
        } else {
            // 起始为0,且写量超过1个扇区,连续写多扇区
            sector_count = to_sector(disk, bytes_to_write);

            // 如果超过一簇,则只写当前簇
            // todo: 这里可以再优化一下,如果簇连写的话,实际是可以连写多簇的
            if ((cluster_sector + sector_count) > file->xfat->sec_per_cluster) {
                sector_count = file->xfat->sec_per_cluster - cluster_sector;
            }

            err = xdisk_write_sector(disk, write_buffer, start_sector, sector_count);
            if (err != FS_ERR_OK) {
                file->err = err;
                return 0;
            }

            curr_write_bytes = sector_count * disk->sector_size;
            write_buffer += curr_write_bytes;
            bytes_to_write -= curr_write_bytes;
        }

        r_count_write += curr_write_bytes;

		err = move_file_pos(file, curr_write_bytes);
		if (err) return 0;
    }

    file->err = file->pos == file->size;
    return r_count_write / elem_size;
}

文件扩容

/**
 * 扩充文件大小,新增的文件数据部分,其内容由mode来控制
 * @param file 待扩充的文件
 * @param size 新的文件大小
 * @param mode 扩充模式
 * @return
 */
static xfat_err_t expand_file(xfile_t * file, xfile_size_t size) {
    xfat_err_t err;
    xfat_t * xfat = file->xfat;
	u32_t curr_cluster_cnt = to_cluseter_count(xfat, file->size); // 当前文件有多少个簇 
    u32_t expect_cluster_cnt = to_cluseter_count(xfat, size); // 要完全存储用户的所有数据,需要的簇数量

    // 当扩充容量需要跨簇时,在簇链之后增加新项
    // 进一步判断是否需要扩容
    if (curr_cluster_cnt < expect_cluster_cnt) 
    {
        u32_t cluster_cnt = expect_cluster_cnt - curr_cluster_cnt; //需要扩容的簇数量
        u32_t start_free_cluster = 0;           //分配的第一个可用簇号
		u32_t curr_culster = file->curr_cluster; // 当前簇号

        // 先定位至文件的最后一簇, 仅需要定位文件大小不为0的簇
        if (file->size > 0) {
			u32_t next_cluster = file->curr_cluster;

			do 
            {
				curr_culster = next_cluster;

				err = get_next_cluster(xfat, curr_culster, &next_cluster); // 寻找文件当前簇的下一簇,do while结构就实现了从文件开始簇寻找到了文件的最后的簇位置
				if (err) 
                {
					file->err = err;
					return err;
				}
			} while (is_cluster_valid(next_cluster));
        }

        // 然后再从最后一簇分配空间
        // 文件:curr_cluster :当前文件末尾的簇
        //       cluster_cnt  : 需要扩容簇的数量
        //       strt_free_cluster :分配的第一个可用簇号
        err = allocate_free_cluster(file->xfat, curr_culster, cluster_cnt, &start_free_cluster, 0, 0, 0);
        if (err) 
        {
            file->err = err;
            return err;
        }

		if (!is_cluster_valid(file->start_cluster)) 
        {
			file->start_cluster = start_free_cluster;
			file->curr_cluster = start_free_cluster;
		} 
        else if (!is_cluster_valid(file->curr_cluster) || is_fpos_cluster_end(file)) 
        {
			file->curr_cluster = start_free_cluster;
		}
    }

    // 最后,再更新文件大小,是否放在关闭文件时进行?
    err = update_file_size(file, size);
    return err;
}

分配空闲簇

/**
 * 分配空闲簇
 * @param xfat xfat结构
 * @param curr_cluster 当前簇号
 * @param count 要分配的簇号
 * @param start_cluster 分配的第一个可用簇号
 * @param r_allocated_count 有效分配的数量
 * @param erase_cluster 是否同时擦除簇对应的数据区
 * @return
 */
static xfat_err_t allocate_free_cluster(xfat_t * xfat, u32_t curr_cluster, u32_t count,
        u32_t * r_start_cluster, u32_t * r_allocated_count, u8_t en_erase, u8_t erase_data) {
    u32_t i;
    xfat_err_t err;
    xdisk_t * disk = xfat_get_disk(xfat);
    u32_t allocated_count = 0;
    u32_t start_cluster = 0;

    // todo:目前简单起见,从头开始查找, 用于FAT32,这里就是从FAT表中寻找簇号为0的簇然后插入到文件的簇链中去
    u32_t cluster_count = xfat->fat_tbl_sectors * disk->sector_size / sizeof(cluster32_t); // 当前文件大小/每一簇大小 = 当前文件簇的数量
    u32_t pre_cluster = curr_cluster; // 当前簇号
    cluster32_t * cluster32_buf = (cluster32_t *)xfat->fat_buffer; // 获得一个簇的结构

   // 根目录是簇2开始的
    for (i = 2; (i < cluster_count) && (allocated_count < count); i++) 
    {
          // 注意是fat32,4字节大小.如果这个条件满足就是轮询到了文件的末尾
        if (cluster32_buf[i].s.next == 0) 
        {    
            if (is_cluster_valid(pre_cluster)) 
            {
                cluster32_buf[pre_cluster].s.next = i; //为需要扩容的簇分配簇号,实现插入需要扩容文件的簇链中
            }

            pre_cluster = i;

            if (++allocated_count == 1) 
            {
                start_cluster = i;// 第一个可用的簇号为2
            }

            if (allocated_count >= count) 
            {
                break;
            }
        }
    }

    if (allocated_count)  // 扩容后文件最后簇
    {
        cluster32_buf[pre_cluster].s.next = CLUSTER_INVALID; // 扩容簇后结尾处理

        // 分配簇以后,要注意清空簇中原有内容,如果条件满足那么代表分配簇链后会清除原有的内容
        if (en_erase) 
        {
            // 逐个擦除所有的簇
            u32_t cluster = start_cluster;
            for (i = 0; i < allocated_count; i++) 
            {
                err = erase_cluster(xfat, cluster, erase_data);
                if (err < 0) 
                {
                    return err;
                }

                cluster = cluster32_buf[cluster].s.next;
            }
        }

        // FAT表项可能有多个,同时更新
        for (i = 0; i < xfat->fat_tbl_nr; i++) 
        {
            u32_t start_sector = xfat->fat_start_sector + xfat->fat_tbl_sectors * i;
            err = xdisk_write_sector(disk, (u8_t *)xfat->fat_buffer, start_sector, xfat->fat_tbl_sectors);
            if (err < 0) 
            {
                return err;
            }
        }
    }

    if (r_allocated_count) 
    {
        *r_allocated_count = allocated_count;
    }

    if (r_start_cluster) 
    {
        *r_start_cluster = start_cluster;
    }

    return FS_ERR_OK;
}

文件创建

前面主要是对文件的数据写入与扩容,文件都是对磁盘中原有的文件的写入。实际也会有新建文件然后将数据写入到新建的文件中。所以,需要实现一个文件创建的接口

如何实现?

文件数据由簇链组成。所以,创建文件其实就是将当前簇下寻找到空闲的目录项,将目录项中的属性进行填充。如[文件名字、扩展名、属性、保留项、创建时间、最后访问日期、写时间、数据起始簇号(不分配簇,设置为0,数据写入的时候分配)、文件字节大小(刚创建无数据,大小为0)]

注意如果当前簇没有空闲目录项,那么就重新找一个新的目录簇

实现过程

  • 当前簇下寻找空闲目录项
  • 填充找到的目录项后,对目录项中的内容进行填充
  • 如果当前没有空闲簇,就找新的目录簇,继续进行第1~2的步骤。

主函数

int main (void) 
{
    xfat_err_t err;
    int i;

    for (i = 0; i < sizeof(write_buffer) / sizeof(u32_t); i++) 
    {
        write_buffer[i] = i;
    }

    err = xdisk_open(&disk, "vidsk", &vdisk_driver, (void *)disk_path);
    if (err) 
    {
        printf("open disk failed!\n");
        return -1;
    }

    err = disk_part_test();
    if (err) return err;

    err = xdisk_get_part(&disk, &disk_part, 1);
    if (err < 0) 
    {
        printf("read partition info failed!\n");
        return -1;
    }

    err = xfat_open(&xfat, &disk_part);
    if (err < 0) 
    {
        return err;
    }
    err = fs_create_test(); // 文件创建测试
    if (err) return err;

    err = xdisk_close(&disk);
    if (err) 
    {
        printf("disk close failed!\n");
        return -1;
    }

    printf("Test End!\n");
    return 0;
}

文件创建测试函数

/**
 * 文件创建测试函数
*/
xfat_err_t fs_create_test (void) 
{
    xfat_err_t err = FS_ERR_OK;
    const char* dir_path = "";  // 这个路径可配置短一些
    char path[256];
    int i, j;

    printf("create test\n");

    // 注意,如果根目录下已经有要创建的文件
    // 或者之前在调试该代码时,已经执行了一部分导致部分文件被创建时
    // 注意在重启调试前,先清除干净根目录下的所有这些文件
    // 如果懒得清除,可以更改要创建的文件名
    for (i = 0; i < 3; i++) 
    {
        for (j = 0; j < 50; j++) 
        {
            // 创建文件
            sprintf(path, "%s/b%d.txt", dir_path, j);
            printf("no %d:create file %s\n", i, path);
            // 文件创建
            err = xfile_mkfile(&xfat, path);
            if (err < 0) 
            {
                if (err == FS_ERR_EXISTED) 
                {
                    // 只有在第一次创建时才会成功
                    printf("file exist %s, continue.\n", path);
                } 
                else 
                {
                    printf("create file failed %s\n", path);
                    return err;
                }
            }
            // 文件写入测试
            err = file_write_test(path, 1024, 1, 1);
            if (err < 0) 
            {
                printf("write file failed! %s\n", path);
                return err;
            }
            printf("create %s ok!\n", path);
        }
    }

    printf("create test ok\n");
    return err;
}

文件创建API

/**
 * 按指定路径,依次创建各级目录,最后创建指定文件
 * @param xfat xfat结构
 * @param file_path 文件的完整路径
 * @return
 */
xfat_err_t xfile_mkfile (xfat_t * xfat, const char * path) 
{
    u32_t parent_cluster;

    // 默认从根目录创建,传入的目录路径可能是/a/b/c/d.txt
    parent_cluster = xfat->root_cluster;

    // 逐级创建目录和文件
    while (!is_path_end(path))  // 判断路径是否为空
    {
        xfat_err_t err;
        u32_t file_cluster = FILE_DEFAULT_CLUSTER;//0x00
        const char * next_path = get_child_path(path); // 获得子目录路径

        // 没有后续路径,则当前创建文件
        if (is_path_end(next_path)) 
        {
            // parent_cluster:默认根目录创建
            // file_cluster : file_cluster = FILE_DEFAULT_CLUSTER; 0x00 文件簇号设置为默认值
            // path         :  文件名字
            err = create_sub_file(xfat, 0, parent_cluster, path, &file_cluster);//创建文件
            return err;
        } 
        else 
        {
            // 在此创建目录, 后续补充
        }

        path = next_path;
    }
    return FS_ERR_OK;
}

文件目录项创建

/**
 * 在指定目录下创建子文件或者目录
 * @param xfat xfat结构
 * @param is_dir 要创建的是文件还是目录
 * @param parent_dir_cluster 父目录起始数据簇号
 * @param file_cluster 预先给定的文件或目录的起始数据簇号。如果文件或目录已经存在,则返回相应的簇号
 * @param child_name 创建的目录或文件名称
 * @return
 */
static xfat_err_t create_sub_file (xfat_t * xfat, u8_t is_dir, u32_t parent_cluster,
                const char* child_name, u32_t * file_cluster) 
{
    xfat_err_t err;
    xdisk_t * disk = xfat_get_disk(xfat);
    diritem_t * target_item = (diritem_t *)0;
    u32_t curr_cluster = parent_cluster, curr_offset = 0;
    u32_t free_item_cluster = CLUSTER_INVALID, free_item_offset = 0;
    u32_t file_diritem_sector;
    u32_t found_cluster, found_offset;
    u32_t next_cluster, next_offset;

    // 遍历找到空闲项,在目录末尾添加新项
    do {

        diritem_t* diritem = (diritem_t*)0; // 目录项
        // 寻找下一目录项目
        /**
         *参数     :xfat 
                  :DIRITEM_GET_ALL :文件类型,寻找所有文件
                  :curr_cluster  当前目录项对应的簇号
                  :curr_offset   当前簇号偏移
                  :found_cluster 目标簇号
                  :found_offset  目标相对簇号偏移
                  :temp_buffer   簇存储缓冲区
                  :diritem       目录项结构体
        */
        err = get_next_diritem(xfat, DIRITEM_GET_ALL, curr_cluster, curr_offset,
                                    &found_cluster, &found_offset, &next_cluster, &next_offset, temp_buffer, &diritem);
        if (err < 0) return err;

         // 已经搜索到目录结束
        if (diritem == (diritem_t*)0) 
        {   
            break;
        }
         // 有效结束标记
        if (diritem->DIR_Name[0] == DIRITEM_NAME_END) 
        {       
            target_item = diritem;
            break;
        }
         // 寻找到空闲目录项 
        else if (diritem->DIR_Name[0] == DIRITEM_NAME_FREE) 
        {
            // 空闲项, 还要继续检查,看是否有同名项
            // 记录空闲项的位置
            if (!is_cluster_valid(free_item_cluster)) 
            {
                free_item_cluster = curr_cluster;
                free_item_offset = curr_offset;
            }
        } 
        // 如果找到与目录项中文件名字与要创建文件的名字相同
        else if (is_filename_match((const char*)diritem->DIR_Name, child_name)) 
        {
            // 仅名称相同,还要检查是否是同名的文件或目录
            int item_is_dir = diritem->DIR_Attr & DIRITEM_ATTR_DIRECTORY;
            if ((is_dir && item_is_dir) || (!is_dir && !item_is_dir)) 
            { // 同类型且同名
                *file_cluster = get_diritem_cluster(diritem);  // 返回
                return FS_ERR_EXISTED;
            } 
            else 
            {   // 不同类型,即目录-文件同名,直接报错
                return FS_ERR_NAME_USED;
            }
        }

        curr_cluster = next_cluster;
        curr_offset = next_offset;
    } while (1);

    // 未找到空闲的项,需要为父目录申请新簇,以放置新文件/目录
    if ((target_item == (diritem_t *)0) && !is_cluster_valid(free_item_cluster)) 
    {
        u32_t parent_diritem_cluster;
        u32_t cluster_count;

        xfat_err_t err = allocate_free_cluster(xfat, found_cluster, 1, &parent_diritem_cluster, &cluster_count, 1, 0);
        if (err < 0)  return err;

        if (cluster_count < 1) {
            return FS_ERR_DISK_FULL;
        }

        // 读取新建簇中的第一个扇区,获取target_item
        file_diritem_sector = cluster_fist_sector(xfat, parent_diritem_cluster);
        err = xdisk_read_sector(disk, temp_buffer, file_diritem_sector, 1);
        if (err < 0) {
            return err;
        }
        target_item = (diritem_t *)temp_buffer;     // 获取新簇项
    } 
    else 
    {    // 找到空闲或末尾
        u32_t diritem_offset;
        if (is_cluster_valid(free_item_cluster)) {
            //找到目标目录项的开始扇区 
            file_diritem_sector = cluster_fist_sector(xfat, free_item_cluster) + to_sector(disk, free_item_offset);
            //找到目录项相对当前簇的偏移
            diritem_offset = free_item_offset;
        } 
        else // 末尾
        {
            file_diritem_sector = cluster_fist_sector(xfat, found_cluster) + to_sector(disk, found_offset);
            diritem_offset = found_offset; // 这里的found_offset = 0
        }
        err = xdisk_read_sector(disk, temp_buffer, file_diritem_sector, 1);
        if (err < 0) 
        {
            return err;
        }
         // 如果找到的是文件末尾那么这里的target_item =  (diritem_t*)temp_buffer,其实就是什么都没做
         // 如果找打的是空闲目录就创建一个target_ite目录项后面往这新的目录项填充数据
        target_item = (diritem_t*)(temp_buffer + to_sector_offset(disk, diritem_offset));     // 获取新簇项
    }

    // 获取目录项之后,根据文件或目录,创建item
    err = diritem_init_default(target_item, disk, is_dir, child_name, FILE_DEFAULT_CLUSTER);
    if (err < 0) 
    {
        return err;
    }

    // 写入所在目录项,实现新的目录项的初始化
    err = xdisk_write_sector(disk, temp_buffer, file_diritem_sector, 1);
    if (err < 0) 
    {
        return err;
    }

    *file_cluster = FILE_DEFAULT_CLUSTER;
    return err;
}

为新的目录项添加簇号

/**
 * 分配空闲簇
 * @param xfat xfat结构
 * @param curr_cluster 当前簇号
 * @param count 要分配的簇号
 * @param start_cluster 分配的第一个可用簇号
 * @param r_allocated_count 有效分配的数量
 * @param erase_cluster 是否同时擦除簇对应的数据区
 * @return
 */
static xfat_err_t allocate_free_cluster(xfat_t * xfat, u32_t curr_cluster, u32_t count,
        u32_t * r_start_cluster, u32_t * r_allocated_count, u8_t en_erase, u8_t erase_data) {
    u32_t i;
    xfat_err_t err;
    xdisk_t * disk = xfat_get_disk(xfat);
    u32_t allocated_count = 0;
    u32_t start_cluster = 0;

    // todo:目前简单起见,从头开始查找, 用于FAT32
    u32_t cluster_count = xfat->fat_tbl_sectors * disk->sector_size / sizeof(cluster32_t); // 当前文件大小/每一簇大小 = 当前文件簇的数量
    u32_t pre_cluster = curr_cluster; // 当前簇号
    cluster32_t * cluster32_buf = (cluster32_t *)xfat->fat_buffer; // 获得一个簇的结构

   // 根目录是簇2开始的
    for (i = 2; (i < cluster_count) && (allocated_count < count); i++) 
    {
          // 注意是fat32,4字节大小.如果这个条件满足就是轮询到了文件的末尾
        if (cluster32_buf[i].s.next == 0) 
        {    
            if (is_cluster_valid(pre_cluster)) 
            {
                cluster32_buf[pre_cluster].s.next = i; //为需要扩容的簇分配簇号
            }

            pre_cluster = i;

            if (++allocated_count == 1) 
            {
                start_cluster = i;// 第一个可用的簇号为2
            }

            if (allocated_count >= count) 
            {
                break;
            }
        }
    }

    if (allocated_count)  // 扩容后文件最后簇
    {
        cluster32_buf[pre_cluster].s.next = CLUSTER_INVALID; // 扩容簇后结尾处理

        // 分配簇以后,要注意清空簇中原有内容,如果条件满足那么代表分配簇链后会清除原有的内容
        if (en_erase) 
        {
            // 逐个擦除所有的簇
            u32_t cluster = start_cluster;
            for (i = 0; i < allocated_count; i++) 
            {
                err = erase_cluster(xfat, cluster, erase_data);
                if (err < 0) 
                {
                    return err;
                }

                cluster = cluster32_buf[cluster].s.next;
            }
        }

        // FAT表项可能有多个,同时更新
        for (i = 0; i < xfat->fat_tbl_nr; i++) 
        {
            u32_t start_sector = xfat->fat_start_sector + xfat->fat_tbl_sectors * i;
            err = xdisk_write_sector(disk, (u8_t *)xfat->fat_buffer, start_sector, xfat->fat_tbl_sectors);
            if (err < 0) 
            {
                return err;
            }
        }
    }

    if (r_allocated_count) 
    {
        *r_allocated_count = allocated_count;
    }

    if (r_start_cluster) 
    {
        *r_start_cluster = start_cluster;
    }

    return FS_ERR_OK;
}

目录项中其他信息填充

/**
 * 缺省初始化driitem
 * @param dir_item 待初始化的diritem
 * @param is_dir 该项是否对应目录项
 * @param name 项的名称
 * @param cluster 数据簇的起始簇号
 * @return
 */
static xfat_err_t diritem_init_default(diritem_t * dir_item, xdisk_t * disk, u8_t is_dir, const char * name, u32_t cluster) {
    xfile_time_t timeinfo;

    xfat_err_t err = xdisk_curr_time(disk, &timeinfo);
    if (err < 0) {
        return err;
    }

    to_sfn((char *)dir_item->DIR_Name, name);
    set_diritem_cluster(dir_item, cluster);
    dir_item->DIR_FileSize = 0;
    dir_item->DIR_Attr = (u8_t)(is_dir ? DIRITEM_ATTR_DIRECTORY : 0);
    dir_item->DIR_NTRes = get_sfn_case_cfg(name);

    dir_item->DIR_CrtTime.hour = timeinfo.hour;
    dir_item->DIR_CrtTime.minute = timeinfo.minute;
    dir_item->DIR_CrtTime.second_2 = (u16_t)(timeinfo.second / 2);
    dir_item->DIR_CrtTimeTeenth = (u8_t)((timeinfo.second & 1) * 1000);

    dir_item->DIR_CrtDate.year_from_1980 = (u16_t)(timeinfo.year - 1980);
    dir_item->DIR_CrtDate.month = timeinfo.month;
    dir_item->DIR_CrtDate.day = timeinfo.day;

    dir_item->DIR_WrtTime = dir_item->DIR_CrtTime;
    dir_item->DIR_WrtDate = dir_item->DIR_CrtDate;
    dir_item->DIR_LastAccDate = dir_item->DIR_CrtDate;

    return FS_ERR_OK;
}

目录创建

目录本质上也是一个特殊文件,只不过这个文件中存储的内容为当前文件下的文件或子目录的目录项的信息。对于这种特殊的文件如何实现创建?

既然是特殊的文件那么实现的步骤也与文件的几乎一样

  • 遍历当前簇的所有目录项,寻找空闲目录项[如果当前没有空闲的目录就寻找下一个簇]
  • 对空闲的目录项信息进行填充[目录名、扩展名、属性、保留、创建时间、最后访问时间、写时间、数据起始簇位置、目录大小]
  • 对于目录中起始簇中存储的数据为目录项,且要创建两个特殊的目录项[.目录与…目录的目录项]
主函数
int main (void) 
{
    xfat_err_t err;
    int i;

    for (i = 0; i < sizeof(write_buffer) / sizeof(u32_t); i++) 
    {
        write_buffer[i] = i;
    }
    err = xdisk_open(&disk, "vidsk", &vdisk_driver, (void *)disk_path);
    if (err) 
    {
        printf("open disk failed!\n");
        return -1;
    }

    err = disk_part_test();
    if (err) return err;

    err = xdisk_get_part(&disk, &disk_part, 1);
    if (err < 0) 
    {
        printf("read partition info failed!\n");
        return -1;
    }

    err = xfat_open(&xfat, &disk_part);
    if (err < 0) 
    {
        return err;
    }
    //目录创建
    err = fs_create_test(); 
    if (err) return err;

    err = xdisk_close(&disk);
    if (err) 
    {
        printf("disk close failed!\n");
        return -1;
    }

    printf("Test End!\n");
    return 0;
}

目录创建测试

xfat_err_t fs_create_test (void) 
{
    xfat_err_t err = FS_ERR_OK;
    const char * dir_path = "/create/c0/c1/c2/c3/c4/c5/c6/c7/c8/c9";  // 这个路径可配置短一些
    char path[256];
    int i, j;
    xfile_t file;

    printf("create test\n");

    // 注意,如果根目录下已经有要创建的文件
    // 或者之前在调试该代码时,已经执行了一部分导致部分文件被创建时
    // 注意在重启调试前,先清除干净根目录下的所有这些文件
    // 如果懒得清除,可以更改要创建的文件名
    for (i = 0; i < 3; i++) 
    {
        // 创建目录
        printf("no %d:create dir %s\n", i, dir_path);
        err = xfile_mkdir(&xfat, dir_path);  // 目录创建
        if (err < 0) {
            if (err == FS_ERR_EXISTED) {
                // 只有在第一次创建时才会成功
                printf("dir exist %s, continue.\n", dir_path);
            } else {
                printf("create dir failed %s\n", dir_path);
                return err;
            }
        }
        
        for (j = 0; j < 50; j++) 
        {
            // 创建文件
            sprintf(path, "%s/b%d.txt", dir_path, j);
            printf("no %d:create file %s\n", i, path);

            err = xfile_mkfile(&xfat, path);
            if (err < 0) 
            {
                if (err == FS_ERR_EXISTED) 
                {
                    // 只有在第一次创建时才会成功
                    printf("file exist %s, continue.\n", path);
                } 
                else 
                {
                    printf("create file failed %s\n", path);
                    return err;
                }
            }

            // 进行一些读写测试,  写有点bug,写3遍就会丢数据
            err = file_write_test(path, 1024, 1, 1);
            if (err < 0) {
                printf("write file failed! %s\n", path);
                return err;
            }
            printf("create %s ok!\n", path);
        }
    }

    printf("begin remove file\n");
    for (j = 0; j < 50; j++) {
        sprintf(path, "%s/b%d.txt", dir_path, j);
        printf("rm file %s\n", path);

        err = xfile_rmfile(&xfat, path);
        if (err < 0) {
            printf("rm file failed %s\n", path);
            return err;
        }
    }

    err = xfile_open(&xfat, &file, dir_path);
    if (err < 0) return err;
    err = list_sub_files(&file, 0);
    if (err < 0) return err;
    err = xfile_close(&file);
    if (err < 0) return err;

    printf("create test ok\n");
    return FS_ERR_OK;
}

目录创建

/**
 * 按指定路径,依次创建各级目录,最后创建指定文件
 * @param xfat xfat结构
 * @param file_path 文件的完整路径
 * @return
 */
xfat_err_t xfile_mkfile (xfat_t * xfat, const char * path) 
{
    u32_t parent_cluster;

    // 默认从根目录创建,传入的目录路径可能是/a/b/c/d.txt
    parent_cluster = xfat->root_cluster;

    // 逐级创建目录和文件
    while (!is_path_end(path))  // 判断路径是否为空
    {
        xfat_err_t err;
        u32_t file_cluster = FILE_DEFAULT_CLUSTER;//0x00
        const char * next_path = get_child_path(path); // 获得子目录路径

        // 没有后续路径,则当前创建文件
        if (is_path_end(next_path)) 
        {
            // parent_cluster:默认根目录创建
            // file_cluster : file_cluster = FILE_DEFAULT_CLUSTER; 0x00 文件簇号设置为默认值
            // path         :  文件名字
            err = create_sub_file(xfat, 0, parent_cluster, path, &file_cluster);//创建文件
            return err;
        } 
        else 
        {
            // 在此创建目录, 后续补充
        }

        path = next_path;
    }
    return FS_ERR_OK;
}

目录项创建

/**
 * 在指定目录下创建子文件或者目录
 * @param xfat xfat结构
 * @param is_dir 要创建的是文件还是目录
 * @param parent_dir_cluster 父目录起始数据簇号
 * @param file_cluster 预先给定的文件或目录的起始数据簇号。如果文件或目录已经存在,则返回相应的簇号
 * @param child_name 创建的目录或文件名称
 * @return
 */
static xfat_err_t create_sub_file (xfat_t * xfat, u8_t is_dir, u32_t parent_cluster,
                const char* child_name, u32_t * file_cluster) 
{
    xfat_err_t err;
    xdisk_t * disk = xfat_get_disk(xfat);
    diritem_t * target_item = (diritem_t *)0;
    u32_t curr_cluster = parent_cluster, curr_offset = 0;
    u32_t free_item_cluster = CLUSTER_INVALID, free_item_offset = 0;
    u32_t file_diritem_sector;
    u32_t found_cluster, found_offset;
    u32_t next_cluster, next_offset;

    // 遍历找到空闲项,在目录末尾添加新项
    do {

        diritem_t* diritem = (diritem_t*)0; // 目录项
        // 寻找下一目录项目
        /**
         *参数     :xfat 
                  :DIRITEM_GET_ALL :文件类型,寻找所有文件
                  :curr_cluster  当前目录项对应的簇号
                  :curr_offset   当前簇号偏移
                  :found_cluster 目标簇号
                  :found_offset  目标相对簇号偏移
                  :temp_buffer   簇存储缓冲区
                  :diritem       目录项结构体
        */
        err = get_next_diritem(xfat, DIRITEM_GET_ALL, curr_cluster, curr_offset,
                                    &found_cluster, &found_offset, &next_cluster, &next_offset, temp_buffer, &diritem);
        if (err < 0) return err;

         // 已经搜索到目录结束
        if (diritem == (diritem_t*)0) 
        {   
            break;
        }
         // 有效结束标记
        if (diritem->DIR_Name[0] == DIRITEM_NAME_END) 
        {       
            target_item = diritem;
            break;
        }
         // 寻找到空闲目录项 
        else if (diritem->DIR_Name[0] == DIRITEM_NAME_FREE) 
        {
            // 空闲项, 还要继续检查,看是否有同名项
            // 记录空闲项的位置
            if (!is_cluster_valid(free_item_cluster)) 
            {
                free_item_cluster = curr_cluster;
                free_item_offset = curr_offset;
            }
        } 
        // 如果找到与目录项中文件名字与要创建文件的名字相同
        else if (is_filename_match((const char*)diritem->DIR_Name, child_name)) 
        {
            // 仅名称相同,还要检查是否是同名的文件或目录
            int item_is_dir = diritem->DIR_Attr & DIRITEM_ATTR_DIRECTORY;
            if ((is_dir && item_is_dir) || (!is_dir && !item_is_dir)) 
            { // 同类型且同名
                *file_cluster = get_diritem_cluster(diritem);  // 返回
                return FS_ERR_EXISTED;
            } 
            else 
            {   // 不同类型,即目录-文件同名,直接报错
                return FS_ERR_NAME_USED;
            }
        }

        curr_cluster = next_cluster;
        curr_offset = next_offset;
    } while (1);

    // 未找到空闲的项,需要为父目录申请新簇,以放置新文件/目录
    if ((target_item == (diritem_t *)0) && !is_cluster_valid(free_item_cluster)) 
    {
        u32_t parent_diritem_cluster;
        u32_t cluster_count;

        xfat_err_t err = allocate_free_cluster(xfat, found_cluster, 1, &parent_diritem_cluster, &cluster_count, 1, 0);
        if (err < 0)  return err;

        if (cluster_count < 1) {
            return FS_ERR_DISK_FULL;
        }

        // 读取新建簇中的第一个扇区,获取target_item
        file_diritem_sector = cluster_fist_sector(xfat, parent_diritem_cluster);
        err = xdisk_read_sector(disk, temp_buffer, file_diritem_sector, 1);
        if (err < 0) {
            return err;
        }
        target_item = (diritem_t *)temp_buffer;     // 获取新簇项
    } 
    else 
    {    // 找到空闲或末尾
        u32_t diritem_offset;
        if (is_cluster_valid(free_item_cluster)) {
            //找到目标目录项的开始扇区 
            file_diritem_sector = cluster_fist_sector(xfat, free_item_cluster) + to_sector(disk, free_item_offset);
            //找到目录项相对当前簇的偏移
            diritem_offset = free_item_offset;
        } 
        else // 末尾
        {
            file_diritem_sector = cluster_fist_sector(xfat, found_cluster) + to_sector(disk, found_offset);
            diritem_offset = found_offset; // 这里的found_offset = 0
        }
        err = xdisk_read_sector(disk, temp_buffer, file_diritem_sector, 1);
        if (err < 0) 
        {
            return err;
        }
         // 如果找到的是文件末尾那么这里的target_item =  (diritem_t*)temp_buffer,其实就是什么都没做
         // 如果找打的是空闲目录就创建一个target_ite目录项后面往这新的目录项填充数据
        target_item = (diritem_t*)(temp_buffer + to_sector_offset(disk, diritem_offset));     // 获取新簇项
    }

    // 获取目录项之后,根据文件或目录,创建item
    err = diritem_init_default(target_item, disk, is_dir, child_name, FILE_DEFAULT_CLUSTER);
    if (err < 0) 
    {
        return err;
    }

    // 写入所在目录项,实现新的目录项的初始化
    err = xdisk_write_sector(disk, temp_buffer, file_diritem_sector, 1);
    if (err < 0) 
    {
        return err;
    }

    *file_cluster = FILE_DEFAULT_CLUSTER;
    return err;
}

文件删除

文件删除的过程就是对文件创建进行销毁的过程

  • 首先遍历目录项,找到需要删除的文件
  • 将文件名字改为?文件
  • 文件所代表的簇号转换为0
  • 目录项其它属性恢复为分配时候的状态
  • 文件的簇链销毁

主函数

int main (void) 
{
    xfat_err_t err;
    int i;

    for (i = 0; i < sizeof(write_buffer) / sizeof(u32_t); i++) 
    {
        write_buffer[i] = i;
    }

    err = xdisk_open(&disk, "vidsk", &vdisk_driver, (void *)disk_path);
    if (err) 
    {
        printf("open disk failed!\n");
        return -1;
    }

    err = disk_part_test();
    if (err) return err;

    err = xdisk_get_part(&disk, &disk_part, 1); 
    if (err < 0) {
        printf("read partition info failed!\n");
        return -1;
    }

    err = xfat_open(&xfat, &disk_part);
    if (err < 0) {
        return err;
    }
    err = fs_create_test(); //在该函数内部实现删除操作
    if (err) return err;

    err = xdisk_close(&disk);
    if (err) {
        printf("disk close failed!\n");
        return -1;
    }

    printf("Test End!\n");
    return 0;
}

删除测试

xfat_err_t fs_create_test (void) 
{
    xfat_err_t err = FS_ERR_OK;
    const char * dir_path = "/create/c0/c1/c2/c3/c4/c5/c6/c7/c8/c9/";  // 这个路径可配置短一些
    char path[256];
    int i, j;
    xfile_t file;

    printf("create test\n");

    // 注意,如果根目录下已经有要创建的文件
    // 或者之前在调试该代码时,已经执行了一部分导致部分文件被创建时
    // 注意在重启调试前,先清除干净根目录下的所有这些文件
    // 如果懒得清除,可以更改要创建的文件名
    for (i = 0; i < 3; i++) 
    {
        // 创建目录
        printf("no %d:create dir %s\n", i, dir_path);
        err = xfile_mkdir(&xfat, dir_path);
        if (err < 0) 
        {
            if (err == FS_ERR_EXISTED) 
            {
                // 只有在第一次创建时才会成功
                printf("dir exist %s, continue.\n", dir_path);
            } 
            else 
            {
                printf("create dir failed %s\n", dir_path);
                return err;
            }
        }
        for (j = 0; j < 50; j++) 
        {
            // 创建文件
            sprintf(path, "%s/b%d.txt", dir_path, j);
            printf("no %d:create file %s\n", i, path);

            err = xfile_mkfile(&xfat, path);
            if (err < 0) 
            {
                if (err == FS_ERR_EXISTED) 
                {
                    // 只有在第一次创建时才会成功
                    printf("file exist %s, continue.\n", path);
                } 
                else 
                {
                    printf("create file failed %s\n", path);
                    return err;
                }
            }

            // 进行一些读写测试,  写有点bug,写3遍就会丢数据
            err = file_write_test(path, 1024, 1, 1);
            if (err < 0) 
            {
                printf("write file failed! %s\n", path);
                return err;
            }
            printf("create %s ok!\n", path);
        }
    }

    // 空目录删除接口
    err = xfile_rmdir(&xfat, dir_path);
    if (err == FS_ERR_OK) 
    {
        printf("rm dir failed!\n");
        return -1;
    }

    printf("begin remove file\n");
    for (j = 0; j < 50; j++) 
    {
        sprintf(path, "%s/b%d.txt", dir_path, j);
        printf("rm file %s\n", path);
        
        // 文件删除
        err = xfile_rmfile(&xfat, path);
        if (err < 0) 
        {
            printf("rm file failed %s\n", path);
            return err;
        }
    }

    err = xfile_open(&xfat, &file, dir_path);
    if (err < 0) return err;
    err = list_sub_files(&file, 0);
    if (err < 0) return err;
    err = xfile_close(&file);
    if (err < 0) return err;
    err = xfile_rmdir(&xfat, dir_path);
    if (err != FS_ERR_OK) {
        printf("rm dir failed!\n");
        return -1;
    }

    printf("create test ok\n");
    return FS_ERR_OK;
}

文件删除接口xfile_rmfile

/**
 * 删除指定路径的文件
 * @param xfat xfat结构
 * @param file_path 文件的路径
 * @return
 */
xfat_err_t xfile_rmfile(xfat_t * xfat, const char * path) 
{
    diritem_t* diritem = (diritem_t*)0;
    u32_t curr_cluster, curr_offset;
    u32_t found_cluster, found_offset;
    u32_t next_cluster, next_offset;
    const char* curr_path;

    curr_cluster = xfat->root_cluster;
    curr_offset = 0;
    for (curr_path = path; curr_path != '\0'; curr_path = get_child_path(curr_path)) 
    {
        do {
            xfat_err_t err = get_next_diritem(xfat, DIRITEM_GET_USED, curr_cluster, curr_offset,
                &found_cluster, &found_offset, &next_cluster, &next_offset, temp_buffer, &diritem);
            if (err < 0) {
                return err;
            }

            if (diritem == (diritem_t*)0) {    // 已经搜索到目录结束
                return FS_ERR_NONE;
            }

            if (is_filename_match((const char*)diritem->DIR_Name, curr_path)) {
                // 找到,比较下一级子目录
                if (get_child_path(curr_path)) {
                    curr_cluster = get_diritem_cluster(diritem);
                    curr_offset = 0;
                }
                break;
            }

            curr_cluster = next_cluster;
            curr_offset = next_offset;
        } while (1);
    }

    if (diritem && !curr_path) {
        xfat_err_t err;

        // 不允许用此删除目录
        if (diritem->DIR_Attr & DIRITEM_ATTR_DIRECTORY) {
            return FS_ERR_PARAM;
        }

        // 这种方式只能用于SFN文件项重命名
        u32_t dir_sector = to_phy_sector(xfat, found_cluster, found_offset);

        diritem->DIR_Name[0] = DIRITEM_NAME_FREE;

        err = xdisk_write_sector(xfat_get_disk(xfat), temp_buffer, dir_sector, 1);
        if (err < 0) return err;
        // 删除簇链
        err = destroy_cluster_chain(xfat, get_diritem_cluster(diritem));
        if (err < 0) return err;

        return FS_ERR_OK;
    }

    return FS_ERR_NONE;
}

簇链处理

/**
 * 解除簇的链接关系
 * @param xfat xfat结构
 * @param cluster 将该簇之后的所有链接依次解除, 并将该簇之后标记为解囊
 * @return
 */
static xfat_err_t destroy_cluster_chain(xfat_t *xfat, u32_t cluster) 
{
    xfat_err_t err = FS_ERR_OK;
    u32_t i, write_back = 0;
    xdisk_t * disk = xfat_get_disk(xfat);
    u32_t curr_cluster = cluster;

    // 先在缓冲区中解除链接关系
    while (is_cluster_valid(curr_cluster)) {
        u32_t next_cluster;
        cluster32_t * cluster32_buf;

        // 先获取一下簇
        err = get_next_cluster(xfat, curr_cluster, &next_cluster);
        if (err < 0) 
        {
            return err;
        }

        // 标记该簇为空闲状态
        cluster32_buf = (cluster32_t *)xfat->fat_buffer;
        cluster32_buf[curr_cluster].s.next = CLUSTER_FREE;

        curr_cluster = next_cluster;
        write_back = 1;
    }

    if (write_back) 
    {
        for (i = 0; i < xfat->fat_tbl_nr; i++) 
        {
            // 文件内容销毁
            u32_t start_sector = xfat->fat_start_sector + xfat->fat_tbl_sectors * i;
            err = xdisk_write_sector(disk, (u8_t *)xfat->fat_buffer, start_sector, xfat->fat_tbl_sectors);
            if (err < 0) 
            {
                return err;
            }
        }
    }

    // todo: 优化,不必要全部重写
    return err;
}

空目录删除

既然有文件的销毁,那么肯定也有目录的销毁。这里先讨论目录下没有子目录与文件的的情况,也称为空目录删除。其过程与文件删除的过程一样

空目录删除接口

/**
 * 删除指定路径的目录(仅能删除目录为空的目录)
 * @param xfat xfat结构
 * @param file_path 目录的路径
 * @return
 */
xfat_err_t xfile_rmdir (xfat_t * xfat, const char * path)
{
    diritem_t* diritem = (diritem_t*)0;
    u32_t curr_cluster, curr_offset;
    u32_t found_cluster, found_offset;
    u32_t next_cluster, next_offset;
    const char* curr_path;

    // 定位path所对应的位置和dirite m
    curr_cluster = xfat->root_cluster;
    curr_offset = 0;
    for (curr_path = path; curr_path != '\0'; curr_path = get_child_path(curr_path)) 
    {
        do {
            xfat_err_t err = get_next_diritem(xfat, DIRITEM_GET_USED, curr_cluster, curr_offset,
                &found_cluster, &found_offset, &next_cluster, &next_offset, temp_buffer, &diritem);
            if (err < 0) 
            {
                return err;
            }

            if (diritem == (diritem_t*)0) {    // 已经搜索到目录结束
                return FS_ERR_NONE;
            }

            if (is_filename_match((const char*)diritem->DIR_Name, curr_path)) {
                // 找到,比较下一级子目录
                if (get_child_path(curr_path)) {
                    curr_cluster = get_diritem_cluster(diritem);
                    curr_offset = 0;
                }
                break;
            }

            curr_cluster = next_cluster;
            curr_offset = next_offset;
        } while (1);
    }

    if (diritem && !curr_path) {
        xfat_err_t err;
        int has_child;
        u32_t dir_sector;

        if (get_file_type(diritem) != FAT_DIR) {
            return FS_ERR_PARAM;
        }

        dir_sector = to_phy_sector(xfat, found_cluster, found_offset);
        err = dir_has_child(xfat, get_diritem_cluster(diritem), &has_child);
        if (err < 0) return err;

        if (has_child) {
            return FS_ERR_NOT_EMPTY;
        }

        // dir_has_child会破坏缓冲区,所以这里重新加载一遍
        err = xdisk_read_sector(xfat_get_disk(xfat), temp_buffer, dir_sector, 1);
        if (err < 0) return err;

        diritem = (diritem_t*)(temp_buffer + to_sector_offset(xfat_get_disk(xfat), found_offset));
        diritem->DIR_Name[0] = DIRITEM_NAME_FREE;

        err = xdisk_write_sector(xfat_get_disk(xfat), temp_buffer, dir_sector, 1);
        if (err < 0) return err;

        err = destroy_cluster_chain(xfat, get_diritem_cluster(diritem));
        if (err < 0) return err;

        return FS_ERR_OK;
    }

    return FS_ERR_NONE;
}

非空目录删除

前面实现的是空目录与文件的删除由于目录下不涉及文件与子目录所以,前面的文件与子目录的删除都是非常简单的。如果目录下存在子目录与文件该如何删除?

步骤

  • 文件是树状结构的因此,首先是找到最深的那一层目录或者是文件,一层一层向上删除知道根目录的下的第一层删除完毕。
  • 删除目标目录

非空目录删除接口

 err = xfile_rmdir_tree(&xfat, "/rmtree/c0");
 /**
 * 删除指定路径的目录(仅能删除目录为空的目录)
 * @param xfat xfat结构
 * @param file_path 目录的路径
 * @return
 */
xfat_err_t xfile_rmdir_tree(xfat_t* xfat, const char* path) 
{
    diritem_t* diritem = (diritem_t*)0;
    u32_t curr_cluster, curr_offset;
    u32_t found_cluster, found_offset;
    u32_t next_cluster, next_offset;
    const char* curr_path;

    // 定位path所对应的位置和diritem,找到目标的目录项于簇开始簇号与簇偏移
    curr_cluster = xfat->root_cluster;
    curr_offset = 0;
    for (curr_path = path; curr_path != '\0'; curr_path = get_child_path(curr_path)) 
    {
        do {
            xfat_err_t err = get_next_diritem(xfat, DIRITEM_GET_USED, curr_cluster, curr_offset,
                &found_cluster, &found_offset, &next_cluster, &next_offset, temp_buffer, &diritem);
            if (err < 0) {
                return err;
            }

            if (diritem == (diritem_t*)0) {    // 已经搜索到目录结束
                return FS_ERR_NONE;
            }

            if (is_filename_match((const char*)diritem->DIR_Name, curr_path)) 
            {
                // 找到,比较下一级子目录
                if (get_child_path(curr_path)) {
                    curr_cluster = get_diritem_cluster(diritem);
                    curr_offset = 0;
                }
                break;
            }

            curr_cluster = next_cluster;
            curr_offset = next_offset;
        } while (1);
    }

    if (diritem && !curr_path) 
    {
        xfat_err_t err;
        u32_t dir_sector;
        u32_t diritem_cluster = get_diritem_cluster(diritem);

        if (get_file_type(diritem) != FAT_DIR) 
        {
            return FS_ERR_PARAM;
        }
        // 将簇号转换为扇区号
        dir_sector = to_phy_sector(xfat, found_cluster, found_offset);
        // 名字修改
        diritem->DIR_Name[0] = DIRITEM_NAME_FREE;
        // 回写
        err = xdisk_write_sector(xfat_get_disk(xfat), temp_buffer, dir_sector, 1);
        if (err < 0) return err;
        // 移除目标下面的子目录
        err = rmdir_all_children(xfat, diritem_cluster);
        if (err < 0) return err;
       // 销毁目标簇号的簇链
        err = destroy_cluster_chain(xfat, diritem_cluster);
        if (err < 0) return err;

        return FS_ERR_OK;
    }

    return FS_ERR_NONE;
}

子目录与子文件删除

/**
 * 解除簇的链接关系
 * @param xfat xfat结构
 * @param cluster 将该簇之后的所有链接依次解除, 并将该簇之后标记为解囊
 * @return
 */
static xfat_err_t destroy_cluster_chain(xfat_t *xfat, u32_t cluster) 
{
    xfat_err_t err = FS_ERR_OK;
    u32_t i, write_back = 0;
    xdisk_t * disk = xfat_get_disk(xfat);
    u32_t curr_cluster = cluster;

    // 先在缓冲区中解除链接关系
    while (is_cluster_valid(curr_cluster)) 
    {
        u32_t next_cluster;
        cluster32_t * cluster32_buf;

        // 先获取一下簇,文件数据是以簇链的形式存储,所以只要找到文件簇号就能根据簇链将文件的所有数据都销毁
        err = get_next_cluster(xfat, curr_cluster, &next_cluster);
        if (err < 0) {
            return err;
        }

        // 标记该簇为空闲状态
        cluster32_buf = (cluster32_t *)xfat->fat_buffer;
        cluster32_buf[curr_cluster].s.next = CLUSTER_FREE; // 将文件从链表中移除就是将簇号标志为空闲

        curr_cluster = next_cluster;
        write_back = 1;
    }

    if (write_back) 
    {
        for (i = 0; i < xfat->fat_tbl_nr; i++) 
        {
            // 重新调整FAT表中的数据
            u32_t start_sector = xfat->fat_start_sector + xfat->fat_tbl_sectors * i;
            err = xdisk_write_sector(disk, (u8_t *)xfat->fat_buffer, start_sector, xfat->fat_tbl_sectors);
            if (err < 0) 
            {
                return err;
            }
        }
    }

    // todo: 优化,不必要全部重写
    return err;
}

文件大小调整接口

当对文件进行写操作或者是删除时候,文件大小都会发生相应的改变,为此需要设置一个文件大小调整的接口,记录文件大小的变化

文件数在磁盘中是以簇链的方式存储数据的。当要进行文件的扩容其实就是增加或者减少簇*n[其中N代表簇的数量]。所以文件大小的接口就是扩容几个簇和截断几个簇的过程,这里就不需要像文件写入数据或者读取数据的时候要考虑一些扇区对齐与不对齐的问题了。

文件大小调整测试接口

/**
 * 调整文件大小。当指定大小小于文件大小时,将截断文件;如果大于,将扩展文件
 * @param file 待调整的文件
 * @param size 调整后的文件大小
 * @param mode 调整模式
 * @return
 */
xfat_err_t xfile_resize (xfile_t * file, xfile_size_t size) 
{
    xfat_err_t err = FS_ERR_OK;

    if (size == file->size)  // 如果传入文件需要改变的大小与文件本身的大小一样则不需要修改
    {
        return FS_ERR_OK;
    } 
    else if (size > file->size) // 需要扩容
    {
        err = expand_file(file, size);
        if (err < 0) {
            return err;
        }
    } 
    else // 需要截断
    {
        err = truncate_file(file, size);
        if (err < 0) 
        {
            return err;
        }

        // 如果使得读写位置超出调整后的位置,调整至文件开始处
        if (file->pos >= size) 
        {
            file->pos = 0;
            file->curr_cluster = file->start_cluster;
        }
    }

    return err;
}

文件扩容接口

/**
 * 扩充文件大小,新增的文件数据部分,其内容由mode来控制
 * @param file 待扩充的文件
 * @param size 新的文件大小
 * @param mode 扩充模式
 * @return
 */
static xfat_err_t expand_file(xfile_t * file, xfile_size_t size) 
{
    xfat_err_t err;
    xfat_t * xfat = file->xfat;
	u32_t curr_cluster_cnt = to_cluseter_count(xfat, file->size);
    u32_t expect_cluster_cnt = to_cluseter_count(xfat, size);

    // 当扩充容量需要跨簇时,在簇链之后增加新项
    if (curr_cluster_cnt < expect_cluster_cnt) 
    {
        u32_t cluster_cnt = expect_cluster_cnt - curr_cluster_cnt;
        u32_t start_free_cluster = 0;
		u32_t curr_culster = file->curr_cluster;

        // 先定位至文件的最后一簇, 仅需要定位文件大小不为0的簇
        if (file->size > 0) {
			u32_t next_cluster = file->curr_cluster;

			do {
				curr_culster = next_cluster;

				err = get_next_cluster(xfat, curr_culster, &next_cluster);
				if (err) {
					file->err = err;
					return err;
				}
			} while (is_cluster_valid(next_cluster));
        }

        // 然后再从最后一簇分配空间
        err = allocate_free_cluster(file->xfat, curr_culster, cluster_cnt, &start_free_cluster, 0, 0, 0);
        if (err) 
        {
            file->err = err;
            return err;
        }

		if (!is_cluster_valid(file->start_cluster)) {
			file->start_cluster = start_free_cluster;
			file->curr_cluster = start_free_cluster;
		} else if (!is_cluster_valid(file->curr_cluster) || is_fpos_cluster_end(file)) {
			file->curr_cluster = start_free_cluster;
		}
    }

    // 最后,再更新文件大小,是否放在关闭文件时进行?
    err = update_file_size(file, size);
    return err;
}

文件截断接口

/**
 * 截断文件,使得文件的最终长度比原来的要小
 * @param file 要截断的文件
 * @param size 最终的大小
 * @param mode 截断的模式
 * @return
 */
static xfat_err_t truncate_file(xfile_t * file, xfile_size_t size) 
{
    xfat_err_t err;
    u32_t pos = 0;
    u32_t curr_cluster = file->start_cluster;

    // 定位到size对应的cluster
    while (pos < size) 
    {
        u32_t next_cluster;

        err = get_next_cluster(file->xfat, curr_cluster, &next_cluster);
        if (err < 0) {
            return err;
        }
        pos += file->xfat->cluster_byte_size;
        curr_cluster = next_cluster;
    }

    // 销毁后继的FAT链
    err = destroy_cluster_chain(file->xfat, curr_cluster);
    if (err < 0) 
    {
        return err;
    }

    if (size == 0) 
    {
        file->start_cluster = 0;
    }

    // 文件截取,当前位置将重置为文件开头,所以直接调整大小即可
    err = update_file_size(file, size);
    return err;
}


工程代码链接中地址:

github:https://github.com/WOLEN6914183/FAT32.git

百度网盘->码:fat3
https://pan.baidu.com/s/1B8IP61aozYquoDFcKE_J6Q

喜欢就微信扫描下面二维码关注我吧!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值