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) { 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: " + getCount()); 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 if (this.crc != this.entry.getChksum()) { 242 throw new IOException("CRC Error. Occured at byte: " + getCount()); 243 } 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: " + getCount()); 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: " + getCount()); 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: " + getCount()); 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 public ArchiveEntry getNextEntry() throws IOException { 423 return getNextCPIOEntry(); 424 } 425 426 /** 427 * Checks if the signature matches one of the following magic values: 428 * 429 * Strings: 430 * 431 * "070701" - MAGIC_NEW 432 * "070702" - MAGIC_NEW_CRC 433 * "070707" - MAGIC_OLD_ASCII 434 * 435 * Octal Binary value: 436 * 437 * 070707 - MAGIC_OLD_BINARY (held as a short) = 0x71C7 or 0xC771 438 */ 439 public static boolean matches(byte[] signature, int length) { 440 if (length < 6) { 441 return false; 442 } 443 444 // Check binary values 445 if (signature[0] == 0x71 && (signature[1] & 0xFF) == 0xc7) { 446 return true; 447 } 448 if (signature[1] == 0x71 && (signature[0] & 0xFF) == 0xc7) { 449 return true; 450 } 451 452 // Check Ascii (String) values 453 // 3037 3037 30nn 454 if (signature[0] != 0x30) { 455 return false; 456 } 457 if (signature[1] != 0x37) { 458 return false; 459 } 460 if (signature[2] != 0x30) { 461 return false; 462 } 463 if (signature[3] != 0x37) { 464 return false; 465 } 466 if (signature[4] != 0x30) { 467 return false; 468 } 469 // Check last byte 470 if (signature[5] == 0x31) { 471 return true; 472 } 473 if (signature[5] == 0x32) { 474 return true; 475 } 476 if (signature[5] == 0x37) { 477 return true; 478 } 479 480 return false; 481 } 482 }