Linux 软链接和硬链接

概述

Linux系统中为了解决文件共享问题,引入了软链接和硬链接的概念,软链接又称符号链接,即soft linksymbolic link,硬链接即为hard link
同时它们还带来了隐藏文件路径、增加权限安全及节省存储等好处。

若一个文件指向另一个文件,该文件的内容仅仅是另一个文件的path,则其为软链接。
若一个inode号对应多个文件名,则称这些文件为硬链接。换言之,硬链接就是同一个文件使用了多个别名。
软链接可由命令ln创建,硬链接可由命令link或ln创建。

硬链接

由于硬链接的文件是有着相同inode号,仅文件名不同,因此硬链接存在以下几点特性:

  • 文件有相同的inode 和 data blocks
  • 只能对文件创建硬链接,不能对目录创建硬链接
  • 只能对已存在的文件创建硬链接
  • 不能跨文件系统创建硬链接
  • 删除一个硬链接文件并不影响其他有相同inode号的文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ ll
total 4
-rw-r--r-- 1 root root 928 Nov 11 08:37 tstfile
$ ln tstfile hlink
$ ll
total 8
-rw-r--r-- 2 root root 928 Nov 11 08:37 hlink
-rw-r--r-- 2 root root 928 Nov 11 08:37 tstfile

$ stat tstfile
File: ‘tstfile’
Size: 928 Blocks: 8 IO Block: 4096 regular file
Device: 803h/2051d Inode: 4851726 Links: 2
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-11-11 08:37:07.554081068 +0800
Modify: 2017-11-11 08:37:07.554081068 +0800
Change: 2017-11-11 08:37:42.216988088 +0800
Birth: -

$ stat hlink
File: ‘hlink’
Size: 928 Blocks: 8 IO Block: 4096 regular file
Device: 803h/2051d Inode: 4851726 Links: 2
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-11-11 08:37:07.554081068 +0800
Modify: 2017-11-11 08:37:07.554081068 +0800
Change: 2017-11-11 08:37:42.216988088 +0800
Birth: -

因为硬链接文件具有相同的地位,并没有谁指向谁之说,所以想要查找一个inode号对应的所有硬链接文件并不容易,通常需要遍历目录下所有的文件,系统里可通过find命令查找一个文件相关的所有硬链接文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ mkdir dir
$ ln tstfile dir/hlink

$ find ./ -samefile tstfile
./dir/hlink
./hlink
./tstfile

$ ls -i tstfile
4851726 tstfile
$ find ./ -inum 4851726
./dir/hlink
./hlink
./tstfile

可以通过命令strace find ./ -samefile tstfile查看具体执行的系统调用,发现该命令会在输入的目录下查询所有的文件,输出与参数文件inode num一致的文件,所以在目录里文件非常多时,该命令会非常耗时的。

软连接

软链接与硬链接不同,若一个文件的数据块中存放的内容是另一文件的路径名时,则该文件就是软链接。
软链接就是一个普通文件,只是数据块内容有点特殊。
软链接有着自己的inode号以及数据块,因此软链接的创建与使用没有类似硬链接的诸多限制。

软链接的特性如下:

  • 软链接有自己的文件属性及权限等
  • 可对不存在的文件或目录创建软链接
  • 软链接可跨文件系统创建
  • 软链接即可对文件创建,也可对目录创建
  • 创建软链接后,链接计数i_nlink不会增加
  • 删除软链接并不影响被指向的文件
  • 若被指向的原文件被删除,则相关软连接被称为死链接(dangling link),若被指向路径文件被重新创建,即恢复为正常的软链接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ ln -s tstfile slink
$ ll
total 4
lrwxrwxrwx 1 root root 7 Nov 11 08:39 slink -> tstfile
-rw-r--r-- 1 root root 928 Nov 11 08:37 tstfile

$ stat tstfile
File: ‘tstfile’
Size: 928 Blocks: 8 IO Block: 4096 regular file
Device: 803h/2051d Inode: 4851726 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-11-11 08:37:07.554081068 +0800
Modify: 2017-11-11 08:37:07.554081068 +0800
Change: 2017-11-11 08:39:04.749766771 +0800
Birth: -

$ stat slink
File: ‘slink’ -> ‘tstfile’
Size: 7 Blocks: 0 IO Block: 4096 symbolic link
Device: 803h/2051d Inode: 4851778 Links: 1
Access: (0777/lrwxrwxrwx) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-11-11 08:39:24.730713312 +0800
Modify: 2017-11-11 08:39:22.901718205 +0800
Change: 2017-11-11 08:39:22.901718205 +0800
Birth: -

$ readlink slink
tstfile

测试程序

Linux里由命令stat来获取一个文件的状态,文件状态保持在数据结构struct stat里,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for file system I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};

其中st_mode变量用来表示文件类型的,特征位的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
S_IFMT     0170000   bit mask for the file type bit fields
S_IFSOCK 0140000 socket
S_IFLNK 0120000 symbolic link
S_IFREG 0100000 regular file
S_IFBLK 0060000 block device
S_IFDIR 0040000 directory
S_IFCHR 0020000 character device
S_IFIFO 0010000 FIFO
S_ISUID 0004000 set-user-ID bit
S_ISGID 0002000 set-group-ID bit (see below)
S_ISVTX 0001000 sticky bit (see below)
S_IRWXU 00700 mask for file owner permissions
S_IRUSR 00400 owner has read permission
S_IWUSR 00200 owner has write permission
S_IXUSR 00100 owner has execute permission
S_IRWXG 00070 mask for group permissions
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IRWXO 00007 mask for permissions for others (not in group)
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission

POSIX标准里定义了如下的宏来检查文件类型:

1
2
3
4
5
6
7
S_ISREG(m)  is it a regular file?
S_ISDIR(m) directory?
S_ISCHR(m) character device?
S_ISBLK(m) block device?
S_ISFIFO(m) FIFO (named pipe)?
S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.)
S_ISSOCK(m) socket? (Not in POSIX.1-1996.)

结合上面的描述,我们可以写如下测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$ cat file_stat.cc
#include <iostream>
#include <string>
#include <sys/stat.h>
#include <unistd.h>

using namespace std;

int main(int argc, char* argv[])
{
struct stat fstat;
string file_path = argv[1];

# use lstat instead of stat as lstat handle symbolic link itself
# when input path is a symbolic, not the file it refers to.
int ret = lstat(file_path.c_str(), &fstat);
cout << "file path: " << file_path << endl;
cout << "file size: " << fstat.st_size << endl;
cout << "file ino: " << fstat.st_ino << endl;
cout << "file mode: " << fstat.st_mode << endl;
cout << "file nlink: " << fstat.st_nlink << endl;

if (S_ISREG(fstat.st_mode)) {
cout << "regular file: " << file_path << endl;
} else if (S_ISLNK(fstat.st_mode)) {
cout << "link file: " << file_path << endl;
char buf[256];
ret = readlink(file_path.c_str(), buf, 255);
buf[ret] = '\0';
cout << "readlink return " << ret << endl;
cout << "link file real path: " << buf << endl;
}

return 0;
}

//g++ -std=c++11 -ofile_stat file_stat.cc

命令行输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ./file_stat hlink
file path: hlink
file size: 928
file ino: 4851726
file mode: 33188
file nlink: 3
regular file: hlink

$ ./file_stat slink
file path: slink
file size: 7
file ino: 4851778
file mode: 41471
file nlink: 1
link file: slink
readlink return 7
link file real path: tstfile

参考

https://www.ibm.com/developerworks/cn/linux/l-cn-hardandsymb-links/index.html
http://www.giannistsakiris.com/2011/04/15/counting-and-listing-hard-links-on-linux/

支持原创