diff --git a/distrod/Cargo.lock b/distrod/Cargo.lock index a926fa5..426531d 100644 --- a/distrod/Cargo.lock +++ b/distrod/Cargo.lock @@ -236,7 +236,7 @@ dependencies = [ [[package]] name = "distrod" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", "chrono", @@ -252,7 +252,7 @@ dependencies = [ "serde", "serde_json", "structopt 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", - "tar", + "tar 0.4.37 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile", "tokio", "xz2", @@ -260,7 +260,7 @@ dependencies = [ [[package]] name = "distrod-exec" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", "env_logger", @@ -272,7 +272,7 @@ dependencies = [ [[package]] name = "distrod_wsl_launcher" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", "bytes", @@ -288,7 +288,7 @@ dependencies = [ "scraper", "structopt 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", "strum", - "tar", + "tar 0.4.37 (git+https://github.com/nullpo-head/tar-rs?branch=append_link)", "tempfile", "tokio", "windows", @@ -760,7 +760,7 @@ dependencies = [ "serde_json", "strum", "systemd-parser", - "tar", + "tar 0.4.37 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile", "toml", "tracing", @@ -1735,6 +1735,16 @@ dependencies = [ "xattr", ] +[[package]] +name = "tar" +version = "0.4.37" +source = "git+https://github.com/nullpo-head/tar-rs?branch=append_link#eacd0c909930685370fa8fd53bec7d16313b1274" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.2.0" diff --git a/distrod/distrod_wsl_launcher/Cargo.toml b/distrod/distrod_wsl_launcher/Cargo.toml index 6b5063f..43cfc7c 100644 --- a/distrod/distrod_wsl_launcher/Cargo.toml +++ b/distrod/distrod_wsl_launcher/Cargo.toml @@ -14,7 +14,7 @@ env_logger = "0.8" strum = { version = "0.20", features = ["derive"] } anyhow = "1.0" xz2 = "0.1" -tar = "0.4" +tar = { git = "https://github.com/nullpo-head/tar-rs", branch = "append_link" } flate2 = "1.0" indicatif = "0.16" chrono = "0.4" diff --git a/distrod/distrod_wsl_launcher/src/main.rs b/distrod/distrod_wsl_launcher/src/main.rs index 15daf7f..e8a95ac 100644 --- a/distrod/distrod_wsl_launcher/src/main.rs +++ b/distrod/distrod_wsl_launcher/src/main.rs @@ -9,8 +9,7 @@ use libs::distro_image::{ }; use libs::distrod_config; use libs::local_image::LocalDistroImage; -use std::collections::HashSet; -use std::ffi::{OsStr, OsString}; +use std::ffi::OsStr; use std::fs::File; use std::io::{self, BufReader, BufWriter, Cursor, Read}; use std::path::{Path, PathBuf}; @@ -20,6 +19,7 @@ use tempfile::tempdir; use tempfile::TempDir; use xz2::read::XzDecoder; +mod tar_helper; mod wsl; static DISTRO_NAME: &str = "Distrod"; @@ -155,6 +155,18 @@ You can install a local .tar.xz, or download an image from linuxcontainers.org. ); let tmp_dir = tempdir().with_context(|| "Failed to create a tempdir")?; let install_targz_path = merge_tar_archive(&tmp_dir, container_org_tar)?; + if let Ok(rootfs_save_path) = std::env::var("SAVE_ROOTFS") { + log::info!( + "Copying the rootfs to the specified path. {:?}", + &rootfs_save_path + ); + std::fs::copy(&install_targz_path, &rootfs_save_path).with_context(|| { + format!( + "Failed to copy the rootfs file to the specified path {:?}", + &rootfs_save_path + ) + })?; + } log::info!("Now Windows is installing the new distribution. This may take a while..."); register_distribution(distro_name, &install_targz_path) @@ -254,63 +266,19 @@ fn merge_tar_archive(work_dir: &TempDir, mut rootfs: tar::Archive) - let encoder = GzEncoder::new(install_targz, flate2::Compression::default()); let mut builder = tar::Builder::new(encoder); - append_tar_archive(&mut builder, &mut rootfs, vec!["/etc/resolv.conf"]) - .with_context(|| "Failed to merge the given image.")?; - append_tar_archive::<_, _, _, &str>(&mut builder, &mut distrod_tar, vec![]) + tar_helper::append_tar_archive::<_, _, _, &str>( + &mut builder, + &mut rootfs, + vec!["/etc/resolv.conf"], + ) + .with_context(|| "Failed to merge the given image.")?; + tar_helper::append_tar_archive::<_, _, _, &str>(&mut builder, &mut distrod_tar, vec![]) .with_context(|| "Failed to merge the given image.")?; builder.finish()?; drop(builder); // So that we can close the install_targz file. Ok(install_targz_path) } -fn append_tar_archive( - builder: &mut tar::Builder, - archive: &mut tar::Archive, - exclusion: I, -) -> Result<()> -where - W: std::io::Write, - R: std::io::Read, - I: IntoIterator, - P: AsRef, -{ - let mut exclusion_set: HashSet = HashSet::new(); - for path in exclusion { - let path = path.as_ref(); - exclusion_set.insert(path.to_os_string()); - // There are several ways to represent an absolute path. - let path = Path::new(path); - if let Ok(alt_path_rel) = path.strip_prefix("/") { - exclusion_set.insert(alt_path_rel.as_os_str().to_owned()); - let mut alt_path_dot = OsString::from("."); - alt_path_dot.push(path); - exclusion_set.insert(alt_path_dot.to_os_string()); - } - } - - for entry in archive - .entries() - .with_context(|| "Failed to read the entries of the archive.")? - { - let mut entry = entry?; - let path = entry.path()?.as_os_str().to_owned(); - if exclusion_set.contains(&path) { - continue; - } - let mut data = vec![]; - { - entry - .read_to_end(&mut data) - .with_context(|| format!("Failed to read the data of an entry: {:?}.", &path))?; - } - let header = entry.header(); - builder - .append(header, Cursor::new(data)) - .with_context(|| format!("Failed to add an entry to an archive. {:?}", path))?; - } - Ok(()) -} - fn register_distribution>(distro_name: &str, tar_gz_filename: P) -> Result<()> { // Install the distro by WSL API only when this app is a Windows Store app and --distro-name is not given. if distro_name == DISTRO_NAME && is_windows_store_app() { @@ -365,14 +333,27 @@ fn add_user(distro_name: &str, user_name: &str) -> Result { let mut user_add = wsl::WslCommand::new(Some("/bin/sh"), distro_name); user_add.arg("-c"); user_add.arg(format!( - "useradd -m --shell /bin/bash '{}' && while ! passwd {}; do : ; done && echo '{} ALL=(ALL:ALL) ALL' >> /etc/sudoers", - user_name, user_name, user_name - )); + "if ! command -v useradd > /dev/null; then \ + echo Error: no 'useradd' command found. exiting.; \ + exit 1; \ + fi; \ + useradd -m --shell /bin/bash '{}' && \ + if ! command -v passwd > /dev/null; then \ + echo no 'passwd' command found. exiting.; \ + exit 1; \ + fi; \ + while ! passwd {}; do : ; done && \ + echo '{} ALL=(ALL:ALL) ALL' >> /etc/sudoers", + user_name, user_name, user_name + )); let status = user_add .status() .with_context(|| "Failed to invoke user_add")?; if status != 0 { - bail!("user_add exited with error code {}", status); + bail!( + "The commands to add a user exited with error code {}", + status + ); } log::info!("Querying the generated uid. This may take some time depending on your machine."); query_uid(distro_name, user_name) diff --git a/distrod/distrod_wsl_launcher/src/tar_helper.rs b/distrod/distrod_wsl_launcher/src/tar_helper.rs new file mode 100644 index 0000000..18559bb --- /dev/null +++ b/distrod/distrod_wsl_launcher/src/tar_helper.rs @@ -0,0 +1,142 @@ +use anyhow::{Context, Result}; +use std::collections::HashSet; +use std::ffi::OsString; +use std::io::{Cursor, Read}; +use std::iter::FromIterator; +use std::path::Path; + +pub fn append_tar_archive( + builder: &mut tar::Builder, + archive: &mut tar::Archive, + exclusion: I, +) -> Result<()> +where + W: std::io::Write, + R: std::io::Read, + I: IntoIterator, + P: AsRef, +{ + let exclusion_set = HashSet::::from_iter( + exclusion + .into_iter() + .flat_map(|path| gen_name_candidates_for_path(path.as_ref())), + ); + + let entries = archive + .entries() + .with_context(|| "Failed to open the archive")?; + + for entry in entries { + let mut entry = entry.with_context(|| "An archive entry is an error.")?; + + let path = entry + .path() + .with_context(|| "Failed to get a path of a tar entry.")? + .as_os_str() + .to_os_string(); + if exclusion_set.contains(path.as_os_str()) { + log::debug!("skipping tar entry: {:?}", &path); + continue; + } + + let mut gnu_header = + to_gnu_header(entry.header()).unwrap_or_else(|| entry.header().clone()); + + if let Some(link_name) = entry + .link_name() + .with_context(|| format!("Failed to get the link_name {:?}", &path))? + { + builder + .append_link(&mut gnu_header, &path, link_name.as_os_str()) + .with_context(|| format!("Failed to append_link {:?}", &path))?; + } else { + let mut data = vec![]; + entry + .read_to_end(&mut data) + .with_context(|| format!("Failed to read the data of an entry: {:?}.", &path))?; + builder + .append_data(&mut gnu_header, &path, Cursor::new(data)) + .with_context(|| format!("Failed to add an entry to an archive. {:?}", &path))?; + } + } + Ok(()) +} + +fn to_gnu_header(header: &tar::Header) -> Option { + if header.as_gnu().is_some() { + return None; + } + if let Some(ustar) = header.as_ustar() { + return Some(convert_ustar_to_gnu_header(ustar)); + } + Some(convert_old_to_gnu_header(header.as_old())) +} + +fn convert_ustar_to_gnu_header(header: &tar::UstarHeader) -> tar::Header { + let mut gnu_header = convert_old_to_gnu_header(header.as_header().as_old()); + let as_gnu = gnu_header + .as_gnu_mut() + .expect("new_gnu should return a GNU header"); + + as_gnu.typeflag = header.typeflag; + as_gnu.uname = header.uname; + as_gnu.gname = header.gname; + as_gnu.dev_major = header.dev_major; + as_gnu.dev_minor = header.dev_minor; + gnu_header +} + +fn convert_old_to_gnu_header(header: &tar::OldHeader) -> tar::Header { + let mut gnu_header = tar::Header::new_gnu(); + let as_gnu = gnu_header + .as_gnu_mut() + .expect("new_gnu should return a GNU header"); + as_gnu.name = header.name; + as_gnu.mode = header.mode; + as_gnu.uid = header.uid; + as_gnu.gid = header.gid; + as_gnu.size = header.size; + as_gnu.mtime = header.mtime; + as_gnu.cksum = header.cksum; + as_gnu.linkname = header.linkname; + gnu_header +} + +fn gen_name_candidates_for_path>(path: P) -> Vec { + // There are several ways to represent an absolute path. + let mut candidates = vec![path.as_ref().as_os_str().to_os_string()]; + let path = path.as_ref(); + if let Ok(alt_path_rel) = path.strip_prefix("/") { + candidates.push(alt_path_rel.as_os_str().to_owned()); + let mut alt_path_dot = OsString::from("."); + alt_path_dot.push(path); + candidates.push(alt_path_dot.to_os_string()); + } + candidates +} + +#[test] +fn append_posix_tar() { + let appended_tar_buf = vec![]; + let mut builder = tar::Builder::new(&mut appended_tar_buf); + + let posix_tar_bytes = include_bytes!("../test_resources/tar_files/posix_tar.tar"); + let mut ar = tar::Archive::new(std::io::Cursor::new(posix_tar_bytes)); + + let entries = ar.entries().unwrap(); + for entry in entries { + let entry = entry.unwrap(); + append_tar_archive::<_, _, _, &str>(&mut builder, &mut ar, vec![]).unwrap(); + } + + let long_pathname = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let long_linkpathname = "link_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + let entry = entries.next().unwrap().unwrap(); + assert_eq!(entry.path().unwrap(), Path::new(long_pathname)); + let mut data = vec![]; + entry.read_to_end(&mut data).unwrap(); + assert_eq!(String::from_utf8(data).unwrap().as_str(), "test"); + + assert!(entries.next().is_none()); +} diff --git a/distrod/libs/src/distro.rs b/distrod/libs/src/distro.rs index e4c3248..0bf9509 100644 --- a/distrod/libs/src/distro.rs +++ b/distrod/libs/src/distro.rs @@ -655,6 +655,8 @@ fn disable_incompatible_systemd_services(rootfs: &HostPath) { "systemd-networkd.service", "systemd-resolved.service", "networking.service", + "fwupd-refresh.service", + "fwupd-refresh.timer", ]; for unit in &to_be_disabled { let disabler = SystemdUnitDisabler::new(&rootfs.as_path(), unit);