// Copyright 2022, 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. //! Logic for configuring and enabling a ZRAM-backed swap device. use anyhow::{anyhow, Context, Result}; use std::fs::{read_to_string, OpenOptions}; use std::io::{Error, Seek, SeekFrom, Write}; use uuid::Uuid; const SWAP_DEV: &str = "block/zram0"; /// Parse "MemTotal: N kB" from /proc/meminfo fn get_total_memory_kb() -> Result { let s = read_to_string("/proc/meminfo")?; let mut iter = s.split_whitespace(); while let Some(x) = iter.next() { if x.starts_with("MemTotal:") { let n = iter.next().context("No text after MemTotal")?; return n.parse::().context("No u32 after MemTotal"); } } Err(anyhow!("MemTotal not found in /proc/meminfo")) } /// Simple "mkswap": Writes swap-device header into specified device. /// The header has no formal public definition, but it can be found in the /// Linux source tree at include/linux/swap.h (union swap_header). /// This implementation is inspired by the one in Toybox. fn mkswap(dev: &str) -> Result<()> { // Size of device, in bytes. let sysfs_size = format!("/sys/{}/size", dev); let len = read_to_string(&sysfs_size)? .trim() .parse::() .context(format!("No u64 in {}", &sysfs_size))? .checked_mul(512) .ok_or_else(|| anyhow!("sysfs_size too large"))?; // SAFETY: We give a constant and known-valid sysconf parameter. let pagesize = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) as u64 }; let mut f = OpenOptions::new().read(false).write(true).open(format!("/dev/{}", dev))?; let last_page = len / pagesize - 1; // Write the info fields: [ version, last_page ] let info: [u32; 2] = [1, last_page.try_into().context("Number of pages out of range")?]; f.seek(SeekFrom::Start(1024))?; f.write_all(&info.iter().flat_map(|v| v.to_ne_bytes()).collect::>())?; // Write a random version 4 UUID f.seek(SeekFrom::Start(1024 + 12))?; f.write_all(Uuid::new_v4().as_bytes())?; // Write the magic signature string. f.seek(SeekFrom::Start(pagesize - 10))?; f.write_all("SWAPSPACE2".as_bytes())?; Ok(()) } /// Simple "swapon", using libc:: wrapper. fn swapon(dev: &str) -> Result<()> { let swapon_arg = std::ffi::CString::new(format!("/dev/{}", dev))?; // SAFETY: We give a nul-terminated string and check the result. let res = unsafe { libc::swapon(swapon_arg.as_ptr(), 0) }; if res != 0 { return Err(anyhow!("Failed to swapon: {}", Error::last_os_error())); } Ok(()) } /// Turn on ZRAM-backed swap pub fn init_swap() -> Result<()> { let dev = SWAP_DEV; // Create a ZRAM block device the same size as total VM memory. let mem_kb = get_total_memory_kb()?; OpenOptions::new() .read(false) .write(true) .open(format!("/sys/{}/disksize", dev))? .write_all(format!("{}K", mem_kb).as_bytes())?; mkswap(dev)?; swapon(dev)?; Ok(()) }