| // 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()); | |
| } | |
| } |