blob: 81c410aad01cb9e09f750b7ff5d8091be3d38517 [file] [log] [blame] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::{bail, Context, Result};
use log::{error, info};
use std::{
fs::File,
io::BufReader,
path::{Path, PathBuf},
process::Command,
};
use tar::Archive;
use xz2::bufread::XzDecoder;
/// Executes a command and logs its result. Returns an error in case something
/// goes wrong.
pub fn execute_command(mut command: Command) -> Result<()> {
info!("Executing command: {:?}", command);
match command.output() {
Ok(output) => {
if output.status.success() {
return Ok(());
}
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let code = output.status.code().unwrap_or(1);
error!(
"Command {command:?} failed with code {code}\nStdout:\n{stdout}\nStderr:\n{stderr}"
);
bail!("Unable to execute command: Got non-zero status code");
}
Err(err) => {
error!("Unable to execute command: {err}");
bail!(err);
}
}
}
/// Uncompresses a tar from `src` to `dst`. In this case `src` needs to point to
/// a tar archive and `dst` to a folder where the items are unpacked to. This
/// also returns an `Vec<PathBuf>` of the entries that have been successfully
/// unpacked to `dst`. Please note that these paths are relative to `dst“.
pub fn uncompress_tar_xz(src: &Path, dst: &Path) -> Result<Vec<PathBuf>> {
let file = File::open(src).context("Unable to open tar archive")?;
let xz_decoder = XzDecoder::new(BufReader::new(file));
let mut result: Vec<PathBuf> = vec![];
let mut archive = Archive::new(xz_decoder);
for entry in archive
.entries()
.context("Unable to access all contents of the tar")?
{
let mut entry = entry.context("Unable to read entry of the tar")?;
entry
.unpack_in(dst)
.context("Unable to unpack entry of the tar")?;
result.push(
entry
.path()
.context("Unable to get tar entries path")?
.to_path_buf(),
);
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
const FILE_CONTENTS: &[u8] = b"Hello World!";
const FILE_NAME: &str = "foo.txt";
const TAR_NAME: &str = "foo.tar.xz";
fn setup_tar_xz() -> Result<tempfile::TempDir> {
// First setup a tempdir.
let tempdir = tempfile::tempdir()?;
// Next create a file in there.
let file_path = tempdir.path().join(FILE_NAME);
std::fs::write(file_path, FILE_CONTENTS)?;
// Now create a tar.xz of that file.
let mut tar_cmd = Command::new("tar");
// Tell tar to (c)ompress an xz (J) of a (f)ile.
tar_cmd.arg("-cJf").arg(tempdir.path().join(TAR_NAME));
// Change dir to the temp path so that that the file is added
// without a directory prefix.
tar_cmd.arg("-C");
tar_cmd.arg(tempdir.path());
// We want to compress the newly created file.
tar_cmd.arg(FILE_NAME);
execute_command(tar_cmd)?;
Ok(tempdir)
}
#[test]
fn test_uncompress_tar_xz() -> Result<()> {
let tempdir = setup_tar_xz()?;
// Create a new dir where we uncompress to.
let new_dir_path = tempdir.path().join("uncompressed");
std::fs::create_dir(&new_dir_path)?;
// Uncompress the file.
let file_path = tempdir.path().join(TAR_NAME);
let result = uncompress_tar_xz(&file_path, &new_dir_path)?;
// Compare for equality.
assert_eq!(result, vec![Path::new(FILE_NAME)]);
let buf = std::fs::read(new_dir_path.join(&result[0]))?;
assert_eq!(&buf, FILE_CONTENTS);
Ok(())
}
#[test]
fn test_execute_bad_commands() {
// This fails even before executing the command because it doesn't exist.
let result = execute_command(Command::new("/this/does/not/exist"));
assert!(result.is_err());
// This fails due to a bad status code of the command.
let result = execute_command(Command::new("false"));
assert!(result.is_err());
// This succeeds.
let result = execute_command(Command::new("ls"));
assert!(result.is_ok());
}
}