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 package org.eclipse.jgit.internal.storage.file;
45
46 import static java.nio.file.StandardCopyOption.ATOMIC_MOVE;
47 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
48 import static org.eclipse.jgit.lib.Constants.OBJ_OFS_DELTA;
49 import static org.eclipse.jgit.lib.Constants.OBJ_REF_DELTA;
50
51 import java.io.BufferedInputStream;
52 import java.io.EOFException;
53 import java.io.File;
54 import java.io.FileOutputStream;
55 import java.io.FilterInputStream;
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.io.OutputStream;
59 import java.io.RandomAccessFile;
60 import java.nio.channels.Channels;
61 import java.text.MessageFormat;
62 import java.util.Collection;
63 import java.util.Collections;
64 import java.util.HashSet;
65 import java.util.List;
66 import java.util.Set;
67 import java.util.zip.CRC32;
68 import java.util.zip.DataFormatException;
69 import java.util.zip.Deflater;
70 import java.util.zip.DeflaterOutputStream;
71 import java.util.zip.Inflater;
72 import java.util.zip.InflaterInputStream;
73
74 import org.eclipse.jgit.errors.CorruptObjectException;
75 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
76 import org.eclipse.jgit.errors.LargeObjectException;
77 import org.eclipse.jgit.errors.MissingObjectException;
78 import org.eclipse.jgit.internal.JGitText;
79 import org.eclipse.jgit.internal.storage.pack.PackExt;
80 import org.eclipse.jgit.lib.AbbreviatedObjectId;
81 import org.eclipse.jgit.lib.AnyObjectId;
82 import org.eclipse.jgit.lib.Constants;
83 import org.eclipse.jgit.lib.InflaterCache;
84 import org.eclipse.jgit.lib.ObjectId;
85 import org.eclipse.jgit.lib.ObjectIdOwnerMap;
86 import org.eclipse.jgit.lib.ObjectInserter;
87 import org.eclipse.jgit.lib.ObjectLoader;
88 import org.eclipse.jgit.lib.ObjectReader;
89 import org.eclipse.jgit.lib.ObjectStream;
90 import org.eclipse.jgit.storage.pack.PackConfig;
91 import org.eclipse.jgit.transport.PackParser;
92 import org.eclipse.jgit.transport.PackedObjectInfo;
93 import org.eclipse.jgit.util.BlockList;
94 import org.eclipse.jgit.util.FileUtils;
95 import org.eclipse.jgit.util.IO;
96 import org.eclipse.jgit.util.NB;
97 import org.eclipse.jgit.util.io.CountingOutputStream;
98 import org.eclipse.jgit.util.sha1.SHA1;
99
100
101
102
103
104 public class PackInserter extends ObjectInserter {
105
106 private static final int INDEX_VERSION = 2;
107
108 private final ObjectDirectory db;
109
110 private List<PackedObjectInfo> objectList;
111 private ObjectIdOwnerMap<PackedObjectInfo> objectMap;
112 private boolean rollback;
113 private boolean checkExisting = true;
114
115 private int compression = Deflater.BEST_COMPRESSION;
116 private File tmpPack;
117 private PackStream packOut;
118 private Inflater cachedInflater;
119
120 private PackConfig pconfig;
121
122 PackInserter(ObjectDirectory db) {
123 this.db = db;
124 this.pconfig = new PackConfig(db.getConfig());
125 }
126
127
128
129
130
131
132
133
134
135 public void checkExisting(boolean check) {
136 checkExisting = check;
137 }
138
139
140
141
142
143
144
145 public void setCompressionLevel(int compression) {
146 this.compression = compression;
147 }
148
149 int getBufferSize() {
150 return buffer().length;
151 }
152
153
154 @Override
155 public ObjectId insert(int type, byte[] data, int off, int len)
156 throws IOException {
157 ObjectId id = idFor(type, data, off, len);
158 if (objectMap != null && objectMap.contains(id)) {
159 return id;
160 }
161
162 if (checkExisting && db.hasPackedObject(id)) {
163 return id;
164 }
165
166 long offset = beginObject(type, len);
167 packOut.compress.write(data, off, len);
168 packOut.compress.finish();
169 return endObject(id, offset);
170 }
171
172
173 @Override
174 public ObjectId insert(int type, long len, InputStream in)
175 throws IOException {
176 byte[] buf = buffer();
177 if (len <= buf.length) {
178 IO.readFully(in, buf, 0, (int) len);
179 return insert(type, buf, 0, (int) len);
180 }
181
182 long offset = beginObject(type, len);
183 SHA1 md = digest();
184 md.update(Constants.encodedTypeString(type));
185 md.update((byte) ' ');
186 md.update(Constants.encodeASCII(len));
187 md.update((byte) 0);
188
189 while (0 < len) {
190 int n = in.read(buf, 0, (int) Math.min(buf.length, len));
191 if (n <= 0) {
192 throw new EOFException();
193 }
194 md.update(buf, 0, n);
195 packOut.compress.write(buf, 0, n);
196 len -= n;
197 }
198 packOut.compress.finish();
199 return endObject(md.toObjectId(), offset);
200 }
201
202 private long beginObject(int type, long len) throws IOException {
203 if (packOut == null) {
204 beginPack();
205 }
206 long offset = packOut.getOffset();
207 packOut.beginObject(type, len);
208 return offset;
209 }
210
211 private ObjectId endObject(ObjectId id, long offset) {
212 PackedObjectInfo obj = new PackedObjectInfo(id);
213 obj.setOffset(offset);
214 obj.setCRC((int) packOut.crc32.getValue());
215 objectList.add(obj);
216 objectMap.addIfAbsent(obj);
217 return id;
218 }
219
220 private static File idxFor(File packFile) {
221 String p = packFile.getName();
222 return new File(
223 packFile.getParentFile(),
224 p.substring(0, p.lastIndexOf('.')) + ".idx");
225 }
226
227 private void beginPack() throws IOException {
228 objectList = new BlockList<>();
229 objectMap = new ObjectIdOwnerMap<>();
230
231 rollback = true;
232 tmpPack = File.createTempFile("insert_", ".pack", db.getDirectory());
233 packOut = new PackStream(tmpPack);
234
235
236 packOut.write(packOut.hdrBuf, 0, writePackHeader(packOut.hdrBuf, 1));
237 }
238
239 private static int writePackHeader(byte[] buf, int objectCount) {
240 System.arraycopy(Constants.PACK_SIGNATURE, 0, buf, 0, 4);
241 NB.encodeInt32(buf, 4, 2);
242 NB.encodeInt32(buf, 8, objectCount);
243 return 12;
244 }
245
246
247 @Override
248 public PackParser newPackParser(InputStream in) {
249 throw new UnsupportedOperationException();
250 }
251
252
253 @Override
254 public ObjectReader newReader() {
255 return new Reader();
256 }
257
258
259 @Override
260 public void flush() throws IOException {
261 if (tmpPack == null) {
262 return;
263 }
264
265 if (packOut == null) {
266 throw new IOException();
267 }
268
269 byte[] packHash;
270 try {
271 packHash = packOut.finishPack();
272 } finally {
273 packOut = null;
274 }
275
276 Collections.sort(objectList);
277 File tmpIdx = idxFor(tmpPack);
278 writePackIndex(tmpIdx, packHash, objectList);
279
280 PackFile realPack = new PackFile(db.getPackDirectory(),
281 computeName(objectList), PackExt.PACK);
282 db.closeAllPackHandles(realPack);
283 tmpPack.setReadOnly();
284 FileUtils.rename(tmpPack, realPack, ATOMIC_MOVE);
285
286 PackFile realIdx = realPack.create(PackExt.INDEX);
287 tmpIdx.setReadOnly();
288 try {
289 FileUtils.rename(tmpIdx, realIdx, ATOMIC_MOVE);
290 } catch (IOException e) {
291 File newIdx = new File(
292 realIdx.getParentFile(), realIdx.getName() + ".new");
293 try {
294 FileUtils.rename(tmpIdx, newIdx, ATOMIC_MOVE);
295 } catch (IOException e2) {
296 newIdx = tmpIdx;
297 e = e2;
298 }
299 throw new IOException(MessageFormat.format(
300 JGitText.get().panicCantRenameIndexFile, newIdx,
301 realIdx), e);
302 }
303
304 boolean interrupted = false;
305 try {
306 FileSnapshot snapshot = FileSnapshot.save(realPack);
307 if (pconfig.doWaitPreventRacyPack(snapshot.size())) {
308 snapshot.waitUntilNotRacy();
309 }
310 } catch (InterruptedException e) {
311 interrupted = true;
312 }
313 try {
314 db.openPack(realPack);
315 rollback = false;
316 } finally {
317 clear();
318 if (interrupted) {
319
320 Thread.currentThread().interrupt();
321 }
322 }
323 }
324
325 private static void writePackIndex(File idx, byte[] packHash,
326 List<PackedObjectInfo> list) throws IOException {
327 try (OutputStream os = new FileOutputStream(idx)) {
328 PackIndexWriter w = PackIndexWriter.createVersion(os, INDEX_VERSION);
329 w.write(list, packHash);
330 }
331 }
332
333 private ObjectId computeName(List<PackedObjectInfo> list) {
334 SHA1 md = digest().reset();
335 byte[] buf = buffer();
336 for (PackedObjectInfo otp : list) {
337 otp.copyRawTo(buf, 0);
338 md.update(buf, 0, OBJECT_ID_LENGTH);
339 }
340 return ObjectId.fromRaw(md.digest());
341 }
342
343
344 @Override
345 public void close() {
346 try {
347 if (packOut != null) {
348 try {
349 packOut.close();
350 } catch (IOException err) {
351
352 }
353 }
354 if (rollback && tmpPack != null) {
355 try {
356 FileUtils.delete(tmpPack);
357 } catch (IOException e) {
358
359 }
360 try {
361 FileUtils.delete(idxFor(tmpPack));
362 } catch (IOException e) {
363
364 }
365 rollback = false;
366 }
367 } finally {
368 clear();
369 try {
370 InflaterCache.release(cachedInflater);
371 } finally {
372 cachedInflater = null;
373 }
374 }
375 }
376
377 private void clear() {
378 objectList = null;
379 objectMap = null;
380 tmpPack = null;
381 packOut = null;
382 }
383
384 private Inflater inflater() {
385 if (cachedInflater == null) {
386 cachedInflater = InflaterCache.get();
387 } else {
388 cachedInflater.reset();
389 }
390 return cachedInflater;
391 }
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407 private class PackStream extends OutputStream {
408 final byte[] hdrBuf;
409 final CRC32 crc32;
410 final DeflaterOutputStream compress;
411
412 private final RandomAccessFile file;
413 private final CountingOutputStream out;
414 private final Deflater deflater;
415
416 private boolean atEnd;
417
418 PackStream(File pack) throws IOException {
419 file = new RandomAccessFile(pack, "rw");
420 out = new CountingOutputStream(new FileOutputStream(file.getFD()));
421 deflater = new Deflater(compression);
422 compress = new DeflaterOutputStream(this, deflater, 8192);
423 hdrBuf = new byte[32];
424 crc32 = new CRC32();
425 atEnd = true;
426 }
427
428 long getOffset() {
429
430
431
432
433
434 return out.getCount();
435 }
436
437 void seek(long offset) throws IOException {
438 file.seek(offset);
439 atEnd = false;
440 }
441
442 void beginObject(int objectType, long length) throws IOException {
443 crc32.reset();
444 deflater.reset();
445 write(hdrBuf, 0, encodeTypeSize(objectType, length));
446 }
447
448 private int encodeTypeSize(int type, long rawLength) {
449 long nextLength = rawLength >>> 4;
450 hdrBuf[0] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (type << 4) | (rawLength & 0x0F));
451 rawLength = nextLength;
452 int n = 1;
453 while (rawLength > 0) {
454 nextLength >>>= 7;
455 hdrBuf[n++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (rawLength & 0x7F));
456 rawLength = nextLength;
457 }
458 return n;
459 }
460
461 @Override
462 public void write(int b) throws IOException {
463 hdrBuf[0] = (byte) b;
464 write(hdrBuf, 0, 1);
465 }
466
467 @Override
468 public void write(byte[] data, int off, int len) throws IOException {
469 crc32.update(data, off, len);
470 if (!atEnd) {
471 file.seek(file.length());
472 atEnd = true;
473 }
474 out.write(data, off, len);
475 }
476
477 byte[] finishPack() throws IOException {
478
479
480
481
482
483 try {
484 file.seek(0);
485 out.write(hdrBuf, 0, writePackHeader(hdrBuf, objectList.size()));
486
487 byte[] buf = buffer();
488 SHA1 md = digest().reset();
489 file.seek(0);
490 while (true) {
491 int r = file.read(buf);
492 if (r < 0) {
493 break;
494 }
495 md.update(buf, 0, r);
496 }
497 byte[] packHash = md.digest();
498 out.write(packHash, 0, packHash.length);
499 return packHash;
500 } finally {
501 close();
502 }
503 }
504
505 @Override
506 public void close() throws IOException {
507 deflater.end();
508 try {
509 out.close();
510 } finally {
511 file.close();
512 }
513 }
514
515 byte[] inflate(long filePos, int len) throws IOException, DataFormatException {
516 byte[] dstbuf;
517 try {
518 dstbuf = new byte[len];
519 } catch (OutOfMemoryError noMemory) {
520 return null;
521 }
522
523 byte[] srcbuf = buffer();
524 Inflater inf = inflater();
525 filePos += setInput(filePos, inf, srcbuf);
526 for (int dstoff = 0;;) {
527 int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff);
528 dstoff += n;
529 if (inf.finished()) {
530 return dstbuf;
531 }
532 if (inf.needsInput()) {
533 filePos += setInput(filePos, inf, srcbuf);
534 } else if (n == 0) {
535 throw new DataFormatException();
536 }
537 }
538 }
539
540 private int setInput(long filePos, Inflater inf, byte[] buf)
541 throws IOException {
542 if (file.getFilePointer() != filePos) {
543 seek(filePos);
544 }
545 int n = file.read(buf);
546 if (n < 0) {
547 throw new EOFException(JGitText.get().unexpectedEofInPack);
548 }
549 inf.setInput(buf, 0, n);
550 return n;
551 }
552 }
553
554 private class Reader extends ObjectReader {
555 private final ObjectReader ctx;
556
557 private Reader() {
558 ctx = db.newReader();
559 setStreamFileThreshold(ctx.getStreamFileThreshold());
560 }
561
562 @Override
563 public ObjectReader newReader() {
564 return db.newReader();
565 }
566
567 @Override
568 public ObjectInserter getCreatedFromInserter() {
569 return PackInserter.this;
570 }
571
572 @Override
573 public Collection<ObjectId> resolve(AbbreviatedObjectId id)
574 throws IOException {
575 Collection<ObjectId> stored = ctx.resolve(id);
576 if (objectList == null) {
577 return stored;
578 }
579
580 Set<ObjectId> r = new HashSet<>(stored.size() + 2);
581 r.addAll(stored);
582 for (PackedObjectInfo obj : objectList) {
583 if (id.prefixCompare(obj) == 0) {
584 r.add(obj.copy());
585 }
586 }
587 return r;
588 }
589
590 @Override
591 public ObjectLoader open(AnyObjectId objectId, int typeHint)
592 throws MissingObjectException, IncorrectObjectTypeException,
593 IOException {
594 if (objectMap == null) {
595 return ctx.open(objectId, typeHint);
596 }
597
598 PackedObjectInfo obj = objectMap.get(objectId);
599 if (obj == null) {
600 return ctx.open(objectId, typeHint);
601 }
602
603 byte[] buf = buffer();
604 packOut.seek(obj.getOffset());
605 int cnt = packOut.file.read(buf, 0, 20);
606 if (cnt <= 0) {
607 throw new EOFException(JGitText.get().unexpectedEofInPack);
608 }
609
610 int c = buf[0] & 0xff;
611 int type = (c >> 4) & 7;
612 if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
613 throw new IOException(MessageFormat.format(
614 JGitText.get().cannotReadBackDelta, Integer.toString(type)));
615 }
616 if (typeHint != OBJ_ANY && type != typeHint) {
617 throw new IncorrectObjectTypeException(objectId.copy(), typeHint);
618 }
619
620 long sz = c & 0x0f;
621 int ptr = 1;
622 int shift = 4;
623 while ((c & 0x80) != 0) {
624 if (ptr >= cnt) {
625 throw new EOFException(JGitText.get().unexpectedEofInPack);
626 }
627 c = buf[ptr++] & 0xff;
628 sz += ((long) (c & 0x7f)) << shift;
629 shift += 7;
630 }
631
632 long zpos = obj.getOffset() + ptr;
633 if (sz < getStreamFileThreshold()) {
634 byte[] data = inflate(obj, zpos, (int) sz);
635 if (data != null) {
636 return new ObjectLoader.SmallObject(type, data);
637 }
638 }
639 return new StreamLoader(type, sz, zpos);
640 }
641
642 private byte[] inflate(PackedObjectInfo obj, long zpos, int sz)
643 throws IOException, CorruptObjectException {
644 try {
645 return packOut.inflate(zpos, sz);
646 } catch (DataFormatException dfe) {
647 throw new CorruptObjectException(
648 MessageFormat.format(
649 JGitText.get().objectAtHasBadZlibStream,
650 Long.valueOf(obj.getOffset()),
651 tmpPack.getAbsolutePath()),
652 dfe);
653 }
654 }
655
656 @Override
657 public Set<ObjectId> getShallowCommits() throws IOException {
658 return ctx.getShallowCommits();
659 }
660
661 @Override
662 public void close() {
663 ctx.close();
664 }
665
666 private class StreamLoader extends ObjectLoader {
667 private final int type;
668 private final long size;
669 private final long pos;
670
671 StreamLoader(int type, long size, long pos) {
672 this.type = type;
673 this.size = size;
674 this.pos = pos;
675 }
676
677 @Override
678 public ObjectStream openStream()
679 throws MissingObjectException, IOException {
680 int bufsz = buffer().length;
681 packOut.seek(pos);
682
683 InputStream fileStream = new FilterInputStream(
684 Channels.newInputStream(packOut.file.getChannel())) {
685
686
687
688
689
690
691 @Override
692 public int read() throws IOException {
693 packOut.atEnd = false;
694 return super.read();
695 }
696
697 @Override
698 public int read(byte[] b) throws IOException {
699 packOut.atEnd = false;
700 return super.read(b);
701 }
702
703 @Override
704 public int read(byte[] b, int off, int len) throws IOException {
705 packOut.atEnd = false;
706 return super.read(b,off,len);
707 }
708
709 @Override
710 public void close() {
711
712
713 }
714 };
715 return new ObjectStream.Filter(
716 type, size,
717 new BufferedInputStream(
718 new InflaterInputStream(fileStream, inflater(), bufsz), bufsz));
719 }
720
721 @Override
722 public int getType() {
723 return type;
724 }
725
726 @Override
727 public long getSize() {
728 return size;
729 }
730
731 @Override
732 public byte[] getCachedBytes() throws LargeObjectException {
733 throw new LargeObjectException.ExceedsLimit(
734 getStreamFileThreshold(), size);
735 }
736 }
737 }
738 }