Programming lesson
Transparente Remote-Dateioperationen mit RPC in C – Ein Leitfaden für Checkpoint 1
Lerne, wie du mit Remote Procedure Calls (RPC) in C transparente Dateioperationen über das Netzwerk implementierst. Dieser Leitfaden fokussiert auf Checkpoint 1: Interposition und Logging mit LD_PRELOAD.
Einführung in transparente Remote-Dateioperationen
In verteilten Systemen greifen Programme oft auf Ressourcen zu, die sich auf entfernten Maschinen befinden. Statt jedes Mal manuell Netzwerkcode einzufügen, ermöglicht Remote Procedure Call (RPC) eine saubere Abstraktion: Der Client ruft eine Funktion auf, die wie eine lokale Funktion aussieht, aber tatsächlich über das Netzwerk einen Server anspricht. In diesem Projekt des Kurses 15.094 implementierst du ein solches RPC-System für Dateioperationen – in C unter Linux. Der Fokus liegt auf Checkpoint 1: Interposition und Logging mittels LD_PRELOAD.
Stell dir vor, du spielst ein Online-Spiel wie Fortnite und willst eine Datei mit deinem Spielstand auf einem zentralen Server speichern. Ohne RPC müsstest du für jeden Lese- oder Schreibvorgang selbst Socket-Code schreiben. Mit RPC rufst du einfach read() auf, und der Rest passiert automatisch – genau das baust du hier.
Was ist Checkpoint 1?
Checkpoint 1 fordert dich auf, eine Interpositionsbibliothek zu schreiben, die alle geforderten C-Funktionen (open, close, read, write, lseek, stat, unlink, getdirentries, getdirtree, freedirtree) abfängt. Zunächst leitest du die Aufrufe einfach an die echten C-Funktionen weiter (Pass-Through). Zusätzlich sendest du einen Log-Eintrag an einen TCP-Server, der die Funktionsnamen auf der Konsole ausgibt. So sieht die Ausgabe beim Server aus:
open
read
read
closeWichtig: Keine zusätzlichen Leerzeichen oder Debug-Ausgaben auf stdout – der Autograder prüft das exakt.
Grundlagen: LD_PRELOAD und Interposition
LD_PRELOAD ist eine Umgebungsvariable unter Linux, die es erlaubt, eine eigene Shared Library vor der Standard-C-Bibliothek zu laden. So kannst du Funktionen wie open() überschreiben. Deine Bibliothek erhält die Kontrolle, ruft dann die echte Funktion über dlsym(RTLD_NEXT, ...) auf und loggt den Aufruf. Beispiel für open:
int open(const char *path, int flags, ...) {
static int (*real_open)(const char *, int, ...) = NULL;
if (!real_open) real_open = dlsym(RTLD_NEXT, "open");
log_call("open");
return real_open(path, flags);
}Dieses Pattern wiederholst du für jede der elf benötigten Funktionen. Beachte: getdirtree und freedirtree sind nicht standardmäßig in libc enthalten – du musst sie über die bereitgestellte Shared Library laden.
TCP-Server für das Logging
Dein Server muss eingehende TCP-Verbindungen akzeptieren und die gesendeten Funktionsnamen zeilenweise auf stdout ausgeben. Ein einfacher, nicht-blockierender Server reicht für Checkpoint 1 – kein Multithreading nötig. Beispiel-Setup:
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
listen(server_fd, 5);
while (1) {
int client = accept(server_fd, NULL, NULL);
char buf[256];
while (read(client, buf, sizeof(buf)) > 0) {
printf("%s", buf);
fflush(stdout);
}
close(client);
}Wichtig: Der Server darf nur die Funktionsnamen ausgeben – kein Debug-Output, keine Extra-Leerzeichen. Verwende stderr für Debugging.
Die geforderten Funktionen im Detail
Folgende elf Funktionen musst du interponieren und loggen:
- open: Datei öffnen, gibt File-Descriptor zurück.
- close: Datei schließen.
- read: Daten aus Datei lesen.
- write: Daten in Datei schreiben.
- lseek: Position im Dateizeiger verschieben.
- stat: Dateiinformationen abrufen.
- unlink: Datei löschen.
- getdirentries: Verzeichniseinträge lesen.
- getdirtree: (nicht standard) Rekursive Verzeichnisstruktur abrufen.
- freedirtree: Speicher der Verzeichnisstruktur freigeben.
Für getdirtree und freedirtree musst du die bereitgestellte Shared Library einbinden. Die Signaturen findest du im Header dirtree.h.
Marshalling und Serialisierung – Ein Ausblick
Obwohl Checkpoint 1 noch keine echten RPCs verlangt, ist es gut, das Konzept der Serialisierung zu verstehen. Später musst du Parameter und Rückgabewerte in ein Netzwerkformat umwandeln (marshalling). Typische Ansätze sind:
- Einfaches Textprotokoll: z.B. "open:path:flags" als String.
- Binärprotokoll: Feste Header-Struktur mit Längenfeldern.
- Verwendung von
htonl/ntohlfür plattformunabhängige Integer.
Ein Beispiel für ein einfaches Textprotokoll: Der Client sendet "open:/etc/passwd:0\n", der Server antwortet mit "3\n" (File-Descriptor) oder "-1\n" bei Fehler. Diese Serialisierung wirst du in späteren Checkpoints ausbauen.
Häufige Fehler und Tipps
Damit dein Code den Autograder besteht, beachte folgende Punkte:
- Keine zusätzliche Ausgabe auf stdout – weder von der Bibliothek noch vom Server. Verwende
fprintf(stderr, ...)für Debugging. - Exaktes Format der Logs: Jeder Funktionsname in einer eigenen Zeile, ohne Prefix oder Suffix.
- Rückgabewerte korrekt durchreichen: Deine Interpositionsfunktion muss den Wert der echten Funktion zurückgeben.
- Thread-Sicherheit: Für Checkpoint 1 nicht nötig, aber später wichtig. Verwende
dlsymmitRTLD_NEXTeinmalig pro Funktion. - Test mit den bereitgestellten Tools:
440cat, 440ls, 440read, 440write, 440tree– stelle sicher, dass sie lokal funktionieren, bevor du den Server startest.
Beispiel: Testlauf mit 440cat
Angenommen, du hast deine Bibliothek als mylib.so kompiliert und den Server auf Port 8080 gestartet. Dann führst du aus:
LD_PRELOAD=./mylib.so ../tools/440cat fooDer Server sollte die Aufrufe in der Reihenfolge ausgeben, wie sie von 440cat getätigt werden – typischerweise open, dann mehrfach read, dann close. Falls du andere Ausgaben siehst, überprüfe deine Interposition.
Zusammenfassung und nächste Schritte
Checkpoint 1 ist die Basis für das gesamte Projekt. Mit LD_PRELOAD und einem einfachen TCP-Server lernst du die Grundlagen der Interposition und des Network-Loggings. Sobald dieser Teil funktioniert, erweiterst du das System um echte RPCs mit Serialisierung, Multithreading und Fehlerbehandlung. Das Ziel ist, dass ein unverändertes Programm wie 440cat transparent auf Dateien eines entfernten Servers zugreift – ganz so, als wären sie lokal.
Denke daran: Deine Lösung muss auf einem 64-bit Linux-System wie dem Andrew Unix Server laufen. Windows oder Mac werden nicht unterstützt. Verwende
gccmit den Flags-fPIC -sharedfür die Bibliothek und-ldlfürdlsym.
Mit diesem Leitfaden bist du gut gerüstet, um Checkpoint 1 zu meistern. Viel Erfolg!