tools/python/pkglint/userland.py
author Danek Duvall <danek.duvall@oracle.com>
Thu, 07 Apr 2011 15:37:10 -0700
changeset 185 773dda89f186
parent 181 87e11e685b1f
child 186 3adedf0f9f4d
permissions -rw-r--r--
7036473 dependency resolution should happen in one step for all component's packages

#!/usr/bin/python
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#

#
# 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."""

        name = "userland.action"

        def __init__(self, config):
                self.description = _(
                    "checks Userland packages for common content errors")
		path = os.getenv('PROTO_PATH')
		if path != None:
			self.proto_path = path.split()
		else:
			self.proto_path = None
		self.runpath_re = [
			re.compile('^/lib(/.*)?$'),
			re.compile('^/usr/'),
			re.compile('^\$ORIGIN/')
		]
		self.initscript_re = re.compile("^etc/(rc.|init)\.d")
                super(UserlandActionChecker, self).__init__(config)

	def startup(self, engine):
		pass

        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

			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")
                elems = os.path.dirname(path).split("/")

                if ("amd64" in elems) or ("sparcv9" in elems) or ("64" in elems):
                    path64 = True
                else:
                    path64 = False

		if bits == 32 and path64:
			result = _("32-bit object '%s' in 64-bit path")
		elif bits == 64 and not path64:
			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 action.name not in ["file"]:
			return

		path = action.hash
		if path == None or path == 'NOHASH':
			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))
		elif "preserve" in action.attrs:
			if "mode" in action.attrs:
				mode = action.attrs["mode"]
				if (int(mode, 8) & 0222) == 0:
					engine.error(
					_("%(path)s has a preserve action, but is not writable (%(mode)s)") %  {"path": path, "mode": mode},
				msgid="%s%s.4" % (self.name, pkglint_id))
			else:
				engine.error(
				_("%(path)s has a preserve action, but no mode") %  {"path": path, "mode": mode},
				msgid="%s%s.3" % (self.name, pkglint_id))

		# checks that require a physical file to look at
		if self.proto_path is not None:
			for directory in self.proto_path:
				fullpath = directory + "/" + path

				if os.path.exists(fullpath):
					break

			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"]
		if self.initscript_re.match(path):
			engine.warning(
				_("SVR4 startup '%s', deliver SMF"
				  " service instead") % path,
				msgid="%s%s.0" % (self.name, pkglint_id))

	init_script.pkglint_desc = _(
		"SVR4 startup scripts should not be delivered.")

class UserlandManifestChecker(base.ManifestChecker):
        """An opensolaris.org-specific class to check manifests."""

        name = "userland.manifest"

	def __init__(self, config):
		super(UserlandManifestChecker, self).__init__(config)

	def component_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

		for action in manifest.gen_actions_by_type("license"):
			license = True
			break

		if license == False:
			engine.error( _("missing license action"),
				msgid="%s%s.0" % (self.name, pkglint_id))

		if 'opensolaris.arc_url' not in manifest:
			engine.error( _("missing ARC data (opensolaris.arc_url)"),
				msgid="%s%s.0" % (self.name, pkglint_id))

	component_check.pkglint_dest = _(
		"license actions and iARC information are required if you deliver files.")