applied patch from Christian Grobmeier
https://issues.apache.org/jira/browse/SANDBOX-241
added the cpio implementation,
added a cpio archive and improved the testcases,
some nitpicking
git-svn-id: https://svn.apache.org/repos/asf/commons/sandbox/compress/trunk@733684 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/NOTICE.txt b/NOTICE.txt
index d488740..72d2e28 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -7,3 +7,4 @@
Original BZip2 classes contributed by Keiron Liddle <keiron@aftexsw.com>, Aftex Software to the Apache Ant project
Original Tar classes from contributors of the Apache Ant project
Original Zip classes from contributors of the Apache Ant project
+Original CPIO classes contributed by Markus Kuss and the jRPM project (jrpm.sourceforge.net)
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java b/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java
index f0ad1a9..47fd98b 100644
--- a/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java
+++ b/src/main/java/org/apache/commons/compress/archivers/ArchiveStreamFactory.java
@@ -24,6 +24,8 @@
import org.apache.commons.compress.archivers.ar.ArArchiveInputStream;
import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream;
+import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream;
+import org.apache.commons.compress.archivers.cpio.CpioArchiveOutputStream;
import org.apache.commons.compress.archivers.jar.JarArchiveInputStream;
import org.apache.commons.compress.archivers.jar.JarArchiveOutputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
@@ -53,6 +55,8 @@
return new TarArchiveInputStream(in);
} else if("jar".equalsIgnoreCase(archiverName)) {
return new JarArchiveInputStream(in);
+ } else if("cpio".equalsIgnoreCase(archiverName)) {
+ return new CpioArchiveInputStream(in);
}
return null;
}
@@ -66,6 +70,8 @@
return new TarArchiveOutputStream(out);
} else if("jar".equalsIgnoreCase(archiverName)) {
return new JarArchiveOutputStream(out);
+ } else if("cpio".equalsIgnoreCase(archiverName)) {
+ return new CpioArchiveOutputStream(out);
}
return null;
}
@@ -86,6 +92,8 @@
return new TarArchiveInputStream(input);
} else if(ArArchiveInputStream.matches(signature)) {
return new ArArchiveInputStream(input);
+ } else if(CpioArchiveInputStream.matches(signature)) {
+ return new CpioArchiveInputStream(input);
}
return null;
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveEntry.java
new file mode 100644
index 0000000..e76e245
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveEntry.java
@@ -0,0 +1,622 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.compress.archivers.cpio;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+
+/**
+ * A cpio archive consists of a sequence of files. There are several types of
+ * headers defided in two categories of new and old format. The headers are
+ * recognized by magic numbers: "070701" ascii for "new" portable format
+ * "070702" ascii for "new" portable format with CRC format "070707" ascii for
+ * old ascii "070707" short for old binary CPIO 2.5 knows also about tar, but it
+ * is not recognized here.
+ *
+ * OLD FORMAT: Each file has a 76(ascii)/26(binary) byte header, a variable
+ * length, NUL terminated filename, and variable length file data. A header for
+ * a filename "TRAILER!!!" indicates the end of the archive.
+ *
+ * All the fields in the header are ISO 646 (approximately ASCII) strings of
+ * octal numbers, left padded, not NUL terminated.
+ *
+ * Field Name Length in Bytes Notes ASCII / BINARY c_magic 6 / 2 c_dev 6 / 2
+ * Device that contains a directory entry for this file c_ino 6 / 2 I-node
+ * number that identifies the input file to the file system c_mode 6 / 2 Mode of
+ * the input file c_uid 6 / 2 User ID of the owner of the input file c_gid 6 / 2
+ * Group ID of the owner of the input file c_nlink 6 / 2 Number of links that
+ * are connected to the input file c_rdev 6 / 2 ID of the remote device from
+ * which the input file is taken only valid for chr and blk special files
+ * c_mtime 11 / 4 Time when data was last modified. For remote files, this field
+ * contains the time at the server c_namesize 6 / 2 Length of the path name,
+ * including the terminating null byte c_filesize 11 / 4 Length of the file in
+ * bytes. This is the length of the data section that follows the header
+ * structure. Must be 0 for FIFOs and directories
+ *
+ * Special files, directories, and the trailer are recorded with the h_filesize
+ * field equal to 0.
+ *
+ * NEW FORMAT: Each file has a 110 byte header, a variable length, NUL
+ * terminated filename, and variable length file data. A header for a filename
+ * "TRAILER!!!" indicates the end of the archive. All the fields in the header
+ * are ISO 646 (approximately ASCII) strings of hexadecimal numbers, left
+ * padded, not NUL terminated.
+ *
+ * Field Name Length in Bytes Notes c_magic 6 c_ino 8 c_mode 8 c_uid 8 c_gid 8
+ * c_nlink 8 c_mtime 8 c_filesize 8 must be 0 for FIFOs and directories c_maj 8
+ * c_min 8 c_rmaj 8 only valid for chr and blk special files c_rmin 8 only valid
+ * for chr and blk special files c_namesize 8 count includes terminating NUL in
+ * pathname c_chksum 8 0 for "new" portable format; for CRC format the sum of
+ * all the bytes in the file
+ *
+ * based on code from the jRPM project (jrpm.sourceforge.net)
+ */
+public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
+
+ private long chksum = 0;
+
+ private short fileFormat = -1;
+
+ private long filesize = 0;
+
+ private long gid = 0;
+
+ private long headerSize = -1;
+
+ private long inode = 0;
+
+ private long maj = 0;
+
+ private long min = 0;
+
+ private long mode = -1;
+
+ private long mtime = -1;
+
+ private String name;
+
+ private long nlink = 0;
+
+ private long rmaj = 0;
+
+ private long rmin = 0;
+
+ private long uid = 0;
+
+ /**
+ * Ceates a CPIOArchiveEntry without a cpio format.
+ */
+ public CpioArchiveEntry() {
+ // do nothing
+ }
+
+ /**
+ * Ceates a CPIOArchiveEntry with a specified format.
+ *
+ * @param format
+ * The cpio format for this entry.
+ */
+ public CpioArchiveEntry(final short format) {
+ setFormat(format);
+ }
+
+ /**
+ * Ceates a CPIOArchiveEntry with a specified name. The format of this entry will
+ * be the new format.
+ *
+ * @param name
+ * The name of this entry.
+ */
+ public CpioArchiveEntry(final String name) {
+ this(FORMAT_NEW);
+ this.name = name;
+ }
+
+ /**
+ * Ceates a CPIOArchiveEntry with a specified name. The format of this entry will
+ * be the new format.
+ *
+ * @param name
+ * The name of this entry.
+ * @param size
+ * The size of this entry
+ */
+ public CpioArchiveEntry(final String name, final long size) {
+ this(FORMAT_NEW);
+ this.name = name;
+ this.setSize(size);
+ }
+
+ /**
+ * Check if the method is allowed for the defined format.
+ */
+ private void checkNewFormat() {
+ if ((this.fileFormat & FORMAT_NEW_MASK) == 0) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Check if the method is allowed for the defined format.
+ */
+ private void checkOldFormat() {
+ if ((this.fileFormat & FORMAT_OLD_MASK) == 0) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Get the checksum.
+ *
+ * @return Returns the checksum.
+ */
+ public long getChksum() {
+ checkNewFormat();
+ return this.chksum;
+ }
+
+ /**
+ * Get the device id.
+ *
+ * @return Returns the device id.
+ * @throws UnsupportedOperationException
+ * if this method is called for a CPIOArchiveEntry with a new format.
+ */
+ public long getDevice() {
+ checkOldFormat();
+ return this.min;
+ }
+
+ /**
+ * Get the major device id.
+ *
+ * @return Returns the major device id.
+ * @throws UnsupportedOperationException
+ * if this method is called for a CPIOArchiveEntry with an old format.
+ */
+ public long getDeviceMaj() {
+ checkNewFormat();
+ return this.maj;
+ }
+
+ /**
+ * Get the minor device id
+ *
+ * @return Returns the minor device id.
+ */
+ public long getDeviceMin() {
+ checkNewFormat();
+ return this.min;
+ }
+
+ /**
+ * Get the filesize.
+ *
+ * @return Returns the filesize.
+ */
+ /* (non-Javadoc)
+ * @see org.apache.commons.compress.archivers.ArchiveEntry#getSize()
+ */
+ public long getSize() {
+ return this.filesize;
+ }
+
+ /**
+ * Get the format for this entry.
+ *
+ * @return Returns the format.
+ */
+ public short getFormat() {
+ return this.fileFormat;
+ }
+
+ /**
+ * Get the group id.
+ *
+ * @return Returns the group id.
+ */
+ public long getGID() {
+ return this.gid;
+ }
+
+ /**
+ * Get the size of this entry on the stream
+ *
+ * @return Returns the size.
+ */
+ public long getHeaderSize() {
+ return this.headerSize;
+ }
+
+ /**
+ * Set the inode.
+ *
+ * @return Returns the inode.
+ */
+ public long getInode() {
+ return this.inode;
+ }
+
+ /**
+ * Get the mode of this entry (e.g. directory, regular file).
+ *
+ * @return Returns the mode.
+ */
+ public long getMode() {
+ return this.mode;
+ }
+
+ /**
+ * Get the name.
+ *
+ * @return Returns the name.
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Get the number of links.
+ *
+ * @return Returns the number of links.
+ */
+ public long getNumberOfLinks() {
+ return this.nlink;
+ }
+
+ /**
+ * Get the remote device id.
+ *
+ * @return Returns the remote device id.
+ * @throws UnsupportedOperationException
+ * if this method is called for a CPIOArchiveEntry with a new format.
+ */
+ public long getRemoteDevice() {
+ checkOldFormat();
+ return this.rmin;
+ }
+
+ /**
+ * Get the remote major device id.
+ *
+ * @return Returns the remote major device id.
+ * @throws UnsupportedOperationException
+ * if this method is called for a CPIOArchiveEntry with an old format.
+ */
+ public long getRemoteDeviceMaj() {
+ checkNewFormat();
+ return this.rmaj;
+ }
+
+ /**
+ * Get the remote minor device id.
+ *
+ * @return Returns the remote minor device id.
+ * @throws UnsupportedOperationException
+ * if this method is called for a CPIOArchiveEntry with an old format.
+ */
+ public long getRemoteDeviceMin() {
+ checkNewFormat();
+ return this.rmin;
+ }
+
+ /**
+ * Get the time in seconds.
+ *
+ * @return Returns the time.
+ */
+ public long getTime() {
+ return this.mtime;
+ }
+
+ /**
+ * Get the user id.
+ *
+ * @return Returns the user id.
+ */
+ public long getUID() {
+ return this.uid;
+ }
+
+ /**
+ * Check if this entry represents a block device.
+ *
+ * @return TRUE if this entry is a block device.
+ */
+ public boolean isBlockDevice() {
+ return (this.mode & S_IFMT) == C_ISBLK;
+ }
+
+ /**
+ * Check if this entry represents a character device.
+ *
+ * @return TRUE if this entry is a character device.
+ */
+ public boolean isCharacterDevice() {
+ return (this.mode & S_IFMT) == C_ISCHR;
+ }
+
+ /**
+ * Check if this entry represents a directory.
+ *
+ * @return TRUE if this entry is a directory.
+ */
+ public boolean isDirectory() {
+ return (this.mode & S_IFMT) == C_ISDIR;
+ }
+
+ /**
+ * Check if this entry represents a network device.
+ *
+ * @return TRUE if this entry is a network device.
+ */
+ public boolean isNetwork() {
+ return (this.mode & S_IFMT) == C_ISNWK;
+ }
+
+ /**
+ * Check if this entry represents a pipe.
+ *
+ * @return TRUE if this entry is a pipe.
+ */
+ public boolean isPipe() {
+ return (this.mode & S_IFMT) == C_ISFIFO;
+ }
+
+ /**
+ * Check if this entry represents a regular file.
+ *
+ * @return TRUE if this entry is a regular file.
+ */
+ public boolean isRegularFile() {
+ return (this.mode & S_IFMT) == C_ISREG;
+ }
+
+ /**
+ * Check if this entry represents a socket.
+ *
+ * @return TRUE if this entry is a socket.
+ */
+ public boolean isSocket() {
+ return (this.mode & S_IFMT) == C_ISSOCK;
+ }
+
+ /**
+ * Check if this entry represents a symbolic link.
+ *
+ * @return TRUE if this entry is a symbolic link.
+ */
+ public boolean isSymbolicLink() {
+ return (this.mode & S_IFMT) == C_ISLNK;
+ }
+
+ /**
+ * Set the checksum. The checksum is calculated by adding all bytes of a
+ * file to transfer (crc += buf[pos] & 0xFF).
+ *
+ * @param chksum
+ * The checksum to set.
+ */
+ public void setChksum(final long chksum) {
+ checkNewFormat();
+ this.chksum = chksum;
+ }
+
+ /**
+ * Set the device id.
+ *
+ * @param device
+ * The device id to set.
+ * @throws UnsupportedOperationException
+ * if this method is called for a CPIOArchiveEntry with a new format.
+ */
+ public void setDevice(final long device) {
+ checkOldFormat();
+ this.min = device;
+ }
+
+ /**
+ * Set major device id.
+ *
+ * @param maj
+ * The major device id to set.
+ */
+ public void setDeviceMaj(final long maj) {
+ checkNewFormat();
+ this.maj = maj;
+ }
+
+ /**
+ * Set the minor device id
+ *
+ * @param min
+ * The minor device id to set.
+ */
+ public void setDeviceMin(final long min) {
+ checkNewFormat();
+ this.min = min;
+ }
+
+ /**
+ * Set the filesize.
+ *
+ * @param size
+ * The filesize to set.
+ */
+ public void setSize(final long size) {
+ if (size < 0 || size > 0xFFFFFFFFL) {
+ throw new IllegalArgumentException("invalid entry size <" + size
+ + ">");
+ }
+ this.filesize = size;
+ }
+
+ /**
+ * Set the format for this entry. Possible values are:
+ * CPIOConstants.FORMAT_NEW, CPIOConstants.FORMAT_NEW_CRC,
+ * CPIOConstants.FORMAT_OLD_BINARY, CPIOConstants.FORMAT_OLD_ASCII
+ *
+ * @param format
+ * The format to set.
+ */
+ void setFormat(final short format) {
+ switch (format) {
+ case FORMAT_NEW:
+ this.fileFormat = FORMAT_NEW;
+ this.headerSize = 110;
+ break;
+ case FORMAT_NEW_CRC:
+ this.fileFormat = FORMAT_NEW_CRC;
+ this.headerSize = 110;
+ break;
+ case FORMAT_OLD_ASCII:
+ this.fileFormat = FORMAT_OLD_ASCII;
+ this.headerSize = 76;
+ break;
+ case FORMAT_OLD_BINARY:
+ this.fileFormat = FORMAT_OLD_BINARY;
+ this.headerSize = 26;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown header type");
+ }
+ }
+
+ /**
+ * Set the group id.
+ *
+ * @param gid
+ * The group id to set.
+ */
+ public void setGID(final long gid) {
+ this.gid = gid;
+ }
+
+ /**
+ * Set the inode.
+ *
+ * @param inode
+ * The inode to set.
+ */
+ public void setInode(final long inode) {
+ this.inode = inode;
+ }
+
+ /**
+ * Set the mode of this entry (e.g. directory, regular file).
+ *
+ * @param mode
+ * The mode to set.
+ */
+ public void setMode(final long mode) {
+ switch ((int) (mode & S_IFMT)) {
+ case C_ISDIR:
+ case C_ISLNK:
+ case C_ISREG:
+ case C_ISFIFO:
+ case C_ISCHR:
+ case C_ISBLK:
+ case C_ISSOCK:
+ case C_ISNWK:
+ break;
+ default:
+ new IllegalArgumentException("Unknown mode");
+ }
+
+ this.mode = mode;
+ }
+
+ /**
+ * Set the name.
+ *
+ * @param name
+ * The name to set.
+ */
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * Set the number of links.
+ *
+ * @param nlink
+ * The number of links to set.
+ */
+ public void setNumberOfLinks(final long nlink) {
+ this.nlink = nlink;
+ }
+
+ /**
+ * Set the remote device id.
+ *
+ * @param device
+ * The remote device id to set.
+ * @throws UnsupportedOperationException
+ * if this method is called for a CPIOArchiveEntry with a new format.
+ */
+ public void setRemoteDevice(final long device) {
+ checkOldFormat();
+ this.rmin = device;
+ }
+
+ /**
+ * Set the remote major device id.
+ *
+ * @param rmaj
+ * The remote major device id to set.
+ * @throws UnsupportedOperationException
+ * if this method is called for a CPIOArchiveEntry with an old format.
+ */
+ public void setRemoteDeviceMaj(final long rmaj) {
+ checkNewFormat();
+ this.rmaj = rmaj;
+ }
+
+ /**
+ * Set the remote minor device id.
+ *
+ * @param rmin
+ * The remote minor device id to set.
+ * @throws UnsupportedOperationException
+ * if this method is called for a CPIOArchiveEntry with an old format.
+ */
+ public void setRemoteDeviceMin(final long rmin) {
+ checkNewFormat();
+ this.rmin = rmin;
+ }
+
+ /**
+ * Set the time in seconds.
+ *
+ * @param time
+ * The time to set.
+ */
+ public void setTime(final long time) {
+ this.mtime = time;
+ }
+
+ /**
+ * Set the user id.
+ *
+ * @param uid
+ * The user id to set.
+ */
+ public void setUID(final long uid) {
+ this.uid = uid;
+ }
+}
diff --git a/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStream.java b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStream.java
new file mode 100644
index 0000000..bcf38d4
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveInputStream.java
@@ -0,0 +1,466 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.compress.archivers.cpio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveInputStream;
+
+/**
+ * CPIOArchiveInputStream is a stream for reading cpio streams. All formats of cpio are
+ * supported (old ascii, old binary, new portable format and the new portable
+ * format with crc).
+ * <p/>
+ * <p/>
+ * The stream can be read by extracting a cpio entry (containing all
+ * informations about a entry) and afterwards reading from the stream the file
+ * specified by the entry.
+ * <p/>
+ * <code><pre>
+ * CPIOArchiveInputStream cpioIn = new CPIOArchiveInputStream(new BufferedInputStream(
+ * new FileInputStream(new File("test.cpio"))));
+ * CPIOArchiveEntry cpioEntry;
+ * <p/>
+ * while ((cpioEntry = cpioIn.getNextEntry()) != null) {
+ * System.out.println(cpioEntry.getName());
+ * int tmp;
+ * StringBuffer buf = new StringBuffer();
+ * while ((tmp = cpIn.read()) != -1) {
+ * buf.append((char) tmp);
+ * }
+ * System.out.println(buf.toString());
+ * }
+ * cpioIn.close();
+ * </pre></code>
+ * <p/>
+ * Note: This implementation should be compatible to cpio 2.5
+ *
+ * based on code from the jRPM project (jrpm.sourceforge.net)
+ */
+
+public class CpioArchiveInputStream extends ArchiveInputStream implements CpioConstants {
+
+ private boolean closed = false;
+
+ private CpioArchiveEntry entry;
+
+ private long entryBytesRead = 0;
+
+ private boolean entryEOF = false;
+
+ private byte[] singleByteBuf = new byte[1];
+
+ private byte tmpbuf[] = new byte[4096];
+
+ private long crc = 0;
+
+ private InputStream in = null;
+
+ /**
+ * Construct the cpio input stream
+ *
+ * @param in The cpio stream
+ */
+ public CpioArchiveInputStream(final InputStream in) {
+ this.in = in;
+ }
+
+ /**
+ * Returns 0 after EOF has reached for the current entry data, otherwise
+ * always return 1.
+ * <p/>
+ * Programs should not count on this method to return the actual number of
+ * bytes that could be read without blocking.
+ *
+ * @return 1 before EOF and 0 after EOF has reached for current entry.
+ * @throws IOException if an I/O error has occurred or if a CPIO file error has
+ * occurred
+ */
+ public int available() throws IOException {
+ ensureOpen();
+ if (this.entryEOF) {
+ return 0;
+ }
+ return 1;
+ }
+
+ /**
+ * Converts a byte array to a long. Halfwords can be swaped with setting
+ * swapHalfWord=true.
+ *
+ * @param number An array of bytes containing a number
+ * @param swapHalfWord Swap halfwords ([0][1][2][3]->[1][0][3][2])
+ * @return The long value
+ */
+ private long byteArray2long(final byte[] number, final boolean swapHalfWord) {
+ long ret = 0;
+ int pos = 0;
+ byte tmp_number[] = new byte[number.length];
+ System.arraycopy(number, 0, tmp_number, 0, number.length);
+
+ if (tmp_number.length % 2 != 0) {
+ throw new UnsupportedOperationException();
+ }
+
+ if (!swapHalfWord) {
+ byte tmp = 0;
+ for (pos = 0; pos < tmp_number.length; pos++) {
+ tmp = tmp_number[pos];
+ tmp_number[pos++] = tmp_number[pos];
+ tmp_number[pos] = tmp;
+ }
+ }
+
+ ret = tmp_number[0] & 0xFF;
+ for (pos = 1; pos < tmp_number.length; pos++) {
+ ret <<= 8;
+ ret |= tmp_number[pos] & 0xFF;
+ }
+ return ret;
+ }
+
+ /**
+ * Closes the CPIO input stream.
+ *
+ * @throws IOException if an I/O error has occurred
+ */
+ public void close() throws IOException {
+ if (!this.closed) {
+ super.close();
+ this.closed = true;
+ }
+ }
+
+ /**
+ * Closes the current CPIO entry and positions the stream for reading the
+ * next entry.
+ *
+ * @throws IOException if an I/O error has occurred or if a CPIO file error has
+ * occurred
+ */
+ public void closeEntry() throws IOException {
+ ensureOpen();
+ while (read(this.tmpbuf, 0, this.tmpbuf.length) != -1) {
+ // do nothing
+ }
+
+ this.entryEOF = true;
+ }
+
+ /**
+ * Check to make sure that this stream has not been closed
+ *
+ * @throws IOException if the stream is already closed
+ */
+ private void ensureOpen() throws IOException {
+ if (this.closed) {
+ throw new IOException("Stream closed");
+ }
+ }
+
+ /**
+ * Reads the next CPIO file entry and positions stream at the beginning of
+ * the entry data.
+ *
+ * @return the CPIOArchiveEntry just read
+ * @throws IOException if an I/O error has occurred or if a CPIO file error has
+ * occurred
+ */
+ public CpioArchiveEntry getNextCPIOEntry() throws IOException {
+ ensureOpen();
+ if (this.entry != null) {
+ closeEntry();
+ }
+ byte magic[] = new byte[2];
+ readFully(magic, 0, magic.length);
+ if (byteArray2long(magic, false) == MAGIC_OLD_BINARY) {
+ this.entry = readOldBinaryEntry(false);
+ } else if (byteArray2long(magic, true) == MAGIC_OLD_BINARY) {
+ this.entry = readOldBinaryEntry(true);
+ } else {
+ byte more_magic[] = new byte[4];
+ readFully(more_magic, 0, more_magic.length);
+ byte tmp[] = new byte[6];
+ System.arraycopy(magic, 0, tmp, 0, magic.length);
+ System.arraycopy(more_magic, 0, tmp, magic.length,
+ more_magic.length);
+ String magicString = new String(tmp);
+ if (magicString.equals(MAGIC_NEW)) {
+ this.entry = readNewEntry(false);
+ } else if (magicString.equals(MAGIC_NEW_CRC)) {
+ this.entry = readNewEntry(true);
+ } else if (magicString.equals(MAGIC_OLD_ASCII)) {
+ this.entry = readOldAsciiEntry();
+ } else {
+ throw new IOException("Unknown magic [" + magicString + "]");
+ }
+ }
+
+ this.entryBytesRead = 0;
+ this.entryEOF = false;
+ this.crc = 0;
+
+ if (this.entry.getName().equals("TRAILER!!!")) {
+ this.entryEOF = true;
+ return null;
+ }
+ return this.entry;
+ }
+
+ private long pad(final long count, final int border) throws IOException {
+ long skip = count % border;
+ if (skip > 0) {
+ skip = this.in.skip(border - skip);
+ }
+ return skip;
+ }
+
+ /**
+ * Reads a byte of data. This method will block until enough input is
+ * available.
+ *
+ * @return the byte read, or -1 if end of input is reached
+ * @throws IOException if an I/O error has occurred or if a CPIO file error has
+ * occurred
+ */
+ public int read() throws IOException {
+ return read(this.singleByteBuf, 0, 1) == -1 ? -1
+ : this.singleByteBuf[0] & 0xff;
+ }
+
+ /**
+ * Reads from the current CPIO entry into an array of bytes. Blocks until
+ * some input is available.
+ *
+ * @param b the buffer into which the data is read
+ * @param off the start offset of the data
+ * @param len the maximum number of bytes read
+ * @return the actual number of bytes read, or -1 if the end of the entry is
+ * reached
+ * @throws IOException if an I/O error has occurred or if a CPIO file error has
+ * occurred
+ */
+ public int read(final byte[] b, final int off, final int len)
+ throws IOException {
+ ensureOpen();
+ if (off < 0 || len < 0 || off > b.length - len) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return 0;
+ }
+
+ if (this.entry == null || this.entryEOF) {
+ return -1;
+ }
+ if (this.entryBytesRead == this.entry.getSize()) {
+ if ((this.entry.getFormat() | FORMAT_NEW_MASK) == FORMAT_NEW_MASK) {
+ pad(this.entry.getSize(), 4);
+ } else if ((this.entry.getFormat() | FORMAT_OLD_BINARY) == FORMAT_OLD_BINARY) {
+ pad(this.entry.getSize(), 2);
+ }
+ this.entryEOF = true;
+ if ((this.entry.getFormat() | FORMAT_NEW_CRC) == FORMAT_NEW_CRC) {
+ if (this.crc != this.entry.getChksum()) {
+ throw new IOException("CRC Error");
+ }
+ }
+ return -1;
+ }
+ int tmplength = (int) Math.min(len, this.entry.getSize()
+ - this.entryBytesRead);
+ if (tmplength < 0) {
+ return -1;
+ }
+
+ int tmpread = this.in.read(b, off, tmplength);
+ if ((this.entry.getFormat() | FORMAT_NEW_CRC) == FORMAT_NEW_CRC) {
+ for (int pos = 0; pos < tmpread; pos++) {
+ this.crc += b[pos] & 0xFF;
+ }
+ }
+ this.entryBytesRead += tmpread;
+
+ return tmpread;
+ }
+
+ private final int readFully(final byte[] b, final int off, final int len)
+ throws IOException {
+ if (len < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ int n = 0;
+ while (n < len) {
+ int count = this.in.read(b, off + n, len - n);
+ if (count < 0) {
+ throw new EOFException();
+ }
+ n += count;
+ }
+ return n;
+ }
+
+ private long readBinaryLong(final int length, final boolean swapHalfWord)
+ throws IOException {
+ byte tmp[] = new byte[length];
+ readFully(tmp, 0, tmp.length);
+ return byteArray2long(tmp, swapHalfWord);
+ }
+
+ private long readAsciiLong(final int length, final int radix)
+ throws IOException {
+ byte tmpBuffer[] = new byte[length];
+ readFully(tmpBuffer, 0, tmpBuffer.length);
+ return Long.parseLong(new String(tmpBuffer), radix);
+ }
+
+ private CpioArchiveEntry readNewEntry(final boolean hasCrc) throws IOException {
+ CpioArchiveEntry ret;
+ if (hasCrc) {
+ ret = new CpioArchiveEntry(FORMAT_NEW_CRC);
+ } else {
+ ret = new CpioArchiveEntry(FORMAT_NEW);
+ }
+
+ ret.setInode(readAsciiLong(8, 16));
+ ret.setMode(readAsciiLong(8, 16));
+ ret.setUID(readAsciiLong(8, 16));
+ ret.setGID(readAsciiLong(8, 16));
+ ret.setNumberOfLinks(readAsciiLong(8, 16));
+ ret.setTime(readAsciiLong(8, 16));
+ ret.setSize(readAsciiLong(8, 16));
+ ret.setDeviceMaj(readAsciiLong(8, 16));
+ ret.setDeviceMin(readAsciiLong(8, 16));
+ ret.setRemoteDeviceMaj(readAsciiLong(8, 16));
+ ret.setRemoteDeviceMin(readAsciiLong(8, 16));
+ long namesize = readAsciiLong(8, 16);
+ ret.setChksum(readAsciiLong(8, 16));
+ ret.setName(readCString((int) namesize));
+ pad(ret.getHeaderSize() + namesize, 4);
+
+ return ret;
+ }
+
+ private CpioArchiveEntry readOldAsciiEntry() throws IOException {
+ CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_ASCII);
+
+ ret.setDevice(readAsciiLong(6, 8));
+ ret.setInode(readAsciiLong(6, 8));
+ ret.setMode(readAsciiLong(6, 8));
+ ret.setUID(readAsciiLong(6, 8));
+ ret.setGID(readAsciiLong(6, 8));
+ ret.setNumberOfLinks(readAsciiLong(6, 8));
+ ret.setRemoteDevice(readAsciiLong(6, 8));
+ ret.setTime(readAsciiLong(11, 8));
+ long namesize = readAsciiLong(6, 8);
+ ret.setSize(readAsciiLong(11, 8));
+ ret.setName(readCString((int) namesize));
+
+ return ret;
+ }
+
+ private CpioArchiveEntry readOldBinaryEntry(final boolean swapHalfWord)
+ throws IOException {
+ CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_BINARY);
+
+ ret.setDevice(readBinaryLong(2, swapHalfWord));
+ ret.setInode(readBinaryLong(2, swapHalfWord));
+ ret.setMode(readBinaryLong(2, swapHalfWord));
+ ret.setUID(readBinaryLong(2, swapHalfWord));
+ ret.setGID(readBinaryLong(2, swapHalfWord));
+ ret.setNumberOfLinks(readBinaryLong(2, swapHalfWord));
+ ret.setRemoteDevice(readBinaryLong(2, swapHalfWord));
+ ret.setTime(readBinaryLong(4, swapHalfWord));
+ long namesize = readBinaryLong(2, swapHalfWord);
+ ret.setSize(readBinaryLong(4, swapHalfWord));
+ ret.setName(readCString((int) namesize));
+ pad(ret.getHeaderSize() + namesize, 2);
+
+ return ret;
+ }
+
+ private String readCString(final int length) throws IOException {
+ byte tmpBuffer[] = new byte[length];
+ readFully(tmpBuffer, 0, tmpBuffer.length);
+ return new String(tmpBuffer, 0, tmpBuffer.length - 1);
+ }
+
+ /**
+ * Skips specified number of bytes in the current CPIO entry.
+ *
+ * @param n the number of bytes to skip
+ * @return the actual number of bytes skipped
+ * @throws IOException if an I/O error has occurred
+ * @throws IllegalArgumentException if n < 0
+ */
+ public long skip(final long n) throws IOException {
+ if (n < 0) {
+ throw new IllegalArgumentException("negative skip length");
+ }
+ ensureOpen();
+ int max = (int) Math.min(n, Integer.MAX_VALUE);
+ int total = 0;
+
+ while (total < max) {
+ int len = max - total;
+ if (len > this.tmpbuf.length) {
+ len = this.tmpbuf.length;
+ }
+ len = read(this.tmpbuf, 0, len);
+ if (len == -1) {
+ this.entryEOF = true;
+ break;
+ }
+ total += len;
+ }
+ return total;
+ }
+
+ public ArchiveEntry getNextEntry() throws IOException {
+ CpioArchiveEntry entry = this.getNextCPIOEntry();
+ if(entry == null) {
+ return null;
+ }
+ return (ArchiveEntry)entry;
+ }
+
+ public static boolean matches( byte[] signature ) {
+ // 3037 3037 30
+
+ if (signature[0] != 0x30) {
+ return false;
+ }
+ if (signature[1] != 0x37) {
+ return false;
+ }
+ if (signature[2] != 0x30) {
+ return false;
+ }
+ if (signature[3] != 0x37) {
+ return false;
+ }
+ if (signature[4] != 0x30) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveOutputStream.java
new file mode 100644
index 0000000..5d22d26
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioArchiveOutputStream.java
@@ -0,0 +1,433 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.compress.archivers.cpio;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.HashMap;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveOutputStream;
+
+/**
+ * CPIOArchiveOutputStream is a stream for writting cpio streams. All formats of cpio
+ * are supported (old ascii, old binary, new portable format and the new
+ * portable format with crc).
+ * <p/>
+ * <p/>
+ * An entry can be written by creating an instance of CPIOArchiveEntry and fill it with
+ * the necessary values and put it into the cpio stream. Afterwards write the
+ * contents of the file into the cpio stream. Either close the stream by calling
+ * finish() or put a next entry into the cpio stream.
+ * <p/>
+ * <code><pre>
+ * CPIOArchiveOutputStream cpioOut = new CPIOArchiveOutputStream(new BufferedOutputStream(
+ * new FileOutputStream(new File("test.cpio"))));
+ * CPIOArchiveEntry cpioEntry = new CPIOArchiveEntry();
+ * cpioEntry.setName("testfile");
+ * String testContents = "12345";
+ * cpioEntry.setFileSize(testContents.length());
+ * cpioOut.putNextEntry(cpioEntry);
+ * cpioOut.write(testContents.getBytes());
+ * cpioOut.finish();
+ * cpioOut.close();
+ * </pre></code>
+ * <p/>
+ * Note: This implementation should be compatible to cpio 2.5
+ *
+ * based on code from the jRPM project (jrpm.sourceforge.net)
+ */
+public class CpioArchiveOutputStream extends ArchiveOutputStream implements CpioConstants {
+
+ private CpioArchiveEntry cpioEntry;
+
+ private boolean closed = false;
+
+ private boolean finished;
+
+ private short entryFormat = FORMAT_NEW;
+
+ private HashMap names = new HashMap();
+
+ private long crc = 0;
+
+ private long written;
+
+ private OutputStream out = null;
+
+ /**
+ * Check to make sure that this stream has not been closed
+ *
+ * @throws IOException if the stream is already closed
+ */
+ private void ensureOpen() throws IOException {
+ if (this.closed) {
+ throw new IOException("Stream closed");
+ }
+ }
+
+ /**
+ * Construct the cpio output stream with a specified format
+ *
+ * @param out The cpio stream
+ * @param format The format of the stream
+ */
+ public CpioArchiveOutputStream(final OutputStream out, final short format) {
+ this.out = new FilterOutputStream(out);
+ setFormat(format);
+ }
+
+ /**
+ * Construct the cpio output stream. The format for this CPIO stream is the
+ * "new" format
+ *
+ * @param out The cpio stream
+ */
+ public CpioArchiveOutputStream(final OutputStream out) {
+ this(out, FORMAT_NEW);
+ }
+
+ /**
+ * Set a default header format. This will be used if no format is defined in
+ * the cpioEntry given to putNextEntry().
+ *
+ * @param format A CPIO format
+ */
+ public void setFormat(final short format) {
+ switch (format) {
+ case FORMAT_NEW:
+ case FORMAT_NEW_CRC:
+ case FORMAT_OLD_ASCII:
+ case FORMAT_OLD_BINARY:
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown header type");
+
+ }
+ this.entryFormat = format;
+ }
+
+ /**
+ * Begins writing a new CPIO file entry and positions the stream to the
+ * start of the entry data. Closes the current entry if still active. The
+ * current time will be used if the entry has no set modification time and
+ * the default header format will be used if no other format is specified in
+ * the entry.
+ *
+ * @param e the CPIO cpioEntry to be written
+ * @throws IOException if an I/O error has occurred or if a CPIO file error has
+ * occurred
+ */
+ public void putNextEntry(final CpioArchiveEntry e) throws IOException {
+ ensureOpen();
+ if (this.cpioEntry != null) {
+ closeEntry(); // close previous entry
+ }
+ if (e.getTime() == -1) {
+ e.setTime(System.currentTimeMillis());
+ }
+ if (e.getFormat() == -1) {
+ e.setFormat(this.entryFormat);
+ }
+
+ if (this.names.put(e.getName(), e) != null) {
+ throw new IOException("duplicate entry: " + e.getName());
+ }
+
+ writeHeader(e);
+ this.cpioEntry = e;
+ this.written = 0;
+ }
+
+ private void writeHeader(final CpioArchiveEntry e) throws IOException {
+ switch (e.getFormat()) {
+ case FORMAT_NEW:
+ out.write(MAGIC_NEW.getBytes());
+ writeNewEntry(e);
+ break;
+ case FORMAT_NEW_CRC:
+ out.write(MAGIC_NEW_CRC.getBytes());
+ writeNewEntry(e);
+ break;
+ case FORMAT_OLD_ASCII:
+ out.write(MAGIC_OLD_ASCII.getBytes());
+ writeOldAsciiEntry(e);
+ break;
+ case FORMAT_OLD_BINARY:
+ boolean swapHalfWord = true;
+ writeBinaryLong(MAGIC_OLD_BINARY, 2, swapHalfWord);
+ writeOldBinaryEntry(e, swapHalfWord);
+ break;
+ }
+ }
+
+ private void writeNewEntry(final CpioArchiveEntry entry) throws IOException {
+ writeAsciiLong(entry.getInode(), 8, 16);
+ writeAsciiLong(entry.getMode(), 8, 16);
+ writeAsciiLong(entry.getUID(), 8, 16);
+ writeAsciiLong(entry.getGID(), 8, 16);
+ writeAsciiLong(entry.getNumberOfLinks(), 8, 16);
+ writeAsciiLong(entry.getTime(), 8, 16);
+ writeAsciiLong(entry.getSize(), 8, 16);
+ writeAsciiLong(entry.getDeviceMaj(), 8, 16);
+ writeAsciiLong(entry.getDeviceMin(), 8, 16);
+ writeAsciiLong(entry.getRemoteDeviceMaj(), 8, 16);
+ writeAsciiLong(entry.getRemoteDeviceMin(), 8, 16);
+ writeAsciiLong(entry.getName().length() + 1, 8, 16);
+ writeAsciiLong(entry.getChksum(), 8, 16);
+ writeCString(entry.getName());
+ pad(entry.getHeaderSize() + entry.getName().length() + 1, 4);
+ }
+
+ private void writeOldAsciiEntry(final CpioArchiveEntry entry) throws IOException {
+ writeAsciiLong(entry.getDevice(), 6, 8);
+ writeAsciiLong(entry.getInode(), 6, 8);
+ writeAsciiLong(entry.getMode(), 6, 8);
+ writeAsciiLong(entry.getUID(), 6, 8);
+ writeAsciiLong(entry.getGID(), 6, 8);
+ writeAsciiLong(entry.getNumberOfLinks(), 6, 8);
+ writeAsciiLong(entry.getRemoteDevice(), 6, 8);
+ writeAsciiLong(entry.getTime(), 11, 8);
+ writeAsciiLong(entry.getName().length() + 1, 6, 8);
+ writeAsciiLong(entry.getSize(), 11, 8);
+ writeCString(entry.getName());
+ }
+
+ private void writeOldBinaryEntry(final CpioArchiveEntry entry,
+ final boolean swapHalfWord) throws IOException {
+ writeBinaryLong(entry.getDevice(), 2, swapHalfWord);
+ writeBinaryLong(entry.getInode(), 2, swapHalfWord);
+ writeBinaryLong(entry.getMode(), 2, swapHalfWord);
+ writeBinaryLong(entry.getUID(), 2, swapHalfWord);
+ writeBinaryLong(entry.getGID(), 2, swapHalfWord);
+ writeBinaryLong(entry.getNumberOfLinks(), 2, swapHalfWord);
+ writeBinaryLong(entry.getRemoteDevice(), 2, swapHalfWord);
+ writeBinaryLong(entry.getTime(), 4, swapHalfWord);
+ writeBinaryLong(entry.getName().length() + 1, 2, swapHalfWord);
+ writeBinaryLong(entry.getSize(), 4, swapHalfWord);
+ writeCString(entry.getName());
+ pad(entry.getHeaderSize() + entry.getName().length() + 1, 2);
+ }
+
+ /**
+ * Closes the current CPIO entry and positions the stream for writing the
+ * next entry.
+ *
+ * @throws IOException if an I/O error has occurred or if a CPIO file error has
+ * occurred
+ */
+ public void closeEntry() throws IOException {
+ ensureOpen();
+
+ if (this.cpioEntry.getSize() != this.written) {
+ throw new IOException("invalid entry size (expected "
+ + this.cpioEntry.getSize() + " but got " + this.written
+ + " bytes)");
+ }
+ if ((this.cpioEntry.getFormat() | FORMAT_NEW_MASK) == FORMAT_NEW_MASK) {
+ pad(this.cpioEntry.getSize(), 4);
+ } else if ((this.cpioEntry.getFormat() | FORMAT_OLD_BINARY) == FORMAT_OLD_BINARY) {
+ pad(this.cpioEntry.getSize(), 2);
+ }
+ if ((this.cpioEntry.getFormat() | FORMAT_NEW_CRC) == FORMAT_NEW_CRC) {
+ if (this.crc != this.cpioEntry.getChksum()) {
+ throw new IOException("CRC Error");
+ }
+ }
+ if (this.cpioEntry != null) {
+ this.cpioEntry = null;
+ }
+ this.crc = 0;
+ this.written = 0;
+ }
+
+ /**
+ * Writes an array of bytes to the current CPIO entry data. This method will
+ * block until all the bytes are written.
+ *
+ * @param b the data to be written
+ * @param off the start offset in the data
+ * @param len the number of bytes that are written
+ * @throws IOException if an I/O error has occurred or if a CPIO file error has
+ * occurred
+ */
+ public synchronized void write(final byte[] b, final int off, final int len)
+ throws IOException {
+ ensureOpen();
+ if (off < 0 || len < 0 || off > b.length - len) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return;
+ }
+
+ if (this.cpioEntry == null) {
+ throw new IOException("no current CPIO entry");
+ }
+ if (this.written + len > this.cpioEntry.getSize()) {
+ throw new IOException("attempt to write past end of STORED entry");
+ }
+ out.write(b, off, len);
+ this.written += len;
+ if ((this.cpioEntry.getFormat() | FORMAT_NEW_CRC) == FORMAT_NEW_CRC) {
+ for (int pos = 0; pos < len; pos++) {
+ this.crc += b[pos] & 0xFF;
+ }
+ }
+ }
+
+ /**
+ * Finishes writing the contents of the CPIO output stream without closing
+ * the underlying stream. Use this method when applying multiple filters in
+ * succession to the same output stream.
+ *
+ * @throws IOException if an I/O exception has occurred or if a CPIO file error
+ * has occurred
+ */
+ public void finish() throws IOException {
+ ensureOpen();
+ // TODO: synchronize and finish
+ if (this.finished) {
+ return;
+ }
+ if (this.cpioEntry != null) {
+ closeEntry();
+ }
+ this.cpioEntry = new CpioArchiveEntry(this.entryFormat);
+ this.cpioEntry.setMode(0);
+ this.cpioEntry.setName("TRAILER!!!");
+ this.cpioEntry.setNumberOfLinks(1);
+ writeHeader(this.cpioEntry);
+ closeEntry();
+ }
+
+ /**
+ * Closes the CPIO output stream as well as the stream being filtered.
+ *
+ * @throws IOException if an I/O error has occurred or if a CPIO file error has
+ * occurred
+ */
+ public void close() throws IOException {
+ if (!this.closed) {
+ super.close();
+ this.closed = true;
+ }
+ }
+
+ private void pad(final long count, final int border) throws IOException {
+ long skip = count % border;
+ if (skip > 0) {
+ byte tmp[] = new byte[(int) (border - skip)];
+ out.write(tmp);
+ }
+ }
+
+ private void writeBinaryLong(final long number, final int length,
+ final boolean swapHalfWord) throws IOException {
+ byte tmp[] = long2byteArray(number, length, swapHalfWord);
+ out.write(tmp);
+ }
+
+ private void writeAsciiLong(final long number, final int length,
+ final int radix) throws IOException {
+ StringBuffer tmp = new StringBuffer();
+ String tmpStr;
+ if (radix == 16) {
+ tmp.append(Long.toHexString(number));
+ } else if (radix == 8) {
+ tmp.append(Long.toOctalString(number));
+ } else {
+ tmp.append(Long.toString(number));
+ }
+
+ if (tmp.length() <= length) {
+ long insertLength = length - tmp.length();
+ for (int pos = 0; pos < insertLength; pos++) {
+ tmp.insert(0, "0");
+ }
+ tmpStr = tmp.toString();
+ } else {
+ tmpStr = tmp.substring(tmp.length() - length);
+ }
+ out.write(tmpStr.getBytes());
+ }
+
+ private void writeCString(final String str) throws IOException {
+ out.write(str.getBytes());
+ out.write('\0');
+ }
+
+ /**
+ * Converts a byte array to a long. Halfwords can be swaped with setting
+ * swapHalfWord=true.
+ *
+ * @param number An array of bytes containing a number
+ * @param length The length of the returned array
+ * @param swapHalfWord Swap halfwords ([0][1][2][3]->[1][0][3][2])
+ * @return The long value
+ */
+ private static byte[] long2byteArray(final long number, final int length,
+ final boolean swapHalfWord) {
+ byte[] ret = new byte[length];
+ int pos = 0;
+ long tmp_number = 0;
+
+ if (length % 2 != 0 || length < 2) {
+ throw new UnsupportedOperationException();
+ }
+
+ tmp_number = number;
+ for (pos = length - 1; pos >= 0; pos--) {
+ ret[pos] = (byte) (tmp_number & 0xFF);
+ tmp_number >>= 8;
+ }
+
+ if (!swapHalfWord) {
+ byte tmp = 0;
+ for (pos = 0; pos < length; pos++) {
+ tmp = ret[pos];
+ ret[pos++] = ret[pos];
+ ret[pos] = tmp;
+ }
+ }
+
+ return ret;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.commons.compress.archivers.ArchiveOutputStream#closeArchiveEntry()
+ */
+ public void closeArchiveEntry() throws IOException {
+ this.closeEntry();
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.commons.compress.archivers.ArchiveOutputStream#putArchiveEntry(org.apache.commons.compress.archivers.ArchiveEntry)
+ */
+ public void putArchiveEntry(ArchiveEntry entry) throws IOException {
+ this.putNextEntry((CpioArchiveEntry)entry);
+ }
+
+ /* (non-Javadoc)
+ * @see java.io.OutputStream#write(int)
+ */
+ public void write(int b) throws IOException {
+ out.write(b);
+ }
+}
diff --git a/src/main/java/org/apache/commons/compress/archivers/cpio/CpioConstants.java b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioConstants.java
new file mode 100644
index 0000000..3b08409
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/cpio/CpioConstants.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.compress.archivers.cpio;
+
+/**
+ * All constants needed by CPIO.
+ *
+ * based on code from the jRPM project (jrpm.sourceforge.net)
+ */
+public interface CpioConstants {
+ /** magic number of a cpio entry in the new format */
+ final String MAGIC_NEW = "070701";
+
+ /** magic number of a cpio entry in the new format with crc */
+ final String MAGIC_NEW_CRC = "070702";
+
+ /** magic number of a cpio entry in the old ascii format */
+ final String MAGIC_OLD_ASCII = "070707";
+
+ /** magic number of a cpio entry in the old binary format */
+ final int MAGIC_OLD_BINARY = 070707;
+
+ /** write/read a CPIOArchiveEntry in the new format */
+ final short FORMAT_NEW = 1;
+
+ /** write/read a CPIOArchiveEntry in the new format with crc */
+ final short FORMAT_NEW_CRC = 2;
+
+ /** write/read a CPIOArchiveEntry in the old ascii format */
+ final short FORMAT_OLD_ASCII = 4;
+
+ /** write/read a CPIOArchiveEntry in the old binary format */
+ final short FORMAT_OLD_BINARY = 8;
+
+ /** Mask for both new formats */
+ final short FORMAT_NEW_MASK = 3;
+
+ /** Mask for both old formats */
+ final short FORMAT_OLD_MASK = 12;
+
+ /** Mask for all file type bits. */
+ final int S_IFMT = 0170000;
+
+ /** Defines a directory */
+ final int C_ISDIR = 0040000;
+
+ /** Defines a symbolic link */
+ final int C_ISLNK = 0120000;
+
+ /** Defines a regular file */
+ final int C_ISREG = 0100000;
+
+ /** Defines a pipe */
+ final int C_ISFIFO = 0010000;
+
+ /** Defines a character device */
+ final int C_ISCHR = 0020000;
+
+ /** Defines a block device */
+ final int C_ISBLK = 0060000;
+
+ /** Defines a socket */
+ final int C_ISSOCK = 0140000;
+
+ /** HP/UX network special (C_ISCTG) */
+ final int C_ISNWK = 0110000;
+
+ /** Permits the owner of a file to read the file */
+ final int C_IRUSR = 000400;
+
+ /** Permits the owner of a file to write to the file */
+ final int C_IWUSR = 000200;
+
+ /**
+ * Permits the owner of a file to execute the file or to search the file's
+ * directory
+ */
+ final int C_IXUSR = 000100;
+
+ /** Permits a file's group to read the file */
+ final int C_IRGRP = 000040;
+
+ /** Permits a file's group to write to the file */
+ final int C_IWGRP = 000020;
+
+ /**
+ * Permits a file's group to execute the file or to search the file's
+ * directory
+ */
+ final int C_IXGRP = 000010;
+
+ /** Permits others to read the file */
+ final int C_IROTH = 000004;
+
+ /** Permits others to write to the file */
+ final int C_IWOTH = 000002;
+
+ /** Permits others to execute the file or to search the file's directory */
+ final int C_IXOTH = 000001;
+
+ /** TODO document */
+ final int C_ISUID = 004000;
+
+ /** TODO document */
+ final int C_ISGID = 002000;
+
+ /** TODO document */
+ final int C_ISVTX = 001000;
+}
diff --git a/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorOutputStream.java b/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorOutputStream.java
index 9efdc25..5f4b96c 100644
--- a/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorOutputStream.java
@@ -565,7 +565,7 @@
int v, t, i, j, gs, ge, totc, bt, bc, iter;
int nSelectors = 0, alphaSize, minLen, maxLen, selCtr;
- int nGroups, nBytes;
+ int nGroups; //, nBytes;
alphaSize = nInUse + 2;
for (t = 0; t < N_GROUPS; t++) {
@@ -796,7 +796,7 @@
}
}
- nBytes = bytesOut;
+ //nBytes = bytesOut;
for (i = 0; i < 16; i++) {
if (inUse16[i]) {
bsW(1, 1);
@@ -820,7 +820,7 @@
}
/* Now the selectors. */
- nBytes = bytesOut;
+ //nBytes = bytesOut;
bsW (3, nGroups);
bsW (15, nSelectors);
for (i = 0; i < nSelectors; i++) {
@@ -831,7 +831,7 @@
}
/* Now the coding tables. */
- nBytes = bytesOut;
+ //nBytes = bytesOut;
for (t = 0; t < nGroups; t++) {
int curr = len[t][0];
@@ -850,7 +850,7 @@
}
/* And finally, the block data proper */
- nBytes = bytesOut;
+ //nBytes = bytesOut;
selCtr = 0;
gs = 0;
while (true) {
diff --git a/src/main/resources/bla.jar b/src/main/resources/bla.jar
deleted file mode 100644
index ad3ed82..0000000
--- a/src/main/resources/bla.jar
+++ /dev/null
Binary files differ
diff --git a/src/main/resources/bla.tar b/src/main/resources/bla.tar
deleted file mode 100644
index c7af537..0000000
--- a/src/main/resources/bla.tar
+++ /dev/null
Binary files differ
diff --git a/src/main/resources/bla.tgz b/src/main/resources/bla.tgz
deleted file mode 100644
index d741f1e..0000000
--- a/src/main/resources/bla.tgz
+++ /dev/null
Binary files differ
diff --git a/src/main/resources/test1.xml b/src/main/resources/test1.xml
deleted file mode 100644
index e611d7b..0000000
--- a/src/main/resources/test1.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0"?>
-<empty/>
\ No newline at end of file
diff --git a/src/main/resources/test2.xml b/src/main/resources/test2.xml
deleted file mode 100644
index 08e6c40..0000000
--- a/src/main/resources/test2.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0"?>
-<empty/>
diff --git a/src/test/java/org/apache/commons/compress/DetectArchiverTestCase.java b/src/test/java/org/apache/commons/compress/DetectArchiverTestCase.java
index b028c6f..e8b50c3 100644
--- a/src/test/java/org/apache/commons/compress/DetectArchiverTestCase.java
+++ b/src/test/java/org/apache/commons/compress/DetectArchiverTestCase.java
@@ -27,6 +27,7 @@
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.ar.ArArchiveInputStream;
+import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream;
import org.apache.commons.compress.archivers.jar.JarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
@@ -38,23 +39,33 @@
final ArchiveInputStream ar = factory.createArchiveInputStream(
new BufferedInputStream(new FileInputStream(
new File(getClass().getClassLoader().getResource("bla.ar").getFile()))));
+ assertNotNull(ar);
assertTrue(ar instanceof ArArchiveInputStream);
final ArchiveInputStream tar = factory.createArchiveInputStream(
new BufferedInputStream(new FileInputStream(
new File(getClass().getClassLoader().getResource("bla.tar").getFile()))));
+ assertNotNull(tar);
assertTrue(tar instanceof TarArchiveInputStream);
final ArchiveInputStream zip = factory.createArchiveInputStream(
new BufferedInputStream(new FileInputStream(
new File(getClass().getClassLoader().getResource("bla.zip").getFile()))));
+ assertNotNull(zip);
assertTrue(zip instanceof ZipArchiveInputStream);
final ArchiveInputStream jar = factory.createArchiveInputStream(
new BufferedInputStream(new FileInputStream(
new File(getClass().getClassLoader().getResource("bla.jar").getFile()))));
+ assertNotNull(jar);
assertTrue(jar instanceof JarArchiveInputStream);
+ final ArchiveInputStream cpio = factory.createArchiveInputStream(
+ new BufferedInputStream(new FileInputStream(
+ new File(getClass().getClassLoader().getResource("bla.cpio").getFile()))));
+ assertNotNull(cpio);
+ assertTrue(cpio instanceof CpioArchiveInputStream);
+
// final ArchiveInputStream tgz = factory.createArchiveInputStream(
// new BufferedInputStream(new FileInputStream(
// new File(getClass().getClassLoader().getResource("bla.tgz").getFile()))));
diff --git a/src/test/java/org/apache/commons/compress/archivers/CpioTestCase.java b/src/test/java/org/apache/commons/compress/archivers/CpioTestCase.java
new file mode 100644
index 0000000..1f724e1
--- /dev/null
+++ b/src/test/java/org/apache/commons/compress/archivers/CpioTestCase.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.compress.archivers;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.compress.AbstractTestCase;
+import org.apache.commons.compress.archivers.cpio.CpioArchiveEntry;
+import org.apache.commons.compress.utils.IOUtils;
+
+public final class CpioTestCase extends AbstractTestCase {
+
+ public void testCpioArchiveCreation() throws Exception {
+ final File output = new File(dir, "bla.cpio");
+
+ final File file1 = getFile("test1.xml");
+ final File file2 = getFile("test2.xml");
+
+ final OutputStream out = new FileOutputStream(output);
+ final ArchiveOutputStream os = new ArchiveStreamFactory().createArchiveOutputStream("cpio", out);
+ os.putArchiveEntry(new CpioArchiveEntry("test1.xml", file1.length()));
+ IOUtils.copy(new FileInputStream(file1), os);
+ os.closeArchiveEntry();
+
+ os.putArchiveEntry(new CpioArchiveEntry("test2.xml", file2.length()));
+ IOUtils.copy(new FileInputStream(file2), os);
+ os.closeArchiveEntry();
+
+ os.close();
+ }
+
+ public void testCpioUnarchive() throws Exception {
+ final File output = new File(dir, "bla.cpio");
+ {
+ final File file1 = getFile("test1.xml");
+ final File file2 = getFile("test2.xml");
+
+ final OutputStream out = new FileOutputStream(output);
+ final ArchiveOutputStream os = new ArchiveStreamFactory().createArchiveOutputStream("cpio", out);
+ os.putArchiveEntry(new CpioArchiveEntry("test1.xml", file1.length()));
+ IOUtils.copy(new FileInputStream(file1), os);
+ os.closeArchiveEntry();
+
+ os.putArchiveEntry(new CpioArchiveEntry("test2.xml", file2.length()));
+ IOUtils.copy(new FileInputStream(file2), os);
+ os.closeArchiveEntry();
+ os.close();
+ }
+
+ // Unarchive Operation
+ final File input = output;
+ final InputStream is = new FileInputStream(input);
+ final ArchiveInputStream in = new ArchiveStreamFactory().createArchiveInputStream("cpio", is);
+ final CpioArchiveEntry entry = (CpioArchiveEntry)in.getNextEntry();
+
+ File target = new File(dir, entry.getName());
+ final OutputStream out = new FileOutputStream(target);
+
+ IOUtils.copy(in, out);
+
+ out.close();
+ in.close();
+ }
+
+}
diff --git a/src/test/resources/bla.cpio b/src/test/resources/bla.cpio
new file mode 100644
index 0000000..47da420
--- /dev/null
+++ b/src/test/resources/bla.cpio
Binary files differ