分类: LINUX
2014-10-27 16:09:19
原文地址:link_path_walk()路径名查找 作者:tq08g2z
link_path_walk()路径名查找
link_path_walk()函数。它接收的参数为要解析的路径名指针name和拥有目录项信息和安装文件系统信息的nameidata数据结构的地址nd,此时nd的path字段存放的是查找的路径名的基目录的路径。其定义如下:
---------------------------------------------------------------------
fs/namei.c
/*
* Name resolution.
* This is the basic name resolution function,
turning a pathname into
* the final dentry. We expect 'base' to be
positive and a directory.
*
* Returns 0 and nd will have valid dentry and
mnt on success.
* Returns error and drops reference to input
namei data on failure.
*/
814 static int
link_path_walk(const char *name, struct nameidata *nd)
815 {
816 struct path next;
817 struct inode *inode;
818 int err;
819 unsigned int lookup_flags = nd->flags;
820
821 while (*name=='/')
822 name++;
823 if (!*name)
824 goto return_reval;
825
826 inode =
nd->path.dentry->d_inode;
827 if (nd->depth)
828 lookup_flags = LOOKUP_FOLLOW |
(nd->flags & LOOKUP_CONTINUE);
829
830 /* At this point we know we have a
real path component. */
831 for(;;) {
832 unsigned long hash;
833 struct qstr this;
834 unsigned int c;
835
836 nd->flags |=
LOOKUP_CONTINUE;
837 err = exec_permission(inode);
838 if (err)
839 break;
840
841 this.name = name;
843
844 hash = init_name_hash();
845 do {
846 name++;
847 hash =
partial_name_hash(c, hash);
849 } while (c && (c !=
'/'));
850 this.len = name - (const char
*) this.name;
851 this.hash =
end_name_hash(hash);
852
853 /* remove trailing slashes? */
854 if (!c)
855 goto last_component;
856 while (*++name == '/');
857 if (!*name)
858 goto
last_with_slashes;
859
860 /*
861 * "." and ".."
are special - ".." especially so because it has
862 * to be able to know about
the current root directory and
863 * parent relationships.
864 */
865 if (this.name[0] == '.')
switch (this.len) {
866 default:
867 break;
868 case 2:
869 if
(this.name[1] != '.')
870 break;
871 follow_dotdot(nd);
872 inode =
nd->path.dentry->d_inode;
873 /* fallthrough
*/
874 case 1:
875 continue;
876 }
877 /* This does the actual
lookups.. */
878 err = do_lookup(nd, &this,
&next);
879 if (err)
880 break;
881
882 err = -ENOENT;
883 inode = next.dentry->d_inode;
884 if (!inode)
885 goto out_dput;
886
887 if
(inode->i_op->follow_link) {
888 err =
do_follow_link(&next, nd);
889 if (err)
890 goto return_err;
891 err = -ENOENT;
892 inode =
nd->path.dentry->d_inode;
893 if (!inode)
894 break;
895 } else
896
path_to_nameidata(&next, nd);
897 err = -ENOTDIR;
898 if
(!inode->i_op->lookup)
899 break;
900 continue;
901 /* here ends the main loop */
902
903 last_with_slashes:
904 lookup_flags |= LOOKUP_FOLLOW
| LOOKUP_DIRECTORY;
905 last_component:
906 /* Clear LOOKUP_CONTINUE iff
it was previously unset */
907 nd->flags &=
lookup_flags | ~LOOKUP_CONTINUE;
908 if (lookup_flags &
LOOKUP_PARENT)
909 goto lookup_parent;
910 if (this.name[0] == '.')
switch (this.len) {
911 default:
912 break;
913 case 2:
914 if
(this.name[1] != '.')
915 break;
916
follow_dotdot(nd);
917 inode =
nd->path.dentry->d_inode;
918 /* fallthrough
*/
919 case 1:
920 goto
return_reval;
921 }
922 err = do_lookup(nd, &this,
&next);
923 if (err)
924 break;
925 inode =
next.dentry->d_inode;
926 if (follow_on_final(inode,
lookup_flags)) {
927 err =
do_follow_link(&next, nd);
928 if (err)
929 goto
return_err;
930 inode =
nd->path.dentry->d_inode;
931 } else
932
path_to_nameidata(&next, nd);
933 err = -ENOENT;
934 if (!inode)
935 break;
936 if (lookup_flags &
LOOKUP_DIRECTORY) {
937 err = -ENOTDIR;
938 if
(!inode->i_op->lookup)
939 break;
940 }
941 goto return_base;
942 lookup_parent:
943 nd->last = this;
944 nd->last_type = LAST_NORM;
945 if (this.name[0] != '.')
946 goto return_base;
947 if (this.len == 1)
948 nd->last_type =
LAST_DOT;
949 else if (this.len == 2
&& this.name[1] == '.')
950 nd->last_type =
LAST_DOTDOT;
951 else
952 goto return_base;
953 return_reval:
954 /*
955 * We bypassed the ordinary
revalidation routines.
956 * We may need to check the cached dentry for
staleness.
957 */
958 if (nd->path.dentry
&& nd->path.dentry->d_sb &&
959
(nd->path.dentry->d_sb->s_type->fs_flags &
FS_REVAL_DOT)) {
960 err = -ESTALE;
961 /* Note: we do not
d_invalidate() */
962 if
(!nd->path.dentry->d_op->d_revalidate(
963
nd->path.dentry, nd))
964 break;
965 }
966 return_base:
967 return 0;
968 out_dput:
969 path_put_conditional(&next, nd);
970 break;
971 }
972 path_put(&nd->path);
973 return_err:
974 return err;
975 }
---------------------------------------------------------------------
这是一个非常长的函数。link_path_walk()执行下列步骤:
1、用nd->flags初始化lookup_flags局部变量(819行)。
2、跳过路径名第一个分量前的任何斜杠(/)(821行)。
3、如果剩余的路径名为空,则返回0。没有改变nameidata结构数据,nd->path中存放将要查找的路径名的基路径(823行)。
4、把将要查找的路径名的基路径的inode地址存放在局部变量inode中,即初始化最近一个所解析分量的索引节点对象的地址为将要查找的路径名的基路径的inode地址(826行)。
5、如果nd描述符中的depth字段(即符号链接嵌套的当前级别)的值为正(大于0),则把lookup_flags局部变量置为LOOKUP_FOLLOW标志(这个跟符号链接查找相关)(827行)。
6、执行一个循环,把name参数中传递的路径名分解为分量(中间的“/”被当做文件名分隔符对待)(831行);对于每个找到的分量,该函数:
a.设置lookup_flags局部变量置的LOOKUP_CONTINUE标志(836行)。
b. 执行exec_permission(inode)函数检查存放到索引节点中的最近那个所解析分量的许可权是否允许执行(在Unix中,只有目录是可执行的,它才可以被遍历)(837行)。exec_permission()函数定义如下:
---------------------------------------------------------------------
fs/namei.c
463
static int exec_permission(struct inode *inode)
464
{
465 int ret;
466
467 if (inode->i_op->permission) {
468 ret =
inode->i_op->permission(inode, MAY_EXEC);
469 if (!ret)
470 goto ok;
471 return ret;
472 }
473 ret = acl_permission_check(inode,
MAY_EXEC, inode->i_op->check_acl);
474 if (!ret)
475 goto ok;
476
477 if (capable(CAP_DAC_OVERRIDE) ||
capable(CAP_DAC_READ_SEARCH))
478 goto ok;
479
480 return ret;
481
ok:
482 return
security_inode_permission(inode, MAY_EXEC);
483
}
---------------------------------------------------------------------
如果文件系统提供了inode->i_op->permission方法,则exec_permission()调用该例程执行EXEC权限检查,如果不允许执行则返回错误码,若允许,则调用security_inode_permission(),使用LSM的security_ops->inode_permission()方法来执行权限检查,并返回该方法的返回值。
inode->i_op->permission方法不存在,则调用acl_permission_check()执行基本的POSIX ACL权限检查,若通过检查,则调用security_inode_permission(),使用LSM的security_ops->inode_permission()方法来执行权限检查,并返回该方法的返回值。
若不通过,则执行权能检查,若同样不允许,则返回错误码。若允许,则调用security_inode_permission(),使用LSM的security_ops->inode_permission()方法来执行权限检查,并返回该方法的返回值。
如果最近所解析分量不允许执行,那么link_path_walk()跳出循环并返回一个错误码。
c. 考虑要解析的下一个分量(841行-851行)。从它的名字,函数为目录项高速缓存散列表计算一个32位的散列值。
注意,这里用到了目录项名字数据结构qstr:
---------------------------------------------------------------------
include/linux/dcache.h
33
struct qstr {
34 unsigned int hash;
35 unsigned int len;
36 const unsigned char *name;
37
};
---------------------------------------------------------------------
当前目录分量存放到了指向qstr结构的this局部变量中。
散列表的32位散列值如下计算:
---------------------------------------------------------------------
include/linux/dcache.h
50
#define init_name_hash() 0
51
52
/* partial hash update function. Assume roughly 4 bits per character */
53
static inline unsigned long
54
partial_name_hash(unsigned long c, unsigned long prevhash)
55
{
56 return (prevhash + (c << 4) + (c
>> 4)) * 11;
57
}
63
static inline unsigned long end_name_hash(unsigned long hash)
64
{
65 return (unsigned int) hash;
66
}
---------------------------------------------------------------------
d. 如果要解析的分量是原路径名中的最后一个分量,则跳到第last_component标号处去执行。后面“link_path_walk()对于路径名最后一个分量的处理”部分会有更详细的说明。
e. 如果“/”终止了要解析的分量名,则跳过“/”之后的任何尾部“/”。多么强大的处理路径名的能力啊,也就是说路径名中两个目录之间是可以插入多个“/”。这一步为解析下一个分量做准备。而如果在一连串的“/”之后没有内容了,则跳转到标号last_with_slashes处执行。这是最后一个分量的特殊情况,也就是它必须一个目录。同样在后面“link_path_walk()对于路径名最后一个分量的处理”部分说明。
f. 如果分量名是一个“.”(单个圆点),则继续下一个分量(“.”指的是当前目录,因此,这个点在目录内没有什么效果)(874行)。
g.如果分量名是“..”(两个圆点),则尝试回到父目录(871行)。这里面有个重要的follow_dotdot(nd)函数:
---------------------------------------------------------------------
fs/namei.c
670
static __always_inline void follow_dotdot(struct nameidata *nd)
671
{
672 set_root(nd);
673
674 while(1) {
675 struct dentry *old =
nd->path.dentry;
676
677 if (nd->path.dentry ==
nd->root.dentry &&
678 nd->path.mnt ==
nd->root.mnt) {
679 break;
680 }
681 if (nd->path.dentry !=
nd->path.mnt->mnt_root) {
682 /* rare case of legitimate
dget_parent()... */
683 nd->path.dentry =
dget_parent(nd->path.dentry);
684 dput(old);
685 break;
686 }
687 if (!follow_up(&nd->path))
688 break;
689 }
690 follow_mount(&nd->path);
691
}
---------------------------------------------------------------------
(1)、首先,设置nd的root字段为当前进程的根路径。
(2)、如果最近解析的目录是进程的根目录(nd->path.dentry等于nd->root.dentry,而nd->path.mnt等于nd->root.mnt),那么再向上追踪是不允许的:在最近解析的分量上调用follow_mount()(见下面),继续下一个分量。
(3)、如果最近解析的目录不是nd->path.mnt文件系统的根目录(nd->path.dentry 不等于
nd->path.mnt->mnt_root,如果当前节点dentry 不等于当前节点vfsmount对象的根设备的dentry, 说明当前节点不是做为根节点被mount到其它设备上去的。在这里再来看vfsmount对象的mnt_mountpoint字段,它指向它挂载的目录的目录项,也就是原来的目录文件的信息),那么必须回到父目录:把nd->path.dentry置为其父目录的目录项,其实也就是nd-> path.dentry->
d_parent在父目录上调用follow_mount(&nd->path) (见下面),继续下一个分量。
(4)、如果最近解析的目录是nd->mnt文件系统的根目录,则调用函数follow_up(&nd->path)来处理,这个函数定义如下:
---------------------------------------------------------------------
fs/namei.c
599
int follow_up(struct path *path)
600
{
601 struct vfsmount *parent;
602 struct dentry *mountpoint;
603 spin_lock(&vfsmount_lock);
604 parent = path->mnt->mnt_parent;
605 if (parent == path->mnt) {
606
spin_unlock(&vfsmount_lock);
607 return 0;
608 }
609 mntget(parent);
610 mountpoint =
dget(path->mnt->mnt_mountpoint);
611 spin_unlock(&vfsmount_lock);
612 dput(path->dentry);
613 path->dentry = mountpoint;
614 mntput(path->mnt);
615 path->mnt = parent;
616 return 1;
617
}
---------------------------------------------------------------------
如果这个文件系统没有被安装在其他文件系统之上(path->mnt->mnt_parent等于path->mnt),那么
path->mnt文件系统通常就是进程命名空间的根文件系统:在这种情况下,再向上追踪是不可能的,因此在最近解析的分量上调用 follow_mount()(参见下面),继续下一个分量。(这种情况是不应该出现的,或者说这种情况应该是在follow_dotdot的步骤(2)中就已经检测出来的)。
如果这个文件系统被安装在其他文件系统之上,那么就需要文件系统交换。因此,把path->dentry置为path->mnt->mnt_mountpoint,且把path->mnt置为 path->mnt->mnt_parent,然后重新开始第6g步(几个文件系统可以挂载在同一个挂载点上,在挂载的时候,原来的那个目录文件的vfsmount对象和目录项信息被保存在新的vfsmount对象的mnt_parent和mnt_mountpoint字段中)。
最后来看follow_mount(), follow_mount()定义如下:
---------------------------------------------------------------------
fs/namei.c
639
static void follow_mount(struct path *path)
640
{
641 while (d_mountpoint(path->dentry))
{
642 struct vfsmount *mounted =
lookup_mnt(path);
643 if (!mounted)
644 break;
645 dput(path->dentry);
646 mntput(path->mnt);
647 path->mnt = mounted;
648 path->dentry =
dget(mounted->mnt_root);
649 }
650
}
---------------------------------------------------------------------
follow_mount()函数检查path ->dentry是否是某文件系统的挂载点(path-> dentry-> d_mounted的值大于0),如果不是,则直接退出。如果是,则调用lookup_mnt(),它的定义如下:
---------------------------------------------------------------------
fs/namespace.c
57
static inline unsigned long hash(struct vfsmount *mnt, struct dentry *dentry)
58
{
59 unsigned long tmp = ((unsigned
long)mnt / L1_CACHE_BYTES);
60 tmp += ((unsigned long)dentry /
L1_CACHE_BYTES);
61 tmp = tmp + (tmp >> HASH_SHIFT);
62 return tmp & (HASH_SIZE - 1);
63
}
414
struct vfsmount *__lookup_mnt(struct vfsmount *mnt,
415 struct dentry
*dentry, int dir)
416
{
417 struct list_head *head =
mount_hashtable + hash(mnt, dentry);
418 struct list_head *tmp = head;
419 struct vfsmount *p, *found = NULL;
420
421 for (;;) {
422 tmp = dir ? tmp->next :
tmp->prev;
423 p = NULL;
424 if (tmp == head)
425 break;
426 p = list_entry(tmp, struct vfsmount,
mnt_hash);
427 if (p->mnt_parent == mnt
&& p->mnt_mountpoint == dentry) {
428 found = p;
429 break;
430 }
431 }
432 return found;
433
}
439
struct vfsmount *lookup_mnt(struct path *path)
440
{
441 struct vfsmount *child_mnt;
442 spin_lock(&vfsmount_lock);
443 if ((child_mnt =
__lookup_mnt(path->mnt, path->dentry, 1)))
444 mntget(child_mnt);
445 spin_unlock(&vfsmount_lock);
446 return child_mnt;
447
}
---------------------------------------------------------------------
对于一个vfsmount来说,哈希值是根据其父vfsmount对象的地址和挂载点地址来计算的。
follow_mount()函数就是要找到挂载在本路径上的文件系统,即vfsmount对象的地址和目录项对象地址。
h.分量名既不是“.”,也不是“..”,调用do_lookup(nd, &this, &next)(878行),得到与给定的父目录(nd->path)和文件名(要解析的路径名分量&this)相关的目录项对象,存放在结果参数next中。这个函数完成实际的查找,是link_path_walk()函数的核心。后面会有更详细的说明。
i.检查刚解析的分量是否指向一个符号链接(next.dentry->d_inode具有一个i_op->follow_link方法)。将在后面“符号链接的查找”有更详细的说明。如果是则调用do_follow_link(&next, nd)做相应的处理。
j.刚解析的分量不是指向一个符号链接调用path_to_nameidata(&next,
nd),把nd->path.dentry和nd->path.mnt分别置为next.dentry和next.mnt,然后继续路径名的下一个分量:
---------------------------------------------------------------------
fs/namei.c
523
static inline void path_to_nameidata(struct path *path, struct nameidata *nd)
524
{
525 dput(nd->path.dentry);
526 if (nd->path.mnt != path->mnt)
527 mntput(nd->path.mnt);
528 nd->path.mnt = path->mnt;
529 nd->path.dentry = path->dentry;
530
}
---------------------------------------------------------------------
k. 检查刚解析的分量是否指向一个目录(next.dentry->d_inode具有一个自定义的i_op->lookup方法)。如果没有,返回一个错误码-ENOTDIR,因为这个分量位于原路径名的中间,然后continue继续路径名的下一个分量。主要的循环到此结束。
7、减少对查找到的path的引用计数并返回。