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