[LINUX] java.io.IOException: Too many levels of symbolic links

trench140

Mitglied
Hallo alle miteinander,

da meine aktuelle Backup-Festplatte inzwischen fast überläuft und Backups der letzten 10 Jahre beinhaltet wollte ich mir ein Programm schreiben, mit welchem ich die komplette Festplatte analysieren und Datei-Duplikate identifizieren kann.
Um dies zu bewerkstelligen durchlaufe ich die gesamte Festplatte rekursiv, bilde über jede Datei einen SHA-256-Hash und speichere diesen zunächst in einer MySQL-Datenbank.

Insgesamt funktionierte das Ganze bei Testläufen schon vielversprechend, aber wenn ich es nun auf meine Festplatte loslasse schmiert mir das Ganze nach einiger Zeit mit obiger Meldung weg. Dazu muss ich sagen, dass ich bereits eine Überprüfung eingebaut habe welche anhand des Absolut- und Canonical-Path entscheidet, ob ein symbolischer Link vorliegt oder nicht und falls ja diesen übergeht.

Insgesamt sieht die letzte Exception (bevor mir das Ganze dann auch noch wegen einer OutOfMemoryError um die Ohren fliegt) so aus:

Code:
java.io.IOException: Too many levels of symbolic links
	at java.io.UnixFileSystem.canonicalize0(Native Method)
	at java.io.UnixFileSystem.canonicalize(UnixFileSystem.java:159)
	at java.io.File.getCanonicalPath(File.java:559)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.isLink(DuplicateFileFinder.java:60)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:111)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:110)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.getDirectoryContent(DuplicateFileFinder.java:107)
	at de.assassinator.duplicatefilefinder.DuplicateFileFinder.main(DuplicateFileFinder.java:51)
04.11.2009 21:05:49 de.assassinator.duplicatefilefinder.DuplicateFileFinder getDirectoryContent

Code in Zeile 60 (beinhaltet die Prüf-Methode für symbolische Links):
Code:
String canonicalName = file.getCanonicalPath();

Code in Zeile 111:
Code:
logger.info("Analyzing file "+childFile.getName());

Code in Zeile 110:
Code:
if(!isLink(childFile)) {

Code in Zeile 107:
Code:
getDirectoryContent(childFile.getAbsolutePath());

Code in Zeile 51:
Code:
getDirectoryContent(startDirectory);

Man sieht also recht gut, dass das Ganze scheinbar wirklich ekelig tief verschachtelt ist (was daran liegt, dass einfach ganze Profile und Home-Verzeichnisse gesichert wurden). Allerdings verstehe ich nun nicht, warum genau an der Stelle das Ganze mir dann um die Ohren fliegt, und ich habe auch irgendwie keine Lösung dazu finden können.

Dass das Ganze am Ende noch mit einer OOME wegfliegt ist eine andere Geschichte, denn die obige Exception tritt viel früher auf, das Programm fängt diese jedoch ab und läuft erstmal weiter, so dass ich mir auch vorstellen kann, dass die OOME behoben sein könnte, wenn die vorherigen Exceptions behoben sind.

Gruß & Danke,
Trench
 
Zuletzt bearbeitet:
Grüß Dich,
hast Du eigetnlich dazu so etwas wie ein Struktogramm?
denn so wird man nicht wirklich schlau was Dein Programm im einzelnen so tut.

Takidoso
 
Hi.

Solche Programme gibt es doch aber schon einige. Warum willst du selbst nochmal eins schreiben?

Der Fehler liegt vermutlich ganz einfach an einem rekursiven Link im Dateisystem den getCanonicalizePath() nicht verarbeiten kann. Da müßte man sich die Datei mal anschauen an der es da hängt.

Gruß
 
Mal ne dumme Frachn: Wie lange dauert es denn, bis das kommt?

UnixFileSystem hat nicht einen, sondern zwei ExpiringCache Instanzen. Defaultmäßig werden die mit 30000ms=30s initialisiert.
Du könntest mal versuchen, die Caches abzuschalten. Die bools useCanonCaches und useCanonPrefixCache werden über zwei String-properties gesteuert, guck mal FileSystem, ganz unten.
(Hmm, ist der Code noch aktuell? Naja, Versuch macht kluch...)
 
Hallo allerseits,

danke für eure Antworten.

Ein Struktogramm habe ich nicht, ich habe einfach drauflos programmiert (ich weiß, soll man nicht, aber es ist ja eh nur für private Zwecke ;) ). Allerdings kann ich gerne mal den gesamten Code der main-Methode anfügen.

Warum will ich noch so ein Programm schreiben... ich wusste nicht, dass es dafür bereits Programme gibt. Im Prinzip war eigentlich zuerst die Idee da, dass ich mal was Komplexeres in Java programmieren wollte, die Idee zu dieser Anwendung kam erst später. Insgesamt sehe ich das Ganze eher unter dem Aspekt des Lerneffektes, wenn ich versuche so etwas selber zu programmieren, dann lerne ich (wie hier eben) extrem viel über Java.

Was die Dauer bis zur Fehlermeldung angeht, das Programm läuft knapp eine Stunde, zu dem Zeitpunkt wurden dann etwa 120000 Dateien geprüft und in meine SQL-Datenbank geschrieben. Die hohe Laufzeit resultiert dabei wohl aus der Hashbildung, ich wollte gleich mal einen Testlauf ohne Hashbildung starten und schauen, wie schnell er wie weit er kommt.

Hier wie versprochen der gesamte Quellcode der Haupt-Klasse
Code:
public class DuplicateFileFinder {
	private static DatabaseConnectionMySQL db;
	private static Logger logger;
	
	public static void main(String[] args) {
		try {
			FileHandler handler = new FileHandler("duplicateFileFinder.log");
			logger = Logger.getLogger("de.assassinator");
		    logger.addHandler(handler);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	    
		SQLData sqlData = new SQLData("localhost", "root", "");
		
		logger.info("Getting MySQL instance...");
		db = DatabaseConnectionMySQL.getInstance(sqlData);
		
		logger.info("Connecting to database");
		db.connect();
		
		logger.info("Creating database...");
		db.createDatabase("duplicatefilefinder");
		
		logger.info("Creating table...");
		db.createFileTable("duplicatefilefinder");
		
		String startDirectory = "/media/backup";
		
		getDirectoryContent(startDirectory);
	}
	
	public static boolean isLink( final File file ) {
		if (file == null || !file.exists()) {
			return false;
		}

		try {
			String canonicalName = file.getCanonicalPath();
			String absoluteName = file.getAbsolutePath();
			
			return !canonicalName.equals(absoluteName);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}
	}
	
	private static void getDirectoryContent(String directoryPath){
		logger.info("Stepping into directory "+directoryPath);
		File parentFile = new File(directoryPath);
		
		File[] arrayFileList = parentFile.listFiles();
		
		if(arrayFileList != null) {
			ArrayList<File> filelist = new ArrayList<File>();
		
			filelist.addAll(Arrays.asList(arrayFileList));
			
			Iterator<File> fileIterator = filelist.iterator();
			
			while(fileIterator.hasNext()) {
				File childFile = fileIterator.next();
			
				if(childFile.isDirectory()) {
					getDirectoryContent(childFile.getAbsolutePath());
				} else if(!isLink(childFile)) {
					logger.info("Analyzing file "+childFile.getName());

					try {
						MessageDigest md = MessageDigest.getInstance("MD5");
					
						byte[] datablock = new byte[65536];
					
						FileInputStream fis = new FileInputStream(childFile);
					
						for(int i = 0; (i = fis.read(datablock)) > -1; ) {
							md.update(datablock,0,i);
						}
								
						fis.close();
							
						String hash = "";
				    
						for (byte b : md.digest()) {
							hash = hash.concat(String.format("%02x", b));
						}
				    
						FileEntry fileEntry= new FileEntry();
						fileEntry.setFilename(childFile.getName());
						fileEntry.setPath(childFile.getAbsolutePath());
						fileEntry.setSize(childFile.length());
						fileEntry.setHash(hash);
				    
						db.insertFileEntry(fileEntry);
					} catch (NoSuchAlgorithmException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (FileNotFoundException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				} else {
					logger.info("No file...");
				}
				
				fileIterator.remove();
			}
		}
	}
}
 
Ich denke, ich habe den Fehler isolieren können. Innerhalb der Backup-Partition gibt es einen symbolischen Link auf das Root-Verzeichnis. wird dieses durchlaufen kommt es im Verzeichnis /proc zu einer gegenseitigen Referenzierung zweier Links über mehrere Ecken.

Das Problem resultiert daraus, dass, wenn man einen symbolischen Link, der auf ein Verzeichnis zeigt, mit isDirectory() prüft, true zurückgegeben wird.

Dies bricht mir hier

Code:
if(childFile.isDirectory()) {
					getDirectoryContent(childFile.getAbsolutePath());
				} else if(!isLink(childFile)) {

das Genick, da gar nicht mehr auf isLink geprüft wird sondern direkt der Content des verlinkten Verzeichnisses geholt wird, womit das Chaos bereits perfekt ist.

Edit:
Tatsächlich war dies der Fehler, lasse ich nun einfach alle Verzeichnisse ohne Hashbildung durchlaufen werden alle ~220000 Dateien korrekt gefunden und das Programm terminiert. Schöner Nebeneffekt: die Speicherbelastung (mit VisualVM geprüft) ist erheblich gesunken. Mal sehen, wie es dann mit Hashbildung geht ;)
 
Zuletzt bearbeitet:
Zurück