7000952 Userland package validation needs some love
authorNorm Jacobs <Norm.Jacobs@Oracle.COM>
Thu, 10 Feb 2011 19:16:15 -0800
changeset 84 b80cfd4e0a16
parent 83 9ab0deb97868
child 85 8098282b503b
7000952 Userland package validation needs some love
make-rules/ips.mk
tools/python/pkglint/userland.py
--- a/make-rules/ips.mk	Thu Feb 10 10:54:36 2011 -0800
+++ b/make-rules/ips.mk	Thu Feb 10 19:16:15 2011 -0800
@@ -130,9 +130,9 @@
 # lint the manifest before we publish with it.
 $(MANIFEST_BASE)-%.linted:	$(MANIFEST_BASE)-%.resolved
 	@echo "VALIDATING MANIFEST CONTENT: $<"
-	PYTHONPATH=$(WS_TOOLS)/python $(PKGLINT) \
-		$(CANONICAL_REPO:%=-c $(WS_LINT_CACHE)) \
-		-f $(WS_TOOLS)/pkglintrc $<
+	$(ENV) PYTHONPATH=$(WS_TOOLS)/python PROTO_DIR=$(PROTO_DIR) \
+		$(PKGLINT) $(CANONICAL_REPO:%=-c $(WS_LINT_CACHE)) \
+			-f $(WS_TOOLS)/pkglintrc $<
 	$(PKGFMT) <$< >[email protected]
 
 # published
--- a/tools/python/pkglint/userland.py	Thu Feb 10 10:54:36 2011 -0800
+++ b/tools/python/pkglint/userland.py	Thu Feb 10 19:16:15 2011 -0800
@@ -21,15 +21,17 @@
 #
 
 #
-# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
 #
 
 # Some userland consolidation specific lint checks
 
 import pkg.lint.base as base
+import pkg.elf as elf
 import re
 import os.path
 
+
 class UserlandActionChecker(base.ActionChecker):
         """An opensolaris.org-specific class to check actions."""
 
@@ -44,64 +46,151 @@
 			re.compile('^/usr/'),
 			re.compile('^\$ORIGIN/')
 		]
+		self.initscript_re = re.compile("^etc/(rc.|init)\.d")
                 super(UserlandActionChecker, self).__init__(config)
 
 	def startup(self, engine):
 		if self.prototype != None:
-			engine.info(_("including prototype checks: %s") % self.prototype, msgid=self.name)
+			engine.info(_("including prototype checks: %s") %
+					self.prototype, msgid=self.name)
+
+        def __realpath(self, path, target):
+		"""Combine path and target to get the real path."""
+
+		result = os.path.dirname(path)
+
+		for frag in target.split(os.sep):
+			if frag == '..':
+				result = os.path.dirname(result)
+			elif frag == '.':
+				pass
+			else:
+				result = os.path.join(result, frag)
+
+		return result
+
+	def __elf_runpath_check(self, path):
+		result = None
+		list = []
+
+		ed = elf.get_dynamic(path)
+		for dir in ed.get("runpath", "").split(":"):
+			if dir == None or dir == '':
+				continue
 
-	def file_exists(self, action, manifest, engine, pkglint_id="001"):
+			match = False
+			for expr in self.runpath_re:
+				if expr.match(dir):
+					match = True
+					break
+
+			if match == False:
+				list.append(dir)
+
+		if len(list) > 0:
+			result = _("bad RUNPATH, '%%s' includes '%s'" %
+				   ":".join(list))
+
+		return result
+
+	def __elf_wrong_location_check(self, path):
+		result = None
+
+		ei = elf.get_info(path)
+		bits = ei.get("bits")
+		frag = os.path.basename(os.path.dirname(path))
+
+		if bits == 32 and frag in ["sparcv9", "amd64", "64"]:
+			result = _("32-bit object '%s' in 64-bit path")
+		elif bits == 64 and frag not in ["sparcv9", "amd64", "64"]:
+			result = _("64-bit object '%s' in 32-bit path")
+
+		return result
+
+	def file_action(self, action, manifest, engine, pkglint_id="001"):
 		"""Checks for existence in the proto area."""
 
-		if self.prototype is None:
+		if action.name not in ["file"]:
 			return
 
+		path = action.attrs["path"]
+
+		# check for writable files without a preserve attribute
+		if 'mode' in action.attrs:
+			mode = action.attrs["mode"]
+
+			if (int(mode, 8) & 0222) != 0 and "preserve" not in action.attrs:
+				engine.error(
+				_("%(path)s is writable (%(mode)s), but missing a preserve"
+				  " attribute") %  {"path": path, "mode": mode},
+				msgid="%s%s.0" % (self.name, pkglint_id))
+
+		# checks that require a physical file to look at
+		if self.prototype is not None:
+			fullpath = self.prototype + "/" + path
+
+			if not os.path.exists(fullpath):
+				engine.info(
+					_("%s missing from proto area, skipping"
+					  " content checks") % path, 
+					msgid="%s%s.1" % (self.name, pkglint_id))
+			elif elf.is_elf_object(fullpath):
+				# 32/64 bit in wrong place
+				result = self.__elf_wrong_location_check(fullpath)
+				if result != None:
+					engine.error(result % path, 
+						msgid="%s%s.2" % (self.name, pkglint_id))
+				result = self.__elf_runpath_check(fullpath)
+				if result != None:
+					engine.error(result % path, 
+						msgid="%s%s.3" % (self.name, pkglint_id))
+
+	file_action.pkglint_desc = _("Paths should exist in the proto area.")
+
+	def link_resolves(self, action, manifest, engine, pkglint_id="002"):
+		"""Checks for link resolution."""
+
+		if action.name not in ["link", "hardlink"]:
+			return
+
+		path = action.attrs["path"]
+		target = action.attrs["target"]
+		realtarget = self.__realpath(path, target)
+		
+		resolved = False
+		for maction in manifest.gen_actions():
+			mpath = None
+			if maction.name in ["dir", "file", "link",
+						"hardlink"]:
+				mpath = maction.attrs["path"]
+
+			if mpath and mpath == realtarget:
+				resolved = True
+				break
+
+		if resolved != True:
+			engine.error(
+				_("%s %s has unresolvable target '%s'") %
+					(action.name, path, target),
+				msgid="%s%s.0" % (self.name, pkglint_id))
+
+	link_resolves.pkglint_desc = _("links should resolve.")
+
+	def init_script(self, action, manifest, engine, pkglint_id="003"):
+		"""Checks for SVR4 startup scripts."""
+
 		if action.name not in ["file", "dir", "link", "hardlink"]:
 			return
 
 		path = action.attrs["path"]
-		fullpath = self.prototype + "/" + path
-		if not os.path.exists(fullpath):
-			engine.error(
-				_("packaged path '%s' missing from proto area") % path, 
+		if self.initscript_re.match(path):
+			engine.warning(
+				_("SVR4 startup '%s', deliver SMF"
+				  " service instead") % path,
 				msgid="%s%s.0" % (self.name, pkglint_id))
 
-	file_exists.pkglint_desc = _("Paths should exist in the proto area.")
-
-	def file_content(self, action, manifest, engine, pkglint_id="002"):
-		"""Checks for file content issues."""
-
-		if self.prototype is None:
-			return
-
-		if action.name is not "file":
-			return
-
-		import pkg.elf as elf
-
-		path = action.attrs["path"]
-		fullpath = self.prototype + "/" + path
-
-		if elf.is_elf_object(fullpath):
-			ed = elf.get_dynamic(fullpath)
-			for dir in ed.get("runpath", "").split(":"):
-				if dir == None or dir == '':
-					continue
-
-				match = False
-				for expr in self.runpath_re:
-					if expr.match(dir):
-						match = True
-						break
-
-				if match == False:
-					engine.error(
-						_("%s has bad RUNPATH, "
-					  	"includes: %s") % (path, dir),
-						msgid="%s%s.1" % (self.name, pkglint_id))
-		# additional checks for different content types should go here
-
-	file_content.pkglint_desc = _("Paths should not deliver common mistakes.")
+	init_script.pkglint_desc = _(
+		"SVR4 startup scripts should not be delivered.")
 
 class UserlandManifestChecker(base.ManifestChecker):
         """An opensolaris.org-specific class to check manifests."""
@@ -112,27 +201,23 @@
 		self.prototype = os.getenv('PROTO_DIR')
 		super(UserlandManifestChecker, self).__init__(config)
 
-	def unpackaged_files(self, manifest, engine, pkglint_id="001"):
-		if self.prototype == None:
+	def license_check(self, manifest, engine, pkglint_id="001"):
+		manifest_paths = []
+		files = False
+		license = False
+
+		for action in manifest.gen_actions_by_type("file"):
+			files = True
+			break
+
+		if files == False:
 			return
 
-		return	# skip this check for now.  It needs more work
-
-		manifest_paths = []
-
-		for action in manifest.gen_actions():
-			if action.name in ["file", "dir", "link", "hardlink"]:
-				manifest_paths.append(action.attrs.get("path"))
+		for action in manifest.gen_actions_by_type("license"):
+			return
 
-		for dirname, dirnames, filenames in os.walk(self.prototype):
-			dir = dirname[len(self.prototype):]
-			for name in filenames + dirnames:
-				path = dir + '/' + name
-				if path not in manifest_paths:
-					engine.error(
-						_("unpackaged path '%s' missing from manifest") % path, 
-					msgid="%s%s.0" % (self.name, pkglint_id))
+		engine.error( _("missing license action"),
+			msgid="%s%s.0" % (self.name, pkglint_id))
 
-	unpackaged_files.pkglint_dest = _(
-		"Prototype paths should be present.")
-
+	license_check.pkglint_dest = _(
+		"license actions are required if you deliver files.")