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
get_total_memory_kb() -> Result<u32>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.
mkswap(dev: &str) -> Result<()>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()
46         .parse::<u64>()
47         .context(format!("No u64 in {}", &sysfs_size))?
48         .checked_mul(512)
49         .ok_or_else(|| anyhow!("sysfs_size too large"))?;
50 
51     // SAFETY: We give a constant and known-valid sysconf parameter.
52     let pagesize = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) as u64 };
53 
54     let mut f = OpenOptions::new().read(false).write(true).open(format!("/dev/{}", dev))?;
55 
56     let last_page = len / pagesize - 1;
57 
58     // Write the info fields: [ version, last_page ]
59     let info: [u32; 2] = [1, last_page.try_into().context("Number of pages out of range")?];
60 
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.
69     f.seek(SeekFrom::Start(pagesize - 10))?;
70     f.write_all("SWAPSPACE2".as_bytes())?;
71 
72     Ok(())
73 }
74 
75 /// Simple "swapon", using libc:: wrapper.
swapon(dev: &str) -> Result<()>76 fn swapon(dev: &str) -> Result<()> {
77     let swapon_arg = std::ffi::CString::new(format!("/dev/{}", dev))?;
78     // SAFETY: 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
init_swap() -> Result<()>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 }
104