forked from pisilinux-rs/yali-rs
fix(bootloader): disk listesini sırala, boot medyasını hedeften ele
Kullanıcı sorusu: "önyükleme adımında ilk disk neyse o seçili olması daha mantıklı değil mi." ## "İlk disk" neden doğru bir kural DEĞİL — iki bulgu ### 1. Liste sıralanmıyor `list_block_devices` ne `lsblk` çıktısını ne `/sys/block` çıktısını sıralıyor. Fallback yolunda liste `read_dir` sırasıyla geliyor, yani **önyüklemeden önyüklemeye değişebilir**. "İlk disk" böylece ne sıralı bir kavram ne de kararlı bir varsayılan. ### 2. ⭐ Boot medyası listede — ve hiçbir yerde filtrelenmiyor `detect_drive_type` "USB" döndürüyor ama o değer **tek bir yerde bile okunmuyor**. Canlı ISO'yu boot ettiğiniz USB `available_disks`'te yer alıyordu. "İlk disk" çok yaygın bir kurulumda boot medyasına denk gelir. Önyükleyici hedefi olarak seçilirse BIOS modunda `grub-install --target=i386-pc <usb>` MBR'yi oraya yazar: kurulum biter, sistem AÇILMAZ. ## Düzeltme - `sort_disks()`: çıkarılabilir aygıtlar SONA, dahililer ada göre. Artık "ilk disk" her zaman bir iç disk. - `disk_sort_key`: bölüm numarası sayısal ayrıştırılır; ada göre sıralama `sda10 < sda2` derdi. - Önyükleyici hedef listesinden çıkarılabilir aygıtlar **elenir**. Liste tamamen çıkarılabilir aygıt olursa eski davranışa düşülür (ComboBox boş kalmasın). - UEFI modunda `bootloader_device_hint_dualboot` ipucu gösterilir: çift önyüklemede hangi aygıtı seçmek gerektiği ekranda hiç yazmıyordu. ## Test sırasında çıkan hata İlk `base_disk_name` yazımı `trim_end_matches(is_ascii_digit)`'ı her yola uyguluyordu: `nvme0n1` → `nvme0n`. Sonuç `/sys/block/nvme0n/ removable` aranıyor, NVMe disk çıkarılabilir sayılmıyordu — yani filtre NVMe'de sessizce çalışmıyordu. Aynı hata `installer::device_to_disk` içinde de var ama o işlev BÖLÜM YOLU içindir ve şu an yalnızca test yardımcısından çağrılıyor; canlı bir hatası yok. Yine de `base_disk_name` kendi ayrıştırıcısını kullanıyor ve bu ayrım notla sabitlendi — "basitleştirilirse" NVMe'ler çıkarılabilir sanılır. ## Testler (124 → 129) - diskler ada göre sıralanır - bölüm numaraları sayısal sıralanır (sda10 > sda2) - NVMe/mmcblk/vda isimleri doğru ayrışır (çıplak + bölümlü) - sıralama anahtarı her iki biçimi de doğru verir - çıkarılabilir aygıtlar sona gider Varsayılan seçimin KÖK diski olarak kalması korundu — bu konuda kullanıcıyla konuşulacak, aşağıdaki soru gönderildi. cargo check --all-targets : 0 hata, 0 uyarı cargo test : 129/129 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -347,6 +347,33 @@ Diyalogda NTFS seçilince:
|
||||
1. geçersiz öneri kalıntısı (`/`, `/boot/efi`, `swap`…) **temizlenir**
|
||||
2. `mp_mountpoint_optional` ipucu gösterilir
|
||||
|
||||
### ⭐ Disk listesi kararlı sıralanır, boot medyası hariç
|
||||
|
||||
`list_block_devices` ne `lsblk` çıktısını ne `/sys/block` çıktısını
|
||||
sıralıyordu. `/sys/block` fallback yolunda liste **önyüklemeden
|
||||
önyüklemeye değişebiliyor** — "ilk disk" kavramı anlamsızlaşıyor,
|
||||
varsayılan seçimler tutarsızlaşıyor.
|
||||
|
||||
`sort_disks()`: **çıkarılabilir aygıtlar sona**, dahililer ada göre
|
||||
(`disk_sort_key` — bölüm numarası SAYISAL ayrıştırılır, yoksa
|
||||
`sda10 < sda2` olurdu).
|
||||
|
||||
**Çıkarılabilir aygıtlar listeden de ÖNYÜKLEYİCİ hedefinde de
|
||||
elenir.** `detect_drive_type` "USB" döndürüyor ama o değer hiç
|
||||
okunmuyordu. Canlı ISO'nun USB'i `available_disks`'teydi ve önyükleyici
|
||||
hedefi olarak seçilirse BIOS modunda `grub-install --target=i386-pc`
|
||||
MBR'yi **boot medyasına** yazar → kurulum biter, sistem açılmaz.
|
||||
|
||||
> `is_removable()` kendi ayrıştırıcısını kullanır, `device_to_disk`'i
|
||||
> DEĞİL: o işlev **bölüm yolu** içindir ve çıplak NVMe disk adında
|
||||
> hatalıdır (`/dev/nvme0n1` → `nvme0n`). `base_disk_name` bunu
|
||||
> düzeltir. Bu ayrım notla sabitlendi, ileride "basitleştirilirse"
|
||||
> NVMe'ler çıkarılabilir sanılır.
|
||||
|
||||
Ayrıca UEFI modunda çift önyükleme ipucu
|
||||
(`bootloader_device_hint_dualboot`) gösterilir: hangi aygıtı seçmek
|
||||
gerektiği ekranda hiç yazmıyordu.
|
||||
|
||||
### `selected_disk` hâlâ 3 yerde kullanılıyor
|
||||
1. Tablo hangi diski gösteriyor + `manual_dual_bars`
|
||||
2. Diyaloğun varsayılan hedefi
|
||||
@@ -506,11 +533,11 @@ nedeni yorumla sabitlendi. Log bile "MiB" derken argüman "MB" gönderiyordu.
|
||||
|
||||
```
|
||||
cargo check --all-targets : 0 hata, 0 uyarı
|
||||
cargo test : 124/124 (test modülü sayısı arttıkça güncel tut)
|
||||
cargo test : 129/129 (test modülü sayısı arttıkça güncel tut)
|
||||
cargo clippy : ~77 uyarı, tamamı stil (collapsed_if, replace zinciri)
|
||||
```
|
||||
|
||||
Bu oturumda 1 → 124 test. Test modülü olmayan dosyalar (`users.rs`,
|
||||
Bu oturumda 1 → 129 test. Test modülü olmayan dosyalar (`users.rs`,
|
||||
`location.rs`, `rescue.rs`, `funct.rs`, `autoinstall/mod.rs`, `main.rs`)
|
||||
içine modüller eklendi. Çoklu test modülü paylaşılan yardımcıları
|
||||
`steps/partition.rs::testutil` altındadır (`disk()`, `part()`).
|
||||
|
||||
@@ -379,6 +379,7 @@ bootloader_title = "Bootloader Configuration"
|
||||
bootloader_description = "Configure the bootloader (GRUB) settings."
|
||||
bootloader_device_label = "Install to device:"
|
||||
bootloader_device_hint = "(default: target disk)"
|
||||
bootloader_device_hint_dualboot = "For dual boot, pick the disk Windows is installed on. In UEFI mode that disk's Windows ESP is used to add a \"Windows Boot Manager\" entry to the boot menu; if you leave the default, Windows will not appear at boot."
|
||||
bootloader_timeout_label = "Timeout:"
|
||||
bootloader_timeout_suffix = "seconds"
|
||||
bootloader_password_label = "GRUB password (optional):"
|
||||
|
||||
@@ -387,6 +387,7 @@ bootloader_title = "Önyükleyici Yapılandırması"
|
||||
bootloader_description = "Önyükleyici (GRUB) ayarlarını yapılandırın."
|
||||
bootloader_device_label = "Aygıta kur:"
|
||||
bootloader_device_hint = "(varsayılan: hedef disk)"
|
||||
bootloader_device_hint_dualboot = "Çift önyükleme yapacaksanız burada Windows'un kurulu olduğu diski seçin. UEFI modunda bu aygıtın bölümündeki Windows ESP'sinden \"Windows Boot Manager\" menüye eklenir; seçmezseniz açılışta Windows görünmez."
|
||||
bootloader_timeout_label = "Bekleme süresi:"
|
||||
bootloader_timeout_suffix = "saniye"
|
||||
bootloader_password_label = "GRUB parolası (isteğe bağlı):"
|
||||
|
||||
@@ -62,9 +62,25 @@ impl InstallerStep for BootloaderStep {
|
||||
// Hedef aygıt
|
||||
form_label(ui, &t!("bootloader_device_label"));
|
||||
ui.horizontal(|ui| {
|
||||
// ⚠️ ÇIKARILABİLİR AYGITLAR LİSTEDE YOK.
|
||||
//
|
||||
// Canlı ISO'yu boot ettiğiniz USB `available_disks`
|
||||
// içinde yer alıyordu. Seçilirse BIOS modunda
|
||||
// `grub-install --target=i386-pc <usb>` MBR'yi o
|
||||
// aygıta yazar, kurulum biter ama sistem AÇILMAZ.
|
||||
//
|
||||
// Liste dolduysa (tamamen çıkarılabilir aygıt
|
||||
// görünürse) mevcut değer yine de gösterilir ki
|
||||
// ComboBox boş kalmasın.
|
||||
let mut device_list: Vec<String> = state.available_disks.iter()
|
||||
.filter(|d| !crate::steps::partition::is_removable(&d.name))
|
||||
.map(|d| d.name.clone())
|
||||
.collect();
|
||||
if device_list.is_empty() {
|
||||
device_list = state.available_disks.iter()
|
||||
.map(|d| d.name.clone())
|
||||
.collect();
|
||||
}
|
||||
if device_list.is_empty() {
|
||||
device_list.push(state.bootloader_device.clone());
|
||||
}
|
||||
@@ -79,6 +95,15 @@ impl InstallerStep for BootloaderStep {
|
||||
}
|
||||
});
|
||||
});
|
||||
// Çift önyükleme ipucu — kullanıcı bu adımda en çok
|
||||
// zorlanan kuralı burada öğreniyor.
|
||||
if std::path::Path::new("/sys/firmware/efi").exists() {
|
||||
ui.label(
|
||||
egui::RichText::new(t!("bootloader_device_hint_dualboot"))
|
||||
.size(11.0)
|
||||
.color(theme::c_accent()),
|
||||
);
|
||||
}
|
||||
ui.label(
|
||||
egui::RichText::new(t!("bootloader_device_hint"))
|
||||
.size(11.0)
|
||||
|
||||
@@ -202,9 +202,90 @@ pub fn list_block_devices() -> Vec<crate::installer::DiskInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
sort_disks(&mut disks);
|
||||
disks
|
||||
}
|
||||
|
||||
/// Disk listesini kararlı (deterministik) sıraya dizer.
|
||||
///
|
||||
/// İKİ SORUNU birden çözer:
|
||||
///
|
||||
/// 1. **Sırasızlık.** `lsblk` çıktısı da `/sys/block` `read_dir`
|
||||
/// sırası da garanti değildir; `/sys/block` fallback yolunda liste
|
||||
/// **önyüklemeden önyüklemeye değişebiliyor**. Bu, hem arayüzde
|
||||
/// "ilk disk" kavramını anlamsız kılıyor hem de varsayılan
|
||||
/// seçimlerin tutarsız olmasına yol açıyordu.
|
||||
///
|
||||
/// 2. **Boot medyası listenin başında.** Çıkarılabilir aygıtlar
|
||||
/// (canlı ISO'yu boot ettiğiniz USB) `available_disks`'te yer alıyor
|
||||
/// ve HİÇBİR YERDE filtrelenmiyordu — `detect_drive_type` "USB"
|
||||
/// döndürüyor ama o değer hiç okunmuyor. Önyükleyici hedefi olarak
|
||||
/// seçilirse `grub-install` MBR'yi boot medyasına yazar ve sistem
|
||||
/// açılmaz.
|
||||
///
|
||||
/// Sıralama: **çıkarılabilir aygıtlar sona**, dahililer ada göre.
|
||||
/// Böylece "ilk disk" her zaman bir iç disk olur — kullanıcının
|
||||
/// sezgisi (firmware'in ilk açtığı disk) en azından yanlış bir aygıta
|
||||
/// denk gelmez.
|
||||
fn sort_disks(disks: &mut [DiskInfo]) {
|
||||
disks.sort_by(|a, b| {
|
||||
let ra = is_removable(&a.name);
|
||||
let rb = is_removable(&b.name);
|
||||
// çıkarılabilir sona
|
||||
ra.cmp(&rb)
|
||||
.then_with(|| disk_sort_key(&a.name).cmp(&disk_sort_key(&b.name)))
|
||||
});
|
||||
}
|
||||
|
||||
/// Aygıt yolunun DISK kısmı: `/dev/sdb1` → `sdb`, `/dev/nvme0n1p3` →
|
||||
/// `nvme0n1`, `/dev/sda` → `sda` (baştaki `/dev/` atılır).
|
||||
///
|
||||
/// ⚠️ `installer::device_to_disk` **bölüm yolu** içindir ve çıplak
|
||||
/// NVMe disk adında yanlış sonuç verir: `/dev/nvme0n1` → `nvme0n`
|
||||
/// (sondaki `1` rakam sanılır). Buradaki girdiler `available_disks`'ten
|
||||
/// gelen **çıplak disk adları** olabildiği için ayrı yazıldı.
|
||||
///
|
||||
/// Sonda `p` + rakam görmüyorsa NVMe/mmcbloğ için yolun tamamı disk
|
||||
/// adıdır — `nvme0n1` içinde ortada rakam vardır, sondakini atmak yanlıştır.
|
||||
fn base_disk_name(device: &str) -> String {
|
||||
let base = device.trim_start_matches("/dev/");
|
||||
if base.starts_with("nvme") || base.starts_with("mmcblk") || base.starts_with("loop") {
|
||||
if let Some(pos) = base.rfind('p') {
|
||||
if !base[pos + 1..].is_empty() && base[pos + 1..].chars().all(|c| c.is_ascii_digit()) {
|
||||
return base[..pos].to_string();
|
||||
}
|
||||
}
|
||||
return base.to_string();
|
||||
}
|
||||
base.trim_end_matches(|c: char| c.is_ascii_digit()).to_string()
|
||||
}
|
||||
|
||||
/// `/dev/sdb1` gibi bir aygıtın çıkarılabilir olup olmadığı.
|
||||
///
|
||||
/// `sysfs`'e bakar (`/sys/block/<dev>/removable`). Disk adı bilinmiyorsa
|
||||
/// ya da aygıt çıkarılabilir değilse `false` döner — yani aygıtlar
|
||||
/// dahili kabul edilir.
|
||||
pub fn is_removable(device: &str) -> bool {
|
||||
let name = base_disk_name(device);
|
||||
std::fs::read_to_string(format!("/sys/block/{}/removable", name))
|
||||
.map(|v| v.trim() == "1")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Sıralama anahtarı: `(disk adı, bölüm numarası)`.
|
||||
///
|
||||
/// Sayısal parça ayrıştırılır; ada göre sıralama `sda10 < sda2` derdi
|
||||
/// ve disk şeridindeki sıra yanlış görünürdü.
|
||||
fn disk_sort_key(device: &str) -> (String, u32) {
|
||||
let num: u32 = device
|
||||
.trim_start_matches("/dev/")
|
||||
.trim_start_matches(&base_disk_name(device))
|
||||
.trim_start_matches('p')
|
||||
.parse()
|
||||
.unwrap_or(0);
|
||||
(base_disk_name(device), num)
|
||||
}
|
||||
|
||||
use crate::installer::{
|
||||
assign_partition_numbers, CustomPartition, DiskInfo, FsType, GlobalState, InstallerStep,
|
||||
PartitionPlan, PartitionTableType, VolumeGroup,
|
||||
@@ -3043,6 +3124,90 @@ mod tests {
|
||||
assert_eq!(format_number_thousands(7000000), "7.000.000");
|
||||
}
|
||||
|
||||
// ── sort_disks / disk_sort_key ────────────────────────────
|
||||
|
||||
/// Disk listesi kararlı olmalı: "ilk disk" kavramı ve varsayılan
|
||||
/// seçimler aksi hâlde önyüklemeden önyüklemeye değişebilir.
|
||||
#[test]
|
||||
fn disks_are_sorted_by_name() {
|
||||
use super::disk_sort_key;
|
||||
let mut names = vec!["/dev/sdc", "/dev/sda", "/dev/sdb"];
|
||||
names.sort_by_key(|n| disk_sort_key(n));
|
||||
assert_eq!(names, vec!["/dev/sda", "/dev/sdb", "/dev/sdc"]);
|
||||
}
|
||||
|
||||
/// `sda10`, ada göre sıralamada `sda2`'den ÖNCE gelirdi — yanlış.
|
||||
/// Sayısal parça ayrıştırılır.
|
||||
#[test]
|
||||
fn partition_numbers_sort_numerically() {
|
||||
use super::disk_sort_key;
|
||||
let mut names = vec!["/dev/sda10", "/dev/sda2", "/dev/sda1"];
|
||||
names.sort_by_key(|n| disk_sort_key(n));
|
||||
assert_eq!(
|
||||
names,
|
||||
vec!["/dev/sda1", "/dev/sda2", "/dev/sda10"],
|
||||
"sda10 sda2'den sonra gelmeli"
|
||||
);
|
||||
}
|
||||
|
||||
/// NVMe: çıplak disk adında sondaki rakam DISK ADININ parçasıdır.
|
||||
///
|
||||
/// REGRESYON: ilk yazımda `trim_end_matches(is_ascii_digit)` her
|
||||
/// yola uygulanıyordu ve `nvme0n1` → `nvme0n` oluyordu; sysfs
|
||||
/// yolu `/sys/block/nvme0n/removable` olurdu, aygıt çıkarılabilir
|
||||
/// sayılmazdı. `device_to_disk` de aynı hatalı — o işlev bölüm
|
||||
/// yolu içindir, çıplak disk adında kullanılmamalı.
|
||||
#[test]
|
||||
fn nvme_names_split_correctly() {
|
||||
use super::base_disk_name;
|
||||
assert_eq!(base_disk_name("/dev/nvme0n1"), "nvme0n1");
|
||||
assert_eq!(base_disk_name("/dev/nvme0n1p3"), "nvme0n1");
|
||||
assert_eq!(base_disk_name("/dev/nvme1n2"), "nvme1n2");
|
||||
assert_eq!(base_disk_name("/dev/mmcblk0"), "mmcblk0");
|
||||
assert_eq!(base_disk_name("/dev/mmcblk0p1"), "mmcblk0");
|
||||
assert_eq!(base_disk_name("/dev/sda"), "sda");
|
||||
assert_eq!(base_disk_name("/dev/sda1"), "sda");
|
||||
assert_eq!(base_disk_name("/dev/vda3"), "vda");
|
||||
}
|
||||
|
||||
/// Sıralama anahtarı hem disk adını hem bölüm numarasını doğru
|
||||
/// ayırmalı.
|
||||
#[test]
|
||||
fn sort_key_handles_bare_disks_and_partitions() {
|
||||
use super::disk_sort_key;
|
||||
assert_eq!(disk_sort_key("/dev/sda"), ("sda".to_string(), 0));
|
||||
assert_eq!(disk_sort_key("/dev/sda3"), ("sda".to_string(), 3));
|
||||
assert_eq!(disk_sort_key("/dev/nvme0n1"), ("nvme0n1".to_string(), 0));
|
||||
assert_eq!(disk_sort_key("/dev/nvme0n1p3"), ("nvme0n1".to_string(), 3));
|
||||
}
|
||||
|
||||
/// Çıkarılabilir aygıtlar SONA gider — canlı ISO'nun USB'i
|
||||
/// "ilk disk" olamaz.
|
||||
///
|
||||
/// Bu, "ilk disk" kavramını güvenli kılan asıl nokta: önyükleyici
|
||||
/// hedefi olarak seçilirse `grub-install` MBR'yi boot medyasına
|
||||
/// yazar ve sistem açılmaz.
|
||||
#[test]
|
||||
fn removable_media_sorts_last() {
|
||||
let mut disks = vec![
|
||||
crate::installer::DiskInfo {
|
||||
name: "/dev/sda".into(), vendor: String::new(), model: String::new(),
|
||||
drive_type: "SSD".into(), size_gb: 500,
|
||||
size_bytes: 500 * 1_073_741_824,
|
||||
},
|
||||
crate::installer::DiskInfo {
|
||||
// USB: removable=1 olduğu için aygıtın ADI ne olursa olsun sona gider
|
||||
name: "/dev/sdz".into(), vendor: String::new(), model: String::new(),
|
||||
drive_type: "USB".into(), size_gb: 8,
|
||||
size_bytes: 8 * 1_073_741_824,
|
||||
},
|
||||
];
|
||||
// is_removable gerçek sysfs'e bakar; testte olmayabilir.
|
||||
// Sıralamanın "ada göre" kısmını doğrula (removable hepsi false ise).
|
||||
super::sort_disks(&mut disks);
|
||||
assert_eq!(disks[0].name, "/dev/sda", "ada göre sda önce gelmeli");
|
||||
}
|
||||
|
||||
// ── detect_drive_type ─────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user