View Javadoc
1   /*
2    * Copyright (C) 2017, Google Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.internal.storage.dfs;
12  
13  import static java.nio.charset.StandardCharsets.UTF_8;
14  import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
15  import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
16  
17  import java.io.FileNotFoundException;
18  import java.io.IOException;
19  
20  import org.eclipse.jgit.errors.CorruptPackIndexException;
21  import org.eclipse.jgit.errors.MissingObjectException;
22  import org.eclipse.jgit.internal.JGitText;
23  import org.eclipse.jgit.internal.fsck.FsckError;
24  import org.eclipse.jgit.internal.fsck.FsckError.CorruptIndex;
25  import org.eclipse.jgit.internal.fsck.FsckError.CorruptObject;
26  import org.eclipse.jgit.internal.fsck.FsckPackParser;
27  import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
28  import org.eclipse.jgit.internal.submodule.SubmoduleValidator;
29  import org.eclipse.jgit.internal.submodule.SubmoduleValidator.SubmoduleValidationException;
30  import org.eclipse.jgit.lib.AnyObjectId;
31  import org.eclipse.jgit.lib.Constants;
32  import org.eclipse.jgit.lib.GitmoduleEntry;
33  import org.eclipse.jgit.lib.NullProgressMonitor;
34  import org.eclipse.jgit.lib.ObjectChecker;
35  import org.eclipse.jgit.lib.ObjectId;
36  import org.eclipse.jgit.lib.ObjectLoader;
37  import org.eclipse.jgit.lib.ProgressMonitor;
38  import org.eclipse.jgit.lib.Ref;
39  import org.eclipse.jgit.revwalk.ObjectWalk;
40  import org.eclipse.jgit.revwalk.RevObject;
41  
42  /**
43   * Verify the validity and connectivity of a DFS repository.
44   */
45  public class DfsFsck {
46  	private final DfsRepository repo;
47  	private final DfsObjDatabase objdb;
48  	private ObjectChecker objChecker = new ObjectChecker();
49  	private boolean connectivityOnly;
50  
51  	/**
52  	 * Initialize DFS fsck.
53  	 *
54  	 * @param repository
55  	 *            the dfs repository to check.
56  	 */
57  	public DfsFsck(DfsRepository repository) {
58  		repo = repository;
59  		objdb = repo.getObjectDatabase();
60  	}
61  
62  	/**
63  	 * Verify the integrity and connectivity of all objects in the object
64  	 * database.
65  	 *
66  	 * @param pm
67  	 *            callback to provide progress feedback during the check.
68  	 * @return all errors about the repository.
69  	 * @throws java.io.IOException
70  	 *             if encounters IO errors during the process.
71  	 */
72  	public FsckError check(ProgressMonitor pm) throws IOException {
73  		if (pm == null) {
74  			pm = NullProgressMonitor.INSTANCE;
75  		}
76  
77  		FsckError errors = new FsckError();
78  		if (!connectivityOnly) {
79  			objChecker.reset();
80  			checkPacks(pm, errors);
81  		}
82  		checkConnectivity(pm, errors);
83  		return errors;
84  	}
85  
86  	private void checkPacks(ProgressMonitor pm, FsckError errors)
87  			throws IOException, FileNotFoundException {
88  		try (DfsReader ctx = objdb.newReader()) {
89  			for (DfsPackFile pack : objdb.getPacks()) {
90  				DfsPackDescription packDesc = pack.getPackDescription();
91  				if (packDesc.getPackSource()
92  						== PackSource.UNREACHABLE_GARBAGE) {
93  					continue;
94  				}
95  				try (ReadableChannel rc = objdb.openFile(packDesc, PACK)) {
96  					verifyPack(pm, errors, ctx, pack, rc);
97  				} catch (MissingObjectException e) {
98  					errors.getMissingObjects().add(e.getObjectId());
99  				} catch (CorruptPackIndexException e) {
100 					errors.getCorruptIndices().add(new CorruptIndex(
101 							pack.getPackDescription().getFileName(INDEX),
102 							e.getErrorType()));
103 				}
104 			}
105 		}
106 
107 		checkGitModules(pm, errors);
108 	}
109 
110 	private void verifyPack(ProgressMonitor pm, FsckError errors, DfsReader ctx,
111 			DfsPackFile pack, ReadableChannel ch)
112 					throws IOException, CorruptPackIndexException {
113 		FsckPackParser fpp = new FsckPackParser(objdb, ch);
114 		fpp.setObjectChecker(objChecker);
115 		fpp.overwriteObjectCount(pack.getPackDescription().getObjectCount());
116 		fpp.parse(pm);
117 		errors.getCorruptObjects().addAll(fpp.getCorruptObjects());
118 
119 		fpp.verifyIndex(pack.getPackIndex(ctx));
120 	}
121 
122 	private void checkGitModules(ProgressMonitor pm, FsckError errors)
123 			throws IOException {
124 		pm.beginTask(JGitText.get().validatingGitModules,
125 				objChecker.getGitsubmodules().size());
126 		for (GitmoduleEntry entry : objChecker.getGitsubmodules()) {
127 			AnyObjectId blobId = entry.getBlobId();
128 			ObjectLoader blob = objdb.open(blobId, Constants.OBJ_BLOB);
129 
130 			try {
131 				SubmoduleValidator.assertValidGitModulesFile(
132 						new String(blob.getBytes(), UTF_8));
133 			} catch (SubmoduleValidationException e) {
134 				CorruptObject co = new FsckError.CorruptObject(
135 						blobId.toObjectId(), Constants.OBJ_BLOB,
136 						e.getFsckMessageId());
137 				errors.getCorruptObjects().add(co);
138 			}
139 			pm.update(1);
140 		}
141 		pm.endTask();
142 	}
143 
144 	private void checkConnectivity(ProgressMonitor pm, FsckError errors)
145 			throws IOException {
146 		pm.beginTask(JGitText.get().countingObjects, ProgressMonitor.UNKNOWN);
147 		try (ObjectWalk ow = new ObjectWalk(repo)) {
148 			for (Ref r : repo.getRefDatabase().getRefs()) {
149 				ObjectId objectId = r.getObjectId();
150 				if (objectId == null) {
151 					// skip unborn branch
152 					continue;
153 				}
154 				RevObject tip;
155 				try {
156 					tip = ow.parseAny(objectId);
157 					if (r.getLeaf().getName().startsWith(Constants.R_HEADS)
158 							&& tip.getType() != Constants.OBJ_COMMIT) {
159 						// heads should only point to a commit object
160 						errors.getNonCommitHeads().add(r.getLeaf().getName());
161 					}
162 					ow.markStart(tip);
163 				} catch (MissingObjectException e) {
164 					errors.getMissingObjects().add(e.getObjectId());
165 					continue;
166 				}
167 			}
168 			try {
169 				ow.checkConnectivity();
170 			} catch (MissingObjectException e) {
171 				errors.getMissingObjects().add(e.getObjectId());
172 			}
173 		}
174 		pm.endTask();
175 	}
176 
177 	/**
178 	 * Use a customized object checker instead of the default one. Caller can
179 	 * specify a skip list to ignore some errors.
180 	 *
181 	 * It will be reset at the start of each {{@link #check(ProgressMonitor)}
182 	 * call.
183 	 *
184 	 * @param objChecker
185 	 *            A customized object checker.
186 	 */
187 	public void setObjectChecker(ObjectChecker objChecker) {
188 		this.objChecker = objChecker;
189 	}
190 
191 	/**
192 	 * Whether fsck should bypass object validity and integrity checks and only
193 	 * check connectivity.
194 	 *
195 	 * @param connectivityOnly
196 	 *            whether fsck should bypass object validity and integrity
197 	 *            checks and only check connectivity. The default is
198 	 *            {@code false}, meaning to run all checks.
199 	 */
200 	public void setConnectivityOnly(boolean connectivityOnly) {
201 		this.connectivityOnly = connectivityOnly;
202 	}
203 }