| // Copyright 2021 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::path::{Path, PathBuf}; |
| use std::process; |
| |
| use anyhow::{Context, Error}; |
| |
| use crate::lsblk::{get_lsblk_devices, LsBlkDevice}; |
| use crate::util::get_command_output; |
| |
| /// Find device path for the disk containing the root filesystem. |
| /// |
| /// The return value is a string in /dev, for example "/dev/sda". |
| fn get_root_disk_device_path() -> Result<PathBuf, Error> { |
| let mut command = process::Command::new("rootdev"); |
| command.args(&["-s", "-d"]); |
| let output = get_command_output(command)?; |
| let output = String::from_utf8(output)?; |
| let trimmed = output.trim(); |
| Ok(Path::new(trimmed).into()) |
| } |
| |
| /// Information about a disk device. |
| #[derive(Clone, Debug, Eq, PartialEq)] |
| pub struct Disk { |
| /// Absolute disk path, e.g. "/dev/sda". |
| pub device_path: PathBuf, |
| |
| /// Whether the disk is removable. This is not completely |
| /// reliable, for example USB SSDs may not show as removable. |
| pub is_removable: bool, |
| |
| /// Whether this disk is what the OS is running from. |
| pub is_root: bool, |
| |
| /// Size of the disk in bytes. |
| pub size_in_bytes: u64, |
| } |
| |
| /// Get information about all disk devices. |
| pub fn get_disks() -> Result<Vec<Disk>, Error> { |
| let devices = get_lsblk_devices(None).context("failed to get block devices")?; |
| |
| let root_disk_kname = |
| get_root_disk_device_path().context("failed to get the root disk's path")?; |
| |
| let mut disks = Vec::new(); |
| for device in devices { |
| if device.device_type != "disk" { |
| continue; |
| } |
| disks.push(Disk { |
| device_path: Path::new(&device.name).into(), |
| is_removable: device.is_removable, |
| is_root: Path::new(&device.kernel_name) == root_disk_kname, |
| size_in_bytes: device.size_in_bytes, |
| }); |
| } |
| Ok(disks) |
| } |
| |
| /// Classification of a disk based on its partition layout. |
| #[derive(Debug, Eq, PartialEq)] |
| pub enum DiskLayout { |
| /// Disk doesn't have CrOS on it. |
| NotCros, |
| |
| /// Disk contains CrOS in the USB layout (the ROOT-B partition is |
| /// a stub). |
| UsbInstaller, |
| |
| /// CrOS is installed to this disk. |
| Installed, |
| } |
| |
| /// Classify a disk using the lsblk output for a single disk. |
| /// |
| /// This checks for ROOT-A and ROOT-B partitions; if they don't both |
| /// exist, it's not a CrOS disk. |
| /// |
| /// The installer is built with the "usb" disk_layout. To save space, |
| /// that layout uses a small (2MiB) ROOT-B. When installed, the ROOT-A |
| /// and ROOT-B sizes must be identical for the updater to |
| /// function. So, if the size of ROOT-A and ROOT-B are not identical, |
| /// this is an installer disk. |
| fn classify_disk_layout(devices: &[LsBlkDevice]) -> DiskLayout { |
| let mut root_a = None; |
| let mut root_b = None; |
| |
| // Find ROOT-A and ROOT-B partitions. If they don't both exist, |
| // this is not a CrOS disk at all. |
| for device in devices { |
| let num = device.partition_number(); |
| |
| // ROOT-A is partition number 3. |
| if num == Some(3) { |
| root_a = Some(device); |
| } |
| // ROOT-B is partition number 5. |
| if num == Some(5) { |
| root_b = Some(device); |
| } |
| } |
| |
| if let (Some(root_a), Some(root_b)) = (root_a, root_b) { |
| if root_a.size_in_bytes == root_b.size_in_bytes { |
| DiskLayout::Installed |
| } else { |
| DiskLayout::UsbInstaller |
| } |
| } else { |
| DiskLayout::NotCros |
| } |
| } |
| |
| /// Check if the root device is an installer. |
| pub fn is_running_from_installer() -> Result<bool, Error> { |
| // Inspect only partitions on the root device. This avoids having |
| // lsblk try to touch devices that might be slow to read. |
| let root_dev = get_root_disk_device_path().context("failed to get root device")?; |
| |
| let devices = get_lsblk_devices(Some(&root_dev)).context("failed to get devices with lsblk")?; |
| |
| Ok(classify_disk_layout(&devices) == DiskLayout::UsbInstaller) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| fn mkdev(name: &str, size_in_bytes: u64) -> LsBlkDevice { |
| LsBlkDevice { |
| kernel_name: name.into(), |
| name: name.into(), |
| is_removable: false, |
| size_in_bytes, |
| device_type: "part".into(), |
| } |
| } |
| |
| #[test] |
| fn test_classify_disk_layout() { |
| let root_a = mkdev("/dev/sda3", 123); |
| let root_b = mkdev("/dev/sda5", 123); |
| let root_c = mkdev("/dev/sda7", 123); |
| let root_b_small = mkdev("/dev/sda5", 11); |
| |
| assert_eq!(classify_disk_layout(&[root_a.clone()]), DiskLayout::NotCros); |
| assert_eq!(classify_disk_layout(&[root_b.clone()]), DiskLayout::NotCros); |
| assert_eq!( |
| classify_disk_layout(&[root_a.clone(), root_c]), |
| DiskLayout::NotCros |
| ); |
| assert_eq!( |
| classify_disk_layout(&[root_a.clone(), root_b]), |
| DiskLayout::Installed |
| ); |
| assert_eq!( |
| classify_disk_layout(&[root_a, root_b_small]), |
| DiskLayout::UsbInstaller |
| ); |
| } |
| } |