You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ant.apache.org by bo...@apache.org on 2012/06/16 06:12:38 UTC
svn commit: r1350857 [1/2] - in /ant/core/trunk: ./
src/main/org/apache/tools/tar/ src/main/org/apache/tools/zip/
Author: bodewig
Date: Sat Jun 16 04:12:37 2012
New Revision: 1350857
URL: http://svn.apache.org/viewvc?rev=1350857&view=rev
Log:
merge tar package from Compress, bringing some POSIX tar support
Added:
ant/core/trunk/src/main/org/apache/tools/tar/TarArchiveSparseEntry.java
- copied, changed from r1348527, commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveSparseEntry.java
Modified:
ant/core/trunk/WHATSNEW
ant/core/trunk/src/main/org/apache/tools/tar/TarBuffer.java
ant/core/trunk/src/main/org/apache/tools/tar/TarConstants.java
ant/core/trunk/src/main/org/apache/tools/tar/TarEntry.java
ant/core/trunk/src/main/org/apache/tools/tar/TarInputStream.java
ant/core/trunk/src/main/org/apache/tools/tar/TarOutputStream.java
ant/core/trunk/src/main/org/apache/tools/tar/TarUtils.java
ant/core/trunk/src/main/org/apache/tools/zip/ZipEncoding.java
ant/core/trunk/src/main/org/apache/tools/zip/ZipEncodingHelper.java
Modified: ant/core/trunk/WHATSNEW
URL: http://svn.apache.org/viewvc/ant/core/trunk/WHATSNEW?rev=1350857&r1=1350856&r2=1350857&view=diff
==============================================================================
--- ant/core/trunk/WHATSNEW (original)
+++ ant/core/trunk/WHATSNEW Sat Jun 16 04:12:37 2012
@@ -49,6 +49,9 @@ Other changes:
Java VMs.
Bugzilla Report 52706.
+ * merged the TAR package from Commons Compress, it can now read
+ archives using POSIX extension headers and STAR extensions.
+
Changes from Ant 1.8.3 TO Ant 1.8.4
===================================
Copied: ant/core/trunk/src/main/org/apache/tools/tar/TarArchiveSparseEntry.java (from r1348527, commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveSparseEntry.java)
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/tar/TarArchiveSparseEntry.java?p2=ant/core/trunk/src/main/org/apache/tools/tar/TarArchiveSparseEntry.java&p1=commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveSparseEntry.java&r1=1348527&r2=1350857&rev=1350857&view=diff
==============================================================================
--- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveSparseEntry.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/tar/TarArchiveSparseEntry.java Sat Jun 16 04:12:37 2012
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.commons.compress.archivers.tar;
+package org.apache.tools.tar;
import java.io.IOException;
Modified: ant/core/trunk/src/main/org/apache/tools/tar/TarBuffer.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/tar/TarBuffer.java?rev=1350857&r1=1350856&r2=1350857&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/tar/TarBuffer.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/tar/TarBuffer.java Sat Jun 16 04:12:37 2012
@@ -51,12 +51,13 @@ public class TarBuffer {
private InputStream inStream;
private OutputStream outStream;
- private byte[] blockBuffer;
+ private final int blockSize;
+ private final int recordSize;
+ private final int recsPerBlock;
+ private final byte[] blockBuffer;
+
private int currBlkIdx;
private int currRecIdx;
- private int blockSize;
- private int recordSize;
- private int recsPerBlock;
private boolean debug;
/**
@@ -83,10 +84,7 @@ public class TarBuffer {
* @param recordSize the record size to use
*/
public TarBuffer(InputStream inStream, int blockSize, int recordSize) {
- this.inStream = inStream;
- this.outStream = null;
-
- this.initialize(blockSize, recordSize);
+ this(inStream, null, blockSize, recordSize);
}
/**
@@ -113,16 +111,15 @@ public class TarBuffer {
* @param recordSize the record size to use
*/
public TarBuffer(OutputStream outStream, int blockSize, int recordSize) {
- this.inStream = null;
- this.outStream = outStream;
-
- this.initialize(blockSize, recordSize);
+ this(null, outStream, blockSize, recordSize);
}
/**
- * Initialization common to all constructors.
+ * Private constructor to perform common setup.
*/
- private void initialize(int blockSize, int recordSize) {
+ private TarBuffer(InputStream inStream, OutputStream outStream, int blockSize, int recordSize) {
+ this.inStream = inStream;
+ this.outStream = outStream;
this.debug = false;
this.blockSize = blockSize;
this.recordSize = recordSize;
@@ -194,10 +191,8 @@ public class TarBuffer {
throw new IOException("reading (via skip) from an output buffer");
}
- if (currRecIdx >= recsPerBlock) {
- if (!readBlock()) {
- return; // UNDONE
- }
+ if (currRecIdx >= recsPerBlock && !readBlock()) {
+ return; // UNDONE
}
currRecIdx++;
@@ -216,13 +211,14 @@ public class TarBuffer {
}
if (inStream == null) {
+ if (outStream == null) {
+ throw new IOException("input buffer is closed");
+ }
throw new IOException("reading from an output buffer");
}
- if (currRecIdx >= recsPerBlock) {
- if (!readBlock()) {
- return null;
- }
+ if (currRecIdx >= recsPerBlock && !readBlock()) {
+ return null;
}
byte[] result = new byte[recordSize];
@@ -337,6 +333,9 @@ public class TarBuffer {
}
if (outStream == null) {
+ if (inStream == null){
+ throw new IOException("Output buffer is closed");
+ }
throw new IOException("writing to an input buffer");
}
@@ -374,6 +373,9 @@ public class TarBuffer {
}
if (outStream == null) {
+ if (inStream == null){
+ throw new IOException("Output buffer is closed");
+ }
throw new IOException("writing to an input buffer");
}
@@ -454,9 +456,8 @@ public class TarBuffer {
} else if (inStream != null) {
if (inStream != System.in) {
inStream.close();
-
- inStream = null;
}
+ inStream = null;
}
}
}
Modified: ant/core/trunk/src/main/org/apache/tools/tar/TarConstants.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/tar/TarConstants.java?rev=1350857&r1=1350856&r2=1350857&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/tar/TarConstants.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/tar/TarConstants.java Sat Jun 16 04:12:37 2012
@@ -26,11 +26,23 @@ package org.apache.tools.tar;
/**
* This interface contains all the definitions used in the package.
*
+ * For tar formats (FORMAT_OLDGNU, FORMAT_POSIX, etc.) see GNU tar
+ * <I>tar.h</I> type <I>enum archive_format</I>
*/
// CheckStyle:InterfaceIsTypeCheck OFF (bc)
public interface TarConstants {
/**
+ * GNU format as per before tar 1.12.
+ */
+ int FORMAT_OLDGNU = 2;
+
+ /**
+ * Pure Posix format.
+ */
+ int FORMAT_POSIX = 3;
+
+ /**
* The length of the name field in a header buffer.
*/
int NAMELEN = 100;
@@ -51,26 +63,49 @@ public interface TarConstants {
int GIDLEN = 8;
/**
+ * The maximum value of gid/uid in a tar archive which can
+ * be expressed in octal char notation (that's 7 sevens, octal).
+ */
+ long MAXID = 07777777L;
+
+ /**
* The length of the checksum field in a header buffer.
*/
int CHKSUMLEN = 8;
/**
* The length of the size field in a header buffer.
+ * Includes the trailing space or NUL.
*/
int SIZELEN = 12;
/**
- * The maximum size of a file in a tar archive (That's 11 sevens, octal).
+ * The maximum size of a file in a tar archive
+ * which can be expressed in octal char notation (that's 11 sevens, octal).
*/
long MAXSIZE = 077777777777L;
+ /** Offset of start of magic field within header record */
+ int MAGIC_OFFSET = 257;
/**
- * The length of the magic field in a header buffer.
+ * The length of the magic field in a header buffer including the version.
*/
int MAGICLEN = 8;
/**
+ * The length of the magic field in a header buffer.
+ */
+ int PURE_MAGICLEN = 6;
+
+ /** Offset of start of magic field within header record */
+ int VERSION_OFFSET = 263;
+ /**
+ * Previously this was regarded as part of "magic" field, but it
+ * is separate.
+ */
+ int VERSIONLEN = 2;
+
+ /**
* The length of the modification time field in a header buffer.
*/
int MODTIMELEN = 12;
@@ -86,11 +121,77 @@ public interface TarConstants {
int GNAMELEN = 32;
/**
- * The length of the devices field in a header buffer.
+ * The length of each of the device fields (major and minor) in a header buffer.
*/
int DEVLEN = 8;
/**
+ * Length of the prefix field.
+ *
+ */
+ int PREFIXLEN = 155;
+
+ /**
+ * The length of the access time field in an old GNU header buffer.
+ *
+ */
+ int ATIMELEN_GNU = 12;
+
+ /**
+ * The length of the created time field in an old GNU header buffer.
+ *
+ */
+ int CTIMELEN_GNU = 12;
+
+ /**
+ * The length of the multivolume start offset field in an old GNU header buffer.
+ *
+ */
+ int OFFSETLEN_GNU = 12;
+
+ /**
+ * The length of the long names field in an old GNU header buffer.
+ *
+ */
+ int LONGNAMESLEN_GNU = 4;
+
+ /**
+ * The length of the padding field in an old GNU header buffer.
+ *
+ */
+ int PAD2LEN_GNU = 1;
+
+ /**
+ * The sum of the length of all sparse headers in an old GNU header buffer.
+ *
+ */
+ int SPARSELEN_GNU = 96;
+
+ /**
+ * The length of the is extension field in an old GNU header buffer.
+ *
+ */
+ int ISEXTENDEDLEN_GNU = 1;
+
+ /**
+ * The length of the real size field in an old GNU header buffer.
+ *
+ */
+ int REALSIZELEN_GNU = 12;
+
+ /**
+ * The sum of the length of all sparse headers in a sparse header buffer.
+ *
+ */
+ int SPARSELEN_GNU_SPARSE = 504;
+
+ /**
+ * The length of the is extension field in a sparse header buffer.
+ *
+ */
+ int ISEXTENDEDLEN_GNU_SPARSE = 1;
+
+ /**
* LF_ constants represent the "link flag" of an entry, or more commonly,
* the "entry type". This is the "old way" of indicating a normal file.
*/
@@ -137,22 +238,51 @@ public interface TarConstants {
byte LF_CONTIG = (byte) '7';
/**
- * The magic tag representing a POSIX tar archive.
+ * Identifies the *next* file on the tape as having a long name.
*/
+ byte LF_GNUTYPE_LONGNAME = (byte) 'L';
+
+ /**
+ * Sparse file type.
+ */
+ byte LF_GNUTYPE_SPARSE = (byte) 'S';
+
+ // See "http://www.opengroup.org/onlinepubs/009695399/utilities/pax.html#tag_04_100_13_02"
+
+ /**
+ * Identifies the entry as a Pax extended header.
+ */
+ byte LF_PAX_EXTENDED_HEADER_LC = (byte) 'x';
+
+ /**
+ * Identifies the entry as a Pax extended header (SunOS tar -E).
+ */
+ byte LF_PAX_EXTENDED_HEADER_UC = (byte) 'X';
+
+ /**
+ * Identifies the entry as a Pax global extended header.
+ */
+ byte LF_PAX_GLOBAL_EXTENDED_HEADER = (byte) 'g';
+
String TMAGIC = "ustar";
/**
+ * The magic tag representing a POSIX tar archive.
+ */
+ String MAGIC_POSIX = "ustar\0";
+ String VERSION_POSIX = "00";
+
+ /**
* The magic tag representing a GNU tar archive.
*/
String GNU_TMAGIC = "ustar ";
+ // Appear to be two possible GNU versions
+ String VERSION_GNU_SPACE = " \0";
+ String VERSION_GNU_ZERO = "0\0";
/**
- * The namr of the GNU tar entry which contains a long name.
+ * The name of the GNU tar entry which contains a long name.
*/
String GNU_LONGLINK = "././@LongLink";
- /**
- * Identifies the *next* file on the tape as having a long name.
- */
- byte LF_GNUTYPE_LONGNAME = (byte) 'L';
}
Modified: ant/core/trunk/src/main/org/apache/tools/tar/TarEntry.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/tar/TarEntry.java?rev=1350857&r1=1350856&r2=1350857&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/tar/TarEntry.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/tar/TarEntry.java Sat Jun 16 04:12:37 2012
@@ -24,9 +24,13 @@
package org.apache.tools.tar;
import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Locale;
+import org.apache.tools.zip.ZipEncoding;
+
/**
* This class represents an entry in a Tar archive. It consists
* of the entry's header, as well as the entry's File. Entries
@@ -72,13 +76,44 @@ import java.util.Locale;
* char devmajor[8];
* char devminor[8];
* } header;
+ * All unused bytes are set to null.
+ * New-style GNU tar files are slightly different from the above.
+ * For values of size larger than 077777777777L (11 7s)
+ * or uid and gid larger than 07777777L (7 7s)
+ * the sign bit of the first byte is set, and the rest of the
+ * field is the binary representation of the number.
+ * See TarUtils.parseOctalOrBinary.
+ * </pre>
+ *
+ * <p>
+ * The C structure for a old GNU Tar Entry's header is:
+ * <pre>
+ * struct oldgnu_header {
+ * char unused_pad1[345]; // TarConstants.PAD1LEN_GNU - offset 0
+ * char atime[12]; // TarConstants.ATIMELEN_GNU - offset 345
+ * char ctime[12]; // TarConstants.CTIMELEN_GNU - offset 357
+ * char offset[12]; // TarConstants.OFFSETLEN_GNU - offset 369
+ * char longnames[4]; // TarConstants.LONGNAMESLEN_GNU - offset 381
+ * char unused_pad2; // TarConstants.PAD2LEN_GNU - offset 385
+ * struct sparse sp[4]; // TarConstants.SPARSELEN_GNU - offset 386
+ * char isextended; // TarConstants.ISEXTENDEDLEN_GNU - offset 482
+ * char realsize[12]; // TarConstants.REALSIZELEN_GNU - offset 483
+ * char unused_pad[17]; // TarConstants.PAD3LEN_GNU - offset 495
+ * };
+ * </pre>
+ * Whereas, "struct sparse" is:
+ * <pre>
+ * struct sparse {
+ * char offset[12]; // offset 0
+ * char numbytes[12]; // offset 12
+ * };
* </pre>
*
*/
public class TarEntry implements TarConstants {
/** The entry's name. */
- private StringBuffer name;
+ private String name;
/** The entry's permission mode. */
private int mode;
@@ -99,16 +134,18 @@ public class TarEntry implements TarCons
private byte linkFlag;
/** The entry's link name. */
- private StringBuffer linkName;
+ private String linkName;
/** The entry's magic tag. */
- private StringBuffer magic;
+ private String magic;
+ /** The version of the format */
+ private String version;
/** The entry's user name. */
- private StringBuffer userName;
+ private String userName;
/** The entry's group name. */
- private StringBuffer groupName;
+ private String groupName;
/** The entry's major device number. */
private int devMajor;
@@ -116,6 +153,12 @@ public class TarEntry implements TarCons
/** The entry's minor device number. */
private int devMinor;
+ /** If an extension sparse header follows. */
+ private boolean isExtended;
+
+ /** The entry's real size in case of a sparse file. */
+ private long realSize;
+
/** The entry's file reference */
private File file;
@@ -134,10 +177,11 @@ public class TarEntry implements TarCons
/**
* Construct an empty entry and prepares the header values.
*/
- private TarEntry () {
- this.magic = new StringBuffer(TMAGIC);
- this.name = new StringBuffer();
- this.linkName = new StringBuffer();
+ private TarEntry() {
+ this.magic = MAGIC_POSIX;
+ this.version = VERSION_POSIX;
+ this.name = "";
+ this.linkName = "";
String user = System.getProperty("user.name", "");
@@ -147,8 +191,8 @@ public class TarEntry implements TarCons
this.userId = 0;
this.groupId = 0;
- this.userName = new StringBuffer(user);
- this.groupName = new StringBuffer("");
+ this.userName = user;
+ this.groupName = "";
this.file = null;
}
@@ -178,19 +222,16 @@ public class TarEntry implements TarCons
this.devMajor = 0;
this.devMinor = 0;
- this.name = new StringBuffer(name);
+ this.name = name;
this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
this.userId = 0;
this.groupId = 0;
this.size = 0;
this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND;
- this.linkName = new StringBuffer("");
- this.userName = new StringBuffer("");
- this.groupName = new StringBuffer("");
- this.devMajor = 0;
- this.devMinor = 0;
-
+ this.linkName = "";
+ this.userName = "";
+ this.groupName = "";
}
/**
@@ -203,38 +244,52 @@ public class TarEntry implements TarCons
this(name);
this.linkFlag = linkFlag;
if (linkFlag == LF_GNUTYPE_LONGNAME) {
- magic = new StringBuffer(GNU_TMAGIC);
+ magic = GNU_TMAGIC;
+ version = VERSION_GNU_SPACE;
}
}
/**
* Construct an entry for a file. File is set to file, and the
* header is constructed from information from the file.
+ * The name is set from the normalized file path.
*
* @param file The file that the entry represents.
*/
public TarEntry(File file) {
+ this(file, normalizeFileName(file.getPath(), false));
+ }
+
+ /**
+ * Construct an entry for a file. File is set to file, and the
+ * header is constructed from information from the file.
+ *
+ * @param file The file that the entry represents.
+ * @param fileName the name to be used for the entry.
+ */
+ public TarEntry(File file, String fileName) {
this();
this.file = file;
- String fileName = normalizeFileName(file.getPath(), false);
- this.linkName = new StringBuffer("");
- this.name = new StringBuffer(fileName);
+ this.linkName = "";
if (file.isDirectory()) {
this.mode = DEFAULT_DIR_MODE;
this.linkFlag = LF_DIR;
- int nameLength = name.length();
- if (nameLength == 0 || name.charAt(nameLength - 1) != '/') {
- this.name.append("/");
+ int nameLength = fileName.length();
+ if (nameLength == 0 || fileName.charAt(nameLength - 1) != '/') {
+ this.name = fileName + "/";
+ } else {
+ this.name = fileName;
}
this.size = 0;
} else {
this.mode = DEFAULT_FILE_MODE;
this.linkFlag = LF_NORMAL;
this.size = file.length();
+ this.name = fileName;
}
this.modTime = file.lastModified() / MILLIS_PER_SECOND;
@@ -247,6 +302,7 @@ public class TarEntry implements TarCons
* to null.
*
* @param headerBuf The header bytes from a tar archive entry.
+ * @throws IllegalArgumentException if any of the numeric fields have an invalid format
*/
public TarEntry(byte[] headerBuf) {
this();
@@ -254,6 +310,20 @@ public class TarEntry implements TarCons
}
/**
+ * Construct an entry from an archive's header bytes. File is set
+ * to null.
+ *
+ * @param headerBuf The header bytes from a tar archive entry.
+ * @param encoding encoding to use for file names
+ * @throws IllegalArgumentException if any of the numeric fields have an invalid format
+ */
+ public TarEntry(byte[] headerBuf, ZipEncoding encoding)
+ throws IOException {
+ this();
+ parseTarHeader(headerBuf, encoding);
+ }
+
+ /**
* Determine if the two entries are equal. Equality is determined
* by the header names being equal.
*
@@ -271,6 +341,7 @@ public class TarEntry implements TarCons
* @param it Entry to be checked for equality.
* @return True if the entries are equal.
*/
+ @Override
public boolean equals(Object it) {
if (it == null || getClass() != it.getClass()) {
return false;
@@ -283,6 +354,7 @@ public class TarEntry implements TarCons
*
* @return the entry hashcode
*/
+ @Override
public int hashCode() {
return getName().hashCode();
}
@@ -314,7 +386,7 @@ public class TarEntry implements TarCons
* @param name This entry's new name.
*/
public void setName(String name) {
- this.name = new StringBuffer(normalizeFileName(name, false));
+ this.name = normalizeFileName(name, false);
}
/**
@@ -336,6 +408,15 @@ public class TarEntry implements TarCons
}
/**
+ * Set this entry's link name.
+ *
+ * @param link the link name to use.
+ */
+ public void setLinkName(String link) {
+ this.linkName = link;
+ }
+
+ /**
* Get this entry's user id.
*
* @return This entry's user id.
@@ -386,7 +467,7 @@ public class TarEntry implements TarCons
* @param userName This entry's new user name.
*/
public void setUserName(String userName) {
- this.userName = new StringBuffer(userName);
+ this.userName = userName;
}
/**
@@ -404,7 +485,7 @@ public class TarEntry implements TarCons
* @param groupName This entry's new group name.
*/
public void setGroupName(String groupName) {
- this.groupName = new StringBuffer(groupName);
+ this.groupName = groupName;
}
/**
@@ -488,11 +569,88 @@ public class TarEntry implements TarCons
* Set this entry's file size.
*
* @param size This entry's new file size.
+ * @throws IllegalArgumentException if the size is < 0.
*/
public void setSize(long size) {
+ if (size < 0){
+ throw new IllegalArgumentException("Size is out of range: "+size);
+ }
this.size = size;
}
+ /**
+ * Get this entry's major device number.
+ *
+ * @return This entry's major device number.
+ */
+ public int getDevMajor() {
+ return devMajor;
+ }
+
+ /**
+ * Set this entry's major device number.
+ *
+ * @param devNo This entry's major device number.
+ * @throws IllegalArgumentException if the devNo is < 0.
+ */
+ public void setDevMajor(int devNo) {
+ if (devNo < 0){
+ throw new IllegalArgumentException("Major device number is out of "
+ + "range: " + devNo);
+ }
+ this.devMajor = devNo;
+ }
+
+ /**
+ * Get this entry's minor device number.
+ *
+ * @return This entry's minor device number.
+ */
+ public int getDevMinor() {
+ return devMinor;
+ }
+
+ /**
+ * Set this entry's minor device number.
+ *
+ * @param devNo This entry's minor device number.
+ * @throws IllegalArgumentException if the devNo is < 0.
+ */
+ public void setDevMinor(int devNo) {
+ if (devNo < 0){
+ throw new IllegalArgumentException("Minor device number is out of "
+ + "range: " + devNo);
+ }
+ this.devMinor = devNo;
+ }
+
+ /**
+ * Indicates in case of a sparse file if an extension sparse header
+ * follows.
+ *
+ * @return true if an extension sparse header follows.
+ */
+ public boolean isExtended() {
+ return isExtended;
+ }
+
+ /**
+ * Get this entry's real file size in case of a sparse file.
+ *
+ * @return This entry's real file size.
+ */
+ public long getRealSize() {
+ return realSize;
+ }
+
+ /**
+ * Indicate if this entry is a GNU sparse block
+ *
+ * @return true if this is a sparse extension provided by GNU tar
+ */
+ public boolean isGNUSparse() {
+ return linkFlag == LF_GNUTYPE_SPARSE;
+ }
/**
* Indicate if this entry is a GNU long name block
@@ -501,7 +659,26 @@ public class TarEntry implements TarCons
*/
public boolean isGNULongNameEntry() {
return linkFlag == LF_GNUTYPE_LONGNAME
- && name.toString().equals(GNU_LONGLINK);
+ && name.equals(GNU_LONGLINK);
+ }
+
+ /**
+ * Check if this is a Pax header.
+ *
+ * @return {@code true} if this is a Pax header.
+ */
+ public boolean isPaxHeader(){
+ return linkFlag == LF_PAX_EXTENDED_HEADER_LC
+ || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
+ }
+
+ /**
+ * Check if this is a Pax header.
+ *
+ * @return {@code true} if this is a Pax header.
+ */
+ public boolean isGlobalPaxHeader(){
+ return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
}
/**
@@ -526,6 +703,54 @@ public class TarEntry implements TarCons
}
/**
+ * Check if this is a "normal file"
+ */
+ public boolean isFile() {
+ if (file != null) {
+ return file.isFile();
+ }
+ if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
+ return true;
+ }
+ return !getName().endsWith("/");
+ }
+
+ /**
+ * Check if this is a symbolic link entry.
+ */
+ public boolean isSymbolicLink() {
+ return linkFlag == LF_SYMLINK;
+ }
+
+ /**
+ * Check if this is a link entry.
+ */
+ public boolean isLink() {
+ return linkFlag == LF_LINK;
+ }
+
+ /**
+ * Check if this is a character device entry.
+ */
+ public boolean isCharacterDevice() {
+ return linkFlag == LF_CHR;
+ }
+
+ /**
+ * Check if this is a block device entry.
+ */
+ public boolean isBlockDevice() {
+ return linkFlag == LF_BLK;
+ }
+
+ /**
+ * Check if this is a FIFO (pipe) entry.
+ */
+ public boolean isFIFO() {
+ return linkFlag == LF_FIFO;
+ }
+
+ /**
* If this entry represents a file, and the file is a directory, return
* an array of TarEntries for this entry's children.
*
@@ -549,17 +774,46 @@ public class TarEntry implements TarCons
/**
* Write an entry's header information to a header buffer.
*
+ * <p>This method does not use the star/GNU tar/BSD tar extensions.</p>
+ *
* @param outbuf The tar entry header buffer to fill in.
*/
public void writeEntryHeader(byte[] outbuf) {
+ try {
+ writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false);
+ } catch (IOException ex) {
+ try {
+ writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false);
+ } catch (IOException ex2) {
+ // impossible
+ throw new RuntimeException(ex2);
+ }
+ }
+ }
+
+ /**
+ * Write an entry's header information to a header buffer.
+ *
+ * @param outbuf The tar entry header buffer to fill in.
+ * @param encoding encoding to use when writing the file name.
+ * @param starMode whether to use the star/GNU tar/BSD tar
+ * extension for numeric fields if their value doesn't fit in the
+ * maximum size of standard tar archives
+ */
+ public void writeEntryHeader(byte[] outbuf, ZipEncoding encoding,
+ boolean starMode) throws IOException {
int offset = 0;
- offset = TarUtils.getNameBytes(name, outbuf, offset, NAMELEN);
- offset = TarUtils.getOctalBytes(mode, outbuf, offset, MODELEN);
- offset = TarUtils.getOctalBytes(userId, outbuf, offset, UIDLEN);
- offset = TarUtils.getOctalBytes(groupId, outbuf, offset, GIDLEN);
- offset = TarUtils.getLongOctalBytes(size, outbuf, offset, SIZELEN);
- offset = TarUtils.getLongOctalBytes(modTime, outbuf, offset, MODTIMELEN);
+ offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN,
+ encoding);
+ offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode);
+ offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN,
+ starMode);
+ offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN,
+ starMode);
+ offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode);
+ offset = writeEntryHeaderField(modTime, outbuf, offset, MODTIMELEN,
+ starMode);
int csOffset = offset;
@@ -568,12 +822,18 @@ public class TarEntry implements TarCons
}
outbuf[offset++] = linkFlag;
- offset = TarUtils.getNameBytes(linkName, outbuf, offset, NAMELEN);
- offset = TarUtils.getNameBytes(magic, outbuf, offset, MAGICLEN);
- offset = TarUtils.getNameBytes(userName, outbuf, offset, UNAMELEN);
- offset = TarUtils.getNameBytes(groupName, outbuf, offset, GNAMELEN);
- offset = TarUtils.getOctalBytes(devMajor, outbuf, offset, DEVLEN);
- offset = TarUtils.getOctalBytes(devMinor, outbuf, offset, DEVLEN);
+ offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN,
+ encoding);
+ offset = TarUtils.formatNameBytes(magic, outbuf, offset, PURE_MAGICLEN);
+ offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
+ offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN,
+ encoding);
+ offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN,
+ encoding);
+ offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN,
+ starMode);
+ offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN,
+ starMode);
while (offset < outbuf.length) {
outbuf[offset++] = 0;
@@ -581,42 +841,122 @@ public class TarEntry implements TarCons
long chk = TarUtils.computeCheckSum(outbuf);
- TarUtils.getCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
+ TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
+ }
+
+ private int writeEntryHeaderField(long value, byte[] outbuf, int offset,
+ int length, boolean starMode) {
+ if (!starMode && (value < 0
+ || value >= (1l << (3 * (length - 1))))) {
+ // value doesn't fit into field when written as octal
+ // number, will be written to PAX header or causes an
+ // error
+ return TarUtils.formatLongOctalBytes(0, outbuf, offset, length);
+ }
+ return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset,
+ length);
}
/**
* Parse an entry's header information from a header buffer.
*
* @param header The tar entry header buffer to get information from.
+ * @throws IllegalArgumentException if any of the numeric fields have an invalid format
*/
public void parseTarHeader(byte[] header) {
+ try {
+ parseTarHeader(header, TarUtils.DEFAULT_ENCODING);
+ } catch (IOException ex) {
+ try {
+ parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true);
+ } catch (IOException ex2) {
+ // not really possible
+ throw new RuntimeException(ex2);
+ }
+ }
+ }
+
+ /**
+ * Parse an entry's header information from a header buffer.
+ *
+ * @param header The tar entry header buffer to get information from.
+ * @param encoding encoding to use for file names
+ * @throws IllegalArgumentException if any of the numeric fields
+ * have an invalid format
+ */
+ public void parseTarHeader(byte[] header, ZipEncoding encoding)
+ throws IOException {
+ parseTarHeader(header, encoding, false);
+ }
+
+ private void parseTarHeader(byte[] header, ZipEncoding encoding,
+ final boolean oldStyle)
+ throws IOException {
int offset = 0;
- name = TarUtils.parseName(header, offset, NAMELEN);
+ name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
+ : TarUtils.parseName(header, offset, NAMELEN, encoding);
offset += NAMELEN;
- mode = (int) TarUtils.parseOctal(header, offset, MODELEN);
+ mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN);
offset += MODELEN;
- userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);
+ userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN);
offset += UIDLEN;
- groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN);
+ groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN);
offset += GIDLEN;
- size = TarUtils.parseOctal(header, offset, SIZELEN);
+ size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN);
offset += SIZELEN;
- modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);
+ modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN);
offset += MODTIMELEN;
offset += CHKSUMLEN;
linkFlag = header[offset++];
- linkName = TarUtils.parseName(header, offset, NAMELEN);
+ linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
+ : TarUtils.parseName(header, offset, NAMELEN, encoding);
offset += NAMELEN;
- magic = TarUtils.parseName(header, offset, MAGICLEN);
- offset += MAGICLEN;
- userName = TarUtils.parseName(header, offset, UNAMELEN);
+ magic = TarUtils.parseName(header, offset, PURE_MAGICLEN);
+ offset += PURE_MAGICLEN;
+ version = TarUtils.parseName(header, offset, VERSIONLEN);
+ offset += VERSIONLEN;
+ userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN)
+ : TarUtils.parseName(header, offset, UNAMELEN, encoding);
offset += UNAMELEN;
- groupName = TarUtils.parseName(header, offset, GNAMELEN);
+ groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN)
+ : TarUtils.parseName(header, offset, GNAMELEN, encoding);
offset += GNAMELEN;
- devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
+ devMajor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
+ offset += DEVLEN;
+ devMinor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
offset += DEVLEN;
- devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
+
+ int type = evaluateType(header);
+ switch (type) {
+ case FORMAT_OLDGNU: {
+ offset += ATIMELEN_GNU;
+ offset += CTIMELEN_GNU;
+ offset += OFFSETLEN_GNU;
+ offset += LONGNAMESLEN_GNU;
+ offset += PAD2LEN_GNU;
+ offset += SPARSELEN_GNU;
+ isExtended = TarUtils.parseBoolean(header, offset);
+ offset += ISEXTENDEDLEN_GNU;
+ realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
+ offset += REALSIZELEN_GNU;
+ break;
+ }
+ case FORMAT_POSIX:
+ default: {
+ String prefix = oldStyle
+ ? TarUtils.parseName(header, offset, PREFIXLEN)
+ : TarUtils.parseName(header, offset, PREFIXLEN, encoding);
+ // SunOS tar -E does not add / to directory names, so fix
+ // up to be consistent
+ if (isDirectory() && !name.endsWith("/")){
+ name = name + "/";
+ }
+ if (prefix.length() > 0){
+ name = prefix + "/" + name;
+ }
+ }
+ }
}
/**
@@ -661,4 +1001,85 @@ public class TarEntry implements TarCons
}
return fileName;
}
+
+ /**
+ * Evaluate an entry's header format from a header buffer.
+ *
+ * @param header The tar entry header buffer to evaluate the format for.
+ * @return format type
+ */
+ private int evaluateType(byte[] header) {
+ if (matchAsciiBuffer(GNU_TMAGIC, header, MAGIC_OFFSET, PURE_MAGICLEN)) {
+ return FORMAT_OLDGNU;
+ }
+ if (matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, PURE_MAGICLEN)) {
+ return FORMAT_POSIX;
+ }
+ return 0;
+ }
+
+ /**
+ * Check if buffer contents matches Ascii String.
+ *
+ * @param expected
+ * @param buffer
+ * @param offset
+ * @param length
+ * @return {@code true} if buffer is the same as the expected string
+ */
+ private static boolean matchAsciiBuffer(String expected, byte[] buffer,
+ int offset, int length){
+ byte[] buffer1;
+ try {
+ buffer1 = expected.getBytes("ASCII");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e); // Should not happen
+ }
+ return isEqual(buffer1, 0, buffer1.length, buffer, offset, length,
+ false);
+ }
+
+ /**
+ * Compare byte buffers, optionally ignoring trailing nulls
+ *
+ * @param buffer1
+ * @param offset1
+ * @param length1
+ * @param buffer2
+ * @param offset2
+ * @param length2
+ * @param ignoreTrailingNulls
+ * @return {@code true} if buffer1 and buffer2 have same contents, having regard to trailing nulls
+ */
+ private static boolean isEqual(
+ final byte[] buffer1, final int offset1, final int length1,
+ final byte[] buffer2, final int offset2, final int length2,
+ boolean ignoreTrailingNulls){
+ int minLen=length1 < length2 ? length1 : length2;
+ for (int i=0; i < minLen; i++){
+ if (buffer1[offset1+i] != buffer2[offset2+i]){
+ return false;
+ }
+ }
+ if (length1 == length2){
+ return true;
+ }
+ if (ignoreTrailingNulls){
+ if (length1 > length2){
+ for(int i = length2; i < length1; i++){
+ if (buffer1[offset1+i] != 0){
+ return false;
+ }
+ }
+ } else {
+ for(int i = length1; i < length2; i++){
+ if (buffer2[offset2+i] != 0){
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+ }
}
Modified: ant/core/trunk/src/main/org/apache/tools/tar/TarInputStream.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/tar/TarInputStream.java?rev=1350857&r1=1350856&r2=1350857&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/tar/TarInputStream.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/tar/TarInputStream.java Sat Jun 16 04:12:37 2012
@@ -23,10 +23,17 @@
package org.apache.tools.tar;
+import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.tools.zip.ZipEncoding;
+import org.apache.tools.zip.ZipEncodingHelper;
/**
* The TarInputStream reads a UNIX tar archive as an InputStream.
@@ -59,6 +66,8 @@ public class TarInputStream extends Filt
// CheckStyle:VisibilityModifier ON
+ private final ZipEncoding encoding;
+
/**
* Constructor for TarInputStream.
* @param is the input stream to use
@@ -70,6 +79,15 @@ public class TarInputStream extends Filt
/**
* Constructor for TarInputStream.
* @param is the input stream to use
+ * @param encoding name of the encoding to use for file names
+ */
+ public TarInputStream(InputStream is, String encoding) {
+ this(is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE, encoding);
+ }
+
+ /**
+ * Constructor for TarInputStream.
+ * @param is the input stream to use
* @param blockSize the block size to use
*/
public TarInputStream(InputStream is, int blockSize) {
@@ -80,16 +98,38 @@ public class TarInputStream extends Filt
* Constructor for TarInputStream.
* @param is the input stream to use
* @param blockSize the block size to use
+ * @param encoding name of the encoding to use for file names
+ */
+ public TarInputStream(InputStream is, int blockSize, String encoding) {
+ this(is, blockSize, TarBuffer.DEFAULT_RCDSIZE, encoding);
+ }
+
+ /**
+ * Constructor for TarInputStream.
+ * @param is the input stream to use
+ * @param blockSize the block size to use
* @param recordSize the record size to use
*/
public TarInputStream(InputStream is, int blockSize, int recordSize) {
- super(is);
+ this(is, blockSize, recordSize, null);
+ }
+ /**
+ * Constructor for TarInputStream.
+ * @param is the input stream to use
+ * @param blockSize the block size to use
+ * @param recordSize the record size to use
+ * @param encoding name of the encoding to use for file names
+ */
+ public TarInputStream(InputStream is, int blockSize, int recordSize,
+ String encoding) {
+ super(is);
this.buffer = new TarBuffer(is, blockSize, recordSize);
this.readBuf = null;
this.oneBuf = new byte[1];
this.debug = false;
this.hasHitEOF = false;
+ this.encoding = ZipEncodingHelper.getZipEncoding(encoding);
}
/**
@@ -106,6 +146,7 @@ public class TarInputStream extends Filt
* Closes this stream. Calls the TarBuffer's close() method.
* @throws IOException on error
*/
+ @Override
public void close() throws IOException {
buffer.close();
}
@@ -131,6 +172,7 @@ public class TarInputStream extends Filt
* @return The number of available bytes for the current entry.
* @throws IOException for signature
*/
+ @Override
public int available() throws IOException {
if (entrySize - entryOffset > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
@@ -148,6 +190,7 @@ public class TarInputStream extends Filt
* @return the number actually skipped
* @throws IOException on error
*/
+ @Override
public long skip(long numToSkip) throws IOException {
// REVIEW
// This is horribly inefficient, but it ensures that we
@@ -171,6 +214,7 @@ public class TarInputStream extends Filt
*
* @return False.
*/
+ @Override
public boolean markSupported() {
return false;
}
@@ -180,12 +224,14 @@ public class TarInputStream extends Filt
*
* @param markLimit The limit to mark.
*/
+ @Override
public void mark(int markLimit) {
}
/**
* Since we do not support marking just yet, we do nothing.
*/
+ @Override
public void reset() {
}
@@ -230,44 +276,37 @@ public class TarInputStream extends Filt
readBuf = null;
}
- byte[] headerBuf = buffer.readRecord();
-
- if (headerBuf == null) {
- if (debug) {
- System.err.println("READ NULL RECORD");
- }
- hasHitEOF = true;
- } else if (buffer.isEOFRecord(headerBuf)) {
- if (debug) {
- System.err.println("READ EOF RECORD");
- }
- hasHitEOF = true;
- }
+ byte[] headerBuf = getRecord();
if (hasHitEOF) {
currEntry = null;
- } else {
- currEntry = new TarEntry(headerBuf);
-
- if (debug) {
- System.err.println("TarInputStream: SET CURRENTRY '"
- + currEntry.getName()
- + "' size = "
- + currEntry.getSize());
- }
-
- entryOffset = 0;
+ return null;
+ }
- entrySize = currEntry.getSize();
+ try {
+ currEntry = new TarEntry(headerBuf, encoding);
+ } catch (IllegalArgumentException e) {
+ IOException ioe = new IOException("Error detected parsing the header");
+ ioe.initCause(e);
+ throw ioe;
+ }
+ if (debug) {
+ System.err.println("TarInputStream: SET CURRENTRY '"
+ + currEntry.getName()
+ + "' size = "
+ + currEntry.getSize());
}
- if (currEntry != null && currEntry.isGNULongNameEntry()) {
+ entryOffset = 0;
+ entrySize = currEntry.getSize();
+
+ if (currEntry.isGNULongNameEntry()) {
// read in the name
StringBuffer longName = new StringBuffer();
byte[] buf = new byte[SMALL_BUFFER_SIZE];
int length = 0;
while ((length = read(buf)) >= 0) {
- longName.append(new String(buf, 0, length));
+ longName.append(new String(buf, 0, length)); // TODO default charset?
}
getNextEntry();
if (currEntry == null) {
@@ -283,10 +322,177 @@ public class TarInputStream extends Filt
currEntry.setName(longName.toString());
}
+ if (currEntry.isPaxHeader()){ // Process Pax headers
+ paxHeaders();
+ }
+
+ if (currEntry.isGNUSparse()){ // Process sparse files
+ readGNUSparse();
+ }
+
+ // If the size of the next element in the archive has changed
+ // due to a new size being reported in the posix header
+ // information, we update entrySize here so that it contains
+ // the correct value.
+ entrySize = currEntry.getSize();
return currEntry;
}
/**
+ * Get the next record in this tar archive. This will skip
+ * over any remaining data in the current entry, if there
+ * is one, and place the input stream at the header of the
+ * next entry.
+ * If there are no more entries in the archive, null will
+ * be returned to indicate that the end of the archive has
+ * been reached.
+ *
+ * @return The next header in the archive, or null.
+ * @throws IOException on error
+ */
+ private byte[] getRecord() throws IOException {
+ if (hasHitEOF) {
+ return null;
+ }
+
+ byte[] headerBuf = buffer.readRecord();
+
+ if (headerBuf == null) {
+ if (debug) {
+ System.err.println("READ NULL RECORD");
+ }
+ hasHitEOF = true;
+ } else if (buffer.isEOFRecord(headerBuf)) {
+ if (debug) {
+ System.err.println("READ EOF RECORD");
+ }
+ hasHitEOF = true;
+ }
+
+ return hasHitEOF ? null : headerBuf;
+ }
+
+ private void paxHeaders() throws IOException{
+ Map<String, String> headers = parsePaxHeaders(this);
+ getNextEntry(); // Get the actual file entry
+ applyPaxHeadersToCurrentEntry(headers);
+ }
+
+ Map<String, String> parsePaxHeaders(InputStream i) throws IOException {
+ Map<String, String> headers = new HashMap<String, String>();
+ // Format is "length keyword=value\n";
+ while(true){ // get length
+ int ch;
+ int len = 0;
+ int read = 0;
+ while((ch = i.read()) != -1) {
+ read++;
+ if (ch == ' '){ // End of length string
+ // Get keyword
+ ByteArrayOutputStream coll = new ByteArrayOutputStream();
+ while((ch = i.read()) != -1) {
+ read++;
+ if (ch == '='){ // end of keyword
+ String keyword = coll.toString("UTF-8");
+ // Get rest of entry
+ byte[] rest = new byte[len - read];
+ int got = i.read(rest);
+ if (got != len - read){
+ throw new IOException("Failed to read "
+ + "Paxheader. Expected "
+ + (len - read)
+ + " bytes, read "
+ + got);
+ }
+ // Drop trailing NL
+ String value = new String(rest, 0,
+ len - read - 1, "UTF-8");
+ headers.put(keyword, value);
+ break;
+ }
+ coll.write((byte) ch);
+ }
+ break; // Processed single header
+ }
+ len *= 10;
+ len += ch - '0';
+ }
+ if (ch == -1){ // EOF
+ break;
+ }
+ }
+ return headers;
+ }
+
+ private void applyPaxHeadersToCurrentEntry(Map<String, String> headers) {
+ /*
+ * The following headers are defined for Pax.
+ * atime, ctime, charset: cannot use these without changing TarEntry fields
+ * mtime
+ * comment
+ * gid, gname
+ * linkpath
+ * size
+ * uid,uname
+ * SCHILY.devminor, SCHILY.devmajor: don't have setters/getters for those
+ */
+ for (Entry<String, String> ent : headers.entrySet()){
+ String key = ent.getKey();
+ String val = ent.getValue();
+ if ("path".equals(key)){
+ currEntry.setName(val);
+ } else if ("linkpath".equals(key)){
+ currEntry.setLinkName(val);
+ } else if ("gid".equals(key)){
+ currEntry.setGroupId(Integer.parseInt(val));
+ } else if ("gname".equals(key)){
+ currEntry.setGroupName(val);
+ } else if ("uid".equals(key)){
+ currEntry.setUserId(Integer.parseInt(val));
+ } else if ("uname".equals(key)){
+ currEntry.setUserName(val);
+ } else if ("size".equals(key)){
+ currEntry.setSize(Long.parseLong(val));
+ } else if ("mtime".equals(key)){
+ currEntry.setModTime((long) (Double.parseDouble(val) * 1000));
+ } else if ("SCHILY.devminor".equals(key)){
+ currEntry.setDevMinor(Integer.parseInt(val));
+ } else if ("SCHILY.devmajor".equals(key)){
+ currEntry.setDevMajor(Integer.parseInt(val));
+ }
+ }
+ }
+
+ /**
+ * Adds the sparse chunks from the current entry to the sparse chunks,
+ * including any additional sparse entries following the current entry.
+ *
+ * @throws IOException on error
+ *
+ * @todo Sparse files get not yet really processed.
+ */
+ private void readGNUSparse() throws IOException {
+ /* we do not really process sparse files yet
+ sparses = new ArrayList();
+ sparses.addAll(currEntry.getSparses());
+ */
+ if (currEntry.isExtended()) {
+ TarArchiveSparseEntry entry;
+ do {
+ byte[] headerBuf = getRecord();
+ if (hasHitEOF) {
+ currEntry = null;
+ break;
+ }
+ entry = new TarArchiveSparseEntry(headerBuf);
+ /* we do not really process sparse files yet
+ sparses.addAll(entry.getSparses());
+ */
+ } while (entry.isExtended());
+ }
+ }
+
+ /**
* Reads a byte from the current tar archive entry.
*
* This method simply calls read( byte[], int, int ).
@@ -294,6 +500,7 @@ public class TarInputStream extends Filt
* @return The byte read, or -1 at EOF.
* @throws IOException on error
*/
+ @Override
public int read() throws IOException {
int num = read(oneBuf, 0, 1);
return num == -1 ? -1 : ((int) oneBuf[0]) & BYTE_MASK;
@@ -312,6 +519,7 @@ public class TarInputStream extends Filt
* @return The number of bytes read, or -1 at EOF.
* @throws IOException on error
*/
+ @Override
public int read(byte[] buf, int offset, int numToRead) throws IOException {
int totalRead = 0;
@@ -399,4 +607,14 @@ public class TarInputStream extends Filt
out.write(buf, 0, numRead);
}
}
+
+ /**
+ * Whether this class is able to read the given entry.
+ *
+ * <p>May return false if the current entry is a sparse file.</p>
+ */
+ public boolean canReadEntryData(TarEntry te) {
+ return !te.isGNUSparse();
+ }
}
+
Modified: ant/core/trunk/src/main/org/apache/tools/tar/TarOutputStream.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/tar/TarOutputStream.java?rev=1350857&r1=1350856&r2=1350857&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/tar/TarOutputStream.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/tar/TarOutputStream.java Sat Jun 16 04:12:37 2012
@@ -23,9 +23,15 @@
package org.apache.tools.tar;
+import java.io.File;
import java.io.FilterOutputStream;
-import java.io.OutputStream;
import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.tools.zip.ZipEncoding;
+import org.apache.tools.zip.ZipEncodingHelper;
/**
* The TarOutputStream writes a UNIX tar archive as an OutputStream.
@@ -43,6 +49,18 @@ public class TarOutputStream extends Fil
/** GNU tar extensions are used to store long file names in the archive. */
public static final int LONGFILE_GNU = 2;
+ /** POSIX/PAX extensions are used to store long file names in the archive. */
+ public static final int LONGFILE_POSIX = 3;
+
+ /** Fail if a big number (e.g. size > 8GiB) is required in the archive. */
+ public static final int BIGNUMBER_ERROR = 0;
+
+ /** star/GNU tar/BSD tar extensions are used to store big number in the archive. */
+ public static final int BIGNUMBER_STAR = 1;
+
+ /** POSIX/PAX extensions are used to store big numbers in the archive. */
+ public static final int BIGNUMBER_POSIX = 2;
+
// CheckStyle:VisibilityModifier OFF - bc
protected boolean debug;
protected long currSize;
@@ -56,8 +74,22 @@ public class TarOutputStream extends Fil
protected int longFileMode = LONGFILE_ERROR;
// CheckStyle:VisibilityModifier ON
+ private int bigNumberMode = BIGNUMBER_ERROR;
+
private boolean closed = false;
+ /** Indicates if putNextEntry has been called without closeEntry */
+ private boolean haveUnclosedEntry = false;
+
+ /** indicates if this archive is finished */
+ private boolean finished = false;
+
+ private final ZipEncoding encoding;
+
+ private boolean addPaxHeadersForNonAsciiNames = false;
+ private static final ZipEncoding ASCII =
+ ZipEncodingHelper.getZipEncoding("ASCII");
+
/**
* Constructor for TarInputStream.
* @param os the output stream to use
@@ -69,6 +101,15 @@ public class TarOutputStream extends Fil
/**
* Constructor for TarInputStream.
* @param os the output stream to use
+ * @param encoding name of the encoding to use for file names
+ */
+ public TarOutputStream(OutputStream os, String encoding) {
+ this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE, encoding);
+ }
+
+ /**
+ * Constructor for TarInputStream.
+ * @param os the output stream to use
* @param blockSize the block size to use
*/
public TarOutputStream(OutputStream os, int blockSize) {
@@ -79,10 +120,33 @@ public class TarOutputStream extends Fil
* Constructor for TarInputStream.
* @param os the output stream to use
* @param blockSize the block size to use
+ * @param encoding name of the encoding to use for file names
+ */
+ public TarOutputStream(OutputStream os, int blockSize, String encoding) {
+ this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE, encoding);
+ }
+
+ /**
+ * Constructor for TarInputStream.
+ * @param os the output stream to use
+ * @param blockSize the block size to use
* @param recordSize the record size to use
*/
public TarOutputStream(OutputStream os, int blockSize, int recordSize) {
+ this(os, blockSize, recordSize, null);
+ }
+
+ /**
+ * Constructor for TarInputStream.
+ * @param os the output stream to use
+ * @param blockSize the block size to use
+ * @param recordSize the record size to use
+ * @param encoding name of the encoding to use for file names
+ */
+ public TarOutputStream(OutputStream os, int blockSize, int recordSize,
+ String encoding) {
super(os);
+ this.encoding = ZipEncodingHelper.getZipEncoding(encoding);
this.buffer = new TarBuffer(os, blockSize, recordSize);
this.debug = false;
@@ -103,6 +167,23 @@ public class TarOutputStream extends Fil
this.longFileMode = longFileMode;
}
+ /**
+ * Set the big number mode.
+ * This can be BIGNUMBER_ERROR(0), BIGNUMBER_POSIX(1) or BIGNUMBER_STAR(2).
+ * This specifies the treatment of big files (sizes > TarConstants.MAXSIZE) and other numeric values to big to fit into a traditional tar header.
+ * Default is BIGNUMBER_ERROR.
+ * @param bigNumberMode the mode to use
+ */
+ public void setBigNumberMode(int bigNumberMode) {
+ this.bigNumberMode = bigNumberMode;
+ }
+
+ /**
+ * Whether to add a PAX extension header for non-ASCII file names.
+ */
+ public void setAddPaxHeadersForNonAsciiNames(boolean b) {
+ addPaxHeadersForNonAsciiNames = b;
+ }
/**
* Sets the debugging flag.
@@ -124,15 +205,25 @@ public class TarOutputStream extends Fil
/**
* Ends the TAR archive without closing the underlying OutputStream.
- * The result is that the two EOF records of nulls are written.
+ *
+ * An archive consists of a series of file entries terminated by an
+ * end-of-archive entry, which consists of two 512 blocks of zero bytes.
+ * POSIX.1 requires two EOF records, like some other implementations.
+ *
* @throws IOException on error
*/
public void finish() throws IOException {
- // See Bugzilla 28776 for a discussion on this
- // http://issues.apache.org/bugzilla/show_bug.cgi?id=28776
+ if (finished) {
+ throw new IOException("This archive has already been finished");
+ }
+
+ if (haveUnclosedEntry) {
+ throw new IOException("This archives contains unclosed entries.");
+ }
writeEOFRecord();
writeEOFRecord();
buffer.flushBlock();
+ finished = true;
}
/**
@@ -141,9 +232,13 @@ public class TarOutputStream extends Fil
* TarBuffer's close().
* @throws IOException on error
*/
+ @Override
public void close() throws IOException {
- if (!closed) {
+ if(!finished) {
finish();
+ }
+
+ if (!closed) {
buffer.close();
out.close();
closed = true;
@@ -172,27 +267,59 @@ public class TarOutputStream extends Fil
* @throws IOException on error
*/
public void putNextEntry(TarEntry entry) throws IOException {
- if (entry.getName().length() >= TarConstants.NAMELEN) {
-
- if (longFileMode == LONGFILE_GNU) {
+ if(finished) {
+ throw new IOException("Stream has already been finished");
+ }
+ Map<String, String> paxHeaders = new HashMap<String, String>();
+ final String entryName = entry.getName();
+ final byte[] nameBytes = encoding.encode(entryName).array();
+ boolean paxHeaderContainsPath = false;
+ if (nameBytes.length >= TarConstants.NAMELEN) {
+
+ if (longFileMode == LONGFILE_POSIX) {
+ paxHeaders.put("path", entryName);
+ paxHeaderContainsPath = true;
+ } else if (longFileMode == LONGFILE_GNU) {
// create a TarEntry for the LongLink, the contents
// of which are the entry's name
TarEntry longLinkEntry = new TarEntry(TarConstants.GNU_LONGLINK,
TarConstants.LF_GNUTYPE_LONGNAME);
- longLinkEntry.setSize(entry.getName().length() + 1);
+ longLinkEntry.setSize(nameBytes.length + 1); // +1 for NUL
putNextEntry(longLinkEntry);
- write(entry.getName().getBytes());
- write(0);
+ write(nameBytes);
+ write(0); // NUL terminator
closeEntry();
} else if (longFileMode != LONGFILE_TRUNCATE) {
- throw new RuntimeException("file name '" + entry.getName()
- + "' is too long ( > "
- + TarConstants.NAMELEN + " bytes)");
+ throw new RuntimeException("file name '" + entryName
+ + "' is too long ( > "
+ + TarConstants.NAMELEN + " bytes)");
}
}
- entry.writeEntryHeader(recordBuf);
+ if (bigNumberMode == BIGNUMBER_POSIX) {
+ addPaxHeadersForBigNumbers(paxHeaders, entry);
+ } else if (bigNumberMode != BIGNUMBER_STAR) {
+ failForBigNumbers(entry);
+ }
+
+ if (addPaxHeadersForNonAsciiNames && !paxHeaderContainsPath
+ && !ASCII.canEncode(entryName)) {
+ paxHeaders.put("path", entryName);
+ }
+
+ if (addPaxHeadersForNonAsciiNames
+ && (entry.isLink() || entry.isSymbolicLink())
+ && !ASCII.canEncode(entry.getLinkName())) {
+ paxHeaders.put("linkpath", entry.getLinkName());
+ }
+
+ if (paxHeaders.size() > 0) {
+ writePaxHeaders(entryName, paxHeaders);
+ }
+
+ entry.writeEntryHeader(recordBuf, encoding,
+ bigNumberMode == BIGNUMBER_STAR);
buffer.writeRecord(recordBuf);
currBytes = 0;
@@ -202,7 +329,8 @@ public class TarOutputStream extends Fil
} else {
currSize = entry.getSize();
}
- currName = entry.getName();
+ currName = entryName;
+ haveUnclosedEntry = true;
}
/**
@@ -216,6 +344,12 @@ public class TarOutputStream extends Fil
* @throws IOException on error
*/
public void closeEntry() throws IOException {
+ if (finished) {
+ throw new IOException("Stream has already been finished");
+ }
+ if (!haveUnclosedEntry){
+ throw new IOException("No current entry to close");
+ }
if (assemLen > 0) {
for (int i = assemLen; i < assemBuf.length; ++i) {
assemBuf[i] = 0;
@@ -233,6 +367,7 @@ public class TarOutputStream extends Fil
+ "' before the '" + currSize
+ "' bytes specified in the header were written");
}
+ haveUnclosedEntry = false;
}
/**
@@ -243,6 +378,7 @@ public class TarOutputStream extends Fil
* @param b The byte written.
* @throws IOException on error
*/
+ @Override
public void write(int b) throws IOException {
oneBuf[0] = (byte) b;
@@ -275,6 +411,7 @@ public class TarOutputStream extends Fil
* @param numToWrite The number of bytes to write.
* @throws IOException on error
*/
+ @Override
public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException {
if ((currBytes + numToWrite) > currSize) {
throw new IOException("request to write '" + numToWrite
@@ -341,6 +478,58 @@ public class TarOutputStream extends Fil
}
/**
+ * Writes a PAX extended header with the given map as contents.
+ */
+ void writePaxHeaders(String entryName,
+ Map<String, String> headers) throws IOException {
+ String name = "./PaxHeaders.X/" + stripTo7Bits(entryName);
+ if (name.length() >= TarConstants.NAMELEN) {
+ name = name.substring(0, TarConstants.NAMELEN - 1);
+ }
+ TarEntry pex = new TarEntry(name,
+ TarConstants.LF_PAX_EXTENDED_HEADER_LC);
+
+ StringWriter w = new StringWriter();
+ for (Map.Entry<String, String> h : headers.entrySet()) {
+ String key = h.getKey();
+ String value = h.getValue();
+ int len = key.length() + value.length()
+ + 3 /* blank, equals and newline */
+ + 2 /* guess 9 < actual length < 100 */;
+ String line = len + " " + key + "=" + value + "\n";
+ int actualLength = line.getBytes("UTF-8").length;
+ while (len != actualLength) {
+ // Adjust for cases where length < 10 or > 100
+ // or where UTF-8 encoding isn't a single octet
+ // per character.
+ // Must be in loop as size may go from 99 to 100 in
+ // first pass so we'd need a second.
+ len = actualLength;
+ line = len + " " + key + "=" + value + "\n";
+ actualLength = line.getBytes("UTF-8").length;
+ }
+ w.write(line);
+ }
+ byte[] data = w.toString().getBytes("UTF-8");
+ pex.setSize(data.length);
+ putNextEntry(pex);
+ write(data);
+ closeEntry();
+ }
+
+ private String stripTo7Bits(String name) {
+ final int length = name.length();
+ StringBuffer result = new StringBuffer(length);
+ for (int i = 0; i < length; i++) {
+ char stripped = (char) (name.charAt(i) & 0x7F);
+ if (stripped != 0) { // would be read as Trailing null
+ result.append(stripped);
+ }
+ }
+ return result.toString();
+ }
+
+ /**
* Write an EOF (end of archive) record to the tar archive.
* An EOF record consists of a record of all zeros.
*/
@@ -351,6 +540,53 @@ public class TarOutputStream extends Fil
buffer.writeRecord(recordBuf);
}
-}
+ private void addPaxHeadersForBigNumbers(Map<String, String> paxHeaders,
+ TarEntry entry) {
+ addPaxHeaderForBigNumber(paxHeaders, "size", entry.getSize(),
+ TarConstants.MAXSIZE);
+ addPaxHeaderForBigNumber(paxHeaders, "gid", entry.getGroupId(),
+ TarConstants.MAXID);
+ addPaxHeaderForBigNumber(paxHeaders, "mtime", entry.getModTime().getTime() / 1000,
+ TarConstants.MAXSIZE);
+ addPaxHeaderForBigNumber(paxHeaders, "uid", entry.getUserId(),
+ TarConstants.MAXID);
+ // star extensions by J\u00f6rg Schilling
+ addPaxHeaderForBigNumber(paxHeaders, "SCHILY.devmajor",
+ entry.getDevMajor(), TarConstants.MAXID);
+ addPaxHeaderForBigNumber(paxHeaders, "SCHILY.devminor",
+ entry.getDevMinor(), TarConstants.MAXID);
+ // there is no PAX header for file mode
+ failForBigNumber("mode", entry.getMode(), TarConstants.MAXID);
+ }
+
+ private void addPaxHeaderForBigNumber(Map<String, String> paxHeaders,
+ String header, long value,
+ long maxValue) {
+ if (value < 0 || value > maxValue) {
+ paxHeaders.put(header, String.valueOf(value));
+ }
+ }
+ private void failForBigNumbers(TarEntry entry) {
+ failForBigNumber("entry size", entry.getSize(), TarConstants.MAXSIZE);
+ failForBigNumber("group id", entry.getGroupId(), TarConstants.MAXID);
+ failForBigNumber("last modification time",
+ entry.getModTime().getTime() / 1000,
+ TarConstants.MAXSIZE);
+ failForBigNumber("user id", entry.getUserId(), TarConstants.MAXID);
+ failForBigNumber("mode", entry.getMode(), TarConstants.MAXID);
+ failForBigNumber("major device number", entry.getDevMajor(),
+ TarConstants.MAXID);
+ failForBigNumber("minor device number", entry.getDevMinor(),
+ TarConstants.MAXID);
+ }
+
+ private void failForBigNumber(String field, long value, long maxValue) {
+ if (value < 0 || value > maxValue) {
+ throw new RuntimeException(field + " '" + value
+ + "' is too big ( > "
+ + maxValue + " )");
+ }
+ }
+}