Java tutorial
/* * ***** BEGIN LICENSE BLOCK ***** * Zimbra Collaboration Suite Server * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2013, 2014, 2016 Synacor, Inc. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, * version 2 of the License. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * You should have received a copy of the GNU General Public License along with this program. * If not, see <https://www.gnu.org/licenses/>. * ***** END LICENSE BLOCK ***** */ /* * Created on 2004. 11. 3. * * TODO To change the template for this generated file go to * Window - Preferences - Java - Code Generation - Code and Comments */ package com.zimbra.cs.redolog.util; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.io.RandomAccessFile; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import com.zimbra.common.util.ByteUtil; import com.zimbra.common.util.CliUtil; import com.zimbra.cs.redolog.RolloverManager; import com.zimbra.cs.redolog.logger.FileHeader; import com.zimbra.cs.redolog.logger.FileLogReader; import com.zimbra.cs.redolog.op.RedoableOp; import com.zimbra.cs.redolog.op.StoreIncomingBlob; /** * @author jhahm */ public class RedoLogVerify { private static Options sOptions = new Options(); private static final String OPT_HELP = "h"; private static final String OPT_QUIET = "q"; private static final String OPT_SHOW_BLOB = "show-blob"; private static final String OPT_NO_OFFSET = "no-offset"; private static final String OPT_MAILBOX_IDS = "m"; static { sOptions.addOption(OPT_HELP, "help", false, "show this output"); sOptions.addOption(OPT_QUIET, "quiet", false, "quiet mode. Only print the log filename and any errors. This option can be used to " + "verify the integrity of redologs with minimal output."); sOptions.addOption(null, OPT_NO_OFFSET, false, "don't show file offsets and size for each redo op"); sOptions.addOption(null, OPT_MAILBOX_IDS, true, "one or more mailbox ids separated by comma or white space. The entire list must be " + "quoted if using space as separator. If this option is given, only redo ops for the " + "specified mailboxes are dumped. Omit this option to dump redo ops for all mailboxes."); sOptions.addOption(null, OPT_SHOW_BLOB, false, "show blob content. Item's blob is printed, surrounded by <START OF BLOB> and <END OF BLOB> " + "markers. The last newline before end marker is not part of the blob."); } private static void usage(String errmsg) { if (errmsg != null) { System.err.println(errmsg); System.err.println(); } HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("zmredodump [options] <redolog file/directory> [...]", "where [options] are:\n", sOptions, "\nMultiple log files/directories can be specified. For each directory, all redolog files " + "directly under it are processed, sorted in ascending redolog sequence order."); System.exit((errmsg == null) ? 0 : 1); } private static CommandLine parseArgs(String args[]) { CommandLineParser parser = new GnuParser(); CommandLine cl = null; try { cl = parser.parse(sOptions, args); } catch (ParseException pe) { usage(pe.getMessage()); } return cl; } private static class Params { public Set<Integer> mboxIds = new HashSet<Integer>(); public boolean quiet = false; public boolean hideOffset = false; public boolean showBlob = false; public boolean help = false; } private static Params initParams(CommandLine cl) { Params params = new Params(); params.help = cl.hasOption(OPT_HELP); if (params.help) return params; params.quiet = cl.hasOption(OPT_QUIET); params.hideOffset = cl.hasOption(OPT_NO_OFFSET); params.showBlob = cl.hasOption(OPT_SHOW_BLOB); String mboxIdList = cl.getOptionValue(OPT_MAILBOX_IDS); if (mboxIdList != null) { String[] ids = mboxIdList.split("[, ]+"); if (ids != null && ids.length > 0) { for (String val : ids) { if (val != null && val.length() > 0) { try { int i = Integer.parseInt(val); if (i > 0) params.mboxIds.add(i); else usage("Invalid mailbox id \"" + val + "\""); } catch (NumberFormatException e) { usage("Invalid mailbox id \"" + val + "\""); } } } } } return params; } private static class BadFile { public File file; public Throwable error; public BadFile(File f, Throwable e) { file = f; error = e; } } private PrintStream mOut; private Params mParams; private List<BadFile> mBadFiles; public RedoLogVerify(Params params, PrintStream out) { mOut = out; mParams = params; if (mParams == null) mParams = new Params(); mBadFiles = new ArrayList<BadFile>(); } public boolean scanLog(File logfile) throws IOException { boolean good = false; FileLogReader logReader = new FileLogReader(logfile, false); logReader.open(); if (!mParams.quiet) { FileHeader header = logReader.getHeader(); mOut.println("HEADER"); mOut.println("------"); mOut.print(header); mOut.println("------"); } boolean hasMailboxIdsFilter = !mParams.mboxIds.isEmpty(); RedoableOp op = null; long lastPosition = 0; long lastOpStartOffset = 0; try { while ((op = logReader.getNextOp()) != null) { lastOpStartOffset = logReader.getLastOpStartOffset(); lastPosition = logReader.position(); if (hasMailboxIdsFilter) { int mboxId = op.getMailboxId(); if (op instanceof StoreIncomingBlob) { List<Integer> list = ((StoreIncomingBlob) op).getMailboxIdList(); if (list != null) { boolean match = false; for (Integer mid : list) { if (mParams.mboxIds.contains(mid)) { match = true; break; } } if (!match) continue; } // If list==null, it's a store incoming blob op targeted at unknown set of mailboxes. // It applies to our filtered mailboxes. } else if (!mParams.mboxIds.contains(mboxId)) { continue; } } if (!mParams.quiet) { printOp(mOut, op, mParams.hideOffset, lastOpStartOffset, lastPosition - lastOpStartOffset); if (mParams.showBlob) { InputStream dataStream = op.getAdditionalDataStream(); if (dataStream != null) { mOut.println("<START OF BLOB>"); ByteUtil.copy(dataStream, true, mOut, false); mOut.println(); mOut.println("<END OF BLOB>"); } } } } good = true; } catch (IOException e) { // The IOException could be a real I/O problem or it could mean // there was a server crash previously and there were half-written // log entries. mOut.println(); mOut.printf("Error while parsing data starting at offset 0x%08x", lastPosition); mOut.println(); long size = logReader.getSize(); long diff = size - lastPosition; mOut.printf("%d bytes remaining in the file", diff); mOut.println(); mOut.println(); if (op != null) { mOut.println("Last suceessfully parsed redo op:"); printOp(mOut, op, false, lastOpStartOffset, lastPosition - lastOpStartOffset); mOut.println(); } // hexdump data around the bad bytes int bytesPerLine = 16; int linesBefore = 10; int linesAfter = 10; long startPos = Math.max(lastPosition - (lastPosition % bytesPerLine) - linesBefore * bytesPerLine, 0); int count = (int) Math.min((linesBefore + linesAfter + 1) * bytesPerLine, lastPosition - startPos + diff); RandomAccessFile raf = null; try { raf = new RandomAccessFile(logfile, "r"); raf.seek(startPos); byte buf[] = new byte[count]; raf.read(buf, 0, count); mOut.printf("Data near error offset %08x:", lastPosition); mOut.println(); hexdump(mOut, buf, 0, count, startPos, lastPosition); mOut.println(); } catch (IOException eh) { mOut.println("Error opening log file " + logfile.getAbsolutePath() + " for hexdump"); eh.printStackTrace(mOut); } finally { if (raf != null) raf.close(); } throw e; } finally { logReader.close(); } return good; } private static SimpleDateFormat sDateFormatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS z"); private static void printOp(PrintStream out, RedoableOp op, boolean hideOffset, long beginOffset, long size) { if (!hideOffset) out.printf("[%08x - %08x: %d bytes; tstamp: %s] ", beginOffset, beginOffset + size - 1, size, sDateFormatter.format(new Date(op.getTimestamp()))); out.println(op.toString()); } public boolean verifyFile(File file) { mOut.println("VERIFYING: " + file.getAbsolutePath()); boolean good = false; try { good = scanLog(file); } catch (IOException e) { mBadFiles.add(new BadFile(file, e)); mOut.println("Exception while verifying " + file.getAbsolutePath()); e.printStackTrace(mOut); } if (!mParams.quiet) mOut.println(); return good; } private boolean verifyFiles(File[] files) { boolean allGood = true; for (File log : files) { boolean b = verifyFile(log); allGood = allGood && b; } return allGood; } private boolean verifyDirectory(File dir) { if (!mParams.quiet) mOut.println("VERIFYING DIRECTORY: " + dir.getAbsolutePath()); File[] all = dir.listFiles(); if (all == null || all.length == 0) return true; List<File> fileList = new ArrayList<File>(all.length); for (File f : all) { if (!f.isDirectory()) { String fname = f.getName(); if (fname.lastIndexOf(".log") == fname.length() - 4) fileList.add(f); } } File[] files = new File[fileList.size()]; fileList.toArray(files); RolloverManager.sortArchiveLogFiles(files); return verifyFiles(files); } private void listErrors() { if (mBadFiles.size() == 0) return; mOut.println(); mOut.println(); mOut.println("-----------------------------------------------"); mOut.println(); mOut.println("The following files had errors:"); mOut.println(); for (BadFile bf : mBadFiles) { mOut.println(bf.file.getAbsolutePath()); mOut.println(" " + bf.error.getMessage()); } } private static void hexdump(PrintStream out, byte[] data, int offset, int length, long offsetOffsetBy, long badBytePos) { int end = Math.min(offset + length, data.length); int bytesPerLine = 16; while (offset < end) { int bytes = Math.min(bytesPerLine, end - offset); // bytes for this line long offsetLineStart = offset + offsetOffsetBy; long offsetLineEnd = offsetLineStart + bytes; out.printf("%08x: ", offsetLineStart); for (int i = 0; i < bytesPerLine; i++) { if (i < bytes) out.printf("%02x", ((int) data[offset + i]) & 0x000000ff); else out.print(" "); out.print(" "); if (i == 7) out.print(" "); } out.print(" "); for (int i = 0; i < bytesPerLine; i++) { if (i < bytes) { int ch = ((int) data[offset + i]) & 0x000000ff; if (ch >= 33 && ch <= 126) // printable ASCII range out.printf("%c", (char) ch); else out.print("."); } else { out.print(" "); } } if (offsetLineStart <= badBytePos && badBytePos < offsetLineEnd) out.print(" **"); out.println(); offset += bytes; } } public static void main(String[] cmdlineargs) { CliUtil.toolSetup(); CommandLine cl = parseArgs(cmdlineargs); Params params = initParams(cl); if (params.help) usage(null); String[] args = cl.getArgs(); if (args.length < 1) usage("No redolog file/directory list specified"); boolean allGood = true; RedoLogVerify verify = new RedoLogVerify(params, System.out); for (int i = 0; i < args.length; i++) { File f = new File(args[i]); boolean good = false; if (f.isDirectory()) good = verify.verifyDirectory(f); else good = verify.verifyFile(f); allGood = allGood && good; } if (!allGood) { verify.listErrors(); System.exit(1); } } }