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 public int available() throws IOException {
103 ensureOpen();
104 if (this.entryEOF) {
105 return 0;
106 }
107 return 1;
108 }
109
110 /**
111 * Closes the CPIO input stream.
112 *
113 * @throws IOException
114 * if an I/O error has occurred
115 */
116 public void close() throws IOException {
117 if (!this.closed) {
118 in.close();
119 this.closed = true;
120 }
121 }
122
123 /**
124 * Closes the current CPIO entry and positions the stream for reading the
125 * next entry.
126 *
127 * @throws IOException
128 * if an I/O error has occurred or if a CPIO file error has
129 * occurred
130 */
131 private void closeEntry() throws IOException {
132 ensureOpen();
133 while (read(this.tmpbuf, 0, this.tmpbuf.length) != -1) { // NOPMD
134 // do nothing
135 }
136
137 this.entryEOF = true;
138 }
139
140 /**
141 * Check to make sure that this stream has not been closed
142 *
143 * @throws IOException
144 * if the stream is already closed
145 */
146 private void ensureOpen() throws IOException {
147 if (this.closed) {
148 throw new IOException("Stream closed");
149 }
150 }
151
152 /**
153 * Reads the next CPIO file entry and positions stream at the beginning of
154 * the entry data.
155 *
156 * @return the CPIOArchiveEntry just read
157 * @throws IOException
158 * if an I/O error has occurred or if a CPIO file error has
159 * occurred
160 */
161 public CpioArchiveEntry getNextCPIOEntry() throws IOException {
162 ensureOpen();
163 if (this.entry != null) {
164 closeEntry();
165 }
166 byte magic[] = new byte[2];
167 readFully(magic, 0, magic.length);
168 if (CpioUtil.byteArray2long(magic, false) == MAGIC_OLD_BINARY) {
169 this.entry = readOldBinaryEntry(false);
170 } else if (CpioUtil.byteArray2long(magic, true) == MAGIC_OLD_BINARY) {
171 this.entry = readOldBinaryEntry(true);
172 } else {
173 byte more_magic[] = new byte[4];
174 readFully(more_magic, 0, more_magic.length);
175 byte tmp[] = new byte[6];
176 System.arraycopy(magic, 0, tmp, 0, magic.length);
177 System.arraycopy(more_magic, 0, tmp, magic.length,
178 more_magic.length);
179 String magicString = ArchiveUtils.toAsciiString(tmp);
180 if (magicString.equals(MAGIC_NEW)) {
181 this.entry = readNewEntry(false);
182 } else if (magicString.equals(MAGIC_NEW_CRC)) {
183 this.entry = readNewEntry(true);
184 } else if (magicString.equals(MAGIC_OLD_ASCII)) {
185 this.entry = readOldAsciiEntry();
186 } else {
187 throw new IOException("Unknown magic [" + magicString + "]. Occured at byte: " + getBytesRead());
188 }
189 }
190
191 this.entryBytesRead = 0;
192 this.entryEOF = false;
193 this.crc = 0;
194
195 if (this.entry.getName().equals(CPIO_TRAILER)) {
196 this.entryEOF = true;
197 return null;
198 }
199 return this.entry;
200 }
201
202 private void skip(int bytes) throws IOException{
203 final byte[] buff = new byte[4]; // Cannot be more than 3 bytes
204 if (bytes > 0) {
205 readFully(buff, 0, bytes);
206 }
207 }
208
209 /**
210 * Reads from the current CPIO entry into an array of bytes. Blocks until
211 * some input is available.
212 *
213 * @param b
214 * the buffer into which the data is read
215 * @param off
216 * the start offset of the data
217 * @param len
218 * the maximum number of bytes read
219 * @return the actual number of bytes read, or -1 if the end of the entry is
220 * reached
221 * @throws IOException
222 * if an I/O error has occurred or if a CPIO file error has
223 * occurred
224 */
225 public int read(final byte[] b, final int off, final int len)
226 throws IOException {
227 ensureOpen();
228 if (off < 0 || len < 0 || off > b.length - len) {
229 throw new IndexOutOfBoundsException();
230 } else if (len == 0) {
231 return 0;
232 }
233
234 if (this.entry == null || this.entryEOF) {
235 return -1;
236 }
237 if (this.entryBytesRead == this.entry.getSize()) {
238 skip(entry.getDataPadCount());
239 this.entryEOF = true;
240 if (this.entry.getFormat() == FORMAT_NEW_CRC
241 && this.crc != this.entry.getChksum()) {
242 throw new IOException("CRC Error. Occured at byte: "
243 + getBytesRead());
244 }
245 return -1; // EOF for this entry
246 }
247 int tmplength = (int) Math.min(len, this.entry.getSize()
248 - this.entryBytesRead);
249 if (tmplength < 0) {
250 return -1;
251 }
252
253 int tmpread = readFully(b, off, tmplength);
254 if (this.entry.getFormat() == FORMAT_NEW_CRC) {
255 for (int pos = 0; pos < tmpread; pos++) {
256 this.crc += b[pos] & 0xFF;
257 }
258 }
259 this.entryBytesRead += tmpread;
260
261 return tmpread;
262 }
263
264 private final int readFully(final byte[] b, final int off, final int len)
265 throws IOException {
266 if (len < 0) {
267 throw new IndexOutOfBoundsException();
268 }
269 int n = 0;
270 while (n < len) {
271 int count = this.in.read(b, off + n, len - n);
272 count(count);
273 if (count < 0) {
274 throw new EOFException();
275 }
276 n += count;
277 }
278 return n;
279 }
280
281 private long readBinaryLong(final int length, final boolean swapHalfWord)
282 throws IOException {
283 byte tmp[] = new byte[length];
284 readFully(tmp, 0, tmp.length);
285 return CpioUtil.byteArray2long(tmp, swapHalfWord);
286 }
287
288 private long readAsciiLong(final int length, final int radix)
289 throws IOException {
290 byte tmpBuffer[] = new byte[length];
291 readFully(tmpBuffer, 0, tmpBuffer.length);
292 return Long.parseLong(ArchiveUtils.toAsciiString(tmpBuffer), radix);
293 }
294
295 private CpioArchiveEntry readNewEntry(final boolean hasCrc)
296 throws IOException {
297 CpioArchiveEntry ret;
298 if (hasCrc) {
299 ret = new CpioArchiveEntry(FORMAT_NEW_CRC);
300 } else {
301 ret = new CpioArchiveEntry(FORMAT_NEW);
302 }
303
304 ret.setInode(readAsciiLong(8, 16));
305 long mode = readAsciiLong(8, 16);
306 if (mode != 0){ // mode is initialised to 0
307 ret.setMode(mode);
308 }
309 ret.setUID(readAsciiLong(8, 16));
310 ret.setGID(readAsciiLong(8, 16));
311 ret.setNumberOfLinks(readAsciiLong(8, 16));
312 ret.setTime(readAsciiLong(8, 16));
313 ret.setSize(readAsciiLong(8, 16));
314 ret.setDeviceMaj(readAsciiLong(8, 16));
315 ret.setDeviceMin(readAsciiLong(8, 16));
316 ret.setRemoteDeviceMaj(readAsciiLong(8, 16));
317 ret.setRemoteDeviceMin(readAsciiLong(8, 16));
318 long namesize = readAsciiLong(8, 16);
319 ret.setChksum(readAsciiLong(8, 16));
320 String name = readCString((int) namesize);
321 ret.setName(name);
322 if (mode == 0 && !name.equals(CPIO_TRAILER)){
323 throw new IOException("Mode 0 only allowed in the trailer. Found entry name: "+name + " Occured at byte: " + getBytesRead());
324 }
325 skip(ret.getHeaderPadCount());
326
327 return ret;
328 }
329
330 private CpioArchiveEntry readOldAsciiEntry() throws IOException {
331 CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_ASCII);
332
333 ret.setDevice(readAsciiLong(6, 8));
334 ret.setInode(readAsciiLong(6, 8));
335 final long mode = readAsciiLong(6, 8);
336 if (mode != 0) {
337 ret.setMode(mode);
338 }
339 ret.setUID(readAsciiLong(6, 8));
340 ret.setGID(readAsciiLong(6, 8));
341 ret.setNumberOfLinks(readAsciiLong(6, 8));
342 ret.setRemoteDevice(readAsciiLong(6, 8));
343 ret.setTime(readAsciiLong(11, 8));
344 long namesize = readAsciiLong(6, 8);
345 ret.setSize(readAsciiLong(11, 8));
346 final String name = readCString((int) namesize);
347 ret.setName(name);
348 if (mode == 0 && !name.equals(CPIO_TRAILER)){
349 throw new IOException("Mode 0 only allowed in the trailer. Found entry: "+ name + " Occured at byte: " + getBytesRead());
350 }
351
352 return ret;
353 }
354
355 private CpioArchiveEntry readOldBinaryEntry(final boolean swapHalfWord)
356 throws IOException {
357 CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_BINARY);
358
359 ret.setDevice(readBinaryLong(2, swapHalfWord));
360 ret.setInode(readBinaryLong(2, swapHalfWord));
361 final long mode = readBinaryLong(2, swapHalfWord);
362 if (mode != 0){
363 ret.setMode(mode);
364 }
365 ret.setUID(readBinaryLong(2, swapHalfWord));
366 ret.setGID(readBinaryLong(2, swapHalfWord));
367 ret.setNumberOfLinks(readBinaryLong(2, swapHalfWord));
368 ret.setRemoteDevice(readBinaryLong(2, swapHalfWord));
369 ret.setTime(readBinaryLong(4, swapHalfWord));
370 long namesize = readBinaryLong(2, swapHalfWord);
371 ret.setSize(readBinaryLong(4, swapHalfWord));
372 final String name = readCString((int) namesize);
373 ret.setName(name);
374 if (mode == 0 && !name.equals(CPIO_TRAILER)){
375 throw new IOException("Mode 0 only allowed in the trailer. Found entry: "+name + "Occured at byte: " + getBytesRead());
376 }
377 skip(ret.getHeaderPadCount());
378
379 return ret;
380 }
381
382 private String readCString(final int length) throws IOException {
383 byte tmpBuffer[] = new byte[length];
384 readFully(tmpBuffer, 0, tmpBuffer.length);
385 return new String(tmpBuffer, 0, tmpBuffer.length - 1);
386 }
387
388 /**
389 * Skips specified number of bytes in the current CPIO entry.
390 *
391 * @param n
392 * the number of bytes to skip
393 * @return the actual number of bytes skipped
394 * @throws IOException
395 * if an I/O error has occurred
396 * @throws IllegalArgumentException
397 * if n < 0
398 */
399 public long skip(final long n) throws IOException {
400 if (n < 0) {
401 throw new IllegalArgumentException("negative skip length");
402 }
403 ensureOpen();
404 int max = (int) Math.min(n, Integer.MAX_VALUE);
405 int total = 0;
406
407 while (total < max) {
408 int len = max - total;
409 if (len > this.tmpbuf.length) {
410 len = this.tmpbuf.length;
411 }
412 len = read(this.tmpbuf, 0, len);
413 if (len == -1) {
414 this.entryEOF = true;
415 break;
416 }
417 total += len;
418 }
419 return total;
420 }
421
422 /** {@inheritDoc} */
423 public ArchiveEntry getNextEntry() throws IOException {
424 return getNextCPIOEntry();
425 }
426
427 /**
428 * Checks if the signature matches one of the following magic values:
429 *
430 * Strings:
431 *
432 * "070701" - MAGIC_NEW
433 * "070702" - MAGIC_NEW_CRC
434 * "070707" - MAGIC_OLD_ASCII
435 *
436 * Octal Binary value:
437 *
438 * 070707 - MAGIC_OLD_BINARY (held as a short) = 0x71C7 or 0xC771
439 */
440 public static boolean matches(byte[] signature, int length) {
441 if (length < 6) {
442 return false;
443 }
444
445 // Check binary values
446 if (signature[0] == 0x71 && (signature[1] & 0xFF) == 0xc7) {
447 return true;
448 }
449 if (signature[1] == 0x71 && (signature[0] & 0xFF) == 0xc7) {
450 return true;
451 }
452
453 // Check Ascii (String) values
454 // 3037 3037 30nn
455 if (signature[0] != 0x30) {
456 return false;
457 }
458 if (signature[1] != 0x37) {
459 return false;
460 }
461 if (signature[2] != 0x30) {
462 return false;
463 }
464 if (signature[3] != 0x37) {
465 return false;
466 }
467 if (signature[4] != 0x30) {
468 return false;
469 }
470 // Check last byte
471 if (signature[5] == 0x31) {
472 return true;
473 }
474 if (signature[5] == 0x32) {
475 return true;
476 }
477 if (signature[5] == 0x37) {
478 return true;
479 }
480
481 return false;
482 }
483 }