@@ -15,15 +15,21 @@ modes iterate over the `kernel-modules` and load them from that path with `modpr
1515#[ macro_use]
1616extern 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+
1828use argh:: FromArgs ;
29+ use early_boot_config_provider:: compression:: OptionalCompressionReader ;
1930use serde:: Deserialize ;
2031use simplelog:: { Config as LogConfig , LevelFilter , SimpleLogger } ;
2132use 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
2935const DEFAULT_DRIVER_CONFIG_PATH : & str = "/etc/drivers/" ;
@@ -81,6 +87,56 @@ struct LinkModulesArgs {}
8187#[ argh( subcommand, name = "load-modules" ) ]
8288struct 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
471548mod 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) ]
525638mod 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 ( ) ;
0 commit comments