Skip to content

A FUSE filesystem providing variant symlinks that resolve differently based on the calling process's environment variables.

License

Notifications You must be signed in to change notification settings

cccheng/varlinkfs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

varlinkfs

A FUSE filesystem providing variant symlinks that resolve differently based on the calling process's environment variables.

What are Variant Symlinks?

Variant symlinks are symbolic links whose target path can change based on context. Unlike regular symlinks that always point to the same location, variant symlinks resolve dynamically using environment variables from the process reading them.

This concept originates from DragonFly BSD and was proposed for Linux (see LWN article on variant symlinks). varlinkfs brings this functionality to Linux using FUSE.

Example: A symlink with template /opt/${VERSION}/bin will resolve to /opt/1.0/bin for a process with VERSION=1.0, and to /opt/2.0/bin for a process with VERSION=2.0.

Features

  • Dynamic symlink resolution based on process environment variables
  • DragonFly BSD style template syntax (${VAR} and $VAR)
  • Create and remove symlinks at runtime via standard ln -s and rm commands
  • Configurable fallback behavior for missing variables
  • Pre-create symlinks via command-line arguments
  • Supports multi-user access with --allow-other
  • Background or foreground operation modes

Requirements

  • Linux with FUSE support (kernel module fuse)
  • Rust 1.85+ (2024 edition)
  • libfuse development libraries

On Debian/Ubuntu:

sudo apt install fuse libfuse-dev

On Fedora/RHEL:

sudo dnf install fuse fuse-devel

Installation

Clone and build from source:

git clone https://github.com/cccheng/varlinkfs.git
cd varlinkfs
cargo build --release

The binary will be at target/release/varlinkfs.

Usage

Basic Usage

# Create a mount point
mkdir -p /mnt/varlink

# Mount the filesystem (runs in background by default)
./varlinkfs /mnt/varlink

# Create a variant symlink
ln -s '/opt/${VERSION}/bin' /mnt/varlink/app-bin

# Processes with different VERSION values see different targets
VERSION=1.0 readlink /mnt/varlink/app-bin
# Output: /opt/1.0/bin

VERSION=2.0 readlink /mnt/varlink/app-bin
# Output: /opt/2.0/bin

# Follow the symlink (if target exists)
VERSION=1.0 ls /mnt/varlink/app-bin

# Unmount (Ctrl+C if running in foreground, or use fusermount)
fusermount -u /mnt/varlink

Pre-creating Symlinks

Create symlinks at mount time using the -s or --symlink flag:

./varlinkfs /mnt/varlink \
    -s 'config=/home/${USER}/.config/${APP}' \
    -s 'data=/data/${ENV}/app'

Running in Foreground

For debugging or when running as a supervised service:

./varlinkfs -f /mnt/varlink

Press Ctrl+C to unmount and exit.

Template Syntax

Symlink targets support two variable reference syntaxes:

Syntax Description Example
${VAR} Braced syntax (preferred) /opt/${VERSION}/bin
$VAR Unbraced syntax $HOME/.config

The braced syntax ${VAR} is recommended as it clearly delimits the variable name. The unbraced syntax $VAR is terminated by any non-alphanumeric, non-underscore character.

Variable names must:

  • Start with a letter (a-z, A-Z) or underscore
  • Contain only letters, digits, and underscores

Examples

# Using braced syntax
ln -s '/opt/${VERSION}/lib/${ARCH}' /mnt/varlink/lib

# Using unbraced syntax
ln -s '$HOME/.config' /mnt/varlink/user-config

# Multiple variables
ln -s '/data/${ENV}/${REGION}/cache' /mnt/varlink/cache

# Adjacent variables
ln -s '${PREFIX}${SUFFIX}' /mnt/varlink/combined

Why No Command Substitution?

varlinkfs intentionally does not support shell-style command substitution ($(cmd) or `cmd`). This is a deliberate design decision for the following reasons:

Security: Command substitution would execute arbitrary commands during symlink resolution. Since FUSE filesystems often run with elevated privileges, this would create a significant attack vector. Any user with symlink creation access could execute commands as root.

Performance: Every readlink() call would potentially spawn a subprocess, adding significant latency compared to the current approach of reading /proc/<pid>/environ (a simple file read).

Side effects: Environment variable reads are pure and side-effect free. Command execution can modify system state, making symlink resolution unpredictable.

Fallback Modes

When an environment variable referenced in a template is not set, varlinkfs uses the configured fallback mode to determine the behavior.

Mode Description Example Result
error Return an error (default) Symlink resolution fails
literal Keep the original ${VAR} text /opt/${VERSION}/bin
empty Replace with empty string /opt//bin
default:VALUE Replace with specified value /opt/VALUE/bin

Usage

# Default: error on missing variable
./varlinkfs /mnt/varlink

# Keep literal variable reference if not set
./varlinkfs --fallback literal /mnt/varlink

# Use empty string for missing variables
./varlinkfs --fallback empty /mnt/varlink

# Use a default value
./varlinkfs --fallback default:latest /mnt/varlink

Configuration Options

Option Short Description Default
--foreground -f Run in foreground (don't daemonize) false
--allow-other Allow other users to access the filesystem false
--allow-create Allow creating symlinks via ln -s true
--allow-remove Allow removing symlinks via rm true
--fallback MODE Fallback mode for missing variables error
--symlink NAME=TEMPLATE -s Create initial symlink (repeatable)
--debug -d Enable debug logging false

Multi-user Access

To allow other users to access the mounted filesystem:

./varlinkfs --allow-other /mnt/varlink

Note: This requires user_allow_other to be enabled in /etc/fuse.conf.

Read-only Mode

To prevent symlink creation/deletion at runtime:

./varlinkfs --allow-create false --allow-remove false /mnt/varlink \
    -s 'mylink=/opt/${VERSION}'

How It Works

  1. When a process reads a symlink (via readlink, stat, or by following it), the kernel sends a FUSE request to varlinkfs with the calling process's PID.

  2. varlinkfs reads the environment variables of the calling process from /proc/<pid>/environ.

  3. The symlink's template is resolved by substituting variable references with values from the process's environment.

  4. The resolved path is returned to the kernel, which then uses it as the symlink target.

This approach is completely transparent to applications - they see regular symlinks that just happen to resolve differently based on their environment.

Limitations

Environment Variables Must Be Set at Process Start

varlinkfs reads environment variables from /proc/<pid>/environ, which is populated only when a process starts and is never updated thereafter. This means:

# Does NOT work - export doesn't update /proc/environ
export VERSION=1.0
cd /mnt/varlink/mylink  # fails: VERSION not found

# Does NOT work - inline assignment with shell built-in
VERSION=1.0 cd /mnt/varlink/mylink  # fails: cd runs in same process

# WORKS - new process starts with VERSION in its environment
VERSION=1.0 bash -c 'cd /mnt/varlink/mylink && pwd'

# WORKS - external commands spawn new processes
VERSION=1.0 ls /mnt/varlink/mylink
VERSION=1.0 readlink /mnt/varlink/mylink
Method Works? Reason
VERSION=1.0 bash -c 'cd x' Yes New bash starts with VERSION in /proc/environ
export VERSION=1.0; cd x No Same shell process, /proc/environ unchanged
VERSION=1.0 cd x No cd is a shell built-in, same process
VERSION=1.0 cat x/file Yes cat is external, spawns new process

Practical solutions:

  1. Set variables before starting your shell - in .bashrc, .profile, or systemd unit files
  2. Start a new shell: VERSION=1.0 exec bash
  3. Use wrapper scripts that exec programs with the correct environment

This is a fundamental limitation of FUSE-based variant symlinks. Kernel-native implementations (like DragonFly BSD) can access the live environment, but FUSE filesystems can only read the static /proc/pid/environ snapshot.

Use Cases

  • Multi-version software: Point applications to version-specific directories based on a VERSION environment variable
  • Environment separation: Use the same paths in development, staging, and production with ENV variable routing
  • User-specific paths: Create shared symlinks that resolve to user-specific locations
  • A/B testing: Route different processes to different backends based on feature flags
  • Container orchestration: Provide environment-aware paths without modifying application code

License

MIT

About

A FUSE filesystem providing variant symlinks that resolve differently based on the calling process's environment variables.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages