linuxutils_system/
swapon.rs1use 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#[derive(Parser)]
24#[command(
25 name = "swapon",
26 about = "Enable devices and files for paging and swapping"
27)]
28pub struct Args {
29 #[arg(short = 'a', long)]
31 all: bool,
32
33 #[arg(short = 'd', long, num_args = 0..=1, default_missing_value = "")]
35 discard: Option<String>,
36
37 #[arg(short = 'e', long)]
39 ifexists: bool,
40
41 #[arg(short = 'p', long)]
43 priority: Option<i32>,
44
45 #[arg(short = 's', long)]
47 summary: bool,
48
49 #[arg(long, num_args = 0..=1, default_missing_value = "")]
51 show: Option<String>,
52
53 #[arg(long)]
55 noheadings: bool,
56
57 #[arg(long)]
59 bytes: bool,
60
61 #[arg(short = 'v', long)]
63 verbose: bool,
64
65 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 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 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}