View Javadoc

1   /*
2    * jGuild Project: jRPM
3    * Released under the Apache License ( http://www.apache.org/LICENSE )
4    */
5   package com.jguild.jrpm.io.cpio;
6   
7   import java.io.EOFException;
8   import java.io.FilterInputStream;
9   import java.io.IOException;
10  import java.io.InputStream;
11  
12  /***
13   * CPIOInputStream is a stream for reading cpio streams.
14   * All formats of cpio are supported (old ascii, old binary, new portable format
15   * and the new portable format with crc).
16   * 
17   * <p>
18   * The stream can be read by extracting a cpio entry (containing all informations
19   * about a entry) and afterwards reading from the stream the file specified by the
20   * entry.
21   * <p>
22   * <code><pre>
23   * CPIOInputStream cpioIn = new CPIOInputStream(new BufferedInputStream(new FileInputStream(new File("test.cpio"))));
24   * CPIOEntry cpioEntry;
25   *
26   * while ((cpioEntry = cpioIn.getNextEntry()) != null) {
27   *     System.out.println(cpioEntry.getName());
28   *     int tmp;
29   *     StringBuffer buf = new StringBuffer();
30   *     while ((tmp = cpIn.read()) != -1) {
31   *        buf.append((char) tmp);
32   *     }
33   *     System.out.println(buf.toString());
34   * }
35   * cpioIn.close();
36   * </pre></code>
37   *
38   * Note: This implementation should be compatible to cpio 2.5
39   *  
40   * @author Michael Kuss
41   */
42  public class CPIOInputStream extends FilterInputStream implements CPIOConstants {
43      private boolean closed = false;
44      private CPIOEntry entry;
45      private long entryBytesRead = 0;
46      private boolean entryEOF = false;
47      private byte[] singleByteBuf = new byte[1];
48      private byte tmpbuf[] = new byte[4096];
49      private long crc = 0;
50  
51      /***
52       * Construct the cpio input stream
53       * 
54       * @param in The cpio stream
55       */
56      public CPIOInputStream(InputStream in) {
57          super(in);
58      }
59  
60      /***
61       * Returns 0 after EOF has reached for the current entry data, otherwise
62       * always return 1.
63       * <p>
64       * Programs should not count on this method to return the actual number of
65       * bytes that could be read without blocking.
66       * 
67       * @return 1 before EOF and 0 after EOF has reached for current entry.
68       * @exception IOException if an I/O error has occurred or
69       *                        if a CPIO file error has occurred  
70       */
71      public int available() throws IOException {
72          ensureOpen();
73          if (entryEOF) {
74              return 0;
75          } else {
76              return 1;
77          }
78      }
79  
80      /***
81       * Converts a byte array to a long. 
82       * Halfwords can be swaped with setting swapHalfWord=true.
83       * 
84       * @param number An array of bytes containing a number
85       * @param swapHalfWord Swap halfwords ([0][1][2][3]->[1][0][3][2])
86       * @return The long value
87       */
88      private long byteArray2long(byte number[], boolean swapHalfWord) {
89          long ret = 0;
90          int pos = 0;
91          byte tmp_number[] = new byte[number.length];
92          System.arraycopy(number, 0, tmp_number, 0, number.length);
93  
94          if (tmp_number.length % 2 != 0) { throw new UnsupportedOperationException(); }
95  
96          if (!swapHalfWord) {
97              byte tmp = 0;
98              for (pos = 0; pos < tmp_number.length; pos++) {
99                  tmp = tmp_number[pos];
100                 tmp_number[pos++] = tmp_number[pos];
101                 tmp_number[pos] = tmp;
102             }
103         }
104 
105         ret = tmp_number[0] & 0xFF;
106         for (pos = 1; pos < tmp_number.length; pos++) {
107             ret <<= 8;
108             ret |= tmp_number[pos] & 0xFF;
109         }
110         return ret;
111     }
112 
113     /***
114      * Closes the CPIO input stream.
115      * 
116      * @exception IOException if an I/O error has occurred
117      */
118     public void close() throws IOException {
119         if (!closed) {
120             super.close();
121             closed = true;
122         }
123     }
124 
125     /***
126      * Closes the current CPIO entry and positions the stream for reading the
127      * next entry.
128      * 
129      * @exception IOException if an I/O error has occurred or
130      *                        if a CPIO file error has occurred  
131      */
132     public void closeEntry() throws IOException {
133         ensureOpen();
134         while (read(tmpbuf, 0, tmpbuf.length) != -1)
135             ;
136         entryEOF = true;
137     }
138 
139     /***
140      * Check to make sure that this stream has not been closed
141      *
142      * @exception IOException if the stream is already closed
143      */
144     private void ensureOpen() throws IOException {
145         if (closed) { throw new IOException("Stream closed"); }
146     }
147 
148     /***
149      * Reads the next CPIO file entry and positions stream at the beginning
150      * of the entry data.
151      * 
152      * @return the CPIOEntry just read
153      * @exception IOException if an I/O error has occurred or
154      *                        if a CPIO file error has occurred  
155      */
156     public CPIOEntry getNextEntry() throws IOException {
157         ensureOpen();
158         if (entry != null) {
159             closeEntry();
160         }
161         byte magic[] = new byte[2];
162         readFully(magic, 0, magic.length);
163         if (byteArray2long(magic, false) == MAGIC_OLD_BINARY) {
164             entry = readOldBinaryEntry(false);
165         } else if (byteArray2long(magic, true) == MAGIC_OLD_BINARY) {
166             entry = readOldBinaryEntry(true);
167         } else {
168             byte more_magic[] = new byte[4];
169             readFully(more_magic, 0, more_magic.length);
170             byte tmp[] = new byte[6];
171             System.arraycopy(magic, 0, tmp, 0, magic.length);
172             System.arraycopy(more_magic, 0, tmp, magic.length,
173                     more_magic.length);
174             String magicString = new String(tmp);
175             if (magicString.equals(MAGIC_NEW)) {
176                 entry = readNewEntry(false);
177             } else if (magicString.equals(MAGIC_NEW_CRC)) {
178                 entry = readNewEntry(true);
179             } else if (magicString.equals(MAGIC_OLD_ASCII)) {
180                 entry = readOldAsciiEntry();
181             } else {
182                 throw new IOException("Unknown magic [" + magicString + "]");
183             }
184         }
185 
186         entryBytesRead = 0;
187         entryEOF = false;
188         crc = 0;
189 
190         if (entry.getName().equals("TRAILER!!!")) {
191             entryEOF = true;
192             return null;
193         }
194         return entry;
195     }
196 
197     private long pad(long count, int border) throws IOException {
198         long skip = count % border;
199         if (skip > 0) skip = in.skip(border - skip);
200         return skip;
201     }
202 
203     /***
204      * Reads a byte of data. This method will block until
205      * enough input is available.
206      * 
207      * @return the byte read, or -1 if end of input is reached
208      * @exception IOException if an I/O error has occurred or
209      *                        if a CPIO file error has occurred  
210      */
211     public int read() throws IOException {
212         return read(singleByteBuf, 0, 1) == -1 ? -1 : singleByteBuf[0] & 0xff;
213     }
214 
215     /***
216      * Reads from the current CPIO entry into an array of bytes. Blocks until
217      * some input is available.
218      * 
219      * @param b the buffer into which the data is read
220      * @param off the start offset of the data
221      * @param len the maximum number of bytes read
222      * @return the actual number of bytes read, or -1 if the end of the
223      *         entry is reached
224      * @exception IOException if an I/O error has occurred or
225      *                        if a CPIO file error has occurred  
226      */
227     public int read(byte[] b, int off, int len) throws IOException {
228         ensureOpen();
229         if (off < 0 || len < 0 || off > b.length - len) {
230             throw new IndexOutOfBoundsException();
231         } else if (len == 0) { return 0; }
232 
233         if (entry == null || entryEOF) { return -1; }
234         if (entryBytesRead == entry.getFileSize()) {
235             if ((entry.getFormat() | FORMAT_NEW_MASK) == FORMAT_NEW_MASK) pad(
236                     entry.getFileSize(), 4);
237             else if ((entry.getFormat() | FORMAT_OLD_BINARY) == FORMAT_OLD_BINARY)
238                     pad(entry.getFileSize(), 2);
239             entryEOF = true;
240             if ((entry.getFormat() | FORMAT_NEW_CRC) == FORMAT_NEW_CRC) {
241                 if (crc != entry.getChksum())
242                         throw new IOException("CRC Error");
243             }
244             return -1;
245         }
246         int tmplength = (int) Math.min(len, entry.getFileSize()
247                 - entryBytesRead);
248         if (tmplength < 0) return -1;
249 
250         int tmpread = in.read(b, off, tmplength);
251         if ((entry.getFormat() | FORMAT_NEW_CRC) == FORMAT_NEW_CRC) {
252             for (int pos = 0; pos < tmpread; pos++)
253                 crc += b[pos] & 0xFF;
254         }
255         entryBytesRead += tmpread;
256 
257         return tmpread;
258     }
259 
260     private final int readFully(byte b[], int off, int len) throws IOException {
261         if (len < 0) throw new IndexOutOfBoundsException();
262         int n = 0;
263         while (n < len) {
264             int count = in.read(b, off + n, len - n);
265             if (count < 0) throw new EOFException();
266             n += count;
267         }
268         return n;
269     }
270 
271     private long readBinaryLong(int length, boolean swapHalfWord)
272             throws IOException {
273         byte tmp[] = new byte[length];
274         readFully(tmp, 0, tmp.length);
275         return byteArray2long(tmp, swapHalfWord);
276     }
277 
278     private long readAsciiLong(int length, int radix) throws IOException {
279         byte tmpBuffer[] = new byte[length];
280         readFully(tmpBuffer, 0, tmpBuffer.length);
281         return Long.parseLong(new String(tmpBuffer), radix);
282     }
283 
284     private CPIOEntry readNewEntry(boolean hasCrc) throws IOException {
285         CPIOEntry ret;
286         if (hasCrc) ret = new CPIOEntry(FORMAT_NEW_CRC);
287         else
288             ret = new CPIOEntry(FORMAT_NEW);
289 
290         ret.setInode(readAsciiLong(8, 16));
291         ret.setMode(readAsciiLong(8, 16));
292         ret.setUID(readAsciiLong(8, 16));
293         ret.setGID(readAsciiLong(8, 16));
294         ret.setNumberOfLinks(readAsciiLong(8, 16));
295         ret.setTime(readAsciiLong(8, 16));
296         ret.setFileSize(readAsciiLong(8, 16));
297         ret.setDeviceMaj(readAsciiLong(8, 16));
298         ret.setDeviceMin(readAsciiLong(8, 16));
299         ret.setRemoteDeviceMaj(readAsciiLong(8, 16));
300         ret.setRemoteDeviceMin(readAsciiLong(8, 16));
301         long namesize = readAsciiLong(8, 16);
302         ret.setChksum(readAsciiLong(8, 16));
303         ret.setName(readCString((int) namesize));
304         pad(ret.getHeaderSize() + namesize, 4);
305 
306         return ret;
307     }
308 
309     private CPIOEntry readOldAsciiEntry() throws IOException {
310         CPIOEntry ret = new CPIOEntry(FORMAT_OLD_ASCII);
311 
312         ret.setDevice(readAsciiLong(6, 8));
313         ret.setInode(readAsciiLong(6, 8));
314         ret.setMode(readAsciiLong(6, 8));
315         ret.setUID(readAsciiLong(6, 8));
316         ret.setGID(readAsciiLong(6, 8));
317         ret.setNumberOfLinks(readAsciiLong(6, 8));
318         ret.setRemoteDevice(readAsciiLong(6, 8));
319         ret.setTime(readAsciiLong(11, 8));
320         long namesize = readAsciiLong(6, 8);
321         ret.setFileSize(readAsciiLong(11, 8));
322         ret.setName(readCString((int) namesize));
323 
324         return ret;
325     }
326 
327     private CPIOEntry readOldBinaryEntry(boolean swapHalfWord)
328             throws IOException {
329         CPIOEntry ret = new CPIOEntry(FORMAT_OLD_BINARY);
330 
331         ret.setDevice(readBinaryLong(2, swapHalfWord));
332         ret.setInode(readBinaryLong(2, swapHalfWord));
333         ret.setMode(readBinaryLong(2, swapHalfWord));
334         ret.setUID(readBinaryLong(2, swapHalfWord));
335         ret.setGID(readBinaryLong(2, swapHalfWord));
336         ret.setNumberOfLinks(readBinaryLong(2, swapHalfWord));
337         ret.setRemoteDevice(readBinaryLong(2, swapHalfWord));
338         ret.setTime(readBinaryLong(4, swapHalfWord));
339         long namesize = readBinaryLong(2, swapHalfWord);
340         ret.setFileSize(readBinaryLong(4, swapHalfWord));
341         ret.setName(readCString((int) namesize));
342         pad(ret.getHeaderSize() + namesize, 2);
343 
344         return ret;
345     }
346 
347     private String readCString(int length) throws IOException {
348         byte tmpBuffer[] = new byte[length];
349         readFully(tmpBuffer, 0, tmpBuffer.length);
350         return new String(tmpBuffer, 0, tmpBuffer.length - 1);
351     }
352 
353     /***
354      * Skips specified number of bytes in the current CPIO entry.
355      * 
356      * @param n the number of bytes to skip
357      * @return the actual number of bytes skipped
358      * @exception IOException if an I/O error has occurred
359      * @exception IllegalArgumentException if n < 0
360      */
361     public long skip(long n) throws IOException {
362         if (n < 0) { throw new IllegalArgumentException("negative skip length"); }
363         ensureOpen();
364         int max = (int) Math.min(n, Integer.MAX_VALUE);
365         int total = 0;
366 
367         while (total < max) {
368             int len = max - total;
369             if (len > tmpbuf.length) {
370                 len = tmpbuf.length;
371             }
372             len = read(tmpbuf, 0, len);
373             if (len == -1) {
374                 entryEOF = true;
375                 break;
376             }
377             total += len;
378         }
379         return total;
380     }
381 }