package org.jboss.loom.utils.compar; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.NoSuchElementException; import java.util.Scanner; import net.lingala.zip4j.exception.ZipException; import net.lingala.zip4j.util.CRCUtil; import org.apache.commons.io.filefilter.IOFileFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Compares a list of hashes with actual files in a directory tree. * * @author Ondrej Zizka, ozizka at redhat.com */ public class FileHashComparer { private static final Logger log = LoggerFactory.getLogger( FileHashComparer.class ); public static enum MatchResult { MATCH("MATCH "), MISMATCH("MISMATCH"), MISSING("MISSING "), EMPTY("EMPTY "); final String padded; private MatchResult( String padded ) { this.padded = padded; } public final String rightPad(){ return this.padded; } }; /** * Reads the hashes from file and compares each entry to the respective file in given base $dir. * Hashes file format is: * 92ae740a ./bin/twiddle.bat * * The filter is applied to the path from the hash file, so it must only work with name, not the actual file - * so e.g. fileFileFilter() can't be used. */ public static ComparisonResult compareHashesAndDir( File hashes, File dir, IOFileFilter filter ) throws FileNotFoundException, IOException{ Map<Path, MatchResult> results = compareHashesAndDir( readHashes(hashes), dir, filter ); return new ComparisonResult( dir ).setMatches( results ).setHashes( hashes ); } public static ComparisonResult compareHashesAndDir( InputStream hashesIS, File dir, IOFileFilter filter ) throws FileNotFoundException, IOException{ Map<Path, MatchResult> results = compareHashesAndDir( readHashes(hashesIS), dir, filter ); return new ComparisonResult( dir ).setMatches( results ); } /** * This is the method which actually creates the map keys and values. * * @returns A map with keys being paths in the server dir (not including path to that), * and values being MatchResult values (MISSING, MATCH, MISMATCH, EMPTY). */ private static Map<Path, MatchResult> compareHashesAndDir( Map<String, Long> hashes, File dir, IOFileFilter filter ) throws IOException { Map<Path, MatchResult> matches = new HashMap(); // Iterate through hashes and compare with files. for( Map.Entry<String, Long> entry : hashes.entrySet() ) { String path = entry.getKey(); // Apply the filter. if( filter != null && ! filter.accept( new File(path) ) ) continue; File file = new File(dir, path); //Path pathNormFull = file.toPath().normalize(); Path pathNormSub = new File(".", path).toPath().normalize(); if( ! file.exists() ){ matches.put( pathNormSub, MatchResult.MISSING ); continue; } if( file.length() == 0 ){ matches.put( pathNormSub, MatchResult.EMPTY ); continue; } long hashReal = computeCrc32(file); Long hash = entry.getValue(); matches.put( pathNormSub, hash == hashReal ? MatchResult.MATCH : MatchResult.MISMATCH ); } return matches; } /** * Reads a file format * 92ae740a ./bin/twiddle.bat * and returns a map of paths -> hashes. * The paths are normalized, while kept relative. I.e. ./foo/../bar/a results in bar/a . */ public static Map<String, Long> readHashes( File file ) throws FileNotFoundException { return readHashes( new FileInputStream(file) ); } public static Map<String, Long> readHashes( InputStream hashesIS ) throws FileNotFoundException { Scanner sc = new Scanner( hashesIS ); Map<String, Long> hashes = new HashMap(); // 92ae740a ./bin/twiddle.bat while( sc.hasNextLine() ){ try { long hash = sc.nextLong(16); String path = sc.nextLine(); path = path.trim(); path = Paths.get(path).normalize().toString(); hashes.put( path, hash ); } catch( NoSuchElementException ex ){ log.warn("Failed parsing line: " + sc.nextLine(), ex); sc.nextLine(); } } return hashes; } /** * Computes CRC32 checksum of given file. * Uses net.lingala.zip4j.util and wraps their exception. */ public static long computeCrc32( File file ) throws IOException { try { return CRCUtil.computeFileCRC(file.getPath()); } catch( ZipException ex ) { throw new IOException("Can't compute CRC32 of " + file.getPath() + ": " + ex.getMessage(), ex); } } }// class