summaryrefslogtreecommitdiff
path: root/crates/flasher-qt/src/main.rs
blob: e30441812e3848d0c47c6c65847b6509606514e7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
use std::{
    env,
    ffi::CString,
    fs,
    os::raw::{c_char, c_int},
};

const BOARDS: &str = include_str!("../../../alt-rootfs-installer/SUPPORTED-BOARDS");

unsafe extern "C" {
    fn run_qt_app(
        argc: c_int,
        argv: *mut *mut c_char,
        boards: *const c_char,
        drives: *const c_char,
    ) -> c_int;
}

fn main() {
    let arguments: Vec<CString> = env::args()
        .map(|arg| CString::new(arg).expect("application argument contains a NUL byte"))
        .collect();
    let mut argument_pointers: Vec<*mut c_char> = arguments
        .iter()
        .map(|arg| arg.as_ptr().cast_mut())
        .collect();
    let boards = CString::new(BOARDS).expect("board list contains a NUL byte");
    let drives = CString::new(available_drives().join("\n"))
        .expect("drive description contains a NUL byte");

    // QApplication only borrows argv and the two strings for the duration of
    // this call; all backing CStrings remain alive until the event loop exits.
    unsafe {
        run_qt_app(
            argument_pointers.len() as c_int,
            argument_pointers.as_mut_ptr(),
            boards.as_ptr(),
            drives.as_ptr(),
        );
    }
}

fn available_drives() -> Vec<String> {
    let mut drives = Vec::new();
    let Ok(entries) = fs::read_dir("/sys/block") else {
        return drives;
    };

    for entry in entries.flatten() {
        let name = entry.file_name().to_string_lossy().into_owned();
        if name.starts_with("loop") || name.starts_with("ram") || name.starts_with("zram") {
            continue;
        }
        let removable = fs::read_to_string(entry.path().join("removable"))
            .map(|value| value.trim() == "1")
            .unwrap_or(false);
        if !removable && !name.starts_with("mmcblk") {
            continue;
        }

        let sectors = fs::read_to_string(entry.path().join("size"))
            .ok()
            .and_then(|value| value.trim().parse::<u64>().ok())
            .unwrap_or(0);
        let model = fs::read_to_string(entry.path().join("device/model"))
            .unwrap_or_default()
            .trim()
            .to_owned();
        let description = if model.is_empty() {
            format!("/dev/{name}")
        } else {
            model
        };
        drives.push(format!(
            "{description}  ·  {}  ·  /dev/{name}",
            format_size(sectors.saturating_mul(512))
        ));
    }
    drives.sort();
    drives
}

fn format_size(bytes: u64) -> String {
    const GIB: f64 = 1024.0 * 1024.0 * 1024.0;
    const MIB: f64 = 1024.0 * 1024.0;
    if bytes as f64 >= GIB {
        format!("{:.1} GB", bytes as f64 / GIB)
    } else {
        format!("{:.0} MB", bytes as f64 / MIB)
    }
}