BTRFS-Recovery

Narf… Das war zu viel. Durch einen Umbau hatte sich offenbar ein Kabel im Lüfter meines Chipsatzes verheddert – keine gute Voraussetzung, wenn man mal eben Wine neu kompiliert. Es kommt, was kommen musste: Kernel panic.

Nunja, Fehler gefunden, weiter gehts. Nicht. Zwar startete das System, meine Home-Partition blieb aber verschwunden. Kurzer Blick auf den Aufbau: Mein Home liegt auf einer separaten Partition, ist mit LUKS verschlüsselt und nutzt – da ich die gigabyteweise IRC-Logs vorratsdatenspeichere und transparente Komprimierung wollte – BTRFS. Das Log verrät: LUKS selbst läuft, mount hingegen scheint sich aufzuhängen. In dmesg sieht alles OK aus, nur die normalen BTRFS-Meldungen, keine Fehler.

Strange, aber Reboot fixed ja fast alles, also schnell die magischen Zeichen eingetippt und zur Sicherheit im Rescue-Mode gestartet. Dort per Hand cryptsetup aufgerufen und erst mal btrfsck losgelassen. Kein Fehler. Mount? Nope.

OK, nun bin ich genervt, vor allem da die Kiste nicht unbedingt schnell bootet. Qemu to the rescue, also schnell ein Debian geklont und die Partition vorgesetzt. Schlechte Idee. Die bei Debian genutzten BTRFS-Libs/Module sind eine Nummer zu alt und können mit der Partition gar nichts anfangen. Ein Arch-Download später kann es weiter gehen.

Erst einmal ein normaler mount in der VM – vielleicht ist ja nur der Host kaputt, außerdem ist auf der CD ein älterer Kernel. Leider ohne erfolg, also VM-Reboot und den Hammer ausgepackt. Mit „mount -o recovery,nospace_cache“ lassen sich viele Fehler schon beheben, aber auch hier scheint der mount nichts zu vollziehen. Keine Reaktion, keine IO-Last, keine Fehlermeldung.

Die Lösung: btrfs_zero_log. Hiermit werden die offenen Transaktionen gelöscht. Die kann zwar zu Datenverlust von Änderungen, die kurz vor Absturz geschrieben wurden, führen, aber besser als gar keine Partition. Der mount in der VM war schlussendlich erfolgreich und auch der Host verfügte kurze Zeit später wieder über alle Dateien. Warum btrfsck das offenbar defekte Log nicht ankreidete ist mir allerdings ein Rätsel – sollte das nicht die Aufgabe des Tools sein?

Software als Windows-Service

Programme starten ist toll. Einige müssen aber immer wieder gestartet werden, hierzu nutzt man unter Windows so genannte „Dienste“. Leider sind das unter Windows NT (also auch z.B. Windows 7/8/10, Windows Server 2008/2012) keine einfachen Programme, sondern erfordern spezielle Registrierungen. Leider gent so etwas (soweit mir bekannt) nicht mit Hausmitteln.

Abhilfe schafft ein kleines Tool: winserv verwandelt ein beliebiges Programm in einen passenden Dienst. Hierbei gibt es folgende Modi:

install richtet ein Programm als Dienst ein
configure ändert einen vorhandenen Dienst
uninstall entfernt den Dienst

Zum Anlegen gibt man folgende Parameter:

-displayname Angezeigter Name des Dienstes
-description Beschreibung (das, was in der Diensteverwaltung in der zweiten Spalte steht)
-start Start-Art des Dienstes – „auto“ „demand“ oder „disabled“ (Bedeutung selbstredend, oder?)

Beispiel:

winserv install testdienst -displayname "Test-Dienst" -description "Test 123" -start auto c:\BLA.EXE

Alternativ kann man auch einen Geplanten Task mit dem Trigger „Bei Systemstart“ verwenden, hier muss man jedoch auf die komfortableren Prüf- und Verwaltungsfunktionen des Dienst-Managers verzichten.

Q’n’D Windows-Shutdown-Daemon in PHP

Alles in Deckung, ich mache wieder unschönes Zeugs. Ich hatte ja bereits erklärt wie man über RPC/Samba einen Windows-PC von Linux herunterfahren kann. Leider ist die Installation von Samba aus Embedded-Systemen eine eher ungünstige Sache und ist in meinem Fall wegen Speichermangel nicht drin. Allerdings ist sowohl auf dem Embedded-System als auch dem betroffenen Rechner PHP verfügbar. I aim to misbehave.

Auf dem herunterzufahrenden Windows-Rechner wird per PHP ein TCP-Socket geöffnet. Dieser Code basiert auf einem Beispiel von Michael Kliewe. Um Scriptkiddies nicht direkt Zugang zu geben ist etwas (sehr schlechte) Challenge-Response-Authentifizierung drin – nunja, ist kein kritisches System. Wird der Client bestätigt folgt ein simples Shutdown per exec. Nicht viel, aber schnell Fertig und funktioniert:

<?php
class SocketChatServer {
    private $address = '0.0.0.0';   // 0.0.0.0 means all available interfaces
    private $port = 12345;          // the TCP port that should be used
    private $maxClients = 10;
 
    private $clients;
    private $socket;
    
    private $salt = 'YourSalt';
    private $pass = 'YourSecret';
 
    public function __construct() {
        // Set time limit to indefinite execution
        set_time_limit(0);
        error_reporting(E_ALL ^ E_NOTICE);
    }
 
    public function start() {
        // Create a TCP Stream socket
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        // Bind the socket to an address/port
        socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
        socket_bind($this->socket, $this->address, $this->port);
        // Start listening for connections
        socket_listen($this->socket, $this->maxClients);
 
 
        $this->clients = array('0' => array('socket' => $this->socket));
 
        while (true) {
            // Setup clients listen socket for reading
            $read[0] = $this->socket;
            for($i=1; $i<count ($this->clients)+1; ++$i) {
                if($this->clients[$i] != NULL) {
                    $read[$i+1] = $this->clients[$i]['socket'];
                }
            }
 
            // Set up a blocking call to socket_select()
            $ready = socket_select($read, $write = NULL, $except = NULL, $tv_sec = NULL);
 
            /* if a new connection is being made add it to the client array */
            if(in_array($this->socket, $read)) {
                for($i=1; $i < $this->maxClients+1; ++$i) {
                    if(!isset($this->clients[$i])) {
                        $this->clients[$i]['socket'] = socket_accept($this->socket);
                        socket_getpeername($this->clients[$i]['socket'], $ip);
                        $this->clients[$i]['ipaddy'] = $ip;
 
                        socket_write($this->clients[$i]['socket'], 'Welcome to the Shutdown Control System'."\r\n");
                        socket_write($this->clients[$i]['socket'], 'Your personal code is '.md5($this->salt.strftime('%Y-%m-%d').$ip)."\r\n");
 
                        $this->log("New client #$i connected: " . $this->clients[$i]['ipaddy']);
                        break;
                    } elseif($i == $this->maxClients - 1) {
                        $this->log('Too many Clients connected!');
                    }
 
                    if($ready < 1) {
                        continue;
                    }
                }
            }
 
            // If a client is trying to write - handle it now
            for($i=1; $i<$this->maxClients+1; ++$i) {
                if(in_array($this->clients[$i]['socket'], $read)) {
                    $data = @socket_read($this->clients[$i]['socket'], 1024, PHP_NORMAL_READ);
 
                    if($data === FALSE) {
                        unset($this->clients[$i]);
                        $this->log('Client disconnected!');
                        continue;
                    }
 
                    $data = trim($data);
 
                    if(!empty($data)) {
                        switch ($data) {
                            case 'exit':
                            case 'quit':
                                socket_write($this->clients[$i]['socket'], "Disconnecting without any action, Goodbye.\r\n");
                                $this->log("Client #$i is exiting");
                                unset($this->clients[$i]);
                                continue;
                            case 'shutdown-'.md5($this->pass.md5($this->salt.strftime('%Y-%m-%d').$ip)):
                                // first write a message to all connected clients
                                for($j=1; $j < $this->maxClients+1; ++$j) {
                                    if(isset($this->clients[$j]['socket'])) {
                                        if($this->clients[$j]['socket'] != $this->socket) {
                                            socket_write($this->clients[$j]['socket'], "System will be shut down now...\r\n");
                                        }
                                    }
                                }
                                // Close the master sockets, server termination requested
                                exec('shutdown /s /t 15 /c "System wurde per Fernbedienung ausgeschaltet"');
                                $this->log("Terminated server (requested by client #$i)");
                                exit;
                            case 'whoami':
                                // first write a message to all connected clients
                                socket_write($this->clients[$i]['socket'], "You are registred with the source IP ".$this->clients[$i]['ipaddy']." - your Code was ".md5($this->salt.strftime('%Y-%m-%d').$ip)." \r\n");
                                
                                break(2);
                            default:
                                for($j=1; $j < $this->maxClients+1; ++$j) {
                                    if(isset($this->clients[$j]['socket'])) {
                                        if(($this->clients[$j]['socket'] != $this->socket)) {
                                            $this->log($this->clients[$i]['ipaddy'] . ' is sending a message to ' . $this->clients[$j]['ipaddy'] . '!');
                                            socket_write($this->clients[$j]['socket'], '[' . $this->clients[$i]['ipaddy'] . '] says: ' . $data . "\r\n");
                                        }
                                    }
                                }
                                break(2);
                        }
                    }
                }
            }
        } // end while
    }
 
    private function log($msg) {
        // instead of echoing to console we could write this to a database or a textfile
        echo "[".date('Y-m-d H:i:s')."] " . $msg . "\r\n";
    }
}

$srv = new SocketChatServer;
$srv->start();
?>

Der Client bastelt sich eine passende Authentifizierung und spielt Zündknopf:

function pc_shutdown() {                                                                                                                                                                                                              
                $pass = 'YoutSecret';                                                                                                                                                                                                         
                                                                                                                                                                                                                                              
                $fp = fsockopen('pc.name', 12345, $errno, $errstr, 2);                                                                                                                                                                              
                if(!$fp) return false;                                                                                                                                                                                                        
                                                                                                                                                                                                                                              
                $line = fgets($fp); //Greeting                                                                                                                                                                                                
                $line = fgets($fp); //Hash                                                                                                                                                                                                    
                                                                                                                                                                                                                                              
                preg_match('/code is ([\w\d]{32})/', $line, $match);                                                                                                                                                                          
                $seed = $match[1];                                                                                                                                                                                                            
                                                                                                                                                                                                                                              
                $cmd = 'shutdown-'.md5($pass.$seed)."\n";                                                                                                                                                                                     
                                                                                                                                                                                                                                              
                fwrite($fp, $cmd);                                                                                                                                                                                                            
                                                                                                                                                                                                                                              
                var_dump(fgets($fp));                                                                                                                                                                                                         
                                                                                                                                                                                                                                              
                fclose($fp);                                                                                                                                                                                                                  
        }

Hoffen wir, dass es läuft und ich nie wieder drauf schauen muss, denn irgendwo tut sowas selbst mir weh 😉

BitBastelei #154 – Microsoft Kommunikationssystem: Netzteil/Logikboard

BitBastelei #154 - Microsoft Kommunikationssystem: Netzteil/Logikboard

(243 MB) 00:18:28

2015-06-21 10:00 🛈

Während meine aktuellen Projekte entweder noch kein zufriedenstellendes Videoscript ergeben oder etwas unter dem Poststreik leiden muss wieder etwas aus der Konserve her: Im Müll fand sich das „Netzteil“ eines Kommunikationssystems von Microsoft. Mal schauen, was da denn wirklich drin steckt und ob es am Ende so „kaputt“ ist, wie der Fundort vermuten lässt.

SI3016 https://www.silabs.com/Support%20Documents/TechnicalDocs/si3016.pdf
TMS320C54x http://www.ti.com/lit/ug/spru307a/spru307a.pdf
CY22050 http://www.cypress.com/?docID=31116
M95512 http://pdf1.alldatasheet.com/datasheet-pdf/view/246004/STMICROELECTRONICS/M95512WDW3G.html
24LC64 http://pdf1.alldatasheet.com/datasheet-pdf/view/74861/MICROCHIP/24LC64.html
TI1020B http://www.ti.com/lit/ds/sles025b/sles025b.pdf
CS8427 http://www.cirrus.com/en/pubs/proDatasheet/CS8427_F5.pdf

Symantec Backup Exec: Bandstatus per PowerShell auslesen

Symantec Backup Exec ist eine in Windows-Umgebungen recht verbreitete Backup-Software. Seit einigen Versionen lassen sich große Teile auch über die „Microsoft-Bash“ PowerShell steuern. Für meinen Zweck wollte ich eine Liste aller im angeschlossenen Bandwechseler eingelegten Bänder und deren Status, so kann ich feststellen welche entnommen werden sollen. Im ersten Schritt muss das PowerShell-Modul geladen werden:

Import-Module 'C:\Program Files\Symantec\Backup Exec\Modules\BEMCLI\BEMCLI'

Nun wird die Liste der Bännder im Wechsler ausgelesen – hier lässt sich feststellen welches Band in welchem Slot des Wechslers verfügbar ist:

Get-BERoboticLibrarySlot -RoboticLibraryDevice "TapeLib01"

Name       SlotNumber IsCleaningSlot  Media
----       ---------- --------------  -----
Schacht 1  1          False           ADLR03L5
Schacht 2  2          False           ADLR05L5
Schacht 3  3          False           ADLR11L5
Schacht 4  4          False           ADLR13L5
Schacht 5  5          False           ADLR24L5
Schacht 6  6          False           ADLR31L5
Schacht 7  7          False           ADLR33L5
Schacht 8  8          False           ADLR48L5
Schacht 9  9          False           ADLR34L5
Schacht 10 10         False           ADLR41L5
Schacht 11 11         False           ADLR49L5
Schacht 12 12         False           ADLR60L5

Zuletzt werden die zugehörigen Banddaten wie z.B. die Dauer des Software-Schreibschutzes gelesen:

Get-BEMedia -MediaVault "Online-Bandmedien" -Verbose | Format-List Name,MediaSet,OverwriteProtectedUntilDate,OverwriteProtectedUntilDate -Force

Name                        : ADLR03L5
MediaSet                    : Monatssicherungen (12 Monate schreibschutz)
OverwriteProtectedUntilDate : 06.03.2016 02:39:53
[…]

Die Daten werden in meinem Fall am Ende einer externen Schnittstelle übergeben, welche die Bandstati sortiert und entsprechende Warnmeldungen zur Entnahme generieren kann.

FTP-Uploads per Shell-Script

Ab und an komme auch ich nicht drumrum mit Zielen zu arbeiten, welche keine SSH/SFTP-Verbindung zulassen, sondern nur per FTP(S) erreichbar sind. Grade beim Automatisieren über Shell-Scripte ist das reichlich unschön. Ein praktischer Client ist in diesem Fall das Urgestein ncftp. Mit dessen Tools lässt sich z.B. schnell ein ein entferntes Verzeichnis löschen und durch die lokale Kopie ersetzen:

echo 'rm foo/bar/*
rm foo/baz/*
rmdir foo/bar
rmdir foo/baz
' | ncftp -u $USER -p $PASS $HOST

ncftpput -R -u $USER -p $PASS $HOST /foo/* ~/dev/foo/*

Windows Shutdown per Linux

Ab und an möchte man entfernte Rechner mal runterfahren. Mit Linux-Zielen ist das per ssh üblicherweise kein Problem, möchte man jedoch von einem Linux-PC den Shutdown-Befehl an einen Windows-Rechner geben muss man schon einmal suchen. Abhilfe schafft wie üblich Samba, welches den „net“-Befehl mitbringt. Ein Shutdown sieht dann wie folgt aus:

net rpc SHUTDOWN -C "Grund für den Shutdown" -f -I 127.0.0.1 -U username%password

CUPS/foomatic/gutenprint: „Unable to get list of printer drivers: Success“

Eigenlich bin ich ja Verfechter papierloser Abläufe, leider sind viele Behörden damit nicht einverstanden oder schreiben sinnloserweise selbstgestrickte Vorgaben statt offener Standards vor. Zum Glück stehen hier noch ein paar Drucker aus den 80ern und 90ern rum, die nicht nach 10 Seiten das Zeitliche segnen. Also: Drucker raus, Papier rein. Error. Bei der Installation des Druckers auf meinen PCs – genauer der Suche nach möglichen Treibern – meldete CUPS teilweise folgende, sehr aussagekräftige, Fehlermeldung:

Unable to get list of printer drivers: Success

Auffällig dabei: Ein Perl-Script bezüglich Foomatic belagert zuvor einen CPU-Kern. Auch konnte ich bei einem System beobachten, dass der Fehler erst nach Installation zusätzlicher Treiber (foomatic-db & Co) auftrat, mit „leerer“ Treiber-Liste jedoch nicht. Der Fehler ist konstant, also sowohl in der Desktop-GUI als auch im CUPS-Webinterface reproduzierbar.

tl;dr: Don’t do gutenprint…

Schauen wir mal genauer auf den 100%-Prozess: Dieser wird vom Nutzer „daemon“ gestartet und hat kein nice-Level. Der genaue Aufruf lautet

/usr/bin/perl /usr/lib/cups/driver/foomatic list

Das Script selbst lässt sich auch auf der Konsole manuell starten. „list“ gibt hierbei nicht wirklich etwas – auch nach mehreren Minuten keine Ausgabe. „help“ am Ende zeigt die möglichen Optionen:

foomatic -A
foomatic -P
foomatic -p [-d ] [-w]
foomatic list
foomatic cat [-w]
foomatic -h

-A : show all Printer ID’s and compatible drivers
-P : show all Printer ID’s whose names and model
matched by the RE. For example:
-P HP will match all names with HP in them
-p : Printer ID
-d : Driver name
If the driver is not specified then the default driver
for the is used.
list : List all possible PPDs in the format needed by the
cups-driverd
cat : Generate PPD file appropriate to the .
Available CUPS PPD URIs are listed by
„foomatic list“.
-w : Generate PPD which is compatible with the CUPS PostScript
driver for Windows (GUI strings are limited to 39
characters).
-h : show help information

Leider scheint es – jedenfalls laut Hilfe – keinen verbose- oder debug-Modus zu geben. Strace to the rescue. Zu sehen nicht viel – die Treiber-Dateien werden nacheinander geladen, die letzten Zeilen lauten:

stat(„/usr/share/foomatic/db/source/driver/xes.xml“, {st_mode=S_IFREG|0644, st_size=522, …}) = 0
stat(„/usr/share/foomatic/db/source/driver/xes.xml“, {st_mode=S_IFREG|0644, st_size=522, …}) = 0
stat(„/usr/share/foomatic/db/source/driver/xes.xml“, {st_mode=S_IFREG|0644, st_size=522, …}) = 0
open(„/usr/share/foomatic/db/source/driver/xes.xml“, O_RDONLY) = 3
lseek(3, 0, SEEK_CUR) = 0
read(3, „\n

Dummerweise ist „xes.xml“ auch die alphabetisch letzte Datei, also eher kein Fehler darin zu vermuten. Narf. Nunja – räumen wir mal auf. Unter /usr/share/foomatic/db/source müssen die Dateien unter PPD, driver, opt und printer weichen. Success. Kind of. Der Foomatic-Prozess endet ohne Fehler, also muss einer der Treiber der Übeltäter sein. Als erstes wandert opt wieder zurück, hier ist nicht viel drin. Noch alles OK. Auch das zurückkopieren von PPD zeigt keine Änderung. Printer klingt interessanter, daher spiele ich hier jede Datei einzel zurück:

for i in * ;do mv $i ../printer && echo $i && (/usr/bin/perl /usr/lib/cups/driver/foomatic list || exit) ; done

Dauert zu lange – ich entscheide mich nur die XML-Dateien der für mich interessanten Hersteller zu nutzen. Läuft noch. Es folgt Drivers. Weniger dateien, trotzdem manuell. Erster Kandidat: necp6 – ein Treiber für meinen Nadeldrucker. Passt – foomatic list liefert nun eine Liste aller NEC-Nadeldrucker. Fehlt noch mein Laser. Da Gutenprint schnell sein soll kopiere ich deren XMLs – nichts geht mehr. Hört sich nach Verursacher an – kopieren wir alles außer Gutenprint: Passt. Auch in CUPS lässt sich nun der Drucker anlegen. Fehler Gefunden würde ich sagen…

Backup Exec: Speicherplatzbedarf bändigen

Symantec Backup Exec kennt seine Backups – leider für einige Fälle zu gut. Standardmäßig wird für jede Sicherung eine Liste der gesicherten Daten, also z.B. die Liste aller Dateien, im Katalog auf der Festplatte des Backupservers abgelegt. Wird das Zielmedium später überschrieben werden auch die Daten gelöscht – wer jedoch Langzeitsicherungen erstellt oder Sicherungsdatenträger aussortiert wird auf Dauer mit einem nicht unerheblichen Platzverbrauch konfrontiert. Mehrere hundert Gigabyte sind so schnell zusammen, welche üblicherweise unter %ProgramFiles%\Symantec\Backup Exec\Catalogs\*SERVERNAME* landen. Doch sind diese Daten wirklich noch notwendig? Ist es nicht auch akzeptabel bei Rücksicherungen, welche älter als einige Monate sind, einige Minuten länger zu warten und bei Bedarf den Katalog neu vom Zieldatenträger zu laden? Um hier Ordnung zu schaffen gibt es gleich drei Lösungen

1. Katalog verschieben

Wer noch an anderer Stelle genügend Platz hat und die Kataloge weiterhin im Direktzugriff haben möchte kann den Katalogordner an ein anderes Ziel verschieben. Die nötigen Infos sind unter TECH74582 zu finden – zwar für deutlich ältere Versionen, die Option sollte sich jedoch auch weiterhin in den Menüs verstecken.

2. Kataloglebensdauer einstellen

In den Optionen gibt es die Möglichkeit Dateilisten nach einer gewissen Zeitspanne zu verwerfen. Der zugehörige Artikel findet sich unter TECH7297 – die Option ist wie auch zuvor in den Menüs weiterhin zu finden. Leider greift die Option nur für neu angelegte Kataloge, ältere müssen ggf. manuell entfernt werden.

3. Manuelles löschen

Auch manuell lässt sich der Ordner aufräumen. Hierzu müssen erst alle Dienste des Backup Exec-Systems gestoppt werden. Im Anschluss löscht man alle *.fh und *.xml-Dateien im Katalogverzeichnis, welche älter als das gewünschte Datum sind. Andere Dateien sollen laut Symantec-Forum nicht gelöscht werden – zwar denke ich nicht, dass diese Aussage für Einträge mit gleichen UUIDs korrekt ist, da die übrigbleibenden Dateien jedoch nur wenige KB groß sind habe ich dies nicht weiter geprüft. Da nun Datenbank und Ordner inkonsistent sind ist ein Reinigungslauf notwendig, hierzu startet man im BE-Verzeichnis den Befehl „CatRebuildIndex.exe -r“ als Administrator. Dies kann einige Zeit in Anspruch nehmen.

Alles keine schönen Lösungen, aber irgendwann ist auch der größte Platz mal voll und Migrieren macht mit Windows keinen Spaß.

Gluon: Build vox x86-Images schlägt fehl

Seltsames Fehlerbild: Mit dem neuen Gluon-Release sind einige Hardwareplattformen hinzu gekommen, alle lassen sich Fehlerfrei bauen, nur die x86-basierten Images machen Probleme. Der Fehler ist im Debugmodus (make … V=s) schnell lokalisiert: Es wird versucht ein ext4-System zu konfigurieren, hierbei wird automatisch geprüft, ob das betroffene System irgendwo gemountet ist. In meinem Fall läuft der Build in einer chroot, da dort die mtab fehlt schlägt diese Prüfung und damit auch der Build fehl.

/home/openwrt/gluon-trunk/upstream/build/x86-generic/openwrt/staging_dir/host/bin/tune2fs  -O extents,uninit_bg,dir_index /home/openwrt/gluon-trunk/upstream/build/x86-generic/profiles/GENERIC/kernel/root.ext4
tune2fs 1.42.8 (20-Jun-2013)
ext2fs_check_if_mount: Can't check if filesystem is mounted due to missing mtab file while determining whether /home/openwrt/gluon-trunk/upstream/build/x86-generic/profiles/GENERIC/kernel/root.ext4 is mounted.
Makefile:269: recipe for target 'install' failed

Da in der chroot /proc verfügbar ist lässt sich das Problem mit einem einfachen Symlink lösen:

ln -sf /proc/mounts /etc/mtab

…und schon läuft der Build wie erwartet.

Nerd Inside