/* * [ReplicatorValidate.java] * * Summary: check that sender replicator files have not been corrupted. * * Copyright: (c) 2009-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 10.4 2009-04-03 tidy up code to check presence of necessary files to make it more WORA. * 10.5 2011-04-22 fully automate StartOver.bat, so no additional copies are needed. */ package com.mindprod.replicatorsender; import com.mindprod.common18.Localise; import com.mindprod.fastcat.FastCat; import com.mindprod.filter.ClamFilter; import com.mindprod.replicatorcommon.Config; import com.mindprod.replicatorcommon.FilenameContext; import com.mindprod.replicatorcommon.IO; import com.mindprod.replicatorcommon.MiniFD; import com.mindprod.replicatorcommon.MiniZD; import com.mindprod.replicatorcommon.ZipnameContext; import com.mindprod.sorted.SortedArrayList; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import static com.mindprod.replicatorcommon.Config.CODE_SIGNING_CERT; import static com.mindprod.replicatorsender.ConfigForSender.*; import static java.lang.System.*; /** * check that sender replicator files have not been corrupted. * * @author Roedy Green, Canadian Mind Products * @version 10.5 2011-04-22 fully automate StartOver.bat, so no additional copies are needed. * TODO: cross check with zipdetailedmanifest.ser as well. * @since 2009 */ @SuppressWarnings( { "FieldCanBeLocal" } ) public final class ReplicatorValidate { /** * list of all zips, with maxi info, from sender.ser */ private static ArrayList allMaxiZips; /** * All the active zip files, with minimal info from zipmanifest.ser */ private static MiniZD[] allMiniZips; /** * list of all files we have distributed */ private static SortedArrayList filesDistributed; /** * ensure all zips are still present in the sender's staging dir */ private static void ensureAllZipsPresent() { out.println( "Ensuring all zips present..." ); try { // this includes retired, but not deleted. for ( MaxiZD maxiZip : allMaxiZips ) { if ( maxiZip == null ) { out.println( "null" ); } else { final String zipName = maxiZip.getRelativeZipFilename(); IO.ensureFileExists( new File( SENDER_ZIP_STAGING_DIR, zipName ) ); } } } catch ( IOException e ) { err.println( e.getMessage() + "\n" + "Current directory: " + IO .getCurrentDir() ); exit( 1 ); } } /** * check that all distributed files are present on the sender so receiver can download them. */ private static void ensureAuxFilesPresent() { out.println( "Ensuring all aux files present..." ); try { IO.ensureFileExists( new File( SENDER_ZIP_STAGING_DIR, "autorun.inf" ) ); IO.ensureFileExists( new File( SENDER_ZIP_STAGING_DIR, "freshness.ser" ) ); IO.ensureFileExists( new File( SENDER_ZIP_STAGING_DIR, Config.ZIPMANIFEST_SER ) ); IO.ensureFileExists( new File( SENDER_ZIP_STAGING_DIR, CODE_SIGNING_CERT + ".cer" ) ); IO.ensureFileExists( new File( SENDER_ZIP_STAGING_DIR, "replicator.icon64.png" ) ); IO.ensureFileExists( new File( SENDER_ZIP_STAGING_DIR, "replicator.splash.png" ) ); IO.ensureFileExists( new File( SENDER_ZIP_STAGING_DIR, "replicator.ico" ) ); IO.ensureFileExists( new File( SENDER_ZIP_STAGING_DIR, "replicator.jar" ) ); for ( char letter = 'A'; letter <= 'Z'; letter++ ) { IO.ensureFileExists( new File( SENDER_ZIP_STAGING_DIR, "replicatorreceivercd" + letter + ".jnlp" ) ); } IO.ensureFileExists( new File( SENDER_ZIP_STAGING_DIR, "replicatorreceiverrelay.jnlp" ) ); IO.ensureFileExists( new File( SENDER_ZIP_STAGING_DIR, "replicatorreceivertest.jnlp" ) ); IO.ensureFileExists( new File( SENDER_ZIP_STAGING_DIR, "replicatorreceiverwebsite.jnlp" ) ); IO.ensureFileExists( new File( SENDER_ZIP_STAGING_DIR, "setup.exe" ) ); } catch ( IOException e ) { err.println( e.getMessage() + "\n" + "Current directory: " + IO .getCurrentDir() ); exit( 1 ); } } /** * check for undeleted zips that should have been deleted already. */ private static void ensureNoStrayZips() { out.println( "Ensuring no stray zips..." ); // if the zip is not in the list, it should not exist // gather list of zips final String[] zips = new File( SENDER_ZIP_STAGING_DIR ) .list( new ClamFilter( "z", ".zip" ) ); for ( String zip : zips ) { boolean shouldBePresent = false; // compare against list of active zips. for ( MaxiZD downloadableZip : allMaxiZips ) { if ( downloadableZip.getRelativeZipFilename().equals( zip ) ) { shouldBePresent = true; break; } } // if fall out bottom without finding it, it is a stray, not even in the active list. if ( !shouldBePresent ) { err.println( "zip should have been deleted previously: " + zip ); // don't actually delete it in case fault is not with zip. } } } /** * ensure mini and maxi versions of the files are consistent */ @SuppressWarnings( { "ValueOfIncrementOrDecrementUsed" } ) private static void ensureSenderReceiverZipStateConsistency() { out.println( "Ensure client and sender zip state is consistent..." ); // prune out Emaciateds Retireds etc. final ArrayList prunedMaxiZips = new ArrayList<>( allMiniZips.length ); for ( MaxiZD maxiZip : allMaxiZips ) { if ( maxiZip.status == ZDStatus.B_CREATED ) { prunedMaxiZips.add( maxiZip ); } } if ( allMiniZips.length != prunedMaxiZips.size() ) { err.println( "number of zips inconsistent in " + Config.ZIPMANIFEST_SER + " (" + allMiniZips.length + ") " + "and " + ConfigForSender.SENDER_SER + " (" + prunedMaxiZips.size() + ")" ); exit( 1 ); } final int size = Math.min( allMiniZips.length, prunedMaxiZips.size() ); // don't change to for:each, need i. for ( int i = 0; i < size; i++ ) { final MaxiZD maxiZD = prunedMaxiZips.get( i ); final MiniZD miniZD = allMiniZips[ i ]; if ( maxiZD.getZipNumber() != miniZD.getZipNumber() ) { final StringBuilder sb = new StringBuilder( 11 ); sb.append( "zip#s inconsistent at offset (" ); sb.append( i ); sb.append( ") in " ); sb.append( Config.ZIPMANIFEST_SER ); sb.append( " (" ); sb.append( miniZD.getZipNumber() ); sb.append( ") and " ); sb.append( SENDER_SER ); sb.append( " (" ); sb.append( maxiZD.getZipNumber() ); sb.append( ")" ); err.println( sb.toString() ); } } } /** * ensure all user files are present in Zips */ @SuppressWarnings( { "EmptyMethod" } ) private static void ensureUserFilesPresentInZips() { out.println( "Checking all distributed files present, correct length and correct time in zips..." ); ZipFile prev = null; int prevZipNumber = -1; for ( MaxiFD maxiFD : filesDistributed ) { final String elementName = maxiFD.getFilename( FilenameContext.INSIDE_ZIP ); ZipFile zipFile; final MaxiZD maxiZD = maxiFD.getZip(); if ( maxiZD.getZipNumber() == prevZipNumber ) { zipFile = prev; } else { try { zipFile = new ZipFile( new File( maxiZD.getZipFilename( ZipnameContext.ON_SOURCE ) ) ); } catch ( IOException e ) { err.println( "Missing or corrupt zip file " + maxiZD.getZipFilename( ZipnameContext.ON_SOURCE ) ); zipFile = null; } } if ( zipFile != null ) { ZipEntry zipEntry = zipFile.getEntry( elementName ); if ( zipEntry == null ) { err.println( "file " + elementName + " is not present in zip " + maxiZD.getZipFilename( ZipnameContext.ON_SOURCE ) ); } else { if ( zipEntry.getSize() != maxiFD.getFileLength() ) { err.println( "file lengths differ in sender.ser and zip for " + elementName + " in " + maxiZD.getZipFilename( ZipnameContext.ON_SOURCE ) ); } if ( Math.abs( zipEntry.getTime() - maxiFD.getTimestamp() ) > Config.ALLOWED_SLOP ) { err.println( "dates differ in sender.ser and zip for " + elementName + " in " + maxiZD.getZipFilename( ZipnameContext.ON_SOURCE ) ); } if ( "deleted".equals( zipEntry.getComment() ) ) { err.println( "sender.ser file marked deleted in zip for " + elementName + " in " + maxiZD.getZipFilename( ZipnameContext.ON_SOURCE ) ); } } } } } /** * check for all active zips have expected status */ private static void ensureValidStatus() { out.println( "Ensuring all zips have expected status..." ); // if the zip is not in the list, it should not exist // gather list of zips // compare against list of active zips. for ( MaxiZD downloadableZip : allMaxiZips ) { switch ( downloadableZip.status ) { case A_UNCREATED: case C_DECOMMISSIONED: case E_EMACIATED: case F_DELETED: err.println( "zip has unexpected status " + downloadableZip.status + " " + downloadableZip .getRelativeZipFilename() ); break; case B_CREATED: case D_RETIRED: } } } /** * ensure zips in order */ @SuppressWarnings( { "EmptyMethod" } ) private static void ensureZipsInOrder() { MaxiZD prev = null; for ( MaxiZD maxiZip : allMaxiZips ) { if ( maxiZip.status == ZDStatus.B_CREATED ) { // zips should be in ascending order both by zip # and by // clumping. if ( prev != null ) { if ( prev.getZipNumber() >= maxiZip.getZipNumber() ) { err.println( "zip numbers out of order in ArrayList at " + maxiZip.dump() ); } if ( prev.getHighestClumpingInZip() > maxiZip.getHighestClumpingInZip() ) { err.println( "zip clumping out of order at " + maxiZip.dump() ); } } prev = maxiZip; } } } /** * restore state of master sender */ @SuppressWarnings( "unchecked" ) // no ; not javaDoc private static void restoreStateFromSenderSer() { try { final FileInputStream fis = new FileInputStream( SENDER_PERSIST_DIR + File.separatorChar + SENDER_SER ); // not compressed. final ObjectInputStream ois = new ObjectInputStream( fis ); // not compressed. // check array of 6 serialVersionUIDs ids on front for match. final long[] fileSerialVersionUIDs = ( long[] ) ois.readObject(); if ( fileSerialVersionUIDs.length != 6 || fileSerialVersionUIDs[ 0 ] != MiniZD.serialVersionUID || fileSerialVersionUIDs[ 1 ] != MaxiZD.serialVersionUID || fileSerialVersionUIDs[ 2 ] != ZDStatus.serialVersionUID || fileSerialVersionUIDs[ 3 ] != MiniFD.serialVersionUID || fileSerialVersionUIDs[ 4 ] != MaxiFD.serialVersionUID || fileSerialVersionUIDs[ 5 ] != SortedArrayList .serialVersionUID ) { err.println( "Incompatible version of " + ConfigForSender .SENDER_PERSIST_DIR + File .separatorChar + ConfigForSender .SENDER_SER ); // extra info err.println( "UID got : wanted" ); err.println( fileSerialVersionUIDs[ 0 ] + ":" + MiniZD.serialVersionUID + " MiniZD" ); err.println( fileSerialVersionUIDs[ 1 ] + ":" + MaxiZD.serialVersionUID + " MaxiZD" ); err.println( fileSerialVersionUIDs[ 2 ] + ":" + ZDStatus.serialVersionUID + " ZDStatsD" ); err.println( fileSerialVersionUIDs[ 3 ] + ":" + MiniFD.serialVersionUID + " MiniFD" ); err.println( fileSerialVersionUIDs[ 4 ] + ":" + MaxiFD.serialVersionUID + " MaxiFD" ); err.println( fileSerialVersionUIDs[ 5 ] + ":" + SortedArrayList.serialVersionUID + " SortedArrayList" ); exit( 1 ); } // ignore nextZipNumber ois.readObject(); // ignore estimates ois.readObject(); allMaxiZips = new ArrayList<>( Arrays.asList( ( MaxiZD[] ) ois.readObject() ) ); filesDistributed = new SortedArrayList<>( Arrays.asList( ( MaxiFD[] ) ois.readObject() ) ); // ignore filesDeleted ois.close(); } catch ( Exception e ) { final FastCat sb = new FastCat( 5 ); sb.append( "Unable to retrieve previous program state from " ); sb.append( qualifyInSenderPersist( SENDER_SER ) ); sb.append( "\n" ); sb.append( e.getMessage() ); sb.append( "\n" ); err.println( Localise.localise( sb.toString() ) ); exit( 1 ); } } /** * Unpack the compressed zip manifest, an array of serialised zip descriptors. */ private static void unpackZipManifestSer() { String zipManifestFilename = ConfigForSender.SENDER_ZIP_STAGING_DIR + File.separatorChar + Config .ZIPMANIFEST_SER; try { final FileInputStream fis = new FileInputStream( zipManifestFilename ); // stream is compressed final GZIPInputStream gzis = new GZIPInputStream( fis, 4 * 1024/* buffsize */ ); final ObjectInputStream ois = new ObjectInputStream( gzis ); long fileSerialVersionUID = ( Long ) ois.readObject(); if ( fileSerialVersionUID != MiniZD.serialVersionUID ) { throw new IOException( "Incompatible version of " + Config.ZIPMANIFEST_SER + " got: " + fileSerialVersionUID + " expected: " + MiniZD.serialVersionUID ); } allMiniZips = ( MiniZD[] ) ois.readObject(); ois.close(); } catch ( ClassNotFoundException e ) { err.println( "Corrupt zip manifest: " + zipManifestFilename + "\n" + e.getMessage() ); } catch ( IOException e ) { err.println( "Unable to read zip manifest: " + zipManifestFilename + "\n" + e.getMessage() ); } } // end unpackZipManifest /** * main to check that Replicator files are not screwed up. * * @param args arg[0] is fully qualified absolute or relative name of properties file e.g xxxreplicator.properties. */ public static void main( String[] args ) { try { // get config info from sender's xxxreplicator.properties file getConfigFromPropertiesFile( args[ 0 ] ); out.println( Localise.localise( "See " + SENDER_PERSIST_DIR + File.separatorChar + SENDER_LOG + " for list of zips/files" ) ); ensureAuxFilesPresent(); restoreStateFromSenderSer(); unpackZipManifestSer(); ensureZipsInOrder(); // cross check SENDER_SER and ZIPMANIFEST_SER ensureSenderReceiverZipStateConsistency(); ensureAllZipsPresent(); ensureValidStatus(); ensureNoStrayZips(); ensureUserFilesPresentInZips(); // we don't currently validate SENDER_SER oldFilesDeleted list or // ZIPDETAILEDMANIFEST_SER combinedFilesAndDeletions } catch ( Throwable e ) { e.printStackTrace( err ); err.println(); exit( 1 ); } } }