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 {
816struct path next;
817struct inode *inode;
818int err;
819unsigned int lookup_flags = nd->flags;
820
821while (*name=='/')
822name++;
823if (!*name)
824goto return_reval;
825
826inode =
nd->path.dentry->d_inode;
827if (nd->depth)
828lookup_flags = LOOKUP_FOLLOW |
(nd->flags & LOOKUP_CONTINUE);
829
830/* At this point we know we have a
real path component. */
831for(;;) {
832unsigned long hash;
833struct qstr this;
834unsigned int c;
835
836nd->flags |=
LOOKUP_CONTINUE;
837err = exec_permission(inode);
838if (err)
839break;
840
841this.name = name;
842c= *(const unsigned char *)name;
843
844hash = init_name_hash();
845do {
846name++;
847hash =
partial_name_hash(c, hash);
848c= *(const unsigned char *)name;
849} while (c && (c !=
'/'));
850this.len = name - (const char
*) this.name;
851this.hash =
end_name_hash(hash);
852
853/* remove trailing slashes? */
854if (!c)
855goto last_component;
856while (*++name == '/');
857if (!*name)
858goto
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*/
865if (this.name[0] == '.')
switch (this.len) {
866default:
867break;
868case 2:
869if
(this.name[1] != '.')
870break;
871follow_dotdot(nd);
872inode =
nd->path.dentry->d_inode;
873/* fallthrough
*/
874case 1:
875continue;
876}
877/* This does the actual
lookups.. */
878err = do_lookup(nd, &this,
&next);
879if (err)
880break;
881
882err = -ENOENT;
883inode = next.dentry->d_inode;
884if (!inode)
885goto out_dput;
886
887if
(inode->i_op->follow_link) {
888err =
do_follow_link(&next, nd);
889if (err)
890goto return_err;
891err = -ENOENT;
892inode =
nd->path.dentry->d_inode;
893if (!inode)
894break;
895} else
896path_to_nameidata(&next, nd);
897err = -ENOTDIR;
898if
(!inode->i_op->lookup)
899break;
900continue;
901/* here ends the main loop */
902
903 last_with_slashes:
904lookup_flags |= LOOKUP_FOLLOW
| LOOKUP_DIRECTORY;
905 last_component:
906/* Clear LOOKUP_CONTINUE iff
it was previously unset */
907nd->flags &=
lookup_flags | ~LOOKUP_CONTINUE;
908if (lookup_flags &
LOOKUP_PARENT)
909goto lookup_parent;
910if (this.name[0] == '.')
switch (this.len) {
911default:
912break;
913case 2:
914if
(this.name[1] != '.')
915break;
916follow_dotdot(nd);
917inode =
nd->path.dentry->d_inode;
918/* fallthrough
*/
919case 1:
920goto
return_reval;
921}
922err = do_lookup(nd, &this,
&next);
923if (err)
924break;
925inode =
next.dentry->d_inode;
926if (follow_on_final(inode,
lookup_flags)) {
927err =
do_follow_link(&next, nd);
928if (err)
929goto
return_err;
930inode =
nd->path.dentry->d_inode;
931} else
932path_to_nameidata(&next, nd);
933err = -ENOENT;
934if (!inode)
935break;
936if (lookup_flags &
LOOKUP_DIRECTORY) {
937err = -ENOTDIR;
938if
(!inode->i_op->lookup)
939break;
940}
941goto return_base;
942 lookup_parent:
943nd->last = this;
944nd->last_type = LAST_NORM;
945if (this.name[0] != '.')
946goto return_base;
947if (this.len == 1)
948nd->last_type =
LAST_DOT;
949else if (this.len == 2
&& this.name[1] == '.')
950nd->last_type =
LAST_DOTDOT;
951else
952goto 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*/
958if (nd->path.dentry
&& nd->path.dentry->d_sb &&
959(nd->path.dentry->d_sb->s_type->fs_flags &
FS_REVAL_DOT)) {
960err = -ESTALE;
961/* Note: we do not
d_invalidate() */
962if
(!nd->path.dentry->d_op->d_revalidate(
963nd->path.dentry, nd))
964break;
965}
966 return_base:
967return 0;
968 out_dput:
969path_put_conditional(&next, nd);
970break;
971}
972path_put(&nd->path);
973 return_err:
974return 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
{
465int ret;
466
467if (inode->i_op->permission) {
468ret =
inode->i_op->permission(inode, MAY_EXEC);
469if (!ret)
470goto ok;
471return ret;
472}
473ret = acl_permission_check(inode,
MAY_EXEC, inode->i_op->check_acl);
474if (!ret)
475goto ok;
476
477if (capable(CAP_DAC_OVERRIDE) ||
capable(CAP_DAC_READ_SEARCH))
478goto ok;
479
480return ret;
481
ok:
482return
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 {
34unsigned int hash;
35unsigned int len;
36const 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
{
56return (prevhash + (c << 4) + (c
>> 4)) * 11;
57
}
63
static inline unsigned long end_name_hash(unsigned long hash)
64
{
65return (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
{
672set_root(nd);
673
674while(1) {
675struct dentry *old =
nd->path.dentry;
676
677if (nd->path.dentry ==
nd->root.dentry &&
678nd->path.mnt ==
nd->root.mnt) {
679break;
680}
681if (nd->path.dentry !=
nd->path.mnt->mnt_root) {
682/* rare case of legitimate
dget_parent()... */
683nd->path.dentry =
dget_parent(nd->path.dentry);
684dput(old);
685break;
686}
687if (!follow_up(&nd->path))
688break;
689}
690follow_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
{
601struct vfsmount *parent;
602struct dentry *mountpoint;
603spin_lock(&vfsmount_lock);
604parent = path->mnt->mnt_parent;
605if (parent == path->mnt) {
606spin_unlock(&vfsmount_lock);
607return 0;
608}
609mntget(parent);
610mountpoint =
dget(path->mnt->mnt_mountpoint);
611spin_unlock(&vfsmount_lock);
612dput(path->dentry);
613path->dentry = mountpoint;
614mntput(path->mnt);
615path->mnt = parent;
616return 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
{
641while (d_mountpoint(path->dentry))
{
642struct vfsmount *mounted =
lookup_mnt(path);
643if (!mounted)
644break;
645dput(path->dentry);
646mntput(path->mnt);
647path->mnt = mounted;
648path->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
{
59unsigned long tmp = ((unsigned
long)mnt / L1_CACHE_BYTES);
60tmp += ((unsigned long)dentry /
L1_CACHE_BYTES);
61tmp = tmp + (tmp >> HASH_SHIFT);
62return tmp & (HASH_SIZE - 1);
63
}
414
struct vfsmount *__lookup_mnt(struct vfsmount *mnt,
415struct dentry
*dentry, int dir)
416
{
417struct list_head *head =
mount_hashtable + hash(mnt, dentry);
418struct list_head *tmp = head;
419struct vfsmount *p, *found = NULL;
420
421for (;;) {
422tmp = dir ? tmp->next :
tmp->prev;
423p = NULL;
424if (tmp == head)
425break;
426p = list_entry(tmp, struct vfsmount,
mnt_hash);
427if (p->mnt_parent == mnt
&& p->mnt_mountpoint == dentry) {
428found = p;
429break;
430}
431}
432return found;
433
}
439
struct vfsmount *lookup_mnt(struct path *path)
440
{
441struct vfsmount *child_mnt;
442spin_lock(&vfsmount_lock);
443if ((child_mnt =
__lookup_mnt(path->mnt, path->dentry, 1)))
444mntget(child_mnt);
445spin_unlock(&vfsmount_lock);
446return 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
{
525dput(nd->path.dentry);
526if (nd->path.mnt != path->mnt)
527mntput(nd->path.mnt);
528nd->path.mnt = path->mnt;
529nd->path.dentry = path->dentry;
530
}
---------------------------------------------------------------------
k.检查刚解析的分量是否指向一个目录(next.dentry->d_inode具有一个自定义的i_op->lookup方法)。如果没有,返回一个错误码-ENOTDIR,因为这个分量位于原路径名的中间,然后continue继续路径名的下一个分量。主要的循环到此结束。
7、减少对查找到的path的引用计数并返回。