symlinkの階層
[root@localhost dir1]# cat mksymlink1.c
[root@localhost dir1]# cat a0
[root@localhost dir1]# cat mksymlink2.c
シンボリックリンクは inode_operationsの.symlinkコールバックがコールされ、掛かるinodeのデータブロックに設定されているリンクpathが走査されます。path走査毎にcurrent->total_link_count++とし、 current->total_link_count>= 40でエラーです。.symlinkコールバックが定義されていないファイルシステム(sysfs/procfs等)のfast_symlinkでは、シンボリックリンクpathはstruct nameidata nd.saved_names[MAX_NESTED_LINKS + 1]に設定され、走査時current->link_count++で、current->link_count>MAX_NESTED_LINKS(8)でエラーとなります。
chrootで/lib等をlinkすると、chroot下のlib->/libですが、chroot後はlibは/libで、/lib->/libで多重階層エラーとなります。
#include <stdio.h> #include <stddef.h> void main() { int i; char buff[30]; system("echo \"12345\" > a0"); for (i = 1; i < 42; i++) { sprintf(buff, "ln -s a%d a%d", i - 1, i); system(buff); } }[root@localhost dir1]# ./a.out
[root@localhost dir1]# cat a0
12345[root@localhost dir1]# cat a40
12345[root@localhost dir1]# cat a41
cat: a41: シンボリックリンクの階層が多すぎます
[root@localhost dir1]# cat mksymlink2.c
#include <stdio.h> #include <stddef.h> void main(int argc, char* argv[]) { int i; char path[32], buff[100]; for (i = 1; i < 50; i++) { sprintf(buff, "mkdir a%d", i); system(buff); } getcwd(path, 32); printf("%s\n", path); for (i = 1; i < 50; i++) { sprintf(buff, "ln -s %s/a%d a%d", path, i, i - 1); system(buff); } }
[root@localhost dir1]# ls a0/a2/a3/a4/a5/a6/a7/a8/a9/a10/a11/a12/a13/a14/a15/a16/a17/a18/a19/a20/a21/a22/a23/a24/a25/a26/a27/a28/a29/a30/a31/a32/a33/a34/a35/a36/a37/a38/a39/a40 a41
[root@localhost dir1]# ls a0/a2/a3/a4/a5/a6/a7/a8/a9/a10/a11/a12/a13/a14/a15/a16/a17/a18/a19/a20/a21/a22/a23/a24/a25/a26/a27/a28/a29/a30/a31/a32/a33/a34/a35/a36/a37/a38/a39/a40/a41 ls: jja0/a2/a3/a4/a5/a6/a7/a8/a9/a10/a11/a12/a13/a14/a15/a16/a17/a18/a19/a20/a21/a22/a23/a24/a25/a26/a27/a28/a29/a30/a31/a32/a33/a34/a35/a36/a37/a38/a39/a40/a41 にアクセスできません: シンボリックリンクの階層が多すぎます
シンボリックリンクは inode_operationsの.symlinkコールバックがコールされ、掛かるinodeのデータブロックに設定されているリンクpathが走査されます。path走査毎にcurrent->total_link_count++とし、 current->total_link_count>= 40でエラーです。.symlinkコールバックが定義されていないファイルシステム(sysfs/procfs等)のfast_symlinkでは、シンボリックリンクpathはstruct nameidata nd.saved_names[MAX_NESTED_LINKS + 1]に設定され、走査時current->link_count++で、current->link_count>MAX_NESTED_LINKS(8)でエラーとなります。
struct nameidata { struct path path; struct qstr last; struct path root; struct inode *inode; /* path.dentry.d_inode */ unsigned int flags; unsigned seq; int last_type; unsigned depth; char *saved_names[MAX_NESTED_LINKS + 1]; /* Intent data */ union { struct open_intent open; } intent; }; static int link_path_walk(const char *name, struct nameidata *nd) { struct path next; int err; while (*name=='/') name++; if (!*name) return 0; for(;;) { struct qstr this; long len; int type; err = may_lookup(nd); if (err) break; len = hash_name(name, &this.hash); this.name = name; this.len = len; type = LAST_NORM; if (name[0] == '.') switch (len) { case 2: if (name[1] == '.') { type = LAST_DOTDOT; nd->flags |= LOOKUP_JUMPED; } break; case 1: type = LAST_DOT; } if (likely(type == LAST_NORM)) { struct dentry *parent = nd->path.dentry; nd->flags &= ~LOOKUP_JUMPED; if (unlikely(parent->d_flags & DCACHE_OP_HASH)) { err = parent->d_op->d_hash(parent, nd->inode, &this); if (err < 0) break; } } if (!name[len]) goto last_component; do { len++; } while (unlikely(name[len] == '/')); if (!name[len]) goto last_component; name += len; err = walk_component(nd, &next, &this, type, LOOKUP_FOLLOW); if (err < 0) return err; if (err) { err = nested_symlink(&next, nd); if (err) return err; } if (can_lookup(nd->inode)) continue; err = -ENOTDIR; break; /* here ends the main loop */ last_component: nd->last = this; nd->last_type = type; return 0; } terminate_walk(nd); return err; } enum { MAX_NESTED_LINKS = 8 }; 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++; do { struct path link = *path; void *cookie; res = follow_link(&link, nd, &cookie); if (!res) res = walk_component(nd, path, &nd->last, nd->last_type, LOOKUP_FOLLOW); put_link(nd, &link, cookie); } while (res > 0); current->link_count--; nd->depth--; return res; } static __always_inline int follow_link(struct path *link, struct nameidata *nd, void **p) { int error; struct dentry *dentry = link->dentry; BUG_ON(nd->flags & LOOKUP_RCU); if (link->mnt == nd->path.mnt) mntget(link->mnt); if (unlikely(current->total_link_count >= 40)) { *p = ERR_PTR(-ELOOP); /* no ->put_link(), please */ path_put(&nd->path); return -ELOOP; } cond_resched(); current->total_link_count++; touch_atime(link->mnt, dentry); nd_set_link(nd, NULL); error = security_inode_follow_link(link->dentry, nd); if (error) { *p = ERR_PTR(error); /* no ->put_link(), please */ path_put(&nd->path); return error; } nd->last_type = LAST_BIND; *p = dentry->d_inode->i_op->follow_link(dentry, nd); error = PTR_ERR(*p); if (!IS_ERR(*p)) { char *s = nd_get_link(nd); error = 0; if (s) error = __vfs_follow_link(nd, s); else if (nd->last_type == LAST_BIND) { nd->flags |= LOOKUP_JUMPED; nd->inode = nd->path.dentry->d_inode; if (nd->inode->i_op->follow_link) { /* stepped on a _really_ weird one */ path_put(&nd->path); error = -ELOOP; } } } return error; }
備考
ディレクトリにハードクリンクできないのは、ファイル実装故でなく、パス走査での参照でない故、プロセスのrootディレクトリを超えての参照が可能となる運用上の理由からかと思います。chrootで/lib等をlinkすると、chroot下のlib->/libですが、chroot後はlibは/libで、/lib->/libで多重階層エラーとなります。