btrfs_cli/filesystem/
mkswapfile.rs1use 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#[derive(Parser, Debug)]
26pub struct FilesystemMkswapfileCommand {
27 #[clap(long, short, default_value = "2G")]
29 pub size: String,
30
31 #[clap(long = "uuid", short = 'U')]
34 pub uuid: Option<ParsedUuid>,
35
36 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 Ok(size as u64)
44}
45
46fn write_swap_header(
47 file: &File,
48 page_count: u32,
49 uuid: &Uuid,
50 page_size: u64,
51) -> Result<()> {
52 let mut header = vec![0u8; page_size as usize];
53 header[0x400] = 0x01;
54 header[0x404..0x408].copy_from_slice(&page_count.to_le_bytes());
55 header[0x40c..0x41c].copy_from_slice(uuid.as_bytes());
56 let sig_offset = page_size as usize - 10;
57 header[sig_offset..].copy_from_slice(b"SWAPSPACE2");
58 file.write_at(&header, 0)
59 .context("failed to write swap header")?;
60 Ok(())
61}
62
63impl Runnable for FilesystemMkswapfileCommand {
64 fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
65 let size = parse_size_with_suffix(&self.size)
66 .with_context(|| format!("invalid size: '{}'", self.size))?;
67
68 let page_size = system_page_size()?;
69
70 anyhow::ensure!(
71 size >= MIN_SWAP_SIZE,
72 "swapfile needs to be at least 40 KiB, got {size} bytes"
73 );
74
75 let uuid = self.uuid.as_deref().copied().unwrap_or_else(Uuid::new_v4);
76
77 let size = size - (size % page_size);
78 let total_pages = size / page_size;
79
80 anyhow::ensure!(
81 total_pages > 10,
82 "swapfile too small after page alignment"
83 );
84
85 let page_count = total_pages - 1;
86 anyhow::ensure!(
87 u32::try_from(page_count).is_ok(),
88 "swapfile too large: page count exceeds u32"
89 );
90
91 let file = OpenOptions::new()
92 .read(true)
93 .write(true)
94 .create_new(true)
95 .mode(0o600)
96 .open(&self.path)
97 .with_context(|| {
98 format!("failed to create '{}'", self.path.display())
99 })?;
100
101 let ret = unsafe {
102 libc::ioctl(file.as_raw_fd(), libc::FS_IOC_SETFLAGS, &FS_NOCOW_FL)
103 };
104 nix::errno::Errno::result(ret)
105 .context("failed to set NOCOW attribute")?;
106
107 fallocate(&file, FallocateFlags::empty(), 0, size as libc::off_t)
108 .context("failed to allocate space for swapfile")?;
109
110 write_swap_header(&file, page_count as u32, &uuid, page_size)?;
111
112 println!(
113 "created swapfile '{}' size {} bytes",
114 self.path.display(),
115 crate::util::human_bytes(size),
116 );
117
118 Ok(())
119 }
120}