Create and read OSS symlinks by using a mount point

Updated at:

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/link

When 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)

/mnt/ossfs/a/b/link

../c/target

The containing directory a/b/

OSS console (storage convention for Symlink objects)

a/b/link

a/c/target

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 ln -s to create symbolic links and readlink to read link targets on the mount point. If the feature is disabled, you cannot create symbolic links or read existing Symlink objects as symbolic links.

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=true
Important

If 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 ln -s

Returns ENOTSUP

Supported

Read an existing Symlink object with readlink

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 EINVAL

Dangling link

Not supported

Can be created; reads return ENOENT until the target object is uploaded

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 target, ../c/target, or a/b/target, resolved from the directory that contains the link

Yes

The target is normalized to an object in the bucket, and the link is created.

An absolute path, such as /mnt/ossfs/a/target or /etc/passwd

No

Returns EINVAL. In environments such as containers with bind mounts, the same bucket can be mounted at multiple paths. ossfs 2.0 cannot reliably determine which object in the bucket an absolute path refers to.

A relative path that escapes the mount point after normalization, such as ../../x for a link one level below the mount point

No

Returns EINVAL.

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.

Important

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/link

Use 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/target

The 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 ENOENT until the target object is uploaded. We recommend that you upload the target object first.