Skip to content

Commit f3be59a

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 2e4c0d4 commit f3be59a

File tree

4 files changed

+197
-30
lines changed

4 files changed

+197
-30
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+
flate2.workspace = true
1314
log.workspace = true
1415
simplelog.workspace = true
1516
snafu.workspace = true

sources/driverdog/src/main.rs

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

18+
// Standard library imports
19+
use std::{
20+
collections::HashMap,
21+
ffi::OsStr,
22+
fs,
23+
io::Read,
24+
path::{Path, PathBuf},
25+
process::{self, Command},
26+
};
27+
28+
// External crate imports
1829
use argh::FromArgs;
30+
use flate2::read::GzDecoder;
1931
use serde::Deserialize;
2032
use simplelog::{Config as LogConfig, LevelFilter, SimpleLogger};
2133
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};
2734

2835
/// Path to the drivers configuration to use
2936
const DEFAULT_DRIVER_CONFIG_PATH: &str = "/etc/drivers/";
@@ -81,6 +88,50 @@ struct LinkModulesArgs {}
8188
#[argh(subcommand, name = "load-modules")]
8289
struct LoadModulesArgs {}
8390

91+
/// Checks if there is a compressed file at the path provided and if so, provides the path with the extension
92+
pub(crate) fn is_compressed(file_path: &Path) -> Option<PathBuf> {
93+
if !file_path.exists() {
94+
let compressed_path = file_path.with_extension("o.gz");
95+
if compressed_path.exists() {
96+
return Some(compressed_path);
97+
}
98+
}
99+
None
100+
}
101+
102+
/// Decompresses a file to the requested destination. The original file is left
103+
/// intact since it could be on a read-only filesystem.
104+
fn decompress_and_copy(file_path: &Path, destination: &Path) -> Result<()> {
105+
// Check if the file has .gz extension
106+
if let Some(extension) = file_path.extension() {
107+
if extension == "gz" {
108+
// Read compressed content using streaming decompression
109+
let compressed_file =
110+
fs::File::open(file_path).context(error::OpenFileSnafu { path: file_path })?;
111+
let mut decoder = GzDecoder::new(compressed_file);
112+
let mut decompressed_content = Vec::new();
113+
114+
decoder
115+
.read_to_end(&mut decompressed_content)
116+
.context(error::DecompressSnafu {
117+
from: file_path,
118+
to: file_path,
119+
})?;
120+
121+
// Write decompressed content to file without .gz extension
122+
fs::write(destination, &decompressed_content)
123+
.context(error::CreateFileSnafu { path: destination })?;
124+
125+
info!(
126+
"Decompressed {} to {}",
127+
file_path.display(),
128+
destination.display()
129+
);
130+
}
131+
}
132+
Ok(())
133+
}
134+
84135
#[derive(Deserialize, Debug)]
85136
#[serde(untagged)]
86137
/// Enum to hold the two types of configurations supported
@@ -219,10 +270,19 @@ where
219270
let object_file_path = build_dir.join(object_file);
220271
if !object_file_path.exists() {
221272
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-
})?;
273+
match is_compressed(&from) {
274+
Some(compressed_path) => {
275+
info!("Found compressed file: {:?}", compressed_path);
276+
decompress_and_copy(&compressed_path, &object_file_path)?;
277+
}
278+
None => {
279+
info!("Copying {:?}", &from);
280+
fs::copy(&from, &object_file_path).context(error::CopySnafu {
281+
from: &from,
282+
to: &object_file_path,
283+
})?;
284+
}
285+
}
226286
}
227287
dependencies_paths.push(object_file_path.to_string_lossy().into_owned());
228288
}
@@ -271,7 +331,7 @@ where
271331
.to_string_lossy()
272332
.into_owned();
273333
// Paths to the dependencies for this object file
274-
let mut dependencies = object_file
334+
let mut dependencies: Vec<String> = object_file
275335
.link_objects
276336
.iter()
277337
.map(|d| {
@@ -282,6 +342,21 @@ where
282342
})
283343
.collect();
284344

345+
for dependency in &mut dependencies {
346+
let dep_path = PathBuf::from(dependency.as_str());
347+
// If compressed, we need to decompress to a temporary path then shift the provided path to this new location
348+
if let Some(compressed_path) = is_compressed(&dep_path) {
349+
let uncompressed = build_dir.join(
350+
dep_path
351+
.file_name()
352+
.context(error::InvalidFileNameSnafu { path: &dep_path })?,
353+
);
354+
info!("Decompressing to {:?}", &uncompressed);
355+
decompress_and_copy(&compressed_path, &uncompressed)?;
356+
*dependency = uncompressed.to_string_lossy().into_owned();
357+
}
358+
}
359+
285360
// Link the object file
286361
let mut args = vec!["-r".to_string(), "-o".to_string(), object_path.clone()];
287362
args.append(&mut dependencies);
@@ -468,54 +543,105 @@ fn main() {
468543
}
469544

470545
/// <コ:ミ くコ:彡 <コ:ミ くコ:彡 <コ:ミ くコ:彡 <コ:ミ くコ:彡 <コ:ミ くコ:彡 <コ:ミ くコ:彡
546+
/// Error types for driverdog operations
471547
mod error {
472548
use snafu::Snafu;
473-
use std::path::PathBuf;
474-
use std::process::{Command, Output};
549+
use std::{
550+
path::PathBuf,
551+
process::{Command, Output},
552+
};
475553

476554
#[derive(Debug, Snafu)]
477555
#[snafu(visibility(pub(super)))]
478556
pub(super) enum Error {
479-
#[snafu(display("'{}' failed - stderr: {}",
480-
bin_path, String::from_utf8_lossy(&output.stderr)))]
557+
/// Command execution failed with non-zero exit status
558+
#[snafu(display(
559+
"'{}' failed - stderr: {}",
560+
bin_path,
561+
String::from_utf8_lossy(&output.stderr)
562+
))]
481563
CommandFailure { bin_path: String, output: Output },
482564

483-
#[snafu(display("Failed to copy '{}' to '{}': {}", from.display(), to.display(), source))]
565+
/// Failed to execute command due to system error
566+
#[snafu(display("Failed to execute '{:?}': {}", command, source))]
567+
ExecutionFailure {
568+
command: Box<Command>,
569+
source: std::io::Error,
570+
},
571+
572+
/// File copy operation failed
573+
#[snafu(display(
574+
"Failed to copy '{}' to '{}': {}",
575+
from.display(),
576+
to.display(),
577+
source
578+
))]
484579
Copy {
485580
from: PathBuf,
486581
to: PathBuf,
487582
source: std::io::Error,
488583
},
489584

490-
#[snafu(display("Failed to deserialize '{}': {}", path.display(), source))]
491-
Deserialize {
585+
/// Failed to read from filesystem path
586+
#[snafu(display("Failed to read path '{}': '{}'", path.display(), source))]
587+
ReadPath {
492588
path: PathBuf,
493-
source: toml::de::Error,
589+
source: std::io::Error,
494590
},
495591

496-
#[snafu(display("Failed to execute '{:?}': {}", command, source))]
497-
ExecutionFailure {
498-
command: Box<Command>,
592+
/// Failed to open file for reading
593+
#[snafu(display("Failed to open file '{}': {}", path.display(), source))]
594+
OpenFile {
595+
path: PathBuf,
596+
source: std::io::Error,
597+
},
598+
599+
/// Failed to create or write file
600+
#[snafu(display("Failed to create file '{}': {}", path.display(), source))]
601+
CreateFile {
602+
path: PathBuf,
499603
source: std::io::Error,
500604
},
501605

606+
/// TOML configuration deserialization failed
607+
#[snafu(display("Failed to deserialize '{}': {}", path.display(), source))]
608+
Deserialize {
609+
path: PathBuf,
610+
source: toml::de::Error,
611+
},
612+
613+
/// Module path contains invalid UTF-8 characters.
502614
#[snafu(display("Module path '{}' is not UTF-8", path.display()))]
503615
InvalidModulePath { path: PathBuf },
504616

617+
/// Requested module set not found in configuration.
618+
#[snafu(display("Missing module set '{}'", target))]
619+
MissingModuleSet { target: String },
620+
621+
/// Logger initialization failed
505622
#[snafu(display("Failed to setup logger: {}", source))]
506623
Logger { source: log::SetLoggerError },
507624

508-
#[snafu(display("Missing module set '{}'", target))]
509-
MissingModuleSet { target: String },
625+
/// Temporary directory creation failed.
626+
#[snafu(display("Failed to create temporary directory: {}", source))]
627+
TmpDir { source: std::io::Error },
510628

511-
#[snafu(display("Failed to read path '{}': '{}'", path.display(), source))]
512-
ReadPath {
513-
path: PathBuf,
629+
/// Gzip decompression operation failed.
630+
#[snafu(display(
631+
"Failed to decompress '{}' to '{}': {}",
632+
from.display(),
633+
to.display(),
634+
source
635+
))]
636+
Decompress {
637+
from: PathBuf,
638+
to: PathBuf,
514639
source: std::io::Error,
515640
},
516641

517-
#[snafu(display("Failed to create temporary directory: {}", source))]
518-
TmpDir { source: std::io::Error },
642+
/// File path has no filename component.
643+
#[snafu(display("Path '{}' has no filename", path.display()))]
644+
InvalidFileName { path: PathBuf },
519645
}
520646
}
521647

@@ -524,13 +650,52 @@ type Result<T> = std::result::Result<T, error::Error>;
524650
#[cfg(test)]
525651
mod test {
526652
use super::*;
653+
use std::fs::File;
527654
use std::path::PathBuf;
655+
use tempfile::TempDir;
528656
use walkdir::WalkDir;
529657

530658
fn test_data() -> PathBuf {
531659
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/tests")
532660
}
533661

662+
fn setup_compression_test_files() -> TempDir {
663+
let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
664+
665+
let regular_file_path = temp_dir.path().join("module.o");
666+
File::create(&regular_file_path).expect("Failed to create regular file");
667+
668+
let compressed_file_path = temp_dir.path().join("compressed_module.o.gz");
669+
File::create(&compressed_file_path).expect("Failed to create compressed file");
670+
temp_dir
671+
}
672+
673+
#[test]
674+
fn test_is_compressed_with_existing_file() {
675+
let temp_dir = setup_compression_test_files();
676+
let regular_file_path = temp_dir.path().join("module.o");
677+
let result = is_compressed(&regular_file_path);
678+
assert!(result.is_none());
679+
}
680+
681+
#[test]
682+
fn test_is_compressed_with_compressed_alternative() {
683+
let temp_dir = setup_compression_test_files();
684+
let non_existent_path = temp_dir.path().join("compressed_module.o");
685+
let compressed_path = temp_dir.path().join("compressed_module.o.gz");
686+
let result = is_compressed(&non_existent_path);
687+
assert!(result.is_some());
688+
assert_eq!(result.unwrap(), compressed_path);
689+
}
690+
691+
#[test]
692+
fn test_is_compressed_with_no_alternative() {
693+
let temp_dir = setup_compression_test_files();
694+
let non_existent_path = temp_dir.path().join("nonexistent.o");
695+
let result = is_compressed(&non_existent_path);
696+
assert!(result.is_none());
697+
}
698+
534699
#[test]
535700
fn parse_linking_config() {
536701
let driver_config_path = test_data();

0 commit comments

Comments
 (0)