@@ -15,15 +15,22 @@ modes iterate over the `kernel-modules` and load them from that path with `modpr
15
15
#[ macro_use]
16
16
extern crate log;
17
17
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
18
29
use argh:: FromArgs ;
30
+ use flate2:: read:: GzDecoder ;
19
31
use serde:: Deserialize ;
20
32
use simplelog:: { Config as LogConfig , LevelFilter , SimpleLogger } ;
21
33
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 } ;
27
34
28
35
/// Path to the drivers configuration to use
29
36
const DEFAULT_DRIVER_CONFIG_PATH : & str = "/etc/drivers/" ;
@@ -81,6 +88,50 @@ struct LinkModulesArgs {}
81
88
#[ argh( subcommand, name = "load-modules" ) ]
82
89
struct LoadModulesArgs { }
83
90
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
+
84
135
#[ derive( Deserialize , Debug ) ]
85
136
#[ serde( untagged) ]
86
137
/// Enum to hold the two types of configurations supported
@@ -219,10 +270,19 @@ where
219
270
let object_file_path = build_dir. join ( object_file) ;
220
271
if !object_file_path. exists ( ) {
221
272
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
+ }
226
286
}
227
287
dependencies_paths. push ( object_file_path. to_string_lossy ( ) . into_owned ( ) ) ;
228
288
}
@@ -271,7 +331,7 @@ where
271
331
. to_string_lossy ( )
272
332
. into_owned ( ) ;
273
333
// Paths to the dependencies for this object file
274
- let mut dependencies = object_file
334
+ let mut dependencies: Vec < String > = object_file
275
335
. link_objects
276
336
. iter ( )
277
337
. map ( |d| {
@@ -282,6 +342,21 @@ where
282
342
} )
283
343
. collect ( ) ;
284
344
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
+
285
360
// Link the object file
286
361
let mut args = vec ! [ "-r" . to_string( ) , "-o" . to_string( ) , object_path. clone( ) ] ;
287
362
args. append ( & mut dependencies) ;
@@ -468,54 +543,105 @@ fn main() {
468
543
}
469
544
470
545
/// <コ:ミ くコ:彡 <コ:ミ くコ:彡 <コ:ミ くコ:彡 <コ:ミ くコ:彡 <コ:ミ くコ:彡 <コ:ミ くコ:彡
546
+ /// Error types for driverdog operations
471
547
mod error {
472
548
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
+ } ;
475
553
476
554
#[ derive( Debug , Snafu ) ]
477
555
#[ snafu( visibility( pub ( super ) ) ) ]
478
556
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
+ ) ) ]
481
563
CommandFailure { bin_path : String , output : Output } ,
482
564
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
+ ) ) ]
484
579
Copy {
485
580
from : PathBuf ,
486
581
to : PathBuf ,
487
582
source : std:: io:: Error ,
488
583
} ,
489
584
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 {
492
588
path : PathBuf ,
493
- source : toml :: de :: Error ,
589
+ source : std :: io :: Error ,
494
590
} ,
495
591
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 ,
499
603
source : std:: io:: Error ,
500
604
} ,
501
605
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.
502
614
#[ snafu( display( "Module path '{}' is not UTF-8" , path. display( ) ) ) ]
503
615
InvalidModulePath { path : PathBuf } ,
504
616
617
+ /// Requested module set not found in configuration.
618
+ #[ snafu( display( "Missing module set '{}'" , target) ) ]
619
+ MissingModuleSet { target : String } ,
620
+
621
+ /// Logger initialization failed
505
622
#[ snafu( display( "Failed to setup logger: {}" , source) ) ]
506
623
Logger { source : log:: SetLoggerError } ,
507
624
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 } ,
510
628
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 ,
514
639
source : std:: io:: Error ,
515
640
} ,
516
641
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 } ,
519
645
}
520
646
}
521
647
@@ -524,13 +650,52 @@ type Result<T> = std::result::Result<T, error::Error>;
524
650
#[ cfg( test) ]
525
651
mod test {
526
652
use super :: * ;
653
+ use std:: fs:: File ;
527
654
use std:: path:: PathBuf ;
655
+ use tempfile:: TempDir ;
528
656
use walkdir:: WalkDir ;
529
657
530
658
fn test_data ( ) -> PathBuf {
531
659
PathBuf :: from ( env ! ( "CARGO_MANIFEST_DIR" ) ) . join ( "src/tests" )
532
660
}
533
661
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
+
534
699
#[ test]
535
700
fn parse_linking_config ( ) {
536
701
let driver_config_path = test_data ( ) ;
0 commit comments