Yeni bir USB-NVMe kutusu aldım. İçine 2 TB'lık bir NVMe SSD taktım, diski LVM ile bölüp XFS ile biçimlendirdim. Kutunun kutusunda "TRIM desteği" yazıyordu. Linux ise kutuyu ilk taktığımda şunu söyledi:
XFS (dm-1): mounting with "discard" option, but the device does not support discard
Yani: "Bu aygıt TRIM'i desteklemiyor." Bununla uğraşırken iki sorun daha çıktı. Kutuyu çıkarıp takınca bilgisayar onu hiç görmedi. Bir keresinde de LVM birimi açılmadı.
Bu yazıda üç sorunu, nedenlerini nasıl bulduğumu ve kalıcı çözümlerini anlatıyorum. Komutların çoğu yalnızca okuyan komutlardır; kendi sisteminizde güvenle deneyebilirsiniz.
Kullanılan donanım
| Parça | Bilgi |
|---|---|
| Kutu | ACASIS TB406Pro, Realtek RTL9210 yongası, USB kimliği 0bda:9210 |
| Disk | Samsung SSD 970 EVO Plus 2TB |
| Bağlantı | USB 10 Gbit/s, UAS sürücüsü |
| Bilgisayar | Intel Core Ultra 200 işlemcili dizüstü, Fedora 44, çekirdek 7.2 |
| İkinci kutu | JMicron JMS583 yongalı kutu, USB kimliği 152d:0583, içinde Samsung 1 TB NVMe SSD |
Birkaç terim:
- TRIM: SSD'ye hangi alanların artık boş olduğunu bildiren komut. SSD'nin hızını ve ömrünü korur. USB kutularda bu komutun adı UNMAP'tir.
- Köprü yongası: Kutunun içinde USB ile NVMe arasında çeviri yapan yonga. Bu kutuda Realtek RTL9210.
- udev kuralı: Bir aygıt takıldığında Linux'un kendiliğinden yapacağı işi tanımlayan dosya.
/etc/udev/rules.d/dizininde durur.
Sorun 1: Kutu TRIM'i destekliyor, Linux kapatıyor
Önce bakılacak yer
lsblk -D /dev/sdb
cat /sys/block/sdb/device/scsi_disk/*/provisioning_mode
DISC-MAX sütunu 0B ise TRIM kapalıdır. provisioning_mode değeri full ise çekirdek TRIM'i bilerek kapatmış demektir.
Kutu kendini nasıl tanıtıyor?
sg3_utils paketindeki araçlarla kutunun verdiği bilgiler okunabilir:
sudo sg_vpd -p lbpv /dev/sdb
sudo sg_vpd -p bl /dev/sdb
sudo sg_readcap -l /dev/sdb
Benim kutumda çıkan sonuç:
| Komut | Sonuç | Anlamı |
|---|---|---|
sg_vpd -p lbpv |
LBPU=1 |
"UNMAP komutunu destekliyorum." |
sg_vpd -p bl |
Maximum unmap LBA count: 0x1400000 |
"Tek seferde yaklaşık 10 GiB TRIM kabul ederim." |
sg_readcap -l |
lbpme=0 |
"TRIM özelliğim kapalı." |
Kutu iki ayrı yerde birbirine zıt bilgi veriyor. Linux lbpme=0 bilgisine bakıp TRIM'i kapatıyor. Bu, RTL9210 kutularında sık görülen bir firmware hatası.
Elle denemek
echo unmap | sudo tee /sys/block/sdb/device/scsi_disk/*/provisioning_mode
lsblk -D /dev/sdb
Bundan sonra DISC-MAX 4G oldu. sudo fstrim -v <bağlama dizini> hatasız çalıştı, çekirdek günlüğünde (journalctl -k) hata çıkmadı. Yani kutu TRIM'i gerçekten destekliyor.
Kalıcı çözüm ve ilk denemedeki hata
İnternette sık önerilen kural, değeri scsi_disk olayında unmap yapar. Bende bu kural çalıştı ama işe yaramadı. Çekirdek, diski hemen ardından yeniden okuyup değeri tekrar full yapıyordu.
Çözüm, kuralı diskin block olayına taşımak. Bu olay çekirdeğin yeniden okumasından sonra gelir. Disk bölümleri ve LVM ise bu olay bittikten sonra işlenir. Böylece LVM birimi de TRIM'i doğru alır.
/etc/udev/rules.d/50-rtl9210-trim.rules dosyası (tek satır):
ACTION=="add|change", SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="9210", RUN+="/bin/sh -c 'for f in /sys%p/device/scsi_disk/*/provisioning_mode; do if [ -w $$f ]; then echo unmap > $$f; fi; done'"
Kendi kutunuzun kimliğini lsusb çıktısından bulup 0bda ve 9210 yerine yazın. Kuralda $$f yazılır; udev bunu $f yapar.
Kuralı etkinleştirmek için:
sudo udevadm control --reload
Doğrulamak için kutuyu çıkarıp takın ve provisioning_mode değerine bakın. Ben kutuyu yeniden bağlatıp değeri saniyenin onda biri aralıklarla izledim. Değer 0.56. saniyede full, 0.68. saniyede unmap oldu; bu, LVM birimi açılmadan önceydi.
"discard" bağlama seçeneği yerine fstrim
discard bağlama seçeneği her silmede hemen TRIM gönderir ve USB kutularda yavaşlatabilir. Daha iyisi ara sıra elle çalıştırmaktır:
sudo fstrim -v /mnt/disk
Dikkat: Sistemin haftalık TRIM zamanlayıcısı (fstrim.timer) yalnızca /etc/fstab'da yazılı dosya sistemlerine dokunur. Kutunuz fstab'da yoksa TRIM'i siz çalıştırmalısınız.
Sorun 2: Kutu takılınca görünmüyor
Belirti
Kutuyu çıkarıp taktım; hiçbir disk görünmedi. journalctl -k çıktısında yeni bağlantı kaydı bile yoktu. USB bağlantı noktalarının durumu da boş görünüyordu:
cat /sys/bus/usb/devices/usb2/2-0:1.0/usb2-port*/state
Hepsi not attached idi.
Neden
İpucu tesadüfen geldi. Kutu, ben lsusb -t çalıştırdıktan 1 saniye sonra kendiliğinden göründü. lsusb, uyku kipindeki USB denetleyicisini uyandırır.
Durum şuydu: Kutunun takıldığı USB denetleyicisi boşta kalınca uyku kipine geçiyor ve kutu takılınca kendiliğinden uyanmıyor. Kutu ancak denetleyici başka bir nedenle uyanınca görünüyor. Bunu doğrulamak için durumu bilerek yeniden oluşturdum. Denetleyiciyi yalnızca okuyan bir komutla (lsusb -v) uyandırınca kutu hemen geldi.
Denetleyicinin uyku durumu şöyle görülür:
lspci -nn | grep -i usb
cat /sys/bus/pci/devices/0000:00:0d.0/power/control
cat /sys/bus/pci/devices/0000:00:0d.0/power/runtime_status
control=auto uykuya izin verildiğini, runtime_status=suspended o an uyuduğunu gösterir. Benim günlüğümde aynı bağlantı noktasıyla ilgili bir BIOS hatası da vardı:
ACPI Error: Aborting method \_SB.PC00.TXHC.RHUB.SS01._PLD
Kalıcı çözüm ve ilk denemedeki hata
Denetleyicinin ve kök USB aygıtlarının uyku kipi kapatılır (power/control=on). İlk kuralımın adı 50- ile başlıyordu ve işe yaramadı. Sistemin kendi 60-autosuspend.rules kuralı ondan sonra çalışıp değeri yeniden auto yapıyordu. udev kuralları dosya adı sırasıyla işlenir. Kural dosyasının adını 70- ile başlatınca sorun çözüldü.
/etc/udev/rules.d/70-xhci-guc.rules dosyası:
ACTION=="add|bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x8086", ATTR{device}=="0x7ec0", ATTR{power/control}="on"
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", KERNEL=="usb[0-9]*", ATTRS{vendor}=="0x8086", ATTRS{device}=="0x7ec0", ATTR{power/control}="on"
0x8086 ve 0x7ec0 benim denetleyicimin kimliği. Kendi kimliğinizi lspci -nn çıktısındaki köşeli parantezden bulun (örnek: [8086:7ec0]).
Bedeli: Denetleyici uyumadığı için dizüstü bilgisayarda pil biraz daha hızlı biter.
Doğrulama: Kural kurulduktan sonra kutuyu fiziksel olarak çıkarıp taktım. Hiçbir komut çalıştırmadan kutu hemen göründü.
Sorun 3: LVM'de eski kayıt kalıyor
Belirti
Kutuyu, LVM birimi açıkken çıkarıp yeniden taktım. Disk bu kez sdb yerine sdc adıyla geldi ve LVM birimi açılmadı. Günlükte şunlar vardı:
PV /dev/sdb4 8:20 is duplicate for PVID ... on 8:36 /dev/sdc4.
Buffer I/O error on dev dm-1, logical block 0, async page read
Neden
Kutu, LVM birimi açıkken çıkarılınca iki şey kalır:
- Ölü eşleme:
/dev/mapper/...aygıtı artık olmayan diski göstermeye devam eder. Eşleme:/dev/mapperaltındaki, LVM biriminin diske bağlantısı. - Eski LVM kaydı: LVM,
/run/lvm/pvs_online/dizininde "bu disk takılı" diye kayıt tutar. Bu kayıt eski diski gösterir. Kutu yeniden takılınca LVM, gelen diski aynı diskin ikinci kopyası sanar ve birimi açmaz.
Çözüm
sudo dmsetup info -c vg_adi-lv_adi # Open sütunu 0 olmalı
sudo dmsetup remove vg_adi-lv_adi # ölü eşlemeyi kaldır
sudo pvscan --cache # LVM kayıtlarını baştan oluştur
sudo vgchange -ay vg_adi # birimi aç
Ölü eşleme hala bağlıysa (Open 0 değilse) önce bağlantısı ayrılmalıdır.
En iyisi bu duruma hiç düşmemek. Kutuyu çıkarmadan önce:
sudo umount /mnt/disk
sudo vgchange -an vg_adi
Masaüstü ortamı kutudaki başka bir bölümü (benim kutumda Ventoy bölümü) kendiliğinden bağlamış olabilir. lsblk ile bakıp onu da ayırın.
Kutu bağlıyken çekilirse ne olur?
Bunu istemeden denemiş oldum: kutu, birim bağlıyken çekildi. systemd birimi kendisi ayırdı. XFS son kayıtlarını diske yazamadı (log I/O error -5) ve dosya sistemini kapattı. Bu bir koruma davranışıdır.
Kutuyu yeniden takıp yukarıdaki LVM adımlarını yapınca XFS Ending clean mount yazdı; onarım gerekmedi. O sırada diske yazma yoktu. Yazma sürerken çekilirse son yazılanlar kaybolabilir.
İkinci kutu: JMicron JMS583
Elimdeki ikinci USB-NVMe kutusunda farklı bir köprü yongası var: JMicron JMS583. Aynı kontrolleri yaptım:
| Komut | Sonuç |
|---|---|
sg_vpd -p lbpv |
LBPU=1 ("UNMAP destekliyorum") |
sg_vpd -p bl |
Maximum unmap LBA count: unlimited |
sg_readcap -l |
lbpme=0 ("TRIM özelliğim kapalı") |
provisioning_mode |
full |
Hata aynı. Bir fark var: Çekirdek bu kutu için kendi istisna listesindeki bir kuralı uygulayıp UAS yerine eski usb-storage sürücüsünü kullanıyor. Günlükte şöyle görünüyor:
usb 4-2: UAS is ignored for this device, using usb-storage instead
Bu sürücüyle TRIM'in çalışıp çalışmadığını ancak deneyerek bilebilirdim; kutunun "destekliyorum" demesi bunu kanıtlamaz. Denedim, çalışıyor (kanıtı bir sonraki bölümde). Kalıcı kural, ilk kutununkiyle aynı yapıda; yalnızca USB kimliği farklı.
/etc/udev/rules.d/50-jms583-trim.rules dosyası (tek satır):
ACTION=="add|change", SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", ATTRS{idVendor}=="152d", ATTRS{idProduct}=="0583", RUN+="/bin/sh -c 'for f in /sys%p/device/scsi_disk/*/provisioning_mode; do if [ -w $$f ]; then echo unmap > $$f; fi; done'"
Kutu takılınca görünmeme sorunu bu kutuda olmadı. Bu kutu, bilgisayarın öteki USB denetleyicisine takılıydı.
TRIM'in SSD'ye gerçekten ulaştığını kanıtlamak
fstrim hata vermedi diye TRIM'in çalıştığından emin olamazsınız. Köprü yongası komutu kabul edip SSD'ye hiç iletmeyebilir. fstrim'in yazdığı sayı da kanıt değildir: XFS her seferinde boş alanın tamamını yazar (bende her seferinde 953.9 GiB trimmed).
Kesin kanıt SSD'nin kendisinden gelir. NVMe SSD, kendi saydığı dolu alanı bildirir. TRIM SSD'ye ulaşırsa bu sayı düşer. smartctl (smartmontools paketi) USB kutu üzerinden de okuyabilir:
sudo smartctl -i -d sntjmicron /dev/sda | grep Utilization # JMicron JMS583
sudo smartctl -i /dev/sdb | grep Utilization # Realtek RTL9210
Çıktı:
Namespace 1 Utilization: 377,917,464,576 [377 GB]
-d değeri köprü yongasına göre değişir. smartctl doğru tipi bulamazsa bunu söyler ve seçenekleri yazar.
Dikkat: SSD bu sayıyı gecikmeyle günceller
İlk denememde ölçümü fstrim'den hemen sonra aldım ve yanlış sonuca vardım. TRIM'den hemen sonra sayı yalnızca 378 GB'tan 366 GB'a indi. Yaklaşık 50 saniye sonra 116 GB'a, bir dakika sonra 0.5 GB'a düştü. Bu yüzden ölçümü sayı oturana kadar tekrarlayın: art arda birkaç okuma aynı çıkınca doğru değerdir.
Bulgu 1: Biçimlendirmek her zaman TRIM göndermez
İkinci kutudaki diski yeniden bölümleyip XFS ile biçimlendirdim. Dosya sistemi boş olduğu halde SSD 378 GB dolu gösteriyordu: biçimlendirme TRIM göndermemişti, eski verinin kapladığı alan SSD için hala doluydu. İlk fstrim bunu 0.5 GB'a indirdi. Kalan 0.5 GB, XFS'in kendi yönetim bilgilerinin kapladığı alan.
Oysa mkfs.xfs belgesine göre biçimlendirirken TRIM gönderir; -K seçeneği bunu kapatır. Bunu da denedim. Boş bölüme 10 GiB yazdım, sonra iki kez biçimlendirdim:
| Adım | İşlem | SSD dolu alanı |
|---|---|---|
| 0 | Başlangıç (boş dosya sistemi) | 0.50 GB |
| 1 | 10 GiB rastgele veriyle dosya yazıldı | 11.24 GB |
| 2 | mkfs.xfs -f -K (TRIM göndermeden biçimlendir) |
11.24 GB |
| 3 | mkfs.xfs -f (varsayılan) |
0.50 GB |
Son adımda mkfs.xfs ekrana Discarding blocks...Done. yazdı. Yani komut satırından varsayılan seçeneklerle çalıştırılan mkfs.xfs TRIM gönderiyor ve TRIM bu kutuda SSD'ye ulaşıyor. Benim ilk biçimlendirmemde kullandığım yöntem ise TRIM göndermemişti. Hangi araçla biçimlendirdiğinizi bilmiyorsanız, biçimlendirdikten sonra bir kez fstrim çalıştırın. Bu deneyi yapan script yazının sonunda (Ek 3).
Bulgu 2: Adım adım deneme
Boş dosya sisteminde şu denemeyi yaptım. Her ölçüm, sayı oturduktan sonra alındı:
| Adım | İşlem | SSD dolu alanı |
|---|---|---|
| 0 | Başlangıç (boş dosya sistemi) | 0.50 GB |
| 1 | fstrim |
0.50 GB |
| 2 | 10 GiB rastgele veriyle dosya yazıldı | 11.24 GB |
| 3 | Dosya silindi, fstrim çalıştırılmadı |
11.24 GB |
| 4 | fstrim |
0.50 GB |
| 5 | TRIM kapatıldı (provisioning_mode=full), fstrim |
the discard operation is not supported |
Okunuşu:
- Adım 2: Sayı tam 10 GiB (10.74 GB) arttı.
- Adım 3: Dosya silindiği halde sayı değişmedi. SSD, dosyanın silindiğini bilmiyor; o alanı hala dolu sayıyor. TRIM'in olmadığı durum budur.
- Adım 4:
fstrimSSD'ye bildirdi ve sayı yaklaşık 10 saniyede eski haline döndü. - Adım 5: TRIM kapalıyken
fstrimçalışmıyor. udev kuralı kurulmadan önceki durum buydu.
Deneme iki kez çalıştırıldı, iki seferde de aynı sonuç çıktı. Denemeyi yapan script yazının sonunda (Ek 2).
Aynı yöntemle ilk kutuyu da kontrol ettim: oradaki SSD'nin dolu alanı TRIM'den sonra 885 GB'tan 330 GB'a inmişti. İki kutuda da TRIM SSD'ye ulaşıyor.
TRIM'siz biçimlendirme: ne olur, ne yapmalı
Ne olur?
Biçimlendirme, diskte yeni ve boş bir dosya sistemi kurar. Eski veriyi silmez. Biçimlendirme TRIM göndermezse SSD, eski verinin bulunduğu alanları hala dolu sayar. Benim diskimde durum şuydu: dosya sistemi boştu, ama 1 TB'lık SSD kendini 378 GB dolu sayıyordu.
Bunun sonuçları:
- SSD'nin kendi iç işi zorlaşır. SSD, yeni yazılar için arka planda boş alan hazırlar (buna çöp toplama denir). Dolu sandığı eski veriyi bu sırada korumak, gerekirse başka yere taşımak zorundadır. Kullanılabilir boş alanı azalır. Genel olarak bilinen sonucu: uzun süren yazmalarda hız düşebilir ve SSD kendi içinde daha çok yazma yapar; bu da ömrünü kısaltır. Bu etkiyi ben ölçmedim; kendi diskimde yalnızca dolu sayılan alanı ölçtüm.
- Kendiliğinden düzelmez, ama zamanla azalır. Dosya sistemi o alanlara yeni veri yazdıkça SSD eski veriyi bırakır. Hiç yazılmayan alanlar ise TRIM gelene kadar dolu sayılır.
- Biçimlendirme veriyi silmek değildir. Eski veri SSD'de durmaya devam eder. Diski başkasına verecekseniz biçimlendirmek yetmez. TRIM de güvenli silme yöntemi değildir:
lsblk -DçıktısındakiDISC-ZEROsütunu 0 ise, TRIM edilen alanın sıfır olarak okunacağının garantisi yoktur.
Ne yapmalı?
Biçimlendirmeden sonra bir kez fstrim çalıştırın. En kolay ve güvenli yol budur. Yalnızca dosya sisteminin boş dediği alanlara TRIM gönderir, verilere dokunmaz:
lsblk -D /dev/sda # DISC-MAX 0B değilse TRIM açık
sudo mount /dev/sda1 /mnt/disk
sudo fstrim -v /mnt/disk
DISC-MAX 0B ise önce yazının başındaki "Sorun 1" bölümüne bakın; TRIM kapalıyken fstrim hata verir.
Sonucu SSD'nin kendisinden doğrulayın. Yaklaşık bir dakika bekleyip dolu alana bakın. Dosya sisteminin dolu alanına yakın bir değer görmelisiniz:
sudo smartctl -i -d sntjmicron /dev/sda | grep Utilization
Ya da biçimlendirmeden önce bölümün tamamına TRIM gönderin. blkdiscard bunu yapar. Biçimlendirme aracınız TRIM göndermese bile SSD temiz başlar. Bunu da denedim:
| Adım | İşlem | SSD dolu alanı |
|---|---|---|
| 0 | Başlangıç | 0.50 GB |
| 1 | 10 GiB rastgele veriyle dosya yazıldı | 11.24 GB |
| 2 | blkdiscard -v /dev/sda1 |
0.002 GB |
| 3 | mkfs.xfs -f -K (TRIM göndermeden biçimlendir) |
0.50 GB |
sudo blkdiscard -v /dev/sda1
Dikkat: blkdiscard verilen bölümdeki bütün veriyi siler. Bende "dosya sistemi var" uyarısını yazdı, ama onay sormadan devam etti. Aygıt adını iki kez kontrol edin (lsblk -o NAME,SIZE,MODEL,MOUNTPOINTS). Belgesine göre bağlı bir bölümde çalışmaz; bu bir korumadır. util-linux 2.41 ve sonrasında fdisk (T komutu) ve sfdisk --discard-free de bölümlenmemiş alana TRIM gönderebilir; bu ikisini denemedim.
Düzenli TRIM: zamanlayıcı USB diskleri görmeyebilir
Benim sistemimde (Fedora 44) fstrim.timer açık ve haftada bir TRIM çalıştırıyor. Çalıştırdığı komut şu:
fstrim --listed-in /etc/fstab:/proc/self/mountinfo --verbose --quiet-unsupported
Bunun iki sonucu var:
--listed-inlistesi soldan okunur ve ilk boş olmayan dosyada durur./etc/fstabboş olmadığı için yalnızca fstab'daki dosya sistemleri işlenir. Elle ya da masaüstünün bağladığı USB diskler hiç işlenmez.--quiet-unsupported, TRIM'in çalışmadığı dosya sistemlerini sessizce atlar. USB diskiniz fstab'da olsa bile, TRIM'i kapalıysa (provisioning_mode=full) zamanlayıcı hata vermeden geçer.
Zamanlayıcının gerçekte neyi işlediğini kaydından görebilirsiniz:
systemctl list-timers fstrim.timer
journalctl -u fstrim.service -n 20
Bendeki son çalışmada yalnızca iç diskteki dosya sistemleri vardı; USB diskler yoktu. USB diskler için fstrim'i siz çalıştırın: büyük silmelerden sonra ve kutuyu çıkarmadan önce.
Hepsini tek script'te toplamak
İlk kutu için bu adımların hepsini yapan bir bakım script'i yazdım. İkinci kutuda yalnızca udev kuralı yeterli oldu. Script dört komut sunuyor:
| Komut | Ne yapar |
|---|---|
durum |
Hiçbir şeyi değiştirmeden durumu gösterir. |
kur |
İki udev kuralını yazar ve ayarları hemen uygular. |
bagla |
Kutuyu bulur, TRIM'i açar, ölü eşlemeyi ve eski LVM kaydını temizler, birimi açar ve bağlar. --sifirla ile kutu görünmezse USB denetleyicisini yeniden başlatır. --fstrim ile TRIM de çalıştırır. |
cikar |
Birimi ve kutudaki öteki bağlı bölümleri ayırır, LVM birimini kapatır. |
Script her adımda önce durumu kontrol eder. Zaten yapılmışsa atlar ve bunu yazar. Kural dosyalarını değiştirmeden önce yedek alır. USB denetleyicisini yalnızca üzerinde başka aygıt yoksa yeniden başlatır. Birimi bir program kullanıyorsa cikar durur ve o programı gösterir. Tüm çıktı /var/log/usb_nvme_kutu.log dosyasına da yazılır.
Kendi sisteminize uyarlamak
Script benim sistemim için yazıldı. Kullanmadan önce dosyanın başındaki şu sabitleri değiştirin:
| Sabit | Benim değerim | Nereden bulunur |
|---|---|---|
KUTU_VID, KUTU_PID |
0bda, 9210 |
lsusb çıktısında kutunun satırı: ID 0bda:9210 |
XHCI_VENDOR, XHCI_DEVICE |
0x8086, 0x7ec0 |
lspci -nn çıktısında USB denetleyicisinin satırı: [8086:7ec0] |
LVM adları ve bağlama dizini ortam değişkeniyle verilebilir:
sudo KUTU_VG=vg_adi KUTU_LV=lv_adi KUTU_BAGLAMA=/mnt/disk ./usb_nvme_kutu.sh durum
| Değişken | Benim değerim | Anlamı |
|---|---|---|
KUTU_VG |
vg_TB406Pro |
LVM birim grubu |
KUTU_LV |
usb_data01 |
LVM birimi |
KUTU_BAGLAMA |
/mnt/altdata |
Bağlama dizini |
KUTU_BAGLAMA_SECENEK |
defaults |
mount seçenekleri |
KUTU_GUC |
1 |
0 verilirse güç kuralı kurulmaz |
KUTU_BEKLEME |
20 |
Kutunun görünmesi için beklenecek saniye |
Script kendi kural dosyalarını yazar (50-rtl9210-trim.rules ve 70-xhci-8086-7ec0-guc.rules). Yazıdaki kuralları elle kurduysanız, script'i kullanmadan önce onları silin; aynı işi yapan iki kural olmasın.
Önerilen kullanım sırası:
sudo ./usb_nvme_kutu.sh durum # önce yalnızca bakın, hiçbir şey değişmez
sudo ./usb_nvme_kutu.sh kur # ilk kurulum (bir kez)
sudo ./usb_nvme_kutu.sh bagla # kutuyu taktıktan sonra
sudo ./usb_nvme_kutu.sh bagla --sifirla # kutu takılı ama görünmüyorsa
sudo ./usb_nvme_kutu.sh cikar # kutuyu çıkarmadan önce
sudo ./usb_nvme_kutu.sh bagla --fstrim # ara sıra TRIM için
Çıkış kodları: 0 başarılı, 1 kullanım hatası, 2 ön koşul yok (root değil, araç eksik), 3 kutu bulunamadı, 4 bir adım başarısız ya da durum sorun buldu, 5 güvenlik için durduruldu.
Script'i kendi sorumluluğunuzda kullanın. Benim sistemimde gerçek kutuyla denendi. Denenmeyen tek kısım, USB denetleyicisini yeniden başlatmak yetmezse kullanılan PCI yeniden tarama (remove/rescan) yolu.
Sonuç
- Kutu "TRIM yok" diyorsa hemen inanmayın.
sg_vpdvesg_readcapile kutunun ne dediğine bakın. Çelişki varsa TRIM büyük olasılıkla çalışır; elle deneyin. - udev kuralı yazdıktan sonra kuralın gerçekten etkili olduğunu kutuyu çıkarıp takarak doğrulayın. Bende iki kural da ilk halinde çalışıyor görünüp işe yaramadı: birini çekirdek, ötekini sistemin kendi kuralı geri alıyordu.
- Kutu takılınca görünmüyorsa USB denetleyicisinin uyku durumuna bakın.
fstrim'in hata vermemesi TRIM'in çalıştığını kanıtlamaz. SSD'nin kendi saydığı dolu alana (smartctl -i, "Namespace 1 Utilization") bakın; sayının oturması için bekleyin.- Diski biçimlendirdikten sonra bir kez
fstrimçalıştırın. Biçimlendirme TRIM göndermemiş olabilir; o zaman SSD eski veriyi dolu saymaya devam eder. - Haftalık TRIM zamanlayıcısı (
fstrim.timer) yalnızca fstab'daki dosya sistemlerini işler ve TRIM'i kapalı olanları sessizce atlar. USB diskler içinfstrim'i kendiniz çalıştırın. - USB diski çıkarmadan önce bağlantıyı ayırın ve LVM birimini kapatın.
Ek 1: Bakım script'inin tamamı
Script'i usb_nvme_kutu.sh adıyla kaydedip çalıştırılabilir yapın: chmod +x usb_nvme_kutu.sh
Script'i göstermek için tıklayın (usb_nvme_kutu.sh)
#!/usr/bin/env bash
# ==============================================================================
# usb_nvme_kutu.sh - ACASIS TB406Pro USB-NVMe kutusunu (Realtek RTL9210 yongasi)
# Linux'ta sorunsuz kullanmak icin bakim script'i (Fedora 44 uzerinde denendi).
#
# DIKKAT: Script tek bir sistem icin yazildi. Kullanmadan once su sabitleri
# kendi sisteminize gore degistirin: KUTU_VID ve KUTU_PID (lsusb ciktisindan),
# XHCI_VENDOR ve XHCI_DEVICE (lspci -nn ciktisindan, kutunun takildigi USB
# denetleyicisi). LVM adlari ve baglama dizini icin asagidaki ayarlara bakin.
# Once yalnizca okuyan 'durum' komutuyla deneyin.
# Yazilma tarihi: 07.11.2026. Disk sifresizdir; script sifreleme yapmaz.
#
# Cozdugu uc sorun:
# 1. Kutu takilinca gorunmuyor. Kutunun takildigi USB denetleyicisi (Intel Core
# Ultra 200 icindeki USB denetleyicisi, PCI kimligi 8086:7ec0) bos kalinca
# uyku kipine geciyor ve kutu takilinca kendiliginden uyanmiyor.
# Cozum: denetleyicinin uyku kipi kapatilir (udev kurali + hemen uygulama).
# Gerekirse denetleyici yeniden baslatilir (--sifirla).
# 2. TRIM kapali. TRIM: SSD'ye hangi alanlarin bos oldugunu bildiren komut;
# SSD'nin hizini ve omrunu korur. Kutu TRIM'i destekliyor ama kendini yanlis
# tanitiyor; cekirdek de TRIM'i kapatiyor (provisioning_mode=full).
# Cozum: udev kurali disk her eklendiginde degeri "unmap" yapar.
# 3. Kutu, LVM birimi kapatilmadan cikarilinca LVM'de eski kayit kaliyor. Kutu
# yeniden takilinca birim acilmiyor ya da olu diski gosteriyor.
# Cozum: olu esleme kaldirilir, LVM kayitlari yenilenir, birim acilir.
# LVM esleme: /dev/mapper altindaki, LVM biriminin diske baglantisi.
#
# Kullanim:
# sudo ./usb_nvme_kutu.sh [komut] [secenekler]
#
# Komutlar:
# durum Hicbir seyi degistirmeden durumu gosterir (komut verilmezse bu).
# kur udev kurallarini yazar ve ayarlari hemen uygular. Bir kez yeterli.
# bagla Kutuyu gorunur yapar, TRIM'i acar, LVM birimini acar ve baglar.
# cikar Birimi ayirir ve LVM birimini kapatir. Ardindan kutu guvenle
# cikarilabilir. Kutuyu cikarmadan once her zaman bunu calistirin.
#
# Gunluk kullanim:
# Ilk kurulum (bir kez): sudo ./usb_nvme_kutu.sh kur
# Kutuyu taktiktan sonra: sudo ./usb_nvme_kutu.sh bagla
# Kutu takili ama gorunmuyorsa: sudo ./usb_nvme_kutu.sh bagla --sifirla
# Kutuyu cikarmadan once: sudo ./usb_nvme_kutu.sh cikar
# Ara sira TRIM icin: sudo ./usb_nvme_kutu.sh bagla --fstrim
#
# Secenekler:
# --sifirla (bagla) Kutu gorunmezse USB denetleyicisini yeniden baslatir.
# Yalnizca o denetleyicide kutudan baska aygit yoksa yapilir.
# --fstrim (bagla) Baglamadan sonra TRIM calistirir (fstrim).
# --force (kur) Kural dosyalari ayni olsa bile yeniden yazar ve udev'e
# yeniden okutur.
# -h, --help Bu yardimi gosterir.
#
# Ayarlar (ortam degiskeni ile degistirilebilir):
# KUTU_VG LVM birim grubu (varsayilan: vg_TB406Pro)
# KUTU_LV LVM birimi (varsayilan: usb_data01)
# KUTU_BAGLAMA Baglama dizini (varsayilan: /mnt/altdata)
# KUTU_BAGLAMA_SECENEK mount secenekleri (varsayilan: defaults)
# KUTU_GUC 1: guc kurali kurulur, 0: kurulmaz (varsayilan: 1)
# Not: Denetleyici uyumadigi icin pil biraz daha hizli
# biter (dizustu bilgisayarda).
# KUTU_BEKLEME Kutunun gorunmesi icin beklenecek saniye (varsayilan: 20)
#
# Mount seceneklerine "discard" bilerek eklenmez. "discard" her silmede hemen
# TRIM gonderir ve USB kutularda yavaslatir. Yerine "bagla --fstrim" kullanin.
#
# Cikis kodlari:
# 0 Basarili. Her adim yapildi ya da zaten yapilmis oldugu icin atlandi.
# (durum komutu icin: sorun bulunmadi)
# 1 Kullanim hatasi (bilinmeyen komut ya da secenek, gecersiz ayar).
# 2 On kosul yok (root degil, gerekli arac kurulu degil).
# 3 Kutu bulunamadi (USB'de gorunmuyor).
# 4 Bir adim basarisiz oldu ya da durum komutu sorun buldu.
# 5 Guvenlik icin durduruldu (aygit kullanimda, birden fazla kutu,
# beklenmeyen durum, script zaten calisiyor).
#
# Log dosyasi: /var/log/usb_nvme_kutu.log
# Kural yedekleri: /var/backups/usb_nvme_kutu/
# ==============================================================================
set -uo pipefail
# ------------------------------------------------------------------ sabitler ---
readonly SCRIPT_ADI="usb_nvme_kutu.sh"
readonly KUTU_VID="0bda" # USB uretici kimligi (Realtek)
readonly KUTU_PID="9210" # USB urun kimligi (RTL9210)
readonly XHCI_VENDOR="0x8086" # USB denetleyicisi PCI uretici kimligi (Intel)
readonly XHCI_DEVICE="0x7ec0" # USB denetleyicisi PCI urun kimligi
readonly VG_ADI="${KUTU_VG:-vg_TB406Pro}"
readonly LV_ADI="${KUTU_LV:-usb_data01}"
readonly BAGLAMA="${KUTU_BAGLAMA:-/mnt/altdata}"
readonly BAGLAMA_SECENEK="${KUTU_BAGLAMA_SECENEK:-defaults}"
readonly GUC_KUR="${KUTU_GUC:-1}"
readonly BEKLEME_SN="${KUTU_BEKLEME:-20}"
readonly TRIM_KURALI="/etc/udev/rules.d/50-rtl9210-trim.rules"
# Guc kurali 70 ile baslar: sistemin 60-autosuspend.rules kurali kok USB
# aygitlarini yeniden "auto" yapiyor; bizim kuralin ondan sonra calismasi gerekir.
readonly GUC_KURALI="/etc/udev/rules.d/70-xhci-8086-7ec0-guc.rules"
# Ilk surum (07.11.2026) bu adla yazilmisti ve ise yaramiyordu: 60-autosuspend.rules
# ondan sonra calisip degeri yeniden "auto" yapiyordu. "kur" bu dosyayi kaldirir.
readonly ESKI_GUC_KURALI="/etc/udev/rules.d/50-xhci-8086-7ec0-guc.rules"
readonly LOG_DOSYASI="/var/log/usb_nvme_kutu.log"
readonly YEDEK_DIZINI="/var/backups/usb_nvme_kutu"
readonly KILIT_DOSYASI="/run/usb_nvme_kutu.lock"
readonly LVM_ONLINE_DIZINI="/run/lvm/pvs_online"
readonly GEREKLI_ARACLAR="udevadm lvm pvs pvscan lvs vgchange lvchange dmsetup findmnt lsblk mount umount fstrim cmp install flock journalctl"
readonly CIKIS_TAMAM=0
readonly CIKIS_KULLANIM=1
readonly CIKIS_ONKOSUL=2
readonly CIKIS_KUTU_YOK=3
readonly CIKIS_ADIM_HATASI=4
readonly CIKIS_GUVENLIK=5
# Calisma sirasinda dolan degiskenler
KOMUT="durum"
SECENEK_SIFIRLA=0
SECENEK_FSTRIM=0
SECENEK_FORCE=0
SORUN_SAYISI=0
BASLANGIC_ZAMANI=""
KUTU_USB="" # Kutunun USB aygit dizini (/sys/bus/usb/devices/2-1 gibi)
KUTU_DISK="" # Kutudaki diskin adi (sdb gibi)
# --------------------------------------------------------- cikti ve kayit ---
zaman() { date '+%H:%M:%S'; }
adim() { printf '\n%s === %s ===\n' "$(zaman)" "$*"; }
bilgi() { printf '%s %s\n' "$(zaman)" "$*"; }
yapildi() { printf '%s YAPILDI: %s\n' "$(zaman)" "$*"; }
atlandi() { printf '%s ATLANDI: %s\n' "$(zaman)" "$*"; }
uyari() { printf '%s UYARI: %s\n' "$(zaman)" "$*"; }
sorun() { printf '%s SORUN: %s\n' "$(zaman)" "$*"; SORUN_SAYISI=$((SORUN_SAYISI + 1)); }
olumcul() { printf '%s HATA: %s\n' "$(zaman)" "$1" >&2; exit "$2"; }
# Komutu ekrana yazar ve calistirir; komutun cikis kodunu dondurur.
calistir() {
printf '%s CALISTIRILIYOR: %s\n' "$(zaman)" "$*"
"$@"
}
# Bir sysfs dosyasina deger yazar. $1: dosya, $2: deger
sysfs_yaz() {
local dosya=$1 deger=$2
printf '%s CALISTIRILIYOR: echo %s > %s\n' "$(zaman)" "$deger" "$dosya"
printf '%s\n' "$deger" >"$dosya"
}
yardim_goster() {
awk 'NR > 1 && /^#/ { sub(/^# ?/, ""); print; next } NR > 1 { exit }' "$0"
}
# ----------------------------------------------------------- on kontroller ---
secenekleri_oku() {
local arg
for arg in "$@"; do
case "$arg" in
durum|kur|bagla|cikar) KOMUT=$arg ;;
--sifirla) SECENEK_SIFIRLA=1 ;;
--fstrim) SECENEK_FSTRIM=1 ;;
--force) SECENEK_FORCE=1 ;;
-h|--help) yardim_goster; exit "$CIKIS_TAMAM" ;;
*) olumcul "Bilinmeyen komut ya da secenek: $arg (yardim icin --help)" "$CIKIS_KULLANIM" ;;
esac
done
if [[ $SECENEK_SIFIRLA == 1 || $SECENEK_FSTRIM == 1 ]]; then
if [[ $KOMUT != bagla ]]; then
olumcul "--sifirla ve --fstrim yalnizca 'bagla' komutuyla kullanilir." "$CIKIS_KULLANIM"
fi
fi
if [[ $SECENEK_FORCE == 1 && $KOMUT != kur ]]; then
olumcul "--force yalnizca 'kur' komutuyla kullanilir." "$CIKIS_KULLANIM"
fi
}
ayarlari_kontrol_et() {
if [[ ! $GUC_KUR =~ ^[01]$ ]]; then
olumcul "KUTU_GUC yalnizca 0 ya da 1 olabilir: $GUC_KUR" "$CIKIS_KULLANIM"
fi
if [[ ! $BEKLEME_SN =~ ^[0-9]+$ ]] || (( BEKLEME_SN < 1 || BEKLEME_SN > 300 )); then
olumcul "KUTU_BEKLEME 1 ile 300 arasinda bir sayi olmali: $BEKLEME_SN" "$CIKIS_KULLANIM"
fi
if [[ $BAGLAMA != /* ]]; then
olumcul "KUTU_BAGLAMA tam yol olmali (/ ile baslamali): $BAGLAMA" "$CIKIS_KULLANIM"
fi
if [[ ! $VG_ADI =~ ^[A-Za-z0-9_.+-]+$ || ! $LV_ADI =~ ^[A-Za-z0-9_.+-]+$ ]]; then
olumcul "KUTU_VG ya da KUTU_LV gecersiz karakter iceriyor." "$CIKIS_KULLANIM"
fi
if [[ $BAGLAMA_SECENEK == *discard* && $BAGLAMA_SECENEK != *nodiscard* ]]; then
uyari "KUTU_BAGLAMA_SECENEK icinde 'discard' var. USB kutuda yavaslatabilir; 'bagla --fstrim' onerilir."
fi
}
onkosullari_kontrol_et() {
local arac eksik=""
if [[ $(id -u) != 0 ]]; then
olumcul "Root olarak calistirin: sudo $0 $KOMUT" "$CIKIS_ONKOSUL"
fi
for arac in $GEREKLI_ARACLAR; do
if ! command -v "$arac" >/dev/null 2>&1; then
eksik="$eksik $arac"
fi
done
if [[ -n $eksik ]]; then
olumcul "Gerekli araclar kurulu degil:$eksik" "$CIKIS_ONKOSUL"
fi
}
# Ayni anda iki kopya calismasin (biri kurallari yazarken digeri okumasin).
kilit_al() {
exec 9>"$KILIT_DOSYASI" || olumcul "Kilit dosyasi acilamadi: $KILIT_DOSYASI" "$CIKIS_ONKOSUL"
if ! flock -n 9; then
olumcul "Script zaten calisiyor (kilit: $KILIT_DOSYASI)." "$CIKIS_GUVENLIK"
fi
}
# Tum ciktiyi hem ekrana hem log dosyasina yazar.
kayit_baslat() {
if ! touch "$LOG_DOSYASI" 2>/dev/null; then
uyari "Log dosyasina yazilamiyor: $LOG_DOSYASI. Yalnizca ekrana yazilacak."
return 0
fi
chmod 0640 "$LOG_DOSYASI"
exec > >(tee -a "$LOG_DOSYASI") 2>&1
printf '\n######## %s %s %s ########\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$SCRIPT_ADI" "$*"
}
# ------------------------------------------------------- aygit bulma isleri ---
# Kutunun USB aygit dizinlerini yazar (her satira bir tane).
kutu_usb_dizinleri() {
local d
for d in /sys/bus/usb/devices/*; do
[[ -f $d/idVendor && -f $d/idProduct ]] || continue
if [[ $(<"$d/idVendor") == "$KUTU_VID" && $(<"$d/idProduct") == "$KUTU_PID" ]]; then
printf '%s\n' "$d"
fi
done
}
# Verilen USB aygit dizini altindaki disk adini yazar (sdb gibi).
usb_altindaki_disk() {
local b
for b in "$1"/*/host*/target*/*/block/*; do
if [[ -e $b ]]; then
basename "$b"
fi
done
}
# KUTU_USB ve KUTU_DISK degiskenlerini doldurur.
# Donus: 0 bulundu, 3 bulunamadi, 5 birden fazla kutu var.
kutu_bul() {
local dizinler sayi
KUTU_USB=""
KUTU_DISK=""
dizinler=$(kutu_usb_dizinleri)
sayi=$(printf '%s' "$dizinler" | grep -c . || true)
if (( sayi == 0 )); then
return "$CIKIS_KUTU_YOK"
fi
if (( sayi > 1 )); then
sorun "Birden fazla $KUTU_VID:$KUTU_PID kutusu takili. Script tek kutu icin yazildi: $(printf '%s ' "$dizinler")"
return "$CIKIS_GUVENLIK"
fi
KUTU_USB=$dizinler
KUTU_DISK=$(usb_altindaki_disk "$KUTU_USB" | head -n1)
return 0
}
# USB denetleyicisinin PCI adresini yazar (0000:00:0d.0 gibi).
# Kutu takiliysa onun bagli oldugu denetleyici, degilse PCI kimligiyle bulunan.
xhci_adresi() {
local yol d
if [[ -n $KUTU_USB ]]; then
yol=$(readlink -f "$KUTU_USB")
while [[ $yol == /sys/devices/* ]]; do
if [[ -f $yol/class && $(<"$yol/class") == 0x0c03* ]]; then
basename "$yol"
return 0
fi
yol=$(dirname "$yol")
done
fi
for d in /sys/bus/pci/devices/*; do
if [[ $(<"$d/vendor") == "$XHCI_VENDOR" && $(<"$d/device") == "$XHCI_DEVICE" ]]; then
basename "$d"
return 0
fi
done
return 1
}
# Denetleyicinin USB kok aygit dizinlerini yazar (usb1, usb2 gibi; usbmon haric).
xhci_kok_hublari() {
local h
for h in /sys/bus/pci/devices/"$1"/usb[0-9]*; do
if [[ -d $h ]]; then
printf '%s\n' "$h"
fi
done
}
provisioning_dosyasi() {
local f
for f in /sys/block/"$1"/device/scsi_disk/*/provisioning_mode; do
if [[ -e $f ]]; then
printf '%s\n' "$f"
return 0
fi
done
return 1
}
dm_adi() { printf '%s-%s' "${VG_ADI//-/--}" "${LV_ADI//-/--}"; }
lv_yolu() { printf '/dev/mapper/%s' "$(dm_adi)"; }
# VG'nin kutudaki PV yolunu yazar (/dev/sdb4 gibi).
pv_yolu() {
pvs --noheadings -o pv_name,vg_name 2>/dev/null \
| awk -v vg="$VG_ADI" '$2 == vg { print $1 }' | head -n1
}
maj_min() { lsblk -dno MAJ:MIN "$1" 2>/dev/null | tr -d ' '; }
# LVM esleme durumunu yazar: yok | saglam | olu
# olu: esleme var ama gosterdigi disk artik yok ya da su anki PV degil.
esleme_durumu() {
local ad bagimlilik pv beklenen
ad=$(dm_adi)
if ! dmsetup info "$ad" >/dev/null 2>&1; then
printf 'yok\n'
return 0
fi
bagimlilik=$(dmsetup deps -o devno "$ad" 2>/dev/null \
| grep -o '([0-9]*, [0-9]*)' | tr -d '() ' | tr ',' ':' | head -n1)
pv=$(pv_yolu)
beklenen=""
if [[ -n $pv ]]; then
beklenen=$(maj_min "$pv")
fi
if [[ -n $bagimlilik && -e /sys/dev/block/$bagimlilik && $bagimlilik == "$beklenen" ]]; then
printf 'saglam\n'
else
printf 'olu\n'
fi
}
esleme_acik_sayisi() {
dmsetup info -c --noheadings -o open "$(dm_adi)" 2>/dev/null | tr -d ' '
}
esleme_discard_max() {
local mm
mm=$(dmsetup info -c --noheadings --separator : -o major,minor "$(dm_adi)" 2>/dev/null | tr -d ' ')
cat "/sys/dev/block/$mm/queue/discard_max_bytes" 2>/dev/null
}
# ------------------------------------------------------------ udev kurallari ---
# Bu iki fonksiyon kural_yaz ve komut_durum icinden adlariyla dolayli cagrilir.
# shellcheck disable=SC2329
trim_kurali_icerigi() {
cat <<EOF
# Realtek RTL9210 USB-NVMe kutusu (ACASIS TB406Pro, USB kimligi ${KUTU_VID}:${KUTU_PID})
# icin TRIM acma kurali. Bu dosyayi tools/${SCRIPT_ADI} yazar. Elle degistirilirse
# script farki gorur, eskisini yedekler ve dosyayi yeniden yazar.
#
# Sorun: Kutu UNMAP (TRIM'in SCSI karsiligi) komutunu destekledigini soyluyor
# (VPD: LBPU=1), ama READ CAPACITY(16) yanitinda lbpme=0 bildiriyor. Cekirdek bu
# yuzden TRIM'i kapatiyor (provisioning_mode=full).
# Cozum: Disk eklendiginde provisioning_mode degeri unmap yapilir.
# Neden "block" olayi: Cekirdek, "scsi_disk" olayindan sonra diski yeniden okuyup
# degeri tekrar full yapiyor. "block" olayi bu okumadan sonra gelir. Bolumler
# (sdX4) ve LVM bu olay bittikten sonra islenir; LVM birimi TRIM'i dogru alir.
ACTION=="add|change", SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", ATTRS{idVendor}=="${KUTU_VID}", ATTRS{idProduct}=="${KUTU_PID}", RUN+="/bin/sh -c 'for f in /sys%p/device/scsi_disk/*/provisioning_mode; do if [ -w \$\$f ]; then echo unmap > \$\$f; fi; done'"
EOF
}
# shellcheck disable=SC2329
guc_kurali_icerigi() {
cat <<EOF
# Intel Core Ultra 200 USB denetleyicisi (PCI kimligi ${XHCI_VENDOR#0x}:${XHCI_DEVICE#0x}) icin guc kurali.
# Bu dosyayi tools/${SCRIPT_ADI} yazar. Elle degistirilirse script farki gorur,
# eskisini yedekler ve dosyayi yeniden yazar.
#
# Sorun: Denetleyici bos kalinca uyku kipine geciyor ve USB kutu takilinca
# kendiliginden uyanmiyor. Kutu ancak denetleyici baska bir nedenle uyaninca
# gorunuyor.
# Cozum: Denetleyicinin ve kok USB aygitlarinin (usb1, usb2) uyku kipi kapatilir.
# Bedeli: Denetleyici uyumadigi icin biraz daha fazla elektrik harcanir.
# Dosya adi 70 ile baslar; sistemin 60-autosuspend.rules kurali kok USB
# aygitlarini "auto" yapiyor, bu kural ondan sonra calisip "on" yapar.
ACTION=="add|bind", SUBSYSTEM=="pci", ATTR{vendor}=="${XHCI_VENDOR}", ATTR{device}=="${XHCI_DEVICE}", ATTR{power/control}="on"
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", KERNEL=="usb[0-9]*", ATTRS{vendor}=="${XHCI_VENDOR}", ATTRS{device}=="${XHCI_DEVICE}", ATTR{power/control}="on"
EOF
}
# Kural dosyasini istenen icerikle karsilastirir; farkliysa yedekleyip yazar.
# $1: hedef dosya, $2: icerigi ureten fonksiyon.
# Donus: 0 yazildi, 1 sorun, 2 zaten ayni (atlandi).
kural_yaz() {
local hedef=$1 uretici=$2 gecici yedek yeni
gecici=$(mktemp) || { sorun "Gecici dosya olusturulamadi."; return 1; }
"$uretici" >"$gecici"
if [[ -f $hedef ]] && cmp -s "$gecici" "$hedef" && [[ $(stat -c '%U:%G %a' "$hedef") == "root:root 644" ]]; then
if [[ $SECENEK_FORCE == 0 ]]; then
rm -f "$gecici"
atlandi "$hedef zaten dogru (icerik, sahip ve izin ayni)."
return 2
fi
bilgi "--force verildi; $hedef ayni olsa da yeniden yaziliyor."
fi
if [[ -f $hedef ]]; then
if ! install -d -m 0700 "$YEDEK_DIZINI"; then
rm -f "$gecici"
sorun "Yedek dizini olusturulamadi: $YEDEK_DIZINI"
return 1
fi
yedek="$YEDEK_DIZINI/$(basename "$hedef").$(date +%Y%m%d-%H%M%S)"
if ! calistir cp -a "$hedef" "$yedek"; then
rm -f "$gecici"
sorun "Yedek alinamadi, $hedef degistirilmedi."
return 1
fi
yapildi "Eski dosya yedeklendi: $yedek"
fi
if ! udevadm verify --no-style "$gecici" >/dev/null 2>&1; then
udevadm verify --no-style "$gecici" 2>&1 | sed 's/^/ /'
rm -f "$gecici"
sorun "Yeni kural udevadm verify kontrolunden gecmedi, $hedef degistirilmedi."
return 1
fi
# Ayni dizinde gecici dosyaya yazilip mv ile tasinir; boylece udev yarim
# yazilmis dosya gormez. Gecici adin sonu .rules olmadigi icin udev onu okumaz.
yeni="$hedef.yeni.$$"
if ! calistir install -o root -g root -m 0644 "$gecici" "$yeni" || ! calistir mv -f "$yeni" "$hedef"; then
rm -f "$gecici" "$yeni"
sorun "$hedef yazilamadi."
return 1
fi
rm -f "$gecici"
yapildi "$hedef yazildi."
return 0
}
# Eski adli guc kuralini yedekleyip kaldirir. Donus: 0 kaldirildi, 1 sorun, 2 yok.
eski_kurali_kaldir() {
local yedek
if [[ ! -e $ESKI_GUC_KURALI ]]; then
atlandi "$ESKI_GUC_KURALI yok."
return 2
fi
if ! install -d -m 0700 "$YEDEK_DIZINI"; then
sorun "Yedek dizini olusturulamadi: $YEDEK_DIZINI"
return 1
fi
yedek="$YEDEK_DIZINI/$(basename "$ESKI_GUC_KURALI").$(date +%Y%m%d-%H%M%S)"
if calistir mv "$ESKI_GUC_KURALI" "$yedek"; then
yapildi "Eski kural kaldirildi, yedegi: $yedek"
return 0
fi
sorun "$ESKI_GUC_KURALI kaldirilamadi."
return 1
}
# ------------------------------------------------------ ayarlari uygulama ---
# Denetleyicinin ve kok USB aygitlarinin uyku kipini hemen kapatir.
guc_uygula() {
local adres hedef dosya
local -a hedefler
if [[ $GUC_KUR == 0 ]]; then
atlandi "KUTU_GUC=0; denetleyicinin guc ayarina dokunulmadi."
return 0
fi
if ! adres=$(xhci_adresi); then
sorun "USB denetleyicisi ($XHCI_VENDOR:$XHCI_DEVICE) bulunamadi."
return 1
fi
mapfile -t hedefler < <(printf '%s\n' "/sys/bus/pci/devices/$adres"; xhci_kok_hublari "$adres")
for hedef in "${hedefler[@]}"; do
dosya="$hedef/power/control"
if [[ ! -w $dosya ]]; then
sorun "$dosya yazilabilir degil."
continue
fi
if [[ $(<"$dosya") == on ]]; then
atlandi "$(basename "$hedef") uyku kipi zaten kapali (power/control=on)."
elif sysfs_yaz "$dosya" on; then
yapildi "$(basename "$hedef") uyku kipi kapatildi."
else
sorun "$dosya yazilamadi."
fi
done
}
# Kutudaki diskte TRIM'i acar (provisioning_mode=unmap).
trim_ac() {
local dosya
if [[ -z $KUTU_DISK ]]; then
atlandi "Kutu ya da disk gorunmuyor; TRIM ayari yapilmadi."
return 0
fi
if ! dosya=$(provisioning_dosyasi "$KUTU_DISK"); then
sorun "/dev/$KUTU_DISK icin provisioning_mode dosyasi bulunamadi."
return 1
fi
if [[ $(<"$dosya") == unmap ]]; then
atlandi "/dev/$KUTU_DISK TRIM zaten acik (provisioning_mode=unmap)."
return 0
fi
if ! sysfs_yaz "$dosya" unmap; then
sorun "$dosya yazilamadi."
return 1
fi
if [[ $(<"$dosya") != unmap || $(<"/sys/block/$KUTU_DISK/queue/discard_max_bytes") == 0 ]]; then
sorun "/dev/$KUTU_DISK TRIM acilamadi (deger: $(<"$dosya"))."
return 1
fi
yapildi "/dev/$KUTU_DISK TRIM acildi (en buyuk TRIM: $(<"/sys/block/$KUTU_DISK/queue/discard_max_bytes") bayt)."
}
# ---------------------------------------------- kutuyu gorunur yapma isleri ---
# Kutu ve diski gorunene kadar bekler. $1: saniye. Donus: 0 goruldu, 3 gorulmedi.
kutu_bekle() {
local sure=$1 gecen=0
while (( gecen < sure )); do
kutu_bul
if (( $? == CIKIS_GUVENLIK )); then
return "$CIKIS_GUVENLIK" # birden fazla kutu; kutu_bul sorunu yazdi
fi
if [[ -n $KUTU_DISK ]]; then
# Diskin udev olaylari (TRIM kurali dahil) bitene kadar bekle. Yalnizca
# "settle" yetmez: cekirdek olayi kuyruga birkac milisaniye gec koyabilir.
if ! udevadm wait --settle --timeout=30 "/sys/block/$KUTU_DISK"; then
uyari "/dev/$KUTU_DISK icin udev islemi 30 saniyede bitmedi."
fi
kutu_bul
return 0
fi
sleep 1
gecen=$((gecen + 1))
done
kutu_bul
if [[ -n $KUTU_DISK ]]; then
return 0
fi
return "$CIKIS_KUTU_YOK"
}
# Denetleyicide kutudan baska USB aygiti yoksa 0 dondurur. Varsa onlari yazar
# ve 1 dondurur. USB coklayicinin (hub) arkasindaki aygitlar da sayilir.
denetleyicide_yalniz_kutu_mu() {
local adres=$1 d ad bulundu=0
for d in /sys/bus/usb/devices/*; do
ad=$(basename "$d")
[[ -f $d/idVendor && $ad != usb* ]] || continue
[[ $(readlink -f "$d") == */"$adres"/* ]] || continue
if [[ $(<"$d/idVendor") != "$KUTU_VID" || $(<"$d/idProduct") != "$KUTU_PID" ]]; then
bilgi "Denetleyicide baska aygit: $ad $(<"$d/idVendor"):$(<"$d/idProduct") $(cat "$d/product" 2>/dev/null)"
bulundu=1
fi
done
if (( bulundu == 1 )); then
return 1
fi
return 0
}
# Kutudaki disk ve bolumleri bossa (bagli degil, LVM vb. kullanmiyor) 0 dondurur.
# Kullaniliyorsa nerede kullanildigini yazar ve 1 dondurur.
kutu_diski_bos_mu() {
local p h
[[ -n $KUTU_DISK ]] || return 0
for p in /sys/block/"$KUTU_DISK" /sys/block/"$KUTU_DISK"/"$KUTU_DISK"*; do
[[ -d $p ]] || continue
if findmnt -rn -S "/dev/$(basename "$p")" >/dev/null; then
bilgi "/dev/$(basename "$p") bagli."
return 1
fi
for h in "$p"/holders/*; do
if [[ -e $h ]]; then
bilgi "/dev/$(basename "$p") su aygit tarafindan kullaniliyor: $(basename "$h")"
return 1
fi
done
done
return 0
}
# USB denetleyicisini yeniden baslatir. Once surucuyu ayirip baglar (unbind/bind);
# yetmezse PCI aygitini kaldirip yeniden tarar (remove/rescan).
denetleyici_sifirla() {
local adres surucu
if ! adres=$(xhci_adresi); then
sorun "USB denetleyicisi bulunamadi; sifirlama yapilmadi."
return 1
fi
if ! denetleyicide_yalniz_kutu_mu "$adres"; then
sorun "Denetleyicide ($adres) kutudan baska aygit var. Onlar da kopacagi icin sifirlama yapilmadi."
return "$CIKIS_GUVENLIK"
fi
if ! kutu_diski_bos_mu; then
sorun "Kutudaki disk kullanimda. Once '$0 cikar' calistirin."
return "$CIKIS_GUVENLIK"
fi
surucu=$(basename "$(readlink -f "/sys/bus/pci/devices/$adres/driver" 2>/dev/null)" 2>/dev/null)
if [[ -z $surucu || $surucu == driver ]]; then
surucu="xhci_hcd"
bilgi "$adres su anda bir surucuye bagli degil; $surucu ile baglanacak."
else
sysfs_yaz "/sys/bus/pci/drivers/$surucu/unbind" "$adres" || sorun "$adres surucuden ayrilamadi."
sleep 2
fi
if sysfs_yaz "/sys/bus/pci/drivers/$surucu/bind" "$adres"; then
yapildi "USB denetleyicisi ($adres) yeniden baslatildi (unbind/bind)."
else
sorun "$adres surucuye baglanamadi."
fi
# Surucu yuklenirken guc ayarini kendisi "auto" yapar; udev kurali hemen
# ardindan "on" yapar. Kontrolden once udev'in bitirmesi beklenir.
udevadm settle --timeout=30
guc_uygula
if kutu_bekle "$BEKLEME_SN"; then
return 0
fi
uyari "Kutu hala gorunmuyor. PCI aygiti kaldirilip yeniden taraniyor (remove/rescan)."
if [[ -e /sys/bus/pci/devices/$adres/remove ]]; then
sysfs_yaz "/sys/bus/pci/devices/$adres/remove" 1 || sorun "$adres kaldirilamadi."
sleep 2
fi
sysfs_yaz /sys/bus/pci/rescan 1 || sorun "PCI yeniden taramasi yapilamadi."
udevadm settle --timeout=30
guc_uygula
if kutu_bekle "$BEKLEME_SN"; then
yapildi "USB denetleyicisi ($adres) yeniden baslatildi (remove/rescan)."
return 0
fi
return "$CIKIS_KUTU_YOK"
}
# -------------------------------------------------------------- LVM isleri ---
olu_esleme_temizle() {
local ad durum acik
ad=$(dm_adi)
durum=$(esleme_durumu)
case "$durum" in
yok)
atlandi "Eski ya da olu LVM eslemesi yok."
return 0 ;;
saglam)
atlandi "LVM eslemesi $ad saglam (su anki diski gosteriyor)."
return 0 ;;
esac
acik=$(esleme_acik_sayisi)
if [[ $acik != 0 ]]; then
sorun "Olu esleme $ad hala kullanimda (acik sayisi: $acik). Once elle ayirin: umount -l $(findmnt -rn -o TARGET -S "$(lv_yolu)" | head -n1)"
return "$CIKIS_GUVENLIK"
fi
if calistir dmsetup remove "$ad"; then
yapildi "Olu LVM eslemesi kaldirildi: $ad"
return 0
fi
sorun "Olu LVM eslemesi kaldirilamadi: $ad"
return 1
}
# LVM'nin "PV takili" kaydi (/run/lvm/pvs_online) su anki diski gostermiyorsa yeniler.
lvm_kaydini_yenile() {
local pv uuid dosya kayitli beklenen
pv=$(pv_yolu)
if [[ -z $pv ]]; then
sorun "$VG_ADI icin PV bulunamadi (pvs). Disk dogru mu?"
return 1
fi
if [[ $(lsblk -dno PKNAME "$pv" 2>/dev/null) != "$KUTU_DISK" ]]; then
sorun "$VG_ADI grubunun PV'si ($pv) kutudaki diskte (/dev/$KUTU_DISK) degil."
return "$CIKIS_GUVENLIK"
fi
uuid=$(pvs --noheadings -o pv_uuid "$pv" 2>/dev/null | tr -d ' -')
dosya="$LVM_ONLINE_DIZINI/$uuid"
beklenen=$(maj_min "$pv")
kayitli=""
if [[ -f $dosya ]]; then
kayitli=$(head -n1 "$dosya")
fi
if [[ $kayitli == "$beklenen" ]]; then
atlandi "LVM kaydi dogru ($pv = $beklenen)."
return 0
fi
bilgi "LVM kaydi eski ya da eksik (kayitli: ${kayitli:-yok}, olmasi gereken: $beklenen)."
if calistir pvscan --cache; then
yapildi "LVM kayitlari yenilendi."
return 0
fi
sorun "pvscan --cache basarisiz oldu."
return 1
}
lv_ac() {
local durum disk_trim lv_trim
durum=$(esleme_durumu)
if [[ $durum == olu ]]; then
sorun "LVM eslemesi olu; birim acilmadi."
return 1
fi
if [[ $durum == yok ]]; then
if ! calistir vgchange -ay "$VG_ADI"; then
sorun "$VG_ADI acilamadi."
return 1
fi
udevadm settle --timeout=30
yapildi "$VG_ADI/$LV_ADI acildi."
else
atlandi "$VG_ADI/$LV_ADI zaten acik."
fi
disk_trim=$(<"/sys/block/$KUTU_DISK/queue/discard_max_bytes")
lv_trim=$(esleme_discard_max)
if [[ $disk_trim != 0 && $lv_trim == 0 ]]; then
bilgi "Diskte TRIM acik ama LVM biriminde kapali; birim tablosu yenileniyor."
if calistir lvchange --refresh "$VG_ADI/$LV_ADI"; then
lv_trim=$(esleme_discard_max)
else
sorun "lvchange --refresh basarisiz oldu."
fi
fi
if [[ $lv_trim == 0 ]]; then
sorun "LVM biriminde TRIM kapali (discard_max_bytes=0)."
return 1
fi
atlandi "LVM biriminde TRIM acik (en buyuk TRIM: $lv_trim bayt)."
return 0
}
baglamayi_yap() {
local lv mevcut_hedef dizindeki_kaynak
lv=$(lv_yolu)
mevcut_hedef=$(findmnt -rn -o TARGET -S "$lv" | head -n1)
if [[ -n $mevcut_hedef ]]; then
if [[ $mevcut_hedef != "$BAGLAMA" ]]; then
uyari "$lv zaten baska bir dizine bagli: $mevcut_hedef. Dokunulmadi."
fi
atlandi "$lv zaten bagli: $mevcut_hedef"
return 0
fi
if ! install -d -m 0755 "$BAGLAMA"; then
sorun "Baglama dizini olusturulamadi: $BAGLAMA"
return 1
fi
dizindeki_kaynak=$(findmnt -rn -o SOURCE --mountpoint "$BAGLAMA" | head -n1)
if [[ -n $dizindeki_kaynak ]]; then
sorun "$BAGLAMA dizinine baska bir aygit bagli: $dizindeki_kaynak"
return "$CIKIS_GUVENLIK"
fi
if [[ -n $(ls -A "$BAGLAMA") ]]; then
sorun "$BAGLAMA dizini bos degil. Icindeki dosyalar gizlenmesin diye baglanmadi."
return "$CIKIS_GUVENLIK"
fi
if calistir mount -o "$BAGLAMA_SECENEK" "$lv" "$BAGLAMA"; then
yapildi "$lv -> $BAGLAMA baglandi."
return 0
fi
sorun "$lv baglanamadi."
return 1
}
fstrim_calistir() {
local hedef
hedef=$(findmnt -rn -o TARGET -S "$(lv_yolu)" | head -n1)
if [[ -z $hedef ]]; then
sorun "Birim bagli degil; fstrim calistirilmadi."
return 1
fi
if [[ $(esleme_discard_max) == 0 ]]; then
sorun "Birimde TRIM kapali; fstrim calistirilmadi."
return 1
fi
if calistir fstrim -v "$hedef"; then
yapildi "fstrim tamamlandi."
return 0
fi
sorun "fstrim basarisiz oldu."
return 1
}
# Bu calisma sirasinda cekirdek gunlugune dusen disk hatalarini gosterir.
cekirdek_hatalarini_goster() {
local satirlar
satirlar=$(journalctl -k --no-pager -o short-precise --since "@$BASLANGIC_ZAMANI" 2>/dev/null \
| grep -E 'I/O error|DID_ERROR|uas_eh|reset.*USB|over-current|unable to read' || true)
if [[ -z $satirlar ]]; then
bilgi "Bu calisma sirasinda cekirdek gunlugunde disk ya da USB hatasi yok."
return 0
fi
uyari "Cekirdek gunlugunde hata satirlari var (kutu cikarilirken 'Synchronize Cache ... DID_ERROR' normaldir):"
printf '%s\n' "$satirlar" | sed 's/^/ /'
}
# ------------------------------------------------------------------- komutlar ---
komut_durum() {
local adres hedef dosya kural uretici gecici durum mod lv hedef_dizin pv uuid kayitli
local -a hedefler
adim "Kutu (USB $KUTU_VID:$KUTU_PID)"
kutu_bul
if [[ -z $KUTU_USB ]]; then
sorun "Kutu USB'de gorunmuyor."
else
bilgi "USB aygiti: $(basename "$KUTU_USB"), hiz: $(<"$KUTU_USB/speed") Mbit/s, urun: $(cat "$KUTU_USB/product" 2>/dev/null)"
if [[ -n $KUTU_DISK ]]; then
bilgi "Disk: /dev/$KUTU_DISK ($(lsblk -dno MODEL,SIZE "/dev/$KUTU_DISK"))"
else
sorun "Kutu USB'de var ama disk gorunmuyor."
fi
fi
adim "USB denetleyicisi (uyku kipi)"
if adres=$(xhci_adresi); then
mapfile -t hedefler < <(printf '%s\n' "/sys/bus/pci/devices/$adres"; xhci_kok_hublari "$adres")
for hedef in "${hedefler[@]}"; do
bilgi "$(basename "$hedef"): power/control=$(<"$hedef/power/control"), durum=$(<"$hedef/power/runtime_status")"
if [[ $GUC_KUR == 1 && $(<"$hedef/power/control") != on ]]; then
sorun "$(basename "$hedef") uyku kipi acik; kutu takilinca gorunmeyebilir. Cozum: $0 kur"
fi
done
else
sorun "USB denetleyicisi ($XHCI_VENDOR:$XHCI_DEVICE) bulunamadi."
fi
adim "udev kurallari"
for kural in "$TRIM_KURALI:trim_kurali_icerigi" "$GUC_KURALI:guc_kurali_icerigi"; do
dosya=${kural%%:*}
uretici=${kural#*:}
if [[ $dosya == "$GUC_KURALI" && $GUC_KUR == 0 ]]; then
atlandi "KUTU_GUC=0; guc kurali kontrol edilmedi."
continue
fi
gecici=$(mktemp)
"$uretici" >"$gecici"
if [[ ! -f $dosya ]]; then
sorun "$dosya yok. Cozum: $0 kur"
elif cmp -s "$gecici" "$dosya"; then
bilgi "$dosya guncel."
else
sorun "$dosya script'in yazacagi icerikten farkli. Cozum: $0 kur"
fi
rm -f "$gecici"
done
if [[ -e $ESKI_GUC_KURALI ]]; then
sorun "Eski adli kural duruyor: $ESKI_GUC_KURALI. Cozum: $0 kur"
fi
adim "TRIM"
if [[ -n $KUTU_DISK ]] && dosya=$(provisioning_dosyasi "$KUTU_DISK"); then
mod=$(<"$dosya")
bilgi "/dev/$KUTU_DISK: provisioning_mode=$mod, en buyuk TRIM=$(<"/sys/block/$KUTU_DISK/queue/discard_max_bytes") bayt"
if [[ $mod != unmap ]]; then
sorun "Diskte TRIM kapali. Cozum: $0 bagla"
fi
else
atlandi "Disk gorunmedigi icin TRIM kontrol edilmedi."
fi
adim "LVM ($VG_ADI/$LV_ADI)"
durum=$(esleme_durumu)
lv=$(lv_yolu)
case "$durum" in
yok) bilgi "Birim kapali (esleme yok)." ;;
saglam) bilgi "Birim acik ve su anki diski gosteriyor. Birimde en buyuk TRIM: $(esleme_discard_max) bayt"
if [[ $(esleme_discard_max) == 0 ]]; then sorun "LVM biriminde TRIM kapali. Cozum: $0 bagla"; fi ;;
olu) sorun "Olu LVM eslemesi var (artik olmayan bir diski gosteriyor). Cozum: $0 bagla" ;;
esac
if [[ -n $KUTU_DISK ]]; then
pv=$(pv_yolu)
if [[ -n $pv ]]; then
uuid=$(pvs --noheadings -o pv_uuid "$pv" 2>/dev/null | tr -d ' -')
kayitli=$(head -n1 "$LVM_ONLINE_DIZINI/$uuid" 2>/dev/null)
if [[ -n $kayitli && $kayitli != "$(maj_min "$pv")" ]]; then
sorun "LVM kaydi eski: $kayitli kayitli, PV $pv su an $(maj_min "$pv"). Cozum: $0 bagla"
else
bilgi "PV: $pv, LVM kaydi: ${kayitli:-yok}"
fi
else
sorun "$VG_ADI icin PV bulunamadi."
fi
fi
adim "Baglama"
hedef_dizin=$(findmnt -rn -o TARGET -S "$lv" 2>/dev/null | head -n1)
if [[ -n $hedef_dizin ]]; then
bilgi "$lv bagli: $hedef_dizin ($(findmnt -rn -o OPTIONS -S "$lv" | head -n1))"
else
bilgi "$lv bagli degil."
fi
}
komut_kur() {
local degisti=0 sonuc
adim "1. TRIM kurali: $TRIM_KURALI"
kural_yaz "$TRIM_KURALI" trim_kurali_icerigi
sonuc=$?
if (( sonuc == 0 )); then degisti=1; fi
adim "2. Guc kurali: $GUC_KURALI"
if [[ $GUC_KUR == 0 ]]; then
atlandi "KUTU_GUC=0; guc kurali yazilmadi."
else
kural_yaz "$GUC_KURALI" guc_kurali_icerigi
sonuc=$?
if (( sonuc == 0 )); then degisti=1; fi
fi
adim "2b. Eski adli guc kurali: $ESKI_GUC_KURALI"
eski_kurali_kaldir
sonuc=$?
if (( sonuc == 0 )); then degisti=1; fi
adim "3. udev kurallarini yeniden okutma"
if (( degisti == 1 )); then
if calistir udevadm control --reload; then
yapildi "udev kurallari yeniden okudu."
else
sorun "udevadm control --reload basarisiz oldu."
fi
else
atlandi "Kural dosyalari degismedi; yeniden okutmaya gerek yok."
fi
adim "4. Ayarlari hemen uygulama"
guc_uygula
kutu_bul
trim_ac
if [[ -n $KUTU_DISK && $(esleme_durumu) == saglam && $(esleme_discard_max) == 0 ]]; then
bilgi "LVM birimi acik ama TRIM sinirlarini almamis; tablo yenileniyor."
calistir lvchange --refresh "$VG_ADI/$LV_ADI" || sorun "lvchange --refresh basarisiz oldu."
fi
}
komut_bagla() {
local sonuc
adim "1. USB denetleyicisinin uyku kipi"
guc_uygula
adim "2. Kutuyu bulma"
kutu_bul
sonuc=$?
if (( sonuc == CIKIS_GUVENLIK )); then
return "$CIKIS_GUVENLIK"
fi
if [[ -n $KUTU_DISK ]]; then
atlandi "Kutu zaten gorunuyor: /dev/$KUTU_DISK"
else
bilgi "Kutu gorunmuyor; $BEKLEME_SN saniye bekleniyor."
if ! kutu_bekle "$BEKLEME_SN"; then
if [[ $SECENEK_SIFIRLA == 0 ]]; then
sorun "Kutu gorunmedi. Kabloyu kontrol edin ya da '$0 bagla --sifirla' calistirin."
return "$CIKIS_KUTU_YOK"
fi
bilgi "--sifirla verildi; USB denetleyicisi yeniden baslatiliyor."
denetleyici_sifirla
sonuc=$?
if (( sonuc != 0 )); then
sorun "Kutu denetleyici sifirlandiktan sonra da gorunmedi."
return "$sonuc"
fi
fi
yapildi "Kutu goruldu: /dev/$KUTU_DISK"
fi
adim "3. TRIM"
trim_ac
adim "4. Olu LVM eslemesi"
olu_esleme_temizle
sonuc=$?
if (( sonuc != 0 )); then
return "$sonuc"
fi
adim "5. LVM kaydi"
lvm_kaydini_yenile
sonuc=$?
if (( sonuc != 0 )); then
return "$sonuc"
fi
adim "6. LVM birimini acma"
lv_ac || true
adim "7. Baglama: $BAGLAMA"
baglamayi_yap
sonuc=$?
if (( sonuc != 0 )); then
return "$sonuc"
fi
adim "8. fstrim"
if [[ $SECENEK_FSTRIM == 1 ]]; then
fstrim_calistir
else
atlandi "--fstrim verilmedi."
fi
adim "9. Cekirdek gunlugu"
cekirdek_hatalarini_goster
}
# Bir baglama noktasini kullanan programlari yazar (fuser, psmisc paketi).
kullananlari_goster() {
if command -v fuser >/dev/null 2>&1; then
fuser -vm "$1" 2>&1 | sed 's/^/ /'
else
bilgi "fuser kurulu degil (psmisc paketi); kullanan programlar gosterilemiyor."
fi
}
# Kutudaki diskin bagli bolumlerini (masaustunun kendiliginden bagladigi Ventoy
# bolumu gibi) ayirir. Ayrilamayan varsa kullanan programlari yazar, 1 dondurur.
diger_bolumleri_ayir() {
local kaynak hedef bulundu=0
while read -r kaynak hedef; do
[[ -n $kaynak ]] || continue
bulundu=1
if calistir umount "$kaynak"; then
yapildi "$kaynak ayrildi ($hedef)."
else
sorun "$kaynak ayrilamadi ($hedef). Kullanan programlar:"
kullananlari_goster "$hedef"
return 1
fi
done < <(findmnt -rn -o SOURCE,TARGET | awk -v d="/dev/$KUTU_DISK" '$1 ~ ("^" d "[0-9]*$")')
if (( bulundu == 0 )); then
atlandi "/dev/$KUTU_DISK uzerinde bagli baska bolum yok."
fi
return 0
}
komut_cikar() {
local lv hedef durum
adim "1. Baglantiyi ayirma"
lv=$(lv_yolu)
hedef=$(findmnt -rn -o TARGET -S "$lv" | head -n1)
if [[ -z $hedef ]]; then
atlandi "$lv bagli degil."
else
if ! calistir umount "$lv"; then
sorun "$lv ayrilamadi. Kullanan programlar:"
kullananlari_goster "$hedef"
return "$CIKIS_GUVENLIK"
fi
yapildi "$hedef ayrildi."
fi
adim "2. Kutudaki diger bolumleri ayirma"
kutu_bul
if [[ -z $KUTU_DISK ]]; then
atlandi "Kutu gorunmuyor; ayrilacak bolum yok."
else
diger_bolumleri_ayir || return "$CIKIS_GUVENLIK"
fi
adim "3. LVM birimini kapatma"
durum=$(esleme_durumu)
if [[ $durum == yok ]]; then
atlandi "$VG_ADI zaten kapali."
elif [[ $durum == olu ]]; then
olu_esleme_temizle || return "$CIKIS_ADIM_HATASI"
elif calistir vgchange -an "$VG_ADI"; then
yapildi "$VG_ADI kapatildi."
else
sorun "$VG_ADI kapatilamadi."
return "$CIKIS_ADIM_HATASI"
fi
adim "4. Sonuc"
kutu_bul
if [[ -n $KUTU_DISK ]] && ! kutu_diski_bos_mu; then
sorun "/dev/$KUTU_DISK hala kullanimda; kutuyu cikarmayin."
return "$CIKIS_GUVENLIK"
fi
bilgi "Kutu guvenle cikarilabilir."
}
# ----------------------------------------------------------------------- main ---
main() {
local sonuc
secenekleri_oku "$@"
ayarlari_kontrol_et
onkosullari_kontrol_et
kilit_al
# Kilit dosyasi (fd 9) alt komutlara da gecer; LVM bunu uyari olarak yazar.
# Uyari zararsizdir, kapatilir.
export LVM_SUPPRESS_FD_WARNINGS=1
kayit_baslat "$@"
BASLANGIC_ZAMANI=$(date +%s)
case "$KOMUT" in
durum) komut_durum ;;
kur) komut_kur ;;
bagla) komut_bagla ;;
cikar) komut_cikar ;;
esac
sonuc=$?
adim "Bitti: $KOMUT"
if (( sonuc == 0 && SORUN_SAYISI > 0 )); then
sonuc=$CIKIS_ADIM_HATASI
fi
if (( sonuc == 0 )); then
bilgi "Sonuc: basarili (cikis kodu 0)."
else
bilgi "Sonuc: $SORUN_SAYISI sorun, cikis kodu $sonuc. Log: $LOG_DOSYASI"
fi
return "$sonuc"
}
main "$@"
exit $?
Ek 2: TRIM deneme script'i
"Bulgu 2"deki denemeyi yapan script. Boş, yeni biçimlendirilmiş bir XFS bölümünde çalışır; bölüm boş değilse hiçbir şey yazmadan durur. Bölüme 10 GiB yazar ve siler. Her adımı saatiyle bir kayıt dosyasına yazar. Sonunda bölümü ayırır ve TRIM'i yeniden açar.
sudo ./trim_deneme.sh /dev/sda /dev/sda1 /tmp/trim.log
sudo KUTU_USB=0bda:9210 SMART_TIP=auto ./trim_deneme.sh /dev/sdb /dev/sdb1 /tmp/trim.log
Script'i göstermek için tıklayın (trim_deneme.sh)
#!/usr/bin/env bash
# TRIM'in SSD'ye gercekten ulastigini SSD'nin kendi "dolu alan" bilgisiyle gosteren deneme.
# Blog yazisi icin kanit uretir. Yalnizca bos, yeni bicimlendirilmis bir bolumde calisir.
#
# Kullanim: sudo ./trim_deneme.sh DISK BOLUM KAYIT_DOSYASI
# ornek: sudo ./trim_deneme.sh /dev/sda /dev/sda1 /tmp/trim.log
#
# Ayarlar (ortam degiskeni):
# KUTU_USB Kutunun USB kimligi (lsusb ciktisindan). Script yanlis diskte
# calismasin diye kontrol edilir. Varsayilan: 152d:0583 (JMS583)
# SMART_TIP smartctl -d degeri. JMicron JMS583 icin sntjmicron, Realtek
# RTL9210 icin auto calisti. Varsayilan: sntjmicron
# ornek: sudo KUTU_USB=0bda:9210 SMART_TIP=auto ./trim_deneme.sh /dev/sdb /dev/sdb1 /tmp/trim.log
#
# DIKKAT: Bolum bos olmali (script bos degilse durur). Bolume 10 GiB yazilir ve silinir.
#
# Adimlar: 0 baslangic, 1 fstrim, 2 10 GiB dosya yaz, 3 dosyayi sil, 4 fstrim,
# 5 TRIM kapaliyken fstrim (karsilastirma), sonra TRIM yeniden acilir.
# Cikis kodlari: 0 basarili, 1 kullanim hatasi, 2 on kosul yok, 5 guvenlik icin durdu.
set -uo pipefail
readonly BEKLENEN_USB="${KUTU_USB:-152d:0583}"
readonly SMART_TIP="${SMART_TIP:-sntjmicron}"
readonly DOSYA_GIB=10
readonly DISK="${1:-}"
readonly BOLUM="${2:-}"
readonly KAYIT="${3:-}"
BAGLAMA=""
kayit() { printf '%s %s\n' "$(date '+%H:%M:%S')" "$*" | tee -a "$KAYIT"; }
olumcul() { kayit "HATA: $1"; exit "$2"; }
# SSD'nin bildirdigi dolu alan (bayt): NVMe 'Namespace 1 Utilization' degeri.
dolu_alan() {
smartctl -i -d "$SMART_TIP" "$DISK" 2>/dev/null \
| awk -F: '/Namespace 1 Utilization/ { gsub(/[ ,]/, "", $2); sub(/\[.*/, "", $2); print $2 }'
}
# SSD dolu alan sayisini arka planda ve gecikmeyle gunceller (denemede ~45 sn).
# Bu yuzden olcum, sayi oturana kadar bekler: 10 sn arayla art arda 3 okuma ayni
# olunca ya da en fazla 300 sn sonra yazar.
olc() {
local b onceki="" ayni=0 gecen=0
while (( gecen <= 300 )); do
b=$(dolu_alan)
if [[ -z $b ]]; then
kayit " SSD dolu alani okunamadi."
return
fi
if [[ $b == "$onceki" ]]; then
ayni=$((ayni + 1))
else
ayni=1
onceki=$b
fi
if (( ayni >= 3 )); then
break
fi
sleep 10
gecen=$((gecen + 10))
done
if (( ayni < 3 )); then
kayit " UYARI: sayi 300 sn icinde oturmadi."
fi
kayit " SSD dolu alani: $b bayt (~$(( b / 1000000000 )) GB), oturma suresi ~$(( gecen - 20 > 0 ? gecen - 20 : 0 )) sn"
}
provisioning_dosyasi() { ls /sys/block/"$(basename "$DISK")"/device/scsi_disk/*/provisioning_mode; }
usb_kimligi() {
local p
p=$(readlink -f "/sys/block/$(basename "$DISK")/device")
while [[ $p != / ]]; do
if [[ -f $p/idVendor ]]; then
printf '%s:%s\n' "$(<"$p/idVendor")" "$(<"$p/idProduct")"
return
fi
p=$(dirname "$p")
done
}
temizle() {
if [[ -n $BAGLAMA ]] && mountpoint -q "$BAGLAMA"; then
rm -f "$BAGLAMA/trim_deneme.bin"
umount "$BAGLAMA" && kayit "Temizlik: $BOLUM ayrildi."
fi
if [[ -n $BAGLAMA && -d $BAGLAMA ]]; then
rmdir "$BAGLAMA"
fi
local f
f=$(provisioning_dosyasi 2>/dev/null)
if [[ -n $f && $(<"$f") != unmap ]]; then
echo unmap >"$f" && kayit "Temizlik: TRIM yeniden acildi (unmap)."
fi
}
on_kontrol() {
[[ -n $DISK && -n $BOLUM && -n $KAYIT ]] || { echo "Kullanim: sudo $0 DISK BOLUM KAYIT_DOSYASI"; exit 1; }
[[ $(id -u) == 0 ]] || { echo "Root olarak calistirin."; exit 2; }
: >"$KAYIT" || exit 2
command -v smartctl >/dev/null || olumcul "smartctl kurulu degil." 2
[[ -b $DISK && -b $BOLUM ]] || olumcul "$DISK ya da $BOLUM yok." 1
[[ $(lsblk -no PKNAME "$BOLUM") == "$(basename "$DISK")" ]] || olumcul "$BOLUM, $DISK uzerinde degil." 1
[[ $(usb_kimligi) == "$BEKLENEN_USB" ]] || olumcul "$DISK beklenen kutu ($BEKLENEN_USB) degil: $(usb_kimligi)" 5
[[ $(lsblk -no FSTYPE "$BOLUM") == xfs ]] || olumcul "$BOLUM XFS degil." 5
if findmnt -rn -S "$BOLUM" >/dev/null; then olumcul "$BOLUM bagli; once ayirin." 5; fi
}
main() {
local f cikti kod
on_kontrol
trap temizle EXIT
kayit "Disk: $DISK ($(usb_kimligi), $(smartctl -i -d "$SMART_TIP" "$DISK" | awk -F: '/Model Number/ {gsub(/^ +/, "", $2); print $2}'))"
kayit "Cekirdek: $(uname -r)"
f=$(provisioning_dosyasi)
kayit "provisioning_mode: $(<"$f"), DISC-MAX: $(lsblk -dno DISC-MAX "$DISK" | tr -d ' ')"
BAGLAMA=$(mktemp -d /run/trim_deneme.XXXXXX)
mount "$BOLUM" "$BAGLAMA" || olumcul "$BOLUM baglanamadi." 5
if [[ -n $(find "$BAGLAMA" -mindepth 1 -maxdepth 1 -print -quit) ]]; then
olumcul "$BOLUM bos degil; deneme yalnizca bos dosya sisteminde yapilir." 5
fi
kayit "$BOLUM baglandi, bos: $(df -h --output=used,size "$BAGLAMA" | tail -1)"
kayit "ADIM 0: Baslangic (yeni bicimlendirilmis, bos dosya sistemi)"
olc
kayit "ADIM 1: fstrim"
kayit " $(fstrim -v "$BAGLAMA" 2>&1)"
olc
kayit "ADIM 2: $DOSYA_GIB GiB rastgele veriyle dosya yaziliyor"
dd if=/dev/urandom of="$BAGLAMA/trim_deneme.bin" bs=1M count=$((DOSYA_GIB * 1024)) \
oflag=direct status=none || olumcul "dd basarisiz oldu." 5
sync
kayit " yazildi: $(du -h "$BAGLAMA/trim_deneme.bin" | cut -f1)"
olc
kayit "ADIM 3: Dosya siliniyor (fstrim yok)"
rm -f "$BAGLAMA/trim_deneme.bin"
sync
olc
kayit "ADIM 4: fstrim"
kayit " $(fstrim -v "$BAGLAMA" 2>&1)"
olc
kayit "ADIM 5: Karsilastirma - TRIM kapali (provisioning_mode=full) iken fstrim"
echo full >"$f"
kayit " provisioning_mode: $(<"$f"), DISC-MAX: $(lsblk -dno DISC-MAX "$DISK" | tr -d ' ')"
cikti=$(fstrim -v "$BAGLAMA" 2>&1)
kod=$?
kayit " $cikti (cikis kodu $kod)"
echo unmap >"$f"
kayit " TRIM yeniden acildi: $(<"$f")"
kayit "Bitti."
}
main
Ek 3: Biçimlendirme deneme script'i
"Bulgu 1"deki biçimlendirme denemesini yapan script. Boş bir XFS bölümüne 10 GiB yazar, sonra bölümü önce mkfs.xfs -K ile, sonra mkfs.xfs ile biçimlendirir ve her adımda SSD'nin dolu alanını ölçer. Bölüm iki kez biçimlendirilir; bölüm boş değilse, bağlıysa ya da kutu beklenen kutu değilse hiçbir şey yapmadan durur.
sudo ./mkfs_deneme.sh /dev/sda /dev/sda1 /tmp/mkfs.log
Script'i göstermek için tıklayın (mkfs_deneme.sh)
#!/usr/bin/env bash
# mkfs.xfs'in bicimlendirirken TRIM gonderip gondermedigini SSD'nin kendi "dolu alan"
# bilgisiyle gosteren deneme. Blog yazisi icin kanit uretir.
#
# Kullanim: sudo ./mkfs_deneme.sh DISK BOLUM KAYIT_DOSYASI
# ornek: sudo ./mkfs_deneme.sh /dev/sda /dev/sda1 /tmp/mkfs.log
#
# Ayarlar (ortam degiskeni):
# KUTU_USB Kutunun USB kimligi (lsusb ciktisindan). Script yanlis diskte
# calismasin diye kontrol edilir. Varsayilan: 152d:0583 (JMS583)
# SMART_TIP smartctl -d degeri. JMicron JMS583 icin sntjmicron, Realtek
# RTL9210 icin auto calisti. Varsayilan: sntjmicron
#
# Adimlar: 0 baslangic, 1 10 GiB yaz, 2 mkfs.xfs -K (TRIM yok), 3 mkfs.xfs (TRIM var).
# DIKKAT: BOLUM iki kez bicimlendirilir. Yalnizca bos XFS bolumde calisir.
# Cikis kodlari: 0 basarili, 1 kullanim hatasi, 2 on kosul yok, 5 guvenlik icin durdu.
set -uo pipefail
readonly BEKLENEN_USB="${KUTU_USB:-152d:0583}"
readonly SMART_TIP="${SMART_TIP:-sntjmicron}"
readonly DOSYA_GIB=10
readonly DISK="${1:-}"
readonly BOLUM="${2:-}"
readonly KAYIT="${3:-}"
BAGLAMA=""
kayit() { printf '%s %s\n' "$(date '+%H:%M:%S')" "$*" | tee -a "$KAYIT"; }
olumcul() { kayit "HATA: $1"; exit "$2"; }
dolu_alan() {
smartctl -i -d "$SMART_TIP" "$DISK" 2>/dev/null \
| awk -F: '/Namespace 1 Utilization/ { gsub(/[ ,]/, "", $2); sub(/\[.*/, "", $2); print $2 }'
}
# SSD sayiyi gecikmeyle gunceller; 10 sn arayla 3 okuma ayni olunca yazilir (en fazla 300 sn).
olc() {
local b onceki="" ayni=0 gecen=0
while (( gecen <= 300 )); do
b=$(dolu_alan)
[[ -n $b ]] || { kayit " SSD dolu alani okunamadi."; return; }
if [[ $b == "$onceki" ]]; then ayni=$((ayni + 1)); else ayni=1; onceki=$b; fi
if (( ayni >= 3 )); then break; fi
sleep 10
gecen=$((gecen + 10))
done
if (( ayni < 3 )); then kayit " UYARI: sayi 300 sn icinde oturmadi."; fi
kayit " SSD dolu alani: $b bayt (~$(( b / 1000000000 )) GB)"
}
usb_kimligi() {
local p
p=$(readlink -f "/sys/block/$(basename "$DISK")/device")
while [[ $p != / ]]; do
if [[ -f $p/idVendor ]]; then printf '%s:%s\n' "$(<"$p/idVendor")" "$(<"$p/idProduct")"; return; fi
p=$(dirname "$p")
done
}
bagla() { BAGLAMA=$(mktemp -d /run/mkfs_deneme.XXXXXX) && mount "$BOLUM" "$BAGLAMA"; }
ayir() {
if [[ -n $BAGLAMA ]]; then
if mountpoint -q "$BAGLAMA"; then umount "$BAGLAMA"; fi
rmdir "$BAGLAMA"
BAGLAMA=""
fi
}
on_kontrol() {
[[ -n $DISK && -n $BOLUM && -n $KAYIT ]] || { echo "Kullanim: sudo $0 DISK BOLUM KAYIT_DOSYASI"; exit 1; }
[[ $(id -u) == 0 ]] || { echo "Root olarak calistirin."; exit 2; }
: >"$KAYIT" || exit 2
local a
for a in smartctl mkfs.xfs; do command -v "$a" >/dev/null || olumcul "$a kurulu degil." 2; done
[[ -b $DISK && -b $BOLUM ]] || olumcul "$DISK ya da $BOLUM yok." 1
[[ $(lsblk -no PKNAME "$BOLUM") == "$(basename "$DISK")" ]] || olumcul "$BOLUM, $DISK uzerinde degil." 1
[[ $(usb_kimligi) == "$BEKLENEN_USB" ]] || olumcul "$DISK beklenen kutu ($BEKLENEN_USB) degil: $(usb_kimligi)" 5
[[ $(lsblk -no FSTYPE "$BOLUM") == xfs ]] || olumcul "$BOLUM XFS degil." 5
if findmnt -rn -S "$BOLUM" >/dev/null; then olumcul "$BOLUM bagli; once ayirin." 5; fi
if [[ -n $(ls -A "/sys/class/block/$(basename "$BOLUM")/holders") ]]; then olumcul "$BOLUM baska bir aygit tarafindan kullaniliyor." 5; fi
}
main() {
on_kontrol
trap ayir EXIT
kayit "Disk: $DISK ($(usb_kimligi)), $(mkfs.xfs -V)"
kayit "provisioning_mode: $(cat /sys/block/"$(basename "$DISK")"/device/scsi_disk/*/provisioning_mode), DISC-MAX: $(lsblk -dno DISC-MAX "$DISK" | tr -d ' ')"
bagla || olumcul "$BOLUM baglanamadi." 5
if [[ -n $(find "$BAGLAMA" -mindepth 1 -maxdepth 1 -print -quit) ]]; then
olumcul "$BOLUM bos degil; deneme yalnizca bos dosya sisteminde yapilir." 5
fi
kayit "ADIM 0: Baslangic (bos dosya sistemi)"
olc
kayit "ADIM 1: $DOSYA_GIB GiB rastgele veriyle dosya yaziliyor"
dd if=/dev/urandom of="$BAGLAMA/deneme.bin" bs=1M count=$((DOSYA_GIB * 1024)) oflag=direct status=none \
|| olumcul "dd basarisiz oldu." 5
sync
ayir || olumcul "$BOLUM ayrilamadi." 5
olc
kayit "ADIM 2: mkfs.xfs -f -K (bicimlendirirken TRIM gonderme)"
mkfs.xfs -f -K "$BOLUM" 2>&1 | grep -iE 'discard' | sed 's/^/ mkfs: /' | tee -a "$KAYIT"
[[ ${PIPESTATUS[0]} == 0 ]] || olumcul "mkfs.xfs -K basarisiz oldu." 5
olc
kayit "ADIM 3: mkfs.xfs -f (varsayilan: bicimlendirirken TRIM gonderir)"
mkfs.xfs -f "$BOLUM" 2>&1 | grep -iE 'discard' | sed 's/^/ mkfs: /' | tee -a "$KAYIT"
[[ ${PIPESTATUS[0]} == 0 ]] || olumcul "mkfs.xfs basarisiz oldu." 5
olc
kayit "Bitti. $BOLUM bos XFS olarak birakildi (UUID $(blkid -s UUID -o value "$BOLUM"))."
}
main