1
2
3
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 }