Keir Fraser | 933f0ac | 2022-10-12 08:23:28 +0000 | [diff] [blame] | 1 | // Copyright 2022, 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 | //! Logic for configuring and enabling a ZRAM-backed swap device. |
| 16 | |
| 17 | use anyhow::{anyhow, Context, Result}; |
| 18 | use std::fs::{read_to_string, OpenOptions}; |
| 19 | use std::io::{Error, Seek, SeekFrom, Write}; |
| 20 | use uuid::Uuid; |
| 21 | |
| 22 | const SWAP_DEV: &str = "block/zram0"; |
| 23 | |
| 24 | /// Parse "MemTotal: N kB" from /proc/meminfo |
| 25 | fn get_total_memory_kb() -> Result<u32> { |
| 26 | let s = read_to_string("/proc/meminfo")?; |
| 27 | let mut iter = s.split_whitespace(); |
| 28 | while let Some(x) = iter.next() { |
| 29 | if x.starts_with("MemTotal:") { |
| 30 | let n = iter.next().context("No text after MemTotal")?; |
| 31 | return n.parse::<u32>().context("No u32 after MemTotal"); |
| 32 | } |
| 33 | } |
| 34 | Err(anyhow!("MemTotal not found in /proc/meminfo")) |
| 35 | } |
| 36 | |
| 37 | /// Simple "mkswap": Writes swap-device header into specified device. |
| 38 | /// The header has no formal public definition, but it can be found in the |
| 39 | /// Linux source tree at include/linux/swap.h (union swap_header). |
| 40 | /// This implementation is inspired by the one in Toybox. |
| 41 | fn mkswap(dev: &str) -> Result<()> { |
| 42 | // Size of device, in bytes. |
| 43 | let sysfs_size = format!("/sys/{}/size", dev); |
| 44 | let len = read_to_string(&sysfs_size)? |
| 45 | .trim() |
Inseob Kim | 888f48d | 2022-11-11 16:49:37 +0900 | [diff] [blame] | 46 | .parse::<u64>() |
| 47 | .context(format!("No u64 in {}", &sysfs_size))? |
| 48 | .checked_mul(512) |
| 49 | .ok_or_else(|| anyhow!("sysfs_size too large"))?; |
Keir Fraser | 933f0ac | 2022-10-12 08:23:28 +0000 | [diff] [blame] | 50 | |
Keir Fraser | 933f0ac | 2022-10-12 08:23:28 +0000 | [diff] [blame] | 51 | // safe because we give a constant and known-valid sysconf parameter |
Inseob Kim | 888f48d | 2022-11-11 16:49:37 +0900 | [diff] [blame] | 52 | let pagesize = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) as u64 }; |
Keir Fraser | 933f0ac | 2022-10-12 08:23:28 +0000 | [diff] [blame] | 53 | |
| 54 | let mut f = OpenOptions::new().read(false).write(true).open(format!("/dev/{}", dev))?; |
| 55 | |
Inseob Kim | 888f48d | 2022-11-11 16:49:37 +0900 | [diff] [blame] | 56 | let last_page = len / pagesize - 1; |
| 57 | |
Keir Fraser | 933f0ac | 2022-10-12 08:23:28 +0000 | [diff] [blame] | 58 | // Write the info fields: [ version, last_page ] |
Inseob Kim | 888f48d | 2022-11-11 16:49:37 +0900 | [diff] [blame] | 59 | let info: [u32; 2] = [1, last_page.try_into().context("Number of pages out of range")?]; |
| 60 | |
Keir Fraser | 933f0ac | 2022-10-12 08:23:28 +0000 | [diff] [blame] | 61 | f.seek(SeekFrom::Start(1024))?; |
| 62 | f.write_all(&info.iter().flat_map(|v| v.to_ne_bytes()).collect::<Vec<u8>>())?; |
| 63 | |
| 64 | // Write a random version 4 UUID |
| 65 | f.seek(SeekFrom::Start(1024 + 12))?; |
| 66 | f.write_all(Uuid::new_v4().as_bytes())?; |
| 67 | |
| 68 | // Write the magic signature string. |
Charisee | 96113f3 | 2023-01-26 09:00:42 +0000 | [diff] [blame] | 69 | f.seek(SeekFrom::Start(pagesize - 10))?; |
Keir Fraser | 933f0ac | 2022-10-12 08:23:28 +0000 | [diff] [blame] | 70 | f.write_all("SWAPSPACE2".as_bytes())?; |
| 71 | |
| 72 | Ok(()) |
| 73 | } |
| 74 | |
| 75 | /// Simple "swapon", using libc:: wrapper. |
| 76 | fn swapon(dev: &str) -> Result<()> { |
| 77 | let swapon_arg = std::ffi::CString::new(format!("/dev/{}", dev))?; |
| 78 | // safe because we give a nul-terminated string and check the result |
| 79 | let res = unsafe { libc::swapon(swapon_arg.as_ptr(), 0) }; |
| 80 | if res != 0 { |
| 81 | return Err(anyhow!("Failed to swapon: {}", Error::last_os_error())); |
| 82 | } |
| 83 | Ok(()) |
| 84 | } |
| 85 | |
| 86 | /// Turn on ZRAM-backed swap |
| 87 | pub fn init_swap() -> Result<()> { |
| 88 | let dev = SWAP_DEV; |
| 89 | |
| 90 | // Create a ZRAM block device the same size as total VM memory. |
| 91 | let mem_kb = get_total_memory_kb()?; |
| 92 | OpenOptions::new() |
| 93 | .read(false) |
| 94 | .write(true) |
| 95 | .open(format!("/sys/{}/disksize", dev))? |
| 96 | .write_all(format!("{}K", mem_kb).as_bytes())?; |
| 97 | |
| 98 | mkswap(dev)?; |
| 99 | |
| 100 | swapon(dev)?; |
| 101 | |
| 102 | Ok(()) |
| 103 | } |