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

2023-10-17 18:10
文章标签 分析 操作 十一 fat32

本文主要是介绍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.txtparent_cluster = xfat->root_cluster;// 逐级创建目录和文件while (!is_path_end(path))  // 判断路径是否为空{xfat_err_t err;u32_t file_cluster = FILE_DEFAULT_CLUSTER;//0x00const 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_itemfile_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));     // 获取新簇项}// 获取目录项之后,根据文件或目录,创建itemerr = 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:目前简单起见,从头开始查找, 用于FAT32u32_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.txtparent_cluster = xfat->root_cluster;// 逐级创建目录和文件while (!is_path_end(path))  // 判断路径是否为空{xfat_err_t err;u32_t file_cluster = FILE_DEFAULT_CLUSTER;//0x00const 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_itemfile_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));     // 获取新簇项}// 获取目录项之后,根据文件或目录,创建itemerr = 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 mcurr_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对应的clusterwhile (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

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

这篇关于FAT32分析(十一)—文件写操作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/227088

相关文章

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

Mysql表的简单操作(基本技能)

《Mysql表的简单操作(基本技能)》在数据库中,表的操作主要包括表的创建、查看、修改、删除等,了解如何操作这些表是数据库管理和开发的基本技能,本文给大家介绍Mysql表的简单操作,感兴趣的朋友一起看... 目录3.1 创建表 3.2 查看表结构3.3 修改表3.4 实践案例:修改表在数据库中,表的操作主要

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处

Java使用Curator进行ZooKeeper操作的详细教程

《Java使用Curator进行ZooKeeper操作的详细教程》ApacheCurator是一个基于ZooKeeper的Java客户端库,它极大地简化了使用ZooKeeper的开发工作,在分布式系统... 目录1、简述2、核心功能2.1 CuratorFramework2.2 Recipes3、示例实践3

Java利用JSONPath操作JSON数据的技术指南

《Java利用JSONPath操作JSON数据的技术指南》JSONPath是一种强大的工具,用于查询和操作JSON数据,类似于SQL的语法,它为处理复杂的JSON数据结构提供了简单且高效... 目录1、简述2、什么是 jsONPath?3、Java 示例3.1 基本查询3.2 过滤查询3.3 递归搜索3.4

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

找不到Anaconda prompt终端的原因分析及解决方案

《找不到Anacondaprompt终端的原因分析及解决方案》因为anaconda还没有初始化,在安装anaconda的过程中,有一行是否要添加anaconda到菜单目录中,由于没有勾选,导致没有菜... 目录问题原因问http://www.chinasem.cn题解决安装了 Anaconda 却找不到 An

Spring定时任务只执行一次的原因分析与解决方案

《Spring定时任务只执行一次的原因分析与解决方案》在使用Spring的@Scheduled定时任务时,你是否遇到过任务只执行一次,后续不再触发的情况?这种情况可能由多种原因导致,如未启用调度、线程... 目录1. 问题背景2. Spring定时任务的基本用法3. 为什么定时任务只执行一次?3.1 未启用

Python使用DrissionPage中ChromiumPage进行自动化网页操作

《Python使用DrissionPage中ChromiumPage进行自动化网页操作》DrissionPage作为一款轻量级且功能强大的浏览器自动化库,为开发者提供了丰富的功能支持,本文将使用Dri... 目录前言一、ChromiumPage基础操作1.初始化Drission 和 ChromiumPage

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件