Skip to main content

linuxutils_system/
swapon.rs

1use linuxutils_common::man::ManContent;
2
3pub const MAN: ManContent = ManContent::empty();
4
5use clap::Parser;
6use cols::{Cols, print_table};
7use std::{
8    ffi::CString,
9    fs::File,
10    io::{self, BufRead},
11    process::ExitCode,
12};
13
14const SWAP_FLAG_PREFER: libc::c_int = 0x8000;
15const SWAP_FLAG_DISCARD: libc::c_int = 0x10000;
16const SWAP_FLAG_DISCARD_ONCE: libc::c_int = 0x20000;
17const SWAP_FLAG_DISCARD_PAGES: libc::c_int = 0x40000;
18
19/// Enable devices and files for paging and swapping.
20///
21/// Calls swapon(2) to activate swap areas. Can enable individual devices
22/// or all swap entries from /etc/fstab.
23#[derive(Parser)]
24#[command(
25    name = "swapon",
26    about = "Enable devices and files for paging and swapping"
27)]
28pub struct Args {
29    /// Enable all swap entries from /etc/fstab
30    #[arg(short = 'a', long)]
31    all: bool,
32
33    /// Enable discard (once, pages, or both if no policy given)
34    #[arg(short = 'd', long, num_args = 0..=1, default_missing_value = "")]
35    discard: Option<String>,
36
37    /// Silently skip non-existent devices
38    #[arg(short = 'e', long)]
39    ifexists: bool,
40
41    /// Set swap priority (0-32767)
42    #[arg(short = 'p', long)]
43    priority: Option<i32>,
44
45    /// Display /proc/swaps summary (deprecated)
46    #[arg(short = 's', long)]
47    summary: bool,
48
49    /// Display formatted table of swap areas
50    #[arg(long, num_args = 0..=1, default_missing_value = "")]
51    show: Option<String>,
52
53    /// Suppress table headers
54    #[arg(long)]
55    noheadings: bool,
56
57    /// Show sizes in bytes instead of human-friendly units
58    #[arg(long)]
59    bytes: bool,
60
61    /// Verbose output
62    #[arg(short = 'v', long)]
63    verbose: bool,
64
65    /// Devices or files to enable
66    devices: Vec<String>,
67}
68
69#[derive(Cols)]
70struct SwapEntry {
71    #[column(header = "NAME")]
72    name: String,
73
74    #[column(header = "TYPE")]
75    swap_type: String,
76
77    #[column(right, header = "SIZE")]
78    size: String,
79
80    #[column(right, header = "USED")]
81    used: String,
82
83    #[column(right, header = "PRIO")]
84    prio: i32,
85}
86
87fn format_size(kib: u64, bytes: bool) -> String {
88    if bytes {
89        return (kib * 1024).to_string();
90    }
91    let b = kib * 1024;
92    if b >= 1024 * 1024 * 1024 {
93        format!("{:.1}G", b as f64 / (1024.0 * 1024.0 * 1024.0))
94    } else if b >= 1024 * 1024 {
95        format!("{:.1}M", b as f64 / (1024.0 * 1024.0))
96    } else if b >= 1024 {
97        format!("{:.1}K", b as f64 / 1024.0)
98    } else {
99        format!("{b}B")
100    }
101}
102
103fn read_proc_swaps(bytes: bool) -> io::Result<Vec<SwapEntry>> {
104    let file = File::open("/proc/swaps")?;
105    let mut entries = Vec::new();
106
107    for line in io::BufReader::new(file)
108        .lines()
109        .map_while(Result::ok)
110        .skip(1)
111    {
112        let fields: Vec<&str> = line.split_whitespace().collect();
113        if fields.len() >= 5 {
114            let size_kib: u64 = fields[2].parse().unwrap_or(0);
115            let used_kib: u64 = fields[3].parse().unwrap_or(0);
116            entries.push(SwapEntry {
117                name: fields[0].to_string(),
118                swap_type: fields[1].to_string(),
119                size: format_size(size_kib, bytes),
120                used: format_size(used_kib, bytes),
121                prio: fields[4].parse().unwrap_or(0),
122            });
123        }
124    }
125
126    Ok(entries)
127}
128
129fn read_fstab_swap_devices() -> Vec<String> {
130    let Ok(file) = File::open("/etc/fstab") else {
131        return Vec::new();
132    };
133    let mut devices = Vec::new();
134
135    for line in io::BufReader::new(file).lines().map_while(Result::ok) {
136        let line = line.trim().to_string();
137        if line.is_empty() || line.starts_with('#') {
138            continue;
139        }
140        let fields: Vec<&str> = line.split_whitespace().collect();
141        if fields.len() >= 3 && fields[2] == "swap" {
142            let options = if fields.len() >= 4 { fields[3] } else { "" };
143            if !options.split(',').any(|o| o == "noauto") {
144                devices.push(fields[0].to_string());
145            }
146        }
147    }
148
149    devices
150}
151
152fn is_swap_active(device: &str) -> bool {
153    let Ok(file) = File::open("/proc/swaps") else {
154        return false;
155    };
156    io::BufReader::new(file)
157        .lines()
158        .map_while(Result::ok)
159        .skip(1)
160        .any(|line| {
161            line.split_whitespace()
162                .next()
163                .is_some_and(|name| name == device)
164        })
165}
166
167fn do_swapon(path: &str, flags: libc::c_int) -> io::Result<()> {
168    let cpath =
169        CString::new(path).map_err(|e| io::Error::other(e.to_string()))?;
170    if unsafe { libc::swapon(cpath.as_ptr(), flags) } < 0 {
171        Err(io::Error::last_os_error())
172    } else {
173        Ok(())
174    }
175}
176
177pub fn run(args: Args) -> ExitCode {
178    // Display modes (no swap activation)
179    if args.show.is_some() || args.summary {
180        if args.summary {
181            match std::fs::read_to_string("/proc/swaps") {
182                Ok(content) => {
183                    print!("{content}");
184                    return ExitCode::SUCCESS;
185                }
186                Err(e) => {
187                    eprintln!("swapon: /proc/swaps: {e}");
188                    return ExitCode::FAILURE;
189                }
190            }
191        }
192
193        let entries = match read_proc_swaps(args.bytes) {
194            Ok(e) => e,
195            Err(e) => {
196                eprintln!("swapon: /proc/swaps: {e}");
197                return ExitCode::FAILURE;
198            }
199        };
200
201        let mut table = SwapEntry::to_table(&entries);
202        table.headings_set(!args.noheadings);
203        let _ = print_table(&table, &mut io::stdout().lock());
204        return ExitCode::SUCCESS;
205    }
206
207    // Build flags
208    let mut flags: libc::c_int = 0;
209    if let Some(ref policy) = args.discard {
210        flags |= SWAP_FLAG_DISCARD;
211        match policy.as_str() {
212            "" => flags |= SWAP_FLAG_DISCARD_ONCE | SWAP_FLAG_DISCARD_PAGES,
213            "once" => flags |= SWAP_FLAG_DISCARD_ONCE,
214            "pages" => flags |= SWAP_FLAG_DISCARD_PAGES,
215            other => {
216                eprintln!("swapon: unknown discard policy: {other}");
217                return ExitCode::FAILURE;
218            }
219        }
220    }
221    if let Some(prio) = args.priority {
222        flags |= SWAP_FLAG_PREFER | (prio & 0x7fff);
223    }
224
225    let devices = if args.all {
226        read_fstab_swap_devices()
227    } else if args.devices.is_empty() {
228        eprintln!("swapon: no device specified. Try 'swapon --help'");
229        return ExitCode::FAILURE;
230    } else {
231        args.devices.clone()
232    };
233
234    let mut failed = false;
235    for device in &devices {
236        if args.ifexists && !std::path::Path::new(device).exists() {
237            continue;
238        }
239        if args.all && is_swap_active(device) {
240            continue;
241        }
242        if args.verbose {
243            eprintln!("swapon: enabling {device}");
244        }
245        if let Err(e) = do_swapon(device, flags) {
246            eprintln!("swapon: {device}: {e}");
247            failed = true;
248        }
249    }
250
251    if failed {
252        ExitCode::FAILURE
253    } else {
254        ExitCode::SUCCESS
255    }
256}