ext2文件系统dir.c分析

2021SC@SDUSC

// SPDX-License-Identifier: GPL-2.0
/*
 *  linux/fs/ext2/dir.c
 *
 * Copyright (C) 1992, 1993, 1994, 1995
 * Remy Card (card@masi.ibp.fr)
 * Laboratoire MASI - Institut Blaise Pascal
 * Universite Pierre et Marie Curie (Paris VI)
 *
 *  from
 *
 *  linux/fs/minix/dir.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 *
 *  ext2 directory handling functions
 *
 *  Big-endian to little-endian byte-swapping/bitmaps by
 *        David S. Miller (davem@caip.rutgers.edu), 1995
 *
 * All code that works with directory layout had been switched to pagecache
 * and moved here. AV
 */

#include "ext2.h"
#include <linux/buffer_head.h>
#include <linux/pagemap.h>
#include <linux/swap.h>
#include <linux/iversion.h>

typedef struct ext2_dir_entry_2 ext2_dirent;

/*
 * 针对MAX_REC_LEN等的测试被放置在64k块大小的地方;如果这在这个arch上是不可能的,我们可以跳过这些测试,加快速度。
 */
/*从磁盘上获取ext2_rec_len*/
static inline unsigned ext2_rec_len_from_disk(__le16 dlen)
{
	unsigned len = le16_to_cpu(dlen);

#if (PAGE_SIZE >= 65536)
	if (len == EXT2_MAX_REC_LEN)
		return 1 << 16;
#endif
	return len;
}
/*将ext2_rec_len写入磁盘*/
static inline __le16 ext2_rec_len_to_disk(unsigned len)
{
#if (PAGE_SIZE >= 65536)
	if (len == (1 << 16))
		return cpu_to_le16(EXT2_MAX_REC_LEN);
	else
		BUG_ON(len > (1 << 16));
#endif
	return cpu_to_le16(len);
}

/*
 *Ext2使用块大小的块。返回inode结构体所属的文件系统的块大小字节数
 */
static inline unsigned ext2_chunk_size(struct inode *inode)
{
	return inode->i_sb->s_blocksize;
}

/*
 * 返回该页中最后一个有效字节的偏移量到页' page_nr',再加1。
 */
static unsigned
ext2_last_byte(struct inode *inode, unsigned long page_nr)
{
/*先获得文件字节大小,减去前边的页的字节数,page_nr << PAGE_CACHE_SHIFT就等于page_nr乘上页大小*/
	unsigned last_byte = inode->i_size;

	last_byte -= page_nr << PAGE_SHIFT;
/*如果page_nr不是最后一页,就返回当前页的最后一个字节位置加一*/
	if (last_byte > PAGE_SIZE)
		last_byte = PAGE_SIZE;
	return last_byte;
}
/*把page页缓存上的from到to的字节修改提交上去*/
static int ext2_commit_chunk(struct page *page, loff_t pos, unsigned len)
{
	struct address_space *mapping = page->mapping;
	struct inode *dir = mapping->host;
	int err = 0;

	inode_inc_iversion(dir);
/*调用页缓冲区的函数把修改提交*/
	block_write_end(NULL, mapping, pos, len, len, page, NULL);
/*如果目录长度改变,更新长度*/
	if (pos+len > dir->i_size) {
		i_size_write(dir, pos+len);
		mark_inode_dirty(dir);
	}
/*如果标志上要求写入立刻同步,就同步,否则释放此页*/
	if (IS_DIRSYNC(dir)) {
		err = write_one_page(page);
		if (!err)
			err = sync_inode_metadata(dir, 1);
	} else {
		unlock_page(page);
	}

	return err;
}
/*检验页有没有错误*/
static bool ext2_check_page(struct page *page, int quiet, char *kaddr)
{
/*dir是页的主人*/
	struct inode *dir = page->mapping->host;
/*sb是dir的文件系统vfs层的超级块*/
	struct super_block *sb = dir->i_sb;
/*获得dir结构体所属的文件系统的块大小字节数*/
	unsigned chunk_size = ext2_chunk_size(dir);
/*文件系统总的inode数目*/
	u32 max_inumber = le32_to_cpu(EXT2_SB(sb)->s_es->s_inodes_count);
	unsigned offs, rec_len;
/*limit是页缓存的大小*/
	unsigned limit = PAGE_SIZE;
	ext2_dirent *p;
	char *error;
/*文件大小右移PAGE_CACHE_SHIFT位得到的是文件的最后一个缓存页的号码,如果等于page的index,就是说page就是文件的最后一部分对应的缓存页,并且文件都在缓冲区里*/
	if ((dir->i_size >> PAGE_SHIFT) == page->index) {
		limit = dir->i_size & ~PAGE_MASK;
/*如果limit为0说明没问题;不为零,说明这个文件目录不是一个完整的块大小的倍数,文件可能不是块大小的倍数,但是文件目录必定是块大小的倍数,这里就返回大小错误*/

		if (limit & (chunk_size - 1))
			goto Ebadsize;
		if (!limit)
			goto out;
	}
/*EXT2_DIR_REC_LEN宏我们前边讲过的,这里就是遍历目录的block块的内容,遍历块内的每一个ext2_dir_entry_2结构体*/
	for (offs = 0; offs <= limit - EXT2_DIR_REC_LEN(1); offs += rec_len) {
/*p指针指向当前应该指向的ext2_dir_entry_2结构体*/
		p = (ext2_dirent *)(kaddr + offs);
/*rec_len当前项的长度*/
		rec_len = ext2_rec_len_from_disk(p->rec_len);
/*当前项至少大于这个,如果小于,说明有问题,返回错误*/
		if (unlikely(rec_len < EXT2_DIR_REC_LEN(1)))
			goto Eshort;
/*规定多有的目录项边界与4对齐,这说明没有对齐,返回没对齐的错误*/
		if (unlikely(rec_len & 3))
			goto Ealign;
/*rec_len和文件名大小不一致*/
		if (unlikely(rec_len < EXT2_DIR_REC_LEN(p->name_len)))
			goto Enamelen;
/*大小超出当前块了,说明rec_len有问题*/
		if (unlikely(((offs + rec_len - 1) ^ offs) & ~(chunk_size-1)))
			goto Espan;
/*目录项的inode编号大于inode的最大编号,编号错误*/
		if (unlikely(le32_to_cpu(p->inode) > max_inumber))
			goto Einumber;
	}
/*说明目录项没有和块边界对齐*/
	if (offs != limit)
		goto Eend;
out:
/*page的flags有一个位标记这个page已经被检查过了,这里标记位为1*/
	SetPageChecked(page);
	return true;

/*如果情况有错*/
Ebadsize:
/*目录的inode指向的文件大小不合法,打印这个块的大小不是块大小的倍数*/
	if (!quiet)
		ext2_error(sb, __func__,
			"size of directory #%lu is not a multiple "
			"of chunk size", dir->i_ino);
	goto fail;
Eshort:
/*rec_len比最小的值还要小*/
	error = "rec_len is smaller than minimal";
	goto bad_entry;
Ealign:
/*目录项未对齐*/
	error = "unaligned directory entry";
	goto bad_entry;
Enamelen:
/*rec_len与名称长度不匹配*/
	error = "rec_len is too small for name_len";
	goto bad_entry;
Espan:
/*目录项超出了块的边界*/
	error = "directory entry across blocks";
	goto bad_entry;
Einumber:
/*inode号码错误*/
	error = "inode out of bounds";
bad_entry:
/*目录项损坏*/
	if (!quiet)
		ext2_error(sb, __func__, "bad entry in directory #%lu: : %s - "
			"offset=%lu, inode=%lu, rec_len=%d, name_len=%d",
			dir->i_ino, error, (page->index<<PAGE_SHIFT)+offs,
			(unsigned long) le32_to_cpu(p->inode),
			rec_len, p->name_len);
	goto fail;
Eend:
/*结束*/
	if (!quiet) {
		p = (ext2_dirent *)(kaddr + offs);
		ext2_error(sb, "ext2_check_page",
			"entry in directory #%lu spans the page boundary"
			"offset=%lu, inode=%lu",
			dir->i_ino, (page->index<<PAGE_SHIFT)+offs,
			(unsigned long) le32_to_cpu(p->inode));
	}
fail:
/*标记这个page标记过,但是有错误*/
	SetPageError(page);
	return false;
}

/*
 * 对ext2_get_page()/ext2_put_page()的调用必须根据kmap_local_page()/kunmap_local()中记录的规则进行嵌套。
 *
 *  注意:ext2_find_entry()和ext2_dotdot()作为对ext2_get_page()的调用,应该作为对ext2_get_page()的调用来处理,以达到嵌套的目的。
 */
/*这个函数可以从页缓存得到目录的inode的第n页数据*/
static struct page * ext2_get_page(struct inode *dir, unsigned long n,
				   int quiet, void **page_addr)
{
/*从目录的inode获得地址空间结构体*/
	struct address_space *mapping = dir->i_mapping;
/*从地址空间读取第n页*/
	struct page *page = read_mapping_page(mapping, n, NULL);
/*如果读取成功了,映射后检查页,如果这个页有错误,就跳转到fail,否则返回该页*/
	if (!IS_ERR(page)) {
		*page_addr = kmap_local_page(page);
		if (unlikely(!PageChecked(page))) {
			if (PageError(page) || !ext2_check_page(page, quiet,
								*page_addr))
				goto fail;
		}
	}
	return page;

fail:
	ext2_put_page(page, *page_addr);
	return ERR_PTR(-EIO);
}

/*
 * 注意!与strncmp不同,ext2_match返回1表示成功,0表示失败。
 *
 * len <= EXT2_NAME_LEN和de != NULL由调用者保证。
 */
static inline int ext2_match (int len, const char * const name,
					struct ext2_dir_entry_2 * de)
{
/*如果长度都不一样,就不可能一样,直接返回错误*/
	if (len != de->name_len)
		return 0;
/*如果目录项的inode为0,说明这个目录项被删除了,返回0*/
	if (!de->inode)
		return 0;
/*对比name和de->name是否一致,返回和memcmp相反的返回值*/
	return !memcmp(name, de->name, len);
}

/*
 * P距离页面结束至少6个字节。这个函数返回p指向的目录项的下一个目录项
 */
static inline ext2_dirent *ext2_next_entry(ext2_dirent *p)
{
/*rec_len是当前的目录项的长度,当前指针加上rec_len个字节长度就得到了下一项的开头,但是rec_len是结构体的第5,6个字节,所以必须保证p至少是页边界的前六个字节之前*/
	return (ext2_dirent *)((char *)p +
			ext2_rec_len_from_disk(p->rec_len));
}

/*验证目录项,base是页的起始地址,offset是要检查的目录项偏移,mask是块大小减一得到的掩码*/
static inline unsigned 
ext2_validate_entry(char *base, unsigned offset, unsigned mask)
{
/*指向要检查的目录项*/
	ext2_dirent *de = (ext2_dirent*)(base + offset);
/*指向要检验的目录项所在页的第一个目录项位置*/
	ext2_dirent *p = (ext2_dirent*)(base + (offset&mask));
	while ((char*)p < (char*)de) {
/*如果检验到rec_len=0,就是有错的,跳出循环*/
		if (p->rec_len == 0)
			break;
		p = ext2_next_entry(p);
	}
/*返回有错误的目录项的页内偏移*/
	return (char *)p - base;
}
/*设置目录项的类型*/
static inline void ext2_set_de_type(ext2_dirent *de, struct inode *inode)
{
/*检查EXT2_FEATURE_INCOMPAT_FILETYPE位,如果为1,就根据mode赋值文件类型,否则置为0,就是未知文件类型*/
	if (EXT2_HAS_INCOMPAT_FEATURE(inode->i_sb, EXT2_FEATURE_INCOMPAT_FILETYPE))
		de->file_type = fs_umode_to_ftype(inode->i_mode);
	else
		de->file_type = 0;
}

/*读取文件的目录内容,filp是要读取得文件指针,dirent是读取出来存放的缓冲区,filldir是把读取出来的数据按照不同的格式存放在dirent缓冲区里的方法*/
static int
ext2_readdir(struct file *file, struct dir_context *ctx)
{
	loff_t pos = ctx->pos;/*文件的偏移*/
	struct inode *inode = file_inode(file);
	struct super_block *sb = inode->i_sb;
/*pos与上缓存块的掩码得到了offset,得到的是在块内的偏移*/
	unsigned int offset = pos & ~PAGE_MASK;
/*n得到的是当前读取到的页编号*/
	unsigned long n = pos >> PAGE_SHIFT;
/*npages是文件占用的页数目*/
	unsigned long npages = dir_pages(inode);
	unsigned chunk_mask = ~(ext2_chunk_size(inode)-1);
/*如果filp->f_version和inode->i_version不一致,就需要检验,这	version记录文件的版本号,每次使用后都会加一,不一致就说明有可能内	容不一样*/
	bool need_revalidate = !inode_eq_iversion(inode, file->f_version);
	bool has_filetype;
/*检验pos值是不是超出限制了*/
	if (pos > inode->i_size - EXT2_DIR_REC_LEN(1))
		return 0;
/*如果ext2文件系统有incompt_feature字段,先把types指针指向文件类型表*/
	has_filetype =
		EXT2_HAS_INCOMPAT_FEATURE(sb, EXT2_FEATURE_INCOMPAT_FILETYPE);

	for ( ; n < npages; n++, offset = 0) {
		char *kaddr, *limit;
		ext2_dirent *de;
/*获取页号*/
		struct page *page = ext2_get_page(inode, n, 0, (void **)&kaddr);
/*如果值有错,报错,并把ctx_pos指向下一页*/
		if (IS_ERR(page)) {
			ext2_error(sb, __func__,
				   "bad page in #%lu",
				   inode->i_ino);
			ctx->pos += PAGE_SIZE - offset;
			return PTR_ERR(page);
		}
		if (unlikely(need_revalidate)) {
/*如果块内偏移不是0,就检验偏移的值,并且新的合法的偏移值赋给ctx_pos*/
			if (offset) {
				offset = ext2_validate_entry(kaddr, offset, chunk_mask);
				ctx->pos = (n<<PAGE_SHIFT) + offset;
			}
			file->f_version = inode_query_iversion(inode);
			need_revalidate = false;
		}
/*根据得到的缓冲区,指向ext2的目录项结构体*/
		de = (ext2_dirent *)(kaddr+offset);
/*得到的limit解释页内的合法的边界*/
		limit = kaddr + ext2_last_byte(inode, n) - EXT2_DIR_REC_LEN(1);
		for ( ;(char*)de <= limit; de = ext2_next_entry(de)) {
/*0长度的目录项是不合法的,返回IO错误,释放当前页*/
			if (de->rec_len == 0) {
				ext2_error(sb, __func__,
					"zero-length directory entry");
				ext2_put_page(page, kaddr);
				return -EIO;
			}
			if (de->inode) {
				unsigned char d_type = DT_UNKNOWN;
/*d_type从目录项里得到文件类型*/
				if (has_filetype)
					d_type = fs_ftype_to_dtype(de->file_type);
/*如果写超出内存边界,释放页并返回*/
				if (!dir_emit(ctx, de->name, de->name_len,
						le32_to_cpu(de->inode),
						d_type)) {
					ext2_put_page(page, kaddr);
					return 0;
				}
			}
/*文件指针加上目录项的长度*/
			ctx->pos += ext2_rec_len_from_disk(de->rec_len);
		}
		ext2_put_page(page, kaddr);
	}
	return 0;
}

/*
 *	ext2_find_entry()
 *在指定目录中查找具有所需名称的项。它返回找到目录的页面(作为参数- res_page)和目录本身。页面返回映射并解锁。目录保证有效。
 *
 *成功时,应该在*res_page上调用ext2_put_page()。
 * 
 *注意:对ext2_get_page()/ext2_put_page()的调用必须根据kmap_local_page()/kunmap_local()中记录的规则进行嵌套。
 * 
 *Ext2_find_entry()和ext2_dotdot()作为对ext2_get_page()的调用,应该作为对ext2_get_page()的调用来处理,以达到嵌套的目的。
 *
 */
struct ext2_dir_entry_2 *ext2_find_entry (struct inode *dir,
			const struct qstr *child, struct page **res_page,
			void **res_page_addr)
{
	const char *name = child->name;
	int namelen = child->len;
/*rec_len是这个名字对应的目录项的大小,按字节算*/
	unsigned reclen = EXT2_DIR_REC_LEN(namelen);
	unsigned long start, n;
	unsigned long npages = dir_pages(dir);/*这个inode有多少个页*/
	struct page *page = NULL;
	struct ext2_inode_info *ei = EXT2_I(dir);
	ext2_dirent * de;
	void *page_addr;
/*空目录,直接返回*/
	if (npages == 0)
		goto out;

	/* OFFSET_CACHE */
	*res_page = NULL;
	*res_page_addr = NULL;
/*i_dir_start_lookup就是目录项在内存的开始寻找块编号*/
	start = ei->i_dir_start_lookup;
	if (start >= npages)
		start = 0;
	n = start;
	do {
		char *kaddr;
/*获得页号*/
		page = ext2_get_page(dir, n, 0, &page_addr);
/*如果失败,返回错误页*/
		if (IS_ERR(page))
			return ERR_CAST(page);
/*否则转化成虚拟地址,然后转成ext2目录项指针类型*/
		kaddr = page_addr;
		de = (ext2_dirent *) kaddr;
/*kaddr是边界地址*/
		kaddr += ext2_last_byte(dir, n) - reclen;
		while ((char *) de <= kaddr) {
/*rec_len为0则释放页,返回*/
			if (de->rec_len == 0) {
				ext2_error(dir->i_sb, __func__,
					"zero-length directory entry");
				ext2_put_page(page, page_addr);
				goto out;
			}
/*判断名字是不是一致,如果一致,跳转到found*/
			if (ext2_match(namelen, name, de))
				goto found;
/*否则就去找下一个*/
			de = ext2_next_entry(de);
		}
		ext2_put_page(page, page_addr);
/*如果已经找到最后页,再从第一页开始找*/
		if (++n >= npages)
			n = 0;
		/*下一页已经经过了文件的总块数目*/
		if (unlikely(n > (dir->i_blocks >> (PAGE_SHIFT - 9)))) {
			ext2_error(dir->i_sb, __func__,
				"dir %lu size %lld exceeds block count %llu",
				dir->i_ino, dir->i_size,
				(unsigned long long)dir->i_blocks);
			goto out;
		}
	} while (n != start);
out:
	return ERR_PTR(-ENOENT);

found:
/*找到了,res_page就是得到目录项所在的缓冲区,然后ei->i_dir_start_lookup标记上次寻找到的页编号*/
	*res_page = page;
	*res_page_addr = page_addr;
	ei->i_dir_start_lookup = n;
	return de;
}

/**
 * 返回的. .'目录项和找到项的页面(作为参数- p)。
 *
 *成功时,应该在*p上调用ext2_put_page().
 *
 * 注意:对ext2_get_page()/ext2_put_page()的调用必须根据kmap_local_page()/kunmap_local()中记录的规则进行嵌套。
 *
 * Ext2_find_entry()和ext2_dotdot()作为对ext2_get_page()的调用,应该作为对ext2_get_page()的调用来处理,以达到嵌套的目的。
 */
struct ext2_dir_entry_2 *ext2_dotdot(struct inode *dir, struct page **p,
				     void **pa)
{
	void *page_addr;
	struct page *page = ext2_get_page(dir, 0, 0, &page_addr);
	ext2_dirent *de = NULL;
/*页正确的话*/
	if (!IS_ERR(page)) {
		de = ext2_next_entry((ext2_dirent *) page_addr);
		*p = page;
		*pa = page_addr;
	}
	return de;
}
/*通过目录的文件名获得这个文件的inode编号,dir是目录的inode,dentry是文件的dentry结构体*/
int ext2_inode_by_name(struct inode *dir, const struct qstr *child, ino_t *ino)
{
	struct ext2_dir_entry_2 *de;
	struct page *page;
	void *page_addr;
	/*获得目录项结构体*/
	de = ext2_find_entry(dir, child, &page, &page_addr);
	if (IS_ERR(de))
		return PTR_ERR(de);
/*如果没有出错,ino得到inode编号*/
	*ino = le32_to_cpu(de->inode);
	ext2_put_page(page, page_addr);
	return 0;
}
/* 对于数据写入的调用函数 */
static int ext2_prepare_chunk(struct page *page, loff_t pos, unsigned len)
{
	return __block_write_begin(page, pos, len, ext2_get_block);
}
/* ext2里把目录项列表的一个项变成inode文件指向的文件 */
void ext2_set_link(struct inode *dir, struct ext2_dir_entry_2 *de,
		   struct page *page, void *page_addr, struct inode *inode,
		   int update_times)
{
/*我们应该在哪里开始找到它,即页偏移量*/
	loff_t pos = page_offset(page) +
			(char *) de - (char *) page_addr;
	unsigned len = ext2_rec_len_from_disk(de->rec_len);
	int err;

	lock_page(page);
/*从pos刷新页面,并且长度为len*/
	err = ext2_prepare_chunk(page, pos, len);
	BUG_ON(err);
	de->inode = cpu_to_le32(inode->i_ino);
	ext2_set_de_type(de, inode);
	err = ext2_commit_chunk(page, pos, len);
	if (update_times)
/*更新页面写入元数据的时间*/
		dir->i_mtime = dir->i_ctime = current_time(dir);
	EXT2_I(dir)->i_flags &= ~EXT2_BTREE_FL;
/*time被更新,它的索引节点被标记为dirty*/
	mark_inode_dirty(dir);
}

/*父节点被锁定。*/
/*ext2增加目录对于文件的连接,就是把一个文件放到目录里,dentry是要放入的文件的dentry结构体,inode是要放的文件*/
int ext2_add_link (struct dentry *dentry, struct inode *inode)
{
/*要放入的目录的inode、文件名和名字长度*/
	struct inode *dir = d_inode(dentry->d_parent);
	const char *name = dentry->d_name.name;
	int namelen = dentry->d_name.len;
/*块大小、目录项结构体大小*/
	unsigned chunk_size = ext2_chunk_size(dir);
	unsigned reclen = EXT2_DIR_REC_LEN(namelen);

	unsigned short rec_len, name_len;
	struct page *page = NULL;
	void *page_addr = NULL;
	ext2_dirent * de;
	unsigned long npages = dir_pages(dir);/*这个文件的页数目*/
	unsigned long n;
	loff_t pos;
	int err;

	/*
	 * 我们在同一个循环中处理目录展开。这段代码运行在i_size之外,因此它锁定页面以保护该区域。
	 */
	for (n = 0; n <= npages; n++) {
		char *kaddr;
		char *dir_end;

		page = ext2_get_page(dir, n, 0, &page_addr);
		err = PTR_ERR(page);
		if (IS_ERR(page))
			goto out;
		lock_page(page);
/*页转化成为虚拟地址*/
		kaddr = page_addr;
/*dir_end得到的值是页内的最后一个合法字节在的偏移位置*/
		dir_end = kaddr + ext2_last_byte(dir, n);
/*de指向开始的地址*/
		de = (ext2_dirent *)kaddr;
/*使kaddr指向最后,并且预留下足够存放要加入的目录项的空间*/
		kaddr += PAGE_SIZE - reclen;
		while ((char *)de <= kaddr) {
			if ((char *)de == dir_end) {
				/* We hit i_size */
				name_len = 0;
				rec_len = chunk_size;
				de->rec_len = ext2_rec_len_to_disk(chunk_size);
				de->inode = 0;
				goto got_it;
			}
/*rec_len == 0说明在IO读写的时候出现错误,释放锁,跳出*/
			if (de->rec_len == 0) {
				ext2_error(dir->i_sb, __func__,
					"zero-length directory entry");
				err = -EIO;
				goto out_unlock;
			}
			err = -EEXIST;
/*发现目录内已经有和要加入的目录项名字一样的,退出*/
			if (ext2_match (namelen, name, de))
				goto out_unlock;
/*name_len是当前到的目录项应该的rec_len,rec_len是当前项的记录的rec_len,因为可能后边的目录项被删除了,使得这两个字段不一样*/
			name_len = EXT2_DIR_REC_LEN(de->name_len);
			rec_len = ext2_rec_len_from_disk(de->rec_len);
/*如果当前的目录项inode号是0说明已经被删除了,并且rec_len大于reclen,说明空间也足够,跳转到找到了*/
			if (!de->inode && rec_len >= reclen)
				goto got_it;
/*如果rec_len比本目录项的空间加上要添加的空间还大,说明后边的空间足够插入一个我们想要插入的目录项,跳转到找到了*/
			if (rec_len >= name_len + reclen)
				goto got_it;
/*加上rec_len就是找下一项*/
			de = (ext2_dirent *) ((char *) de + rec_len);
		}
		unlock_page(page);
		ext2_put_page(page, page_addr);
	}
	BUG();
	return -EINVAL;

got_it:
	pos = page_offset(page) +
		(char *)de - (char *)page_addr;
/*要向页缓冲写入数据*/
	err = ext2_prepare_chunk(page, pos, rec_len);
	if (err)
		goto out_unlock;
/*如果inode不为0,说明当前目录项不是空的,但是这个目录项的后边有空间*/
	if (de->inode) {

/*de1指向后边的空间*/
		ext2_dirent *de1 = (ext2_dirent *) ((char *) de + name_len);
/*赋值新的目录项rec_len,更新原来的目录项*/
		de1->rec_len = ext2_rec_len_to_disk(rec_len - name_len);
		de->rec_len = ext2_rec_len_to_disk(name_len);
		de = de1;
	}
/*开始逐个赋值,名字长度,文件名,inode编号*/
	de->name_len = namelen;
	memcpy(de->name, name, namelen);
	de->inode = cpu_to_le32(inode->i_ino);
	ext2_set_de_type (de, inode);
/*提交修改*/
	err = ext2_commit_chunk(page, pos, rec_len);
/*目录的inode修改时间更正*/
	dir->i_mtime = dir->i_ctime = current_time(dir);
	EXT2_I(dir)->i_flags &= ~EXT2_BTREE_FL;
	mark_inode_dirty(dir);
	/* OFFSET_CACHE */
out_put:
	ext2_put_page(page, page_addr);
out:
	return err;
out_unlock:
	unlock_page(page);
	goto out_put;
}

/*
 *Ext2_delete_entry通过将一个目录项与前一个目录项合并来删除它。页面是最新的。
 */
int ext2_delete_entry (struct ext2_dir_entry_2 *dir, struct page *page,
			char *kaddr)
{
	struct inode *inode = page->mapping->host;
/*from是dir目录项在的页对应的块的开始,文件系统的块大小和page大小不一样,to是要删除的目录项的结尾*/
	unsigned from = ((char*)dir - kaddr) & ~(ext2_chunk_size(inode)-1);
	unsigned to = ((char *)dir - kaddr) +
				ext2_rec_len_from_disk(dir->rec_len);
	loff_t pos;
	ext2_dirent * pde = NULL;
/*de指向要删除的目录项所在块的开始*/
	ext2_dirent * de = (ext2_dirent *) (kaddr + from);
	int err;

	while ((char*)de < (char*)dir) {
		if (de->rec_len == 0) {
			ext2_error(inode->i_sb, __func__,
				"zero-length directory entry");
			err = -EIO;
			goto out;
		}
		pde = de;
		de = ext2_next_entry(de);
	}
/*pde是要删除的目录项前边的一个,from就是它的页内偏移*/
	if (pde)
		from = (char *)pde - kaddr;
	pos = page_offset(page) + from;
	lock_page(page);
/*预写入*/
	err = ext2_prepare_chunk(page, pos, to - from);
	BUG_ON(err);
	if (pde)
/*前边项的rec_len增加,就代表后边的项删除*/
		pde->rec_len = ext2_rec_len_to_disk(to - from);
	dir->inode = 0;
/*提交修改*/
	err = ext2_commit_chunk(page, pos, to - from);
	inode->i_ctime = inode->i_mtime = current_time(inode);
	EXT2_I(inode)->i_flags &= ~EXT2_BTREE_FL;
	mark_inode_dirty(inode);
out:
	return err;
}

/*
 *设置目录的第一个片段。把当前目录项列表设置为空,就是说列表里只有./和../两个项,inode是要修改的目录项
 */
int ext2_make_empty(struct inode *inode, struct inode *parent)
{
	struct page *page = grab_cache_page(inode->i_mapping, 0);
	unsigned chunk_size = ext2_chunk_size(inode);
	struct ext2_dir_entry_2 * de;
	int err;
	void *kaddr;

	if (!page)
		return -ENOMEM;

	err = ext2_prepare_chunk(page, 0, chunk_size);
	if (err) {
		unlock_page(page);
		goto fail;
	}
/*先修改第一个./目录,kaddr是页开始虚拟地址*/
	kaddr = kmap_atomic(page);
/*吧页内全部变成0*/
	memset(kaddr, 0, chunk_size);
	de = (struct ext2_dir_entry_2 *)kaddr;
/*设置./目录的值,依次是名称长度,rec_len,名称,inode号码,文件类	型*/
	de->name_len = 1;
	de->rec_len = ext2_rec_len_to_disk(EXT2_DIR_REC_LEN(1));
	memcpy (de->name, ".\0\0", 4);
	de->inode = cpu_to_le32(inode->i_ino);
	ext2_set_de_type (de, inode);
/*设置../的值,先把de指向第二个要写入的项*/
	de = (struct ext2_dir_entry_2 *)(kaddr + EXT2_DIR_REC_LEN(1));
	de->name_len = 2;
	de->rec_len = ext2_rec_len_to_disk(chunk_size - EXT2_DIR_REC_LEN(1));
	de->inode = cpu_to_le32(parent->i_ino);
	memcpy (de->name, "..\0", 4);
	ext2_set_de_type (de, inode);
/*释放页缓冲区*/
	kunmap_atomic(kaddr);
/*提交修改*/
	err = ext2_commit_chunk(page, 0, chunk_size);
fail:
	put_page(page);
	return err;
}

/*
 *例程检查指定的目录是否为空(对于rmdir)
 */
int ext2_empty_dir (struct inode * inode)
{
	void *page_addr = NULL;
	struct page *page = NULL;
	unsigned long i, npages = dir_pages(inode);/*npages是inode的页数目*/
	int dir_has_error = 0;

	for (i = 0; i < npages; i++) {
		char *kaddr;
		ext2_dirent * de;
/*获得遍历到的当前页*/
		page = ext2_get_page(inode, i, dir_has_error, &page_addr);
/*检查获得的是不是坏的*/
		if (IS_ERR(page)) {
			dir_has_error = 1;
			continue;
		}
/*获得页的虚拟地址,转化成目录项指针类型*/
		kaddr = page_addr;
		de = (ext2_dirent *)kaddr;
/*kaddr指向结尾边界*/
		kaddr += ext2_last_byte(inode, i) - EXT2_DIR_REC_LEN(1);

		while ((char *)de <= kaddr) {
			if (de->rec_len == 0) {
				ext2_error(inode->i_sb, __func__,
					"zero-length directory entry");
				printk("kaddr=%p, de=%p\n", kaddr, de);
				goto not_empty;
			}
/*如果inode编号不是0并且还不是./和../目录说明不为空*/
			if (de->inode != 0) {
				/* check for . and .. */
				if (de->name[0] != '.')
					goto not_empty;
				if (de->name_len > 2)
					goto not_empty;
				if (de->name_len < 2) {
					if (de->inode !=
					    cpu_to_le32(inode->i_ino))
						goto not_empty;
				} else if (de->name[1] != '.')
					goto not_empty;
			}
/*下一个目录项*/
			de = ext2_next_entry(de);
		}

/*下一页*/
		ext2_put_page(page, page_addr);
	}
	return 1;

not_empty:
	ext2_put_page(page, page_addr);
	return 0;
}
/*ext2的目录节点的操作函数结构体*/
const struct file_operations ext2_dir_operations = {
	.llseek		= generic_file_llseek,
	.read		= generic_read_dir,
	.iterate_shared	= ext2_readdir,
	.unlocked_ioctl = ext2_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= ext2_compat_ioctl,
#endif
	.fsync		= ext2_fsync,
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值