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