现在,我们的“路径行走”只剩下最后一个小问题需要处理了——符号链接。
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk
nested_symlink 就是用来处理符号链接的,现在 nd 还“站”在原来的目录上,next 才指向了当前这个符号链接。咱们进去看看:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink
从本质上来讲符号链接就是一个路径字符串,这和硬连接有着本质上的区别(请参考【dentry-inode 结构图】)。而且对于创建目录的链接,硬连接有着严格的限制(比如普通用户就不允许创建目录的硬连接),但是符号链接就没有那么多的限制,用户可以随意创建目录的链接,甚至可以创建一个链接的死循环(比如:a->b;b->c;c->a)。既然不限制创建各式各样的符号链接,那么在读取的时候就需要格外小心了,Kernel 设置了两个限制位,这里我们就遇到了第一个:MAX_NESTED_LINKS,它的值是 8,它限制了符号链接的嵌套(递归)层数,那么什么时候会发生嵌套(递归)呢,别着急,等我们游览完符号链接的时候自然就清楚了,这里先卖个关子;还有一个是链接长度,这个比较好理解,马上我们就会遇到。
回到我们的旅程,这里先检查嵌套层数,一共有两个地方来对嵌套进行控制:一个是进程(1581);另一个是当前的“路径行走”(1586)。没问题的话就可以进行下一步了:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink
符号链接的目标很有可能也是一个符号链接,所以应该用一个循环来跟踪它。仔细观察这个循环体,我们发现了一个老朋友 walk_component,还记得吗,当它返回正值的时候(其实就是 1)就表明当前目标是一个符号链接(我们就是这么进来的,当然应该记得了),这时就需要再一次循环(1600)。既然这样的话那我们就大胆的猜测 follow_link 也应该和 link_path_walk 差不多,返回时 nd 也应该是“站在”最终目标所在的目录上,然后在 walk_component 的帮助下“站上”最终目标。
现在就来验证我们的猜测:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink > follow_link
在这里就是 Kernel 给符号链接设置的第二个限制:链接长度——简单地说就是在一个路径中符号链接的个数——不能超过 40 个。因为我门刚刚从 rcu-walk 模式切换过来,而 rcu-walk 是不允许抢占的,所以现在需要看看刚才有没有想要抢占但又抢占失败的进程,如果有的话就让人家先运行,毕竟我们现在是 ref-walk 模式,不是那么着急的,这就是 cond_resched 所做的(838)。接下来就是更新当前这个符号链接的访问时间(841),然后 nd_set_link 先把当前递归深度相应的路径字符串指针初始化成 NULL。揭晓来就要真正的“跟随链接”了。
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink > follow_link
这里主要就是启动具体文件系统自己的“跟随链接”机制,等它返回后 nd 中的 saved_names[nd->depth] 就会保存一个指向路径字符串的指针,随后我们就用着个字符串启动一次“全新的”路径查找。
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink > follow_link
s 就是上面提到的那个路径字符串,看看这个 if (s) 里的代码,是不是似曾相似?其中第一个 if(857)是错误处理我们不用关心。请看看第二个,如果路径以“/”开头就设置一下预设根目录和当前路径,是不是很像当年那个 path_init?如果不是“/”开头呢,那就以当前路径为起点,而这时 nd 就是“站在”当前路径上的,所以不用做啥处理直接就可以使用。接下来又是一个老朋友 link_path_walk,其实都不能说是老朋友,因为我们现在还在 link_path_walk 的肚子里呢。但现在还不能算是符号链接的嵌套(递归),还记得这个嵌套(递归)计数在哪里么?没错是在 nested_symlink,也就是说只有再次进入 nested_symlink 才能算是递归一次。想想我们是怎么进来的,别忘了 link_path_walk 并不处理路径中的最终目标,所以要想递归就必须在子路径中加入符号链接。简而言之,符号链接的嵌套(递归)是发生在该符号链接所指目标的路径中存在另一个符号链接。
举个例子,比如现在有一个符号链接“a”它指向一个目录“/tmp/dir/”,就像这样“a -> /tmp/dir/”。这个目录下还有一个文件“/tmp/dir/file”,这时有另一个符号链接“b”,我们把它指向“a/file”,就像这样“b -> a/file”。如果这时访问“b”,就会递归一次,因为“b”所指的路径中“a/”就是一个符号链接且是一个子路径。
通过这样的解释,大家应该了解在什么情况下会发生嵌套(递归)了吧。从 link_path_walk 返回之后,nd 应该已经“站在”最终目录等待着对最终目标的处理了。这正好应证了刚开始我们的猜测,接下来队最终目标的处理就会交给 walk_component,如果这个最终目标也是一个符号链接那么 walk_component 就会返回 1 并再一次 nested_symlink 里的 do-while 循环。
当最终目标不是符号链接的时候,nested_symlink 的使命也就完成了:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink
在这里就会恢复嵌套(递归)计数。
现在符号链接我们也参观完毕了,那么 link_path_walk 也就结束了,接下来就要对打开最终目标了。
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk
点击(此处)折叠或打开
...
- if (err) {
- err = nested_symlink(&next, nd);
- if (err)
- return err;
- }
...
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink
点击(此处)折叠或打开
- static inline int nested_symlink(struct path *path, struct nameidata *nd)
- {
- int res;
-
- if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
- path_put_conditional(path, nd);
- path_put(&nd->path);
- return -ELOOP;
- }
- BUG_ON(nd->depth >= MAX_NESTED_LINKS);
-
- nd->depth++;
- current->link_count++;
...
回到我们的旅程,这里先检查嵌套层数,一共有两个地方来对嵌套进行控制:一个是进程(1581);另一个是当前的“路径行走”(1586)。没问题的话就可以进行下一步了:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink
点击(此处)折叠或打开
...
- do {
- struct path link = *path;
- void *cookie;
-
- res = follow_link(&link, nd, &cookie);
- if (res)
- break;
- res = walk_component(nd, path, LOOKUP_FOLLOW);
- put_link(nd, &link, cookie);
- } while (res > 0);
...
现在就来验证我们的猜测:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink > follow_link
点击(此处)折叠或打开
- static __always_inline int
- follow_link(struct path *link, struct nameidata *nd, void **p)
- {
...
- error = -ELOOP;
- if (unlikely(current->total_link_count >= 40))
- goto out_put_nd_path;
-
- cond_resched();
- current->total_link_count++;
-
- touch_atime(link);
- nd_set_link(nd, NULL);
...
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink > follow_link
点击(此处)折叠或打开
...
- nd->last_type = LAST_BIND;
- *p = dentry->d_inode->i_op->follow_link(dentry, nd);
- error = PTR_ERR(*p);
- if (IS_ERR(*p))
- goto out_put_nd_path;
...
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink > follow_link
点击(此处)折叠或打开
...
- error = 0;
- s = nd_get_link(nd);
- if (s) {
- if (unlikely(IS_ERR(s))) {
- path_put(&nd->path);
- put_link(nd, link, *p);
- return PTR_ERR(s);
- }
- if (*s == '/') {
- set_root(nd);
- path_put(&nd->path);
- nd->path = nd->root;
- path_get(&nd->root);
- nd->flags |= LOOKUP_JUMPED;
- }
- nd->inode = nd->path.dentry->d_inode;
- error = link_path_walk(s, nd);
- if (unlikely(error))
- put_link(nd, link, *p);
- }
-
- return error;
...
- }
举个例子,比如现在有一个符号链接“a”它指向一个目录“/tmp/dir/”,就像这样“a -> /tmp/dir/”。这个目录下还有一个文件“/tmp/dir/file”,这时有另一个符号链接“b”,我们把它指向“a/file”,就像这样“b -> a/file”。如果这时访问“b”,就会递归一次,因为“b”所指的路径中“a/”就是一个符号链接且是一个子路径。
通过这样的解释,大家应该了解在什么情况下会发生嵌套(递归)了吧。从 link_path_walk 返回之后,nd 应该已经“站在”最终目录等待着对最终目标的处理了。这正好应证了刚开始我们的猜测,接下来队最终目标的处理就会交给 walk_component,如果这个最终目标也是一个符号链接那么 walk_component 就会返回 1 并再一次 nested_symlink 里的 do-while 循环。
当最终目标不是符号链接的时候,nested_symlink 的使命也就完成了:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink
点击(此处)折叠或打开
...
- current->link_count--;
- nd->depth--;
- return res;
- }
现在符号链接我们也参观完毕了,那么 link_path_walk 也就结束了,接下来就要对打开最终目标了。