fix(partition): "yalnızca GRUB için" bölüm mountpoint'siz kaydedilemiyordu

Kullanıcı raporu: "sda1 grub eklenecek o yüzden herhangi bir mount
olayı yok ve yine bağlama noktası boş olamaz uyarısı alıyorum."

Önceki commit yalnızca `validate_manual_on`'i düzeltmişti. Kaydetme
DİYALOĞUNDA ikinci bir kural vardı ve koşulu `!is_pv` idi — yani
fiziksel bölüm düzenlenirken de mountpoint zorunluydu.

Kullanıcı sda1'i "kuruluma ait değil, yalnızca GRUB için" diye
işaretlemek istiyordu ve diyalog kabul etmiyordu. Üstelik iki kural
birbirinden farklıydı: validate serbest bırakırken diyalog reddediyor
ve kullanıcı hangisinin geçerli olduğunu anlayamıyordu.

## mountpoint_required() — tek kural, iki yerde

Kural artık saf bir yardımcıda tanımlı ve HER İKİ yerde de aynen
kullanılıyor:

  - LVM PV → serbest
  - düzenlenen FİZİKSEL bölüm (orig_device dolu) → serbest
  - yeni tanım → zorunlu

⚠️ İki yerde ayrı yazılırsa birinde geçen diğerinde reddedilir ve
kullanıcı "İleri"ye geçemez. Not yorumla sabitlendi.

## Hata mesajı artık bölümü adıyla söylüyor

`mp_error_empty_mountpoint_named` eklendi. Genel mesaj ("Bağlama noktası
boş olamaz") ekranda beş bölüm varken hangisinin kastedildiğini
belli etmiyordu; bu bir hatanın iki tur turmasına yol açmıştı.

## Ölü i18n anahtarları silindi

`rescue_button_tooltip` ve `job_display_manager` kodda KULLANILMIYORDU
ve `en.toml`'da karşılıkları yoktu. Proje kuralı gereği çeviri
yazmak yerine silindi: sessizleştirmek yerine kökten kaldır.
tr/en artık tam eşleşiyor (463/463).

## Testler (98 → 102)

- GRUB için ayrılmış fiziksel bölüm boş bırakılabilir
- yeni tanımda mountpoint hâlâ zorunlu
- LVM PV hiçbir koşulda mountpoint istemez
- kullanıcının tam senaryosu (sda GRUB-only + sdb Linux + sdc /home)
  geçerli kabul ediliyor

cargo check --all-targets : 0 hata, 0 uyarı
cargo test              : 102/102

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erkan IŞIK
2026-09-27 16:10:29 +03:00
parent a47c6e52bc
commit 948e1d548d
3 changed files with 136 additions and 10 deletions
+1
View File
@@ -296,6 +296,7 @@ job_mount_custom = "Mounting manual partitions"
# ── Extra Localization / i18n ─────────────────────────────
mp_delete_confirm_title = "Delete Partition"
mp_error_empty_mountpoint = "Mount point cannot be empty."
mp_error_empty_mountpoint_named = "Mount point cannot be empty: {device}. Pick a mount point from the list, or mark the partition for deletion and continue."
mp_plan_valid = "✓ Partition plan is valid. You can proceed."
mp_select_disk_first = "← Select a disk first."
mp_add_dialog_hint = "/ or /home or swap"
+1 -2
View File
@@ -151,7 +151,6 @@ check_warn_notice = "Bazı önerilen gereksinimler karşılanmıyor. Kurulum d
check_all_pass = "Tüm sistem gereksinimleri karşılandı."
recheck = "🔄 Yeniden Kontrol Et"
rescue_button = "🆘 Kurtarma Kipi"
rescue_button_tooltip = "PiSi Linux'u Kurtarma Modunda Yeniden Kur"
# ── Users (ek) ────────────────────────────────────────────
pwd_very_weak = "Çok zayıf"
@@ -261,6 +260,7 @@ job_mount_custom = "Manuel bölümler bağlanıyor"
# ── Ek Yerelleştirme / i18n ──────────────────────────────────
mp_delete_confirm_title = "Bölümü Sil"
mp_error_empty_mountpoint = "Bağlama noktası boş olamaz."
mp_error_empty_mountpoint_named = "Bağlama noktası boş olamaz: {device}. Listeden bir bağlama noktası seçin ya da bölümü \"Sil\" işaretleyip geçin."
mp_plan_valid = "Bölüm planı geçerli. İleri tuşuna basabilirsiniz."
mp_select_disk_first = "← Önce bir disk seçin."
mp_add_dialog_hint = "/ veya /home veya swap"
@@ -317,7 +317,6 @@ job_ldconfig = "Paylaşımlı kütüphaneler yapılandırılıyor (ldc
job_update_env = "Sistem ortamı güncelleniyor"
job_cleanup_live = "Canlı ortam izleri temizleniyor"
job_datetime = "Tarih & saat yapılandırılıyor"
job_display_manager = "Görüntü yöneticisi yapılandırılıyor"
job_autologin = "Otomatik oturum açma uygulanıyor"
job_netinstall = "Ek paketler kuruluyor"
job_setup_lvm = "LVM yapılandırılıyor"
+134 -8
View File
@@ -456,8 +456,12 @@ fn validate_manual_on(
// Zorunluluk yeni tanımlarda (orig_device boş) ve LVM PV dışında
// geçerlidir: kullanıcı "ne işe yarayacak" diye sormadan kaydedemez.
for p in &live_parts {
if p.orig_device.is_empty() && p.mountpoint.is_empty() && p.fstype != FsType::Lvm {
return Some(t!("mp_error_empty_mountpoint").to_string());
if p.mountpoint.is_empty() && mountpoint_required(Some(p), &p.fstype) {
// Bölümü ADIYLA söyle. Genel mesaj, ekranda birden fazla
// bölüm varken hangisinin kastedildiğini belli etmiyor ve
// kullanıcıyı (varsa) hiç ilgisi olmayan bir bölümde
// aramaya itiyordu.
return Some(t!("mp_error_empty_mountpoint_named", device = p.device).to_string());
}
}
@@ -468,6 +472,33 @@ fn validate_manual_on(
// Çoklu disk yardımcıları
// ─────────────────────────────────────────────
/// Bir tanımın bağlama noktası BOŞ olmak zorunda mı?
///
/// Boş bırakmak "bu bölüm kuruluma ait değil" demektir. İki durumda
/// serbesttir:
///
/// 1. **LVM PV** — üzerinde mantıksal hacimler yaşar, kendisi
/// bağlanmaz.
/// 2. **Düzenlenen FİZİKSEL bölüm** (`orig_device` dolu) — kullanıcı
/// çift önyüklemeli kurulumda Windows'un boot bölümünü "yalnızca
/// GRUB için, mount edilmeyecek" diye işaretlemek isteyebilir.
///
/// Yeni bir tanımda (`None` ya da `orig_device` boş) ZORUNLUDUR: kullanıcı
/// "ne işe yarayacak" diye sormadan kaydedemez.
///
/// ⚠️ Bu kural hem `validate_manual_on`'da hem de kaydetme diyaloğunda
/// aynen uygulanmalı — birinde serbest olan diğerinde reddediliyorsa
/// kullanıcı "İleri"ye geçemez.
fn mountpoint_required(editing: Option<&CustomPartition>, fstype: &FsType) -> bool {
if *fstype == FsType::Lvm {
return false;
}
match editing {
None => true,
Some(p) => p.orig_device.is_empty(),
}
}
/// "Yeni Bölüm" diyaloğu için varsayılan bağlama noktası önerisi.
///
/// `/`, `/boot/efi` ve `swap` bir kurulumda **TEKİLDİR** — birdir. Bu
@@ -1919,12 +1950,25 @@ impl PartitionStep {
let encrypt = self.manual.add_encrypt;
let luks_password = if encrypt { self.manual.add_luks_password.clone() } else { String::new() };
// LVM PV'nin bağlama noktası YOKTUR; üzerinde
// mantıksal hacimler yaşar. Diğer her dosya
// sistemi için mountpoint zorunludur.
let is_pv = fstype == FsType::Lvm;
if mp.is_empty() && !is_pv {
self.manual.error = Some(t!("mp_error_empty_mountpoint").to_string());
// Aynı kural `validate_manual_on` ile paylaşılır
// (`mountpoint_required`): ikisi de "fizikselse
// serbest" demeli, yoksa birinde geçen diğerinde
// reddedilir.
let editing = if is_edit {
self.manual.edit_idx.and_then(|i| state.custom_partitions.get(i))
} else {
None
};
if mp.is_empty() && mountpoint_required(editing, &fstype) {
// Hangi bölüm olduğunu SÖYLE: ekranda beş
// bölüm varken genel mesaj kullanıcıyı
// yanlış bölümü aramaya yolluyordu.
let label = editing
.map(|p| p.device.clone())
.unwrap_or_else(|| t!("mp_new_partition").to_string());
self.manual.error = Some(
t!("mp_error_empty_mountpoint_named", device = label).to_string()
);
} else if target_disk.is_empty() {
self.manual.error = Some(t!("mp_select_disk_first").to_string());
} else if is_edit {
@@ -3174,6 +3218,88 @@ mod multidisk_tests {
);
}
// ── REGRESYON: "yalnızca GRUB için" bölüm ──────────────────
/// Düzenlenen FİZİKSEL bölümün mountpoint'i boş bırakılabilir.
///
/// Kullanıcı raporu: "sda1 grub eklenecek o yüzden herhangi bir
/// mount olayı yok ve yine bağlama noktası boş olamaz uyarısı
/// alıyorum."
///
/// REGRESYON: kaydetme diyaloğunun koşulu `!is_pv` idi; fiziksel
/// bölüm düzenlenirken de mountpoint zorunluydu. Çift önyüklemeli
/// kurulumda Windows'un boot bölümünü "kuruluma ait değil, yalnızca
/// GRUB için" diye işaretlemek isteyen kullanıcı DİYALOĞU geçemiyordu.
#[test]
fn grub_only_physical_partition_may_have_no_mountpoint() {
let sda1 = p("/dev/sda", "/dev/sda1", 512, FsType::Fat32, "");
assert!(
!mountpoint_required(Some(&sda1), &FsType::Fat32),
"yalnızca GRUB için kullanılacak fiziksel bölüm boş bırakılabilmeli"
);
}
/// Yeni tanımda mountpoint yine de ZORUNLU.
///
/// Yukarıdaki gevşetme "her şey serbest" olmamalı — kullanıcı
/// eklediği yeni bir bölümün ne işe yarayacağını söylemeden
/// kaydedemez, yoksa sessizce mount edilmeyen bir tanım kalır.
#[test]
fn new_definition_still_requires_a_mountpoint() {
let mut new_def = p("/dev/sdc", "/dev/sdc1", 100_000, FsType::Ext4, "");
new_def.orig_device = String::new();
assert!(
mountpoint_required(Some(&new_def), &FsType::Ext4),
"yeni tanımda mountpoint zorunlu olmalı"
);
// Ekleme modunda düzenlenen bölüm yoktur → zorunlu
assert!(
mountpoint_required(None, &FsType::Ext4),
"ekleme modunda mountpoint zorunlu olmalı"
);
}
/// LVM PV serbest — mount edilmez, üzerinde LV'ler yaşar.
#[test]
fn lvm_pv_never_requires_a_mountpoint() {
let physical = p("/dev/sda", "/dev/sda1", 20_000, FsType::Ext4, "");
assert!(
!mountpoint_required(Some(&physical), &FsType::Lvm),
"fiziksel bölüm LVM'ye çevrilse bile PV mountpoint istemez"
);
assert!(
!mountpoint_required(None, &FsType::Lvm),
"yeni PV de mountpoint istemez"
);
}
/// Kullanıcının tam senaryosu: sda'da Windows (sda1 yalnızca GRUB
/// için, mount edilmeyecek), sdb'de Linux, sdc boş. Düzen GEÇERLİ
/// olmalı — GRUB için ayrılmış bölüm yüzünden reddedilmemeli.
#[test]
fn full_user_scenario_with_grub_only_partition_is_valid() {
let disks = vec![
disk("/dev/sda", 250),
disk("/dev/sdb", 500),
disk("/dev/sdc", 1_000),
];
let parts = vec![
// Windows boot — yalnızca GRUB, mount yok
p("/dev/sda", "/dev/sda1", 512, FsType::Fat32, ""),
p("/dev/sdb", "/dev/sdb1", 1_024, FsType::Fat32, "/boot/efi"),
p("/dev/sdb", "/dev/sdb2", 8_192, FsType::Swap, "swap"),
p("/dev/sdb", "/dev/sdb3", 400_000, FsType::Ext4, "/"),
// sdc'de kullanıcının eklediği /home
p("/dev/sdc", "/dev/sdc1", 900_000, FsType::Ext4, "/home"),
];
assert_eq!(
validate_manual_on(&parts, &[], true, &disks),
None,
"GRUB için ayrılmış bölüm düzeni geçersiz kılmamalı: {:?}",
validate_manual_on(&parts, &[], true, &disks)
);
}
/// numaralandırma: iki diskte bağımsız slot dağıtımı.
/// ESKİ test (renumber_keeps_disks_independent) yalnızca fiziksel
/// bölümleri kullandığı için TUUTOLOJİK'ydi: global (disk-suz)