Parken verboten! - Schutz vor dem "grünen Festplattentod" und etwas "systemd-Magic"

      Parken verboten! - Schutz vor dem "grünen Festplattentod" und etwas "systemd-Magic"

      In der Folge dieses kleinen HowTos will ich die Einrichtung des Dienstes "parkverbot" mittels systemd unter openSUSE >= 12.2 beschreiben.

      Die Einschränkung bezüglich der Version begründet sich darin, daß ältere Versionen von openSUSE zumindest per default keine Unterstützung für systemd als init-System besitzen. Für openSUSE 12.1 findet sich noch kein fertiges RPM in den offiziellen Repos, wer sein Glück mit einem Rebuild aus dem src.rpm probieren will, der kann ja über Erfolg/Mißerfolg berichten.

      Zunächst natürlich die Antwort auf die Frage

      "Was soll das Ganze überhaupt?"

      Hierzu kann sich der geneigte Leser diesen Thread in linuxforen.de ansehen.

      Der grüne Festplattentod - Load Cycle Count - Liste anfälliger Festplatten - linuxforen.de -- User helfen Usern

      Die dort gegebenen Tipps mittels hdparm-Befehlen das "Parken" der Festplatte zu verhindern funktionieren leider nicht immer zuverlässig, auch mein kleines Netbook will trotz der entsprechenden Einträge noch immer viel zu häufig den Kopf der Platte parken und wieder aufwecken.

      Enter "parkverbot" ...

      man parkverbot schrieb:


      Introduction
      Modern rotational hard disks have a misfeature involving the regular automatic unloading of the heads, measurable by the S.M.A.R.T. attribute no. 193/225
      "Load_Cycle_Count". While we do not know for sure, it is thinkable that manufacturers attempt to save power, or at least sell it that way.

      The downside of this is significant latency on wakeup, and the constant hard retraction of the head is believed to reduce the disk's life. It is also not
      reliably deactivatable: `hdparm -B` can yield "APM_level = not supported", in other cases, setting APM is supported, but has no effect. Furthermore, hdparm(8)
      does not work with SCSI/SAS disks or disks behind protocol translation bridges (most USB enclosures). Using a DOS utility disk just is not realistic. (In
      short, you suck, dear manufacturers.)

      Description
      The "parkverbot" daemon will issue small read requests periodically to a random location on disk in an effort to reset the inactivity timer in the hardware
      and so prevent the dreaded head unloading.

      The current algorithms are not fully proof, but it works reasonably well in practice for the amount of code it is composed of. The statistical success/failure
      rate with disks in the original author's environment has been observed to be 0.4 unloads/day.

      parkverbot obsoletes the earlier "thkd" package.

      The name stems from German and indicates parking prohibited (you probably already guessed that), or more commonly known as a "no parking" zone.


      Die Installation ist unter 12.2/12.3 schnell erledigt

      Quellcode

      1. # zypper in parkverbot


      Die Aktivierung und Einrichtung erfordert allerdings ein wenig Handarbeit und bietet zusätzlich noch die Möglichkeit ein wenig über die Möglichkeiten von "systemd" zu lernen, denn das Paket liefert zwei unterschiedliche ".service"-files mit.

      Quellcode

      1. rpm -ql parkverbot | grep systemd
      2. /usr/lib/systemd/system/parkverbot.service
      3. /usr/lib/systemd/system/parkverbot@.service


      Zunächst kurz zu den Optionen von parkverbot:

      Quellcode

      1. parkverbot --help
      2. Usage: parkverbot [-b kbytes] [-r kbytes] [-t sec] [-?|--help] [--usage]
      3. -b kbytes Buffer size, in KB (default: 64)
      4. -r kbytes Guard window size, in KB (default: 16384)
      5. -t sec Interval between requests, in s (default: 4.0)
      6. -?, --help Show this help message
      7. --usage Display brief usage message
      Für den normalen Anwender dürfte hier vor allem die Option "-t" von Interesse sein, der default von 4 Sekunden ist doch recht kurz geraten und anhand der Funktionsweise macht es Sinn das Intervall so lange wie möglich zu wählen um (Akku)Strom zu sparen, aber dazu später mehr.

      Schauen wir uns zunächst die Datei /usr/lib/systemd/system/parkverbot.service etwas genauer an.

      Quellcode

      1. [Unit]
      2. Description=Hard disk head parking inhibitor
      3. [Service]
      4. EnvironmentFile=/etc/sysconfig/parkverbot
      5. ExecStart=/usr/sbin/parkverbot $PARKVERBOT_DISKS
      6. Restart=on-abort
      7. RestartSec=3
      8. Nice=-5
      9. [Install]
      10. WantedBy=basic.target
      Die Datei enthält zwei für uns wichtige Details:

      1)

      Quellcode

      1. EnvironmentFile=/etc/sysconfig/parkverbot
      Aus dieser Datei werden für den Dienst benötigte Variablen ausgelesen, namentlich

      2)

      Quellcode

      1. ExecStart=/usr/sbin/parkverbot $PARKVERBOT_DISKS
      die Variable "$PARKVERBOT_DISKS",

      Da diese Datei noch nicht exisitiert, müssen wir sie zunächst anlegen, wir gehen dabei davon aus, daß nur eine Festplatte vorhanden ist, welche /dev/sda heisst.

      Also legen wir als root eine Datei "/etc/sysconfig/parkverbot" mit folgendem Inhalt an.

      Quellcode

      1. PARKVERBOT_DISKS="/dev/sda"

      Nun kann der Dienst als root mittels

      Quellcode

      1. # systemctl enable parkverbot.service && systemctl start parkverbot.service
      aktiviert und gestartet werden.
      Nachprüfen erfolgt mit

      Quellcode

      1. systemctl status parkverbot.service
      2. parkverbot.service - Hard disk head parking inhibitor
      3. Loaded: loaded (/usr/lib/systemd/system/parkverbot.service; enabled)
      4. Active: active (running) since Mon, 2013-06-10 20:58:13 CEST; 9s ago
      5. Main PID: 3849 (parkverbot)
      6. CGroup: name=systemd:/system/parkverbot.service
      7. └ 3849 /usr/sbin/parkverbot /dev/sda

      So weit, so gut, aber alle 4 Sekunden ein kurzes Aufwachen der Festplatte erschien mir dann doch zu viel des Guten, aber auch hier gibt es einen einfachen Weg.

      Das .service file liest die Variable "$PARKVERBOT_DISKS" und übergibt deren Wert an das Programm "parkverbot" und nur weil die Variable "DISKS" heisst, verbietet sie es mir nicht auch andere Parameter mitzugeben.

      Ändert man nun die Datei /etc/sysconfig/parkverbot ein wenig ab

      Quellcode

      1. PARKVERBOT_DISKS="/dev/sda -t 60"
      und startet den Service neu, dann

      Quellcode

      1. # systemctl restart parkverbot.service && systemctl status parkverbot.service
      2. parkverbot.service - Hard disk head parking inhibitor
      3. Loaded: loaded (/usr/lib/systemd/system/parkverbot.service; disabled)
      4. Active: active (running) since Mon, 2013-06-10 21:04:34 CEST; 15ms ago
      5. Main PID: 3902 (parkverbot)
      6. CGroup: name=systemd:/system/parkverbot.service
      7. └ 3902 /usr/sbin/parkverbot /dev/sda -t 60
      erfolgt das Aufwecken nur noch alle 60 Sekunden. Welcher Wert zur eigenen Hardware passt muss man durch Ausprobieren herausfinden, je länger das Intervall gemacht werden kann, ohne daß die Platte "geparkt" wird, desto besser für den Akku, die 60 Sekunden sind aber nur ein Vorschlag, der nicht ganz unsinnig erscheint bei mir klappt das ganz gut mit diesem Wert.

      So weit so gut, aber was macht man bei einer Kiste mit mehr als einer Festplatte?

      Nun, genau für dieses Szenario kann man die zweite service-Datei "/usr/lib/systemd/system/parkverbot@.service" im Paket verwenden, schauen wir sie uns einmal genauer an.

      Quellcode

      1. [Unit]
      2. Description=Run parkverbot daemon on %I
      3. BindTo=%i.device
      4. [Service]
      5. ExecStart=/usr/sbin/parkverbot %f
      6. Restart=on-abort
      7. RestartSec=3
      8. Nice=-5
      9. [Install]
      10. WantedBy=basic.target
      Diese Datei enthält einige spezielle Variablen, deren Bedeutung man unter

      Quellcode

      1. man systemd.unit
      genauer nachlesen kann.

      Für dieses kleine HowTo soll nur erwähnt werden, daß solche services mit einem "@" im Namen mittels entsprechender "Zusatzinformation" aufgerufen werden können und damit einem bestimmten Gerät zugeordnet sind.

      Will man z.B. diesen Service für die Platte /dev/sda starten, dann erreicht man dies mit

      Quellcode

      1. # systemctl start parkverbot@[B]dev-sda[/B].service
      Wer mehrere Festplatten hat, kann also mehrfach den Service aktivieren und gibt dabei jeweils den passenden Namen mit, für 2 Platten /dev/sda und /dev/sdb sähe das dann so aus.

      Quellcode

      1. # systemctl enable parkverbot@dev-sda.service && systemctl start parkverbot@dev-sda.service
      2. # systemctl enable parkverbot@dev-sdb.service && systemctl start parkverbot@dev-sdb.service

      Haken an der Sache, hier fehlt die Möglichkeit einen Wert für "-t" oder andere Parameter mitzugeben, dazu benötigt man noch etwas weitere Handarbeit.

      Weiter geht es in Beitrag 2 dieses Threads.
      "Programming today is a race between software engineers striving to build better & bigger idiot-proof programs and the Universe trying to produce bigger & better idiots. So far, the Universe is winning." (Rick Cook)

      Dies ist ein _öffentliches_ Supportforum, keinerlei Support per PN, EMail oder Instant Messenger.

      openSUSE Leap 42.3 - Kernel 4.17.x - fluxbox 1.3.7

      Bitmessage: BM-2D8h8QZmvHfgbixWeiG1NDZHG1iXAhBz8K

      AW: Parken verboten! - Schutz vor dem "grünen Festplattentod" und etwas "systemd-Magi

      Nun folgt ein wenig "systemd für Fortgeschrittene", denn die hier vorgeschlagene Lösung des Problems zeigt noch ein paar weitere Features dieses Tools auf.

      Die "unsaubere" Lösung wäre es einfach die Datei "/usr/lib/systemd/system/parkverbot@.service" zu bearbeiten, aber das hätte einen großen Haken. Bei jedem Update des Paketes "parkverbot" würden diese Änderungen überschrieben, das kann es also nicht sein.

      Was man jedoch -und das gilt für alle service files, die man gerne abändern möchte- ohne Probleme machen kann, ist eine Kopie der Datei unter "/etc/systemd/system" abzulegen und diese dann nach Wunsch zu bearbeiten, denn systemd bevorzugt bei gleichem Namen die Datei unter /etc/systemd/system und diese wird bei einem Update auch nicht überschrieben.

      Also wäre die einfachste Lösung:

      Quellcode

      1. # cp /usr/lib/systemd/system/parkverbot@.service /etc/systemd/system/parkverbot@.service
      danach editieren der Datei /etc/systemd/system/parkverbot@.service

      Quellcode

      1. [Unit] Description=Run parkverbot daemon on %I
      2. BindTo=%i.device
      3. [Service]
      4. ExecStart=/usr/sbin/parkverbot %f [b]-t 60[/b]
      5. Restart=on-abort
      6. RestartSec=3
      7. Nice=-5
      8. [Install]
      9. WantedBy=basic.target
      und man hätte sein Ziel erreicht.

      Das ist allerdings noch recht unflexibel, der Wert für -t ist hart kodiert, das geht auch noch etwas eleganter.

      Quellcode

      1. [Unit] Description=Run parkverbot daemon on %I
      2. BindTo=%i.device
      3. [Service]
      4. [b]EnvironmentFile=/etc/sysconfig/parkverbot-%i[/b]
      5. ExecStart=/usr/sbin/parkverbot %f [b]$PARKVERBOT_OPTS[/b]
      6. Restart=on-abort
      7. RestartSec=3
      8. Nice=-5
      9. [Install]
      10. WantedBy=basic.target
      Mit diesen Änderung erreicht man Folgendes:

      a) Man legt nun für jede der gewünschten Festplatten eine Konfigurationsdatei /etc/sysconfig/parkverbot-dev-sdX an und kann dort

      b) über die Variable "PARKVERBOT_OPTS" die gewünschten Werte einstellen.

      Beispiel für zwei Platten, die erste /dev/sda soll alle 30 Sekunden, die zweite /dev/sdb alle 60 Sekunden aufgeweckt werden.

      /etc/sysconfig/parkverbot-dev-sda

      Quellcode

      1. PARKVERBOT_OPTS="-t 30"


      /etc/sysconfig/parkverbot-dev-sdb

      Quellcode

      1. PARKVERBOT_OPTS="-t 60"
      und dann wie gehabt

      Quellcode

      1. # systemctl enable parkverbot@dev-sda.service && systemctl start parkverbot@dev-sda.service
      2. # systemctl enable parkverbot@dev-sdb.service && systemctl start parkverbot@dev-sdb.service
      Wer seinen Festplatten via udev-Regel per symlink einen eindeutigen, zweiten Namen gibt, der allerdings _keine_ Leerzeichen, "/" oder "-" enthalten darf und direkt unter /dev/ liegen muss, kann auch das potentielle Problem sich ändernder Gerätebezeichnungen umgehen.

      Greetz,

      RM
      "Programming today is a race between software engineers striving to build better & bigger idiot-proof programs and the Universe trying to produce bigger & better idiots. So far, the Universe is winning." (Rick Cook)

      Dies ist ein _öffentliches_ Supportforum, keinerlei Support per PN, EMail oder Instant Messenger.

      openSUSE Leap 42.3 - Kernel 4.17.x - fluxbox 1.3.7

      Bitmessage: BM-2D8h8QZmvHfgbixWeiG1NDZHG1iXAhBz8K

      Externe Festplatten (USB)

      Hier ein Ansatz für über USB angeschlossene, externe Festplatten. Anhand einer angepassten udev-Regel wird beim Einstecken "parkverbot" gestartet und beim Ausstöpseln wieder gestoppt.

      Dabei wird zwischen einer externen USB-Festplatte und z.B. USB-Sticks unterschieden, auf welchen der Dienst Parkverbot wegen "dreht sich nicht, wozu also parken verhindern?" keinen Sinn macht.

      Zunächst die Vorarbeit für einen angepassten systemd-Service, nennen wir ihn mal "/etc/systemd/system/external_parkverbot@.service".

      Quellcode

      1. [Unit]
      2. Description=Run parkverbot daemon on %I
      3. BindTo=%i.device
      4. [Service]
      5. EnvironmentFile=/etc/sysconfig/[B]parkverbot-external-USB[/B]
      6. ExecStart=/usr/sbin/parkverbot %f $PARKVERBOT_OPTS
      7. Restart=on-abort
      8. RestartSec=3
      9. Nice=-5
      10. [Install]
      11. WantedBy=basic.target


      Environment-Datei /etc/sysconfig/parkverbot-external-USB

      Quellcode

      1. PARKVERBOT_OPTS="-t 60"
      (Die 60 Sekunden sind erneut nur ein halbwegs sinnvoller Vorschlag.)

      Und nun zur eigentlichen "Magie", wir benötigen eine udev-Regel, die zuverlässig externe USB-Festplatten erkennt.

      Allgemein funktionieren udev-Regeln immer nach dem Prinzip:

      "Wenn $BEDINGUNG(EN) erfüllt, dann führe $AKTION aus"

      Man hat also immer zwei Teile; der erste bestimmt, ob die Regel für ein bestimmtes Gerät greift, der zweite Teil führt bei einem kompletten "Match" auf alle Bedingungen (sic!) die festgelegte Aktion aus.

      Die hier vorgestellte Regel funktioniert bei mir und unterscheidet zwischen diversen USB-Sticks, über einen USB-Cardreader angeschlossene SD-Karten und zwei unterschiedlichen Festplatten, da ich allerdings z.B. keine SSD habe, kann ich darüber keine Aussagen machen. Wer eine externe SSD-Platte mit Anschlussmöglichkeit über USB besitzt, kann hier ein paar Informationen liefern, dazu am Ende des Posts ein paar Hinweise.

      Zunächst die von mir angewandten Regeln, anschließend werde ich sie in ihren Einzelteilen erklären.

      /etc/udev/rules.d/99-parkverbot.rules

      Quellcode

      1. ACTION=="add", KERNEL!="sda", KERNEL=="sd[b-z]", SUBSYSTEM=="block", ENV{ID_BUS}!="usb", RUN+="/bin/sh -c '/usr/bin/sleep 1 && /usr/bin/systemctl start external_parkverbot@dev-%k.service'"
      2. ACTION=="remove", KERNEL!="sda", KERNEL=="sd[b-i]", SUBSYSTEM=="block", ENV{ID_BUS}!="usb", RUN+="/bin/sh -c '/usr/bin/systemctl stop external_parkverbot@dev-%k.service'"


      Das

      Quellcode

      1. ACTION=="add"
      bzw.

      Quellcode

      1. ACTION=="remove"


      ist wohl selbsterklärend, der Ausdruck

      Quellcode

      1. KERNEL!="sda"

      schliesst für diese Regel die interne Festplatte (bei mir eben nur eine und die heisst auch immer sda) aus, während

      Quellcode

      1. KERNEL=="sd[b-z]"

      alle weiteren Geräte ab sdb als potentielle Kandidaten einschliesst.

      Anmerkung:

      Wer mehr als eine interne Festplatte hat, muss hier logischerweise ein wenig anpassen, z.B bei zwei Platten (die sda und sdb heissen) würde das so aussehen.

      Quellcode

      1. KERNEL!="sd[a-b]"

      oder

      Quellcode

      1. KERNEL!="sda|sdb"


      und

      Quellcode

      1. KERNEL=="sd[c-z]"


      Die nächste Bedingung

      Quellcode

      1. SUBSYSTEM=="block"


      ist eigentlich nur Kosmetik, da es wohl kaum ein Gerät geben wird, welches vom Kernel "sdX" genannt werden wird und kein blockorientiertes Gerät ist, aber sicher ist sicher.

      Nun kommt der schwierigste Teil, die Unterscheidung zwischen USB-Stick und über USB angeschlossene Festplatte.

      Die Grundlage für diese Bedingung kann man mittels

      Quellcode

      1. udevadm info --query=all --name=/dev/sdX #X=Platzhalter für den Namen des Geräts, also a,b,c ....
      herausfinden.

      Ein USB-Stöpsel bzw. SD-Karte über einen per USB angeschlossenen Cardreader ergibt hier unter anderem:

      Quellcode

      1. E: ID_BUS=usb
      Bei den von mir getesteten, externen Festplatten wird jedoch berücksichtigt, daß ja hinter dem USB-Anschluss ein IDE- bzw. SATA-Controller sitzt, an welchem die Festplatte hängt, weshalb die Ausgabe dort dann

      Quellcode

      1. E: ID_BUS=ata
      so aussieht.

      Die Bedingung

      Quellcode

      1. ENV{ID_BUS}!="usb"

      ist hier also eine Negierung "alles, was nicht ID_BUS=usb hat", eine positive Bedingung wäre

      Quellcode

      1. ENV{ID_BUS}=="ata"

      aber ich bin mir hier nicht sicher, ob damit alle IDE/SATA/SCSI-Platten erschlagen werden, die per USB angeschlossen sind.

      Anmerkung:

      Wer also eine/mehrere externe, per USB angeschlossene Festplatten hat, der kann hier seine Ausgabe von

      Quellcode

      1. udevadm info --query=all --name=/dev/sdX #X an den Gerätenamen anpassen!
      posten, wobei man die Teile der Ausgabe, die Seriennummern enthalten, natürlich gerne weglassen kann.

      Zum Schluss noch der Teil der udev-Regel, der dann den entsprechenden Dienst startet.

      Quellcode

      1. RUN+="/bin/sh -c '/usr/bin/systemctl start external_parkverbot@dev-%k.service'"

      bzw.

      Quellcode

      1. RUN+="/bin/sh -c '/usr/bin/systemctl stop external_parkverbot@dev-%k.service'"


      sollte fast selbsterklärend sein, einzig das "%k" bedarf eines Kommentars.

      Diese udev-Variable steht für den "Kernelnamen" eines Gerätes, also der Name, der unter /dev/ auftaucht.
      Heisst die Platte also nach dem Anstöpseln "/dev/sde" so wird "%k" durch "sde" ersetzt, also genau das, was wir ja wollen, der Dienst soll nur für dieses Device gestartet werden.

      Anmerkung:

      Bei der Verwendung von "RUN+=" gilt es zwei Eigenheiten von udev zu beachten.

      a) Man sollte _immer_ absolute Pfade für seine Programme verwenden, udev sucht sonst nur in /usr/lib/udev und ignoriert die Variable $PATH.

      b) Der Konstrukt

      Quellcode

      1. '/bin/sh -c "/Pfad/PROGRAMM -OPTIONEN ARGUMENTE"'

      sieht zwar kompliziert aus, ist aber notwendig, sofern ein Programm/Script mit Argumenten/Optionen aufgerufen wird.

      Ein

      Quellcode

      1. RUN+="/Pfad/PROGRAMM"

      funktioniert nur dann, wenn keine Optionen an das Programm übergeben werden müssen, der Konstrukt mit '/bin/sh -c "...."' funktioniert aber auch da, man ist also damit immer auf der sicheren Seite.

      Greetz,

      RM
      "Programming today is a race between software engineers striving to build better & bigger idiot-proof programs and the Universe trying to produce bigger & better idiots. So far, the Universe is winning." (Rick Cook)

      Dies ist ein _öffentliches_ Supportforum, keinerlei Support per PN, EMail oder Instant Messenger.

      openSUSE Leap 42.3 - Kernel 4.17.x - fluxbox 1.3.7

      Bitmessage: BM-2D8h8QZmvHfgbixWeiG1NDZHG1iXAhBz8K