symlink-symlinkat系统调用及示例

好的,我们来深入学习 symlink 和 symlinkat 系统调用

1. 函数介绍

在 Linux 文件系统中,一个文件可以有多个名字,这通过链接 (Link) 来实现。链接主要分为两种:

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

硬链接 (Hard Link): 由 link() 和 linkat() 创建。多个硬链接直接指向同一个 inode(文件的实际数据)。删除一个硬链接不会影响文件数据,只有当所有硬链接都被删除时,文件数据才会被真正删除。硬链接不能跨文件系统,也不能指向目录(除了 . 和 ..)。

符号链接 (Symbolic Link, Symlink): 由 symlink() 和 symlinkat() 创建。它更像是一个“快捷方式”或“指针”,它本身是一个特殊的文件,包含了指向另一个文件或目录的路径名(可以是绝对路径或相对路径)。删除符号链接本身不会影响目标文件,但如果删除了目标文件,符号链接就会变成“悬空链接”(dangling link),无法访问。

symlink 和 symlinkat 的作用就是创建符号链接。

  • symlink: 在指定路径创建一个符号链接,指向另一个指定的路径。

  • symlinkat: 是 symlink 的扩展版本,允许更灵活地指定符号链接的创建位置(使用文件描述符作为参考目录)。

简单来说,symlink 和 symlinkat 就是让你用程序来创建文件或目录的“快捷方式”。

典型应用场景:

  • 创建快捷方式:为一个深层目录或复杂路径的文件创建一个容易访问的别名。

  • 库版本管理:例如,libfoo.so 可能是一个指向 libfoo.so.1.2.3 的符号链接,方便程序链接。

  • 配置文件管理:将配置文件放在一个标准位置,但通过符号链接指向实际存储位置。

  • 避免文件复制:通过符号链接共享文件,而不占用额外磁盘空间。

2. 函数原型

1
2
3
4
5
6
7
8
#include <unistd.h> // 包含系统调用声明

// 创建符号链接
int symlink(const char *target, const char *linkpath);

// 创建符号链接 (扩展版,支持相对路径)
int symlinkat(const char *target, int newdirfd, const char *linkpath);

3. 功能

  • symlink: 创建一个名为 linkpath 的符号链接,其内容是字符串 target。这个 target 可以是任意路径字符串,不需要在调用时就存在。

  • symlinkat: 类似于 symlink,但 linkpath 的解释方式不同。如果 linkpath 是相对路径,它是相对于 newdirfd 文件描述符所指向的目录来解析的。如果 linkpath 是绝对路径,则 newdirfd 被忽略。特殊的 newdirfd 值 AT_FDCWD 可以用来表示当前工作目录,此时 symlinkat() 的行为与 symlink() 相同。

4. 参数

target:

  • const char * 类型。

  • 指向一个以 null 结尾的字符串,该字符串定义了符号链接的目标。这个目标路径在创建符号链接时不需要存在,可以是绝对路径(如 /usr/bin/python3)或相对路径(如 ../bin/my_script)。

linkpath:

  • const char * 类型。

  • 指向一个以 null 结尾的字符串,该字符串指定了要创建的符号链接文件的名称和路径。

  • 对于 symlink: 这个路径是相对于当前工作目录解析的。

  • 对于 symlinkat: 如果是相对路径,则相对于 newdirfd 解析;如果是绝对路径,则忽略 newdirfd。

newdirfd (仅 symlinkat):

  • int 类型。

  • 一个已打开目录的文件描述符。linkpath 如果是相对路径,将相对于这个目录进行解析。

  • 特殊值 AT_FDCWD 表示当前工作目录。

5. 返回值

  • 成功: 返回 0。

  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

两者共享许多相同的错误码:

  • EACCES: 写入包含 linkpath 的目录权限不足。

  • EEXIST: linkpath 已经存在。

  • EFAULT: target 或 linkpath 指向了调用进程无法访问的内存地址。

  • EIO: I/O 错误。

  • ELOOP: 解析 linkpath 时遇到符号链接循环。

  • ENAMETOOLONG: target 或 linkpath 太长。

  • ENOENT: linkpath 的某个前缀目录不存在。

  • ENOMEM: 内核内存不足。

  • ENOSPC: 设备空间不足,无法创建新的目录项。

  • ENOTDIR: linkpath 的某个前缀不是目录。

  • EPERM: 文件系统不支持符号链接,或者由于其他原因被禁止(例如挂载了 nosymfollow 选项)。

  • EROFS: linkpath 所在的文件系统是只读的。

  • EBADF: (仅 symlinkat) newdirfd 不是有效的文件描述符,且不等于 AT_FDCWD。

7. 相似函数或关联函数

  • link / linkat: 创建硬链接。

  • readlink / readlinkat: 读取符号链接本身的内容(即它指向的目标路径)。

  • unlink: 删除文件或目录的链接。对于符号链接,它删除的是符号链接本身,而不是目标文件。

  • lstat: 获取文件状态,但如果文件是符号链接,它返回的是符号链接本身的信息,而不是目标文件的信息。

  • stat: 获取文件状态,如果文件是符号链接,它会跟随链接到目标文件并返回目标文件的信息。

8. 示例代码

下面的示例演示了如何使用 symlink 和 symlinkat 来创建符号链接,并展示它们与硬链接和普通文件的区别。

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h> // 包含 stat, lstat
#include <fcntl.h> // 包含 open, O_* flags
#include <string.h>
#include <errno.h>
#include <libgen.h> // 包含 dirname, basename

// 辅助函数:打印文件信息
void print_file_info(const char *path, const char* description) {
struct stat sb;
printf("--- %s: %s ---\n", description, path);

// 使用 lstat 获取符号链接本身的信息
if (lstat(path, &sb) == -1) {
perror("lstat");
return;
}

printf(" Inode: %ld\n", (long) sb.st_ino);
printf(" Links: %ld\n", (long) sb.st_nlink);
printf(" Size: %ld bytes\n", (long) sb.st_size);

if (S_ISLNK(sb.st_mode)) {
printf(" Type: Symbolic Link\n");
// 读取符号链接指向的目标
char target_path&#91;1024];
ssize_t len = readlink(path, target_path, sizeof(target_path) - 1);
if (len != -1) {
target_path&#91;len] = '\0';
printf(" -> Points to: %s\n", target_path);
} else {
perror("readlink");
}
} else if (S_ISREG(sb.st_mode)) {
printf(" Type: Regular File\n");
} else if (S_ISDIR(sb.st_mode)) {
printf(" Type: Directory\n");
} else {
printf(" Type: Other (Mode: %o)\n", sb.st_mode & S_IFMT);
}
printf("\n");
}

int main() {
const char *original_file = "original_file.txt";
const char *hard_link = "hard_link_to_original.txt";
const char *sym_link_absolute = "symlink_absolute_to_original.txt";
const char *sym_link_relative = "symlink_relative_to_original.txt";
const char *sym_link_dangling = "symlink_to_nonexistent.txt";
const char *test_dir = "test_symlink_dir";
const char *sym_link_in_dir = "symlink_in_dir.txt";
int dirfd;

printf("--- Demonstrating symlink and symlinkat ---\n");

// 1. 创建一个原始文件
FILE *f = fopen(original_file, "w");
if (!f) {
perror("fopen original_file.txt");
exit(EXIT_FAILURE);
}
fprintf(f, "This is the content of the original file.\n");
fclose(f);
printf("Created original file: %s\n", original_file);

// 2. 创建硬链接
if (link(original_file, hard_link) == -1) {
perror("link");
// 清理并退出
unlink(original_file);
exit(EXIT_FAILURE);
}
printf("Created hard link: %s -> %s\n", hard_link, original_file);

// 3. 创建符号链接 (绝对路径)
// 注意:target 是字符串,不一定需要存在
if (symlink("/full/path/to/somewhere", sym_link_absolute) == -1) {
perror("symlink absolute");
// 清理并退出
unlink(original_file);
unlink(hard_link);
exit(EXIT_FAILURE);
}
printf("Created symbolic link (absolute target): %s -> /full/path/to/somewhere\n", sym_link_absolute);

// 4. 创建符号链接 (相对路径)
if (symlink(original_file, sym_link_relative) == -1) {
perror("symlink relative");
// 清理并退出
unlink(original_file);
unlink(hard_link);
unlink(sym_link_absolute);
exit(EXIT_FAILURE);
}
printf("Created symbolic link (relative target): %s -> %s\n", sym_link_relative, original_file);

// 5. 创建一个指向不存在文件的符号链接 (悬空链接)
if (symlink("this_file_does_not_exist.txt", sym_link_dangling) == -1) {
perror("symlink dangling");
// 清理并退出
unlink(original_file);
unlink(hard_link);
unlink(sym_link_absolute);
unlink(sym_link_relative);
exit(EXIT_FAILURE);
}
printf("Created dangling symbolic link: %s -> this_file_does_not_exist.txt\n", sym_link_dangling);

// 6. 演示 symlinkat
// 首先创建一个目录
if (mkdir(test_dir, 0755) == -1 && errno != EEXIST) {
perror("mkdir test_symlink_dir");
// 清理并退出
unlink(original_file);
unlink(hard_link);
unlink(sym_link_absolute);
unlink(sym_link_relative);
unlink(sym_link_dangling);
exit(EXIT_FAILURE);
}
printf("\nCreated directory: %s\n", test_dir);

// 打开目录以获取文件描述符
dirfd = open(test_dir, O_RDONLY | O_DIRECTORY);
if (dirfd == -1) {
perror("open test_symlink_dir");
// 清理并退出
unlink(original_file);
unlink(hard_link);
unlink(sym_link_absolute);
unlink(sym_link_relative);
unlink(sym_link_dangling);
rmdir(test_dir);
exit(EXIT_FAILURE);
}

// 使用 symlinkat 在目录中创建符号链接
// linkpath 是相对路径 "symlink_in_dir.txt",它相对于 dirfd (test_symlink_dir) 解析
// target 是 "../original_file.txt",这是符号链接存储的内容
if (symlinkat("../original_file.txt", dirfd, sym_link_in_dir) == -1) {
perror("symlinkat");
close(dirfd);
// 清理并退出
unlink(original_file);
unlink(hard_link);
unlink(sym_link_absolute);
unlink(sym_link_relative);
unlink(sym_link_dangling);
rmdir(test_dir);
exit(EXIT_FAILURE);
}
printf("Created symbolic link using symlinkat: %s/%s -> ../original_file.txt\n", test_dir, sym_link_in_dir);
close(dirfd);

// 7. 检查所有创建的文件/链接的信息
printf("\n--- File Information ---\n");
print_file_info(original_file, "Original File");
print_file_info(hard_link, "Hard Link");
print_file_info(sym_link_absolute, "Symbolic Link (Absolute)");
print_file_info(sym_link_relative, "Symbolic Link (Relative)");
print_file_info(sym_link_dangling, "Dangling Symbolic Link");
char full_sym_link_in_dir&#91;512];
snprintf(full_sym_link_in_dir, sizeof(full_sym_link_in_dir), "%s/%s", test_dir, sym_link_in_dir);
print_file_info(full_sym_link_in_dir, "Symbolic Link (Created with symlinkat)");

// 8. 演示访问符号链接
printf("--- Accessing Files ---\n");
// 访问原始文件
if (access(original_file, F_OK) == 0) {
printf("Can access original file '%s'.\n", original_file);
}
// 访问硬链接 (效果同原始文件)
if (access(hard_link, F_OK) == 0) {
printf("Can access hard link '%s'.\n", hard_link);
}
// 访问相对符号链接 (应该成功,指向存在的文件)
if (access(sym_link_relative, F_OK) == 0) {
printf("Can access symbolic link '%s' (follows to existing target).\n", sym_link_relative);
} else {
printf("Cannot access symbolic link '%s': %s\n", sym_link_relative, strerror(errno));
}
// 访问悬空符号链接 (会失败)
if (access(sym_link_dangling, F_OK) == 0) {
printf("Can access dangling symbolic link '%s' (unexpected).\n", sym_link_dangling);
} else {
printf("Cannot access dangling symbolic link '%s': %s (expected)\n", sym_link_dangling, strerror(errno));
}

// 9. 清理创建的文件和目录
printf("\n--- Cleaning Up ---\n");
unlink(original_file);
unlink(hard_link);
unlink(sym_link_absolute);
unlink(sym_link_relative);
unlink(sym_link_dangling);
unlink(full_sym_link_in_dir);
rmdir(test_dir);
printf("All files and directory cleaned up.\n");

printf("\n--- Summary ---\n");
printf("1. Hard links (link) point to the same inode as the original file.\n");
printf("2. Symbolic links (symlink) are separate files containing a path string.\n");
printf("3. Symbolic links can point to non-existent targets (dangling).\n");
printf("4. symlinkat allows creating symlinks relative to a directory file descriptor.\n");

return 0;
}

9. 编译和运行

1
2
3
4
5
6
# 假设代码保存在 symlink_example.c 中
gcc -o symlink_example symlink_example.c

# 运行程序
./symlink_example

10. 预期输出

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
--- Demonstrating symlink and symlinkat ---
Created original file: original_file.txt
Created hard link: hard_link_to_original.txt -> original_file.txt
Created symbolic link (absolute target): symlink_absolute_to_original.txt -> /full/path/to/somewhere
Created symbolic link (relative target): symlink_relative_to_original.txt -> original_file.txt
Created dangling symbolic link: symlink_to_nonexistent.txt -> this_file_does_not_exist.txt

Created directory: test_symlink_dir
Created symbolic link using symlinkat: test_symlink_dir/symlink_in_dir.txt -> ../original_file.txt

--- File Information ---
--- Original File: original_file.txt ---
Inode: 123456
Links: 2
Size: 42 bytes
Type: Regular File

--- Hard Link: hard_link_to_original.txt ---
Inode: 123456
Links: 2
Size: 42 bytes
Type: Regular File

--- Symbolic Link (Absolute): symlink_absolute_to_original.txt ---
Inode: 123457
Links: 1
Size: 23 bytes
Type: Symbolic Link
-> Points to: /full/path/to/somewhere

--- Symbolic Link (Relative): symlink_relative_to_original.txt ---
Inode: 123458
Links: 1
Size: 18 bytes
Type: Symbolic Link
-> Points to: original_file.txt

--- Dangling Symbolic Link: symlink_to_nonexistent.txt ---
Inode: 123459
Links: 1
Size: 27 bytes
Type: Symbolic Link
-> Points to: this_file_does_not_exist.txt

--- Symbolic Link (Created with symlinkat): test_symlink_dir/symlink_in_dir.txt ---
Inode: 123460
Links: 1
Size: 21 bytes
Type: Symbolic Link
-> Points to: ../original_file.txt

--- Accessing Files ---
Can access original file 'original_file.txt'.
Can access hard link 'hard_link_to_original.txt'.
Can access symbolic link 'symlink_relative_to_original.txt' (follows to existing target).
Cannot access dangling symbolic link 'symlink_to_nonexistent.txt': No such file or directory (expected)

--- Cleaning Up ---
All files and directory cleaned up.

--- Summary ---
1. Hard links (link) point to the same inode as the original file.
2. Symbolic links (symlink) are separate files containing a path string.
3. Symbolic links can point to non-existent targets (dangling).
4. symlinkat allows creating symlinks relative to a directory file descriptor.

11. 总结

symlink 和 symlinkat 是创建符号链接的标准系统调用。

  • symlink(target, linkpath): 最常用的创建符号链接的方式。linkpath 相对于当前工作目录解析。

  • symlinkat(target, newdirfd, linkpath): 提供了更灵活的路径解析方式,特别是当需要在特定目录下创建链接时非常有用。当 newdirfd 为 AT_FDCWD 时,行为与 symlink 相同。

理解符号链接与硬链接的区别至关重要:

  • 硬链接增加文件的链接计数,共享 inode。

  • 符号链接是独立的文件,内容是目标路径字符串。

符号链接是 Linux 文件系统中强大而灵活的工具,广泛用于系统管理和程序设计中。

data-ad-format="auto" data-full-width-responsive="true">