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-&lt;version&gt;.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