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

Rain_Maker

Administrator
Teammitglied
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

Code:
# 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.

Code:
rpm -ql parkverbot | grep systemd
/usr/lib/systemd/system/parkverbot.service
/usr/lib/systemd/system/parkverbot@.service
Zunächst kurz zu den Optionen von parkverbot:

Code:
 parkverbot --help
Usage: parkverbot [-b kbytes] [-r kbytes] [-t sec] [-?|--help] [--usage]
  -b kbytes     Buffer size, in KB (default: 64)
  -r kbytes     Guard window size, in KB (default: 16384)
  -t sec        Interval between requests, in s (default: 4.0)
  -?, --help    Show this help message
  --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.

Code:
[Unit]
Description=Hard disk head parking inhibitor

[Service]
EnvironmentFile=/etc/sysconfig/parkverbot
ExecStart=/usr/sbin/parkverbot $PARKVERBOT_DISKS
Restart=on-abort
RestartSec=3
Nice=-5

[Install]
WantedBy=basic.target
Die Datei enthält zwei für uns wichtige Details:

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

2)
Code:
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.
Code:
PARKVERBOT_DISKS="/dev/sda"
Nun kann der Dienst als root mittels

Code:
# systemctl enable parkverbot.service && systemctl start parkverbot.service
aktiviert und gestartet werden.
Nachprüfen erfolgt mit
Code:
systemctl status parkverbot.service 
parkverbot.service - Hard disk head parking inhibitor
          Loaded: loaded (/usr/lib/systemd/system/parkverbot.service; enabled)
          Active: active (running) since Mon, 2013-06-10 20:58:13 CEST; 9s ago
        Main PID: 3849 (parkverbot)
          CGroup: name=systemd:/system/parkverbot.service
                  └ 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

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

Code:
# systemctl restart parkverbot.service && systemctl status parkverbot.service 
parkverbot.service - Hard disk head parking inhibitor
          Loaded: loaded (/usr/lib/systemd/system/parkverbot.service; disabled)
          Active: active (running) since Mon, 2013-06-10 21:04:34 CEST; 15ms ago
        Main PID: 3902 (parkverbot)
          CGroup: name=systemd:/system/parkverbot.service
                   └ 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.

Code:
[Unit]
Description=Run parkverbot daemon on %I
BindTo=%i.device

[Service]
ExecStart=/usr/sbin/parkverbot %f
Restart=on-abort
RestartSec=3
Nice=-5

[Install]
WantedBy=basic.target
Diese Datei enthält einige spezielle Variablen, deren Bedeutung man unter

Code:
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

Code:
# 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.

Code:
# systemctl enable parkverbot@dev-sda.service && systemctl start parkverbot@dev-sda.service

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

Rain_Maker

Administrator
Teammitglied
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:

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

Code:
[Unit] Description=Run parkverbot daemon on %I
BindTo=%i.device

[Service]
ExecStart=/usr/sbin/parkverbot %f [b]-t 60[/b]
Restart=on-abort
RestartSec=3
Nice=-5
[Install]
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.

Code:
[Unit] Description=Run parkverbot daemon on %I
BindTo=%i.device

[Service] 
[b]EnvironmentFile=/etc/sysconfig/parkverbot-%i[/b]
ExecStart=/usr/sbin/parkverbot %f [b]$PARKVERBOT_OPTS[/b]
Restart=on-abort
RestartSec=3
Nice=-5
[Install]
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

Code:
PARKVERBOT_OPTS="-t 30"
/etc/sysconfig/parkverbot-dev-sdb

Code:
PARKVERBOT_OPTS="-t 60"
und dann wie gehabt

Code:
# systemctl enable parkverbot@dev-sda.service && systemctl start parkverbot@dev-sda.service  
# 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
 

Rain_Maker

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

Code:
[Unit]
Description=Run parkverbot daemon on %I
BindTo=%i.device

[Service]
EnvironmentFile=/etc/sysconfig/[B]parkverbot-external-USB[/B]
ExecStart=/usr/sbin/parkverbot %f $PARKVERBOT_OPTS
Restart=on-abort
RestartSec=3
Nice=-5

[Install]
WantedBy=basic.target
Environment-Datei /etc/sysconfig/parkverbot-external-USB

Code:
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

Code:
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'"
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

Code:
ACTION=="add"
bzw.

Code:
ACTION=="remove"
ist wohl selbsterklärend, der Ausdruck

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

Code:
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.

Code:
KERNEL!="sd[a-b]"
oder

Code:
KERNEL!="sda|sdb"
und
Code:
KERNEL=="sd[c-z]"
Die nächste Bedingung

Code:
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

Code:
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:

Code:
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

Code:
E: ID_BUS=ata
so aussieht.

Die Bedingung

Code:
ENV{ID_BUS}!="usb"
ist hier also eine Negierung "alles, was nicht ID_BUS=usb hat", eine positive Bedingung wäre
Code:
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

Code:
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.

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

Code:
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

Code:
'/bin/sh -c "/Pfad/PROGRAMM -OPTIONEN ARGUMENTE"'
sieht zwar kompliziert aus, ist aber notwendig, sofern ein Programm/Script mit Argumenten/Optionen aufgerufen wird.

Ein

Code:
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
 
Oben