天天看點

fat檔案系統查找、删除、建立目錄

作者:清風成長社

Fat檔案系統查找過程

查找一個檔案是通過檔案名查找的,對于fat檔案系統,可以直接從vfat_lookup函數看

主體流程如下:

vfat_lookup
    vfat_find
    	fat_search_long
    		while (1)
    			fat_get_entry(inode, &cpos, &bh, &de)
    			fat_name_match(sbi, name, name_len, bufname, len)
    fat_build_inode
		inode = new_inode(sb);
			inode->i_ino = iunique(sb, MSDOS_ROOT_INO);
			fat_fill_inode(inode, de)
           

vfat_lookup調了fat_search_long查找檔案,其中qname->name就是檔案名,比如說我們打開file.txt,就會先查找檔案,這裡傳入的參數qname->name就是檔案名,列印出來就是file.txt。

static int vfat_find(struct inode *dir, const struct qstr *qname,
		     struct fat_slot_info *sinfo)
{
	unsigned int len = vfat_striptail_len(qname);
	if (len == 0)
		return -ENOENT;
	return fat_search_long(dir, qname->name, len, sinfo);
}
           

fat_search_long裡面有個死循環,裡面先調用fat_get_entry(inode, &cpos, &bh, &de)擷取目錄項,也就是de變量,msdos_dir_entry這個結構體,這個就是fdt表,也是fat檔案系統的目錄項。

先判斷檔案名是否等于DELETED_FLAG,就是0xe5,如果是,代碼這個是删除檔案,直接continue擷取下一個目錄項繼續解析。一般短檔案名,通過fat_name_match(sbi, name, name_len, bufname, len)函數,比對,比對上的話,說明找到該檔案,直接跳轉到found,

int fat_search_long(struct inode *inode, const unsigned char *name,
		    int name_len, struct fat_slot_info *sinfo)
{
	struct super_block *sb = inode->i_sb;
	struct msdos_sb_info *sbi = MSDOS_SB(sb);
	struct buffer_head *bh = NULL;
	struct msdos_dir_entry *de;
	unsigned char nr_slots;
	wchar_t *unicode = NULL;
	unsigned char bufname[FAT_MAX_SHORT_SIZE];
	loff_t cpos = 0;
	int err, len;
	err = -ENOENT;
	while (1) {
		if (fat_get_entry(inode, &cpos, &bh, &de) == -1)
			goto end_of_dir;
parse_record:
		nr_slots = 0;
		if (de->name[0] == DELETED_FLAG)
			continue;
		if (de->attr != ATTR_EXT && (de->attr & ATTR_VOLUME))
			continue;
		if (de->attr != ATTR_EXT && IS_FREE(de->name))
			continue;
		if (de->attr == ATTR_EXT) {
			int status = fat_parse_long(inode, &cpos, &bh, &de,
						    &unicode, &nr_slots);
			if (status < 0) {
				err = status;
				goto end_of_dir;
			} else if (status == PARSE_INVALID)
				continue;
			else if (status == PARSE_NOT_LONGNAME)
				goto parse_record;
			else if (status == PARSE_EOF)
				goto end_of_dir;
		}
		/* Never prepend '.' to hidden files here.
		 * That is done only for msdos mounts (and only when
		 * 'dotsOK=yes'); if we are executing here, it is in the
		 * context of a vfat mount.
		 */
		len = fat_parse_short(sb, de, bufname, 0);
		if (len == 0)
			continue;
		/* Compare shortname */
		if (fat_name_match(sbi, name, name_len, bufname, len))
			goto found;
		if (nr_slots) {
			void *longname = unicode + FAT_MAX_UNI_CHARS;
			int size = PATH_MAX - FAT_MAX_UNI_SIZE;
			/* Compare longname */
			len = fat_uni_to_x8(sb, unicode, longname, size);
			if (fat_name_match(sbi, name, name_len, longname, len))
				goto found;
		}
	}
found:
	nr_slots++;	/* include the de */
	sinfo->slot_off = cpos - nr_slots * sizeof(*de);
	sinfo->nr_slots = nr_slots;
	sinfo->de = de;
	sinfo->bh = bh;
	sinfo->i_pos = fat_make_i_pos(sb, sinfo->bh, sinfo->de);
	err = 0;
end_of_dir:
	if (unicode)
		__putname(unicode);
	return err;
}
           

找到檔案後,會調用fat_build_inode建立索引節點資訊,因為索引節點不是一直存在的,在我們嵌入式場景中,記憶體通常比較小,記憶體回收的時候會釋放掉之前建立過的索引節點,是以這裡大部分情況下都是走new_inode(sb)配置設定新的inode,然後調iunique(sb, MSDOS_ROOT_INO)查找一個沒使用過的編号填充到inode->i_ino,通過都是從1開始往後配置設定,inode->i_ino就像是身份證一樣的資訊,差別每個inode,這個我們調試一些檔案系統問題的時候通常會用到它。

struct inode *fat_build_inode(struct super_block *sb,
			struct msdos_dir_entry *de, loff_t i_pos)
{
	struct inode *inode;
	int err;
	fat_lock_build_inode(MSDOS_SB(sb));
	inode = fat_iget(sb, i_pos);
	if (inode)
		goto out;
	inode = new_inode(sb);
	if (!inode) {
		inode = ERR_PTR(-ENOMEM);
		goto out;
	}
	inode->i_ino = iunique(sb, MSDOS_ROOT_INO);
	inode->i_version = 1;
	err = fat_fill_inode(inode, de);
	if (err) {
		iput(inode);
		inode = ERR_PTR(err);
		goto out;
	}
	fat_attach(inode, i_pos);
	insert_inode_hash(inode);
out:
	fat_unlock_build_inode(MSDOS_SB(sb));
	return inode;
}
           

然後調用fat_fill_inode填充inode資訊,可以看到第一個判斷,如果這個目錄項是目錄,這裡填充的一些資訊,如:

MSDOS_I(inode)->i_start = fat_get_start(sbi, de); // 就是擷取簇号

MSDOS_I(inode)->i_logstart = MSDOS_I(inode)->i_start;

其實就是簇号,可以參考建立目錄時的處理,參考函數fat_alloc_new_dir的實作。

set_nlink的作用是設定i_nlink,它代表的是該目錄的子目錄數量,fat_subdirs就是計算子目錄數量,一個目錄至少有2個子目錄,目前目錄和上級目錄。我的代碼是4.14版本的,這裡還加了個判斷fat_validate_dir判斷該目錄是否為有效目錄,據我所知,核心4.4版本是沒有該函數判斷的,這會導緻一些問題不能及時發現。

int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)
{
	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
	int error;
	MSDOS_I(inode)->i_pos = 0;
	inode->i_uid = sbi->options.fs_uid;
	inode->i_gid = sbi->options.fs_gid;
	inode->i_version++;
	inode->i_generation = get_seconds();
	if ((de->attr & ATTR_DIR) && !IS_FREE(de->name)) {
		inode->i_generation &= ~1;
		inode->i_mode = fat_make_mode(sbi, de->attr, S_IRWXUGO);
		inode->i_op = sbi->dir_ops;
		inode->i_fop = &fat_dir_operations;
		MSDOS_I(inode)->i_start = fat_get_start(sbi, de);
		MSDOS_I(inode)->i_logstart = MSDOS_I(inode)->i_start;
		error = fat_calc_dir_size(inode);
		if (error < 0)
			return error;
		MSDOS_I(inode)->mmu_private = inode->i_size;
		set_nlink(inode, fat_subdirs(inode));
		error = fat_validate_dir(inode);
		if (error < 0)
			return error;
	} else { /* not a directory */
		inode->i_generation |= 1;
		inode->i_mode = fat_make_mode(sbi, de->attr,
			((sbi->options.showexec && !is_exec(de->name + 8))
			 ? S_IRUGO|S_IWUGO : S_IRWXUGO));
		MSDOS_I(inode)->i_start = fat_get_start(sbi, de);
		MSDOS_I(inode)->i_logstart = MSDOS_I(inode)->i_start;
		inode->i_size = le32_to_cpu(de->size);
		inode->i_op = &fat_file_inode_operations;
		inode->i_fop = &fat_file_operations;
		inode->i_mapping->a_ops = &fat_aops;
		MSDOS_I(inode)->mmu_private = inode->i_size;
	}
	if (de->attr & ATTR_SYS) {
		if (sbi->options.sys_immutable)
			inode->i_flags |= S_IMMUTABLE;
	}
	fat_save_attrs(inode, de->attr);
	inode->i_blocks = ((inode->i_size + (sbi->cluster_size - 1))
			   & ~((loff_t)sbi->cluster_size - 1)) >> 9;
	fat_time_fat2unix(sbi, &inode->i_mtime, de->time, de->date, 0);
	if (sbi->options.isvfat) {
		fat_time_fat2unix(sbi, &inode->i_ctime, de->ctime,
				  de->cdate, de->ctime_cs);
		fat_time_fat2unix(sbi, &inode->i_atime, 0, de->adate, 0);
	} else
		inode->i_ctime = inode->i_atime = inode->i_mtime;
	return 0;
}
           

關于fat檔案系統查找就介紹這幾個函數,主要是要了解幾個關鍵的結構體、inode,目錄項這幾個重要的東西在查找中的作用。

fat檔案系統删除

對于檔案系統删除,有2個系統調用,入口函數一個是do_unlinkat,一個是do_rmdir,它們大體流程一緻,這裡主要有2件事,一個是釋放目錄項,給fdt表檔案名首位元組設定成0xe5,那麼查找的時候先判斷de->name[0]如果是DELETED_FLAG就跳過了,不記得可以往上看回查找過程,一個是釋放簇(在fat表寫為0),資料區不變。

do_unlinkat
    vfs_unlink
        dir->i_op->unlink
            vfat_unlink
                vfat_find
                fat_remove_entries
                    while (nr_slots && de >= (struct msdos_dir_entry *)bh->b_data) {
                        de->name[0] = DELETED_FLAG; // 删除标志0xe5
                        de--;
                        nr_slots--;
                    }
    dput
        dentry_kill
            __dentry_kill
                iput
                    iput_final
                        evict
                            op->evict_inode
                                fat_evict_inode
                                    // 這裡進入到fat檔案系統層,往下就是釋放簇
           

fat檔案系統建立目錄

fat_alloc_new_dir就是建立目錄的函數,可以看到,建立的時候先在fat表找到一個未使用的簇号,配置設定出來,然後fat_clus_to_blknr函數通過該簇号計算出扇區編号,然後用sb_getblk對應的扇區讀到記憶體上,緊接着就是填充fdt表,de[0]就是目前目錄,de[1]就是上級目錄,還有就是填充一些其他資訊,如修改時間,簇号等,fat_zeroed_cluster就是把該簇其他扇區資料清0。

memcpy(de[0].name, MSDOS_DOT, MSDOS_NAME);
memcpy(de[1].name, MSDOS_DOTDOT, MSDOS_NAME);           
int fat_alloc_new_dir(struct inode *dir, struct timespec *ts)
{
	struct super_block *sb = dir->i_sb;
	struct msdos_sb_info *sbi = MSDOS_SB(sb);
	struct buffer_head *bhs[MAX_BUF_PER_PAGE];
	struct msdos_dir_entry *de;
	sector_t blknr;
	__le16 date, time;
	u8 time_cs;
	int err, cluster;
	err = fat_alloc_clusters(dir, &cluster, 1);
	if (err)
		goto error;
	blknr = fat_clus_to_blknr(sbi, cluster);
	bhs[0] = sb_getblk(sb, blknr);
	if (!bhs[0]) {
		err = -ENOMEM;
		goto error_free;
	}
	fat_time_unix2fat(sbi, ts, &time, &date, &time_cs);
	de = (struct msdos_dir_entry *)bhs[0]->b_data;
	/* filling the new directory slots ("." and ".." entries) */
	memcpy(de[0].name, MSDOS_DOT, MSDOS_NAME);
	memcpy(de[1].name, MSDOS_DOTDOT, MSDOS_NAME);
	de->attr = de[1].attr = ATTR_DIR;
	de[0].lcase = de[1].lcase = 0;
	de[0].time = de[1].time = time;
	de[0].date = de[1].date = date;
	if (sbi->options.isvfat) {
		/* extra timestamps */
		de[0].ctime = de[1].ctime = time;
		de[0].ctime_cs = de[1].ctime_cs = time_cs;
		de[0].adate = de[0].cdate = de[1].adate = de[1].cdate = date;
	} else {
		de[0].ctime = de[1].ctime = 0;
		de[0].ctime_cs = de[1].ctime_cs = 0;
		de[0].adate = de[0].cdate = de[1].adate = de[1].cdate = 0;
	}
	fat_set_start(&de[0], cluster);
	fat_set_start(&de[1], MSDOS_I(dir)->i_logstart);
	de[0].size = de[1].size = 0;
	memset(de + 2, 0, sb->s_blocksize - 2 * sizeof(*de));
	set_buffer_uptodate(bhs[0]);
	mark_buffer_dirty_inode(bhs[0], dir);
	err = fat_zeroed_cluster(dir, blknr, 1, bhs, MAX_BUF_PER_PAGE);
	if (err)
		goto error_free;
	return cluster;
error_free:
	fat_free_clusters(dir, cluster);
error:
	return err;
}