1 /*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2002, 2014 Oracle and/or its affiliates. All rights reserved. 5 * 6 */ 7 8 package com.sleepycat.je.util; 9 10 import java.io.File; 11 import java.io.PrintStream; 12 import java.util.ArrayList; 13 import java.util.Iterator; 14 import java.util.List; 15 import java.util.logging.Level; 16 17 import com.sleepycat.je.Database; 18 import com.sleepycat.je.DatabaseConfig; 19 import com.sleepycat.je.DatabaseException; 20 import com.sleepycat.je.DatabaseExistsException; 21 import com.sleepycat.je.DatabaseNotFoundException; 22 import com.sleepycat.je.DatabaseStats; 23 import com.sleepycat.je.DbInternal; 24 import com.sleepycat.je.Environment; 25 import com.sleepycat.je.EnvironmentConfig; 26 import com.sleepycat.je.EnvironmentFailureException; 27 import com.sleepycat.je.JEVersion; 28 import com.sleepycat.je.VerifyConfig; 29 import com.sleepycat.je.cleaner.VerifyUtils; 30 import com.sleepycat.je.dbi.DatabaseImpl; 31 import com.sleepycat.je.dbi.DbTree; 32 import com.sleepycat.je.dbi.EnvironmentImpl; 33 import com.sleepycat.je.utilint.CmdUtil; 34 import com.sleepycat.je.utilint.LoggerUtils; 35 36 /** 37 * Verifies the internal structures of a database. 38 * 39 * <p>To verify a database and write the errors to stream:</p> 40 * 41 * <pre> 42 * DbVerify verifier = new DbVerify(env, dbName, quiet); 43 * verifier.verify(); 44 * </pre> 45 */ 46 public class DbVerify { 47 48 private static final String usageString = 49 "usage: " + CmdUtil.getJavaCommand(DbVerify.class) + "\n" + 50 " -h <dir> # environment home directory\n" + 51 " [-c ] # check cleaner metadata\n" + 52 " [-q ] # quiet, exit with success or failure\n" + 53 " [-s <databaseName> ] # database to verify\n" + 54 " [-v <interval>] # progress notification interval\n" + 55 " [-V] # print JE version number"; 56 57 File envHome = null; 58 Environment env; 59 String dbName = null; 60 boolean quiet = false; 61 boolean checkLsns = false; 62 boolean openReadOnly = true; 63 64 private int progressInterval = 0; 65 66 /** 67 * The main used by the DbVerify utility. 68 * 69 * @param argv The arguments accepted by the DbVerify utility. 70 * 71 * <pre> 72 * usage: java { com.sleepycat.je.util.DbVerify | -jar 73 * je-<version>.jar DbVerify } 74 * [-q] [-V] -s database -h dbEnvHome [-v progressInterval] 75 * </pre> 76 * 77 * <p> 78 * -V - show the version of the JE library.<br> 79 * -s - specify the database to verify<br> 80 * -h - specify the environment directory<br> 81 * -q - work quietly and don't display errors<br> 82 * -v - report intermediate statistics every progressInterval Leaf 83 * Nodes 84 * </p> 85 * 86 * @throws EnvironmentFailureException if an unexpected, internal or 87 * environment-wide failure occurs. 88 */ main(String argv[])89 public static void main(String argv[]) 90 throws DatabaseException { 91 92 DbVerify verifier = new DbVerify(); 93 verifier.parseArgs(argv); 94 95 boolean ret = false; 96 try { 97 verifier.openEnv(); 98 ret = verifier.verify(System.err); 99 } catch (Throwable T) { 100 if (!verifier.quiet) { 101 T.printStackTrace(System.err); 102 } 103 } finally { 104 105 verifier.closeEnv(); 106 107 /* 108 * Show the status, only omit if the user asked for a quiet run and 109 * didn't specify a progress interval, in which case we can assume 110 * that they really don't want any status output. 111 * 112 * If the user runs this from the command line, presumably they'd 113 * like to see the status. 114 */ 115 if ((!verifier.quiet) || (verifier.progressInterval > 0)) { 116 System.err.println("Exit status = " + ret); 117 } 118 119 System.exit(ret ? 0 : -1); 120 } 121 } 122 DbVerify()123 DbVerify() { 124 } 125 126 /** 127 * Creates a DbVerify object for a specific environment and database. 128 * 129 * @param env The Environment containing the database to verify. 130 * 131 * @param dbName The name of the database to verify. 132 * 133 * @param quiet true if the verification should not produce errors to the 134 * output stream 135 */ DbVerify(Environment env, String dbName, boolean quiet)136 public DbVerify(Environment env, 137 String dbName, 138 boolean quiet) { 139 this.env = env; 140 this.dbName = dbName; 141 this.quiet = quiet; 142 } 143 printUsage(String msg)144 void printUsage(String msg) { 145 System.err.println(msg); 146 System.err.println(usageString); 147 System.exit(-1); 148 } 149 parseArgs(String argv[])150 void parseArgs(String argv[]) { 151 152 int argc = 0; 153 int nArgs = argv.length; 154 while (argc < nArgs) { 155 String thisArg = argv[argc++]; 156 if (thisArg.equals("-q")) { 157 quiet = true; 158 } else if (thisArg.equals("-V")) { 159 System.out.println(JEVersion.CURRENT_VERSION); 160 System.exit(0); 161 } else if (thisArg.equals("-h")) { 162 if (argc < nArgs) { 163 envHome = new File(argv[argc++]); 164 } else { 165 printUsage("-h requires an argument"); 166 } 167 } else if (thisArg.equals("-s")) { 168 if (argc < nArgs) { 169 dbName = argv[argc++]; 170 } else { 171 printUsage("-s requires an argument"); 172 } 173 } else if (thisArg.equals("-v")) { 174 if (argc < nArgs) { 175 progressInterval = Integer.parseInt(argv[argc++]); 176 if (progressInterval <= 0) { 177 printUsage("-v requires a positive argument"); 178 } 179 } else { 180 printUsage("-v requires an argument"); 181 } 182 } else if (thisArg.equals("-c")) { 183 checkLsns = true; 184 } else if (thisArg.equals("-rw")) { 185 186 /* 187 * Unadvertised option. Open the environment read/write so that 188 * a checkLsns pass gets an accurate root LSN to start from in 189 * the event that a recovery split the root. A read/only 190 * environment open will keep any logging in the log buffers, 191 * and the LSNs stored in the INs will be converted to 192 * DbLsn.NULL_LSN. 193 */ 194 openReadOnly = false; 195 } 196 } 197 198 if (envHome == null) { 199 printUsage("-h is a required argument"); 200 } 201 } 202 openEnv()203 void openEnv() 204 throws Exception { 205 206 if (env == null) { 207 EnvironmentConfig envConfig = new EnvironmentConfig(); 208 envConfig.setReadOnly(openReadOnly); 209 env = new Environment(envHome, envConfig); 210 } 211 } 212 closeEnv()213 void closeEnv() { 214 try { 215 if (env != null) { 216 env.close(); 217 } 218 } finally { 219 env = null; 220 } 221 } 222 223 /** 224 * Verifies a database and write errors found to a stream. 225 * 226 * @param out The stream to write errors to. 227 * 228 * @return true if the verification found no errors. 229 */ verify(PrintStream out)230 public boolean verify(PrintStream out) 231 throws DatabaseException { 232 233 boolean ret = true; 234 VerifyConfig verifyConfig = new VerifyConfig(); 235 verifyConfig.setPrintInfo(!quiet); 236 if (progressInterval > 0) { 237 verifyConfig.setShowProgressInterval(progressInterval); 238 verifyConfig.setShowProgressStream(out); 239 } 240 241 EnvironmentImpl envImpl = DbInternal.getEnvironmentImpl(env); 242 243 /* If no database is specified, verify all. */ 244 List<String> dbNameList = null; 245 List<String> internalDbs = null; 246 DbTree dbMapTree = envImpl.getDbTree(); 247 248 if (dbName == null) { 249 dbNameList = env.getDatabaseNames(); 250 251 dbNameList.addAll(dbMapTree.getInternalNoRepDbNames()); 252 if (envImpl.isReplicated()) { 253 dbNameList.addAll(dbMapTree.getInternalRepDbNames()); 254 } 255 internalDbs = dbMapTree.getInternalNoLookupDbNames(); 256 } else { 257 dbNameList = new ArrayList<String>(); 258 internalDbs = new ArrayList<String>(); 259 if (dbMapTree.getInternalNoLookupDbNames().contains(dbName)) { 260 /* Allow an internal dbName for debugging. [#22209] */ 261 internalDbs.add(dbName); 262 } else { 263 dbNameList.add(dbName); 264 } 265 } 266 267 /* Check application data. */ 268 Iterator<String> iter = dbNameList.iterator(); 269 while (iter.hasNext()) { 270 String targetDb = iter.next(); 271 LoggerUtils.envLogMsg(Level.INFO, envImpl, 272 "DbVerify.verify of " + targetDb + 273 " starting"); 274 275 DatabaseConfig dbConfig = new DatabaseConfig(); 276 dbConfig.setReadOnly(true); 277 dbConfig.setAllowCreate(false); 278 DbInternal.setUseExistingConfig(dbConfig, true); 279 Database db; 280 try { 281 db = env.openDatabase(null, targetDb, dbConfig); 282 } catch (DatabaseNotFoundException e) { 283 /* DB was removed after getting names -- ignore it. */ 284 continue; 285 } catch (DatabaseExistsException e) { 286 /* Should never happen, ExclusiveCreate is false. */ 287 throw EnvironmentFailureException.unexpectedException(e); 288 } 289 290 try { 291 if (!verifyOneDbImpl(DbInternal.getDatabaseImpl(db), 292 targetDb, 293 verifyConfig, 294 out)) { 295 ret = false; 296 } 297 } finally { 298 if (db != null) { 299 db.close(); 300 } 301 302 LoggerUtils.envLogMsg(Level.INFO, envImpl, 303 "DbVerify.verify of " + targetDb + 304 " ending"); 305 } 306 } 307 308 /* 309 * Check internal databases, which don't have to be opened 310 * through a Database handle. 311 */ 312 iter = internalDbs.iterator(); 313 while (iter.hasNext()) { 314 String targetDb = iter.next(); 315 LoggerUtils.envLogMsg(Level.INFO, envImpl, 316 "DbVerify.verify of " + targetDb + " starting"); 317 318 try { 319 DatabaseImpl dbImpl = dbMapTree.getDb(null, targetDb, 320 null); 321 try { 322 if (!verifyOneDbImpl(dbImpl, targetDb, 323 verifyConfig, out)) { 324 ret = false; 325 } 326 } finally { 327 dbMapTree.releaseDb(dbImpl); 328 } 329 } finally { 330 LoggerUtils.envLogMsg(Level.INFO, envImpl, 331 "DbVerify.verify of " + targetDb + 332 " ending"); 333 } 334 } 335 336 return ret; 337 } 338 verifyOneDbImpl(DatabaseImpl dbImpl, String name, VerifyConfig verifyConfig, PrintStream out)339 private boolean verifyOneDbImpl(DatabaseImpl dbImpl, 340 String name, 341 VerifyConfig verifyConfig, 342 PrintStream out) 343 throws DatabaseException { 344 345 boolean status = true; 346 347 if (verifyConfig.getPrintInfo()) { 348 out.println("Verifying database " + name); 349 } 350 351 if (checkLsns) { 352 /* Check the obsolete lsns */ 353 if (verifyConfig.getPrintInfo()) { 354 out.println("Checking obsolete offsets for " + name); 355 } 356 357 try { 358 VerifyUtils.checkLsns(dbImpl, out); 359 } catch (DatabaseException e) { 360 if (verifyConfig.getPrintInfo()) { 361 out.println("Problem from checkLsns: " + e); 362 } 363 364 status = false; 365 } 366 } else { 367 368 /* 369 * Check the tree. Use DatabaseImpl.verify so we can get a status 370 * return. 371 */ 372 if (verifyConfig.getPrintInfo()) { 373 out.println("Checking tree for " + name); 374 } 375 376 DatabaseStats stats = dbImpl.getEmptyStats(); 377 status = dbImpl.verify(verifyConfig, stats); 378 if (verifyConfig.getPrintInfo()) { 379 380 /* 381 * Intentionally use print, not println, because 382 * stats.toString() puts in a newline too. 383 */ 384 out.print(stats); 385 } 386 } 387 388 if (verifyConfig.getPrintInfo()) { 389 out.println(); 390 } 391 392 return status; 393 } 394 } 395