22840497 pkg publication should not overwrite content-related attributes
authorShawn Walker-Salas <shawn.walker@oracle.com>
Sun, 28 Feb 2016 16:24:18 -0800
changeset 3315 e2377565405f
parent 3314 0c8948c04faa
child 3316 97e1801e1f2c
22840497 pkg publication should not overwrite content-related attributes
src/modules/server/transaction.py
src/publish.py
src/tests/api/t_api_search.py
src/tests/cli/t_pkgrecv.py
src/tests/cli/t_pkgsend.py
--- a/src/modules/server/transaction.py	Wed Mar 02 20:16:17 2016 -0800
+++ b/src/modules/server/transaction.py	Sun Feb 28 16:24:18 2016 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
 #
 
 from __future__ import print_function
@@ -483,9 +483,12 @@
                             digest.get_least_preferred_hash(action)
                         fname = hash_val
 
-                        # Extract ELF information
+                        # Extract ELF information if not already provided.
                         # XXX This needs to be modularized.
-                        if haveelf and data[:4] == "\x7fELF":
+                        if haveelf and data[:4] == "\x7fELF" and (
+                            "elfarch" not in action.attrs or
+                            "elfbits" not in action.attrs or
+                            "elfhash" not in action.attrs):
                                 elf_name = os.path.join(self.dir,
                                     ".temp-{0}".format(fname))
                                 elf_file = open(elf_name, "wb")
--- a/src/publish.py	Wed Mar 02 20:16:17 2016 -0800
+++ b/src/publish.py	Sun Feb 28 16:24:18 2016 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
 #
 
 from __future__ import print_function
@@ -52,6 +52,16 @@
 
 nopub_actions = [ "unknown" ]
 
+# These attributes should always be stripped from input manifests for 'publish';
+# they will be re-calculated during publication.
+strip_attrs = [
+    "elfarch",
+    "elfbits",
+    "elfhash",
+    "pkg.csize",
+    "pkg.size",
+]
+
 def error(text, cmd=None):
         """Emit an error message prefixed by the command name """
 
@@ -422,9 +432,10 @@
                 if a.name == "set" and a.attrs["name"] in ["pkg.fmri", "fmri"]:
                         continue
                 elif a.has_payload:
-                        # Don't trust values provided; forcibly discard these.
-                        a.attrs.pop("pkg.size", None)
-                        a.attrs.pop("pkg.csize", None)
+                        # Forcibly discard content-related attributes to prevent
+                        # errors when reusing manifests with different content.
+                        for attr in strip_attrs:
+                                a.attrs.pop(attr, None)
                         path = pkg.actions.set_action_data(a.hash, a,
                             basedirs=basedirs, bundles=bundles)[0]
                 elif a.name in nopub_actions:
--- a/src/tests/api/t_api_search.py	Wed Mar 02 20:16:17 2016 -0800
+++ b/src/tests/api/t_api_search.py	Sun Feb 28 16:24:18 2016 -0800
@@ -20,7 +20,7 @@
 # CDDL HEADER END
 #
 
-# Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
 
 import testutils
 if __name__ == "__main__":
@@ -47,6 +47,8 @@
 
 
 class TestApiSearchBasics(pkg5unittest.SingleDepotTestCase):
+        # Tests in this suite use the read only data directory.
+        need_ro_data = True
 
         example_pkg10 = """
             open [email protected],5.11-0
@@ -382,7 +384,7 @@
 add dir group=bin mode=0755 owner=root path=usr/share/info
 add dir group=bin mode=0755 owner=root path=usr/share/man
 add dir group=bin mode=0755 owner=root path=usr/share/man/man1
-add file tmp/example_file elfarch=i386 elfbits=32 elfhash=68cca393e816e6adcbac1e8ffe9c618de70413e0 group=bin mode=0555 owner=root path=usr/bin/gmake pkg.size=12
+add file ro_data/elftest.so.1 elfarch=i386 elfbits=32 elfhash=68cca393e816e6adcbac1e8ffe9c618de70413e0 group=bin mode=0555 owner=root path=usr/bin/gmake pkg.size=12
 add file tmp/example_file group=bin mode=0444 owner=root path=usr/share/info/make.info pkg.size=12
 add file tmp/example_file group=bin mode=0444 owner=root path=usr/share/info/make.info-1 pkg.size=12
 add file tmp/example_file group=bin mode=0444 owner=root path=usr/share/info/make.info-2 pkg.size=12
@@ -398,7 +400,7 @@
 
         res_bug_983 = set([
             ("pkg:/[email protected]", "basename", "link path=usr/sfw/bin/gmake target=../../bin/gmake"),
-            ('pkg:/[email protected]', 'basename', 'file a686473102ba73bd7920fc0ab1d97e00a24ed704 chash=f88920ce1f61db185d127ccb32dc8cf401ae7a83 elfarch=i386 elfbits=32 elfhash=68cca393e816e6adcbac1e8ffe9c618de70413e0 group=bin mode=0555 owner=root path=usr/bin/gmake pkg.csize=30 pkg.size=12'),
+            ('pkg:/[email protected]', 'basename', 'file 038cc7a09940928aeac6966331a2f18bc40e7792 chash=a3e76bd1b97b715cddc93b2ad6502b41efa4d833 elfarch=i386 elfbits=32 elfhash=6975080bd79b8ccf8b6ba2e64a83ca67fac3e256 group=bin mode=0555 owner=root path=usr/bin/gmake pkg.csize=1358 pkg.size=3948'),
             ('pkg:/[email protected]', 'gmake - GNU make', 'set name=description value="gmake - GNU make"')
         ])
 
--- a/src/tests/cli/t_pkgrecv.py	Wed Mar 02 20:16:17 2016 -0800
+++ b/src/tests/cli/t_pkgrecv.py	Sun Feb 28 16:24:18 2016 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
 #
 
 import testutils
@@ -49,6 +49,7 @@
 import unittest
 import zlib
 
+from pkg.actions import fromstr
 from pkg.digest import DEFAULT_HASH_FUNC
 from six.moves.urllib.parse import urlparse
 from six.moves.urllib.request import url2pathname
@@ -62,6 +63,8 @@
 class TestPkgrecvMulti(pkg5unittest.ManyDepotTestCase):
         # Cleanup after every test.
         persistent_setup = False
+        # Tests in this suite use the read only data directory.
+        need_ro_data = True
 
         scheme10 = """
             open pkg:/[email protected],5.11-0
@@ -160,6 +163,7 @@
             add license tmp/copyright3 license=copyright
             add file tmp/bronzeA2 mode=0444 owner=root group=bin path=/A1/B2/C3/D4/E5/F6/bronzeA2
             add depend fmri=pkg:/[email protected] type=require
+            add file ro_data/elftest.so.1 mode=0755 owner=root group=bin path=bin/true
             close
         """
 
@@ -1251,9 +1255,9 @@
                 self.pkgrecv(self.dpath1, "-d {0} -n -v \*".format(self.tempdir))
                 expected = """\
 Retrieving packages (dry-run) ...
-        Packages to add:        9
-      Files to retrieve:       17
-Estimated transfer size: 528.00 B
+        Packages to add:       9
+      Files to retrieve:      19
+Estimated transfer size: 3.17 kB
 """
                 self.assert_(expected in self.output, self.output)
                 for s in self.published:
@@ -1267,9 +1271,9 @@
                 self.pkgrecv(self.dpath1, "-a -d {0} -n -v \*".format(self.tempdir))
                 expected = """\
 Archiving packages (dry-run) ...
-        Packages to add:        9
-      Files to retrieve:       17
-Estimated transfer size: 528.00 B
+        Packages to add:       9
+      Files to retrieve:      19
+Estimated transfer size: 3.17 kB
 """
                 self.assert_(expected in self.output, self.output)
                 for s in self.published:
@@ -1282,9 +1286,9 @@
                    .format(self.tempdir))
                 expected = """\
 Retrieving packages (dry-run) ...
-        Packages to add:        9
-      Files to retrieve:       17
-Estimated transfer size: 528.00 B
+        Packages to add:       9
+      Files to retrieve:      19
+Estimated transfer size: 3.17 kB
 """
                 self.assert_(expected in self.output, self.output)
                 for s in self.published:
@@ -1423,6 +1427,77 @@
                 self.pkgrepo("verify -s {0} --disable dependency"
                     .format(self.dpath6))
 
+        def test_15_content_attrs(self):
+                """Ensure that relevant content-related attributes will not be
+                modified by pkgrecv.  This is important if changes are made to
+                how some attributes are calculated in the future and
+                modifications would invalidate signatures."""
+
+                #
+                # For now, this only needs to test 'elfhash'.
+                #
+                mfpath = os.path.join(self.test_root, "content-attrs.p5m")
+                with open(mfpath, "wb") as mf:
+                        mf.write("""\
+set name=pkg.fmri value=pkg://test/[email protected]
+file elftest.so.1 mode=0755 owner=root group=bin path=bin/true
+""")
+
+                # Create a repository and publish sample package.
+                rpath = tempfile.mkdtemp(dir=self.test_root)
+                self.create_repo(rpath)
+                ret, pfmri = self.pkgsend(rpath,
+                    "publish -d {0} {1}".format(self.ro_data_root, mfpath))
+                self.pkgrepo("list -s {0}".format(rpath))
+
+                # Now get the actual manifest and get current elfhash value.
+                orepo = repo.Repository(root=rpath)
+                rmpath = orepo.manifest(pfmri)
+                rm = manifest.Manifest()
+                rm.set_content(pathname=rmpath)
+                ract = list(rm.gen_actions_by_type('file'))[0]
+                oelfhash = ract.attrs["elfhash"]
+
+                # Create a new repository and pkgrecv package to that one so
+                # that we can safely modify it in place.
+                nrpath = tempfile.mkdtemp(dir=self.test_root)
+                self.create_repo(nrpath)
+                self.pkgrecv(rpath, "-d {0} \*".format(nrpath))
+                nrepo = repo.Repository(root=nrpath)
+                nmpath = nrepo.manifest(pfmri)
+                nm = manifest.Manifest()
+                nmcontent = rm.tostr_unsorted().replace(
+                    "elfhash=", "elfhash=42.")
+                nm.set_content(nmcontent)
+                nm.store(nmpath)
+                # Modifying the manifest requires a catalog rebuild.
+                self.pkgrepo("rebuild --no-index -s {0}".format(nrpath))
+
+                # Now create another repository and pkgrecv package *without*
+                # using --clone to that one and verify that elfhash remains
+                # unchanged from previous repository version.
+                trpath = tempfile.mkdtemp(dir=self.test_root)
+                self.create_repo(trpath)
+                self.pkgrecv(nrpath, "-d {0} \*".format(trpath))
+                trepo = repo.Repository(root=trpath)
+                tmpath = trepo.manifest(pfmri)
+                tm = manifest.Manifest()
+                tm.set_content(pathname=tmpath)
+                tact = list(tm.gen_actions_by_type('file'))[0]
+                self.assertEqual("42." + oelfhash, tact.attrs["elfhash"])
+
+                # Do the same thing again, but use --clone this time.
+                trpath = tempfile.mkdtemp(dir=self.test_root)
+                self.create_repo(trpath)
+                self.pkgrecv(nrpath, "--clone -d {0} -p \*".format(trpath))
+                trepo = repo.Repository(root=trpath)
+                tmpath = trepo.manifest(pfmri)
+                tm = manifest.Manifest()
+                tm.set_content(pathname=tmpath)
+                tact = list(tm.gen_actions_by_type('file'))[0]
+                self.assertEqual("42." + oelfhash, tact.attrs["elfhash"])
+
+
 class TestPkgrecvHTTPS(pkg5unittest.HTTPSTestClass):
 
         example_pkg10 = """
--- a/src/tests/cli/t_pkgsend.py	Wed Mar 02 20:16:17 2016 -0800
+++ b/src/tests/cli/t_pkgsend.py	Sun Feb 28 16:24:18 2016 -0800
@@ -20,7 +20,7 @@
 # CDDL HEADER END
 #
 
-# Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
 
 import testutils
 if __name__ == "__main__":
@@ -36,6 +36,7 @@
 import stat
 import tempfile
 import unittest
+import six
 from six.moves import range
 from six.moves.urllib.error import HTTPError
 from six.moves.urllib.request import urlopen, Request, pathname2url
@@ -54,6 +55,8 @@
 
 class TestPkgsendBasics(pkg5unittest.SingleDepotTestCase):
         persistent_setup = False
+        # Tests in this suite use the read only data directory.
+        need_ro_data = True
 
         def setUp(self):
                 # This test suite needs an actual depot.
@@ -1406,6 +1409,53 @@
                 self.assertEqualDiff(self.reduceSpaces(expected),
                     self.reduceSpaces(actual))
 
+        def test_28_content_attrs(self):
+                """Ensure that content-related attributes are ignored for
+                'pgksend publish'."""
+
+                mfpath = os.path.join(self.test_root, "content-attrs.p5m")
+                with open(mfpath, "wb") as mf:
+                        mf.write("""\
+set name=pkg.fmri value=pkg://test/[email protected]
+file elftest.so.1 mode=0755 owner=root group=bin path=bin/true pkg.size=ignored pkg.csize=ignored elfhash=ignored elfbits=ignored elfarch=ignored
+""")
+
+                # Verify pkgsend publish can be performed even though values for
+                # content-related attributes are invalid.  They should be
+                # forcibly dropped and added back.
+                ret, pfmri = self.pkgsend(self.dc.get_depot_url(),
+                    "publish -d {0} {1}".format(self.ro_data_root, mfpath))
+
+                # Now get the actual manifest and ensure all attributes were
+                # overwritten.
+                repo = self.dc.get_repo()
+                rmpath = repo.manifest(pfmri)
+                rm = manifest.Manifest()
+                rm.set_content(pathname=rmpath)
+                a = list(rm.gen_actions_by_type('file'))[0]
+
+                # These attributes should always match pre-determined values.
+                expected = {
+                    'elfarch': 'i386',
+                    'elfbits': '32',
+                    'group': 'bin',
+                    'mode': '0755',
+                    'owner': 'root',
+                    'path': 'bin/true',
+                    'pkg.csize': '1358',
+                    'pkg.size': '3948'
+                }
+                actual = dict(
+                    (k, v) for (k, v) in six.iteritems(a.attrs)
+                    if k in expected
+                )
+                self.assertEqualDiff(expected, actual)
+
+                # 'elfhash' can vary depending upon whether we've changed the
+                # content-hashing algorithms, so we only verify it exists and
+                # doesn't have a value of 'ignored'.
+                self.assertNotEqual(a.attrs['elfhash'], 'ignored')
+
 
 class TestPkgsendHardlinks(pkg5unittest.CliTestCase):