好的,我们来深入学习 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 文件系统中强大而灵活的工具,广泛用于系统管理和程序设计中。