Revert^2 "Implement basic mmd zram setup"
This reverts commit 6cc3928d8edb2a0ce2754b26c91d2f75401c9398.
Reason for revert: Roll forward this change with the change in mmd.rc to
not run chmod and chown operations on every boot, but only run it when
the zram setup in mmd is enabled. This will allow device owner to
assign correct zram device type in their sepolicy when enabling zram
setup in mmd.
Bug: 375432644
Bug: 370509309
Test: m; manually verify mmd zram setup works
Test: verify the failed test in b/381372137 passes with this new change
through on demand test run
Change-Id: I84855aaea35cf9fc604853098ed1d43f8a90867b
diff --git a/Android.bp b/Android.bp
index 369fbe4..098b802 100644
--- a/Android.bp
+++ b/Android.bp
@@ -29,6 +29,7 @@
rustlibs: [
"liblibc",
"libmockall",
+ "libnix",
"libthiserror",
],
}
diff --git a/mmd.rc b/mmd.rc
index ea764ce..276f6ab 100644
--- a/mmd.rc
+++ b/mmd.rc
@@ -22,6 +22,14 @@
chown root system /sys/block/zram0/writeback
chmod 0220 /sys/block/zram0/writeback
+on boot && property:mmd.zram.enabled=true
+ # Allow mmd to run mkswap on zram device
+ chown root mmd /dev/block/zram0
+ chmod 0664 /dev/block/zram0
+ # Allow mmd to update zram disk size
+ chown root mmd /sys/block/zram0/disksize
+ chmod 0664 /sys/block/zram0/disksize
+
on property:sys.boot_completed=1
# Copy AConfig flag value to "mmd.enabled_aconfig" system property because
# AConfig flag does not support init "on property" trigger.
diff --git a/src/main.rs b/src/main.rs
index 374beb6..859d5d7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -19,8 +19,6 @@
mod properties;
mod service;
-use std::time::Duration;
-
use binder::BinderFeatures;
use log::error;
use log::info;
@@ -28,6 +26,10 @@
use log::LevelFilter;
use mmd::zram::recompression::is_zram_recompression_activated;
use mmd::zram::recompression::ZramRecompression;
+use mmd::zram::setup::activate_zram;
+use mmd::zram::setup::is_zram_swap_activated;
+use mmd::zram::setup::parse_zram_size_spec;
+use mmd::zram::setup::SetupApiImpl;
use mmd::zram::stats::load_total_zram_size;
use mmd::zram::writeback::is_zram_writeback_activated;
use mmd::zram::writeback::ZramWriteback;
@@ -36,10 +38,24 @@
use rustutils::system_properties;
use crate::properties::BoolProp;
+use crate::properties::StringProp;
// In Android zram writeback file is always "/data/per_boot/zram_swap".
const ZRAM_WRITEBACK_FILE_PATH: &str = "/data/per_boot/zram_swap";
+fn setup_zram() -> anyhow::Result<()> {
+ let zram_activated = is_zram_swap_activated::<SetupApiImpl>()?;
+ if zram_activated {
+ info!("zram is already on, skipping zram setup");
+ return Ok(());
+ }
+
+ let zram_size_spec = StringProp::ZramSize.get("50%");
+ let zram_size = parse_zram_size_spec(&zram_size_spec)?;
+ activate_zram::<SysfsZramApiImpl, SetupApiImpl>(zram_size)?;
+ Ok(())
+}
+
fn main() {
// "mmd --set-property" command copies the AConfig flag to "mmd.enabled_aconfig" system
// property as either "true" or "false".
@@ -63,9 +79,9 @@
return;
}
- // TODO: This is to wait until swapon_all command sets up zram. Remove this when mmd sets up
- // zram.
- std::thread::sleep(Duration::from_secs(60));
+ if BoolProp::ZramEnabled.get(false) {
+ setup_zram().expect("zram setup");
+ }
let total_zram_size = match load_total_zram_size::<SysfsZramApiImpl>() {
Ok(v) => v,
diff --git a/src/os.rs b/src/os.rs
index 235fa83..337692f 100644
--- a/src/os.rs
+++ b/src/os.rs
@@ -16,6 +16,9 @@
use std::io;
+use nix::unistd::sysconf;
+use nix::unistd::SysconfVar;
+
const MEMINFO_PATH: &str = "/proc/meminfo";
/// [MeminfoApi] is a mockable interface for access to "/proc/meminfo".
@@ -47,3 +50,10 @@
// SAFETY: `sysconf` simply returns an integer.
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as u64 }
}
+
+/// Returns the page count of the system.
+pub fn get_page_count() -> u64 {
+ sysconf(SysconfVar::_PHYS_PAGES)
+ .expect("PHYS_PAGES should be a valid sysconf variable")
+ .expect("PHYS_PAGES variable should be supported") as u64
+}
diff --git a/src/properties.rs b/src/properties.rs
index 435b1b2..9169412 100644
--- a/src/properties.rs
+++ b/src/properties.rs
@@ -37,6 +37,7 @@
/// clippy::enum_variant_names is allowed because we may add more properties.
#[allow(clippy::enum_variant_names)]
pub enum BoolProp {
+ ZramEnabled,
ZramWritebackEnabled,
ZramWritebackHugeIdleEnabled,
ZramWritebackIdleEnabled,
@@ -50,6 +51,7 @@
impl BoolProp {
fn flag_name(&self) -> &'static str {
match self {
+ Self::ZramEnabled => "zram.enabled",
Self::ZramWritebackEnabled => "zram.writeback.enabled",
Self::ZramWritebackHugeIdleEnabled => "zram.writeback.huge_idle.enabled",
Self::ZramWritebackIdleEnabled => "zram.writeback.idle.enabled",
@@ -134,6 +136,26 @@
}
}
+/// String system properties for mmd.
+///
+/// clippy::enum_variant_names is allowed because we may add more properties.
+#[allow(clippy::enum_variant_names)]
+pub enum StringProp {
+ ZramSize,
+}
+
+impl StringProp {
+ fn flag_name(&self) -> &'static str {
+ match self {
+ Self::ZramSize => "zram.size",
+ }
+ }
+
+ pub fn get(&self, default: &str) -> String {
+ read(self.flag_name()).unwrap_or_else(|| default.to_string())
+ }
+}
+
fn read<T: FromStr>(flag_name: &str) -> Option<T> {
let value = GetServerConfigurableFlag(SERVER_CONFIG_NAMESPACE, flag_name, "");
if !value.is_empty() {
diff --git a/src/zram.rs b/src/zram.rs
index c22011c..7aeb4a2 100644
--- a/src/zram.rs
+++ b/src/zram.rs
@@ -16,6 +16,7 @@
pub mod idle;
pub mod recompression;
+pub mod setup;
pub mod stats;
pub mod writeback;
@@ -51,6 +52,8 @@
pub trait SysfsZramApi {
/// Read "/sys/block/zram0/disksize".
fn read_disksize() -> io::Result<String>;
+ /// Write "/sys/block/zram0/disksize".
+ fn write_disksize(contents: &str) -> io::Result<()>;
/// Read "/sys/block/zram0/mm_stat".
fn read_mm_stat() -> io::Result<String>;
@@ -82,6 +85,10 @@
std::fs::read_to_string(ZRAM_DISKSIZE_PATH)
}
+ fn write_disksize(contents: &str) -> io::Result<()> {
+ std::fs::write(ZRAM_DISKSIZE_PATH, contents)
+ }
+
fn read_mm_stat() -> io::Result<String> {
std::fs::read_to_string(ZRAM_MM_STAT_PATH)
}
diff --git a/src/zram/setup.rs b/src/zram/setup.rs
new file mode 100644
index 0000000..6e78039
--- /dev/null
+++ b/src/zram/setup.rs
@@ -0,0 +1,160 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This module implements zram setup functionality.
+//!
+//! The setup implemented in this module assumes that the zram kernel module has been loaded early on init with only 1 zram device (`zram0`).
+//!
+//! zram kernel documentation https://docs.kernel.org/admin-guide/blockdev/zram.html
+
+#[cfg(test)]
+mod tests;
+
+use std::io;
+
+use crate::os::get_page_count;
+use crate::os::get_page_size;
+use crate::zram::SysfsZramApi;
+
+const MKSWAP_BIN_PATH: &str = "/system/bin/mkswap";
+const ZRAM_DEVICE_PATH: &str = "/dev/block/zram0";
+const PROC_SWAPS_PATH: &str = "/proc/swaps";
+
+const MAX_ZRAM_PERCENTAGE_ALLOWED: u64 = 500;
+
+/// [SetupApi] is the mockable interface for swap operations.
+#[cfg_attr(test, mockall::automock)]
+pub trait SetupApi {
+ /// Set up zram swap device, returning whether the command succeeded and its output.
+ fn mkswap(device_path: &str) -> io::Result<std::process::Output>;
+ /// Specify the zram swap device.
+ fn swapon(device_path: &std::ffi::CStr) -> io::Result<()>;
+ /// Read swaps areas in use.
+ fn read_swap_areas() -> io::Result<String>;
+}
+
+/// The implementation of [SetupApi].
+pub struct SetupApiImpl;
+
+impl SetupApi for SetupApiImpl {
+ fn mkswap(device_path: &str) -> io::Result<std::process::Output> {
+ std::process::Command::new(MKSWAP_BIN_PATH).arg(device_path).output()
+ }
+
+ fn swapon(device_path: &std::ffi::CStr) -> io::Result<()> {
+ // SAFETY: device_path is a nul-terminated string.
+ let res = unsafe { libc::swapon(device_path.as_ptr(), 0) };
+ if res == 0 {
+ Ok(())
+ } else {
+ Err(std::io::Error::last_os_error())
+ }
+ }
+
+ fn read_swap_areas() -> io::Result<String> {
+ std::fs::read_to_string(PROC_SWAPS_PATH)
+ }
+}
+
+/// Whether or not zram is already set up on the device.
+pub fn is_zram_swap_activated<S: SetupApi>() -> io::Result<bool> {
+ let swaps = S::read_swap_areas()?;
+ // Skip the first line which is header.
+ let swap_lines = swaps.lines().skip(1);
+ // Swap is turned on if swap file contains entry with zram keyword.
+ for line in swap_lines {
+ if line.contains("zram") {
+ return Ok(true);
+ }
+ }
+ Ok(false)
+}
+
+/// Error from [parse_zram_size_spec].
+#[derive(Debug, thiserror::Error)]
+pub enum ZramSpecError {
+ /// Zram size was not specified
+ #[error("zram size is not specified")]
+ EmptyZramSizeSpec,
+ /// Zram size percentage needs to be between 1 and 500%
+ #[error(
+ "zram size percentage {0} is out of range (expected the between 1 and {})",
+ MAX_ZRAM_PERCENTAGE_ALLOWED
+ )]
+ ZramPercentageOutOfRange(u64),
+ /// Parsing zram size error
+ #[error("zram size is not an int: {0}")]
+ ParseZramSize(#[from] std::num::ParseIntError),
+}
+
+/// Parse zram size that can be specified by a percentage or an absolute value.
+pub fn parse_zram_size_spec(spec: &str) -> Result<u64, ZramSpecError> {
+ parse_size_spec_with_page_info(spec, get_page_size(), get_page_count())
+}
+
+fn parse_size_spec_with_page_info(
+ spec: &str,
+ system_page_size: u64,
+ system_page_count: u64,
+) -> Result<u64, ZramSpecError> {
+ if spec.is_empty() {
+ return Err(ZramSpecError::EmptyZramSizeSpec);
+ }
+
+ if let Some(percentage_str) = spec.strip_suffix('%') {
+ let percentage = percentage_str.parse::<u64>()?;
+ if percentage == 0 || percentage > MAX_ZRAM_PERCENTAGE_ALLOWED {
+ return Err(ZramSpecError::ZramPercentageOutOfRange(percentage));
+ }
+ return Ok(system_page_count * percentage / 100 * system_page_size);
+ }
+
+ let zram_size = spec.parse::<u64>()?;
+ Ok(zram_size)
+}
+
+/// Error from [activate].
+#[derive(Debug, thiserror::Error)]
+pub enum ZramActivationError {
+ /// Failed to update zram disk size
+ #[error("failed to write zram disk size: {0}")]
+ UpdateZramDiskSize(std::io::Error),
+ /// Failed to swapon
+ #[error("swapon failed: {0}")]
+ SwapOn(std::io::Error),
+ /// Mkswap command failed
+ #[error("failed to execute mkswap: {0}")]
+ ExecuteMkSwap(std::io::Error),
+ /// Mkswap command failed
+ #[error("mkswap failed: {0:?}")]
+ MkSwap(std::process::Output),
+}
+
+/// Set up a zram device with provided parameters.
+pub fn activate_zram<Z: SysfsZramApi, S: SetupApi>(
+ zram_size: u64,
+) -> Result<(), ZramActivationError> {
+ Z::write_disksize(&zram_size.to_string()).map_err(ZramActivationError::UpdateZramDiskSize)?;
+
+ let output = S::mkswap(ZRAM_DEVICE_PATH).map_err(ZramActivationError::ExecuteMkSwap)?;
+ if !output.status.success() {
+ return Err(ZramActivationError::MkSwap(output));
+ }
+
+ let zram_device_path_cstring = std::ffi::CString::new(ZRAM_DEVICE_PATH)
+ .expect("device path should have no nul characters");
+ S::swapon(&zram_device_path_cstring).map_err(ZramActivationError::SwapOn)?;
+
+ Ok(())
+}
diff --git a/src/zram/setup/tests.rs b/src/zram/setup/tests.rs
new file mode 100644
index 0000000..2f9c28f
--- /dev/null
+++ b/src/zram/setup/tests.rs
@@ -0,0 +1,191 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use super::*;
+use mockall::predicate::*;
+use mockall::Sequence;
+use std::os::unix::process::ExitStatusExt;
+use std::sync::LockResult;
+use std::sync::Mutex;
+use std::sync::MutexGuard;
+
+use crate::zram::MockSysfsZramApi;
+use crate::zram::ZRAM_API_MTX;
+
+const PROC_SWAP_HEADER: &str = "Filename Type Size Used Priority\n";
+const DEFAULT_PAGE_SIZE: u64 = 4096;
+const DEFAULT_PAGE_COUNT: u64 = 998875;
+const DEFAULT_ZRAM_SIZE: u64 = 1000000;
+
+fn success_command_output() -> std::process::Output {
+ std::process::Output {
+ status: std::process::ExitStatus::from_raw(0),
+ stderr: "".to_owned().into_bytes(),
+ stdout: "".to_owned().into_bytes(),
+ }
+}
+
+fn failure_command_output() -> std::process::Output {
+ std::process::Output {
+ status: std::process::ExitStatus::from_raw(1),
+ stderr: "".to_owned().into_bytes(),
+ stdout: "".to_owned().into_bytes(),
+ }
+}
+
+/// Mutex to synchronize tests using [MockSetupApi].
+///
+/// mockall for static functions requires synchronization.
+///
+/// https://docs.rs/mockall/latest/mockall/#static-methods
+pub static SETUP_API_MTX: Mutex<()> = Mutex::new(());
+
+struct MockContext<'a> {
+ write_disksize: crate::zram::__mock_MockSysfsZramApi_SysfsZramApi::__write_disksize::Context,
+ read_swap_areas: crate::zram::setup::__mock_MockSetupApi_SetupApi::__read_swap_areas::Context,
+ mkswap: crate::zram::setup::__mock_MockSetupApi_SetupApi::__mkswap::Context,
+ swapon: crate::zram::setup::__mock_MockSetupApi_SetupApi::__swapon::Context,
+ // Lock will be released after mock contexts are dropped.
+ _setup_lock: LockResult<MutexGuard<'a, ()>>,
+ _zram_lock: LockResult<MutexGuard<'a, ()>>,
+}
+
+impl<'a> MockContext<'a> {
+ fn new() -> Self {
+ let _zram_lock = ZRAM_API_MTX.lock();
+ let _setup_lock = SETUP_API_MTX.lock();
+ Self {
+ write_disksize: MockSysfsZramApi::write_disksize_context(),
+ read_swap_areas: MockSetupApi::read_swap_areas_context(),
+ mkswap: MockSetupApi::mkswap_context(),
+ swapon: MockSetupApi::swapon_context(),
+ _setup_lock,
+ _zram_lock,
+ }
+ }
+}
+
+#[test]
+fn is_zram_swap_activated_zram_off() {
+ let mock = MockContext::new();
+ mock.read_swap_areas.expect().returning(|| Ok(PROC_SWAP_HEADER.to_string()));
+
+ assert!(!is_zram_swap_activated::<MockSetupApi>().unwrap());
+}
+
+#[test]
+fn is_zram_swap_activated_zram_on() {
+ let mock = MockContext::new();
+ let zram_area = "/dev/block/zram0 partition 7990996 87040 -2\n";
+ mock.read_swap_areas.expect().returning(|| Ok(PROC_SWAP_HEADER.to_owned() + zram_area));
+
+ assert!(is_zram_swap_activated::<MockSetupApi>().unwrap());
+}
+
+#[test]
+fn parse_zram_spec_invalid() {
+ assert!(parse_size_spec_with_page_info("", DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT).is_err());
+ assert!(
+ parse_size_spec_with_page_info("not_int%", DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT).is_err()
+ );
+ assert!(
+ parse_size_spec_with_page_info("not_int", DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT).is_err()
+ );
+}
+
+#[test]
+fn parse_zram_spec_percentage_out_of_range() {
+ assert!(parse_size_spec_with_page_info("0%", DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT).is_err());
+ assert!(parse_size_spec_with_page_info("501%", DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT).is_err());
+}
+
+#[test]
+fn parse_zram_spec_percentage() {
+ assert_eq!(parse_size_spec_with_page_info("33%", 4096, 5).unwrap(), 4096);
+ assert_eq!(parse_size_spec_with_page_info("50%", 4096, 5).unwrap(), 8192);
+ assert_eq!(parse_size_spec_with_page_info("100%", 4096, 5).unwrap(), 20480);
+ assert_eq!(parse_size_spec_with_page_info("200%", 4096, 5).unwrap(), 40960);
+ assert_eq!(parse_size_spec_with_page_info("100%", 4096, 3995500).unwrap(), 16365568000);
+}
+
+#[test]
+fn parse_zram_spec_bytes() {
+ assert_eq!(
+ parse_size_spec_with_page_info("1234567", DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT).unwrap(),
+ 1234567
+ );
+}
+
+#[test]
+fn activate_success() {
+ let mock = MockContext::new();
+ let zram_size = 123456;
+ let mut seq = Sequence::new();
+ mock.write_disksize
+ .expect()
+ .with(eq("123456"))
+ .times(1)
+ .returning(|_| Ok(()))
+ .in_sequence(&mut seq);
+ mock.mkswap
+ .expect()
+ .with(eq(ZRAM_DEVICE_PATH))
+ .times(1)
+ .returning(|_| Ok(success_command_output()))
+ .in_sequence(&mut seq);
+ mock.swapon
+ .expect()
+ .with(eq(std::ffi::CString::new(ZRAM_DEVICE_PATH).unwrap()))
+ .times(1)
+ .returning(|_| Ok(()))
+ .in_sequence(&mut seq);
+
+ assert!(activate_zram::<MockSysfsZramApi, MockSetupApi>(zram_size).is_ok());
+}
+
+#[test]
+fn activate_failed_update_size() {
+ let mock = MockContext::new();
+ mock.write_disksize.expect().returning(|_| Err(std::io::Error::other("error")));
+
+ assert!(matches!(
+ activate_zram::<MockSysfsZramApi, MockSetupApi>(DEFAULT_ZRAM_SIZE),
+ Err(ZramActivationError::UpdateZramDiskSize(_))
+ ));
+}
+
+#[test]
+fn activate_failed_mkswap() {
+ let mock = MockContext::new();
+ mock.write_disksize.expect().returning(|_| Ok(()));
+ mock.mkswap.expect().returning(|_| Ok(failure_command_output()));
+
+ assert!(matches!(
+ activate_zram::<MockSysfsZramApi, MockSetupApi>(DEFAULT_ZRAM_SIZE),
+ Err(ZramActivationError::MkSwap(_))
+ ));
+}
+
+#[test]
+fn activate_failed_swapon() {
+ let mock = MockContext::new();
+ mock.write_disksize.expect().returning(|_| Ok(()));
+ mock.mkswap.expect().returning(|_| Ok(success_command_output()));
+ mock.swapon.expect().returning(|_| Err(std::io::Error::other("error")));
+
+ assert!(matches!(
+ activate_zram::<MockSysfsZramApi, MockSetupApi>(DEFAULT_ZRAM_SIZE),
+ Err(ZramActivationError::SwapOn(_))
+ ));
+}