ext2文件系统源代码之dir.c

来源:互联网 发布:mac系统windows虚拟机 编辑:程序博客网 时间:2024/05/20 06:28
今天我们来看ext2源代码的dir.c文件,这个文件是做和目录有关的操作的,也是很重要的一个文件,大家认真看哦,有什么和我不一致的大家一起探讨~~
在讲这个之前我觉得还是先说下ext2文件系统的目录方面的基础比较好,这样会更容易看明白。
ext2文件系统分为inode和block,inode是索引,block是数据块,那我们怎么定义目录呢?其实,目录其实也是文件的一种,只不过是目录的内容是目录内的文件信息,目录也有inode,而目录的inode索引导向的block内的内容就是一个一个的目录内文件信息结构体,就是ext2_dir_entry_2,新版的ext2目录结构体,定义如下
struct ext2_dir_entry_2 {__le32inode;/* Inode号*/__le16rec_len;/*本目录项的长度*/__u8name_len;/*文件名长度 */__u8file_type;/*这个文件的文件类型*/charname[EXT2_NAME_LEN];/*文件名*/};


rec_len代表当前目录项的长度,这个字段比较方便的一点是,当删除这个目录项的时候,只需要把上一个的目录项的rec_len加上要删除的目录项的长度就可以了,而不需要把删除的节点的后边的目录项一个一个的向前对齐,这样就节省了IO开销。
file_type字段的值,也就是文件类型,有以下几种
enum {         EXT2_FT_UNKNOWN,      /*未知*/         EXT2_FT_REG_FILE,     /*常规文件*/         EXT2_FT_DIR,     /*目录文件*/         EXT2_FT_CHRDEV,       /*字符设备文件*/         EXT2_FT_BLKDEV,       /*块设备文件*/         EXT2_FT_FIFO,    /*命名管道文件*/         EXT2_FT_SOCK,    /*套接字文件*/         EXT2_FT_SYMLINK, /*符号连文件*/         EXT2_FT_MAX      /*文件类型的最大个数*/};


还有EXT2_DIR_REC_LEN宏,ext2的目录项规定大小必须是4的倍数,EXT2_DIR_REC_LEN宏的参数是文件名的长度,我们看一下宏的定义
/
* * EXT2_DIR_PAD defines the directory entries boundaries * * NOTE: It must be a multiple of 4 */#define EXT2_DIR_PAD 4#define EXT2_DIR_ROUND (EXT2_DIR_PAD - 1)#define EXT2_DIR_REC_LEN(name_len)(((name_len) + 8 + EXT2_DIR_ROUND) & \ ~EXT2_DIR_ROUND)


前边的8大家不明白的话,可以看ext2_dir_entry_2的实现,前边的几个变量加起来就是8个字节,再加上名称长度就得到了目录项的理论长度,加上EXT2_DIR_ROUND,就是3,与3,得到边界按照四对齐的大小
那么开始吧!
/* *  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) * 法国最好的大学,法国巴黎第六大学的Remy Card大神,内核ext2的好多代码都是他写的 *  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/pagemap.h>typedef struct ext2_dir_entry_2 ext2_dirent;/*返回inode结构体所属的文件系统的块大小字节数*/static inline unsigned ext2_chunk_size(struct inode *inode){return inode->i_sb->s_blocksize;}/*释放申请的页*/static inline void ext2_put_page(struct page *page){kunmap(page);page_cache_release(page);}/*返回一个文件占用的页的数目*/static inline unsigned long dir_pages(struct inode *inode){/*inode->i_size是文件大小的字节数,PAGE_CACHE_SIZE是缓存页的大小,PAGE_CACHE_SHIFT是缓存页大小换算成二进制位有多少位,这样的算法得到的就是inode对应的文件的占用缓存页的数目*/return (inode->i_size+PAGE_CACHE_SIZE-1)>>PAGE_CACHE_SHIFT;}/*返回inode对应的文件的页号为page_nr的最后一个合法的字节的位置,再加一,提示:页号从零开始*/static unsignedext2_last_byte(struct inode *inode, unsigned long page_nr){/*先获得文件字节大小*/unsigned last_byte = inode->i_size;/*减去前边的页的字节数,page_nr << PAGE_CACHE_SHIFT就等于page_nr乘上页大小*/last_byte -= page_nr << PAGE_CACHE_SHIFT;/*如果page_nr不是最后一页,就返回当前页的最后一个字节位置加一*/if (last_byte > PAGE_CACHE_SIZE)last_byte = PAGE_CACHE_SIZE;return last_byte;}/*把page页缓存上的from到to的字节修改提交上去*/static int ext2_commit_chunk(struct page *page, unsigned from, unsigned to){/*找到这个缓冲区的拥有着*/struct inode *dir = page->mapping->host;int err = 0;dir->i_version++;/*调用页缓冲区的函数把修改提交*/page->mapping->a_ops->commit_write(NULL, page, from, to);/*如果标志上要求写入立刻同步,就同步,否则释放此页*/if (IS_DIRSYNC(dir))err = write_one_page(page, 1);elseunlock_page(page);return err;}/*检验页有没有错误*/static void ext2_check_page(struct page *page){/*dir是页的主人*/struct inode *dir = page->mapping->host;/*sb是dir的文件系统vfs层的超级块*/struct super_block *sb = dir->i_sb;/*chunk_size是文件大小*/unsigned chunk_size = ext2_chunk_size(dir);/*返回页的虚拟地址*/char *kaddr = page_address(page);/*文件系统总的inode数目*/u32 max_inumber = le32_to_cpu(EXT2_SB(sb)->s_es->s_inodes_count);unsigned offs, rec_len;/*limit是页缓存的大小*/unsigned limit = PAGE_CACHE_SIZE;ext2_dirent *p;char *error;/*文件大小右移PAGE_CACHE_SHIFT位得到的是文件的最后一个缓存页的号码,如果等于page的index,就是说page就是文件的最后一部分对应的缓存页,并且文件都在缓冲区里*/if ((dir->i_size >> PAGE_CACHE_SHIFT) == page->index) {/*limit得到文件大小最后一个页的页内偏移*/limit = dir->i_size & ~PAGE_CACHE_MASK;/*如果不为零,说明这个文件目录不是一个完整的块大小的倍数,文件可能不是块大小的倍数,但是文件目录必定是块大小的倍数,这里就返回大小错误*/if (limit & (chunk_size - 1))goto Ebadsize;/*如果limit为0说明没问题*/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 = le16_to_cpu(p->rec_len);/*当前项至少大于这个,如果小于,说明有问题,返回错误*/if (rec_len < EXT2_DIR_REC_LEN(1))goto Eshort;/*规定多有的目录项边界与4对齐,这说明没有对齐,返回没对齐的错误*/if (rec_len & 3)goto Ealign;/*rec_len和文件名大小不一致*/if (rec_len < EXT2_DIR_REC_LEN(p->name_len))goto Enamelen;/*大小超出当前块了,说明rec_len有问题*/if (((offs + rec_len - 1) ^ offs) & ~(chunk_size-1))goto Espan;/*目录项的inode编号大于inode的最大编号,编号错误*/if (le32_to_cpu(p->inode) > max_inumber)goto Einumber;}/*说明目录项没有和块边界对齐*/if (offs != limit)goto Eend;out:/*page的flags有一个位标记这个page已经被检查过了,这里标记位为1*/SetPageChecked(page);return;/* 解决问题 */Ebadsize:/*目录的inode指向的文件大小不合法,打印这个块的大小不是块大小的倍数*/ext2_error(sb, "ext2_check_page","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:/*目录项坏*/ext2_error (sb, "ext2_check_page", "bad entry in directory #%lu: %s - ""offset=%lu, inode=%lu, rec_len=%d, name_len=%d",dir->i_ino, error, (page->index<<PAGE_CACHE_SHIFT)+offs,(unsigned long) le32_to_cpu(p->inode),rec_len, p->name_len);goto fail;Eend: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_CACHE_SHIFT)+offs,(unsigned long) le32_to_cpu(p->inode));fail:/*标记这个page标记过,但是有错误*/SetPageChecked(page);SetPageError(page);}/*从页缓存得到目录的inode的第n页数据*/static struct page * ext2_get_page(struct inode *dir, unsigned long n){/*从目录的inode获得地址空间结构体*/struct address_space *mapping = dir->i_mapping;/*从地址空间读取第n页*/struct page *page = read_mapping_page(mapping, n, NULL);/*如果读取成功了*/if (!IS_ERR(page)) {/*映射后检查页*/kmap(page);if (!PageChecked(page))ext2_check_page(page);/*如果这个页有错误,就跳转到fail*/if (PageError(page))goto fail;}return page;fail:/*有错误的页要释放掉,返回IO错误号码*/ext2_put_page(page);return ERR_PTR(-EIO);}/*ext2的字符串对比函数,和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至少是页边界的前六个字节之前,这个函数返回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 + le16_to_cpu(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;}/*ext2文件系统的文件类型表,上边我们说过的*/static unsigned char ext2_filetype_table[EXT2_FT_MAX] = {[EXT2_FT_UNKNOWN]= DT_UNKNOWN, /*未知*/[EXT2_FT_REG_FILE]= DT_REG,     /*常规文件*/[EXT2_FT_DIR]= DT_DIR,     /*目录文件*/[EXT2_FT_CHRDEV]= DT_CHR,     /*字符设备文件*/[EXT2_FT_BLKDEV]= DT_BLK,     /*块设备文件*/[EXT2_FT_FIFO]= DT_FIFO,    /*命名管道文件*/[EXT2_FT_SOCK]= DT_SOCK,    /*套接字文件*/[EXT2_FT_SYMLINK]= DT_LNK,     /*符号连文件*/};/*S_SHIFT宏是位的偏移,S_IFREG等宏的位都在12位以后,这个结构体方便通过文件的模式,mode字段获得文件类型*/#define S_SHIFT 12static unsigned char ext2_type_by_mode[S_IFMT >> S_SHIFT] = {[S_IFREG >> S_SHIFT]= EXT2_FT_REG_FILE,[S_IFDIR >> S_SHIFT]= EXT2_FT_DIR,[S_IFCHR >> S_SHIFT]= EXT2_FT_CHRDEV,[S_IFBLK >> S_SHIFT]= EXT2_FT_BLKDEV,[S_IFIFO >> S_SHIFT]= EXT2_FT_FIFO,[S_IFSOCK >> S_SHIFT]= EXT2_FT_SOCK,[S_IFLNK >> S_SHIFT]= EXT2_FT_SYMLINK,};/*设置目录项的类型*/static inline void ext2_set_de_type(ext2_dirent *de, struct inode *inode){/*获得目录项的模式*/mode_t mode = inode->i_mode;/*检查EXT2_FEATURE_INCOMPAT_FILETYPE位,如果为1,就根据mode赋值文件类型,否则置为0,就是未知文件类型*/if (EXT2_HAS_INCOMPAT_FEATURE(inode->i_sb, EXT2_FEATURE_INCOMPAT_FILETYPE))de->file_type = ext2_type_by_mode[(mode & S_IFMT)>>S_SHIFT];elsede->file_type = 0;}/*读取文件的目录内容,filp是要读取得文件指针,dirent是读取出来存放的缓冲区,filldir是把读取出来的数据按照不同的格式存放在dirent缓冲区里的方法*/static intext2_readdir (struct file * filp, void * dirent, filldir_t filldir){/*先得到文件的偏移*/loff_t pos = filp->f_pos;/*获得目录的inode*/struct inode *inode = filp->f_path.dentry->d_inode;struct super_block *sb = inode->i_sb;/*pos与上缓存块的掩码得到了offset,得到的是在块内的偏移*/unsigned int offset = pos & ~PAGE_CACHE_MASK;/*n得到的是当前读取到的页编号*/unsigned long n = pos >> PAGE_CACHE_SHIFT;/*npages是文件占用的页数目*/unsigned long npages = dir_pages(inode);unsigned chunk_mask = ~(ext2_chunk_size(inode)-1);unsigned char *types = NULL;/*如果filp->f_version和inode->i_version不一致,就需要检验,这version记录文件的版本号,每次使用后都会加一,不一致就说明有可能内容不一样*/int need_revalidate = filp->f_version != inode->i_version;/*检验pos值是不是超出限制了*/if (pos > inode->i_size - EXT2_DIR_REC_LEN(1))return 0;/*如果ext2文件系统有incompt_feature字段,先把types指针指向文件类型表*/if (EXT2_HAS_INCOMPAT_FEATURE(sb, EXT2_FEATURE_INCOMPAT_FILETYPE))types = ext2_filetype_table;/*遍历从当前读到的页到最后的页*/for ( ; n < npages; n++, offset = 0) {char *kaddr, *limit;ext2_dirent *de;/*前边讲过的函数,根据inode和页编号得到这个inode的第n页*/struct page *page = ext2_get_page(inode, n);/*如果值有错,报错,并把f_pos指向下一页*/if (IS_ERR(page)) {ext2_error(sb, __FUNCTION__,   "bad page in #%lu",   inode->i_ino);filp->f_pos += PAGE_CACHE_SIZE - offset;return -EIO;}/*获得page的虚拟地址*/kaddr = page_address(page);/*如果需要检验的话*/if (unlikely(need_revalidate)) {/*如果块内偏移不是0,就检验偏移的值,并且新的合法的偏移值赋给f_pos*/if (offset) {offset = ext2_validate_entry(kaddr, offset, chunk_mask);filp->f_pos = (n<<PAGE_CACHE_SHIFT) + offset;}/*保持版本号一致,不需要检验*/filp->f_version = inode->i_version;need_revalidate = 0;}/*根据得到的缓冲区,指向ext2的目录项结构体*/de = (ext2_dirent *)(kaddr+offset);/*ext2_last_byte函数我们上边讲过,就是页内最后一个合法的字节,得到的limit解释页内的合法的边界*/limit = kaddr + ext2_last_byte(inode, n) - EXT2_DIR_REC_LEN(1);/*遍历页内的每一个目录项,ext2_next_entry函数我们之前也讲过,下一个目录项*/for ( ;(char*)de <= limit; de = ext2_next_entry(de)) {/*0长度的目录项是不合法的,返回IO错误,释放当前页*/if (de->rec_len == 0) {ext2_error(sb, __FUNCTION__,"zero-length directory entry");ext2_put_page(page);return -EIO;}/*如果inode号不为0,(为0说明这个项已经被删除了,直接跳过)*/if (de->inode) {int over;unsigned char d_type = DT_UNKNOWN;/*d_type从目录项里得到文件类型*/if (types && de->file_type < EXT2_FT_MAX)d_type = types[de->file_type];/*使用传进来的filldir函数来填充dirent缓冲区*/offset = (char *)de - kaddr;over = filldir(dirent, de->name, de->name_len,(n<<PAGE_CACHE_SHIFT) | offset,le32_to_cpu(de->inode), d_type);/*如果写超出内存边界,释放页并返回*/if (over) {ext2_put_page(page);return 0;}}/*文件指针加上目录项的长度*/filp->f_pos += le16_to_cpu(de->rec_len);}/*读完以后要释放*/ext2_put_page(page);}return 0;}/*ext2文件系统在一个给定的目录内寻找一个目录项,返回的目录项保证是合法的,参数page得到的是目录项被找到的缓冲区,*/struct ext2_dir_entry_2 * ext2_find_entry (struct inode * dir,struct dentry *dentry, struct page ** res_page){/*name就是目录名,namelen名字长度*/const char *name = dentry->d_name.name;int namelen = dentry->d_name.len;/*EXT2_DIR_REC_LEN宏前边讲过,rec_len就是这个名字对应的目录项的大小,按字节算*/unsigned reclen = EXT2_DIR_REC_LEN(namelen);unsigned long start, n;/*这个inode有多少个页*/unsigned long npages = dir_pages(dir);struct page *page = NULL;/*从inode得到ext2_inode_info结构体,内存里的ext2_inode_info保存ext2的一些信息*/struct ext2_inode_info *ei = EXT2_I(dir);ext2_dirent * de;/*空目录,直接返回*/if (npages == 0)goto out;/* 先把它赋值为NULL */*res_page = NULL;/*i_dir_start_lookup就是目录项在内存的开始寻找块编号*/start = ei->i_dir_start_lookup;if (start >= npages)start = 0;n = start;do {/*ext2_get_page函数,之前讲过,获得第n页*/char *kaddr;page = ext2_get_page(dir, n);/*如果成功了的话*/if (!IS_ERR(page)) {/*转化成虚拟地址,然后转成ext2目录项指针类型*/kaddr = page_address(page);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, __FUNCTION__,"zero-length directory entry");ext2_put_page(page);goto out;}/*ext2_match之前讲过,判断名字是不是一致,如果一致,跳转到found*/if (ext2_match (namelen, name, de))goto found;/*没找到就去找下一个*/de = ext2_next_entry(de);}ext2_put_page(page);}/*如果已经找到最后页,再从第一页开始找*/if (++n >= npages)n = 0;/* 下一页超过了文件的总块数目,不过这基本不可能 */if (unlikely(n > (dir->i_blocks >> (PAGE_CACHE_SHIFT - 9)))) {ext2_error(dir->i_sb, __FUNCTION__,"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 NULL;found:/*找到了,res_page就是得到目录项所在的缓冲区*/*res_page = page;/*ei->i_dir_start_lookup标记上次寻找到的页编号*/ei->i_dir_start_lookup = n;return de;}/*获得dir目录所在的../目录项,p获得../目录项所在的页*/struct ext2_dir_entry_2 * ext2_dotdot (struct inode *dir, struct page **p){/*之前讲过,这个函数获得dir目录的第0页*/struct page *page = ext2_get_page(dir, 0);ext2_dirent *de = NULL;/*页正确的话*/if (!IS_ERR(page)) {/*目录项列表中,第一个是./,而下一个就是../*/de = ext2_next_entry((ext2_dirent *) page_address(page));*p = page;}return de;}/*通过目录的文件名获得这个文件的inode编号,dir是目录的inode,dentry是文件的dentry结构体*/ino_t ext2_inode_by_name(struct inode * dir, struct dentry *dentry){ino_t res = 0;struct ext2_dir_entry_2 * de;struct page *page;/*上边刚讲过的函数,获得目录项结构体*/de = ext2_find_entry (dir, dentry, &page);if (de) {/*res得到inode编号*/res = le32_to_cpu(de->inode);ext2_put_page(page);}return res;}/* ext2里吧目录项列表的一个项变成inode文件指向的文件 */void ext2_set_link(struct inode *dir, struct ext2_dir_entry_2 *de,struct page *page, struct inode *inode){/*首先找到de指向的目录项开始结束偏移地址*/unsigned from = (char *) de - (char *) page_address(page);unsigned to = from + le16_to_cpu(de->rec_len);int err;/*对页缓冲区写入要先锁住,然后调用prepare_write准备都下*/lock_page(page);err = page->mapping->a_ops->prepare_write(NULL, page, from, to);BUG_ON(err);/*赋值inode编号*/de->inode = cpu_to_le32(inode->i_ino);/*之前讲过的函数,写完编号写文件类型*/ext2_set_de_type (de, inode);/*把修改提交*/err = ext2_commit_chunk(page, from, to);/*减少页引用计数*/ext2_put_page(page);/*目录的inode修改时间记录*/dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC;EXT2_I(dir)->i_flags &= ~EXT2_BTREE_FL;/*这个inode已经脏了*/mark_inode_dirty(dir);}/*ext2增加目录对于文件的连接,就是把一个文件放到目录里,dentry是要放入的文件的dentry结构体,inode是要放的文件*/int ext2_add_link (struct dentry *dentry, struct inode *inode){/*要放入的目录的inode*/struct inode *dir = dentry->d_parent->d_inode;/*文件名和名字长度*/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;ext2_dirent * de;/*这个文件的页数目*/unsigned long npages = dir_pages(dir);unsigned long n;char *kaddr;unsigned from, to;int err;/*遍历目录的目录项列表*/for (n = 0; n <= npages; n++) {char *dir_end;/*前边讲过这个函数,获得dir目录的第n页*/page = ext2_get_page(dir, n);/*检查返回结果*/err = PTR_ERR(page);if (IS_ERR(page))goto out;/*页转化成为虚拟地址*/lock_page(page);kaddr = page_address(page);/*dir_end得到的值是页内的最后一盒合法字节在的偏移位置*/dir_end = kaddr + ext2_last_byte(dir, n);/*de指向开始的地址*/de = (ext2_dirent *)kaddr;/*使kaddr指向最后,并且预留下足够存放要加入的目录项的空间*/kaddr += PAGE_CACHE_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 = cpu_to_le16(chunk_size);de->inode = 0;goto got_it;}/*发现了rec_len为0的目录项,说明在IO读写的时候出现错误,释放锁,跳出*/if (de->rec_len == 0) {ext2_error(dir->i_sb, __FUNCTION__,"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 = le16_to_cpu(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);}/*没找到就报BUG*/BUG();return -EINVAL;got_it:/*from和to是表示要加入的目录项在页内的偏移位置开始和结束*/from = (char*)de - (char*)page_address(page);to = from + rec_len;/*要向页缓冲写入的时候都要先调用prepare_write*/err = page->mapping->a_ops->prepare_write(NULL, page, from, to);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 = cpu_to_le16(rec_len - name_len);/*原来的rec_len值变更*/de->rec_len = cpu_to_le16(name_len);/*de指向要写入的目录项地址*/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, from, to);/*目录的inode修改时间更正*/dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC;EXT2_I(dir)->i_flags &= ~EXT2_BTREE_FL;/*写后都要标记脏*/mark_inode_dirty(dir);/* OFFSET_CACHE */out_put:ext2_put_page(page);out:return err;out_unlock:unlock_page(page);goto out_put;}/*ext2删除一个目录项,dir是删除的目录项,page是目录项缓冲区的页,其实删除很简单,就是找到那个目录项,把inode号码变成0,然后把前边那一项的rec_len加上删除项的长度*/int ext2_delete_entry (struct ext2_dir_entry_2 * dir, struct page * page ){struct address_space *mapping = page->mapping;struct inode *inode = mapping->host;/*kaddr是页开始虚拟地址*/char *kaddr = page_address(page);/*from是dir目录项在的页对应的块的开始,文件系统的块大小和page大小不一样*/unsigned from = ((char*)dir - kaddr) & ~(ext2_chunk_size(inode)-1);/*to是要删除的目录项的结尾*/unsigned to = ((char*)dir - kaddr) + le16_to_cpu(dir->rec_len);ext2_dirent * pde = NULL;/*de指向要删除的目录项所在块的开始*/ext2_dirent * de = (ext2_dirent *) (kaddr + from);int err;/*遍历检查所在的块*/while ((char*)de < (char*)dir) {/*0长度的目录不合法,不能忍,直接返回错误*/if (de->rec_len == 0) {ext2_error(inode->i_sb, __FUNCTION__,"zero-length directory entry");err = -EIO;goto out;}pde = de;/*下一项*/de = ext2_next_entry(de);}/*pde是要删除的目录项前边的一个,from就是它的页内偏移*/if (pde)from = (char*)pde - (char*)page_address(page);/*向页内写入之前要锁住的*/lock_page(page);/*预写入*/err = mapping->a_ops->prepare_write(NULL, page, from, to);BUG_ON(err);/*依次写入rec_len和inode号码,rec_len变成原来的前边的项的rec_len加上后边要删除的那个rec_len*/if (pde)/*前边项的rec_len增加,就代表后边的项删除*/pde->rec_len = cpu_to_le16(to-from);/*要删除的目录项inode号变成0*/dir->inode = 0;/*提交修改*/err = ext2_commit_chunk(page, from, to);/*修改时间*/inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC;EXT2_I(inode)->i_flags &= ~EXT2_BTREE_FL;/*标记脏了*/mark_inode_dirty(inode);out:/*释放对这个页的引用*/ext2_put_page(page);return err;}/*把当前目录项列表设置为空,就是说列表里只有./和../两个项,inode是要修改的目录项的所在inode */int ext2_make_empty(struct inode *inode, struct inode *parent){struct address_space *mapping = inode->i_mapping;struct page *page = grab_cache_page(mapping, 0);/*块大小*/unsigned chunk_size = ext2_chunk_size(inode);struct ext2_dir_entry_2 * de;int err;void *kaddr;/*看看得到的页是不是有问题*/if (!page)return -ENOMEM;/*预写入操作*/err = mapping->a_ops->prepare_write(NULL, page, 0, chunk_size);if (err) {unlock_page(page);goto fail;}/*先修改第一个./目录,kaddr是页开始虚拟地址*/kaddr = kmap_atomic(page, KM_USER0);/*吧页内全部变成0*/memset(kaddr, 0, chunk_size);/*de指向第一个项*/de = (struct ext2_dir_entry_2 *)kaddr;/*设置./目录的值,依次是名称长度,rec_len,名称,inode号码,文件类型*/de->name_len = 1;de->rec_len = cpu_to_le16(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));/*设置../目录的值,依次是名称长度,rec_len,名称,inode号码,文件类型*/de->name_len = 2;de->rec_len = cpu_to_le16(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, KM_USER0);/*提交修改*/err = ext2_commit_chunk(page, 0, chunk_size);fail:/*失败了就释放页缓存*/page_cache_release(page);return err;}/*检查一个目录是不是空的,inode是要检查的目录的inode结构体 */int ext2_empty_dir (struct inode * inode){struct page *page = NULL;/*npages是inode的页数目*/unsigned long i, npages = dir_pages(inode);/*遍历目录的每一个页*/for (i = 0; i < npages; i++) {char *kaddr;ext2_dirent * de;/*获得遍历到的当前页*/page = ext2_get_page(inode, i);/*检查获得的是不是坏的*/if (IS_ERR(page))continue;/*获得页的虚拟地址,转化成目录项指针类型*/kaddr = page_address(page);de = (ext2_dirent *)kaddr;/*kaddr指向结尾边界*/kaddr += ext2_last_byte(inode, i) - EXT2_DIR_REC_LEN(1);/*遍历当前页*/while ((char *)de <= kaddr) {/*如果有rec_len为0的项直接返回不为空*/if (de->rec_len == 0) {ext2_error(inode->i_sb, __FUNCTION__,"zero-length directory entry");printk("kaddr=%p, de=%p\n", kaddr, de);goto not_empty;}/*如果inode编号不是0并且还不是./和../目录说明不为空*/if (de->inode != 0) {/* 检查 . 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);}return 1;not_empty:/*结束时释放当前页*/ext2_put_page(page);return 0;}/*ext2的目录节点的操作函数结构体*/const struct file_operations ext2_dir_operations = {.llseek= generic_file_llseek,.read= generic_read_dir,.readdir= ext2_readdir,.ioctl= ext2_ioctl,#ifdef CONFIG_COMPAT.compat_ioctl= ext2_compat_ioctl,#endif.fsync= ext2_sync_file,};



0 0
原创粉丝点击