forked from pisilinux-rs/yali-rs
feat(bootloader): varsayılan aygıt artık mevcut işletim sistemi olan disk
Kullanıcı kararı: "zaten işletim sistemi olan diski seç".
## Neden kök diski değil
BIOS modunda `grub-install` seçilen diskin MBR'sine yazar ve MBR'yi
okuyan aygıt firmware'in açılış sırasındaki ilk diski olmak ZORUNDA:
sda: Windows sdb: PisiLinux (kök burada)
GRUB → sdb firmware sdb'yi açmaz, PisiLinux HİÇ GÖRÜNMEZ
GRUB → sda firmware GRUB'a düşer, GRUB ikisini de listeler
Salt kök diski varsayılan tutmak dual-boot'ta sistemi açılmaz
bırakıyordu. `selected_disk` ise manuel modda "aktif düzenleme
diski" olduğu için /home eklemek için geçilen diske yanlış yönlendiriyordu.
## Kural (default_boot_device, saf + test edilebilir)
1. çıkarılabilir aygıt (ISO'nun USB'i) → ASLA hedef değil
2. kök diski dışında bölümü olan ilk dahili disk → mevcut OS diski
3. kök diski
4. selected_disk (son çare)
"İşletim sistemi kurulu"nun en ucuz ve en güvenilir göstergesi: kök
diski dışında hâlihazırda bölüm tablosu olan disk.
Tek diskli kurulumda 3. adım çalışır → davranış kök diskiyle aynı,
geriye dönük uyum bozulmaz.
## Tespit: alt süreç YOK
`disk_partition_count()` sysfs'ten okuyor
(`/sys/block/<disk>/<disk><N>` girdileri). `lsblk` çağırmak
`on_enter`'da bile gereksiz ve kare başına alt süreç yasağına
ayrık düşüyordu.
## UI
`boot_device_reason` aynı fonksiyonu çağırıp seçimin bu kurala
uyup uymadığını denetler; uyuyorsa gerekçe metni gösterilir. Böylece
sessiz bir varsayılan sürpriz olmaz ve **ikinci bir uygulama olmaz**.
## Testler (129 → 137)
bootloader.rs'de test modülü yoktu, eklendi (8 test):
- mevcut OS diski seçilir (kullanıcının senaryosu)
- tek disk → kök disk
- hiçbir disk bölümlü değil → kök disk
- kök disk "mevcut OS" sayılmaz
- birden çok adayda sıralı ilki seçilir
- kök disk bilinmiyorsa
- liste boşken kök disk kullanılır
- hiçbir şey bilinmiyorsa None
Bir test ilk yazımda yanlış beklenti koydu: liste boşken None bekliyordum,
doğrusu kök diske düşmek (liste yalnızca OS diski arayışı için gerekiyor).
Beklenti düzeltildi.
cargo check --all-targets : 0 hata, 0 uyarı
cargo test : 137/137
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -374,6 +374,45 @@ 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.
|
||||
|
||||
### ⭐ Önyükleyici varsayılanı: KÖRK diski değil, **mevcut OS diski**
|
||||
|
||||
`steps/bootloader.rs::default_boot_device()` saf ve test edilebilir:
|
||||
|
||||
```
|
||||
1. çıkarılabilir aygıt (ISO'nun USB'i) → ASLA hedef değil
|
||||
2. kök diski dışında bölümü olan ilk dahili disk → mevcut OS diski
|
||||
3. kök diski
|
||||
4. selected_disk (son çare)
|
||||
```
|
||||
|
||||
**Neden kök diski değil.** BIOS modunda `grub-install` seçilen
|
||||
diskin **MBR'sine** yazar ve MBR'yi okuyan aygıt firmware'in açılış
|
||||
sırasındaki ilk diski olmak ZORUNDA:
|
||||
|
||||
```
|
||||
sda: Windows sdb: PisiLinux (kök burada)
|
||||
GRUB → sdb firmware sdb'yi açmaz, PisiLinux HİÇ GÖRÜNMEZ
|
||||
GRUB → sda firmware GRUB'a düşer, GRUB ikisini de listeler
|
||||
```
|
||||
|
||||
"İşletim sistemi kurulu"nun en ucuz ve en güvenilir göstergesi:
|
||||
**kök diski dışında hâlihazırda bölüm tablosu olan disk**. Tespit
|
||||
`disk_partition_count()` ile `sysfs`'ten yapılır — **alt süreç yok**
|
||||
(`lsblk` çalıştırmak `on_enter`'da bile gereksiz).
|
||||
|
||||
Tek diskli kurulumda 3. adım çalışır, yani davranış kök diskiyle
|
||||
aynıdır — geriye dönük uyum bozulmaz.
|
||||
|
||||
Kullanıcı elle değiştirdiyse ipucu (`bootloader_device_why_existing`)
|
||||
gösterilmez; ipucu yalnızca seçim bu kurala uyuyorsa çıkar, yani
|
||||
sessiz bir sürpriz olmaz.
|
||||
|
||||
> ⚠️ Bu, daha önceki iki varsayılanın da yerine geçti:
|
||||
> `selected_disk` (manuel modda "aktif düzenleme diski" olduğu için
|
||||
> yanlış diske gidiyordu) ve salt kök diski (dual-boot'ta yanlış).
|
||||
> Kural artık ikisinden de iyi; `boot_device_reason` aynı fonksiyonu
|
||||
> çağırıp UI metnini oradan üretir — **ikinci bir uygulama yok**.
|
||||
|
||||
### `selected_disk` hâlâ 3 yerde kullanılıyor
|
||||
1. Tablo hangi diski gösteriyor + `manual_dual_bars`
|
||||
2. Diyaloğun varsayılan hedefi
|
||||
@@ -533,11 +572,11 @@ nedeni yorumla sabitlendi. Log bile "MiB" derken argüman "MB" gönderiyordu.
|
||||
|
||||
```
|
||||
cargo check --all-targets : 0 hata, 0 uyarı
|
||||
cargo test : 129/129 (test modülü sayısı arttıkça güncel tut)
|
||||
cargo test : 137/137 (test modülü sayısı arttıkça güncel tut)
|
||||
cargo clippy : ~77 uyarı, tamamı stil (collapsed_if, replace zinciri)
|
||||
```
|
||||
|
||||
Bu oturumda 1 → 129 test. Test modülü olmayan dosyalar (`users.rs`,
|
||||
Bu oturumda 1 → 137 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()`).
|
||||
|
||||
@@ -380,6 +380,7 @@ 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_device_why_existing = "This device was picked because it already holds an operating system: the bootloader has to be written to the disk the firmware already boots."
|
||||
bootloader_timeout_label = "Timeout:"
|
||||
bootloader_timeout_suffix = "seconds"
|
||||
bootloader_password_label = "GRUB password (optional):"
|
||||
|
||||
@@ -388,6 +388,7 @@ 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_device_why_existing = "Bu aygıt, üzerinde hâlihazırda işletim sistemi bulunduğu için seçildi: önyükleyici açılışta zaten okunan diske yazılmalıdır."
|
||||
bootloader_timeout_label = "Bekleme süresi:"
|
||||
bootloader_timeout_suffix = "saniye"
|
||||
bootloader_password_label = "GRUB parolası (isteğe bağlı):"
|
||||
|
||||
+231
-9
@@ -4,12 +4,99 @@ use crate::installer::{GlobalState, InstallerStep};
|
||||
use crate::ui::theme;
|
||||
use crate::funct::{form_label, form_input_text, form_input_password};
|
||||
|
||||
/// Bir diskin bölüm sayısını `sysfs`'ten okur (alt süreç yok).
|
||||
///
|
||||
/// `lsblk` çalıştırmak kare başına olsaydı her karede süreç doğardu;
|
||||
/// `on_enter`'da bile gereksiz. sysfs'te her bölüm
|
||||
/// `/sys/block/<disk>/<disk><N>`, `…/start` dosyasıyla listelenir.
|
||||
fn disk_partition_count(disk: &str) -> usize {
|
||||
let base = crate::steps::partition::base_disk_name(disk);
|
||||
if base.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
let dir = format!("/sys/block/{}", base);
|
||||
let Ok(rd) = std::fs::read_dir(&dir) else { return 0 };
|
||||
rd.flatten().filter(|e| {
|
||||
let name = e.file_name().to_string_lossy().to_string();
|
||||
// Yalnızca "<base><rakam>" biçimindeki girdiler bölümdür.
|
||||
name.strip_prefix(&base)
|
||||
.map(|rest| !rest.is_empty() && rest.chars().all(|c| c.is_ascii_digit()))
|
||||
.unwrap_or(false)
|
||||
}).count()
|
||||
}
|
||||
|
||||
/// Önyükleyici için varsayılan aygıtı seçer.
|
||||
///
|
||||
/// Kural: **KÖR diski seçme; zaten işletim sistemi kurulu olan diski
|
||||
/// seç.** Çünkü BIOS modunda `grub-install` seçilen diskin MBR'sine
|
||||
/// yazar ve MBR'yi okuyan aygıt firmware'in açılış sırasındaki ilk
|
||||
/// diski olmak ZORUNDA. Çift önyüklemeli bir makinede bu, mevcut
|
||||
/// işletim sisteminin diski — kökün kurulduğu disk DEĞİL.
|
||||
///
|
||||
/// Somut olarak: sda'da Windows, sdb'de PisiLinux kuruluyor. GRUB
|
||||
/// sdb'nin MBR'sine yazılırsa firmware sdb'yi açmaz, PisiLinux hiç
|
||||
/// görünmez. sda'nın MBR'sine yazılırsa firmware GRUB'a düşer, GRUB
|
||||
/// da ikisini de listeler.
|
||||
///
|
||||
/// "İşletim sistemi kurulu"nun en güvenilir ve en ucuz göstergesi:
|
||||
/// **kök diski dışında, hâlihazırda bölüm tablosu olan bir disk**
|
||||
/// (yani disk dolu değil, içinde tanım var).
|
||||
///
|
||||
/// Sıralama:
|
||||
/// 1. Çıkarılabilir aygıt (canlı ISO'nun USB'i) — ASLA hedef değil
|
||||
/// 2. Kök diski dışında bölümü olan ilk dahili disk → mevcut OS diski
|
||||
/// 3. Kök diski
|
||||
/// 4. Aktif düzenleme diski (`selected_disk`) — son çare
|
||||
///
|
||||
/// Tek diskli kurulumda 3. adım çalışır: başka disk yoktur, davranış
|
||||
/// kök diskiyle aynıdır.
|
||||
pub fn default_boot_device<F>(
|
||||
root_disk: Option<&str>,
|
||||
disks: &[crate::installer::DiskInfo],
|
||||
has_partitions: F,
|
||||
) -> Option<String>
|
||||
where
|
||||
F: Fn(&str) -> bool,
|
||||
{
|
||||
// (1) çıkarılabilir aygıtlar ve (2) kök diski ele
|
||||
disks.iter()
|
||||
.filter(|d| !crate::steps::partition::is_removable(&d.name))
|
||||
.filter(|d| Some(d.name.as_str()) != root_disk)
|
||||
.find(|d| has_partitions(&d.name))
|
||||
.map(|d| d.name.clone())
|
||||
.or_else(|| root_disk.map(|s| s.to_string()))
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BootloaderStep {
|
||||
show_password: bool,
|
||||
timeout_str: String,
|
||||
}
|
||||
|
||||
/// Seçili aygıt, "zaten işletim sistemi olan disk" kuralıyla mı
|
||||
/// belirlendi? Değilse (kullanıcı elle değiştirdiyse ya da tek disk
|
||||
/// varsa) `None` döner ve ipucu gösterilmez.
|
||||
fn boot_device_reason(state: &GlobalState, current: &str) -> Option<String> {
|
||||
let root_disk = state
|
||||
.custom_partitions
|
||||
.iter()
|
||||
.find(|p| p.mountpoint == "/" && !p.delete)
|
||||
.map(|p| p.disk.clone())
|
||||
.or_else(|| state.partition_plan.as_ref().map(|p| p.disk.clone()));
|
||||
|
||||
// Aynı kuralı bir kez daha çalıştırıp sonucun aygıtla örtüştüğünü
|
||||
// doğruluyoruz. Kural tek kaynakta; burada yalnızca UI metni var.
|
||||
let expected = default_boot_device(root_disk.as_deref(), &state.available_disks, |d| {
|
||||
disk_partition_count(d) > 0
|
||||
})?;
|
||||
|
||||
if expected == current {
|
||||
Some(t!("bootloader_device_why_existing").to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl InstallerStep for BootloaderStep {
|
||||
fn name(&self) -> String {
|
||||
t!("bootloader").to_string()
|
||||
@@ -19,22 +106,31 @@ impl InstallerStep for BootloaderStep {
|
||||
if self.timeout_str.is_empty() {
|
||||
self.timeout_str = state.bootloader_timeout.to_string();
|
||||
}
|
||||
// Varsayılan GRUB aygıtı, KÖKün olduğu disk olmalı.
|
||||
// Varsayılan GRUB aygıtı: zaten işletim sistemi kurulu olan
|
||||
// disk. Detaylı gerekçe `default_boot_device` yorumunda.
|
||||
//
|
||||
// Önceden `state.selected_disk` kullanılıyordu; bu disk manuel
|
||||
// modda "aktif düzenleme diski"dir ve kullanıcı tabloyu gezmek
|
||||
// için /home ekleyeceği sdb'ye çevirmişse GRUB sessizce oraya
|
||||
// yönlendiriliyordu. Doğru varsayılan kökün diski; kullanıcı
|
||||
// ComboBox'tan değiştirebilir.
|
||||
// Önceki davranışlar ve nedenleri:
|
||||
// - `selected_disk` → manuel modda "aktif düzenleme diski";
|
||||
// /home eklemek için sdb'ye geçince GRUB sessizce oraya
|
||||
// gidiyordu.
|
||||
// - yalnızca kök diski → çift önyüklemeli makinede firmware'in
|
||||
// açmadığı diske MBR yazılıyor, PisiLinux hiç görünmüyor.
|
||||
if state.bootloader_device.is_empty() {
|
||||
let root_disk = state
|
||||
.custom_partitions
|
||||
.iter()
|
||||
.find(|p| p.mountpoint == "/" && !p.delete)
|
||||
.map(|p| p.disk.clone())
|
||||
.or_else(|| state.partition_plan.as_ref().map(|p| p.disk.clone()))
|
||||
.or_else(|| state.selected_disk.clone());
|
||||
if let Some(d) = root_disk {
|
||||
.or_else(|| state.partition_plan.as_ref().map(|p| p.disk.clone()));
|
||||
|
||||
let chosen = default_boot_device(
|
||||
root_disk.as_deref(),
|
||||
&state.available_disks,
|
||||
|d| disk_partition_count(d) > 0,
|
||||
)
|
||||
.or_else(|| state.selected_disk.clone());
|
||||
|
||||
if let Some(d) = chosen {
|
||||
state.bootloader_device = d;
|
||||
}
|
||||
}
|
||||
@@ -95,6 +191,17 @@ impl InstallerStep for BootloaderStep {
|
||||
}
|
||||
});
|
||||
});
|
||||
// Varsayılanın NEDEN bu aygıt olduğunu söyle. Sessiz
|
||||
// bir varsayılan sürpriz yaratmasın: kullanıcı
|
||||
// nedenini anlamazsa dual-boot'ta yanlış aygıtta
|
||||
// kalır ve açılışta Windows görünmez.
|
||||
if let Some(why) = boot_device_reason(&state, &state.bootloader_device) {
|
||||
ui.label(
|
||||
egui::RichText::new(why)
|
||||
.size(11.0)
|
||||
.color(theme::c_accent()),
|
||||
);
|
||||
}
|
||||
// Çift önyükleme ipucu — kullanıcı bu adımda en çok
|
||||
// zorlanan kuralı burada öğreniyor.
|
||||
if std::path::Path::new("/sys/firmware/efi").exists() {
|
||||
@@ -146,3 +253,118 @@ impl InstallerStep for BootloaderStep {
|
||||
!state.bootloader_device.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::default_boot_device;
|
||||
use crate::installer::DiskInfo;
|
||||
|
||||
fn d(name: &str) -> DiskInfo {
|
||||
DiskInfo {
|
||||
name: name.to_string(),
|
||||
vendor: String::new(),
|
||||
model: "TEST".to_string(),
|
||||
drive_type: String::new(),
|
||||
size_gb: 100,
|
||||
size_bytes: 100 * 1_073_741_824,
|
||||
}
|
||||
}
|
||||
|
||||
/// `has_partitions` verilen aygıtın bölümü var mı diye bakar
|
||||
/// (test ortamında gerçek sysfs yok, bu yüzden dışarıdan verilir).
|
||||
fn parts_on(disks_with_parts: &[&str]) -> impl Fn(&str) -> bool {
|
||||
let v: Vec<String> = disks_with_parts.iter().map(|s| s.to_string()).collect();
|
||||
move |name: &str| v.iter().any(|p| p == name)
|
||||
}
|
||||
|
||||
/// ⭐ Kullanıcının senaryosu: sda'da Windows, sdb'de PisiLinux
|
||||
/// kuruluyor. GRUB sda'ya yazılmalı — çünkü firmware açılışta
|
||||
/// sda'yı okuyor. Kök diski (sdb) seçilirse PisiLinux hiç görünmez.
|
||||
#[test]
|
||||
fn picks_the_disk_that_already_has_an_os() {
|
||||
let disks = vec![d("/dev/sda"), d("/dev/sdb"), d("/dev/sdc")];
|
||||
let got = default_boot_device(Some("/dev/sdb"), &disks, parts_on(&["/dev/sda"]));
|
||||
assert_eq!(
|
||||
got.as_deref(),
|
||||
Some("/dev/sda"),
|
||||
"mevcut işletim sistemi olan disk seçilmeli"
|
||||
);
|
||||
}
|
||||
|
||||
/// Tek diskli kurulumda davranış KÖRK diskiyle aynıdır — başka
|
||||
/// aday yoktur.
|
||||
#[test]
|
||||
fn single_disk_install_falls_back_to_the_root_disk() {
|
||||
let disks = vec![d("/dev/sda")];
|
||||
assert_eq!(
|
||||
default_boot_device(Some("/dev/sda"), &disks, parts_on(&["/dev/sda"])).as_deref(),
|
||||
Some("/dev/sda")
|
||||
);
|
||||
}
|
||||
|
||||
/// Çok diskli ama HİÇBİRİ bölümlü değilse (tamamen boş makineler)
|
||||
/// kök disk seçilir.
|
||||
#[test]
|
||||
fn no_existing_os_anywhere_falls_back_to_root() {
|
||||
let disks = vec![d("/dev/sda"), d("/dev/sdb")];
|
||||
assert_eq!(
|
||||
default_boot_device(Some("/dev/sdb"), &disks, parts_on(&[])).as_deref(),
|
||||
Some("/dev/sdb")
|
||||
);
|
||||
}
|
||||
|
||||
/// Kök diskin kendisi bölümlü olsa bile "mevcut OS" sayılmaz —
|
||||
/// kurulum oraya yapılıyor, oraya varsayılan yazmak eski davranış.
|
||||
#[test]
|
||||
fn root_disk_is_never_treated_as_the_existing_os() {
|
||||
let disks = vec![d("/dev/sda"), d("/dev/sdb")];
|
||||
// sda'da kurulum, sdb boş → sda
|
||||
assert_eq!(
|
||||
default_boot_device(Some("/dev/sda"), &disks, parts_on(&["/dev/sda"])).as_deref(),
|
||||
Some("/dev/sda")
|
||||
);
|
||||
}
|
||||
|
||||
/// Birden çok aday varsa SIRALAMA önemlidir: liste zaten
|
||||
/// kararlı sıralıdır, ilki seçilir.
|
||||
#[test]
|
||||
fn first_candidate_in_sorted_order_wins() {
|
||||
let disks = vec![d("/dev/sda"), d("/dev/sdb"), d("/dev/sdc")];
|
||||
assert_eq!(
|
||||
default_boot_device(Some("/dev/sdd"), &disks, parts_on(&["/dev/sdb", "/dev/sdc"])).as_deref(),
|
||||
Some("/dev/sdb"),
|
||||
"sıralı listedeki ilk bölümlü disk seçilmeli"
|
||||
);
|
||||
}
|
||||
|
||||
/// Kök disk bilinmiyorsa (otomatik mod / henüz taranmamış) yine
|
||||
/// bölümlü disk seçilir; hiçbiri yoksa `None`.
|
||||
#[test]
|
||||
fn unknown_root_disk_is_handled() {
|
||||
let disks = vec![d("/dev/sda"), d("/dev/sdb")];
|
||||
assert_eq!(
|
||||
default_boot_device(None, &disks, parts_on(&["/dev/sda"])).as_deref(),
|
||||
Some("/dev/sda")
|
||||
);
|
||||
assert_eq!(default_boot_device(None, &disks, parts_on(&[])), None);
|
||||
}
|
||||
|
||||
/// Disk listesi boşken bile KÖRK disk biliniyorsa o kullanılır —
|
||||
/// liste yalnızca "mevcut işletim sistemi olan disk" arayışı için
|
||||
/// gerekiyordu, son çare kök disktir.
|
||||
#[test]
|
||||
fn empty_disk_list_still_uses_the_root_disk() {
|
||||
assert_eq!(
|
||||
default_boot_device(Some("/dev/sda"), &[], parts_on(&["/dev/sda"])).as_deref(),
|
||||
Some("/dev/sda"),
|
||||
"liste boş olsa da bilinen kök disk kullanılmalı"
|
||||
);
|
||||
}
|
||||
|
||||
/// Ne disk ne de kök biliniyorsa `None` — `on_enter` bu durumda
|
||||
/// `selected_disk`'e düşer, o da yoksa aygıt seçilemez.
|
||||
#[test]
|
||||
fn nothing_known_yields_none() {
|
||||
assert_eq!(default_boot_device(None, &[], parts_on(&[])), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ fn sort_disks(disks: &mut [DiskInfo]) {
|
||||
///
|
||||
/// 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 {
|
||||
pub 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') {
|
||||
|
||||
Reference in New Issue
Block a user