ossfs 2.0.6 及以后版本支持将 POSIX 文件系统的符号链接(symlink,又称软链接)映射到 OSS 的 Symlink 对象。开启该功能后,您可以在挂载点内使用 ln -s 创建符号链接、使用 readlink 读取链接指向,为 Bucket 中的对象建立“别名”,在不复制对象内容的情况下实现数据的多路径访问。本文介绍 symlink 的解析规则、开启方式、支持的形式以及创建方法。
背景信息
在 OSS 中,同一份数据经常需要被多个路径访问,例如为数据集、模型文件或共享配置维护多个“别名”,或在 AI Agent 沙箱中复用同一份数据。对象存储不支持硬链接,复制对象会增加存储成本,多份副本之间也难以保持一致。ossfs 2.0.6 起支持将挂载点内的符号链接映射为 OSS Symlink 对象:链接对象只记录目标对象名,不复制数据,读取链接时直接访问目标对象。
基本原理
相对路径 symlink 与绝对路径 symlink
symlink 的目标路径有两种形式:绝对路径与相对路径。绝对路径形式的 symlink 以文件系统根目录 / 为基准解析,与链接自身的位置无关;相对路径形式的 symlink 则以链接所在的目录为基准解析,而不是以当前工作目录为基准。“相对谁”是理解 symlink 行为的关键,也是最容易产生误解的地方。
以一个例子说明。假设挂载点为 /mnt/ossfs,Bucket 中存在文件 oss://<bucket>/a/c/target,现在要在目录 oss://<bucket>/a/b/ 下创建链接 oss://<bucket>/a/b/link 指向它:
ln -s ../c/target /mnt/ossfs/a/b/link解析这条链接时,内核从链接所在的本地目录 /mnt/ossfs/a/b/ 出发拼接目标路径:/mnt/ossfs/a/b/ + ../c/target = /mnt/ossfs/a/c/target。这个过程与执行命令时所处的目录无关,无论在哪个工作目录下执行 cat /mnt/ossfs/a/b/link,解析结果都相同。如果误以为 ../c/target 是以工作目录为基准解析的,就会对链接的实际指向产生错误预期。
需要注意的是,readlink 返回的并不是创建时传入的原始字符串,而是规范化后的相对路径,例如上例返回 ../../a/c/target。规范化结果以挂载点根目录为基准重新表达目标位置,跟随链接时得到的对象与创建时一致。
OSS Symlink 对象与控制台兼容
OSS 原生支持 Symlink 对象:通过 PutSymlink 接口创建,目标记录在 x-oss-symlink-target 头中,通过 GetSymlink 接口读取。需要注意的是,PutSymlink 接口本身并不校验目标内容,理论上可以写入任意字符串;“目标必须是 Bucket 内对象名”实际上是 OSS 控制台的解析约定。控制台展示和跳转 Symlink 对象时,会把目标当作 Bucket 内的对象名来解析;若目标被写成其他形式(例如绝对路径),控制台将无法识别和跳转,成为死链接。
也就是说,控制台约定的解析基准是 Bucket 根目录,与 POSIX 相对路径 symlink“以链接所在目录为基准”不同。仍以上文的例子说明,同一条链接 a/b/link 指向 a/c/target,在不同视角下的目标字符串并不相同:
视角 | 链接路径 | 目标字符串 | 解析基准 |
POSIX 文件系统(创建时传入) |
|
| 链接所在目录 |
OSS 控制台(Symlink 对象的存储约定) |
|
| Bucket 根目录 |
ossfs 2.0 的 symlink 实现遵循这一约定,并在两套解析基准之间自动完成转换:在挂载点内创建的链接,OSS 存储的 target 为 Bucket 内相对路径(如 a/c/target),控制台可正常识别和跳转;通过 API 或控制台创建、target 为 Bucket 内相对路径的 Symlink 对象,在挂载点内也能正常 readlink 和读取。若 target 写成以 / 开头的绝对路径,挂载点读取该链接会返回 EINVAL。
配置方法
symlink 功能通过 enable_symlink 挂载选项控制,默认关闭,需要在挂载时显式开启。配置项说明如下:
配置项 | 是否必填 | 说明 | 默认值 |
enable_symlink | 否 | 是否启用 symlink 支持。开启后可在挂载点内使用 | false |
挂载命令示例:
ossfs2 mount /mnt/ossfs/ \
--oss_bucket <your-bucket> \
--oss_endpoint <your-endpoint> \
--oss_access_key_id <ak> \
--oss_access_key_secret <sk> \
--enable_symlink=true未开启该选项时,挂载点内使用 ln -s 创建符号链接会返回 ENOTSUP(Operation not supported);读取 Bucket 中已有的 Symlink 对象时,readlink 无法获取目标,读取内容也会被截断。若 Bucket 中已存在 Symlink 对象,必须开启此选项后再挂载。
环境要求
参数 | 要求 |
ossfs 版本 | 2.0.6 及以上。 |
操作系统 | Linux,且支持 FUSE 挂载(ossfs 2.0 依赖 FUSE)。 |
链接目标 | 必须是同一 Bucket 内的对象,不支持跨 Bucket、跨账号引用。 |
功能效果
开启 enable_symlink 前后的行为对比如下:
操作 | 未开启 enable_symlink | 开启 enable_symlink |
使用 | 返回 | 支持 |
使用 | 失败,无法识别为符号链接 | 返回规范化后的相对路径 |
读取已有 Symlink 对象的内容 | 按普通对象返回,内容可能被截断 | 返回目标对象的完整内容 |
绝对路径 target | 不支持 | 不支持,创建和读取返回 |
悬空链接 | 不支持 | 可创建,目标对象上传前读取返回 |
Symlink 对象只保存目标对象名,不复制目标对象的数据。
ossfs 2.0 支持的 symlink 形式
基于上文与 OSS 控制台的兼容约定,ossfs 2.0 仅接受能换算为 Bucket 内对象名的链接目标,具体支持情况如下:
链接目标形式 | 是否支持 | 行为说明 |
Bucket 内的相对路径(如 | 支持 | 规范化后指向 Bucket 内对象,正常创建。 |
绝对路径(如 | 不支持 | 返回 |
折叠后仍逃逸出挂载点的相对路径(如挂载点下一层的链接使用 | 不支持 | 返回 |
此外,ossfs 2.0 创建链接时不校验目标是否存在,目标不存在时链接也会创建成功(悬空链接,dangling symlink),访问时返回 ENOENT,直到目标对象被上传。链接目标可以是文件,也可以是目录。
通过 API 或控制台创建的 Symlink 对象,其 target 为 Bucket 内相对路径(如 a/c/target)时,在开启 enable_symlink 的挂载点内可正常 readlink 和读取;target 为以 / 开头的绝对路径时,读取返回 EINVAL。因此通过 API 或控制台创建 Symlink 对象时,target 也应使用 Bucket 内相对路径。
不建议在链接目标路径的中间使用 .. 分量。ossfs 2.0 在创建时会对目标路径做规范化(按字面折叠 .、..),而内核是在跟随链接时才逐级解析:若中间分量本身是 symlink,内核会先展开它,.. 回退到展开后目标的父目录,两种解析结果可能不同。
如何创建正确的相对路径 symlink
命令行方式
以挂载点 /mnt/ossfs、在 /mnt/ossfs/a/b/ 下创建指向 /mnt/ossfs/a/c/target 的链接为例,目标需使用相对路径:
cd /mnt/ossfs/a/b
ln -s ../c/target link
# 或者跳过 cd 直接使用
ln -s ../c/target /mnt/ossfs/a/b/link
# 读取链接指向,返回规范化后的相对路径(../../a/c/target)
readlink /mnt/ossfs/a/b/link
# 跟随链接正常读取目标内容
cat /mnt/ossfs/a/b/linkPython 脚本方式
批量创建链接时,可先将目标规范化为“相对于链接所在目录”的相对路径。与 ossfs 2.0 运行时无法判定绝对路径归属不同,脚本由使用者显式传入挂载点,因此可以直接接受本地绝对路径,并在工具侧完成换算:
#!/usr/bin/env python3
"""Create relative symlinks on an ossfs 2.0 mountpoint.
Usage: python3 create_oss_symlink.py <mountpoint> <link_path> <target_path>
All arguments are local paths; <link_path> and <target_path> must be under
<mountpoint>. The script converts the target into a path relative to the
link's parent directory, so that ossfs 2.0 stores it as an in-bucket object
name.
"""
import os
import sys
def create_relative_symlink(mountpoint, link_path, target_path):
mountpoint = os.path.realpath(mountpoint)
def to_bucket_rel(p):
# Lexical conversion, consistent with ossfs 2.0: no symlink
# expansion, inner ".." folded literally.
p = os.path.normpath(os.path.abspath(p))
if not p.startswith(mountpoint + os.sep):
raise ValueError(f"path escapes the mountpoint: {p}")
return os.path.relpath(p, mountpoint)
# Bucket-relative object name of the target (what OSS actually stores).
bucket_target = to_bucket_rel(target_path)
link_path = os.path.abspath(link_path)
if not link_path.startswith(mountpoint + os.sep):
raise ValueError(f"link path escapes the mountpoint: {link_path}")
# POSIX readlink semantics: relative to the link's parent directory.
link_parent_rel = os.path.relpath(os.path.dirname(link_path), mountpoint)
relative_target = os.path.relpath(bucket_target, link_parent_rel)
if os.path.lexists(link_path):
os.remove(link_path)
os.symlink(relative_target, link_path)
print(f"{link_path} -> {relative_target}")
if __name__ == "__main__":
if len(sys.argv) != 4:
print(__doc__)
sys.exit(1)
create_relative_symlink(sys.argv[1], sys.argv[2], sys.argv[3])使用示例:
python3 create_oss_symlink.py /mnt/ossfs /mnt/ossfs/a/b/link /mnt/ossfs/a/c/target脚本以挂载点根为基准换算目标。若挂载时配置了 oss_bucket_prefix,OSS 实际存储的对象名为该前缀拼接脚本内部的 bucket 相对路径,换算由 ossfs 2.0 自动完成,不影响脚本的使用方式。
使用建议
适合使用的场景:
为同一对象建立多个访问路径,例如数据集、模型文件或共享配置的“别名”,无需复制数据。
在 AI Agent 沙箱、代码执行环境中用链接组织工作目录,底层对象只保留一份。
需要在挂载点与 OSS 控制台之间互通链接的场景,target 统一使用 Bucket 内相对路径。
需要注意的场景:
需要引用其他 Bucket 或其他账号下的对象:Symlink 只能指向当前 Bucket 内的对象,请改用其他方式。
目标对象尚未上传:悬空链接可以创建,但上传目标对象前读取会返回
ENOENT,建议先上传目标对象。