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
54 changes: 54 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion crates/rattler/src/install/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,10 @@ pub fn link_package_sync(
Ok(paths)
}

fn compute_paths(
/// A helper function that generates the paths for the package files based on
/// the [PathsJson] and [IndexJson] with any required modifications for noarch
/// python packages.
pub fn compute_paths(
index_json: &IndexJson,
paths_json: &PathsJson,
python_info: Option<&PythonInfo>,
Expand Down
13 changes: 13 additions & 0 deletions crates/rattler_fuse/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "rattler_fuse"
version = "0.1.0"
description = "A create for mounting conda environments as a FUSE filesystem"
categories.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
readme.workspace = true

[dependencies]
fuser = "0.15.1"
14 changes: 14 additions & 0 deletions crates/rattler_fuse/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
28 changes: 28 additions & 0 deletions crates/rattler_fuse_test/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "rattler_fuse_test"
version = "0.1.0"
categories.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
readme.workspace = true

[dependencies]
fuser = "0.15.1"
rattler_lock = { path = "../rattler_lock" }
rattler_conda_types = { path = "../rattler_conda_types" }
rattler_cache = { path = "../rattler_cache" }
rattler = { path = "../rattler" }
rattler_digest = { path = "../rattler_digest" }
reqwest = { workspace = true }
reqwest-middleware = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
regex = { workspace = true }
once_cell = { workspace = true }
memchr = { workspace = true }
tracing = { workspace = true }

[target.'cfg(unix)'.dependencies]
clap = { workspace = true }
libc = { workspace = true }
180 changes: 180 additions & 0 deletions crates/rattler_fuse_test/src/filesystem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use crate::patching::OpenFile;
use crate::tree::EnvTree;
use crate::tree_objects::Node;
use fuser::consts::FOPEN_KEEP_CACHE;
use fuser::{
FileType, Filesystem, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, ReplyOpen, Request,
};
use libc::ENOENT;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
use std::time::Duration;
use std::vec;

// All data is read-only so it can be cached forever
const TTL: Duration = Duration::from_secs(365 * 24 * 60 * 60);

pub struct RattlerFS {
tree: EnvTree,
uid: u32,
gid: u32,
open_files: HashMap<u64, OpenFile>,
}

impl RattlerFS {
pub fn new(tree: EnvTree, uid: u32, gid: u32) -> RattlerFS {
RattlerFS {
tree,
uid,
gid,
open_files: HashMap::new(),
}
}
}

impl Filesystem for RattlerFS {
fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
if let Some(node) = self.tree.find_by_inode(parent) {
if let Ok(dir) = node.borrow().as_directory() {
if let Some(child) = dir.get_child(name) {
return reply.entry(&TTL, &child.borrow().stat(self.uid, self.gid), 0);
}
}
}
reply.error(ENOENT);
}

fn readlink(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyData) {
if let Some(node) = self.tree.find_by_inode(ino) {
if let Ok(symlink) = node.borrow().as_symlink() {
return reply.data(symlink.readlink().as_os_str().as_bytes());
}
}
reply.error(ENOENT);
}

fn getattr(&mut self, _req: &Request<'_>, ino: u64, _fh: Option<u64>, reply: ReplyAttr) {
if let Some(node) = self.tree.find_by_inode(ino) {
return reply.attr(&TTL, &node.borrow().stat(self.uid, self.gid));
}
reply.error(ENOENT);
}

fn open(&mut self, _req: &Request<'_>, ino: u64, _flags: i32, reply: ReplyOpen) {
let node = self.tree.find_by_inode(ino);
match node {
Some(node) => {
let borrowed = node.borrow();
let open_file = borrowed.open();
let fd = open_file.fd();
self.open_files.insert(fd, open_file);
reply.opened(fd, FOPEN_KEEP_CACHE);
}
None => {
reply.error(ENOENT);
}
}
}

fn release(
&mut self,
_req: &Request<'_>,
_ino: u64,
fh: u64,
_flags: i32,
_lock_owner: Option<u64>,
_flush: bool,
reply: fuser::ReplyEmpty,
) {
if self.open_files.remove(&fh).is_none() {
reply.error(ENOENT);
} else {
reply.ok();
}
}

fn read(
&mut self,
_req: &Request<'_>,
_ino: u64,
fh: u64,
offset: i64,
size: u32,
_flags: i32,
_lock: Option<u64>,
reply: ReplyData,
) {
let mut buffer = vec![0; size as usize];
let f = self.open_files.get_mut(&fh).unwrap();
if let Ok(bytes_read) = f.read_at(&mut buffer, offset as u64) {
reply.data(&buffer[..bytes_read]);
} else {
panic!("Failed to read from file");
}
}

fn readdir(
&mut self,
_req: &Request<'_>,
ino: u64,
_fh: u64,
offset: i64,
mut reply: ReplyDirectory,
) {
let node = self.tree.find_by_inode(ino);
match node {
Some(node) => {
let borrowed = node.borrow();
match &*borrowed {
Node::Directory(dir) => {
let entries = dir.children();
if offset <= 0 {
let _ = reply.add(ino, 1, FileType::Directory, ".");
}
if offset <= 1 {
let _ = if ino == self.tree.root_ino() {
reply.add(ino, 2, FileType::Directory, "..")
} else {
match &*borrowed.parent().borrow() {
Node::Directory(_) => reply.add(
borrowed.parent().borrow().ino(),
2,
FileType::Directory,
"..",
),
_ => {
unreachable!()
}
}
};
}
for (i, entry) in
entries.into_iter().enumerate().skip((offset - 2) as usize)
{
let borrowed = entry.borrow();
let kind: FileType = match &*borrowed {
Node::Directory(_) => FileType::Directory,
Node::File(_) | Node::EntryPoint(_) => FileType::RegularFile,
Node::Symlink(_) => FileType::Symlink,
};
if reply.add(borrowed.ino(), (i + 3) as i64, kind, borrowed.name()) {
break;
}
}
reply.ok();
}
Node::Symlink(_) => {
panic!("TODO Symlink!");
}
Node::File(_) | Node::EntryPoint(_) => {
reply.error(ENOENT);
}
}
}
None => {
reply.error(ENOENT);
}
}
}
}
73 changes: 73 additions & 0 deletions crates/rattler_fuse_test/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use crate::filesystem::RattlerFS;
use clap::{Arg, ArgAction, Command};
use fuser::MountOption;
use rattler_cache::default_cache_dir;
use rattler_lock::DEFAULT_ENVIRONMENT_NAME;
use std::path::PathBuf;
use std::vec;
use tree::EnvTree;

mod filesystem;
mod patching;
mod tree;
mod tree_objects;

#[tokio::main]
async fn main() {
let matches = Command::new("rattler_fuse_test")
.arg(
Arg::new("lock_file")
.required(true)
.index(1)
.help("Path to the pixi.lock file"),
)
.arg(
Arg::new("mount_point")
.required(true)
.index(2)
.help("Act as a client, and mount FUSE at given path"),
)
.arg(
Arg::new("env_name")
.long("env-name")
.default_value(DEFAULT_ENVIRONMENT_NAME)
.help("Name of the environment to mount"),
)
.arg(
Arg::new("print-tree")
.long("print-tree")
.action(ArgAction::SetTrue)
.help("Print the tree before mounting"),
)
.get_matches();

let lock_file_path = PathBuf::from(matches.get_one::<String>("lock_file").unwrap());
let cache_dir = default_cache_dir()
.unwrap()
.join(rattler_cache::PACKAGE_CACHE_DIR);
let env_name = matches.get_one::<String>("env_name").unwrap();
let target_dir = PathBuf::from(matches.get_one::<String>("mount_point").unwrap());
let tree = EnvTree::from_lock_file(&lock_file_path, env_name, &target_dir, &cache_dir)
.await
.unwrap();
if matches.get_flag("print-tree") {
tree.print_tree();
}

println!(
"Mounting {} from {} at {} with cache dir {}",
env_name,
lock_file_path.display(),
target_dir.display(),
cache_dir.display()
);
let options = vec![
MountOption::RO,
MountOption::FSName("rattlerfs".to_string()),
MountOption::AllowOther,
MountOption::AutoUnmount,
];
let uid = unsafe { libc::getuid() };
let gid = unsafe { libc::getgid() };
fuser::mount2(RattlerFS::new(tree, uid, gid), &target_dir, &options).unwrap();
}
Loading
Loading