diff --git a/assets/bin/sexpkg b/assets/bin/sexpkg deleted file mode 120000 index 1381872..0000000 --- a/assets/bin/sexpkg +++ /dev/null @@ -1 +0,0 @@ -../../target/release/sexpkg \ No newline at end of file diff --git a/assets/etc/index-conflict.md b/assets/etc/index-conflict.md new file mode 100644 index 0000000..b1e8c34 --- /dev/null +++ b/assets/etc/index-conflict.md @@ -0,0 +1,17 @@ +Format += + +``` cfg *** bin *** +``` + +``` cfg *** sbin *** +``` + +``` cfg *** include *** +``` + +``` cfg *** lib *** +``` + +``` cfg *** share *** +``` diff --git a/assets/etc/sexpkg.md b/assets/etc/sexpkg.md index 3931c93..e0e9343 100644 --- a/assets/etc/sexpkg.md +++ b/assets/etc/sexpkg.md @@ -2,14 +2,14 @@ Sexpkg's config file = -``` sh *** Repository list and priority *** +``` cfg *** Repository list and priority *** gnu /pkg/gnu/sexpkg/var/gnu musl /pkg/gnu/sexpkg/var/musl ``` -``` sh *** Clean exclude *** +``` cfg *** Clean exclude *** * ``` -``` sh *** Clean include *** +``` cfg *** Clean include *** ``` diff --git a/assets/var/gnu/glibc.md b/assets/var/gnu/glibc.md index cd97aa0..60807c1 100644 --- a/assets/var/gnu/glibc.md +++ b/assets/var/gnu/glibc.md @@ -2,7 +2,7 @@ glibc 2.41 = https://ftp.gnu.org/gnu/libc/{name}-{version}.tar.xz -``` sh build.sctipt +``` sh *** build.sctipt *** mkdir build; cd build && ../configure \ --prefix=/pkg/gnu/glibc \ @@ -12,7 +12,7 @@ mkdir build; cd build && make -j$(nproc) && make install ``` -``` sh dependencies +``` cfg *** dependencies *** binutils linux ``` diff --git a/src/commands/delete.rs b/src/commands/delete.rs index 5dbfe07..cccdeda 100644 --- a/src/commands/delete.rs +++ b/src/commands/delete.rs @@ -4,7 +4,6 @@ use std::path::Path; use rayon::prelude::*; use std::os::unix::fs::MetadataExt; - use super::get_var_path; use crate::utils::parser; @@ -22,7 +21,7 @@ pub fn delete(repo: &str, pkgname: &str) { eprintln!("{} not installed in {}", pkgname, repo) } - let subdirs = ["lib", "include", "bin"]; + let subdirs = ["bin", "lib", "libexec", "include", "share"]; for subdir in &subdirs { let dir_path = base_dir.join(subdir); if dir_path.exists() { diff --git a/src/commands/disable.rs b/src/commands/disable.rs new file mode 100644 index 0000000..5649def --- /dev/null +++ b/src/commands/disable.rs @@ -0,0 +1,35 @@ +use std::path::PathBuf; +use std::fs; +use crate::utils::deletecopy::deletecopy; +use crate::utils::shell::*; + +pub fn disable(repo: &String, pkgname: &String) -> Result<(), String> { + let source = PathBuf::from("/pkg").join(repo).join(pkgname); + let _ = fs::File::create(&source.join("disabled")); + let destination = source.parent() + .ok_or("Failed to get parent directory for path")? + .to_path_buf(); + + let dirs_to_copy = vec![ + ("bin"), + ("lib"), + ("libexec"), + ("include"), + ("share"), + ]; + + for base_system_folder_dir in dirs_to_copy { + let src = source.join(base_system_folder_dir); + let dest = destination.join(base_system_folder_dir); + + if src.exists() { + deletecopy(&src, &dest) + .map_err(|e| format!("Failed to delete copy {} to {}: {}", src.display(), dest.display(), e))?; + } + } + + mount_overlay(&destination)?; + shell_update()?; + + Ok(()) +} diff --git a/src/commands/enable.rs b/src/commands/enable.rs new file mode 100644 index 0000000..a952521 --- /dev/null +++ b/src/commands/enable.rs @@ -0,0 +1,11 @@ +use std::path::PathBuf; +use std::fs; +use super::pkglink::pkglink; + +pub fn enable(repo: &String, pkgname: &String) -> Result<(), String> { + let source = PathBuf::from("/pkg").join(repo).join(pkgname); + let _ = fs::remove_file(&source.join("disabled")); + + pkglink(&repo, &pkgname)?; + Ok(()) +} diff --git a/src/commands/install.rs b/src/commands/install.rs index 6a0b195..5cd92cf 100644 --- a/src/commands/install.rs +++ b/src/commands/install.rs @@ -1,47 +1,100 @@ use std::fs; -use std::path::Path; -use std::process::Command; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; use std::process; use super::*; use crate::utils::parser; -use crate::utils::hardcopy::hardcopy; +use crate::commands::pkglink::pkglink; + pub fn install(repo: &String, pkgname: &String) -> Result<(), bool> { let var_path = get_var_path(); let pkg_md_path = var_path.join(format!("{}/{}.md", repo, pkgname)); if !pkg_md_path.exists() { - match parser::get_repo_addr(repo) { - Ok(repo_addr) => { - let rsync_command = format!( - "rsync --include='{}.md' --exclude='*' {} {}", - pkgname, - repo_addr, - pkg_md_path.to_str().unwrap() - ); - let rsync_output = Command::new("sh") - .arg("-c") - .arg(rsync_command) - .output() - .expect("Failed to execute rsync"); + upload_from_repo(&repo, &pkgname, &pkg_md_path)?; + } - if !rsync_output.status.success() { - eprintln!("broken repo: {}", repo); - return Err(false); - } - if !pkg_md_path.exists() { - eprintln!("not found {} in {} repo", pkgname, repo); - return Err(true); - } + check_dependency(&repo, &pkg_md_path)?; + download(&pkgname, &pkg_md_path)?; + + let src_dir = PathBuf::from("/pkg/src").join(&pkgname); + build(&repo, &pkgname, &src_dir, &pkg_md_path)?; + pkglink(&repo, &pkgname).expect("Failed link package"); + + println!("Package {} installed successfully from repo {}", pkgname, repo); + Ok(()) +} + + +pub fn install_all(pkgname: &String) { + let repos = match parser::get_repo_list() { + Ok(repos) => repos, + Err(e) => { + eprintln!("Failed to get repository list: {}", e); + return; + } + }; + + let mut success = false; + for repo in repos { + println!("Trying to install {} from repo {}...", pkgname, repo); + + match install(&repo, pkgname) { + Ok(()) => { + success = true; + break; } - Err(e) => { - eprintln!("Repository {} not found: {}", repo, e); - return Err(false); + Err(no_repo_package) => { + if no_repo_package { + continue; + } else { + process::exit(1) + } } } } + if !success { + eprintln!("Package {} not found in any available repository", pkgname); + } +} + +fn upload_from_repo(repo: &String, pkgname: &String, pkg_md_path: &Path) -> Result<(), bool> { + match parser::get_repo_addr(repo) { + Ok(repo_addr) => { + let rsync_command = format!( + "rsync --include='{}.md' --exclude='*' {} {}", + pkgname, + repo_addr, + pkg_md_path.to_str().unwrap() + ); + let rsync_output = Command::new("sh") + .arg("-c") + .arg(rsync_command) + .output() + .expect("Failed to execute rsync"); + + if !rsync_output.status.success() { + eprintln!("broken repo: {}", repo); + return Err(false); + } + if !pkg_md_path.exists() { + eprintln!("not found {} in {} repo", pkgname, repo); + return Err(true); + } + + Ok(()) + } + Err(e) => { + eprintln!("Repository {} not found: {}", repo, e); + return Err(true); + } + } +} + +fn check_dependency(repo: &String, pkg_md_path: &Path) -> Result<(), bool> { let deps = match parser::get_deps(&pkg_md_path) { Ok(deps) => deps, Err(e) => { @@ -51,10 +104,8 @@ pub fn install(repo: &String, pkgname: &String) -> Result<(), bool> { }; for dependency in deps.lines() { - let dependency = dependency.trim(); - if !dependency.is_empty() { - let pkg_dir = Path::new("/pkg").join(repo).join(dependency); - if !pkg_dir.exists() { + if !dependency.trim().is_empty() { + if !Path::new("/pkg").join(repo).join(dependency).exists() { match install(repo, &dependency.to_string()) { Ok(()) => {} Err(_) => {process::exit(1) } @@ -63,9 +114,12 @@ pub fn install(repo: &String, pkgname: &String) -> Result<(), bool> { } } + Ok(()) +} - let url = match parser::get_url(&pkg_md_path) { +fn download(pkgname: &String, pkg_md_path: &Path) -> Result<(), bool> { + let url = match parser::get_url(pkg_md_path) { Ok(url) => url, Err(e) => { eprintln!("Failed to parse URL: {}", e); @@ -73,93 +127,142 @@ pub fn install(repo: &String, pkgname: &String) -> Result<(), bool> { } }; + let src = PathBuf::from("/pkg/src").join(pkgname); - let src = Path::new("/pkg/src"); - fs::create_dir_all(&src).expect("Failed to create src directory"); - let wget_command: String; - let src_dir: String; - if url.ends_with(".tar.gz") { - wget_command = format!("wget -O- {} | tar -xz -C {}", url, src.to_str().unwrap()); - src_dir = format!( - "{}/{}", - src.to_str().unwrap(), - url - .rsplit('/') - .next() - .expect("Failed to extract archive name from URL") - .trim_end_matches(".tar.gz")) - } else if url.ends_with(".tar.xz") { - wget_command = format!("wget -O- {} | tar -xJ -C {}", url, src.to_str().unwrap()); - src_dir = format!( - "{}/{}", - src.to_str().unwrap(), - url - .rsplit('/') - .next() - .expect("Failed to extract archive name from URL") - .trim_end_matches(".tar.xz")) + if let Err(e) = fs::create_dir_all(&src) { + eprintln!("Failed to create directory {}: {}", src.display(), e); + return Err(false); + } + + if !url.ends_with(".git") { + let compress_flag = if url.ends_with(".bz2") { + "--bzip2" + } else if url.ends_with(".xz") { + "--xz" + } else if url.ends_with(".lz") { + "--lzip" + } else if url.ends_with(".lzma") { + "--lzma" + } else if url.ends_with(".lzo") { + "--lzop" + } else if url.ends_with(".zst") { + "--zstd" + } else if url.ends_with(".gz") { + "--gzip" + } else { + eprintln!("Unsupported compression format for URL: {}", url); + return Err(false); + }; + + let wget_output = Command::new("wget") + .arg("-O-") + .arg(&url) + .stdout(Stdio::piped()) + .spawn(); + + let tar_input = match wget_output { + Ok(child) => child.stdout.unwrap(), + Err(e) => { + eprintln!("Failed to execute wget: {}", e); + return Err(false); + } + }; + + let tar_status = Command::new("tar") + .arg("-x") + .arg(compress_flag) + .arg("-C") + .arg(&src) + .stdin(tar_input) + .status(); + + if tar_status.is_err() || !tar_status.unwrap().success() { + eprintln!("Failed to extract archive from URL: {}", url); + return Err(false); + } + + let entries = fs::read_dir(&src).unwrap(); + let dirs: Vec<_> = entries + .filter_map(|entry| entry.ok()) + .filter(|entry| entry.file_type().map_or(false, |ft| ft.is_dir())) + .collect(); + + if dirs.len() == 1 { + let single_dir = dirs[0].path(); + + for entry in fs::read_dir(&single_dir).unwrap() { + let entry = entry.unwrap(); + let dest = src.join(entry.file_name()); + fs::rename(entry.path(), dest).unwrap(); + } + + fs::remove_dir(single_dir).unwrap(); + } } else { - eprintln!("Unsupported archive format for URL: {}", url); - return Err(false); + let git_status = Command::new("git") + .arg("clone") + .arg(&url) + .arg(&src) + .status(); + + if git_status.is_err() || !git_status.unwrap().success() { + eprintln!("Failed to clone git repository from URL: {}", url); + return Err(false); + } } - println!("Downloading and extracting package from: {}", url); + Ok(()) +} - let wget_output = Command::new("sh") - .arg("-c") - .arg(wget_command) - .output() - .expect("Failed to execute wget and tar"); - if !wget_output.status.success() { - eprintln!("Failed to download and extract package: {}", String::from_utf8_lossy(&wget_output.stderr)); - return Err(false); - } - - // 3. Выполнение bash-скрипта сборки - let build_script = match parser::get_build_script(&pkg_md_path) { +fn build( + repo: &String, + pkgname: &String, + src_dir: &Path, + pkg_md_path: &Path, +) -> Result<(), bool> { + let build_script = match parser::get_build_script(pkg_md_path) { Ok(script) => script, - Err(e) => { - eprintln!("Failed to parse build script: {}", e); + Err(error) => { + eprintln!("Failed to parse build script: {}", error); return Err(false); } }; - println!("{}", &src_dir); - let build_output = Command::new("sh") + let output = Command::new("zsh") .arg("-c") - .arg(build_script) - .current_dir(&src_dir) - .output() - .expect("Failed to execute build script"); + .arg(&build_script) + .current_dir(src_dir) + .output(); - if !build_output.status.success() { - eprintln!("Build failed: {}", String::from_utf8_lossy(&build_output.stderr)); + if let Err(e) = output { + eprintln!("Failed to execute build script: {}", e); return Err(false); } - let build_script_dest = Path::new("/pkg") - .join(repo) - .join(pkgname) - .join("build-script.md"); - fs::copy(&pkg_md_path, &build_script_dest).expect("Failed to copy build script"); + let output = output.unwrap(); + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + eprintln!("Script failed with error: {}", stderr); + return Err(false); + } - let pkg_dir = Path::new("/pkg").join(repo).join(pkgname); - hardcopy( - &pkg_dir.join("bin"), - &Path::new("/pkg").join(repo).join("bin"), - ).expect("Failed to copy bin directory"); - hardcopy( - &pkg_dir.join("include"), - &Path::new("/pkg").join(repo).join("include"), - ).expect("Failed to copy include directory"); - hardcopy( - &pkg_dir.join("lib"), - &Path::new("/pkg").join(repo).join("lib"), - ).expect("Failed to copy lib directory"); + let dest_dir = PathBuf::from("/pkg").join(repo).join(pkgname); + if let Err(e) = fs::create_dir_all(&dest_dir) { + eprintln!("Failed to create destination directory: {}", e); + return Err(false); + } - fs::remove_dir_all(&src_dir).expect("Failed to clean up src directory"); + let dest_path = dest_dir.join("build-script.md"); + if let Err(e) = fs::copy(pkg_md_path, &dest_path) { + eprintln!("Failed to copy build script to destination: {}", e); + return Err(false); + } + + if let Err(e) = fs::remove_dir_all(src_dir) { + eprintln!("Failed to remove source directory: {}", e); + return Err(false); + } - println!("Package {} installed successfully from repo {}", pkgname, repo); Ok(()) } diff --git a/src/commands/install_all.rs b/src/commands/install_all.rs deleted file mode 100644 index ed36f94..0000000 --- a/src/commands/install_all.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::process; - -use crate::utils::parser; -use super::install::install; - - -pub fn install_all(pkgname: &String) { - let repos = match parser::get_repo_list() { - Ok(repos) => repos, - Err(e) => { - eprintln!("Failed to get repository list: {}", e); - return; - } - }; - - let mut success = false; - for repo in repos { - println!("Trying to install {} from repo {}...", pkgname, repo); - - match install(&repo, pkgname) { - Ok(()) => { - success = true; - break; - } - Err(no_repo_package) => { - if no_repo_package { - continue; - } else { - process::exit(1) - } - } - } - } - if !success { - eprintln!("Package {} not found in any available repository", pkgname); - } -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 61a226f..e9a59a7 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,6 +1,8 @@ pub mod install; -pub mod install_all; pub mod delete; +pub mod pkglink; +pub mod disable; +pub mod enable; use std::path::PathBuf; diff --git a/src/commands/pkglink.rs b/src/commands/pkglink.rs new file mode 100644 index 0000000..44c9c96 --- /dev/null +++ b/src/commands/pkglink.rs @@ -0,0 +1,35 @@ +use std::path::PathBuf; +use crate::utils::hardcopy::hardcopy_handler; +use crate::utils::shell::*; + +pub fn pkglink(repo: &String, pkgname: &String) -> Result<(), String> { + let source = PathBuf::from("/pkg").join(repo).join(pkgname); + if source.join("disabled").exists() { return Ok(()) } + + let destination = source.parent() + .ok_or("Failed to get parent directory for path")? + .to_path_buf(); + + let dirs_to_copy = vec![ + ("bin"), + ("lib"), + ("libexec"), + ("include"), + ("share") + ]; + + for base_system_folder_dir in dirs_to_copy { + let src = source.join(base_system_folder_dir); + let dest = destination.join(base_system_folder_dir); + + if src.exists() { + hardcopy_handler(&src, &dest) + .map_err(|e| format!("Failed to copy {} to {}: {}", src.display(), dest.display(), e))?; + } + } + + mount_overlay(&destination)?; + shell_update()?; + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index de947cd..1e66022 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,17 +38,49 @@ fn main() { ), ) .subcommand( - clap::Command::new("hardcopy") - .about("Create a hard link or copy directory structure") + clap::Command::new("pkglink") + .about("Create package links and mount overlays") .arg( - clap::Arg::new("source") - .help("Source path") + clap::Arg::new("repo") + .help("Repository name") .required(true) .index(1), ) .arg( - clap::Arg::new("destination") - .help("Destination path") + clap::Arg::new("pkgname") + .help("Package name") + .required(true) + .index(2), + ), + ) + .subcommand( + clap::Command::new("disable") + .about("Disable package") + .arg( + clap::Arg::new("repo") + .help("Repository name") + .required(true) + .index(1), + ) + .arg( + clap::Arg::new("pkgname") + .help("Package name") + .required(true) + .index(2), + ), + ) + .subcommand( + clap::Command::new("enable") + .about("Enable package") + .arg( + clap::Arg::new("repo") + .help("Repository name") + .required(true) + .index(1), + ) + .arg( + clap::Arg::new("pkgname") + .help("Package name") .required(true) .index(2), ), @@ -61,7 +93,7 @@ fn main() { match args.len() { 1 => { let pkgname = args[0]; - commands::install_all::install_all(pkgname); + commands::install::install_all(pkgname); } 2 => { let repo = args[0]; @@ -80,13 +112,29 @@ fn main() { } else { commands::delete::delete(repo, pkgname); } - } else if let Some(hardcopy_matches) = matches.subcommand_matches("hardcopy") { - let source_path = std::path::Path::new(hardcopy_matches.get_one::("source").unwrap()); - let destination_path = std::path::Path::new(hardcopy_matches.get_one::("destination").unwrap()); + } else if let Some(pkglink_matches) = matches.subcommand_matches("pkglink") { + let repo = pkglink_matches.get_one::("repo").unwrap(); + let pkgname = pkglink_matches.get_one::("pkgname").unwrap(); - match utils::hardcopy::hardcopy(source_path, destination_path) { - Ok(_) => println!("Hardcopy completed successfully."), - Err(e) => eprintln!("Error during hardcopy: {}", e), + match commands::pkglink::pkglink(&repo, &pkgname) { + Ok(_) => println!("pkglink completed successfully."), + Err(e) => eprintln!("Error during pkglink: {}", e), + } + } else if let Some(disable_matches) = matches.subcommand_matches("disable") { + let repo = disable_matches.get_one::("repo").unwrap(); + let pkgname = disable_matches.get_one::("pkgname").unwrap(); + + match commands::disable::disable(&repo, &pkgname) { + Ok(_) => println!("disable completed successfully."), + Err(e) => eprintln!("Error during disable: {}", e), + } + } else if let Some(enable_matches) = matches.subcommand_matches("enable") { + let repo = enable_matches.get_one::("repo").unwrap(); + let pkgname = enable_matches.get_one::("pkgname").unwrap(); + + match commands::enable::enable(&repo, &pkgname) { + Ok(_) => println!("enable completed successfully."), + Err(e) => eprintln!("Error during enable: {}", e), } } else { println!("No command provided. Use `pkg --help` for usage information."); diff --git a/src/utils/deletecopy.rs b/src/utils/deletecopy.rs new file mode 100644 index 0000000..058a8fd --- /dev/null +++ b/src/utils/deletecopy.rs @@ -0,0 +1,33 @@ +use rayon::prelude::*; +use std::fs; +use std::io; +use std::path::Path; +use std::os::unix::fs::MetadataExt; + + +pub fn deletecopy(source: &Path, destination: &Path) -> io::Result<()> { + let metadata = fs::symlink_metadata(source)?; + + if metadata.file_type().is_file() { + if let Ok(dest_metadata) = fs::metadata(destination) { + if dest_metadata.ino() == metadata.ino() { + fs::remove_file(destination)?; + return Ok(()); + } + } + } else if metadata.file_type().is_dir() { + let entries: Vec<_> = fs::read_dir(source)?.collect::>>()?; + entries.par_iter().try_for_each(|entry| { + let path_source = entry.path(); + if let Some(file_name) = path_source.file_name() { + deletecopy(&path_source, &destination.join(file_name)) + } else { + Ok(()) + } + })?; + } else if metadata.file_type().is_symlink() { + fs::remove_file(destination)?; + } + + Ok(()) +} diff --git a/src/utils/hardcopy.rs b/src/utils/hardcopy.rs index dcb825c..280cde1 100644 --- a/src/utils/hardcopy.rs +++ b/src/utils/hardcopy.rs @@ -1,31 +1,216 @@ use rayon::prelude::*; use std::fs; -use std::io; -use std::path::Path; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; use std::os::unix; +use std::os::unix::fs::MetadataExt; +use std::sync::mpsc; +use std::thread; -pub fn hardcopy(source: &Path, destination: &Path) -> io::Result<()> { + +fn hardcopy( + source: &Path, + destination: &Path, + conflict_sender: Option, mpsc::Sender)>>, +) -> io::Result<()> { let metadata = fs::symlink_metadata(source)?; + if metadata.file_type().is_file() { - fs::hard_link(source, destination)?; + match fs::hard_link(source, destination) { + Ok(_) => {} + Err(_) => { + if let Ok(dest_metadata) = fs::metadata(destination) { + if dest_metadata.ino() == metadata.ino() { + return Ok(()); + } + } + + match crate::utils::parser::get_index_conflict(destination) { + Ok(index_source) => { + if index_source == source { + fs::remove_file(destination)?; + fs::hard_link(source, destination)?; + } else { + return Ok(()); + } + } + Err(_) => { + let conflict_list = find_files_with_location(&destination); + + let count = conflict_list.len(); + if count == 1 { + fs::remove_file(destination)?; + fs::hard_link(source, destination)?; + } else if count >= 1 { + let (response_tx, response_rx) = mpsc::channel(); + + if let Some(sender) = &conflict_sender { + sender.send((conflict_list.clone(), response_tx)).unwrap(); } + + let selected_source = response_rx.recv().unwrap(); + append_index_block(&selected_source, &destination)?; + if selected_source == source { + fs::remove_file(destination)?; + fs::hard_link(source, destination)?; + } else { + return Ok(()); + } + } + } + } + } + } } else if metadata.file_type().is_dir() { fs::create_dir_all(destination)?; let entries: Vec<_> = fs::read_dir(source)?.collect::>>()?; entries.par_iter().try_for_each(|entry| { - let path = entry.path(); - if let Some(file_name) = path.file_name() { + let path_source = entry.path(); + if let Some(file_name) = path_source.file_name() { let dest_path = destination.join(file_name); - hardcopy(&path, &dest_path) + hardcopy(&path_source, &dest_path, conflict_sender.clone()) } else { Ok(()) } })?; } else if metadata.file_type().is_symlink() { - let target = fs::read_link(source)?; - unix::fs::symlink(target, destination)?; + let symlink_value = fs::read_link(source)?; + fs::remove_file(destination)?; + unix::fs::symlink(symlink_value, destination)?; } Ok(()) } + +pub fn hardcopy_handler( + source: &Path, + destination: &Path, +) -> io::Result<()> { + let (tx, rx): ( + mpsc::Sender<(Vec, mpsc::Sender)>, + mpsc::Receiver<(Vec, mpsc::Sender)>, + ) = mpsc::channel(); + + thread::spawn(move || { + for (conflict_list, response_tx) in rx { + let selected_source = choise_index_conflict(conflict_list); + + response_tx.send(selected_source).unwrap(); + } + }); + + hardcopy(source, destination, Some(tx)) +} + +fn find_files_with_location(destination: &Path) -> Vec { + let mut found_files = Vec::new(); + + let mut components = destination.components(); + + let prefix = match (components.next(), components.next(), components.next()) { + (Some(first), Some(second), Some(third)) => { + PathBuf::from(first.as_os_str()) + .join(second.as_os_str()) + .join(third.as_os_str()) + } + _ => { + return Vec::new(); + } + }; + + let file_location: PathBuf = components.as_path().to_path_buf(); + + if let Ok(entries) = fs::read_dir(&prefix) { + for entry in entries.filter_map(Result::ok) { + let path = entry.path(); + + if path.is_dir() { + let target_path = path.join(&file_location); + if target_path.exists() { + if !path.join(PathBuf::from("disabled")).exists() { + found_files.push(target_path); + } + } + } + } + } + + found_files +} + + +fn choise_index_conflict(conflict_list: Vec) -> PathBuf { + for (index, path) in conflict_list.iter().enumerate() { + println!("{}: {}", index + 1, path.display()); + } + + let count = conflict_list.len(); + + loop { + print!("Choose a path to resolve the conflict (1-{}): ", count); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + io::stdin() + .read_line(&mut input) + .expect("Failed to read input"); + + match input.trim().parse::() { + Ok(selected) if selected >= 1 && selected <= count => { + return conflict_list[selected - 1].clone(); + } + _ => { + println!("Invalid input. Please enter a number between 1 and {}.", count); + } + } + } +} + + +fn append_index_block(source: &Path, destination: &Path) -> io::Result<()> { + let source_components: Vec<_> = source.iter().collect(); + let base_system_folder = source_components[4].to_str().unwrap(); + + let index_conflict_path = Path::new("/pkg/gnu/sexpkg/etc/index-conflict.md"); + let content = fs::read_to_string(&index_conflict_path)?; + + let start_marker = format!("``` cfg *** {} ***", base_system_folder); + + let lines: Vec<&str> = content.lines().collect(); + let mut start_block_index = None; + let mut end_block_index = None; + + for (i, line) in lines.iter().enumerate() { + if line.contains(&start_marker) { + start_block_index = Some(i); + } else if start_block_index.is_some() && line.trim() == "```" { + end_block_index = Some(i); + break; + } + } + + let end_block_index = end_block_index.ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidData, "End block not found") + })?; + + let new_line = format!( + "{} {}", + destination.to_str().unwrap(), + source.to_str().unwrap() + ); + let mut new_content = String::new(); + for (i, line) in lines.iter().enumerate() { + if i == end_block_index { + new_content.push_str(&new_line); + new_content.push('\n'); + } + new_content.push_str(line); + new_content.push('\n'); + } + + let mut file = fs::File::create(&index_conflict_path)?; + file.write_all(new_content.as_bytes())?; + + Ok(()) +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index aa50896..04ea9bc 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,2 +1,4 @@ pub mod hardcopy; pub mod parser; +pub mod deletecopy; +pub mod shell; diff --git a/src/utils/parser.rs b/src/utils/parser.rs index aa8cdc6..5218c57 100644 --- a/src/utils/parser.rs +++ b/src/utils/parser.rs @@ -1,7 +1,7 @@ use std::fs; use std::env; use std::io::{self, BufRead}; -use std::path::Path; +use std::path::{Path,PathBuf}; pub fn get_name>(file_path: P) -> io::Result { @@ -131,6 +131,57 @@ pub fn get_use_status(repo: &str, dependency: &str) -> bool { } +pub fn get_index_conflict>(destination: P) -> io::Result { + let destination_path = destination.as_ref(); + + let parts: Vec<&str> = destination_path + .iter() + .map(|component| component.to_str().unwrap_or("")) + .collect(); + + if parts.len() < 4 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid destination path format", + )); + } + + let system_struct_folder = parts[3]; // bin, sbin, include, lib, share + + let etc = Path::new("/pkg/gnu/sexpkg/etc"); + let cfg_path = etc.join("index-conflict.md"); + + let start_marker = format!("``` cfg *** {} ***", system_struct_folder); + let end_marker = "```"; + + let block_content = extract_block(&cfg_path, &start_marker, end_marker)?; + + let destination_str = destination_path.to_str().ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidInput, "Failed to convert destination path to string") + })?; + + for line in block_content.lines() { + let trimmed_line = line.trim(); + if trimmed_line.starts_with(destination_str) { + let mut words = trimmed_line.split_whitespace(); + if let Some(_) = words.next() { + if let Some(path_source) = words.next() { + return Ok(PathBuf::from(path_source)); + } + } + } + } + + Err(io::Error::new( + io::ErrorKind::NotFound, + format!( + "No matching line found for destination: {}", + destination_path.display() + ), + )) +} + + fn read_first_line>(file_path: P) -> io::Result { let file = fs::File::open(file_path)?; let reader = io::BufReader::new(file); diff --git a/src/utils/shell.rs b/src/utils/shell.rs new file mode 100644 index 0000000..fa3f5a0 --- /dev/null +++ b/src/utils/shell.rs @@ -0,0 +1,73 @@ +use std::path::Path; +use std::process::Command; +use std::env; + +pub fn mount_overlay(path_repo: &Path) -> Result<(), String> { + let lowerdirs = vec![ + path_repo.join("bin"), + path_repo.join("sbin"), + ]; + + let lowerdir_str = lowerdirs.iter() + .map(|p| p.to_string_lossy()) + .collect::>() + .join(":"); + + let mounts = vec![ + ("/usr/bin", &lowerdir_str), + ("/usr/sbin", &lowerdir_str), + ("/bin", &lowerdir_str), + ("/sbin", &lowerdir_str), + ]; + + for (target, lowerdir) in mounts { + let output = Command::new("mount") + .arg("-t").arg("overlay") + .arg("overlay") + .arg("-o").arg(format!("lowerdir={}", lowerdir)) + .arg(target) + .output() + .map_err(|e| format!("Failed to execute mount command: {}", e))?; + + if !output.status.success() { + return Err(format!( + "Mount failed for target {}: {}", + target, + String::from_utf8_lossy(&output.stderr) + )); + } + } + + Ok(()) +} + + +pub fn shell_update() -> Result<(), String> { + let shell = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string()); + + let output_hash = Command::new(&shell) + .arg("-c") + .arg("hash -r") + .output() + .map_err(|e| format!("Failed to execute hash -r with shell {}: {}", shell, e))?; + + if !output_hash.status.success() { + return Err(format!( + "hash -r failed: {}", + String::from_utf8_lossy(&output_hash.stderr) + )); + } + + let output_ldconfig = Command::new("ldconfig") + .output() + .map_err(|e| format!("Failed to execute ldconfig: {}", e))?; + + if !output_ldconfig.status.success() { + return Err(format!( + "ldconfig failed: {}", + String::from_utf8_lossy(&output_ldconfig.stderr) + )); + } + + Ok(()) +}