/******************************************************************************* * Copyright (c) 2011 The Board of Trustees of the Leland Stanford Junior University * as Operator of the SLAC National Accelerator Laboratory. * Copyright (c) 2011 Brookhaven National Laboratory. * EPICS archiver appliance is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. *******************************************************************************/ package edu.stanford.slac.archiverappliance.PlainPB.utils; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.PosixFilePermissions; import java.util.LinkedList; import org.apache.log4j.Logger; import org.epics.archiverappliance.ByteArray; import org.epics.archiverappliance.Event; import org.epics.archiverappliance.utils.nio.ArchPaths; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadInfo; import edu.stanford.slac.archiverappliance.PB.utils.LineEscaper; import edu.stanford.slac.archiverappliance.PlainPB.FileBackedPBEventStream; import edu.stanford.slac.archiverappliance.PlainPB.PBFileInfo; /** * Validate every line in a PB file by unmarshalling it and accessing the timestamp. * If there are any exceptions, then copy only the events that can be unmarshalled correctly and are monotonically sequenced correctly to a temporary file and then replace the original file with the temporary file. * If the -b option is specified; then the original file is moved to a new file with a <code>.bak.</code> extension. * * @author mshankar * */ public class ValidateAndFixPBFile { private static Logger logger = Logger.getLogger(ValidateAndFixPBFile.class.getName()); /** * @param args   * @throws Exception   */ public static void main(String[] args) throws Exception { if(args == null || args.length <= 0) { printHelpMsg(); return; } boolean verboseMode = false; boolean makeBackups = false; LinkedList<String> argsAfterOptions = new LinkedList<String>(); for(String arg : args) { if(arg.equals("-v")) { verboseMode = true; } else if(arg.equals("-b")) { makeBackups = true; } else if(arg.equals("-h")) { printHelpMsg(); return; } else { argsAfterOptions.add(arg); } } for(String fileName : argsAfterOptions) { Path path = Paths.get(fileName); if(Files.isDirectory(path)) { Files.walkFileTree(path, new FileVisitor<Path>() { private boolean verboseMode = false; private boolean makeBackups = false; FileVisitor<Path> init(boolean verboseMode, boolean makeBackups) { this.verboseMode = verboseMode; this.makeBackups = makeBackups; return this; } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { boolean isValid = ValidatePBFile.validatePBFile(file, verboseMode); if(!isValid) { logger.debug("Path " + file + " is not a valid PB file"); fixPBFile(file, verboseMode, makeBackups); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } }.init(verboseMode, makeBackups)); } else { boolean isValid = ValidatePBFile.validatePBFile(path, verboseMode); if(!isValid) { logger.debug("Path " + path + " is not a valid PB file"); fixPBFile(path, verboseMode, makeBackups); } } } } private static void printHelpMsg() { System.out.println(); System.out.println("Usage: validateAndFix.sh <options> <Any number of file or folder names>"); System.out.println(); System.out.println("\t-h Prints this help"); System.out.println("\t-v Turns on verbose logging."); System.out.println("\t-b If a file is fixed, keep the original as a .bak file."); System.out.println(); System.out.println(); System.out.println(); } public static void fixPBFile(Path path, boolean verboseMode, boolean makeBackups) { System.out.println("Fixing " + path + " which is an invalid PB file " + (makeBackups ? " with backups " : "")); long skippedEvents = 0; try (ArchPaths contexts = new ArchPaths()) { String[] pathNames = path.toString().split(File.separator); String finalNameComponent = pathNames[pathNames.length-1]; String tempFileName = finalNameComponent + ".bak"; Path tempPath = path.resolveSibling(tempFileName); if(tempPath.equals(path)) { throw new IOException("When computing the temp file name, the original file name " + path + " and the temp file name " + tempPath + " are the same "); } if(verboseMode) logger.info("Temporary file is " + tempPath.toString() + " with final component of temp file " + tempFileName); try { PBFileInfo info = new PBFileInfo(path); long previousEpochSeconds = Long.MIN_VALUE; long eventnum = 0; try(FileBackedPBEventStream strm = new FileBackedPBEventStream(info.getPVName(), path, info.getType()); OutputStream os = new BufferedOutputStream(Files.newOutputStream(tempPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) { byte[] headerBytes = LineEscaper.escapeNewLines(PayloadInfo.newBuilder() .setPvname(info.getPVName()) .setType(strm.getDescription().getArchDBRType().getPBPayloadType()) .setYear(info.getDataYear()) .build().toByteArray()); os.write(headerBytes); os.write(LineEscaper.NEWLINE_CHAR); for(Event ev : strm) { try { long epochSeconds = ev.getEpochSeconds(); if(epochSeconds >= previousEpochSeconds) { previousEpochSeconds = epochSeconds; ByteArray val = ev.getRawForm(); os.write(val.data, val.off, val.len); os.write(LineEscaper.NEWLINE_CHAR); } else { if(verboseMode) logger.debug("Skipping non sequential event " + eventnum + " in file " + path.toString()); skippedEvents++; } } catch(Throwable t) { if(verboseMode) logger.debug("Skipping event " + eventnum + " in file " + path.toString()); skippedEvents++; } eventnum++; } } catch(Throwable t) { logger.error("Exception fixing PB file " + path, t); } if(makeBackups) { Path tPath = Files.createTempFile(path.getParent(), "Temp", "tempbak", PosixFilePermissions.asFileAttribute(Files.getPosixFilePermissions(path, LinkOption.NOFOLLOW_LINKS))); Files.move(path, tPath, StandardCopyOption.REPLACE_EXISTING); Files.move(tempPath, path, StandardCopyOption.REPLACE_EXISTING); Files.move(tPath, tempPath, StandardCopyOption.REPLACE_EXISTING); } else { Files.move(tempPath, path, StandardCopyOption.REPLACE_EXISTING); } } catch(Exception ex) { logger.error("Exception fixing PB file " + path, ex); } } catch(Exception ex) { logger.error("Exception fixing PB file " + path, ex); } if(verboseMode) logger.info("Skipped events " + skippedEvents + " when fixing " + path.toString()); } }