Skip to main content

btrfs_cli/filesystem/
mkswapfile.rs

1use crate::{
2    Format, Runnable,
3    util::{ParsedUuid, parse_size_with_suffix},
4};
5use anyhow::{Context, Result};
6use clap::Parser;
7use nix::{
8    fcntl::{FallocateFlags, fallocate},
9    libc,
10};
11use std::{
12    fs::{File, OpenOptions},
13    os::unix::{
14        fs::{FileExt, OpenOptionsExt},
15        io::AsRawFd,
16    },
17    path::PathBuf,
18};
19use uuid::Uuid;
20
21const FS_NOCOW_FL: libc::c_long = 0x0080_0000;
22const MIN_SWAP_SIZE: u64 = 40 * 1024;
23
24/// Create a swapfile on a btrfs filesystem
25#[derive(Parser, Debug)]
26pub struct FilesystemMkswapfileCommand {
27    /// Size of the swapfile
28    #[clap(long, short, default_value = "2G")]
29    pub size: String,
30
31    /// UUID to embed in the swap header (clear, random, time, or explicit UUID;
32    /// default: random)
33    #[clap(long = "uuid", short = 'U')]
34    pub uuid: Option<ParsedUuid>,
35
36    /// Path to the swapfile to create
37    pub path: PathBuf,
38}
39
40fn system_page_size() -> Result<u64> {
41    let size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
42    anyhow::ensure!(size > 0, "failed to get system page size");
43    #[allow(clippy::cast_sign_loss)]
44    // sysconf returns positive value after ensure check
45    Ok(size as u64)
46}
47
48fn write_swap_header(
49    file: &File,
50    page_count: u32,
51    uuid: &Uuid,
52    page_size: u64,
53) -> Result<()> {
54    #[allow(clippy::cast_possible_truncation)] // page_size fits in usize
55    let mut header = vec![0u8; page_size as usize];
56    header[0x400] = 0x01;
57    header[0x404..0x408].copy_from_slice(&page_count.to_le_bytes());
58    header[0x40c..0x41c].copy_from_slice(uuid.as_bytes());
59    #[allow(clippy::cast_possible_truncation)] // page_size fits in usize
60    let sig_offset = page_size as usize - 10;
61    header[sig_offset..].copy_from_slice(b"SWAPSPACE2");
62    file.write_at(&header, 0)
63        .context("failed to write swap header")?;
64    Ok(())
65}
66
67impl Runnable for FilesystemMkswapfileCommand {
68    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
69        let size = parse_size_with_suffix(&self.size)
70            .with_context(|| format!("invalid size: '{}'", self.size))?;
71
72        let page_size = system_page_size()?;
73
74        anyhow::ensure!(
75            size >= MIN_SWAP_SIZE,
76            "swapfile needs to be at least 40 KiB, got {size} bytes"
77        );
78
79        let uuid = self.uuid.as_deref().copied().unwrap_or_else(Uuid::new_v4);
80
81        let size = size - (size % page_size);
82        let total_pages = size / page_size;
83
84        anyhow::ensure!(
85            total_pages > 10,
86            "swapfile too small after page alignment"
87        );
88
89        let page_count = total_pages - 1;
90        anyhow::ensure!(
91            u32::try_from(page_count).is_ok(),
92            "swapfile too large: page count exceeds u32"
93        );
94
95        let file = OpenOptions::new()
96            .read(true)
97            .write(true)
98            .create_new(true)
99            .mode(0o600)
100            .open(&self.path)
101            .with_context(|| {
102                format!("failed to create '{}'", self.path.display())
103            })?;
104
105        let ret = unsafe {
106            libc::ioctl(file.as_raw_fd(), libc::FS_IOC_SETFLAGS, &FS_NOCOW_FL)
107        };
108        nix::errno::Errno::result(ret)
109            .context("failed to set NOCOW attribute")?;
110
111        #[allow(clippy::cast_possible_wrap)] // size fits in off_t
112        fallocate(&file, FallocateFlags::empty(), 0, size as libc::off_t)
113            .context("failed to allocate space for swapfile")?;
114
115        #[allow(clippy::cast_possible_truncation)]
116        // validated above with try_from
117        write_swap_header(&file, page_count as u32, &uuid, page_size)?;
118
119        println!(
120            "created swapfile '{}' size {} bytes",
121            self.path.display(),
122            crate::util::human_bytes(size),
123        );
124
125        Ok(())
126    }
127}