package agent import ( "os" "path/filepath" "strings" "syscall" "testing" ) // TestSpecialFilesAreNotCountedAsDataLoss ist der Kerntest der Phase 6. // // Ein Socket oder eine benannte Pipe *fehlt* nicht im Backup — sie *gehoert* // nicht hinein: Sie hat keinen Inhalt, den man sichern und zurueckschreiben // koennte. Ein Socket ist der Endpunkt eines laufenden Prozesses. // // Zaehlte man sie als uebergangenes Objekt, waere jede Sicherung eines // Linux-Systems ein Teilfehler: In /var/run und /tmp liegen staendig Sockets. // Nach einer Woche klickt niemand mehr einen Teilfehler an — und dann faellt // auch der echte nicht mehr auf. func TestSpecialFilesAreNotCountedAsDataLoss(testInstance *testing.T) { sourcePath := testInstance.TempDir() if writeError := os.WriteFile(filepath.Join(sourcePath, "nutzdaten.txt"), []byte("echte Daten"), 0o644); writeError != nil { testInstance.Fatalf("die Datei ließ sich nicht anlegen: %v", writeError) } pipePath := filepath.Join(sourcePath, "warteschlange.fifo") if pipeError := syscall.Mkfifo(pipePath, 0o644); pipeError != nil { testInstance.Skipf("eine benannte Pipe ließ sich nicht anlegen: %v", pipeError) } discoveryResult, discoveryError := Discover(sourcePath, DiscoveryOptions{}) if discoveryError != nil { testInstance.Fatalf("die Erfassung schlug fehl: %v", discoveryError) } // Die Pipe muss vermerkt sein — verschwiegen werden darf sie nicht. if !discoveryResult.HasProblems() { testInstance.Fatal("die benannte Pipe wurde nicht vermerkt") } // Aber sie ist kein Datenverlust. if discoveryResult.DataLossProblemCount() != 0 { testInstance.Errorf("die benannte Pipe zählt als Datenverlust (%d)", discoveryResult.DataLossProblemCount()) } // Die Meldung muss verstaendlich sein: „prw-r--r--" sagt niemandem etwas. var reasonText string for _, problem := range discoveryResult.Problems { if strings.HasSuffix(problem.Path, "warteschlange.fifo") { reasonText = problem.Reason } } if !strings.Contains(reasonText, "Pipe") { testInstance.Errorf("die Meldung benennt den Objekttyp nicht verständlich: %q", reasonText) } if !strings.Contains(reasonText, "kein Fehler") { testInstance.Errorf("die Meldung sagt nicht, dass es kein Fehler ist: %q", reasonText) } } // TestPermissionErrorsRemainDataLoss ist die Gegenprobe. // // Eine nicht lesbare Datei ist etwas voellig anderes als ein Socket: Dort waren // Daten, und sie fehlen jetzt im Backup. Der Lauf muss ein Teilfehler sein. func TestPermissionErrorsRemainDataLoss(testInstance *testing.T) { if os.Geteuid() == 0 { testInstance.Skip("als root greifen Dateirechte nicht") } sourcePath := testInstance.TempDir() lockedDirectory := filepath.Join(sourcePath, "gesperrt") if makeError := os.Mkdir(lockedDirectory, 0o755); makeError != nil { testInstance.Fatalf("das Verzeichnis ließ sich nicht anlegen: %v", makeError) } if writeError := os.WriteFile(filepath.Join(lockedDirectory, "geheim.txt"), []byte("Daten"), 0o644); writeError != nil { testInstance.Fatalf("die Datei ließ sich nicht anlegen: %v", writeError) } if chmodError := os.Chmod(lockedDirectory, 0o000); chmodError != nil { testInstance.Fatalf("die Rechte ließen sich nicht setzen: %v", chmodError) } // Aufraeumen, sonst laesst sich das Testverzeichnis nicht entfernen. testInstance.Cleanup(func() { _ = os.Chmod(lockedDirectory, 0o755) }) discoveryResult, discoveryError := Discover(sourcePath, DiscoveryOptions{}) if discoveryError != nil { testInstance.Fatalf("die Erfassung schlug fehl: %v", discoveryError) } if discoveryResult.DataLossProblemCount() == 0 { testInstance.Error("ein Rechtefehler zählt nicht als Datenverlust — dort fehlen Daten") } if discoveryResult.PermissionProblemCount() == 0 { testInstance.Error("der Rechtefehler wird nicht als solcher ausgewiesen") } } // TestHardlinksAreBackedUpAsSeparateFiles hält die bekannte Grenze fest. // // Der **Inhalt** kommt vollstaendig zurueck, und dank Deduplizierung liegt er // nur einmal im Repository. Die Verknuepfung selbst geht jedoch verloren: Nach // einer Wiederherstellung sind es zwei unabhaengige Dateien. // // Das ist eine Grenze, keine Ueberraschung — sie steht hier, damit sie // niemandem erst im Ernstfall auffaellt. func TestHardlinksAreBackedUpAsSeparateFiles(testInstance *testing.T) { sourcePath := testInstance.TempDir() originalPath := filepath.Join(sourcePath, "original.txt") if writeError := os.WriteFile(originalPath, []byte("gemeinsamer Inhalt"), 0o644); writeError != nil { testInstance.Fatalf("die Datei ließ sich nicht anlegen: %v", writeError) } if linkError := os.Link(originalPath, filepath.Join(sourcePath, "verknuepfung.txt")); linkError != nil { testInstance.Skipf("eine harte Verknüpfung ließ sich nicht anlegen: %v", linkError) } discoveryResult, discoveryError := Discover(sourcePath, DiscoveryOptions{}) if discoveryError != nil { testInstance.Fatalf("die Erfassung schlug fehl: %v", discoveryError) } fileCount := 0 for _, discoveredEntry := range discoveryResult.Entries { if discoveredEntry.EntryType == EntryTypeFile { fileCount++ } } // Beide erscheinen als eigenstaendige Dateien — das ist der dokumentierte // Zustand. Aendert sich das, gehoert die Dokumentation nachgezogen. if fileCount != 2 { testInstance.Errorf("es wurden %d Dateien erfasst, erwartet wurden zwei "+ "(harte Verknüpfungen werden als eigenständige Dateien gesichert)", fileCount) } if discoveryResult.DataLossProblemCount() != 0 { testInstance.Error("eine harte Verknüpfung wurde als Datenverlust gewertet") } }