7 Kasım 2026 Cumartesi

USB Disk Trim ( Discard ) Desteğinin kullanılması - Acasis TB406Pro

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:

  1. Ölü eşleme: /dev/mapper/... aygıtı artık olmayan diski göstermeye devam eder. Eşleme: /dev/mapper altındaki, LVM biriminin diske bağlantısı.
  2. 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: fstrim SSD'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ındaki DISC-ZERO sü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-in listesi soldan okunur ve ilk boş olmayan dosyada durur. /etc/fstab boş 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_vpd ve sg_readcap ile 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çin fstrim'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

Hiç yorum yok:

Yorum Gönder

Not: Yalnızca bu blogun üyesi yorum gönderebilir.

USB Disk Trim ( Discard ) Desteğinin kullanılması - Acasis TB406Pro

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 "...