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