package com.github.lindenb.jvarkit.io.tar; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.util.Arrays; public class TarReader implements Closeable { private static final int BLOCK_SIZE=512; private static final byte zeroBlock[]=new byte[BLOCK_SIZE]; {{{ Arrays.fill(zeroBlock, (byte)0); }}} private static byte[] _malloc(int size) { return new byte[size]; } /* Posix tar header*/ private class TARFileHeader { byte name[]=_malloc(100); byte mode[]=_malloc(8); byte uid[]=_malloc(8); byte gid[]=_malloc(8); byte size[]=_malloc(12); byte mtime[]=_malloc(12); byte checksum[]=_malloc(8); byte typeflag[]=_malloc(1); byte linkname[]=_malloc(100); byte magic[]=_malloc(6); byte version[]=_malloc(2); byte uname[]=_malloc(32); byte gname[]=_malloc(32); byte devmajor[]=_malloc(8); byte devminor[]=_malloc(8); byte prefix[]=_malloc(155); byte pad[]=_malloc(12); public String getName() { int length=0; while(length< name.length && name[length]!=0) { length++; } return new String(name,0,length); } public long getSize() { return parseOctalOrBinary(size,0,size.length); } } public class Entry extends InputStream { private final long length; private long nRead=0L; private final TARFileHeader meta; private Entry(final TARFileHeader meta) { this.meta=meta; this.length=meta.getSize(); } public long getOffset() { return nRead; } public long getLength() { return length; } @Override public int read() throws IOException { if(nRead>=length) return -1; int c=TarReader.this.in.read(); if(c==-1) return -1; nRead++; if(nRead==length) { finish(); } return c; } private void finish() throws IOException { while(nRead%BLOCK_SIZE!=0) { if(TarReader.this.in.read()==-1) throw new IOException(); ++nRead; } } public void skip() throws IOException { while(read()!=-1) ; } public String getName() { return meta.getName(); } @Override public void close() { } @Override public String toString() { return getName()+" "+nRead+"/"+length; } } private InputStream in=null; public TarReader(InputStream in) throws IOException { this.in=in; } static int copy(byte array[],byte field[],int off) { System.arraycopy(array, off,field,0,field.length); return off+field.length; } private TARFileHeader readTARFileHeader() throws IOException { byte array[]=new byte[BLOCK_SIZE]; int nRead=0; while(nRead< BLOCK_SIZE) { int x=in.read(array,nRead,BLOCK_SIZE-nRead); if(x<0) { throw new IOException("cannot read bytes "+nRead+" / "+array.length); } nRead+=x; } if(Arrays.equals(array, zeroBlock)) return null; TARFileHeader h=new TARFileHeader(); int off=0; off=copy(array,h.name,off); off=copy(array,h.mode,off); off=copy(array,h.uid,off); off=copy(array,h.gid,off); off=copy(array,h.size,off); off=copy(array,h.mtime,off); off=copy(array,h.checksum,off); off=copy(array,h.typeflag,off); off=copy(array,h.linkname,off); off=copy(array,h.magic,off); off=copy(array,h.version,off); off=copy(array,h.uname,off); off=copy(array,h.gname,off); off=copy(array,h.devmajor,off); off=copy(array,h.devminor,off); off=copy(array,h.prefix,off); off=copy(array,h.pad,off); if(off!=BLOCK_SIZE) throw new IOException(); return h; } @Override public void close() throws IOException { if(in!=null) in.close(); in=null; } public Entry next() throws IOException { TARFileHeader h= readTARFileHeader(); if(h==null) return null; return new Entry(h); } /* -rw-r--r-- tm/tm 11255269 2014-01-23 14:20 citations.dmp -rw-r--r-- tm/tm 2768900 2014-01-23 14:20 delnodes.dmp -rw-r--r-- tm/tm 419 2014-01-23 14:20 division.dmp -rw-r--r-- tm/tm 11559 2014-01-23 14:20 gc.prt -rw-r--r-- tm/tm 3566 2014-01-23 14:20 gencode.dmp -rw-r--r-- tm/tm 554847 2014-01-23 14:20 merged.dmp -rw-r--r-- tm/tm 96596932 2014-01-23 14:20 names.dmp */ private static long parseOctalOrBinary(final byte[] buffer, final int offset, final int length) { if ((buffer[offset] & 0x80) == 0) { return parseOctal(buffer, offset, length); } final boolean negative = buffer[offset] == (byte) 0xff; if (length < 9) { return parseBinaryLong(buffer, offset, length, negative); } return parseBinaryBigInteger(buffer, offset, length, negative); } private static long parseOctal(final byte[] buffer, final int offset, final int length) { long result = 0; int end = offset + length; int start = offset; if (length < 2){ throw new IllegalArgumentException("Length "+length+" must be at least 2"); } if (buffer[start] == 0) { return 0L; } // Skip leading spaces while (start < end){ if (buffer[start] == ' '){ start++; } else { break; } } // Must have trailing NUL or space byte trailer; trailer = buffer[end-1]; if (trailer == 0 || trailer == ' '){ end--; } else { throw new IllegalArgumentException(""); } // May have additional NUL or space trailer = buffer[end-1]; if (trailer == 0 || trailer == ' '){ end--; } for ( ;start < end; start++) { final byte currentByte = buffer[start]; // CheckStyle:MagicNumber OFF if (currentByte < '0' || currentByte > '7'){ throw new IllegalArgumentException(""); } result = (result << 3) + (currentByte - '0'); // convert from ASCII // CheckStyle:MagicNumber ON } return result; } private static long parseBinaryLong(final byte[] buffer, final int offset, final int length, final boolean negative) { if (length >= 9) { throw new IllegalArgumentException("At offset " + offset + ", " + length + " byte binary number" + " exceeds maximum signed long" + " value"); } long val = 0; for (int i = 1; i < length; i++) { val = (val << 8) + (buffer[offset + i] & 0xff); } if (negative) { // 2's complement val--; val ^= ((long) Math.pow(2, (length - 1) * 8) - 1); } return negative ? -val : val; } private static long parseBinaryBigInteger(final byte[] buffer, final int offset, final int length, final boolean negative) { byte[] remainder = new byte[length - 1]; System.arraycopy(buffer, offset + 1, remainder, 0, length - 1); BigInteger val = new BigInteger(remainder); if (negative) { // 2's complement val = val.add(BigInteger.valueOf(-1)).not(); } if (val.bitLength() > 63) { throw new IllegalArgumentException("At offset " + offset + ", " + length + " byte binary number" + " exceeds maximum signed long" + " value"); } return negative ? -val.longValue() : val.longValue(); } }