pijul nest
guest [sign in]

Fork channel

Create a new channel as a copy of main.

Rename channel

Rename main to:

Delete channel

Delete main? This cannot be undone.

extract.rs
use crate::{find_files::*, Client, Downloaded, Error};
use std::collections::{BTreeSet, HashMap, HashSet};
use std::ffi::CString;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
use tracing::*;

use crate::deb;

/// 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 element).
    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>>,
}

#[derive(Debug)]
struct Vertex<'a> {
    pkg: deb::Stanza<'a>,
    downloaded: Downloaded,
    index: Option<usize>,
    lowlink: usize,
    on_stack: bool,
    scc: usize,
    deps: Vec<usize>,
    context_path: Option<Arc<PathBuf>>,
    final_path: Option<Arc<PathBuf>>,
    deps_paths: Vec<Arc<PathBuf>>,
    last_transitive: (usize, usize),
    transitive_deps: Vec<Arc<(usize, PathBuf)>>,
    transitive_deps_h: BTreeSet<Arc<(usize, PathBuf)>>,
    ld_path: Vec<Arc<PathBuf>>,
    ld_path_h: BTreeSet<Arc<PathBuf>>,
    files: BTreeSet<Arc<String>>,
    v: usize,
}

/// Messages from the extractor.
#[derive(Debug)]
pub enum Msg {
    /// Downloading
    Downloading(String),
    /// Result
    Ok(Packages),
    /// Error
    Error(Error),
}

/// Download a Debian package and its dependency DAG from package
/// indices and the package's name.
pub async fn download_extract_deps<F: futures::Sink<Msg> + Clone + Send + Unpin + 'static>(
    index: &[deb::Index],
    client: &Client,
    package: &str,
    link_extra: &[(regex::Regex, regex::Regex)],
    tx: F,
) -> Result<Packages, Error>
where
    F::Error: std::fmt::Debug,
{
    let Some(pkg) = multi_lookup(index, &package).await else {
        return Err(Error::PackageNotFound {
            pkg: package.to_string(),
        });
    };

    let mut vertices = Vec::new();

    // We first run a DFS of the dependencies and store them in
    // `vertices` for later.
    let mut stack = vec![(pkg, None)];
    let mut seen: HashMap<_, (Vec<&str>, usize)> = HashMap::new();
    while let Some((pkg, rx)) = stack.pop() {
        info!("{:?}", pkg);
        if let Some(rx) = rx {
            let downloaded: Result<Result<Downloaded, Error>, _> = rx.await;
            let downloaded = downloaded.unwrap()?;
            seen.get_mut(pkg.package).unwrap().1 = vertices.len();
            debug!("initial deps: {:?}", seen.get(pkg.package).unwrap());
            let v_len = vertices.len();
            vertices.push(Vertex {
                downloaded,
                deps: Vec::new(),
                pkg,
                context_path: None,
                deps_paths: Vec::new(),
                files: BTreeSet::new(),
                final_path: None,
                ld_path: Vec::new(),
                ld_path_h: BTreeSet::new(),
                index: None,
                lowlink: 0,
                on_stack: false,
                scc: 0,
                last_transitive: (0, 0),
                transitive_deps: Vec::new(),
                transitive_deps_h: BTreeSet::new(),
                v: v_len,
            });
        } else if !seen.contains_key(pkg.package) {
            let rx = spawn_extract(client.clone(), &pkg, tx.clone()).await;
            let depends = pkg.depends.clone();
            let pkg_package = pkg.package;
            stack.push((pkg, Some(rx)));
            let deps = push_deps(&depends, index, &mut stack).await;
            seen.insert(pkg_package, (deps, 0));
        }
    }

    // Turn dependencies into their index in `vertices`.
    for v in vertices.iter_mut() {
        v.deps = seen
            .get(v.pkg.package)
            .unwrap()
            .0
            .iter()
            .map(|x| seen.get(x).unwrap().1)
            .collect()
    }

    let sccs = tarjan(&mut vertices);
    debug!("sccs: {:?}", sccs);

    let mut result = Vec::new();
    let files = Arc::new(Mutex::new(HashMap::new()));

    let mut ld_paths = HashSet::new();
    for scc in sccs.iter() {
        ld_paths.clear();

        // Two independent missions in one loop:
        // - Hash the context
        // - Collect the `ld_paths` of each package in the SCC.
        for v in scc.iter() {
            hash_context(&mut vertices, *v, &client.store_path, link_extra);

            // Find the extra ld paths to look for.
            vertices[*v].add_ld_paths().unwrap();
            ld_paths.extend(vertices[*v].ld_path.iter().cloned());
            for dep in vertices[*v].deps.iter() {
                for l in vertices[*dep].ld_path.iter() {
                    ld_paths.insert(l.clone());
                }
            }
        }

        // Once we have the ld paths of the entire SCC, add them to
        // each vertex of the SCC.
        for v in scc.iter() {
            for l in ld_paths.iter().cloned() {
                if vertices[*v].ld_path_h.insert(l.clone()) {
                    vertices[*v].ld_path.push(l.clone());
                }
            }
        }

        // Then, find the libraries in the `ld_paths` of each package
        // of the SCC.
        for v in scc.iter() {
            let a = vertices[*v].transitive_deps.len();
            vertices[*v].find_libs();
            vertices[*v].last_transitive = (a, vertices[*v].transitive_deps.len());
        }

        // And finalize each of the component (patch ELFs etc).
        for v in scc.iter() {
            span!(Level::DEBUG, "Finalize");
            let f = finalize(&mut vertices, &sccs, client, link_extra, &files, *v).await?;
            vertices[*v].final_path = Some(f.clone());
            result.push(f)
        }
    }
    let paths = vertices.last().unwrap().files.iter().cloned().collect();
    debug!("paths = {:?}", paths);
    debug!("result = {:?}", result);
    Ok(Packages { result, paths })
}

fn tarjan(vertices: &mut [Vertex]) -> Vec<Vec<usize>> {
    let mut sccs = Vec::new();
    let mut stack = Vec::new();
    let mut index = 0;

    #[derive(Debug)]
    struct C {
        vi: usize,
        d: usize,
    }
    let mut call_stack = vec![C {
        vi: vertices.len() - 1,
        d: 0,
    }];

    'outer: while let Some(mut c) = call_stack.pop() {
        debug!(
            "visiting {:?}: {:?} {:?} {:?}",
            c, vertices[c.vi].pkg.package, vertices[c.vi].index, vertices[c.vi].lowlink
        );
        if vertices[c.vi].index.is_none() {
            let ref mut v = vertices[c.vi];
            v.index = Some(index);
            v.lowlink = index;
            v.on_stack = true;
            index += 1;
            stack.push(c.vi);
        }
        if c.d > 0 {
            // Returning from the recursive call.
            let dep = vertices[c.vi].deps[c.d - 1];
            vertices[c.vi].lowlink = vertices[c.vi].lowlink.min(vertices[dep].lowlink);
        }
        while c.d < vertices[c.vi].deps.len() {
            let wi = vertices[c.vi].deps[c.d];
            debug!("dep of {:?}: {:?} ({:?})", c.vi, wi, vertices[c.vi].deps);
            if let Some(index) = vertices[wi].index {
                if vertices[wi].on_stack {
                    vertices[c.vi].lowlink = vertices[c.vi].lowlink.min(index)
                }
                c.d += 1;
            } else {
                c.d += 1;
                call_stack.push(c);
                call_stack.push(C { vi: wi, d: 0 });
                // Recursive call.
                continue 'outer;
            }
        }
        if Some(vertices[c.vi].lowlink) == vertices[c.vi].index {
            let mut scc = Vec::new();
            debug!("new scc");
            while let Some(p) = stack.pop() {
                debug!("-> {:?} {:?}", p, vertices[p].pkg.package);
                vertices[p].scc = sccs.len();
                vertices[p].on_stack = false;
                scc.push(p);
                if p == c.vi {
                    break;
                }
            }
            sccs.push(scc)
        }
    }
    sccs
}

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());
                }
            }
        }
    }
    Ok(())
}

async fn hash_reader(
    file: &Path,
    mut reader: impl tokio::io::AsyncReadExt + Unpin,
    hasher: &mut blake3::Hasher,
) -> std::io::Result<u64> {
    let mut buffer = [0; 65536];
    let mut total = 0;
    loop {
        match reader.read(&mut buffer).await {
            Ok(0) => return Ok(total),
            Ok(n) => {
                hasher.update(&buffer[..n]);
                total += n as u64;
            }
            Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
            Err(e) => {
                return Err(e);
            }
        }
    }
}

async fn extract_task(client: &Client, download: &mut Downloaded) -> Result<(), Error> {
    debug!("extracting {:?}", download);
    let path = download.path.with_extension("");
    let lock = client.lock_store_path(&path).await;
    let p = path.clone();
    let dp = download.path.clone();
    match tokio::task::spawn_blocking(move || {
        let mut f = std::io::BufReader::new(std::fs::File::open(&dp)?);
        let d = match deb::Deb::read(&mut f) {
            Ok(d) => d,
            Err(e) => {
                std::fs::remove_file(&dp).unwrap_or(());
                return Err(e.into());
            }
        };
        std::fs::create_dir_all(&p).unwrap_or(());

        d.decompress(&mut f, &p)
    })
    .await
    .unwrap()
    {
        Ok(()) => {
            drop(lock);
            download.path = path;
            Ok::<(), Error>(())
        }
        Err(e) => {
            std::fs::remove_dir_all(&path).unwrap_or(());
            std::fs::remove_file(&download.path).unwrap_or(());
            drop(lock);
            Err(e.into())
        }
    }
}

async fn spawn_extract<'a, S: futures::Sink<Msg> + Send + Unpin + 'static>(
    client: Client,
    stanza: &deb::Stanza<'a>,
    log_tx: S,
) -> tokio::sync::oneshot::Receiver<Result<Downloaded, Error>>
where
    S::Error: std::fmt::Debug,
{
    let url = client.url(stanza.file_name.as_deref());
    let sha256 = stanza.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, log_tx).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(());
        }
        drop(permit);
        Ok::<_, Error>(())
    });
    rx
}

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);
        }
    }
    for ind in index {
        if let Some(i) = ind.lookup_virtual_async(package).await.into_iter().next() {
            return Some(i);
        }
    }
    debug!("multi_lookup: package {:?} not found", package);
    None
}

type Files = Arc<Mutex<HashMap<PathBuf, PathBuf>>>;

fn hash_context(
    vertices: &mut [Vertex],
    v: usize,
    store_path: &Path,
    link_extra: &[(regex::Regex, regex::Regex)],
) {
    let mut context_hasher = blake3::Hasher::new();
    context_hasher.update(vertices[v].pkg.sha256.unwrap().as_bytes());
    for d in vertices[v].deps.iter() {
        let w = &vertices[*d];
        if w.scc == vertices[v].scc {
            // If in the same SCC, we can't know anything other than the SHA256.
            context_hasher.update(w.pkg.sha256.unwrap().as_bytes());
        } else {
            context_hasher.update(w.final_path.as_ref().unwrap().to_str().unwrap().as_bytes());
        }
    }
    for (a, b) in link_extra {
        context_hasher.update(b"\0");
        context_hasher.update(a.to_string().as_bytes());
        context_hasher.update(b"\0");
        context_hasher.update(b.to_string().as_bytes());
    }
    let s = data_encoding::HEXLOWER.encode(context_hasher.finalize().as_bytes());
    debug!("hash context {:?} {:?}", vertices[v].pkg.package, s);
    vertices[v].context_path = Some(Arc::new(store_path.join(s)));
}

impl<'a> Vertex<'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<S: AsRef<str> + std::fmt::Debug>(
        &mut self,
        final_path: &Path,
        extra: &[S],
    ) -> Result<(), Error> {
        debug!("linking extra path in {:?}: {:?}", final_path, extra);
        for path in extra {
            let path = path.as_ref();
            let final_extra = final_path.join(path);

            for d in self.deps_paths.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(());
                    }
                }
            }
        }
        Ok(())
    }

    fn add_ld_paths(&mut self) -> Result<(), std::io::Error> {
        let mut ld_so = self.downloaded.path.join("etc");
        ld_so.push("ld.so.conf.d");
        if let Ok(dir) = std::fs::read_dir(&ld_so) {
            for f in dir {
                let f = f?;
                if let Ok(f) = std::fs::read_to_string(&f.path()) {
                    for path in f.lines().rev().filter(|x| !x.starts_with("#")) {
                        let path = Path::new(path);
                        if self.ld_path_h.insert(Arc::new(path.to_path_buf())) {
                            self.ld_path.push(Arc::new(path.to_path_buf()))
                        }
                    }
                }
            }
        }
        if !self.ld_path.is_empty() {
            info!("ld_path: {:?}", self.ld_path)
        }
        Ok(())
    }

    /// 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.
    fn find_libs(&mut self) {
        for ld in self.ld_path.iter() {
            let path = self.downloaded.path.join(ld.strip_prefix("/").unwrap());
            if std::fs::metadata(&path).is_ok() {
                let path = Arc::new((self.v, ld.strip_prefix("/").unwrap().to_path_buf()));
                if self.transitive_deps_h.insert(path.clone()) {
                    debug!("find_libs {:?}: adding {:?}", self.pkg.package, path);
                    self.transitive_deps.push(path);
                }
            }
        }
    }
}

async fn patch_elf<'a>(
    vertices: &mut [Vertex<'a>],
    v: usize,
    f: &Path,
    dest_path: &Path,
    files: &Files,
) -> 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 interp = interp.to_str().unwrap();
        let files = files.lock().unwrap();

        let p = Path::new(interp).strip_prefix("/").unwrap();

        let subst = if let Some(subst) = files.get(p) {
            subst.join(p)
        } else if interp.starts_with("/usr")
            || interp.starts_with("/lib")
            || interp.starts_with("/bin")
        {
            // https://www.freedesktop.org/wiki/Software/systemd/TheCaseForTheUsrMerge/
            let p2 = "usr".to_string() + interp;
            let p2 = Path::new(&p2);
            debug!("{:?}", p2);
            if let Some(subst) = files.get(p2) {
                subst.join(p2)
            } else {
                error!("No subst for {:?}", p2);
                let mut p2 = p2.to_path_buf();
                while p2.pop() {
                    debug!("p2 = {:?} {:?}", p2, files.get(&p2));
                }
                return Ok(false);
            }
        } 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);
        };
        let subst = CString::new(subst.to_str().unwrap()).unwrap();
        info!("set interpreter {:?}", subst);
        elf.set_interpreter(subst.to_bytes_with_nul());
    } else if needed.is_empty() {
        // No need to patch
        return Ok(false);
    }

    let mut deps_h = BTreeSet::new();
    let mut path = String::new();
    debug!("needed: {:?} -> {:?}", f, needed);
    let mut satisfied = HashMap::new();
    // First, prefer local deps.
    for ld in vertices[v].ld_path.iter() {
        let mut p = vertices[v]
            .context_path
            .clone()
            .unwrap()
            .join(ld.strip_prefix("/").unwrap());
        let mut at_least_one = false;
        for n in needed.iter() {
            let e = satisfied.entry(n);
            use std::collections::hash_map::Entry;
            if let Entry::Occupied(_) = e {
                continue;
            }

            p.push(&n);
            debug!("trying local path {:?}", p);
            if tokio::fs::metadata(&p).await.is_ok() {
                if let Entry::Vacant(e) = e {
                    e.insert(());
                }
                at_least_one = true;
                p.pop();
                break;
            } else {
                p.pop();
            }
        }
        if at_least_one {
            if !path.is_empty() {
                path.push(':')
            }
            path.push_str(p.to_str().unwrap())
        }
    }

    for dep_ in vertices[v].transitive_deps.iter().rev() {
        let (dep, dep_path) = dep_.as_ref();
        if !deps_h.insert(dep) {
            continue;
        }
        let mut dep = if vertices[*dep].scc == vertices[v].scc {
            vertices[*dep].context_path.clone().unwrap().join(dep_path)
        } else {
            vertices[*dep].final_path.clone().unwrap().join(dep_path)
        };
        debug!("dep of {:?}: {:?}", f, dep);
        let mut is_needed = false;
        for n in needed.iter() {
            let e = satisfied.entry(n);
            use std::collections::hash_map::Entry;
            if let Entry::Occupied(_) = e {
                continue;
            }
            dep.push(&n);
            let exists = tokio::fs::metadata(&dep).await.is_ok();
            if exists {
                if let Entry::Vacant(e) = e {
                    e.insert(());
                }
                debug!("exists {:?}", dep);
                is_needed = true;
            }
            dep.pop();
        }
        if !is_needed {
            continue;
        }
        if !path.is_empty() {
            path.push(':')
        }
        path.push_str(dep.to_str().unwrap());
    }

    /*
    // Add potential local libs.
    let mut last_added = None;
    for (d, _) in find_files(vertices[v].downloaded.path.clone())? {
        if last_added.as_deref() == d.parent() {
            continue;
        }
        for n in needed.iter() {
            if d.file_name().unwrap().to_str().unwrap() == n {
                last_added = d.parent().map(|x| x.to_path_buf());
                if !path.is_empty() {
                    path.push(':')
                }
                let suffix = d
                    .parent()
                    .unwrap()
                    .strip_prefix(&vertices[v].downloaded.path)
                    .unwrap();
                path.push_str(
                    vertices[v]
                        .context_path
                        .as_ref()
                        .unwrap()
                        .join(suffix)
                        .to_str()
                        .unwrap(),
                );
            }
        }
    }
    */
    path.push('\0');
    info!("Setting path {:?}", path);
    if path.len() > 1 {
        elf.set_runpath(&path.as_bytes());
    }

    debug!("patching into {:?}", dest_path);
    Ok(elf.update(Some(dest_path)).unwrap()) // map_err(From::from)
}

async fn finalize<'a>(
    vertices: &mut [Vertex<'a>],
    sccs: &[Vec<usize>],
    client: &Client,
    link_extra: &[(regex::Regex, regex::Regex)],
    files: &Files,
    v: usize,
) -> Result<Arc<PathBuf>, Error> {
    debug!(
        "finalize {:?} {:#?}",
        vertices[v].pkg.package, vertices[v].transitive_deps
    );

    let mut ld_path = std::mem::replace(&mut vertices[v].ld_path, Vec::new());
    let mut ld_path_h = std::mem::replace(&mut vertices[v].ld_path_h, BTreeSet::new());
    let mut transitive_deps = std::mem::replace(&mut vertices[v].transitive_deps, Vec::new());
    let mut transitive_deps_h =
        std::mem::replace(&mut vertices[v].transitive_deps_h, BTreeSet::new());
    let mut deps_paths = Vec::new();

    for dep in vertices[v].deps.iter() {
        debug!(
            "dep: {:?} -> {:?} {:?}",
            vertices[v].pkg.package, dep, vertices[*dep].pkg.package
        );

        let direct = if vertices[*dep].scc == vertices[v].scc {
            vertices[*dep].context_path.clone().unwrap()
        } else {
            vertices[*dep].final_path.clone().unwrap()
        };

        for ld in vertices[*dep].ld_path.iter() {
            if ld_path_h.insert(ld.clone()) {
                ld_path.push(ld.clone());
            }
            let l = ld.strip_prefix("/").unwrap();
            if std::fs::metadata(&direct.join(l)).is_ok() {
                let path = Arc::new((*dep, l.to_path_buf()));
                if transitive_deps_h.insert(path.clone()) {
                    transitive_deps.push(path);
                }
            }
        }

        for d in vertices[*dep].transitive_deps.iter() {
            if transitive_deps_h.insert(d.clone()) {
                debug!(
                    "adding transitive dep: {:?} -> {:?}",
                    vertices[v].pkg.package, d
                );
                transitive_deps.push(d.clone());
            }
        }

        debug!(
            "adding direct dep: {:?} {:?} {:?}",
            vertices[*dep].scc, vertices[v].scc, direct
        );
        deps_paths.push(direct)
    }

    debug!("transitive {:#?}", transitive_deps);

    // Direct deps should come after transitive ones, in order to be
    // picked up earlier when patching the ELFs.
    for dep in vertices[v].deps.iter() {
        let direct = if vertices[*dep].scc == vertices[v].scc {
            vertices[*dep].context_path.clone().unwrap()
        } else {
            vertices[*dep].final_path.clone().unwrap()
        };

        for ld in vertices[*dep].ld_path.iter() {
            let l = ld.strip_prefix("/").unwrap();
            if std::fs::metadata(&direct.join(l)).is_ok() {
                let path = Arc::new((*dep, l.to_path_buf()));
                // Push even if it already exists since we need direct
                // deps to come after indirect ones.
                transitive_deps.push(path);
            }
        }
    }

    vertices[v].ld_path = ld_path;
    vertices[v].ld_path_h = ld_path_h;
    vertices[v].transitive_deps = transitive_deps;
    vertices[v].transitive_deps_h = transitive_deps_h;
    vertices[v].deps_paths = deps_paths;

    let dest = vertices[v].context_path.clone().unwrap();

    let lock = client.lock_store_path(&*dest).await;

    let base_package_name = vertices[v]
        .pkg
        .file_name
        .unwrap()
        .split('/')
        .last()
        .unwrap();
    let base_package_name = Path::new(&base_package_name).with_extension("");
    let base_package_name = base_package_name.to_str().unwrap();

    let final_path = if std::fs::metadata(&*dest).is_err() {
        info!(
            "create final path for {dest:?} ({:?})",
            vertices[v].pkg.package
        );
        match create_final_path(
            client,
            &files,
            vertices,
            sccs,
            v,
            &dest,
            &base_package_name,
            link_extra,
        )
        .await
        {
            Ok(x) => x,
            Err(e) => {
                tokio::fs::remove_dir_all(&*dest).await.unwrap_or(());
                return Err(e);
            }
        }
    } else {
        info!("found, no patching: {:?}", dest);
        let mut output_hasher = blake3::Hasher::new();
        let blakesums = dest.join("blake3sums");
        let file = match tokio::fs::File::open(&blakesums).await {
            Ok(file) => file,
            Err(e) => {
                error!(
                    "Error {:?} {:?}: {:?}",
                    blakesums, vertices[v].downloaded.path, e
                );
                return Err(e.into());
            }
        };
        hash_reader(&blakesums, file, &mut output_hasher).await?;

        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? {
            vertices[v].files.insert(Arc::new(l));
        }

        client.store_path.join(&format!(
            "{}-{}",
            data_encoding::HEXLOWER.encode(output_hasher.finalize().as_bytes()),
            base_package_name,
        ))
    };
    let final_path = Arc::new(final_path);

    add_subst(vertices[v].downloaded.path.clone(), &dest, files.clone()).await?;

    info!("symlink {:?} {:?}", vertices[v].downloaded.path, final_path);
    match std::os::unix::fs::symlink(&*dest, &*final_path) {
        Ok(()) => (),
        Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
            let got = std::fs::read_link(&*final_path)?;
            debug!("Path already exists, previous value {:?}", got);
            // This situation means that we've come to the same
            // result via different build recipes.
            /*
                if dest != got {
                return Err(Error::WrongResultSymlink {
                expected: dest,
                got,
            });
            }
                 */
        }
        Err(e) => return Err(e.into()),
    }
    drop(lock);
    info!("done with {:?}", vertices[v].pkg.package);
    Ok(final_path)
}

async fn copy(
    hashing: &mut Vec<tokio::task::JoinHandle<Result<Option<(PathBuf, String)>, Error>>>,
    from: &Path,
    to: &Path,
) -> Result<(), std::io::Error> {
    let mut stack = vec![from.to_path_buf()];
    while let Some(elt) = stack.pop() {
        let dir = tokio::fs::read_dir(&elt).await;
        debug!("copy {:?}", dir);
        if let Ok(mut dir) = dir {
            let p = elt.strip_prefix(&from).unwrap();
            tokio::fs::create_dir_all(&to.join(&p)).await.unwrap();
            while let Some(e) = dir.next_entry().await.unwrap() {
                stack.push(e.path());
            }
        } else {
            let p = elt.strip_prefix(&from).unwrap();
            if p == Path::new("blake3sums") {
                continue;
            }
            debug!("copy {:?} to {:?}", elt, to.join(&p));
            let to_p = to.join(&p);
            tokio::fs::hard_link(&elt, &to_p).await.unwrap_or(());
            let p = p.to_path_buf();
            hashing.push(tokio::spawn(async move {
                // hash + write
                info!("copy, hashing {:?}", to_p);
                if let Ok(file) = tokio::fs::File::open(&to_p).await {
                    let mut hasher = blake3::Hasher::new();
                    hash_reader(&to_p, file, &mut hasher).await.unwrap();
                    let hex = data_encoding::HEXLOWER.encode(hasher.finalize().as_bytes());
                    Ok::<_, Error>(Some((p, hex)))
                } else {
                    Ok(None)
                }
            }));
        }
    }
    Ok(())
}

async fn create_final_path<'a>(
    client: &Client,
    files: &Files,
    vertices: &mut [Vertex<'a>],
    sccs: &[Vec<usize>],
    v: usize,
    dest: &Path,
    base_package_name: &str,
    link_extra: &[(regex::Regex, regex::Regex)],
) -> Result<PathBuf, Error> {
    // Link the required libexec before hashing.
    let tmp = async_tempfile::TempDir::new_in(dest.parent().unwrap()).await?;
    vertices[v]
        .link_extra(&tmp.dir_path(), &["usr/libexec", "usr/lib/gcc"])
        .await
        .unwrap();

    let scc = vertices[v].scc;

    let mut hashing = Vec::new();
    for (pkg, dep) in link_extra {
        if pkg.is_match(vertices[v].pkg.package) {
            for s in &sccs[..scc] {
                for &d in s.iter() {
                    if dep.is_match(vertices[d].pkg.package) {
                        debug!(
                            "match, copying {:?} to {:?}",
                            vertices[d], vertices[v].context_path
                        );
                        copy(
                            &mut hashing,
                            &vertices[d].context_path.clone().unwrap(),
                            &tmp.dir_path(),
                        )
                        .await?;
                    }
                }
            }
        }
    }

    // Patch the ELFs, now that we have all the deps.
    let mut hashes = Vec::with_capacity(hashing.len());
    debug!("create_final_path {:?}", vertices[v].downloaded.path);
    for (f, meta) in find_files(vertices[v].downloaded.path.to_path_buf())? {
        debug!("f = {:?}", f);
        let rel = f.strip_prefix(&vertices[v].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 !patch_elf(vertices, v, &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(());
        }

        let f = rel.to_path_buf();
        hashing.push(tokio::spawn(async move {
            // hash + write
            info!("create_final_path: hashing {:?}", f);
            if let Ok(file) = tokio::fs::File::open(&dest_path).await {
                let mut hasher = blake3::Hasher::new();
                hash_reader(&dest_path, 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.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)?;
        }
    }
    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 vertices[v].files.iter() {
            file.write_all(path.as_bytes()).await?;
            file.write_all(b"\n").await?;
        }
    }

    for (f, _) in find_dirs(vertices[v].downloaded.path.to_path_buf())? {
        let rel = f.strip_prefix(&vertices[v].downloaded.path).unwrap();
        vertices[v]
            .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.unwrap();
    std::mem::forget(tmp);

    Ok(final_path)
}

async fn push_deps<'a>(
    depends: &[deb::Dep<'a>],
    index: &'a [deb::Index],
    stack: &mut Vec<(
        deb::Stanza<'a>,
        Option<tokio::sync::oneshot::Receiver<Result<Downloaded, Error>>>,
    )>,
) -> Vec<&'a str> {
    let mut d = Vec::new();
    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)
                };
                d.push(dep.package);
                stack.push((dep, None))
            }
            deb::Dep::Alternatives { alt } => {
                debug!("alt {:?}", alt);
                let stack_len = stack.len();
                for dep in alt {
                    if let Some(dep_) = multi_lookup(index, &dep.name).await {
                        d.push(dep_.package);
                        stack.push((dep_, None));
                        break;
                    }
                }
                if stack.len() == stack_len {
                    panic!("Not found: {:?}", alt);
                }
            }
        }
    }
    d
}