linux011文件系统中按照名字查找节点的算法分析

来源:互联网 发布:群发软件 编辑:程序博客网 时间:2024/06/05 01:14
/* #define NO_TRUNCATE */#define MAY_EXEC 1#define MAY_WRITE 2#define MAY_READ 4static int permission(struct m_inode * inode,int mask){int mode = inode->i_mode;/*i节点有设备号,但是链接数为0,说明是空的,为已经删除的节点*/if (inode->i_dev && !inode->i_nlinks)return 0;/**/if (!(current->uid && current->euid))mode=0777;/*如果当前进程的用户id或有效用户id和inode的属主id相同,则取inode属主权限*/else if (current->uid==inode->i_uid || current->euid==inode->i_uid)mode >>= 6;/*如果当前进程的有效组id或组id和inode的组id相同,则取inode的组id权限*/else if (current->gid==inode->i_gid || current->egid==inode->i_gid)mode >>= 3;return mode & mask & 0007;}/*嵌入汇编,对比两个字符串。知道嵌入汇编的使用就可以,具体的汇编参考x86手册*/static int match(int len,const char * name,struct dir_entry * de){register int same __asm__("ax");/*如果de不存在或de为空项,或者比对长度大于最大长度则返回*/if (!de || !de->inode || len > NAME_LEN)return 0;if (len < NAME_LEN && de->name[len])return 0;__asm__("cld\n\t"                  "fs ; repe ; cmpsb\n\t""setz %%al":"=a" (same)     /*输出*//*输入参数,0->0,si->name,di->de->name,cx->len*/:"0" (0),"S" ((long) name),"D" ((long) de->name),"c" (len)/*用到的寄存器*/:"cx","di","si");return same;}/**函数功能:从指定的节点中查找一个指定名字的目录。*参数:dir指定的节点,name指定要查找的目录,namelen长度* 返回值:res_dir指定目录的指针*/static struct buffer_head * find_entry(struct m_inode * dir,const char * name, int namelen, struct dir_entry ** res_dir){int entries;int block,i;struct buffer_head * bh;struct dir_entry * de;/*NO_TRUNCATE定义了宏后,当名字过长也不会截断。*/#ifdef NO_TRUNCATEif (namelen > NAME_LEN)return NULL;#elseif (namelen > NAME_LEN)namelen = NAME_LEN;#endif/*计算目标节点中能有多少个目录项*/entries = dir->i_size / (sizeof (struct dir_entry));*res_dir = NULL;if (!namelen)/*长度为0,则返回*/return NULL;/*查找的目录为 ".." */if(namelen==2 && get_fs_byte(name)=='.' && get_fs_byte(name+1)=='.'){/*如果当前进程的根节点指针是指定目录,则文件名修改为 '.' */if((*dir) == current->root)  namelen=1;/*如果该目录节点号等于ROOT_INO,说明是文件系统的根节点。取文件系统的超级块*/else if((*dir)->i_num == ROOT_INO){/*在一个安装点上的'..'将导致目录切换到安装的文件系统的目录i节点。但是由于设置了mounted标志,因而可以取出该新目录*/sb->get_super((*dir)-<i_dev);/*如果安装到的i节点存在,则释放原i节点,然后对安装到的i节点进行处理。让dir指向被安装到的i节点,该i节点引用书加1*/if(sb->s_imount){input(*dir);(*dir) = sb->s_imount;(*dir)->i_count++;}}}if (!(block = dir->i_zone[0]))/*节点没有数据块,则返回*/return NULL;if (!(bh = bread(dir->i_dev,block))) /*如果读取数据块失败,返回*/return NULL;i = 0;de = (struct dir_entry *) bh->b_data; /*节点表示一个目录,其数据块包含的额都是目录项*//*循环搜索*/while (i < entries) {/*如果当前块已经搜索完毕,还没有搜索到,则释放当前块*/if ((char *)de >= BLOCK_SIZE+bh->b_data) {brelse(bh);bh = NULL;/*读入下一块数据,如果下一块数据为空,则跳过*/if (!(block = bmap(dir,i/DIR_ENTRIES_PER_BLOCK)) ||    !(bh = bread(dir->i_dev,block))) {i += DIR_ENTRIES_PER_BLOCK;continue;}/*指向新读入的数据块,继续寻找*/de = (struct dir_entry *) bh->b_data;}/*如果匹配,则使res_dir指向对应的目录项,并返回一个缓冲区块*/if (match(namelen,name,de)) {*res_dir = de;return bh;}de++;i++;}brelse(bh);return NULL;}/* *函数功能:往指定节点中添加一个目录项 *参数:dir指定的inode,name要添加的目录项名字,namelenn名字长度 * * */static struct buffer_head * add_entry(struct m_inode * dir,const char * name, int namelen, struct dir_entry ** res_dir){int block,i;struct buffer_head * bh;struct dir_entry * de;*res_dir = NULL;#ifdef NO_TRUNCATEif (namelen > NAME_LEN)return NULL;#elseif (namelen > NAME_LEN)namelen = NAME_LEN;#endifif (!namelen)return NULL;if (!(block = dir->i_zone[0]))return NULL;if (!(bh = bread(dir->i_dev,block)))return NULL;i = 0;     /*循环查找一个空项*/de = (struct dir_entry *) bh->b_data;while (1) {/*如果de已经超出当前的块,则重新申请一个新块,申请失败,则返回*/if ((char *)de >= BLOCK_SIZE+bh->b_data) {brelse(bh);bh = NULL;block = create_block(dir,i/DIR_ENTRIES_PER_BLOCK);if (!block)return NULL;if (!(bh = bread(dir->i_dev,block))) {i += DIR_ENTRIES_PER_BLOCK;continue;}/*指向新的数据块*/de = (struct dir_entry *) bh->b_data;}/*如果超出当前节点的大小,就修改节点大小和时间,以及更新修改标志*/if (i*sizeof(struct dir_entry) >= dir->i_size) {de->inode=0;dir->i_size = (i+1)*sizeof(struct dir_entry);dir->i_dirt = 1;dir->i_ctime = CURRENT_TIME;}/*如果找到一个空项(de->inode==0,说明是空项),然后修改该目录的修改时间,并添加指定name的目录到这个项中*/if (!de->inode) {dir->i_mtime = CURRENT_TIME;for (i=0; i < NAME_LEN ; i++)de->name[i]=(i<namelen)?get_fs_byte(name+i):0;bh->b_dirt = 1;*res_dir = de;return bh;}de++;i++;}brelse(bh);return NULL;}/* 函数功能:返回顶层目录的节点指针 *参数:pathname要查找目录的路径 */static struct m_inode * get_dir(const char * pathname){char c;const char * thisname;struct m_inode * inode;struct buffer_head * bh;int namelen,inr,idev;struct dir_entry * de;/*如果当前进程的根目录为空或者根目录引用数为0,则出错*/if (!current->root || !current->root->i_count)panic("No root inode");/*如果当前进程的当前目录或当前目录的引用数为0,则出错*/if (!current->pwd || !current->pwd->i_count)panic("No cwd inode");/*如果路径中第一符号为'/'则是绝对路径,否则就是相对路径*/if ((c=get_fs_byte(pathname))=='/') {inode = current->root;pathname++;} else if (c)inode = current->pwd;elsereturn NULL;/* empty name is bad */inode->i_count++;while (1) {thisname = pathname;/*如果当前节点不是目录,或没有执行权限就释放节点,返回*/if (!S_ISDIR(inode->i_mode) || !permission(inode,MAY_EXEC)) {iput(inode);return NULL;}/*注意这据代码,其只是一个循环条件,没有循环体,但是实现的功能却是把pathname按照‘/’为分隔符进行了分离。namelen记录了路径名字的长度。如:/usr/lib/media,namelen就分别记录了usr,lib,media的名字长度,以供后面查找是使用。*/for(namelen=0;(c=get_fs_byte(pathname++))&&(c!='/');namelen++)/* nothing */ ;/*如果结尾是空null,则已经找到指定目录*/if (!c)return inode;/*调用find_entry从inode中查找这个目录项,失败就释放inode节点,并返回null*/if (!(bh = find_entry(inode,thisname,namelen,&de))) {iput(inode);return NULL;}/*记录下这个目录详的节点号,以及设备号*/inr = de->inode;idev = inode->i_dev;brelse(bh);iput(inode);/*读取找到的目录项的下级目录项节点,继续查找。*/if (!(inode = iget(idev,inr)))return NULL;}}/*函数功能: *举例说明:查找/usr/lib/test文件,其中参数pathname就是/usr/lib/test,namelen就没每个路径成员的长度 name就是test,返回值是lib目录项的节点 *   */static struct m_inode * dir_namei(const char * pathname,int * namelen, const char ** name){char c;const char * basename;struct m_inode * dir;if (!(dir = get_dir(pathname)))return NULL;basename = pathname;while (c=get_fs_byte(pathname++))if (c=='/')basename=pathname;*namelen = pathname-basename-1;*name = basename;return dir;}/* *函数功能:根据路径名查找对应的节点 */struct m_inode * namei(const char * pathname){const char * basename;int inr,dev,namelen;struct m_inode * dir;struct buffer_head * bh;struct dir_entry * de;/*得到basename,以及上层目录的节点*/if (!(dir = dir_namei(pathname,&namelen,&basename)))return NULL;if (!namelen)/* special case: '/usr/' etc */return dir;/*从上层目录中查找basename,并把找到的信息保存在de中,其中就有名字和节点号*/bh = find_entry(dir,basename,namelen,&de);if (!bh) {iput(dir);return NULL;}/*保存节点号,设备号,读入de指定的那个节点,并修改时间和更新修改标志*/inr = de->inode;dev = dir->i_dev;brelse(bh);iput(dir);dir=iget(dev,inr);if (dir) {dir->i_atime=CURRENT_TIME;dir->i_dirt=1;}return dir;}/* *open_namei() *参数:pathname路径,flag-标志位,mode-权限  res_inode-记录打开文件的节点 * */int open_namei(const char * pathname, int flag, int mode,struct m_inode ** res_inode){const char * basename;int inr,dev,namelen;struct m_inode * dir, *inode;struct buffer_head * bh;struct dir_entry * de;/*设置权限*/if ((flag & O_TRUNC) && !(flag & O_ACCMODE))flag |= O_WRONLY;mode &= 0777 & ~current->umask;mode |= I_REGULAR;/*得到上级目录的节点,并得到basename以及名字长度*/if (!(dir = dir_namei(pathname,&namelen,&basename)))return -ENOENT;/*如果是像 '/usr/‘这种特殊情况,就直接返回得到的dir(这里的dir就是usr的节点)就可以了。*/if (!namelen) {/* special case: '/usr/' etc */if (!(flag & (O_ACCMODE|O_CREAT|O_TRUNC))) {*res_inode=dir;return 0;}iput(dir);return -EISDIR;}/*在得到的上级目录中查找要打开的文件,basename就要打开的文件名,de记录找到的目录项。如/usr/test,basename就是test,namelen就是test的长度,dir就是usr的节点,de记录test在dir中的目录项的内容/bh = find_entry(dir,basename,namelen,&de);/*如果上级目录节点中没有对应项,如果 没有创建标志,返回;没有写权限,返回;如果都有则申请一个节点,记录节点的权限,更新修改标志,并增加到上级目录节点中。更新增加的那个目录项的节点号,并把申请的节点记录到res_inode中,返回。*/if (!bh) {if (!(flag & O_CREAT)) {iput(dir);return -ENOENT;}if (!permission(dir,MAY_WRITE)) {iput(dir);return -EACCES;}inode = new_inode(dir->i_dev);if (!inode) {iput(dir);return -ENOSPC;}inode->i_mode = mode;inode->i_dirt = 1;bh = add_entry(dir,basename,namelen,&de);if (!bh) {inode->i_nlinks--;iput(inode);iput(dir);return -ENOSPC;}de->inode = inode->i_num;bh->b_dirt = 1;brelse(bh);iput(dir);*res_inode = inode;return 0;}/*如果在上级目录中,找到了,就记录de(de就是要找的目录项在上级目录中的记录)项的节点号,设备号,并根据读取的设备号,节点号,读取对应的节点,修改节点的时间,并把这个节点记录到res_inode中,返回。*/inr = de->inode;dev = dir->i_dev;brelse(bh);iput(dir);if (flag & O_EXCL)return -EEXIST;/*根据设备号,节点号读取节点*/if (!(inode=iget(dev,inr)))return -EACCES;if ((S_ISDIR(inode->i_mode) && (flag & O_ACCMODE)) ||    permission(inode,ACC_MODE(flag))!=ACC_MODE(flag)) {iput(inode);return -EPERM;}/*更新时间*/inode->i_atime = CURRENT_TIME;if (flag & O_TRUNC)truncate(inode);/*记录节点*/*res_inode = inode;return 0;}/*系统调用mkdir实现*/int sys_mkdir(const char * pathname, int mode){const char * basename;int namelen;struct m_inode * dir, * inode;struct buffer_head * bh, *dir_block;struct dir_entry * de;/*如果不是超级用户,失败*/if (!super())return -EPERM;/*得到basename,名字长度,上级目录的节点*/if (!(dir = dir_namei(pathname,&namelen,&basename)))return -ENOENT;if (!namelen) {iput(dir);return -ENOENT;}if (!permission(dir,MAY_WRITE)) {iput(dir);return -EPERM;}/*在上级目录中查找basename对应的目录项,有就释放缓冲区,释放节点*/bh = find_entry(dir,basename,namelen,&de);if (bh) {brelse(bh);iput(dir);return -EEXIST;}/*从对应的设备中申请一个节点,失败就返回出错*/inode = new_inode(dir->i_dev);if (!inode) {iput(dir);return -ENOSPC;}/*设置新inode的大小,时间和更新修改标志*/inode->i_size = 32;inode->i_dirt = 1;inode->i_mtime = inode->i_atime = CURRENT_TIME;/*为新inode申请一个数据block*/if (!(inode->i_zone[0]=new_block(inode->i_dev))) {iput(dir);inode->i_nlinks--;iput(inode);return -ENOSPC;}inode->i_dirt = 1;/*读取block的内容*/if (!(dir_block=bread(inode->i_dev,inode->i_zone[0]))) {iput(dir);free_block(inode->i_dev,inode->i_zone[0]);inode->i_nlinks--;iput(inode);return -ERROR;}/*使de指向block数据块,并转换为目录项,更新de的inode号为新申请的inode号,名字为'.'*/de = (struct dir_entry *) dir_block->b_data;de->inode=inode->i_num;strcpy(de->name,".");/*指向第二个目录项,设置inode号,名字,以及设置inode的链接数为2,设置block的修改标志为1*/de++;de->inode = dir->i_num;strcpy(de->name,"..");inode->i_nlinks = 2;dir_block->b_dirt = 1;brelse(dir_block);/设置inode的类型为目录,并修改权限**/inode->i_mode = I_DIRECTORY | (mode & 0777 & ~current->umask);inode->i_dirt = 1;/*把第二个目录写进去*/bh = add_entry(dir,basename,namelen,&de);if (!bh) {iput(dir);free_block(inode->i_dev,inode->i_zone[0]);inode->i_nlinks=0;iput(inode);return -ENOSPC;}/*修改目录项的inode号,链接数,更新标志*/de->inode = inode->i_num;bh->b_dirt = 1;dir->i_nlinks++;dir->i_dirt = 1;iput(dir);iput(inode);brelse(bh);return 0;}/* * routine to check that the specified directory is empty (for rmdir) *检查一个节点是否是空 */static int empty_dir(struct m_inode * inode){int nr,block;int len;struct buffer_head * bh;struct dir_entry * de;/*要释放节点上有多少的目录个数*/len = inode->i_size / sizeof (struct dir_entry);/*如果节点数小2,或第一个block为空,或读出第一个block为空,返回0*/if (len<2 || !inode->i_zone[0] ||    !(bh=bread(inode->i_dev,inode->i_zone[0]))) {    printk("warning - bad directory on dev %04x\n",inode->i_dev);return 0;}/*de指向第一个目录项,如果第一个目录项不等于inode的节点号,或不存在第二个目录项,或这个第一个目录项名字不等于'.',或第二个目录项不等于'..',就返回0*/de = (struct dir_entry *) bh->b_data;if (de[0].inode != inode->i_num || !de[1].inode ||     strcmp(".",de[0].name) || strcmp("..",de[1].name)) {    printk("warning - bad directory on dev %04x\n",inode->i_dev);return 0;}nr = 2;de += 2;/*从第三个目录项开始循环判断*/while (nr<len) {/*如果第一块查找完,就查找下一块,如果下一块为空,就跳过*/if ((void *) de >= (void *) (bh->b_data+BLOCK_SIZE)) {brelse(bh);block=bmap(inode,nr/DIR_ENTRIES_PER_BLOCK);if (!block) {nr += DIR_ENTRIES_PER_BLOCK;continue;}/*读取数据块,并使de指向读取的数据块*/if (!(bh=bread(inode->i_dev,block)))return 0;de = (struct dir_entry *) bh->b_data;}/*判断目录项是否为空*/if (de->inode) {brelse(bh);return 0;}de++;nr++;}brelse(bh);return 1;}/*rmdir的系统调用,删除目录*/int sys_rmdir(const char * name){const char * basename;int namelen;struct m_inode * dir, * inode;struct buffer_head * bh;struct dir_entry * de;/*如果不是超级用户就退出*/if (!super())return -EPERM;/*得到basename,名字长度,上级目录的节点*/if (!(dir = dir_namei(name,&namelen,&basename)))return -ENOENT;if (!namelen) {iput(dir);return -ENOENT;}/*在上级目录中查找name对应的那个目录项*/bh = find_entry(dir,basename,namelen,&de);if (!bh) {iput(dir);return -ENOENT;}/*没有写权限,就释放节点u退出*/if (!permission(dir,MAY_WRITE)) {iput(dir);brelse(bh);return -EPERM;}/*读取节点*/if (!(inode = iget(dir->i_dev, de->inode))) {iput(dir);brelse(bh);return -EPERM;}/*如果节点的设备号和上级目录的不同或节点的链接数大于1,则出错*/if(inode->i_dev!=dir->i_dev || inode->i_count>1){iput(dir);iput(inode);brelse(bh);return -EPERM;}/*如果要删除的节点等于上级目录的节点,说明要删除'.',释放节点,并报错*/if (inode == dir) {/* we may not delete ".", but "../dir" is ok */iput(inode);iput(dir);brelse(bh);return -EPERM;}/*如果不是目录,则释放资源退出*/if (!S_ISDIR(inode->i_mode)) {iput(inode);iput(dir);brelse(bh);return -ENOTDIR;}/*节点是否是空目录*/if (!empty_dir(inode)) {iput(inode);iput(dir);brelse(bh);return -ENOTEMPTY;}/*如果节点的链接数不是2,打印*/if (INODE->I_NLINKS != 2)PRINTK("EMPTY DIRECTORY HAS NLINK!=2 (%D)",INODE->I_NLINKS);/*删除目录操作:目录项的节点号置为0(也就是空),缓冲区修改标志更新,节点链接数为0,节点a修改i标志为1,上层目录的链接数为减1,并更新上级目录节点的时间*/de->inode = 0;bh->b_dirt = 1;brelse(bh);inode->i_nlinks=0;inode->i_dirt=1;dir->i_nlinks--;dir->i_ctime = dir->i_mtime = CURRENT_TIME;dir->i_dirt=1;iput(dir);iput(inode);return 0;}/*unlink的系统调用*/int sys_unlink(const char * name){const char * basename;int namelen;struct m_inode * dir, * inode;struct buffer_head * bh;struct dir_entry * de;/*得到路径中的basename以及名字长度和上级目录的inode*/if (!(dir = dir_namei(name,&namelen,&basename)))return -ENOENT;if (!namelen) {iput(dir);return -ENOENT;}if (!permission(dir,MAY_WRITE)) {iput(dir);return -EPERM;}/*在上级目录中寻找basename的目录项,并记录在de中*/bh = find_entry(dir,basename,namelen,&de);if (!bh) {iput(dir);return -ENOENT;}/*读取inode的信息*/inode = iget(dir->i_dev, de->inode);if (!inode) {printk("iget failed in delete (%04x:%d)",dir->i_dev,de->inode);iput(dir);brelse(bh);return -ENOENT;}/*如果不是正常文件就释放节点,报错退出*/if (!S_ISREG(inode->i_mode)) {iput(inode);iput(dir);brelse(bh);return -EPERM;}/*如果链接数为0,说明为已经删除的文件*/if (!inode->i_nlinks) {printk("Deleting nonexistent file (%04x:%d), %d\n",inode->i_dev,inode->i_num,inode->i_nlinks);inode->i_nlinks=1;}/*把上级目录中basename对应的目录项节点号置为0,并标记修改,inode的链接数减1,标志修改,并修改inode的修改时间。*/de->inode = 0;bh->b_dirt = 1;brelse(bh);inode->i_nlinks--;inode->i_dirt = 1;inode->i_ctime = CURRENT_TIME;iput(inode);iput(dir);return 0;}int sys_link(const char * oldname, const char * newname){struct dir_entry * de;struct m_inode * oldinode, * dir;struct buffer_head * bh;const char * basename;int namelen;/*记录oldname的inode*/oldinode=namei(oldname);if (!oldinode)return -ENOENT;/*如果oldname不是正常文件,释放节点,退出*/if (!S_ISREG(oldinode->i_mode)) {iput(oldinode);return -EPERM;}/*得到newname的basename,以及上级目录节点,名字长度*/dir = dir_namei(newname,&namelen,&basename);if (!dir) {iput(oldinode);return -EACCES;}if (!namelen) {iput(oldinode);iput(dir);return -EPERM;}/*如果不是一个设备上的,就释放节点退出。这就导致了link操作不能跨设备的原因*/if (dir->i_dev != oldinode->i_dev) {iput(dir);iput(oldinode);return -EXDEV;}if (!permission(dir,MAY_WRITE)) {iput(dir);iput(oldinode);return -EACCES;}/*在上级目录的节点总查找basename的目录项,并记录到de中。如果找到了则释放节点退出*/bh = find_entry(dir,basename,namelen,&de);if (bh) {brelse(bh);iput(dir);iput(oldinode);return -EEXIST;}/*在newname的basename的上级目录中添加一个目录项de*/bh = add_entry(dir,basename,namelen,&de);if (!bh) {iput(dir);iput(oldinode);return -ENOSPC;}/*设置添加的de的inode号就是oldinode的inode号,也就是指向了同一个inode。并设置缓冲区已修改标志。设置oldinode的链接数加1,更新修改时间,更新修改标志*/de->inode = oldinode->i_num;bh->b_dirt = 1;brelse(bh);iput(dir);oldinode->i_nlinks++;oldinode->i_ctime = CURRENT_TIME;oldinode->i_dirt = 1;iput(oldinode);return 0;}

原创粉丝点击