@@ -15,15 +15,22 @@ modes iterate over the `kernel-modules` and load them from that path with `modpr
1515#[ macro_use]
1616extern 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
1829use argh:: FromArgs ;
30+ use flate2:: read:: GzDecoder ;
1931use serde:: Deserialize ;
2032use simplelog:: { Config as LogConfig , LevelFilter , SimpleLogger } ;
2133use 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
2936const DEFAULT_DRIVER_CONFIG_PATH : & str = "/etc/drivers/" ;
@@ -81,6 +88,50 @@ struct LinkModulesArgs {}
8188#[ argh( subcommand, name = "load-modules" ) ]
8289struct 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
471547mod 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) ]
525651mod 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