Archiv des Autors: romeofox

Interface-Name bei SuSE festlegen

Hat man in einem Rechner mehrere Netzwerkkarten möchte man eigentlich, das die Karte, die heute eth1 ist das auch nach dem nächsten booten ist. Durch die automatische Hardware-Erkennung ist das aber leider nicht immer der Fall, was gerade bei einer Firewall recht störend sein kann.

Liesen sich diese Einstellungen früher in der Netzwerkkonfiguration unter /etc/sysconfig/network eintragen, so muss man diese bei SuSE 10.0 ganz wo anders einstellen.

Um die Namen für die Interfaces festzulegen müssen diese in der Datei
/etc/udev/rules.d/30-net_persistent_names.rules
eingetragen werden.
Normalerweise trägt Yast bei der Konfiguration alle Karten dort ein. Ein solcher Eintrag sieht beispielsweise so aus:

SUBSYSTEM=="net", ACTION=="add", SYSFS{address}=="00:50:ba:5e:17:4b", IMPORT="/sbin/rename_netiface %k eth0"

Hier kann dann einfach ein anderer Bezeichner gewählt werden oder die Reihenfolge angepasst werden.

Weitere Infos finden sich auch in der Datei /usr/share/doc/packages/sysconfig/README.Persistent_Interface_Names.

Bei SuSE 11.1 heißt die Datei 70-persistent-net.rules und die Einträge sehen so aus:

SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:1c:23:88:3f:63", ATTR{type}=="1", KERNEL=="eth*", NAME="eth1"

Die o.g. README gibt es leider nicht mehr, zu udev (und auch zu den Netzwerk-Interfacen) gibt es /usr/share/doc/packages/udev/writing_udev_rules/index.html.

Es gibt wohl auch ein Tool namens nameif, aber das funktioniert bei mir nicht (keine /etc/mactab).

Meldungen auf dem Linux-Desktop ausgeben

Wenn man aus einem Script heraus eine Meldung auf dem Linux-Desktop ausgeben will, hat man dazu verschiedene Möglichkeiten. Einige stelle ich hier vor.

Ich gehe hier mal davon aus, das KDE eingesetzt wird, da ich mit Gnome keine Erfahrung habe. Die ein oder andere Lösung mag aber u.U. auch mit Gnome funktionieren.

Ausgabeumleitung in ein Fenster

Mit KDE kann man einfach ein

echo huhu > /dev/pts/0

machen. Der KWrited Daemon lauscht auf diesem Device und stellt den Text in einem Fenster dar. Das funktioniert auch, wenn das Script nicht unter dem Benutzer läuft, der das Display besitzt. Weitere Ausgaben, auch von anderen Scripten werden an die Ausgabe angehängt, es geht also immer nur ein Fenster auf. (Wie man den Inhalt löscht o.ä. habe ich noch nicht herausgefunden).

Es ist aber nicht immer /dev/pts/0, es kann auch /dev/pts/3 o.ä. sein. Auf welchem Device der Daemon lauscht kann mit

lsof | grep kded4 | grep '/dev/pts/'

ermittelt werden.

Xmessage

Mit Xmessage lassen sich einzelne Meldungen anzeigen, die erst nach dem Klick auf den OK-Button verschwinden. Man kann aber auch mit -timeout eine Zeit in Sekunden angeben, nach der die Meldung automatisch verschwinden soll. Mit -center wird die Nachricht in der Bildschirmmitte ausgegeben, die Größe lässt sich mit -geometry BreitexHöhe (z.B. -geometry 500×100) angeben. Auch die Buttons können beeinflusst werden (siehe man 1 xmessage)

KDialog

Die schönsten Meldungen und sogar richtige Dialoge mit Eingabefeldern lassen sich mit kdialog erstellen.

Eine einfache Meldung erscheint mit

kdialog --msgbox "Danke, Sie haben eine einfache Dialogbox sehr glücklich gemacht"

Um die Meldung auf allen Arbeitsflächen des Desktop erscheinen zu lassen muss man kstart zu Hilfe nehmen:

kstart --alldesktops --window Information \nkdialog --msgbox "Danke, Sie haben eine einfache Dialogbox sehr glücklich gemacht"

Leider gibt es anscheinend zu kstart außer dem was mit kstart --help angezeigt wird keine weitere Dokumentation. Zu kdialog gibt es immerhin ein deutschsprachiges Tutorial unter techbase.kde.org/Development/Tutorials/Shell_Scripting_with_KDE_Dialogs_%28de%29.

Und sonst

Es gibt noch weitere Tool für die X-Ausgabe, hier sind einige kurz vorgestellt:

xmore
Zeigt den Inhalt einer Datei an. Kann anscheinden nichts mit STDIN anfangen.
gmessage
AFAIK ein xmessage-Clone für Gnome.
perl-tk
Für Perl gibt es eine Tk-Erweiterung, mit der man „richtige“ Programme bauen kann.

Links

Zwei verschiedene MySQL-Server auf einem Rechner

Wie bringt man einen 4.1-Server und einen 5.0-Server auf einem Linux-Rechner gleichzeitig ans laufen? Diese Frage stellte sich mir kürzlich, da ich verschiedene Intra- und Extra-Websites betreue und hier gibts bei einer nun auch Mysql 5.0. Die anderen laufen aber noch mit der ‚alten‘ Version 4.1

Nach einiger Recherche fand ich zwar eine Anleitung von Kristian Köhntopp, wie man viele verschiedenen MySQL Instanzen auf einem Server recht komfortabel zum Laufen bringt, aber ich wollte nur eine zusätzliche Instanz und hatte auch keine Lust, den Server selbst zu kompilieren (was von MySQL AB auch nicht wirklich empfohlen wird). So suchte ich nach einer Lösung, das Ganze mit den von MySQL bereitgestellten RPMs zu machen.

Zzt. ist auf meinem Linux-Rechner die Version 4.1 installiert (von den SuSE-10.0 CDs) und läuft.

Zwei Dinge mussten bewerkstelligt werden:

  1. Die Installation des zweiten Servers muss in andere Verzeichnisse erfolgen, als im RPM-Paket vorgesehen.
  2. Der 5.0er Server muss einen anderen TCP-Port und einen anderen Socket benutzen. Dazu muss eine eigene my.cnf benutzt werden.

Punkt 1 kann mit der relocate-Funktion von RPM bewerkstelligt werden. Zusätzlich mussten noch ein paar Dateien gepatcht werden, damit der Server auch die neuen Verzeichnisse findet. Doch dazu später mehr.

Die Sachen mit den Config-Dateien ist relativ einfach, der Server (und auch der Client) sucht sie entweder in /etc, im sog. Datadir (normalerweise /var/lib/mysql) oder als .my.cnf im Home-Verzeichnis des Users. Wenn also in /etc nichts liegt aber im jeweiligen Datadir (was ja unbedingt unterschiedlich sein muss), findet jeder Server automatisch die richtige Config-Datei. Na ja, so ganz automatisch nicht, dazu muss ein wenig an den Start-Scripten geschraubt werden. Doch zunächst mal zur

Installation.

Man besorge sich die aktuellen RPMs bei www.mysql.com, bei mir waren es

  • MySQL-server-5.0.21-0.glibc23.i386.rpm
  • MySQL-Max-5.0.21-0.glibc23.i386.rpm
  • MySQL-client-5.0.21-0.glibc23.i386.rpm

Wer die speziellen Funktionalitäten des Max-Servers nicht braucht lässt ihn weg. Ebenso kann auch auf das Client-Paket verzichtet werden, aber wo man gerade schon mal dabei ist …

Als Nächstes sollte man die bestehenden Datenbanken sichern, man weiß ja nie 😎

Dazu wird erstmal der laufenden Server gestoppt:

rcmysql stop

Danach die Datenbanken sichern:

cd /var/lib
cp -prv mysql mysql41

Die Config-Datei und das Start-Script sollten auch vorsichtshalber in Sicherheit gebracht werden:

cp -pv /etc/my.cnf /etc/my41.cnf
cp -pv /etc/init.d/mysql /etc/init.d/mysql41

Nun die Verzeichnisse anlegen:

mkdir -p /usr/local/mysql50 /var/lib/mysql50

Wir sehen, das Installationsverzeichnis soll /usr/local/mysql50 sein und das Datadir, in dem später u.a. die Datenbank-Dateien liegen, soll /var/lib/mysql50 sein.

Entsprechend werden die Installationspfade per –relocate=<alt>=<neu> verbogen (siehe man rpm).
Mit –test macht man erstmal ein Testlauf:

rpm -vv --test
  --relocate=/usr/share/mysql/=/usr/local/mysql50/share/mysql/
  --relocate=/usr/share/man/=/usr/local/mysql50/man/
  --relocate=/usr/share/info/=/usr/local/mysql50/info/
  --relocate=/usr/sbin/=/usr/local/mysql50/sbin/
  --relocate=/usr/lib/mysql/=/usr/local/mysql50/lib/
  --relocate=/usr/bin/=/usr/local/mysql50/bin/
  --relocate=/etc/=/usr/local/mysql50/etc/
    -ih MySQL-server-5.0.21-0.glibc23.i386.rpm

Wenn das soweit gut aussieht kann man den Test-Schalter weglassen und installieren.
Nach dem Server kann man nun auf die gleiche Art das Max- und das Client-Paket installieren, so man will.

Konfiguration und Einstellung

Nun sollte man eine Konfigdatei erstellen und nach /var/lib/mysql50 legen. Wichtig ist, dass sich die Angaben für Port und Socket von denen der 4.1er Konfigdatei unterscheiden.
Desweiteren ganz wichtig: in der my.cnf für den 5.0er Server muss die folgenden Zeile beim Abschnitt [mysqld] rein:

language = /usr/local/mysql50/share/mysql/english

Sonst sucht der Server diese Datei in /usr/share/mysql/english/errmsg.sys und findet logischerweise die der 4.1er-Version, was nicht passt (im error-log steht dann: „Error message file ‚/usr/share/mysql/english/errmsg.sys‘ had only 304 error messages, but it should contain at least 463 error messages.“)
Meine Konfig-Datei sieht so aus: my.cnf

Die alte my.cnf aus /etc wird nach /var/lib/mysql verschoben, damit jeder Server seine eigene Datei benutzt. In /etc darf keine mehr sein!

Nun das Startscript ins richtige Verzeichnis kopieren und umbenennen:

cp /usr/local/mysql50/etc/init.d/mysql /etc/init.d/mysql50

In dieser Datei müssen basedir und datadir entspr. gesetzt werden:
basedir=/usr/local/mysql50
datadir=/var/lib/mysql50

Dann muss ein wenig gebastelt werden, denn das Hilfs-Startscript mysqld-safe in /usr/local/mysql50/bin interessiert sich nicht richtig für die vom eigentlichen Startscript übergebenen Werte sondern versucht selbst basedir und datadir zu ermitteln und findet, da dort die falschen Vorgaben drin stehen, dabei die falschen Pfade. Dadurch wird schlussendlich die falsche my.cnf gefunden und somit die falschen Vorgaben verwendet (bei SuSEs mysqld-safe sind andere defaults eingebaut). Ich habe das korrigiert:

116c116
< if test -f ./share/mysql/english/errmsg.sys -a -x ./bin/mysqld
---
> if test -f ./share/mysql/english/errmsg.sys -a -x ./sbin/mysqld
119c119
<   ledir=$MY_BASEDIR_VERSION/bin               # Where mysqld is
---
>   ledir=$MY_BASEDIR_VERSION/sbin      # Where mysqld is
150c150
<   DATADIR=/var/lib/mysql
---
>   DATADIR=/var/lib/mysql50

Zum Einspielen des Patches benennt man mysqld_safe in mysqld_safe-orig um und verwendet das Programm patch zum editieren:

patch -o mysqld_safe mysqld_safe-orig mysqld_safe.patch

… 3 – 2 – 1 – Start

Nun sollte sich der Server mit

/etc/init.d/mysql50 start

zum Leben erwecken lassen. Mit netstat -ltn kann man prüfen, ob der Server auf dem neuen Port lauscht.

Dem Client muss man nun natürlich sagen, mit welchem Server er in Verbindung treten soll:

mysql -S /var/lib/mysql50/mysql.sock -u root -p

Dabei wird der alte Client der SuSE-Installation verwendet. Um den neuen Client zu verwenden, muss man ihn mit vollen Pfad ansprechen:

/usr/local/mysql50/bin/mysql -S /var/lib/mysql50/mysql.sock -u root -p

Klappt natürlich nur, wenn man ihn wie oben angegeben installiert hat.

Man kann das Ganze aber mit Hilfe der entspr. cnf-Datei machen:

/usr/local/mysql50/bin/mysql --defaults-file=/var/lib/mysql50/my.cnf -u root -p mysql

Bei PHP muss man entweder in der php.ini den entsprechenden Socket angeben oder die PHP-Variable mysql.default_socket bzw. mysql.default_port in der Apache-Konfig oder per htaccess definieren. Natürlich kann man auch den Port oder Socket beim Aufruf von mysql_connect() an dem Hostnamen anhängen. Man sollte bei der Verwendung von Port aber beachten, das man „127.0.0.1“ statt „localhost“ benutzt, mysql_connect zieht sonst u.U. den (falschen) Socket vor. Ich empfehle die Lektüre der entsprechenden PHP-Doku.

So, und nun wünsche ich viel Spaß beim nachbasteln…

Verzeichnisse auf einen anderen Rechner übertragen

Im vorigen Artikel habe ich gezeigt, wie man mit TAR ein komplettes Verzeichnis so verschieben bzw. kopieren kann, dass alle Rechte erhalten bleiben. Hier kommt nun die erweiterte Fassung zum kopieren von Verzeichnissen auf einen anderen Rechner mittels SSH und TAR. Und als Bonbon gibt’s ein Script zum Erstellen von Backups von anderen Rechnern.

Um das Verzeichnis foo vom eigenen Rechner auf dem Rechner kermit als Unterverzeichnis von /bar zu speichern benutzt man den folgenden Befehl:

tar cpSf - foo | ssh kermit "cd /bar && tar xpSvf -"

Wie man sieht, ähnelt der Aufruf sehr dem Befehl zum kopieren auf dem lokalen Rechner, lediglich das „ssh kermit“ ist neu (und die Klammern brauchen wir hier nicht). Ich gehe hier davon aus, dass der User des lokalen Rechners auch auf dem Rechner kermit bekannt ist. Soll auf kermit ein anderer Account, z.B. klaus, verwendet werden, schreibt man einfach „ssh klaus@kermit“. Der User muss auf jeden Fall ausreichende Rechte im Zielverzeichnis haben. Da ein Ziel ist, die Rechte beizubehalten, sollte das root sein.

Man kann tar die Daten auch komprimieren lassen und bei geringer Bandbreite das Ganze somit beschleunigen:

tar zcpSf - foo | ssh kermit "cd /bar && tar zxpSvf -"

Man kann das Ganze auch umkehren, also Verzeichnisse des entfernten Rechners auf den lokalen kopieren:

ssh kermit tar cSpf - /foo | (cd /bar && tar xpSvf -)

kopiert das Verzeichnis /foo von kermit auf dem lokalen Rechner ins Verzeichnis /bar.

Man kann dem ersten tar-Befehl übrigens auch mehrere Verzeichnisse übergeben. Das eignet sich hervoragend für ein einfaches Backup, insbesondere zusammen mit gzip:

ssh kermit tar cSpf - /foo /bar | gzip -f9 > kermit_backup.tgz

Das sichert die Verzeichnisse /foo und /bar des Rechners kermit als komprimiertes Tar-Archiv in der Datei kermit_backup.tgz auf dem Lokalen Rechner.

Ich mache so täglich Sicherungen von wichtigen Verzeichnissen meiner Server.

Auch hier kann man tar die Komprimierung übernehmen lassen:

ssh kermit tar zcSpf - /foo /bar > kermit_backup.tgz

Diese Variante ist schneller, die andere komprimiert dafür geringfügig besser.

Komplette Verzeichnisbäume verschieben

Ich musste mal wieder ein Verzeichnis von der einen Partition auf eine andere schieben um Platz zu schaffen. Wichtig dabei ist, dass die Rechte der Dateien und Verzeichnisse nicht verändert und Sym-Links korrekt kopiert werden. Dazu kann man sehr gut den tar-Befehl nutzen.

Nehmen wir mal an, wir wollen das komplette Verzeichnis foobar unterhalb von /var nach /usr kopieren, so dass es hinterher als /usr/foobar zu finden ist.
Man wechselt dazu als root in das übergeordnete Verzeichnis der Quelle (hier also nach /var) und gibt den folgenden Befehl ein:

tar cSpf - foobar | ( cd /usr && tar xSpvf - )

Wie man sieht, wird hinter den „cd“ das Zielverzeichnis angegeben.
Der Linke Teil packt das Verzeichnis foobar in einen tar-Stream und gibt ihn auf der Standardausgabe aus (dafür sorgt das – hinter dem f ). Die Parameter S und p sorgen dafür, dass die Rechte unverändert bleiben (p) und dass leere bzw. kleine Dateien effizient übertragen werden (S). Dieser Stream wird dann mit der Pipe (|) an den rechten Teil weitergereicht. Hier wird dann erstmal in das Zielverzeichnis gewechselt und der über die Standardeingabe (wieder f – ) empfangene Stream dort ausgepackt.

Anschließend kann das Quelleverzeichnis gelöscht werden.

Wie das ganze auch über Rechnergrenzen mittels ssh funktioniert zeige ich in einem weiteren Artikel.

ATA statt AJAX

Da habe ich doch heute beim Lesen des Ajax-Artikels in der neuen c’t (05/2006 S. 152) feststellen müssen, dass ich, ohne es zu wissen, schon mal Ajax zu Fuß gecodet haben. Naja, nicht ganz, denn mit XML hatte das nichts zu tun. Daher müsste man das eher ATA (Asynchronous Tag Apending 😉 nennen. Immerhin auch ein Scheuerzeugs.

Ich musste für eine Webanwendung eine Möglichkeit schaffen, Daten in ein Formular hinzufügen, daraus zu entfernen und auch ändern zu können. Es ging hier um Rufnummern. Natürlich sollte dazu das Formular nicht jedes mal neu aufgerufen werden.

Ich behalf mir also mit einem Popup-Fenster, in dem über ein kleines Formular die Rufnummer bearbeitet oder hinzugefügt werden konnte. Diese Formular bekommt die Daten per GET über den Link geliefert und wertet diese per PHP aus. Beim Abschicken des Formulars wird über den Eventhandler „onClick“ des Buttons die Funktion sendData() aufgerufen. Diese überträgt die Formular-Daten als assoziatives Array an die aufrufende Seite, indem dort je nach Mode die Funktion changeMSN oder an addMSN aufgerufen wird.

function sendData(bnt,mode) {
  var theForm = bnt.form;
  var str = "";
  var MSNarray = new Array();
  for (var i = 0; i > theForm.length; ++i) {
    // Das Array zusammenbauen:
    switch (theForm.elements[i].type) {
      case "button" : break; // überspringen

      case "checkbox" : MSNarray[theForm.elements[i].name] = theForm.elements[i].checked ? "X" : "";
                        break;

      default: MSNarray[theForm.elements[i].name] = theForm.elements[i].value;
    }
  }
  // Daten übertragen:
  if(mode == "ADD") {
    opener.addMSN(MSNarray);
  } else {
    opener.changeMSN(MSNarray);
  }
  self.close();
}

Diese Funktionen fügen dann entweder eine Rufnummer im Formular hinzu (addMSN) oder verändern eine schon vorhandene (changeMSN).

addMSN geht zunächst einmal das übergebene Array durch und holt sich u.a. die Rufnummer. Dann muss diese dem HTML-Dokument hinzugefügt werden. Die Nummern sind in einer Tabelle organisiert, die sich mit

msntbl = document.getElementById("msnlist_body");

aus dem DOM-Baum fischen lässt. Um nun eine Zeile an die Tabelle anzuhängen müssen verschiedene DOM-Objekte erzeugt und angehängt werden:

  // neues tr-Element erzeugen:
  var newTR = document.createElement("tr");
  // das neue Element am Ende der Tabelle anhängen:
  msntbl.appendChild(newTR);
  // neues td-Element erzeugen (bleibt leer):
  var newTD = document.createElement("td");
  // das neue Element an das TR anhängen:
  newTR.appendChild(newTD);
  // noch ein neues td-Element erzeugen (für die MSN):
  var newTD = document.createElement("td");
  // auch an das TR anhängen:
  newTR.appendChild(newTD);
  // das Atribut ID erzeugen und anhängen:
  var AttrID = document.createAttribute("id");
  AttrID.nodeValue = "MSN_"+msn;
  newTD.setAttributeNode(AttrID);
  // jetzt können wir das neue Element per ID ansprechen:
  document.getElementById("MSN_"+msn).className = "msn";
  // Text-Knoten erzeugen
  if(RNBvon == "") {
    var Txt = document.createTextNode(msn);
  } else {
    var Txt = document.createTextNode(msn+"-"+RNBvon+"..."+RNBbis);
  }
  // an das zuletzt erzeugte TD anhängen:
  newTD.appendChild(Txt);

  // noch ein neues td-Element erzeugen (für die PK und die serialisierten Daten):
  var newTD = document.createElement("td");
  // auch an das TR anhängen:
  newTR.appendChild(newTD);
  // neues INPUT-Element erzeugen:
  var newInput = document.createElement("input");
  newTD.appendChild(newInput);
  // das Atribut type erzeugen und anhängen:
  var AttrType = document.createAttribute("type");
  AttrType.nodeValue = "hidden";
  newInput.setAttributeNode(AttrType);
  // das Atribut ID erzeugen und anhängen:
  var AttrId = document.createAttribute("id");
  AttrId.nodeValue = "MSNdata_"+msn;
  newInput.setAttributeNode(AttrId);
  // jetzt können wir das neue Input-Element per ID ansprechen:
  document.getElementById("MSNdata_"+msn).name = "MSN["+msn+"]";
  document.getElementById("MSNdata_"+msn).value = serialize(MSNarray);

Wie man sieht, werden die eigentlichen Daten in ein Input-Element als serialisierten String eingefügt. Ich habe diesen Weg gewählt, da ich wesentlich mehr Daten übertragen als in diesem Beispiel dargestellt. Die Serialisierung entspricht der von PHP, so dass PHP diesen String dann in ein Array umwandeln kann. Ich werde vielleicht in einem späteren Artikel auf diese Sache näher eingehen.
Nach dieser DOM-Arie ist die neue Nummer im Formular sichtbar.

Die Funktion changeMSN muss anders arbeiten. Sie muss die betroffene Rufnummer aus der Liste heraussuchen und verändern. Das ist relativ einfach, da die Nummer Bestandteil der ID aller Elemente ist und diese über getElementById zu fassen sind. Hier am Beispiel des versteckten Input-Elements:

  // den serialisierten String holen:
  var feature_item = document.getElementById("MSNdata_"+msn);
  ...
  // String bearbeiten
  ...
  // den neuen serialisierten String schreiben:
  feature_item.value = newSerial;

So einfach kann das sein 🙂
Das Löschen funktioniert ähnlich, da ich die Nummer im Formular nicht wirklich entferne, sondern nur die Darstellung mit

  document.getElementById("MSN_"+msn).style.textDecoration='line-through';

ändere und die Nummer einem versteckten Inuput-Element mit der ID deleteMSN hinzufüge.

So weit für heute mit dem AJAX für Arme. Demnächst mehr in diesem Theater.