blob: 1d45d1d40d7ee0719d76258f24938aa28a828ad1 [file] [log] [blame]
// 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
);
}
}