Skip to content

Commit b7e9086

Browse files
committed
driverdog: Add ability to detect and decompress objects
The files that are linked at build time could be compressed which allows space savings on disk for a minor boot time increase. driverdog now can detect that the requested file may be compressed and have a .gz extension. If so, then it will decompress before working with the file. Signed-off-by: Matthew Yeazel <yeazelm@amazon.com>
1 parent 47398ec commit b7e9086

File tree

5 files changed

+231
-14
lines changed

5 files changed

+231
-14
lines changed

sources/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sources/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ derive-getters = "0.5"
131131
dns-lookup = "2"
132132
env_logger = "0.11"
133133
envy = "0.4"
134-
flate2 = { version = "1", default-features = false }
134+
flate2 = { version = "1" }
135135
fs2 = "0.4"
136136
futures = { version = "0.3", default-features = false }
137137
futures-channel = { version = "0.3", default-features = false }
@@ -193,7 +193,7 @@ syn = { version = "2", default-features = false }
193193
tar = { version = "0.4", default-features = false }
194194
tempfile = "3"
195195
test-case = "3"
196-
tokio = { version = "~1.43", default-features = false } # LTS
196+
tokio = { version = "~1.43", default-features = false } # LTS
197197
tokio-retry = "0.3"
198198
tokio-test = "0.4"
199199
tokio-tungstenite = { version = "0.20", default-features = false }

sources/driverdog/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ exclude = ["README.md"]
1010

1111
[dependencies]
1212
argh.workspace = true
13+
early-boot-config-provider.workspace = true
1314
log.workspace = true
1415
simplelog.workspace = true
1516
snafu.workspace = true

sources/driverdog/src/main.rs

Lines changed: 164 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,21 @@ modes iterate over the `kernel-modules` and load them from that path with `modpr
1515
#[macro_use]
1616
extern crate log;
1717

18+
use std::io;
19+
use std::{
20+
collections::HashMap,
21+
ffi::OsStr,
22+
fs,
23+
path::{Path, PathBuf},
24+
process::{self, Command},
25+
};
26+
use tempfile::NamedTempFile;
27+
1828
use argh::FromArgs;
29+
use early_boot_config_provider::compression::OptionalCompressionReader;
1930
use serde::Deserialize;
2031
use simplelog::{Config as LogConfig, LevelFilter, SimpleLogger};
2132
use snafu::{ensure, OptionExt, ResultExt};
22-
use std::collections::HashMap;
23-
use std::ffi::OsStr;
24-
use std::fs;
25-
use std::path::{Path, PathBuf};
26-
use std::process::{self, Command};
2733

2834
/// Path to the drivers configuration to use
2935
const DEFAULT_DRIVER_CONFIG_PATH: &str = "/etc/drivers/";
@@ -81,6 +87,56 @@ struct LinkModulesArgs {}
8187
#[argh(subcommand, name = "load-modules")]
8288
struct LoadModulesArgs {}
8389

90+
/// Checks if there is a compressed file at the path provided and if so, provides the path with the extension
91+
pub(crate) fn is_compressed(file_path: &Path) -> Option<PathBuf> {
92+
if !file_path.exists() {
93+
let compressed_path = file_path.with_extension("o.gz");
94+
if compressed_path.exists() {
95+
return Some(compressed_path);
96+
}
97+
}
98+
None
99+
}
100+
101+
/// Copies a file to the destination, decompressing if necessary.
102+
///
103+
/// Uses OptionalCompressionReader to automatically detect and decompress
104+
/// gzip-compressed files. The original file is left intact.
105+
fn copy_and_decompress(source_path: &Path, destination: &Path) -> Result<()> {
106+
let source_file =
107+
fs::File::open(source_path).context(error::OpenFileSnafu { path: source_path })?;
108+
109+
let mut reader = OptionalCompressionReader::new(source_file);
110+
111+
// Create a temporary file in the destination directory
112+
let destination_dir = destination
113+
.parent()
114+
.ok_or_else(|| error::Error::InvalidFileName {
115+
path: destination.to_path_buf(),
116+
})?;
117+
118+
let mut temp_file = NamedTempFile::new_in(destination_dir)
119+
.context(error::CreateFileSnafu { path: destination })?;
120+
121+
io::copy(&mut reader, &mut temp_file).context(error::DecompressSnafu {
122+
from: source_path,
123+
to: destination,
124+
})?;
125+
126+
// Atomically move the temporary file to the final destination
127+
temp_file
128+
.persist(destination)
129+
.context(error::PersistTempFileSnafu { path: destination })?;
130+
131+
info!(
132+
"Copied and decompressed {} to {}",
133+
source_path.display(),
134+
destination.display()
135+
);
136+
137+
Ok(())
138+
}
139+
84140
#[derive(Deserialize, Debug)]
85141
#[serde(untagged)]
86142
/// Enum to hold the two types of configurations supported
@@ -219,10 +275,16 @@ where
219275
let object_file_path = build_dir.join(object_file);
220276
if !object_file_path.exists() {
221277
let from = driver_path.join(object_file);
222-
fs::copy(&from, &object_file_path).context(error::CopySnafu {
223-
from: &from,
224-
to: &object_file_path,
225-
})?;
278+
match is_compressed(&from) {
279+
Some(compressed_path) => {
280+
info!("Found compressed file: {:?}", compressed_path);
281+
copy_and_decompress(&compressed_path, &object_file_path)?;
282+
}
283+
None => {
284+
info!("Copying {:?}", &from);
285+
copy_and_decompress(&from, &object_file_path)?;
286+
}
287+
}
226288
}
227289
dependencies_paths.push(object_file_path.to_string_lossy().into_owned());
228290
}
@@ -271,7 +333,7 @@ where
271333
.to_string_lossy()
272334
.into_owned();
273335
// Paths to the dependencies for this object file
274-
let mut dependencies = object_file
336+
let mut dependencies: Vec<String> = object_file
275337
.link_objects
276338
.iter()
277339
.map(|d| {
@@ -282,6 +344,20 @@ where
282344
})
283345
.collect();
284346

347+
for dependency in &mut dependencies {
348+
let dep_path = PathBuf::from(dependency.as_str());
349+
if let Some(compressed_path) = is_compressed(&dep_path) {
350+
let uncompressed = build_dir.join(
351+
dep_path
352+
.file_name()
353+
.context(error::InvalidFileNameSnafu { path: &dep_path })?,
354+
);
355+
info!("Decompressing to {:?}", &uncompressed);
356+
copy_and_decompress(&compressed_path, &uncompressed)?;
357+
*dependency = uncompressed.to_string_lossy().into_owned();
358+
}
359+
}
360+
285361
// Link the object file
286362
let mut args = vec!["-r".to_string(), "-o".to_string(), object_path.clone()];
287363
args.append(&mut dependencies);
@@ -468,10 +544,13 @@ fn main() {
468544
}
469545

470546
/// <コ:ミ くコ:彡 <コ:ミ くコ:彡 <コ:ミ くコ:彡 <コ:ミ くコ:彡 <コ:ミ くコ:彡 <コ:ミ くコ:彡
547+
/// Error types for driverdog operations
471548
mod error {
472549
use snafu::Snafu;
473-
use std::path::PathBuf;
474-
use std::process::{Command, Output};
550+
use std::{
551+
path::PathBuf,
552+
process::{Command, Output},
553+
};
475554

476555
#[derive(Debug, Snafu)]
477556
#[snafu(visibility(pub(super)))]
@@ -487,6 +566,24 @@ mod error {
487566
source: std::io::Error,
488567
},
489568

569+
#[snafu(display("Failed to create file '{}': {}", path.display(), source))]
570+
CreateFile {
571+
path: PathBuf,
572+
source: std::io::Error,
573+
},
574+
575+
#[snafu(display(
576+
"Failed to decompress '{}' to '{}': {}",
577+
from.display(),
578+
to.display(),
579+
source
580+
))]
581+
Decompress {
582+
from: PathBuf,
583+
to: PathBuf,
584+
source: std::io::Error,
585+
},
586+
490587
#[snafu(display("Failed to deserialize '{}': {}", path.display(), source))]
491588
Deserialize {
492589
path: PathBuf,
@@ -499,6 +596,16 @@ mod error {
499596
source: std::io::Error,
500597
},
501598

599+
#[snafu(display("Path '{}' has no filename", path.display()))]
600+
InvalidFileName { path: PathBuf },
601+
602+
/// Failed to move temporary file to final destination.
603+
#[snafu(display("Failed to move temporary file to {}: {}", path.display(), source))]
604+
PersistTempFile {
605+
path: PathBuf,
606+
source: tempfile::PersistError,
607+
},
608+
502609
#[snafu(display("Module path '{}' is not UTF-8", path.display()))]
503610
InvalidModulePath { path: PathBuf },
504611

@@ -514,6 +621,12 @@ mod error {
514621
source: std::io::Error,
515622
},
516623

624+
#[snafu(display("Failed to open file '{}': {}", path.display(), source))]
625+
OpenFile {
626+
path: PathBuf,
627+
source: std::io::Error,
628+
},
629+
517630
#[snafu(display("Failed to create temporary directory: {}", source))]
518631
TmpDir { source: std::io::Error },
519632
}
@@ -524,13 +637,52 @@ type Result<T> = std::result::Result<T, error::Error>;
524637
#[cfg(test)]
525638
mod test {
526639
use super::*;
640+
use std::fs::File;
527641
use std::path::PathBuf;
642+
use tempfile::TempDir;
528643
use walkdir::WalkDir;
529644

530645
fn test_data() -> PathBuf {
531646
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/tests")
532647
}
533648

649+
fn setup_compression_test_files() -> TempDir {
650+
let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
651+
652+
let regular_file_path = temp_dir.path().join("module.o");
653+
File::create(&regular_file_path).expect("Failed to create regular file");
654+
655+
let compressed_file_path = temp_dir.path().join("compressed_module.o.gz");
656+
File::create(&compressed_file_path).expect("Failed to create compressed file");
657+
temp_dir
658+
}
659+
660+
#[test]
661+
fn test_is_compressed_with_existing_file() {
662+
let temp_dir = setup_compression_test_files();
663+
let regular_file_path = temp_dir.path().join("module.o");
664+
let result = is_compressed(&regular_file_path);
665+
assert!(result.is_none());
666+
}
667+
668+
#[test]
669+
fn test_is_compressed_with_compressed_alternative() {
670+
let temp_dir = setup_compression_test_files();
671+
let non_existent_path = temp_dir.path().join("compressed_module.o");
672+
let compressed_path = temp_dir.path().join("compressed_module.o.gz");
673+
let result = is_compressed(&non_existent_path);
674+
assert!(result.is_some());
675+
assert_eq!(result.unwrap(), compressed_path);
676+
}
677+
678+
#[test]
679+
fn test_is_compressed_with_no_alternative() {
680+
let temp_dir = setup_compression_test_files();
681+
let non_existent_path = temp_dir.path().join("nonexistent.o");
682+
let result = is_compressed(&non_existent_path);
683+
assert!(result.is_none());
684+
}
685+
534686
#[test]
535687
fn parse_linking_config() {
536688
let driver_config_path = test_data();
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Unit tests for compression-related functionality
2+
3+
use std::fs::File;
4+
use std::io::Write;
5+
use std::path::PathBuf;
6+
use tempfile::TempDir;
7+
8+
use super::*;
9+
10+
/// Creates a temporary directory with test files for compression tests
11+
fn setup_test_files() -> TempDir {
12+
let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
13+
14+
// Create a regular object file
15+
let regular_file_path = temp_dir.path().join("module.o");
16+
File::create(&regular_file_path).expect("Failed to create regular file");
17+
18+
// Create a compressed object file
19+
let compressed_file_path = temp_dir.path().join("compressed_module.o.gz");
20+
File::create(&compressed_file_path).expect("Failed to create compressed file");
21+
22+
temp_dir
23+
}
24+
25+
#[test]
26+
fn test_is_compressed_with_existing_file() {
27+
let temp_dir = setup_test_files();
28+
29+
// Test with a file that exists
30+
let regular_file_path = temp_dir.path().join("module.o");
31+
let result = is_compressed(&regular_file_path);
32+
33+
// Should return None since the file exists and is not compressed
34+
assert!(result.is_none());
35+
}
36+
37+
#[test]
38+
fn test_is_compressed_with_compressed_alternative() {
39+
let temp_dir = setup_test_files();
40+
41+
// Test with a non-existent file that has a compressed alternative
42+
let non_existent_path = temp_dir.path().join("compressed_module.o");
43+
let compressed_path = temp_dir.path().join("compressed_module.o.gz");
44+
45+
let result = is_compressed(&non_existent_path);
46+
47+
// Should return Some with the compressed file path
48+
assert!(result.is_some());
49+
assert_eq!(result.unwrap(), compressed_path);
50+
}
51+
52+
#[test]
53+
fn test_is_compressed_with_no_alternative() {
54+
let temp_dir = setup_test_files();
55+
56+
// Test with a non-existent file that has no compressed alternative
57+
let non_existent_path = temp_dir.path().join("nonexistent.o");
58+
59+
let result = is_compressed(&non_existent_path);
60+
61+
// Should return None since neither the file nor a compressed alternative exists
62+
assert!(result.is_none());
63+
}

0 commit comments

Comments
 (0)