symlink-symlinkat系统调用及示例

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

1. 函数介绍

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

  1. 硬链接 (Hard Link): 由 link() 和 linkat() 创建。多个硬链接直接指向同一个 inode(文件的实际数据)。删除一个硬链接不会影响文件数据,只有当所有硬链接都被删除时,文件数据才会被真正删除。硬链接不能跨文件系统,也不能指向目录(除了 . 和 ..)。
  2. 符号链接 (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 的目录权限不足。
  • EEXISTlinkpath 已经存在。
  • EFAULTtarget 或 linkpath 指向了调用进程无法访问的内存地址。
  • EIO: I/O 错误。
  • ELOOP: 解析 linkpath 时遇到符号链接循环。
  • ENAMETOOLONGtarget 或 linkpath 太长。
  • ENOENTlinkpath 的某个前缀目录不存在。
  • ENOMEM: 内核内存不足。
  • ENOSPC: 设备空间不足,无法创建新的目录项。
  • ENOTDIRlinkpath 的某个前缀不是目录。
  • EPERM: 文件系统不支持符号链接,或者由于其他原因被禁止(例如挂载了 nosymfollow 选项)。
  • EROFSlinkpath 所在的文件系统是只读的。
  • EBADF: (仅 symlinkatnewdirfd 不是有效的文件描述符,且不等于 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 文件系统中强大而灵活的工具,广泛用于系统管理和程序设计中。

此条目发表在linux文章分类目录,贴了标签。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注