Create and read OSS symlinks by using a mount point
ossfs 2.0.6 and later can map symbolic links (symlinks, also called soft links) in a POSIX file system to OSS Symlink objects. After you enable this feature, you can use ln -s to create symlinks and readlink to read link targets on a mount point, creating aliases for objects in a bucket without copying object content. This topic describes how symlinks are resolved, how to enable the feature, which link targets are supported, and how to create links.
Background information
In OSS, the same data often needs to be accessed through multiple paths. For example, you may keep multiple aliases for a dataset, a model file, or a shared configuration, or reuse the same data in an AI Agent sandbox. Object storage does not support hard links. Copying objects increases storage costs and makes it difficult to keep multiple copies consistent. ossfs 2.0.6 and later can map symbolic links on a mount point to OSS Symlink objects. A link object stores only the target object name, does not copy data, and reads the target object directly.
How symlinks are resolved
Relative and absolute symlink targets
A symlink target can be an absolute path or a relative path. An absolute target is resolved from the file system root directory / and is independent of the link's own location. A relative target is resolved from the directory that contains the link, not from the current working directory. What the path is relative to determines how a symlink behaves and is the most common source of misunderstandings.
Consider the following example. Assume that the mount point is /mnt/ossfs and the bucket contains the object oss://<bucket>/a/c/target. In the directory oss://<bucket>/a/b/, you want to create the link oss://<bucket>/a/b/link that points to that object:
ln -s ../c/target /mnt/ossfs/a/b/linkWhen the kernel resolves this link, it starts from the local directory that contains the link, /mnt/ossfs/a/b/, and appends the target: /mnt/ossfs/a/b/ + ../c/target = /mnt/ossfs/a/c/target. The result does not depend on the directory in which you run the command. Running cat /mnt/ossfs/a/b/link from any working directory resolves to the same object. If you assume that ../c/target is resolved from the working directory, your expectation of the link's destination will be wrong.
Note that readlink does not return the exact string that you passed when you created the link. It returns a normalized relative path, for example ../../a/c/target in the preceding example. The normalized value restates the target location from the mount point root, and following the link resolves to the same object as when you created it.
OSS Symlink objects and console compatibility
OSS natively supports Symlink objects. A Symlink object is created by using the PutSymlink operation, its target is stored in the x-oss-symlink-target header, and the target is read by using the GetSymlink operation. Note that the PutSymlink operation does not validate the target, so you can technically store any string. The requirement that "the target must be an object name in the bucket" is a parsing convention of the OSS console. When the console displays or navigates to a Symlink object, it parses the target as an object name in the bucket. If the target is stored in another form, such as an absolute path, the console cannot recognize or navigate to it, and the link becomes a dead link.
In other words, the console resolves targets from the bucket root, which differs from the POSIX rule that a relative symlink is resolved from the directory that contains the link. Using the preceding example, the same link a/b/link that points to a/c/target has different target strings depending on the perspective:
Perspective | Link path | Target string | Resolved from |
POSIX file system (passed at creation) |
|
| The containing directory |
OSS console (storage convention for Symlink objects) |
|
| Bucket root |
The symlink implementation of ossfs 2.0 follows this convention and converts between the two resolution bases. A link created on the mount point stores its target as a bucket-relative path (for example, a/c/target), which the console can recognize and navigate to. A Symlink object created by using an API or the console with a bucket-relative target can also be read with readlink and followed on the mount point. If the target is an absolute path that starts with /, reading the link on the mount point returns EINVAL.
Configuration methods
Symlinks are controlled by the enable_symlink mount option. The feature is disabled by default and must be explicitly enabled when you mount the bucket. The following table describes the option.
Option | Required | Description | Default |
enable_symlink | No | Enables symlink support. After you enable it, you can use | false |
Example mount command:
ossfs2 mount /mnt/ossfs/ \
--oss_bucket <your-bucket> \
--oss_endpoint <your-endpoint> \
--oss_access_key_id <ak> \
--oss_access_key_secret <sk> \
--enable_symlink=trueIf this option is not enabled, using ln -s to create a symbolic link on the mount point returns ENOTSUP (Operation not supported). For Symlink objects that already exist in the bucket, readlink cannot read the target, and the content returned when you read the object is truncated. If the bucket already contains Symlink objects, you must enable this option before you mount the bucket.
Environment requirements
Item | Requirement |
ossfs version | 2.0.6 or later. |
Operating system | Linux with FUSE support. ossfs 2.0 relies on FUSE for mounting. |
Link target | Must be an object in the same bucket. Cross-bucket and cross-account references are not supported. |
Effects of the feature
The following table compares behavior with and without enable_symlink:
Operation | enable_symlink disabled | enable_symlink enabled |
Create a symbolic link with | Returns | Supported |
Read an existing Symlink object with | Fails; the object is not recognized as a symbolic link | Returns the normalized relative path |
Read the content of an existing Symlink object | Returns the object as a regular object; the content may be truncated | Returns the complete content of the target object |
Absolute path target | Not supported | Not supported; creation and reads return |
Dangling link | Not supported | Can be created; reads return |
A Symlink object stores only the target object name and does not copy the data of the target object.
Supported symlink targets in ossfs 2.0
Based on the compatibility convention with the OSS console, ossfs 2.0 accepts only link targets that can be converted to an object name in the bucket. The following table describes the supported targets:
Target form | Supported | Behavior |
A relative path in the bucket, such as | Yes | The target is normalized to an object in the bucket, and the link is created. |
An absolute path, such as | No | Returns |
A relative path that escapes the mount point after normalization, such as | No | Returns |
In addition, ossfs 2.0 does not check whether the target exists when it creates a link. The link is created even if the target does not exist (a dangling symlink). Accessing the link returns ENOENT until the target object is uploaded. The target can be a file or a directory.
When the target of a Symlink object created by using an API or the console is a bucket-relative path (for example, a/c/target), the object can be read with readlink and followed on a mount point that has enable_symlink enabled. If the target is an absolute path that starts with /, reading the link returns EINVAL. Therefore, when you create a Symlink object by using an API or the console, use a bucket-relative target as well.
Do not use .. components in the middle of a target path. ossfs 2.0 normalizes the target when it creates the link by folding . and .. literally, whereas the kernel resolves the path component by component when it follows the link. If a middle component is itself a symlink, the kernel expands it first and .. refers to the parent of the expanded target. The two results can differ.
Create correct relative symlinks
Use the command line
The following example uses the mount point /mnt/ossfs. It creates a link in /mnt/ossfs/a/b/ that points to /mnt/ossfs/a/c/target. The target must be a relative path:
cd /mnt/ossfs/a/b
ln -s ../c/target link
# Or skip the cd command
ln -s ../c/target /mnt/ossfs/a/b/link
# Read the link target. The command returns the normalized relative path (../../a/c/target).
readlink /mnt/ossfs/a/b/link
# Follow the link to read the target content
cat /mnt/ossfs/a/b/linkUse a Python script
When you create links in batches, convert the target to a path that is relative to the directory that contains the link. Unlike ossfs 2.0 at run time, which cannot determine which bucket an absolute path belongs to, the script receives the mount point from you and can accept local absolute paths and convert them.
#!/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])Example:
python3 create_oss_symlink.py /mnt/ossfs /mnt/ossfs/a/b/link /mnt/ossfs/a/c/targetThe script converts the target from the mount point root. If oss_bucket_prefix is configured when you mount the bucket, the object name that OSS actually stores is the prefix followed by the bucket-relative path used by the script. ossfs 2.0 performs this conversion automatically, and the way you use the script does not change.
Usage recommendations
Suitable scenarios:
Create multiple access paths for the same object, such as aliases for a dataset, a model file, or a shared configuration, without copying data.
Organize working directories with links in AI Agent sandboxes and code execution environments while keeping a single copy of the underlying object.
Scenarios that require links to work in both the mount point and the OSS console. In these scenarios, always use bucket-relative targets.
Scenarios that require attention:
Referencing objects in another bucket or another account. A Symlink can point only to objects in the current bucket. Use another method instead.
Target object not uploaded yet. A dangling link can be created, but reads return
ENOENTuntil the target object is uploaded. We recommend that you upload the target object first.