| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! Implements LVM helper functions. |
| |
| use std::fs::OpenOptions; |
| use std::fs::{self}; |
| use std::io::Seek; |
| use std::io::SeekFrom; |
| use std::io::Write; |
| use std::path::Path; |
| use std::path::PathBuf; |
| use std::process::Command; |
| use std::str; |
| |
| use anyhow::Context; |
| use anyhow::Result; |
| use log::info; |
| use log::warn; |
| |
| use crate::hiberutil::checked_command; |
| use crate::hiberutil::checked_command_output; |
| use crate::hiberutil::is_snapshot_active; |
| use crate::hiberutil::stateful_block_partition_one; |
| |
| /// Define the minimum size of a block device sector. |
| const SECTOR_SIZE: usize = 512; |
| /// Define the size of an LVM extent. |
| const LVM_EXTENT_SIZE: u64 = 64 * 1024; |
| |
| /// Get the path to the given logical volume. |
| pub fn lv_path(volume_group: &str, name: &str) -> PathBuf { |
| PathBuf::from(format!("/dev/{}/{}", volume_group, name)) |
| } |
| |
| /// Get the volume group name for the stateful block device. |
| pub fn get_vg_name(blockdev: &str) -> Result<String> { |
| let output = checked_command_output(Command::new("/sbin/pvdisplay").args([ |
| "-C", |
| "--noheadings", |
| "-o", |
| "vg_name", |
| blockdev, |
| ])) |
| .context("Cannot run pvdisplay to get volume group name")?; |
| |
| Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) |
| } |
| |
| /// Determine if the given logical volume exists. |
| pub fn lv_exists(volume_group: &str, name: &str) -> Result<bool> { |
| let volume = full_lv_name(volume_group, name); |
| let output = Command::new("/sbin/lvdisplay") |
| .arg(&volume) |
| .output() |
| .context("Failed to get output for child process")?; |
| Ok(output.status.success()) |
| } |
| |
| /// Enumerate all logical volumes in a volume group. |
| pub fn get_lvs(volume_group: &str) -> Result<Vec<String>> { |
| let output = checked_command_output(Command::new("/sbin/lvdisplay").args([ |
| "-C", |
| "--options=name", |
| "--noheadings", |
| volume_group, |
| ])) |
| .context("Failed to LVs in volume group '{volume_group}'")?; |
| let output_string = String::from_utf8_lossy(&output.stdout); |
| let mut elements: Vec<String> = vec![]; |
| output_string.split_whitespace().for_each(|e| { |
| elements.push(e.trim().to_string()); |
| }); |
| |
| Ok(elements) |
| } |
| |
| /// Activate a logical volume. |
| pub fn activate_lv(volume_group: &str, name: &str) -> Result<()> { |
| if lv_path(volume_group, name).exists() { |
| // LV is already active |
| return Ok(()); |
| } |
| |
| let full_name = full_lv_name(volume_group, name); |
| checked_command(Command::new("/sbin/lvchange").args(["-ay", &full_name])) |
| .context("Failed to activate logical volume '{full_name}'")?; |
| |
| Ok(()) |
| } |
| |
| /// Create a new thinpool volume under the given volume group, with the |
| /// specified name and size. |
| pub fn create_thin_volume(volume_group: &str, size: u64, name: &str) -> Result<()> { |
| // lvcreate --thin -V "${lv_size}b" -n "{name}" "${volume_group}/thinpool" |
| let size_arg = format!("{}b", size); |
| let thinpool = format!("{}/thinpool", volume_group); |
| checked_command( |
| Command::new("/sbin/lvcreate").args(["--thin", "-V", &size_arg, "-n", name, &thinpool]), |
| ) |
| .context("Cannot create logical volume") |
| } |
| |
| /// Take a newly created thin volume and ensure space is fully allocated for it |
| /// from the thinpool. This is destructive to the data on the volume. |
| pub fn thicken_thin_volume<P: AsRef<Path>>(path: P, size: u64) -> Result<()> { |
| let mut file = OpenOptions::new() |
| .write(true) |
| .open(path.as_ref()) |
| .context(format!( |
| "Failed to open thin disk: {}", |
| path.as_ref().display() |
| ))?; |
| let buf = [0u8; SECTOR_SIZE]; |
| let skip = LVM_EXTENT_SIZE - (SECTOR_SIZE as u64); |
| let mut offset = 0; |
| |
| loop { |
| file.write_all(&buf).context("Failed to thicken LV")?; |
| offset += LVM_EXTENT_SIZE; |
| if offset >= size { |
| break; |
| } |
| // Unwrap is fine here because LVM_EXTENT_SIZE can fit in an i64. |
| file.seek(SeekFrom::Current(skip.try_into().unwrap())) |
| .context(format!("Failed to seek {}/{} in LV", offset + skip, size))?; |
| } |
| |
| Ok(()) |
| } |
| |
| /// Remove a logical volume. |
| pub fn lv_remove(volume_group: &str, name: &str) -> Result<()> { |
| let volume = full_lv_name(volume_group, name); |
| |
| checked_command(Command::new("/sbin/lvremove").args(["-y", &volume])) |
| .context(format!("Failed to remove logical volume '{volume}'")) |
| } |
| |
| /// Get the fully qualified name of an LV. |
| fn full_lv_name(volume_group: &str, name: &str) -> String { |
| format!("{}/{}", volume_group, name) |
| } |
| |
| pub struct ActivatedLogicalVolume { |
| lv_arg: Option<String>, |
| } |
| |
| impl ActivatedLogicalVolume { |
| pub fn new(vg_name: &str, lv_name: &str) -> Result<Option<Self>> { |
| // If it already exists, don't reactivate it. |
| if fs::metadata(lv_path(vg_name, lv_name)).is_ok() { |
| return Ok(None); |
| } |
| |
| activate_lv(vg_name, lv_name)?; |
| |
| Ok(Some(Self { |
| lv_arg: Some(full_lv_name(vg_name, lv_name)), |
| })) |
| } |
| |
| /// Don't deactivate the logical volume on drop. |
| pub fn dont_deactivate(&mut self) { |
| self.lv_arg = None; |
| } |
| } |
| |
| impl Drop for ActivatedLogicalVolume { |
| fn drop(&mut self) { |
| if let Some(lv_arg) = self.lv_arg.take() { |
| let r = checked_command(Command::new("/sbin/lvchange").args(["-an", &lv_arg])); |
| |
| match r { |
| Ok(_) => { |
| info!("Deactivated LV {}", lv_arg); |
| } |
| Err(e) => { |
| warn!("Failed to deactivate LV {}: {}", lv_arg, e); |
| } |
| } |
| } |
| } |
| } |
| |
| pub fn activate_physical_lv(lv_name: &str) -> Result<Option<ActivatedLogicalVolume>> { |
| if !is_snapshot_active() { |
| return Ok(None); |
| } |
| |
| let partition1 = stateful_block_partition_one()?; |
| // Assume that a failure to get the VG name indicates a non-LVM system. |
| let vg_name = match get_vg_name(&partition1) { |
| Ok(vg) => vg, |
| Err(_) => { |
| return Ok(None); |
| } |
| }; |
| |
| ActivatedLogicalVolume::new(&vg_name, lv_name) |
| } |