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.ObjectInserter;
68 import org.eclipse.jgit.lib.ProgressMonitor;
69 import org.eclipse.jgit.transport.PackParser;
70 import org.eclipse.jgit.transport.PackedObjectInfo;
71 import org.eclipse.jgit.util.FileUtils;
72 import org.eclipse.jgit.util.NB;
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(final 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 @Override
173 public long getPackSize() {
174 if (newPack == null)
175 return super.getPackSize();
176
177 File pack = newPack.getPackFile();
178 long size = pack.length();
179 String p = pack.getAbsolutePath();
180 String i = p.substring(0, p.length() - ".pack".length()) + ".idx";
181 File idx = new File(i);
182 if (idx.exists() && idx.isFile())
183 size += idx.length();
184 return size;
185 }
186
187 @Override
188 public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving)
189 throws IOException {
190 tmpPack = File.createTempFile("incoming_", ".pack", db.getDirectory());
191 tmpIdx = new File(db.getDirectory(), baseName(tmpPack) + ".idx");
192 try {
193 out = new RandomAccessFile(tmpPack, "rw");
194
195 super.parse(receiving, resolving);
196
197 out.seek(packEnd);
198 out.write(packHash);
199 out.getChannel().force(true);
200 out.close();
201
202 writeIdx();
203
204 tmpPack.setReadOnly();
205 tmpIdx.setReadOnly();
206
207 return renameAndOpenPack(getLockMessage());
208 } finally {
209 if (def != null)
210 def.end();
211 try {
212 if (out != null && out.getChannel().isOpen())
213 out.close();
214 } catch (IOException closeError) {
215
216 }
217 cleanupTemporaryFiles();
218 }
219 }
220
221 @Override
222 protected void onPackHeader(long objectCount) throws IOException {
223
224 }
225
226 @Override
227 protected void onBeginWholeObject(long streamPosition, int type,
228 long inflatedSize) throws IOException {
229 crc.reset();
230 }
231
232 @Override
233 protected void onEndWholeObject(PackedObjectInfo info) throws IOException {
234 info.setCRC((int) crc.getValue());
235 }
236
237 @Override
238 protected void onBeginOfsDelta(long streamPosition,
239 long baseStreamPosition, long inflatedSize) throws IOException {
240 crc.reset();
241 }
242
243 @Override
244 protected void onBeginRefDelta(long streamPosition, AnyObjectId baseId,
245 long inflatedSize) throws IOException {
246 crc.reset();
247 }
248
249 @Override
250 protected UnresolvedDelta onEndDelta() throws IOException {
251 UnresolvedDelta delta = new UnresolvedDelta();
252 delta.setCRC((int) crc.getValue());
253 return delta;
254 }
255
256 @Override
257 protected void onInflatedObjectData(PackedObjectInfo obj, int typeCode,
258 byte[] data) throws IOException {
259
260 }
261
262 @Override
263 protected void onObjectHeader(Source src, byte[] raw, int pos, int len)
264 throws IOException {
265 crc.update(raw, pos, len);
266 }
267
268 @Override
269 protected void onObjectData(Source src, byte[] raw, int pos, int len)
270 throws IOException {
271 crc.update(raw, pos, len);
272 }
273
274 @Override
275 protected void onStoreStream(byte[] raw, int pos, int len)
276 throws IOException {
277 out.write(raw, pos, len);
278 }
279
280 @Override
281 protected void onPackFooter(byte[] hash) throws IOException {
282 packEnd = out.getFilePointer();
283 origEnd = packEnd;
284 origHash = hash;
285 packHash = hash;
286 }
287
288 @Override
289 protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta,
290 ObjectTypeAndSize info) throws IOException {
291 out.seek(delta.getOffset());
292 crc.reset();
293 return readObjectHeader(info);
294 }
295
296 @Override
297 protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj,
298 ObjectTypeAndSize info) throws IOException {
299 out.seek(obj.getOffset());
300 crc.reset();
301 return readObjectHeader(info);
302 }
303
304 @Override
305 protected int readDatabase(byte[] dst, int pos, int cnt) throws IOException {
306 return out.read(dst, pos, cnt);
307 }
308
309 @Override
310 protected boolean checkCRC(int oldCRC) {
311 return oldCRC == (int) crc.getValue();
312 }
313
314 private static String baseName(File tmpPack) {
315 String name = tmpPack.getName();
316 return name.substring(0, name.lastIndexOf('.'));
317 }
318
319 private void cleanupTemporaryFiles() {
320 if (tmpIdx != null && !tmpIdx.delete() && tmpIdx.exists())
321 tmpIdx.deleteOnExit();
322 if (tmpPack != null && !tmpPack.delete() && tmpPack.exists())
323 tmpPack.deleteOnExit();
324 }
325
326 @Override
327 protected boolean onAppendBase(final int typeCode, final byte[] data,
328 final PackedObjectInfo info) throws IOException {
329 info.setOffset(packEnd);
330
331 final byte[] buf = buffer();
332 int sz = data.length;
333 int len = 0;
334 buf[len++] = (byte) ((typeCode << 4) | sz & 15);
335 sz >>>= 4;
336 while (sz > 0) {
337 buf[len - 1] |= 0x80;
338 buf[len++] = (byte) (sz & 0x7f);
339 sz >>>= 7;
340 }
341
342 tailDigest.update(buf, 0, len);
343 crc.reset();
344 crc.update(buf, 0, len);
345 out.seek(packEnd);
346 out.write(buf, 0, len);
347 packEnd += len;
348
349 if (def == null)
350 def = new Deflater(Deflater.DEFAULT_COMPRESSION, false);
351 else
352 def.reset();
353 def.setInput(data);
354 def.finish();
355
356 while (!def.finished()) {
357 len = def.deflate(buf);
358 tailDigest.update(buf, 0, len);
359 crc.update(buf, 0, len);
360 out.write(buf, 0, len);
361 packEnd += len;
362 }
363
364 info.setCRC((int) crc.getValue());
365 return true;
366 }
367
368 @Override
369 protected void onEndThinPack() throws IOException {
370 final byte[] buf = buffer();
371
372 final MessageDigest origDigest = Constants.newMessageDigest();
373 final MessageDigest tailDigest2 = Constants.newMessageDigest();
374 final MessageDigest packDigest = Constants.newMessageDigest();
375
376 long origRemaining = origEnd;
377 out.seek(0);
378 out.readFully(buf, 0, 12);
379 origDigest.update(buf, 0, 12);
380 origRemaining -= 12;
381
382 NB.encodeInt32(buf, 8, getObjectCount());
383 out.seek(0);
384 out.write(buf, 0, 12);
385 packDigest.update(buf, 0, 12);
386
387 for (;;) {
388 final int n = out.read(buf);
389 if (n < 0)
390 break;
391 if (origRemaining != 0) {
392 final int origCnt = (int) Math.min(n, origRemaining);
393 origDigest.update(buf, 0, origCnt);
394 origRemaining -= origCnt;
395 if (origRemaining == 0)
396 tailDigest2.update(buf, origCnt, n - origCnt);
397 } else
398 tailDigest2.update(buf, 0, n);
399
400 packDigest.update(buf, 0, n);
401 }
402
403 if (!Arrays.equals(origDigest.digest(), origHash) || !Arrays
404 .equals(tailDigest2.digest(), this.tailDigest.digest()))
405 throw new IOException(
406 JGitText.get().packCorruptedWhileWritingToFilesystem);
407
408 packHash = packDigest.digest();
409 }
410
411 private void writeIdx() throws IOException {
412 List<PackedObjectInfo> list = getSortedObjectList(null );
413 final FileOutputStream os = new FileOutputStream(tmpIdx);
414 try {
415 final PackIndexWriter iw;
416 if (indexVersion <= 0)
417 iw = PackIndexWriter.createOldestPossible(os, list);
418 else
419 iw = PackIndexWriter.createVersion(os, indexVersion);
420 iw.write(list, packHash);
421 os.getChannel().force(true);
422 } finally {
423 os.close();
424 }
425 }
426
427 private PackLock renameAndOpenPack(final String lockMessage)
428 throws IOException {
429 if (!keepEmpty && getObjectCount() == 0) {
430 cleanupTemporaryFiles();
431 return null;
432 }
433
434 final MessageDigest d = Constants.newMessageDigest();
435 final byte[] oeBytes = new byte[Constants.OBJECT_ID_LENGTH];
436 for (int i = 0; i < getObjectCount(); i++) {
437 final PackedObjectInfo oe = getObject(i);
438 oe.copyRawTo(oeBytes, 0);
439 d.update(oeBytes);
440 }
441
442 final String name = ObjectId.fromRaw(d.digest()).name();
443 final File packDir = new File(db.getDirectory(), "pack");
444 final File finalPack = new File(packDir, "pack-" + name + ".pack");
445 final File finalIdx = new File(packDir, "pack-" + name + ".idx");
446 final PackLock keep = new PackLock(finalPack, db.getFS());
447
448 if (!packDir.exists() && !packDir.mkdir() && !packDir.exists()) {
449
450
451
452 cleanupTemporaryFiles();
453 throw new IOException(MessageFormat.format(
454 JGitText.get().cannotCreateDirectory, packDir
455 .getAbsolutePath()));
456 }
457
458 if (finalPack.exists()) {
459
460
461 cleanupTemporaryFiles();
462 return null;
463 }
464
465 if (lockMessage != null) {
466
467
468
469 try {
470 if (!keep.lock(lockMessage))
471 throw new LockFailedException(finalPack,
472 MessageFormat.format(
473 JGitText.get().cannotLockPackIn, finalPack));
474 } catch (IOException e) {
475 cleanupTemporaryFiles();
476 throw e;
477 }
478 }
479
480 try {
481 FileUtils.rename(tmpPack, finalPack,
482 StandardCopyOption.ATOMIC_MOVE);
483 } catch (IOException e) {
484 cleanupTemporaryFiles();
485 keep.unlock();
486 throw new IOException(MessageFormat.format(
487 JGitText.get().cannotMovePackTo, finalPack), e);
488 }
489
490 try {
491 FileUtils.rename(tmpIdx, finalIdx, StandardCopyOption.ATOMIC_MOVE);
492 } catch (IOException e) {
493 cleanupTemporaryFiles();
494 keep.unlock();
495 if (!finalPack.delete())
496 finalPack.deleteOnExit();
497 throw new IOException(MessageFormat.format(
498 JGitText.get().cannotMoveIndexTo, finalIdx), e);
499 }
500
501 try {
502 newPack = db.openPack(finalPack);
503 } catch (IOException err) {
504 keep.unlock();
505 if (finalPack.exists())
506 FileUtils.delete(finalPack);
507 if (finalIdx.exists())
508 FileUtils.delete(finalIdx);
509 throw err;
510 }
511
512 return lockMessage != null ? keep : null;
513 }
514 }