1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 package org.eclipse.jgit.internal.storage.file;
47
48 import java.io.File;
49 import java.io.FileOutputStream;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.RandomAccessFile;
53 import java.security.MessageDigest;
54 import java.text.MessageFormat;
55 import java.util.Arrays;
56 import java.util.List;
57 import java.util.zip.CRC32;
58 import java.util.zip.Deflater;
59
60 import org.eclipse.jgit.errors.LockFailedException;
61 import org.eclipse.jgit.internal.JGitText;
62 import org.eclipse.jgit.lib.AnyObjectId;
63 import org.eclipse.jgit.lib.Constants;
64 import org.eclipse.jgit.lib.CoreConfig;
65 import org.eclipse.jgit.lib.ObjectId;
66 import org.eclipse.jgit.lib.ObjectInserter;
67 import org.eclipse.jgit.lib.ProgressMonitor;
68 import org.eclipse.jgit.transport.PackParser;
69 import org.eclipse.jgit.transport.PackedObjectInfo;
70 import org.eclipse.jgit.util.FileUtils;
71 import org.eclipse.jgit.util.NB;
72
73
74
75
76
77
78
79 public class ObjectDirectoryPackParser extends PackParser {
80 private final FileObjectDatabase db;
81
82
83 private final CRC32 crc;
84
85
86 private final MessageDigest tailDigest;
87
88
89 private int indexVersion;
90
91
92 private boolean keepEmpty;
93
94
95 private File tmpPack;
96
97
98
99
100
101 private File tmpIdx;
102
103
104 private RandomAccessFile out;
105
106
107 private long origEnd;
108
109
110 private byte[] origHash;
111
112
113 private long packEnd;
114
115
116 private byte[] packHash;
117
118
119 private Deflater def;
120
121
122 private PackFile newPack;
123
124 ObjectDirectoryPackParser(FileObjectDatabase odb, InputStream src) {
125 super(odb, src);
126 this.db = odb;
127 this.crc = new CRC32();
128 this.tailDigest = Constants.newMessageDigest();
129
130 indexVersion = db.getConfig().get(CoreConfig.KEY).getPackIndexVersion();
131 }
132
133
134
135
136
137
138
139
140
141 public void setIndexVersion(int version) {
142 indexVersion = version;
143 }
144
145
146
147
148
149
150
151
152
153
154
155 public void setKeepEmpty(final boolean empty) {
156 keepEmpty = empty;
157 }
158
159
160
161
162
163
164
165
166
167 public PackFile getPackFile() {
168 return newPack;
169 }
170
171 @Override
172 public long getPackSize() {
173 if (newPack == null)
174 return super.getPackSize();
175
176 File pack = newPack.getPackFile();
177 long size = pack.length();
178 String p = pack.getAbsolutePath();
179 String i = p.substring(0, p.length() - ".pack".length()) + ".idx";
180 File idx = new File(i);
181 if (idx.exists() && idx.isFile())
182 size += idx.length();
183 return size;
184 }
185
186 @Override
187 public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving)
188 throws IOException {
189 tmpPack = File.createTempFile("incoming_", ".pack", db.getDirectory());
190 tmpIdx = new File(db.getDirectory(), baseName(tmpPack) + ".idx");
191 try {
192 out = new RandomAccessFile(tmpPack, "rw");
193
194 super.parse(receiving, resolving);
195
196 out.seek(packEnd);
197 out.write(packHash);
198 out.getChannel().force(true);
199 out.close();
200
201 writeIdx();
202
203 tmpPack.setReadOnly();
204 tmpIdx.setReadOnly();
205
206 return renameAndOpenPack(getLockMessage());
207 } finally {
208 if (def != null)
209 def.end();
210 try {
211 if (out != null && out.getChannel().isOpen())
212 out.close();
213 } catch (IOException closeError) {
214
215 }
216 cleanupTemporaryFiles();
217 }
218 }
219
220 @Override
221 protected void onPackHeader(long objectCount) throws IOException {
222
223 }
224
225 @Override
226 protected void onBeginWholeObject(long streamPosition, int type,
227 long inflatedSize) throws IOException {
228 crc.reset();
229 }
230
231 @Override
232 protected void onEndWholeObject(PackedObjectInfo info) throws IOException {
233 info.setCRC((int) crc.getValue());
234 }
235
236 @Override
237 protected void onBeginOfsDelta(long streamPosition,
238 long baseStreamPosition, long inflatedSize) throws IOException {
239 crc.reset();
240 }
241
242 @Override
243 protected void onBeginRefDelta(long streamPosition, AnyObjectId baseId,
244 long inflatedSize) throws IOException {
245 crc.reset();
246 }
247
248 @Override
249 protected UnresolvedDelta onEndDelta() throws IOException {
250 UnresolvedDelta delta = new UnresolvedDelta();
251 delta.setCRC((int) crc.getValue());
252 return delta;
253 }
254
255 @Override
256 protected void onInflatedObjectData(PackedObjectInfo obj, int typeCode,
257 byte[] data) throws IOException {
258
259 }
260
261 @Override
262 protected void onObjectHeader(Source src, byte[] raw, int pos, int len)
263 throws IOException {
264 crc.update(raw, pos, len);
265 }
266
267 @Override
268 protected void onObjectData(Source src, byte[] raw, int pos, int len)
269 throws IOException {
270 crc.update(raw, pos, len);
271 }
272
273 @Override
274 protected void onStoreStream(byte[] raw, int pos, int len)
275 throws IOException {
276 out.write(raw, pos, len);
277 }
278
279 @Override
280 protected void onPackFooter(byte[] hash) throws IOException {
281 packEnd = out.getFilePointer();
282 origEnd = packEnd;
283 origHash = hash;
284 packHash = hash;
285 }
286
287 @Override
288 protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta,
289 ObjectTypeAndSize info) throws IOException {
290 out.seek(delta.getOffset());
291 crc.reset();
292 return readObjectHeader(info);
293 }
294
295 @Override
296 protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj,
297 ObjectTypeAndSize info) throws IOException {
298 out.seek(obj.getOffset());
299 crc.reset();
300 return readObjectHeader(info);
301 }
302
303 @Override
304 protected int readDatabase(byte[] dst, int pos, int cnt) throws IOException {
305 return out.read(dst, pos, cnt);
306 }
307
308 @Override
309 protected boolean checkCRC(int oldCRC) {
310 return oldCRC == (int) crc.getValue();
311 }
312
313 private static String baseName(File tmpPack) {
314 String name = tmpPack.getName();
315 return name.substring(0, name.lastIndexOf('.'));
316 }
317
318 private void cleanupTemporaryFiles() {
319 if (tmpIdx != null && !tmpIdx.delete() && tmpIdx.exists())
320 tmpIdx.deleteOnExit();
321 if (tmpPack != null && !tmpPack.delete() && tmpPack.exists())
322 tmpPack.deleteOnExit();
323 }
324
325 @Override
326 protected boolean onAppendBase(final int typeCode, final byte[] data,
327 final PackedObjectInfo info) throws IOException {
328 info.setOffset(packEnd);
329
330 final byte[] buf = buffer();
331 int sz = data.length;
332 int len = 0;
333 buf[len++] = (byte) ((typeCode << 4) | sz & 15);
334 sz >>>= 4;
335 while (sz > 0) {
336 buf[len - 1] |= 0x80;
337 buf[len++] = (byte) (sz & 0x7f);
338 sz >>>= 7;
339 }
340
341 tailDigest.update(buf, 0, len);
342 crc.reset();
343 crc.update(buf, 0, len);
344 out.seek(packEnd);
345 out.write(buf, 0, len);
346 packEnd += len;
347
348 if (def == null)
349 def = new Deflater(Deflater.DEFAULT_COMPRESSION, false);
350 else
351 def.reset();
352 def.setInput(data);
353 def.finish();
354
355 while (!def.finished()) {
356 len = def.deflate(buf);
357 tailDigest.update(buf, 0, len);
358 crc.update(buf, 0, len);
359 out.write(buf, 0, len);
360 packEnd += len;
361 }
362
363 info.setCRC((int) crc.getValue());
364 return true;
365 }
366
367 @Override
368 protected void onEndThinPack() throws IOException {
369 final byte[] buf = buffer();
370
371 final MessageDigest origDigest = Constants.newMessageDigest();
372 final MessageDigest tailDigest2 = Constants.newMessageDigest();
373 final MessageDigest packDigest = Constants.newMessageDigest();
374
375 long origRemaining = origEnd;
376 out.seek(0);
377 out.readFully(buf, 0, 12);
378 origDigest.update(buf, 0, 12);
379 origRemaining -= 12;
380
381 NB.encodeInt32(buf, 8, getObjectCount());
382 out.seek(0);
383 out.write(buf, 0, 12);
384 packDigest.update(buf, 0, 12);
385
386 for (;;) {
387 final int n = out.read(buf);
388 if (n < 0)
389 break;
390 if (origRemaining != 0) {
391 final int origCnt = (int) Math.min(n, origRemaining);
392 origDigest.update(buf, 0, origCnt);
393 origRemaining -= origCnt;
394 if (origRemaining == 0)
395 tailDigest2.update(buf, origCnt, n - origCnt);
396 } else
397 tailDigest2.update(buf, 0, n);
398
399 packDigest.update(buf, 0, n);
400 }
401
402 if (!Arrays.equals(origDigest.digest(), origHash) || !Arrays
403 .equals(tailDigest2.digest(), this.tailDigest.digest()))
404 throw new IOException(
405 JGitText.get().packCorruptedWhileWritingToFilesystem);
406
407 packHash = packDigest.digest();
408 }
409
410 private void writeIdx() throws IOException {
411 List<PackedObjectInfo> list = getSortedObjectList(null );
412 final FileOutputStream os = new FileOutputStream(tmpIdx);
413 try {
414 final PackIndexWriter iw;
415 if (indexVersion <= 0)
416 iw = PackIndexWriter.createOldestPossible(os, list);
417 else
418 iw = PackIndexWriter.createVersion(os, indexVersion);
419 iw.write(list, packHash);
420 os.getChannel().force(true);
421 } finally {
422 os.close();
423 }
424 }
425
426 private PackLock renameAndOpenPack(final String lockMessage)
427 throws IOException {
428 if (!keepEmpty && getObjectCount() == 0) {
429 cleanupTemporaryFiles();
430 return null;
431 }
432
433 final MessageDigest d = Constants.newMessageDigest();
434 final byte[] oeBytes = new byte[Constants.OBJECT_ID_LENGTH];
435 for (int i = 0; i < getObjectCount(); i++) {
436 final PackedObjectInfo oe = getObject(i);
437 oe.copyRawTo(oeBytes, 0);
438 d.update(oeBytes);
439 }
440
441 final String name = ObjectId.fromRaw(d.digest()).name();
442 final File packDir = new File(db.getDirectory(), "pack");
443 final File finalPack = new File(packDir, "pack-" + name + ".pack");
444 final File finalIdx = new File(packDir, "pack-" + name + ".idx");
445 final PackLock keep = new PackLock(finalPack, db.getFS());
446
447 if (!packDir.exists() && !packDir.mkdir() && !packDir.exists()) {
448
449
450
451 cleanupTemporaryFiles();
452 throw new IOException(MessageFormat.format(
453 JGitText.get().cannotCreateDirectory, packDir
454 .getAbsolutePath()));
455 }
456
457 if (finalPack.exists()) {
458
459
460 cleanupTemporaryFiles();
461 return null;
462 }
463
464 if (lockMessage != null) {
465
466
467
468 try {
469 if (!keep.lock(lockMessage))
470 throw new LockFailedException(finalPack,
471 MessageFormat.format(
472 JGitText.get().cannotLockPackIn, finalPack));
473 } catch (IOException e) {
474 cleanupTemporaryFiles();
475 throw e;
476 }
477 }
478
479 if (!tmpPack.renameTo(finalPack)) {
480 cleanupTemporaryFiles();
481 keep.unlock();
482 throw new IOException(MessageFormat.format(
483 JGitText.get().cannotMovePackTo, finalPack));
484 }
485
486 if (!tmpIdx.renameTo(finalIdx)) {
487 cleanupTemporaryFiles();
488 keep.unlock();
489 if (!finalPack.delete())
490 finalPack.deleteOnExit();
491 throw new IOException(MessageFormat.format(
492 JGitText.get().cannotMoveIndexTo, finalIdx));
493 }
494
495 try {
496 newPack = db.openPack(finalPack);
497 } catch (IOException err) {
498 keep.unlock();
499 if (finalPack.exists())
500 FileUtils.delete(finalPack);
501 if (finalIdx.exists())
502 FileUtils.delete(finalIdx);
503 throw err;
504 }
505
506 return lockMessage != null ? keep : null;
507 }
508 }