diff --git a/WHATSNEW b/WHATSNEW index 685791bbf..6badfc7ca 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -132,6 +132,9 @@ Other changes: * has a new dirmode attribute to specify the permissions for directories. +* can now store Unix permissions in a way that can be + reconstructed by Info-Zip's unzip command. + Changes from Ant 1.5.1Beta1 to 1.5.1 ==================================== diff --git a/build.xml b/build.xml index 242c847fc..27ac14204 100644 --- a/build.xml +++ b/build.xml @@ -1183,9 +1183,21 @@ - + + + + + + + + + + + + + + + @@ -1219,9 +1231,17 @@ - + + + + + + + + + + + diff --git a/docs/manual/CoreTasks/zip.html b/docs/manual/CoreTasks/zip.html index df308e50f..de7aac1cb 100644 --- a/docs/manual/CoreTasks/zip.html +++ b/docs/manual/CoreTasks/zip.html @@ -52,6 +52,15 @@ but causes problems if you try to open them from within Java and your filenames contain non US-ASCII characters. Use the encoding attribute and set it to UTF8 to create zip files that can safely be read by Java.

+ +

Starting with Ant 1.6, <zip> can store Unix permissions +inside the archive (see description of the filemode and dirmode +attributes for <zipfileset>). +Unfortunately there is no portable way to store these permissions. +Ant uses the algorithm used by Info-Zip's +implementation of the zip and unzip commands - these are the default +versions of zip and unzip for many Unix and Unix-like systems.

+

Parameters

@@ -144,15 +153,64 @@ Java.

The zip task supports any number of nested <fileset> elements to specify the files to be included in the archive.

-

zipfileset

-

A <zipfileset> has three additional attributes: prefix, fullpath, and src. The -prefix and fullpath attributes modify the location of the files when they are placed -inside the archive. If the prefix attribute is set, all files in the fileset are prefixed -with that path in the archive. If the fullpath attribute is set, the file described by the fileset is placed at that -exact location in the archive. (The fullpath attribute can only be set for filesets that represent a single file. The prefix and fullpath attributes cannot both be set on the same fileset.) The src attribute -may be used in place of the dir attribute to specify a zip file whose -contents will be extracted and included in the archive. As with directories, include and exclude patterns may be used to specify a subset of the zip file -for inclusion in the archive.

+ +

zipfileset

+ +

A <zipfileset> is a special form of a +<fileset> that adds some extra functionality. It +supports all attributes of <fileset> in addition to +those listed below.

+ +

Parameters

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
prefixall files in the fileset are prefixed with that + path in the archive.No
fullpaththe file described by the fileset is placed at + that exact location in the archive.No
srcmay be used in place of the dir attribute + to specify a zip file whose contents will be extracted and + included in the archive.No
filemodeA 3 digit octal string, specify the user, group + and other modes in the standard Unix fashion. Only applies to + plain files. Default is 644. since Ant 1.6.No
dirmodeA 3 digit octal string, specify the user, group + and other modes in the standard Unix fashion. Only applies to + directories. Default is 755. since Ant 1.6.No
+ +

The fullpath attribute can only be set for filesets that +represent a single file. The prefix and fullpath +attributes cannot both be set on the same fileset.

+ +

When using the src attribute, include and exclude patterns +may be used to specify a subset of the zip file for inclusion in the +archive as with the dir attribute.

+

zipgroupfileset

A <zipgroupfileset> allows for multiple zip files to be merged into the archive. Each file found in this fileset is added to the archive diff --git a/src/main/org/apache/tools/ant/taskdefs/Ear.java b/src/main/org/apache/tools/ant/taskdefs/Ear.java index 95e79f260..2c1ee87be 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Ear.java +++ b/src/main/org/apache/tools/ant/taskdefs/Ear.java @@ -134,7 +134,8 @@ public class Ear extends Jar { super.initZipOutputStream(zOut); } - protected void zipFile(File file, ZipOutputStream zOut, String vPath) + protected void zipFile(File file, ZipOutputStream zOut, String vPath, + int mode) throws IOException { // If the file being added is META-INF/application.xml, we // warn if it's not the one specified in the "appxml" @@ -150,11 +151,11 @@ public class Ear extends Jar { + " be ignored (please use appxml attribute to " + archiveType + " task)", Project.MSG_WARN); } else { - super.zipFile(file, zOut, vPath); + super.zipFile(file, zOut, vPath, mode); descriptorAdded = true; } } else { - super.zipFile(file, zOut, vPath); + super.zipFile(file, zOut, vPath, mode); } } diff --git a/src/main/org/apache/tools/ant/taskdefs/Jar.java b/src/main/org/apache/tools/ant/taskdefs/Jar.java index 586c94672..4d19a21c3 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Jar.java +++ b/src/main/org/apache/tools/ant/taskdefs/Jar.java @@ -314,7 +314,7 @@ public class Jar extends Zip { Project.MSG_WARN); } - zipDir(null, zOut, "META-INF/"); + zipDir(null, zOut, "META-INF/", ZipFileSet.DEFAULT_DIR_MODE); // time to write the manifest ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintWriter writer = new PrintWriter(baos); @@ -324,7 +324,8 @@ public class Jar extends Zip { ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); super.zipFile(bais, zOut, "META-INF/MANIFEST.MF", - System.currentTimeMillis(), null); + System.currentTimeMillis(), null, + ZipFileSet.DEFAULT_FILE_MODE); super.initZipOutputStream(zOut); } @@ -387,20 +388,22 @@ public class Jar extends Zip { writer.flush(); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - super.zipFile(bais, zOut, INDEX_NAME, System.currentTimeMillis(), null); + super.zipFile(bais, zOut, INDEX_NAME, System.currentTimeMillis(), null, + ZipFileSet.DEFAULT_FILE_MODE); } /** * Overriden from Zip class to deal with manifests */ - protected void zipFile(File file, ZipOutputStream zOut, String vPath) + protected void zipFile(File file, ZipOutputStream zOut, String vPath, + int mode) throws IOException { if ("META-INF/MANIFEST.MF".equalsIgnoreCase(vPath)) { if (! doubleFilePass || (doubleFilePass && skipWriting)) { filesetManifest(file, null); } } else { - super.zipFile(file, zOut, vPath); + super.zipFile(file, zOut, vPath, mode); } } @@ -408,14 +411,14 @@ public class Jar extends Zip { * Overriden from Zip class to deal with manifests */ protected void zipFile(InputStream is, ZipOutputStream zOut, String vPath, - long lastModified, File file) + long lastModified, File file, int mode) throws IOException { if ("META-INF/MANIFEST.MF".equalsIgnoreCase(vPath)) { if (! doubleFilePass || (doubleFilePass && skipWriting)) { filesetManifest(file, is); } } else { - super.zipFile(is, zOut, vPath, lastModified, null); + super.zipFile(is, zOut, vPath, lastModified, null, mode); } } diff --git a/src/main/org/apache/tools/ant/taskdefs/War.java b/src/main/org/apache/tools/ant/taskdefs/War.java index 5071780d8..bd778e38f 100644 --- a/src/main/org/apache/tools/ant/taskdefs/War.java +++ b/src/main/org/apache/tools/ant/taskdefs/War.java @@ -172,7 +172,8 @@ public class War extends Jar { /** * add another file to the stream */ - protected void zipFile(File file, ZipOutputStream zOut, String vPath) + protected void zipFile(File file, ZipOutputStream zOut, String vPath, + int mode) throws IOException { // If the file being added is WEB-INF/web.xml, we warn if it's // not the one specified in the "webxml" attribute - or if @@ -187,11 +188,11 @@ public class War extends Jar { + "(please use webxml attribute to " + archiveType + " task)", Project.MSG_WARN); } else { - super.zipFile(file, zOut, vPath); + super.zipFile(file, zOut, vPath, mode); descriptorAdded = true; } } else { - super.zipFile(file, zOut, vPath); + super.zipFile(file, zOut, vPath, mode); } } diff --git a/src/main/org/apache/tools/ant/taskdefs/Zip.java b/src/main/org/apache/tools/ant/taskdefs/Zip.java index 3e597be4f..76b51e2ae 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Zip.java +++ b/src/main/org/apache/tools/ant/taskdefs/Zip.java @@ -388,7 +388,9 @@ public class Zip extends MatchingTask { // Add the implicit fileset to the archive. if (baseDir != null) { - addFiles(getDirectoryScanner(baseDir), zOut, "", ""); + addFiles(getDirectoryScanner(baseDir), zOut, "", "", + ZipFileSet.DEFAULT_DIR_MODE, + ZipFileSet.DEFAULT_FILE_MODE); } // Add the explicit filesets to the archive. addFiles(filesets, zOut); @@ -473,10 +475,28 @@ public class Zip extends MatchingTask { * prependig the given prefix to each filename. * *

Ensure parent directories have been added as well. + * + * @deprecated use six-arg version instead. */ protected void addFiles(FileScanner scanner, ZipOutputStream zOut, String prefix, String fullpath) throws IOException { + addFiles(scanner, zOut, prefix, fullpath, ZipFileSet.DEFAULT_DIR_MODE, + ZipFileSet.DEFAULT_FILE_MODE); + } + + /** + * Add all files of the given FileScanner to the ZipOutputStream + * prependig the given prefix to each filename. + * + *

Ensure parent directories have been added as well. + * + * @since Ant 1.6 + */ + protected void addFiles(FileScanner scanner, ZipOutputStream zOut, + String prefix, String fullpath, int dirMode, + int fileMode) + throws IOException { if (prefix.length() > 0 && fullpath.length() > 0) { throw new BuildException("Both prefix and fullpath attributes must" @@ -500,7 +520,7 @@ public class Zip extends MatchingTask { if (!name.endsWith("/")) { name += "/"; } - addParentDirs(thisBaseDir, name, zOut, prefix); + addParentDirs(thisBaseDir, name, zOut, prefix, dirMode); } // files that matched include patterns @@ -514,13 +534,13 @@ public class Zip extends MatchingTask { File f = new File(thisBaseDir, files[i]); if (fullpath.length() > 0) { // Add this file at the specified location. - addParentDirs(null, fullpath, zOut, ""); - zipFile(f, zOut, fullpath); + addParentDirs(null, fullpath, zOut, "", dirMode); + zipFile(f, zOut, fullpath, fileMode); } else { // Add this file with the specified prefix. String name = files[i].replace(File.separatorChar, '/'); - addParentDirs(thisBaseDir, name, zOut, prefix); - zipFile(f, zOut, prefix + name); + addParentDirs(thisBaseDir, name, zOut, prefix, dirMode); + zipFile(f, zOut, prefix + name, fileMode); } } } @@ -550,13 +570,16 @@ public class Zip extends MatchingTask { String vPath = entry.getName(); if (zipScanner.match(vPath)) { if (fullpath.length() > 0) { - addParentDirs(null, fullpath, zOut, ""); - zipFile(in, zOut, fullpath, entry.getTime(), zipSrc); + addParentDirs(null, fullpath, zOut, "", + fs.getDirMode()); + zipFile(in, zOut, fullpath, entry.getTime(), zipSrc, + fs.getFileMode()); } else { - addParentDirs(null, vPath, zOut, prefix); + addParentDirs(null, vPath, zOut, prefix, + fs.getDirMode()); if (!entry.isDirectory()) { zipFile(in, zOut, prefix + vPath, entry.getTime(), - zipSrc); + zipSrc, fs.getFileMode()); } } } @@ -701,8 +724,20 @@ public class Zip extends MatchingTask { return result; } + /** + * @deprecated use four-arg version instead. + */ protected void zipDir(File dir, ZipOutputStream zOut, String vPath) throws IOException { + zipDir(dir, zOut, vPath, ZipFileSet.DEFAULT_DIR_MODE); + } + + /** + * @since Ant 1.6 + */ + protected void zipDir(File dir, ZipOutputStream zOut, String vPath, + int mode) + throws IOException { if (addedDirs.get(vPath) != null) { // don't add directories we've already added. // no warning if we try, it is harmless in and of itself @@ -723,17 +758,28 @@ public class Zip extends MatchingTask { ze.setMethod (ZipEntry.STORED); // This is faintly ridiculous: ze.setCrc (EMPTY_CRC); - - // this is 040775 | MS-DOS directory flag in reverse byte order - ze.setExternalAttributes(0x41FD0010L); + ze.setUnixMode(mode); zOut.putNextEntry (ze); } } + /** + * @deprecated use six-arg version instead. + */ protected void zipFile(InputStream in, ZipOutputStream zOut, String vPath, long lastModified, File file) throws IOException { + zipFile(in, zOut, vPath, lastModified, file, + ZipFileSet.DEFAULT_FILE_MODE); + } + + /** + * @since Ant 1.6 + */ + protected void zipFile(InputStream in, ZipOutputStream zOut, String vPath, + long lastModified, File file, int mode) + throws IOException { if (entries.contains(vPath)) { if (duplicate.equals("preserve")) { @@ -759,14 +805,15 @@ public class Zip extends MatchingTask { ze.setTime(lastModified); /* - * XXX ZipOutputStream.putEntry expects the ZipEntry to know its - * size and the CRC sum before you start writing the data when using - * STORED mode. + * ZipOutputStream.putNextEntry expects the ZipEntry to + * know its size and the CRC sum before you start writing + * the data when using STORED mode. * * This forces us to process the data twice. * - * I couldn't find any documentation on this, just found out by try - * and error. + * In DEFLATED mode, it will take advantage of a Zip + * Version 2 feature where size can be stored after the + * data (as the data itself signals end of data). */ if (!doCompress) { long size = 0; @@ -800,6 +847,7 @@ public class Zip extends MatchingTask { ze.setCrc(cal.getValue()); } + ze.setUnixMode(mode); zOut.putNextEntry(ze); byte[] buffer = new byte[8 * 1024]; @@ -814,8 +862,20 @@ public class Zip extends MatchingTask { addedFiles.addElement(vPath); } + /** + * @deprecated use six-arg version instead. + */ protected void zipFile(File file, ZipOutputStream zOut, String vPath) throws IOException { + zipFile(file, zOut, vPath, ZipFileSet.DEFAULT_FILE_MODE); + } + + /** + * @since Ant 1.6 + */ + protected void zipFile(File file, ZipOutputStream zOut, String vPath, + int mode) + throws IOException { if (file.equals(zipFile)) { throw new BuildException("A zip file cannot include itself", getLocation()); @@ -823,18 +883,31 @@ public class Zip extends MatchingTask { FileInputStream fIn = new FileInputStream(file); try { - zipFile(fIn, zOut, vPath, file.lastModified(), null); + zipFile(fIn, zOut, vPath, file.lastModified(), null, mode); } finally { fIn.close(); } } /** - * Ensure all parent dirs of a given entry have been added. + * @deprecated use five-arg version instead. */ protected void addParentDirs(File baseDir, String entry, ZipOutputStream zOut, String prefix) throws IOException { + addParentDirs(baseDir, entry, zOut, prefix, + ZipFileSet.DEFAULT_DIR_MODE); + } + + /** + * Ensure all parent dirs of a given entry have been added. + * + * @since Ant 1.6 + */ + protected void addParentDirs(File baseDir, String entry, + ZipOutputStream zOut, String prefix, + int dirMode) + throws IOException { if (!doFilesonly) { Stack directories = new Stack(); int slashPos = entry.length(); @@ -855,7 +928,7 @@ public class Zip extends MatchingTask { } else { f = new File(dir); } - zipDir(f, zOut, prefix + dir); + zipDir(f, zOut, prefix + dir, dirMode); } } } @@ -874,10 +947,14 @@ public class Zip extends MatchingTask { String prefix = ""; String fullpath = ""; + int fileMode = ZipFileSet.DEFAULT_FILE_MODE; + int dirMode = ZipFileSet.DEFAULT_DIR_MODE; if (fs instanceof ZipFileSet) { ZipFileSet zfs = (ZipFileSet) fs; prefix = zfs.getPrefix(); fullpath = zfs.getFullpath(); + fileMode = zfs.getFileMode(); + dirMode = zfs.getDirMode(); } if (prefix.length() > 0 @@ -889,10 +966,10 @@ public class Zip extends MatchingTask { // Need to manually add either fullpath's parent directory, or // the prefix directory, to the archive. if (prefix.length() > 0) { - addParentDirs(null, prefix, zOut, ""); - zipDir(null, zOut, prefix); + addParentDirs(null, prefix, zOut, "", dirMode); + zipDir(null, zOut, prefix, dirMode); } else if (fullpath.length() > 0) { - addParentDirs(null, fullpath, zOut, ""); + addParentDirs(null, fullpath, zOut, "", dirMode); } if (fs instanceof ZipFileSet @@ -900,7 +977,7 @@ public class Zip extends MatchingTask { addZipEntries((ZipFileSet) fs, ds, zOut, prefix, fullpath); } else { // Add the fileset. - addFiles(ds, zOut, prefix, fullpath); + addFiles(ds, zOut, prefix, fullpath, dirMode, fileMode); } } } diff --git a/src/main/org/apache/tools/ant/types/ZipFileSet.java b/src/main/org/apache/tools/ant/types/ZipFileSet.java index 5859ad799..870a4f283 100644 --- a/src/main/org/apache/tools/ant/types/ZipFileSet.java +++ b/src/main/org/apache/tools/ant/types/ZipFileSet.java @@ -80,12 +80,28 @@ import org.apache.tools.zip.UnixStat; */ public class ZipFileSet extends FileSet { + /** + * Default value for the dirmode attribute. + * + * @since Ant 1.6 + */ + public static final int DEFAULT_DIR_MODE = + UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM; + + /** + * Default value for the filemode attribute. + * + * @since Ant 1.6 + */ + public static final int DEFAULT_FILE_MODE = + UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM; + private File srcFile = null; private String prefix = ""; private String fullpath = ""; private boolean hasDir = false; - private int fileMode = UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM; - private int dirMode = UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM; + private int fileMode = DEFAULT_FILE_MODE; + private int dirMode = DEFAULT_DIR_MODE; public ZipFileSet() { super(); diff --git a/src/main/org/apache/tools/zip/ZipEntry.java b/src/main/org/apache/tools/zip/ZipEntry.java index 351de763d..06f6797ea 100644 --- a/src/main/org/apache/tools/zip/ZipEntry.java +++ b/src/main/org/apache/tools/zip/ZipEntry.java @@ -198,6 +198,8 @@ public class ZipEntry extends java.util.zip.ZipEntry { */ public void setUnixMode(int mode) { setExternalAttributes((mode << 16) + // MS-DOS read-only attribute + | ((mode & 0200) == 0 ? 1 : 0) // MS-DOS directory flag | (isDirectory() ? 0x10 : 0)); platform = PLATFORM_UNIX; diff --git a/src/testcases/org/apache/tools/zip/ZipEntryTest.java b/src/testcases/org/apache/tools/zip/ZipEntryTest.java index 9633e06ef..db151d654 100644 --- a/src/testcases/org/apache/tools/zip/ZipEntryTest.java +++ b/src/testcases/org/apache/tools/zip/ZipEntryTest.java @@ -131,12 +131,25 @@ public class ZipEntryTest extends TestCase { (ze.getExternalAttributes() >> 16) & 0xFFFF); assertEquals(0, ze.getExternalAttributes() & 0xFFFF); + ze.setUnixMode(0444); + assertEquals(3, ze.getPlatform()); + assertEquals(0444, + (ze.getExternalAttributes() >> 16) & 0xFFFF); + assertEquals(1, ze.getExternalAttributes() & 0xFFFF); + ze = new ZipEntry("foo/"); + assertEquals(0, ze.getPlatform()); + ze.setUnixMode(0777); + assertEquals(3, ze.getPlatform()); + assertEquals(0777, + (ze.getExternalAttributes() >> 16) & 0xFFFF); + assertEquals(0x10, ze.getExternalAttributes() & 0xFFFF); + ze.setUnixMode(0577); assertEquals(3, ze.getPlatform()); assertEquals(0577, (ze.getExternalAttributes() >> 16) & 0xFFFF); - assertEquals(0x10, ze.getExternalAttributes() & 0xFFFF); + assertEquals(0x11, ze.getExternalAttributes() & 0xFFFF); } }