Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 198 additions & 0 deletions project/libfuse-fs/docs/bind_mount.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# Bind Mount Support in libfuse-fs

## Overview

This document describes the bind mount feature added to `libfuse-fs` that enables container volume management without requiring kernel mount privileges for each operation.

## Features

- **User-space bind mount management**: Bind mounts are configured through passthrough and overlay filesystem APIs
- **Container volume support**: Enables mounting host directories (e.g., `/proc`, `/sys`, `/dev`) into overlay merged layers
- **Multiple bind mounts**: Support for multiple bind mount points simultaneously
- **Read/Write support**: Bind mounts can be configured as read-only or writable
- **Automatic cleanup**: Bind mounts are cleaned up when the filesystem unmounts

## Usage

### Command Line Interface

Both `passthrough` and `overlayfs_example` binaries support the `--bind` flag:

```bash
--bind "target:source"
```

Where:
- `target`: Path relative to the root directory where the bind mount will appear
- `source`: Absolute path on the host to bind mount

Multiple `--bind` flags can be specified for multiple bind mounts.

### Examples

#### Passthrough Filesystem with Bind Mount

```bash
sudo ./target/debug/examples/passthrough \
--rootdir /path/to/root \
--mountpoint /path/to/mount \
--bind "proc:/proc" \
--bind "sys:/sys" \
--privileged \
--allow-other
```

#### Overlay Filesystem with Bind Mounts

```bash
sudo ./target/debug/examples/overlayfs_example \
--mountpoint /root/merged \
--upperdir /root/upper \
--lowerdir /root/ubuntu-rootfs \
--bind "proc:/proc" \
--bind "sys:/sys" \
--bind "dev:/dev" \
--bind "dev/pts:/dev/pts" \
--bind "etc/resolv.conf:/etc/resolv.conf" \
--privileged \
--allow-other
```

### Programmatic API

#### PassthroughArgs

```rust
use libfuse_fs::passthrough::PassthroughArgs;

let args = PassthroughArgs {
root_dir: "/path/to/root",
mapping: None,
bind_mounts: vec![
("proc".to_string(), "/proc".to_string()),
("sys".to_string(), "/sys".to_string()),
],
};

let fs = new_passthroughfs_layer(args).await?;
```

#### OverlayArgs

```rust
use libfuse_fs::overlayfs::{OverlayArgs, mount_fs};

let bind_mounts = vec![
("proc".to_string(), "/proc".to_string()),
("sys".to_string(), "/sys".to_string()),
];

let mount_handle = mount_fs(OverlayArgs {
mountpoint: "/path/to/mount",
upperdir: "/path/to/upper",
lowerdir: vec!["/path/to/lower"],
privileged: true,
mapping: None,
name: None::<String>,
allow_other: true,
bind_mounts,
}).await;
```

## Implementation Details

### Architecture

1. **Configuration**: Bind mounts are specified in `PassthroughArgs` and stored in `Config`
2. **Setup**: During `PassthroughFs::import()`, bind mounts are created using kernel `mount --bind`
3. **Operation**: FUSE operations transparently access bind-mounted directories
4. **Cleanup**: Bind mounts are tracked and can be explicitly cleaned up via `cleanup_bind_mounts()`

### Key Components

- `passthrough/config.rs`: Stores bind mount configuration
- `passthrough/mod.rs`: Implements bind mount setup and cleanup
- `overlayfs/mod.rs`: Propagates bind mounts to upper layer

### Security Considerations

- Requires `sudo` privileges to execute kernel bind mount commands
- Bind mounts persist as long as the filesystem is mounted
- Cleanup is performed explicitly or on process termination
- Always validate paths to prevent security issues

## Testing

Two test scripts are provided:

### bind_passthrough_test.sh

Tests bind mount functionality with passthrough filesystem:
```bash
cd project/libfuse-fs
./tests/bind_passthrough_test.sh
```

### bind_overlay_test.sh

Tests bind mount functionality with overlay filesystem:
```bash
cd project/libfuse-fs
./tests/bind_overlay_test.sh
```

Both tests verify:
- Bind mount directory creation
- Read access to bind-mounted content
- Write access (if writable)
- Multiple bind mounts coexisting
- Overlay layer functionality

## Troubleshooting

### "Transport endpoint is not connected"

This error typically indicates a stale mount point. Clean it up:

```bash
sudo umount -l /path/to/mount
sudo rm -rf /path/to/mount
sudo mkdir -p /path/to/mount
```

### Permission Denied

Ensure the command is run with `sudo` or appropriate privileges:

```bash
sudo ./target/debug/examples/overlayfs_example ...
```

### Bind Mount Not Visible

Check that:
1. The source path exists and is accessible
2. The target directory was created successfully
3. The bind mount was actually performed (check with `mountpoint`)

```bash
mountpoint /path/to/upper/proc
ls -la /path/to/upper/proc
```

## Future Enhancements

Potential improvements for the bind mount feature:

1. **Read-only flag**: Add support for read-only bind mounts
2. **Recursive bind mounts**: Support for `--rbind` option
3. **Unmount on error**: Automatic cleanup on initialization errors
4. **User-space implementation**: Implement bind mount logic without kernel mount for rootless containers
5. **Bind mount options**: Support additional mount options (noexec, nosuid, etc.)

## References

- Original issue: Container volume management requirements
- Linux `mount(2)` man page
- FUSE documentation
- Kubernetes CSI specification
1 change: 1 addition & 0 deletions project/libfuse-fs/examples/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ async fn main() -> Result<(), std::io::Error> {
// In production, set to false unless you specifically need multi-user access
// and have proper permission checks in place.
allow_other: true,
bind_mounts: vec![],
})
.await;
println!("Mounted");
Expand Down
20 changes: 20 additions & 0 deletions project/libfuse-fs/examples/overlayfs_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ struct Args {
mapping: Option<String>,
#[arg(long)]
allow_other: bool,
/// Bind mount paths in format "target:source" (repeatable)
/// Example: --bind "proc:/proc" --bind "sys:/sys"
#[arg(long)]
bind: Vec<String>,
}

fn set_log() {
Expand All @@ -44,6 +48,21 @@ async fn main() {
set_log();
debug!("Starting overlay filesystem with args: {:?}", args);

// Parse bind mounts from "target:source" format
let bind_mounts: Vec<(String, String)> = args
.bind
.iter()
.filter_map(|bind_spec| {
let parts: Vec<&str> = bind_spec.splitn(2, ':').collect();
if parts.len() == 2 {
Some((parts[0].to_string(), parts[1].to_string()))
} else {
tracing::error!("Invalid bind mount specification: {}", bind_spec);
None
}
})
.collect();

let mut mount_handle = mount_fs(OverlayArgs {
name: None::<String>,
mountpoint: args.mountpoint,
Expand All @@ -52,6 +71,7 @@ async fn main() {
mapping: args.mapping,
privileged: args.privileged,
allow_other: args.allow_other,
bind_mounts,
})
.await;

Expand Down
20 changes: 20 additions & 0 deletions project/libfuse-fs/examples/passthrough.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ struct Args {
options: Option<String>,
#[arg(long)]
allow_other: bool,
/// Bind mount paths in format "target:source" (repeatable)
/// Example: --bind "proc:/proc" --bind "sys:/sys"
#[arg(long)]
bind: Vec<String>,
}

fn set_log() {
Expand All @@ -48,9 +52,25 @@ async fn main() {
set_log();
debug!("Starting passthrough filesystem with args: {:?}", args);

// Parse bind mounts from "target:source" format
let bind_mounts: Vec<(String, String)> = args
.bind
.iter()
.filter_map(|bind_spec| {
let parts: Vec<&str> = bind_spec.splitn(2, ':').collect();
if parts.len() == 2 {
Some((parts[0].to_string(), parts[1].to_string()))
} else {
tracing::error!("Invalid bind mount specification: {}", bind_spec);
None
}
})
.collect();

let fs = new_passthroughfs_layer(PassthroughArgs {
root_dir: args.rootdir,
mapping: args.options,
bind_mounts,
})
.await
.expect("Failed to init passthrough fs");
Expand Down
2 changes: 2 additions & 0 deletions project/libfuse-fs/src/overlayfs/async_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,7 @@ mod tests {
let layer = new_passthroughfs_layer(PassthroughArgs {
root_dir: lower.clone(),
mapping: None::<&str>,
bind_mounts: vec![],
})
.await
.unwrap();
Expand All @@ -1104,6 +1105,7 @@ mod tests {
new_passthroughfs_layer(PassthroughArgs {
root_dir: upperdir,
mapping: None::<&str>,
bind_mounts: vec![],
})
.await
.unwrap(),
Expand Down
9 changes: 6 additions & 3 deletions project/libfuse-fs/src/overlayfs/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ mod test {
let fs = unwrap_or_skip_eperm!(
new_passthroughfs_layer(PassthroughArgs {
root_dir: rootdir,
mapping: None::<&str>
mapping: None::<&str>,
bind_mounts: vec![],
})
.await,
"init passthrough layer"
Expand Down Expand Up @@ -255,7 +256,8 @@ mod test {
let fs = unwrap_or_skip_eperm!(
new_passthroughfs_layer(PassthroughArgs {
root_dir: rootdir,
mapping: None::<&str>
mapping: None::<&str>,
bind_mounts: vec![],
})
.await,
"init passthrough layer"
Expand Down Expand Up @@ -300,7 +302,8 @@ mod test {
let fs = unwrap_or_skip_eperm!(
new_passthroughfs_layer(PassthroughArgs {
root_dir: rootdir,
mapping: None::<&str>
mapping: None::<&str>,
bind_mounts: vec![],
})
.await,
"init passthrough layer"
Expand Down
5 changes: 4 additions & 1 deletion project/libfuse-fs/src/overlayfs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2724,6 +2724,7 @@ where
pub mapping: Option<M>,
pub name: Option<N>,
pub allow_other: bool,
pub bind_mounts: Vec<(String, String)>, // List of (target_path, source_path) for bind mounts
}

/// Mounts the filesystem using the given parameters and returns the mount handle.
Expand Down Expand Up @@ -2756,16 +2757,18 @@ where
let layer = new_passthroughfs_layer(PassthroughArgs {
root_dir: lower,
mapping: args.mapping.as_ref().map(|m| m.as_ref()),
bind_mounts: vec![],
})
.await
.expect("Failed to create lower filesystem layer");
lower_layers.push(Arc::new(layer));
}
// Create upper layer
// Create upper layer with bind mounts
let upper_layer = Arc::new(
new_passthroughfs_layer(PassthroughArgs {
root_dir: args.upperdir,
mapping: args.mapping.as_ref().map(|m| m.as_ref()),
bind_mounts: args.bind_mounts.clone(),
})
.await
.expect("Failed to create upper filesystem layer"),
Expand Down
6 changes: 6 additions & 0 deletions project/libfuse-fs/src/passthrough/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (C) 2020-2022 Alibaba Cloud. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;
Expand Down Expand Up @@ -181,6 +182,10 @@ pub struct Config {

/// UID/GID mapping. Format: `uidmapping=H:T:L[:H2:T2:L2...],gidmapping=H:T:L[:H2:T2:L2...]`
pub mapping: IdMappings,

/// Bind mount mappings: Map of target paths (relative to root_dir) to source paths (absolute on host)
/// Example: {"proc" => "/proc", "sys" => "/sys"}
pub bind_mounts: HashMap<PathBuf, PathBuf>,
}

impl Default for Config {
Expand All @@ -207,6 +212,7 @@ impl Default for Config {
use_mmap: false,
max_mmap_size: 1024 * 1024 * 1024,
mapping: IdMappings::default(),
bind_mounts: HashMap::new(),
}
}
}
Loading