use crate::{Error, IspmMetadata, PackageArtifacts, PackageInfo, PackageSpec, Result};
use cargo_subcommand::Subcommand;
use flate2::{write::GzEncoder, Compression};
#[cfg(unix)]
use std::time::SystemTime;
use std::{
fs::write,
path::{Path, PathBuf},
};
use tar::{Builder, Header};
use typed_builder::TypedBuilder;
#[cfg(unix)]
pub const HOST_DIRNAME: &str = "linux64";
#[cfg(windows)]
pub const HOST_DIRNAME: &str = "win64";
#[derive(TypedBuilder, Debug, Clone)]
pub struct Package {
pub spec: PackageSpec,
pub target_profile_dir: PathBuf,
}
impl Package {
pub const INNER_PACKAGE_FILENAME: &'static str = "package.tar.gz";
pub const METADATA_FILENAME: &'static str = "ispm-metadata";
pub const ADDON_TYPE: &'static str = "addon";
pub const COMPRESSION_LEVEL: u32 = 6;
pub fn from_subcommand(subcommand: &Subcommand) -> Result<Self> {
let target_profile_dir = subcommand.build_dir(subcommand.target());
let spec = PackageSpec::from_subcommand(subcommand)?
.with_artifacts(&PackageArtifacts::from_subcommand(subcommand)?);
Ok(Self {
spec,
target_profile_dir,
})
}
pub fn package_dirname(&self) -> Result<String> {
if self.spec.typ == Self::ADDON_TYPE {
Ok(format!(
"simics-{}-{}",
self.spec.package_name, self.spec.version
))
} else {
Err(Error::NonAddonPackage)
}
}
pub fn full_package_name(&self) -> String {
format!("{}-{}", self.spec.package_name, self.spec.host)
}
pub fn package_name(&self) -> String {
format!(
"simics-pkg-{}-{}",
self.spec.package_number, self.spec.version
)
}
pub fn package_name_with_host(&self) -> String {
format!("{}-{}", self.package_name(), self.spec.host)
}
pub fn package_filename(&self) -> String {
format!("{}.ispm", self.package_name_with_host())
}
#[cfg(unix)]
pub fn set_header_common(header: &mut Header) -> Result<()> {
use libc::{getgid, getpwuid, getuid};
use std::ffi::CStr;
header.set_mtime(
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs(),
);
header.set_uid(unsafe { getuid() } as u64);
header.set_gid(unsafe { getgid() } as u64);
let username = unsafe {
CStr::from_ptr(
getpwuid(getuid())
.as_ref()
.ok_or_else(|| Error::PackageMetadataFieldNotFound {
field_name: "username".to_string(),
})?
.pw_name,
)
}
.to_str()?
.to_string();
let groupname = unsafe {
CStr::from_ptr(
getpwuid(getuid())
.as_ref()
.ok_or_else(|| Error::PackageMetadataFieldNotFound {
field_name: "groupname".to_string(),
})?
.pw_name,
)
}
.to_str()?
.to_string();
header.set_username(&username)?;
header.set_groupname(&groupname)?;
header.set_mode(0o755);
Ok(())
}
#[cfg(windows)]
pub fn set_header_common(_header: &mut Header) -> Result<()> {
Ok(())
}
pub fn create_inner_tarball(&self) -> Result<(Vec<u8>, usize)> {
let tar_gz = Vec::new();
let encoder = GzEncoder::new(tar_gz, Compression::new(Self::COMPRESSION_LEVEL));
let mut tar = Builder::new(encoder);
let mut uncompressed_size = 0;
let package_info = PackageInfo::from(&self.spec);
let package_info_string = serde_yaml::to_string(&package_info)? + &package_info.files();
let package_info_data = package_info_string.as_bytes();
uncompressed_size += package_info_data.len();
let mut metadata_header = Header::new_gnu();
metadata_header.set_size(package_info_data.len() as u64);
Self::set_header_common(&mut metadata_header)?;
tar.append_data(
&mut metadata_header,
PathBuf::from(self.package_dirname()?)
.join("packageinfo")
.join(self.full_package_name()),
package_info_data,
)?;
self.spec.files.iter().try_for_each(|(pkg_loc, src_loc)| {
let src_path = PathBuf::from(src_loc);
uncompressed_size += src_path.metadata()?.len() as usize;
tar.append_path_with_name(src_path, pkg_loc)?;
Ok::<(), Error>(())
})?;
tar.finish()?;
Ok((tar.into_inner()?.finish()?, uncompressed_size))
}
pub fn create_tarball(&self) -> Result<Vec<u8>> {
let tar_gz = Vec::new();
let encoder = GzEncoder::new(tar_gz, Compression::new(Self::COMPRESSION_LEVEL));
let mut tar = Builder::new(encoder);
let (inner_tarball, uncompressed_size) = self.create_inner_tarball()?;
let mut ispm_metadata = IspmMetadata::from(&self.spec);
ispm_metadata.uncompressed_size = uncompressed_size;
let ispm_metadata_string = serde_json::to_string(&ispm_metadata)?;
let ispm_metadata_data = ispm_metadata_string.as_bytes();
let mut ispm_metadata_header = Header::new_gnu();
ispm_metadata_header.set_size(ispm_metadata_data.len() as u64);
Self::set_header_common(&mut ispm_metadata_header)?;
tar.append_data(
&mut ispm_metadata_header,
Self::METADATA_FILENAME,
ispm_metadata_data,
)?;
let mut inner_tarball_header = Header::new_gnu();
inner_tarball_header.set_size(inner_tarball.len() as u64);
Self::set_header_common(&mut inner_tarball_header)?;
tar.append_data(
&mut inner_tarball_header,
Self::INNER_PACKAGE_FILENAME,
inner_tarball.as_slice(),
)?;
tar.finish()?;
Ok(tar.into_inner()?.finish()?)
}
pub fn build<P>(&mut self, output: P) -> Result<PathBuf>
where
P: AsRef<Path>,
{
let package_dirname = PathBuf::from(self.package_dirname()?);
self.spec.files.iter_mut().try_for_each(|pkg_src_loc| {
pkg_src_loc.0 = package_dirname
.join(&pkg_src_loc.0)
.to_str()
.ok_or_else(|| Error::PathConversionError {
path: package_dirname.join(&pkg_src_loc.0),
})?
.to_string();
Ok::<(), Error>(())
})?;
let tarball = self.create_tarball()?;
let path = output.as_ref().join(self.package_filename());
write(&path, tarball).map_err(|e| Error::WritePackageError {
path: path.clone(),
source: e,
})?;
Ok(path)
}
}