fix(partition): P0 veri kaybı — bölüm numaralandırması yanlış modellemiş

## Bulgu

`plan_disk_layout` bölüm numaralarını "başlangıç offset'ine göre
sıralayıp 1'den numaralandırıyordu" varsayımıyla çalışıyordu.
28 Eyl 2026'da canlı ISO'da ölçüldü: **varsayım yanlış.**

## Ölçüm

Kernel bölümü **başlangıç offset'ine göre değil, bölüm tablosu
slotuna göre** adlandırır. msdos ve gpt için ayrı ayrı doğrulandı.

msdos (VM'de, reboot sonrası taze okuma):
    /dev/sdc1 =    1 MiB (100 MiB)   korunan
    /dev/sdc2 = 5001 MiB (1000 MiB)  YENİ
    /dev/sdc3 =  301 MiB (4700 MiB)  korunan — KAYMADI

gpt (host'ta loop + --partscan): GPT giriş dizisi ile LBA sırası
bilerek FARKLI kuruldu (dizi 2 = 5001 MiB, dizi 3 = 301 MiB). Kernel
yine dizi sırasını verdi.

## Etkisi (veri kaybı)

Eski kod `device_map`'i TERS yazıyordu: yeni bölüme `sdX3`, korunan
bölüme `sdX2`. FAZ C mkfs'i KORUNMASI GEREKEN 4700 MiB'lik bölüme
uygulardı — tam olarak `plan_disk_layout`'ın önlemek için yazıldığı
veri kaybı senaryosu.

## Düzeltme

    // hayatta kalanlar numaralarını KORUR
    let mut taken: BTreeSet<u32> = survivors...;
    // her yeni bölüm en küçük BOŞ slotu alır
    for p in planned.iter_mut() { let num = ilk boş slot; }

Konum hesabı (first-fit / en büyük boş bölge) DEĞİŞMEDİ — o hâlâ
gerekli. Yalnızca numaralandırma basitleştirildi.

`remapped` tamamen kaldırıldı: hayatta kalan bölüm artık kaymadığı
için hep boş kalıyordu. Ölü durum bırakılmadı; dönüş tipi
`Result<Vec<PlannedCreate>, String>` oldu, FAZ B'deki döngü silindi.

## Uçtan uca doğrulama

Kodun tahmini ile gerçek kernel BİREBİR örtüştü (2 yeni bölüm +
1 silme; slot 2 ve slot 4 — slot 3'ü hayatta kalan bölüm tutuyor):

    kernel: p1=1/100  p2=5001/300  p3=301/4700  p4=5301/1000
    kod   :                sdc2          .........  sdc4

## Ders

Bu, `assign_partition_numbers`'ın "en küçük boş slot" kuralının zaten
DOĞRU olduğu anlamına gelir. `plan_disk_layout`'ın bütün varlık
sebebi (offset'e göre yeniden numaralandırma) gerekli değilmiş.

Ölçüm tahminin yerine geçti. 11 test vardı ama hepsi AYNI yanlış
varsayımı paylaşıyordu — testler doğruyu kanıtlamaz, varsayımı
tutarlı kılar. "Kernel şunu yapar" inancı ölçülmeli.

## Testler (150 → 151)

- live_verified_sdc_oversized_new_partition: artık slot 2 bekliyor
- delete_plus_create_renumbers_physical_partitions →
  delete_plus_create_keeps_physical_partition_numbers
- deleting_middle_shifts_later_partitions_down →
  deleting_a_partition_makes_others_slide (kayma YOK)
- deleting_last_partition_shifts_nothing → ...changes_nothing

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erkan IŞIK
2026-09-28 01:03:56 +03:00
parent 5f78df981a
commit 546bb79be7
2 changed files with 159 additions and 160 deletions
+62 -44
View File
@@ -724,11 +724,11 @@ nedeni yorumla sabitlendi. Log bile "MiB" derken argüman "MB" gönderiyordu.
```
cargo check --all-targets : 0 hata, 0 uyarı
cargo test : 150/150 (test modülü sayısı arttıkça güncel tut)
cargo test : 151/151 (test modülü sayısı arttıkça güncel tut)
cargo clippy : ~77 uyarı, tamamı stil (collapsed_if, replace zinciri)
```
Bu oturumda 1 → 150 test. Test modülü olmayan dosyalar (`users.rs`,
Bu oturumda 1 → 151 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()`).
@@ -827,7 +827,7 @@ senaryoları dene:
| Dosya | Son durum |
|---|---|
| `src/jobs/partition.rs` | `plan_disk_layout`, `PhysicalExtent`, `PlannedCreate`, `disk_total_mb`, `read_physical_extents`, `device_map`; `disk_free_regions_mb` silindi |
| `src/jobs/partition.rs` | `plan_disk_layout` (canlı kernel ile doğrulandı), `PhysicalExtent`, `PlannedCreate`, `disk_total_mb`, `read_physical_extents`, `device_map`; `disk_free_regions_mb` ve `remapped` silindi |
| `src/jobs/cleanup.rs` | **YENİ** — `EmergencyCleanupJob` |
| `src/steps/partition.rs` | **tek diske odaklı tablo, şerit YOK** (diyalogda da disk seçicisi yok), **seçime göre görünen butonlar** (`selected_free`), diske filtreli LVM, `validate_manual_on`, `manual_capacity_error`, `mountpoint_required`, `suggest_mountpoint`, `has_symbolic_remaining`, `build_table_rows`, `normalize_lsblk_mountpoint`, `on_disk_changed`, `format_checkbox_from`, `skip_format_from`; `multidisk_tests`, `table_scope_tests`, `free_space_tests`, `symbolic_capacity_tests`, `selection_tests`, `edit_flow_tests`, `edit_disk_tests`, `testutil` |
| `src/jobs/users.rs` | `set_password_via_chpasswd` (stdin pipe) |
@@ -843,59 +843,77 @@ senaryoları dene:
---
## 🛑 DOĞRULANMADI: `plan_disk_layout`'ın çekirdek varsayımı yanlış görünüyor
## ✅ ÇÖZÜLDÜ: `plan_disk_layout` gerçek kernel ile doğrulandı ve düzeltildi
**Durum: inceleniyor, düzeltme YAPILMADI.** 28 Eyl 2026'da canlı
ISO'ya SSH ile bağlanıp gerçek `parted` + kernel davranışı ölçüldü.
**28 Eyl 2026.** Bu bir P0 **veri kaybı** hatasıydı; canlı ISO'da
ölçüldü, düzeltildi ve düzeltme gerçek kernel ile doğrulandı.
### Deney (sdc, 8 GiB, tamamen boş — test için kullanıldı)
### ❌ Eski (YANLIŞ) varsayım
> *"parted rm N sonrası kernel bölümleri başlangıç offset'ine göre
> sıralayıp 1'den numaralandırır."*
Kod buna göre `survivors.sort_by_key(start)` yapıp her şeyi 1'den
numaralandırıyor, kaymış bölümleri `remapped` ile bildiriyordu.
### ✅ Gerçek davranış (ölçüldü)
**Kernel, bölümü başlangıç offset'ine göre DEĞİL, bölüm tablosu
slotuna göre adlandırır.** Bu hem `msdos` hem `gpt` için doğrulandı.
**msdos ölçümü** (VM'de, reboot sonrası taze kernel okuması):
```
1. mklabel msdos
2. mkpart 1-101 MiB · 101-301 MiB · 301-5001 MiB
3. rm 2 → 200 MiB boşluk
4. mkpart 5001-6001 MiB (1000 MiB) → boşluğa SIĞMAZ
mklabel msdos · 1-101 · 101-301 · 301-5001 · rm 2 · mkpart 5001-6001 MiB
→ /dev/sdc1 = 1 MiB (100 MiB) korunan
/dev/sdc2 = 5001 MiB (1000 MiB) YENİ
/dev/sdc3 = 301 MiB (4700 MiB) korunan — KAYMADI
```
`parted` yeni bölümü **MBR slot 2**'ye yazdı. Kernel'in okuması:
**gpt ölçümü** (host'ta loop + `--partscan`): GPT giriş dizisi ile
LBA sırası **bilerek farklı** kuruldu (dizi 2 = 5001 MiB, dizi 3 =
301 MiB). Kernel yine dizi sırasını verdi → LBA'ya göre sıralamıyor.
```
/dev/sdc1 start=1 MiB 100 MiB ← korunan
/dev/sdc2 start=5001 MiB 1000 MiB ← YENİ bölüm
/dev/sdc3 start=301 MiB 4700 MiB ← ESKİ bölüm (301 MiB'de AMA sdc3!)
### Düzeltme
```rust
// hayatta kalanlar numaralarını KORUR
let mut taken: BTreeSet<u32> = survivors.iter().filter_map(|(_,_,o)| *o).collect();
// her yeni bölüm en küçük BOŞ slotu alır
for p in planned.iter_mut() {
let num = (1..).map(|n| n as u32).find(|n| !taken.contains(n))...;
}
```
`sdc3` **301 MiB**'de başlıyor ama 3 numarasını taşıyor; yeni bölüm
**5001 MiB**'de başlıyor ama 2 numarasını. **Kernel offset'e göre
sıralamıyor — MBR slot numarasını doğrudan aygıt adı yapıyor.**
`remapped` **tamamen kaldırıldı** — hayatta kalan bölüm artık kaymadığı
için hep boş kalıyordu; ölü durum bırakılmadı. Dönüş tipi
`Result<Vec<PlannedCreate>, String>` oldu, FAZ B'deki döngü silindi.
### Bu neden kritik
### Uçtan uca doğrulama
`plan_disk_layout` (ve `AGENTS.md`'deki gerekçesi) varsayımı:
*"parted rm N sonrası kernel bölümleri başlangıç offset'ine göre
sıralayıp 1'den numaralandırır."*
Kodun tahmini ile gerçek kernel **birebir** örtüştü (2 yeni bölüm +
1 silme, `slot 2` ve `slot 4` — slot 3'ü hayatta kalan bölüm tutuyor):
Gerçekte (MBR'da) öyle değil. O halde `plan_disk_layout` bu senaryoda
`/dev/sdc3` öngörüyor; **gerçekte yeni bölüm `/dev/sdc2`**. `device_map`
yanlış aygıtı gösterir → FAZ C **4700 MiB'lik KORUNAN bölümü** mkfs
eder. Tam olarak `plan_disk_layout`'ın *önlemek için* yazıldığı
veri kaybı.
```
kernel: p1=1MiB/100 p2=5001MiB/300 p3=301MiB/4700 p4=5301MiB/1000
kod : sdc2 5001-5301 sdc4 5301-6301
```
### Doğrulanmadan önce çözülecek iki soru
### Bu neden önemliydi
1. **GPT davranışı ölçülmedi.** `sda` ve `sdb` **GPT**, ölçüm
**msdos** yapıldı. GPT'de kernel `efi_partition` diziyi LBA'ya göre
sıralıyor olabilir → iki tablo tipi farklı davranabilir. Ayırt edici
test hazırlandı ama tamamlanmadı: GPT giriş dizisi ile LBA sırası
farklı bir imaj (`/tmp/opencode/gpttest.img`) VM'e takılıp
kernel'in adlandırması okunacak.
2. **Kernel tablosu taze miydi?** `partx -a` her çağrıda "error"
döndürdü ama tabloyu yine de güncelledi. `reboot` ve ACPI güç
düğmesi yetki yokluğu nedeniyle **çalışmadı** (`/proc/uptime`
değişmedi) → tazeden okuma ile doğrulanamadı. Bu VM'de
`BLKRRPART` izni yok.
Eski kod `device_map`'i **ters** yazıyordu: yeni bölüme `sdX3`,
korunan bölüme `sdX2` diyordu. FAZ C `mkfs`'i **korunması gereken
4700 MiB'lik bölüme** uygulardı — tam olarak `plan_disk_layout`'ın
*önlemek için* yazıldığı veri kaybı.
> ⚠️ Bu blok düzeltme DEĞİL, uyarıdır. `plan_disk_layout`'a
> dokunmadan önce yukarıdaki iki soru cevaplanmalı; aksi hâlde
> mevcut davranış ile "düzeltilmiş" davranış arasında hangisinin
> doğru olduğunu bilemeyiz.
> 💡 **Bu, `assign_partition_numbers`'ın "en küçük boş slot" kuralının
> zaten DOĞRU olduğu anlamına gelir.** `plan_disk_layout`'ın bütün
> varlık sebebi (offset'e göre yeniden numaralandırma) gerçekte
> gerekli değilmiş. Yeni bölümlere verilen konum (first-fit /
> en büyük boş bölge) hâlâ gerekli ve kaldı; **yalnızca numaralandırma
> basitleştirildi.**
>
> Ölçüm, tahminin yerine geçmiştir. Ders: "kernel şunu yapar" diye
> inanılan her kural **ölçülmelidir** — bu proje tam da o yüzden
> `plan_disk_layout`'a 11 test yazmış ama testler de aynı yanlış
> varsayımı paylaşıyordu.
+97 -116
View File
@@ -77,7 +77,7 @@ pub fn plan_disk_layout(
physical: &[PhysicalExtent],
destroyed: &[u32],
creates: &[(usize, u64)],
) -> Result<(Vec<PlannedCreate>, Vec<(u32, String)>), String> {
) -> Result<Vec<PlannedCreate>, String> {
// 1. Silinmeyen fiziksel bölümler
let mut survivors: Vec<(u64, u64, Option<u32>)> = physical
.iter()
@@ -145,37 +145,53 @@ pub fn plan_disk_layout(
survivors.push((start_mb, end_mb, None));
}
// 3. Başlangıç offset'ine göre sırala → kernel numaralarını dağıt.
// `parted` bölüm tablosunu bu sırayla gösterir; kernel de aynı
// sırayı kullanır.
survivors.sort_by_key(|(s, _, _)| *s);
// 3. ⭐ Bölüm NUMARALARI: tablo slotuna göre dağıt.
//
// ÖNCEKİ YANLIŞ YAKLAŞIM: "başlangıç offset'ine göre sırala,
// 1'den numaralandır". Canlı ISO'da ölçüldü ve YANLIŞ çıktı
// (aşağıya). Şimdi: hayatta kalan fiziksel bölümler KENDİ
// numaralarını KORUR, her yeni bölüm EN KÜÇÜK BOŞ SLOTU alır.
//
// Ölçüm (28 Eyl 2026, gerçek parted + kernel):
//
// msdos: rm 2 sonra mkpart 5001-6001MiB
// parted MBR slot 2'ye yazdı
// kernel: /dev/sdc1=1MiB /dev/sdc2=5001MiB(YENİ) /dev/sdc3=301MiB
//
// gpt: aynı işlem, GPT giriş dizisi = 1(1MiB) 2(5001MiB) 3(301MiB)
// kernel: loop0p1=1MiB loop0p2=5001MiB(YENİ) loop0p3=301MiB
//
// İkisinde de kernel bölümü **başlangıç offset'ine göre
// sıralamıyor**; **tablo slotunu doğrudan aygıt numarası**
// yapıyor. Yani 301 MiB'de başlayan bölüm `3` kalıyor,
// 5001 MiB'de başlayan yeni bölüm `2` oluyor.
//
// Bu, UI'ın "en küçük boş slot" mantığının zaten DOĞRU
// olduğu anlamına gelir; `assign_partition_numbers`'a
// güvenmek yerine burada da aynı kural uygulanır.
let mut planned_by_index: HashMap<usize, u32> = HashMap::new();
let mut remapped: Vec<(u32, String)> = Vec::new();
for (idx, (start, _, orig)) in survivors.iter().enumerate() {
let num = (idx + 1) as u32;
let device = part_path(disk, num);
match orig {
// Fiziksel bölüm: yeni numarasını öğren
Some(old) => {
if *old != num {
remapped.push((*old, device.clone()));
}
}
// Yeni bölüm: hangi tanım olduğunu eşle
None => {
// Bu aralık hangi planlanmış bölüme ait?
if let Some(p) = planned
.iter_mut()
.find(|p| p.start_mb == *start && p.num == 0)
{
p.num = num;
p.device = device.clone();
planned_by_index.insert(p.part_index, num);
}
}
}
// Hayatta kalan fiziksel bölümler numaralarını korur; onların
// slotu "dolu" sayılır.
let mut taken: std::collections::BTreeSet<u32> = survivors
.iter()
.filter_map(|(_, _, orig)| *orig)
.collect();
// Yeni bölümlere sırayla en küçük boş slotu ver. `planned` zaten
// oluşturma sırasındadır; `parted` de her `mkpart`'ı en küçük
// boş slota yazar.
for p in planned.iter_mut() {
let num = (1..)
.map(|n| n as u32)
.find(|n| !taken.contains(n))
.ok_or_else(|| {
format!("{}: bölüm tablosunda boş slot kalmadı", disk)
})?;
taken.insert(num);
p.num = num;
p.device = part_path(disk, num);
planned_by_index.insert(p.part_index, num);
}
// Eşleşmeyen planlanmış bölüm kalmamalı
@@ -185,7 +201,7 @@ pub fn plan_disk_layout(
"{}: planlanan bölüm sayısı ile eşleşen sayı uyuşmuyor", disk
);
Ok((planned, remapped))
Ok(planned)
}
/// Başlangıç offset'ine göre sıralı, dolu aralıklardan boş bölgeleri üretir.
@@ -862,19 +878,9 @@ impl Job for CustomPartitionJob {
.map(|&i| (i, creates[i].size_mb))
.collect();
let (planned, remapped) =
let planned =
plan_disk_layout(disk, total_mb, &physical, &destroys, &create_specs)?;
// Silmeden sonra kaymış fiziksel bölümler: FAZ C bunları
// UI'ın numarasıyla değil, GERÇEK yollarıyla işleyecek.
for (old_num, new_dev) in &remapped {
ui.log(format!(
"{}: {} → {} (silinen bölüm nedeniyle numara kaydı)",
disk, part_path(disk, *old_num), new_dev
));
device_map.insert(part_path(disk, *old_num), new_dev.clone());
}
for plan in &planned {
let part = creates[plan.part_index];
// Yeni bölüm: UI'ın tahmin ettiği yol → gerçek yol.
@@ -1295,119 +1301,101 @@ mod layout_tests {
/// ```
///
/// 2. bölüm silinmiş. Boşluk 200 MiB — 1000 MiB'lik yeni bölüm
/// **buraya SIĞMAZ**. Kod konumu kendisi hesapladığı için
/// diskin sonuna (5001 MiB) yazması ve 3. bölümün 2'ye kayması
/// gerekir.
/// **buraya SIĞMAZ** → diskin sonuna (5001 MiB) yazılır.
///
/// Ölçülen kernel sonucu:
/// /dev/sdc1 = 1 MiB (100 MiB) ← korunan
/// /dev/sdc2 = 5001 MiB (1000 MiB) ← YENİ
/// /dev/sdc3 = 301 MiB (4700 MiB) ← korunan, KAYMADI
#[test]
fn live_verified_sdc_oversized_new_partition() {
let physical = vec![phys(1, 1, 101), phys(3, 301, 5001)];
let destroyed = vec![2u32];
let creates = vec![(0usize, 1000u64)];
let (planned, remapped) =
let planned =
plan_disk_layout("/dev/sdc", 8192, &physical, &destroyed, &creates)
.expect("planlanmalı");
assert_eq!(planned.len(), 1, "tek yeni bölüm planlanmalı");
let p = &planned[0];
println!(
"PLAN: {}MiB-{}MiB slot {} ({}), kayma {:?}",
p.start_mb, p.end_mb, p.num, p.device, remapped
"PLAN: {}MiB-{}MiB slot {} ({})",
p.start_mb, p.end_mb, p.num, p.device
);
// 200 MiB'lik boşluğa SIĞMAZ → disk sonuna yazılmalı
assert!(
p.start_mb >= 5001,
"boşluğa sığmayan bölüm disk sonuna konmalı, {}MiB yazıldı",
p.start_mb
);
assert_eq!(p.start_mb, 5001, "yeni bölüm diskin sonuna konmalı");
assert_eq!(p.end_mb - p.start_mb, 1000, "istenen boyut korunmalı");
// Eski 3 numaralı bölüm 2'ye KAYMALI
assert!(
remapped.iter().any(|(old, new)| *old == 3 && new.contains("sdc2")),
"eski slot 3, sdc2'ye kaymalıydı; kayma listesi: {:?}",
remapped
);
// ⭐ ÖLÇÜLEN GERÇEK: yeni bölüm boşaltılan slot 2'yi alır,
// 301 MiB'de başlayan korunan bölüm 3'te KALIR.
assert_eq!(p.num, 2, "yeni bölüm serbest kalan slot 2'yi almalı");
assert_eq!(p.device, "/dev/sdc2");
}
/// REGRESYON — asıl bulgu.
/// ⭐ REGRESYON — canlı ölçümle düzeltildi.
///
/// `assign_partition_numbers` UI'da yeni bölüme "en küçük boş slot"u
/// (= 2) veriyor. Ancak `parted rm 2` sonrası serbest kalan boşluk
/// yalnızca 100 MB; 5000 MB'lık istenen bölüm oraya SIĞMAZ ve
/// first-fit diskin SONUNA kayar. parted bölümleri başlangıç
/// offset'ine göre sıraladığı için kernel'de yeni bölüm 3 olur.
/// ÖNCE (YANLIŞ): kod bölümleri başlangıç offset'ine göre
/// sıralayıp 1'den numaralandırıyordu; yeni bölüm 3, eski 3 → 2
/// "kaydı" diye raporlanıyordu.
///
/// UI "sda2 = yeni" derken diskte "sda3 = yeni" olurdu; FAZ C
/// eski sda3'ü (artık sda2) biçimlendirirdi → VERİ KAYBI.
/// GERÇEK (ölçüldü, msdos VE gpt): kernel tablo slotunu doğrudan
/// aygıt numarası yapıyor. Silinen slot boşalınca yeni bölüm
/// onu alır, hayatta kalanlar KENDİ numaralarında kalır.
#[test]
fn delete_plus_create_renumbers_physical_partitions() {
fn delete_plus_create_keeps_physical_partition_numbers() {
let physical = vec![
phys(1, 1, 100), // korunuyor
phys(2, 100, 200), // SİLİNECEK
phys(3, 200, 30_200), // korunuyor
];
// 5000 MB'lik yeni bölüm
let creates = vec![(0usize, 5_000u64)];
let (planned, remapped) =
let planned =
plan_disk_layout("/dev/sda", 40_000, &physical, &[2], &creates).expect("planlanmalı");
// Yeni bölüm 100–200 boşluğuna SIĞMAZ → diskin sonuna (30200+) gider
// Konum: 100–200 boşluğuna SIĞMAZ → diskin sonuna
assert_eq!(
planned[0].start_mb, 30_200,
"boşluk 100 MB olduğu için yeni bölüm diskin sonuna yerleşmeli"
);
// Başlangıç offset'ine göre sıra: sda1(1), sda2(200), sda3(30200)
assert_eq!(planned[0].num, 3, "yeni bölümün gerçek numarası 3 olmalı");
assert_eq!(planned[0].device, "/dev/sda3");
// Eski sda3 artık sda2 — FAZ C bunu bilmeli
assert_eq!(
remapped, vec![(3, "/dev/sda2".to_string())],
"silinen orta bölüm, sonrakileri bir kaydırmalı"
);
// ⭐ Numara: boşaltılan slot 2 → yeni bölüm 2 olur
assert_eq!(planned[0].num, 2, "yeni bölüm boşaltılan slotu almalı");
assert_eq!(planned[0].device, "/dev/sda2");
}
/// Sadece silme, yeni bölüm yok: hayatta kalanlar kayar.
/// Sadece silme, yeni bölüm yok: hayatta kalanlar KAYMAZ.
///
/// Önceden "sda3 → sda2" kayması bekleniyordu; ölçümde olmadı.
#[test]
fn deleting_middle_shifts_later_partitions_down() {
let physical = vec![
phys(1, 1, 100),
phys(2, 100, 200),
phys(3, 200, 300),
];
let (planned, remapped) =
fn deleting_a_partition_makes_others_slide() {
let physical = vec![phys(1, 1, 100), phys(2, 100, 200), phys(3, 200, 300)];
let planned =
plan_disk_layout("/dev/sda", 40_000, &physical, &[2], &[]).expect("planlanmalı");
assert!(planned.is_empty(), "yeni bölüm yok");
// sda1→sda1, sda3→sda2
assert_eq!(remapped, vec![(3, "/dev/sda2".to_string())]);
}
/// Silinen bölüm sonuncusa hiçbir şey kaymaz.
/// Silinen bölüm sonuncusa da durum aynı: kayma yok.
#[test]
fn deleting_last_partition_shifts_nothing() {
fn deleting_last_partition_changes_nothing() {
let physical = vec![phys(1, 1, 100), phys(2, 100, 200)];
let (_, remapped) =
let planned =
plan_disk_layout("/dev/sda", 40_000, &physical, &[2], &[]).expect("planlanmalı");
assert!(remapped.is_empty(), "son bölüm silinince kayma olmaz");
assert!(planned.is_empty());
}
/// Basit durum: silme yok, yeni bölüm diskin sonuna.
/// Numara = mevcut en yüksek numara + 1 (yani 3).
#[test]
fn simple_create_after_existing_partitions() {
let physical = vec![phys(1, 1, 100), phys(2, 100, 30_100)];
// Disk 50_000 MB: son bölüm 30_100'de bitiyor, 19_899 MB boş var.
let (planned, remapped) =
plan_disk_layout("/dev/sda", 50_000, &physical, &[], &[(0, 10_000)])
.expect("planlanmalı");
let planned = plan_disk_layout("/dev/sda", 50_000, &physical, &[], &[(0, 10_000)])
.expect("planlanmalı");
assert_eq!(planned[0].start_mb, 30_100);
assert_eq!(planned[0].num, 3);
assert_eq!(planned[0].num, 3, "dolu slotlar 1,2 → yeni bölüm 3");
assert_eq!(planned[0].device, "/dev/sda3");
assert!(remapped.is_empty(), "fiziksel bölümler kaymamalı");
}
/// "Kalan alan" (sembolik) en büyük bölgeye gider.
@@ -1415,7 +1403,7 @@ mod layout_tests {
fn symbolic_takes_the_largest_region() {
// İki boş bölge: 200–300 (küçük) ve 1000–20000 (büyük)
let physical = vec![phys(1, 1, 200), phys(2, 300, 1000)];
let (planned, _) = plan_disk_layout("/dev/sda", 20_000, &physical, &[], &[(0, 0)])
let planned = plan_disk_layout("/dev/sda", 20_000, &physical, &[], &[(0, 0)])
.expect("planlanmalı");
assert_eq!(planned[0].start_mb, 1000, "en büyük bölge seçilmeli");
assert!(planned[0].symbolic);
@@ -1427,7 +1415,7 @@ mod layout_tests {
fn multiple_creates_get_increasing_slots() {
let physical = vec![phys(1, 1, 100)];
let creates = vec![(0usize, 2_000u64), (1usize, 3_000u64), (2usize, 0u64)];
let (planned, _) = plan_disk_layout("/dev/sda", 40_000, &physical, &[], &creates)
let planned = plan_disk_layout("/dev/sda", 40_000, &physical, &[], &creates)
.expect("planlanmalı");
assert_eq!(planned.len(), 3);
@@ -1465,16 +1453,13 @@ mod layout_tests {
let sda = vec![phys(1, 1, 100), phys(2, 100, 30_100)];
let sdb = vec![phys(1, 1, 100)];
let (p_sda, r_sda) =
plan_disk_layout("/dev/sda", 40_000, &sda, &[], &[(0, 1_000)]).expect("sda");
let (p_sdb, r_sdb) =
plan_disk_layout("/dev/sdb", 40_000, &sdb, &[], &[(0, 2_000)]).expect("sdb");
let p_sda = plan_disk_layout("/dev/sda", 40_000, &sda, &[], &[(0, 1_000)]).expect("sda");
let p_sdb = plan_disk_layout("/dev/sdb", 40_000, &sdb, &[], &[(0, 2_000)]).expect("sdb");
// sda'da 2 fiziksel bölüm var → yeni 3 olur; sdb'de 1 var → yeni 2.
// Kritik olan ikisinin de AYRI kalması: sda1 ile sdb1 çakışmamalı.
assert_eq!(p_sda[0].device, "/dev/sda3");
assert_eq!(p_sdb[0].device, "/dev/sdb2");
assert!(r_sda.is_empty() && r_sdb.is_empty());
}
/// EN KRİTİK ÇOKLU DİSK SENARYOSU: kök sda'da, /home sdb'de.
@@ -1483,28 +1468,24 @@ mod layout_tests {
fn deleting_on_one_disk_does_not_renumber_another() {
// sda: sda1 korunuyor, sda2 siliniyor, yeni 5000 MB
let sda = vec![phys(1, 1, 100), phys(2, 100, 200)];
let (p_sda, r_sda) =
plan_disk_layout("/dev/sda", 40_000, &sda, &[2], &[(0, 5_000)]).expect("sda");
let p_sda = plan_disk_layout("/dev/sda", 40_000, &sda, &[2], &[(0, 5_000)]).expect("sda");
// sdb: bağımsız — kendi fiziksel bölümü
let sdb = vec![phys(1, 1, 20_000)];
let (p_sdb, r_sdb) =
plan_disk_layout("/dev/sdb", 40_000, &sdb, &[], &[(0, 1_000)]).expect("sdb");
let p_sdb = plan_disk_layout("/dev/sdb", 40_000, &sdb, &[], &[(0, 1_000)]).expect("sdb");
// sda'da 200–40000 boş: yeni bölüm oraya gider, numarası 2
assert_eq!(p_sda[0].device, "/dev/sda2");
assert!(r_sda.is_empty(), "sda1 zaten 1, kayma yok");
// sdb HİÇ ETKİLENMEMELİ
assert_eq!(p_sdb[0].device, "/dev/sdb2");
assert!(r_sdb.is_empty(), "sdb numaraları sda'dan bağımsız");
}
/// NVMe/MMC adlandırması: sda1 değil sda1p1 değil — part_path'e bırakıldı.
#[test]
fn nvme_slots_use_p_separator() {
let physical = vec![phys(1, 1, 100)];
let (planned, _) = plan_disk_layout("/dev/nvme0n1", 10_000, &physical, &[], &[(0, 500)])
let planned = plan_disk_layout("/dev/nvme0n1", 10_000, &physical, &[], &[(0, 500)])
.expect("planlanmalı");
assert_eq!(planned[0].device, "/dev/nvme0n1p2");
}