ls是Linux和Unix下最常使用的命令之一,主要用来列举目录下的文件信息,-l参数允许查看当前目录下所有可见文件的详细属性,包括文件属性、所有者、文件大小等信息。但是,当其显示符号链接的属性时,无论其指向文件属性如何,都会显示777,即任何人可读可写可执行。本文从ls命令源码出发,由浅入深地分析该现象的原因,简略探究了Linux 4.10下的符号链接链接、文件系统与权限的源码实现。
关键词:Linux ls 符号链接 文件系统 权限 源码分析
在Linux中每个文件有所有者、所在组、其它组的概念[11]。所有者一般为文件的创建者,谁创建了该文件,就天然的成为该文件的所有者;当某个用户创建了一个文件后,这个文件的所在组就是该用户所在的组;除开文件的所有者和所在组的用户外,系统的其它用户都是文件的其它组。ls 命令将每个由 Directory 参数指定的目录或者每个由 File 参数指定的名称写到标准输出,以及所要求的和标志一起的其它信息。ls -l中显示的内容常如下所示:
-rwxrw-r‐-1 root root 1213 Feb 2 09:39 abc |
---|
前10个字符说明了文件类型与权限。第一个字符代表文件(-)、目录(d),链接(l),其余字符每3个一组(rwx),读(r)、写(w)、执行(x)。第一组rwx:文件所有者的权限是读、写和执行;第二组rw-:与文件所有者同一组的用户的权限是读、写但不能执行;第三组r--:不与文件所有者同组的其他用户的权限是读不能写和执行。权限也可用数字表示为:r=4,w=2,x=1 因此rwx=4+2+1=7。
如前所述,若第一个字符显示为l,说明该文件是符号链接。符号链接(软链接)是一类特殊的文件, 其包含有一条以绝对路径或者相对路径的形式指向其它文件或者目录的引用[12]。符号链接的操作是透明的:对符号链接文件进行读写的程序会表现得直接对目标文件进行操作。某些需要特别处理符号链接的程序(如备份程序)可能会识别并直接对其进行操作。一个符号链接文件仅包含有一个文本字符串,其被操作系统解释为一条指向另一个文件或者目录的路径。它是一个独立文件,其存在并不依赖于目标文件。如果删除一个符号链接,它指向的目标文件不受影响。如果目标文件被移动、重命名或者删除,任何指向它的符号链接仍然存在,但是它们将会指向一个不复存在的文件。这种情况被有时被称为被遗弃。
但是,我们常常发现,创建符号链接其权限就会显示为lrwxrwxrwx,为什么?是ls命令对符号链接进行了处理,还是文件本身权限即如此?这样会不会带来一些安全隐患?怀着这些问题,本文由浅入深,从ls命令出发,探索了其背后的系统调用至vfs文件系统实现细节,力求解释这些问题。但作者水平有限,尚有很多细节不清楚,不对之处恳请批评指正。
ls命令是Linux shell下最常用的命令之一,主要用来列举目录下的文件信息。经过搜索引擎查找[1],要查看该命令的源代码需要下载对应软件包coreutils的源代码。其实只要知道了软件包的名字,既可以按照文献[1]的方法使用apt-get source下载,也可以从软件包coreutils的官网[2]下载。下载完毕后,使用source insight软件建立工程,即可方便地开始源码阅读。本文使用截止2017年2月18日最新版本的coreutils-8.26。
打开/src/ls.c,从main函数开始,忽略开始的初始化、颜色设定等内容,1451行调用的decode_switches对参数进行了一些处理,由于研究的是ls程序,所以第一个switch(ls_mode)关注LS_LS。接下来,1703行设置dereference = DEREF_UNDEFINED。关键部分代码为1752行的一个switch(true),它根据传入的参数,设置相应的标志,如-l设置format = long_format,H、I、L设置了dereference的一些模式,由于作者平时经常使用的是ls –l,所以仅关注-l选项下的情况,dereference仍然为DEREF_UNDEFINED。同时,该函数2125行对format == long_format的情况,做了一些格式输出上的工作。
发现1467行对dereference变量的判断影响了如何处理符号链接。若仅使用-l选项,dereference赋值为DEREF_NEVER,即仅仅拷贝复制符号链接自身。
若设置了递归枚举,设置一个哈希表来检测是否出现了目录环。接下来开辟cwd_file变量空间,其是指向fileinfo结构体的向量指针,保存了要描述的文件。关于fileinfo结构体源码中已经给了很好的注释,其中struct stat类型的变量stat具体描述了文件的信息,往往由stat()或lstat()函数返回。struct stat类型的定义可以在Linux源码include\uapi\asm-generic中找到,可以看出新版本64位中与常见文档中相比增加了许多pad填充,并将类型的一些宏定义取消了,直接采用了unsigned long。
下面主要调用了gobble_file函数,添加文件到当前的文件表中,即放到cwd_file中未使用的第一个位置上。3131行的switch语句根据dereference值,调用stat()或lstat()函数,由上面分析可知,ls –l是DEREF_NEVER,故调用lstat()函数,并将结果存入fileinfo.stat中(代码中是变量f);接下来,代码再根据结构体fileinfo.stat,对fileinfo其他部分赋值,在long_format情况下,fileinfo. scontext是SELinux有关函数lgetfilecon()获得的安全上下文(context)。同时也可以看出,默认-l选项是不对符号链接进行追踪的,所以调用的函数也都是对应版本。3235行判断当文件为符号链接且模式为long_format时会成立,由此赋值了fileinfo的linkok、linkmode等值。接下来函数主要根据要输出哪些信息,将fileinfo中的值保存了下来。
最后,在main函数1538行,根据文件的数目,调用print_current_files ()来输出文件内容,print_long_format()中第3967行通过filemodestring()函数将文件的读写执行权限填入了modebuf,该函数在filemode.c中定义。在填入时,ls程序未对符号链接做特殊处理,由此可见,符号链接权限问题的关键在于lstat()函数的实现是如何填入stat结构体中st_mode的。
stat是用来获得文件信息的系统调用[3],要寻找该系统调用的源代码,首先要理解系统调用的流程。这里参考了文献[4][5],根据自己理解,C语言调用stat函数时,调用的是C库对该函数的实现,接着执行库中函数的具体实现的代码,其中非常关键的一句代码就是 int 0x80,中断使得进程从用户态切换到内核态,中断处理程序然后开始执行内核中对应80号中断的系统调用处理程序的代码 system_call;system_call 系统调用处理程序就根据传入的系统调用号从系统调用服务程序数组中寻找对应系统调用服务程序,最后执行完成后按照调用顺序的相反顺序一步步返回结果。
根据一般规律,系统调用定义的名字就是在函数前面加一个sys_,由此在include\linux\syscalls.h中发现了一系列stat的声明,而fs\Stat.c中是对应的定义。(内核中使用SYSCALL_DEFINE2的宏定义来定义系统调用,展开就是声明的形式。)这里会发现,4.10内核中同时存在newstat与stat,无论新旧,实现都是使用了vfs_stat函数,传入参数为kstat,差别在于宏倒数第二个参数的类型。(但是这个参数具体有什么作用?实现中好像并没有用到这个参数。)
接下来需要看vfs_lstat的实现,他与vfs_stat都是调用了vfs_fstatat,区别在于vfs_lstat给最后一个参数赋值为了AT_SYMLINK_NOFOLLOW,说明不要追踪符号链接。
vfs_fstatat首先对flag参数进行检查,必须有(并非等于)规定的几种标识之一;然后调用user_path_at,根据返回结果再调用vfs_getattr和path_put;之后看似是一个错误处理,会返回到retry,没有错误则函数退出返回。对关键数据stat赋值的部分应该就在vfs_getattr函数了。
为了进行后面的分析,这里需要Linux内核文件系统有一定的了解[6][7]。Linux 有着极其丰富的文件系统,大体上可分如下几类:
网络文件系统,如 nfs、cifs 等;
磁盘文件系统,如 ext4、ext3 等;
特殊文件系统,如 proc、sysfs、ramfs、tmpfs 等。
实现以上这些文件系统并在 Linux 下共存的基础就是 Linux VFS(Virtual File System 又称 Virtual Filesystem Switch),即虚拟文件系统。VFS 作为一个通用的文件系统,抽象了文件系统的四个基本概念:文件、目录项 (dentry)、索引节点 (inode) 及挂载点,其在内核中为用户空间层的文件系统提供了相关的接口。VFS 实现了 open()、read() 、stat()等系统调并使得 cp 等用户空间程序可跨文件系统。VFS 真正实现了上述内容中:在 Linux 中除进程之外一切皆是文件。
Linux VFS 存在四个基本对象:超级块对象 (superblock object)、索引节点对象 (inode object)、目录项对象 (dentry object) 及文件对象 (file object)。超级块对象代表一个已安装的文件系统;索引节点对象代表一个文件;目录项对象代表一个目录项,如设备文件 event5 在路径 /dev/input/event5 中,其存在四个目录项对象:/ 、dev/ 、input/ 及 event5。文件对象代表由进程打开的文件。这四个对象与进程及磁盘文件间的关系如图,其中 d_inode 即为硬链接。为文件路径的快速解析,Linux VFS 设计了目录项缓存(Directory Entry Cache,即 dcache)。下面列出几个关键数据结构,并在关注的部分给出注释。
1 struct nameidata { //文件查找临时结构体
2
3 struct path path;//包含vfsmount挂载点和dentry目录项
4
5 struct qstr last;
6
7 struct path root;
8
9 struct inode *inode; /* path.dentry.d_inode */
10
11 unsigned int flags;
12
13 unsigned seq, m_seq;
14
15 int last_type; /*路径中的最后一个component的类型*/
16
17 unsigned depth; //符号链接嵌套的级别
18
19 int total_link_count;
20
21 struct saved {
22
23 struct path link;
24
25 struct delayed_call done;
26
27 const char *name;
28
29 unsigned seq;
30
31 } *stack, internal[EMBEDDED_LEVELS];
32
33 struct filename *name; //保存要查找的文件名
34
35 struct nameidata *saved;
36
37 struct inode *link_inode;
38
39 unsigned root_seq;
40
41 int dfd;
42
43 };
44
45 struct dentry {//目录项对象
46
47 /* RCU lookup touched fields */
48
49 unsigned int d_flags; /* protected by d_lock */
50
51 seqcount_t d_seq; /* per dentry seqlock */
52
53 struct hlist_bl_node d_hash; /* lookup hash list */
54
55 struct dentry *d_parent; /* parent directory */
56
57 struct qstr d_name;
58
59 struct inode *d_inode; /* Where the name belongs to - NULL is
60
61 * negative */
62
63 unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
64
65
66
67 /* Ref lookup also touches following */
68
69 struct lockref d_lockref; /* per-dentry lock and refcount */
70
71 const struct dentry_operations *d_op;//目录项方法
72
73 struct super_block *d_sb; /* The root of the dentry tree */
74
75 unsigned long d_time; /* used by d_revalidate */
76
77 void *d_fsdata; /* fs-specific data */
78
79
80
81 union {
82
83 struct list_head d_lru; /* LRU list */
84
85 wait_queue_head_t *d_wait; /* in-lookup ones only */
86
87 };
88
89 struct list_head d_child; /* child of parent list */
90
91 struct list_head d_subdirs; /* our children */
92
93 /*
94
95 * d_alias and d_rcu can share memory
96
97 */
98
99 union {
100
101 struct hlist_node d_alias; /* inode alias list */
102
103 struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
104
105 struct rcu_head d_rcu;
106
107 } d_u;
108
109 };
110
111 struct inode { //索引节点
112
113 umode_t i_mode; //文件类型与访问权限,是本文所关注的重点部分
114
115 unsigned short i_opflags;//2.6内核中没有的字段,哪里去找这个字段注释?
116
117 kuid_t i_uid;
118
119 kgid_t i_gid;
120
121 unsigned int i_flags;
122
123
124
125 #ifdef CONFIG_FS_POSIX_ACL
126
127 struct posix_acl *i_acl;
128
129 struct posix_acl *i_default_acl;
130
131 #endif
132
133
134
135 const struct inode_operations *i_op; //索引节点的操作
136
137 struct super_block *i_sb;
138
139 struct address_space *i_mapping;
140
141
142
143 #ifdef CONFIG_SECURITY
144
145 void *i_security;
146
147 #endif
148
149
150
151 /* Stat data, not accessed from path walking */
152
153 unsigned long i_ino;
154
155 /*
156
157 * Filesystems may only read i_nlink directly. They shall use the
158
159 * following functions for modification:
160
161 *
162
163 * (set|clear|inc|drop)_nlink
164
165 * inode_(inc|dec)_link_count
166
167 */
168
169 union {
170
171 const unsigned int i_nlink;
172
173 unsigned int __i_nlink;
174
175 };
176
177 dev_t i_rdev;
178
179 loff_t i_size;
180
181 struct timespec i_atime;
182
183 struct timespec i_mtime;
184
185 struct timespec i_ctime;
186
187 spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
188
189 unsigned short i_bytes;
190
191 unsigned int i_blkbits;
192
193 blkcnt_t i_blocks;
194
195
196
197 #ifdef __NEED_I_SIZE_ORDERED
198
199 seqcount_t i_size_seqcount;
200
201 #endif
202
203
204
205 /* Misc */
206
207 unsigned long i_state;
208
209 struct rw_semaphore i_rwsem;
210
211
212
213 unsigned long dirtied_when; /* jiffies of first dirtying */
214
215 unsigned long dirtied_time_when;
216
217
218
219 struct hlist_node i_hash;
220
221 struct list_head i_io_list; /* backing dev IO list */
222
223 #ifdef CONFIG_CGROUP_WRITEBACK
224
225 struct bdi_writeback *i_wb; /* the associated cgroup wb */
226
227
228
229 /* foreign inode detection, see wbc_detach_inode() */
230
231 int i_wb_frn_winner;
232
233 u16 i_wb_frn_avg_time;
234
235 u16 i_wb_frn_history;
236
237 #endif
238
239 struct list_head i_lru; /* inode LRU list */
240
241 struct list_head i_sb_list;
242
243 struct list_head i_wb_list; /* backing dev writeback list */
244
245 union {
246
247 struct hlist_head i_dentry;
248
249 struct rcu_head i_rcu;
250
251 };
252
253 u64 i_version;
254
255 atomic_t i_count;
256
257 atomic_t i_dio_count;
258
259 atomic_t i_writecount;
260
261 #ifdef CONFIG_IMA
262
263 atomic_t i_readcount; /* struct files open RO */
264
265 #endif
266
267 const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
268
269 struct file_lock_context *i_flctx;
270
271 struct address_space i_data;
272
273 struct list_head i_devices;
274
275 union {
276
277 struct pipe_inode_info *i_pipe;
278
279 struct block_device *i_bdev;
280
281 struct cdev *i_cdev;
282
283 char *i_link;
284
285 unsigned i_dir_seq;
286
287 };
288
289
290
291 __u32 i_generation;
292
293
294
295 #ifdef CONFIG_FSNOTIFY
296
297 __u32 i_fsnotify_mask; /* all events this inode cares about */
298
299 struct hlist_head i_fsnotify_marks;
300
301 #endif
302
303
304
305 #if IS_ENABLED(CONFIG_FS_ENCRYPTION)
306
307 struct fscrypt_info *i_crypt_info;
308
309 #endif
310
311
312
313 void *i_private; /* fs or device private pointer */
314
315 };
316
317
318
319 struct file { //文件对象,描述进程怎样与一个打开的文件进行交互
320
321 union {
322
323 struct llist_node fu_llist;
324
325 struct rcu_head fu_rcuhead;
326
327 } f_u;
328
329 struct path f_path;
330
331 struct inode *f_inode; /* cached value */
332
333 const struct file_operations *f_op;
334
335
336
337 /*
338
339 * Protects f_ep_links, f_flags.
340
341 * Must not be taken from IRQ context.
342
343 */
344
345 spinlock_t f_lock;
346
347 atomic_long_t f_count;
348
349 unsigned int f_flags;
350
351 fmode_t f_mode;
352
353 struct mutex f_pos_lock;
354
355 loff_t f_pos; //文件偏移
356
357 struct fown_struct f_owner;
358
359 const struct cred *f_cred; //进程相关安全上下文信息,如uid、权限等
360
361 struct file_ra_state f_ra;
362
363
364
365 u64 f_version;
366
367 #ifdef CONFIG_SECURITY
368
369 void *f_security;
370
371 #endif
372
373 /* needed for tty driver, and maybe others */
374
375 void *private_data;
376
377
378
379 #ifdef CONFIG_EPOLL
380
381 /* Used by fs/eventpoll.c to link all the hooks to this file */
382
383 struct list_head f_ep_links;
384
385 struct list_head f_tfile_llink;
386
387 #endif /* #ifdef CONFIG_EPOLL */
388
389 struct address_space *f_mapping;
390
391 } __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
有了基础补充后,继续4.0.1节分析系统调用。user_path_at调用并返回user_path_at_empty的返回值,user_path_at_empty 返回filename_lookup的返回值,传参数时使用了getname_flags函数(实在看不懂这个函数,但是2.6内核要简洁的多,直接返回char*,4.10多设计了一个filename *)将用户传入的文件名const char __user *name转化为了struct filename *类型,其中__user宏定义为空的,可能是为了标记该参数由用户传入吧。(为什么要一层一层来调用,不怕效率低吗?)
filename_lookup主要调用了path_lookupat函数,在之前先调用了set_nameidata,对nameidata进行了一些初始化赋值,将要查询的文件名放入了结构体nameidata中,并且将从current取出的nameidata保存下来,组成了一个链表。(这里发现current是宏定义,开始的时候source insight自动追踪变成了循环宏定义,应该是追踪错了头文件,因为有许多个current.h。后来查阅资料看是获得调用系统调用进程的数据结构信息。)
接着进入path_lookupat函数,此时参数为0 | LOOKUP_RCU,即LOOKUP_ RCU。RCU(Read-Copy Update)是内核数据同步的一种锁机制。
1.函数首先调用path_init()函数,path_init()函数主要是初始化查询,将nd实例的mnt和dentry成员设置为根目录或者工作目录的对应项
a,绝对路径(以/开始),获得根目录的dentry。它存储在task_struct中fs指向的fs_struct结构中。task_struct->fs_struct.root 。
b,相对路径,直接从当前进程task_struct结构中的获得指针fs,它指向的一个fs_struct,fs_struct中有一个指向“当前工作目录”的dentry。
2,path_lookupat()然后循环调用link_path_walk()函数。link_path_walk()函数将传入的路径名转化为dentry目录项:
首先跳过路径名的’/’,如果只有'/'则直接返回0;得到正确的路径名后,进入一个循环,每次都调用may_lookup()函数对inode节点做权限检查,如果权限不够也直接返回fail,在Unix中,只有目录是可执行的,它才可以被遍历;接下来计算的哈希与目录项高速缓存有关;该循环不断更新last_type和last,如果是最后一部分的返回,若不是则调用walk_component()函数。walk_component先处理LAST_DOTS,若发现LAST_NORM类型,即普通文件,则调用lookup_fast()在缓存中查找,若没有好的结果则调用lookup_slow(),获得i_mutex,重新检查缓存并向文件系统查找,他们都会调用follow_managed来处理挂载点;若有必要,期间walk_component会更新nameidata结构体的path。
link_path_walk会随着文件路径每部分深入,并追踪其间遇到的每个符号链接,直到其到达最后一部分,返回给nameidata.last。这个link_path_walk()函数本身非常复杂,也比较难懂,细节内容可参考文献[8][9][13],[13]对源码的注释非常清楚。link_path_walk函数主要是根据给定的路径,找到最后一个路径分量的目录项对象和安装点。
3. 在循环中还会调用trailing_symlink()函数来继续追踪最后部分的符号链接。trailing_symlink()会先调用may_follow_link(),这个函数检查符号链接的一些不安全权限情况。接着调用get_link(),先更新相关的访问时间等信息,然后调用inode中的get_link()方法完成符号链接解析;注意,原来的follow_link被get_link代替,而put_link,通过在get_link中设置set_delayed_call代替。
4. 最后对nd进行一些恢复收尾工作。
audit_inode调用了__audit_inode,定义在kernel/auditsc.c中,保存查找的inode和device,从名字猜测是审计用,这里先不关心。然后恢复current->nameidata,并释放name内存,与开头对应。这样filename_lookup结束,返回retval变量至user_path_at函数,即返回path_lookupat的结果。由此可见user_path_at就是检查是否存在这个文件,以及相关权限是否允许。
security_inode_getattr定义在security/security.c,首先检查dentry的inode的i_flags是否为S_PRIVATE,即inode文件系统的安装标识。若不是,则调用call_int_hook(inode_getattr, 0, path),这个宏定义就看不懂了,涉及到security_hook_heads的很多东西,这里先忽略。
注释说明是在没有安全检查的情况下获得属性,即没有调用security_inode_getattr,实现很简单,从dentry目录项中获得inode,然后调用inode索引节点对象的getattr方法,再用generic_fillattr填充到返回的stat中,stat->mode=inode->i_mode。Inode方法中保存的函数指针,就指向了每个具体文件操作系统的的函数。
通过分析,ls –l获得的符号链接就是vfs下层文件系统getattr返回的信息,那么下层文件系统getattr如何实现?这取决于不同的具体文件系统,《深入理解Linux内核》中提到ext2使用generic_getattr,但是在4.10源码中已经难以寻找到了,再深入的内容需要额外的耐心。那么如何查看inode的信息呢?Debugfs是一种特殊的文件系统,提供了把内核信息传递到用户空间的方式,与/proc类似。在debugfs中执行mi命令+要查看的文件,可以得到完整的inode信息。如下图所示,符号链接inode中的mode值确实为0120777。这会不会有什么安全隐患,为什么要这样设计?
文献[10]也有这样的描述“Symbolic links (sometimes called soft links) do not link to inodes, but create a name to name mapping. Symbolic links are created with ln -s. As you can see below, the symbolic link gets an inode of its own. Permissions on a symbolic link have no meaning, since the permissions of the target apply. Hard links are limited to their own partition (because they point to an inode), symbolic links can link anywhere (other file systems, even networked).”大意为“符号链接不链接到inode结点,而是创建名字到名字的映射。符号链接拥有自己的inode结点,其权限是没有意义的,因为应用的是链接目标文件的权限。符号链接可以链接至任何地方,如跨文件系统,甚至网络”。在path_lookupat查找路径时,已经对目录进行了权限检查,一般情况,如open系统调用,在路径寻找的时候都会使用do_follow_link函数来自动解析掉符号链接,(之前分析的path_lookupat循环中trailing_symlink也是用来追踪最后一个分量dentry为符号链接的情况,但应该由于设置了falg没有继续追踪符号链接。奇怪的是根据flag没有发现可以不调用trailing_symlink。)所以符号链接本身的权限没有意义;对文件系统详细分析的优秀文档可见[14]。
但是,在分析源码过程中见到的may_follow_link()函数就考虑了一些可能的安全隐患。此外,假想一种场景,若系统或A app想使用B app的凭证文件key,恶意的B app可以将key这个文件符号链接至其他任何地方,如C app的凭证,会不会引发这样的一些问题?仍待探索。
1. 想看懂Linux内核源码,甚至只是filesystem这一部分的源码,都需要对整个Linux内核源码有一定的认识。如遇到的系统调用,进程相关current,安全相关security.c的内容。
2. 看源码前一定尽力找文档!看源码前一定尽力找文档!看源码前一定尽力找文档!开始作者仅根据《深入理解Linux内核》这本书在探索path_lookupat,发现4.10与书上的2.6内核有不少的差别,所以一定要参考源码目录下的document目录下的文档(没有国人翻译成中文吗?),里面讲了源码的实现细节,并且和现役版本对应。另外也可以搜索其他人分析源码的博客,但往往比较旧。
3. 不要完全相信source insight的自动查找,遇见问题要相信自己的眼睛,再结合网上的Linux源码索引,优先用google不要用百度。
4. 使用source insight建立工程太大的话同步符号要好久,所以不妨先只加入自己关注的部分,比如只加入fs文件夹下的源码,这样效率比较高。
5. 对内核中使用的数据结构和设计思路越清楚,越有利于看懂源码。
6. 有了一定基础后,看懂源码不是梦,但非常非常需要耐心与时间,尤其是细节部分。由于自己最近耐心不佳,基础也欠缺,所以没有在意太多细节,仅力求了解全貌。
[1] Linux命令源码的查看. http://blog.chinaunix.net/uid-27177626-id-3389673.html
[2] Coreutils - GNU core utilities. http://www.gnu.org/software/coreutils/coreutils.html
[3] stat (C System Call). http://codewiki.wikidot.com/c:system-calls:stat
[4] Linux 系统调用内核源码分析. http://woshijpf.github.io/2016/05/10/Linux-%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E5%86%85%E6%A0%B8%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/
[5] Linux系统调用(syscall)原理. http://gityuan.com/2016/05/21/syscall/
[6] 理解 Linux 的硬链接与软链接. https://www.ibm.com/developerworks/cn/linux/l-cn-hardandsymb-links/#major4
[7] Linux 的虚拟文件系统--各结构之间的联系. http://scudong.blogbus.com/logs/11599350.html
[8] link_path_walk()路径名查找.http://blog.chinaunix.net/uid-12567959-id-160996.html
[9] 《深入理解Linux内核》P495-P504
[10] Chapter 9. file links. http://linux-training.be/security/ch09.html#idp65315008
[11] Linux下用户组、文件权限详解. http://www.cnblogs.com/123-/p/4189072.html
[12] 符号链接. https://zh.wikipedia.org/zh/%E7%AC%A6%E5%8F%B7%E9%93%BE%E6%8E%A5
[13] Linux文件系统(七)---系统调用之open操作(三) 之 open_namei函数. http://blog.csdn.net/shanshanpt/article/details/39927553
[14] linux内核follow_link分析. http://blog.csdn.net/sanwenyublog/article/details/50856837