pijul nest
guest [sign in]

Adding the OCaml interface

pmeunier
Jun 5, 2025, 5:35 PM
ODUDDQRY373JMDR6W2BHUWSKEU6TEICQMNZIAIFKJPNUA5UN3C4QC

Dependencies

  • [2] UWQB743K First working shell (with ocaml code)
  • [3] FRNJITN6 Elfedit: Error instead of assert when there is no section table
  • [4] VR4QJBTN Elfedit: file sizes not aligned with page boundaries
  • [5] E6W7X7U3 Elfedit: fixing an alignment issue on GCC
  • [6] AR2X3TLA Initial commit: introducing elfedit
  • [7] VZTI2E23 Elfedit: handling the case where we move the same section we're adding
  • [8] 5PJAXSZ7 Elfedit: correct reallocation of dynstr/dynamic
  • [9] ZV57M4VN Debugging
  • [10] Z43EQIWN Elfedit: edit to file
  • [11] WGAKJ25E Adding a debug_svg command

Change contents

  • replacement in src/mount.rs at line 1
    [2.50][2.51:87]()
    use libc::{MS_BIND, mount, umount};
    [2.50]
    [2.87]
    use libc::{MS_BIND, MS_PRIVATE, mount, umount};
  • edit in src/mount.rs at line 8
    [2.192]
    [2.192]
    }
    pub fn make_root_private() -> Result<(), std::io::Error> {
    unsafe {
    let err = mount(
    std::ptr::null(),
    b"/\0".as_ptr() as *const i8,
    std::ptr::null(),
    MS_PRIVATE,
    std::ptr::null(),
    );
    if err == 0 {
    Ok(())
    } else {
    Err(std::io::Error::last_os_error())
    }
    }
  • replacement in src/mount.rs at line 28
    [2.208][2.208:349]()
    pub fn bind(source: &str, target: &str) -> Result<Mount, std::io::Error> {
    let target = std::ffi::CString::new(target).unwrap();
    [2.208]
    [2.349]
    pub fn bind<S: AsRef<std::path::Path>, T: AsRef<std::path::Path>>(
    source: S,
    target: T,
    ) -> Result<Mount, std::io::Error> {
    let target = target.as_ref().to_str().unwrap();
    let target = CString::new(target).unwrap();
    let source = source.as_ref().to_str().unwrap();
    let source = CString::new(source).unwrap();
  • replacement in src/mount.rs at line 38
    [2.395][2.395:461]()
    std::ffi::CString::new(source).unwrap().as_ptr(),
    [2.395]
    [2.461]
    source.as_ptr(),
  • replacement in src/mount.rs at line 44
    [2.602][2.602:639]()
    error!("err {:?}", err);
    [2.602]
    [2.639]
    if err == 0 {
    return Ok(Mount {
    target: Some(target),
    });
    }
    Err(std::io::Error::last_os_error())
    }
    }
    pub fn ramfs<T: AsRef<std::path::Path>>(target: T) -> Result<Mount, std::io::Error> {
    let target = target.as_ref().to_str().unwrap();
    let target = CString::new(target).unwrap();
    unsafe {
    let err = mount(
    b"ramfs\0".as_ptr() as *const i8,
    target.as_ptr(),
    b"ramfs\0".as_ptr() as *const i8,
    0,
    std::ptr::null(),
    );
  • replacement in src/main.rs at line 1
    [2.1363][2.1364:1421]()
    use libc::*;
    use std::ffi::CString;
    use std::path::Path;
    [2.1363]
    [2.1421]
    use elpe::extract::*;
    use elpe::*;
    use std::path::{Path, PathBuf};
  • edit in src/main.rs at line 5
    [2.1441]
    [2.1441]
    use tokio::io::AsyncWriteExt;
    use tonic::codegen::tokio_stream::StreamExt;
  • edit in src/main.rs at line 9
    [2.1530][2.1530:1586]()
    use elpe::extract::*;
    use elpe::mount::*;
    use elpe::*;
  • replacement in src/main.rs at line 15
    [2.1659][2.1659:1694]()
    deb_client: Arc<elpe::Client>,
    [2.1659]
    [2.1694]
    deb_client: elpe::Client,
    sender: tokio::sync::mpsc::UnboundedSender<(
    crate::container::BuildRequest,
    tokio::sync::oneshot::Sender<Result<PathBuf, String>>,
    )>,
  • edit in src/main.rs at line 21
    [2.1696][2.1696:1711]()
    use proto::*;
  • replacement in src/main.rs at line 24
    [2.1775][2.1775:1800]()
    async fn derivation(
    [2.1775]
    [2.1800]
    async fn add_path(
  • replacement in src/main.rs at line 26
    [2.1815][2.1815:2000]()
    request: tonic::Request<DerivationRequest>,
    ) -> Result<tonic::Response<DerivationReply>, tonic::Status> {
    debug!("Got a request from {:?}", request.remote_addr());
    [2.1815]
    [2.2000]
    request: tonic::Request<tonic::Streaming<proto::AddPathRequest>>,
    ) -> Result<tonic::Response<proto::DerivationReply>, tonic::Status> {
    let mut r = request.into_inner();
    let mut current_file = None;
    let ref store = self.deb_client.store_path;
    let tmp_dir = tempfile::tempdir_in(store)?;
    let mut hasher = blake3::Hasher::new();
  • replacement in src/main.rs at line 35
    [2.2001][2.2001:2198]()
    // let reply = hello_world::HelloReply {
    // message: format!("Hello {}!", request.into_inner().name),
    // };
    // Ok(Response::new(reply))
    unimplemented!()
    [2.2001]
    [2.2198]
    loop {
    trace!("waiting for next in stream");
    let Some(r) = r.next().await else { break };
    let r = r.unwrap();
    match r.request {
    Some(proto::add_path_request::Request::File(f)) => {
    hasher.update(b"\0f");
    hasher.update(f.name.as_bytes());
    hasher.update(b"\0");
    let p = tmp_dir.path().join(&f.name);
    tokio::fs::create_dir_all(p.parent().unwrap()).await?;
    current_file = Some(tokio::fs::File::create(&p).await?)
    }
    Some(proto::add_path_request::Request::Directory(d)) => {
    hasher.update(b"\0d");
    hasher.update(d.name.as_bytes());
    hasher.update(b"\0");
    let p = tmp_dir.path().join(&d.name);
    tokio::fs::create_dir_all(&p).await.unwrap();
    }
    Some(proto::add_path_request::Request::Contents(c)) => {
    hasher.update(&c.content);
    current_file.as_mut().unwrap().write_all(&c.content).await?;
    }
    None => break,
    }
    }
    let path = store.join(data_encoding::HEXLOWER.encode(hasher.finalize().as_bytes()));
    use tokio::io::ErrorKind;
    let new = tmp_dir.into_path();
    match tokio::fs::rename(&new, &path).await {
    Ok(()) => (),
    Err(e) if e.kind() == ErrorKind::DirectoryNotEmpty => (),
    Err(e) => {
    tokio::fs::remove_dir_all(&new).await?;
    return Err(e.into());
    }
    }
    info!("add_path extracted to {:?}", path);
    Ok(tonic::Response::new(proto::DerivationReply {
    result: Some(proto::derivation_reply::Result::Ok(
    proto::DerivationResult {
    destdir: vec![path.to_str().unwrap().to_string()],
    paths: Vec::new(),
    path_patterns: Vec::new(),
    },
    )),
    }))
    }
    async fn derivation(
    &self,
    request: tonic::Request<proto::DerivationRequest>,
    ) -> Result<tonic::Response<proto::DerivationReply>, tonic::Status> {
    debug!("derivation request");
    let r = request.into_inner();
    let (tx, rx) = tokio::sync::oneshot::channel();
    self.sender
    .send((
    crate::container::BuildRequest {
    name: r.name,
    paths: r.paths,
    script: r.builder,
    target: r.target,
    },
    tx,
    ))
    .unwrap();
    Ok(tonic::Response::new(match rx.await.unwrap() {
    Ok(out) => proto::DerivationReply {
    result: Some(proto::derivation_reply::Result::Ok(
    proto::DerivationResult {
    destdir: vec![out.to_str().unwrap().to_string()],
    paths: Vec::new(),
    path_patterns: Vec::new(),
    },
    )),
    },
    Err(e) => proto::DerivationReply {
    result: Some(proto::derivation_reply::Result::Error(e)),
    },
    }))
  • replacement in src/main.rs at line 121
    [2.2249][2.2249:2371]()
    request: tonic::Request<UbuntuReleaseRequest>,
    ) -> Result<tonic::Response<DerivationReply>, tonic::Status> {
    [2.2249]
    [2.2371]
    request: tonic::Request<proto::UbuntuReleaseRequest>,
    ) -> Result<tonic::Response<proto::DerivationReply>, tonic::Status> {
  • replacement in src/main.rs at line 137
    [2.2850][2.2850:2932]()
    path: vec![p.to_str().unwrap().to_string()],
    error: None,
    [2.2850]
    [2.2932]
    result: Some(proto::derivation_reply::Result::Ok(
    proto::DerivationResult {
    destdir: vec![p.to_str().unwrap().to_string()],
    paths: Vec::new(),
    path_patterns: Vec::new(),
    },
    )),
  • replacement in src/main.rs at line 149
    [2.2995][2.2995:3117]()
    request: tonic::Request<UbuntuPackageRequest>,
    ) -> Result<tonic::Response<DerivationReply>, tonic::Status> {
    [2.2995]
    [2.3117]
    request: tonic::Request<proto::UbuntuPackageRequest>,
    ) -> Result<tonic::Response<proto::DerivationReply>, tonic::Status> {
  • replacement in src/main.rs at line 162
    [2.3507][2.3507:3538]()
    info!("path {:?}", p);
    [2.3507]
    [2.3538]
    info!("path {:?} {:#?}", r.name, p);
  • replacement in src/main.rs at line 164
    [2.3595][2.3595:3773]()
    path: p
    .iter()
    .rev()
    .map(|x| x.to_str().unwrap().to_string())
    .collect(),
    error: None,
    [2.3595]
    [2.3773]
    result: Some(proto::derivation_reply::Result::Ok(
    proto::DerivationResult {
    destdir: p
    .result
    .iter()
    .rev()
    .map(|x| x.to_str().unwrap().to_string())
    .collect(),
    paths: p.paths.into_iter().filter_map(Arc::into_inner).collect(),
    path_patterns: Vec::new(),
    },
    )),
  • edit in src/main.rs at line 177
    [2.3785][2.3785:4845]()
    }
    }
    pub fn child<P: AsRef<Path>>(path: &[P]) {
    let store = Path::new("/home/pe/Projets/frix/store/ailpe/store");
    std::fs::create_dir_all(store).unwrap();
    let vm_store = Path::new("/ailpe/store");
    let mut path_env = "PATH=".to_string();
    let mut ld_env = "LD_LIBRARY_PATH=".to_string();
    let mut mounts = Vec::new();
    for p in path {
    use std::fmt::Write;
    let host = store.join(p.as_ref().file_name().unwrap());
    let guest = vm_store.join(p.as_ref().file_name().unwrap());
    std::fs::create_dir_all(&host).unwrap();
    write!(
    &mut path_env,
    "{}/usr/bin:{}/bin:",
    guest.to_str().unwrap(),
    guest.to_str().unwrap()
    )
    .unwrap();
    write!(
    &mut ld_env,
    "{}/lib64/x86_64-linux-gnu:{}/lib/x86_64-linux-gnu:",
    guest.to_str().unwrap(),
    guest.to_str().unwrap()
    )
    .unwrap();
    mounts.push(Mount::bind(p.as_ref().to_str().unwrap(), host.to_str().unwrap()).unwrap());
  • edit in src/main.rs at line 178
    [2.4851][2.4851:5931]()
    println!("{:?}", mounts);
    // Fork in order to be able to unmount despite the chroot.
    let f = unsafe { fork() };
    if f == 0 {
    let c = std::ffi::CString::new("/home/pe/Projets/frix/store").unwrap();
    if unsafe { chroot(c.as_ptr()) } < 0 {
    println!("child {:?}", std::io::Error::last_os_error());
    }
    println!("{:?}\n{:?}", path_env, ld_env);
    let penv = CString::new(path_env).unwrap();
    let lenv = CString::new(ld_env).unwrap();
    unsafe {
    let c = std::ffi::CString::new(
    "/ailpe/store/tmp3/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",
    )
    .unwrap();
    execve(
    c.as_ptr(),
    [c.as_ptr(), std::ptr::null()].as_ptr(),
    [penv.as_ptr(), lenv.as_ptr(), std::ptr::null()].as_ptr(),
    );
    println!("execve {:?}", std::io::Error::last_os_error());
    }
    } else {
    let mut status = 0;
    unsafe { waitpid(f, &mut status, 0) };
    std::mem::drop(mounts);
    }
  • edit in src/main.rs at line 179
    [2.5933][2.5933:6903]()
    /*
    let lua = mlua::Lua::new();
    let map_table = lua.create_table().unwrap();
    map_table.set(1, "one").unwrap();
    map_table.set("two", 2).unwrap();
    lua.globals().set("map_table", map_table).unwrap();
    let derivation = lua
    .create_function(|st, t: mlua::Table| {
    t.for_each(|k: String, v: mlua::Value| {
    println!("{:?} {:?}", k, v);
    Ok(())
    }).unwrap();
    Ok(1)
    })
    .unwrap();
    lua.globals().set("__derivation", derivation).unwrap();
    let ubuntu = lua
    .create_function(|st, (t, name): (mlua::Table, String)| {
    t.for_each(|k: String, v: mlua::Value| {
    println!("{:?} {:?}", k, v);
    Ok(())
    }).unwrap();
    let mut t = st.create_table().unwrap();
    t.set(1, "one").unwrap();
    Ok(t)
    })
    .unwrap();
    lua.globals().set("__ubuntu", ubuntu).unwrap();
  • replacement in src/main.rs at line 180
    [2.6904][2.6904:7530]()
    let ubuntu_ctx = lua
    .create_function(|st: &mlua::Lua, t: mlua::Table| -> mlua::Result<mlua::Table> {
    t.for_each(|k: String, v: mlua::Value| {
    println!("{:?} {:?}", k, v);
    Ok(())
    }).unwrap();
    let mut t = st.create_table().unwrap();
    t.set(1, "one").unwrap();
    Ok(t)
    })
    .unwrap();
    lua.globals().set("__ubuntu_ctx", ubuntu_ctx).unwrap();
    lua.load(std::fs::read_to_string(&std::env::args().nth(1).unwrap()).unwrap())
    .exec()
    .unwrap();
    return;
    */
    #[tokio::main]
    async fn main() {
    [2.6904]
    [2.7530]
    fn main() {
  • edit in src/main.rs at line 188
    [2.7780][2.7780:8287]()
    let elpe = Elpe {
    deb_client: Arc::new(Client {
    c: lazy_init::Lazy::new(),
    mirror: "http://fr.archive.ubuntu.com/ubuntu".to_string(),
    store_path: Path::new("/home/pe/Projets/frix/store").to_path_buf(),
    in_release: None.into(),
    }),
    };
    let addr = "0.0.0.0:50051".parse().unwrap();
    tonic::transport::Server::builder()
    .add_service(elpe_server::ElpeServer::new(elpe))
    .serve(addr)
    .await
    .unwrap();
  • replacement in src/main.rs at line 189
    [2.8288][2.8288:8417]()
    /*
    let h_secu = c.in_release("noble-security").await.unwrap();
    let h = c.in_release("noble").await.unwrap();
    [2.8288]
    [2.8417]
    let store_path = Path::new("/home/pe/Projets/frix/store");
    let user = "pe";
    let home = "/home/pe";
    let container_channel = crate::container::serve(user, store_path);
  • replacement in src/main.rs at line 195
    [2.8418][2.8418:8638]()
    let p_secu = c.packages(&h_secu, "main", "amd64").await.unwrap();
    let p_main = c.packages(&h, "main", "amd64").await.unwrap();
    let p_universe = c.packages(&h, "universe", "amd64").await.unwrap();
    [2.8418]
    [2.8638]
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async move {
    privdrop::PrivDrop::default().user(user).apply().unwrap();
    unsafe {
    std::env::set_var("HOME", home);
    }
    let (sender, receiver) = tokio::sync::mpsc::unbounded_channel::<(
    crate::container::BuildRequest,
    tokio::sync::oneshot::Sender<Result<PathBuf, String>>,
    )>();
  • replacement in src/main.rs at line 206
    [2.8639][2.8639:8830]()
    let index_secu = deb::Index::open(&p_secu).unwrap();
    let index_main = deb::Index::open(&p_main).unwrap();
    let index_universe = deb::Index::open(&p_universe).unwrap();
    [2.8639]
    [2.8830]
    let t = tokio::spawn(crate::container::forward(receiver, container_channel));
  • replacement in src/main.rs at line 208
    [2.8831][2.8831:9063]()
    let h = Arc::new(Mutex::new(HashMap::new()));
    let index = &[index_main, index_universe, index_secu];
    download_extract_deps(index, &c, h.clone(), "hello-traditional")
    .await
    .unwrap();
    [2.8831]
    [2.9063]
    let elpe = Elpe {
    deb_client: Client::new(store_path, "http://fr.archive.ubuntu.com/ubuntu"),
    sender,
    };
  • replacement in src/main.rs at line 213
    [2.9064][2.9064:9131]()
    download_extract_deps(index, &c, h.clone(), "bash-static")
    [2.9064]
    [2.9131]
    /*
    let x = elpe
    .deb_client
    .command("noble", "qemu-utils", "usr/bin/qemu-nbd")
    .await
    .output()
  • replacement in src/main.rs at line 222
    [2.9174][2.9174:9218]()
    trace!("{:#?}", h);
    return;
    [2.9174]
    [2.9218]
    panic!("x = {:?}", x);
    */
  • replacement in src/main.rs at line 225
    [2.9219][2.9219:10292]()
    let pid = unsafe {
    syscall(
    SYS_clone3,
    &libc::clone_args {
    flags: (CLONE_NEWPID | CLONE_NEWNET | CLONE_NEWNS) as u64,
    pidfd: 0,
    parent_tid: 0,
    child_tid: 0,
    stack: 0,
    stack_size: 0,
    tls: 0,
    set_tid: 0,
    set_tid_size: 0,
    cgroup: 0,
    exit_signal: SIGCHLD as u64,
    },
    std::mem::size_of::<libc::clone_args>(),
    )
    };
    println!("pid {:?}", pid);
    if pid == 0 {
    child(&[
    "/home/pe/Projets/frix/tmp",
    "/home/pe/Projets/frix/tmp2",
    "/home/pe/Projets/frix/tmp3",
    ]);
    } else {
    println!("{:?}", std::io::Error::last_os_error());
    let x = unsafe { waitpid(pid as i32, std::ptr::null_mut(), 0) };
    println!("x {:?}", x);
    }
    */
    [2.9219]
    [2.10292]
    let addr = "0.0.0.0:50051".parse().unwrap();
    tokio::select! {
    _ = tonic::transport::Server::builder()
    .add_service(proto::elpe_server::ElpeServer::new(elpe))
    .serve(addr)
    => {}
    _ = t => {}
    }
    })
  • edit in src/lib.rs at line 1
    [2.10326]
    [2.10327]
    //! Run bash scripts in containers on Linux, with only deterministic
    //! Debian or Ubuntu packages in scope.
    #![deny(
    missing_docs,
    trivial_casts,
    trivial_numeric_casts,
    unused_import_braces,
    unused_qualifications
    )]
  • edit in src/lib.rs at line 13
    [2.10406]
    [2.10406]
    use std::sync::Arc;
  • edit in src/lib.rs at line 16
    [2.10453]
    [2.10453]
    use tokio::sync::{Mutex, Semaphore};
  • edit in src/lib.rs at line 18
    [2.10469][2.10469:10489]()
    use std::sync::Arc;
  • edit in src/lib.rs at line 19
    [2.10490]
    [2.10490]
    /// Handle Debian packages.
  • edit in src/lib.rs at line 22
    [2.10504]
    [2.10504]
    /// Extract a Debian package and all dependencies into the store.
  • edit in src/lib.rs at line 25
    [2.10522]
    [2.10522]
    /// Recursive iterator on the files/directories in a path.
    pub mod find_files;
    /// Mount and unmount paths.
  • edit in src/lib.rs at line 30
    [2.10537]
    [2.10537]
    /// Run scripts in a chroot inside a new Linux namespace, with the
    /// right directories mounted and symbolically linked to simulate a
    /// Debian/Ubuntu filesystem hierarchy.
    pub mod container;
  • edit in src/lib.rs at line 36
    [2.10538]
    [2.10538]
    /// Errors
  • edit in src/lib.rs at line 39
    [2.10579]
    [2.10579]
    /// An error from manipulating a Debian package.
  • edit in src/lib.rs at line 42
    [2.10634]
    [2.10634]
    /// IO error
  • edit in src/lib.rs at line 45
    [2.10692]
    [2.10692]
    /// An error from persisting a temporary file.
  • edit in src/lib.rs at line 48
    [2.10763]
    [2.10763]
    /// An error from creating a temporary file.
  • edit in src/lib.rs at line 50
    [2.10789]
    [2.10789]
    Tempfile(#[from] async_tempfile::Error),
    #[error(transparent)]
    /// An error from `elfedit`, this crate's companion ELF patcher.
  • edit in src/lib.rs at line 54
    [2.10822]
    [2.10822]
    /// HTTP errors
  • edit in src/lib.rs at line 57
    [2.10885]
    [2.10885]
    /// Wrong signature (on an Ubuntu index)
  • edit in src/lib.rs at line 60
    [2.10932]
    [2.10932]
    /// Wrong package hash
  • replacement in src/lib.rs at line 62
    [2.10986][2.10986:11035]()
    WrongHash { expected: String, got: String },
    [2.10986]
    [2.11035]
    WrongHash {
    /// Expected hash.
    expected: String,
    /// Obtained hash.
    got: String
    },
    /// Wrong package or file size
  • edit in src/lib.rs at line 71
    [2.11077]
    [2.11077]
    /// Wrong result symlink: Elpe packages have one "input name"
    /// given by the hash of their inputs, and that directory is
    /// symlinked from the "output name", which is the hash of their
    /// output. This makes the outputs verifiable all the way down:
    /// failures to verify result in this error.
  • replacement in src/lib.rs at line 77
    [2.11150][2.11150:11210]()
    WrongResultSymlink { expected: PathBuf, got: PathBuf },
    [2.11150]
    [2.11210]
    WrongResultSymlink {
    /// Expected output.
    expected: PathBuf,
    /// Obtained output.
    got: PathBuf
    },
    /// The contained process failed to produce the file or directory
    /// called `$DESTDIR`.
    #[error("Failed to produce $DESTDIR")]
    NoDestDir,
    /// The build process returned a status other than 0.
    #[error("Build process returned {status}")]
    BuildReturn {
    /// Return status.
    status: i32
    },
  • edit in src/lib.rs at line 95
    [2.11213]
    [2.11213]
    /// A Debian index client.
    #[derive(Clone)]
  • replacement in src/lib.rs at line 98
    [2.11233][2.11233:11417]()
    // dest: PathBuf,
    pub c: lazy_init::Lazy<reqwest::Client>,
    pub mirror: String,
    pub store_path: PathBuf,
    pub in_release: tokio::sync::Mutex<Option<Arc<InRelease>>>,
    [2.11233]
    [2.11417]
    c: lazy_init::Lazy<reqwest::Client>,
    mirror: String,
    store_path: PathBuf,
    in_release: Arc<Mutex<Option<Arc<InRelease>>>>,
    download_sem: Arc<Semaphore>,
    store_locks: Arc<std::sync::Mutex<HashMap<PathBuf, Arc<Mutex<()>>>>>,
    timeout: std::time::Duration,
    }
    /// A locked store path, to prevent concurrent writes to the
    /// store. For performance reasons, these are memory locks rather than
    /// filesystem locks.
    pub struct StoreLock {
    locks: Arc<std::sync::Mutex<HashMap<PathBuf, Arc<Mutex<()>>>>>,
    p: PathBuf,
    _lock: tokio::sync::OwnedMutexGuard<()>,
    }
    impl Drop for StoreLock {
    fn drop(&mut self) {
    debug!("store lock: dropping {:?}", self.p);
    self.locks.lock().unwrap().remove(&self.p);
    }
  • edit in src/lib.rs at line 133
    [2.11601]
    [2.11601]
    ///
  • edit in src/lib.rs at line 155
    [2.12113]
    [2.12113]
    const MAX_PARALLEL_DOWNLOADS: usize = 20;
  • edit in src/lib.rs at line 159
    [2.12128]
    [2.12128]
    pub fn new<P: AsRef<std::path::Path>>(store_path: P, mirror: &str) -> Self {
    Client {
    c: lazy_init::Lazy::new(),
    mirror: mirror.to_string(),
    store_path: store_path.as_ref().to_path_buf(),
    in_release: Arc::new(None.into()),
    download_sem: Arc::new(Semaphore::new(MAX_PARALLEL_DOWNLOADS)),
    store_locks: Arc::new(std::sync::Mutex::new(HashMap::new())),
    timeout: std::time::Duration::from_secs(30),
    }
    }
  • replacement in src/lib.rs at line 172
    [2.12172][2.12172:12228]()
    self.c.get_or_create(|| reqwest::Client::new())
    [2.12172]
    [2.12228]
    self.c.get_or_create(|| reqwest::ClientBuilder::new()
    .read_timeout(self.timeout)
    .build().unwrap()
    )
    }
    pub async fn command<R: Into<std::borrow::Cow<'static, str>>, P: AsRef<std::path::Path>>(
    &self,
    release: R,
    pkg: &str,
    cmd: P,
    ) -> Command {
    let h = self.in_release(release.into()).await.unwrap();
    let arch = if cfg!(target_arch = "x86_64") {
    "amd64"
    } else if cfg!(target_arch = "aarch64") {
    "aarch64"
    } else {
    unreachable!()
    };
    let (main, universe) = tokio::join!(
    self.packages(&h, "main", arch),
    self.packages(&h, "universe", arch)
    );
    let main = main.unwrap();
    let universe = universe.unwrap();
    let index = vec![
    deb::Index::open(&main).unwrap(),
    deb::Index::open(&universe).unwrap(),
    ];
    let p = extract::download_extract_deps(&index, self, pkg)
    .await
    .unwrap();
    debug!("running {:?}", p.result.last().unwrap().join(&cmd));
    Command::new(p.result.last().unwrap().join(&cmd))
    }
    pub async fn lock_store_path<P: AsRef<std::path::Path>>(&self, path: P) -> StoreLock {
    debug!("locking store path {:?}", path.as_ref());
    let p = path.as_ref().to_path_buf();
    let mutex = {
    let mut locks = self.store_locks.lock().unwrap();
    locks
    .entry(p.clone())
    .or_insert_with(|| Arc::new(Mutex::new(())))
    .clone()
    };
    let lock = mutex.lock_owned().await;
    debug!("lock acquired: {:?}", p);
    StoreLock {
    locks: self.store_locks.clone(),
    p,
    _lock: lock,
    }
  • replacement in src/lib.rs at line 328
    [2.15717][2.15717:15742]()
    return Ok(l)
    [2.15717]
    [2.15742]
    return Ok(l);
  • edit in src/lib.rs at line 337
    [2.15995]
    [2.15995]
    let store_lock = self.lock_store_path(&ubuntu).await;
  • edit in src/lib.rs at line 344
    [2.16209]
    [2.16209]
    let tmp = async_tempfile::TempFile::new_in(ubuntu.parent().unwrap()).await?;
  • replacement in src/lib.rs at line 349
    [2.16285][2.16285:16357]()
    .arg(&format!("--output={}", ubuntu.to_str().unwrap()))
    [2.16285]
    [2.16357]
    .arg("--overwrite")
    .arg(&format!("--output={}", tmp.file_path().to_str().unwrap()))
  • replacement in src/lib.rs at line 361
    [2.16728][2.16728:16759]()
    std::mem::drop(i);
    [2.16728]
    [2.16759]
    drop(i);
  • replacement in src/lib.rs at line 364
    [2.16850][2.16850:16935]()
    self.read_in_release(release, tokio::fs::File::open(&ubuntu).await?)
    [2.16850]
    [2.16935]
    self.read_in_release(release, tokio::fs::File::open(&tmp.file_path()).await?)
  • edit in src/lib.rs at line 371
    [2.17124]
    [2.17124]
    drop(store_lock);
    tokio::fs::rename(&tmp.file_path(), &ubuntu).await?;
    std::mem::forget(tmp);
  • replacement in src/lib.rs at line 410
    [2.18231][2.18231:18284]()
    fn url(&self, package: &deb::Stanza) -> String {
    [2.18231]
    [2.18284]
    fn url(&self, file_name: Option<&str>) -> String {
  • replacement in src/lib.rs at line 412
    [2.18317][2.18317:18368]()
    let filename = package.file_name.unwrap();
    [2.18317]
    [2.18368]
    let filename = file_name.unwrap();
  • edit in src/lib.rs at line 416
    [2.18409]
    [2.18409]
    /// Download the given URL and check the hash, moving the
    /// resulting dir atomically.
  • edit in src/lib.rs at line 422
    [2.18689]
    [2.18689]
    debug!("is_ok {:?}", path);
  • edit in src/lib.rs at line 425
    [2.18752]
    [2.18752]
    let lock = self.lock_store_path(path.clone()).await;
  • replacement in src/lib.rs at line 429
    [2.18806][2.18806:18860]()
    let r = self.client().get(url).send().await?;
    [2.18806]
    [2.18860]
    const MAX_ATTEMPTS: usize = 10;
    for i in 1..=MAX_ATTEMPTS {
    match self.try_download(url, sha256, &path).await {
    Ok(()) => break,
    Err(e) if i == MAX_ATTEMPTS => return Err(e),
    Err(e) => {
    error!("attempt {:?} at downloading {:?}: {:?}", i, url, e);
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    }
    }
    }
    drop(lock); // Ensure lock lives until here.
    Ok((Downloaded { path }, true))
    }
    async fn try_download(
    &self,
    url: &str,
    sha256: &str,
    path: &std::path::Path,
    ) -> Result<(), Error> {
    let r = self
    .client()
    .get(url)
    .send()
    .await?;
  • replacement in src/lib.rs at line 459
    [2.19005][2.19005:19064]()
    let mut f = tokio::fs::File::create(&path).await?;
    [2.19005]
    [2.19064]
    let mut f = async_tempfile::TempFile::new_in(self.store_path.as_path()).await?;
  • replacement in src/lib.rs at line 474
    [2.19503][2.19503:19543]()
    Ok((Downloaded { path }, true))
    [2.19503]
    [2.19543]
    tokio::fs::rename(&f.file_path(), &path).await?;
    std::mem::forget(f);
    Ok(())
  • file addition: find_files.rs (----------)
    [2.15]
    use std::path::PathBuf;
    use tracing::*;
    pub fn find_files(path: PathBuf) -> Result<FindFiles, std::io::Error> {
    debug!("find_files {:?}", path);
    let meta = std::fs::metadata(&path)?;
    Ok(FindFiles {
    stack: vec![(path, meta)],
    })
    }
    pub struct FindFiles {
    stack: Vec<(PathBuf, std::fs::Metadata)>,
    }
    impl Iterator for FindFiles {
    type Item = (PathBuf, std::fs::Metadata);
    fn next(&mut self) -> Option<Self::Item> {
    while let Some((p, meta)) = self.stack.pop() {
    debug!("find_files {:?}", p);
    if meta.is_symlink() || meta.is_file() {
    return Some((p, meta));
    } else if let Ok(dir) = std::fs::read_dir(&p) {
    for e in dir {
    if let Ok(e) = e {
    self.stack.push((e.path(), e.metadata().unwrap()));
    }
    }
    return Some((p, meta));
    } else {
    error!("could not read {:?}", p);
    continue;
    }
    }
    None
    }
    }
    pub fn find_dirs(path: PathBuf) -> Result<FindDirs, std::io::Error> {
    debug!("find_files {:?}", path);
    let meta = std::fs::metadata(&path)?;
    Ok(FindDirs {
    stack: vec![(path, meta)],
    })
    }
    pub struct FindDirs {
    stack: Vec<(PathBuf, std::fs::Metadata)>,
    }
    impl Iterator for FindDirs {
    type Item = (PathBuf, std::fs::Metadata);
    fn next(&mut self) -> Option<Self::Item> {
    while let Some((p, meta)) = self.stack.pop() {
    if meta.is_symlink() {
    continue;
    }
    if let Ok(dir) = std::fs::read_dir(&p) {
    for e in dir {
    if let Ok(e) = e {
    self.stack.push((e.path(), e.metadata().unwrap()));
    }
    }
    return Some((p, meta));
    }
    }
    None
    }
    }
  • replacement in src/extract.rs at line 1
    [2.19587][2.19588:19628]()
    use crate::{Client, Downloaded, Error};
    [2.19587]
    [2.19628]
    use crate::{Client, Downloaded, Error, find_files::*};
  • replacement in src/extract.rs at line 7
    [2.19775][2.19775:19803]()
    use tokio::sync::Semaphore;
    [2.19775]
    [2.19803]
    use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
  • edit in src/extract.rs at line 12
    [2.19837]
    [2.19837]
    /// The result of downloading/extracting a package.
    #[derive(Debug, Clone)]
    pub struct Packages {
    /// Paths of each of the extracted packages, in reverse order from
    /// the root (i.e. the root is the last).
    pub result: Vec<Arc<PathBuf>>,
    /// Paths to directories (i.e. */usr/bin, */usr/lib etc), useful
    /// to create $PATH or $CFLAGS from the client.
    pub paths: Vec<Arc<String>>,
    }
  • replacement in src/extract.rs at line 25
    [2.19899][2.19899:19925]()
    client: &Arc<Client>,
    [2.19899]
    [2.19925]
    client: &Client,
  • replacement in src/extract.rs at line 27
    [2.19944][2.19944:19979]()
    ) -> Result<Vec<PathBuf>, Error> {
    [2.19944]
    [2.19979]
    ) -> Result<Packages, Error> {
  • replacement in src/extract.rs at line 30
    [2.20068][2.20068:20122]()
    let pkg = multi_lookup(index, &package).unwrap();
    [2.20068]
    [2.20122]
    let pkg = multi_lookup(index, &package).await.unwrap();
  • replacement in src/extract.rs at line 35
    [2.20224][2.20224:20257]()
    deps_h: BTreeSet::new(),
    [2.20224]
    [2.20257]
    transitive_deps: Vec::new(),
    transitive_deps_h: BTreeSet::new(),
  • edit in src/extract.rs at line 39
    [2.20322]
    [2.20322]
    files: BTreeSet::new(),
  • edit in src/extract.rs at line 41
    [2.20330][2.20330:20396]()
    let tasks = Arc::new(Semaphore::new(MAX_PARALLEL_DOWNLOADS));
  • edit in src/extract.rs at line 42
    [2.20429]
    [2.20429]
    let mut paths = BTreeSet::new();
  • replacement in src/extract.rs at line 47
    [2.20544][2.20544:20592]()
    let downloaded = rx.await.unwrap();
    [2.20544]
    [2.20592]
    let downloaded = rx.await.unwrap()?;
  • replacement in src/extract.rs at line 49
    [2.20643][2.20643:20721]()
    elt.finalize(client, &files, downloaded, &mut stack, &mut result)
    [2.20643]
    [2.20721]
    let final_path = elt
    .finalize(client, &files, downloaded, &mut stack, &mut result)
  • replacement in src/extract.rs at line 52
    [2.20746][2.20746:20817]()
    seen.insert(elt.package.package, (elt.ld_path, elt.deps));
    [2.20746]
    [2.20817]
    seen.insert(
    elt.package.package,
    (elt.ld_path, elt.transitive_deps, final_path),
    );
    paths = elt.files;
  • replacement in src/extract.rs at line 58
    [2.20834][2.20834:20910]()
    if let Some((ld_path, deps)) = seen.get(&elt.package.package) {
    [2.20834]
    [2.20910]
    if let Some((ld_path, transitive_deps, final_path)) = seen.get(&elt.package.package) {
    debug!("already seen {:?}", elt.package.package);
  • replacement in src/extract.rs at line 66
    [2.21215][2.21215:21371]()
    for dep in deps {
    if last.deps_h.insert(dep.clone()) {
    last.deps.push(dep.clone());
    [2.21215]
    [2.21371]
    for dep in transitive_deps {
    if last.transitive_deps_h.insert(dep.clone()) {
    debug!("adding transitive dep: {:?} -> {:?}", last.package.package, dep);
    last.transitive_deps.push(dep.clone());
  • edit in src/extract.rs at line 72
    [2.21419]
    [2.21419]
    last.deps.push(final_path.clone());
  • edit in src/extract.rs at line 75
    [2.21463][2.21463:21653]()
    }
    use std::collections::hash_map::Entry;
    if let Entry::Vacant(e) = seen.entry(elt.package.package) {
    e.insert((Vec::new(), Vec::new()));
  • replacement in src/extract.rs at line 76
    [2.21674][2.21674:21705]()
    unreachable!()
    [2.21674]
    [2.21705]
    seen.insert(
    elt.package.package,
    (Vec::new(), Vec::new(), Arc::new(PathBuf::new())),
    );
  • replacement in src/extract.rs at line 82
    [2.21720][2.21720:23414]()
    let client = client.clone();
    let url = client.url(&elt.package);
    let sha256 = elt.package.sha256.unwrap().to_string();
    let (tx, rx) = tokio::sync::oneshot::channel();
    let files = files.clone();
    let tasks = tasks.clone();
    tokio::spawn(async move {
    let permit = tasks.acquire_owned().await.unwrap();
    info!("downloading {:?}", url);
    let (mut task, downloaded) = client.download_url(&url, &sha256).await.unwrap();
    info!("finished downloading {:?}", url);
    if downloaded {
    tokio::task::spawn_blocking(move || {
    extract_task(&mut task, &mut *files.lock().unwrap()).unwrap();
    info!("finished extracting {:?}", url);
    tx.send(task).unwrap_or(());
    info!("sent {:?}", url);
    })
    .await
    .unwrap();
    } else {
    task.path.set_extension("");
    let mut files = files.lock().unwrap();
    for f in find_files(task.path.clone()) {
    if let Ok(p) = f.strip_prefix(&task.path) {
    use std::collections::hash_map::Entry;
    if let Entry::Vacant(e) = files.entry(p.to_path_buf()) {
    debug!("cached {:?} -> {:?}", p, f);
    e.insert(task.path.clone());
    }
    }
    }
    tx.send(task).unwrap_or(());
    [2.21720]
    [2.23414]
    elt.spawn_extract(client.clone())
    .await;
    elt.push_deps(index, &mut stack).await;
    }
    }
    Ok(Packages {
    result,
    paths: paths.into_iter().collect(),
    })
    }
    async fn add_subst(downloaded: PathBuf, target: &Path, files: Files) -> Result<(), std::io::Error> {
    let mut files = files.lock().unwrap();
    for (f, m) in find_files(downloaded.clone())? {
    if !m.is_dir() {
    if let Ok(p) = f.strip_prefix(&downloaded) {
    use std::collections::hash_map::Entry;
    if let Entry::Vacant(e) = files.entry(p.to_path_buf()) {
    debug!("add_subst {:?} -> {:?}", p, target);
    e.insert(target.to_path_buf());
  • replacement in src/extract.rs at line 103
    [2.23432][2.23432:23601]()
    std::mem::drop(permit);
    Ok::<_, Error>(())
    });
    elt.rx = Some(rx);
    push_deps(index, elt, &mut stack);
    [2.23432]
    [2.23601]
    }
  • replacement in src/extract.rs at line 106
    [2.23617][2.23617:23632]()
    Ok(result)
    [2.23617]
    [2.23632]
    Ok(())
  • replacement in src/extract.rs at line 109
    [2.23635][2.23635:23724]()
    async fn hash_reader(
    mut reader: impl tokio::io::AsyncReadExt + std::marker::Unpin,
    [2.23635]
    [2.23724]
    pub async fn hash_reader(
    mut reader: impl tokio::io::AsyncReadExt + Unpin,
  • replacement in src/extract.rs at line 128
    [2.24190][2.24190:24306]()
    fn extract_task(
    download: &mut Downloaded,
    files: &mut HashMap<PathBuf, PathBuf>,
    ) -> Result<(), Error> {
    [2.24190]
    [2.24306]
    async fn extract_task(client: &Client, download: &mut Downloaded) -> Result<(), Error> {
  • edit in src/extract.rs at line 130
    [2.24347]
    [2.24347]
    let path = download.path.with_extension("");
    let lock = client.lock_store_path(&path).await;
  • replacement in src/extract.rs at line 133
    [2.24426][2.24426:24949]()
    let d = deb::Deb::read(&mut f)?;
    download.path.set_extension("");
    std::fs::create_dir_all(&download.path)?;
    d.decompress(&mut f, &download.path, |path| {
    use std::collections::hash_map::Entry;
    let path = if let Ok(p) = path.strip_prefix(".") {
    p.to_path_buf()
    } else {
    path.to_path_buf()
    };
    debug!("extract {:?} -> {:?}", path, download.path);
    if let Entry::Vacant(e) = files.entry(path) {
    e.insert(download.path.clone());
    [2.24426]
    [2.24949]
    let d = match deb::Deb::read(&mut f) {
    Ok(d) => d,
    Err(e) => {
    std::fs::remove_file(&download.path).unwrap_or(());
    return Err(e.into());
    }
    };
    std::fs::create_dir_all(&path).unwrap_or(());
    match d.decompress(&mut f, &path) {
    Ok(()) => {
    download.path = path;
    drop(lock);
    Ok(())
  • replacement in src/extract.rs at line 147
    [2.24959][2.24959:24979]()
    })?;
    Ok(())
    [2.24959]
    [2.24979]
    Err(e) => {
    std::fs::remove_dir_all(&path).unwrap_or(());
    std::fs::remove_file(&download.path).unwrap_or(());
    drop(lock);
    Err(e.into())
    }
    }
  • replacement in src/extract.rs at line 156
    [2.24982][2.24982:25114]()
    const MAX_PARALLEL_DOWNLOADS: usize = 20;
    fn multi_lookup<'a>(index: &'a [deb::Index], package: &str) -> Option<deb::Stanza<'a>> {
    [2.24982]
    [2.25114]
    async fn multi_lookup<'a>(index: &'a [deb::Index], package: &str) -> Option<deb::Stanza<'a>> {
    for ind in index {
    if let Some(i) = ind.lookup_async(package).await {
    return Some(i);
    }
    }
  • replacement in src/extract.rs at line 163
    [2.25137][2.25137:25185]()
    if let Some(i) = ind.lookup(&package) {
    [2.25137]
    [2.25185]
    if let Some(i) = ind.lookup_virtual_async(package).await.into_iter().next() {
  • replacement in src/extract.rs at line 170
    [2.25241][2.25241:25314]()
    struct FindFiles {
    stack: Vec<(PathBuf, std::fs::Metadata, bool)>,
    }
    [2.25241]
    [2.25314]
    type Files = Arc<Mutex<HashMap<PathBuf, PathBuf>>>;
  • replacement in src/extract.rs at line 172
    [2.25315][2.25315:25475]()
    fn find_files(path: PathBuf) -> FindFiles {
    let meta = std::fs::metadata(&path).unwrap();
    FindFiles {
    stack: vec![(path, meta, false)],
    }
    }
    [2.25315]
    [2.25475]
    impl<'a> StackElt<'a> {
    /// Some libexec files (e.g. gcc) are searched relative to their
    /// original package /bin path. Propagating the /usr/libexec
    /// folder avoids overrides and ad-hoc code in the client.
    async fn link_extra(&mut self, final_path: &Path, extra: &[&str]) -> Result<(), Error> {
    debug!("linking extra path in {:?}: {:?}", final_path, extra);
    for path in extra {
    let final_extra = final_path.join(path);
  • replacement in src/extract.rs at line 181
    [2.25476][2.25476:25963]()
    impl Iterator for FindFiles {
    type Item = PathBuf;
    fn next(&mut self) -> Option<Self::Item> {
    while let Some((p, meta, visited)) = self.stack.pop() {
    if visited {
    continue;
    }
    if let Ok(dir) = std::fs::read_dir(&p) {
    self.stack.push((p, meta, true));
    for e in dir {
    if let Ok(e) = e {
    self.stack.push((e.path(), e.metadata().unwrap(), false));
    [2.25476]
    [2.25963]
    for d in self.deps.iter() {
    let d_extra = d.join(path);
    let Ok(meta) = tokio::fs::metadata(&d_extra).await else {
    continue;
    };
    let mut stack = vec![(d_extra.clone(), meta)];
    while let Some((elt, meta)) = stack.pop() {
    if let Ok(mut dir) = tokio::fs::read_dir(&elt).await {
    let p = elt.strip_prefix(&d_extra).unwrap();
    tokio::fs::create_dir_all(&final_extra.join(&p)).await?;
    while let Some(e) = dir.next_entry().await? {
    stack.push((e.path(), e.metadata().await?));
    }
    } else if meta.is_file() {
    let p = elt.strip_prefix(&d_extra).unwrap();
    debug!("libexec hard link {:?} to {:?}", elt, final_extra.join(&p));
    tokio::fs::hard_link(&elt, &final_extra.join(&p))
    .await
    .unwrap_or(());
  • edit in src/extract.rs at line 202
    [2.26003][2.26003:26192]()
    } else if meta.is_file() || meta.is_symlink() {
    return Some(p);
    } else {
    error!("could not read {:?}", p);
    continue;
  • replacement in src/extract.rs at line 204
    [2.26216][2.26216:26229]()
    None
    [2.26216]
    [2.26229]
    Ok(())
  • edit in src/extract.rs at line 206
    [2.26235][2.26235:26290]()
    }
    type Files = Arc<Mutex<HashMap<PathBuf, PathBuf>>>;
  • edit in src/extract.rs at line 207
    [2.26291][2.26291:26315]()
    impl<'a> StackElt<'a> {
  • edit in src/extract.rs at line 229
    [2.27145]
    [2.27145]
    /// Let `path` be the path to the raw extracted deb package, and
    /// `dest` be the final destination of this package in the store
    /// (when taking the package's context into account).
    ///
    /// Then, `find_libs` finds all paths in `path` that match the
    /// patterns in self.ld_path (`self.ld_path` paths are of the form
    /// `/usr/lib/x86_64-linux`), and add their equivalent in `dest` to
    /// `self.transitive_deps`.
    ///
    /// This makes sure that packages depending on `self` will be able
    /// to find the correct paths to these libs.
  • replacement in src/extract.rs at line 246
    [2.27478][2.27478:27574]()
    if self.deps_h.insert(path.clone()) {
    self.deps.push(path);
    [2.27478]
    [2.27574]
    if self.transitive_deps_h.insert(path.clone()) {
    self.transitive_deps.push(path);
  • replacement in src/extract.rs at line 253
    [2.27623][2.27623:27719]()
    fn patch_elf(&mut self, f: &Path, dest_path: &Path, files: &Files) -> Result<bool, Error> {
    [2.27623]
    [2.27719]
    async fn patch_elf(
    &mut self,
    f: &Path,
    dest_path: &Path,
    files: &Files,
    ) -> Result<bool, Error> {
  • replacement in src/extract.rs at line 266
    [2.27900][2.27900:27980]()
    let Ok(mut elf) = Elf::open(&file) else {
    return Ok(false);
    [2.27900]
    [2.27980]
    let mut elf = match Elf::open(&file) {
    Ok(elf) => elf,
    Err(e) => {
    info!("error opening {:?}: {:?}", file, e);
    return Ok(false);
    }
  • replacement in src/extract.rs at line 278
    [2.28163][2.28163:28222]()
    let has_needed = parsed.needed().next().is_some();
    [2.28163]
    [2.28222]
    let needed: Vec<_> = parsed
    .needed()
    .map(|x| x.unwrap().to_str().unwrap().to_string())
    .collect();
  • replacement in src/extract.rs at line 318
    [2.29778][2.29778:29812]()
    info!("{:?}", subst);
    [2.29778]
    [2.29812]
    info!("set interpreter {:?}", subst);
  • replacement in src/extract.rs at line 320
    [2.29872][2.29872:29904]()
    } else if !has_needed {
    [2.29872]
    [2.29904]
    } else if needed.is_empty() {
  • replacement in src/extract.rs at line 327
    [2.30057][2.30057:30140]()
    for dep in self.deps.iter().rev() {
    debug!("dep = {:?}", dep);
    [2.30057]
    [2.30140]
    for dep in self.transitive_deps.iter().rev() {
    debug!("dep of {:?}: {:?}", f, dep);
  • edit in src/extract.rs at line 330
    [2.30177]
    [2.30177]
    continue;
    }
    let mut dep = dep.to_path_buf();
    let mut is_needed = false;
    for n in needed.iter() {
    dep.push(&n);
    is_needed |= tokio::fs::metadata(&dep).await.is_ok();
    dep.pop();
    }
    if !is_needed {
  • replacement in src/extract.rs at line 349
    [2.30423][2.30423:30447]()
    if has_needed {
    [2.30423]
    [2.30447]
    if path.len() > 1 {
  • replacement in src/extract.rs at line 363
    [2.30794][2.30794:30858]()
    result: &mut Vec<PathBuf>,
    ) -> Result<(), Error> {
    [2.30794]
    [2.30858]
    result: &mut Vec<Arc<PathBuf>>,
    ) -> Result<Arc<PathBuf>, Error> {
  • edit in src/extract.rs at line 367
    [2.30986]
    [2.30986]
    self.deps.sort();
  • edit in src/extract.rs at line 371
    [2.31099]
    [2.31099]
    debug!(
    "finalize {:?} {:#?}",
    self.package.package, self.transitive_deps
    );
  • edit in src/extract.rs at line 378
    [2.31238]
    [2.31238]
    let lock = client.lock_store_path(&dest).await;
  • replacement in src/extract.rs at line 384
    [2.31342][2.31342:31390]()
    let initial_deps_len = self.deps.len();
    [2.31342]
    [2.31390]
    let initial_deps_len = self.transitive_deps.len();
  • replacement in src/extract.rs at line 393
    [2.31790][2.31790:33466]()
    info!("patching");
    // Patch the ELFs, now that we have all the deps.
    let mut hashing = Vec::new();
    for f in find_files(downloaded.path.clone()) {
    let dest_path = dest.join(&f.strip_prefix(&downloaded.path).unwrap());
    std::fs::create_dir_all(dest_path.parent().unwrap()).unwrap();
    let patched = self.patch_elf(&f, &dest_path, &files).unwrap_or(false);
    if !patched {
    // Hard link
    debug!("hard link {:?} {:?}", f, dest_path);
    std::fs::hard_link(&f, &dest_path).unwrap_or(());
    }
    hashing.push(tokio::spawn(async move {
    // hash + write
    info!("hashing {:?}", f);
    if let Ok(link) = std::fs::read_link(&dest_path) {
    Ok(Some((dest_path, link.to_str().unwrap().to_string())))
    } else if let Ok(file) = tokio::fs::File::open(&dest_path).await {
    let mut hasher = blake3::Hasher::new();
    hash_reader(file, &mut hasher).await?;
    let hex = data_encoding::HEXLOWER.encode(hasher.finalize().as_bytes());
    Ok::<_, Error>(Some((f, hex)))
    } else {
    Ok(None)
    }
    }));
    }
    info!("patched all");
    let mut hashes = Vec::with_capacity(hashing.len());
    for h in hashing.into_iter() {
    if let Some(h) = h.await.unwrap().unwrap() {
    hashes.push(h)
    [2.31790]
    [2.33466]
    info!("create final path for {dest:?}");
    match self
    .create_final_path(client, &files, &downloaded.path, &dest, &base_package_name)
    .await
    {
    Ok(x) => x,
    Err(e) => {
    tokio::fs::remove_dir_all(&dest).await.unwrap_or(());
    return Err(e);
  • edit in src/extract.rs at line 404
    [2.33498][2.33498:34260]()
    hashes.sort_by(|a, b| a.0.cmp(&b.0));
    info!("hashed all");
    let mut output_hasher = blake3::Hasher::new();
    let blakesums = dest.join("blake3sums");
    let mut file = std::fs::File::create(&blakesums).unwrap();
    for (path, hash) in hashes {
    debug!("strip {:?} {:?}", path, downloaded.path);
    let path = path.to_str().unwrap();
    writeln!(file, "{} {}", hash, path)?;
    writeln!(output_hasher, "{} {}", hash, path)?;
    }
    client.store_path.join(&format!(
    "{}-{}",
    data_encoding::HEXLOWER.encode(output_hasher.finalize().as_bytes()),
    base_package_name,
    ))
  • replacement in src/extract.rs at line 405
    [2.34277][2.34277:34318]()
    info!("found, no patching");
    [2.34277]
    [2.34318]
    info!("found, no patching: {:?}", dest);
  • replacement in src/extract.rs at line 408
    [2.34430][2.34430:34503]()
    let file = tokio::fs::File::open(&blakesums).await.unwrap();
    [2.34430]
    [2.34503]
    let file = match tokio::fs::File::open(&blakesums).await {
    Ok(file) => file,
    Err(e) => {
    error!("Error {:?} {:?}: {:?}", blakesums, downloaded.path, e);
    return Err(e.into());
    }
    };
  • edit in src/extract.rs at line 416
    [2.34561]
    [2.34561]
    let r = tokio::io::BufReader::new(
    tokio::fs::File::open(&dest.join("paths")).await.unwrap(),
    );
    let mut l = r.lines();
    while let Some(l) = l.next_line().await? {
    self.files.insert(Arc::new(l));
    }
  • edit in src/extract.rs at line 431
    [2.34777]
    [2.34777]
    let final_path = Arc::new(final_path);
  • edit in src/extract.rs at line 433
    [2.34778]
    [2.34778]
    add_subst(downloaded.path.clone(), &dest, files.clone()).await?;
  • replacement in src/extract.rs at line 437
    [2.34885][2.34885:34941]()
    for dep in &mut self.deps[initial_deps_len..] {
    [2.34885]
    [2.34941]
    for dep in &mut self.transitive_deps[initial_deps_len..] {
  • replacement in src/extract.rs at line 442
    [2.35124][2.35124:35187]()
    match std::os::unix::fs::symlink(&dest, &final_path) {
    [2.35124]
    [2.35187]
    match std::os::unix::fs::symlink(&dest, &*final_path) {
  • replacement in src/extract.rs at line 445
    [2.35286][2.35286:35346]()
    let got = std::fs::read_link(&final_path)?;
    [2.35286]
    [2.35346]
    let got = std::fs::read_link(&*final_path)?;
  • replacement in src/extract.rs at line 455
    [2.35617][2.35617:35650]()
    result.push(final_path);
    [2.35617]
    [2.35650]
    result.push(final_path.clone());
  • replacement in src/extract.rs at line 464
    [2.35968][2.35968:36112]()
    for dep in self.deps.iter() {
    if last.deps_h.insert(dep.clone()) {
    last.deps.push(dep.clone());
    [2.35968]
    [2.36112]
    for dep in self.transitive_deps.iter() {
    if last.transitive_deps_h.insert(dep.clone()) {
    debug!("adding transitive dep: {:?} -> {:?}", last.package.package, dep);
    last.transitive_deps.push(dep.clone());
  • edit in src/extract.rs at line 469
    [2.36130]
    [2.36130]
    }
    for f in self.files.iter() {
    last.files.insert(f.clone());
  • edit in src/extract.rs at line 473
    [2.36144]
    [2.36144]
    last.deps.push(final_path.clone());
  • edit in src/extract.rs at line 475
    [2.36154]
    [2.36154]
    drop(lock);
  • replacement in src/extract.rs at line 477
    [2.36209][2.36209:36224]()
    Ok(())
    [2.36209]
    [2.36224]
    Ok(final_path)
  • edit in src/extract.rs at line 479
    [2.36230][2.36230:36232]()
    }
  • replacement in src/extract.rs at line 480
    [2.36233][2.36233:36881]()
    fn push_deps<'a>(index: &'a [deb::Index], elt: StackElt<'a>, stack: &mut Vec<StackElt<'a>>) {
    let depends = elt.package.depends.clone();
    stack.push(elt);
    for dep in depends.iter() {
    match dep {
    deb::Dep::Simple(s) => {
    debug!("dep {:?}", s);
    let dep = multi_lookup(index, &s.name).unwrap();
    stack.push(StackElt {
    package: dep,
    rx: None,
    deps: Vec::new(),
    deps_h: BTreeSet::new(),
    ld_path: Vec::new(),
    ld_path_h: BTreeSet::new(),
    })
    [2.36233]
    [2.36881]
    async fn spawn_extract(
    &mut self,
    client: Client,
    ) {
    let client = client.clone();
    let url = client.url(self.package.file_name.as_deref());
    let sha256 = self.package.sha256.unwrap().to_string();
    let (tx, rx) = tokio::sync::oneshot::channel();
    tokio::spawn(async move {
    let permit = client.download_sem.clone().acquire_owned().await.unwrap();
    info!("downloading {:?}", url);
    let (mut task, _) = match client.download_url(&url, &sha256).await {
    Ok(x) => x,
    Err(e) => {
    tx.send(Err(e)).unwrap_or(());
    return Ok(());
    }
    };
    let is_extracted = std::fs::metadata(&task.path.with_extension("")).is_ok();
    info!("finished downloading {:?}", url);
    if !is_extracted {
    // Sets extension
    if let Err(e) = extract_task(&client, &mut task).await {
    info!("finished extracting {:?} {:?}", url, e);
    tx.send(Err(e)).unwrap_or(());
    } else {
    info!("finished extracting {:?}, Ok", url);
    tx.send(Ok(task)).unwrap_or(());
    }
    info!("sent {:?}", url);
    } else {
    task.path.set_extension("");
    tx.send(Ok(task)).unwrap_or(());
  • replacement in src/extract.rs at line 514
    [2.36895][2.36895:37481]()
    deb::Dep::Alternatives { alt } => {
    debug!("alt {:?}", alt);
    for dep in alt {
    if let Some(dep_) = multi_lookup(index, &dep.name) {
    stack.push(StackElt {
    package: dep_,
    rx: None,
    deps: Vec::new(),
    deps_h: BTreeSet::new(),
    ld_path: Vec::new(),
    ld_path_h: BTreeSet::new(),
    });
    return;
    [2.36895]
    [2.37481]
    drop(permit);
    Ok::<_, Error>(())
    });
    self.rx = Some(rx);
    }
    async fn push_deps(self, index: &'a [deb::Index], stack: &mut Vec<StackElt<'a>>) {
    let depends = self.package.depends.clone();
    stack.push(self);
    for dep in depends.iter() {
    match dep {
    deb::Dep::Simple(s) => {
    debug!("dep {:?}", s);
    let Some(dep) = multi_lookup(index, &s.name).await else {
    panic!("could not find {:?}", s.name)
    };
    stack.push(StackElt {
    package: dep,
    rx: None,
    deps: Vec::new(),
    transitive_deps: Vec::new(),
    transitive_deps_h: BTreeSet::new(),
    ld_path: Vec::new(),
    ld_path_h: BTreeSet::new(),
    files: BTreeSet::new(),
    })
    }
    deb::Dep::Alternatives { alt } => {
    debug!("alt {:?}", alt);
    for dep in alt {
    if let Some(dep_) = multi_lookup(index, &dep.name).await {
    stack.push(StackElt {
    package: dep_,
    rx: None,
    deps: Vec::new(),
    transitive_deps: Vec::new(),
    transitive_deps_h: BTreeSet::new(),
    ld_path: Vec::new(),
    ld_path_h: BTreeSet::new(),
    files: BTreeSet::new(),
    });
    return;
    }
  • edit in src/extract.rs at line 558
    [2.37503]
    [2.37503]
    panic!("Not found: {:?}", alt);
  • replacement in src/extract.rs at line 560
    [2.37521][2.37521:37569]()
    panic!("Not found: {:?}", alt);
    [2.37521]
    [2.37569]
    }
    }
    }
    async fn create_final_path(
    &mut self,
    client: &Client,
    files: &Files,
    downloaded_path: &Path,
    dest: &Path,
    base_package_name: &str,
    ) -> Result<PathBuf, Error> {
    // Link the required libexec before hashing.
    let tmp = async_tempfile::TempDir::new_in(dest.parent().unwrap()).await?;
    self.link_extra(&tmp.dir_path(), &["usr/libexec", "usr/lib/gcc"])
    .await?;
    // Patch the ELFs, now that we have all the deps.
    let mut hashing = Vec::new();
    let mut hashes = Vec::with_capacity(hashing.len());
    debug!("create_final_path {:?}", downloaded_path);
    for (f, meta) in find_files(downloaded_path.to_path_buf())? {
    debug!("f = {:?}", f);
    let rel = f.strip_prefix(&downloaded_path).unwrap();
    let dest_path = tmp.dir_path().join(&rel);
    if meta.is_dir() {
    tokio::fs::create_dir_all(dest_path).await.unwrap_or(());
    continue;
    }
    if meta.is_symlink() {
    // Relink the file to the subst.
    let target = tokio::fs::read_link(&f).await?;
    debug!("relink {:?} -> {:?} {:?}", f, target, target.is_relative());
    let subst = {
    let l = files.lock().unwrap();
    let target_rel = rel.parent().unwrap().join(&target);
    if let Some(subst) = l.get(&target_rel) {
    Some(subst.join(&target_rel))
    } else {
    None
    }
    };
    if let Some(subst) = subst {
    debug!("relink to {:?}", dest_path);
    tokio::fs::symlink(&subst, &dest_path).await?;
    hashes.push((f, subst.to_str().unwrap().to_string()))
    } else {
    // Leave the symlink untouched
    // tokio::fs::create_dir_all(dest_path.parent().unwrap()).await.unwrap_or(());
    debug!("symlink {:?} {:?} {:?}", target, dest_path, f);
    tokio::fs::symlink(&target, &dest_path).await?;
    hashes.push((f, target.to_str().unwrap().to_string()));
    }
    continue;
    }
    if !self
    .patch_elf(&f, &dest_path, &files)
    .await
    .unwrap_or(false)
    {
    // Hard link
    debug!("hard link {:?} {:?}", f, dest_path);
    tokio::fs::hard_link(&f, &dest_path).await.unwrap_or(());
    }
    hashing.push(tokio::spawn(async move {
    // hash + write
    info!("hashing {:?}", f);
    if let Ok(file) = tokio::fs::File::open(&dest_path).await {
    let mut hasher = blake3::Hasher::new();
    hash_reader(file, &mut hasher).await.unwrap();
    let hex = data_encoding::HEXLOWER.encode(hasher.finalize().as_bytes());
    Ok::<_, Error>(Some((f, hex)))
    } else {
    Ok(None)
    }
    }));
    }
    info!("patched all");
    for h in hashing.into_iter() {
    if let Some(h) = h.await.unwrap().unwrap() {
    hashes.push(h)
    }
    }
    hashes.sort_by(|a, b| a.0.cmp(&b.0));
    info!("hashed all");
    let mut output_hasher = blake3::Hasher::new();
    {
    let blakesums = tmp.dir_path().join("blake3sums");
    let mut file = tokio::fs::File::create(&blakesums).await?;
    for (path, hash) in hashes {
    let path = path
    .strip_prefix(&downloaded_path)
    .unwrap()
    .to_str()
    .unwrap();
    file.write_all(hash.as_bytes()).await?;
    file.write_all(b" ").await?;
    file.write_all(path.as_bytes()).await?;
    file.write_all(b"\n").await?;
    writeln!(output_hasher, "{} {}", hash, path)?;
  • edit in src/extract.rs at line 668
    [2.37593]
    [2.37593]
    let final_path = client.store_path.join(&format!(
    "{}-{}",
    data_encoding::HEXLOWER.encode(output_hasher.finalize().as_bytes()),
    base_package_name,
    ));
    {
    let transitive = tmp.dir_path().join("paths");
    let mut file = tokio::fs::File::create(&transitive).await?;
    for path in self.files.iter() {
    file.write_all(path.as_bytes()).await?;
    file.write_all(b"\n").await?;
    }
    }
    for (f, _) in find_dirs(downloaded_path.to_path_buf())? {
    let rel = f.strip_prefix(&downloaded_path).unwrap();
    self.files
    .insert(Arc::new(final_path.join(rel).to_str().unwrap().to_string()));
    }
    info!("rename {:?} to {:?}", tmp.dir_path(), dest);
    tokio::fs::rename(tmp.dir_path(), dest).await?;
    std::mem::forget(tmp);
    Ok(final_path)
  • replacement in src/extract.rs at line 700
    [2.37671][2.37671:37731]()
    rx: Option<tokio::sync::oneshot::Receiver<Downloaded>>,
    [2.37671]
    [2.37731]
    rx: Option<tokio::sync::oneshot::Receiver<Result<Downloaded, Error>>>,
  • replacement in src/extract.rs at line 702
    [2.37760][2.37760:37796]()
    deps_h: BTreeSet<Arc<PathBuf>>,
    [2.37760]
    [2.37796]
    transitive_deps: Vec<Arc<PathBuf>>,
    transitive_deps_h: BTreeSet<Arc<PathBuf>>,
  • edit in src/extract.rs at line 706
    [2.37867]
    [2.37867]
    files: BTreeSet<Arc<String>>,
  • replacement in src/deb.rs at line 15
    [2.38227][2.38227:38268]()
    global: Header,
    control: Header,
    [2.38227]
    [2.38268]
    _global: Header,
    _control: Header,
  • edit in src/deb.rs at line 47
    [2.39018]
    [2.39018]
    }
    /*
    impl<'a> From<Dep<'a>> for OwnedDep {
    fn from(e: Dep<'a>) -> Self {
    match e {
    Dep::Simple(s) => OwnedDep::Simple(s.to_owned()),
    Dep::Alternatives { alt } => OwnedDep::Alternatives {
    alt: alt.iter().map(From::from).collect()
    },
    }
    }
    }
    /// A dependency, which is either a "simple" dependency, or a list of
    /// alternatives.
    #[derive(Debug, Clone)]
    pub enum OwnedDep {
    Simple(OwnedSimpleDep),
    Alternatives { alt: Vec<OwnedSimpleDep> },
    }
    /// A single dependency.
    #[derive(Debug, Clone)]
    pub struct OwnedSimpleDep {
    pub name: String,
    pub any: bool,
    pub constraints: Vec<(String, OwnedVersion)>,
  • edit in src/deb.rs at line 76
    [2.39021]
    [2.39021]
    impl<'a> From<SimpleDep<'a>> for OwnedSimpleDep {
    fn from(e: SimpleDep<'a>) -> Self {
    OwnedSimpleDep {
    name: e.name.to_string(),
    any: e.any,
    constraints: e.constraints.iter().map(|(a, b)| (a.into(), b.into())).collect(),
    }
    }
    }
    */
  • replacement in src/deb.rs at line 113
    [2.40118][2.40118:40207]()
    return nom::IResult::Err(nom::Err::Error(nom::error::make_error(
    [2.40118]
    [2.40207]
    return IResult::Err(nom::Err::Error(nom::error::make_error(
  • edit in src/deb.rs at line 126
    [2.40437]
    [2.40437]
    #[test]
    fn test_grub_common() {
    use tracing_subscriber::prelude::*;
    use tracing_subscriber::util::SubscriberInitExt;
    tracing_subscriber::registry()
    .with(
    tracing_subscriber::EnvFilter::try_from_default_env()
    .unwrap_or_else(|_| String::new().into()),
    )
    .with(tracing_subscriber::fmt::layer())
    .try_init();
  • edit in src/deb.rs at line 139
    [2.40438]
    [2.40438]
    let (rest, p) = parse_control("Package: grub-common\nArchitecture: amd64\nVersion: 2.12-1ubuntu7\nBuilt-Using: lzo2 (= 2.10-2build3)\nMulti-Arch: foreign\nPriority: optional\nSection: admin\nSource: grub2\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>\nOriginal-Maintainer: GRUB Maintainers <pkg-grub-devel@alioth-lists.debian.net>\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 12476\nDepends: libc6 (>= 2.38), libdevmapper1.02.1 (>= 2:1.02.36), libefiboot1t64 (>=38), libefivar1t64 (>= 38), libfreetype6 (>= 2.2.1), libfuse3-3 (>= 3.2.3), liblzma5 (>= 5.1.1alpha+20120614), debconf (>= 0.5) | debconf-2.0, gettext-base, lsb-base (>= 3.0-6), python3, python3-apt\nRecommends: os-prober (>= 1.33)\nSuggests: multiboot-doc, grub-emu, mtools, xorriso (>= 0.5.6.pl00), desktop-base (>= 4.0.6), console-setup\nConflicts: init-select\nBreaks: apport (<< 2.1.1), friendly-recovery (<< 0.2.13), lupin-support (<< 0.55), mdadm (<< 2.6.7-2)\nReplaces: grub-coreboot (<< 2.00-4), grub-efi (<< 1.99-1), grub-efi-amd64 (<< 2.00-4), grub-efi-ia32 (<< 2.00-4), grub-efi-ia64 (<< 2.00-4), grub-ieee1275 (<< 2.00-4), grub-linuxbios (<< 1.96+20080831-1), grub-pc (<< 2.00-4), grub-yeeloong (<< 2.00-4), init-select\nFilename: pool/main/g/grub2/grub-common_2.12-1ubuntu7_amd64.deb\nSize: 2119862\nMD5sum: 86e63081ae4ee34d6a4f408ac6dbf0e4\nSHA1: b98f9f88e5480423e041fd647b438ba2f6e6f5ea\nSHA256: d8110b97b6fb6da40449c74b9cb527f02965dffaae8344399a711617f5a69249\nSHA512: 85c63b8d3e6a870df48533ab525df13abe9ec4861ff4b5577def94f65a2ebf6ac44a54a6cd0eca1c825e1c7aa2bd14e4d4ed53f83d381963ac1dc51daa16482a\nHomepage: https://www.gnu.org/software/grub/\nDescription: GRand Unified Bootloader (common files)\nTask: ubuntu-desktop-minimal, ubuntu-desktop, ubuntu-desktop-raspi, kubuntu-desktop, xubuntu-minimal, xubuntu-desktop, lubuntu-desktop, ubuntustudio-desktop-core, ubuntustudio-desktop, ubuntukylin-desktop,ubuntukylin-desktop-minimal, ubuntu-mate-core, ubuntu-mate-desktop, ubuntu-budgie-desktop-minimal, ubuntu-budgie-desktop, ubuntu-budgie-desktop-raspi, ubuntu-unity-desktop, edubuntu-desktop-gnome-minimal, edubuntu-desktop-gnome-raspi, ubuntucinnamon-desktop-minimal, ubuntucinnamon-desktop-raspi\nDescription-md5: 9c75036dc0a0792fedbc58df208ed227").unwrap();
    assert!(p.depends.iter().any(|x| match x {
    Dep::Simple(s) => s.name == "liblzma5",
    _ => false
    }));
    assert!(rest.is_empty())
    }
  • edit in src/deb.rs at line 161
    [2.40789]
    [2.40789]
    #[derive(Debug, Clone)]
    pub struct OwnedVersion {
    pub epoch: u32,
    pub upstream_version: OwnedUpstream,
    pub debian: Option<String>,
    }
  • replacement in src/deb.rs at line 208
    [2.41977][2.41977:42054]()
    return nom::IResult::Err(nom::Err::Error(nom::error::make_error(
    [2.41977]
    [2.42054]
    return IResult::Err(nom::Err::Error(nom::error::make_error(
  • replacement in src/deb.rs at line 258
    [2.43214][2.43214:43283]()
    return nom::IResult::Err(nom::Err::Error(nom::error::make_error(
    [2.43214]
    [2.43283]
    return IResult::Err(nom::Err::Error(nom::error::make_error(
  • replacement in src/deb.rs at line 279
    [2.43923][2.43923:43946]()
    _ => {
    [2.43923]
    [2.43946]
    p => {
    if let Some(p) = p {
    d.push(p)
    }
  • edit in src/deb.rs at line 285
    [2.44064]
    [2.44064]
    } else {
    panic!("Alternatives {:?}", s)
  • edit in src/deb.rs at line 308
    [2.44523]
    [2.44523]
    #[derive(Debug, Clone)]
    pub struct OwnedUpstream(String);
  • replacement in src/deb.rs at line 329
    [2.45047][2.45047:45205]()
    regex::Regex::new(r"^[a-z0-9.~+-]+-([a-z0-9.~+]+)").unwrap();
    static ref RE: regex::Regex = regex::Regex::new(r"^[a-z0-9.~+]+").unwrap();
    [2.45047]
    [2.45205]
    regex::Regex::new(r"^[a-z0-9\.~+-]+-([a-z0-9\.~+]+)").unwrap();
    static ref RE: regex::Regex = regex::Regex::new(r"^[a-z0-9\.~+]+").unwrap();
  • edit in src/deb.rs at line 334
    [2.45264]
    [2.45264]
    debug!("parse_version RE_DASH {:?}", s);
  • edit in src/deb.rs at line 338
    [2.45362]
    [2.45362]
    debug!("parse_version RE {:?}", s);
  • replacement in src/deb.rs at line 342
    [2.45432][2.45432:45505]()
    return nom::IResult::Err(nom::Err::Error(nom::error::make_error(
    [2.45432]
    [2.45505]
    debug!("parse_version failed {:?}", s);
    return IResult::Err(nom::Err::Error(nom::error::make_error(
  • replacement in src/deb.rs at line 410
    [2.47010][2.47010:47081]()
    unsafe { std::str::from_utf8_unchecked(&self.file_id).trim() }
    [2.47010]
    [2.47081]
    std::str::from_utf8(&self.file_id).unwrap().trim()
  • replacement in src/deb.rs at line 430
    [2.47748][2.47748:47816]()
    assert_eq!(std::mem::size_of::<H>(), HEADER_SIZE as usize);
    [2.47748]
    [2.47816]
    assert_eq!(size_of::<H>(), HEADER_SIZE as usize);
  • edit in src/deb.rs at line 432
    [2.47859]
    [2.47859]
    let pb: *mut H = &mut b;
  • replacement in src/deb.rs at line 434
    [2.47917][2.47917:47996]()
    &mut b as *mut H as *mut u8,
    std::mem::size_of::<H>(),
    [2.47917]
    [2.47996]
    pb as *mut u8,
    size_of::<H>(),
  • edit in src/deb.rs at line 443
    [2.48092][2.48092:48123]()
    const SIGNATURE_SIZE: u64 = 8;
  • replacement in src/deb.rs at line 457
    [2.48439][2.48439:48536]()
    pub fn decompress<R: BufRead + Seek, P: AsRef<std::path::Path>, F: FnMut(&std::path::Path)>(
    [2.48439]
    [2.48536]
    pub fn decompress<R: BufRead + Seek, P: AsRef<std::path::Path>>(
  • edit in src/deb.rs at line 461
    [2.48586][2.48586:48604]()
    mut f: F,
  • replacement in src/deb.rs at line 470
    [2.49033][2.49033:49170]()
    for e in ar.entries()? {
    let mut e = e?;
    f(&e.path()?);
    e.unpack_in(&path)?;
    [2.49033]
    [2.49170]
    for e in ar.entries().unwrap() {
    let mut e = e.unwrap();
    debug!("zstd decompress {:?}", e.path());
    // f(&e.path()?);
    e.unpack_in(&path).unwrap();
    }
    } else if self.data.file_id().ends_with(".tar.xz") {
    let mut ar = tar::Archive::new(xz::read::XzDecoder::new(r.take(self.data.file_size)));
    for e in ar.entries().unwrap() {
    let mut e = e.unwrap();
    debug!("xz decompress {:?}", e.path());
    // f(&e.path()?);
    e.unpack_in(&path).unwrap();
    }
    } else if self.data.file_id().ends_with(".tar") {
    let mut ar = tar::Archive::new(r.take(self.data.file_size));
    for e in ar.entries().unwrap() {
    let mut e = e.unwrap();
    debug!("tar extract {:?}", e.path());
    // f(&e.path()?);
    e.unpack_in(&path).unwrap();
  • edit in src/deb.rs at line 507
    [2.49556]
    [2.49556]
    assert_eq!(global.file_size, 4);
  • replacement in src/deb.rs at line 510
    [2.49618][2.49618:49704]()
    r.skip_until(b'c')?; // 'control.…'
    r.seek(SeekFrom::Current(-1))?;
    [2.49618]
    [2.49704]
    // r.skip_until(b'c')?; // 'control.…'
    // r.seek(SeekFrom::Current(-1))?;
  • replacement in src/deb.rs at line 525
    [2.50230][2.50230:50259]()
    unimplemented!()
    [2.50230]
    [2.50259]
    let mut decompressor = xz::read::XzDecoder::new(&ctrl[..]);
    let mut result = Vec::new();
    decompressor.read_to_end(&mut result)?;
    result
  • replacement in src/deb.rs at line 557
    [2.51398][2.51398:51592]()
    r.seek(SeekFrom::Start(
    SIGNATURE_SIZE + 2 * HEADER_SIZE + control.file_size,
    ))?;
    r.skip_until(b'd')?; // 'data.…'
    r.seek(SeekFrom::Current(-1))?;
    [2.51398]
    [2.51592]
    if control.file_size % 2 == 1 {
    r.seek(SeekFrom::Current(1))?;
    }
  • edit in src/deb.rs at line 561
    [2.51630]
    [2.51630]
    debug!("data = {:?}", data);
  • replacement in src/deb.rs at line 565
    [2.51669][2.51669:51710]()
    global,
    control,
    [2.51669]
    [2.51710]
    _global: global,
    _control: control,
  • edit in src/deb/index.rs at line 2
    [2.51917][2.51917:51933]()
    use tracing::*;
  • edit in src/deb/index.rs at line 4
    [2.51990]
    [2.51990]
    use tracing::*;
  • replacement in src/deb/index.rs at line 11
    [2.52192][2.52192:52211]()
    pub struct Index {
    [2.52192]
    [2.52211]
    struct Index_ {
  • replacement in src/deb/index.rs at line 13
    [2.52234][2.52234:52302]()
    lru: Arc<Mutex<lru::LruCache<String, Option<(usize, usize)>>>>,
    [2.52234]
    [2.52302]
    lru: Mutex<lru::LruCache<String, Option<(usize, usize)>>>,
  • edit in src/deb/index.rs at line 16
    [2.52305]
    [2.52305]
    #[derive(Clone)]
    pub struct Index(Arc<Index_>);
  • replacement in src/deb/index.rs at line 23
    [2.52515][2.52515:52534]()
    Ok(Index {
    [2.52515]
    [2.52534]
    Ok(Index(Arc::new(Index_ {
  • replacement in src/deb/index.rs at line 25
    [2.52557][2.52557:52649]()
    lru: Arc::new(Mutex::new(lru::LruCache::new(NonZeroUsize::new(256).unwrap()))),
    [2.52557]
    [2.52649]
    lru: Mutex::new(lru::LruCache::new(NonZeroUsize::new(256).unwrap())),
    })))
    }
    pub async fn lookup_async<'a>(&'a self, package: &str) -> Option<Stanza<'a>> {
    let s = self.clone();
    let p = package.to_string();
    tokio::task::spawn_blocking(move || s.lookup_range(&p))
    .await
    .unwrap()
    .map(|(m1, m2)| {
    let s = &*self.0.map;
    let line = std::str::from_utf8(&s[m1..m2]).unwrap().trim();
    parse_control(line).unwrap().1
    })
    }
    pub async fn lookup_virtual_async<'a>(&'a self, package: &str) -> Vec<Stanza<'a>> {
    let s = self.clone();
    let r =
    regex::bytes::Regex::new(&format!(r#"\nProvides:[^\n]*[, ]{package}[,\s\n]"#)).unwrap();
    tokio::task::spawn_blocking(move || {
    r.find_iter(&*s.0.map)
    .map(|m| {
    let map = &*s.0.map;
    s.frame(0, map.len(), m.start())
    })
    .collect::<Vec<_>>()
  • edit in src/deb/index.rs at line 54
    [2.52660]
    [2.52660]
    .await
    .unwrap()
    .into_iter()
    .map(|(m1, m2)| {
    let line = std::str::from_utf8(&self.0.map[m1..m2]).unwrap().trim();
    parse_control(line).unwrap().1
    })
    .collect()
    }
    pub fn lookup<'a>(&'a self, package: &str) -> Option<Stanza<'a>> {
    self.lookup_range(package).map(|(m1, m2)| {
    let s = &*self.0.map;
    let line = std::str::from_utf8(&s[m1..m2]).unwrap().trim();
    parse_control(line).unwrap().1
    })
  • replacement in src/deb/index.rs at line 72
    [2.52667][2.52667:52727]()
    pub fn lookup(&self, package: &str) -> Option<Stanza> {
    [2.52667]
    [2.52727]
    fn frame(&self, a: usize, b: usize, mut m1: usize) -> (usize, usize) {
    let s = &*self.0.map;
    while m1 > a && (s[m1] != b'\n' || s[m1 + 1] != b'\n') {
    m1 -= 1;
    }
    let mut m2 = m1 + 2;
    while m2 + 1 < b && (s[m2] != b'\n' || s[m2 + 1] != b'\n') {
    m2 += 1;
    }
    if m2 < b {
    m2 += 1
    }
    (m1, m2)
    }
    fn lookup_range(&self, package: &str) -> Option<(usize, usize)> {
  • replacement in src/deb/index.rs at line 91
    [2.52770][2.52770:52798]()
    let s = &*self.map;
    [2.52770]
    [2.52798]
    let s = &*self.0.map;
  • replacement in src/deb/index.rs at line 93
    [2.52799][2.52799:52853]()
    match self.lru.lock().unwrap().get(package) {
    [2.52799]
    [2.52853]
    match self.0.lru.lock().unwrap().get(package) {
  • replacement in src/deb/index.rs at line 95
    [2.52891][2.52891:53061]()
    let line = std::str::from_utf8(&s[*m1..*m2]).unwrap().trim();
    let (_, st) = parse_control(line).unwrap();
    return Some(st)
    [2.52891]
    [2.53061]
    return Some((*m1, *m2));
  • replacement in src/deb/index.rs at line 97
    [2.53075][2.53075:53170]()
    Some(None) => {
    return None
    },
    None => {},
    [2.53075]
    [2.53170]
    Some(None) => return None,
    None => {}
  • edit in src/deb/index.rs at line 115
    [2.53549][2.53549:53904]()
    let mut m1 = (a + b) / 2;
    while m1 > a && (s[m1] != b'\n' || s[m1 + 1] != b'\n') {
    m1 -= 1;
    }
    let mut m2 = m1 + 2;
    while m2 + 1 < b && (s[m2] != b'\n' || s[m2 + 1] != b'\n') {
    m2 += 1;
    }
    if m2 < b {
    m2 += 1
    }
  • edit in src/deb/index.rs at line 116
    [2.53905]
    [2.53905]
    let (m1, m2) = self.frame(a, b, (a + b) / 2);
  • replacement in src/deb/index.rs at line 122
    [2.54112][2.54112:54185]()
    self.lru.lock().unwrap().put(package.to_string(), None);
    [2.54112]
    [2.54185]
    self.0.lru.lock().unwrap().put(package.to_string(), None);
  • replacement in src/deb/index.rs at line 130
    [2.54469][2.54469:54593]()
    self.lru.lock().unwrap().put(package.to_string(), Some((m1, m2)));
    return Some(st);
    [2.54469]
    [2.54593]
    self.0
    .lru
    .lock()
    .unwrap()
    .put(package.to_string(), Some((m1, m2)));
    return Some((m1, m2));
  • replacement in src/deb/index.rs at line 159
    [2.55329][2.55329:55394]()
    self.lru.lock().unwrap().put(package.to_string(), None);
    [2.55329]
    [2.55394]
    self.0.lru.lock().unwrap().put(package.to_string(), None);
  • file addition: container.rs (----------)
    [2.15]
    use crate::{Error, find_files::*, mount};
    use futures::{SinkExt, StreamExt};
    use std::collections::{HashMap, HashSet};
    use std::ffi::CString;
    use std::os::unix::fs::PermissionsExt;
    use std::path::{Path, PathBuf};
    use std::sync::Arc;
    use tracing::*;
    /// A request to build a package.
    #[derive(bincode::Encode, bincode::Decode, Debug)]
    pub struct BuildRequest {
    pub name: String,
    pub paths: Vec<String>,
    pub script: String,
    pub target: String,
    }
    /// The channel used to communicate with the container process.
    pub struct ContainerChannel {
    r: std::os::unix::net::UnixStream,
    w: std::os::unix::net::UnixStream,
    }
    /// A function that forwards messages from the receiver to the
    /// container process and back.
    pub async fn forward(
    mut receiver: tokio::sync::mpsc::UnboundedReceiver<(
    BuildRequest,
    tokio::sync::oneshot::Sender<Result<PathBuf, String>>,
    )>,
    c: ContainerChannel,
    ) -> Result<(), Error> {
    let r = tokio::net::UnixStream::from_std(c.r).unwrap();
    let w = tokio::net::UnixStream::from_std(c.w).unwrap();
    let encoder = tokio_util::codec::LengthDelimitedCodec::new();
    let mut writer = tokio_util::codec::FramedWrite::new(w, encoder);
    let decoder = tokio_util::codec::LengthDelimitedCodec::new();
    let mut reader = tokio_util::codec::FramedRead::new(r, decoder);
    let mut pending = HashMap::new();
    let mut id = 1u64;
    loop {
    tokio::select! {
    x = receiver.recv() => {
    if let Some((msg, resp)) = x {
    pending.insert(id, resp);
    let mut bytes = bytes::BytesMut::new();
    bytes.extend_from_slice(&bincode::encode_to_vec(&(id, msg), bincode::config::standard()).unwrap());
    debug!("sending to process {:?}", bytes.len());
    writer.send(bytes.into()).await?;
    id += 1;
    }
    }
    x = reader.next() => {
    debug!("received process response {:?}", x);
    if let Some(Ok(msg)) = x {
    let ((id, resp), _) = bincode::decode_from_slice::<(u64, ProcessResult), _>(&msg, bincode::config::standard()).unwrap();
    let chan = pending.remove(&id).unwrap();
    chan.send(resp).unwrap_or(());
    } else {
    panic!("received none");
    }
    }
    }
    }
    }
    type ProcessResult = Result<PathBuf, String>;
    /// Start the container process. This function forks: the child blocks
    /// indefinitely, while the parent returns. This needs to be a
    /// separate process in order to run as root and create namespaces.
    pub fn serve(user: &str, store_path: &Path) -> ContainerChannel {
    let (r0, w0) = std::os::unix::net::UnixStream::pair().unwrap();
    let (r1, w1) = std::os::unix::net::UnixStream::pair().unwrap();
    r0.set_nonblocking(true).unwrap();
    r1.set_nonblocking(true).unwrap();
    w0.set_nonblocking(true).unwrap();
    w1.set_nonblocking(true).unwrap();
    let pid = unsafe { libc::fork() };
    if pid == 0 {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async move {
    let r1 = tokio::net::UnixStream::from_std(r1).unwrap();
    let w0 = tokio::net::UnixStream::from_std(w0).unwrap();
    async_serve(user, store_path, r1, w0).await
    });
    unreachable!()
    } else {
    ContainerChannel { r: r0, w: w1 }
    }
    }
    /// Inner server callable from a Tokio runtime, used since Tokio
    /// runtimes don't survive forks.
    async fn async_serve(
    user: &str,
    store: &Path,
    r: tokio::net::UnixStream,
    w: tokio::net::UnixStream,
    ) {
    let encoder = tokio_util::codec::LengthDelimitedCodec::new();
    let writer = Arc::new(tokio::sync::Mutex::new(
    tokio_util::codec::FramedWrite::new(w, encoder),
    ));
    let decoder = tokio_util::codec::LengthDelimitedCodec::new();
    let mut reader = tokio_util::codec::FramedRead::new(r, decoder);
    debug!("drv process waiting");
    while let Some(received) = reader.next().await {
    if let Ok(received) = received {
    debug!("drv_process received {:?}", received.len());
    let ((id, rec_msg), _) = bincode::decode_from_slice::<(u64, BuildRequest), _>(
    &received,
    bincode::config::standard(),
    )
    .unwrap();
    let writer = writer.clone();
    let store = store.to_path_buf();
    let user = user.to_string();
    tokio::spawn(async move {
    let result: ProcessResult =
    run_in_container(&user, &store, rec_msg).map_err(|e| format!("{:?}", e));
    debug!("result {:?}", result);
    let v = bincode::encode_to_vec(&(id, result), bincode::config::standard()).unwrap();
    let mut bytes = bytes::BytesMut::new();
    debug!("drv_process replying {:?}", v.len());
    bytes.extend_from_slice(&v);
    writer.lock().await.send(bytes.into()).await.unwrap()
    });
    }
    }
    info!("drv_process exited");
    }
    fn run_in_container(user: &str, store: &Path, r: BuildRequest) -> Result<PathBuf, Error> {
    let mut hasher = blake3::Hasher::new();
    hasher.update(r.name.as_bytes());
    hasher.update(b"\n");
    hasher.update(r.target.as_bytes());
    hasher.update(b"\n");
    debug!("run in container, path = {:#?}", r.paths);
    for p in r.paths.iter() {
    hasher.update(p.as_bytes());
    hasher.update(b"\n");
    }
    hasher.update(r.script.as_bytes());
    hasher.update(b"\n");
    let name = data_encoding::HEXLOWER.encode(hasher.finalize().as_bytes());
    // Guest dest.
    let dest = store.join(&name);
    if std::fs::metadata(&dest).is_ok() {
    let mut output_hasher = blake3::Hasher::new();
    let blakesums = dest.join("blake3sums");
    let file = match std::fs::File::open(&blakesums) {
    Ok(file) => file,
    Err(e) => {
    error!("Error {:?} {:?}: {:?}", blakesums, dest, e);
    return Err(e.into());
    }
    };
    output_hasher.update_reader(file)?;
    return Ok(store.join(&format!(
    "{}-{}",
    data_encoding::HEXLOWER.encode(output_hasher.finalize().as_bytes()),
    r.name,
    )));
    }
    // Tmp host path where things will be mounted.
    let tmp_dir = store.join(format!("{}.drv", name));
    // Full path of the store in the host.
    let tmp_store = Path::new(&tmp_dir).join(store.strip_prefix("/").unwrap());
    std::fs::create_dir_all(&tmp_store)?;
    // Host dest.
    let tmp_dest = tmp_store.join(&name);
    let pid = unsafe {
    libc::syscall(
    libc::SYS_clone3,
    &libc::clone_args {
    flags: (libc::CLONE_NEWPID | libc::CLONE_NEWNET | libc::CLONE_NEWNS) as u64,
    pidfd: 0,
    parent_tid: 0,
    child_tid: 0,
    stack: 0,
    stack_size: 0,
    tls: 0,
    set_tid: 0,
    set_tid_size: 0,
    cgroup: 0,
    exit_signal: libc::SIGCHLD as u64,
    },
    size_of::<libc::clone_args>(),
    )
    };
    if pid == 0 {
    match std::panic::catch_unwind(|| {
    inner_process(
    user, &r, &tmp_dir, &dest, &store, &tmp_store, &name,
    )
    }) {
    Ok(Ok(())) => std::process::exit(0),
    Ok(Err(Error::BuildReturn { status })) => std::process::exit(status),
    _ => std::process::exit(1),
    }
    } else {
    debug!("waitpid");
    let mut status = 0;
    unsafe { libc::waitpid(pid as i32, &mut status, 0) };
    info!("return status {:?}", status);
    if status != 0 {
    debug!("returning error");
    return Err(Error::BuildReturn { status });
    }
    // Now that the paths have been unmounted, delete.
    if let Ok(dir) = std::fs::read_dir(&tmp_store) {
    for entry in dir {
    std::fs::remove_dir(&entry?.path()).unwrap_or(());
    }
    std::fs::remove_dir(&tmp_store).unwrap_or(());
    }
    }
    // And hash the output.
    debug!("tmp_dest {:?}", tmp_dest);
    let Ok(hashed) = hash_all(&tmp_dest) else {
    return Err(Error::NoDestDir);
    };
    let out = store.join(&format!(
    "{}-{}",
    data_encoding::HEXLOWER.encode(hashed.as_bytes()),
    r.name,
    ));
    std::fs::remove_dir_all(&dest).unwrap_or(());
    // Should be a symlink
    std::fs::rename(&tmp_dest, &dest).unwrap();
    match std::os::unix::fs::symlink(&dest, &out) {
    Ok(()) => (),
    Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
    let got = std::fs::read_link(&out).unwrap();
    if dest != got {
    Err::<(), _>(Error::WrongResultSymlink {
    expected: dest,
    got,
    })
    .unwrap();
    }
    }
    Err(e) => return Err(e.into()),
    }
    Ok(out)
    }
    fn patch_result_elf(root: &Path, f: &Path, target: &str) -> Result<bool, Error> {
    use elfedit::*;
    info!("patch_elf {:?}", f);
    let file = std::fs::OpenOptions::new()
    .read(true)
    .write(true)
    .open(&f)?;
    let mut elf = match Elf::open(&file) {
    Ok(elf) => elf,
    Err(e) => {
    info!("error opening {:?}: {:?}", file, e);
    return Ok(false);
    }
    };
    info!("patching {:?}", f);
    let Some(parsed) = elf.parse().unwrap() else {
    info!("No dynamic section");
    return Ok(false);
    };
    let needed: Vec<_> = parsed
    .needed()
    .map(|x| x.unwrap().to_str().unwrap().to_string())
    .collect();
    let interp = parsed.interpreter();
    if let Some(interp) = interp.unwrap() {
    let mut interp_ = interp.to_str().unwrap();
    let mut interp = Path::new(interp_).to_path_buf();
    debug!("existing interp: {interp:?}");
    if interp_.starts_with("/usr") || interp_.starts_with("/lib") {
    while interp_.starts_with("/usr") || interp_.starts_with("/lib") {
    if let Ok(target) = std::fs::read_link(&interp) {
    let target = if target.is_relative() {
    interp.parent().unwrap().join(target)
    } else {
    target
    };
    interp = target;
    interp_ = interp.to_str().unwrap();
    } else {
    break;
    }
    }
    debug!("target: {target:?}");
    let subst = CString::new(interp_).unwrap();
    info!("set interpreter {:?}", subst);
    elf.set_interpreter(subst.to_bytes_with_nul());
    } else {
    // TODO: not sure what else to do here, we
    // might want to set the interpreter to a
    // different value (equivalent to "recompiling
    // everything" on Nix).
    info!("Interpreter is {interp_}. Already patched?");
    return Ok(false);
    }
    } else if needed.is_empty() {
    return Ok(false);
    }
    let mut deps_h = HashSet::new();
    let mut path = String::new();
    for n in needed.iter() {
    for p in &["/usr/lib", "/usr/lib64", &format!("/usr/lib/{target}")] {
    let Ok(dep) = std::fs::read_link(Path::new(p).join(n)) else {
    continue;
    };
    if !deps_h.insert(dep.clone()) {
    continue;
    }
    debug!("patch_elf needed: {:?}", dep);
    debug!("root: {:?}", root);
    if !path.is_empty() {
    path.push(':')
    }
    path.push_str(dep.parent().unwrap().to_str().unwrap());
    }
    }
    path.push('\0');
    info!("Setting path {:?}", path);
    if path.len() > 1 {
    elf.set_runpath(&path.as_bytes());
    }
    Ok(elf.update(None).unwrap()) // map_err(From::from)
    }
    /// Can't be async since this needs to fork and Tokio doesn't work
    /// across forks.
    ///
    /// The fork is used to manage the mounts.
    fn inner_process(
    user: &str,
    r: &BuildRequest,
    tmp_dir: &Path,
    dest: &Path,
    store: &Path,
    tmp_store: &Path,
    name: &str,
    ) -> Result<(), Error> {
    let tmp_usr = tmp_dir.join("usr");
    std::fs::create_dir_all(&tmp_usr)?;
    mount::make_root_private().unwrap();
    std::mem::forget(mount::Mount::ramfs(&tmp_usr).unwrap());
    std::os::unix::fs::symlink("/usr/lib", &tmp_dir.join("lib")).unwrap_or(());
    std::os::unix::fs::symlink("/usr/lib64", &tmp_dir.join("lib64")).unwrap_or(());
    for host in r.paths.iter() {
    let guest = tmp_dir.join(host.strip_prefix("/").unwrap());
    debug!("mounting {:?} to {:?}", host, guest);
    std::fs::create_dir_all(&guest).unwrap();
    debug!("created {:?} to {:?}", host, guest);
    match mount::Mount::bind(host, guest.to_str().unwrap()) {
    Ok(m) => std::mem::forget(m),
    Err(e) => error!("{e:?}"),
    }
    debug!("mounted {:?}", guest);
    if let Ok(target) = std::fs::read_link(&host) {
    info!("mounting link {:?}", target);
    if target.is_absolute() {
    let guest = tmp_dir.join(target.strip_prefix("/").unwrap());
    std::fs::create_dir_all(&guest).unwrap();
    match mount::Mount::bind(&target, guest.to_str().unwrap()) {
    Ok(m) => std::mem::forget(m),
    Err(e) => error!("{e:?}"),
    }
    }
    }
    let guest_usr = guest.join("usr");
    if let Ok(find) = find_files(guest_usr.clone()) {
    std::fs::create_dir(&guest_usr).unwrap_or(());
    for (f, m) in find {
    // Stripped: guest path of the link.
    let stripped = f.strip_prefix(&guest).unwrap();
    // Target: host path of the link.
    let target = tmp_dir.join(stripped);
    debug!("mount link {:?} {:?} {:?}", f, target, m);
    if m.is_dir() {
    std::fs::create_dir(&target).unwrap_or(());
    } else if let Ok(out) = std::fs::read_link(&f) {
    if std::fs::remove_file(&target).is_err() {
    std::fs::remove_dir_all(&target).unwrap_or(());
    }
    std::os::unix::fs::symlink(&out, &target).unwrap();
    } else {
    if std::fs::remove_file(&target).is_err() {
    std::fs::remove_dir_all(&target).unwrap_or(());
    }
    std::os::unix::fs::symlink(Path::new(&host).join(&stripped), &target).unwrap();
    }
    }
    }
    }
    let builder_base = format!("{name}-builder.sh");
    let tmp_builder = tmp_store.join(&builder_base);
    let builder = store.join(&builder_base);
    std::fs::write(&tmp_builder, &r.script).unwrap();
    let mut perm = std::fs::metadata(&tmp_builder).unwrap().permissions();
    perm.set_mode(0o555);
    std::fs::set_permissions(&tmp_builder, perm).unwrap();
    let (uid, gid) = {
    let user_ffi = CString::new(user).unwrap();
    let pw = unsafe { libc::getpwnam(user_ffi.as_ptr()) };
    assert!(!pw.is_null());
    let pw = unsafe { &*pw };
    (pw.pw_uid, pw.pw_gid)
    };
    std::os::unix::fs::chown(tmp_dir, Some(uid), Some(gid)).unwrap();
    std::os::unix::fs::chown(tmp_store, Some(uid), Some(gid)).unwrap();
    let out_env = format!("DESTDIR={}", dest.to_str().unwrap()).to_string();
    let out_env = CString::new(out_env.as_str()).unwrap();
    let c = CString::new(builder.to_str().unwrap()).unwrap();
    let pid = unsafe { libc::fork() };
    if pid == 0 {
    info!("chrooting to {:?}", tmp_dir);
    privdrop::PrivDrop::default()
    .chroot(tmp_dir)
    .user(user)
    .apply()
    .unwrap();
    std::env::set_current_dir("/").unwrap();
    debug!("execve {:?}", c);
    unsafe {
    libc::execve(
    c.as_ptr(),
    [c.as_ptr(), std::ptr::null()].as_ptr(),
    [out_env.as_ptr(), std::ptr::null()].as_ptr(),
    );
    }
    panic!("execve failed: {:?}", std::io::Error::last_os_error())
    } else {
    let mut status = 0;
    unsafe { libc::waitpid(pid, &mut status, 0) };
    debug!("fork returned {status}");
    if status != 0 {
    debug!("returning error");
    return Err(Error::BuildReturn { status });
    }
    }
    info!("chrooting to {:?}", tmp_dir);
    unsafe {
    let c = CString::new(tmp_dir.to_str().unwrap()).unwrap();
    libc::chroot(c.as_ptr());
    }
    if let Ok(f) = find_files(dest.to_path_buf()) {
    for (f, _meta) in f {
    // Potentially patch the ELF.
    debug!("patching {f:?}");
    if let Err(e) = patch_result_elf(&tmp_dir, &f, &r.target) {
    error!("{:?}", e);
    }
    }
    }
    Ok(())
    }
    /// Create the `blake3sums` file of `p` and hash that file.
    fn hash_all(p: &Path) -> Result<blake3::Hash, Error> {
    let mut hashes = Vec::new();
    for (f, _meta) in find_files(p.to_path_buf())? {
    // hash + write
    info!("hashing {:?}", f);
    if let Ok(link) = std::fs::read_link(&f) {
    hashes.push((f, link.to_str().unwrap().to_string()))
    } else if f.is_file() {
    let file = std::fs::File::open(&f)?;
    let mut hasher = blake3::Hasher::new();
    hasher.update_reader(file).unwrap();
    let hex = data_encoding::HEXLOWER.encode(hasher.finalize().as_bytes());
    hashes.push((f, hex))
    }
    }
    hashes.sort_by(|a, b| a.0.cmp(&b.0));
    info!("hashed all");
    let mut output_hasher = blake3::Hasher::new();
    let blakesums = p.join("blake3sums");
    let mut file = std::fs::File::create(&blakesums).unwrap();
    use std::io::Write;
    for (path, hash) in hashes {
    let path = path.to_str().unwrap();
    writeln!(file, "{} {}", hash, path)?;
    writeln!(output_hasher, "{} {}", hash, path)?;
    }
    Ok(output_hasher.finalize())
    }
  • edit in elpe/lib/elpegrpc.proto at line 3
    [2.55524]
    [2.55524]
    message AddPathRequest {
    message File {
    string name = 1;
    int64 length = 2;
    int32 permissions = 3;
    }
    message FileContents {
    int64 start = 1;
    bytes content = 2;
    }
    message Directory {
    string name = 1;
    int32 permissions = 3;
    }
    oneof request {
    File file = 1;
    Directory directory = 2;
    FileContents contents = 3;
    }
    }
  • edit in elpe/lib/elpegrpc.proto at line 30
    [2.55594]
    [2.55594]
    repeated string paths = 3;
    string target = 4;
    }
    message PathPattern {
    string pattern = 1;
    repeated string matches = 2;
    }
    message DerivationResult {
    repeated string destdir = 1;
    repeated string paths = 2;
    repeated PathPattern path_patterns = 3;
  • replacement in elpe/lib/elpegrpc.proto at line 46
    [2.55623][2.55623:55680]()
    repeated string path = 1;
    optional string error = 2;
    [2.55623]
    [2.55680]
    oneof result {
    DerivationResult ok = 1;
    string error = 2;
    }
  • edit in elpe/lib/elpegrpc.proto at line 66
    [2.55903]
    [2.55903]
    repeated string path_patterns = 3;
  • edit in elpe/lib/elpegrpc.proto at line 71
    [2.55985]
    [2.55985]
    rpc AddPath (stream AddPathRequest) returns (DerivationReply);
  • replacement in elpe/lib/elpe.ml at line 6
    [2.56239][2.56239:56662]()
    let connection address port =
    (* Setup Http/2 connection *)
    let* addresses =
    Lwt_unix.getaddrinfo address (string_of_int port)
    [ Unix.(AI_FAMILY PF_INET) ]
    in
    let socket = Lwt_unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
    let* () = Lwt_unix.connect socket (List.hd addresses).Unix.ai_addr in
    let error_handler e =
    raise (Error e)
    in
    H2_lwt_unix.Client.create_connection ~error_handler socket
    [2.56239]
    [2.56662]
    type store_path = { path : string }
  • edit in elpe/lib/elpe.ml at line 41
    [2.58003]
    [2.58003]
    ~do_request:
    (H2_lwt_unix.Client.request connection ~error_handler:(fun _ ->
    failwith "Error"))
    ~handler:
    (Client.Rpc.unary enc ~f:(fun decoder ->
    let+ decoder = decoder in
    match decoder with
    | Some decoder -> (
    Reader.create decoder |> decode |> function
    | Ok v -> v
    | Error e ->
    failwith
    (Printf.sprintf "Could not decode request: %s"
    (Result.show_error e)))
    | None -> Elpe.Derivation.Response.make ()))
    ()
    let derivation connection ~name ~builder ~paths ~target =
    let req = Elpegrpc.Elpe.DerivationRequest.make ~name ~builder ~paths ~target () in
    let open Ocaml_protoc_plugin in
    let open Elpegrpc.Elpe in
    let encode, decode = Service.make_client_functions Elpe.derivation in
    let enc = encode req |> Writer.contents in
    Client.call ~service:"elpe.Elpe" ~rpc:"Derivation"
  • edit in elpe/lib/elpe.ml at line 82
    [2.58601]
    [2.58601]
    let rec walk_dir_rec encode f buf path path_name =
    let open Ocaml_protoc_plugin in
    let* dir = Lwt_unix.opendir path in
    Lwt.finalize
    (fun () ->
    let open Elpegrpc.Elpe in
    let rec walk () =
    Lwt.catch
    (fun () ->
    let* entry = Lwt_unix.readdir dir in
    if entry = ".." || entry = "." then walk ()
    else
    let path = Filename.concat path entry in
    let path_name = Filename.concat path_name entry in
    let* stat = Lwt_unix.lstat path in
    let* () =
    match stat.st_kind with
    | Unix.S_DIR ->
    let req =
    AddPathRequest.make
    ~request:
    (`Directory
    (AddPathRequest.Directory.make ~name:path_name
    ~permissions:0o644 ()))
    ()
    in
    let enc = encode req |> Writer.contents in
    f (Some enc);
    walk_dir_rec encode f buf path path_name
    | Unix.S_REG ->
    let* file = Lwt_unix.openfile path [ O_RDONLY ] 0 in
    let ff =
    AddPathRequest.File.make ~name:path_name
    ~length:stat.st_size ~permissions:0o644 ()
    in
    let req = AddPathRequest.make ~request:(`File ff) () in
    let enc = encode req |> Writer.contents in
    let () = f (Some enc) in
    let rec read_all n =
    let* r = Lwt_unix.read file buf 0 4096 in
    if r != 0 then
    let req =
    AddPathRequest.make
    ~request:
    (`Contents
    (AddPathRequest.FileContents.make ~start:n
    ~content:(Bytes.sub buf 0 r) ()))
    ()
    in
    let enc = encode req |> Writer.contents in
    let () = f (Some enc) in
    read_all (n + r)
    else Lwt.return ()
    in
    read_all 0
    | _ -> Lwt.return ()
    in
    walk ())
    (function End_of_file -> Lwt.return () | e -> Lwt.fail e)
    in
    walk ())
    (fun () -> Lwt_unix.closedir dir)
    let add_path connection path0 =
    let open Ocaml_protoc_plugin in
    let open Elpegrpc.Elpe in
    let encode, decode = Service.make_client_functions Elpe.addPath in
    Client.call ~service:"elpe.Elpe" ~rpc:"AddPath"
    ~do_request:
    (H2_lwt_unix.Client.request connection ~error_handler:(fun _ ->
    failwith "Error"))
    ~handler:
    (Client.Rpc.client_streaming ~f:(fun f response ->
    let buf = Bytes.create 4096 in
    let* _ = walk_dir_rec encode f buf path0 "" in
    f None;
    let+ decoder = response in
    match decoder with
    | Some decoder -> (
    Reader.create decoder |> decode |> function
    | Ok v -> v
    | Error e ->
    failwith
    (Printf.sprintf "Could not decode request: %s"
    (Result.show_error e)))
    | None -> Elpe.Derivation.Response.make ()))
    ()
    type build_result = { destdir : string list; paths : string list }
  • replacement in elpe/lib/elpe.ml at line 175
    [2.58628][2.58628:58644]()
    object (self)
    [2.58628]
    [2.58644]
    object
  • replacement in elpe/lib/elpe.ml at line 177
    [2.58677][2.58677:58780]()
    method setup : string list Lwt.t = Lwt.return []
    method build : string list Lwt.t = self#setup
    [2.58677]
    [2.58780]
    (* Returns the script to setup the build process, also used in shells. *)
    method setup : string Lwt.t = Lwt.return ""
    (* List of paths resulting from building the package. *)
    method build : build_result Lwt.t = failwith "Not implemented"
  • replacement in elpe/lib/elpe.ml at line 203
    [2.59234][2.59234:59267]()
    Lwt.return res.path)
    [2.59234]
    [2.59267]
    match res with
    | `Ok r -> Lwt.return r.destdir
    | `Error e -> failwith e
    | _ -> assert false)
  • edit in elpe/lib/elpe.ml at line 210
    [2.59348][2.59348:59349]()
  • replacement in elpe/lib/elpe.ml at line 212
    [2.59439][2.59439:59465]()
    Lwt.return res.path
    [2.59439]
    [2.59465]
    match res with
    | `Ok r -> Lwt.return { destdir = r.destdir; paths = r.paths }
    | `Error e -> failwith e
    | _ -> assert false
  • edit in elpe/lib/elpe.ml at line 223
    [2.59678]
    [2.59678]
    method target : string = "x86_64-linux-gnu"
    val extra_paths : string list ref = ref []
    method derivation (drv : derivation) =
    let* path = drv#build in
    extra_paths := path.destdir @ !extra_paths;
    Lwt.return path
    method local_src p =
    let path_drv =
    object
    inherit derivation
    method name = Filename.basename p
  • edit in elpe/lib/elpe.ml at line 237
    [2.59679]
    [2.59679]
    method! build =
    let c =
    match !backend_conn with
    | None -> failwith "no conn"
    | Some c -> c
    in
    let* res = add_path c p in
    let res, _ = Result.get_ok res in
    match res with
    | `Ok r -> Lwt.return { destdir = r.destdir; paths = r.paths }
    | `Error e -> failwith e
    | _ -> assert false
    end
    in
    let* built = path_drv#build in
    extra_paths := built.destdir @ !extra_paths;
    Lwt.return (List.hd built.destdir)
    method src : string Lwt.t = failwith ("No src defined for " ^ self#name)
  • replacement in elpe/lib/elpe.ml at line 258
    [2.59699][2.59699:59856]()
    let* bash = (ubuntu "bash-static")#build in
    let bash = List.hd bash in
    let* b = self#build_inputs in
    let* b =
    Lwt_list.map_p
    [2.59699]
    [2.59856]
    let* bash = self#derivation (ubuntu "bash-static") in
    let bash = List.hd bash.destdir in
    let* build_inputs = self#build_inputs in
    let* () =
    Lwt_list.iter_p
  • replacement in elpe/lib/elpe.ml at line 265
    [2.59908][2.59908:59983]()
    Lwt.return (List.map (fun p -> p ^ "/usr/bin") p))
    b
    [2.59908]
    [2.59983]
    extra_paths := p.destdir @ !extra_paths;
    Lwt.return ())
    build_inputs
  • replacement in elpe/lib/elpe.ml at line 269
    [2.59992][2.59992:60214]()
    let path =
    List.fold_left
    (List.fold_left (fun acc x -> if acc == "" then x else acc ^ ":" ^ x))
    "" b
    in
    Lwt.return [ "#!" ^ bash ^ "\n" ^ "export PATH=" ^ path ^ "\n" ]
    end
    [2.59992]
    [2.60214]
    Lwt.return
    ("#!" ^ bash
    ^ "/usr/bin/bash-static\n\
    set -xe\n\
    export PATH=/usr/bin\n\
    export LIBRARY_PATH=/lib/" ^ self#target ^ ":/lib64/" ^ self#target
    ^ ":/usr/lib:/usr/lib64:/usr/lib/x86_64-linux-gnu\n\n")
  • replacement in elpe/lib/elpe.ml at line 277
    [2.60215][2.60215:60343]()
    let last_built_module : std_derivation option ref = ref None
    let build (spec : std_derivation) = last_built_module := Some spec
    [2.60215]
    [2.60343]
    method pre_unpack = Lwt.return ""
    method post_unpack = Lwt.return ""
  • replacement in elpe/lib/elpe.ml at line 280
    [2.60344][2.60344:60588]()
    let run_shell (spec : std_derivation) cmd =
    Lwt_main.run
    (let open Lwt.Syntax in
    let port = 50051 in
    let address = "127.0.0.1" in
    let* c = connection address port in
    backend_conn := Some c;
    let* b = spec#setup in
    [2.60344]
    [2.60588]
    method unpack_phase =
    let* src = self#src in
    let* all = Lwt.all [ self#pre_unpack; self#post_unpack ] in
    match all with
    | pre :: post :: _ -> Lwt.return (pre ^ "\ncp -R " ^ src ^ "/* .\n" ^ post)
    | _ -> assert false
  • replacement in elpe/lib/elpe.ml at line 287
    [2.60589][2.60589:61249]()
    let* bash = (ubuntu "bash-static")#build in
    let bash = List.hd bash in
    let f = Filename.temp_dir "elpe-" "" ^ "/setup" in
    Unix.mkfifo f 0o666;
    let pid =
    Unix.create_process
    (bash ^ "/usr/bin/bash-static")
    (match cmd with
    | None -> [| "bash"; "--init-file"; f; "-i" |]
    | Some cmd -> [| "bash"; "--init-file"; f; "-i"; "-c"; cmd |])
    Unix.stdin Unix.stdout Unix.stderr
    in
    let p = Unix.openfile f [ Unix.O_WRONLY ] 0o644 in
    List.fold_left
    (fun () b ->
    let _ = Unix.write_substring p b 0 (String.length b) in
    ())
    () b;
    Unix.close p;
    [2.60589]
    [2.61249]
    method pre_configure = Lwt.return ""
    method post_configure = Lwt.return ""
  • replacement in elpe/lib/elpe.ml at line 290
    [2.61250][2.61250:61359]()
    match Unix.waitpid [] pid with
    | _, Unix.WEXITED e -> Lwt.return e
    | _ -> failwith "Unknown")
    [2.61250]
    [2.61359]
    method configure_phase =
    let* all = Lwt.all [ self#pre_configure; self#post_configure ] in
    match all with
    | pre :: post :: _ ->
    Lwt.return
    (pre ^ "\nif [[ -e configure ]]; then ./configure; fi\n" ^ post)
    | _ -> assert false
  • replacement in elpe/lib/elpe.ml at line 298
    [2.61360][2.61360:61596]()
    let run_build (spec : derivation) =
    Lwt_main.run
    (let open Lwt.Syntax in
    let port = 50051 in
    let address = "127.0.0.1" in
    let* c = connection address port in
    backend_conn := Some c;
    let* b = spec#build in
    [2.61360]
    [2.61596]
    method pre_build = Lwt.return ""
    method post_build = Lwt.return ""
  • replacement in elpe/lib/elpe.ml at line 301
    [2.61597][2.61597:61678]()
    let* bash = (ubuntu "bash-static")#build in
    let bash = List.hd bash in
    [2.61597]
    [2.61678]
    method build_phase =
    let* all = Lwt.all [ self#pre_build; self#post_build ] in
    match all with
    | pre :: post :: _ -> Lwt.return (pre ^ "\nif [[ -e Makefile ]]; then make; fi\n" ^ post)
    | _ -> assert false
  • replacement in elpe/lib/elpe.ml at line 307
    [2.61679][2.61679:61761]()
    let f = Filename.temp_dir "elpe-" "" ^ "/setup" in
    Unix.mkfifo f 0o666;
    [2.61679]
    [2.61761]
    method pre_install = Lwt.return ""
    method post_install = Lwt.return ""
  • replacement in elpe/lib/elpe.ml at line 310
    [2.61762][2.61762:61943]()
    let pid =
    Unix.create_process
    (bash ^ "/usr/bin/bash-static")
    [| "bash"; "--init-file"; f; "-i" |]
    Unix.stdin Unix.stdout Unix.stderr
    in
    [2.61762]
    [2.61943]
    method install_phase =
    let* all = Lwt.all [ self#pre_install; self#post_install ] in
    match all with
    | pre :: post :: _ -> Lwt.return (pre ^ "\nif [[ -e Makefile ]]; then make install; fi\n" ^ post)
    | _ -> assert false
  • replacement in elpe/lib/elpe.ml at line 316
    [2.61944][2.61944:62150]()
    let p = Unix.openfile f [ Unix.O_WRONLY ] 0o644 in
    List.fold_left
    (fun () b ->
    let _ = Unix.write_substring p b 0 (String.length b) in
    ())
    () b;
    Unix.close p;
    [2.61944]
    [2.62150]
    method! build =
    let* phases =
    Lwt.all
    [
    self#setup;
    self#unpack_phase;
    self#configure_phase;
    self#build_phase;
    self#install_phase;
    ]
    in
    let builder = String.concat "\n" phases in
    let c =
    match !backend_conn with None -> failwith "no conn" | Some c -> c
    in
    let* res = derivation c ~name:self#name ~builder ~paths:!extra_paths ~target:self#target in
    let res, _ = Result.get_ok res in
    match res with
    | `Ok r -> Lwt.return { destdir = r.destdir; paths = r.paths }
    | `Error e -> failwith e
    | _ -> assert false
    end
  • replacement in elpe/lib/elpe.ml at line 339
    [2.62151][2.62151:62347]()
    let _ =
    match Unix.waitpid [] pid with
    | _, Unix.WEXITED e -> print_endline ("ended waitpid " ^ string_of_int e)
    | _ -> print_endline "other"
    in
    Lwt.return ())
    [2.62151]
    let last_built_module : std_derivation option ref = ref None
    let build (spec : std_derivation) = last_built_module := Some spec
  • replacement in elpe/lib/dune at line 5
    [2.62434][2.62434:62510]()
    (libraries grpc-lwt lwt lwt.unix h2 h2-lwt-unix pbrt ocaml-protoc-plugin))
    [2.62434]
    [2.62510]
    (libraries
    grpc-lwt
    lwt
    lwt.unix
    h2
    h2-lwt-unix
    pbrt
    ocaml-protoc-plugin
    tar
    tar.gz
    tar-unix))
  • replacement in elpe/lib/dune at line 26
    [2.62611][2.62611:62676]()
    "--ocaml_out=annot=[@@deriving show { with_path = false }]:."
    [2.62611]
    [2.62676]
    "--ocaml_out=annot=[@@deriving show { with_path = false }, eq]:."
  • replacement in elpe/dune-project at line 17
    [2.62990][2.62990:63016]()
    (depends ocaml elpe lwt)
    [2.62990]
    [2.63016]
    (depends ocaml elpe lwt core core_unix)
  • replacement in elpe/dune-project at line 22
    [2.63041][2.63041:63115]()
    (depends ocaml lwt grpc-lwt h2-lwt-unix pbrt ocaml-protoc-plugin protoc)
    [2.63041]
    [2.63115]
    (depends ocaml lwt grpc-lwt h2-lwt-unix pbrt ocaml-protoc-plugin protoc tar tar-unix)
  • replacement in elpe/bin/elpe_bin.ml at line 1
    [2.63278][2.63279:63289]()
    open Elpe
    [2.63278]
    [2.63289]
    let connection address port =
    let open Lwt.Syntax in
    (* Setup Http/2 connection *)
    let* addresses =
    Lwt_unix.getaddrinfo address (string_of_int port)
    [ Unix.(AI_FAMILY PF_INET) ]
    in
    let socket = Lwt_unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
    let* () = Lwt_unix.connect socket (List.hd addresses).Unix.ai_addr in
    let error_handler e = raise (Elpe.Error e) in
    H2_lwt_unix.Client.create_connection ~error_handler socket
    let run_shell (spec : Elpe.std_derivation) cmd =
    Lwt_main.run
    (let open Lwt.Syntax in
    let port = 50051 in
    let address = "127.0.0.1" in
    let* c = connection address port in
    Elpe.backend_conn := Some c;
    let* b = spec#setup in
  • replacement in elpe/bin/elpe_bin.ml at line 22
    [2.63290][2.63290:63487]()
    let usage_msg = "elpe [-verbose] [-c <command>] <file1> [<file2>]"
    let verbose = ref false
    let cmd = ref ""
    let input_files = ref []
    let anon_fun filename = input_files := filename :: !input_files
    [2.63290]
    [2.63487]
    let* bash = (Elpe.ubuntu "bash-static")#build in
    let bash = List.hd bash.destdir in
    let f = Filename.temp_dir "elpe-" "" ^ "/setup" in
    Unix.mkfifo f 0o666;
  • replacement in elpe/bin/elpe_bin.ml at line 28
    [2.63488][2.63488:63635]()
    let speclist =
    [
    ("-verbose", Arg.Set verbose, "Output debug information");
    ("-c", Arg.Set_string cmd, "Run the specified command");
    ]
    [2.63488]
    [2.63635]
    let pid =
    Unix.create_process
    (bash ^ "/usr/bin/bash-static")
    (match cmd with
    | None -> [| "bash"; "--init-file"; f; "-i" |]
    | Some cmd -> [| "bash"; "--init-file"; f; "-i"; "-c"; cmd |])
    Unix.stdin Unix.stdout Unix.stderr
    in
    let p = Unix.openfile f [ Unix.O_WRONLY ] 0o644 in
    let _ = Unix.write_substring p b 0 (String.length b) in
    Unix.close p;
    match Unix.waitpid [] pid with
    | _, Unix.WEXITED e -> Lwt.return e
    | _ -> failwith "Unknown")
    let run_build (spec : Elpe.derivation) =
    Lwt_main.run
    (let open Lwt.Syntax in
    let port = 50051 in
    let address = "127.0.0.1" in
    let* c = connection address port in
    Elpe.backend_conn := Some c;
    let* b = spec#build in
    print_endline (List.fold_left (fun _ x -> x) "" b.destdir);
    Lwt.return ())
  • replacement in elpe/bin/elpe_bin.ml at line 56
    [2.63636][2.63636:63661]()
    let compile_and_run () =
    [2.63636]
    [2.63661]
    let compile input_files =
  • replacement in elpe/bin/elpe_bin.ml at line 67
    [2.63864][2.63864:63886]()
    @ !input_files)
    [2.63864]
    [2.63886]
    @ input_files)
  • edit in elpe/bin/elpe_bin.ml at line 83
    [2.64216]
    [2.64216]
    Dynlink.set_allowed_units [ "Elpe" ];
  • replacement in elpe/bin/elpe_bin.ml at line 89
    [2.64350][2.64350:64512]()
    Dynlink.loadfile obj;
    match !last_built_module with
    | None -> 0
    | Some last ->
    run_shell last (if String.length !cmd == 0 then None else Some !cmd)
    [2.64350]
    [2.64512]
    Dynlink.loadfile obj
    let shell =
    Core.Command.basic ~summary:""
    (let%map_open.Command cmd =
    flag "-c" (optional string) ~doc:"Command to run in this shell"
    and files = anon (non_empty_sequence_as_list ("file" %: string)) in
    fun () ->
    Elpe.last_built_module := None;
    compile files;
    match !Elpe.last_built_module with
    | None -> Unix._exit 0
    | Some last -> Unix._exit (run_shell last cmd))
    let build =
    Core.Command.basic ~summary:""
    (let%map_open.Command files =
    anon (non_empty_sequence_as_list ("file" %: string))
    in
    fun () ->
    Elpe.last_built_module := None;
    compile files;
    let _ =
    match !Elpe.last_built_module with
    | None -> ()
    | Some last -> run_build (last :> Elpe.derivation)
    in
    ())
  • replacement in elpe/bin/elpe_bin.ml at line 118
    [2.64513][2.64513:64676]()
    let () =
    Arg.parse speclist anon_fun usage_msg;
    if !input_files != [] then
    let exit = compile_and_run () in
    Unix._exit exit
    else Printf.eprintf "\n"
    [2.64513]
    let command =
    Core.Command.group ~summary:"Elpe" [ ("shell", shell); ("build", build) ]
    let () = Command_unix.run command
    (* if !input_files != [] then *)
    (* let exit = compile_and_run () in *)
    (* Unix._exit exit *)
    (* else Printf.eprintf "\n" *)
  • replacement in elpe/bin/dune at line 6
    [2.64790][2.64790:64826]()
    (libraries elpe grpc-lwt dynlink))
    [2.64790]
    (libraries elpe grpc-lwt dynlink core core_unix.command_unix)
    (preprocess
    (pps ppx_jane)))
  • replacement in elfedit/src/lib.rs at line 585
    [5.486][5.486:530]()
    if id.class == Bits::B32 {
    [5.486]
    [5.530]
    if id.class == Bits::B32 {
  • replacement in elfedit/src/lib.rs at line 591
    [5.815][5.815:838]()
    })
    [5.815]
    [5.838]
    });
  • replacement in elfedit/src/lib.rs at line 598
    [5.1173][5.1173:1196]()
    })
    [5.1173]
    [5.1196]
    });
  • replacement in elfedit/src/lib.rs at line 621
    [5.2076][5.2076:2190]()
    let mut aux_ptr = current.add((&*(current as *const Verneed)).aux as usize) as *const u8;
    [5.2076]
    [5.2190]
    let mut aux_ptr =
    current.add((&*(current as *const Verneed)).aux as usize) as *const u8;
  • replacement in elfedit/src/lib.rs at line 628
    [5.2491][5.2491:2529]()
    break
    [5.2491]
    [5.2529]
    break;
  • replacement in elfedit/src/lib.rs at line 635
    [5.2837][5.2837:2871]()
    break
    [5.2837]
    [5.2871]
    break;
  • replacement in elfedit/src/lib.rs at line 775
    [3.407][3.407:461]()
    return Err(Error::NoSectionTable)
    [3.407]
    [3.461]
    return Err(Error::NoSectionTable);
  • replacement in elfedit/src/lib.rs at line 784
    [3.635][3.635:689]()
    return Err(Error::NoSectionTable)
    [3.635]
    [3.689]
    return Err(Error::NoSectionTable);
  • replacement in elfedit/src/lib.rs at line 966
    [3.1073][3.1073:1200]()
    Err(Error::NoSectionTable) => {
    return Ok(None)
    },
    Err(e) => return Err(e)
    [3.1073]
    [3.1200]
    Err(Error::NoSectionTable) => return Ok(None),
    Err(e) => return Err(e),
  • replacement in elfedit/src/lib.rs at line 1356
    [6.14561][4.828:933]()
    self.add_load_segment(id, new_offset, new.len(), rounded_last_vaddr as usize, 6, page_size);
    [6.14561]
    [6.26107]
    self.add_load_segment(
    id,
    new_offset,
    new.len(),
    rounded_last_vaddr as usize,
    6,
    page_size,
    );
  • edit in elfedit/src/lib.rs at line 1367
    [6.26118]
    [6.14666]
    /// Beware: the last segment of the file must be RW.
  • replacement in elfedit/src/lib.rs at line 1387
    [6.15227][6.3952:4025]()
    p.set_flags(id, flags | 6); // must be RW since it's at the end.
    [6.15227]
    [6.15259]
    p.set_flags(id, flags);
  • replacement in elfedit/src/lib.rs at line 1489
    [6.15974][6.991:1088]()
    pub fn update<P: AsRef<std::path::Path>>(mut self, into: Option<P>) -> Result<bool, Error> {
    [6.15974]
    [6.4901]
    pub fn update(mut self, into: Option<&std::path::Path>) -> Result<bool, Error> {
  • replacement in elfedit/src/lib.rs at line 1526
    [6.1871][6.1871:1959]()
    std::fs::set_permissions(into.as_ref(), self.f.metadata()?.permissions())?;
    [6.1871]
    [6.1959]
    std::fs::set_permissions(into, self.f.metadata()?.permissions())?;
  • edit in elfedit/src/lib.rs at line 2090
    [5.3772][5.3772:3773]()
  • edit in elfedit/src/lib.rs at line 2110
    [5.4098][5.4098:4099]()
  • edit in default.nix at line 130
    [2.68788]
    [2.68788]
    };
    # grpc-src = pkgs.fetchFromGitHub {
    # owner = "dialohq";
    # repo = "ocaml-grpc";
    # rev = "b71fba7067bad2cad62df9abd8b4e190e3c4fc94";
    # sha256 = "sha256-NtNU0ANMLRFFLoGtHMB0ynHbyh8YTfRF+PttJi9pYHU=";
    # };
    grpc-src = pkgs.fetchFromGitHub {
    owner = "dialohq";
    repo = "ocaml-grpc";
    rev = "0.2.0";
    sha256 = "sha256-YARAm3EVTbptAoKA0lysJRYeKPaCaeWU6X9Lzkn+30E=";
  • replacement in default.nix at line 149
    [2.68891][2.68891:69086]()
    src = pkgs.fetchurl {
    url = "https://github.com/dialohq/ocaml-grpc/archive/refs/tags/0.2.0.tar.gz";
    sha256 = "sha256-5myWWT3qziJ9m84aRXodGRZ5sGlUNBcY/6nkkzJ4in4=";
    };
    [2.68891]
    [2.69086]
    src = grpc-src;
  • edit in default.nix at line 161
    [2.69350]
    [2.69350]
    src = grpc-src;
    propagatedBuildInputs = with pkgs; [
    ocamlPackages.stringext
    ocamlPackages.lwt
    grpc
    ];
    };
    tar = ocamlPackages.buildDunePackage rec {
    pname = "tar";
    version = "3.3.0";
  • replacement in default.nix at line 173
    [2.69378][2.69378:69536]()
    url = "https://github.com/dialohq/ocaml-grpc/archive/refs/tags/0.2.0.tar.gz";
    sha256 = "sha256-5myWWT3qziJ9m84aRXodGRZ5sGlUNBcY/6nkkzJ4in4=";
    [2.69378]
    [2.69536]
    url = "https://github.com/mirage/ocaml-tar/archive/refs/tags/v3.3.0.tar.gz";
    sha256 = "sha256-xxwUxq8G9G8LH8EedLaPAWNvxtpRFiWZyEv8YPgad+g=";
  • replacement in default.nix at line 177
    [2.69588][2.69588:69620]()
    ocamlPackages.stringext
    [2.69588]
    [2.69620]
    ocamlPackages.camlp-streams
    ocamlPackages.cstruct
    ocamlPackages.decompress
    ];
    };
    tar-unix = ocamlPackages.buildDunePackage rec {
    pname = "tar-unix";
    version = "3.3.0";
    src = pkgs.fetchurl {
    url = "https://github.com/mirage/ocaml-tar/archive/refs/tags/v3.3.0.tar.gz";
    sha256 = "sha256-xxwUxq8G9G8LH8EedLaPAWNvxtpRFiWZyEv8YPgad+g=";
    };
    propagatedBuildInputs = with pkgs; [
  • replacement in default.nix at line 192
    [2.69646][2.69646:69659]()
    grpc
    [2.69646]
    [2.69659]
    tar
  • edit in default.nix at line 195
    [2.69675]
    [2.69675]
    tar-lwt-unix = tar-unix.overrideAttrs (_: {name = "tar-lwt-unix"; });
  • edit in default.nix at line 207
    [2.69968]
    [2.69968]
    tar
    tar-lwt-unix
  • edit in default.nix at line 231
    [2.70364]
    [2.70364]
    ocamlPackages.core
    ocamlPackages.core_unix
    tar
    tar-lwt-unix
  • replacement in default.nix at line 238
    [2.70431][2.70431:70440]()
    elpe
    [2.70431]
    [2.70440]
    # elpe
  • replacement in Cargo.toml at line 10
    [2.71122][2.71122:71144]()
    members = ["elfedit"]
    [2.71122]
    [2.71144]
    members = ["elfedit", "elfedit-cli"]
  • edit in Cargo.toml at line 39
    [2.71823]
    [2.71823]
    privdrop = "0.5.5"
    serde_derive = "1.0.219"
    serde = "1.0.219"
    tokio-bincode = "0.1.0"
    tokio-util = "0.7.15"
    bincode = "2.0.1"
    futures-util = "0.3.31"
    bytes = "1.10.1"
    async-tempfile = "0.7.0"
    xz = "0.1.0"
  • edit in Cargo.lock at line 34
    [2.72846]
    [2.72846]
    [[package]]
    name = "anstream"
    version = "0.6.18"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
    dependencies = [
    "anstyle",
    "anstyle-parse",
    "anstyle-query",
    "anstyle-wincon",
    "colorchoice",
    "is_terminal_polyfill",
    "utf8parse",
    ]
    [[package]]
    name = "anstyle"
    version = "1.0.10"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
  • edit in Cargo.lock at line 57
    [2.72859]
    [2.72859]
    name = "anstyle-parse"
    version = "0.2.6"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
    dependencies = [
    "utf8parse",
    ]
    [[package]]
    name = "anstyle-query"
    version = "1.1.2"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
    dependencies = [
    "windows-sys 0.59.0",
    ]
    [[package]]
    name = "anstyle-wincon"
    version = "3.0.7"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
    dependencies = [
    "anstyle",
    "once_cell",
    "windows-sys 0.59.0",
    ]
    [[package]]
  • edit in Cargo.lock at line 102
    [2.73421]
    [2.73421]
    [[package]]
    name = "async-tempfile"
    version = "0.7.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "c8a57b75c36e16f4d015e60e6a177552976a99b6947724403c551bcfa7cb1207"
    dependencies = [
    "tokio",
    ]
  • edit in Cargo.lock at line 208
    [2.75616]
    [2.75616]
    name = "bincode"
    version = "1.3.3"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
    dependencies = [
    "serde",
    ]
    [[package]]
    name = "bincode"
    version = "2.0.1"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
    dependencies = [
    "bincode_derive",
    "serde",
    "unty",
    ]
    [[package]]
    name = "bincode_derive"
    version = "2.0.1"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
    dependencies = [
    "virtue",
    ]
    [[package]]
  • edit in Cargo.lock at line 308
    [2.77540]
    [2.77540]
    [[package]]
    name = "cfg_aliases"
    version = "0.1.1"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
  • edit in Cargo.lock at line 316
    [2.77553]
    [2.77553]
    name = "clap"
    version = "4.5.38"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
    dependencies = [
    "clap_builder",
    ]
    [[package]]
    name = "clap_builder"
    version = "4.5.38"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
    dependencies = [
    "anstream",
    "anstyle",
    "clap_lex",
    "strsim",
    ]
    [[package]]
    name = "clap_lex"
    version = "0.7.4"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
    [[package]]
    name = "colorchoice"
    version = "1.0.3"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
    [[package]]
  • edit in Cargo.lock at line 432
    [2.79662]
    [2.79662]
    name = "elfedit-cli"
    version = "0.1.0"
    dependencies = [
    "clap",
    "elfedit",
    ]
    [[package]]
  • edit in Cargo.lock at line 443
    [2.79711]
    [2.79711]
    "async-tempfile",
  • edit in Cargo.lock at line 445
    [2.79722]
    [2.79722]
    "bincode 2.0.1",
  • edit in Cargo.lock at line 447
    [2.79733]
    [2.79733]
    "bytes 1.10.1",
  • edit in Cargo.lock at line 451
    [2.79782]
    [2.79782]
    "futures-util",
  • edit in Cargo.lock at line 458
    [2.79848]
    [2.79848]
    "privdrop",
  • edit in Cargo.lock at line 463
    [2.79896]
    [2.79896]
    "serde",
    "serde_derive",
  • edit in Cargo.lock at line 470
    [2.79950]
    [2.79950]
    "tokio-bincode",
    "tokio-util",
  • edit in Cargo.lock at line 476
    [2.80011]
    [2.80011]
    "xz",
  • edit in Cargo.lock at line 1031
    [2.93497]
    [2.93497]
    [[package]]
    name = "is_terminal_polyfill"
    version = "1.70.1"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
  • edit in Cargo.lock at line 1226
    [2.98537]
    [2.98537]
    name = "nix"
    version = "0.28.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
    dependencies = [
    "bitflags",
    "cfg-if",
    "cfg_aliases",
    "libc",
    ]
    [[package]]
  • edit in Cargo.lock at line 1383
    [2.102370]
    [2.102370]
    ]
    [[package]]
    name = "privdrop"
    version = "0.5.5"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "879d008129b086c1c067a3b7ce406bb9766c29f20387e7883724d8dddbce7064"
    dependencies = [
    "libc",
    "nix",
  • edit in Cargo.lock at line 1802
    [2.112139]
    [2.112139]
    [[package]]
    name = "strsim"
    version = "0.11.1"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
  • edit in Cargo.lock at line 1949
    [2.115474]
    [2.115474]
    name = "tokio-bincode"
    version = "0.1.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "0caad59482605518068065f14e2f91f43297d7240fa78dab9b4ba289ad0cf5b1"
    dependencies = [
    "bincode 1.3.3",
    "bytes 0.4.12",
    "serde",
    "tokio-codec",
    ]
    [[package]]
    name = "tokio-codec"
    version = "0.1.2"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b"
    dependencies = [
    "bytes 0.4.12",
    "futures 0.1.31",
    "tokio-io",
    ]
    [[package]]
  • replacement in Cargo.lock at line 2026
    [2.116744][2.116744:116763]()
    version = "0.7.13"
    [2.116744]
    [2.116763]
    version = "0.7.15"
  • replacement in Cargo.lock at line 2028
    [2.116828][2.116828:116906]()
    checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
    [2.116828]
    [2.116906]
    checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
  • edit in Cargo.lock at line 2195
    [2.120733]
    [2.120733]
    [[package]]
    name = "unty"
    version = "0.0.4"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
  • edit in Cargo.lock at line 2226
    [2.121389]
    [2.121389]
    name = "utf8parse"
    version = "0.2.2"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
    [[package]]
  • edit in Cargo.lock at line 2250
    [2.121968]
    [2.121968]
    name = "virtue"
    version = "0.0.18"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
    [[package]]
  • edit in Cargo.lock at line 2537
    [2.129714]
    [2.129714]
    ]
    [[package]]
    name = "xz"
    version = "0.1.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "3c887690ff2a2e233e8e49633461521f98ec57fbff9d59a884c9a4f04ec1da34"
    dependencies = [
    "xz2",