Skip to content

Commit

Permalink
fix: Fix a tar archive containing files with log path/linkname was no…
Browse files Browse the repository at this point in the history
…t handled correctly (Fix #6)
  • Loading branch information
nullpo-head committed Nov 14, 2021
1 parent 2fe6aef commit aeb96fd
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 64 deletions.
22 changes: 16 additions & 6 deletions distrod/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion distrod/distrod_wsl_launcher/Cargo.toml
Expand Up @@ -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"
Expand Down
95 changes: 38 additions & 57 deletions distrod/distrod_wsl_launcher/src/main.rs
Expand Up @@ -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};
Expand All @@ -20,6 +19,7 @@ use tempfile::tempdir;
use tempfile::TempDir;
use xz2::read::XzDecoder;

mod tar_helper;
mod wsl;

static DISTRO_NAME: &str = "Distrod";
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -254,63 +266,19 @@ fn merge_tar_archive<R: Read>(work_dir: &TempDir, mut rootfs: tar::Archive<R>) -
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<W, R, I, P>(
builder: &mut tar::Builder<W>,
archive: &mut tar::Archive<R>,
exclusion: I,
) -> Result<()>
where
W: std::io::Write,
R: std::io::Read,
I: IntoIterator<Item = P>,
P: AsRef<OsStr>,
{
let mut exclusion_set: HashSet<OsString> = 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<P: AsRef<Path>>(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() {
Expand Down Expand Up @@ -365,14 +333,27 @@ fn add_user(distro_name: &str, user_name: &str) -> Result<u32> {
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)
Expand Down
116 changes: 116 additions & 0 deletions distrod/distrod_wsl_launcher/src/tar_helper.rs
@@ -0,0 +1,116 @@
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<W, R, I, P>(
builder: &mut tar::Builder<W>,
archive: &mut tar::Archive<R>,
exclusion: I,
) -> Result<()>
where
W: std::io::Write,
R: std::io::Read,
I: IntoIterator<Item = P>,
P: AsRef<Path>,
{
let exclusion_set = HashSet::<OsString>::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<tar::Header> {
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<P: AsRef<Path>>(path: P) -> Vec<OsString> {
// 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
}
2 changes: 2 additions & 0 deletions distrod/libs/src/distro.rs
Expand Up @@ -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);
Expand Down

0 comments on commit aeb96fd

Please sign in to comment.