iX 8/2024
S. 58
Titel
Administration

Die GNU Coreutils in Rust reimplementiert

Als Investition in die Zukunft sieht der uutils-coreutils-Entwickler Daniel Hofstetter seine Arbeit an der Reimplementierung der GNU Coreutils in Rust. Im iX-Interview erklärt er, warum.

Von Susanne Nolte

Mit den uutils-coreutils will ein Team von Rust-Entwicklern um den Debian-Entwickler Sylvestre Ledru den altehrwürdigen, etwa fünfzig Jahre alten GNU Coreutils eine Frischzellenkur verabreichen: In einer modernen Sprache geschrieben sollen sie bei fast vollständiger Kompatibilität auch auf neuen Plattformen als gewohnte und geschätzte Adminwerkzeuge ihren Dienst verrichten. Im iX-Interview stand uutils-coreutils-Entwickler Daniel Hofstetter Rede und Antwort.

iX: Daniel, du arbeitest unter anderem an den uutils-coreutils, also den in Rust neu codierten Coreutils. Wie bist du auf Rust gekommen?

Daniel: Meine ersten Schritte mit Rust machte ich vor rund 10 Jahren, nachdem ich dank hackernews erstmals auf das Rust-Projekt aufmerksam wurde. Bei diesen ersten Schritten blieb es dann aber auch. Erst Jahre später begann ich mich erneut mit Rust zu beschäftigen und verwendete Rust für „Advent of Code“ (siehe ix.de/z63u, Anm. d. Red.). Diesmal war ich positiv überrascht, wie angenehm das Arbeiten damit war, trotz anfänglicher Schwierigkeiten mit dem Borrow Checker. Seither verwende ich Rust.

iX: Was war die Intention, die Coreutils in Rust neu zu schreiben?

Daniel: Die ursprüngliche Intention war, diese grundlegende Software in einer modernen Sprache neu zu schreiben. Die erste Version der Coreutils wurde immerhin bereits Anfang der 70er-Jahre veröffentlicht. Ein weiteres Ziel war, die Coreutils auf anderen Plattformen verfügbar zu machen. Rust bot sich als moderne, plattformunabhängige Sprache für Systemprogrammierung an, um diese Ziele zu erreichen.

Die Neuimplementierung kann daher als Investition in die Zukunft dieser wichtigen Tools betrachtet werden. Mit einer modernen Programmiersprache ist es einfacher, neue Contributors zu gewinnen.

iX: Wo liegen für dich die wichtigsten Unterschiede zwischen Rust und C?

Daniel: Für mich liegt der wichtigste Unterschied in der Developer Experience. Diese umfasst die Dokumentation, die Syntax, die Fehlermeldungen des Compilers, das Design der Standardbibliothek und das Tooling mit cargo, clippy und Co. Hier wird deutlich, dass Rust Jahrzehnte nach C entstanden ist.

Rust behebt auch einige der Kernprobleme von C und C++. Zum Beispiel verhindert das Speicherverwaltungssystem von Rust das Auftreten vieler klassischer Fehler. Auch die parallele Programmierung ist jetzt sicherer und einfacher zu handhaben.

iX: Welche Unterschiede ergeben sich daraus beim Programmieren, etwa beim Speicher-Handling, den Systembibliotheken und dem Dateisystem-Handling?

Daniel: Rust bietet dank cargo eine sehr einfache Möglichkeit, neue Abhängigkeiten zu einem Projekt hinzuzufügen. Wenn wir ein neues Feature für eines der Tools benötigen, schauen wir zuerst, ob bereits eine entsprechende Bibliothek existiert. Falls ja, reicht häufig ein cargo add, um sie zum Projekt hinzuzufügen. Bei einem plattformübergreifenden C-Projekt ist dies mühsamer.

Im Gegensatz zu C bietet Rust in der Standardbibliothek eine plattformübergreifende API für das Dateisystem-Handling an. Dies erleichtert unsere Arbeit, die Tools auf unterschiedliche Plattformen zu portieren, da viele der Tools wie cp, mv oder cat diese API verwenden.

iX: Rust soll ja vor allem mehr Sicherheit bieten. Inwieweit und wodurch werden ein ls, ps oder du dadurch sicherer als ihre GNU-Pendants?

Daniel: Der Vorteil von Rust dürfte in diesem Fall eher theoretischer Natur sein. Die GNU Coreutils gibt es bereits so lange, dass in der Zwischenzeit die meisten Bugs entdeckt und behoben wurden, die es mit Rust wahrscheinlich nicht gegeben hätte. Außerdem hat die GNU-Implementierung eine ausgezeichnete Erfolgsbilanz in Bezug auf die Sicherheit, mit nur wenigen CVEs. Die GNU-Entwickler machen eine sehr gute Arbeit.

iX: Mit „Unsafe Rust“ lassen sich die strikten Regeln von „Safe Rust“ so weit lockern, dass auch unsichere Konstrukte wieder erlaubt sind. Verführt das nicht dazu, das immer dann zu nutzen, wenn das strikte Ownership-Konzept zu mühselig ist? Und wird Rust dadurch nicht wieder unsicher?

Daniel: Natürlich wird Rust potenziell unsicherer, je häufiger ich „Unsafe Rust“ verwende. Aber ich denke, die meisten Rust-Entwickler:innen setzen „Unsafe Rust“ mit Bedacht ein, wenn es unumgänglich ist.

Bestimmte Programme erfordern zum Beispiel Aufrufe von Systembibliotheken, und in solchen Fällen sind die unsicheren Aufrufe auf das unbedingt Notwendige beschränkt.

Darüber hinaus fördert das Design von Rust die Verwendung von sicheren Abstraktionen. Entwickler kapseln unsicheren Code in der Regel in sicheren Wrappern und minimieren so das Risiko von undefiniertem Verhalten.

iX: Wenn ich mich dank des automatisierten Speichermanagements von Rust um bestimmte Dinge nicht mehr kümmern muss, muss ich sie auch nicht mehr verstehen. Birgt das nicht die Gefahr, dass etwa durch unsafe-Code verursachte Lecks noch schwerer auffindbar sind?

Daniel: Diese Gefahr besteht. Ich sehe dies bei mir selbst. Ich habe C zwar während des Studiums kennengelernt, aber seither nicht mehr verwendet. Und ich hätte wohl auch Schwierigkeiten, solche Lecks zu finden, da mir die Erfahrung in diesem Bereich fehlt.

Allerdings ist dies in der Rust-Welt nur selten ein Problem. Unsichere Aufrufe beschränken sich in der Regel auf wichtige Bibliotheken wie libc, die mit Tools wie AddressSanitizer aka ASan und verschiedenen statischen Analyzern rigoros getestet werden. Diese Werkzeuge helfen dabei, Speicherlecks und andere Probleme zu erkennen und zu verhindern.

iX: Ein Aspekt der Sicherheit ist der Reifegrad einer Software. Nun ist die Bugfix-Historie der Rust-Tools entsprechend ihrem Alter recht kurz, die der C-Tools sehr lang, der Unterschied im Reifegrad also sehr hoch. Wie schätzt du den Stand jetzt und in absehbarer Zukunft ein?

Daniel: Ich denke, die Rust-Tools durchlaufen dieselben Phasen wie die C-Tools. Anfänglich, während der Entwicklungsphase, gibt es viele Bugs, und je mehr es Richtung Maintenance-Phase geht, desto mehr nimmt die Zahl der Bugs ab.

Auch wird die Programmierkultur der Rust-Entwickler wahrscheinlich durch verschiedene moderne Ansätze beeinflusst, wie regelmäßige Releases, ein integriertes Testframework und ein einfaches Upgrade von Abhängigkeiten dank starker Typprüfungen.

iX: Welche Unterschiede gibt es zwischen den uutils-coreutils und den GNU Coreutils bei der Codemenge und bei den Laufzeiten?

Daniel: Die Anzahl der Codezeilen zu vergleichen ist nicht wirklich sinnvoll. Wir verwenden für uutils-coreutils viele externe Bibliotheken. Beispielsweise nutzen wir lscolors für die Farbverwaltung von ls. Parallel dazu unterstützen wir mehr Plattformen als die GNU-Implementierung.

Was die Laufzeiten angeht, so hängt es von den einzelnen Tools und deren Verwendung ab. Manchmal sind wir schneller, manchmal langsamer. Dies liegt daran, dass unser Fokus vorerst auf der Kompatibilität liegt und nicht auf der Performance. Sobald wir die – fast – vollständige Kompatibilität erreicht haben, werden wir an der Performance der wichtigsten Tools arbeiten.

iX: Für die uutils-coreutils hat Sylvestre Ledru die Kompatibilität als Ziel ja hoch priorisiert. Wie ist der Stand der Dinge?

Daniel: Bei verschiedenen Tools wie cksum und wc haben wir das Ziel erreicht. Doch wenn wir das Gesamtprojekt betrachten, dann liegt noch einige Arbeit vor uns. Von den etwas mehr als 600 Testdateien der GNU-Testsuite scheitern noch rund 100 Testdateien an mindestens einem Test.

Bei einigen dieser Tests sind die Fehler leicht zu beheben, bei anderen dauert es länger, und einige können vermutlich ignoriert werden. Wer benutzt zum Beispiel pr, um Textdateien zum Drucken zu konvertieren?

iX: Die GNU-Programme werden mitunter als Bloatware kritisiert. Als Beispiel müssen dann immer true und false herhalten. Die OpenBSD-Versionen bestehen aus dem schlichten:

int main(int argc, char *argv[])
{
   return (0);
}

respektive return (1). Immerhin müssen sie nur die Werte 0 oder 1 zurückgeben. Dagegen bestehen die GNU-Versionen von true aus 64 Zeilen und von false aus zwei Zeilen, die auf true verweisen. Die Rust-Versionen von true und false hingegen kommen auf zweimal 60+ – viele davon also doppelt. Dafür kennen die Rust- und die GNU-Versionen beider Befehle die Optionen --help und --version. Werden da die uutils-Versionen nicht einfach nur um der GNU-Kompatibilität willen auch zur Bloatware, bestückt mit unnötigen Dopplern?

Daniel: In diesem konkreten Beispiel würde ich nicht unbedingt von Bloatware sprechen. Für mich ergibt es Sinn, dass auch true und false die Optionen --help und --version besitzen, wie alle anderen Tools auch.

Außerdem ist es wichtig, den Begriff Bloatware zu hinterfragen. All die Optionen, die im Lauf der Zeit hinzugefügt wurden, sind eingeführt worden, um spezifische Benutzerbedürfnisse zu erfüllen. Die bestehende Komplexität des Codes ergibt sich oft aus der Notwendigkeit, alle möglichen Kombinationen dieser Optionen zu behandeln. So bieten beispielsweise Tools wie ls und cp eine Vielzahl von Optionen, um verschiedenen Anforderungen gerecht zu werden, was ihren Nutzen eher erhöht als zu unnötigem Bloat beiträgt.

iX: Welche Vorteile nennst du Admins, wenn sie dich fragen, warum sie die uutils-coreutils statt der GNU Coreutils verwenden sollen?

Daniel: Zunächst möchten wir hervorheben, dass unsere Implementierung bereits von einigen Leuten in der Produktion verwendet wird. Unsere Implementation bietet verschiedene Vorteile gegenüber der GNU-Implementation:

  • Sie ist wirklich plattformübergreifend. Wir unterstützen Linux, macOS, Windows, Android und FreeBSD. Wir wissen, dass unsere Implementierung auch auf anderen Betriebssystemen gut funktioniert. Sie ist auch architekturübergreifend: Sie läuft dort, wo der Rust-Compiler läuft.
  • Bei einigen Tools sind wir performanter. Aber, um fair zu sein, bei anderen sind wir auch langsamer.
  • Wenn sie relevant sind, zeigen wir bessere Fehlermeldungen an als GNU.
  • Wir bieten einige Extensions. Zum Beispiel wird --progress bei cp und mv sehr geschätzt. Weitere Extensions sind dokumentiert (siehe ix.de/z63u, Anm. d. Red.).
  • Wir verwenden GitHub, wodurch es einfach ist, Code beizusteuern (siehe ix.de/z63u, Anm. d. Red.).
  • Für manche – nicht für uns – kann die Tatsache, dass die Lizenz MIT und nicht GPL ist, ein Vorteil sein.

iX: Nun sind die Systembibliotheken auf verschiedenen OS doch recht unterschiedlich. Welche Auswirkungen hat das, machen sich dadurch Qualitätsunterschiede bemerkbar?

Daniel: Rust zeichnet sich durch die Abstraktion dieser Unterschiede aus und verfügt über eine leistungsstarke Standardbibliothek, die plattformübergreifend entwickelt wurde. Außerdem sind viele Bibliotheken, auf die wir angewiesen sind, ebenfalls plattformübergreifend.

Trotzdem müssen wir einige plattformspezifische Unterschiede beachten. Zum Beispiel werden Hardlinks unter Android nicht vollständig unterstützt, also müssen wir das berücksichtigen. Ebenso aktivieren wir SELinux-Funktionen nur auf Betriebssystemen, auf denen sie relevant sind.

Insgesamt macht Rust, in Kombination mit unserem CI-System, das über mehrere Betriebssysteme hinweg testet, den Umgang mit diesen Unterschieden relativ einfach und gewährleistet eine konsistente Qualität über alle Plattformen hinweg.

iX: Welche Pläne und Erfahrungen hast du mit Portierungen auf andere OS?

Daniel: Momentan unterstützen wir Linux, als Packages in den meisten Distributionen enthalten, macOS, Windows, FreeBSD und Android. Demnächst dürfte noch OpenBSD hinzukommen. Weitere Plattformen sind momentan nicht geplant. Sollte der Wunsch für weitere Plattformen auftauchen, so sind wir offen dafür.

Die wichtigste Erfahrung bei der Portierung ist sicherlich, dass sie ein CI-System und die Anpassung bestehender Tests an die neue Plattform erfordert. Im Allgemeinen ist dies aber recht einfach zu bewerkstelligen.

iX: Nun gibt es viele weitere – recht verstreute – Rust-Projekte, die die GNU Coreutils nicht wie ihr eins zu eins reimplementieren wollen, quasi Coreutils-Geschwister, die anders heißen und sich teilweise anders verhalten. Wie beurteilst du die Vorhaben der Entwickler und deren Tools? Wie bewertest du das Verhältnis zwischen diesen Rust-Tools und den uutils?

Daniel: Diese Projekte sind interessant und eine Bereicherung. Sie zeigen auf, wie einzelne Tools der Coreutils aussehen könnten, wenn sie von Grund auf neu entwickelt werden. Programme wie fd-find, ripgrep, eza und bat sind ausgezeichnete Programme. Wir arbeiten mit ihnen bei einigen gemeinsam genutzten Bibliotheken zusammen.

iX: Welches Verbreitungsziel hast du euch für die uutils gesetzt?

Daniel: Zurzeit haben wir uns keine spezifischen Verbreitungsziele gesetzt. Unser Hauptaugenmerk liegt darauf, die Grundlagen für die Verbreitung zu schaffen, indem wir die – fast – vollständige Kompatibilität mit GNU Coreutils anstreben. Dies stellt sicher, dass die Benutzer problemlos zu uutils wechseln können.

Sobald wir diesen Grad an Kompatibilität erreicht haben, wird die Verbreitung und Akzeptanz natürlicherweise folgen. Unser Ziel ist es, uutils zu einem zuverlässigen Drop-in Replacement für die GNU Coreutils zu machen, damit die Benutzer von den Sicherheits- und Leistungsverbesserungen von Rust profitieren können.

iX: Denkst du, dass die Rust-Tools mittel- oder langfristig die C-Tools ersetzen werden? Etwa, dass die uutils-coreutils die GNU-Coreutils-Basispakete der Linux-Distributionen ablösen – analog dazu die findutils und diffutils – und die Rust-Geschwister wie dua, procs und eza in coreutils-opt-Paketen oder ähnlichen landen?

Daniel: Langfristig kann ich mir vorstellen, dass die eine oder andere Linux-Distribution die GNU Coreutils durch uutils-coreutils ablöst. Dieser Wandel wird vorangetrieben durch den steigenden Reifegrad von uutils-coreutils, zusammen mit der wachsenden Anerkennung der Vorteile von Rust in Bezug auf Sicherheit, Wartung, und Performance.

Das Interview führte Susanne Nolte. Wir danken Sylvestre Ledru für seine Unterstützung. (sun@ix.de)

Kommentieren