symlink-symlinkat系统调用及示例
好的,我们来深入学习 symlink 和 symlinkat 系统调用
1. 函数介绍
在 Linux 文件系统中,一个文件可以有多个名字,这通过链接 (Link) 来实现。链接主要分为两种:
硬链接 (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. 函数原型
#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 来创建符号链接,并展示它们与硬链接和普通文件的区别。
#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[1024];
ssize_t len = readlink(path, target_path, sizeof(target_path) - 1);
if (len != -1) {
target_path[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[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. 编译和运行
# 假设代码保存在 symlink_example.c 中
gcc -o symlink_example symlink_example.c
# 运行程序
./symlink_example
10. 预期输出
--- 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 文件系统中强大而灵活的工具,广泛用于系统管理和程序设计中。