aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerging.java13
-rw-r--r--src/tools/xcode-common/java/com/google/devtools/build/xcode/zip/ZipFiles.java153
2 files changed, 163 insertions, 3 deletions
diff --git a/src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerging.java b/src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerging.java
index 20b8b72c7d..69c00d3f9c 100644
--- a/src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerging.java
+++ b/src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerging.java
@@ -30,6 +30,7 @@ import com.google.devtools.build.xcode.common.Platform;
import com.google.devtools.build.xcode.common.TargetDeviceFamily;
import com.google.devtools.build.xcode.plmerge.KeysToRemoveIfEmptyString;
import com.google.devtools.build.xcode.plmerge.PlistMerging;
+import com.google.devtools.build.xcode.zip.ZipFiles;
import com.google.devtools.build.xcode.zip.ZipInputEntry;
import java.io.IOException;
@@ -37,6 +38,7 @@ import java.io.OutputStream;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@@ -184,15 +186,20 @@ public final class BundleMerging {
*/
private void addEntriesFromOtherZip(ZipCombiner combiner, Path sourceZip, String entryNamesPrefix)
throws IOException {
+ Map<String, Integer> externalFileAttributes = ZipFiles.unixExternalFileAttributes(sourceZip);
try (ZipInputStream zipIn = new ZipInputStream(Files.newInputStream(sourceZip))) {
while (true) {
ZipEntry zipInEntry = zipIn.getNextEntry();
if (zipInEntry == null) {
break;
}
- // TODO(bazel-team): preserve the external file attribute field in the source zip entry.
- combiner.addFile(entryNamesPrefix + zipInEntry.getName(), DOS_EPOCH, zipIn,
- ZipInputEntry.DEFAULT_DIRECTORY_ENTRY_INFO);
+ Integer externalFileAttr = externalFileAttributes.get(zipInEntry.getName());
+ if (externalFileAttr == null) {
+ externalFileAttr = ZipInputEntry.DEFAULT_EXTERNAL_FILE_ATTRIBUTE;
+ }
+ combiner.addFile(
+ entryNamesPrefix + zipInEntry.getName(), DOS_EPOCH, zipIn,
+ ZipInputEntry.DEFAULT_DIRECTORY_ENTRY_INFO.withExternalFileAttribute(externalFileAttr));
}
}
}
diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/zip/ZipFiles.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/zip/ZipFiles.java
new file mode 100644
index 0000000000..21d64699f7
--- /dev/null
+++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/zip/ZipFiles.java
@@ -0,0 +1,153 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Licensed 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 com.google.devtools.build.xcode.zip;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+
+/**
+ * Utility code for reading information from zip files.
+ */
+public final class ZipFiles {
+ /** Read a little-endian integer comprised of {@code count} bytes from the input channel. */
+ private static int readBytes(int count, ReadableByteChannel input) throws IOException {
+ ByteBuffer buffer = ByteBuffer.allocate(count);
+ if (input.read(buffer) != count) {
+ throw new IOException("could not read expected number of bytes: " + count);
+ }
+ int result = 0;
+ for (int i = count - 1; i >= 0; i--) {
+ result <<= 8;
+ result |= buffer.get(i) & 0xff;
+ }
+ return result;
+ }
+
+ /**
+ * Returns the external file attributes of each entry as a mapping from the entry name to the
+ * 32-bit value. As long as the attributes are generated by a Unix host, this includes the POSIX
+ * file permissions in the upper two bytes. Entries not generated by a Unix host are not included
+ * in the result.
+ */
+ public static Map<String, Integer> unixExternalFileAttributes(Path zipFile) throws IOException {
+ // Field descriptions in comments were taken from this document:
+ // http://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
+ ImmutableMap.Builder<String, Integer> attributes = new ImmutableMap.Builder<>();
+ try (SeekableByteChannel input = Files.newByteChannel(zipFile)) {
+ // The data we care about is toward the end of the file, after the compressed data for each
+ // file. We begin by looking for the start of the end of central directory record, which is
+ // marked by the signature 0x06054b50
+ //
+ // This contains the centralDirectoryStartOffset value, which tells us where to seek to find
+ // the first central directory entry. Each such entry is marked by the signature 0x02014b50
+ // and appear in sequence, one entry for each file in the .zip.
+ //
+ // The central directory entry contains many values, including the file name, the external
+ // file attributes, and the version made by value. If the version made by indicates a Unix
+ // host (0x03??), we include the external file attributes in the returned map.
+
+ long offset = input.size() - 4;
+ while (offset >= 0) {
+ input.position(offset);
+ int signature = readBytes(4, input);
+ if (signature == 0x06054b50) {
+ break;
+ } else if (signature == 0x06064b50) {
+ throw new IOException("Zip64 format not supported: " + zipFile);
+ }
+ offset--;
+ }
+ if (offset < 0) {
+ throw new IOException();
+ }
+
+ // Read end of central directory structure
+ input.position(input.position()
+ + 2 // number of this disk
+ + 2 // number of the disk with the start of the central directory
+ );
+ int entryCount = readBytes(2, input);
+ input.position(input.position()
+ + 2 // total number of entries in the central directory
+ );
+ input.position(input.position()
+ + 4 // size of the central directory
+ );
+ int centralDirectoryStartOffset = readBytes(4, input);
+ if (0xffffffff == centralDirectoryStartOffset) {
+ throw new IOException("Zip64 format not supported.");
+ }
+
+ input.position(centralDirectoryStartOffset);
+ int entriesFound = 0;
+
+ // Read each central directory entry
+ while ((entriesFound < entryCount) && (readBytes(4, input) == 0x02014b50)) {
+ int versionMadeBy = readBytes(2, input);
+ input.position(input.position()
+ + 2 // version needed to extract
+ + 2 // general purpose bit flag
+ + 2 // compression method
+ + 2 // last mod file time
+ + 2 // last mod file date
+ + 4 // crc-32
+ + 4 // compressed size
+ + 4 // uncompressed size
+ );
+ int filenameLength = readBytes(2, input);
+ int extraFieldLength = readBytes(2, input);
+ int fileCommentLength = readBytes(2, input);
+ input.position(input.position()
+ + 2 // disk number start
+ + 2 // internal file attributes
+ );
+ int externalFileAttributes = readBytes(4, input);
+ input.position(input.position()
+ + 4 // relative offset of local header
+ );
+ ByteBuffer filenameBuffer = ByteBuffer.allocate(filenameLength);
+ if (filenameLength != input.read(filenameBuffer)) {
+ throw new IOException(
+ String.format(
+ "Could not read file name (length %d) in central directory record",
+ filenameLength));
+ }
+ input.position(input.position() + extraFieldLength + fileCommentLength);
+ entriesFound++;
+ if ((versionMadeBy >> 8) == 3) {
+ // Zip made by a Unix host - the external file attributes are POSIX permissions.
+ String filename = new String(filenameBuffer.array(), StandardCharsets.UTF_8);
+ attributes.put(filename, externalFileAttributes);
+ }
+ }
+ if (entriesFound != entryCount) {
+ System.err.printf(
+ "WARNING: Expected %d entries in central directory record in '%s', but found %d\n",
+ entryCount, zipFile, entriesFound);
+ }
+ }
+ return attributes.build();
+ }
+
+ private ZipFiles() {}
+}