Skip to main content

treeboot_core/
discovery.rs

1use std::path::{Path, PathBuf};
2
3use crate::context::resolve_worktree_path;
4use crate::{Error, Result, Worktree};
5
6const INIT_SCRIPT_PATHS: &[&str] = &[".treeboot.sh", ".treebootrc", ".config/treeboot/init"];
7const CONFIG_PATHS: &[&str] = &[
8    ".treeboot.toml",
9    "treeboot.toml",
10    ".config/treeboot/config.toml",
11];
12
13/// Discovered treeboot init scripts for a worktree.
14#[derive(Debug, Clone, Default, PartialEq, Eq)]
15pub struct InitScriptDiscovery {
16    /// First executable init script found in treeboot precedence order.
17    pub executable: Option<PathBuf>,
18    /// Existing init script paths that were ignored because they are not
19    /// executable on this platform.
20    pub ignored: Vec<PathBuf>,
21}
22
23impl InitScriptDiscovery {
24    /// Discovers executable and ignored init scripts for a worktree.
25    #[must_use]
26    pub fn discover(context: &Worktree) -> Self {
27        discover_scripts(&context.worktree_path)
28    }
29}
30
31pub(crate) fn discover_scripts(worktree_path: &Path) -> InitScriptDiscovery {
32    let mut ignored = Vec::new();
33
34    for relative in INIT_SCRIPT_PATHS {
35        let path = worktree_path.join(relative);
36
37        if !path.is_file() {
38            continue;
39        }
40
41        if is_executable(&path) {
42            return InitScriptDiscovery {
43                executable: Some(path),
44                ignored,
45            };
46        }
47
48        ignored.push(path);
49    }
50
51    InitScriptDiscovery {
52        executable: None,
53        ignored,
54    }
55}
56
57pub(crate) fn discover_config(
58    worktree_path: &Path,
59    requested_config: Option<&Path>,
60) -> Result<Option<PathBuf>> {
61    if let Some(path) = requested_config {
62        let path = resolve_worktree_path(worktree_path, path);
63
64        if path.is_file() {
65            return Ok(Some(path));
66        }
67
68        return Err(Error::ConfigNotFound(path));
69    }
70
71    Ok(CONFIG_PATHS
72        .iter()
73        .map(|relative| worktree_path.join(relative))
74        .find(|path| path.is_file()))
75}
76
77#[cfg(unix)]
78fn is_executable(path: &Path) -> bool {
79    use std::os::unix::fs::PermissionsExt;
80
81    path.metadata()
82        .map(|metadata| metadata.permissions().mode() & 0o111 != 0)
83        .unwrap_or(false)
84}
85
86#[cfg(not(unix))]
87fn is_executable(path: &Path) -> bool {
88    path.is_file()
89}