@@ -48,6 +48,7 @@ pub struct SwapFcConfig {
4848 pub priority : i32 ,
4949 pub force_use_loop : bool ,
5050 pub directio : bool ,
51+ pub use_btrfs_compression : bool ,
5152}
5253
5354impl SwapFcConfig {
@@ -78,6 +79,7 @@ impl SwapFcConfig {
7879 priority : config. get_as ( "swapfc_priority" ) . unwrap_or ( 50 ) ,
7980 force_use_loop : config. get_bool ( "swapfc_force_use_loop" ) ,
8081 directio : config. get_bool ( "swapfc_directio" ) ,
82+ use_btrfs_compression : config. get_bool ( "swapfc_use_btrfs_compression" ) ,
8183 } )
8284 }
8385}
@@ -265,26 +267,42 @@ impl SwapFc {
265267 . open ( & swapfile_path) ?;
266268 }
267269
268- // Disable COW for btrfs
269- let _ = Command :: new ( "chattr" ) . args ( [ "+C" ] ) . arg ( & swapfile_path) . status ( ) ;
270+ // Determine if we're using btrfs compression mode
271+ let use_compression = self . config . use_btrfs_compression ;
272+ let use_loop = self . config . force_use_loop || use_compression;
273+
274+ if use_compression {
275+ // For btrfs compression: use sparse file with truncate
276+ // This allows btrfs to compress the data and only allocate used blocks
277+ info ! ( "swapFC: creating sparse file for btrfs compression" ) ;
278+ Command :: new ( "truncate" )
279+ . args ( [ "--size" , & self . config . chunk_size . to_string ( ) ] )
280+ . arg ( & swapfile_path)
281+ . status ( ) ?;
282+ // Do NOT use chattr +C - we want compression!
283+ } else {
284+ // Disable COW for btrfs (normal mode)
285+ let _ = Command :: new ( "chattr" ) . args ( [ "+C" ] ) . arg ( & swapfile_path) . status ( ) ;
270286
271- // Allocate space
272- Command :: new ( "fallocate" )
273- . args ( [ "-l" , & self . config . chunk_size . to_string ( ) ] )
274- . arg ( & swapfile_path)
275- . status ( ) ?;
287+ // Allocate space with fallocate
288+ Command :: new ( "fallocate" )
289+ . args ( [ "-l" , & self . config . chunk_size . to_string ( ) ] )
290+ . arg ( & swapfile_path)
291+ . status ( ) ?;
292+ }
276293
277- let swapfile = if self . config . force_use_loop {
278- let directio = if self . config . directio { "on" } else { "off" } ;
294+ let ( swapfile, loop_device) = if use_loop {
295+ // Create loop device
296+ let directio = if self . config . directio && !use_compression { "on" } else { "off" } ;
279297 let loop_dev = run_cmd_output ( & [
280298 "losetup" , "-f" , "--show" ,
281299 & format ! ( "--direct-io={}" , directio) ,
282300 & swapfile_path. to_string_lossy ( ) ,
283301 ] ) ?;
284- fs :: remove_file ( & swapfile_path ) ? ;
285- loop_dev
302+ let loop_dev = loop_dev . trim ( ) . to_string ( ) ;
303+ ( loop_dev. clone ( ) , Some ( loop_dev ) )
286304 } else {
287- swapfile_path. to_string_lossy ( ) . to_string ( )
305+ ( swapfile_path. to_string_lossy ( ) . to_string ( ) , None )
288306 } ;
289307
290308 // mkswap
@@ -295,13 +313,21 @@ impl SwapFc {
295313 . status ( ) ?;
296314
297315 // Generate and start swap unit
316+ // Use discard=pages for compressed mode to release space when pages are freed
317+ let discard_option = if use_compression { "pages" } else { "discard" } ;
298318 let unit_name = gen_swap_unit (
299319 Path :: new ( & swapfile) ,
300320 Some ( self . priority ) ,
301- Some ( "discard" ) ,
321+ Some ( discard_option ) ,
302322 & format ! ( "swapfc_{}" , self . allocated) ,
303323 ) ?;
304324
325+ // Store loop device info for cleanup
326+ if let Some ( ref loop_dev) = loop_device {
327+ let loop_info_path = format ! ( "{}/swapfc/loop_{}" , WORK_DIR , self . allocated) ;
328+ let _ = fs:: write ( & loop_info_path, format ! ( "{}\n {}" , loop_dev, swapfile_path. display( ) ) ) ;
329+ }
330+
305331 self . priority -= 1 ;
306332
307333 systemctl ( "daemon-reload" , "" ) ?;
@@ -316,6 +342,10 @@ impl SwapFc {
316342
317343 let tag = format ! ( "swapfc_{}" , self . allocated) ;
318344
345+ // Check if we have loop device info for this swap
346+ let loop_info_path = format ! ( "{}/swapfc/loop_{}" , WORK_DIR , self . allocated) ;
347+ let loop_info = fs:: read_to_string ( & loop_info_path) . ok ( ) ;
348+
319349 for unit_path in crate :: helpers:: find_swap_units ( ) {
320350 if let Ok ( content) = crate :: helpers:: read_file ( & unit_path) {
321351 if content. contains ( & tag) {
@@ -331,7 +361,24 @@ impl SwapFc {
331361
332362 force_remove ( & unit_path, true ) ;
333363
334- if Path :: new ( & dev) . is_file ( ) {
364+ // Clean up loop device and backing file if applicable
365+ if let Some ( ref info) = loop_info {
366+ let lines: Vec < & str > = info. lines ( ) . collect ( ) ;
367+ if lines. len ( ) >= 2 {
368+ let loop_dev = lines[ 0 ] ;
369+ let backing_file = lines[ 1 ] ;
370+
371+ // Detach loop device
372+ let _ = Command :: new ( "losetup" )
373+ . args ( [ "-d" , loop_dev] )
374+ . status ( ) ;
375+
376+ // Remove backing file
377+ force_remove ( backing_file, false ) ;
378+ }
379+ // Remove loop info file
380+ force_remove ( & loop_info_path, false ) ;
381+ } else if Path :: new ( & dev) . is_file ( ) {
335382 force_remove ( & dev, false ) ;
336383 }
337384 }
0 commit comments