A Rust library for parsing torrent files using libtorrent via FFI.
- Parse .torrent files and extract metadata
- Extract torrent name, files, piece layout, and total size
- Support for both single-file and multi-file torrents
- Proper error handling for invalid or corrupted torrent files
- Rust 1.70 or later
- libtorrent-rasterbar 2.0.x
- OpenSSL
- C++17 compiler
- FUSE 3.x (libfuse3-dev)
TorrentFS uses FUSE to mount the virtual filesystem. To allow non-root users to access the mount point, you need to configure FUSE:
-
Edit
/etc/fuse.conf:sudo sed -i 's/#user_allow_other/user_allow_other/' /etc/fuse.confOr manually uncomment the
user_allow_otherline in/etc/fuse.conf:user_allow_other -
Ensure your user is in the
fusegroup:sudo usermod -aG fuse $USER(Log out and back in for group changes to take effect)
-
On some systems, you may need to install FUSE development headers:
# Ubuntu/Debian sudo apt-get install libfuse3-dev # Fedora sudo dnf install fuse3-devel # Arch Linux sudo pacman -S fuse3
TorrentFS presents a layered virtual filesystem through FUSE.
/
├── metadata/ # Subdirectory for managing .torrent files (copy them here)
├── data/ # Subdirectory for browsing and reading torrent contents
└── .stats # Virtual file with system statistics
When a multi-file torrent (a torrent containing more than one file) is added, its top-level entry in /data/ is represented as a directory — not a regular file. This is the correct and expected FUSE behaviour for multi-file torrents, not a bug.
For example, listing a torrent entry with ls -la:
dr-xr-xr-x 2 user user 0 Jan 1 1970 ubuntu-22.04.iso.torrent
Observed behaviour for multi-file torrent entries:
- File type:
d(directory), not-(regular file) - Permissions:
r-xr-xr-x(read-only directory) - Size:
0(directories in FUSE report size 0) - Listing the directory: reveals the torrent's internal file/directory tree
Why? Multi-file torrents internally contain a directory tree of files. TorrentFS mirrors this structure faithfully in the FUSE filesystem — the torrent entry itself becomes a directory node, and its contents (subdirectories and files) appear as children within it.
| Torrent Type | Top-level Entry in /data |
Contents |
|---|---|---|
| Single-file | Directory (contains one file) | The single data file |
| Multi-file | Directory | Subdirectories + files from torrent |
Both single-file and multi-file torrents always appear as directory entries. A single-file torrent's directory contains exactly one child (the torrent's data file); a multi-file torrent's directory contains the full file tree as defined in the .torrent metadata.
For a multi-file torrent like archlinux.torrent:
/data/archlinux.torrent/
├── arch/
│ ├── x86_64/
│ │ ├── core.db (regular file, readable)
│ │ └── extra.db (regular file, readable)
│ └── aarch64/
│ └── core.db (regular file, readable)
└── README.md (regular file, readable)
Each subdirectory and file within the torrent is accessible by reading through the FUSE mount — content is downloaded on demand as needed.
Torrent files added under metadata/<source_path>/ are grouped in data/<source_path>/:
metadata/
└── os/
└── linux/
└── archlinux.torrent # ← copy .torrent here
data/
└── os/
└── linux/
└── archlinux.torrent/ # ← appears as directory (multi-file) or directory with single file
Add this to your Cargo.toml:
[dependencies]
torrentfs = "0.1"use torrentfs::{TorrentInfo, TorrentError};
fn main() -> Result<(), TorrentError> {
let info = TorrentInfo::from_file("example.torrent")?;
println!("Torrent: {}", info.name());
println!("Total Size: {} bytes", info.total_size());
println!("Piece Length: {} bytes", info.piece_length());
println!("Number of Pieces: {}", info.num_pieces());
println!("Number of Files: {}", info.num_files());
let files = info.files()?;
for file in files {
println!(" {} ({} bytes)", file.path, file.size);
}
let hash = info.info_hash()?;
print!("Info Hash: ");
for byte in &hash {
print!("{:02x}", byte);
}
println!();
Ok(())
}The main type for working with torrent metadata.
from_file(path)- Load a torrent file from diskname()- Get the torrent nametotal_size()- Get total size in bytespiece_length()- Get piece length in bytesnum_pieces()- Get number of piecesnum_files()- Get number of filesfiles()- Get list of files with paths and sizesinfo_hash()- Get the 20-byte SHA1 info hashmetadata()- Get all metadata in one struct
The library uses thiserror for error handling:
pub enum TorrentError {
InvalidFile(String),
ParseError(String),
IoError(std::io::Error),
NullPointer,
Unknown { code: i32, message: String },
}# Ensure libtorrent is installed
# On Ubuntu/Debian:
sudo apt-get install libtorrent-rasterbar-dev
# Build
cargo build
# Run tests
cargo test
# Run example
cargo run --example torrent_info -- /path/to/file.torrentPre-built images are available at ghcr.io/tsix404/torrentfs.
# Pull the latest stable release
docker pull ghcr.io/tsix404/torrentfs:latest
# Pull the development build (latest main branch)
docker pull ghcr.io/tsix404/torrentfs:dev
# Pull a specific version
docker pull ghcr.io/tsix404/torrentfs:v0.1.0| Tag | Description |
|---|---|
dev |
Latest push to main branch |
latest |
Latest Git tag starting with v* (stable release) |
vX.Y.Z |
Specific semver release |
vX.Y |
Minor version track (e.g., v0.1) |
docker build -t torrentfs .TorrentFS requires FUSE kernel access inside the container. Use the following flags when running:
# Create a mount point on the host
mkdir -p /tmp/torrentfs
# Run with FUSE support
docker run --rm \
--cap-add SYS_ADMIN \
--device /dev/fuse \
-v /tmp/torrentfs:/mnt:shared \
ghcr.io/tsix404/torrentfs:latestRequired flags:
--cap-add SYS_ADMIN— grants the container capability to mount FUSE filesystems--device /dev/fuse— passes the FUSE device into the container-v /host/path:/mnt:shared— mounts a host directory for the virtual filesystem (use:sharedfor bidirectional access)
Note: On systems with AppArmor or SELinux, you may need additional security profiles. See your distribution's documentation for details.
MIT