• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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