UTF-8 Dateinamen: Linux vs. Mac OS X (Stand 2014)

Wie schon vor 8 Jahren beschrieben gibt es auch heute immer noch Probleme beim Austausch von Dateien, deren Dateinamen Buchstaben jenseits des ASCII Zeichensatzes beinhalten.

Die Situation hat sich bis heute in 2 Punkten geändert.

  1. die UTF-8 Form D unterstützung in Linux ist beinahe perfekt. Die Dateimanager zeigen die Dateien mit den zusammengesetzen Buchstaben richtig an
  2. Mac OS X Mavericks geht jetzt bei NFS mounts davon aus, dass die Dateinamen Form C dekodiert sind (was bei Linux NFS Server ja richtig ist).

Leider gibt es immer noch eine Menge Lücken, bspw. der Austausch von ext3 Datenträgern zu Mac OS X via Paragon ExtFS for Mac OS X, welches keine Konvertierung vornimmt und daher Form C Dateien direkt an Mac OS X weitergibt, deren Programme dann mit diesen Dateien nicht umgehen können.

Punkt oder Komma

Wenn ein Program dass Gleitkommazahlen von einer oder in eine Textdatei liest, bzw. schreibt nicht mehr funktioniert, dann sollte man sich genauer mit LC_NUMERIC befassen. Dieser Artikel behandelt die Auswirkungen auf Qt- und reine C-Programme.

LC_NUMERIC ist Bestandteil der sogenannte locales, der Lokalisierungen (oder auch Regionalisierungen) auf einem System. Diese Lokalisierungen lassen sich sehr fein einstellen – bspw. LC_MONETARY für die Währung oder eben LC_NUMERIC für das Zahlenformat – aber auch zusammenfassend mittels LC_ALL. Setzt man bspw. LC_ALL=de_CH.UTF-8, dann wird diese Einstellung auf alle sublocales übertragen und für LC_MONETARY ist der Franken gesetzt, wenn auch das meiste andere ziemlich deutsch ist.

Ok, nun aber zum Zahlenformat – und um die Sache ein bischen zu beschleunigen, soll eine Textdatei mit folgendem Inhalt ausgelesen werden.

#Hallöle
33.456,78
33456.78
33,456.78
33,99
33.98
33.777

Um die Zahlen besser zu verstehen muss noch einmal ausgeholt werden. LC_NUMERIC legt zwei Parameter fest.
Den Dezimaltrenner und den Tausender-Trenner. Er wird folgendermassen gesetzt:

Locale Dezimaltrenner Tausender-Trenner
en_US . ,
de_DE , .
C .

Das bedeutet, dass die deutsche Lokalisiserung genau andersherum als die US-Amerikanische ist. Die sog. C-Locale (auch POSIX-Locale genannt) kennt nur den Dezimaltrenner. Diese Locale ist als Rückfallebene gedacht.

Wie verhält sich nun ein Qt-Programm (Qt4), dass die o.g. Textdatei zeilenweise einliest und versucht den Text in Zahlen zu konvertieren? Zuerst einmal wird ein Testprogram unter der Umgebung LC_NUMERIC=de_DE.UTF-8 gestartet.

localeTest_de

Die Erklärung ist überrasch komplex. Zuerst einmal muss festgestellt werden, dass Qt Programme generell LC_NUMERIC-aware sind. Dies wird dadurch erreicht, dass QCoreApplication

setlocale(LC_ALL,"");

aufruft und damit locales aus dem Environment dem Programm zur Verfügung gestellt werden (dies wird durch die doppelten Anführungszeichen erreicht). Dennoch werten nicht alle Qt-Methoden LC_NUMERIC aus.

QByteArray::toDouble()
ignoriert die gesetzte locale und benutzt immer die C-Locale.

QString::toDouble()
verhält sich am kompliziertesten. Es wird der Dezimaltrenner der gesetzten locale ausgewertet, nicht aber der Tausender-Trenner (die Gründe liegen in der Kompatibilität zu C, siehe später). Gleichzeitig wird die C-Locale immer als Fallback mitausgewertet. Bei Qt5 verhält sich übrigens QString::toDouble wie QByteArray::toDouble

QLocale::toDouble()
hingegen orientiert sich ausschliesslich an der gesetzten locale.

Zum Vergleich nun die Ausgabe unter dem Environment LC_NUMERIC=en_US.UTF-8

localeTest_en

Interessant ist nun der Vergleich zu reinen C-Programme. Denn reine C-Programme mit ihren typischen Funktionen

printf/fprintf
scanf/fscanf
strtof

..usw. ignorieren standardmässig die gesetzte locale und stützen sich immer auf die C-Locale. Erst durch den schon oben genannten Aufruf von setlocale werden C-Programme locale-aware.
Werden allerdings C-Funktionen – bspw. aus einer Bibliothek – von einem Qt-Programm verwendet, sind sie automatisch locale-aware, da ja wie schon erwähnt QCoreApplication setlocale aufruft.

Dies ist eine große potentielle Fehlerquelle!

Um den Bogen zu QString::toDouble() nochmals zu spannen, sei erwähnt, dass C-Funktionen den Tausender-Trenner standardmässig ignorieren. Um z.B. printf zur Ausgabe des Tausender-Trenners bei Gleitkommazahlen zu zwingen, muss man

printf("Gezwungen zu %'f",myfloat);

einen Abostroph vor dem Formatierungszeichen einführen.
Zum Ausprobieren liegt das Qt Beispielprogramm sowie zwei plain C-Programme im diesem Archiv bei

Libraries Suchpfade (DT_PATH vs. DT_RUNPATH)

Seit geraumer Zeit gilt in der ELF Spezifikation (betrifft Linux) das ELF Symbol DT_RPATH (oder nur rpath) als deprecated, also veraltet. Der Nachfolger ist DT_RUNPATH (oder nur runpath) und wird von den GNU linkern seit langem unterstützt.

Es gibt nur ein entscheidenden Unterschied: Während DT_RPATH zur Laufzeit vor der Umgebungsvariable $LD_LIBRARY_PATH durchsucht wurde, wird DT_RUNPATH erst nach der Umgebungsvariable ausgewertet.

Wenn diese beiden ELF Symbole gleichzeitig vorhanden sind wird DT_RPATH ignoriert. Die gcc Linker Option -Wl,-rpath erzeugt derzeit standardmässig beide, was man aber mit dem Flag –disable-new-dtags ausschalten kann um das alte Verhalten (rpath gewinnt vor $LD_LIBRARY_PATH) zu erzwingen.

Der Grund für diese – zugegeben nicht neue – Änderung besteht wohl darin mehr Druck aufzubauen um zumindest auf SW-Distributionsseite nicht mehr den LD_LIBRARY_PATH zu benutzen. Dieser sollte nur noch dem Benutzer vorbehalten sein um seine speziellen Wünsche abzudecken.

Dieses Verhalten, sowie eine genauere Beschreibung des Suchmechanismus unter Mac OS X, ist in der Präsentation über Portablen Code aktualisiert.

Kommando in bash kann nicht starten obwohl ‚which‘ es findet

Manchmal liegen Programme in verschiedenen Versionen vor und verlangen nach einem Ansatz der es erlaubt die verschiedenen Versionen relativ einfach zu starten.
Eine Möglichkeit ist es die PATH Umgebungsvariable so zu setzen, dass mehrere Verzeichnisse die das Programm beinhalten gelistet sind, z.B.

PATH=~/binNeu:~/binAlt

Ein fikives Programm testMe ,würde nun aus ~/binNeu starten. Wenn man nun temporär dieses Verzeichnis umbenennt, sollte testMe aus ~/binAlt gestartet werden.
Tatsächlich wird das auch vom which Kommando so angezeigt. Bei der Ausführung von testMe, gibt die bash erstaunlicherweise die Fehlermeldung aus, das ~/binNeu/testMe (!) nicht gefunden wird.

Grund dafür ist eine Cache-Hashtable welche die bash am Anfang anlegt um einen Programmstart zu beschleunigen. Und genau diesen muss man in diesem Fall mit folgendem Kommando löschen.

hash -r

Alternativ könnte man auch das File sourcen, welches direkt oder indirekt die PATH Variable setzt, oder schlicht die bash schließen und neustarten.

pdf mit barcodes ausdrucken (gelöst)

Paketscheine von Hermes bspw. enthalten immer einen Barcode.

Leider wird dieser nicht immer korrekt ausgedruckt, sondern teilweise punktiert – also völlig unbrauchbar – ausgedruckt.
Zuerst hatte ich CUPS, bzw. die Treiber/Filter im Verdacht. Tatsächlich scheint es aber an den Programmen zu liegen.

Korrekt ist der Ausdruck eigentlich nur in folgenden Kombinationen:

    Adobe Reader 9 unter Linux (Farbe oder SW egal)
    Adobe Reader 11 unter Mac OS X (nur mit der Option „In Graustufen (SW) drucken“ des Adobe-eigenen Druckerdialogs)

Überhaupt nicht – unabhängig von der Einstellung Farbe/SW – funktioniert es mit:

    Okular unter Linux
    Evince unter Linux
    Mac OS X Vorschau (10.6.8)

Warum das so ist? Ich weiß es zumindest derzeit nicht.
=> Jetzt weiß ich es! Siehe Kommentar #1

truecrypt mount ext2 reparieren

Was tun wenn eine ext2-formatierte truecrypt Partition repariert werden muss? Praktisch kann das schnell der Fall sein, wenn z.B. das Filesystem nicht mehr sauber ist und mittels Ext2FS für Mac OS X (Paragon) eingehängt wird.

Dann muß unter Linux e2fsck (fsck.ext2) ran, bspw.

e2fsck -a /dev/mapper/truecrypt1

D.h. der Befehl wird nicht auf die Partition selber, sonder auf den dm-Container ausgeführt. Das bereinigen der Partition funktioniert aber nur zuverlässig, wenn beim truecrypt mount unter „Optionen“ die Checkbox „Do not mount“ angehackt ist, d.h. der eigentliche Inhalt des dm-Containers wird unter keinem Mountpoint (normalerweise /media/truecrypt1) angelegt, trotz aktiviertem dm-Container.

CalDAV Synchronisationsprobleme (mit davical)

Mir ist es jetzt schon zweimal passiert, dass die Clients untereinander die Synchronisation entweder komplett eingestellt haben oder nur noch teilweise Kalendereinträge anzeigten. Was war passiert?

Beim ersten Fall hatte ich in Mozilla Lightning einen Kalendereintrag mit einer speziellen (customized) Wiederholungsregel angelegt (letzter Mittwoch im Monat). Das schmeckt dem iOS Kalender (egal ob Version 4.x oder 5.x) gar nicht – genauso wenig wie dem Programm iCal (3.x) unter Mac OS X (Snow Leopard). In diesem Fall blieb mir nichts anderes übrig als auf diese Wiederholungsregel zu verzichten.
Darauf gekommen bin ich relativ schnell, da ich einfach die letzten Einträge die ich vorgenommen habe sukzessiv zurückgenommen habe.

Der zweite Fall war etwas schwieriger. Am Sonntag erzeugte ich noch munter Einträge (3 Wochen im Voraus) in Mozilla Lightning, alles ok. Beim Blick auf den iOS Kalender fiel mir nichts auf – ich hatte aber eh nur die momentane Woche in Beobachtung.
Das Problem bemerkte ich später wiederum an Lightning, der jetzt einige Einträge gar nicht mehr anzeigte – auch der laufenden Woche. Ausserdem waren die Einträge die ich am Sonntag vornahm, weg (die übrigens im iOS Kalender gar nicht auftauchten).
Was war den hier passiert? Eine Beobachtung des Apache Log ergab, dass sehr wohl alle Kalenderänderungen mit
"PUT /davical/caldav.php/<myCalender>/<current-ID.ics> HTTP/1.1 ..."
immer noch übertragen werden. Einträge löschen wurde übrigens noch sauber synchronisiert. Eine erstmalige Synchro auf einem Mac OS X Rechner mit iCal schlug fehl, nichts wurde übertragen.
Die Lösung brachte eine erstmalige Synchro mit dem KDE Kontact Kalender – der holte alle (auch die verschwundenen) Einträge hervor und zeigte sie an. Und in einem, der am Sonntag angelegten Einträge, befand sich im Beschreibungstext ein Sonderzeichen (falsche Kodierung), welches mit Copy/Paste von einem Webartikel reinkopiert wurde. Nachdem ich das Zeichen entfernte, klappte die Synchro anstandslos auf allen Clients. Daher kann ich nur empfehlen keine Texte aus einer Quelle zu kopieren, bei der die Zeichensatzkodierung nicht mit dem des Kalender-Clients übereinstimmt. Gerade auf Webseiten ist die Kodierung oft sehr unterschiedlich.
Die fast noch wichtigere Erkenntnis ist, dass schnelles Fehlertracking nicht mit einer SW Monokultur zu schaffen ist. Je heterogener die SW Landschaft (Clients) ist, desto robuster läuft der Netzwerkdienst.

CardDAV

Nachdem ich CalDAV zur Synchronisierung von Kalendern schon einige Zeit im Einsatz habe, stolperte ich erst kürzlich über das Adressbuch-Pendant CardDAV. Das Protokoll – ein weiteres der DAV Familie – ist erst im Request For Comments Status, aber dieser ist quasi schon abgeschlossen.
Interessant wurde CardDAV für mich als ich hörte, dass Davical – der CalDAV Server den ich einsetze – seit einiger Zeit (Version 0.9.9.4) auch CardDAV vollständig beherrscht.

Und was gibt es schöneres als synchronisierte Adressdaten?

Ich muss noch dazusagen, dass ich immer noch einen openLDAP Adressserver laufen habe, welcher aber an 2 Krankheiten leidet (und das schon seit Jahren)

  1. Es gibt kein verbindliches Personenschema für die Applikationen (Mozilla verwendet ein anderes als Apple.. und das wiederum hat nichts gemeinsam mit anderen Welten der kommerziellen LDAP Server)
  2. Schreibunterstützung: nada – am besten man programmiert sich seinen eigenen LDAP Client – aber da der Tag nur 24h hat…

Ok – genug geschwafelt: CardDAV rocks – es geht alles

..aber nur nach stundenlanger Frickelei (es lohnt sich aber, also dranbleiben).

  1. Davical updaten, falls nicht schon geschehen. Folgende Stolperfallen warten:
    Ev. sind die User davical_app und davical_dba noch nicht angelegt (kommt bei sehr alter davicaldb vor). In diesem Fall mit
    psql -qXAt -c "CREATE USER davical_app NOCREATEDB NOCREATEROLE;" template1
    psql -qXAt -c "CREATE USER davical_dba NOCREATEDB NOCREATEROLE;" template1
    die User anlegen.
    Einige Sequenzen und Tables gehören ev. direkt postgres anstatt davical_dba – mit folgendem Befehl wird bspw. hier die Sequenz dav_id_seq geändert:
    psql davical -c "ALTER SEQUENCE dav_id_seq OWNER TO davical_dba"
  2. Das Anlegen einer neuen Collection (Principal Collection) ist ganz einfach.
    In der neugestalteten Webseite einfach Ist ein Kalender ab- und Ist ein Adressbuch anhacken. Der Name der Collection ist natürlich wählbar – es empfiehlt sich z.B. „contacts

    Konkret ist dann diese Kollektion (das Adressbuch) unter /davical/caldav.php/USERNAME/contacts
    auf dem Server zu erreichen.

  3. Mac OS X Adressbuch anschliessen
    Oh ja – wenn man mittels SSL drauzugreift, dann gibts Probleme.
    Im der Applikation Adressbuch kann man zwar ein CardDAV Account anlegen bei dem man die Authorisierungsdaten und den kompletten Serverpfad (s.o.) eingeben kann, man läuft aber immer auf eine Fehlermeldung hinaus.
    Die Lösung ist, zweimal „Create“ anzuklicken um den fehlerhaften Account anzulegen.

    Dann editiert man manuell folgende Datei:

    ~/Library/Application Support/AddressBook/Sources/<UNIQUE-ID>/Configuration.plist
    Dort trägt man unter Server String die komplette URL ein.
    https://SERVERNAME/davical/caldav.php/USERNAME/contacts
    Am besten modifiziert man noch das Feld HaveWriteAccess auf den Wert auf „1“

  4. iPhone Konfigurieren
    Das geht im Falle eines SSL Zugriffes NICHT am Handy selber. Es geht nur über das iPhone Configuration Utility welches man von Apple herunterladen muss.
    Dort erstellt man einen neues Konfigurationsprofil mit einem CardDAV Account und installiert dieses dann auf dem angeschlossenen iPhone (es beeinträchtigt ein ev. vorhandenes Profil nicht!)
    Das Konfigurationsprofil innerhalb dieses Programmes erlaubt die komplette Angabe einer URL (Principal URL).
  5. KDE / akonadi
    Unglaublich – es geht einfach – man muss nur erstmal draufkommen wie.
    In den KDE Systemeinstellungen kann man bei
    Persönliche Informationen -> Einrichtung der Akonadi Resourcen -> GroupDAV Resourcen
    u.a. CardDAV und CalDAV Anschlüsse einrichten, welche dann von allen akonadi-aware Programmen (wie KMail oder Adressbook) genutzt werden kann.

Was noch fehlt ist ein nativer Thunderbird CardDAV Anschluss, es soll über ein 3rd Party Produkt names SoGo gehen.. aber ich sehe grade keinen Grund es auszuprobieren. Stattdessen geniesse ich mit CardDAV eine weitere Perle der OpenSource Welt.

cups-polld im busy-loop

Falls man in seiner CUPS Konfiguration einen Host permanent abfrägt (BrowsePoll), und CUPS der Version 1.4.x einsetzt, kann es passieren, dass bei länger aussetzender Netzwerkverbindung zum BrowsePoll Zielrechner der cups-polld Prozess in einen busy-loop gerät aus dem der Prozess nicht mehr herauskommt.

Dies kann v.a. auftreten, wenn der CUPS Client sich mit WLAN zum CUPS Server verbindet. Das Fehlverhalten merkt man dann an 100% Auslastung eines CPU-Cores durch cups-polld. Selber konnte ich dass seit längerem auf Mac OS X Clients erleben.
Mac OS X 10.6.8 beinhaltet derzeit CUPS 1.4.7, während Mac OS X Lion CUPS 1.5.x einsetzt, welches ein Bugfix für dieses Problem beinhaltet.

Eine genaue Fehlerbeschreibgung und einen source patch gibt es auf den Bugs Seiten des Debian Projektes
Die Seite wiederum widmet sich aber dem Problem unter Linux – um das Problem auf Mac OS X

  1. reproduzierbar zu machen (und damit testbar)
  2. zu lösen

stellte ich folgende Vorgehensweise auf:

  1. Auf dem Mac OS X Client wird der cups-polld Prozess mangels strace mit der Aktivitätsanzeige beobachtet
  2. Im Normallzustand war der letzte Call von cups-polld:
    _semwait_signal(..)
  3. Auf Linux Seite wird nun die erste Firewallregel geschaltet
    iptables -I OUTPUT 1 -p tcp -d --sport ipp -j REJECT --reject-with tcp-reset
  4. Danach wartet man bis der cups-polld Prozess in die Phase
    recvfrom(...)
    kommt, danach wird auf Linux noch die 2 FW Regel gestartet.
  5. iptables -I INPUT 1 -p tcp -s --dport ipp -j REJECT --reject-with tcp-reset
  6. Danach wird nach ca. 7 Minuten der cups-polld Prozess in den busy-loop übergehen.

Jetzt muss CUPS mit dem auf den Debian Seiten verfügbaren Patch versehen werden. Dazu lädt man derzeit auf den CUPS Seiten die letze 1.4 Version herunter (1.4.8) und patcht diesen. Der Patch beschränkt sich auf die Datei cups/request.c und lautet folgendermassen

+ {
+ status = httpUpdate(http);
+ }
+- while (http->state == HTTP_POST_RECV);
++ while (status != HTTP_ERROR && http->state == HTTP_POST_RECV);
+
+ DEBUG_printf(("2cupsGetResponse: status=%d", status));
+

Beim anschliessenden kompilieren unter Mac OS X ist natürlich die Entwicklungsumgebung notwendig, zumindest die gcc toolchain auf der Kommandozeile. Folgende Schritte sind notwendig:

  • ./configure CFLAGS=“-arch i386 -arch x86_64″ LDFLAGS=“-arch i386 -arch x86_64″

    Dies führt zum Kompilat von 32- und 64bit Varianten der Objects, der statischen Archive und von executables.

  • Das Mischkompilat (Universal Binary für 32- und 64bit Varianten) ist notwendig um die zentrale Bibliothek libcups.2.dylib für beide Architekturen anzubieten. Ansonsten würde so ziemlich alle 32bit Applikationen beim Start crashen.

    Leider reicht das o.g. configure nicht aus um die dynamische Bibliothek auch auf 32bit zum Kompilieren (auf einem aktuellen 64bit Rechner). LDFLAGS greift hier nicht – deshalb muss manuell in der Datei Makedefs in der Zeile ARCHFLAGS ebenfalls -arch i386 -arch x86_64 eingetragen werden. Danach nur noch

  • make
  • make install

Jetzt noch ein bischen zittern beim Neustart und dann war’s das schon,
hier ein fertiges CUPS 1.4.8 Paket für Mac OS X Snow Leopard OHNE JEGLICHE GEWÄHR!

stty -a < /dev/ttyS....

hängt? Ebenso der Befehl

echo „hallo“ > /dev/ttyS…

?? Dann ist wahrscheinlich die Data Carrier Detect Line (DCD) auf High, so dass diese beiden Kommandos auf ewig warten bis die DCD auf Low schaltet.

Umgehen kann man das bei dem ersten Befehl mit

stty -a -F /dev/ttyS...

Dieser benutzt beim open den O_NDELAY Flag, welcher gleichbedeutend zum O_NONBLOCKING ist.
Prinzipiell sollte man aber bei der termios Struktur das c_cflag CLOCAL setzen, welches die Modem Status Lines ignoriert. Wird dieses Flag nicht gesetzt und schreibt die termios Struktur mittels tcsetattr an die serielle Schnittstelle zurück, hat DCD high und obiges Problem tritt auf.