001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.commons.compress.archivers.cpio;
020
021 import java.io.EOFException;
022 import java.io.IOException;
023 import java.io.InputStream;
024
025 import org.apache.commons.compress.archivers.ArchiveEntry;
026 import org.apache.commons.compress.archivers.ArchiveInputStream;
027 import org.apache.commons.compress.utils.ArchiveUtils;
028
029 /**
030 * CPIOArchiveInputStream is a stream for reading cpio streams. All formats of
031 * cpio are supported (old ascii, old binary, new portable format and the new
032 * portable format with crc).
033 * <p/>
034 * <p/>
035 * The stream can be read by extracting a cpio entry (containing all
036 * informations about a entry) and afterwards reading from the stream the file
037 * specified by the entry.
038 * <p/>
039 * <code><pre>
040 * CPIOArchiveInputStream cpioIn = new CPIOArchiveInputStream(
041 * new FileInputStream(new File("test.cpio")));
042 * CPIOArchiveEntry cpioEntry;
043 * <p/>
044 * while ((cpioEntry = cpioIn.getNextEntry()) != null) {
045 * System.out.println(cpioEntry.getName());
046 * int tmp;
047 * StringBuffer buf = new StringBuffer();
048 * while ((tmp = cpIn.read()) != -1) {
049 * buf.append((char) tmp);
050 * }
051 * System.out.println(buf.toString());
052 * }
053 * cpioIn.close();
054 * </pre></code>
055 * <p/>
056 * Note: This implementation should be compatible to cpio 2.5
057 *
058 * This class uses mutable fields and is not considered to be threadsafe.
059 *
060 * Based on code from the jRPM project (jrpm.sourceforge.net)
061 */
062
063 public class CpioArchiveInputStream extends ArchiveInputStream implements
064 CpioConstants {
065
066 private boolean closed = false;
067
068 private CpioArchiveEntry entry;
069
070 private long entryBytesRead = 0;
071
072 private boolean entryEOF = false;
073
074 private final byte tmpbuf[] = new byte[4096];
075
076 private long crc = 0;
077
078 private final InputStream in;
079
080 /**
081 * Construct the cpio input stream
082 *
083 * @param in
084 * The cpio stream
085 */
086 public CpioArchiveInputStream(final InputStream in) {
087 this.in = in;
088 }
089
090 /**
091 * Returns 0 after EOF has reached for the current entry data, otherwise
092 * always return 1.
093 * <p/>
094 * Programs should not count on this method to return the actual number of
095 * bytes that could be read without blocking.
096 *
097 * @return 1 before EOF and 0 after EOF has reached for current entry.
098 * @throws IOException
099 * if an I/O error has occurred or if a CPIO file error has
100 * occurred
101 */
102 @Override
103 public int available() throws IOException {
104 ensureOpen();
105 if (this.entryEOF) {
106 return 0;
107 }
108 return 1;
109 }
110
111 /**
112 * Closes the CPIO input stream.
113 *
114 * @throws IOException
115 * if an I/O error has occurred
116 */
117 @Override
118 public void close() throws IOException {
119 if (!this.closed) {
120 in.close();
121 this.closed = true;
122 }
123 }
124
125 /**
126 * Closes the current CPIO entry and positions the stream for reading the
127 * next entry.
128 *
129 * @throws IOException
130 * if an I/O error has occurred or if a CPIO file error has
131 * occurred
132 */
133 private void closeEntry() throws IOException {
134 ensureOpen();
135 while (read(this.tmpbuf, 0, this.tmpbuf.length) != -1) { // NOPMD
136 // do nothing
137 }
138
139 this.entryEOF = true;
140 }
141
142 /**
143 * Check to make sure that this stream has not been closed
144 *
145 * @throws IOException
146 * if the stream is already closed
147 */
148 private void ensureOpen() throws IOException {
149 if (this.closed) {
150 throw new IOException("Stream closed");
151 }
152 }
153
154 /**
155 * Reads the next CPIO file entry and positions stream at the beginning of
156 * the entry data.
157 *
158 * @return the CPIOArchiveEntry just read
159 * @throws IOException
160 * if an I/O error has occurred or if a CPIO file error has
161 * occurred
162 */
163 public CpioArchiveEntry getNextCPIOEntry() throws IOException {
164 ensureOpen();
165 if (this.entry != null) {
166 closeEntry();
167 }
168 byte magic[] = new byte[2];
169 readFully(magic, 0, magic.length);
170 if (CpioUtil.byteArray2long(magic, false) == MAGIC_OLD_BINARY) {
171 this.entry = readOldBinaryEntry(false);
172 } else if (CpioUtil.byteArray2long(magic, true) == MAGIC_OLD_BINARY) {
173 this.entry = readOldBinaryEntry(true);
174 } else {
175 byte more_magic[] = new byte[4];
176 readFully(more_magic, 0, more_magic.length);
177 byte tmp[] = new byte[6];
178 System.arraycopy(magic, 0, tmp, 0, magic.length);
179 System.arraycopy(more_magic, 0, tmp, magic.length,
180 more_magic.length);
181 String magicString = ArchiveUtils.toAsciiString(tmp);
182 if (magicString.equals(MAGIC_NEW)) {
183 this.entry = readNewEntry(false);
184 } else if (magicString.equals(MAGIC_NEW_CRC)) {
185 this.entry = readNewEntry(true);
186 } else if (magicString.equals(MAGIC_OLD_ASCII)) {
187 this.entry = readOldAsciiEntry();
188 } else {
189 throw new IOException("Unknown magic [" + magicString + "]. Occured at byte: " + getBytesRead());
190 }
191 }
192
193 this.entryBytesRead = 0;
194 this.entryEOF = false;
195 this.crc = 0;
196
197 if (this.entry.getName().equals(CPIO_TRAILER)) {
198 this.entryEOF = true;
199 return null;
200 }
201 return this.entry;
202 }
203
204 private void skip(int bytes) throws IOException{
205 final byte[] buff = new byte[4]; // Cannot be more than 3 bytes
206 if (bytes > 0) {
207 readFully(buff, 0, bytes);
208 }
209 }
210
211 /**
212 * Reads from the current CPIO entry into an array of bytes. Blocks until
213 * some input is available.
214 *
215 * @param b
216 * the buffer into which the data is read
217 * @param off
218 * the start offset of the data
219 * @param len
220 * the maximum number of bytes read
221 * @return the actual number of bytes read, or -1 if the end of the entry is
222 * reached
223 * @throws IOException
224 * if an I/O error has occurred or if a CPIO file error has
225 * occurred
226 */
227 @Override
228 public int read(final byte[] b, final int off, final int len)
229 throws IOException {
230 ensureOpen();
231 if (off < 0 || len < 0 || off > b.length - len) {
232 throw new IndexOutOfBoundsException();
233 } else if (len == 0) {
234 return 0;
235 }
236
237 if (this.entry == null || this.entryEOF) {
238 return -1;
239 }
240 if (this.entryBytesRead == this.entry.getSize()) {
241 skip(entry.getDataPadCount());
242 this.entryEOF = true;
243 if (this.entry.getFormat() == FORMAT_NEW_CRC
244 && this.crc != this.entry.getChksum()) {
245 throw new IOException("CRC Error. Occured at byte: "
246 + getBytesRead());
247 }
248 return -1; // EOF for this entry
249 }
250 int tmplength = (int) Math.min(len, this.entry.getSize()
251 - this.entryBytesRead);
252 if (tmplength < 0) {
253 return -1;
254 }
255
256 int tmpread = readFully(b, off, tmplength);
257 if (this.entry.getFormat() == FORMAT_NEW_CRC) {
258 for (int pos = 0; pos < tmpread; pos++) {
259 this.crc += b[pos] & 0xFF;
260 }
261 }
262 this.entryBytesRead += tmpread;
263
264 return tmpread;
265 }
266
267 private final int readFully(final byte[] b, final int off, final int len)
268 throws IOException {
269 if (len < 0) {
270 throw new IndexOutOfBoundsException();
271 }
272 int n = 0;
273 while (n < len) {
274 int count = this.in.read(b, off + n, len - n);
275 count(count);
276 if (count < 0) {
277 throw new EOFException();
278 }
279 n += count;
280 }
281 return n;
282 }
283
284 private long readBinaryLong(final int length, final boolean swapHalfWord)
285 throws IOException {
286 byte tmp[] = new byte[length];
287 readFully(tmp, 0, tmp.length);
288 return CpioUtil.byteArray2long(tmp, swapHalfWord);
289 }
290
291 private long readAsciiLong(final int length, final int radix)
292 throws IOException {
293 byte tmpBuffer[] = new byte[length];
294 readFully(tmpBuffer, 0, tmpBuffer.length);
295 return Long.parseLong(ArchiveUtils.toAsciiString(tmpBuffer), radix);
296 }
297
298 private CpioArchiveEntry readNewEntry(final boolean hasCrc)
299 throws IOException {
300 CpioArchiveEntry ret;
301 if (hasCrc) {
302 ret = new CpioArchiveEntry(FORMAT_NEW_CRC);
303 } else {
304 ret = new CpioArchiveEntry(FORMAT_NEW);
305 }
306
307 ret.setInode(readAsciiLong(8, 16));
308 long mode = readAsciiLong(8, 16);
309 if (mode != 0){ // mode is initialised to 0
310 ret.setMode(mode);
311 }
312 ret.setUID(readAsciiLong(8, 16));
313 ret.setGID(readAsciiLong(8, 16));
314 ret.setNumberOfLinks(readAsciiLong(8, 16));
315 ret.setTime(readAsciiLong(8, 16));
316 ret.setSize(readAsciiLong(8, 16));
317 ret.setDeviceMaj(readAsciiLong(8, 16));
318 ret.setDeviceMin(readAsciiLong(8, 16));
319 ret.setRemoteDeviceMaj(readAsciiLong(8, 16));
320 ret.setRemoteDeviceMin(readAsciiLong(8, 16));
321 long namesize = readAsciiLong(8, 16);
322 ret.setChksum(readAsciiLong(8, 16));
323 String name = readCString((int) namesize);
324 ret.setName(name);
325 if (mode == 0 && !name.equals(CPIO_TRAILER)){
326 throw new IOException("Mode 0 only allowed in the trailer. Found entry name: "+name + " Occured at byte: " + getBytesRead());
327 }
328 skip(ret.getHeaderPadCount());
329
330 return ret;
331 }
332
333 private CpioArchiveEntry readOldAsciiEntry() throws IOException {
334 CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_ASCII);
335
336 ret.setDevice(readAsciiLong(6, 8));
337 ret.setInode(readAsciiLong(6, 8));
338 final long mode = readAsciiLong(6, 8);
339 if (mode != 0) {
340 ret.setMode(mode);
341 }
342 ret.setUID(readAsciiLong(6, 8));
343 ret.setGID(readAsciiLong(6, 8));
344 ret.setNumberOfLinks(readAsciiLong(6, 8));
345 ret.setRemoteDevice(readAsciiLong(6, 8));
346 ret.setTime(readAsciiLong(11, 8));
347 long namesize = readAsciiLong(6, 8);
348 ret.setSize(readAsciiLong(11, 8));
349 final String name = readCString((int) namesize);
350 ret.setName(name);
351 if (mode == 0 && !name.equals(CPIO_TRAILER)){
352 throw new IOException("Mode 0 only allowed in the trailer. Found entry: "+ name + " Occured at byte: " + getBytesRead());
353 }
354
355 return ret;
356 }
357
358 private CpioArchiveEntry readOldBinaryEntry(final boolean swapHalfWord)
359 throws IOException {
360 CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_BINARY);
361
362 ret.setDevice(readBinaryLong(2, swapHalfWord));
363 ret.setInode(readBinaryLong(2, swapHalfWord));
364 final long mode = readBinaryLong(2, swapHalfWord);
365 if (mode != 0){
366 ret.setMode(mode);
367 }
368 ret.setUID(readBinaryLong(2, swapHalfWord));
369 ret.setGID(readBinaryLong(2, swapHalfWord));
370 ret.setNumberOfLinks(readBinaryLong(2, swapHalfWord));
371 ret.setRemoteDevice(readBinaryLong(2, swapHalfWord));
372 ret.setTime(readBinaryLong(4, swapHalfWord));
373 long namesize = readBinaryLong(2, swapHalfWord);
374 ret.setSize(readBinaryLong(4, swapHalfWord));
375 final String name = readCString((int) namesize);
376 ret.setName(name);
377 if (mode == 0 && !name.equals(CPIO_TRAILER)){
378 throw new IOException("Mode 0 only allowed in the trailer. Found entry: "+name + "Occured at byte: " + getBytesRead());
379 }
380 skip(ret.getHeaderPadCount());
381
382 return ret;
383 }
384
385 private String readCString(final int length) throws IOException {
386 byte tmpBuffer[] = new byte[length];
387 readFully(tmpBuffer, 0, tmpBuffer.length);
388 return new String(tmpBuffer, 0, tmpBuffer.length - 1);
389 }
390
391 /**
392 * Skips specified number of bytes in the current CPIO entry.
393 *
394 * @param n
395 * the number of bytes to skip
396 * @return the actual number of bytes skipped
397 * @throws IOException
398 * if an I/O error has occurred
399 * @throws IllegalArgumentException
400 * if n < 0
401 */
402 @Override
403 public long skip(final long n) throws IOException {
404 if (n < 0) {
405 throw new IllegalArgumentException("negative skip length");
406 }
407 ensureOpen();
408 int max = (int) Math.min(n, Integer.MAX_VALUE);
409 int total = 0;
410
411 while (total < max) {
412 int len = max - total;
413 if (len > this.tmpbuf.length) {
414 len = this.tmpbuf.length;
415 }
416 len = read(this.tmpbuf, 0, len);
417 if (len == -1) {
418 this.entryEOF = true;
419 break;
420 }
421 total += len;
422 }
423 return total;
424 }
425
426 /** {@inheritDoc} */
427 @Override
428 public ArchiveEntry getNextEntry() throws IOException {
429 return getNextCPIOEntry();
430 }
431
432 /**
433 * Checks if the signature matches one of the following magic values:
434 *
435 * Strings:
436 *
437 * "070701" - MAGIC_NEW
438 * "070702" - MAGIC_NEW_CRC
439 * "070707" - MAGIC_OLD_ASCII
440 *
441 * Octal Binary value:
442 *
443 * 070707 - MAGIC_OLD_BINARY (held as a short) = 0x71C7 or 0xC771
444 */
445 public static boolean matches(byte[] signature, int length) {
446 if (length < 6) {
447 return false;
448 }
449
450 // Check binary values
451 if (signature[0] == 0x71 && (signature[1] & 0xFF) == 0xc7) {
452 return true;
453 }
454 if (signature[1] == 0x71 && (signature[0] & 0xFF) == 0xc7) {
455 return true;
456 }
457
458 // Check Ascii (String) values
459 // 3037 3037 30nn
460 if (signature[0] != 0x30) {
461 return false;
462 }
463 if (signature[1] != 0x37) {
464 return false;
465 }
466 if (signature[2] != 0x30) {
467 return false;
468 }
469 if (signature[3] != 0x37) {
470 return false;
471 }
472 if (signature[4] != 0x30) {
473 return false;
474 }
475 // Check last byte
476 if (signature[5] == 0x31) {
477 return true;
478 }
479 if (signature[5] == 0x32) {
480 return true;
481 }
482 if (signature[5] == 0x37) {
483 return true;
484 }
485
486 return false;
487 }
488 }