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).
60pub fn copy_library_to(
61    source_root: &Path,
62    lib_name: &str,
63    dest_root: &Path,
64    dest_lib64_path: &str,
65    dest_lib_path: &str,
66    extra_lib_paths: &[&str],
67) -> Result<()> {
68    let src = find_library(source_root, lib_name, extra_lib_paths).with_context(|| {
69        format!(
70            "Could not find library '{}' in source (searched lib64, lib, systemd paths)",
71            lib_name
72        )
73    })?;
74
75    // Check if this is a systemd private library
76    let dest_path = if src.to_string_lossy().contains("lib64/systemd")
77        || src.to_string_lossy().contains("lib/systemd")
78    {
79        // Systemd private libraries stay in their own directory
80        let dest_dir = dest_root.join(dest_lib64_path).join("systemd");
81        fs::create_dir_all(&dest_dir)?;
82        dest_dir.join(lib_name)
83    } else if src.to_string_lossy().contains("lib64") {
84        dest_root.join(dest_lib64_path).join(lib_name)
85    } else {
86        dest_root.join(dest_lib_path).join(lib_name)
87    };
88
89    if dest_path.exists() {
90        return Ok(()); // Already copied
91    }
92
93    // Handle symlinks - copy both the symlink target and create the symlink
94    if src.is_symlink() {
95        let link_target = fs::read_link(&src)?;
96
97        // Resolve the actual file
98        let actual_src = if link_target.is_relative() {
99            src.parent()
100                .context("Library path has no parent")?
101                .join(&link_target)
102        } else {
103            source_root.join(link_target.to_str().unwrap().trim_start_matches('/'))
104        };
105
106        if actual_src.exists() {
107            // Copy the actual file first
108            let target_name = link_target.file_name().unwrap_or(link_target.as_os_str());
109            let target_dest = dest_path.parent().unwrap().join(target_name);
110            if !target_dest.exists() {
111                fs::copy(&actual_src, &target_dest)?;
112            }
113            // Create symlink
114            if !dest_path.exists() {
115                std::os::unix::fs::symlink(&link_target, &dest_path)?;
116            }
117        } else {
118            // Symlink target not found, copy the symlink itself
119            fs::copy(&src, &dest_path)?;
120        }
121    } else {
122        fs::copy(&src, &dest_path)?;
123    }
124
125    Ok(())
126}
127
128/// Create a symlink if it doesn't already exist.
129///
130/// Returns `Ok(true)` if the symlink was created, `Ok(false)` if it already existed.
131/// This is useful for idempotent symlink creation (e.g., enabling systemd services).
132pub fn create_symlink_if_missing(target: &Path, link: &Path) -> Result<bool> {
133    if link.exists() || link.is_symlink() {
134        return Ok(false);
135    }
136    std::os::unix::fs::symlink(target, link).with_context(|| {
137        format!(
138            "Failed to create symlink {} -> {}",
139            link.display(),
140            target.display()
141        )
142    })?;
143    Ok(true)
144}