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); // TODO default charset? 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 }