1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! This module implements zram setup functionality.
16 //!
17 //! The setup implemented in this module assumes that the zram kernel module has been loaded early on init with only 1 zram device (`zram0`).
18 //!
19 //! zram kernel documentation https://docs.kernel.org/admin-guide/blockdev/zram.html
20
21 #[cfg(test)]
22 mod tests;
23
24 use std::io;
25
26 use crate::os::get_page_count;
27 use crate::os::get_page_size;
28 use crate::zram::SysfsZramApi;
29
30 const MKSWAP_BIN_PATH: &str = "/system/bin/mkswap";
31 const ZRAM_DEVICE_PATH: &str = "/dev/block/zram0";
32 const PROC_SWAPS_PATH: &str = "/proc/swaps";
33
34 const MAX_ZRAM_PERCENTAGE_ALLOWED: u64 = 500;
35
36 /// [SetupApi] is the mockable interface for swap operations.
37 #[cfg_attr(test, mockall::automock)]
38 pub trait SetupApi {
39 /// Set up zram swap device, returning whether the command succeeded and its output.
mkswap(device_path: &str) -> io::Result<std::process::Output>40 fn mkswap(device_path: &str) -> io::Result<std::process::Output>;
41 /// Specify the zram swap device.
swapon(device_path: &std::ffi::CStr) -> io::Result<()>42 fn swapon(device_path: &std::ffi::CStr) -> io::Result<()>;
43 /// Read swaps areas in use.
read_swap_areas() -> io::Result<String>44 fn read_swap_areas() -> io::Result<String>;
45 }
46
47 /// The implementation of [SetupApi].
48 pub struct SetupApiImpl;
49
50 impl SetupApi for SetupApiImpl {
mkswap(device_path: &str) -> io::Result<std::process::Output>51 fn mkswap(device_path: &str) -> io::Result<std::process::Output> {
52 std::process::Command::new(MKSWAP_BIN_PATH).arg(device_path).output()
53 }
54
swapon(device_path: &std::ffi::CStr) -> io::Result<()>55 fn swapon(device_path: &std::ffi::CStr) -> io::Result<()> {
56 // SAFETY: device_path is a nul-terminated string.
57 let res = unsafe { libc::swapon(device_path.as_ptr(), 0) };
58 if res == 0 {
59 Ok(())
60 } else {
61 Err(std::io::Error::last_os_error())
62 }
63 }
64
read_swap_areas() -> io::Result<String>65 fn read_swap_areas() -> io::Result<String> {
66 std::fs::read_to_string(PROC_SWAPS_PATH)
67 }
68 }
69
70 /// Whether or not zram is already set up on the device.
is_zram_swap_activated<S: SetupApi>() -> io::Result<bool>71 pub fn is_zram_swap_activated<S: SetupApi>() -> io::Result<bool> {
72 let swaps = S::read_swap_areas()?;
73 // Skip the first line which is header.
74 let swap_lines = swaps.lines().skip(1);
75 // Swap is turned on if swap file contains entry with zram keyword.
76 for line in swap_lines {
77 if line.contains("zram") {
78 return Ok(true);
79 }
80 }
81 Ok(false)
82 }
83
84 /// Error from [parse_zram_size_spec].
85 #[derive(Debug, thiserror::Error)]
86 pub enum ZramSpecError {
87 /// Zram size was not specified
88 #[error("zram size is not specified")]
89 EmptyZramSizeSpec,
90 /// Zram size percentage needs to be between 1 and 500%
91 #[error(
92 "zram size percentage {0} is out of range (expected the between 1 and {})",
93 MAX_ZRAM_PERCENTAGE_ALLOWED
94 )]
95 ZramPercentageOutOfRange(u64),
96 /// Parsing zram size error
97 #[error("zram size is not an int: {0}")]
98 ParseZramSize(#[from] std::num::ParseIntError),
99 }
100
101 /// Parse zram size that can be specified by a percentage or an absolute value.
parse_zram_size_spec(spec: &str) -> Result<u64, ZramSpecError>102 pub fn parse_zram_size_spec(spec: &str) -> Result<u64, ZramSpecError> {
103 parse_size_spec_with_page_info(spec, get_page_size(), get_page_count())
104 }
105
parse_size_spec_with_page_info( spec: &str, system_page_size: u64, system_page_count: u64, ) -> Result<u64, ZramSpecError>106 fn parse_size_spec_with_page_info(
107 spec: &str,
108 system_page_size: u64,
109 system_page_count: u64,
110 ) -> Result<u64, ZramSpecError> {
111 if spec.is_empty() {
112 return Err(ZramSpecError::EmptyZramSizeSpec);
113 }
114
115 if let Some(percentage_str) = spec.strip_suffix('%') {
116 let percentage = percentage_str.parse::<u64>()?;
117 if percentage == 0 || percentage > MAX_ZRAM_PERCENTAGE_ALLOWED {
118 return Err(ZramSpecError::ZramPercentageOutOfRange(percentage));
119 }
120 return Ok(system_page_count * percentage / 100 * system_page_size);
121 }
122
123 let zram_size = spec.parse::<u64>()?;
124 Ok(zram_size)
125 }
126
127 /// Error from [activate].
128 #[derive(Debug, thiserror::Error)]
129 pub enum ZramActivationError {
130 /// Failed to update zram disk size
131 #[error("failed to write zram disk size: {0}")]
132 UpdateZramDiskSize(std::io::Error),
133 /// Failed to swapon
134 #[error("swapon failed: {0}")]
135 SwapOn(std::io::Error),
136 /// Mkswap command failed
137 #[error("failed to execute mkswap: {0}")]
138 ExecuteMkSwap(std::io::Error),
139 /// Mkswap command failed
140 #[error("mkswap failed: {0:?}")]
141 MkSwap(std::process::Output),
142 }
143
144 /// Set up a zram device with provided parameters.
activate_zram<Z: SysfsZramApi, S: SetupApi>( zram_size: u64, ) -> Result<(), ZramActivationError>145 pub fn activate_zram<Z: SysfsZramApi, S: SetupApi>(
146 zram_size: u64,
147 ) -> Result<(), ZramActivationError> {
148 Z::write_disksize(&zram_size.to_string()).map_err(ZramActivationError::UpdateZramDiskSize)?;
149
150 let output = S::mkswap(ZRAM_DEVICE_PATH).map_err(ZramActivationError::ExecuteMkSwap)?;
151 if !output.status.success() {
152 return Err(ZramActivationError::MkSwap(output));
153 }
154
155 let zram_device_path_cstring = std::ffi::CString::new(ZRAM_DEVICE_PATH)
156 .expect("device path should have no nul characters");
157 S::swapon(&zram_device_path_cstring).map_err(ZramActivationError::SwapOn)?;
158
159 Ok(())
160 }
161