fli4l wird seit der Version 2.0 in Module (Pakete) aufgeteilt, z.B.
Mit dem Basis-Paket ist fli4l ein reiner Ethernet-Router. Für ISDN und/oder DSL ist das Paket isdn und/oder dsl in dem fli4l-Verzeichnis auszupacken. Entsprechendes gilt für die anderen Pakete.
Aus den Paketen wird in Abhängigkeit von der konkreten Konfiguration eine Konfigurationsdatei namens rc.cfg und zwei Archive namens rootfs.img und opt.img erstellt, die alle Konfigurationsinformationen und alle benötigten Dateien enthalten. Diese Dateien werden mit Hilfe von mkfli4l erzeugt, welches die einzelnen Pakete einliest und auf Fehler in der Konfiguration prüft.
mkfli4l akzeptiert die in Tabelle 8.1 angegebenen Parameter. Fehlen sie, werden die in Klammern angegebenen default-Werte genommen. Eine vollständige Liste der Optionen (Tabelle 8.1) erhält man, wenn man
mkfli4l -haufruft.
Option | Bedeutung | |
-c, - -config | Setzen des Verzeichnisses, in dem mkfli4l die config-Dateien der Pakete sucht (default: config) | |
-x, - -check | Setzen des Verzeichnisses, in dem mkfli4l die zum Prüfen der Pakete benötigten Dateien sucht (<package>.txt, <package>.exp und <package>.ext; default: check) | |
-l, - -log | Setzen der Logdatei; mkfli4l protokolliert Fehlermeldungen und Warnungen in dieser Datei (default: img/mkfli4l.log) | |
-p, - -package | Angabe der Pakete, die geprüft werden sollen, diese Option kann mehrmals angegeben werden, wenn man mehrere Pakete im Zusammenhang prüfen will. Bei Verwendung von -p wird allerdings grundsätzlich zuerst die Datei <check_dir>/base.exp eingelesen, um die allgemeinen regulären Ausdrücke, die vom Basis-Paket bereitgestellt werden, zur Verfügung zu stellen. Diese Datei muss also existieren. | |
-i, - -info | Gibt Auskunft über den Verlauf der Prüfung (welche Dateien werden gelesen, welche Prüfungen werden durchgeführt, welche besonderen Dinge traten während des Prüfprozesses auf) | |
-v, - -verbose | Ausführlichere Variante von -i | |
-h, - -help | Zeigt die Hilfe an | |
-d, - -debug | Gestattet das Debuggen des Prüfprozesses. Dies ist als Hilfe für Paketentwickler gedacht, die etwas genauer wissen möchten, wie die Prüfung des Paketes abläuft. | |
Debugoption | Bedeutung | |
check | show check process | |
zip-list | show generation of zip list | |
zip-list-skipped | show skipped files | |
zip-list-regexp | show regular expressions for ziplist | |
opt-files | check all files in opt/<package>.txt | |
ext-trace | show trace of extended checks |
Ein Paket kann mehrere OPTs enthalten, wenn es aber nur eins enthält, ist es allerdings zweckmäßig, das Paket genauso wie das OPT zu nennen. Im Folgenden ist <PACKAGE> durch den jeweiligen Paket-Namen zu ersetzen. Ein Paket besteht aus folgenden Teilen:
Die einzelnen Teile sind im Folgenden näher beschrieben.
In der Datei config/<PACKAGE>.txt werden vom Benutzer Änderungen an der Konfiguration des Pakets vorgenommen. Alle Variablen eines OPTs sollten einheitlich mit dem Namen des OPTs beginnen, also zum Beispiel:
#------------------------------------------------------------------- # Optional package: TELNETD #------------------------------------------------------------------- OPT_TELNETD='no' # install telnetd: yes or no TELNETD_PORT='23' # telnet port, see also FIREWALL_DENY_PORT_x
Ein OPT sollte in der Konfigurationsdatei durch einen Header (siehe oben) entsprechend abgegrenzt werden. Dies erhöht die Übersichtlichkeit, zumal ein Paket ja auch mehrere OPTs enthalten kann. Die dem OPT zugehörigen Variablen sollten -- ebenfalls im Interesse der Übersichtlichkeit -- nicht weiter eingerückt werden. Kommentare und Leerzeilen sind erlaubt, wobei Kommentare einheitlich in Spalte 33 beginnen sollen. Ist eine Variable inklusive ihrer Belegung länger als 32 Zeichen, ist der Kommentar eine Zeile versetzt ab Spalte 33 einzufügen. Längere Kommentare werden jeweils ab Spalte 33 beginnend auf mehrere Zeilen aufgeteilt. Diese Maßnahmen sollen die Lesbarkeit der Konfigurationsdatei erhöhen.
Alle Werte hinter dem Gleichheitszeichen müssen in Hochkommata8.1 eingefasst werden, da es sonst beim Booten zu Problemen kommen kann.
Variablen, die aktiv sind (s.u.), werden in die rc.cfg übernommen, alles andere wird ignoriert. Einzige Ausnahme sind Variablen mit dem Namen <PACKAGE>_DO_DEBUG. Diese dienen zur Fehlersuche in Paketen und werden pauschal übernommen.
Die Datei opt/<PACKAGE>.txt enthält Anweisungen, die beschreiben
mkfli4l generiert darauf basierend die erforderlichen Archive.
Leere Zeilen und Zeilen, die mit "`#"' anfangen, werden ignoriert. In einer der ersten Zeilen sollte die Version des Paket-Dateiformats wie folgt stehen:
<erste Spalte> <zweite Spalte> <dritte Spalte> opt_format_version 1 -
Die restlichen Zeilen haben folgende Syntax:
<erste Spalte> <zweite Spalte> <dritte Spalte> <folgende spalten> Variable Wert Datei Optionen
Falls mehrere Variablen auf denselben Wert geprüft werden sollen, kann auch eine Liste von Variablen (durch Kommata getrennt) verwendet werden. In diesem Falle reicht es aus, wenn mindestens eine Variable den in der zweiten Spalte geforderten Wert enthält. Wichtig ist dabei, dass zwischen den einzelnen Variablen keine Leerzeichen stehen!
Bei OPT-Variablen (also Variablen, die mit OPT_ beginnen und typischerweise den Typ YESNO haben) kann das Präfix "`OPT_"' weggelassen werden. Des Weiteren ist es unwichtig, ob Variablen in Groß- oder in Kleinbuchstaben (oder beliebig gemischt) notiert werden.
Es ist möglich, vor den Wert ein "`!"' zu schreiben. In diesem Falle wird die Prüfung negiert, d.h. die Datei wird genau dann kopiert, wenn die Variable diesen Wert nicht enthält.
Beginnt der Dateiname mit einem "`rootfs:"'-Präfix, wird die Datei in die Liste der ins RootFS aufzunehmenden Dateien übernommen. Der Präfix wird vorher entfernt.
Liegt die Datei unterhalb der aktuellen Konfigurationsverzeichnisses, wird sie in die Liste der aus dem Konfigurationsverzeichnis zu übernehmenden Dateien aufgenommen, andernfalls wird die unter opt/ liegende Datei genommen. Die Datei darf dann kein rootfs:-Präfix haben.
Ist die zu kopierende Datei ein Kernel-Modul, kann man die konkrete Kernel-Version durch ${KERNEL_VERSION} ersetzen. mkfli4l nimmt dann die Version aus der Konfiguration und setzt sie hier ein. Dadurch kann man einem Paket Module für verschiedene Kern-Versionen mitgeben, und es wird immer die für den Kern richtige Version auf den Router kopiert. Für Kernel-Module kann der Pfad auch vollkommen entfallen, mkfli4l findet das Modul anhand von modules.dep und modules.alias, siehe den Abschnitt "`Automatische Auflösung von Abhängigkeiten für Kernel-Module"'.
Option | Bedeutung | Standardwert | ||||||||||
type= | Der Typ des Eintrags:
Wenn vorhanden, muss diese Option an erster Stelle stehen. Der Typ "`local"' steht hierbei für den Typ eines im Dateisystem existierenden Objekts und entspricht somit "`file"', "`dir"', "`node"' oder "`symlink"' (je nachdem). Die anderen Typen mit Ausnahme von "`file"' können Einträge im Archiv erzeugen, die nicht im lokalen Dateisystem vorliegen müssen. Das wird z.B. benutzt, um Gerätedateien im RootFS-Archiv anzulegen. |
local | ||||||||||
uid= | Der Eigentümer der Datei, entweder numerisch oder als Name aus passwd | root | ||||||||||
gid= | Die Gruppe der Datei, entweder numerisch oder als Name aus group | root | ||||||||||
mode= | Die Zugriffsrechte |
Dateien und Geräte:
rw-r--r-- (644)
Verzeichnisse: rwxr-xr-x (755)
Verknüpfungen: rwxrwxrwx (777)
|
||||||||||
flags=
(type=file) |
Konvertierungen vor der Aufnahme ins Archiv:
|
|||||||||||
name= | Alternativer Name, unter dem der Eintrag ins Archiv aufgenommen wird | |||||||||||
devtype=
(type=node) |
Beschreibt den Typ des Geräts ("`c"' für zeichenorientierte und "`b"' für blockorientierte Geräte). Muss an zweiter Stelle stehen. | |||||||||||
major=
(type=node) |
Beschreibt die so genannte "`Major"'-Nummer der Gerätedatei. Muss an dritter Stelle stehen. | |||||||||||
minor=
(type=node) |
Beschreibt die so genannte "`Minor"'-Nummer der Gerätedatei. Muss an vierter Stelle stehen. | |||||||||||
linktarget=
(type=symlink) |
Beschreibt das Ziel der symbolischen Verknüpfung. Muss an zweiter Stelle stehen. | |||||||||||
Einige Beispiele:
OPT_TELNETD='yes'
, setze
uid/gid auf root und die Rechte auf 755 (rwxr-xr-x
)
telnetd yes files/usr/sbin/in.telnetd mode=755
r-xr-xr-x
) und konvertiere die
Datei ins Unix-Format bei gleichzeitigem Entfernen aller überflüssigen
Zeichen
base yes etc/rc0.d/rc500.killall mode=555 flags=sh
PCMCIA_PCIC='i82365'
, setze
uid/gid auf root und die Rechte auf 644 (rw-r--r--
)
pcmcia_pcic i82365 files/lib/modules/${KERNEL_VERSION}/pcmcia/i82365.ko
rw-r--r--
)
net_drv_% 3c503 3c503.ko
powermanagement !none etc/rc.d/rc100.pm mode=555 flags=sh
myopta,myoptb yes files/usr/local/bin/myopt-common.sh mode=555 flags=sh
Dieses Beispiel ist letztlich nur eine Kurzschreibweise für:
myopta yes files/usr/local/bin/myopt-common.sh mode=555 flags=sh myoptb yes files/usr/local/bin/myopt-common.sh mode=555 flags=sh
Und letzteres ist eine Kurzschreibweise für:
opt_myopta yes files/usr/local/bin/myopt-common.sh mode=555 flags=sh opt_myoptb yes files/usr/local/bin/myopt-common.sh mode=555 flags=sh
base yes rootfs:files/usr/bin/beep.sh mode=555 flags=sh name=bin/beep
Die Dateien werden nur kopiert, wenn die oben genannten
Bedingungen erfüllt sind und das dazugehörige OPT_PACKAGE='yes'
gesetzt ist. Welche OPT-Variable dazugehört, wird über
die Datei check/<PACKAGE>.txt beschrieben.
Wenn im Paket eine Variable referenziert wird, die nicht vom Paket selbst definiert wird, kann es passieren, dass das entsprechende Paket nicht installiert ist. Das würde zu einer Fehlermeldung in mkfli4l führen, da es erwartet, dass alle von opt/<PACKAGE>.txt referenzierten Variablen definiert sind.
Um diese Situation korrekt handhaben zu können, wurde die "`weak"'-Deklaration eingeführt. Sie hat das folgende Format:
weak <Variable> -
Dadurch wird die Variable definiert, wenn sie nicht bereits vorhanden ist und ihr Wert wird auf "`undefiniert"' gesetzt. Dabei ist jedoch zu beachten, dass hier das "`OPT_"'-Präfix nicht weggelassen werden darf (falls es existiert), weil sonst eine Variable ohne dieses Präfix definiert wird.
Ein Beispiel aus der opt/rrdtool.txt:
weak opt_openvpn - [...] openvpn yes files/usr/lib/collectd/openvpn.so
Ohne die weak-Definition würde mkfli4l bei der Nutzung des Pakets "`rrdtool"' eine Fehlermeldung anzeigen, wenn das "`openvpn"'-Paket nicht ebenfalls vorliegt. Mit Hilfe der weak-Definition kommt auch in dem Fall, dass das "`openvpn"'-Paket nicht vorliegt, keine Fehlermeldung.
In manchen Situationen möchte man originale Dateien im Archiv durch konfigurationsspezifische Dateien wie z.B. Host-Keys, eigene Firewall-Skripte, ... ersetzen. mkfli4l unterstützt dieses Szenario, indem es prüft, ob eine zu kopierende Datei im Konfigurationsverzeichnis zu finden ist und übernimmt in diesem Falle diese Datei in die Liste der ins opt.img bzw. rootfs.img aufzunehmenden Dateien.
Eine weitere Möglichkeit, konfigurationsspezifische Dateien ans Archiv anzuhängen wird im Abschnitt Erweiterte Prüfungen der Konfiguration beschrieben.
net_drv_% ne2k-pci ne2k-pci.ko
dazu, dass sowohl 8390.ko als auch crc32.ko ins Archiv aufgenommen werden, da ne2k_pci von beiden abhängt.
Die notwendigen Einträge in modules.dep und modules.alias werden in das RootFS mit aufgenommen und können von modprobe zum Laden der Treiber genutzt werden.
Mit Hilfe der Datei check/<PACKAGE>.txt können die Inhalte der Variablen auf Gültigkeit überprüft werden. Diese Überprüfung war in früheren Versionen fest im Programm mkfli4l eingebaut, wurde aber im Zuge der Modularisierung von fli4l in die Check-Dateien ausgelagert. In dieser Datei ist für jede Variable aus den Konfigurationsdateien eine Zeile vorhanden. Diese Zeilen bestehen aus vier bis fünf Spalten, welche folgende Funktionen haben:
Oftmals hat man das Problem, dass bestimmte Variablen Optionen beschreiben, die man nur in bestimmten Situationen benötigt. Deshalb können Variablen als optional markiert werden. Optionale Variablen werden mit einem vorangestellten "`+"' gekennzeichnet. Sie können dann da sein, müssen aber nicht. Arrays können auch mit einem "`++"' Präfix versehen werden. Steht ein "`+"' davor, kann das Array da sein oder ganz fehlen. Steht "`++"' davor, können zusätzlich auch noch einzelne Elemente des Arrays fehlen.
Falls eine Variable von keiner OPT-Variablen abhängt, gilt sie als aktiv. Falls sie von einer OPT-Variablen abhängig ist, ist sie genau dann aktiv, wenn
Andernfalls ist die Variable inaktiv.
Hinweis: Inaktive OPT-Variablen werden, wenn sie in der Konfiguration
mit "`yes"' belegt werden, auf den Wert "`no"' zurückgesetzt; dies wird von
mkfli4l auch mit einer entsprechenden Warnmeldung
(bspw. "`OPT_Y='yes' ignored, because OPT_X='no'
"') kommentiert. Bei
transitiven Abhängigkeitsketten (OPT_Z hängt von OPT_Y ab, das
wiederum von OPT_X abhängt) funktioniert dies aber nur dann
zuverlässig, wenn die Namen aller OPT-Variablen mit "`OPT_"' beginnen.
Aus Kompatibilitätsgründen mit zukünftigen fli4l-Versionen muss die hier angegebene Variable identisch sein mit der Variable in OPT_VARIABLE, wobei das letzte "`%"' durch ein "`N"' ersetzt und alles dahinter entfernt wurde. Ein Array HOST_%_IP4 bekommt also zwingend die N-Variable HOST_N zugewiesen und ein Array PF_USR_CHAIN_%_RULE_% also die N-Variable PF_USR_CHAIN_%_RULE_N, und diese N-Variable ist selbst wieder eine Array-Variable mit der zugehörigen N-Variable PF_USR_CHAIN_N. Alle anderen Benennungen der N-Variable werden mit zukünftigen fli4l-Versionen inkompatibel sein!
Name | Bedeutung |
NONE | Es wird keine Überprüfung vorgenommen |
YESNO | Die Variable muss "`yes"' oder "`no"' sein |
NOTEMPTY | Die Variable darf nicht leer sein |
NOBLANK | Die Variable darf kein Leerzeichen enthalten |
NUMERIC | Die Variable muss numerisch sein |
IPADDR | Die Variable muss eine IP-Adresse sein |
DIALMODE | Die Variable muss "`on"', "`off"' oder "`auto"' sein |
Werden die Werte mit einem "`WARN_"'-Präfix versehen, so führt ein illegaler Wert nicht zu einer Fehlermeldung und damit zu einem Abbruch von mkfli4l, sondern nur zur Ausgabe einer Warnung.
Die möglichen Prüfungen werden durch reguläre Ausdrücke in check/base.exp definiert. Diese Datei kann erweitert werden und enhält neuerdings z.B. zusätzlich folgende Prüfungen: HEX, NUMHEX, IP_ROUTE, DISK und PARTITION.
Die Anzahl der Ausdrücke kann jederzeit erweitert werden, hier ist Rückmeldung von den Paket-Entwicklern erforderlich.
Zusätzlich können reguläre Ausdrücke auch direkt in den Check-Dateien angegeben werden, wobei man auch Bezug auf existierende Ausdrücke nehmen kann. Statt YESNO könnte man z.B. auch
RE:yes|noschreiben. Sinnvoll ist es dann, wenn ein Test nur ein einziges Mal ausgeführt wird und relativ einfach ist. Für genauere Informationen siehe nächstes Kapitel.
Hinweis: Dies funktioniert zur Zeit jedoch nicht für Array-Variablen. Auch darf die Variable nicht optional sein, es darf also kein "`+"' vor dem Variablennamen stehen.
Beispiel:
OPT_TELNETD - - YESNO "no"
Fehlt OPT_TELNETD nun in der Konfigurationsdatei, wird "`no"' angenommen und dieser Wert auch in die rc.cfg geschrieben.
Die Sache mit dem Prozentzeichen lässt sich am Besten mit einem Beispiel erklären. Nehmen wir an, in der check/base.txt steht:
NET_DRV_N - - NUMERIC NET_DRV_% - NET_DRV_N NONE NET_DRV_%_OPTION - NET_DRV_N NONE
Das heißt, dass je nach Wert von NET_DRV_N die Variablen NET_DRV_N, NET_DRV_1_OPTION, NET_DRV_2_OPTION, NET_DRV_3_OPTION, usw. überprüft werden.
In der Version 2.0 gab es nur die oben angeführten sieben Werte-Bereiche, auf die Variablen geprüft werden können: NONE, NOTEMPTY, NUMERIC, IPADDR, YESNO, NOBLANK, DIALMODE. Die Überprüfung war in mkfli4l fest eingebaut, nicht erweiterbar und beschränkte sich auf wesentliche "`Datentypen"', die mit vertretbarem Aufwand geprüft werden können.
Mit der Version 2.1 wurde diese Prüfung neu implementiert. Ziel der neuen Implementierung ist eine flexiblere Prüfung der Variablen, die auch in der Lage ist, komplexere Ausdrücke zu prüfen. Deshalb werden reguläre Ausdrücke verwendet, die in einem oder mehreren separaten Dateien abgespeichert werden. Dadurch wird es zum einen möglich, Variablen zu prüfen, die im Augenblick noch nicht geprüft werden, und zum anderen können Entwickler optionaler Pakete eigene Ausdrücke definieren, um die Konfiguration ihrer Pakete prüfen zu lassen.
Eine Beschreibung regulärer Ausdrücke findet man via "`man 7 regex"' oder z.B. hier: http://unixhelp.ed.ac.uk/CGI/man-cgi?regex+7.
Spezifizieren kann man die Ausdrücke auf zwei Wegen:
Diese Datei liegt im check-Verzeichnis und trägt den gleichen Namen wie das dazugehörige Paket, also z.B. check/base.exp. Sie enthält Definitionen für Ausdrücke, die in der Datei check/<PACKAGE>.txt referenziert werden können. So enthält check/base.exp im Augenblick Definitionen für die bekannten Prüfungen und check/isdn.exp eine Definition für die Variable ISDN_CIRC_x_ROUTE (das Fehlen dieser Überprüfung war der Auslöser dieser Änderungen).
Die Syntax lautet wie folgt, wobei man auch hier bei Bedarf doppelte Hochkommata verwenden kann:
<Name> = '<Regulärer Ausdruck>' : '<Fehlermeldung>'oder am Beispiel aus check/base.exp:
NOTEMPTY = '.*[^ ]+.*' : 'should not be empty' YESNO = 'yes|no' : 'only yes or no are allowed' NUMERIC = '0|[1-9][0-9]*' : 'should be numeric (decimal)' OCTET = '1?[0-9]?[0-9]|2[0-4][0-9]|25[0-5]' : 'should be a value between 0 and 255' IPADDR = '((RE:OCTET)\.){3}(RE:OCTET)' : 'invalid ipv4 address' EIPADDR = '()|(RE:IPADDR)' : 'should be empty or contain a valid ipv4 address' NOBLANK = '[^ ]+' : 'should not contain spaces' DIALMODE = 'auto|manual|off' : 'only auto, manual or off are allowed' NETWORKS = '(RE:NETWORK)([[:space:]]+(RE:NETWORK))*' : 'no valid network specification, should be one or more network address(es) followed by a netmask, for instance 192.168.6.0/24'
In den regulären Ausdrücken können auch Referenzen auf bereits existierende Definitionen enthalten sein. Diese werden dann einfach an der Stelle eingefügt. Dadurch ist es einfacher, reguläre Ausdrücke zu konstruieren. Eingefügt werden die Referenzen einfach durch '(RE:Referenz)'. (Siehe die Definition des Ausdrucks NETWORKS oben für ein entsprechendes Beispiel.)
Die Fehlermeldungen tendieren dazu, zu lang zu werden. Daher besteht die Möglichkeit, sie über mehrere Zeilen zu verteilen. Die folgenden Zeilen müssen dann immer mit einem Leerzeichen oder Tabulator beginnen. Beim Einlesen der check/<PACKAGE>.exp-Datei werden überflüssige Leerzeichen auf eins reduziert und Tabulatoren durch Leerzeichen ersetzt. Ein Eintrag in der check/<PACKAGE>.exp könnte dann so aussehen:
NUM_HEX = '0x[[:xdigit:]]+' : 'should be a hexadecimal number (a number starting with "0x")'
Manche Ausdrücke kommen nur einmal vor, dann lohnt es sich nicht, dafür einen regulären Ausdruck in einer check/<PACKAGE>.exp-Datei zu definieren. Dann kann man diesen Ausdruck einfach in die Check-Datei schreiben, z.B.:
# Variable OPT_VARIABLE VARIABLE_N VALUE MOUNT_BOOT - - RE:ro|rw|no
MOUNT_BOOT kann lediglich die Werte "`ro"', "`rw"' oder "`no"' annehmen, alles andere wird abgelehnt.
Will man Bezug auf existierende reguläre Ausdrücke nehmen, fügt man einfach eine Referenz via "`(RE:...)"' ein. Beispiel:
# Variable OPT_VARIABLE VARIABLE_N VALUE LOGIP_LOGDIR OPT_LOGIP - RE:(RE:ABS_PATH)|auto
Fügt ein optionales Paket einen zusätzlichen Wert für eine Variable hinzu, die von einem regulären Ausdruck geprüft wird, muss der reguläre Ausdruck erweitert werden. Dies geschieht einfach durch Definition der neuen möglichen Werte durch einen regulären Ausdruck (wie oben beschrieben) und Ergänzung des bestehenden regulären Ausdrucks in einer eigenen check/<PACKAGE>.exp-Datei. Dass ein bestehender Ausdruck modifiziert werden soll, kennzeichnet ein führendes "`+"'. Der neue Ausdruck ergänzt den bestehenden Ausdruck, indem der neue Wert als Alternative an den bestehenden Wert angehängt wird. Verwendet ein anderer Ausdruck den ergänzten Ausdruck, gilt auch dort die Ergänzung. Die angegebene Fehlermeldung wird einfach an die vorhandene hinten angehängt.
Am Beispiel der Ethernet-Treiber könnte das wie folgt aussehen:
NET_DRV = '3c503|3c505|3c507|...' : 'invalid ethernet driver, please choose one' ' of the drivers in config/base.txt'
PCMCIA_NET_DRV = 'pcnet_cs|xirc2ps_cs|3c574_cs|...' : '' +NET_DRV = '(RE:PCMCIA_NET_DRV)' : ''
Nun kann man zusätzlich auch noch PCMCIA-Treiber auswählen.
Wenn man NET_DRV wie oben um die PCMCIA-Treiber erweitert hat, aber das Paket "`pcmcia"' deaktiviert hat, könnte man dennoch einen PCMCIA-Treiber in der config/base.txt auswählen, ohne dass eine Fehlermeldung beim Erstellen der Archive auftritt. Um das zu verhindern, kann man den regulären Ausdruck auch abhängig von einer YESNO-Variablen in der Konfiguration erweitern. Dazu wird der Name der Variablen, die bestimmt ob der Ausdruck erweitert wird, mit runden Klammern direkt hinter den Namen des Ausdrucks gehängt. Ist die Variable aktiv und hat den Wert "`yes"', wird der Ausdruck erweitert, sonst nicht.
PCMCIA_NET_DRV = 'pcnet_cs|xirc2ps_cs|3c574_cs|...' : '' +NET_DRV(OPT_PCMCIA) = '(RE:PCMCIA_NET_DRV)' : ''
Wird jetzt OPT_PCMCIA='no'
gesetzt, und in der config/base.txt wird z.B. der
PCMCIA-Treiber xirc2ps_cs benutzt, gibt es beim Erstellen der Archive eine
Fehlermeldung.
Hinweis: Dies funktioniert nicht, wenn die Variable nicht explizit in der Konfigurationsdatei gesetzt wird, sondern ihren Wert über eine Standard-Belegung in der check/<PACKAGE>.txt erhält. In diesem Fall muss man also in der Konfigurationsdatei die Variable explizit setzen und ggf. auf die Standard-Belegung verzichten.
Alternativ kann man auch beliebige Werte von Variablen als Bedingung verwenden, die Syntax sieht dann wie folgt aus:
+NET_DRV(KERNEL_VERSION=~'^3\.16\..*$') = ...
Wenn KERNEL_VERSION zu dem angegebenen regulären Ausdruck passt, also irgendein Kernel aus der 3.16er Versionsreihe genutzt wird, dann wird die Liste der erlaubten Netzwerktreiber um die angegebenen Treiber ergänzt.
Hinweis: Dies funktioniert nicht, wenn die Variable nicht explizit in der Konfigurationsdatei gesetzt wird, sondern ihren Wert über eine Standard-Belegung in der check/<PACKAGE>.txt erhält. In diesem Fall muss man also in der Konfigurationsdatei die Variable explizit setzen und ggf. auf die Standard-Belegung verzichten.
Findet die Prüfung einen Fehler, erscheint eine Fehlermeldung der folgenden Art:
Error: wrong value of variable HOSTNAME: '' (may not be empty) Error: wrong value of variable MOUNT_OPT: 'rx' (user supplied regular expression)
Beim ersten Fehler wurde der Ausdruck in einer check/<PACKAGE>.exp-Datei definiert und ein Hinweis auf den Fehler wird mit ausgegeben. Im zweiten Falle wurde der Ausdruck direkt in einer check/<PACKAGE>.txt-Datei spezifiziert, deshalb gibt es keinen zusätzlichen Hinweis auf die Fehlerursache.
Reguläre Ausdrücke sind wie folgt definiert:
Regulärer Ausdruck: Eine oder mehrere Alternativen, getrennt durch '|', z.B. "`ro|rw|no"'. Trifft eine der Alternativen zu, trifft der ganze Ausdruck zu (hier wären "`ro"', "`rw"' und "`no"' gültige Ausdrücke).
Eine Alternative ist eine Verkettung mehrerer Teilstücke, die einfach aneinandergereiht werden.
Ein Teilstück ist ein "`Atom"', gefolgt von einem einzelnen "`*"', "`+"', "`?"' oder "`{min, max}"'. Die Bedeutung ist wie folgt:
Ein "`Atom"' ist ein
Ein Ausdruck in rechteckigen Klammern bedeutet Folgendes:
Sehen wir uns das mal an einigen Beispielen an!
NUMERIC: Ein numerischer Wert besteht aus mindestens einer, aber ansonsten beliebig vielen Ziffern. "`Mindestens ein"' drückt man mit "`+"' aus, eine Ziffer hatten wir schon als Beispiel. Zusammengesetzt ergibt das:
NUMERIC = '[0-9]+'oder alternativ
NUMERIC = '[[:digit:]]+'
NOBLANK: Ein Wert, der keine Leerzeichen enthält, ist ein beliebiges Zeichen (außer dem Leerzeichen) und davon beliebig viele:
NOBLANK = '[^ ]*'
bzw. wenn der Wert zusätzlich auch nicht leer sein darf:
NOBLANK = '[^ ]+'
IPADDR: Sehen wir uns das Ganze nochmal am Beispiel der IPv4-Addresse an. Eine
IPv4-Adresse besteht aus vier "`Octets"', die durch einen Punkt ("`."') voneinander getrennt sind. Ein
Octet kann eine Zahl zwischen 0 und 255 sein. Definieren wir als
erstes ein Octet. Es kann
eine Zahl zwischen 0 und 9 sein: | [0-9] |
eine Zahl zwischen 10 und 99: | [1-9][0-9] |
eine Zahl zwischen 100 und 199: | 1[0-9][0-9] |
eine Zahl zwischen 200 und 249: | 2[0-4][0-9] |
eine Zahl zwischen 250 und 255 sein: | 25[0-5] |
Das Ganze sind Alternativen, also fassen wir sie einfach mittels "`|"' zu einem Ausdruck zusammen: "`[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]"' und haben damit ein Octet. Daraus können wir nun eine IPv4-Adresse machen, vier Octets mit Punkten voneinander getrennt (der Punkt muss mittels eines Backslashs maskiert werden, da er sonst für ein beliebiges Zeichen steht). Basierend auf der Syntax der exp-Dateien sieht das Ganze dann wie folgt aus:
OCTET = '[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]' IPADDR = '((RE:OCTET)\.){3}(RE:OCTET)'
Will man reguläre Ausdrücke entwerfen und testen, kann man dazu das "`regexp"'-Programm verwenden, das sich in dem Verzeichnis unix bzw. windows des Pakets "`base"' befindet. Es akzeptiert die folgende Syntax:
usage: regexp [-c <check dir>] <regexp> <string>
Dabei bedeuten die Parameter Folgendes:
'...'
oder "..."
angeben, wobei doppelte Anführungsstriche nur nötig sind, wenn
einfache Hochkommata in dem Ausdruck vorkommen sollen)
Das könnte z.B. wie folgt aussehen:
./i586-linux-regexp -c ../check '[0-9]' 0 adding user defined regular expression='[0-9]' ('^([0-9])$') checking '0' against regexp '[0-9]' ('^([0-9])$') '[0-9]' matches '0' ./i586-linux-regexp -c ../check '[0-9]' a adding user defined regular expression='[0-9]' ('^([0-9])$') checking 'a' against regexp '[0-9]' ('^([0-9])$') regex error 1 (No match) for value 'a' and regexp '[0-9]' ('^([0-9])$') ./i586-linux-regexp -c ../check IPADDR 192.168.0.1 using predefined regular expression from base.exp adding IPADDR='((RE:OCTET)\.){3}(RE:OCTET)' ('^(((1?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.){3}(1?[0-9]?[0-9]|2[0-4][0-9]|25[0-5]))$') 'IPADDR' matches '192.168.0.1' ./i586-linux-regexp -c ../check IPADDR 192.168.0.256 using predefined regular expression from base.exp adding IPADDR='((RE:OCTET)\.){3}(RE:OCTET)' ('^(((1?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.){3}(1?[0-9]?[0-9]|2[0-4][0-9]|25[0-5]))$') regex error 1 (No match) for value '192.168.0.256' and regexp '((RE:OCTET)\.){3}(RE:OCTET)' (unknown:-1) wrong value of variable cmd_var: '192.168.0.256' (invalid ipv4 address)
Manchmal ist es notwendig, komplexere Überprüfungen durchzuführen. Beispiele für solche komplexeren Dinge wären z.B. Abhängigkeiten zwischen Paketen oder Bedingungen, die nur erfüllt sein müssen, wenn Variablen bestimmte Werte annehmen. So muss z.B. bei Auswahl eines PCMCIA-ISDN-Adapters auch das Paket "`pcmcia"' installiert werden.
Um diese Überprüfungen durchführen zu können, kann man in check/<PACKAGE>.ext (auch ext-Skript genannt) kleinere Tests schreiben. Die Sprache besteht aus folgenden Elementen:
~
, copy_pending, samenet, subnet
Zu den Datentypen ist zu sagen, dass Variablen auf Grund des zugehörigen regulären Ausdrucks fest einem Datentyp zugeordnet werden:
Das bedeutet unter anderem, dass eine Variable vom Typ ENUMERIC nicht als Index beim Zugriff auf eine Array-Variable benutzt werden kann, auch wenn man sich vorher vergewissert hat, dass sie nicht leer ist. Der folgende Code funktioniert somit nicht:
# sei TEST eine Variable vom Typ ENUMERIC if (test != "") then # Fehler: You can't use a non-numeric ID in a numeric # context. Check type of operand. set i=my_array[test] # Fehler: You can't use a non-numeric ID in a numeric # context. Check type of operand. set j=test+2 fi
Eine Lösung für dieses Problem bietet split:
if (test != "") then # alle Elemente von test_% sind numerisch split(test, test_%, ' ', numeric) # OK set i=my_array[test_%[1]] # OK set j=test_%[1]+2 fi
An verschiedenen Stellen werden Zeichenketten benötigt, etwa wenn eine Warnung ausgegeben werden soll. In einigen Fällen, die in dieser Dokumentation beschrieben werden, wird eine solche Zeichenkette dabei nach Variablen durchsucht; werden welche gefunden, werden diese durch ihren Inhalt oder andere Attribute ersetzt. Diese Ersetzung wird Variablenersetzung genannt.
Dies soll an einem Beispiel verdeutlicht werden. Es gelte die Konfiguration:
# config/base.txt HOSTNAME='fli4l' # config/dns_dhcp.txt HOST_N='1' # Anzahl der Hosts HOST_1_NAME='client' HOST_1_IP4='192.168.1.1'
Dann werden die Zeichenketten wie folgt umgeschrieben, wenn die Variablenersetzung in dem jeweiligen Kontext aktiv ist:
"Mein Router heißt $HOSTNAME" # --> "Mein Router heißt fli4l" "HOSTNAME ist Teil des Pakets %{HOSTNAME}" # --> "HOSTNAME ist Teil des Pakets base" "@HOST_N ist $HOST_N" # --> " # Anzahl der Hosts ist 1"
Wie man sehen kann, gibt es prinzipiell drei Möglichkeiten der Ersetzung:
Will man ein "`$"', "`@"' oder "`%"' im Text haben, schreibt man "`$$"', "`@@"' bzw. "`%%"'.
Hinweis: Elemente von Array-Variablen können auf diese Weise nicht in Zeichenketten integriert werden, weil es keine Möglichkeit gibt, einen Index anzugeben.
Generell unterliegen nur Konstanten der Variablenersetzung; Zeichenketten, die über eine Variable hereinkommen, bleiben unverändert. Ein Beispiel soll dies verdeutlichen - es sei die folgende Konfiguration gegeben:
HOSTNAME='fli4l' TEST='${HOSTNAME}'
Dann führt der Code:
warning "${TEST}"
zur Ausgabe von:
Warning: ${HOSTNAME}
und nicht zur Ausgabe von:
Warning: fli4l
In den folgenden Abschnitten wird explizit darauf hingewiesen, unter welchen Umständen Zeichenketten der Variablenersetzung unterliegen.
Damit kann z.B. ein OPT deklarieren, dass es einen Drucker-Dienst oder einen Webserver-Dienst bereitstellt. Es kann jeweils nur ein einziges Paket geben, dass einen Dienst bereitstellt. Damit kann man verhindern, dass z.B. zwei Webserver parallel installiert werden, was naheliegenderweise nicht gehen würde, da sich die beiden Server um den Port 80 streiten würden. Zusätzlich wird die aktuelle Version des Dienstes angegeben, so dass Weiterentwicklungen Rechnung getragen werden kann. Die Versionsnummer besteht aus zwei- oder drei Zahlen, die durch Punkte voneinander getrennt sind, etwa "`4.0"' oder "`2.1.23"'.
Typischerweise werden Dienste auf OPTs, nicht auf ganze Pakete abgebildet.
So besitzt etwa das Paket "`tools"' eine ganze Reihe von Programmen, die
jeweils ihre eigene provides-Anweisung definieren, so sie denn
via OPT_...='yes'
aktiviert sind.
Die Syntax lautet:
provides <Name> version <Version>
Beispiel aus dem Paket "`easycron"':
provides cron version 3.10.0
Die Versionsnummer sollte vom OPT-Entwickler in der dritten Komponente angehoben werden, wenn lediglich Funktionserweiterungen vorgenommen wurden und die Schnittstelle zum OPT kompatibel geblieben ist. Die Versionsnummer sollte in der ersten oder zweiten Komponente angehoben werden, wenn sich die Schnittstelle in irgendeiner Weise inkompatibel verändert hat (z.B. auf Grund von Variablenumbenennungen, Pfad-Änderungen, verschwundenen oder umbenannten Dienstprogrammen etc.).
Benötigt man zur Erbringung der eigenen Funktionalität einen anderen Dienst (z.B. einen Webserver), kann man hiermit diese Abhängigkeit zu einem Dienst mit einer bestimmten Version spezifizieren. Die Version kann zweistellig (z.B. "`2.1"') oder dreistellig (z.B. "`2.1.11"') angegeben werden, wobei die zweistellige Variante alle Versionen akzeptiert, die ebenfalls so beginnen, während die dreistellige Version nur genau diese angegebene Version akzeptiert. Des Weiteren kann eine Liste von solchen Versionsnummern angegeben werden, falls mehrere Versionen des Dienstes kompatibel mit dem Paket sind.
Die Syntax lautet:
depends on <Name> version <Version>+
Ein Beispiel: Paket "`server"' enthalte:
provides server version 1.0.1
Sei ein Paket "`client"' gegeben. Darin seien folgende depends-Anweisungen beispielhaft enthalten:8.2
depends on server version 1.0 # OK, '1.0' passt zu '1.0.1' depends on server version 1.0.1 # OK, '1.0.1' passt zu '1.0.1' depends on server version 1.0.2 # Fehler, '1.0.2' passt nicht zu '1.0.1' depends on server version 1.1 # Fehler, '1.1' passt nicht zu '1.0.1' depends on server version 1.0 1.1 # OK, '1.0' passt zu '1.0.1' depends on server version 1.0.2 1.1 # Fehler, weder '1.0.2' noch '1.1' passen # zu '1.0.1'
Mit Hilfe dieser drei Funktionen kann man Nutzer warnen, einen Fehler signalisieren oder die Prüfung sofort abbrechen. Die Syntax sieht wie folgt aus:
warning "text"
error "text"
fatal_error "text"
Alle an diese Funktionen übergebenen Zeichenketten-Konstanten unterliegen der Variablenersetzung.
Benötigt man aus irgendeinem Grund eine temporäre Variable, kann man diese einfach mit "`set var [= value]"' anlegen. Die Variable darf kein Konfigurationsvariable sein!8.3 Lässt man den "`= value"' Teil weg, wird die Variable einfach auf "`yes"' gesetzt, so dass man sie hinterher einfach in einer if-Anweisung testen kann. Wird ein Zuweisungsteil angegeben, kann hinter dem Gleichheitszeichen alles stehen: normale Variablen, indizierte Variablen, Zahlen, Zeichenketten, Versionsnummern.
Zu beachten ist, dass durch diese Zuweisung gleichzeitig der Typ der temporären Variablen festgelegt wird. Wird eine Zahl zugewiesen, "`merkt"' mkfli4l sich, dass diese Variable eine Zahl enthält, und erlaubt später das Rechnen damit. Versucht man, mit einer anders getypten Variable zu rechnen, wird dies fehlschlagen. Beispiel:
set i=1 # OK, i ist eine numerische Variable set j=i+1 # OK, j ist eine numerische Variable und enthält den Wert 2 set i="1" # OK, i ist nun eine Zeichenketten-Variable set j=i+1 # Fehler "You can't use a non-numeric ID in a numeric # context. Check type of operand." # --> mit Zeichenketten kann man nicht rechnen!
Man kann auch temporäre Arrays (siehe unten) anlegen. Beispiel:
set prim_%[1]=2 set prim_%[2]=3 set prim_%[3]=5 warning "${prim_n}"
Dabei wird die Anzahl der Elemente in dem Array in der Variable prim_n von mkfli4l verwaltet. Der obige Code führt somit zu folgender Ausgabe:
Warning: 3
Wenn auf der rechten Seite einer Zuweisung eine Zeichenketten-Konstante steht, unterliegt sie zum Zeitpunkt der Zuweisung der Variablenersetzung. Dies wird im folgenden Beispiel demonstriert. Der Code:
set s="a" set v1="$s" # v1="a" set s="b" set v2="$s" # v2="b" if (v1 == v2) then warning "gleich" else warning "ungleich" fi
produziert die Ausgabe "`ungleich"', weil die Variablen v1 und v2 bereits während der Zuweisung den aktuellen Inhalt der Variablen s ersetzen.
Hinweis: Eine in einem Skript gesetzte Variable ist bei der Abarbeitung weiterer Skripte sichtbar - es existiert zur Zeit kein Lokalitätsprinzip für derart eingeführte Variablen. Da die Reihenfolge, in der die Skripte verschiedener Pakete abgearbeitet wird, nicht definiert ist, sollte man sich nie darauf verlassen, dass Variablen irgendwelche Werte besitzen bzw. von einem anderen Paket übernommen haben.
Will man auf einzelne Elemente einer %-Variablen (eines Arrays) zugreifen, muss man den Original-Namen der Variable, wie er in der check/<PACKAGE>.txt-Datei steht, verwenden, und dabei für jedes "`%"'-Zeichen einen Index mit Hilfe von "`[Index]"' anhängen.
Beispiel: Will man auf die Elemente der Variable PF_USR_CHAIN_%_RULE_% zugreifen, benötigt man zwei Indizes, weil die Variable zwei "`%"'-Zeichen besitzt. Alle Elemente ausgeben kann man z.B. mit Hilfe des folgenden Codes (die foreach-Schleife wird weiter unten erläutert):
foreach i in pf_usr_chain_n do # nur ein Index nötig, da nur ein '%' im Variablennamen set j_n=pf_usr_chain_%_rule_n[i] # Achtung: ein # foreach j in pf_usr_chain_%_rule_n[i] # ist leider nicht möglich, deshalb der Umweg über j_n! foreach j in j_n do # zwei Indizes nötig, da zwei '%' im Variablennamen set rule=pf_usr_chain_%_rule_%[i][j] warning "Rule $i/$j: ${rule}" done done
Mit der folgenden Beispiel-Konfiguration
PF_USR_CHAIN_N='2' PF_USR_CHAIN_1_NAME='usr-chain_a' PF_USR_CHAIN_1_RULE_N='2' PF_USR_CHAIN_1_RULE_1='ACCEPT' PF_USR_CHAIN_1_RULE_2='REJECT' PF_USR_CHAIN_2_NAME='usr-chain_b' PF_USR_CHAIN_2_RULE_N='1' PF_USR_CHAIN_2_RULE_1='DROP'
gibt es dann die folgenden Ausgaben:
Warning: Rule 1/1: ACCEPT Warning: Rule 1/2: REJECT Warning: Rule 2/1: DROP
Alternativ kann man direkt über alle Werte des Arrays iterieren, kennt dann allerdings nicht die exakten Indizes der Einträge (was auch nicht immer erforderlich ist):
foreach rule in pf_usr_chain_%_rule_% do warning "Rule %{rule}='${rule}'" done
Das produziert mit der Beispiel-Konfiguration von oben die folgenden Ausgaben:
Warning: Rule PF_USR_CHAIN_1_RULE_1='ACCEPT' Warning: Rule PF_USR_CHAIN_1_RULE_2='REJECT' Warning: Rule PF_USR_CHAIN_2_RULE_1='DROP'
An dem zweiten Beispiel sieht man auch schön die Bedeutung der %<Name>-Syntax: Innerhalb der Zeichenkette wird %rule durch den Namen der betrachteten Variable ersetzt (also z.B. PF_USR_CHAIN_1_RULE_1), während $rule durch dessen Inhalt (also z.B. ACCEPT) ersetzt wird.
Einige Variablen enthalten Passwörter, die nicht im Klartext in der rc.cfg stehen sollen. Diese Variablen können mittels crypt verschlüsselt werden und werden damit in das Format überführt, dass auch auf dem Router benötigt wird. Verwendet wird das wie folgt:
crypt (<Variable>)
Die crypt-Funktion ist die einzige Stelle, an der eine Konfigurationsvariable verändert werden kann.
stat ermöglicht es, Eigenschaften einer Datei abzufragen. Zur Verfügung gestellt wird im Augenblick lediglich die Größe einer Datei. Wenn man auf Dateien unterhalb des aktuellen Konfigurationsverzeichnisses testen will, kann man die interne Variable config_dir benutzen. Die Syntax lautet:
stat (<Dateiname>, <Schlüssel>)
Der Aufruf sieht wie folgt aus (wobei die verwendeten Parameter nur Beispiele sind):
foreach i in openvpn_%_secret do stat("${config_dir}/etc/openvpn/$i.secret", keyfile) if (keyfile_res != "OK") then error "OpenVPN: missing secretfile <config>/etc/openvpn/$i.secret" fi done
In dem Beispiel wird geprüft, ob eine Datei im aktuellen
Konfigurationsverzeichnis vorhanden ist. Wenn also OPENVPN_1_SECRET='test'
in der Konfigurationsdatei gesetzt wird, prüft die Schleife im ersten
Durchlauf, ob im aktuellen Konfigurationsverzeichnis die Datei
etc/openvpn/test.secret vorhanden ist.
Nach dem Aufruf sind zwei Variablen definiert:
Das könnte dann z.B. so aussehen:
stat ("unix/Makefile", test) if ("$test_res" == "OK") then warning "test_size = $test_size" else error "Error '$test_res' while trying to get size of 'unix/Makefile'" fi
Ein als Zeichenketten-Konstante übergebener Dateiname unterliegt der Variablenersetzung.
Wenn Sie in einer Datei per "`grep"'8.4 suchen wollen, steht Ihnen das fgrep-Kommando zur Verfügung. Die Syntax lautet:
fgrep (<Dateiname>, <RegEx>)
Wenn die Datei <Dateiname> nicht existiert wird mkfli4l mit einem fatalen Fehler beendet! Wenn Sie also nicht sicher sind, ob die Datei immer vorhanden ist, testen Sie die Existenz von <Dateiname> vorher mit stat ab. Nach dem Aufruf von fgrep steht Ihnen das Suchresultat in dem Array FGREP_MATCH_% zur Verfügung, wobei der Index x wie üblich von eins bis FGREP_MATCH_N reicht. FGREP_MATCH_1 verweist dabei auf den gesamten Bereich der Zeile, auf den der reguläre Ausdruck gepasst hat, während FGREP_MATCH_2 bis FGREP_MATCH_N den jeweils n-1-ten geklammerten Teil beinhalten.
Ein erstes einfaches Beispiel soll die Verwendung demonstrieren. Die Datei opt/etc/shells enthält die Zeile:
/bin/sh
Der folgende Code
fgrep("opt/etc/shells", "^/(.)(.*)/") foreach v in FGREP_MATCH_% do warning "%v='$v'" done
produziert die folgende Ausgabe:
Warning: FGREP_MATCH_1='/bin/' Warning: FGREP_MATCH_2='b' Warning: FGREP_MATCH_3='in'
Der reguläre Ausdruck hat (nur) auf "`/bin/"' gepasst, deshalb steht auch (nur) dieser Teil der Zeile in der Variable FGREP_MATCH_1. Der erste geklammerte Teil im Ausdruck passt auf das erste Zeichen hinter dem ersten "`/"', deshalb steht auch nur "`b"' in FGREP_MATCH_2. Der zweite geklammerte Teil umfasst den Rest hinter den "`b"' bis zum letzten "`/"', somit steht "`in"' in der Variable FGREP_MATCH_3.
Das folgende zweite Beispiel demonstriert eine praxisnahe Verwendung von fgrep an einem Beispiel aus der check/base.ext. Hier wird getestet, ob alle in der PF_FORWARD_x angegebenen tmpl:-Referenzen vorhanden sind:
foreach n in pf_forward_n do set rule=pf_forward_%[n] if (rule =~ "tmpl:([^[:space:]]+)") then foreach m in match_% do stat("$config_dir/etc/fwrules.tmpl/$m", tmplfile) if(tmplfile_res == "OK") then add_to_opt "etc/fwrules.tmpl/$m" else stat("opt/etc/fwrules.tmpl/$m", tmplfile) if(tmplfile_res == "OK") then add_to_opt "etc/fwrules.tmpl/$m" else fgrep("opt/etc/fwrules.tmpl/templates", "^$m[[:space:]]+") if (fgrep_match_n == 0) then error "Can't find tmpl:$m for PF_FORWARD_${n}='$rule'!" fi fi fi done fi done
Sowohl ein als Zeichenketten-Konstante übergebener Dateiname als auch als Zeichenketten-Konstante übergebener regulärer Ausdruck unterliegen der Variablenersetzung.
Oftmals werden Variablen mit mehreren Parametern belegt, die dann in Startup-Skripten erst wieder auseinandergenommen werden. Will man diese bereits vorher auseinandernehmen und Tests auf ihnen durchführen, nimmt man split. Die Syntax lautet:
split (<Zeichenkette>, <Array>, <Trennzeichen>)
Die Zeichenkette kann durch eine Variable oder direkt als Konstante angegeben werden. mkfli4l zerlegt ihn an den Stellen, an denen das Trennzeichen auftaucht, und erzeugt pro Teil ein Element des Arrays. Über diese Elemente kann man dann hinterher iterieren und Prüfungen vornehmen. Steht zwischen zwei Trennzeichen nichts, wird ein Array-Element mit einer leeren Zeichenkette als Wert erzeugt. Ausnahme ist "` "': Hier werden alle Leerzeichen konsumiert und keine leeren Variablen erzeugt.
Sollen die bei der Zerlegung entstandenen Elemente in einem numerischen Kontext verwendet werden (z.B. als Indizes), muss das beim Aufruf von split spezifiert werden. Das geschieht durch das zusätzliche Attribut "`numeric"'. Der Aufruf sieht dann wie folgt aus:
split (<Zeichenkette>, <Array>, <Trennzeichen>, numeric)
Ein Beispiel:
set bar="1.2.3.4" split (bar, tmp_%, '.', numeric) foreach i in tmp_% do warning "%i = $i" done
Die produzierte Ausgabe ist:
Warning: TMP_1 = 1 Warning: TMP_2 = 2 Warning: TMP_3 = 3 Warning: TMP_4 = 4
Hinweis: Wenn die "`numeric"'-Variante verwendet wird, dann prüft mkfli4l zum Zeitpunkt der Zerlegung nicht, ob die Teil-Zeichenketten auch wirklich numerisch sind! Bei einer späteren Verwendung in einem numerischen Kontext (etwa beim Addieren) löst mkfli4l jedoch einen fatalen Fehler aus, wenn eine solche Variable doch nicht numerisch ist. Beispiel:
set bar="a.b.c.d" split (bar, tmp_%, '.', numeric) # Fehler: invalid number 'a' set i=tmp_%[1]+1
Eine an split im ersten Parameter übergebene Zeichenketten-Konstante unterliegt der Variablenersetzung.
Mit der Funktion add_to_opt können zusätzliche Dateien ans Opt- oder RootFS-Archiv angehängt werden. Es können dabei alle Dateien unterhalb von opt/ oder aus dem Konfigurationsverzeichnis ausgewählt werden. Eine Beschränkung nur auf die Dateien, die mit einem bestimmten Paket geliefert werden, gibt es nicht. Liegt eine Datei sowohl unter opt/ als auch im Konfigurationsverzeichnis im gleichen Pfad, bevorzugt add_to_opt die Dateien aus dem Konfigurationsverzeichnis. Die Funktion add_to_opt wird in der Regel dann eingesetzt, wenn komplexe logische Regeln darüber entscheiden, ob und welche Dateien in das Archiv aufgenommen werden müssen.
Die Syntax sieht wie folgt aus:
add_to_opt <Datei> [<Flags>]
Die Flags sind optional. Es gelten die in Tabelle 8.2 aufgeführten Standard-Werte, falls keine Flags angegeben sind.
Es folgt ein Beispiel aus dem Paket "`sshd"':
if (opt_sshd) then foreach pkf in sshd_public_keyfile_% do stat("$config_dir/etc/ssh/$pkf", publickeyfile) if(publickeyfile_res == "OK") then add_to_opt "etc/ssh/$pkf" "mode=400 flags=utxt" else error "sshd: missing public keyfile %pkf=$pkf" fi done fi
Mit stat wird zunächst geprüft, ob die Datei im Konfigurationsverzeichnis existiert. Ist die Datei vorhanden, wird sie ans Archiv angehängt, andernfalls bricht mkfli4l mit einer entsprechenden Fehlermeldung ab.
Hinweis: Auch bei add_to_opt prüft mkfli4l zuerst, ob die zu kopierende Datei im Konfigurationsverzeichnis zu finden ist.
Sowohl ein als Zeichenketten-Konstante übergebener Dateiname als auch als Zeichenketten-Konstante übergebene Flags unterliegen der Variablenersetzung.
if (expr) then statement else statement fi
Eine klassische Fallunterscheidung, wie man sie kennt. Ist die Bedingung wahr, wird der then-Teil ausgeführt, ist die Bedingung falsch, wird der else-Teil ausgeführt.
Will man Tests über Array-Variablen durchführen, muss man jede einzelne Variable testen. Dazu gibt es die foreach-Schleife in zwei Varianten.
foreach <Laufvariable> in <Array-Variable> do <Anweisung> done foreach <Laufvariable> in <Array-Variable-1> <Array-Variable-2> ... do <Anweisung> done
Diese Schleife iteriert über alle angegebenen Array-Variablen, jeweils angefangen beim ersten Element bis hin zum letzten; die Anzahl der Elemente im Array wird dabei der dem Array zugeordneten N-Variable entnommen. Die Laufvariable nimmt dabei die jeweiligen Werte der Array-Variablen an. Zu beachten ist dabei, dass bei optionalen Array-Variablen, die in der Konfiguration nicht vorhanden sind, ein leeres Element generiert wird. Unter Umständen muss das im Skript berücksichtigt werden, was man z.B. wie folgt tun kann:
foreach i in template_var_opt_% do if (i != "") then warning "%i is present (%i='$i')" else warning "%i is undefined (empty)" fi done
Wie man auch am Beispiel erkennen kann, lässt sich der Name der jeweiligen Array-Variablen durch die %<Laufvariable>-Konstruktion ermitteln.
Die Anweisung in der Schleife kann eine der oben beschriebenen Kontrollelemente oder Funktionen (if, foreach, provides, depends, ...) sein.
Will man auf genau ein Element eines Arrays zugreifen, kann man dieses mittels der Syntax <Array>[<Index>] ansprechen. Der Index kann dabei eine normale Variable, eine Zahlenkonstante oder wiederum ein indiziertes Array sein.
foreach <Laufvariable> in <N-Variable> do <Anweisung> done
Diese Schleife läuft von 1 bis zum Wert, der in der N-Variable steht. Man kann die Laufvariable dazu benutzen, um Array-Variablen zu indizieren. Will man also nicht nur über eine Array-Variable iterieren, sondern über mehrere gleichzeitig, die alle durch dieselben N-Variable kontrolliert werden, nimmt man diese Variante der Schleife und verwendet die Laufvariable zum Indizieren mehrerer Array-Variablen. Beispiel:
foreach i in host_n do set name=host_%_name[i] set ip4=host_%_ip4[i] warning "$i: name=$name ip4=$ip4" done
Das ergibt bei entsprechend gefüllten HOST_%_NAME- und HOST_%_IP4-Arrays beispielsweise:
Warning: 1: name=berry ip4=192.168.11.226 Warning: 2: name=fence ip4=192.168.11.254 Warning: 3: name=sandbox ip4=192.168.12.254
Ausdrücke verknüpfen Werte und Operatoren zu einem neuen Wert. Ein Wert kann dabei eine gewöhnliche Variable, ein Array-Element oder eine Konstante (Zahl, Zeichenkette oder Versionsnummer) sein. Alle Zeichenketten-Konstanten, die in Ausdrücken auftreten, unterliegen der Variablenersetzung.
Operatoren erlauben so gut wie alles, was man von einer Programmiersprache gewöhnt ist. Ein Test auf die Gleichheit zweier Variablen könnte also so aussehen:
var1 == var2 "$var1" == "$var2"
Zu beachten ist dabei, dass der Vergleich in Abhängigkeit vom Typ der Variable erfolgt, der in check/<PACKAGE>.txt festgelegt wurde. Ist eine der beiden Variablen numerisch, erfolgt der Vergleich auf numerischer Basis, d.h. die Zeichenketten werden in Zahlen umgewandelt und dann verglichen. Sonst erfolgt der Vergleich auf Zeichenketten-Basis; ein Vergleich "05" == "5" ergibt "`falsch"', ein Vergleich "18" < "9" ergibt "`wahr"' auf Grund der lexikographischen Ordnung auf Zeichenketten: die Ziffer "`1"' liegt vor der Ziffer "`9"' im zugrunde liegenden ASCII-Zeichensatz.
Für den Vergleich von Versionen wird das Hilfskonstrukt numeric(version) eingeführt, welches den numerischen Wert für eine Versionsnummer für Vergleichszwecke bestimmt. Dabei gilt:
numeric(version) := major * 10000 + minor * 1000 + sub
wobei "`major"' die erste Komponente der Versionsnummer darstellt, "`minor"' die zweite und "`sub"' die dritte; fehlt "`sub"', entfällt der Term in der obigen Summe einfach (oder anders ausgedrückt, für "`sub"' wird null angenommen).
Eine vollständige Auflistung aller Ausdrücke ist in Tabelle 8.3 zu finden. Dabei steht "`val"' für einen beliebig getypten Wert, "`number"' für einen numerischen Wert und "`string"' für eine Zeichenkette.
Ausdruck | wahr wenn |
id | id == "`yes"' |
val == val | identisch getypte Werte sind gleich |
val != val | identisch getypte Werte sind ungleich |
val == number | numerischer Wert von val == number |
val != number | numerischer Wert von val != number |
val < number | numerischer Wert von val < number |
val > number | numerischer Wert von val > number |
val == version | numeric(val) == numeric(version) |
val < version | numeric(val) < numeric(version) |
val > version | numeric(val) > numeric(version) |
val =~ string |
regulärer Ausdruck in string auf val passt |
( expr ) | Ausdruck in Klammern ist wahr |
expr && expr | beide Ausdrücke sind wahr |
expr || expr | mind. einer der beiden Audrücke ist wahr |
copy_pending(id) | siehe Beschreibung |
samenet (string1, string2) | string1 das gleiche netz wie string2 beschreibt |
subnet (string1, string2) | string1 ein Subnetz von string2 beschreibt |
Mit dem Match-Operator =~
kann man prüfen, ob ein regulärer
Ausdruck auf den Wert einer Variable passt. Weiterhin kann man
den Operator auch nutzen, um Teilausdrücke aus einer Variablen zu
extrahieren. Nach erfolgreichem Anwenden eines regulären Ausdrucks auf
eine Variable enthält das Array MATCH_% die gefundenen Teile. Das
könnte z.B. wie folgt aussehen:
set foo="foobar12" if ( foo =~ "(foo)(bar)([0-9]*)" ) then foreach i in match_% do warning "match %i: $i" done fi
Ein mkfli4l-Aufruf führt dann zu folgender Ausgabe:
Warning: match MATCH_1: foo Warning: match MATCH_2: bar Warning: match MATCH_3: 12
Bei Verwendung von =~
kann Bezug auf alle existierenden
regulären Ausdrücke genommen werden. Will man z.B. prüfen, ob ein
PCMCIA-Ethernet-Treiber ausgewählt wurde, ohne dass OPT_PCMCIA auf
"`yes"' gesetzt wurde, könnte das wie folgt aussehen:
if (!opt_pcmcia) then foreach i in net_drv_% do if (i =~ "^(RE:PCMCIA_NET_DRV)$") then error "If you want to use ..." fi done fi
Wie in dem Beispiel demonstriert wird, ist es wichtig, den regulären Ausdruck mit Hilfe von ˆ und $ zu verankern, wenn man den Ausdruck auf die gesamte Variable anwenden will. Ansonsten liefert der Match-Ausdruck schon "`wahr"', wenn nur ein Teil der Variable vom regulären Ausdruck abgedeckt wird, was in diesem Fall sicherlich nicht erwünscht ist.
Mit den im Check-Prozess gewonnenen Informationen prüft die Funktion copy_pending, ob in Abhängigkeit vom Wert einer Variable eine Datei kopiert wurde oder nicht. Das kann man verwenden, um z.B. zu testen, ob der vom Nutzer angegebene Treiber auch wirklich existiert und kopiert wurde. copy_pending akzeptiert den zu prüfenden Namen in Form einer Variablen oder einer Zeichenkette.8.5 copy_pending prüft dazu, ob
Dabei liefert copy_pending "`wahr"' zurück, wenn im letzten Schritt festgestellt wurde, dass keine Datei kopiert wurde, das Kopieren also somit noch aussteht (also "`pending"' ist).
Ein kleines Beispiel für die Anwendung all dieser Funktionen findet man in check/base.ext:
foreach i in net_drv_% do if (copy_pending("%i")) then error "No network driver found for %i='$i', check config/base.txt" fi done
Hier werden alle Elemente des Arrays NET_DRV_% angemeckert, für die keine Kopieraktion vorgenommen wurde, für die also in der opt/base.txt kein entsprechender Eintrag existiert.
Zum Prüfen von Routen benötigt man ab und zu einen Test, ob zwei Netzwerke identisch sind oder eines ein Subnetz eines anderen ist. Dazu gibt es die beiden Funktionen samenet und subnet. Dabei liefert
samenet (netz1, netz2)
"`wahr"', wenn beide Netze identisch sind, und
subnet (netz1, netz2)
gibt "`wahr"' zurück, wenn "`netz1"' ein Subnetz von "`netz2"' ist.
Ist ein OPT gezwungen, dem Kernel andere Boot-Parameter zu übergeben, so musste früher die Variable KERNEL_BOOT_OPTION geprüft werden, ob der nötige Parameter enthalten war, und ggf. eine Warnung oder eine Fehlermeldung ausgegeben werden. Mit der internen Variable KERNEL_BOOT_OPTION_EXT kann man nötige, aber fehlende Optionen direkt im ext-Skript ergänzen. Ein Beispiel aus der check/base.ext:
if (powermanagement =~ "apm.*|none") then if ( ! kernel_boot_option =~ "acpi=off") then set kernel_boot_option_ext="${kernel_boot_option_ext} acpi=off" fi fi
Damit wird "`acpi=off"' an den Kernel übergeben, falls keine Energieverwaltung oder welche vom Typ "`APM"' gewünscht ist.
Verschiedene Kernelversionslinien unterscheiden sich häufig in einigen Details:
Diese Unterschiede werden zum großen Teil durch mkfli4l automatisch behandelt. Um die zur Verfügung stehenden Module zu beschreiben, kann man zum einen die zur Prüfung verwendeten Prüfungen in Abhängigkit von der Version erweitern (bedingte reguläre Ausdrücke), und zum anderen erlaubt mkfli4l versionsabhängige opt/<PACKAGE>.txt-Dateien. Dies heißen dann opt/<PACKAGE>_<Kernel-Version>.txt, wobei die Komponenten der Kernel-Version durch Unterstriche voneinander getrennt werden. Ein Beispiel: Das Paket "`base"' enthält die folgenden Dateien im opt-Verzeichnis:
Die erste Datei (base.txt) wird immer verarbeitet. Die anderen
beiden Dateien werden nur verarbeitet, wenn die Kernelversion "`3.16(.*)"'
bzw. "`3.17(.*)"' lautet. Wie man sieht, können Versionskomponenten im Dateinamen
weggelassen werden, wenn man eine ganze Gruppe von Kerneln "`erschlagen"'
möchte. Unter Annahme von KERNEL_VERSION='3.16.41'
werden für ein Paket
<PACKAGE> die folgenden Dateien (sofern vorhanden) eingelesen und
verarbeitet:
Die Dokumentation wird in den Dateien
abgelegt. Die HTML-Dateien können auch aufgeteilt werden, d.h. für jedes enthaltene OPT eine. Dann muss trotzdem eine <PACKAGE>.html angelegt werden, die auf die anderen Dateien verweist. Änderungen sollten in folgenden Dateien dokumentiert werden:
Die gesamte Text-Dokumentation darf keine Tabulatoren enthalten und muss nach spätestens 79 Zeichen einen harten Zeilenumbruch haben. Die stellt sicher, dass die Dokumentation auch mit einem Editor ohne automatischen Zeilenumbruch richtig gelesen werden kann.
Wer mag kann auch eine Dokumentation im LATEX-Format erstellen und daraus dann HTML- und PDF-Fassungen erzeugen. Als Beispiel kann die Dokumentation von fli4l dienen. Einen Rahmen für die Dokumentation und die minimal benötigten LATEX-Macros kann man im Paket "`template"' finden. Eine kurze Beschreibung ist in den folgenden Unterabschnitten zu finden.
Die fli4l-Dokumentation steht zur Zeit in den folgenden Sprachen zur Verfügung: deutsch, englisch (<SPRACHE> = "`english"') und französisch (<SPRACHE> = "`french"'). Es steht einem Paket-Entwickler jedoch frei, sein Paket in beliebigen Sprachen zu dokumentieren. Im Sinne der Verständlichkeit wird jedoch empfohlen, eine Dokumentation in deutsch und/oder englisch (idealerweise in beiden Sprachen) anzufertigen.
Zum Erstellen der Dokumentation aus LATEX-Quellen gibt es folgende Anforderungen an die Umgebung:
Die Dateien der Dokumentation werden nach folgendem Schema benannt:
Diese Dateien werden im Verzeichnis fli4l/<PACKAGE>/doc/<SPRACHE>/tex/<PACKAGE> abgelegt. Für das Paket "`sshd"' sieht das z.B. wie folgt aus:
$ ls fli4l/doc/deutsch/tex/sshd/ Makefile sshd_appendix.tex sshd_main.tex sshd.tex
Das Makefile ist für die Generierung der Dokumentation verantwortlich, die sshd.tex-Datei stellt einen Rahmen für die eigentliche Dokumentation und den Anhang bereit, der sich in den anderen beiden Dateien befindet. Ansehen kann man sich das am Beispiel der Dokumentation des "`template"'-Pakets.
LATEX arbeitet ähnlich wie HTML "`Tag-orientiert"', nur dass die Tags
hier "`Kommandos"' heißen und folgendes Format aufweisen: \kommando
bzw. \begin{umgebung}
...\end{umgebung}
Nach Möglichkeit sollte man mit Hilfe von Kommandos eher die Bedeutung des jeweiligen Textes auszeichnen und weniger dessen Darstellung. Es ist also vorteilhaft, z.B.
\warning{Bitte nicht ... tun}
statt
\emph{Bitte nicht ... tun}
zu verwenden.
Jedes Kommando bzw. jede Umgebung kann noch weitere Parameter
aufnehmen, die mit \kommando{parameter1}{paramter2}{paramterN}
geschrieben werden.
Manche Kommandos haben optionale Parameter, die in eckigen (statt geschweiften)
Klammern stehen: \kommando[optionalerParameter]{parameter1}
... Dabei kommt im Normalfall nur ein optionaler Parameter vor, in
seltenen Fällen aber auch mehrere.
Einzelne Absätze werden im Dokument durch Leerzeilen getrennt. Innerhalb dieser Absätze nimmt LATEX selbst den Zeilenumbruch und die Worttrennung vor.
Folgende Buchstaben haben eine spezielle Bedeutung in LATEX und müssen,
sollten sie in normalem Text vorkommen, mit einem vorangestellten \
maskiert werden: # $ & _ % { }. "`~
"' und "`^
"' müssen
wie folgt geschrieben werden: \verb?~?
\verb?^?
Die wichtigsten LATEX-Kommandos werden in der Dokumentation des "`template"'-Pakets verwendet und erklärt.
Alle Textdateien (sowohl Dokumentation als auch Skripte, die später auf dem Router liegen) müssen im DOS-Dateiformat, also mit CR/LF statt nur LF am Zeilenende in das Paket gelegt werden. Dadurch wird erreicht, dass Windows-Nutzer die Dokumentation auch mit "`notepad"' lesen können und durch eine Änderung eines Skripts unter Windows das Ganze später auf dem Router trotzdem lauffähig bleibt. Die Skripte werden beim Bauen der Archive in das auf dem Router benötigte Format konvertiert (siehe die Beschreibung der Flags in Tabelle 8.2).
Sollte ein Programm aus dem Paket eine neue Schnittstelle definieren, die andere Programme nutzen können, so ist die Dokumentation dieser Schnittstelle in einer separaten Dokumentation unter doc/dev/<PACKAGE>.txt abzulegen.
Sollte ein Paket zusätzliche Client-Programme mitliefern, so sind diese im Verzeichnis windows/ für Windows-Clients und im Verzeichnis unix/ für Unix- und Linux-Clients abzulegen.
Angepasste Programme und Quellcodes können im Verzeichnis src/<PACKAGE>/ beigelegt werden. Sollen die Programme genauso wie die restlichen fli4l-Programme gebaut werden, bitte einen Blick in die Dokumentation des "`src"'-Pakets werfen.
Alle Dateien, die nachher auf dem Router liegen, werden unter opt/etc/ und opt/files/ abgelegt. Dabei liegen unter:
Die Skripte in opt/etc/boot.d/, opt/etc/rc.d/ und opt/etc/rc0.d/ werden wie folgt benannt:
rc<nummer>.<name>
Die Nummer entscheidet über die Reihenfolge der Ausführung, der Name gibt einen Hinweis darauf, welches Programm/Paket von diesem Skript behandelt wird.