rustpm 0.2.6

A fast, friendly APT frontend with kernel, desktop, and sources management
use anyhow::{bail, Result};
use std::process::{Command, ExitStatus, Stdio};
use std::io::{BufRead, BufReader};
use std::sync::mpsc;
use std::thread;

use super::detector::running_kernel;

fn stream_command(
    program: &str,
    args: &[&str],
    tx: &mpsc::Sender<String>,
) -> Result<ExitStatus> {
    let mut child = Command::new(program)
        .args(args)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()?;

    let stdout = child.stdout.take().unwrap();
    let stderr = child.stderr.take().unwrap();
    let tx1 = tx.clone();
    let tx2 = tx.clone();

    let t1 = thread::spawn(move || {
        for line in BufReader::new(stdout).lines().map_while(Result::ok) {
            let _ = tx1.send(line);
        }
    });
    let t2 = thread::spawn(move || {
        for line in BufReader::new(stderr).lines().map_while(Result::ok) {
            let _ = tx2.send(line);
        }
    });

    let status = child.wait()?;
    let _ = t1.join();
    let _ = t2.join();
    Ok(status)
}

pub fn install_kernel(version: &str, tx: &mpsc::Sender<String>) -> Result<()> {
    let image = format!("linux-image-{}", version);
    let headers = format!("linux-headers-{}", version);

    let _ = tx.send(format!("Installing {} and {} ...", image, headers));

    let status = stream_command(
        "apt-get",
        &["install", "-y", &image, &headers],
        tx,
    )?;

    if !status.success() {
        bail!("apt-get install failed with status: {}", status);
    }

    trigger_grub_update(tx)?;
    Ok(())
}

pub fn remove_kernel(version: &str, tx: &mpsc::Sender<String>) -> Result<()> {
    let running = running_kernel();
    if version == running {
        bail!("refusing to remove running kernel {}", version);
    }

    let image = format!("linux-image-{}", version);
    let headers = format!("linux-headers-{}", version);

    let _ = tx.send(format!("Removing {} and {} ...", image, headers));

    let status = stream_command(
        "apt-get",
        &["remove", "-y", &image, &headers],
        tx,
    )?;

    if !status.success() {
        bail!("apt-get remove failed with status: {}", status);
    }

    trigger_grub_update(tx)?;
    Ok(())
}

pub fn update_kernel(tx: &mpsc::Sender<String>) -> Result<()> {
    let _ = tx.send("Upgrading kernel packages...".into());

    let status = stream_command(
        "apt-get",
        &["install", "-y", "--only-upgrade", "linux-image-amd64", "linux-headers-amd64"],
        tx,
    )?;

    if !status.success() {
        bail!("kernel upgrade failed");
    }

    trigger_grub_update(tx)?;
    Ok(())
}

pub fn pin_kernel(version: &str) -> Result<()> {
    let image = format!("linux-image-{}", version);
    let headers = format!("linux-headers-{}", version);

    let status = Command::new("apt-mark")
        .args(["hold", &image, &headers])
        .status()?;

    if !status.success() {
        bail!("apt-mark hold failed");
    }
    Ok(())
}

pub fn unpin_kernel(version: &str) -> Result<()> {
    let image = format!("linux-image-{}", version);
    let headers = format!("linux-headers-{}", version);

    let status = Command::new("apt-mark")
        .args(["unhold", &image, &headers])
        .status()?;

    if !status.success() {
        bail!("apt-mark unhold failed");
    }
    Ok(())
}

pub fn trigger_grub_update(tx: &mpsc::Sender<String>) -> Result<()> {
    let _ = tx.send("Updating GRUB...".into());

    let status = stream_command("update-grub", &[], tx)?;

    if !status.success() {
        let _ = tx.send("Warning: update-grub returned non-zero exit code".into());
    }

    Ok(())
}