Bug 971802 - Make mozpack capable of storing unix file permissions in jars. r=glandium
authorTed Mielczarek <ted@mielczarek.org>
Thu, 13 Feb 2014 07:47:00 -0500
changeset 185779 cf5d9c8a1368897a58b0d8ccff3e400f78605285
parent 185778 acefe67f49db7628846a2d8cb51b43dc391c8039
child 185780 11fcd667723d2bd9601d6b49c2c3535adb7ed3ea
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersglandium
bugs971802
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 971802 - Make mozpack capable of storing unix file permissions in jars. r=glandium
python/mozbuild/mozpack/copier.py
python/mozbuild/mozpack/files.py
python/mozbuild/mozpack/mozjar.py
--- a/python/mozbuild/mozpack/copier.py
+++ b/python/mozbuild/mozpack/copier.py
@@ -431,17 +431,17 @@ class Jarrer(FileRegistry, BaseFile):
         with JarWriter(fileobj=dest, compress=self.compress,
                        optimize=self.optimize) as jar:
             for path, file in self:
                 if path in old_contents:
                     deflater = DeflaterDest(old_contents[path], self.compress)
                 else:
                     deflater = DeflaterDest(compress=self.compress)
                 file.copy(deflater, skip_if_older)
-                jar.add(path, deflater.deflater)
+                jar.add(path, deflater.deflater, mode=file.mode)
             if self._preload:
                 jar.preload(self._preload)
 
     def open(self):
         raise RuntimeError('unsupported')
 
     def preload(self, paths):
         '''
--- a/python/mozbuild/mozpack/files.py
+++ b/python/mozbuild/mozpack/files.py
@@ -1,14 +1,15 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import errno
 import os
+import platform
 import re
 import shutil
 import stat
 import uuid
 import mozbuild.makeutil as makeutil
 from mozbuild.preprocessor import Preprocessor
 from mozbuild.util import FileAvoidWrite
 from mozpack.executables import (
@@ -160,24 +161,40 @@ class BaseFile(object):
         '''
         Return a file-like object allowing to read() the content of the
         associated file. This is meant to be overloaded in subclasses to return
         a custom file-like object.
         '''
         assert self.path is not None
         return open(self.path, 'rb')
 
+    @property
+    def mode(self):
+        '''
+        Return the file's unix mode, or None if it has no meaning.
+        '''
+        return None
+
 
 class File(BaseFile):
     '''
     File class for plain files.
     '''
     def __init__(self, path):
         self.path = path
 
+    @property
+    def mode(self):
+        '''
+        Return the file's unix mode, as returned by os.stat().st_mode.
+        '''
+        if platform.system() == 'Windows':
+            return None
+        assert self.path is not None
+        return os.stat(self.path).st_mode
 
 class ExecutableFile(File):
     '''
     File class for executable and library files on OS/2, OS/X and ELF systems.
     (see mozpack.executables.is_executable documentation).
     '''
     def copy(self, dest, skip_if_older=True):
         real_dest = dest
--- a/python/mozbuild/mozpack/mozjar.py
+++ b/python/mozbuild/mozpack/mozjar.py
@@ -371,17 +371,17 @@ class JarReader(object):
         if self.is_optimized:
             preload = JarStruct.get_data('uint32', self._data)[0]
         entries = OrderedDict()
         offset = self._cdir_end['cdir_offset']
         for e in xrange(self._cdir_end['cdir_entries']):
             entry = JarCdirEntry(self._data[offset:])
             offset += entry.size
             # Creator host system. 0 is MSDOS, 3 is Unix
-            host = entry['creator_version'] >> 16
+            host = entry['creator_version'] >> 8
             # External attributes values depend on host above. On Unix the
             # higher bits are the stat.st_mode value. On MSDOS, the lower bits
             # are the FAT attributes.
             xattr = entry['external_attr']
             # Skip directories
             if (host == 0 and xattr & 0x10) or (host == 3 and
                                                 xattr & (040000 << 16)):
                 continue
@@ -559,26 +559,28 @@ class JarWriter(object):
         if not self._optimize:
             end['cdir_offset'] = offset
             for entry, _ in self._contents.itervalues():
                 self._data.write(entry.serialize())
         # Store the end of central directory.
         self._data.write(end.serialize())
         self._data.close()
 
-    def add(self, name, data, compress=None):
+    def add(self, name, data, compress=None, mode=None):
         '''
         Add a new member to the jar archive, with the given name and the given
         data.
         The compress option indicates if the given data should be compressed
         (True), not compressed (False), or compressed according to the default
         defined when creating the JarWriter (None).
         When the data should be compressed (True or None with self.compress ==
         True), it is only really compressed if the compressed size is smaller
         than the uncompressed size.
+        The mode option gives the unix permissions that should be stored
+        for the jar entry.
         The given data may be a buffer, a file-like instance, a Deflater or a
         JarFileReader instance. The latter two allow to avoid uncompressing
         data to recompress it.
         '''
         if name in self._contents:
             raise JarWriterError("File %s already in JarWriter" % name)
         if compress is None:
             compress = self._compress
@@ -592,19 +594,22 @@ class JarWriter(object):
             elif hasattr(data, 'read'):
                 data.seek(0)
                 deflater.write(data.read())
             else:
                 raise JarWriterError("Don't know how to handle %s" %
                                      type(data))
         # Fill a central directory entry for this new member.
         entry = JarCdirEntry()
-        # Not storing as created on unix, which avoids having to deal with
-        # st_mode.
         entry['creator_version'] = 20
+        if mode is not None:
+            # Set creator host system (upper byte of creator_version)
+            # to 3 (Unix) so mode is honored when there is one.
+            entry['creator_version'] |= 3 << 8
+            entry['external_attr'] = (mode & 0xFFFF) << 16L
         if deflater.compressed:
             entry['min_version'] = 20  # Version 2.0 supports deflated streams
             entry['general_flag'] = 2  # Max compression
             entry['compression'] = JAR_DEFLATED
         else:
             entry['min_version'] = 10  # Version 1.0 for stored streams
             entry['general_flag'] = 0
             entry['compression'] = JAR_STORED