use crate::dist;
use std::fs;
use std::io;
use std::path::{Component, Path, PathBuf};
use std::str;
use crate::errors::*;
pub use self::toolchain_imp::*;
pub trait ToolchainPackager: Send {
fn write_pkg(self: Box<Self>, f: fs::File) -> Result<()>;
}
pub trait InputsPackager: Send {
fn write_inputs(self: Box<Self>, wtr: &mut dyn io::Write) -> Result<dist::PathTransformer>;
}
pub trait OutputsRepackager {
fn repackage_outputs(self: Box<Self>, wtr: &mut dyn io::Write)
-> Result<dist::PathTransformer>;
}
#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
mod toolchain_imp {
use super::ToolchainPackager;
use std::fs;
use crate::errors::*;
impl<T: Send> ToolchainPackager for T {
fn write_pkg(self: Box<Self>, _f: fs::File) -> Result<()> {
bail!("Automatic packaging not supported on this platform")
}
}
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
mod toolchain_imp {
use super::tarify_path;
use std::collections::BTreeMap;
use std::fs;
use std::io::{Read, Write};
use std::path::{Component, Path, PathBuf};
use std::process;
use std::str;
use walkdir::WalkDir;
use crate::errors::*;
pub struct ToolchainPackageBuilder {
dir_set: BTreeMap<PathBuf, PathBuf>,
file_set: BTreeMap<PathBuf, PathBuf>,
}
impl ToolchainPackageBuilder {
pub fn new() -> Self {
ToolchainPackageBuilder {
dir_set: BTreeMap::new(),
file_set: BTreeMap::new(),
}
}
pub fn add_common(&mut self) -> Result<()> {
self.add_dir(PathBuf::from("/tmp"))
}
pub fn add_executable_and_deps(&mut self, executable: PathBuf) -> Result<()> {
let mut remaining = vec![executable];
while let Some(obj_path) = remaining.pop() {
assert!(obj_path.is_absolute());
let tar_path = tarify_path(&obj_path)?;
if self.file_set.contains_key(&tar_path) {
continue;
}
let ldd_libraries = find_ldd_libraries(&obj_path).with_context(|| {
format!("Failed to analyse {} with ldd", obj_path.display())
})?;
remaining.extend(ldd_libraries);
self.file_set.insert(tar_path, obj_path);
}
Ok(())
}
pub fn add_dir(&mut self, dir_path: PathBuf) -> Result<()> {
assert!(dir_path.is_absolute());
if !dir_path.is_dir() {
bail!(format!(
"{} was not a dir when readying for tar",
dir_path.to_string_lossy()
))
}
if dir_path
.components()
.next_back()
.expect("asserted absolute")
== Component::RootDir
{
return Ok(());
}
let tar_path = tarify_path(&dir_path)?;
self.dir_set.insert(tar_path, dir_path);
Ok(())
}
pub fn add_file(&mut self, file_path: PathBuf) -> Result<()> {
assert!(file_path.is_absolute());
if !file_path.is_file() {
bail!(format!(
"{} was not a file when readying for tar",
file_path.to_string_lossy()
))
}
let tar_path = tarify_path(&file_path)?;
self.file_set.insert(tar_path, file_path);
Ok(())
}
pub fn add_dir_contents(&mut self, dir_path: &Path) -> Result<()> {
for entry in WalkDir::new(&dir_path).follow_links(false) {
let entry = entry?;
let file_type = entry.file_type();
if file_type.is_dir() {
continue;
} else if file_type.is_symlink() {
let metadata = fs::metadata(entry.path())?;
if !metadata.file_type().is_file() {
continue;
}
} else if !file_type.is_file() {
continue;
}
trace!("walkdir add_file {}", entry.path().display());
self.add_file(entry.path().to_owned())?
}
Ok(())
}
pub fn into_compressed_tar<W: Write + Send + 'static>(self, writer: W) -> Result<()> {
use gzp::{
deflate::Gzip,
par::compress::{Compression, ParCompress, ParCompressBuilder},
};
let ToolchainPackageBuilder { dir_set, file_set } = self;
let par: ParCompress<Gzip> = ParCompressBuilder::new()
.compression_level(Compression::default())
.from_writer(writer);
let mut builder = tar::Builder::new(par);
for (tar_path, dir_path) in dir_set.into_iter() {
builder.append_dir(tar_path, dir_path)?
}
for (tar_path, file_path) in file_set.into_iter() {
let file = &mut fs::File::open(file_path)?;
builder.append_file(tar_path, file)?
}
builder.finish().map_err(Into::into)
}
}
fn find_ldd_libraries(executable: &Path) -> Result<Vec<PathBuf>> {
let process::Output {
status,
stdout,
stderr,
} = process::Command::new("ldd").arg(executable).output()?;
if !status.success() {
let mut elf = fs::File::open(executable)?;
let mut elf_bytes = [0; 0x12];
elf.read_exact(&mut elf_bytes)?;
if elf_bytes[..0x4] != [0x7f, 0x45, 0x4c, 0x46] {
bail!("Elf magic not found")
}
let little_endian = match elf_bytes[0x5] {
1 => true,
2 => false,
_ => bail!("Invalid endianness in elf header"),
};
let e_type = if little_endian {
(elf_bytes[0x11] as u16) << 8 | elf_bytes[0x10] as u16
} else {
(elf_bytes[0x10] as u16) << 8 | elf_bytes[0x11] as u16
};
if e_type != 0x02 {
bail!("ldd failed on a non-ET_EXEC elf")
}
return Ok(vec![]);
}
if !stderr.is_empty() {
trace!(
"ldd stderr non-empty: {:?}",
String::from_utf8_lossy(&stderr)
)
}
let stdout = str::from_utf8(&stdout).context("ldd output not utf8")?;
Ok(parse_ldd_output(stdout))
}
fn parse_ldd_output(stdout: &str) -> Vec<PathBuf> {
let mut libs = vec![];
for line in stdout.lines() {
let line = line.trim();
let mut parts: Vec<_> = line.split_whitespace().collect();
match parts.pop() {
Some(s) if s.starts_with('(') && s.ends_with(')') => (),
Some(_) | None => continue,
}
if parts.len() > 3 {
continue;
}
let libpath = match (parts.first(), parts.get(1), parts.get(2)) {
(Some(_libname), Some(&"=>"), None) => continue,
(Some(libname), Some(&"=>"), Some(libpath)) => {
let libname_path = PathBuf::from(libname);
if libname_path.is_absolute() && libname_path.exists() {
libs.push(libname_path)
}
PathBuf::from(libpath)
}
(Some(libpath), None, None) => PathBuf::from(libpath),
_ => continue,
};
if !libpath.is_absolute() {
continue;
}
libs.push(libpath)
}
libs
}
#[test]
fn test_ldd_parse() {
let ubuntu_ls_output = "\tlinux-vdso.so.1 => (0x00007fffcfffe000)
\tlibselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f69caa6b000)
\tlibc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f69ca6a1000)
\tlibpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f69ca431000)
\tlibdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f69ca22d000)
\t/lib64/ld-linux-x86-64.so.2 (0x00007f69cac8d000)
\tlibpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f69ca010000)
";
assert_eq!(
parse_ldd_output(ubuntu_ls_output)
.iter()
.map(|p| p.to_str().unwrap())
.collect::<Vec<_>>(),
&[
"/lib/x86_64-linux-gnu/libselinux.so.1",
"/lib/x86_64-linux-gnu/libc.so.6",
"/lib/x86_64-linux-gnu/libpcre.so.3",
"/lib/x86_64-linux-gnu/libdl.so.2",
"/lib64/ld-linux-x86-64.so.2",
"/lib/x86_64-linux-gnu/libpthread.so.0",
]
)
}
#[test]
fn test_ldd_parse_static() {
let static_outputs = &[
"\tstatically linked", "\tldd (0x7f79ef662000)", ];
for static_output in static_outputs {
assert_eq!(parse_ldd_output(static_output).len(), 0)
}
}
#[test]
fn test_ldd_parse_v2_30() {
let archlinux_ls_output = "\tlinux-vdso.so.1 (0x00007ffddc1f6000)
\tlibcap.so.2 => /usr/lib/libcap.so.2 (0x00007f4980989000)
\tlibc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f69ca6a1000)
\tlibc.so.6 => /usr/lib/libc.so.6 (0x00007f49807c2000)
\t/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f49809e9000)
";
assert_eq!(
parse_ldd_output(archlinux_ls_output)
.iter()
.map(|p| p.to_str().unwrap())
.collect::<Vec<_>>(),
&[
"/usr/lib/libcap.so.2",
"/lib/x86_64-linux-gnu/libc.so.6",
"/usr/lib/libc.so.6",
"/lib64/ld-linux-x86-64.so.2",
"/usr/lib64/ld-linux-x86-64.so.2",
]
)
}
}
pub fn make_tar_header(src: &Path, dest: &str) -> io::Result<tar::Header> {
let metadata_res = fs::metadata(&src);
let mut file_header = tar::Header::new_ustar();
if let Ok(metadata) = metadata_res {
file_header.set_metadata(&metadata);
} else {
warn!(
"Couldn't get metadata of file {:?}, falling back to some defaults",
src
);
file_header.set_mode(0o644);
file_header.set_uid(0);
file_header.set_gid(0);
file_header.set_mtime(0);
file_header
.set_device_major(0)
.expect("expected a ustar header");
file_header
.set_device_minor(0)
.expect("expected a ustar header");
file_header.set_entry_type(tar::EntryType::file());
}
assert!(dest.starts_with('/'));
let dest = dest.trim_start_matches('/');
assert!(!dest.starts_with('/'));
file_header.set_path(&dest)?;
Ok(file_header)
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn tarify_path(path: &Path) -> Result<PathBuf> {
let final_path = simplify_path(path)?;
let mut components = final_path.components();
assert_eq!(components.next(), Some(Component::RootDir));
Ok(components.as_path().to_owned())
}
pub fn simplify_path(path: &Path) -> Result<PathBuf> {
let mut final_path = PathBuf::new();
for component in path.components() {
match component {
c @ Component::RootDir | c @ Component::Prefix(_) | c @ Component::Normal(_) => {
final_path.push(c)
}
Component::ParentDir => {
let is_symlink = fs::symlink_metadata(&final_path)
.context("Missing directory while simplifying path")?
.file_type()
.is_symlink();
if is_symlink {
bail!("Cannot handle symlinks in parent paths")
}
final_path.pop();
}
Component::CurDir => continue,
}
}
Ok(final_path)
}