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::fs::File;
25 use std::fs::Permissions;
26 use std::io;
27 use std::os::unix::fs::PermissionsExt;
28 use std::path::Path;
29
30 use dm::loopdevice;
31 use dm::loopdevice::LoopDevice;
32
33 use crate::os::fallocate;
34 use crate::zram::SysfsZramApi;
35
36 const MKSWAP_BIN_PATH: &str = "/system/bin/mkswap";
37 const ZRAM_DEVICE_PATH: &str = "/dev/block/zram0";
38 const PROC_SWAPS_PATH: &str = "/proc/swaps";
39
40 /// [SetupApi] is the mockable interface for swap operations.
41 #[cfg_attr(test, mockall::automock)]
42 pub trait SetupApi {
43 /// Set up zram swap device, returning whether the command succeeded and its output.
mkswap(device_path: &str) -> io::Result<std::process::Output>44 fn mkswap(device_path: &str) -> io::Result<std::process::Output>;
45 /// Specify the zram swap device.
swapon(device_path: &std::ffi::CStr) -> io::Result<()>46 fn swapon(device_path: &std::ffi::CStr) -> io::Result<()>;
47 /// Read swaps areas in use.
read_swap_areas() -> io::Result<String>48 fn read_swap_areas() -> io::Result<String>;
49 /// Set up a new loop device for a backing file with size.
attach_loop_device( file_path: &Path, device_size: u64, ) -> anyhow::Result<loopdevice::LoopDevice>50 fn attach_loop_device(
51 file_path: &Path,
52 device_size: u64,
53 ) -> anyhow::Result<loopdevice::LoopDevice>;
54 }
55
56 /// The implementation of [SetupApi].
57 pub struct SetupApiImpl;
58
59 impl SetupApi for SetupApiImpl {
mkswap(device_path: &str) -> io::Result<std::process::Output>60 fn mkswap(device_path: &str) -> io::Result<std::process::Output> {
61 std::process::Command::new(MKSWAP_BIN_PATH).arg(device_path).output()
62 }
63
swapon(device_path: &std::ffi::CStr) -> io::Result<()>64 fn swapon(device_path: &std::ffi::CStr) -> io::Result<()> {
65 // SAFETY: device_path is a nul-terminated string.
66 let res = unsafe { libc::swapon(device_path.as_ptr(), 0) };
67 if res == 0 {
68 Ok(())
69 } else {
70 Err(std::io::Error::last_os_error())
71 }
72 }
73
read_swap_areas() -> io::Result<String>74 fn read_swap_areas() -> io::Result<String> {
75 std::fs::read_to_string(PROC_SWAPS_PATH)
76 }
77
attach_loop_device( file_path: &Path, device_size: u64, ) -> anyhow::Result<loopdevice::LoopDevice>78 fn attach_loop_device(
79 file_path: &Path,
80 device_size: u64,
81 ) -> anyhow::Result<loopdevice::LoopDevice> {
82 loopdevice::attach(
83 file_path,
84 0,
85 device_size,
86 &loopdevice::LoopConfigOptions { direct_io: true, writable: true, autoclear: true },
87 )
88 }
89 }
90
91 /// Whether or not zram is already set up on the device.
is_zram_swap_activated<S: SetupApi>() -> io::Result<bool>92 pub fn is_zram_swap_activated<S: SetupApi>() -> io::Result<bool> {
93 let swaps = S::read_swap_areas()?;
94 // Skip the first line which is header.
95 let swap_lines = swaps.lines().skip(1);
96 // Swap is turned on if swap file contains entry with zram keyword.
97 for line in swap_lines {
98 if line.contains("zram") {
99 return Ok(true);
100 }
101 }
102 Ok(false)
103 }
104
105 /// Error from [activate].
106 #[derive(Debug, thiserror::Error)]
107 pub enum ZramActivationError {
108 /// Failed to update zram disk size
109 #[error("failed to write zram disk size: {0}")]
110 UpdateZramDiskSize(std::io::Error),
111 /// Failed to swapon
112 #[error("swapon failed: {0}")]
113 SwapOn(std::io::Error),
114 /// Mkswap command failed
115 #[error("failed to execute mkswap: {0}")]
116 ExecuteMkSwap(std::io::Error),
117 /// Mkswap command failed
118 #[error("mkswap failed: {0:?}")]
119 MkSwap(std::process::Output),
120 }
121
122 /// Set up a zram device with provided parameters.
activate_zram<Z: SysfsZramApi, S: SetupApi>( zram_size: u64, ) -> Result<(), ZramActivationError>123 pub fn activate_zram<Z: SysfsZramApi, S: SetupApi>(
124 zram_size: u64,
125 ) -> Result<(), ZramActivationError> {
126 Z::write_disksize(&zram_size.to_string()).map_err(ZramActivationError::UpdateZramDiskSize)?;
127
128 let output = S::mkswap(ZRAM_DEVICE_PATH).map_err(ZramActivationError::ExecuteMkSwap)?;
129 if !output.status.success() {
130 return Err(ZramActivationError::MkSwap(output));
131 }
132
133 let zram_device_path_cstring = std::ffi::CString::new(ZRAM_DEVICE_PATH)
134 .expect("device path should have no nul characters");
135 S::swapon(&zram_device_path_cstring).map_err(ZramActivationError::SwapOn)?;
136
137 Ok(())
138 }
139
140 /// Error from [create_zram_writeback_device].
141 #[derive(Debug, thiserror::Error)]
142 pub enum WritebackDeviceSetupError {
143 /// Failed to create backing file
144 #[error("failed to create backing file: {0}")]
145 CreateBackingFile(std::io::Error),
146 /// Failed to create the backing device
147 #[error("failed to create backing device: {0}")]
148 CreateBackingDevice(anyhow::Error),
149 }
150
151 /// Create a zram backing device with provided file path and size.
create_zram_writeback_device<S: SetupApi>( file_path: &Path, device_size: u64, ) -> std::result::Result<LoopDevice, WritebackDeviceSetupError>152 pub fn create_zram_writeback_device<S: SetupApi>(
153 file_path: &Path,
154 device_size: u64,
155 ) -> std::result::Result<LoopDevice, WritebackDeviceSetupError> {
156 let swap_file =
157 File::create(file_path).map_err(WritebackDeviceSetupError::CreateBackingFile)?;
158 scopeguard::defer! {
159 let _ = std::fs::remove_file(file_path);
160 }
161 std::fs::set_permissions(file_path, Permissions::from_mode(0o600))
162 .map_err(WritebackDeviceSetupError::CreateBackingFile)?;
163
164 fallocate(&swap_file, device_size).map_err(WritebackDeviceSetupError::CreateBackingFile)?;
165
166 let loop_device = S::attach_loop_device(file_path, device_size)
167 .map_err(WritebackDeviceSetupError::CreateBackingDevice)?;
168
169 Ok(loop_device)
170 }
171
172 /// Enables zram writeback limit.
enable_zram_writeback_limit<Z: SysfsZramApi>() -> std::io::Result<()>173 pub fn enable_zram_writeback_limit<Z: SysfsZramApi>() -> std::io::Result<()> {
174 Z::write_writeback_limit_enable("1")
175 }
176