@@ -8,7 +8,9 @@ use std::process::{Command, Stdio};
88use clap:: { Parser , Subcommand } ;
99
1010use systemd_swap:: config:: { Config , WORK_DIR } ;
11- use systemd_swap:: helpers:: { am_i_root, find_swap_units, force_remove, get_what_from_swap_unit, makedirs, read_file} ;
11+ use systemd_swap:: helpers:: {
12+ am_i_root, find_swap_units, force_remove, get_what_from_swap_unit, makedirs, read_file,
13+ } ;
1214use systemd_swap:: meminfo:: { get_mem_stats, get_page_size} ;
1315use systemd_swap:: swapfc:: SwapFc ;
1416use systemd_swap:: systemd:: { notify_ready, notify_stopping, swapoff} ;
@@ -38,10 +40,10 @@ enum Commands {
3840#[ derive( Debug , Clone , Copy , PartialEq ) ]
3941enum SwapMode {
4042 Auto ,
41- ZramSwapfc , // zram + writeback to swap files (best for desktop! )
42- ZswapSwapfc , // zswap + swap files (alternative )
43- ZramOnly , // zram only (for non-btrfs )
44- Manual , // Use explicit config values
43+ ZramSwapfc , // zram + writeback to swap files (legacy option )
44+ ZswapSwapfc , // zswap + swap files (BEST for installed desktops: btrfs, ext4, xfs )
45+ ZramOnly , // zram only (for LiveCD or unsupported filesystems )
46+ Manual , // Use explicit config values
4547}
4648
4749fn main ( ) {
@@ -67,19 +69,33 @@ fn main() {
6769}
6870
6971/// Detect filesystem type for a path
72+ /// Falls back to root filesystem if path doesn't exist yet
7073fn get_path_fstype ( path : & str ) -> Option < String > {
71- // Check the parent directory or the path itself
72- let check_path = if Path :: new ( path) . exists ( ) {
73- path. to_string ( )
74- } else if let Some ( parent) = Path :: new ( path) . parent ( ) {
75- parent. to_string_lossy ( ) . to_string ( )
74+ // Build list of paths to check: path itself, parent, grandparent, ..., root
75+ let mut check_path = None ;
76+ let mut current = Path :: new ( path) ;
77+
78+ // First check if the path itself exists
79+ if current. exists ( ) {
80+ check_path = Some ( path. to_string ( ) ) ;
7681 } else {
77- "/" . to_string ( )
78- } ;
82+ // Walk up the directory tree to find an existing parent
83+ while let Some ( parent) = current. parent ( ) {
84+ if parent. exists ( ) && parent. to_string_lossy ( ) != "" {
85+ check_path = Some ( parent. to_string_lossy ( ) . to_string ( ) ) ;
86+ break ;
87+ }
88+ current = parent;
89+ }
90+ }
91+
92+ // Fall back to root if nothing found
93+ let check_path = check_path. unwrap_or_else ( || "/" . to_string ( ) ) ;
7994
8095 let output = Command :: new ( "df" )
8196 . args ( [ "--output=fstype" , & check_path] )
8297 . stdout ( Stdio :: piped ( ) )
98+ . stderr ( Stdio :: null ( ) )
8399 . output ( )
84100 . ok ( ) ?;
85101
@@ -98,7 +114,12 @@ fn is_swapfc_supported(path: &str) -> bool {
98114
99115/// Parse swap_mode from config
100116fn get_swap_mode ( config : & Config ) -> SwapMode {
101- match config. get ( "swap_mode" ) . unwrap_or ( "auto" ) . to_lowercase ( ) . as_str ( ) {
117+ match config
118+ . get ( "swap_mode" )
119+ . unwrap_or ( "auto" )
120+ . to_lowercase ( )
121+ . as_str ( )
122+ {
102123 "zram+swapfc" | "zram_swapfc" => SwapMode :: ZramSwapfc ,
103124 "zswap+swapfc" | "zswap" => SwapMode :: ZswapSwapfc ,
104125 "zram" | "zram_only" => SwapMode :: ZramOnly ,
@@ -116,7 +137,7 @@ fn run_zram_only(config: &Config) -> Result<(), Box<dyn std::error::Error>> {
116137 }
117138 notify_ready ( ) ;
118139 info ! ( "Zram setup complete (fallback mode)" ) ;
119-
140+
120141 // Keep running to respond to signals
121142 loop {
122143 std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 60 ) ) ;
@@ -136,23 +157,34 @@ fn start() -> Result<(), Box<dyn std::error::Error>> {
136157
137158 // Initialize directories
138159 makedirs ( WORK_DIR ) ?;
139- makedirs ( format ! ( "{}/system/local-fs.target.wants" , systemd_swap:: config:: RUN_SYSD ) ) ?;
140- makedirs ( format ! ( "{}/system/swap.target.wants" , systemd_swap:: config:: RUN_SYSD ) ) ?;
160+ makedirs ( format ! (
161+ "{}/system/local-fs.target.wants" ,
162+ systemd_swap:: config:: RUN_SYSD
163+ ) ) ?;
164+ makedirs ( format ! (
165+ "{}/system/swap.target.wants" ,
166+ systemd_swap:: config:: RUN_SYSD
167+ ) ) ?;
141168
142169 let config = Config :: load ( ) ?;
143170 let swap_mode = get_swap_mode ( & config) ;
144171
145172 // Determine effective mode
173+ // For installed systems (btrfs, ext4, xfs): use zswap + swapfc (best performance)
174+ // For LiveCD or unsupported filesystems: use zram only
146175 let effective_mode = match swap_mode {
147176 SwapMode :: Auto => {
148177 let swapfc_path = config. get ( "swapfc_path" ) . unwrap_or ( "/swapfc/swapfile" ) ;
149178 if is_swapfc_supported ( swapfc_path) {
150179 let fstype = get_path_fstype ( swapfc_path) . unwrap_or_default ( ) ;
151- info ! ( "Auto-detected {}: using zswap + swapfc" , fstype) ;
180+ info ! ( "Auto-detected {} filesystem : using zswap + swapfc (best for installed systems) " , fstype) ;
152181 SwapMode :: ZswapSwapfc
153182 } else {
154183 let fstype = get_path_fstype ( swapfc_path) . unwrap_or_else ( || "unknown" . to_string ( ) ) ;
155- info ! ( "Filesystem '{}' not supported for swap files: using zram only" , fstype) ;
184+ info ! (
185+ "Filesystem '{}' not supported for swap files: using zram only (LiveCD mode)" ,
186+ fstype
187+ ) ;
156188 SwapMode :: ZramOnly
157189 }
158190 }
@@ -164,9 +196,9 @@ fn start() -> Result<(), Box<dyn std::error::Error>> {
164196
165197 match effective_mode {
166198 SwapMode :: ZramSwapfc => {
167- // Desktop-optimized mode: zram for speed + swapfc for overflow
168- // zram is faster than zswap because it's a dedicated block device
169-
199+ // Legacy mode: zram for speed + swapfc for overflow
200+ // Note: zswap+swapfc is now preferred for installed systems
201+
170202 // Set up signal handler
171203 signal_hook:: flag:: register (
172204 signal_hook:: consts:: SIGTERM ,
@@ -206,7 +238,10 @@ fn start() -> Result<(), Box<dyn std::error::Error>> {
206238 }
207239 Err ( e) => {
208240 // swapfc failed but zram is already running - continue in zram-only mode
209- warn ! ( "swapFC: initialization failed: {} - continuing with zram-only" , e) ;
241+ warn ! (
242+ "swapFC: initialization failed: {} - continuing with zram-only" ,
243+ e
244+ ) ;
210245 notify_ready ( ) ;
211246 loop {
212247 std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 60 ) ) ;
@@ -219,9 +254,10 @@ fn start() -> Result<(), Box<dyn std::error::Error>> {
219254 }
220255
221256 SwapMode :: ZswapSwapfc => {
222- // For zswap: create swap file FIRST, then enable zswap
223- // zswap needs a backing swap device to work
224-
257+ // RECOMMENDED for installed desktops (btrfs, ext4, xfs)
258+ // zswap compresses pages in RAM before writing to swap files
259+ // Create swap file FIRST, then enable zswap (zswap needs backing swap)
260+
225261 // Set up signal handler
226262 signal_hook:: flag:: register (
227263 signal_hook:: consts:: SIGTERM ,
@@ -256,20 +292,23 @@ fn start() -> Result<(), Box<dyn std::error::Error>> {
256292 }
257293 }
258294 Err ( e) => {
259- warn ! ( "swapFC: initialization failed: {} - falling back to zram-only" , e) ;
295+ warn ! (
296+ "swapFC: initialization failed: {} - falling back to zram-only" ,
297+ e
298+ ) ;
260299 run_zram_only ( & config) ?;
261300 }
262301 }
263302 }
264303
265304 SwapMode :: ZramOnly => {
266- // For zram: just set up zram, no swap files needed
305+ // For LiveCD or unsupported filesystems: zram only , no swap files
267306 if let Err ( e) = systemd_swap:: zram:: start ( & config) {
268307 error ! ( "Zram: {}" , e) ;
269308 }
270309 notify_ready ( ) ;
271310 info ! ( "Zram setup complete" ) ;
272-
311+
273312 // Keep running to respond to signals
274313 loop {
275314 std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 60 ) ) ;
@@ -281,7 +320,7 @@ fn start() -> Result<(), Box<dyn std::error::Error>> {
281320
282321 SwapMode :: Manual => {
283322 // Legacy mode: use explicit config values
284-
323+
285324 // Warn about incompatible configurations
286325 if config. get_bool ( "zram_enabled" )
287326 && ( config. get_bool ( "zswap_enabled" ) || config. get_bool ( "swapfc_enabled" ) )
@@ -435,7 +474,7 @@ fn status() -> Result<(), Box<dyn std::error::Error>> {
435474 if usage. zswap_active {
436475 let zswap_original = swap_used. saturating_sub ( usage. swap_used_disk ) ;
437476 let zswap_compressed = usage. zswap_pool_bytes ;
438-
477+
439478 let ratio = if zswap_original > 0 {
440479 ( zswap_compressed as f64 / zswap_original as f64 ) * 100.0
441480 } else {
@@ -444,22 +483,30 @@ fn status() -> Result<(), Box<dyn std::error::Error>> {
444483
445484 println ! ( ) ;
446485 println ! ( " === Pool Statistics ===" ) ;
447- println ! ( " pool_size: {:.1} MiB (compressed)" , zswap_compressed as f64 / 1024.0 / 1024.0 ) ;
448- println ! ( " stored_data: {:.1} MiB (original)" , zswap_original as f64 / 1024.0 / 1024.0 ) ;
486+ println ! (
487+ " pool_size: {:.1} MiB (compressed)" ,
488+ zswap_compressed as f64 / 1024.0 / 1024.0
489+ ) ;
490+ println ! (
491+ " stored_data: {:.1} MiB (original)" ,
492+ zswap_original as f64 / 1024.0 / 1024.0
493+ ) ;
449494 println ! ( " pool_utilization: {}%" , usage. zswap_pool_percent) ;
450495 println ! ( " compress_ratio: {:.0}%" , ratio) ;
451496
452497 // If running as root, show additional debugfs stats
453498 if is_root && ( zswap. stored_pages > 0 || zswap. written_back_pages > 0 ) {
454499 let page_size = get_page_size ( ) ;
455-
500+
456501 println ! ( ) ;
457502 println ! ( " === Writeback Statistics (debugfs) ===" ) ;
458503 println ! ( " stored_pages: {}" , zswap. stored_pages) ;
459504 println ! ( " same_filled_pages: {}" , zswap. same_filled_pages) ;
460- println ! ( " written_back_pages: {} ({:.1} MiB)" ,
461- zswap. written_back_pages,
462- ( zswap. written_back_pages * page_size) as f64 / 1024.0 / 1024.0 ) ;
505+ println ! (
506+ " written_back_pages: {} ({:.1} MiB)" ,
507+ zswap. written_back_pages,
508+ ( zswap. written_back_pages * page_size) as f64 / 1024.0 / 1024.0
509+ ) ;
463510 println ! ( " pool_limit_hit: {}" , zswap. pool_limit_hit) ;
464511 println ! ( " reject_reclaim_fail: {}" , zswap. reject_reclaim_fail) ;
465512 }
@@ -468,9 +515,18 @@ fn status() -> Result<(), Box<dyn std::error::Error>> {
468515 if swap_used > 0 {
469516 println ! ( ) ;
470517 println ! ( " === Effective Swap Usage ===" ) ;
471- println ! ( " kernel_reported_used: {:.1} MiB" , swap_used as f64 / 1024.0 / 1024.0 ) ;
472- println ! ( " in_zswap_pool (RAM): {:.1} MiB" , zswap_original as f64 / 1024.0 / 1024.0 ) ;
473- println ! ( " actual_disk_used: {:.1} MiB" , usage. swap_used_disk as f64 / 1024.0 / 1024.0 ) ;
518+ println ! (
519+ " kernel_reported_used: {:.1} MiB" ,
520+ swap_used as f64 / 1024.0 / 1024.0
521+ ) ;
522+ println ! (
523+ " in_zswap_pool (RAM): {:.1} MiB" ,
524+ zswap_original as f64 / 1024.0 / 1024.0
525+ ) ;
526+ println ! (
527+ " actual_disk_used: {:.1} MiB" ,
528+ usage. swap_used_disk as f64 / 1024.0 / 1024.0
529+ ) ;
474530 let percent_in_ram = ( zswap_original as f64 / swap_used as f64 ) * 100.0 ;
475531 println ! ( " swap_in_ram: {:.0}%" , percent_in_ram) ;
476532 }
@@ -479,17 +535,18 @@ fn status() -> Result<(), Box<dyn std::error::Error>> {
479535 }
480536
481537 // Zram status
482- let zramctl_output = Command :: new ( "zramctl" )
483- . stdout ( Stdio :: piped ( ) )
484- . output ( ) ;
538+ let zramctl_output = Command :: new ( "zramctl" ) . stdout ( Stdio :: piped ( ) ) . output ( ) ;
485539
486540 if let Ok ( output) = zramctl_output {
487541 let output_str = String :: from_utf8_lossy ( & output. stdout ) ;
488542 if output_str. contains ( "[SWAP]" ) {
489543 println ! ( "\n Zram:" ) ;
490544 for line in output_str. lines ( ) {
491545 if line. starts_with ( "NAME" ) || line. contains ( "[SWAP]" ) {
492- let line = line. trim_end_matches ( "[SWAP]" ) . trim_end_matches ( "MOUNTPOINT" ) . trim ( ) ;
546+ let line = line
547+ . trim_end_matches ( "[SWAP]" )
548+ . trim_end_matches ( "MOUNTPOINT" )
549+ . trim ( ) ;
493550 println ! ( " {}" , line) ;
494551 }
495552 }
@@ -513,4 +570,3 @@ fn status() -> Result<(), Box<dyn std::error::Error>> {
513570
514571 Ok ( ( ) )
515572}
516-
0 commit comments