Docker Links in 'windowsfilters'

Dank Docker war bei mir vor ein paar Wochen eine Reparaturinstalltion notwendig geworden.
Offiziell war ich natürlich selbst daran Schuld, weil man den von Docker angelegten windowsfilters Ordner auf gar keinen Fall selbst löschen darf!

Grund genug sich das Thema mal etwas genauer anzusehen …


Docker für Windows Server speichert seine Images standardmäßig im Verzeichnis c:\ProgramData\docker\windowsfilters.
Und damit das richtig undurchschaubar wird, werden tausende Links im Dateisystem angelegt, damit alle Docker-Container auf “das gleiche” Basissystem verweisen (verlinken) können.

Wenn man Docker deinstalliert, bleibt dieser Ordner gerne übrig und sein Inhalt lässt sich großteils nicht löschen, weil bei den Dateien sowohl der Administrators Gruppe wie auch dem LocalSystem sämtliche Schreib und Löschrechte fehlen.

Die offizielle Lösung, die mir ein sehr lieber Kollege empfohlen hat, lautet docker-ci-zap.
Der dokumentierte Aufruf .\docker-ci-zap.exe -folder "C:\ProgramData\docker" löscht das ganze Verzeichnis inklusive dem windowsfilters Ordner über einige spezielle API Funktionen im Systemkern.

Doch wenn man per Konfiguration die Speicherung wo anders hin verlagert hat (z.B.: per Konfiguration in c:\ProgramData\docker\config\daemon.json) aber schon vorher Images geladen hat, dann stürzt (bei mir zumindest) das Tool docker-ci-zap einfach ab und macht gar nichts.

Auf keinen Fall den Ordner direkt löschen!

Tja, als Admin löst man das Problem von nicht-löschbaren Ordnern gerne mit so etwas wie:
icacls C:\ProgramData\docker\windowsfilters /grant Administrators:F /Q /C /T
Dann erhält die Admin-Gruppe auf alles Schreibrechte und das Löschen kann beginnen (manchmal muss man auch noch den Besitz übernehmen).
Und damit löscht man sich auch gleich die halbe Windows Umgebung weg. Zumindest fehlen aber diverse Benutzerprofile und Registry-Hive-Dateien.

Denn innerhalb der Images gibt es diverse Dateisystem-Links. Man nennt sie auch “symbolische Verknüpfungen”, die in NTFS durch so genannte Reparse-Points umgesetzt werden.

Während innerhalb einer Docker-Session ein Link auf C:\Windows auf einen Ordner innerhalb der windowsfilters verweist, glaubt aber die Windows-API von außerhalb, dass die Link-Verfolgung auf dem realen C:\Windows weiterläuft.

Und so löscht man sich mit einem
rmdir /s /q c:\ProgramData\docker\windowsfilters wichtige Dateien des Host-Systems weg. Zwar lassen sich geladene DLLs und Programme nicht löschen, aber diverse INF und MANIFEST Dateien in System32 wandern still und heimlich ins Nirvana. Vor allem sind C:\Users, C:\ProgramData und C:\Program Files betroffen.

Ich weiß nicht, welche Dateien es am Ende genau trifft, aber es reichte aus, dass mein System nicht mehr booten konnte. Das war mit der Systemwiederherstellung zwar reparierbar, aber Spaß machte mir diese Aktion absolut nicht.
Vor allem weil es meinen Rechner in der Firma betraf.

Lösungsansatz

Tatsächlich konnte ich dann nicht widerstehen und begann die APIs zu studieren und ließ die Ergebnisse sofort ins GATE Projekt einfließen. Junctions, “SymLinks” und das von Docker und Hyper-V benutzte “HCM” (Host Compute Service) bauen alle auf Reparse-Points auf und ein jeder dieser Verzeichnis-Einträge hat das Attribute FILE_ATTRIBUTE_REPARSE_POINT gesetzt.
Und das bekommt man per GetAttributes() oder FindFirstFile() mitgeteilt.

Das Schöne dabei ist, dass klassische Windows APIs wie DeleteFile() und RemoveDirectory() im Normalfall nur den Reparse-Point löschen und nicht das Ziel, wo dieser hinzeigt.

Bei einer Standard-Löschung gehen die Tools durch alle Unterverzeichnisse und löschen zuerst deren Inhalt und am Ende erst das Verzeichnis selbst. Deshalb wird auch das reale C:\ Laufwerk durchlöchert, wenn man nur windowsfilters wegputzen will.

Die Lösung ist also bei jedem windowsfilters Unterverzeichnis zu prüfen, ob dieses ein Reparse-Point ist. Wenn ja, dann sollte RemoveDirectory() funktionieren, obwohl der Inhalt gar nicht leer ist. Und bei allen Nicht-Reparse-Points geht man wie üblich vor.

Auf diese Weise wird nur “innerhalb” des Docker-Images gelöscht.

Aber Achtung! Diese Angabe ist ohne Gewähr. Auch durch dieses Verfahren kann das laufenden System beschädigt oder zumindest beeinträchtigt werden. Anwendung auf eigene Gefahr!

Fazit

Bei einem Test der Link-Lösch-Prozedur wurde mein privater Docker-Server scheinbar nicht beschädigt und ich konnte 11 GB auf C:\ freigeben, weil die Images ohnehin auf D:\ nochmals heruntergeladen wurden. Ein Packages Verzeichnis ließ sich trotzdem nicht Löschen, aber das ignoriere ich vorläufig mal.

Ich habe also wieder etwas gelernt und dem GATE Framework Reparse-Points beigebracht.
Interessant dabei ist, dass Windows zwischen Datei und Verzeichnis-Links per API unterscheidet, während POSIX SymLinks immer wie Dateien mit unlink() löscht und rmdir() ausschließlich bei realen leeren Verzeichnissen funktioniert.

Angefressen bin ich trotzdem!
Von Docker hätte ich erwartet, dass es seine eigenen “Reinigungskräfte” mitbringt. Wie die Netzsuche zeigte, haben sich auch schon andere Leute wegen Docker einen PC oder einen Server zerschossen. Und eine gute Software sollte solche “Missverständnisse” im Dateisystem erst gar nicht möglich machen.