原创 4.4bsd vfs中pathname translate的分析

2008-4-3 23:25 4967 6 6 分类: 软件与OS

路径名的解析是比较复杂的部分,bsd把这部分分成两块,称为top half和bottom half(但这和linux中中断处理的top
half,bottom half意思不大一样)。top half就是vfs层做的与具体文件系统无关的处理,bootom
half则是文件系统相关的。
整个过程大体分为4步:
1.从用户空间buffer或者网络层的buffer(对于remote
fs)拷贝要解析的pathname
2.确定适合的起始点(一般设为当前目录或根目录),获取其vnode
3.调用文件系统相关的lookup函数解析pathname,如果出错会返回错误给vfs层,否则返回对应的vnode。
4.如果fs相关lookup没有返回错误,则要判断pathname是否已经全部解析完(即每个component是否都处理了),如果没有解析完,并且返回的vnode不是个目录,则返回错误,否则检查返回的vnode是否始一个另一个文件系统的mount点,如果是
则设置lookup的目录为这个新文件系统的mount点,这个时候我们的pathname将要跨文件系统,最后返回到第3步,重新做,直到所有的pathname
compoent 全部解析完。
核心的函数是lookup(),它被namei()调用完成解析工作。
int
lookup(ndp)
 register struct nameidata *ndp;
{
1. register char
*cp;  /* pointer into pathname argument */
2 register struct vnode *dp =
0; /* the directory we are searching */
3 struct vnode *tdp;  /* saved dp
*/
4 struct mount *mp;  /* mount table entry */
5 int docache;   /* == 0
do not cache last component */
6 int wantparent;   /* 1 => wantparent or
lockparent flag */
7 int rdonly;   /* lookup read-only flag bit */
8 int
error = 0;
9 struct componentname *cnp = &ndp->ni_cnd;
10 struct
proc *p = cnp->cn_proc;


 /*
  * Setup: break out flag bits into variables.
 
*/
11 wantparent = cnp->cn_flags & (LOCKPARENT |
WANTPARENT);
12 docache = (cnp->cn_flags & NOCACHE) ^
NOCACHE;
13 if (cnp->cn_nameiop == DELETE ||
14     (wantparent
&& cnp->cn_nameiop != CREATE))
15  docache = 0;
16 rdonly =
cnp->cn_flags & RDONLY;
17 ndp->ni_dvp =
NULL;
18 cnp->cn_flags &= ~ISSYMLINK;
19 dp =
ndp->ni_startdir;
20 ndp->ni_startdir = NULLVP;
21 vn_lock(dp,
LK_EXCLUSIVE | LK_RETRY, p);


dirloop:
 /*
  * Search a new directory.
  *
  * The cn_hash
value is for use by vfs_cache.
  * The last component of the filename is left
accessible via
  * cnp->cn_nameptr for callers that need the name. Callers
needing
  * the name set the SAVENAME flag. When done, they assume
  *
responsibility for freeing the pathname buffer.
  */
22 cnp->cn_consume
= 0;
23 cnp->cn_hash = 0;
24 for (cp = cnp->cn_nameptr; *cp != 0
&& *cp != '/'; cp++)
25  cnp->cn_hash += (unsigned
char)*cp;
26 cnp->cn_namelen = cp - cnp->cn_nameptr;
27 if
(cnp->cn_namelen > NAME_MAX) {
28  error = ENAMETOOLONG;
29  goto
bad;
30 }
#ifdef NAMEI_DIAGNOSTIC
31 { char c = *cp;
32 *cp =
'\0';
33 printf("{%s}: ", cnp->cn_nameptr);
34 *cp = c;
}
#endif
35 ndp->ni_pathlen -=
cnp->cn_namelen;
36 ndp->ni_next = cp;
37 cnp->cn_flags |=
MAKEENTRY;
38 if (*cp == '\0' && docache ==
0)
39  cnp->cn_flags &= ~MAKEENTRY;
40 if (cnp->cn_namelen == 2
&&
41     cnp->cn_nameptr[1] == '.' &&
cnp->cn_nameptr[0] == '.')
42  cnp->cn_flags |=
ISDOTDOT;
43 else
44  cnp->cn_flags &= ~ISDOTDOT;
45 if
(*ndp->ni_next == 0)
46 cnp->cn_flags |=
ISLASTCN;
47 else
48  cnp->cn_flags &= ~ISLASTCN;



 /*
  * Check for degenerate name (e.g. / or "")
  * which is a way
of talking about a directory,
  * e.g. like "/." or ".".
  */
49 if
(cnp->cn_nameptr[0] == '\0') {
50  if (dp->v_type != VDIR)
{
51   error = ENOTDIR;
52   goto bad;
  }
53  if
(cnp->cn_nameiop != LOOKUP) {
54  error = EISDIR;
55   goto
bad;
  }
56  if (wantparent) {
57   ndp->ni_dvp =
dp;
58   VREF(dp);
  }
59  ndp->ni_vp = dp;
60  if
(!(cnp->cn_flags & (LOCKPARENT | LOCKLEAF)))
61   VOP_UNLOCK(dp, 0,
p);
62  if (cnp->cn_flags & SAVESTART)
63   panic("lookup:
SAVESTART");
64  return (0);
 }


 /*
  * Handle "..": two special cases.
  * 1. If at root directory
(e.g. after chroot)
  *    or at absolute root directory
  *    then
ignore it so can't get out.
  * 2. If this vnode is the root of a
mounted
  *    filesystem, then replace it with the
  *    vnode which was
mounted on so we take the
  *    .. in the other file system.
 
*/
65 if (cnp->cn_flags & ISDOTDOT) {
66  for (;;) {
67   if (dp
== ndp->ni_rootdir || dp == rootvnode) {
68    ndp->ni_dvp =
dp;
69    ndp->ni_vp = dp;
70    VREF(dp);
71    goto
nextname;
   }
72   if ((dp->v_flag & VROOT) == 0 ||
73      
(cnp->cn_flags & NOCROSSMOUNT))
74    break;
75   tdp =
dp;
76   dp =
dp->v_mount->mnt_vnodecovered;
77   vput(tdp);
78   VREF(dp);
79   vn_lock(dp,
LK_EXCLUSIVE | LK_RETRY, p);
  }
 }


 /*
  * We now have a segment name to search for, and a directory to
search.
  */
unionlookup:
80 ndp->ni_dvp = dp;
81 ndp->ni_vp =
NULL;
82 if (error = VOP_LOOKUP(dp, &ndp->ni_vp, cnp)) {
#ifdef
DIAGNOSTIC
83  if (ndp->ni_vp != NULL)
84   panic("leaf should be
empty");
#endif
#ifdef NAMEI_DIAGNOSTIC
85  printf("not
found\n");
#endif
86  if ((error == ENOENT) &&
87     
(dp->v_flag & VROOT) &&
88      (dp->v_mount->mnt_flag
& MNT_UNION)) {
89   tdp = dp;
90   dp =
dp->v_mount->mnt_vnodecovered;
91   vput(tdp);
92   VREF(dp);
93   vn_lock(dp,
LK_EXCLUSIVE | LK_RETRY, p);
94   goto unionlookup;
  }


95  if (error != EJUSTRETURN)
96   goto bad;
  /*
   * If creating
and at end of pathname, then can consider
   * allowing file to be
created.
   */
97  if (rdonly) {
98   error = EROFS;
99   goto
bad;
  }
  /*
   * We return with ni_vp NULL to indicate that the
entry
   * doesn't currently exist, leaving a pointer to the
   *
(possibly locked) directory inode in ndp->ni_dvp.
   */
100  if
(cnp->cn_flags & SAVESTART) {
101   ndp->ni_startdir =
ndp->ni_dvp;
102   VREF(ndp->ni_startdir);
  }
103  return
(0);
 }
#ifdef NAMEI_DIAGNOSTIC
104 printf("found\n");
#endif


 /*
  * Take into account any additional components consumed by
  * the
underlying filesystem.
  */
105 if (cnp->cn_consume > 0)
{
106  cnp->cn_nameptr += cnp->cn_consume;
107  ndp->ni_next +=
cnp->cn_consume;
108  ndp->ni_pathlen -=
cnp->cn_consume;
109  cnp->cn_consume = 0;
 }


110 dp = ndp->ni_vp;
 /*
  * Check to see if the vnode has been
mounted on;
  * if so find the root of the mounted file system.
 
*/
111 while (dp->v_type == VDIR && (mp = dp->v_mountedhere)
&&
112        (cnp->cn_flags & NOCROSSMOUNT) == 0)
{
113  if (vfs_busy(mp, 0, 0, p))
114   continue;
115  error =
VFS_ROOT(mp, &tdp);
116  vfs_unbusy(mp, p);
117  if
(error)
118   goto bad2;
119  vput(dp);
120  ndp->ni_vp = dp =
tdp;
 }


 /*
  * Check for symbolic link
  */
121 if ((dp->v_type == VLNK)
&&
122     ((cnp->cn_flags & FOLLOW) || *ndp->ni_next ==
'/')) {
123  cnp->cn_flags |= ISSYMLINK;
124  return (0);
 }


nextname:
 /*
  * Not a symbolic link.  If more pathname,
  *
continue at next component, else return.
  */
125 if (*ndp->ni_next ==
'/') {
126  cnp->cn_nameptr = ndp->ni_next;
127  while
(*cnp->cn_nameptr == '/')
{
128   cnp->cn_nameptr++;
129   ndp->ni_pathlen--;
  }
130  vrele(ndp->ni_dvp);
131  goto
dirloop;
 }
 /*
  * Disallow directory write attempts on read-only file
systems.
  */
132 if (rdonly &&
133     (cnp->cn_nameiop ==
DELETE || cnp->cn_nameiop == RENAME)) {
134  error = EROFS;
135  goto
bad2;
 }
136 if (cnp->cn_flags & SAVESTART)
{
137  ndp->ni_startdir =
ndp->ni_dvp;
138  VREF(ndp->ni_startdir);
 }
139 if
(!wantparent)
140  vrele(ndp->ni_dvp);
141 if ((cnp->cn_flags &
LOCKLEAF) == 0)
142  VOP_UNLOCK(dp, 0, p);
143 return (0);


bad2:
144 if ((cnp->cn_flags & LOCKPARENT) &&
*ndp->ni_next == '\0')
145  VOP_UNLOCK(ndp->ni_dvp, 0,
p);
146 vrele(ndp->ni_dvp);
bad:
147 vput(dp);
148 ndp->ni_vp
= NULL;
149 return (error);
}
传递给lookup的参数就是前面定义的那个struct
nameidata,要解析的pathname存在其ni_ptr域中,pathname的长度则存在ni_pathlen中。解析的start目录由ni_startdir指定。整个操作是一个段一个段进行的,所谓一个段或者说一个component就是指两个'/'间的目录或文件名,比如:"/usr/local/src/tmp”就分为usr,local,src,tmp4个段。
另外ndp->ni_cnd->cn_flags指定一些flag,可以为create,delete,rename等,表示这次lookup是为了创建还是删除文件(目录)。此外如果这个flag指定了LOCKPARENT,则最终我们将要返回其parent的vnode,并且要locked,如果指定了WANTPARENT,则返回其parent
unlocked的vnode,如果指定LOCKLEAF,则在返回待解析(查找)的pathname
的locked住的vnode,否则就返回unlocked
的vnode。parent vnode在ndp->ni_dvp,而该pathname的vnode在ndp->ni_vp
中返回。
1~21句做一些准备工作,根据ndp的cn_flags初始化一些局部变量留待下面用,比如是否需要把这次解析的pathname放入name
cache中,如果docache==0,则表示不需要。又如是否需要返回parent vnode等。最后获得start
directory即解析的开始点。
22~30句获得下一个component的名字,长度,计算它的hash值,pathname
名称在cnp->cn_nameptr中,最后抽取的这个component
name就是cnp->cn_nameptr到局部变量cp间的那段string,component
name的长度则保存在cnp->cn_namelen中,如果这个长度大于NAME_MAX,则返回ENAMETOOLONG,跳到bad处退出。
31~34句是输出调试信息,即打印刚抽取的component的name。35~36更新ndp->ni_pathlen值,表示剩下的pathname的长度,并让ndp->ni_next指向下一个component的头。37~38句根据情况设置MAKEENTRY,如果设置了这个标志,表示这次lookup的结果要缓存到name
cache中。38~48句则是判断是否这个component是个".."或是最后一个需要解析的component。
49~64句处理被解析的component是空string的特殊情况,这中情况下,如果该component不是个目录或者是目录但本次namei的操作不是LOOKUP,则都返回错误退出。否则根据flag返回最后解析的结果。vnode从ndp->ni_vp,parent
vnode从ndp->ni_dvp返回(均等于dp,即最后一次解析的vnode)。
65~79句则是处理当前将被解析的component名是".."的情况,分两种情况处理:1,如果它是个root目录,则ndp->ni_dvp
= dp;ndp->ni_vp = dp;VREF(dp);后跳到下一个component处理;2,如果它是另一个文件系统的mount点,则dp =
dp->v_mount->mnt_vnodecovered将最后解析的vnode替换为新文件系统mount点的vnode,开始进入另一个文件系统解析。
unionlookup这段(80~94句)实际解析刚刚抽取的component
name,它调用底层文件系统相关的lookup函数,如果出错则panic,但对于一种特殊情况,86~94句,如果文件系统相关lookup返回的是文件或目录不存在,并且该vnode是root
目录,并且它所在文件系统和地层的文件系统是union在一起的,则替换vnode为mnt_vnodecovered,重新解析。
95~103句根据flag返回适当的值最后退出。
105~109句根据底层fs
consume的字节调整下一个component的头位置,剩余pathname的长度等参数。
110~120句判断刚刚解析得到的vnode是否是一个目录且是否是一个mount点,如果是,如果cnp->cn_flags
没有指定NOCROSSMOUNT,则我们调用VFS_ROOT找到该mounted文件系统的root
vnode,再替换刚解析的结果vnode即那个dp变量。
121~124句处理symbolic
link的情况,如果是,并且flags指定了FOLLOW,设置一个ISSYMLINK然后退出。
125~131句则是找到下一个component
name的开始位置,再jump到dirloop开始解析下一个component。
132~143句则是在解析完后做一些后续处理,比如如果这个vfs是read-only的,而这次lookup是为delete和rename服务的,则返回EROFS错误。
最终提供给内核其他部分解析pathname的是namei()函数,它通过调用lookup()完成工作。代码如下:
int
namei(ndp)
 register struct nameidata *ndp;
{
A. register struct
filedesc *fdp; /* pointer to file descriptor state */
 register char *cp;  /*
pointer into pathname argument */
 register struct vnode *dp; /* the
directory we are searching */
 struct iovec aiov;  /* uio for reading
symbolic links */
 struct uio auio;
 int error, linklen;
 struct
componentname *cnp = &ndp->ni_cnd;
 struct proc *p =
cnp->cn_proc;


 ndp->ni_cnd.cn_cred = ndp->ni_cnd.cn_proc->p_ucred;
#ifdef
DIAGNOSTIC
 if (!cnp->cn_cred || !cnp->cn_proc)
  panic ("namei: bad
cred/proc");
 if (cnp->cn_nameiop & (~OPMASK))
  panic ("namei:
nameiop contaminated with flags");
 if (cnp->cn_flags &
OPMASK)
  panic ("namei: flags contaminated with
nameiops");
#endif
 fdp = cnp->cn_proc->p_fd;


 /*
  * Get a buffer for the name to be translated, and copy the
  *
name into the buffer.
  */
 if ((cnp->cn_flags & HASBUF) ==
0)
  MALLOC(cnp->cn_pnbuf, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK);
 if
(ndp->ni_segflg == UIO_SYSSPACE)
  error = copystr(ndp->ni_dirp,
cnp->cn_pnbuf,
       MAXPATHLEN,
&ndp->ni_pathlen);
 else
  error = copyinstr(ndp->ni_dirp,
cnp->cn_pnbuf,
       MAXPATHLEN, &ndp->ni_pathlen);
 if (error)
{
  free(cnp->cn_pnbuf, M_NAMEI);
  ndp->ni_vp = NULL;
  return
(error);
 }
 ndp->ni_loopcnt = 0;
#ifdef KTRACE
 if
(KTRPOINT(cnp->cn_proc,
KTR_NAMEI))
  ktrnamei(cnp->cn_proc->p_tracep,
cnp->cn_pnbuf);
#endif


 /*
  * Get starting point for the translation.
  */
 if
((ndp->ni_rootdir = fdp->fd_rdir) == NULL)
  ndp->ni_rootdir =
rootvnode;
 dp = fdp->fd_cdir;
B. VREF(dp);
 for (;;)
{
  /*
   * Check if root directory should replace current
directory.
   * Done at start of translation and after symbolic link.
  
*/
  cnp->cn_nameptr = cnp->cn_pnbuf;
  if (*(cnp->cn_nameptr) ==
'/') {
   vrele(dp);
   while (*(cnp->cn_nameptr) == '/')
{
    cnp->cn_nameptr++;
    ndp->ni_pathlen--;
   }
   dp =
ndp->ni_rootdir;
   VREF(dp);
  }
  ndp->ni_startdir =
dp;
  if (error = lookup(ndp)) {
   FREE(cnp->cn_pnbuf,
M_NAMEI);
   return (error);
C.  }
  /*
   * Check for symbolic
link
   */
  if ((cnp->cn_flags & ISSYMLINK) == 0) {
   if
((cnp->cn_flags & (SAVENAME | SAVESTART)) ==
0)
    FREE(cnp->cn_pnbuf, M_NAMEI);
   else
    cnp->cn_flags |=
HASBUF;
   return (0);
  }
  if ((cnp->cn_flags & LOCKPARENT)
&& ndp->ni_pathlen == 1)
   VOP_UNLOCK(ndp->ni_dvp, 0,
p);
  if (ndp->ni_loopcnt++ >= MAXSYMLINKS) {
   error =
ELOOP;
   break;
  }
  if (ndp->ni_pathlen > 1)
   MALLOC(cp,
char *, MAXPATHLEN, M_NAMEI, M_WAITOK);
  else
   cp =
cnp->cn_pnbuf;
  aiov.iov_base = cp;
  aiov.iov_len =
MAXPATHLEN;
  auio.uio_iov = &aiov;
  auio.uio_iovcnt =
1;
  auio.uio_offset = 0;
  auio.uio_rw = UIO_READ;
  auio.uio_segflg =
UIO_SYSSPACE;
  auio.uio_procp = (struct proc *)0;
  auio.uio_resid =
MAXPATHLEN;
  if (error = VOP_READLINK(ndp->ni_vp, &auio,
cnp->cn_cred)) {
   if (ndp->ni_pathlen > 1)
    free(cp,
M_NAMEI);
   break;
  }
  linklen = MAXPATHLEN -
auio.uio_resid;
  if (linklen + ndp->ni_pathlen >= MAXPATHLEN)
{
   if (ndp->ni_pathlen > 1)
    free(cp, M_NAMEI);
   error =
ENAMETOOLONG;
   break;
  }
  if (ndp->ni_pathlen > 1)
{
   bcopy(ndp->ni_next, cp + linklen,
ndp->ni_pathlen);
   FREE(cnp->cn_pnbuf,
M_NAMEI);
   cnp->cn_pnbuf = cp;
  }
else
   cnp->cn_pnbuf[linklen] = '\0';
  ndp->ni_pathlen +=
linklen;
  vput(ndp->ni_vp);
  dp =
ndp->ni_dvp;
D. }
 FREE(cnp->cn_pnbuf,
M_NAMEI);
 vrele(ndp->ni_dvp);
 vput(ndp->ni_vp);
 ndp->ni_vp
= NULL;
 return
(error);
}
这个代码,A~B这段将要解析的pathname拷贝到buffer中,还有就是做了点参数检查
B~C这段设置start
directory,设置nameptr和pathname的长度,最后调用lookup()完成解析。
C~D段则是特别用来处理symbolic
link的,如果lookup()在cnp->cn_flags 中返回了ISSYMLINK,表示lookup()最后解析的是一个symbolic
link,而且ndp里面的标志还指定了FOLLOW,这就要求我们跟踪进symbolic link继续解析。
代码通过分配一个uio,调用
VOP_READLINK读取symbolic link的内容,调整被解析的pathname指向symbolic
link指向的文件,重新回到B解析,B处的for(;;)循环一直解析完symbolic link后退出,但是如果遇到过多的link,或者循环的symbolic
link,则至多解析MAXSYMLINKS=8次,就返回 ELOOP,这样避免了死循环。


文章评论0条评论)

登录后参与讨论
我要评论
0
6
关闭 站长推荐上一条 /2 下一条