Skip to main content

leviso_elf/
copy.rs

1//! File and library copying utilities.
2
3use anyhow::{Context, Result};
4use std::fs;
5use std::os::unix::fs::PermissionsExt;
6use std::path::Path;
7
8use crate::paths::find_library;
9
10/// Make a file executable (chmod 755).
11pub fn make_executable(path: &Path) -> Result<()> {
12    let mut perms = fs::metadata(path)
13        .with_context(|| format!("Failed to read metadata: {}", path.display()))?
14        .permissions();
15    perms.set_mode(0o755);
16    fs::set_permissions(path, perms)
17        .with_context(|| format!("Failed to set permissions: {}", path.display()))?;
18    Ok(())
19}
20
21/// Copy a directory recursively, handling symlinks.
22///
23/// Returns the total size in bytes of all files copied.
24pub fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<u64> {
25    let mut total_size: u64 = 0;
26
27    if !src.is_dir() {
28        return Ok(0);
29    }
30
31    fs::create_dir_all(dst)?;
32
33    for entry in fs::read_dir(src)? {
34        let entry = entry?;
35        let path = entry.path();
36        let dest_path = dst.join(entry.file_name());
37
38        if path.is_dir() {
39            total_size += copy_dir_recursive(&path, &dest_path)?;
40        } else if path.is_symlink() {
41            let target = fs::read_link(&path)?;
42            if !dest_path.exists() && !dest_path.is_symlink() {
43                std::os::unix::fs::symlink(&target, &dest_path)?;
44            }
45        } else {
46            fs::copy(&path, &dest_path)?;
47            if let Ok(meta) = fs::metadata(&dest_path) {
48                total_size += meta.len();
49            }
50        }
51    }
52
53    Ok(total_size)
54}
55
56/// Copy a library from source to destination, handling symlinks.
57///
58/// The `dest_lib64_path` and `dest_lib_path` parameters specify where
59/// libraries should be copied (e.g., "lib64" for initramfs, "usr/lib64" for rootfs).
60///
61/// The `private_lib_dirs` parameter specifies subdirectories that should preserve
62/// their structure (e.g., `&["systemd"]` for LevitateOS, `&["openrc"]` for AcornOS,
63/// or `&[]` if no private library directories are needed).
64pub fn copy_library_to(
65    source_root: &Path,
66    lib_name: &str,
67    dest_root: &Path,
68    dest_lib64_path: &str,
69    dest_lib_path: &str,
70    extra_lib_paths: &[&str],
71    private_lib_dirs: &[&str],
72) -> Result<()> {
73    let src = find_library(source_root, lib_name, extra_lib_paths).with_context(|| {
74        format!(
75            "Could not find library '{}' in source (searched lib64, lib, extra paths)",
76            lib_name
77        )
78    })?;
79
80    // Check if this is a private library (e.g., systemd, openrc)
81    let src_str = src.to_string_lossy();
82    let private_dir = private_lib_dirs.iter().find(|dir| {
83        src_str.contains(&format!("lib64/{}", dir)) || src_str.contains(&format!("lib/{}", dir))
84    });
85
86    let dest_path = if let Some(dir) = private_dir {
87        // Private libraries stay in their own subdirectory
88        let dest_dir = dest_root.join(dest_lib64_path).join(dir);
89        fs::create_dir_all(&dest_dir)?;
90        dest_dir.join(lib_name)
91    } else if src_str.contains("lib64") {
92        dest_root.join(dest_lib64_path).join(lib_name)
93    } else {
94        dest_root.join(dest_lib_path).join(lib_name)
95    };
96
97    if dest_path.exists() {
98        return Ok(()); // Already copied
99    }
100
101    // Handle symlinks - copy both the symlink target and create the symlink
102    if src.is_symlink() {
103        let link_target = fs::read_link(&src)?;
104
105        // Resolve the actual file
106        let actual_src = if link_target.is_relative() {
107            src.parent()
108                .context("Library path has no parent")?
109                .join(&link_target)
110        } else {
111            source_root.join(link_target.to_str().unwrap().trim_start_matches('/'))
112        };
113
114        if actual_src.exists() {
115            // Copy the actual file first
116            let target_name = link_target.file_name().unwrap_or(link_target.as_os_str());
117            let target_dest = dest_path.parent().unwrap().join(target_name);
118            if !target_dest.exists() {
119                fs::copy(&actual_src, &target_dest)?;
120            }
121            // Create symlink
122            if !dest_path.exists() {
123                std::os::unix::fs::symlink(&link_target, &dest_path)?;
124            }
125        } else {
126            // Symlink target not found, copy the symlink itself
127            fs::copy(&src, &dest_path)?;
128        }
129    } else {
130        fs::copy(&src, &dest_path)?;
131    }
132
133    Ok(())
134}
135
136/// Create a symlink if it doesn't already exist.
137///
138/// Returns `Ok(true)` if the symlink was created, `Ok(false)` if it already existed.
139/// This is useful for idempotent symlink creation (e.g., enabling systemd services).
140#[must_use = "return value indicates whether symlink was created"]
141pub fn create_symlink_if_missing(target: &Path, link: &Path) -> Result<bool> {
142    if link.exists() || link.is_symlink() {
143        return Ok(false);
144    }
145    std::os::unix::fs::symlink(target, link).with_context(|| {
146        format!(
147            "Failed to create symlink {} -> {}",
148            link.display(),
149            target.display()
150        )
151    })?;
152    Ok(true)
153}