@@ -56,7 +56,7 @@ struct DownloadContext {
5656struct RawDiskDownloadParams {
5757 http_tx : ByteBoundedSender < bytes:: Bytes > ,
5858 writer_handle : tokio:: task:: JoinHandle < Result < u64 , std:: io:: Error > > ,
59- external_decompressor : Option < tokio :: process :: Child > ,
59+ is_compressed : bool ,
6060 decompressed_progress_rx : mpsc:: UnboundedReceiver < u64 > ,
6161 raw_written_progress_rx : mpsc:: UnboundedReceiver < u64 > ,
6262}
@@ -69,12 +69,6 @@ struct ProcessingHandles {
6969 tar_extractor_handle : tokio:: task:: JoinHandle < Result < ( ) , String > > ,
7070}
7171
72- /// Components returned by external decompressor pipeline setup
73- struct ExternalDecompressorPipeline {
74- writer_handle : tokio:: task:: JoinHandle < Result < u64 , std:: io:: Error > > ,
75- decompressor : tokio:: process:: Child ,
76- }
77-
7872/// Components returned by pipeline setup
7973struct TarPipelineComponents {
8074 http_tx : ByteBoundedSender < bytes:: Bytes > ,
@@ -1423,159 +1417,36 @@ async fn coordinate_download_and_processing(
14231417 Ok ( ( ) )
14241418}
14251419
1426- /// Setup external decompressor pipeline for XZ compression
1427- async fn setup_external_decompressor_pipeline (
1428- http_rx : ByteBoundedReceiver < bytes:: Bytes > ,
1429- block_writer : AsyncBlockWriter ,
1430- decompressed_progress_tx : mpsc:: UnboundedSender < u64 > ,
1431- debug : bool ,
1432- ) -> Result < ExternalDecompressorPipeline , Box < dyn std:: error:: Error > > {
1433- // XZ: Use external xzcat process
1434- let ( mut decompressor, decompressor_name) = start_decompressor_process ( "disk.img.xz" ) . await ?;
1435-
1436- let decompressor_stdin = decompressor. stdin . take ( ) . unwrap ( ) ;
1437- let decompressor_stdout = decompressor. stdout . take ( ) . unwrap ( ) ;
1438- let decompressor_stderr = decompressor. stderr . take ( ) . unwrap ( ) ;
1439-
1440- let ( error_tx, error_rx) = mpsc:: unbounded_channel :: < String > ( ) ;
1441-
1442- // Spawn stderr reader
1443- tokio:: spawn ( spawn_stderr_reader (
1444- decompressor_stderr,
1445- error_tx. clone ( ) ,
1446- decompressor_name,
1447- ) ) ;
1448-
1449- // Spawn error processor
1450- tokio:: spawn ( process_error_messages ( error_rx) ) ;
1451-
1452- // Spawn blocking task: read from channel and write to xzcat stdin
1453- // First, create a sync file handle from the async stdin
1454- #[ cfg( unix) ]
1455- let stdin_fd = {
1456- use std:: os:: unix:: io:: { AsRawFd , FromRawFd } ;
1457- let raw_fd = decompressor_stdin. as_raw_fd ( ) ;
1458- // Duplicate the fd so we can use it in blocking context
1459- let dup_fd = unsafe { libc:: dup ( raw_fd) } ;
1460- if dup_fd == -1 {
1461- return Err ( std:: io:: Error :: last_os_error ( ) . into ( ) ) ;
1462- }
1463- // SAFETY: dup_fd is a valid file descriptor (we checked above)
1464- unsafe { std:: fs:: File :: from_raw_fd ( dup_fd) }
1465- } ;
1466- #[ cfg( not( unix) ) ]
1467- let stdin_fd: std:: fs:: File = {
1468- return Err ( "XZ streaming decompression is not supported on non-unix platforms" . into ( ) ) ;
1469- } ;
1470-
1471- // Drop the original async stdin (the dup'd fd still points to the pipe)
1472- drop ( decompressor_stdin) ;
1473-
1474- let stdin_writer_handle = {
1475- tokio:: task:: spawn_blocking ( move || {
1476- use std:: io:: Write as _;
1477- let reader = ChannelReader :: new_byte_bounded ( http_rx) ;
1478- let mut reader = reader;
1479- let mut stdin = stdin_fd;
1480- let mut buffer = vec ! [ 0u8 ; 1024 * 1024 ] ; // 1MB chunks
1481-
1482- loop {
1483- match reader. read ( & mut buffer) {
1484- Ok ( 0 ) => break , // EOF
1485- Ok ( n) => {
1486- if let Err ( e) = stdin. write_all ( & buffer[ ..n] ) {
1487- return Err ( format ! ( "Error writing to xzcat: {}" , e) ) ;
1488- }
1489- }
1490- Err ( e) => return Err ( format ! ( "Error reading stream: {}" , e) ) ,
1491- }
1492- }
1493-
1494- drop ( stdin) ;
1495- Ok :: < ( ) , String > ( ( ) )
1496- } )
1497- } ;
1498-
1499- // Spawn task: xzcat stdout -> block writer with sparse detection
1500- let writer = block_writer;
1501- let progress_tx = decompressed_progress_tx;
1502- let writer_handle = tokio:: spawn ( async move {
1503- let mut stdout = decompressor_stdout;
1504- let mut buffer = vec ! [ 0u8 ; 8 * 1024 * 1024 ] ; // 8MB buffer
1505-
1506- // Auto-detect sparse image format from initial data
1507- let mut detector = FormatDetector :: new ( ) ;
1508- let mut parser: Option < SparseParser > = None ;
1509- let mut format_determined = false ;
1510-
1511- loop {
1512- let n = match tokio:: io:: AsyncReadExt :: read ( & mut stdout, & mut buffer) . await {
1513- Ok ( 0 ) => {
1514- finalize_format_at_eof ( & mut detector, format_determined, & writer, debug)
1515- . await ?;
1516- break ;
1517- }
1518- Ok ( n) => n,
1519- Err ( e) => return Err ( e) ,
1520- } ;
1521-
1522- let _ = progress_tx. send ( n as u64 ) ;
1523-
1524- process_buffer_with_format_detection (
1525- & buffer,
1526- n,
1527- & mut detector,
1528- & mut parser,
1529- & mut format_determined,
1530- & writer,
1531- debug,
1532- )
1533- . await ?;
1534- }
1535-
1536- // Wait for stdin writer to finish and propagate any errors
1537- stdin_writer_handle
1538- . await
1539- . map_err ( |e| std:: io:: Error :: other ( format ! ( "Stdin writer task failed: {}" , e) ) ) ?
1540- . map_err ( |e| std:: io:: Error :: other ( format ! ( "Stdin writer error: {}" , e) ) ) ?;
1541-
1542- writer. close ( ) . await
1543- } ) ;
1544-
1545- Ok ( ExternalDecompressorPipeline {
1546- writer_handle,
1547- decompressor,
1548- } )
1549- }
1550-
1551- /// Setup in-process decompression pipeline for Gzip or None compression
15521420async fn setup_inprocess_decompression_pipeline (
15531421 http_rx : ByteBoundedReceiver < bytes:: Bytes > ,
15541422 block_writer : AsyncBlockWriter ,
15551423 decompressed_progress_tx : mpsc:: UnboundedSender < u64 > ,
15561424 compression_type : Compression ,
15571425 debug : bool ,
1426+ xz_memlimit_mb : u64 ,
15581427) -> Result < tokio:: task:: JoinHandle < Result < u64 , std:: io:: Error > > , Box < dyn std:: error:: Error > > {
1559- // Gzip or None: decompress in-process and write directly to block writer
15601428 let writer = block_writer;
15611429 let progress_tx = decompressed_progress_tx;
15621430
1563- // Create an async channel for decompressed data
15641431 let ( data_tx, mut data_rx) = mpsc:: channel :: < Vec < u8 > > ( 16 ) ;
15651432
1566- // Spawn blocking task: read, decompress, send to async channel
15671433 let reader_handle = tokio:: task:: spawn_blocking ( move || {
15681434 let reader = ChannelReader :: new_byte_bounded ( http_rx) ;
15691435
1570- // Apply in-process gzip decompression if needed
15711436 let processed_reader: Box < dyn std:: io:: Read + Send > = match compression_type {
15721437 Compression :: Gzip => {
15731438 if debug {
15741439 eprintln ! ( "[DEBUG] Applying in-process gzip decompression" ) ;
15751440 }
15761441 Box :: new ( flate2:: read:: GzDecoder :: new ( reader) )
15771442 }
1578- _ => Box :: new ( reader) ,
1443+ Compression :: Xz => {
1444+ crate :: fls:: decompress:: create_mt_xz_decoder ( reader, xz_memlimit_mb) ?
1445+ }
1446+ Compression :: None => Box :: new ( reader) ,
1447+ Compression :: Zstd => {
1448+ return Err ( "Zstd in-process decompression is not supported" . to_string ( ) ) ;
1449+ }
15791450 } ;
15801451
15811452 let mut reader = processed_reader;
@@ -1657,7 +1528,7 @@ async fn coordinate_raw_disk_download(
16571528 let mut progress =
16581529 ProgressTracker :: new ( options. common . newline_progress , options. common . show_memory ) ;
16591530 progress. set_content_length ( Some ( layer_size) ) ;
1660- progress. set_is_compressed ( params. external_decompressor . is_some ( ) ) ;
1531+ progress. set_is_compressed ( params. is_compressed ) ;
16611532 progress. bytes_received = initial_buffer. len ( ) as u64 ;
16621533 let update_interval = Duration :: from_secs_f64 ( options. common . progress_interval_secs ) ;
16631534
@@ -1772,24 +1643,6 @@ async fn coordinate_raw_disk_download(
17721643 progress. decompress_duration = Some ( elapsed) ;
17731644 progress. write_duration = Some ( elapsed) ;
17741645
1775- // Wait for external decompressor process and check exit status
1776- if let Some ( mut decompressor) = params. external_decompressor {
1777- match decompressor. wait ( ) . await {
1778- Ok ( status) => {
1779- if !status. success ( ) {
1780- return Err ( format ! (
1781- "Decompressor process failed with exit code: {:?}" ,
1782- status. code( )
1783- )
1784- . into ( ) ) ;
1785- }
1786- }
1787- Err ( e) => {
1788- return Err ( format ! ( "Failed to wait for decompressor process: {}" , e) . into ( ) ) ;
1789- }
1790- }
1791- }
1792-
17931646 // Final progress update
17941647 let _ = progress. update_progress ( Some ( layer_size) , update_interval, true ) ;
17951648
@@ -2520,48 +2373,31 @@ async fn flash_raw_disk_image_directly(
25202373 byte_bounded_channel :: < bytes:: Bytes > ( max_buffer_bytes, buffer_capacity) ;
25212374 let ( decompressed_progress_tx, decompressed_progress_rx) = mpsc:: unbounded_channel :: < u64 > ( ) ;
25222375
2523- // For gzip and none, we can decompress in-process and write directly to block writer
2524- // For XZ, we need the external xzcat process
2525- let needs_external_decompressor = compression_type == Compression :: Xz ;
2376+ if compression_type == Compression :: Zstd {
2377+ return Err ( "Zstd in-process decompression is not supported" . into ( ) ) ;
2378+ }
25262379
25272380 if options. common . debug {
25282381 eprintln ! (
2529- "[DEBUG] Compression type: {:?}, using external decompressor: {} " ,
2530- compression_type, needs_external_decompressor
2382+ "[DEBUG] Compression type: {:?}, using in-process decompressor" ,
2383+ compression_type
25312384 ) ;
25322385 }
25332386
2534- // Spawn the processing pipeline based on compression type
2535- let ( writer_handle, external_decompressor) = if needs_external_decompressor {
2536- // XZ: Use external xzcat process
2537- let pipeline = setup_external_decompressor_pipeline (
2538- http_rx,
2539- block_writer,
2540- decompressed_progress_tx,
2541- options. common . debug ,
2542- )
2543- . await ?;
2544-
2545- ( pipeline. writer_handle , Some ( pipeline. decompressor ) )
2546- } else {
2547- // Gzip or None: decompress in-process and write directly to block writer
2548- let handle = setup_inprocess_decompression_pipeline (
2549- http_rx,
2550- block_writer,
2551- decompressed_progress_tx,
2552- compression_type,
2553- options. common . debug ,
2554- )
2555- . await ?;
2556-
2557- ( handle, None )
2558- } ;
2387+ let writer_handle = setup_inprocess_decompression_pipeline (
2388+ http_rx,
2389+ block_writer,
2390+ decompressed_progress_tx,
2391+ compression_type,
2392+ options. common . debug ,
2393+ options. common . xz_memlimit_mb ,
2394+ )
2395+ . await ?;
25592396
2560- // Coordinate download and processing
25612397 let params = RawDiskDownloadParams {
25622398 http_tx,
25632399 writer_handle,
2564- external_decompressor ,
2400+ is_compressed : compression_type != Compression :: None ,
25652401 decompressed_progress_rx,
25662402 raw_written_progress_rx,
25672403 } ;
0 commit comments